租户级运行时调整
考虑到大租户在举行活动时,会带来大量的流量,产生很多的消息,为了报表的展示,也需有大量的数据清洗加工任务。此时单个租户可能会消耗大量的系统资源,如果前期对流量的预估不准确,资源扩容不够,可能会导致整个系统处理缓慢或不可用。下面我们从入口流量、消息消费、任务调度几个方面来讨论在运行层面按租户调整的解决方案。
按租户管理流量
在前面我们讨论的多租户访问设计中,登录时我们是可以区分出当前会话对应的租户。常见的浏览器访问,我们可以在登录成功后,将租户代码写入到会话级Cookie中,APP可以在本地保存租户代码,访问时添加到HTTP头当中。服务端请求信息中包含了租户信息,我们就可以在网状层进行流程的分发或限流。我们使用常见的Nginx+OpenResty来举例实现方案:
像Kong、Apisix等网关都是基于这个技术组合,支持使用插件的方式来实现。
- WAF限流:很多WAF也有限流的功能,一般有规则数量的限制,规则参数、性能也各不相同,可以根据具体情况确定哪些限流可以放到WAF层。通常考虑将变化不大的规则放到WAF层设置。
- 网关分/限流:OpenResty中需要考虑Cache失效的情况,需要有超时、访问失败等的异常保护和默认值处理。分/限流的规则尽量简单高性能,避免在流量大时,因为处理耗时导致系统响应慢或宕机。
- 内网SLB:使用内网SLB可能简化运维管理,特别是在紧张的情况下,可以快速添加服务器或下线异常服务器(SLB可以设置权重),不容易出错。如果流量不是特别大,内网SLB带来的影响有限。如果确实担心影响性能或从成本考虑,也可以使用Nginx直接转发到服务器,配置和脚本要简单清晰,避免紧急情况下改错配置。
- 业务服务器:根据部署方式,扩容时,可以根据服务器镜像快速创建,也可以考虑云厂商的弹性服务,包括ServerLess服务。
- 业务关注点:当前的分流策略主要是为了隔离风险,避免单个租户的异常导致整个SaaS系统异常。所以目的是将风险较大的租户独立出来,而不是弃之于不顾。相反将其服务独立出来后,一旦出现问题,我们能够更好的针对性的对服务进行调整,能够将损失降到最低,而且不会影响其他租户。
按租户管理消息消费
在多租户的场景,所有租户都共用一个MQ。MQ的使用我们可以简单分为两类:一类是削峰填谷,将耗时操作进行异步处理,这种情况发布-订阅是1对1的关系;另一类是业务逻辑解耦,发布-订阅一般是1对多的关系。一般来说第一类流量比较大,而且存在不确定性,第二类一般流量不会太大。我们这里主要讨论的也是第一类情况。当某个租户因为业务原因或运维调整数据导致大量的消息产生,可能会导致整个系统消费缓慢。此时业务量大租户因本身业务原因,对问题的容忍度比较高,但对于其他受影响的租户则难以接受,明明业务量这么小,怎么处理还这么慢?为此我们来讨论一下对应的解决方案:
- 正常流量:对于正常情况就会存在部分租户流量较大的情况,可以在设计时考虑添加租户维度的区分。以RocketMQ为例,可以使用Topic区分业务,使用Tag来区分租户,针对需要分流的租户使用单独的Tag和消费组。当流量过大时,针对单独的消费组增加资源。
- 异常流量:如果不确定哪个租户在什么时间会有大的消息流量,当发现堆积告警时来处理,可以采用消息转发的机制来处理。默认所有租户的消费都是相同的逻辑,当出现异常流量时,将指定租户的消息重新入到新的Topic中,使用单独的消费组来处理异常流量。因为转发的速度很快,基本上可以很好的降低大流量带来的影响。因此在消息设计时,也需要能够区分出消息所属的租户,可以使用Tag或给消息添加元数据(自定义属性)的方式来区分,如果通过消息体来区分,处理的成本可能会比较高,所以不推荐。
在实际过程中,不同的业务或使用的MQ可能有差别,但解决思路可以进行参考。一类是提前区分流量和对应的消费资源;另一类是应急处理流量,能够区分流量,并在消费端分流。
部分业务适用于云厂的Serverless服务,能够支持较好的弹性,可以按规则自动扩缩容。如果消费端主要是对服务器的资源消耗,也可以考虑使用Serverless来根据流量自动伸缩,这样设计和运维更简单。如果消费端还依赖数据库等有资源限制的服务处理能力,则不建议使用此方式,否则消费服务器多了后,反而会导致整个系统有风险。
按租户调度任务
当租户的数据量变大时,需要对数据进行统计分析时,已无法直接从数据库查询出来,需要单独的数据清洗任务来进行数据聚合和计算。一般有两类统计,一类实时性要求较高,需要秒级处理完;另一类是周期统计(日、周、月、季、年)。
- 对于实时性较高的统计:可以通过消息队列来实现清洗,按租户处理可以按上面消息处理的方式进行。和异步业务逻辑处理不同,清洗的运算一般较大,尽量在从库进行,避免影响业务运行;而且数据清洗一般都是可以重置的,如果出现问题,可以针对某个时间点以后的数据进行重新清洗。
- 对于周期性统计:一般可以根据周期设置定时任务进行清洗,因为每个租户的处理逻辑是一样的,所以可以遍历租户,切换数据库连接进行处理。需要注意处理异常重试、数据执行超时中断等问题,因此需要为每个任务每个租户记录任务处理状态。如果团队有条件也可以使用大数据技术,但投入一般较大,前期不建议引入。
- 任务管理:将任务生成和任务处理进行解耦,任务正常定时生成,异常时也可以手工生成。同时包含超时/异常任务的监控、自动重试的能力。
- 任务运行:简单的设计可以晚于任务生成定时运行,所有任务处理完后退出;如果考虑任务生成的不确定性,可以考虑任务循环运行,如果没有任务就休眠几秒再检查。设计主要考虑任务对数据库访问的压力,运维的成本等。有的语言可能存在长时间运行后,占用内存多运行慢等问题,可以在任务处理运行一段时间后,进行重启(注意重启时间到后,等最后一个任务处理完)。
- 指定租户任务处理:当出现需要刷新租户数据,系统异常导致数据修复等场景,可能需要增加任务处理服务器对单个租户进行处理,因此任务处理的设计需要支持指定租户,用于专门加速处理某个租户的任务。