这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记。
1 RPC基础
1.1 RPC入门
RPC是远程过程调用的简称,是分步式系统不同节点间流行的通信方式。而只有满足RPC规则的函数才能注册为RPC函数:
1.方法只能有两个可序列化参数
2.其中第二个参数是指针类型,函数返回error类型
3.必须是公开方法
//服务端
func main(){
rpc.RegisterName("方法的名字", new(初始化函数))//它会将方法的所有函数注册
listener, err := net.Listen("tcp", "localhost:1234")
conn, err := listener.Accept()
rpc.ServeConn(conn)
}
//客户端
func main(){
client, err := rpc.Dail("tcp", "localhost:1234")
err = client.Call("包含绝对路径的服务名.函数名",函数参数1, 函数参数2)
}
1.2 更安全的RPC接口
//服务端的规范
const HelloServiceName = "path/to/pkg.Helloservice"
//只有满足HelloServiceInterface接口的类才能调用注册的函数
type HelloserviceInterface = interface{
Hello(request string, reply *string) error
}
func RegisterHelloService(svc HelloServiceInterface) error {
return rpc.RegisterName(HelloserviceName, svc)
}
//客户端的接口规范
type HelloeServiceClient struct {
//增加其他的判断操作相关的函数
*rpc.Client
}
//类型判断,倘若HelloServiceClient不满足HelloServiceInterface的话,此句报错
var _ HelloServiceInterface = (*HelloServiceClient)(nil)
//同时我们可以包装Dail函数和访问函数,更加安全
func DailHelloService(netswork, address string) (*HelloServiceClient, error){
c, err := rpc.Dail(network, address)
return &HelloServiceClient{Client:c}, nil
}
func(p *HelloServieClient) Hello(request string, reply *string) error {
return p.Client.Call(HelloServiceName+"Hello", request, reply)
}
1.3 跨语言的RPC
其实就是使用JSON传递信息
在最后启动的时候改为:
type serverRequest struct {
//和clientRequest基本相同
Method string `json: "method"`
Paeams *json.RawMessage `json: "params"`
id *json.RawMessage `json: "id"`
}
//启动的更改
go rpc.ServeCode(jsonrpc.NewServerCode(conn))
//信息接收
type serverResponse struct {
Id *json.RawMessage `json: "id"`
Result interface{} `json: "result"`
Error interface{} `json: "error"`
}
//数据的绑定
type clietnRequest struct{
Method string `json: "method"`
Paeams [1]interface{} `json: "params"`
id uint64 `json: "id"`
}
//启动模块
client := rpc.NewClientWithCode(jsonrpc.NewClientCode(conn))
//信息接收
type clientResponse struct {
Id uint64 `json: "id"`
Result *json.RawMessage `json: "result"`
Error interface{} `json: "error"`
}
同样的,如果注册服务后可以借用http的中间件开始RPC服务,因为HTTP服务采用的是Gob协议,并没有采用其他的协议接口,所以其他语言同样无法访问。
2 Protobuf基础
2.1 protobuf基础
每次需要获取的依赖:
go get -u google.golang.org/protobuf
为文件增加依赖:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
//永久性获取
在主文件夹下新建“pb”文件夹,再在其中建立相关接口分包,包中的文件以“.proto”结尾
syntax = "proto3"; //表明编译的版本,只有proto3支持gRPC
package student; //生成的包名
option go_package = "./需要生成到的文件夹";//一般是./proto
message Student { //message相当于struct,支持嵌套
string name = 1;
bool male = 2;
repeated int32 scores = 3;
}
enum PhoneType { //枚举类型,值必须从0开始
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4;
google.protobuf.Timestamp last_updated = 5;
}
message AddressBook {
repeated Person people = 1;
}
//map字段对应map类型, 声明格式map<string, int64>对应map[string]int64
完成编辑后在终端输入以下代码实现编译(首先要cd到pb包中,在进行操作):
//格式
protoc --proto_path=IMPORT_PATH --go_out=OUT_DIR --go_opt=paths=source_relative
//注意你的GOOS不能错误
//生成编译完成的包
protoc --go_out=. ./源文件名.proto
解释:
1.–proto_path:指定 import 路径可以指定多个参数,编译时按顺序查找,省略时默认查找当前目录。
2.*.proto 文件中也可以引入其他 *.proto 文件,这里主要用于指定被引入文件的位置。
3.–go_out:golang编译支持,指定输出文件路径(其他语言则替换即可,比如 --java_out 等等)。
4.–go_opt:指定参数,比如--go_opt=paths=source_relative就是表明生成文件输出使用相对路径。
2.2字段
2.2.1 保留字段(Reserved field)
更新消息的时候,某些字段或者标识符会被删除,升级时,需要对一些字段或者标识符进行保留,保证它不被复用,需要关键字reservrd:
message Foo{
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
2.2.2 标量类型
| proto类型 | go类型 | 备注 |
|---|---|---|
| double | float 64 | |
| float | float32 | |
| int32 | int32 | |
| int64 | int64 | |
| uint32 | uint32 | |
| uint64 | uint64 | 适合负数 |
| sint32 | int32 | 适合负数 |
| fixed32 | uint32 | 固长编码适合大于2^28的值 |
| fixed64 | uint64 | 固长编码适合大于2^56的值 |
| sfixed32 | int32 | 固长编码 |
| sfixed64 | int64 | 固长编码 |
| bool | bool | |
| string | string | UTF8编码,长度不超过2^32 |
| bytes | []byte | 任意字节序列,长度不超过2^32 |
注意: 如果标量类型没有被赋值,不会被序列化,解析时会被赋予默认值。(bool默认false)
2.2.3 枚举类型
1.关键词enum,规定枚举类型的第一个选项的标识符必须是0。
2.使用语句 option allow_alias = true表示可以使用别名。
2.2.4 其他消息类型
1.result:一种消息类型,在SearchResponse作为一个消息字段类型使用。
2.Any:表示任意类型,可以表示不在.proto中定义的任意内置类型。
3 oneof:某消息包含多个字段,但这些字段同一时间最多只允许一个被设置时,可以通过oneof来保证这样的行为,对oneof中任意一个字段设值,都会将其他字段清空。
4.map:声明方式map<string, int32> 变量名,它的key不能是double, float, bytes类型。
2.2.5定义服务
如果消息类型是用来远程通信的,可以在 .proto 文件中定义 RPC 服务接口。例如我们定义了一个名为 SearchService 的 RPC 服务,提供了 Search 接口,入参是 SearchRequest 类型,返回类型是 SearchResponse
2.2.6 补充
在编写的文件中,每个字段都有一个数字,= 1 这个不是赋值,而是编号。一个 message 中,每个字段都有唯一的编号,这些数字用于标识二进制格式的字段(数据传输时会被压缩等),当编号范围是 1-15 时,存储编号需要一个字节,简单来说一个message中最好不要超过15个编号。
PS.写的时间比较久了,只是对网上的博客的总结(个人使用)