構建屬於你自己的 dapr 服務發現
寫在最前: 這篇文章其實算是馬後炮了,因爲一直拖延症的問題,順帶過了一個五一假期,結果發現已經有社區貢獻者提供了 Consul 的服務發現實現,於是本來寫了一半的文章只能進行調整了。拖延症害人啊!幾個草稿的文章看來要儘快趕出來了🤦♂️
在上一篇文章中,我其實遺留了一個問題:如何定義 dapr 的服務發現呢?其實在後面閱讀 dapr 的源碼之後也前一篇文章的評論中提到了答案:目前 dapr 提供了內置兩種服務發現模式:K8s 模式和用於獨立部署的 mDNS 模式。mDNS 模式在某些網絡環境下可能存在問題(比如跨機房),不過沒有關係,dapr 同時提供了可擴展能力,可以通過定義自主的服務發現能力擴展 dapr 的邊界。
從 NameResolution 到 Resolver 接口
在 pkg/components/nameresolution/registry.go
文件中,dapr 定義了一個 NameResolution
結構體用於服務註冊和發現:
type (
// NameResolution is a name resolution component definition.
NameResolution struct {
Name string
FactoryMethod func() nr.Resolver
}
// Registry handles registering and creating name resolution components.
Registry interface {
Register(components ...NameResolution)
Create(name, version string) (nr.Resolver, error)
}
nameResolutionRegistry struct {
resolvers map[string]func() nr.Resolver
}
)
其中真正的服務解析則是依靠 components-contrib
中實現了 Resolver
接口的具體實現執行。
// Resolver is the interface of name resolver.
type Resolver interface {
// Init initializes name resolver.
Init(metadata Metadata) error
// ResolveID resolves name to address.
ResolveID(req ResolveRequest) (string, error)
}
其中 Init
會在 Runtime 初始化時被調用,而 ResolveID
則會在服務查詢時調用。比如在 pkg/messaging/direct_messaging.go
的方法 getRemoteApp
中進行服務的解析:
func (d *directMessaging) getRemoteApp(appID string) (remoteApp, error) {
id, namespace, err := d.requestAppIDAndNamespace(appID)
if err != nil {
return remoteApp{}, err
}
request := nr.ResolveRequest{ID: id, Namespace: namespace, Port: d.grpcPort}
address, err := d.resolver.ResolveID(request)
if err != nil {
return remoteApp{}, err
}
return remoteApp{
namespace: namespace,
id: id,
address: address,
}, nil
}
當然,事實上這樣並不完全足夠,還需要把這個服務註冊放入 dapr 支持的服務中去:
runtime.WithNameResolutions(
nr_loader.New("mdns", func() nr.Resolver {
return nr_mdns.NewResolver(logContrib)
}),
nr_loader.New("kubernetes", func() nr.Resolver {
return nr_kubernetes.NewResolver(logContrib)
}),
nr_loader.New("consul", func() nr.Resolver {
return nr_consul.NewResolver(logContrib)
}),
),
上面的這些是設定的 dpar 目前支持的一些服務發現功能,而我們之前服務發現也一直使用的 Consul 實現,已經滿足我們的需求了…😓拖延症害人啊!
從原理到實現
上面提到了我們需要實現一個 Resolver
接口的實現,我們可以預見到我們大概會需要這麼一個東西:
type resolver struct {}
// NewResolver creates Consul name resolver.
func NewResolver() nr.Resolver
// Init will configure component. It will also register service or validate client connection based on config
func (r *resolver) Init(metadata nr.Metadata) error
// ResolveID resolves name to address via consul
func (r *resolver) ResolveID(req nr.ResolveRequest) (string, error)
接下來就需要一個 client *consul.Client
去實現服務的註冊:
type resolver struct {
client *consul.Client
}
func (r *resolver) Init(metadata nr.Metadata) error {
// ...
if err := r.client.Agent().ServiceRegister(regData); err != nil {
return fmt.Errorf("failed to register consul service: %w", err)
}
// ...
}
註冊服務完成後,在調用具體的服務時,我們需要獲取具體的服務地址:
func (r *resolver) ResolveID(req nr.ResolveRequest) (string, error) {
// ...
services, _, err := r.client.Health().Service(req.ID, "", true, cfg.QueryOptions)
// ...
}
當然上面的演示代碼只是部分核心功能代碼,如果需要拓展更多的實現細節內容,需要查看具體的官方接收社區貢獻的實現:components-contrib/nameresolution/consul
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://www.4async.com/2021/05/building-your-own-dapr-service-discovery/