今天正式进入微服务的世界,什么是微服务?为什么要微服务?什么时候上微服务?服务注册与发现组件Consul如何使用?
一、什么是微服务?
简单来说,微服务是一种架构风格。它将一个庞大、复杂的单体应用(Monolith)拆分成一组小型、独立的服务。
-
独立性:每个服务运行在自己的进程中。
-
单一职责:一个服务只做好一件事(如:用户服务、订单服务、支付服务)。
-
轻量级通信:服务之间通过网络协议(通常是 HTTP/JSON 或 gRPC)进行通信。
-
去中心化:每个服务可以有自己的数据库、技术栈和发布节奏。
二、为什么要微服务?
企业选择微服务,核心动机通常是为了解决“规模化”带来的痛点:
-
独立部署(Speed):修个小 Bug 不再需要重新发布整个系统,只需发布受影响的微服务。
-
团队自治(Scale):不同团队负责不同服务。再也不用担心 50 个人在同一个代码库里提交代码导致的合并冲突。
-
故障隔离(Resilience):支付服务挂了,用户可能暂时不能买东西,但依然可以浏览商品。
-
按需伸缩(Efficiency):如果双 11 期间下单量大,你可以只给“订单服务”增加机器,而不是把昂贵的内存全部堆给整个单体应用。
三、什么时候上微服务?
微服务不是免费的午餐,它会显著增加运维、网络延迟和分布式事务的复杂性。“在没有足够的复杂性之前,不要考虑微服务。”
你可以通过以下信号判断是否该转型了:
-
团队规模:当一个团队超过 10-15 人,沟通成本开始指数级上升。
-
发布频率:每天都要发布多个功能,单体架构的编译和测试速度成了瓶颈。
-
技术债务:代码耦合严重,改 A 坏 B,没人敢动老代码。
-
性能瓶颈:系统某一部分需要高并发支持,但无法单独扩容。
四、服务注册与发现组件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”获取源码
如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!