java 开发微服务
1.简介
微服务架构本质上是进入分布式系统工程的旅程。 随着越来越多的微服务正在开发和部署,它们很有可能必须以某种方式彼此对话。 这些通信方式不仅会因传输方式和协议而异,而且也会以同步或异步方式发生变化。
目录
在本教程的这一部分中,我们将讨论应用于微服务体系结构的最广泛使用的通信方式。 正如我们将要看到的,每个人都有自己的优点和缺点,正确的选择在很大程度上取决于应用程序体系结构,需求和业务约束。 最重要的是,您没有义务只选择一个并坚持下去。 绝对有可能在不同的微服务组之间体现不同的通信模式,这取决于它们的角色,规范和命运。 值得提醒的是,我们在本教程的开头部分已经谈到了微服务的核心原则之一: 为工作选择合适的工具 。
2.使用HTTP
在当今世界, HTTP很可能是目前使用最广泛的通信协议。 它是万维网的基础组成部分之一,尽管已经很长时间没有变化,但最近却进行了重大改进以应对现代Web应用程序的挑战。
HTTP协议的语义确实很简单,但同时又足够灵活和强大。 在HTTP协议(更准确地说是HTTP / 1.1 )之上构建了几种主要的交互范例(或样式),它们相对于微服务体系结构实现显然处于主导地位。
2.1 SOAP
SOAP (或简单对象访问协议 )是用于在Web服务的实现中交换结构化信息的首批规范之一。 它的设计始于1998年,主要集中在主要通过HTTP协议传输的XML消息上。
SOAP协议的发展产生了一个特别创新的想法,即Web服务描述语言 (或简称WSDL ):一种基于XML的接口定义语言 ,用于描述SOAP Web服务提供的功能。 就像我们稍后将要看到的那样,从WSDL中吸取的教训告诉我们,以某种形式或其他形式,显式服务合同(或架构,规范,描述)的概念对于将提供商和使用者联系在一起是绝对必要的。
SOAP已经有20多年的历史了,为什么还要提起它呢? 令人惊讶的是,有很多系统都使用SOAP Web服务进行接口并且仍然被大量使用。
2.2 REST
对于许多人来说, REST体系结构风格的出现标志着SOAP时代的结束(严格来说,事实并非如此)。
代表性状态转移 ( REST )是一种体系结构样式,它定义了一组用于创建Web服务的约束。 符合REST体系结构样式的 Web服务 或RESTful Web服务 可在 Internet 上的计算机系统之间提供互操作性 。 符合REST的Web服务允许请求系统 通过使用统一且预定义的 无状态 操作 集 来访问和操纵 Web资源 的 文本表示 。
通过使用无状态协议和标准操作,REST系统旨在通过重新使用可以管理和更新的组件来实现快速性能,可靠性和扩展能力,即使在系统运行时也不会影响整个系统。
https://zh.wikipedia.org/wiki/Representational_state_transfer
术语表示状态转移的起源可以追溯到2000年,当时Roy Fielding在他著名的博士学位论文“建筑风格和基于网络的软件体系结构设计”中对其进行了定义 。
有趣的是, REST体系结构样式基本上与所使用的协议无关,但是由于HTTP而获得了极大的普及和采用。 这不是巧合,因为当今网络应用程序和API代表了很大一部分应用程序。
为了符合RESTful的标准 ,系统或应用程序应满足六个约束。 实际上,按照微服务体系结构的规则,它们都可以很好地发挥作用。
- 统一接口:谁是客户端都没有关系,请求看起来一样。
- 客户端和服务器分离 :服务器和客户端独立运行(关注点分离)。
- 无状态性 :在请求之间,没有特定于客户端的上下文存储在服务器上,并且来自任何客户端的每个请求均包含所有需要服务的信息。
- 可缓存的 :客户端和中介可以缓存响应,而响应则隐式或显式地将自身定义为可缓存的,以防止客户端获取过时的数据。
- 分层系统 :客户端通常无法确定客户端是直接连接到终端服务器还是中间设备。
- 按需代码(可选):服务器可以通过传输可执行代码(通常是某种脚本)来临时扩展或定制客户端的功能。
REST在HTTP协议的上下文中使用时,依赖于资源,统一资源定位符( URL ), 标准HTTP方法 , 标头和状态码来设计服务器与客户端之间的交互。 下表概述了HTTP协议语义到按照REST体系结构样式设计的可想象的库管理Web API的典型映射。
网址: https : //api.library.com/books/ | ||||||
得到 | 放 | 补丁 | 开机自检 | 删除 | 选件 | 头 |
检索集合中的所有资源。 | 用另一个集合替换整个集合。 | 一般不使用。 | 在集合中创建一个新条目。 新条目的URI通常由操作返回。 | 删除整个集合。 | 列出可用的HTTP方法(可能是其他选项)。 | 检索集合中的所有资源(仅应返回标头)。 |
网址:https://api.library.com/books/17 | ||||||
得到 | 放 | 补丁 | 开机自检 | 删除 | 选件 | 头 |
检索单个资源的表示形式。 | 完全替换资源(如果不存在,则创建它)。 | 更新资源(通常是部分更新 。 | 一般不使用。 | 删除资源。 | 列出可用的HTTP方法(可能是其他选项)。 | 检索单个资源(仅应返回标头)。 |
幂: 是 | 幂: 是 | 幂等: 否 | 幂等: 否 | 幂: 是 | 幂: 是 | 幂: 是 |
安全: 是 | 安全: 否 | 安全: 否 | 安全: 否 | 安全: 否 | 安全: 是 | 安全: 是 |
在REST (尤其是微服务 )的上下文中,每种HTTP方法还有其他一些微妙但异常重要的期望(如果需要的话,也可以包含隐含的前提):幂等性和安全性。 即使多次发送相同的输入给操作带来相同的效果(仅发送一次输入),该操作也被认为是幂等的。 因此,如果不修改一个或多个资源,则该操作是安全的。 关于幂等性和安全性的假设对于处理故障以及做出减少故障的决策至关重要。
综上所述,开始构建RESTful Web API非常容易,因为大多数每种编程语言都将HTTP服务器和客户端烘焙到其基础库中。 使用它们也不是一件容易的事:从命令行( curl , httpie ),使用专用的桌面客户端( Postman , Insomnia ),或者甚至从Web浏览器(尽管如果不安装其他插件,您也无能为力)。
REST的这种简单性和灵活性需要付出一定的代价:缺少对可发现性和自省的一流支持。 服务器和客户端之间关于资源以及输入和输出内容的协议是带外知识。
API样式书及其设计指南和设计主题是学习构建最佳RESTful Web API的最佳实践和模式的绝佳资源。 顺便说一句,如果您觉得REST体系结构样式限制您的API遵循CRUD (创建/读取/更新/删除)语义,那肯定是一个神话 。
2.3 REST:救援合同
RESTful Web API缺乏明确的,可共享的,描述性的合同(除了静态文档),一直是社区中积极研究和开发的领域。 幸运的是,最近的努力最终集中在建立OpenAPI Initiative和发布OpenAPI 3.0规范 (以前称为Swagger )上。
所述 的OpenAPI 规范(OAS)定义了一个标准,用于编程语言无关的接口描述 的REST API ,它允许人类和计算机发现和理解的服务的能力,而无需访问源代码,附加文档或网络业务的检查。 通过 OpenAPI 正确定义 后 ,使用者可以用最少的实现逻辑来理解远程服务并与之交互。 与接口说明针对低级编程所做的类似, OpenAPI 规范消除了调用服务时的猜测。 – https://github.com/OAI/OpenAPI-Specification
OpenAPI不是每个人都有义务使用的事实上的标准,而是一种经过深思熟虑的综合手段来管理RESTful Web API的合同。 正如我们稍后将在本教程中看到的那样,它带来的另一个好处是,围绕OpenAPI的工具真是太神奇了。
在替代选项中,值得一提的是API蓝图 , RAML , Apiary和Apigee 。 老实说,您将要使用的内容并不重要,向合同驱动的开发和协作的转变确实无关紧要。
2.4 GraphQL
一切都在向前发展, REST的主要地位被GraphQL的新手动摇了 。
GraphQL 是API的查询语言,是用于使用现有数据来完成这些查询的运行时。 GraphQL 为您的API中的数据提供了完整且易于理解的描述,使客户能够准确地询问他们需要什么,仅此而已,使随着时间的推移更容易开发API,并启用了功能强大的开发人员工具。 – https://graphql.org/
GraphQL有一个有趣的故事。 它最初于2012年在Facebook上创建,旨在解决为客户端/服务器应用程序处理其数据模型的挑战。 GraphQL规范的公开开发仅在2015年才开始,自那时以来,这项相当新的技术一直在稳步获得普及和广泛采用。
GraphQL 不是能够进行任意计算的编程语言,而是用于查询具有本规范中定义的功能的应用程序服务器的语言。 GraphQL 没有为实现它的应用程序服务器 强制 使用特定的编程语言或存储系统。 相反,应用程序服务器利用其功能并将其映射到 GraphQL 编码 的统一语言,类型系统和理念 。 这提供了对产品开发友好的统一界面以及强大的工具构建平台。 – http://facebook.github.io/graphql/June2018/
使GraphQL特别吸引微服务的原因是其一系列核心设计原则:
- 它是分层的 :如今,大多数数据都组织为分层结构。 为了实现与这种现实的一致性, GraphQL查询本身是分层结构的。
- 强 类型化 :每个应用程序都声明自己的类型系统(也称为模式)。 每个GraphQL查询都在该类型系统的上下文中执行,而GraphQL服务器则在执行该查询之前强制执行该查询的有效性和正确性。
- 客户端指定的 查询 : GraphQL服务器发布可用于其客户端的功能。 客户有责任确切地指定如何使用这些已发布的功能,以便给定的GraphQL查询准确返回客户的要求。
- 内省 :由特定GraphQL服务器管理的特定类型系统必须可以由GraphQL语言本身查询。
GraphQL使客户可以控制所需的数据。 尽管它有一些缺点,但强类型和自省的引人注目的好处通常使GraphQL成为一个不错的选择。
毫不奇怪,大多数GraphQL实现也是基于HTTP的,并且有充分的理由:可以作为构建Web API的基础。 简而言之, GraphQL服务器应仅处理HTTP GET
和POST
方法。 由于GraphQL中的概念模型是实体图,因此此类实体不会由URL标识。 相反, GraphQL服务器在单个端点(通常为/graphql
)上运行,该端点处理对给定服务的所有请求。
令人惊讶的是(或没有?),许多人将GraphQL和REST视为直接竞争对手:您必须选择一个或另一个。 但事实是,两者都是极好的选择,并且可以愉快地共存以最有效的方式解决业务问题。 这就是微服务的全部内容,对不对?
的实现GraphQL中存在许多编程语言 (例如, graphql的Java的Java, 桑格利亚斯卡拉,只是仅举几例),但JavaScript的一个突出,并设置步伐,为整个生态系统。
让我们看一下如何用GraphQL模式和类型描述上一节中的RESTful Web API。
schema {
query: Query
mutation: Mutation
}
type Book {
isbn: ID!
title: String!
year: Int
}
type Query {
books: [Book]
book(isbn: ID!): Book
}
# this schema allows the following mutation:
type Mutation {
addBook(isbn: ID!, title: String!, year: Int): Book
updateBook(isbn: ID!, title: String, year: Int): Book
removeBook(isbn: ID!): Boolean
}
突变和查询之间的分隔自然为特定操作的安全性提供了明确的保证。
可以说,随着越来越多的公司对其进行改编或已经进行改编, GraphQL正在缓慢而稳定地改变着Web API的格局。 您可能没有想到,但是RESTful和GraphQL通常并排部署。 这种共存的新模式之一是后端 ( BFF ),其中GraphQL Web API面向RESTful Web服务。
3.不仅HTTP
尽管HTTP是最主要的方法,但是还有一些通信框架和库可以超越此范围。 像RPC样式的对话一样,是进程间通信的最早形式。
本质上, RPC是一种请求-响应协议,其中客户端将请求发送到远程服务器,以使用提供的参数执行指定的过程。 除非客户端和服务器之间的通信是异步的,否则客户端通常会阻塞,直到远程服务器发回响应为止。 尽管RPC非常有效(大多数时候交换格式是二进制格式),但RPC过去在跨不同语言和平台的互操作性和可移植性方面存在巨大的问题。 那么,为什么要铲除旧骨灰呢?
3.1 gRPC
HTTP / 2是HTTP协议的主要修订版,它解除了驱动网络通信的新方式的限制。 GRPC ,流行的,高性能的,开源的通用RPC从框架谷歌 ,是谁在桥梁的一个RPC与语义HTTP / 2协议。
要在此处添加注释,尽管gRPC或多或少与基础传输无关,但除HTTP / 2之外,没有支持其他传输(并且没有计划在不久的将来进行更改)。 在底层 , gRPC建立在Google广泛采用和成熟的另一项技术之上,称为协议缓冲区 。
协议缓冲区是一种用于序列化结构化数据的灵活,高效,自动化的机制–以XML为例,但更小,更快,更简单。 您定义要一次构造数据的方式,然后可以使用生成的特殊源代码轻松地使用各种语言在各种数据流中写入和读取结构化数据。 您甚至可以更新数据结构,而不会破坏已按照“旧”格式编译的已部署程序。 – https://developers.google.com/protocol-buffers/docs/overview
默认情况下, gRPC使用协议缓冲区作为其接口定义语言 ( IDL )和其基础消息交换格式。 IDL包含所有数据结构和服务的定义,并在gRPC服务器与其客户端之间进行合同。
例如,这是使用协议缓冲区规范从上一节重新定义Web API的非常简化的尝试。
syntax = "proto3";
import "google/protobuf/empty.proto";
option java_multiple_files = true;
option java_package = "com.javacodegeeks.library";
package library;
service Library {
rpc addBook(AddBookRequest) returns (Book);
rpc getBooks(Filter) returns (BookList);
rpc removeBook(RemoveBookRequest) returns (google.protobuf.Empty);
rpc updateBook(UpdateBookRequest) returns (Book);
}
message Book {
string title = 1;
string isbn = 2;
int32 year = 3;
}
message RemoveBookRequest {
string isbn = 1;
}
message AddBookRequest {
string title = 1;
string isbn = 2;
int32 year = 3;
}
message UpdateBookRequest {
string isbn = 1;
Book book = 2;
}
message Filter {
int32 year = 1;
string title = 2;
string isbn = 3;
}
message BookList {
repeated Book books = 1;
}
gRPC提供了许多主流编程语言的绑定,并依赖于协议缓冲区工具和插件来生成代码(但是,如果您使用Go进行编程,那么您会很幸运,因为Go语言生态系统是目前的最新技术)。 gRPC是建立内部服务对服务或服务对消费者通信的有效渠道的绝佳方法。
如今, gRPC围绕着许多激动人心的发展。 最有前途的是用于Web客户端的gRPC (当前为beta),它将提供一个JavaScript客户端库,使浏览器客户端可以直接访问gRPC服务器。
3.2 Apache节俭
公平地说, gRPC不是唯一可用的RPC样式的框架。 Apache Thrift是另一种致力于可伸缩的跨语言服务开发的工具。 它结合了软件堆栈和代码生成引擎,以构建可在多种语言之间高效且无缝运行的服务。
Apache Thrift是专门为支持跨客户端和服务器代码的非原子版本更改而设计的。 它与gRPC和协议缓冲区非常相似,并且具有相同的细分市场。 尽管它不如gRPC流行,但它支持25种编程语言的绑定,并依赖于模块化的传输机制(包括HTTP )。
Apache Thrift有自己的接口定义语言方言,它非常类似于协议缓冲区 。 作为比较,这是使用Apache Thrift重写的Web API定义的另一个版本。
namespace java com.javacodegeeks.library
service Library {
void addBook(1: Book book),
list getBooks(1: Filter filter),
bool removeBook(1: string isbn),
Book updateBook(1: string isbn, 2: Book book)
}
struct Book {
1: string title,
2: string isbn,
3: optional i32 year
}
struct Filter {
1: optional i32 year;
2: optional string title;
3: optional string isbn;
}
3.3 Apache Avro
最后但并非最不重要的一点是,数据序列化系统Apache Avro通常用于RPC样式的通信和消息交换。 Apache Avro与其他API的不同之处在于,该模式以JSON格式表示,例如,这是将我们的Web API转换为Apache Avro 。
{
"namespace": "com.javacodegeeks.avro",
"protocol": "Library",
"types": [
{
"name": "Book",
"type": "record",
"fields": [
{"name": "title", "type": "string"},
{"name": "isbn", "type": "string"},
{"name": "year", "type": "int"}
]
}
],
"messages": {
"addBook": {
"request": [{"name": "book", "type": "Book"}],
"response": "null"
},
"removeBook": {
"request": [{"name": "isbn", "type": "string"}],
"response": "boolean"
},
"updateBook": {
"request": [
{"name": "isbn", "type": "string"},
{"name": "book", "type": "Book"}
],
"response": "Book"
}
}
}
Apache Avro的另一个独特功能是根据文件扩展名制定不同的规范,例如:
- * .avpr :定义Avro协议规范
- * .avsc :定义Avro架构规范
- * .avdl:定义Avro IDL
与Apache Thrift相似, Apache Avro支持包括HTTP在内的不同传输(也可以是无状态或有状态 )。
4. REST,GraphQL,gRPC,Thrift……如何选择?
要了解每种通信方式最适合的位置, 了解RPC,REST和GraphQL是一个很好的起点。
5.消息传递
请求-响应不是构造分布式系统(尤其是微服务)中通信的唯一方法。 消息传递是另一种本质上异步的通信方式,它围绕所有参与者之间的消息交换展开。
消息传递是均匀驱动的应用程序和微服务的灵魂。 在实践中,它们主要是基于事件源或命令查询责任隔离 ( CQRS )体系结构的原理来实现的,但是对事件驱动的含义的定义要比这更广泛。
公平地说,有很多不同的选择可以讨论和选择。 因此,为了保持理智,我们将更加关注核心概念,而不是具体的解决方案。
5.1 WebSocket和服务器发送的事件
如果您的微服务架构由RESTful Web服务组成,那么选择本机HTTP消息传递解决方案是合乎逻辑的方法。
WebSocket协议通过单个连接启用客户端和服务器之间的双向( 全双工 )通信通道。 有趣的是, WebSocket是一个独立的基于TCP的协议,但同时“……它旨在在HTTP端口80和443上工作,并支持HTTP代理和中介……”( https://tools.ietf.org / html / rfc6455 ) 。
对于非双向通信, 服务器发送的事件 (或简称为SSE )是使服务器能够通过HTTP (或使用专用服务器推送协议)将数据推送到客户端的一种简便的好方法。
随着HTTP / 2的日益普及, WebSocket和服务器发送的事件的作用逐渐减弱,因为它们的大多数功能已经被支持到协议本身中。
5.2消息队列和代理
在软件开发中,消息传递异常有趣且空间拥挤。 Java消息服务 (JMS), 高级消息队列协议 (AMQP), 简单(或流) 面向 文本的消息协议 (STOMP), Apache Kafka , NATS , NSQ , ZeroMQ ,更不用说Redis Pub / Sub了 ,即将到来的Redis流和吨云解决方案。 怎么说,甚至PostgreSQL也包括一个 !
根据您的应用程序需求,很可能会找到多个消息代理供您选择。 但是,您可能需要解决一个有趣的挑战:
- 高效地发布消息架构(以共享消息中打包的内容)
- 随着时间的推移发展消息模式(理想情况下,不会破坏事物)
令人惊讶的是,我们的老朋友协议缓冲区 Apache Thrift和Apache Avro可能非常适合这些目的。 例如, Apache Kafka通常与Schema Registry一起使用,以存储所有消息模式的版本历史记录。 该注册表基于Apache Avro构建。
我们没有讨论过的其他有趣的库(因为它们仅面向消息格式,而不是服务或协议),它们是FlatBuffers , Cap'n Proto和MessagePack 。
5.3演员模型
actor模型 (始于1973年)引入了actor的概念,将其作为并发计算的通用原语,并通过异步发送消息彼此进行通信。 任何参与者在响应收到的消息时,可以同时执行以下操作之一:
- 向其他参与者发送有限数量的消息
- 实例化有限数量的新演员
- 更改指定的行为以处理收到的下一条消息
使用消息传递的后果是参与者之间不会共享任何状态。 他们可以修改自己的私有状态 ,但只能通过消息相互影响。
您可能已经听说过Erlang ,这是一种编程语言,用于构建对高可用性有要求的大规模可扩展软实时系统。 它是成功的参与者模型实现的最佳示例之一。
在JVM上,毋庸置疑的领导者是Akka :一个用于为Java和Scala构建高度并发,分布式和弹性消息驱动的应用程序的工具包。 它最初是作为参与者模型的实现而来的,但多年来已发展成为成熟的面向分布式系统开发商的瑞士刀。
坦白地说, 参与者 模型背后的思想和原则使其成为实施微服务的重要候选者。
5.4 Aeron
对于高效且对延迟至关重要的通信,到目前为止我们讨论的框架可能不是最佳选择。 您当然可以回退到定制的TCP / UDP传输,但是那里有很多选项。
Aeron是一种高效可靠的UDP单播, UDP组播和IPC消息传输。 它开箱即用地支持Java,而性能是重点。 Aeron旨在在任何消息传递系统中实现最高的吞吐量和最低的可预测延迟。 Aeron与简单二进制编码(SBE)集成在一起,以实现消息编码和解码中的最佳性能。
5.5 RSocket
RSocket是一种二进制协议,可用于字节流传输,例如TCP , WebSockets和Aeron 。 它通过仅使用一个连接的异步消息传递来支持多个对称交互模型:
- 请求/响应(1个流)
- 请求/流(有限的流)
- 一劳永逸(无回应)
- 频道(双向流)
除其他外,它支持会话恢复,该会话恢复允许跨不同的传输连接恢复长寿命的流。 当网络连接频繁断开,切换和重新连接时,这特别有用。
6.云原生
云计算无疑是当今大多数应用程序部署的地方。 为争夺市场份额而进行的激烈争斗补充了持续不断的创新。 其中之一是无服务器计算 ,其中云提供商可以动态管理服务器管理和容量计划决策。 “ 无服务器 ”一词的存在有点令人困惑,因为仍然需要服务器,但是部署和执行模型会发生变化。
令人兴奋的部分是,无服务器代码可以与部署为更传统的微服务的应用程序一起使用。 甚至,可以将基于微服务架构设计的整个应用程序构建在纯无服务器组件之上,从而大大减少了操作负担。
如果无服务器计算对您来说是新的,请在Martin Fowler博客上的这篇文章中很好地介绍了这种架构。
6.1服务功能
行动中的无服务器计算的最佳例子之一就是功能即服务 ( FaaS )。 您可能会猜到,这种模型中的部署单位是一种功能(理想情况下,可以使用任何语言,但是Java,JavaScript和Go最有可能是您现在可以实际使用的功能)。 这些功能预计将在几毫秒内启动,以便处理各个请求或对传入消息做出React。 不使用时,这些功能不会消耗任何资源,完全不会产生任何费用。
每个云提供商都提供自己的功能作为服务平台,但是值得一提的是Apache OpenWhisk , OpenFaaS和riff项目,这是几个开源的,完善的功能作为服务实现。
6.2母语
几周前 Google公开宣布,这实际上是无服务器运动的新生成员。
Knative 组件扩展了 Kubernetes, 以提供一组中间件组件,这些组件对于构建可在任何地方运行的现代,以源为中心和基于容器的应用程序必不可少:内部,云端或什至在第三方数据中心。 … Knative 组件为开发人员提供 Kubernetes的本机 API,用于将无服务器风格的功能,应用程序和容器部署到自动扩展运行时。 – https://github.com/knative/docs
Knative处于开发的早期阶段,但是它对无服务器计算的潜在影响可能是革命性的。
7.结论
在本节的过程中,我们讨论了在遵循微服务体系结构的应用程序中构造微服务(及其客户端)之间的通信的许多不同样式。 我们已经了解了架构或合同的重要性和重要性,这是在服务提供商和消费者(组织内的团队)之间建立健康协作的必要手段。 最后但并非最不重要的一点是,多种沟通方式的结合当然是可能的并且是有道理的,但是,此类决策应该由实际需求驱动,而不是大肆宣传(不幸的是,这在行业中经常发生)。
8.接下来
在本教程的下一部分中,我们将评估Java格局和使用最广泛的框架,以在JVM上构建生产级微服务 。
完整的规范文件集可以下载 。
翻译自: https://www.javacodegeeks.com/2018/08/microservices-java-developers-microservices-communication.html
java 开发微服务