前言前端
近年来,服务化和微服务的架构随着线上业务对响应变化和发布频率要求的不断提升已经变得日益常见。像DevOps和Docker等理念和技术的成熟也在为这一趋势推波助澜。许多企业对微服务思考的关注点也从最初的“该不应用”、“该如何用”逐渐转向“如何评价服务拆分的好坏”、“服务拆分后的数据如何治理”这样更加具体而实际的方面。纵观社区里与微服务有关的话题,既有一些适合大众阅读的概念入门科普文章,也有一些像“架构去中心化”、“消息异步解耦”、“最终一致性补偿”这类专业性的技术讨论,但却不多看到可以比较深刻的介绍微服务实施经验,说清楚“什么地方会有什么坑”这类问题的实践性内容。数据库
若将微服务的方方面面铺开,将是一个很是庞大而复杂的知识体系。万事开头难,如何迈出服务划分的第一步是对于那些从未采用过微服务的项目首先要面对的问题。虽然服务的划分原本是一件十分凭借架构师我的经验和对业务理解的主观工做,但其中仍然有些规律可循。根据服务的类型特色,能够有如下几种常见的方式:编程
1. 根据业务进行建模,依据业务领域的边界划分,这也是当下微服务社区十分推崇的领域驱动设计(Domain Driven Design,简称DDD)方法;后端
2. 根据资源使用类型划分,这种方式主要使用在对硬件资源需求很高、或对特定硬件类型/区域地址等存在依赖的大型服务系统,例如系统的某些功能很是消耗CPU、或是某些功能必须在有加密狗设备的机器上运行。此时能够依据系统各部分对不一样资源的须要做为边界进行最安全的划分,以最大化运行效率;安全
3. 根据数据边界划分,直接从数据表结构或数据源着手,依据数据归属关系界定服务。这种划分方法简单粗暴且能避免数据耦合,但容易致使潜在隐患,甚至能够被认为是一种反模式。仅仅对于强数据驱动的系统,如某些报表系统和多数据源ETL系统等适用,在实际案例中不多见。数据结构
从现实上来讲,咱们平时遇到的绝大部分系统都是须要为特定终端用户群体服务的。所以,采用基于业务领域的建模的方法一般都会是个不错的选择,它也是目前在划分服务问题中的最主流的方法。不过即便有了理论指导的支撑,服务划分里的利弊权衡并非非黑即白的事情,依然存在很多模棱两可之处,只有在犯下一些错误后才能摸索出适合本身系统的方法。本文将会聚焦在这些点。架构
没有什么能比一个具体而贴近真实的案例更具备表明性了。所以,咱们将剖析一个传统行业企业的线上业务:某汽车产品代理商的线上销售和售后平台,介绍它在微服务转型过程当中遇到的种种状况,但愿能以此做为前车可鉴,让后来者避开没必要要的趟坑。这个案例中讲述的场景原型并不是彻底来自同一个项目,而是从咱们过去一年中所经历的多个项目的实际场景抽取出来的,去除了其中的敏感信息,并添加了基于真实状况的适当演绎,使之更加完整。简单介绍一下案例的背景:这是一家颇具规模的汽车代理商企业,承接多个国际一线品牌汽车的销售和周边资源整合的业务,具备庞大的实体店网点。其线上的IT业务系统已经存在了十余年,提供的功能从最初的进销存管理、客户信息管理等到如今的面向消费者客户的服务系统,十分复杂。系统中的线上销售和售后平台部分是相对比较新、且需求变化特别迅速的部分,也是目前出问题最频繁、收到抱怨最多的部分。框架
下面咱们将从几个方面展开这个话题。异步
不要从数据库开始建模编程语言
传统软件开发中,数据模型被认为是整个系统的核心,业务逻辑仅仅是对数据的CRUD加上简单的计算呈现。有些项目团队的架构评审会花不少时间来讨论系统庞大的ER图(实体关系图)设计。但在微服务架构设计时,ER图并不是最佳选择,特别是在服务划分时采用ER图进行建模甚至是十分有害的。
在领域驱动设计的实践中,有一个和ER图建模有些类似的环节,叫作“领域建模”。相比ER图建模一般只有开发人员参与,并从数据表的角度考虑模型的方法,领域建模的过程须要由产品的业务人员和核心开发人员共同参与,先梳理用户场景,而后从业务领域角度,逐步肯定场景中的实体、关联和聚合等元素。其中的“实体、关联”与ER图中的“实体、关系”有些类似,但含义并不一致。领域模型中的“实体”本质上是业务场景中须要被持久化存储的对象,但存储方式不必定是数据库,更不必定是关系型数据库,而ER图中的“实体”最终对应的就是关系型数据库的表。领域模型中的“关联”是两个实体在业务上下文之间有业务含义的联系,而ER图中的“关系”只的是两张表之间的一个关联外键。
那么,使用ER图给微服务建模会存在什么问题呢?
首先,ER图设计时假定了使用的是关系型的数据库。微服务的一个特色在于它支持异构架构,系统的不一样部分,依据实际须要可选择不一样的编程语言、框架以及数据库的类型。关系型数据库采用平面表的结构,若是有两类嵌套关系的对象,只能使用关联表来表达两个实体之间的关系,而后经过复杂的SQL语句在查询时将多个表拼成更大的平面表,最后在业务代码里再分解到各个独立的对象里去。这些单独的表又可能在其余地方与另外一个表存在关联查询。这样设计出来的表结构的冗余很低,且使用很是灵活,但正是这种灵活性往致使系统中多个业务逻辑表出现错中缠绕的关系,从而使剥离单独服务进行异构设计变得困难。
其次,ER图还会致使实体类似的不一样业务逻辑在设计时被耦合在一块儿。举一个具体的例子,在汽车代理销售系统中,不一样品牌汽车的购买流程后端实际上分别对接的是彻底不一样的分销渠道系统和逻辑流程,但它们在用户视图上所须要的信息比较一致,所以存储的数据结构也比较类似。这个系统在最初设计时采用了ER图的方式建模,因为ER图模型不关心业务层面上的东西,不一样品牌汽车的实体数据看上去都是一种类型的数据,仅仅是一个品牌字段的差别而已,所以被理所固然的设计成了同一张表。上层逻辑实现的时候使用了大量的if-else语句来区分各类品牌的购买流程,结果使得多条彻底不一样的业务线糅合在同一个上下文里,后来的开发很是容易在这里错改、漏改代码。若当初使用的是领域建模,则不一样的购车流程会天然的被划分到各类不一样的用户场景,即便它们在数据结构上看起来基本相同,也会被识别为两个独立的上下文,这就会使得将来划分服务时可以更加容易。
最后,ER图设计的架构会使得系统的模块之间倾向于使用数据库集成,而非API集成。在ER图中没有明确划分模块和表的全部权关系,全部的数据表对全部的模块都是可见的,假若不加额外约束,各个模块便都会轻易的读写其中的内容。数据集成并不会直接致使服务没法拆分,但因为数据的全部权不清晰,十分容易引起的意想不到的情况。仍是举销售平台的例子,在ER图建模获得的模型中,有一张与购买记录相关的表。在一次销售业务的代码更新中,对购买结果的增长了字段,在测试过程当中没有发现问题,结果上线几天之后,因为售后服务也在修改这个表而致使出现了脏数据,形成难以排查的故障。
固然,咱们并不是要彻底否定ER图建模的价值,只不过经过数据库角度创建模型的过程容易倾向于设计出庞大的单体应用,于是不太适应于服务划分的目的。
端到端的划分服务
在拆服务时要端到端的划分,这是咱们在设计微服务时常常听到的一句话。端到端的划分,指的是一个服务负责一个业务领域,这个功能领域的全部逻辑、数据都归它管,这有利于微服务的数据治理。与之相对的是MVC那样的横向服务划分,将全部数据归一块,全部逻辑归另外一块,特别是在跨团队管理的状况,横向划分服务会带来十分高昂的协调和联调成本。
道理没必要多说,仍是讲个例子吧。在汽车销售平台的最初架构中,使用了典型的MVC三层结构,因为人比较多,分红了前台组、后台组,就时不时要出现新开发一个功能,一动底层数据结构,结果上层的另外一个不相干页面挂了,一查发现原来那这个页面间接的用了同一个数据模型。比较典型的例子是,有一回负责后台的小组调整了汽车销售服务里面的汽车参数信息相关的对象结构,结果一上线,销售功能正常,试驾服务的页面挂了。原来是试驾功能的前端开发人员在处理汽车信息时候,看到销售模块有现成功能,就直接拿来复用了。这即是服务上下层分团队开发致使的问题。
此外,横向划分服务也不利于系统的开发效率的提高。不一样业务服务对功能发布频率的的需求是不同的,好比试驾平台常常推出新的优惠促销活动,须要尽快进行一次版本更新,于此同时售后服务有一个须要和第三方联调的功能也已经差很少完成并提交到代码仓库了,但因为须要协调第三方系统的时间,最近还不可以上线,此时两边的业务主管就要开掐了。相似这样的状况其实常常发生,一般使用特性分支、特性开关等流程或者技术手段可以规避一部分业务开发进度不一致的风险,但若要从根本上解决这种问题,仍是须要端到端的按业务来划分服务。
最后,横向划分模块对于问题的追踪调试也不友好,几乎每次事故调查老是要穿插涉及在几个团队之间不停的协调开会,由于一个完整业务流老是要贯穿先后几个层的功能。
须要指出的是,端到端划分服务并不是是说服务与服务之间都是平级的。实际上,服务之间能够再聚合成更高层级的组合服务的,以及在最顶端的API Gateway也能够算是一类服务。只不过,在核心业务层的这些服务,每一个都单独提供了某项特定的业务价值。
识别核心业务服务
微服务的架构一般并不是是一开始就重头设计出来的,而是先有整块的单体架构,随着业务的复杂度上升,才逐步拆分出来。业务领域建模除了能用来指导适当的服务划分,另外一个重要的做用是让工做聚焦到核心的业务服务中。
不管多复杂系统都是为特定业务价值而存在的。在系统的实现中必然会存在与核心业务最相关的部分、辅助核心业务的部分、和非核心业务关系的部分。它们在领域驱动设计的术语中称为“核心域”、“支撑子域”和“通用子域”。在进行领域建模的时候就应该顺便识别出系统里的关键领域。将系统的业务领域罗列出来而后划分出重要性,这件事情听起来彷佛是画蛇添足,甚至有点荒唐,但对于复杂系统,实际去作这件事带来的价值可能远比它看起来更大。
首先,当咱们将一个复杂的系统的各类业务仔细清点到台面上之后,获得的列表每每会比许多人最初想象得长得多,它能提醒咱们系统的复杂度是否已通过高了。其次,列举业务的过程也是开发者与业务人员沟通的过程,来自不一样业务线的表明也许会为某部分业务的价值点发生争执,或是提供一些许多开发人员此前并不了解的细节信息,将这些问题当面讨论清楚并不是什么坏事。此外,当咱们真的去仔细思考一个业务系统的核心价值时,也许会得出令人意想不到的结果。
继续举例子,在汽车的销售和售后平台中,经过业务建模,能够划分出许多子领域:
• 在线购车(渠道A)
• 在线购车(渠道B)
• 在线购车(渠道C)
• 试驾预定
• 售后服务
• 用户反馈
• 订单系统
• 促销活动
• 车辆市价信息
• 车辆参数信息
• 4s店信息
• 用户信息管理
• …
说明:这里渠道指的是销售平台对接的一个第三方系统,其中每一个渠道能够对应多个汽车品牌
而后咱们要从中识别出业务中的核心域。必须强调,服务领域划分仅仅是表明服务对系统关键业务贡献的价值,处于核心域中的那些服务应该是该系统业务成功的主要促成因素,从战略层面讲,企业应该在本身的核心领域上具备必定壁垒优点。通过讨论,开发者和业务人员最终得出让人大跌眼镜的结论,核心域部分的服务只有“试驾预定”和“售后服务”,由于这两项才是该企业最具竞争力的业务。而看起来十分重要的“在线购车”服务,因为并不具备特别的行业竞争优点而被划到了支撑子域中。正确的服务划分定位将对系统将来的发展策略产生积极的影响。
在这个系统中的“车辆市价信息”、“车辆参数信息”、“4s店信息”等服务都被划归到了通用子域。在通用子域中的服务并不是最没有价值或是复杂度最低,而只是说明系统在这些服务领域中一般不具优点,所以这部分功能彻底能够考虑外包开发或者购买第三方的现成服务。
使用符合业务结构的API
前面介绍业务建模的时候咱们强调了使用“API集成”的必要性,以及它的反面形式“数据库集成”所带来的问题。在微服务的架构中,服务的技术选型多是异构的,API的实现也会各有不一样。除了Web应用比较流行的RESTful标准,还有像SOAP、ProtolBuf、MessageQueue等不一样的协议与格式标准,它们均可以被做为服务之间通讯的API。不一样的API设计对服务使用的体验差异会很大,除去技术缘由对API协议的倾向性,在设计和评价API方面依然有许多值得注意的地方。
一个好的API设计应该在接口的元数据中向用户提供尽量多的有意义的业务信息,这里指的元数据包括例如API的名称、参数、标签等等用户在不须要专门查看文档就能够看到的内容。一般在各类不一样的API协议里,总会存在一个类似的概念,就是目标路径。好比RESTful的URL地址,ProtolBuf的消息类型嵌套结构,MessageQueue名称里用斜线划分Topic路径的惯例等。以RESTful标准为例,能够试比较下面两种URL地址的差别。
• 第一种:/users/123/orders/123
• 第二种:/orders?id=123&user_id=123
显然前一种地址包含的信息更多,它告诉了访问者订单(orders)是属于用户(users)这个实体中的一个子实体,而且包含了一个潜在业务规则,即若是ID为123的这个用户不存在了,那么查询他下面的全部订单信息也是不具备业务意义的。实体之间的嵌套关系能够经过领域建模过程当中的聚合识别。固然,这种URL结构有时会致使很长的API路径,但相比它所带来的业务语义,咱们认为仍是值得的。
相似的语义化例子还有例如:
• PUT /services/questions/123
• PUT /services/questions/123/comments/123
这是售后服务部分的两个API,分别用来更新提问的内容和提问的评论内容,它们也是按层级组织的。
有些通讯协议中还提供了其余能够代表业务语义的元素。好比RESTful中使用GET/POST/PUT操做语义(查询/建立/修改),以及HTTP返回值中的语义信息等。在实际设计API时充分利用这些协议特性,可以使服务变得更加易用。
尾声
在这篇文章里,咱们列举了一些微服务划分的实践中常见的反例和值得注意的问题,但愿能为读者设计微服务架构时扫清一些阻碍。实际上,本文中提到的许多概念,包括微服务和领域驱动设计,服务的划分都只是其中的冰山一角,在冰水之下还有更多的内容值得咱们在实践中去探索。