GENGEN
主页
vuepress
  • GIT命令
  • python+django
  • vue cli搭建项目
  • babel es6转换es5
  • docker aliyun配置
  • npm 配置
  • linux 常用命令
  • Ubuntu 下Linux 命令
  • github
  • gitee
  • csdn
  • 关于我
主页
vuepress
  • GIT命令
  • python+django
  • vue cli搭建项目
  • babel es6转换es5
  • docker aliyun配置
  • npm 配置
  • linux 常用命令
  • Ubuntu 下Linux 命令
  • github
  • gitee
  • csdn
  • 关于我
  • java基础

    • JDK8 函数式编程
    • JDK8 新特性之Date-Time
    • Servlet 源码分析
    • ArrayList 源码
    • LinkedList 源码
    • HashMap 源码
    • String 源码
    • BigDecimal 源码
    • java 类的加载
    • Class 源码
    • Synchronized锁升级
    • 事务的传播机制
    • knowledge
  • JAVA WEB

    • Java Servlet
    • 权限设计
    • logback日志的链路追踪
  • DATABASE

    • MySQL EXPLAIN详解
    • MySQL 索引
    • MySQL 表锁、行锁
    • MySQL ACID与transcation
    • 分布式事务
    • MySQL MVCC机制
    • Mysql 乐观锁与悲观锁
    • 分布式锁1 数据库分布式锁
    • 分布式锁2 Redis分布式锁
    • 分布式锁3 ZK分布式锁
  • SpringCloud

    • SpringCloud服务注册中心之Eureka
    • SpringCloud服务注册中心之Zookeeper
    • SpringCloud服务调用之Ribbon
    • SpringCloud服务调用之OpenFeign
    • SpringCloud服务降级之Hystrix
    • SpringCloud服务网关之Gateway
    • SpringCloud Config分布式配置中心
    • SpringCloud服务总线之Bus
    • SpringCloud消息驱动之Stream
    • SpringCloud链路追踪之Sleuth
    • SpringCloud Alibaba Nacos
    • SpringCloud Alibaba Sentinel
  • Spring

    • SpringBoot
    • Spring-data-jpa入门
    • SpringCloud问题
    • dispatcherServlet 源码分析
    • @SpringBootApplication注解内部实现与原理
    • spring启动初始化初始化
  • 中间件

    • 分布式协调服务器Zookeeper
    • 服务治理Dubbo
    • 分布式配置管理平台Apollo
    • 消息中间件框架Kafka
    • 分布式调度平台ElasticJob
    • 可视化分析工具Kibana
    • ElacticSearch 基础
    • ElacticSearch进阶
    • ElacticSearch集成
  • 环境部署

    • 应用容器引擎Docker
    • DockerCompose服务编排
    • 负载均衡Nginx
    • Nginx的安装配置
    • K8S基础
  • 代码片段

    • listener 监听模式
    • spingboot 整合redis
    • XSS过滤
    • profile的使用
    • ConfigurationProperties注解
  • 设计模式

    • 工厂模式
    • 单例模式
    • 装饰者模式
    • 适配器模式
    • 模板方法模式
    • 观察者模式
  • 读书笔记

    • 《Spring in Action 4》 读书笔记
    • 《高性能mysql》 读书笔记
  • NoSQL

    • Redis基础
    • Redis高级
    • Redis集群
    • Redis应用
  • MQ

    • rabbitMQ基础
    • rabbitMQ高级
    • rabbitMQ集群
  • JVM

    • JVM体系架构概述
    • 堆参数调整
    • GC 分代收集算法
    • JVM 垃圾回收器
    • JVM 相关问题
  • JUC

    • JUC总览
    • volatile关键字
    • CAS
    • ABA问题
    • collections包下线程安全的集合类
    • Lock 锁
    • LockSupport
    • AQS
    • Fork/Join分支框架
    • JUC tools
    • BlockingQueue 阻塞队列
    • Executor 线程池
    • CompletableFuture
    • 死锁以及问题定位分析
  • Shell

    • shell命令
    • shell基础
  • Activiti

    • IDEA下的Activiti HelloWord
    • 流程定义的CRUD
    • 流程实例的执行
    • 流程变量
  • VUE

    • vue基础
    • vue router
    • Vuex
    • Axios 跨域
    • dialog 弹出框使用
    • vue 动态刷新页面
    • vue 封装分页组件
    • vue 动态菜单
    • vue 常用传值
  • Solidity 智能合约

    • Solidity 基础
    • Solidity ERC-20
    • Solidity 101
  • English

    • 时态

分布式锁二 Redis分布式锁

概述

  • redis实现分布式锁类似数据库分布式锁。针对某个资源,保证其访问的互斥性。使用redis实现锁,主要就是讲资源放入redis中,利用其原子性的特性。当其它线程访问资源时,先在redis中看是否存在,如果存在不允许继续操作。

  • redis有两个方法:setnx() expire().setnx接收两个参数key,value。如果key存在,则不做任何操作,返回0,若key不存在,则设置成功,返回1。expire 设置过期时间,要注意的是 setnx 命令不能设置 key 的超时时间,只能通过 expire() 来对 key 设置。这两个方法是实现redis分布式锁的重要方法。

  • 加锁:加锁中需要设置过期时间,这两个操作必须是原子性的,否则加锁后没有成功设置过期时间,导致死锁。

  • 解锁:解锁必须是解除自己加上的锁,比如一个A线程执行效率特别慢,导致锁失效后还未执行完,这时B线程拿到了锁,之后A线程执行完毕去解锁,结果把B线程的锁解了。导致后面的C、D、E都可以拿到锁。

实现过程

  • 引入redis 并配置,实现参考代码片段里的 springboot整合redis

  • 和数据库分布式锁类似,当执行一个流程时,先加锁,如果成功则执行业务代码,完成后解锁.

  • 当加锁失败,说明此时已经存在锁,此处抛AppException异常,可以上游捕获此异常后续处理.

代码片段

  • redis锁代码
@Component
public class RedisLock {
     /**
     * 解锁脚本,原子操作
     */
    private static final String unlockScript =
            "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n"
                    + "then\n"
                    + "    return redis.call(\"del\",KEYS[1])\n"
                    + "else\n"
                    + "    return 0\n"
                    + "end";
    @Autowired
    private StringRedisTemplate redisTemplate;

     /**
       * 功能描述: <br>
       *< 加锁,有阻塞>
       *
       * @param
       * @return
       */
    public String lock(String name, long expire, long timeout){
        long startTime = System.currentTimeMillis();
        String token;
        do{
            token = tryLock(name, expire);
            if(token == null) {
                if((System.currentTimeMillis()-startTime) > (timeout-50)){
                    break;
                }
                try {
                    Thread.sleep(50); //try 50 per sec
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    return null;
                }
            }
        }while(token==null);

        return token;
    }
    /**
     * 加锁,无阻塞
     * @param name
     * @param expire
     * @return
     */
    public String tryLock(String name, long expire) {
        String token = UUID.randomUUID().toString();
        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
        RedisConnection conn = factory.getConnection();
        try{
            Boolean result = conn.set(name.getBytes(Charset.forName("UTF-8")), token.getBytes(Charset.forName("UTF-8")),
                    Expiration.from(expire, TimeUnit.MILLISECONDS), RedisStringCommands.SetOption.SET_IF_ABSENT);
            if(result!=null && result){
                return token;
            }
        }finally {
            RedisConnectionUtils.releaseConnection(conn, factory);
        }
        return null;
    }
    /**
     * 解锁
     * @param name
     * @param token
     * @return
     */
    public boolean unlock(String name, String token) {
        byte[][] keysAndArgs = new byte[2][];
        keysAndArgs[0] = name.getBytes(Charset.forName("UTF-8"));
        keysAndArgs[1] = token.getBytes(Charset.forName("UTF-8"));
        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
        RedisConnection conn = factory.getConnection();
        try {
            Long result = (Long)conn.scriptingCommands().eval(unlockScript.getBytes(Charset.forName("UTF-8")), ReturnType.INTEGER, 1, keysAndArgs);
            if(result!=null && result>0){
                return true;
            }
        }finally {
            RedisConnectionUtils.releaseConnection(conn, factory);
        }

        return false;
    }
}
  • 封装
//回调接口
public interface CallBack<T> {
    /**
     * 调用
     * @return 结果
     */
    T invoke();
}
  /**
     * Redis实体类,封装加锁参数
     * @return 结果
     */
public class RedisLockBean {

    private String name;

    private long expire;

    private long timeout;

    public RedisLockBean(String name, long expire, long timeout) {
        this.name = name;
        this.expire = expire;
        this.timeout = timeout;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public long getExpire() {
        return expire;
    }

    public void setExpire(long expire) {
        this.expire = expire;
    }

    public long getTimeout() {
        return timeout;
    }

    public void setTimeout(long timeout) {
        this.timeout = timeout;
    }
}
/**
* 模板
*/
public interface RedisLockTemplate {
    /**
     * 执行
     * @param callBack 回调
     * @return 执行结果
     */
    <T> T execute(RedisLockBean redisLockBean, CallBack<T> callBack);
}
/**
 * 〈〉<br>
 * 模板实现类
 *
 * @author 88395515
 * @date: Created in 15:59 2019/9/9
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
@Component
public class RedisLockTemplateImpl implements RedisLockTemplate {

    private Logger LOG = LoggerFactory.getLogger(RedisLockTemplateImpl.class);

    @Autowired
    RedisLock redisLock;

    @Override
    public <T> T execute(RedisLockBean redisLockBean, CallBack<T> callBack) {
        String token = null;
        try {
            token = redisLock.lock(redisLockBean.getName(), redisLockBean.getExpire(), redisLockBean.getTimeout());
            if (token != null) {
                LOG.info("RedisLockTemplate,获取到锁");
                return callBack.invoke();
            }
            LOG.error("RedisLockTemplate,未获取到锁");
            throw new AppException(CoreErrorCode.ONE_BY_ONE_EXCEPTION, oneByOne.getDescription() + "业务正在处理中");
        } finally {
            if (token != null) {
                redisLock.unlock(redisLockBean.getName(), token);
            }
        }

    }
}
  • 使用
@Autowired
RedisLockTemplate redisLockTemplate;

@RequestMapping(value = "/b")
public ModelAndView b() {
    ModelAndView mv = new ModelAndView("b");
    redisLockTemplate.execute(new RedisLockBean("LOCK_DEMO_B", 1000, 1000),
        new CallBack<Object>() {
            @Override
            public Object invoke() {
                //TODO 业务逻辑代码
                return null;
            }
        });
    return mv;
}

总结

  • 上述代码,仅对 redis 单实例架构有效,当面对 redis 集群时就无效了。但是一般情况下,我们的 redis 架构多数会做成“主备”模式,然后再通过 redis 哨兵实现主从切换,这种模式下我们的应用服务器直接面向主机,也可看成是单实例,因此上述代码实现也有效。但是当在主机宕机,从机被升级为主机的一瞬间的时候,如果恰好在这一刻,由于 redis 主从复制的异步性,导致从机中数据没有即时同步,那么上述代码依然会无效,导致同一资源有可能会产生两把锁,违背了分布式锁的原则。

  • 当 redis 的架构是单实例模式时,如果存在主备且可以忍受小概率的锁出错,那么就可以直接使用上述代码,当然最严谨的方式还是使用官方的 Redlock 算法实现。其中 Java 包推荐使用 redisson。

Redisson 改写redis分布式锁

  • Redisson是架设在Redis基础上的一个Java主内存数据网格(In-Memory Data Grid)。在Java实用工具包中常用接口的基础上,为使用者提供了一系列具有分布式特性的常用工具类。
  • Redisson Wiki:https://github.com/redisson/redisson/wiki

jar包依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.5.0</version>
</dependency>

配置Redisson

public class RedissonManager {
    private static Config config = new Config();
    //声明redisso对象
    private static Redisson redisson = null;
   //实例化redisson
    static{
       /**
         * 单机模式,如果有密码后面setPassword
         */
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        /**
         * 哨兵模式
         */
        /*config.useSentinelServers().addSentinelAddress("redis://172.29.3.245:26378","redis://172.29.3.245:26379", "redis://172.29.3.245:26380")
                .setMasterName("mymaster")
                .setPassword("a123456").setDatabase(0);*/
        /**
         * 集群模式
         */
        /*config.useClusterServers().addNodeAddress(
                "redis://172.29.3.245:6375","redis://172.29.3.245:6376", "redis://172.29.3.245:6377",
                "redis://172.29.3.245:6378","redis://172.29.3.245:6379", "redis://172.29.3.245:6380")
                .setPassword("a123456").setScanInterval(5000);*/
       
        //得到redisson对象
        redisson = (Redisson) Redisson.create(config);
    }

 //获取redisson对象的方法
    public static Redisson getRedisson(){
        return redisson;
    }
}

锁的获取和释放

public class DistributedRedisLock {
    private static Logger LOG = LoggerFactory.getLogger(DistributedRedisLock.class);
   //从配置类中获取redisson对象
    private static Redisson redisson = RedissonManager.getRedisson();
    private static final String LOCK_TITLE = "redisLock_";
   //加锁
    public static boolean acquire(String lockName,int timeout){
        //声明key对象
        String key = LOCK_TITLE + lockName;
        //获取锁对象
        RLock mylock = redisson.getLock(key);
        //加锁,并且设置锁过期时间,防止死锁的产生
       try {
            return mylock.tryLock(time, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            LOG.error("======lock,e:{}======" + Thread.currentThread().getName(),e);
            return Boolean.FALSE;
        }
    }
  //锁的释放
    public static void release(String lockName){
        //必须是和加锁时的同一个key
        String key = LOCK_TITLE + lockName;
        //获取所对象
        RLock mylock = redisson.getLock(key);
        //释放锁(解锁)
        mylock.unlock();
        LOG.info("======unlock======"+Thread.currentThread().getName());
    }
}

改写 RedisLockTemplateImpl

@Component
public class RedisLockTemplateImpl implements RedisLockTemplate {

    private static Logger LOG = LoggerFactory.getLogger(RedisLockTemplateImpl.class);

    @Autowired
    RedisLock redisLock;

    @Override
    public <T> T execute(RedisLockBean redisLockBean, CallBack<T> callBack) {
         boolean acquire = false;
        try {
            acquire = DistributedRedisLock.acquire(redisLockBean.getName(), redisLockBean.getTimeout());
            if (acquire){
                LOG.info("RedisLockTemplate,获取到锁");
                return callBack.invoke();
            }
            LOG.error("RedisLockTemplate,未获取到锁");
            throw new AppException(CoreErrorCode.ONE_BY_ONE_EXCEPTION, oneByOne.getDescription() + "业务正在处理中");
        } finally {
            if(acquire){
                DistributedRedisLock.release(redisLockBean.getName());
            }  
        }
    }
}
Last Updated:
Contributors: 88395515
Prev
分布式锁1 数据库分布式锁
Next
分布式锁3 ZK分布式锁