版权声明:欢迎转载,请标明出处,如有问题,欢迎指正!谢谢!微信:w1186355422 https://blog.csdn.net/weixin_39800144/article/details/84951803
本文讲述,如何使用redis来实现分布式锁。这种实现方式,满足了分布式锁系列–01分布式锁入门介绍一文中,分布式锁约束的前三条:互斥性,安全性,对称性。因为是单机版本,所有无法满足第四条。自己编码来实第四点,是比较麻烦的,后面会介绍如何使用开源的Redisson框架来实现分布式锁。
实现原理
有一个redis服务实例,在分布式系统中,所有需要获取锁的客户端,都需要访问这个redis实例:
如果锁不存在,则写入key-value格式的数据,并设定过期时间,这个value,是为了保证解锁时,不会误解别人的锁,过期时间,是为了保证,万一加锁后客户端挂掉,解锁失败,当过期时间到了,redis会自动删除该锁,防止死锁。
如果锁已经存在,则说明已经有其它客户端持有该锁,可等待其释放(key-value 被主动删除或者因过期而被动删除)再尝试获取锁。
释放锁时,删除该key-value即可,需要确保删除的是自己加的锁,且使用watch机制和事务,来确保删除锁操作的原子性。
本文主要分为以下几个步骤实现:
- 1.pom.xml引入依赖
- 2.JedisManager管理JedisPool
- 3.RedisDistributedLock分布式锁工具类
- 4.测试代码
1.pom.xml引入依赖
<!-- redis -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
2.JedisManager管理JedisPool
自定义JedisManager类,用于管理jedisPool,配置参数可以根据自己需要做修改,这个连接池的管理和配置,可以自己实现,这里只是一个参考:
public class JedisManager {
private static final Logger logger = LoggerFactory.getLogger(ZookeeperClient.class);
private static final String REDIS_HOST = "xx.xx.xx.xx";
private static final Integer REDIS_PORT = 6379;
private static JedisPool jedisPool;
private static JedisPoolConfig config = new JedisPoolConfig();
private static void init(){
config.setMaxTotal(1000);
config.setMaxIdle(10);
config.setMaxWaitMillis(10*1000);
config.setTestOnBorrow(true);//borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
config.setTestOnReturn(true);//return一个jedis实例给pool时,是否检查连接可用,设置为true时,返回的对象如果验证失败,将会被销毁,否则返回
}
public static JedisPool getJedisPool(){
if(null == jedisPool){
synchronized (JedisPool.class){
if(null == jedisPool){
init();
jedisPool = new JedisPool(config, REDIS_HOST, REDIS_PORT, 3000,"password");
logger.info("【Redis lock】jedisPool初始化成功......");
return jedisPool;
}
}
}
return jedisPool;
}
}
3.RedisDistributedLock分布式锁工具类
public class RedisDistributedLock {
private static final Logger logger = LoggerFactory.getLogger(ZookeeperClient.class);
private static final String LOCK_SUCCESS = "OK";
private static final Integer RELEASE_SUCCESS = 1;
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
private static JedisPool jedisPool = JedisManager.getJedisPool();
/**
* 加锁
*
* @param lockName 锁名,对应被争用的共享资源
* @param randomValue 随机值,需要保持全局唯一,便于释放时校验锁的持有者
* @param expireTime 过期时间,到期后自动释放,防止出现问题时死锁,资源无法释放
* @return
*/
public static boolean acquireLock(String lockName,String randomValue,int expireTime){
Jedis jedis = jedisPool.getResource();
try {
while (true){
String result = jedis
.set(lockName, randomValue, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if(LOCK_SUCCESS.equals(result)){
logger.info("【Redis lock】success to acquire lock for [ "+lockName+" ],expire time:"+expireTime+"ms");
return true;
}
}
}catch (Exception ex){
ex.printStackTrace();
}finally {
if(null != jedis){
jedis.close();
}
}
logger.info("【Redis lock】failed to acquire lock for [ "+lockName+" ]");
return false;
}
/**
* redis释放锁
* watch和muti命令保证释放时的对等性,防止误解锁
*
* @param lockName 锁名,对应被争用的共享资源
* @param randomValue 随机值,需要保持全局唯一,以检验锁的持有者
* @return 是否释放成功
*/
public static boolean releaseLock(String lockName,String randomValue){
Jedis jedis = jedisPool.getResource();
try{
jedis.watch(lockName);//watch监控
if(randomValue.equals(jedis.get(lockName))){
Transaction multi = jedis.multi();//开启事务
multi.del(lockName);//添加操作到事务
List<Object> exec = multi.exec();//执行事务
if(RELEASE_SUCCESS.equals(exec.size())){
logger.info("【Redis lock】success to release lock for [ "+lockName+" ]");
return true;
}
}
}catch (Exception ex){
logger.info("【Redis lock】failed to release lock for [ "+lockName+" ]");
ex.printStackTrace();
}finally {
if(null != jedis){
jedis.unwatch();
jedis.close();
}
}
return false;
}
}
4.测试代码
/***
* 测试redis分布式锁
* @param id
* @param stock
*/
@Override
public void updateStockById1(Integer id, Integer stock) {
System.out.println("---------->"+Thread.currentThread().getName());
String randomValue = id+ UUID.randomUUID().toString();//随机值,确保全局唯一
RedisDistributedLock.acquireLock(id.toString(),randomValue,5*1000);//加锁
//业务逻辑
ProductStock product = productStockDao.getById(id);
Integer stock1 = product.getStock();
stock1 = stock1+stock;
productStockDao.updateStockById(id,stock1);
RedisDistributedLock.releaseLock(id.toString(),randomValue);//释放锁
}
测试的方式是,我们有一个产品库存,调用接口时,会去更新库存+1,如果没有加锁,并发情况下,库存会和逾期值差距很大,这里可以用jmter模拟并发来做测试。(也可以启两个服务,做个负载均衡,这样模拟更加真实,后面会把这种测试方式的代码上传到github)