当前位置: 首页 > news >正文

基于redisson实现接口幂等性

说明

实现幂等性的方法有很多种,本次仅基于redisson锁进行处理
本次开发基于自行封装的redis开发组件,有兴趣的可以看下redis组件

代码编写

pom.xml引入redisson

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.39.0</version>
</dependency>

redisson相关配置

  • CusRedissonConfiguration(此次仅单体)
@Configuration
public class CusRedissonConfiguration {private final static String REDIS_ADDRESS_PATTERN = "%s://%s:%s";@Beanpublic RedissonClient singleRedissonClient(SingleProperties singleProperties) {Config config = new Config();config.useSingleServer().setAddress(String.format(REDIS_ADDRESS_PATTERN,BooleanUtils.isTrue(singleProperties.getEncryptEnabled()) ?RedisConstants.redisProtocol.ENCRYPT_REDIS : RedisConstants.redisProtocol.REDIS,singleProperties.getHost(), singleProperties.getPort())).setUsername(singleProperties.getUsername()).setPassword(singleProperties.getPassword());return Redisson.create(config);}@Bean@ConditionalOnProperty(prefix = "cus.redisson", name = "idempotent-default", havingValue = "true", matchIfMissing = true)public Idempotent idempotent(){return new DefaultIdempotentUtil();}}
  • CusRedissonConfiguration
@Configuration
public class CusRedissonConfiguration {private final static String REDIS_ADDRESS_PATTERN = "%s://%s:%s";@Beanpublic RedissonClient singleRedissonClient(SingleProperties singleProperties) {Config config = new Config();config.useSingleServer().setAddress(String.format(REDIS_ADDRESS_PATTERN,BooleanUtils.isTrue(singleProperties.getEncryptEnabled()) ?RedisConstants.redisProtocol.ENCRYPT_REDIS : RedisConstants.redisProtocol.REDIS,singleProperties.getHost(), singleProperties.getPort())).setUsername(singleProperties.getUsername()).setPassword(singleProperties.getPassword());return Redisson.create(config);}@Bean@ConditionalOnProperty(prefix = "cus.redisson", name = "idempotent-default", havingValue = "true", matchIfMissing = true)public Idempotent idempotent(){return new DefaultIdempotentUtil();}}
  • SingleProperties
@Component
@ConfigurationProperties(prefix = "cus.redisson.single-properties")
public class SingleProperties {private String host = RedisConstants.defaultRedisInfo.DEFAULT_IP;private String port = RedisConstants.defaultRedisInfo.DEFAULT_PORT;private String username;private String password;private Integer database = RedisConstants.defaultRedisInfo.DEFAULT_DATABASE;private Boolean encryptEnabled = false;public String getHost() {return host;}public void setHost(String host) {this.host = host;}public String getPort() {return port;}public void setPort(String port) {this.port = port;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public Boolean getEncryptEnabled() {return encryptEnabled;}public void setEncryptEnabled(Boolean encryptEnabled) {this.encryptEnabled = encryptEnabled;}public Integer getDatabase() {return database;}public void setDatabase(Integer database) {this.database = database;}
}

lock代码编写

  • Lock
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Lock {//锁键String lockKey();//等待时间long waitTime() default 60L;//自动释放时间long leaseTime();//时间单位TimeUnit timeUnit() default TimeUnit.SECONDS;//锁类型String lockType() default "FAIR";//校验lockKey是否存在boolean lockKeyExistFlag() default false;
}
  • LockAspect
    此处为核心处理代码,如果需要检验lockKey是否是被伪造的,可以将lockKeyExistFlag设置为true,由idempotent执行相关校验逻辑
    getLockKey可以视为通用方法,后续考虑放到core中
@Aspect
@Component
public class LockAspect {private final static Logger LOGGER = LoggerFactory.getLogger(LockAspect.class);private LockServiceFactory lockServiceFactory;private Idempotent idempotent;@Autowiredpublic void setLockServiceFactory(LockServiceFactory lockServiceFactory) {this.lockServiceFactory = lockServiceFactory;}@Autowiredpublic void setIdempotent(Idempotent idempotent) {this.idempotent = idempotent;}@Around("@annotation(lock)")public Object around(ProceedingJoinPoint joinPoint, Lock lock) throws Throwable {String lockKey = this.getLockKey(joinPoint, lock);if (StringUtils.isEmpty(lockKey)) {LOGGER.error("lockKey is empty");return null;}//校验lockKey是否是被伪造的,lockKey的生成和校验逻辑可以自定义if (lock.lockKeyExistFlag() && !idempotent.lockKeyExist(lockKey)) {LOGGER.error("the lockKey was forged");return null;}LockService lockService = lockServiceFactory.getLockServiceByType(lock.lockType());CusLockInfo cusLockInfo = new CusLockInfo();cusLockInfo.setLockKey(lockKey);cusLockInfo.setWaitTime(lock.waitTime());cusLockInfo.setLeaseTime(lock.leaseTime());cusLockInfo.setTimeUnit(lock.timeUnit());try {lockService.lock(cusLockInfo);if (!lockService.lock(cusLockInfo)) {throw new RuntimeException("Failed to acquire lock");}return joinPoint.proceed();} finally {//开启了伪造校验,结束后需进行清除if (lock.lockKeyExistFlag() && !idempotent.clear(lockKey)) {LOGGER.error("Verification resource lockKey clearance failed, lockKey value is{}", lockKey);}lockService.unLock(cusLockInfo);}}/*** 结合el表达式解析lockKey** @param joinPoint 切点* @param lock      锁信息* @return lockKey*/private String getLockKey(ProceedingJoinPoint joinPoint, Lock lock) {// 获取方法签名MethodSignature signature = (MethodSignature) joinPoint.getSignature();// 创建表达式解析器ExpressionParser parser = new SpelExpressionParser();// 创建评估上下文EvaluationContext context = new StandardEvaluationContext();// 将方法参数绑定到上下文中String[] paramNames = signature.getParameterNames();Object[] paramValues = joinPoint.getArgs();for (int i = 0; i < paramNames.length; i++) {context.setVariable(paramNames[i], paramValues[i]);}// 解析lockKey的值return parser.parseExpression(lock.lockKey()).getValue(context, String.class);}
}

lockKey相关逻辑

  • Idempotent
    实现类可自定义,yml中指定idempotent-default为false,自行注册Idempotent的自定义实现类即可
public interface Idempotent {/*** 创建唯一性lockKey** @param args 构建lockKey的参数* @return lockKey*/String createUniqueLockKey(String... args);/*** 校验lockKey是否存在(是否是伪造的)** @param lockKey lockKey* @return lockKey是否存在*/Boolean lockKeyExist(String lockKey);/*** 清除校验数据* @param lockKey lockKey* @return 是否清除成功*/Boolean clear(String lockKey);
}
  • DefaultIdempotentUtil
@Component
public class DefaultIdempotentUtil implements Idempotent {private final static Logger logger = LoggerFactory.getLogger(DefaultIdempotentUtil.class);private final static String PREFIX = "token_%s_%s";public final static String DEFAULT_SERVERNAME = "default";public final static String UNIQUE_FLAG = "true";public RedisHelper redisHelper;@Autowiredpublic void setRedisHelper(RedisHelper redisHelper) {this.redisHelper = redisHelper;}/*** 获取token的接口需要防止疯狂获取导致redis暴库** @param serverName 服务名* @return token*/public String createUniqueTokenWithServerName(String serverName) {serverName = Optional.ofNullable(serverName).orElse(DEFAULT_SERVERNAME);String uuid = UUID.randomUUID().toString();String uniqueToken = String.format(PREFIX, serverName, uuid);logger.debug("{} generate unique_token {}", serverName, uniqueToken);redisHelper.strSet(uniqueToken, UNIQUE_FLAG);return uniqueToken;}@Overridepublic String createUniqueLockKey(String... args) {// 确保至少有一个参数被提供if (args == null || args.length == 0) {throw new IllegalArgumentException("At least one argument is required.");}// 使用第一个参数作为serverNamereturn createUniqueTokenWithServerName(args[0]);}@Overridepublic Boolean lockKeyExist(String lockKey){return StringUtils.isNotEmpty(redisHelper.strGet(lockKey));}@Overridepublic Boolean clear(String lockKey) {return redisHelper.del(lockKey);}
}

yml相关配置

spring:redis:host: ipport: portdatabase: 1username: rootpassword: rootjedis:pool:# 资源池中最大连接数# 默认8,-1表示无限制;可根据服务并发redis情况及服务端的支持上限调整max-active: ${SPRING_REDIS_POOL_MAX_ACTIVE:50}# 资源池运行最大空闲的连接数# 默认8,-1表示无限制;可根据服务并发redis情况及服务端的支持上限调整,一般建议和max-active保持一致,避免资源伸缩带来的开销max-idle: ${SPRING_REDIS_POOL_MAX_IDLE:50}# 当资源池连接用尽后,调用者的最大等待时间(单位为毫秒)# 默认 -1 表示永不超时,设置5秒max-wait: ${SPRING_REDIS_POOL_MAX_WAIT:5000}
cus:redisson:single-properties:host: ipport: portusername: rootpassword: rootidempotent-default: true

参考资料

[1].redis代码


http://www.mrgr.cn/news/94044.html

相关文章:

  • Jenkins链接私有仓库Failed to connect to repository,stderr: No ECDSA...的问题
  • bootloader相关部分
  • 通道注意力机制、空间注意力机制、混合注意力机制
  • 【Spring 事务】
  • PySide(PyQT),QGraphicsRectItem的setPos()和setRect()的坐标位置的区别
  • 【WRF-Urban】使用 LCZ 替换 WRF 运行中的 LUCC 数据
  • 【Yonghong 企业日常问题07 】 东方通TongWeb替代Tomcat的实战指南!
  • 如何使用logrotete定时切割mysql的慢日志
  • Android DUKPT - 3DES
  • shell编程——条件表达式和if判断
  • liunx磁盘挂载和jar启动命令
  • Vue3项目-大事件
  • 在资源有限中逆势突围:从抗战智谋到寒门高考的破局智慧
  • 瑞芯微RK3576(2)-调试过程中遇到的问题
  • 使用STM32CubeMX配置定时器中断实现LED每秒闪烁一次(STM32G070CBT6)
  • 制作自定义镜像
  • 【OpenGL】01-配置环境
  • react中字段响应式
  • 安装、配置和启动 ssh 服务,实现远程连接服务器
  • SVT-AV1源码分析函数 svt_av1_optimize_b