Redis-习-01-Redis分布式锁(Lua脚本)
SpringBoot 项目中,采用 set key value nx px 命令确保获取锁的原子性操作,使用 Lua 脚本确保释放锁的原子性操作。(仅限单节点Redis)
获取锁释放锁代码
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;
/**
* 该加锁方法仅针对单实例 Redis 可实现分布式加锁
* 对于 Redis 集群则无法使用
* 支持重复,线程安全
* @param lockKey 加锁键
* @param clientId 加锁客户端唯一标识(采用UUID)
* @param seconds 锁过期时间
* @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;
});
}
/**
* 与 tryLock 相对应,用作释放锁
* @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;
});
}
测试
代码
Controller代码
@ResponseBody
@RequestMapping("/system/testLock")
public R testLock(String productId) {
String s = lockTestService.testLock(productId);
return R.ok().put("data", s);
}
Service代码
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.*;
/**
* 系统用户
*/
@Service("lockTestService")
@Transactional
public class LockTestServiceImpl implements LockTestService {
@Autowired
private RedisManager redisManager;
//商品详情
private static HashMap<String, Integer> product = new HashMap();
//订单表
private static HashMap<String, String> orders = new HashMap();
//库存表
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 "限量抢购商品["+productId+"]共" + product.get(productId) + ",现在成功下单" + stringStringMap.size()
+ ",剩余库存" + stock.get(productId) + "件";
}
/*@Override
public synchronized String testLock(String productId) {
if (stock.get(productId) == 0) {
//已近买完了
return "活动已经结束了";
} else {
//还没有卖完
try {
//模拟操作数据库
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) {
//已近买完了
return "活动已经结束了";
} else {
//还没有卖完
String clientId = "";
try {
// 作为Redis的key对应的value,释放锁时要用
clientId = UUID.randomUUID().toString();
// 死循环,直到获取锁为止
while (true) {
// 获取锁
Boolean lock = redisManager.getLock(productId, clientId, 2);
if (lock) {
//模拟操作数据库
Thread.sleep(100);
orders.put(UUID.randomUUID().toString(), productId);
stock.put(productId, stock.get(productId) - 1);
break;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
redisManager.releaseLock(productId, clientId);
}
}
return select_info(productId);
}
}
测试过程
使用 Apache 提供的 ab 压测工具。用法参考
同时开三个窗口,同时请求,分别测试使用synchronized同步方法和分布式锁。
以 Note3 请求参数为例(另外还有 Nove4 和 R5)
同步方法synchronized
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)
分布式锁
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)
同步方法只允许同一个时间一个线程进入方法,不管请求参数是否一致,一律加锁。而分布式锁只针对同一钟产品或者参数加锁,对其他的参数不产生影响。如:我测试的参数分别为 Note3,Nove4,R5。分布式锁在Redis中则是以 Note3,Nove4,R5分别为key,所以互相不影响,可以同时访问,效率比同步方法更高。