做者:amchtml
引言:上一篇文章介紹瞭如何建立安裝 TarsGo,同時也闡述瞭如何開始一個 TarsGo HTTP 服務。本文就要開始 TarsGo 的主力業務了:基於 TARS 自帶的 RPC 協議,設計 TarsGo 服務。 本文的內容大體思路與官方 Quick Start 相同,但例子會有所不一樣,同時對於一些坑也會解釋得詳細點。本文的代碼能夠在個人 GitHub repo 中找到。前端
設計目標
上一篇文章中,個人 HTTP 服務器向前端返回一串 Json 字符串,其中包含了服務器時間。這一次,我設計一個服務(命名爲amc.GoTarsServer.GoTarsObj
)來提供服務器時間。HTTP 服務則向這個新服務請求時間以後再返回給用戶。前文提到 HTTP 服務的實例名稱相對不過重要,可是供內部 RPC 調用的服務,其名稱就很重要了,它是其餘服務進行尋址的重要依據。c++
設計協議
TARS 框架的原生 RPC 調用是使用專門設計的 tars 協議
(文件後綴名 .tars
)進行通訊的。這個協議其實也不神祕,讀者能夠自行嘗試一下、多看一些示例,很快就能夠了解了。這裏我按照我本身寫的協議文件來講明吧:git
// filename: DateTime.tars module amc { struct GetTimeReq { 0 optional string timeFmt; }; struct GetTimeRsp { 0 require long utcTimestamp; // UTC UNIX timestamp 1 require long localTimestamp; 2 require string localTimeStr; }; interface DateTime { int GetTime(GetTimeReq req, out GetTimeRsp rsp); }; };
協議解析
上面的協議中,其實包含了幾個部分:github
- 文件名:是協議的一部分。協議文件名其實也就是這個協議包的名稱,轉換工具會將其轉爲同名的源文件。
- 模塊名:參考 C++ 的命名空間,開發者能夠自由利用這個模塊名。我我的喜歡維持和 App 同名。
- 接口和方法:接口(interface)內能夠定義多個 RPC 方法
- 複合數據類型:使用
struct
定義複合數據類型
因此,解讀上面的協議,以下:web
- 在接口
DateTime
下,定義了一個方法:GetTime
- GetTime 方法包含兩個參數,分別是兩個結構體。
- 入參中包含了變量名
timeFmt
,表示以什麼樣的格式返回時間信息 - 出參包含了
UTC
時間戳、本地時間戳和時間字符串
我的建議
- 在建立協議的時候,我喜歡以
int MethodName(MethodReq req, out MethodRsq rsp)
的模式來命名,不管是否有入參和出參,方法中的req
和rsp
都會存在。這種設計方式比較適合將來的擴展,若是須要添加參數或返回信息,只須要在兩個struct
中添加便可。 - 第一次建立協議的時候,若是入參都是必要的,那麼建議均設置爲
require
屬性,表示該參數是必須的;可是在之後擴展協議時,新增參數應設置爲optional
屬性,保證還未升級到新版本協議的 clients 仍能正常調用。
服務端代碼設計
建立源碼模板
首先,咱們能夠用 TarsGo 自帶的工具首先生成工程模版:json
cd $GOPATH/src/github.com/TarsCloud/TarsGo/tars/tools chmod +x create_tars_server.sh ./create_tars_server.sh amc GoTarsServer GoTars
執行腳本後,在相應目錄下會生成必要的源文件:centos
$ cd ~/go/src/amc/GoTarsServer $ ls -l total 36 -rw-rw-r-- 1 centos centos 159 Jan 7 00:00 GoTars.tars -rw-rw-r-- 1 centos centos 303 Jan 7 00:00 gotars_imp.go -rw-rw-r-- 1 centos centos 964 Jan 7 00:00 config.conf -rw-rw-r-- 1 centos centos 422 Jan 7 00:00 main.go drwxrwxr-x 2 centos centos 4096 Jan 7 00:00 client drwxrwxr-x 2 centos centos 4096 Jan 7 00:00 debugtool -rw-rw-r-- 1 centos centos 252 Jan 7 00:00 makefile -rw-rw-r-- 1 centos centos 59 Jan 7 00:00 start.sh drwxrwxr-x 2 centos centos 4096 Jan 7 00:00 vendor
其中 GoTars.tars
文件,咱們就不須要了,用上面的 DateTime.tars
文件替換之。接着,咱們使用 TarsGo 的工具,將協議文件轉換爲源文件:服務器
cd ~/go/src/amc/GoTarsServer tars2go DateTime.tars
執行後,tars2go
會在當前目錄下,根據 .tars
文件中指定的 module
字段,生成一個新的目錄。好比上面的協議文件,module
是 amc
,那麼 tars2go
就生成 amc
目錄。讀者能夠自行查看目錄下的文件,若是 .tars
文件更新的話,須要再次執行 tars2go
命令刷新相應的文件——固然,我以爲徹底能夠調整 makefile
的邏輯來自動實現這一點。app
實現協議
協議的實現,在 gotars_imp.go
文件中實現。下面我只列出該文件中實現的主要部分:
package main import ( "fmt" "time" "context" "github.com/TarsCloud/TarsGo/tars" amc "amc/GoTarsServer/amc" ) // GoTarsImp servant implementation type GoTarsImp struct {} // 獲取log對象 var log = tars.GetLogger("logic") func (imp *GoTarsImp) GetTime(ctx context.Context, req *amc.GetTimeReq, rsp *amc.GetTimeRsp) (int32, error) { // Note 4 // get timestamp utc_time := time.Now() local_time := utc_time.Local() // convert time string var time_str string if "" == (*req).TimeFmt { log.Debug("Use default time format") time_str = local_time.Format("01/02 15:04:05 2006") } else { log.Debug(fmt.Sprintf("Got format string: %s", (*req).TimeFmt)) // ...... // ...... time_str = local_time.Format(time_str) } // construct response (*rsp).UtcTimestamp = utc_time.Unix() (*rsp).LocalTimestamp = local_time.Unix() (*rsp).LocalTimeStr = time_str return 0, nil };
針對代碼裏的幾個註釋說明以下:
- 這裏導入的包,就是前文
tars2go
所生成的amc
目錄下的go
文件。經過導入該包,咱們就能夠 access 到咱們在前面的.tars
文件中所定義的結構體和方法。這裏實際上是寫了一個基於$GOPATH
的絕對路徑來存取該包。 - 定義了該 servant 的對象,供 server 調用——這個後文講到 server 時會再提到。
- 使用 TARS 自帶的服務器本地日誌模塊。該模塊須要傳入一個文件名參數,模塊會根據該文件名,在
/usr/local/app/tars/app_log/amc/GoTarsServer/
目錄下生成日誌文件。好比我用的 log 文件名就是:amc.GoTarsServer_logic.log
。 - 這是
.tars
文件中GetTime
的實現,它做爲GoTarsImp
對象的一個方法來實現。從返回值的角度,TarsGo RPC 方法的返回值除了協議中定義的(本例中是int
,對應於 Go 的int32
)以外,還有一個error
,若是須要的話,讀者能夠利用。
變量 / 方法名轉換
細心的讀者可能會發現,在上面的實現中,數據變量名和協議中定義的並不相同。是的,這就是剛轉 Go 的開發者很容易遇到的坑之一:Go 語言是使用變量 / 方法 / 常量的命名方式來決定其可見性的,只有在首字母爲大寫的時候,該元素才能供外部訪問。 筆者特地在 .tars
文件中,變量名採用了首字母小寫的駝峯式命名法。讀者能夠看到,tars2go
會自動將變量名和方法名的首字母改成大寫,以保證其可見性。請開發者注意,不然會在編譯時遇到未定義錯誤。
server 代碼調用
如今讓咱們回到 main.go
文件。其實工具自動生成的代碼就差很少了,須要修改的是包導入的部分和新建app
對象語句,改成以下所示:
import ( "fmt" "os" "github.com/TarsCloud/TarsGo/tars" amc "amc/GoTarsServer/amc" ) func main() { // Get server config cfg := tars.GetServerConfig() imp := new(GoTarsImp) err := imp.Init() if err != nil { fmt.Printf("GoTarsImp init fail, err:(%s)\n", err) os.Exit(-1) } // 默認爲amc.GoTars,因爲使用了DateTime.tars,須要修改成amc.DateTime app := new(amc.DateTime) app.AddServantWithContext(imp, cfg.App+"."+cfg.Server+".GoTarsObj") tars.Run() }
其餘不變。
客戶端代碼設計
這裏我選擇了上一篇文章中提到的 GoWebServer
來調用這個 TARS 服務。這裏咱們就須要將已有的代碼進行改造了。須要改造的代碼是 goweb_imp.go
文件 :
package main import ( "fmt" "net/http" "github.com/TarsCloud/TarsGo/tars" amc "amc/GoTarsServer/amc" ) func HttpRootHandler(w http.ResponseWriter, r *http.Request) { comm := tars.NewCommunicator() app := new(amc.DateTime) obj := "amc.GoTarsServer.GoTarsObj" comm.SetProperty("locator", "tars.tarsregistry.QueryObj@tcp -h 192.168.211.128 -p 17890") req := amc.GetTimeReq{} rsp := amc.GetTimeRsp{} req.TimeFmt = "" comm.StringToProxy(obj, app) _, err := app.GetTime(&req, &rsp) if err != nil { // ...... 系統錯誤處理 } else { // ...... 從 rsp 中取出問題 } ret_str := fmt.Sprintf("{\"msg\":\"Hello, Tars-Go!\", \"time\":\"%s\"}", rsp.LocalTimeStr) w.Header().Set("Content-Type", "application/json;charset=utf-8") w.Write([]byte(ret_str)) return }
主要邏輯的說明以下:
- 選擇路由,這裏讀者能夠參照官方 Quick Start 文檔的說明來解釋。這裏筆者就只採用正式用法。其中
192.168.211.128
和17890
是 TARS 主控tarsregistry
的地址,能夠經過TarsWeb界面查看 - 準備用於承載參數和返回值的結構體
- 這兩行就是實際的 RPC 調用
發佈服務
服務發佈的方法在前一篇文章已經說明了。GoWebServer
只須要在原有基礎上作更新操做便可。本文的 GoTarsServer
也同理。不一樣的是在 協議
選項,應該選擇 TARS
。
服務發佈、一切正常後,參照上一篇文章,再次訪問 HTTP 服務,而後咱們再查看 GoTarsServer
的 log,咱們就能夠看到二者已經成功地聯繫起來啦。
總結
TARS能夠在考慮到易用性和高性能的同時快速構建系統並自動生成代碼,幫助開發人員和企業以微服務的方式快速構建本身穩定可靠的分佈式應用,從而令開發人員只關注業務邏輯,提升運營效率。多語言、敏捷研發、高可用和高效運營的特性使 TARS 成爲企業級產品。
TARS微服務助您數字化轉型,歡迎訪問:
TARS官網:https://TarsCloud.org
TARS源碼:https://github.com/TarsCloud
獲取《TARS官方培訓電子書》:https://wj.qq.com/s2/6570357/3adb/
或掃碼獲取: