1:setnx方式
public String redisLock() throws InterruptedException{
String lockKey = "locke_key";
String clientId = UuidUtils.generateUuid();
try{
Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.MICROSECONDS);
if (!locked){
return "已被其他线程锁定";
}
//业务逻辑扣除数据的
}finally {
String value = stringRedisTemplate.opsForValue().get(lockKey);
if (value.equals(clientId)){
stringRedisTemplate.delete(lockKey);
}
}
return "end";
}
存在的问题:
- 超时问题,已经超时,逻辑还没执行完成
- 设置vlaue的原因,防止线程3,删除线程2的锁
- 锁延长的机制,开启timer
jedis执行luna脚本:
2:Redisson方式
底层依靠Luna脚本,保证原子操作实现的分布式锁
依赖redis 的单线程保证分布式是锁\
官网: https://redisson.org/
分布式场景下的应用
分布式锁,bloom Filter
锁支持重入
使用:
导包:
org.redisson
redisson-spring-boot-starter
3.17.0
redisson方式分布式锁
public String redisLock2() throws InterruptedException{
String lockKey = "locke_key";
RLock lock = redisson.getLock(lockKey);
try{
lock.lock();
//业务逻辑扣除数据的
}finally {
lock.unlock();
}
return "end";
}
private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
if (leaseTime != -1) {
return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
}
RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e != null) {
return;
}
// lock acquired
if (ttlRemaining == null) {
//负责锁超时的续费功能
scheduleExpirationRenewal(threadId);
}
});
return ttlRemainingFuture;
}
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
internalLockLeaseTime = unit.toMillis(leaseTime);
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"return redis.call('pttl', KEYS[1]);",
Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}
private void renewExpiration() {
ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ee == null) {
return;
}
Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ent == null) {
return;
}
Long threadId = ent.getFirstThreadId();
if (threadId == null) {
return;
}
RFuture<Boolean> future = renewExpirationAsync(threadId);
future.onComplete((res, e) -> {
if (e != null) {
log.error("Can't update lock " + getName() + " expiration", e);
return;
}
if (res) {
// reschedule itself
renewExpiration();
}
});
}
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
ee.setTimeout(task);
}
实现事务的原理:Luna脚本
主节点挂了,导致的不在可用的解决方案:
换组件 使用zk, zk 强一致性CP架构
3:RedLock
解决redis的主从挂断的问题
原理是:
redlock对多个redisson节点发送lock ,只有半数加锁成功,返回true,认为加锁成功
问题:
性能较差,各个节点存在网络延迟,失败回滚的场景
public String redisLock2() throws InterruptedException{
String lockKey = "locke_key";
RLock lock1 = redisson1.getLock(lockKey);
RLock lock2 = redisson2.getLock(lockKey);
RLock lock3 = redisson3.getLock(lockKey);
RedissonRedLock redissonRedLock = new RedissonRedLock(lock1,lock2,lock3);
try{
boolean b = redissonRedLock.tryLock();
if (b){
//业务逻辑扣除数据的
}
}finally {
redissonRedLock.unlock();
}
return "end";
}
优化方案:
分段:
在不同的节点存储不同的分段