电商项目之Redis实现限制接口调用次数

在这里插入图片描述

1 问题背景

客户频繁绑定域名,这会影响电商的整个域名绑定基础服务。因此要限制客户绑定域名的次数,阻止客户频繁绑定域名。要求是10分钟内只能允许操作10次,随后必须要等待10min过后才能再次操作。

2 前言

本方案是笔者自己想出来的,还未经过生产环境的考验,如有不正确的地方欢迎在评论区中指出。

3 思路

核心是限制调用次数,想到了限流,常见的有滑动窗口算法的限流,使用到的是Redis的Sorted Set。本篇使用Redis的另外一个数据结构——List。

利用List实现队列,存储的元素是时间戳。如果队列中的元素个数小于10个,那么往队列的左端添加元素并放行,让这个请求执行后面的业务逻辑。如果队列中元素个数大于等于10个,判断最新添加进去的元素距离当前时间是否已经超过了10min,如果不超过,则先从队列的右端弹出元素,随后从队列的左端添加元素,阻止执行后面的业务逻辑。如果超过10min,则移除队列里面所有元素,从队列的左端添加元素,放行执行业务逻辑。

考虑到并发线程引发的一系列问题,使用LUA脚本保证原子性。

4 代码实现

核心代码如下所示(主要看LUA脚本怎么实现):
redisTemplate.execute()的入参也要仔细看是什么类型,笔者在这里调试了很久才跑通

@Slf4j
public class IssueDomainLimitTest extends AppTest {

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    private static final DefaultRedisScript<Long> SCRIPT = new DefaultRedisScript<>();


    public static final String SAVE_LUA_SCRIPT = "local count = redis.call('LLEN', KEYS[1])\n" +
            "local value0 = redis.call('LINDEX', KEYS[1], 0)\n" +
            "local result\n" +
            "if( count < tonumber(ARGV[1]))\n" +
            "then\n" +
            "  redis.call('LPUSH', KEYS[1], ARGV[2])\n" +
            "  result = 1\n" +
            "elseif( tonumber(ARGV[2]) < tonumber(value0) + tonumber(ARGV[4]) )\n" +
            "then  \n" +
            "  redis.call('RPOP', KEYS[1])\n" +
            "  redis.call('LPUSH', KEYS[1], ARGV[2])\n" +
            "  redis.call('EXPIRE', KEYS[1], ARGV[3])\n" +
            "  result = 0\n" +
            "else\n" +
            "  redis.call('LTRIM', KEYS[1], 1, 0)\n" +
            "  redis.call('LPUSH', KEYS[1], ARGV[2])\n" +
            "  redis.call('EXPIRE', KEYS[1], ARGV[3])\n" +
            "  result = 1\n" +
            "end\n" +
            "return result";


    static {
        SCRIPT.setScriptText(SAVE_LUA_SCRIPT);
        SCRIPT.setResultType(Long.class);
    }

    private static final Integer COUNT_LIMIT = 10;

    private static final Integer TIMEOUT = 3600;

    private static final Integer TIME_INTERVAL = 60 * 10 * 1000;

    @Test
    void test() {
        String key = "issue_domain:%s:%s";
        Long shopId = 0L;
        String domain = "ac.sho.com";
        String redisKey = String.format(key, shopId, domain);
        long currentTimeMillis = System.currentTimeMillis();
        log.info("redisKey: {}, currentTimeMillis: {}", redisKey, currentTimeMillis);
        Long countResult = redisTemplate.execute(SCRIPT, Collections.singletonList(redisKey), COUNT_LIMIT, currentTimeMillis, TIMEOUT, TIME_INTERVAL);
        
        if (countResult <= 0) {
            // 阻止继续执行
            return ;
        }
        
        log.info("countResult: {}", countResult);
    }

}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值