原文鏈接:https://blog.gopheracademy.com/birthday-bash-2014/kite-microservice-library/ 此為中文翻譯
轉載請注明出處:https://www.cnblogs.com/Ama2ingYJ/p/13307002.html
用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)並隨意使用。歡迎貢獻!讓我知道你的想法。