导读:前端
网易考拉(如下简称考拉)是网易旗下以跨境业务为主的综合型电商,自2015年1月9日上线公测后,业务保持了高速增加,这背后离不开其技术团队的支撑。微服务化是电商IT架构演化的必然趋势,网易考拉的服务架构演进也经历了从单体应用走向微服务化的整个过程,如下整理自网易考拉陶杨在近期Apache Dubbo Meetup上的分享,经过该文,您将了解到:算法
考拉在2015年初上线的时候,线上只有七个工程,商品详情页、购物车下单页等都耦合在中间这个online的工程里面。数据库
在上线之初的时候,这种架构仍是比较有优点的,由于当时考拉的开发人员也不是不少,把全部的功能都耦合在一个进程里面,利于集中开发、测试和上线,是一种比较高效和节省成本的方式。json
可是随着业务的不断发展,包括需求的逐步增多,开发团队的不断扩容,这时候,单体架构的一些劣势就逐渐的暴露出来了,例如开发效率低:功能之间的相互耦合,不一样需求的不一样分支也常常会修改同一块代码,致使合代码的过程很是痛苦,并且常常会出问题。后端
再例如上线成本高:几乎全部的发布需求都会涉及到这些应用的上线,同时不断增加的业务需求,也会使得咱们的代码愈来愈臃肿,形成维护困难、可用性差,功能之间相互耦合,都耦合在一个进程里面,致使一旦某一个业务需求涉及的代码或者资源出现问题,那么就会影响其余的业务。好比说咱们曾经在online工程里面,由于优惠券兑换热点的问题,影响了核心的下单服务。性能优化
这个架构在考拉运行的4到5个月的时间里,从开发到测试再到上线,你们都特别痛苦。因此咱们就开始进行了服务化拆分的工做。网络
这个是考拉如今的分布式服务架构。伴随着服务化的拆分,咱们的组织架构也进行了不少调整,出现了商品中心、用户中心和订单中心等等。拆分实际上是由业务驱动的,经过业务来进行一些横向拆分或者纵向拆分,同时,拆分也会面对一个拆分粒度的问题,好比怎么才算一个服务,或者说服务拆的过细,是否是会致使咱们管理成本太高,又或者说是否会带来架构上的新问题。架构
考拉的拆分由粗到细是一个逐步演进的过程。随着服务化的拆分,使得服务架构愈来愈复杂,随之而来产生了各类各样的公共技术,好比说服务治理、平台配置中心、分布式事务和分布式定时任务等等。并发
微服务框架在服务化中起到了很重要的做用,是服务化改造的基石,通过严格的技术选型流程后,咱们选用了Dubbo来做为考拉服务改造的一个重要支柱。Dubbo能够解决服务化过程当中服务的定义、服务的注册与发现、服务的调用和路由等问题,此外,Dubbo也具备一些服务治理的功能和服务监控的功能。下面我将介绍考拉基于Dubbo作的一些服务化实践。app
首先来讲一下 熔断。
在进行服务化拆分以后,应用中原有的本地调用就会变成远程调用,这样就引入了更多的复杂性。好比说服务A依赖于服务B,这个过程当中可能会出现网络抖动、网络异常,或者说服务B变得不可用或者很差用时,也会影响到A的服务性能,甚至可能会使得服务A占满整个线程池,致使这个应用上其它的服务也受影响,从而引起更严重的雪崩效应。
所以,服务之间有这样一种依赖关系以后,须要意识到服务的依赖实际上是不稳定的。此时,须要经过采起一些服务治理的措施,例如熔断、降级、限流、隔离和超时等,来保障应用不被外部的异常拖垮。Dubbo提供了降级的特性,好比能够经过mock参数来配置一些服务的失败降级或者强制降级,可是Dubbo缺乏自动熔断的特性,因此咱们在Dubbo上引入了Hystrix。
消费者在进行服务调用的时候会通过熔断器,当服务提供者出现异常的时候,好比暂时性的不可用,熔断器就会打开,对消费端进行调用短路,此时,消费端就不会再发起远程调用,而是直接走向降级逻辑。与此同时,消费端会持续的探测服务的可用性,一旦服务恢复,熔断器就会关闭,从新恢复调用。在Dubbo的服务治理平台上,能够对Hystrix上运行的各类动态参数进行动态的配置,包括是否容许自动熔断,是否要强制熔断,熔断的失败率和时间窗口等等。
下面再说一下 限流。
当用户的请求量,调用超过系统可承受的并发时系统QPS会下降、出现不可用甚至存在宕机的风险。这就须要一个机制来保护咱们的系统,当预期并发超过系统可承受的范围时,进行快速失败、直接返回,以保护系统。
Dubbo提供了一些基础的限流特性,例如能够经过信号量的配置来限制咱们消费者的调用并发,或者限制提供者的执行并发。可是这些是远远不够的,考拉自研了限流框架NFC,并基于Dubbo filter 的形式,实现了对Dubbo的支持,同时也支持对URL等其余资源的限流。经过配置中心动态获取流控规则,对于资源的请求,好比Dubbo调用会通过流控客户端,进行处理并判断是否触发限流,一旦请求超出定义的阈值,就会快速失败。
同时,这些限流的结果会上报到监控平台。上图中的页面就是考拉流控平台的一个监控页面,咱们在页面上能够对每个资源(URL、Dubbo接口)进行一个阈值的配置,并对限流进行准实时监控,包括流控比率、限流次数和当前的QPS等。限流框架除了实现基本的并发限流以外,也基于令牌桶和漏桶算法实现了QPS限流,并基于Redis实现了集群级别的限流。这些措施保障系统在高流量的状况下不会被打垮。
在服务化的过程当中,系统变得愈来愈复杂,服务数量变得愈来愈多,此时须要引入更多维度的监控功能,帮助快速的去定位并解决系统中的各种问题。监控主要分为这四个方面,日志、Metrics、Trace和HealthCheck。
在应用程序、操做系统运行的时候,都会产生各类各样的日志,经过日志平台对这些日志进行采集、分析和展现,并支持查询和操做。Metrics反映的是系统运行的基本状态,包括瞬时值或者聚合值,例如系统的CPU使用率、磁盘使用率,以及服务调用过程当中的平均延时等。Trace是对服务调用链的一个监控,例如调用过程当中的耗时分析、瓶颈分析、依赖分析和异常分析等。Healthcheck能够探测应用是否准备就绪,是否健康,或者是否还存活。
接下来,围绕Dubbo来介绍一下考拉在监控方面的改造实践。
第一个是服务监控。
Dubbo提供了服务监控功能,支持按期上报服务监控数据,经过代码加强的方式,采集Dubbo调用数据,存储到时序数据库里面,将Dubbo的调用监控功能接入到考拉本身的监控平台。
上图中的页面是对Dubbo提供者的服务监控,包括对服务接口、源集群等不一样维度的监控,除了全局的调用监控,还包括不一样维度的监控,例如监控项里的调用次数。有时候咱们更关心慢请求的状况,因此会将响应时间分为多个范围,好比说从0到10毫秒,或是从10到50毫秒等,这样就能够看到在各个范围内请求的数量,从而更好地了解服务质量。
同时,也能够经过各类报警规则,对报警进行定义,当服务调用出现异常时,经过邮件、短信和电话的形式通知相关人员。监控平台也会对异常堆栈进行采集,例如说此次服务调用的异常的缘由,是超时仍是线程满了的,能够在监控平台上直接看到。同时生成一些监控报表,帮助咱们更好地了解服务的性能,推动开发去改进。
第二个是Trace。
咱们参考了Dapper,自研了Trace平台,并经过代码加强的方式,实现了对Dubbo调用链路的采集。相关调用链参数如TarceID,SpanID 等是经过Dubbo的隐式传参来传递的。Trace能够了解在服务调用链路中的一个耗时分析和瓶颈分析等。Trace平台上能够展现一次服务调用,经历了哪些节点,最耗时的那个节点是在哪里,从而能够有针对性的去进行性能优化。Trace还能够进行依赖分析,这些依赖是否合理,可否经过一些业务手段或者其它手段去减小一些不合理的依赖。
Trace对异常链路进行监控报警,及时的探测到系统异常并帮助咱们快速的定位问题,同时和日志平台作了打通,经过TraceId能够很快的获取到关联的异常日志。
第三个是健康检查。
健康检查也是监控中很重要的一个方面,以更优雅的方式上线应用实例。咱们和自动部署平台结合,实现应用的健康检查。服务启动的时候能够经过Readiness接口判断应用依赖的各类资源,包括数据库、消息队列等等是否已经准备就绪。只有健康检查成功的时候才会触发出注册操做。同时Agent也会在程序运行的过程当中定时的检查服务的运行状态。
同时,也经过这些接口实现更优雅的停机,仅依赖shutdownhook,在某些状况下不必定靠谱,好比会有shutdownhook执行前后顺序的问题。应用发布的时候,首先调用offline接口,将注册服务所有从注册中心反注册,这时再也不有新的流量进来,等到一段时间后,再执行停机发布操做,能够实现更加优雅的停机。
下面来介绍一下考拉在服务测试方面的实践。服务测试分为接口测试、单链路压测、全链路压测和异常测试四个维度。
接口测试
经过接口测试,能够来验证对外提供的Dubbo服务是否正确,所以咱们也有接口测试平台,帮助QA更好的进行接口测试,包括对接口的编辑(入参、出参),用例的编辑和测试场景的执行等,
单链路压测
单链路的压测,主要面对单个功能的压测,好比要上线一个重要功能或者比较重要的接口以前,必须经过性能测试的指标才能够上线。
全链路压测
考拉做为电商平台,在大促前都会作全链路压测,用以探测系统的性能瓶颈,和对系统容量的预估。例如,探测系统的各种服务的容量是否够,须要扩容多少,以及限流的阈值要定多少合适,均可以经过全链路压测来给出一些合理的值。
异常测试
对服务调用链路中的一些节点进行系统异常和服务异常的注入,也能够获取他们的强度依赖关系。好比一个很是重要的接口,能够从Trace获取的调用链路,而后对调用链的依赖的各个服务节点进行异常注入。经过接口的表现,系统就会判断这个接口的强度依赖关系,以改善这些不合理的强依赖关系。
随着考拉服务化的发展,咱们自研了API网关,API网关能够做为外部流量的统一接口,提供了包括路由转发、流控和日志监控等一些公共的功能。
考拉的API网关是经过泛化调用的方式来调用后台Dubbo的服务的。Dubbo原生的泛化调用的性能比普通Api调用要差一些,因此咱们也对泛化调用性能作了一些优化,也就是去掉了泛化调用在返回结果时的一次对象转换。最终压测的结果泛化的性能甚至比正常的调用性能还要好些。
考拉在业务发展的过程当中产生了很多多语言的需求,例如,咱们的前端团队但愿能够用Node应用调用Dubbo服务。对比了易用性,选用了开源的jsonrpc 方案,而后在后端的Dubbo服务上暴露了双协议,包括Dubbo协议和json rpc协议。
但在实施的过程当中,也遇到了一些小问题,好比说,对于Dubbo消费者来讲,无论是什么样的协议提供者,都是invoker。经过一个负载均衡策略,选取一个invoker进行调用,这个时候就会致使原来的Java客户端选用一个jsonrpc协议的提供者。这样若是他们的API版本不一致,就有可能致使序列化异常,出现调用失败的状况。因此,咱们对Dubbo的一些调用逻辑作了改造,例如在Java客户端的消费者进行调用的时候,除非显示的配置,不然默认只用Dubbo协议去调用。另外,考拉也为社区的jsonrpc扩展了隐式传参的功能,由于能够用Dubbo隐式传参的功能来传递一些全链路参数。
注册中心瓶颈多是大部分电商企业都会遇到的问题,考拉也不例外。咱们如今线上的Dubbo服务实例大概有4000多个,可是在ZooKeeper中注册的节点有一百多万个,包括服务注册的URL和消费者订阅的URL。
Dubbo应用发布时的惊群效应、重复通知和消费者拉取带来的瞬时流量一下就把ZooKeeper集群的网卡打满,ZooKeeper还有另一个问题,他的强一致性模型致使CPU的利用率不高。
就算扩容,也解决不了ZooKeeper写性能的问题,ZooKeeper写是不可扩展的,而且应用发布时有大量的请求排队,从而使得接口性能急剧降低,表现出来的现象就是应用启动十分缓慢。
所以,在今年年初的时候就咱们决定把ZooKeeper注册中心给替换掉,对比了现有的一些开源的注册中心,包括Consul、Eruka、etcd等,以为他们并不适合Dubbo这种单进程多服务的注册模型,同时容量可否应对将来考拉的发展,也是一个问号。因而,咱们决定自研注册中心,目前正在注册中心的迁移过程中,采用的是双注册中心的迁移方案,即服务会同时注册ZooKeeper注册中心,还有新的注册中心,这样对原有的架构不会产生太大的影响。
考拉新的注册中心改造方案和如今社区的差很少,好比说也作了一个注册数据的拆分,往注册中心注册的数据只包含IP, Port 等关键数据,其它的数据都写到了Redis里面,注册中心实现使用了去中心化的一个架构,包括使用最终一致性来换取咱们接口性能的一个提高。后面若是接入Dubbo,会考虑使用Nacos而不是ZooKeeper做为注册中心。
考拉最近也在进行第二机房的建设,经过两个机房独立部署相同的一套系统,以实现同城双活。针对双机房的场景,Dubbo会作必定的改造,例如同机房优先调用,相似于即将发布的Dubbo2.7.0中的路由特性。在Dubbo在服务注册的时候,读取系统环境变量的环境标或者机房标,再将这些机房标注册到注册中心,而后消费端会作一个优先级路由,优先进行同机房的服务调用。
容器化也是咱们在规划的一个方向。随着服务化进程的演进,服务数也变得愈来愈多,经过容器化、DevOps能够提高测试、部署和运维效率。
Service Mesh在今年很是火,经过Service Mesh将服务框架的的能力好比注册发,路由和负载均衡,服务治理等下沉到Sidecar,使用独立进程的方式来运行。对于业务工程的一个解耦,帮助咱们实现一个异构系统,对多语言支持,也能够解决中间件升级推进困难以及各类依赖的冲突,业务方也能够更好的关注于业务开发,这也会是将来探索的一个方向。