您的当前位置:首页正文

SpringBoot2实现分布式锁——这才是实现分布式锁的正确姿势!

来源:我们爱旅游
SpringBoot2实现分布式锁——这才是实现分布式锁的正确姿势!

原⽂:

经常被问到”如何实现分布式锁”,看来这是⼤家的⼀个痛点。

其实Java世界的”半壁江⼭”——Spring早就提供了分布式锁的实现。早期,分布式锁的相关代码存在于Spring Cloud的⼦项⽬Spring Cloud Cluster中,后来被迁到SpringIntegration中。

Spring Cloud Cluster的GitHub: ,⾥⾯有些⽤例,以及基于ETCD、hazelcast的分布式锁实现值得⼀看⾥⾯有些⽤例,以及基于ETCD、hazelcast的分布式锁实现值得⼀看。Spring Integration的GitHub:

TIPS

这就是明明 说⾃⼰实现了 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⾥⾯如何使⽤分布式锁。加依赖

org.springframework.boot

spring-boot-starter-integration

org.springframework.integration spring-integration-redis

org.springframework.boot

spring-boot-starter-data-redis

写配置:

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中如何存储等它详细描述了该类的特性,例如可重⼊性以及锁在Redis中如何存储等。写代码:测试

@GetMapping(\"test\")

public void test() throws InterruptedException { Lock lock = redisLockRegistry.obtain(\"lock\");

boolean b1 = lock.tryLock(3, TimeUnit.SECONDS); log.info(\"b1 is : {}\ TimeUnit.SECONDS.sleep(5);

boolean b2 = lock.tryLock(3, TimeUnit.SECONDS); log.info(\"b2 is : {}\ 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 : false2019-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.Ijava.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:Gitee:

经常被问到”如何实现分布式锁”,看来这是⼤家的⼀个痛点。

其实Java世界的”半壁江⼭”——Spring早就提供了分布式锁的实现。早期,分布式锁的相关代码存在于Spring Cloud的⼦项⽬Spring Cloud Cluster中,后来被迁到SpringIntegration中。

Spring Cloud Cluster的GitHub: ,⾥⾯有些⽤例,以及基于ETCD、hazelcast的分布式锁实现值得⼀看⾥⾯有些⽤例,以及基于ETCD、hazelcast的分布式锁实现值得⼀看。Spring Integration的GitHub:

TIPS

这就是明明 说⾃⼰实现了 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⾥⾯如何使⽤分布式锁。加依赖

123456789

org.springframework.boot

spring-boot-starter-integration

org.springframework.integration spring-integration-redis

10 org.springframework.boot

11 spring-boot-starter-data-redis12

写配置:

1spring:2 redis:

3 port: 63794 host: localhost

写代码:初始化

1@Configuration

2public class RedisLockConfiguration {3 @Bean

4 public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) {5 return new RedisLockRegistry(redisConnectionFactory, \"spring-cloud\");6 }7}

强烈建议阅读⼀下 org.springframework.integration.redis.util.RedisLockRegistry 的注释,它详细描述了该类的特性,例如可重⼊性以及锁在Redis中如何存储等它详细描述了该类的特性,例如可重⼊性以及锁在Redis中如何存储等。写代码:测试

1@GetMapping(\"test\")

2public void test() throws InterruptedException {3 Lock lock = redisLockRegistry.obtain(\"lock\");

4 boolean b1 = lock.tryLock(3, TimeUnit.SECONDS);5 log.info(\"b1 is : {}\", b1);6

7 TimeUnit.SECONDS.sleep(5);8

9 boolean b2 = lock.tryLock(3, TimeUnit.SECONDS);10 log.info(\"b2 is : {}\", b2);11

12 lock.unlock();13 lock.unlock();14}

测试

启动1个实例,访问 http://localhost:8080/test ,会看到类似如下的⽇志:

12019-03-15 00:48:38.948 INFO 21893 --- [nio-8080-exec-1] c.itmuch.lock.SpringBootLockApplication : b1 is : true22019-03-15 00:48:43.958 INFO 21893 --- [nio-8080-exec-1] c.itmuch.lock.SpringBootLockApplication : b2 is : true

启动2个实例,并迅速访问两个实例的 /test 端点,会在第⼆个实例上看到类似如下⽇志:

12019-03-15 00:50:08.222 INFO 21898 --- [nio-8081-exec-3] c.itmuch.lock.SpringBootLockApplication : b1 is : false22019-03-15 00:50:13.233 INFO 21898 --- [nio-8081-exec-3] c.itmuch.lock.SpringBootLockApplication : b2 is : true

32019-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;4

5java.lang.IllegalStateException: You do not own lock at spring-cloud:lock

6 at org.springframework.integration.redis.util.RedisLockRegistry$RedisLock.unlock(RedisLockRegistry.java:300) ~[spring-integration-redis-5.1.3.RELEASE.jar:5.1.3.RELEASE]7 at com.itmuch.lock.SpringBootLockApplication.test(SpringBootLockApplication.java:33) ~[classes/:na]8 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_201]

9 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_201]

10 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_201]11 at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_201]

12 at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:189) ~[spring-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]

13 at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]

14 at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102) ~[spring-webmvc-5.1.5.RELEASE.jar:5.1.5.RELEASE]15 at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.1.5.RELEASE.jar:5.1.5.RELEAS16 at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800) ~[spring-webmvc-5.1.5.RELEASE.jar:5.1.5.RELEASE]17 at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.1.5.RELEASE.jar:5.1.5.RELEASE]18 at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038) ~[spring-webmvc-5.1.5.RELEASE.jar:5.1.5.RELEASE]19 at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) ~[spring-webmvc-5.1.5.RELEASE.jar:5.1.5.RELEASE]

20 at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) ~[spring-webmvc-5.1.5.RELEASE.jar:5.1.5.RELEASE]21 at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897) ~[spring-webmvc-5.1.5.RELEASE.jar:5.1.5.RELEASE]22 at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[tomcat-embed-core-9.0.16.jar:9.0.16]

23 at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882) ~[spring-webmvc-5.1.5.RELEASE.jar:5.1.5.RELEASE]24 at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.16.jar:9.0.16]

25 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.16.jar:9.0.16]26 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.16.jar:9.0.16]27 at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.16.jar:9.0.16]

28 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.16.jar:9.0.16]29 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.16.jar:9.0.16]

30 at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]31 at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]

说明第⼆个实例没有拿到锁,证明了分布式锁的存在。⾄此,是不是让你有删掉⼿写的那些蹩脚的分布式锁的冲动呢?

配套代码

GitHub:Gitee:

因篇幅问题不能全部显示,请点此查看更多更全内容