需求背景:
云端在线架构有上游A-中间模块B-下游C三个模块,都是proto格式数据传输交互。流量方向为从A->B->C,当A模块需要给proto增加某个字段,这个字段中间模块B并不关系,只需要透传给下游模块C来使用。
那么A和C模块都升级proto的情况下,作为透传模块的B能不能不升级proto文件呢?答案是肯定的,这就要说到proto3的unknown字段支持了(proto2和大于proto 3.5版本的支持)
proto定义:
A模块和c模块的proto:
syntax = "proto3";
package dcs;
option go_package = "dcs_type";
message Directive{
Header header = 1;
Payload payload = 2;
}
message Header{
string namespace = 1;
string name = 2;
string messageid = 3;
}
message Payload{
string token = 1;
}
b模块的proto:
syntax = "proto3";
package ui;
option go_package = "ui_type";
message Directive{
Header header = 1;
Payload payload = 2;
}
message Header{
string namespace = 1;
string name = 2;
}
message Payload{
string token = 1;
}
从两份proto文件可以看到,A模块和C模块相比较B模块的proto,就是header里面多了一个messageid字段。
demo
先将proto 生成go class文件,然后
上游A模块代码:
package main
import (
"bytes"
"fmt"
"net/http"
dcs_type "through_proto_check/through_dcs/dcs_type"
proto "github.com/golang/protobuf/proto"
)
func main() {
direct := new(dcs_type.Directive)
direct.Header = &dcs_type.Header{
Namespace: "screen",
Name: "voiceinput",
Messageid: "11111111",
}
direct.Payload = &dcs_type.Payload{
Token: "vvvvvvv",
}
out, err := proto.Marshal(direct)
if err != nil {
panic(err)
}
url := "http://127.0.0.1:8702/saiya/ui"
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(out))
client := &http.Client{}
res, err := client.Do(req)
if err != nil {
panic(err)
}
fmt.Println(res)
}
中间B模块代码:
package main
import (
"bytes"
"fmt"
"net/http"
"through_proto_check/through_ui/ui_type"
proto "github.com/golang/protobuf/proto"
"github.com/kataras/iris"
)
func dcsEvents(ctx iris.Context) {
body, err := ctx.GetBody()
if err != nil {
panic(err)
}
uiReq := new(ui_type.Directive)
secUIReq := new(ui_type.Directive)
err = proto.Unmarshal(body, uiReq)
if err != nil {
panic(err)
}
secUIReq.Header = uiReq.Header
secUIReq.Payload = uiReq.Payload
fmt.Println(secUIReq)
out, err := proto.Marshal(secUIReq)
if err != nil {
panic(err)
}
url := "http://127.0.0.1:8703/saiya/us"
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(out))
client := &http.Client{}
res, err := client.Do(req)
if err != nil {
panic(err)
}
fmt.Println(res)
}
func main() {
app := iris.New()
app.Post("/saiya/ui", dcsEvents)
fmt.Println("service running...")
app.Run(iris.Addr(":8702"))
}
下游C模块代码
package main
import (
"fmt"
us_type "through_proto_check/through_us/us_type"
proto "github.com/golang/protobuf/proto"
"github.com/kataras/iris"
)
func usEvents(ctx iris.Context) {
body, err := ctx.GetBody()
if err != nil {
panic(err)
}
usReq := new(us_type.Directive)
err = proto.Unmarshal(body, usReq)
if err != nil {
panic(err)
}
fmt.Println(usReq)
}
func main() {
app := iris.New()
app.Post("/saiya/us", usEvents)
fmt.Println("service running...")
app.Run(iris.Addr(":8703"))
}
结果
分别依次启动C,B,A服务:
C服务:
[work@ through_us]#go run main.go
service running...
Now listening on: http://0.0.0.0:8703
Application started. Press CTRL+C to shut down.
header:<namespace:"screen" name:"voiceinput" messageid:"11111111" > payload:<token:"vvvvvvv" >
B服务:
[work@ through_ui]#go run main.go
service running...
Now listening on: http://0.0.0.0:8702
Application started. Press CTRL+C to shut down.
header:<namespace:"screen" name:"voiceinput" 3:"11111111" > payload:<token:"vvvvvvv" >
&{200 OK 200 HTTP/1.1 1 1 map[Content-Length:[0] Date:[Thu, 05 Dec 2019 11:38:32 GMT]] {} 0 [] false false map[] 0xc00004ad00 <nil>}
A服务:
[work@ through_dcs]#go run main.go
&{200 OK 200 HTTP/1.1 1 1 map[Content-Length:[0] Date:[Thu, 05 Dec 2019 11:20:41 GMT]] {} 0 [] false false map[] 0xc00002a100 <nil>}
说明
从C打印出来的数据可以看到成功拿到了mssageid,这个key在B模块的proto中是没有定义的。
从b模块打印出来的信息可以看到:
3:"11111111"
经过A模块marshal传输过来的数据,在B模块unmarshal后,message这个不认识的字段的信息(unknown field)被编码成了pb_id:val这样的数据给暂存下来,这样的数据经过marshal后会保留,传给下游C模块经unmarshal后,会根据pb_id找到key为messageid。至此,C模块成功的拿到了messageid。
有疑问加站长微信联系(非本文作者)