Go基础(⑤Consul)

发布于:2025-09-07 ⋅ 阅读:(17) ⋅ 点赞:(0)

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


    网站公告

    今日签到

    点亮在社区的每一天
    去签到