手写Redis分布式锁

锁的种类

  • 单机版同一个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下面配置负载均衡
image.png
接下来测试没问题

压力测试

jmeter测试
image.png
为什么加了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。
    • image.png
    • 上面的代码不具备原子性,需要修改。(假如在建立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脚本

image.png
**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 **
:::

条件判断

image.png
**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();
    }
}
 
  • 27
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值