当前位置:首页 > IT科技

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

微服务架构设计模式里有一条讲到,微服务配要设计可配置的置中服务。把服务从单体架构细分成微服务后,微服务配所有配置属性都集中存储在一个位置,置中更易于管理。微服务配这个集中存储管理配置的置中地方,就是微服务配配置中心。

使用配置中心还有一个好处就是置中,往往都支持应用配置的微服务配热更新,这样就不需要像修改本地配置那样进行发版部署了。置中

但是微服务配这么好的事儿就没有缺点了吗?当然有,除非有基础设施支持,置中否则它需要额外的微服务配人力进行设计和运维。不过好在有各种开源框架比如 Spring Cloud Config,置中能使服务接入配置中心,微服务配没有什么侵入性。至少在表面使用上感觉不到有变化。

那么在 Go 里有没有类似的方案呢?经过我这周的试验探索,还真发现了,这个方案落地也很简单,今天就跟大家简单说说。更详细的还得是大家上手操作起来才能感受到,站群服务器本方案涉及的代码太多,可以给我的公众号:网管叨bi叨,发送消息【go-config】获取项目下载地址。

分享软件开发和系统架构设计基础、Go 语言和Kubernetes。

有人可能会说远程配置中心,我就把配置放在 ETCD 上,项目启动的时候拉下来不就行了?先别着急,咱先看看隔壁家 Spring 是怎么实现这个事儿,有没有我们可以学习的地方。

Spring 的配置和配置中心

用过 Spring 的同学都接触过,在 Java 的项目里都有一个resources​目录,这个目录里一般都会有类似名字叫application.properties 的配置文件。

配置文件

也有可能配置文件的后缀名是.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 上,比如这样:

远程配置中心 Nacos

但是在应用程序我们仍然可以继续使用 @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 配置中心里:

ETCD 中的配置

后来我按照官网的例子,死活读不到我这个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)

}

这个接口的具体实现我就不放上来了实在是太多,可以自己下载项目去看,下载链接获取方式,给我的公众号「网管叨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处理

// 告诉Viper远程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 的一个演示。

配置动态更新

总结

今天给大家讲了微服务配置中心的实现方案,先介绍了下 SpringCloudConfig 标准下的使用方案,因为Spring生态比较完整,对这方面支持的比较好,像ETCD、Consul甚至Git什么的都支持拿来做配置中心。

Go 里边的 Viper 库也很强大,只是用 Etcd 当配置中心的时候需要我们自己做些扩展,虽然没有那么开箱即用,但是研究问题动手解决的过程还是很有意思的。

对了,Viper 支持同时使用本地和远程配置,本地配置优先级高于远程,大家不要弄混了。

这里我再给个建议像是服务器启动参数 server.port,application.name这类几乎是项目创建完后就不会再改的配置,放在本地配置文件就好。

分享到:

滇ICP备2023006006号-16