在分布式(三)——实现分布式锁里说过可以通过redis实现分布式锁。
构建一个redis实现的分布锁demo,直观的了解分布锁。
一、引入依赖
<!--引入redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.3.8.RELEASE</version>
</dependency>
二、思路
- 获取锁的时候,通过setnx加锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断
- 获取锁的时候,用expire命令为锁添加一个超时时间,超过这个时间则放弃获取锁(防止死锁)
- 释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放
三、编码
3.1 分布锁实体
/**
* @author jc_hook
* @ClassName Lock
* @Description 分布锁实体
* @date 2022/6/28
*/
@Data
public class DistributedLock {
private String name;
private String value;
public DistributedLock(String name, String value) {
this.name = name;
this.value = value;
}
}
3.2 分布锁配置类
/**
* @author jc_hook
* @ClassName DistributedLockConfig
* @Description 分布式锁配置
* @date 2022/6/28
*/
@Slf4j
@Component
public class DistributedLockConfig {
/**
* 锁的有效时间,防止死锁
*/
private final static long LOCK_EXPIRE = 30*1000L;
/**
* 默认30ms尝试一次
*/
private final static long LOCK_TRY_INTERVAL = 30L;
/**
* 默认尝试20s
*/
private final static long LOCK_TRY_TIMEOUT = 20 * 1000L;
private RedisTemplate template;
public void setRedisTemplate(RedisTemplate redisTemplate) {
this.template = redisTemplate;
}
/**
* 尝试获取全局锁
*
* @param lock 锁的名称
* @return true 获取成功,false获取失败
*/
public boolean tryLock(DistributedLock lock) {
return getLock(lock, LOCK_TRY_TIMEOUT, LOCK_TRY_INTERVAL, LOCK_EXPIRE);
}
/**
* 尝试获取全局锁
* SETEX:可以设置超时时间
*
* @param lock 锁的名称
* @param timeout 获取超时时间 单位ms
* @return true 获取成功,false获取失败
*/
public boolean tryLock(DistributedLock lock, long timeout) {
return getLock(lock, timeout, LOCK_TRY_INTERVAL, LOCK_EXPIRE);
}
/**
* 尝试获取全局锁
*
* @param lock 锁的名称
* @param timeout 获取锁的超时时间
* @param tryInterval 多少毫秒尝试获取一次
* @return true 获取成功,false获取失败
*/
public boolean tryLock(DistributedLock lock, long timeout, long tryInterval) {
return getLock(lock, timeout, tryInterval, LOCK_EXPIRE);
}
/**
* 尝试获取全局锁
*
* @param lock 锁的名称
* @param timeout 获取锁的超时时间
* @param tryInterval 多少毫秒尝试获取一次
* @param lockExpireTime 锁的过期
* @return true 获取成功,false获取失败
*/
public boolean tryLock(DistributedLock lock, long timeout, long tryInterval, long lockExpireTime) {
return getLock(lock, timeout, tryInterval, lockExpireTime);
}
/**
* 操作redis获取全局锁
*
* @param lock 锁的名称
* @param timeout 获取的超时时间
* @param tryInterval 多少ms尝试一次
* @param lockExpireTime 获取成功后锁的过期时间
* @return true 获取成功,false获取失败
*/
public boolean getLock(DistributedLock lock, long timeout, long tryInterval, long lockExpireTime) {
try {
if (StringUtils.isEmpty(lock.getName()) || StringUtils.isEmpty(lock.getValue())) {
return false;
}
long startTime = System.currentTimeMillis();
do {
if (!template.hasKey(lock.getName())) {
ValueOperations<String, String> ops = template.opsForValue();
ops.set(lock.getName(), lock.getValue(), lockExpireTime, TimeUnit.MILLISECONDS);
return true;
} else {
//存在锁
log.debug("lock is exist!!!");
}
//尝试超过了设定值之后直接跳出循环
if (System.currentTimeMillis() - startTime > timeout) {
return false;
}
//每隔多长时间尝试获取
Thread.sleep(tryInterval);
}
while (template.hasKey(lock.getName()));
} catch (InterruptedException e) {
log.error(e.getMessage());
return false;
}
return false;
}
/**
* 获取锁
* SETNX(SET If Not Exists):当且仅当 Key 不存在时,则可以设置,否则不做任何动作。
*/
public Boolean getLockNoTime(DistributedLock lock) {
if (!StringUtils.isEmpty(lock.getName())) {
return false;
}
// setIfAbsent 底层封装命令 是 setNX()
boolean falg = template.opsForValue().setIfAbsent(lock.getName(), lock.getValue());
return false;
}
/**
* 释放锁
*/
public void releaseLock(DistributedLock lock) {
if (!StringUtils.isEmpty(lock.getName())) {
template.delete(lock.getName());
}
}
}
3.3 业务方法使用锁
@Override
@CacheEvict(cacheNames = "goods",key = "'selectList'")
public void shipment(GoodsEntity goodsEntity){
distributedLockConfig.setRedisTemplate(redisTemplate);
Long goodsId = goodsEntity.getId();
//设置分布式锁
DistributedLock lock = new DistributedLock(goodsId.toString(),goodsId.toString());
if(distributedLockConfig.tryLock(lock)){
try {
Integer buyNum = goodsEntity.getNum();
goodsEntity = getBaseMapper().selectById(goodsId);
//只更新数量
UpdateWrapper<GoodsEntity> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id",goodsId);
updateWrapper.set("num",goodsEntity.getNum()-buyNum);
update(updateWrapper);
}catch (Throwable ex){
throw new RuntimeException("购置商品失败!请联系管理员");
}
//释放锁
distributedLockConfig.releaseLock(lock);
}
}