Commit 53db9de93dc56dbd212fbe763daa46b44934c1d7
1 parent
94dfe411
增加 Redis 分布式锁
Showing
13 changed files
with
109 additions
and
6 deletions
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
pom.xml
| ... | ... | @@ -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
src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/SysRedisKeyConstants.java
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 | 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 监控端点的配置项 | ... | ... |