微服务解决的痛点是单体膨胀之后的效率低下。微服务带给我们的好处有很多,但具体能不能使⽤微服务还要根据具体的项⽬和团队来决定。搞不定微服务技术栈就不要往里面跳。微服务的概念最早是在 2014 年由 Martin Fowler 和James Lewis 共同提出,他们定义了微服务是由单一应用程序构成的小服务,拥有自己的进程与轻量化处理,服务依业务功能设计,以全自动的方式部署,与其他服务使用 HTTP API 通讯。同时,服务会使用最小规模的集中管理 (例如 Docker)技术,服务可以用不同的编程语言与数据库等。但是问题是微服务化后数据库分库查询怎么办、服务拆分后的分布式事务怎么办等等问题。

  oh my goder!原来只需要做一个单体应用就一切ok,现在我们需要解决这么多问题,然后还要单体应用里要写的业务逻辑,这里也一个不能少。这可太真实了。针对上述问题,你必须有可⾏的解决⽅案之后,才能进⼀步进⾏服务化拆分。所以更加真实的是,事实上很多人现在并不需要、或者是并不能上微服务。比如:1.单体应用没有梳理清楚业务复杂度,根本上就有问题,微服务也无力回天。 2.人员太少,微服务只会增加系统复杂度、运维成本。 3.系统设计问题,代码写的乱,dao层逻辑随处写,开发流程问题,都不是靠微服务能解决的。
  
  所以如果你在开发初期,或者人手不够,没有运维,就老老实实用单体应用。事实上也不推荐初创公司上微服务。但是同时上单体应用的同时,做好业务逻辑拆分,整体系统设计,就足以超过那些随便上车然后掉坑的微服务系统了,也为以后的微服务化打下基础。项目初期在快速开发和验证想法的时候就不必采取过于复杂的方式,一般开发人员超过10人需要考虑使用服务化。服务化拆分的几种姿势:⼀是业务维度聚类,业务和数据关系密切的应该放在⼀起。⼆是功能维度聚类,公共功能聚合为⼀个服务。三是⼈员聚类,这是个实际中的考量,如果某⼏个业务就是这⼏个⼈⽐较熟,那么最好放在⼀起,未来开发部署都好办。四是性能聚类,性能要求⾼的并发⼤的和性能要求低的并发⼩的,要分开为不同的服务,这样部署和运⾏都独⽴,好维护。

  如果要从头开始做微服务,把下面的准备工作先做好。没有配套基础设施就上微服务最后一定会崩,可以说随便上微服务是终极无敌焦油坑。

  1、建设好基础设施,RPC、服务治理、日志、监控、持续集成、持续部署、运维自动化是基本的,其它包括服务编排、分布式追踪等。
  2、要逐步演进和迭代,不要过于激进,更不要拆分过细,拆分的粒度,要与团队的架构相互匹配。(康威定律)
  3、微服务与数据库方面,是个很大的难点,可以深入了解下领域驱动设计,做好领域建模,特别是数据库要随着服务一起拆分

  说好的微服务的 “ 轻量级 ” 呢?都这么多基础设施还好意思说自己是 “ 轻量级 ” ,感觉比 ESB 还要复杂啊?确实如此,微服务并不是很多人认为的那样又简单又轻量级。要做好微服务,这些基础设施都是必不可少的,否则微服务就会变成一个焦油坑,让业务和团队在里面不断挣扎且无法自拔。因此也可以说,微服务并没有减少复杂度,而只是将复杂度从 ESB 转移到了基础设施。你可以看到, “ 服务发现 ”“ 服务路由 ” 等其实都是 ESB 的功能,只是在微服务中剥离出来成了独立的基础系统。虽然建设完善的微服务基础设施是一项庞大的工程,但也不用太过灰心,认为自己团队小或者公司规模不大就不能实施微服务了。第一个原因是已经有开源的微服务基础设施全家桶了,例如大名鼎鼎的 Spring Cloud 项目,涵盖了服务发现、服务路由、网关、配置中心等功能;第二个原因是如果微服务的数量并不是很多的话,并不是每个基础设施都是必须的。

  通常情况下,我建议按照下面优先级来搭建基础设施:

  1. 服务发现、服务路由、服务容错:这是最基本的微服务基础设施。
  2. 接口框架、 API 网关:主要是为了提升开发效率,接口框架是提升内部服务的开发效率, API 网关是为了提升与外部服务对接的效率。
  3. 自动化部署、自动化测试、配置中心:主要是为了提升测试和运维效率。
  4. 服务监控、服务跟踪、服务安全:主要是为了进一步提升运维效率。

  以上 3 和 4 两类基础设施,其重要性会随着微服务节点数量增加而越来越重要,但在微服务节点数量较少的时候,可以通过人工的方式支撑,虽然效率不高,但也基本能够顶住。

  

服务化要解决的问题

  首先来看一次微服务流程。
微服务

  首先服务提供者(就是提供服务的一方)按照一定格式的服务描述,向注册中心注册服务,声明自己能够提供哪些服务以及服务的地址是什么,完成服务发布。接下来服务消费者(就是调用服务的一方)请求注册中心,查询所需要调用服务的地址,然后以约定的通信协议向服务提供者发起请求,得到请求结果后再按照约定的协议解析结果。而且在服务的调用过程中,服务的请求耗时、调用量以及成功率等指标都会被记录下来用作监控,调用经过的链路信息会被记录下来,用于故障定位和问题追踪。在这期间,如果调用失败,可以通过重试等服务治理手段来保证成功率。

  然后我们来对应一下单体、SOA、微服务的组件应用。

微服务 SOA 单体
服务描述 wsdl api
服务注册  uddl 引用jar
服务框架 esb spring为代表的一众框架
服务监控 esb 基本没有,挺多JVM调优
服务跟踪 esb 基本没有,崩了一个系统就崩了
服务治理 esb+负载均衡 基本没有,特定容灾备份算

服务描述

  服务如何定义?答:⽤接⼝来描述约定,约定内容包括接⼝名、接⼝参数以及接⼝返回值。常用的服务描述方式包括 RESTful API、XML 配置以及 IDL 文件三种。其中,RESTful API 方式通常用于 HTTP 协议的服务描述,并且常用 Wiki 或者Swagger来进行管理。XML 配置方式多用作 RPC 协议的服务描述,通过 *.xml 配置文件来定义接口名、参数以及返回值类型等。IDL 文件方式通常用作 Thrift 和 gRPC 这类跨语言服务调用框架中,比如 gRPC 就是通过 Protobuf文件来定义服务的接口名、参数以及返回值的数据结构。

现在流行的服务描述:RESTful API、XML 配置以及 IDL 文件。

注册中心

  服务如何发布和订阅?答:通过注册中心,服务提供者将自己提供的服务以及地址登记到注册中心,服务消费者则从注册中心查询所需要调用的服务的地址,然后发起请求。一般来讲,注册中心的工作流程是:1.服务提供者在启动时,根据服务发布文件中配置的发布信息向注册中心注册自己的服务。2.服务消费者在启动时,根据消费者配置文件中配置的服务信息向注册中心订阅自己所需要的服务。3.注册中心返回服务提供者地址列表给服务消费者。4.当服务提供者发生变化,比如有节点新增或者销毁,注册中心将变更通知给服务消费者。

  根据注册中心原理的描述,注册中心必须提供以下最基本的 API,例如:
  服务注册接口:服务提供者通过调用服务注册接口来完成服务注册
  服务反注册接口:服务提供者通过调用服务反注册接口来完成服务注销。
  心跳汇报接口:服务提供者通过调用心跳汇报接口完成节点存活状态上报。
  服务订阅接口:服务消费者通过调用服务订阅接口完成服务订阅,获取可用的服务提供者节点列表。
  服务变更查询接口:服务消费者通过调用服务变更查询接口,获取最新的可用服务节点列表。

  注册中心作为服务提供者和服务消费者之间沟通的桥梁,它的重要性不言而喻。所以注册中心一般都是采用集群部署来保证高可用性,并通过分布式一致性协议来确保集群中不同节点之间的数据保持一致。以开源注册中心 ZooKeeper为例,ZooKeeper 集群中包含多个节点,服务提供者和服务消费者可以同任意一个节点通信,因为它们的数据一定是相同的。注册中心除了要支持最基本的服务注册和服务订阅功能以外,还必须具备对服务提供者节点的健康状态检测功能,这样才能保证注册中心里保存的服务节点都是可用的。还是以 ZooKeeper 为例,它是基于 ZooKeeper 客户端和服务端的长连接和会话超时控制机制,来实现服务健康状态检测的。

  注册中心可以说是实现服务化的关键,因为服务化之后,服务提供者和服务消费者不在同一个进程中运行,实现了解耦,这就需要一个纽带去连接服务提供者和服务消费者,而注册中心就正好承担了这一角色。此外,服务提供者可以任意伸缩即增加节点或者减少节点,通过服务健康状态检测,注册中心可以保持最新的服务节点信息,并将变化通知给订阅服务的服务消费者。注册中心一般采用分布式集群部署,来保证高可用性,并且为了实现异地多活,有的注册中心还采用多 IDC 部署,这就对数据一致性产生了很高的要求,这些都是注册中心在实现时必须要解决的问题。

现在流行的注册中心:zookeeper

服务框架

  整个服务如何跑起来?答:通过特定的服务框架,约定服务间通信用哪种协议、数据传输使用哪种方式、数据压缩采用什么格式等等。

现在流行的服务框架有:Dubbo、Spring Cloud

服务监控

  服务如何监控?

  通常对于⼀个服务,我们最关⼼的是QPS(调⽤量)、AvgTime(平均耗时)以及P999(99.9%的请求性能在多少毫秒以内)这些指标。这时候你就需要⼀种通⽤的监控⽅案,能够覆盖业务埋点、数据收集、数据处理,最后到数据展示的全链路功能。

现在流行的服务监控项目有:skywalking、Dubbo-Monitor、zipkin和pinpoint。

服务追踪

  服务如何定位?答:在单体应⽤拆分为微服务之后,⼀次⽤户调⽤可能依赖多个服务,每个服务⼜部署在不同的节点上,如果⽤户调⽤出现问题,你需要有⼀种解决⽅案能够将⼀次⽤户请求进⾏标记,并在多个依赖的服务系统中继续传递,以便串联所有路径,从⽽进⾏故障定位。具体操作原理如下:1.服务消费者发起调用前,会在本地按照一定的规则生成一个 requestid,发起调用时,将requestid 当作请求参数的一部分,传递给服务提供者。2.服务提供者接收到请求后,记录下这次请求的 requestid,然后处理请求。如果服务提供者继续请求其他服务,会在本地再生成一个自己的 requestid,然后把这两个 requestid 都当作请求参数继续往下传递。3.以此类推,通过这种层层往下传递的方式,一次请求,无论最后依赖多少次服务调用、经过多少服务节点,都可以通过最开始生成的 requestid 串联所有节点,从而达到服务追踪的目的。

  一般一个服务追踪系统可以分为三层。
  数据采集层,负责数据埋点并上报。

  数据处理层,负责数据的存储与计算。实时数据处理,针对实时数据处理,一般采用Storm或者Spark Streaming来对链路数据进行实时聚合加工,存储一般使用OLTP数据仓库,比如HBase,使用traceId作为RowKey,能天然地把一整条调用链聚合在一起,提高查询效率。离线数据处理,针对离线数据处理,一般通过运行MapReduce或者Spark批处理程序来对链路数据进行离线计算,存储一般使用Hive。

  数据展示层,负责数据的图形化展示。实际项目中主要用到两种图形展示,一种是调用链路图,一种是调用拓扑图,可以用Zipkin、Pinpoint实现

  基于Dapper衍生出来的服务追踪系统,比较有名的有Twitter的Zipkin、阿里的鹰眼、美团的MTrace等。

服务治理

  服务如何治理?答:服务监控能够发现问题,服务追踪能够定位问题所在,而解决问题就得靠服务治理了。服务治理就是通过一系列的手段来保证在各种意外情况下,服务调用仍然能够正常进行。比如:单机故障策略、单IDC故障策略、依赖服务不可用策略等等,需要考虑一系列服务治理手段。常用的服务治理手段有:

节点管理

  1. 注册中心主动摘除机制

  这种机制要求服务提供者定时的主动向注册中心汇报心跳,注册中心根据服务提供者节点最近一次汇报心跳的时间与上一次汇报心跳时间做比较,如果超出一定时间,就认为服务提供者出现问题,继而把节点从服务列表中摘除,并把最近的可用服务节点列表推送给服务消费者。

  1. 服务消费者摘除机制

  虽然注册中心主动摘除机制可以解决服务提供者节点异常的问题,但如果是因为注册中心与服务提供者之间的网络出现异常,最坏的情况是注册中心会把服务节点全部摘除,导致服务消费者没有可用的服务节点调用,但其实这时候服务提供者本身是正常的。所以,将存活探测机制用在服务消费者这一端更合理,如果服务消费者调用服务提供者节点失败,就将这个节点从内存中保存的可用服务提供者节点列表中移除。

负载均衡

  一般情况下,服务提供者节点不是唯一的,多是以集群的方式存在,尤其是对于大规模的服务调用来说,服务提供者节点数目可能有上百上千个。由于机器采购批次的不同,不同服务节点本身的配置也可能存在很大差异,新采购的机器CPU和内存配置可能要高一些,同等请求量情况下,性能要好于旧的机器。对于服务消费者而言,在从服务列表中选取可用节点时,如果能让配置较高的新机器多承担一些流量的话,就能充分利用新机器的性能。这就需要对负载均衡算法做一些调整。

  常用的负载均衡算法主要包括以下几种。

  1. 随机算法

  顾名思义就是从可用的服务节点中随机选取一个节点。一般情况下,随机算法是均匀的,也就是说后端服务节点无论配置好坏,最终得到的调用量都差不多。

  1. 轮询算法

  就是按照固定的权重,对可用服务节点进行轮询。如果所有服务节点的权重都是相同的,则每个节点的调用量也是差不多的。但可以给某些硬件配置较好的节点的权重调大些,这样的话就会得到更大的调用量,从而充分发挥其性能优势,提高整体调用的平均性能。

  1. 最少活跃调用算法

  这种算法是在服务消费者这一端的内存里动态维护着同每一个服务节点之间的连接数,当调用某个服务节点时,就给与这个服务节点之间的连接数加1,调用返回后,就给连接数减1。然后每次在选择服务节点时,根据内存里维护的连接数倒序排列,选择连接数最小的节点发起调用,也就是选择了调用量最小的服务节点,性能理论上也是最优的。

  1. 一致性Hash算法

  指相同参数的请求总是发到同一服务节点。当某一个服务节点出现故障时,原本发往该节点的请求,基于虚拟节点机制,平摊到其他节点上,不会引起剧烈变动。

  这几种算法的实现难度也是逐步提升的,所以选择哪种节点选取的负载均衡算法要根据实际场景而定。如果后端服务节点的配置没有差异,同等调用量下性能也没有差异的话,选择随机或者轮询算法比较合适;如果后端服务节点存在比较明显的配置和性能差异,选择最少活跃调用算法比较合适。

路由规则

  1. 静态配置

  就是在服务消费者本地存放服务调用的路由规则,在服务调用期间,路由规则不会发生改变,要想改变就需要修改服务消费者本地配置,上线后才能生效。

  1. 动态配置

  这种方式下,路由规则是存在注册中心的,服务消费者定期去请求注册中心来保持同步,要想改变服务消费者的路由配置,可以通过修改注册中心的配置,服务消费者在下一个同步周期之后,就会请求注册中心来更新配置,从而实现动态更新。

服务容错

  服务调用并不总是一定成功的,前面我讲过,可能因为服务提供者节点自身宕机、进程异常退出或者服务消费者与提供者之间的网络出现故障等原因。对于服务调用失败的情况,需要有手段自动恢复,来保证调用成功。

  常用的手段主要有以下几种。

  FailOver:失败自动切换。就是服务消费者发现调用失败或者超时后,自动从可用的服务节点列表总选择下一个节点重新发起调用,也可以设置重试的次数。这种策略要求服务调用的操作必须是幂等的,也就是说无论调用多少次,只要是同一个调用,返回的结果都是相同的,一般适合服务调用是读请求的场景。

  FailBack:失败通知。就是服务消费者调用失败或者超时后,不再重试,而是根据失败的详细信息,来决定后续的执行策略。比如对于非幂等的调用场景,如果调用失败后,不能简单地重试,而是应该查询服务端的状态,看调用到底是否实际生效,如果已经生效了就不能再重试了;如果没有生效可以再发起一次调用。

  FailCache:失败缓存。就是服务消费者调用失败或者超时后,不立即发起重试,而是隔一段时间后再次尝试发起调用。比如后端服务可能一段时间内都有问题,如果立即发起重试,可能会加剧问题,反而不利于后端服务的恢复。如果隔一段时间待后端节点恢复后,再次发起调用效果会更好。

  FailFast:快速失败。就是服务消费者调用一次失败后,不再重试。实际在业务执行时,一般非核心业务的调用,会采用快速失败策略,调用失败后一般就记录下失败日志就返回了。

  从我对服务容错不同策略的描述中,你可以看出它们的使用场景是不同的,一般情况下对于幂等的调用,可以选择FailOver或者FailCache,非幂等的调用可以选择FailBack或者FailFast。

  在实际的微服务架构实践中,上面这些服务治理手段一般都会在服务框架中默认集成了,比如阿里开源的服务框架Dubbo、微博开源的服务框架Motan等,不需要业务代码去实现。如果想自己实现服务治理的手段,可以参考这些开源服务框架的实现。

Dubbo框架里的微服务组件

微服务架构的开源项目

注册中心选型Eureka、Consul

RPC框架选型Dubbo、Spring Cloud、gRPC

监控系统选型ELK、Graphite、CAT

服务追踪系统HTTP、RabbitMQ、Kafka、Zipkin

配置中心选型Spring Cloud Config、Disconf、Apollo

服务治理平台SpringCloud配套、美团OCTO

微服务、容器化、DevOps

  微服务需要DevOps,而DevOps需要容器化。所以当容器技术发展成熟之后,DevOps和微服务就到了大放异彩的时候了。

  而要实现DevOps,就必须开发完成代码开发后,能自动进行测试,测试通过后,能自动发布到线上。对应的这两个过程就是CI和CD,具体来讲就是:

  CI(Continuous Integration),持续集成。开发完成代码开发后,能自动地进行代码检查、单元测试、打包部署到测试环境,进行集成测试,跑自动化测试用例。

  CD(Continuous Deploy),持续部署。代码测试通过后,能自动部署到类生产环境中进行集成测试,测试通过后再进行小流量的灰度验证,验证通过后代码就达到线上发布的要求了,就可以把代码自动部署到线上。

  目前业界比较通用的实现DevOps的方案主要有两种,一种是使用Jenkins,一种是使用GitLab。

下一代微服务体系Service Mesh