How do I prevent the interface from repeating requests

The problem

I recently encountered a data exception bug caused by repeated interface requests. The scenario is as follows: In a notification module, the teacher sends the notification to the parent. After the parent clicks on the notification, the client needs to invoke an ACK interface to tell the server that the parent has read the notification and insert the parent’s information into the notify_read table, which is about (notify_id, member_id). Add the read_num field of notify to the read_num field. When a network problem occurs, the client requests this interface multiple times.

int count = queryRead(notifyId, memberId);
if( count == 0){
    insertNotify(notifyId, memberId);
    incrementNotifyReadNum(notify, memberId);
}
Copy the code

When concurrent requests occur, it is possible that one request is executed to insertNotify at the same time that another request is executed to insertNotify. Cause the incrementNotifyReadNum method to be executed twice, and the data is in error.

The solution

Since our service is distributed, we need to consider the case of two requests landing on two different servers when solving the problem. There were two possible solutions to this problem.

Using a database

Create a new intermediate table

CREATE TABLE request (rkey varchar (32) PRIMARY KEY (' rkey '))Copy the code

Each request inserts a record into the table before service processing and deletes the record after the request is complete.

try{
    insertRequest(String.format("NOTIFY_%s_%s", notify, memberId))

    int count = queryRead(notifyId, memberId);
    if( count == 0){
        insertNotify(notifyId, memberId);
        incrementNotifyReadNum(notify, memberId);
    }
}catch(Exception e){
    //doSomething();
}finally{
 deleteRequest(String.format("NOTIFY_%s_%s", notify, memberId));
}

Copy the code

The unique key in the database is used to ensure that only one request can be inserted into the request table at the same time. Failed requests are not processed. The solution is simple, but it will consume DB performance and be difficult to scale if the request volume is large.

Use Redis counter

The process of using Redis is similar to that of using a database. Each time a request is made, a setnx operation is performed on the key. If the setnx operation returns 1, only the current request is processing this service. When the setnx operation returns zero, it indicates that there are other requests processing the business.

**SETNX key value **

Set the value of key to value if and only if the key does not exist. If the given key already exists, SETNX does nothing. SETNX is short for SET if Not eXists. Time complexity: O(1) Returned value: 1 is returned if the setting is successful. Setting failed, return 0.

String key = String.format("NOTIFY_%s_%s", notify, memberId);
try{
    int requestCount =  redisServer.setnx(key);
    
    if(requestCount == 1){
        int count = queryRead(notifyId, memberId);
        if( count == 0){
            insertNotify(notifyId, memberId);
            incrementNotifyReadNum(notify, memberId);
        }
    }
    
}catch(Exception e){
    //doSomething(); }finally{// Remember to release redisserver.delete (key); }Copy the code

Redis’ setnx command is often used to implement distributed locking, which makes use of this feature to prevent the interface from repeating requests. Compared to the database implementation, the Redis implementation performs better and is easier to extend.