一、什么是配置中心?
配置中心,是把会变化的配置从程序里搬到程序外,并且能被集中、统一、可控地管理。
二、Go轻量级配置中心Consul的使用
1. 在配置中心配置一个KV “config/golang_per_day/50”
2. 编码
package main
import (
"bytes"
"fmt"
"log"
"os"
"os/signal"
"sync"
"syscall"
"github.com/gin-gonic/gin"
"github.com/hashicorp/consul/api"
"github.com/spf13/viper"
)
const (
srvName = "golang_per_day_50"
srvHost = "192.168.1.31" //这里写自己的 IP 地址,否则docker里的Consul无法访问到
srvPort = 8080
srvID = "golang_per_day_50-192.168.1.31:8080"
)
var (
appConfig *Config
lock sync.RWMutex
)
type Config struct {
Consul ConsulConfig `mapstructure:"consul" json:"consul"`
Redis RedisConfig `mapstructure:"redis" json:"redis"`
}
type RedisConfig struct {
Host string `mapstructure:"host" json:"host"`
Port int `mapstructure:"port" json:"port"`
Db int `mapstructure:"db" json:"db"`
Password string `mapstructure:"password" json:"password"`
}
type ConsulConfig struct {
Host string `mapstructure:"host" json:"host"`
Port int `mapstructure:"port" json:"port"`
}
func main() {
r := gin.Default()
// 定义一个 ping 接口
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
// 1. 定义健康检查接口
r.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{
"status": "ok",
})
})
// 2. 读取配置中心的配置,这里用Consul
config := api.DefaultConfig()
config.Address = "192.168.1.31:8500" // Consul 的地址
client, _ := api.NewClient(config)
kvPath := "config/golang_per_day/50"
loadConfig(client, kvPath)
go watchConfig(client, kvPath)
// 3. 注册服务到 Consul
registerToConsul()
// 4. 启动 Gin 服务
go func() {
if err := r.Run(fmt.Sprintf(":%d", srvPort)); err != nil {
log.Fatal("服务启动失败: ", err)
}
}()
// 5. 优雅退出:监听信号注销服务
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
// 退出前注销服务
deregisterFromConsul()
log.Println("服务已退出并从 Consul 注销")
}
// 注册逻辑
func registerToConsul() {
config := api.DefaultConfig()
config.Address = fmt.Sprintf("%s:%d", appConfig.Consul.Host, appConfig.Consul.Port) // Consul 的地址
client, _ := api.NewClient(config)
registration := &api.AgentServiceRegistration{
ID: srvID,
Name: srvName,
Address: srvHost,
Port: srvPort,
Check: &api.AgentServiceCheck{
HTTP: fmt.Sprintf("http://%s:%d/health", srvHost, srvPort),
Interval: "5s", // 每 5 秒检查一次
Timeout: "3s", // 超时时间
},
}
err := client.Agent().ServiceRegister(registration)
if err != nil {
log.Fatal("注册失败: ", err)
}
log.Println("服务注册成功!")
}
// 注销逻辑
func deregisterFromConsul() {
config := api.DefaultConfig()
client, _ := api.NewClient(config)
_ = client.Agent().ServiceDeregister(srvID)
}
// 读取配置中心的 Redis 配置
func loadConfig(client *api.Client, path string) {
pair, _, err := client.KV().Get(path, nil)
if err != nil {
log.Fatal("读取配置失败: ", err)
}
v := viper.New()
v.SetConfigType("yaml")
err = v.ReadConfig(bytes.NewBuffer(pair.Value))
if err != nil {
log.Fatal("读取配置失败: ", err)
}
newConfig := Config{}
err = v.Unmarshal(&newConfig)
if err != nil {
log.Fatal("解析配置失败: ", err)
}
lock.Lock()
defer lock.Unlock()
appConfig = &newConfig
log.Println("Redis 配置更新: ", appConfig)
}
// 监听 Consul K/V 变化
func watchConfig(client *api.Client, path string) {
var lastIndex uint64
for {
// Consul 的 WaitIndex 机制:如果有变化会立即返回,否则阻塞直到超时
pair, meta, err := client.KV().Get(path, &api.QueryOptions{
WaitIndex: lastIndex,
})
if err != nil {
log.Printf("监听出错: %v", err)
continue
}
if pair != nil && meta.LastIndex > lastIndex {
lastIndex = meta.LastIndex
loadConfig(client, path)
}
}
}
3. 运行程序
$ go run .
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /ping --> main.main.func1 (3 handlers)
[GIN-debug] GET /health --> main.main.func2 (3 handlers)
2025/12/25 00:00:58 Redis 配置更新: &{{127.0.0.1 8500} { 6379 2 123456}}
2025/12/25 00:00:59 Redis 配置更新: &{{127.0.0.1 8500} { 6379 2 123456}}
2025/12/25 00:00:59 服务注册成功!
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8080
[GIN] 2025/12/25 - 00:00:59 | 200 | 0s | 192.168.1.31 | GET "/health"
4. 在Consul中修改“config/golang_per_day/50”的值为
consul:
host: 127.0.0.1
port: 8500
redis:
addr: 192.168.1.31
port: 6379
password: "123456"
db: 22
5. 程序里立马就热更新了配置
三、Consul做配置中心的优缺点
1. 优点:
-
架构极简:如果你已经用了 Consul 做服务发现,不需要再引入新组件。
-
强一致性(CP):Consul 基于 Raft 协议,确保所有实例拿到的配置都是一致的。
-
原子性更新:支持 Check-And-Set (CAS) 操作,防止配置并发冲突。
-
原生健康检查:配置分发可以与节点的健康状态挂钩。
2. 缺点:
-
缺少可视化版本管理:Consul 默认的 UI 只能改 K/V,没有 Nacos 那种“发布历史”、“回滚”、“灰度发布”的现成功能(需要自己二开或配合 Git 使用)。
-
权限细粒度一般:虽然有 ACL,但相比 Apollo 那种精确到字段的权限控制,显得比较粗糙。
四、比喻
人生,本质上是一套 Go 程序 + Consul 配置中心
Go 代码,是你真正掌握的东西。它决定了你能不能跑、会不会 panic、有没有并发安全。
Consul 配置,是你对现实的妥协方案。它不提升你的能力,只决定你在什么条件下不至于崩溃。
*源码地址*
1、公众号“Codee君”回复“每日一Go”获取源码
如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!