Redis应用(4)——Redis的项目应用(三):抢购图书2.0 ---> Lua脚本 & Redis+Lua+Redission实现抢购 & Redission锁

目录

  • 引出
  • Lua脚本及其应用
    • Lua语法
    • Lua脚本
      • 定义商品库存信息
      • 编写Lua脚本(去库存-1)
  • Redis+Lua脚本项目应用(三):抢购图书
    • 3.0版本:redis+Lua,数据不安全
      • 1.在resources下新建lua脚本
      • 2.写lua脚本的配置类
      • 3.controller层调用lua脚本
      • 4.进行测试
      • dto层的响应
  • 4.0版本:Redis+Lua+Redission
    • 1.采用redission加锁
    • 2.进行测试
  • 总结

引出


1.Lua脚本,基础语法,去库存脚本编写;
2.SpringBoot整合Lua脚本,配置类RedisScript
3.调用lua脚本,execute(redisScript, keys, wanted);
4.加锁和未加锁的测试,Redission锁的应用;

Lua脚本及其应用

https://www.lua.org/

在这里插入图片描述

https://www.dba.cn/book/lua/

Lua语法

Lua简化语法

local ——>定义变量(var let)

KEYS[1] ——>数组 从一开始 (key, field)

ARGV[1] —->Redis值

tonumber—->将字符串转换为数值

redis.call()

if then

end

Lua脚本

以商品库存为例

定义商品库存信息

Redis应用(4)——Redis的项目应用(三):抢购图书2.0 ---> Lua脚本 & Redis+Lua+Redission实现抢购 & Redission锁_第1张图片

设置图书的redis存储方式: hash

编写Lua脚本(去库存-1)

将书去(减少)库存放入lua脚本。

  • 检查数量
  • 判断数量是否大于0
  • 做-1操作
-- 图书去库存放到lua脚本中
-- 传的参数:物品,数量,购买数量
-- 采用redis的hash数据结构
-- 1.首先定义变量
local book = KEYS[1] -- 获取第一个key,物品
local amount = KEYS[2] -- 获取第二个key,当前物品的数量
local wanted = tonumber(ARGV[1]) -- 获取要减少的库存,即要订购的数量

-- 2.判断产品是否存在,book amount 函数:HEXISTS key field
-- 如果存在返回integer 1,不存在返回0
local isExists = tonumber(redis.call("HEXISTS",book,amount))

-- 判断key,field是否存在
if isExists == 1 then
    -- key 和 field field 存在
    -- 获取图书数量,判断是否大于0,且减库存后也大于0,hget book amount
    local currVal = tonumber(redis.call("hget",book,amount))
    -- 判断是否大于0,且减库存后也大于0
    if currVal>0 and currVal-wanted >= 0 then
        -- 进行去库存,将数量减少wanted,hincrby book amount -1
        redis.call("hincrby",book,amount,0-wanted)
        return 8 -- 去库存成功
    else
        return 4 -- 因为库存不足,导致失败
    end
else
    return 9 -- key 和 field是否存在
end

Redis+Lua脚本项目应用(三):抢购图书

3.0版本:redis+Lua,数据不安全

1.在resources下新建lua脚本

Redis应用(4)——Redis的项目应用(三):抢购图书2.0 ---> Lua脚本 & Redis+Lua+Redission实现抢购 & Redission锁_第2张图片

将书去(减少)库存放入lua脚本。

  • 检查数量
  • 判断数量是否大于0
  • 做-1操作
-- 图书去库存放到lua脚本中
-- 传的参数:物品,数量,购买数量
-- 采用redis的hash数据结构
-- 1.首先定义变量
local book = KEYS[1] -- 获取第一个key,物品
local amount = KEYS[2] -- 获取第二个key,当前物品的数量
local wanted = tonumber(ARGV[1]) -- 获取要减少的库存,即要订购的数量

-- 2.判断产品是否存在,book amount 函数:HEXISTS key field
-- 如果存在返回integer 1,不存在返回0
local isExists = tonumber(redis.call("HEXISTS",book,amount))

-- 判断key,field是否存在
if isExists == 1 then
    -- key 和 field field 存在
    -- 获取图书数量,判断是否大于0,且减库存后也大于0,hget book amount
    local currVal = tonumber(redis.call("hget",book,amount))
    -- 判断是否大于0,且减库存后也大于0
    if currVal>0 and currVal-wanted >= 0 then
        -- 进行去库存,将数量减少wanted,hincrby book amount -1
        redis.call("hincrby",book,amount,0-wanted)
        return 8 -- 去库存成功
    else
        return 4 -- 因为库存不足,导致失败
    end
else
    return 9 -- key 和 field是否存在
end

2.写lua脚本的配置类

package com.tianju.redisDemo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;

/**
 * 运行lua脚本的配置类
 */
@Configuration
public class RedisConfig {

    @Bean // 别人写的类方容器中
    public RedisScript<Long> redisScript(){
        DefaultRedisScript redisScript = new DefaultRedisScript<>();
        redisScript.setResultType(Long.class);
        // lua脚本的位置
        redisScript.setLocation(
                new ClassPathResource("/lua/book-unstock.lua") // 关联lua脚本
        );
        return redisScript;
    }
}

3.controller层调用lua脚本

package com.tianju.redisDemo.controller;

import com.tianju.redisDemo.dto.HttpResp;
import com.tianju.redisDemo.dto.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;


@RestController
@RequestMapping("/api/book")
@Slf4j
public class BookUnStockController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

//    @Autowired
    @Resource
    private RedisScript<Long> redisScript;

    @RequestMapping("/unstock")
    public HttpResp unStock(
            String book, // hash的key,物品
            String amount, // field,字段,当前数量
            String wanted // 购买数量
    ){
        List<String> keys = new ArrayList<>();
        keys.add(book);
        keys.add(amount);
        Long v = stringRedisTemplate
                .opsForHash()
                .getOperations()
                .execute(redisScript, keys, wanted);// wanted表示想要的数量
        if (v.intValue()==4){
            return HttpResp.results(ResultCode.BOOK_RUSH_ERROR,new Date(),"库存不足");
        }
        if (v.intValue()==9){
            return HttpResp.results(ResultCode.BOOK_RUSH_ERROR,new Date(),"key不存在");
        }
        if (v.intValue()==8){
            Object o = stringRedisTemplate.opsForHash().get(book, amount);
            log.debug(">>>当前库存:"+o);
            return HttpResp.results(ResultCode.BOOK_RUSH_SUCCESS,new Date(),"图书抢购成功");
        }
        return HttpResp.results(ResultCode.LUA_SCRIPT_ERROR,new Date(),null);
    }
}

4.进行测试

(1)client方法测试;

Redis应用(4)——Redis的项目应用(三):抢购图书2.0 ---> Lua脚本 & Redis+Lua+Redission实现抢购 & Redission锁_第3张图片

(2)JMeter进行高并发测试

Redis应用(4)——Redis的项目应用(三):抢购图书2.0 ---> Lua脚本 & Redis+Lua+Redission实现抢购 & Redission锁_第4张图片

Redis应用(4)——Redis的项目应用(三):抢购图书2.0 ---> Lua脚本 & Redis+Lua+Redission实现抢购 & Redission锁_第5张图片

dto层的响应

HttpResp.java

package com.tianju.redisDemo.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class HttpResp<T> implements Serializable {
    private ResultCode resultCode;
    private Date time;
    private T result;

    public static <T> HttpResp <T> results(
            ResultCode resultCode,
            Date time,
            T results){

        HttpResp httpResp = new HttpResp();
        httpResp.setResultCode(resultCode);
        httpResp.setTime(time);
        httpResp.setResult(results);
        return httpResp;
    }
}

ResultCode.java

package com.tianju.redisDemo.dto;

import sun.dc.pr.PRError;

/**
 * 枚举类型,http请求的返回值
 */
public enum ResultCode {
    BOOK_RUSH_SUCCESS(20010,"图书抢购成功"),
    BOOK_RUSH_ERROR(3001,"图书抢购失败"),
    LUA_SCRIPT_ERROR(3002,"Lua脚本操作失败")
    ;
    private Integer code;
    private String msg;

    private ResultCode(Integer code,String msg){
        this.code =code;
        this.msg = msg;
    }
}

4.0版本:Redis+Lua+Redission

1.采用redission加锁

package com.tianju.redisDemo.controller;

import com.tianju.redisDemo.dto.HttpResp;
import com.tianju.redisDemo.dto.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;


@RestController
@RequestMapping("/api/book")
@Slf4j
public class BookUnStockController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

//    @Autowired
    @Resource
    private RedisScript<Long> redisScript;

    @Autowired
    private RedissonClient redissonClient;

    @RequestMapping("/unstock")
    public HttpResp unStock(
            String book, // hash的key,物品
            String amount, // field,字段,当前数量
            String wanted // 购买数量
    ){
        List<String> keys = new ArrayList<>();
        keys.add(book);
        keys.add(amount);

        // 1.获取锁对象
        RLock myLock = redissonClient.getLock("myLock");

        try {
            // 2.准备锁代码,并进行加锁
            myLock.lock();

            Long v = stringRedisTemplate
                    .opsForHash()
                    .getOperations()
                    .execute(redisScript, keys, wanted);// wanted表示想要的数量
            if (v.intValue()==4){
                return HttpResp.results(ResultCode.BOOK_RUSH_ERROR,new Date(),"库存不足");
            }
            if (v.intValue()==9){
                return HttpResp.results(ResultCode.BOOK_RUSH_ERROR,new Date(),"key不存在");
            }
            if (v.intValue()==8){
                Object o = stringRedisTemplate.opsForHash().get(book, amount);
                log.debug(">>>当前库存:"+o);
                return HttpResp.results(ResultCode.BOOK_RUSH_SUCCESS,new Date(),"图书抢购成功");
            }
            return HttpResp.results(ResultCode.LUA_SCRIPT_ERROR,new Date(),null);
        } finally {
            // 3.解除锁
            myLock.unlock(); // 把锁释放
        }
    }
}

2.进行测试

Redis应用(4)——Redis的项目应用(三):抢购图书2.0 ---> Lua脚本 & Redis+Lua+Redission实现抢购 & Redission锁_第6张图片

Redis应用(4)——Redis的项目应用(三):抢购图书2.0 ---> Lua脚本 & Redis+Lua+Redission实现抢购 & Redission锁_第7张图片


总结

1.Lua脚本,基础语法,去库存脚本编写;
2.SpringBoot整合Lua脚本,配置类RedisScript
3.调用lua脚本,execute(redisScript, keys, wanted);
4.加锁和未加锁的测试,Redission锁的应用;

你可能感兴趣的:(#,Redis,redis,lua,junit)