登入帳戶  | 訂單查詢  | 購物車/收銀台( 0 ) | 在線留言板  | 付款方式  | 聯絡我們  | 運費計算  | 幫助中心 |  加入書簽
會員登入 新註冊 | 新用戶登記
HOME新書上架暢銷書架好書推介特價區會員書架精選月讀2023年度TOP分類閱讀雜誌 香港/國際用戶
最新/最熱/最齊全的簡體書網 品種:超過100萬種書,正品正价,放心網購,悭钱省心 送貨:速遞 / EMS,時效:出貨後2-3日

2024年03月出版新書

2024年02月出版新書

2024年01月出版新書

2023年12月出版新書

2023年11月出版新書

2023年10月出版新書

2023年09月出版新書

2023年08月出版新書

2023年07月出版新書

2023年06月出版新書

2023年05月出版新書

2023年04月出版新書

2023年03月出版新書

2023年02月出版新書

『簡體書』微服务开发实战

書城自編碼: 3668392
分類: 簡體書→大陸圖書→計算機/網絡程序設計
作者: [美]保罗·奥斯曼 著 邓彪 译
國際書號(ISBN): 9787302581857
出版社: 清华大学出版社
出版日期: 2021-07-01

頁數/字數: /
書度/開本: 16开 釘裝: 平装

售價:NT$ 490

我要買

share:

** 我創建的書架 **
未登入.



新書推薦:
变态心理揭秘
《 变态心理揭秘 》

售價:NT$ 279.0
非洲三万里(2024版)
《 非洲三万里(2024版) 》

售價:NT$ 381.0
不思而美:一个人的心灵简史
《 不思而美:一个人的心灵简史 》

售價:NT$ 325.0
减压七处方
《 减压七处方 》

售價:NT$ 314.0
成为作家
《 成为作家 》

售價:NT$ 269.0
工作文化史 古代卷
《 工作文化史 古代卷 》

售價:NT$ 381.0
像亚马逊一样思考
《 像亚马逊一样思考 》

售價:NT$ 442.0
中国震撼
《 中国震撼 》

售價:NT$ 403.0

建議一齊購買:

+

NT$ 566
《 PHP 从入门到项目实践(超值版) 》
+

NT$ 200
《 C语言程序设计习题集(第3版) 》
+

NT$ 380
《 Java轻量级Web开发深度探索 》
+

NT$ 1188
《 PHP、MySQL与JavaScript学习手册(第五版) 》
+

NT$ 644
《 Python基础教程(第3版) 》
+

NT$ 534
《 Java高并发编程指南 》
編輯推薦:
《微服务开发实战》将为你开发微服务时可能涉及的许多主题提供方便的参考。我们将以秘笈的方式呈现开发技巧,这些秘笈将帮助你从单体架构顺利转换到微服务架构。我们将讨论在选择架构和管理微服务方式时遇到的特定问题或面临的挑战。本书秘笈包含大量有效、简单、经过测试的示例,你可以将它们应用到自己的开发过程中。我们希望本书可以帮助你思考、计划和执行基于微服务的应用程序的开发。
內容簡介:
《微服务开发实战》详细阐述了与微服务相关的基本解决方案,主要包括单体架构应用程序分解、边缘服务、服务间通信、客户端模式、可靠性模式、安全性、监控和可观察性、扩展、部署微服务等内容。此外,本书还提供了相应的示例、代码,以帮助读者进一步理解相关方案的实现过程。 本书适合作为高等院校计算机及相关专业的教材和教学参考书,也可作为相关开发人员的自学教材和参考手册。
關於作者:
保罗·奥斯曼有十多年的构建内部和外部平台的经验。从面向第三方的公共API到内部平台团队,他帮助构建了支持大型消费者应用的分布式系统。他拥有管理多个工程师团队的经历,致力于快速交付基于服务的软件系统。
保罗·奥斯曼发表过多篇有关微服务和运维一体化的技术文章,并进行了多次主题演讲。他是开放技术平台和工具的热情拥护者。
目錄
第1章 单体架构应用程序分解 1
1.1 导语 1
1.2 组织开发团队 1
1.2.1 实战操作 2
1.2.2 示例讨论 3
1.3 按业务功能分解微服务 3
1.3.1 理论阐释 4
1.3.2 实战操作 4
1.4 识别有界上下文 5
1.4.1 理论阐释 5
1.4.2 实战操作 8
1.5 迁移生产环境中的数据 9
1.5.1 理论阐释 9
1.5.2 实战操作 9
1.6 重构单体架构应用程序 12
1.6.1 理论阐释 12
1.6.2 实战操作 14
1.7 将单体架构应用程序升级为服务 16
1.7.1 理论阐释 16
1.7.2 实战操作 16
1.8 升级测试套件 18
1.8.1 做好准备 18
1.8.2 实战操作 18
1.9 使用Docker进行本地开发 19
1.9.1 做好准备 19
1.9.2 实战操作 19
1.10 将请求路由到服务 20
1.10.1 理论阐释 21
1.10.2 实战操作 21
第2章 边缘服务 23
2.1 导语 23
2.2 使用边缘代理服务器控制对服务的访问 23
2.2.1 操作说明 24
2.2.2 实战操作 25
2.3 通过边车模式扩展服务 28
2.3.1 理论阐释 28
2.3.2 实战操作 29
2.4 使用API网关将请求路由到服务 31
2.4.1 设计时需要考虑的问题 32
2.4.2 实战操作 33
2.5 使用Hystrix停止级联故障 40
2.5.1 理论阐释 41
2.5.2 实战操作 41
2.6 速率限制 45
2.6.1 理论阐释 45
2.6.2 实战操作 46
2.7 使用服务网格解决共同关注的问题 46
2.7.1 理论阐释 47
2.7.2 实战操作 47
第3章 服务间通信 49
3.1 导语 49
3.2 从服务到服务的通信 50
3.2.1 理论阐释 50
3.2.2 实战操作 51
3.3 并发异步请求 56
3.3.1 理论阐释 56
3.3.2 实战操作 57
3.4 使用服务发现来查找服务 61
3.4.1 理论阐释 61
3.4.2 实战操作 62
3.5 服务器端负载均衡 67
3.5.1 理论阐释 67
3.5.2 实战操作 68
3.6 客户端负载均衡 69
3.6.1 理论阐释 69
3.6.2 实战操作 69
3.7 构建事件驱动的微服务 71
3.7.1 理论阐释 72
3.7.2 实战操作 72
3.8 不断演变的API 77
3.8.1 理论阐释 77
3.8.2 实战操作 78
第4章 客户端模式 79
4.1 导语 79
4.2 使用依赖性的Future对并发进行建模 79
4.2.1 理论阐释 80
4.2.2 实战操作 80
4.3 服务于前端的后端 88
4.3.1 理论阐释 88
4.3.2 实战操作 90
4.4 使用JSON和HTTP实现RPC一致性 97
4.4.1 理论阐释 98
4.4.2 实战操作 98
4.5 使用Thrift 103
4.5.1 理论阐释 103
4.5.2 实战操作 103
4.6 使用gRPC 107
4.6.1 理论阐释 107
4.6.2 实战操作 107
第5章 可靠性模式 113
5.1 导语 113
5.2 使用断路器实现背压 114
5.2.1 理论阐释 114
5.2.2 实战操作 115
5.3 使用指数退避算法重试请求 126
5.3.1 理论阐释 126
5.3.2 实战操作 127
5.4 通过缓存提高性能 130
5.4.1 理论阐释 130
5.4.2 实战操作 131
5.5 通过CDN提供更高效的服务 136
5.5.1 理论阐释 137
5.5.2 实战操作 138
5.5.3 优雅地降低用户体验 138
5.6 通过游戏日演习验证容错能力 139
5.6.1 理论阐释 139
5.6.2 先决条件 140
5.6.3 实战操作 140
5.6.4 游戏日演习的模板 141
5.7 引入自动化混沌工程 142
5.7.1 理论阐释 142
5.7.2 实战操作 143
第6章 安全性 145
6.1 导语 145
6.2 身份验证微服务 146
6.2.1 理论阐释 146
6.2.2 实战操作 148
6.3 确保容器安全 162
6.3.1 理论阐释 162
6.3.2 实战操作 162
6.4 安全配置 163
6.4.1 理论阐释 163
6.4.2 实战操作 164
6.5 安全日志记录 176
6.6 基础架构即代码 176
6.6.1 理论阐释 176
6.6.2 实战操作 177
第7章 监控和可观察性 181
7.1 导语 181
7.2 结构化JSON日志记录 182
7.2.1 理论阐释 182
7.2.2 实战操作 182
7.3 使用StatsD和Graphite收集度量值 186
7.3.1 理论阐释 186
7.3.2 实战操作 186
7.4 使用Prometheus收集度量值 190
7.4.1 理论阐释 190
7.4.2 实战操作 191
7.5 通过跟踪使调试更容易 194
7.5.1 理论阐释 195
7.5.2 实战操作 195
7.6 出现问题时发出警报 197
7.6.1 理论阐释 198
7.6.2 实战操作 198
第8章 扩展 203
8.1 导语 203
8.2 使用Vegeta对微服务进行负载测试 203
8.2.1 理论阐释 203
8.2.2 实战操作 204
8.3 使用Gatling对微服务进行负载测试 209
8.3.1 理论阐释 209
8.3.2 实战操作 209
8.4 构建自动扩展集群 212
8.4.1 理论阐释 212
8.4.2 实战操作 212
第9章 部署微服务 215
9.1 导语 215
9.2 配置服务以在容器中运行 216
9.2.1 理论阐释 217
9.2.2 实战操作 217
9.3 使用Docker Compose运行多容器应用程序 218
9.3.1 理论阐释 218
9.3.2 实战操作 218
9.4 在Kubernetes上部署服务 220
9.4.1 理论阐释 220
9.4.2 实战操作 221
9.5 使用金丝雀部署方式测试版本 223
9.5.1 理论阐释 223
9.5.2 实战操作 224
內容試閱
关于微服务
在过去几年中,微服务已成为越来越受欢迎的主题。与任何新的架构概念一样,人们对微服务也有很大的误解,甚至微服务(Microservices)一词本身也令人困惑,新手通常难以分辨清楚微服务的适当大小(提示:实际上它并不是指代码库的大小),并且可能会陷入如何开始使用这种架构风格的困境。
面向服务的架构并不是什么新鲜事物。20世纪90年代,各种公司都在推广Web服务,以解决越来越大型化的、僵化的代码库问题。Web服务承诺将提供可重用的功能,代码库可以轻松使用这些功能。诸如SOAP和WSDL之类的技术开始获得采用,但它们似乎从未实现易用性承诺。与此同时,诸如PHP、Ruby和Python之类的开源语言以及诸如Symfony、Rails和Django之类的框架使得开发单体架构的以Web为中心的代码库变得更加容易。
就这样过了几十年,我们又重新对服务产生了兴趣。那么,为什么会有这种变化?首先,随着富Web和移动应用程序的出现,每个系统当前都是分布式系统。由于云计算的出现,计算和存储资源比以往任何时候都便宜。容器正在改变我们对部署和运营服务的思考方式。许多消费者服务已经超出了其单体架构代码库的规模,并且团队也发现单体架构应用程序难以扩展。
微服务的出现,可以帮助解决许多问题。
采用微服务架构的先决条件
微服务并不是的。尽管它们有很多好处,但是它们也带来了一些特定的挑战。在决定转向微服务之前,重要的一点是要拥有一定的基础设施和工具。Martin Fowler和Phil Calcado都撰写过有关微服务先决条件的文章。Martin Fowler的文章网址如下:
https://martinfowler.com/bliki/MicroservicePrerequisites.html
Phil Calcado的文章网址如下:
http://philcalcado.com/2017/06/11/calcados_microservices_prerequisites.html
在此,我们不想鹦鹉学舌重复别人讲过的东西;相反,我只想强调,在开始开发微服务之前,必须进行一定程度的自动化和监控。你的团队应该乐于承担值守职责,并且你应该拥有一个用于管理警报和升级的系统,如PagerDuty(http://pagerduty.com/)。
微服务的优势
采用微服务架构可以获得多种好处,以下我们将详细讨论。
伸缩性
在单体架构代码库中,不容易实现伸缩性,导致铺张过大、浪费资源,或者捉襟见肘、不敷使用。而微服务则不存在这个问题,它可以轻松地根据自己的需求扩展应用程序的各个部分。例如,应用程序的某个特定部分可能是每个用户都会用到的(如身份验证/授权服务),而另一部分则仅由部分用户使用(如搜索或发送消息)。不同的流量模式将转化为不同的扩展需求以及应用于扩展服务的不同技术。我们可以针对用户的每个请求开发服务,使得数据读取和存储的成本更低。另外,对于不需要提供非常一致结果的服务还可以利用缓存技术,这样响应速度更加快捷。
团队组织
当工程师团队在采用单独部署的单独代码库上工作时,他们能够独立地做出很多决定,而无须与组织中的其他团队进行协调。这意味着工程师可以自由地提交代码,设计自己的代码审查流程并部署到生产中,而无须始终进行协调。在单体架构应用程序中,工程师不得不将其更改放入队列中,然后在特定时间与其他团队的更改一起部署,这种情况非常普遍。如果出现问题(有害部署是常见的中断原因之一),则整个变更集将被回滚,从而延误了多个团队的工作。微服务允许团队以更大的自主权来帮助你避免这种情况。
可靠性
当单体架构应用程序失效时,它往往会完全失效。例如,首先是数据库不可用,然后该应用程序会尝试在连接池中使用陈旧的连接,终,服务请求的线程或进程被锁定,并且使用户白屏死机或无法运行移动应用程序。微服务使你可以根据情况决定如何处理应用程序特定部分的故障。如果你的服务无法访问数据库,则好返回一个过时的缓存或一个空响应;如果你的服务已经失败并开始返回HTTP 503响应,则上游服务可以通过施加背压来响应,从而使服务能够被恢复。
微服务为你提供了更大的自由来隔离系统中的故障,从而为用户带来更愉快的体验。
本书将为你开发微服务时可能涉及的许多主题提供方便的参考。我们将以秘笈的方式呈现开发技巧,这些秘笈将帮助你从单体架构顺利转换到微服务架构。我们将讨论在选择架构和管理微服务方式时遇到的特定问题或面临的挑战。本书秘笈包含大量有效、简单、经过测试的示例,你可以将它们应用到自己的开发过程中。我们希望本书可以帮助你思考、计划和执行基于微服务的应用程序的开发。祝阅读愉快!
本书读者
如果你是想构建有效且可扩展的微服务的开发人员,那么本书非常适合你。本书假定你具有微服务架构的一些基础知识。
内容介绍
本书共包含9章,具体内容如下。
第1章“单体架构应用程序分解”,将详细介绍如何实现从单体架构应用程序到微服务的过渡,本章秘笈着重于架构设计。当你开始使用微服务这种新的架构风格开发功能时,你将需要了解如何应对一些初的挑战。
第2章“边缘服务”,教你如何使用开源软件将服务公开到公共互联网、控制路由、扩展服务的功能,以及在部署和扩展微服务时如何应对许多常见挑战。
第3章“服务间通信”,将详细讨论使你能够自信地处理微服务架构中必定需要的各种交互的方法。
第4章“客户端模式”,将讨论对依赖的服务调用进行建模并聚合来自各种服务的响应以创建特定于客户端的API的技术。此外,我们还将讨论如何管理不同的微服务环境,使用JSON和HTTP实现RPC一致性,以及gRPC和Thrift的使用。
第5章“可靠性模式”,将讨论许多有用的可靠性模式,这些可靠性模式可以在设计和构建微服务时使用,以减少预期和意外的系统故障的影响。
第6章“安全性”,包括身份验证微服务、确保容器安全等秘笈,可帮助你学习构建、部署和操作微服务时要考虑的许多实践。
第7章“监控和可观察性”,将详细介绍若干个监控和可观察性方面的功能。我们将演示如何修改服务以发出结构化日志。此外,还将研究度量指标,使用许多不同的系统来收集、汇总和可视化度量指标。
第8章“扩展”,将讨论使用不同工具的负载测试。我们还将在AWS中设置自动伸缩组,使它们可以按需扩展。
第9章“部署微服务”,讨论了容器、业务流程和调度,以及将更改安全地交付给用户的各种方法。本章中的秘笈应作为一个良好的起点,特别是如果你习惯于在虚拟机或裸机服务器上部署单体架构应用程序的话。
充分利用本书
阅读本书,假定你具有一些有关微服务架构的基础知识。另外,本书还需要安装若干软件包,在相应的操作秘笈中都提供了这些安装操作的说明或网址。
下载示例代码文件
读者可以从www.packtpub.com下载本书的示例代码文件。具体步骤如下。
(1)登录或注册www.packtpub.com。
(2)在Search(搜索)框中输入本书名称Microservices Development Cookbook的一部分(不分区大小写,并且不必输入完全),即可看到本书出现在推荐下拉菜单中,如图P-1所示。
(3)单击选择Microservices Development Cookbook这本书,在其详细信息页面中单击Download code from GitHub(从GitHub中下载代码)按钮,如图P-2所示。需要说明的是,你需要登录此网站才能看到该下载按钮(注册账号是免费的)。

图P-1

图P-2
该书代码包在GitHub中的托管地址如下:
https://github.com/packtpublishing/microservices-development-cookbook
在该页面上单击Code(代码)按钮,然后选择Download ZIP即可下载本书代码包,如图P-3所示。

图P-3
如果代码有更新,则也会在现有GitHub存储库上更新。
下载文件后,请确保使用版本解压缩或解压缩文件夹。
? WinRAR/7-Zip(Windows系统)。
? Zipeg/iZip/UnRarX(Mac系统)。
? 7-Zip/PeaZip(Linux系统)。

本书约定
本书使用了许多文本约定。
(1)CodeInText:表示文本中的代码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟URL和用户输入等。以下段落就是一个示例:
无论是处理微服务架构还是单体架构代码库,都需要从根本上解决系统在某种故障状态下正常运行的问题。可以访问以下链接以获取更多信息:
https://www.youtube.com/watch?v=tZ2wj2pxO6Q
(2)有关代码块的设置如下:
class AttachmentsService
def upload(message_id, user_id, file_name, data, media_type)
message = Message.find_by!(message_id, user_id: user_id)
file = StorageBucket.files.create(
key: file_name,
body: StringIO.new(Base64.decode64(data), ‘rb’),
public: true
)
(3)当要强调代码块的特定部分时,相关行或项目以粗体显示。示例如下:
dependencies {
testCompile group: ‘junit’, name: ‘junit’, version: ‘4.12’
compile group: ‘io.github.resilience4j’, name: ‘resilience4j-
circuitbreaker’, version: ‘0.11.0’
compile group: ‘org.springframework.boot’, name: ‘spring-boot-
starter-web’
}
(4)任何命令行输入或输出都采用如下所示的粗体代码形式:
brew install docker-compose
(5)术语或重要单词采用中英文对照形式,在括号内保留其英文原文。示例如下:
康威定律(Conway’s Law)告诉我们,组织设计的系统架构是组织本身的沟通结构的副本,也就是说,产品的系统架构将打上开发它的组织的沟通结构的烙印。这通常意味着,软件工程团队的组织结构将对其开发的软件的设计结构产生深远的影响。
(6)本书还使用了以下两个图标。
表示警告或重要的注意事项。
表示提示或小技巧。
编写体例
本书大多数章节以秘笈形式编写,每一节就是一个秘笈,每个秘笈中又分别包括“理论阐释”“做好准备”“实战操作”等小节,使你既能理解微服务开发相关知识和原理,又能带着问题进入实战编码阶段,实现有目的且高效的学习。
关于作者
保罗?奥斯曼有十多年的构建内部和外部平台的经验。从面向第三方的公共API到内部平台团队,他帮助构建了支持大型消费者应用的分布式系统。他拥有管理多个工程师团队的经历,致力于快速交付基于服务的软件系统。
保罗?奥斯曼发表过多篇有关微服务和运维一体化的技术文章,并进行了多次主题演讲。他是开放技术平台和工具的热情拥护者。

第4章 客户端模式
本章包含以下操作秘笈。
使用依赖性的Future对并发进行建模。服务于前端的后端。使用JSON和HTTP实现RPC一致性。使用Thrift。使用gRPC。4.1 导 语
在构建面向服务的架构时,很容易陷入一种思考泥潭,即如何采用通用的方式来表示由特定服务控制的域实体和行为。但事实是,我们很少以通用方式使用服务—我们通常将对多个服务的调用组合在一起,并使用响应来创建新的聚合响应主体。
我们通常以类似于过去从数据库聚合数据的方式进行服务调用,因此必须考虑系统中不同类型之间的关系以及如何以方式对数据依赖关系进行建模。
我们还希望使客户端的开发变得更容易。在设计通用API时,也有一个容易陷入的思考泥潭,那就是如何以正确的方法做事而不是以简单的方式做事。例如,你也许听到过有人批评某个API设计不是RESTful架构的,这种论调就很可能已经落入这种窠臼。对于某个服务来说,如果客户端需要对它进行数十次的调用才能获取所需的数据,那么该服务就难言优秀。在设计包含微服务的系统时,必须从客户端的角度考虑数据聚合。
客户端不仅要考虑其正在调用的服务,而且往往还必须考虑到这些服务的实例,它们需要配置自身才能进行调用。常见的是使用模拟环境(Staging Environment)或测试环境(Testing Environment)。在微服务架构中,这些环境变得更加复杂。
在本章中,我们将讨论用于对依赖性的服务调用进行建模的技巧,以及如何聚合来自各种服务的响应以创建特定于客户端的API。我们还将讨论如何管理不同的微服务环境、如何使RPC与JSON和HTTP保持一致,以及gRPC和Thrift二进制协议的应用。
4.2 使用依赖性的Future对并发进行建模
在前面的秘笈中已经介绍过,开发人员可以使用异步方法进行服务调用,这些服务调用在单独的线程中处理。这非常重要,因为阻塞网络的输入/输出(I/O)将严重限制我们的服务能够处理的传入请求的数量。
4.2.1 理论阐释
如果服务会阻塞网络I/O,那么它在每个进程中就只能处理相对较少的请求,这要求我们在水平扩展上花费更多的资源。在我们的图像消息示例中,message-service服务需要为两个用户(消息的发送者和接收者)调用social-service社交图服务,确保在允许发送消息之前,这两个用户彼此已添加关注(或称为“互粉”)。我们修改了请求方法,以返回包装响应的CompletableFuture实例,然后等待所有结果完成,以验证消息的发送者和接收者是否具有对称的互粉关系。当你发出多个互不依赖的请求(也就是说,你不需要获得上一个请求的响应即可发出下一个请求)时,此模块应该能够正常工作。但是,在这种情况下,如果我们有依赖性的服务调用,则需要一种更好的方法来对依赖进行建模。
例如,在我们假想的pichat应用程序中,我们需要显示一个屏幕,其中列出了已经关注的用户的信息。为此,我们需要调用social-service的社交图服务以获取用户列表,然后调用users-service服务以获取用户的详细信息,如每个用户的显示名称和头像。这样的用例就涉及进行有依赖性的服务调用。我们需要一种有效的方法来对这种服务调用进行建模,同时仍然以异步操作方式来调度,允许它们在单独的执行线程中运行。
在本秘笈中,我们将通过使用CompletableFuture以及Java 8 Stream(流)的组合来对依赖性的服务调用进行建模,进而演示上述操作。我们将创建一个示例客户端应用程序,该应用程序将调用social-service服务以获取已登录用户所关注的用户列表,然后调用users-service服务以获取每个用户的详细信息。
4.2.2 实战操作
为了对依赖性的异步服务调用进行建模,我们将需要利用Java 8中的两个功能。流对于处理数据很有用,因此在本示例中,将使用它们来从已经加关注的列表中提取用户名,并将函数映射到每个元素。Java 8的CompletableFuture是可以组合的,这使我们可以自然地表达Future之间的依赖关系。
在此秘笈中,我们将创建一个简单的客户端应用程序,该应用程序将调用social- service服务以获取当前用户已经加关注的用户列表。对于返回的每个用户,应用程序将从users-service服务中获取用户的详细信息。为了便于演示,我们将本示例构建为命令行应用程序,但它也可以是另一个微服务、网页或移动客户端。
注意:
为了构建具有Spring Boot应用程序所有功能的命令行应用程序,我们将采取一些作弊手段,仅实现CommandLineRunner并在run()方法中调用System.exit(0);。
在开始构建应用程序之前,我们将简要介绍假设的social-service服务和users-service服务的响应。此外,我们还可以通过仅将适当的JSON响应托管在本地Web服务器上来模拟这些服务。后我们还将分别使用端口8000和8001来运行social-service服务和users- service服务。social-service服务有一个端点/followings/:username,它将返回一个JSON对象,其中包含指定用户名的已经加关注的好友列表。JSON响应将类似于以下片段:

{
”username”: ”paulosman”,
”followings”: [
”johnsmith”,
”janesmith”,
”petersmith”
]
}

users-service服务具有一个名为/users/:username的端点,它将返回用户详细信息的JSON表示,包括用户名、全名和头像URL等,具体如下:

{
”username”: ”paulosman”,
”full_name”: ”Paul Osman”,
”avatar_url”: ”http://foo.com/pic.jpg”
}

现在我们已经有了自己的服务,并且简要介绍了希望从每个服务获得的响应,接下来即可继续执行以下步骤来构建客户端应用程序。
(1)创建一个名为UserDetailsClient的新Java/Gradle应用程序。其build.gradle文件将包含以下内容:

group ‘com.packtpub.microservices’
version ‘1.0-SNAPSHOT’

buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath group: ‘org.springframework.boot’, name: ‘spring-
boot-gradle
-plugin’, version: ‘1.5.9.RELEASE’
}
}

apply plugin: ‘java’
apply plugin: ‘org.springframework.boot’

sourceCompatibility = 1.8

repositories {
mavenCentral()
}

dependencies {
testCompile group: ‘junit’, name: ‘junit’, version: ‘4.12’
compile group: ‘org.springframework.boot’,
name: ‘spring-boot-starter-web’
}

(2)创建一个名为com.packtpub.microservices.ch04.user.models的程序包和一个名为UserDetails的新类。我们将使用该类来对用户服务的响应进行建模,具体如下:

package com.packtpub.microservices.ch04.user.models;

import com.fasterxml.jackson.annotation.JsonProperty;

public class UserDetails {
private String username;

@JsonProperty(”display_name”)

private String displayName;

@JsonProperty(”avatar_url”)
private String avatarUrl;

public UserDetails() {}

public UserDetails(String username, String displayName,
String avatarUrl) {
this.username = username;
this.displayName = displayName;
this.avatarUrl = avatarUrl;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getDisplayName() {
return displayName;
}

public void setDisplayName(String displayName) {
this.displayName = displayName;
}

public String getAvatarUrl() {
return avatarUrl;
}

public void setAvatarUrl(String avatarUrl) {
this.avatarUrl = avatarUrl;
}

public String toString() {
return String.format(”[UserDetails: %s, %s, %s]”, username,
displayName, avatarUrl);
}
}

(3)在com.packtpub.microservices.ch04.user.models程序包中创建另一个类,名为Followings。这将用于对social-service服务的响应进行建模,具体如下:

package com.packtpub.microservices.ch04.user.models;

import java.util.List;

public class Followings {
private String username;
private List followings;

public Followings() {}

public Followings(String username, List followings) {
this.username = username;
this.followings = followings;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public List getFollowings() {
return followings;
}

public void setFollowings(List followings) {
this.followings = followings;
}

public String toString() {
return String.format(”[Followings for username: %s - %s]”,
username, followings);
}
}

(4)创建一个服务表示来调用social-service。为了更好地体现其意义,我们将其命名为SocialService,并将它放在com.packtpub.microservices.ch04.user.services程序包中,具体如下:

package com.packtpub.microservices.ch04.user.services;

import com.packtpub.microservices.models.Followings;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.CompletableFuture;

@Service
public class SocialService {

private final RestTemplate restTemplate;

public SocialService(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}

@Async
public CompletableFuture
getFollowings(String username) {
String url =
String.format(”http://localhost:8000/followings/
%s”, username);
Followings followings = restTemplate.getForObject(url,
Followings.class);
return CompletableFuture.completedFuture(followings);
}
}

(5)为users-service服务创建服务表示。同样,为了更好地体现其意义,我们将在同一个程序包中调用类UserService,具体如下:

package com.packtpub.microservices.services;

import com.packtpub.microservices.models.Followings;
import com.packtpub.microservices.models.UserDetails;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.CompletableFuture;

@Service
public class UserService {
private final RestTemplate restTemplate;

public UserService(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}

@Async
public CompletableFuture
getUserDetails(String username) {
String url = String.format(”http://localhost:8001/users/
%s”, username);
UserDetails userDetails = restTemplate.getForObject(url,
UserDetails.class);
return CompletableFuture.completedFuture(userDetails);
}
}

(6)现在,我们已经有了对服务的响应进行建模的类,并且也有了服务对象来表示我们将要调用的服务。现在可以通过创建主类将这些联系在一起,该主类将使用Future的可组合性对依赖关系进行建模,从而以依赖性的方式调用这两个服务。
创建一个名为UserDetailsClient的新类,具体如下:

package com.packtpub.microservices.ch04.user;

import com.packtpub.microservices.models.Followings;
import com.packtpub.microservices.models.UserDetails;
import com.packtpub.microservices.services.SocialService;
import com.packtpub.microservices.services.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

@SpringBootApplication
public class UserDetailsClient implements CommandLineRunner {

public UserDetailsClient() {}

@Autowired
private SocialService socialService;

@Autowired
private UserService userService;

public CompletableFuture>
getFollowingDetails(String username) {
return socialService.getFollowings(username).thenApply(f ->
f.getFollowings().stream().map(u ->userService.
getUserDetails(u)).map(CompletableFuture::join).
collect(Collectors.toList()));
}

public static void main(String[] args) {
SpringApplication.run(UserDetailsClient.class, args);
}

@Override
public void run(String... args) throws Exception {
Future> users = getFollowingDetails
(”paulosman”);
System.out.println(users.get());
System.out.println(”Heyo”);
System.exit(0);
}
}

该操作的秘密就在以下方法中:

CompletableFuture> getFollowingDetails(String
username)
{
return socialService.getFollowings(username).thenApply(
f -> f.getFollowings().stream().map(u ->
userService.getUserDetails(u)).map(
CompletableFuture::join).collect(Collectors.toList()));
}

回想一下,SocialService中的getFollowings方法返回CompletableFuture。 CompletableFuture具有一个名为thenApply的方法,该方法将获取Future的终结果(Followings)并将其应用到Lambda中。在这种情况下,我们将采用Followings,并使用Java 8 Stream API调用由social-service服务返回的用户名列表的映射。该映射将对每个用户名应用函数,函数将在UserService上调用getUserDetails。CompletableFuture:: join方法用于将List>转换为Future>,这是执行此类依赖服务调用时的常见操作。后,我们收集结果并将其作为列表返回。
4.3 服务于前端的后端
当软件从桌面和基于Web的应用程序转移到移动App时,分布式架构变得更加流行。许多组织将重点放在构建平台而不是产品上。这种方法强调API的重要性,产品可以向客户端和第三方合作伙伴公开这些API。
4.3.1 理论阐释
随着API成为任何基于Web的应用程序的给定条件,尝试基于相同的API构建客户端应用程序(移动应用或JavaScript)的做法也变得很流行,这些API可以向第三方合作伙伴提供功能。这种做法的思路是,如果你公开了一个设计良好的通用API,那么你将拥有构建任何类型的应用程序所需的一切。其通用架构如图4-1所示。
图4-1

原 文

译 文

原 文

译 文

Mobile App

移动App

Backend

后端

Web App

Web应用程序

Database

数据库

3rd Party Partners

第三方合作伙伴



上述方法的缺陷在于,它假定方(移动App和Web应用程序)和第三方(合作伙伴)应用程序的需求始终保持一致,而这种情况很少发生。更常见的是,你希望第三方集成中的某些功能类型和方客户端中的功能集是不一样的。
此外,你还希望容忍(甚至是鼓励)方客户端中的更改—你的客户端应用程序将不断发展,并且会不断更改其API要求。
后,你无法预期第三方合作伙伴使用你的API实现的所有可能用例,因此通用设计是有益的,但是这需要你能够预期移动App和Web应用程序的需求,而且过于笼统的API设计通常也会阻碍你的产品的需求。一个很好的例子是一个服务器端网站,该网站可被重写为单页JavaScript应用程序。但在使用通用API的情况下,此类项目可能会导致需要数十个XMLHttpRequest的页面视图来呈现单个页面视图。
服务于前端的后端(Backend For Frontend,BFF)是一种架构模式,涉及为客户端应用程序的不同类创建单独的、定制的API。根据你要支持的客户端应用程序的类别多少,你可以开发单独的BFF层,而不是在架构中仅有单个API层。如何对客户端进行分类完全取决于你的业务需求。开发人员可以决定为所有移动客户端使用单个BFF层,也可以将它们分为iOS BFF和Android BFF。同样,你也可以选择为Web应用程序和第三方合作伙伴提供单独的BFF层,如图4-2所示。
图4-2

原 文

译 文

原 文

译 文

Mobile App

移动App

Mobile BFF

移动BFF

Web App

Web应用程序

3rd Party BFF

为第三方合作伙伴提供的BFF

3rd Party Partners

第三方合作伙伴



在上述系统中,每种类别的客户端都向自己的BFF层发出请求,然后BFF层可以聚合对下游服务的调用,并构建一个整体的、定制的API。
4.3.2 实战操作
为了设计和构建BFF层,我们应该首先设计API。实际上,我们已经在这样做了。在前面的秘笈中,我们演示了使用CompletableFuture以异步方式向我们的系统和social-service服务发出请求,然后对于返回的每个用户,向user-details-service发出异步请求以获取某些用户的配置文件信息。对于我们的移动App来说,这是BFF层的绝佳用例。想象一下,我们的移动App具有一个屏幕,其中显示了该用户关注的用户列表,包括他们的基本信息,例如其头像、用户名和显示名称等。由于社交图信息(用户已经关注的用户列表)和用户个人资料信息(头像、用户名和显示名称)是两项单独服务的职责,因此,如果按照以前的做法的话,那么这是很麻烦的,它要求我们的移动客户端聚合对这些服务的调用,以显示已关注好友的页面。相反,我们可以创建一个移动BFF层来处理此聚合,并向客户端返回一个很方便的响应。我们的请求端点如下:

GET /users/:user_id/following

我们希望得到的响应主体如下:

{
”username”: ”paulosman”,
”followings”: [
{
”username”: ”friendlyuser”,
”display_name”: ”Friendly User”,
”avatar_url”: ”http://example.com/pic.jpg”
},
{
...
}
]
}

如我们所见,BFF将返回一个响应,其中包含在移动App中显示已关注好友的屏幕时所需的所有信息。
(1)创建一个名为bff-mobile的新Gradle/Java项目,其build.gradle文件将包含以下内容:

group ‘com.packtpub.microservices’
version ‘1.0-SNAPSHOT’

buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath group: ‘org.springframework.boot’,
name: ‘spring-boot-gradle-plugin’,
version: ‘1.5.9.RELEASE’
}
}

apply plugin: ‘java’
apply plugin: ‘org.springframework.boot’

sourceCompatibility = 1.8

repositories {
mavenCentral()
}

dependencies {
testCompile group: ‘junit’, name: ‘junit’, version: ‘4.12’
compile group: ‘org.springframework.boot’,
name: ‘spring-boot-starter-web’
}

(2)创建一个名为com.packtpub.microservices.ch04.mobilebff的新程序包和一个名为Main的新类。具体如下:

package com.packtpub.microservices.ch04.mobilebff;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
(3)创建一个名为com.packtpub.microservices.ch04.mobilebff.models的新程序包和一个名为User的新类。具体如下:

package com.packtpub.microservices.ch04.mobilebff.models;

import com.fasterxml.jackson.annotation.JsonProperty;

public class User {
private String username;

@JsonProperty(”display_name”)
private String displayName;

@JsonProperty(”avatar_url”)
private String avatarUrl;

public User() {}

public User(String username, String displayName,
String avatarUrl) {
this.username = username;
this.displayName = displayName;
this.avatarUrl = avatarUrl;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getDisplayName() {
return displayName;
}

public void setDisplayName(String displayName) {
this.displayName = displayName;
}

public String getAvatarUrl() {
return avatarUrl;
}

public void setAvatarUrl(String avatarUrl) {
this.avatarUrl = avatarUrl;
}

public String toString() {
return String.format(
”[User username:%s, displayName:%s, avatarUrl:%s]”,
username, displayName, avatarUrl);
}
}

(4)创建另一个模块,将其命名为Followings。具体如下:

package com.packtpub.microservices.ch04.mobilebff.models;

import java.util.List;

public class Followings {
private String username;

private List followings;

public Followings() {}

public Followings(String username, List followings) {
this.username = username;
this.followings = followings;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public List getFollowings() {
return followings;
}

public void setFollowings(List followings) {
this.followings = followings;
}
}

(5)我们将创建的后一个模块称为HydratedFollowings。它和Followings模块比较相似,但不是将用户列表存储为字符串,而是包含User对象的列表。具体如下:

package com.packtpub.microservices.ch04.mobilebff.models;

import java.util.List;

public class HydratedFollowings {
private String username;

private List followings;

public HydratedFollowings() {}

public HydratedFollowings(String username, List
followings) {
this.username = username;
this.followings = followings;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public List getFollowings() {
return followings;
}

public void setFollowings(List followings) {
this.followings = followings;
}
}

(6)创建服务客户端。创建一个名为com.packtpub.microservices.ch04.mobilebff.services的新程序包和一个名为SocialGraphService的新类。具体如下:

package com.packtpub.microservices.ch04.mobilebff.services;

import com.packtpub.microservices.ch04.mobilebff.models.Followings;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.CompletableFuture;

@Service
public class SocialGraphService {

private final RestTemplate restTemplate;

public SocialGraphService(RestTemplateBuilder
restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}

@Async
public CompletableFuture
getFollowing(String username) {
String url =
String.format(”http://localhost:4567/followings/
%s”, username);
Followings followings = restTemplate.getForObject(url,
Followings.class);
return CompletableFuture.completedFuture(followings);
}
}

(7)创建一个名为UsersService的新类,它将作为我们的用户服务的客户端。具体如下:

package com.packtpub.microservices.ch04.mobilebff.services;

import com.packtpub.microservices.ch04.mobilebff.models.User;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.CompletableFuture;

@Service
public class UsersService {

private final RestTemplate restTemplate;

public UsersService(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}

@Async
public CompletableFuture getUserDetails(String username)
{
String url = String.format(”http://localhost:4568/users/
%s”, username);
User user = restTemplate.getForObject(url, User.class);
return CompletableFuture.completedFuture(user);
}
}

(8)现在可以通过创建公开端点的控制器将步骤(2)~(7)中创建的包、类和模块绑定在一起。如果你已经完成了前面的秘笈,那么下面这段代码看起来会很熟悉,因为我们使用了完全相同的模式来对依赖性的异步服务调用进行建模。
创建一个名为com.packtpub.microservices.ch04.mobilebff.controllers的程序包和一个名为UsersController的新类:

package com.packtpub.microservices.ch04.mobilebff.controllers;

import
com.packtpub.microservices.ch04.mobilebff.models.HydratedFollowings;
import com.packtpub.microservices.ch04.mobilebff.models.User;
import
com.packtpub.microservices.ch04.mobilebff.services.SocialGraphService;
import com.packtpub.microservices.ch04.mobilebff.services.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

@RestController
public class UsersController {

@Autowired
private SocialGraphService socialGraphService;

@Autowired
private UsersService userService;

@RequestMapping(path = ”/users/{username}/followings”,
method = RequestMethod.GET)


public HydratedFollowings getFollowings(@PathVariable String
username)
throws ExecutionException, InterruptedException {
CompletableFuture> users =
socialGraphService.getFollowing
(username).thenApply(f -> f.getFollowings().stream().map(
u -> userService.getUserDetails(u)).map(
CompletableFuture::join).collect(Collectors.toList()));
return new HydratedFollowings(username, users.get());
}
}

(9)在编写完成之后,运行该应用程序并发送GET请求到/users/username/followings中。此时,你应该返回一个完全混合的JSON响应,其中包含用户的用户名和该用户已经关注的每个用户的详细信息。
4.4 使用JSON和HTTP实现RPC一致性
构建多个微服务时,服务之间的一致性和约定开始产生实际影响。当微服务架构中出现问题时,你可能终需要花大量的时间来调试这些服务。因此,如果能够对特定服务接口的性质做出某些假设,则可以节省大量的时间和精力。

4.4.1 理论阐释
拥有一致的远程过程调用(Remote Procedure Call,RPC)方法,可以使你将某些问题整理编写到可以在服务之间轻松共享的库中。通过采用一致的方法,可以简化诸如身份验证、应如何解释标头、响应主体中包含哪些信息,以及如何请求分页响应之类的事情。此外,报告错误的方式也应尽可能保持一致。
由于微服务架构通常由不同的团队以不同的编程语言编写的服务组成,因此必须使用一切方法努力实现一致的RPC语义,这可能是以库的方式,使用与构建服务时一样多的语言来实现。这可能会很麻烦,但为了客户端与各种服务进行对话时可以假定一致性,这样的麻烦是值得的。
在本秘笈中,我们将重点介绍在Java中使用Spring Boot编写的服务。我们将编写一个自定义的序列化器(Serializer),以一致的方式显示资源和资源集合,其中包括分页信息。然后,我们将修改message-service服务以使用新的序列化器。
4.4.2 实战操作
在本秘笈中,我们将创建一个包装器(Wrapper)类来表示带有分页信息的资源集合。我们还将使用来自jackson库的JsonRootName注解使单个资源表示形式保持一致。
首先可以修改前面秘笈中介绍过的message-service服务,将以下代码添加到其中。
(1)创建一个名为ResourceCollection的新类。此类将是一个普通的简单Java对象(Plain Ordinary Java Object,POJO),其中包含代表页码的字段、项目列表,以及可用于访问集合中下一页的URL。具体如下:

package com.packtpub.microservices.ch04.message.models;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;

import java.util.List;

@JsonRootName(”result”)
public class ResourceCollection {

private int page;

@JsonProperty(”next_url”)
private String nextUrl;

private List items;

public ResourceCollection(List items, int page, String
nextUrl) {
this.items = items;
this.page = page;
this.nextUrl = nextUrl;
}

public int getPage() {
return page;
}

public void setPage(int pageNumber) {
this.page = page;
}

public String getNextUrl() {
return nextUrl;
}

public void setNextUrl(String nextUrl) {
this.nextUrl = nextUrl;
}

public List getItems() {
return items;
}

public void setItems(List items) {
this.items = items;
}
}

(2)创建或修改Message模块。在这里可以使用JsonRootName注解将Message表示形式包装在单个JSON对象(该对象还包括item键)中。为了获得一致性的表示,还应该将它们添加到我们的服务作为资源公开的所有模块中。具体如下:

package com.packtpub.microservices.ch04.message.models;

import com.fasterxml.jackson.annotation.JsonRootName;

@JsonRootName(”item”)
public class Message {
private String id;
private String toUser;
private String fromUser;
private String body;

public Message(String id, String toUser, String fromUser,
String body) {
this.id = id;
this.toUser = toUser;
this.fromUser = fromUser;
this.body = body;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getToUser() {
return toUser;
}

public void setToUser(String toUser) {
this.toUser = toUser;
}

public String getFromUser() {
return fromUser;
}

public void setFromUser(String fromUser) {
this.fromUser = fromUser;
}

public String getBody() {
return body;
}

public void setBody(String body) {
this.body = body;
}
}

(3)以下控制器将返回一个消息列表和一条特定消息。我们将消息列表包装在先前创建的ResourceCollection类中:

package com.packtpub.microservices.ch04.message.controllers;

import com.packtpub.microservices.ch04.message.models.Message;
import com.packtpub.microservices.ch04.message.models.ResourceCollection;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@RestController
public class MessageController {

@RequestMapping(value = ”/messages”, method =
RequestMethod.GET)
public ResourceCollection
messages(@RequestParam(name=”page”, required=false,
defaultValue=”1”) int page,
HttpServletRequest request)
{
List messages = Stream.of(
new Message(”1234”,”paul”, ”veronica”, ”hello!”),
new Message(”5678”,”meghann”, ”paul”, ”hello!”)
).collect(Collectors.toList());

String nextUrl = String.format(”%s?page=%d”,
request.getRequestURI(), page 1);

return new ResourceCollection<>(messages, page, nextUrl);
}

@RequestMapping(value = ”/messages/{id}”, method =
RequestMethod.GET)
public Message message(@PathVariable(”id”) String id) {
return new Message(id, ”paul”, ”veronica”, ”hi dad”);
}
}

(4)如果通过对/messages发出请求来测试请求项目集合,那么现在应该返回以下JSON:

{
”result”: {
”page”: 1,
”items”: [
{
”id”: ”1234”,
”toUser”: ”paul”,
”fromUser”: ”veronica”,
”body”: ”hello!”
},
{
”id”: ”5678”,
”toUser”: ”meghann”,
”fromUser”: ”paul”,
”body”: ”hello!”
}
],
”next_url”: ”/messages?page=2”
}
}

(5)对于单个资源,应该会返回以下JSON:

{
”item”: {
”id”: ”123”,
”toUser”: ”paul”,
”fromUser”: ”veronica”,
”body”: ”hi dad”
}
}

对资源或资源列表的表示方式进行一些标准化可以极大地简化微服务架构中服务的处理。但是,使用JSON和HTTP进行此操作涉及大量的手动工作,因此可以考虑将其抽象化。在接下来的秘笈中,我们将探索使用Thrift和gRPC,这是RPC的HTTP/JSON表示方式的两种替代方法。
4.5 使用Thrift
JSON和HTTP是用于数据传输和定义的简单、直接的解决方案,完全可以满足许多微服务架构的目的。但是,如果想要实现类型安全并且需要更好的性能,那么值得考虑诸如Thrift或gRPC之类的二进制解决方案。
4.5.1 理论阐释
Apache Thrift是一种由Facebook发明的接口定义语言(Interface Definition Language,IDL)和二进制传输协议。它允许你通过定义结构(与大多数语言中的对象相似)和服务公开的异常来指定API。IDL中定义的Thrift接口可用于以受支持的语言生成代码,然后将其用于管理RPC调用。受支持的语言包括C、C 、Python、Ruby和Java。
二进制协议(如Thrift)的好处主要是提高了性能和类型安全性。根据使用的JSON库,对较大的JSON有效负载进行序列化和反序列化的成本可能会非常高,并且JSON没有客户端可以在处理响应时使用的任何类型系统。另外,由于Thrift包含可用于以任何受支持的语言生成代码的IDL,因此让Thrift处理客户端和服务器代码的生成很容易,从而减少了需要完成的手动工作量。
由于Apache Thrift不使用HTTP作为传输层,因此导出Thrift接口的服务将启动其自己的Thrift服务器。在本秘笈中,我们将为message-service服务定义IDL,并使用Thrift生成处理程序代码。然后,我们将创建服务器样板,它处理启动服务、侦听指定端口等操作。
4.5.2 实战操作
本秘笈需要执行以下操作。
(1)使用以下build.gradle文件创建一个新的Gradle/Java项目:

group ‘com.packtpub.microservices’
version ‘1.0-SNAPSHOT’

buildscript {
repositories {
maven {
url ”https://plugins.gradle.org/m2/”
}
}
dependencies {
classpath ”gradle.plugin.org.jruyi.gradle:thrift-gradle-
plugin:0.4.0”
}
}

apply plugin: ‘java’
apply plugin: ‘org.jruyi.thrift’
apply plugin: ‘application’

mainClassName =
‘com.packtpub.microservices.ch04.MessageServiceServer’

compileThrift {
recurse true

generator ‘html’
generator ‘java’, ‘private-members’
}

sourceCompatibility = 1.8

repositories {
mavenCentral()
}

dependencies {
compile group: ‘org.apache.thrift’, name: ‘libthrift’, version:
‘0.11.0’
testCompile group: ‘junit’, name: ‘junit’, version: ‘4.12’
}

(2)创建一个名为src/main/thrift的目录和一个名为service.thrift的文件。这是服务的IDL文件。我们将定义一个MessageException异常、实际的Message对象和一个MessageService接口。有关Thrift IDL文件的特定语法的更多信息可以访问Thrift项目网站上提供的说明文档,其网址如下:

https://thrift.apache.org/docs/idl
为简单起见,我们将在服务中定义一个方法,以返回特定用户的消息列表。具体如下:

namespace java com.packtpub.microservices.ch04.thrift

exception MessageException {
1: i32 code,
2: string description
}

struct Message {
1: i32 id,
2: string from_user,
3: string to_user,
4: string body
}

service MessageService {
list inbox(1: string username) throws
(1:MessageException e)
}

(3)运行汇编Gradle任务将为先前的IDL生成代码。现在,我们将创建MessageService类的实现。这将从先前的IDL扩展自动生成的接口。为简单起见,我们的MessageService实现将不会连接到任何数据库,而是使用收件箱(Inboxe)的静态的、硬编码的表示形式(收件箱将在构造函数中被构建)。

package com.packtpub.microservices.ch04.thrift;

import com.packtpub.microservices.ch04.thrift.Message;
import com.packtpub.microservices.ch04.thrift.MessageException;
import com.packtpub.microservices.ch04.thrift.MessageService;
import org.apache.thrift.TException;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class MessageServiceImpl implements MessageService.Iface {

private Map> messagesRepository;

MessageServiceImpl() {
// 使用一些示例消息来填充我们的模拟存储库
messagesRepository = new HashMap<>();
messagesRepository.put(”usertwo”, Stream.of(
new Message(1234, ”userone”, ”usertwo”, ”hi”),
new Message(5678, ”userthree”, ”usertwo”, ”hi”)
).collect(Collectors.toList()));
messagesRepository.put(”userone”, Stream.of(
new Message(1122, ”usertwo”, ”userone”, ”hi”),
new Message(2233, ”userthree”, ”userone”, ”hi”)
).collect(Collectors.toList()));
}

@Override
public List inbox(String username) throws TException {
if (!messagesRepository.containsKey(username))
throw new MessageException(100, ”Inbox is empty”);
return messagesRepository.get(username);
}
}

(4)创建服务器。创建一个名为MessageServiceServer的新类,具体如下:

package com.packtpub.microservices.ch04.thrift;

import com.packtpub.microservices.ch04.thrift.MessageService;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;
import org.apache.thrift.transport.TTransportException;

public class MessageServiceServer {

private TSimpleServer server;

private void start() throws TTransportException {
TServerTransport serverTransport = new TServerSocket(9999);
server = new TSimpleServer(new
TServer.Args(serverTransport)
.processor(new MessageService.Processor<>(new
MessageServiceImpl())));
server.serve();
}

private void stop() {
if (server != null && server.isServing())
server.stop();
}

public static void main(String[] args) {
MessageServiceServer service = new MessageServiceServer();
try {
if (args[1].equals(”start”))
service.start();
else if (args[2].equals(”stop”))
service.stop();
} catch (TTransportException e) {
e.printStackTrace();
}
}
}

服务现已构建完毕,并且已经将Apache Thrift应用于RPC。你可以进行下一步的练习,尝试使用相同的IDL生成可用于调用此服务的客户端代码。
4.6 使用gRPC
4.5节介绍了Thrift,本节将介绍同为二进制解决方案的gRPC。
4.6.1 理论阐释
gRPC初是由Google发明的远程过程调用(RPC)框架。与Thrift不同,gRPC利用了现有技术,特别是协议缓冲区(Protocol Buffer),用于其IDL,并且将HTTP/2用于其传输层。在完成了前面的秘笈之后,你应该会感觉到gRPC与Thrift在很多方面都很相似。不过,与Thrift IDL不同的是,其类型和服务是在.proto文件中定义的,并且.proto文件可以通过协议缓冲区的编译器生成代码。
4.6.2 实战操作
本秘笈需要执行以下操作。
(1)使用以下build.gradle文件创建一个新的Gradle/Java项目。值得一提的是,由于需要安装和配置protobuf Gradle插件,才能使用Gradle从protobuf文件生成代码。因此,这里需要将protobuf库列为依赖项。后,还必须告诉集成开发环境(IDE)到哪里去寻找已生成的类。具体如下:

group ‘com.packtpub.microservices’
version ‘1.0-SNAPSHOT’

buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath ‘com.google.protobuf:protobuf-gradle-
plugin:0.8.3’
}
}

apply plugin: ‘java’
apply plugin: ‘com.google.protobuf’
apply plugin: ‘application’

mainClassName =
‘com.packtpub.microservices.ch04.grpc.MessageServer’

sourceCompatibility = 1.8

repositories {
mavenCentral()
}

def grpcVersion = ‘1.10.0’

dependencies {
compile group: ‘com.google.api.grpc’, name: ‘proto-google-
common-protos’, version: ‘1.0.0’
compile group: ‘io.grpc’, name: ‘grpc-netty’, version:
grpcVersion
compile group: ‘io.grpc’, name: ‘grpc-protobuf’, version:
grpcVersion
compile group: ‘io.grpc’, name: ‘grpc-stub’, version:
grpcVersion
testCompile group: ‘junit’, name: ‘junit’, version: ‘4.12’
}

protobuf {
protoc {
artifact = ‘com.google.protobuf:protoc:3.5.1-1’
}
plugins {
grpc {
artifact = ”io.grpc:protoc-gen-grpc-
java:${grpcVersion}”
}
}
generateProtoTasks {
all()*.plugins {
grpc {}
}
}
}

// 向IntelliJ IDEA、Eclipse或NetBeans等IDE告知已生成的代码
sourceSets {
main {
java {
srcDirs ‘build/generated/source/proto/main/grpc’
srcDirs ‘build/generated/source/proto/main/java’
}
}
}

(2)创建一个名为src/main/proto的新目录和一个名为message_service.proto的新文件。这就是为我们的服务提供的protobuf的定义。和上一个秘笈一样,为简单起见,我们将仅公开一个方法,该方法将返回指定用户的消息列表。具体如下:

option java_package = ”com.packtpub.microservices.ch04.grpc”;

message Username {
required string username = 1;
}

message Message {
required string id = 1;
required string from_user = 2;
required string to_user = 3;
required string body = 4;
}

message InboxReply {
repeated Message messages = 1;
}

service MessageService {
rpc inbox(Username) returns (InboxReply) {}
}

(3)实现实际的服务。为此,我们需要创建一个名为MessageServer的新类,其中包含用于启动和停止服务器的所有必需样板。我们还将创建一个名为MessageService的内部类,该类扩展了已生成的MessageServiceGrpc.MessageServiceImplBase类。具体如下:

package com.packtpub.microservices.ch04.grpc;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;

import java.io.IOException;

public class MessageServer {

private final int port;
private final Server server;

private MessageServer(int port) throws IOException {
this(ServerBuilder.forPort(port), port);
}

private MessageServer(ServerBuilder serverBuilder, int port)
{
this.port = port;
this.server = serverBuilder.addService(new
MessageService()).build();
}

public void start() throws IOException {
server.start();
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
// 在这里使用stderr,因为日志管理程序可能已被其Java虚拟机(JVM)
// 关闭钩子重置
System.err.println(”*** shutting down gRPC server
since JVM is shutting down”);
MessageServer.this.stop();
System.err.println(”*** server shut down”);
}
});
}

public void stop() {
if (server != null) {
server.shutdown();
}
}

private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}

private static class MessageService extends
MessageServiceGrpc.MessageServiceImplBase {
public void inbox(MessageServiceOuterClass.Username
request,
StreamObserver
responseObserver) {
MessageServiceOuterClass.InboxReply reply =
MessageServiceOuterClass.InboxReply.newBuilder().addMessages(
MessageServiceOuterClass.Message.newBuilder()
.setId(”1234”)
.setFromUser(”Paul”)
.setToUser(”Veronica”)
.setBody(”hi”)
).addMessages(
MessageServiceOuterClass.Message.newBuilder()
.setId(”5678”)
.setFromUser(”FooBarUser”)
.setToUser(”Veronica”)
.setBody(”Hello again”)
).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}

public static void main(String[] args) throws Exception {
MessageServer server = new MessageServer(8989);
server.start();
server.blockUntilShutdown();
}
}

 

 

書城介紹  | 合作申請 | 索要書目  | 新手入門 | 聯絡方式  | 幫助中心 | 找書說明  | 送貨方式 | 付款方式 香港用户  | 台灣用户 | 海外用户
megBook.com.tw
Copyright (C) 2013 - 2024 (香港)大書城有限公司 All Rights Reserved.