选 Redis 客户端,本质上是在"命令可预测性"和"连接模型"之间做权衡。Java 生态里最早普及的 Jedis 是同步阻塞的,一个实例同时只能服务一个线程,生产必须靠连接池;后来 Lettuce 基于 Netty,默认线程安全、连接可复用,成了 Spring Boot 2 之后的默认选择。但换了客户端不等于解决了分布式锁——Redlock 试图在多实例上保证互斥,却因时钟、GC、重启等现实问题引来争议。本文把三篇笔记串成一条线:先看清两个客户端的差异与各自坑点,再谈分布式锁到底在保什么。


一、Jedis:同步阻塞,生产必须走连接池

Jedis 是 Java 里最早普及的 Redis 客户端,核心特点是调用是同步阻塞的:一个 Jedis 实例同时只能被一个线程使用。生产环境必须走连接池 JedisPool,用完归还,否则会泄连接或把带状态的连接交给下一个请求。它对 Redis 命令的包装非常直白,调试时能直接对应 redis-cli。集群模式用 JedisCluster,但 slot 迁移、MOVED/ASK 重定向和连接数爆炸都要自己关注。新项目更常选 LettuceRedissonJedis 仍适合老代码维护和对命令序列要求严的脚本场景。

三个容易踩的坑

  • new Jedis() 直连上线:一个实例只能单线程用,并发下互相踩。统一走 JedisPooltry-with-resourcesfinallyreturnResource,禁止跨线程复用同一实例。
  • 混淆 pipeline 与 transaction:管道只减少 RTT,不保证原子性;多键一致性要用 MULTI/EXEC 或 Lua。
  • 集群下跨 slot 做事务:会失败。同一业务对象用 hash tag 锁定 slot;超时、最大等待和测试时间要配到与业务 SLA 相符。

什么时候用:当你维护早期 Spring Data Redis 项目、或需要一对一命令映射以便复现线上问题时,Jedis 仍然直接。高并发、响应式栈和要跨线程复用连接的新服务,应优先考虑 Lettuce。分布式锁、延迟队列这类高层抽象不要用 Jedis 手拼,用 Redisson 更省心。


二、Lettuce:基于 Netty 的线程安全客户端

Lettuce 是基于 Netty 的 Redis 客户端,默认线程安全且可以连接复用。它提供同步 API 与基于 Reactor 的异步 API,Spring Boot 2 以后默认就是它。与 Jedis 相比,不用每个线程借还实例,但要理解共享连接上的命令竖线与 pub/sub 占用。集群、传送、SSL 和超时都在 ClientOptionsClusterClientOptions 里配。异步链路下要避免在阻塞方法里调 reactive API,否则线程模型会乱。

三个容易踩的坑

  • 分不清 StatefulRedisConnection 与连接池:单连接多线程复用时命令会交织;事务、阻塞脚本要独占连接或用池。
  • 忽略断线重连拓扑:默认 auto-reconnect 在集群折叠时可能打到旧节点,要配 topology refresh,明确缓存、超时与重连策略。
  • 与 Spring Cache、Redisson 混用同一连接对象Lettuce 负责命令通道,分布式锁仍建议交给专门库,不要混用同一连接。

什么时候用:新建 Spring Boot 服务、需要跨线程共享连接或要 WebFlux 异步访问 Redis 时,优先选 Lettuce。极致追求命令级可预测性、或老项目已经把 Jedis 连接池调稳的,不必为了换库而换。若需要红锁、延迟队列、布隆过滤器,Lettuce 只当底层通道,业务语义用 Redisson


三、Redlock 分布式锁及其争议:看清它到底在保什么

Redis Redlock 是在多个独立 Redis 实例上多数派加锁的算法:向半数以上节点用同一随机值 SET NX PX,全部成功才算拿到锁。它试图在主从复制延迟下仍保持互斥。Martin Kleppmann 指出时钟回拨、长时间 GC 和实例重启会让锁失效而业务还在临界区。工程上更稳妥的是单 Redis + fencing token,或把锁下沉到数据库与协调服务。理解 Redlock 的价值是看清分布式锁到底在保证什么。

三个容易踩的坑

  • 释放锁用 DEL:会删掉别人刚拿到的锁。锁必须带随机值,释放时用 Lua 比对 value,禁止 DEL 错别人的锁。
  • 业务超过 TTL 还在写:拿到锁后业务必须在 TTL 内完成,或用续期。GC 停顿超过 TTL 就要主动放弃,不能继续写。
  • 把单实例锁当跨机房强一致:单实例 Redis 锁适合任务去重,不要把它吹成跨机房强一致。需要真正互斥时加 fencing token:每次加锁递增版本,存储层拒绝旧版本写入。

什么时候用:多个 Redis 独立部署、只能用 Redis 做协调、且能接受极小窗口的重复进入时,可以考虑 Redlock。账务、库存扣减这类不能错的场景,优先用数据库约束或 ZooKeeper/etcd。单实例 Redis 锁适合任务去重,不要把它吹成跨机房强一致。


附:Redis 客户端与锁方案对比表

场景 用什么 不要用什么
老项目维护、需一对一命令映射复现问题 Jedis + JedisPool new Jedis() 直连、跨线程复用同一实例
高并发、跨线程复用连接、WebFlux 异步 Lettuce(默认线程安全) 为换库而换、把 Jedis 池调稳的硬迁
红锁 / 延迟队列 / 布隆过滤器等高层语义 Lettuce 当通道 + Redisson 做业务 Jedis/Lettuce 手拼分布式锁
多实例强一致加锁 Redlock(接受极小重复窗口) 单实例锁吹成跨机房强一致
账务 / 库存等不能错的互斥 数据库约束 / ZooKeeper / etcd Redlock 或单实例锁硬扛
任务去重、弱一致保护 单实例 Redis 锁(带随机值 + Lua 释放) DEL 释放、业务超 TTL 继续写