您当前的位置:首页 > 电脑百科 > 程序开发 > 架构

细数软件架构中的解耦

时间:2020-11-23 11:21:30  来源:  作者:

解耦的对立面是耦合,耦合是指阻碍变化的依赖;解耦是要在依赖的基础上,做到应对可能的变化。

架构的定义

架构是软件方法学的范畴,它解决的是软件组织的问题,不解决软件算法的问题。两者的区别可用下图的积木做个类比:

算法就像一个个的积木块,比如绿色的圆柱,蓝色的三角,红色的方块等。而架构则是把各种积木块,组装成一个城堡,一辆小火车。为搭建这个城堡或小火车,架构师脑子里得有张图纸,图纸里既要定义需要哪些形形色色的积木块,又要考虑如何将它们组装起来。这工作很像建筑师,英文也的确叫 architect。

这样类比,很容易让不太理解技术的企业家们陷入误区,会觉得架构师要比算法工程师更厉害?其实不然,这是两个细分领域的才能。不知道您注意到小火车车头上的烟囱没?它是一个像鸡腿菇一样的弧线造型,浇灌出这种造型的模子,要比三角形和方块形要难很多,它需要更深奥的几何学的支撑,这可以形象的看做是算法工程师解决的问题。

架构的意义

架构解决软件组织的问题,它能给企业创造什么价值?换句话说,好的软件组织,跟差的软件组织,从商业价值创造的角度,有什么不同?笔者以为架构的价值体现在可用性和敏捷性两个角度,但今天要讲的是敏捷性。敏捷性指的是快速、低成本、高质量地应对扩张市场的差异化需求。企业在初创期积累了不少软件资产,这些资产在当初的市场环境下,已被论证取得了市场业绩。但是伴随着企业扩张,市场会更加精细化、场景化,这些都会给我们的软件提出新的需求,企业需要借助前些年在这个领域积累的先发优势,一方面快速占领细分的市场;另一方面复用曾经积累的资产,发挥资产的规模经济效应。

比如京东电商,从高价值、标准化的 3C 数码起家,建立起自营电商模式;紧接着开始扩品,做低价值、但高频次、依然标准化的日用百货圈用户粘性;再做相对非标的服装发展女性用户和生态模式等,直指行业竞争的关键区;除了扩品还伴随着场景扩张,诸如 2B 企业业务、下沉市场拼购业务、泰国印尼国际业务等。供给角度的品类扩张,需求角度的场景扩张,构成了京东矩阵式垂直业务线。它们正是复用了零售中台的软件基础设施,才在一定程度上做到了快速扩张。

架构的灵魂

既然软件组织的价值如此重要,那么好的软件组织的标准是什么呢?又该如何做到呢?好坏的标准在解耦。解耦的对立面是耦合,耦合是指阻碍变化的依赖;解耦是要在依赖的基础上,做到应对可能的变化。依赖是必不可少的,依赖的本质是分工,正如亚当斯密的《国富论》论述的那样,分工有助于专业化、有助于提高效率。太抽象了!说了这么多,没讲清楚解耦是什么。的确,笔者也认为这样的解释只能让已经理解了的人再表示一次赞同,无法让原本不理解的人变得理解,这样毫无意义!我该如何诠释?事实上,很多真理是建立在归纳法基础上的。归纳法的好处是见得多了自然就会(归纳似乎是人脑的一种本能),比如诗词,只要熟读唐诗三百首,不会吟诗也会吟。不信你看,先来一篇叫“大漠孤烟直”的,没啥概念;再读一篇叫“空山新雨后”的,有点感觉了;最后“小桥流水人家”你自己就会了。如何写出点有意境的诗,你张口就来“床前明月光”,还不是自己写的?如果你去到草原晚上触景生情,即兴来上一句“明月篝火烤肥羊”,就能媲美“日照香炉生紫烟”了。所以笔者觉得,最好的方式就是细数那些软件架构中的解耦,让读者从铺陈式的实例中,自己找感觉。

笔者分 3 类 6 组(每类分进程内的应用层和进程间的架构层)给大家举例:

细数软件架构中的解耦

 

外加中间的 Naming 解析与 Proxy 代理融合的 CNAME 别名,总共 7 个案例。

中间层映射

中间层映射的设计理念是当 A 对 B 有依赖时,A 不要直接依赖 B,而是抽象一个中间层,让 A 依赖中间层,再由中间层映射到 B,从而当 B 变成 C 时,不用修改 A,只用调整中间层的映射关系。中间层映射,在应用层表现为面向接口动态绑定,在架构层表现为 Naming 解析动态绑定。

应用层 - 面向接口动态绑定

面向接口编程的核心思想是“先想清楚做什么,再想让谁来做”。什么叫想清楚了做什么?就是用接口的形式,描述输入什么,输出什么;但接口更多描述的是语法层面,语义层面的刻画还需配合单元测试及其断言(技术上叫 Test Driven),还有文档。这跟企业家们常读的《高效能人士的 7 个习惯》里面讲的“以终为始”,思想上如出一辙。让谁来做?就涉及到运行时动态绑定。比如下图:

细数软件架构中的解耦

 

JAVA 面向对象的语言里,使用方通过 Provider 接口 Response doService(Request r) 来对外刻画它的招标文件。然后三个供应方,LocalProvider、RemoteProvider 和 AsyncProvider 来应标。使用方只使用 Provider 接口,至于它跟哪个具体的 Provider 绑定,完全可以在“采购”时刻动态替换。

面向接口动态绑定的解耦,体现在使用方把依赖的服务抽象为一个接口,依赖这个抽象的接口,而不依赖于具体的服务提供者,以便应对服务提供者变化的可能性。

架构层 -Naming 解析动态绑定

细数软件架构中的解耦

 

上图是域名服务 DNS 的示意流程。客户端并不直接通过 IP 地址来访问 Provider#A 或 B,而是先询问 Naming 服务,并依据返回的服务列表,再访问 Provider#A 或 B。如果某个 Provider 故障了,可以替换转移到其他的 Provider。出于性能考虑,也可以在客户端把 Naming 的结果缓存起来,并配个缓存更新机制。

基于 ZooKeeper 的应用层名字服务,思想上类似 DNS。不同的是,它基于 TCP 长链接来实现 Server Push,可及时刷新服务列表。

Naming 解析动态绑定的解耦,体现在使用方把依赖的对象或网络进程,抽象为一个名字,名字代表的具体服务提供者则通过 Lookup 机制返回,进而做到如果提供者有变化,只要改变 Lookup 的结果,无需改变使用方代码。

前后节植入

前后节植入的设计理念是服务器是流程的集合,流程是环节的序列。改变一个流程的行为,可以通过在其前后植入一个新环节来实现。前后节植入,在应用层表现为 Chain 拦截模式,在架构层表现为 Proxy 代理模式。

应用层 -Chain 拦截模式

细数软件架构中的解耦

 

上图是 Strtus2 的架构,每个 Action 的执行,都会被包裹在一系列 Interceptor 里面,形成一条处理链 Chain,每个 Interceptor 会进行 PreHandler 和 PostHandler 处理。这里的 Interceptor 可以增加、删除或替换,以此实现可拓展性。比如可以在 Interceptor 里做鉴权、日志、性能统计、限流等。

Chain 插拔的动态绑定,通过增删替 Interceptor,把过去 URL 与 Action 的 1:1 的处理关系,转变成了 M:N 的处理链。一类请求(某个 URL),可以被多个 Interceptor 处理;一个 Interceptor 也可以处理多类请求。

顺便说一下,Strtus2 这里说的“动态绑定”,是配置相对硬编码而言的。严格意义上,这里的绑定是编译期的,不是运行期的,是静态的绑定。类似的架构还有 Spring AOP 和 Servlet Filters 机制。

架构层 -Proxy 代理模式

细数软件架构中的解耦

 

上图是一个 Proxy 架构模式,这个应用极其广泛。比如 HTTP 的 Nginx,SQL 的 Apache Calcite,memcached 和 redis 的 twitter/twemproxy。为什么?因为 Proxy 对于 Backend 而言就是流量入口,是中间人,能扮演架构层面的 AOP 机制,可拓展性非常强。

当一个请求过来后,刚开始 Proxy 转发给 Backend#A。但是业务发展了,Proxy 也可以转发给 Backend#B 以实现负载均衡,更重要的是 A 和 B 还可以不同的版本,以实现灰度发布。还可以植入 PrePlugin 和 PostPlugin:

在 PrePlugin 里可以做权限控制、流量控制、请求改写、缓存加速、恶意流量拦截、PV 统计、性能 Profile、ChaosMonkey 混沌事件植入等等。

在 PostPlugin 里,还可以做响应报文改写,安全加密(后端不用考虑数据安全,对外时统一加密处理)、压缩加速等等。

两者融合的实例 -CNAME 别名

细数软件架构中的解耦

 

上图是一种混合模式:既有 Naming 解析,又有 Proxy 代理。而且 Naming 服务,为了支持可拓展,还引入了父子层级,末端的 Naming 服务,完全可以委托给上一层级的 Naming 服务。

在 DNS 里面,我们经常会看到 www.example.org 的域名解析,CNAME 别名到 www.example.org.cdnprovider.com (它是 cdnprovider.com 的子域名),这样客户端不用修改,依然访问的是 www.example.org ,但是对应的后端服务,却不再是直接访问 Provider#A 或 B,而是中间植入了 CNAME Proxy,再由 Proxy 依据 Plugin 的决策,是否转发给问 Provider#A 或 B。

这个设计太棒了!它使得商业公司 cdnprovider.com 给 www.example.org 提供 CDN 服务时,完全是零侵入,不需要修改任何一段代码,只需要在域名服务商那修改 www.example.org 的域名解析,这个操作代表 www.example.org 同意 cdnprovider.com 为他们提供 CDN 服务,代表授权。这一切,都源于基于 Naming 解析的动态绑定实现的解耦。同样的,除了 CDN,我们的恶意流量清洗、灰度发布、性能分析等都可以采用这种方式,实现零侵入插拔。

事件流订阅

事件流订阅的设计理念是将瞬间的过程化调用转变成可回放的指令,对指令的响应可以不用再预定义。事件流订阅,在应用层表现为 Mediator 中介模式,在架构层表现为 Broker 消息模式。

应用层 -Mediator 中介模式

A 直接调用 B,意味着 A 对 B 产生了强依赖。当然我们可以通过面向接口编程,把这个依赖降低,降低到只依赖接口,不依赖实现。简单说,我们只依赖对事情的处理结果,不依赖于如何实现这个处理结果。

但是这还不够,因为我们还依赖了接口,接口意味着对处理语义的刻画。现实中有些情况,连语义的描述都要发生变化,也就是接口都要发生变化,如何进一步解耦呢?如下图:

细数软件架构中的解耦

 

A 不直接调用 B,而通过中介 Mediator,解耦两步:

  • 先由 A 调用 Mediator: A 持有 Mediator 的引用,执行 Mediator 的方法,即 mediator.publish(e)。
  • 再由 Mediator 调用 B: 为了解耦 Mediator 对外界的依赖,我们用面向接口 EventHandler 来实现依赖反转。让 B 来实现 EventHandler,当然如果 B 已经存在,或更有话语权,依然应该遵循依赖反转的原则,只不过 Mediator 模式的推进方可以再实现一个 Adaptor,来帮助既有的 B 适配到 EventHandler。

有了上述的设计模式后,具体的执行分三步:

  • 订阅:通过 mediator.subscribe(b) 把未来的事件处理提前注册到 Mediator。
  • 发布:A 向 Mediator 发布自己的事件。注意这个理念特别重要,A 仅仅发布发生了什么事情,A 并没有直接调用 B 声明对事情的处理。也就是 A 连对 B 的接口都不再依赖了!举个例子,比如新员工入职,刚开始要为员工办理磁条卡,只是办理磁条卡的供应商可能是甲,也可能是乙。这叫面向接口编程,但这还不够,因为随着公司的发展,现在新员工入职,有人脸识别了,不用再办磁条卡了,而是要登记人脸识别,另外员工福利更好了,对异地公干的新员工入职还会发放一笔安家费,这些都是之前的“接口”没有描述的。
  • 执行:当 mediator 收到 A 的事件后(A 调用了 mediator.publish(e)),mediator 会通过 EventHandler 来回调预先通过 mediator.subscribe(b) 注册的处理类。

上述 Mediator,有些局限性,对所有的 Event,只能有一种 EventHandler。如果我们把 Mediator 升级为一种通用的处理机制,一种平台,自然会有各种各样的 Event,自然会我们会对 Event 做个分类或分组。我们把 Event 的分类或分组,叫做 Topic;而把 Event 理解为 Topic 这个类里面的具体实例。并在 Mediator 里面维护,从 Topic 到 EventHandler 的一组处理器。如下图所示:

细数软件架构中的解耦

 

可以看到上述架构通过 Map<Topic, List> resolver 来维护从 Topic 到 EventHandler 的一组处理。为什么是 List,而不是 EventHandler 呢?为了更加灵活,比如上文提到的「现在新员工入职,有人脸识别了,不用再办磁条卡了,而是要登记人脸识别,另外员工福利更好了,对异地公干的新员工入职还会发放一笔安家费」。

架构层 -Broker 消息模式

细数软件架构中的解耦

 

上图的 Broker 模式,跟 Mediator 模式其实没有本质的不同,只不过 Broker 更加突出了借助消息中间件 MQ 实现异步。客户端提交一个委托,Broker 持久化完成,并回复 ACK,表示委托已收到。接着委托的消费处理,可以是离线的。通常需要支持 Group 机制:Group 内部多个 Instance 是负载均衡的,它们共同瓜分委托消息的处理;而 Group 间是冗余复制的,它们各自消费各自的,相互之间隔离,有助于实现业务可拓展性。

比如一个新员工入职,它产生一个“新人入职”事件,然后行政部门会为其准备工卡、财务部门会为其准备工资卡、HR 部门会为其缴纳社保。当然,随着公司业务发展,可能还会增加,比如业务部门的业务培训,风控部门的合规性培训等。

跟前面说的 Proxy 模式,相同点在于它们都是在架构层面实现可拓展性。不同点是,Proxy 模式支持的是 PreHandler 和 PostHandler;而 Broker 模式支持的是 MidHandler。

关于作者

作者李伟,研究微服务与大数据方向,擅长中台架构敏捷性课题。就职于京东零售,担任资深架构师,加入京东前曾就职于搜狐和百度。



Tags:解耦   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
解耦的对立面是耦合,耦合是指阻碍变化的依赖;解耦是要在依赖的基础上,做到应对可能的变化。架构的定义架构是软件方法学的范畴,它解决的是软件组织的问题,不解决软件算法的问题。...【详细内容】
2020-11-23  Tags: 解耦  点击:(132)  评论:(0)  加入收藏
大家好,欢迎来到设计模式专题,我们的主旨是介绍一些有趣好玩的设计模式。今天我们介绍的设计模式叫做命令模式(command),在这个模式下,我们可以实现do和undo的解耦,让使用方不用...【详细内容】
2020-10-16  Tags: 解耦  点击:(111)  评论:(0)  加入收藏
作者:人月神话,新浪博客同名简介:多年SOA规划建设,私有云PaaS平台架构设计经验,长期从事一线项目实践今天准备谈下微服务架构下各个微服务间如何解耦,以及对于已经紧耦合的微服...【详细内容】
2020-07-19  Tags: 解耦  点击:(61)  评论:(0)  加入收藏
一、前言”前后端分离“已经成为互联网项目开发的业界标杆,通过Tomcat+Ngnix(也可以中间有个Node.js),有效地进行解耦。并且前后端分离会为以后的大型分布式架构、弹性计算架...【详细内容】
2019-10-29  Tags: 解耦  点击:(99)  评论:(0)  加入收藏
▌简易百科推荐
为了构建高并发、高可用的系统架构,压测、容量预估必不可少,在发现系统瓶颈后,需要有针对性地扩容、优化。结合楼主的经验和知识,本文做一个简单的总结,欢迎探讨。1、QPS保障目标...【详细内容】
2021-12-27  大数据架构师    Tags:架构   点击:(3)  评论:(0)  加入收藏
前言 单片机开发中,我们往往首先接触裸机系统,然后到RTOS,那么它们的软件架构是什么?这是我们开发人员必须认真考虑的问题。在实际项目中,首先选择软件架构是非常重要的,接下来我...【详细内容】
2021-12-23  正点原子原子哥    Tags:架构   点击:(7)  评论:(0)  加入收藏
现有数据架构难以支撑现代化应用的实现。 随着云计算产业的快速崛起,带动着各行各业开始自己的基于云的业务创新和信息架构现代化,云计算的可靠性、灵活性、按需计费的高性价...【详细内容】
2021-12-22    CSDN  Tags:数据架构   点击:(10)  评论:(0)  加入收藏
▶ 企业级项目结构封装释义 如果你刚毕业,作为Java新手程序员进入一家企业,拿到代码之后,你有什么感觉呢?如果你没有听过多模块、分布式这类的概念,那么多半会傻眼。为什么一个项...【详细内容】
2021-12-20  蜗牛学苑    Tags:微服务   点击:(8)  评论:(0)  加入收藏
我是一名程序员关注我们吧,我们会多多分享技术和资源。进来的朋友,可以多了解下青锋的产品,已开源多个产品的架构版本。Thymeleaf版(开源)1、采用技术: springboot、layui、Thymel...【详细内容】
2021-12-14  青锋爱编程    Tags:后台架构   点击:(20)  评论:(0)  加入收藏
在了解连接池之前,我们需要对长、短链接建立初步认识。我们都知道,网络通信大部分都是基于TCP/IP协议,数据传输之前,双方通过“三次握手”建立连接,当数据传输完成之后,又通过“四次挥手”释放连接,以下是“三次握手”与“四...【详细内容】
2021-12-14  架构即人生    Tags:连接池   点击:(16)  评论:(0)  加入收藏
随着移动互联网技术的快速发展,在新业务、新领域、新场景的驱动下,基于传统大型机的服务部署方式,不仅难以适应快速增长的业务需求,而且持续耗费高昂的成本,从而使得各大生产厂商...【详细内容】
2021-12-08  架构驿站    Tags:分布式系统   点击:(23)  评论:(0)  加入收藏
本系列为 Netty 学习笔记,本篇介绍总结Java NIO 网络编程。Netty 作为一个异步的、事件驱动的网络应用程序框架,也是基于NIO的客户、服务器端的编程框架。其对 Java NIO 底层...【详细内容】
2021-12-07  大数据架构师    Tags:Netty   点击:(16)  评论:(0)  加入收藏
前面谈过很多关于数字化转型,云原生,微服务方面的文章。虽然自己一直做大集团的SOA集成平台咨询规划和建设项目,但是当前传统企业数字化转型,国产化和自主可控,云原生,微服务是不...【详细内容】
2021-12-06  人月聊IT    Tags:架构   点击:(23)  评论:(0)  加入收藏
微服务看似是完美的解决方案。从理论上来说,微服务提高了开发速度,而且还可以单独扩展应用的某个部分。但实际上,微服务带有一定的隐形成本。我认为,没有亲自动手构建微服务的经历,就无法真正了解其复杂性。...【详细内容】
2021-11-26  GreekDataGuy  CSDN  Tags:单体应用   点击:(35)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条