來自公眾號:IT牧場
都9102年了,你還在手寫分散式鎖嗎?
經常被問到”如何實現分散式鎖”,看來這是大家的一個痛點。
其實Java世界的”半壁江山”——Spring早就提供了分散式鎖的實現。早期,分散式鎖的相關程式碼存在於Spring Cloud的子專案Spring Cloud Cluster中,後來被遷到Spring Integration中。
•Spring Cloud Cluster的GitHub:https://github.com/spring-cloud/spring-cloud-cluster[1] ,裡面有些用例,以及基於ETCD、hazelcast的分散式鎖實現值得一看。•Spring Integration的GitHub:https://github.com/spring-projects/spring-integration[2]
TIPS
這就是明明 Spring Cloud官方[3] 說自己實現了
Global Locks
,但又無法找到任何相關檔案的原因——人家早把相關程式碼搬遷到Spring Integration了。
可能有不少童鞋對Spring Integration不是很熟悉,簡單介紹一下——官方說法,這是一個 企業整合樣式
的實現;通俗地說,Spring Integration的定位是一個輕量級的ESB,儘管它做了很多ESB不做的事情。順便說一下,Spring Cloud Stream的底層也是Spring Integration。
Spring Integration提供的全域性鎖目前為如下儲存提供了實現:
•Gemfire[4]•JDBC[5]•Redis[6]•Zookeeper[7]
它們使用相同的API抽象——這正是Spring最擅長的。這意味著,不論使用哪種儲存,你的編碼體驗是一樣的,有一天想更換實現,只需要修改依賴和配置就可以了,無需修改程式碼。
TIPS
實現無關性:舉個例子,你目前有一個基於Redis的分散式鎖,但有一天想講儲存改為Consul。此時,統一的API至關重要——我相信你不會希望僅僅是修改了分散式鎖的儲存,就得讓開發把相關程式碼翻新一遍。
大學的時候,不也講過面向介面程式設計的重要性嘛。
因此,即使Spring Integration沒有為您使用的儲存提供實現(例如Consul),你也應該基於Spring Integration的API去開發。這樣未來的某一天,一旦Spring Integration為你的儲存提供支援,你就可以無痛切換啦。
編碼
下麵以Redis為例,講解Spring Integration裡面如何使用分散式鎖。
1、加依賴
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-integrationartifactId>
dependency>
<dependency>
<groupId>org.springframework.integrationgroupId>
<artifactId>spring-integration-redisartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
2 、寫配置:
spring:
redis:
port: 6379
host: localhost
3、寫程式碼:初始化
@Configuration
public class RedisLockConfiguration {
@Bean
public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) {
return new RedisLockRegistry(redisConnectionFactory, "spring-cloud");
}
}
強烈建議閱讀一
下 org.springframework.integration.redis.util.RedisLockRegistry
的註釋,它詳細描述了該類的特性,例如可重入性以及鎖在Redis中如何儲存等。
4、寫程式碼:測試
@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、啟動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、啟動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]
說明第二個實體沒有拿到鎖,證明瞭分散式鎖的存在。
至此,是不是讓你有刪掉手寫的那些蹩腳的分散式鎖的衝動呢?
References
[1]
: https://github.com/spring-cloud/spring-cloud-cluster[2]
: https://github.com/spring-projects/spring-integration[3]
Spring Cloud官方: https://spring.io/projects/spring-cloud[4]
Gemfire: https://docs.spring.io/spring-integration/docs/5.1.2.RELEASE/reference/html/gemfire.html#gemfire-lock-registry[5]
JDBC: https://docs.spring.io/spring-integration/docs/5.1.2.RELEASE/reference/html/jdbc.html#jdbc-lock-registry[6]
Redis: https://docs.spring.io/spring-integration/docs/5.1.2.RELEASE/reference/html/redis.html#redis-lock-registry[7]
Zookeeper: https://docs.spring.io/spring-integration/docs/5.1.2.RELEASE/reference/html/zookeeper.html#zk-lock-registry
朋友會在“發現-看一看”看到你“在看”的內容