一、背景
本文的内容以自己在日常工作遇到的问题切入。订单中心会对接上游各业务线的下单请求,而创建订单一般会关联到商品的查询和库存的扣减。
如图所示,随着业务的发展,订单中台接入的业务请求量越来越大,订单中心和商品库存中心的压力越来越大。所以目前保障中台订单交易的稳定就显 得日益重要,在日常的系统运行中,我们遇到了一些亟待解决的痛点。
(1)目前系统没有在下单接口做统一的限流。中台目前接入了多个业务方,没有基于业务身份去做精细化的限流。如果上游某一业务流量突增,难以保 证不会影响其他业务的请求。
(2)部分业务存在商品抢购的场景。某些热门商品的下单QPS峰值会超过1000,库存表单行的库存记录会频繁更新,对数据库造成的 压力非常大,同时会影响到drds同一分区数据表的相关服务。 因此,我们希望可以引入精细化限流,可以基于参数限制某些特定的流量。
二、目标
主要是希望限流效果可以达到以下目标
(1)基于业务身份,各个业务线之间创建订单的流量实现隔离限流
(2)对于热门商品的库存扣减可以进行限流
而在限流选型上,结合我们服务部署在本地和云上机房的实际情况,希望达成的目标如下
(1)本地机房和云上机房的实例都可以接入限流
(2)单个服务的所有实例作为集群设置阈值来进行流控
三、可选方案
1.内部的commons-rate-limiter包
com.google.common.util.concurrent.RateLimiter
包是google开源的一个简单限流工具,限流原理是token-bucket
算法,基于RateLimiter包的话可以使用concurrentHashMap存储多个限流资源。 可以提供接口级别的限流(不支持熔断)。 目前内部有一些服务是在用这个包。
优点:
- 接入非常简单,无代码侵入
- 可以通过动态配置限流参数,维护成本很小。
缺点:
- 功能单一,只能提供方法级别的限流,没有后续扩展的计划。
- 只支持单机限流。
2. 开源sentinel
Sentinel 是面向分布式服务架构的轻量级流量控制框架,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来 帮助您保护服务的稳定性。 阿里巴巴开源的项目,可以提供集群化的限流能力,提供了可视化的操作页面,需要单独部署。 如果使用sentinel的集群限流能力,则需要单独部署token server或者采用embedded模式部署(embedded模式下每个服务可能都要单 独配置,可行性不高)。 限流的流量统计原理是滑动时间窗口算法。
部署方案
需要部署一个Sentinel Dashboard(控制台)实例和Token Server,服务实例以SDK接入的方式接入限流。
优点:
- 提供了集群化的限流功能和热点参数限流功能。
- 提供了可视化的操作界面。
- 代码开源,可以基于源项目做一些改造。
缺点:
- Sentinel Dashboard的配置不支持持久化,仅保存在内存。如图中所示,如果需要持久化,则需要进行改造,接入配置中心(需要使用配置中心的读写API,可能会有不安全因素)。
- Token Server维护token server和client之间的映射,但这种mapping关系是简单地以ip+port进行配置的,sentinel并不提供动态的服务发现。而 我们的服务在本地机房和阿里云上都有实例,部署在阿里云上的服务实例是以K8S形式部署的,每次发布ip都会发生变更。如果引入的话,则需要每 次都进行手动配置变更,可行性很低。
- Token Server会存在单点问题,请求token的流量都会打到该实例。如果token server宕机,则只能退化到本地限流。如果考虑token server的高可 用方案,则引入了更复杂的因素。
3. 阿里云AHAS
应用高可用服务AHAS是一款专注于提高应用高可用能力的SaaS产品,提供应用架构自动探测、故障注入式高可用能力演练、一键应用 防护和增加功能开关等功能,可以快速低成本地提升应用可用性。
接入方案:
优点:
- 提供了集群化的基础流控功能。
- 控制台的功能非常强大,提供了较为全面的实例和流量监控功能。
- 流控规则不需要自己去做持久化。
- 接入成本比较低,不需要部署控制台和token server。
缺点:
- 暂不支持热点参数的集群化限流。
- 云上版本需要根据流量等级收取一定的费用。
四、综合评估
commons-rate-limiter
开源sentinel
阿里云AHAS
结论: 根据综合比较,commons-rate-limiter和开源sentinel都无法满足上面提到的核心目标,所以选择了阿里云AHAS作为流控的实施方案。
五、具体实践
- 服务接入
- 引入ahas 相关依赖
- 系统中为了保证接口的扩展性,将入口请求都封装成了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());
}
测试详情
测试场景一:简单限流
用户下单,对创建订单总流量进行限制。 测试接口QPS=1500,在压测过程中开启流控规则 配置限流规则为单机QPS阈值=500,流控效果为快速失败。
在控制台开启限流规则后,超过阈值的请求就直接拒绝了。(可以看到流控效果是比较稳定的)
###测试场景二:热点参数限流
如图所示,多个用户下单,订单有多种商品,但其中某个商品是热门商品,这个时候设置商品GoodID为热点参数,配置热点参数限流阈值为200。 压测QPS=1500,其中热点商品GoodID = xxx的 QPS=500,其他商品的请求为1000。
这个时候可以看到流控系统统计到GoodID相同的请求QPS约为500,则每秒会有300的对该商品的请求被限流(热点请求QPS 减去 设置的热点阈值),而非 热点的请求不受影响,符合预期。
###测试场景三:集群限流
接入测试环境的所有机器,AHAS会动态发现所有client,组建集群,如下图所示。
服务一共有11个实例,配置集群限流为200,如果集群通信失败,则退化本地阈值为40。
之后开始向集群发送请求(QPS有波动),可以看到集群的通过QPS被限制在200,符合预期。
总结
阿里云AHAS提供了较为完善的服务流控功能,基本实现原理和开源Sentinel相同,但是功能和支持更全面。可以作为系统接入流控功能的一个选择。 目前可以提供的功能有
- 单机限流
- 集群限流
- 热点参数限流(单机版)
同时,提供了很多配置选项,例如失败退化策略和退化单机阈值。同时,也提供了比Sentinel更为完善的监控Dashboard。
限流的实现原理其实离不开Time Window
, Token Bucket
和Leaky Bucket
这样的经典算法,如果对这些算法的原理有比较清晰的理解,设计一个限流系统并不是很难的事情。