项目场景:
分布式环境下,基于java单机的syscronized或者lock相关的锁不能生效。
解决方案
-
redis分布式锁(基于setnx封装,或者使用redisson实现)(并发比zk高)
加锁: set k v nx ex [time-1s] + watch dog
释放锁:delete k
死锁情况:
- 加锁且没有释放锁(需要加释放锁操作,如delete key)
- 加锁后,程序还没有执行到释放锁操作代码,程序已经挂掉。(加锁时设置过期机制) -
基于zookeeper,顺序临时节点
-
基于数据库主键或者唯一索引实现
具体实现
redis分布式锁
基于jedis操作setnx ex命令
ps.代码不完善,仅供参考
代码:
/**
* 添加string数据并加锁
*
* @param key
* @param value
*/
public static String stringSetNxEx(String key, String value,int expireTime) {
Jedis jedis = null;
boolean isBroken = false;
String lastVal = null;
try {
jedis = getJedis();
jedis.select(0);
jedis.set(key,value,"NX","EX",expireTime);
lastVal = jedis.get(key);
} catch (Exception e) {
e.printStackTrace();
isBroken = true;
} finally {
closeResource(jedis, isBroken);
}
return lastVal;
}
测试代码:
public static void main(String[] args) throws InterruptedException {
String key = "businness:id:lock";
String value = String.valueOf(RandomUtils.nextInt());
// 加锁
String valueInRedis = stringSetNxEx(key,value,60);
// 判断是否是当前线程加锁
System.out.println(valueInRedis);
if(valueInRedis.equals(value)){
// todo 业务逻辑
Thread.sleep(1000);
// 释放锁
delKey(key);
System.out.println("已释放锁");
}else{
System.out.println("加锁失败");
}
}
运行结果:
- redis未加锁情况:
- 已加锁情况(手动操作redis加锁)
基于redisson
redisson中提供了丰富的api 比如readlock writelock redlock等,这里只用简单的lock尝试
1.引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.16.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.添加配置bean
import org.redisson.Redisson;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissionConfig {
@Bean
public Redisson redisson(){
// 单机模式配置
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(1);
return (Redisson)Redisson.create(config);
}
}
3.编写业务控制器demo
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@RestController
@Slf4j
public class RedissonController {
@Autowired
private Redisson redisson;
@Resource
private StringRedisTemplate template;
@GetMapping(value = "/lock/demo")
public String demo(){
// 0.标识号
String clientID = UUID.randomUUID().toString();
// 1.这个相当于一把锁,控制只能一个人来
String lockKey = "bussiness:count:lock";
// 创建锁对象
RLock lock = redisson.getLock(lockKey);
try {
lock.lock(30, TimeUnit.SECONDS);
log.info("redis 加锁");
// 获取redis中的数量 并扣减
synchronized(this){
int count = Integer.parseInt(template.opsForValue().get("count"));
if (count > 0){
int realCount = count - 1;
template.opsForValue().set("count", realCount + "");
log.info("count扣减成功,剩余 {}",realCount);
}else {
log.error("count扣减失败,数量不足",count);
}
}
}finally {
// 释放锁
lock.unlock();
log.info("redis 释放锁");
}
return null;
}
}
运行结果:
1.redis中设置count数量
2.调用api
http://localhost:8080/lock/demo
3.超出count数量调用结果
4.添加延时,模拟业务代码,查看锁的key
zookeeper实现分布式锁
TODO
Mysql实现分布式锁
TODO