当前位置: 首页 > article >正文

【ETCD】【实操篇(十)】基于 ETCD 实现一个简单的服务注册及发现功能

在这里插入图片描述

ETCD 是一个高可用的分布式键值存储系统,它广泛应用于分布式系统中作为配置中心、服务注册与发现的核心组件。通过 ETCD 实现服务注册中心,能够有效地解决服务发现与动态扩展的问题,确保各个服务能够在分布式环境中进行自动发现与连接。

在本篇文章中,我们将通过手撸一个基于 ETCD 的服务注册中心,演示如何使用 ETCD 实现服务注册与发现,并分析其技术原理。

目录

      • 技术原理
      • 1. 服务注册流程
      • 2. 服务发现流程
      • 3. 实现步骤
        • 3.1 环境准备
        • 3.2 使用 Go 实现服务注册与发现
          • 1. 安装 Go 客户端
          • 2. 创建服务注册与发现的 Go 代码
        • 3.3 代码分析
        • 3.4 运行服务注册中心
      • 4. 总结

技术原理

服务注册与发现通常需要两个关键功能:

  1. 服务注册:服务向服务注册中心报告自己正在运行的信息,例如服务地址、端口、健康检查状态等。
  2. 服务发现:其他服务可以通过查询服务注册中心获取到注册的服务信息,以实现动态连接。

ETCD 提供了非常适合用于服务注册与发现的特性:

  • 键值存储:ETCD 是一个分布式键值存储系统,服务注册信息可以以键值对的形式存储在 ETCD 中。
  • 租约机制(Lease):ETCD 支持租约机制,使得注册的服务在超时后自动失效,非常适合用来实现服务的自动过期与清理。
  • 监听机制(Watch):ETCD 提供了监听机制,可以实时监控某个键的变化,当服务注册或注销时,监听到变化的客户端能够及时获得更新。

1. 服务注册流程

服务在启动时需要向 ETCD 注册自己的信息。这些信息通常包括:

  • 服务名称(如:my-service
  • 服务实例的地址(如:http://127.0.0.1:8080
  • 服务的健康检查信息
  • 服务的租约(让服务在一定时间后自动注销)

2. 服务发现流程

其他服务通过查询 ETCD 获取到已注册服务的实例信息,并与其进行通信。为了支持动态的服务发现,服务发现者可以通过监听相关的键(即服务信息的键),实时获取服务的变化。


3. 实现步骤

我们将实现一个基于 ETCD 的服务注册与发现系统,包括服务注册、心跳续约、服务注销和服务发现。

3.1 环境准备

首先,确保你已经安装并启动了 ETCD。如果你尚未安装 ETCD,可以使用以下命令进行安装和启动:

# 安装 etcd
curl -L https://github.com/etcd-io/etcd/releases/download/v3.5.0/etcd-v3.5.0-linux-amd64.tar.gz -o etcd.tar.gz
tar xzvf etcd.tar.gz
cd etcd-v3.5.0-linux-amd64

# 启动 etcd 服务
./etcd --name my-etcd-node --data-dir /tmp/etcd-data --listen-peer-urls http://127.0.0.1:2380 --listen-client-urls http://127.0.0.1:2379 --advertise-client-urls http://127.0.0.1:2379 --initial-cluster-token etcd-cluster-1 --initial-cluster my-etcd-node=http://127.0.0.1:2380 --initial-cluster-state new
3.2 使用 Go 实现服务注册与发现

我们将使用 Go 编写服务注册中心,以下是主要实现步骤:

1. 安装 Go 客户端

首先,我们需要安装 ETCD 的 Go 客户端:

go get go.etcd.io/etcd/v3
2. 创建服务注册与发现的 Go 代码

创建一个 Go 程序,完成服务注册、心跳续约、注销和发现的功能。

package main

import (
	"context"
	"fmt"
	"github.com/google/uuid" // 用于生成实例ID
	clientv3 "go.etcd.io/etcd/client/v3"
	"log"
	"time"
)

var cli *clientv3.Client

func init() {
	// 初始化etcd客户端
	var err error
	cli, err = clientv3.New(clientv3.Config{
		Endpoints:   []string{"127.0.0.1:2379"}, // 替换为你的etcd集群地址
		DialTimeout: 5 * time.Second,
	})
	if err != nil {
		log.Fatal(err)
	}
}

// 服务注册
func registerService(serviceName, serviceAddress string) (clientv3.LeaseID, error) {
	// 生成一个唯一的实例ID
	instanceID := uuid.New().String()

	// 创建租约
	grantResp, err := cli.Grant(context.Background(), 10)
	if err != nil {
		return 0, err
	}

	// 将服务信息注册到 ETCD,使用 serviceName + instanceID 作为唯一的注册键
	key := fmt.Sprintf("/services/%s/%s", serviceName, instanceID)
	_, err = cli.Put(context.Background(), key, serviceAddress, clientv3.WithLease(grantResp.ID))
	if err != nil {
		return 0, err
	}
	cli.KeepAlive(context.Background(), grantResp.ID)

	// 返回租约 ID,后续可以用来续约
	return grantResp.ID, nil
}

// 服务注销
func deregisterService(serviceName, instanceID string) error {
	key := fmt.Sprintf("/services/%s/%s", serviceName, instanceID)
	_, err := cli.Delete(context.Background(), key)
	return err
}

// 服务发现
func discoverService(serviceName string) ([]string, error) {
	// 查询所有与 serviceName 相关的服务实例
	keyPrefix := fmt.Sprintf("/services/%s", serviceName)
	resp, err := cli.Get(context.Background(), keyPrefix, clientv3.WithPrefix())
	if err != nil {
		return nil, err
	}
	if len(resp.Kvs) == 0 {
		return nil, fmt.Errorf("没有找到该服务的实例")
	}

	// 提取所有服务实例地址
	var serviceAddresses []string
	for _, kv := range resp.Kvs {
		serviceAddresses = append(serviceAddresses, string(kv.Value))
	}
	return serviceAddresses, nil
}

func main() {
	// 连接到 ETCD 集群
	var err error
	cli, err = clientv3.New(clientv3.Config{
		Endpoints:   []string{"http://127.0.0.1:2379"},
		DialTimeout: 5 * time.Second,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer cli.Close()

	// 注册多个服务实例
	_, err = registerService("my-service", "http://127.0.0.1:8081")
	if err != nil {
		log.Fatalf("服务注册失败: %v", err)
	}
	log.Println("服务实例 1 注册成功,地址:http://127.0.0.1:8081")

	_, err = registerService("my-service", "http://127.0.0.1:8082")
	if err != nil {
		log.Fatalf("服务注册失败: %v", err)
	}
	log.Println("服务实例 2 注册成功,地址:http://127.0.0.1:8082")

	// 服务发现
	serviceAddresses, err := discoverService("my-service")
	if err != nil {
		log.Printf("服务发现失败: %v", err)
	} else {
		log.Printf("发现服务实例:%v", serviceAddresses)
	}

	// 模拟服务注销
	time.Sleep(20 * time.Second)
	err = deregisterService("my-service", "instance-1") // 示例:注销特定实例
	if err != nil {
		log.Fatalf("服务注销失败: %v", err)
	}
	log.Println("服务实例 1 已注销")

	// 服务发现
	serviceAddresses, err = discoverService("my-service")
	if err != nil {
		log.Printf("服务发现失败: %v", err)
	} else {
		log.Printf("发现服务实例:%v", serviceAddresses)
	}
}

3.3 代码分析
  1. 注册服务:服务通过 registerService 函数向 ETCD 注册自己的信息,使用了 ETCD 的租约机制确保服务在指定时间后自动注销。如果需要续约,服务可以通过 keepAliveService 进行定期续约。

  2. 服务注销:如果服务关闭或退出,我们可以使用 deregisterService 函数显式删除注册信息。

  3. 服务发现:服务发现者通过 discoverService 函数从 ETCD 获取服务注册信息,动态地获取服务地址。

  4. 租约与心跳:每个服务注册都有一个租约,租约会在一定时间后过期,服务可以定期续约来延长租期,避免服务被自动清除。

3.4 运行服务注册中心

运行上述 Go 程序后,你可以看到服务会注册到 ETCD 中,服务会持续进行续约,直到程序终止。其他服务可以通过相同的 discoverService 方法进行服务发现。


4. 总结

通过基于 ETCD 实现的服务注册中心,服务能够方便地注册到 ETCD 中,利用 ETCD 的租约机制实现自动注销与过期。同时,借助 ETCD 提供的监听机制,其他服务可以实时发现注册信息,动态连接到服务。这种方式简单而高效,适用于分布式系统中的服务注册与发现。

在实际应用中,可以通过增强健康检查、增加负载均衡、支持多租户等方式对该服务注册中心进行扩展,使其满足更多的实际需求。


http://www.kler.cn/a/452921.html

相关文章:

  • SLAM/数字图象处理基础
  • Java抽象工厂+单例模式
  • python+reportlab创建PDF文件
  • Ubuntu20.04 交叉编译Qt5.15.15 for rk3588
  • UDP传输层通信协议详解
  • linux创建虚拟串口
  • VScode实时检查c++语法错误
  • 【STM32 Modbus编程】-作为从设备写入寄存器
  • Linux 中检查 Apache Web Server (httpd) 正常运行时间的 4 种方法
  • 开源轮子 - HTTP Client组件
  • 关于Qt中query.addBindValue()和query.bindValue()报错:Parameter count mismatch
  • 深入理解 PyTorch 的 view() 函数:以多头注意力机制(Multi-Head Attention)为例 (中英双语)
  • Ubuntu 24使用systemctl配置service自动重启
  • AWS Transfer 系列:简化文件传输与管理的云服务
  • ubuntu22.04修改mysql存储路径
  • 【ES6复习笔记】数值扩展(16)
  • 【更新】Docker新手入门教程2:在Windows系统通过compose创建多个mysql镜像并配置应用
  • 数字IC后端设计实现十大精华主题分享
  • 【数据科学导论】第一二章·大数据与数据表示与存储
  • 如何不让场景UI受后处理影响
  • k-Means聚类算法 HNUST【数据分析技术】(2025)
  • 宝塔面板跨服务器数据同步教程:双机备份零停机
  • centos7 下使用 Docker Compose
  • Git工作流的最佳实践
  • flask后端开发(1):第一个Flask项目
  • stm32制作CAN适配器5--WinUsb上位机编写