发布时间:2023-11-24 08:00
目录
服务间通信:API接口
概述
同步通信
使用REST API
使用gRPC API
断路器模式(Circuit breaker pattern)
服务发现模式
异步通信
使用异步消息模式
消息
单块应用
运行在同一个进程中,直接使用函数调用进行通信
需要与其他应用集成时,采用 REST Api 进行通信
微服务应用
被分解出来的服务之间需要协作
分布式环境下,协作会跨主机、跨进程
当前最流行的进程间通信方式是 REST
服务间的通信方式对于应用的可用性至关重要
可选项
同步通信:如基于HTTP的 REST 和 gRPC
异步通信:如基于消息的的AMQP 和 STOMP
通信内容类型:
基于文本:JSON、XML
基于字节:Avro,Protocol Buffers
微服务的API 可发为两类
操作:由客户端调用,包括名字,参数,返回类型
事件:包括类型和字段集,发布到消息通道(Message Channel)
跟单块应用不同,编译期无法发现微服务 API 的问题,只有在运行期才能发现
最佳实践
使用某种接口定义语言(IDL)对服务接口进行定义,如 Swagger
Swagger:Rest API的描述语言
API-First:先定义 API,再进行开发
微服务 API 的演进
当加入新功能时,或功能变更时,API 需随着时间而演进
不应该,也不可能强制所有调用者同步更新
应采用滚动升级(Rolling Update),确保新旧版本同时在线
当所有调用者都完成升级后,再撤回旧版本
Roy Fielding. Architectural Styles and the Design of Network-based Software Architectures. 2000
资源的定义、URI标识符(一对多)、表示(JSON),操作/Http动词(请求方式),状态码
REST成熟度模型
0级:没有资源概念。使用一个 URI 处理所有请求,请求内容封装在请求体中
http://localhost:8080/services
{type: 'teacher', operation: 'getAll', parameter: 'name="张%"'}
{type: 'teacher', operation: 'getAll', parameter: 'name="张%"'}
面向服务的架构:SOA,SOAP,BPEL
1级:引入资源概念,一个资源的所有操作都通过一个URI完成
http://localhost:8080/services/teacher
{operation: 'getAll', parameter: 'name = ...'}
2级:引入HTTP动词,资源的不同操作使用适当的HTTP动词和返回状态码
POST /services/teachers {name: 'zhangsan', } ------ 201
GET /services/teachers?name=张&age=37 ----- 200
PUT
3级:实现HATEOAS()原则 ,不仅返回当前的资源表示,还返回后续操作的链接,使客户端无须再执行硬编码URI。(注:这才是真正的RESTful,但实际中很少能做到)
Axios.get("http://localhost/assignment/1")
Axios.get(json.href.key)
json: {[,,,,], href: {key: http://localhost/api/assignment/1}}
Spring-HATEOAS
使用 Open API 规范 描述 API
缺点:
无法一次操作获取多个对象
返回太多无用的信息 http://localhost/student/1 {name: zhangsan, id: 123, password: 1123, calssId: 123} http://localhost/class/123
解决办法:GraphQL
二进制的基于消息的协议
使用 基于Protocol Buffer协议进行描述
使用Protocol Buffer生成客户端桩(Stub)和服务器端骨架(skeleton)
使用 HTTP/2
问题:当请求的服务总是超时,怎么办?
服务雪崩:一个服务拖跨一个应用
目标:零延时
当一个服务的请求连续出现超时,并且连续超时的次数超过某个阀值时,则再次请求该服务时会被断路器直接拒绝,而不会发出真正的请求。
以下内容来自网络
如果调用失败,将失败值的增加1
如果调用失败的次数超过某个阈值,就打开电路
如果电路处于打开状态,则立即返回错误信息或返回一个默认值
如果电路处于打开状态且已经过了一段时间,则半开电路
如果电路半开,下一次请求调次失败,请再次打开
如果电路半开,下一次请求调用成功,请将其关闭
断路器可部署在 API 网关上
网关返回缓存数据,或直接错误数据
开源实现:Hystrix for JVM 、Polly for .NET、Spring Cloud Hystrix, Dubbo, Nacos
基础设施实现:Kubernetes + Istio Service Mesh
发送消息前,如何知道目标服务的网络位置,如IP和端口?
我们现在的做法(最差):硬编码
传统做法:从配置文件中获取
微服务:因为要满足扩展性,目标服务可能会动态增减,因此目标IP地址是动态的
必须提供一个动态的服务发现机制
提供服务注册组件,管理所有服务实例的网络位置信息
当服务实例启动或停止时,会更新服务网络数据
注册中心会定期轮询服务实例其健康状态,因此服务实例也要提供类似API
客户端向服务注册中心询问服务的网络信息,得到回复后发出请求
两种实现:
服务及其客户端直接与服务注册中心交互
自我注册模式:服务实例启动或停止时,自动更新注册中心
客户端发现模式:客户端从注册中心获取所有可用服务实例及其负载均衡配置
spring cloud netflix
通过基础设施完成:Kubernetes
第三方注册模式:服务实例由第三方自动注册
服务器端发现模式:客户端向路由器发送请求,后者负责服务发现
虽然与基础设施耦合,但还是推荐(因为不用写代码)
一个基于消息的应用,通常被称为消息代理(message broker)
作为服务通信的中介
通信过程:
客户端发送一条消息到代理
服务从代理中读取消息
服务进行处理并将结果封装成一条消息
服务将消息发回给代理
客户端从代理读取返回的消息
客户端根据消息进行处理
以上过程是异步的
客户端发完消息后,并不会等着消息的返回
客户端不需要知道服务的网络信息
服务端不需要知道客户身份信息
消息模式:客户端通过异常消息调用服务
案例:使用 Spring Integration 开发一个 hello world 例子
消息模型
出处: Enterprise Integration Patterns
消息通过消息通道(message channel)进行交换
发送者往通道上发送消息
接收者从通道接收消息
消息
由消息头(header)和消息体(body) 构成
消息头包括:
一组键值对
消息的元数据
消息id
消息返回地址:返回消息的通道
消息体是消息要发送的数据,可以是文本或二进制类型
三种消息类型
文档:只包含纯数据
命令:指定操作和参数
事件:指出发送者发生的事件,如下订单
消息通道
消息通道类型:点对点,订阅-发布式
可以实现同步的消息通信,但这种场景选用 REST
异步请求/响应,或异步点对点
单向通知:只请求,没响应
订阅-发布:多个服务从通道中获取消息并处理