16384 个槽位
每份数据分片会存储在多个互为主从的多节点上
数据写入先写主节点,再同步到从节点(支持配置为阻塞同步)
同一分片多个节点间的数据不保持一致性
读取数据时,当客户端操作的key没有分配在该节点上时,redis会返回转向指令,指向正确的节点
扩容时时需要需要把旧节点的数据迁移一部分到新节点
在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。
16379 端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议,gossip
协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。
优点:
- 无中心架构,支持动态扩容;
- 数据按照
slot
存储分布在多个节点,节点间数据共享,可动态调整数据分布;
- 高可用性。部分节点不可用时,集群仍可用。集群模式能够实现自动故障转移(failover),节点之间通过
gossip
协议交换状态信息,用投票机制完成Slave
到Master
的角色转换。
缺点:
- 不支持批量操作(pipeline)。
- 数据通过异步复制,不保证数据的强一致性。
- 事务操作支持有限,只支持多
key
在同一节点上的事务操作,当多个key
分布于不同的节点上时无法使用事务功能。
key
作为数据分区的最小粒度,不能将一个很大的键值对象如hash
、list
等映射到不同的节点。
- 不支持多数据库空间,单机下的Redis可以支持到16个数据库,集群模式下只能使用1个数据库空间。
- 只能使用0号数据库。
哈希分区算法有哪些?
节点取余分区。使用特定的数据,如Redis的键或用户ID,对节点数量N取余:hash(key)%N计算出哈希值,用来决定数据映射到哪一个节点上。
优点是简单性。扩容时通常采用翻倍扩容,避免数据映射全部被打乱导致全量迁移的情况。
一致性哈希分区。为系统中每个节点分配一个token,范围一般在0~232,这些token构成一个哈希环。数据读写执行节点查找操作时,先根据key计算hash值,然后顺时针找到第一个大于等于该哈希值的token节点。
这种方式相比节点取余最大的好处在于加入和删除节点只影响哈希环中相邻的节点,对其他节点无影响。
虚拟槽分区,所有的键根据哈希函数映射到0~16383整数槽内,计算公式:slot=CRC16(key)&16383。每一个节点负责维护一部分槽以及槽所映射的键值数据。Redis Cluser采用虚拟槽分区算法。
过期键的删除策略?
1、被动删除。在访问key时,如果发现key已经过期,那么会将key删除。
2、主动删除。定时清理key,每次清理会依次遍历所有DB,从db随机取出20个key,如果过期就删除,如果其中有5个key过期,那么就继续对这个db进行清理,否则开始清理下一个db。
3、内存不够时清理。Redis有最大内存的限制,通过maxmemory参数可以设置最大内存,当使用的内存超过了设置的最大内存,就要进行内存释放, 在进行内存释放的时候,会按照配置的淘汰策略清理内存。
内存淘汰策略有哪些?
当Redis的内存超过最大允许的内存之后,Redis 会触发内存淘汰策略,删除一些不常用的数据,以保证Redis服务器正常运行。
Redisv4.0前提供 6 种数据淘汰策略:
- volatile-lru:LRU(
Least Recently Used
),最近使用。利用LRU算法移除设置了过期时间的key
- allkeys-lru:当内存不足以容纳新写入数据时,从数据集中移除最近最少使用的key
- volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
- allkeys-random:从数据集中任意选择数据淘汰
- no-eviction:禁止删除数据,当内存不足以容纳新写入数据时,新写入操作会报错
Redisv4.0后增加以下两种:
- volatile-lfu:LFU,Least Frequently Used,最少使用,从已设置过期时间的数据集中挑选最不经常使用的数据淘汰。
- allkeys-lfu:当内存不足以容纳新写入数据时,从数据集中移除最不经常使用的key。
内存淘汰策略可以通过配置文件来修改,相应的配置项是maxmemory-policy
,默认配置是noeviction
。
如何保证缓存与数据库双写时的数据一致性?
1、先删除缓存再更新数据库
进行更新操作时,先删除缓存,然后更新数据库,后续的请求再次读取时,会从数据库读取后再将新数据更新到缓存。
存在的问题:删除缓存数据之后,更新数据库完成之前,这个时间段内如果有新的读请求过来,就会从数据库读取旧数据重新写到缓存中,再次造成不一致,并且后续读的都是旧数据。
2、先更新数据库再删除缓存
进行更新操作时,先更新MySQL,成功之后,删除缓存,后续读取请求时再将新数据回写缓存。
存在的问题:更新MySQL和删除缓存这段时间内,请求读取的还是缓存的旧数据,不过等数据库更新完成,就会恢复一致,影响相对比较小。
3、异步更新缓存
数据库的更新操作完成后不直接操作缓存,而是把这个操作命令封装成消息扔到消息队列中,然后由Redis自己去消费更新数据,消息队列可以保证数据操作顺序一致性,确保缓存系统的数据正常。
以上几个方案都不完美,需要根据业务需求,评估哪种方案影响较小,然后选择相应的方案。
缓存常见问题
缓存穿透
缓存穿透是指查询一个不存在的数据,由于缓存是不命中时被动写的,如果从DB查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到DB去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了。
怎么解决?
- 缓存空值,不会查数据库。
- 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的
bitmap
中,查询不存在的数据会被这个bitmap
拦截掉,从而避免了对DB
的查询压力。
布隆过滤器的原理:当一个元素被加入集合时,通过K个哈希函数将这个元素映射成一个位数组中的K个点,把它们置为1。查询时,将元素通过哈希函数映射之后会得到k个点,如果这些点有任何一个0,则被检元素一定不在,直接返回;如果都是1,则查询元素很可能存在,就会去查询Redis和数据库。
布隆过滤器一般用于在大数据量的集合中判定某元素是否存在。
缓存雪崩
缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重挂掉。
解决方法:
- 在原有的失效时间基础上增加一个随机值,使得过期时间分散一些。这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
- 加锁排队可以起到缓冲的作用,防止大量的请求同时操作数据库,但它的缺点是增加了系统的响应时间,降低了系统的吞吐量,牺牲了一部分用户体验。当缓存未查询到时,对要请求的 key 进行加锁,只允许一个线程去数据库中查,其他线程等候排队。
- 设置二级缓存。二级缓存指的是除了 Redis 本身的缓存,再设置一层缓存,当 Redis 失效之后,先去查询二级缓存。例如