四种分布式锁
redis原生分布式锁。
/**
没有对于锁失效和锁续命进行解决,需要的可以根据下面的说明和思路,自行解决。
*/
@Override
public boolean deduckStore(Long goodId, Integer num) {
String key = "goods:deduck:" + goodId;
//setIfAbsent 对应redis原生的setnx
//锁失效
//锁续命 ttl 30 10 30
boolean lock = redisTemplate.opsForValue().setIfAbsent(key,goodId,30L, TimeUnit.SECONDS);
while (lock == false){
//自旋,等待再次获取锁。
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock = redisTemplate.opsForValue().setIfAbsent(key,goodId,30L, TimeUnit.SECONDS);
}
try {
//45s
Goods goods = this.baseMapper.selectById(goodId);
if(goods.getStokeNum() >= num){//判断是否可以进去扣减库存
goods.setStokeNum(goods.getStokeNum() - num);
this.updateById(goods);
}
}finally {
redisTemplate.delete(key);
}
return true;
}
redis加锁:
使用setnx保证数据不会更改的原理,当key值相同时,如果线程A写入数据后,其他线程在继续对该key写入数据时就会失败,只有等该key删除或失效后,其他线程才能写入。这样就保证了同一时刻只有一个线程获取到锁。
redis死锁:
当线程A得到锁后,线程A所在的服务器宕机,或者线程A运行出错,没有释放分布式锁,此时,其他服务器上的线程就会进入无限等待状态,导致死锁。解决方法,在使用setnx写入锁时指定锁(key)的失效时间。在到达失效时间后redis自动清除锁。其他的服务器就能正常写入锁执行业务。
锁失效:
由于服务器的性能和业务的复杂程度不同,我们不能明确执行业务的具体时间,所以当失效时间过短时,会出现锁失效问题。假设线程A拿到锁后由于所在的服务器性能低,执行业务需要40s,而锁的失效时间指定的是30秒。当线程A还在执行业务时,锁失效了,此时线程B就能加锁成功,进性业务的执行,而10秒后线程A业务执行完毕,释放锁时,删除的是线程B写入的锁,又导致线程C可以加锁成功。依次循环往复,从而产生锁失效的问题。解决方法:进行锁续命,在业务还未执行完,但是锁快要失效时,重新设置锁的失效时间。
锁续命:
在锁还没有用完,但是又快要过期时,重置锁的过期时间。可以对一个已经带有生存时间的 key
执行 EXPIRE 命令,新指定的生存时间会取代旧的生存时间。例如锁的过期时间是30秒,可以通过TTL监听锁的剩余生存时间,当还剩10秒时,使用EXPIRE 命令将锁的生存时间重新设置为30秒。从而达到锁续命的目的。当线程死掉或者所在的服务器宕机时,不会执行锁续命的代码,只有代码正常运行时才会定时执行锁续命代码,所以不会出现死锁现象。
redisson框架分布式锁。
使用步骤
1.引入redisson框架
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.5</version>
</dependency>
2.配置类
@Configuration
public class RedissonConfig {
@Resource
private RedisProperties redisProperties;
@Bean
public RedissonClient redissonClient(){
RedissonClient redissonClient;
Config config = new Config();
String url = "redis://"+redisProperties.getHost()+":"+redisProperties.getPort();
//单机redis,如果是redis集群使用config.useClusterServers().addNodeAddress(...)
config.useSingleServer().setAddress(url)
.setPassword(null)
.setDatabase(redisProperties.getDatabase());
try{
redissonClient = Redisson.create(config);
return redissonClient;
}catch(Exception e){
e.printStackTrace();
return null;
}
}
}
3.分布式锁实现
@Resource
private RedissonClient redissonClient;
/**
* redisson框架实现分布式锁
* */
@Override
public boolean deduckStock(Long goodsId, Integer num) {
try {
//获取锁
RLock lock = redissonClient.getLock("/myLock");
//设置超时时间
lock.lock(10L,TimeUnit.SECONDS);
//获取锁成功后开始执行业务
Goods goods = this.getById(goodsId);
if(goods == null){
return false;
}
if(goods.getStock() >= num){
goods.setStock(goods.getStock() - num);
}else{
throw new RuntimeException("商品库存不足!");
}
return this.updateById(goods);
} catch (Exception e) {
e.printStackTrace();
}finally {
//释放锁资源
lock.unlock();
}
return false;
}
spring integration redis
官网参考:https://docs.spring.io/spring-integration/docs/current/reference/html/redis.html#redis-lock-registry
原理图:
使用步骤
1.引入依赖
<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.配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.integration.redis.util.RedisLockRegistry;
@Configuration
public class RedisLockConfig {
@Bean
public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) {
//registryKey全局分布式锁key前缀
return new RedisLockRegistry(redisConnectionFactory, "mylock");
}
}
3.分布式锁实现
@Resource
private RedisLockRegistry redisLockRegistry;
/**
* spring integration redis分布式锁实现
* */
@Override
public boolean deduckStock(Long goodsId, Integer num) {
Lock lock = redisLockRegistry.obtain("lock:" + goodsId);
try {
boolean hasLock = lock.tryLock(10, TimeUnit.SECONDS);//尝试获取锁
if(hasLock){
//获取锁成功后开始执行业务
Goods goods = this.getById(goodsId);
if(goods == null){
return false;
}
if(goods.getStock() >= num){
goods.setStock(goods.getStock() - num);
}else{
throw new RuntimeException("商品库存不足!");
}
return this.updateById(goods);
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放锁
lock.unlock();
}
return false;
}
redis集群做分布式锁的问题:数据一致性问题。单个redis不存在这种问题。当线程A在主redis中写入锁后,主redis还未来得及将数据同步到从redis中,线程B在从redis中写入锁会成功,因为从redis中还没有最新数据,这样就会因为数据不一致导致的锁失效问题。但是这种情况很极端,如果对业务的数据要求不是很严格,可以忽略该情况。
curator
curator官网地址:http://curator.apache.org/
原理图:
使用步骤
1.引入curator框架
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.1.0</version>
</dependency>
2.配置类
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CuratorLockConfig {
@Bean(initMethod = "start")
public CuratorFramework curatorFramework(){
//重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
//connectString使用逗号分隔如: localhost:2181,localhost:2182
CuratorFramework client = CuratorFrameworkFactory.newClient("localhost:2181", retryPolicy);
return client;
}
}
3.分布式锁实现
@Resource
private CuratorFramework curatorFramework;
/**
* curator框架实现分布式锁
* */
@Override
public boolean deduckStock(Long goodsId, Integer num) {
InterProcessMutex lock = new InterProcessMutex(curatorFramework,"/mylock");
try {
//获取锁
boolean hasLock = lock.acquire(10, TimeUnit.SECONDS);
if(hasLock){
//获取锁成功后开始执行业务
Goods goods = this.getById(goodsId);
if(goods == null){
return false;
}
if(goods.getStock() >= num){
goods.setStock(goods.getStock() - num);
}else{
throw new RuntimeException("商品库存不足!");
}
return this.updateById(goods);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
//释放锁资源
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
curator集群做分布式锁:没有数据一致性问题,因为curator具有数据强一致性。当有节点建立时,集群中大多数的zookeeper成功写入节点后才会返回建立成功。这样当领导zookeeper掉线时,会暂停服务,等新的领导选举成功后才会继续服务,新的领导具有最新的数据。
如何选择分布式锁
基于redis的分布式锁在redis集群架构时会有数据一致性问题,但是该问题比较极端,不易发生,如果业务的严谨性不是很高,有一定的容错,可以优先选择使用spring integration redis作为分布式锁。redis原生太麻烦,redisson框架比较重。
基于zookeeper的curator由于强一致性的特点,不会有数据一致性问题,但是强一致性也带来,性能不如redis,所以,如果业务的容错率很低,但是性能的要求不是很高,可以选择curator作为分布式锁。