我报名参与金石计划1期挑战——分割10万奖池,这是我的第3篇文章,点击查看活动详情


微服务架构规划形式里有一条讲到,要规划可装备的服务。把服务从单体架构细分成微服务后,所有装备特点都会集存储在一个方位,更易于办理。这个会集存储办理装备的当地叫,便是装备中心。

运用装备中心还有一个优点便是,往往都支撑运用装备的热更新,这样就不需求像修正本地装备那样进行发版布置了。

可是这么好的事儿就没有缺点了吗?当然有,除非有基础设施支撑,不然它需求额定的人力进行规划和运维。不过好在有各种开源框架比方 Spring Cloud Config,能使服务接入装备中心,没有什么侵入性。至少在外表运用上感觉不到有改变。

那么在 Go 里有没有相似的方案呢?经过我这周的实验探究,还真发现了,这个方案落地也很简略,今日就跟咱们简略说说。更详细的还得是咱们上手操作起来才干感受到,本方案触及的代码太多,请给我的大众号:网管叨bi叨,发送消息【go-config】来领取

有人可能会说长途装备中心,我就把装备放在 ETCD 上,项目发动的时分拉下来不就行了?先别着急,咱先看看隔壁家 Spring 是怎样完成这个事儿,有没有咱们能够学习的当地。

Spring 的装备和装备中心

用过 Spring 的同学都触摸过,在 Java 的项目里都有一个resources目录,这个目录里一般都会有相似名字叫application.properties 的装备文件。

微服务配置中心, 这个方案 Go 里用起来不输SpringCloud

也有可能装备文件的后缀名是.yaml,那么特点装备的格局便是YAML格局的。

在这些装备文件里设置的特点装备,都能够经过能够经过@Value注解注入到目标的特点上,比方假定咱们在装备文件里设定了订单的扣头为95折的装备

order:
  discount: 95

那在代码里,咱们就能像下面这样,把特点装备的值绑定到类实例的特点上:

public class CoffeeOrderController {
    @Value("${order.discount}")
    private Integer discount;
  ......
}

后来,由于微服务流行起来了,咱们又对自己的服务拆得乐此不疲,所以 Spring 家族里后来又有了 SpringCloud,像咱们知道的知名厂商 Alibaba、Netflix 都按它这个规范开源自己内部运用的组件,就有了咱们天天看到的各种材料推广文里面的 SpringCloud-Netflix,SpringCloud- Alibaba这些。

SpringCloud- Alibaba在国内由于阿里的关系运用更广泛一些,它里面提供的装备中心方案是一个叫 Nacos 的组件,由于有 SpringCloudConfig 这个规范存在,不论各个厂商的长途装备中心是用什么组件,都需求完成 SpringCloudConfig 里的规范。

最直观的优点便是,比方说我把运用的特点装备放到了长途的 Nacos 上,比方这样:

微服务配置中心, 这个方案 Go 里用起来不输SpringCloud

可是在运用程序咱们依然能够持续运用 @Value注解拿到放在长途装备中心的特点值。如果本地和长途装备中心都有的话,以本地磁盘里的装备优先。

是不是很便利?这就相似运用里运用的是一个门面形式,基层加载运用的组件提供的driver来完成项目装备的载入。

那在 Go 里面有没有相似的方案呢?有,尽管没有 SpringCloud 这个支撑的组件那么全,可是支撑 ETCD 和 Consul 做装备中心,也够用了。

Go 项目的装备和装备中心

聊到 Go 项目的装备和装备中心,我见过的几十个项目里,是的,前几年待过的两个拿融资多的创业公司里,项目便是多,不停地尝试,不然投资人那不好说啊,咳咳。有的,做做没有效果就抛弃了 T—_—T。

说回来,咱们装备的事儿,在这几十个项目里根本上分成两大派,有用 Viper 或者另一个Yaml开源库直接操作本地文件的。还有一派是直接读 ETCD ,拿下来把字节流转到本地装备目标的。

那有没有一种方案能兼容本地装备和长途装备中心两种形式的?

我看了一下 Viper 是支撑从长途 ETCD 或者 Consul 取装备的。

可是呢,经过我的实验,发现官网的给的比如有BUG,从 ETCD 上底子读不了装备,更别提热更新了,这点咱们先按下不表,我先给咱们介绍下 Viper 的根本运用。

主要是我也没从头用过,曾经用的项目架子里是他人搭好的,哈哈~,不过你们面试的时分可别这么说大真话,今日看完我的文章,至少装备中心这块的架构选型,我是能够吹吹的,你们呢?

怎样装置 Viper 包什么的,我就不说了,官网上都有,文末会附上官网的链接,下面直接上代码。假设,不是假设,我真在项目装备文件里写了个数据库衔接信息的YAML装备。

database:
  type: mysql
  dsn: "user:pass@tcp(localhost:30306)/db_name?charset=utf8&parseTime=True&loc=Local"
  maxopen: 100
  maxidle: 10
  maxlifetime: 300

然后用 Viper 怎样读这个装备呢?这儿直接在装备文件目录下用一个 Go 的 init 函数,在函数里把装备用 Viper 反序列化到一个全局变量里,供项目运用。

type databaseConfig struct {// 装备特点跟类型字段不同名是要加下面这个tag
	Type        string        `mapstructure:"type"`
	DSN         string        `mapstructure:"dsn"`
	MaxOpenConn int           `mapstructure:"maxopen""`
	MaxIdleConn int           `mapstructure:"maxidle"`
	MaxLifeTime time.Duration `mapstructure:"maxlifetime"`
}
var Database *databaseConfig
func init() {
	// 获取当前文件的路径
	_, filename, _, _ := runtime.Caller(0)
	// 装备文件目录的路径
	configBaseDir := path.Dir(filename)
	vp := viper.New()
	vp.AddConfigPath(configBaseDir)
	vp.SetConfigType("yaml")
	err := vp.ReadInConfig()
	if err != nil {
		panic(err)
	}
	vp.UnmarshalKey("database", &Database)
	Database.MaxLifeTime *= time.Second
}

除了把装备项反序列化到结构体类型里,还能经过相似 Spring 里的@Value那种方法读单个装备项的值。

vp.Get("database.type")

不过我更倾向于反序列化到结构体这种方法,运用起来更便利,一起热更新装备时这种方法也更便利些。

项目里实例化数据库衔接的时分,就能够像这样,用上咱们的装备啦。

// 详细完整实例代码,真实太多
// 请给我的大众号:网管叨bi叨,发送消息【go-config】来领取。
  db, err := gorm.Open(config.Database.Type, config.Database.DSN)
	if err != nil {
		panic(err)
	}
	db.DB().SetMaxOpenConns(config.Database.MaxOpenConn)
	db.DB().SetMaxIdleConns(config.Database.MaxIdleConn)
	db.DB().SetConnMaxLifetime(config.Database.MaxLifeTime)
	if err = db.DB().Ping(); err != nil {
		panic(err)
	}

下面咱们接着来说,Viper 运用长途装备中心的情况。这儿我给咱们安利下我的 ETCD 集群 K8s 建立教程:用Kubernetes建立Etcd集群和WebUI,不然如果你本地没有 ETCD 的话,不太好实践,除非…你嚯嚯下你们公司测验环境的ETCD,嘿嘿,保命要紧,仍是在自己电脑上建立吧。

别的安利下我的 K8s 教程,上面用的Nacos也是我用 K8s 建立的,在教程里都有,在大众号网管叨bi叨回复k8s就能拿到教程,绝对实用。

下面咱们给项目加一个 redis 衔接信息的装备

redis:
  address: "localhost:6579"
  password: "DFgsdfhshf"
  dbnumber: 0
  maxactive: 100
  maxidle: 20

把这个装备放到长途的ETCD 装备中心里:

微服务配置中心, 这个方案 Go 里用起来不输SpringCloud

后来我按照官网的比如,搞了一下死活读不到我这个key 对应的装备,在网上查了一下,究其原因,是由于 Viper 依赖 crypt 库,而 crypt 到目前还不支撑新版 ETCD 的 API。

ETCD 的 KV 中能够存储加密的数据,Viper 在获取的时分经过 crypt 主动解密,这个初衷是好的,可是公司里的装备中心根本上都是内网访问,再则加密存储的话,我就不能像上面这样直接在客户端里进行KV修改了,有什么方法呢?

看网上有技能大佬剖析,能够经过重新完成remoteConfigFactory接口

type remoteConfigFactory interface {
	Get(rp RemoteProvider) (io.Reader, error)
	Watch(rp RemoteProvider) (io.Reader, error)
	WatchChannel(rp RemoteProvider) (<-chan *RemoteResponse, chan bool)
}

把加解密的部分去掉。正好咱们上星期刚共享了工厂形式—工厂形式有三个Level,你能用Go写到第几层?,你觉得这个是哪种工厂呢?

这个接口的详细完成我就不放上来了真实是太多,能够自己下载项目去看,下载链接获取方法,给我的大众号「网管叨bi叨」发送消息【go-config】获取项目下载链接。

运用 Viper 读取长途装备,还需求匿名导入它提供的一个库。

_ "github.com/spf13/viper/remote"

下面演示一下运用 Viper 读取长途装备和热更新装备的代码。

type RedisConfig struct {
	Address   string `mapstructure:"address"`
	Password  string `mapstructure:"password"`
	DbNumber  int    `mapstructure:"dbnumber"`
	MaxActive int    `mapstructure:"maxactive"`
	MaxIdle   int    `mapstructure:"maxidle"`
}
var Redis *RedisConfig
func init() {
	// 初始化 Viper 和上面比如里的相同,这儿省掉
  ...
  代码里省掉全部error处理
  // 读取长途ETCD里的KV
  err := vp.AddRemoteProvider("etcd", "http://127.0.0.1:32379", "root/config/viper-test/config")
	vp.SetConfigType("yaml")
  // 读本地装备
	err = vp.ReadInConfig()
	err = vp.ReadRemoteConfig()
	vp.UnmarshalKey("database", &Database)
	Database.MaxLifeTime *= time.Second
	vp.UnmarshalKey("redis", &Redis)
  // 这儿简略输出一下 redis 的装备,就不做其他演示了
  fmt.Printf("Redis Config: %v\n", Redis)
  // 监听KV改变,进行热更新
	go watchRemoteConfig(vp)
}

监听装备改变,进行热更新这块,我暂时完成的简略点,用了下轮询,后面有好的方法了再更新。

func watchRemoteConfig(vp *viper.Viper) {
	for {
		time.Sleep(5 * time.Second)
		err := vp.WatchRemoteConfigOnChannel()
		if err != nil {
			zlog.Error("Read Config Server Error", zap.Error(err))
			return
		}
		// 监控长途装备的改变
		vp.UnmarshalKey("redis", &Redis)
    fmt.Printf("Redis Config: %v\n", Redis)
	}
}

这儿演示的装备热更新我便是简略向控制台输出了一下 Redis 的装备,发动后我试了一下,在ETCD里把装备修正后能直接在项目里改变过来,下面是我把Redis装备的端口从 6579 改成 6580 的一个演示。

微服务配置中心, 这个方案 Go 里用起来不输SpringCloud

总结

今日给咱们讲了微服务装备中心的完成方案,先介绍了下 SpringCloudConfig 规范下的运用方案,由于Spring生态比较完整,对这方面支撑的比较好,像ETCD、Consul甚至Git什么的都支撑拿来做装备中心。

Go 里面的 Viper 库也很强壮,只是用 Etcd 当装备中心的时分需求咱们自己做些扩展,尽管没有那么开箱即用,可是研究问题着手处理的进程仍是很有意思的。

对了,Viper 支撑一起运用本地和长途装备,本地装备优先级高于长途,咱们不要弄混了。

这儿我再给个建议像是服务器发动参数 server.portapplication.name这类几乎是项目创立完后就不会再改的装备,放在本地装备文件就好。

关于本次用的项目的下载方法,给我的大众号「网管叨bi叨」发送消息【go-config】就能获取到项目下载链接。其实我是用的之前Go Web教程里的项目,所以你们要经常关注,说不定哪天项目就更新了。

相关链接

  • Viper 官方链接
  • 处理Viper不能读取ETCD装备