四举措加强网站建设,vi设计应用部分有哪些,网站用什么程序做的,官方网站建设教程Redis 如何实现分布式锁1. 什么是分布式锁1.1 分布式锁的特点1.2 分布式锁的场景1.3 分布式锁的实现方式2. Redis 实现分布式锁2.1 setnx expire2.2 set ex px nx2.3 set ex px nx 校验唯一随机值#xff0c;再删除2.4 Redisson 实现分布式锁1. 什么是分布式锁
分布式锁其实…
Redis 如何实现分布式锁1. 什么是分布式锁1.1 分布式锁的特点1.2 分布式锁的场景1.3 分布式锁的实现方式2. Redis 实现分布式锁2.1 setnx expire2.2 set ex px nx2.3 set ex px nx 校验唯一随机值再删除2.4 Redisson 实现分布式锁1. 什么是分布式锁
分布式锁其实就是控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源往往需要互斥来防止彼此干扰以保证一致性。
1.1 分布式锁的特点 互斥性任意时刻只有一个客户端能持有锁 可重入性一个线程获取锁之后可以再次对其请求加锁 锁超时释放持有锁超时释放防止不必要的资源浪费也可以防止死锁 高效、高可用加锁和解锁需要高效同时也需要保证高可用防止分布式锁失效 安全性锁只能被持有的客户端删除不能被其他客户端删除。
1.2 分布式锁的场景 使用分布式锁的场景一般需要满足以下场景 系统是一个分布式系统Java 的锁已经锁不住了操作共享资源比如库里唯一的用户数据同步访问即多个进程同时操作共享资源。 分布式锁的业务场景 扣减库存抢红包…
1.3 分布式锁的实现方式 数据库乐观锁 基于 ZooKeeper 的分布式锁; 基于 Redis 的分布式锁。
这里主要介绍使用 Redis 实现分布式锁的方案。
2. Redis 实现分布式锁
2.1 setnx expire
SETNX 是 SET IF NOT EXISTS 的简写。命令格式是 SETNX key value如果 lockKey 不存在则 SETNX 成功返回 1如果这个 lockKey 已经存在了则返回 0。
伪代码
// 1. 加锁
ifjedis.setnx(lockKey, lockValue) 1{// 2. 设置过期时间jedis.expire(lockKey, expireTime);try {// 3. 业务处理do something;} catch (Exception e) {log.error(处理失败, e);} finally {// 4. 释放锁jedis.del(lockKey);}
}在这个方案中 setnx 与 expire 「不是原子操作」如果执行完第一步 jedis.setnx() 加锁后异常了第二步 jedis.expire() 未执行相当于这个锁没有过期时间「有产生死锁的可能」。正对这个问题如何改进
2.2 set ex px nx
基于 Redis 的 SET 扩展命令SET key value[EX seconds][PX milliseconds][NX|XX]保证 SETNX EXPIRE 两条指令的原子性。 EX second 设置键的过期时间为 second 秒 PX millisecond 设置键的过期时间为 millisecond 毫秒 NX 表示 key 不存在的时候才能 set 成功也即保证只有第一个客户端请求才能获得锁而其他客户端请求只能等其释放锁才能获取 XX 只在键已经存在时才对键进行设置操作。
伪代码
// 1. 加锁并设置过期时间
ifjedis.set(lockKey, lockValue, NX, EX, 100s) 1{try {// 2. 业务处理do something;} catch (Exception e) {log.error(处理失败, e);} finally {// 3. 释放锁jedis.del(lockKey);}
}在这个方案中存在两个问题 锁过期释放了业务还没执行完。假设线程a获取锁成功一直在执行临界区的代码。但是100s过去后它还没执行完。但是这时候锁已经过期了此时线程b又请求过来。显然线程b就可以获得锁成功也开始执行临界区的代码。那么问题就来了临界区的业务代码都不是严格串行执行的了。 锁被别的线程误删。假设线程a执行完后去释放锁。但是它不知道当前的锁可能是线程b持有的线程a去释放锁时有可能过期时间已经到了此时线程b进来占有了锁。那线程a就把线程b的锁释放掉了但是线程b临界区业务代码可能都还没执行完。
2.3 set ex px nx 校验唯一随机值再删除
既然锁可能被别的线程误删那我们给 value 值设置一个标记当前线程唯一的随机数在删除的时候校验一下就可以了。
伪代码 // 1. 加锁并设置过期时间ifjedis.set(lockKey, uni_lockValue, NX, EX, 100s) 1{try {// 2. 业务处理do something;} catch (Exception e) {log.error(处理失败, e);} finally {// 3. 判断是不是当前线程加的锁if (uni_lockValue.equals(jedis.get(lockKey))) {// 4. 释放锁jedis.del(lockKey);}}}这里的 3. 是非原子性的我们使用 lua 脚本来优化一下
if redis.call(get,KEYS[1]) ARGV[1] then return redis.call(del,KEYS[1])
elsereturn 0
end;这个方案还是会存在**「锁过期释放业务没执行完」**的问题有些小伙伴认为稍微把锁过期时间设置长一些就可以了。其实我们设想一下是否可以给获得锁的线程开启一个定时守护线程每隔一段时间检查锁是否还存在存在则对锁的过期时间延长防止锁过期提前释放。
2.4 Redisson 实现分布式锁 只要线程一加锁成功就会启动一个 watch dog 看门狗它是一个后台线程会每隔 10s 检查一下如果 线程1 还持有锁那么就会不断的延长锁 key 的生存时间。Redisson 完美解决了「锁过期释放业务没执行完」问题。
Redisson lock 和 tryLock 原理解析
package com.pointer.mall.common.util;import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;/*** author gaoyang* date 2023-02-23 20:24*/
Slf4j
Component
public class RedissonUtil {Resourceprivate RedissonClient redissonClient;/*** 加锁** param lockKey*/public void lock(String lockKey) {RLock lock redissonClient.getLock(lockKey);lock.lock();}/*** 带过期时间的锁** param lockKey key* param leaseTime 上锁后自动释放锁时间*/public void lock(String lockKey, long leaseTime) {RLock lock redissonClient.getLock(lockKey);lock.lock(leaseTime, TimeUnit.SECONDS);}/*** 带超时时间的锁** param lockKey key* param leaseTime 上锁后自动释放锁时间* param unit 时间单位*/public void lock(String lockKey, long leaseTime, TimeUnit unit) {RLock lock redissonClient.getLock(lockKey);lock.lock(leaseTime, unit);}/*** 尝试获取锁** param lockKey key* return*/public boolean tryLock(String lockKey) {RLock lock redissonClient.getLock(lockKey);return lock.tryLock();}/*** 尝试获取锁** param lockKey key* param waitTime 最多等待时间* param leaseTime 上锁后自动释放锁时间* return boolean*/public boolean tryLock(String lockKey, long waitTime, long leaseTime) {RLock lock redissonClient.getLock(lockKey);try {return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);} catch (InterruptedException e) {log.error(RedissonUtils - tryLock异常, e);}return false;}/*** 尝试获取锁** param lockKey key* param waitTime 最多等待时间* param leaseTime 上锁后自动释放锁时间* param unit 时间单位* return boolean*/public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) {RLock lock redissonClient.getLock(lockKey);try {return lock.tryLock(waitTime, leaseTime, unit);} catch (InterruptedException e) {log.error(RedissonUtils - tryLock异常, e);}return false;}/*** 释放锁** param lockKey key*/public void unlock(String lockKey) {RLock lock redissonClient.getLock(lockKey);lock.unlock();}/*** 是否存在锁** param lockKey key* return*/public boolean isLocked(String lockKey) {RLock lock redissonClient.getLock(lockKey);return lock.isLocked();}
}