brpc/docs/cn/new_protocol.md
2022-12-14 20:13:26 +08:00

157 lines
9.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# server端多协议
brpc server一个端口支持多种协议大部分时候这对部署和运维更加方便。由于不同协议的格式大相径庭严格地来说一个端口很难无二义地支持所有协议。出于解耦和可扩展性的考虑也不太可能集中式地构建一个针对所有协议的分类器。我们的做法就是把协议归三类后逐个尝试
- 第一类协议:标记或特殊字符在最前面,比如[baidu_std](baidu_std.md)hulu_pbrpc的前4个字符分别是PRPC和HULU解析代码只需要检查前4个字节就可以知道协议是否匹配最先尝试这类协议。这些协议在同一个连接上也可以共存。
- 第二类协议有较为复杂的语法没有固定的协议标记或特殊字符可能在解析一段输入后才能判断是否匹配目前此类协议只有http。
- 第三类协议协议标记或特殊字符在中间比如nshead的magic_num在第25-28字节。由于之前的字段均为二进制难以判断正确性在没有读取完28字节前我们无法判定消息是不是nshead格式的所以处理起来很麻烦若其解析排在http之前那么<=28字节的http消息便可能无法被解析因为程序以为是“还未完整的nshead消息”。
考虑到大多数链接上只会有一种协议我们会记录前一次的协议选择结果下次首先尝试。对于长连接这几乎把甄别协议的开销降到了0虽然短连接每次都得运行这段逻辑但由于短连接的瓶颈也往往不在于此这套方法仍旧是足够快的。在未来如果有大量的协议加入我们可能得考虑一些更复杂的启发式的区分方法。
# client端多协议
不像server端必须根据连接上的数据动态地判定协议client端作为发起端自然清楚自己的协议格式只要某种协议只通过连接池或短链接发送即独占那个连接那么它可以是任意复杂或糟糕的格式。因为client端发出时会记录所用的协议等到response回来时直接调用对应的解析函数没有任何甄别代价。像memcacheredis这类协议中基本没有magic number在server端较难和其他协议区分开但让client端支持却没什么问题。
# 支持新协议
brpc就是设计为可随时扩展新协议的步骤如下
> 以nshead开头的协议有统一支持看[这里](nshead_service.md)。
## 增加ProtocolType
在[options.proto](https://github.com/brpc/brpc/blob/master/src/brpc/options.proto)的ProtocolType中增加新协议类型如果你需要的话可以联系我们增加以确保不会和其他人的需求重合。
目前的ProtocolType18年中:
```c++
enum ProtocolType {
PROTOCOL_UNKNOWN = 0;
PROTOCOL_BAIDU_STD = 1;
PROTOCOL_STREAMING_RPC = 2;
PROTOCOL_HULU_PBRPC = 3;
PROTOCOL_SOFA_PBRPC = 4;
PROTOCOL_RTMP = 5;
PROTOCOL_HTTP = 6;
PROTOCOL_PUBLIC_PBRPC = 7;
PROTOCOL_NOVA_PBRPC = 8;
PROTOCOL_NSHEAD_CLIENT = 9; // implemented in brpc-ub
PROTOCOL_NSHEAD = 10;
PROTOCOL_HADOOP_RPC = 11;
PROTOCOL_HADOOP_SERVER_RPC = 12;
PROTOCOL_MONGO = 13; // server side only
PROTOCOL_UBRPC_COMPACK = 14;
PROTOCOL_DIDX_CLIENT = 15; // Client side only
PROTOCOL_REDIS = 16; // Client side only
PROTOCOL_MEMCACHE = 17; // Client side only
PROTOCOL_ITP = 18;
PROTOCOL_NSHEAD_MCPACK = 19;
PROTOCOL_DISP_IDL = 20; // Client side only
PROTOCOL_ERSDA_CLIENT = 21; // Client side only
PROTOCOL_UBRPC_MCPACK2 = 22; // Client side only
// Reserve special protocol for cds-agent, which depends on FIFO right now
PROTOCOL_CDS_AGENT = 23; // Client side only
PROTOCOL_ESP = 24; // Client side only
PROTOCOL_THRIFT = 25; // Server side only
}
```
## 实现回调
均定义在struct Protocol中该结构定义在[protocol.h](https://github.com/brpc/brpc/blob/master/src/brpc/protocol.h)。其中的parse必须实现除此之外server端至少要实现process_requestclient端至少要实现serialize_requestpack_requestprocess_response。
实现协议回调还是比较困难的这块的代码不会像供普通用户使用的那样有较好的提示和保护你得先靠自己搞清楚其他协议中的类似代码然后再动手最后发给我们做code review。
### parse
```c++
typedef ParseResult (*Parse)(butil::IOBuf* source, Socket *socket, bool read_eof, const void *arg);
```
用于把消息从source上切割下来client端和server端使用同一个parse函数。返回的消息会被递给process_request(server端)或process_response(client端)。
参数source是读取到的二进制内容socket是对应的连接read_eof为true表示连接已被对端关闭arg在server端是对应server的指针在client端是NULL。
ParseResult可能是错误也可能包含一个切割下来的message可能的值有
- PARSE_ERROR_TRY_OTHERS 不是这个协议框架会尝试下一个协议。source不能被消费。
- PARSE_ERROR_NOT_ENOUGH_DATA : 到目前为止数据内容不违反协议但不构成完整的消息。等到连接上有新数据时新数据会被append入source并重新调用parse。如果不确定数据是否一定属于这个协议source不应被消费如果确定数据属于这个协议也可以把source的内容转移到内部的状态中去。比如http协议解析中即使source不包含一个完整的http消息它也会被http parser消费掉以避免下一次重复解析。
- PARSE_ERROR_TOO_BIG_DATA : 消息太大拒绝掉以保护server连接会被关闭。
- PARSE_ERROR_NO_RESOURCE : 内部错误,比如资源分配失败。连接会被关闭。
- PARSE_ERROR_ABSOLUTELY_WRONG : 应该是这个协议比如magic number匹配了但是格式不符合预期。连接会被关闭。
### serialize_request
```c++
typedef bool (*SerializeRequest)(butil::IOBuf* request_buf,
Controller* cntl,
const google::protobuf::Message* request);
```
把request序列化进request_bufclient端必须实现。发生在pack_request之前一次RPC中只会调用一次。cntl包含某些协议比如http需要的信息。成功返回true否则false。
### pack_request
```c++
typedef int (*PackRequest)(butil::IOBuf* msg,
uint64_t correlation_id,
const google::protobuf::MethodDescriptor* method,
Controller* controller,
const butil::IOBuf& request_buf,
const Authenticator* auth);
```
把request_buf打包入msg每次向server发送消息前包括重试都会调用。当auth不为空时需要打包认证信息。成功返回0否则-1。
### process_request
```c++
typedef void (*ProcessRequest)(InputMessageBase* msg_base);
```
处理server端parse返回的消息server端必须实现。可能会在和parse()不同的线程中运行。多个process_request可能同时运行。
在r34386后必须在处理结束时调用msg_base->Destroy()为了防止漏调考虑使用DestroyingPtr<>。
### process_response
```c++
typedef void (*ProcessResponse)(InputMessageBase* msg_base);
```
处理client端parse返回的消息client端必须实现。可能会在和parse()不同的线程中运行。多个process_response可能同时运行。
在r34386后必须在处理结束时调用msg_base->Destroy()为了防止漏调考虑使用DestroyingPtr<>。
### verify
```c++
typedef bool (*Verify)(const InputMessageBase* msg);
```
处理连接的认证只会对连接上的第一个消息调用需要支持认证的server端必须实现不需要认证或仅支持client端的协议可填NULL。成功返回true否则false。
### parse_server_address
```c++
typedef bool (*ParseServerAddress)(butil::EndPoint* out, const char* server_addr_and_port);
```
把server_addr_and_port(Channel.Init的一个参数)转化为butil::EndPoint可选。一些协议对server地址的表达和理解可能是不同的。
### get_method_name
```c++
typedef const std::string& (*GetMethodName)(const google::protobuf::MethodDescriptor* method,
const Controller*);
```
定制method name可选。
### supported_connection_type
标记支持的连接方式。如果支持所有连接方式设为CONNECTION_TYPE_ALL。如果只支持连接池和短连接设为CONNECTION_TYPE_POOLED_AND_SHORT。
### name
协议的名称,会出现在各种配置和显示中,越简短越好,必须是字符串常量。
## 注册到全局
实现好的协议要调用RegisterProtocol[注册到全局](https://github.com/brpc/brpc/blob/master/src/brpc/global.cpp)以便brpc发现。就像这样
```c++
Protocol http_protocol = { ParseHttpMessage,
SerializeHttpRequest, PackHttpRequest,
ProcessHttpRequest, ProcessHttpResponse,
VerifyHttpRequest, ParseHttpServerAddress,
GetHttpMethodName,
CONNECTION_TYPE_POOLED_AND_SHORT,
"http" };
if (RegisterProtocol(PROTOCOL_HTTP, http_protocol) != 0) {
exit(1);
}
```