Interface idempotence solution: Realize the implementation of interface idempotency based on token

1. What is idempotence

The idempotency of the interface - talk about the idempotency of the interface in detail, that is, the solution

2. Solve the problem of interface idempotence based on the token scheme

1. Introduction to token mechanism scheme

(1) Implementation ideas

It is a very common solution to ensure idempotency through the token mechanism, and it is also suitable for most scenarios. This solution requires a certain degree of interaction between the front and back ends to complete.
insert image description here
1) The server provides the token acquisition interface for the client to use. After the server generates the token, if it is currently a distributed architecture, store the token in redis, and if it is a single architecture, it can be stored in the jvm cache.
2) When the client obtains the token, it will initiate a request with the token.
3) After receiving the client request, the server will first judge whether the token exists in redis. If it exists, complete the business processing, and delete the token after the business processing is completed. If it does not exist, it means that the current request is a repeated request, and the corresponding identifier is directly returned to the client.

(2) Question: Execute the business first and then delete the token

Under high concurrency, it is very likely that the token exists at the time of the first visit to complete specific business operations. However, before the token is deleted, the client initiates a request with the token. At this time, because the token still exists, the second request will also pass the verification and perform specific business operations.

The idea of ​​a solution to this problem is to change parallel to serial. It will cause a certain performance loss and throughput reduction.
The first solution: add a thread lock to the business code execution and delete the token as a whole. When subsequent threads come to access again, the queue is blocked.

The second solution: using redis single thread and incr is an atomic feature. When the token is acquired for the first time, the token is used as the key to auto-increment. Then return the token. When the client carries the token to access and execute the business code, it does not need to delete it to determine whether the token exists, but continues to incr it. If the return value after incr is 2. It is a legal request to allow execution. If it is another value, it means an illegal request and returns directly.
insert image description here

(3) Question: delete the token first and then execute the business

In fact, there will be problems if you delete the token before executing the business. If the execution of the specific business code times out or fails, and no clear result is returned to the client, the client may retry
, but at this time the previous token has been deleted. If it is deleted, it will be considered as a repeated request and will no longer be
processed.
insert image description here

This solution does not require additional processing, and a token can only represent one request. Once the business execution is abnormal, let the client re-acquire the token and re-initiate an access. 推荐使用先删除token方案.

(4) Disadvantages of the scheme

But whether you delete the token first or delete the token later, there will be the same problem. Each business request will generate an additional request to obtain the token.

However, if the business fails or times out, in a production environment, at most ten out of ten thousand will fail. Then for these ten or so requests, the other nine thousand nine hundred requests will generate additional requests, and some The gain outweighs the loss. Although redis has good performance, it is also a waste of resources.

2. Implementation based on custom business process

insert image description here

(1) Obtain token interface

The front end first generates a token through the token interface, and at the same time, the token is stored in redis, and the expiration time is set.

@GetMapping("/genToken")
public String genToken(){
    
    
	String token = String.valueOf(IdUtils.nextId());
	redisTemplate.opsForValue().set(token,0,30, TimeUnit.MINUTES);
	return token;
}

(2) Add feign interceptor to order service

import feign.RequestInterceptor;
import feign.RequestTemplate;
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;
import java.util.Enumeration;

@Component
public class FeignInterceptor implements RequestInterceptor {
    
    

    @Override
    public void apply(RequestTemplate template) {
    
    

        //传递令牌
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes != null){
    
    
            HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
            if (request != null){
    
    
                Enumeration<String> headerNames = request.getHeaderNames();
                while (headerNames.hasMoreElements()){
    
    
                    String hearName = headerNames.nextElement();
                    // 从请求header中获取token,并且传递
                    if ("token".equals(hearName)){
    
    
                        String headerValue = request.getHeader(hearName);
                        //传递token
                        template.header(hearName,headerValue);
                    }
                }
            }
        }
    }
}
// 定义feign拦截器
@Bean
public FeignInterceptor feignInterceptor(){
    
    
	return new FeignInterceptor();
}

(3) Define the method of generating an order

/**
* 生成订单
* @param order
* @return
*/
@PostMapping("/genOrder")
public String genOrder(@RequestBody Order order, HttpServletRequest request){
    
    
	//获取令牌
	String token = request.getHeader("token");
	//校验令牌
	try {
    
    
		if (redisTemplate.delete(token)){
    
    
			//令牌删除成功,代表不是重复请求,执行具体业务
			order.setId(String.valueOf(idWorker.nextId()));
			order.setCreateTime(new Date());
			order.setUpdateTime(new Date());
			// 生成订单
			int result = orderService.addOrder(order);
			if (result == 1){
    
    
				System.out.println("success");
				return "success";
			}else {
    
    
				System.out.println("fail");
				return "fail";
			}
		}else {
    
    
			//删除令牌失败,重复请求
			System.out.println("repeat request");
			return "repeat request";
		}
	}catch (Exception e){
    
    
		throw new RuntimeException("系统异常,请重试");
	}
}

(4) test

Get the token through postman, put the token in the request header. Open two postman tab pages. Adding orders at the same time, it can be found that one execution is successful and the other repeats the request.

3. Implementation based on custom annotations

Directly embedding the token implementation into the method will cause a lot of repetitive code. Therefore, the above code can be modified through custom annotations. For methods that need to be idempotent, just add custom annotations.

(1) Custom idempotent annotations

/**
* 幂等性注解
*/
@Target({
    
    ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Idemptent {
    
    
}

(2) Add web interceptor

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

public class IdemptentInterceptor implements HandlerInterceptor {
    
    

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        if (!(handler instanceof HandlerMethod)) {
    
    
            return true;
        }

        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
		// 如果添加了该注解
        Idemptent annotation = method.getAnnotation(Idemptent.class);
        if (annotation != null){
    
    
            //进行幂等性校验
            checkToken(request);
        }

        return true;
    }


    @Autowired
    private RedisTemplate redisTemplate;

    //幂等性校验
    private void checkToken(HttpServletRequest request) {
    
    
        String token = request.getHeader("token");
        if (StringUtils.isEmpty(token)){
    
    
            throw new RuntimeException("非法参数");
        }

        boolean delResult = redisTemplate.delete(token);
        if (!delResult){
    
    
            //删除失败
            throw new RuntimeException("重复请求");
        }
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
    

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
    

    }
}

Register interceptor:

@Bean
public IdemptentInterceptor idemptentInterceptor() {
    
    
	return new IdemptentInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
    
    
	//幂等拦截器
	registry.addInterceptor(idemptentInterceptor());
	super.addInterceptors(registry);
}

(3) Use custom annotations

@Idemptent
@PostMapping("/genOrder")
public String genOrder(@RequestBody Order order){
    
    
	order.setId(String.valueOf(IdUtils.nextId()));
	order.setCreateTime(new Date());
	order.setUpdateTime(new Date());
	int result = orderService.addOrder(order);
	if (result == 1){
    
    
		System.out.println("success");
		return "success";
	}else {
    
    
		System.out.println("fail");
		return "fail";
	}
}

Guess you like

Origin blog.csdn.net/A_art_xiang/article/details/132106309