技术趋势关键词:分布式架构+微服务架构(针对移动互联网)+一体式架构(前两者结合+UI等敏捷开发)
【译者的话】otto.de是德国的一家网上购物网站,本篇前半部分主要介绍了几个系统架构以及它们的优缺点,后半部分主要讲解otto.de的微服务架构。
在我们开始开发otto.de网上商店时,我们选择了分布式垂直架构。之前的工作经验告诉我们,一体化架构(monolithic architecture)不能够满足不断增长的需求。爆发式增长的数据,持续提高的负载和对系统的扩展,所有的这些强迫我们去重新思考网站的架构。
这篇文章将会描述我们的解决办法,还有我们这么做的原因。
在项目刚开始的时候,团队通常会考虑使用什么编程语言和合适的架构。当谈到服务端应用时,Java和Spring框架,Ruby on rails或者类似的框架通常会成为团队的选择。
选择了语言和框架后,经过一段时间的开发,一个简单的应用诞生了。与此同时,一体式架构(macro-architecture)毫无争议的成为了团队的选择。但是,这种架构的缺点也渐渐地浮出了水面:
当然,这并不是说一个新的应用一开始就变得巨大而混乱。在最开始的时候,新应用结构清晰,简单易懂,可扩展性也高,它能够很轻松的解决需求问题。在接下来的一段时间中,越来越多的代码被编写。为了应对日益增长的复杂度,系统被分层,抽象,模块,服务和框架被引入到系统中,最终变成了我们看到的样子。
即便是中型的应用(比如说一个50000的Java应用),一体化架构也会渐渐地变得令人讨厌,更不用说那些对扩展性要求较高的应用了。
最终,曾经轻量简洁的应用将会变成下一代开发者的噩梦。
问题的关键在于,如何避免这种类型的开发,并且将轻量应用好的那部分保留下来。换句话说,我们如何能够获得一个可持续发展的架构,这个架构在多年之后依旧能够让开发者保持高效开发。
在软件的开发过程中,有许多关于结构化代码的概念:函数,方法,类,库,框架等等。这些概念并不是程序运行所必须的,发明他们的原因是为了帮助开发者更好的理解他们的应用。
目前软件开发者已经理解了这些概念,一个问题接踵而来:为什么这些概念仅仅被应用于一个软件?是什么阻碍我们将应用拆分成多个低耦合的部分?
有三件事情我们需要牢记在心:
如果我们放手不管,由多个小型模块组成的系统并不会出现,一个巨大而混乱的系统将会取而代之。这时候,致命的问题已然出现,然而悔之晚矣。
正常情况下,一个系统是否需要被扩展,是否需要处理巨大的代码库在初期是非常清楚的。然后当你遇到以上提到的障碍,你要么没尝试解决他们,要么只能沉沦在无尽的苦果中。
在OTTO,在最开始的时候就花费了大量时间去建立4个跨职能的团队,根据前面提到的康威定律,一个项目属于4个团队,最终就能产生一个由4模块组成的应用,这样就避免了一体化应用的诞生。
因为我们之前操作过大型的一体化应用,操作的复杂性对于我们似乎是一个可以被解决的问题---操作200个一体化应用和操作200个更小型的系统没有太大的区别。
初始消耗可以通过标准化和自动化来克服。因为我们没有提到云服务,你还需要做相关的基础操作来启动自动化服务。虽然有些麻烦,但做过一次后,一切将自动化,你会获得巨大的便利。
如何将一体化应用转变为由多个小模块组成的应用?首先,让我们仔细想想一个应用能够从哪些维度进行扩展。
纵向分解是一个非常自然而通用的方法,以至于常常被开发者所忽略。相比于把所有的功能集中到单一的应用中,我们将应用分解成了多个小模块,它们相互独立,互不影响。
我们可以根据业务域来分解系统。举个例子来说,在otto.de我们就讲网上商城分解成了11个不同的垂直模块:后勤办公室,产品,订单等等。
每一个垂直模块属于一个单一团队,它们有独立的前端,后端和数据存储。在模块之间共享代码是严令禁止的。当然,在特殊的情况下,如果我们需要分享代码,我们会建立一个开源的项目来解决该问题。
因此,每个垂直模块是一个独立自主的系统,就像Stefan Tikov在Substainable Architecture中提到的那样。
一个垂直模块依旧可能成为一个相对大型的一体化应用,因此我们需要继续对垂直模块进行拆分。一种方法是将一个垂直模块分解成更多的垂直模块,另外一种方法是通过分布式计算将系统分解成多个模块,不同的是这些模块运行在他们自己的进程中,并且通过REST来传递信息。
在这种情况下,应用不仅仅被垂直分解,同时还会被水平分解。这种架构中,请求到达应用后,对请求的处理会被分布于多个模块中,然后每一模块产生的结果汇总成一个响应,发送回请求者。
这些模块并不会共享一个数据库架构,因为这样做会导致模块间的紧密耦合:数据结构的改变会使得一个模块不能够被独立的部署。
当系统需要处理大量的数据,或者当一个分布式的应用被操作时,分片是很恰当的选择。比如说,分片非常适合向全球范围提供服务的应用。
因为我们暂时没有利用到分片这个概念,在这篇文章就不再详细描述了。
每当服务器承受不了巨大的负载压力时,负载均衡就会容重登场。通过对一个应用拷贝多次,同时利用负载均衡器将负载分解来缓解压力。
在负载均衡中,不同实例的应用通常会分时使用同一个数据库,数据库因此成为了系统的瓶颈,但是我们可以通过制定良好的扩展策略来避免这个问题。相比于关系数据库,NoSQL能够很好的处理扩展性的问题,这也就是NoSQL能够在软件世界中占据一亩三分地的原因之一。
所有以上提到的办法能够组合在一起,能够达到任何级别的可扩展性。
当你没有相应的需求时,组合的结果会变得有点太复杂。幸运地是,开发者并不需要在一开始的时候就制定庞大的计划,相反,他们可以循序渐进,一步步朝着目标架构前进。
举个例子来说,在otto.de,架构一开始是4个垂直模块加上负载均衡,在过去的三年里,产生了更多的垂直模块。在此期间,某些模块变得非常的巨大笨重。因此,我们引入了微服务架构,同时通过扩展垂直模块来建立分布式计算。
微服务最近变得非常的流行。微服务是一种架构风格,它能够根据业务域将系统分解成多个细粒度,独立的模块。
在这种情况下,微服务可以是一个小的垂直模块,或者是分布式计算机构中的一个服务。与传统方法的不同之处在于应用的大小:一个微服务仅仅实现了一个业务域中的几个功能,它结构清晰,一个开发者能够很轻松的掌握它。
一个微服务非常的小,因此多个微服务能够运行在单一的服务器上。我们对“Fat JARs”有丰富的经验,通常能通过执行java –jar <file>来执行它们。如果需要的话,也能开启一个Jetty或者类似的服务器。
为了简化不同微服务的部署和操作问题,每一个服务器运行在独立的Docker容器中。
REST和微服务架构是一个很好的组合,它适合于构建大型的系统。一个微服务可以负责提供REST资源,超媒体(hypermedia)可以用来解决服务发现的问题,在涉及到接口的版本控制,服务部署独立性的情况下,媒体类型(media type)有很大的帮助。
总而言之,微服务架构有许多的好处,比如说:
微服务架构遵守敏捷开发的原则。一个不能完全满足用户的新特性不仅可以被迅速的创建,而且还能够被快速的销毁。
在微服务架构中,哪一部分将难以改变?内部模块的扩展已经不再是关键问题,最难以改变的事情是有关微服务架构的决定,比如说如何将微服务整合到系统的方法,或者模块间传输信息协议的选择。
因此,otto.de严格区分了微架构(micro-architecture)和宏架构(macro-architecture)。微架构都是关于垂直架构或者微服务架构的内部结构,并且全部交由各自的团队全权处理。
但是,明确宏架构的大体方向是有价值的:
我们的架构已经熬过了一轮软件开发周期,与此同时,我们开始标准化微服务使用的方式。
目前为止,我已经详细说明了许多有关系统分解的内容。但是,用户体验是我们的系统的最终目标,我们希望我们的应用保持一致性,感觉就像是一个整体。
因此,问题来了:我们如何能够集成一个分布式的系统,同时让用户意识不到我们架构的分布特性。
对于前端集成,最简单的办法就是使用超链接。
每一个服务负责不同的页面,页面的导航通过链接来实现。
使用AJAX的目的也很明显,它能够通过Javascript重新加载页面的不同内容,并且将他们整合在特定的页面。
主要注意的是,服务之间涉及到的依赖非常的小,服务互相之间需要对使用的URL和媒体类型(media type)保持一致性。
当然,图片在不同页面的显示也需要保持一致性。除此之外,分布式的服务需要对他们各自的Javascript库和版本保持一致。
为了保持一致性,在我们的系统中,静态资源,比如说CSS,JS和图片都是通过一个中央资源服务器来进行传输。
在垂直架构,共享资源的部署和版本控制是一个完全不同的话题,这需要一篇独立的文章来详细解释。在这里我们不做过多解释,我们只需要记住,共享资源的同时保持服务独立是具有非常大的挑战性。
有一种不太知名的方法,它能整合不同服务的资源到同一个站点。这种方法我们称之为Server-Side Includes或者是Edge-Side Includes。大多数的Web服务器或者反向代理都只支持这个功能。
这项技术非常的简单:一个服务插入插入一条带有URL的包含语句(include statement)。然后这个URL会被web服务器或者反向代理解析,代理根据URL获取到一个响应,然后用这个响应代替页面中的包含语句。
在我们的商城中,每一个页面都包含了来自搜索&导航服务(SAN)的导航信息:
<html> ... <esi:include src=“/san/...“ /> ... </html>
反向代理(我们使用了Varnish)解析了页面,然后将URL分解出来,SAN根据URL提供相应的HTML片段。
然后Varnish代理用这个HTML片段取代包含语句,并将重新生成的页面发送回用户。
在这种方式下,用户根本意识不到页面是由多个来自不同服务的片段组成的。
以上提到的技术仅仅解决了前端集成问题,现在我们来谈谈服务端集成。不同的服务之间需要共同的数据,但是他们又不能共享同一个数据库,因此我们想了一些办法来处理这个问题。
数据拷贝就是一个办法。比如说,其他的服务需要关于产品的数据,它们就会定期的向负责产品数据的垂直模块(Product)请求数据,这样产品数据的更新能够很迅速的被其他服务检测到。
我们没有使用任何的消息队列来向客户端推送(push)数据。相反的,每当服务需要更新数据时,它们会轮询(poll)Atom Feed。
值得一提的是,某些不好的事情必须牺牲服务的可用性来避免,我们在开发过程中不得不面对这个矛盾。
理论上来说,在某些情况下,我们可以避免数据的拷贝,这样服务就能够同步使用其他的服务了。一个购物篮并不需要保存额外的产品信息,相反它可以直接向产品模块请求数据。
我们并没有这么做,为什么呢?
我们长期和垂直架构打交道,我们非常明确的知道,在早期的时候,严格的界限会使得微服务的开发,测试,迁移变得更加独立,方便。
按照以上罗列的办法,经过三年的工作后,我们变得经验丰富。
回顾往事,如果我早点做这些事情,我们的一体化系统会变得更加的精细。现在,otto.de的未来属于微服务。
原文链接: On Monoliths and Microservices
随着DevOps、持续交付等理念的深入人心,微服务(Microservices)架构开始走进我们的视野。微服务是用一组小服务的方式来构建一个应用,服务独立运行在不同的进程中,服务之间通过轻量的通讯机制(如RESTful接口)来交互,并且服务可以通过自动化部署方式独立部署。正因为微服务架构中的服务之间是相互独立的,所以不同的服务可以使用不同的语言来开发,或者根据业务的需求使用不同类型的数据库。3月底,来自ThoughtWorks的James Lewis和Martin Fowler分享了他们对微服务架构的理解以及看法。文章中作者详细介绍了微服务的特点以及相对于传统架构的微服务架构的优势。
在James看来,传统的整体风格的架构在构建部署和扩展伸缩方面有很大的局限性,传统的整体(monolithic)风格的架构一般分为三部分:客户端用户界面(HTML页面和JavaScript程序)、数据库、服务端应用程序。服务端程序负责处理HTTP请求、执行业务逻辑、加载和更新数据库数据、查询和填充HTML页面,这个服务端应用就像是一块铁板,笨重且不可拆分,系统中任何程序的改变都需要整个应用重新构建和部署新版本。另外传统的整体风格的架构在进行水平扩展时也只能整个系统扩展,而不能针对某一个功能模块进行扩展。而微服务架构可以完美的解决统一风格架构所遇到的种种问题。微服务架构将系统以组件化的方式分解为多个服务,服务之间相对独立且松耦合,单一功能的改变只需要重新构建部署相应的服务即可。
传统的开发模式在分工时往往以技术为单位,比如UI团队、服务端团队和数据库团队,这样的分工可能会导致任何功能上的改变都需要跨团队沟通和协调。而微服务则倡导围绕服务来分工,不同的服务可以采用不同的技术来实现,一个团队中应该包含开发所需的所有技能,比如用户体验、数据库、项目管理。
传统的软件开发常常以项目为周期,一旦项目开发完成即交付给运维部门,而在微服务架构中,一个团队应该去把控产品的整个生命周期,而不只是开发,这其实也是DevOps的核心理念,所以微服务中的每一个服务都是一个产品,而不是一个项目。
微服务架构抛弃了ESB复杂的业务规则编排、消息路由等功能,微服务架构中服务是高内聚的,每个服务都会处理相应的业务,所有的业务逻辑应该尽量在服务内部处理,且服务间的通信尽可能的轻量化,比如使用Restful的方式。
传统的软件开发中经常会使用同一个技术平台来解决所有的问题,而经验表明使用合适的工具做合适的事情会让开发变得事半功倍。微服务架构天生就具有这样的特性,我们可以使用Node.js来开发一个简单的报表页面,使用C++来编写一个实时聊天组件。
微服务架构的引入为多样化持久保存数据提供可能,持久层可以使用传统关系数据库和NoSQL。不同于传统的应用,微服务架构中,我们可以为每个服务选择一个新的适合业务逻辑的数据库系统,比如MongoDB、PostgreSQL。这样做的好处是显而易见的,首先我们可以根据业务类型(读多还是写多等)来决定使用哪种类型的数据库,其次这样可以减小单个数据库的负载。
就James的文章来看,微服务架构主要有以下几个优点:
James的文章在社区引起了广泛的讨论,近日,Contino公司的CTO Benjamin Wootton在highscalability上撰文表示微服务并没有想象中的那么好,并建议开发者在选用此架构时一定要慎重。Benjamin认为微服务架构时可能会面临下面一些挑战:
每一种架构都有其优缺点,当然微服务也包括在内。我们需要根据项目业务和团队情况来选择合适的架构,微服务才刚刚开始发展,这也是顺应PaaS、持续交付、DevOps等新技术理念下的产物,期待微服务架构能得到更广泛的应用。关于微服务,如果您有新的见解,欢迎与我们分享。
感谢杨赛对本文的审校。