Go基础(⑤Consul)
Consul 简单说就是个 “服务管家”,专门帮你管理服务器、App 这些 “服务” 的。
比如你公司有很多台服务器,上面跑着各种程序(像登录功能、支付功能),这些都叫 “服务”。Consul 能干这几件事:
找得到谁在线:它能自动发现哪些服务在运行,哪些挂了,不用你手动记 IP 地址。
告诉大家去哪找服务:比如你想调用支付功能,Consul 会告诉你现在哪个服务器上的支付服务是好的,直接去连它。
保平安:时刻盯着服务状态,一旦某个服务挂了,马上通知大家,别再往那儿发请求了。
管配置:比如所有服务都需要一个数据库密码,你在 Consul 里改一次,所有服务就能自动拿到新密码,不用一台台服务器去改。
总之,就是帮你在一堆服务里搞清楚 “谁活着”“在哪”“怎么通信”,让复杂的系统跑起来更稳、更省心。
安装与启动 Consul
1. 下载安装
官网下载地址:Install | Consul | HashiCorp Developer
根据操作系统选择对应版本,解压后得到可执行文件 consul
2. 验证安装
consul --version # 输出版本信息表示安装成功
3. 启动开发模式
consul agent -dev -client=0.0.0.0
-dev:开发模式,无需复杂配置,数据存于内存
-client=0.0.0.0:允许外部访问(默认只允许 localhost)
启动成功后,可通过以下方式访问:
Web 管理界面:http://localhost:8500
命令行交互:使用 consul 命令
API 接口:默认端口 8500(HTTP)
步骤 1:编写 “用户服务” 代码(提供/user/{id}
接口)
先写一个 Go 程序,功能是:
提供GET /user/{id}接口,返回用户信息
支持通过命令行指定端口(方便启动多个实例)
自动注册到 Consul(服务名统一为user-service)
创建文件user_service.go:
package mainimport ("flag""fmt""net/http""strconv""github.com/hashicorp/consul/api"
)func main() {// 1. 通过命令行参数指定端口(默认8081)port := flag.Int("port", 8081, "服务端口")flag.Parse()portStr := strconv.Itoa(*port) // 转成字符串用于URL// 2. 注册服务到ConsulregisterToConsul(*port)// 3. 定义API接口:/user/{id}http.HandleFunc("/user/", func(w http.ResponseWriter, r *http.Request) {// 提取URL中的id(例如:/user/123 → id=123)id := r.URL.Path[len("/user/"):]if id == "" {http.Error(w, "请提供用户ID", http.StatusBadRequest)return}// 返回用户信息,包含当前服务的端口(方便区分实例)fmt.Fprintf(w, "用户ID: %s,处理请求的服务端口: %d", id, *port)})// 4. 健康检查接口(供Consul判断服务是否存活)http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {w.WriteHeader(http.StatusOK)fmt.Fprint(w, "healthy")})// 5. 启动服务fmt.Printf("用户服务实例启动,端口: %d\n", *port)http.ListenAndServe(":"+portStr, nil)
}// 注册服务到Consul
func registerToConsul(port int) {// 连接Consulconfig := api.DefaultConfig()client, err := api.NewClient(config)if err != nil {fmt.Printf("连接Consul失败: %v\n", err)return}// 注册信息(服务名统一为user-service,ID唯一)serviceID := fmt.Sprintf("user-service-%d", port) // 每个实例ID唯一(用端口区分)reg := &api.AgentServiceRegistration{Name: "user-service", // 服务名(所有实例相同)ID: serviceID, // 实例唯一IDAddress: "127.0.0.1", // 服务IPPort: port, // 服务端口Check: &api.AgentServiceCheck{ // 健康检查HTTP: fmt.Sprintf("http://127.0.0.1:%d/health", port),Interval: "5s", // 每5秒检查一次Timeout: "1s",DeregisterCriticalServiceAfter: "10s", // 不健康10秒后自动注销},}// 执行注册if err := client.Agent().ServiceRegister(reg); err != nil {fmt.Printf("注册到Consul失败: %v\n", err)} else {fmt.Printf("已注册到Consul,服务ID: %s\n", serviceID)}
}
步骤 2:启动 3 个用户服务实例(不同端口)
打开 3 个终端窗口,分别启动 3 个实例(端口 8081、8082、8083):
终端 1(端口 8081):
go run user_service.go -port 8081
输出应包含:
已注册到Consul,服务ID: user-service-8081
用户服务实例启动,端口: 8081
验证注册结果:
访问 Consul 网页 http://localhost:8500 → 点击左侧Services
→ 点击user-service
,会看到 3 个健康实例(Status 为passing
)
步骤 3:编写 “负载均衡器” 代码(模拟分发请求)
这个程序的作用是:
作为统一入口(监听端口 8000)
接收用户请求(如/user/123)
从 Consul 获取所有健康的user-service实例
用 “轮询” 规则选择一个实例,转发请求
将实例的响应返回给用户
创建文件load_balancer.go:
package mainimport ("fmt""io/ioutil""net/http""sync/atomic""github.com/hashicorp/consul/api"
)var (roundRobinIndex int32 = 0 // 轮询计数器(原子操作保证并发安全)
)func main() {// 1. 连接Consulconfig := api.DefaultConfig()client, err := api.NewClient(config)if err != nil {fmt.Printf("连接Consul失败: %v\n", err)return}// 2. 定义负载均衡器的入口接口(监听8000端口)http.HandleFunc("/user/", func(w http.ResponseWriter, r *http.Request) {// a. 从Consul获取所有健康的user-service实例services, _, err := client.Health().Service("user-service", "", true, nil)if err != nil || len(services) == 0 {http.Error(w, "获取服务实例失败", http.StatusInternalServerError)return}// b. 轮询选择一个实例(索引自增,取模)index := atomic.AddInt32(&roundRobinIndex, 1) - 1 // 原子自增,避免并发问题selected := services[index%int32(len(services))].ServicetargetURL := fmt.Sprintf("http://%s:%d%s", selected.Address, selected.Port, r.URL.Path)// c. 转发请求到选中的实例fmt.Printf("转发请求到: %s\n", targetURL)resp, err := http.Get(targetURL)if err != nil {http.Error(w, "调用服务实例失败", http.StatusInternalServerError)return}defer resp.Body.Close()// d. 读取实例的响应,返回给用户body, _ := ioutil.ReadAll(resp.Body)w.Write(body)})// 3. 启动负载均衡器(统一入口)fmt.Println("负载均衡器启动,监听端口: 8000")http.ListenAndServe(":8000", nil)
}
启动负载均衡器:
go run load_balancer.go
123