Idempotence realization-interface idempotence

Interface idempotence

1. What is idempotence

For the same business operation, no matter how many times it is called, the result will be the same.
That is to say, the extra effect produced by the method call once and the call multiple times is the same, and it is idempotent.

2. Why is idempotence needed?

In an environment with high system concurrency, it is very likely that the client or the caller cannot receive timely feedback from the server or even the call timeout problem due to network, congestion and other problems. In short, it means that the requester has called your service, but has not received any information, in a completely confusing state. For example, the order issue may encounter the following problems:
1. When creating an order, the first call to the service timed out. Will the second call result in two orders?
2. When the order is created successfully to reduce the inventory, will the deduction be made for the first time the inventory reduction is overtime?
3. When the order is paid, the server has successfully deducted the money, but the interface feedback has timed out. At this time, if the payment is called again, will there be an extra deduction?
As a consumer, the first two are acceptable, and the third is MMP, hahaha! ! !
This situation generally has the following two solutions:
1. The server provides an API to query whether the operation is successful. After the first timeout, the caller calls the query interface. If it finds it, it will go through the successful process, and if it fails, it will go fail. Process.
2. The server needs to use an idempotent method to ensure that the results of one and multiple requests are consistent.

3. Scenarios that produce idempotence

The problem of idempotence can be seen everywhere in our development, distributed and micro-service architecture;
  1. Due to network fluctuations, repeated requests may be caused;
  2. The user repeats the operation, the user may unintentionally trigger multiple orders and multiple transactions when using the product, or even deliberately trigger multiple transactions without responding;
  3. The application uses a failure or timeout retry mechanism (such as nginx retry, RPC retry or business layer retry, etc.)
  4. The interface of the third-party platform: (such as: payment success callback interface), because of exceptions caused multiple asynchronous callbacks;
  5. The middleware/application service may also retry according to its own characteristics
  6. The user double-clicks the submit button;
  7. The page is refreshed repeatedly;
  8. Use the browser back button to repeat the previous operation, resulting in repeated submission of the form;
  9. Use the browser history to repeatedly submit the form;
  10. Repeated HTTP requests from the browser;
  11. Repetitive execution of timed tasks;

3. The idempotence of RESTFUL HTTP

  • GET: Just get the resource, without any side effects on the resource itself, it is naturally idempotent.
  • HEAD: In essence, like GET, getting header information is mainly for probing, and it is idempotent.
  • OPTIONS: Get the methods supported by the current URL, so it is also idempotent.
  • DELETE: Used to delete resources, with side effects, but it should satisfy idempotence. For example, to delete a resource based on id, the caller can call it N times without worrying about the error caused (it changes according to business requirements).
  • Put and Post: Both can be used for adding/modifying. The difference between using Put requests is idempotent, and Post is not idempotent. It should not be simple to add or modify, depending on whether the business needs it. Idempotent to choose.
Insert picture description here

4. The realization of idempotence

  1. Front-end implementation: Section 5
  2. Back-end implementation: Section 6
Insert picture description here

5. Front-end idempotence

5.1 The button can only be clicked once

For the client-side interaction interface, you can intercept part of it on the front end, such as preventing repeated submission of the form, graying out the button, hiding it, and not clicking. However, the front-end interceptor is obviously aimed at ordinary users. Anyone who knows a little technology can simulate the request call interface, so the back-end idempotence is very important.

5.2 Token mechanism (front-end + back-end)

For situations such as the client's continuous click or the caller's timeout retry, such as submitting an order, such an operation can be implemented using the Token mechanism to prevent repeated submissions.

The realization of TOKEN mechanism: Simply put, when calling the interface, the caller first requests a global ID (TOKEN) from the backend, and the request is carried out with this global ID. The backend needs to verify the global ID to ensure the power Waiting for operation

Insert picture description here

The main process steps are as follows:

  • Before the client enters the business operation (for example, when entering the submission page), first send a request to obtain the token, the server will generate a globally unique ID and save it in redis, and return this ID to the client at the same time;
  • The client must carry this token when calling the service request, and it is generally placed on the request header;
  • The server operates redis to delete the token by doing del. If the deletion is successful, it proves that it is the first request, and the subsequent operations are performed;
  • If the deletion fails, it means that the operation is repeated, and the specified result is directly returned to the client.

Through the above process analysis, the only key point is how to generate this global unique ID. In distributed services, there is often a service that generates a global ID to ensure the uniqueness of the ID. However, the amount of engineering and the difficulty of implementation are relatively large. UUID data The amount is relatively large, here you can choose the snowflake algorithm to generate a globally unique ID.

Disadvantages of the token mechanism:
Every time a business request is requested, there will be additional requests (a business to obtain a token request once and determine whether the token exists). In fact, in a real production environment, there may only be about 10 requests for 10,000 requests that will be retried. For these 10 requests, we let 9990 requests have additional requests. (Of course redis performance is very good, time-consuming will not be too obvious)

6. Back-end idempotence realization

6.1 Normal way

The example process is as follows:

  1. Received Alipay payment success request
  2. Check whether the current order has been processed according to trade_no
  3. If the order has been processed, return directly, if not processed, continue to execute
  4. Open local affairs
  5. The local system adds money to the user
  6. Set the order status to success
  7. In the process of submitting a local transaction, for the same order, if Alipay informs multiple times at the same time, what will happen? When multiple notifications reach step 2 at the same time, the query orders are all unprocessed and will continue to be executed downwards. Finally, the user will be paid twice locally. This method is suitable for stand-alone devices, and the notifications are executed in order, and can only be used to write and play by yourself.

6.2 JVM locking method

In method 1, there is a problem with concurrency. At this time, we use the Lock in java to prevent concurrent operations. The process is as follows: 1. Receive a successful Alipay payment request
  1. Call Lock in java to lock
  2. Check whether the current order has been processed according to trade_no
  3. If the order has been processed, return directly, if not processed, continue to execute
  4. Open local affairs
  5. The local system adds money to the user
  6. Set the order status to success
  7. Commit local affairs
  8. Release Lock lock analysis problem:
    Lock can only work in one jvm. If multiple requests are processed by the same system, the above method of using Lock is no problem. However, in Internet systems, most of them are deployed in clusters. In the system, multiple sets of code will be deployed after the same set of codes. If Alipay sends multiple notifications at the same time and forwards them to different machines through load balancing, the above lock will not work. At this time, multiple requests are equivalent to lock-free processing, and the result in mode 1 will appear again. At this point we need distributed locks for processing.

6.3 Token mechanism (same as 5.2)

6.4 Unique index

You can limit the repeated insertion of data. When the data is repeated, an exception will be thrown into the database to ensure that there will be no dirty data.
For the insert operation, when we insert data, there will be two situations?
1. Auto-incrementing primary key
If it is an auto-incrementing primary key, multiple insertions will definitely cause repeated insertions.
2. Business primary key (unique index)
If it is a business primary key, suppose we make a unique index on the order id, provided that we can guarantee this amount The id of the order is unique, even if it is submitted multiple times.

6.5 The realization of pessimistic lock (query statement lock for update)

  1. Received Alipay payment success request
  2. Open local things
  3. Query order information and add pessimistic lock select * from t_order where order_id = trade_no for update;
  4. Determine if the order has been processed
  5. If the order has been processed, return directly, if not processed, continue to execute
  6. Add money to the local system to the user
  7. Set the order status to success
  8. Submit local things
The key point is for update. For update, please explain:
1. When thread A executes for update, the data will lock the current record. When other threads execute this line of code (there is also for update if there is no problem. ), it will wait for thread A to release the lock before it can acquire the lock and continue subsequent operations.
2. When the transaction is submitted, the lock acquired by for update will be automatically released.

Problem: for update can normally achieve the effects we need, and can ensure the idempotence of the interface, but there are some shortcomings:
if the business processing is time-consuming, and in the case of concurrency, the subsequent threads will be in a waiting state for a long time, occupying a lot of threads, so These threads are in an invalid waiting state. The number of threads in our web service is generally limited. If a large number of threads are in a waiting state due to acquiring the for update lock, it is not conducive to concurrent operation of the system.

6.6 Implementation of Finite State Machine

When designing a document-related business, or a 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 limited state At this time, if the state machine is already in the next state, but there is a change in the previous state, theoretically it cannot be changed. In this way, the idempotency of the finite state machine is guaranteed.
For the update operation, if the order status needs to be modified in the business, the order is pending payment, payment in progress, payment success, payment failure, order timeout, etc., it is best to support only single item changes (irreversible) in the design, so that in the where condition when updating You can add status=the last status I expect. If you call it multiple times, it will actually be executed once.
update xx set status = "Paying" where status ='Pending payment' and id=xx (Note: update is currently read, multiple updates will wait)

6.7 Implementation of Optimistic Locking

If you update existing data, you can perform lock updates, or you can use optimistic locking when designing the table structure, such as adding a version field to do optimistic locking, which can ensure execution efficiency and idempotence. The version version of Optimistic Locking must be incremented when updating business data.
You can also use update with conditions to achieve optimistic locking, and achieve optimistic locking through version or other conditions

  • The main process of the example is as follows:
  1. Received Alipay payment success request
  2. Query order information
    select * from t_order where order_id = trade_no;
  3. Determine whether the order has been processed
  4. If the processing returns directly without processing, continue to execute
  5. Open local affairs
  6. Add money to local system users
  7. Set the order status to success (here to determine the result returned by update)
    update t_order set status = 1 where order_id = trade_no where status = 0;
  • Returns 1 to indicate that the update was successful, and the transaction is committed
  • Return to other means that the update failed and the transaction is rolled back
  • Because update is the current read, that is, multiple transactions to update the same row of data, update will lock the data on an exclusive lock until the transaction is committed.
  • example:
    比如,表名A,字段名为 number,如下的SQL语句:
        语句1:update A set number=number+ 5 where id=1;
        语句2:update A set number=number+ 7 where id=1;

        假设这两条SQL语句同时被mysql执行,id=1的记录中number字段的原始值为 10,那么是否有可能出现这种情况:

        语句1和2因为同时执行,他们得到的number的值都是10,都是在10的基础上分别加5和7,导致最终number被更新为15或17,而不是22?
         
    这个其实就是 关系型数据库本身就需要解决的问题。首先,他们同时被MySQL执行,你的意思其实就是他们是并发执行的,而并发执行的事务在关系型数据库中是有专门的理论支持的- ACID,事务并行等理论,所有关系型数据库实现,包括Oracle, MySQL都需要遵循这个原理。简单一点理解就是锁的原理。这个时候第一个update会持有id=1这行记录的 排它锁,第二个update需要持有这个记录的排它锁的才能对他进行修改,正常的话, 第二个update会阻塞,直到第一个update提交成功,他才会获得这个锁,从而对数据进行修改。也就是说,按照关系型数据库的理论,这两个update都成功的话,id=1的number一定会被修改成22。
Insert picture description here

6.8 Anti-weight table + unique constraint realization

Need to add a table to prevent data duplication
CREATE TABLE `t_uq_dipose` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT, 
    `ref_type` varchar(32) NOT NULL DEFAULT '' COMMENT '关联对象类型', 
    `ref_id` varchar(64) NOT NULL DEFAULT '' COMMENT '关联对象id', 
    PRIMARY KEY (`id`), 
    UNIQUE KEY `uq_1` (`ref_type`,`ref_id`) COMMENT '保证业务唯一性' 
    ) ENGINE=InnoDB;

For any business, there is a business type (ref_type), and the business has a globally unique order number. When the business comes, first check whether there are related records in the t_uq_dipose table. If it does not exist, continue to release.
The process is as follows:

  1. Received Alipay payment success request
  2. Query t_uq_dipose (condition ref_id, ref-type) to determine whether the order has been processed
    select * from t_uq_dipose where ref_type = '充值订单' and ref_id = trade_no;
  3. Determine whether the order has been processed
  4. If the order has been processed, return directly, if not processed, continue to release
  5. Open local affairs
  6. Add money to the local system to the user
  7. Set the order status to success
  8. Insert data into t_uq_dipose, the insertion is successful, the local transaction is submitted, the insertion fails, and the local transaction is rolled back:
try{ 
    insert into t_uq_dipose (ref_type,ref_id) values ('充值订单',trade_no); 
    提交本地事务:
}catch(Exception e){ 
    回滚本地事务; 
}
Insert picture description here

Note:
For the same business, the ref_type is the same. When concurrent, inserting data will only have one success, and the other will violate the unique constraint. Enter the catch logic, the current transaction will be rolled back, and the last operation will succeed. Thereby ensuring idempotent operation.
This method can be written as a general method, but in the case of large business volume, t_uq_dipose inserting data will become the bottleneck of the system. It is necessary to consider sub-table operations to solve performance problems.
In the above process, inserting records into t_uq_dipose is best performed at the end. The reason: the insert operation will lock the table. Putting it at the end can minimize the time to lock the table and improve the concurrency of the system. Regarding the message service, how can consumers ensure the idempotence of message processing?
Each message has a unique message id, which is similar to the trade_no in the above business. The idempotence of message consumption can be realized by using the above method.

reference

Idempotence

  1. https://www.cnblogs.com/Leo_wl/p/12640651.html
  2. https://www.cnblogs.com/itsoku123/p/10860527.html
  3. https://zhuanlan.zhihu.com/p/151438657
  4. https://www.bilibili.com/video/BV1YJ411V7aj?p=15
    Database:
  5. https://blog.csdn.net/zmemorys/article/details/104814110
  6. https://blog.csdn.net/silyvin/article/details/79294508?tdsourcetag=s_pctim_aiomsg
  7. https://blog.csdn.net/winy_lm/article/details/49718193
  8. https://blog.csdn.net/sinat_27143551/article/details/89968902