Kratos 读源码笔记一(配置加载)

摘要:
=nil{panic(err)}//watchkeyifer:=c.Value(key);=w、 Next()iferors.Is(err,=nil{time.Sleep(time.Second)c.log.Errorf(“failedwatchnextconfig:err)continue}ifer:=c.reader.Resolve();

从入口文件看配置(初始化/加载/绑定/热加载)

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
", 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 读源码笔记一(配置加载)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇什么是Grunt(三)Cacti的使用下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

lua中文教程【高级知识】

一、编译和运行和调试 1、lua和其他解释型语言一样,先转换成为中间码再执行 2、dofile和loadfile的区别:loadfile编译返回不执行,返回错误代码;dofile执行,返回错误信息 3、loadstring通常用于程序外部的代码,load表达式,在前边加上“return”,assert方法发出警告信息 4、require方法:加载运行库【可...

Nginx使用Lua脚本加解密RSA字符串

本文主要介绍使用Lua脚本对采用RSA加密后的字符串进行解密的过程。 使用第三方类库lua-resty-rsa,参考地址:https://github.com/spacewander/lua-resty-rsa 下载并安装第三方依赖库 # Redis集群连接库依赖RSA加解密第三方依赖库[lua-resty-rsa],因此需要提前安装此第三方依赖库 #...

在linux系统中安装LANMP

1.安装LANMP步骤 root@kali:~# wget http://dl.wdlinux.cn/files/lanmp_v3.tar.gz #下载 root@kali:~# tar xzvf lanmp_v3.tar.gz #解压 root@kali:~# sh lanmp.sh #运行报错,原因:系统的dash兼容性不好,而编译常用的就是d...

yolov3输出检测图片位置信息

前言我们在进行图片识别后需要进行进一步的处理,该文章会介绍:1.怎样取消lables;2.输出并保存(.txt)标记框的位置信息 一.去掉label 在darknet/src/image.c 收索draw_detections_v3 .在该函数对应目录下进行修改。   二.目标定位(Object localization)框的数据信息 以图片左上角为(...

1036. 跟奥巴马一起编程(15)

美国总统奥巴马不仅呼吁所有人都学习编程,甚至以身作则编写代码,成为美国历史上首位编写计算机代码的总统。2014年底,为庆祝“计算机科学教育周”正式启动,奥巴马编写了很简单的计算机代码:在屏幕上画一个正方形。现在你也跟他一起画吧! 输入格式: 输入在一行中给出正方形边长N(3<=N<=20)和组成正方形边的某种字符C,间隔一个空格。 输出格式:...

Linux centos 安装php5.4和pthreads

原文章:https://blog.csdn.net/weixin_42135441/article/details/82743893 1.下载php5.4和pthreads并解压。 # wget http://www.php.net/distributions/php-5.4.36.tar.gz # wget http://pecl.php.net/get/...