从0到1构造自定义限流组件

这篇具有很好参考价值的文章主要介绍了从0到1构造自定义限流组件。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一 背景

在系统高可用设计中,接口限流是一个非常重要环节,一方面是出于对自身服务器资源的保护,另一方面也是对依赖资源的一种保护措施。比如对于 Web 应用,我限制单机只能处理每秒 1000 次的请求,超过的部分直接返回错误给客户端。虽然这种做法损害了用户的使用体验,但是它是在极端并发下的无奈之举,是短暂的行为,因此是可以接受的。

二 设计思路

常见的限流有2种思路

  • 第一种是限制总量,也就是限制某个指标的累积上限,常见的是限制当前系统服务的用户总量,例如:某个抢购活动商品数量只有 100 个,限制参与抢购的用户上限为 1 万个,1 万以后的用户直接拒绝。

  • 第二种是限制时间量,也就是限制一段时间内某个指标的上限,例如 1 分钟内只允许 10000 个用户访问;每秒请求峰值最高为 10 万。

三 限流算法

目前实现限流算法主要分为3类,这里不详细展开介绍:

1)时间窗口

固定时间窗口算法是最简单的限流算法,它的实现原理就是控制单位时间内请求的数量,但是这个算法有个缺点就是临界值问题。
为了解决临界值的问题,又推出滑动时间窗口算法,其实现原理大致上是将时间分为一个一个小格子,在统计请求数量的时候,是通过统计滑动时间周期内的请求数量。

2)漏斗算法

漏斗算法的核心是控制总量,请求流入的速率不确定,超过流量部分益出,该算法比较适用于针对突发流量,想要尽可能的接收全部请求的场景。其缺点也比较明显,这个总量怎么评估,大小怎么配置,而且一旦初始化也没法动态调整。

3)令牌桶算法

令牌桶算法的核心是控制速率,令牌产生的速度是关键,不断的请求获取令牌,获取不到就丢弃。该算法比较适用于针对突发流量,以保护自身服务资源以及依赖资源为主,支持动态调整速率。缺点的话实现比较复杂,而且会丢弃很多请求。

四 实现步骤

我们自定义的这套限流组件有是基于guava RateLimiter封装的,采用令牌桶算法以控制速率为主,支持DUCC动态配置,同时支持限流后的降级措施。接下来看一下整体实现方案

1、自定义RateLimiter Annotation标签

这里主要对限流相关属性的一个定义,包括每秒产生的令牌数、获取令牌超时时间、降级逻辑实现以及限流开关等内容

@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SysRateLimit {

    /**
     * 每秒产生的令牌数 默认500
     *
     * @return
     */
    double permitsPerSecond() default 500D;

    /**
     * 获取令牌超时时间 默认100
     *
     * @return
     */
    long timeout() default 100;

    /**
     * 获取令牌超时时间单位 默认毫秒
     *
     * @return
     */
    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;

    /**
     * 服务降级方法名称 Spring bean id
     *
     * @return
     */
    String fallbackBeanId() default "";

    /**
     * 限流key 唯一
     *
     * @return
     */
    String limitKey() default "";
}

2、基于Spring Aspect 构造切面

首先就是我们需要构造一个Aspect切面用于扫描我们自定义的SysRateLimit标签

@Slf4j
@EnableAspectJAutoProxy
@Aspect
public class SysRateLimitAspect {
    
    /**
     * 自定义切入点
     */
    @Pointcut("@annotation(com.jd.smb.service.ratelimiter.annotation.SysRateLimit)")
    public void pointCut() {

    }

    /**
     * 方法前执行限流方案
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 如果未获取到对象,直接执行方法
        if (signature == null) {
            return joinPoint.proceed();
        }

        try {
            Method method = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(), signature.getMethod().getParameterTypes());
            // 获取注解对象
            SysRateLimit sysRateLimit = method.getAnnotation(SysRateLimit.class);
            if (sysRateLimit == null) {
                return joinPoint.proceed();
            }
            
        } catch (Exception e) {
            // todo log
        }
        return joinPoint.proceed();
    }
}

获取自定义SysRateLimit标签的各种属性

 // 限流key
String limitKey = sysRateLimit.limitKey();
if (StringUtils.isBlank(limitKey)) {
    return joinPoint.proceed();
}
// 令牌桶数量
double permitsPerSecond = sysRateLimit.permitsPerSecond();
// 获取令牌超时时间
long timeout = sysRateLimit.timeout();
// 获取令牌超时时间单位
TimeUnit timeUnit = sysRateLimit.timeUnit();

将我们自定义的SysRateLimiter 和 Guava RateLimiter 进行整合

  1. 首先我们需要构造一个全局Map,用于存储我们开启限流的方法,key就是我们定义的limitKey, value就是我们转换后的Guava RateLimiter
 /**
 * 存储RateLimiter(key: limitKey value:RateLimiter )
 */
private static final Map<String, RateLimiter> LIMITER_MAP = new ConcurrentHashMap<>();
  1. 接着就是核心逻辑:这里首先从我们创建的Map中获取Guava RateLimiter,获取不到就创建RateLimiter.create(permitsPerSecond) ;然后调用RateLimiter.tryAcquire()尝试获取令牌桶,获取成功则执行后续的逻辑,这里重点获取失败后,我们需要执行我们的降级方法。(注意:Guava RateLimiter 有很多API,这里我们不展开讨论,后续会针对Guava限流的源码进行详细的解析)
RateLimiter rateLimiter;
// Map中是否存在 存在直接获取
if (LIMITER_MAP.containsKey(limitKey)) {
    rateLimiter = LIMITER_MAP.get(limitKey);
} else {
    // 不存在创建后放到Map中
    rateLimiter = RateLimiter.create(permitsPerSecond);
    LIMITER_MAP.put(limitKey, rateLimiter);
}
// 尝试获取令牌
if (!rateLimiter.tryAcquire(timeout, timeUnit)) {
    // todo 限流后降级措施
    return this.fallBack(sysRateLimit, joinPoint, signature);
}

降级方案执行

上面我们在获取令牌桶超时后,需要执行我们的降级逻辑,怎么做呢?也很简单,我们在定义SysRateLimiter的时候有个fallBackBeanId,这个就是我们执行降级逻辑的bean对象Id,需要我们提前进行创建。接着我们看一下是怎么实现的。

    /**
     * 执行降级逻辑
     *
     * @param sysRateLimit
     * @param joinPoint
     * @param signature
     * @return
     */
    private Object fallBack(SysRateLimit sysRateLimit, ProceedingJoinPoint joinPoint, MethodSignature signature) {
        String fallbackBeanId = sysRateLimit.fallbackBeanId();
        // 当没有配置具体的降级实现方案的时候 可以结合业务世纪情况设置限流错误码
        if (StringUtils.isBlank(fallbackBeanId)) {
            // 自定义的 可以结合自己系统里的进行设置
            return ApiResult.error(ResultCode.REACH_RATE_LIMIT);
        }

        try {
            // SpringContext中通过BeanId获取对象 SpringUtils只是获取bean对象的工具类 有多种实现方式 可自行百度
            Object bean = SpringUtils.getBean(fallbackBeanId);
            Method method = bean.getClass().getMethod(signature.getName(), signature.getParameterTypes());
            // 执行对应的方法
            return method.invoke(bean, joinPoint.getArgs());
        } catch (Exception e) {
            // todo error log
        }
        return ApiResult.error(ResultCode.REACH_RATE_LIMIT);
    }

这样我们大概的一个架子就弄好了。 接下来我们看看实际该如何使用

3、具体应用

在方法入口引入SysRateLimiter标签

@Slf4j
@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
public class UserQueryController extends AbstractController {

    /**
     * 查询用户信息
     *
     * @param request
     * @return
     */
    @GetMapping("/info/{id}")
    @SysRateLimit(permitsPerSecond = 500, limitKey = "UserQueryController.info", fallbackBeanId = "userQueryControllerFallBack",
            timeout = 100, timeUnit = TimeUnit.MILLISECONDS)
    public ApiResult<UserInfo> info(@PathVariable Long id, HttpServletRequest request) {
        // todo 业务逻辑查询 这里不展开
        return ApiResult.success();
    }
}

设置降级方法

@Service
public class UserQueryControllerFallBack {

    /**
     * 降级后执行的逻辑
     *
     * @param request
     * @return
     */
    public ApiResult<UserInfo> info(Long id, HttpServletRequest request) {
        // todo 编写限流降级后的逻辑 可以是降级码 也可以是默认对象
        return ApiResult.success(null);
    }
}

当请求进来的时候,会结合我们设置的阈值进行令牌桶的获取,获取失败后会执行限流,这里我们进行了限流后的降级处理。其实到这里我们完成限流组件的简单封装和使用,但是仍有一些点需要我们进行处理,例如如何动态设置令牌的数量,接下来我们就看一下如何实现令牌的动态设置。

4、动态设置令牌数量

通过DUCC配置令牌数量 我们需要定义一个DUCC配置,这里面内容很简单,配置我们设置limitKey的令牌数量

@Data
@Slf4j
@Component
public class RateLimitConfig {

    /**
     * 配置config key: limitKey value: 数量
     */
    private Map<String, Integer> limitConfig;

    /**
     * 监听ducc配置
     *
     * @param json
     */
    @LafValue(key = "rate.limit.conf")
    public void setConfig(String json) {
        if (StringUtils.isBlank(json)) {
            return;
        }
        Map<String, Integer> map = JsonModelUtils.getModel(json, Map.class, null);
        if (map != null) {
            Wrapper.wrapperBean(map, this, true);
        }
    }
}

通过DUCC配置获取指定limitKey的令牌数量,获取失败则采用方法设置默认数量,这样我们后面设置令牌数量就可以通过DUCC动态的配置了

 /**
     * 获取令牌桶数量
     *
     * @param sysRateLimit
     * @return
     */
    private double getPermitsPerSecond(SysRateLimit sysRateLimit) {
        // 方法默认令牌数量
        double defaultValue = sysRateLimit.permitsPerSecond();
        if (rateLimitConfig == null || rateLimitConfig.getLimitConfig() == null) {
            return defaultValue;
        }
        // 配置的令牌数量
        Integer value = rateLimitConfig.getLimitConfig().get(sysRateLimit.limitKey());
        if (value == null) {
            return defaultValue;
        }
        return value;
    }

5、后续其他配置

其实后续我们的其他属性都可以通过DUCC动态化的来配置,这里呢因为和令牌桶数量类似,就不再展开描述了。感兴趣的小伙伴可以自行设置,根据我们的使用,使用默认配置即可。

作者:京东零售 王磊

来源:京东云开发者社区文章来源地址https://www.toymoban.com/news/detail-492193.html

到了这里,关于从0到1构造自定义限流组件的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 服务高可用保障:服务限流,Nginx实现服务限流

    一、前言 1.1什么是限流? 限流存在于高可用服务中。 用于高可用的保护手段,主要包括:缓存,降级,限流 限流:只允许指定的事件进入系统,超过的部分将被拒绝服务,排队或者降级处理。 1.2为什么需要限流? 一:服务扛不住压力了 二:因为资源的稀缺或者处于安全防

    2024年02月08日
    浏览(37)
  • 高可用三大利器 — 熔断、限流和降级

    近年来,各大厂Google、微软、阿里、腾讯等都在提高可用的概念。高可用(High Availability,简称HA)是指系统或服务在遭受故障或异常情况时仍能持续提供稳定和可靠的运行能力。 在武侠世界里,“利器”通常指的是武器中的上乘、出色之物;武器对于武者的重要性不言而喻

    2024年02月15日
    浏览(47)
  • 高并发整体可用性:一文详解降级、限流和熔断

      水满则溢,月盈则亏,任何事物都不可能无限制的发展,我们的系统服务能力也一样。   当随着流量的不断增长,达到或超过服务本身的可承载范围,系统服务的自我保护机制的建立就显得很重要了。   本文希望可以用最通俗的解释和贴切的实例来带大家了解什么是限流

    2024年02月11日
    浏览(37)
  • EditText不显示系统键盘,可用来显示自定义的键盘

    系统键盘 包含普通键盘和现在很多ROM定制的密码安全键盘 调用已下方法即可解决: https://developer.android.google.cn/reference/android/widget/TextView#setShowSoftInputOnFocus(boolean) 但是,此方法是API 21Android 5.0加入的, 所以为了兼容低版本, 建议使用已下方法:  public static final boolean notShowSoftInput

    2024年04月10日
    浏览(33)
  • 微服务 - Nginx网关 · 进程机制 · 限流熔断 · 性能优化 · 动态负载 · 高可用

    系列目录 微服务 - 概念 · 应用 · 架构 · 通讯 · 授权 · 跨域 · 限流 微服务 - Consul集群化 · 服务注册 · 健康检测 · 服务发现 · 负载均衡 微服务 - Redis缓存 · 数据结构 · 持久化 · 分布式 · 高并发 微服务 - Nginx网关 · 进程机制 · 限流熔断 · 性能优化 · 动态负载 · 高可用

    2024年02月02日
    浏览(74)
  • springboot 自定义注解 ,实现接口限流(计数器限流)【强行喂饭版】

    思路:通过AOP拦截注解标记的方法,在Redis中维护一个计数器来记录接口访问的频率, 并根据限流策略来判断是否允许继续处理请求。 另一篇:springboot 自定义注解 ,aop切面@Around; 为接口实现日志插入【强行喂饭版】 不多说,直接上代码: 一:创建限流类型 二:创建注解

    2024年02月15日
    浏览(64)
  • DRF的限流组件(源码分析)

    限流,限制用户访问频率,例如:用户1分钟最多访问100次 或者 短信验证码一天每天可以发送50次, 防止盗刷。 对于匿名用户,使用用户IP作为唯一标识。 对于登录用户,使用用户ID或名称作为唯一标识。 局部配置(views) 全局配置(settings) 本质,每个限流的类中都有一个 all

    2023年04月21日
    浏览(32)
  • SpringBoot定义拦截器+自定义注解+Redis实现接口防刷(限流)

    在拦截器Interceptor中拦截请求 通过地址+请求uri作为调用者访问接口的区分在Redis中进行计数达到限流目的 定义参数 访问周期 最大访问次数 禁用时长 代码实现 定义拦截器:实现HandlerInterceptor接口,重写preHandle()方法 注册拦截器:配置类实现WebMvcConfigurer接口,重写addIntercep

    2024年02月05日
    浏览(59)
  • redis + AOP + 自定义注解实现接口限流

    限流(rate limiting) ​ 是指在一定时间内,对某些资源的访问次数进行限制,以避免资源被滥用或过度消耗。限流可以防止服务器崩溃、保证用户体验、提高系统可用性。 限流的方法有很多种,常见的有以下几种: 漏桶算法: ​漏桶算法通过一个固定大小的漏桶来模拟流量

    2024年02月03日
    浏览(35)
  • SpringMvc集成开源流量监控、限流、熔断降级、负载保护组件Sentinel

    前言:作者查阅了Sentinel官网、51CTO、CSDN、码农家园、博客园等很多技术文章都没有很准确的springmvc集成Sentinel的示例,因此整理了本文,主要介绍SpringMvc集成Sentinel 随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的

    2024年02月05日
    浏览(63)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包