Redis 补充
Redis 为什么快?
- 内存操作
- 优化的数据结构
- 单线程事件循环 + IO 多路复用(命令执行永远单线程,所有原子性)
Redis 架构模式 * 4
standalone
master/replica 可以 写主读从
master 会将 写操作同步 到 replica;每次添加 replica 可能会经历 全量复制 的步骤,后续再增量复制
sentinel
解决问题:master 宕机后写失败,只能读 replica。需要需要人工介入重选 master 并重新配置所有 client 的 master IP。sentinel 自动执行这些步骤,实现 主从故障转移
哨兵其实是一个运行在特殊模式下的 Redis 进程,所以它也是一个节点。哨兵节点主要负责三件事情:监控、选主、通知。
至少需要三台机器。master * 1 + replica * 2 + sentinel * 3 共 6 台机器(或 3 台机器,一台一个哨兵+一个主/从)
cluster
突破机器的内存限制(垂直扩容),实现水平扩容,每个节点承担部分数据的存储,而且负载均衡
Redis 消息队列 * 3
list, stream, Pub/Sub 三种模式
消息模型有两种
- 点对点: Point-to-Point(P2P)
- 发布订阅:Publish/Subscribe(Pub/Sub)
优缺:
list 仅支持一生产+一消费(类似 P2P)
使用 lpush, rpop/brpop 指令
pub/sub 可以多个消费者(使用 PUBLISH, SUBSCRIBE)
list 没有 ack 机制,可能会丢消息
解决:使用
B-RPOP-LPUSH <this-list> <back-list>
指令取内容(取之前先存一份备份,确定没问题了再 rpop back-list 里的数据)pub/sub 不支持持久化,也没有 ack
Stream 比较完整地实现了消息队列的内容
不是所有客户端都支持三种模式,例如:Jedis, lettuce, Redisson
参考:Redis 消息队列的三种方案(List、Streams、Pub/Sub) 最后的参考链接有好东西
Redis 分布式锁
set <key> <value> nx
返回值为 1 获得锁,而且最好在 value 中存入当前线程的信息,释放锁的时候只允许获得锁的线程释放
还需要考虑死锁的问题,最好设置 TTL,但是又要自动续期,可以使用 Redisson 的 RLock,自带 Watch Dog 监控
参考:分布式锁详解
缓存读取策略
- cache-aside, Miss 的时候 app 直接去 DB,然后再新增 cache
- read-through cache, app 直连 cache,cache miss 的时候自己去 DB,对 app 而言没有 DB 的概念
参考:Caching Strategies and How to Choose the Right One 有图,有优缺(类似)
缓存一致性 cache consistency
缓存更新的策略
Cache invalidation, Cache 失效
DB 更新之后删除对应的 cache
- 使用:简单场景,读多写少
- 缺:cache 失效要查 DB
- 先删 cache 再更新 db 行不行?不行,在未更新 db 之前,其他读请求重新设置一个 old cache,后面的查询就一直 get old value
Write-through caching
同时更新 cache 和 DB(先 cache)
- 适用:读多写少
- 缺:增加请求耗时(因为要同时操作 cache, db)
Write-behind caching
更新 cache,有空的时候异步更新 DB
- 适用:写多
- 缺:丢数据风险高
参考
- Three Ways to Maintain Cache Consistency 比较简单
- 极端事务处理模式:Write-behind 缓存
- What are write-through and write-behind caching?
- Redis系列(七):缓存只是读写回种这么简单吗?如果是,那么请你一定看看这篇文章!
- 3种常用的缓存读写策略详解
- 数据库和缓存如何保证一致性?
- Caching Best Practices
缓存问题
雪崩:大量缓存同时过期 或 Redis 宕机
均匀设置过期时间(加随机数)
互斥锁
当业务线程在处理用户请求时,如果发现访问的数据不在 Redis 里,就加个互斥锁(带超时事件,避免释放失败),保证同一时间内只有一个请求来构建缓存(从数据库读取数据,再将数据更新到 Redis 里),当缓存构建完成后,再释放锁。未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值
双 key 策略
我们对缓存数据可以使用两个 key,一个是主 key,会设置过期时间,一个是备 key,不会设置过期,它们只是 key 不一样,但是 value 值是一样的,相当于给缓存数据做了个副本。当业务线程访问不到「主 key 」的缓存数据时,就直接返回「备 key 」的缓存数据,然后在更新缓存的时候,同时更新「主 key 」和「备 key 」的数据
后台更新缓存
业务线程不再负责更新缓存,缓存也不设置有效期,而是让缓存“永久有效”,并将更新缓存的工作交由后台线程定时更新。还有些 问题 要考虑
宕机解决
- 限流
- Redis HA
击穿:热点缓存过期(缓存击穿是缓存雪崩的一个子集)
- 互斥锁
- 热点数据不设置过期,或即将过期通知后台重设时间
穿透:不在缓存也不在数据库
为什么发生?
- 业务误操作,缓存中的数据和数据库中的数据都被误删除了
- 黑客恶意攻击,故意大量访问某些读取不存在数据的业务
解决:
- 限制非法请求(API 入口检验请求参数)
- 缓存空值或者默认值
- 使用布隆过滤器快速判断数据是否存在
参考
其他
- 在线 Redis 貌似是旧版本,不支持
bitpos key bit start end BIT
这种语法 - redisdeveloper: HowTos & Tutorials 提供了很多使用案例(虽然是针对 RedisEnterprise,但是思路可参考)
- redis.com 的 SOLUTIONS-USE CASES 和 RESOURCES 中包含很多资源
- java面试八股文之——Redis夺命连环25问