Commit 53db9de93dc56dbd212fbe763daa46b44934c1d7
1 parent
94dfe411
增加 Redis 分布式锁
Showing
13 changed files
with
109 additions
and
6 deletions
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
pom.xml
| @@ -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
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
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 监控端点的配置项 |