文章目录
锁的种类
- 单机版同一个JVM虚拟机内,
synchronized
或者Lock
接口。 - 分布式多个不同JVM虚拟机,单机的线程锁机制不再起作用,资源类在不同的服务器之间共享了。
需要注意的问题
独占性
OnlyOne,任何时刻只能有且仅有一个线程持有
高可用
若redis集群环境下,不能因为某一个节点挂了而出现获取锁和释放锁失败的情况
高并发请求下,依旧性能OK好使
防死锁
杜绝死锁,必须有超时控制机制或者撤销操作,有个兜底终止跳出方案
不乱抢
防止张冠李戴,不能私下unlock别人的锁,只能自己加锁自己释放,自己约的锁含着泪也要自己解
重入性
同一个节点的同一个线程如果获得锁之后,它也可以再次获取这个锁。
分布式锁
**setnx key value**
Base案例(boot+redis)
server.port=7777
# ========================swagger=====================
spring.swagger2.enabled=true
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
# http://localhost:7777/swagger-ui.html#/
# ========================redis??=====================
spring.redis.database=0
# ???????IP
spring.redis.host=192.168.200.100
spring.redis.port=6379
spring.redis.password=123456
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
# ========================alibaba.druid=====================
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.druid.test-while-idle=false
# ========================mybatis===================
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.xzh.redis.entities
package com.xzh.redis.controller;
import com.xzh.redis.service.InventoryService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Api(tags = "redis分布式锁测试")
public class InventoryController
{
@Resource
private InventoryService inventoryService;
@ApiOperation("扣减库存,一次卖一个")
@GetMapping(value = "/inventory/sale")
public String sale()
{
return inventoryService.sale();
}
}
package com.xzh.redis.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Service
@Slf4j
public class InventoryService {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String port;
private Lock lock = new ReentrantLock();
public String sale() {
String retMessage = "";
lock.lock();
try {
//1 查询库存信息
String result = stringRedisTemplate.opsForValue().get("inventory001");
Integer inventorynumber = result == null ? 0 : Integer.parseInt(result);
if (inventorynumber > 0) {
//有库存
stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventorynumber));
retMessage = "恭喜你,成功卖出一个商品,库存剩余:" + inventorynumber;
System.out.println(retMessage + "\t" + "服务端口号:" + port);
} else {
retMessage = "库存不足";
}
} finally {
lock.unlock();
}
return retMessage + "\t" + "服务端口号:" + port;
}
}
初始化版本简单添加
将7777拷贝到8888
负载均衡
在/usr/local/nginx/conf下面配置负载均衡
接下来测试没问题
压力测试
jmeter测试
为什么加了synchronized或者Lock还是没有控制住?
在单机环境下,可以使用synchronized或Lock来实现。
- 但是在分布式系统中,因为竞争的线程可能不在同一个节点上(同一个jvm中),所以需要一个让所有进程都能访问到的锁来实现**(比如redis或者zookeeper来构建)**
- 不同进程jvm层面的锁就不管用了,那么可以利用第三方的一个组件,来获取锁,未获取到锁,则阻塞当前想要运行的线程
Redis分布式锁
setnx
public String sale() {
String retMessage = "";
String key = "xzhRedislock";
String uuidValue = IdUtil.simpleUUID() + ": " + Thread.currentThread().getId();
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue);
if(!flag){
//暂停20ms,递归重试
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
sale();
}else {
//抢锁成功
try {
//1 查询库存信息
String result = stringRedisTemplate.opsForValue().get("inventory001");
Integer inventorynumber = result == null ? 0 : Integer.parseInt(result);
if (inventorynumber > 0) {
//有库存
stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventorynumber));
retMessage = "恭喜你,成功卖出一个商品,库存剩余:" + inventorynumber;
System.out.println(retMessage + "\t" + "服务端口号:" + port);
} else {
retMessage = "库存不足";
}
} finally {
stringRedisTemplate.delete(key);
}
}
return retMessage + "\t" + "服务端口号:" + port;
}
通过递归重试的方式
问题
测试手工OK,测试Jmeter压测5000OK
不太推荐,进一步完善递归是一种思想没错,但是容易导致StackOverflowError,
用自旋替代递归
public String sale() {
String retMessage = "";
String key = "xzhRedislock";
String uuidValue = IdUtil.simpleUUID() + ": " + Thread.currentThread().getId();
while(!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue)){
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//抢锁成功
try {
//1 查询库存信息
String result = stringRedisTemplate.opsForValue().get("inventory001");
Integer inventorynumber = result == null ? 0 : Integer.parseInt(result);
if (inventorynumber > 0) {
//有库存
stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventorynumber));
retMessage = "恭喜你,成功卖出一个商品,库存剩余:" + inventorynumber;
System.out.println(retMessage + "\t" + "服务端口号:" + port);
} else {
retMessage = "库存不足";
}
} finally {
stringRedisTemplate.delete(key);
}
}
return retMessage + "\t" + "服务端口号:" + port;
}
宕机与过期+死锁
- 部署了微服务的Java程序机器挂了
- 代码层面根本没有走到finally这块没办法保证解锁(无过期时间该key一直存在),这个key没有被删除,需要加入一个过期时间限定key。
- 上面的代码不具备原子性,需要修改。(假如在建立key的一瞬间宕机,那么expire指定还是无法执行!)
while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue, 30L, TimeUnit.SECONDS)) {
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
防止误删锁操作
实际业务处理时间如果超过了默认设置key的过期时间??
张冠李戴,删除了别人的锁
//必须删除自己的key,而不是别人的key
if(stringRedisTemplate.opsForValue().get(key).equalsIgnoreCase(uuidValue)){
stringRedisTemplate.delete(key);
}
上面的代码还是有原子性的问题:
- ========假设线程A
**if**
判断成功,但是这一瞬间A的key过期了,B的key又已经建立,随后A就会删除B的key…
Lua脚本
**eval script numkeys [key [key ...]] [arg [arg ...]]**
Redis调用Lua脚本通过eval命令保证代码执行的原子性,直接用**return**
返回脚本执行后的结果值
**eval "return 'hello lua'" 0**
利用lua脚本将多条命令一起执行,并获得最后一个代码的返回值
**eval "redis.call('set','k1','v1') redis.call('expire','k1','30') redis.call('get','k1') return redis.call('ttl','k1')" 0**
动态传入数据,参数
**eval "redis.call('mset',KEYS[1],ARGV[1],KEYS[2],ARGV[2])" 2 k1 k2 lua1 lua2**
官方脚本示例
**eval "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 RedisLock 1 1111-2222-3333 **
:::info
**eval **
**"if **
**redis.call('get',KEYS[1])==ARGV[1] then **
**return redis.call('del',KEYS[1]) **
**else return 0 **
**end" **
**1 RedisLock 1 1111-2222-3333 **
:::
条件判断
**eval script numkeys [key [key ...]] [arg [arg ...]]**
key需要指定个数,但是arg直接跟在key的后面不用指定个数。
最后可以将java代码进行修改了
String luaSript =
"if redis.call('get',KEYS[1])==ARGV[1] then " +
"return redis.call('del',KEYS[1]) " +
"else return 0 " +
"end";
stringRedisTemplate.execute(new DefaultRedisScript<>(luaSript, Long.class), Arrays.asList(key), uuidValue);
Long.class表示返回值的类型。
可重入锁
可重入锁又名递归锁
是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
如果是1个有 synchronized 修饰的递归调用方法**,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。**
所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
- setnx,只能解决有无的问题。够用但是不完美
- hset,不但解决有无,还解决可重入问题
**hset redis锁名字(RedisLock) 某个请求线程的UUID+ThreadID 加锁的次数**
**hincrby redis锁名字(RedisLock) 某个请求线程的UUID+ThreadID 1**
:锁一次**hincrby redis锁名字(RedisLock) 某个请求线程的UUID+ThreadID -1**
:解锁一次
Lua脚本实现
if redis.call('exists',KEYS[1]) == 0 or redis.call('exists',KEYS[1],ARGV[1]) == 1 then
redis.call('hincrby',KEYS[1],ARGV[1],1)
redis.call('expire',KEYS[1],ARGV[2])
return 1
else
return 0
end
if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then
return nil
elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then
return redis.call('del',KEYS[1])
else
return 0
end
整合进微服务
开发自己的RedisDistributedLock类
package com.xzh.redis.mylock;
import cn.hutool.core.util.IdUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class RedisDistributedLock implements Lock {
private StringRedisTemplate stringRedisTemplate;
private String lockName;
private String uuidValue;
private long expireTime;
public RedisDistributedLock( StringRedisTemplate stringRedisTemplate,String lockName) {
this.lockName = lockName;
this.stringRedisTemplate = stringRedisTemplate;
this.uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
this.expireTime = 50L;
}
@Override
public void lock() {
tryLock();
}
@Override
public boolean tryLock() {
try {
tryLock(-1L, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
if (time == -1L) {
String luaScript = "if redis.call('exists',KEYS[1]) == 0 or redis.call('exists',KEYS[1],ARGV[1]) == 1 then redis.call('hincrby',KEYS[1],ARGV[1],1) redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end";
while (!stringRedisTemplate.execute(new DefaultRedisScript<>(luaScript, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))) {
try {
TimeUnit.MILLISECONDS.sleep(60);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
return true;
}
return false;
}
@Override
public void unlock() {
String luaScript = "if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then return nil elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then return redis.call('del',KEYS[1]) else return 0 end";
Long flag = stringRedisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class), Arrays.asList(lockName), uuidValue);
if (flag == null) {
throw new RuntimeException("释放锁失败,锁不存在!");
}
}
//<=======================用不到========================>
@Override
public Condition newCondition() {
return null;
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
}
@Service
@Slf4j
public class InventoryService {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String port;
private Lock myredislock = new RedisDistributedLock(stringRedisTemplate, "xzhRedislock");
public String sale() {
String retMessage = "";
myredislock.lock();
try {
//1 查询库存信息
String result = stringRedisTemplate.opsForValue().get("inventory001");
Integer inventorynumber = result == null ? 0 : Integer.parseInt(result);
if (inventorynumber > 0) {
//有库存
stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventorynumber));
retMessage = "恭喜你,成功卖出一个商品,库存剩余:" + inventorynumber;
System.out.println(retMessage + "\t" + "服务端口号:" + port);
} else {
retMessage = "库存不足";
}
} finally {
myredislock.unlock();
}
return retMessage + "\t" + "服务端口号:" + port;
}
}
引入工厂模式
package com.xzh.redis.mylock;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.locks.Lock;
@Component
public class DistributedLockFactory {
private String lockName;
@Resource
private StringRedisTemplate stringRedisTemplate;
public Lock getDistributedLock(String lockType) {
if (lockType == null) return null;
if (lockType.equalsIgnoreCase("REDIS")) {
this.lockName = "RedisLock";
return new RedisDistributedLock(stringRedisTemplate, lockName);
} else if (lockType.equalsIgnoreCase("ZOOKEEPER")) {
this.lockName = "ZookeeperLock";
//TODO: 返回ZookeeperLock
return null;
}
return null;
}
}
最后的service
public class InventoryService {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String port;
@Resource
private DistributedLockFactory distributedLockFactory;
public String sale() {
String retMessage = "";
Lock myredislock = distributedLockFactory.getDistributedLock("REDIS");
myredislock.lock();
try {
//1 查询库存信息
String result = stringRedisTemplate.opsForValue().get("inventory001");
Integer inventorynumber = result == null ? 0 : Integer.parseInt(result);
if (inventorynumber > 0) {
//有库存
stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventorynumber));
retMessage = "恭喜你,成功卖出一个商品,库存剩余:" + inventorynumber;
System.out.println(retMessage + "\t" + "服务端口号:" + port);
} else {
retMessage = "库存不足";
}
} finally {
myredislock.unlock();
}
return retMessage + "\t" + "服务端口号:" + port;
}
}
测试可重入性
在service中添加测试代码
private void testReEnter()
{
Lock redisLock = distributedLockFactory.getDistributedLock("REDIS");
redisLock.lock();
try
{
System.out.println("################测试可重入锁#######");
}finally {
redisLock.unlock();
}
}
报错:
- ThreadID相同,但是UUID不同。
需要修改,将UUID改为工厂初始化。
private String uuid;
public DistributedLockFactory() {
this.uuid = IdUtil.simpleUUID();
}
this.uuidValue = uuidValue + ":" + Thread.currentThread().getId();
自动续期
if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 1 then
return redis.call('expire',KEYS[1],ARGV[2])
else
return 0
end
//暂停几秒钟线程,为了测试自动续期
try { TimeUnit.SECONDS.sleep(120); } catch (InterruptedException e) { e.printStackTrace(); }
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException
{
//......
this.renewExpire();
return true;
}
private void renewExpire()
{
String script =
"if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 1 then " +
"return redis.call('expire',KEYS[1],ARGV[2]) " +
"else " +
"return 0 " +
"end";
new Timer().schedule(new TimerTask()
{
@Override
public void run()
{
if (stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName),uuidValue,String.valueOf(expireTime))) {
renewExpire();
}
}
},(this.expireTime * 1000)/3);
}
最后的代码
@Service
@Slf4j
public class InventoryService {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String port;
@Resource
private DistributedLockFactory distributedLockFactory;
public String sale() {
String retMessage = "";
Lock myredislock = distributedLockFactory.getDistributedLock("REDIS");
myredislock.lock();
try {
//1 查询库存信息
String result = stringRedisTemplate.opsForValue().get("inventory001");
Integer inventorynumber = result == null ? 0 : Integer.parseInt(result);
if (inventorynumber > 0) {
//有库存
stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventorynumber));
retMessage = "恭喜你,成功卖出一个商品,库存剩余:" + inventorynumber;
System.out.println(retMessage + "\t" + "服务端口号:" + port);
try { TimeUnit.SECONDS.sleep(120); } catch (InterruptedException e) { e.printStackTrace(); }
} else {
retMessage = "库存不足";
}
} finally {
myredislock.unlock();
}
return retMessage + "\t" + "服务端口号:" + port;
}
/*private void testReEnter()
{
Lock redisLock = distributedLockFactory.getDistributedLock("REDIS");
redisLock.lock();
try
{
System.out.println("################测试可重入锁#######");
}finally {
redisLock.unlock();
}
}*/
}
/*private String getString() {
String retMessage = "";
String key = "xzhRedislock";
String uuidValue = IdUtil.simpleUUID() + ": " + Thread.currentThread().getId();
while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue, 30L, TimeUnit.SECONDS)) {
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//抢锁成功
try {
//1 查询库存信息
String result = stringRedisTemplate.opsForValue().get("inventory001");
Integer inventorynumber = result == null ? 0 : Integer.parseInt(result);
if (inventorynumber > 0) {
//有库存
stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventorynumber));
retMessage = "恭喜你,成功卖出一个商品,库存剩余:" + inventorynumber;
System.out.println(retMessage + "\t" + "服务端口号:" + port);
} else {
retMessage = "库存不足";
}
} finally {
//必须删除自己的key,而不是别人的key
*//*if(stringRedisTemplate.opsForValue().get(key).equalsIgnoreCase(uuidValue)){
stringRedisTemplate.delete(key);
}
String luaSript =
"if redis.call('get',KEYS[1])==ARGV[1] then " +
"return redis.call('del',KEYS[1]) " +
"else return 0 " +
"end";
stringRedisTemplate.execute(new DefaultRedisScript<>(luaSript, Long.class), Arrays.asList(key), uuidValue);
}
return retMessage + "\t" + "服务端口号:" + port;
}*/
/*private String getString() {
String retMessage = "";
String key = "xzhRedislock";
String uuidValue = IdUtil.simpleUUID() + ": " + Thread.currentThread().getId();
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue);
if(!flag){
//暂停20ms,递归重试
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
sale();
}else {
//抢锁成功
try {
//1 查询库存信息
String result = stringRedisTemplate.opsForValue().get("inventory001");
Integer inventorynumber = result == null ? 0 : Integer.parseInt(result);
if (inventorynumber > 0) {
//有库存
stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventorynumber));
retMessage = "恭喜你,成功卖出一个商品,库存剩余:" + inventorynumber;
System.out.println(retMessage + "\t" + "服务端口号:" + port);
} else {
retMessage = "库存不足";
}
} finally {
stringRedisTemplate.delete(key);
}
}
return retMessage + "\t" + "服务端口号:" + port;
}*/
/**
* 单机版加锁配合nginx和Jmeter压测后,不满足高并发分布式锁的性能要求,出现了超卖的现象
*/
/*private String getString() {
String retMessage = "";
lock.lock();
try {
//1 查询库存信息
String result = stringRedisTemplate.opsForValue().get("inventory001");
Integer inventorynumber = result == null ? 0 : Integer.parseInt(result);
if (inventorynumber > 0) {
//有库存
stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventorynumber));
retMessage = "恭喜你,成功卖出一个商品,库存剩余:" + inventorynumber;
System.out.println(retMessage + "\t" + "服务端口号:" + port);
} else {
retMessage = "库存不足";
}
} finally {
lock.unlock();
}
return retMessage + "\t" + "服务端口号:" + port;
}*/
public class RedisDistributedLock implements Lock {
private StringRedisTemplate stringRedisTemplate;
private String lockName;
private String uuidValue;
private long expireTime;
public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName,String uuidValue) {
this.lockName = lockName;
this.stringRedisTemplate = stringRedisTemplate;
this.uuidValue = uuidValue + ":" + Thread.currentThread().getId();
this.expireTime = 30L;
}
@Override
public void lock() {
tryLock();
}
@Override
public boolean tryLock() {
try {
tryLock(-1L, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException{
if(time != -1L){
this.expireTime = unit.toSeconds(time);
}
String script =
"if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then redis.call('hincrby',KEYS[1],ARGV[1],1) redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end";
while (Boolean.FALSE.equals(stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime)))) {
TimeUnit.MILLISECONDS.sleep(50);
}
resetExpire();
return true;
}
private void resetExpire() {
String luaScript = "if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 1 then " +
"return redis.call('expire',KEYS[1],ARGV[2]) " +
"else " +
"return 0 " +
"end";
new Timer().schedule(new TimerTask() {
@Override
public void run() {
if (stringRedisTemplate.execute(new DefaultRedisScript<>(luaScript, Boolean.class), Arrays.asList(lockName),uuidValue,String.valueOf(expireTime))) {
resetExpire();
}
}
},(this.expireTime * 1000)/3);
}
@Override
public void unlock() {
String luaScript = "if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then return nil elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then return redis.call('del',KEYS[1]) else return 0 end";
Long flag = stringRedisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class), Arrays.asList(lockName), uuidValue);
if (flag == null) {
throw new RuntimeException("释放锁失败,锁不存在!");
}
}
//<=======================用不到========================>
@Override
public Condition newCondition() {
return null;
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
}
@Component
public class DistributedLockFactory {
private String lockName;
private String uuid;
public DistributedLockFactory() {
this.uuid = IdUtil.simpleUUID();
}
@Resource
private StringRedisTemplate stringRedisTemplate;
public Lock getDistributedLock(String lockType) {
if (lockType == null) return null;
if (lockType.equalsIgnoreCase("REDIS")) {
this.lockName = "RedisLock";
return new RedisDistributedLock(stringRedisTemplate, lockName,uuid);
} else if (lockType.equalsIgnoreCase("ZOOKEEPER")) {
this.lockName = "ZookeeperLock";
//TODO: 返回ZookeeperLock
return null;
}
return null;
}
}
@Configuration
public class RedisConfig
{
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory)
{
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
//设置key序列化方式string
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置value的序列化方式json
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
@Configuration
@EnableSwagger2
public class Swagger2Config
{
@Value("${swagger2.enabled}")
private Boolean enabled;
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(enabled)
.select()
.apis(RequestHandlerSelectors.basePackage("com.xzh.redis")) //你自己的package
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("springboot利用swagger2构建api接口文档 "+"\t"+ DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDateTime.now()))
.description("springboot+redis整合")
.version("1.0")
.termsOfServiceUrl("https://www.baidu.com/")
.build();
}
}
@RestController
@Api(tags = "redis分布式锁测试")
public class InventoryController
{
@Resource
private InventoryService inventoryService;
@ApiOperation("扣减库存,一次卖一个")
@GetMapping(value = "/inventory/sale")
public String sale()
{
return inventoryService.sale();
}
}