原文連結:https://blog.gopheracademy.com/birthday-bash-2014/kite-microservice-library/ 此為中文翻譯
用GO語言來編寫web服務是一件很輕鬆的事。簡單而又強大的net/http
包允許你以一種快速的方式編寫高效能的web服務。然而,有時候你僅僅想要編寫一個RPC後端應用。本質上,你想有很多獨立執行的應用程式,他們各自負責自己的那塊工作。他們應當接收請求並恰當的回覆。
很顯然,一旦脫離了基本的需求,事情就變得複雜了。在真實場景中,你可能擁有數百個正在執行的web服務,並希望能和他們安全的(並經過身份驗證)通訊交流。為了達成這一目的,首先必須與某一個應用建立連線。除非你只有很少的幾個應用節點,你很難記住某個特定應用的IP地址或hostname(有太多應用)。僅僅把所有host的IP地址持久化儲存也是不夠的,因為host IP可能改變。你需要的是一個能讓你訪問、詢問並取得某應用IP地址的服務,就像DNS伺服器。
所以說搭建一個有許多應用的分散式系統比較難。Koding的Kite庫旨在以一種簡單快捷輕便的方式搭建分散式微服務應用。Kite框架本身有很多細節部分,在這篇文章中只會大概闡述Kite能幹什麼。
Kite介紹
Kite是一個用GO語言編寫的微服務RPC框架,它使得使用者能編寫清晰易懂的分散式系統。它在便捷使用和效能之間找到了一個平衡。Kite既是一個RPC伺服器又是客戶端。它能與其它的Kite同伴進行雙向通訊。一個Kite節點由以下引數確定(順序很重要):
- Username: Kite的擁有者,比如 Brian, Fatih, Damian etc..
- Environment: 當前環境,比如 “production”, “testing”, “staging”, etc…
- Name: 標識Kite型別的簡稱,比如 mykite,fs,terminal, etc…
- Version: 三位數的語義版本(semantic version)
- Region: 當前地區,比如 “Europe”, “Asia” 或其他地方
- Hostname: Kite的hostname
- ID: 唯一ID,用來確定一個Kite。由Kite框架生成,也可以自行更改
這些識別符號很重要因為Kite就是通過他們來讓他人鑑別和搜尋自己。
Kite使用SockJS在很多不同傳輸方法(websocket, xhr, etc..)提供WebSocket模擬(emulation ),這意味著你也可以通過瀏覽器來連結Kite(見Kite.js)。Kite使用修改過的dnode protocal來進行RPC訊息傳遞。Kite協議增加了一個額外的session和authentication層,這樣就能輕鬆地識別Kite。在後臺,它使用JWT進行身份驗證和會話資訊管理。
通過“Kontrol”這個服務發現機制,一個Kite可以發現其他Kites與他們安全地進行身份驗證通訊。Kontrol使用etcd作為後臺儲存。但是你也用其他的替代(當前支援PostgreSQL),只要它實現了 kontrol.Storage介面。Kontrol同時也有許多認證使用者的方式。這是可定製的所以人們能用自己方式使用Kontrol。
如何使用Kite
我們現在來學習一下。編寫Kite並讓他們之間通訊很有趣。首先,介紹一個最簡單的形式(原諒我忽略了錯誤處理,你不應該像我這樣:))
package main
import "github.com/koding/kite"
func main() {
k := kite.New("first", "1.0.0")
k.Run()
}
這裡我們建立了一個kite,它的名字是first,版本是1.0.0。Run()
方法用來啟動一個阻塞伺服器(就像http.Serve
)。它現在能夠接受請求。由於沒分配埠號,作業系統會自動為我們分配一個。
現在我們分配一個埠號,這樣我們就能使另一個kite和他連線(否則,你需要從日誌中選擇分配的URL)。為了更改Kite配置,例如埠號,一些屬性(Environemt, Region, etc...),你需要更改Config
域:
package main
import "github.com/koding/kite"
func main() {
k := kite.New("first", "1.0.0")
k.Config.Port = 6000
k.Run()
}
如有需要,配置值也可以被環境變數覆蓋。
讓我們建立第二個kite來和第一個kite通訊:
package main
import (
"fmt"
"github.com/koding/kite"
)
func main() {
k := kite.New("second", "1.0.0")
client := k.NewClient("http://localhost:6000/kite")
client.Dial()
response, _ := client.Tell("kite.ping")
fmt.Println(response.MustString())
}
這回我們直接連線新的kite,因為我們已經知道了URL。對於一個RPC系統,你得有URL路徑的概念。Kite使用方法名來讓別人呼叫。每個方法對應一個Handle(就像http.Handler
)。Kite框架有一些預設的方法,其中一個就是kite.ping
,它返回一個pong
字串作為響應(他不需要任何身份驗證資訊)。響應可以是任何東西,從能被序列化的GO型別到JSON,這取決於傳送方。Kite也有一些預先定義好的輔助方法來把響應轉換成給定型別。在這個例子裡,second kite 和 first kite 連線並呼叫了first kite 的 kite.ping
方法。我們沒有傳遞任何引數(下面將解釋),所以如果你執行,可以看到:
$ go run second.go
pong
為Kite新增方法
現在我們新增第一個自定義方法。這個方法用來接收一個值並返回它的平方值。方法名字是square
。要將函式分配給方法,只需確保其滿足 kite.Handler
介面kite.Handler :
package main
import "github.com/koding/kite"
func main() {
k := kite.New("first", "1.0.0")
k.Config.Port = 6000
k.Config.DisableAuthentication = true
k.HandleFunc("square", func(r *kite.Request) (interface{}, error) {
a := r.Args.One().MustFloat64()
return a * a, nil
})
k.Run()
}
通過 second kite 呼叫:
package main
import (
"fmt"
"github.com/koding/kite"
)
func main() {
k := kite.New("second", "1.0.0")
client := k.NewClient("http://localhost:6000/kite")
client.Dial()
response, _ := client.Tell("square", 4)
fmt.Println(response.MustFloat64())
}
可以看到,唯一改變的是方法呼叫。呼叫 "square" 方法也傳遞了引數4。執行例子得到:
$ go run second.go
16
就這麼簡單。
服務發現,如何找到對方
服務發現被整合到了Kite框架中。就像前面所說,這是一個非常基本的概念,並且在Kite API也得到了充分體現。這意味著Kite框架強制使用者使用服務發現機制。為了能發現自己,對方要知道你的真實身份。也就是說你需要進行身份驗證。身份驗證可以通過多種方法完成,這取決於Kontrol怎麼執行。它可以被完全禁用,可以詢問使用者密碼(通過kite cli),可以獲取令牌並驗證使用者提供的內容等等。
kitectl
是一個方便的CLI程式,可用於通過命令列輕鬆管理kites。我們可以用它(通過 kitectl register
命令)來向Kontrol認證我們的host,所以I每個執行在我們host的kite例項將預設被驗證。這個命令在home目錄下建立kite.key
檔案,它由kontrol自己簽名認證。其中內容沒有加密,但是因為已簽名,所以可以用它和Kontrol安全交流。我們的使用者名稱會被儲存到Kontrol中,所以其他人可以信任我們(當然他們得使用同一個Kontrol伺服器)。相信Kontrol意味著可以相信任何人。這很重要因為可能會有其他的Kontrol伺服器,他們也在你的內網中或者是公開的。
我們將使用先前的例子,不過這次會把 first kite 註冊到Kontrol並從 second kite 取得它的IP地址:
package main
import (
"net/url"
"github.com/koding/kite"
)
func main() {
k := kite.New("first", "1.0.0")
k.Config.Port = 6000
k.HandleFunc("square", func(r *kite.Request) (interface{}, error) {
a := r.Args.One().MustFloat64()
return a * a, nil
})
k.Register(&url.URL{Scheme: "http", Host: "localhost:6000/kite"})
k.Run()
}
如你所見, 我們用Register()
方法把自己註冊到Kontrol中。唯一傳遞的引數時我們自己的URL,其他人可以通過它和我們連線。這個值儲存在Kontrol中,其他kite例項可以從那裡獲取到它。Register()
方法很特殊,如果你斷開連線並重連,它會自動重新註冊。為了保護Kontrol,我們使用了exponential backoff演算法進行重連嘗試。因為Koding在實際生產也大量是用它,所以有許多類似這樣的小細節小改進。另一點是註冊時你不需要傳遞Kontrol的URL,因為你已經通過驗證,Kontrol的URL被存放在kite.key
中了。你要做的僅僅是呼叫Register()
方法。
現在我們尋找first kite並呼叫其square
方法:
package main
import (
"fmt"
"github.com/koding/kite"
"github.com/koding/kite/protocol"
)
func main() {
k := kite.New("second", "1.0.0")
// search a kite that has the same username and environment as us, but the
// kite name should be "first"
kites, _ := k.GetKites(&protocol.KontrolQuery{
Username: k.Config.Username,
Environment: k.Config.Environment,
Name: "first",
})
// there might be several kites that matches our query
client := kites[0]
client.Dial()
response, _ := client.Tell("square", 4)
fmt.Println(response.MustFloat64())
}
首先GetKites()
方法獲取了一個list,其中包含匹配我們查詢的所有kites。GetKites()
連線到Kontrol並獲取所有URL匹配給定查詢的kites節點。該查詢必須採用樹路徑形式(與etcd中使用的格式相同),所以Username和Environment需要在你搜尋first kite之前給定。在這個例子中,我們假定只有一個匹配上了,接著取出它,撥號並呼叫方法,這樣就能得到和之前一樣的結果。
因此,動態註冊和獲取kites是一件大事。你可以設計一個分散式系統,它能容忍你定義的某些條件。一個例子是開啟10個first
kites,每個都以你的名字命名。如果另一個kite節點從Kontrol中獲取,它會得到一個包含10個kite節點及其URL的list,之後該怎麼做完全取決於這個kite例項。可以隨機挑選一個,也可以輪詢呼叫,抑或是ping所有list裡的kite節點並選取最快的一個等等。
這一切都交給了呼叫方。Kontrol並不知道某個kite例項會有什麼行為,它只知道該節點是否連線(註冊)上了。這樣的簡化讓使用者可以基於該框架構建更復雜的系統。
結論
Kite框架還有許多其它這裡沒涉及的小改進與特性。比如Kite.js可以在瀏覽器上作為客戶端使用。它還包含一個等效node.js的伺服器。它包含開箱即用的通道代理和反向代理,可用於在單個埠/應用後面多路複用kite。Koding正在實際生產中使用它,因此預設情況下它具有許多基於效能的修復和改進。
編寫Kite並使用它是最重要的部分。一旦開始使用它,你就可以感受到API的簡單性。Kite庫易於使用,因為它與Go具有相同的理念。它使用一些用Go編寫的最好的開源專案(例如etcd)。Go使編寫穩定平臺作為Kite庫的基礎變得簡單。由於Go的性質,擴充套件和改進Kite庫也很容易。
希望你對這個框架的想法和意圖及其功能和侷限性有所瞭解。我們正在廣泛使用和維護它。但是,我們也有很多事情想改進(例如提供其他訊息協議和傳輸協議)。盡情fork專案(https://github.com/koding/kite)並隨意使用。歡迎貢獻!讓我知道你的想法。