CloudWeGo开源社区 kitex-contrib/config-etcd 代码学习

121 阅读2分钟

config-etcd

config-etcd模块,使用etcd作为配置中心,对client端和server端分别进行配置,便于项目管理,使得在不修改项目代码的情况下能够更新配置。

config-etcdclient端实现了4项配置:

// Options return a list client.Option
func (s *EtcdClientSuite) Options() []client.Option {
    opts := make([]client.Option, 0, 7)
    opts = append(opts, WithRetryPolicy(s.service, s.client, s.etcdClient, s.uid, s.opts)...)
    opts = append(opts, WithRPCTimeout(s.service, s.client, s.etcdClient, s.uid, s.opts)...)
    opts = append(opts, WithCircuitBreaker(s.service, s.client, s.etcdClient, s.uid, s.opts)...)
    opts = append(opts, WithDegradation(s.service, s.client, s.etcdClient, s.uid, s.opts)...)
    return opts
}

server端实现了一项配置:

func (s *EtcdServerSuite) Options() []server.Option {
    opts := make([]server.Option, 0, 2)
    opts = append(opts, WithLimiter(s.service, s.etcdClient, s.uid, s.opts))
    return opts
}

作为ketix的一个补充库,config-etcd使用了 "github.com/cloudwego/kitex/client" 的WithSuite方法来进行配置传递:

// WithSuite adds an option suite for client.
func WithSuite(suite Suite) Option {
    return Option{F: func(o *client.Options, di *utils.Slice) {
       var nested struct {
          Suite   string
          Options utils.Slice
       }
       nested.Suite = fmt.Sprintf("%T(%+v)", suite, suite)

       for _, op := range suite.Options() {
          op.F(o, &nested.Options)
       }
       di.Push(nested)
    }}
}

client配置

对于client端的配置过程如下:

type configLog struct{}

func (cl *configLog) Apply(opt *utils.Options) {
    fn := func(k *etcd.Key) {
       klog.Infof("etcd config %v", k)
    }
    opt.EtcdCustomFunctions = append(opt.EtcdCustomFunctions, fn)
}

func main() {
    etcdClient, err := etcd.NewClient(etcd.Options{})
    if err != nil {
       panic(err)
    }

    cl := &configLog{}

    serviceName := "ServiceName" // your server-side service name
    clientName := "ClientName"   // your client-side service name
    client, err := echo.NewClient(
       serviceName,
       client.WithHostPorts("0.0.0.0:8888"),
       client.WithSuite(etcdclient.NewSuite(serviceName, clientName, etcdClient, cl)),
    )
    if err != nil {
       log.Fatal(err)
    }
    for {
       req := &api.Request{Message: "my request"}
       resp, err := client.Echo(context.Background(), req)
       if err != nil {
          klog.Errorf("take request error: %v", err)
       } else {
          klog.Infof("receive response %v", resp)
       }
       time.Sleep(time.Second * 10)
    }
}

首先,用户可以创建自己的configLog结构并实现一个Apply方法,在生成配置文件的过程中将自定义结构传递给etcdclient.NewSuite,用于记录配置日志。 第一步要传递配置参数:

etcdClient, err := etcd.NewClient(etcd.Options{})

Options结构如下:

// Options etcd config options. All the fields have default value.
type Options struct {
    Node             []string 
    Prefix           string
    ServerPathFormat string
    ClientPathFormat string
    Timeout          time.Duration
    LoggerConfig     *zap.Config
    ConfigParser     ConfigParser
}
  • NodeEtcd节点,默认值为 "http://127.0.0.1:2379"
  • Prefix为配置文件前缀,默认值为:"/KitexConfig"
  • ServerPathFormatserver路径,默认值为:"{{.ServerServiceName}}/{{.Category}}"
  • ClientPathFormatclient路径,默认值为:"{{.ServerServiceName}}/{{.Category}}"
  • Timeout为超时时长,默认值为:5 * time.Second
  • ConfigParser是一个Config配置解析器,默认值如下:
type parser struct{}

// Decode decodes the data to struct in specified format.
func (p *parser) Decode(data string, config interface{}) error {
    return json.Unmarshal([]byte(data), config)
}

此外需要注意的是ServerPathFormat和ClientPathFormat是一个包含占位符的模板字符串,然后通过创建一个结构体 data 来给占位符 {{.ClientServiceName}}{{.ServerServiceName}}{{.Category}} 提供实际值。最后,利用 text/template 包中的方法替换占位符,生成最终的字符串结果。

最后,通过NewSuite方法生成配置,并传递给kitex.clientWithSuite,后者会调用前者返回的结构的Options方法,生成最终配置项。

server配置

server端的配置过程与client端基本相同,唯一不同的在于,server端使用etcdServer.NewSuite

type EchoImpl struct{}

// Echo implements the Echo interface.
func (s *EchoImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) {
    klog.Info("echo called")
    return &api.Response{Message: req.Message}, nil
}

func main() {
    klog.SetLevel(klog.LevelDebug)
    serviceName := "ServiceName" // your server-side service name
    etcdClient, _ := etcd.NewClient(etcd.Options{})
    svr := echo.NewServer(
       new(EchoImpl),
       server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: serviceName}),
       server.WithSuite(etcdServer.NewSuite(serviceName, etcdClient)),
    )
    if err := svr.Run(); err != nil {
       log.Println("server stopped with error:", err)
    } else {
       log.Println("server stopped")
    }
}