分布式缓存的25个优秀实践与线上案例 done

本文介绍了使用分布式缓存的最佳实践和线上案例,包括缓存设计的核心要素、优秀实践及常见问题解决方案,帮助开发者避免生产事故。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

杨彪,蚂蚁金服技术专家,《分布式服务架构:原理、设计与实战》和《可伸缩服务架构:框架与中间件》作者。近10年互联网和游戏行业工作经验。


本文节选自即将出版的《可伸缩服务架构:框架与中间件》一书,作者:李艳鹏、杨彪、李海亮、贾博岩、刘淏。



本文主要介绍使用分布式缓存的优秀实践和线上案例。这些案例是笔者在多家互联网公司里积累并形成的优秀实践,能够帮助大家在生产实践中避免很多不必要的生产事故。


一、缓存设计的核心要素


我们在应用中决定使用缓存时,通常需要进行详细的设计,因为设计缓存架构看似简单,实则不然,里面蕴含了很多深奥的原理,如果使用不当,则会造成很多生产事故甚至是服务雪崩之类的严重问题。


笔者在做设计评审的过程中,总结了所有与缓存设计相关的设计点,这里列出来供大家参考。


1、容量规划


  • 缓存内容的大小

  • 缓存内容的数量

  • 淘汰策略

  • 缓存的数据结构

  • 每秒的读峰值

  • 每秒的写峰值


2、性能优化


  • 线程模型

  • 预热方法

  • 缓存分片

  • 冷热数据的比例


3、高可用


  • 复制模型

  • 失效转移

  • 持久策略

  • 缓存重建


4、缓存监控


  • 缓存服务监控

  • 缓存容量监控

  • 缓存请求监控

  • 缓存响应时间监控


5、注意事项


  • 是否有可能发生缓存穿透

  • 是否有大对象

  • 是否使用缓存实现分布式锁

  • 是否使用缓存支持的脚本(Lua)

  • 是否避免了Race Condition


笔者在这里把这些设计点提供给读者,请读者在做缓存设计时把每一项作为一个思考的起点,思考我们在设计缓存时是否想到了这些点,以避免在设计的过程中因忽略某一项而导致严重的线上事故发生。


二、缓存设计的优秀实践


笔者在做设计评审的过程中,总结了一些开发人员在设计缓存系统时的优秀实践,如下所述:


优秀实践1


缓存系统主要消耗的是服务器的内存,因此,在使用缓存时必须先对应用需要缓存的数据大小进行评估,包括缓存的数据结构、缓存大小、缓存数量、缓存的失效时间,然后根据业务情况自行推算在未来一定时间内的容量的使用情况,根据容量评估的结果来申请和分配缓存资源,否则会造成资源浪费或者缓存空间不够。


优秀实践2


建议将使用缓存的业务进行分离,核心业务和非核心业务使用不同的缓存实例,从物理上进行隔离,如果有条件,则请对每个业务使用单独的实例或者集群,以减小应用之间互相影响的可能性。笔者就经常听说有的公司应用了共享缓存,造成缓存数据被覆盖以及缓存数据错乱的线上事故


优秀实践3


根据缓存实例提供的内存大小推算应用需要使用的缓存实例数量,一般在公司里会成立一个缓存管理的运维团队,这个团队会将缓存资源虚拟成多个相同内存大小的缓存实例。


例如一个实例有4GB内存,在应用申请时可以按需申请足够的实例数量来使用,对这样的应用需要进行分片,详情请参考《可伸缩服务架构:框架与中间件》中4.4.3的内容。这里需要注意,如果我们使用了RDB备份机制,每个实例使用4GB内存,则我们的系统需要大于8GB内存,因为RDB备份时使用了 copy-on-write 机制,需要fork出一个子进程,并且复制一份内存,因此需要双份的内存存储大小。

关于redis两种备份机制:https://www.cnblogs.com/rollenholt/p/3874443.html


优秀实践4


缓存一般是用来加速数据库的读操作的,一般先访问缓存后访问数据库,所以缓存的超时时间的设置是很重要的。笔者曾经在一家互联网公司遇到过由于运维操作失误导致缓存超时设置得较长,从而拖垮服务的线程池,最终导致服务雪崩的情况。


优秀实践5


所有的缓存实例都需要添加监控,这是非常重要的,我们需要对慢查询、大对象、内存使用情况做可靠的监控。


优秀实践6


我们不推荐多个业务共享一个缓存实例,但是由于成本控制的原因,这种情况经常出现,我们需要通过规范来限制各个应用使用的key有唯一的前缀,并进行隔离设计,避免产生缓存互相覆盖的问题。


优秀实践7


任何缓存的key都必须设定缓存失效时间,且失效时间不能集中在某一点,否则会导致缓存占满内存或者缓存雪崩


优秀实践8


低频访问的数据不要放在缓存中,如我们前面所说的,我们使用缓存的主要目的是提高读取性能。


曾经有个小伙伴设计了一套定时的批处理系统,由于批处理系统需要对一个大的数据模型进行计算,所以该小伙伴把这个数据模型保存在每个节点的本地缓存中,并通过消息队列接收更新的消息来维护本地缓存中模型的实时性,但是这个模型每个月只用了一次,所以这样使用缓存是很浪费的。


既然是批处理任务,就需要把任务进行分割,进行批量处理,采用分而治之、逐步计算的方法,得出最终的结果即可。


优秀实践9


缓存的数据不易过大,尤其是Redis,因为Redis使用的是单线程模型,在单个缓存key的数据过大时,会阻塞其他请求的处理。


优秀实践10


对于存储较多value的key,尽量不要使用HGETALL等集合操作,该操作会造成请求阻塞,影响其他应用的访问。


优秀实践11


缓存一般用于在交易系统中加速查询的场景,有大量的更新数据时,尤其是批量处理时,请使用批量模式,但是这种场景较少。


优秀实践12


如果对性能的要求不是非常高,则尽量使用分布式缓存,而不要使用本地缓存,因为本地缓存在服务的各个节点之间复制,在某一时刻副本之间是不一致的,如果这个缓存代表的是开关,而且分布式系统中的请求有可能会重复,就会导致重复的请求走到两个节点,一个节点的开关是开,一个节点的开关是关,如果请求处理没有做到幂等,就会造成处理重复,在严重情况下会造成资金损失。


优秀实践13


在写缓存时一定要写入完全正确的数据,如果缓存数据的一部分有效、一部分无效,则宁可放弃缓存,也不要把部分数据写入缓存,否则会造成空指针、程序异常等。


优秀实践14


在通常情况下,读的顺序是先缓存,后数据库;写的顺序是先数据库,后缓存


优秀实践15


在使用本地缓存(如Ehcache)时,一定要严格控制缓存对象的个数及声明周期。由于JVM的特性,过多的缓存对象会极大影响JVM的性能,甚至导致内存溢出等


优秀实践16


在使用缓存时,一定要有降级处理,尤其是对关键的业务环节,缓存有问题或者失效时也要能回源到数据库进行处理。


三、关于常见的缓存问题的线上案例


笔者在多家互联网公司负责架构方案评审和线上事故复盘,这里列举其中的一些典型案例,供大家参考和借鉴。


案例1


现象:某应用程序的数据库负载瞬时升高。


原因:在应用程序中对使用的大量缓存key设置了同一个固定的失效时间,当缓存失效时,会造成在一段时间内同时访问数据库,造成数据库的压力较大。


总结:在使用缓存时需要进行缓存设计,要充分考虑如何避免常见的缓存穿透、缓存雪崩、缓存并发等问题,尤其是对于高并发的缓存使用,需要对key的过期时间进行随机设置,例如,将过期时间设置为10秒+random(2),也就是将过期时间随机设置成10~12秒。


案例2


现象:导致迁移前后两个系统的核心操作重复。


原因:在迁移的过程中,重复的流量进入了不同的节点,由于使用了本地缓存存储迁移开关,而迁移开关在开关打开的瞬间导致各个节点的开关状态不一致,有的是开、有的是关,所以对于不同节点的流量的处理重复,一个走了开关开的逻辑,一个走了开关关的逻辑。


总结:避免使用本地缓存来存储迁移开关,迁移开关应该在有状态的订单上标记。


案例3


现象:某模块设计使用了缓存加速数据库的读操作的性能,但发现数据库负载并没有明显下降。


原因:由于这个模块的使用方查询请求的数据在数据库中不存在,是非法的数据,所以导致缓存没有命中,每次都穿透到数据库,且量级较大。


总结:在使用缓存时需要进行缓存设计,要充分考虑如何避免常见的缓存穿透、缓存雪崩、缓存并发等问题,尤其是对高并发的缓存使用,需要对无效的key进行缓存,以抵挡恶意的或者无意的对无效缓存查询的攻击或影响。


案例4


现象:监控系统报警,Redis中单个哈希键占用的空间巨大。


原因:应用系统使用了哈希键,哈希键本身有过期时间,但是哈希键里面的每个键值对没有过期时间。


总结:在设计Redis的过程中,如果有大量的键值对要保存,则请使用字符串键的数据库类型,并对每个键都设置过期时间,请不要在哈希键内部存储一个没有边界的集合数据实际上,无论是对缓存、内存还是对数据库的设计,如果使用任意一个集合的数据结构,则都要考虑为它设置最大限制,避免内存用光,最常见的是集合溢出导致的内存溢出的问题。


案例5


现象:某业务项目由于缓存宕机导致业务逻辑中断,数据不一致。


原因:Redis进行主备切换,导致瞬间内应用连接Redis异常,应用并没有对缓存做降级处理


总结:对于核心业务,在使用缓存时一定要有降级方案。常见的降级方案是在数据库层次预留足够的容量,在某一部分缓存出现问题时,可以让应用暂时回源到数据库继续业务逻辑,而不应该中断业务逻辑,但是这需要严格的容量评估,请参考《分布式服务架构:原理设计与实战》第3章的内容。


案例6


现象:某应用系统负载升高,响应变慢,发现应用进行频繁GC,甚至出现OutOfMemroyError: GC overhead limt exceed的错误日志。


原因:

因为这个项目是个历史项目,使用了Hibernate ORM框架,在Hibernate中开启了二级缓存,使用了Ehcache;但是在Ehcache中没有控制缓存对象的个数,缓存对象增多,导致内存紧张,所以进行了频繁的GC操作


总结:

使用本地缓存(如Ehcache、OSCache、应用内存)时,一定要严格控制缓存对象的个数及声明周期


案例7


现象:某个正常运行的应用突然报警线程数过高,之后很快就出现了内存溢出


原因:由于缓存连接数达到最大限制,应用无法连接缓存,并且超时时间设置得较大,导致访问缓存的服务都在等待缓存操作返回,由于缓存负载较高,处理不完所有的请求,但是这些服务都在等待缓存操作返回,服务这时在等待,并没有超时,就不能降级并继续访问数据库。这在BIO模式下线程池就会撑满,使用方的线程池也都撑满;在NIO模式下一样会使服务的负载增加,服务响应变慢,甚至使服务被压垮。


总结:在使用远程缓存(如Redis、Memcached)时,一定要对操作超时时间进行设置,这是非常关键的,一般我们设计缓存作为加速数据库读取的手段,也会对缓存操作做降级处理,因此推荐使用更短的缓存超时时间,如果一定要给出一个数字,则希望是100毫秒以内


案例8


现象:某项目使用缓存存储业务数据,上线后出现错误问题,开发人员束手无策。


原因:开发人员不知道如何发现、排查、定位和解决缓存问题。


总结:在设计缓存时要有降级方案,在遇到问题时首先使用降级方法,还要设计完善的监控和报警功能,帮助开发人员快速发现缓存问题,进而来定位和解决问题。


案例9


现象:某项目在使用缓存后,开发测试通过,到生产环境后,服务却出现了不可预知的问题。


原因:该应用的缓存key与其他应用缓存 key冲突,导致互相覆盖,出现逻辑错误。


总结:在使用缓存时一定要有隔离的设计,可以通过不同的缓存实例来做物理隔离,也可以通过各个应用的缓存key使用不同的前缀进行逻辑隔离

新书抢先看



本文节选自即将出版的《可伸缩服务架构:框架与中间件》一书,此书现已开放预售,可登录网址:https://item.jd.com/12308233.html 抢先订购。更多新书精华内容也将由DBAplus社群陆续呈现。





<think>嗯,用户想知道如何测试和验证Redis或其他分布式缓存系统的键过期机制。我需要先回忆一下Redis的键过期机制是怎么工作的。Redis使用的是被动删除和主动删除两种方式。被动删除是在访问键时检查是否过期,主动删除则是定期随机检查一些键。那测试的时候,可能需要覆盖这两种情况。 首先,用户可能需要确认键是否真的在设定的时间后过期。这可以通过设置一个键,带过期时间,然后等待一段时间后检查是否存在。但要注意,被动删除可能不会立即生效,所以需要主动去获取这个键,触发检查。或者使用Redis的`TTL`命令查看剩余时间。不过,如果系统是分布式的,可能有多个节点,需要确认所有节点上的键都过期了。这时候可能需要检查每个节点的状态,或者使用一致性检查工具。 然后,测试不同过期时间的准确性,比如短期(几秒)和长期(几小时)的过期是否准确。这时候可能需要编写脚本,设置多个不同过期时间的键,并记录它们的过期情况。还要考虑系统时间的问题,比如服务器时间是否正确,是否会影响过期机制。另外,分布式环境下,时钟同步也是一个问题,如果不同节点时间不一致,可能导致过期时间不一致。 接下来是主动过期策略的测试。Redis的主动删除是每隔一段时间随机检查一些键,所以可能需要模拟大量带过期时间的键,然后观察内存使用情况,看看是否会被正确清理。这时候可能需要使用工具比如`redis-cli`的`INFO`命令查看内存和键的数量变化。 另外,在分布式缓存系统中,比如Redis集群,键可能分布在不同的节点上。测试的时候需要确保即使某个节点宕机,过期机制依然有效,或者故障转移后过期时间仍然正确。这可能涉及到高可用性和数据一致性的测试。 还有边界情况,比如过期时间设置为0或者负数,Redis会如何处理?根据文档,过期时间应该是正整数,设置非正数可能会导致立即删除或者错误。需要实际测试这些情况,并验证系统的反应。 另外,测试工具的选择也很重要。可以使用单元测试框架,比如Python的`unittest`,结合Redis的客户端库,如`redis-py`,编写自动化测试用例。或者使用像`Redis-Check-AOF`这样的工具来检查持久化文件中的过期键是否正确处理。 还需要考虑并发情况下的表现,比如同时设置大量带过期时间的键,系统是否能正常处理,不会因为过期键的清理导致性能下降或崩溃。这时候可能需要压力测试工具,比如`redis-benchmark`,模拟高并发场景。 最后,测试结果的验证,需要确保日志记录正确,或者通过监控工具查看键的过期事件。Redis的配置中有没有开启过期事件的通知功能,这可能需要配置`notify-keyspace-events`选项,然后订阅相关频道来捕获过期事件。 总结一下,测试步骤可能包括:基本功能测试、时间准确性测试、主动删除策略测试、分布式环境测试、边界条件测试、并发压力测试和监控验证。需要综合运用多种测试方法和工具来全面验证键过期机制的正确性。</think>### 如何测试和验证Redis等分布式缓存系统的键过期机制 键过期机制是缓存系统的核心功能之一,测试和验证需从功能、时间精度、分布式一致性等维度展开。以下是分步说明: --- #### **1. 基本功能验证** - **设置键并检查过期** 使用`SETEX`或`EXPIRE`命令设置带过期时间的键,等待指定时间后检查键是否存在: ```bash SETEX test_key 10 "value" # 设置10秒后过期 TTL test_key # 查看剩余生存时间(返回-2表示已过期) GET test_key # 过期后返回nil ``` 需注意:Redis采用惰性删除和定期删除策略,键可能在访问时才会触发过期检查[^1]。 - **边界条件测试** - 设置0或负数的过期时间(Redis会立即删除键)。 - 超长过期时间(如`EXPIRE key 2147483647`,测试最大值限制)。 --- #### **2. 时间精度测试** - **短期过期验证** 设置1秒、5秒等短时间,验证系统是否精确删除。可通过脚本循环检测: ```python import redis, time r = redis.Redis() r.setex("short_key", 5, "data") for _ in range(6): print(r.get("short_key")) # 每秒检查一次 time.sleep(1) ``` - **时钟同步问题** 在分布式系统中,若节点间时钟不同步,可能导致过期时间不一致。需使用NTP服务同步时间,并检查跨节点键的过期一致性。 --- #### **3. 主动过期策略测试** Redis默认每100ms随机抽查20个键,删除过期键。可通过以下方式验证: - **模拟大量过期键** 批量设置大量短期过期的键,观察内存释放情况: ```bash for i in {1..1000}; do redis-cli SETEX key_$i 2 "data"; done ``` 使用`INFO memory`监控内存变化。 --- #### **4. 分布式场景测试** - **跨节点一致性** 在Redis集群中,键分布在多个节点。需验证: - 键在过期后,所有副本节点同步删除。 - 故障转移后,过期时间仍正确生效。 - **使用Redisson等客户端库** 测试分布式锁等场景下的自动续期和过期行为。 --- #### **5. 监控日志验证** - **开启键过期事件通知** 修改Redis配置`notify-keyspace-events Ex`,订阅`__keyevent@0__:expired`频道接收过期事件。 ```bash CONFIG SET notify-keyspace-events Ex SUBSCRIBE __keyevent@0__:expired ``` - **日志分析** 检查Redis日志中是否记录过期键的删除操作。 --- #### **6. 工具辅助测试** - **Redis-CLI调试** 使用`DEBUG OBJECT key`查看键的内部信息,包括过期时间戳。 - **压力测试工具** 如`memtier_benchmark`模拟高并发写入/读取,验证过期机制在高负载下的稳定性。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值