2021年天猫双十一落下帷幕,支撑数千亿交易的阿里中间件也升级到公有云架构。如何有效利用云产品做好我们的业务大促备战,这是一个大家都比较关心的问题。今天趁着618大促来袭前,谈一谈我们所积累的最佳实践。
1. 大促的不确定性挑战
上图是我们的一个业务大图,大促时候我们会碰到许多的不确定性因素的挑战,如流量不确定,用户行为不确定,安全攻击不确定,研发变更风险不确定,故障影响不确定。推荐的方式从入口模拟和防护,但是这么来看其实是不能解决所有问题,所以需要IDC内部进一步做故障演练和流量防护。
因为流量不确定,所以我们需要容量评估以及业务评估确定流量峰值然后通过限流将流量变成确定性的条件;用户行为不确定,所以我们需要通过仿真、多个场景模拟用户行为进行压测与演练,我们需要做到更加真实的仿真并且及时发现系统的瓶颈与优化点,并及时优化;安全攻击的不确定性,我们需要网关有waf防护的能力,比如黑产刷单流量我们如何识别并且通过流控限制其访问量,从而保护正常用户的流量;关于研发变更风险的不确定,我们需要通过变更管控来限制大促时进行不必要的变更;因为担心出故障时影响面的不确定性,所以我们需要通过不断的故障演练来锤炼我们的系统,了解故障的影响面并且尽可能控制问题的影响面,避免系统雪崩的问题出现;大促是一个项目,备战的方法论是把大促不确定性变成确定性,希望能有一些方法论提供给大家,配合我们产品的最佳实践帮助到大家。
2. 目标确定
大促的目标有很多,比如支撑 XX 的流量峰值,30%的成本优化,流畅的用户体验等。大促备战的目标在技术同学看来一般围绕成本、效率、稳定性。成本我们考虑到Nacos、云原生网关的资源成本,以及应用的机器数量,利用好K8s的弹性能力保证稳定性与成本的一个平衡;在效率方面 PTS 全链路压测提供了开箱即用的全链路压测工具,最接近用户的仿真压测流量,可以极大地提升效率,同时MSE Switch能力提供了动态配置能力帮助预案的使用;在稳定性方面,MSE 服务治理提供了端到端的流量控制能力,以及弹性过程中的无损上下线能力保证大促过程中的流量平滑无损;同时我们可以配合MSE服务治理的全链路灰度能力,使用测试流量进行全链路的功能预演,尽早暴露问题点。
3. 备战流程
简单来说,大促备战流程需要关心以下四个点:容量评估、预案准备、预热、限流。将以下这些点都做到了,就可以把不确定因素都变成了确定性的条件,那么即使是面对大促的流量洪峰,整个系统会非常的平稳。
容量评估
容量评估的目的为了实现成本与稳定性的最佳平衡,因此我们需要基于大促的应用表现,确定我们的应用在一定的业务条件下的性能基线;同时另一方面出于成本优化的目标,我们需要确定机器的成本预算。
通过性能评估以及性能优化,确保应用的性能
评估业务变化以及系统的变化
基于业务模型评估,确定全链路压测模型,并进行全链路压测验收
评估容量的极限水位,保持 60% 的容量水位,开启弹性扩缩容能力。
性能评估
首先需要通过 PTS 平台对服务进行压测,摸清系统对外核心接口的服务能力以及相应的系统水位。阿里云产品关于压测以及全链路压测首推的就是 PTS。
PTS 对比一般的压测工具,具有以下优势
优势一:功能强大
全 SaaS 化形态,无需额外安装和部署。
0 安装的云端录制器,更适合移动端APP场景。
数据工厂功能,0 编码实现压测的 API/URL 的请求参数格式化。
复杂场景的全可视化编排,支持登录态共享、参数传递、业务断言,同时可扩展的指令功能支持多形态的思考时间、流量蓄洪等。
RPS /并发多压测模式。
流量支持动态秒级调整,百万 QPS 亦可瞬时脉冲。
强大的报表功能,将压测客户端的实时数据做多维度细分展示和统计,同时自动生成报告供查阅和导出。
压测 API/场景均可调试,压测过程提供日志明细查询。
优势二:流量真实
流量来源于全国上百城市覆盖各运营商(可拓展至海外),真实模拟最终用户的流量来源,相应的报表、数据更接近用户真实体感。
施压能力无上限,最高支持千万 RPS 的压测流量。
当性能压测出系统瓶颈后,我们需要分层次地对系统进行优化。
业务以及系统的变化
评估每次大促相比之前(如果有参考的话)的稳定性评估,以及业务动向,这次业务相比之前是否有量级的提升,业务的玩法是否有变化比如618的预售以及抢红包、满减等业务玩法是否会对系统造成稳定性的风险,对用户动向进行分析,从全局视角看系统的关键路径,保障核心业务,梳理强弱依赖。
基于业务模型评估,确定全链路压测模型,并进行全链路压测验收
要发起一次性能压测,首先需要创建一个压测场景。一个压测场景包含一个或多个并行的业务(即串联链路),每个业务包含一个或多个串行的请求(即API)。
通过压测可以对我们的系统做好一个风险识别与性能管理的目的,通过全链路压测可以提前发现系统的瓶颈,识别性能风险,防止系统的性能腐化。
云产品的资源容量评估与参考
MSE Nacos 容量参考
评估Nacos注册配置中心水位我们可以从连接数、服务数来评估,有些应用可能服务数特别多,再加上大促时候流量徒增使得应用动态扩容,随着Pod数增加,连接数、服务提供者数量也会线性增加,因此大促前保证 60% 以内的水位是必要的。
MSE 网关容量参考
评估网关水位最直观的就是按照CPU来判断,建议30%,不超过60%。为什么定的这么低?参考水位是根据集团内部网关运维经验给的,网关经常会有些突发流量,尤其对于电商类业务,例如大促、秒杀等,还有要考虑流量攻击的因素,例如ddos攻击等,网关不能根据CPU跑80%、90%这样评估容量,一旦有突发情况网关就很容易被打满。阿里内部网关比较极端,网关CPU一般会控制在10%以下。同时还有一些因素,网关突发流量一般都是瞬时的,有可能网关瞬时CPU已经被打到很高了,但是监控平均后不是很高,因此网关的秒级监控能力也是非常必要的。
预案
一句话来描述就是有意识地为潜在或者有可能出现的风险制定应对处理方案。
比如很多影响用户体验、性能的开关,一些日常为了观察分析用户行为的埋点等一些与业务无关的功能,我们在大促的时候都是需要通过预案平台关闭掉这些功能,当然预案还有一些业务流量相关的预案,涉及到流控、限流、降级后面会详细介绍。
预热
预热就是技术的容量预案,预热限流。为什么要做预热?跟我们的流量模型有关,很多时候我们的流量模型是自然上去慢慢的下来。但大促的时候可不是这样,流量可能突然暴涨几十倍,然后维持住,慢慢下来。预热要预热什么?我们当时做了很多预热,数据的预热,应用的预热,连接的预热。
数据的预热
先讲数据的预热,我们访问一个数据,它的数据链路太长了,其实离应用越近可能效果越好。首先数据预测要看什么数据该怎么预热?首先把量特别大的数据,跟用户相关的数据,就是购物车、红包、卡券,做数据库的预热。
模拟用户查询,查询这个数据库,把它的数据放到我们的内存里面。
很多应用都是靠缓存挡住的,缓存一旦失效数据库就挂掉,因为数据库挡不住。这时要提前把数据预热到缓存里面。
做数据的预热的目的是为了减少关键的数据的链路,可以从内存读到的就没必要去缓存中读,可以从缓存中读的就不应该访问数据库。
应用的预热
小流量服务预热
相比于一般场景下,刚发布微服务应用实例跟其他正常实例一样一起平摊线上总QPS。小流量预热方法通过在服务消费端根据各个服务提供者实例的启动时间计算权重,结合负载均衡算法控制刚启动应用流量随启动时间逐渐递增到正常水平的这样一个过程帮助刚启动运行进行预热,详细 QPS 随时间变化曲线如图所示:
应用小流量预热过程QPS曲线
同时我们可以通过调整小流量预热的曲线,解决大促时扩容的机器流量平滑无损。
应用小流量预热过程原理图
通过小流量预热方法,可以有效解决,高并发大流量下,资源初始化慢所导致的大量请求响应慢、请求阻塞,资源耗尽导致的刚启动 Pod 宕机事故。
值得一提的是MSE 云原生网关也支持了小流量预热,我们看一下实战中的效果,68节点是刚扩容的实例。
并行类加载
JDK7上,如果调用Classloader.registerAsParallelCapable方法,则会开启并行类加载功能,把锁的级别从ClassLoader对象本身,降低为要加载的类名这个级别。换句话说只要多线程加载的不是同一个类的话,loadClass方法都不会锁住。
我们可以看 Classloader.registerAsParallelCapable 方法的介绍
说明
protected static boolean registerAsParallelCapable() Registers the caller as parallel capable. The registration succeeds if and only if all of the following conditions are met: 1. no instance of the caller has been created 2. all of the super classes (except class Object) of the caller are registered as parallel capable
它要求注册该方法时,其注册的类加载器无实例并且该类加载器的继承链路上所有类加载器都调用过registerAsParallelCapable,对于低版本的Tomcat/Jetty webAppClassLoader 以及fastjson的ASMClassLoader都未开启类加载,如果应用里面有多个线程在同时调用loadClass方法进行类加载的话,那么锁的竞争将会非常激烈。
MSE Agent 通过无侵入方式在类加载器被加载前开启其并行类加载的能力,无需用户升级Tomcat/Jetty,同时支持通过配置动态开启类加载并行类加载能力。
参考材料:http://140.205.xx.xx/2016/01/29/3721/
连接的预热
以 JedisPool 预建连接为例,提前建立Redis等连接池连接,而不是等流量进来后开始建立连接导致大量业务线程等待连接建立。
org.apache.commons.pool2.impl.GenericObjectPool#startEvictor
protected synchronized void startEvictor(long delay) {
if(null != _evictor) {
EvictionTimer.cancel(_evictor);
_evictor = null;
}
if(delay > 0) {
_evictor = new Evictor();
EvictionTimer.schedule(_evictor, delay, delay);
}
}
JedisPool 通过定时任务去异步保证最小连接数的建立,但这会导致应用启动时,Redis连接并未建立完成。
主动预建连接方式:在使用连接之前使用 GenericObjectPool#preparePool 方法去手动去准备连接。
在微服务上线过程中,在初始化Redis的过程中提前去创建 min-idle 个 redis 连接,确保连接建立完成后再开始发布服务。
同样有类似问题,预建数据库连接等异步建连逻辑,保证在业务流量进来之前,异步连接资源一切就绪。
再谈预热
预热为什么要做。我们阿里云甚至有体系化的产品功能比如:无损上线,流量防护的预热模式等,因为预热是保障系统在大促态稳定性的很重要的一环。通过预热帮助我们的系统进入大促态,这个过程就好比于百米短跑。其他业务按照自然流来的都是长跑,但是短跑你必须得热身,没有热身,起步阶段可能就出抽筋、拉伤等大问题。
流控限流
流控是保障微服务稳定性最常用也是最直接的一种控制手段。每个系统、服务都有其能承载的容量上限,流控的思路非常简单,当某个接口的请求 QPS 超出一定的上限后,拒绝多余的请求,防止系统被突发的流量打垮。市面上最常见的方案是单机维度的流控,比如通过 PTS 性能测试预估某个接口的容量上限是 100 QPS,服务有 10 个实例,则配置单机流控 10 QPS。但很多时候,由于流量分布的不确定性,单机维度的流量控制存在一些效果不佳的情况。
典型场景1:精确控制对下游的调用总量
场景:服务 A 需要频繁调用服务 B 的查询接口,但服务 A 和 B 的容量存在差异,服务 B 约定最多给服务 A 提供总共 600 QPS 的查询能力,通过流控等手段进行控制。
痛点:若按照单机流控的策略配置,由于调用逻辑、负载均衡策略等原因,A调用B到达每个实例的流量分布可能非常不均,部分流量较大的服务 B 实例触发单机流控,但总体限制量尚未达到,导致 SLA 未达标。这种不均的情况经常会发生在调用某个依赖服务或组件(如数据库访问)的时候,这也是集群流控的一个典型场景:精确控制微服务集群对下游服务(或数据库、缓存)的调用总量。
典型场景2:业务链路入口进行请求总量控制
场景:在 Nginx/Ingress 网关、API Gateway (Spring Cloud Gateway, Zuul) 进行入口流量控制,希望精确控制某个或某组 API 的流量来起到提前保护作用,多余流量不会打到后端系统。
痛点:如果按照单机维度配置,一方面不好感知网关机器数变化,另一方面网关流量不均可能导致限流效果不佳;而且从网关入口角度来讲,配置总体阈值是最自然的手段。
MSE 服务治理集群流控
MSE 服务治理流控降级提供了以下特性:
专业的防护手段:
入口流量控制:按照服务容量进行流量控制,常用于应用入口,例如: Gateway、前端应用、服务提供方等。
热点隔离:将热点和普通流量隔离出来,避免无效热点抢占正常流量的容量。
对依赖方隔离 / 降级:对应用和应用之间、应用内部采用隔离 / 降级手段,将不稳定的依赖的对应用的影响减至最小,从而保证应用的稳定性。
系统防护:MSE 应用流控降级可以根据系统的能力(例如 Load、CPU 使用率等)来动态调节入口的流量,保证系统稳定性。
丰富的流量监控:
秒级流量分析功能,动态规则实时推送。
流量大盘编排,核心业务场景了然于胸。
灵活的接入方式:
提供SDK、Java Agent以及容器接入等多种方式,低侵入快速上线。
MSE 服务治理的集群流控可以精确地控制某个服务接口在整个集群的实时调用总量,可以解决单机流控因流量不均匀、机器数频繁变动、均摊阈值太小导致限流效果不佳的问题,结合单机流控兜底,更好地发挥流量防护的效果。
对于上面的场景,通过 MSE 服务治理的集群流控,无论是 Dubbo 服务调用、Web API 访问,还是自定义的业务逻辑,均支持精确控制调用总量,而无关调用逻辑、流量分布情况、实例分布。既可以支撑数十万 QPS 大流量控制,也支持分钟小时级业务维度小流量精确控制。防护触发后的行为可由用户自定义(如返回自定义的内容、对象)。
防止突发流量将业务打垮
通过开启 MSE 流量防护能力,根据压测结果中不同接口和系统指标配置限流、隔离以及系统保护等规则,在最短的时间内接入并提供持续提供防护。碰到一些特殊的业务场景比预期的流量要大的多的时候,通过提前配置流控规则,及时地将多余的流量拒绝或排队等待,从而保护了前端系统不被打挂。同时,在一些核心接口也出现了突发的响应时间增大的情况,下游服务挂掉导致从网关到后端服务整条链路 RT 飙高,这时候利用 MSE 实时监控和链路功能快速定位到慢调用和不稳定服务,及时进行流控和并发控制,将系统从崩溃的边缘拉了回来,业务迅速回到正常水平。这个过程就像“给系统做心脏复苏”,可以有效保障业务系统不挂掉,非常形象。
防止热点流量将业务打垮
大促中我们还要防止热点流量,一些“黑马”热点商品,或者一些黑产刷单流量超出业务预期的流量进入我们的系统后,很可能会将我们的系统拖垮,我们通过热点参数流控能力,可以将热点用户、热点商品的流量控制在我们业务模型预估的范围内,从而保护正常用户的流量,有效保护我们的系统稳定性。
保障调用端稳定性,避免级联故障
我们都知道当流量近似稳态时,并发线程数 = QPS * RT(s) ,当我们调用的下游RT升高时,那么并发的线程数将会飙高,从而出现服务调用的堆积。这也是大促中常见的一个问题,因为依赖的下游支付服务因网络抖动出现大量慢调用,导致该应用的线程池全部被该服务调用占满,无法处理正常业务流程。 MSE 实时监控和链路功能可以帮助我们快速定位到慢调用和不稳定服务,准确找到应用变慢的根因,并对其配置并发隔离策略,可以有效保证我们应用的稳定性。
保护重点业务,避免雪崩
雪崩是很可怕的一件事情,当它发生时,我们连救都救不回来了,只能眼睁睁地看着应用一个个宕机。因此在业务高峰期,某些下游的服务提供者遇到性能瓶颈,甚至影响业务。我们可以对部分非关键服务消费者配置自动熔断,当一段时间内的慢调用比例或错误比例达到一定条件时自动触发熔断,后续一段时间服务调用直接返回Mock的结果,这样既可以保障调用端不被不稳定服务拖垮,又可以给不稳定下游服务一些“喘息”的时间,同时可以保障整个业务链路的正常运转。
4. 大促的总结
每一次大促都是一次很宝贵的经验,在大促后,做好总结和复盘沉淀经验是必不可少的一环。我们需要收集整理系统的峰值指标,系统的瓶颈与短板,以及踩过的坑,思考哪些东西是可以沉淀下来帮助下一次大促备战,让下一次大促无需重头再来,让下一次大促的用户体验可以更加丝滑。
大促还有许许多多的不确定性,大促流量和用户行为是不确定的,如何将不确定性风险变成相对确定的事情?大促是一个项目,备战的方法论是把大促不确定性变成确定性,通过方法论推动产品最佳实践配合大促成功,同时也通过大促这场考试,锤炼我们的系统,沉淀我们的最佳实践。在这里祝大家618大促考试顺利,系统稳如磐石。为了用不停机的计算服务,为了永远在线的应用业务,Fighting!