Redis
2021-09-09 14:33:09 1 举报
AI智能生成
redis结构和性能
作者其他创作
大纲/内容
四种模式
单机
优点
成本低,没有备用节点,不需要其他的开支。
缺点
可靠性保证不是很好,单节点有宕机的风险。
单机高性能受限于CPU的处理能力,redis是单线程的
主从
优点
一旦 主节点宕机,从节点 作为 主节点 的 备份 可以随时顶上来。
扩展 主节点 的 读能力,分担主节点读压力。
高可用基石:除了上述作用以外,主从复制还是哨兵模式和集群模式能够实施的基础,因此说主从复制是Redis高可用的基石。
缺点
不能主从自动切换,一旦 主节点宕机,从节点 晋升成 主节点,同时需要修改 应用方 的 主节点地址,还需要命令所有 从节点 去 复制 新的主节点,整个过程需要 人工干预。
主节点 的 写能力 受到 单机的限制
主节点 的 存储能力 受到 单机的限制
从库给主库发送 psync 命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。psync 命令包含了主库的 runID 和复制进度 offset 两个参数;runID是每个redis的实列,offset=-1代表全量复制
主库收到 psync 命令后,会用 FULLRESYNC 响应命令带上两个参数:主库 runID 和主库目前的复制进度 offset,返回给从库。从库收到响应后,会记录下这两个参数
在主库将数据同步给从库的过程中,主库会在内存中用专门的 replication buffer,记录 RDB 文件生成后收到的所有写操作,当主库完成 RDB 文件发送后,就会把此时 replication buffer 中的修改操作发给从库,从库再重新执行这些操作。这样一来,主从库就实现同步了
一旦主从库完成了全量复制,它们之间就会一直维护一个网络连接,主库会通过这个连接将后续陆续收到的命令操作再同步给从库,这个过程也称为基于长连接的命令传播,可以避免频繁建立连接的开销
主从库间网络断了怎么办?
增量复制
当主从库断连后,主库会把断连期间收到的写操作命令,写入 replication buffer,同时也会把这些操作命令也写入 repl_backlog_buffer 这个缓冲区
repl_backlog_buffer 是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己已经读到的位置。
当网络再次连接时,从库会发送psync命令,并将读到的位置告诉主库,主库会判断是增量复制还是全量复制
哨兵
做了什么?
监控整个Redis集群,发现故障,自动进行故障转移,简单概括:有三个职责,监控,选主,通知
怎么做的?
每秒向主,从,其他哨兵发送PING命令
响应时间超过指定值被认定为主观下线
如果是主服务器被标记为主观下线,那么正在监视这个主服务器的其他哨兵也会给这个主服务器发送PING命令
如果超过足够数量的哨兵认为这个主服务器主观下线,就会将该主服务器标记为客观下线
根据从库的优先级以及从库和旧主库的数据同步程度选出新的主库
哨兵节点
基于 pub/sub 机制的哨兵集群组成过程
基于 INFO 命令的从库列表,这可以帮助哨兵和从库建立连接
基于哨兵自身的 pub/sub 功能,这实现了客户端和哨兵之间的事件通知
切片集群
切片集群,也叫分片集群,就是指启动多个 Redis 实例组成一个集群,然后按照一定的规则,把收到的数据划分成多份,每一份用一个实例来保存
纵向扩展(scale up)
级单个 Redis 实例的资源配置,包括增加内存容量、增加磁盘容量、使用更高配置的 CPU。就像下图中,原来的实例内存是 8GB,硬盘是 50GB,纵向扩展后,内存增加到 24GB,磁盘增加到 150GB
缺点:数据持久化时由于内容较多,会阻塞redis主线程
横向扩展(scale out)
横向增加当前 Redis 实例的个数,就像下图中,原来使用 1 个 8GB 内存、50GB 磁盘的实例,现在使用三个相同配置的实例
Redis Cluster
用于实现切片集群
规定了数据和实例的对应规则
采用哈希槽(Hash Slot),来处理数据和实例之间的映射关系
首先根据键值对的 key,按照CRC16 算法计算一个 16 bit 的值
再用这个 16bit 值对 16384 取模,得到 0~16383 范围内的模数,每个模数代表一个相应编号的哈希槽
子主题
持久化方式
AOF
写后日志,先写命令再写日志,开源避免日志里的命令是错误的
存的是所有写命令
会导致文件很大
AOF 重写机制
会重新创建一个AOF文件,读取数据库中的所有键值对,然后对每一个键值对用一条命令记录它的写入
文件小,将之前AOF对同一个key的多条命令变成一条,做数据恢复时会比较块
主线程 fork 出后台的 bgrewriteaof 子进程执行
写时复制,一个拷贝,两处日志
fork时会阻塞主线程
日志写回方式
Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘
会影响redis性能
Everysec,每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘
可能会丢失1s的数据
No,操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
宕机后缓冲区的内容就没有了
主线程操作
需要顺序、逐一重新执行操作命令
RDB
内存快照,写时复制技术(Copy-On-Write, COW)
save
在主线程中执行,会导致阻塞,只能执行查找
bgsave
创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 Redis RDB 文件生成的默认配置。
快照生成间隔期间数据会丢失
记录的时最终数据,不需要按顺序逐一执行,效率高
AOF和RDB混合使用
RDB快照生成后AOF文件会被清空
数据基本不会丢失
数据存储类型
全局哈希表
Redis 使用了一个全局哈希表(一个哈希表实则是一个数组,数组的每个元素称为哈希桶)来保存所有键值对。哈希表的最大好处很明显,就是让我们可以用 O(1) 的时间复杂度来快速查找到键值对
哈希冲突
链式哈希:指同一个哈希桶中的多个元素用一个链表来保存,它们之间依次用指针连接
缺点:链表上的元素只能逐一查找,当哈希冲突多时,链表就会很长,会影响效率
渐进式 rehash
Redis 默认使用了两个全局哈希表:哈希表 1 和哈希表 2。一开始,当你刚插入数据时,默认使用哈希表 1,此时的哈希表 2 并没有被分配空间。随着数据逐步增多,Redis 开始执行 rehash,给哈希表2分配更大的空间,如是哈希表1的两倍,把hash1中的数据采用渐进式rehash拷贝到哈希表2中,然后释放哈希表1的空间,为后续rehash做准备。
每处理一个请求时,就从hash1中的第一个索引位置,顺带将这个索引位置上的所有entries拷贝到hash2中。渐进式rehash不会阻塞客户端的请求
底层数据结构
简单动态字符串
时间复杂度O(1)
哈希表
时间复杂度O(1)
压缩列表
类似于数组
压缩列表在表头有三个字段 zlbytes、zltail 和 zllen,分别表示列表长度、列表尾的偏移量和列表中的 entry 个数;压缩列表在表尾还有一个 zlend,表示列表结束。
在压缩列表中,如果我们要查找定位第一个元素和最后一个元素,可以通过表头三个字段的长度直接定位,复杂度是 O(1)。而查找其他元素时,就没有这么高效了,只能逐个查找,此时的复杂度就是 O(N) 了
数组和压缩列表都是非常紧凑的数据结构,比链表占用的内存要更少,提高内存的利用率
双向链表
有序链表,时间复杂度O(N)
整数数组
有序链表,时间复杂度O(N)
数组和压缩列表都是非常紧凑的数据结构,提高内存的利用率
跳表
有序链表只能逐一查找元素,导致操作起来非常缓慢,于是就出现了跳表。具体来说,跳表在链表的基础上,增加了多级索引,通过索引位置的几个跳转,实现数据的快速定位,时间复杂度就是 O(logN)
Value值类型
String(字符串)
简单动态字符串
Hash(哈希)
哈希表
压缩列表
List(列表)
压缩列表
双向链表
Set(集合)
哈希表
整数数组
Zset (sorted set:有序集合)
压缩列表
跳表
慢操作
慢查询命令
用其他高效命令代替。比如说,如果你需要返回一个 SET 中的所有成员时,不要使用 SMEMBERS 命令,而是要使用 SSCAN 多次迭代返回,避免一次返回大量数据,造成线程阻塞。
当你需要执行排序、交集、并集操作时,可以在客户端完成,而不要用 SORT、SUNION、SINTER 这些命令,以免拖慢 Redis 实例
KEYS。它用于返回和输入模式匹配的所有 key,因为 KEYS 命令需要遍历存储的键值对,所以操作延时高,KEYS 命令一般不被建议用于生产环境中
过期 key 操作
大批量的key同时间内过期,导致删除过期key的机制一直触发,引起redis操作阻塞
持久化
AOF使用awalys机制
内存大小
如果 Redis 的内存不够用了,操作系统会启动 swap 机制,这就会直接拖慢 Redis
总结
关于如何分析、排查、解决Redis变慢问题,我总结的checklist如下:
1、使用复杂度过高的命令(例如SORT/SUION/ZUNIONSTORE/KEYS),或一次查询全量数据(例如LRANGE key 0 N,但N很大)
分析:a) 查看slowlog是否存在这些命令 b) Redis进程CPU使用率是否飙升(聚合运算命令导致)
解决:a) 不使用复杂度过高的命令,或用其他方式代替实现(放在客户端做) b) 数据尽量分批查询(LRANGE key 0 N,建议N<=100,查询全量数据建议使用HSCAN/SSCAN/ZSCAN)
2、操作bigkey
分析:a) slowlog出现很多SET/DELETE变慢命令(bigkey分配内存和释放内存变慢) b) 使用redis-cli -h $host -p $port --bigkeys扫描出很多bigkey
解决:a) 优化业务,避免存储bigkey b) Redis 4.0+可开启lazy-free机制
3、大量key集中过期
分析:a) 业务使用EXPIREAT/PEXPIREAT命令 b) Redis info中的expired_keys指标短期突增
解决:a) 优化业务,过期增加随机时间,把时间打散,减轻删除过期key的压力 b) 运维层面,监控expired_keys指标,有短期突增及时报警排查
4、Redis内存达到maxmemory
分析:a) 实例内存达到maxmemory,且写入量大,淘汰key压力变大 b) Redis info中的evicted_keys指标短期突增
解决:a) 业务层面,根据情况调整淘汰策略(随机比LRU快) b) 运维层面,监控evicted_keys指标,有短期突增及时报警 c) 集群扩容,多个实例减轻淘汰key的压力
5、大量短连接请求
分析:Redis处理大量短连接请求,TCP三次握手和四次挥手也会增加耗时
解决:使用长连接操作Redis
6、生成RDB和AOF重写fork耗时严重
分析:a) Redis变慢只发生在生成RDB和AOF重写期间 b) 实例占用内存越大,fork拷贝内存页表越久 c) Redis info中latest_fork_usec耗时变长
解决:a) 实例尽量小 b) Redis尽量部署在物理机上 c) 优化备份策略(例如低峰期备份) d) 合理配置repl-backlog和slave client-output-buffer-limit,避免主从全量同步 e) 视情况考虑关闭AOF f) 监控latest_fork_usec耗时是否变长
7、AOF使用awalys机制
分析:磁盘IO负载变高
解决:a) 使用everysec机制 b) 丢失数据不敏感的业务不开启AOF
8、使用Swap
分析:a) 所有请求全部开始变慢 b) slowlog大量慢日志 c) 查看Redis进程是否使用到了Swap
解决:a) 增加机器内存 b) 集群扩容 c) Swap使用时监控报警
9、进程绑定CPU不合理
分析:a) Redis进程只绑定一个CPU逻辑核 b) NUMA架构下,网络中断处理程序和Redis进程没有绑定在同一个Socket下
解决:a) Redis进程绑定多个CPU逻辑核 b) 网络中断处理程序和Redis进程绑定在同一个Socket下
10、开启透明大页机制
分析:生成RDB和AOF重写期间,主线程处理写请求耗时变长(拷贝内存副本耗时变长)
解决:关闭透明大页机制
11、网卡负载过高
分析:a) TCP/IP层延迟变大,丢包重传变多 b) 是否存在流量过大的实例占满带宽
解决:a) 机器网络资源监控,负载过高及时报警 b) 提前规划部署策略,访问量大的实例隔离部署
1、使用复杂度过高的命令(例如SORT/SUION/ZUNIONSTORE/KEYS),或一次查询全量数据(例如LRANGE key 0 N,但N很大)
分析:a) 查看slowlog是否存在这些命令 b) Redis进程CPU使用率是否飙升(聚合运算命令导致)
解决:a) 不使用复杂度过高的命令,或用其他方式代替实现(放在客户端做) b) 数据尽量分批查询(LRANGE key 0 N,建议N<=100,查询全量数据建议使用HSCAN/SSCAN/ZSCAN)
2、操作bigkey
分析:a) slowlog出现很多SET/DELETE变慢命令(bigkey分配内存和释放内存变慢) b) 使用redis-cli -h $host -p $port --bigkeys扫描出很多bigkey
解决:a) 优化业务,避免存储bigkey b) Redis 4.0+可开启lazy-free机制
3、大量key集中过期
分析:a) 业务使用EXPIREAT/PEXPIREAT命令 b) Redis info中的expired_keys指标短期突增
解决:a) 优化业务,过期增加随机时间,把时间打散,减轻删除过期key的压力 b) 运维层面,监控expired_keys指标,有短期突增及时报警排查
4、Redis内存达到maxmemory
分析:a) 实例内存达到maxmemory,且写入量大,淘汰key压力变大 b) Redis info中的evicted_keys指标短期突增
解决:a) 业务层面,根据情况调整淘汰策略(随机比LRU快) b) 运维层面,监控evicted_keys指标,有短期突增及时报警 c) 集群扩容,多个实例减轻淘汰key的压力
5、大量短连接请求
分析:Redis处理大量短连接请求,TCP三次握手和四次挥手也会增加耗时
解决:使用长连接操作Redis
6、生成RDB和AOF重写fork耗时严重
分析:a) Redis变慢只发生在生成RDB和AOF重写期间 b) 实例占用内存越大,fork拷贝内存页表越久 c) Redis info中latest_fork_usec耗时变长
解决:a) 实例尽量小 b) Redis尽量部署在物理机上 c) 优化备份策略(例如低峰期备份) d) 合理配置repl-backlog和slave client-output-buffer-limit,避免主从全量同步 e) 视情况考虑关闭AOF f) 监控latest_fork_usec耗时是否变长
7、AOF使用awalys机制
分析:磁盘IO负载变高
解决:a) 使用everysec机制 b) 丢失数据不敏感的业务不开启AOF
8、使用Swap
分析:a) 所有请求全部开始变慢 b) slowlog大量慢日志 c) 查看Redis进程是否使用到了Swap
解决:a) 增加机器内存 b) 集群扩容 c) Swap使用时监控报警
9、进程绑定CPU不合理
分析:a) Redis进程只绑定一个CPU逻辑核 b) NUMA架构下,网络中断处理程序和Redis进程没有绑定在同一个Socket下
解决:a) Redis进程绑定多个CPU逻辑核 b) 网络中断处理程序和Redis进程绑定在同一个Socket下
10、开启透明大页机制
分析:生成RDB和AOF重写期间,主线程处理写请求耗时变长(拷贝内存副本耗时变长)
解决:关闭透明大页机制
11、网卡负载过高
分析:a) TCP/IP层延迟变大,丢包重传变多 b) 是否存在流量过大的实例占满带宽
解决:a) 机器网络资源监控,负载过高及时报警 b) 提前规划部署策略,访问量大的实例隔离部署
缓存污染
定义:在一些场景下,有些数据被访问的次数非常少,甚至只会被访问一次。当这些数据服务完访问请求后,如果还继续留存在缓存中的话,就只会白白占用缓存空间。这种情况,就是缓存污染
影响:会有大量不再访问的数据滞留在缓存中。如果这时数据占满了缓存空间,我们再往缓存中写入新数据时,就需要先把这些数据逐步淘汰出缓存,这就会引入额外的操作时间开销,进而会影响应用的性能
问题处理:缓存的淘汰策略
noeviction
不会进行数据淘汰
volatile-random
从已设置过期时间的数据集中任意选择数据淘汰。
allkeys-random
从数据集中任意选择数据淘汰
volatile-ttl
针对的是设置了过期时间的数据,把这些数据中剩余存活时间最短的筛选出来并淘汰掉
将访问次数少的key,设置过期时间,可以有效避免缓存污染
LRU 算法
volatile-lru
从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。
allkeys-lru
从数据集中挑选最近最少使用的数据淘汰
核心思想:如果一个数据刚刚被访问,那么这个数据肯定是热数据,还会被再次访问。按照这个核心思想,Redis 中的 LRU 策略,会在每个数据对应的 RedisObject 结构体中设置一个 lru 字段,用来记录数据的访问时间戳。在进行数据淘汰时,LRU 策略会在候选数据集中淘汰掉 lru 字段值最小的数据(也就是访问时间最久的数据)
LFU 算法
volatile-lfu
从已设置过期时间的数据集挑选使用频率最低的数据淘汰
allkeys-lfu
从数据集中挑选使用频率最低的数据淘汰
核心思想:LFU 缓存策略是在 LRU 策略基础上,为每个数据增加了一个计数器,来统计这个数据的访问次数,从两个维度来筛选并淘汰数据:一是,数据访问的时效性(访问时间离当前时间的远近);二是,数据的被访问次数。当使用 LFU 策略筛选淘汰数据时,首先会根据数据的访问次数进行筛选,把访问次数最低的数据淘汰出缓存。如果两个数据的访问次数相同,LFU 策略再比较这两个数据的访问时效性,把距离上一次访问时间更久的数据淘汰出缓存
命令
keys
会造成线程阻塞
原理就是扫描整个redis里面所有的db的key数据,然后根据我们的通配的字符串进行模糊查找出来
一次返回
scan
不会阻塞主线程
迭代返回
返回的数据可能会重复
0 条评论
下一页