分布式锁三大技术方案实战——基于redis方式实现分布式锁(终极篇)
Posted 北溟溟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式锁三大技术方案实战——基于redis方式实现分布式锁(终极篇)相关的知识,希望对你有一定的参考价值。
前言
前面的俩个章节分别介绍了如何通过数据库和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的版本,避免以下的错误提示。
pom使用一个redisson的启动器redisson-spring-boot-starter,方便我们集成redisson。
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.11.6</version> </dependency>
- 项目集成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"
②主配置文件中加载redisson文件配置
③修改redis配置
说明:此处使用RedissonConnectionFactory工厂类的连接配置,不使用之前的RedisConnectionFactory,否则会有冲突。
④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分布式锁实现类
/* * ****************************************************************************************************************************************** * 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实例状态,根据监控状态释放锁,避免手动释放不成功,造成死锁。
- 分布锁并发测试验证
①jmeter并发测试
②测试结果日志
结语
ok,到这里有关于分布式锁的内容就告一段落了,作者加班加点吐血整理。希望能够对你有所帮助,我们下期见,别忘了关注加点赞哦!
以上是关于分布式锁三大技术方案实战——基于redis方式实现分布式锁(终极篇)的主要内容,如果未能解决你的问题,请参考以下文章
Spring Boot + Redis实战-利用自定义注解+分布式锁实现接口幂等性