Redis實現分散式鎖(spring定時任務叢集應用Redis分散式鎖)
阿新 • • 發佈:2018-12-16
之前2片文章介紹了
描述:
不管用不用動態執行,單機服務都是沒有問題的,但是如果服務是叢集模式下,那麼一個任務在每臺機器都會執行一次,這肯定不是我們需要的,我們要實現的是整個叢集每次只有一個任務執行成功,但是spring對此並沒有很好的支援,所以我們需要有一個統一的資料獲取處,參考網上決定利用rides的一致性來實現,開始就參考網上利用setnx命令實現的,但是還是感覺不太完善,就去拋開定時任務,直接實現Redis分散式鎖應該是最合適的,在定時任務業務處加鎖,業務執行完解鎖即可。
此事例經過多個網站參考並自己實際操作是不存在任何問題,完全可以正常使用。
實現:
import redis.clients.jedis.JedisCluster; import java.util.Collections; /** * * redis實現分散式鎖,並釋放鎖 */ public class RedisTool { 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; /** * 嘗試獲取分散式鎖 * @param jedis Redis客戶端 * @param lockKey 鎖 * @param requestId 請求標識 * @param expireTime 超期時間,毫秒 * @return 是否獲取成功 */ public static boolean tryGetDistributedLock(JedisCluster jedis, String lockKey, String requestId, int expireTime) { /* *設定鎖並設定超時時間,lockKey表示Redis key,requestId表示Redis value,SET_IF_NOT_EXIST表示有值不進行設定(NX), * SET_WITH_EXPIRE_TIME表示是否設定超時時間(PX)設定,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; } /** * 釋放分散式鎖 * @param jedis Redis客戶端 * @param lockKey 鎖 * @param requestId 請求標識 * @return 是否釋放成功 */ public static boolean releaseDistributedLock(JedisCluster jedis, String lockKey, String requestId) { /* * 利用Lua指令碼程式碼,首先獲取鎖對應的value值,檢查是否與requestId相等,如果相等則刪除鎖(解鎖) * eval命令執行Lua程式碼的時候,Lua程式碼將被當成一個命令去執行,並且直到eval命令執行完成,Redis才會執行其他命令,這樣就不會出現上一個程式碼執行完掛了後邊的出現問題,還是一致性的解決 * */ 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; } }
根據上篇部落格的業務進行引用
package com.rails.travel.conf.task.myschedule; import com.rails.travel.common.FrameSpringBeanUtil; import com.rails.travel.common.RedisTool; import redis.clients.jedis.JedisCluster; import java.util.Date; public class MyRunnable3 implements Runnable { //因為是執行緒所以無法注入bean物件,只能通過這種方法去獲取bean物件 //FrameSpringBeanUtil工具類在另一篇部落格:https://blog.csdn.net/QiaoRui_/article/details/83094960 private JedisCluster jedisCluster = FrameSpringBeanUtil.getBean(JedisCluster.class); @Override public void run() { //加鎖,呼叫上邊的工具類,引數具體看工具類說明 boolean lock = RedisTool.tryGetDistributedLock(jedisCluster, MyRunnable3.class.toString(), MyRunnable3.class.toString(), 3000); //加鎖成功則執行業務,不成功則不執行業務 if (lock){ //此處業務程式碼 System.out.print("業務執行了3" + new Date()); //業務執行完成解鎖,如果在執行業務中或者是在解鎖出現了異常,宕機等,鎖會根據加鎖時的key過期時間自己消除 RedisTool.releaseDistributedLock(jedisCluster,MyRunnable3.class.toString(), MyRunnable3.class.toString()); } } }
說明:
加鎖解鎖工具類直接是檢視部落格和官網等獲取用的,部落格必須看,可以完全明白實現原理及現在網上的錯誤做法的問題所在,我開始用的就是部落格中提到的第一種錯誤方法