Redis核心技术与实战
Redis功能
hyperloglog
用户统计基数的数据集合类型
pfadd 增加计算
pfcount 统计数据
example:
pfadd uv user1
pfcout uv
pfmerge 合并多个值
code:
1 | PFADD page1:uv user1 user2 user3 |
布隆过滤器
场景:判断某个值在不在集合中,有误差,比如推荐系统,过滤用户已经看过的新闻
redis命令
添加元素
bf.add news user1
判断是否在集合中
bf.exists news users
添加多个元素
bf.madd news user1 user2 user3
判断多个元素是否在集合中
bf.mexists news user1 user2 user3
redis做消息队列
1 | 基于List做消息队列 |
INFO memory
Memory
used_memory:1073741736
used_memory_human:1024.00M
used_memory_rss:1997159792
used_memory_rss_human:1.86G
…
mem_fragmentation_ratio:1.86
1 | 这里有一个 mem_fragmentation_ratio 的指标,它表示的就是 Redis 当前的内存碎片率。那么,这个碎片率是怎么计算的呢?其实,就是上面的命令中的两个指标 used_memory_rss 和 used_memory 相除的结果。 |
mem_fragmentation_ratio = used_memory_rss/ used_memory
```
首先,Redis 需要启用自动内存碎片清理,可以把 activedefrag 配置项设置为 yes,命令如下:config set activedefrag yes
- active-defrag-ignore-bytes 100mb:表示内存碎片的字节数达到 100MB 时,开始清理;active-defrag-threshold-lower 10:表示内存碎片空间占操作系统分配给 Redis 的总空间比例达到 10% 时,开始清理。
- active-defrag-cycle-min 25: 表示自动清理过程所用 CPU 时间的比例不低于 25%,保证清理能正常开展;
- active-defrag-cycle-max 75:表示自动清理过程所用 CPU 时间的比例不高于 75%,一旦超过,就停止清理,从而避免在清理时,大量的内存拷贝阻塞 Redis,导致响应延迟升高。
缓存区
输入缓冲区溢出:
原因:
- 写入了 bigkey,比如一下子写入了多个百万级别的集合类型数据;
- 服务器端处理请求的速度过慢,例如,Redis 主线程出现了间歇性阻塞,无法及时处理正常发送的请求,导致客户端发送的请求在缓冲区越积越多。
解决办法 - 调整客户端缓存大小,这个一般是1GB,但是对于一般的生成环境够用了,也没有参数来调整
- 避免客户端写入bigkey
输出缓冲区溢出:
原因:
- 服务器端返回 bigkey 的大量结果;
- 执行了 MONITOR 命令;
- 缓冲区大小设置得不合理。
解决办法:
- 避免 bigkey 操作返回大量数据结果;
- 避免在线上环境中持续使用 MONITOR 命令。
- 使用 client-output-buffer-limit 设置合理的缓冲区大小上限,或是缓冲区连续写入时间和写入量上限。
主从复制也会导致缓存区溢出
- 控制主节点保存的数据量大小
- 设置合理的复制缓存区大小
- 控制从节点数量
Redis缓存
Redis用户缓存需要解决如下几个问题:
- Redis 缓存具体是怎么工作的?
- Redis 缓存如果满了,该怎么办?
- 为什么会有缓存一致性、缓存穿透、缓存雪崩、缓存击穿等异常,该如何应对?
- Redis 的内存毕竟有限,如果用快速的固态硬盘来保存数据,可以增加缓存的数据量,那么,Redis 缓存可以使用快速固态硬盘吗?
解决缓存问题
解决缓存不一致
操作顺序 | 是否有并发请求 | 潜在问题 | 现象 | 应对方案 |
---|---|---|---|---|
先删除缓存值,再更新数据库 | 无 | 缓存删除成功,但数据库更新失败 | 应用从数据库读取到旧数据 | 重试数据库更新 |
先删除缓存值,再更新数据库 | 有 | 缓存删除后,尚未更新数据库,有并发读请求 | 并发请求从数据库读到旧值,并且更新到缓存,导致后续请求都读取旧值 | 延迟双删除 |
先更新数据库,再删除缓存 | 无 | 数据库更新成功,但缓存删除失败 | 应用从缓存读到旧数据 | 重试缓存删除 |
先更新数据库,再删除缓存 | 有 | 数据库更新成功后,尚未删除缓存,有并发读请求 | 并发请求从缓存中读到旧值 | 等待缓存删除完成,期间会有不一致数据短暂存在 |
解决缓存雪崩, 缓存击穿,缓存穿透
| 问题 | 原因 | 应对方案 |
|:–|:–|:–|:–|
| 缓存雪崩 | * 大量数据同时过期 * 缓存实例宕机 | 1. 给缓存数据的过期时间加上小的随机数 2. 服务降级 3. 服务熔断 4. 请求限流 5. Redis缓存主从集群 |
|缓存击穿 | 访问非常频繁的热点数据过期 |不给热点数据设置过期时间,一直保留 |
|缓存穿透|缓存和数据库中都没有要访问的数据|1. 缓存空值或缺省值 2. 请使用布隆过滤器快速判断 3. 请求入口前端对请求合法性进行检查|
解决缓存污染问题
那什么是缓存污染呢?在一些场景下,有些数据被访问的次数非常少,甚至只会被访问一次。当这些数据服务完访问请求后,如果还继续留存在缓存中的话,就只会白白占用缓存空间。这种情况,就是缓存污染。
如何解决:
LRU 策略和 LFU 策略
大内存Redis
使用ssd解决大内存Redis问题
https://github.com/OpenAtomFoundation/pika
Redis应对并发
- 使用锁
- 使用原子操作, Lua脚本
分布式锁
为了避免 Redis 实例故障而导致的锁无法工作的问题,Redis 的开发者 Antirez 提出了分布式锁算法 Redlock。Redlock 算法的基本思路,是让客户端和多个独立的 Redis 实例依次请求加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁了,否则加锁失败。这样一来,即使有单个 Redis 实例发生故障,因为锁变量在其它实例上也有保存,所以,客户端仍然可以正常地进行锁操作,锁变量并不会丢失。
ACID
Redis通过MULTI, EXEC, DISCARD,WATCH四个命令来支持事务机制
命令 | 作用 |
---|---|
MULTI | 开启一个事务 |
EXEC | 提交事务,从命令队列中取出提交的操作命令,进行实际执行 |
DISCARD | 放弃一个事务,清空命令队列 |
WATCH | 检测一个或多个键的值在事务执行期间是否发生变化,如果发生变化,那么当前事务放弃执行 |
Redis 的事务机制可以保证一致性和隔离性,但是无法保证持久性。不过,因为 Redis 本身是内存数据库,持久性并不是一个必须的属性,我们更加关注的还是原子性、一致性和隔离性这三个属性。原子性的情况比较复杂,只有当事务中使用的命令语法有误时,原子性得不到保证,在其它情况下,事务都可以原子性执行。
主从故障
- 主从数据不一致。Redis 采用的是异步复制,所以无法实现强一致性保证(主从数据时时刻刻保持一致),数据不一致是难以避免的。我给你提供了应对方法:保证良好网络环境,以及使用程序监控从库复制进度,一旦从库复制进度超过阈值,不让客户端连接从库。
- 对于读到过期数据,这是可以提前规避的,一个方法是,使用 Redis 3.2 及以上版本;另外,你也可以使用 EXPIREAT/PEXPIREAT 命令设置过期时间,避免从库上的数据过期时间滞后。不过,这里有个地方需要注意下,因为 EXPIREAT/PEXPIREAT 设置的是时间点,所以,主从节点上的时钟要保持一致,具体的做法是,让主从节点和相同的 NTP 服务器(时间服务器)进行时钟同步。
脑裂
所谓的脑裂,就是指在主从集群中,同时有两个主节点,它们都能接收写请求。而脑裂最直接的影响,就是客户端不知道应该往哪个主节点写入数据,结果就是不同的客户端会往不同的主节点上写入数据。而且,严重的话,脑裂会进一步导致数据丢失。
- min-slaves-to-write:这个配置项设置了主库能进行数据同步的最少从库数量;
- min-slaves-max-lag:这个配置项设置了主从库间进行数据复制时,从库给主库发送 ACK 消息的最大延迟(以秒为单位)。
假设我们将 min-slaves-to-write 设置为 1,把 min-slaves-max-lag 设置为 12s,把哨兵的 down-after-milliseconds 设置为 10s,主库因为某些原因卡住了 15s,导致哨兵判断主库客观下线,开始进行主从切换。同时,因为原主库卡住了 15s,没有一个从库能和原主库在 12s 内进行数据复制,原主库也无法接收客户端请求了。这样一来,主从切换完成后,也只有新主库能接收请求,不会发生脑裂,也就不会发生数据丢失的问题了。