Kite: 一個分散式微服務框架(翻譯)

4ma2ingZed發表於2020-07-15

原文連結: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伺服器。

所以說搭建一個有許多應用的分散式系統比較難。KodingKite庫旨在以一種簡單快捷輕便的方式搭建分散式微服務應用。Kite框架本身有很多細節部分,在這篇文章中只會大概闡述Kite能幹什麼。

Kite介紹

Kite是一個用GO語言編寫的微服務RPC框架,它使得使用者能編寫清晰易懂的分散式系統。它在便捷使用和效能之間找到了一個平衡。Kite既是一個RPC伺服器又是客戶端。它能與其它的Kite同伴進行雙向通訊。一個Kite節點由以下引數確定(順序很重要):

  1. Username: Kite的擁有者,比如 Brian, Fatih, Damian etc..
  2. Environment: 當前環境,比如 “production”, “testing”, “staging”, etc…
  3. Name: 標識Kite型別的簡稱,比如 mykite,fs,terminal, etc…
  4. Version: 三位數的語義版本(semantic version)
  5. Region: 當前地區,比如 “Europe”, “Asia” 或其他地方
  6. Hostname: Kite的hostname
  7. 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)並隨意使用。歡迎貢獻!讓我知道你的想法。

相關文章