每日一Go-49、Go微服务--服务注册与发现Consul

0 阅读4分钟

    今天正式进入微服务的世界,什么是微服务?为什么要微服务?什么时候上微服务?服务注册与发现组件Consul如何使用?

一、什么是微服务?

简单来说,微服务是一种架构风格。它将一个庞大、复杂的单体应用(Monolith)拆分成一组小型、独立的服务。

  • 独立性:每个服务运行在自己的进程中。

  • 单一职责:一个服务只做好一件事(如:用户服务、订单服务、支付服务)。

  • 轻量级通信:服务之间通过网络协议(通常是 HTTP/JSON 或 gRPC)进行通信。

  • 去中心化:每个服务可以有自己的数据库、技术栈和发布节奏。

二、为什么要微服务?

企业选择微服务,核心动机通常是为了解决“规模化”带来的痛点:

  • 独立部署(Speed):修个小 Bug 不再需要重新发布整个系统,只需发布受影响的微服务。

  • 团队自治(Scale):不同团队负责不同服务。再也不用担心 50 个人在同一个代码库里提交代码导致的合并冲突。

  • 故障隔离(Resilience):支付服务挂了,用户可能暂时不能买东西,但依然可以浏览商品。

  • 按需伸缩(Efficiency):如果双 11 期间下单量大,你可以只给“订单服务”增加机器,而不是把昂贵的内存全部堆给整个单体应用。

三、什么时候上微服务?

微服务不是免费的午餐,它会显著增加运维、网络延迟和分布式事务的复杂性。“在没有足够的复杂性之前,不要考虑微服务。”

你可以通过以下信号判断是否该转型了:

  1. 团队规模:当一个团队超过 10-15 人,沟通成本开始指数级上升。

  2. 发布频率:每天都要发布多个功能,单体架构的编译和测试速度成了瓶颈。

  3. 技术债务:代码耦合严重,改 A 坏 B,没人敢动老代码。

  4. 性能瓶颈:系统某一部分需要高并发支持,但无法单独扩容。

四、服务注册与发现组件Consul如何使用

4.1 安装Consul:在docker中安装Consul

#docker-compose.yml
networks:
    codee_jun:
        driver: bridge
services:
    consul:
        container_name: consul
        image: consul:1.15.4
        ports:
            - 8500:8500
        restart: always
        networks:
            - codee_jun
//启动Consul服务
docker-compose up -d

图片

4.2 具体代码实现

package main
import (
    "fmt"
    "log"
    "os"
    "os/signal"
    "syscall"
    "github.com/gin-gonic/gin"
    "github.com/hashicorp/consul/api"
)
const (
    srvName = "golang_per_day_49"
    srvHost = "192.168.1.31" //这里写自己的 IP 地址,否则docker里的Consul无法访问到
    srvPort = 8080
    srvID   = "golang_per_day_49-192.168.1.31:8080"
)
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
    registerToConsul()
    // 3. 启动 Gin 服务
    go func() {
        if err := r.Run(fmt.Sprintf(":%d", srvPort)); err != nil {
            log.Fatal("服务启动失败: ", err)
        }
    }()
    // 4. 优雅退出:监听信号注销服务
    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 = "127.0.0.1:8500" // 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)
}

运行demo

go run .

这个时候Consul里就多了一个服务“golang_per_day_49”

图片

同时,会收到Consul的健康检测信号

图片

五、原理解析

5.1 为什么要写/health接口?

    答:Consul作为一个注册中心,它必须知道你的服务是否还“活着”。如果服务崩了/断网了,Consul连续几次访问/health都失败,就会把你从服务列表里剔除。

5.2 为什么需要srvID?

    答:在微服务中,同一个服务会有多个实例;Name是服务名“golang_per_day_49”,ID必须唯一,通常用 服务名-IP-端口来命名,以便Consul区分具体是哪个实例下线了。

5.3 优雅退出的重要性是什么?

    答:如果没有优雅退出,Consul不会立即知道你下线了,它会等到健康检查超时后才剔除你。优雅退出可以让服务在关闭的同时主动告诉Consul:"我撤了,你继续努力吧。"

六、比喻

真正成熟的人,不追求唯一

而追求 可替代但不可或缺

微服务里,没有哪个实例是不可替代的,但整个服务必须存在

人生也是这样:你可以被替换,但你不能随便掉线;你可以平凡,但你必须稳定。

*源码地址*

1、公众号“Codee君”回复“每日一Go”获取源码

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


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