Redis 基础

redis的优点?

  • 非常快
  • 支持丰富的数据类型
  • 操作具有原子性

Redis适用场景?

  • 缓存:减轻查询压力,提升系统性能
  • 分布式锁:保证数据准确性、避免不同节点重复工作
  • Session共享:解决集群服务模式下,用户单点登陆问题
  • 排行榜:利用SortSet实现
  • 消息队列:可以利用List实现一个消息队列

redis的数据结构?

String、Hash、List、Set、SortedSet。

  • String:redis中的字符串是一种动态字符串,可以修改,底层实现有点类似于 Java 中的 ArrayList。基本操作:set、get、mset、mget
  • List:类似Java中的LinkedList,它是链表而不是数组,因此插入删除非常快,但是搜索定位比较慢。基本操作:rpush、rpush、LPOP、RPOP、lrange
  • Hash:当于 Java 中的 HashMap,内部实现也差不多类似,都是通过 "数组 + 链表" 的链地址法来解决部分 哈希冲突。大字典扩容比较耗时,redis使用渐进式rehash,即同时保留两个字典,同时检索两个字典,当rehash完成后,检索新的字典,扩容为原来的两倍。基本操作:HSET、HGET。HSET books java "think in java"
  • set:相当于 Java 语言中的 HashSet,它内部的键值对是无序、唯一的。它的内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值 NULL。
  • hset:它类似于 Java 中 SortedSetHashMap 的结合体,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以为每个 value 赋予一个 score 值,用来代表排序的权重。它由跳跃表实现的。常见操作:ZADD(添加)、ZRANGE(按score排名,取出范围)、ZREVRANGE(按score倒序排名,取出范围)、ZCARD(相当于count)、ZSCORE(获取指定的value的score)

如果有大量的key需要设置过期时间,需要注意什么?

如果大量的key过期时间设置的过于集中,到过期的那个时间点,Redis可能会出现短暂的卡顿现象。严重的话会出现缓存雪崩,我们一般需要在时间上加一个随机值,使得过期时间分散一些。

那你使用过Redis分布式锁吗,怎么使用的?

先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放,setnx指令可以同时指定过期时间。

如果时间过期后,线程还没有执行完成,我们可以使用redission框架来做分布式锁,同时用看门狗机制,在定时检查加锁线程是否完成,如果没有完成,自动给锁续加锁时间。

redis正在给线上的业务提供服务,那使用keys指令会有什么问题?

Redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。

这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

使用过Redis怎么做异步消息队列?

用list作为队列结构,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试,或者用blpop阻塞到有消息到来。

Redis做异步消息队列可以多次消费吗?

可以用pub/sub主题模式,实现广播。

Redis做pub/Sub的缺点?

redis作为pub/sub做消息广播,有丢消息的风险。

由于redis缓存保护机制,对于Pub/Sub客户端,大小限制是8M,当输出缓冲区超过8M时,会关闭连接。持续性限制是,当客户端缓冲区大小持续60秒超过2M,关闭客户端连接,导致消息丢失。

redis实现延时队列?

使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。

redis两种持久化方式?

RDB做镜像全量持久化,AOF做增量持久化。因为RDB会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要AOF来配合使用。在redis实例重启时,会使用RDB持久化文件重新构建内存,再使用AOF重放近期的操作指令来实现完整恢复重启之前的状态。

redis两种持久化方式的优缺点?

  • RDB:
    • 优点:RDB对redis性能影响非常小,在持久化数据时会fork一个子进程进行持久化,而且在恢复数据时比AOF快。
    • 缺点:RDB是快照文件,默认是五分钟发生一次,如果redis实例掉电,可能发生五分钟数据丢失。AOF丢失的数据会少很多。RDB文件如果过大,可能导致客户端暂停几毫秒甚至几百毫秒。
  • AOP:
    • 优点:AOF可以基本不丢失数据,三种持久化模式,同步写回、每秒写回、操作系统控制的写回。由于是追加写,所以写入的性能很高。
    • 缺点:AOF的数据文件比RDB还要大。
  • 两者怎么选择?
    • 由于两者针对的场景不一样,所以两者都要使用,首先使用RDB对数据快速恢复,然后使用AOF对恢复的数据进行补全。

redis机器突然宕机会怎么样?

取决于AOF日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。

RDB的原理是什么?

RDB有两种持久化方式,同步的save会阻塞。异步的bgsave不会阻塞。

redis是使用bgsave + fork + copy on write 来实现的。首先使用异步持久化,然后fork出一个子进程进行持久化。fork子进程后,会将主进程的内存权限设置为read-only,当主进程发生write时,会将写的区域复制一份出来,写新的区域,从而不影响子进程复制。

redis 使用pipeline的好处?

不适用pipeline时,是一条命令一次执行一条返回,需要一个rtt。使用pipeline可以在一个rtt中执行多条命令。pipeline对应可靠性要求高的系统,每次写入都要求返回的系统不适用。适用于批量写入,且允许少量失败的场景。

redis同步机制?

redis做主从同步分为两部分,一部分是全量同步,一部分是增量同步。当从节点连接加入集群后,会发起同步命令, 此时主节点会做一次bgsave,后续的操作会写入内存buffer中,然后将RDB文件全量同步给从节点,从节点将RDB镜像加载到内存中。加载完成后将修改期间的操作同步到子节点重放,完成同步。后续的增量数据通过AOF日志同步即可。

如果同步时网络中断,或者服务宕机了,重连后会把缺少的数据补齐。

redis集群方案?

基于高可用的 Redis Sentinal模式,在马上宕机后,将slave提升为master继续服务。

基于扩展性的 Redis Cluster模式,在单个redis内存不足时,使用Cluster进行分片存储。

redis 为什么那么快?

redis采用的是基于内存的单线程模型的KV数据库,由C语言编写的,能提供10+万的QPS。

  • 完全基于内存,绝大部分请求都是存粹的内存操作,速度非常快。
  • 数据结构简单,对数据操作也简单,redis中的数据结构是专门设计的。
  • 采用单线程,避免了不必要的上下文切换,也不存在多线程切换带来的CPU消耗,不用考虑锁,也不会出现死锁等导致性能消耗。
  • 使用多路I/O复用模式,非阻塞IO。

什么是上下文切换?

线程时间片用完或者执行结束,会放弃CPU,此时执行的上下文会被保存起来。新获取到CPU资源的线程会将原来执行中断保存下来的的数据恢复到CPU中继续执行,这就是上下文切换。

单线程redis在多核服务器上是否很浪费?

是的,可以在单机上部署多个redis实例。

redis单机瓶颈怎么解决?

使用 cluster 集群部署,cluster 集群下挂载多个master节点,每个master下挂载多个slave节点。如果要进行横线扩展,只需要添加master节点即可。

此时集群使用的是主从同步,读写分离。

讲讲sentinel哨兵机制?

哨兵模式要求至少有三个实例,一个master节点,两个slave节点,每个节点上都有一个哨兵服务。master提供写服务,slave提供读服务,当master接待宕机,会将一个slave节点提升为master节点进行,保证集群的高可用。

哨兵服务的组要作用:

  • 集群监控:监控master、slave实例是否正常工作。
  • 消息通知:如果某个redis实例有故障,哨兵服务负责发送报警消息给管理员。
  • 故障转移:如果master节点宕机,会自动转移到slave节点上。
  • 配置中心:故障发生后,通知client客户端新的master地址。

redis过期策略?

  • 定期删除:定期随机抽取一些设置了过期时间的key,检查是否过期,过期了就删除。

  • 惰性删除:不主动删除,当有查询时,检查是否过期,没有过期返回,过期了直接删除。

redis的内存淘汰机制?

  • 返回错误(noeviction):当内存达到限制,再次尝试申请内存时,返回错误。
  • 回收最少使用:
    • allkeys-lru:尝试在所有的keys里,回收最少使用的key。
    • volatile-lru:尝试在过期集合中,回收最少使用的key。
  • 随机回收:
    • allkeys-random:尝试在所有的keys里,随机回收。
    • volatile-random:尝试在过期集合中,随机回收。
  • 回收在过期集合里存活时间短的key(volatile-ttl)

布隆过滤器?

布隆过滤器实际上是一个很长的二进制向量和一系列随机映射函数组成。作用是检索一个元素是否在集合中。优点是查询的空间效率和时间效率很高,缺点是有一定的误识别率和删除困难。

原理:当一个元素加入集合时,通过N个散列函数将元素映射成一个位数组中的N个点,把他们设置为1。检索时,如果有任意一个点是0,那么被检索元素一定不在集合中,否则这个元素可能在集合中。

缺点:

  • 存在误判,即所有散列位上都是1,但是查找的元素并不在容器中。如果布隆过滤器是黑名单,则可以建立一个白名单来存储可能会误判的元素。
  • 删除困难,bit位上存储的是1,不能简单直接置为0,可能会影响其他元素的判断。

二进制向量长度:插入元素个数和误判率确定。

散列函数个数:散列函数个数由于二进制向量长度n /插入元素的个数的m共同决定。·

为什么需要分布式事务锁?

在分布式或者集群环境下,避免不同节点重复相同的工作,比如多个节点发送多条消息、避免破坏数据的正确性,比如两个节点同时操作一条数据。

java中常见的实现方式?

  • 基于 MySQL 中的锁:MySQL 本身有自带的悲观锁 for update 关键字。
  • 基于 Zookeeper 有序节点:Zookeeper 允许临时创建有序的子节点,这样客户端获取节点列表时,就能够当前子节点列表中的序号判断是否能够获得锁。
  • 基于 Redis 的单线程:由于 Redis 是单线程,所以命令会以串行的方式执行,并且本身提供了像 SETNX(set if not exists) 这样的指令,本身具有互斥性。

什么缓存穿透?怎么解决?

查询一个一定不存在的数据,由于缓存没有命中,会去查询数据,如果查不到数据,则不会写缓存,这导致每次请求都会到数据库,造成缓存穿透。

解决办法:

  • 缓存空对象:如果查询为空,仍然将空结果做短时间缓存。同时会导致缓存和存储的数据有一段时间窗口不一致,缓存空后,又写入了数据。此时可以在写入时,去掉缓存中的空对象。
  • 布隆过滤器:如果布隆过滤器判断不存在的对象,一定不存在,从而避免缓存穿透。

什么是缓存雪崩?改如何解决?

如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。

解决办法:

  • 加锁排队:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待
  • 数据预热:可以通过缓存 reload 机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的 key,设置不同的过期时间,让缓存失效的时间点尽量均匀
  • 做二级缓存,或者双缓存策略:Cache1 为原始缓存,Cache2 为拷贝缓存,Cache1 失效时,可以访问 Cache2,Cache1 缓存失效时间设置为短期,Cache2 设置为长期。
  • 缓存过期随机值:在缓存的时候给过期时间加上一个随机值,这样就会大幅度的减少缓存在同一时间过期。

怎么保证缓存和数据库数据的一致性?

  • 设置合理的过期时间,避免直接修改数据库。
  • 新增、更改、删除数据库操作时同步更新 Redis,可以使用事物机制来保证数据的一致性。

什么是 RDB 内存快照?

在 Redis 执行「写」指令过程中,内存数据会一直变化。所谓的内存快照,指的就是 Redis 内存中的数据在某一刻的状态数据。

在生成 RDB 期间,Redis 可以同时处理写请求么?

可以的,Redis 使用操作系统的多进程写时复制技术 COW(Copy On Write) 来实现快照持久化,保证数据一致性。

Redis 在持久化时会fork`一个子进程,快照持久化完全交给子进程来处理,父进程继续处理客户端请求。

当主线程执行写指令修改数据的时候,这个数据就会复制一份副本, bgsave 子进程读取这个副本数据写到 RDB 文件。

这既保证了快照的完整性,也允许主线程同时对数据进行修改,避免了对正常业务的影响。

如何实现数据尽可能少丢失又能兼顾性能呢?

使用混合持久化,将 rdb 文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是自持久化开始到持久化结束的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小。

于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。

什么是 Cluster 集群?

Redis 集群是一种分布式数据库方案,集群通过分片来进行数据管理,并提供复制和故障转移功能。

将数据划分为若干个slots,每个节点负责一部分槽位。槽位的信息存储于每个节点中。

哈希槽又是如何映射到 Redis 实例上呢?

  • 根据键值对的 key,使用 CRC16 算法,计算出一个 16 bit 的值
  • 将 16 bit 的值对 16384 执行取模,得到 0 ~ 16383 的数表示 key 对应的哈希槽。
  • 根据该槽信息定位到对应的实例。
  • Redis 的key是与hash槽绑定的,所以新实例加入后,只需要手动给实例指定hash槽即可,同时手动迁移数据。

Cluster 如何实现故障转移?

  • 一个节点发现某个节点失联了,它会将这条信息向整个集群广播,其它节点也就可以收到这点失联信息。
  • 如果一个节点收到了某个节点失联的数量已经达到了集群的大多数,就可以标记该节点为确定下线状态,然后向整个集群广播,强迫其它节点也接收该节点已经下线的事实。
  • 对该失联节点进行主从切换。

Redis线程模型?

Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器。它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型。

  • 文件事件处理器使用 I/O 多路复用程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
  • 当被监听的套接字准备好执行连接应答、读取、写入、关闭等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。

虽然文件事件处理器以单线程方式运行, 但通过使用 I/O 多路复用程序来监听多个套接字, 文件事件处理器既实现了高性能的网络通信模型, 又可以很好地与 redis 服务器中其他同样以单线程方式运行的模块进行对接, 这保持了 Redis 内部单线程设计的简单性。

Redis事务的三个阶段?

  1. 事务开始 MULTI
  2. 命令入队
  3. 事务执行 EXEC

Redis事务保证原子性吗,支持回滚吗?

Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容