利用redis+lua优雅实现每天给用户发短信次数限制

最近的一个需求要求实现每天给用户发短信不超过3次,并且发短信间隔不能小于1小时,思来想去有没有什么比较好的实现方式呢,最终决定用redis+lua来实现,第一次写lua脚本。还不太熟练,不过完美的把功能实现了,废话不多说,上代码

1.controller层的调用

package com.example.lua.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;


@RestController
public class Test1Controller {
    @Autowired
    private StringRedisTemplate redisTemplate;
 
    @RequestMapping("test1")
    public void test() {
        System.out.println("xxxxxxxxxxxxxx");
            //调用lua脚本并执行
            DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
            redisScript.setResultType(Boolean.class);//返回类型是Long
            redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/clear-local-key1.lua")));

            LocalDateTime now = LocalDateTime.now();
            int dayOfYear = now.getDayOfYear();
            int minute = now.getMinute();
            int hour = now.getHour();

            String userId = "123";
            //默认value
            String limitValue = "1."+(hour * 60 + minute);
            //当晚23:59:59秒过期
            int expireTime = (24*60 - (hour * 60 + minute))*60;
            Boolean ans = redisTemplate.execute(redisScript, Arrays.asList("sms_limit_key:"+userId+":"+dayOfYear), limitValue,String.valueOf(expireTime));
            if(ans) {
                System.out.println("发短信给用户");
            }
    }
}

2.lua脚本的编写

local sms_send_key = KEYS[1];
local sms_send_value = ARGV[1];
local expire_time = ARGV[2];
--解析string字符串 分解出.前缀和.后缀(str = '1.234'  prefix = 1 suffix = 234)
local function parse(str)
    local start_index ,end_index = string.find(str,".",1)
    local prefix = string.sub(str,1,start_index)
    local suffix = string.sub(str,start_index+2,-1)
    return prefix,suffix
end

local value  = redis.call("setnx",sms_send_key,sms_send_value)
--设置成功 说明之前不存在
if(value == 1)
      then   redis.call("expire",sms_send_key,expire_time)
      return true;
else
      local oldValue = tostring(redis.call("get",sms_send_key))
      local oldPre,oldSuf = parse(oldValue)
      local newPre,newSuf = parse(sms_send_value)
--超过60分钟可以发送
        if tonumber(oldPre) <=2 and (tonumber(newSuf) - tonumber(oldSuf)) >= 60
            then
            local newValue = tostring(-0)..'.'..tostring(oldSuf)
            redis.call("incrbyfloat",sms_send_key,sms_send_value)
            redis.call("incrbyfloat",sms_send_key,tonumber(newValue))
            return true;
        else
            return false;
        end
end

好了,结束

### 使用 RedisLua 实现秒杀功能 #### 方案概述 为了确保秒杀过程中的并发安全性,可以采用 Redis 结合 Lua 脚本来处理请求。这种方式能够保证操作的原子性,防止因高并发带来的数据不致问题[^2]。 #### 关键代码示例 下面是个简单的 Lua 脚本用于实现秒杀逻辑: ```lua -- 定义Lua脚本 local function try_seckill(redis_key, user_id) -- 获取当前商品剩余数量 local stock = tonumber(redis.call('GET', redis_key)) if not stock or stock <= 0 then return 'Fail' end -- 尝试减少库存并记录购买者ID redis.call('DECR', redis_key) redis.call('SADD', redis_key .. '_buyers', user_id) return 'Success' end return try_seckill(KEYS[1], ARGV[1]) ``` 此段 Lua 代码实现了如下功能: - 检查指定的商品是否有足够的库存; - 如果有,则扣除件商品并将用户的 ID 添加到已购名单中; - 返回成功或失败的结果给客户端; 该脚本通过 `redis.call()` 函数调用了两个 Redis 命令——`GET` 和 `DECR` 来读取和更新存储于 Redis 的商品库存数,并利用集合(`SET`)类型的命令 `SADD` 记录已经参与过秒杀活动的用户列表。 #### Java端调用方式 在Java应用程序里可以通过 Jedis 或 Lettuce 这样的 Redis 客户端库来执行上述 Lua 脚本。以下是使用Jedis的个例子: ```java // 创建连接池配置对象 JedisPoolConfig poolConfig = new JedisPoolConfig(); poolConfig.setMaxTotal(8); poolConfig.setMinIdle(0); try (Jedis jedis = new Jedis("localhost")) { String script = "local function try_seckill(redis_key,user_id)\n" + "..."; // 上面定义好的Lua脚本内容 Object result = jedis.eval(script, Collections.singletonList(productKey), Collections.singletonList(userId)); } catch(Exception e){ System.out.println(e.getMessage()); } ``` 以上代码展示了如何创建Redis 连接并通过 eval 方法传递 Lua 脚本以及必要的参数(如产品 key 和 用户 id),从而完成次完整的秒杀尝试[^4]。 #### 并发控制机制 由于整个秒杀流程是在单条 Lua 脚本内部完成的,因此即使面对大量并发访问也能保持良好的性能表现。更重要的是,在 eval 执行期间所有的指令都被视为单事务的部分被执行,这有效避免了竞态条件的发生[^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值