当前位置: 首页 > 科技观察

听说Redis会遇到并发、雪崩等问题?10分钟我解决了

时间:2023-03-22 14:53:43 科技观察

一、Redis雪崩、渗透、并发等5大解决方案缓存雪崩数据没有加载到缓存中,或者缓存同时大面积失效,导致所有请求检查了数据库,导致数据库CPU和内存负载过高,甚至宕机。比如雪崩的一个简单过程:1.redis集群大面积失效。2、缓存失效,但仍有大量请求访问缓存服务redis。3、大量redis故障后,大量请求转向mysql数据库。5、由于大量的应用服务依赖mysql和redis服务,这个时候很快就会演变成服务器集群雪崩,最后网站彻底崩溃。如何防止缓存雪崩:1.缓存的高可用缓存层设计为高可用,防止缓存大规模失效。即使个别节点、个别机器甚至机房宕机,仍然可以提供服务。比如RedisSentinel和RedisCluster都实现了高可用。2.缓存降级可以使用本地缓存如ehcache(暂时支持),但主要是限制源服务访问,资源隔离(熔断),降级等,当访问量激增,出现问题时服务,仍然有必要确保服务仍然可用。系统可以根据一些关键数据自动降级,也可以配置开关实现手动降级,这涉及到运维的配合。降级的最终目标是保持核心服务可用,即使有损。比如在推荐服务中,很多都是个性化的需求。如果不能提供个性化需求,可以降级补充热点数据,以免造成前端页面大片空白。降级前需要对系统进行梳理,比如:哪些业务是核心(必须保证),哪些业务可以暂时不提供服务(使用静态页面替换)等,配合服务器后期设置整体方案的核心指标,如:(1)General:比如有些服务偶尔会因为网络抖动超时或者服务正在上线,可以自动降级;(2)警告:部分服务成功率在一段时间内波动(如95~100%之间),可自动降级或手动降级,并发出告警;(3)错误:比如可用率低于90%,或者数据库连接池炸毁,或者流量突然飙升到系统可以承受的最大阈值。可根据情况自动降级或手动降级;(4)严重错误:比如由于特殊原因导致数据错误,此时需要紧急手动降级。3.Redis备份与快速预热1)Redis数据备份与恢复2)缓存快速预热4.提前演练最后建议演练缓存层去后应用和后端的负载以及可能出现的情况项目上线前down掉问题,提前排练高可用,提前发现问题。缓存穿透缓存穿透是指查询不存在的数据。例如:如果从缓存redis中没有命中,则需要从mysql数据库中查询。如果找不到数据,则不会写入缓存。这样会导致每次请求时都向数据库查询不存在的数据,造成缓存穿透。解决方法:如果查询数据库也是空的,直接设置一个默认值存入缓存,这样第二次就从缓存中获取该值,而不用继续访问数据库。设置一个过期时间或者有值时替换缓存中的值。可以为key设置一些格式规则,然后在查询前过滤掉不符合规则的key。缓存并发这里的并发指的是多个redis客户端同时设置key导致的并发问题。其实redis本身就是单线程运行的。多个客户端同时运行。按照先来先执行的原则,先来先执行,其余的都阻塞。当然还有一种方案就是把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等复杂结构。这些结构可能会执行非常细粒度的操作,例如将元素添加到长列表的末尾,在散列中添加或删除对象。这些操作可能需要大量的锁,导致同步开销大大增加。总之,在单线程的情况下,不需要考虑各种锁的问题,没有释放锁的操作,也没有可能死锁带来的性能消耗。2)单线程多进程集群方案单线程的威力其实很强大,每个核心的效率也很高。自然,多线程可以有比单线程更高的性能上限,但是在当今的计算环境下,即使是单机多线程的上限也往往不能满足需求。需要进一步探索的是多服务器集群解决方案。在这些解决方案中,多线程技术仍然不可用。所以单线程、多进程集群是一种时髦的解决方案。3)CPU消耗采用单线程,避免了不必要的上下文切换和竞争条件,不会因为多进程或多线程引起的切换而消耗CPU。但是,如果CPU成为Redis的瓶颈,或者您不想让服务器的其他CPU核心闲置怎么办?可以考虑启动多个Redis进程。Redis是key-value数据库,不是关系型数据库,数据之间没有约束。只要客户端能区分哪些key放在哪个Redis进程中就可以了。Redis单线程的优缺点单进程单线程优点代码更清晰,处理逻辑更简单。不需要考虑各种锁,没有释放锁的操作,也没有因为可能出现的死锁而导致的性能消耗。进程或多线程切换导致的CPU消耗单进程单线程的弊端无法充分发挥多核CPU的性能,但可以通过单机开多个Redis实例来改善;IO多路复用技术redis利用网络IO多路复用技术,保证系统在多连接时的高吞吐量。多路复用——指多个socket连接,多路复用——指多路复用一个线程。多路复用主要有3种技术: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)操作,并设置合理的超时时间。伪代码如下:publicvoidwrite(Stringkey,Objectdata){redis.delKey(key);db.updateData(数据);线程.睡眠(500);redis.delKey(键);具体步骤是:首先删除缓存;再次写入数据库;休眠500毫秒;再次删除缓存。那么,这500毫秒是怎么确定的,应该休眠多长时间呢?需要评估自己项目读取数据业务逻辑的耗时。这样做的目的是保证读请求结束,写请求可以删除读请求造成的缓存脏数据。当然这个策略也考虑到了redis和数据库主从同步的耗时问题。最后写数据休眠时间:在读数据业务逻辑耗时的基础上,增加几百ms。例如:睡眠1秒。设置缓存过期时间理论上,为缓存设置过期时间是一种保证最终一致性的解决方案。所有写操作都以数据库为准。只要到了缓存过期时间,后续的读请求自然会从数据库中读取新的值,然后回填缓存。这种方案的缺点是双重删除策略+缓存超时设置的组合,所以最坏的情况是超时时间内数据不一致,增加了写入请求的耗时。2、方案二:异步更新缓存(基于订阅binlog的同步机制)整体技术思路:MySQLbinlog增量订阅消费+消息队列+增量数据更新到redis读取Redis:热数据基本写入MySQLRedis中:增删改查全部操作MySQL更新Redis数据:MySQ数据操作binlog更新到RedisRedis更新1)数据操作主要分为两部分:一是全量(一次将所有数据写入redis)还有一种是增量(Real-timeupdate)这里是增量的,指的是mysql的更新、插入、删除变更数据。2)读取binlog后进行分析,利用消息队列推送更新各站的redis缓存数据。这样,一旦MySQL中产生新的写入、更新、删除等操作,就可以将binlog相关的消息推送到Redis,Redis可以根据binlog中的记录更新Redis。这个机制其实和MySQL的主从备份机制很像,因为MySQL的主从备份机制也是通过binlog实现数据一致性的。这里可以结合使用Canal(阿里的一个开源框架),通过它可以订阅MySQL的binlog,canal只是模仿mysql从库的备份请求,这样Redis的数据更新就达到了同样的效果影响。当然你也可以使用其他第三方作为这里的消息推送工具:kafka、rabbitMQ等来推送和更新Redis。