这是我参与「第五届青训营 」伴学笔记创作活动的第 10 天
1 Redis简介
随着数据量增加、读写压力的不断增加,MySQL服务器或者MySQL集群的压力越来越大。为此我们可以将数据分冷热,其中热数据就是经常被访问的数据,我们将热数据存储到内存中,这时就会使用到Redis数据库。
1.1 读写场景
读场景下,我们先从Redis中读取数据,如果Redis数据库中没有再读MySQL数据;而写场景下,服务端先写入MySQL数据,然后通过监听binlog来修改Redis数据
1.2 工作原理
- 数据从内存中读写;
- 数据保存到硬盘上防止重启数据丢失,Redis会将增量数据保存到AOF文件,将全量数据保存到RDB文件中;
- 单线程处理所有操作命令,按时间顺序执行。
2. 应用案例
连续签到、消息通知、计数、排行榜、限流、分布式锁
示例代码:redis_course: 青训营redis课程Demo (gitee.com)
2.1 连续签到
业务逻辑:每个用户每天进行签到,断签计数将归零,连续签到必须在每天的23:59:59前;
Redis设计:数据结构为String,可以存储字符串、数字、二进制数据等,通常和expire配合使用,应用场景有存储计数、Session等;
- key: cc_uid_xxxxxxxxxxxx
- value: 252
- expireAt: 当前时间往后的后天时间0点
2.2 消息通知
业务逻辑:当文章更新时,将更新后的文章推送到ES,用户就能搜索到最新的文章数据;
Redis设计:数据结构为list,其实现由一个双线链表和listpack实现;
2.3 计数
业务逻辑:文章的被点赞数、文章的被阅读数、用户的关注数、用户的收藏数等的计数;
Redis设计:数据结构为hash,将文章的被点赞数、文章的被阅读数、用户的关注数、用户的收藏数等存入一个hash结构里面,每次通过key将所有计数信息读取出来;
2.4 排行榜
业务逻辑:积分变化时,进行排名的实时更新;
Redis设计:使用dict实现通过key操作跳表的功能;
2.5 限流
业务逻辑:假设1秒内放行的请求数为N,超过N则禁止;
Redis设计:构建一个key,添加当前时间的时间戳(精确到秒),对该Key调用incr,超过N则禁止访问;
2.6 分布式锁
业务逻辑:并发场景下要求一次只能有一个协程执行,执行完成后其他等待中的协程才能执行;
Redis设计:利用Redis的单线程执行命令以及setnx只有未设置过才能执行的特性实现分布式锁;
3. Redis注意事项
3.1 大Key
定义:string类型的value字节数大于10KB,或者复杂数据结构元素个数大于5000个或者总value字节数大于10MB的Key极为大Key。大Key会导致读取成本高容易导致慢查询,请求可能超时,并且由于Redis的单线程操作容易出现服务阻塞等问题。
处理方法:将大Key拆分为小Key,或者将value进行压缩。集合类结构例如hash、list、set等可以区分冷热数据,如榜单列表使用zset,只缓存前10页的数据,后续数据走db。
3.2 热Key
定义:当用户访问一个Key的QPS特别高时,会导致Server实例出现CPU负载突增或者不均的情况。
处理方法:(1)访问Redis前,在业务服务侧设置Localcache,降低访问Redis的QPS。LocalCache中缓存过期或未命中,则从Redis中将数据更新到LocalCache。(2)将热Key复制写入多份,例如key1:value、key2:value,访问的时候访问多个key,但value是同一个,以此将qps分散到不同实例上,降低负载,但是代价是更新时需要更新多个key,存在数据短暂不一致的风险。
3.3 慢查询
- 批量操作一次性传入过多的key/value,如mset/hmset/sadd/zadd等O(n)操作建议单批次不要超过100,超过100之后性能下降明显;
- zset大部分命令都是O(Iog(n)),当大小超过5k以上时,简单的zadd/zrem也可能导致慢查询;
- 操作的单个value过大,超过10KB,即避免使用大Key;
3.3 缓存穿透和缓存雪崩
缓存穿透即热点数据查询绕过缓存直接查询数据库,缓存雪崩即大量缓存同时过期。避免方法如下:
- 如一个不存在的userlD。这个id在缓存和数据库中都不存在。则可以缓存一个空值,下次再查缓存直接反空值;
- 通过bloom filter算法来存储合法Key,得益于该算法超高的压缩率,只需占用极小的空间就能存储大量key值;
- 将缓存失效时间分散开,比如在原有的失效时间基础上增加一个随机值,对于热点数据过期时间尽量设置得长一些,冷门的数据可以相对设置过期时间短一些;
- 使用缓存集群,避免单机宕机造成的缓存雪崩。