Prevent repeated submission of AOP + annotation

Idempotent problem

Definition of idempotency in Http/1.1: one and multiple requests for a resource should have the same result for the resource itself (except for problems such as network timeout). In other words, any number of executions will have the same impact on the resource itself as one execution

Under what circumstances is idempotent required (scenario)

Repeated submissions are often encountered in business, whether the request is re initiated due to network problems and unable to receive the request results, or the repeated submission is caused by front-end operations.

In the transaction and payment system, this kind of repeated submission is particularly obvious: users click repeatedly to submit orders, and only one order should be generated in the background

Services that declare idempotency believe that external callers will make multiple calls. In order to prevent multiple changes to the state of system data caused by multiple external calls, the service is designed as idempotent

  1. Front end duplicate submission

  2. Interface timeout retry

  3. Repeated consumption of messages, etc

Why should an interface implement idempotent

Repeat submission, and only one response result corresponding to this data will be generated in the background

Idempotent core thought

Idempotent is guaranteed through a unique business order number

Anti duplicate submission strategy (solution)

  1. Optimistic lock

  2. Anti duplication table (table to prevent data duplication)

    • The implementation method uses the unique index of mysql
    • technological process:
    • Create a de duplication table, establish a unique index for a standard field, the client requests the server, and the server inserts some information requested this time into this table
    • Because a field in the table is a unique index, if the insertion is successful, it indicates that there is no information requested this time, and continue to execute the requested business logic. If the insertion fails, it indicates that the current request has been executed and returns directly
  3. Distributed lock

    • setNX command based on redis
    • setnx key v: set the value of key to v if and only if the key does not exist. If the given key exists, setnx will not do anything. The command returns 1 when it is set successfully and 0 when it fails
    • technological process:
    • The client requests the server first and will get a unique field that can represent the requested business
    • Store this field in redis in the form of SETNX, and set the corresponding timeout according to the business
    • If the setting is successful, it proves that this is the first request, and the subsequent business logic is executed
    • If the setting fails, it indicates that the current request has been executed and returns directly
  4. Token token

    • When the client requests a page, the server will generate a globally unique ID as a token and save it in redis
    • Then, when requesting the business interface again, carry the token and put it in the request header.
    • The server will verify the token. If the verification is successful, the business will be executed and the token in redis will be deleted
    • If it is judged that the token does not exist in redis, it means that the operation is repeated, and the duplicate mark is directly returned to the client, which ensures that the business code will not be executed repeatedly

Idempotency of Get, Post, Put and Delete

  1. get is used to obtain resources without side effects, so it is idempotent
  2. post is often used to add and modify data to the database. Each call will produce new data. The data often changes, so it is not idempotent
  3. put is often used to create and update a specified piece of data. If the data does not exist, it will be created. If it exists, it will update the data. The side effects of multiple calls are the same as that of one call, so it satisfies idempotence
  4. The side effects of one or more delete calls on the system are the same, so they are idempotent

At which level should idempotent design be performed

Layer 1: app, pc, etc

Layer 2: load balancing

The third layer: gateway layer, which mainly does routing forwarding, request authentication, identity authentication, flow restriction, etc. If idempotency is implemented in the gateway layer, the business code needs to be written in the gateway layer, which is generally not recommended

The fourth layer: business layer: it mainly deals with business logic, so it is not suitable

The fifth layer: persistence layer: when dealing with the database, if you don't do it, it may have a certain impact on the data, so you usually do idempotent verification in the persistence layer

Layer 6: database layer

How to prevent repeated submission in the project

Prevent repeated submission based on annotation + AOP aspect. Analyze the annotation from AOP aspect. When submitting the first request of the interface, store the interface request mark (composed of interface method, parameter, request token, etc.) to redis, and set the timeout time T. each request of the interface first checks the interface mark in redis. If there is an interface request mark, it is determined that the interface is repeatedly submitted and intercepted and returned

  1. Custom annotation (expiration time can be set)

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface PreventDuplication {
        /**
         * Anti duplicate submission expiration time 10s (using the features of redis key)
         * @return
         */
        long expireSecond() default 10;
    }
    
  2. Define the aspect and intercept the Controller for surround notification (you can customize what needs to be intercepted)

    /**
     * @description:Prevent duplicate submission of facet classes
     * @author:xiaoyige
     * @createTime:2022/4/21 20:44
     * @version:1.0
     */
    @Slf4j
    //@Aspect
    @Component
    public class PreventDuplicationAspect {
        private static final String point_cut = "execution(public * *..controller..*.*(..))";
    
        @Autowired
        RedisTemplate redisTemplate;
    
        @Around(point_cut)
        public Object interceptor(ProceedingJoinPoint p) throws Throwable {
    
            MethodSignature signature = (MethodSignature) p.getSignature();
            Method method = signature.getMethod();
            if (method != null) {
                log.info("The current method is:{}.{},Parameters are:{}",
                        p.getTarget().getClass(), method.getName(), p.getArgs());
            }
            //Get duplicate submission comments
            PreventDuplication preventDuplication = AnnotatedElementUtils.findMergedAnnotation(method, PreventDuplication.class);
    
            if (preventDuplication == null) {
                return p.proceed();
            }
    
            //Generate key
            String redisCacheKey = getRedisCacheKey(method, p.getArgs());
    
            //Query whether there is a key cache in redis
            if (redisTemplate.hasKey(redisCacheKey)) {
                throw new RuntimeException("Data has been submitted, please wait!");
            } else {
             //The key is stored in redis
                redisTemplate.opsForValue().set(redisCacheKey, "testUser", preventDuplication.expireSecond(), TimeUnit.SECONDS);
            }
    
            //Execute the method normally and return
            try {
                return p.proceed();
            } catch (Exception e) {
                //Ensure that the time limit flag is released when the method execution is abnormal
                redisTemplate.delete(redisCacheKey);
            }
            return null;
    
        }
    
        /**
         * Assembly key
         *
         * @param method
         * @param args
         * @return
         */
        private String getRedisCacheKey(Method method, Object[] args) {
            StringBuilder sb = new StringBuilder();
            sb.append(method.getName()).append(":");
            for (Object arg : args) {
                sb.append(arg.toString());
            }
            return sb.toString();
        }
    }
    
    
    
    
    
    
    
    
    

Tags: Java Redis

Posted by aspekt9 on Sun, 22 May 2022 05:04:51 +0300