前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手把手教你 SpringBoot 分布式锁的实现

手把手教你 SpringBoot 分布式锁的实现

作者头像
业余草
发布2020-06-29 10:43:15
1.4K0
发布2020-06-29 10:43:15
举报
文章被收录于专栏:业余草

编辑:业余草

来源:https://urlify.cn/632yIv

前言

前段时间面试了一个高级程序员,问他了一些分布式锁的知识,回答的还不错。后来进了我们的团队,刚好让他接手一个新项目,需要用到分布式锁,我说这是你的强项,你来实现。

谁知,项目上线后,各种问题接连爆发出来。后来我找他谈话,主要是说,千万不要重复造轮子,直接吧 A 项目中的 Redisson 用法实现复制过来一份不就行了,干嘛非要自己搞一套,还搞出问题。。。

今天,我们一起来手把手来实现一个高效的 SpringBoot 分布式锁!本文通过 Spring Boot 整合 redisson 来实现分布式锁,并结合 demo 测试结果。

分析设计要点

当我们在设计分布式锁的时候,我们应该考虑分布式锁至少要满足的一些条件,同时考虑如何高效的设计分布式锁,这里我认为以下几点是必须要考虑的。

1、互斥

在分布式高并发的条件下,我们最需要保证,同一时刻只能有一个线程获得锁,这是最基本的一点。

2、防止死锁

在分布式高并发的条件下,比如有个线程获得锁的同时,还没有来得及去释放锁,就因为系统故障或者其它原因使它无法执行释放锁的命令,导致其它线程都无法获得锁,造成死锁。

所以分布式非常有必要设置锁的有效时间,确保系统出现故障后,在一定时间内能够主动去释放锁,避免造成死锁的情况。

3、性能

对于访问量大的共享资源,需要考虑减少锁等待的时间,避免导致大量线程阻塞。

所以在锁的设计时,需要考虑两点。

1、锁的颗粒度要尽量小。比如你要通过锁来减库存,那这个锁的名称你可以设置成是商品的ID,而不是任取名称。这样这个锁只对当前商品有效,锁的颗粒度小。

2、锁的范围尽量要小。比如只要锁2行代码就可以解决问题的,那就不要去锁10行代码了。

4、重入

我们知道ReentrantLock是可重入锁,那它的特点就是:同一个线程可以重复拿到同一个资源的锁。重入锁非常有利于资源的高效利用。关于这点之后会做演示。

针对以上Redisson都能很好的满足,下面就来使用它。

首先看下常规Redis锁的处理图

代码实现

添加依赖

代码语言:javascript
复制
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.10.6</version>
</dependency>

配置信息

代码语言:javascript
复制
spring:
# redis
  redis:
    host: 47.103.5.190
    port: 6379
    jedis:
      pool:
# 连接池最大连接数(使用负值表示没有限制)
        max-active: 100
# 连接池中的最小空闲连接
        max-idle: 10
# 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1
# 连接超时时间(毫秒)
      timeout: 5000
#默认是索引为0的数据库
      database: 0

配置类

代码语言:javascript
复制
/**
 * redisson 配置,下面是单节点配置:
 *
 * @author gourd
 */
@Configuration
publicclassRedissonConfig{
@Value("${spring.redis.host}")
privateString host;
@Value("${spring.redis.port}")
privateString port;
@Value("${spring.redis.password:}")
privateString password;
@Bean
publicRedissonClient redissonClient() {
Config config = newConfig();
//单节点
        config.useSingleServer().setAddress("redis://"+ host + ":"+ port);
if(StringUtils.isEmpty(password)) {
            config.useSingleServer().setPassword(null);
} else{
            config.useSingleServer().setPassword(password);
}
//添加主从配置
// config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
// 集群模式配置 setScanInterval()扫描间隔时间,单位是毫秒, //可以用"rediss://"来启用SSL连接
// config.useClusterServers().setScanInterval(2000).addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001").addNodeAddress("redis://127.0.0.1:7002");
returnRedisson.create(config);
}
}

Redisson 工具类

代码语言:javascript
复制
/**
 * redis分布式锁帮助类
 *
 * @author gourd
 *
 */
publicclassRedisLockUtil{
privatestaticDistributedLocker distributedLocker = SpringContextHolder.getBean("distributedLocker",DistributedLocker.class);
/**
     * 加锁
     * @param lockKey
     * @return
     */
publicstaticRLocklock(String lockKey) {
return distributedLocker.lock(lockKey);
}
/**
     * 释放锁
     * @param lockKey
     */
publicstaticvoid unlock(String lockKey) {
        distributedLocker.unlock(lockKey);
}
/**
     * 释放锁
     * @param lock
     */
publicstaticvoid unlock(RLocklock) {
        distributedLocker.unlock(lock);
}
/**
     * 带超时的锁
     * @param lockKey
     * @param timeout 超时时间   单位:秒
     */
publicstaticRLocklock(String lockKey, int timeout) {
return distributedLocker.lock(lockKey, timeout);
}
/**
     * 带超时的锁
     * @param lockKey
     * @param unit 时间单位
     * @param timeout 超时时间
     */
publicstaticRLocklock(String lockKey, int timeout,TimeUnit unit ) {
return distributedLocker.lock(lockKey, unit, timeout);
}
/**
     * 尝试获取锁
     * @param lockKey
     * @param waitTime 最多等待时间
     * @param leaseTime 上锁后自动释放锁时间
     * @return
     */
publicstaticboolean tryLock(String lockKey, int waitTime, int leaseTime) {
return distributedLocker.tryLock(lockKey, TimeUnit.SECONDS, waitTime, leaseTime);
}
/**
     * 尝试获取锁
     * @param lockKey
     * @param unit 时间单位
     * @param waitTime 最多等待时间
     * @param leaseTime 上锁后自动释放锁时间
     * @return
     */
publicstaticboolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
return distributedLocker.tryLock(lockKey, unit, waitTime, leaseTime);
}
/**
     * 获取计数器
     *
     * @param name
     * @return
     */
publicstaticRCountDownLatch getCountDownLatch(String name){
return distributedLocker.getCountDownLatch(name);
}
/**
     * 获取信号量
     *
     * @param name
     * @return
     */
publicstaticRSemaphore getSemaphore(String name){
return distributedLocker.getSemaphore(name);
}
}

底层封装

代码语言:javascript
复制
/**
 * @author gourd
 */
publicinterfaceDistributedLocker{
RLocklock(String lockKey);
RLocklock(String lockKey, int timeout);
RLocklock(String lockKey, TimeUnit unit, int timeout);
boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime);
void unlock(String lockKey);
void unlock(RLocklock);
}
/**
 * @author gourd
 */
@Component
publicclassRedisDistributedLockerimplementsDistributedLocker{
@Autowired
privateRedissonClient redissonClient;
@Override
publicRLocklock(String lockKey) {
RLocklock= redissonClient.getLock(lockKey);
lock.lock();
returnlock;
}
@Override
publicRLocklock(String lockKey, int leaseTime) {
RLocklock= redissonClient.getLock(lockKey);
lock.lock(leaseTime, TimeUnit.SECONDS);
returnlock;
}
@Override
publicRLocklock(String lockKey, TimeUnit unit ,int timeout) {
RLocklock= redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
returnlock;
}
@Override
publicboolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
RLocklock= redissonClient.getLock(lockKey);
try{
returnlock.tryLock(waitTime, leaseTime, unit);
} catch(InterruptedException e) {
returnfalse;
}
}
@Override
publicvoid unlock(String lockKey) {
RLocklock= redissonClient.getLock(lockKey);
lock.unlock();
}
@Override
publicvoid unlock(RLocklock) {
lock.unlock();
}
}

测试

模拟并发测试

代码语言:javascript
复制
/**
 * redis分布式锁控制器
 * @author gourd
 * @since 2019-07-30
 */
@RestController
@Api(tags = "redisson", description = "redis分布式锁控制器")
@RequestMapping("/redisson")
@Slf4j
publicclassRedissonLockController{
/**
     * 锁测试共享变量
     */
privateInteger lockCount = 10;
/**
     * 无锁测试共享变量
     */
privateInteger count = 10;
/**
     * 模拟线程数
     */
privatestaticint threadNum = 10;
/**
     * 模拟并发测试加锁和不加锁
     * @return
     */
@GetMapping("/test")
@ApiOperation(value = "模拟并发测试加锁和不加锁")
publicvoidlock(){
// 计数器
finalCountDownLatch countDownLatch = newCountDownLatch(1);
for(int i = 0; i < threadNum; i ++) {
MyRunnable myRunnable = newMyRunnable(countDownLatch);
Thread myThread = newThread(myRunnable);
            myThread.start();
}
// 释放所有线程
        countDownLatch.countDown();
}
/**
     * 加锁测试
     */
privatevoid testLockCount() {
String lockKey = "lock-test";
try{
// 加锁,设置超时时间2s
RedisLockUtil.lock(lockKey,2, TimeUnit.SECONDS);
            lockCount--;
            log.info("lockCount值:"+lockCount);
}catch(Exception e){
            log.error(e.getMessage(),e);
}finally{
// 释放锁
RedisLockUtil.unlock(lockKey);
}
}
/**
     * 无锁测试
     */
privatevoid testCount() {
        count--;
        log.info("count值:"+count);
}
publicclassMyRunnableimplementsRunnable{
/**
         * 计数器
         */
finalCountDownLatch countDownLatch;
publicMyRunnable(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
publicvoid run() {
try{
// 阻塞当前线程,直到计时器的值为0
                countDownLatch.await();
} catch(InterruptedException e) {
                log.error(e.getMessage(),e);
}
// 无锁操作
            testCount();
// 加锁操作
            testLockCount();
}
}
}

调用接口后打印值:

测试结果

根据打印结果可以明显看到,未加锁的 count-- 后值是乱序的,而加锁后的结果和我们预期的一样。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/06/27 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档