Redis企业级解决方案

目录

1 Redis脑裂

1.1  概念

1.2 解决方案

2 缓存预热

3 缓存穿透 

1 概念

2 解决方案

4 缓存击穿

1 概念

2 解决方案

5 缓存雪崩

1 概念

2 解决方案

6 分布式锁

分布式锁主流的实现方案: 

1 设置锁和过期时间(redis)

1. 通过setnx上锁

2. 通过del释放锁

3. 如果锁一直不释放,需要增加过期时间,防止资源浪费。 

4. 如果在上锁之后,设置过期时间之前,服务器异常,就无法设置过期时间,可以在上锁的同时设置过期时间。

2 防止误删  

3 保证删除原子性  

7 消息队列 

1 List消息队列

8 发布/订阅消息队列

8 数据一致性解决方案  

1 延时双删策略 

9 企业级持久化解决方案

1 RDB的生成策略

2 AOF的生成策略

3 企业级的数据备份方案


Redis脑裂

1.1  概念

1. 假设现在有三台机器,分别安装了redis服务,结构如图

2. 如果此时 master 服务器所在区域网络通信出现异常,导致和两台 slave 机器无法正常通信,但是和 客户端的连接是正常的。那么sentinel 就会从两台 slave 机器中选举其中一个作为新的 master 来处
理客户端请求。

3. 这个时候,已经存在两台 master 服务器, client 发送的数据会持续保存在旧的 master 服务器中,而新的master slave 中没有新的数据。如果一分钟以后,网络恢复正常,服务之间能够正常通信。 此时,sentinel 会把旧的 master 会变成新的 master slave 节点。
4. 问题出现了,我们都知道, slave 会从 master 中同步数据,保持主从数据一致。这个时候,变成了 slave节点的旧 master 会丢失掉通信异常期间从客户端接收到的数据。

1.2 解决方案

第一个参数表示最少的 slave 节点为 1
第二个参数表示数据复制和同步的延迟不能超过 10
配置了这两个参数:如果发生脑裂:原 master 会在客户端写入操作的时候拒绝请求。这样可以避免大量数据丢失。

缓存预热

新启动的系统没有任何缓存数据,在缓存重建数据的过程中,系统性能和数据库负载都不太好,所以最 好是在系统上线之前就把要缓存的热点数据加载到缓存中,这种缓存预加载手段就是缓存预热。

缓存穿透 

1 概念

如果某个 key 对应的数据不存在,而又未对该 key 做缓存,所以每次请求都会穿过缓存直接到数据库进行 查询,并发量高的情况下进而导致数据库直接宕机,这就是缓存穿透。

2 解决方案

1. 对空值缓存:如果一个查询返回的数据为空(不管数据是否存在),我们仍然把这个空结果缓存, 设置空结果的过期时间会很短,最长不超过5 分钟。
2. 设置白名单:使用 bitmaps 类型定义一个可以访问的名单,用户 id 作为偏移量,每次访问查询是否 在白名单中,如果不存在,则拒绝访问。
3. 布隆过滤器:类似一个 hash set ,用来判断某个元素( key )是否在某个集合中。
和一般的 hash set 不同的是,这个算法无需存储 key 的值,对于每个 key ,只需要 k 个比特位,每个
存储一个标志,用来判断 key 是否在集合中。

 

4 缓存击穿

1 概念

某一个热点 key ,在缓存过期的一瞬间,同时有大量的请求打进来,由于此时缓存过期了,所以请求最 终都会走到数据库,造成瞬时数据库请求量大、压力骤增,甚至可能打垮数据库。

2 解决方案

1. 加互斥锁:在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线 程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,其他线程直接查询缓存。
2. 热点数据不过期:直接将缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存。

缓存雪崩

1 概念

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发 到DB DB 瞬时压力过重雪崩。
和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不 到从而查数据库。

2 解决方案

1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
3. 设置热点数据永远不过期。

分布式锁

随着业务发展的需要,原单机部署的系统被演化成分布式集群系统,由于分布式系统多线程、多进程且分布在不同的机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API 并不能提供分 布式锁的能力。为了解决这个问题需要一种跨JVM 的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!

 

 

分布式锁主流的实现方案: 

1. 基于数据库实现分布式锁
2. 基于缓存( Redis 等)
3. 基于 Zookeeper
每一种分布式锁解决方案都有各自的优缺点:
1. 性能: Redis 最高
2. 可靠性: Zookeeper 最高

1 设置锁和过期时间(redis)

1. 通过setnx上锁

由于 setnx 只有不存在该 key 的时候,可以设置成功,并返回 1 ,否则设置失败,并返回 0

 

2. 通过del释放锁

 

3. 如果锁一直不释放,需要增加过期时间,防止资源浪费。 

 

4. 如果在上锁之后,设置过期时间之前,服务器异常,就无法设置过期时间,可以在上锁的同时设置过期时间。

2 防止误删  

避免误删情况出现,可以在加锁过程中添加一个加锁的唯一 id ,通过跟该 id 对比,阻止误删的情况出现。

 

3 保证删除原子性  

Lua 脚本
Lua 是一种轻量小巧的脚本语言,用标准 C 语言编写并以源代码形式开放, 其设计目的是为了 嵌入应用 程序 中,从而为应用程序提供灵活的扩展和定制功能。
Redis 中引入 lua 的优势:
减少网络开销:多个请求通过脚本一次发送,减少网络延迟
原子操作 :将脚本作为一个整体执行,中间不会插入其他命令,无需使用事务
复用:客户端发送的脚本永久存在 redis 中,其他客户端可以复用脚本
可嵌入性:可嵌入 JAVA C# 等多种编程语言,支持不同操作系统跨平台交互
lua 进行比较 uuid ,对比成功后删除键值对的代码:

 

if redis.call ( 'get' , KEYS [ 1 ]) == ARGV [ 1 ]
then
return redis.call ( 'del' , KEYS [ 1 ])
else
return 0
end
if 中的比较如果是 true , 那么 执行 del 并返回 del 结果;如果 if 结果为 false 直接返回 0
其中的 KEYS[1] , ARGV[1] 是参数 ,我们只调用 jedis 执行脚本的时候,传递这两个参数就可以了。 通过jedis 执行 lua 脚本

 

消息队列 

1 List消息队列

List 底层的实现就是一个「链表」,在头部和尾部操作元素,时间复杂度都是 O(1) ,这意味着它非常符合消息队列的模型。

生产者使用 lpush发布消息

 

消费者这一侧,使用 rpop拉取消息:

 

一般编写消费者逻辑时,通过一个 死循环 实现,如果此时队列为空,那消费者依旧会频繁拉取消息,造成资源浪费。

 

Redis 提供「阻塞式」拉取消息的命令:brpop / blpop,这里的 B 指的是阻塞(Block)。

 

brpop key timeout :移除并返回最后一个值,同时需要传入一个超时时间( timeout ),如果设置为 0,则表示不设置超时,直到有新消息才返回,否则会在指定的超时时间后返回 NULL
缺点:
不支持重复消费:消费者拉取消息后,这条消息就从 List 中删除了,无法被其它消费者再次消费,
即不支持多个消费者消费同一批数据
消息丢失:消费者拉取到消息后,如果发生异常宕机,那这条消息就丢失了

发布/订阅消息队列

Redis 提供了 PUBLISH / SUBSCRIBE 命令,来完成发布、订阅的操作。

多个消费者,同时消费同一批数据 

 

通过生产者,发布一条消息。

 

客户端接收到消息

 

使用 Pub/Sub 这种方案,既支持阻塞式拉取消息,还很好地满足了多组消费者,消费同一批数据的业务需求。
但是该方案会引起 消息丢失
消费者下线
Redis 宕机
消费者: 通过 jedis 订阅频道,需要一个 JedisPubSub 子类对象,并重写 onMessage 方法用于接受消息

 

8 数据一致性解决方案  

读取缓存步骤一般没有什么问题,但是一旦涉及到数据更新:数据库和缓存更新,就容易出现 缓存
(Redis) 和数据库( MySQL )间的数据一致性问题
不管是先写 MySQL 数据库,再删除 Redis 缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况。
例:
1. 如果删除了缓存 Redis ,还没有来得及写库 MySQL ,另一个线程就来读取,发现缓存为空,则去数 据库中读取数据写入缓存,此时缓存中为脏数据。
2. 如果先写了库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数据不一致情
况。 因为写和读是并发的,没法保证顺序, 就会出现缓存和数据库的数据不一致的问题

1 延时双删策略 

1. 先删除缓存。
2. 再写数据库。
3. 休眠 500 毫秒;
4. 再次删除缓存。
需要评估自己的项目的读数据业务逻辑的耗时。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
当然这种策略还要考虑 redis 和数据库主从同步的耗时。最后的的写数据的休眠时间:则在读数据业务逻辑的耗时基础上,加几百ms 即可。比如:休眠 1 秒。
缺点: 结合双删策略 + 缓存超时设置,这样最差的情况就是在超时时间内数据存在不一致,而且又增加了写请求的耗时。

 

9 企业级持久化解决方案

在企业中不要仅仅使用 RDB ,因为那样会导致丢失很多数据。
也不要仅仅使用 AOF ,因为那样有两个问题:
1. 通过 AOF 做冷备,没有 RDB 做冷备,来的恢复速度更快;
2. RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug
综合使用 AOF RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择;
RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。
如果 RDB 在执行 snapshotting 操作,那么 redis 不会执行 AOF rewrite ;如果 redis 再执行 AOF rewrite , 那么就不会执行RDB snapshotting
如果 RDB 在执行 snapshotting ,此时用户执行 BGREWRITEAOF 命令,那么等 RDB 快照生成之后,才会去执行AOF rewrite

1 RDB的生成策略

如果希望能确保 RDB 最多丢 1 分钟的数据,那么尽量就是每隔 1 分钟都生成一个快照。不过到底是 10000 条执行一次RDB ,还是 1000 条执行一次 RDB ,这个根据需要根据自己的应用和业务的数据量来确定。

2 AOF的生成策略

AOF一定要打开,fsync方式选择everysec。一般可能会调整的参数可能就是下面俩参数了

auto-aof-rewrite-percentage 100
就是当前 AOF 大小膨胀到超过上次 100% ,上次的两倍。
auto-aof-rewrite-min-size 64mb
根据自己的数据量来定, 16mb 32mb

3 企业级的数据备份方案

RDB 非常适合做冷备,每次生成之后,就不会再有修改。
数据备份方案:
1. 写定时调度脚本去做数据备份。
2. 每小时都 copy 一份 rdb 的备份,到一个目录中去,仅仅保留最近 48 小时的备份。
3. 每天都保留一份当日的 rdb 的备份,到一个目录中去,仅仅保留最近 1 个月的备份。
4. 每次 copy 备份的时候,都把太旧的备份给删了。
5. 每天晚上将当前服务器上所有的数据备份,发送一份到远程的云服务上去。

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值