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