在巴斯、克莱门茨和凯兹曼合著的《软件架构实践》一书中,性能被定义为软件满足其时间需求的能力。它涉及面对特定类型需求时对系统资源的管理,以实现可接受的时间行为。第2章介绍了软件性能体现在运行过程中的时间效率、空间效率与用户需求之间的匹配程度。那么如何进行性能优化呢?
由《软件架构实践》一书可知,性能优化策略可以分解为与控制资源需求相关的因素和与管理资源供应相关的因素。本章除了会对软件性能优化策略进行深度解读外,还将介绍针对性能优化的“点”“线”“面”级别的思考,并介绍性能优化的PDCA循环以及性能优化与CAP理论的关系。
《软件架构实践》一书介绍了多种性能优化策略,本节选择常见的策略进行介绍和扩展,并结合案例和场景介绍不同策略的选择方法。
可以把性能优化过程简化为3个部分——事件请求到达、业务处理和性能优化处理、在时间限制内返回响应,如图3-1所示。
图3-1 性能优化过程
事件请求可以看作一系列资源需求申请。例如,在大促销期间,10000个用户都想“秒杀”一款手机。然而,在具体的时间点,比如20:00,手机只有3台,不可能让10000个用户都抢到,也没有必要让10000个用户“抢手机”的请求都经历完整的IT系统处理过程。通过某种算法放入200个用户后,可以直接返回失败页面给其他用户。这样做相当于控制了资源需求,减少了资源的争用,提升了性能。我们将这类策略称为控制资源需求相关因素的策略。在这种场景下,通常还有另一类策略,即管理资源相关因素的策略。例如,有10000个请求要访问活动红包。之前,活动红包的领取服务由2台服务器提供,现在请求激增并超出了服务器的处理上限。如果此时将服务器扩展到20台,那么每台服务器只需处理500个活动红包访问服务,这也能提升性能。
控制资源需求相关因素的策略包括按优先级处理需求、减少开销、提高资源效率和限制时间响应(限流)。
1.按优先级处理需求
按照优先级处理需求在许多场景中都有实际应用,其中一个典型场景就是消息通知系统。根据业务的特点,可以将消息分为必须送达的消息和允许丢失的消息;根据到达的时间容忍度,可以将消息分为高优先级消息、中优先级消息和低优先级消息。高优先级消息可以利用更多的机器或者更高配置的硬件来处理,以确保其会被优先处理。
2.减少开销
按照领域拆分服务已经成为行业共识。然而,在具体实施过程中,将一些较小的服务分组为较大的服务可以提高性能。这是因为使用大量较小的服务进行可修改性设计,可能会遇到性能问题。服务实例之间的调用会带来开销,并且可能在总体执行时间方面产生较大的开销。
下面看一个典型案例。
在支付平台上,用户付款时需要进行安全风险识别,包括付款前和付款后。几年前,在进行大型营销活动时,支付并发量很高(每秒数十万次),而安全服务会拖慢响应时间并降低并发能力。那时采取的措施是在极端情况下降低支付前的安全服务。后来,安全服务的容量提升了,业务团队做出了不降级的决策。然而,安全平台内部有3个串接的微服务,微服务之间仍存在网络调用的开销。后来采取的策略是将平台内部的3个微服务组合在一起,以本地调用的方式运行,从而减少开销并提升性能。
3.提高资源效率
提高关键服务中的代码效率可以减少延时。在具体实践中,可以通过代码评审和编写测试用例来实现这一目标。常见的技巧包括在数据库更新操作中进行批量更新,以及在内存计算中采用效率更高的算法等。
4.限制时间响应(限流)
当业务事件过快到达系统而无法被处理时,除了让事件排队等待处理外,还有一种应对策略,即直接丢弃它们,以保护正常容量范围内的服务提供。断路器开关就是一种常见的限流策略实践。
管理资源相关因素的策略,出发点不是控制资源请求,而是在处理能力上做文章。管理资源相关因素的策略分为3类,即增加资源、增加并发和使用缓存,如图3-2所示。
图3-2 管理资源相关因素的策略
1.增加资源
增加资源,顾名思义,就是利用更强大的基础设施,例如更强大的服务器或更快的网络,来提升性能。例如,在节点之间进行数据复制时,网络带宽和物理距离是制约因素,这时就可以在这两方面进行设备扩展。
2.增加并发
并发是指系统在同一时间段内同时执行多个计算程序的能力。衡量并发能力的一个重要指标是单位时间内的吞吐量,而响应时间是衡量软件性能的重要指标。关于响应时间和吞吐量的关系,我们在2.3.3节介绍过,这里不再重复。
要增加并发能力并满足响应时间基线(如300ms),常见的做法有以下几种。
1) 采用并行处理的方式。 并发之所以会出现,是因为存在共享资源。例如,在单线程情况下处理过期积分,任务只能一个一个地串行处理。而使用多线程可以同时处理多个任务。由于积分业务任务之间没有关联,特别适合多线程并行处理,因此增加并发量,通过多线程的方式处理是首选。当然,线程数并非越多越好,具体数量取决于CPU核心数、机器整体配置以及任务处理本身的资源消耗,可以通过性能压测来确定适当的线程数。
2) 将同步调用转换为异步调用。 典型的异步通信指将RPC调用转换为向消息服务器发送消息。RPC同步调用需要等待下游系统处理返回结果,而采取消息传递的方式则可以让消息成功到达消息服务器,不用等待返回过程。
3) 分拆资源。 资源的分拆包括代码粒度的分拆锁、分离锁及分拆业务资源。
· 分拆锁: 涉及多个共享资源,如查询营销账户余额、扣减营销账户余额、增加个人用户账户余额3个操作。粗粒度锁对3个操作整体进行加锁,但为了提高并发能力,可以将这些锁请求分散到更多的锁上,从而有效降低锁竞争程度。这样,等待被阻塞的线程会更少,从而提高可伸缩性。
· 分离锁: 在某些情况下,可以对锁定的资源进行技术上的拆分。例如,Concurrency HashMap使用包含16个锁的数组实现。这样,当资源请求进来时,只有1/16的概率会访问到同一个数组,并在该粒度上进行加锁,从而大大提高整体并发性能。
· 分拆业务资源: 在Facebook的系统中有一种方法,通过使用多个key_index(key:xxx#N)来获取热点key的数据。这种方法本质上是将key分散,对于对一致性要求不高但需要高并发读取的情况非常有效。另一个非常典型的应用场景是分拆红包模板。下面简单介绍这个业务流程。
用户参与平台的抢红包过程包括以下几个步骤:
①后台配置红包模板,设置金额上限(如1000万元)以及其他参数,比如单个用户可以领取的次数等。
②用户在页面上抢红包,系统生成符合模板规则的固定或随机金额。
③扣减红包总余额。
④将相应金额的红包发放给用户,生成用户的红包记录。可以看出,红包模板是一个热点数据,每次抢红包都需要扣减红包总余额。
在上述4个步骤中,瓶颈在第3个步骤。解决这个问题有多种方法,但最直接和简单的方法就是对红包模板进行分拆。例如,对于总金额为1000万元的活动,可以按照100万的模板进行配置,这样并发能力相当于提升了10倍。
3.使用缓存
使用缓存的最大作用是提升性能。有一句话说得很好:“缓存为王”。如何通过缓存来提升性能?第10章会单独介绍,这里不再展开。若想更深入地了解缓存,可以查阅于君泽等编写的《深入分布式缓存:从原理到实践》一书。
当需要进行软件性能优化时,可以从4个方面入手——点、线、面、体。“点”指的是代码中的单条语句或单个函数,可以通过代码级别的优化来提升性能;“线”指的是代码中的执行路径,可以通过算法和数据结构的优化来提升性能;“面”指的是代码中的整个模块或子系统,可以通过系统级别的优化来提升性能;“体”则指的是整个系统,可以通过硬件和架构的优化来提升性能。因此,进行性能优化时,需要根据具体的性能瓶颈选择相应的优化方案,并从点、线、面、体4个方面入手。
我们先来看一个点优化的场景。
在一个电商网站上,有一个商品详情页,该页面包含了多张图片和文字介绍,加载速度较慢,影响用户体验。
分析思路:
1)需要确定具体是哪些图片和文字介绍的加载速度较慢,可以通过浏览器开发者工具的性能分析功能进行查看。
2)需要确定加载速度较慢的原因,比如网络延时、服务器响应速度慢或者图片过大等。
3)需要有针对性地优化加载速度较慢的元素,例如,使用压缩图片、使用CDN加速、使用懒加载等方法来提升页面加载速度。
解决方案:
1)通过Chrome浏览器的开发者工具查看页面加载速度,确定加载速度较慢的元素。
2)使用图片压缩工具对图片进行压缩,减小图片的大小。
3)使用CDN加速服务将静态资源分发到全球多个节点,提升用户访问速度。
4)对于文字介绍,可以使用懒加载的方式,在用户滚动到该部分时再进行加载,减少页面初始加载数据。
看完这个案例后,我们再来看一个持续优化的案例。
在营销活动中发放红包,需要控制预算。如果发生超发情况,那么资金损失将由出资方承担。
原始代码逻辑如下:
1)开始事务。
2)加锁,按输入参数锁定对应的预算记录(判断余额是否足够)。
3)扣减余额。
4)插入用户领红包记录单据。
5)分支1:提交,结束事务。
6)分支2:回滚,结束事务。
优化思路是缩短加锁时间。
1)开始事务。
2)插入用户领红包记录单据并扣减余额(这一条无记录并发)。
3)如果预算余额够用,则扣减指定预算记录余额(减少SQL语句,同时使用了乐观锁)。
4)分支1:提交,结束事务。
5)分支2:回滚,结束事务。
经过上面的改造,对于单记录的预算并发,由50TPS提升到了800TPS,但是我们的目标是达到3000TPS。于是,还需要继续对这个案例进行优化。
从点到线,我们延续上小节中的案例。对于已经完成的优化操作,用一张图来总结,如图3-3所示。
图3-3 已经完成的优化操作
当前的业务场景是“用户领取红包”,要解决的问题是“提升TPS”,解决思路是“寻找处理的瓶颈,持续优化”。
在3.2节中,我们提到了第一个优化点,即缩短数据库访问的加锁时间,以提升TPS。在具体的优化过程中,我们将悲观锁模式优化为乐观锁模式,通过使用“select * from table for update”语句,将性能从50TPS提升到800TPS。然而,我们的目标是达到3000TPS,因此需要持续进行优化。这种持续优化的方法是性能优化的延伸,从优化一个点到优化整体。
我们进一步发现了耗费时间的操作,即数据库服务器端的处理,示意图如图3-4所示。
图3-4 数据库服务器端的处理示意图
执行update语句后,MySQL服务器返回OK,然后客户端发送commit指令,MySQL服务器执行提交操作。当update语句执行完毕后,MySQL服务器根据具体响应立即进行提交或回滚操作,行锁可立即释放,从而解决了上述问题。基于这个想法,DBA开发了commit_on_success&rollback_on_fail的MySQL补丁。
测试结果显示,性能从800TPS提升到了4000TPS。按理说,提升到4000TPS已经满足了要求。如果需要继续优化,那么基于线的性能优化模式还可以继续扩展。一个典型的做法是将预算记录进行分割。并发写入可以理解为数据库单记录的最大并发点,需要进行锁定和排队。如果将一个预算记录分割为100条,那么并发可能性岂不是降低了99%?具体操作类似于普通的分表操作,此处不再展开。
这个案例阐述了如何实现点到线的性能优化。案例中的优化可以分为3个阶段,如图3-5所示。
图3-5 案例中性能优化的3个阶段
从图3-5可以看出,优化事务处理的第一阶段是在SQL语句上下功夫,缩短事务处理的长度。第二阶段继续缩短事务处理的长度,通过应用MySQL补丁,在更新操作后直接进行提交操作,这样可以减少网络访问量并减少一次提交处理操作。第三阶段则从应用侧出发,对热点预算记录进行分拆。这一步的研发成本较高,需要结合业务对平台性能评估的要求做出综合决策。
同样,我们以一个典型案例来说明如何基于“面”进行性能优化。具体的业务背景这里就不介绍了,拆解之后的技术问题就是多索引的查询问题。
随着数据规模的扩大,采用MySQL进行分库分表是常规做法。而要拆分数据就涉及对数据量规模的考量,需要确定分库分表的数量。确定好分库分表的数量以后,需要确定以库/表的某个字段来区分分库分表,该字段需要是一个ID字段,即该字段的值可以唯一标记一条记录。我们假设该字段为indexKey。
该业务系统的逻辑结构如图3-6所示。
图3-6 示例系统的逻辑结构
使用userId作为查询的IndexKey是没有问题的,那么使用电子邮箱(email)地址呢?业界有一种常规做法——数据复制,也就是将email作为indexKey数据存储一份,并且进行数据拆分,示意图如图3-7所示。
图3-7 数据复制流程示意图
如图3-7所示,应用可以采取双写模式来确保以userId、email为主键(indexKey)的记录都能成功保存,或者采用本地补偿的方式来保证准实时的数据一致性。以用户信息为例,如表3-1所示。
表3-1 用户信息表
传统方案涉及的工作内容如图3-8所示。
图3-8 传统方案涉及的工作内容
传统方案的挑战在于,当主键非常多的时候,比如有 N 个,那么数据规模也会扩充到 N 倍。并且每新建一个主键,从逻辑库物理部署到应用层逻辑都需要进行调整。
为了解决这个问题,我们采用索引关系表+补偿的模式。这种方式可以动态配置所需要的索引,并且数据库容量低于传统方案。另外,由于采用了动态配置和相关模块,因此扩展查询索引的实现成本也很低。
举个例子,如果要实现一个以用户为中心的信息系统,传统方案可能涉及10张表(其中,存储用户信息的表为userInfo表),用户信息达到1亿条(半年累计)。假设存储1条用户信息需要1KB的空间,那么1亿条信息将需要约95GB的存储空间。因此,粗略估计,该系统需要存储762GB的数据(其他表记录数可能大于userInfo表,但字段可能较少,这里按8倍userInfo表估算总存储量)。
如果需要进行5个维度的查询,那么传统方案与笔者方案的对比如表3-2所示。
表3-2 两种方案的对比
由图3-9可知,写操作分为3个步骤,这里以保存操作为例(更新操作与此类似)。
步骤1:存储userInfo信息(假设对应db00库,userinfo00表),同一事务中存储db00库copyinfo(副本信息)备用。
步骤2:通过定时任务遍历副本信息列表。
步骤3:根据副本信息生成索引记录,比如emailIndex。
图3-9 写操作的实现思路
由图3-10可知,读操作的步骤如下。
步骤1:根据确定的email可以查询到userId,因为根据email的哈希值可以确定分库分表后的位置。例如,emailIndex中存储了email和userId的关系。
步骤2:根据userId也可以获得正确的用户信息(路由到正确的库表)。
图3-10 读操作的实现思路
进一步优化的思路:使用热点用户的userId作为key(键),将userInfo作为value(值),直接放入缓存中。同时,以热点查询条件(如手机号、email等)形成的phone-userId关系和email-userId关系也可以放入缓存。
让我们来看一个基于整个体系的性能优化案例。背景是一个细分领域的电商平台,最初是单体架构,后来演进为分布式架构。然而,整体交易创建的性能仍然无法满足快速增长的业务需求。在集群环境下,交易创建速度为1000笔/s,目前希望提升到10000笔/s,即提升10倍。相应的服务依赖关系如图3-11所示。
我们对问题进行分析后发现,将交易创建速度从1000笔/s提升到10000笔/s,可以从以下两个方向进行改进:第一个方向是改进交易创建服务本身的数据库;第二个方向是改进交易创建服务所依赖的查询服务。本节以营销服务为例进行说明。
图3-11 服务依赖关系
首先,经过分析发现,交易订单写入包括创建交易订单、交易订单扩展表等。目前与分库分表相关的开源组件比较多,如Apache ShardingSphere,此处不赘述。
其次,经过阅读代码和SQL监测,对慢SQL进行治理。具体的策略如下。
· 分析执行计划,关注索引,确定扫表行数,确认是否有文件排序问题。
· 查询规范约束,杜绝select*格式的查询,尽量直接使用索引查询。
· 进行索引优化。
经过SQL优化后,营销查询服务仍然需要提升QPS(每秒查询率)。由于营销服务是典型的读多写少的服务,因此适合使用缓存。这里进一步考虑使用本地缓存和远程集中式缓存的组合。详细内容参见第10章。
查询数据库和缓存结合的流程如图3-12所示。
不难看出,读缓存的思路是先读本地缓存,再读远程集中缓存,最后读数据库。在这个过程中有一个异步定时任务,用来接收数据库变更事件,主动更新缓存,避免出现数据库与缓存不一致问题。
图3-12 查询数据库和缓存结合的流程
在使用缓存方面有如下几个原则:
· 使用缓存时要降低数据库的读压力,尽可能缓存需求的数据。
· 数据库中的数据变动要主动失效。
· 大促高峰前开启本地缓存。
· 缓存需要预热,这有助于提高缓存命中率。
上述优化改善了营销服务调用的返回时间。由于经过优化后的营销数据库的读并发能力仍无法满足要求,因此笔者采用了读写分离的方法,进一步拆分了读操作。
常见的优化模式包括读写分离和数据拆分。读写分离主要解决并发读写相互影响的问题,特别是高并发写入时,响应时间变慢,这影响了大量用户的读操作。而数据拆分则主要解决仍存在高并发读取的问题,将读数据库拆分成多个数据库和多个表,负载进一步降低,从而提升了性能。笔者采用了读写分离和数据拆分相结合的模式,实现思路如图3-13所示。
图3-13 读写分离和数据拆分相结合模式的实现思路
在电商系统中,下单交易系统需要在强一致的情况下执行结算账单、扣除库存、扣除账户余额(或发起支付)以及发货等一系列操作。如果采用同步方式实现,则需要等待前面所有操作都完成后才能进行下一个操作,这样会导致系统响应时间长、性能低下,特别是在高并发场景下容易出现系统崩溃等问题。
因此,可以采用异步调用的方式来优化业务性能。具体来说,可以将结算账单、扣除库存、扣除账户余额、发货等操作拆分成不同的任务,通过消息队列将任务发送到异步处理系统中进行处理。这样就可以将任务的执行和响应解耦,提高系统的并发能力和响应速度。
采用异步处理也有相应的复杂度成本。下面举一个具体例子。如图3-14所示,创建订单、锁定优惠券、锁定库存本身都具有强事务特性。要实现上述功能,一种方案是采取分布式事务保障,但这样做肯定会损失一部分性能,因此笔者采取了合理编排任务以及异步化废单消息的方式来实现。
图3-14 合理编排任务以及异步化废单消息
3.2~3.5节呈现的是一个逐步扩大范围的性能优化实践,由点及面逐步深入,同时扩展到单系统及链路级复杂系统。那么性能优化有没有具体的可操作流程呢?PDCA(Plan、Do、Check、Action,计划、执行、检查、行动)方法提供了这样的指引。
PDCA方法论是一种结构化的问题解决方法,又称戴明环。它将任务按照顺序分为计划、执行、检查、行动4个环节。这4个环节顺序执行,循环不止。
在PDCA方法论下,首先需要制订具体的计划,包括确定任务、阶段目标、时间节点和责任人。然后实施、落地各项具体活动。接着进行检查,检查实际执行结果,找出正确和错误之处。最后,在行动环节明确下一步需要采取的措施,根据检查结果改进计划。
PDCA方法论的优点在于不断循环下去,不断迭代,推动组织进入良性循环,不断发现新的待改进问题。针对这些问题,首先要进行根因分析,制订具体的实施计划。然后定期检查实施结果和预期目标是否一致。最后对改进结果进行复盘,保留好的方面,改进不好的方面,进入下一阶段的循环。
使用PDCA方法论的关键在于明确目标,而不是简单定性问题。只有不断循环,才能不断提高业务,取得胜利。
对于3.3节中提及的案例,我们可以使用PDCA方法进行描述。
1)计划:针对用户领取红包场景下的性能瓶颈,我们计划寻找瓶颈点,并持续优化以提升TPS。目前是50TPS,目标是3000TPS。
2)执行:经过分析发现,缩短数据库访问的加锁时间,并将悲观锁优化为乐观锁可以提升性能。因此我们进行了相关代码改造。
3)检查:使用工具进行并发测试后发现,性能提升至800TPS。
4)行动:仍未达到预期的3000TPS,因此回到计划阶段,寻找新的瓶颈点,继续优化。
5)计划:持续提升性能。我们发现在MySQL服务器侧返回OK后,需要客户端发出提交指令,然后MySQL服务器才能执行提交。这种方式会导致行锁无法释放,从而降低了性能。
6)执行:为了解决这个问题,我们采取commit_on_success&rollback_on_fail的方式。在更新执行完毕后,MySQL服务器直接根据响应来进行提交或回滚操作,这样行锁就能立即释放,从而提升了性能。
7)检查:再次进行并发测试后,发现性能提升至4000TPS。
8)行动:4000TPS超过了预期目标(3000TPS),本次优化任务完成。
美国的穆拉特·埃尔德等编写的《持续架构实践:敏捷和DevOps时代下的软件架构》一书很好地介绍了性能和成本、可扩展性、易用性、可用性的关系,如图3-15所示。性能除了和上述非功能要素相关外,还和数据一致性等其他非功能性要素相关,本节就对此进行专门解读。
图3-15 性能和成本、可扩展性、易用性、可用性的关系
一般来说,可用性是指系统能够持续正常运行,不间断地为用户提供服务的能力;数据一致性则是指系统中的数据在多个副本中保持一致的能力。针对这些要素,在设计上进行权衡时,可以从以下几个方面考虑。
· 数据一致性和可用性的权衡。在分布式系统中,为了保证数据的一致性,在进行数据更新时需要等待所有副本都更新完成才能返回响应,这会影响系统的可用性。为了提高系统的可用性,可以采用异步复制的方式,即在更新主副本后,异步地更新其他副本,这样可以提高系统的可用性,但会牺牲数据的一致性。
· 数据一致性和性能的权衡。为了保证数据的一致性,可以采用同步复制的方式,即在更新主副本时同步更新其他副本,这样可以保证数据的一致性,但会影响系统的性能。为了提高系统的性能,可以采用异步复制的方式,但会牺牲数据的一致性。
· 可用性和性能的权衡。为了提高系统的可用性,可以采用冗余设计,即在出现故障时自动切换到备份系统,这样可以保证系统的可用性,但会牺牲系统的性能。为了提高系统的性能,可以采用负载均衡的方式将请求分摊到多个服务器上,但这样会增加系统的复杂度。
· 数据一致性、可用性和性能的综合权衡。可以根据具体业务场景和系统需求进行综合取舍。例如,在高并发的电商场景下,为了保证数据的一致性和可用性,可以采用一主多从的架构,主库负责写入操作,从库负责读取操作,通过异步复制来保证数据的一致性。同时,为了提升性能,可以在读写分离的基础上,针对高并发读的场景进行分库分表,提升对应并发写性能。
关于性能和其他非功能要素的权衡,这里举几个具体的例子。
案例1 满足性能要求的同时放松强一致性,保障最终一致性。在支付平台的设计中,会对资金账户进行记账,确保账务一致。然而,当遇到热点账户问题时,更新同一个账户余额可能会引发并发写的性能问题。为解决这个问题,一般采取缓冲记账模式。先记录账务明细事件,不立即更新余额,而是按照时间(如分钟或小时)异步批量处理以更新余额。需要注意的是业务限制,尤其是在入金场景中使用缓冲记账,因为出金账户会扣减余额,有可能导致透支。
案例2 为了满足性能要求,考虑适当地进行数据冗余,虽然这会增加存储成本。以用户领取优惠券为例,优惠券的使用规则存储在券模板上。而用户领取优惠券的记录通常会按照用户ID分库分表来进行存储和查询。在查询用户优惠券是否满足支付条件时,需要对券模板进行查询。可以考虑将相应的规则数据冗余到用户优惠券表中,以消除对券模板数据的依赖。
案例3 这是一个真实案例。在优惠券领取业务中,因为对预算进行了分片管理,所以可以通过一个路由规则将其路由到相应的子预算。然而,在日常的非高并发场景中有一个碎片合并的模块,专门用来转移合并后的预算。但是在秒杀场景下,直接禁用合并模块是更好的选择。因为为了保障用户的业务可用性要求和性能要求(返回的RT),我们宁愿对业务逻辑做一些取舍。没有花完的碎片预算只是没有被最大化地使用,并没有超出预算。所以说,没有最好的设计,只有最合适的设计。设计即权衡。
案例4 通常情况下,有高性能要求的系统往往都具有用户访问多、并发高的特点。并发高意味着服务响应时间应该在100ms之内,页面的响应时间不应超过2s。但也有特殊情况,比如高管查看经营分析报表,该报表的用户可能只有几十人,且并发很低,但对于性能仍有极致的要求,期望页面能“秒开”。常规页面在1s内打开也不难,但对于统计计算型的报表,涉及大量数据载入和指标计算,则并非容易。
案例5 为了性能,可以牺牲可扩展性或增加耦合。某支付服务依赖一个安全服务,而安全服务又依赖一个计算服务。通过性能压测发现,在极致要求下,安全服务调用计算服务的时间比较长。后来决定将这两个服务合并在一起,运行时进行本地调用,从而满足了提升支付服务性能的需求。
总之,在软件架构设计中,需要在性能、可用性和数据一致性之间取得平衡。
本章介绍了软件性能优化方法体系,通过案例描述了不同场景下的性能优化策略以及基于点、线、面、体的优化方法,如寻找瓶颈点、优化数据库访问、数据一致性与可用性的取舍等,还介绍了使用PDCA方法论进行优化、性能与其他非功能要素取舍的方法。最终强调了在软件架构设计中需要平衡性能、可用性和数据一致性,只有这样,才能根据具体需求进行综合取舍以构建优秀的软件架构。