Golang和Thrift
Thrift是一款RPC協議+工具。我們團隊選擇了Thrift的主要原因是之前gRPC對gevent的支援不夠好。目前雖然有支援,但是合併也 還沒有多久。而Thrift有餓了麼搞的一套,相對來說好用一些。
翻滾吧,RESTful
RESTful這些年來可謂是大紅大紫,因為跨平臺,human-readable等等。但是實際上我們接RESTful介面的時候,就很蛋疼了。一般 我們都這樣幹:
- 準備好請求對應的介面的引數,也許要加一堆的頭部
- 請求對應的介面,設定超時
- 判斷返回的狀態碼,是否200,400,500等等
- 如果是200,解析json
- 一般返回的json都不會是隻有一級的,所以我們還要拿json裡的某一層。舉個例子,返回的是:
{ "code": 200, "message": "success", "result": { "name": "someone like you" } }
- 如果是Python這種動態語言,取name可能是這樣:
>>> name = json_dict.get("result", {}).get("name") >>> if name: print(name)
- 如果是Golang,Java等靜態語言,還要先定義好結構體或者類,然後unmarshal,並且判斷是否marshal出錯。。。
所以,RESTful寫一個兩個還算簡單,但是接多了真的是要瘋。有了RPC,它會自動幫你生成native的程式碼,遠端呼叫就像是呼叫 一個函式一樣簡單。不過說到底,RESTful只是一種表現形式,通過呼叫RESTful介面其實也是一種RPC,不過是一種蛋疼得RPC。 我們還是用Thrfit或者gRPC吧。
Thrift
Thrift有如下幾個概念:
-
Protocol: 協議,可以類比為HTTP協議
-
Transport: 如何傳輸,可以類比為TCP
-
Server: 一個組合上述東西的抽象概念,可以類比為web server
- TSimpleServer 單執行緒阻塞IO的server
- TThreadPoolServer 多執行緒阻塞IO的server
- TNonblockingServer 多執行緒,使用非阻塞IO的server
Thrift資料型別
- bool
- byte
- i16
- i32
- i64
- double
- string
- binary
- list
- set
- map
- struct 類似於Go的struct
- exception 異常
- service 類似於Go和Java的介面
沒有unsigned的型別。論文裡說原因是很多程式語言沒有這玩意兒,另外據觀察用的也少(其實我用的不少啊啊啊啊啊)。
Go和Thrift
Go的server類似於這樣:
func rpcServer() { nagatoHandler := &NagatoRPCHandler{} transportFactory := thrift.NewTBufferedTransportFactory(BufferSize) protocolFactory := thrift.NewTBinaryProtocolFactoryDefault() transport, err := thrift.NewTServerSocket(config.rpcAddr) if err != nil { logrus.Fatalf("failed to start rpc socket: %s", err) } processor := CustomizedTProcessor{p: nagato.NewNagatoServiceProcessor(nagatoHandler)} server := thrift.NewTSimpleServer4(processor, transport, transportFactory, protocolFactory) logrus.Infof("rpc server is on %s", config.rpcAddr) server.Serve() }
其中最上面的nagatoHandler就是一個type NagatoRPCHandler struct{}
然後給他實現service裡定義的方法。因為最後
RPC生成程式碼裡的Processor其實是一個介面。實現了那些方法就好了。
更詳細的例子看:ofollow,noindex" target="_blank">https://thrift.apache.org/tutorial/go
坑
thrift.NewTSimpleServer4
// CustomizedTProcessor 是定製化的TProcessor,用來搞一些事情 type CustomizedTProcessor struct { p thrift.TProcessor } // Process 是為了搞事情。。。 func (c CustomizedTProcessor) Process(ctx context.Context, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { start := time.Now() // 執行 success, err = c.p.Process(ctx, iprot, oprot) // 統計,然後返回 end := time.Now() latency := end.Sub(start) var status string if success { status = "200" } else { status = "400" } /* endpoint暫時不好拿。可以參考生成的程式碼裡有這麼一行: name, _, seqId, err := iprot.ReadMessageBegin() 但是目前我還沒有看完所有的thrift程式碼,不敢斷定是否所有的protocol實現都不會受影響。所以暫時不這麼幹。使用reflect 拿出一個可以做處標識的先。 -。-其實現在這裡endpoint也標識不出啥。。。but。。。 */ endpoint := reflect.TypeOf(c.p).String() entry := logrus.WithFields(logrus.Fields{ "request-id": "UNKNOW", "status":status, "method":"rpc", "uri":endpoint, "ip":"UNKNOW", "latency":latency, "user-agent": "ThriftRPC", "time":end.Format(time.RFC3339), }) if success { entry.Info() } else { entry.Error(err.Error()) } histogramVec.With( prometheus.Labels{ "method":"rpc", "endpoint": endpoint, "service":"nagato", "status":status, }, ).Observe(latency.Seconds()) return success, err }
當然,目前這個實現還很粗糙。本來是可以拿到具體是哪個processor的。但是name, _, seqId, err := iprot.ReadMessageBegin()
這一行,有點侵入到thrift的實現了。。。而且還沒有讀完thrift的程式碼,不敢亂動。。。