A lock is a lock that guarantees mutual exclusivity for access to the state of a resource. In practice, the state is usually a string. Using Redis to implement locks, the main is to put the state in Redis, using its atomicity, when other threads access, if the state already exists in Redis, some operations will not be allowed. Spring Boot uses Redis primarily through RedisTemplate(or StringRedisTemplate).
Now let’s use Spring Boot + Redis to implement redis distributed lock
1. First we refer to the Redis dependency that comes with spring-boot
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Copy the code
2. Understand the main commands of Redis locking
- SETNX (SET if Not exist) : Sets the key to value and returns 1 if and only if the key does Not exist. If the given key already exists, SETNX does nothing and returns 0.
- GETSET: Sets the given key to value and returns the old value of the key. Obtain the old value based on the key, and then set the new value.
- EXPIRE sets the lifetime for a given key. When the key expires, it will be deleted automatically.
3. Redis lock
/** ** lock * @param key Lock unique flag * @param value Current time + timeout time * @return
*/
public boolean lock(String key, String value){
if(stringRedisTemplate.opsForValue().setIfAbsent(key,value)){
return true; } /** * the following code is to prevent operation exceptions after locking, Not running unlock operation Prevent deadlocks * / / / get the expiration date of the lock String currentValue = (String) stringRedisTemplate. OpsForValue () get (key); // Suppose the lock expiresif(! Stringutils.isempty (currentValue) && long.parselong (currentValue) < System.currentTimemillis ()){// Get the time of the last lock and set the time of the new lock String oldValue = (String) stringRedisTemplate.opsForValue().getAndSet(key,value); // Check whether the timestamp is the same as that of the previous lockif(! StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue) ){return true; }}return false;
Copy the code
4. Redis unlocked
/** * Unlock * @param key * @param time */ public void unlock(String key,String time){try {// Get the lock timestamp String currentValue = stringRedisTemplate.opsForValue().get(key); // Check whether the timestamp passed in is the same as the lock timestampif(! Strings. IsNullOrEmpty (currentValue) && CurrentValue.equals (time)){// Delete the lock if it is the same stringRedisTemplate.opsForValue().getOperations().delete(key); } } catch (Exception e) { log.error("Unlock failed, exception {}",e); }}Copy the code
5. Simulate the seckill scenario
@RestController @Slf4j public class RedisController { @Autowired private RedisLock redisLock; Private static final int TIMEOUT = 5 * 1000; private static final int TIMEOUT = 5 * 1000; @override public void Spike(String productId){// Long time = system.currentTimemillis () + TIMEOUT;if(! redisLock.lock(productId,String.valueOf(time))){return "The queue is too large. Please try again later."; } int stockNum = stock.get(productId); // Query the inventory of the commodity, if it is 0, the activity endsif(stockNum==0){
throw new OperationFailedException("End of activity");
}else{// order (simulate different openids) orders.put(keyutil.getopenid (),productId); // Inventory reduction without processing, there will be a high concurrency oversold situation, under the number, more than inventory reduction situation. // The inventory is not stored in map due to concurrency. StockNum -= 1; stockNum -= 1; try{ Thread.sleep(100); Catch (InterruptedException e){e.printStackTrace(); } stock. Put (productId,stockNum); } // unlock redisLock.unlock(productId, string.valueof (time)); }}Copy the code
Note: The test concurrency can be tested using Apache AB concurrent load pressure