[ CloudWeGo 微服务实践 - 08 ] Nacos 服务发现扩展 (2)
- 2021 年 11 月 15 日
本文字数:4482 字
阅读完需:约 15 分钟
昨天的《 [ CloudWeGo 微服务实践 - 08 ] Nacos 服务发现扩展 (1)》 中,我们准备好了所有的开发环境。今天就准备封装这个组件。
目标
封装一个以 Nacos 为注册中心的服务发现扩展
环境
MacOS Monterey 12.0.1
go version go1.16.5 darwin/amd64
Nacos 2.0.3
步骤
服务注册
我们先回复一下之前怎么做服务注册的>> [ CloudWeGo 微服务实践 - 05 ] 服务注册(2),作为简单回顾之后,我们尝试重新做一下 Nacos 即可。
创建文件
mkdir registry
cd registry
touch registry.go
实现接口
package registry
import (
"log"
"github.com/cloudwego/kitex/pkg/registry"
)
type nacosRegistry struct{}
func NewNacosRegistry() (registry.Registry, error) {
return &nacosRegistry{}, nil
}
var _ registry.Registry = (*nacosRegistry)(nil)
func (n *nacosRegistry) Register(info *registry.Info) error {
log.Printf("Register info:%v", info)
return nil
}
func (n *nacosRegistry) Deregister(info *registry.Info) error {
log.Printf("Deregister info:%v", info)
return nil
}
引入 Nacos SDK
nacosRegistry 对象中我们增加 nacos 客户端连接的属性,注入依赖
type nacosRegistry struct {
cli naming_client.INamingClient
}
记得在 import 中添加(IDE 也会自动添加)依赖包
"github.com/nacos-group/nacos-sdk-go/clients/naming_client"
并修改创建实例的 New 方法
func NewNacosRegistry(cli naming_client.INamingClient) (registry.Registry, error) {
return &nacosRegistry{cli: cli}, nil
}
整体如下:
package registry
import (
"log"
"github.com/cloudwego/kitex/pkg/registry"
"github.com/nacos-group/nacos-sdk-go/clients/naming_client"
)
type nacosRegistry struct {
cli naming_client.INamingClient
}
func NewNacosRegistry(cli naming_client.INamingClient) (registry.Registry, error) {
return &nacosRegistry{cli: cli}, nil
}
var _ registry.Registry = (*nacosRegistry)(nil)
func (n *nacosRegistry) Register(info *registry.Info) error {
log.Printf("Register info:%v", info)
return nil
}
func (n *nacosRegistry) Deregister(info *registry.Info) error {
log.Printf("Deregister info:%v", info)
return nil
}
Register 单元测试
我们直接用 Goland 的工具生成相关代码
func Test_nacosRegistry_Register(t *testing.T) {
sc := []constant.ServerConfig{
*constant.NewServerConfig("127.0.0.1", 8848),
}
cc := constant.ClientConfig{
NamespaceId: "public",
TimeoutMs: 5000,
NotLoadCacheAtStart: true,
LogDir: "/tmp/nacos/log",
CacheDir: "/tmp/nacos/cache",
RotateTime: "1h",
MaxAge: 3,
LogLevel: "debug",
}
client, err := clients.NewNamingClient(
vo.NacosClientParam{
ClientConfig: &cc,
ServerConfigs: sc,
},
)
if err != nil {
t.Errorf("err:%v", err)
return
}
type fields struct {
cli naming_client.INamingClient
}
type args struct {
info *registry.Info
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "common",
fields: fields{client},
args: args{info: ®istry.Info{
ServiceName: "demo.baiyutang.local",
Addr: &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 8848},
Weight: 999,
StartTime: time.Now(),
Tags: map[string]string{"env": "local"},
}},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
n := &nacosRegistry{
cli: tt.fields.cli,
}
if err := n.Register(tt.args.info); (err != nil) != tt.wantErr {
t.Errorf("Register() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
运行测试用例,我们留意到如下日志:
完善 Register 实现
func (n *nacosRegistry) Register(info *registry.Info) error {
if info.ServiceName == "" {
return fmt.Errorf("registry.Info cannot is empty")
}
host, port, err := net.SplitHostPort(info.Addr.String())
if err == nil {
return fmt.Errorf("parse registry info addr error")
}
p, err := strconv.Atoi(port)
if err != nil {
return err
}
_, e := n.cli.RegisterInstance(vo.RegisterInstanceParam{
Ip: host,
Port: uint64(p),
ServiceName: info.ServiceName,
Weight: float64(info.Weight),
Enable: true,
Healthy: true,
Ephemeral: true,
Metadata: info.Tags,
})
if e != nil {
return fmt.Errorf("RegisterInstance err:%v", e)
}
return nil
}
重新运行 Register 单元测试
看到 PASS
,就是好消息了,代码运行正常。
验证注册数据
我们直接 curl 查一下 Nanos 的数据:
curl -X GET 'http://127.0.0.1:8848/nacos/v1/ns/instance/list?serviceName=demo.baiyutang.local'
成功了。
Deregister 单元测试
还按照刚刚的方法创建用例:
这次我们把创建 Nacos 客户端连接的代码简单封装起来,不用每次每个用例都复制好长的代码:
func getNacosClient() (naming_client.INamingClient, error) {
sc := []constant.ServerConfig{
*constant.NewServerConfig("127.0.0.1", 8848),
}
cc := constant.ClientConfig{
NamespaceId: "public",
TimeoutMs: 5000,
NotLoadCacheAtStart: true,
LogDir: "/tmp/nacos/log",
CacheDir: "/tmp/nacos/cache",
RotateTime: "1h",
MaxAge: 3,
LogLevel: "debug",
}
return clients.NewNamingClient(
vo.NacosClientParam{
ClientConfig: &cc,
ServerConfigs: sc,
},
)
}
继续完善用例:
func Test_nacosRegistry_Deregister(t *testing.T) {
client, err := getNacosClient()
if err != nil {
t.Errorf("err:%v", err)
return
}
type fields struct {
cli naming_client.INamingClient
}
type args struct {
info *registry.Info
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "common",
args: args{info: ®istry.Info{
ServiceName: "demo.baiyutang.local",
Addr: &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 8848},
Weight: 999,
StartTime: time.Now(),
Tags: map[string]string{"env": "local"},
}},
fields: fields{client},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
n := &nacosRegistry{
cli: tt.fields.cli,
}
if err := n.Deregister(tt.args.info); (err != nil) != tt.wantErr {
t.Errorf("Deregister() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
运行 Deregister 单元测试
看到如下日志,就是我们刚刚方法中打的日志,我们继续完善代码
完善 Deregister 实现
func (n *nacosRegistry) Deregister(info *registry.Info) error {
host, port, err := net.SplitHostPort(info.Addr.String())
if err != nil {
return err
}
p, err := strconv.Atoi(port)
if err != nil {
return err
}
if _, err = n.cli.DeregisterInstance(vo.DeregisterInstanceParam{
Ip: host,
Port: uint64(p),
ServiceName: info.ServiceName,
Ephemeral: true,
}); err != nil {
return err
}
return nil
}
验证注销服务
因为 Nacos 检查健康检查机制,会屏蔽不健康的实例,所以可能注册的实例存在一段时间后,我们用 curl 的方式会自动过期。在下面的注销测试时,需要提前跑一下注册的逻辑。调试期间,先简单手动验证。
服务发现
创建文件
mkdir resolver
cd resolver
touch resolver.go
实现接口
package resolver
import (
"context"
"github.com/cloudwego/kitex/pkg/discovery"
"github.com/cloudwego/kitex/pkg/rpcinfo"
)
type nacosResolver struct {
}
func NewNacosResolver() discovery.Resolver {
return &nacosResolver{}
}
func (n nacosResolver) Target(_ context.Context, target rpcinfo.EndpointInfo) (description string) {
}
func (n nacosResolver) Resolve(_ context.Context, desc string) (discovery.Result, error) {
}
func (n nacosResolver) Diff(cacheKey string, prev, next discovery.Result) (discovery.Change, bool) {
return discovery.DefaultDiff(cacheKey, prev, next)
}
func (n nacosResolver) Name() string {
return "nacos"
}
var _ discovery.Resolver = (*nacosResolver)(nil)
完善逻辑
package resolver
import (
"context"
"fmt"
"github.com/cloudwego/kitex/pkg/discovery"
"github.com/cloudwego/kitex/pkg/rpcinfo"
"github.com/nacos-group/nacos-sdk-go/clients/naming_client"
"github.com/nacos-group/nacos-sdk-go/vo"
)
type nacosResolver struct {
cli naming_client.INamingClient
}
func NewNacosResolver(cli naming_client.INamingClient) discovery.Resolver {
return &nacosResolver{cli: cli}
}
func (n nacosResolver) Target(_ context.Context, target rpcinfo.EndpointInfo) (description string) {
return target.ServiceName()
}
func (n nacosResolver) Resolve(_ context.Context, desc string) (discovery.Result, error) {
res, err := n.cli.SelectInstances(vo.SelectInstancesParam{
ServiceName: desc,
HealthyOnly: true,
})
if err != nil {
return discovery.Result{}, err
}
var instances []discovery.Instance
for _, in := range res {
instances = append(instances, discovery.NewInstance(
"tcp",
fmt.Sprintf("%s:%d", in.Ip, in.Port),
int(in.Weight),
in.Metadata),
)
}
return discovery.Result{
Cacheable: true,
CacheKey: desc,
Instances: instances,
}, nil
}
func (n nacosResolver) Diff(cacheKey string, prev, next discovery.Result) (discovery.Change, bool) {
return discovery.DefaultDiff(cacheKey, prev, next)
}
func (n nacosResolver) Name() string {
return "nacos"
}
var _ discovery.Resolver = (*nacosResolver)(nil)
总结
后期完善好单元测试再开源出来,稍安勿躁,近期一直再按照 CloudWeGo 的规范调试 GitHub Action。调试完就 PR。
版权声明: 本文为 InfoQ 作者【baiyutang】的原创文章。
原文链接:【http://xie.infoq.cn/article/bd6c6bf8bdf4835fed29f4b56】。文章转载请联系作者。
baiyutang
广州 2017.12.13 加入
Microservices | Golang | Cloud Nitive | “Smart work,Not hard”
评论