【已解决】Java 项目中利用 Redis 配合 Lua 脚本对短信推送消息做推送限制

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Redis 版,经济版 1GB 1个月
简介: 【已解决】Java 项目中利用 Redis 配合 Lua 脚本对短信推送消息做推送限制

🎉场景介绍:在实际应用场景中,我们通常需要对短信推送消息做发送限制的处理,避免一定时间间隔内发送过多相同内容的推送短信对用户造成骚扰,也要防止攻击者恶意调用短信推送接口造成短信资源的极大浪费。尽管部分三方接口提供方的原生接口就已经做了限制,但是为了能够更好地满足定制化需求以及在编码过程中的疏忽造成接口重复调用导致资源浪费,因此我们需要在项目中做短信推送限制的处理。

💡方案设计:

首先,将需要推送的短信内容以及接收短信的手机号进行拼接成新的字符串,并对该字符串进行加密处理转换成16进制字符串,作为存入到 redis 中的 key.

其次,执行 Lua 脚本,该脚本用于在 redis 中存储具有过期时间的 key,并返回重复校验的结果。

最后,获取校验结果,根据校验结果进行相应的处理操作,如果是重复发送的短信就将该接口和手机号加入数据埋点,用于监控推送限制情况,并抛出重复发送的异常;否则,正常发送短信消息。

在对项目中的业务代码做了抽离后,只保留了和方案设计相关的代码,如下所示

 public class SmsService {
  private static Logger logger = LoggerFactory.getLogger(SmsService.class);
  @Autowired
    private JedisTemplate jedisTemplate;
  @Autowired
  private MeterRegistry meterRegistry;
    private static final String LIMIT_LUA = "local num = redis.call('incr', KEYS[1])\n" +
            "if num == 1 then\n" +
            "      redis.call('expire', KEYS[1], ARGV[1])\n" +
            "end\n" +
            "if num > tonumber(ARGV[2]) then\n" +
            "      return 0\n" +
            "end\n" +
            "return 1";
  /**
     * 发送短信
     * @param sendSmsRequest 发送短信请求信息
     * @return 
     */
    @Transactional
    public String sendSms(SendSmsRequest sendSmsRequest) {
      String key = sendSmsRequest.getPhoneNumber() + sendSmsRequest.getTemplateCode() + sendSmsRequest.getTemplateParam();
    key = DigestUtils.md5Hex(key);
        // 60秒内同一手机号相同内容只允许发送两次
        boolean limitFlag = checkIsLimited(key, 60L, 2L);
        if (limitFlag) {
            logger.warn("短信推送被限制,sendSmsRequest:{}", JsonTool.serialize(sendSmsRequest));
            // 加入数据埋点,用于监控超出推送限制的情况
            meterRegistry.counter("sms.repeat.limit.count", "/test/sms", uri, sendSmsRequest.getPhoneNumber(), user).increment();
            throw new ServiceException("短信推送超出限制");
        }
    // 推送短信,次数省略100w行代码...
     }
    /**
     * 推送频率限制,seconds内只允许limitNum次
     * @param key key
     * @param seconds 时间 秒
     * @param limitNum 限制次数
     * @return true:限制 false:不限制
     */
    public boolean checkIsLimited(String key, long seconds, long limitNum) {
        try {
            Long result = (Long) jedisTemplate.eval(LIMIT_LUA, CoreTool.array2List(key), CoreTool.array2List(String.valueOf(seconds), String.valueOf(limitNum)));
            return result == 0;
        } catch (Exception e) {
            logger.warn("校验是否需要推送限制异常", e);
        }
          return false;
      }
  }
}

其中,对上述 Lua 脚本功能具体的注释如下:

local num = redis.call('incr', KEYS[1])  -- 计数加1(第一次调用时,key不存在,默认会创建并赋值1)
if num == 1 then                         -- 第一次调用
  redis.call('expire', KEYS[1], ARGV[1])  -- 对key设置过期时间
end
if num > tonumber(ARGV[2]) then          -- 非第一次访问,判断是否在过期时间内大于限定的访问次数
  return 0
end
return 1


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
2天前
|
存储 缓存 NoSQL
Redis系列学习文章分享---第十三篇(Redis多级缓存--JVM进程缓存+Lua语法)
Redis系列学习文章分享---第十三篇(Redis多级缓存--JVM进程缓存+Lua语法)
8 1
|
1天前
|
NoSQL API Redis
使用Redis Lua脚本实现高级限流策略
使用Redis Lua脚本实现高级限流策略
16 0
|
1天前
|
NoSQL Java Redis
数据管理DMS产品使用合集之在使用AWS DMS与ElastiCache for Redis进行通信时遇到Java超时错误,该怎么办
阿里云数据管理DMS提供了全面的数据管理、数据库运维、数据安全、数据迁移与同步等功能,助力企业高效、安全地进行数据库管理和运维工作。以下是DMS产品使用合集的详细介绍。
6 0
|
2天前
|
消息中间件 NoSQL Java
Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)
Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)
8 0
|
2天前
|
缓存 NoSQL Java
Redis系列学习文章分享---第四篇(Redis快速入门之Java客户端--商户查询缓存+更新+双写一致+穿透+雪崩+击穿+工具封装)
Redis系列学习文章分享---第四篇(Redis快速入门之Java客户端--商户查询缓存+更新+双写一致+穿透+雪崩+击穿+工具封装)
6 0
|
2天前
|
存储 NoSQL Java
Redis系列学习文章分享---第三篇(Redis快速入门之Java客户端--短信登录+session+验证码+拦截器+登录刷新)
Redis系列学习文章分享---第三篇(Redis快速入门之Java客户端--短信登录+session+验证码+拦截器+登录刷新)
4 0
|
弹性计算 数据可视化 关系型数据库
阿里云服务器部署Java Web项目和连接MySQL数据库全流程
阿里云服务器部署Java Web项目和连接MySQL数据库全流程
5236 0
阿里云服务器部署Java Web项目和连接MySQL数据库全流程
|
1月前
|
存储 Java Maven
使用 nohup 部署和后台运行Java 项目
使用 nohup 部署和后台运行Java 项目
233 1
使用 nohup 部署和后台运行Java 项目
|
1月前
|
Java 应用服务中间件 Docker
【Docker】部署启动java项目
【Docker】部署启动java项目
86 2
|
10月前
|
Java
docker-compose部署java项目
docker-compose部署java项目
636 0