这是我参与更文挑战的第9天,活动详情查看: 更文挑战
前言:
幂等性指的是多次操作,结果是一致的,其任意多次执行所产生的影响均与一次执行的影响相同,例如我们多个线程去查询数据库,多次操作数据库数据要保证一致。现在企业级的事项目大部分都是分布式的,而在分布式环境下幂等是非常常见也是我们必须要解决的问题;
常见的幂等解决方案:
- 唯一键索引
- 分布式锁 (redis(jedis、redisson)或zookeeper实现)
- tonken机制 (流水号,防止页面重复提交)
- 悲观锁(获取数据的时候加锁(锁表或锁行))
- 乐观锁 (基于版本号version实现, 在更新数据那一刻校验数据)
- 状态机 (状态变更, 更新数据时判断状态)
我们今天主要说一个代码层面通过注解的形式
来实现一个通用幂等组件
其大体流程如下图:
设计注解(ElementType.METHOD
方法层面):
(默认值expireTime为过期时间也就是一定时间内要求 当前用户当前任务只能执行一次)
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 幂等校验
*
* @author taoze
* @version 1.0
* @date 4/6/21 11:00 AM
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiIdempotent {
/**
* 过期时间 默认14400秒
*
* @return
*/
int expireTime() default 14400;
}
复制代码
扫描注解AOP(可以根据自己的业务去实现自己的注解扫描验证条件
):
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* 幂等效验Aop
* (BaseController只是统一返回结果集及项目个性化要求,可以不写)
* @author taoze
* @version 1.0
* @date 4/6/21 11:06 AM
*/
@Aspect
@Component
@Slf4j
public class IdempotentAspect extends BaseController {
@Autowired
private IdempotentHandler idempotentHandler;
@Around(value = "@annotation(com.a.b.service.api.idempotent.ApiIdempotent)")
public Object IdempotentAspectMethod(ProceedingJoinPoint point) throws Throwable {
long startExecute = System.currentTimeMillis();
MethodSignature signature = (MethodSignature)point.getSignature();
ApiIdempotent annotation = signature.getMethod().getAnnotation(ApiIdempotent.class);
int expireTime = annotation.expireTime();
Object[] args = point.getArgs();
//获取header
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes)ra;
HttpServletRequest request = sra.getRequest();
String requestId = request.getHeader("requestId");
if (StringUtils.isBlank(requestId)) {
return this.success();
}
Integer userId = getCurrentUserId();
if(null==userId){
return this.success();
}
String url = request.getRequestURL().toString();
int startIndex = StringUtils.ordinalIndexOf(url, "/", 3);
String key = url.substring(startIndex + 1) + userId;
log.info("IdempotentAspect -> IdempotentAspectMethod url = [{}] | @ApiIdempotent = [{}] | param = [{}]", url,
expireTime, args);
if (!idempotentHandler.checkToken(key, requestId, expireTime)) {
return this.success();
}
Object proceed = point.proceed();
log.info("IdempotentAspect -> IdempotentAspectMethod url = [{}] 结束,耗时: {}", url,
(System.currentTimeMillis() - startExecute));
return proceed;
}
}
复制代码
幂等校验业务实现(小编用的Redis,大家也可以根据自己的情况使用别的):
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 幂等校验
*
* @author taoze
* @version 1.0
* @date 4/2/21 11:46 AM
*/
@Component
@Slf4j
public class IdempotentHandler {
@Autowired
private RedisConnection<String, Object> redisConnectionCustom;
@Autowired
private PeppaCoreRedisClientConfig redisClientConfig;
/**
* 效验幂等
*
* @param key
* @param value 请求Id
* @param expireTime 过期时间
* @return
*/
public boolean checkToken(String key, String value, int expireTime) {
if (StringUtils.isBlank(value) || StringUtils.isBlank(key)) {
throw new DomainException(ClassroomServiceErrorEnum.ASSET_PARAM_ERROR);
}
key = key.replace("/", "-");
Long addNum = redisClientConfig.sAdd(RedisConstant.IDEMPOTENT_TOKEN_KEY + key, value);
if (addNum == 1) {
redisClientConfig.expire(RedisConstant.IDEMPOTENT_TOKEN_KEY + key, expireTime);
return true;
}
return false;
}
/**
* 效验幂等 默认过期时间 14400秒
*
* @param key
* @param value
* @return
*/
public boolean checkToken(String key, String value) {
return checkToken(key, value, 14400);
}
}
复制代码
ok!一个简单的幂等组件实现了,当前代码只是普通的幂等实现,大家也可以根据自己的需求去做优化优化,希望可以对大家有帮助,有不对的地方希望大家可以提出来的,共同成长;
整洁成就卓越代码,细节之中只有天地
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END