南宁h5建站,做网站大概需要多少钱,企业宣传如何做网站,黑糖wordpress什么是分布式锁#xff0c;为什么需要分布式锁
在多线程并发请求当中#xff0c;为了保证我们的资源同一时刻只有一个线程进行操作#xff08;如商品超卖问题、购票系统等#xff09;#xff0c;我们通常要添加锁机制#xff0c;如ReentrantLock#xff0c;也就是可重入…什么是分布式锁为什么需要分布式锁
在多线程并发请求当中为了保证我们的资源同一时刻只有一个线程进行操作如商品超卖问题、购票系统等我们通常要添加锁机制如ReentrantLock也就是可重入的互斥锁与synchronized功能类似因为比较灵活所以经常使用。这在单机情况下是没有问题的但在多节点的情况下也就意味着有多个进程ReentrantLock锁机制可能就会不起作用所以我们需要一种能够跨进程的锁也就是同一时刻只能让一个进程获取锁来控制共享资源的访问。
分布式锁有哪些实现方式
基于数据库分布式锁悲观锁如select xxx for update、乐观锁如version版本号机制基于 Redis 实现分布式锁基于分布式协调服务 ZooKeeper 实现分布式锁 核心也是使用了每个节点都会用到的第三方组件例如mysql、redis、zookeeper Redis 实现分布式锁
使用setnx命令在 Redis 中setnx 命令是可以帮助我们实现互斥setnx 即 set if not exists (对应 Java 中的 setIfAbsent 方法)如果 key 不存在的话会设置 key 的值如果 key 已经存在 则啥也不做。
if(redisTemplate.opsForValue().setIfAbsent(key, value , time, TimeUnit)){ //加锁try {do something //业务处理}catch(){}finally {// 释放锁String delVal valueOperations.get(key).toString();if (value.equals(delVal)){redisTemplate.delete(key);}}
}通常情况下我们一般使用setnx expire来实现防止死锁但仍然会有锁被别的线程误删的问题查询删除不是一个原子操作会有并发问题 为什么会有锁被别的线程误删假如线程A和线程B都执行同一段代码进行加锁线程A加锁成功当出现业务执行时间过长超过了过期时间这时线程A释放了锁此时线程B就能加锁成功接下来执行线程B业务操作这个时候线程A业务操作执行完了在finally方法中执行delete key这个时候线程A就会把线程B的锁给释放了。 所以一般释放的锁的时候最好使用lua脚本来进行释放来实现原子性的查询比较并删除锁。
if redis.call(get,KEYS[1]) ARGV[1] then return redis.call(del,KEYS[1])
elsereturn 0
end;lua脚本的话可以保证我们执行的时候多个命令执行期间不回被其他线程打断或出现竞争状态也就是可以看作一次请求保证了我们命令的原子性。 但这个方案仍然有个缺点锁过期释放了业务还没执行完。对于可能存在锁过期释放业务没执行完的问题。我们可以稍微把锁过期时间设置长一些让其大于正常业务处理时间。如果你觉得不是很稳还可以给获得锁的线程开启一个定时守护线程每隔一段时间检查锁是否还存在存在则对锁的过期时间延长防止锁过期提前释放如每5秒查看一下锁的过期时间如果小于10秒就延期针对这种锁续约机制redission框架就帮我们解决了这个问题
基于Redisson的分布式锁的实现
首先redission使用方式也比较简单
引入依赖
dependencygroupIdorg.redisson/groupIdartifactIdredisson/artifactIdversion3.17.0/version
/dependency 写redisson的配置类
Bean
public RedissonClient redissonClient(){...// 添加redis地址// 设置锁的超时时间// 创建 RedissonClient 对象
}使用redissonClient客户端加锁
Autowired
private RedissonClient redissonClient;public void test() throws InterruptedException {RLock lock redissonClient.getLock(anyLock);boolean locked lock.tryLock(1,10,TimeUnit.SECONDS);// 参数1.获取锁的最大等待时间期间会重试2.锁自动释放时间3.时间单位if(locked){try{// 业务操作}finally{//释放锁lock.unlock();}}
}假如我们加锁没有传参数直接使用tryLock()Redisson则会设置默认的锁过期时间为30s并且如果任务超过了30s还没有执行完毕则后台会有一个线程默认没隔10s执行task重置过期时间也就是WatchDog机制 redisson看门狗自动续期源码
private T RFutureLong tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {RFutureLong ttlRemainingFuture;if (leaseTime ! -1) {ttlRemainingFuture tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);} else {ttlRemainingFuture tryLockInnerAsync(waitTime, internalLockLeaseTime,TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);}ttlRemainingFuture.onComplete((ttlRemaining, e) - {if (e ! null) {return;}// lock acquiredif (ttlRemaining null) {if (leaseTime ! -1) {internalLockLeaseTime unit.toMillis(leaseTime);} else {scheduleExpirationRenewal(threadId);}}});return ttlRemainingFuture;}tryAcquireAsync方法
如果没有设置过期时间就会执行默认的过期时间lockWatchdogTimeout 30 * 1000(ms)执行回调函数即看门狗机制
protected void scheduleExpirationRenewal(long threadId) {ExpirationEntry entry new ExpirationEntry();// 将线程放入缓存操作ExpirationEntry oldEntry EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);if (oldEntry ! null) {// 如果已经有该线程则不再延期oldEntry.addThreadId(threadId);} else {entry.addThreadId(threadId);renewExpiration();}}
//---------------------------------------------------------------
private void renewExpiration() {ExpirationEntry ee EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ee null) {// 缓存不存在则不再续约return;}Timeout task commandExecutor.getConnectionManager().newTimeout(new TimerTask() {Overridepublic 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;}// 执行续约的lua脚本RFutureBoolean future renewExpirationAsync(threadId);future.onComplete((res, e) - {if (e ! null) {log.error(Cant update lock getRawName() expiration, e);EXPIRATION_RENEWAL_MAP.remove(getEntryName());return;}if (res) {// 延期成功回调自己继续续约renewExpiration();}});}// 每隔internalLockLeaseTime/310秒检查一次}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);ee.setTimeout(task);}// -----------------------------------------------------------------------
protected RFutureBoolean renewExpirationAsync(long threadId) {return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,if (redis.call(hexists, KEYS[1], ARGV[2]) 1) then redis.call(pexpire, KEYS[1], ARGV[1]); return 1; end; return 0;,Collections.singletonList(getRawName()),internalLockLeaseTime, getLockName(threadId));}关键方法renewExpiration()
函数开启了一个定时任务在10s后执行并且会在调用成功后再次调用“自己”即续约机制可以看到Redisson也是使用Lua脚本进行锁续约的lua脚本里会进行判断锁是否存在如果存在则重置过期时间为30s
最后关于Redisson Redisson是Java的redis客户端之一提供了一些api方便操作redis。锁只是它的一个工具类其他还包括分布式对象、分布式集合等等详细可参考https://github.com/redisson/redisson/wiki/