Redis中用lua脚本实现扣减库存保证了原子性,并用工厂模式

StockController 类
package com.atyoyo.distributedlock.controller;
import com.atguigu.distributedlock.service.StockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class StockController {
    @Autowired
    private StockService stockService;
    //减库存
    @GetMapping(value = "stock/deduct")
    public String deduct(){
        stockService.deduct();

        return "hello stock deduct!!!";

    }

}
StockService 类

@Service
public class StockService {

    @Autowired
    private StockMapper stockMapper;
    /**
     * redisTemplate
     */
    //StringRedisTemplate 和 RedisTemplate  序列化器不同
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private DistributedLockClient distributedLockClient;

    public void deduct() {
        DistributedRedisLock redisLock = distributedLockClient.getRedisLock("lock");
        redisLock.lock();

        try {
            String stock = redisTemplate.opsForValue().get("stock").toString();
            //2.判断库存是否充足
            if (stock != null && stock.length() != 0) {
                Integer st = Integer.valueOf(stock);
                if (st > 0) {
                    //扣减库存
                    redisTemplate.opsForValue().set("stock", String.valueOf(--st));
                }
            }
        } finally {
            redisLock.unlock();
        }


    }

}
DistributedLockClient 工厂类

//
@Component
public class DistributedLockClient {
@Autowired
private StringRedisTemplate redisTemplate;
    public DistributedRedisLock getRedisLock(String lockName){
        return new DistributedRedisLock(redisTemplate,lockName);
    }
}
DistributedRedisLock 类
package com.atyoyo.distributedlock.lock;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;


public class DistributedRedisLock implements Lock {
    private StringRedisTemplate redisTemplate;
    // 线程局部变量,可以在线程内共享参数
    private String lockName;
    private String uuid;

    private Integer expire = 30;
    private static final ThreadLocal<String> THREAD_LOCAL = new
            ThreadLocal<>();
    public DistributedRedisLock (StringRedisTemplate redisTemplate, String lockName) {
        this.redisTemplate = redisTemplate;
        this.lockName = lockName;
        this.uuid = THREAD_LOCAL.get();
        if (StringUtils.isBlank(uuid)) {
            this.uuid = UUID.randomUUID().toString();
            THREAD_LOCAL.set(uuid);
        }
    }
    @Override
    public void lock() {
       this.lock(expire);
    }

    public void lock(Integer expire){
        this.expire = expire;
        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\n" +
                "\t\t\n" +
                "\t\tif redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1\n" +
                "\t\tthen\n" +
                "\t\t\tredis.call('hincrby', KEYS[1], ARGV[1], 1)\n" +
                "\t\t\tredis.call('expire', KEYS[1], ARGV[2])\n" +
                "\t\t\treturn 1\n" +
                "\t\telse \n" +
                "\t\t\treturn 0\n" +
                "\t\tend"
                ;
        if (!this.redisTemplate.execute(new DefaultRedisScript<>(script,
                Boolean.class), Arrays.asList(lockName), uuid, expire.toString())){
            try {
// 没有获取到锁,重试
                Thread.sleep(60);
                lock(expire);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public void unlock(){
        String script = "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\n" +
                "\t\n" +
                "\t\tif redis.call('hexists', KEYS[1], ARGV[1]) == 0\n" +
                "\t\tthen\n" +
                "\t\t\treturn nil\n" +
                "\t\telseif redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0\n" +
                "\t\tthen \n" +
                "\t\t\treturn redis.call('del', KEYS[1])\n" +
                "\t\telse \n" +
                "\t\t\treturn 0\n" +
                "\t\tend";
// 如果返回值没有使用Boolean,Spring-data-redis 进行类型转换时将会把 null
        //      转为 false,这就会影响我们逻辑判断
// 所以返回类型只好使用 Long:null-解锁失败;0-重入次数减1;1-解锁成功。
        Long result = this.redisTemplate.execute(new DefaultRedisScript<>
                (script, Long.class), Arrays.asList(lockName), uuid);

//        3.6.4. 使用及测试
//        在业务代码中使用:
//        测试:
//        测试可重入性:
// 如果未返回值,代表尝试解其他线程的锁
        if (result == null) {
            throw new IllegalMonitorStateException("attempt to unlock lock, not locked by lockName: "
                    + lockName + " with request: " + uuid);
        } else if (result == 1) {
            THREAD_LOCAL.remove();
        }
    }

    @Override
    public Condition newCondition() {
        return null;
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值