Etcd - 分布式配置中心2 - runtime切换配置

简介

背景接上一篇 Etcd - 分布式配置中心
这里是使用 etcd 获取配置信息,后在程序中热切换配置的例子

代码调用流程

  • controllers
package controllers

import (
"github.com/jinzhu/gorm"
"vincent.com/iris-demo/config"
"vincent.com/iris-demo/services"
)

//Todo - model
type Todo struct {
gorm.Model
Desc string
}

func init() {
services.DB.AutoMigrate(&Todo{})
go config.StartWatch()
}

// AddTodo - insert todo item
func AddTodo(desc string) {
// fmt.Println(services.DB)
services.DB.Create(&Todo{Desc: desc})
}
  • services
package services

import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql" //import driver
)

// DB instantce
var (
DB *gorm.DB
preDB *gorm.DB
err error
)

func init() {
// initDb("root:my-secret-pw@(localhost:4306)/test2?charset=utf8&parseTime=True&loc=Local")
}

//InitDb -
func InitDb(dialects string) {
if DB != nil {
preDB = DB
defer preDB.Close()
}
DB, err = gorm.Open("mysql", dialects)
// defer
if err != nil {
DB.Close()
panic("failed to connect database")
}
}
  • config
package config

import (
"context"
"log"
"time"

"go.etcd.io/etcd/clientv3"
"vincent.com/iris-demo/services"
)

// constants
var (
cli *clientv3.Client
err error
)

func init() {
cli, err = clientv3.New(clientv3.Config{
Endpoints: []string{"http://127.0.0.1:23790", "http://127.0.0.1:23791"},
DialTimeout: 5 * time.Second,
})

if err != nil {
// handle error!
cli.Close()
log.Fatalln(err.Error())
}
// fmt.Printf("cli init finished")
getDBString()
}

// GetDBString -
func getDBString() {
resp, err := cli.Get(context.Background(), "sample_key")
if err != nil {
log.Fatal("get key error", err)
}
// fmt.Printf("get the sample_key: %v\n", string(resp.Kvs[0].Value))
// log.Fatal("break now")
// DBch <- string(resp.Kvs[0].Value)
services.InitDb(string(resp.Kvs[0].Value))
}

//StartWatch -
func StartWatch() {
rch := cli.Watch(context.Background(), "sample_key")
for wresp := range rch {
for _, ev := range wresp.Events {
services.InitDb(string(ev.Kv.Value))
// fmt.Printf("%s %q : %q\n", ev.Type, ev.Kv.Key, ev.Kv.Value)
}
}
}

解读

controllers 初试化会先初始化 import 的几个包.
services 的 init 是空的,config 的 init,
先获取 etcd 的特定节点的配置 -> 调用 InitDb 初始化数据库实例 -> controllers 会  启动一个 goroutine watch 节点信息的变化 -> 信息变化会从新触发 InitDb 产生新的数据库实例,并且关闭旧的实例连接

简单测试

用 jmeter 100 个现成访问 /hello 接口, 手动切换节点信息

ETCDCTL_API=3 ./etcdctl put sample_key ‘root:my-secret-pw@(localhost:3306)/dbname’

整个程序过程中没有中断和报错, 但会时不时有错误信息 Mysql resource temporarily unavailable, 搜了下一般是链接数满了,或者内存不够用等造成的,应该是太大负载造成的, 确实也造成了数据的丢失.这个其实有个问题, 为什么资源不可用的情况下,gorm 没有  抛 error.  但是这个测试大概率和切换是没有关系, 说明切换的过程其实是成功的, 两个数据库里都是有大量数据的,  并且没有因为切换生成新实例的过程而报错. watch 的过程也是起作用的.
问题 2: 如果 etcd 集群和客户端中间失联了,  客户端应该有个 fallback 的方案,比如启动的时候走本地配置文件.