spring 为我们统一的分布式锁的接口
application.yml文件
spring:
redis:
port: 6379
host: 192.168.10.103
server:
port: 8888
zookeeper:
host: 192.168.10.100:2181,192.168.10.101:2181,192.168.10.103:2181
redis集群配置方式(此处是3主3从)
spring:
redis:
cluster:
nodes: 192.168.10.103:7000,192.168.10.103:7001,192.168.10.103:7002,192.168.10.103:7003,192.168.10.103:7004,192.168.10.103:7005
max-redirects: 10
database: 0
timeout: 1000000
lettuce:
pool:
## 连接池最大连接数(使用负值表示没有限制)
max-active: 300
## 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1
## 连接池中的最大空闲连接
max-idle: 100
## 连接池中的最小空闲连接
min-idle: 20
之前都是手写一个分布式锁,其实Spring早就提供了分布式锁的实现。早期,分布式锁的相关代码存在于Spring Cloud的子项目Spring Cloud Cluster中,后来被迁移到Spring Integration中。
Spring Integration提供的全局锁,目前为这几种存储提供了实现:Gemfire、JDBC、Redis、Zookeeper。
它们使用相同的API抽象--这正是Spring最擅长的。这意味着,不论使用哪种存储,你的编码体验都是一样的,有一天想更换实现,只需要修改依赖和配置就可以了,无需修改代码。(抄的)
spring boot版本是 2.0.0.Release
org.springframework.boot
spring-boot-starter-parent
2.0.0.RELEASE
redis分布式锁,内部使用Redission实现
org.springframework.boot
spring-boot-starter-integration
org.springframework.integration
spring-integration-redis
org.springframework.boot
spring-boot-starter-data-redis
org.apache.commons
commons-pool2
配置类
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
redisTemplate.setDefaultSerializer(serializer);
return redisTemplate;
}
@Bean
public LockRegistry lockRegistry(LettuceConnectionFactory redisConnectionFactory) {
return new RedisLockRegistry(redisConnectionFactory, "haojunjie");
}
}
其中@Configuration 代表这个类是一个配置类,然后@AutoConfigureAfter(RedisAutoConfiguration.class) 是让我们这个配置类在内置的配置类之后在配置,这样就保证我们的配置类生效,并且不会被覆盖配置。其中需要注意的就是方法名一定要叫redisTemplate 因为@Bean注解是根据方法名配置这个bean的name的。特别注意新版本的redis集群配置坑多。
测试代码(此处有个很奇怪的问题使用spring boot test测试会出现连接redis集群会出现localhost:6379连接不上去,你的配置连接没有效果,但是不使用测试类就没问题)
@GetMapping("lock")
public String lockTest() {
Lock lock = lockRegistry.obtain("hello");
lock.lock();
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return "success";
}
key是 创建RedisLockRegistry:obtain里面传递的字符串
这里就是haojunjie:hello,内部使用Redission开源实现,已经为我们考虑很多问题:锁的自动续费,死锁,可重入性,执行的原子性(lua脚本实现),不会释放掉别人的锁(锁的认证)
Redis实现分布式锁,具备下列优缺点:
优点:实现简单,性能好,并发能力强,如果对并发能力有要求,推荐使用
缺点:可靠性有争议,极端情况会出现锁失效问题,如果对安全要求较高,不建议使用,redis争锁是不停的去尝试的获取,而zookeeper是监听
zookeeper分布式锁,内部使用Curator Framework Recipess实现,开箱即用
注意zookeeper的版本匹配:本人是 zookeeper 3.4.10
zookeeper 3.4.x 对应curator 2.12.x系列
zookeeper 3.5.x 对应 curator4.x系列
版本不对应会出现连接异常,没有此方法之类奇怪的异常......
Zookeeper 客户端框架 Curator-Framework 来自Netflix公司,现在归Apache。
在使用ZK开发时会遇到让人头疼的几个问题,ZK连接管理、SESSION失效等一些异常问题的处理,Curator替我们解决了这些问题,通过对ZK连接状态的监控来做出相应的重连等操作,并触发事件!更好的地方是Curator对ZK的一些应用场景提供了非常好的实现,而且有很多扩充,这些都符合ZK使用规范。
其中Curator-Recipes包括有Elections(领导选举)、Locks(锁)、Queues(队列)、Barriers(屏障)、Counters(共享计数器)、Caches(状态管理,可用做配置管理、缓存等)
org.springframework.boot
spring-boot-starter-integration
org.springframework.integration
spring-integration-zookeeper
4.3.21.RELEASE
org.apache.zookeeper
zookeeper
curator-framework
org.apache.curator
org.apache.curator
curator-framework
2.12.0
zookeeper
org.apache.zookeeper
org.apache.curator
curator-recipes
2.12.0
org.apache.zookeeper
zookeeper
3.4.10
slf4j-api
org.slf4j
slf4j-log4j12
org.slf4j
log4j
log4j
配置类
@Configuration
public class ZookeeperLockConfiguration {
@Value("${zookeeper.host}")
private String zkUrl;
@Bean
public CuratorFrameworkFactoryBean curatorFrameworkFactoryBean() {
return new CuratorFrameworkFactoryBean(zkUrl);
}
@Bean
public ZookeeperLockRegistry zookeeperLockRegistry(CuratorFramework curatorFramework) {
return new ZookeeperLockRegistry(curatorFramework, "/asset-detections");
}
}
测试使用上面的controller接口
等第一个连接释放锁
第二个连接释放锁
等全部释放锁
每一个连接获取到锁并释放临时节点就会消失,并且当前最小的节点(序号)获取到锁,有兴趣可以研究上zookeeper的节点操作,自增属性,临时节点等等。
看看锁的一些特性Zookeeper是否满足:
1.互斥:因为只有一个最小节点,满足互斥特性
2.锁释放:使用Zookeeper可以有效的解决锁无法释放的问题,因为在创建锁的时候,客户端会在ZK中创建一个临时节点,一旦客户端获取到锁之后突然挂掉(Session连接断开),那么这个临时节点就会自动删除掉。其他客户端就可以再次获得锁,你可以查看这个路径下有几个临时节点来判断有几个人争锁
3.阻塞锁:使用Zookeeper可以实现阻塞的锁,客户端可以通过在ZK中创建顺序节点,并且在节点上绑定监听器,一旦节点有变化,Zookeeper会通知客户端,客户端可以检查自己创建的节点是不是
当前所有节点中序号最小的,如果是,那么自己就获取到锁,便可以执行业务逻辑了。
3.可重入:使用Zookeeper也可以有效的解决不可重入的问题,客户端在创建节点的时候,把当前客户端的主机信息和线程信息直接写入到节点中,下次想要获取锁的时候和当前最小的节点中的数据比对一下就可以了。如果和自己的信息一样,那么自己直接获取到锁,如果不一样就再创建一个临时的顺序节点,参与排队。
4.高可用:使用Zookeeper可以有效的解决单点问题,ZK是集群部署的,只要集群中有半数以上的机器存活,就可以对外提供服务。
5.高性能:Zookeeper集群是满足强一致性的,因此就会牺牲一定的性能,与Redis相比略显不足,有兴趣可以看下zookeeper的zab算法(挺有意思)
总结:
优点:使用非常简单,不用操心释放问题,强一致性,安全
缺点:性能比Redis稍差一些
Redis实现:实现比较简单,性能最高,但是可靠性难以维护
Zookeeper实现:实现最简单,可靠性最高,性能比redis略低
数据库实现:性能很差,实现复杂,并且在集群下也不安全,不推荐使用