书名:Spring微服务实战(第2版)
ISBN:978-7-115-58748-0
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
著 [美] 约翰•卡内尔(John Carnell)
[哥斯] 伊拉里•华卢波•桑切斯(Illary Huaylupo Sánchez)
译 陈文辉
责任编辑 孙喆思
人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
读者服务热线:(010)81055410
反盗版热线:(010)81055315
读者服务:
微信扫码关注【异步社区】微信公众号,回复“e58748”获取本书配套资源以及异步社区15天VIP会员卡,近千本电子书免费畅读。
Original English language edition, entitled Spring Microservices in Action, Second Edition by John Carnell and Illary Huaylupo Sánchez published by Manning Publications, USA. Copyright © 2021 by Manning Publications.
Simplified Chinese-language edition copyright © 2022 by Posts & Telecom Press. All rights reserved.
本书中文简体字版由Manning Publications授权人民邮电出版社独家出版。未经出版者书面许可,不得以任何方式复制或抄袭本书内容。
版权所有,侵权必究。
本书以一个名为O-stock的项目为主线,介绍云、微服务等概念以及Spring Boot和Spring Cloud等诸多Spring项目,并介绍如何将O-stock项目一步一步地从单体架构重构成微服务架构,进而将这个项目拆分成众多微服务,让它们运行在各自的Docker容器中,实现持续集成/持续部署,并最终自动部署到云环境(AWS)的Kubernetes集群中。针对在重构过程中遇到的各种微服务开发会面临的典型问题(包括开发、测试和运维等问题),本书介绍了解决这些问题的核心模式,以及在实战中如何选择特定Spring Cloud子项目或其他工具(如Keycloak、Zipkin、ELK技术栈)解决这些问题。
本书适合拥有构建分布式应用程序的经验、拥有Spring的知识背景以及对学习构建基于微服务的应用程序感兴趣的Java开发人员阅读。对于希望使用微服务构建基于云的应用程序,以及希望了解如何将基于微服务的应用部署到云上的开发人员,本书也具有很好的学习参考价值。
微服务旨在解决传统的单体架构应用所带来的顽疾(如代码维护难、部署不灵活、稳定性不高、无法快速扩展)。从微服务这一概念提出至今,业界已经涌现一批帮助实现微服务的工具,而Spring Cloud无疑是其中的佼佼者,这不仅是因为Spring在Java开发中的重要地位,更是因为它提供一整套微服务实施方案,包括服务注册、服务发现、分布式配置、客户端负载均衡、服务容错保护、网关、安全、事件驱动、分布式服务跟踪等一系列久经检验的工具。
本书对微服务的概念进行了详细的介绍,并介绍了微服务开发过程中遇到的典型问题,以及解决这些问题的核心模式,在这一过程中,介绍了在实战中如何选择特定Spring Cloud子项目解决这些问题。本书非常好地把握了理论和实践的平衡。相信读者阅读完本书之后,会掌握微服务的概念,明白如何在生产环境中实施微服务架构,学会在生产中运用Spring Cloud、容器等工具,最终将项目自动部署到云环境中。
“旧时王谢堂前燕,飞入寻常百姓家。”得益于Spring Cloud一系列项目提供的开箱即用的工具,如今,许多公司将自身产品从单体架构换成了微服务架构。然而,“单体架构”并非贬义词,“微服务架构”也不是银弹。首先,微服务架构作为一种分布式架构,必然会带来分布式架构的固有的难题,如分布式事务。其次,由于系统中存在诸多微服务,微服务架构需要企业内部强大的DevOps能力作为支撑,否则会给开发和运维带来更多的难题。每一种工具只有在合适的背景下,才能发挥出自己的优势。项目是否应该采用微服务架构,应该从产品使用人数、开发成本、企业内部DevOps能力、组织架构等多个方面进行思考,切不可盲目随大流。本书作者介绍了微服务架构的一些权衡,为微服务开发提供了一套指南,相信读者可以从中有所收获。
虽然翻译本书花费了我大量的业余时间,但我也在这个过程中学到了许多。感谢人民邮电出版社编辑们在翻译过程中对我的指导与指正。同时,我想要感谢我的爱人在这个过程中对我的支持与奉献,还要感谢我的小孩,你们是我坚持的动力来源。
限于时间和精力,也囿于我本人的知识积累,在翻译过程中难免犯错。如果读者发现本书翻译中存在哪些不足或纰漏之处,欢迎提出宝贵意见。读者可以通过memphychan@gmail.com联系我。希望本书能够对读者有用!
陈文辉
2021年12月于山东
我的梦想是为我最热衷的领域——计算机科学,尤其是软件开发的发展做出贡献,而这本书是我梦想的一部分。在相互联系的全球当下,计算机领域展露出其非凡的重要性。在人类活动的各个领域,我们每天都能看到这些计算机领域的学科令人难以置信的变化。但是,既然有很多其他主题可以写,为什么还要写微服务架构这一主题呢?
“微服务”这个词有很多解释。但在本书中,我将微服务定义为分布式、松耦合的软件服务,它执行少量定义良好的任务。微服务逐渐成为单体应用程序的替代方案,它通过将大型代码库分解为小的、定义良好的部分,帮助解决传统的大型代码库的复杂性问题。
在我十几年的工作经验中,我致力于软件开发,使用不同的语言和不同类型的软件架构。我踏入开发之旅时使用的架构现在实际上已经过时了。当代世界迫使我们不断更新自己的知识,软件开发领域也在加速创新。出于对最新知识和实践的探索,我在几年前决定涉足微服务领域。从那时起,由于微服务提供的优势(如可伸缩性、速度和可维护性等优势),它成为我使用最多的架构。成功地进入微服务领域促使我承担起撰写这本书的任务,以此作为一个机会来系统化和分享我所学到的知识。
作为一名软件开发人员,我认识到不断研究并将新知识应用到开发中是多么重要。在撰写这本书之前,我决定分享我的发现,并开始在我工作的位于我的祖国哥斯达黎加的一家软件开发公司的博客平台上发表微服务的文章。当我写这些文章时,我意识到我在我的职业生涯中找到了新的激情和目标。在写完其中一篇文章几个月后,我收到了Manning出版社的一封电子邮件,让我有机会写这本书的第2版与大家分享。
这本书的第1版是由约翰•卡内尔(John Carnell)撰写的,他是一位有着多年软件开发经验的专业人士。我在第1版的基础上,结合自己的理解与解释,撰写了第2版。第2版包含各种设计模式,能够帮助你使用Spring创建一个成功的微服务架构。这个框架可以为微服务开发人员遇到的许多常见的开发问题提供开箱即用的解决方案。现在,让我们用Spring开始这段美妙的微服务之旅。
我非常感谢有机会写这本书,让我在分享知识的同时也学到了知识。感谢Manning出版社对我的信任,让我能与这么多人分享我的作品,尤其感谢Michael Stephens给予我这个绝佳的机会。感谢John Carnell的支持、工作与知识。感谢我的技术开发编辑Robert Wenner的宝贵贡献。感谢我的编辑Lesley Trites全程陪伴我,为我提供宝贵的帮助。
我还要感谢Stephan Pirnbaum和John Guthrie,他们作为我的技术审查员检查了我的工作,确保了这本书的整体质量。感谢我的项目编辑Deirdre Hiam、文字编辑Frances Buran、校对员Katie Tennant、审稿编辑Aleks Dragosavljevic。我还要感谢所有的审稿人(Aditya Kumar、Al Pezewski、Alex Lucas、Arpit Khandelwal、Bonnie Malec、Christopher Kardell、David Morgan、Gilberto Taccari、Harinath Kuntamukkala、Iain Campbell、Kapil Dev S、Konstantin Eremin、Krzysztof Kamyczek、Marko Umek、Matthew Greene、Philippe Vialatte、Pierre-Michel Ansel、Ronald Borman、Satej Kumar Sahu、Stephan Pirnbaum、Tan Wee、Todd Cook和Víctor Durán)——是你们的建议帮助这本书变得更好。
我要感谢我的父母和我的整个家庭,他们支持我、鼓励我去追求我的梦想,他们以奉献精神作为榜样,帮助我成为了今天的专业人士。我还要感谢Eli,他在漫长的工作日里一直陪伴在我身边。我还要感谢我的朋友们,他们在整个过程中一直信任我、鼓励我。
最后,但同样重要,我感谢购买这本书的你,感谢你允许我向你分享我的知识。我希望你喜欢读它,就像我喜欢写它一样。我希望这本书能对你的职业生涯做出有价值的贡献。
本书是为实践Java/Spring的开发人员编写的,他们需要有关如何构建和实施基于微服务的应用程序的实践建议和示例。当我们写这本书时,我们希望保持与第1版相同的中心思想。我们希望它基于核心微服务模式,与Spring Boot和Spring Cloud的最新实践和示例保持一致。读者会发现,几乎每章都有特定的微服务设计模式,以及用Spring Cloud实现的示例。
● 拥有构建分布式应用程序经验(1~3年)的Java开发人员。
● 拥有Spring的知识背景(1年以上)的人。
● 对学习构建基于微服务的应用程序感兴趣的人。
● 对使用微服务构建基于云的应用程序感兴趣的人。
● 想要知道Java和Spring是否是用于构建基于微服务的应用程序的相关技术的人。
● 有兴趣了解如何将基于微服务的应用部署到云上的人。
本书包含12章和3个附录。
● 第1章介绍微服务架构为什么是构建应用程序,尤其是基于云的应用程序的重要相关方法。
● 第2章介绍我们将使用的Spring云技术,并提供如何按照十二要素应用程序最佳实践构建云原生微服务的指南。本章还将介绍如何使用Spring Boot构建第一个基于REST的微服务。
● 第3章介绍如何通过架构师、应用工程师和DevOps工程师的角度来审视微服务,并提供在第一个基于REST的微服务中实现某些微服务最佳实践的指南。
● 第4章介绍容器,重点介绍容器和虚拟机之间的主要区别。本章还将介绍如何使用几个Maven插件和Docker命令来容器化微服务。
● 第5章介绍如何使用Spring Cloud Config管理微服务的配置。Spring Cloud Config可帮助确保服务的配置信息集中在单个存储库中,并且在所有服务实例中都是版本控制和可重复的。
● 第6章介绍服务发现路由模式。在这一章中,读者将学习如何使用Spring Cloud和Netflix的Eureka服务将服务的位置从客户的使用中抽象出来,还将学习如何使用Spring Cloud LoadBalancer和Netflix Feign客户端实现客户端负载均衡。
● 第 7 章讨论如何在一个或多个微服务实例关闭或处于降级状态时保护微服务的消费者。这一章将演示如何使用Spring Cloud和Resilience4j来实现断路器模式、后备模式和舱壁模式。
● 第8章介绍服务网关路由模式。使用Spring Cloud Gateway,我们将为我们的所有微服务建立一个单一入口点。我们将演示如何使用Spring Cloud Gateway的过滤器来构建可以针对流经服务网关的所有服务强制执行的策略。
● 第9章介绍如何使用Keycloak实现服务验证和授权。在本章中,我们将介绍OAuth2的一些基本原则,以及如何使用Spring和Keycloak来保护微服务架构。
● 第10章讨论如何使用Spring Cloud Stream和Apache Kafka将异步消息传递引入微服务。本章还介绍如何使用Redis进行缓存查找。
● 第11章介绍如何使用Spring Cloud Sleuth、Zipkin和ELK技术栈来实现日志关联、日志聚合和跟踪等常见的日志记录模式。
● 第12章是本书的基石项目。我们将使用在本书中构建的服务,将它们部署到亚马逊弹性Kubernetes服务(Amazon Elastic Kubernetes Service,Amazon EKS)。我们还将讨论如何使用Jenkins等工具自动构建和部署微服务。
● 附录A展示额外的微服务架构最佳实践,并解释Richardson成熟度模型。
● 附录B是OAuth2的补充资料。OAuth2是一种非常灵活的身份验证模型,这一附录简要介绍OAuth2可用于保护应用程序及其相应微服务的不同方式。
● 附录C介绍如何使用Spring Boot Actuator、Micrometer、Prometheus和Grafana等几种技术来监控Spring Boot微服务。
总体上看,开发人员应该阅读第1~3章,这3章提供了关于最佳实践和在Java 11中使用Spring Boot实现微服务的基本信息。对于Docker新手,我们强烈建议仔细阅读第4章,因为它简要介绍了全书中使用的所有Docker概念。
本书的其余部分讨论了几种微服务模式,如服务发现、分布式跟踪、API网关等。阅读本书的方法是按顺序阅读各章,并遵循各章的代码示例。
本书几乎每章都有源代码示例,它们有的在带编号的代码清单中,有的在普通的文本中。在这两种情况下,源代码都以等宽字体印刷,以将其与普通文本分开。
每章在配套源代码中都有一个对应的文件夹。本书中的所有代码使用Maven作为主要构建工具进行构建,并使用Docker作为容器工具以运行在Java 11上。每章的README.md文件中包含以下信息:
● 本章的简要介绍;
● 初始配置所需的工具;
● “如何使用”部分;
● 示例的构建命令;
● 示例的运行命令;
● 联系方式和贡献信息。
我们在整本书中遵循的一个核心概念是,每章的代码示例应该能够完全独立于其他任何一章运行。这是什么意思呢?例如,读者应该能够获取第10章中的代码,并在不需要遵循前几章示例的情况下运行它。每章中构建的每个服务都有一个对应的Docker镜像,并且都使用Docker Compose来执行Docker镜像,以确保每章都有一个可复制的运行时环境。
在很多情况下,原始的源代码已被重新调整了格式。我们添加了换行符,重新加工了缩进,以适应书的页面空间。在极少数情况下,甚至还不止如此,代码清单还包括行连续标记(➥)。此外,在文本中描述代码时,源代码中的注释通常会从代码清单中移除。许多代码清单附带了代码注解,突出重要的概念。
约翰•卡内尔(John Carnell)是一名软件架构师,为Genesys Cloud领导开发团队。约翰每天大部分时间都在教Genesys Cloud客户和内部开发人员如何交付基于云的呼叫中心和电话解决方案,以及基于云开发的最佳实践。他使用AWS平台亲手构建基于电话的微服务。他的日常工作是设计和构建跨Java、Clojure和Go等多种技术平台的微服务。此外,他是一位高产的演讲者和作家。他经常在当地的用户群体发表演讲,并且是“The No Fluff Just Stuff Software Symposium”的常规发言人。在过去的二十多年里,他是许多基于Java的技术书籍和行业刊物的作者、合作者和技术审稿人。约翰拥有马奎特大学(Marquette University)学士学位和威斯康星大学奥什科什分校(University of Wisconsion Oshkosh)工商管理硕士(MBA)学位。约翰是一位充满激情的技术专家,他不断探索新技术和编程语言。当他不演讲、不写作或者不编码的时候,他与妻子Janet和3个孩子(Christopher、Agatha和Jack)以及他的狗Vader生活在北卡罗来纳州的卡里。
伊拉里•华卢波•桑切斯(Illary Huaylupo Sánchez)是一名软件工程师,她毕业于森福泰克大学(Cenfotec University),并拥有哥斯达黎加拉丁美洲科技大学(Latin American University of Science and Technology)的IT管理MBA学位。她在软件开发方面的知识相当广泛,拥有使用Java和其他编程语言(如Python、C#、Node.js)以及其他技术(如各种数据库、框架、云服务等)的经验。目前,她在哥斯达黎加圣何塞的微软公司担任高级软件工程师,在那里她将大部分时间花在研究和开发各种流行的最新项目上。她还拥有十多年的Oracle认证开发经验,并曾在大型公司如IBM、Gorilla Logic、Cargill和BAC Credomatic(一家著名的拉丁美洲银行)担任过高级软件工程师。她喜欢挑战,总是愿意学习新的编程语言和新技术。在空闲时间,她喜欢弹贝斯吉他,并与家人和朋友在一起。
本书封面插画的标题为《克罗地亚男人》(A Man from Croatia)。该插画取自克罗地亚斯普利特民族博物馆2008年出版的Balthasar Hacquet的Images and Descriptions of Southwestern and Eastern Wenda, Illyrians, and Slavs的最新重印版本。Hacquet(1739—1815)是一名奥地利医生及科学家,他花数年时间去研究奥地利帝国很多地区的植物、地质和人种,以及伊利里亚部落过去居住的(罗马帝国的)威尼托地区、尤里安阿尔卑斯山地区及西巴尔干地区等。Hacquet发表的很多科学论文和书籍中都有手绘插图。Hacquet的出版物中丰富多样的插图生动地描绘了200年前阿尔卑斯山东部地区和巴尔干西北地区的独特性。
那时候相距几公里的两个村庄村民的衣着都迥然不同,很好区分,而且通过着装还能很容易地分辨出他们所属的社会阶层或者行业。从那之后着装的要求发生了改变,不同地区的多样性也逐渐消亡。现在很难说出不同大陆的居民有多大区别,例如,现在很难区分斯洛文尼亚的阿尔卑斯山地区那些美丽小镇、村庄或巴尔干沿海小镇的居民,以及欧洲其他地区的居民。
Manning出版社利用两个世纪前的服装来设计书籍封面,以此来赞颂计算机产业所具有的创造性、主动性和趣味性,这些插画也因而重获生机。
本书由异步社区出品,社区(https://www.epubit.com/)为您提供相关资源和后续服务。
本书提供配套源代码,您在异步社区本书页面中点击“配套资源”,跳转到下载界面,按提示进行操作即可获得。注意:为保证购书读者的权益,该操作会给出相关提示,要求输入提取码进行验证。
您还可以扫码右侧二维码, 关注【异步社区】微信公众号,回复“e58748”直接获取,同时可以获得异步社区15天VIP会员卡,近千本电子书免费畅读。
作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。
当您发现错误时,请登录异步社区,按书名搜索,进入本书页面,点击“提交勘误”,输入勘误信息,点击“提交”按钮即可。本书的作者和编辑会对您提交的勘误进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。
扫描下方二维码,您将会在异步社区微信服务号中看到本书信息及相关的服务提示。
我们的联系邮箱是contact@epubit.com.cn。
如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。
如果您有兴趣出版图书、录制教学视频,或者参与图书技术审校等工作,可以发邮件给本书的责任编辑(sunzhesi@ptpress.com.cn)。
如果您来自学校、培训机构或企业,想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。
如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接通过邮件发给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。
“异步社区”是人民邮电出版社旗下IT专业图书社区,致力于出版精品IT技术图书和相关学习产品,为作译者提供优质出版服务。异步社区创办于2015年8月,提供大量精品IT技术图书和电子书,以及高品质技术文章和视频课程。更多详情请访问异步社区官网https://www.epubit.com。
“异步图书”是由异步社区编辑团队策划出版的精品IT专业图书的品牌,依托于人民邮电出版社的计算机图书出版积累和专业编辑团队,相关图书在封面上印有异步图书的LOGO。异步图书的出版领域包括软件开发、大数据、AI、测试、前端、网络技术等。
异步社区
微信服务号
本章主要内容
● 了解微服务架构
● 了解企业采用微服务的原因
● 使用Spring、Spring Boot和Spring Cloud来搭建微服务
● 了解云的概念和基于云的计算模型
实现新架构不是一件容易的事情,它带来了许多挑战,如应用程序可伸缩性、服务发现、监控、分布式跟踪、安全性、管理等。本书将介绍Spring中的微服务世界,讲解如何应对所有这些挑战,并展示将微服务应用于业务应用程序的权衡。你将学习如何使用诸如Spring Cloud、Spring Boot、Swagger、Docker、Kubernetes、ELK(Elasticsearch、Logstash和Kibana)、Stack、Grafana、Prometheus等技术来构建微服务应用程序。
Spring Boot和Spring Cloud为Java开发人员提供了一条从构建传统的单体的Spring应用到构建可以部署在云端的微服务应用的平滑迁移路径。本书使用实际的例子、图表和描述性的文本,提供了如何实现微服务架构的更多细节。
最后,你将学习如何实现诸如客户端负载均衡、动态伸缩、分布式跟踪等技术和技巧,以使用Spring Boot和Spring Cloud创建灵活、新式和自主的基于微服务的业务应用程序。通过应用Kubernetes、Jenkins和Docker等技术,你还可以创建自己的构建/部署管道,以实现业务中的持续交付和集成。
软件架构是指建立软件组件之间的结构、操作和交互的所有基本部分。本书解释了如何创建一个微服务架构,该架构由松耦合的软件服务组成,这些软件服务执行少量定义良好的任务,并通过网络使用消息进行通信。我们首先考虑一下微服务和其他一些常见架构之间的区别。
一种常见的企业架构类型是多层或n层架构。通过这种设计,应用程序被划分为多个层,每个层都有自己的职责和功能,如用户界面(UI)、服务、数据、测试等。例如,当你创建应用程序时,你先为UI创建一个特定的项目或解决方案,然后为服务创建一个项目或解决方案,再为数据层创建一个项目或解决方案,以此类推。最后,你将拥有几个项目,将这些项目组合起来,创建一个完整的应用程序。对于大型企业系统,n层架构应用程序有许多优点,包括:
● n层架构应用程序提供了良好的关注点分离,使得人们可以分别考虑用户界面、数据和业务逻辑等领域;
● 团队很容易在n层架构应用程序的不同组件上独立工作;
● 因为这是一个易于理解的企业架构,所以为n层架构项目找到熟练的开发人员相对容易。
n层架构应用程序也有缺点,例如:
● 当你想要进行更改时,必须停止并重新启动整个应用程序;
● 消息往往在各层之间上下传递,这可能是低效的;
● 一旦部署,重构一个大型的n层架构应用程序可能会很困难。
虽然我们在本书中讨论的一些主题与n层架构应用程序直接相关,但我们将更直接地关注微服务与另一种常见架构(通常称为单体架构)的区别。
许多中小型基于Web的应用程序都是使用单体架构风格构建的。在单体架构中,应用程序作为单个可部署的软件制品交付。所有用户界面、业务和数据库访问逻辑都打包到一个唯一的应用程序中,并部署到应用程序服务器。图1-1显示了这个应用程序的基本架构。
虽然应用程序可能是作为单个工作单元部署的,但经常会有多个开发团队在一个应用程序上工作。每个开发团队负责应用程序的不同部分,并且他们经常用自己的功能部件来服务特定的客户。例如,想象一个场景,我们有一个内部定制的客户关系管理(CRM)应用,它涉及多个团队之间的合作,包括UI/UX团队、客户团队、数据仓库团队以及金融从业者等。
尽管微服务架构的支持者有时会否定单体应用程序,但单体应用程序通常是一个很好的选择。与n层或微服务等更复杂的架构相比,单体应用程序更容易构建/部署。如果用例定义良好并且不太可能改变,那么从单体应用程序开始可能是一个很好的决定。
然而,当应用程序的规模和复杂性开始增加时,单体应用程序可能会变得难以管理。对单体应用程序的每一个更改都可能对应用程序的其他部分产生级联效应,这可能会使应用程序变得耗时且代价高昂,特别是在生产系统中。我们有的第三个选择——微服务架构,提供了更大的灵活性和可维护性。
图1-1 单体应用程序强迫多个开发团队同步他们的交付日期,因为他们的代码需要被
作为一个整体单元进行构建、测试和部署
微服务的概念最初悄悄蔓延到软件开发社区中,是作为对尝试(在技术上和组织上)扩大大型单体应用程序所面临的诸多挑战的直接回应。微服务是一种小型的、松耦合的分布式服务。微服务允许你将一个大型的应用分解为具有狭义职责定义的便于管理的组件。微服务通过将一个大型代码库分解为多个精确定义的小型代码库,帮助解决了大型代码库中传统的复杂问题。
对微服务的思考需要围绕两个关键概念展开:分解(decomposing)和分离(unbundling)。应用程序的功能应该完全彼此独立。如果我们以前面提到的CRM应用程序为例,将其分解为微服务,那么它看起来可能像图1-2所示的样子。
图1-2 使用微服务架构,CRM应用将会被分解成一系列完全独立的微服务,
让每个开发团队都能够按各自的步伐前进
图1-2显示了每个团队是如何完全拥有自己的服务代码和服务基础设施的。他们可以彼此独立地去构建、部署和测试,因为他们的代码、源代码控制存储库和基础设施(应用服务器和数据库)现在是完全独立于应用的其他部分的。总的来说,微服务架构具有以下特征。
● 应用程序逻辑被分解为具有定义明确的、协调的职责边界的细粒度组件。
● 每个组件都有一个小的职责领域,并且完全独立部署。单个微服务对业务域的某一部分负责。
● 微服务采用HTTP这样的轻量级通信协议和JSON(JavaScript Object Notation,JavaScript对象表示法),在服务消费者和服务提供者之间进行数据交换。
● 服务的底层采用什么技术实现并没有什么影响,因为微服务应用程序始终使用技术中立的格式(JSON是最常见的)进行通信。这意味着使用微服务方法构建的应用程序能够使用多种编程语言和技术进行构建。
● 微服务利用其小、独立和分布式的性质,使组织拥有具有明确责任领域的更小型开发团队。这些团队可能为同一个目标工作,如交付一个应用程序,但是每个团队只负责他们在做的服务。
图1-3对比了一个典型的小型电子商务应用程序的单体设计和微服务设计。
图1-3 单体架构和微服务架构对比
习惯于为当地市场服务的公司突然发现,他们可以接触到全球的客户群,不过,更大的全球客户群也带来了全球竞争。更多的竞争影响了开发人员构建应用程序的方式。
● 复杂性上升。客户期望一个组织的所有部门都知道他们是谁。与单个数据库通信并且不与其他应用程序集成的“孤立的”应用程序已不再是常态。如今,应用程序需要与多个服务和数据库进行通信,这些服务和数据不仅位于公司数据中心内,还位于外部互联网服务供应商内。
● 客户期待更快速的交付。客户不想再等软件包的下一个年度版本了。相反,他们期望一个软件产品中的各个功能是分开的,以便新功能在几周(甚至几天)内即可快速发布。
● 客户还要求可靠的性能和可伸缩性。全球性的应用程序使预测应用程序能处理多少事务以及何时会到达该事务量变得非常困难。应用程序需要跨多个服务器快速扩大,然后在事务量高峰过去之后无缝收缩。
● 客户期望他们的应用程序可用。因为客户与竞争对手之间只有点击一下鼠标的距离,所以企业的应用程序必须具有高度的弹性。应用程序中某个部分的故障或问题不应该导致整个应用程序崩溃。
为了满足这些期望,作为应用开发人员,我们不得不接受这样一个不可思议的东西:要构建高度可伸缩的高度冗余的应用程序,我们需要将应用程序分解成可以互相独立构建和部署的小型服务。如果将应用程序“分解”为较小的服务,并将它们从单体制品中转移出来,那么就可以构建具有下面这些特性的系统。
● 灵活性——可以将解耦的服务进行组合和重新安排,以快速交付新的功能。一个正在使用的代码单元越小,更改越不复杂,测试部署代码所需的时间越短。
● 有弹性——解耦的服务意味着应用程序不再是单个“泥浆球”,其中一部分应用程序的降级会导致整个应用程序失败。故障可以限制在应用程序的一小部分中,并在整个应用程序遇到中断之前被控制。这也使应用程序在出现不可恢复的错误的情况下能够优雅地降级。
● 可伸缩性——解耦的服务可以轻松地跨多个服务器进行水平分布,从而可以适当地对功能 / 服务进行伸缩。单体应用程序中的所有逻辑是交织在一起的,即使只有一小部分应用程序是瓶颈,整个应用程序也需要扩展。小型服务的扩展是局部的,成本效益更高。
为此,当我们开始讨论微服务时,请记住:
小型的、简单的、解耦的服务 = 可伸缩的、弹性的、灵活的应用程序
系统和组织本身可以从微服务方法中获益——理解这一点是很重要的。为了在组织中获益,我们可以反向应用康威定律(Conway’s law)。这个定律指出了几点可以改善公司内部沟通和结构的措施。
康威定律(由Melvin R. Conway在1968年4月的文章“How do Committees Invent”中首次提到)指出“设计系统的组织其产生的设计等价于组织间的沟通结构”。基本上,它所表明的是,团队内部以及与其他团队之间的沟通方式直接反映在他们生产的代码中。
如果我们反向应用康威定律,也称为逆康威策略(inverse Conway maneuver),并基于微服务架构设计公司结构,那么通过创建松耦合的自治团队来实现微服务,就能改善应用程序的通信、稳定性和组织结构。
Spring已经成为构建基于Java的应用程序的最流行的开发框架。Spring的核心是建立在依赖注入的概念上的。依赖注入框架(dependency injection framework)允许你通过约定(以及注解)将应用程序中对象之间的关系外部化,而不是在对象内部彼此硬编码实例化代码,这使开发人员能更高效地管理大型Java项目。Spring在应用程序的不同的Java类之间充当中间人,管理着它们的依赖关系。Spring本质上就是让你像玩乐高积木一样将自己的代码组装在一起。
Spring框架令人印象深刻的地方在于它能够与时俱进并进行自我改造。Spring开发人员很快发现,许多开发团队正在从将应用程序的展现、业务和数据访问逻辑打包在一起并部署为单个制品的单体应用程序模型中迁移,转向高度分布式的模型,在这种模型中,小型服务可以快速部署到云端。为了响应这种转变,Spring开发人员启动了两个项目,即Spring Boot和Spring Cloud。
Spring Boot是对Spring框架理念重新思考的结果。虽然Spring Boot包含了Spring的核心特性,但它剥离了Spring中的许多“企业”特性,而提供了一个基于Java的、面向REST (Representational State Transfer,表征状态转移)的微服务框架。只需一些简单的注解,Java开发者就能够快速构建一个可打包和部署的REST服务,这个服务并不需要外部的应用容器。
注意 虽然本书会在第3章中更详细地介绍REST,但REST背后的核心概念是,服务应该使用HTTP动词(GET、POST、PUT和DELETE)来代表服务中的核心操作,并且应该使用轻量级的面向Web的数据序列化协议(如JSON)来从服务请求数据和从服务接收数据。
Spring Boot的主要特性如下。
● 嵌入式Web服务器,用于避免应用程序部署的复杂性:Tomcat(默认)、Jetty或Undertow。
这是Spring Boot的一个基本概念,所选的Web服务器是可部署JAR文件的一部分。对于Spring Boot应用程序,部署应用程序的唯一必要条件是在服务器上安装Java。
● 快速启动项目的建议配置(各种starter项目)。
● 尽可能地自动配置Spring功能。
● 可供生产使用的广泛特性(如度量、安全性、状态验证、外部化配置等)。
使用Spring Boot对我们的微服务有以下好处。
● 减少开发时间,提高效率和生产力。
● 提供嵌入式HTTP服务器来运行Web应用程序。
● 避免编写很多样板式代码。
● 促进与Spring生态系统的集成(包括Spring Data、Spring Security、Spring Cloud等)。
● 提供一套各种开发插件。
在构建基于云的应用程序时,微服务已经成为十分常见的架构模式之一,因此Spring开发人员社区为开发人员提供了Spring Cloud。Spring Cloud框架使得将微服务实施和部署到私有云或公有云变得更加简单。Spring Cloud在一个公共框架中封装了多个流行的云管理微服务框架,并且让这些技术的使用和部署像为代码添加注解一样简便。第2章将介绍Spring Cloud中的不同组件。
本书提供了一个使用Spring Boot、Spring Cloud和其他有用的现代技术创建完整的微服务架构的分步指南。图1-4展示了我们将在本书中使用的一些服务和技术集成的高层概述。
图1-4描述了在我们将要创建的微服务架构中,更新和检索组织信息的客户端请求。要启动请求,客户端首先需要使用Keycloak进行身份验证以获得访问令牌。一旦获得令牌,客户端就向Spring Cloud Gateway发出请求。API网关服务是我们整个架构的入口点。该服务将与Eureka服务发现进行通信,以检索组织和许可证服务的位置,然后调用特定的微服务。
一旦请求到达组织服务,该服务将通过Keycloak验证访问令牌,以查看令牌是否有效以及用户是否有权继续该过程。确认令牌有效后,组织服务将从组织数据库更新和检索其信息,并将这些信息作为HTTP响应发送回客户端。作为另一条路,更新完组织信息后,组织服务将向Kafka主题添加一条消息,以便许可证服务可以知道这一更改。
图1-4 我们将在本书中使用的服务和技术的高层概述
当消息到达许可证服务后,Redis会在Redis的内存数据库中存储特定信息。在整个过程中,此架构将使用来自Zipkin的分布式跟踪,并使用Elasticsearch和Logstash来管理和显示日志,同时使用Spring Boot Actuator、Prometheus和Grafana来公开和显示应用程序度量。
随着我们继续向前探索,我们将看到诸如Spring Boot、Spring Cloud、Elasticsearch、Logstash、Kibana、Prometheus、Grafana和Kafka等主题。所有这些技术听起来可能很复杂,但是随着本书的深入,我们将看到如何创建和集成构成图1-4中框架的不同组件。
本书的范围很广,它涵盖了从基本定义到创建微服务架构的更复杂实现的所有内容。
本书是关于使用各种Spring项目(如Spring Boot和Spring Cloud)构建基于微服务架构的应用程序的,这些应用程序可以部署到公司内运行的私有云或亚马逊、谷歌和微软等运行的公有云上。本书涵盖以下主题。
● 微服务是什么、最佳实践,以及构建基于微服务的应用程序的设计考虑因素。
● 什么时候不应该构建基于微服务的应用程序。
● 如何使用Spring Boot框架来构建微服务。
● 支持微服务应用程序的核心运维模式,特别是基于云的应用程序。
● Docker是什么,如何将它与基于微服务的应用程序集成。
● 如何使用Spring Cloud来实现本章稍后描述的运维模式。
● 如何创建应用程序度量,并在监控工具中可视化这些度量。
● 如何利用Zipkin和Sleuth实现分布式跟踪。
● 如何使用ELK技术栈来管理应用程序日志。
● 如何利用已学的知识,构建一个部署管道,在本地将服务部署到内部管理的私有云或公有云厂商所提供的环境中。
读完本书,你将具备构建和部署Spring Boot微服务所需的知识,明白实施微服务的关键设计决策,了解如何将服务配置管理、服务发现、消息传递、日志记录和跟踪以及安全性结合在一起,以交付一个健壮的微服务环境,最后你还会看到如何使用不同的技术部署微服务。
我猜你能读到这里是因为:
● 你是一名Java开发人员或对Java有很深入的理解;
● 你拥有Spring背景;
● 你对学习如何构建基于微服务的应用程序感兴趣;
● 你对如何使用微服务来构建基于云的应用程序感兴趣;
● 你想知道Java和Spring是否是用于构建基于微服务的应用程序的相关技术;
● 你想知道实现微服务架构的前沿技术;
● 你有兴趣了解如何将基于微服务的应用部署到云上。
本书提供了一份在Java中实现微服务架构的详细指南。书中既有描述性和可视化信息,又有大量实际操作的代码示例,为如何使用不同Spring项目(如Spring Boot和Spring Cloud)的最新版本实现微服务架构提供了编程指南。
此外,本书介绍了微服务模式、最佳实践,以及与这种类型的架构相关联的基础设施技术,模拟了真实世界的应用程序开发环境。让我们转移一下注意力,使用Spring Boot构建一个简单的微服务。
在本节中,我们将了解如何使用Spring Boot创建微服务,以及为什么云与基于微服务的应用程序有关。
本节不会详细介绍有关如何创建微服务的诸多代码,而只是简单介绍如何创建服务,以便展示使用Spring Boot是多么简单。为此,我们将创建一个简单的REST服务“Hello World”,它包含一个使用GET HTTP动词的主端点。此服务端点将以接收请求参数和URL参数(也称为路径变量)的形式接收参数。图1-5展示了REST服务将做什么,Spring Boot微服务将会如何处理用户请求的一般流程。
图1-5 Spring Boot抽象出了常见的REST微服务任务(路由到业务逻辑、从URL中解析HTTP参数、JSON与Java对象相互映射),并让开发人员专注于服务的业务逻辑。此图显示了向控制器传递参数的三种不同方式
这个例子并不详尽,甚至没有说明应该如何构建一个生产级的微服务,但它同样值得你注意,因为它只需要写很少的代码。在第2章之前,我们不打算介绍如何设置项目构建文件或代码的细节。如果你想要查看Maven pom.xml文件以及实际代码,你可以在可下载代码的第1章部分找到它。
注意 第1章中的所有源代码都能在本书的配套源代码中找到。
对于本例,我们有一个名为Application
的Java类,你可以在类文件com/huaylupo/ spmia/ch01/SimpleApplication/Application.java中找到它。我们将使用这个类公开一个名为/hello的REST端点。代码清单1-1展示了Application类的代码。
代码清单1-1 使用Spring Boot的Hello World:一个(非常)简单的Spring微服务
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication ⇽--- 告诉Spring Boot,该类是Spring Boot服务的入口点
@RestController ⇽--- 告诉Spring Boot,要将该类中的代码公开为Spring RestController
@RequestMapping(value="hello") ⇽--- 此应用程序中公开的所有URL将以前缀/hello开头
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@GetMapping(value="/{firstName}") ⇽--- 公开一个基于GET请求的REST端点,其中包含两个参数:通过@PathVariable获取的firstName和通过@RequestParam获取的lastName
public String helloGET(
@PathVariable("firstName") String firstName, ⇽--- 将firstName和lastName参数映射到传入hello方法的两个变量
@RequestParam("lastName") String lastName) {
return String.format(
"{\"message\":\"Hello %s %s\"}", ⇽--- 返回我们手工构建的简单JSON字符串(在第2章中,我们不会创建任何JSON)
firstName, lastName);
}
}
class HelloRequest{ ⇽--- 包含用户发送的JSON结构的字段
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
在代码清单1-1中,主要是使用GET HTTP动词公开一个端点,该端点将在URL上取两个参数(firstName
和lastName
):一个来自路径变量(@PathVariable
),另一个来自请求参数(@RequestParam
)。这个端点返回一个简单的JSON字符串,它的净荷包含消息"Hello firstName lastName"
。如果在服务上调用了端点/hello/illary?lastName=huaylupo
,返回的结果将会是:
{"message":"Hello illary huaylupo"}
让我们启动Spring Boot应用程序。为此,我们在命令行执行下面的命令:
mvn spring-boot:run
这个Maven命令使用pom.xml文件中定义的Spring Boot插件来使用嵌入式Tomcat服务器启动应用程序。一旦你执行了mvn spring-boot:run
命令,并且一切启动正确,你应该会在命令行窗口中看到图1-6所示的内容。
注意 如果你从命令行运行命令,请确保你位于根目录中。根目录是包含pom.xml文件的目录。否则,你会遇到这个错误:No plugin found for prefix 'spring-boot' in the current project and in the plugin groups。
Java与Groovy以及Maven与Gradle
Spring Boot框架支持Java和Groovy编程语言。Spring Boot还支持Maven和Gradle构建工具。Gradle引入了基于Groovy的DSL(domain specific language,领域特定语言)来声明项目配置,而不是像Maven那样的XML文件。虽然Gradle是一个强大、灵活的一流工具,但Java开发人员社区仍在使用Maven。因此,本书将只包含Maven中的示例,以保持本书的可管理性,并使内容更聚焦,以便于照顾到尽可能多的读者。
图1-6 Spring Boot服务通过控制台与服务端口通信
要执行服务,你需要使用基于浏览器的REST工具。你会发现,许多工具(包括图形和命令行)都可用于调用基于REST的服务。本书将使用Postman。图1-7和图1-8展示了对端点的两次不同的Postman调用以及从服务中返回的结果。
图1-7 向/hello端点发出GET请求,响应以JSON净荷的形式展示了请求的数据
图1-8 向/hello端点发出POST请求,响应以JSON净荷表示请求和响应的数据
图1-8展示了如何使用POST HTTP动词进行调用的简单示例。必须强调的是,这个示例只是用于演示。在接下来的几章中,你将看到,当涉及在我们的服务中创建新记录时,POST方法是首选。
这个简单的示例代码没有展示Spring Boot的全部功能,也没有展示创建一个服务的最佳实践,而是展示了只用几行代码就能用Java编写一个完整的HTTP JSON REST服务,其中带有基于URL和参数的路由映射。虽然Java是一门强大的编程语言,但与其他编程语言相比,它却获得了啰唆冗长的名声,但有了Spring,我们只需要少量行数的代码即可实现诸多功能。接下来,让我们看看为什么以及何时适合使用微服务方法来构建应用程序。
云计算是通过互联网交付计算和虚拟化IT服务——数据库、网络、软件、服务器、分析等,以提供灵活、安全、易于使用的环境。云计算在公司的内部管理中提供了显著的优势,比如低额初始投资、易于使用和维护、可伸缩性等。
云计算模型允许用户选择这些模型提供的信息和服务的控制级别。云计算模型以其首字母缩写而闻名,通常称为XaaS——它是一个缩写,意思是“anything as a service”。最常见的云计算模型有以下几种,图1-9展示了这些模型之间的差异。
● 基础设施即服务(Infrastructure as a Service,IaaS)——供应商提供的基础设施,向用户提供服务器、存储和网络等计算资源的访问。在这个模型中,用户负责一切与基础设施维护和应用程序可伸缩性相关的东西。IaaS平台包括AWS(EC2)、Azure虚拟机(Azure Virtual Machines)、谷歌计算引擎(Google Compute Engine)和Kubernetes等。
● 容器即服务(Container as a Service,CaaS)——此模型介于IaaS和PaaS之间,它指的是一种基于容器的虚拟化形式。与IaaS模型不同,使用IaaS的开发人员必须管理部署服务的虚拟机,而使用CaaS则可以将微服务部署在一个轻量级、可移植的虚拟容器(如Docker)中,并将其部署到云供应商。云供应商不但运行容器运行所在的虚拟服务器,而且提供用于构建、部署、监控和缩扩容的综合工具。CaaS平台包括谷歌容器引擎(Google Container Engine,GKE),亚马逊的弹性容器服务(Elastic Container Service,ECS)。在第11章中,我们将看到如何将构建的微服务部署到Amazon ECS。
● 平台即服务(Platform as a Service,PaaS)——此模型提供平台和环境,让用户专注于应用程序的开发、执行和维护,甚至可以使用供应商提供的工具来创建这些应用程序(例如操作系统、数据库管理系统、技术支持、存储、托管、网络等)。用户不需要在物理基础设施上投入资金,也不需要花时间来管理它,这使得用户可以专注于应用程序的开发。PaaS平台包括Google App Engine、Cloud Foundry、Heroku和AWS Elastic Beanstalk。
● 函数即服务(Function as a Service,FaaS)——也称为无服务器架构,尽管名字如此,但这个架构并不意味着在没有服务器的情况下运行特定的代码,它真正的意思是在云中执行功能的一种方式,供应商在云中提供所有必需的服务器。无服务器架构允许我们只关注服务的开发,而不必担心缩扩容、供应和服务器管理。相反,我们可以只专注于上传我们的函数,而不处理任何管理基础设施。FaaS平台包括AWS(Lambda)、Google Cloud Function和Azure Function。
● 软件即服务(Software as a Service,SaaS)——也称为按需软件,此模型使用户无须进行任何部署或维护就可以使用特定的应用程序。在大多数情况下,通过Web浏览器访问应用程序。在这个模型中,一切都由服务供应商管理:应用程序、数据、操作系统、虚拟化、服务器、存储和网络。用户只需租用服务并使用软件即可。SaaS平台包括Salesforce、SAP和Google Business。
图1-9 不同的云计算模型归结于用户或云供应商各自要负责什么
注意 如果你不小心,基于FaaS的平台就会将你的代码锁定到一个云供应商平台上,因为你的代码会被部署到供应商特定的运行时引擎上。使用基于FaaS的模型,你可能会使用通用的编程语言(Java、Python、JavaScript等)编写服务,但你仍然会将自己束缚在底层供应商的API和部署函数的运行时引擎上。
微服务架构的核心概念之一就是每个服务都被打包和部署为离散的独立制品。服务实例应该迅速启动,服务的每一个实例都是完全相同的。在编写微服务时,你迟早要决定是否将服务部署到下列某个环境之中。
● 物理服务器——虽然你可以将微服务构建和部署到物理机器上,但由于物理服务器的局限性,很少有组织会这样做。你无法快速提高物理服务器的容量,并且在多个物理服务器之间水平伸缩微服务的成本可能会非常高。
● 虚拟机镜像——微服务的主要优点之一是能够快速启动和关闭实例,以响应可伸缩性和服务故障事件。虚拟机是主要云供应商的心脏和灵魂。
● 虚拟容器——虚拟容器是在虚拟机镜像上部署微服务的自然延伸。许多开发人员不是将服务部署到完整的虚拟机,而是将他们的服务作为Docker容器(或等效的容器技术)部署到云端。虚拟容器在虚拟机内运行,使用虚拟容器,你可以将单个虚拟机隔离成共享相同虚拟机镜像的一系列独立进程。微服务可以被打包,然后开发人员可以在IaaS私有或公有云中快速部署和启动服务的多个实例。
基于云的微服务的优势是以弹性的概念为中心。云服务供应商允许开发人员在几分钟内快速启动新的虚拟机和容器。如果服务的容量需求下降,开发人员可以关闭容器,从而避免产生额外的费用。使用云供应商部署微服务可以显著地提高应用程序的水平可伸缩性(添加更多的服务器和服务实例)。
服务器弹性也意味着应用程序可以更具弹性。如果其中一个微服务遇到问题并且处理能力正在不断地下降,那么启动新的服务实例可以让应用程序保持足够长的存活时间,让开发团队能够从容而优雅地解决问题。
在本书中,所有的微服务和相应的服务基础设施都将使用Docker容器部署到基于CaaS的云供应商。这是用于微服务的常见部署拓扑结构。CaaS云供应商最常见的特征如下。
● 简化基础设施管理—— CaaS云供应商让开发人员能够对自己的服务拥有更多的控制权。可以通过简单的API调用启动和停止新服务。
● 大规模的水平可伸缩性—— CaaS云供应商允许开发人员快速简便地启动服务的一个或多个实例。这种功能意味着可以快速扩大服务以及绕过表现不佳或出现故障的服务器。
● 通过地理分布实现高冗余—— CaaS供应商必然拥有多个数据中心。通过使用CaaS云供应商部署微服务,可以比使用数据中心里的集群拥有更高级别的冗余。
为什么不是基于PaaS的微服务
本章前面讨论了5种云平台——基础设施即服务(IaaS)、容器即服务(CaaS)、平台即服务(PaaS)、函数即服务(FaaS)和软件即服务(SaaS)。本书专注于使用基于CaaS的方法构建微服务。虽然某些云供应商可以让你抽象出微服务的部署基础设施,但本书将教你如何独立于供应商,部署应用程序的所有部分(包括服务器)。
例如,Cloud Foundry、AWS Elastic Beanstalk、Google App Engine和Heroku可以让你无须知道底层应用程序容器即可部署你的服务。它们提供了一个Web接口和一个命令行接口(CLI),以允许你将应用程序部署为WAR或JAR文件。应用程序服务器和相应的Java容器的设置和调优都与你无关。虽然这很方便,但每个云供应商的平台都有与其各自的PaaS解决方案相关的不同特点。
本书中构建的服务都会打包为Docker容器。本书选择Docker的主要原因是,Docker可以部署到所有主要的云供应商之中。在后面的章节中,我们将了解Docker是什么,以及如何集成Docker来运行本书中使用的所有服务和基础设施。
尽管构建单个微服务的概念很易于理解,但运行和支持健壮的微服务应用程序(尤其是在云中运行)不只是涉及为服务编写代码。图1-10展示了在编写或构建微服务时需要考虑的一些准则。
图1-10 微服务不只是业务逻辑,还需要考虑服务的运行环境以及服务的伸缩性和弹性
编写一个健壮的服务需要考虑几个主题。让我们更详细地了解图1-10中提及的要点。
● 大小适当——如何确保微服务的大小适当。这样才不会让微服务承担太多的职责。请记住,服务大小适当,就能快速更改应用程序,降低整个应用程序中断的总体风险。
● 位置透明——如何管理服务调用的物理细节。在一个微服务应用程序中,多个服务实例可以快速启动和关闭。
● 有弹性——如何通过绕过失败的服务,确保采取“快速失败”的方法来保护微服务消费者和应用程序的整体完整性。
● 可重复——如何确保提供的每个新服务实例与生产环境中的所有其他服务实例具有相同的配置和代码库。
● 可伸缩——如何建立一种通信,使服务之间的直接依赖关系最小化,并确保可以优雅地扩展微服务。
当我们更详细地研究这些要点时,本书采用了一种基于模式的方法。通过基于模式的方法,我们将看到可以跨不同技术实现来使用的通用设计。虽然本书选择了使用Spring Boot和Spring Cloud来实现本书中所使用的模式,但你完全可以把这里的概念和其他技术平台一起使用。具体来说,本书涵盖以下微服务模式:
● 核心开发模式;
● 路由模式;
● 客户端弹性模式;
● 安全模式;
● 日志记录和跟踪模式;
● 应用程序度量模式;
● 构建/部署模式。
如何创建一个微服务并没有一个正式的定义,了解这一点是很重要的。在下一节中,你将看到构建微服务时必须考虑的一些常见方面。
核心开发模式解决了构建微服务的基础问题,图1-11突出了我们将要讨论的基本服务设计的主题。
以下模式(如图1-11所示)展示了构建微服务的基础。
● 服务粒度——如何将业务域分解为微服务,才能使每个微服务都具有适当程度的职责?服务职责划分过于粗粒度,在不同的业务问题领域重叠,会使服务随着时间的推移变得难以维护。服务职责划分过于细粒度,则会使应用程序的整体复杂性增加,并将服务变为无逻辑的(除了访问数据存储所需的逻辑)“哑”数据抽象层。第3章将会介绍服务粒度。
● 通信协议——开发人员如何与服务进行通信?第一步是定义需要同步协议还是异步协议。对于同步协议,最常见的协议是基于HTTP的REST,使用XML(Extensible Markup Language,可扩展标记语言)、JSON或诸如Thrift之类的二进制协议来与微服务来回传输数据。对于异步协议,最流行的协议是高级消息队列协议(Advanced Message Queuing Protocol,AMQP),使用一对一(队列)或一对多(主题)的消息代理,如RabbitMQ、Apache Kafka和Amazon简单队列服务(Simple Queue Service,SQS)。在后面的章节中,我们将学习通信协议。
● 接口设计——如何设计实际的服务接口,便于开发人员进行服务调用?如何构建服务?最佳实践是什么?下一章将介绍最佳实践和接口设计。
● 服务的配置管理——如何管理微服务的配置,使其在不同的云环境之间移动?第5章将介绍如何通过外部化配置和配置文件来管理微服务的配置。
● 服务之间的事件处理——如何使用事件解耦微服务,才能最小化服务之间的硬编码依赖关系,并提高应用程序的弹性?答案是使用Spring Cloud Stream的事件驱动架构,这将在第10章中介绍。
图1-11 在设计微服务时需要考虑服务是如何通信和被消费的
路由模式负责处理希望消费微服务的客户端应用程序如何发现服务的位置并路由到服务。在基于云的应用程序中,可能会运行成百上千个微服务实例。要强制执行安全和内容策略,需要抽象这些服务的物理IP地址,并为服务调用提供单个入口点。如何做到这一点?以下模式将回答这个问题:
● 服务发现——通过服务发现及其关键特性服务注册表,可以让微服务变成可发现的,这样客户端应用程序就可以发现它们,而无须将服务的位置硬编码到它们的应用程序中。如何做到这一点?我们将在第6章对此进行解释。请记住,服务发现是内部服务,而不是面向客户端的服务。注意,在本书中,我们使用的是Netflix Eureka服务发现,但也有其他服务注册表,如etcd、Consul和Apache ZooKeeper。此外,有些系统没有显式的服务注册中心。相反,它们使用了一种被称为服务网格(service mesh)的服务间通信基础设施。
● 服务路由——通过API网关,可以为所有服务提供单个入口点,以便将安全策略和路由规则统一应用于微服务应用程序中的多个服务和服务实例。如何做到这一点?使用Spring Cloud API Gateway能做到,第8章中会对此进行解释。
图1-12展示了服务发现和服务路由之间如何看起来像是具有硬编码的事件顺序(首先是服务路由,然后是服务发现)。然而,这两种模式并不是彼此依赖的。例如,我们可以实现没有服务路由的服务发现,也可以实现没有服务发现的服务路由(尽管它的实现更加困难)。
图1-12 服务发现和服务路由是所有大规模微服务应用程序的关键部分
因为微服务架构是高度分布式的,所以开发人员必须对如何防止单个服务(或服务实例)中的问题级联暴露给服务的消费者这个问题十分敏感。为此,这里将介绍4种客户端弹性模式。
● 客户端负载均衡——如何在服务上缓存服务实例的位置,才能让对微服务的多个实例的调用负载均衡到该微服务的所有健康实例。
● 断路器模式——如何阻止客户端继续调用出现故障的或遭遇性能问题的服务。在服务运行缓慢时,它会消耗调用它的客户端上的资源。开发人员希望这些微服务调用快速失败,以便调用客户端可以快速响应并采取适当的措施。
● 后备模式——当服务调用失败时,如何提供“插件”机制,允许服务的客户端尝试通过调用微服务之外的其他方法来执行工作。
● 舱壁模式——微服务应用程序使用多个分布式资源来执行它们的工作。该模式指的是如何分隔这些调用,表现不佳的服务调用才不会对应用程序的其他部分产生负面影响。
图1-13展示了这些模式如何在服务表现不佳时,保护服务消费者不受影响。第7章将会介绍这些主题。
图1-13 使用微服务时,必须保护服务调用者远离表现不佳的服务。记住,慢速或无响应的服务所
造成的中断并不仅仅局限于直接关联的服务
为了确保微服务不向公众开放,将以下安全模式应用到架构是很重要的,这样可以确保只有拥有正确凭据的已准予的请求才能调用服务。图1-14展示了如何实现以下3种模式来构建可以保护微服务的验证服务。
图1-14 使用基于令牌的安全方案,不用传递客户端凭据就能实现服务验证和授权
● 验证——如何确定调用服务的服务客户端就是它们自己声称的那个。
● 授权——如何确定是否允许调用微服务的服务客户端执行它们正在尝试进行的操作。
● 凭据管理和传播——如何避免客户端要不断地提供它们的凭据信息才能访问事务中涉及的服务调用。为实现这一点,我们需要了解如何使用基于令牌的安全标准,如OAuth2和JSON Web Token(JWT),来获取可以从一个服务调用传递到另一个服务调用的令牌,以对用户进行验证和授权。
什么是OAuth2
OAuth2是一个基于令牌的安全框架,允许用户使用第三方验证服务进行验证。如果用户成功通过验证,则会被授予一个令牌,该令牌必须与每个请求一起发送。
OAuth2背后的主要目标是,在调用多个服务来完成用户请求时,用户不需要在处理请求的时候为每个服务都提供它们的凭据就能完成验证。虽然第9章会介绍OAuth,但是Aaron Parecki编写的OAuth2文档仍然值得一读。
微服务架构的缺点是调试、跟踪和监控问题要困难得多,因为一个简单的操作可能会在应用程序中触发大量的微服务调用。后面的章节将介绍如何使用Spring Cloud Sleuth、Zipkin和ELK技术栈实现分布式跟踪。出于这个原因,我们将研究以下3种核心日志记录和跟踪模式,以实现分布式跟踪。
● 日志关联——如何将一个用户事务的服务之间生成的所有日志联系在一起。使用这种模式时,我们需要了解如何实现一个关联ID,这是一个唯一的标识符,在一个事务中调用所有服务时都会携带它,它能够将每个服务生成的日志条目联系在一起。
● 日志聚合——使用这种模式,我们需要了解如何跨所有服务将微服务(及其各个实例)生成的所有日志合并到一个可查询的数据库中,并了解事务中涉及的服务的性能特征。
● 微服务跟踪——最后,我们将探讨如何在涉及的所有服务中可视化客户端事务的流程,并了解事务所涉及的服务的性能特征。
图1-15展示了这些模式如何配合在一起。第11章将更加详细地介绍日志记录和跟踪模式。
图1-15 一个深思熟虑的日志记录和跟踪策略使跨多个服务的
调试事务变得可管理
应用程序度量模式处理应用程序如何监控度量数据,并对应用程序中可能的失败原因进行告警。该模式展示了度量服务如何负责获取(抓取)、存储和查询与业务相关的数据,以防止服务中出现潜在的性能问题。该模式包含以下3个主要组件。
● 度量数据——如何创建有关应用程序运行状况的关键信息,以及如何公开这些度量数据。
● 度量服务——可以在哪里存储和查询这些应用程序度量数据。
● 度量可视化套件——可以在哪里可视化应用程序和基础设施的业务相关的时间数据。
图1-16展示了微服务生成的度量数据是如何高度依赖于度量服务和可视化套件的。如果无法理解和分析信息,那么有能生成并显示无限信息的度量数据也没用。度量服务可以使用“拉”或“推”的风格获取度量数据。
● 使用“推”的风格,服务实例调用度量服务公开的服务API来发送应用程序度量数据。
● 使用“拉”的风格,度量服务向一个函数发出请求或查询来获取应用程序度量数据。
图1-16 通过“推”或“拉”的方式从微服务中获取度量数据,并在度量服务中收集和存储度量,使用度量可视化套件和告警管理工具进行显示
重要的是要理解监控度量数据是微服务架构的一个重要方面,而且由于这种架构具有高度分布性,此类架构的监控要求往往比单体架构更高。
微服务架构的核心部分之一是微服务的每个实例都应该和其他所有实例相同。开发人员不能允许配置漂移(某些东西在部署到服务器上之后会发生变化)出现,因为这可能会导致应用程序不稳定。
此模式的目标是将基础设施的配置集成到构建/部署过程中,这样就不用再将软件制品(如Java WAR或EAR文件)部署到已经在运行的基础设施中了。相反,开发人员希望在构建过程中构建和编译微服务并准备运行微服务的虚拟服务器镜像,在部署微服务时,就能部署服务器运行所需的整个机器镜像。图1-17阐述了这个过程。本书最后将介绍如何创建构建/部署管道。第12章将介绍以下模式和主题。
图1-17 开发人员希望微服务及其运行所需的服务器成为在不同环境间作为一个整体部署的原子制件
● 构建/部署管道——如何创建一个可重复的构建/部署过程,只需一键即可构建/部署到组织中的任何环境。
● 基础设施即代码——如何将服务的供应作为可在源代码控制下执行和管理的代码去对待。
● 不可变服务器—— 一旦创建了微服务镜像,如何确保它在部署之后永远不会更改。
● 凤凰服务器(Phoenix server)——如何确保运行单个容器的服务器定期被拆卸,并从一个不可变的镜像重新创建。服务器运行的时间越长,就越容易发生配置漂移。当对系统配置的临时更改未进行记录时,可能会发生配置漂移。
使用这些模式和主题的目的是在配置漂移影响到你的上层环境(如交付准备环境或生产环境)之前,尽可能快地暴露并消除配置漂移。
注意 本书中的代码示例(除了第12章)都能在本地台式机上运行。第1章的代码可以直接从命令行运行。从第3章开始,所有代码都要编译并作为Docker容器运行。
现在我们已经讨论了我们将在整本书中使用的模式,接下来继续学习第2章。第2章将介绍我们要使用的Spring Cloud技术、设计面向云微服务的应用程序的一些最佳实践,以及使用Spring Boot和Java创建我们的第一个微服务的第一步。
● 单体架构将所有流程紧密耦合,并作为单一服务运行。
● 微服务是非常小的功能部件,负责一个特定的范围领域。
● Spring Boot允许你创建这两种类型的架构。
● 单体架构往往是简单、轻量级应用程序的理想选择,而微服务架构通常更适合开发复杂和逐渐演变的应用程序。最后,选择软件架构将完全取决于项目规模、时间和需求,以及其他因素。
● Spring Boot用于简化基于REST的JSON微服务的构建,其目标是让用户只需要少量注解,就能够快速构建微服务。
● 编写微服务很容易,但是完全可以将其用于生产则需要额外的深谋远虑。有几类微服务模式,包括核心开发模式、路由模式、客户端弹性模式、安全模式、日志记录和跟踪模式、应用程序度量模式以及构建/部署模式。
● 路由模式处理想要消费微服务的客户端应用程序如何发现服务的位置并将其路由到该服务这一问题。
● 要防止服务实例中的问题向上和向外级联暴露给服务的消费者,请使用客户端弹性模式。其中包括避免调用失败服务的断路器模式,可创建备用路径以便在服务失败时检索数据或执行特定操作的后备模式,可用于扩展和消除所有可能瓶颈或故障点场景的客户端负载均衡模式,以及限制并发调用服务数量以阻止性能差的调用对其他服务产生负面影响的舱壁模式。
● OAuth2是最常见的用户授权协议,是保护微服务架构的最佳选择。
● 构建/部署模式允许开发人员将基础设施配置集成到构建/部署流程中,这样,开发人员就不用将Java WAR或EAR文件等软件制品部署到已经运行的基础设施中了。
微信扫码关注【异步社区】微信公众号,回复“e58748”获取本书配套资源以及异步社区15天VIP会员卡,近千本电子书免费畅读。
本章主要内容
● 学习Spring Cloud技术
● 了解云原生应用程序的原则
● 应用十二要素应用程序最佳实践
● 使用Spring Cloud构建微服务
如果没有正确地管理微服务的设计、实现和维护,那么它们很快就会成为一个问题。当我们开始采用微服务解决方案时,必须应用最佳实践来保持架构尽可能高效和可伸缩,以避免产品内部的性能问题、瓶颈或操作问题。坚持应用最佳实践也能让新开发人员更容易跟上系统的开发速度。在我们继续讨论微服务架构时,请务必牢记:一个系统分布得越广,它可能发生故障的地方就越多。
这句话的意思是,使用微服务架构时,我们会有更多的故障点。这是因为我们现在有一个由多个相互交互的服务组成的生态系统,而不是一个单体应用程序。这就是开发人员在创建微服务应用程序或架构时经常会遇到各种管理和同步挑战或者故障点的主要原因。为了避免可能出现的故障点,我们将使用Spring Cloud。Spring Cloud提供了一系列功能(服务注册和发现、断路器、监控以及其他功能),这些功能允许我们以最小的配置快速构建微服务架构。
本章将简要介绍本书中要用的Spring Cloud技术。这是一个高层次的概述。在你使用各项技术时,我们会根据需要讲解这些技术的细节。由于我们将在接下来的章节中使用微服务,因此理解微服务的概念、好处和开发模式是至关重要的。
从零开始实现我们在第1章中解释过的所有模式将是一项巨大的工作。幸好,Spring团队将大量经过实战检验的开源项目整合到了一个称为Spring Cloud的Spring子项目中。
Spring Cloud是一个工具集,它将VMware、HashiCorp和Netflix等开源公司的工作封装在交付模式中。Spring Cloud简化了项目的设置和配置,并为Spring应用程序中最常见的模式提供了解决方案。这样我们就可以专注于编写代码,而不会陷入配置构建和部署微服务应用程序的所有基础设施的细节中。图2-1将第1章中列出的模式映射到了实现它们的Spring Cloud项目。
图2-1 通过Spring Cloud,我们可以将打算直接使用的技术与我们到目前为止探讨过的
微服务模式对应起来
Spring Cloud Config通过集中式服务来处理应用程序配置数据的管理,因此应用程序配置数据(特别是环境特定的配置数据)与部署的微服务是完全分离的。这确保了无论启动多少个微服务实例,这些微服务实例始终具有相同的配置。Spring Cloud Config拥有自己的属性管理存储库,但也可以与以下开源项目集成。
● Git—— 一个开源版本控制系统,允许开发人员管理和跟踪任何类型的文本文件的更改。Spring Cloud Config集成了Git后端存储库,能从存储库中读出应用程序的配置数据。
● Consul—— 一种开源的服务发现工具,允许服务实例向该服务注册自己。服务客户端可以向Consul查询其服务实例的位置。Consul还包括键值存储数据库,Spring Cloud Config用它来存储应用程序的配置数据。
● Eureka—— 一个开源的Netflix项目,像Consul一样,提供类似的服务发现功能。Eureka同样有一个可以被Spring Cloud Config使用的键值数据库。
通过Spring Cloud服务发现,开发人员可以从消费服务的客户端中抽象出部署服务器的物理位置(IP地址或服务器名称)。服务消费者通过逻辑名称而不是物理位置来调用服务器的业务逻辑。Spring Cloud服务发现也处理服务实例在启动和关闭时的注册和注销。Spring Cloud服务发现可以使用以下服务来实现:
● Consul;
● ZooKeeper;
● Eureka。
注意 尽管Consul和ZooKeeper非常强大和灵活,但Java开发人员社区仍在广泛使用Eureka。因此,这本书将包含Eureka的例子,以保持本书的可管理性,并使内容更聚焦,以便于照顾到尽可能多的读者。但如果你对Consul或ZooKeeper感兴趣,请务必阅读附录C和附录D。附录C包含一个如何使用Consul作为服务发现的示例,附录D包含一个如何使用ZooKeeper的示例。
Spring Cloud与多个开源项目进行了大量整合。对于微服务客户端弹性模式,Spring Cloud封装了Resilience4j库和Spring Cloud LoadBalancer项目,你可以轻松地在微服务中使用它们。通过使用Resilience4j库,你可以快速实现服务客户端弹性模式,如断路器、重试和舱壁等模式。
虽然Spring Cloud LoadBalancer项目简化了与诸如Eureka这样的服务发现代理的集成,但它也为服务消费者提供了客户端对服务调用的负载均衡。这使得即使服务发现代理暂时不可用,客户端也可以继续进行服务调用。
API网关为微服务应用程序提供服务路由功能。正如其名称所示,服务网关代理服务请求,确保在调用目标服务之前,对微服务的所有调用都经过一个“前门”。通过集中的服务调用,开发人员可以强制执行标准服务策略,如安全授权、验证、内容过滤和路由规则。你可以使用Spring Cloud Gateway实现API网关。
注意 在这本书中,我们使用由Spring Framework 5 Project Reactor(允许与Spring Web Flux集成)和Spring Boot 2构建的Spring Cloud Gateway来更好地集成我们的Spring项目。
Spring Cloud Stream是一种可让开发人员轻松地将轻量级消息处理集成到微服务中的支持技术。借助Spring Cloud Stream,开发人员能够构建智能的微服务,它们使用在你的应用程序中出现的异步事件。你还可以快速整合微服务与RabbitMQ和Kafka等消息代理。
Spring Cloud Sleuth允许将唯一跟踪标识符集成到应用程序所使用的HTTP调用和消息通道(RabbitMQ、Apache Kafka)中。这些跟踪号码(有时称为关联ID或跟踪ID)能够让开发人员在事务流经应用程序中的不同服务时跟踪事务。有了Spring Cloud Sleuth,跟踪ID会被自动添加到微服务生成的任何日志记录语句中。
Spring Cloud Sleuth与日志聚合技术工具(如ELK技术栈)和跟踪工具(如Zipkin)结合时,能够展现出真正的威力。Open Zipkin获取Spring Cloud Sleuth生成的数据,让你可以可视化单个事务所涉及的服务调用流程。ELK技术栈是3个开源项目(Elasticsearch、Logstash和Kibana)名称的首字母缩写。
● Elasticsearch是搜索和分析引擎。
● Logstash 是服务器端数据处理管道,它消费数据并转换数据,以便将数据发送到“秘密存储点(stash)”。
● Kibana是一个客户端用户界面,允许用户查询并可视化整个技术栈的数据。
Spring Cloud Security是一个验证和授权框架,可以控制哪些人可以访问你的服务,以及他们可以用服务做什么。因为Spring Cloud Security是基于令牌的,它允许服务通过验证服务器发出的令牌彼此进行通信。接收HTTP调用的每个服务可以检查提供的令牌,以确认用户的身份以及用户对该服务的访问权限。此外,Spring Cloud Security还支持JSON Web Token(JWT)。JWT框架标准化了创建OAuth2令牌的格式,并为创建的令牌进行数字签名提供了标准。
在上一节中,我们解释了构建微服务时将要使用的所有不同的Spring Cloud技术。因为每一种技术都是独立的服务,要详细介绍这些服务,整整一章的内容都不够。不过,在本章结束时,我们想留给读者一个小小的代码示例,再次演示将这些技术集成到微服务开发中是多么容易。
与代码清单1-1中的第一个代码示例不同,这个代码示例不能运行,因为运行它需要先设置和配置许多支持服务。不过,不要担心,这些Spring Cloud服务的设置是一次性的。一旦将它们设置完成,你的微服务就可以反复使用这些功能。在本书的开头,我们无法将所有的精华都融入一个代码示例中。代码清单2-1中的代码快速演示了如何将远程服务的服务发现以及客户端负载均衡集成到我们的Hello World示例中。
代码清单2-1 使用Spring Cloud的Hello World服务
package com.optima.growth.simpleservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@RestController
@RequestMapping(value="hello")
@EnableEurekaClient ⇽--- 告诉服务向Eureka服务发现代理注册以查找远程服务的位置
public class Application {
public static void main(String[] args) {
SpringApplication.run(ContactServerAppApplication.class, args);
}
public String helloRemoteServiceCall(String firstName,String lastName){
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> restExchange = ⇽--- 使用一个装饰好的RestTemplate类来获取一个“逻辑”服务ID,Eureka在幕后查找服务的物理位置
restTemplate.exchange(
"http://logical-service-id/name/" + "{firstName}/
{lastName}", HttpMethod.GET, null, String.class,
firstName, lastName);
return restExchange.getBody();
}
@RequestMapping(value="/{firstName}/{lastName}",
method = RequestMethod.GET)
public String hello(@PathVariable("firstName") String firstName,
@PathVariable("lastName") String lastName) {
return helloRemoteServiceCall(firstName, lastName);
}
}
这段代码包含了很多内容,让我们慢慢分析。记住,这个代码清单只是一个例子,在第2章配套的源代码中是找不到的。我们把它放在这里,是为了让读者稍微了解一下本书后面的内容。
第一点要注意的是@EnableEurekaClient
注解。这个注解告诉微服务使用Eureka服务发现代理去注册它自己,因为你将要使用服务发现去查找远程REST服务端点。注意,配置是在一个属性文件中的,该属性文件告诉简单服务要进行通信的Eureka服务器的地址和端口号。
第二点要注意的是helloRemoteServiceCall
方法中发生的事情。@Enable- EurekaClient
注解告诉Spring Boot你正在启用Eureka客户端。需要强调的是,如果你的pom.xml中已经有了spring-cloud-starter-netflix-eureka-client
依赖项,那么这个注解是可选的。这个RestTemplate
类允许你传入自己想要调用的服务的逻辑服务ID,例如:
ResponseEntity<String> restExchange = restTemplate.exchange
(http://logical-service-id/name/{firstName}/{lastName}
在幕后,RestTemplate
类与Eureka服务进行通信,查找一个或多个命名服务实例的物理位置。作为服务的消费者,你的代码不需要知道服务的位置。
RestTemplate
类还使用Spring Cloud LoadBalancer库。这个库将会检索与服务有关的所有物理端点的列表。每次客户端调用该服务时,它都不必经过集中式负载均衡器,就可以对不同服务实例的调用进行“轮询”(round-robin)。通过消除集中式负载均衡器并将其移到客户端,可以消除应用程序基础设施中的另一个故障点(故障的负载均衡器)。
我们希望你现在有这样的深刻印象:你只用几个注解就可以为微服务添加大量的功能。这就是Spring Cloud的真正魅力。作为开发人员,你可以利用Netflix和Consul等知名云计算公司的微服务功能,这些功能是久经考验的。Spring Cloud简化了它们的使用,仅仅是使用一些简单的Spring Cloud注解和配置条目。在开始构建我们的第一个微服务之前,让我们看看实现云原生微服务的最佳实践。
在本节中,我们放缓一下脚步,先了解设计云服务的应用程序的最佳实践。在第1章中,我们解释了云计算模型之间的区别,但是云到底是什么呢?云不是一个地方,而是一套技术资源管理系统,可以让你使用虚拟基础设施来替代本地机器和私有数据中心。云应用程序有几种级别或类型,但在本节中,我们只关注两种类型的云应用程序——云就绪(cloud-ready)和云原生(cloud-native)。
云就绪应用程序是曾经在计算机或现场服务器上使用过的应用程序。随着云的到来,这些类型的应用程序已经从静态环境转移到动态环境,目的是在云中运行。例如,云未就绪(cloud-unready)应用程序可以是本地内部部署应用程序,它只包含一个特定的数据库配置,必须在每个安装环境(开发环境、交付准备环境、生产环境)中进行定制。为了让像这样的应用程序云就绪,我们需要将应用程序的配置外部化,以便它能够快速适应不同的环境。通过这么做,我们可以确保应用程序在构建期间不需要更改任何源代码,就能在多个环境中运行。
云原生应用程序(图2-2)是专门为云计算架构设计的,能享受云计算架构的所有好处和服务。在创建这种类型的应用程序时,开发人员将功能划分为微服务,这些微服务使用容器等可伸缩组件,使这些组件能够在多个服务器上运行。然后,这些服务由虚拟基础设施通过具有持续交付工作流的DevOps流程进行管理。
云就绪应用程序不需要任何更改或转变就可以在云中工作,理解这一点很重要。它们被设计用来处理下游组件不可用的情况。云原生开发有以下4个原则。
图2-2 云原生应用程序是通过容器等可伸缩组件构建的,以微服务的形式部署,
并由虚拟基础设施通过具有持续交付工作流的DevOps流程进行管理
● DevOps是development(Dev)和operations(Ops)的缩写。DevOps指的是一种软件开发方法,它关注软件开发人员和IT运维人员之间的交流、协作和集成。DevOps的主要目标是以较低的成本实现软件交付过程和基础设施变更的自动化。
● 微服务是小型的松耦合的分布式服务。这些特性允许你将一个大型应用程序分解为易于管理的具有严格定义职责的组件。它们还通过将大型代码库分解为定义良好的小型代码片段来帮助解决传统的复杂问题。
● 持续交付是一种软件开发实践。通过这种做法,交付软件的过程是自动化的,以允许向生产环境中进行短期交付。
● 容器是在虚拟机(virtual machine,VM)镜像上部署微服务的自然延伸。许多开发人员没有将服务部署到完整的虚拟机上,而是将他们的服务作为Docker容器(或类似的容器技术)部署到云上。
因为我们将在本书中重点讨论微服务的创建,所以我们应该记住,根据定义,这些微服务都是云原生的。这意味着一个微服务应用程序可以在多个云供应商上执行,同时享有云服务的所有好处。
为了应对创建云原生微服务的挑战,我们将使用Heroku的最佳实践指南,称为十二要素应用程序(twelve-factor app),来构建高质量的微服务。十二要素应用程序使我们能够开发和构建云原生应用程序(微服务)。我们可以将此方法视为开发和设计实践的集合,这些实践在构建分布式服务时侧重于动态扩展和基本点。
这套方法是由几个Heroku开发人员在2002年创造的,主要目标是在构建微服务时提供12种最佳实践。我们选择这个十二要素文档是因为它是创建云原生应用程序时要遵循的最完整的指南之一。这个指南不仅提供了有关现代应用程序开发中常见问题的通用词汇表,还提供了解决这些问题的健壮解决方案。图2-3显示了十二要素宣言所涵盖的最佳实践。
注意 在本章中,我们将为你提供每个最佳实践的简要概述,随着你继续阅读,你将看到我们是如何自始至终贯彻十二要素方法的。此外,我们将把这些实践应用到Spring Cloud项目和其他技术的例子中。
图2-3 十二要素应用程序最佳实践
通过此实践,每个微服务都应该有一个单独的、源代码控制的代码库。另外,必须要强调,服务器供应也应该处于版本控制中。请记住,版本控制是对一个或一组文件的更改的管理。
代码库可以有多个部署环境实例(如开发环境、测试环境、交付准备环境、生产环境等),但它不与任何其他微服务共享。这是一条重要的指导原则,因为如果我们把代码库共享给所有微服务,我们最终将产生许多属于不同环境的不可变版本。图2-4展示了多个部署环境共享的单个代码库。
图2-4 多个部署环境共享的单个代码库
此最佳实践通过构建工具(如Maven或Gradle(Java))显式声明应用程序使用的依赖项。第三方JAR依赖项应该使用它们的特定版本号来声明。这允许你始终使用相同的库版本构建微服务。
如果你不熟悉构建工具概念,那么图2-5可以帮助你了解构建工具的工作原理。首先,Maven读取存储在pom.xml文件中的依赖项,然后在本地存储库中搜索这些依赖项。如果找不到这些依赖项,则继续从Maven中央存储库下载这些依赖项,并将其添加到本地存储库中以备将来使用。
图2-5 Maven读取存储在pom.xml文件中的依赖项,然后在本地存储库中搜索它们。如果找不到依赖项,那么Maven将从Maven存储库中下载依赖项,并将它们添加到本地存储库中
此实践涉及如何存储应用程序配置(特别是特定于环境的配置)。永远不要在源代码中添加嵌入式配置!相反,最好将配置与可部署的微服务完全分离。
想象一下这样的场景:你想要更新一个特定的微服务的配置,该服务已经在服务器上复制了100次。如果将这个配置打包在微服务中,则需要对这100个实例中的每一个都进行重新部署,才能完成更改。然而,微服务可以加载外部配置,并可以使用云服务在运行时重新加载该配置,而无须重新启动微服务。图2-6中的示例展示了环境应该是什么样子的。
图2-6 将特定于环境的配置外部化
微服务通常会通过网络与数据库、API RESTful服务、其他服务器或消息传递系统进行通信。当这样做时,你应该确保可以在本地和第三方连接之间替换部署实现,而不需要对应用程序代码进行任何更改。在第12章中,我们将看到如何将微服务从本地管理的数据库转移到由亚马逊管理的数据库。图2-7展示了我们的应用程序可能拥有的一些后端服务的示例。
图2-7 后端服务是应用程序通过网络使用的任何服务。在部署应用程序时,你应该能够在不更改代码的
情况下将本地连接替换为第三方连接
这条最佳实践提醒我们保持应用程序部署的构建、发布和运行阶段完全分离。我们应该能够构建独立于运行它们的环境的微服务。一旦构建了代码,任何运行时更改都需要回退到构建流程并重新部署。已构建服务是不可变的,不能更改。
发布阶段负责将已构建服务与每个目标环境的特定配置相结合。如果不分离不同的阶段,则可能会导致代码中的问题和差异无法跟踪,或最好的情况下,难以跟踪。如果我们修改已经部署在生产环境中的服务,那么更改将不会记录在存储库中,并且可能出现两种情况:服务的新版本没有更改,或者我们被迫将更改复制到服务的新版本。图2-8展示了这条最佳实践的高层架构示例。
图2-8 最佳实践是严格分离微服务的构建、发布和运行阶段
微服务应该始终是无状态的,并且应该只包含执行请求的事务所必需的信息。微服务可以随时终止和替换,而不必担心服务实例的丢失会导致数据丢失。如果有存储状态的特定要求,则必须通过内存缓存(如Redis)或后端数据库来完成。图2-9展示了无状态微服务的工作方式。
图2-9 无状态微服务不会在服务器上存储任何会话数据(状态),它们使用SQL数据库或 NoSQL数据库来存储所有信息
端口绑定意味着通过特定端口发布服务。在微服务架构中,微服务在打包的时候应该是完全独立的,可运行的微服务中要包含一个运行时引擎。运行服务时不需要单独的Web或应用程序服务器。服务应该在命令行上自行启动,并且可通过公开的HTTP端口立即访问。
并发最佳实践解释了云原生应用程序应该使用进程模型水平扩展。这是什么意思?让我们设想一下,我们可以创建多个进程,然后在不同的进程之间分配服务的负载或应用程序,而不是让单个重要进程变得更大。
垂直扩展(纵向扩展)指的是增加硬件基础设施(如CPU、RAM)。水平扩展(横向扩展)是指添加更多的应用程序实例。当需要扩展时,启动更多的微服务实例,进行横向扩展而不是纵向扩展。图2-10展示了这两种扩展类型之间的区别。
图2-10 横向扩展和纵向扩展之间的区别
微服务是可任意处置的,可以根据需要启动和停止,以促进弹性扩展,并快速部署应用程序代码和配置更改。理想情况下,从启动命令执行到进程准备好接收请求,启动应该持续几秒钟。
可任意处置的意思是,我们可以用新实例移除失败实例,而不会影响任何其他服务。如果某个微服务的实例由于底层硬件故障而失败,我们可以关闭该实例而不影响其他微服务,并在需要时在其他地方启动另一个实例。
这条最佳实践指的是尽可能让不同的环境(如开发环境、交付准备环境、生产环境)保持相似。环境应始终包含部署代码的类似版本,基础设施和服务也一样。这可以通过持续部署来实现,它尽可能自动化部署过程,允许在短时间内在环境之间部署微服务。
代码一提交就应该被测试,然后尽快从开发环境推进到生产环境。如果我们想要避免部署错误,这条指导原则是必不可少的。拥有类似的开发环境和生产环境允许我们在部署和执行应用程序时掌控所有可能的场景。
日志是事件流。在写入日志时,应该通过Logstash或Fluentd等工具来管理日志,这些工具收集日志并将它们写到一个集中位置。微服务永远不应该关心这是如何发生的,它只需要专注于将日志条目写入标准输出(stdout)。
在第 11 章中,我们将演示如何提供一个自动配置来将这些日志发送到ELK技术栈(Elasticsearch、Logstash和Kibana)。图2-11展示了如何使用此技术栈在微服务架构中进行日志记录工作。
图2-11 使用ELK架构管理微服务日志
开发人员通常不得不针对他们的服务执行管理任务(如数据迁移或转换)。这些任务不应该是临时指定的,而应该通过源代码存储库管理和维护的脚本来完成。这些脚本应该是可重复的,并且在每个运行的环境中都是不可变的(脚本代码不会针对每个环境进行修改)。在运行微服务时,定义我们需要考虑的任务类型是很重要的,这样如果我们有多个带有这些脚本的微服务,就能够执行所有的管理任务,而不必手动执行这些任务。
注意 如果你有兴趣阅读更多关于Heroku的十二要素宣言,请访问十二要素应用程序网站。
在第8章中,我们将解释如何使用Spring Cloud API Gateway实现这些特性。既然我们已经了解了最佳实践是什么,我们就可以继续下一节了,在下一节中,我们将开始使用Spring Boot和Spring Cloud构建我们的第一个微服务。
我们想要确保本书提供的示例都是与你的工作息息相关的。为此,我们将围绕一家名为Optima Growth的虚构公司的软件产品来组织本书的章节和对应的代码示例。
Optima Growth是一家软件开发公司,其核心产品Optima Stock(我们称之为O-stock)提供企业级资产管理应用程序。该产品覆盖了所有关键要素:库存、软件交付、许可证管理、合规、成本和资源管理。其主要目标是使组织获得准确时间点的软件资产描述。这家公司大约有12年的历史。
Optima Growth打算重构其核心产品O-stock。虽然应用程序的大部分业务逻辑将保持原样,但应用程序本身将从单体设计分解为更小的微服务设计,其部件可以独立部署到云端。与O-stock相关的平台革新可能是该公司的“生死”时刻。
注意 本书中的示例不会构建整个O-stock应用程序。相反,我们将从手头的问题域构建特定的微服务,然后构建支持这些服务的基础设施。我们将通过使用各种Spring Cloud和一些非Spring Cloud的技术来实现这些。
成功采用基于云的微服务架构的能力将影响技术组织的所有成员,包括架构团队、工程(开发)团队和运维团队。每个团队都需要投入,最终,当团队重新评估他们在这个新环境中的职责时,他们可能需要重组。让我们开始Optima Growth的旅程吧,先做一些基础工作——识别和构建O-stock中使用的几个微服务,然后使用Spring Boot构建这些服务。
注意 我们知道资产管理系统的架构是复杂的。因此,在本书中,我们将只使用其中的一些基本概念,着重以一个简单的系统为例创建一个完整的微服务架构。创建一个完整的软件资产管理应用程序超出了本书的范围。
在本节中,我们将为前一节中提到的Optima Growth公司构建一个名为许可证服务的微服务骨架。我们将使用Spring Boot创建所有的微服务。
正如之前提到的,Spring Boot是标准Spring库之上的一个抽象层,它允许我们快速构建基于Groovy和Java的Web应用程序和微服务,所需的流程和配置比成熟的Spring应用程序少得多。对于许可证服务示例,我们将使用Java作为核心编程语言,使用Apache Maven作为构建工具。在接下来的几节中,我们将要完成以下工作。
(1)构建微服务的基本骨架,并创建构建应用程序的Maven脚本。
(2)实现一个Spring引导类,它将启动用于微服务的Spring容器,并启动类的所有初始化工作。
要开始构建微服务,你需要有以下工具:
● Java 11;
● Maven 3.5.4或更高版本;
● Spring Tools 4,或者你也可以在你选定的集成开发环境(IDE)中下载;
● IDE,如Eclipse、IntelliJ IDEA和NetBeans。
注意 从现在开始,所有的代码清单都将使用Spring Framework 5和Spring Boot 2创建。重要的是要理解,我们不打算解释Spring Boot的所有特性,只会强调一些创建微服务的重要点。另一个重要的事实是,我们将在这本书中使用Java 11,以便于照顾到尽可能多的读者。
首先,你要使用Spring Initializr为O-stock的许可证服务创建一个骨架项目。在使用Spring Initializr创建新的Spring Boot项目时,可以从扩展列表中选择依赖项。此外,你还可以使用Spring Initializr更改将要创建的特定项目配置。图2-12和图2-13展示了用于创建许可证服务的Spring Initializr页面的外观。
图2-12 用于许可证服务的Spring Initializr配置
图2-13 用于许可证服务的Spring Initializr依赖项
创建项目并将其作为Maven项目导入你的首选IDE之后,让我们添加以下包:
com.optimagrowth.license.controller
com.optimagrowth.license.model
com.optimagrowth.license.service
图2-14展示了IDE中许可证服务的初始项目结构。代码清单2-2展示了我们许可证服务的pom.xml文件的内容。
图2-14 O-stock的许可证服务项目结构,里面有引导类、应用程序属性、
测试用例和pom.xml
注意 关于如何测试我们的微服务的深入讨论超出了本书的范围。如果你有兴趣深入了解如何创建单元、集成和平台测试,我们强烈推荐Alex Soto Bueno、Andy Gumbrecht和Jason Porter的书Testting Java Microservices(Manning,2018)。
代码清单2-2 许可证服务的Maven pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId> ⇽--- ❶告诉Maven包含Spring Boot起步工具包依赖项
spring-boot-starter-parent
</artifactId>
<version>2.2.3.RELEASE</version>
<relativePath/> <!--从仓库中查找父依赖-->
</parent>
<groupId>com.optimagrowth</groupId>
<artifactId>licensing-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>License Service</name>
<description>Ostock Licensing Service</description>
<properties>
<java.version>11</java.version> ⇽--- ❷默认情况下,pom.xml文件会添加Java 6。为了使用Spring 5,我们用Java 11来覆盖它
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId> ⇽--- ❸告诉Maven包含Spring Actuator依赖项
spring-boot-starter-actuator
</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId> ⇽--- ❹告诉Maven包含Spring Boot Web依赖项
spring-boot-starter-web
</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId> ⇽--- ❺告诉Maven包含Spring特定的Maven插件,用于构建和部署Spring Boot应用程序
spring-boot-maven-plugin
</artifactId>
</plugin>
</plugins>
</build>
</project>
注意 Spring Boot项目不需要显式地设置各个Spring依赖项。这些依赖项是自动从pom.xml文件中定义的Spring Boot核心artifact中提取的。Spring Boot 2.x版本使用Spring framework 5。
这里不会详细讨论整个文件,但是我们要注意几个关键的地方。Spring Boot被分解成许多个独立的项目。其理念是,如果不打算在应用程序中使用Spring Boot的各个部分,就不必“拉取整个世界”。这也使不同的Spring Boot项目能够相互独立地发布新版本的代码。
为了简化开发人员的开发工作,Spring Boot团队将相关的依赖项目收集到了各种“起步”(starter)工具包中。在代码清单2-2中,Maven pom文件的❶告诉Maven需要拉取Spring Boot框架的特定版本(在我们的例子中是2.2.3)。在❷中,你指定了将要使用的Java版本,而在❸和❹中,你确定了要拉取Spring Actuator和Spring Boot Web起步工具包。请注意,Spring Actuator依赖项不是必需的,但在接下来的章节中我们会使用几个Actuator端点,这就是我们在此处添加这个依赖项的原因。这两个项目几乎是所有基于Spring Boot REST服务的核心。你会发现,服务中构建的功能越多,这些依赖项目的列表就会变得越长。
此外,Spring还提供了Maven插件,可简化Spring Boot应用程序的构建和部署。在pom文件的❺中,告诉Maven构建脚本安装最新的Spring Boot Maven插件。此插件包含许多附加任务(如spring-boot:run
),可以简化Maven和Spring Boot之间的交互。
为了检查Spring Boot引入我们许可证服务中的Spring依赖项,我们可以使用Maven目标——dependency:tree
。图2-15展示了许可证服务的依赖树。
图2-15 O-stock的许可证服务的依赖树。依赖树展示了在服务中声明和使用的所有依赖项
在本节中,我们的目标是在Spring Boot中启动并运行一个简单的微服务,然后重复这个步骤以提供一些功能。为此,你需要在许可证服务微服务中创建以下两个类:
● 一个Spring引导类,可被Spring Boot用于启动和初始化应用程序;
● 一个Spring控制器类,用来公开可以在微服务上调用的HTTP端点。
很快你就会看见,Spring Boot使用注解来简化设置和配置服务。这一点在代码清单2-3的引导类中显而易见。这个引导类位于src/main/java/com/optimagrowth/license目录的LicenseService- Application.java文件中。
代码清单2-3 引入@SpringBootApplication注解
package com.optimagrowth.license;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication ⇽--- 告诉Spring Boot框架,这是项目的引导类
public class LicenseServiceApplication {
public static void main(String[] args) {
SpringApplication.run( ⇽--- 启动整个Spring Boot服务
LicenseServiceApplication.class, args);
}
}
在这段代码中需要注意的第一件事是@SpringBootApplication
注解的用法。Spring Boot使用这个注解来告诉Spring容器,这个类是bean定义的源。在Spring Boot应用程序中,可以通过以下方法来定义Spring bean。
(1)用@Component
、@Service
或@Repository
注解标签来标注一个Java类。
(2)用@Configuration
注解标签来标注一个类,然后为每个我们想要构建的Spring bean定义一个构造器方法并为方法添加上@Bean
标签。
注意 Spring bean是Spring框架在运行时使用控制反转(Inversion of Control,IoC)容器管理的对象。它们被创建并添加到一个“对象仓库”中,以便以后可以获取它们。
在幕后,@SpringBootApplication
注解将代码清单2-3中的应用程序类标记为配置类,然后开始自动扫描Java类路径上所有的类以形成其他的Spring bean。
在代码清单2-3中,需要注意的第二件事是LicenseServiceApplication
类的main()
方法。在main()
方法中,SpringApplication.run(LicenseServiceApplication. class, args)
调用启动了Spring容器,返回了一个Spring ApplicationContext
对象。(这里没有使用ApplicationContext
做任何事情,因此没有在代码中展示。)
关于@SpringBootApplication
注解及其对应的LicenseServiceApplication
类,最容易记住的是,它是整个微服务的引导类。服务的核心初始化逻辑应该放在这个类中。
现在我们知道了如何创建微服务的骨架和引导类,让我们继续第3章。在第3章中,我们将解释在构建微服务时必须考虑的一些关键角色,以及这些角色是如何参与O-stock方案的创作的。此外,我们将解释一些额外的技术,使我们的微服务更加灵活和健壮。
● Spring Cloud是Netflix和HashiCorp等公司开源技术的集合。该技术已经用Spring注解进行了“包装”,从而显著简化了这些服务的设置和配置。
● 云原生应用程序是通过容器等可伸缩组件构建的,以微服务的形式部署,并由虚拟基础设施通过具有持续交付工作流的DevOps流程进行管理。
● DevOps是development(Dev)和operations(Ops)的缩写。DevOps指的是一种软件开发方法,它关注软件开发人员和IT运维人员之间的交流、协作和集成。DevOps的主要目标是以较低的成本实现软件交付过程和基础设施变更的自动化。
● 由Heroku提出的十二要素应用程序宣言提供了在构建云原生微服务时应该贯彻的最佳实践。
● 十二要素应用程序宣言的最佳实践包括代码库、依赖、配置、后端服务、构建/发布运行、进程、端口绑定、并发、可任意处置、开发环境/生产环境等同、日志和管理进程。
● 你可以通过Spring Initializr创建一个新的Spring Boot项目,同时从扩展列表中选择依赖项。
● Spring Boot是构建微服务的理想框架,因为它允许开发人员使用几个简单的注解即可构建基于REST的JSON服务。
微信扫码关注【异步社区】微信公众号,回复“e58748”获取本书配套资源以及异步社区15天VIP会员卡,近千本电子书免费畅读。