在微服務中常常須要使用分佈式鎖,來執行一些任務。例如按期刪除過時數據,在多個服務中只須要一個去執行便可。html
如下說明非嚴格意義的分佈式鎖,由於 redis 實現嚴格意義的分佈式鎖仍是比較複雜的,對於平常簡單使用使用以下簡單方法便可。即偶爾不執行任務不影響業務。git
實現要點github
1)得到鎖、釋放鎖須要是原子操做。要麼獲取成功,要麼失敗。釋放要麼成功,要麼失敗redis
2)任務完成須要本身釋放本身的鎖,不能釋放別人的鎖。spring
3)鎖要有過時時間限制,防止任務崩潰沒有釋放鎖,致使其餘節點沒法得到鎖。springboot
4)執行節點超時長時間不釋放鎖,到下次任務開始執行並行存在的狀況分佈式
要考慮的風險點spring-boot
1)獲取鎖失敗,偶爾不執行任務要不影響業務或告警人工干預微服務
2)redis 宕機,致使沒法獲取鎖this
方案一:低版本使用 jedis 實現
1 添加依賴,高版本或低版本有些方法可能沒有
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.10.2</version>
</dependency>
2 實現方法
@Slf4j public abstract class AbsDistributeLock { private Jedis jedis; public void initDistributeLock(String ip, int port, Integer database, String password) { jedis = new Jedis(ip, port); if (password != null && !password.isEmpty()) { jedis.auth(password.trim()); } if (database == null || database < 0 || database > 15) { database = 0; } jedis.select(database); } 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"; private static final Long RELEASE_SUCCESS = 1L; /** * 具體的任務須要子類去實現 * * @throws RTException 分佈式鎖異常 */ public abstract void taskService() throws RTException; /** * 任一執行,ANY OF * 全部節點任意一個執行任務 taskService 便可,沒有得到鎖的節點不執行任務 * * @param lockKey lockKey * @param keyValue keyValue * @param expireTime 過時時間 ms */ public void onlyOneNodeExecute(String lockKey, String keyValue, int expireTime) { boolean getLock = false; try { if ((getLock = getDistributedLock(jedis, lockKey, keyValue, expireTime))) { taskService(); } } finally { if (getLock) { releaseDistributedLock(jedis, lockKey, keyValue); } } } /** * 全部串行執行,ALL IN LINE * 全部節點都必須執行該任務,每次只能一個執行。 * * @param lockKey lockKey * @param keyValue keyValue * @param expireTime 過時時間 ms */ public void allNodeExecute(String lockKey, String keyValue, int expireTime) { try { while (!(getDistributedLock(jedis, lockKey, keyValue, expireTime))) { try { Thread.sleep(200); } catch (InterruptedException e) { log.info(e.getMessage()); } } taskService(); } finally { releaseDistributedLock(jedis, lockKey, keyValue); } } /** * @param jedis 客戶端 * @param lockKey key * @param keyValue key值 * @param expireTime 過時時間,ms */ public static boolean getDistributedLock(Jedis jedis, String lockKey, String keyValue, int expireTime) { String result = jedis.set(lockKey, keyValue, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { log.info("ip:[{}] get lock:[{}], value:[{}], getLock result:[{}]", IpUtil.getLocalIpAddr(), lockKey, keyValue, result); return true; } else { return false; } } public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String keyValue) { 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(keyValue)); log.info("ip:[{}] release lock:[{}], value:[{}], release result: [{}]", IpUtil.getLocalIpAddr(), lockKey, keyValue, result); if (RELEASE_SUCCESS.equals(result)) { return true; } return false; } }
方案二:高版本的springboot,使用 lua 腳本執行
1 添加依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.2.0.RELEASE</version> </dependency>
2 代碼實現
@Slf4j public abstract class AbsDistributeLockLua { private RedisTemplate<String, String> redisTemplate; public AbsDistributeLockLua(RedisTemplate<String, String> redisTemplate) { this.redisTemplate = redisTemplate; } /** * 具體的任務須要子類去實現 * * @throws RTException 分佈式鎖異常 */ public abstract void taskService() throws RTException; /** * 任一執行,ANY OF * 全部節點任意一個執行任務 taskService 便可,沒有得到鎖的節點不執行任務 * * @param lockKey lockKey * @param keyValue keyValue * @param expireTime 過時時間 ms */ public void onlyOneNodeExecute(String lockKey, String keyValue, int expireTime) { boolean getLock = false; try { if ((getLock = getDistributeLock(redisTemplate, lockKey, keyValue, expireTime))) { taskService(); } } finally { if (getLock) { releaseDistributeLock(redisTemplate, lockKey, keyValue); } } } /** * 全部串行執行,ALL IN LINE * 全部節點都必須執行該任務,每次只能一個執行。 * * @param lockKey lockKey * @param keyValue keyValue * @param expireTime 過時時間 ms */ public void allNodeExecute(String lockKey, String keyValue, int expireTime) { try { while (!(getDistributeLock(redisTemplate, lockKey, keyValue, expireTime))) { try { Thread.sleep(200); } catch (InterruptedException e) { log.info(e.getMessage()); } } taskService(); } finally { releaseDistributeLock(redisTemplate, lockKey, keyValue); } } /** * 經過lua腳本 加鎖並設置過時時間 * * @param key 鎖key值 * @param value 鎖value值 * @param expire 過時時間,單位毫秒 * @return true:加鎖成功,false:加鎖失敗 */ public boolean getDistributeLock(RedisTemplate<String, String> redisTemplate, String key, String value, int expire) { DefaultRedisScript<String> redisScript = new DefaultRedisScript<String>(); redisScript.setResultType(String.class); String strScript = "if redis.call('setNx',KEYS[1],ARGV[1])==1 then return redis.call('pexpire',KEYS[1],ARGV[2]) else return 0 end"; redisScript.setScriptText(strScript); try { Object result = redisTemplate.execute(redisScript, redisTemplate.getStringSerializer(), redisTemplate.getStringSerializer(), Collections.singletonList(key), value, expire); System.out.println("redis返回:" + result); return "1".equals("" + result); } catch (Exception e) { //能夠本身作異常處理 return false; } } /** * 經過lua腳本釋放鎖 * * @param key 鎖key值 * @param value 鎖value值(僅當redis裏面的value值和傳入的相同時才釋放,避免釋放其餘線程的鎖) * @return true:釋放鎖成功,false:釋放鎖失敗(可能已過時或者已被釋放) */ public boolean releaseDistributeLock(RedisTemplate<String, String> redisTemplate, String key, String value) { DefaultRedisScript<String> redisScript = new DefaultRedisScript<>(); redisScript.setResultType(String.class); String strScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; redisScript.setScriptText(strScript); try { Object result = redisTemplate.execute(redisScript, redisTemplate.getStringSerializer(), redisTemplate.getStringSerializer(), Collections.singletonList(key), value); return "1".equals("" + result); } catch (Exception e) { //能夠本身作異常處理 return false; } } }
代碼地址:https://github.com/crazyCodeLove/distribute-lock
參考文獻:
https://www.cnblogs.com/bcde/p/11132479.html
https://blog.csdn.net/u013985664/article/details/94459529