您还未登录! 登录 | 注册 | 帮助  

您的位置: 首页 > 软件开发专栏 > 开发技术 > 正文

企业从单体架构向微服务架构转型的 9 个难点

发表于:2020-05-27 作者:twt社区 来源:twt企业IT社区

使用微服务架构方案能解决企业面临的很多挑战,而且目前微服务架构的框架都比较成熟,例如Spring cloud或者dubbo在各大互联网平台都有成功案例,但看似简单的框架在实际开发过程中会面临很多问题。本文整理了企业从单体架构向微服务架构转型的中的设计难点问题。

整理者:潘志伟,某金融企业,拥有十多年从业经验,精通微服务架构,精通大数据,拥有亿级用户平台架构经验,万级并发的API网关经验。还有以下专家分享:顾黄亮 苏宁消费金融有限公司 技术总监、zhuqibs Mcd 软件开发工程师 等

问题一:企业从单体架构往微服务架构转型怎么启动?

这是大家比较关注的问题,企业打算转型微服务,但是真正的实施后发现又很难。其实微服务架构转型不仅仅是一门技术活,更主要的的是组织结构和技术转型的结合,其中组织机构转型是起步的首要条件,包括统一思路和充分培训。

(1) 思想统一

当准备要实施微服务的时候首要条件就是获得高层的认可,因为涉及到组织结构的调整以及后续人力资源的增补,比如在单体应用中其组织机构包括开发部、测试部、运维部、DBA部,每个部门各司其职由高层统一指挥,看似很非常合理的组织结构,但是在项目或者迭代实际过程中会花费大量的时间去跨部门沟通,形成了孤岛式功能团队。

但是在实施微服务的时候,希望能协同配合快速交付,如果还是需要多次跨部门协调处理问题的话,那么“微”很难实现“微”的好处,微服务的团队应该是如下所示,所以如果没有高层参与那么组织架构就不会调整。

(2) 充分培训

微服务开发关注点:微服务架构的开发人员具备“精”、“气”、“神”的特质,否则在后续发展阶段一定会出现各种难题。“精”是指熟悉业务,熟悉选型的开发框架,“气”是指大家的思想认知一致,能够在一个频道上对话,“神”是指需要了解其理论知识,明白为什么需要这样而不是那样。微服务在开发设计过程中需要关注以下点:

一份基准代码多份部署(deploy):程序部署需要做到和环境无关,不需要改动任何一行代码,如图2-3

显式声明依赖关系:通过依赖清单 ,确切地声明所有依赖项(例如MAVEN 依赖),新进开发者简化了环境配置流程“做产品”而不是“做项目”

在环境中存储配置:所要求的代码和配置严格分离,配置可以完全不一样,但是代码必须是一样的,配置和代码无关“去中心化”地治理技术

把后端服务当作资源:后端服务是指程序运行所需要的通过网络调用的各种服务如数据库,MQ,缓存等。例如在不进行任何代码改动的情况下,将MySQL 数据库换成第三方服务

严格分离构建和运行:构建阶段是指将代码仓库转化为可执行包的过程,发布阶段会将构建的结果和当前部署所需配置相结合,并能够立刻在运行环境中投入使用,如回滚,运行阶段是指针对选定的发布版本,在执行环境中启动一系列应用程序进程

无状态进程运行应用:运行环境中,应用程序通常是以一个和多个 进程 运行的,任何需要持久化的数据都要存储在 后端服务内,比如数据库

问题二:微服务中所谓的服务到底如何拆分,服务拆分到什么粒度算好的服务?

在谈服务拆分之前首先给服务做个定义:服务是分布式架构下的基础单元,包含了一组特定的功能。微服务拆分的方式没有明确标准,可谓说是千人千面,每个人对于服务拆分理解程度和拆分尺度都不一样,有的团队按每个接口一个服务。一般来说我们在拆分的时候会结合理论知识和拆分原则来综合考虑:

1) 微服务拆分的理论指导

- 团队规模大小

一般来说5-7个人一个小组比较合适,因为沟通效率和团队可扩展性都能得到保障。如果一个团队人数过少的话,本来应该是多人开发的服务最后由1-2人来开发,会导致本来设计好的服务拆分逻辑最后却都合并在一个工程上做开发了,失去了微服务的意义。

- 项目交付周期

尽可能缩短项目交付周期短,把频繁需求变更的功能尽量独立成单独的服务,保证快速的迭代,还能满足快速上线的需求,缩短了项目交付周期,同时还能做到随时回滚,风险变小,从而提高系统稳定性。

- 变更影响范围

一个业务迭代功能点,尽量不要分布到多个微服务中,尽量将关联的实体对象存于一个微服务,避免分布式事务,比如把20%经常变动的部分进行抽离,80%不经常变动的单独部署和管理。

- 吞吐量大小

频繁访问,吞吐量大的服务,尽量独立微服务,方便扩容, 能够有效地提高资源利用率。

2) 服务拆分原则

- 高内聚低耦合

高内聚低耦合是软件工程中的概念,在软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。但是在微服务拆分中同样适用,服务拆分的一个准则是高内聚低耦合。从功能粒度来看,高内聚即每个服务尽可能只完成一件事(最大限度的聚合);低耦合即减少外部服务依赖,尽量避免服务再调用服务。从数据库角度来看每个服务单独使用独立的数据库,外部如果需要使用数据必须通过接口调用。

- 以业务模型切入

有了高内聚低耦合的前提,那么可以通过业务线来做拆分,比如用户、商品、订单、评论都拆分为独立的服务。把相关的业务都聚合在同一个服务中,这样也避免了跨库所带来数据一致性的问题。有可能以业务模型切入的方式初期阶段会比较粗,但是可以通过后续的迭代频率和吞吐量大小的指标再来衡量是否需要继续拆分。

问题三:分布式事务怎么解决?

一旦完成服务拆分,就会涉及到分布式事务,在谈数据一致性要求的时候有2个非常重要的理论即CAP定理和Base理论:

CAP定理:C表示一致性,也就是所有用户看到的数据是一样的,A表示可用性,是指总能找到一个可用的数据副本,P表示分区容错性,能够容忍网络中断等故障。

BASE理论:BA指的是基本业务可用性,支持分区失败,当分布式系统出现故障的时候,允许损失一部分可用性,例如在电商大促的时候,对一些非核心链路的功能进行降级处理来提高系统的可用性,S表示柔性状态,允许系统存在中间状态,这个中间状态不会影响系统整体可用性。比如,数据库读写分离,写库同步到读库(主库同步到从库)会有一个延时,E表示最终一致性,数据最终是一致的,例如主从同步虽然有短暂的数据不一致情况,但是最终数据还是一致的。

在实际中可以通过本地事务和发送MQ消息这种柔性事务方式来解决分布式事物所面临的问题,既能保障服务的稳定性又能保障调用效率的高效性,针对MQ可以使用Apache的RocketMQ所提供的事物消息和本地事物表结合。

问题四:微服务框架选型?

在选择微服务架构框架的时候,都在讨论目前主流的微服务框架Dubbo以及Spring Cloud。Dubbo出生于阿里系,是阿里巴巴服务化治理的核心框架,并被广泛应用于中国各互联网公司,只需要通过spring配置的方式即可完成服务化,对于应用无入侵。设计的目的还是服务于自身的业务为主。Spring Cloud 是大名鼎鼎的 Spring 家族的产品, 专注于企业级开源框架的研发。Spring Cloud 自从发展到现在,仍然在不断的高速发展,几乎考虑了服务治理的方方面面,开发起来非常的便利和简单。

这2种开发框架各巨头互联网公司都有深度使用,所以选择任何一套框架都不会成为技术的瓶颈,关键还是看团队熟悉哪种框架,选择最擅长的,而不是去跟风。

问题五:微服务架构下网关的必要性以及在网关下做限流、熔断、降级等操作

在谈到网关的时候,首先需要确认下目前微服务的业务线有几条,如果只有单一的业务线,那么有没有网关意义不大。其实网关可以理解为一个反向路由,它屏蔽内部细节,为调用者提供统一入口,接收所有调用者请求,通过路由机制转发到服务实例,同时网关也是“过滤器”集合,可以实现一系列与业务无关的横切面功能,如安全认证、限流熔断、日志监控。

- 网关工作原理

协议转换 :将不同的协议转换成“通用协议”,然后再将通用协议转化成本地系统能够识别的协议 ,例如把 http 协议统一转换为 dubbo 协议。

链式处理:消息从第一个插件流入,从最后一个插件流出,每个步骤的插件对经过的消息进行处理,整个过程形成了一个链条。优势在于它将处理请求和处理步骤分开,每个处理的插件,只关心这个插件上需要做的处理操作,处理步骤和逻辑顺序由“链”来完成。

异步请求:所有的请求都会通过 API 网关访问应用服务,无论业务量如何变化,网关的吞吐量要保持稳定状态。假如把网关的请求看成一次 IO 操作的话,处理请求的线程,从接受请求开始直到服务端返回响应,都是阻塞状态。操作系统所能承载的线程数是有限的,如果多个线程都处在这种状态,会导致系统缓慢。异步请求是指每个请求访问网关的时候,会被包装成一个事件, CPU 内核会维持一个监听器,不断轮询请求事件,请求的线程不用一直等待数据的返回。它在请求完毕以后,就直接返回了。

- 网关限流、降级

网关的熔断、降级是针对接口而言,可以选择hystrix或者sentinel来做服务包括,一般来说需要具备以下设置:

设置错误率:可以设置每个服务错误率到达制定范围后开始熔断或降级;

具备人工干预:可以人工手动干预,主动触发降级服务;

设置时间窗口:可配置化来设置熔断或者降级触发的统计时间窗口;

具备主动告警:当接口熔断之后,需要主动触发短信告知当前熔断的接口信息;

问题六:超时时间如何设置?

微服务中存在一次接口调用涉及到多个依赖服务,每个依赖服务的耗时又不一样,所以设置怎么样的超时时间非常有讲究,首先必须要有一刀切的态度,即每个接口的响应时间不能超过阀值(比如1秒或者2秒),一方面提升用户体验,另外一方面也是增加系统的稳定性。如果调用链路比较深的,则需要把非必要链路通过发送MQ消息的方式解耦,其次通过并行调用的方式来降低系统的响应时间。总的来说超时时间一般不会超过1秒,如何优化到一秒,需要从系统的全局考虑,而不是只关注某一个点。

问题七:熔断设计需要考虑哪些点?

在进行服务化拆分之后,系统中原有的本地调用就会变成远程调用,这样就引入了更多的复杂性。比如说服务A依赖于服务B,这个过程中可能会出现网络抖动、网络异常,服务B变得不可用或者响应慢时,也会影响到A的服务性能,甚至可能会使得服务A占满整个线程池,导致这个应用上其它的服务也受影响,从而引发更严重的雪崩效应。需要针对如下几项做了个性化配置:

Ø 错误率:可以设置每个服务错误率到达制定范围后开始熔断或降级;

Ø 人工干预:可以人工手动干预,主动触发降级服务;

Ø 时间窗口:可配置化来设置熔断或者降级触发的统计时间窗口;

主动告警:当接口熔断之后,需要主动触发短信告知当前熔断的接口信息;

目前市场上可选择的产品例如:Hystrix或者Sentinel做服务熔断和降级,这里推荐下 Sentinel ,不管是Dubbo还是SpringCloud 只要使用官方给定的依赖即可快速接入。

问题八:微服务架构的业务系统众多,那么数据的一致性怎么保障,数据的隔离机制如何实现等等?

当前微服务架构的业务系统越来越多,无论是做缓存场景,还是内存数据库场景,redis的使用非常普遍,但是每套业务系统都部署一套redis集群,相当浪费资源,而且,考虑到同城和异地的信息系统建设,费用也相当之高,是否有机制可以类似中台一样,建立一个统一的redis平台,提供各种场景的服务?那么数据的一致性怎么保障,数据的隔离机制如何实现,性能如何评估等等?

答1:

(1)首先统一的redis中心是很“技术”, 因为你要一个强大的技术人员或团队;

(2)为了保证一致性,redis cluster读取数据是从master上读取数据的,这样可以保证数据的一致性,当然,性能也就差了;redis 主从模式,写master节点,异步同步slave节点,读从slave上读取数据,读性能提高了,但一致性难以保证。这也就是门德尔不可能三角中的CAP原则中,保证P的同时,CA不可能同时满足。

(3)当然,也不是没有解决方案,但redis作为一个缓存数据库,并没有做的这么复杂。现代分布式数据库中,使用multi raft架构,最大限度的解决了这个问题----master是变化的,根据应用的不同不断的变化,同时读永远从变化的master上写入和读取。

(4)redis也是有事物的,但只保证了一致性和隔离性,没有原子性,一致性上面说过了。因为redis本质上是单线程的,一个一个的去执行命令。这种顺序执行,隔离性是有保证的。

答2:

首选纠正下你对微服务架构的理解,在微服务架构下,要求每个原子服务的数据库、缓存都是相互独立的,原因是当服务所依赖的数据库或者缓存有问题只影响它本身的服务,不影响其他服务,避免级联问题。

其次关于你所担心的资源浪费问题,可以考虑每个服务的调用量来设置不同的服务资源配置,目前不管是虚拟化使用docker还是云平台所提供的redis服务,都可以做到非常低的费用。

所以,想微服务稳定,按标准的模式来,每种资源做隔离,而不是聚集在一起。

答3:

这个架构有问题,统一的redis平台或者是集群提供服务,因此这个集群肯定是横向扩容的,只能是cluster集群架构,所以从一致性、数据隔离、性能评估三个方面来分析:

1、一致性可以做到,cluster的特性可以保证数据一致性

2、数据隔离做不到, 单机支持多个数据库,并且每个数据库的数据是隔离的不能共享。cluster就没有数据库的概念,不支持多数据库。

3、性能评估取决于承载业务的访问量。

问题九:接口拆分多个微服务后带来的接口响应慢,怎么办?

答1:

理论上不会有慢的现象,可从以下方面查 :

(1)使用skywalking或其他APM监控软件,定位问题,哪种服务慢;

(2)查看慢的服务所属容器的cpu和内存配置,以及在运行时的cpu和内存负载;

(3)如果cpu和内存占用很大,需要进一步拆分应用;

(4)检查是否有串行的微服务,此类微服务不适合拆分 。

答2:

这个问题应该是拆分之前没有做好规划

1、拆分以后链路会变长,服务之间的通信、交互、处理会耗时间,这是正常的现象,但不至于造成性能陡降 ;

2、拆分原则有几个,轻重、快慢、读写、多少 ;

3、如果慢,通过链路监控看慢在哪里,然后进行扩容、包括微服务组件扩容,优化 。

答3:

一个应用功能被拆分成多个服务之后,原本调用一个接口就能完成的功能如今变成需要调用多个服务,如果按顺序逐个调用的话,使用微服务改造后的接口会比原始接口响应时间更长,因此要把原本串行调用的服务修改为并行调用。例如接口 A ,需要调用 S1 (耗时 200 毫秒), S2 (耗时 180 毫秒), S3 (耗时 320 毫秒)这 3 个接口,使用串行调用方式,那么接口 A 累计耗时 =SUM(S1+S2+S3)=700 毫秒。为了让响应时间更短,就需要把这些串行调用的方式更改为并行调用的方式,并行调用方式调用接口 A 累计耗时为 MAX(S1 , S2 , S3)=320 毫秒。可以使用 jdk8 提供的 CompletableFuture 方法来并行执行。