掘金 后端 ( ) • 2024-04-26 16:44

🙏废话不多说系列,直接开干🙏

女23.webp

一、Guava RateLimiter 限流简介

Guava 提供的 RateLimiter 可以限制 物理 或逻辑资源的被访问速率。(有点类似 Samephore,但是有不同,RateLimiter 控制的是速率,Samphore 控制的是 并发量。)

RateLimiter 的原理类似于 令牌桶,它主要由 许可发出的速率来定义,如果没有额外的配置,许可证将按每秒许可证规定的固定速度分配,许可将被平滑的分发,若请求超过 permitsPerSecond  则 RateLimiter 按照每秒 1/permitsPerSecond 的速率释放许可。

二、使用 RateLimiter

(1)引入 maven 依赖
<!-- 引入 google Guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>30.1-jre</version>
</dependency>
(2)简单测试示例
import com.google.common.util.concurrent.RateLimiter;

/**
 * guava 限流组件
 */
public class RateLimiterTest {
    public static void main(String[] args) {
        long startTimeStamp = System.currentTimeMillis();
        // 这里的1 表示每秒允许处理的量为1个
        RateLimiter limiter = RateLimiter.create(1.0);
        for (int i = 0; i < 10; i++) {
            // 请求 RateLimiter ,超过 permits 会被阻塞
            limiter.acquire();
            System.out.println("call permits ..." + i);
        }
        long endTimestamp = System.currentTimeMillis();
        System.out.println(endTimestamp - startTimeStamp);
    }
}
(3)实际项目使用演示

① 新建 ServiceImpl 提供API限流

import com.google.common.util.concurrent.RateLimiter;
import org.springframework.stereotype.Service;

@Service
public class GuavaRateLimiterService {

    /**
     * 每秒控制 5 个许可
     */
    RateLimiter rateLimiter = RateLimiter.create(5.0);

    /**
     * 获取令牌
     *
     * @return 是否成功获取
     */
    public boolean tryAcquire() {
        return rateLimiter.tryAcquire();
    }
}

② controller 层

import edu.study.module.up.service.GuavaRateLimiterService;
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.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * guava ratelimiter API限流
 */
@RestController
@RequestMapping(path = {"/guava"})
public class GuavaRateLimiterController {

    @Autowired
    private GuavaRateLimiterService rateLimiterService;
    Logger logger = LoggerFactory.getLogger(GuavaRateLimiterController.class);

    @ResponseBody
    @RequestMapping("/ratelimiter")
    public Result testRateLimiter() {
        if (rateLimiterService.tryAcquire()) {
            logger.info("成功获取许可");
            return new Result().success(1001, "成功获取许可");
        }
        logger.info("未获取许可");
        return new Result().success(1002, "未获取许可");
    }
}

class Result {
    private Integer code;
    private String msg;

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Result() {
    }

    private Result(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Result success(Integer code, String msg) {
        return new Result(code, msg);
    }
}
(4)使用切面和注解改造

使用上述方式使用RateLimiter的方式不够优雅,尽管我们可以把RateLimiter的逻辑包在service里面,controller直接调用即可,但是如果我们换成:自定义注解+切面 的方式实现的话,会优雅的多,

① 自定义注解类
package edu.study.module.up.annotation;

import java.lang.annotation.*;

// 自定义注解可以不包含任何属性,成为一个表示注解
@Inherited
@Documented
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimitAspect {
}
② 自定义切面类
package edu.study.module.up.annotation;

import com.alibaba.fastjson.JSONObject; // 引入 fastjson 依赖包即可
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.RateLimiter;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * 切面:对@RateLimitAspect拦截点进行限流处理
 */
@Component
@Scope
@Aspect
public class RateLimitAop {

    @Autowired
    private HttpServletResponse response;

    /**
     * 比如说,我这里设置“并发数”为 5
     */
    private RateLimiter rateLimiter = RateLimiter.create(5.0);

    @Pointcut("@annotation(edu.study.module.up.annotation.RateLimitAspect)")
    public void serviceLimit() {

    }

    @Around("serviceLimit()")
    public Object around(ProceedingJoinPoint joinPoint) {
        Boolean flag = rateLimiter.tryAcquire();
        Object obj = null;
        try {
            if (flag) {
                obj = joinPoint.proceed();
            } else {
                String result = JSONObject.toJSONString(ImmutableMap.of(100, "failure"));
                output(response, result);
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("flag=" + flag + ",obj=" + obj);
        return obj;
    }

    private void output(HttpServletResponse response, String msg) throws IOException {
        response.setContentType("application/json; charset=UTF-8");
        ServletOutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            outputStream.write(msg.getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            outputStream.flush();
            outputStream.close();
        }
    }

}
③ 测试 Controller 类
package edu.study.module.up.controller;

import edu.study.module.up.annotation.RateLimitAspect;
import edu.study.module.up.service.GuavaRateLimiterService;
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.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * guava ratelimiter API限流
 */
@RestController
@RequestMapping(path = {"/guava"})
public class GuavaRateLimiterController {

    @Autowired
    private GuavaRateLimiterService rateLimiterService;
    Logger logger = LoggerFactory.getLogger(GuavaRateLimiterController.class);

    @ResponseBody
    @RequestMapping("/ratelimiter")
    public Result testRateLimiter() {
        if (rateLimiterService.tryAcquire()) {
            logger.info("成功获取许可");
            return new Result().success(1001, "成功获取许可");
        }
        logger.info("未获取许可");
        return new Result().success(1002, "未获取许可");
    }

    @RateLimitAspect // 如果需要对某个接口进行限流,则使用此注解标记即可
    @ResponseBody
    @RequestMapping("/test/ratelimiter")
    public String test() {
        return new Result().success(1001, "success").toString();
    }
}

class Result {
    private Integer code;
    private String msg;

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Result() {
    }

    private Result(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Result success(Integer code, String msg) {
        return new Result(code, msg);
    }
}
④ 测试结果演示

image.png

动态测试演示图:

RatelimiterTest2.gif

OVER

🙏至此,非常感谢阅读🙏

女23.webp