Commit 53db9de93dc56dbd212fbe763daa46b44934c1d7

Authored by YunaiV
1 parent 94dfe411

增加 Redis 分布式锁

README.md
... ... @@ -38,6 +38,8 @@
38 38 1. Redis 监控:监控 Redis 数据库的使用情况,使用的 Redis Key 管理
39 39 1. Java 监控:基于 Spring Boot Admin 实现 Java 应用的监控
40 40 1. 链路追踪:基于 SkyWalking 实现性能监控,特别是链路的追踪
  41 +1. 分布式锁:基于 Redis 实现分布式锁,满足并发场景
  42 +1. 幂等组件:基于 Redis 实现幂等组件,解决重复请求问题
41 43  
42 44 ### 研发工具
43 45  
... ... @@ -45,7 +47,6 @@
45 47 1. 代码生成:前后端代码的生成(Java、Vue、SQL),支持 CRUD 下载
46 48 1. 系统接口:基于 Swagger 自动生成相关的 RESTful API 接口文档
47 49 1. 数据库文档:基于 Screw 自动生成数据库文档
48   -1. 幂等组件:基于 Redis 实现幂等组件,解决重复请求问题
49 50  
50 51 ## 在线体验
51 52  
... ...
http-client.env.json
1 1 {
2 2 "local": {
3   - "baseUrl": "http://127.0.0.1:8080/api",
  3 + "baseUrl": "http://127.0.0.1:48080/api",
4 4 "token": "test1"
5 5 }
6 6 }
... ...
... ... @@ -30,6 +30,10 @@
30 30 <druid.version>1.2.4</druid.version>
31 31 <mybatis-plus.version>3.4.1</mybatis-plus.version>
32 32 <redisson.version>3.14.1</redisson.version>
  33 + <!-- Config 配置中心相关 -->
  34 + <apollo.version>1.7.0</apollo.version>
  35 + <!-- 服务保障相关 -->
  36 + <lock4j.version>2.2.0</lock4j.version>
33 37 <!-- 监控相关 -->
34 38 <skywalking.version>8.3.0</skywalking.version>
35 39 <spring-boot-admin.version>2.3.1</spring-boot-admin.version>
... ... @@ -125,7 +129,7 @@
125 129 <dependency>
126 130 <groupId>com.ctrip.framework.apollo</groupId>
127 131 <artifactId>apollo-client</artifactId> <!-- 引入 Apollo Client 库,实现内嵌的配置中心 -->
128   - <version>1.7.0</version>
  132 + <version>${apollo.version}</version>
129 133 </dependency>
130 134  
131 135 <!-- Job 定时任务相关 -->
... ... @@ -134,6 +138,13 @@
134 138 <artifactId>spring-boot-starter-quartz</artifactId>
135 139 </dependency>
136 140  
  141 + <!-- 服务保障相关 -->
  142 + <dependency>
  143 + <groupId>com.baomidou</groupId>
  144 + <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
  145 + <version>${lock4j.version}</version>
  146 + </dependency>
  147 +
137 148 <!-- 监控相关 -->
138 149 <dependency>
139 150 <groupId>org.apache.skywalking</groupId>
... ... @@ -217,7 +228,7 @@
217 228  
218 229 <dependency>
219 230 <groupId>cn.smallbun.screw</groupId>
220   - <artifactId>screw-core</artifactId>
  231 + <artifactId>screw-core</artifactId> <!-- 实现数据库文档 -->
221 232 <version>${screw.version}</version>
222 233 </dependency>
223 234  
... ...
src/main/java/cn/iocoder/dashboard/common/exception/enums/GlobalErrorCodeConstants.java
... ... @@ -29,7 +29,8 @@ public interface GlobalErrorCodeConstants {
29 29 ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常");
30 30  
31 31 // ========== 自定义错误段 ==========
32   - ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求");
  32 + ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求,请稍后重试"); // 重复请求
  33 + ErrorCode CONCURRENCY_REQUESTS = new ErrorCode(901, "请求失败,请稍后重试"); // 并发请求,不允许
33 34  
34 35 ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
35 36  
... ...
src/main/java/cn/iocoder/dashboard/framework/lock4j/config/Lock4jConfiguration.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.lock4j.config;
  2 +
  3 +import cn.hutool.core.util.ClassUtil;
  4 +import cn.iocoder.dashboard.framework.lock4j.core.DefaultLockFailureStrategy;
  5 +import cn.iocoder.dashboard.framework.lock4j.core.Lock4jRedisKeyConstants;
  6 +import org.springframework.context.annotation.Bean;
  7 +import org.springframework.context.annotation.Configuration;
  8 +
  9 +@Configuration
  10 +public class Lock4jConfiguration {
  11 +
  12 + static {
  13 + // 手动加载 Lock4jRedisKeyConstants 类,因为它不会被使用到
  14 + // 如果不加载,会导致 Redis 监控,看到它的 Redis Key 枚举
  15 + ClassUtil.loadClass(Lock4jRedisKeyConstants.class.getName());
  16 + }
  17 +
  18 + @Bean
  19 + public DefaultLockFailureStrategy lockFailureStrategy() {
  20 + return new DefaultLockFailureStrategy();
  21 + }
  22 +
  23 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/lock4j/core/DefaultLockFailureStrategy.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.lock4j.core;
  2 +
  3 +import cn.iocoder.dashboard.common.exception.ServiceException;
  4 +import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
  5 +import com.baomidou.lock.LockFailureStrategy;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +
  8 +/**
  9 + * 自定义获取锁失败策略,抛出 {@link cn.iocoder.dashboard.common.exception.ServiceException} 异常
  10 + */
  11 +@Slf4j
  12 +public class DefaultLockFailureStrategy implements LockFailureStrategy {
  13 +
  14 + @Override
  15 + public void onLockFailure(String key, long acquireTimeout, int acquireCount) {
  16 + log.debug("[onLockFailure][线程:{} 获取锁失败,key:{} 获取超时时长:{} ms]", Thread.currentThread().getName(), key, acquireTimeout);
  17 + throw new ServiceException(GlobalErrorCodeConstants.CONCURRENCY_REQUESTS);
  18 + }
  19 +
  20 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/lock4j/core/Lock4jRedisKeyConstants.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.lock4j.core;
  2 +
  3 +import cn.iocoder.dashboard.framework.redis.core.RedisKeyDefine;
  4 +import org.redisson.api.RLock;
  5 +
  6 +import static cn.iocoder.dashboard.framework.redis.core.RedisKeyDefine.KeyTypeEnum.HASH;
  7 +
  8 +/**
  9 + * Lock4j Redis Key 枚举类
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +public interface Lock4jRedisKeyConstants {
  14 +
  15 + RedisKeyDefine LOCK4J = new RedisKeyDefine("分布式锁",
  16 + "lock4j:%s", // 参数来自 DefaultLockKeyBuilder 类
  17 + HASH, RLock.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); // Redisson 的 Lock 锁,使用 Hash 数据结构
  18 +
  19 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/lock4j/package-info.java 0 → 100644
  1 +/**
  2 + * 分布式锁组件,使用 https://gitee.com/baomidou/lock4j 开源项目
  3 + */
  4 +package cn.iocoder.dashboard.framework.lock4j;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/SysRedisKeyConstants.java
... ... @@ -8,7 +8,7 @@ import java.time.Duration;
8 8 import static cn.iocoder.dashboard.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
9 9  
10 10 /**
11   - * Redis Key 枚举类
  11 + * System Redis Key 枚举类
12 12 *
13 13 * @author 芋道源码
14 14 */
... ...
src/main/java/cn/iocoder/dashboard/modules/tool/controller/test/ToolTestDemoController.http 0 → 100644
  1 +### 请求 /get-permission-info 接口 => 成功
  2 +GET {{baseUrl}}/tool/test-demo/get?id=1
  3 +Authorization: Bearer {{token}}
... ...
src/main/java/cn/iocoder/dashboard/modules/tool/controller/test/ToolTestDemoController.java
1 1 package cn.iocoder.dashboard.modules.tool.controller.test;
2 2  
  3 +import cn.hutool.core.thread.ThreadUtil;
3 4 import cn.iocoder.dashboard.common.pojo.CommonResult;
4 5 import cn.iocoder.dashboard.common.pojo.PageResult;
5 6 import cn.iocoder.dashboard.framework.excel.core.util.ExcelUtils;
... ... @@ -8,6 +9,7 @@ import cn.iocoder.dashboard.modules.tool.controller.test.vo.*;
8 9 import cn.iocoder.dashboard.modules.tool.convert.test.ToolTestDemoConvert;
9 10 import cn.iocoder.dashboard.modules.tool.dal.dataobject.test.ToolTestDemoDO;
10 11 import cn.iocoder.dashboard.modules.tool.service.test.ToolTestDemoService;
  12 +import com.baomidou.lock.annotation.Lock4j;
11 13 import io.swagger.annotations.Api;
12 14 import io.swagger.annotations.ApiImplicitParam;
13 15 import io.swagger.annotations.ApiOperation;
... ... @@ -21,6 +23,7 @@ import javax.validation.Valid;
21 23 import java.io.IOException;
22 24 import java.util.Collection;
23 25 import java.util.List;
  26 +import java.util.concurrent.TimeUnit;
24 27  
25 28 import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
26 29 import static cn.iocoder.dashboard.framework.logger.operatelog.core.enums.OperateTypeEnum.EXPORT;
... ... @@ -62,7 +65,11 @@ public class ToolTestDemoController {
62 65 @ApiOperation("获得测试示例")
63 66 @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
64 67 @PreAuthorize("@ss.hasPermission('tool:test-demo:query')")
  68 + @Lock4j // 分布式锁
65 69 public CommonResult<ToolTestDemoRespVO> getTestDemo(@RequestParam("id") Long id) {
  70 + if (true) { // 测试分布式锁
  71 + ThreadUtil.sleep(5, TimeUnit.SECONDS);
  72 + }
66 73 ToolTestDemoDO testDemo = testDemoService.getTestDemo(id);
67 74 return success(ToolTestDemoConvert.INSTANCE.convert(testDemo));
68 75 }
... ...
src/main/resources/application-dev.yaml
... ... @@ -81,6 +81,13 @@ apollo:
81 81 username: ${spring.datasource.username}
82 82 password: ${spring.datasource.password}
83 83  
  84 +--- #################### 服务保障相关配置 ####################
  85 +
  86 +# Lock4j 配置项
  87 +lock4j:
  88 + acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
  89 + expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
  90 +
84 91 --- #################### 监控相关配置 ####################
85 92  
86 93 # Actuator 监控端点的配置项
... ...
src/main/resources/application-local.yaml
... ... @@ -81,6 +81,13 @@ apollo:
81 81 username: ${spring.datasource.username}
82 82 password: ${spring.datasource.password}
83 83  
  84 +--- #################### 服务保障相关配置 ####################
  85 +
  86 +# Lock4j 配置项
  87 +lock4j:
  88 + acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
  89 + expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
  90 +
84 91 --- #################### 监控相关配置 ####################
85 92  
86 93 # Actuator 监控端点的配置项
... ...