服务、服务间接口限流实现

简介: `shigen`是一位坚持更新博客的写手,专注于记录个人成长、分享认知与感动。本文探讨了接口限流的重要性,通过实例分析了在调用第三方API时遇到的“请求过多”问题及其解决方法,包括使用`Thread.sleep()`和`Guava RateLimiter`进行限流控制,以及在分布式环境中利用Redis实现更高效的限流策略。

shigen坚持更新文章的博客写手,记录成长,分享认知,留住感动。个人IP:shigen

背景

接口的限流相信大家并不陌生,如果服务不做好限流的话,极容易造成网络、内存、磁盘等等性能的波动,重则出现服务被流量打挂的场景,服务的限流更多的是主动的防御。那么服务调用三方的接口不限流的话,就容易造成三方服务的大面积报错。最近在阅读同事的代码时候,就遇到了这样的一个问题:在for循环中调用第三方接口:

for (int i=0;i< ids.length(); i++) {
   
  callApi(args);
}

最后查看日志,三方接口疯狂的抛出错误:

too many requests!

先来说下我是怎么解决的。批量调用三方的API场景是捞取数据库中某个字段为空的记录,然后去调用三方的接口,解析接口返回的字段,更新DB。因为这些数据是需要我们在开发环境和预发环境提前准备好的,所以不会考虑到生产环境的并发。没错,解决起来也是很简单的。

查看了文档,发现官方的描述是这样的:

As a free api, only allows one request peer secord.

意思也就是qps是1。或许搞惯了高并发的,觉得qps是1就很low,但是免费的才是最贵的这句话不是没有道理的。

于是果断的修改了下代码,在原来的代码中加上这样的逻辑:

for (int i=0;i< ids.length(); i++) {
   
  Thread.sleep(1000);
  callApi(args);
}

让每个线程的执行时间间隔1s。再重新部署代码,调度下来,虽然执行的慢了许多,但是基本上调用三方接口没有再出现请求次数过多的错误了。

优化

似乎以上的方式就能很好的解决这个问题,但是稍微再往深处想一下,如果是多线程的调用、如果是集群的并发调用怎么办。在这里,我们把这两种场景分开讨论。

多线程实现

简单的Thread.sleep()貌似确实可以解决问题,但是我们也得看下它的弊端:

  • 会阻塞当前的线程,不适合高并发的场景,会占用系统的资源
  • 不是很灵活
  • 最重要的是并发场景下,根本起不到流控的作用

我们写个多线程的代码看下:

    private static void MethodOne() {
   
        for (int i = 0; i < CALL_TIMES; i++) {
   
            int finalI = i;
            new Thread(() -> {
   
                try {
   
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
   
                    throw new RuntimeException(e);
                }
                callApi(finalI);
                log.info("exec subThread other task....");
            }).start();
        }
    }

代码很简单,就是在for循环中起多线程然后去调用接口。来看下实际的调用结果:

日志输出

可以看出这里并发情况下,并没有做好流控。

GoogleGuava RateLimiter就更的解决这个问题,先看下代码:

    // permitsPerSecond 一秒钟一个请求
    static RateLimiter limiter = RateLimiter.create(1);

    private static void MethodTwo() {
   
        for (int i = 0; i < CALL_TIMES; i++) {
   
            int finalI = i;
            new Thread(() -> {
   
                limiter.acquire();
                callApi(finalI);
                log.info("exec subThread other task....");
            }).start();
        }
    }

这里就是用到了guava的com.google.common.util.concurrent.RateLimiter,实现的类似令牌桶的算法(相关的流控算法可参考文章:常见的限流算法- python版本)。查看日志,符合我们想要的流控效果:

日志输出

这个令牌是基于JVM层面的,和JUC包下的各种锁一样。

分布式环境

上边已经提交到了googleGuava是基于JVM层面的,那么在分布式环境下,这些就都不适用了。这里我能想到的就是Redis,天然的分布式资源解决方案。相关的文章其实很早之前就有过分享了,可以查看原文:如何优雅地实现接口防刷

其实核心就是这个全局的拦截器:

当然,对于Redis的操作并不是原子性的,这里可以有一个优化点:使用LUA脚本

好了,以上就是今天的全部内容了,与shigen一起,每天不一样!

目录
相关文章
|
7月前
|
缓存 算法 Java
限流算法 - 基本实现
限流算法 - 基本实现
62 0
|
6月前
|
监控 Java API
5.Gateway之限流、熔断
5.Gateway之限流、熔断
170 0
|
7月前
|
存储 算法 NoSQL
常见限流算法及其实现
在分布式系统中,随着业务量的增长,如何保护核心资源、防止系统过载、保证系统的稳定性成为了一个重要的问题。限流算法作为一种有效的流量控制手段,被广泛应用于各类系统中。本文将详细介绍四种常见的限流算法、两种常用的限流器工具,从原理、源码的角度进行分析。
450 0
|
7月前
|
缓存 Java 应用服务中间件
常见的限流降级方案
【1月更文挑战第21天】
|
7月前
|
算法 Go API
限流算法~
限流算法~
64 1
|
7月前
|
缓存 算法 NoSQL
常见限流算法解读
常见限流算法解读
|
算法
限流常见的算法有哪些?
常见的限流算法有以下几种:
85 0
|
缓存 NoSQL 算法
限流实现-专题一
在实际业务中,经常会碰到突发流量的情况。如果公司基础架构做的不好,服务无法自动扩容缩容,在突发高流量情况下,服务会因为压力过大而崩溃。更恐怖的是,服务崩溃如同多米诺骨牌,一个服务出问题,可能影响到整个公司所有组的业务。
|
缓存 算法 网络协议
限流实现2
剩下的几种本来打算能立即写完,没想到一下三个月过去了,很是尴尬。本次主要实现如下两种算法 - 令牌桶算法 - 漏斗算法
|
算法 NoSQL API
限流功能的实现
限流功能的实现
188 0