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

1.5 微服务实施

微服务改造过程中会面临很多挑战,接下来从服务拆分、服务通信以及服务稳定性设计这几个维度出发,讨论微服务实施过程中需要着重注意的问题。

1.5.1 微服务拆分

服务拆分是微服务改造的第一步,也是最为关键的一步,拆分是否合理,决定了整个微服务化过程的成败,因此,需要有科学的拆分原则和拆分方法,保证拆分过程有序进行。

1.拆分原则

微服务拆分前,需要确定拆分的基本原则,微服务拆分的一个非常重要原则是无状态,无状态服务方便扩展和运维,所以无状态设计需要贯穿到微服务架构设计的全局层面进行思考。

服务拆分粒度并不是越细越好,需要结合当前团队的人员情况而定,比如当前团队只有5个人,如果拆分后的微服务个数超过50,和人员的数量严重不匹配,不仅不会带来效率的提升,反而会导致效率的下降,建议拆分后每个人维护的微服务不要超过3个,并且每个微服务都有相应的备用负责人,规避可能的突发风险;针对多团队异地开发的情况,最好拆分后每个子团队负责的微服务相对独立,尽量减少异地团队的直接沟通成本。

微服务拆分时经常会遇到一些问题,比如是否需要拆分、拆分粒度的问题等,一般原则是遇到痛点问题时才进行拆分,非拆分不可时才启动微服务拆分,不要为了拆分而拆分。对于拆分方案拿捏不准的场景,尽量先不进行拆分,等想清楚了再定,避免拆分不合理,带来大量返工。

拆分后的微服务组织上层次不要过深,一个请求的处理过程中经过的微服务个数不要超过5个,链路太深会导致定位和追查问题特别麻烦,同时也会带来一定的性能损耗。

对于拆分后的子服务来说,尽量避免相互依赖的场景,子服务调用时相互依赖会导致升级和维护时特别麻烦,增加很多不必要的运维成本。

2.拆分方法

微服务拆分的总体思想是根据高耦合、低内聚的原则识别出各个微服务的边界。具体拆分思路和业务形态紧密相关,没有绝对的标准,下面介绍微服务架构拆分中使用比较多的两种拆分方式。

(1)以数据为维度进行拆分

按照数据拆分,是微服务拆分中最常见的一种方式,没有特殊考虑时一般根据领域实体数据进行拆分,拆分出来的服务负责处理给定的数据/资源的所有操作。

以电商系统为例,大体可分为订单系统、用户系统、库存系统等。以订单系统为例,订单系统就负责订单核心数据的维护、订单数据的增删查改,确立了主要实体数据后可以根据数据的查询、修改等确定服务的接口,如订单的各种查询接口以及订单状态更新接口。

根据数据维度把系统可以拆分出若干个子服务,但这些数据之间会有不少关联关系。

以百度贴吧为例,贴吧中的人、吧、帖是三个最重要的实体数据,这些实体数据可以分别放到不同的服务中。但是一些常见的实体关系,比如人发过的帖子、人收藏的吧,以及吧的会员、吧的帖子列表等,这些实体关系数据的维护,原则上放在哪个实体上维护没有标准的答案,某个人发过的帖子放在人服务和帖服务都可以,反复在这些地方纠结也不会带来太多的收益,可以人为地指定一个约束,如某个人发过的帖子就放在帖服务里面。

(2)按照使用场景拆分服务

按照使用场景进行服务拆分,这种拆分方式也比较常见,如顺风车的所有后端查询操作之前都在一个单体服务里面,有固定路线/临时路线查询顺路订单,有根据路线查询附近订单,还有查询跨城订单,以及后续的需求乘客查询附近司机,乘客查询顺路司机等。之前这些在线查询接口都在一个单体服务中,多个RD会同时负责和修改这个服务的代码,开发效率低下,测试、上线都会相互影响,维护起来比较困难,后来把这些查询按照功能都拆分为不同的子服务,每个RD单独负责,大大提高了开发、测试以及运维效率。

(3)重要和非重要的拆分

将核心逻辑和非核心逻辑拆分为不同的微服务,然后采用不同的高可用处理方案,比如核心服务尽量做到机房、集群等多维度的冗余和隔离;非核心服务则可以适当降低可用性标准,出现问题时只要能够及时降级和熔断即可。

(4)变和不变的拆分

按照变更频次对服务进行拆分,尽量将变化聚集到少部分微服务上面,系统的绝大多数服务变更很少,可以减少变更对整个系统的冲击和影响。

1.5.2 微服务通信

为了保证拆分之后的微服务能够通力合作,共同对外提供服务能力,需要通过一定的机制将拆分之后的微服务合起来,也就是微服务通信,接下来讨论微服务通信过程中常见的一些问题。

1.微服务通信方式选择

微服务通信时,首先需要考虑的是通信方式的选择,对一些不需要返回业务数据的微服务来说,究竟是采用同步方式还是异步方式呢?同步方式架构层面要求比较高,异步方式架构层面比较优雅,但运维成本比较高,出现问题时排查起来不太方便。之前公司中有过一次不成功的架构升级实例,业务形态完全围绕订单状态流转进行,当前的架构初衷是构建一套基于事件驱动的基础设施,所有微服务均以事件驱动的方式进行触发,最后不仅对业务人员的挑战比较大,同时运维、高可用和问题追查的成本比较高。因此微服务化时,核心服务推荐均采用同步通信的方式,一些非核心服务可以采用异步通信的方式。

2.微服务编排

微服务编排上,一些可以并行调用的场景推荐采用并行调用的方式,可以减少请求的整体耗时,提高用户体验。

当然对于有些场景来说,直接采用并行调用不一定是最好的方式,需要综合考虑和分析。比如,对于搜索和广告引擎来说,整个系统会有大量的过滤子服务组成,如果完全采用串行的方式,并且有着良好的漏斗模型设计,这时请求的整体耗时可能会上升,但整体的调用量会减少不少,成本上有很大的节省;如果采用完全并行的方式,整体耗时会有很大的改善,但无法充分利用漏斗模型,成本上会有一定的浪费。面对这种场景,需要有一套完善的微服务编排引擎,能够同时兼顾性能和成本上的需求,做到微服务编排的最优化。

3.API接口设计

API接口设计上,需要尽可能简单易用,一个接口只实现一个确定的功能,不要设计复杂或者含义不明确的接口,避免滥用;同时接口设计时做好前后向兼容,以及幂等性设计。

4.合理设置超时和重试

微服务通信上,需要合理设置超时和重试,超时和重试设置不要只考虑当前链路,而要从请求全链路的角度,对超时和重试进行统一梳理。

除非有特殊原因,建议在调用的入口统一进行重试,请求过程中不再进行重试,避免故障时的多级重试导致的整体雪崩。

5.数据一致性设计

同时操作多个微服务的场景,需要保证不同微服务之间的数据一致性,数据一致性设计尽量遵循简单的原则,除非特别场景,不要使用分布式事务。

多个微服务操作时,成功或失败需要以核心数据为准,当同时对多个核心数据进行操作时,可以以其中一个为准,遇到个别数据不一致的场景,可以采用线下对账的方式对不一致的数据进行校对和修正。

1.5.3 微服务稳定性保障

微服务改造中,挑战最大的就是拆分之后的稳定性保障,拆分之后链路复杂、故障点众多,需要一套体系化的稳定性保障机制。

1.稳定性保障的目标

微服务稳定性保障需要从事前、事中和事后全方位进行考虑。微服务架构下,应用程序、依赖服务、网络、硬件等都有可能出现故障,稳定性设计和保障的具体目标如下。

故障预防 ,尽可能减少故障的产生,绝大多数稳定性问题和稳定性故障发生都有一定的诱因,并且一般是在多种拦截手段均失灵的情况下故障才会发生,如果我们在故障发生前制定完备的稳定性保障措施,可以最大限度地减少稳定性故障的发生。

故障快速定位 ,完全不出故障的业务是不存在的,关键是出故障时能够快速发现故障,只有及时发现,才能在最短时间内采取相应的解决措施。

故障快速止损 ,发生故障后第一时间要进行业务止损,恢复业务的正常运行,故障深层次的具体原因可以事后再分析和复盘解决。

2.稳定性保障的6个维度

系统故障点很多,稳定性保障就是对故障点进行管理的过程。可以从故障点管理的角度将整个稳定性设计和保障分为如下 隔离、冗余、容灾容错、变更管理、时间相关故障管理 运维友好 6个维度。

(1)隔离

影响系统稳定性的不稳定性因素和故障点很多,从稳定性保障的角度看,很自然的想法就是,如何尽量减少如此多的故障点给系统稳定性带来的隐患,比如某电商业务有200个微服务组成,如果这100个微服务中的任何一个出问题,导致业务不可用,那么系统的可用性就会很低。业务层面如果能够梳理和抽象出保障业务核心运行的最小系统(比如上面提到的电商业务最小系统包含的服务可能会小于50个,其他都是增值和支撑性的服务),同时将最小系统之外的不稳定性因素从最小系统中隔离出来,就可以大大增强系统的稳定性和健壮性。因此,稳定性设计的第一个原则就是“隔离”,通过各种隔离机制,将核心服务之前的故障点隔离出去,保证核心服务的可用性。

(2)冗余

通过隔离,可以减少绝大多数不稳定性因素对系统稳定性的影响,但如果核心服务或核心服务所在的环境出问题,就无法从隔离中受益。我们需要有相应的机制保证核心业务的部分单元出问题时,业务整体运行不受大的影响,这种机制就是冗余。通过服务级别、机器级别、集群级别、机房级别等多种维度的冗余,我们可以保证:即便核心服务出问题,也可以通过相应的流量切换策略,将流量切到冗余节点上,保证业务不受影响。

(3)容灾容错

通过冗余可以保证核心服务及其部署环境变化时的系统稳定性,但如果系统外部输入有变化,比如遇到突然大流量、异常流量或者突发的安全攻击,而系统事先没有相应的应对机制,则业务很可能瞬间被击垮。因此,稳定性设计的第三个原则是“容灾容错”,通过构建多维度的容灾容错体系,保证系统面对异常输入时,仍然能够提高稳定的输出能力。

提示

隔离、冗余及容灾是解决外部环境与输入导致的各种故障点和风险。

隔离 强调的是将微服务架构体系中非核心服务导致的故障隔离出去,减少非核心因素对业务核心的稳定性影响,隔离工作做好之后只需要考虑核心服务的稳定性。

冗余 解决的是核心服务面对各种环境变化时的稳定性应对,比如服务故障、交换机故障、网络故障、机房故障等,通过各种层次的冗余和流量调度机制,保证业务面对各种硬件和环境变化时仍然可以通过冗余切换提供稳定的服务。

容灾 解决的是面对各种输入变化时的稳定性应对,比如突发大流量、异常请求、安全攻击等,容灾容错保证系统面对各种输入突变问题时,稳定性不受大的影响。通过隔离、冗余和容灾,解决了非核心故障点风险、环境和输入风险,保障了业务最小子系统面对各自外部变化时均能够提供稳定输出。

(4)变更管理

通过隔离、冗余与容灾可以排除和应对业务最小子系统的各种外部稳定性风险,变更管理、时间相关故障管理是为了解决核心系统自身的稳定性问题。绝大部分稳定性故障都是由变更引起,系统如果长时间没有任何变更,很少会有稳定性问题,因此服务稳定性保障的关键一环是严把变更这一关,保证变更质量。

(5)时间相关故障管理

服务没有变更时,有一类故障很少发生并且很难发现,就是随时间变化而产生的ID越界和溢出,这类故障平常测试时很难发现,并且发生时会对整个系统产生很大的影响,需要从设计层面开始把控,比如随时间变化的ID字段尽量使用int64。

(6)运维友好

隔离、冗余和容灾减少了核心服务的外部稳定性风险,变更管理和时间相关故障管理保证了核心服务自身的稳定性。上述5种措施构成了业务稳定性预防的整个闭环,但即使设计得再好,也不能完全杜绝稳定性故障,稳定性风险只能最大限度地减少,因此服务的研发生命周期中,还需要加上运维友好的考虑。设计时需要针对稳定性问题的快速发现进行特别考虑,如日志怎么输出,统计信息如何收集等。通过运维友好设计,比如log、metric和trace等方式的良好设计与运用,建立全方位多维度的故障发现机制,保证出现问题时可以快速发现和快速止损,最大程度上减少稳定性风险的影响。

3.稳定性保障的设计建议

总之,隔离、冗余和容灾、变更管理、时间相关故障管理和运维友好构成了整个的稳定性保障体系,下面会对这几个维度详细展开讨论。

(1)隔离机制

隔离机制的指导原则是将变和不变、重要和非重要区分开来,变更是稳定性故障的最主要的来源,将容易发生变化的部分从核心服务和核心流程中剥离开来,减少核心部分的变更,可以保障核心系统的稳定性。

隔离机制的一大手段就是 解耦 ,通过解耦可以把核心服务和非核心服务隔离开来,同时核心服务访问非核心服务时,通过熔断、超时和重试等机制,最大限度地保障非核心服务故障不会影响整体的稳定性。

异步化 是流程隔离的主要方式,为了减少非核心服务故障对核心服务的影响,可以将一些非核心流程异步化处理。

为了减少对核心服务的影响,核心服务建议单独部署,如果特殊情况下需要和其他服务混部,一定要保证有精细的资源隔离机制,保证不会因为其他服务资源使用不当,影响核心服务的稳定性;同时核心服务用到的一些关键基础服务、关键数据服务也建议隔离开来,分开部署,避免其他服务出问题对核心流程的影响。

(2)冗余

为了保证故障时冗余机制的可用性,事先需要进行完善的冗余容量规划,各级故障发生后,剩余容量足够承载峰值流量,一个大体的原则是:机房内尽量满足N+2冗余,2用来冗余(应急变更故障和其他故障),机房间N+1冗余,1个机房挂掉后,剩余机房能承载所有流量。

为了尽量避免冗余同时失效的情况,冗余副本之间需要相互独立,完全对等,不能相互依赖,机房内副本跨交换机部署(此时一般也能保证跨机柜),如果有多机房冗余的情况,各机房独立,不能有完全相同的依赖。

系统为了构建完善的冗余机制,服务设计时尽量无状态,无状态的服务随时可部署启动,方便水平扩展。

为了用好冗余,常态下,需要支持灵活的流量调度策略,具体包含服务发现、流量路由、负载均衡等;节点故障时,通过健康检查、故障节点剔除、动态路由切换等机制,可以平滑地将流量从故障节点切到冗余节点,保证节点故障不会影响系统整体稳定性。

(3)容灾容错

为了减少异常流量对系统的影响,服务开发和设计时需要采取防御性编程,提前想到可能面临的种种异常情况,并针对性地进行防御和解决。

服务可以通过降级和限流,减少突发大流量对系统的冲击,保证系统稳定输出,为了保证降级和限流操作的即时性,系统需要支持配置的动态修改和生效。

(4)变更管理

为了提高变更的质量,减少变更带来的稳定性风险,需要从测试、上线和规范等多角度进行保障。

测试上,通过建设线上/线下多级测试环境,比如线下的联调环境、集成环境、仿真环境,线上的预发布环境,通过不同用途的环境,提高各种场景的模拟能力,提高故障拦截率。

针对变更,需要制定完善的变更规范,变更时严格按照规范进行,再小的变更都可能会产生稳定性隐患,因此变更时一定要加强稳定性意识,变更的每一步操作都要进行各项监控项检查,如果出现问题立即进行回滚。

对所有变更操作,不管是服务变更、配置变更和数据变更,均要采用灰度机制,灰度观察没有问题后再放量,关键特性需要设置相应的特性开关,方便根据需要灵活地对特性进行开启和关闭。

最后,针对变更过程中比较容易遇到的问题,可以梳理出一套变更反模式出来,供其他人作为反面例子参考,下面是梳理的之前线上服务遇到的典型变更反模式。

1)代码搭车上线。

比如由于缺乏有效的代码修改管理机制,某产品线由于代码搭车上线,出现过多次线上故障,并且由于变更时涉及的修改比较多,导致问题定位和追查时非常困难。

2)服务回滚时遗漏回滚代码。

某业务隐私页面改动,首页浮层上线有问题,导致呼叫量下降,上线回滚后恢复;回滚后没有第一时间把代码回滚掉,其他人上线时将未回滚的问题代码再次带上线,导致连续两天出现系统故障。

因此,服务回滚的时候,必须第一时间回滚代码,保证主线代码任何时候都是干净没有问题的。

3)服务启动或者回滚时间过长。

某产品计价服务上线异常,回滚时单个服务回滚时间太长,导致未能短时间内快速止损。经排查,回滚过程中部署系统和服务都存在耗时过长的现象,由于服务回滚速度比较慢,产生了较大的线上服务故障。定期检查和优化服务的启动和回滚时间,保证出现故障时可以第一时间完成回滚操作。

4)配置文件缺少有效的校验机制。

配置文件由模型产出,数据配送系统实时配送到线上服务,模型产生的配置文件有问题,引发线上故障。因此,针对配置文件,尤其是策略模型产出的配置文件,需要建立严格的检查和校验机制。

5)小流量后的修改没有经过严格的测试和灰度验证。

某服务经过小流量灰度后,代码又有少量修改,再次上线时未灰度,导致线上故障。再小的变更,都要进行测试、灰度和双重检查(double check)。修改一行代码,也可能导致线上的稳定性故障。

(5)运维友好

为了实现运维友好的系统设计,系统需要将故障分析和定位时涉及的所有相关信息监控起来,构建完善的监控闭环,对系统层、服务层、接口层、业务层等维度进行监控收集和告警。

为了减少系统的稳定性隐患,微服务架构设计中尽量遵循简单的设计原则,从业务的真实需求出发,避免纯粹从技术角度出发的高大上技术方案,如果不是业务的核心功能,必要时可以进行一定的折中和裁剪,尽量保证系统的简单和简洁性。

另外,尽量使用一些验证过的技术,对于没有充分验证过的新技术的引入需要特别谨慎,如果一定要引入,需要控制好节奏,循序渐进。 8T5a5swOMt/XK/rLACyYLqbZSauHwSC5adkb3v7w2TYUsfajGQvqOHbNZMYwJs6v

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