购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.4 微服务化开展前的准备工作

微服务改造是个综合性的系统工程,涉及研发全流程的各个维度,因此在微服务实施前需要进行一些必要的准备工作,比如从团队、技术上进行一系列的储备,确保微服务实施可以稳步进行。下面重点从微服务开发框架、微服务标准化以及持续集成和发布这几个维度分析微服务开展前的一些准备工作。

1.4.1 微服务开发框架

微服务背景下,微服务通信越来越复杂,通信场景和通信方式越来越多。一个运行良好的服务,不仅需要完成必需的功能需求,还要关注性能、稳定性、健壮性、兼容性、扩展性等诸多方面,此外还有日志、监控、统计、安全、容灾、容错等。从上面的需求来看,写一个完善的服务需要考虑的因素太多,特别是在移动互联网这种需求多变、对项目开发迭代速度和开发效率要求特别高的场景下,工程师很难在很短的时间考虑这么全面,写出符合预期的高性能高可用服务。

绝大多数服务对非功能特性和服务治理的需求都有一定的相似性,因此如果能够将功能需求之外的部分从业务中剥离,通用的、业务无关的特性对业务人员透明,那么业务人员只需要将精力聚焦在业务逻辑上,就可以快速完成微服务的开发。

微服务开发框架就是为了实现上述需求而产生的,聚焦和业务逻辑关系不太大的非功能需求,提高微服务开发效率,同时更为重要的是,使用相同的服务框架,业务服务可以更容易地实现服务标准化。

通常情况下,一个成熟完善的服务治理框架,基本功能层面需要包含如下几个部分。

(1)业务服务的脚手架

业务服务的脚手架是指在不考虑和其他服务通信的情况下,如何快速搭建一个业务服务。微服务架构下,需要从头快速创建微服务的场景很多,很多服务,尤其是在线服务,除了业务相关的处理逻辑之外,服务启动、服务退出、服务请求处理等流程几乎类似,至少可以针对绝大多数场景,抽象出一些通用的模式来。同时业务服务都会使用一些通用的基础组件,如日志库、配置库等,这些基础组件每个服务都会需要,因此对组件的性能、稳定性都有很高的要求。

(2)微服务通信

微服务通信包含的东西很多,如网络模型、服务发现、流量路由、负载均衡、连接池管理等。

(3)集成公司内的其他基础设施

很多公司都会有一些自研或者在开源基础上二次开发的基础设施,比如各种中间件产品、配置中心、部署平台、监控平台等,服务开发框架很重要的一个作用是和这些内部基础设施进行集成,对微服务业务人员屏蔽这些基础设施的细节。特别是一些中大型公司基础设施体系在实现上差异很大,从而很难直接使用开源的微服务开发框架,一般采用自研或者在开源基础上进行二次开发。

(4)微服务治理支持

系统经过微服务改造后,在效率、稳定性等环节都会遇到不少新的挑战,需要完善的服务治理支持,这些服务治理一般也是以微服务开发框架为支撑或者抓手的。服务治理是个很大的话题,放在第2章中专门讨论,这里不再展开。

1.微服务框架的选型考虑

选择和评估一个微服务开发框架时,应该从稳定性、易用性、调试与运维友好性这几个维度来考量。

1)稳定性和可靠性是第一位的,如果代码质量不高,经常出现一些不太好解释的现象,这样肯定会影响线上服务的可用性。

2)易用性,首先API接口设计要非常简洁明确,没有什么歧义,使用起来非常方便,没有什么大的心智负担。对于一些配置项框架要设置最合理的默认值,如果一个框架有非常多的配置参数,并且都交给用户,美其名曰是给用户灵活性,其实是把复杂和负担留给用户,把简单留给自己,这种框架是极其不负责任的。

3)调试与运维友好性常常被市面上的大多数微服务开发框架所忽略,很多框架经常号称自己有强大的性能表现,但很少有框架在调试和运维上下工夫,如何让使用框架的人在遇到问题时方便快捷地定位,如何支持业务自定义地添加调试和定位信息,这些都是框架需要考虑的问题。

4)最后要考虑功能和性能,功能和性能要能够满足当前业务需求。把这个放在最后是因为绝大多数框架在功能和性能上均能够满足需求。

对于性能评估和性能测试,大家平常一般主要关注QPS和耗时这两块。需要强调的是,性能测试前需要确定性能测试的基准,比如99.9%的响应时间必须在10ms之内。后续所有的性能测试需要严格按照这个基准执行。

针对QPS需要关注框架在多核或多线程下的扩展性;针对耗时,不仅要关注平均耗时,更要观察耗时长尾分布,以及耗时长尾是否有放大效应。

2.微服务改造时的语言选型考虑

不少人在进行微服务改造时,经常会有这样的疑问和想法:是否有必要在微服务化的同时选择更合适的语言?比如之前是PHP,微服务改造时想用Golang进行重构,这样是否可行?每种语言背后都有一套完善的语言栈体系,语言栈切换过程中很容易踩到一些坑,同时微服务改造本身也是一个复杂度很高的过程,如果把微服务改造和语言切换这两个风险很高的事情放在一起进行,会导致整个微服务改造过程中的风险增加很多,当过程中出现问题时也很难排查和控制。因此除非有特别的原因,不建议在微服务改造的时候进行语言层面的整体切换和重构,当然个别相对独立且风险可控的子服务,可以考虑语言层面的重构。

3.协议层面的扩展和增强

Thrift是当前使用非常广泛的高性能RPC框架,它功能强大,使用起来很容易上手,我们可以在短时间内迅速搭建一个可用的Thrift在线服务,以很好地解决开放效率、兼容性、性能等问题。Thrift有着完善的RPC和多语言支持,但直接在生产环境中使用开源的Thrift框架还是会遇到不少问题,比如没有完善的集群管理和服务治理支持、RPC协议无法扩展、没有定位和诊断相关的可视化支持等。国内外不少使用Thrift的团队都会针对开源Thrift进行定制和二次开发,下面就以生产环境中Thrift服务治理的一些实战经验为例,详细讨论对微服务开发框架的整体思考,以及Thrift协议层面的扩展和增强。

Thrift服务治理整体架构如图1-1所示,分为业务层、服务层、协议层和传输层4个部分,其中业务层是服务的业务逻辑实现,客户端和服务端通过IDL接口进行交互,对开源Thrift的扩展与增强通过服务层、协议层和传输层来完成。

服务层对开源Thrift进行两方面的增强:一个是客户端在集群管理方面的扩展,具体包括服务发现、流量路由、节点熔断和健康检查、负载均衡以及连接池管理等;还有一个是链路治理相关的插件实现,客户端和服务端分别提供通用的插件扩展机制,不同场景下进行相应的扩展和定制。

图1-1 Thrift服务治理整体架构

开源Thrift在扩展性方面有着非常出色的设计,协议、传输和调度层面均可以方便地进行扩展,但唯独在RPC协议层面没有提供扩展的入口,协议层定位是提供RPC协议扩展支持,通过协议扩展,可以方便地支持很多服务治理特性。

Thrift作为二进制协议,优点是传输效率和性能比较高,缺点是测试和调试诊断不太方便。而HTTP协议恰恰相反,HTTP有太多现成的工具可以使用,测试和调试非常方便。Thrift在传输层,增加了一个传输分发层的概念,使得Thrift服务同时可以接收HTTP协议消息,从而方便我们对Thrift服务进行测试。

RPC协议扩展是RPC框架非常重要的一个基础能力,基于扩展能力才能灵活支持不同场景下的扩展需求,比如以压缩特性为例,当某个基础Thrift服务当前不支持压缩特性时,如果想支持压缩特性,需要将传输层修改为支持压缩的传输层。Thrift压缩和非压缩的传输层之间不能兼容,需要客户端和服务端同时升级。对于有重点上游业务的基础服务来说,同时升级客户端和服务端是几乎不可能的事情。另外,为了支持分布式跟踪特性,需要在请求的整个链路中传递跟踪相关信息,如果框架协议层面无法支持,就需要每个业务均做出相应的支持,这不仅会影响业务迭代效率,同时因为每个业务实现方式各异,很难保证分布式跟踪方案的完备性和标准化。

开源Thrift在RPC协议支持上实现比较简单,支持name(rpc方法名)、message_type 和seq_id 3个字段,并且没有针对RPC协议扩展进行预留设计,比如预定扩展字段等,导致后续要对RPC协议进行扩展支持特别困难。

实际使用的Thrift过程中,经常会碰到一些通用的服务治理需求,比如分布式跟踪、全链路压测、多环境流量调度支持等,这些通用的服务治理能力最好是通过RPC协议的方式下沉到框架层面,对业务透明,否则每个业务人员都需要感知这些特性,因此在RPC协议层面进行扩展支持和增强是Thrift使用过程中不可回避的问题,大体有如下扩展思路。

(1)协议层扩展

开源Thrift之所以无法原生支持扩展,主要是没有预留消息头长度之类的信息,协议层只要增加一个字段来标识协议层的总长度,消息解析时把不认识的跳过即可。因此可以针对每个协议层协议增加一个后向兼容的新协议,比如通过BinaryProtocolNew兼容BinaryProtocol,通过CompactProtocolNew兼容CompactProtocol。具体来说,BinaryProtocolNew根据version判断新老版本,如果为0x80010000就是老版本,按照官方的BinaryProtocol进行解析;否则是新版本,按照新版本进行解析。

新版本在协议层增加Length字段,表示编码后的协议层数据总长度。在有Length字段后,如果接收到新协议消息,可以把不认识的字段直接跳过,实现前向兼容。通过协议层层面扩展RPC协议,新协议可以识别旧的协议,可以实现协议的前向兼容。这里最大的问题是旧协议无法针对新协议进行处理,没有办法实现协议的后向兼容。

(2)传输层扩展

Facebook内部使用的fbthrift采用的就是传输层扩展的方式,RPC消息发送前在业务请求前面加一个Header,Header里面可以添加一些自定义的RPC协议字段,如协议类型、压缩类型、是否支持加密与logid等;接收时先接收Header,根据Header中协议类型初始化相应的协议类和传输类,进行后续的报文解析。这种方式和协议层扩展相比,灵活性和通用性更好,但兼容性的问题依然存在,没有办法和开源的官方协议版本兼容。

(3)基于rpc name的扩展

Thrift RPC协议当前包括rpc_name、seq_id和msg_type 3个字段,这3个字段只有rpc_name是字符串类型,属于变长字段,可以考虑基于这个字段做一些文章。思路是将rpc_name扩展成JSON格式的RPC协议,示例如下:


rpc_name?trace_id=welcome&span_id=didi&compress_type=1

在开源Thrift的rpc_name字段基础上,加上JSON格式的KV信息,用于服务治理相关的扩展支持,同时为了减少对业务的影响,可以在Thrift编译器层面进行修改,Thrift编译器生成代码时,自动加上相关的支持。

和上面两种方式相比,该方法优雅地屏蔽了使用方式的差异,但仍然没有解决和官方版本的完全兼容问题。

(4)基于协议保留自动的扩展

为了提高RPC通信时的效率,Thrift在协议定义时为每个字段指定相应的字段序号,网络交互时不再传递字段名,而是统一传递字段序号,减少网络传输的数据量。但不清楚Thrift官方具体出于哪些考虑,当前字段序号不能设置为0,0作为保留字段。可以利用保留字段0进行RPC协议的扩展。

协议扩展信息的透传通过各语言SDK的方式进行,SDK需要提供相应的机制,方便业务设置和获取协议扩展信息,因此SDK需要提供业务和框架之间信息传递的方式。对于PHP、Python等语言来说,可以直接通过全局变量来完成;对于C++、Java等多线程语言来说,可以通过线程局部变量TLS的方式进行传递;对于Golang这种基于协程的语言,可以参考TLS,自行实现协程局部变量GLS,基于GLS进行数据透传。

基于协议保留字段的协议扩展见图1-2。

在客户端增加了协议装饰层,协议扩展信息通过TLS的方式统一维护,协议装饰层通过TLS获取扩展信息后,使用保留字段序号0,和业务请求一块打包,隐式传递到对端。

在服务端,流量拦截机制会在请求解析时判定是否有0号字段,如果有就将该字段解析出来,通过TLS机制设置后供业务查询使用。

通过协议扩展机制,可以方便地支持分布式跟踪等特性,服务治理相关的处理直接下沉到框架SDK中,对业务透明。而本节开头提到的压缩支持等特性,只需要在协议扩展中增加压缩标志,这样服务端可以同时兼容压缩和非压缩两种请求消息,实现业务友好的平滑兼容升级。

4.集群通信的“可靠性”

在没有完善的服务治理支持的情况下,当下游节点故障时,所有的上游服务均需要将故障节点摘除,如果不及时摘除,上游在服务访问时仍然会选择故障节点,会导致大量的访问失败。根本原因是上游服务不具备对下游故障的感知能力,没有及时将故障节点从可用节点中摘除,这是很多团队初期都会碰到的一个痛点问题。

图1-2 Thrift协议优雅扩展方案

为了保证下游变更时上游无感知,调用下游服务时的SDK需要具备故障节点熔断和健康检查能力,上游每次访问下游服务时均记录服务访问的metric指标,最核心的指标就是成功还是失败。为了判断下游服务的健康状况,针对每个下游节点引入了一个健康度的概念。为了简化判断,每次访问失败时健康度加1,访问成功时健康度减1,每次访问下游节点前,判断该节点的健康度,如果超过一定的阈值,可以判断该节点为故障节点,从可用节点列表中摘除,这个过程就是熔断。这里有个细节,当判定节点为故障节点后,还需要判断服务当前故障节点是否已经超过一定的阈值,如果故障节点过多且可用节点过少,服务的全部流量均会打到少量的可用节点上,这些节点可能会因为承载不了这么大的流量而雪崩。为了避免这种现象,当故障节点超过一定阈值时,不再将新的故障节点加入到故障节点列表,仍然放在可用列表中,负载均衡时可以承载一部分流量。当然这部分流量会访问失败,但通过分流这部分失败流量,可以避免集群雪崩导致的全部流量访问失败。

为了保证微服务集群通信的链路稳定和正常运行,下游节点熔断后还需要有相应的探测和恢复机制,也就是节点健康检查机制,一般有 被动探测 主动探测 两种方式。

被动探测是将故障节点设置为故障状态,冷却一段时间后重新将该节点加入到可用节点列表中使用。如果下游节点已经恢复,访问下游成功时,下游服务的健康度会逐渐恢复正常;如果下游没有恢复,访问下游仍然失败,会将该节点再次从可用节点列表中摘除,同时设置冷却时间,等待下一次探测。

和被动探测直接使用正常业务流量进行请求不同,为了避免对正常业务的影响,主动探测是使用额外的探测请求来判断下游节点的可用性。主动探测模式下,节点熔断后,每隔一段时间定时启动主动探测流程,并且和被动探测时直接将故障下游节点放回可用节点列表不同,主动探测返回成功标识后才会将节点放回到可用节点列表,如果探测失败则不进行任何处理,等待第一次的定时探测流程。和被动探测相比,在下游节点故障时主动探测不会导致额外的业务请求失败,对业务的可用性影响较小。但是使用主动探测有个前提, 健康检查时必须使用正确的协议来构造和下游故障节点的连接 ,比如通过HTTP协议访问下游时,就需要通过构造HTTP请求对故障下游进行探测;通过Redis协议访问下游时,就需要通过构造Redis请求对故障下游进行探测。

此外,为了减少故障节点反复故障时访问失败,当将故障节点重新放回可用节点列表时,不会直接将该节点的健康度直接清零,而是维持在一定的水位上,比如下游服务的健康度阈值为10(连续超过10次访问失败时,判定下游节点为故障节点),如果将健康度重置为0,在故障节点不稳定性状态时,需要累积10次失败才能重新进入故障状态,因此可以设置为一个相对高的水位,比如为8,保证失败几次后可以快速进入故障状态,避免对业务产生影响。

从上面的分析和讨论来看,节点的熔断和健康检查机制比较复杂,涉及很多技术细节。业务发展初期,在服务访问SDK不太收敛的情况下,各个业务以及不同语言下分别增加熔断和健康检查逻辑是件有挑战的事情,对于一些有众多多语言上游服务的基础服务,为了保证下游故障或者变更时对上游节点透明,可以采用增加一个Proxy代理的思路,将节点熔断和健康检查等集群访问和服务治理均收敛到代理上,以保证下游基础服务的变更对上游服务透明。

1.4.2 微服务标准化

1.接口标准化

接口标准化在微服务架构设计过程中发挥着重要的作用。在微服务的交互中,统一规范的接口标准可以大大减少沟通成本,提高开发和维护效率。

很多公司主流协议为HTTP协议和Protobuf/Thrift协议,HTTP协议开发、测试比较方便,而Protobuf/Thrift等二进制协议性能更好些。 一般来说,和端交互比较多的业务服务使用HTTP协议较多,对性能有一定要求的后台基础服务偏向使用二进制协议。

以Thrift协议来说,Thrift协议本身有自己的一套IDL,通过IDL生成标准服务代码,包含数据通路和类型定义,相对规范;为了保持微服务接口层面的一致性,实现微服务接口标准化,HTTP协议可以参考Thrift协议的思路,增加IDL描述支持。HTTP服务的IDL化可以规范标准、降低人为操作的出错率、降低下游服务接入成本和维护成本。下游服务不需要再维护代码,只需要维护配置,这点对于尚未使用统一的服务治理框架的团队来说特别有益。下游服务的升级修改收敛于单纯的IDL文件,版本更好管理。

我们可以基于接口标准化,为接口增加相应的描述信息,并通过代码自动生成机制,将IDL文件直接生成接口文档,实现接口文档标准化。

为了减少业务对服务治理支持的开销,可以把服务治理相关的配置放到IDL文件,实现服务治理配置的代码化。

因此,IDL文件成为服务标准化中非常重要的一个载体,通过IDL文件可以实现接口标准化、文档标准化和服务治理标准化。

2.日志标准化

日志标准化是服务标准化的关键一环,各个微服务之间只有遵循相同的日志规范,才能方便后续的日志治理,针对Log、Trace等日志形态,均需要制定统一的规范。

日志输出的目的不仅是发现问题,好的日志可以帮助我们快速排查出问题的原因,进而采取相应的应对措施,因此日志规范需要聚焦如何更加方便快捷地追查问题,提高问题追查的效率。

3.透传通道标准化

为了对微服务进行精细化的治理和管控,很多服务治理特性均需要在同一请求处理的各个环节获取到请求相关的服务治理信息,因此系统需要能够支持服务治理相关的信息在请求处理的整个链路中透传,服务治理聚焦的都是非功能需求,这些需求业务服务其实是不需要关注的,因此需要有一定的机制来减少服务治理的非功能特性对业务的侵入,最大程度上实现业务透明的服务治理接入。

透传通道就是为了实现上述所说的服务治理透明接入,思路是将服务治理相关的非功能需求信息从业务中解耦出来,在所有微服务之间进行传递。

透传通道承载业务无关的服务治理信息传递,是业务微服务和服务治理基础设施的桥梁,关系到整个服务治理的效率和效果,因此需要有标准化的透传通道,实现通道数据传输的可靠、透明和可扩展。

1.4.3 持续集成与发布

微服务改造过程中,需要保证改造过程中的修改及时得到验证,因此需要在持续集成和发布方面进行提前准备,确保微服务改造能够正常迭代进行。

1.研发全流程平台

微服务研发全流程包括很多环节,比如代码分支管理、代码review、测试准入、提测、自动化测试、服务部署、服务变更等,每个环节都有相应的工作流,研发工作流平台将研发全流程中的工作流串在一起,形成一个完整的闭环。这不仅可以提供特性研发工作的质量和规范度,还可以基于完整闭环机制方便地对每个特性的线上效果进行复盘和总结,通过微服务数字化运营,提高业务的迭代效率。

2.灰度发布

微服务化在架构层面是一个非常大的改动,为了不影响线上业务的稳定性,需要采用灰度发布的方式,每一次发布均需要先进行小范围灰度,观察完全没有问题之后再放量发布。

灰度发布的维度可以根据业务的特点而定,常见的灰度维度有按照机器、按照流量百分比、按照城市或者人群等。

为了支持精细化灰度发布,微服务改造前需要对部署系统进行适配升级,保证微服务化过程的每一步都精确可控,将对线上服务的影响降到最低。 tKHpQcwOOj4Lg5XPZEZNXB9zQmkUdaOoN4NTf2n7e1HFbiYFGT4hSTgzsYm7ScEk

点击中间区域
呼出菜单
上一章
目录
下一章
×

打开