这是我参与「第五届青训营 」伴学笔记创作活动的第 17 天
Redis
Redis是什么
速度催生Redis
问题
单表数据->分库分表
MySQL单机->MySQL集群(数据量增长、读写数据压力不断增加)
大量数据的读取走MySQL,从磁盘取出,速度太慢
解决
将经常访问的热数据直接存储到Redis(内存)中,冷数据存在MySQL中
在读数据时就可以先访问Redis,没有在访问MySQL,大大提高读取速度
在写入时也可以通过监听Binlog从而使写入的数据保存到Redis中
Redis基本工作原理
-
数据从内存中读写
-
单线程处理所有操作命令(按命令先后顺序执行)
-
Redis两大文件存放在磁盘中防止重启数据丢失(解决了重启导致的内存清空问题,一定程度上实现了数据持久化)
- AOF文件,保存增量数据,如果执行失败,重启时会重新执行
- RDB文件,保存Redis所有数据,重启时会读取该文件
Redis应用案例
扩展:我们在不同情况使用不同数据结构的原因,一是提升性能、一是压缩存储
数据结构-sds(Java-String)
- 可以存储字符串、数字、二进制
- 和expire配合使用
- 使用场景:存储计数、Session
消息通知
使用list作为消息队列,实现消息通知
Quicklist由一个双向链表和listpack实现
在Redis中,链表的一个节点不止存放一个数据,而是使用Listpack结构存储大量数据
计数
使用hash数据结构实现
hash表满了之后存储数据会产生哈希冲突,可以通过相同节点下拉链表解决,但是链表太长严重影响hash查询速度,正常是O(1),因此当链表达到一定长度,会进行扩容,在扩容过程中会将链表数据存到新的节点(数据迁移),这就涉及到rehash,新建一个hash表,将旧表中的数据迁移到新表,但是扩容不能影响用户正常使用,因此需要渐进式rehash,基本原理就是,每次用户访问时都会迁移少量数据,将整个迁移过程,平摊到所有的访问过程。
排行榜
使用zset数据结构实现
限流
分布式锁
使用setnx实现,两个特性(Redis是单线程执行命令、setnx只有key未设置过才能执行成功)
存在的缺陷:业务超时解锁导致并发问题,业务执行时间超过锁超时时间、redis主备切换临界点问题,主备切换后,A持有的锁还未同步到新的主节点时,B可以在新主节点获取点,从而导致并发访问、redis集群脑裂,导致出现多个主节点
Redis使用注意事项
1.大Key
| 数据类型 | 大Key标准 |
|---|---|
| String类型 | value的字节数大于10kb |
| Hash/Set/Zset/list等复杂数据结构 | 元素个数大于5000或总value字节数大于10MB |
危害
- 读取成本高
- 容易导致慢查询(过期、删除)
- 主从复制异常,服务器阻塞,无法正常响应请求
- 请求Redis超时报错
解决
拆分、压缩(主要考虑解压缩问题,因为Redis写少读多)、冷热分离(zset只缓存前十页数据,后续走db)
2.热Key
用户访问一个Key的QPS特别高,导致Server实例出现cpu负载突增或者不均的情况,这种Key就是热Key
解决
1.设置Localcache
在业务服务侧设置Localcache,降低Redis的QPS。Localcache未命中或缓存过期,再从Redis更新数据。Java的Guava、Golang的Bigcache就是这类LocalCache
2.拆分
将热Key复制到多个实例中,降低单个实例的负载
3.Redis代理
具备热Key承载能力,具备热Key发现并且自己是实现了LocalCache功能
3,慢查询场景
批量操作一次性传入过多Key/Value,性能会明显下降、zset命令时间复杂度大部分都是O(log(n))等
4.缓存穿透、缓存雪崩
缓存穿透:热点数据查询绕过缓存,直接查询数据库
危害:查询不存在的数据,或者缓存过期时,大量数据会直接走db,短时间大量访问db会导致db宕机
缓存雪崩:大量缓存同时过期
解决
-
减少缓存穿透
- 缓存空值,数据在缓存和数据库都不存在,下次再访问直接返回空值
- 布隆过滤器,使用Bloom filter算法存储合法Key,该算法具有超高压缩率,占用极小空间就能存储大量key值
-
避免缓存雪崩
- 缓存空值,将缓存失效时间分散开
- 使用缓存集群,避免单机宕机造成的缓存雪崩