What? Your interface has not yet used interface equivalence? This article will let you know what is the interface equivalence and how to solve the interface equivalence

The question leads:

  • There are many operations in the system, no matter how many times they are done, they should produce the same effect or return the same result. For example 1. The front end repeatedly submits the selected data, and only one response result corresponding to this data should be generated in the background; 2. When we initiate a payment request, the user account should only be deducted once, 3. Create a business order, only one business request Creating one, creating multiple will cause big problems, etc. Many important situations require idempotent features to support.

solution:

  • Query operation: query once and query multiple times, the query result is the same when the data is unchanged. select is a natural idempotent operation;
  • Delete operation: Delete operation is also idempotent. Delete one time and multiple deletes all delete data. (Note that the returned results may be different, the deleted data does not exist, 0 is returned, multiple deleted data, multiple returned results);
  • Unique index to prevent new dirty data. For example: Alipay’s fund account, Alipay also has user accounts, each user can only have one fund account, how to prevent the creation of multiple fund accounts for the user, then add a unique index to the user ID in the fund account table, so a user adds Successfully record a fund account. Key points: unique index or unique composite index to prevent dirty data in new data (when the table has a unique index and an error is added during concurrency, you can query it again. The data should already exist, just return the result);
  • The token mechanism prevents repeated page submissions. Business requirements: The data on the page can only be clicked and submitted once; Reason: Due to repeated clicks, network retransmission, or nginx retransmission, the data will be repeatedly submitted; Solution: The cluster environment uses token plus redis (redis single thread Yes, processing needs to be queued); Single JVM environment: use token plus redis or token plus jvm memory. Processing flow: 1. Before data submission, apply for a token from the service. The token is placed in redis or jvm memory, and the token is valid; 2. After the submission, the token is verified in the background, and the token is deleted at the same time, and a new token is generated and returned. Token features: to apply, one-time validity, and current limit. Note: Redis needs to use the delete operation to determine the token. A successful deletion means that the token has passed verification. If select+delete is used to verify the token, it is not recommended to use it if there is a concurrency problem;
  • Pessimistic lock-lock acquisition when acquiring data. select * from table_xxx where id='xxx' for update; Note: The id field must be a primary key or a unique index, otherwise it is a lock table, which is a deadly pessimistic lock. It is usually used with transactions. The data lock time may be very long. Choose according to actual situation;
  • Optimistic locking-Optimistic locking only locks the table when the data is updated, and does not lock the table at other times, so it is more efficient than pessimistic locking. There are many ways to implement optimistic locking, which can be achieved through version or other status conditions:
  1. Realize update table_xxx set name=#name#,version=version+1 where version=#version# through the version number;
  2. Restricted by conditions update table_xxx set avai_amount=avai_amount-#subAmount# where avai_amount-#subAmount# >= 0 Requirements: quality-#subQuality# >=, this scenario is suitable for no version number, only update is for data security verification, suitable for inventory Model, share deduction and rollback share, higher performance;
  • Note: The update operation of optimistic lock is best to use the primary key or unique index to update, this is a row lock, otherwise the table will be locked during the update. It is better to change the above two sql to the following two
update table_xxx set name=#name#,version=version+1 where id=#id# and version=#version#;
update table_xxx set avai_amount=avai_amount-#subAmount# where id=#id# and avai_amount-#subAmount# >= 0;
  • Distributed lock-still take the example of inserting data. If the distribution is a system, it is difficult to construct a globally unique index. For example, the unique field cannot be determined. At this time, distributed locks can be introduced through a third-party system (redis or zookeeper), insert data or update data in the business system, acquire distributed locks, perform operations, and then release the locks. This is actually the idea of ​​introducing multi-threaded concurrent locks into multiple systems, that is, the solution in distributed systems Ideas. Key points: A long process process requires that it cannot be executed concurrently. You can obtain a distributed lock based on a certain flag (user ID + suffix, etc.) before the process is executed. When other processes are executed, acquiring the lock will fail, that is, the process only There can be one that can be executed successfully. After the execution is completed, release the distributed lock (distributed locks need to be provided by a third-party system);

  • select + insert——Background system with low concurrency, or some task JOB, in order to support idempotence and support repeated execution, the simple processing method is to query some key data first to determine whether it has been executed, and business processing is in progress. That's it. Note: Do not use this method for core high-concurrency processes;

  • State machine is idempotent-in designing document-related business, or task-related business, a state machine (state change diagram) will definitely be involved, that is, there is a state on the business document, and the state will change under different circumstances. In general, there is a finite state machine. At this time, if the state machine is already in the next state, a change of the previous state comes at this time, and theoretically it cannot be changed. In this way, the idempotency of the finite state machine is guaranteed . Note: There is a long state flow in order and other document business. It is necessary to have a deep understanding of the state machine, which is of great help to the improvement of business system design capabilities

  • How to ensure idempotence for APIs that provide external interfaces. For example, the payment interface provided by UnionPay: it needs to be connected to the merchant to submit the payment request with: source source, the seq serial number
    source+seq is uniquely indexed in the database to prevent multiple payments (when concurrent, only one request can be processed). Important: In order to support idempotent invocation of the external interface, the interface has two fields that must be passed, one is the source source and the other is the source serial number seq. These two fields are combined and uniquely indexed in the provider system, so as a third party When calling, first check in the local system to see if it has been processed and return the corresponding processing result; if it has not been processed, perform the corresponding processing and return the result. Note that in order to be idempotent and friendly, you must first check whether the transaction has been processed. If you insert the business system directly without querying, an error will be reported, but it has actually been processed.

Practical operation-solve interface equivalence based on token

  • Create interface add two methods, create token, check token
/**
 * @Author wyj PreventDuplicateSubmissions 接口冥等性  防止重复提交
 * @Date: 2020-12-12 20:41
 */
public interface IdempotentByTokenService {
    
    
    /**
     * 创建token
     * @return
     */
     R createToken();

    /**
     * 检查token
     * @return
     */
     boolean checkToken( );
}
  • Implement interface, implement method logic
/**
 * @Author wyj
 * @Date: 2020-12-12 20:46
 */
@Service
public class IdempotentByTokenServiceImpl implements IdempotentByTokenService {
    
    
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 创建token
     * @return
     */
    @Override
    public R createToken() {
    
    
        String str = UUID.randomUUID().toString().replace("-", "");
        StringBuilder token = new StringBuilder();
        token.append(Rediskey.IDEMPOTENT_PREFIX).append(str);
        redisTemplate.opsForValue().set(token.toString(), token.toString(), RedisExpire.FIVE_MINUTES, TimeUnit.SECONDS);
        return R.success(token.toString());
    }

    /**
     * 检验token
     * @return
     */
    @Override
    public boolean checkToken()  {
    
    
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String token = request.getHeader(Rediskey.IDEMPOTENT_TOKEN_NAME);
        if (StringUtils.isEmpty(token)) {
    
    
            token = request.getParameter(Rediskey.IDEMPOTENT_TOKEN_NAME);
            if (StringUtils.isEmpty(token)) {
    
    
                throw new ApiMallPlusException("参数不合法,未传防止重复提交token");
            }
        }
        //如果redis中不包含该token,说明token已经被删除了,抛出请求重复异常
        if (!redisTemplate.hasKey(token)){
    
    
            throw new ApiMallPlusException("上次请求还在处理,请稍后在请求!");
        }
        Boolean del=redisTemplate.delete(token);
        //如果删除不成功(已经被其他请求删除),抛出请求重复异常
        if (!del){
    
    
            throw new ApiMallPlusException("上次请求还在处理,请稍后在请求!");
        }
        return  true;
    }
}
  • Create a custom annotation, add this annotation to the method that needs to realize the idempotence of the interface, and realize the token verification
/**
 * @author wyj
 * 通过自定义注解在需要实现接口幂等性的方法上添加此注解,实现token验证
 */
@Target({
    
    ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiIdempotent {
    
    
}
  • Define the interface and other aspects, and mark the interface of @ApiIdempotent, the circumcision check token will be performed
/**
 * @Author wyj
 * @Date: 2020-12-13 13:02
 */
@Aspect
@Component
@Slf4j
public class IdempotentAspect {
    
    
    @Autowired
    private IdempotentByTokenService idempotentByTokenService;

    @Pointcut("@annotation(com.cgmcomm.mallplus.component.ApiIdempotent)")
    public void idempotentCut() {
    
    
    }

    @Around("idempotentCut()")
    public Object around(ProceedingJoinPoint point)  {
    
    
        return idempotentByTokenService.checkToken();
    }
}
  • test
/**
 * @Author wyj
 * @Date: 2020-12-13 11:13
 */
@RestController
@RequestMapping("api")
@Api(description = "获取接口冥等性token防止重复提交",value = "IdempotentController")
public class IdempotentController {
    
    

    @Autowired
    private IdempotentByTokenService idempotentByTokenService;

    /**
     * 获取接口冥等性token
     * @return
     */
    @GetMapping("/get/token")
    @ApiOperation("获取防止重复提交token")
    public Object  getToken(){
    
    
        return idempotentByTokenService.createToken();
    }


    @GetMapping("/test/Idempotence")
    @ApiIdempotent
    public boolean testIdempotence() {
    
    
        String token = "接口幂等性测试";
        return true;
    }
}
  • Test steps: first access the equivalent token interface of the get interface to get the token, and pass the obtained token to the test interface. After accessing the test interface twice, it will be found that the first time is successful, and the second time is a prompt not to repeat the operation.
  • The above is to achieve interface equivalence based on token

Guess you like

Origin blog.csdn.net/weixin_45528650/article/details/111148741