第四章 集成

4.1寻找理想的集成技术

集成是微服务相关技术中最重要的一个,做得好,你的微服务可以保持自治性,独立地修改和发布他们;但如果做得不好的话会带来灾难。

4.1.1避免破坏性修改

应该尽量避免服务提供者的修改导致消费方也随之发生改变。比如,如果一个微服务在一个响应中添加了一个字段,那么已有的消费方不应该受到影响。

4.1.2保证API的技术无关性

保持开放心态的人一定会喜欢微服务,因为微服务特别提倡服务之间的通信方式的技术无关性!这就意味着,不应该选择那种对微服务具体实现技术有限制的集成方式。
HTTP协议的流行,前端技术的流行,跨平台,兼容,生态开放,这些特性都起到了巨大的作用。现在,如果还用限定性的技术来做系统,后边一定会遇到很多痛苦的问题,甚至刚开始就会遇到。一位同事关于跨平台的例子,曾经的U8的关于跨平台的例子,拐着弯儿实现和直接到达。

4.1.3使你的服务易于消费方使用

让你的服务便于消费方使用。以前遇到过这样来方便消费方使用的办法---提供客户端库,这样在传统软件行业也许值得提倡,但是在微服务架构下绝对不应该被提倡,因为它会造成耦合的增加。(当年HTTP客户端的事儿,软件领域是OK的,微服务领域,除非定位是SDK,否则就是不OK)

4.1.4隐藏内部实现细节

我们不希望消费方和服务内部实现的细节绑定在一起,因为这会增加耦合。所以,所有倾向于暴露内部实现细节的技术都不应该被采用。

4.2为用户创建接口

4.3共享数据库

业界所见到的最常见的集成形式就是“数据库集成”。这种方式看起来非常简单,而且可能是最快的集成方式,这也是它这么流行的原因。


image.png

首先,这使得外部系统能够查看内部实现细节,并与其绑定在一起。各个服务各自对表结构的修改很容易导致其他服务的不可用,每次修改都需要做大量的回归测试来保证功能的正确性。
其次,因为与数据库绑定在了一起,所以需要使用一个合适的驱动。一段时间后,可能想要更换数据库(关系->非关系),很难替换到已经使用了很久的数据库。再见,松耦合。
最后,哪个服务都可以修改数据库,如果有一些相同的修改逻辑排布在不同的服务中,当修复一个bug时,你需要修改多个地方,然后对这些修改的服务分别部署。再见,松耦合,再见,微服务。

4.4同步与异步

同步通信听起来合理,因为可以知道事情到底成功与否。异步通信对于运行时间比较常的任务来说比较有用,否则就需要在客户端和服务器之间开启一个长连接,而这是非常不实际的。
同步:请求/响应
异步:请求,注册一个回调,服务端操作结束后,调用回调。
基于事件的系统天生就是异步的,整个系统都很聪明,业务逻辑不会集中于某个核心大脑,而是平均地分布在不同的协作者中。基于事件的协作方式耦合性很低。客户端发布一个事件,但并不需要知道谁或者什么会对此作出响应。你可以在不影响客户端的情况下对该事件添加新的订阅者。

4.5编排与协同

image.png
image.png

编排:让客户服务作为中心大脑,创建时,它会跟积分账户,电子邮件服务以及邮政服务通过请求/响应的方式进行通信。假如使用的是同步的请求/响应模式,我们甚至能知道每一步是否都成功了。

缺点
会让少量的服务成为“上帝”服务,而与其他打交道的那些服务通常都会沦为贫血的,基于CRUD的服务。


image.png

协同:仅仅从客户服务中使用异步的方式触发一个事件,该事件名可以叫做“客户创建”。电子邮件服务,邮政服务以及积分账户可以简单地订阅这些事件并且做响应的处理。
协同的方式可以降低系统的耦合度,并且你能更加灵活地对现有系统进行修改。使用协同方式,在这种方式下,每个服务都足够聪明,并且能够很好地完成自己的任务。

不同的场景选择不同的方式,所以,需要了解不同技术的实现细节,从而更好地做出选择。

针对请求/响应方式,可以考虑两种技术:RPC(Remote Procedure Call,远程过程调用)和REST(Representational State Transfer,表述性状态转移)。

4.6远程过程调用

RPC的种类繁多,其中一些依赖于接口定义(SOAP,Thrift,protocol buffers等)。
有很多技术 本质上是二进制的,比如Java RMI,Thrift,protocol buffers等,而SOAP使用XML作为消息格式。有些RPC实现与特定的网络协议相绑定(比如SOAP名义上使用的就是HTTP)。根据自己的使用场景来选择不同的网络技术。

4.6.1技术的耦合

有些RPC机制,如Java RMI,与特定的平台紧密绑定,这对于服务端和客户端的技术选型造成了一定限制。Thrif和protocol buffers对于不同语言的支持很好,从而在一定程度上减小这个问题的影响。

4.6.2本地调用和远程调用并不相同

RPC的核心想法是隐藏远程调用的复杂性。但是很多RPC的的实现隐藏的有些过头了。简单地把一个本地的API改造成为跨服务的远程API往往会带来问题。开发人员会在不知道该调用是远程调用的情况下对其进行使用。
封装和隐藏带来的效率和生产力不必说,但同时也带来了“无知”,这也是为什么很多公司注重员工对源码的研究能力和探究兴趣。

分布式计算中一个非常著名的错误观点就是“网络是可靠的”。

4.6.3脆弱性

生产者和消费者共用模型,如果服务端修改了模型,那么,对象反序列化的时候就会出问题。需要把生产者和消费者的部署绑定在一起。

4.6.4RPC很糟糕吗

如果你决定要选用RPC这种方式的话,需要注意:不要对远程调用过度抽象。
RPC是请求/响应协作方式中的一种,相比使用数据库集成的方式,RPC显然是一个巨大的进步,但是,我们还有其他的选择。

4.7REST

REST是RPC的一种替代方案。
REST本身并没有提到底层应该使用什么协议,尽管事实上最常用的是HTTP。HTTP的一些特性,比如动词,是的在HTTP之上实现REST要简单的多,而如果使用其他协议的话,就需要自己实现这些特性了。

4.7.1REST和HTTP

REST架构风格声明了一组对所有资源的标准方法,而HTTP恰好也定义了一组方法可供使用。GET使用幂等的方式获取资源,POST创建一个新资源。
HTTP也可以用来实现RPC,比如SOAP就是基于HTTP进行路由的,但不幸的是它只用到了HTTP很少的特性,而动词和HTTP的错误码都被忽略了。

4.7.3JSON,XML还是其他

目前来说,JSON更加流行。
JSON也有一些缺点。XML使用链接来进行超媒体控制。JSON标准中并没有类似的东西。
JSON的流行与互联网的流行和普及分不开,但XML也有其适用的场景。比如,很多地方的导入导出就用的XML,iUAP设计器的导入导出就用的是XML。

4.7.4留心过多的约定

我们很容易把存储的数据直接暴露给消费者,那如何避免这个问题呢?
一般先设计外部接口,等到外部接口稳定之后再实现微服务内部的数据持久化。在此期间,可以简单地将实体持久化到本地磁盘的文件上。这并非长久之计,但这样做可以保证服务的几口是由消费者的需求驱动出来的,从而避免数据存储方式对外部接口的影响。
设计的一些事儿,可以从全局出发,可以为很多的场景做预备,扩展性,松耦合等等,但也同时要避免过度设计~

4.7.5基于HTTP的REST的缺点

虽然HTTP可以用于大流量的通信场景,但对于低延迟通信来说并不是最好的选择。相比之下,有一些构建于TCP(Transmission Control Protocol,传输控制协议)或者其他网络技术之上的协议更加高效。比如WebSockets更加高效,在初始的HTTP握手之后,客户端和服务端之间就仅仅通过TCP连接了。

对于服务和服务之间的通信来说,如果低延迟或者较小的消息尺寸对于你来说很重要的话,那么一般来讲HTTP不是一个好主意。你可能需要选择一个不同的底层协议。

有些RPC的实现支持高级的序列化和反序列化机制,这部分可能会成为服务端和客户端之间的一个耦合点,因为实现一个具有容错性的读取器不是一件容易的事情,但从快速启动的角度来看,它们还是非常有吸引力的。

综上,基于HTTP的REST仍然是一个比较合理的默认选择。

4.8实现基于事件的异步协作方式

4.8.1技术选择

微服务发布事件的机制和消费者接收事件的机制。
像RabbitMQ这样的消息代理能够处理上述两个方面的问题,而且有很好的可伸缩性和弹性,但也同时会增加开发流程的复杂度,需要一个消息系统(即消息代理)才能开发以及测试服务,也需要额外的机器和知识来保持这些基础设施的正常运行。但是,一但做好这些,它会是实现松耦合,时间驱动架构的一种非常有效的方法。
但是,要注意一点:尽量让中间件保持简单,而把业务逻辑放在自己的服务里。

4.8.2异步架构的复杂性

异步的问题:
一个耗时的异步请求/响应,需要考虑响应返回时需要怎么处理。

  • 返回到发送请求节点,如果节点服务停止了怎么办?
  • 不返回发送请求节点,是否需要把信息事先存到某个地方,以便后续处理
    对于习惯进程间同步调用的程序员来说,使用异步模式也需要思维上的转换。

注意设置作业最大重试次数。必要时需要实现一个消息医院(或者叫死信队列),所有失败的消息都会被发送到这里。可能还需要做一个界面来显示这些消息,如果需要的话还可以触发一个重试。

还要注意,需要确保各个流程有很好的监控机制,并考虑使用关联ID,这种机制可以帮助你对跨进程的请求进行追踪。

4.9服务即状态机

服务应该根据限界上下文进行划分。每个服务应该拥有与这个上下文中行为相关的所有逻辑(高内聚)

4.10响应式扩展

响应式扩展(Reactive extensions,Rx)提供了一种机制,你可以把多个调用的结果组装起来并在此基础上执行操作。调用本身可以是阻塞或者非阻塞的。

RxJava
很多RX实现都在分布式系统中找到了归宿,因为调用的细节被屏蔽了,所以事情也更容易处理。其漂亮之处在于,可以把多个不同的调用组合起来,这样就可以更容易地对下游服务的并发调用做处理。
https://zhuanlan.zhihu.com/p/27678951
https://juejin.im/post/5b8f536c5188255c352d3528
https://www.jianshu.com/p/0cd258eecf60

4.11微服务世界中的DRY和代码重用的危险

DRY(Don't Repeat Yourself),如果不遵守,可能会导致代码规模变大,从而降低可维护性。
经验:在微服务内部不要违反DRY,但在跨服务的情况下可以适当违反DRY,服务之间引入大量的耦合回避重复代码带来更糟糕的问题。“但这的确是一个值得进一步探索的问题!!!!”

客户端库
如果你想要使用客户端库,一定要保证其中只包含处理底层传输协议的代码,比如服务发现和故障处理等。千万不要把与目标服务相关的逻辑放到客户端库中。

4.12按引用访问

数据一直在变动,你现在拿到的这个实体不一定就是之前的那个实体,这个实体的很多属性可能都已经发生了变化,时间越长,概率越大。很多时候,为了保证数据的实时性,我们可能拿到的应该是一个引用,而非副本。这样,在需要查看时,获取最新的信息,这样就可以保证时效性。但这同时带来了查询的压力。这时,可以引入“时效性”缓存,这样,可以在请求压力和数据时效性之间达成较好的平衡。
上边说的不是一定的,要根据不同的业务场景采用不同的方案。

4.13版本管理

4.13.1尽可能推迟

减小破坏性修改影响的最好办法就是尽量不要这样做。
Martin Fowler->容错性读取器 http://martinfowler.com/bliki/TolerantReader.html
客户端尽可能灵活地消费服务响应。系统中的每个模块都应该“宽进严出”。在请求/响应的场景下,该原则可以帮助我们在服务发生改变时,减少消费方的修改。

比如对实体序列化的控制,序列化id.

4.13.2及早发现破坏性修改

针对所有消费者的测试,尽早发现问题。
契约的定义,当服务提供者和服务消费者约定好接口后,契约就已经形成,当契约不更改的情况下,服务提供者的所有提交修改都必须符合契约的定义。

4.13.3还有那个语义化的版本管理

语义化版本管理:MAJOR.MINOR.PATCH MAJOR的改变意味着其中包含向后不兼容的修改;MINOR的改变以为这有新功能的增加,但应该是向后兼容的;最后,PATCH的改变代表对已有功能的缺陷修复。

4.13.4不同的接口共存

image.png

希望微服务可以独立于彼此进行发布,有一种办法是同时包含新老接口的版本。这样,既更新了接口,也给消费者时间做迁移。一旦所有的消费者不再访问老的接口,就可以删除该接口以及相关的代码。
同时维护多个版本接口不推介这样,因为维护负担重。为了使其更可控,可以在内部把素有对V1的请求进行转换处理,然后去访问V2,继而V2再去访问V3,使用这种方式后,以后需要删除哪些代码就比较清楚了。

场景:有团队正在使用你们团队编写的当前接口,而且顾不上升级。

4.13.5同时使用多个版本的服务

image.png

短期内同时使用两个版本的服务是合理的,尤其是当你做蓝绿部署或者金丝雀发布时。如果升级消费者到新版本的时间过程,就该考虑是否应该在微服务中暴露两套API的做法了。

4.14用户界面

4.14.1走向数字化

从组合的角度来考虑用户界面,如果把我们提供的能力看成是不同的绳索,组合就是把它们编织起来,可以为桌面应用程序,移动端设备,可穿戴设备的客户提供不同的体验。

React的成功不是偶然,Redux更加确定了这一点的必然性。

4.14.2约束

桌面Web应用---需要考虑与用户浏览器以及屏幕解析度相关的约束
移动端----移动网络的带宽,交互方式可能会导致电量消耗过快,从而导致客户流失
尽管我们的核心服务可能是一样的,但需要对不同的场景的约束进行考虑。

4.14.3API组合

image.png

UI如何聚合呢?使用API入口(gateway)可以很好的缓解这一问题,在这种模式下多个底层的调用会被聚合成为一个调用。

4.14.4UI片段的组合

image.png

让服务主动暴露出一部分UI,然后只需要简单地把这些片段组合在一起就可以创建出整体UI。可以使用类似服务端模板的技术来实现拼装。但也有问题:

  • 保证用户体验的一致性比较困难
  • 原生应用和胖客户无法消费服务端提供的UI组件(展示没问题,想使用其中的很多功能与原生应用交互却很难)
  • 服务提供的能力难以嵌入到小部件或者页面中,联动也不容易做。

4.14.5为前端服务的后端

image.png

问题在于,如果该入口变得太厚,包含的逻辑太多,就会难以维护,它们会被逐渐交由单独的团队来管理,并且因为它们变得太厚,很多功能的修改都会导致这部分代码的修改。


image.png

一个后端只为一个应用或者用户界面服务(BFF(Backends For Frontends),为前端服务的后端)。允许团队在专注于提供给定UI的同事,也会处理与之先骨干的服务端组件。

4.14.6一种混合方式

一个组织可能会选择基于片段组装的方式来构建网站,但对于移动应用来说,BFF可能是更好的方式。关键是要保持底层服务能力的内聚性。

4.15与第三方软件集成

组织经常面临的问题:

  • 组织对软件的需求几乎不可能完全由内部满足
  • 自研对于大多数商业公司来说是非常低效的。
    应该自己做,还是买?
    如果某个软件非常特殊,并且它是你的站战略性资产的话,那就自己构建;如果不是这么特别的话,那就购买。

4.15.1缺乏控制

如何与现有购买的产品集成?厂家决定的。使用什么语言进行扩展?厂家决定的。能否引入针对该工具配置文件的版本控制?厂家决定的。
---尽量把集成和定制化的工作放在自己能够控制的部分。

4.15.2定制化

销售口中的深度化定制一定要小心。针对一个巨无霸,N久年前的工具做定制化开发,你会明白什么是绝望。。。


image.png

扩展开发的一些思考:
1.支持扩展,但是需要绕一大圈,甚至需要去弄懂计算机基础的很多东西,需要会N多年前的老技术,绕大圈解决问题完全是情非得已,而不应该成为第一拨人的首选。
2.简单,易懂,直接,这应该是扩展支持该有的姿态。

4.15.3意大利面式的集成

服务之间的集成是一件非常重要的事情,理想情况下应该存在一些为数不多的标准化集成的方式。
很多时候,我们总是喜欢将就一下,从第一个人将就到最后一个人不得不将就。很多时候,我们面临的不是这样可以做到,那样也可以做到的问题,而是标准的问题。把你丢进一个集成方式有十几种的组织中,你再牛逼也觉得苦逼。

4.15.4在自己可控的平台进行定制化

SAAS当然是有用的,但不适用于从头开始构建的系统。关键是把事情移到自己可控的部分做。


image.png

image.png

如果我没理解错的话,上图应该是峰哥当年给U8云化指的出路。
理出来该有的基础接口(现代化,精简化,清晰化,JSON化):
领域元数据接口,表单元数据接口,参照接口,增删改查公共接口。这是我们当年走过的路子。。。

4.15.5绞杀者模式

其实个人感觉这种方式自己玩就可以了,或者在领导不知道的情况下,自己在长久待的组织中一点一点啃。大多数情况下,你跟领导说我要一点一点绞杀,领导会跟你说“这事儿咱别干了,太慢”
绞杀者模式,绞杀者可以捕获并拦截对老系统的调用,进而决定把调用路由到遗留代码中还是导向新写的代码中。逐步对老系统进行替换。

老手和新手的区别

《Effective C++》作者Scott Mayer ,C++老手和 C++新手的区别就是前者手背上有很多伤疤


image.png

过去的事情如果就这么简单的过去了,那未来只会更糟 --《驴得水》


image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容