前言
Redis数据是内存读写,基于Reactor模式和优化的数据结构,使得速度很快,存储的是KV键值对数据。
一、基本数据类型
1.1 String(字符串)
最简单也是最常用的数据结构,底层实现为简单动态字符串(Simple Dynamic String,SDS)。
SDS有五种实现方式:SDS_TYPE5、SDS_TYPE8、SDS_TYPE16、SDS_TYPE32、SDS_TYPE64,Redis会根据初始化的长度决定使用哪种类型,从而减少内存的使用。
使用场景:常规数据缓存、用户单位时间的计数、页面单位时间的计数、分布式锁。
1.2 List(列表)
双向链表,list的pop操作是原子性的。
使用场景:消息队列,秒杀缓存商品队列,list做分页查询。
1.3 Set(集合)
无序唯一的集合。
使用场景:去重的场景,实现交集、并集、差集的操作。
1.4 Hash(散列表)
对象的每个字段都单独存储,可获取部分字段信息。
使用场景:存储对象。
1.5 Zset(有序集合)
有序唯一的集合,通过跳表实现。
使用场景:排行榜。
二、持久化机制
2.1 持久化目的
- 重用数据,比如重启机器。
- 数据同步,Redis集群的主从节点通过RDB文件同步数据。
2.2 持久化方式
- RDB(快照):redis.conf默认配置中,触发条件为1/5/15分钟,其中如果有10000/10/1个key发生变化,则会再次触发。
RDB是Redis由子线程执行,并不会阻塞主线程。 - AOF(追加):每一条写命令,写入缓冲区中,再子线程每秒写入文件中。
- 混合:RDB+AOF
三、内存管理
Redis通过过期字典,保存数据的过期时间。字符串通过setex设置过期时间,其他方法依靠expire命令。
3.1 过期策略
- 定期删除:每隔一段时间,抽取一批key,检查过期则删除。
- 惰性删除:取出key时,检查过期则删除。
- 混合:定期+惰性
3.2 淘汰机制
过期策略依然会存在漏网之鱼,需要内存淘汰机制,避免OutOfMemory。主要为LRU(最近最少使用),LFU(使用频率最少),随机。
8中数据淘汰策略:
- volatile-lru:从已设置过期时间的数据集中,淘汰最近最少使用的数据。
- volatile-ttl:从已设置过期时间的数据集中,淘汰即将要过期的数据。
- volatile-random:从已设置过期时间的数据集中,淘汰任意数据。
- allkeys-lru:当内存不足以容纳新数据时,淘汰最近最少使用的数据。(最常用的)
- allkeys-random:从数据集中,淘汰任意数据。
- no-eviction:禁止淘汰数据。
- volatile-lfu:从以设置过期时间的数据集中,淘汰使用频率最少的数据。
- allkeys-lfu:当内存不足以容纳新数据时,淘汰使用频率最少的数据。
四、性能优化
4.1 批量操作减少网络传输
一个Redis命令的执行,可以简化为4步:发送命令,命令排队,命令执行,返回结果。
使用批量操作,可以减少第1、4步的网络IO。
- 原生批量操作命令
mget(获取多个指定key的值)、mset(设置多个指定key 的值)、hmget(获取哈希表中多个指定字段的值)、hmset(设置多个field-value)、sadd(添加多个元素) - pipeline
对于不支持批量操作的命令,可以利用pipeline(流水线),将一批Redis命令封装成一组,一次性的提交到Redis服务器。 - Lua脚本
批量操作多条命令。
4.2 大量key集中过期
定期删除,是由主线程执行,如果突然遇到大量key过期的话,客户端请求会被阻塞。
解决方案:
- key的过期时间随机设置。
- 开启lazy-free,删除操作交给子线程。
4.3 大key
一个key对应的value所占内存比较大,就可以看作是大key。
查找大key方式:
- Redis自带参数--bigkeys。
- 开源工具分析RDB文件,如redis-rdb-tools、rdb_bigkeys。
- 公有云的Redis分析服务。
处理大key方法:
- 分割大key:将大key分割成多个小key,需要修改业务代码。
- 手动清理:使用unlink命令异步删除。
- 采用合适的数据结构
- 开启lazy-free
4.4 热key
一个key的访问次数比较多,就可以看作的热key。
处理热key会占用大量的资源,可能会影响其他请求。如果热key的请求量超过了Redis的处理能力,Redis就会宕机。
查找热key方式:
- Redis自带参数--hotkeys。
- 使用monitor命令。
- 开源项目分析,如京东零售的hotkey。
- 根据业务情况预估。
- 公有云的Redis分析服务。
解决rekey方法:
- 读写分离。
- 使用Redis Cluster,将热点数据分散存储再多个Redis节点上。
- 二级缓存,将热key存放一份到JVM本地内存中(可以用Caffeine)。
4.5 慢查询命令
Redis中大部分命令都是O(1)的时间复杂度,也有少部分命令是O(n),如:
keys *:返回所有符合规则的key。
hgetall:返回一个Hash中所有的键值对。
lrange:返回List中指定范围内的元素。
smembers:返回Ser中的所有元素。
sinter/sunion/sdiff:甲酸多个Set的交集/并集/差集。
时间复杂度在O(n)以上的命令,如:
zrange/zrevrange:返回Sorted Set中指定排名范围内的所有元素。
zremrangebyrank/zremrangebyscore:移除Sorted Set中指定排名/范围内的所有元素。
查找慢查询方式:
- 在redis.conf文件中,使用参数slowlog-log-slower-than设置耗时命令的阈值,使用slowlog-max-len设置最大记录条数,这样就记录了慢查询日志(slow log),通过slowlog get命令获取日志。
4.6 内存碎片
产生内存碎片的原因:
- Redis存储数据时,向操作系统申请的内存空间,可能大于实际需要的空间。
- 频繁修改Redis中的数据。
查看碎片方式:
- 使用info memory命令
清理碎片方法:
- Redis自带的内存整理,activedefrag配置为yes
4.7 主从同步
- 增量同步:master维护一个缓冲区,记录slave复制的偏移量,当slave发来同步请求时,master发送偏移量之后的写命令。
- 全量同步:slave向master发送同步请求,master生成RDB文件,并将执行命令期间的写操作存到缓冲区,将RDB+缓冲区一起发送给slave。
五、常见问题
5.1 缓存穿透
大量请求的key不合理,既不在缓存中,也不在数据库中。
解决方法:
- 参数校验:对不合理的、不符合格式的参数,直接返回错误信息。
- 布隆过滤器:将数据存放在bloom中,判断不存在是100%。
5.2 缓存击穿
大量请求的key是热key,不在缓存中,但在数据库中。通常是过期造成的,也可能是未发现的热key。
解决方法:
- 热key永不过期,或者过期时间比较长。
- 热key提前预热。
- 请求数据库时,先获取互斥锁,保证只有一个请求会落到数据库上。
5.3 缓存雪崩
缓存在同一时间大面积失效,导致大量请求直接落到数据库上。
解决方法:
- Redis集群。
- 过期时间随机设置。
- 设置二级缓存。
5.4 缓存和数据库的一致性
遇到写请求时,先更新DB,然后删除cache。
如果更新DB成功,而删除cache失败,那么增加cache更新重试机制:如果Redis当前不可用导致的失败,可以隔一段时间重试;如果多次失败,将失败的key存入队列中,等Redis可用之后,在将key删除。
5.5 Redis阻塞
造成阻塞的原因:
- O(n)命令。
- 手动save生成RDB快照。
- AOF日志记录阻塞(主线程执行完命令之后再记录日志到磁盘)。
- AOF刷盘阻塞。
- AOF文件重写阻塞。
- 大key阻塞。
- 查找大key命令。
- 删除大key。