最近在用golang 做一个系统,里面有一个需求是可以随时暂停某项功能,这个涉及到运行时动态改变配置。首先配置写死在代码中,是绝对不可能实现这个需求的,每次从数据获取,虽说可以解决这个问题,但是耗时,每次请求都要去查数据库。有人会说引入缓存保存从数据库查询的值,定时去同步数据中的值到缓存。这是一个可行的办法,但是用etcd3的watcher会达到更加高效,优雅。etcd是一个高可用的键值存储系统,主要用于共享配置和服务发现.。 etcd 里面有个watcher 可以监视某个key, 如果key中的value有改变,会监听到这个改变事件,然后刷新etcd种的最新版本到本地缓存。下面上代码分析:
const(
etcdKey = "/prod/xxx/config" // 缓存的key,也是watcher监听的key
)
var (
....................................
.....................................
confVals map[string]interface{} // 配置的本地缓存,防止etcd宕机,也不影响业务,当watcher监听的key的value变化,会刷新缓存到这个map中。
lock sync.RWMutex // 读写锁
)
// 启动etcd
func start() {
.............
cli, err := clientv3.New(clientv3.Config{
Endpoints: parseEndpoints(*etcdEndpoints),
DialTimeout: time.Duration(*etcdDialTimeout) * time.Second,
})
...........
defer cli.Close()
if err := fetchConfig(cli); err != nil {
glog.Errorf("Fail to fetchConfig from etcd3")
}
watchConfigChanges(cli)
}
// 解析etcd 配置
func parseEndpoints(csv string) []string {
eps := []string{}
for _, v := range strings.Split(csv, ",") {
if v != "" {
eps = append(eps, v)
}
}
return eps
}
func fetchConfig(cli *clientv3.Client) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(*etcdOpTimeout)*time.Millisecond)
resp, err := cli.Get(ctx, etcdKey)
cancel()
if err != nil {
return err
}
for _, kv := range resp.Kvs {
if err := updateConfigVals(kv.Value); err != nil {
return err
}
break
}
return nil
}
// watcher 监听指定的key, key 发送变化更新confVals
func watchConfigChanges(cli *clientv3.Client) {
rch := cli.Watch(context.Background(), etcdKey)
for wresp := range rch {
for _, ev := range wresp.Events {
if string(ev.Kv.Key) != etcdKey || ev.Type != mvccpb.PUT {
continue
}
if err := updateConfigVals(ev.Kv.Value); err != nil {
glog.Errorf("Failed to update config values, %+v", err)
}
break
}
}
}
func updateConfigVals(b []byte) error {
m := map[string]interface{}{}
'''''''''''
'''''''''''
更新confVals
(&lock).Lock()
confVals = m
return nil
}