Commit 53db9de93dc56dbd212fbe763daa46b44934c1d7

Authored by YunaiV
1 parent 94dfe411

增加 Redis 分布式锁

README.md
@@ -38,6 +38,8 @@ @@ -38,6 +38,8 @@
38 1. Redis 监控:监控 Redis 数据库的使用情况,使用的 Redis Key 管理 38 1. Redis 监控:监控 Redis 数据库的使用情况,使用的 Redis Key 管理
39 1. Java 监控:基于 Spring Boot Admin 实现 Java 应用的监控 39 1. Java 监控:基于 Spring Boot Admin 实现 Java 应用的监控
40 1. 链路追踪:基于 SkyWalking 实现性能监控,特别是链路的追踪 40 1. 链路追踪:基于 SkyWalking 实现性能监控,特别是链路的追踪
  41 +1. 分布式锁:基于 Redis 实现分布式锁,满足并发场景
  42 +1. 幂等组件:基于 Redis 实现幂等组件,解决重复请求问题
41 43
42 ### 研发工具 44 ### 研发工具
43 45
@@ -45,7 +47,6 @@ @@ -45,7 +47,6 @@
45 1. 代码生成:前后端代码的生成(Java、Vue、SQL),支持 CRUD 下载 47 1. 代码生成:前后端代码的生成(Java、Vue、SQL),支持 CRUD 下载
46 1. 系统接口:基于 Swagger 自动生成相关的 RESTful API 接口文档 48 1. 系统接口:基于 Swagger 自动生成相关的 RESTful API 接口文档
47 1. 数据库文档:基于 Screw 自动生成数据库文档 49 1. 数据库文档:基于 Screw 自动生成数据库文档
48 -1. 幂等组件:基于 Redis 实现幂等组件,解决重复请求问题  
49 50
50 ## 在线体验 51 ## 在线体验
51 52
http-client.env.json
1 { 1 {
2 "local": { 2 "local": {
3 - "baseUrl": "http://127.0.0.1:8080/api", 3 + "baseUrl": "http://127.0.0.1:48080/api",
4 "token": "test1" 4 "token": "test1"
5 } 5 }
6 } 6 }
@@ -30,6 +30,10 @@ @@ -30,6 +30,10 @@
30 <druid.version>1.2.4</druid.version> 30 <druid.version>1.2.4</druid.version>
31 <mybatis-plus.version>3.4.1</mybatis-plus.version> 31 <mybatis-plus.version>3.4.1</mybatis-plus.version>
32 <redisson.version>3.14.1</redisson.version> 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 <skywalking.version>8.3.0</skywalking.version> 38 <skywalking.version>8.3.0</skywalking.version>
35 <spring-boot-admin.version>2.3.1</spring-boot-admin.version> 39 <spring-boot-admin.version>2.3.1</spring-boot-admin.version>
@@ -125,7 +129,7 @@ @@ -125,7 +129,7 @@
125 <dependency> 129 <dependency>
126 <groupId>com.ctrip.framework.apollo</groupId> 130 <groupId>com.ctrip.framework.apollo</groupId>
127 <artifactId>apollo-client</artifactId> <!-- 引入 Apollo Client 库,实现内嵌的配置中心 --> 131 <artifactId>apollo-client</artifactId> <!-- 引入 Apollo Client 库,实现内嵌的配置中心 -->
128 - <version>1.7.0</version> 132 + <version>${apollo.version}</version>
129 </dependency> 133 </dependency>
130 134
131 <!-- Job 定时任务相关 --> 135 <!-- Job 定时任务相关 -->
@@ -134,6 +138,13 @@ @@ -134,6 +138,13 @@
134 <artifactId>spring-boot-starter-quartz</artifactId> 138 <artifactId>spring-boot-starter-quartz</artifactId>
135 </dependency> 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 <dependency> 149 <dependency>
139 <groupId>org.apache.skywalking</groupId> 150 <groupId>org.apache.skywalking</groupId>
@@ -217,7 +228,7 @@ @@ -217,7 +228,7 @@
217 228
218 <dependency> 229 <dependency>
219 <groupId>cn.smallbun.screw</groupId> 230 <groupId>cn.smallbun.screw</groupId>
220 - <artifactId>screw-core</artifactId> 231 + <artifactId>screw-core</artifactId> <!-- 实现数据库文档 -->
221 <version>${screw.version}</version> 232 <version>${screw.version}</version>
222 </dependency> 233 </dependency>
223 234
src/main/java/cn/iocoder/dashboard/common/exception/enums/GlobalErrorCodeConstants.java
@@ -29,7 +29,8 @@ public interface GlobalErrorCodeConstants { @@ -29,7 +29,8 @@ public interface GlobalErrorCodeConstants {
29 ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常"); 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 ErrorCode UNKNOWN = new ErrorCode(999, "未知错误"); 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,7 +8,7 @@ import java.time.Duration;
8 import static cn.iocoder.dashboard.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING; 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 * @author 芋道源码 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 package cn.iocoder.dashboard.modules.tool.controller.test; 1 package cn.iocoder.dashboard.modules.tool.controller.test;
2 2
  3 +import cn.hutool.core.thread.ThreadUtil;
3 import cn.iocoder.dashboard.common.pojo.CommonResult; 4 import cn.iocoder.dashboard.common.pojo.CommonResult;
4 import cn.iocoder.dashboard.common.pojo.PageResult; 5 import cn.iocoder.dashboard.common.pojo.PageResult;
5 import cn.iocoder.dashboard.framework.excel.core.util.ExcelUtils; 6 import cn.iocoder.dashboard.framework.excel.core.util.ExcelUtils;
@@ -8,6 +9,7 @@ import cn.iocoder.dashboard.modules.tool.controller.test.vo.*; @@ -8,6 +9,7 @@ import cn.iocoder.dashboard.modules.tool.controller.test.vo.*;
8 import cn.iocoder.dashboard.modules.tool.convert.test.ToolTestDemoConvert; 9 import cn.iocoder.dashboard.modules.tool.convert.test.ToolTestDemoConvert;
9 import cn.iocoder.dashboard.modules.tool.dal.dataobject.test.ToolTestDemoDO; 10 import cn.iocoder.dashboard.modules.tool.dal.dataobject.test.ToolTestDemoDO;
10 import cn.iocoder.dashboard.modules.tool.service.test.ToolTestDemoService; 11 import cn.iocoder.dashboard.modules.tool.service.test.ToolTestDemoService;
  12 +import com.baomidou.lock.annotation.Lock4j;
11 import io.swagger.annotations.Api; 13 import io.swagger.annotations.Api;
12 import io.swagger.annotations.ApiImplicitParam; 14 import io.swagger.annotations.ApiImplicitParam;
13 import io.swagger.annotations.ApiOperation; 15 import io.swagger.annotations.ApiOperation;
@@ -21,6 +23,7 @@ import javax.validation.Valid; @@ -21,6 +23,7 @@ import javax.validation.Valid;
21 import java.io.IOException; 23 import java.io.IOException;
22 import java.util.Collection; 24 import java.util.Collection;
23 import java.util.List; 25 import java.util.List;
  26 +import java.util.concurrent.TimeUnit;
24 27
25 import static cn.iocoder.dashboard.common.pojo.CommonResult.success; 28 import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
26 import static cn.iocoder.dashboard.framework.logger.operatelog.core.enums.OperateTypeEnum.EXPORT; 29 import static cn.iocoder.dashboard.framework.logger.operatelog.core.enums.OperateTypeEnum.EXPORT;
@@ -62,7 +65,11 @@ public class ToolTestDemoController { @@ -62,7 +65,11 @@ public class ToolTestDemoController {
62 @ApiOperation("获得测试示例") 65 @ApiOperation("获得测试示例")
63 @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class) 66 @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
64 @PreAuthorize("@ss.hasPermission('tool:test-demo:query')") 67 @PreAuthorize("@ss.hasPermission('tool:test-demo:query')")
  68 + @Lock4j // 分布式锁
65 public CommonResult<ToolTestDemoRespVO> getTestDemo(@RequestParam("id") Long id) { 69 public CommonResult<ToolTestDemoRespVO> getTestDemo(@RequestParam("id") Long id) {
  70 + if (true) { // 测试分布式锁
  71 + ThreadUtil.sleep(5, TimeUnit.SECONDS);
  72 + }
66 ToolTestDemoDO testDemo = testDemoService.getTestDemo(id); 73 ToolTestDemoDO testDemo = testDemoService.getTestDemo(id);
67 return success(ToolTestDemoConvert.INSTANCE.convert(testDemo)); 74 return success(ToolTestDemoConvert.INSTANCE.convert(testDemo));
68 } 75 }
src/main/resources/application-dev.yaml
@@ -81,6 +81,13 @@ apollo: @@ -81,6 +81,13 @@ apollo:
81 username: ${spring.datasource.username} 81 username: ${spring.datasource.username}
82 password: ${spring.datasource.password} 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 # Actuator 监控端点的配置项 93 # Actuator 监控端点的配置项
src/main/resources/application-local.yaml
@@ -81,6 +81,13 @@ apollo: @@ -81,6 +81,13 @@ apollo:
81 username: ${spring.datasource.username} 81 username: ${spring.datasource.username}
82 password: ${spring.datasource.password} 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 # Actuator 监控端点的配置项 93 # Actuator 监控端点的配置项