精华内容
下载资源
问答
  • 全链路压测及阿里全链路压测详解
    2021-06-15 23:22:49

    一、前言

    很多公司有线下性能测试,那为什么还要做全链路压测呢,全链路能解决一般性能测试的什么问题呢?我认为在每个环境做性能测试是相互补充的过程。在线下的性能测试,由于机器监控,部署迅速以及相应的权限充足,我们可以迅速定位到一些性能bug,如内存泄漏,死锁,超卖等问题,但是线下的机器达到的指标不能准确的反馈线上的实际情况,我们并不能简单通过一些充满大量经验值的公式去推算,这样的结果和拍脑袋也没啥太大差异,再加上线下环境大多以分链路、模块压测为主,所以全链路压测在这样的背景下就诞生了,我们的前提是在线下已经模块压测完成,无明显瓶颈的情况下开展,在线上进行链路的充分模拟压测。

    二、什么是全链路压测

    全链路压测就是基于实际的生产业务场景、系统环境,模拟海量的用户请求和数据对整个业务链进行压力测试,并持续调优的过程。

    三、全链路压测解决什么问题

    针对业务场景越发复杂化、海量数据冲击下整个业务系统链的可用性、服务能力的瓶颈,让技术更好的服务业务,创造更多的价值。

    四、面对的问题点以及解决方案

    1、业务模型梳理

    首先应该明确的是:全链路压测针对的是现代越来越复杂的业务场景和全链路的系统依赖。所以首先应该将核心业务和非核心业务进行拆分,确认流量高峰针对的是哪些业务场景和模块,针对性的进行扩容准备,而不是为了解决海量流量冲击而所有的系统服务集群扩容几十倍,这样会造成不必要的成本投入。

    2、数据模型构建

    数据构建和准备,应该考虑这几点问题:

    ①、数据的真实性和可用性

    可以从生产环境完全移植一份当量的数据包,作为压测的基础数据,然后基于基础数据,通过分析历史数据增长趋势,预估当前可能的数据量;

    ②、数据脱敏

    基于生产环境的全链路压测,必须考虑的一点是不能产生脏数据,以免对生产造成影响,影响用户体验等,因此在数据准备时需要进行数据脱敏;

    ③、数据隔离

    同样,为了避免造成脏数据写入,可以考虑通过压测数据隔离处理,落入影子库,mock对象等手段,来防止数据污染;

    3、压测工具选型

    全链路压测应对的都是海量的用户请求冲击,可以使用分布式压测的手段来进行用户请求模拟,目前有很多的开源工具可以提供分布式压测的方式,比如jmeter、Ngrinder、locust等。可以基于这些压测工具进行二次开发,由Contorller机器负责请求分发,agent机器进行压测,然后测试结果上传Contorller机器。
    考虑到压测量较大的情况下回传测试结果会对agent本身造成一定资源占用,可以考虑异步上传,甚至事务补偿机制。

    4、压测环境搭建

    全链路压测都是基于生产环境,解决了业务模型和数据以及压测工具选型开发,就要考虑系统扩容和风险规避了,比如压测不能影响实际的生产业务运行,还有资源申请等。重新搭建一套完全匹配生产环境的压测环境,成本太高,且需求频次较低,投入成本太大。

    5、系统容量规划

    前面提到了业务拆分和流量预估,在系统容量规划阶段,首先应该对单个接口单个服务进行基准测试,调整配置参数,得到一个基准线,然后进行分布式集群部署,通过nginx负载均衡。
    至于扩容,要考虑到服务扩容和DB资源扩容,以及服务扩容带来的递减效应。
    至于大流量冲击情况下,可以考虑队列等待、容器锁、长连接回调、事务降级等方式来解决。

    6、测试集群部署

    能做全链路压测的业务系统基本都是分布式系统架构,这时服务集群部署和负载均衡就是需要实现和考虑的技术点。
    需要解决的问题有:

    ①、服务间通信问题
    一般通信方式有两种:同步和异步。

    • 同步调用:

      REST(JAX-RS,Spring Boot)
      RPC(Thrift, Dubbo)

    • 异步调用:

      (Kafka, Notify, MetaQ)

    区别:

    • 同步调用一致性强,但是要考虑性能和调用失败的事务处理。
    • 异步调用的话,可以降低服务间的耦合,提升性能体验,但是一致性是需要解决的。

    ②、负载均衡问题

    需要将大流量冲击均匀的分发给集群上的每台机器,目前比较优秀的负载均衡服务器是nginx,但nginx的部署貌似也存在一些问题,我们公司之前就遇到过订单重复问题。

    ③、容灾问题

    需要确保的一点是:当服务中的某台或者某部分服务宕机,可以及时的进行服务转发,而不至于连锁反应下整个系统链路的服务挂掉。

    7、数据收集监控
    • 压测数据收集,需要由agent机回送给Contorller机器,但数据量过大会占用一定的资源,可以考虑异步实现测试结果回送。

    • 监控,现在有很多优秀的专业监控工具,比如Nmon、Zabbix,全链路监控工具Zipkin、PinPoint以及携程开源的全链路监控工具CAT。或者可以针对需要,二次开发JVM自带的一些监控工具,做到实时全方位监控。

    五、例:阿里全链路压测

    为什么需要容量规划?

    阿里巴巴有着非常丰富的业务形态,每种业务都由一系列不同的业务系统来提供服务,每个业务系统都分布式地部署在不同的机器上。随着业务的发展,特别是在大促营销等活动场景下(比如双11),需要为每个业务系统准备多少机器对于阿里巴巴技术团队来说是一大难题。“容量规划”正是为解决这个难题而诞生,容量规划的目的在于让每一个业务系统能够清晰地知道:什么时候应该加机器、什么时候应该减机器?双 11 等大促场景需要准备多少机器,既能保障系统稳定性、又能节约成本
    在这里插入图片描述

    容量规划四步骤

    在双 11 等大促场景的准备过程当中,容量规划一般分为四个阶段:

    • 业务流量预估阶段:通过历史数据分析未来某一个时间点业务的访问量会有多大;

    • 系统容量评估阶段:初步计算每一个系统需要分配多少机器;

    • 容量的精调阶段:通过全链路压测来模拟大促时刻的用户行为,在验证站点能力的同时对整个站点的容量水位进行精细调整;

    • 流量控制阶段:对系统配置限流阈值等系统保护措施,防止实际的业务流量超过预估业务流量的情况下,系统无法提供正常服务。

      在第一个阶段当中,通过合适的预测算法和丰富的历史数据,通常能够比较准确地预估业务的访问量。即使在第一阶段预估的业务访问量跟实际的存在误差,通过第四阶段的流量控制也能够确保站点始终处于良好的服务状态。做完业务访问量的预估之后,容量规划进入第二阶段,为系统进行容量的初步评估。如何通过精准的容量评估,用最小的成本来支撑好预估的业务量是这个阶段的核心问题。
      要计算一个系统需要多少台机器,除了需要知道未来的业务调用量之外,还有一个更重要的变量,就是单台机器的服务能力。获取单台机器的服务能力在阿里巴巴是通过单机压测的方式来获取。**在阿里,为了精准地获取到单台机器的服务能力,压力测试都是直接在生产环境进行,这有两个非常重要的原因:单机压测既需要保证环境的真实性,又要保证流量的真实性。**否则获取到的单台机器服务能力值将会有比较大的误差,影响到整个容量规划的准确性。

    生产环境进行单台机器压力测试的方式主要分为 4 种:

    • 模拟请求:通过对生产环境的一台机器发起模拟请求调用来达到压力测试的目的

      模拟请求的实现比较简单,也有非常多的开源或者商业工具可以来做请求模拟,比如 apache ab、webbench、httpload、jmeter、loadrunner。通场情况下,新系统上线或者访问量不大的系统采用这种方式来进行单机压测。模拟请求的缺点在于,模拟请求和真实业务请求之间存在的差异,会对压力测试的结构造成影响。模拟请求的另一个缺点在于写请求的处理比较麻烦,因为写请求可能会对业务数据造成污染,这个污染要么接受、要么需要做特殊的处理(比如将压测产生的数据进行隔离)。

    • 复制请求:通过将一台机器的请求复制多份发送到指定的压测机器

      为了使得压测的请求跟真实的业务请求更加接近,在压测请求的来源方式上,我们尝试从真实的业务流量进行录制和回放,采用请求复制的方式来进行压力测试。请求复制的方式比请求模拟请求方式的准确性更高,因为业务的请求更加真实了。从不足上来看,请求复制同样也面临着处理写请求脏数据的问题,此外复制的请求必须要将响应拦截下来,所以被压测的这台机器需要单独提供,且不能提供正常的服务。请求复制的压力测试方式,主要用于系统调用量比较小的场景。

    • 请求转发:将分布式环境中多台机器的请求转发到一台机器上

      对于系统调用量比较大的场景,我们有更好的处理办法。其中的一种做法我们称为请求的引流转发,阿里巴巴的系统基本上都是分布式的,通过将多台机器的请求转发到一台机器上,让一台机器承受更大的流量,从而达到压力测试的目的。请求的引流转发方式不仅压测结果非常精准、不会产生脏数据、而且操作起来也非常方便快捷,在阿里巴巴也是用的非常广泛的一种单机压测方式。当然,这种压测方式也有一个前提条件就是系统的调用量需要足够大,如果系统的调用量非常小,即使把所有的流量都引到一台机器,还是无法压测到瓶颈。

    • 调整负载均衡:修改负载均衡设备的权重,让压测的机器分配更多的请求

      与请求引流转发的方式类似,最后一种压测方式同样是让分布式环境下的某一台机器分配更多的请求。不同的地方在于采用的方式是通过去调整负载均衡设备的权重。调整负载均衡方式活的的压测结果非常准确、并且不会产生脏数据。前提条件也需要分布式系统的调用量足够大。

    在阿里巴巴,单机压测有一个专门的压测平台。压测平台在前面介绍的 4 种压测方式基础上,构件了一套自动化的压测系统。在这个系统上,可以配置定时任务定期对系统进行压测,也可以在任意想压测的时间点手动触发一次压测。在进行压测的同时,实时探测压测机器的系统负载,一旦系统负载达到预设的阈值即立刻停止压测,同时输出一份压测报告。

    因为是在生产环境进行压测,我们必须非常小心,保障压测过程不影响到正常的业务。在单机压测平台上,每个月将进行 5000 次以上的压测,系统发布或者大的变更都将通过单机压测来验证性能是否有变化,通过单机压测获取的单机服务能力值也是容量规划一个非常重要的参考依据。

    有了预估的业务访问量,也知道了系统单台机器的服务能力,粗略的要计算需要多少台机器就非常简单了。

    最小机器数 = 预估的业务访问量 / 单机能力。

    通常情况下,我们会预留少量的 buffer 来防止评估的误差和意外情况。

    为什么需要全链路压测?

    进行到这一步,我们已经完成了系统容量的粗略评估,然而做到这一步是不是就够了呢?过去的教训曾经狠狠地给我们上了一课。

    我们对每一个系统都做好了粗略的容量计算,以为一切都会比较顺利了,可是真实场景并非如此,当双 11 的零点到来的时候,许多系统的运行情况比我们想象的要更坏。原因在于真实的业务场景下,每个系统的压力都比较大,而系统之间是有相互依赖关系的,单机压测没有考虑到依赖环节压力都比较大的情况,会引入一个不确定的误差。这就好比,我们要生产一个仪表,每一个零件都经过了严密的测试,最终把零件组装成一个仪表,仪表的工作状态会是什么样的并不清楚。

    事实上我们也有一些血的教训。在 2012 年的双 11 零点,我们一个系统的数据库的网卡被打满了,从而导致部分用户无法正常购物,尽管当时我们做了非常充分的准备,但还有一些事情是我们没考虑到的。

    需要怎么样才能解决这个问题?在 2013 年的双 11 备战过程当中,在很长一段时间内这都是我们面临的一个难题。在中国,学生通常都会有期末考试,为了在期末考试中取得比较好的成绩,老师通常会让学生们在考试前先做几套模拟题。双 11 对我们的系统来说就是一年一度的期末考试,所以我们冒出了这么一个想法:“如果能让双 11 提前发生,让系统提前经历双 11 的模拟考验,这个问题就解决了”。通过对双 11 零点的用户行为进行一次高仿真的模拟,验证整个站点的容量、性能和瓶颈点,同时验证之前进行的容量评估是否合理,不合理的地方再进行适当的微调。

    我们为此研发了一套新的压测平台——“全链路压测”。双 11 的模拟可不是一件简单的事情,上亿的用户在阿里巴巴平台上挑选、购买好几百万种不同类型的商品,场景的复杂性非常高。有三个最主要的难点需要解决:

    用于的请求量非常大,在双 11 零点,每秒的用户请求数超过 1000w;

    模拟的场景要跟双 11 零点尽可能的贴近,如果模拟的场景跟双 11 零点差距太大,将不具备实际的参考价值,而双 11 零点的业务场景非常复杂;

    我们需要在生产环节去模拟双 11,如何去做到模拟的用户请求不对正常的业务和数据造成影响。

    为了能够发出每秒 1000w 以上的用户请求,全链路压测构件了一套能够发出超大规模用户请求的流量平台。流量平台由一个控制节点和上千个 worker 节点组成,每一个 worker 节点上都部署了我们自己研发的压测引擎。压测引擎除了需要支持阿里巴巴业务的请求协议,还需要具备非常好的性能,要不然 1000w 的用户请求,我们将无法提供足够多的 worker 节点。上千个压测引擎彼此配合、紧密合作,我们能像控制一台机器一样控制整个压测集群,随心所欲的发出 100w/s 或者 1000w/s 的用户请求。

    1000w+/s 的用户请求量不仅要能够发送出来,而且还需要跟双 11 的用户行为尽可能的接近,而双 11 是一个非常复杂的业务场景。为了使得模拟能够更加真实,我们做了非常多的工作。首先,我们从生产环境提取一份跟双 11 同等数量级的基础数据(包含:买家、卖家、店铺、商品、优惠等等),做好筛选和敏感字段的脱敏,作为全链路压测的基础数据。然后基于这些基础数据,结合前几年的历史数据,通过相应的预测算法,得到今年双 11 的业务模型。

    双 11 的业务模型包含 100 多个业务因子,比如:买家数量、买家种类、卖家数量、卖家种类、商品数量、商品种类,pc 和无线的占比,购物车里的商品数量,每一种业务类型的访问量级等等)。有了业务模型之后,再根据业务模型构造相应的压测请求,最终将压测请求上传到压测引擎。

    全链路压测直接在生产环境进行双 11 的模拟,在前面的单机压测方式中也有提到,对于模拟请求的方式,需要考虑脏数据的处理方式。全链路压测的所有数据都在生产环境做了数据隔离,包含存储、缓存、消息、日志等一系列的状态数据。在压测请求上会打上特殊的标记,这个标记会随着请求的依赖调用一直传递下去,任何需要对外写数据的地方都会根据这个标记的判断写到隔离的区域,我们把这个区域叫做影子区域。全链路压测对粗略的容量评估起到了精调的作用,使双 11 零点的各种不确定性变的更加确定。

    超限后的流量控制如何做?

    前面章节我们讨论的都是”容量规划”,我们知道容量规划是基于一套精密的业务模型,而这个业务模型是根据历年来的大促数据,以及复杂的预测模型推算出来的。然而,不论这个模型多么强壮,它始终是一个预测。这就意味着我们存在着预测和现实流量有误差。

    这个并不仅仅是一个担心,这个发生过非常多次。最近的一个例子是在 16 年的双 11,我们为某一个重要的场景预备了足以应付 16.2 万每秒的峰值,然而那天的峰值实际上到达了 20 万每秒,超过我们准备能力将近 13%,你可能觉得这只会对峰值产生影响,这些额外的 2W 请求马上就会被消耗掉,但并不是你想的这样。

    当一台机器超负荷运转的时候,这台处理请求的时间会变长。这会给用户带来不好的体验,用户会试图重复提交请求,这无形中又给系统带来了更多的请求压力。随着请求堆积的越来越多,系统性能会逐渐下降甚至无法响应新的请求。

    当一台机器挂掉以后, 负载均衡会把请求重定向到另外的机器上去,这又无形中给别的机器带来了更多的任务,而这些机器也处于一个饱和的状态,很快也会像第一台机器一样,也无法响应新的请求。就这样,在很短的时间之内,越来越多的机器会停止响应,最终导致整个集群都无法响应。这就使我们常常说的“雪崩效应”。一旦“雪崩”发生,就很难停止。我们必须有一个有效的机制,来监控和控制进入的流量,来防止灾难的发生。

    然而,流控并不仅仅用于流量高峰,它在很多的场景都可能用的到。比如在一个业务的链路上,有一个下游系统出现了问题,响应时间变得很长。这个问题在链路上会被放大,甚至导致整个链路不可用。这意味着流控也需要可以根据响应时间来控制系统的健康,当一个应用响应的时间超过阈值,我们可以认为这个应用不可控,应该迅速将它降级。

    除了流控的激发原因之外,流控也可以灵活的定义流控的方式。不同的业务场景,可以采取不同的流控方式。比如说,对于有的应用,我们可以简单的丢弃这个请求,有的应用,则需要对下游应用进行降级,甚至直接加入黑名单。而有的应用,则需要把这些多余的请求排队,等到高峰期过后,系统没有那么忙碌之后,再逐步消耗这些流量。

    所以,我们最终的流控框架可以从三个纬度着手,运行状况,调用关系,流控方式。应用可以灵活的根据自己的需求,任意组合。

    下面这个是我们流控的架构图:

    阿里高可用的两大法宝

    • 第一步,我们在程序入口给所有的方法都进行埋点;
    • 第二步,我们把这些埋点方法的运行状态,调用关系统计记录下来;
    • 第三步,我们通过从预设好的规则中心接收规则,来根据第二步中统计到的系统状态进行控制。

    然而,当系统发生流控的时候,系统虽然是安全的,但是它始在一个“受损”状态下运行。所以我们也在问题排除之后,解除流量控制。用我们上面的场景作为例子。一个链路上的一个下游应用出现了问题,导致响应时间变长,从而导致上游应用的系统负载过高。过了一会儿之后,这个下游应用恢复了,响应时间大大缩短。然而这个时候,上游应用的负载并不能马上恢复,因为进来的请求已经堆积了一段时间了。

    这就意味着,如果我们采用传统的方式,用系统负载来判断是否应该恢复流控,那么即使问题已经修复,系统地负载仍然处于一个比较高的状态。这样就会导致系统恢复慢。既要迅速恢复,同时也要系统稳定。最后我们采取的方式是,让 rt,load, 允许通过的 qps 达到动态平衡。

    阿里高可用的两大法宝

    让我们来看一下最后取得的效果。用了新的算法之后,我们可以看到系统稳定在一定的范围之内,同时当问题机器恢复之后,流量也能够很快的恢复。

    从近几年双 11 零点的业务稳定性上来看,全链路压测是一个明显的分水岭,在全链路压测之后整个站点的稳定性明显好于全链路压测之前。全链路压测已经成为阿里巴巴大促备战的必要环节,无论是双 11 大促、双 12 大促,还是平时一些比较小的促销活动,每一次活动之前都会进行好几轮的全链路压测来对系统进行一次全方位的模拟验证,提前暴露各个环节的问题。全链路压测的诞生使得阿里大促备战的系统稳定性有了质的提升,被誉为大促备战的核武器。

    除了全链路压测来验证我们的容量规划的正确性以外,流量控制的策略在我们的大促技术规划时也很重要,限流框架通过 自由组合运行状态,调用链路,限流措施的灵活组合,覆盖了多种业务场景。同时,通过动态平衡,可以做到快恢复,最低的减低对用户使用体验的冲击。流量控制和流量压测两者结合,让我们的系统稳定健康地渡过各种极限业务场景。

    部分转载自他人博客

    更多相关内容
  • 2020QECon全球软件质量&效能大会,性能测试专场李杰做的百万级流量无人值守全链路压测实践报告的PPT文档,分享给大家!
  • 京东618/双十一大促压测实践 京东618/双十一大促压测实践 京东618/双十一大促压测实践 京东618/双十一大促压测实践 京东618/双十一大促压测实践
  • 全链路压测

    2018-09-12 16:46:40
    GOPS全球运维大会2017·北京站资料大礼包,全链路压测
  • 旨为开始着手全链路压测以及实施全链路压测提供可行性思路以及链路执行过程的实现与思考
  • 2-华为-华为应用市场春晚百万级QPS全链路压测实践-吴进高.pdf
  • 电商大促作战指南之全链路压测

    千次阅读 2022-02-06 17:25:10
    电商大促作战指南、全链路压测、营销

    电商大促作战指南请看上一篇:电商大促作战指南,因篇幅有限,这里单独对全链路压测进行详述。

    目录

    1. 压测人员确定

    2. 核心链路业务梳理

    3. 流量预估与压测模型产出

    3.1 入口流量预估

    3.2 节点流量预估

    3.3 压测模型产出

    4. 压测数据准备与联调

     5. 压测执行

    5.1 前置工作

    5.2 分场景不同时长压测

    5.3 监控指标

    5.4 压测记录

    5.5 压测结束

    6. 压测恢复 

    7. 压测复盘


    全链路压测是一项既耗时又耗人力的工作,所以必须要做好详细且周密的计划,全链路压测涉及的重要节点如下图所示:

    另外,在做全链路压测前,核心链路涉及的应用首先要消灭掉已知的性能问题,否则会影响全链路压测进度。笔者曾经经历的几次全链路压测中没有一次能完整顺利地进行的,往往需要2~3次才能压到预期的结果。下面会针对全链路压测每个环节的流程和注意点进行详述。

    1. 压测人员确定

    压测人员需要做到广而精,“广”是指人员要覆盖到核心链路应用负责人、运维、DBA、重要中间件负责人。“精”是指压测人员需要熟知负责应用的上下游,同时能在压测期间出现性能问题时能快速解决问题。
     

    压测人员示例
    应用负责人
    导购方辰
    交易**
    营销**
    ............

    2. 核心链路业务梳理

    核心链路业务梳理内容主要包括两项:应用强弱依赖梳理(限流降级等)、高风险业务梳理(比如上次大促后,存在大版本升级的业务应用或者未经历过大促的业务)。这里最好产出一张应用大盘图,标注出核心链路,示例如下:

    图参考:极客时间《全链路压测30讲》

    在梳理出核心链路后,需要应用owner再梳理出核心接口,主要用于三个场景:性能指标监控、限流等稳定性措施配置、压测。示例如下:

    图参考:极客时间《全链路压测30讲》 

     另外高风险业务梳理完成后需要给出明确的稳定性方案,比如可降级,需要给出降级方案,不可降级需要给出可替代或者紧急预案。尽量在大促前把所有可能都设想一遍。

    3. 流量预估与压测模型产出

    流量预估比较考验应用负责人的功底,应用负责人要熟悉负责应用的上下游依赖和重要核心接口的性能指标,只有这样才能合理地进行流量预估。这个阶段应用负责人需要做几下几项工作:

    • 梳理出关键路径的核心接口,包括其上下游依赖应用及性能指标
    • 根据历史大促水位和接口性能指标预估出接口容量,比如接口的qps值
    • 输出应用压测模型

    3.1 入口流量预估

    系统流量入口是指调用链路的起始端,比如首页、商品详情页和列表页等场景的调用始端是指mall、detail等应用,预估的方式通常是参考历史GMV或者订单数量或者DAU(笔者所在公司可是使用同时在线的门店数量),在参考的同时也要考虑一些不变的因素,如营销活动、活动商品的相似性等。以20210518大促流量预估示例,在进行预估时参考了20201215的大促流量,同时营销活动场景也类似,所以具有很强的参考意义。附流量预估参考GMV通用公式如下:

    预估大促qps值=(预估大促GMV/历史大促GMV)*历史大促qps值

    另外,在预估时要考虑以下不变的因素(因素较多,需要参考具体的业务场景):

    • 营销活动差异:参考历史大促进行预估时需要注意到,相同GMV不同的营销活动场景,相应的大促qps峰值是不同的,所以在进行预估时需要考虑营销活动的差异性。比如1215和518大促活动玩法、GMV相似,那在整点的qps峰值也是相似的,是很有参考价值的。
    • 活动商品的差异:活动商品的差异会影响成交的订单量,间接影响成交的GMV,最终也会影响到流量预估的准确性。如327大促和518大促GMV一样,但针对的商品类型不一样,327大促针对的是A类商品,518针对的是B类商品,B类大促的客单价是高于A类大促的,所以理论上327大促的订单量会大于518大促的订单量,即327大促的qps值理论上会高于518大促的qps值。
    • 活动时间的差异:事实上518大促的qps值是低于各应用owner给出的qps值,是因为忽略到了季节的差异性,即“一年中的上半年是下单的淡季”。相对于年终来说流量相对较少(年终有春节和元旦重要的节假日)。
    • ......

    3.2 节点流量预估

    作为流量入口链路上的节点同样在大促中给出流量预估值,如营销核心应用smc应用和mrc应用。节点流量由入口流量根据流量分支模型,按比例转化而来。分支流量模型以系统链路为计算基础,遵循以下几个原则:

    • 同一入口,不同链路占比流量独立计算
    • 针对同一链路上同一节点,若存在多次调用,需计算按倍数同比放大
    • DB写流量重点关注

    举例说明,mrc应用中 multiMatchForKeyDataId 接口是营销的核心接口,流量预估时首先切分上游的流量,如下图

    multiMatchForKeyDataId的上游流量除应用owner评估外,还需要同各上游确认预估值是否能满足上游调用(如icm的1.085\%的流量大本次大促预估中是否有流量发生变化,若有变化,变化系数多少,一般这个值是由上游应用owner来确认),见下表:

    上表统合上面计算出mrc应用multiMatchForKeyDataId接口预估出2010,同时做好客户端和服务端限流即可,该预估方式得出的值与1222号大促值十分接近。在理想情况下应该有个接口调用地图,地图中每个应用的核心接口做为节点,节点连线表示调用关系,连线的值作为qps值。

    3.3 压测模型产出

     在各应用做好流量预测后,便可以初步得出压测模型,如下图所示,该压测模型用于后期与压测得出模型、大促真实流量模型进行对比,用于验证流量预测的偏差,为后续大促支持积累宝贵的压测经验。

    附图中值是示例值,仅供参考

    4. 压测数据准备与联调

            压测数据准备包括影子库申请、流量录制和压测数据初始化(影子库)。其中压测数据初始化是指将流量录制时间点的数据快照初始化到影子库中。笔者所在公司压测平台采用的是流量平台进行流量录制(GoReplay),录制回放过程中使用压测标透传整个链路,透传到DB层使用影子库进行隔离,这里不详述全链路压测的技术方案。压测数据准备过程中需要注意以下几点:

    • 流量录制的场景尽量选择与大促相似的场景
    • 流量录制时间点的线上数据要保存快照初始化到影子库中,前后时间不能相差太大,否则会出现压测请求得不到正常响应
    • 压测数据重点关注导购和营销数据的时效性,往往流量录制时间点和真正压测时间不在同一个时间段,经常会出现活动数据失效的场景

    基于以上数据准备后,便可进行小比例流量进行调试,在不影响线上业务的前提下确保流量入口接口请求成功率100%、响应数据能正常写入影子库、确保所有的监控都已到位等。

     5. 压测执行

     压测执行是压测过程中最关键的环节,压测的目的是指出系统的“最大”和“最佳”点,如下图所示(引用关于性能测试的几个要点):

    上图中在轻负载区域和重负载区域交界处称为"最佳点"。而重负载区域和负载失效区域交界处称为"最大点"。

    • 当系统的负载等于“最佳点”时,系统的整体效率最高,系统资源利用率适中,用户请求能够得到快速响应
    • 当系统负载处于"最佳点"和"最大点"之间时,系统可以继续工作,但是响应时间开始变长,系统资源利用率较高,并持续保持该状态,如果负载一直持续,将最终会导致少量的用户无法忍受而放弃
    • 当系统负载大于“最大点”时,将会导致较多用户因无法忍受超长的等待而放弃使用系统,有时甚至会出现系统崩溃而无法响应用户请求的情况发生

     所以压测过程中要考虑压测时长,比如180s、300s、600s系统指标是怎么样的,进而找到系统的“最佳”和“最大”性能点。因为有个很形象的说法就 是:你能够承担100千克的重量,而且也能走,但是你能否承担100千克的重量行走1个月。笔者曾经做单接口压测时就走入了一个误区,只要发现某个场景某个轮次达到系统“个人理解”意义的最高点,便停止压测了,事实上这个最高点是位于“最佳”点,还是位于“最大点”却不得而知,即无法得出系统(或者接口)的真正水位。

    5.1 前置工作

    缓存预热、接口限流值关闭等。另外压测执行前还需要确定施压流量的地域分布,应尽量拟合真实的用户分布,才能保证测试结果真实可信。对于区域性的在线业务,施压机分布在当地的同一机房,是可以理解的。如果是全国性的在线业务,施压机也应该按照用户分布,在全国各地域部署。

    5.2 分场景不同时长压测

     压测场景需要按大促业务场景来定制,比如本次大促有秒杀券,则构造基础数据与流量录入环节需要考虑相应的场景,确定好压测场景好再按系统容量可分为基线、容量摸高、稳定性和异常场景,最后分不同时长压测来观察系统稳健度,所以压测执行前需要规划好压测场景、压测轮次和每轮压测持续的时长,这是一项比较复杂的工作。

    5.3 监控指标

     在压测过程中需要时刻观察系统指标,包括但不限于:

    • 服务器指标:机器cpu、内存、网络流入流出、jvm指标等
    • 数据库指标:慢sql数量、qps、索引命中率、锁等待时长等
    • 业务指标:下单趋势、下单成功率等
    • 系统指标:核心接口qps、平均rt、失败率等
    • ......

    一旦指标出现异常则停止压测,所以这里有必要梳理出压测启动、停止和结束准则(不再详述)。压测结束后,压测报告中重点观察三个核心指标(需要具体到秒级别的统计,往往系统高峰期会产生秒级曲线):请求成功率、接口(系统)qps、接口(系统)平均rt。

    5.4 压测记录

    压测过程中还需要做好压测指标、压测问题和压测现场记录,因压测参与人多,压测时间比较宝贵,记录方式可以采用截图结合概要记录的方式来进行压测记录,只要做到重要信息不遗漏即可,方便压测结束后进行压测结果分析。

    5.5 压测结束

    压测执行结束后,一般全链路压测结果会有两种场景:

    1. 低于大促性能要求,需要解决系统瓶颈,再次进行全链路压测,直到系统满足大促目标容量
    2. 达到大促性能要求(一般达到大促预估容量1.5倍以上),则可尝试对系统进行进一步摸高,压出系统的瓶颈点

    下图是笔者压测执行过程示例(仅供参考):

    6. 压测恢复 

    压测恢复是指压测后进行的一系列有关压测资源回收和数据清理的操作,比如回收压测机器、应用缩容、回收影子库、清理压测数据等。为避免排查压测性能问题时监控信息丢失(比如影子库监控是随着影子库的回收而回收),资源的回收信息需要同步到压测技术群。笔者曾经为了排查压测产生的慢sql问题,因影子库回收而不得不自行进行一次压测来恢复现场。

    7. 压测复盘

    压测复盘是对压测过程进行分析总结,并根据以下点来决定是否要再进行一次全链路压测:

    • 是否达到大促性能要求
    • 是否出现影响大促的性能问题,如果出现性能问题,则需要分析具体性能问题并给出解决方案

    如果压测结论符合预期,可以得出系统的性能指标,并据此来配置限流降级策略来保证系统稳定性。下图示例为笔者在一次全链路压测时遇到的性能问题总结示例提供参考:


          如转载,请注明出处!欢迎关注微信公众号:方辰的博客

    展开全文
  • 链路性能压测方案介绍,本文档从宏观方面去介绍全链路压测的方案实施、技术刷选、细节
  • 点击上方“朱小厮的博客”,选择“设为星标”后台回复"书",获取后台回复“k8s”,可领取k8s资料在阿里淘宝 双11 的过程中,长期以来都是在生产环节做全链路压测的,通过实践我们发现在生产...

    点击上方“朱小厮的博客”,选择“设为星标”

    后台回复"书",获取

    后台回复“k8s”,可领取k8s资料

    在阿里淘宝 双11 的过程中,长期以来都是在生产环节做全链路压测的,通过实践我们发现在生产环境中做压测,实际上会和一个 IT 组织的结构、成熟度、流程等紧密相关,所以我们把全链路压测从简单的制作范围内脱离出来,变成整个业务连续性的方案。

    本文分四个方面为大家阐述:第一,整个全链路压测的意义,为什么要在生产环节上做全链路压测;第二,关于落地的技术点和解决方案;第三,生产过程中做全链路压测流程上的建议,考虑到每个组织的承受度不一样,给大家提供一些建议;第四,如何在第三方实现整个在生产环境中做业务连续性包括压测的结果。

    全链路压测的意义 

    ALIWARE

    8d5456b616dc003b35b22d4c05be424e.png

    上图显示了三个问题,实际上是不同的 IT 组织在和测试交流的时候,这三个问题是比较有代表性的。

    1. 很多测试同行说他们线下也做过性能测试,但是到了线上之后还是存在很多问题,因为不太可能会在线下模拟一个跟线上 1:1 的环境。在有很多第三方接口的情况下,大家也很少会去模拟线上整个场景。因此我们在线下做了很多测试工作后,总结出了为什么很多从线下容量推导到线上容量的公司却最终效果不是很好,就是这样的原因。

    2. 现在所有的 IT 组织都在搞 DevOps,我们的功能从一个月迭代一次到现在一周迭代一次,留给测试的时间越来越短。功能测试时间从之前的一周、两周缩短到现在三四天、两三天的时间,那性能测试就没有办法按时上线,很有可能会出现各种各样的性能问题,这会直接影响到企业的品牌影响力。

    3. 平时线上水位比较低,很少达到高峰期,但是会出现一些突发情况。比如像去年的疫情使得很多公司的业务变成在线业务。比如教育行业,之前是课堂上老师面对面的教育,现在选择线上在线平台来做,这类突发的情况会使测试工程师,包括开发运维团队受到很大的困扰。在这之前我先介绍一个概念,这个概念是由《黑天鹅》的原作者 Nassim Nicholas Taleb 提出,概念中心是脆弱与反脆弱

    33b9da45d5f95f88504ee148755536c6.png

    什么是脆弱?脆弱就像玻璃,大家知道玻璃很脆易碎。脆弱的反义词是什么?不是强韧也不是坚韧,可能是反脆弱。什么是反脆弱呢?比如乒乓球,大家知道乒乓球在地上不用很大的力就可以破坏掉,踩一脚就破坏掉了,但是高速运动的情况下,乒乓球我们施加的力度越大,它的反弹力度越大,说明乒乓球在运动过程中有反脆弱的特性。

    我们的 IT 系统实际上也是这样的。不管什么代码都不能保证是完全没有问题的,我们的基础设施可能也是脆弱的,像服务器、数据库等总会有局限。我们的框架也总是脆弱的,将这些问题综合在一起,我们希望通过某些手段,比如通过预案、风险的识别,或者通过一些熔断的手段,最终把这些东西组合在一起,让整个 IT 系统有反脆弱的特性。总之,我们希望通过一些手段使得 IT 系统有足够的冗余,而且有足够多的预案应对突发的不确定性风险。

    651727781db4e7c4888e9c852d9ff6db.png

    如何打造 IT 系统反脆弱能力呢?我们希望通过一些手段,比如说像线上的压测能力,提供不确定的因素,接着通过在这个过程中实时监控,包括预案的能力,最终把这些不确定性的因素识别出来,并且在线上生产压测过程中对它做一些处理,更多可能会通过事后复盘等方式,做到对不确定性因素的识别。接着我们可能会在生产环境中通过之前的手段,在生产环境上做一个稳定性的常态化压测,实现长期稳定的场景,最终我们可能达到反脆弱能力所需要的整体监控的能力、运营防护能力,以及管控路由能力,这会让整个 IT 系统具备反脆弱的特性。

    全链路压测解决方案

    ALIWARE

    如何在生产环境上做全链路压测?它需要用到哪些技术手段?

    1

    压测进程演变

    191a26d1609be45ed919e023893dc0c4.png

    一般情况下,测试是怎么样从线下一点点往线上演变的?我把它分为四个阶段:

    1. 目前绝大多数 IT 可以做到的是线下单系统压测,即针对单个接口或者单个场景做压测,同时也会做系统分析和性能分析。但在复杂的业务场景之下,我们可能没办法去充分发现问题,很多都是由开发或者测试同学自发进行的活动。

    2. 我们成立了一个类似于测试实验室或者测试组织的机构,这样一个大的部门可能会构造出一批类似于生产环境的性能测试环境,在这上面我们可能会做更多的事情,比如说做一个线下环境的全链路压测,并且我们可以根据之前积累的经验在上面做一些线下的回归,包括性能的诊断等。其实这一步相当于整个测试往前再走一步,对测试环境中的链路做一些分析,在上面演变一些能力,比如说风险的控制等等。

    3. 目前绝大部分 IT 企业和互联网企业愿意尝试线上生产环境的业务压测。这部分实际上和之前的第二阶段相差不多,但是在这个过程中人为的把它分为了两层:第一层是单纯的做全链路压测,很多 IT 公司已经在非生产环节中做了只读业务的压测,因为这样不会对数据造成污染。而再往下一层 ,有些组织可能会在正常生产时段中做进一步的全链路压测,这种情况下我们就会要求这个组织拥有更高的能力。

    比如说我们需要对整个压测流量做一些染色,能够区分出来正常的业务数据,正常的流量和非正常的压测流量,可能有的会做一些环境的隔离,而在业务生产期间内我们做生产的压测,需要考虑到整个流量的偏移、限流,包括熔断机制等。不管怎样做业务,可能都会对最终的生产业务造成一定的影响,真正出现问题的时候可能需要有快速的熔断机制。

    4. 做到压缩熔断渲染,包括对熔断的机制——有了这样的能力之后,最后一个阶段就是整个生产链路的全链路压测,包括读写,它就具备了基本能力。这个方面我们其实更多的是通过引入库表,加上技术手段,在这个生产上做全链路压测,包括读业务、写业务等,同时我们有系统故障演练和生产变更演练的能力,在这种情况下我们可能最终具备了数据隔离能力、监控隔离能力和日志隔离能力。

    2

    全链路压测关键技术

    3897c46edca7ac4501679215f0cc6acc.png

    对于整个全链路压测来说,我们需要几个关键的技术:

    • 全链路流量染色

    可能通过在压缩机上做一些标识,比如加一个后缀,或者通过一些标识手段把流量读出来,分散到相关的表里去。同时在全链路流量展示过程中我们还需要做流量的识别,对于压测流量经过的每一个中间件,每一个服务我们都希望能够准确的识别出来,这个流量是来自于压测机还是来自于正常流量,这是第一步。

    • 全链路的数据隔离

    我们需要通过哪些手段,比如通过影子库,通过运维的同学做一个和生产上面同样的影子库,然后切到影子库上,或者在生产库上做一个相同的影子表,来做数据隔离。第一种方式安全度高一些,但是缺点在于我们用影子库的时候整个生产环境是不可用的。生产影子库不能完全模拟出整个线上的情况,因为影子表需要我们有更高的技术水平,能够保障整个链路可追踪,包括整个数据如果一旦出错数据恢复能力等等。

    • 全链路风险管控机制

    也就是风险熔断机制,一旦真的发现生产环境的线上压测对我们的业务造成了影响,我们需要通过一些规则或者其他的指标来自动触发风险熔断,包括管控等等这样的手段,不管是提供施压机的流量,还是把生产系统损坏的部分做业务隔离,这样的手段都是我们做生产过程中全链路压测的必要手段。

    • 全链路日志日志隔离

    其实日志本身不会对全链路造成太大的影响,但是因为做数字化水平的提升,日志基本上是BI同学包括运营的同学对整个业务分析最重要的数据来源,如果我们不做日志隔离很有可能会对 BI 决策造成一定的影响,比如压测过程中我们会大量采用某个地域的流量对生产环境做访问,BI 的同学可能会通过日志分析发现某一个地区做大,导致他错误的运营决策,所以说对于生产过程中的全链路压测来说,我们需要在整个生产过程中做一定的日志隔离,区分出来正常的生产流量和压测流量之间的存储。

    3

    全链路压测和业务连续性平台核心功能

    d88ea37021f89622d92805c9f39120a8.png

    这部分是真正想作为全链路压测和业务连续性平台所需要的功能。

    1. 首先是有来自于全地域的压测流量工具,这个流量工具具备的功能包括全地域流量挖掘、流量改造相关的功能。

    2. 整个压测识别,包括影子存储一部分的功能。黄色的部分是正常流量,蓝色的部分是压测的流量,我们可能通过施压机的改造让蓝色的部分加入一些标识,通过 Agent 的技术,它可以标识出带有的流量,通过底层的 Agent 技术将这些落到相应的影子库或者影子表里去,或者是缓存的影子区里。

    3. 做熔断的规则管理,所以需要有合理的控制台,这里可能会做一些安装探针管理,包括整个架构的管理、库表的维护、规则的维护、熔断机制的维护等。

    4. 最后是真正的施压部分。这里可能会安装一些探针或者是 Agent,这些 Agent 的作用是能够让这些流量落到相应的影子表里去,还有是通过相应的监控指标,比如说我们的错误达到 1%,或者是检查时间超过了一定的阈值之后,Agent 会及时上报,通过规则配置起到限流的作用。

    通过这套架构,我们现在可以做到目前比按照整体环境大约节省成本是 40%左右,基本上对整个生产业务没有任何切入。

    4

    全链路压测风险防控能力

    821547c53533c35bd9e5a7b264ace198.png

    下面来具体谈一谈如何做一个影子数据库,包括整个流量识别。

    橙色的部分是真正的压测流量,这部分我们会在施压机上做一个标识,现在是会加一个后缀。另外还会在服务器做 filter,它其实是拦截器,我们会拦截到流量里面相关的标识,然后把它做区分、做染色,并且做跟踪,每一个请求基本上可以真正做到在任何中间件以及项目堆里都是透明可见的。

    真正在压测过程中通过 Agent 字节码结束将它直接改写,将字节的条件替换成压缩的条件。当然要先把影子库建好,通过底层的追踪我们可以把相应的流量,如果数据库就会走得比较明确,之后我们会做流量的测试,看看是否比较明确,而且我们可以做到整个测试数据带有标识,一旦真的没有走到诊断里面去,我们也可以在正常的表里做删除,并且每一个经过的区域对我们来说都是可见的。

    通过这样的方式,目前绝大部分 IT 组织是分三个阶段,当然有一些非常成熟的是分为两个阶段:

    1. 在上线之前发现问题,大多是由线下的开发或者测试调试过程中发现问题,然后做到整个接口的优化,确保最后没有代码的问题,包括 DNS 问题。这类问题基本上是在线下的环境,开发的环境解决掉。

    2. 在部署过程中,我们会做第三方插件比如安全等等问题,但是目前随着容器的发展,开发部署环境会被逐渐淡化掉。

    3. 在线上真正做生产环境的压测,这部分可能会做容量规划或者是压测,其他像整个大环境,比如说 CDN 或者 DNS问题,或者是整个线上系统容量评估等等问题。

    这些是我们目前在整个测试生命周期里希望在各个阶段实现的目的。

    压测流程的建议

    ALIWARE

    考虑到各个组织的成熟度不一样,我们提供的这些建议不一定适用于所有的 IT 组织,但大家可以根据自身情况参考一下。

    我们一般为第三方实施全链路压测,线上生产压测,会经历五个阶段:

    首先是和第三方一起梳理业务的阶段,我们会做以下几件事情:

    1.根据过往系统使用情况评估业务系统的性能指标、容量指标;

    2.对现有信息系统做系统架构的梳理,确定整个被染色流量的路径途径;

    3.对压测时长,包括间隔等做沟通,确认相关的压测场景设计;

    4.生产数据脱敏,如果有一部分涉及到生产数据可能会做生产数据的脱敏等相关工作。

    这部分做完是做第二部分,对某些应用进行改造。比如说做流量打标工作,通过监控的流量确定业务系统,可能在业务系统里会做相关监控的接入,相关的第三方组件会进行 Mock,整个压测场景的创建会和第三方沟通好。包括流量表建设和预案等等接入。

    三是整个压测的过程,整个生产状态下的全链路压测,会对整个系统进行性能优化及容量评估。

    四是将线上全链路压测常态化,这里面会有一些事情,比如说限流、降级、混沌工程验收,包括生产发布的事情。

    五是对于整个活动做复盘,看应急预案是否生效,还有哪些地方需要优化,这是生产环节全链路压测的生命周期。

    我们现在做一些更深入的东西,整个开发过程中,目前大家都使用 DevOps,可能单接口的性能测试在过程中就已经用到了,目前我们给企业打造的都包含了接口级别的单机性能测试,使用单机测试工具,在发布过程中开始验收单接口的性能问题,保证这个接口发到线上不会有代码级别错误,最终会省掉集成压测包括测试环境中压测的步骤,直接走到线上压测这个过程。单接口阶段我们会支持相应主流的框架压测,目前我们也在不断做测试环境集群的压测支持,还是希望直接用户跳过这个步骤开始在线上直接做流量隔离的压测。

    739e275eb3d6bc8d7614bbb94a88e06a.png

    上图是我们认为一个完整的业务连续性平台需要的功能。

    1.压测流量发起的控制台,流量发起端,这块实际上是管理整个压测流量和场景设计;

    2.流量隔离控制台,这部分希望能够做到统一切流,当出现问题的时候可以一下把压测流量切掉,统一路由;

    3.压测过程中有整个流量监控包括系统监控;压测过程中对于整个应用的性能监控平台,包括链路监控、JVM 监控、组件监控等等;

    4.真正的混沌工程,包括流控规则、隔离规则、降级规则等等平台,这里会维护相应的规则。

    最终我们希望这个平台能够达到的目的是:随时随地以低成本实现全链路压测;对于运维平台可以进行周期性的故障演练,并把这种能力给运维团队,让他们随时随地发起变更;为整个上线活动包括大促做一些兜底,可以避免突发活动击穿。因为长期固化生产压测会为我们带来容量和水位的极限,在演练过程中进行预案的实施,突发过程中会有更好的手段去避免,去做防护。

    以阿里为例,现在基本上可以做到以按月为主,因为大家知道淘宝每个月都有活动,每年有三个大活动:6.18、双11、双12。我们目前可以做小的演练,以周为实施单位做 双11、双12 或者 6.18 的大促,而且我们可以很清晰的组织 BU 内或者跨 BU 的压测活动,并能够很明确扩容方案。

    客户案例

    ALIWARE

    下面是我们给第三方的实施案例。

    • 案例一

    “四通一达”的案例接入,我们对他们的系统进行了应用的分解,最开始确认的压缩场景大概有 4 个,后来通过流量渲染、流量染色、流量跟踪发现整个染色大概有 23 个,通过线上建立影子表的方式,建完影子表之后通过小规模的流量染色,顺着把整个影子库、影子表的方案接入生产环境,并且在生产压测过程中没有造成任何影响,并且通过我们压测的 23 个场景,在去年的 双11 里没有出现任何问题,包括爆仓或者是超单的现象出现。

    8c0bfe70b21868c998558dc2b09552a5.png

    他们前年做这个事的时候,大概有 50 多个人花费了四个月时间,他们维护了一套单独环境,这个环境还是有一定的差别,上线之后还是出现了订单积压的现象,通过我们做全链路压测了之后,现在基本上一个月时间用了 5 个核心骨干做了全链路压测,基本上已经具备了随时上线应用,自己复制,做流量应用、流量染色,测试的周期也是以天为单位,一个比较小的迭代上线基本上一天到两天就可以完成整个线上的性能回归。对于大的流量,双11、双12 大促活动基本上一周时间就可以完成整个主链路的性能回归,并且完全可以评估出目前生产环境的容量,包括扩容、生产环境变更等等这样的功能。

    • 案例二

    某美妆行业客户,所有的系统基本上是由第三方开发的,没有做过性能评估,基本什么都不知道,最关键的问题在于更换的几次第三方导致整个应用比较复杂,出现的问题是下线一个功能导致整个系统崩溃不能用。我们评估了一下,每一单成交之后硬件成本大概是 0.18 元,正好我在 2012 年就在淘宝做压测,他们这个指标大概是 2014 年淘宝花费的 9-10 倍,最关键的问题在于他们还有很多未知的风险,比如说他们上线了一个新应用,想做一个推广,结果直接出了故障,导致秒杀系统崩溃了,基本上那个推广活动没有起到任何效果。

    我们大概用一个多月的时间帮他们做线上环境,给他们梳理了 22 个核心链路,22 个系统,大概 600 多台服务器,我们花费的时间,第一个生产链路建设的时间比较长,大概花了半个月左右的时间,后续是他们自己实施的,总共 22 条链路花了 55 天时间,把整个操作系统线上的容量全部厘清,在整个过程中我们没有对生产环节的数据做污染,包括整个日志做了日志的隔离。在整个过程中我们本着共建的态度,帮助客户建立了日常线上压测的回归机制。

    b1dd82d19b1913f3f488c002f4203aba.png

    从短期收益来看,可能我们对应用的服务器数量做了一些调整,把有些服务器从收益比较低的链路调整到收益比较高的链路上,最终把他们整个资源的消耗率降到了 20% 左右,并且我们做了全链路压测之后,给他们做了一个基线,他们每次以这个基线为基础做性能的迭代。

    目前他们已经完全掌握了整个生产环境压测的流程,每次上线基本上都可以按照自己的规划来做。他们今年的目标是要把整个服务器的资源降低至少 50% 左右,现在也正在为此而努力。

    来自:阿里巴巴中间件

    想知道更多?扫描下面的二维码关注我后台回复"技术",加入技术群
    后台回复“k8s”,可领取k8s资料
    
    
    
    【精彩推荐】
    展开全文
  • 电商全链路压测平台方案设计.docx
  • 大促之前全链路压测原理篇大促之前全链路压测原理篇全链路压测的意义链路压测方案刨析线下压测预生产环境压测引流压测全链路压测四种压测方案对比全链路压测概述什么是全链路压测解决什么问题精确的容量规划进行全...

    大促之前全链路压测原理篇

    全链路压测的意义

    在这里插入图片描述
    上图是 2012 年淘宝核心业务应用关系的拓扑图,还不包含其他的非核心业务应用,所谓的核心业务就是和交易相关的,和钱相关的业务。这张图大家可能看不清楚,看不清楚才是正常的,因为当时的阿里应用数量之多、应用间关系之混乱靠人工确实已经无法理清楚了。
    在真实的业务场景中,每个系统的压力都比较大,而系统之间是有相互依赖关系的,单机压测没有考虑到依赖环节压力都比较大的情况,会引入一个不确定的误差。这就好比,我们要生产一个仪表,每一个零件都经过了严密的测试,最终把零件组装成一个仪表,仪表的工作状态会是什么样的并不清楚。

    链路压测方案刨析

    线下压测

    顾名思义就是在测试环境进行压测,且是针对一些重点项目这种测试手段,因为测试环境硬件资源以及压测数据与线上差别太大并且服务间依赖关系错综复杂,测试环境很难模拟且不够稳定,压测出来的数据指标参考价值不大,难以用测试环境得出的结果推导生产真实容量。

    预生产环境压测

    在这里插入图片描述
    这个一般是将生产环境的硬件以及软件同步复制到预生产环境一份,然后对服务内部的外部调用接口进行拦截,然后进行压测这样可以评估出来生产环境的真实容量以及达到压测的目的,但是成本非常高,需要将生产环境的硬件完全的复制一份,并且维护成本非常高,部署的时候需要同步的在预生产环境进行部署,以及压测代码的更改。

    引流压测

    随着业务量的不断增长,考虑到线下测试结果的准确性,开始尝试生产压测,这种压测手段,我们称之为引流压测。事实上没有真正的模拟放大压力进行测试,而是一种通过缩小在线服务集群数的方式来放大单机处理量。比如一个业务系统的集群有100个节点,将其中90个节点模拟下线或转发流量到剩余的10个节点上实施压测。

    在这里插入图片描述
    引流压测的弊端在于,DB承受压力不变,上下游系统的压力不变。压测结果仅能代表单个应用的性能,但往往无法识别链路和架构级的隐患,而且在引流过程中倘若出现异常或突如其来的业务高峰,很容易造成生产故障。

    全链路压测

    随着微服务架构的流行,服务按照不同的维度进行拆分,一次请求往往需要涉及到多个服务。互联网应用构建在不同的软件模块集上,这些软件模块,有可能是由不同的团队开发、可能使用不同的编程语言来实现、有可能布在了几千台服务器,横跨多个不同的数据中心。因此,就需要一些可以帮助理解系统行为、用于分析性能问题的工具,以便发生故障的时候,能够快速定位和解决问题,但是他的缺点也很明显就是需要的技术难度很高,需要克服流量染色,数据隔离,日志隔离,风险熔断等技术难题,因为在生产环境压测,所以风险也是非常高的。
    所以,在复杂的微服务架构系统中,几乎每一个前端请求都会形成一个复杂的分布式服务调用链路。一个请求完整调用链可能如下图所示:
    在这里插入图片描述

    四种压测方案对比

    在这里插入图片描述

    全链路压测概述

    什么是全链路压测

    基于实际的生产业务场景、生产环境,模拟海量的用户请求和数据对整个业务链(通常是核心业务链)进行压力测试,并持续调优的过程。

    解决什么问题

    解决在业务场景越发复杂化、海量数据冲击下系统整个业务链的可用性、服务能力的瓶颈,以及容量规划等问题。

    精确的容量规划

    为什么需要容量规划
    什么时候增减机器、保障系统稳定性、节约成本

    容量规划的目的在于让每一个业务系统能够清晰地知道:什么时候该加机器、什么时候应该减机器?双11等大促场景需要准备多少机器,既能保障系统稳定性、又能节约成本容量规划四步走

    1. 业务流量预估阶段:通过历史数据分析未来某一个时间点业务的访问量会有多大
    2. 系统容量评估阶段:初步计算每一个系统需要分配多少机器
    3. 容量的精调阶段:通过全链路压测来模拟大促时刻的用户行为,在验证站点能力的同时对整个站点的容量水位进行精细调整
    4. 流量控制阶段:对系统配置限流阈值等系统保护措施,防止实际的业务流量超过预估业务流量的情况下,系统无法提供正常服务流量控制阶段:对系统配置限流阈值等系统保护措施,防止实际的业务流量超过预估业务流量的情况下,系统无法提供正常服务

    进行全链路的性能监控

    全链路性能监控 从整体维度到局部维度展示各项指标,将跨应用的所有调用链性能信息集中展现,可方便度量整体和局部性能,并且方便找到故障产生的源头,生产上可极大缩短故障排除时间。

    • 保证系统稳定性:可能提前预估系统存在的各种问题,提前模拟高并发场景,有备无患。
    • 请求链路追踪,故障快速定位:可以通过调用链结合业务日志快速定位错误信息。
    • 精准的容量评估:能够定位到最需要扩容的服务,帮助公司用最低的成本满足业务的性能要求
    • 真实的性能验证:能够在生成环境以最真实的环境来验证系统的真实性能。
    • 数据分析,优化链路:可以得到用户的行为路径,汇总分析应用在很多业务场景。
      在这里插入图片描述

    如何展开全链路压测

    业务模型梳理

    • 首先应该将核心业务和非核心业务进行拆分,确认流量高峰针对的是哪些业务场景和模块,针对性 的进行扩容准备。
    • 梳理出对外的接口:使用MOCK(模拟)方式做挡板。
    • 千万不要污染正常数据:认真梳理数据处理的每一个环节,确保 mock 数据的处理结果不会写入到 正常库里面

    数据模型构建

    • 数据的真实性和可用性:可以从生产环境完全移植一份当量的数据包,作为压测的基础数据,然后基于基础数据,通过分析历史数据增长趋势,预估当前可能的数据量
    • 数据隔离:千万千万不要污染正常数据:认真梳理数据处理的每一个环节,可以考虑通过压测数据 隔离处理,落入影子库,mock对象等手段,来防止数据污染

    压测工具选型
    使用分布式压测的手段来进行用户请求模拟,目前有很多的开源工具可以提供分布式压测的方式,比如JMeter、nGrinder、Locust等。

    业务模块介绍

    现在我们对整体的业务进行介绍以及演示
    在这里插入图片描述

    全链路整体架构

    上面介绍了为什么需要全链路压测,下面来看下全链路压测的整体架构。

    整体架构如下主要是对压测客户端的压测数据染色,全链路中间件识别出染色数据,并将正常数据和压测数据区分开,进行数据隔离,这里主要涉及到mysql数据库,RabbitMQ,Redis,还需要处理因为hystrix线程池不能通过ThreadLocal传递染色表示的问题。
    在这里插入图片描述

    需要应对的问题

    业务问题

    如何开展全链路压测?在说这个问题前,我们先考虑下,全链路压测有哪些问题比较难解决。

    1. 涉及的系统太多,牵扯的开发人员太多
      在压测过程中,做一个全链路的压测一般会涉及到大量的系统,在整个压测过程中,光各个产品的人员协调就是一个比较大的工程,牵扯到太多的产品经理和开发人员,如果公司对全链路压测早期没有足够的重视,那么这个压测工作是非常难开展的。
    2. 模拟的测试数据和访问流量不真实
      在压测过程中经常会遇到压测后得到的数据不准确的问题,这就使得压测出的数据参考性不强,为什么会产生这样的问题?主要就是因为压测的环境可能和生产环境存在误差、参数存在不一样的地方、测试数据存在不一样的地方这些因素综合起来导致测试结果的不可信。
    3. 压测生产数据未隔离,影响生产环境
      在全链路压测过程中,压测数据可能会影响到生产环境的真实数据,举个例子,电商系统在生产环境进行全链路压测的时候可能会有很多压测模拟用户去下单,如果不做处理,直接下单的话会导致系统一下子会产生很多废订单,从而影响到库存和生产订单数据,影响到日常的正常运营。

    技术问题

    探针的性能消耗

    APM组件服务的影响应该做到足够小。

    服务调用埋点本身会带来性能损耗,这就需要调用跟踪的低损耗,实际中还会通过配置采样率的方式,选择一部分请求去分析请求路径。在一些高度优化过的服务,即使一点点损耗也会很容易察觉到,而且有可能迫使在线服务的部署团队不得不将跟踪系统关停。
    代码的侵入性
    即也作为业务组件,应当尽可能少入侵或者无入侵其他业务系统,对于使用方透明,减少开发人员的负担。
    对于应用的程序员来说,是不需要知道有跟踪系统这回事的。如果一个跟踪系统想生效,就必须需要依赖应用的开发者主动配合,那么这个跟踪系统也太脆弱了,往往由于跟踪系统在应用中植入代码的bug或疏忽导致应用出问题,这样才是无法满足对跟踪系统“无所不在的部署”这个需求。
    可扩展性
    一个优秀的调用跟踪系统必须支持分布式部署,具备良好的可扩展性。能够支持的组件越多当然越好。或者提供便捷的插件开发API,对于一些没有监控到的组件,应用开发者可以自行扩展。
    数据的分析
    数据的分析要快 ,分析的维度尽可能多。跟踪系统能提供足够快的信息反馈,就可以对生产环境下的异常状况做出快速反应。分析的全面,能够避免二次开发。

    全链路压测核心技术

    上面从总体架构层面分析了全链路压测的核心,下面就分析下全链路压测用到的核心技术点

    全链路流量染色
    做到微服务和中间件的染色标志的穿透
    通过压测平台对输出的压力请求打上标识,在订单系统中提取压测标识,确保完整的程序上下文都持有该标识,并且能够穿透微服务以及各种中间件,比如 MQ,hystrix,Fegin等。
    全链路服务监控
    需要能够实时监控服务的运行状况以及分析服务的调用链,我们采用skywalking进行服务监控和压测分析
    在这里插入图片描述
    全链路日志隔离
    做到日志隔离,防止污染生产日志
    当订单系统向磁盘或外设输出日志时,若流量是被标记的压测流量,则将日志隔离输出,避免影响生产日志。
    全链路风险熔断
    流量控制,防止流量超载,导致集群不可用
    当订单系统访问会员系统时,通过RPC协议延续压测标识到会员系统,两个系统之间服务通讯将会有白黑名单开关来控制流量流入许可。该方案设计可以一定程度上避免下游系统出现瓶颈或不支持压测所带来的风险,这里可以采用Sentinel来实现风险熔断。

    全链路数据隔离

    对各种存储服务以及中间件做到数据隔离,防止数据污染

    数据库隔离

    当会员系统访问数据库时,在持久化层同样会根据压测标识进行路由访问压测数据表。数据隔离的手段有多种,比如影子库、影子表,或者影子数据,三种方案的仿真度会有一定的差异,他们的对比如下。
    在这里插入图片描述

    消息队列隔离

    当我们生产的消息扔到MQ之后,接着让消费者进行消费,这个没有问题,压测的数据不能够直接扔到MQ中的,因为它会被正常的消费者消费到的,要做好数据隔离,方案有队列隔离,消息隔离,他们对比如下。
    在这里插入图片描述

    Redis 隔离

    通过 key 值来区分,压测流量的 key 值加统一后缀,通过改造RedisTemplate来实现key的路由。

    框架实现

    流量染色方案

    上面分析了从整体分析了全链路压测用的的核心技术,下面就来实现第一个流量染色。
    流量识别
    要想压测的流量和数据不影响线上真实的生产数据,就需要线上的集群能识别出压测的流量,只要能识别出压测请求的流量,那么流量触发的读写操作就很好统一去做隔离了。
    全链路压测发起的都是Http的请求,只需请求头上添加统一的压测请求头。
    通过在请求协议中添加压测请求的标识,在不同服务的相互调用时,一路透传下去,这样每一个服务都能识别出压测的请求流量,这样做的好处是与业务完全的解耦,只需要应用框架进行感知,对业务方代码无侵入。
    在这里插入图片描述

    MVC接收数据

    客户端传递过来的数据可以通过获取Header的方式获取到,并将其设置进当前的ThreadLocal,交给后面的方法使用。
    MVC拦截器实现

    /**
     * 链路跟踪Request设置值
     */
    public class MvcWormholeWebInterceptor implements WebRequestInterceptor {
    
    
        @Override
        public void preHandle(WebRequest webRequest) {
            //失效上下文,解决Tomcat线程复用问题
            WormholeContextHolder.invalidContext();
            String wormholeValue = webRequest.getHeader(WormholeContextHolder.WORMHOLE_REQUEST_MARK);
            if (StringUtils.isNotEmpty(wormholeValue)) {
                WormholeContextHolder.setContext(new WormholeContext(wormholeValue));
            }
        }
    
        @Override
        public void postHandle(WebRequest webRequest, ModelMap modelMap) throws Exception {
    
        }
    
        @Override
        public void afterCompletion(WebRequest webRequest, Exception e) throws Exception {
    
        }
    }
    

    Tomcat线程复用问题
    tomcat默认使用线程池来管理线程,一个请求过来,如果线程池里面有空闲的线程,那么会在线程池里面取一个线程来处理该请求,一旦该线程当前在处理请求,其他请求就不会被分配到该线程上,直到该请求处理完成。请求处理完成后,会将该线程重新加入线程池,因为是通过线程池复用线程,就会如果线程内部的ThreadLocal没有清除就会出现问题,需要新的请求进来的时候,清除ThreadLocal。

    Fegin传递染色标识

    我们项目的微服务是使用Fegin来实现远程调用的,跨微服务传递染色标识是通过MVC拦截器获取到请求Header的染色标识,并放进ThreadLocal中,然后交给Fegin拦截器在发送请求之前从ThreadLocal中获取到染色标识,并放进Fegin构建请求的Header中,实现微服务之间的火炬传递
    在这里插入图片描述

    public class WormholeFeignRequestInterceptor implements RequestInterceptor {
    
        @Override
        public void apply(RequestTemplate requestTemplate) {
            WormholeContext wormholeContext = WormholeContextHolder.getContext();
            if (null != wormholeContext) {
                requestTemplate.header(WormholeContextHolder.WORMHOLE_REQUEST_MARK, wormholeContext.toString());
            }
        }
    }
    

    Hystrix传递染色标识

    Hystrix隔离技术
    Hystrix 实现资源隔离,主要有两种技术:
    信号量
    信号量的资源隔离只是起到一个开关的作用,比如,服务 A 的信号量大小为 10,那么就是说它同时只允许有 10 个 tomcat 线程来访问服务 A,其它的请求都会被拒绝,从而达到资源隔离和限流保护的作用在这里插入图片描述
    线程池
    线程池隔离技术,是用 Hystrix 自己的线程去执行调用;而信号量隔离技术,是直接让 tomcat 线程去调用依赖服务。信号量隔离,只是一道关卡,信号量有多少,就允许多少个 tomcat 线程通过它,然后去执行。在这里插入图片描述
    Hystrix穿透
    如果使用线程池模式,那么存在一个ThreadLocal变量跨线程传递的问题,即在主线程的
    ThreadLocal变量,无法在线程池中使用,不过Hystrix内部提供了解决方案。
    在这里插入图片描述
    封装Callable任务

    public final class DelegatingWormholeContextCallable<V> implements Callable<V> {
        private final Callable<V> delegate;
        // 用户信息上下文(根据项目实际情况定义ThreadLocal上下文)
        private WormholeContext orginWormholeContext;
    
        public DelegatingWormholeContextCallable(Callable<V> delegate,
                                                 WormholeContext wormholeContext) {
            this.delegate = delegate;
            this.orginWormholeContext = wormholeContext;
        }
    
        public V call() throws Exception {
            //防止线程复用销毁ThreadLocal的数据
            WormholeContextHolder.invalidContext();
            // 将当前的用户上下文设置进Hystrix线程的TreadLocal中
            WormholeContextHolder.setContext(orginWormholeContext);
            try {
                return delegate.call();
            } finally {
                // 执行完毕,记得清理ThreadLocal资源
                WormholeContextHolder.invalidContext();
            }
        }
    
        public static <V> Callable<V> create(Callable<V> delegate,
                                             WormholeContext wormholeContext) {
            return new DelegatingWormholeContextCallable<V>(delegate, wormholeContext);
        }
    }
    

    实现Hystrix的并发策略类
    因为Hystrix默认的并发策略不支持ThreadLocal传递,我们可以自定义并发策略类继承
    HystrixConcurrencyStrategy

    public class ThreadLocalAwareStrategy extends HystrixConcurrencyStrategy {
    
        // 最简单的方式就是引入现有的并发策略,进行功能扩展
        private final HystrixConcurrencyStrategy existingConcurrencyStrategy;
    
        public ThreadLocalAwareStrategy(
                HystrixConcurrencyStrategy existingConcurrencyStrategy) {
            this.existingConcurrencyStrategy = existingConcurrencyStrategy;
        }
    
        @Override
        public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
            return existingConcurrencyStrategy != null
                    ? existingConcurrencyStrategy.getBlockingQueue(maxQueueSize)
                    : super.getBlockingQueue(maxQueueSize);
        }
    
        @Override
        public <T> HystrixRequestVariable<T> getRequestVariable(
                HystrixRequestVariableLifecycle<T> rv) {
            return existingConcurrencyStrategy != null
                    ? existingConcurrencyStrategy.getRequestVariable(rv)
                    : super.getRequestVariable(rv);
        }
    
        @Override
        public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
                                                HystrixProperty<Integer> corePoolSize,
                                                HystrixProperty<Integer> maximumPoolSize,
                                                HystrixProperty<Integer> keepAliveTime, TimeUnit unit,
                                                BlockingQueue<Runnable> workQueue) {
            return existingConcurrencyStrategy != null
                    ? existingConcurrencyStrategy.getThreadPool(threadPoolKey, corePoolSize,
                    maximumPoolSize, keepAliveTime, unit, workQueue)
                    : super.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize,
                    keepAliveTime, unit, workQueue);
        }
    
        /**
         * 包装Callable 用我们自定义的DelegatingWormholeContextCallable 替换 JDK的 Callable
         *
         * @param callable
         * @param <T>
         * @return
         */
        @Override
        public <T> Callable<T> wrapCallable(Callable<T> callable) {
            return existingConcurrencyStrategy != null
                    ? existingConcurrencyStrategy
                    .wrapCallable(new DelegatingWormholeContextCallable<T>(callable, WormholeContextHolder.getContext()))
                    : super.wrapCallable(new DelegatingWormholeContextCallable<T>(callable, WormholeContextHolder.getContext()));
        }
    
    

    Hystrix注入新并发策略并进行刷新

    public class HystrixThreadLocalConfiguration {
    
        @Autowired(required = false)
        private HystrixConcurrencyStrategy existingConcurrencyStrategy;
    
        @PostConstruct
        public void init() {
            // Keeps references of existing Hystrix plugins.
            HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance()
                    .getEventNotifier();
            HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance()
                    .getMetricsPublisher();
            HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance()
                    .getPropertiesStrategy();
            HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance()
                    .getCommandExecutionHook();
    
            HystrixPlugins.reset();
    
            HystrixPlugins.getInstance().registerConcurrencyStrategy(new ThreadLocalAwareStrategy(existingConcurrencyStrategy));
            HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
            HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
            HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
            HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
        }
    }
    

    数据隔离方案

    JDBC数据源隔离
    在这里插入图片描述
    数据隔离需要对DB,Redis,RabbitMQ进行数据隔离
    通过实现Spring动态数据源 AbstractRoutingDataSource ,通过 ThreadLocal 识别出来压测数据,如果是压测数据就路由到影子库,如果是正常流量则路由到主库,通过流量识别的改造,各个服务都已经能够识别出压测的请求流量了。
    代码实现
    数据源路由Key持有对象
    根据路由Key选择将操作路由给那个数据源

    /**
     * 动态数据源上下文
     */
    public class DynamicDataSourceContextHolder {
        public static final String PRIMARY_DB = "primary";
        public static final String SHADOW_DB = "shadow";
    
        private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
            /**
             * 将 master 数据源的 key作为默认数据源的 key
             */
            @Override
            protected String initialValue() {
                return PRIMARY_DB;
            }
        };
    
    
        /**
         * 数据源的 key集合,用于切换时判断数据源是否存在
         */
        public static List<Object> dataSourceKeys = new ArrayList<>();
    
        /**
         * 切换数据源
         *
         * @param key
         */
        public static void setDataSourceKey(String key) {
            contextHolder.set(key);
        }
    
        /**
         * 获取数据源
         *
         * @return
         */
        public static String getDataSourceKey() {
            return contextHolder.get();
        }
    
        /**
         * 重置数据源
         */
        public static void clearDataSourceKey() {
            contextHolder.remove();
        }
    
        /**
         * 判断是否包含数据源
         *
         * @param key 数据源key
         * @return
         */
        public static boolean containDataSourceKey(String key) {
            return dataSourceKeys.contains(key);
        }
    
        /**
         * 添加数据源keys
         *
         * @param keys
         * @return
         */
        public static boolean addDataSourceKeys(Collection<? extends Object> keys) {
            return dataSourceKeys.addAll(keys);
        }
    
    

    动态数据源实现类
    根据路由Key实现数据源的切换

    **
     * 动态数据源实现类
     */
    public class WormholeDynamicDataSource extends AbstractRoutingDataSource {
    
        /**
         * 如果不希望数据源在启动配置时就加载好,可以定制这个方法,从任何你希望的地方读取并返回数据源
         * 比如从数据库、文件、外部接口等读取数据源信息,并最终返回一个DataSource实现类对象即可
         */
        @Override
        protected DataSource determineTargetDataSource() {
            //获取当前的上下文
            WormholeContext wormholeContext = WormholeContextHolder.getContext();
            //如果不为空使用影子库
            if (null != wormholeContext) {
                DynamicDataSourceContextHolder.setDataSourceKey(DynamicDataSourceContextHolder.SHADOW_DB);
            } else {
                //为空则使用主数据源
                DynamicDataSourceContextHolder.setDataSourceKey(DynamicDataSourceContextHolder.PRIMARY_DB);
            }
            return super.determineTargetDataSource();
        }
    
        /**
         * 如果希望所有数据源在启动配置时就加载好,这里通过设置数据源Key值来切换数据,定制这个方法
         */
        @Override
        protected Object determineCurrentLookupKey() {
            //获取当前的上下文
            WormholeContext wormholeContext = WormholeContextHolder.getContext();
            //如果不为空使用影子库
            if (null != wormholeContext) {
                DynamicDataSourceContextHolder.setDataSourceKey(DynamicDataSourceContextHolder.SHADOW_DB);
            } else {
                //为空则使用主数据源
                DynamicDataSourceContextHolder.setDataSourceKey(DynamicDataSourceContextHolder.PRIMARY_DB);
            }
            return DynamicDataSourceContextHolder.getDataSourceKey();
        }
    
        /**
         * 设置默认数据源
         *
         * @param defaultDataSource
         */
        public void setDefaultDataSource(Object defaultDataSource) {
            super.setDefaultTargetDataSource(defaultDataSource);
        }
    
        /**
         * 设置数据源
         *
         * @param dataSources
         */
        public void setDataSources(Map<Object, Object> dataSources) {
            super.setTargetDataSources(dataSources);
            // 将数据源的 key 放到数据源上下文的 key 集合中,用于切换时判断数据源是否有效
            DynamicDataSourceContextHolder.addDataSourceKeys(dataSources.keySet());
        }
    }
    

    Redis 数据源隔离
    同时通过 ThreadLocal 识别出来压测数据,自定义Redis的主键的序列化方式,如果是压测数据则在主键后面加上后缀,这样就可以通过不同主键将Redis数据进行隔离。
    实现key序列化

    public class KeyStringRedisSerializer extends StringRedisSerializer {
    
        @Resource
        private WormholeIsolationConfiguration isolationConfiguration;
    
        public byte[] serialize(@Nullable String redisKey) {
            WormholeContext wormholeContext = WormholeContextHolder.getContext();
            if (null != wormholeContext) {
                redisKey = isolationConfiguration.generateIsolationKey(redisKey);
            }
            return super.serialize(redisKey);
        }
    }
    
    

    配置序列化器

    /**
     * Redis 配置类
     */
    @Configuration
    @ConditionalOnClass({RedisTemplate.class, RedisOperations.class, RedisConnectionFactory.class})
    public class WormholeRedisAutoConfiguration {
    
        @Bean
        public KeyStringRedisSerializer keyStringRedisSerializer() {
            return new KeyStringRedisSerializer();
        }
    
        @Bean("redisTemplate")
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
            RedisTemplate template = new RedisTemplate();
            //使用fastjson序列化
            FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
            // value值的序列化采用fastJsonRedisSerializer
            template.setValueSerializer(fastJsonRedisSerializer);
            template.setHashValueSerializer(fastJsonRedisSerializer);
            // key的序列化采用StringRedisSerializer
            template.setKeySerializer(keyStringRedisSerializer());
            template.setHashKeySerializer(keyStringRedisSerializer());
            template.setConnectionFactory(factory);
            return template;
        }
    
        @Bean
        public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) throws UnknownHostException {
            StringRedisTemplate template = new StringRedisTemplate();
            template.setKeySerializer(keyStringRedisSerializer());
            template.setHashKeySerializer(keyStringRedisSerializer());
            template.setConnectionFactory(factory);
            return template;
        }
    }
    

    RabbitMQ 数据隔离

    在这里插入图片描述
    自动创建影子队列
    因为SpringAMQP中的关键方法是私有的,无法通过继承的方式进行实现对以配置好的队列进行扩展,所以需要自定义该类,来实现对自动创建影子队列,并和交换器进行绑定
    在这里插入图片描述
    代码实现
    改造 RabbitListenerAnnotationBeanPostProcessor 类来实现创建MQ影子队列以及将影子Key绑定到影子队列。

    public class WormholeRabbitListenerAnnotationBeanPostProcessor extends RabbitListenerAnnotationBeanPostProcessor {
    
        @Resource
        private WormholeIsolationConfiguration wormholeIsolationConfiguration;
    
        /**
         * routingKey 前置处理器
         *
         * @param queueName
         * @param routingKey
         * @return
         */
        @Override
        public String preProcessingRoutingKey(String queueName, String routingKey) {
            //如果是影子队列就将routingKey转换为 影子routingKey
            if (wormholeIsolationConfiguration.checkIsolation(queueName) && !wormholeIsolationConfiguration.checkIsolation(routingKey)) {
                return wormholeIsolationConfiguration.generateIsolationKey(routingKey);
            }
            return routingKey;
        }
    
        /**
         * 处理队列问题,如果来了一个队列就生成一个shadow的队列
         *
         * @param queues
         * @return
         */
        @Override
        public List<String> handelQueues(List<String> queues) {
            List<String> isolationQueues = new ArrayList<>();
            if (null != queues && !queues.isEmpty()) {
                for (String queue : queues) {
                    //添加shadow队列
                    isolationQueues.add(wormholeIsolationConfiguration.generateIsolationKey(queue));
                }
                queues.addAll(isolationQueues);
            }
            return queues;
        }
    }
    

    传递染色标识
    因为MQ是异步通讯,为了传递染色标识,会在发送MQ的时候将染色标识传递过来,MQ接收到之后放进当前线程的 ThreadLocal 里面,这个需要扩展Spring的SimpleRabbitListenerContainerFactory 来实现
    在这里插入图片描述
    代码实现

    public class WormholeSimpleRabbitListenerContainerFactory extends SimpleRabbitListenerContainerFactory {
    
    
        @Override
        protected SimpleMessageListenerContainer createContainerInstance() {
            SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer();
            simpleMessageListenerContainer.setAfterReceivePostProcessors(message -> {
                //防止线程复用 销毁ThreadLocal
                WormholeContextHolder.invalidContext();
                //获取消息属性标识
                String wormholeRequestContext = message.getMessageProperties().getHeader(WormholeContextHolder.WORMHOLE_REQUEST_MARK);
                if (StringUtils.isNotEmpty(wormholeRequestContext)) {
                    WormholeContextHolder.setContext(wormholeRequestContext);
                }
                return message;
            });
            return simpleMessageListenerContainer;
        }
    }
    
    

    发送MQ消息处理
    同上,需要传递染色标识,就通过继承 RabbitTemplate 重写 convertAndSend 方法来实现传递染色标识。

    public class ShadowRabbitTemplate extends RabbitTemplate {
        public ShadowRabbitTemplate(ConnectionFactory connectionFactory) {
            super(connectionFactory);
        }
    
        @Autowired
        private WormholeIsolationConfiguration isolationConfiguration;
    
        @Override
        public void send(final String exchange, final String routingKey,
                         final Message message, @Nullable final CorrelationData correlationData)
                throws AmqpException {
            WormholeContext wormholeContext = WormholeContextHolder.getContext();
            if (null == wormholeContext) {
                super.send(exchange, routingKey, message, correlationData);
            } else {
                message.getMessageProperties().setHeader(WormholeContextHolder.WORMHOLE_REQUEST_MARK, wormholeContext.toString());
                //生成Rabbit 隔离Key
                String wormholeRoutingKey = isolationConfiguration.generateIsolationKey(routingKey);
                //调用父类进行发送
                super.send(exchange, wormholeRoutingKey, message, correlationData);
            }
        }
    }
    

    接口隔离方法

    Mock 第三方接口
    对于第三方数据接口需要进行隔离,比如短信接口,正常的数据需要发送短信,对于压测数据则不能直接调用接口发送短信,并且需要能够识别出来压测数据,并进行MOCK接口调用
    在这里插入图片描述
    核心类实现

    @Aspect
    public class WormholeMockSection {
    
        /**
         * 切点 拦截@WormholeMock的注解
         */
        @Pointcut("@annotation(com.heima.wormhole.component.mock.annotation.WormholeMock)")
        public void pointCut() {
        }
    
        /**
         * 环绕通知
         *
         * @param point
         * @return
         * @throws Throwable
         */
        @Around("pointCut()")
        public Object section(ProceedingJoinPoint point) throws Throwable {
            WormholeContext wormholeContext = WormholeContextHolder.getContext();
            Object[] parameter = point.getArgs();
            //如果没有wormholeContext 就执行正常方法
            if (null == wormholeContext) {
                return point.proceed(parameter);
            }
            //如果存在就执行MOCK方法
            WormholeMock wormholeMock = WormholeMockUtils.getMethodAnnotation(point, WormholeMock.class);
            if (null != wormholeMock) {
                //获取到 Mock 回调类
                WormholeMockCallback wormholeMockCallback = WormholeMockUtils.getWormholeMockCallback(wormholeMock);
                if (null != wormholeMockCallback) {
                    return wormholeMockCallback.handelMockData(parameter);
                }
            }
            return null;
        }
    
    }
    
    

    使用方式
    在具体方法上面加上注解就可以使用了

    @Service
    public class SmsNotifyServiceImpl implements SmsNotifyService {
        private Logger logger = LoggerFactory.getLogger(SmsNotifyServiceImpl.class);
    
        @Override
        @WormholeMock(maxDelayTime = 10, minDelayTime = 2)
        public Boolean send(NotifyVO notifyVO) {
            logger.info("开始发送短信通知.....");
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
            }
            return true;
        }
    
    }
    

    零侵入方案

    如果开发的中间件需要各个微服务大量改造,对开发人员来说就是一个灾难,所以这里采用零侵入的springboot starter 来解决
    自动装配
    使用微服务得 @Conditional 来完成配置得自动装配,这里用MVC的配置来演示自动装配,其他的都是类似
    这样可以最大限度的优化代码并提高可扩展性。

    @Configuration
    @Import({WormholeRedisAutoConfiguration.class, WormholeRabbitAutoConfiguration.class, WormholeMVCAutoConfiguration.class, WormholeHystrixAutoConfiguration.class, WormholeFeginAutoConfiguration.class, WormholeDataSourceAutoConfiguration.class, WormholeMybatisConfiguration.class, WormholeMockAutoConfiguration.class})
    public class WormholeAutoConfiguration {
    
        @Bean
        @ConditionalOnMissingBean
        public WormholePropertiesConfiguration wormholePropertiesConfiguration() {
            return new WormholePropertiesConfiguration();
        }
    }
    
    Conditional 简介

    @Conditional表示仅当所有指定条件都匹配时,组件才有资格注册 。 该@Conditional注释可以在以下任一方式使用:

    • 作为任何@Bean方法的方法级注释
    • 作为任何类的直接或间接注释的类型级别注释 @Component,包括@Configuration类
    • 作为元注释,目的是组成自定义构造型注释

    Conditional派生注解
    @Conditional派生了很多注解,下面给个表格列举一下派生注解的用法
    在这里插入图片描述
    SpringBoot starter
    和自动装配一样,Spring Boot Starter的目的也是简化配置,而Spring Boot Starter解决的是依赖管理配置复杂的问题,有了它,当我需要构建一个Web应用程序时,不必再遍历所有的依赖包,一个一个地添加到项目的依赖管理中,而是只需要一个配置 spring-boot-starter-web 。
    使用规范
    在 Spring Boot starter 开发规范中,项目中会有一个空的名为 xxx-spring-boot-starter 的项目,这个项目主要靠 pom.xml 将所有需要的依赖引入进来。同时项目还会有一个 xxx-spring-boot-autoconfigure 项目,这个项目主要写带 @Configuration 注解的配置类,在这个类或者类中带@Bean 的方法上。

    项目使用

    在 xxx-spring-boot-starter的项目下的resources文件夹下面新建一个META-INF文件,并在下面创建spring.factories文件,将我们的自动配置类配置进去

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 
    com.heima.wormhole.autoconfiguration.WormholeAutoConfiguration
    

    在这里插入图片描述

    展开全文
  • 在阿里淘宝 双11 的过程中,长期以来都是在生产环节做全链路压测的,通过实践我们发现在生产环境中做压测,实际上会和一个 IT 组织的结构、成熟度、流程等紧密相关,所以我们把全链路压测从简单...
  • IT行业技术实践案例参考学校
  • 国内知名的系统高可用专家数列科技宣布开源旗下核心产品能力,对外开放生产全链路压测平台产品的源代码,并正式命名为Takin。 目前顺丰科技、希音、中通快递、中国移动、永辉超市、爱库存、浙江大学等50+行业头部...
  • 可以在无业务代码侵入的情况下,嵌入到各个应用程序节点,实现生产环境的全链路性能测试,适用于复杂的微服务架构系统。
  • 不知道大家发现没,阿里、京东、字节、美团、饿了么、滴滴、陌陌等大厂的技术文章里,最近频繁提到全链路压测在企业内部的落地。本想抱着拜读一二的心理去看,结果一旦涉及到具体的落地细节,他们却都跟约好了一样...
  • 最近几年全链路压测无疑成为了一个热门话题,在各个技术峰会上都可以看到它的身影。一些大型的互联网公司,比如阿里巴巴、京东、滴滴等,都已将全链路压测应用到了生产环境。 笔者曾有幸深度参与到阿里巴巴全链路...
  • 分布式系统如何做好全链路压测

    千次阅读 2022-02-28 15:52:45
    随着互联网的发展,各行业均在大力开展数字化转型,这意味着传统线下服务均在整体线上化,各行业信息系统都在像互联网行业...首先要做的,就是要对系统最大能力做到“心中有数”,即开展全链路压测,即覆盖系统技术
  • 什么是全链路压测 基于实际的生产业务场景、系统环境(生产环境),模拟海量的用户请求和数据对整个业务链(通常是核心业务链)进行压力测试,并持续调优的过程。 为什么要做全链路压测 原因在于真实的业务场景下...
  • 腾讯-财付通支付全链路压测-张银红.pdf
  • 全链路压测方案梳理

    千次阅读 2019-06-10 15:13:43
    全链路压测的概念挺火的,想做成却没有机会(毕竟不是互联网巨头类的公司),所以在这里也不想纸上谈兵,可能过段时间它就会被更新更高大上的概念给替换了,但是我们可以收集一下相关资料(目前可以开展全链路压测的...
  • 淘系双十一全链路压测流程概览

    多人点赞 热门讨论 2022-03-09 10:13:30
    为了给大家带来丝滑的购物体验,势必要提前进行链路的压测,模拟当天的场景,为之后的改进明确方向,本文就简单带领大家揭开双十一全链路压测的神秘面纱~ 压测的目的主要有以下几点: 摸底业务模块的性能,QPS、...
  • 资深架构师张克房在GOPS2017全球运维大会上做了主题为《京东全链路压测军演系统(ForceBot)》的分享,就ForceBot的部署架构,压测流量识别,如何有效的推动系统改造,ForceBot的未来挑战进行了深入的分析。
  • 本文想通过梳理全链路压测系统从设计到落地的整个实践过程,来详细介绍全链路压测系统具体是如何设计,以及如何落地的。希望能从技术落地实践的角度,给同行业的同学一些参考和启发。 2. 解决方案 2.1 业内实践 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,001
精华内容 4,000
关键字:

全链路压测

友情链接: snlc061.zip