1.1 互联网架构变迁
1.1.1 互联网架构的核心问题
互联网应用的业务特征决定了它和企业级应用具有诸多不同,具体来说,主要有以下几点。
海量用户
互联网应用几乎无差别地为全世界所有的用户提供服务,与服务于局域网用户的企业级应用相比,其用户量要大得多,由海量用户产生的数据量自然也会呈几何级增长。
与日常生活中的真实场景不同,在网站用户量超过应用负荷的阈值之前,互联网用户不会明显感受到由用户量增长所导致的服务质量下降。举一个简单的例子,当我们去商场购物时,如果只有10位顾客在场,所感受到的环境舒适度、所享受的服务质量,以及等待时间,与有100位顾客在场时肯定会有很大的不同。而在网上购物时,有10位用户同时购买与有100位用户同时购买,我们几乎感受不到任何差别。但用户数一旦超过了网站应用所能够承载的阈值,比如1000万人同时购买,那么整个网站在处理不当的情况下便会完全失去响应,如果处理得严重不当,还会导致用户交易数据丢失,最坏的情况是部分用户付款之后却不能收到商品,且投诉无门、查无对账。
互联网应用对于用户量的预估远远没有企业级应用那么准确,在业务发展迅速的情况下,用户量的增长是爆发性且没有上限的。
产品迅速迭代
随着业务模式的快速拓展,互联网应用功能推陈出新的速度也越来越快。在当今这个节奏如此快的时代,时间成本显得非常关键。敏捷地探知市场需求并将其实现,是互联网行业的立命之本。产品快速升级必然会推动开发、测试、交付甚至系统迅速迭代。
7×24小时不间断服务
互联网应用是一个面向全球的服务应用,由于具有时区差异,因此应用必须保证全天随时可用。
各种意外情况,如光缆挖断、机房失火等,都可能对系统的可用性产生影响,每次宕机都会造成很大的损失。另外,如果系统设计得不够健壮,对其升级和维护时就要停止服务。频繁的系统升级同样会对系统可用性产生很大的影响,而互联网公司每天多次进行应用上线是很常见的行为,发布常态化已渐渐成为互联网行业的标准。
虽然随时随处可用的难度非常大,但互联网应用会尽量缩短宕机时间。通常使用3~5个9 (3个9即99.9%,4个9即99.99%,5个9即99.999%)作为衡量系统可用性的指标,表示系统在1年的运行过程中可以正常使用的时间与总运行时间的比值,下面分别计算3个9、4个9、5个9指标下的全年宕机时间,我们来感受一下它们的可靠性差异。
在真实的运营环境下,系统可用性指标每提升1个9都是非常不容易的。
流量突增
不同类型的互联网公司有着不同的流量突增场景。比如,电商类公司的流量会在“双11”这样的大型促销活动期间突增几倍、几十倍甚至上百倍;社交类公司的流量会在热点事件爆发时突增。
流量突增分为可预期型突增和不可预期型突增,像促销活动、有计划的热点事件(如美国总统大选、世界杯总决赛等)等引起的流量突增属于可预期的流量突增,可以通过提前扩容、预案演练等方式精心为这些流量突增准备应对方案。而意料之外的热点事件(如地震)往往事发突然,系统来不及准备应对措施,因此若系统本身的可用性、弹性等非功能需求十分成熟,便可以在某种程度上应对流量突增了。
业务组合复杂
很多互联网公司都是跨界巨头,我们知道,即使不跨界,在单一领域编织一个大规模的成型业务系统也并不简单。
以电商行业为例,电商系统在应用系统层面大致可划分为卖场、交易、订单、仓储、物流等主流程系统,搜索、推荐、社区、会员、客服、退换货等面向用户的前端系统,商品、价格、库存、配货、促销、供应链等面向后台员工的后端系统,以及广告、商家、支付、清算、财务、报表等面向合作伙伴的辅助系统,每个应用系统又会划分为很多子系统。一个粗略的电商系统业务架构如图1-1所示。
图1-1 一个粗略的电商系统业务架构
由于互联网行业的扩张速度势不可当,以及其业务特征具有特殊性,因此相应的底层支撑技术面临的挑战也越来越大。由规模扩张而衍生的问题包括数据海量、响应迟缓、稳定性差、伸缩性差、系统繁多和开发困难等,因此针对这些问题,互联网的技术架构也在逐渐转变,遵循着从集中式到分布式再到云平台的方向逐步演进。
1.1.2 从集中式架构到分布式架构
集中式架构又称单体式架构,在Web 2.0时代并未兴起时,这种架构十分流行。进入21世纪以来,基于 Web 应用的 B/S(Browser/Server)架构逐渐取代了基于桌面应用的 C/S (Client/Server)架构。B/S架构的后端系统大都采用集中式架构,它当时凭借优雅的分层设计统一了服务器后端开发领域。
传统的三层架构模型
在Web 2.0时代刚刚流行的时候,互联网应用与企业级应用并没有本质的区别,集中式架构分为标准的三层:数据访问层、服务层和Web层。传统的三层架构模型如图1-2所示。
图1-2 传统的三层架构模型
数据访问层用于定义数据访问接口,实现对真实数据库的访问;服务层用于对应用业务逻辑进行处理;Web层用于处理异常、逻辑跳转控制、页面渲染模板等,其又被称为MVC(Model View Controller)层。
这三层之间既可以共享领域模型对象,又可以进行更加细致的拆分。通常的做法是,数据访问层使用实体对象(Entity),每个实体对象对应数据库中的一条数据。实体对象和值对象(VO)组成领域模型(Domain Model),被服务层使用。而逻辑控制层由于需要和前端的Web页面打交道,需要封装大量的表单,因此使用由领域模型转换的数据传输对象(DTO)。
服务层是整个系统的核心,它既直接提供公开的API,也可以通过Web层提供API。服务层同时可以提供部分私有实现,用于屏蔽底层实现细节。数据访问层应该只由服务层直接调用,它无须公开任何公有API。
由于 NoSQL 在传统三层架构模型时代还未兴起,因此数据访问层主要是对关系型数据库进行访问。在Java开发中,访问关系型数据库要通过统一的接口,即JDBC。通过JDBC可以无缝地切换至不同的数据库。常见的关系型数据库有Oracle、SQL Server、MySQL和DB2等,这些经典的关系型数据库也一直沿用至今。
然而存储于关系型数据库的二维关系表格数据与面向对象的域模型并不容易一一映射,因此出现了很多ORM(Object-Relationship-Mapping)框架。MyBatis及其前身IBATIS,JPA以及它的默认实现Hibernate,这些都是ORM领域中开源框架的翘楚。JPA是Java官方的持久化层规范,其完全以面向对象理念去操作数据库,这种方式虽然设计新颖,但实际用起来却略显笨重。因此,很多互联网公司都采用了更加轻量、可控性更高的MyBatis作为ORM框架的首选。
服务层用于编写应用的具体业务逻辑,它需要一个使用便捷且可以对数据访问层和Web层承前启后的框架。
Java官方推荐的EJB 2.X过于笨重,其中大量的XML配置以及烦琐的部署方式,使得它使用起来非常不便。虽然后来Sun公司又推出了EJB 3.X,在使用上简化了很多,但依然无法成为Java开发的标准。
由Rod Johnson这位业界大神开发的Spring Framework,极大地简化了Java EE的开发,它提供的IOC(控制反转)和AOP(面向切面编程)特性为开发者提供了便利,并且迅速地成了Java后端开发的实际标准。Spring Framework提供了一个容器,容器中的任何对象都以Bean的方式注入,它像胶水一样优雅地粘贴数据访问对象和其他第三方组件,它并不仅仅是一个定位于服务层的框架,而是一个贯穿于应用整个生命周期的生态圈。
Web层又叫MVC层,它用于分离前端展现和后端服务。由于Java的标准实现——Servlet侵入了大量的HttpRequest、HttpResponse、HttpSession等 API,导致基于Servlet开发的程序并不适合用于单元测试,而且实现配置、跳转、表单封装等操作时也需要做大量的重复工作,因此,很多MVC框架应运而生,用于改善开发流程。
常见的MVC框架有Strtus 1.X,以及基于WebWork封装的Struts 2.X和Spring MVC。初期Struts系列由于使用简单而备受青睐,后来Spring对MVC投入的力度越来越大,由于其更加清晰的设计理念以及强大的与Spring Framework融合的能力,使得它渐渐成为业界主流。
在这种All in One的集中式架构下,每个开发者都是全栈工程师。由Spring + Struts(Spring MVC)+ Hibernate组成的SSH框架套件,或由Spring + Struts(Spring MVC)+ IBATIS(MyBatis)组成的SSI框架套件,成了技术选型的主流。当时的软件工程方法论主要关注质量保证和设计灵活性,TDD(测试驱动开发)和DDD(领域驱动开发)也是时常被讨论的话题。
分布式架构、SOA和服务化
由于互联网应用规模迅速增长,集中式架构已无法做到无限制地提升系统的吞吐量,它只能通过增加服务器的配置有限度地提升系统的处理能力,这种伸缩方式被称为垂直伸缩。与之相对的伸缩方式被称为水平伸缩,水平伸缩能够仅通过增减服务器数量相应地提升和降低系统的吞吐量。这种分布式系统架构,在理论上为吞吐量的提升提供了无限的可能。因此,用于搭建互联网应用的服务器也渐渐放弃了昂贵的小型机,转而采用大量的廉价PC服务器。
分布式系统的引入,虽然解决了整个应用的吞吐量上限问题,但它并不是能够解决一切问题的“银色子弹”。分布式系统在带来便利的同时,也带来了额外的复杂度。
分布式场景下比较著名的难题就是 CAP 定理。CAP 定理认为,在分布式系统中,系统的一致性(Consistency)、可用性(Availability)、分区容忍性(Partition tolerance),三者不可能同时兼顾。在分布式系统中,由于网络通信的不稳定性,分区容忍性是必须要保证的,因此在设计应用的时候就需要在一致性和可用性之间权衡选择。互联网应用比企业级应用更加偏向保持可用性,因此通常用最终一致性代替传统事务的ACID强一致性。
随着分布式系统架构的普及,越来越多的互联网公司在重新审视一个并不崭新但却一直难于落地的概念,那就是SOA。
SOA 即面向服务架构,这是一个特别宽泛的概念,可以简单地认为 SOA 约等于“模块化开发 + 分布式计算”。SOA需要从宏观和微观两个不同的角度讨论。
宏观SOA面向高层次的部门级别、公司级别甚至行业级别,涉及商业、管理、技术等方面,设计时要全局考虑。SOA是面向宏观层面的架构,其带来的收益也最能在宏观层面体现出来,因此很多业界专家都认为SOA的概念过于抽象、不接地气。
微观SOA则面向团队和个人,涉及具体服务在业务、架构和开发方面的实施,在架构体系上包括服务治理、服务编排等内容。微观层面的SOA更容易实施。
由于SOA有相当大的实施难度和相当高的学习门槛,因此我们不妨先从一个小故事说起,从中“管窥”一点SOA的大意和作用。根据亚马逊前著名员工Steve Yegge的著名“酒后吐槽”事件可知,2002年左右,亚马逊 CEO 贝佐斯就在亚马逊内部强制推行了以下六项原则(摘自酷壳网)。
所有团队开发的程序模块都要通过Service接口将数据与功能开放出来。
团队间程序模块的信息通信都要通过上述接口。
除上述通信方式外,其他方式一概不允许使用:不能直接连接程序,不能直接读取其他团队的数据库,不能使用共享内存模式,不能使用他人模块的内部入口等。
任何技术都可以使用,比如HTTP、Corba、发布/订阅、自定义的网络协议等。
所有的Service接口,毫无例外,都必须从骨子里到表面上被设计成能对外界开放的。也就是说,团队必须做好规划与设计,以便未来把接口开放给全世界的程序员。
不按照以上要求做的人会被炒鱿鱼。
据说,亚马逊网站上展示产品明细的页面,可能需要调用上百个服务,以便生成高度个性化的内容。贝佐斯还提到,亚马逊的公司文化已经转变成“一切以服务为第一”,公司的系统架构都围绕这一宗旨构建。如今,这已经成为亚马逊进行所有设计的基础。贝佐斯的六项原则展示出了超强的信念和高远的眼光,即使放到十几年后的今天,依然令人感到醍醐灌顶。
由于分布式系统十分复杂,因此产生了大量的用于简化分布式系统开发的分布式中间件和分布式数据库,服务化的架构设计理念也被越来越多的公司所认同。
2011年前后,阿里巴巴开源的 Dubbo 框架成为对后世影响深远的一款分布式服务框架。Dubbo 官方文档公布了一张有关 SOA 系统演化过程的图片(见图1-3),彻底奏响了分布式和SOA时代的最强音。服务发现、负载均衡、失效转移、动态扩容、数据分片、调用链路监控等分布式系统的核心功能也一个个趋于成熟。
图1-3 SOA系统演化过程
自动化运维
随着分布式系统愈加成熟,应用的规模越来越大,系统构成也越来越复杂,服务器的数量迅速地从几十台、上百台增加到成千上万台。企业内部服务器数量的大幅增长,使得服务器出现故障的频次也大幅增加,手工运维时代的瓶颈随之到来。
运维工程师越来越难以远程登录每一台服务器去搭建环境、部署应用、清理磁盘、查看服务器状态以及排查系统错误,此时急需自动化运维体系与开发技术体系配合。自动化运维工具主要包括两大类:监控自动化工具以及流程自动化工具。
监控自动化工具可以对服务器的 CPU、内存、磁盘 I/O、网络 I/O 等重要配置进行主动探测监控,一旦指标超过或接近阈值则自动通过邮件、短信等方式通知相关责任人。使用Nagios、Zabbix等系统监控工具可以有效实现这一点。
流程自动化工具主要对服务器进行维护,同时实现应用上线部署等日常操作的自动化和标准化。Puppet、Chef、Ansible、SaltStack 等自动化运维管理工具的出现,快速地将运维工作推向自动化,让一名运维工程师可以很容易地维护成千上万台服务器。
解放交付的DevOps
分布式架构解决了互联网应用吞吐量的瓶颈;越来越成熟的分布式中间件也屏蔽了分布式系统的复杂度,提升了开发工程师的工作效率;自动化运维工具则提升了运维工程师的工作效率。但是,由于目标不同,在固有的将开发和运维划分为不同部门的组织结构中,部门之间的配合并不总是很默契的。
开发部门的驱动力通常是频繁交付新特性,而运维部门则更关注服务的可靠性。两者目标的不匹配使得部门之间产生了鸿沟,从而降低了业务交付的速度与价值。
直到DevOps方法论出现,开发与运维之间的鸿沟才得以渐渐消失。DevOps是可以帮助开发工程师和运维工程师在实现各自目标的前提下,向最终用户交付价值最大化、质量最高的成果的一系列基本原则。DevOps在软件开发和交付流程中强调“在产品管理、软件开发以及运维之间进行沟通与协作”。
DevOps是一种公司文化的变迁,它代表了开发、运维和测试等环节之间的协作,因此多种工具可以组成一个完整的DevOps工具链,如图1-4所示。
图1-4 DevOps工具链
1.1.3 从分布式架构到云原生架构
随着虚拟化技术的成熟和分布式架构的普及,用来部署、管理和运行应用的云平台被越来越多地提及。IaaS、PaaS和SaaS是云计算的三种基本服务类型,分别表示关注硬件基础设施的基础设施即服务、关注软件和中间件平台的平台即服务,以及关注业务应用的软件即服务。容器的出现,使原有的基于虚拟机的云主机应用,彻底转变为更加灵活和轻量的“容器+编排调度”的云平台应用。
新纪元的分水岭——容器技术
在过去几年里,云平台发展迅速,但其中困扰运维工程师最多的,是需要为各种迥异的开发语言安装相应的运行时环境。虽然自动化运维工具可以降低环境搭建的复杂度,但仍然不能从根本上解决环境的问题。
Docker的出现成为了软件开发行业新的分水岭,容器技术的成熟也标志着技术新纪元的开启。Docker提供了让开发工程师可以将应用和依赖封装到一个可移植的容器中的能力,这项举措使得Docker大有席卷整个软件行业并且进而改变行业游戏规则的趋势,这像极了当年智能手机刚出现时的场景——改变了整个手机行业的游戏规则。Docker通过集装箱式的封装方式,让开发工程师和运维工程师都能够以Docker所提供的“镜像+分发”的标准化方式发布应用,使得异构语言不再是捆绑团队的枷锁。
新纪元的编排与调度系统
容器单元越来越散落使得管理成本逐渐上升,大家对容器编排工具的需求前所未有的强烈, Kubernetes、Mesos、Swarm等为云原生应用提供了强有力的编排和调度能力,它们是云平台上的分布式操作系统。
Kubernetes 是目前世界范围内关注度最高的开源项目,它是一个出色的容器编排系统,用于提供一站式服务。Kubernetes 出身于互联网行业巨头——Google,它借鉴了由上百位工程师花费十多年时间打造的Borg系统的理念,安装极其简易,网络层对接方式十分灵活。
Mesos则更善于构建一个可靠的平台,用来运行多任务关键工作负载,包括Docker容器、遗留应用程序(如Java)和分布式数据服务(如Spark、Kafka、Cassandra、Elastic)。Mesos采用两级调度的架构,开发人员可以很方便地结合公司的业务场景定制Mesos Framework。
其实无论是Kubernetes还是Mesos,它们都不是专门为了容器而开发的。Mesos早于Docker出现,而Kubernetes的前身Borg更是早已出现,它们都是基于解除资源与应用程序本身的耦合限制而开发的。运行于容器中的应用,其轻量级的特性恰好能够与编排调度系统完美结合。
唯一为了Docker而生的编排系统是Swarm,它由Docker所在的Moby公司出品,用于编排基于Docker的容器实例。不同于Kubernetes和Mesos,Swarm是面向Docker容器的,相较于Kubernetes面向云原生PaaS平台,以及Mesos面向“大数据+编排调度”平台,Swarm显得功能单一。在容器技术本身已不是重点的今天,编排能力和生态规划均略逊一筹的Swarm已经跟不上前两者的脚步。
Kubernetes和Mesos的出色表现给行业中各类工程师的工作模式带来了颠覆性的改变。他们再也不用像照顾宠物那样精心地“照顾”每一台服务器,当服务器出现问题时,只要将其换掉即可。业务开发工程师不必再过分关注非功能需求,只需专注自己的业务领域即可。而中间件开发工程师则需要开发出健壮的云原生中间件,用来连接业务应用与云平台。
架构设计的变革——微服务
单体应用虽然简单且深入人心,但是随着越来越多的应用被部署到云端,它的劣势也体现得愈加明显。因为应用变更的范围和周期被捆绑在一起,因此即使只变更应用的一部分,也需要重新构建并部署整个单体应用,而且扩展时无法只对需要更多资源的部分模块进行单独扩展,必须将应用整体扩展。这种粗粒度的划分,不利于对系统进行管理,也不利于资源的充分利用。因此,人们越来越倾向于将应用进行合理的拆分。
在过去的几年中,微服务已经迅速成为了技术圈最热门的术语之一,微服务是一种架构风格,它将一个复杂的单体应用分解成多个独立部署的微型服务,每个服务运行在自己的进程中,服务间的通信采用轻量级通信机制,如 RESTful API。服务可以使用不同的开发语言和数据存储技术。通过服务拆分,系统可以更加自由地将资源分配到所需的应用中,而无须直接扩展整个应用。图1-5直观地展现了单体应用与微服务的区别。
图1-5 单体应用与微服务的区别
采用微服务架构风格的团队将围绕业务组织团队,而不是围绕技术组织团队,这一点和DevOps有异曲同工之妙。实施微服务前的组织结构如图1-6所示,对于集中式架构而言,拆分大型应用通常需要在技术层面上设立UI团队、后端开发团队、数据库团队。在这种团队划分方式下,即使进行简单更改也会导致协作团队垮掉。
微服务架构风格则采用围绕业务线进行划分的方式,以保证一个团队中能拥有UI工程师、开发工程师、DBA和项目经理。实施微服务后的组织结构如图1-7所示。
微服务的优势是通过清晰的模块边界构建易于理解的架构风格,它可以让每个服务具有独立部署、与开发语言无关的能力。分布式系统的开发成本和运维开销则是伴随微服务的普及而需要付出的代价。
图1-6 实施微服务前的组织结构
图1-7 实施微服务后的组织结构
相比于集中式架构,微服务架构需要额外处理的分布式开发和运维工作包括以下几点。
配置管理。相比于集中式架构的属性文件配置方式,微服务架构更加倾向于使用集中化的配置中心来存储配置数据。配置中心不一定在任何时候都是100%高可用的,大部分时间,配置是从客户端的缓存中读取的,如果配置中心恰好在配置修改时不可用,就会带来很大的影响,导致配置修改无法及时生效。配置修改要想及时生效,配置中心必须有推送配置变更事件的能力。如果配置中心是高可用的,也要慎重考虑如何保证多个配置中心间的数据一致性。
服务发现。单体应用的服务是可数且可人工运维的,而对于基于微服务架构的应用而言,其服务数非常多,数不胜数。因此,微服务框架要具有服务发现的能力。一般情况下,服务发现是通过向注册中心注册服务实例的运行时标识以及对其进行监听并反向通知其状态变化来实现的。
负载均衡。与服务发现类似,大量的微服务应用实例无法通过静态修改负载均衡器的方式进行运维,因此需要反向代理或使用客户端负载均衡器配合服务发现动态调整负载均衡策略。
弹性扩缩容。这是集中式架构所不具备的能力,即能够在流量洪峰期通过增加应用实例的水平伸缩来增强服务的处理能力,并且能够在流量回归正常时简单地关闭应用实例,平滑地将多余的资源移出集群。
分布式调用追踪。大量微服务应用的调用和交互,需要依靠一套完善的调用链追踪系统来实现,包括确定服务当前的运行状况,以及在出现状况时迅速定位相应的问题点。
日志中心。在微服务架构中,散落在应用节点上的日志不易排查,而且随着应用实例的销毁,日志也会丢失,因此需要将日志发送至日志中心统一进行存储和排查。
自愈能力。这是一个进阶功能,如果微服务应用可以通过健康检查感知各个服务实例的存活状态,并通过系统资源监控以及SLA分析获知应用当前的承载量,同时应用本身具有弹性扩缩容能力且微服务管控系统具有自动服务发现以及调整负载均衡的能力,那么便可以根据合理的调度策略配置通过调度系统来自动增加、关闭和重启应用实例,达到系统自愈的效果,使系统更加健壮。
在容器技术开源社区、编排系统开源社区的推动,以及微服务等开发理念的带动下,将应用部署到云端已经是不可逆转的趋势。随着云化技术的不断发展,云原生的概念也应运而生。在现有业务代码不变的情况下,要想让分布式系统无缝入云,需要改变的就是中间件。因此,从分布式中间件向云原生中间件变迁,便是本书的重点。