参考:
1、https://xiaomi-info.github.io/2019/12/17/redis-distributed-lock/ 基于jedis
2、https://www.baeldung.com/tag/redis/
对接访问
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
1 public class RedisDetector { 2 3 private static Jedis jedis; 4 5 static boolean statusFlag = false; 6 7 static { 8 9 new Thread(() -> { 10 while (true) { 11 if (jedis == null) { 12 initJedis(); 13 } 14 15 if (jedis != null) { 16 try { 17 jedis.ping(); 18 statusFlag = true; 19 } catch (Exception e) { 20 statusFlag = false; 21 jedis = null; 22 } 23 } 24 25 try { 26 Thread.sleep(5000); 27 System.out.println("Sleep 5s"); 28 } catch (InterruptedException e) { 29 e.printStackTrace(); 30 } 31 } 32 }).start(); 33 } 34 35 private static void initJedis() { 36 jedis = new Jedis("127.0.0.1", 6379); 37 System.out.println("call initJedis...."); 38 } 39 }
使用方,使用前首先判断Redis可用标记