当前位置:首页 > 公众号精选 > 架构师社区
[导读]这里,我们实现Web接口限流,具体方式为:使用自定义注解封装基于令牌桶限流算法实现接口限流。 不使用注解实现接口限流 搭建项目 这里,我们使用SpringBoot项目来搭建Http接口限流项目,SpringBoot项目本质上还是一个Maven项目。所以,小伙伴们可以直接创建

这里,我们实现Web接口限流,具体方式为:使用自定义注解封装基于令牌桶限流算法实现接口限流。

不使用注解实现接口限流

搭建项目

这里,我们使用SpringBoot项目来搭建Http接口限流项目,SpringBoot项目本质上还是一个Maven项目。所以,小伙伴们可以直接创建一个Maven项目,我这里的项目名称为mykit-ratelimiter-test。接下来,在pom.xml文件中添加如下依赖使项目构建为一个SpringBoot项目。

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>
    <groupId>io.mykit.limiter</groupId>
    <artifactId>mykit-ratelimiter-test</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>mykit-ratelimiter-test</name>

    <properties>
        <guava.version>28.2-jre</guava.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version><!--$NO-MVN-MAN-VER$-->
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

可以看到,我在项目中除了引用了SpringBoot相关的Jar包外,还引用了guava框架,版本为28.2-jre。

创建核心类

这里,我主要是模拟一个支付接口的限流场景。首先,我们定义一个PayService接口和MessageService接口。PayService接口主要用于模拟后续的支付业务,MessageService接口模拟发送消息。接口的定义分别如下所示。

  • PayService

package io.mykit.limiter.service;
import java.math.BigDecimal;
/**
 * @author binghe
 * @version 1.0.0
 * @description 模拟支付
 */

public interface PayService {
    int pay(BigDecimal price);
}
  • MessageService

package io.mykit.limiter.service;
/**
 * @author binghe
 * @version 1.0.0
 * @description 模拟发送消息服务
 */

public interface MessageService {
    boolean sendMessage(String message);
}

接下来,创建二者的实现类,分别如下。

  • MessageServiceImpl

package io.mykit.limiter.service.impl;
import io.mykit.limiter.service.MessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
/**
 * @author binghe
 * @version 1.0.0
 * @description 模拟实现发送消息
 */

@Service
public class MessageServiceImpl implements MessageService {
    private final Logger logger = LoggerFactory.getLogger(MessageServiceImpl.class);
    @Override
    public boolean sendMessage(String message) {
        logger.info("发送消息成功===>>" + message);
        return true;
    }
}
  • PayServiceImpl

package io.mykit.limiter.service.impl;
import io.mykit.limiter.service.PayService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
/**
 * @author binghe
 * @version 1.0.0
 * @description 模拟支付
 */

@Service
public class PayServiceImpl implements PayService {
    private final Logger logger = LoggerFactory.getLogger(PayServiceImpl.class);
    @Override
    public int pay(BigDecimal price) {
        logger.info("支付成功===>>" + price);
        return 1;
    }
}

由于是模拟支付和发送消息,所以,我在具体实现的方法中打印出了相关的日志,并没有实现具体的业务逻辑。

接下来,就是创建我们的Controller类PayController,在PayController类的接口pay()方法中使用了限流,每秒钟向桶中放入2个令牌,并且客户端从桶中获取令牌,如果在500毫秒内没有获取到令牌的话,我们可以则直接走服务降级处理。

PayController的代码如下所示。

package io.mykit.limiter.controller;
import com.google.common.util.concurrent.RateLimiter;
import io.mykit.limiter.service.MessageService;
import io.mykit.limiter.service.PayService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

/**
 * @author binghe
 * @version 1.0.0
 * @description 测试接口限流
 */

@RestController
public class PayController {
    private final Logger logger = LoggerFactory.getLogger(PayController.class);
    /**
     * RateLimiter的create()方法中传入一个参数,表示以固定的速率2r/s,即以每秒2个令牌的速率向桶中放入令牌
     */

    private RateLimiter rateLimiter = RateLimiter.create(2);

    @Autowired
    private MessageService messageService;
    @Autowired
    private PayService payService;
    @RequestMapping("/boot/pay")
    public String pay(){
        //记录返回接口
        String result = "";
        //限流处理,客户端请求从桶中获取令牌,如果在500毫秒没有获取到令牌,则直接走服务降级处理
        boolean tryAcquire = rateLimiter.tryAcquire(500, TimeUnit.MILLISECONDS);
        if (!tryAcquire){
            result = "请求过多,降级处理";
            logger.info(result);
            return result;
        }
        int ret = payService.pay(BigDecimal.valueOf(100.0));
        if(ret > 0){
            result = "支付成功";
            return result;
        }
        result = "支付失败,再试一次吧...";
        return result;
    }
}

最后,我们来创建mykit-ratelimiter-test项目的核心启动类,如下所示。

package io.mykit.limiter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author binghe
 * @version 1.0.0
 * @description 项目启动类
 */

@SpringBootApplication
public class MykitLimiterApplication {

    public static void main(String[] args){
        SpringApplication.run(MykitLimiterApplication.class, args);
    }
}

至此,我们不使用注解方式实现限流的Web应用就基本完成了。

运行项目

项目创建完成后,我们来运行项目,运行SpringBoot项目比较简单,直接运行MykitLimiterApplication类的main()方法即可。

项目运行成功后,我们在浏览器地址栏输入链接:http://localhost:8080/boot/pay。页面会输出“支付成功”的字样,说明项目搭建成功了。如下所示。

亿级流量场景下如何为HTTP接口限流?看完我懂了!!

此时,我只访问了一次,并没有触发限流。接下来,我们不停的刷浏览器,此时,浏览器会输出“支付失败,再试一次吧…”的字样,如下所示。

亿级流量场景下如何为HTTP接口限流?看完我懂了!!

在PayController类中还有一个sendMessage()方法,模拟的是发送消息的接口,同样使用了限流操作,具体代码如下所示。

@RequestMapping("/boot/send/message")
public String sendMessage(){
    //记录返回接口
    String result = "";
    //限流处理,客户端请求从桶中获取令牌,如果在500毫秒没有获取到令牌,则直接走服务降级处理
    boolean tryAcquire = rateLimiter.tryAcquire(500, TimeUnit.MILLISECONDS);
    if (!tryAcquire){
        result = "请求过多,降级处理";
        logger.info(result);
        return result;
    }
    boolean flag = messageService.sendMessage("恭喜您成长值+1");
    if (flag){
        result = "消息发送成功";
        return result;
    }
    result = "消息发送失败,再试一次吧...";
    return result;
}

sendMessage()方法的代码逻辑和运行效果与pay()方法相同,我就不再浏览器访问 http://localhost:8080/boot/send/message 地址的访问效果了,小伙伴们可以自行验证。

不使用注解实现限流缺点

通过对项目的编写,我们可以发现,当在项目中对接口进行限流时,不使用注解进行开发,会导致代码出现大量冗余,每个方法中几乎都要写一段相同的限流逻辑,代码十分冗余。

如何解决代码冗余的问题呢?我们可以使用自定义注解进行实现。

使用注解实现接口限流

使用自定义注解,我们可以将一些通用的业务逻辑封装到注解的切面中,在需要添加注解业务逻辑的方法上加上相应的注解即可。针对我们这个限流的实例来说,可以基于自定义注解实现。

实现自定义注解

实现,我们来创建一个自定义注解,如下所示。

package io.mykit.limiter.annotation;
import java.lang.annotation.*;
/**
 * @author binghe
 * @version 1.0.0
 * @description 实现限流的自定义注解
 */

@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRateLimiter {
    //向令牌桶放入令牌的速率
    double rate();
    //从令牌桶获取令牌的超时时间
    long timeout() default 0;
}

自定义注解切面实现

接下来,我们还要实现一个切面类MyRateLimiterAspect,如下所示。

package io.mykit.limiter.aspect;

import com.google.common.util.concurrent.RateLimiter;
import io.mykit.limiter.annotation.MyRateLimiter;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;

/**
 * @author binghe
 * @version 1.0.0
 * @description 一般限流切面类
 */

@Aspect
@Component
public class MyRateLimiterAspect {

    private RateLimiter rateLimiter = RateLimiter.create(2);

    @Pointcut("execution(public * io.mykit.limiter.controller.*.*(..))")
    public void pointcut(){

    }

    /**
     * 核心切面方法
     */

    @Around("pointcut()")
    public Object process(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();

        //使用反射获取方法上是否存在@MyRateLimiter注解
        MyRateLimiter myRateLimiter = signature.getMethod().getDeclaredAnnotation(MyRateLimiter.class);
        if(myRateLimiter == null){
            //程序正常执行,执行目标方法
            return proceedingJoinPoint.proceed();
        }
        //获取注解上的参数
        //获取配置的速率
        double rate = myRateLimiter.rate();
        //获取客户端等待令牌的时间
        long timeout = myRateLimiter.timeout();

        //设置限流速率
        rateLimiter.setRate(rate);

        //判断客户端获取令牌是否超时
        boolean tryAcquire = rateLimiter.tryAcquire(timeout, TimeUnit.MILLISECONDS);
        if(!tryAcquire){
            //服务降级
            fullback();
            return null;
        }
        //获取到令牌,直接执行
        return proceedingJoinPoint.proceed();

    }

    /**
     * 降级处理
     */

    private void fullback() {
        response.setHeader("Content-type""text/html;charset=UTF-8");
        PrintWriter writer = null;
        try {
            writer =  response.getWriter();
            writer.println("出错了,重试一次试试?");
            writer.flush();;
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(writer != null){
                writer.close();
            }
        }
    }
}

自定义切面的功能比较简单,我就不细说了,大家有啥问题可以关注【冰河技术】微信公众号来进行提问。

接下来,我们改造下PayController类中的sendMessage()方法,修改后的方法片段代码如下所示。

@MyRateLimiter(rate = 1.0, timeout = 500)
@RequestMapping("/boot/send/message")
public String sendMessage(){
    //记录返回接口
    String result = "";
    boolean flag = messageService.sendMessage("恭喜您成长值+1");
    if (flag){
        result = "消息发送成功";
        return result;
    }
    result = "消息发送失败,再试一次吧...";
    return result;
}

运行部署项目

部署项目比较简单,只需要运行MykitLimiterApplication类下的main()方法即可。这里,为了简单,我们还是从浏览器中直接输入链接地址来进行访问

效果如下所示。

亿级流量场景下如何为HTTP接口限流?看完我懂了!!

接下来,我们不断的刷新浏览器。会出现“消息发送失败,再试一次吧..”的字样,说明已经触发限流操作。

亿级流量场景下如何为HTTP接口限流?看完我懂了!!

基于限流算法实现限流的缺点

上面介绍的限流方式都只能用于单机部署的环境中,如果将应用部署到多台服务器进行分布式、集群,则上面限流的方式就不适用了,此时,我们需要使用分布式限流。至于在分布式场景下,如何实现限流操作,我们就在下一篇中进行介绍。

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

亿级流量场景下如何为HTTP接口限流?看完我懂了!!

亿级流量场景下如何为HTTP接口限流?看完我懂了!!

亿级流量场景下如何为HTTP接口限流?看完我懂了!!

长按订阅更多精彩▼

亿级流量场景下如何为HTTP接口限流?看完我懂了!!

如有收获,点个在看,诚挚感谢

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭