谈谈从网关限流的技术选型到使用Redis+Lua实现分布式并发限流控制的整个过程

谈谈从网关限流的技术选型到使用Redis+Lua实现分布式并发限流控制的整个过程

本文记录了笔者在平时工作中自接到任务后做的需求调研、技术选型、分析设计、代码实现等一系列过程,总结记录下来作为积累,亦可为有需要的参考下。

为啥要做网关限流

随着业务不断的增长,用户大幅增加,现有的硬件设施短时间内无法到位,系统优化已做过一轮又一轮,为保证系统健康稳定的运行,考虑在如下几种情形下该如何处理呢?

  • 某天请求量大增,且都是商户的正常业务请求,超过了系统处理能力,商户请求大量超时了,怎么办?
  • A商户一直在调用查询接口导致其他商户的正常业务请求大量超时,怎么办?

第一种情况是商户都是正常业务请求,但因为同时请求的商户太多了,导致请求量太大,超出了系统处理能力导致请求超时。这种情况下需要确认系统的TPS(QPS)、系统吞吐量等一些系统性能健康指标,然后将请求控制在安全指标范围内即可,关于如何确认这些系统指标,网上有许多资料,本文不在此详述。
第二种情况是因为某个商户A占用了大量的请求资源导致其他商户的请求大量超时,这时应该对商户A的请求做限制,不让他过多的占用宝贵的请求资源,将其占用的让给其他商户请求使用。

网关限流选型

确认需求后就要考虑要怎么做了。有没有现成的工具可以直接拿来用啊?等等诸如此类的一些想法。
但首先,基于之前的分析,我们希望我们的限流具备的功能有:

  1. 能够控制系统总请求量
  2. 能够控制某个商户A的总请求量
  3. 能够控制某个接口(如交易查询接口)的总请求量
  4. 能够控制商户A的某个接口(如交易查询接口)的总请求量
  5. 能够控制其他自定义维度的总请求量,如某个请求IP进来的总请求量

带着这些目的,查找相关资料,最终有三个解决方案/工具可供选择。如下:

  1. Guava的RateLimiter基于令牌桶算法的本地限流工具。其适用于单服务限流,多台集群服务限流不准确。
  2. Alibaba的Sentinel分布式系统的流量防卫兵,1.4.0 开始引入了集群流控模块,之前版本也是基于本地限流的模式。1.4.0之前会有流量不均导致总体限流效果不佳的问题。假设集群中有 10 台机器,我们给每台机器设置单机限流阈值为 10 QPS,理想情况下整个集群的限流阈值就为 100 QPS。不过实际情况下流量到每台机器可能会不均匀,会导致总量没有到的情况下某些机器就开始限流。因此仅靠单机维度去限制的话会无法精确地限制总体流量。(在选型时,1.4.0版本还未发布)
  3. 基于Redis + Lua的分布式限流模式开发。Redis + Lua模式是个高性能、原子性的,所以其支持集群模式下的精确限流。

具体对比如下表:

解决方案 特点 适用场景
Guava的RateLimiter 使用令牌桶算法,即一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。桶中存放的令牌数有最大上限,超出之后就被丢弃或者拒绝。当流量或者网络请求到达时,每个请求都要获取一个令牌,如果能够获取到,则直接处理,并且令牌桶删除一个令牌。如果获取不到,该请求就要被限流,要么直接丢弃,要么在缓冲区等待。可以实现平滑突发限流和平滑预热限流。 支持单机限流,不支持集群限流
Alibaba的Sentinel 分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。 1.4.0之前版本支持单机限流,集群总体限流效果不佳;1.4.0及之后版本支持单机限流和集群限流
基于Redis + Lua的分布式限流模式 高性能、原子性的流量累加计数判断限流模式 支持单机限流,支持集群限流

因我们的系统是集群部署的,且在选型时Sentinel系统的1.4.0版本还未发布,故我们选择了第三种方案,即基于Redis + Lua的分布式限流模式开发限流功能。现在,Sentinel系统已经发展得比较完善了,而且不单单只有限流,还有其他如流量整形、熔断降级、系统负载保护、热点防护等多个维度来防护系统,是个比较合适的选择。我们在选型时,应首选现有的比较成熟易用的组件。这里主要是记录整理当时的整个过程。

限流系统设计演进

技术选型完成之后就到系统设计了。第一版设计是为网关限流量身定制的(数据库表字段是按照网关的参数设置的),之后评审时觉得应该可以用到其他系统的限流,但这版设计肯定是不符合要求了,后面再设计一版通过了。

  • V0版本——为网关接口限流量身打造

网关限流配置表如下(BIZ_SYS、PARTNER_ID和SERVICE为联合唯一索引):

名称 描述 字段类型 长度 是否主键 是否必填
ID 主键 VARCHAR2 32
BIZ_SYS 业务系统,规则配置使用的系统 VARCHAR2 16
PARTNER_ID 平台(商户)ID,该字段为空时取ALL字符串 VARCHAR2 32
SERVICE 网关接口服务名,该字段为空时取ALL字符串 VARCHAR2 64
PERMITS 许可数(限流值) NUMBER
LIMIT_SECONDS 限流时长(单位秒),默认值1 NUMBER 10
MEMO 备注 VARCHAR2 128
STATUS 状态 1-有效,2-失效 CHAR 1
GMT_CREATE 创建时间 TMIESTAMP
CREATOR 创建人 VARCHAR2 50
GMT_MODIFIED 最后修改时间 TMIESTAMP
MODIFIER 最后修改人 VARCHAR2 50

该版因只针对网关的特殊参数字段(平台ID、服务名)做限流,其他的字段(如IP等)无法处理,且无法适用于其他系统,故须改进设计。

  • V1版本——支持自定义限流规则,满足多维度限流要求

TP_TRAFFIC_RULE_TYPE_CONF(限流规则类型配置表)如下:

名称 描述 字段类型 长度 是否主键 是否必填
RULE_TYPE_CODE 规则类型编码 VARCHAR2 16
BIZ_SYS 业务系统,规则配置使用的系统 VARCHAR2 16
RULE_TYPE_NAME 规则类型名称 VARCHAR2 64
RULE_FACTOR 规则要素,规则使用的字段名,多个以英文,分开,如指定商户指定接口服务名的规则要素为partnerId,service VARCHAR2 64
MEMO 备注 VARCHAR2 128
STATUS 状态 1-有效,2-失效 CHAR 1
GMT_CREATE 创建时间 TMIESTAMP
CREATOR 创建人 VARCHAR2 50
GMT_MODIFIED 最后修改时间 TMIESTAMP
MODIFIER 最后修改人 VARCHAR2 50

TP_TRAFFIC_RULE_CONF(网关限流配置表)如下(BIZ_SYS、RULE_TYPE_CODE、RULE_FACTOR_VALUE为联合唯一索引):

名称 描述 字段类型 长度 是否主键 是否必填
ID 主键 VARCHAR2 32
BIZ_SYS 业务系统,规则配置使用的系统 VARCHAR2 16
RULE_TYPE_CODE 规则类型编码 VARCHAR2 16
RULE_FACTOR_VALUE 配置具体限流规则要素值,json字符串,如指定商户a指定接口服务b的规则要素值为{“partnerId”:“a”,“service”:“b”} VARCHAR2 256
PERMITS 许可数(限流值) NUMBER
LIMIT_SECONDS 限流时长(单位秒),默认值1 NUMBER 10
MEMO 备注 VARCHAR2 128
STATUS 状态 1-有效,2-失效 CHAR 1
GMT_CREATE 创建时间 TMIESTAMP
CREATOR 创建人 VARCHAR2 50
GMT_MODIFIED 最后修改时间 TMIESTAMP
MODIFIER 最后修改人 VARCHAR2 50

该版可根据不同限流要求配置对应限流规则以达到限流目的。也满足其他系统接入限流功能需求。

基于配置规则的限流配置思路:
如需要控制网关查询接口(接口编码为query)允许每秒100个请求,并且a商户的网关查询接口允许每秒5个请求,该如何设置?

Step1.配置限流规则类型:并将规则类型以接入的业务系统BIZ_SYS的维度缓存在redis中,如网关接入的缓存key=gateway,限流规则类型配置表记录如下:

RULE_TYPE_CODE BIZ_SYS RULE_TYPE_NAME RULE_FACTOR MEMO STATUS
spclService gateway 指定接口规则类型 service 指定接口规则类型 1
spclSerMerchant gateway 指定接口指定商户规则类型 service,partnerId 指定接口指定商户规则类型 1

Step2.配置限流规则:并将限流规则配置以{BIZ_SYS}{RULE_TYPE_CODE}{依次遍历拼接RULE_FACTOR_VALUE}做为key放入redis缓存起来供后续累加计数使用。

如下配置redis的缓存key依次为:
gateway_spclService_query、
gateway_spclSerMerchant_query_null(这里因partnerId未配置值所以取null,该配置为spclSerMerchant规则类型query接口服务的默认配置)、
gateway_spclSerMerchant_query_a

ID BIZ_SYS RULE_TYPE_CODE RULE_FACTOR_VALUE PERMITS LIMIT_SECONDS STATUS
1 gateway spclService {“service”:“query”} 100 1 1
2 gateway spclSerMerchant {“service”:“query”} 2 1 1
3 gateway spclSerMerchant {“service”:“query”,“partnerId”:“a”} 5 1 1

上表中3条限流规则分别表示:

1.gateway系统接入的spclService规则类型匹配规则要素字段为service=query时,限定流量为每秒100个请求;
2.gateway系统接入的spclSerMerchant规则类型匹配规则要素字段为service=query且未找到指定partnerId值配置时的默认配置,限定流量为每秒2个请求;
3.gateway系统接入的spclSerMerchant规则类型匹配规则要素字段为service=query且partnerId=a时,限定流量为每秒5个请求。

其中,对于配置1和2的区别再做下说明。

配置1的限流规则类型为spclService,该类型只配置了一个要素字段service,所以该配置是针对接口服务名service这个字段做的一个总限流配置,即不管是哪个商户的请求,只要是调query接口服务的,都要累加到这个规则中做限流判断。

而对于配置2的限流规则类型为spclSerMerchant,该类型配置了两个要素字段,即service和partnerId,而配置2中只有service=query,partnerId字段配置没有,这表示的是一条指定接口指定商户的默认规则配置,当未找到针对该商户的配置时则使用该配置。例如,b商户也同样调用query接口,因找不到针对b商户的spclSerMerchant类型规则配置,故其使用配置2这条规则。同理,若c商户也同样调用query接口时也使用配置2这条规则。那有同学就问了,这不是和配置1一样了吗?都用在配置2这条规则上了。答案是否定的。看是使用了同一个规则,时则它是借用该配置,复制一个副本到自己的规则配置中!b商户调用query接口的配置2这条规则在redis的缓存key为gateway_spclSerMerchant_query_b,而c商户调用query接口的配置2这条规则在redis的缓存key为gateway_spclSerMerchant_query_c,所以针对b、c商户调用query接口的限流计算也是分开独立计算的。

注:这里的所有配置都会立即更新到redis中,以保证配置是最新的。

限流逻辑流程

在这里插入图片描述

1.通过业务系统gateway查询到有效的限流规则类型列表
2.根据列表个数开启多线程执行限流逻辑
3.拼装限流规则key去redis查找限流规则,拼装格式为{BIZ_SYS}{RULE_TYPE_CODE}{依次遍历拼接RULE_FACTOR_VALUE}。其中,BIZ_SYS和RULE_TYPE_CODE在1.中的规则类型有,RULE_FACTOR_VALUE值拼装则是根据限流服务公布出去的接口Map参数获取的,按规则类型表的RULE_FACTOR中的配置顺序依次获取拼装成redis的限流规则key。
如a商户调用query接口,通过gateway查询到配置了两条有效的限流规则类型spclService和spclSerMerchant,再开启2个线程分别处理限流逻辑,其中spclService的RULE_FACTOR配置了service字段则拼装的查找redis限流规则key为gateway_spclService_query,spclSerMerchant的RULE_FACTOR配置了service,partnerId字段则拼装的查找redis限流规则key为gateway_spclSerMerchant_query_a。
4.调用redis LUA脚本计算是否限流
5.合并多线程计算限流的结果返回调用系统

限流代码

接入限流的业务系统调用限流方法如下:

/**
 * 限流服务接口
 *
 */
public interface RateLimitFacade {
   

    /**
     * 限流判断方法
     *
     * @param bizSys 限流请求的业务系统编码,如gateway
     * @param rateLimitReq 限流请求map
     * @return 限流结果,当限流时success=false并返回限流对应的resultCode、unityResultCode及错误信息,未限流时success=true并且resultCode、unityResultCode=S0001
     */
    ModelResult<String> rateLimit(String bizSys, Map<String,String> rateLimitReq);
}

限流实现类:

/**
 * @ClassName RateLimitFacadeImpl
 * @Description 限流服务接口实现
 * 
 **/
@Service
@Component
@Slf4j
public class RateLimitFacadeImpl implements RateLimitFacade {
   

    @Resource
    private RateLimitCoreService rateLimitCoreService;

    @Override
    public ModelResult<String> rateLimit(String bizSys, Map<String, String> rateLimitReq) {
   
        try {
   
            //请求对象参数校验
            Assert
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值