当前位置:首页 > 问答 > 正文

Redis大流量下的高并发查询怎么搞,设计思路和实战分享

关于Redis在大流量高并发查询下的设计和实战,核心思路就是一句话:让Redis做它最擅长的事情,并且保护好它,下面我结合一些常见的做法和思路来具体说说。

第一,设计上,核心是减少对Redis的“压力”和“依赖”。

你不能把Redis当成一个万能数据库,什么数据都往里塞,什么查询都让它做,高并发下,首先要考虑的是如何让每次查询都变得更“轻”。

  • 缓存一定要设置过期时间。 这是铁律,不管什么数据,都必须设置TTL(生存时间)。(来源:Redis官方文档及几乎所有缓存最佳实践指南)这有两个关键好处:一是防止冷数据(很少被访问的数据)长期占用宝贵的内存;二是作为一种兜底策略,即使后续更新缓存的逻辑出问题了,数据最多只错一段时间,到期后会自动重建,避免永久性数据错误。
  • 缓存 key 的设计要讲究。 key的设计要易读、简洁且能唯一标识数据,比如用冒号分隔的层级方式,业务:子业务:唯一ID,像 user:info:123, product:detail:456,这样的好处是清晰,而且方便后面做模式匹配和批量管理,避免使用过长、过于复杂的key,这会消耗更多内存。
  • 避免使用大key。 (来源:阿里云、腾讯云等云服务商发布的Redis实践文章)所谓大key,就是指一个key对应的value非常大,比如一个hash里存了几万条字段,或者一个list里存了几十万个元素,大key在序列化/反序列化、网络传输、甚至Redis自身清理时都会非常耗时,容易阻塞其他请求,解决方法是拆分,比如把一个大hash按某种规则拆成多个小的hash。
  • 尽量使用批量操作。 (来源:Redis性能优化相关技术博客)高并发场景下,网络往返次数是性能杀手,相比于循环执行10次 GET key,使用一次 MGET key1 key2 ... key10 性能会高出几个数量级,同样,设置数据时也可以考虑使用 MSET,Pipeline(管道)技术也是类似的思路,将多个命令打包一次发送,减少网络延迟。

第二,实战中,核心是处理好数据一致性和缓存异常。

Redis大流量下的高并发查询怎么搞,设计思路和实战分享

光有好的设计还不够,在程序运行起来后,如何应对各种情况才是关键。

  • 经典的缓存穿透、击穿、雪崩。 这是高并发场景下必须面对的三大难题。

    • 穿透:指的是查询一个根本不存在的数据,Redis里没有,于是每次请求都直接打到数据库上,解决办法很简单,即使查不到数据,也在Redis里缓存一个空值(比如null)并设置一个较短的过期时间(比如1-5分钟),这样后续的请求在Redis层就被拦截了。
    • 击穿:指的是一个非常热点的key在失效的瞬间,大量请求同时涌来,直接击穿缓存,全部去查询数据库,解决办法是使用互斥锁,当第一个请求发现缓存失效时,它先去获取一个分布式锁,然后才去数据库加载数据并重建缓存,在此期间,其他请求要么等待锁释放后直接读取新缓存,要么可以返回一个默认值。
    • 雪崩:指的是大量key在同一时间点失效,导致所有请求都落数据库,解决办法是给缓存的过期时间加上一个随机值,比如原本统一设置10分钟过期,现在可以设置为 10分钟 + 0到3分钟的随机数,这样就能避免缓存集体失效。
  • 缓存更新策略要选对。 数据在数据库里变更后,如何同步到Redis?常见的有几种:

    Redis大流量下的高并发查询怎么搞,设计思路和实战分享

    • 先更新数据库,再删除缓存:这是比较推荐的做法。(来源:Martin Fowler的博客等关于缓存模式的文章)为什么是删除而不是更新缓存?因为删除是幂等的(执行多次效果一样),而且避免了并发写操作可能带来的数据错乱问题,虽然这种方式在删除后、新缓存建立前可能会有极短时间的数据不一致,但通常是可以接受的。
    • 延时双删:对于一致性要求极高的场景,可以在更新数据库后,先删一次缓存,然后延迟几百毫秒再删一次,目的是为了清除可能在第一次删除后、数据库主从同步完成前,被旧数据污染了的缓存。
  • 做好降级和熔断。 (来源:微服务架构中的常用设计模式,如Netflix Hystrix)Redis再快也有可能出故障,比如网络抖动、内存满了、CPU爆了,你不能让Redis的故障导致整个系统崩溃,在你的应用代码里,必须对Redis的访问做降级和熔断处理,当连续多次访问Redis超时或失败,就自动“熔断”,在一段时间内直接绕开Redis去查数据库(虽然慢,但能保证服务可用),或者返回一个默认的兜底数据,等Redis恢复后,再自动关闭熔断,这需要依靠一些框架(如Sentinel、Hystrix)或自己实现简单的计数器来实现。

第三,架构层面,考虑扩展性。

当单机Redis性能达到瓶颈时,就要考虑分布式方案。

  • 读写分离:通过主从复制(Master-Slave),让主节点处理写请求,从节点处理读请求,分摊压力。
  • 数据分片:也就是Redis Cluster集群模式。(来源:Redis官方文档)将数据按照一定规则分布到多个Redis实例上,每个实例只存储一部分数据,这样就能水平扩展性能和存储容量,这是应对超大规模流量和数据的终极方案。

用好Redis应对高并发,是一个系统工程,它要求你在Key设计、命令使用、过期策略、异常处理、更新策略、架构扩展等多个环节上都要有细致的考虑和得当的措施,核心思想始终是:让Redis轻装上阵,并确保即使在最坏的情况下,系统也能有基本的可用性。