Redis
Redis常见数据结构以及使用场景
string
介绍:虽然Reids是C语言编写的,但是其string底层并没有采用C的字符串,而是自己构建了一种简单动态字符串
常用命令:set,get,strlen,exists,decr,incr,setex等
应用场景:一般用于需要计数的场景,比如用户的访问次数,热点文章的点赞数和转发数等等
list
介绍:底层为双向链表
常用命令:rpush,lpush,rpop,lpop,lrange,llen等
应用场景:消息队列
hash
介绍:类似于JDK1.8之前的HashMap,内部实现也差不多是数组+链表。
常用命令:hset,hmset,hexists,hget,hgetall,hkeys,hvals等
应用场景:系统中对象数据的存储
set
介绍:类似于Java中的HashSet
常用命令:sadd,spop,smembers,sismember,scard,sinterstore,sunion等
应用场景:集合运算,比如集合的交集和并集
sorted set
介绍:基于跳表实现,和set相比增加了一个权重参数score,使得集合中的元素可以根据score进行有序排列。
常用命令:zadd,zcard,zscore,zrange,zrevrange,zrem等
应用场景:需要对数据根据某个权重进行排序的场景。比如直播间礼物排行 。
Redis到底是单线程还是多线程
Redis 6.0版本之前的单线程指的是其网络I/O和键值对读写是有一个线程完成的。也就是只有网络请求模块和数据操作模块是单线程的,而其他的持久化、集群数据同步等,其实是由额外的线程执行的
Redis 6.0引入的多线程指的是网络I/O采用了多线程,而键值对读写命令仍然是单线程处理的,所以Redis仍然是并发安全的
Redis单线程为什么还快
- 命令执行基于内存操作
- 命令执行是单线程操作,没有线程切换开销
- 基于IO多路复用机制(epoll)提升Redis的I/O利用率
- 高效的数据存储结构:全局hash表以及多种高效数据结构,比如:跳表,压缩列表,链表等等
Redis底层数据是如何用跳表来存储的
将有序链表改造为支持类似“折半查找”的算法,可以让链表可以快速的插入、删除和查找。常用于Sorted Set的底层实现。

Redis Key过期了为什么内存没释放
设置了过期时间的key被没有设置过期时间的相同key覆盖了
127.0.0.1:6379> set name vingkin ex 120 OK 127.0.0.1:6379> ttl name (integer) 119 127.0.0.1:6379> set name vingkin OK 127.0.0.1:6379> ttl name (integer) -1 # name被覆盖,永不过时与过期数据删除策略有关
过期数据删除策略
惰性删除: 在取出key的时候对数据进行过期检查。这样对CPU友好,但是会造成太多过期key没有被删除
定期删除: 每隔一段时间抽取一批key执行删除过期key操作。并且Redis底层会通过限制删除操作的时常和频率来减少删除操作对CPU的影响
定期删除对内存更加友好,惰性删除对CPU更加友好。所以Redis采用两者结合的方式进行过期数据删除
Redis Key没设置过期时间为什么被Redis主动删除了
Redis的内存淘汰机制使用了allkeys-lru或者allkeys-random或者allkeys-lfu
Redis内存淘汰机制
当Redis已用内存超过maxmemory限定时,触动主动清理策略。
主动清理策略再Redis4.0 之前一共实现了6中内存淘汰机制,在4.0之后,又增加了2种策略,总共8种:
针对设置了过期时间的key做处理:
- volatile-lru(least recently used): 从已设置过期时间的数据集(
server.db[i].expires)中挑选最近最少使用的数据淘汰(最常用) - volatile-ttl: 从已设置过期时间的数据集中挑选即将要过期的数据淘汰
- volatile-random: 从已设置过期时间的数据集中任意选择数据淘汰
- volatile-lfu(least frequently used): 从已设置过期时间的数据集中挑选最不经常使用的数据淘汰(访问次数最少)
针对所有的key做处理:
- allkeys-lru:(least recently used): 当内存不足以容纳新写入数据时,在键空间中,移出最近最少使用的key(最常用)
- allkeys-random: 从数据集中任意选择数据淘汰
- allkeys-lfu(least frequently used): 当内存不足以容纳新写入的数据时,在键空间中移出最不经常使用的key
不处理:
- no-eviction: 禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。(基本不使用)
绝大多数情况都是用LRU策略,当存在大量的热点缓存数据时,LFU可能更好点,以访问次数的多少作为参考点。
删除Key的命令会阻塞Redis吗
DEL key [key ...]
时间复杂度:
O(N),其中N为被删除key的数量
删除单个字符串类型的key,时间复杂度为O(1)
删除单个列表、集合、有序集合或哈希表类型的key,时间复杂度为O(M),其中M为以上数据类型种元素的数量。
当删除的key是所占内存很大时,不管是string还是其他的数据类型,都有可能会阻塞Redis
Redis高可用方案
主从模式
不能保证高可用,当master节点挂掉后需要运维介入切换节点,一般不使用

哨兵模式
在redis 3.0以前的版本要实现集群一般时借助哨兵sentinel工具来监控master节点的状态,如果master节点异常,则会做出主从切换,将某一台slave作为master,哨兵的配置略微复杂,并且性能和高可用性等各方面表现一般,特别时主从切换的瞬间存在访问瞬断的情况,而且哨兵模式只有一个主节点对外提供服务,没法支持很高的并发(单节点理论支持最高并发量为10万),且单个主节点内存也不宜设的过大(一般为10G),否则会导致持久化文件过大,影响数据恢复或主从同步的效率。
sentinel,哨兵是redis集群中非常重要的一个组件,主要有以下功能:
- 集群监控:负责监控redis master和slave进程是否正常工作
- 消息通知:如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员
- 故障转移:如果master node挂掉了,会自动转移到slave node上。故障转移时,判断一个master node是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举。
- 配置中心:如果故障转移发生了,通知client客户端新的master地址
哨兵用于实现redis集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。
- 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的
- 哨兵通常需要3个实例,来保证自己的健壮性
- 哨兵 + redis主从的部署架构,是不保证数据零丢失的,只能保证redis集群的高可用性

集群模式
Redis集群是由多个主从节点群组成的分布式服务器群,它具有复制,高可用和分片的特性。Redis集群不需要sentine哨兵也能完成节点移出和故障转移的功能。需要将每个节点设置成集群模式,这种集群模式没有中心节点,可水平扩展,据官方文档称可以线性扩展到上万个节点(官方推荐不超过1000个)。Redis集群的性能和高可用性均优于之前版本的哨兵模式,且集群配置非常简单。
- 通过hash的方式,将数据分片,每个节点均衡存储一定哈希槽(哈希值)区间的数据
- 每份数据分片会存储在多个互为主从的多节点上
- 数据先写入主节点,再同步到从节点(支持配置为阻塞同步)
- 同一分片多个节点间的数据不保持强一致性
- 读取数据时,当客户端操作的key没有分配在该节点上时,redis会返回转向指令,转向正确的节点
- 扩容时,需要把旧节点的数据迁移一部分到新节点
在redis cluster架构下,每个redis节点都要开放两个端口号,一个用于连接,一个用于节点间通信

Reids集群模式下数据hash分片算法
Redis Cluster将所有数据划分为16384个槽位,每个节点负责其中一部分槽位。槽位的信息存储于每个节点中。
当Redis Cluster的客户端来连接集群时,它也会得到一份集群的槽位配置信息并将其缓存在客户端本地。这样当客户端要查找某个key时,可以根据槽位定位算法定位到目标节点。
槽位定位算法
Cluster默认会对key使用crc16算法进行hash得到一个整数值,然后用这个整数值对16384进行取模来得到具体槽位。
HASH_SLOT = CRC16(key) % 16384
再根据槽位值和Redis节点的对应关系就可以定位到key具体是落在哪个Redis节点上
Redis执行命令出现死循环Bug
如果想随机查看Redis中的一个key,Redis里面有一个RANDOMKEY命令可以从Redis中随机取出一个key,这个命令可能导致Redis死循环阻塞。
出现这个问题的原因主要还是在于Redis的过期数据删除策略
RANDOMKEY随机拿出一个key后,首先会检查该key是否过期,如果过期,则会先删除这个key然后重新选取,直到找到一个未过期的key返回。如果Redis中有大量的key已经过期,但是没有被即使清理,那么这个循环会持续很久才结束。这个流程是发生在master节点中的。
如果发生在slave节点中,那么问题会更严重。slave是不会主动清理过期key的,当一个key过期时,master会先清理删除它,然后向slave发送一个DEL命令,告知slave也删除这个key,以此达到主从库的数据一致性。
假设Redis中存在大量已过期但是未被清理的key,在slave中执行RANDOMKEY时,因为不会删除过期key,则有可能无限制的命中过期key,陷入死循环,导致Redis实例卡死。
这其实是Redis 5.0之前的一个Bug,修复方案就是给RANDOMKEY增加最多执行次数,无论是否找到key,都返回。
主从切换导致缓存雪崩具体场景
为什么要保证主从节点机器时钟一致
我们假设,slave的机器时钟比master走得快很多
此时,Redis master里设置了过期时间的key,从slave角度来看,可能会有很多在master里没过期的数据其实已经过期了
如果此时操作主从切换,把slave提升为新的master
slave成为新的master后,就会开始大量清理过期key,此时就会导致以下结果:
- master大量清理过期key,主线程可能会发生阻塞,无法及时处理客户端请求
- Redis中数据大量过期,引发缓存雪崩甚至系统崩溃
当master和slave机器始终严重不一致时,对业务的影响非常大。所以一定要保证主从节点的机器时钟一致性。
