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

引出


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锁的应用;

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Redis引入Lua脚本的原因是因为在某些特定领域,需要扩充若干指令的原子性执行,仅使用原生命令无法完成。Redis为这样的用户场景提供了Lua脚本支持。用户可以向服务器发送Lua脚本来执行自定义动作,并获取脚本的响应数据。Redis服务器会单线程原子性地执行Lua脚本,保证在处理过程中不会被其他请求打断。 使用RedisLua脚本有以下好处: 1. 减少网络开销:可以将多个请求通过脚本的形式一次发送,减少网络时延。 2. 原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他请求插入。因此在脚本运行过程中无需担心竞态条件,无需使用事务。 3. 复用:客户端发送的脚本会永久存在Redis中,这样其他客户端可以复用这一脚本,而不需要使用代码完成相同的逻辑。 通过使用Lua脚本Redis能够更好地满足用户的特定需求,并提供更高效的执行方式。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Redis中使用Lua脚本(一)](https://blog.csdn.net/lpf463061655/article/details/98971806)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Arya's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值