SoFunction
Updated on 2025-03-03

Sample code for JAVA to implement redis distributed double locking

Background: During the daily development process, I encountered a requirement, such as an object User (name, age, sex) with three attributes. Now the user needs to add a new interface to prevent this interface from being requested by multiple people at the same time, and data with the same name and age and gender are generated;

Thinking about this problem

If a thread calls a new interface for a user, it will query whether there is any relevant data in the database in the business, so as not to throw exception prompts and not allow the operation to be saved to the database; this consideration is our most common consideration. There is another problem. If it is an external system, the amount of concurrency involved in operations is particularly large, and the amount of concurrency that calls this interface is also large. Simply checking whether there is duplicate data in the library to prevent duplicate data from being inserted can only prevent part of the problem data from entering the library. If there are two users A and B at the same time, the name and age of the
(Age and gender are the same considerations); at this time, I checked from the library and there was no existing data. At this time, in order to prevent the duplicate data operated by these two users A and B into the library at the same time, we have to add a distributed lock (if synchronized can be used in a single application). The distributed architecture needs to use Redis distributed lock or Redission distributed lock to achieve corresponding control;

When designing distributed Redis locks to avoid the same name and age combination, or the same age and sex combination when adding User, you need to build a key that can uniquely identify these conditions. Since Redis locks are often used to ensure the atomicity of operations, and your need is to check and avoid duplicate data, it may actually be more inclined to use other data structures of Redis (such as sets, ordered sets, or hash tables) to assist the implementation rather than just using a separate key-value lock

Basic ideas for using Redis's key-value lock

1. Define the key of the lock: The key of the lock should be able to uniquely identify the resource or operation you want to protect. In your scenario, since it involves a combination check of multiple fields, you can consider these
The fields are combined into a string as a key. For example:

For a combination of name and age, you can use user🔒name:{name}:age:{age}.

For a combination of age and sex, you can use user🔒age:{age}:sex:{sex}.

2. Set the lock: Before trying to add a new User, try to set this lock. If the lock is set successfully (i.e. no other process or thread holds this lock), the check logic continues.

3. Check and insert: Under the protection of the lock, check whether there is already a User with the same name and age or a combination of age and sex in the database. If it does not exist, perform an insert operation.

4. Release the lock: Whether the operation is successful or failed, the lock must be released in the end so that other processes or threads can acquire the lock and perform the operation.

Combined with Redis data structure to avoid duplication

However, a more efficient way would be to use a Redis's set (Set) or an ordered set (Sorted Set) to store the existing combination and check if the new combination already exists.

1. Use collections:

  • For a combination of name and age, you can create a collection user:name_age, where each element is a string of {name}:{age}.
  • For a combination of age and sex, another collection user:age_sex can be created, where each element is a string of {age}:{sex}.
  • When adding a new User, first check whether the combination already exists in the corresponding collection. If it does not exist, it is added to the collection and performs a database insert operation.
    2. Use ordered sets (sorted in some order if needed):
  • Similar to a collection, but you can specify a score for the element to store and retrieve elements in a specific order.

Things to note

Performance Considerations: As elements in the collection increase, the inspection operation may slow down. Therefore, you may want to consider using hash tables or other data structures to optimize lookup performance.
Transactional: Ensure that the operations that check collections and insert databases are atomic to prevent data changes after checking but before inserting.
Lock timeout: Set the timeout time of the lock to prevent deadlock.
Lock granularity: Depending on your application scenario, you may need to adjust the granularity of the lock. For example, if operations are very frequent and can be subject to some degree of repeated checking, you can consider relaxing the granularity of the lock or using a lighter synchronization mechanism.

Implement code

Only avoid duplication of name and age

Some codes for the following implementation: I hope it can help everyone understand the ideas.

import ;  
import ;  
import ;  
import ;  
  
import ;  
  
@Service  
public class UserService {  
  
    @Autowired  
    private RedisTemplate<String, String> redisTemplate;  
  
    // Assumption of lock expiration time    private static final long LOCK_EXPIRATION_TIME = 10L; // 10 seconds  
    // Try to acquire the lock    private boolean tryLock(String key) {  
        ValueOperations<String, String> opsForValue = ();  
        // Try to set the lock, if the key does not exist, the setting will be successful, and the expiration time will be set        return (key, "locked", LOCK_EXPIRATION_TIME, );  
    }  
  
    // Release the lock    private void releaseLock(String key) {  
        (key);  
    }  
  
    // Added User logic    public void addUserIfNotExists(User user) {  
        String nameAgeLockKey = "user:lock:name:" + () + ":age:" + ();  
  
        // Try to acquire the lock, and the following business verification will continue to be performed only after successfully obtaining the lock.        if (tryLock(nameAgeLockKey)) {  
            try {  
                // Perform database check here (whether the same name and age already exist)                
                // If it does not exist, perform the insert operation  
                // Assuming the check is passed, perform the insertion operation (the specific database operation is omitted here)                ("User added successfully");  
  
            } finally {  
                // Release the lock                releaseLock(nameAgeLockKey);  
            }  
        } else {  
            // Failed to acquire the lock, it may be that other processes are processing the same combination            ("Failed to acquire lock(s), user addition may be in progress");  
        }  
    }  
  
    // ... Other codes ...}

Avoid name and age and sex duplication: use double distributed lock implementation:

import ;  
import ;  
import ;  
import ;  
  
import ;  
  
@Service  
public class UserService {  
  
    @Autowired  
    private RedisTemplate<String, String> redisTemplate;  
  
    // Assumption of lock expiration time    private static final long LOCK_EXPIRATION_TIME = 10L; // 10 seconds  
    // Try to acquire the lock    private boolean tryLock(String key) {  
        ValueOperations<String, String> opsForValue = ();  
        // Try to set the lock, if the key does not exist, the setting will be successful, and the expiration time will be set        return (key, "locked", LOCK_EXPIRATION_TIME, );  
    }  
  
    // Release the lock    private void releaseLock(String key) {  
        (key);  
    }  
  
    // Added User logic    public void addUserIfNotExists(User user) {  
    	//Add two locks        String nameAgeLockKey = "user:lock:name:" + () + ":age:" + ();  
        String ageSexLockKey = "user:lock:age:" + () + ":sex:" + ();  
  
        // Try to acquire two locks        if (tryLock(nameAgeLockKey) && tryLock(ageSexLockKey)) {  
            try {  
                // Perform database check here (whether there is a User with the same name and age or the same age and sex)                // If it does not exist, perform the insert operation  
                // Assuming the check is passed, perform the insertion operation (the specific database operation is omitted here)                ("User added successfully");  
  
            } finally {  
                // Release the lock Release twice                releaseLock(nameAgeLockKey);  
                releaseLock(ageSexLockKey);  
            }  
        } else {  
            // Failed to acquire the lock, it may be that other processes are processing the same combination            ("Failed to acquire lock(s), user addition may be in progress");  
        }  
    }  
  
    // ... Other codes ...}

Double locked pointsThe above code example simplifies error handling and retry logic. In actual applications, you may need to deal with various exceptions, such as the Redis server being unavailable, the lock being accidentally deleted or expired, etc. Additionally, if the business logic is complex or the execution time is long, you may want to consider using more advanced lock mechanisms such as Redis's publish/subscribe mode, Lua scripts, or Redis's RedLock algorithm to ensure the security and reliability of the lock.

Also, note that the setIfAbsent operation in the tryLock method is atomic, which means it completes the checking and setting operations in a single Redis command, thus avoiding race conditions. However, due to factors such as network latency, Redis server performance, multiple clients may still try to acquire the same lock almost simultaneously. Therefore, even if locks are used, you need to carefully design your business logic and error handling strategies.

This is the article about the example code of JAVA to implement redis distributed double locking. For more related content on JAVA redis distributed double locking, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!