Article directory
Redis-Xi-01-Redis Distributed Lock (Lua script)
In SpringBook project, set key value nx px command is used to ensure the atomic operation of acquiring lock, and Lua script is used to ensure the atomic operation of releasing lock. (Single node Redis only)
Get Lock Release Lock Code
private static final Long RELEASE_SUCCESS = 1L; private static final String LOCK_SUCCESS = "OK"; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "EX"; private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; @SuppressWarnings("rawtypes") @Autowired private RedisTemplate redisTemplate; /** * This locking method can realize distributed locking only for single instance Redis * Not available for Redis clusters * Supporting duplication, thread safety * @param lockKey Key lock * @param clientId Locking Client Unique Identification (UUID) * @param seconds Lock expiration time * @return */ public Boolean getLock(String lockKey, String clientId, long seconds) { return (Boolean) redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> { Jedis jedis = (Jedis) redisConnection.getNativeConnection(); String result = jedis.set(lockKey, clientId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, seconds); if (LOCK_SUCCESS.equals(result)) { return Boolean.TRUE; } return Boolean.FALSE; }); } /** * Corresponding to tryLock, it is used to release locks * @param lockKey * @param clientId * @return */ public Boolean releaseLock(String lockKey, String clientId) { return (Boolean) redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> { Jedis jedis = (Jedis) redisConnection.getNativeConnection(); Object result = jedis.eval(RELEASE_LOCK_SCRIPT, Collections.singletonList(lockKey), Collections.singletonList(clientId)); if (RELEASE_SUCCESS.equals(result)) { return Boolean.TRUE; } return Boolean.FALSE; }); }
test
Code
Controller code
@ResponseBody @RequestMapping("/system/testLock") public R testLock(String productId) { String s = lockTestService.testLock(productId); return R.ok().put("data", s); }
Service code
package com.masteryee.common.service.impl; import com.google.common.collect.Maps; import com.masteryee.common.service.LockTestService; import com.masteryee.msedu.util.RedisManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; /** * System user */ @Service("lockTestService") @Transactional public class LockTestServiceImpl implements LockTestService { @Autowired private RedisManager redisManager; //Commodity details private static HashMap<String, Integer> product = new HashMap(); //Order form private static HashMap<String, String> orders = new HashMap(); //An inventory statement private static HashMap<String, Integer> stock = new HashMap(); static { product.put("Note3", 10000); stock.put("Note3", 10000); product.put("Nove4", 10000); stock.put("Nove4", 10000); product.put("R5", 10000); stock.put("R5", 10000); } public String select_info(String productId) { Map<String, String> stringStringMap = Maps.filterValues(orders, r -> r.equalsIgnoreCase(productId)); return "Limited panic buying goods["+productId+"]common" + product.get(productId) + ",Successful order now" + stringStringMap.size() + ",Surplus stock" + stock.get(productId) + "piece"; } /*@Override public synchronized String testLock(String productId) { if (stock.get(productId) == 0) { //It's almost sold out. return "The activity is over. } else { //Not sold out yet try { //Analog operation database Thread.sleep(100); orders.put(UUID.randomUUID().toString(), productId); stock.put(productId, stock.get(productId) - 1); } catch (InterruptedException e) { e.printStackTrace(); } } return select_info(productId); }*/ @Override public String testLock(String productId) { if (stock.get(productId) == 0) { //It's almost sold out. return "The activity is over."; } else { //Not sold out yet String clientId = ""; try { // The value corresponding to the key of Redis is used when releasing the lock. clientId = UUID.randomUUID().toString(); // Dead loop until lock is acquired while (true) { // Get lock Boolean lock = redisManager.getLock(productId, clientId, 2); if (lock) { //Analog operation database Thread.sleep(100); orders.put(UUID.randomUUID().toString(), productId); stock.put(productId, stock.get(productId) - 1); break; } } } catch (InterruptedException e) { e.printStackTrace(); } finally { // Release lock redisManager.releaseLock(productId, clientId); } } return select_info(productId); } }
Testing process
Use the ab pressure measurement tool provided by Apache. Usage reference
Three windows are opened at the same time, and requests are made at the same time. synchronized synchronization method and distributed lock are tested respectively.
Take Note3 request parameters for example (plus Nove4 and R5)
synchronized method
E:\develop\Apache AB\httpd-2.4.39-o102s-x64-vc14\Apache24\bin>ab -n 100 -c 50 http://localhost/system/testLock?productId=Note3 This is ApacheBench, Version 2.3 <$Revision: 1843412 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking localhost (be patient).....done Server Software: Server Hostname: localhost Server Port: 80 Document Path: /system/testLock?productId=Note3 Document Length: 93 bytes Concurrency Level: 50 Time taken for tests: 26.739 seconds Complete requests: 100 Failed requests: 91 (Connect: 0, Receive: 0, Length: 91, Exceptions: 0) Total transferred: 21292 bytes HTML transferred: 9392 bytes Requests per second: 3.74 [#/sec] (mean) Time per request: 13369.702 [ms] (mean) Time per request: 267.394 [ms] (mean, across all concurrent requests) Transfer rate: 0.78 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.2 0 1 Processing: 150 6632 5420.0 5257 21673 Waiting: 150 6632 5420.2 5257 21673 Total: 151 6633 5420.0 5257 21673 Percentage of the requests served within a certain time (ms) 50% 5257 66% 8642 75% 9630 80% 10436 90% 15346 95% 20877 98% 21471 99% 21673 100% 21673 (longest request)
Distributed lock
E:\develop\Apache AB\httpd-2.4.39-o102s-x64-vc14\Apache24\bin>ab -n 100 -c 50 http://localhost/system/testLock?productId=Note3 This is ApacheBench, Version 2.3 <$Revision: 1843412 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking localhost (be patient).....done Server Software: Server Hostname: localhost Server Port: 80 Document Path: /system/testLock?productId=Note3 Document Length: 93 bytes Concurrency Level: 50 Time taken for tests: 10.676 seconds Complete requests: 100 Failed requests: 91 (Connect: 0, Receive: 0, Length: 91, Exceptions: 0) Total transferred: 21292 bytes HTML transferred: 9392 bytes Requests per second: 9.37 [#/sec] (mean) Time per request: 5338.227 [ms] (mean) Time per request: 106.765 [ms] (mean, across all concurrent requests) Transfer rate: 1.95 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.2 0 1 Processing: 172 3888 2260.7 3963 10333 Waiting: 171 3888 2260.9 3963 10331 Total: 172 3888 2260.7 3963 10333 Percentage of the requests served within a certain time (ms) 50% 3963 66% 4776 75% 5237 80% 5931 90% 6758 95% 8234 98% 9715 99% 10333 100% 10333 (longest request)
The synchronization method allows only one thread to enter the method at the same time, regardless of whether the request parameters are identical or not, it is always locked. Distributed locks only lock the same clock product or parameters, and have no effect on other parameters. For example, the parameters I tested were Note3, Nove4 and R5. Distributed locks in Redis use Note3, Nove4 and R5 as key s, so they do not affect each other, can be accessed at the same time, and are more efficient than synchronization methods.