Redis-Xi-01-Redis Distributed Lock (Single Node Lua script)

Keywords: Redis Jedis Apache Database

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.

Posted by rimedey on Wed, 02 Oct 2019 16:52:10 -0700