Redisson 分布式锁实现之源码篇 → 为什么推荐用 Redisson 客户端

Redisson中给出的是UUID + ThreadId)获取锁。

信号量(Semaphore)会维护一组许可证用于限制线程对资源的访问,当我们有一资源允许线程并发访问,但我们希望能限制访问量,就可以用信号量对访问线程进行限制。当线程要访问资源时,要先调用信号量的acquire方法获取访问许可证,当线程访问完毕后,调用信号量的release归还许可证

你已经知道 Srpring 会我们的标注的 @FeignClient 的接口创建了一个代理对象,那么有了这个代理对象我们就可以做增强处理(e.g. 前置增强、后置增强),那么你知道是如何实现的吗?感兴趣的朋友可以再翻翻源码寻找答案(温馨提示:增强逻辑在 InvocationHandler 中)。还有 Feign 与 Ribbon 和 Hystrix 等组件的协作,感兴趣的朋友可以自行下载源码学习了解。

保证服务雪崩问题

熔断(熔断池 ) 限流(redis锁 ) 服务降级(关闭某些服务)

Semaphore源码解析(二)  限流

https://www.cnblogs.com/youzhibing/p/14696450.html

单服务下,用 JDK 中的 synchronized 或 Lock 的实现类可实现对共享资源的并发访问

  分布式服务下,JDK 中的锁就显得力不从心了,分布式锁也就应运而生了

  分布式锁的实现方式有很多,常见的有如下几种

    基于 MySQL,利用行级悲观锁(select ... for update)

    基于 Redis,利用其 (setnx + expire) 或 set   比较特殊的语句通过 Lua 脚本

  在 Redis 中执行 Lua 脚本有两种方法:eval 和 evalsha

    基于 Zookeeper,利用其临时目录和事件回调机制  Zookeeper 文件系统 和 通知机制 

  • Zookeeper 的核心是原子广播机制,这个机制保证了各个 server 之间的同步。实现这个机制的协议叫做 Zab 协议。Zab 协议有两种模式,它们分别是恢复模式和广播模式。
  • (1)数据发布/订阅

    (2)负载均衡

    (3)命名服务

    (4)分布式协调/通知

    (5)集群管理

    (6)Master 选举

    (7)分布式锁

    (8)分布式队列

Java 语言的 Redis 客户端有很多,推荐使用的有:Jedis、lettuce、Redisson

 

分布式锁的特点

 

可以类比 JDK 中的锁

  互斥

    不仅要保证同个服务中不同线程的互斥,还需要保证不同服务间、不同线程的互斥

    如何处理互斥,是自旋、还是阻塞 ,还是其他 ?

  超时

    锁超时设置,防止程序异常奔溃而导致锁一直存在,后续同把锁一直加不上

  续期

    程序具体执行的时长无法确定,所以过期时间只能是个估值,那么就不能保证程序在过期时间内百分百能运行完

    所以需要进行锁续期,保证业务能够正常执行完

  可重入

    可重入锁又名递归锁,是指同一个线程在外层方法已经获得锁,再进入该线程的中层或内层方法会自动获取锁

    简单点来说,就是同个线程可以反复获取同一把锁

 专一释放

    通俗点来讲:谁加的锁就只有它能释放这把锁

    为什么会出现这种错乱释放的问题了,举个例子就理解了

 

 公平锁:多个线程按照申请锁的顺序去获得锁,所有线程都在队列里排队,这样就保证了队列中的第一个先得到锁

    非公平锁:多个线程不按照申请锁的顺序去获得锁,而是同时直接去尝试获取锁

    JDK 中的 ReentrantLock 就有公平和非公平两种实现,有兴趣的可以去看看它的源码

    多数情况下用的是非公平锁,但有些特殊情况下需要用公平锁

 

Redisson 实现分布式锁

  关于 Redisson,更多详细信息可查看官方文档

  Redisson 是 Redis 官方推荐的 Java 版的 Redis 客户端,它提供了非常丰富的功能,其中就包括本文关注的分布式锁

 

简单示例开始之前,我们先看下环境;版本不同,会有一些差别

    JDK:1.8

    Redis:3.2.8

    Redisson:3.13.6

简单示例

    先将 Redis 信息配置给 Redisson,创建出 RedissonClient

    Redis 的部署方式不同,Redisson 配置模式也会不同,详细信息可查看:Configuration

    我们就配置最简单的 Single instance mode 

 

 

  客服端的创建过程中,会生成一个 id 作为唯一标识,用以区分分布式下不同节点中的客户端

 

  id 值就是一个 UUID,客户端启动时生成

 

  尝试获取锁主要做了两件事:1、尝试获取锁,2、锁续期

 

  尝试获取锁主要涉及到一段 lua 代码

 

 

 

@RedisLock(lockName = "device_goods_stock",key="'skuId:'+#skuId" , expire = 2000)

 

 


/*
* Copyright (c) 2018-2999 广州亚米信息科技有限公司 All rights reserved.
*
* https://www.gz-yami.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/

package com.box.lock.annotation;

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

/**
* 使用redis进行分布式锁
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {

/**
* redis锁 名字
*/
String lockName() default "";

/**
* redis锁 key 支持spel表达式
*/
String key() default "";

/**
* 过期秒数,默认为5毫秒
*
* @return 轮询锁的时间
*/
int expire() default 5000;

/**
* 超时时间单位
*
* @return 毫秒
*/
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;


}



/*
* Copyright (c) 2018-2999 广州亚米信息科技有限公司 All rights reserved.
*
* https://www.gz-yami.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/

package com.box.lock.aspect;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.box.lock.annotation.RedisLock;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
* @author yuandingwang
*/
@Aspect
@Component
public class RedisLockAspect {

@Autowired
private RedissonClient redissonClient;

private static final String REDISSON_LOCK_PREFIX = "redisson_lock:";

@Around("@annotation(redisLock)")
public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
String spel = redisLock.key();
String lockName = redisLock.lockName();

RLock rLock = redissonClient.getLock(getRedisKey(joinPoint, lockName, spel));
rLock.lock(redisLock.expire(), redisLock.timeUnit());

Object result = null;
try {
//执行方法
result = joinPoint.proceed();
// System.out.println("分布式锁");
} finally {
rLock.unlock();
}
return result;
}

/**
* 将spel表达式转换为字符串
*
* @param joinPoint 切点
* @return redisKey
*/
private String getRedisKey(ProceedingJoinPoint joinPoint, String lockName, String spel) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method targetMethod = methodSignature.getMethod();
Object target = joinPoint.getTarget();
Object[] arguments = joinPoint.getArgs();
return REDISSON_LOCK_PREFIX + lockName + StrUtil.COLON + parse(target, spel, targetMethod, arguments);
}

private static String parse(Object rootObject,String spel, Method method, Object[] args) {
if (StringUtils.isBlank(spel)) {
return StrUtil.EMPTY;
}
//获取被拦截方法参数名列表(使用Spring支持类库)
LocalVariableTableParameterNameDiscoverer u =
new LocalVariableTableParameterNameDiscoverer();
String[] paraNameArr = u.getParameterNames(method);
if (ArrayUtil.isEmpty(paraNameArr)) {
return spel;
}
//使用SPEL进行key的解析
ExpressionParser parser = new SpelExpressionParser();
//SPEL上下文
StandardEvaluationContext context = new MethodBasedEvaluationContext(rootObject,method,args,u);
//把方法参数放入SPEL上下文中
for (int i = 0; i < paraNameArr.length; i++) {
context.setVariable(paraNameArr[i], args[i]);
}
return parser.parseExpression(spel).getValue(context, String.class);
}
}






posted @ 2021-07-05 10:43  小蚊子大人KN  阅读(310)  评论(0编辑  收藏  举报