首页 > 数据库 >手搓Lock注解,一举解决redisson分布式锁

手搓Lock注解,一举解决redisson分布式锁

时间:2024-07-29 21:08:31浏览次数:13  
标签:redisson log keys lock lockKeys key Lock new 分布式

最近闲来时间,写了个redis-lock-stater,在pom中引入即可,直接在所要加锁的函数中使用@lock注解即可
1、首先需要了解什么是分布式锁
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作,若不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,这个时候,便需要使用到分布式锁。
分布式锁应具备的条件
在分布式系统环境下,一段代码在同一时间只能被一个机器的一个线程执行
高可用的获取锁与释放锁
高性能的获取锁与释放锁
具备可重入特性(一个线程多次获取同一把锁)
具备锁失效机制,即自动解锁,防止死锁
具备非阻塞特性,即没有获取到锁将直接返回获取锁失败
常用的Redis分布式锁
Redis实现分布式锁的核心便在于SETNX命令,它是SET if Not eXists的缩写,如果键不存在,则将键设置为给定值,在这种情况下,它等于SET;当键已存在时,不执行任何操作;成功时返回1,失败返回0
不过setnx作为锁有一定的缺陷,例如宕机后无法实现对锁的释放,因此会在SET key value [EX seconds] [PX milliseconds] [NX|XX]加入生存时间,不过这种方法也会引起当锁自动释放后,加锁处理逻辑未执行完的问题。
由于引入了看门狗机制
`@Api(tags = "Redis")
@RestController
@RequestMapping("/testRedis")
@Slf4j
public class TestRedisController {

private static final ThreadFactory THREAD_FACTORY = new ThreadFactoryBuilder().setNamePrefix("shouhu-").setDaemon(true).build();
private static final ScheduledExecutorService daemonPool = Executors.newScheduledThreadPool(5,THREAD_FACTORY);

@Resource
private RedisTemplate<String ,Object> redisTemplate;

@GetMapping("/testSetNX")
@ApiOperation("SETNX")
public ResultVO<Object> testSetNX(@RequestParam Long goodsId){
	String key = "lock_" + goodsId;
	String value = UUID.randomUUID().toString();
	ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
	ScheduledFuture<?> scheduledFuture = null;
	try {
		// 加锁
		Boolean ifAbsent = valueOperations.setIfAbsent(key, value, 30, TimeUnit.SECONDS);
		log.info("加锁{}返回值:{}",key,ifAbsent);
		if ((null==ifAbsent) || (!ifAbsent)){
			log.info("加锁失败,请稍后重试!");
			return ResultUtils.error("加锁失败,请稍后重试!");
		}
		// 模拟看门狗逻辑
		AtomicInteger count = new AtomicInteger(1);
		scheduledFuture = daemonPool.scheduleWithFixedDelay(() -> {
			log.info("看门狗第:{}次执行开始", count.get());
			Object cache = redisTemplate.opsForValue().get(key);
			if (Objects.nonNull(cache) && (value.equals(cache.toString()))) {
				// 重新设置有效时间为30秒
				redisTemplate.expire(key, 30, TimeUnit.SECONDS);
				log.info("看门狗第:{}次执行结束,有效时间为:{}", count.get(), redisTemplate.getExpire(key));
			}else {
				log.info("看门狗执行第:{}次异常:key:{} 期望值:{} 实际值:{}",count.get(), key, value, cache);
			}
			count.incrementAndGet();
		}, 10, 10, TimeUnit.SECONDS);
		// 执行业务逻辑
		TimeUnit.SECONDS.sleep(5);
		log.info("业务逻辑执行结束");
	}catch (Exception e){
		log.error("testSetNX exception:",e);
		return ResultUtils.sysError();
	}finally {
		// 释放锁,判断是否是当前线程加的锁
		String delVal = valueOperations.get(key).toString();
		if (value.equals(delVal)){
			Boolean delete = redisTemplate.delete(key);
			log.info("释放{}锁结果:{}",key,delete);
			// 关闭看门狗线程
			if (Objects.nonNull(scheduledFuture)){
				boolean cancel = scheduledFuture.cancel(true);
				log.info("关闭看门狗结果:{}",cancel);
			}
		}else {
			log.info("不予释放,key:{} value:{} delVal:{}",key,value,delVal);
		}
	}
	return ResultUtils.success("success");
}

}`

2、Redisson是什么
Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid),是一款具有诸多高性能功能的综合类开源中间件,提供的功能特性及其在项目中所起的作用远大于原生Redis所提供的各种功能,让开发者对Redis的关注进行分离,可以将更多的精力放在处理业务逻辑上。
什么是红锁?
可以使用红锁来解决主从架构锁失效问题:就是说在主从架构系统中,线程A从master中获取到分布式锁,数据还未同步到slave中时master就挂掉了,slave成为新的master,其它线程从新的master获取锁也成功了,就会出现并发安全问题
红锁算法:
1)应用程序获取系统当前时间,毫秒级
2)应用程序使用相同的key、value值依次从多个Redis实例中获取锁,如果某一个节点超过一定时间仍然没有获取到锁则直接放弃,尽快尝试从下一个Redis节点获取锁,以避免被宕机的节点阻塞。
3)计算获取锁的消耗时间=客户端程序当前时间-step1中的时间,获取锁的消耗时间小于总的锁定时间(例如30s)并且半数以上节点(假如有5个节点,则至少有3个节点)获取锁成功,才认为获取锁成功
4)计算剩余锁定时间=总的锁定时间-step3中的消耗时间
// 用于Redis集群架构下,这些节点是完全独立的,所以不使用复制或任何其他隐式协调系统 // 该对象可以用来将多个RLock对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例 @GetMapping("/testRedLock") @ApiOperation("红锁") public ResultVO<Object> testRedLock(@RequestParam Long id) { String threadName = Thread.currentThread().getName(); RLock one = redissonClient.getLock("one_" + id); RLock two = redissonClient.getLock("two_" + id); RLock three = redissonClient.getLock("three_" + id); RedissonMultiLock redLock = new RedissonRedLock(one, two, three); try { redLock.lock(); log.info("{}:获得锁,开始执行业务", threadName); TimeUnit.SECONDS.sleep(2); log.info("{}:执行结束", threadName); return ResultUtils.success(); } catch (Exception e) { log.error("testRedLock exception:", e); return ResultUtils.sysError(); } finally { // 注意:不能使用isLocked()和isHeldByCurrentThread()方法,会抛出UnsupportedOperationException异常 redLock.unlock(); log.info("{}:释放锁成功", threadName); } }

3、使用AOP对Redisson进行封装

其中lock注解为
`@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lock {

/**
 * 锁的模式:默认情况下,当参数有多个使用 MULTIPLE 否则使用公平锁
 *
 * @return the lock model
 */
LockModel lockModel() default LockModel.AUTO;

/** @return [] string [ ] */
String[] keys();

/**
 * key的静态常量:当key的spel的值是LIST,数组时使用+号连接将会被spel认为这个变量是个字符串,只能产生一把锁,达不到我们的目的,<br>
 * 而我们如果又需要一个常量的话.这个参数将会在拼接在每个元素的后面
 *
 * @return String string
 */
String keyConstant() default "";

/**
 * 锁超时时间,默认使用配置文件全局设置
 *
 * @return long long
 */
long lockTime() default 0;

/**
 * 等待加锁超时时间,默认使用配置文件全局设置 -1 则表示一直等待
 *
 * @return long long
 */
long waitTime() default 0;

}核心代码为@Around(value = "lockAspect(lock)", argNames = "proceedingJoinPoint,lock")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint, Lock lock) throws Throwable {
//keys为lock注解的keys
String[] keys = lock.keys();
if (keys.length == 0) {
throw new LockException("the lock keys are required");
}
//获取被注解方法的参数名和参数值
String[] parameterNames = new LocalVariableTableParameterNameDiscoverer()
.getParameterNames(((MethodSignature) proceedingJoinPoint.getSignature()).getMethod());
Object[] args = proceedingJoinPoint.getArgs();

    long waitTime = lock.waitTime();
    if (waitTime == 0) {
        waitTime = lockProperties.getWaitTime();
    }
    long lockTime = lock.lockTime();
    if (lockTime == 0) {
        lockTime = lockProperties.getLockTime();
    }
    LockModel lockModel = lock.lockModel();
    if (lockModel == LockModel.AUTO) {
        if (keys.length > 1) {
            lockModel = LockModel.RED_LOCK;
        } else {
            lockModel = LockModel.FAIR;
        }
    }
    if (lockModel != LockModel.MULTIPLE && lockModel != LockModel.RED_LOCK && keys.length > 1) {
        throw new RuntimeException("the lock mode " + lockModel.name() + " should have a single parameter");
    }
    RLock rLock;
    List<String> lockKeys = null;
    switch (lockModel) {
        case FAIR:
            lockKeys = getValueBySpel(keys[0], parameterNames, args, lock.keyConstant());
            //lockKeys.get(0)为锁的名称
            rLock = redissonClient.getFairLock(lockKeys.get(0));
            break;
        case RED_LOCK:
            List<RLock> rLocks = new ArrayList<>();
            for (String key : keys) {
                lockKeys = getValueBySpel(key, parameterNames, args, lock.keyConstant());
                rLocks.addAll(lockKeys.stream().map(redissonClient::getLock).collect(Collectors.toList()));
            }
            rLock = new RedissonRedLock(rLocks.toArray(new RLock[0]));
            break;
        case MULTIPLE:
            rLocks = new ArrayList<>();
            for (String key : keys) {
                lockKeys = getValueBySpel(key, parameterNames, args, lock.keyConstant());
                rLocks.addAll(lockKeys.stream().map(redissonClient::getLock).collect(Collectors.toList()));
            }
            rLock = new RedissonMultiLock(rLocks.toArray(new RLock[0]));
            break;
        case REENTRANT:
            lockKeys = getValueBySpel(keys[0], parameterNames, args, lock.keyConstant());
            if (lockKeys.size() == 1) {
                rLock = redissonClient.getLock(lockKeys.get(0));
                break;
            }
            rLock = new RedissonRedLock(
                    lockKeys.stream().map(redissonClient::getLock).toArray(RLock[]::new));
            break;
        case READ:
            lockKeys = getValueBySpel(keys[0], parameterNames, args, lock.keyConstant());
            rLock = redissonClient.getReadWriteLock(lockKeys.get(0)).readLock();
            break;
        case WRITE:
            lockKeys = getValueBySpel(keys[0], parameterNames, args, lock.keyConstant());
            rLock = redissonClient.getReadWriteLock(lockKeys.get(0)).writeLock();
            break;
        default:
            throw new LockException("lock model " + lockModel.name() + " is not supported");
    }`

通过AOP和Spel解析对加@LOck的函数进行处理

标签:redisson,log,keys,lock,lockKeys,key,Lock,new,分布式
From: https://www.cnblogs.com/dhsfxbk/p/18331063

相关文章

  • 分布式事务
    CAP定理一致性:分布式应用读写是一致的,应用A做了修改应用B读取时会获取修改可用性:分布式应用服务是可访问的分区容错性:分布式应用之前存在网络问题会导致各个应用无法通讯分区容错性是必须要保证的。一致性和可用性不能同时保证,假如应用A做了修改为了保证一致性在A没有做数据......
  • Hadoop伪分布式/分布式平台搭建教程以及安装过程中遇到部分问题的解决经验
    Hadoop伪分布式/分布式平台搭建教程声明:本搭建教程参考XMU的数据库实验室林子雨老师的搭建文档,附带了一点我们在安装时候遇到的问题和解决经验。XMU安装指导文档网址:https://dblab.xmu.edu.cn/blog/2544/目录文章目录Hadoop伪分布式/分布式平台搭建教程目录1.Linux......
  • Lua脚本解决Redis 分布式锁
    Redis分布式锁由于判断锁和释放锁是两个步骤,在判断一致后如果线程阻塞导致锁超时释放。之后阻塞结束,当前线程继续执行释放了其它线程的锁。锁设计失败解决方法:通过lua封装比较和释放锁两个步骤:要么同时成功,要么同时失败我的疑问?为什么不对判断和释放锁两个步骤再加锁@Over......
  • 服务端高并发分布式结构演进之路
    一、概述本文以一个“电子商务” 应用为例,介绍从一百个到千万级并发情况下服务端的架构的演进过程,同时列举出每个演进阶段会遇到的相关技术,了解过后可以对架构的演进有一个整体的认知。二、常见概念在正式引入架构演进之前,先对其中一些比较重要的概念做前置介绍。1、......
  • C. Monoblock
    原题链接题解把美丽看成1+有多少相邻的不同的连接块这样就能贡献来做了code#include<bits/stdc++.h>#definelllonglongusingnamespacestd;lla[100005];voidsolve(){lln,q;cin>>n>>q;for(inti=1;i<=n;i++)cin>>a[i];llans=n*(n+1)/......
  • 科普文:常见的分布式协议与算法
    主要列举一致性Hash算法、Gossip协议、QuorumNWR算法、PBFT算法、PoW算法、ZAB协议,Paxos会分开单独讲。科普文:搞懂ApacheBookKeeper和一致性协议-CSDN博客科普文:分布式系统中的一致性协议概叙-CSDN博客一致性Hash算法#一致性Hash算法是为了解决Hash算法的迁移成本,以一个1......
  • [AI]在家中使用日常设备运行您自己的 AI 集群.适用于移动、桌面和服务器的分布式 LLM
    创作不易只因热爱!!热衷分享,一起成长!“你的鼓励就是我努力付出的动力”AI发展不可谓不快,从ollama个人电脑CPU运行到现在,日常设备AI集群.下面对比一下,两款开源AI大模型的分布式推理应用,exo和cake.1.AI集群推理应用exo和cake的简单对比......
  • 基于redis实现分布式锁
           分布式锁1.基于redis实现分布式锁注意:这里设置过期时间,是为了预防死锁。如果某个线程获取了锁,但还没等它执行完业务,释放锁。服务器就宕机了,那么就不会有人再去释放锁,出现了死锁问题。简单业务代码:publicinterfaceILock{booleantryLock(longt......
  • LockSupport
    LockSupprot用来阻塞和唤醒线程,底层实现依赖于Unsafe类(后面会细讲)。该类包含一组用于阻塞和唤醒线程的静态方法,这些方法主要是围绕park和unpark展开。publicclassMain{publicstaticvoidmain(String[]args){ThreadmainThread=Thread.currentThre......
  • 链路追踪和分析-Sleuth+Zipkin-微服务核心组件【分布式微服务笔记06】
    链路追踪和分析-Sleuth+Zipkin-微服务核心组件【分布式微服务笔记06】链路追踪和分析-Sleuth+Zipkin在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用,来协同产生最后的请求结果,每一个请求都会形成一条复杂的分布式服务调用链路链路中的任何......