自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

聊聊為什么 IDL 只能擴(kuò)展字段而非修改

大數(shù)據(jù) 數(shù)據(jù)分析
本文聊聊 grpc proto 變更時(shí)的兼容問題,核心只有一條:對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉,永遠(yuǎn)只增加字段而不修改。

[[439484]]

本文轉(zhuǎn)載自微信公眾號(hào)「董澤潤的技術(shù)筆記」,作者董澤潤  。轉(zhuǎn)載本文請(qǐng)聯(lián)系董澤潤的技術(shù)筆記公眾號(hào)。

前幾年業(yè)界流行使用 thrift, 比如滴滴。這幾年 grpc 越來越流行,很多開源框架也集成了,我司大部分服務(wù)都同時(shí)開放 grpc 和 http 接口

相比于傳統(tǒng)的 http1 + json 組合,這兩種技術(shù)都用到了 IDL, 即 Interface description language 接口描述語言,相當(dāng)于增加了 endpoint schema 約束,不同語言只需要一份相同的 IDL 文件即可生成接口代碼。

很多人喜歡問:proto buf 與 json 比起來有哪些優(yōu)勢(shì)?比較經(jīng)典的面試題

IDL 文件管理每個(gè)公司不一樣,有的保存在單獨(dú) gitlab 庫,有的是 mono repo 大倉庫。當(dāng)業(yè)務(wù)變更時(shí),IDL 文件經(jīng)常需要修改,很多新手總是容易踩坑,本文聊聊 grpc proto 變更時(shí)的兼容問題,核心只有一條:對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉,永遠(yuǎn)只增加字段而不修改

測(cè)試修改兼容性

本文測(cè)試使用 grpc-go example 官方用例,感興趣自查

  1. syntax = "proto3"
  2.  
  3. option go_package = "google.golang.org/grpc/examples/helloworld/helloworld"
  4. package helloworld; 
  5.  
  6. // The greeting service definition. 
  7. service Greeter { 
  8.   // Sends a greeting 
  9.   rpc SayHello (HelloRequest) returns (HelloReply) {} 
  10.  
  11. // The request message containing the user's name
  12. message HelloRequest { 
  13.   string name = 1; 
  14.  
  15. // The response message containing the greetings 
  16. message HelloReply { 
  17.   string message = 1; 
  18.   string additional = 2; 
  19.   int32 age = 3; 
  20.   int64 id = 4; 

每次修改后使用 protoc 重新生成代碼

  1. protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative helloworld/helloworld.proto 

Server 每次接受請(qǐng)求后,返回 HelloReply 結(jié)構(gòu)體

  1. // SayHello implements helloworld.GreeterServer 
  2. func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { 
  3.  log.Printf("Received: %v"in.GetName()) 
  4.  return &pb.HelloReply{ 
  5.   Message:    "Hello addidional " + in.GetName(), 
  6.   Additional: "this is addidional field"
  7.   Age:        10, 
  8.   Id:         12345, 
  9.  }, nil 

Client 每次只打印 Server 返回的結(jié)果

修改字段編號(hào)

將 HelloReply 結(jié)構(gòu)體字段 age 編號(hào)變成 12, 然后 server 使用新生成的 IDL 庫,client 使用舊版本

  1. zerun.dong$ ./greeter_client 
  2. ...... 
  3. 2021/12/08 22:23:38 Greeting: { 
  4.  "message""Hello addidional world"
  5.  "additional""this is addidional field"
  6.  "id": 12345 

可以看到 client 沒有讀到 age 字段,因?yàn)?IDL 是根據(jù)序號(hào)傳輸?shù)模琧lient 讀不到 seq 3, 所以修改序號(hào)不兼容

修改字段 name

修改 HelloReploy 字段 id, 變成 score 類型和序號(hào)不變

  1. // The response message containing the greetings 
  2. message HelloReply { 
  3.   string message = 1; 
  4.   string additional = 2; 
  5.   int32 age = 3; 
  6.   int64 score = 4; 

重新編譯 server, 并用舊版本 client 訪問

  1. zerun.dong$ ./greeter_client 
  2. ...... 
  3. 2021/12/08 22:29:18 Greeting: { 
  4.  "message""Hello addidional world"
  5.  "additional""this is addidional field"
  6.  "age": 10, 
  7.  "id": 12345 

可以看到,雖然修改了字段名,但是 client 仍然讀到了正確的值 12345, 如果字段含義不變,那么只修改名稱是兼容的

修改類型

有些類型是兼容的,有些不可以,而且還要考慮不同的語言。這里測(cè)試三種

1.字符串與字節(jié)數(shù)組

  1. // The response message containing the greetings 
  2. message HelloReply { 
  3.   string message = 1; 
  4.   bytes additional = 2; 
  5.   int32 age = 3; 
  6.   int64 id = 4; 

我們將 additional 字段由 string 類型修改為 bytes

  1. // The response message containing the greetings 
  2. type HelloReply struct { 
  3.  Message    string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"
  4.  Additional []byte `protobuf:"bytes,2,opt,name=additional,proto3" json:"additional,omitempty"
  5.  Age        int32  `protobuf:"varint,3,opt,name=age,proto3" json:"age,omitempty"
  6.  Id         int64  `protobuf:"varint,4,opt,name=id,proto3" json:"id,omitempty"

可以看到 go 結(jié)構(gòu)體由 string 變成了 []byte, 我們知道這兩個(gè)其實(shí)可以互換

  1. zerun.dong$ ./greeter_client 
  2. ...... 
  3. 2021/12/08 22:35:43 Greeting: { 
  4.  "message""Hello addidional world"
  5.  "additional""this is addidional field"
  6.  "age": 10, 
  7.  "id": 12345 

最后結(jié)果也證明 client 可以正確的處理數(shù)據(jù),即修改成兼容類型沒有任何問題

2.int32 int64 互轉(zhuǎn)

  1. message HelloReply { 
  2.   string message = 1; 
  3.   string additional = 2; 
  4.   int64 age = 3; 
  5.   int64 id = 4; 

這里我們將 age 由 int32 修改成 int64 字段,位數(shù)不一樣,如果同樣小于 int32 最大值沒有問題,此時(shí)我們?cè)?server 端將 age 賦于 2147483647 + 1 剛好超過最大值

  1. zerun.dong$ ./greeter_client 
  2. ...... 
  3. 2021/12/08 22:43:32 Greeting: { 
  4.  "message""Hello addidional world"
  5.  "additional""this is addidional field"
  6.  "age": -2147483648, 
  7.  "id": 12345 

我們可以看到 age 變成了負(fù)數(shù),如果業(yè)務(wù)剛好允許負(fù)值,那么此時(shí)一定會(huì)出邏輯問題,而且難以排查 bug, 這其實(shí)是非常典型的向上向下兼容問題

3.非兼容類型互轉(zhuǎn)

  1. message HelloReply { 
  2.   string message = 1; 
  3.   string additional = 2; 
  4.   string age = 3; 
  5.   int64 id = 4; 

我們將 age 由 int32 變成 string 字符串,依舊使用 client 舊版本測(cè)試

  1. zerun.dong$ ./greeter_client 
  2. ...... 
  3. 2021/12/08 22:55:21 Greeting: { 
  4.  "message""Hello addidional world"
  5.  "additional""this is addidional field"
  6.  "id": 12345 
  7. 2021/12/08 22:55:21 message:"Hello addidional world" additional:"this is addidional field" id:12345 3:"this is age" 
  8. 2021/12/08 22:57:56 r.Age is 0 

可以看到結(jié)構(gòu)體 json 序列化打印時(shí)不存在 Age 字段,但是 log 打印時(shí)發(fā)現(xiàn)了不兼容的 3:"this is age", 注意 grpc 會(huì)保留不兼容的數(shù)據(jù)

同時(shí) r.Age 默認(rèn)是 0 值,即非兼容類型修改是有問題的

刪除字段

  1. message HelloReply { 
  2.   string message = 1; 
  3.   string additional = 2; 
  4.   // string age = 3; 
  5.   int64 id = 4; 

刪除字段 age 也就是說序號(hào)此時(shí)有空洞,運(yùn)行 client 舊版本協(xié)義

  1. zerun.dong$ ./greeter_client 
  2. ...... 
  3. 2021/12/08 23:02:12 Greeting: { 
  4.  "message""Hello addidional world"
  5.  "additional""this is addidional field"
  6.  "id": 12345 
  7. 2021/12/08 23:02:12 message:"Hello addidional world"  additional:"this is addidional field"  id:12345 
  8. 2021/12/08 23:02:12 0 

沒有問題,打印 r.Age 當(dāng)然是默認(rèn)值 0, 即刪除字段是兼容的

為什么 required 在 proto3 中取消了?

  1. message SearchRequest { 
  2.   required string query = 1; 
  3.   optional int32 page_number = 2; 
  4.   optional int32 result_per_page = 3; 

熟悉 thrift 或是使用 proto2 協(xié)議的都習(xí)慣使用 required optional 來定義字段屬于,擴(kuò)展字段一般標(biāo)記為 optional, 必傳字段使用 required 來約束

官方解釋如下 issues2497[1],簡單說就是 required 打破了更新 IDL 時(shí)的兼容性

  • 永遠(yuǎn)不能安全地向 proto 定義添加 required 字段,也不能安全地刪除現(xiàn)有的 required 字段,因?yàn)檫@兩個(gè)操作都會(huì)破壞兼容性
  • 在一個(gè)復(fù)雜的系統(tǒng)中,proto 定義在系統(tǒng)的許多不同組件中廣泛共享,添加/刪除 required 字段可以輕松地降低系統(tǒng)的多個(gè)部分
  • 多次看到由此造成的生產(chǎn)問題,并且 Google 內(nèi)部幾乎禁止任何人添加/刪除 required 字段

上面是谷歌得出的結(jié)論,大家可以借鑒一下,但也不能唯 G 家論

小結(jié)

IDL 修改還有很多測(cè)試用例,感興趣的可以多玩玩,比如結(jié)構(gòu)體間的轉(zhuǎn)換問題,比如 enum 枚舉類型。上文測(cè)試的都是 server 端使用新協(xié)義,client 使用舊協(xié)義,如果反過來呢?想測(cè)試 thrift 的可以看看這篇 thrift missing guide[2]

 

本文能過測(cè)試 case 想告訴大家,IDL 只能追加杜絕修改 (產(chǎn)品測(cè)試階段隨變改,無所謂)

 

責(zé)任編輯:武曉燕 來源: 董澤潤的技術(shù)筆記
相關(guān)推薦

2022-12-26 00:00:03

非繼承關(guān)系JDK

2022-05-17 22:20:41

哨兵Redis機(jī)制

2022-05-11 08:22:54

IO負(fù)載NFSOS

2012-03-06 20:51:04

iOS

2022-02-21 07:54:28

單元測(cè)試編程開發(fā)

2024-01-30 07:55:03

KubernetesAPI服務(wù)器

2015-08-27 16:48:11

FirefoxChrome

2022-01-19 22:51:57

設(shè)計(jì)匿名用戶

2017-02-10 09:55:53

SwiftObjective-C

2024-09-24 08:18:13

2022-03-28 08:24:52

MySQL聚簇索引非聚簇索引

2017-04-17 11:50:13

51CTO 學(xué)院

2021-11-29 10:24:56

WasmEnvoy 負(fù)載均衡

2018-12-29 15:41:41

阿里巴巴程序員serialVersi

2023-02-15 08:41:56

多層維表性能寬表

2019-07-02 08:30:25

蘋果 iOS系統(tǒng)

2020-12-29 05:34:00

動(dòng)態(tài)代理

2023-07-05 08:17:38

JDK動(dòng)態(tài)代理接口

2024-08-07 08:14:26

2024-02-21 21:28:29

Linux系統(tǒng)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)