config-etcd
config-etcd模块,使用etcd作为配置中心,对client端和server端分别进行配置,便于项目管理,使得在不修改项目代码的情况下能够更新配置。
config-etcd对client端实现了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
}
Node为Etcd节点,默认值为 "http://127.0.0.1:2379"Prefix为配置文件前缀,默认值为:"/KitexConfig"ServerPathFormat为server路径,默认值为:"{{.ServerServiceName}}/{{.Category}}"ClientPathFormat为client路径,默认值为:"{{.ServerServiceName}}/{{.Category}}"Timeout为超时时长,默认值为:5 * time.SecondConfigParser是一个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.client的WithSuite,后者会调用前者返回的结构的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")
}
}