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


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

 

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

其實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

 

 

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

其實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里面如何使用分布式鎖。

  • 加依賴

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <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>
  • 寫配置:

    1
    2
    3
    4
    spring:
    redis:
    port: 6379
    host: localhost
  • 寫代碼:初始化

    1
    2
    3
    4
    5
    6
    7
    @Configuration
    public class RedisLockConfiguration {
    @Bean
    public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) {
    return new RedisLockRegistry(redisConnectionFactory, "spring-cloud");
    }
    }

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

  • 寫代碼:測試

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @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 ,會看到類似如下的日志:

    1
    2
    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 端點,會在第二個實例上看到類似如下日志:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM