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

不管现实多么惨不忍睹,都要持之以恒地相信,这只是黎明前短暂的黑暗而已。不要惶恐眼前的难关迈不过去,不要担心此刻的付出没有回报,别再花时间等待天降好运。真诚做人,努力做事!你想要的,岁月都会给你。Redis应用(4)——Redis的项目应用(三):抢购图书2.0 —> Lua脚本 & Redis+Lua+Redission实现抢购 & Redission锁,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

引出


1.Lua脚本,基础语法,去库存脚本编写;
2.SpringBoot整合Lua脚本,配置类RedisScript<Long>;
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存储方式: 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脚本

在这里插入图片描述

将书去(减少)库存放入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方法测试;

在这里插入图片描述

(2)JMeter进行高并发测试

在这里插入图片描述

在这里插入图片描述

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.进行测试

在这里插入图片描述

在这里插入图片描述


总结

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/165097.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!