每日一Go-50、Go微服务--配置中心

0 阅读4分钟

一、什么是配置中心?

配置中心,是把会变化的配置从程序里搬到程序外,并且能被集中、统一、可控地管理。

二、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
[GIN2025/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”获取源码

2、pan.baidu.com/s/1B6pgLWfS…


如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!