为什么要限流

限流的本质目的在于保护系统。一般来说,我们系统地运行状态都是可控的,我们也会给予系统足够的资源让其能够正常稳定的运行。但是在某些特殊的状态下(例如促销、秒杀、或者是系统BUG,我遇到过最多的是第三种情况),系统的压力可能会陡然增加,此时要保证系统能够提供稳定的运行而不至于挂掉就必须使用限流。限流是一种对系统自身的保护措施,保护系统不会因为瞬时的大量请求而崩溃。毕竟相较于系统处理速度放慢或者丢失一些数据而言,系统挂掉这个最糟糕的情况是我们无论如何都不愿意见到的。

实现限流的策略

当我们需要把业务数据同步到搜索引擎的时候,我们可以有多种数据同步方案。最简单的一种策略就是直接在搜索引擎中开启一个REST接口,之后业务直接调用该接口即可实现数据的增删改,该方案的主要问题在于HTTP协议是有事务的,简单来说就是一个HTTP请求必然要对应一个HTTP响应,这就导致当搜索引擎实现限流时,前端的请求很有可能会超时,这显然不是我们所愿意看到的。

我们主要使用如下的方案来进行数据同步:

  1. 搜索引擎监听MQ中指定的Topic,并且对应此Topic中的msg定义了一系列的操作;
  2. 业务线向指定Topic中发送消息,搜索引擎在收到消息后,会把消息直接写到Redis中,等待后续处理;
  3. 搜索引擎的SLA会根据限流策略定时的从Redis取消息,之后对消息的内容进行解析;
  4. 根据解析的结果来决定操作的类型,如果是删除操作就直接删除该数据;
  5. 如果是更新或者新增数据,则搜索引擎根据预先定义好的内容去业务线的接口查询数据,之后把数据保存下来;

从上面可以看到,业务线现在只需要提供变更消息和数据查询接口,并且整个操作变成了异步的,这对实现限流操作是非常有益的。

在上数据同步操作的第三步中,我们根据定好的SLA策略从Redis中取消息,这是实现SLA的根本方法。事实上,我们可以从两个维度来定义不同的SLA:

  • 业务线角度
  • 用户角度

业务线角度来定义SLA的含义是,对于不同的业务线,我们给予不同的策略。例如,一般来说,商品数据的新增速度是远低于订单数据的新增速度的,这也很容易理解,因为一件商品一般都会对应多个订单。由于这个事实,我们可以给予商品数据与订单数据不同的限流策略,让订单数据的限流阈值高于商品数据。

而用户角度来定义SLA的含义则是给予流量大的用户更高的限流阈值。其实这也非常容易理解,某些大客户的日订单数是肯定要大于那些不活跃的用户的,那么此时再让这些用户使用同样的限流策略显然是不合理的。所以,除了业务线我们还可以对不同的用户实现不同的限流策略。

业务线 \ 用户 大客户 小客户
商品数据 策略A 策略B
订单数据 策略C 策略D

如上面的表格所示,我们可以使用更为细致的SLA策略来保证系统在压力陡增时的稳定性并且不影响用户平时的正常使用。

限流算法

考虑到用户操作的实际情况,一般来说用户是有可能在短时间内进行比较多的操作的,但是长时间的大量操作显然是不合理的。基于以上事实(或者是推断),我们可以限制用户在指定时间内的最大操作次数。例如,我们可以限制用户在5s内最多发起40次请求,这40次请求你可以选择在1s内用完也可以4s内用完,但是不论你花了多长时间,一旦你在5s内的请求数达到了40次,那么在5s内接下来的剩余时间里,所有的请求都会被延迟执行。

如果把整个时间轴看成离散的,那么每5s的时间就是一个点。假设我们已经定义好了一个开始时间t1,此时的请求数n为0,后面每次发生请求时我们都会把n+1,并且计算当前时间减去t1的结果t2

  1. t2 < 5 && n < 40,未达到阈值,系统正常运行;
  2. t2 < 5 && n == 40,达到阈值,系统触发限流操作;
  3. t2 >= 5,进入下一个时间片,t1 = 当前时间,n = 0;

总结

SLA的基本策略比较简单,但是要在系统中用好其实还是不容易的。一般来说好的SLA需要反复调整找到最合适的策略,并且还需要生产环境的检验,所以良好的日志记录是帮助提升SLA能力的关键。只有不断的根据系统的运行情况以及考虑到可能的突发情况,才能设计出一个比较实用的SLA模块。