我报名参与金石计划1期挑战——分割10万奖池,这是我的第3篇文章,点击查看活动详情
微服务架构规划形式里有一条讲到,要规划可装备的服务。把服务从单体架构细分成微服务后,所有装备特点都会集存储在一个方位,更易于办理。这个会集存储办理装备的当地叫,便是装备中心。
运用装备中心还有一个优点便是,往往都支撑运用装备的热更新,这样就不需求像修正本地装备那样进行发版布置了。
可是这么好的事儿就没有缺点了吗?当然有,除非有基础设施支撑,不然它需求额定的人力进行规划和运维。不过好在有各种开源框架比方 Spring Cloud Config,能使服务接入装备中心,没有什么侵入性。至少在外表运用上感觉不到有改变。
那么在 Go 里有没有相似的方案呢?经过我这周的实验探究,还真发现了,这个方案落地也很简略,今日就跟咱们简略说说。更详细的还得是咱们上手操作起来才干感受到,本方案触及的代码太多,请给我的大众号:网管叨bi叨,发送消息【go-config】来领取。
有人可能会说长途装备中心,我就把装备放在 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 上,比方这样:
可是在运用程序咱们依然能够持续运用 @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 装备中心里:
后来我按照官网的比如,搞了一下死活读不到我这个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 的一个演示。
总结
今日给咱们讲了微服务装备中心的完成方案,先介绍了下 SpringCloudConfig 规范下的运用方案,由于Spring生态比较完整,对这方面支撑的比较好,像ETCD、Consul甚至Git什么的都支撑拿来做装备中心。
Go 里面的 Viper 库也很强壮,只是用 Etcd 当装备中心的时分需求咱们自己做些扩展,尽管没有那么开箱即用,可是研究问题着手处理的进程仍是很有意思的。
对了,Viper 支撑一起运用本地和长途装备,本地装备优先级高于长途,咱们不要弄混了。
这儿我再给个建议像是服务器发动参数 server.port
,application.name
这类几乎是项目创立完后就不会再改的装备,放在本地装备文件就好。
关于本次用的项目的下载方法,给我的大众号「网管叨bi叨」发送消息【go-config】就能获取到项目下载链接。其实我是用的之前Go Web教程里的项目,所以你们要经常关注,说不定哪天项目就更新了。
相关链接
- Viper 官方链接
- 处理Viper不能读取ETCD装备