前言
微服务通信的难点
在微服务架构中,不同的服务是托管在不同的代码库,运行在不同的进程甚至是机器上的,这会比单体架构中服务之间进行通信付出更大的成本。
目前有很多进程间通信的技术可供开发者选择,服务可以基于同步请求/响应的通信机制,比如HTTP RESTFUL
或者 gRPC
。另外也可以使用具有异步的消息队列,比如AMQP
。消息的格式也不尽相同,可以选择具有可读性的JSON
,也可以使用更加高效的、基于二进制的Protocol Buffers
在通信中最重要的东西其实是协议Protocol,只要双方进行通信,那就都是基于某种Protocol实现的。比如说使用MQ进行通信,以Pulsar
进行举例,那么一条消息的ID通常是以下格式
分区ID:分块ID:偏移量
如果使用HTTP进行通信,那么其实URL Header Body如何编码及解析,也是一种协议,比如HTTP HEADER必带的某些属性,比如版本,以及空行等等。
一个小故事
在一个小镇上,住着两位好朋友小红和小黄。他们原本都住在同一个大房子里,但为了冒险,它们决定各自搬到了两个可爱的小屋。
小红是个数学小天才,总是在小镇上帮助其他小伙伴解决数学难题。而小黄则是一位传递小专家,负责在小镇上传递各种有趣的消息。
有一天,小红和小黄决定要一起完成一个神奇的任务。它们需要通过特别的邮差服务来传递重要的信息,这可是一项相当有趣的冒险!
小红拿出了一张漂亮的纸条,上面写着一些奇妙的数学问题。她想要通过邮差服务向小黄请教一下。于是,小红将这张纸条装进了一个充满好奇心的信封,并用彩色的绳子系上了一个大大的蝴蝶结。
小黄接到这个特别的信封后,发现上面的问题真的很有趣。但是,小黄想要回答,于是它又用另一种特别的方式,通过一张快乐的小卡片回复了小红。
这时候,小红和小黄开始发现通信并不容易,因为它们住在不同的小屋里。小红告诉小黄,她得学会一些特殊的规矩,比如信封上要写上版本号,还得在信件里放一些额外的信息。
而小黄则告诉小红,有时候它会收到很多信息,所以需要小红耐心等待,不然它可能来不及回复。
实现微服务通信的几种方案
在一个请求中获取多个资源的挑战--GraphQL
REST资源通常以业务为导向,因此在设计REST API时的一个常见问题就是如何使得客户端可以在单个请求中检索多个相关的对象 。假设客户端想要检索Order
和这个Order
的Consumer
,纯REST API要求客户端至少发出两个请求,一个用于Order,另一个用于Consumer。更复杂的情况需要往返并且遭受更多的延迟
一个解决方案是API允许客户端在获取资源时检索相关资源,例如客户可以受用GET /orders/order-id-1345?expand=consumer 检索Order及其consumer 。这种方法在许多场景中都很有效,但对于更加复杂的场景来说,它通常是不够的,实现它也可能很耗时。
这就导致了GraphQL的出现,它们旨在支持高效的数据获取,以下是一个典型的GraphQL请求数据 来进行参数的请求
arduino
GraphQL请求格式
{
hero {
name
height
mass
}
}
hero是我们要获取的对象
在对象内,我们指定了要获取的字段,name height 和 mass
最后的结果类似
{
"hero": {
"name": "Luke Skywalker",
"height": 1.72,
"mass": 77
}
}
同步请求
grpc:使用REST的一个挑战是由于HTTP仅提供优先数量的动词,因此设计支持多个更新操作的REST API并不容易,避免这个问题的进程间通信方式就是GRPC,这是一个用于编写跨语言客户端和服务端的框架
grpc是一种基于二进制消息的协议,可以通过Protocol Buffer的IDL来定义grpc 的api 可以使用protocol buffer编译器来生成客户端的桩 和服务端的骨架 ,分别称为stub 和 skeleton,编译器可以为各种语言生成代码,包括java c ``nodejs和golang等
客户端和服务器底层使用的是http 2协议,grpc api由一个或者多个请求/响应消息定义组成,服务定义类似于接口,是强类型方法的集合,除了支持简单的请求/响应RPC之外,grpc还支持流式rpc,分为客户端流式、服务端流式、双向流式等等,并以protocol buffer格式交换二进制的消息数据,protocol buffer是一种高效且紧凑的二进制标记格式。protocol buffers消息的每个字段都有编号,并且有一个类型代码,消息接收方可以提取所需的字段,并跳过其无法识别的字段,因此grpc使api能够在保持向后兼容的同时进行变更
grpc的好处
- 设计具有复杂更新操作的API很简单
- 具有高效、紧凑的进程间通信消息,效率很高,尤其是在交换大量消息的时候
- 支持在远程过程调用和消息传递过程中使用双向流式消息方式
- 实现了客户端和用各种语言编写的服务端之间的互操作性
弊端
- 基于REST JSON的API机制相比,请求的客户端需要做更多的工作,比如定义对应的protocol buffer文件
- 老的防火墙可能不支持http 2
使用服务发现
只要我们进行某些远程调用,不管是rpc还是restful api,为了发出请求,我们都需要知道服务实例的网络位置,我们把这个过程叫做服务发现。
通常RPC调用的服务发现通过服务注册与发现中心来实现,而restful api的服务发现是通过DNS + IP和端口来实现的
在传统的应用程序中,服务实例的网络位置通常是静态的。例如,代码可以从偶尔更新的配置文件中读取网络位置。但在现代的基于云微服务的应用程序中,通常不那么简单,IP地址和服务实例并不是强关联的了,某些时候服务实例会动态的销毁、创建,空出IP地址和占用IP地址
因此服务实例需要具有动态分配的网络地址,并且服务实例会因为自动扩展、故障和升级的原因进行动态的更改
服务发现在概念上非常简单:关键是一个服务注册表,这是包含服务实例网络位置信息的一个数据库,当服务实例启动和停止时,服务发现机制会更新服务注册表。当客户端调用服务时,服务发现机制会查询服务注册表以获取可用服务实例的列表,并将请求路由到其中一个服务实例
实现服务发现有以下两种主要方式
- 服务及其客户直接与服务注册表交互,比如 CONSUL 和 ETCD 等等 也就是自注册+客户端服务发现 服务实例掉哟个服务注册表的注册API来注册起网络位置,并且会定期进行健康检查,通过心跳机制防止过期;当客户端想要调用服务的时候,会查询服务注册表一获取服务实例的列表。为了提高性能,客户端可以选择缓存服务实例,然后通过负载均衡算法来选择服务实例之后发出请求。并且可以处理多平台部署的问题。比如,有一些服务在K8S上部署,其余服务在遗留环境中运行。在这种情况下,使用 consul 就能同时适用于两种环境,而基于k8s的服务发现仅能用于部署在K8S平台上的部分服务
- 基础设施来处理服务发现 Docker\K8s,部署平台为每个服务提供DNS 虚拟IP 和解析的DNS名称等等 服务注册、发现、路由完全都通过部署平台进行处理
消息队列
消息队列和上述提到的不同的最大的点就是 不管是HTTP RPC 还是服务注册与发现中心
它们的调用都是同步的 即发出请求 立马就能得到结果
而消息队列可以是异步的 生产者生产完成消息之后 消费者并不一定需要立马去进行消息的消费和后续处理
异步 是消息队列最重要的功能之一
具体的消息队列学习内容可以看笔者之前的关于消息队列系统学习的一篇博客
简单来说 消息队列就是
通过生产者、消息中间件、消费者这几个组件实现点对点和发布订阅、PULL/PUSH几种模式
消息队列又根据有没有消息中间件这个组件分为无代理消息和有代理消息,区别就是有没有消息处理的中间件存在
无代理消息:
- 允许更轻的网络流量和更低的延迟 少了两次转发
- 避免了消息中间件可能成为性能瓶颈的可能性
- 不需要维护消息 降低操作复杂度
弊端:
- 服务之间需要知道彼此的位置 必须采用服务发现机制
- 导致可用性降低 因为在交换消息时 消息的发送方和接收方都必须在线
总结
本文主要向大家介绍了实现微服务通信的难点以及多种解决方式,同步通信方式有GraphQL
rpc
服务发现
等等方式,异步通信主要是使用MQ
。
创作不易,如果有收获欢迎点赞、评论、收藏,您的支持就是我最大的动力。