Kratos 读源码笔记一(配置加载)
2021/8/27 17:36:05
本文主要是介绍Kratos 读源码笔记一(配置加载),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
从入口文件看配置(初始化/加载/绑定/热加载)
main.go
//main.go 初始化配置 c := config.New( config.WithSource( file.NewSource(flagconf), //文件配置源 //也可以自己实现远程配置中心数据源 ), ) //加载配置数据 if err := c.Load(); err != nil { panic(err) } var bc conf.Bootstrap //将配置绑定到数据结构 if err := c.Scan(&bc); err != nil { panic(err) } // watch key if err := c.Watch("service.name", func(key string, value config.Value) { log.Printf("config changed: %s = %v\n", key, value) }); err != nil { panic(err) }
由入口文件可以看到,首先声明了数据源,再使用Kratos的配置接口加载/绑定/监控等操作,大胆猜测,c.Load() - c.Scan(&bc) - c.Watch() 一定调用了配置源的相关方法
先来看看实现一个配置源需要实现哪些接口
//config/source.go //通过config.WithSource 知道了 配置来源需要实现以下两个方法 type Source interface { Load() ([]*KeyValue, error) Watch() (Watcher, error) }
暂时不去看配置源的具体实现,回到入口文件,看看 c.Load() 的具体实现
func (c *config) Load() error { for _, src := range c.opts.sources { kvs, err := src.Load() //这里验证了我们的猜测,调用了具体配置源的 Load() 方法 if err != nil { return err } if err := c.reader.Merge(kvs...); err != nil { c.log.Errorf("failed to merge config source: %v", err) return err } w, err := src.Watch() //调用了具体配置源的 Watch() 方法 if err != nil { c.log.Errorf("failed to watch config source: %v", err) return err } c.watchers = append(c.watchers, w) go c.watch(w) //此处开启了一个协程,处理热加载 } if err := c.reader.Resolve(); err != nil { c.log.Errorf("failed to resolve config source: %v", err) return err } return nil }
c.Load() 中开启了一个协程监控配置源变更,稍后我们看看这个具体实现,先看 c.Watch() 中发生了什么
func (c *config) Watch(key string, o Observer) error { if v := c.Value(key); v.Load() == nil { return ErrNotFound } //将要监控的配置假如观察者中 c.observers.Store(key, o) return nil }
好,现在回过头去看上面提到的协程
func (c *config) watch(w Watcher) { for { kvs, err := w.Next() if errors.Is(err, context.Canceled) { c.log.Infof("watcher's ctx cancel : %v", err) return } if err != nil { time.Sleep(time.Second) c.log.Errorf("failed to watch next config: %v", err) continue } if err := c.reader.Merge(kvs...); err != nil { c.log.Errorf("failed to merge next config: %v", err) continue } if err := c.reader.Resolve(); err != nil { c.log.Errorf("failed to resolve next config: %v", err) continue } c.cached.Range(func(key, value interface{}) bool { k := key.(string) v := value.(Value) if n, ok := c.reader.Value(k); ok && !reflect.DeepEqual(n.Load(), v.Load()) { v.Store(n.Load()) if o, ok := c.observers.Load(k); ok { o.(Observer)(k, v) } } return true }) } }
以上主要能看到 Kratos 的配置是怎样加载和监控的了,具体的细节还需要去看每一个方法的实现。这里我们主要讨论,怎样实现配置源
以文件配置源举例:
实现 Source 接口即可。
type Source interface { Load() ([]*KeyValue, error) Watch() (Watcher, error) }
具体实现:
Load() ([]*KeyValue, error)
config/file/file.go
//本地文件或远程配置中心只要实现以上两个方法就可以,以本地文件配置为例 func (f *file) Load() (kvs []*config.KeyValue, err error) { fi, err := os.Stat(f.path) if err != nil { return nil, err } if fi.IsDir() { return f.loadDir(f.path) } kv, err := f.loadFile(f.path) if err != nil { return nil, err } return []*config.KeyValue{kv}, nil } //先来看loadFile func (f *file) loadFile(path string) (*config.KeyValue, error) { file, err := os.Open(path) if err != nil { return nil, err } defer file.Close() data, err := ioutil.ReadAll(file) if err != nil { return nil, err } info, err := file.Stat() if err != nil { return nil, err } return &config.KeyValue{ Key: info.Name(), Format: format(info.Name()), Value: data, }, nil } //loadDir 读取里循环调用了loadFile, 不支持子目录和隐藏文件 func (f *file) loadDir(path string) (kvs []*config.KeyValue, err error) { files, err := ioutil.ReadDir(f.path) if err != nil { return nil, err } for _, file := range files { // ignore hidden files if file.IsDir() || strings.HasPrefix(file.Name(), ".") { continue } kv, err := f.loadFile(filepath.Join(f.path, file.Name())) if err != nil { return nil, err } kvs = append(kvs, kv) } return }
Watch() (Watcher, error)
config/file/file.go
func (f *file) Watch() (config.Watcher, error) { return newWatcher(f) }
config/source.go Watcher的定义如下
// Watcher watches a source for changes. type Watcher interface { Next() ([]*KeyValue, error) Stop() error }
config/file/watcher.go
//Next() ([]*KeyValue, error) 实现 func (w *watcher) Next() ([]*config.KeyValue, error) { select { case <-w.ctx.Done(): return nil, w.ctx.Err() case event := <-w.fw.Events: if event.Op == fsnotify.Rename { if _, err := os.Stat(event.Name); err == nil || os.IsExist(err) { if err := w.fw.Add(event.Name); err != nil { return nil, err } } } fi, err := os.Stat(w.f.path) if err != nil { return nil, err } path := w.f.path if fi.IsDir() { path = filepath.Join(w.f.path, filepath.Base(event.Name)) } kv, err := w.f.loadFile(path) if err != nil { return nil, err } return []*config.KeyValue{kv}, nil case err := <-w.fw.Errors: return nil, err } } //Stop() error 实现 func (w *watcher) Stop() error { w.cancel() return w.fw.Close() } func newWatcher(f *file) (config.Watcher, error) { fw, err := fsnotify.NewWatcher() if err != nil { return nil, err } if err := fw.Add(f.path); err != nil { return nil, err } ctx, cancel := context.WithCancel(context.Background()) return &watcher{f: f, fw: fw, ctx: ctx, cancel: cancel}, nil }
这篇关于Kratos 读源码笔记一(配置加载)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-14后台交互资料入门指南
- 2024-11-14如何轻松创建项目环境:新手入门教程
- 2024-11-14如何抽离公共代码:初级开发者指南
- 2024-11-14Python编程入门指南
- 2024-11-14Python编程入门:如何获取参数
- 2024-11-14JWT 用户校验:简单教程与实践
- 2024-11-14Pre-commit 自动化测试入门指南
- 2024-11-14Python编程基础
- 2024-11-14Server Action入门教程:轻松掌握服务器操作
- 2024-11-14Server Component入门教程:轻松搭建服务器组件