1. 程式人生 > >Spring Boot 2實現分散式鎖——這才是實現分散式鎖的正確姿勢!

Spring Boot 2實現分散式鎖——這才是實現分散式鎖的正確姿勢!

今年企業對Java開發的市場需求,你看懂了嗎? >>>   

都9102年了,你還在手寫分散式鎖嗎?

經常被問到”如何實現分散式鎖”,看來這是大家的一個痛點。

其實Java世界的”半壁江山”——Spring早就提供了分散式鎖的實現。早期,分散式鎖的相關程式碼存在於Spring Cloud的子專案Spring Cloud Cluster中,後來被遷到Spring Integration中。

TIPS

這就是明明 Spring Cloud官方 說自己實現了 Global Locks ,但又無法找到任何相關文件的原因——人家早把相關程式碼搬遷到Spring Integration了。

可能有不少童鞋對Spring Integration不是很熟悉,簡單介紹一下——官方說法,這是一個 企業整合模式

的實現;通俗地說,Spring Integration的定位是一個輕量級的ESB,儘管它做了很多ESB不做的事情。順便說一下,Spring Cloud Stream的底層也是Spring Integration。

Spring Integration提供的全域性鎖目前為如下儲存提供了實現:

它們使用相同的API抽象——這正是Spring最擅長的。這意味著,不論使用哪種儲存,你的編碼體驗是一樣的,有一天想更換實現,只需要修改依賴和配置就可以了,無需修改程式碼

TIPS

實現無關性:舉個例子,你目前有一個基於Redis的分散式鎖,但有一天想講儲存改為Consul。此時,統一的API至關重要——我相信你不會希望僅僅是修改了分散式鎖的儲存,就得讓開發把相關程式碼翻新一遍

大學的時候,不也講過面向介面程式設計的重要性嘛。

因此,即使Spring Integration沒有為您使用的儲存提供實現(例如Consul),你也應該基於Spring Integration的API去開發。這樣未來的某一天,一旦Spring Integration為你的儲存提供支援,你就可以無痛切換啦

編碼

下面以Redis為例,講解Spring Integration裡面如何使用分散式鎖。

  • 加依賴

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-integration</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.integration</groupId>
      <artifactId>spring-integration-redis</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  • 寫配置:

    spring:
      redis:
        port: 6379
        host: localhost
    
  • 寫程式碼:初始化

    @Configuration
    public class RedisLockConfiguration {
      @Bean
      public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) {
        return new RedisLockRegistry(redisConnectionFactory, "spring-cloud");
      }
    }
    

    強烈建議閱讀一下 org.springframework.integration.redis.util.RedisLockRegistry 的註釋,它詳細描述了該類的特性,例如可重入性以及鎖在Redis中如何儲存等

  • 寫程式碼:測試

    @GetMapping("test")
    public void test() throws InterruptedException {
      Lock lock = redisLockRegistry.obtain("lock");
      boolean b1 = lock.tryLock(3, TimeUnit.SECONDS);
      log.info("b1 is : {}", b1);
    
      TimeUnit.SECONDS.sleep(5);
    
      boolean b2 = lock.tryLock(3, TimeUnit.SECONDS);
      log.info("b2 is : {}", b2);
    
      lock.unlock();
      lock.unlock();
    }
    

測試

  • 啟動1個例項,訪問 http://localhost:8080/test ,會看到類似如下的日誌:

    2019-03-15 00:48:38.948  INFO 21893 --- [nio-8080-exec-1] c.itmuch.lock.SpringBootLockApplication  : b1 is : true
    2019-03-15 00:48:43.958  INFO 21893 --- [nio-8080-exec-1] c.itmuch.lock.SpringBootLockApplication  : b2 is : true
    
  • 啟動2個例項,並迅速訪問兩個例項的 /test 端點,會在第二個例項上看到類似如下日誌:

    2019-03-15 00:50:08.222  INFO 21898 --- [nio-8081-exec-3] c.itmuch.lock.SpringBootLockApplication  : b1 is : false
    2019-03-15 00:50:13.233  INFO 21898 --- [nio-8081-exec-3] c.itmuch.lock.SpringBootLockApplication  : b2 is : true
    2019-03-15 00:50:13.252 ERROR 21898 --- [nio-8081-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: You do not own lock at spring-cloud:lock] with root cause
    
    java.lang.IllegalStateException: You do not own lock at spring-cloud:lock
    	at org.springframework.integration.redis.util.RedisLockRegistry$RedisLock.unlock(RedisLockRegistry.java:300) ~[spring-integration-redis-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    	at com.itmuch.lock.SpringBootLockApplication.test(SpringBootLockApplication.java:33) ~[classes/:na]
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_201]
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_201]
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_201]
    	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_201]
    	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:189) ~[spring-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102) ~[spring-webmvc-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800) ~[spring-webmvc-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038) ~[spring-webmvc-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) ~[spring-webmvc-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) ~[spring-webmvc-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897) ~[spring-webmvc-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    	at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[tomcat-embed-core-9.0.16.jar:9.0.16]
    	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882) ~[spring-webmvc-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.16.jar:9.0.16]
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.16.jar:9.0.16]
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.16.jar:9.0.16]
    	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.16.jar:9.0.16]
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.16.jar:9.0.16]
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.16.jar:9.0.16]
    	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    

    說明第二個例項沒有拿到鎖,證明了分散式鎖的存在。

至此,是不是讓你有刪掉手寫的那些蹩腳的分散式鎖的衝動呢?

配套程式碼

GitHub:https://github.com/eacdy/spring-boot-study/tree/master/spring-boot-lock-redis

Gitee:https://gitee.com/itmuch/spring-boot-study/tree/master/spring-boot-lock-redis

本文首發

http://www.itmuch.com/spring-boot/global-lock/

關注我

相關推薦

spring-boot-2.0.3之redis快取實現,不是你想的那樣哦

前言   開心一刻     小白問小明:“你前面有一個5米深的坑,裡面沒有水,如果你跳進去後該怎樣出來了?”小明:“躺著出來唄,還能怎麼出來?”小白:“為什麼躺著出來?”小明:“5米深的坑,還沒有水,跳下去不死就很幸運了,殘是肯定會殘的,不躺著出來,那能怎麼出來?”小白:“假設沒死也沒殘呢?”小明:“你當

spring-boot-2.0.3之redis快取實現,不是你想的那樣哦

前言  開心一刻     小白問小明:“你前面有一個5米深的坑,裡面沒有水,如果你跳進去後該怎樣出來了?”小明:“躺著出來唄,還能怎麼出來?”小白:“為什麼躺著出來?”小明:“5米深的坑,還沒有水,跳下去不死就很幸運了,殘是肯定會殘的,不躺著出來,那能怎麼出來?”小白:“假設沒死也沒殘呢?”小明:“你當我超

Spring Boot Admin 詳解(Spring Boot 2.0,基於 Eureka 的實現

Spring Boot Admin 用於監控基於 Spring Boot 的應用,它是在 Spring Boot Actuator 的基礎上提供簡潔的視覺化 WEB UI。 (一)簡介 Spring Boot Admin 提供了很多功能,如顯示 name、id 和 version,顯示線上狀態,

是俯臥撐的正確做法你真的會做俯臥撐嗎?

這才是俯臥撐的正確做法!你真的會做俯臥撐嗎? 俯臥撐可以說是一個最方便、隨時隨地都可以進行的運動,有研究稱做俯臥撐可以作為檢測體能健康的一個標準,因為完成一個俯臥撐幾乎要動用到身體的每一個部位。男人做俯臥撐的好處有哪些呢?經常做俯臥撐的男人性生活質量更高,而且對人體的生理機能、體質等都有較好的鍛鍊,還可以有

Spring Boot 2實現分散式——實現分散式正確姿勢

今年企業對Java開發的市場需求,你看懂了嗎? >>>   

spring boot 定時任務基於zookeeper的分布式實現

刪除 etl .org stack pub 具體實現 spec 分享 -i 基於ZooKeeper分布式鎖的流程 在zookeeper指定節點(locks)下創建臨時順序節點node_n 獲取locks下所有子節點children 對子節點按節點自增序號從小到大排序

從零實現 Spring Boot 2.0 整合 weixin-java-mp(weixin-java-tools) 獲取 openId,用於微信授權

步驟: 一、內網穿透申請二級域名(有伺服器和域名者可略過) 二、申請微信公眾平臺測試號(有已認證的微信服務號者可略過) 三、搭建 Spring Boot 2.0 專案實現獲取openId 一、內網穿透: 因為要直接用內網本機開發除錯,微信網頁授權在回撥時要訪問本機,所以直接

3、Spring Boot 2.x整合Mybatis並且實現單表的增刪除改查

上一篇建立了一個最簡單的Spring Boot 2.x專案,算是有了一個初步的認識。 這一篇我們在上一篇專案的基礎上,實現以下目標: 1、專案中整合Mybatis Generator進行mapper相關檔案的自動生成; 2、整合Mybatis連線資料庫並且實現單表的增刪

【直播預告】:Java Spring Boot實戰系列課程(第十講):Spring Boot 2.0實戰高併發分散式快取

內容概要:Redis作為開源分散式高併發快取,在網際網路公司高併發系統中廣泛使 用,本次課程講解如何使用最新的Java Spring Data實戰Redis,以及底層API的實現原始碼。主講人:徐雷(阿里雲棲特邀Java專家)直播時間:2019年1月1日 週二 今晚20:00直播地點:【阿里Java技術進階】

Java Spring Boot 2.0實戰Redis分散式快取與底層API架構

《阿里巴巴Java Spring Boot 2.0開發實戰課程》10課 本期分享專家:徐雷—阿里巴巴雲棲Java講師,MongoDB講師 本期分享主題:Java Spring Boot 2.0實戰Redis分散式快取與底層API架構(面試題) 內容概要:Redis分散式高併發快取,高併發架構的必備技術!

spring boot 2.0 quartz 輕鬆實現定時任務和作業排程

    最近在做一個彩票相關的專案,主要涉及到不定時開獎,不定時封盤,原本打算使用spring boot 自帶的Schedule進行這一系列的工作,由於不能自動的新增定時任務,所以使用quartz,spring boot 2.0集成了quartz,所以決定嘗試下quartz用

spring boot 2.1.4 快取 Hazelcast實現(四)

hazelcast.xml檔案可以拆分成多個,並且在hazelcast.xml檔案中預設可以從系統配置項讀取配置屬性,這裡轉成在s

spring boot 2.1.4 快取 Hazelcast實現(三)

Hazelcast官方提供了監控包,下載路徑https://hazelcast.org/download/ 裡面有Hazelca

spring boot 2.1.4 hibernate二級快取 Hazelcast實現(二)

在(一)中我們配置好了 hibernate二級快取 Hazelcast實現,但是當我們使用spring cache相關注

Spring Boot 2.x基礎教程:JSR-303實現請求引數校驗

請求引數的校驗是很多新手開發非常容易犯錯,或存在較多改進點的常見場景。比較常見的問題主要表現在以下幾個方面: 僅依靠前端框架解決引數校驗,缺失服務端的校驗。這種情況常見於需要同時開發前後端的時候,雖然程式的正常使用不會有問題,但是開發者忽略了非正常操作。比如繞過前端程式,直接模擬客戶端請求,這時候就會突然在

Spring Boot 2.X(八):Spring AOP 實現簡單的日誌切面

AOP 1.什麼是 AOP ? AOP 的全稱為 Aspect Oriented Programming,譯為面向切面程式設計,是通過預編譯方式和執行期動態代理實現核心業務邏輯之外的橫切行為的統一維護的一種技術。AOP 是面向物件程式設計(OOP)的補充和擴充套件。 利用 AOP 可以對業務邏輯各部分進行隔離

Spring Boot 2 實戰:利用Redis的Geo功能實現查詢附近的位置

![](https://img2020.cnblogs.com/other/1739473/202006/1739473-20200619114304616-406392139.jpg) ## 1. 前言 老闆突然要上線一個需求,獲取當前位置方圓一公里的業務代理點。明天上線!當接到這個需求的時候我差點吐血

Spring Boot 2.x基礎教程:實現檔案上傳

檔案上傳的功能實現是我們做Web應用時候最為常見的應用場景,比如:實現頭像的上傳,Excel檔案資料的匯入等功能,都需要我們先實現檔案的上傳,然後再做圖片的裁剪,excel資料的解析入庫等後續操作。 今天通過這篇文章,我們就來一起學習一下如何在Spring Boot中實現檔案的上傳。 ## 動手試試 *

Spring Boot 2.x基礎教程:使用JTA實現多資料來源的事務管理

在一個Spring Boot專案中,連線多個數據源還是比較常見的。之前也介紹瞭如何在幾種常用框架的場景下配置多資料來源,具體可見: - [Spring Boot 2.x基礎教程:JdbcTemplate的多資料來源配置](http://blog.didispace.com/spring-boot-learn

Spring Boot學習——AOP編程的簡單實現

col .com tsig 訪問 pan -s ping 編程範式 lan 首先應該明白一點,AOP是一種編程範式,是一種程序設計思想,與具體的計算機編程語言無關,所以不止是Java,像.Net等其他編程語言也有AOP的實現方式。AOP的思想理念就是將通用邏輯