为什么使用分布式锁
web应用中防止特别事务并发,或者需要保证业务操作顺序 在事务中,redis锁和数据库读写共同作用,达到线程安全 例如事务切面下
事务开始
redis.lock
select * from a
update a
redis.unlock
事务提交
那么可能就会发生事务A先占有锁并执行,执行完毕后释放锁,但是未提交,此时事务B也开始执行了,但是注意由于事务A未提交,a上的写锁未解锁,B事务依然卡在select * from a,因此依然达到了串行的效果
最简易锁
public boolean lock(String lockPrefix, String orderNo, int expireMillionSeconds) {
String realKey = "RedisDistribLock_" + lockPrefix + "_" + orderNo;
boolean res = this.redisService.setForLock(realKey, expireMillionSeconds);
if (res) {
this.logger.warn("====> 分布式锁 " + realKey + " 获取成功");
} else {
this.logger.warn("====> 分布式锁 " + realKey + " 获取失败");
}
return res;
}
public boolean unLock(String lockPrefix, String orderNo) {
String realKey = "RedisDistribLock_" + lockPrefix + "_" + orderNo;
boolean res = this.redisService.deleteByKey(realKey);
if (res) {
this.logger.warn("====> 分布式锁 " + realKey + " 删除成功");
} else {
this.logger.warn("====> 分布式锁 " + realKey + " 删除失败");
}
return res;
}
setForLock
public <T> boolean setForLock(String key, int expireMillionSeconds) {
Jedis jedis = null;
boolean var6;
try {
jedis = (Jedis)this.jedisPool.getResource();
jedis.select(this.redisConfig.getSelectdb());
SetParams setParams = new SetParams();
setParams.nx();
setParams.px((long)expireMillionSeconds);
String result = jedis.set(key, "1", setParams);
if (result != null && result.equals("OK")) {
var6 = true;
return var6;
}
this.logger.info("=====> 分布式锁获取失败 key:" + key);
var6 = false;
} finally {
this.returnToPool(jedis);
}
return var6;
}
![image.png](https://img-blog.csdnimg.cn/img_convert/df05518d06764070436e840ea540a13d.png#clientId=ua202e097-c8aa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=255&id=u496c04ef&margin=[object Object]&name=image.png&originHeight=382&originWidth=1368&originalType=binary&ratio=1&rotation=0&showTitle=false&size=94860&status=done&style=none&taskId=u0881cbce-1f8e-464f-a581-a8dffd7e41d&title=&width=912)deleteByKey
public boolean deleteByKey(String key) {
Jedis jedis = null;
boolean var5;
try {
jedis = (Jedis)this.jedisPool.getResource();
jedis.select(this.redisConfig.getSelectdb());
long ret = jedis.del(key);
var5 = ret > 0L;
} finally {
this.returnToPool(jedis);
}
return var5;
}
这是最简单的redis分布式锁,简单的设置一个值,和过期时间占据锁,结算直接删除key,此种锁无阻塞
进阶redis锁
package com.xishan.store.item.server.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
@Component
public class RedisLock {
@Autowired
private RedisTemplate redisTemplate;
private static final Long SUCCESS = 1L;
@Value("${lock.timeout:10000}")
private long timeout;
public Boolean tryLock(String key, String value, long expireTime) {
Long start = System.currentTimeMillis();
try{
for(;;){
Boolean ret = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.MILLISECONDS);
if(ret){
return true;
}
long end = System.currentTimeMillis() - start;
if (end>=timeout) {
return false;
}
}
}finally {
}
}
public Boolean unlock(String key, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Object result = redisTemplate.execute(redisScript, Collections.singletonList(key),value);
if(SUCCESS.equals(result)) {
return true;
}
return false;
}
}
这种方式可以设置加锁失败的循环等待时间,并且可以多设置一个value,相当于多了一个参数,那么我们可以利用这个参数标识(例如UUID)加锁线程,这样就做到了 一个线程加的锁只有它自己能解锁。
可重入redis锁
@Slf4j
@Component
public class RedisDistributedLockImpl implements IRedisDistributedLock {
public static final String PREFIX = "Lock:";
private ThreadLocal<String> threadLocal = new ThreadLocal<>();
private static final Charset UTF8 = Charset.forName("UTF-8");
private static final String UNLOCK_LUA;
static {
StringBuilder sb = new StringBuilder();
sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
sb.append("then ");
sb.append(" return redis.call(\"del\",KEYS[1]) ");
sb.append("else ");
sb.append(" return 0 ");
sb.append("end ");
UNLOCK_LUA = sb.toString();
}
@Autowired
private RedisTemplate redisTemplate;
@Override
public boolean lock(String key, long requireTimeOut, long lockTimeOut) {
String originValue = threadLocal.get();
if (!StringUtils.isBlank(originValue) && isReentrantLock(key, originValue)) {
return true;
}
String value = UUID.randomUUID().toString();
long end = System.currentTimeMillis() + requireTimeOut;
try {
while (System.currentTimeMillis() < end) {
if (setNX(wrapLockKey(key), value, lockTimeOut)) {
threadLocal.set(value);
return true;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
private boolean setNX(String key, String value, long expire) {
List<String> keyList = new ArrayList<>();
keyList.add(key);
return (boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> {
Boolean result = connection
.set(key.getBytes(UTF8),
value.getBytes(UTF8),
Expiration.milliseconds(expire),
RedisStringCommands.SetOption.SET_IF_ABSENT);
return result;
});
}
private boolean isReentrantLock(String key, String originValue) {
String v = (String) redisTemplate.opsForValue().get(key);
return v != null && originValue.equals(v);
}
@Override
public boolean release(String key) {
String originValue = threadLocal.get();
if (StringUtils.isBlank(originValue)) {
return false;
}
return (boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> {
return connection
.eval(UNLOCK_LUA.getBytes(UTF8), ReturnType.BOOLEAN, 1, wrapLockKey(key).getBytes(UTF8),
originValue.getBytes(UTF8));
});
}
private String wrapLockKey(String key) {
return PREFIX + key;
}
}
threadLocal中存放线程生成的UUID 存为value 加锁,解锁都需要尝试取出这个value。如果和key不匹配都会加锁,或者解锁
|