Java操作Redis:客户端选型、序列化与分布式锁实战指南

发布时间:2026/9/29 8:39:28

Java操作Redis:客户端选型、序列化与分布式锁实战指南 最近在折腾 Redis 相关的项目发现不少同行在 Java 操作 Redis 客户端这块还在能用就行的阶段。Jedis、Lettuce、Redisson 到底怎么选连接池参数调多少合适序列化器的坑踩了也不知道怎么排查这些都是实际开发里绕不开的问题。这篇文章我会从客户端选型讲起到环境搭建、核心 API 实操、序列化方案再聊到分布式锁这类高级玩法最后分享一些排查手段和效率工具。适合刚接触 Java 操作 Redis 的入门读者也适合用了一段时间但想系统梳理的开发者。1. 客户端三选一Jedis、Lettuce、Redisson到底该用哪个很多初学者第一个问题就是Java 操作 Redis 用什么客户端去网上搜答案五花八门有人推荐 Jedis有人推荐 Lettuce还有人直接上 Redisson。我得先说清楚一件事这三个东西虽然都是客户端但定位和适用场景差别很大选错后续会很被动。1.1 Jedis直来直去的经典派Jedis 是老牌客户端API 设计跟 Redis 命令几乎一一对应set就是sethgetAll就是hgetAll几乎没有学习成本。以前 Jedis 在并发场景下有个明显短板它本身不是线程安全的每个线程都要拿自己的连接实例所以必须依赖连接池。你在用 Jedis 的时候心里得时刻想着用完要把连接还给池子否则线程一多很容易把连接耗尽。不过话说回来连接池模式本身很成熟只要配置合理吞吐量完全够用。Jedis 的另一个特点是用起来直接你能清楚地看到我在用连接这个过程。对新手来说这其实是好事情因为 Redis 连接的本质一目了然不会像某些封装得太狠的库那样出了问题根本不知道连接是怎么管理的。1.2 Lettuce异步原生的现代化选手Lettuce 基于 Netty 实现核心卖点是一个连接可以供多个线程共享底层靠异步事件循环。这意味着在并发量上来的时候你不需要像 Jedis 那样创建几十上百个连接一个连接就能扛住很可观的 QPS。从 Spring Boot 1.x 到 2.x 的演进里默认的 Redis 客户端也从 Jedis 切换到了 Lettuce这其实代表了业界对连接管理理念的变化。Lettuce 默认也不开连接池因为它自己就能很好地复用连接。但要注意的是如果服务端 Redis 的maxclients配置很小或者你有大量阻塞命令在跑单一连接反而可能成为瓶颈。1.3 Redisson为分布式场景而生的老大哥Redisson 严格来说不只是一个客户端它更像是一个基于 Redis 的分布式框架。分布式锁、延迟队列、信号量、布隆过滤器这些复杂数据结构它都直接提供了现成的 API。举个例子你想实现一个分布式锁用 Jedis 的话得自己写SET lock uuid NX EX 30这种命令还要配合 Lua 脚本处理释放逻辑稍有不慎就容易出并发问题。用 Redisson 的话一行redissonClient.getLock(orderLock).lock()就搞定了锁的自动续期机制它内部已经封装好了。1.4 三个客户端的适用场景对比我在日常项目里的选型经验是这样的客户端线程安全连接池需求学习成本适合场景Jedis否必须低简单业务、新手学习、需要精确控制连接时Lettuce是可选中Spring Boot 默认项目、高并发读多写少场景Redisson是可选中高分布式锁、分布式集合、需要高级数据结构的场景我个人不太推荐在复杂项目里裸用 Jedis因为连接管理太费精力。Spring Boot 项目直接用默认的 Lettuce 就好如果需要分布式锁再引入 Redisson。用 Redisson 不冲突它也可以直接连 Redis 实例。2. 环境准备与依赖配置不借助 Spring Boot 把客户端跑起来很多教程上来就讲怎么在 Spring Boot 里操作 Redis搞得大家以为离开 Spring Boot 就没法用 Redis 了。其实 Java 操作 Redis 的根本很简单一个 Java 工程、一个 Redis 服务端、一个客户端依赖就够了。2.1 基础设施安装与验证我本地的实验环境是 Windows 11装的是 Redis 7.x 的 Windows 移植版。装完以后一定要把 Redis 服务跑起来然后打开命令行验证一下redis-cli -h 127.0.0.1 -p 6379 -a yourpassword注意Redis 7.x 以后也支持ACL权限控制了如果你在配置文件里启用了 ACL用-a传密码的方式可能不生效得先认证默认用户。但本地测试一般没那么复杂有个密码就够了。如果你的 Redis 在远程服务器上记得检查防火墙是否放行了 6379 端口还有bind配置是否允许外部访问。我见过太多人配置文件里保持默认的bind 127.0.0.1然后远程连不上折腾半天才发现是 bind 的问题。2.2 依赖引入Maven 与 Gradle 两种方式以 Maven 为例如果使用 Jedis在pom.xml中加入dependency groupIdredis.clients/groupId artifactIdjedis/artifactId version5.1.5/version /dependency如果是 Lettucedependency groupIdio.lettuce/groupId artifactIdlettuce-core/artifactId version6.4.2.RELEASE/version /dependency如果要用 Redissondependency groupIdorg.redisson/groupId artifactIdredisson/artifactId version3.42.0/version /dependencyGradle 对着翻译成implementation redis.clients:jedis:5.1.5这种格式就行没什么好说的。版本这里我要多提醒一句不要随手拿最新版最好看一眼该版本对应的最低 Java 编译版本。比如 Jedis 5.x 要求 Java 8 以上如果你项目还在 Java 7那只能用 Jedis 2.x。另外Lettuce 的版本命名里有RELEASE后缀别去掉否则可能拉不下来。2.3 用 Jedis 写出第一个连接我把三种客户端的连接方式都写一遍先看 JedisJedisPoolConfig config new JedisPoolConfig(); // 最多 50 个连接 config.setMaxTotal(50); // 最多 10 个空闲连接 config.setMaxIdle(10); // 池子中最小空闲连接数 config.setMinIdle(1); // 最多等待 5 秒 config.setMaxWaitMillis(5000); // 拿到连接后要不要验证可用 config.setTestOnBorrow(true); // 还回去时验证可用 config.setTestOnReturn(true); JedisPool pool new JedisPool(config, 127.0.0.1, 6379, 2000, yourpassword); try (Jedis jedis pool.getResource()) { jedis.set(hello, world); System.out.println(jedis.get(hello)); } pool.close();注意到try-with-resources了吗因为 Jedis 实例内部持有一个 Socket 连接用完不还池子最终会被耗尽。用try-with-resources可以保证连接一定会归还这是使用 Jedis 的核心纪律。2.4 Lettuce 和 Redisson 的连接方式对比Lettuce 的简单版不需要连接池RedisClient client RedisClient.create( RedisURI.create(redis://:yourpassword127.0.0.1:6379) ); StatefulConnectionString, String conn client.connect(); RedisCommandsString, String commands conn.sync(); commands.set(hello, world); System.out.println(commands.get(hello)); conn.close(); client.shutdown();Redisson 更简洁它用配置对象构建客户端Config config new Config(); config.useSingleServer().setAddress(redis://127.0.0.1:6379).setPassword(yourpassword); RedissonClient redisson Redisson.create(config); RBucketString bucket redisson.getBucket(hello); bucket.set(world); System.out.println(bucket.get()); redisson.shutdown();三种客户端各自用起来都不复杂真正复杂的是理解每种连接模式的原理。Lettuce 为什么不需要池子因为它内部的 Netty 连接是异步复用的。Redisson 为什么操作对象化因为它把 Redis 的数据结构都映射成了 Java 对象的视角。这些理念差异决定了你在对应生态里写代码的方式。3. 核心 API 实操从 String 到 Hash 逐个过一遍连接建立起来之后就是正式操作了。Redis 有五种基本数据类型每个都有对应的 Java API。我按 Jedis 的写法来展示思路清晰Lettuce 和 Redisson 的 API 差异稍后对比。3.1 String 类型不只是 set/getString 的使用频率最高但很多人只知道set和get其实里面可以做的优化操作很多。try (Jedis jedis pool.getResource()) { // 基础 set/get jedis.set(user:1:name, 张三); String name jedis.get(user:1:name); // 不存在才设置常用于分布式锁占位 Long setnx jedis.setnx(user:1:lock, 1); // 设置过期时间单位秒 jedis.setex(sms:code:13712345678, 60, 482913); // 先取值再重新赋值 String oldValue jedis.getSet(counter, 100); // 数值自增 jedis.incr(page_view); jedis.incrBy(page_view, 10); }incr这种原子操作在做计数器时很有用但你得知道它的底层类型是字符串Redis 内部把它当成整数来解析所以你不能往这个 key 塞非数字的字符串否则会报错。关于setex有个隐蔽的注意点如果你后面还要给这个 key 再次调set过期时间会被覆盖掉因为set是直接赋值不会保留之前的 TTL。Redis 的过期策略是整键过期不是键值更新后重新计时。一旦set了新的 value原来设的过期时间就消失了这是高并发场景下常踩的坑。3.2 List 类型队列还是栈取决于你 push 和 pop 的方向List 在 Java 客户端里对应lpush、rpush、lpop、rpop这一套命令。它的典型用法是实现简单消息队列。try (Jedis jedis pool.getResource()) { // 从右边入队从左边出队 FIFO先进先出 jedis.rpush(task:queue, task-1); jedis.rpush(task:queue, task-2); String task jedis.lpop(task:queue); // 查看列表长度 Long length jedis.llen(task:queue); // 取元素但不弹出 ListString range jedis.lrange(task:queue, 0, -1); }如果你用lpush和lpop那这个 List 就是一个栈LIFO先放进去的后取出来。队列模式建议消费端用brpop而非lpop因为brpop是阻塞式的队列空的时候会一直阻塞等待避免在空队列上空转消耗 CPU。要注意lrange key 0 -1会把整个 List 全取出来如果列表特别大这种操作不仅慢还会阻塞 Redis 主线程。真要全量遍历应该用lscan这类命令分批处理或者考虑用其他数据结构替代。3.3 Hash 类型Java 的 Map 放进 RedisHash 对应 Java 里的MapString, String在存对象属性时非常实用。拿用户信息举例try (Jedis jedis pool.getResource()) { jedis.hset(user:1, name, 张三); jedis.hset(user:1, age, 30); jedis.hset(user:1, city, 上海); // 一次性设置多个字段 MapString, String user new HashMap(); user.put(email, zhangsanexample.com); user.put(phone, 13712345678); jedis.hset(user:1, user); // 取单个字段 String name jedis.hget(user:1, name); // 取所有字段 MapString, String all jedis.hgetAll(user:1); // 获取所有字段名 SetString fields jedis.hkeys(user:1); // 字段不存在时才写入 Long hsetnx jedis.hsetnx(user:1, nickname, 阿三); }Hash 比 String 在存储对象上有个明显优势你可以只修改某一个字段不需要把整个 JSON 都拿出来重新序列化再放回去。但要注意Hash 的底层编码是ziplist或hashtable当字段数量多或者字段值大的时候会从ziplist转成hashtable性能特点会变化。这个一般不用你操心理但如果你追求极致性能可以去了解一下这个转换机制。3.4 Set 和 ZSet去重、交集、排行榜Set 用于去重和集合运算ZSet 多了一个 score 字段用于排序所以排行榜功能天生就是为 ZSet 设计的。try (Jedis jedis pool.getResource()) { // Set 操作 jedis.sadd(tag:java, article:1); jedis.sadd(tag:java, article:2); jedis.sadd(tag:redis, article:2); // 交集 SetString inter jedis.sinter(tag:java, tag:redis); // 并集 SetString union jedis.sunion(tag:java, tag:redis); // 差集 SetString diff jedis.sdiff(tag:java, tag:redis); // ZSet 排行榜 jedis.zadd(ranking:hot, 10, product:1001); jedis.zadd(ranking:hot, 20, product:1002); jedis.zadd(ranking:hot, 15, product:1003); // 按分数从高到低取前 3 名 SetString top3 jedis.zrevrange(ranking:hot, 0, 2); // 给某个成员加分 jedis.zincrby(ranking:hot, 5, product:1003); }ZSet 的zadd可以同时添加多个成员zrevrange是按分数倒序zrange是按分数正序。注意如需获取分数的值要学会用zrevrangeWithScores或者用zscore查询单个成员的分数。Redis 的 6.0 以后新增了 Bitmap、HyperLogLog、GEO 等更高级的数据结构Java 客户端也都支持。比如地理位置相关的应用可以直接用geoadd、geodist这些比自己在内存里计算经纬度距离靠谱得多。3.5 Pipeline 与事务减少 RTT 的利器这是一个很多初学者不知道的慢查询优化点。假设你要插入 1000 个订单号到 Redis逐个执行set命令意味着要发起 1000 次网络请求每次都有一次 RTT。用 Pipeline 可以一次性发送给服务端try (Jedis jedis pool.getResource()) { Pipeline pipeline jedis.pipelined(); for (int i 0; i 1000; i) { pipeline.set(order:202501: i, String.valueOf(i)); } pipeline.sync(); }Pipeline 的优势在批量操作时极为明显性能差距可以达到一个数量级。但你要学会区分 Pipeline 和事务Pipeline 只是减少网络往返并不保证多条命令的原子性如果你需要一个原子性的批量操作应该用multi和exec或者直接使用 Lua 脚本。Redis 的eval命令天然是原子的这也是分布式锁和复杂操作的底层基石。try (Jedis jedis pool.getResource()) { Transaction tx jedis.multi(); tx.set(account:1001, 100); tx.decrBy(account:1001, 30); tx.incrBy(account:1002, 30); // 原子性执行 ListObject results tx.exec(); }注意事务里的decrBy如果让余额变成负数Redis 不会报错它只保证执行不保证业务合法性。真正要校验业务逻辑还得配合 Lua 脚本或乐观锁方案。4. 序列化是最大的坑JDK 序列化和 JSON 序列化的取舍很多人用 Redis 操作字符串很顺手一存对象就开始各种报错RedisDesktopManager 里看到一堆乱码。这些乱码十有八九是序列化方案的问题。4.1 Redis 里只能存字节Redis 本质上只认字节流。你存什么进去它存的就是字节你取什么出来它返回的还是字节。所谓对象直接存入其实是 Java 客户端帮你做了一层序列化。如果你用的是 Spring Boot 的RedisTemplate默认的序列化器是JdkSerializationRedisSerializer它会把 Java 对象序列化成带类型信息的二进制格式。结果就是你在终端工具里看这个 key看到的是一串\xAC\xED\x00\x05t\x00...这种乱码。这个坑有一个非常实际的影响跨语言读取的时候。如果你的数据要被 Python、Go 或 Node.js 的应用读取JDK 序列化后的数据是几乎无法被其他语言解析的。更麻烦的是如果你的 Java 类结构升级了比如字段改名或删除反序列化可能直接报InvalidClassException或者类型不兼容的错误。4.2 JSON 序列化跨语言、可读性强的选择我个人的建议是除非有特殊要求否则不要用 JDK 默认序列化。业界更常用的方案是GenericJackson2JsonRedisSerializer它把对象序列化成 JSON 字符串既有可读性也能做跨语言解析。以 Spring Boot 为例Configuration public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); // 使用 GenericJackson2JsonRedisSerializer 替换默认序列化 GenericJackson2JsonRedisSerializer jsonRedisSerializer new GenericJackson2JsonRedisSerializer(); StringRedisSerializer stringRedisSerializer new StringRedisSerializer(); // key 用 String 序列化方便肉眼识别 template.setKeySerializer(stringRedisSerializer); template.setHashKeySerializer(stringRedisSerializer); // value 用 JSON 序列化 template.setValueSerializer(jsonRedisSerializer); template.setHashValueSerializer(jsonRedisSerializer); template.afterPropertiesSet(); return template; } }这里有个细节key 为什么要用StringRedisSerializer因为如果你 key 也用 JSON 序列化那么同一个字符串 key 存进去会带一层引号包装你在运维工具里看到的 key 名会变成\user:1\排查问题极其痛苦。业界普遍做法是key 用 Stringvalue 用 JSON。4.3 反序列化时的类型信息JSON 序列化有一个逃不开的问题反序列化时需要知道目标类型。比如你存了一个User对象取出来的时候 JVM 怎么知道它应该还原成User而非MapGenericJackson2JsonRedisSerializer会在 JSON 里写入一个额外的类型字段默认是class。这样反序列化时它会照着这个字段来还原类型。但这个方案也有隐患如果你在前后端接口上使用了动态代理类、内部类或 Lambda 相关类型可能序列化时会带上奇怪的类名反序列化就会失败。如果你确定你的 value 类型是固定的比如某类业务里就是存Order对象我更推荐用针对性更强的Jackson2JsonRedisSerializer把ObjectMapper配好并且显式指定目标类型。这样能减少class带来的安全隐患和冗余字段。当下安全控制比较严格的环境里反序列化白名单和class滥用是安全测试的重点关注项这点要提前考虑好。4.4 字符串与对象不要混用还有一种很常见的坑一个 key 存的是 String另一个 key 存的是序列化后的对象而你在代码里用了同一个RedisTemplate去读。因为你 value 序列化器统一了可能 String 类型的 value 在序列化以后也变成带引号的 JSON 字符串取出来的时候多了一对引号或者类型转换直接抛出异常。我的经验是String 类型的缓存尽量用StringRedisTemplate对象类型的缓存用配置好的RedisTemplate两者区分开不要混在一个视角里操作。这样不同的 key 使用不同的序列化器互不干扰。如果真要排查序列化问题用Redis Desktop Manager这类可视化工具直接看原始字节会更直观。看到\xAC\xED开头的基本可以确定是 JDK 序列化什么时候存进去的、用的哪个模板是什么能立刻有个方向。5. 高级玩法与典型坑从分布式锁到防止缓存穿透连接操作熟练了序列化搞明白了就到了上生产环境的时候。生产环境里最常遇到的问题就是并发场景下 Redis 的数据一致性问题以及各种缓存异常。5.1 分布式锁的正确写法分布式锁是 Java 操作 Redis 的高频面试题也是生产环境的高频需求。很多人的写法有问题// 有问题设置值和过期时间不是原子的 if (jedis.setnx(lock:order:1001, 1) 1) { jedis.expire(lock:order:1001, 10); // 执行业务逻辑 // ... jedis.del(lock:order:1001); }这个写法有什么问题如果setnx成功之后在expire之前进程崩溃了锁就永远没有过期时间会一直占用。解决方式是用一个命令完成设置值和过期时间// 正确SET key value NX EX seconds 是原子的 String result jedis.set(lock:order:1001, requestId, NX, EX, 10); if (OK.equals(result)) { try { // 处理业务 } finally { // 释放锁时要校验持有者 String lockScript if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end; jedis.eval(lockScript, Collections.singletonList(lock:order:1001), Collections.singletonList(requestId)); } }这个requestId可以是 UUID它的作用是确保只有锁的持有者才能释放锁。如果不用requestId判断可能会出现线程 A 拿到锁执行时间超过过期时间锁自动释放了线程 B 拿到锁然后 A 执行完了调del把 B 的锁给删了。这就是经典的删锁删到自己头上问题。用 Lua 脚本保证判断删除是原子的是分布式锁的标准实践。如果你直接用 Redisson它会自动通过一个看门狗机制给锁续期解决业务没执行完锁就被过期的问题省心不少。5.2 缓存穿透、缓存击穿、缓存雪崩的应对这三个概念面试天天问但真正落地的方案值得展开说说。缓存穿透查询一个不存在的数据Redis 查不到数据库也没有每次请求都直接打 DB。解决方案是布隆过滤器拦截或者把空值也缓存起来。空值缓存要注意设置较短的过期时间防止大量无效 key 堆积占内存。缓存击穿某个热点 key 突然过期高并发同时去打 DB。解决方案是互斥锁重建缓存即只让一个线程去查 DB 然后写缓存其他线程等待或者返回旧值。用 Redis 分布式锁就能实现。缓存雪崩大量 key 在同一时间过期或者 Redis 实例挂了导致 DB 压力瞬间飙升。解决方案是过期时间加随机值避免同一时间集体过期。Redis 本身可以配置持久化和高可用架构来对抗挂掉的情况这是运维层面的事但是 Java 开发也需要在代码层面做熔断和降级。5.3 Java 客户端中的连接池参数调优很多人在生产环境直接用默认连接池参数这是有隐患的。以 Lettuce 为例默认是不开启连接池的如果你的代码里大量使用同步命令一定要确认你用的是共享连接还是每条命令一个连接。对于 Jedis 连接池的调优我一般建议参考这几个参数参数推荐值说明maxTotal50~200根据业务 QPS 和 Redis 实例性能调整maxIdle10~20保留的常驻连接数量minIdle1~5低峰期最小连接数减少冷启动maxWaitMillis3000~5000获取连接的超时时间超过则抛异常testOnBorrowtrue借出前校验连接可用性代价是每次校验多一次 PINGtestWhileIdletrue定期校验空闲连接避免 Redis 服务端主动断开后拿到死连接一个很重要的判断如果你在日志里看到JedisConnectionException或Could not get a resource from the pool最可能的原因是连接池满了或者 Redis 服务端把空闲连接断掉了。前者调大maxTotal和maxWaitMillis后者靠testWhileIdle加上合理的超时设置。5.4 Redis 操作的时间复杂度意识Java 操作 Redis 的 API 虽然简单但你要有命令复杂度的意识。比如keys *这个命令线上环境千万别用它会全量扫描所有 key在 key 数量大的时候阻塞 Redis 主线程。Java 客户端对应的 API 是jedis.keys(*)很多初学者喜欢拿这个排查问题。正确的方案是用SCAN命令游标式遍历Jedis 里对应的是jedis.scan相关方法。同样的道理smembers返回全量 Set 成员对大集合也是 O(n)要小心使用。这些经验在常规教程里不会提但生产事故往往就是这种不起眼的命令触发的。Redis 单线程模型决定了它处理每个命令都是排队执行的。一旦碰到keys *或者大集合的全量遍历整个 Redis 实例的响应时间都会被拖累。所以作为 Java 开发者你在封装 Redis 工具类时就应该把这种危险命令挡在门外。6. 排查与运维视角可视化客户端、慢查询和常见异常盘点写代码只是前半段上线之后排查问题才是真正磨练人的地方。这里分享一些我把 Java 客户端跑在生产环境时积累的排查手段。6.1 用 Redis Desktop Manager 看数据是否正常Redis Desktop Manager 是最常用的可视化客户端之一但是我得提醒你它只是一个可视化工具不是生产环境排障的万能药。它的核心价值是快速查看 key 是否存在、TTL 还剩多少、value 是什么尤其是验证序列化器是否得当。打开 RDM你如果发现一堆 key 以\xAC\xED\x00\x05开头大概率是JdkSerializationRedisSerializer干的如果你发现 key 名带了引号大概率是 key 用 JSON 序列化导致的。生产环境建议把 key 命名规范定为业务名:模块名:ID层级清晰不仅在 RDM 里好看也方便后续的运维统计。现在的 Navicat 也支持连接 Redis还有 Another Redis Desktop Manager 这类开源选择。工具你可以随喜好选但记住一个原则工具只能帮助你观察不能替代你理解序列化方案和连接池机制。6.2 利用 Redis 慢查询日志定位问题命令Redis 本身提供了慢查询日志功能。你可以把超过一定执行时间的命令记录下来Java 客户端侧可以通过配置参数来设置阈值。在 Redis 配置文件里搜slowlog-log-slower-than默认是 10000 微秒10ms生产环境建议设到 1000 微秒1ms甚至更低。排查的时候流程一般是用SLOWLOG GET或可视化工具看慢命令列表找出耗时最长的那一批命令结合 Java 代码重新审视是不是用了keys *是不是有hgetAll大 Hash是不是用lrange拉了超大 List在客户端侧做预防禁止危险命令、设置命令超时时间、用 Pipeline 批量处理Java 客户端里的命令超时时间也值得检查。Jedis 默认的soTimeout是 2000 毫秒如果你的网络环境较慢或者 Redis 实例在做持久化时阶段性卡顿命令很容易超时。这时你需要根据业务对延迟的容忍度来合理调整超时时间。设太短会引发大量异常设太长会掩盖整体性能问题。6.3 Java 端的 Redis 异常分类我在实操中把常见的异常归了类这样排查起来方向更明确异常类型典型报错最常见原因连接类异常connect timed out网络不通、防火墙拦截、bind 配置限制认证类异常NOAUTH Authentication required密码没传或传错连接池耗尽Could not get a resource from the pool连接池太小、命令阻塞太久、连接泄漏序列化异常SerializationException/ClassCastException用了 JDK 序列化后又改了类结构或 JSON 类型信息丢失命令执行异常ERR wrong number of argumentsAPI 用错参数个数不对看到异常不要急着问同事或上网搜先按这个分类自查能解决大多数问题。尤其是连接池耗尽这个坑我见过太多团队排查半天网络最后发现代码里某个finally少写了jedis.close()。6.4 在 Java 侧做 Redis 调用的统一封装最后分享一个工程上的建议不要在每个业务代码里直接塞 Jedis 连接管理逻辑。写一个统一 Redis 服务类把连接获取、序列化、异常转换都封装好。业务代码只需要调用redisService.getObject(key, User.class)这种清晰的方法。这样做的收益是将来换客户端从 Jedis 换到 Lettuce或者引入 Redisson时只需要改服务类内部实现业务层代码完全不用动排查问题时也有统一日志入口。这个习惯我在多个项目里验证过确实能省掉不少维护成本。另外如果你的调用方拿不到连接或者连接池满了统一的封装里应该做好快速失败和降级策略。不是所有 Redis 查询都必须成功要根据业务判断这个缓存挂了是直接报错还是返回一个降级值降级到数据库查询这些决策提前定好比在故障现场做决定靠谱得多。
延伸阅读

更多相关文章

2026/9/29 8:34:27

【Codex教育管理系统】接入音频转录服务维护转写参数与音频文件入口

音频转录服务在教育管理系统中的价值,在于围绕 音频转录服务 的核心字段、接口动作和页面状态维护业务数据。模块需要和现有接口、权限、页面状态保持一致,不能只写成普通后台表格。 本文基于 系统功能/三方服务_音频转录服务 对应源码,把业务目标拆成模型字段、接口规则、页…

2026/9/29 9:44:33

SpringBoot+uni-app驾考系统全链路源码解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/29 9:44:33

Servlet 在 JSP 里到底干什么

JSP 负责显示页面(给人看),Servlet 负责干活(后台处理) 核心作用(4 件事) 接收浏览器发来的请求、获取参数(表单提交、url 传参)做业务处理:判断逻辑、调用…

2026/9/29 9:44:33

STM32开发参考方案怎么找?国内资源平台与高频例程拆解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/29 9:44:33

反射电压详解:反激变压器设计中的核心权衡与实例计算

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/29 9:44:33

Allegro中DXF导入与元件精确放置全流程详解

做PCB设计的兄弟应该都有体会,结构工程师甩过来一个DXF,让你照着画板框、放定位孔、卡元件位置,结果你导入Allegro之后,尺寸对不上、图层乱成一锅粥、元件放半天也落不到该在的地方。今天就把这整套流程梳理一遍,从DXF…

2026/9/29 9:39:32

第一次作业全流程拆解:从题目分析到代码提交的避坑指南

“3.2第一次作业”这个标题,很多刚入门的同学一看会觉得稀松平常:不就是一个作业嘛。但根据我这几年带课、改作业的实际经验,真正决定一门课后续学习节奏的,恰恰就是这份看起来最简单的“第一次作业”。它检验的不是你有多聪明&am…

2026/9/28 3:03:23

东莞市品牌网站建设报价常见报错与解决

东莞品牌网站建设报价单背后:一份保姆级建站教程避坑实录 网站做好了没人访问,这大概是很多老板最头疼的事。花了大几万做的品牌站,上线后流量惨淡,比路边摊还冷清。别急着骂外包公司,很多“东莞品牌网站建设报价”里藏着不少猫腻,比如用模板站冒充定制…

2026/9/28 6:05:15

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/29 7:00:49

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/29 0:04:04

AI Evals实战指南:从零搭建LLM应用评估体系与CI/CD集成

1. 为什么AI Evals值得你花时间搞明白做LLM应用的人,迟早会撞上同一堵墙:模型输出飘忽不定,今天答得好好的,明天换个问法就胡说八道。你改了一版提示词,感觉好像好了点,但到底好了多少?说不清。…

2026/9/29 0:04:04

Java采购管理系统实战:从数据库设计到事务一致性

简介:这是一套面向Java Web初学者与课程设计者的采购管理系统完整源码,采用JSP技术搭建,配合MySQL数据库,用于解决企业采购信息的管理问题,适合作为毕业设计、课程大作业或进销存类项目的参考模板。系统实现了用户登录…

2026/9/29 3:53:39

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/26 19:58:38

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/29 6:36:14

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑