这才是实现分布式锁的正确姿势!
都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.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>
2 写配置:
spring:redis:port: 6379host: localhost
3 写代码:初始化
@Configuration
public class RedisLockConfiguration {@Beanpublic 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 causejava.lang.IllegalStateException: You do not own lock at spring-cloud:lockat 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]
说明第二个实例没有拿到锁,证明了分布式锁的存在。
至此,是不是让你有删掉手写的那些蹩脚的分布式锁的冲动呢?
这才是实现分布式锁的正确姿势!相关推荐
- 【最全】Spring Boot 实现分布式锁——这才是实现分布式锁的正确姿势!
ava世界的"半壁江山"--Spring早就提供了分布式锁的实现.早期,分布式锁的相关代码存在于Spring Cloud的子项目Spring Cloud Cluster中,后来被迁 ...
- 【分布式缓存系列】Redis实现分布式锁的正确姿势
一.前言 在我们日常工作中,除了Spring和Mybatis外,用到最多无外乎分布式缓存框架--Redis.但是很多工作很多年的朋友对Redis还处于一个最基础的使用和认识.所以我就像把自己对分布式缓 ...
- 程序员如何 Get 分布式锁的正确姿势?| 技术头条
作者 | 刘春龙 责编 | 郭芮 在很多互联网产品应用中,有些场景需要加锁处理,比如秒杀.全局递增ID.楼层生成等等,大部分的解决方案是基于DB实现的,Redis也是较为常见的方案之一. Redis为 ...
- 掌握Redis分布式锁的正确姿势
本文中案例都会在上传到git上,请放心浏览 git地址:https://github.com/muxiaonong/Spring-Cloud/tree/master/order-lock 本文会使用到 ...
- Redis实现分布式锁的正确姿势 | Spring Cloud 36
一.分布式锁 1.1 什么是分布式锁 分布式锁,即分布式系统中的锁.在单体应用中我们通过锁解决的是控制共享资源访问的问题,而分布式锁,就是解决了分布式系统中控制共享资源访问的问题.与单体应用不同的是, ...
- Redis 作者 Antirez 讲如何实现分布式锁?Redis 实现分布式锁天然的缺陷分析Redis分布式锁的正确使用姿势!...
Redis分布式锁基本原理 采用 redis 实现分布式锁,主要是利用其单线程命令执行的特性,一般是 setnx, 只会有一个线程会执行成功,也就是只有一个线程能成功获取锁:看着很完美. 然而-- 看 ...
- 七种方案!探讨Redis分布式锁的正确使用姿势
前言 日常开发中,秒杀下单.抢红包等等业务场景,都需要用到分布式锁.而Redis非常适合作为分布式锁使用.本文将分七个方案展开,跟大家探讨Redis分布式锁的正确使用方式.如果有不正确的地方,欢迎大家 ...
- 有一说一!这才是RabbitMQ实现分布式事务的正确姿势(项目实战)
分布式事务 随着互联网快速发展,微服务,SOA 等服务架构模式正在被大规模的使用,现在分布式系统一般由多个独立的子系统组成,多个子系统通过网络通信互相协作配合完成各个功能. 有很多用例会跨多个子系统才 ...
- Redis分布式锁的正确打开方式
Redis分布式锁的正确打开方式 为什么需要分布式锁 分布式锁原理 单机锁和分布式锁的联系与区别 Redis分布式锁的演进史 第一版 SETNX 如何避免死锁 解决锁被别人释放 锁过期时间不好评估 R ...
最新文章
- 【虚拟化】Dockerfile构建JDK镜像
- kubernetes之一步一个坑
- matlab freqs函数用法,Matlab freqs 函数
- ObjectiveC 深浅拷贝
- 程序员——知识 [转载]
- Java语言的八大优点
- c语言程序进制,C语言编程:进制转换器
- 编译CWM-recovery
- Python地理数据处理 十三:栅格数据处理(一)
- 保证金监控中心保证期货开户和交易记录
- Qt文档阅读笔记-trimmed()与simplified()官方解析与实例
- 基于JAVA EE的临床科室管理系统
- Windows无线网络设置中的7种安全类型简介
- 利用python下载fnl数据方法
- ros源码下载及编译
- httpqyl.php,linux运维架构--PHP开发-零基础学习PHP视频教程
- 【python实验二】
- idea 2020取消Import Maven projects automatically
- 家用双wan口路由器推荐_什么路由器有两个以太口 家用双wan口路由器推荐
- 无效租赁合同要怎么去处理