头大!!!接口每秒有调用次数限制?

前言

最近在项目中接触了一个需求,需求中有一部分是需要调用一个 “每秒有调用次数限制的接口” ,第一次碰到,感觉比较有意思,记录一下需求以及和小伙伴们一起构思的解决方案分享给大家。

1、需求描述

公司最近在做一个项目,需要调用第三方的接口获取订单数据。但是第三方系统提供出来的接口有调用限制,每秒只能被请求6次,如果超过这个限制,就会报异常。需求要求在保证效率的前提下(每秒调用6次需要打满),还需要这个功能高可用(少报错甚至不报错)。

2、问题所在

问题1:多线程并发时无法控制每秒调用总量问题:

如果说这个需求,在我们学生时代开发的demo中出现,我觉得这都不会算是问题。因为,毕竟你只有一台机跑项目,而且自己写的项目基本上也是单线程。简单的将任务进行分组调用,然后记录时间即可,伪代码如下:

				/**
                 * 调用第三方接口获取订单数据方法
                 * @param userReqs:用户请求信息列表
                 */
                public Result getOrdersByThird(List<UserRequest> userReqs) throws InterruptedException {
                    List<UserRequest> userReqGroup = new ArrayList<>();
                    for (UserRequest userReq : userReqs) {
                        userReqGroup.add(userReq);
                        // 将用户请求分为每6个一组,一旦达到了6个就开始调用第三方接口
                        if(userReqGroup.size() == 6){
                            long startTime = System.currentTimeMillis();
                            for (UserRequest callRequest : userReqGroup) {
                                开始调用第三方接口,详细代码省略.....
                            }
                            long diff = System.currentTimeMillis() - startTime;
                            // 如果处理一组请求时间小于1s(1000 ms),让线程休眠快出的时间;否则代码接着往下执行即可。
                            if(diff < 1000){
                                Thread.sleep(diff);
                            }
                            // 每一组请求调用完成之后,清空分组,让分组列表重新计数
                			userReqGroup.clear();
                        }
                    }
                    // 返回处理结果,代码省略....
                }

可是,企业级开发,代码肯定是运行在多台机器上的,且每一台机器都会多线程去跑,多机器多线程的话,上述代码肯定就会有问题,因为你只能保证当前在这台机器上执行这段代码的线程调用不超过6次/s,但是无法保证每秒调用的总量不超过这个阈值。

3、解决方案

前提条件: 调用第三方的代码,我们公司是用两台机去跑的,且每台机的定时任务中配置了四个线程。

方案一: 既然问题是来源于多机器多线程并发的时候,无法控制每秒调用的总量,那我们是不是可以考虑分布式锁呢,部署了代码的每台机器的每个线程,调用之前先尝试去获取分布式锁,如果拿到了就执行调用代码,没拿到就休眠一会再尝试。这里采用 redis 的 setnx() 做分布式锁书写代码案例。

				/**
                 * 调用第三方接口获取订单数据方法
                 * @param userReqs:用户请求信息列表
                 */
                public Result handOrders(List<UserRequest> userReqs) throws InterruptedException {
                    // 首先按照每组6个,将请求信息进行分组
                    List<UserRequest> userReqGroup = new ArrayList<>(6);
                    for (UserRequest userReq : userReqs) {
                        userReqGroup.add(userReq);
                        // 将用户请求分为每6个一组,一旦达到了6个就开始调用第三方接口
                        if(userReqGroup.size() == 6){
                            getOrdersByThird(userReqGroup);
                            // 每一组请求调用完成之后,清空分组,让分组列表重新计数
                            userReqGroup.clear();
                        }
                    }
                    // 返回处理结果,代码省略....
                }

                public void getOrdersByThird(List<UserRequest> userReqGroup) throws InterruptedException {
                    // 尝试获取redis分布式锁
                    if(redis.setnx(key_mutex, "1")){
                        // 根据业务耗时设置互斥锁3min过期时间,防止因为机器宕机导致死锁
                        redis.expire(key_mutex, 3 * 60);
                        long startTime = System.currentTimeMillis();
                        for (UserRequest callRequest : userReqGroup) {
                            开始调用第三方接口,详细代码省略.....
                        }
                        long diff = System.currentTimeMillis() - startTime;
                        // 如果处理一组请求时间小于1s(1000 ms),让线程休眠快出的时间;否则代码接着往下执行即可。
                        if(diff < 1000){
                            Thread.sleep(diff);
                        }
                        // 使用完成之后把互斥锁(键给删掉)释放掉
                        redis.delKey(key_mutex);
                    } else {
                        // 如果获取不到,就休息50毫秒重试
                        Thread.sleep(50L);
                        getOrdersByThird(userReqGroup);
                    }
                }

方案二: 每次调用完第三方接口之后,让线程休眠1s,但是这样仍然会可能出现问题。比如说:我机器A让线程1去跑这段代码,然后20ms跑完了,睡眠1s,然后机器A可能去调度线程2跑这段代码,同样也是花20ms跑完,睡眠1s…以此类推,因为公司跑这段代码的最大线程数是8,所以仍然可能会出现在1s内超过6次【阈值】调用的情况的。

那如果出现这种机制咋整呢?简单!对于超过6次限制的调用请求,虽然说返回失败了,但是我们可以在代码里面捕获,然后提供重试机制不就好了嘛,这时可能会有小伙伴说了,如果重试中的请求也失败了咋整呢?那就再多重试一次呗,我们一般会提供2-3次重试机制,对付公司同时并发量不高的情况下,应该还是够用了的。因为代码比较简单,这里就只是阐述解决方案了。

分享到此就结束了,完结撒花~~~~


我是杰哥,一个在IT行业中正在不断学习的程序员。 欢迎各位说话好听的人才们一键三连,你们的支持是我更新的最大动力,咱们下期见~

文章持续更新,可以微信搜索「 杰哥是真想教会你 」第一时间阅读,回复【面试】有我准备的一些在精不在多的面试资料。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值