①添加dependency依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
②创建DistributeLock.java
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import javax.annotation.Resource;
import java.util.Arrays;
@Component
public class DistributeLock {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private DefaultRedisScript<Long> script;
private static final Long RELEASE_SUCCESS = 1L;
/**
* 保存如果不存在
*/
private static final String SET_IF_NOT_EXIST = "NX";
/**
* 只有存在才会替换
*/
private static final String SET_IF_EXIST = "XX";
/**
* 秒
*/
private static final String EXP_SECONGD = "EX";
/**
* 毫秒
*/
private static final String EXP_MILLISECONDS = "PX";
private static final String UNLOCK_LUA ="if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
/**
* 获取锁
* @return
*/
/*private boolean tryLock(String lockKey , String threadId , long expireTime){
if(expireTime <= 0){
return false;
}
//存入redis -- 如果用事务保障 这个返回始终为null - redis版本2.1.x
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, threadId, expireTime, TimeUnit.MILLISECONDS);
if(result == null){
return false;
}
return result;
}*/
public synchronized String lock(String lockKey, String threadId, long expireTime) {
return (String) stringRedisTemplate.execute((RedisCallback<? extends Object>) connection -> {
Object nativeConnection = connection.getNativeConnection();
//EX = seconds; PX = milliseconds
if(nativeConnection instanceof JedisCluster){
return ((JedisCluster) nativeConnection).set(lockKey, threadId, SET_IF_NOT_EXIST, EXP_MILLISECONDS, expireTime);
}else if(nativeConnection instanceof Jedis){
Byte[] args = new Byte[1024];
return ((Jedis) nativeConnection).set(lockKey, threadId, SET_IF_NOT_EXIST, EXP_MILLISECONDS, expireTime);
}
return 0L;
});
}
/**
* 释放锁
* @param lockKey
*
* @param threadId
* @return
*/
public boolean unlock(String lockKey , String threadId){
//使用lua脚本 , 先判断是否是自己设置的锁,再执行删除
Long result = stringRedisTemplate.execute(script, Arrays.asList(lockKey,threadId));
return RELEASE_SUCCESS.equals(result);
}
@Bean
public DefaultRedisScript<Long> defaultRedisScript(){
DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<>();
defaultRedisScript.setResultType(Long.class);
defaultRedisScript.setScriptText("if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end");
return defaultRedisScript;
}
}
③应用
场景:在商户提现的时候可能同时存在商店的客户确认收货后该商店的账户钱增多,因此当商户提现时需要锁定该账户信息。
serviceImpl
@Autowired
private DistributeLock distributeLock;
@Override
public Map<Integer, Object> applyExtract(ExtractDto dto) {
Map<Integer, Object> map = new HashMap<>();
UserVO userVO = userFeignService.user(dto.getUserId());
String accNo = userVO.getUsername();
QueryWrapper<StoreInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("acc_no", accNo);
StoreInfo store = storeMapper.selectOne(queryWrapper);
if (StringUtils.isEmpty(store)) {
map.put(1, false);
map.put(2, "该账户无可提现金额");
return map;
}
BigDecimal balance = store.getAccBalance();
if (balance.compareTo(dto.getExtractAmt()) < 0 || balance.compareTo(BigDecimal.ZERO) < 0) {
map.put(1, false);
map.put(2, "账号可提现余额不足");
return map;
} else {
//todo 加分布式锁
String lockKey = LOCK_PREFIX + accNo;
Long threadId = Thread.currentThread().getId();
String lockFlag = distributeLock.lock(lockKey, threadId.toString(), 60000);
if ("OK".equals(lockFlag)) {//加锁成功
//更新商户的账户余额
BigDecimal accBalance = store.getAccBalance().subtract(dto.getExtractAmt());
store.setAccBalance(accBalance);
store.setUpdateTime(new Date());
Extract extract = new Extract();
extract.setExtractNo(DateUtils.getDateNo());
extract.setStoreNo(store.getStoreNo());
extract.setAccNo(accNo);
extract.setExtractAmt(dto.getExtractAmt());
extract.setExtractState(0);
extract.setAppTime(new Date());
extractMapper.insert(extract);
storeMapper.updateById(store);
map.put(1, true);
map.put(2, "提现申请成功");
//解锁
distributeLock.unlock(lockKey, threadId.toString());
return map;
} else {
map.put(1, false);
map.put(2, "该商户正在加锁,无法操作余额");
return map;
}
}
}