这是我参与「第五届青训营 」伴学笔记创作活动的第 11 天
Redis 是什么
为什么需要Redis
随着互联网业务场景的发展,对系统QPS的要求越来越高,系统架构从原来的单机数据库,发展到数据库主从集群(主服务器写,从服务器读)。
到最后提出把常用的数据放在内存中,需要的时候直接读取。
于是,就诞生了内存数据库,Redis。
特性
- 单线程处理所有操作命令。
数据结构
String 数据结构
Hash 数据结构
List 数据结构
Set 数据结构
Zset 数据结构
Redis 基本原理
架构
持久化实现
- 客户端输入 Redis 命令,会通过RESP协议发送指令到 redis 服务端。
- 服务端的服务进程接收到读写命令后,会将数据读写到内存中
- 如果是写操作,在写入之前,会追加一条写命令到 AOF 文件(循环写入,最新的会覆盖最久的)中。
- 如果发生宕机操作,首先会从 RDB 文件(全量备份,一般每隔5分钟同步一次)中恢复原来数据。
- 再读取 AOF 文件,如果发现有没有执行的 AOF 日志,就执行来同步数据。
应用场景
连续签到
使用 string 数据类型
消息通知
技术
排行榜
限流
分布式锁
Redis 使用注意事项
大Key
大Key标准
- string类型
- value 的字节数大于10KB即为大key
- Hash/Set/Zset/list等复杂数据结构
- 元素个数大于5000个或总value字节数大于10MB即为大Key。
大Key危害
原因
redis 是单线程程序所有命令,所以没处理完大key,其他命令都不能处理,会被阻塞掉。
总结
-
读取成本高
-
容易导致慢查询(过期、删除)
-
主从复制异常,服务阻塞。
- 主节点数据需要同步给从节点,对于大key,消耗大量网络资源、CPU资源。
-
访问缓存数据超时
怎么解决大key
KV 数据结构
拆分
将一个大key拆分为小key。
如图,将 article:70011 拆分为 article:70011、article:70011_2、article:70011_3。 其中第一个 key 的值的前缀 [3] 代表key被拆分成了三份,并紧跟id [70011]。
这样,就可以推断出拆分后的两个 key 的名字。
最后将结果拼接就可以得到原来的数据。
缺点
- 拼接数据这个逻辑太复杂了(维护多个key,拼接数据)。
- 解析[3] 这个字符消耗大量时间。
压缩
将 value 压缩后再存入 key,读取的时候解压缩。
性能优化
选择合适的解压算法,由于读取的时间比较多,所以更关注解压性能,可以写入100key,然后反解压出来,看消耗时间。
json 字符串可以尝试 MessagePack
集合数据结构hash、list、set、zset
拆分
可以 hash 取余,将 key 分配到新的数据结构中。
比如 hash 得到 0、1,就可将得到0的,分到一个集合,结果为1的,分配到另一个集合。
区分冷热
像排行榜这种业务场景,可以使用 zset 只存前十页的数据,其他的数据,访问去数据库读取。
这样就可以减少 key 存储的数据量。
热 key
定义
一个 key 保存的数据大量被访问,如果此时 redis 做了分区,那么大量的流量就会打到集群的某一台机器上。
机器承载不住,就会出现问题。
QPS一般大于500的 Key,可以被称作热Key,没有固定标准。
热 key 会导致 CPU 负载突增或者不均的情况。
解决方案
Localcache
从 redis 中读取数据后,放到 JVM 或者 Golang 服务器的缓存中,下次再有相同的访问请求时,可以直接从本机读取,也就是本地缓存。
本地缓存可以承载比分布式缓存更大的压力,更高的QPS。
本地缓存通过缓存过期管理、缓存分片技术。
缓存过期管理会给本地缓存设置过期时间,一旦过期后,立马去 redis 里更新缓存。
拆分
对于一个热 Key,将这个 Key 拆分成多个 Key,数据分成多份进行存储。
这样做,原本只访问集群中一台服务器的请求,现在会访问到集群中多台服务器,实现了对高 qps 请求的负载均衡。
但这样做可能会导致,更新 key 的时候,多台服务器必须都更新成功,否则就会出现数据不一致的问题。这需要大量代码逻辑维护。
字节跳动实现 --使用 redis 代理的热 Key 承载能力
客户端所有的 redis Get 请求都会先发给 Proxy 服务器,由 Proxy 服务器将请求转发给 Redis 服务器执行。
Proxy 服务器会统计 Redis 中每个 Key 执行的次数,一旦达到某个阈值,比如500,就会将这个定义为热 Key。
Proxy 服务器就会将这个键值对保存在本机的本地缓存中。
下次再有请求访问时,直接返回,就实现了对热 Key 的处理逻辑。
慢查询
容易导致 redis 慢查询的操作
- 一次性传入过多的 key/value,一般规定单批次不要超过100个。
- zset 大部分命令都是o(log(n)),当大小超过5K以上时,普通的zadd、zrem 也可能导致慢查询。
- key 的单个 value 过大,超过10KB。
- 对大 key 的 delete/expire 操作也可能导致慢查询。
缓存穿透
简介
请求的 key 在redis,大量请求直接访问到数据库,可能会导致服务器宕机。
解决方案
- 缓存空值
- 布隆过滤器
缓存雪崩
简介
缓存在同一时间大量过期,就是缓存雪崩。
解决方案
- 缓存设置过期时间时在原有时间基础上加上随机值。
- 使用集群,避免单机宕机导致缓存雪崩。