前言
本文目的是为了介绍 gRPC
在使用过程中 远程调用函数的返回值内容(不同于 rpc 接口函数的 return
后的返回参数)
是grpc
原生支持的一种 远程调用的执行状态,因为目前没有找到系统说明的地方,这里专门记录一下
- 源码路径:
/usr/local/include/grpcpp/impl/codegen/status_code_enum.h
使用方式
#include <grpcpp/grpcpp.h> using grpc::Status; // 服务类定义 class A final : public B::Service { .... Status UploadServationData(ServerContext *context, const UploadObSvrRequest *request, UploadResponse *response) override; ... } Status A::UploadServationData(ServerContext *context, const Request *request, Response *response) { // TODO: 实现您的逻辑 return Status::OK; }
只要在服务端如上实现,那么在客户端调用时就可以获取到特定的状态返回码,用以判定执行结果,方便调试和出错处理。
客户端使用方式
#include <grpcpp/grpcpp.h> using grpc::Status; class DevDataClient { public: DevDataClient(std::shared_ptr<Channel> channel) : stub_(B::NewStub(channel)) {} ... } .... // 实际的 RPC。 Status status = DevDataClient.stub_->UploadServationData(&context, request, &reply);// 这里为了演示,直接使用了父类对象,实际使用过程中应当出于软件安全考虑避免这种情况 // 对它的状态进行操作。 if (status.ok()) { return std::to_string(reply.sensordata_size()); } else { std::cout << __FILE__ << __LINE__ << std::endl; std::cout << status.error_code() << ": " << status.error_message() << std::endl; return "RPC 失败"; } ....
以下是全部的 gPRC
的可用合法状态码介绍,包括了 常见使用场景 和 错误处理建议 帮助使用者更方便上手。
gRPC 状态码枚举文档
gRPC 框架定义了一系列的状态码,用于表示远程过程调用(RPC)的结果。以下是grpc
命名空间中StatusCode
枚举的详细说明:
OK (0)
操作成功,没有错误。
- 常见使用场景:远程过程调用成功完成。
- 错误处理建议:无需采取任何错误处理措施,正常流程。
CANCELLED (1)
操作被取消,通常是调用方主动请求的。
- 常见使用场景:调用方在操作完成前取消了请求。
- 错误处理建议:检查调用逻辑,确保在取消请求时释放资源,避免内存泄漏。
UNKNOWN (2)
未知错误。如果从另一个地址空间接收到的状态值属于在此地址空间未知的错误空间,或者API没有返回足够的错误信息,可能会返回此错误。
- 常见使用场景:系统内部错误,或者接收到来自未知错误空间的状态。
- 错误处理建议:记录错误详情,尝试重新执行操作,如果问题持续,可能需要联系系统管理员。
INVALID_ARGUMENT (3)
客户端指定了无效的参数。与FAILED_PRECONDITION
不同,INVALID_ARGUMENT
表示无论系统状态如何,参数本身都是有问题的(例如,文件名格式错误)。
- 常见使用场景:调用方提供了不合规范的参数。
- 错误处理建议:验证输入参数的有效性,提供清晰的错误信息给调用方。
DEADLINE_EXCEEDED (4)
操作在完成前已超时。对于改变系统状态的操作,即使操作本身已经成功完成,也可能返回此错误。例如,即使服务器的响应是成功的,如果响应延迟导致超时,也可能返回此错误。
- 常见使用场景:操作因超时而未能完成。
- 错误处理建议:根据操作性质决定是否重试,考虑增加超时时间或优化性能。
NOT_FOUND (5)
请求的实体(如文件或目录)未找到。
- 常见使用场景:请求的资源(如文件、目录或数据库记录)不存在。
- 错误处理建议:确认资源路径或标识符,提供错误信息并可能要求用户重新输入。
ALREADY_EXISTS (6)
尝试创建的实体(如文件或目录)已存在。
- 常见使用场景:尝试创建已存在的资源。
- 错误处理建议:检查资源是否已存在,提供更新或删除现有资源的选项。
PERMISSION_DENIED (7)
调用者没有执行指定操作的权限。PERMISSION_DENIED
不应用于因资源耗尽导致的拒绝(应使用RESOURCE_EXHAUSTED
),也不应用于无法识别调用者的情况(应使用UNAUTHENTICATED
)。
- 常见使用场景:调用方没有足够的权限执行操作。
- 错误处理建议:要求调用方提供适当的权限或认证信息。
RESOURCE_EXHAUSTED (8)
资源耗尽,可能是每个用户的配额,或者整个文件系统没有空间。
- 常见使用场景:系统资源耗尽,如达到API调用配额限制。
- 错误处理建议:实施资源配额管理,提示用户减少请求频率或升级服务。
FAILED_PRECONDITION (9)
操作因系统不在操作执行所需的状态而被拒绝。例如,要删除的目录可能不为空,或者对非目录应用了删除操作等。
- 常见使用场景:操作依赖于特定的系统状态,而当前状态不满足要求。
- 错误处理建议:确保所有前提条件都满足后再重试操作。
ABORTED (10)
由于并发问题(如序列号检查失败、事务中止等)通常会导致操作中止。
- 常见使用场景:由于并发问题,如事务冲突,操作被中止。
- 错误处理建议:检查并发控制机制,可能需要重试或调整事务逻辑。
OUT_OF_RANGE (11)
操作尝试超出有效范围。例如,尝试读取文件末尾之后的内容。
- 常见使用场景:操作尝试超出了有效范围,如索引超出数组界限。
- 错误处理建议:检查索引或范围参数,确保它们在有效范围内。
UNIMPLEMENTED (12)
操作未实现或在此服务中不支持/未启用。
- 常见使用场景:请求的方法或功能在服务中未被实现。
- 错误处理建议:提示调用方该功能不可用,或提供替代方案。
INTERNAL (13)
内部错误。意味着底层系统预期的一些不变量已被破坏。如果看到这些错误,说明有严重的问题。
- 常见使用场景:内部错误,如系统崩溃或严重的程序错误。
- 错误处理建议:记录错误详情,尝试重启服务或联系技术支持。
UNAVAILABLE (14)
服务当前不可用。这很可能是暂时性的条件,通过带有退避的重试可能会得到纠正。注意,对于非幂等操作,重试并不总是安全的。
- 常见使用场景:服务不可用,可能是由于过载或维护。
- 错误处理建议:实施重试机制,增加退避策略,或提示用户稍后再试。
DATA_LOSS (15)
不可恢复的数据丢失或损坏。
- 常见使用场景:数据丢失或损坏,无法恢复。
- 错误处理建议:尝试从备份中恢复数据,如果没有备份,可能需要手动重建丢失的数据。
UNAUTHENTICATED (16)
请求没有有效的认证凭据进行操作。
- 常见使用场景:调用方未提供有效的认证凭据。
- 错误处理建议:要求调用方进行认证。
DO_NOT_USE (-1)
强制用户包含一个默认分支。
- 常见使用场景:这是一个保留值,不应用于实际的错误状态。
- 错误处理建议:如果遇到此状态码,检查代码逻辑,确保没有错误地使用此值。
2024-05-19 更新内容
gRPC 自定义返回状态解释内容
在构建 gRPC 服务时,正确处理和返回错误状态对于确保服务的健壮性和易用性至关重要。以下是如何在 gRPC 服务中实现自定义返回状态,并遵循错误处理的最佳实践以及如何进行测试的一些示例。
自定义返回状态的重要性
自定义返回状态允许我们提供详细的错误信息,帮助客户端开发者快速定位问题,并采取适当的行动。例如,如果数据库查询未返回结果,我们可以返回一个 NOT_FOUND
状态码,并附加一条消息:
return ::grpc::Status(::grpc::StatusCode::NOT_FOUND, "在数据库中没有找到数据");
错误处理最佳实践
在实现服务时,我们遵循一些最佳实践来确保错误处理得当:
- 明确的错误分类:确保错误被清晰地分类,便于客户端理解。
- 一致性:在服务中保持错误处理的一致性。
- 避免敏感信息:在自定义消息中避免包含敏感信息。
- 错误恢复策略:为不同类型的错误提供恢复策略。
示例代码
以下是 GetDeviceSN
方法的一个实现示例,展示了如何使用自定义返回状态:/
grpc::Status CModuleServiceImpl::GetDeviceSN(ServerContext *context, const GetDeviceSNRequest *request, GetDeviceSNResponse *response) { std::cout << "[" << __FILE__ << ":" << __LINE__ << "]:" << "接口 GetDeviceSN 开始 " << std::endl; // TODO: 实现您的逻辑 auto sqlData = mysqlTool->GetStationSnByMysql(DB_EN_STATION_INFO_SN); if (sqlData.empty()) { return ::grpc::Status(::grpc::StatusCode::NOT_FOUND, ""); } for (auto &data : sqlData) { std::cout << "[" << __FILE__ << ":" << __LINE__ << "]:" << " 台站SN号为 " << data.m_key << " 值为 " << data.m_value << std::endl; response->set_device_sn(data.m_value); } std::cout << "[" << __FILE__ << ":" << __LINE__ << "]:" << "接口 GetDeviceSN 结束 " << std::endl; return grpc::Status::OK; }
单元测试和集成测试
为了确保我们的服务按预期工作,我们编写了单元测试和集成测试:
- 单元测试:针对
GetDeviceSN
方法,我们编写了测试用例来验证在数据库返回空结果时,方法是否正确返回NOT_FOUND
状态。 - 集成测试:我们还编写了集成测试来模拟数据库查询,并验证整个服务流程是否按预期工作。
通过这些测试,我们可以确保我们的服务在面对各种情况时都能正确地返回状态,并帮助客户端开发者更好地处理这些状态。
更加丰富的 场景使用模拟展示
在 gRPC 服务开发中,根据不同的场景假设,我们可以定义各种自定义返回状态码和说明内容。以下是一些常见的场景假设以及相应的状态码和说明内容的例子:
场景 1: 用户认证失败
当用户尝试调用一个需要认证的服务时,如果认证失败,服务应该返回一个明确的错误。
// 假设认证失败 if (!user.isAuthenticated()) { return ::grpc::Status(::grpc::StatusCode::UNAUTHENTICATED, "用户认证失败,请提供有效的认证信息"); }
场景 2: 权限不足
即使用户已经通过认证,如果他们没有足够的权限执行某个操作,服务应该返回一个权限不足的错误。
// 假设用户没有执行操作的权限 if (!user.hasPermission("execute_operation")) { return ::grpc::Status(::grpc::StatusCode::PERMISSION_DENIED, "用户没有执行此操作的权限"); }
场景 3: 请求参数无效
如果客户端提供的请求参数无效或不符合预期格式,服务应该返回一个错误,指出参数无效。
// 假设请求参数无效 if (!request.isValid()) { return ::grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, "提供的请求参数无效"); }
场景 4: 资源达到配额限制
在资源使用达到配额限制时,服务应该通知客户端,并阻止进一步的操作。
// 假设资源使用已达到配额限制 if (resourceUsage.isOverQuota()) { return ::grpc::Status(::grpc::StatusCode::RESOURCE_EXHAUSTED, "资源使用已达到配额限制"); }
场景 5: 服务不可用
如果服务由于维护或其他原因暂时不可用,应该返回一个状态码,告知客户端服务当前不可用
// 假设服务正在维护中 if (service.isUnderMaintenance()) { return ::grpc::Status(::grpc::StatusCode::UNAVAILABLE, "服务当前不可用,请稍后再试"); }
场景 6: 依赖服务失败
如果 gRPC 服务依赖于其他服务,而这些服务失败或响应超时,应该返回一个错误。
// 假设依赖的服务调用失败 if (!dependentService.call()) { return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "依赖服务调用失败,无法继续执行"); }
场景 7: 数据不一致
当操作依赖的数据状态不一致时,服务应该返回一个错误,指出数据问题。
// 假设数据状态不一致 if (data.isInconsistent()) { return ::grpc::Status(::grpc::StatusCode::ABORTED, "数据状态不一致,操作无法完成"); }
场景 8: 客户端需要重试
在某些情况下,如果操作失败,可能是因为临时的问题,服务可以建议客户端稍后重试。
// 假设操作因为临时问题失败 if (temporaryErrorOccurred()) { return ::grpc::Status(::grpc::StatusCode::UNAVAILABLE, "操作失败,请稍后重试"); }
场景 9: 操作被取消
如果客户端取消请求,服务应该能够适当地清理资源并返回一个取消状态。
// 假设客户端取消了请求 if (context.IsCancelled()) { return ::grpc::Status(::grpc::StatusCode::CANCELLED, "客户端取消了请求"); }
场景 10: 未知错误
在捕获到未预期的错误时,服务应该返回一个通用的未知错误状态。
// 假设发生了未知错误 catch (const std::exception& e) { return ::grpc::Status(::grpc::StatusCode::UNKNOWN, "发生了未知错误:" + std::string(e.what())); }
在实现 gRPC 服务时,合理使用这些状态码和说明内容可以帮助客户端开发者更好地理解错误,并采取适当的行动。
分享一个有趣的 学习链接:https://xxetb.xet.tech/s/HY8za