如何使用阿里云AHAS实现服务精准限流

一、背景

本文的内容以自己在日常工作遇到的问题切入。订单中心会对接上游各业务线的下单请求,而创建订单一般会关联到商品的查询和库存的扣减。

如图所示,随着业务的发展,订单中台接入的业务请求量越来越大,订单中心和商品库存中心的压力越来越大。所以目前保障中台订单交易的稳定就显 得日益重要,在日常的系统运行中,我们遇到了一些亟待解决的痛点。

system.png

(1)目前系统没有在下单接口做统一的限流。中台目前接入了多个业务方,没有基于业务身份去做精细化的限流。如果上游某一业务流量突增,难以保 证不会影响其他业务的请求。

(2)部分业务存在商品抢购的场景。某些热门商品的下单QPS峰值会超过1000,库存表单行的库存记录会频繁更新,对数据库造成的 压力非常大,同时会影响到drds同一分区数据表的相关服务。 因此,我们希望可以引入精细化限流,可以基于参数限制某些特定的流量。

二、目标

主要是希望限流效果可以达到以下目标

(1)基于业务身份,各个业务线之间创建订单的流量实现隔离限流

(2)对于热门商品的库存扣减可以进行限流

而在限流选型上,结合我们服务部署在本地和云上机房的实际情况,希望达成的目标如下

(1)本地机房和云上机房的实例都可以接入限流

(2)单个服务的所有实例作为集群设置阈值来进行流控

三、可选方案

1.内部的commons-rate-limiter包

com.google.common.util.concurrent.RateLimiter包是google开源的一个简单限流工具,限流原理是token-bucket算法,基于RateLimiter包的话可以使用concurrentHashMap存储多个限流资源。 可以提供接口级别的限流(不支持熔断)。 目前内部有一些服务是在用这个包。

优点:

  1. 接入非常简单,无代码侵入
  2. 可以通过动态配置限流参数,维护成本很小。

缺点:

  1. 功能单一,只能提供方法级别的限流,没有后续扩展的计划。
  2. 只支持单机限流。

2. 开源sentinel

Sentinel 是面向分布式服务架构的轻量级流量控制框架,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来 帮助您保护服务的稳定性。 阿里巴巴开源的项目,可以提供集群化的限流能力,提供了可视化的操作页面,需要单独部署。 如果使用sentinel的集群限流能力,则需要单独部署token server或者采用embedded模式部署(embedded模式下每个服务可能都要单 独配置,可行性不高)。 限流的流量统计原理是滑动时间窗口算法。

部署方案

img.png

需要部署一个Sentinel Dashboard(控制台)实例和Token Server,服务实例以SDK接入的方式接入限流。

优点:

  1. 提供了集群化的限流功能和热点参数限流功能。
  2. 提供了可视化的操作界面。
  3. 代码开源,可以基于源项目做一些改造。

缺点:

  1. Sentinel Dashboard的配置不支持持久化,仅保存在内存。如图中所示,如果需要持久化,则需要进行改造,接入配置中心(需要使用配置中心的读写API,可能会有不安全因素)。
  2. Token Server维护token server和client之间的映射,但这种mapping关系是简单地以ip+port进行配置的,sentinel并不提供动态的服务发现。而 我们的服务在本地机房和阿里云上都有实例,部署在阿里云上的服务实例是以K8S形式部署的,每次发布ip都会发生变更。如果引入的话,则需要每 次都进行手动配置变更,可行性很低。
  3. Token Server会存在单点问题,请求token的流量都会打到该实例。如果token server宕机,则只能退化到本地限流。如果考虑token server的高可 用方案,则引入了更复杂的因素。

3. 阿里云AHAS

应用高可用服务AHAS是一款专注于提高应用高可用能力的SaaS产品,提供应用架构自动探测、故障注入式高可用能力演练、一键应用 防护和增加功能开关等功能,可以快速低成本地提升应用可用性。

接入方案:

ahas.png

优点:

  1. 提供了集群化的基础流控功能。
  2. 控制台的功能非常强大,提供了较为全面的实例和流量监控功能。
  3. 流控规则不需要自己去做持久化。
  4. 接入成本比较低,不需要部署控制台和token server。

缺点:

  1. 暂不支持热点参数的集群化限流。
  2. 云上版本需要根据流量等级收取一定的费用。

四、综合评估

commons-rate-limiter

开源sentinel

阿里云AHAS

结论: 根据综合比较,commons-rate-limiter和开源sentinel都无法满足上面提到的核心目标,所以选择了阿里云AHAS作为流控的实施方案。

五、具体实践

  1. 服务接入
  2. 引入ahas 相关依赖
  3. 系统中为了保证接口的扩展性,将入口请求都封装成了Request的形式 需要自定义埋点接入,为了保证埋点的侵入比较小,抽出公共的util类。
public class RateLimitUtils {
    private static final int ENTRY_BATCH_COUNT = 1;
    public static <T> T limit(Executable<T> func, String resourceName) throws BoltException {
        Entry entry = null;
        try {
            entry = SphU.entry(resourceName, EntryType.IN, ENTRY_BATCH_COUNT);
            return func.execute();
        } catch (BlockException e) {
            log.warn("sentinel-rate-limit block:", e);
            throw BoltExceptionUtils.buildException(PRODUCT_SERVICE_NAME, EXCEPTION_CODE_RATE_LIMIT, "REQUEST FLOW OVER LIMIT");
        } catch (BoltException e) {
            throw e;
        } finally {
            if (entry != null) {
            entry.exit();
        }
    } 
    
    public static <T> T paramLimit(Executable<T> func, String resourceName, Object... args) throws BoltException {
        Entry entry = null;
        try {
            entry = SphU.entry(resourceName, EntryType.IN, ENTRY_BATCH_COUNT, args);
            return func.execute();
        } catch (BlockException e) {
            log.warn("sentinel-rate-param-limit block:", e);
            throw BoltExceptionUtils.buildException(PRODUCT_SERVICE_NAME, EXCEPTION_CODE_RATE_LIMIT, "REQUEST FLOW OVER LIMIT");
        } catch (BoltException e) {
            throw e;
        } finally {
            if (entry != null) {
                entry.exit(ENTRY_BATCH_COUNT, args);
            } 
        }
   }
}

接入示例

public TLockInventoryCountResponse lockInventoryCount(@LogObject TLockInventoryCountRequest request) throws BoltException, TException {

    int bizId = request.getHeader() == null ? 0 : request.getHeader().getBizId();
    return RateLimitUtils.paramLimit(() -> {
        inventoryService.lockInventoryCount(request);
        return new TLockInventoryCountResponse();
    }, RateLimitConstant.RESOURCE_OPERATE_INVENTORY, bizId, request.getInventoryId());
}

测试详情

测试场景一:简单限流

config1.png

用户下单,对创建订单总流量进行限制。 测试接口QPS=1500,在压测过程中开启流控规则 配置限流规则为单机QPS阈值=500,流控效果为快速失败。

config1.png

在控制台开启限流规则后,超过阈值的请求就直接拒绝了。(可以看到流控效果是比较稳定的)

###测试场景二:热点参数限流

system2.png

config2.png

如图所示,多个用户下单,订单有多种商品,但其中某个商品是热门商品,这个时候设置商品GoodID为热点参数,配置热点参数限流阈值为200。 压测QPS=1500,其中热点商品GoodID = xxx的 QPS=500,其他商品的请求为1000。

test2.png

这个时候可以看到流控系统统计到GoodID相同的请求QPS约为500,则每秒会有300的对该商品的请求被限流(热点请求QPS 减去 设置的热点阈值),而非 热点的请求不受影响,符合预期。

###测试场景三:集群限流

monitor3.png

接入测试环境的所有机器,AHAS会动态发现所有client,组建集群,如下图所示。

config3.png

服务一共有11个实例,配置集群限流为200,如果集群通信失败,则退化本地阈值为40。

test3.png

之后开始向集群发送请求(QPS有波动),可以看到集群的通过QPS被限制在200,符合预期。

总结

阿里云AHAS提供了较为完善的服务流控功能,基本实现原理和开源Sentinel相同,但是功能和支持更全面。可以作为系统接入流控功能的一个选择。 目前可以提供的功能有

  1. 单机限流
  2. 集群限流
  3. 热点参数限流(单机版)

同时,提供了很多配置选项,例如失败退化策略和退化单机阈值。同时,也提供了比Sentinel更为完善的监控Dashboard。

限流的实现原理其实离不开Time Window, Token BucketLeaky Bucket这样的经典算法,如果对这些算法的原理有比较清晰的理解,设计一个限流系统并不是很难的事情。

猜你喜欢

转载自juejin.im/post/7113877276403433479