【Redis】客户端
参考:
Redis 客户端 Jedis、lettuce 和 Redisson 对比 https://www.cnblogs.com/54chensongxia/p/13815761.html
https://xiaomi-info.github.io/2019/12/17/redis-distributed-lock/ 基于jedis
https://www.baeldung.com/tag/redis/
Jedis :老牌的 Redis 的 Java 实现客户端,提供了比较全面的 Redis 命令的支持
- 使用阻塞的 I/O,且其方法调用都是同步的,程序流需要等到 sockets 处理完 I/O 才能执行,不支持异步;
- Jedis 客户端实例不是线程安全的,所以需要通过连接池来使用 Jedis。
lettuce :是一种可扩展的线程安全的 Redis 客户端,支持异步模式。如果避免阻塞和事务操作,如BLPOP和MULTI/EXEC,多个线程就可以共享一个连接。lettuce 底层基于 Netty,支持高级的 Redis 特性,比如哨兵,集群,管道,自动重新连接和Redis数据模型。
Redisson:在 Redis 的基础上实现的 Java 驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的 Java 常用对象,还提供了许多分布式服务。其中包括( BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson 提供了使用Redis 的最简单和最便捷的方法。Redisson 的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
对接访问
JedisPool -> JedisPoolAbstrace -> Pool<Jedis> { GenericObjectPool 来自apache.commons.pool2 }
// 1. Redis单节点模式 Jedis jedis = new Jedis("127.0.0.1", 6379); System.out.println(jedis.ping()); // 2. 通过Pool方式 public class JedisUtils { private static JedisPool jedisPool; static { // 配置连接池 JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(5); config.setMaxIdle(3); config.setMinIdle(2); config.testOnBorrow(true); // 创建连接池 jedisPool = new JedisPool(config, "localhost", 6379); } public static Jedis getJedis() { return jedisPool.getResource(); } } // 使用方,通过调用close()完成申请的Jedis被pool回收 try (Jedis jedis = JedisUtils.getJedis()) { jedis.xxxx catch(JedisExecption e) { } // 3. Redis集群部署 Set<HostAndPort> clusterNodes = new HashSet<HostAndPort>(); clusterNodes.add(new HostAndPort("192.168.248.128", 7001)); clusterNodes.add(new HostAndPort("192.168.248.128", 7002)); JedisCluster jc = new JedisCluster(clusterNodes); jc.set("address", "深圳"); String address = jc.get("address"); System.out.println(address);
分布式锁
关注: 1、加锁解锁的操作原子性;2、指定过期时间,避免加锁节点异常退出而锁永远无法释放;3、不能释放别人加的锁
参考: https://www.cnblogs.com/linjiqin/p/8003838.html
1、加锁:
错误实现:setnx(set if not exist 成功返回1,否则返回0)+ expire(设置过期时间):非原子操作,当执行expire时程序宕机,则导致锁永远不会被释放
正确实现:根据Jedis版本API定义有差异
private static final String LOCK_SUCCESS = "OK"; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; /** * 尝试获取分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 常 UUID.randomUUID().toString() * @param expireTime 超期时间 * @return 是否获取成功 */ public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) { String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { return true; } return false; }
基于3.5.2样例(Jedis API有调整)
SetParams params = new SetParams(); params.ex(100); // 设置超时时间 params.nx(); // 若锁不存在才进行写操作 jedis.set(key, requestId, params); // 成功返回OK
解决方案:通过lua脚本 参考https://xiaomi-info.github.io/2019/12/17/redis-distributed-lock/
2、解锁 (通过lua脚本实现原子操作)
private static final Long RELEASE_SUCCESS = 1L; /** * 释放分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @return 是否释放成功 */ public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); if (RELEASE_SUCCESS.equals(result)) { return true; } return false; }
错误的实现:多线程场景,可能if判断成功后,锁已被别的客户端获取,此时执行删除的是别人创建的锁
// 判断加锁与解锁是不是同一个客户端 if (requestId.equals(jedis.get(lockKey))) { // 若在此时,这把锁突然不是这个客户端的,则会误删除别人获取的分布式锁 jedis.del(lockKey); }
连通性检测
定时检测redis可用性,当Redis恢复时重新初始化Jedis
public class RedisDetector { private static Jedis jedis; static boolean statusFlag = false; static { new Thread(() -> { while (true) { if (jedis == null) { initJedis(); } if (jedis != null) { try { jedis.ping(); statusFlag = true; } catch (Exception e) { statusFlag = false; jedis = null; } } try { Thread.sleep(5000); System.out.println("Sleep 5s"); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } private static void initJedis() { jedis = new Jedis("127.0.0.1", 6379); System.out.println("call initJedis...."); } }
使用方,使用前首先判断Redis可用标记
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)