标签:resource 简单 runtime 原子性 trace 中间 定义 etl asp
Redis分布式锁
1、锁场景
任务通过竞争获取锁才能才能对该资源进行操作(竞争锁)
当有一个任务对资源进行操作时(占有锁)
其他任务不能对该资源进行操作(任务阻塞)
直到该任务操作结束(释放锁)
竞争锁 -> 占有锁 -> 任务阻塞 -> 释放锁
graph LR
A(竞争锁) -->B(占有锁)
B(占有锁) --> C[任务阻塞]
C[任务阻塞] --> D[释放锁]
2、实现思路
加锁:
通过setnx向特定的key写入一个随机值,并设置失效时间,写值成功即加锁成功
1、必须给锁设置失效时间:避免死锁
2、加锁时,每个节点产生一个随机字符串:避免误删锁
3、写入随机值与设置失效时间同一命令:保证原子性
解锁:
匹配随机值,删除redis上特定的key数据,保证获取数据,判断一致性,以及删除数据也是原子性
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
3、代码实现
1、RedisTemplate实现
基于RedisTemplate简单封装的RedisHelper,这不是重点
3、很多教程解锁用的lua脚本。我感觉一句读操作一句写操作不会产生不一致的问题,就直接解锁了。
@Component
@Scope("prototype")
public class RedisLock implements Lock {
/**
* 锁的key
*/
@Setter
private String key;
private String value;
@Setter
private long seconds = 10;
@Resource
private RedisHelper redisHelper;
/**
* 阻塞锁
*/
@Override
public void lock() {
while (!tryLock()) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public boolean tryLock() {
String value = UUID.randomUUID().toString();
Boolean b = redisHelper.setNx(this.key, value, this.seconds, TimeUnit.SECONDS);
if (b) {
this.value = value;
}
return b;
}
@Override
@SneakyThrows
public boolean tryLock(long time, @NonNull TimeUnit unit) {
String value = UUID.randomUUID().toString();
Boolean b = redisHelper.setNx(this.key, value, time, unit);
if (b) {
this.value = value;
}
return b;
}
@Override
public void unlock() {
String value = redisHelper.get(key);
if (value.equals(this.value)) {
redisHelper.del(key);
}
}
@Override
@NonNull
public Condition newCondition() {
return null;
}
}
使用:
@Resource
private RedisLock redisLock;
set store 100
*/
@GetMapping("/getStore")
public R
int i;
// 阻塞加锁
redisLock.setKey("store-lock");
redisLock.lock();
try {
String store = redisHelper.get("store");
if ("0".equals(store)) {
return R.error("库存不足");
}
i = Integer.parseInt(store) - 1;
log.info("剩余库存数量:{}", i);
redisHelper.set("store", String.valueOf(i));
} finally {
// 释放锁
redisLock.unlock();
}
return R.ok(i);
}
2、Redisson实现
自动续约,不用自己开线程检查
依赖
配置
/**使用
@Resource
private Redisson redisson;
@GetMapping("/getStore1")
public R
String lockKey = "redisLockKey";
RLock lock = redisson.getLock(lockKey);
lock.lock();
int i;
// 阻塞加锁
try {
String store = redisHelper.get("store1");
if ("0".equals(store)) {
return R.error("库存不足");
}
i = Integer.parseInt(store) - 1;
log.info("剩余库存数量:{}", i);
redisHelper.set("store1", String.valueOf(i));
} finally {
// 释放锁
lock.unlock();
}
return R.ok(i);
}
3、封装功能
定义注解(可重入锁注解,其他类型略)
/**@description 可重入锁注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TryLock {
}
定义注解处理器
@Slf4j
@Aspect
@Component
@AllArgsConstructor
public class LockHandler {
private Redisson redisson;
/**
* 可重入锁处理
*/
@SneakyThrows
@Around("@annotation(tryLock)")
public Object lockAroundAction(ProceedingJoinPoint proceeding, TryLock tryLock) {
String key = tryLock.key();
long expire = tryLock.expire();
TimeUnit timeUnit = tryLock.timeUnit();
RLock lock = redisson.getLock(key);
lock.lock(expire, timeUnit);
try {
return proceeding.proceed();
} finally {
lock.unlock();
}
}
}
使用
// 注解并指定key
@TryLock(key = "store2_lock")
@GetMapping("/getStore2")
public R
int i;
// 阻塞加锁
String store = redisHelper.get("store2");
if ("0".equals(store)) {
String msg = "库存不足";
log.info(msg);
return R.error(msg);
}
i = Integer.parseInt(store) - 1;
log.info("剩余库存数量:{}", i);
redisHelper.set("store2", String.valueOf(i));
return R.ok(i);
}
标签:resource 简单 runtime 原子性 trace 中间 定义 etl asp
原文地址:https://www.cnblogs.com/zuier/p/11029424.html