笔记大纲
1.Resp通信协议
RESP(redis serialization protocol)redis序列化协议,底层采用的是TCP的连接方式,通过tcp进行数据传输,然后根据解析规则解析相应信息,它具有着实现简单、解析极快、人类可读的特点。
RESP可以序列化不同的数据类型,如整数,字符串,数组,还有一种特定的错误类型,它是二进制安全的,因为它使用前缀长度来传输数据。
RESP虽然是为Redis设计的,但是同样也可以用于其他C/S的软件,但是Redis Cluster集群模式,节点间通信协议不是resp而是基于Gossip方式。
协议将传输的结构数据分为5种最小单元类型,结束时统一加上回车换行符号\r\n。
1、单行字符串 以 + 符号开头。
2、多行字符串 以 $ 符号开头,后跟字符串长度。
3、整数值 以 : 符号开头,后跟整数的字符串形式。
4、错误消息 以 - 符号开头。
5、数组以 * 号开头,后跟数组的长度。
例如,向redis写入set minor hello命令,对应的协议内容为:*3\r\n $3 \r\n set \r\n $5 \r\n minor \r\n $5 \r\n hello \r\n
2.Redis的线程IO模型
Redis基于Reactor
模型开发了专属的网络事件处理器:文件事件处理器 FEH
,这个处理器是单线程的,通过IO多路复用同时监听多个Client端的socket请求,根据socket的请求事件类型来为其选择对应的事件处理器。
2.1 Redis6.0中的多线程
2.1.1 Redis6.0之前是单线程吗?
Redis在处理客户端的请求时,处理socket连接、解析、执行、返回等操作都是由一个主线程处理 ,这就是所谓的单线程架构。但是严格来说,Redis并不是单线程架构,它除了主工作线程外还有许多后台线程处理着不同的任务,例如连接监控、内存清理等等。
2.1.2 Redis6.0之前为什么不采用多线程架构?
Redis官方的解释是:Redis的瓶颈不在于CPU,而是内存和网络。工作任务单线程化的好处就是维护性高、复杂度低,虽然多线程能够带来更好的并发,但是增加了设计复杂度、上下文切换等问题。单线程机制使得Redis内部的数据结构的实现难度也降低,他的Hash、Set等结构就可以无锁化设计。
2.1.3 Redis6.0的多线程是什么?
Redis6.0的多线程并不是让Redis变成多线程架构,他的工作线程仍然是单线程架构,只不过对网络IO连接使用了多线程模式。这样能够提高网络IO性能,分担Redis同步IO读写负载。
Redis6.0的多线程默认是关闭的,可以通过配置文件开启。
1. 主线程负责接收连接请求,将socket放入等待队列。
1. 主线程将socket分配给对应的IO线程后将阻塞,IO线程解析连接。
1. 主线程将IO线程解析后的指令进行单线程模式执行命令。
1. IO线程回写socket。
3.内存淘汰策略
当Redis内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换(swap)。交换会让Redis的性能急剧下降,对于访问量比较频繁的Redis来说,这样存取效率基本上等于不可用。在生产环境中我们是不允许Redis出现交换行为的,为了限制最大使用内存,Redis提供了配置参数maxmemory来限制内存超出期望大小。当实际内存超出maxmemory 时,Redis提供了几种可选策略(maxmemory-policy)来让用户自己决定该如何腾出新的空间以继续提供读写服务。
3.1 Noeviction
这种模式下,写请求将会拒绝服务,只提供读请求,这也是默认的方式。
3.2 Allkeys-LRU
在所有的key中,选出最近最少使用的会被淘汰。
3.3 Allkeys-Random
在所有的key中,随机淘汰。
3.4 Volatile-LRU
在设置了过期时间的key中,使用LRU最近最少使用算法来淘汰缓存。
3.5 Volatile-TTL
设置了过期时间的key中,ttl剩余时间越小优先淘汰。
3.6 Volatile-Random
设置了过期时间的key中,随机淘汰。
Redis使用的是近似LRU算法,跟原生LRU算法不太一样,之所以不使用原生 LRU是因为要消耗大量额外空间和改造数据结构。
Redis采用随机采样法来模拟LRU,达到和LRU近似的效果,采样基数可以配置,越大越接近LRU。
Redis4.0后新增了一种淘汰策略LFU(最近访问频率)。
Redis的key最近访问时间并不是调用系统函数获得的,每次调用系统函数消耗较大,所以Redis缓存了一份系统时间戳,后台定时任务每毫秒更新。
4.删除策略
Redis会将每个设置了过期时间的key放入到一个独立的字典中,以后会定时遍历这个字典来删除到期的key。除了定时遍历之外,它还会使用惰性策略来删除过期的 key,所谓惰性策略就是在客户端访问这个key的时候,redis对key的过期时间进行检查,如果过期了就立即删除。定时删除是集中处理,惰性删除是零散处理。
4.1 定时扫描
Redis 默认会每100ms扫描,过期扫描不会遍历过期字典中所有的key,而是采用了一种简单的贪心策略。
1、从过期字典中随机20个key;
2、删除这20个key 中已经过期的key;
3、如果过期的key比率超过1/4,那就重复步骤1;
设想一个大型的Redis 实例中所有的key在同一时间过期了,会出现怎样的结果?毫无疑问,Redis会持续扫描过期字典,直到过期字典中过期的 key变得稀疏,才会停止。这就会导致线上读写请求出现明显的卡顿现象。导致这种卡顿的另外一种原因是内存管理器需要频繁回收内存页,这也会产生一定的CPU消耗。所以开发人员一定要注意过期时间,如果有大批量的key过期,要给过期时间设置一个随机范围,而不能全部在同一时间过期,避免缓存雪崩。
从库不会进行过期扫描,从库对过期的处理是被动的。主库在key到期时,会在 AOF文件里增加一条del指令,同步到所有的从库,从库通过执行这条del指令来删除过期的key。
4.2 惰性删除
谓惰性策略就是在客户端访问这个key的时候,redis对key的过期时间进行检查,如果过期了就立即删除,不会给你返回任何结果。
4.3 LazyFree机制
LazyFree机制使用del命令删除体积较大的键,又或者在使用flushDB和flushAll删除包含大量键的数据库时,造成redis阻塞的情况;另外redis在清理过期数据和淘汰内存超限的数据时,如果碰巧撞到了大体积的键也会造成服务器阻塞。
为了解决以上问题,redis4.0引入了lazyfree的机制,它可以将删除键或数据库的操作放在后台线程里执行,从而尽可能地避免服务器阻塞。 lazyfree的原理不难想象,就是在删除对象时只是进行逻辑删除,然后把对象丢给后台,让后台线程去执行真正的destruct,避免由于对象体积过大而造成阻塞。例如unlink命令可以让删除操作交给后台线程,执行flushDB时加上async命令可以后台执行。
4.4 Redis内存碎片整理
Redis数据删除键或值后,数据所占用内存不会立即释放给操作系统,而是交给内存分配管理器,所以对操作系统来说Redis仍然占用着这些内存空间。
4.0版本后redis提供了自动内存碎片清理的功能,目的就是将redis的键值数据进行整理聚合等操作,让内存碎片率降低。