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 main
import (
"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. 注册服务到Consul
registerToConsul(*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) {
// 连接Consul
config := 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, // 实例唯一ID
Address: "127.0.0.1", // 服务IP
Port: 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 main
import (
"fmt"
"io/ioutil"
"net/http"
"sync/atomic"
"github.com/hashicorp/consul/api"
)
var (
roundRobinIndex int32 = 0 // 轮询计数器(原子操作保证并发安全)
)
func main() {
// 1. 连接Consul
config := 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))].Service
targetURL := 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