Java环境下的微服务
本文涉及的内容,能让你学到什么?
本书适用于开发微服务的Java开发人员和架构师。我们在开始介绍微服务架构前,先讲述一些抽象的基本概念。不幸的是,使用新技术并不能神奇地解决分布式系统问题。但是我们通过一些做的很好的公司,它们是如何使用微服务来进行构建的,包括文化、组织结构和市场压力。然后我们深入了解几个Java微服务框架,附带的源代码反馈可以在GitHub上找到。我们会讨论有关部署、集群、故障转移以及Docker和Kubernetes在这些领域是如何解决这些问题。随后我们会重点介绍一些使用Docker,Kubernetes和NetflixOSS的示例,以演示微服务架构。我们这么少的章节无法讨论所有的问题,但不代表除了这些其他的都不重要,比如:配置、日志记录和连续交付。
微服务不仅仅是技术上的讨论。 微服务的实现源于复杂自适应理论,服务设计,技术演进,领域驱动设计,依赖考量等背景。 它们允许使用这些技术的组织,能够真正地展示敏捷、快速响应并在快速发展的商业世界中保持竞争力。 让我们近距离的来看看吧。
你实际是在为一家软件公司工作
软件正在统治这个世界。企业正在慢慢意识到这一点,这个现象有两个主要的驱动因素:通过高品质的服务和技术的快速投产来提供价值。 这本书主要是涉及到动手实践,但在我们深入了解技术之前,我们需要适当地了解整个环境以及关键因素。近年来我们一直在谈论让企业如何变得敏捷,但我们需要充分了解这是什么意思,否则一切都是都是白搭。
服务的价值
100多年来,我们的商业市场一直在创造产品,并推动消费者想要这些产品:书桌,微波炉,汽车,鞋子等等。 “生产者主导”经济背后的理念来自于亨利·福特(Henry Ford)的观点:“如果您能以低成本生产大量产品,市场几乎是无限的”。为了达到目的,还需要几个单向渠道直接向客户进行营销,以说服他们他们需要这些产品,并且他们的生活将会由这些产品得以大大的改善。在20世纪的大部分时间里,这些单向渠道以电视、报纸、杂志广告和高速公路广告牌等形式存在。然而,这个生产者主导的经济已经翻篇了,因为市场中充满了过剩的产品(你需要多少手机/汽车/电视机?)此外,互联网以及社交网络正在改变公司如何与消费者(或更重要的是消费者如何与他们互动)的交互渠道。
社交网络使作为消费者的我们能够更自由地彼此分享我们对于公司产品的看法和意见。与营销渠道相比,我们更加相信自己的家人和朋友。这就是为什么我们去社交媒体选择餐馆、酒店和航空公司。我们以点评、评价以及朋友圈等等形式进行反馈,好的反馈能够对公司的品牌产生积极的推动作用,反之,能让某些公司伤透脑筋。现在,公司和消费者之间的双向关系前所未有,公司需要更加努力的经营自己的品牌。
后工业公司正在学习如何培养和客户之间的关系(使用双向渠道沟通),并通过这些关系让客户了解到自己的产品如何为他们带来价值。公司通过服务、客户体验和反馈来提供持续维系这些关系。客户根据对于自身的价值和体验来选择哪些服务消费。以Uber为例,它本身并不拥有任何库存和售卖的商品,坐在别人的车里,对于我并没有任何价值,但通常会由我想要去某个地方(例如商业会议)而带来价值。通过这种方式,Uber和我通过对服务的使用来创造价值。展望未来,公司将需要专注于为客户提供有价值的服务,而服务的形式也包括了信息技术服务。
技术的商品化
技术与经济、生物和法律相似,有繁荣与萧条循环周期,技术引起了伟大的创新,如:蒸汽机、电话和计算机。在竞争激烈的市场中,不断变化的创新需要大量的投资和建设才能快速适应各自的市场。而这将带来更多的竞争、更强的能力和更低的价格,最终使创新技术成为商品。持续创新和改进将成为不断的循环。 这种商品化使我们经历了大型机、个人计算机,直到现在所谓的“云计算”,而它的出现使我们的商业计算几乎没有前期的资本投入。在云计算的基础上,我们正在为技术服务带来新的创新。
开源技术也是技术领域的引领者。随着时间的推移,开发人员可以使用开源技术用很低的成本构建一个原来需要昂贵专有软件才能构建的产品。这驱使社区构建了诸如:操作系统(Linux)、编程语言(Go)、消息队列(Apache ActiveMQ)和Web服务器(httpd)等知名的产品。即使是最初拒绝开源的公司也开源他们自己的技术并为现有社区做出贡献。随着开源和开放的生态系统已经成为常态,我们开始直接从开放源码社区(例如:Apache Spark、Docker和Kubernetes)看到软件技术的大量创新。
纷乱
服务设计与技术的进步,能使更多的人开始创建自己的服务,可以学习编程,使用框架,并且利用按需计算(serverless)进行服务的构建。可以利用微博、微信渠道与客户进行免费的沟通。随着我们市场的快速发展,任何一个创业公司的新点子的有效实施,都会使得原有公司的业务死的很惨。
而这个事实让大多数CIO和CTO感到害怕。随着软件成为成为公司构建服务,经验和差异化的关键因素,许多人意识到他们必须成为各自垂直行业的软件公司。以前企业将IT视为成本,而今发现它已经变为与对手比拼的关键,为了不落后于对手,他们必须变得敏捷起来。
拥抱组织结构的敏捷
大多数诞生于20世纪(工业时代)的公司并不是以敏捷的方式进行构建。它们的构建是为了最大限度地提高效率、减少流程中的变化、消除工人的创造性思维,并将工作人员组装成流水线的方式。整个过程被构建成类似机器一样,接受输入,经过高度定制调优的流程,完成生产输出。这种自上而下的分级管理结构,对于变化也是自上而下的思考,来自底层的信息经历了许多层面的管理和传达,最终到达高层,经过高层的决断后再下派到下层,而这一过程一般需要18个月左右的周期。这种组织方法能够极大的进行产品生产并尽可能的压榨流程中的每一点效率,但它并不适合用来提供服务。
客户可不能放置在生产线上,他们想要什么时候出现就什么时候出现,他们喜欢和客户服务进行对话,而非一个自助电话系统。他们不会按定义好的菜单来填写,而是简单的对话输入,如果让他们等很久,他们会疯。
这意味着我们面向客户的服务需要考虑更多的变化,他们需要面对任何不可预知的输入。客户希望通过你提供的服务进行对话,如果该服务不足以满足他们的需求,那么就需要明确快速的反馈给他们有关如何解决的办法。该反馈可以由服务的维护者来把握,他可以快速调整服务和交互模型,以更好地适应客户,而不用等待自上而下的18个月规划周期。你需要你的前线业务团队能够快速的做出反应,需要自主、目标驱动和自组织的团队,他们负责为客户(付费客户、业务合作伙伴以及同行团队等)提供吸引人的用户体验。快速反馈周期、自主团队、共同目标和谈话是组织必须遵守的先决条件,只有这样企业才能够在未知的后工业时代生存下去。
说了这么多,没有一本微服务的书会这么介绍对于组织敏捷性的期望,但是会提到康威定律。
任何设计系统的组织,必然会产生以下设计结果:即其结构就是该组织沟通结构的写照。简单来说: 产品必然是其组织沟通结构的缩影。
Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization's communication structure.
要建立敏捷软件系统,我们必须从构建敏捷的组织结构开始。敏捷的业务结构将有利于微服务技术的落地,但是构建分布式系统很困难,在接下来的部分中,我们将介绍在构建和设计这些服务时必须牢记的问题。
什么是微服务架构?
微服务架构(MSA)是一个使用服务的理念,将旧有的软件系统按照领域模型拆分成小规模、内聚的,且有上下文边界的设计方法论。这些服务相互隔离、自治,它们通过相互通信来完成业务功能。微服务是一个便于小团队开发的模式,它允许各个自治的小团队采用各自擅长的技术来实现服务,而不会给整个系统带来冲击。
团队之间通过承诺来进行沟通,这种方式的目的是将服务发布的内容和计划让其他组件知晓,使他们能够使用到自己的服务,而描述这些承诺就是需要足够的wiki以及文档。如果文档不够好、API定义也不清晰,那么服务提供方就没有做好他们的工作。
每个团队都有涉及服务、技术选型、解决问题的职责,他们会在部署、管理服务上花时间,甚至可能会在凌晨2点起床处理问题。例如:在亚马逊,有一个单独的团队负责税率计算功能,而这个服务会在订购时被调用。这个服务的模型(Item,Address,Tax等)在团队内大家都心知肚明,这个团队拥有税率计算服务的设计、开发和运维的权限。围绕这些,亚马逊也提供了非常成熟的自动化技术,用于服务的构建、部署和运维。
我们能够使用微服务来定义服务的范畴,它能帮助我们便于:
- 理解具体服务的工作,避免一开始陷入到大应用的过多概念中
- 本地快速构建服务
- 为问题域采用最适合的技术(写多?读多?低延时?高并发?)
- 测试服务
- 和其他的服务保持独立的构建、部署和发布的节奏
- 能够按需的识别出架构中需要伸缩的部分
- 能够平滑的升级系统
微服务帮助我们解决: 我们怎样能够快速响应,怎样解耦服务以及组织团队 的问题。它允许团队专注于提供的服务以及面对问题的快速响应,而避免了不必要的技术或者组织同步。下面这些内容是你采用了微服务架构后,不会遇到的问题:
- 跨工程的需求
- 不必要的会议
- 共享类库
- 企业级共享模块
微服务架构是否适合你?微服务架构提供了很多好处,但是它也带来了它自己的不足之处。你可以认为微服务是一种处理问题有偏向性的优化方式,它专注于问题的快速和可伸缩的解决,但并不一定是成本最低的解决方式,它可能造成资源的集中,或许从开发者的角度去看,或多或少有些结构上的重复。运维操作嘛,会有一些复杂度,而且微服务架构实施后,会使得开发人员不是那么容易从全局看清整个系统,这使得进行问题调试的想法变得很困难,在某些场景下你必须对强事务做出一定的牺牲,而最重要的开发团队对这种开发模式并不一定熟悉。
不是每一块业务都是能够使用很少的资源进行改造为微服务架构的,也许面向客户的前台系统可以,但是后台支撑系统就没那么容易。但假设两种模式的系统一旦混合在一起使用,我们能够发现微服务架构能够慢慢的影响到整个系统的其他部分,让它们慢慢改变。
挑战
用微服务架构设计云端和本地的应用需要思考它们在构建、部署和运维上的不同,我们不能及其悲观的看待每一次微服务调用,用容错等方式处理它。而事实上,那些用微服务架构组织的复杂系统,确实需要我们去应对不确定性,在这个章节将会介绍5个主要的相关方面,而在后续开发微服务时,需要时刻记在心上。
应对失败(design for failure)
在复杂的系统下,任何内容都是不可靠的,磁盘的损坏,网线插口被拔掉,在生产环境的数据库上进行维护,vm无缘无故的消失。而这些单点的故障会造成系统部分功能的问题,进而导致系统层层出现问题,最终造成整个系统不可用。
在构建应用时,一般传统做法是我们先预想我们的应用可能在那个地方出现问题,然后在问题可能出现的点进行预防措施,比如创建一些拦截的手段来使当前处理得以执行,避免直接失败。这种处理方式是以问题为导向的处理方式,因为我们在一个复杂系统中无法准确的预知哪里会出现问题。但是问题总会出现,所以我们需要让应用能够优雅的处理失败而不是防止它发生,我们应该能够优雅的处理失败,而不是看着它不断向上抛出,最终导致整个系统崩溃。
构建一个分布式系统远比构建一个共享内存、单进程的单体应用要来的复杂,一个重要的难点在于沟通方式从共享内存变为了网络调用,而网络调用不总是那么可靠。网络调用失败的原因有很多种(例如:网络信号的长度、缆线、路由器或者交换机的损坏,以及防火墙等),而且网络调用容易成为一个主要的瓶颈。网络调用除了不可靠以外,还有可能造成对于性能产生潜在的影响,下游的响应时间对你的服务会产生影响,同时也会导致上游系统出现问题。
网络调用一般意义上很难调试,理想情况下,如果你的网络调用不能完全成功,那么它们应该立即失败,这样应用程序就能立即捕获到这个问题(例如:IOException),当捕获到这个问题,我们就可以立刻采取行动,提供降级的行为或者返回一个消息让用户稍后再试。但是网络错误在分布式环境下远远没有这么简单,试想如果下游的应用处理请求的时间比平时常很多怎么办?这种致命的场景将会成为拖慢你的应用的瓶颈,下游的网络调用超时,将会使你的服务被慢慢拖住,最终导致服务的不可用,而由于你的服务挂在那里,从而导致你的上游也变慢,最终级联的问题导致整个分布式系统出现问题。
依赖设计(design with dependencies)
从一个组织或者站在一个分布式系统的角度,为了能够快速响应、敏捷,我们需要在设计系统时考虑依赖这个影响因素。在内部团队的组织中、技术选择上以及团队治理上,我们需要松耦合。使用微服务架构的一个目标是利用团队自治、服务自治的优点,这意味着能够在不影响到整个系统的前提下,使我们能够快速的响应用户需求。这也意味着我们依赖的服务,当它们不可用或者降级时,我们能够优雅的处理这些问题。
在《Dependency Oriented Thinking》这本书中,作者 Ganesh Prasad 对于依赖这个话题,说“创新的一个原则就是丢弃约束,换句话讲,你可以认为解决一个以往的问题而减少了一个或者多个依赖”。而问题就是我们能够在脑海中快速的构建组织结构,但是它会在系统设计上带来很多的零散依赖。
例如:当我们需要变更服务时,首先需要咨询其他的几个团队(DBA,测试和安全),这样会显得非常麻烦,和任何一个团队的沟通都是同步的,任何沟通的延迟,都会造成项目的延迟。如果能够将这些依赖的团队整合到你的团队中,你就能够减少这些零碎的步骤,可以更快的解决用户的需求,避免在人上面耗费资源。
另一个依赖管理的故事是针对遗留系统。将遗留系统的细节暴露给下游系统是一场灾难,这样做一个小的变动(比如客户ID长度从16变为20)就会使下游系统收到影响。我们需要仔细考虑如何同其他系统之间保持隔离。
领域设计(design with the Domain)
几个世纪以来,我们一直使用建模这个工具来简化和理解问题,例如:手机上使用的GPS地图软件就是进行城市中导航的绝佳模型,但是这个模型却在从一个城市飞往另一个城市的飞行旅行中显得力不从心,此刻最好的模型是给出合适的方位、地标和航线信息。从这个例子中可以看出,模型的定义离不开它的语境,不能偏离它处理的问题域,更深入的说没有模型是万能的,模型只有在上下文语境中才有意义。Eric Evans 具有开创新的著作 《Domain-Driven Design》 (Addison-Wesley, 2004) 指导我们如何为复杂的业务过程构造模型,并将其转换为软件系统。最终软件中最复杂的部分不是技术本身,而是运行时操作那些那些有歧义、循环或相互矛盾的业务模型。人类能够通过给定一个上下文语境来理解模型,但是计算机需要更多的提示和帮助,这些模型和上下文语境必须都打包入软件,如果我们能这样建模,那么业务逻辑在任何时候做出修改,我们就能快速的在软件实现中定位它们。在软件实现中定位了这些变动,通过修改模型等方式响应了需求的变化,而这个迭代的过程需要尽可能的快速。
其实这就是应用划分的一个策略,比如做交易的订单中心,商品中心,这些都是模型和上下文的复合体
在 Evans 的书中,对于领域建模的一个工具就是识别和明确的区分不同的模型,并将它们各自聚合在具备边界的上下文中。
一个具备边界的上下文就是一个模型的集合,而其中的模型是业务逻辑的简化和通信。例如,当我们努力的提高效率时又要兼顾扩展性(听起来很熟悉)。在一个简单的汽车部件应用程序中,我们尝试提出整个领域的统一的“规范模型”,我们最终得到了像Part
、Price
和Address
这样的对象。如果库存应用程序使用Part
对象,它将指的是类似“制动器”或“车轮”的具体类型。而在汽车质量保证系统中,Part
可能是指具有序列号和唯一标识符,它用来完成跟踪某些质量测试结果等工作。我们尝试重用同一个规范模型,但库存跟踪和质量保证的问题是对于Part
对象存在语义上不同。在具备边界的上下文中,Part
将被明确地建模为PartType
,并在该上下文中被理解为Part
的类型,而不是Part
的特定实例。通过两个单独的具备边界的上下文,这些Part
对象可以在自己的模型中一致地发展,而不是以奇怪的方式相互依赖,因此我们已经达到了敏捷性或灵活性。
深入理解领域对象需要时间,可能需要几次迭代才能充分识别业务模型中存在的不同点,并将其妥善拆分,使其能够独立修改。构建微服务架构至少面对着一个困难,那就是如何雕琢一个巨石应用,因为在这个巨石中有非常多的逻辑,你的任务就是识别它其中的不同,然后雕琢它。注意,在一个全新的项目中,你无法完成雕琢。事实上,所有的微服务成功实践者(例如:亚马逊、Netflix)都是在雕琢巨石应用的过程中实践微服务架构的,而不是一开始就用微服务的方式去解决问题。
承诺设计(Design with Promises)
自治的团队和服务在微服务环境中是基本组成,但是服务提供者和消费者的关系也非常重要,在自治的开发模式下,开发人员将无法把责任定给其他的服务或者团队,因为你没有拥有它们,它们通过定义关系来做到自治。你所能做的就是选择是否接受提供服务功能性的承诺,作为服务的提供者,所能做的就是对服务有确定预期的承诺。承诺理论(Promise theory)在 Mark Burgess 2004年提出,并在其的书 《In Search of Certainty》 (O'Reilly, 2015) 中进行了阐明,它研究了自治系统(包括了:人、计算机和组织)之间相互提供服务的问题。
在分布式系统的场景下,承诺(Promise)将更能清楚的表达一个服务能提供什么,能更清楚的假设能做到什么,不能做到什么。例如:我们团队维护了图书推荐服务,我们承诺为每个用户提供不同的图书推荐。当业务方请求我们的接口时,如果我们的后端依赖的产品挂了该如何是好(存储用户图书推荐关系的数据库)?我们可以抛出一个异常给业务方,但是体验不太好,因为可能会让调用方的系统出现问题。因为我们做出了承诺,我们就竭尽全力的维护这个承诺,包括返回一个默认的图书列表。有时确实无法维护承诺,那么最好的方式是返回用户期望的结果。这里的关键在于我们的服务,尽量维护其承诺(返回一些图书推荐),即使我们的依赖服务不能维护其承诺(数据库已经挂了)。在维护承诺的过程中,这也有助于让系统的其余部分正常工作,并维护其他服务的承诺。
对于承诺(Promise)的另一个视角是换位思考(比如交换服务提供者和消费者),但是我们如何决定这两边的价值,以及如何做出承诺呢?如果没有消费方调用我们的服务,获取我们的数据,那么提供的服务是否还有价值呢?有一种方式用来描述消费者和服务提供者之间的承诺,就是使用consumer-driven contracts。使用这个方式,我们能够捕获到我们承诺的价值,使用它能够测试我们是否能维护我们的承诺。
洋人说了这么多,其实价值不大,简单说就是服务设计要取中庸之道,即不能让消费者麻烦,也不要坑了自己的未来
分布式系统管理
当下,管理一个单一系统(一台机器)要比管理一个分布式系统显得容易许多。如果只是一台机器,一个应用服务器,如果出现问题,我们立刻就知道该做什么。如果你需要做一个配置变更,或者升级软件,这些内容就一定在一个物理或者逻辑位置,改动它就好了,管理、调试和变更都显得很容易。一个单一系统在某些场景下非常常见,但是如果堆伸缩性有要求,那就显得力不从心了,这时微服务架构就登场了。就像我们之前讨论的,微服务并没有那么便宜,它是一次价值交换,我们使用系统管理的复杂性来换取柔性和伸缩性。
下面是一些关于微服务部署和管理的常见问题:
- 如何启动和停止一批服务?
- 如何能够跨服务的手机日志、指标和SLA?
- 如何在一个具备弹性的环境中发现服务?
- 如何做负载均衡?
- 如何察觉整个集群和某个服务的健康度?
- 如何重启一个具备故障迁移的服务?
- 如何做细粒度的服务路由?
- 如何应对服务的安全性?
- 如何在集群的部分机器出现崩溃或者行为异常时减少对他们的请求或者进行隔离?
- 如何部署服务的不同版本,然后通过路由解决?
- 如何在大量服务中完成配置变更?
- 如何能够安全且可复制的修改应用代码或者配置?
这些都不是简单的问题,本书接下来的部分致力于Java开发者如何使用微服务并解决上述的部分问题,而全部的解决方案将会在本书的第二版中涉及。
技术解决方案
本书借来来的部分将会介绍一些流行的技术组件,用它们来帮助我们解决微服务架构下开发和交付场景下遇到的问题。微服务的采用并不是一个技术问题,而需要正确的组织架构和团队组建方式,这些是首要的问题,将SOAP切换到REST并不是微服务架构的实践。
对于Java开发第一步,先在本地创建微服务,本书介绍了三款出名的Java微服务框架:Spring Boot、Dropwizard和WildFly Swarm。其中每个框架都有各自的维护方,属于不同的组织,也有不同的方式来开发微服务。因为是大部分团队使用,所以选择的框架都是通用性或较为常用的,但这并不代表着只有这些框架。有一些基于响应式的微服务框架,例如:Vert.x和Lagom,使用基于事件模型方式进行编程,与普通的开发模型有些不同,同时也有更陡峭的学习曲线,本书还是使用大多数企业Java开发人员习惯的开发框架。
本书的目标是让读者能够使用上述框架的最基本特性,我们将在最后一节深入一些高级概念,但是在这些框架的最初使用阶段,我们会创建一些hello-world级别的简单应用。本书不是一个针对微服务面面俱到的参考书,在每章结束的地方会给出一些继续深入学习的参考链接,我们会在hello-world级别的应用上不断的迭代增加一些特性,让读者对一些模式有更加深化的认识。
在这些框架的最后迭代过程中,将会演示如何实现承诺(Promise)等技术,使我们的服务能够面对失败的场景。我们将深入部分 NetflixOSS 的产品,例如:Hystrix ,它让我们更容易的实现这些内容,而我们将会从服务端和客户端不同途径来探索有何种更好的方式。
当完成了所有的示例,我们也会探讨Linux容器为微服务的部署、管理以及隔离带来的价值。Docker和Kubernetes为可伸缩的分布式系统带来了卓有成效的简化,我们将围绕容器和微服务的最佳实践展开讨论。
在本书的最后部分,我们遗留了一些你会更深入学习的话题,比如:分布式配置、日志、指标和持续交付。
准备你的环境
我们将使用Java 1.8 来构建所有的示例,确保你的环境比以下罗列的内容更新:
- JDK 1.8
- Maven 3.2+
- 命令行控制(bash, cmd, Cyg-win, etc)
Spring生态中有很多出色的工具,可以选择命令行或者IDE集成的方式使用它们。以Spring Boot为例,我们使用Spring CLI v1.5.4.RELEASE。
在IDE中可以替代的工具有:
而对于Dropwizard和WildFly Swarm,我们使用Jboss Forge CLI通过插件的方式,以交互式的方式来创建工程:
在Mac下,可以通过
brew upgrade jboss-forge
安装JBoss Forge
在IDE中可以替代的工具有:
- Eclipse based IDE: JBoss Developer Studio
- Netbeans
- IntelliJ IDEA
最后,当我们将微服务以Docker容器的方式部署在Kubernetes中时,我们需要在对应的环境下安装以下工具:
- Vagrant 1.8.1
- VirtualBox 5.0.x
- Container Development Kit 2.x
- Kubernetes/Openshift CLI
- Docker CLI (optional)
由于作者是redhat旗下的工程师,以redhat产品为主,所以这些环境的安装笔者出于通用性将其做了一些更改,以下内容以笔者的环境为准: