共计 4917 个字符,预计需要花费 13 分钟才能阅读完成。
本篇内容介绍了“Redis 遇到并发、雪崩问题怎么解决”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让丸趣 TV 小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
一、Redis 雪崩、穿透、并发等 5 大难题解决方案
缓存雪崩
数据未加载到缓存中,或者缓存同一时间大面积的失效,从而导致所有请求都去查数据库,导致数据库 CPU 和内存负载过高,甚至宕机。
比如一个雪崩的简单过程:
1、redis 集群大面积故障
2、缓存失效,但依然大量请求访问缓存服务 redis
3、redis 大量失效后,大量请求转向到 mysql 数据库
4、mysql 的调用量暴增,很快就扛不住了,甚至直接宕机
5、由于大量的应用服务依赖 mysql 和 redis 的服务,这个时候很快会演变成各服务器集群的雪崩,最后网站彻底崩溃。
如何预防缓存雪崩:
1. 缓存的高可用性
缓存层设计成高可用,防止缓存大面积故障。即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务,例如 Redis Sentinel 和 Redis Cluster 都实现了高可用。
2. 缓存降级
可以利用 ehcache 等本地缓存(暂时支持),但主要还是对源服务访问进行限流、资源隔离(熔断)、降级等。
当访问量剧增、服务出现问题仍然需要保证服务还是可用的。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级,这里会涉及到运维的配合。
降级的最终目的是保证核心服务可用,即使是有损的。
比如推荐服务中,很多都是个性化的需求,假如个性化需求不能提供服务了,可以降级补充热点数据,不至于造成前端页面是个大空白。
在进行降级之前要对系统进行梳理,比如:哪些业务是核心 (必须保证),哪些业务可以容许暂时不提供服务(利用静态页面替换) 等,以及配合服务器核心指标,来后设置整体预案,比如:
(1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
(2)警告:有些服务在一段时间内成功率有波动(如在 95~100% 之间),可以自动降级或人工降级,并发送告警;
(3)错误:比如可用率低于 90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
(4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
3.Redis 备份和快速预热
1)Redis 数据备份和恢复
2)快速缓存预热
4. 提前演练
最后,建议还是在项目上线前,演练缓存层宕掉后,应用以及后端的负载情况以及可能出现的问题,对高可用提前预演,提前发现问题。
缓存穿透
缓存穿透是指查询一个一不存在的数据。例如:从缓存 redis 没有命中,需要从 mysql 数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
解决思路:
如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库。设置一个过期时间或者当有值的时候将缓存中的值替换掉即可。
可以给 key 设置一些格式规则,然后查询之前先过滤掉不符合规则的 Key。
缓存并发
这里的并发指的是多个 redis 的 client 同时 set key 引起的并发问题。其实 redis 自身就是单线程操作,多个 client 并发操作,按照先到先执行的原则,先到的先执行,其余的阻塞。当然,另外的解决方案是把 redis.set 操作放在队列中使其串行化,必须的一个一个执行。
缓存预热
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。
这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
解决思路:
1、直接写个缓存刷新页面,上线时手工操作下;
2、数据量不大,可以在项目启动的时候自动进行加载;
目的就是在系统上线前,将数据加载到缓存中。
二、Redis 为什么是单线程,高并发快的 3 大原因详解
Redis 的高并发和快速原因
1.redis 是基于内存的,内存的读写速度非常快;
2.redis 是单线程的,省去了很多上下文切换线程的时间;
3.redis 使用多路复用技术,可以处理并发的连接。非阻塞 IO 内部实现采用 epoll,采用了 epoll+ 自己实现的简单的事件框架。epoll 中的读、写、关闭、连接都转化成了事件,然后利用 epoll 的多路复用特性,绝不在 io 上浪费一点时间。
下面重点介绍单线程设计和 IO 多路复用核心设计快的原因。
为什么 Redis 是单线程的?
1. 官方答案
因为 Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。
2. 性能指标
关于 redis 的性能,官方网站也有,普通笔记本轻松处理每秒几十万的请求。
3. 详细原因
1)不需要各种锁的性能消耗
Redis 的数据结构并不全是简单的 Key-Value,还有 list,hash 等复杂的结构,这些结构有可能会进行很细粒度的操作,比如在很长的列表后面添加一个元素,在 hash 当中添加或者删除
一个对象。这些操作可能就需要加非常多的锁,导致的结果是同步开销大大增加。
总之,在单线程的情况下,就不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。
2)单线程多进程集群方案
单线程的威力实际上非常强大,每核心效率也非常高,多线程自然是可以比单线程有更高的性能上限,但是在今天的计算环境中,即使是单机多线程的上限也往往不能满足需要了,需要进一步摸索的是多服务器集群化的方案,这些方案中多线程的技术照样是用不上的。
所以单线程、多进程的集群不失为一个时髦的解决方案。
3)CPU 消耗
采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU。
但是如果 CPU 成为 Redis 瓶颈,或者不想让服务器其他 CUP 核闲置,那怎么办?
可以考虑多起几个 Redis 进程,Redis 是 key-value 数据库,不是关系数据库,数据之间没有约束。只要客户端分清哪些 key 放在哪个 Redis 进程上就可以了。
Redis 单线程的优劣势
单进程单线程优势
代码更清晰,处理逻辑更简单不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗不存在多进程或者多线程导致的切换而消耗 CPU
单进程单线程弊端
无法发挥多核 CPU 性能,不过可以通过在单机开多个 Redis 实例来完善;
IO 多路复用技术
redis 采用网络 IO 多路复用技术来保证在多连接的时候,系统的高吞吐量。
多路 - 指的是多个 socket 连接,复用 - 指的是复用一个线程。多路复用主要有三种技术:select,poll,epoll。epoll 是最新的也是目前最好的多路复用技术。
这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),主要以上两点造就了 Redis 具有很高的吞吐量。
Redis 高并发快总结
1. Redis 是纯内存数据库,一般都是简单的存取操作,线程占用的时间很多,时间的花费主要集中在 IO 上,所以读取速度快。
2. 再说一下 IO,Redis 使用的是非阻塞 IO,IO 多路复用,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争。
3. Redis 采用了单线程的模型,保证了每个操作的原子性,也减少了线程的上下文切换和竞争。
4. 另外,数据结构也帮了不少忙,Redis 全程使用 hash 结构,读取速度快,还有一些特殊的数据结构,对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如,跳表,使用有序的数据结构加快读取的速度。
5. 还有一点,Redis 采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大。
三、Redis 缓存和 MySQL 数据一致性方案详解
需求起因
在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节。所以,就需要使用 redis 做一个缓冲操作,让请求先访问到 redis,而不是直接访问 MySQL 等数据库。
这个业务场景,主要是解决读数据从 Redis 缓存,一般都是按照下图的流程来进行业务操作。
读取缓存步骤一般没有什么问题,但是一旦涉及到数据更新:数据库和缓存更新,就容易出现缓存 (Redis) 和数据库(MySQL)间的数据一致性问题。
不管是先写 MySQL 数据库,再删除 Redis 缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况。举一个例子:
1. 如果删除了缓存 Redis,还没有来得及写库 MySQL,另一个线程就来读取,发现缓存为空,则去数据库中读取数据写入缓存,此时缓存中为脏数据。
2. 如果先写了库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数据不一致情况。
因为写和读是并发的,没法保证顺序, 就会出现缓存和数据库的数据不一致的问题。
如来解决?这里给出两个解决方案,先易后难,结合业务和技术代价选择使用。
缓存和数据库一致性解决方案
1. 第一种方案:采用延时双删策略
在写库前后都进行 redis.del(key)操作,并且设定合理的超时时间。
伪代码如下:
public void write(String key,Object data){ redis.delKey(key);
db.updateData(data);
Thread.sleep(500);
redis.delKey(key);
}
具体的步骤就是:
先删除缓存;再写数据库;休眠 500 毫秒;再次删除缓存。
那么,这个 500 毫秒怎么确定的,具体该休眠多久呢?
需要评估自己的项目的读数据业务逻辑的耗时。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
当然这种策略还要考虑 redis 和数据库主从同步的耗时。最后的的写数据的休眠时间:则在读数据业务逻辑的耗时基础上,加几百 ms 即可。比如:休眠 1 秒。
设置缓存过期时间
从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。所有的写操作以数据库为准,只要到达缓存过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。
该方案的弊端
结合双删策略 + 缓存超时设置,这样最差的情况就是在超时时间内数据存在不一致,而且又增加了写请求的耗时。
2、第二种方案:异步更新缓存(基于订阅 binlog 的同步机制)
技术整体思路:
MySQL binlog 增量订阅消费 + 消息队列 + 增量数据更新到 redis
读 Redis:热数据基本都在 Redis 写 MySQL: 增删改都是操作 MySQL 更新 Redis 数据:MySQ 的数据操作 binlog,来更新到 Redis
Redis 更新
1)数据操作主要分为两大块:
一个是全量 (将全部数据一次写入到 redis) 一个是增量(实时更新)
这里说的是增量, 指的是 mysql 的 update、insert、delate 变更数据。
2)读取 binlog 后分析,利用消息队列, 推送更新各台的 redis 缓存数据。
这样一旦 MySQL 中产生了新的写入、更新、删除等操作,就可以把 binlog 相关的消息推送至 Redis,Redis 再根据 binlog 中的记录,对 Redis 进行更新。
其实这种机制,很类似 MySQL 的主从备份机制,因为 MySQL 的主备也是通过 binlog 来实现的数据一致性。
这里可以结合使用 canal(阿里的一款开源框架),通过该框架可以对 MySQL 的 binlog 进行订阅,而 canal 正是模仿了 mysql 的 slave 数据库的备份请求,使得 Redis 的数据更新达到了相同的效果。
当然,这里的消息推送工具你也可以采用别的第三方:kafka、rabbitMQ 等来实现推送更新 Redis。
“Redis 遇到并发、雪崩问题怎么解决”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注丸趣 TV 网站,丸趣 TV 小编将为大家输出更多高质量的实用文章!