服务调用是指一个软件系统通过某种通信协议请求另一个软件系统提供特定服务的过程。 这一过程涉及服务提供者(Provider)和服务消费者(Consumer)之间的信息交换,通常通过网络或本地通信机制来实现。 服务调用是分布式系统和微服务架构中的核心概念,能够实现不同系统或组件之间的协作和功能整合。
模块化开发:
灵活性和扩展性:
高可用性和容错性:
开发效率:
异步处理:
复杂性增加:
性能开销:
故障传播:
安全风险:
监控和调试难度:
根据不同的应用场景和技术实现,广义的服务调用的方式主要有以下几种:
远程过程调用允许一个程序调用另一台计算机上的服务,就像调用本地函数一样。常用的RPC框架有:
gRPC(gRPC Remote Procedure Calls)是一个高性能、开源的RPC框架,由Google开发。 gRPC基于HTTP/2协议和Protocol Buffers(protobuf)数据序列化协议,提供了多种编程语言的支持。其主要特性包括高性能、语言无关、流式处理、双向通信等。
Protocol Buffers:gRPC使用Protocol Buffers(protobuf)作为其接口定义语言(IDL)和数据序列化格式。 开发者通过定义.proto文件来描述服务接口和消息结构,然后使用protobuf编译器生成客户端和服务器端的代码。
HTTP/2:gRPC基于HTTP/2协议,提供了高效的多路复用、头部压缩和服务器推送等特性,显著提升了通信性能和效率。
四种通信模式:
1.本地调用:调用端发起本地调用,本地调用的请求经过客户端 Stub进行封装。
2.封装参数:gRPC 会给对应的服务接口名生成一个代理类,即客户端 Stub。客户端Stub的作用是屏蔽掉 RPC 调用的具体底层细节,使得用户无感知的调用远程服务。 客户端Stub 会将当前调用的方法的方法名、参数类型、实参数等根据protobuf协议组装成网络传输的消息体。
3.发送:客户端Stub 封装后的消息体会序列化二进制之后,通过Socket发送给RPC服务端。然后socket进入等待
状态。
4.接收:被调用端通过socket接收到数据之后,将数据传递给服务端stub。
5.解封参数:服务端stub收到数据之后,根据protobuf协议解封方法名、参数等信息。
6.调用服务:根据解析出来的方法名、参数等信息调用本地的方法,执行具体的业务逻辑。
7.结果返回:执行完业务逻辑之后,将结果返回。
8.封装结果:按照protobuf协议将结果数据进行封装。
9.结果发送:将封装后的结果数据通过socket返回给客户端。
10.结果接受:客户端socket从 等待
状态被唤醒,开始接收服务端的结果数据。
11.结果解封:收到数据之后,按照protobuf协议将结果数据解封,得到客户端可以识别的结果。
12.本地返回:客户端拿到可以识别的结果之后,进行后续的逻辑处理。至此完成一个完成的gRPC调用流程。
以下是一个简单的gRPC代码示例,展示如何定义服务并实现客户端和服务器。
1. 定义.proto文件
首先,创建一个名为calculator.proto
的文件,定义服务和消息结构:
syntax = "proto3";
package calculator;
// 定义请求和响应消息
message AddRequest {
int32 a = 1;
int32 b = 2;
}
message AddResponse {
int32 result = 1;
}
// 定义计算器服务
service Calculator {
rpc Add (AddRequest) returns (AddResponse);
}
2. 生成代码
使用Protocol Buffers编译器生成Java代码:
protoc --java_out=. --grpc-java_out=. calculator.proto
3. 实现服务器端代码
创建一个简单的gRPC服务器,处理加法请求。假设我们使用Java:
CalculatorService.java
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
public class CalculatorService extends CalculatorGrpc.CalculatorImplBase {
@Override
public void add(AddRequest request, StreamObserver responseObserver) {
int a = request.getA();
int b = request.getB();
int result = a + b;
AddResponse response = AddResponse.newBuilder()
.setResult(result)
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
public static void main(String[] args) throws IOException, InterruptedException {
Server server = ServerBuilder.forPort(50051)
.addService(new CalculatorService())
.build();
server.start();
System.out.println("Server started, listening on " + server.getPort());
server.awaitTermination();
}
}
4. 实现客户端代码
创建一个简单的gRPC客户端,调用加法服务:
CalculatorClient.java
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
public class CalculatorClient {
public static void main(String[] args) {
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051)
.usePlaintext()
.build();
CalculatorGrpc.CalculatorBlockingStub stub = CalculatorGrpc.newBlockingStub(channel);
AddRequest request = AddRequest.newBuilder()
.setA(10)
.setB(20)
.build();
AddResponse response = stub.add(request);
System.out.println("Result: " + response.getResult());
channel.shutdown();
}
}
Dubbo 是一个高性能的 RPC(Remote Procedure Call,远程过程调用)框架,广泛应用于微服务架构中,用于实现服务之间的调用和通信。
Dubbo 从设计上不绑定任何一款特定通信协议,HTTP/2、REST、gRPC、JsonRPC、Thrift、Hessian2 等几乎所有主流的通信协议,Dubbo 框架都可以提供支持。
这样的 Protocol 设计模式给构建微服务带来了最大的灵活性,开发者可以根据需要如性能、通用型等选择不同的通信协议,不再需要任何的代理来实现协议转换,甚至你还可以通过 Dubbo 实现不同协议间的迁移。
Dubbo Protocol 被设计支持扩展,您可以将内部私有协议适配到 Dubbo 框架上,进而将私有协议接入 Dubbo 体系,以享用 Dubbo 的开发体验与服务治理能力。比如 Dubbo3 的典型用户阿里巴巴,就是通过扩展支持 HSF 协议实现了内部 HSF 框架到 Dubbo3 框架的整体迁移。
Dubbo 还支持多协议暴露,您可以在单个端口上暴露多个协议,Dubbo Server 能够自动识别并确保请求被正确处理,也可以将同一个 RPC 服务发布在不同的端口(协议),为不同技术栈的调用方服务。
Dubbo 提供了两款内置高性能 Dubbo2、Triple (兼容 gRPC) 协议实现,以满足部分微服务用户对高性能通信的诉求,两者最开始都设计和诞生于阿里巴巴内部的高性能通信业务场景。 Dubbo2 协议是在 TCP 传输层协议之上设计的二进制通信协议 Triple 则是基于 HTTP/2 之上构建的支持流式模式的通信协议,并且 Triple 完全兼容 gRPC 但实现上做了更多的符合 Dubbo 框架特点的优化。
Dubbo 对通信协议的支持具有以下特点:
JSON-RPC 是一种轻量级的远程过程调用(RPC)协议,使用 JSON 作为数据传输格式。它通过网络在客户端和服务器之间传递方法调用和返回结果。 JSON-RPC 简化了跨网络的远程方法调用。以
客户端调用:
请求组装:
发送请求:
服务器处理:
响应组装:
发送响应:
客户端处理响应:
简单的 JSON-RPC 请求和响应的示例:
请求示例
{
"jsonrpc": "2.0",
"method": "subtract",
"params": [42, 23],
"id": 1
}
响应示例
{
"jsonrpc": "2.0",
"result": 19,
"id": 1
}
请求对象:
jsonrpc
: JSON-RPC 版本号,必须是 "2.0"。method
: 方法名,表示客户端希望调用的远程方法。params
: 参数列表,可以是位置参数数组或命名参数对象。id
: 请求 ID,用于匹配请求和响应。响应对象:
jsonrpc
: JSON-RPC 版本号,必须是 "2.0"。result
: 方法调用的返回结果,如果方法调用成功。error
: 错误对象,如果方法调用失败。id
: 请求 ID,用于匹配请求和响应。错误处理
JSON-RPC 定义了标准的错误对象,包含以下字段:
code
: 错误码,一个数字表示错误类型。message
: 错误消息,一个简短的描述。data
: 附加的错误数据,可选字段,提供额外的错误信息。错误示例
{
"jsonrpc": "2.0",
"error": {
"code": -32601,
"message": "Method not found"
},
"id": 1
}
优点:
缺点:
JSON-RPC 是一种高效的远程过程调用协议,适用于轻量级和中小规模的分布式系统。它的简洁性和高效性使其在现代 web 服务和微服务架构中得到了广泛应用。
Apache Thrift 是一个跨语言的 RPC(Remote Procedure Call,远程过程调用)框架,由 Facebook 开发,并捐赠给 Apache 基金会。 Thrift 提供了高效的二进制通信协议和序列化机制,支持多种编程语言,适用于大规模分布式系统。
IDL(接口描述语言):
service Calculator {
i32 add(1: i32 num1, 2: i32 num2),
i32 subtract(1: i32 num1, 2: i32 num2)
}
代码生成:
thrift --gen java calculator.thrift
客户端调用:
例如,在 Java 中:
TTransport transport = new TSocket("localhost", 9090);
transport.open();
TProtocol protocol = new TBinaryProtocol(transport);
Calculator.Client client = new Calculator.Client(protocol);
int result = client.add(5, 3);
System.out.println("Result: " + result);
transport.close();
服务器处理:
例如,在 Java 中:
public class CalculatorHandler implements Calculator.Iface {
@Override
public int add(int num1, int num2) {
return num1 + num2;
}
@Override
public int subtract(int num1, int num2) {
return num1 - num2;
}
}
public static void main(String[] args) {
try {
CalculatorHandler handler = new CalculatorHandler();
Calculator.Processor processor = new Calculator.Processor(handler);
TServerTransport serverTransport = new TServerSocket(9090);
TServer server = new TSimpleServer(new TServer.Args(serverTransport).processor(processor));
System.out.println("Starting the server...");
server.serve();
} catch (Exception e) {
e.printStackTrace();
}
}
Thrift 文件(.thrift):
Thrift 编译器:
传输层(Transport):
协议层(Protocol):
服务器(Server):
优点:
缺点:
Thrift 适用于大规模、高性能的分布式系统,通过其跨语言支持和高效的通信机制,简化了服务之间的调用和通信。
Web 服务是一种允许不同应用程序通过网络相互通信和交换数据的技术。Web 服务通常基于标准的协议和格式,如 HTTP、SOAP、REST 和 JSON。以下是 Web 服务的基本原理和服务调用的工作流程。
Web 服务的核心思想是通过标准的协议和数据格式,使得不同的系统能够互操作。Web 服务通常分为两类:SOAP 和 RESTful。
SOAP(Simple Object Access Protocol):
RESTful(Representational State Transfer):
以下描述了 RESTful Web 服务调用的典型流程:
定义 API 接口:
GET /users
获取所有用户。GET /users/{id}
根据 ID 获取特定用户。POST /users
创建新用户。PUT /users/{id}
更新特定用户。DELETE /users/{id}
删除特定用户。客户端请求:
发送请求:
服务器处理:
构建响应:
发送响应:
客户端处理响应:
以下是一个使用 JavaScript 和 Fetch API 调用 RESTful Web 服务的示例:
// 定义请求 URL
const url = 'https://api.example.com/users';
// GET 请求获取所有用户
fetch(url)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
// POST 请求创建新用户
const newUser = {
name: 'John Doe',
email: '[email protected]'
};
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(newUser)
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
优点:
缺点:
Web 服务通过标准化的协议和数据格式,实现了不同系统之间的互操作和数据交换,广泛应用于现代分布式系统和微服务架构中。
消息队列是一种用于在分布式系统中实现异步通信和解耦的机制。消息队列允许不同的应用程序通过消息的形式进行通信,而无需直接调用彼此的服务。以下是消息队列服务调用的基本原理和工作流程。
消息队列系统通常包括以下组件:
消息生产者(Producer):
消息消费者(Consumer):
消息代理(Broker):
消息生产者将消息发送到消息代理,消息代理将消息存储并分发给订阅了该消息的消费者。消费者处理接收到的消息,实现异步通信。
以下描述了典型的消息队列服务调用流程:
消息生产者发送消息:
消息代理存储消息:
消息消费者订阅队列:
消息分发:
消费者处理消息:
确认处理(可选):
以下是一个使用 RabbitMQ 进行消息队列服务调用的示例:
import pika
# 连接到 RabbitMQ 服务器
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
# 声明一个队列
channel.queue_declare(queue='hello')
# 发送消息到队列
channel.basic_publish(exchange='', routing_key='hello', body='Hello, World!')
print(" [x] Sent 'Hello, World!'")
# 关闭连接
connection.close()
import pika
# 连接到 RabbitMQ 服务器
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
# 声明一个队列
channel.queue_declare(queue='hello')
# 定义回调函数处理消息
def callback(ch, method, properties, body):
print(f" [x] Received {body}")
# 订阅队列
channel.basic_consume(queue='hello', on_message_callback=callback, auto_ack=True)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
优点:
缺点:
消息队列广泛应用于各种分布式系统和微服务架构中,包括但不限于:
通过消息队列,系统可以实现高效的异步通信和解耦,增强系统的弹性和扩展性。
GraphQL 是一种用于 API 的查询语言,以及一个用于通过单个端点获取数据的运行时环境。与传统的 REST API 相比,GraphQL 提供了更灵活和高效的数据查询方式。以下是 GraphQL 服务调用的基本原理和工作流程。
Schema(模式):
查询(Query):
变更(Mutation):
订阅(Subscription):
定义 Schema:
编写查询或变更:
发送请求:
解析请求:
返回结果:
以下是使用 JavaScript 和 Apollo Client 进行 GraphQL 服务调用的示例。
安装依赖:
npm install apollo-server graphql
定义 Schema 和 Resolver:
const { ApolloServer, gql } = require('apollo-server');
// 定义 Schema
const typeDefs = gql`
type Query {
hello: String
user(id: ID!): User
}
type Mutation {
createUser(name: String!): User
}
type User {
id: ID!
name: String!
}
`;
// 模拟数据
let users = [];
let idCounter = 1;
// 定义 Resolvers
const resolvers = {
Query: {
hello: () => 'Hello, world!',
user: (parent, args) => users.find(user => user.id === args.id),
},
Mutation: {
createUser: (parent, args) => {
const user = { id: idCounter++, name: args.name };
users.push(user);
return user;
},
},
};
// 创建 Apollo Server 实例
const server = new ApolloServer({ typeDefs, resolvers });
// 启动服务器
server.listen().then(({ url }) => {
console.log(`Server ready at ${url}`);
});
安装依赖:
npm install @apollo/client graphql
编写客户端代码:
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';
import { HttpLink } from 'apollo-link-http';
import fetch from 'cross-fetch';
// 创建 Apollo Client 实例
const client = new ApolloClient({
link: new HttpLink({ uri: 'http://localhost:4000', fetch }),
cache: new InMemoryCache()
});
// 查询数据
const GET_HELLO = gql`
query {
hello
}
`;
client.query({ query: GET_HELLO })
.then(result => console.log(result))
.catch(error => console.error(error));
// 变更数据
const CREATE_USER = gql`
mutation createUser($name: String!) {
createUser(name: $name) {
id
name
}
}
`;
client.mutate({ mutation: CREATE_USER, variables: { name: 'John Doe' } })
.then(result => console.log(result))
.catch(error => console.error(error));
优点:
缺点:
GraphQL 适用于以下场景:
通过 GraphQL,系统能够实现更灵活和高效的数据查询和变更,特别适用于需要高度交互和复杂数据需求的应用。
https://archmanual.com
https://github.com/yingqiangh/ArchManual