viper配置框架的介绍支持zookeeper的读取和监听

viper作为配置框架,其功能非常的强大,我们没有理由不去了解一下。我们先看官网对它的功能简介:

viper是完整配置解决方案,他可以处理所有类型和格式的配置文件,他有如下功能:

  • 设置默认配置
  • 支持读取 JSON TOML YAML HCL 和 Java 属性配置文件
  • 监听配置文件变化,实时读取读取配置文件内容
  • 读取环境变量值
  • 读取远程配置系统 (etcd Consul) 和监控配置变化
  • 读取命令 Flag 值
  • 读取 buffer 值
  • 读取确切值

乍一看,未免有相见恨晚之感,可仔细一想,不免脑袋里有另外一种声音:不会不支持读取 zookeeper 吧?好吧,至少我是这样的。

基于这种想法,当然要去立马尝试,如下:

viper.AddRemoteProvider("zookeeper", "xx.xx.xx.xx:2181", "/viper/test")

返回结果是:

Unsupported Remote Provider Type zookeeper

果不其然,于是追踪 viper.AddRemoteProvider 的源码,发现viper只支持如下几种

var SupportedRemoteProviders = []string{"etcd", "consul", "firestore"}

如果就此打住,未免有点太可惜,作为偏执狂,总想着能否来改造下viper,让其支持 zookeeper ,于是在issue上找是否有人遇到同样的问题,还整让我找到了, 传送 。但是不完整,且稍微有点bug。所以根据他的基础上,我做了些调整。进入正题,我们开始修改viper源码。说明下,我的viper版本是最新的 1.7.0

修改源码

1、添加zookeeper.go

添加的位置: github.com/bketelsen/crypt/zookeeper , zookeeper 目录需要自己创建, github.com/bketelsen/crypt 是viper的依赖包,会自动下载

文件内容:

package zookeeper

import (
	"errors"
	"fmt"
	zk "github.com/samuel/go-zookeeper/zk"
	//"github.com/xordataexchange/crypt/backend"
	"github.com/bketelsen/crypt/backend"
	"strings"
	"time"
)

type Client struct {
	client *zk.Conn
	waitIndex uint64
}

func New(machines []string) (*Client, error) {
	zkclient, _, err := zk.Connect(machines, time.Second)
	if err != nil {
		return nil, err
	}
	return &Client{zkclient, 0}, nil
}

func (c *Client) Get(key string) ([]byte, error) {
	resp, _, err := c.client.Get(key)
	if err != nil {
		return nil, err
	}
	return []byte(resp), nil
}

func nodeWalk(prefix string, c *Client, vars map[string]string) error {
	l, stat, err := c.client.Children(prefix)
	if err != nil {
		return err
	}

	if stat.NumChildren == 0 {
		b, _, err := c.client.Get(prefix)
		if err != nil {
			return err
		}
		vars[prefix] = string(b)

	} else {
		for _, key := range l {
			s := prefix + "/" + key
			_, stat, err := c.client.Exists(s)
			if err != nil {
				return err
			}
			if stat.NumChildren == 0 {
				b, _, err := c.client.Get(s)
				if err != nil {
					return err
				}
				vars[s] = string(b)
			} else {
				nodeWalk(s, c, vars)
			}
		}
	}
	return nil
}

func (c *Client) GetValues(key string, keys []string) (map[string]string, error) {
	vars := make(map[string]string)
	for _, v := range keys {
		v = fmt.Sprintf("%s/%s", key, v)
		v = strings.Replace(v, "/*", "", -1)
		_, _, err := c.client.Exists(v)
		if err != nil {
			return vars, err
		}
		if v == "/" {
			v = ""
		}
		err = nodeWalk(v, c, vars)
		if err != nil {
			return vars, err
		}
	}
	return vars, nil
}

func (c *Client) List(key string) (backend.KVPairs, error) {
	var list backend.KVPairs
	resp, stat, err := c.client.Children(key)
	if err != nil {
		return nil, err
	}

	if stat.NumChildren == 0 {
		return list, nil
	}

	entries, err := c.GetValues(key, resp)
	if err != nil {
		return nil, err
	}

	for k, v := range entries {
		list = append(list, &backend.KVPair{Key: k, Value: []byte(v)})
	}
	return list, nil
}

func (c *Client) createParents(key string) error {
	flags := int32(0)
	acl := zk.WorldACL(zk.PermAll)

	if key[0] != '/' {
		return errors.New("Invalid path")
	}

	payload := []byte("")
	pathString := ""
	pathNodes := strings.Split(key, "/")
	for i := 1; i < len(pathNodes); i++ {
		pathString += "/" + pathNodes[i]
		_, err := c.client.Create(pathString, payload, flags, acl)
		// not being able to create the node because it exists or not having
		// sufficient rights is not an issue. It is ok for the node to already
		// exist and/or us to only have read rights
		if err != nil && err != zk.ErrNodeExists && err != zk.ErrNoAuth {
			return err
		}
	}
	return nil
}

func (c *Client) Set(key string, value []byte) error {
	err := c.createParents(key)
	if err != nil {
		return err
	}
	_, err = c.client.Set(key, []byte(value), -1)
	return err
}

func (c *Client) Watch(key string, stop chan bool) <-chan *backend.Response {
	respChan := make(chan *backend.Response, 0)
	go func() {
		for {
			resp, _, watch, err := c.client.GetW(key)
			if err != nil {
				respChan <- &backend.Response{nil, err}
				time.Sleep(time.Second * 5)
			}

			select {
			case e := <-watch:
				if e.Type == zk.EventNodeDataChanged {
					resp, _, err = c.client.Get(key)
					if err != nil {
						respChan <- &backend.Response{nil, err}
					}
					c.waitIndex = 0
					respChan <- &backend.Response{[]byte(resp), nil}
				}
			}
		}
	}()
	return respChan
}

这个文件是实现 ConfigManager 接口,我们在上图中看到 etcdconsulfilestore ,均有实现该接口,接口的定义很简单

type ConfigManager interface {
	Get(key string) ([]byte, error)
	List(key string) (KVPairs, error)
	Set(key string, value []byte) error
	Watch(key string, stop chan bool) <-chan *Response
}

2、修改config.go

文件的位置: github.com/bketelsen/crypt/config/config.go ,如下图

func NewStandardEtcdConfigManager(machines []string) (ConfigManager, error) 方法下面添加如下方法:

// NewStandardZookeeperConfigManager returns a new ConfigManager backed by Zookeeper.
// Data will be encrypted.
func NewStandardZookeeperConfigManager(machines []string) (ConfigManager, error) {
	store, err := zookeeper.New(machines)
	if err != nil {
		return nil, err
	}
	return NewStandardConfigManager(store)
}

func NewEtcdConfigManager(machines []string, keystore io.Reader) (ConfigManager, error) 方法下面添加如下方法:

// NewZookeeperConfigManager returns a new ConfigManager backed by zookeeper.
// Data will be encrypted.
func NewZookeeperConfigManager(machines []string, keystore io.Reader) (ConfigManager, error) {
	store, err := zookeeper.New(machines)
	if err != nil {
		return nil, err
	}
	return NewConfigManager(store, keystore)
}

这两个方法是初始化 ConfigManager 对象,也就是我们刚才添加的 zookeeper.go 文件的对象

3、修改remote.go

文件的位置: github.com/spf13/viper/remote/remote.go ,如下图

找到74行,用下面的代码替换 func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) 方法

func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) {
	var cm crypt.ConfigManager
	var err error

	if rp.SecretKeyring() != "" {
		var kr *os.File
		kr, err = os.Open(rp.SecretKeyring())
		if err != nil {
			return nil, err
		}
		defer kr.Close()
		switch rp.Provider() {
		case "etcd":
			cm, err = crypt.NewEtcdConfigManager([]string{rp.Endpoint()}, kr)
		case "zookeeper":
			cm, err = crypt.NewZookeeperConfigManager([]string{rp.Endpoint()}, kr)
		case "firestore":
			cm, err = crypt.NewFirestoreConfigManager([]string{rp.Endpoint()}, kr)
		default:
			cm, err = crypt.NewConsulConfigManager([]string{rp.Endpoint()}, kr)
		}
	} else {
		switch rp.Provider() {
		case "etcd":
			cm, err = crypt.NewStandardEtcdConfigManager([]string{rp.Endpoint()})
		case "zookeeper":
			cm, err = crypt.NewStandardZookeeperConfigManager([]string{rp.Endpoint()})
		case "firestore":
			cm, err = crypt.NewStandardFirestoreConfigManager([]string{rp.Endpoint()})
		default:
			cm, err = crypt.NewStandardConsulConfigManager([]string{rp.Endpoint()})
		}
	}
	if err != nil {
		return nil, err
	}
	return cm, nil
}

细心的读者可能已经发现,其实就添加了两个case选项:

4、修改viper.go

文件的位置: github.com/spf13/viper/viper.go ,如下图

取+监听zookeeper(1)\image-20200521222843002.png)

找到两个 SupportedRemoteProviders 定义的定法,1.7.0版本的行号分别是:290,331。只要添加 zookeeper ,即可

SupportedRemoteProviders = []string{"etcd", "consul", "firestore", "zookeeper"}

好了,修改代码的工作已经完了,接下来我们来测试:

测试

注意:zookeeper中已经设置了内容

set /viper/test {"appName":"test","nodes":["127.0.0.1","127.0.0.2","127.0.0.3"]}

package main

import (
	"fmt"
	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
	_ "github.com/spf13/viper/remote"
	"time"
)

type config struct {
	AppName string
	Nodes []string
}

func main() {
	var waitGroup=sync.WaitGroup{}
	waitGroup.Add(1)
	readRemoteZookeeper()
	go watchRemoteZookeeper()
	waitGroup.Wait()
}

func readRemoteZookeeper() {
	viper.AddRemoteProvider("zookeeper", "62.234.15.24:2181", "/viper/test")
	viper.SetConfigType("json")
	err := viper.ReadRemoteConfig()
	if err != nil {
		panic(fmt.Sprintf("read remote zookeeper error:+%v", err))
	}

	var C config
	viper.Unmarshal(&C)
	fmt.Printf("从zookeeper读取配置内容:%+v\n", C)
}

func watchRemoteZookeeper() {
	go func() {
		for {
  //delay after each request
			time.Sleep(time.Second * 5)
			err := viper.WatchRemoteConfig()
			if err != nil {
				fmt.Errorf("unable to read remote config: %v", err)
				continue
			}
			fmt.Printf("从zookeeper读取更新内容:appName=%s,nodes=%+v\n", viper.Get("appName"), viper.Get("nodes"))
		}
	}()
}

输出内容:

从zookeeper读取配置内容:{AppName:test Nodes:[127.0.0.1 127.0.0.2 127.0.0.3]}
从zookeeper读取更新内容:appName=test,nodes=[127.0.0.1 127.0.0.2 127.0.0.3]

如果我们修改zookeeper的内容,则viper会读取到更新后的内容:

set /viper/test {"appName":"test","nodes":["127.0.0.1","127.0.0.2","127.0.0.3","127.0.0.4"]}
从zookeeper读取更新内容:appName=test,nodes=[127.0.0.1 127.0.0.2 127.0.0.3 127.0.0.4]

结语

让viper支持 zookeeper 并不复杂的,并且基本上不需要修改原有的方法, 这要归结于viper用到一个非常重要的设计原则: 开闭原则 ,读者可以自行体会。

关于viper的基本使用, github 已经有非常详细的例子,这里就不再赘述,如有疑问,可以私信我

到此这篇关于viper配置框架的介绍支持zookeeper的读取和监听的文章就介绍到这了,更多相关viper配置框架支持zookeeper的读取和监听内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • golang 使用 viper 读取自定义配置文件

    viper 支持 Yaml.Json. TOML.HCL 等格式,读取非常的方便. viper 官网有案例:https://github.com/spf13/viper go get github.com/spf13/viper 创建 config.yaml 文件 database: driver: mysql host: 127.0.0.1 port: 3306 username: blog dbname: blog password: 123456 建一个 config.go 用于初始化配置

  • DB2 9(Viper)快速入门

    正在看的db2教程是:DB2 9(Viper)快速入门. 为了帮助您快速掌握 DB2 自身的 XML 特性,请完成几个普通的任务,比如: 创建用于管理 XML 数据的数据库对象,包括一个测试数据库.一些示例表和视图. 使用 INSERT 和 IMPORT 语句将 XML 数据填充到数据库中. 验证您的 XML 数据.使用 DB2 开发和注册您的 XML 模式,并在导入数据时使用 XMLVALIDATE 选项.后续文章将包括其他主题,比如使用 SQL 查询.更新和删除 DB2 XML 数据,使用

  • viper配置框架的介绍支持zookeeper的读取和监听

    viper作为配置框架,其功能非常的强大,我们没有理由不去了解一下.我们先看官网对它的功能简介: viper是完整配置解决方案,他可以处理所有类型和格式的配置文件,他有如下功能: 设置默认配置 支持读取 JSON TOML YAML HCL 和 Java 属性配置文件 监听配置文件变化,实时读取读取配置文件内容 读取环境变量值 读取远程配置系统 (etcd Consul) 和监控配置变化 读取命令 Flag 值 读取 buffer 值 读取确切值 乍一看,未免有相见恨晚之感,可仔细一想,不免脑袋

  • zookeeper+Springboot实现服务器动态上下线监听教程详解

    目录 zookeeper+Springboot实现服务器动态上下线监听教程 一.什么是服务器动态上下线监听 二.为什么要实现对服务器上下线的监听 三.编码实现 四.测试 1.启动客户端,开启监听 2.按照下面的流程启动服务器端 zookeeper+Springboot实现服务器动态上下线监听教程 一.什么是服务器动态上下线监听 客户端能够实时洞察到服务器上下线的变化,现在我们看看下面三个变化给集群.服务器.客户端三者的变化 初始情况 服务器3启动 服务器2下线 从上面的图我们可以知道,在集群中,

  • Flutter输入框TextField属性及监听事件介绍

    textField用于文本输入,它提供了很多属性: const TextField({ ... TextEditingController controller, FocusNode focusNode, InputDecoration decoration = const InputDecoration(), TextInputType keyboardType, TextInputAction textInputAction, TextStyle style, TextAlign textA

  • Java分布式服务框架Dubbo介绍

    目录 1.什么是Dubbo? 2.Dubbo核心组件是? 3.Dubbo的工作原理是? 4.介绍一下Dubbo框架分层? 5.Dubbo支持哪些协议? 1.dubbo默认协议: 2.rmi协议: 3.hessian协议: 4.http协议: 5.webservice协议: 6.thrift协议: 7.redis协议: 8.memcached协议: 6.Dubbo核心配置有哪些? 7.Dubbo有哪几种集群容错方案.哪几种负载均衡策略? 8.Dubbo用到哪些设计模式,简要介绍? 9.Dubbo有

  • Android 常见的图片加载框架详细介绍

    Android 常见的图片加载框架 图片加载涉及到图片的缓存.图片的处理.图片的显示等.而随着市面上手机设备的硬件水平飞速发展,对图片的显示要求越来越高,稍微处理不好就会造成内存溢出等问题.很多软件厂家的通用做法就是借用第三方的框架进行图片加载. 开源框架的源码还是挺复杂的,但使用较为简单.大部分框架其实都差不多,配置稍微麻烦点,但是使用时一般只需要一行,显示方法一般会提供多个重载方法,支持不同需要.这样会减少很不必要的麻烦.同时,第三方框架的使用较为方便,这大大的减少了工作量.提高了开发效率.

  • 聊聊Golang中很好用的viper配置模块

    前言 viper 支持Yaml.Json. TOML.HCL 等格式,读取非常的方便. 安装 go get github.com/spf13/viper 如果提示找不到golang.org/x/text/这个库,是因为golang.org/x/text/这个库在GitHub上托管的路径不一致. 解决办法: 可以从https://github.com/golang/text下载源码下来,然后到$GOPATH/src下面创建golang.org/x/文件夹(已存在的忽略),把压缩包的文件解压到gol

  • .NET 6全新配置对象ConfigurationManager介绍

    介绍 本节为大家带来.NET 6新增的ConfigurationManager,很多人好奇为啥要讲这个,读取加载配置信息都随手就来了,我们往下看一下. 翻译:这添加了 ASP.NET Core 的新 WebApplcation 和 WebApplicationBuilder已经使用的类型,允许从配置(例如appsettings.json和DOTNET_/ASPNETCORE_环境变量)中读取,同时仍然能够添加新的配置源,而无需显式重建配置.每次通过IConfigurationBuilder界面添

  • SpringMVC框架的介绍与使用详解

    目录 SpringMVC介绍 SpringMVC特点 SpringMVC框架部署 SpringMVC框架使用 静态资源放行配置 SpringMVC介绍 SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,跟Spring,Mybatis框架并称为ssm.Spring MVC是由Spring官方提供的基于MVC设计理念的web框架也是基于Servlet封装的用于实现MVC控制的框架,实现前端和服务端的交互.为什么叫MVC呢,这是因为,M是模型(Mo

  • 基于Tomcat 数据源的原理、配置、使用介绍

    1.数据源的作用及操作原理 在程序代码中使用数据源是可以提升操作性能的,这种性能的提升依靠于运行的原理. 传统JDBC操作步骤 1.加载数据库驱动程序,数据库驱动程序通过CLASSPATH配置: 2.通过DriverManager类取得数据库连接对象: 3.通过Connection实例化PreparedStatement对象,编写SQL命令操作数据库: 4.数据库属于资源操作,操作完成后进行数据库的关闭以释放资源.如图所示: 对于不同的用户只有操作不同,但是对于1.2.4三个步骤很明显是一个重复

  • Node.js开源应用框架HapiJS介绍

    一.HapiJS介绍 HapiJS是一个开源的.基于Node.js的应用框架,它适用于构建应用程序和服务,其设计目标是让开发者把精力集中于开发可重用的应用程序的业务逻辑,向开发者提供构建应用程序业务逻辑所需的基础设施.HapiJS目前的最新版本为7.2.0版. 二.HapiJS安装和项目配置 1.安装Hapi库 HapiJS的安装很简单,执行如下命令: 复制代码 代码如下: $ sudo npm install hapi -g hapi@7.2.0 /usr/local/lib/node_mod

随机推荐