1. 概述
本教程将探讨在gRPC中实现重试策略的多种方法。gRPC是Google开发的远程过程调用框架,支持多种编程语言互操作,但我们将重点讨论Java实现。
2. 重试的重要性
现代应用日益依赖分布式架构,这种架构通过水平扩展处理高负载,同时提升高可用性。但也引入了更多潜在故障点,因此在开发多微服务应用时,容错能力至关重要。
RPC调用可能因以下因素临时失败:
- 网络延迟或连接中断
- 服务器内部错误导致无响应
- 系统资源繁忙
- 下游服务过载或不可用
- 其他相关问题
重试是一种容错机制。重试策略可根据特定条件自动重试失败请求,并定义重试频率和持续时间。这种简单模式能有效处理临时故障,提升系统可靠性。
3. RPC故障阶段
首先了解远程过程调用可能失败的阶段:
客户端应用发起请求,gRPC客户端库将其发送至服务器。服务器收到后,gRPC服务器库将请求转发给服务器应用逻辑。
RPC可能在以下阶段失败:
- 离开客户端前
- 服务器端但未到达应用逻辑前
- 服务器应用逻辑中
4. gRPC的重试支持
由于重试是重要的恢复机制,gRPC在特殊情况下会自动重试失败请求,并允许开发者定义重试策略以获得更精细的控制。
4.1. 透明重试
必须明确:gRPC仅在请求未到达应用服务器逻辑时才能安全重试。超出此范围,gRPC无法保证事务的幂等性。让我们看看透明重试的完整流程:
如前所述,内部重试仅在离开客户端前或服务器端但未到达应用逻辑前才安全执行。这种重试策略称为透明重试。一旦服务器应用成功处理请求,将返回响应且不再进行重试。
当RPC到达gRPC服务器库时,gRPC最多执行一次重试(多次重试会增加网络负载)。但当RPC未能离开客户端时,可能无限次重试。
4.2. 重试策略
为给开发者更多控制权,gRPC支持在单个服务或方法级别配置重试策略。当请求越过第2阶段后,将受可配置重试策略管辖。服务所有者可通过服务配置(JSON文件)配置RPC的重试策略。
服务所有者通常通过DNS等名称解析服务将服务配置分发给gRPC客户端。但当名称解析不提供服务配置时,服务消费者或开发者可编程式配置。
gRPC支持多个重试参数:
配置项 | 说明 |
---|---|
maxAttempts | 最大RPC尝试次数(含原始请求),默认最大值为5 |
initialBackoff | 重试间的初始退避延迟 |
maxBackoff | 指数退避增长的上限,必填且必须大于零 |
backoffMultiplier | 每次重试后退避时间将乘以此值,当乘数大于1时呈指数增长,必填且必须大于零 |
retryableStatusCodes | 当gRPC调用失败状态匹配时将自动重试。服务所有者设计可重试方法时应谨慎,这些方法需是幂等的,或仅允许在未对服务器做任何修改的RPC错误状态码上重试 |
值得注意的是,gRPC客户端使用initialBackoff、maxBackoff和backoffMultiplier参数来随机化重试前的延迟。
有时服务器可能在响应元数据中发送指令,要求不重试或延迟后重试,这称为服务器回压。
现在我们已讨论gRPC的透明重试和基于策略的重试特性,总结gRPC整体重试管理机制:
5. 编程式应用重试策略
假设我们有一个广播服务,通过调用底层通知服务(向手机发送短信)向市民广播消息。政府使用此服务发布紧急公告。使用该服务的客户端应用必须具备重试策略,以缓解临时故障导致的问题。
让我们深入探讨。
5.1. 高层设计
首先查看broadcast.proto文件中的接口定义:
syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.baeldung.grpc.retry";
package retryexample;
message NotificationRequest {
string message = 1;
string type = 2;
int32 messageID = 3;
}
message NotificationResponse {
string response = 1;
}
service NotificationService {
rpc notify(NotificationRequest) returns (NotificationResponse){}
}
*broadcast.proto文件定义了包含远程方法*notify()的NotificationService,以及两个DTO:NotificationRequest和NotificationResponse。**
整体来看,gRPC应用的客户端和服务器端使用的类如下:
后续可使用broadcast.proto文件生成实现NotificationService所需的Java源代码。Maven插件将生成NotificationRequest、NotificationResponse和NotificationServiceGrpc类。
服务器端的GrpcBroadcastingServer类使用ServerBuilder注册NotificationServiceImpl来广播消息。客户端的GrpcBroadcastingClient类使用gRPC库的ManagedChannel类管理通道以执行RPC。
服务配置文件retry-service-config.json定义了重试策略:
{
"methodConfig": [
{
"name": [
{
"service": "retryexample.NotificationService",
"method": "notify"
}
],
"retryPolicy": {
"maxAttempts": 5,
"initialBackoff": "0.5s",
"maxBackoff": "30s",
"backoffMultiplier": 2,
"retryableStatusCodes": [
"UNAVAILABLE"
]
}
}
]
}
前文我们已了解maxAttempts、指数退避参数和retryableStatusCodes等重试策略。当客户端调用broadcast.proto文件中定义的NotificationService的远程方法*notify()*时,gRPC框架将强制执行这些重试设置。
5.2. 实现重试策略
查看GrpcBroadcastingClient类:
public class GrpcBroadcastingClient {
protected static Map<String, ?> getServiceConfig() {
return new Gson().fromJson(new JsonReader(new InputStreamReader(GrpcBroadcastingClient.class.getClassLoader()
.getResourceAsStream("retry-service-config.json"), StandardCharsets.UTF_8)), Map.class);
}
public static NotificationResponse broadcastMessage() {
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080)
.usePlaintext()
.disableServiceConfigLookUp()
.defaultServiceConfig(getServiceConfig())
.enableRetry()
.build();
return sendNotification(channel);
}
public static NotificationResponse sendNotification(ManagedChannel channel) {
NotificationServiceGrpc.NotificationServiceBlockingStub notificationServiceStub = NotificationServiceGrpc
.newBlockingStub(channel);
NotificationResponse response = notificationServiceStub.notify(NotificationRequest.newBuilder()
.setType("Warning")
.setMessage("Heavy rains expected")
.setMessageID(generateMessageID())
.build());
channel.shutdown();
return response;
}
}
broadcast()方法构建包含必要配置的ManagedChannel对象,然后将其传递给*sendNotification(),后者进一步调用存根上的notify()*方法。
ManagedChannelBuilder类中设置包含重试策略的服务配置的关键方法是:
- **disableServiceConfigLookup():明确禁用通过名称解析的服务配置查找
- **enableRetry():启用每个方法的重试配置
- **defaultServiceConfig():显式设置服务配置
getServiceConfig()方法从retry-service-config.json文件读取服务配置,并返回其内容的Map表示。随后此Map被传递给ManagedChannelBuilder类的*defaultServiceConfig()*方法。
最后,创建ManagedChannel对象后,我们调用NotificationServiceGrpc.NotificationServiceBlockingStub类型的notificationServiceStub对象的*notify()*方法广播消息。该策略同样适用于非阻塞存根。
建议使用专用类创建ManagedChannel对象,便于集中管理,包括重试策略配置。
为演示重试功能,服务器端的NotificationServiceImpl类被设计为随机不可用。查看GrpcBroadcastingClient的实际运行效果:
@Test
void whenMessageBroadCasting_thenSuccessOrThrowsStatusRuntimeException() {
try {
NotificationResponse notificationResponse = GrpcBroadcastingClient.sendNotification(managedChannel);
assertEquals("Message received: Warning - Heavy rains expected", notificationResponse.getResponse());
} catch (Exception ex) {
assertTrue(ex instanceof StatusRuntimeException);
}
}
*该方法调用GrpcBroadcastingClient类的sendNotification()来调用服务器端远程过程广播消息。可通过日志验证重试行为:*
6. 总结
本文探讨了gRPC库中的重试策略特性。通过JSON文件声明式设置策略是强大功能,但建议仅用于测试场景或名称解析无法提供服务配置的情况。
重试失败请求可能导致不可预测的结果,因此应谨慎设置,仅用于幂等事务。
本文代码可在GitHub获取。