Springboot2.x 使用 Redisson 分布式可重入锁

参考

注意

  1. 在单元测试中,测试多线程更新年龄失败,但是并没有抛出错误。 https://blog.csdn.net/MissLone/article/details/119569393
  2. Redisson 可以作为 Redis 客户端使用,而不仅仅是分布式锁一种使用方式。
  3. 看门狗的锁默认超时时间为30s,当服务器宕机后,因为锁的有效期是 30 秒,所以会在 30 秒内自动解锁。
  4. 可以通过给锁设置过期时间,让其自动解锁,则无需手动解锁。
  5. 红锁。
  6. 多个实例争抢锁,使用getLock会一直等待锁释放,然后新实例拿到锁再执行。可以使用tryLock设置获取时间,获取不到就会返回 false,但是不会自动续期。

环境

环境 版本 操作
windows 10
vs code 1.84.2
Spring Boot Extension Pack v0.2.1 vscode插件
Extension Pack for Java v0.25.15 vscode插件
JDK 11
Springboot 2.3.12.RELEASE
mybatis-spring-boot-starter 2.1.4 mvn依赖
redisson-spring-boot-starter 3.25.0 mvn依赖
Apache Maven 3.8.6
Redis 3.0.504 windows 版本,已停更,建议 linux 中使用
Apache 2.4.54 包含 abs 压测工具

正文

配置

  1. 引入 pom.xml 依赖。
<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>2.1.4</version>
</dependency>
  1. 添加 src\main\resources\application.properties 配置,引入redisson 配置文件(单节点与集群配置不一样)。
# 单节点 Redisson 配置
spring.redis.redisson.file=classpath:redisson.yaml
  1. 创建文件src\main\resources\redisson.yaml并写入配置。

https://github.com/redisson/redisson/wiki/2.-Configuration#262-single-instance-yaml-config-format

singleServerConfig:
  idleConnectionTimeout: 10000
  connectTimeout: 10000
  timeout: 3000
  retryAttempts: 3
  retryInterval: 1500
  password: null
  subscriptionsPerConnection: 5
  clientName: null
  address: "redis://127.0.0.1:6379"
  subscriptionConnectionMinimumIdleSize: 1
  subscriptionConnectionPoolSize: 50
  connectionMinimumIdleSize: 24
  connectionPoolSize: 64
  database: 0
  dnsMonitoringInterval: 5000
threads: 16
nettyThreads: 32
codec: !<org.redisson.codec.Kryo5Codec> {}
transportMode: "NIO"

代码

  1. src\main\java\com\xiaqiuchu\demo\service\StudentService.java
package com.xiaqiuchu.demo.service;

import javax.annotation.Resource;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import com.xiaqiuchu.demo.entity.Student;
import com.xiaqiuchu.demo.mapper.StudentMapper;

@Service
public class StudentService {
    @Resource
    StudentMapper studentMaper;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 手动管理缓存(基于分布式锁)
     * @param id
     * @return
     */
    public Student toOld(Integer id){
        String lockName = "StudentServiceToOld" + id;
        RLock lock = redissonClient.getLock(lockName);
        try {
            lock.lock();
            System.out.println("获取到锁");
            Student student = studentMaper.findById(id);
            //
            if(Integer.compare(student.getAge(), 100) > 0){
                Assert.isTrue(false, "超过100岁");
            }
            student.setAge((byte) (student.getAge() + 1));
            studentMaper.updateById(student);
            System.out.println("修改成功"+student.getAge());
            return student;
        } finally {
            lock.unlock();
        }
    }
}

  1. src\main\java\com\xiaqiuchu\demo\controller\StudentController.java
package com.xiaqiuchu.demo.controller;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.xiaqiuchu.demo.service.StudentService;
import org.springframework.web.bind.annotation.GetMapping;

@RestController
@RequestMapping("/student")
public class StudentController {
    
    @Resource
    StudentService studentService;
    
    // http://127.0.0.1:8080/student/toOldByThread
    @GetMapping("toOldByThread")
    public String toOldByThread() {

        ExecutorService executorService = Executors.newFixedThreadPool(6);
        for (int i = 0; i < 110; i++) {
            executorService.execute(()->{
                System.out.println(Thread.currentThread().getName());
                try {
                    studentService.toOld(1);
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
              
            });
        }
        executorService.shutdown();

        return "SUCCESS";
    }

    // http://127.0.0.1:8080/student/toOldByAB
    // ab压测测试 abs -n 110  -c 10 http://127.0.0.1:8080/student/toOldByAB
    @GetMapping("toOldByAB")
    public String toOldByAB() {
        studentService.toOld(1);
        return "SUCCESS";
    }
    
    
}

测试

  1. ab 压测
abs -n 110  -c 10 http://127.0.0.1:8080/student/toOldByAB
posted @   夏秋初  阅读(161)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
点击右上角即可分享
微信分享提示