目前幾乎不少大型網站及應用都是分佈式部署的,分佈式場景中的數據一致性問題一直是一個比較重要的話題。分佈式的CAP理論告訴咱們「任何一個分佈式系統都沒法同時知足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance),最多隻能同時知足兩項。」因此,不少系統在設計之初就要對這三者作出取捨。在互聯網領域的絕大多數的場景中,都須要犧牲強一致性來換取系統的高可用性,系統每每只須要保證「最終一致性」,只要這個最終時間是在用戶能夠接受的範圍內便可。java
在不少場景中,咱們爲了保證數據的最終一致性,須要不少的技術方案來支持,好比分佈式事務、分佈式鎖等。有的時候,咱們須要保證一個方法在同一時間內只能被同一個線程執行。redis
基於數據庫實現分佈式鎖;
基於緩存(Redis等)實現分佈式鎖;
基於Zookeeper實現分佈式鎖;spring
儘管有這三種方案,可是不一樣的業務也要根據本身的狀況進行選型,他們之間沒有最好只有更適合!但因爲操做數據庫須要必定的開銷,會照成必定的性能問題。而且使用數據庫行級鎖並不必定靠譜,尤爲是當咱們鎖表並不大的時候,因此實際開發過程當中使用Redis或Zookeeper的比較多。數據庫
當咱們在設計分佈式鎖的時候,咱們應該考慮分佈式鎖至少要知足的一些條件,同時考慮如何高效的設計分佈式鎖,這裏我認爲如下幾點是必需要考慮的。api
在分佈式高併發的條件下,咱們最須要保證,同一時刻只能有一個線程得到鎖,這是最基本的一點。緩存
在分佈式高併發的條件下,好比有個線程得到鎖的同時,尚未來得及去釋放鎖,就由於系統故障或者其它緣由使它沒法執行釋放鎖的命令,致使其它線程都沒法得到鎖,形成死鎖。因此分佈式很是有必要設置鎖的有效時間
,確保系統出現故障後,在必定時間內可以主動去釋放鎖,避免形成死鎖的狀況。服務器
高併發分佈式系統中,線程互斥等待會成爲性能瓶頸,須要好的中間件和實現來保證性能。併發
咱們知道ReentrantLock是可重入鎖,那它的特色就是:同一個線程能夠重複拿到同一個資源的鎖。重入鎖很是有利於資源的高效利用。關於這點以後會作演示。針對以上Redisson都能很好的知足,下面就來分析下它。app
爲了更好的理解分佈式鎖的原理,我這邊本身畫張圖經過這張圖來分析。分佈式
加鎖機制:
線程去獲取鎖,獲取成功: 執行lua腳本,保存數據到redis數據庫。
線程去獲取鎖,獲取失敗: 一直經過while循環嘗試獲取鎖,獲取成功後,執行lua腳本,保存數據到redis數據庫。
watch dog看門狗:
在一個分佈式環境下,假如一個線程得到鎖後,忽然服務器宕機了,那麼這個時候在必定時間後這個鎖會自動釋放,你也能夠設置鎖的有效時間(不設置默認30秒),這樣的目的主要是防止死鎖的發生。但在實際開發中會有下面一種狀況:
//設置鎖1秒過去 redissonLock.lock("redisson", 1); /** * 業務邏輯須要諮詢2秒 */ redissonLock.release("redisson"); /** * 線程1 進來得到鎖後,線程一切正常並無宕機,但它的業務邏輯須要執行2秒,這就會有個問題,在 線程1 執行1秒後,這個鎖就自動過時了, * 那麼這個時候 線程2 進來了。那麼就存在 線程1和線程2 同時在這段業務邏輯裏執行代碼,這固然是不合理的。 * 並且若是是這種狀況,那麼在解鎖時系統會拋異常,由於解鎖和加鎖已經不是同一線程了,具體後面代碼演示。 */
因此這個時候看門狗
就出現了,它的做用就是 線程1 業務尚未執行完,時間就過了,線程1 還想持有鎖的話,就會啓動一個watch dog後臺線程,不斷的延長鎖key的生存時間。注意
正常這個看門狗線程是不啓動的,還有就是這個看門狗啓動後對總體性能也會有必定影響,因此不建議開啓看門狗。
首先在pom裏依賴Redisson
<!-- redisson --> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>${org-redisson.version}</version> </dependency>
在公共模塊配置Redisson
import org.redisson.Redisson; import org.redisson.config.Config; import org.redisson.config.SingleServerConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.ResourceBundle; /** * <p>實例Redisson類</p> * * @author lft * @version 1.0 * @date 2019/6/28 0028 * @since jdk1.8 */ @Component public class RedissonManager { private static Logger logger = LoggerFactory.getLogger(RedissonManager.class); private static String REDIS_IP; private static String REDIS_PORT; private static String REDIS_PASSWD; private static String PROJECT_ENV; @Value("${spring.profiles.active}") public void setProjectEnv(String projectEnv) { PROJECT_ENV = projectEnv; } private static Config config = new Config(); private static Redisson redisson = null; private static void init() { ResourceBundle properties = ResourceBundle.getBundle("application-"+PROJECT_ENV); REDIS_IP = properties.getString("spring.redis.host").trim(); REDIS_PORT = properties.getString("spring.redis.port").trim(); if(properties.containsKey("spring.redis.password")){ REDIS_PASSWD = properties.getString("spring.redis.password"); } SingleServerConfig singleServerConfig = config.useSingleServer(); singleServerConfig.setAddress("redis://"+REDIS_IP+":"+REDIS_PORT); if(null != REDIS_PASSWD && !"".equals(REDIS_PASSWD)){ singleServerConfig.setPassword(REDIS_PASSWD); } //獲得redisson對象 redisson = (Redisson) Redisson.create(config); } //實例化redisson public static Redisson getInstance(){ init(); return redisson; } }
加鎖和釋放鎖的方法
import org.redisson.Redisson; import org.redisson.api.RLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.TimeUnit; /** * <p>Redisson的使用</p> * * @author lft * @version 1.0 * @date 2019/6/28 0028 * @since jdk1.8 */ public class DistributedRedisLock { private static Logger logger = LoggerFactory.getLogger(DistributedRedisLock.class); private static Redisson redisson = RedissonManager.getInstance(); private static final String LOCK_TITLE = "redis_lock_"; //加鎖 public static boolean lockup(String lockName){ //聲明key對象 String key = LOCK_TITLE + lockName; //獲取鎖對象 RLock mylock = redisson.getLock(key); //設置過時時間,防止死鎖 mylock.lock(60, TimeUnit.SECONDS); logger.info("======lock======"+Thread.currentThread().getName()); return true; } //鎖的釋放 public static void release(String lockName){ String key = LOCK_TITLE + lockName; RLock mylock = redisson.getLock(key); //釋放鎖(解鎖) mylock.unlock(); logger.info("======unlock======"+Thread.currentThread().getName()); } }
業務邏輯使用鎖
/** * 測試Redis鎖 * @param request * @return * @throws Exception */ @PostMapping("/lockTest") public Object lockTest(HttpServletRequest request) throws Exception{ String key = "test"; //加鎖 DistributedRedisLock.lockup(key); //TODO 業務代碼 LOGGER.info("=========================執行測試任務"); //釋放鎖 DistributedRedisLock.release(key); return ResultUtils.success("鎖測試結束!"); }
zookeeper分佈式鎖參考:https://cloud.tencent.com/developer/article/1462302