Instant resubmit thousands of times, I used SpringBoot + Redis Kang Zhu

Preface:

In the actual development project, a Foreign exposed interfaces are often faced with a large number of repetitive moment request is submitted , if want to filter out duplicate requests cause harm to the business, it needs to achieve power and so on !

We explain the concept of idempotent:

Affect any number of times to perform are the same as produced by the impact of the first performance . According to this meaning, the ultimate meaning is the impact of the database can only be a one-time, can not be repeated.

How to ensure that idempotency, usually have the following means:

1, the database build unique index, you can ensure that the final data into the database is only a 2, token mechanisms, first get a token before each interface request, and then again when the next request this token in the header plus the body of the request, the background verification, if the verification by deleting the token, the next request is again determined token 3, pessimistic locking or optimistic locking, pessimistic locking can guarantee that every other time for update sql can not update data (in the database engine is innodb time, select the conditions must be a unique index to prevent lock the whole table) 4, after the first inquiry to determine, first, whether there is data by querying the database, if there is proof has been requested before, directly rejected the request, if there is not, it proved to be the first to come direct release.

redis achieve schematics automatic idempotent:

 

image.png

First, build redis service Api

1, first of all is to build redis server.

2, is introduced into the springboot the redis Stater, or Spring jedis may be encapsulated, used behind the main set is its api methods and methods exists, here we use the packaged redisTemplate springboot

 

/**
 * redis工具类
 */
@Component 
publicclassRedisService{
 @Autowired
 privateRedisTemplate redisTemplate;
 /**
     * 写入缓存
     * @param key
     * @param value
     * @return
     */
 publicbooleanset(finalString key, Object value) {
 boolean result = false;
 try{
 ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            result = true;
        } catch(Exception e) {
            e.printStackTrace();
        }
 return result;
    }
 /**
     * 写入缓存设置时效时间
     * @param key
     * @param value
     * @return
     */
 publicboolean setEx(finalString key, Object value, Long expireTime) {
 boolean result = false;
 try{
 ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
            result = true;
        } catch(Exception e) {
            e.printStackTrace();
        }
 return result;
    }
 /**
     * 判断缓存中是否有对应的value
     * @param key
     * @return
     */
 publicboolean exists(finalString key) {
 return redisTemplate.hasKey(key);
    }
 /**
     * 读取缓存
     * @param key
     * @return
     */
 publicObjectget(finalString key) {
 Object result = null;
 ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
        result = operations.get(key);
 return result;
    }
 /**
     * 删除对应的value
     * @param key
     */
 publicboolean remove(finalString key) {
 if(exists(key)) {
 Booleandelete= redisTemplate.delete(key);
 returndelete;
        }
 returnfalse;
    }
} 

Second, the custom annotation AutoIdempotent

A custom annotation, the main purpose of the definition of this comment is to add it in the way the need to achieve power, etc., all of a method annotated it will automatically idempotent. If background scanning by the reflection to the annotation, the processing method will automatically idempotent using annotation ElementType.METHOD element is represented only on the method, etentionPolicy.RUNTIME represents at runtime.

 

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public@interfaceAutoIdempotent{
} 

Three, token create and test

1, token service interface

We create a new interface to create a token service, which is mainly two methods to create a token, used to verify a token. Create a token is generated primarily a string token check if the main is to convey the request object, an object, why should they request it? The main role is to get the header inside the token, and then test to obtain the specific error message is returned to the front end by throwing the Exception. Reorganization: micro-channel public number, search cloud library technology team, ID: souyunku

 

publicinterfaceTokenService{
 /**
     * 创建token
     * @return
     */
 public String createToken();
 /**
     * 检验token
     * @param request
     * @return
     */
 publicboolean checkToken(HttpServletRequest request) throwsException;
} 

2, token service implementation class

redis cited token service to create token using a random algorithm to generate random uuid string of tools, and then put into the redis (in order to prevent redundant data retention, provided here expiration time 10000 seconds, depending on the specific video services), If put successful, and returns the token value. checkToken method is to obtain the value of the token (if not get in the header, they are taken from the paramter), if it does not exist in the header, directly thrown exception. The exception information may be captured to the interceptors, then return to the distal end.

 

@Service
publicclassTokenServiceImplimplementsTokenService{
 @Autowired
 privateRedisService redisService;
 /**
     * 创建token
     *
     * @return
     */
 @Override
 publicString createToken() {
 String str = RandomUtil.randomUUID();
 StrBuilder token = newStrBuilder();
 try{
            token.append(Constant.Redis.TOKEN_PREFIX).append(str);
            redisService.setEx(token.toString(), token.toString(),10000L);
 boolean notEmpty = StrUtil.isNotEmpty(token.toString());
 if(notEmpty) {
 return token.toString();
            }
        }catch(Exception ex){
            ex.printStackTrace();
        }
 returnnull;
    }
 /**
     * 检验token
     *
     * @param request
     * @return
     */
 @Override
 publicboolean checkToken(HttpServletRequest request) throwsException{
 String token = request.getHeader(Constant.TOKEN_NAME);
 if(StrUtil.isBlank(token)) {// header中不存在token
            token = request.getParameter(Constant.TOKEN_NAME);
 if(StrUtil.isBlank(token)) {// parameter中也不存在token
 thrownewServiceException(Constant.ResponseCode.ILLEGAL_ARGUMENT, 100);
            }
        }
 if(!redisService.exists(token)) {
 thrownewServiceException(Constant.ResponseCode.REPETITIVE_OPERATION, 200);
        }
 boolean remove = redisService.remove(token);
 if(!remove) {
 thrownewServiceException(Constant.ResponseCode.REPETITIVE_OPERATION, 200);
        }
 returntrue;
    }
} 

Fourth, the interceptor configuration

1, Web configuration class, implement WebMvcConfigurerAdapter, the main role is to add to the configuration autoIdempotentInterceptor class, so we went to the interceptor to take effect, pay attention to the use of @Configuration notes, so it's time to start the container can be added into the context of

 

@Configuration
publicclassWebConfigurationextendsWebMvcConfigurerAdapter{
 @Resource
 privateAutoIdempotentInterceptor autoIdempotentInterceptor;
 /**
     * 添加拦截器
     * @param registry
     */
 @Override
 publicvoid addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(autoIdempotentInterceptor);
 super.addInterceptors(registry);
    } 
} 

2, intercept processor: The main function is to intercept the scan AutoIdempotent annotation to the method, then call checkToken tokenService () method of checking whether the token is correct, if the exception is caught up into the abnormality information will be returned to the front end json

 

/**
 * 拦截器
 */
@Component
publicclassAutoIdempotentInterceptorimplementsHandlerInterceptor{
 @Autowired
 privateTokenService tokenService;
 /**
     * 预处理
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
 @Override
 publicboolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throwsException{
 if(!(handler instanceofHandlerMethod)) {
 returntrue;
        }
 HandlerMethod handlerMethod = (HandlerMethod) handler;
 Method method = handlerMethod.getMethod();
 //被ApiIdempotment标记的扫描
 AutoIdempotent methodAnnotation = method.getAnnotation(AutoIdempotent.class);
 if(methodAnnotation != null) {
 try{
 return tokenService.checkToken(request);// 幂等性校验, 校验通过则放行, 校验失败则抛出异常, 并通过统一异常处理返回友好提示
            }catch(Exception ex){
 ResultVo failedResult = ResultVo.getFailedResult(101, ex.getMessage());
                writeReturnJson(response, JSONUtil.toJsonStr(failedResult));
 throw ex;
            }
        }
 //必须返回true,否则会被拦截一切请求
 returntrue;
    }
 @Override
 publicvoid postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throwsException{
    }
 @Override
 publicvoid afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throwsException{
    }
 /**
     * 返回的json值
     * @param response
     * @param json
     * @throws Exception
     */
 privatevoid writeReturnJson(HttpServletResponse response, String json) throwsException{
 PrintWriter writer = null;
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=utf-8");
 try{
            writer = response.getWriter();
            writer.print(json);
        } catch(IOException e) {
        } finally{
 if(writer != null)
                writer.close();
        }
    }
} 

Fifth, test cases

1, the analog service requesting

First, we need to pass / get / token path to acquire specific token by getToken () method, and then we call testIdempotence method, which notes above the @AutoIdempotent, the interceptor will intercept all requests to determine the processing method when there are above the annotation time, checkToken () method is invoked TokenService, if Exception caught an exception will be thrown caller, let's take a look at the simulation request:

 

@RestController
publicclassBusinessController{
 @Resource
 privateTokenService tokenService;
 @Resource
 privateTestService testService;
 @PostMapping("/get/token")
 publicString  getToken(){
 String token = tokenService.createToken();
 if(StrUtil.isNotEmpty(token)) {
 ResultVo resultVo = newResultVo();
            resultVo.setCode(Constant.code_success);
            resultVo.setMessage(Constant.SUCCESS);
            resultVo.setData(token);
 returnJSONUtil.toJsonStr(resultVo);
        }
 returnStrUtil.EMPTY;
    }
 @AutoIdempotent
 @PostMapping("/test/Idempotence")
 publicString testIdempotence() {
 String businessResult = testService.testIdempotence();
 if(StrUtil.isNotEmpty(businessResult)) {
 ResultVo successResult = ResultVo.getSuccessResult(businessResult);
 returnJSONUtil.toJsonStr(successResult);
        }
 returnStrUtil.EMPTY;
    }
} 

2, using a request postman

First visit get / token path to get to the specific token:

image.png

Use to get to the token, and then put a specific request to the header, you can see the first request is successful, then we request a second time:

 

image.png

The second request is to return to the repetitive operation, visible through repetitive verification, and then we repeated requests to let only the first successful, the second is a failure:

image.png

VI Summary

本篇介绍了使用springboot和拦截器、redis来优雅的实现接口幂等,对于幂等在实际的开发过程中是十分重要的,因为一个接口可能会被无数的客户端调用,如何保证其不影响后台的业务处理,如何保证其只影响数据一次是非常重要的,它可以防止产生脏数据或者乱数据,也可以减少并发量,实乃十分有益的一件事。而传统的做法是每次判断数据,这种做法不够智能化和自动化,比较麻烦。而今天的这种自动化处理也可以提升程序的伸缩性。


 

发布了16 篇原创文章 · 获赞 5 · 访问量 536

Guess you like

Origin blog.csdn.net/weixin_46329358/article/details/104610030