1。体系结构的定义体系结构是软件方法论的范畴。它解决的是软件组织问题,而不是软件算法问题。两者的区别可以用下图中的积木来类比:算法就像是一块一块地搭积木,比如绿色圆柱体、蓝色三角形、红色方块等,其结构就是将各种积木块拼装成一个城堡和小火车。为了建造这座城堡或一辆小火车,建筑师必须在他的脑海中有一个图纸。在绘图中,他不仅要定义需要哪些各种积木,还要考虑如何组装它们。这个工作和建筑师很像,英文确实叫architect。这样的类比很容易让不懂技术的创业者陷入误区。你觉得架构师比算法工程师厉害吗?其实不然,这是两个细分的天赋。不知道大家注意到小火车车头的烟囱了吗?它是像鸡腿菇一样的弧形。这种形状的模具倒出来比三角形和方形的要难得多。它需要更深刻的几何支撑。这可以看作是算法工程师。问题。2、架构的意义架构解决了软件组织的问题,它能为企业创造什么价值?换句话说,从商业价值创造的角度来看,好的软件组织和坏的软件组织有什么区别?笔者认为架构的价值体现在易用性和敏捷性两个角度,而今天我们要说的是敏捷性。敏捷性是指快速、低成本、高质量地响应不断扩大的市场的差异化需求。公司在起步阶段积累了大量的软件资产,这些资产在原有的市场环境下已经取得了市场业绩。但是随着企业规模的扩大,市场会更加精细化和场景化,这会对我们的软件提出新的需求。企业一方面需要利用过去几年在该领域积累的先发优势,快速占领细分市场;另一方面,可以对积累的资产进行再利用,充分发挥资产的规模效应。比如京东电商,从高价值、标准化的3C数码出发,建立自营电商模式;然后开始拓展产品,做低价值,高频,还是标准化的生活用品。相对非标准服装的女性用户和生态模式的发展,直接指向了行业竞争的重点领域;除了产品拓展,还伴随着场景拓展,比如2B企业业务、下沉市场团购业务、泰国印尼国际业务等。从供给角度拓展品类,从需求角度拓展场景,构成了京东的矩阵垂直业务线。正是因为对零售中台软件基础设施的复用,他们在一定程度上实现了快速扩张。3.架构的灵魂既然软件组织的价值如此重要,那么一个好的软件组织的标准是什么?以及如何去做?好的和坏的标准是脱钩的。解耦的反面是耦合。耦合是指阻碍变化的依赖;解耦是在依赖的基础上应对可能发生的变化。依赖是必不可少的,依赖的本质是分工。正如亚当斯密的《国富论》所讨论的,分工有利于专业化和效率化。太抽象了!说了这么多,也没说清楚什么是解耦。的确,笔者也认为,这样的解释只能让已经理解的人再次认同,不能让不理解的人理解,所以毫无意义!我应该如何解释它?其实很多道理都是靠归纳得出的。归纳法的好处是看多了就自然而然了(归纳好像是人脑的一种本能),比如诗歌,只要背诵300首唐诗,不用背诵就可以吟诗知道它。不信你看看,先来一篇《尘埃的沙漠》,没有概念;再看了一篇《空山新雨过后》,有点感触;最后,《小桥流水》你自己就知道了。怎么写出有意境的诗,张口就来《床前月色》,不是自己写的吗?如果晚上去草原感受风景,随口说一句“明月篝火烤肥羊”,堪比“日照炉出紫烟”。因此,笔者认为最好的办法是详细统计软件架构中的解耦,让读者从说明性的例子中找到自己的感受。作者分为3类6组(每类分为进程中的应用层和进程间的架构层)给大家举个例子:在Namingresolution和Proxy代理融合中间加上CNAME别名,共7例。中间层映射中间层映射的设计理念是当A对B有依赖时,A不直接依赖B,而是抽象出一个中间层,让A依赖中间层,再将中间层映射到B,这样当B变为ForC时,不需要修改A,只需要调整中间层的映射关系即可。中间层的映射在应用层表现为面向接口的动态绑定,在架构层表现为命名解析动态绑定。应用层——面向接口的动态绑定面向接口编程的核心思想是“先想好做什么,再想好为谁做”。想清楚该做什么是什么意思?就是用接口的形式来描述什么是输入,什么是输出;但是接口更多的是语法层面的描述,语义层面的描述需要结合单元测试和断言(技术上叫TestDriven),还有文档。这与创业者常读的《高效能人士的 7 个习惯》中的“以终为始”如出一辙。让谁做?它涉及运行时动态绑定。例如下图:在Java面向对象语言中,用户使用Provider接口ResponsedoService(Requestr)对外描述其投标文件。然后三个提供商LocalProvider、RemoteProvider和AsyncProvider应该出价。用户只使用Provider接口。至于具体绑定哪个Provider,可以在“购买”的那一刻动态更换。接口动态绑定的解耦体现在用户将依赖的服务抽象成一个接口,依赖这个抽象的接口,不依赖具体的服务提供者,以应对服务提供者变化的可能。架构层-命名解析动态绑定上图是域名服务DNS的流程示意图。客户端不直接通过IP地址访问Provider#A或B,而是先询问Naming服务,然后根据返回的服务列表访问Provider#A或B。如果一个Provider出现故障,可以将其替换并转移到另一个Provider。出于性能考虑,也可以将Naming结果缓存在客户端,配置缓存更新机制。基于ZooKeeper的应用层名称服务,在思想上类似于DNS。不同的是它基于TCP长链接实现了ServerPush,可以及时刷新服务列表。命名解决了动态绑定的解耦,体现在用户将依赖对象或网络进程抽象成一个名称,通过Lookup机制返回该名称所代表的具体服务提供者,这样如果提供者发生变化,只需更改Lookup结果而不更改消费代码。前后端植入的设计理念是服务器是进程的集合,进程是一个链接的序列。改变一个进程的行为可以通过在它前后插入一个新的链接来实现。前后段的植入展示了应用层的Chain拦截模式和架构层的Proxy代理模式。应用层-链拦截模式上图为Strtus2的结构。每个Action的执行都会被一系列的Interceptor包裹起来,形成一个处理链Chain。每个Interceptor都会进行PreHandler和PostHandler的处理。这里的Interceptor可以增删改换,实现可扩展性。比如鉴权、日志记录、性能统计、限流等都可以在Interceptor中完成。链插件动态绑定,通过Interceptor的增删改查,将以往URL与Action的1:1处理关系,转变为M:N的处理链。一类请求(某个URL)可以被多个Interceptor处理;拦截器还可以处理多种类型的请求。顺便说一句,这里Strtus2所说的“动态绑定”是相对于硬编码的配置。严格来说,这里的绑定是编译时的,不是运行时的,是静态绑定。类似的架构包括SpringAOP和ServletFilters机制。架构层——Proxy代理模式上图是一种Proxy架构模式,应用比较广泛。例如,用于HTTP的Nginx、用于SQL的ApacheCalcite、用于memcached和redis的twitter/twemproxy。为什么?因为Proxy是Backend的流量入口,是一个中间人,在架构层面可以充当AOP机制,扩展性非常强。当请求到来时,Proxy首先将其转发给Backend#A。但是随着业务的发展,Proxy也可以转发到Backend#B来实现负载均衡,更重要的是A和B还可以有不同的版本来实现灰度发布。PrePlugin和PostPlugin也可以植入:在PrePlugin中,可以在PrePlugin中进行权限控制、流量控制、请求重写、缓存加速、恶意流量拦截、PV统计、性能剖析、ChaosMonkey混沌事件植入等。在PostPlugin中,还可以做响应消息重写、安全加密(后端不需要考虑数据安全,对外时加密)、压缩加速等。两者融合的例子——CNAME别名上图是混合模式:既有Namingresolution,也有Proxy代理。而且,命名服务为了支持可扩展性,还引入了父子层次结构。末端的Namingservice可以完全委托给上层的Namingservice。在DNS中,我们经常看到www.example.org的域名解析,CNAME别名为www.example.org.cdnprovider.com(是cdnprovider.com的子域名),这样客户端就不需要修改了,并且仍然访问的是www.example.org,但是相应的后端服务不再是直接访问Provider#A或者B,而是在中间植入了一个CNAMEProxy,然后Proxy会根据Provider#A转发给Provider#A插件的决定。或B.这个设计太棒了!允许商业公司cdnprovider.com零侵入地为www.example.org提供CDN服务,无需修改任何一段代码,只需要在域名处修改www.example.org的域名解析服务提供者。这个操作代表www.example.org同意cdnprovider.com为其提供CDN服务,代表授权。这一切都源于基于Naming解析的动态绑定实现的解耦。同样,除了CDN,我们的恶意流量清洗、金丝雀发布、性能分析等都可以使用这种方式实现零入侵的插拔。4.事件流订阅事件流订阅的设计理念是将瞬时的过程调用转化为可重放的命令,对命令的响应不需要预先定义。事件流订阅在应用层表现为Mediator中介模式,在架构层表现为Broker消息模式。Applicationlayer-Mediator中介模式A直接调用B,也就是说A对B有很强的依赖,当然我们可以通过面向接口编程将这种依赖降低到只依赖接口而不依赖实现。简单的说,我们只依赖于事物的处理结果,而不是依赖于如何实现这个处理结果。但这还不够,因为我们还依赖接口,也就是处理语义的描述。现实中的某些情况下,连语义的描述都会发生变化,即界面也会发生变化。如何进一步解耦?如下图:A没有直接调用B,而是通过一个中介Mediator,分两步解耦:首先A调用Mediator:A持有Mediator的引用,执行Mediator的方法,即mediator.publish(e).然后Mediator调用B:为了解耦Mediator对外界的依赖,我们使用面向接口的EventHandler来实现依赖倒置。让B实现EventHandler。当然,如果B已经存在,或者有更多的发言权,仍然要遵循依赖倒置的原则,但是Mediator模式的推动者可以实现另一个Adapter来帮助现有的B适配EventHandler。有了上面的设计模式,具体实现分为三步:订阅:通过mediator.subscribe(b)预先将未来的事件处理注册到Mediator中。发布:A将自己的事件发布给Mediator。注意这个概念特别重要,A只发布发生了什么,A不会直接调用B来声明事情的处理。即A不再依赖B的接口!比如新员工入职,一开始需要为员工申请磁条卡,但是办理磁条卡的供应商可能是A,也可能是B。这就叫接口-面向编程,但这还不够,因为随着公司的发展,现在新员工入职时已经有了人脸识别,不再需要申请磁条卡了。而是需要注册人脸识别,员工福利更好,对于异地出差的新员工,还将发放安家费。这些在前面的“接口”中没有描述。执行:当mediator收到A的事件(A调用mediator.publish(e)),mediator会通过EventHandler回调mediator.subscribe(b)预先注册的处理类。上面提到的Mediator有一些限制。对于所有事件,只能有一个EventHandler。如果我们把Mediator升级为一个通用的处理机制,一个平台,自然会有各种各样的事件,我们自然会对事件进行分类或者分组。我们将事件的分类或分组称为主题;我们将事件理解为主题类中的特定实例。并在Mediator中维护了从Topic到EventHandler的一套处理器。如下图所示:可以看到上面的架构通过Map
