为什么Redis单线程效率还能那么高?
- 单线程操作,避免了上下文切换带来的CPU开销
- 纯内存操作,相较于数据库查询(访问磁盘),效率更高
- 基于非阻塞的IO复用模型机制
- 拥有丰富的数据结构
Redis的缺点
- 由于Redis是内存数据库,所以需要提前预估和节约内存。如果内存增长过快需要定期删除数据
- 如果进行完整重同步,由于需要生成RDB文件并进行传输,会占用主机CPU
- 修改配置文件需要重启,将硬盘中的数据加载进内存,时间比较长,这段时间内Redis不能提供服务
Redis的基本数据类型
常用基本数据类型(5种)
名称 | 英文名 | 作用域 |
---|---|---|
字符串 | String | 缓存、计数器、分布式Session |
哈希 | Hash | 存放对象 |
列表 | list | 消息队列、文章列表 |
集合 | set | 标签、随机数、社交图谱 |
有序集合 | ZSET | 排行榜 |
Redis6.0之前为什么不使用多线程?
- Redis的瓶颈并不是CPU,而是内存与网络
- 使用pipeline来提高Redis性能
- 单线程的维护成本低,不需要管理线程安全问题
- 一般的小公司,使用单线程Redis就足够了
Redis的高级功能
- 慢查询:快速定位系统中的慢操作,监测发生时间、耗时、命令的详细信息。
- pipeline管道:可以将多次IO往返的时间缩短为一次,但要求管道中执行的指令间没有因果关系
- watch命令:确保事务中的key没有被其他客户端修改过才执行事务,否则不执行
- 分布式锁:setnx命令实现,使用Redisson实现效果更佳。具体可见:http://t.csdn.cn/WSDHS
Redis的内存淘汰机制
Redis作为一个内存数据库,在内存空间不足的时候,为了保证命中率,就会和操作系统页面置换算法类似,选择一定的淘汰策略。
LFU(Least Frequently Used)算法根据数据的历史访问频率来淘汰数据,其核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
- no-enviction:禁止驱逐数据,这是默认策略。当内存不足以容纳新数据时,新写入操作就会报错。运行内存达到最大时,不回消除任何数据,且不会提供任何服务,并且直接返回错误。
- allkeys-lru:从所有 key 中使用 LRU 算法进行淘汰(LRU 算法:最近最少使用算法)
- allkeys-lfu: 从所有 key 中使用 LFU 算法进行淘汰
- volatile-lru: 从设置了过期时间的 key 中使用 LRU 算法进行淘汰
- volatile-lfu: 从设置了过期时间的 key 中使用 LFU 算法进行淘汰
- allkeys-random: 从所有 key 中随机淘汰数据;
- volatile-random: 从设置了过期时间的 key 中随机淘汰数据;
- volatile-ttl: 在设置了过期时间的key中,淘汰过期时间剩余最短的。
Redis过期策略
Redis中同时使用了惰性过期和定期过期两种过期策略。
- 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
- 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
- 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
BIGKey的危害?如何解决?
什么是 Bigkey
在Redis中,一个字符串类型最大可以到512MB,一个二级数据结构(比如hash、list、set、zset等)可以存储大约40亿个(2^32-1)个元素,但实际上不会达到这么大的值,一般情况下如果达到下面的情况,就可以认为它是Bigkey了。
【字符串类型】: 单个string类型的value值超过1MB,就可以认为是Bigkey。
【非字符串类型】:哈希、列表、集合、有序集合等, 它们的元素个数超过2000个,就可以认为是Bigkey。
- 网络阻塞:对BIGKey执行读请求时,少量的QPS就可能导致宽带使用率被占满,导致Redis实例,乃至所在物理机变慢
- 数据倾斜:BigKey所在的Redis实例内存使用率远超其他实例,无法使数据分片的内存资源达到均衡
- Redis阻塞:对元素较多的hash,list,zset等做运算会耗时较久,使主线程阻塞
- CPU压力:对BigKey的数据序列化和反序列化会导致CPU的使用率飙升,影响Redis实例和本机其他应用
解决方法:
Bigkey拆分
优化Bigkey的原则就是string减少字符串长度,list、hash、set、zset等减少元素数量。当我们知道哪些key是Bigkey时,可以把单个key拆分成多个key,比如以下拆分方式可以参考。
- big list:list1、list2、...listN
- big hash:可以做二次的hash,例如hash%100
- 按照日期拆分多个:key20220310、key20220311、key202203212
Redis解决Key冲突
- 业务隔离
- key的设计: 业务模块+系统名称+关键(id),针对用户可以加入(userid)
- 分布式锁:场景:多个客户端并发写key 客户端拿到锁,才能进行操作,避免多个客户端竞争该key
- 时间戳:key拼接时间戳,根据时间戳保证多个客户端的业务执行顺序
如何提高缓存命中率
- 提前加载
- 增加缓存的存储空间,增加缓存的数据
- 调整缓存的存储类型,如对象通过Hash存储
缓存与数据库双写一致性
对于缓存操作分成两种:删除缓存和更新缓存(一般不考虑)
删除缓存:
先删缓存后更新DB(延时双删解决问题)
先更新DB后删缓存(问题出现率相对较少)
Redis Cluster 集群
Redis Cluster 中,Sharding 采用 slot (槽) 的概念,一共分成 16384 个槽,这有点儿类似 pre sharding 思路。对于每个进入 Redis 的键值对,根据 key 进行散列,分配到这 16384 个 slot 中的某一个中。使用的 hash 算法也比较简单,就是 CRC16 后 16384 取模 [crc16(key)%16384]。
假设现在我们是三个主节点分别是:A, B, C 三个节点,它们可以是一台机器上的三个端口,也可以是三台不同的服务器。那么,采用哈希槽 (hash slot) 的方式来分配 16384 个 slot 的话,它们三个节点分别承担的 slot 区间是:
- 节点 A 覆盖 0-5000;
- 节点 B 覆盖 5001-10000;
- 节点 C 覆盖 10001-16383
这时,如果节点 B 出现故障,整个集群就会出现缺少 5001 到 10000 的哈希槽范围而不可用。
如何将同一类数据固定的保存在同一个Redis实例上?
数据key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,分两种情况:
- key中包含"{}",且"{}"中至少包含一个字符,"{}"中的部分为有效部分
- key中不包含"{}",整个key都是有效部分
使这类数据使用相同的有效部分,例如key都以{typeId}为前缀即可保存在同一个Redis实例上
故障转移
故障迁移
Redis常见性能问题和解决方案
- 持久化 性能问题
- 早期仅支持全量复制->部分复制(一台机器性能开销过大)
- 因此开始配置主从 :主节点不再做持久化而是交给从节点来做
- 数据比较重要,开启AOF。策略最好配置每秒同步。
- 主从复制 流畅,建议同一个局域网内操作,负责网络开销过大
- 尽量避免主库压力过大,增加从库
- 主从复制 尽量不要使用网状结构、线性结构
线上Redis响应慢处理思路
1、紧急处理方案,扩容
2、生产环境查看Redis内存使用率,分析一定时间段内key数量变化分析是否是大量数据未设置过期时间,或者是因为新版本迭代引起
3、清除bigkey,优化生成bigkey的代码块,调整未设置过期时间的代码块
4、根据业务场景调整淘汰策略