冉冉学姐唐伯虎心糖:甜蜜暴击,一口入魂!

核心内容摘要

小欣奈Vlog拜年:新年新气象,跟着欣奈一起“欣”想事成!
潜藏的狙击:探索“大雷擦狙击视频在线观看高清免费”的视觉盛宴

每日主题大赛:点燃你的创作激情,赢取无限可能!

在高并发系统架构中Redis 是当之无愧的“守门员”。

它凭借超高的内存访问速度、丰富的数据结构和灵活的部署模式成为众多互联网平台实现高可用、高性能缓存架构的首选。

但 Redis 真的能“扛住一切并发”吗如果你经历过双

春运售票、热门微博热搜等峰值流量时刻可能就会意识到光有 Redis 还不够它需要“设计得好”更需要“用得对”。

本文将从实际工程视角出发结合架构原理、

常见问题与解决方案、

案例分析全面剖析 Redis 如何助力系统应对千万级并发压力助你构建真正“顶得住”的缓存系统。

**

**Redis 为何能成为高并发利器RedisRemote Dictionary Server是一个基于内存、支持多种数据结构、具有持久化能力的高性能 Key-Value 数据库。

(

高并发的技术支撑单线程事件驱动架构Redis 使用 epoll Reactor 模型避免线程切换开销处理能力反而更强。

内存存储 高效数据结构Hash、List、Set、ZSet、Bitmap、HyperLogLog灵活应对各种数据场景。

指令执行快多数操作为 O(

或 O(logN)极少阻塞响应时间常在1毫秒以内。

多路复用机制通过 ae_select 实现高效的 I/O 事件响应。

二实际 QPS 表现在优化配置和硬件支持下单节点 Redis 的读写 QPS 可达 10 万级别而在采用集群模式时通过多个节点的协同工作能够实现性能的线性扩展。

然而即便在这样的性能保障下Redis 在面对一些特定的典型场景时仍然可能会遇到性能上的挑战难以应对在大型促销活动期间如双十一购物节某些热门商品的详情页面会遭遇数据请求的急剧增加这种情况下 Redis 的处理能力可能会受到考验在短视频平台的首页推荐系统中每个用户访问时都需要实时渲染个性化的首页内容这种高并发的实时数据处理需求对 Redis 的性能同样是一个不小的挑战在抢票系统或预约服务中用户在特定时刻会集中进行秒杀操作同时还需要实时查询库存信息这种高并发的读写操作对 Redis 的性能同样是一个严峻的考验。

为了避免 Redis 成为整个系统架构中的性能瓶颈我们在进行架构设计时必须提前做好充分的准备和规划。

**

**架构设计高并发下的 Redis 应该怎么部署**一**基础部署模型单机模式这种模式适合中小系统或非核心业务的测试环境。

它通常用于开发和测试阶段因为其部署简单资源消耗较少但不适合生产环境因为缺乏高可用性和扩展性。

主从模式 Sentinel这种模式通过增加Sentinel来增强系统的高可用性。

Sentinel负责监控主从服务器的健康状态并在主服务器出现故障时自动进行主备切换从而保证服务的连续性。

Redis Cluster这是核心生产环境的首选部署方式它支持水平扩展能够通过增加节点来提升系统的处理能力和存储容量。

这种模式下数据被自动分片到多个节点上提高了系统的性能和可用性。

公司自研有些公司会结合Redis的主从和哨兵模式通过自研客户端和Zookeeper消息通知机制构建自有分布式集群架构例如去哪儿网的Qunar Redis Cluster分布式架构二官方Redis Cluster 模式的痛点尽管 Redis Cluster 在理论上支持分布式架构并且提供了许多优势但在实际应用中也面临一些挑战和限制跨槽事务受限在Redis Cluster中由于数据被分散存储在不同的槽中传统的事务操作会受到限制因为一个事务可能需要跨多个槽进行操作这在Cluster模式下并不直接支持。

无法避免热点 key 压力集中在高并发的场景下某些key可能会成为热点导致访问频率极高。

在Cluster模式下这些热点key的压力仍然集中在单个节点上可能会导致该节点成为瓶颈。

故障恢复复杂度较高虽然Redis Cluster提供了高可用性但当节点发生故障时数据的迁移和恢复过程相对复杂。

需要考虑数据一致性、迁移效率和系统稳定性等多方面因素。

针对热 key、慢查询、大 key、写放大等场景还需采取额外的“架构兜底”措施。

这些措施可能包括热点key的分散策略、慢查询的优化、大key的拆分以及写操作的优化等以确保系统的稳定运行和性能表现。

三**、典型问题解析与最佳实践方案**一缓存雪崩集中失效数据库雪崩缓存机制在保护数据库方面扮演着至关重要的角色它通过将常用数据存储在内存中从而提高了数据访问的速度降低了对数据库的直接访问压力。

然而当缓存完全失效时原本应该由缓存处理的数据请求将直接涌向数据库这种情况可能引发严重的连锁反应。

当大量请求同时涌向数据库时数据库可能会因为无法承受如此巨大的负载而瘫痪导致数据无法读取或写入从而进一步导致整个系统的崩溃。

这种由于缓存失效而引发的系统崩溃现象被称为“雪崩”。

雪崩通常发生在两种情况下第一种情况是 Redis本身出现故障或不可用这时所有依赖于 Redis 的缓存数据都将无法访问导致请求直接打到数据库。

第二种情况是 Redis 中的 key 失效这可能是由于 key 的过期时间设置不当或者在更新数据时未能正确更新缓存 key导致数据不一致。

当这些失效的 key 被请求时由于它们在缓存中不存在请求也将直接打到数据库从而引发雪崩效应。

发生场景Redis集群不可用为了保证 Redis 的高可用性生产环境的Redis正常应该都部署为集群模式并确保数据在多个节点之间进行复制。

这样即使某个节点不可用也不会导致所有缓存 key 失效。

同时为增强容灾能力应对某个机房不可用的情况需要将 Redis 集群跨越多个机房进行部署例如在机房 A 部署1个 Master 和1个 Slave在机房 B 部署另外一个 Slave这样可以更好地满足容灾要求。

为了避免在重启 Redis时出现数据丢失的情况需要在重启之前使用 BGSAVE 指令存储 rdb 文件并在重启后重新加载。

此外还可以通过人工触发缓存预热来避免出现雪崩的情况。

因此在进行服务器重启操作时务必小心谨慎避免造成不必要的损失。

发生场景大量缓存失效有时候缓存会在同一时间集中预热。

比如如果每天凌晨定期更新最新数据到缓存就会导致大量缓存在同一时间更新然后它们可能会在一段时间后突然集中失效。

这种情况下可以考虑使用分布式缓存预热的方法将缓存的更新分散到不同的时间段进行避免集中失效的情况发生。

同时也可以通过设置缓存的过期时间让缓存在一定的时间后自动失效避免长时间的缓存失效问题。

除此之外还可以采用多级缓存的方式将缓存分层让不同层级的缓存在不同的时间进行更新和失效提高缓存的稳定性和效率。

缓存雪崩正常情况下缓存能作为数据库或下游服务的屏障。

缓存失效时如果业务流量较大可能会导致系统雪崩因为流量会直接穿透到数据库或下游服务导致它们过载。

为了有效降低系统中由于缓存失效所带来的风险可以采取一系列策略来分散缓存的失效时间从而避免在某一特定时间点发生大规模的缓存失效。

具体解决策略如下错峰过期策略对于每个缓存的key不仅仅设置一个固定的过期时间还会加入一个随机的扰动值。

这样每个key的过期时间都会有所不同从而使得缓存失效时间分散开来避免了缓存同时失效导致的系统压力。

缓存预热机制通过预先在后台运行任务可以提前将那些预计将会成为热点的数据加载到缓存中。

这样当用户访问这些数据时可以直接从缓存中获取而不需要每次都去访问数据库从而减轻了数据库的压力并且提高了系统的响应速度。

实施多级缓存策略可以在系统中部署本地缓存和Redis缓存的双层拦截机制。

本地缓存可以快速响应本地应用的请求而Redis缓存则可以作为第二层缓存提供更广泛的缓存覆盖。

这种多级缓存策略可以进一步提高缓存的命中率减少对后端数据库的直接访问从而有效分散缓存失效带来的风险。

二缓存穿透恶意或无效请求直达数据库缓存穿透是指当查询缓存时由于数据库中本身就不存在相应的数据因此缓存中也就不存在该数据的情况。

在这种情况下如果不断有用户发起对该数据的请求那么这些请求将持续穿透缓存直接访问数据库从而导致数据库的压力增加影响系统性能。

一个典型的例子是查询一个不存在的数据例如查询 id 为 -1 的数据。

由于这个数据在数据库中不存在因此缓存中也不会存在这个数据。

如果用户不断发起这个请求那么每次请求都会穿透缓存直接打到数据库中对数据库造成不必要的压力。

若用户持续访问极大可能是攻击者所为此类攻击易使数据库承受巨大压力最终可能导致数据库崩溃。

不断缓存空值可能会造成空数据污染解决思路

数据校验要解决这类问题首要的是在接入层实施严格的数据校验。

这包括验证 id 是否为正整数、确定 key 的数据类型以及确保参数在合理范围内。

通过在入口层设置这些规则可以有效拦截所有不合法的请求从而避免 Redis 穿透现象的发生。

缓存****空值若某些值无法查询可以在Redis中为对应的key缓存“null”值确保下次访问时仅命中缓存。

这类缓存需设置失效时间考虑到查询不到的数据多为冷数据建议将缓存时间设置得稍短一些。

频率控制缓存空值策略虽能防范普通用户穿透但对狡猾的攻击者则效果不佳。

如果是真正的攻击者他并不会一直用同一个值去请求例如 id 最大值是 999999攻击者可能会从 9999991 开始

9999993……逐个尝试这样既能通过系统的合法检查又能轻易穿透缓存。

如果只用缓存空值的方案很可能整个缓存会一直被这种“空数据”污染。

因此若采用空数据缓存策略避免缓存穿透必须在上游采取针对恶意攻击的频控措施如基于IP维度的限制。

一旦侦测到疑似恶意请求应直接拒绝或返回伪造/降级数据确保缓存安全。

使用布隆过滤器有什么方法可以提前判断数据是否存在于缓存中从而避免不必要的数据库查询吗一种常见的做法是使用缓存系统的“存在性检查”功能。

在尝试从缓存中获取数据之前可以先使用这个功能检查数据是否已存在于缓存中。

如果数据不存在则可以直接返回避免了对数据库的无效查询。

这种方法可以有效减少数据库的负载提高系统的响应速度。

针对这个问题虽然将所有key存储到Java Set中进行contains操作听起来是个不错的想法但实际上由于缓存全量的key可能占据数十GB的内存空间因此并不适合存储在内存中。

你可能会提议将这些key缓存到Redis中但考虑到contains操作的开销几十GB的内存是否值得如此使用如果资源充足为何不一开始就选择Redis来存储整个数据库然而全量key存储于Redis同样不是理想的解决方案。

那有没有可能把这个内存空间减少呢或许你会想到使用 bitmap。

的确减少内存空间的一个有效方法是使用bitmap。

它能显著降低内存使用例如从几十GB降低到几GB。

然而使用bitmap的一个限制是它需要整型key的bit进行置1运算。

若采用哈希方式可能会遇到哈希冲突导致标记不准确。

但是减少内存空间的思路切实可行其中布隆过滤器是有效的解决方案。

它是一种特殊的bitmap通过将一个key经过多次哈希后将对应的bit位标记为1从而判断元素是否存在。

如下图所示。

布隆过滤器示意图布隆过滤器以极小空间甚至小于bitmap高效存储大量元素专注于元素存在性的快速标记。

布隆过滤器的一个显著缺点是“假阳性”。

以判断X是否存在为例三个哈希函数的结果依赖于位

位3是否都为1。

但若有其他元素插入导致这些位已被置为1X的判断可能会产生“误判”。

因此布隆过滤器声称“存在”时可能存在小概率错误。

然而当其声称“不存在”时则相对可信。

然而布隆过滤器已足以高效应对缓存穿透问题。

通过构建一个布隆过滤器来覆盖全数据库数据我们可在查询缓存前先行过滤。

若布隆过滤器回应“不存在”则无需进一步查询缓存或数据库因为数据必然不存在。

即便布隆过滤器偶尔误报“存在”这种小概率事件导致的额外查询开销也是可接受的。

以算法工程师为例他们每日需更新用户的推荐数据。

然而新注册用户缺乏这些数据即便透过缓存也无法获取。

为解决此问题我们可每日构建一个布隆过滤器来存储所有用户信息。

当发现布隆过滤器中不存在的用户时即可确定其为新用户无需进一步查询直接返回默认的推荐数据即可。

使用布隆过滤器逻辑然而布隆过滤器也有其局限性一是上面提到的可能产生误判二是无法支持删除操作。

因此在需要频繁删除数据的场景中布隆过滤器的应用需谨慎考虑。

三缓存击穿热点 key 失效瞬间数据库被打爆使用Redis缓存时还存在一个常见的问题即缓存的key经常设有过期时间。

当这些key失效特别是当它们是热门项时流量可能会突然激增突破Redis的屏障全部涌向数据库。

这种情况在高并发环境下尤为明显可能导致数据库承受巨大的压力甚至引发数据库崩溃的风险。

为了避免这种情况可以采取以下几种策略使用适当的过期时间根据数据的访问频率和重要性合理设置key的过期时间。

对于频繁访问且重要的数据可以设置较长的过期时间以减少失效的可能性。

设置缓存预热机制在系统启动时或低峰时段提前加载并缓存热点数据确保在高并发时段这些数据仍然有效。

实现缓存降级策略当Redis缓存失效时可以通过一些降级策略来减轻数据库的压力。

例如可以使用一些简单的算法或策略来快速生成部分结果或者返回一些默认值以减轻数据库的负载。

引入负载均衡机制在数据库层面引入负载均衡机制将请求分散到多个数据库实例上避免单点故障和压力过大。

监控和预警实时监控Redis和数据库的状态和性能指标当发现异常情况时及时发出预警以便及时采取措施应对。

通过综合考虑以上策略可以有效减少Redis缓存key失效时对数据库的影响提高系统的稳定性和可靠性。

缓存失效的瞬间大量相同的数据库查询及缓存更新操作解决思路

永不过期针对部分热门key建议采用定时任务进行定期更新而非设置过期时间。

此举能有效预防key失效从而提升系统稳定性和效率。

逻辑过期永不过期的策略即保持key的物理状态不失效这种方法通常仅适用于特定且热门的key。

考虑到全量key的存储往往受限于Redis的内存成本。

因此另一种解决方案是设定key的物理过期时间同时引入逻辑过期时间。

例如对于Key1Value1的缓存若其物理过期时间为2024年5月1日01:00我们可在逻辑上将过期时间设为2024年5月1日00:00并相应地调整value的存储方式。

{ v:Value1, t:1714492800000 }当检测到数据过期时可以异步地重新加载缓存并同时更新其物理和逻辑过期时间。

这样只有一条异步线程会穿透到数据库确保了线程的可控性。

在此过程中必须确保物理过期时间始终长于逻辑过期时间。

互斥锁使用互斥锁是处理该问题的有效方式。

一旦发现key不存在应立即加锁只允许一条线程访问数据库查询实际数据而其他线程则需等待。

一旦等待的线程被唤醒并重新检查缓存此时缓存中已有数据即可直接从缓存中返回数据无需再次查询数据库。

缓存不存在的时候只让一条线程去查询数据库其余线程阻塞等待缓存的更新以下是 Java 里利用 Redisson 来操作一个分布式锁的实现代码供参考public String query(String key) { String data stringRedisTemplate.opsForValue().get(key); if (StringUtils.isEmpty(data)) { RLock locker redissonClient.getLock(locker_key);//针对这个key拿锁 //获取分布式锁 if (locker.tryLock()) { try { data stringRedisTemplate.opsForValue().get(key); //注这里双重检查是为了加速因为可能拿到锁之前别的线程已经把数据写入了Redis中 if (StringUtils.isEmpty(data)) { //回源到数据库查询 data getDataFromDB(key); stringRedisTemplate.opsForValue().set(key, data, 5, TimeUnit.SECONDS); } } finally { locker.unlock(); } } else {//拿不到锁证明有别的线程已经在做回源操作了等待一下递归返回 Thread.sleep(

; return query(key); } } return data; }使用分布式锁是为了确保只有一条线程能够访问数据库但实际上在大多数场景下进程级别的锁就足够了。

由于服务数量有限并发请求对数据库的影响并不大因此不需要过度使用分布式锁。

四缓存打满Redis 内存溢出、服务抖动Redis作为内存数据库受限于有限的内存空间。

当内存接近饱和时必须采取淘汰策略释放不再使用的键以容纳新数据。

这一内存上限通过“maxmemory”配置项来设定。

maxmemory 100mb注这与是否开启持久化没有关系也就是说假设内存给了100MB即便开了持久化并且磁盘空间充足在达到 100MB 的时候都会触发淘汰策略。

Redis****的淘汰策略noeviction默认不删除任意数据但 Redis 还会根据引用计数器进行释放这时如果内存不够会直接返回错误。

volatile-lru选择最近的最久未使用的数据进行淘汰但是只从设置了过期时间的数据集中选择。

allkeys-lru选择最近的最久未使用的数据进行淘汰选择的数据集包括设置过期时间以及未设置过期时间的。

volatile-lfu选择使用频率最低的数据进行淘汰但是只从设置了过期时间的数据集中选择。

allkeys-lfu选择使用频率最低的数据进行淘汰选择的数据集包括设置过期时间以及未设置过期时间的。

volatile-random随机选择一个数据进行淘汰但是只从设置了过期时间的数据集中选择。

allkeys-random随机选择一个数据进行淘汰选择的数据集包括设置过期时间以及未设置过期时间的。

volatile-ttl选择最接近过期的数据进行释放操作但是只从设置了过期时间的数据集中选择。

发生场景缓存容量用满通常出现于以下两种场景

把Redis当存储使用在某些特定的应用场景中Redis是一个极其有效的存储工具尤其是在消息推送和实时在线数据处理方面表现突出。

然而它的内存管理策略却需要仔细考虑。

如果不为存储在Redis中的数据设置过期时间其内存使用将会持续增长。

当内存配置不足以容纳所有数据时Redis会启动淘汰策略即删除部分数据以释放内存空间。

然而如果选择的淘汰策略不合适例如选择allkeys-lru最近最少使用策略可能会错误地淘汰掉部分关键数据导致数据丢失或应用性能下降。

因此在使用Redis时我们不仅要充分利用其高性能的特性还需要注意其内存管理和淘汰策略的配置确保数据的完整性和应用的稳定性。

Bug 数据逐步污染****缓存在正常的应用场景中Redis 主要被视作一种高效的缓存解决方案。

它的主要作用在于快速存储和检索数据以优化对数据库或其他慢速存储系统的访问。

然而当使用 Redis 作为缓存时我们经常会设置一些过期时间以确保数据不会无限期地存储在缓存中。

然而有时开发人员可能会忽略设置过期时间或者可能会将过期时间设置得过长。

这可能是由于时间单位的误解或者可能是对 expireAt 和 expire 命令的混淆。

无论是哪种情况如果过期时间设置不当都可能导致缓存空间的过度使用。

当缓存空间被填满时Redis 的性能可能会受到严重影响。

因为 Redis 的数据存储在内存中所以当内存空间被占满时它将无法再存储新的数据。

这可能导致一些请求无法得到处理或者可能导致 Redis 实例崩溃。

因此合理设置和管理 Redis 的过期时间是非常重要的以确保其稳定且高效地运行。

解决方案

存储隔离对于永不过期的数据需要将其与正常的缓存数据进行集群的分离。

这是为了确保能够设置不同的淘汰策略以适应不同的数据需求和场景。

通过将永不过期的数据与正常的缓存数据分开存储可以更加灵活地管理这些数据并根据其特性和使用情况进行优化。

在集群分离后可以为永不过期的数据设置特定的存储策略以确保它们始终可用且不会被错误地淘汰。

同时对于正常的缓存数据可以根据实际需求和应用场景设置适当的淘汰策略如基于时间、访问频率或数据大小等因素进行淘汰。

通过这种分离和设置不同的淘汰策略可以提高系统的整体性能和稳定性确保关键数据的可靠性和可用性。

同时这也为系统提供了更大的灵活性和可扩展性以适应不断变化的数据需求和应用场景。

容量监控在使用Redis作为存储时预估容量和冗余设置是至关重要的需要特别关注其容量管理和监控。

首先需要根据业务发展的实际情况提前预估好数据增量的需求并为可能的高速增长预留足够的冗余空间。

这是因为如果数据不设置过期时间随着时间的推移Redis中的数据量会逐渐增加可能会超出其存储容量限制导致性能下降或数据丢失。

其次即使制定了详尽的容量计划实际情况可能总是充满变数需要实时监控Redis集群的使用情况以便及时应对突发的变化。

通过设置监控告警可以在数据量超过危险阈值时立即扩容并排查导致数据量增加的原因从而避免数据丢失或性能问题。

此外还需要关注Redis中key的过期时间设置。

为了避免出现key永不过期或过期时间设置不合理的情况应该定期扫描所有key的过期时间并检查是否有需要设置过期时间但未设置的key或者将过期时间设置得过于短暂的key。

通过这些设置可以及时找出问题的源头并在问题爆发之前采取相应的措施确保Redis的稳定运行。

总之在使用Redis作为存储时需要提前预估好数据增量的需求并实时监控Redis集群的使用情况同时关注key的过期时间设置。

只有这样才能确保Redis的稳定运行满足业务的需求。

五热 Key 问题高并发下的“流量集中爆点”热key问题是Redis 中常见的一个性能瓶颈也是Redis性能优化的难点即便在集群模式下进行水平扩容也无法完全解决。

由于key是按照哈希算法分桶存储对同一key的访问会集中在同一实例上导致该实例承受巨大压力。

即使Redis实例性能高达10W QPS也无法完全避免热key问题因为该实例还需处理写请求和其他key的请求。

因此单key的读QPS会受到严重影响。

热 key 问题可能引发多重负面影响首先可能导致接口响应超时进而可能引发连锁反应形成雪崩效应。

其次由于网卡负载过重可能导致大量请求处理失败。

最后热 key 占用过多的连接数将对其他请求的处理造成干扰。

要解决热key问题关键在于准确识别和管理热key。

实际上大部分key并非热key而且在大部分时间里热key也并非持续热。

因此针对热key的有效识别和管理成为解决问题的核心。

如何处理热 key成功识别出热 key 并明确了其具体归属后我们能如何利用这些信息来进行处理和优化呢接下来让我们一探究竟。

本地****缓存为了高效应对热key最为行之有效的方式就是在访问Redis之前增设一层本地缓存。

鉴于本地缓存的存储空间相对有限因此合理利用这一资源专门用于存储这些热门的key显得尤为关键。

在高并发系统中设计本地缓存时需充分考虑实时下发特定key并主动将其植入本地缓存的场景。

例如在即将到来的“双11”大促中部分促销商品所对应的热key完全可以提前预估又或者通过实时监控我们已得知某个明星的“瓜”即将成为公众关注的焦点。

对于这些可预见的热key仅仅依靠事后修改代码进行发布显然为时已晚。

因此在设计之初我们就应预先规划好本地缓存的部署策略。

在正常情况下这些缓存可能并不会发挥太大作用但一旦我们主动下发特定的key它们便能迅速被本地缓存所捕获并存储。

更为理想的是这些key能够保持长效不受时间限制同时还需确保它们能够异步地从Redis中同步最新值以保证本地缓存的实时性和准确性。

本机Redis备机本地缓存是个很好的做法但是它有一些局限性。

首先本地缓存的空间很小。

因为通常服务本身的内存并不会分配很大而本地缓存只能占据其中很小的一部分所以对于本地缓存只能“精挑细选”部分数据放入其中。

这里涉及到一定的淘汰策略设计、热 key 发现机制等。

再者如何维护本地缓存和 Redis保持 Redis 和数据库的一致性等这些本身也具有一定的挑战。

如果希望把全 Redis 的数据都导入理论上也不是不可能。

因为很多 Redis 集群的总内存大小并非“遥不可及”可以让这个服务的内存分配足够大例如 20GB 的空间里面大部分空间作为 Redis 的本地缓存然而会存在导致服务内存太大而影响服务本身质量例如 GC 问题严重的风险。

一种黑科技方案是将 Redis 集群的备实例全部部署在服务所在机器上。

这样服务访问本机备实例的速度接近访问本地缓存。

由于每个服务实例的流量都分散到其本机部署的备实例上相当于将热 key 的流量倾斜自然分散。

此时本机备实例就承担了类似本地缓存的功能。

另一方面由于 Redis 本身有主备同步机制所以实际上这个本机的备伪本地缓存的一致性通过 Redis 的同步机制实现了。

这一套方案适用于服务数量不是很多Redis 本身存储量也不是很大且机器的部署还是物理机部署的场景通常一台物理机内存容量很大上面会混合部署很多服务提升利用率。

并且最好 Redis 的集群是主从/哨兵模式的集群如果是 Cluster 模式的集群由于实例太多需要部署很多备实例再者配置集群地址的时候也很麻烦。

使用本地 Redis 备机充当本地缓存

备份存储Cluster 模式下某个 key 是存储在固定的某个实例上的所以热 key 才如此棘手因为所有流量都打到同一个实例上。

那么有没有可能打散这些流量呢答案是有可能的。

如果把热 key 备份成 N 份例如原 key 是 goods:iphone:detail那么这 N 份的 key 就分为 goods:iphone:detail:0goods:iphone:detail:1goods:iphone:detail:2……goods:iphone:detail:N-1分散在集群的多个节点查询的时候可以按照一定的散列规则分散去访问不同的 key 副本规则可以选择随机散列、按用户散列等。

同一个 key 备份存储在不同实例上随机散列的示意 Java 代码如下int N M * 2//M是集群里的节点数得到备份数量 //生成随机数 int random new Random().nextInt(N); //构造备份新key String bakHotKey hotKey “_” random String data getFromRedis(bakHotKey); if (data null) {//查询不到缓存从数据库查询出来放到对应的备份Key data getFromDB(); saveToRedis(bakHotKey, expireTime); }注以上代码中 N 取了节点数 2 倍的原因是由于 Redis 的散列存储算法是内置固定的无法 100% 保证不同的备份 key 肯定落在不同副本上所以 N 的取值上取了一点冗余。

读写分离以上所有的解决思路都是需要修改代码发版的那万一现在线上已经出现热 key 并影响到服务了有没有不发版的思路来临时解决问题呢因为热 key 基本都是读场景的可以利用 Redis 集群里的备节点去扛这些读流量。

(

开启读写分离。

如果是云上的一些 Redis 产品读写分离通常已经集成在其中只需要开启即可例如腾讯云的 Redis详见腾讯云Redis开关读写分离[1]。

如果是自建部署的 Redis 集群则需要根据自建集群的架构开发一个支持读写分离的客户端 SDK。

(

扩备副本。

正常情况下副本是用来做容灾的如果开启了读写分离那么扩副本还能扩展读流量。

六Big Key问题单key引发的性能瓶颈除了热 key 是大型系统的棘手的问题之外大 key 也是其中一个。

大 key 带来的问题

内存倾斜由于单个 key 只会存在于集群中的某个实例大 key 的存在容易让这个实例的内存占用比别的实例大大 key 的操作又会使得这个实例的 CPU 负载普遍更大容易成为隐患点。

网络阻塞涉及到大 key 的操作尤其是使用 hgetall、get、hmget 等操作时网络 I/O 可能会成为瓶颈。

阻塞查询由于 Redis 内部处理大 key 是单线程处理的而大 key 的操作通常特别耗时这就导致这个实例上的其他语句都在排队受到阻塞进而影响整个集群的服务能力。

大 key 可能发生的场景

部分列表类存储例如粉丝列表如果用一个 list 或者 hash 存储全量的粉丝列表对于一个大 V 博主他的粉丝列表必然会是大 key。

统计类的集合有时候需要按天统计某类用户的集合如果采取类似 set 的方式去存储随着用户人数的逐步累积此 key 必然是大 key。

大数据缓存类通常会用 Redis 作为数据库的缓存如果缓存的数据过大例如把几万行的数据变成一个 json 存储这个 key就会变成大 key。

如何发现大 key

分析 RBD 文件可以在 Redis 实例上执行 bgsave然后对 dump 出来的 RDB 文件进行分析从而找到其中的大 key。

scandebugRedis 有个 debug object 命令可以看到这个 key 在内存中序列化后的大小。

结合 scan 命令就可以筛选出当前实例所有 key 的大小从而找到哪些是大 key。

redis-cli --bigkeysredis-cli 中自带一个 bigkeys 命令可以找到某个实例 5 种数据类型String、hash、list、set、zset的最大 key。

例如redis-cli --bigkeys -i

1如何删除大 key当侦查出大 key 之后最直接的应对手段应该就是删除这些大 key 了。

然而如果直接使用 del 指令删除大 key 可能又踩进坑了。

很多时候大 key 是一个集合类型而集合类型的删除操作时间复杂度是O(M)M 是元素的数量。

DEL keyTime complexity: O(N) where N is the number of keys that will be removed. When a key to remove holds a value other than a string, the individual complexity for this key is O(M) where M is the number of elements in the list, set, sorted set or hash. Removing a single key that holds a string value is O(

.删除 1 个字符串是 O(

的时间复杂度那么删除一个大 key 的字符串耗时也是比较大的而在一个单线程模型的 Redis 里执行这样的耗时操作无疑又会阻塞其他指令的执行。

Lazy Free即惰性删除或延迟释放。

当删除键的时候Redis

0 提供异步延时释放 key 内存的功能——把 key 释放操作放在 Background I/O 子线程处理从而减少删除 big key 对 Redis 主线程的阻塞。

UNLINKRedis

4.

0 版本提供了一个 UNLINK 命令其时间复杂度是 O(

在指定删除某个 key 时无论 key 有多大都会分配另外一个线程做回收内存的操作。

集合 scan 命令如果 Redis 版本比较低我们也可以使用集合配套的 scan 命令分批删除。

例如删除大的 hash 可以额使用 hscan 命令每次获取少量的字段再用 hdel 命令删除这些字段删除大的 set 则可以使用 sscan 每次扫描少量元素然后使用 srem 进行删除。

如何避免产生大 key前面介绍了如何发现大 key及如何优雅地删除它们但是最好的方法还是提前做好设计。

针对可能出现的大 key在设计之初就可以避免它的产生最好的手段便是拆。

例如一个粉丝列表 list。

针对一些大 V 博主可以按照粉丝的 userid 决定其存在于哪个 list拆分成 list

list

list

list3 等。

针对一个大的 hash也可以将不同的 field 分散成多个子 hash并且要先计算在哪个子 hash 中进行获取。

根据粉丝的 userid 进行散列到不同的 list 之中

实战

案例分析去哪儿网 Redis 高并发缓存优化实践作为国内领先的在线旅游平台去哪儿网在酒店、机票、火车票等核心业务模块中承载着亿级用户的访问需求Redis 在其中发挥着核心缓存支撑作用。

尤其在节假日、机票促销、火车票开售等流量高峰期对 Redis 缓存系统的稳定性和并发处理能力提出了极高要求。

一案例背景在某次“五一”假期即将到来之际去哪儿网特别推出了大规模的出行优惠活动。

这一活动吸引了大量用户访问其网站导致首页推荐、搜索列表、价格日历等接口的并发请求数量急剧增加。

由于访问量的激增部分业务系统开始出现响应延迟缓存命中率也随之下降。

在这样的高负载情况下一些Redis实例的CPU使用率飙升至90%以上甚至更高。

经过深入分析我们找出了造成这一系列问题的核心原因由于某些热点key例如航线价格日历的访问过于集中导致了实例倾斜的问题。

这意味着大部分的请求都集中在少数几个Redis实例上使得这些实例的负载远高于其他实例。

数据缓存的过期时间设置得完全相同这导致了在某个时间点上大量缓存数据几乎同时过期从而引发了缓存雪崩现象。

缓存雪崩是指大量缓存同时失效导致大量请求直接打到后端数据库对数据库造成巨大压力。

热门推荐key的缓存击穿问题也不容忽视。

当缓存中的某个热门推荐key失效时大量请求会瞬间回源到数据库这就像在缓存上打了一个洞导致数据库承受巨大的压力。

还有个别大key例如某些组合推荐数据的操作可能会阻塞其他请求。

这些大key的处理时间较长会占用更多的资源从而影响到其他请求的处理速度。

缓存系统的监控粒度不足导致问题的发现和响应存在滞后。

由于监控不够细致无法及时发现并解决缓存系统中的问题从而影响了整个系统的稳定性和性能。

二优化策略去哪儿网业务研发和DBA团队以下简称技术团队在面对性能瓶颈时迅速采取了一系列多维度的优化方案以提升系统性能和用户体验

缓存****逻辑过期 异步刷新针对热门航线日历、爆款机票等高频访问的 key去哪儿网技术团队引入了逻辑过期机制。

他们设置了相对较长的物理 TTLTime To Live并在数据结构中维护了一个逻辑过期时间字段。

当访问命中逻辑过期时系统会立即返回旧数据并由后台异步任务刷新缓存。

这样的设计保证了服务的连续性即使在数据更新过程中也不会中断服务。

热 key 多副本随机分片针对特定的热点 key例如 flight:price:calendar:去哪儿网技术团队引入了多副本副 key 机制。

他们将这些热点 key 拆分成多个副本如 flight:price:calendar:SHA-BEJ:

:

:2 等。

在查询时系统会根据 hash(userId) 或随机因子选取一个副本进行读取这样可以有效地将流量均匀分散到不同的 Redis 节点上从而避免了单点压力过大导致的性能问题。

本地缓存前置对于一些常驻热数据如部分城市首页推荐、火车票热门站点等去哪儿网技术团队在 JVM 内部设置了一层本地缓存。

这些数据会在服务启动时或通过定时任务主动进行预热。

此外结合 Redis Pub/Sub 实现数据的异步推送刷新这样可以减少对 Redis 的依赖从而提升整体的 QPS上限。

缓存****预热 随机过期时间对于活动类数据比如出行攻略、节日打包套餐等去哪儿网技术团队采用了分时段的预热任务来加载数据。

同时他们为这些数据设置了随机的过期时间例如 TTL 600s rand(

s)这样做可以有效避免缓存同时失效带来的雪崩风险。

Redis****资源隔离与集群拆分去哪儿网技术团队将写多读少的数据与高频读场景如价格日历、机票搜索结果分配至不同的 Redis 集群以实现资源的隔离。

针对不同业务集群设置不同的淘汰策略部分缓存集群采用 volatile-lfu 策略而重要配置类 key 则设置为 volatile-ttl对于那些“永不过期”的数据去哪儿网技术团队部署了独立的 Redis 集群以避免这些数据影响到缓存类资源的回收。

构建完整监控与告警链路去哪儿网技术团队接入了 Prometheus 和 Grafana对 Redis 实例的连接数、key数量、hit/miss比、慢查询等核心指标进行了细粒度监控。

还为关键指标设置了告警阈值并支持按集群和业务维度进行追踪。

此外去哪儿网还搭建了 Redis bigkeys 扫描任务定时检测潜在的大 key以避免因误操作或脏数据导致的实例性能问题。

三优化结果在经过一系列精心策划和实施的优化措施之后公司核心Redis集群成功地应对了“五一”假期期间的高访问量挑战系统能够稳定地处理每日上亿次的Redis请求确保了服务的连续性和可靠性。

在这一过程中核心Redis集群性能指标表现优异具体如下指标项优化前优化后Redis 单节点 CPU 峰值72%28%缓存命中率64%

9

5%数据库回源 QPS4000500 以下接口平均响应耗时120ms56ms此次优化的实践不仅成功地支撑了节假日高峰时段的访问需求而且通过这一过程去哪儿网技术团队还积累并沉淀了一套高效可用、能够应对高并发场景的缓存框架。

这套框架的建立为未来面对“暑期旺季”、“十一高峰”、“春节高峰”等节假日时去哪儿网能够提供更加稳定和高效的技术支持打下了坚实的基础。

Redis 高并发使用规范清单为了确保Redis在高并发环境下的性能和稳定性所有存储在Redis中的key都必须设置一个合理且恰当的过期时间。

这样可以有效避免因数据长期不更新而导致的资源浪费。

对于那些被频繁访问的热点key建议采取多副本策略或者将数据落地到本地缓存中以减轻Redis服务器的压力并提高访问速度。

对于那些可能返回空值的缓存应当设置一个较短的生存时间TTL以避免空值缓存长期占用内存空间从而影响系统的整体性能。

定期对那些长时间未被访问的长尾key进行清理可以有效减少Redis占用的内存空间提高系统的运行效率。

在进行多个操作时合理使用Lua脚本或Pipeline技术可以减少网络请求的往返次数从而降低延迟提高Redis的处理能力。

对于那些体积较大的key应当进行拆分处理以避免单个key操作导致的Redis阻塞确保系统的响应速度和稳定性。

设置合适的淘汰策略对于内存管理至关重要。

通常情况下volatile-lru策略是一个常用且有效的选择它可以根据最近最少使用LRU算法淘汰那些带有过期时间的key从而优化内存使用。

运维与监控可视化助你“看清 Redis 健康”推荐使用一些高效的工具如 Redis Insight、Prometheus 结合 Grafana以及通过 bigkeys 定时任务扫描等方法更好地监控 Redis 的运行状态。

通过设置多维度的告警机制例如针对内存使用情况、缓存命中率、客户端连接数等关键指标可以快速地发现系统中的热点问题或异常情况从而及时采取措施确保 Redis 的稳定运行。

关于运维、监控及可视化健康度方面的具体实践欢迎关注去哪儿DBA团队后续的专题文章。

未来趋势Redis 还够用吗Redis 确实强大但对于极致高并发、低延迟需求也有一些新兴选择值得关注KeyDB基于多线程架构实现垂直扩展、Dragonfly采用创新内存管理突破百万级QPS、Tair阿里云自研支持冷热数据分层等新一代高性能缓存系统。

它们在内存碎片整理、数据压缩算法、集群通信协议等方面展现出独特优势特别是在处理TB级缓存和亚毫秒级响应场景中表现突出。

9.1下载软件-9.1下载软件应用

百度百家号客服电话人工服务

123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123