微服务近年来可谓煊赫一时,合理的使用微服务架构能够解耦系统、提供更好的软件伸缩性以及提升组织的敏捷性。然而现实中较少有项目一开始就会选择使用微服务架构,绝大多数新项目在最初都会务实地从更容易掌控的单体架构起步构建,若是最终发现单体架构复杂到影响了团队的开发效率及软件的伸缩性等方面时,才会开始考虑逐步将系统往微服务架构作演进。web
现实中任何软件架构都是诸多trade-off的结果,想要得到微服务架构所带来的好处也就意味着有能力承担它所带来的的反作用。Martin Fowler就曾在MicroservicePrerequisites一文中指出实施微服务所须要的先决条件,他用“个子是否够高”形象地比喻了微服务所需的技能门槛。redis
而对于既有系统,还须要一种务实的演进方法和实施策略,使得可以伴随着恰当的代码重构,逐步积累能力和完善基础设施,最终平稳的将其演进到微服务架构。sql
本文总结了一些从既有系统到微服务演进之路上会遇到的问题和解决策略。文中使用“既有系统”而非“遗留系统”,是由于遗留系统给人一种即将退出生命周期、行将就木的感受,而咱们则但愿把精力投入到还有长远商业价值的系统上,经过合理的微服务演进让其具备持续的生命力。数据库
演进策略编程
本文推荐的从既有系统到微服务的一种务实安全的演进策略是:自上向下分析,自下向上重构,逐步完善配套。缓存
所谓“自上向下分析”,主要包含如下步骤:安全
1.总体演进路线规划:性能优化
梳理既有系统的领域模型,设计合理的内部服务边界,按照优先级和依赖关系规划演进路线;网络
2.服务治理方案设计:架构
按照优先级,为新服务定义职责,接口,与既有系统的交互方式以及跨服务的集成测试方案;
定义新服务的打包、测试、发布、部署、集成方式,目标是可以为其构建独立的代码库和持续交付流水线;
3.代码解耦设计和重构:
分析属于新服务的独立代码以及和既有系统耦合的代码,从物理打包和逻辑代码重构两层面解决耦合问题;
针对不一样的解耦策略,制定不一样的测试策略,完善自动化测试以支撑对应的代码重构工做。
所谓“自下向上重构”,指的是按照前面的分析设计结果,从代码重构开始,自下向上按照优先级和必定节奏持续进行服务化改造。
而“逐步完善配套”,指的是随着服务化的开展,逐步完善代码库管理,多流水线集成,并逐步按需引入服务治理框架,积累微服务须要的技术和工具能力。
上述过程是一个迭代的过程:经过适度的分析和设计,规划出具体的落地工做,而后经过小步增量的实践迅速得到成果和反馈,在过程当中逐步培养人的能力、完善支撑微服务架构的工程实践。
自上向下设计
明确目标和约束
对既有系统作微服务化解耦,须要对不一样解耦方向能得到的收益和存在的约束作到心中有数。见过一些组织在作微服务拆分时只强调能够得到的片面好处,忽略了对组织更有益的其它潜在价值,或者低估了微服务化带来的问题。这每每会致使不合理的服务边界划分或者错误的优先级排序。
沿着不一样的边界划分,目的是为了避免同的价值目标:
沿着系统内不一样的变化缘由和变化频率作服务划分
经过隔离不一样的变化方向,减小特性开发之间的干扰,使能小的独立交付团队。经过独立代码库、独立流水线,独立的开发、测试、交付和运维过程,提升交付效率和响应速度。
沿着不一样的资源使用边界作服务划分
经过将不一样资源占用特征的服务进行隔离,使能独立的水平弹缩,优化资源使用效率和提高业务响应能力。
沿着不一样性能路径边界作服务划分
经过将性能核心路径做为独立服务进行隔离,能够为性能核心路径使用不一样的技术栈以及作各类极致的性能优化;另外一方面避免各类改动影响到关键路径的性能降低(例如被动引入更多的异步交互等)。
因为服务划分会为系统引入新内部边界,因此必须考虑以下的约束:
数据一致性约束:服务划分后可能带来数据一致性变弱的问题,须要考虑是否能够接受;
性能约束:服务划分后带来的潜在性能降低,须要考虑如何度量以及承受程度;
容错性约束:服务划分为系统内部引入更多的分布式故障点,须要可以为其找到可接受的容错设计;
耦合关系约束:服务划分会放大系统的耦合问题,因此须要考虑沿着系统的松耦合边界进行服务划分,避免服务间复杂的交互或者联动修改。
在开始能够按照理想的价值目标去划分微服务边界,而后再接受每一项约束的挑战,最终的服务划分方案每每是一个在目标和约束之间逐渐平衡后的结果。
避免过分设计陷阱
对既有系统的微服务改造设计每每会陷入“架构设计陷阱”。过于详尽的分析和设计反而经常会阻碍微服务的拆分,常常获得一个“成本很大,困难不少”的论证。
对于这种状况,建议采用 快速启动、增量交付、大胆实验、当心求证 的原则。即快速构建目标,经过敏捷和精益软件开发的方式快速实践,经过反馈进行快速学习,在行动中解决各类问题。
具体的实践过程当中:
有了基本的分析后,快速成立试点团队做为探索者进行解耦验证,尽早得到反馈;
虽然快速启动,可是短时间目标要明确,经过迭代的增量交付来规避风险;
在实践过程当中逐步按需对修改影响较大的特性补充和完善自动化测试;
对有较大风险的代码修改,能够先拷贝一份在新的服务内作实验,得到足够反馈后再择机合入原代码库;
能够借助工具分析代码的依赖关系。曾经在一个项目咱们经过部署doxygen和graphviz来可视化代码的依赖关系和解耦进展,取得了不错的效果。
微服务设计
关于微服务设计的方方面面已经有不少优秀的书和文章了,例如《微服务设计》就是一本不错的教材。即使如此到任何一个具体的领域,仍有不少困难和挑战,须要领域专家和软件工程师们密切配合去解决。
使用领域驱动设计(DDD)方法能够帮助全部参与者从新梳理业务并达成共识。经过识别业务的界定上下文和聚合根,能够为如何划分服务提供参考。能够尝试组织DDD Workshop,可是要清楚这只是一项工具,并且有时成本并不小。DDD是一个演进式的过程,更多的工做须要随着深刻业务和代码,经过实践收集反馈迭代式的进行。
现实状况中,负责系统架构演进的人员都是对业务和设计现状比较熟悉的专家,一种高效的作法是从当前的数据模型直接入手。分析每一张表和每项字段所支撑的业务,将业务按照数据的内聚性进行分类,而后以此做为服务划分的起点。能够假设已经将数据按照新的服务边界从新分库分表,而后尝试基于此从新构建每条业务流程,并在过程当中解决因为数据拆分而出现的各类问题。该作法适合对微服务架构有经验的人和领域专家合做完成,这样可以对出现的各类问题找到不偏颇的解决方案。
天下没有免费的午饭,有时为了获得微服务的好处,是须要作一些妥协的。例如数据模型中某一实体的不一样属性具备不一样的业务内聚度,因此同一律念的不一样属性数据被分到了不一样服务中,可是这些数据在某些场景下要保持同步(例如须要被总体删除或修改等)。最多见的解决方案是选择一个稳定的服务做为对该实体的权威拥有者,其它服务经过某种手段(例如消息队列)和该服务对齐各类实例操做。这意味着业务要能接受最终一致性,还得接受在某些异常场景下数据一直没有同步成功而上报的告警。
在设计服务的集成方式时,须要站在业务角度去识别谁是更稳定的服务。依据“向稳定方向依赖”的原则,咱们只会让不稳定的服务去调用稳定服务的API,而反过来稳定的服务最好经过消息队列发布事件。那些须要跨越多个服务去获取数据的服务,通常能够经过监听事件和缓存与系统解耦,但这并不是适合全部场景。在某些场景下因为业务的一致性和性能限制,咱们确实须要往回退,把某些服务进行合并。这就是个不断的头脑风暴,而后再在各类选择中作trade-off,最终得到平衡的过程。
对于缺少经验的团队能够从较容易拆分的服务作起。例如一个web服务端能够先把路由和基本鉴权拆分出来,交给API Gateway负责;而后再把各类报表和统计等一致性与性能要求相对低的拆分出来,最后再尝试切分其它业务处理。
一旦服务拆分出来,就能够根据业务特征从新优化数据模型并选择更适合的数据库。另外服务的API设计也是有技巧的:应用接口隔离原则,须要API能独立完成功能,又要粒度相对小能够灵活组合。这方面亚马逊各类AWS服务的API设计就是不错的样例。
自下向上重构
获得了可行的服务划分方案,接下来就须要实际操做代码,将新服务的代码与既有系统进行解耦,为独立的服务代码库和流水线作好准备。
目录/包结构调整
软件的包结构通常和构建软件的组织结构以及建模方式有关。通常复杂系统同时存在着两个大的变化方向:技术维度和业务维度,而软件的包结构每每只能反映其中的一个维度。当组织结构以软件的技术维度进行划分,那么系统的包结构也基本上会以此划分,这时业务维度的变化每每会映射到系统的每个包上。反过来也是同样!衡量哪一种包结构合理,每每是看当前哪一个是主要的变化方向。对主要的变化方向进行拆包隔离,能够下降代码变化之间的互相影响程度。
若是按照变化方向进行包的拆分,就会发现系统中应该存在不少小的包,最后每一个服务是一堆原子的小包组合。这本质上是将系统从新进行合理模块化的过程。Adam Drake在文章Enough with the microservices中就直接指出微服务架构应该先从良好的模块化重构作起,大多数时候当模块化作好了甚至会发现不少问题已经获得解决了。
然而既有系统的模块合理化调整很难仅经过从新拆包达成!由于代码是有逻辑的,模块化的逻辑边界不可能刚恰好落在代码文件边界。大多数状况下都须要先对某一个代码文件进行拆分,对某一个类或者函数进行重构,对某一段逻辑进行从新设计,而后才能从新获得一个一致的逻辑和物理边界,支撑继续的拆包工做。
以前见过一个组织经过拆包进行系统解耦,他们把新服务和既有系统共享的全部代码拆分红不少小的共享包。这样作后看似每一个服务在构建和流水线是独立性的,可是问题在于那些共享包的代码量并不小并且包含不少耦合的业务逻辑,新的修改常常致使新服务和既有系统一块儿升级更新。
能够先对新服务创建独立的目录,而后尝试把属于新服务的代码逐渐往独立目录中迁移,在这一过程当中识别出新服务和既有系统耦合的代码,而后一边重构,再一边继续调整目录和包结构,最后使得新服务和既有系统在物理和逻辑上同时解耦。
代码重构
软件重构目的是为了解耦新服务和既有系统之间的共享代码。共享代码通常分为以下几种形式:
1.共同依赖的组件或者类,这又分为以下几种状况:
稳定的基础功能代码。例如编解码库,加解密等等。这些代码能够按照功能发布成独立组件,供每一个服务自行决定使用。
服务间接口和消息定义。这类代码能够划分到独立的库中,尽可能保持向前兼容,由接口的消费方自由选择依赖的版本。服务间的API和消息定义在本质上是契约的共享,可使用契约描述文件代替共享代码,使用时自动从契约描述生成代码,这对于不一样技术栈的服务会比较友好。
不合理编码致使的耦合。例如耦合了全部功能的大而全的单例类,通常是一些全局配置类或者是“建立一切”的工厂类等。这种状况须要对原有设计进行重构,对大而全的类进行拆分,将属于不一样服务的代码拆分到不一样的类中,由各个服务领回属于本身的代码。
2.共同继承的接口或者类,这又分为以下几种状况:
继承是为了组合:须要将继承的公共处理重构为支撑组件,由不一样的服务根据须要自行选择组合和使用方式。
继承是为了面向接口编程,这时接口每每是为了配合某些公共业务处理而作的抽象。这些公共处理能够按照如下几种状况进行重构:
接口背后的公共处理包含了复杂的业务逻辑,优先考虑将该公共处理变为一个服务。这时须要将继承接口上的同步调用变为服务间的消息接口。
接口背后的公共处理简单或者并不稳定,能够考虑按照“Replication Over Reuse”的原则,由每一个服务自行实现,减小服务间的代码共享。
接口背后的公共处理复杂,可是包含的业务逻辑相对稳定,若是不能将其独立为服务(例如因为性能缘由),能够将其打包成公共组件,由每一个服务自行组合使用。
从既有系统到微服务演进,在具体的落地中会发现最基础的工做主要是代码重构。而可否很好的实施代码重构是一个体现团队基本软件技能素质的过程,须要团队提高软件设计、代码重构、自动化测试方面的能力。
逐步完善配套
随着自下向上的重构,新服务的代码逐渐解耦到独立的目录或者包中,这时就能够按需补齐服务化所欠缺的服务治理机制和各类工程实践。在服务代码不具有独立性的时候开始尝试搭建各类服务治理机制和工程流水线,每每会引入不少偶发复杂度,对工具提出一些不切实际的要求。
服务治理
微服务做为面向服务架构当下可以流行,缘由之一在于随着技术的进步各类服务治理工具均可以廉价得到。服务注册发现、API网关、消息队列、负载均衡、服务监控、集群运维等每种需求均可以在网络上找到一批的开源工具,而团队则须要根据本身的现状进行合理的选择。有经验的团队能够把各类不一样的治理工做交给最合适的工具去作,而对于缺少经验的团队来讲能够先从某一工具入手累积经验。曾经有一个团队在开始不想引入过多工具复杂性,先选择使用redis作缓存和消息队列,随后又使用redis作分布式配置以及服务的注册与发现等等。后来随着能力提高,转而使用etcd替代redis作服务的注册发现,使用kafka作消息队列。
对服务治理工具的选择要避免陷入选择困难症。每一个团队都会以为本身的业务特殊,开源工具老是不能知足本身的全部要求。带着这种想法很容易裹足不前,一再浪费架构重构的合适时机。精益的作法是,先找到业界广泛使用的工具,一边使用一边解决问题,一旦开始了不少问题在实践中总能迎刃而解。对于一些注重性能的系统,不可避免的须要对开源组件在特定业务场景下进行优化定制,也最好先开始使用而后在实践中肯定优化的方向。
持续交付
“服务有本身独立代码库和交付流水线,能够避免交付过程当中的互相干扰,提升交付速度和质量”,遗憾的是上述描述实际上是个伪命题!
真正减小团队干扰、提升交付速度和质量的是“正确的解耦”自己。独立的代码库和流水线会将架构约束显示化,让团队成员难以犯错。可是若是过早的对不成熟或者不稳定的架构边界进行固化,反而会下降团队的效率,让后续架构调整变得困难。另外在系统没有合理解耦的状况下,独立的代码库和流水线只会让交互变得更复杂,致使对构建和发布工具提出一堆不合理的要求。
可是若是服务之间确实已经正交拆分,代码边界和架构边界一致而且是稳定的,这时独立的代码库和流水线就能够下降团队在交付流水线上的互相干扰和排队,此时就值得为新的服务创建独立的代码库和自动化流水线。考虑到服务之间的集成,每每须要多级流水线,这时选择一款支持pipeline的持续集成工具是必要的。Jenkins2.x以及GoCD是此类产品的表明。
适应的组织结构和文化
康威定律常常被拿来讲明组织结构和系统架构之间的互相做用关系。在对既有系统的服务化重构中,软件架构和团队结构同步进行调整会让整个过程更加顺畅。曾经有一个系统最初按照技术维度划分团队,后来为了提升响应市场的速度把团队按照不一样的业务类型进行了调整。从新划分后的团队开始发现他们会常常修改同一公共组件,这时他们自发的对该组件进行了解耦,将其中和业务相关的逻辑各自领了回去,而后将剩下的稳定逻辑下沉到了基础设施中。
除了匹配的组织结构,还须要团队逐渐调整本身的文化。专门的测试人员和运维人员在微服务架构下必然成为瓶颈,须要改变传统的细分工的文化。团队每一个成员都要有意愿和能力承担起服务的测试和运维工做,这须要组织从文化建设到考核方式作对应的调整。
总结
对于既有系统作微服务演进,一旦第一个服务改形成功,后续的服务借助前面的成功经验和已有的基础实施,会更加的容易拆分。不过第一个服务的拆分确实须要投入比较大的决心和精力,本文给出了一些建议,归根结底总结起来就是:以精益的方式开展,以代码解耦为核心,以服务化技能作武装,以组织结构和文化调整作基础!
欢迎工做一到五年的Java工程师朋友们加入Java架构开发:744677563
群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用本身每一分每一秒的时间来学习提高本身,不要再用"没有时间“来掩饰本身思想上的懒惰!趁年轻,使劲拼,给将来的本身一个交代!