• 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

  • 相关阅读:
    Navicat cannot create file xxx.sql.bak
    异步分片计算在腾讯文档的实践
    一次磁盘占用率 100% 的排查经历!
    程序员必知的“大数据”基础知识!
    用树莓派构建一台服务器,永久运行网站
    elasticsearch 百亿级数据检索案例与原理
    Redis 持久化策略浅析
    一文聊透微服务接口设计原则
    MySQL架构总览>查询执行流程>SQL解析顺序
    用 Python 破解 WiFi 密码,太刺激了
  • 原文地址:https://www.cnblogs.com/shihaiming/p/10984221.html
Copyright © 2020-2023  润新知