前言

前面的俩个章节分别介绍了如何通过数据库和zookeeper的方式实现分布式锁,本节我们将使用redis的方式实现分布式锁。该小节也是我们分布式锁三大技术方案实战的终极篇,是我们在实际开发环境中最为常用的一种分布式锁方案。三种分布式锁从不同维度比较,性能如下。综合考虑,我们如果性能优先的话,优先考虑redis的方式。本小节redis实现的分布式锁,我们主要使用redis分布式锁的一个集成工具类redisson实现,该工具类帮助我们实现了许多分布式锁,用法类似我们的java本地锁,如可重入锁(Reentrant Lock)、公平锁(Fair Lock)、 联锁(MultiLock)、红锁(RedLock)、读写锁(ReadWriteLock)、信号量(Semaphore)、闭锁(CountDownLatch)等,具体可参考官方文档Redisson官方文档链接地址https://github.com/redisson/redisson/wiki/。其内部实现是使用lua脚本实现,从而保证我们操作命令事务的原子性。

从理解的难易程度(从低到高)

数据库 > 缓存 > Zookeeper

从实现的复杂性角度(从低到高)

Zookeeper >= 缓存 > 数据库

从性能角度(从高到低)

缓存 > Zookeeper >= 数据库

从可靠性角度(从高到低)

Zookeeper > 缓存 > 数据库

正文

  • 搭建redis集群

说明:可参考作者的博客docker环境下docker-compose安装高可用redis集群详解(一主二从三哨兵),该篇有关于redis哨兵模式集群的详细搭建过程。

  • 引入redisson的pom文件

说明:高版本会有异常出现,但不会影响使用,异常如下。本节作者使用低版本3.11.6的版本,避免以下的错误提示。

redis 多实例分布式锁 redis实现分布式锁最好方案_redis

pom使用一个redisson的启动器redisson-spring-boot-starter,方便我们集成redisson。

<dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.11.6</version> </dependency>

redis 多实例分布式锁 redis实现分布式锁最好方案_redis_02

  • 项目集成redisson

说明:redisson-spring-boot-starter使用redisson有俩种方式,一种通过javaconfig配置,一种通过yaml文件引入,本文我们使用yaml的方式引入。具体使用流程可查看官方文档地址,里面有详细说明,地址:https://github.com/redisson/redisson/wiki/

①创建redisson.yaml文件

sentinelServersConfig:
  #连接空闲超时,单位:毫秒
  idleConnectionTimeout: 10000
  #连接超时,单位:毫秒
  connectTimeout: 10000
  #命令等待超时,单位:毫秒
  timeout: 30000
  #命令失败重试次数
  retryAttempts: 3
  #命令重试发送时间间隔,单位:毫秒
  retryInterval: 1500
  #密码
  password: root
  #单个连接最大订阅数量
  subscriptionsPerConnection: 5
  #客户端名称
  clientName: null
  #负载均衡算法类的选择
  loadBalancer: !<org.redisson.connection.balancer.RoundRobinLoadBalancer> {}
  #从节点发布和订阅连接的最小空闲连接数
  subscriptionConnectionMinimumIdleSize: 1
  #从节点发布和订阅连接池大小
  subscriptionConnectionPoolSize: 50
  #从节点最小空闲连接数
  slaveConnectionMinimumIdleSize: 32
  #从节点连接池大小
  slaveConnectionPoolSize: 64
  #主节点最小空闲连接数
  masterConnectionMinimumIdleSize: 32
  #主节点连接池大小
  masterConnectionPoolSize: 64
  #读取操作的负载均衡模式
  readMode: "SLAVE"
  sentinelAddresses:
    - "redis://192.168.23.134:26379"
    - "redis://192.168.23.134:26380"
    - "redis://192.168.23.134:26381"
  #主服务器的名称
  masterName: "mymaster"
  #数据库编号
  database: 0
  #在Redisson启动期间启用sentinels列表检查,默认为true,这里我们设置为false,不检查
  checkSentinelsList: false
#线程池数量
threads: 0
#Netty线程池数量
nettyThreads: 0
#编码
codec: !<org.redisson.codec.JsonJacksonCodec> { }
#传输模式
"transportMode": "NIO"

redis 多实例分布式锁 redis实现分布式锁最好方案_List_03

②主配置文件中加载redisson文件配置

redis 多实例分布式锁 redis实现分布式锁最好方案_redis_04

③修改redis配置

说明:此处使用RedissonConnectionFactory工厂类的连接配置,不使用之前的RedisConnectionFactory,否则会有冲突。

redis 多实例分布式锁 redis实现分布式锁最好方案_分布式_05

④redisson集成后可使用的bean,可直接注入使用,不用在redis配置中声明注入。

RedissonClient

RedissonRxClient

RedissonReactiveClient

RedisTemplate

ReactiveRedisTemplate

  • 使用可重入锁(Reentrant Lock)实现分布式锁案例

①创建一个redis分布锁测试接口

/*
 * ******************************************************************************************************************************************
 * Copyright (c) 2021 .
 * All rights reserved.
 * 项目名称:atp-platform
 * 项目描述:应用测试平台管理端
 * 版权说明:本软件属云嘀科技有限公司所有,在未获得云嘀科技有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 * *******************************************************************************************************************************************
 */
package com.yundi.atp.platform.module.sys.controller;


import com.yundi.atp.platform.common.Result;
import com.yundi.atp.platform.module.sys.entity.User;
import com.yundi.atp.platform.module.sys.service.DistributeLockService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * <p>
 * 分布式锁 前端控制器
 * </p>
 *
 * @author yanp
 * @since 2021-04-29
 */
@Api(tags = {"分布式锁测试"})
@RestController
@RequestMapping("/sys/distributeLock")
public class DistributeLockController {
    @Autowired
    private DistributeLockService distributeLockService;

    /**
     * 分布式锁获取查询数据:方式一(mysql)
     * @return
     */
    @ApiOperation(value = "通过mysql方式获取分布式锁案例")
    @GetMapping(value = "/findAllUserInfoByMysqlLock")
    public Result findAllUserInfoByMysqlLock() {
        List<User> userList = distributeLockService.findAllUserInfoByMysqlLock();
        //1.没有获取到锁,直接返回提示信息
        if (userList == null) {
            return Result.fail("正在全力为您加载中,请稍后重试!");
        }
        return Result.success(userList);
    }

    /**
     * 分布式锁获取查询数据:方式二(zookeeper)
     * @return
     */
    @ApiOperation(value = "通过zookeeper方式获取分布式锁案例")
    @GetMapping(value = "/findAllUserInfoByZookeeperLock")
    public Result findAllUserInfoByZookeeperLock() {
        List<User> userList = distributeLockService.findAllUserInfoByZookeeperLock();
        //1.没有获取到锁,直接返回提示信息
        if (userList == null) {
            return Result.fail("正在全力为您加载中,请稍后重试!");
        }
        return Result.success(userList);
    }

    /**
     * 分布式锁获取查询数据:方式三(redis)
     * @return
     */
    @ApiOperation(value = "通过redisson方式获取分布式锁案例")
    @GetMapping(value = "/findAllUserInfoByRedissonLock")
    public Result findAllUserInfoByRedissonLock() {
        List<User> userList = distributeLockService.findAllUserInfoByRedissonLock();
        //1.没有获取到锁,直接返回提示信息
        if (userList == null) {
            return Result.fail("正在全力为您加载中,请稍后重试!");
        }
        return Result.success(userList);
    }

}

redis 多实例分布式锁 redis实现分布式锁最好方案_分布式_06

②redis分布式锁实现类

/*
 * ******************************************************************************************************************************************
 * Copyright (c) 2021 .
 * All rights reserved.
 * 项目名称:atp-platform
 * 项目描述:应用测试平台管理端
 * 版权说明:本软件属云嘀科技有限公司所有,在未获得云嘀科技有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 * *******************************************************************************************************************************************
 */
package com.yundi.atp.platform.module.sys.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yundi.atp.platform.common.Constant;
import com.yundi.atp.platform.module.sys.entity.DistributeLock;
import com.yundi.atp.platform.module.sys.entity.User;
import com.yundi.atp.platform.module.sys.mapper.DistributeLockMapper;
import com.yundi.atp.platform.module.sys.service.DistributeLockService;
import com.yundi.atp.platform.module.sys.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 分布式锁 服务实现类
 * </p>
 *
 * @author yanp
 * @since 2021-04-29
 */
@Slf4j
@Service
public class DistributeLockServiceImpl extends ServiceImpl<DistributeLockMapper, DistributeLock> implements DistributeLockService {
    @Autowired
    private UserService userService;
    @Autowired
    private CuratorFramework curatorFramework;
    @Autowired
    private RedissonClient redissonClient;

    @Override
    public List<User> findAllUserInfoByMysqlLock() {
        //1.删除过期的锁
        this.remove(new QueryWrapper<DistributeLock>().eq("method_name", "findAllUserInfoByMysqlLock").le("expire_time", LocalDateTime.now()));
        //2.申请锁
        DistributeLock distributeLock = new DistributeLock();
        distributeLock.setMethodName("findAllUserInfoByMysqlLock");
        distributeLock.setCreateTime(LocalDateTime.now());
        distributeLock.setExpireTime(LocalDateTime.now().plusMinutes(1));
        try {
            this.save(distributeLock);
        } catch (Exception e) {
            log.error(Thread.currentThread().getName() + ":分布式锁已被占用,请稍后重试!");
            return null;
        }
        //3.执行具体的业务
        List<User> userList = userService.findAllUserInfo();
        try {
            //模拟业务需要执行5秒钟
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //4.释放锁
        this.removeById(distributeLock.getId());
        //5.返回结果
        return userList;
    }

    @Override
    public List<User> findAllUserInfoByZookeeperLock() {
        String lockName = Constant.LOCK_ROOT_PATH + "findAllUserInfoByZookeeperLock";
        log.info("==================================1.线程:{}({})开始获取锁!!!==========================", lockName, Thread.currentThread().getName());
        //1.创建zookeeper分布式锁
        InterProcessSemaphoreMutex lock = new InterProcessSemaphoreMutex(curatorFramework, lockName);
        try {
            //2.获取锁资源
            boolean flag = lock.acquire(30, TimeUnit.SECONDS);
            if (flag) {
                log.info("==================================2.线程:{}({})获取到了锁,开始执行业务!!!==========================", lockName, Thread.currentThread().getName());
                //3.处理具体的业务
                List<User> userList = userService.findAllUserInfo();
                return userList;
            }
        } catch (Exception e) {
            log.info("======================================3.获取锁异常:{}({}):{}========================", lockName, Thread.currentThread().getName(), e.getMessage());
            return null;
        } finally {
            try {
                lock.release();
                log.info("===================================4.释放锁:{}({})=========================", lockName, Thread.currentThread().getName());
            } catch (Exception e) {
                log.info("===================================5.释放锁出错:{}({}):{}========================================", lockName, Thread.currentThread().getName(), e.getMessage());
            }
        }
        return null;
    }

    @Override
    public List<User> findAllUserInfoByRedissonLock() {
        //1.获取锁
        RLock rLock = redissonClient.getLock("lock-findAllUserInfoByRedissonLock");
        try {
            //2.加锁
            rLock.lock();
            log.info("==================={}:acquire lock success=============", Thread.currentThread().getName());
            //3.处理具体的业务
            return userService.findAllUserInfo();
        } catch (Exception e) {
            log.error("redisson acquire lock exception:{}", e);
            return null;
        } finally {
            //4.解锁
            rLock.unlock();
            log.info("==================={}:release lock success=============", Thread.currentThread().getName());
        }

    }
}

redis 多实例分布式锁 redis实现分布式锁最好方案_分布式_07

③启动项目看是否可以生成锁

redis 多实例分布式锁 redis实现分布式锁最好方案_redis_08

redis 多实例分布式锁 redis实现分布式锁最好方案_List_09

这里生成的锁是有看门狗机制的,如果我们的业务在还没有执行完的过程中,锁会自动续期,直到业务完成,看门狗会一直监控我们的redis实例状态,根据监控状态释放锁,避免手动释放不成功,造成死锁。 

redis 多实例分布式锁 redis实现分布式锁最好方案_redis_10

  • 分布锁并发测试验证

①jmeter并发测试

redis 多实例分布式锁 redis实现分布式锁最好方案_List_11

②测试结果日志

redis 多实例分布式锁 redis实现分布式锁最好方案_分布式锁_12

结语

ok,到这里有关于分布式锁的内容就告一段落了,作者加班加点吐血整理。希望能够对你有所帮助,我们下期见,别忘了关注加点赞哦!