高可用部署
主从 master-slave
特点
- 读写分离:主节点写;多个从节点读
- 水平扩容:从节点水平扩容支撑读高并发
- 主从节点间的数据不保证及时一致性
部署建议:
- 主节点做持久化,备份RDB,以防止主节点宕机重启后空数据状态被从节点同步后集群数据清空
- master同步压力过大时使用主从从结构解决
- 单节点内存分配尽量不超过10G
- 为了Master的稳定性,主从复制不要用图状结构,用单向链表结构更稳 定,即主从关系为:Master<–Slave1<–Slave2<–Slave3...,这样的结构 也方便解决单点故障问题,实现Slave对Master的替换,也即,如果 Master挂了,可以立马启用Slave1做Master,其他不变。
- 为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个子网
核心原理:
- slave启动后---PSYNC---> master
- 若slave是初次连接master
开始full resynchronization:
master启动一个后台线程,开始生成一份 RDB
master缓存新收到的client命令
RDB 生成完毕后----> slave ----> RDB落盘---->加载RDB至内存
缓存的client命令----> slave ----> 同步命令 - master ----client命令----> slave
- 断线重连后
仅复制给 slave 部分缺少的数据
哨兵 sentinel
特点
- 集群监控:负责监控 redis master 和 slave 进程是否正常工作。
- 通知告警:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给 管理员。
- 故障转移:如果 master 挂掉了,会选举 slave 为新master。
- 配置同步:如果故障转移发生了,通知 client 客户端新的 master 地址。
部署建议:
- 至少 3 个哨兵实例
- 哨兵只能保证 redis 集群的高可用性
集群 Redis Cluster
摘自https://juejin.cn/post/6844904097116585991
特点
- 无中心结构,所有的redis节点彼此互联(PING-PONG机制),内部使用gossip二进制协议优化传输速度和带宽
- 节点的fail是通过集群中超过半数的节点检测失效时才生效
- 客户端与redis节点直连,不需要中间代理层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
- 每份数据分片会存储在多个互为主从的多节点上(支持配置为阻塞同步)
机制
- 每个节点有一个取值范围为0-16383的插槽(slot)
- 对key算CRC16结果对16384取余,以余找slot得节点,跳转节点取命令结果
- 为了保证高可用,Cluster模式也引入主从复制模式,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点
- 当其它主节点ping一个主节点A时,如果半数以上的主节点与A通信超时,那么认为主节点A宕机了。如果主节点A和它的从节点都宕机了,那么该集群就无法再提供服务了
部署要点
- 端口开放redis命令端口A和A+10000端口(cluster bus)
- 扩容时时需要需要把旧节点的数据迁移一部分到新节点
优点
- 无中心架构,支持动态扩容,对业务透明
- 可线性扩展到1000多个节点,节点可动态添加或删除
- 节点间互相监控,每个节点都保存各自的数据和整个集群的状态
- 自动Failover(故障转移),节点之间通过gossip协议交换状态信息,用投票机制完成slave到master的角色转换
缺点
- 运维复杂,数据迁移需要人工干预
- slave充当“冷备”,不能缓解读压力
- 批量操作限制,目前只支持具有相同slot值的key执行批量操作,对mset、mget、sunion等操作支持不友好
- key事务操作支持限制,只支持落在同一节点的多key事务操作,多key分布不同节点时无法使用事务功能
- 只能使用0号数据库
- 分布式逻辑和存储模块耦合等
单节点分布式锁
摘自https://www.jianshu.com/p/1145cd7e0cf1
加锁
-- 加锁脚本,其中KEYS[]为外部传入参数
-- KEYS[1]表示key
-- KEYS[2]表示value
-- KEYS[3]表示过期时间
if redis.call("setnx", KEYS[1], KEYS[2]) == 1 then --setnx: 不存在则添加
return redis.call("pexpire", KEYS[1], KEYS[3])
else
return 0
解锁
-- 解锁脚本
-- KEYS[1]表示key
-- KEYS[2]表示value
-- return -1 表示未能获取到key或者key的值与传入的值不相等
if redis.call("get",KEYS[1]) == KEYS[2] then
return redis.call("del",KEYS[1])
else
return -1
RedLock
redis官方分布式锁
- 安全特性:互斥访问,即永远只有一个 client 能拿到锁
- 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本 锁住某资源的 client crash 了或者出现了网络分区
- 容错性:只要大部分 Redis 节点存活就可以正常提供服务
系统缓存异常
缓存雪崩
指缓存同一时间大面积的失效,导致后面的请求落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
- 给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓 存标记失效,则更新数据缓存。
缓存穿透
指大量请求缓存和数据库中都没有的数据,导致所有的请求都落到数据库上, 造成数据库短时间内承受大量请求而崩掉。
解决方案
- 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦 截;
- 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key- value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会 导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力 攻击
- 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层 存储系统的查询压力
缓存击穿
指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由 于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数 据库压力瞬间增大,造成过大压力。
和缓存雪崩的不同:
缓存击穿指并发查同 一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案
- 设置热点数据永远不过期。
- 取数据库时加互斥锁,未获得锁则等待缓存更新
常用客户端
Redisson:对一部分Java对象进行了redis环境下的实现,代码成熟度高,面向业务开发比较友好
Jedis:提供了全部redis命令的api,对直接操作redis比较友好
缓存更新数据一致性
摘自https://www.huaweicloud.com/articles/52ba6b30c546d3a1e23b1e65e8150c64.html
基础方案:
- 给缓存设置过期时间,保证最终一致性
不依赖过期时间的更新策略:
- 先更新数据库,再更新缓存
[不推荐]
并发导致缓存脏数据
多写造成性能浪费 - 先删除缓存,再更新数据库
并发导致缓存脏数据
延时双删策略
逻辑繁琐 - 先更新数据库,再删除缓存
[推荐]
可能产生脏数据的场景
a.缓存刚好失效
b.请求A查询数据库,得一个旧值
c.请求B将新值写入数据库
d.请求B删除缓存
e.请求A将查到的旧值写入缓存
完全解决小概率的脏数据问题
流程
(1)更新数据库数据
(2)数据库会将操作信息写入binlog日志当中
(3)订阅程序提取出所需要的数据以及key
(4)另起一段非业务代码,获得该信息
(5)尝试删除缓存操作,发现删除失败
(6)将这些信息发送至消息队列
(7)重新从消息队列中获得该数据,重试操作