zoukankan      html  css  js  c++  java
  • Redisson 分布式锁实现之源码篇 → 为什么推荐用 Redisson 客户端

    信号量(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);
    }
    }






    小蚊子大人
  • 相关阅读:
    HTTP状态码
    MySQL的order by时区分大小写
    CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap的实现原理和适用场景
    Map接口
    Python中创建守护进程
    df说磁盘空间满了, du说没有,到底谁是对的
    几种分布式文件系统对比
    Unity:控制粒子特效的移动方向
    创建NuGet包
    NuGet的简单使用
  • 原文地址:https://www.cnblogs.com/ywsheng/p/14971329.html
Copyright © 2011-2022 走看看