This is the ninth day of my participation in the Gwen Challenge.More article challenges
Preface:
Idempotent refers to the fact that the results of multiple operations are consistent, and the impact of any multiple execution is the same as that of a single execution. For example, when multiple threads are querying a database, the data in the database must be consistent for multiple operations. Now enterprise projects are mostly distributed, and in the distributed environment idempotent is very common and we must solve the problem;
Common idempotent solutions:
- Unique key index
- Distributed locks (Redis (Jedis, Redisson) or ZooKeeper implementations)
- Tonken mechanism (serial number to prevent page duplication)
- Pessimistic locking (locking (table or row) while fetching data)
- Optimistic lock (implemented based on version number, verifies data at the moment it is updated)
- State machine (state change, judge state when updating data)
We’ll focus today on a code layer that implements a generic idempotent component in the form of annotations
The general process is shown as follows:
Design annotations (ElementType.METHOD METHOD level) : (The default value expireTime is expireTime, which requires the current user to execute the current task only once within a certain period of time)
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:00am */ @target ({elementtype.method}) @Retention(retentionPolicy.runtime) public @interface ApiIdempotent {/** * expiration time default 14400 s ** @return */ int expireTime() default 14400; }Copy the code
Scan annotation AOP (you can implement your own annotation scan validation criteria based on your business) :
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; /** * IDEmpotence Aop * (BaseController only returns uniform result set and project personalization requirements, * @author Taoze * @version 1.0 * @date 4/6/21 11:06am */ @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(); / / get the 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 = [{}] End {}", url, (System.currentTimeMillis() - startExecute)); return proceed; }}Copy the code
Idempotent check business implementation (xiaofian with Redis, we can also use other according to their own situation) :
import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * IdempotentHandler ** @author Taoze * @version 1.0 * @date 4/2/21 11:46am */ @component @slf4j public class IdempotentHandler { @Autowired private RedisConnection<String, Object> redisConnectionCustom; @Autowired private PeppaCoreRedisClientConfig redisClientConfig; /** * effect idempotency ** @param key * @param value Request Id * @param expireTime Expiration time * @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; } public Boolean checkToken(String key, String key, String key, String key, String key, String key, String key, String key, String key) String value) { return checkToken(key, value, 14400); }}Copy the code
Ok! A simple idempotent component implementation, the current code is just ordinary idempotent implementation, we can also according to their own needs to do optimization, I hope to help you, there are wrong places I hope we can put forward, grow together;
Neat makes for great code, and there’s only so much detail