01 概述
02高性能篇
个人理解三高系统的核心在于高性能,系统的性能高,系统的处理速度快,吞吐量自然高,此时系统能够应对高并发的流量;系统的性能高,我们系统的对外承诺的TP99, TP999就比较低,超时等影响可用率的情况自然减少,系统的可用性也会提高,所以三高系统建设的出发点可以从系统的性能如何优化进行建设,高性能篇从系统的读写两个维度谈下系统的性能优化方法论及实践。
2.1 方法论
首先我们要清楚知道影响系统性能的因素有那些,通常有以下三方面的因素:计算(computation),通信(communication),存储(storage)。计算层面:系统本身的计算逻辑复杂,Fullgc;通信层面:依赖的下游耗时比较高;存储层面:大库大表,慢sql,ES集群的数据节点,索引,分片,分片大小设置的不合理;针对这些问题,我们可以从读写两个维度针对性能问题进行优化,下图是我工作中解决性能问题的一些方法。
2.2 几个实践问题的探讨
2.2.1 读优化:缓存和数据库的结合艺术
2.2.1.1 读多写少的系统
针对读多写少的系统,我们一般采用同步更新数据库,后删除缓存;数据库来应对写的流量,缓存来应对读的流量,提高读的性能;此种方案我们是以数据库数据为主,缓存数据为辅,这是前司大部分团队采用的技术方案。
2.2.1.2 写多读少的系统
针对写多读少的系统,我们一般采用同步更新缓存,异步更新数据库,通过缓存来进行抗写的流量,异步化更新数据库,通过缓存和异步化提高系统的性能;此种技术方案以缓存数据为主,数据库数据为辅,这是我了解到的京东物流这边大部分团队的技术方案,例如我们物流平台-统一平台小组的订运关系单据的存储采用的就是这种技术方案。
2.2.2 写优化:秒杀场景下的异步化
针对于这种流量洪峰下的秒杀场景,对于接单接口的性能是很大的考验,所以接单接口不会有很多同步交互的复杂逻辑。我们一般都是先异步将订单接下来,返回给用户成功,通过消息队列来削峰处理订单,缓存存储相关sku的库存,当扣减库存成功后,再短信通知用户支付订单。
03高并发篇
3.1 方法论
3.1.1 X轴:水平扩展
水平扩展就是扩容,是我们采用最多的抗并发的措施:加机器,扩分片,每年618,双11大促时,这是我们的常规操作,现有分组下的机器处理能力有限,我们通过扩容来应对大促的流量,扩容我们分为应用层和存储层,应用层都是无状态的服务,我们可以通过公司部署平台行云快速的扩容增加机器,存储层的扩容相对比较麻烦,新增分片后,还涉及到数据的迁移以及分片规则的调整。
3.1.2 Y轴:纵向扩展
整个软件应用的架构经历单体应用,SOA, 微服务,服务网格的演进。早期的架构风格所有的服务功能都融合在单体应用中,通过单体服务来抗所有的流量,后来随着业务的复杂性和用户的增多以及我们基于对业务的深入理解采用DDD(领域驱动设计)来指导我们按照领域划分服务,进行微服务建设,下图是一个电商的单体应用到按照DDD进行微服务划分的一个演进过程。
3.1.3 Z轴:垂直扩展
当我们在应用层进行水平扩展时,每增加一台机器都会增加数据库的访问链接,数据库的连接数属于宝贵资源,达到上限后,应用层再扩容就会出现连接数耗尽的异常,此时存储层成了整个系统高并发的瓶颈,针对数据库我们一般采用分片(分而治之)的思想:分库分表,通过增加库实例来增加访问的连接数,下图是订单进行分库分表的架构。
集群中数据库的主从库数量是有限制的的,达到最大限制后,一个机房的数据库集群成了系统的瓶颈,解决方案是进行单元化建设,系统的流量和数据闭环在一个单元,这个单元分布在全国甚至全世界不同的地域,而不是集中在某个机房某个地域,北京的系统单元为北京用户提供服务,上海的系统单元为上海用户提供服务,就类似于京东物流的仓库一样,建在离用户最近的地方,北京仓服务北京用户,上海仓服务上海用户。大家可以看到系统的建设和业务的发展底层的思想都是统一的,中国的头部互联网还都是业务驱动,所以技术要服务好业务。总之我们可以看到通过分库分表和单元化这种垂直扩展提高并发的同时也增强了系统的可用性,下图是我们进行单元化建设的过程。
3.2 几个实践问题的探讨
3.2.1 DDD在零售物流平台的实践
3.2.1.1 业务流程
正向流程:从B端商家视角来看,商家选择服务商品比如卓配产品后,进行下单,服务商进行接单,分配快递员,快递员上门取件,对用户邮寄的货品进行称重量方,询价计费,然后商家支付运费,快递员完成揽收,进入履约层面:货品进行运输,配送,直至C端用户签收完成妥投。
逆向流程:从C端用户视角来看,用户需要申请售后退货,待商家审核通过后,选择相应的商品服务进行下单,后续的流程和正向流程类似。
3.2.1.2 应用领域划分
从领域划分角度来看,将领域划分为:商品服务域,订单域,支付结算域,履约域,每个领域包括了提供的功能如下图;至于为啥这样划分,我的思考是这样的,相较于C端零售侧的电商交易,我们是服务于商家的B端物流侧的物流交易;C端零售针对于用户提供的是实物的商品:手机,电脑;B端物流针对于商家提供的是虚拟的商品:一种履约物流服务,将货品从商家交付到用户;所以无论是我们的卓配产品还是电子面单产品,我们为商户提供了这些虚拟商品,商家选择这些虚拟商品后,就可以下单,取消,改单,支付,服务商进行履约;
3.2.2 热key处理
-
本地缓存:在应用层增加本地缓存;先查本地缓存,本地缓存没有查询分布式缓存,分布式缓存没有查询数据库;
-
随机数法:针对某个key,我们可以在这个key后面增加一个随机数,比如增加两位的随机,就可以将该key分散到100个分片上,避免热点分片。
04 高可用篇
保证系统的可用性是系统建设中的重中之重,如果没有可用性,高性能和高并发也无从谈起,高可用的建设通常是通过保护系统和冗余的方法来进行容错保证系统的可用性。本篇主要从三个维度:应用层,存储层,部署层谈下可用性的建设。应用层的内容来自我的另一篇文章:万字长文浅谈系统稳定性建设。
4.1 方法论
4.1.1 应用层
4.1.1.1 限流
4.1.1.2 熔断降级
-
人工降级:人工降级一般采用降级开关来控制,公司内部一般采用配置中心Ducc来做开关降级,开关的修改也是线上操作,这块也需要做好监控;
-
自动降级:自动降级是采用自动化的中间件例如Hystrix,公司的小盾龙等;如果采用自动降级的话;我们必须要对降级的条件非常的明确,比如失败的调用次数等。
4.1.1.3 超时设置
超时时间在设置的时候需要遵循漏斗原则,从上游系统到下游系统设置的超时时间要逐渐减少,如下图所示。为什么要满足漏斗原则,假设不满足漏斗原则,比如服务A调取服务B的超时时间设置成500ms,而服务B调取服务C的超时时间设置成800ms,这个时候回导致服务A调取服务B大量的超时从而导致可用率降低,而此时服务B从自身角度看是可用的。
4.1.1.4 重试
分布式系统中性能的影响主要是通信,无论是在分布式系统中还是垮团队沟通,communication是最昂贵的;比如我们研发都知道需求的交付有一半以上甚至更多的时间花在跨团队的沟通上,真正写代码的时间是很少的;分布式系统中我们查看调用链路,其实我们系统本身计算的耗时是很少的,主要来自于外部系统的网络交互,无论是下游的业务系统,还是中间件:Mysql, redis, es等等;所以在和外部系统的一次请求交互中,我们系统是希望尽最大努力得到想要的结果,但往往事与愿违,由于不可靠网络的原因,我们在和下游系统交互时,都会配置超时重试次数,希望在可接受的SLA范围内一次请求拿到结果,但重试不是无限的重试,我们一般都是配置重试次数的限制,偶尔抖动的重试可以提高我们系统的可用率,如果下游服务故障挂掉,重试反而会增加下游系统的负载,从而增加故障的严重程度。在一次请求调用中,我们要知道对外提供的API,后面是有多少个service在提供服务,如果调用链路比较长,服务之间rpc交互都设置了重试次数,这个时候我们需要警惕重试风暴。如下图service D 出现问题,重试风暴会加重service D的故障严重程度。对于API的重试,我们还要区分该接口是读接口还是写接口,如果是读接口重试一般没什么影响,写接口重试一定要做好接口的幂等性。
4.1.1.5 隔离
4.1.1.5.1 系统建设层面隔离
4.1.1.5.2 环境的隔离
从研发到上线阶段我们会使用不同的环境,比如业界常见的环境分为:开发,测试,预发和线上环境;研发人员在开发环境进行开发和联调,测试人员在测试环境进行测试,运营和产品在预发环境进行UAT,最终交付的产品部署到线上环境提供给用户使用。在研发流程中,我们部署时要遵循从应用层到中间件层再到存储层,都要在一个环境,严禁垮环境的调用,比如测试环境调用线上,预发环境调用线上等。
4.1.1.5.3 数据隔离
随着业务的发展,我们对外提供的服务往往会支撑多业务,多租户,所以这个时候我们会按照业务进行数据隔离;比如我们组产生的物流订单数据业务方就包含京东零售,其他电商平台,ISV等,为了避免彼此的影响我们需要在存储层对数据进行隔离,数据的隔离可以按照不同粒度,第一种是通过租户id字段进行区分,所有的数据存储在一张表中,另外一个是库粒度的区分,不同的租户单独分配对应的数据库。
4.1.1.5.4 核心/非核心流程隔离
我们知道应用是分级的,京东内部针对应用的重要程度会将应用分为0,1,2,3级应用。业务的流程也分为黄金流程和非黄金流程。在业务流程中,针对不同级别的应用交互,需要将核心和非核心的流程进行隔离。例如在交易业务过程中,会涉及到订单系统,支付系统,通知系统,那这个过程中核心系统是订单系统和支付系统,而通知相对来说重要性不是那么高,所以我们会投入更多的资源到订单系统和支付系统,优先保证这两个系统的稳定性,通知系统可以采用异步的方式与其他两个系统解耦隔离,避免对其他另外两个系统的影响。
4.1.1.5.5 读写隔离
应用层面,领域驱动设计(DDD)中最著名的CQRS(Command Query Responsibility Segregation)将写服务和读服务进行隔离。写服务主要处理来自客户端的command写命令,而读服务处理来自客户端的query读请求,这样从应用层面进行读写隔离,不仅可以提高系统的可扩展性,同时也会提高系统的可维护性,应用层面我们都采用微服务架构,应用层都是无状态服务,可以扩容加机器随意扩展,存储层需要持久化,扩展就比较费劲。除了应用层面的CQRS,在存储层面,我们也会进行读写隔离,例如数据库都会采用一主多从的架构,读请求可以路由到从库从而分担主库的压力,提高系统的性能和吞吐量。所以应用层面通过读写隔离主要解决可扩展问题,存储层面主要解决性能和吞吐量的问题。
4.1.1.5.6 线程池隔离
线程是昂贵的资源,为了提高线程的使用效率,复用线程,避免创建和销毁的消耗,我们采用了池化技术,线程池,但是在使用线程的过程中,我们也做好线程池的隔离,避免多个API接口复用同一个线程。
没有回复内容