本文介绍etcd作为配置中心,如何监听修改,并同步更新到PHP配置文件。
思路
创建一个进程(即go程序)watch监听etcd的增删改操作。一旦监听到修改,即根据指定的PHP配置文件的具体路径去修改对应的配置内容。
为了方便操作,PHP配置文件统一采用json后缀,保存json内容。
一个前缀对应一个配置文件。所以前缀下的单个key就等于配置文件里的单个配置项。
代码
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"time"
clientv3 "go.etcd.io/etcd/client/v3"
)
var client *clientv3.Client
var point = []string{"http://127.0.0.1:2379"}
var timeout = 5
var confPath = "../myphp/test_config.json" //php配置文件的路径(相对路径或者绝对路径)。
var watchKeyPre = "/jay/" //监视的key前缀(以/结尾)。
const (
_ = iota
etcdAdd
etcdDel
)
func main() {
initEtcd() //连接etcd服务
watchEtcd() //监视etcd修改
defer client.Close() //etcd服务要在主函数退出的时候再关闭
}
//连接etcd服务
func initEtcd() {
etcdConf := clientv3.Config{
Endpoints: point,
DialTimeout: time.Duration(timeout) * time.Second,
}
var err error
client, err = clientv3.New(etcdConf)
if err != nil {
panic(err)
}
fmt.Println("connect to etcd success")
// defer client.Close() //在这里关闭的话无法监视修改
}
//监视key修改
func watchEtcd() {
log.Printf("===== start watch key: %s =====\n", watchKeyPre)
rch := client.Watch(context.Background(), watchKeyPre, clientv3.WithPrefix())
//通道会一直阻塞等待消息
for wresp := range rch {
for _, ev := range wresp.Events {
log.Printf("Type: %s Key:%s Value:%s\n", ev.Type, ev.Kv.Key, ev.Kv.Value)
//CreateRevision代表创建key的时候的版本,Version代表当前key的版本号
// log.Printf("CreateRevision:%v,verison:%v", ev.Kv.CreateRevision, ev.Kv.Version)
newKey := removePre(ev.Kv.Key, watchKeyPre) //只保留前缀后面的值
if newKey == "" {
continue
}
var opType int
switch ev.Type {
case clientv3.EventTypePut:
opType = etcdAdd
case clientv3.EventTypeDelete:
opType = etcdDel
}
if opType == 0 {
continue
}
newValue := string(ev.Kv.Value) //[]byte转string
//修改配置
go UpdateConfig(confPath, newKey, newValue, opType)
}
}
}
//更新PHP配置文件(假设配置文件的内容是一个json字符串)
func UpdateConfig(path string, newKey string, newValue interface{}, opType int) {
content, err := os.ReadFile(path)
if err != nil {
log.Println("UpdateConfig error:", err)
}
var confArr = make(map[string]interface{})
err = json.Unmarshal(content, &confArr)
if err != nil {
log.Println("UpdateConfig error:", err)
}
fmt.Println("otype=", opType)
if opType == etcdAdd {
confArr[newKey] = newValue
} else {
delete(confArr, newKey)
}
newJson, err := json.Marshal(confArr)
if err != nil {
log.Println("UpdateConfig error:", err)
}
//替换旧的配置内容
err = os.WriteFile(path, newJson, 0777)
if err != nil {
log.Println("UpdateConfig error:", err)
}
log.Printf("UpdateConfig success:key=%s,value=%v\n", newKey, newValue)
}
//去掉前缀
func removePre(newKey []byte, prefix string) (real string) {
key := newKey[len(prefix):]
real = string(key)
return
}
测试
启功程序后,在控制台执行更新,新增,删除key:
程序监听结果:
查看PHP配置也对应被改变了:
PHP这边如何修改?
PHP这边目前的配置文件后缀是php。里面直接return一个包含多个配置信息的大数组。
为了保证能够顺利兼容配置中心推送的修改。配置文件要改为以json作为后缀,然后里面直接是一个json字符串包含所有配置信息。
同时要考虑PHP项目的代码能否正常读取到json格式下的配置信息。最好是对原有的PHP项目改动降到最少。
目前项目是tp3框架。发现框架启动的时候,php加载配置的入口方法是支持读取json文件的,会根据文件后缀自动转换格式。(狂喜,本来还想着自己得转换一下json字符串再返回)
有个地方需要修改,就是读取配置文件,不要一股脑的全部给后缀加上php。多加一个判断即可。
结尾
还有地方需要完善:比如如何监听多个前缀然后推送并修改多个服务器的PHP配置文件?