UDN-企业互联网技术人气社区

板块导航

浏览  : 2046
回复  : 0

[开发框架] 实践 | Otto奥托集团的架构选型之路

[复制链接]
遇见未知的自已的头像 楼主
发表于 2015-11-12 16:39:34 | 显示全部楼层 |阅读模式
本帖最后由 遇见未知的自已 于 2015-11-12 16:40 编辑

  otto.de是德国的一家网上购物网站,本篇前半部分主要介绍了几个系统架构以及它们的优缺点,后半部分主要讲解otto.de的微服务架构。

  奥托集团(Otto Group)来自德国领先的电子商务解决方案及服务的提供商,在全球综合B2C排名中,仅次于亚马逊排在第二位,同时也是全球最大在线服装、服饰和生活用品零售渠道商 。2010/11财政年度,Otto Group的收入为114.04亿欧元。本文翻译自Otto的技术博客。

  在我们开始开发otto.de网上商店时,我们选择了分布式垂直架构。之前的工作经验告诉我们,一体化架构(monolithic architecture)不能够满足不断增长的需求。爆发式增长的数据、持续提高的负载和对系统的扩展,所有的这些强迫我们去重新思考网站的架构。

  这篇文章将会描述我们的解决办法,还有我们这么做的原因。

  一体化(Monoliths)

  在项目刚开始的时候,团队通常会考虑使用什么编程语言和合适的架构。当谈到服务端应用时,Java和Spring框架、Ruby on rails或者类似的框架通常会成为团队的选择。

  选择了语言和框架后,经过一段时间的开发,一个简单的应用诞生了。与此同时,一体式架构(macro-architecture)毫无争议的成为了团队的选择。但是,这种架构的缺点也渐渐地浮出了水面:

 
  • 它导致了重量级微架构(a heavyweight Micro Architecture)
  • 负载均衡限制了应用的可扩展性
  • 系统的可维护性受到影响,尤其是那些大型应用
  • 零停机部署(Zero downtime deployment)变得非常的困难,尤其是那些有状态的应用(stateful application)
  • 多个团队开发效率低,并且需要额外的协调

  
9968d04028100b967dc10c96122def2c.png



  当然,这并不是说一个新的应用一开始就变得巨大而混乱。在最开始的时候,新应用结构清晰、简单易懂、可扩展性也高,它能够很轻松的解决需求问题。在接下来的一段时间中,越来越多的代码被编写。为了应对日益增长的复杂度,系统被分层、抽象、模块、服务和框架被引入到系统中,最终变成了我们看到的样子。

  即便是中型的应用(比如说一个50000的Java应用),一体化架构也会渐渐地变得令人讨厌,更不用说那些对扩展性要求较高的应用了。

  最终,曾经轻量简洁的应用将会变成下一代开发者的噩梦。

  分而治之

  问题的关键在于,如何避免这种类型的开发,并且将轻量应用好的那部分保留下来。换句话说,我们如何能够获得一个可持续发展的架构,这个架构在多年之后依旧能够让开发者保持高效开发。

  在软件的开发过程中,有许多关于结构化代码的概念:函数、方法、类、库、框架等等。这些概念并不是程序运行所必须的,发明他们的原因是为了帮助开发者更好的理解他们的应用。

  目前软件开发者已经理解了这些概念,一个问题接踵而来:为什么这些概念仅仅被应用于一个软件?是什么阻碍我们将应用拆分成多个低耦合的部分?

  有三件事情我们需要牢记在心:
  • 康威定律:软件开发最开始时仅有一个团队,根据康威定律,因此会产生一个应用。(译者注:可以参考图片理解)
  • 初始消耗:部署应用,并让它运行起来似乎是一个非常简单的任务。实际上,你需要建立并管理VCS代码库,编译文件,构建管道,用于部署的程序、硬件、虚拟机、日志文件、监控软件等等。所有的这些都需要花费一定的时间去处理。
  • 操作的复杂性:大型分布式系统比一个小型的负载均衡集群更难操作。


  如果我们放手不管,由多个小型模块组成的系统并不会出现,一个巨大而混乱的系统将会取而代之。这时候,致命的问题已然出现,然而悔之晚矣。

  正常情况下,一个系统是否需要被扩展,是否需要处理巨大的代码库在初期是非常清楚的。然后当你遇到以上提到的障碍,你要么没尝试解决他们,要么只能沉沦在无尽的苦果中。

  在OTTO,在最开始的时候就花费了大量时间去建立4个跨职能的团队,根据前面提到的康威定律,一个项目属于4个团队,最终就能产生一个由4模块组成的应用,这样就避免了一体化应用的诞生。

  因为我们之前操作过大型的一体化应用,操作的复杂性对于我们似乎是一个可以被解决的问题---操作200个一体化应用和操作200个更小型的系统没有太大的区别。

  初始消耗可以通过标准化和自动化来克服。因为我们没有提到云服务,你还需要做相关的基础操作来启动自动化服务。虽然有些麻烦,但做过一次后,一切将自动化,你会获得巨大的便利。

  可扩展性

  如何将一体化应用转变为由多个小模块组成的应用?首先,让我们仔细想想一个应用能够从哪些维度进行扩展。

  纵向分解(Vertical Decomposition)

  纵向分解是一个非常自然而通用的方法,以至于常常被开发者所忽略。相比于把所有的功能集中到单一的应用中,我们将应用分解成了多个小模块,它们相互独立,互不影响。

  
678823cc560e281da4182244019352b1.png



  我们可以根据业务域来分解系统。举个例子来说,在otto.de我们就讲网上商城分解成了11个不同的垂直模块:后勤办公室、产品、订单等等。

  每一个垂直模块属于一个单一团队,它们有独立的前端、后端和数据存储。在模块之间共享代码是严令禁止的。当然,在特殊的情况下,如果我们需要分享代码,我们会建立一个开源的项目来解决该问题。

  因此,每个垂直模块是一个独立自主的系统,就像Stefan Tikov在Substainable Architecture中提到的那样。

  分布式计算

  一个垂直模块依旧可能成为一个相对大型的一体化应用,因此我们需要继续对垂直模块进行拆分。一种方法是将一个垂直模块分解成更多的垂直模块,另外一种方法是通过分布式计算将系统分解成多个模块,不同的是这些模块运行在他们自己的进程中,并且通过REST来传递信息。

  
a50bfb983158c5a9941b1d6b39a4a77d.png



  在这种情况下,应用不仅仅被垂直分解,同时还会被水平分解。这种架构中,请求到达应用后,对请求的处理会被分布于多个模块中,然后每一模块产生的结果汇总成一个响应,发送回请求者。

  这些模块并不会共享一个数据库架构,因为这样做会导致模块间的紧密耦合:数据结构的改变会使得一个模块不能够被独立的部署。

  分片

  当系统需要处理大量的数据,或者当一个分布式的应用被操作时,分片是很恰当的选择。比如说,分片非常适合向全球范围提供服务的应用。

  
472d4d62a6b10d28197485fbe1d052f8.png



  因为我们暂时没有利用到分片这个概念,在这篇文章就不再详细描述了。

  负载均衡

  每当服务器承受不了巨大的负载压力时,负载均衡就会容重登场。通过对一个应用拷贝多次,同时利用负载均衡器将负载分解来缓解压力。

  
534e9a5b4072496dfaeb95b39b47ddfb.png



  在负载均衡中,不同实例的应用通常会分时使用同一个数据库,数据库因此成为了系统的瓶颈,但是我们可以通过制定良好的扩展策略来避免这个问题。相比于关系数据库,NoSQL能够很好的处理扩展性的问题,这也就是NoSQL能够在软件世界中占据一亩三分地的原因之一。

  最大的可扩展性

  所有以上提到的办法能够组合在一起,能够达到任何级别的可扩展性。

  
3b78ab05a6f3e64c4096df0853ca7696.png



  当你没有相应的需求时,组合的结果会变得有点太复杂。幸运地是,开发者并不需要在一开始的时候就制定庞大的计划,相反,他们可以循序渐进,一步步朝着目标架构前进。

  举个例子来说,在otto.de,架构一开始是4个垂直模块加上负载均衡,在过去的三年里,产生了更多的垂直模块。在此期间,某些模块变得非常的巨大笨重。因此,我们引入了微服务架构,同时通过扩展垂直模块来建立分布式计算。

  微服务(Microservices)

  微服务最近变得非常的流行。微服务是一种架构风格,它能够根据业务域将系统分解成多个细粒度,独立的模块。

  在这种情况下,微服务可以是一个小的垂直模块,或者是分布式计算机构中的一个服务。与传统方法的不同之处在于应用的大小:一个微服务仅仅实现了一个业务域中的几个功能,它结构清晰,一个开发者能够很轻松的掌握它。

  一个微服务非常的小,因此多个微服务能够运行在单一的服务器上。我们对“Fat JARs”有丰富的经验,通常能通过执行java –jar 来执行它们。如果需要的话,也能开启一个Jetty或者类似的服务器。

  为了简化不同微服务的部署和操作问题,每一个服务器运行在独立的Docker容器中。

  REST和微服务架构是一个很好的组合,它适合于构建大型的系统。一个微服务可以负责提供REST资源,超媒体(hypermedia)可以用来解决服务发现的问题,在涉及到接口的版本控制,服务部署独立性的情况下,媒体类型(media type)有很大的帮助。

  总而言之,微服务架构有许多的好处,比如说:

 
  • 在微服务架构下进行开发是非常有趣的:每几周或者几个月,你就可能开始一个新的开发项目。
  • 由于微服务非常小,微服务架构不需要重量级框架和过多的模板代码。
  • 他们能够被独立的部署。因此持续交付或者持续部署变得非常的简单。
  • 微服务架构能够支持多个独立的团体同时开发。
  • 开发者能够为每一个服务选择最恰当的开发语言。不用担心对项目产生影响,开发者可以尝试新的语言或者框架。但是需要注意,这并不意味这你能够随意行使这项权利。
  • 因为微服务足够小,只需消耗较少的资源就能将他们替换成新的项目。
  • 这种架构的可扩展性相比于一体化架构显得非常的好,每一个服务都能被独立的扩展。


  微服务架构遵守敏捷开发的原则。一个不能完全满足用户的新特性不仅可以被迅速的创建,而且还能够被快速的销毁。

  宏架构和微架构(Macro-and Micro-Architecture)

  在微服务架构中,哪一部分将难以改变?内部模块的扩展已经不再是关键问题,最难以改变的事情是有关微服务架构的决定,比如说如何将微服务整合到系统的方法,或者模块间传输信息协议的选择。

  因此,otto.de严格区分了微架构(micro-architecture)和宏架构(macro-architecture)。微架构都是关于垂直架构或者微服务架构的内部结构,并且全部交由各自的团队全权处理。

  但是,明确宏架构的大体方向是有价值的:

 
  • 垂直分解:系统被分割成多个垂直模块,每一个模块完全属于一个特定的团队。模块与模块之间的信息传递禁止在用户请求的过程中进行,而必须在后台执行。
  • RESTful架构:不同服务之间的信息传递和整合只通过REST来执行。
  • 零分享架构:服务间不会通过共享可变状态(mutable state)来进行信息交换或者分享信息。没有HTTP sessions,没有中央数据存储中心,没有共享代码。但是多个服务的实例之间有可能共享一个数据库。
  • 数据管理:对于每一个数据节点,只有一个系统负责管理。其他的系统只能通过REST API读取数据,然后将需要的数据拷贝回自己的数据库。

  我们的架构已经熬过了一轮软件开发周期,与此同时,我们开始标准化微服务使用的方式。

  集成

  目前为止,我已经详细说明了许多有关系统分解的内容。但是,用户体验是我们的系统的最终目标,我们希望我们的应用保持一致性,感觉就像是一个整体。

  因此,问题来了:我们如何能够集成一个分布式的系统,同时让用户意识不到我们架构的分布特性。

  超链接

  对于前端集成,最简单的办法就是使用超链接。

  每一个服务负责不同的页面,页面的导航通过链接来实现。

  
3d1b19817d0a72cc65d61b4e95209e93.png



  AJAX

  使用AJAX的目的也很明显,它能够通过JavaScript重新加载页面的不同内容,并且将他们整合在特定的页面。

  主要注意的是,服务之间涉及到的依赖非常的小,服务互相之间需要对使用的URL和媒体类型(media type)保持一致性。

  
1c19e3e61f7ea899aecdd1b56da16418.png



  资源服务器(Asset Server)

  当然,图片在不同页面的显示也需要保持一致性。除此之外,分布式的服务需要对他们各自的JavaScript库和版本保持一致。

  为了保持一致性,在我们的系统中,静态资源,比如说CSS,JS和图片都是通过一个中央资源服务器来进行传输。

  在垂直架构,共享资源的部署和版本控制是一个完全不同的话题,这需要一篇独立的文章来详细解释。在这里我们不做过多解释,我们只需要记住,共享资源的同时保持服务独立是具有非常大的挑战性。

  Edge-Side Includes

  有一种不太知名的方法,它能整合不同服务的资源到同一个站点。这种方法我们称之为Server-Side Includes或者是Edge-Side Includes。大多数的Web服务器或者反向代理都只支持这个功能。

  这项技术非常的简单:一个服务插入插入一条带有URL的包含语句(include statement)。然后这个URL会被web服务器或者反向代理解析,代理根据URL获取到一个响应,然后用这个响应代替页面中的包含语句。

  在我们的商城中,每一个页面都包含了来自搜索&导航服务(SAN)的导航信息:
 
  1.  <html>
  2. ...
  3. <esi:include src=“/san/...“ />
  4. ...
  5. </html>
复制代码


  反向代理(我们使用了Varnish)解析了页面,然后将URL分解出来,SAN根据URL提供相应的HTML片段。

  然后Varnish代理用这个HTML片段取代包含语句,并将重新生成的页面发送回用户。

  在这种方式下,用户根本意识不到页面是由多个来自不同服务的片段组成的。

  数据拷贝(Data Replication)

  以上提到的技术仅仅解决了前端集成问题,现在我们来谈谈服务端集成。不同的服务之间需要共同的数据,但是他们又不能共享同一个数据库,因此我们想了一些办法来处理这个问题。

  数据拷贝就是一个办法。比如说,其他的服务需要关于产品的数据,它们就会定期的向负责产品数据的垂直模块(Product)请求数据,这样产品数据的更新能够很迅速的被其他服务检测到。

  我们没有使用任何的消息队列来向客户端推送(push)数据。相反的,每当服务需要更新数据时,它们会轮询(poll)Atom Feed。

  值得一提的是,某些不好的事情必须牺牲服务的可用性来避免,我们在开发过程中不得不面对这个矛盾。

  没有远程服务调用(NO Remote Service Calls)

  理论上来说,在某些情况下,我们可以避免数据的拷贝,这样服务就能够同步使用其他的服务了。一个购物篮并不需要保存额外的产品信息,相反它可以直接向产品模块请求数据。

  我们并没有这么做,为什么呢?

 
  • 当一个系统的主要功能依赖于其它系统时,系统的可测试性受到影响。
  • 一个缓慢的服务会影响到其它系统的请求,当请求越多,雪球越滚越大,最终影响到了整个系统的可用性。
  • 系统的可扩展性受到了限制。
  • 独立的服务部署变得非常的困难。


  我们长期和垂直架构打交道,我们非常明确的知道,在早期的时候,严格的界限会使得微服务的开发、测试、迁移变得更加独立、方便。

  经验教训

  按照以上罗列的办法,经过三年的工作后,我们变得经验丰富。

  回顾往事,如果我早点做这些事情,我们的一体化系统会变得更加的精细。现在,otto.de的未来属于微服务。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部