散装java 散装java
首页
  • Java基础
  • JVM
  • Java多线程
  • 知识点
  • 案例
  • Redis
  • RabbitMQ
  • Kafka
  • Elasticsearch
  • MySQL
  • Linux
  • Docker
  • Zookeeper
  • Nginx
  • Git
  • JMeter
  • Gradle
  • 常见BUG
  • 常见解决方案
  • 资源
  • 问答
💖支持
Gitee (opens new window)
首页
  • Java基础
  • JVM
  • Java多线程
  • 知识点
  • 案例
  • Redis
  • RabbitMQ
  • Kafka
  • Elasticsearch
  • MySQL
  • Linux
  • Docker
  • Zookeeper
  • Nginx
  • Git
  • JMeter
  • Gradle
  • 常见BUG
  • 常见解决方案
  • 资源
  • 问答
💖支持
Gitee (opens new window)
  • Spring Framework

    • Spring Framework 源码拉取编译技巧
  • Spring知识点

    • Spring 导读
    • Spring 过滤器和拦截器的区别
    • Spring Boot 自动装配原理是如何实现的
  • Spring集成

  • 案例

    • 大文件上传-分片-秒传-断点续传
    • 布隆过滤器使用
    • Spring Boot 集成 Zookeeper 实现分布式锁
    • Spring Boot 集成 Redis 实现分布式锁
      • 简介
      • 自定义 redis 分布式锁
        • 锁定义
        • 锁使用
      • Redisson 实现分布式锁
        • 锁定义
        • 锁使用
      • 本地测试说明
    • Spring Boot 集成 MySQL 实现分布式锁
  • Spring
  • 案例
散装java
2023-01-10
目录

Spring Boot 集成 Redis 实现分布式锁

提示

本文中的完整代码已上传 Gitee:

https://gitee.com/bulkall/bulk-demo/tree/master/spring-boot-lock/spring-boot-lock-redis (opens new window)

# 简介

本项目会演示 Redis 分布式锁的使用,会以常见的 “超卖”业务去演示

本示例代码主要包括两部分

  1. Redisson 实现的分布式锁使用演示
  2. 自己实现的 Redis 分布式锁使用演示

# 自定义 redis 分布式锁

# 锁定义

@Slf4j
@Component
public class BulkRedisLock {
    private static final String LOCK_PREFIX = "redisLock";
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 尝试获取锁
     *
     * @param requestId  请求id
     * @param expireTime 过期时间  单位毫秒
     * @return true false
     */
    public boolean lock(String requestId, int expireTime) {
        //这里利用redis的set命令
        //使用redis保证原子操作(判断是否存在,添加key,设置过期时间)
        while (true) {
            if (Boolean.TRUE.equals(stringRedisTemplate.boundValueOps(LOCK_PREFIX).
                    setIfAbsent(requestId, expireTime, TimeUnit.SECONDS))) {
                return true;
            }
        }
    }

    /**
     * 将锁释放掉
     * <p>
     * 为何解锁需要校验 requestId 因为不是自己的锁不能释放
     * 客户端A加锁,一段时间之后客户端A解锁,在执行 lock 之前,锁突然过期了。
     * 此时客户端B尝试加锁成功,然后客户端A再执行 unlock 方法,则将客户端B的锁给解除了。
     *
     * @param requestId 请求id
     * @return true false
     */
    public boolean unlock(String requestId) {
        //这里使用Lua脚本保证原子性操作
        String script = "if  redis.call('get', KEYS[1]) == ARGV[1] then " +
                "return redis.call('del', KEYS[1]) " +
                "else return 0 end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long res = stringRedisTemplate.execute(redisScript, Collections.singletonList(LOCK_PREFIX), requestId);
        return new Long(1).equals(res);
    }

    /**
     * 创建续命子线程
     *
     * @param time      操作预期耗时
     * @param requestId 唯一标识
     * @return 续命线程 Thread
     */
    public Thread watchDog(int time, String requestId) {
        return new Thread(() -> {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(time * 2 / 3);
                    //重置时间
                    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                            "return redis.call('expire', KEYS[1],ARGV[2]) " +
                            "else return '0' end";
                    List<Object> args = new ArrayList();
                    args.add(requestId);
                    args.add(time);
                    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
                    stringRedisTemplate.execute(redisScript, Collections.singletonList(LOCK_PREFIX), args);
                } catch (Exception e) {
                    log.info("watchDog异常:{}", e.getMessage());
                    return;
                }
            }
        });
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

# 锁使用

public String reduceStockByMyLock(Integer id) {
     // requestId 确保每一个请求生成的都不一样,这里使用 uuid,也可以使用其他分布式唯一 id 方案
     String requestId = UUID.randomUUID().toString().replace("-", "");
     int expireTime = 10;
     bulkRedisLock.lock(requestId, expireTime);
     // 开启续命线程,
     Thread watchDog = bulkRedisLock.watchDog(expireTime, requestId);
     watchDog.setDaemon(true);
     watchDog.start();
     try {
         ProductStock stock = productStockMapper.selectById(id);
         if (stock != null && stock.getStock() > 0) {
             productStockMapper.reduceStock(id);
         } else {
             throw new RuntimeException("库存不足!");
         }
     } finally {
         watchDog.interrupt();
         bulkRedisLock.unlock(requestId);
     }
     return "ok";
 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# Redisson 实现分布式锁

# 锁定义

Redisson 已经定义好了,具体可以参考 RLock 实现类

# 锁使用

public String reduceStock(Integer id) {
     RLock lock = redissonClient.getLock("lock");
     lock.lock();
     try {
         ProductStock stock = productStockMapper.selectById(id);
         if (stock != null && stock.getStock() > 0) {
             productStockMapper.reduceStock(id);
         } else {
             throw new RuntimeException("库存不足!");
         }
     } finally {
         lock.unlock();
     }
     return "ok";
 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 本地测试说明

  1. 压测工具使用 JMeter , 当然也可以用别的,我用的这个, 此处不了解的可以参考 JMeter 相关操作
  2. 本地测试,借助于 IDEA 的 Allow parallel run 功能启动多个相同的服务(模拟线上环境多个副本),注意修改端口(-Dserver.port=8089),操作可以看下图
  3. 使用 Nginx 工具,将启动的多个项目做负载均衡; 此处不了解的可以参考 Nginx 配置负载均衡
  4. 接下来就是使用 JMeter 开启多个线程去压测 Nginx 暴露出来的接口了
上次更新: 2023/01/10, 19:45:17
Spring Boot 集成 Zookeeper 实现分布式锁
Spring Boot 集成 MySQL 实现分布式锁

← Spring Boot 集成 Zookeeper 实现分布式锁 Spring Boot 集成 MySQL 实现分布式锁→

Theme by Vdoing | Copyright © 2022-2024 散装java | MIT License | 鲁ICP备2022022143号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式