聊到“分布式缓存与微服务架构的集成”,很多人第一反应是“这不就是个Redis的事嘛”。实际动手之后你才会发现,把Redis启动起来太简单了,但把它稳定、高效、安全地嵌进几十个微服务里,还要处理缓存穿透、击穿、雪崩、数据不一致、大Key热Key这些坑,才是真正难啃的部分。这篇文章我结合自己做过的项目,从缓存到底该不该上、该怎么选型,到代码里怎么写、线上怎么排障,完整拆一遍集成这件事。无论你是刚开始做微服务改造,还是已经在生产环境被缓存问题折磨,都能从里面找到可以直接用的东西。
1. 微服务架构里到底需不需要分布式缓存
1.1 加缓存是为了解决什么问题
微服务架构拆分之后,一个明显的副作用是:原本一次本地方法调用,变成了跨服务的远程调用。你查一个订单详情,可能先调用户服务拿用户信息,再调商品服务拿商品数据,最后调订单服务拼装结果。每一个远程调用都有网络开销、序列化开销、连接池等待开销,放大个几倍甚至一个数量级很正常。
数据库这时候就成了最容易被打垮的瓶颈。关系型数据库擅长的是事务和复杂查询,并不擅长抗高并发读。如果同一个热点数据被几千个请求同时访问,数据库的连接池先会被占满,然后整条链路跟着雪崩。分布式缓存的定位就是把这些重复的、高频率的读请求挡在数据库前面,用内存的高吞吐来消化掉大部分访问压力。
我做过一个电商后台的商品详情页接口,原来直接查MySQL,单机大概能扛住每秒一两千的请求,数据库CPU已经飙到快90%。加了Redis缓存之后,同样的机器配置,接口TPS能到两万以上,数据库CPU降到个位数,P99延迟从八十多毫秒降到了五毫秒以内。这就是缓存带来的最直观效果。
1.2 什么时候不要上分布式缓存
但并不是所有场景都适合上缓存。有些团队把缓存当成万能膏药,哪里慢就贴哪里,最后反而拖垮了系统。我见过几个典型的反面案例:
数据强一致性要求极高的时候,比如账户余额、库存扣减,这种场景如果你把数据放进缓存,缓存与数据库的任何一次短暂不一致都可能造成资损。这类数据就应该走数据库事务,配合分布式锁和幂等设计,而不是用缓存来加速。
写多读少的场景也不适合。比如日志上报、埋点数据,写入频繁但基本不会被反复读取。你就算把每条日志都塞进缓存,收益也趋近于零,反而白白占用内存,还要承担缓存和数据库之间的同步成本。
数据量极小且访问量也小的内部管理后台,比如一个只有几百个用户使用的配置中心后台,直接查数据库可能不到一毫秒就返回了,加缓存除了显得技术栈豪华,没有任何实际意义。
所以做集成之前,先问自己三个问题:这个数据是不是高频读?读多写少还是写多读少?业务上能不能接受短暂的不一致?这三个问题回答清楚了,要不要上缓存自然就有答案了。
1.3 缓存选型:为什么Redis成了事实标准
确定了要上缓存之后,接下来就是选型。目前业界分布式缓存方案基本就是Redis的天下,偶尔还有Memcached在存量系统里呆着。Memcached本身并不差,纯内存、性能足够、实现简单,但它的数据结构只有简单的字符串,而且没有持久化能力,集群方案也比较粗糙。一旦服务重启,缓存全部清空,在微服务架构里这个体验非常难受。
Redis能成为事实标准,核心原因是它在几个关键维度上都做得足够均衡:基于内存的读写性能极高,官方数据单实例吞吐可以到十万级QPS;数据结构非常丰富,除了String还有Hash、List、Set、ZSet,很多业务逻辑可以直接在Redis里面完成,比如排行榜、去重、队列;它支持RDB和AOF两种持久化,允许你在追求高性能的同时保留数据恢复能力;再加上哨兵模式和Cluster集群方案,生产环境的可用性比较容易得到保障。
另外Redis的客户端生态非常成熟。Java有Jedis、Lettuce、Redisson,Python有redis-py,Go有go-redis。这些客户端不仅封装了基础读写,连分布式锁、限流器、消息队列这类高阶玩法都集成了。选Redis,后续遇到问题能搜到的方案最多,踩坑成本相对最低。
2. 集成方案的整体设计与关键决策
2.1 本地缓存、分布式缓存与多级缓存的取舍
微服务集成缓存,第一个要做的选择不是引入Redis,而是想清楚缓存要放在哪一层。我见过很多团队一上来就直奔Redis,进程内缓存看都不看一眼,结果把简单的方案搞复杂了。
进程内缓存,也就是像Caffeine、Guava Cache这种跑在应用JVM里的缓存,最大的优势是快,因为连网络请求都省了,直接内存读取,延迟是亚毫秒级的。缺点是每个服务实例各存一份,数据不一致是必然的,而且受限于单机内存容量。分布式缓存,也就是Redis,优点是全局统一、容量可扩展、数据一致性相对好控制,缺点是每一次读写都有一次网络IO开销,延迟至少在一毫秒左右。
多级缓存是很多大厂的选择,本地缓存扛住极端热点,Redis扛住大部分读请求,数据库兜底。但这个方案有一个非常现实的问题:一致性维护的复杂度会成倍上升。本地缓存过期了,Redis里的数据是最新的,但本地缓存还没更新,应用读到的就是旧数据。你需要自己设计本地缓存的刷新机制、版本号校验、消息通知等。我实际体验是,大多数业务系统根本不需要一开始就上多级缓存,把Redis这层做好已经能解决90%的问题,等真出现热点问题再针对性优化也不迟。
2.2 缓存数据的粒度和Key设计
缓存粒度决定了缓存的复用率和一致性维护难度,这个环节很容易被低估。有些开发者把整个接口返回对象塞进一个Key里,比如一个订单详情接口直接把整个DTO序列化存进Redis,这当然简单,但问题很多:数据量大的时候单Key体积膨胀,修改任何一个字段都要重刷整个缓存,缓存命中率很低,还容易产生大Key。
更好的做法是按数据维度拆分缓存粒度。用户信息缓存就只存用户信息,商品基本信息缓存就只存商品基本信息,订单数据单独缓存。如果业务需要聚合数据,由服务层负责组装,而不是靠Redis存一个巨大的聚合对象。这样每个缓存的变更粒度更细,命中率更高,也更好维护。
Key的设计规范也非常重要。我建议统一使用“业务域:实体名:唯一标识[:附加维度]”这种格式,比如auth:token:{userId}、product:detail:{productId}、order:list:{userId}:{page}。好处是肉眼可读、Redis内按前缀统计方便、排查问题时能快速定位是哪个服务的缓存。切忌用没有含义的纯数字或者随机串做Key,线上出了事根本没法查。
2.3 缓存更新策略:哪套方案最不容易出错
缓存更新的套路市面上讲得很多,Cache Aside、Read Through、Write Through、Write Behind,每种都有适用场景。微服务架构里我见到用得最多、也最不容易出错的是Cache Aside模式。
Cache Aside的核心逻辑是:读请求先查缓存,命中就直接返回;没命中就查数据库,然后回填缓存;写请求先更新数据库,成功后删除缓存。这里有一个关键点,很多新手会搞错:一定要先更新数据库,再删除缓存,而不是先删缓存再更新数据库。
我先说为什么不能先删缓存再更新数据库。如果请求A先删了缓存,然后还没更新数据库,这时请求B来了,它查缓存没命中,回去查数据库,查到的还是旧数据,并把旧数据回填到了缓存里。等请求A更新完数据库,缓存里躺着的依然是旧数据,这个不一致可能要等缓存过期才能修复。
反过来,先更新数据库再删缓存,问题也不少,最常见的是更新数据库成功、删除缓存失败,结果缓存里是旧数据。这个问题的兜底方案,我在后面的排障章节会详细讲。总而言之,Cache Aside不是完美的,但它是最容易理解和控制的。还有一点需要强调:如果你的业务对一致性要求真的很高,那就别加缓存,加了缓存就等于接受了“最终一致”。
2.4 网络抖动与超时:必须提前定的三个参数
分布式缓存比本地缓存多了一道网络链路,这也就意味着它多了一类故障:Redis本身没挂,但网络抖动导致客户端连接超时了。很多团队上线前没想清楚这个事,Redis一抖动,所有依赖缓存的接口全部报错,连锁反应比Redis挂掉还严重。
我建议在集成之初就把三个参数定下来。第一个是连接超时,比如Lettuce里的connectTimeout和readTimeout,一般设置成200到500毫秒就够用了,时间太长会拖垮整个请求链路。第二个是重试策略,重试不是越多越好,最多一到两次,而且仅对读操作重试,写操作的重复执行可能造成数据重复。第三个是降级策略,明确缓存不可用的时候服务是返回失败还是直接查数据库。大多数只读场景我建议缓存放通,也就是Redis异常时直接走数据库,保证主流程可用;但前提是数据库自身能扛住,不然降级就变成了雪崩的开始。
3. 实操:从零把Redis集成到微服务
3.1 基于Spring Boot的基础配置
接下来进入代码落地环节。我用Java生态里最常见的Spring Boot 3.x + Spring Cloud场景来演示,这套思路换成Python、Go其实一样,缓存模式是语言无关的。
第一步是引入依赖,在你的pom.xml里加上spring-boot-starter-data-redis和连接池相关的commons-pool2。前者是Spring对Redis客户端的封装,默认基于Lettuce客户端;后者用来管理连接池参数。光有spring-boot-starter-data-redis还不行,不配连接池的话Redis客户端的连接管理会很粗糙。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>接着在application.yml里配置Redis连接信息。我一般会把超时时间和连接池参数都显式写出来,而不是依赖默认值。Lettuce默认的配置在生产环境经常不够用。
spring: data: redis: host: redis-sentinel port: 26379 password: ${REDIS_PASSWORD} timeout: 500ms connect-timeout: 500ms lettuce: pool: max-active: 50 max-idle: 20 min-idle: 5 max-wait: 300ms sentinel: master: mymaster nodes: - sentinel1:26379 - sentinel2:26379连接池里的max-active不是越大越好,这点要特别提醒。如果服务有20个实例,每个实例配200个连接,Redis端就要承受4000个连接,Lettuce是单线程多路复用,大多数情况下几十个连接已经足够了。配太多反而增加Redis端的线程切换开销,还容易把Redis的连接数打满。
3.2 RedisTemplate与自定义序列化
直接用Spring Boot提供的StringRedisTemplate只能存字符串,大部分业务对象需要JSON序列化。Spring Boot默认的RedisTemplate用的是JdkSerializationRedisSerializer,存进去的值是一串二进制乱码,肉眼完全不可读,排查问题的时候想死的心都有。而且JDK序列化后的体积太大,浪费内存。
我建议自定义一个RedisTemplate配置类,Key用StringRedisSerializer,Value用GenericJackson2JsonRedisSerializer。这样Redis里看到的是可读的字符串Key和JSON格式的Value,排查问题方便很多。
@Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); StringRedisSerializer keySerializer = new StringRedisSerializer(); GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer(); template.setKeySerializer(keySerializer); template.setHashKeySerializer(keySerializer); template.setValueSerializer(valueSerializer); template.setHashValueSerializer(valueSerializer); template.afterPropertiesSet(); return template; } }这里有个坑要提醒:GenericJackson2JsonRedisSerializer序列化LocalDateTime这类Java时间类型时会报错,需要在ObjectMapper里注册JavaTimeModule,并禁用WRITE_DATES_AS_TIMESTAMPS。我在项目里遇到过好几次,服务启动没问题,一往Redis里写带时间字段的对象就抛异常。配置好ObjectMapper可以省掉很多后续麻烦。
3.3 用Spring Cache做声明式缓存
Spring Cache提供了一套声明式缓存抽象,通过注解就能给方法加缓存,对业务代码侵入非常小。它支持@Cacheable、@CachePut、@CacheEvict等注解,底层通过RedisCacheManager接入Redis。
使用@Cacheable时,框架会以方法参数作为Key去查缓存,命中就直接返回结果,方法体不会执行;没命中就执行方法,然后把返回值写入缓存。这个特性对读多写少的接口很友好。
@Service public class ProductService { @Cacheable(cacheNames = "product:detail", key = "#productId") public Product getProductDetail(Long productId) { // 查询数据库或调用其他微服务 return productMapper.selectById(productId); } @CacheEvict(cacheNames = "product:detail", key = "#productId") public void updateProduct(Long productId, Product product) { productMapper.updateById(product); } }用这种注解方式,缓存逻辑和业务逻辑完全解耦,代码看着很干净。但要注意两个问题:一是@Cacheable命中缓存时对参数为null的情况不会缓存,如果数据库里查不到就返回null,下一次请求还要再查一次数据库,这会造成缓存穿透。解决办法是设置unless = "#result == null"配合缓存空值,或者用布隆过滤器。二是默认情况下Spring Cache的缓存没有过期时间,你需要通过RedisCacheManager的配置给每个cacheNames设置TTL,不然缓存会无限增长,把Redis内存吃满。
3.4 更灵活的方式:Redisson与分布式锁
Spring Cache适合快速落地,但遇到复杂场景就有点力不从心。比如缓存里要存一个复杂的数据结构,需要对某个Hash字段单独更新;比如缓存重建时需要加分布式锁防止大量请求同时打到数据库;比如要使用延迟队列、发布订阅这些高阶能力。这个时候我更喜欢直接操作Redisson客户端。
Redisson是一个功能非常全的Redis客户端,它对分布式锁、RateLimiter、Semaphore这些并发组件做了很成熟的上层封装。用Redisson实现分布式锁比自己写SETNX+Lua脚本可靠得多,官方把看门狗续期这些细节都处理好了。
假设商品详情缓存没有命中,为了避免所有请求同时打到数据库,可以先尝试加锁,拿到锁的请求负责从数据库加载数据并重建缓存,没拿到锁的请求短暂等待之后重新读缓存。
@Autowired private RedissonClient redissonClient; public Product getProductWithLock(Long productId) { String cacheKey = "product:detail:" + productId; Product product = cacheService.get(cacheKey, Product.class); if (product != null) { return product; } RLock lock = redissonClient.getLock("lock:product:" + productId); try { if (lock.tryLock(1, 5, TimeUnit.SECONDS)) { product = cacheService.get(cacheKey, Product.class); if (product != null) { return product; } product = productMapper.selectById(productId); cacheService.set(cacheKey, product, 10, TimeUnit.MINUTES); return product; } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } return productMapper.selectById(productId); }这种“双重检查+分布式锁”方案在缓存击穿场景下非常经典,也是我自己在项目里用得最多的写法。注意锁的粒度要细到业务对象维度,而不是锁整个服务,否则并发量一上来,锁本身就成了瓶颈。
4. 高可用和数据一致性建设
4.1 主从、哨兵、Cluster:部署模式怎么选
Redis集成到微服务之后,它的可用性直接决定了整条链路的稳定性,这块不能只靠开发人员拍脑袋,得和运维一起定方案。Redis有单机、主从复制、哨兵、Cluster四种常见的部署模式。
单机模式最多用来做本地开发。生产环境哪怕业务量再小,我都建议至少做一主一从的部署,主节点挂了从节点还能顶上。主从复制的局限在于它不提供自动故障转移,主节点宕机时需要人工介入,因此引入哨兵模式是生产环境的最低底线。
哨兵模式在Redis 2.8之后成为标准方案,它解决了主节点故障自动切换的问题。哨兵进程会持续监控所有Redis节点,主节点挂掉后自动把某个从节点提升为主节点。微服务客户端通过连接哨兵节点获取当前主节点地址,RedisTemplate和Redisson都内置了哨兵支持。哨兵模式适合缓存数据量在几十G以内、QPS在十万左右的场景,多数业务系统到这个撑度已经足够了。
当缓存数据量超过单机内存,或者QPS需要扩展到百万级别时,就该上Redis Cluster了。Cluster采用无中心架构,数据按Key的CRC16哈希分散到16384个槽位,分布在多个主节点上。每个主节点再配一个或多个从节点,主节点挂了从节点自动顶替。这带来一个很重要的变化:你没法用MGET跨节点批量获取Key了,也没法在一个事务里操作多个Key了,这对业务代码是有侵入性的。选型时要想清楚,不是越高级越好,而是匹配当前数据规模和增长预期。
4.2 缓存与数据库的双写一致性
缓存与数据库的一致性问题,是所有缓存方案里最磨人的一个。我前面说Cache Aside是“先更新数据库,再删除缓存”,但它有个明显的失败场景:删除缓存那一步如果失败了,比如网络抖动、Redis超时,缓存里的旧数据就会一直存在,直到过期时间到了才会被纠正。
针对删除失败的情况,业界有几个常用应对手段。第一个是消息队列重试,把删除缓存的任务丢进MQ,由专门的消费者执行删除操作,失败了自动重试。这个方案简单可行,但实时性会受到MQ延迟影响。第二个是延时双删,在更新数据库之后,先删一次缓存,隔几百毫秒再删一次,用来处理并发请求在第一次删除后把旧数据写回缓存的窗口期。这个方案在多数场景下够用,但间隔时间设置多少完全靠经验,设置不好还是会漏。第三个是监听数据库的binlog日志,通过Canal之类的工具把MySQL的变更事件同步出去,再由消费者解析事件删除对应缓存。这个方案对业务代码零侵入,实时性也好,但需要额外运维一套Canal组件,重了点。
我实际项目的建议是,如果团队规模不大、技术栈偏简单,直接用“先更新数据库再删缓存+延迟双删+较短TTL兜底”这套组合拳。如果你的系统已经很依赖MQ,再加一条“删除失败投递MQ重试”的兜底链路会更稳。记住一个底层原则:缓存和数据库的一致本来就是最终一致,能做的是把不一致的时间窗口压缩到足够短,而不是追求任何时候都绝对相等。
4.3 穿透、击穿、雪崩的实战防御
这三个问题翻译成人话分别是:缓存和数据库里都没有这条数据,每次请求都白跑一遍数据库;某个Key突然被大量请求同时访问,而这个Key正好在缓存里过期了;大量Key在同一时间集体过期,所有请求同时涌向数据库。它们都会导致数据库压力暴增,表现相似,但成因和防线完全不同。
对付穿透,业界最常用的两个方案是缓存空值和布隆过滤器。缓存空值很简单,数据库查不到数据时在Redis里放一个短TTL的空占位符,后续请求直接命中空值缓存,不再打库。布隆过滤器更高阶一些,它把存在的数据ID放进去,请求来了先判断ID是否存在,不存在就拦截掉。但布隆过滤器有误判率,配置参数需要根据数据量估算,不能随便用默认值。
对付击穿,核心思路就是分布式锁加重建缓存,这就是我在实操章节演示的那段代码。要注意的是锁的粒度要细到被请求的Key,而不是锁整个类,更不是锁整个缓存服务。还有一个思路是“逻辑过期”,不设置物理TTL,而是在Value里塞一个过期时间字段,读取时发现逻辑过期就由后台线程异步重建缓存,好处是请求永远能拿到数据,坏处是实现复杂度较高。
对付雪崩,最简单有效的手段是给过期时间加一个随机偏移量,比如基础TTL是10分钟,每个Key加上0到120秒的随机值。这样原本可能同时失效的Key会错开过期时间。另一个思路是Redis部署层面做主从或者集群,避免Redis自身挂掉导致的雪崩。业务侧还要做限流和熔断,数据库扛不住的时候直接返回兜底数据,比如商品页面的缓存被清了,可以先返回一个基础版本页面,而不是让所有请求都穿透到数据库。
4.4 监控和容量规划
Redis部署上去了,缓存代码也写了,如果没有监控,就等于蒙着眼开车。我一般会从两个方面下手:一是系统自身的监控,二是业务层面的缓存使用监控。
系统监控方面,Redis官方提供的INFO命令已经包含非常丰富的信息,包括内存、连接数、命中率、持久化状态、复制状态等。把这些指标接入Prometheus和Grafana之后,可以配置告警规则。甚至不用自己写prober,直接用redis_exporter就能把绝大多数指标暴露出来。我建议至少监控这几个指标:内存使用率、连接数、命令执行延迟、过期Key数、命中率。命中率低于80%就要排查是不是Key设计有问题,或者大量请求压根没走到缓存层。
容量规划方面,一个常见的错误是等内存满了再想怎么办。Redis默认的maxmemory通常是物理内存总量,一旦内存不够,Redis会按照maxmemory-policy开始淘汰数据。如果策略是noeviction,新写入直接报错,线上马上就有可见故障;如果是allkeys-lru,Redis会随机淘汰一些Key,可能导致热数据被莫名清掉。所以我建议上线前就要定好内存上限和淘汰策略,并通过监控持续观察内存增长曲线,提前规划扩容或者升级到Cluster模式。
5. 踩坑实录与排查技巧
5.1 缓存不一致:最常见也最难查
缓存一致性问题的排查难度,主要体现在它往往是偶发的,而且场景依赖很复杂。我印象最深的一次线上事故是这样的:用户修改了自己的昵称,页面有时候显示新昵称,有时候显示旧昵称,持续了大概十分钟才恢复正常。
当时的代码是典型的“先删缓存,再更新数据库”,而且是先清Redis,再发MQ去更新数据库。如果MQ消费比较慢,在这个时间窗口内,新的读请求发现缓存里没有数据,就去数据库查,查到旧值并回填到Redis。数据库更新完成之后,Redis里躺着的依然是旧数据,两个服务实例之间读到的内容还不一样。
排查的时候是用Redis里的Key和数据库里的数据做比对,发现过期的KeyTTL还很长,才定位到是删除顺序导致的问题。后来我们把所有这类操作统一改成了“先更新数据库,再删缓存”,同时给MQ加了一条延迟兜底任务,每五分钟扫描一次可疑的缓存条目。后来这类问题基本就绝迹了。这个案例想说明的是,缓存一致性问题的排查不能只盯着Redis代码,要把写库、消息队列、缓存刷新整条链路的时序都拉出来看。
5.2 大Key和热Key:上线前就要清理
大Key和热Key是一对兄弟坑。大Key是指Redis里某个Key的Value特别大,比如一个Hash里塞了几万条数据,一个String值为几兆。它在读取时会导致单次命令执行时间非常长,拖慢整个Redis实例;网络传输也会占用大量带宽;如果数据分布到集群里的某个分片,还会造成分片间内存和流量不均匀。
定位大Key不需要什么高级工具,Redis自带redis-cli --bigkeys这个命令就能扫描出占用空间最大的Key。排查出来之后,处理思路一般是拆分:一个Hash太大就拆成多个Hash,或者换用更轻量的数据结构;一个String太大就看能不能拆成多个Key;已经不是核心数据就清理掉。
热Key是另一个让人头疼的问题,某个明星突然上了热搜,他的微博被几百万用户同一秒点击,对应的缓存Key就被打爆了。热Key会导致Redis单分片CPU飙升、请求超时,常规的加副本、扩容都很难解决。我的经验是,针对这类极热数据,可以在客户端加一层本地缓存,把热Key缓存到应用实例内部,有效分散Redis单分片的压力。当然这要考虑本地缓存与Redis之前的数据一致性问题,一般用短TTL来控制,接受短暂的不一致。
5.3 慢查询与连接耗尽
Redis的慢查询和数据库的慢查询原理很相似,当你发现某个接口的P99延迟变高时,可以先用SLOWLOG GET命令查看Redis侧的命令执行耗时。常见原因包括:使用KEYS *做全Key遍历,一次性操作数百万个Key;在使用大Key进行HGETALL、SMEMBERS这类命令时,返回的数据量过大;批量操作时一次携带几万个参数执行MGET或LPUSH。
连接耗尽问题也经常出现。Lettuce虽然是基于Netty多路复用的,但在某些高并发场景下连接池依然会被打满。表现为客户端抛出RedisConnectionFailureException或者Cannot get Jedis connection(如果你用的是Jedis客户端)。排查思路是看Redis端的INFO clients里的connected_clients,再对照应用端的连接池配置,确认max-active设置是否合理。我曾经遇到过一次因为连接池max-wait设置太长,导致线程大量堆积在等待连接的场景,接口吞吐量直线下降,最后把max-wait缩短并加大max-active才恢复。
5.4 日常排障命令速查表
我整理了一份日常排查分布式缓存问题时最常使用的命令和思路,给运维和开发同学做个速查。
| 场景 | 排查手段 | 关键指标 |
|---|---|---|
| 内存快满了 | redis-cli INFO memory | used_memory、maxmemory、evicted_keys |
| 命中率下降 | redis-cli INFO stats | keyspace_hits、keyspace_misses |
| 大Key定位 | redis-cli --bigkeys | 最大Key的key名和value大小 |
| 慢查询定位 | redis-cli SLOWLOG GET 50 | 执行耗时、命令参数 |
| 热Key发现 | 客户端采集热点统计或RedisInsight分析 | 访问频次、分片CPU |
| Key过期策略确认 | redis-cli CONFIG GET maxmemory-policy | 淘汰策略名称 |
| 集群槽位分布 | redis-cli -c CLUSTER SLOTS | 主从映射关系 |
| 哨兵切换状态 | redis-cli -p 26379 SENTINEL master mymaster | master地址、slaves数量 |
这些命令不一定每天用到,但线上出问题时,能快速定位方向非常重要。我习惯把常用的几条命令写成一个运维脚本放到跳板机上,省得每次出问题都现场翻文档。
<opinion_anchor>分布式缓存与微服务架构的集成</opinion_anchor>