Redis


多线程
我们通常说,Redis 是单线程,主要是指 Redis 的网络 IO和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。
假如采用多线程,就会出现一个并发状态难以控制,要通过引入一大堆互斥语句来进行对资源加锁,一方面是增加了系统复杂度和降低了系统的可维护性,另一方面或许还提升不了多少性能。所以直接采用单线程模式。
为什么redis单线程,性能还这么好?
一方面,Redis 的大部分操作在内存上完成,再加上它采用了高效的数据结构,例如哈希表和跳表,这是它实现高性能的一个重要原因。另一方面,就是 Redis 采用了多路复用机制,使其在网络 IO 操作中能并发处理大量的客户端请求,实现高吞吐率。
Redis 单线程是指它对网络 IO 和数据读写的操作采用了一个线程,而采用单线程的一个核心原因是避免多线程开发的并发控制问题。单线程的 Redis 也能获得高性能,跟多路复用的 IO 模型密切相关,因为这避免了 accept() 和 send()/recv() 潜在的网络 IO 操作阻塞点。
redis6.0以后,因为网络io这一块使用多线程进行处理了。也就是说读写操作还是单线程操作,但是连接socket等等网络请求都是多线程处理。
当前IO中存在那些问题
一个请求可能会影响后续请求。比如bigkey阻塞住了,后续请求也都处理不了。
持久化
既然是内存数据库?为什么要有持久化,一方面是做个数据恢复,第二方面是希望在重启时候避免关系数据库压力。
AOF
AOF 是先写内存数据库在写aof文件
回写策略

重写机制
比如
incr a 1 变成 incr a 2
incr a 1
2行变1行减少了文件内容
AOF输出过程会阻塞主线程吗?
我们上面提到一个开启Always,表示每次同步都会执行写入AOF文件中,具体就以这个策略分析会不会阻塞主线程。
- 监听命令写入aof buffer中
- 首先redis监听到操作事件后,执行完毕会去写入到aof buffer中,等待后续刷新到磁盘中。
- 如果此时在进行aof重写过程中还会写入到aof_rewrite_buf_blocks缓冲区一份。(保证aof重写过程中还能处理新命令不丢失)
- 如果此时设置了从机传播标记,将更新发布给从机
- 文件循环机制结束(比如说set name yll命令响应给客户端了结束了) 或者 定时任务(每1S去检查?)去执行刷盘。
- 调用write()把aof_buffer中的缓存数据写入文件系统
- 调用fsync()同步数据到硬盘
Q:为什么调用wirte()还要调用fsync()呢?
A:因为很多操作系统为了减少磁盘io,都是采用延迟写的方式,数据可能还是存在page cahce或者什么东西,并没有真正的落库,所以此时断电,如果操作系统没有处理(我记得操作系统会在断电前全部写入?),这部分数据就会丢失,而客户端确认为写入成功,所以这边会出现问题。而fsync()就是阻塞直到所有文件系统中的缓存刷入到磁盘。
Q:为什么我们说会阻塞主线程?
A:这边就和事件的调度有关系了。redis其中的事件会等待前一个事件结束执行完毕才会去执行。看下面图片的思路,大致上这样。

Q:如果是Everysec,那么他最大的会丢失多少时间数据?
A:答案是2S,因为这是定时任务每秒触发刷盘。而且Redis 只保证: 如果两次执行时间事件处理器之间的时间间隔≥t,就必须要触发一次,所以如果超过执行fsync()过程中超过2S,就直接里面返回再次执行,这两秒数据可能丢失。
Redis 笔记
重写会阻塞主线程吗?
“一个拷贝”就是指,每次执行重写时,主线程 fork 出后台的 bgrewriteaof 子进程。此时,fork 会把主线程的内存拷贝一份给 bgrewriteaof 子进程,这里面就包含了数据库的最新数据。然后bgrewriteaof 子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。
如果在重写过程中又出现写的操作时候,其实在AOF原始缓存区会写入这个指令,拷贝的AOF缓存区也会写入,保证了数据一致性

问题
1.AOF重写过程中会不会阻塞主线程?
第一个是这个创建和拷贝过程由内核执行,是会阻塞主线程的。而且,在拷贝过程中,子进程要拷贝父进程的页表,这个过程的耗时和 Redis 实例的内存大小有关。如果 Redis 实例内存大,页表就会大,fork执行时间就会长,这就会给主线程带来阻塞风险。
第二个是操作系统在分配内存空间时,有查找和锁的开销,这就会导致阻塞。
2.为什么重写日志不和原来AOF共用?
主线程和子线程会竞争,导致主线程效率低下
RDB
内存快照。所谓内存快照,就是指内存中的数据在某一个时刻的状态记录
既然是要对redis内存快照,为了保证可靠性,所以采用全量数据快照
执行方式
| 模式 | 解释 |
|---|---|
| save | 在主线程中执行,会导致阻塞 |
| bgsave | 创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是Redis RDB 文件生成的默认配置。 |
简单来说,bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。
bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。
但是由于快照过程中又出现写的操作,应该怎么处理?
写时复制技术(Copy-On-Write, COW)

如果主线程要修改一块数据(例如图中的键值对 C),那么,这块数据就会被复制一份,生成该数据的副本。然后,bgsave 子进程会把这个副本数据写入 RDB 文件,而在这个过程中,主线程仍然可以直接修改原来的数据。
快照频率
快照过快可能导致我上一次快照还没结束就出现下一次快照,最终导致机器宕机。
快照过慢可能会导致数据丢失较多。
提出标记元素方案?内存占用过多得不偿失
数据恢复
redis数据恢复怎么恢复的。如果redis开启aof,默认使用目录低下aof文件进行恢复,如果没有,就创建一个空的aof。否则就用rdb恢复。
总结
最后我只是想说,在Redis4.0后开启一个混合模式,也就是RDB+AOF模式
特点:
- 触发时机:AOF重写过程中,RDB数据+AOF增量数据
- 格式
- aof文件开头是rdb的格式, 先加载 rdb内容再加载剩余的 aof。
- aof文件开头不是rdb的格式,直接以aof格式加载整个文件。
集群
高可用的意义就是一方面要保证数据完整,一方面要保证机器尽量不宕机处于可用状态。
主从复制
读性能差时候就可以考虑主从模式通过增加数据副本从而增加读的能力了
全量同步
一般也是指数据进行全量同步到从节点从而保证节点之间数据一致。

主要三个阶段吧
- 从节点发送 psync ? -1。psync 命令包含了主库的 runID 和复制进度 offset 两个参数。
- 然后主节点回复给他自己的runId 和 复制进度。
- 主库生成RDB文件放入缓冲区发送给从节点。
- 从库收到RDB文件,为了保证数据完整度,先清空数据,才进行加载。
- 如果在加载过程中,主库还收到其他写命令,先放在replication buffer,并且发送给从库。
这样就实现啦
主库复制压力大?
如果我们同时采用3个从节点进行全量复制,这时候主节点会非常耗费时间,具体耗时操作花费在哪里呢?生成 RDB 文件和传输 RDB 文件。
有内存问题,有cpu问题,有网络io问题最终可能会导致redis宕机了。
所以我们可以采用级联方式来进行减少主库压力,具体方案是主-从-从

网络连接断开?
redis2.8后增加了一个增量同步。在网络状态恢复后,根据情况来选择增量或者全量恢复。
注意:从节点会保存自己复制进度,主节点会保存自己总的日志进度。
当主从库断连后,主库会把断连期间收到的写操作命令,写入 replication buffer,同时也会把这些操作命令也写入 repl_backlog_buffer 这个缓冲区。

主从库的连接恢复之后,从库首先会给主库发送 psync 命令,并把自己当前的slave_repl_offset 发给主库,主库会判断自己的 master_repl_offset 和 slave_repl_offset之间的差距。
在网络断连阶段,主库可能会收到新的写操作命令,所以,一般来说,master_repl_offset会大于 slave_repl_offset。此时,主库只用把 master_repl_offset 和 slave_repl_offset之间的命令操作同步给从库就行。
但是如果 repl_backlog_buffer中最小的偏移量也大于slave_repl_offset,说明在宕机这一块写入了太多数据,已经被覆盖了(参考redolog 环形类似)。这时候就需要重新全量复制啦。或者你可以通过参数调大这个大小。
还有个注意点repl_backlog_buffer是每有一个从节点就有一个。
哨兵选举
主从模式可以增加读的能力,但是当我们主节点挂了,我们对外提供不了写服务,高可用性也就不存在,为了能够保证高可用,redis实现了哨兵机制来进行主从自动切换。
流程
监控->选择->通知
监控
主观下线:哨兵监控到某一台服务长时间没回应,认为不提供服务了
客观下线:多数哨兵都对某一台服务认为主观下线
哨兵进程会使用 PING 命令检测它自己和主、从库的网络连接情况,用来判断实例的状态。(核心参数down-after-milliseconds,多久不返回认为主观下线)
因为网络拥堵环境下,可能会出现一个误判的情况,所以我们需要进行哨兵集群部署。
当一个哨兵发现从节点不可用,无所谓的,因为只是从节点
当一个哨兵发现主节点不可用,通知其他哨兵进行对他检测,如果发现认为主观下线大于大于N/2+1节点数(可以配置),就认为客观下线,开启选举主哨兵
主哨兵选举过程参考raft
具体规则
- 拿到半数以上的赞成票;
- 拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值。以3个哨兵为例,假设此时的 quorum 设置为 2,

选择
默认的条件
- 过滤掉网络状况不好的,断开超过一定次数认为网络不好
- slave-master,优先级,配置项
- 日志完整程度,比较复制的日志offset
- runId最小的
切换
这个过程就简单了 ,哨兵只是通知让他们重新建立主从关系
哨兵如何知道相互节点?
哨兵配置只需要配置一个主节点,当我们启动哨兵时候,他会去订阅一个主题,发布自己信息,然后推送相关的其他哨兵信息,从而进行相互通信。
哨兵如何知道从节点的信息?
基于对主节点info命令进行知道
哨兵可以和客户端进行通信吗?
客户端可以实现一些机制来监听redis集群事件以及相关变化。

集群
上述所有操作也只是简单解决一个读的问题,那我们想解决一个写性能问题,改怎么办呢?
市面上有很多相关的方案,我们还是以redis cluster为主讲下吧。
如何选择机器
假如我现在有一台8G的redis服务器,现在要你扩容到最少能存25G的数据,怎么选择?
纵向扩展:直接买一台32G内存的服务器
横向扩展:进行集群搭建 ,再买3台8G服务器,共同搭建一个集群环境
(当前这边机器配置只是我随便说的,具体还是要看真实情况)
那这两种扩展有什么区别呢?
RDB进行快照过程中,fork操作会阻塞主线程,而fork过程主要和数据量有关系,所以数据量越大,越容易阻塞主线程,从而进行影响redis吞吐量。而且纵向扩展硬件成本 不支持你大规模扩容,比如一台电脑4根内存条,你只能插4条,还要多久要主板支持了。所以如果在不考虑硬件和持久化的前提下,采用这个方案还是扩展还是比较简单的。(现在好像大家都用阿里云redis,默认主从了,在线就可以扩容方便的很,价格也比实体便宜吧)
在面向百万、千万级别的用户规模时,横向扩展的 Redis 切片集群会是一个非常好的选择。
数据切片后,在多个实例之间如何分布?
Redis Cluster 方案采用哈希槽(Hash Slot,接下来我会直接称之为 Slot),来处理数据和实例之间的映射关系。在 Redis Cluster 方案中,一个切片集群共有 16384个哈希槽,这些哈希槽类似于数据分区,每个键值对都会根据它的 key,被映射到一个哈希槽中
首先根据键值对的 key,按照CRC16 算法计算一个 16 bit的值;然后,再用这个 16bit 值对 16384 取模,得到 0~16383 范围内的模数,每个模数代表一个相应编号的哈希槽。
cluster create: 创建集群
cluster meet :建立连接
cluster addslots:指定hash槽

在手动分配哈希槽时,需要把 16384 个槽都分配完,否则Redis 集群无法正常工作。
redis实例之间会互相通信,进行hash槽之前节点相互记录,在客户端和redis连接过程中,就把集群中hash槽信息返回给客户端,客户端缓存下来即刻。
如果hash槽在动态迁移怎么办?
hash槽迁移完毕,客户端保存老的hash槽信息,这时候redis就会返回给客户端
GET hello:key
(error) MOVED 13320 172.16.19.5:6379

说明这个键值对已经迁移到172.16.19.5:6379,客户端需要更新hash槽信息,重新向新节点发出请求
hash槽迁移中
GET hello:key
(error) ASK 13320 172.16.19.5:6379

第一,表明 Slot 数据还在迁移中;第二,ASK 命令把客户端所请求数据的最新实例地址返回给客户端,此时,客户端需要给实例 3 发送 ASKING 命令,然后再发送操作命令
所以在迁移过程中,客户端接受到ask命令,并不会更改缓存,而只是进行重新发送请求,并且再次访问过程中,也是返回一样ask,需要重新请求。
集群中的主从复制
集群中的每个节点都有1个至N个复制品,其中一个为主节点,其余的为从节点,如果主节点下线了,集群就会把这个主节点的一个从节点设置为新的主节点继续工作,这样集群就不会因为一个主节点的下线而无法正常工作。
集群中节点数量
最少3个主3从。兼顾高可用高性能
集群中宕机问题?
如何判断节点是否宕机?
在主从哨兵模式中,哨兵节点是独立的,哨兵之间是不知道哨兵之间的信息,通关关联主节点发布订阅模式来实现知道相互之间哨兵信息的。
在集群分片,各个分片节点公共构建一个大型的逻辑Redis,所以在建立集群过程中,他们本身就是通过通信互相知道其他节点信息和槽信息。所以我一开始就有个疑惑了?
集群节点是怎么判断宕机的?
和主从一样引入哨兵机制?
想了下,如果引入哨兵机制,又要增加系统的复杂度,而且Redis集群的吞吐量会不会因为又多了些心跳请求而下降?所以也理所当然的百度,事实确实也是这样,集群过程中他们本身主节点就会进行相互通性,就拿它顺便作为一个检测机制呗。
每一个节点都存有这个集群所有主节点以及从节点的信息。它们之间通过互相的ping-pong判断是否节点可以连接上。如果有一半以上的节点去ping一个节点的时候没有回应,集群就认为这个节点宕机了,然后去连接它的备用节点。
集群进入fail状态的必要条件
- 某个主节点和所有从节点全部挂掉,我们集群就进入faill状态。
- 如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态.
- 如果集群任意master挂掉,且当前master没有slave.集群进入fail状态
当前环境3主6从,每台主节点2个从
来分析一下第一点。1主2从挂了之后,其实这个节点挂了,相当于整个集群环境中,这个节点下的hash槽数据全部丢失,所以不能够提供一个完整的服务
再来分析下第二点,挂了2主,节点无法进行选举了,选举方法其实还是一样的,因为数量少于一半了,永远选不出来。
第三点,其实和第一点很像,主节点挂了,没有新的从节点去接手该主节点数据。
Redis数据结构
SDS
struct __attribute__ ((__packed__)) sdshdr8 {
// 字符数组现有⻓度。 8 位无符号整型,占用 1 字节的内存空间。
uint8_t len; /* used */
// 字符数组已经分配的⻓度, 不包括结构体和\0结束符
uint8_t alloc;
/* excluding the header and null terminator */
// SDS 类型
unsigned char flags;
/* 3 lsb of type, 5 unused bits */
// 字符数组,用来保存实际数据
char buf[];
};
编码
字符串类型的内部编码有3种:
- int:8个字节的⻓整型。
- embstr:小于等于44个字节的字符串。
- raw:大于44个字节的字符串。
问题
Q:为什么要使用 SDS?
A:
- 效率方面,空间预分配,惰性释放内存的。从而减少分配内存的次数,获取数组长度o(1)复杂度
- 安全方面,可以禁止缓冲区溢出。那问题来了,什么是缓冲池溢出?其实就是下一个内存不是这个变量的,导致超出范围的数据导致数据有问题。
Q:为什么 SDS 规定字符串字节数达到 44 就要升级?
A:
typedef struct redisObject {
unsigned type:4; // 4bit
unsigned encoding:4; // 4bit
unsigned lru:LRU_BITS; // 24bit
int refcount; // 4字节
void *ptr; // 8字节
} robj;
redisObject 是 16个字节,sds最少3个字节,\0也占用1字节,所以64-20 = 44;
Redis 笔记
Q:SDS 是怎么做了那些优化内存事情?
A:
- 争对不同类型的数据存储不同数据结构
- 字节对齐。这个属性主要是用来进行设置结构体字节对齐用的(告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐(同事看源码说的)
Q:SDS 字符串在 Redis 内部模块实现中也被⼴泛使⽤,请简单说⼏个?
A:
- RedisObject 中的key
Q:SDS那些场景下使用浪费内存?
A:set 100000 100000,采用int进行编码,redisobject 2个 分别是key 和 value 32字节,Redis 会使用一个全局哈希表保存所有键值对,哈希表的每一项是一个 dictEntry 的结构体,他占用24字节,由于分配都是分配2的幂次方,所以32字节。最终分配32+32 =64字节。很多是元数据,如果用压缩列表会省空间。
Hash

编码:
- ziplist(压缩列表):当哈希类型元素个数⼩于hash-max-ziplist-entries 配置(默认512个)、同时所有值都⼩于hash-max-ziplist-value配置(默认64 字节)时,Redis会使⽤ziplist作为哈希的内部实现,ziplist使⽤更加紧凑的 结构实现多个元素的连续存储,所以在节省内存⽅⾯⽐hashtable更加优秀。
- hashtable(哈希表):当哈希类型⽆法满⾜ziplist的条件时,Redis会使 ⽤hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,⽽ hashtable的读写时间复杂度为O(1)。
问题
Q:哈希表有哪些优化内存方式?
A:
- 针对数据量采用不同数据结构。在平衡读取效率时候采用哈希表
- 1
Q:Redis 什么时候触发扩容缩容?
A:
- 服务器目前没有执行BGSAVE(rdb持久化)命令或者BGREWRITEAOF(AOF文件重写)命令,并且散列表的负载因子大于等于1。
- 服务器目前正在执行BGSAVE命令或者BGREWRITEAOF命令,并且负载因子大于等于5。
- 当负载因子小于0.1时,程序自动开始执行收缩操作。
- 负载因子是什么(看完hashmap源码时候,话说不是负载因子是我们传递过去的吗哈哈哈哈,分享时候我还问这边怎么没有,实际他=当前元素数量/桶的数量)
Q:Redis 如何实现 rehash?
A:
- 首先要了解为什么要扩容?散列表中保存的键值对会也会不断地增加或减少,为了保证负载因子维持在一个合理的范围,当散列表内的键值对过多或过少时,内需要定期进行rehash,以提升性能或节省内存。
- 给tab【1】分配好内存,字典表rehashids设为0,标识开始同步tab【0】第一个桶下面数据迁移到tab【1】,迁移完毕时候,rehashids自增+1;随着迁移完毕,rehashids设置为-1,此时在替换下tab【0】 和 tab【1】。
Q:渐进式rehash怎么去执⾏?
A:
- 因为redis是单线程的,如果你在同一时间同步完成rehash,比如数据量大一点,可能过程很久,造成了线上不可以用,所以才有渐进式rehash。
- 流程差不多。但是不会主动迁移桶了,而是每次执行增删改查时候,都会去同步一次桶。大致也可以理解分批处理。
Q:但如果服务器⻓期没有执⾏命令的话,rehash 怎么完成?渐进式rehash怎么去执⾏?
A:按照上面说的,我们只有每次执行命令顺带一条,如果没有命令,他会有个时间派发任务去执行。
Q:Redis 是如何解决 hash 冲突的?为什么不使⽤红⿊树?
A:通过链表解决hash冲突的。为什么不用红黑树,
- 内存占用多,要标记红黑节点各种各样
- 本身需要频繁rehash,成本性能都有影响
List

编码:
- ziplist(压缩列表):当哈希类型元素个数⼩于hash-max-ziplist-entries 配置(默认512个)、同时所有值都⼩于hash-max-ziplist-value配置(默认64 字节)时,Redis会使⽤ziplist作为哈希的内部实现,ziplist使⽤更加紧凑的 结构实现多个元素的连续存储,所以在节省内存⽅⾯⽐hashtable更加优秀。
- linkedlist(链表):当列表类型⽆法满⾜ziplist的条件时,Redis会使⽤ linkedlist作为列表的内部实现。
问题
Q:作为一个消费队列,对比下?
A:
- 消费可靠性
- 消息堆积问题
- 分组消费问题
- 延时消费
- 持久化问题
- …
总结来说,redis作为一个轻量级消息队列,和重量级的消息队列对比,肯定是有很多缺陷。而且高可用下会影响到很多数据。

Set

编码
- intset(整数集合):当集合中的元素都是整数且元素个数⼩于set-max- intset-entries配置(默认512个)时,Redis会选⽤intset来作为集合的内部实 现,从⽽减少内存的使⽤。
- hashtable(哈希表):当集合类型⽆法满⾜intset的条件时,Redis会使 ⽤hashtable作为集合的内部实现。
Zset
编码
- ziplist(压缩列表):当有序集合的元素个数⼩于zset-max-ziplist- entries配置(默认128个),同时每个元素的值都⼩于zset-max-ziplist-value配 置(默认64字节)时,Redis会⽤ziplist来作为有序集合的内部实现,ziplist 可以有效减少内存的使⽤。
- skiplist(跳跃表):当ziplist条件不满⾜时,有序集合会使⽤skiplist作 为内部实现,因为此时ziplist的读写效率会下降。
问题
Q:有序集合中的元素不能重复,score可以重复吗?
A:可以的。
计算

缓存
对于缓存而言,我们通常有读写缓存和只读缓存,一般我们常规优化接口中,我们一般都是设计只读缓存,所以我们现在讲下只读缓存中问题。
缓存一致性问题
| 缓存类型 | 问题 | 情况 | 解决方案 |
|---|---|---|---|
| 先修改数据库,后删除缓存 | 原子性问题 | 修改数据库成功,删除缓存失败 | 本地cas重试,rocketmq等等 |
| 先修改数据库,后删除缓存 | 并发问题 | 短暂内数据有问题 | |
| 先删除缓存,后修改数据库 | 原子性问题 | 删除缓存成功,修改数据库失败 | 本地cas重试,rocketmq等等 |
| 先删除缓存,后修改数据库 | 并发问题 | 导致缓存还是老的数据 | 延迟双删除,先删除缓存,再修改数据库,延时一段时间再删除一次缓存 |
redis缓存问题案例
操作过程:插入10W条数据,同时添加缓存,保证原子性。再兼顾性能和一致性上,采用Canal监听binlog写入redis缓存。
后续现象:出现服务宕机(具体不知道啥服务?或许redis,或许基础服务)
原因:Canal监听binlog时候必须采用row格式进行监听,事务提交了才会写入binlog,由于在一个事务里面,导致一瞬间写入太多数据,以至于监听过程中10W个redis操作,导致redis服务高度紧绷,提供不了外部服务嗯。
缓存雪崩
缓存雪崩的意思就是大量缓存在同一瞬间过期,或者说宕机了,导致最终请求达到数据库,照成宕机。
解决方案
- 对于缓存过期时间都设置合理,可以适当加个随机数
- 根据业务重要性来选择服务降级,比如库存字段可以选择查询数据库,比如商品图片可以就返回一个null。(但是需要业务编码麻烦)
- 从并发角度理解,其实是瞬间流量是非常大的,所以才有后续的宕机问题。为了保证服务高可用,不影响其他服务,可以适当选择降级来进行某中程度上进行避免。
- 针对主节点自己宕机的方案,就是构建高可用主从方案抵制单节点故障。
缓存击穿
缓存击穿值得是访问同一个缓存的并发量很大,但是由于他过期了,全部请求达到数据库上,造成宕机。
解决方案:
- 缓存永不过期
缓存穿透
大量请求缓存不存在数据,业务层面上也不存在的数据,导致请求全在数据库,然后宕机
- 布隆过滤器(原理不说了)
- 缺省值
- 校验一些特殊值
淘汰机制
内存对于我们来说是一个非常昂贵的资源,当Redis内存面临爆满时候,我们就要选择一个合适的淘汰机制来面对这个困境
策略
- 直接返回异常 noevction
- volatile-lru
- volatile-lfu
- volatile-random
- volatile-ttl
- allkeys-lru
- allkeys-lfu
- allkeys-random
LRU 最近最少使用的
LFU 最近使用次数最少的
其实就说一个关键点
Redis 在实现 LFU 策略的时候,只是把原来 24bit 大小的 lru 字段,又进一
步拆分成了两部分。
-
ldt 值:lru 字段的前 16bit,表示数据的访问时间戳;
-
counter 值:lru 字段的后 8bit,表示数据的访问次数。
对于counter值,1<<8 = 256 ,有点少,但是其中用了一点奇奇怪怪非线性增长的方法,让他能够增长很慢,同时也有衰减模式。
缓冲区
缓冲区的功能其实很简单,主要就是用一块内存空间来暂时存放命令数据,以免出现因为数据和命令的处理速度慢于发送速度而导致的数据丢失和性能问题。
Redis是CS架构的,为了避免上诉的问题,所以对于每个客户端都有输入输出缓冲区

输入缓冲区
说两个问题案例
- A客户端写入大量数据导致缓冲区溢出,Redis服务端不得已关闭A客户端的连接,释放缓冲区
- ABCD…多个客户端都在建立连接,最终内存占用太多,Redis不得已去开启淘汰机制去淘汰以至于实际使用的数据很少了,导致操作性能问题,最终导致OOM
所以为了缓冲池能够真正被我们利用起来,并能够起效果
解决角度就是
- 增加缓冲区大小(代码中写的默认是1GB,如果要改就要动源码)
- 发送命令速度方向下手(尽量避免大key等相关操作)
输出缓冲区
问题:
- bigKey问题
- MONITOR命令
- 缓冲区太小
bigkey问题就不用说了,一下子查询bigkey到输出缓冲区,直接撑爆了。
MONITOR命令是持续监控Redis相关参数,会让输出缓冲区不断变大。
缓冲区太小导致随便查询一下都撑爆了
个人理解其实关键在于怎么缓冲区大小和写入缓冲区的速度,所以其实在解决这个问题就很简单了,有相应配置
client-output-buffer-limit pubsub 8mb 2mb 60
第一个参数是值得是什么客户端
第二个参数指的是缓冲区大小多少
第三个和第四个配合使用,指的是在60S内写入超过2MB就关闭客户端连接
主从缓冲区
主从同步传输RDB文件后,还会进行接受写命令请求存在repl_backlog_buffer ,如果此时大量写请求,就会导致同步失败,最终OOM。
解决方案其实也很简单了,根据自身的redis机器配置情况,选择合理的
client-output-buffer-limit slave 1024mb 128mb 60
配置也是可以解决的。
总结
缓冲区的意义就是增加一点数据可用性和服务器性能,但是为了能够好好利用这个特征,我们必须要合理的业务需求,配置适当的机器,选择合理的方式解决,最终其实都回归2个特点
- 缓存区大小配置
- 写入速度
事件驱动
redis 内部有一个小型的事件驱动,都是依托 I/O 多路复用技术支撑起来的。
利用 I/O 多路复用技术,监听感兴趣的文件 I/O 事件,例如读事件,写事件等,同时也要维护一个以文件描述符为主键,数据为某个预设函数的事件表,这里其实就是一个数组或者链表 。当事件触发时,比如某个文件描述符可读,系统会返回文件描述符值,用这个值在事件表中找到相应的数据项,从而实现回调。同样的,定时事件也是可以实现的,因为系统提供的 I/O 多路复用技术中的函数允许我们设定时间值。
总的来说分为
- 文件事件
- 时间事件

IO多路复用监听多个套接字,如果套接字有相关事件,放入到等待队列中,等待文件事件分派器进行分发,其中相关操作就是建立连接,请求命令,响应命令等等。


其中文件事件和时间事件都是在主函数中初始化后进行开启的一个无限循环过程中,去实现各种事件。具体时间事件过程处理中不是很准确的,因为他为了避免循环,通过计算时间阻塞来进行的。
Redis6.0新特性
- 网络io多线程,实际操作还是单线程
- ACL权限设置
- SSL验证
问题
Q:系统突然边慢了,你怎么考虑选择?
Q:Redis系统突然边慢了,你怎么考虑选择?
A:
- 执行某些O(n)操作,或者操作一个大Key。
- 大量key同时过期,删除key(这边如果异步删除CPU性能问题,同步删除就很直接了)
- 内存满了
- 内存超出使用swap(第一次知道这个技术,在内存快要满的时候存入一些到磁盘)
- 数据量大 ,aof和rdb fork过程会耗时阻塞
- aof同步写日志刷磁盘
- 开启了透明内存大业,之前操作系统一般一页是4kb,这边就变成2M,以至于小改动也要分配2M内存以至于也要分配2M,修改新增操作过多就会导致内存不够以至于性能问题。
- CPU上下文切换,所以要和绑定线程核
- 网卡压力大
Q:你认为一个程序员应该做的事情?
A:
- 作为一个程序员,技术对我们来说是至关重要的,有了技术才能去支持业务,当然反过来好的业务也能催生新技术。
- 第二点就是一个总结,我们平时都是做的业务相关的东西,肯定需要我们去提前了解业务,并且每次开发完毕后都需要有思考过程,比如某某过程我如果用什么过好了。
- 日常优化思考,多关注线上情况。
- 保持好奇心,对知识的热情一定要有,催促的我们走向更崭新的技术未来
[ 同事xx不说了 ]:[Redis数据结构]
[ 官网 ]:Redis官网
[ 源码]:Redis源代码
Redis全方位解析:特性、问题与解决方案
本文全面介绍了Redis的多线程、持久化、集群、数据结构、缓存等方面。阐述了单线程高性能原因及6.0新特性,分析AOF和RDB持久化机制,探讨主从复制、哨兵选举和集群搭建,介绍数据结构及缓存问题与解决办法,还提及淘汰机制、缓冲区和事件驱动等内容。

被折叠的 条评论
为什么被折叠?



