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

Golang|etcd服务注册与发现 策略模式

  • etcd 是一个开源的 分布式键值存储系统(Key-Value Store),主要用于配置共享和服务发现。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • ETCD是一个键值(KV)数据库,类似于Redis,支持分布式集群。
  • ETCD也可以看作是一个分布式文件系统,类似于ZooKeeper,可以对文件和目录进行监听。
  • 在服务注册场景下,ETCD中的key是类似于文件路径的字符串,value为空。
  • 每台服务器启动后,会主动将自己的IP地址和提供的服务名称写入ETCD。
  • 为了防止key过期,服务器会每隔一段时间(如9秒)重新写入自己的信息。
  • 通过设置租期(如2秒),ETCD可以在服务器宕机后及时清理过期的key。
  • 客户端通过查询ETCD来获取能够提供服务的服务器IP地址。
  • 客户端可以查询具有特定前缀的key,以获取所有提供相同服务的服务器IP。
  • ETCD支持监听功能,客户端可以监听特定前缀的变化,实时获取新的服务器信息。
package service_hubimport ("context""errors""github.com/jmh000527/criker-search/index_service/load_balancer""github.com/jmh000527/criker-search/utils""go.etcd.io/etcd/api/v3/v3rpc/rpctypes"etcdv3 "go.etcd.io/etcd/client/v3""strings""sync""time"
)// EtcdServiceHub 服务注册中心,使用单例模式构造。
// 该服务用于与etcd进行交互,管理服务的注册、注销以及心跳续约等功能。
type EtcdServiceHub struct {client             *etcdv3.Client             // etcd客户端,用于与etcd进行操作heartbeatFrequency int64                      // 服务续约的心跳频率,单位:秒watched            sync.Map                   // 存储已经监视的服务,以避免重复监视loadBalancer       load_balancer.LoadBalancer // 负载均衡策略的接口,支持多种负载均衡实现
}const (ServiceRootPath = "/criker-search" // etcd key的前缀
)var (etcdServiceHub *EtcdServiceHub // 该全局变量包外不可见,包外想使用时通过GetServiceHub()获得hubOnce        sync.Once       // 单例模式需要用到一个once
)// GetServiceHub ServiceHub的构造函数,采用单例模式。
//
// 参数:
//   - etcdServers: 包含etcd服务器地址的字符串切片。
//   - heartbeatFrequency: 心跳频率,表示服务心跳的间隔时间(以秒为单位)。
//
// 返回值:
//   - *EtcdServiceHub: 返回一个初始化好的EtcdServiceHub实例。
func GetServiceHub(etcdServers []string, heartbeatFrequency int64) *EtcdServiceHub {// 检查是否已经存在etcdServiceHub实例if etcdServiceHub == nil {// 使用sync.Once确保单例模式,hubOnce.Do中的代码块只会被执行一次hubOnce.Do(func() {// 创建一个新的etcd客户端,连接到指定的etcd服务器client, err := etcdv3.New(etcdv3.Config{Endpoints:   etcdServers,     // etcd 服务器的地址列表DialTimeout: 3 * time.Second, // 连接超时时间})if err != nil {// 如果连接etcd服务器失败,记录错误并终止程序utils.Log.Fatal("连接etcd失败:", err)}// 初始化一个新的EtcdServiceHub实例etcdServiceHub = &EtcdServiceHub{client:             client,                      // 设置etcd客户端heartbeatFrequency: heartbeatFrequency,          // 设置心跳频率loadBalancer:       &load_balancer.RoundRobin{}, // 使用Round-Robin负载均衡策略}})}// 返回已初始化的etcdServiceHub实例return etcdServiceHub
}// RegisterService 注册服务。
// 第一次注册时,会向etcd写入一个key,并创建一个租约;后续注册仅进行续约。
//
// 参数:
//   - service: 微服务的名称。
//   - endpoint: 微服务服务器的地址。
//   - leaseId: 租约ID,第一次注册时应置为0。
//
// 返回值:
//   - etcdv3.LeaseID: 返回租约ID。
//   - error: 返回错误信息,如果操作成功则为nil。
func (hub *EtcdServiceHub) RegisterService(service, endpoint string, leaseId etcdv3.LeaseID) (etcdv3.LeaseID, error) {// 检查是否为首次注册(租约ID是否小于等于0)if leaseId <= 0 {// 首次注册: 创建一个新的租约,租约的有效期为heartbeatFrequency秒leaseGrantResponse, err := hub.client.Grant(context.Background(), hub.heartbeatFrequency)if err != nil {// 如果创建租约失败,记录错误并返回utils.Log.Printf("创建租约失败: %v", err)return 0, err}// 构建服务在etcd中的key,路径形如: /{ServiceRootPath}/{service}/{endpoint}key := strings.TrimRight(ServiceRootPath, "/") + "/" + service + "/" + endpoint// 将服务注册到etcd中,并将租约与该服务绑定_, err = hub.client.Put(context.Background(), key, "", etcdv3.WithLease(leaseGrantResponse.ID))if err != nil {// 如果注册服务失败,记录错误并返回utils.Log.Printf("服务注册失败: %v", err)return leaseGrantResponse.ID, err}utils.Log.Printf("成功注册服务: %v", key)// 返回新的租约IDreturn leaseGrantResponse.ID, nil} else {// 续约: 通过租约ID进行续租操作_, err := hub.client.KeepAliveOnce(context.Background(), leaseId)if errors.Is(err, rpctypes.ErrLeaseNotFound) {// 如果续租时发现租约不存在,则重新注册服务,将leaseID置为0重新进行注册utils.Log.Printf("未找到租约,重新注册服务")return hub.RegisterService(service, endpoint, 0)} else if err != nil {// 如果续租过程中发生其他错误,记录错误并返回utils.Log.Printf("续租失败: %v", err)return 0, err}// 如果续租成功,则返回现有的租约IDreturn leaseId, nil}
}// UnregisterService 主动注销服务。
// 从etcd中删除服务的注册信息。
//
// 参数:
//   - service: 微服务的名称。
//   - endpoint: 微服务服务器的地址。
//
// 返回值:
//   - error: 返回错误信息,如果操作成功则为nil。
func (hub *EtcdServiceHub) UnregisterService(service string, endpoint string) error {// 构建服务在etcd中的key,路径形如: /{ServiceRootPath}/{service}/{endpoint}key := strings.TrimRight(ServiceRootPath, "/") + "/" + service + "/" + endpoint// 从etcd中删除服务注册信息_, err := hub.client.Delete(context.Background(), key)if err != nil {// 如果删除操作失败,记录错误并返回utils.Log.Printf("注销服务失败: %v", err)return err}// 成功注销服务,记录日志utils.Log.Printf("成功注销服务: %v", key)return nil
}// GetServiceEndpoints 服务发现。
// 从etcd中查询指定服务的所有endpoint,并返回这些endpoint的列表。
// 参数:
//   - service: 微服务的名称。
//
// 返回值:
//   - []string: 包含所有服务endpoint的列表。如果查询失败,则返回nil。
func (hub *EtcdServiceHub) GetServiceEndpoints(service string) []string {// 构造服务的key前缀,用于获取服务的所有endpointprefix := strings.TrimRight(ServiceRootPath, "/") + "/" + service + "/"// 从etcd中获取以指定前缀为开头的所有key-value对getResponse, err := hub.client.Get(context.Background(), prefix, etcdv3.WithPrefix())if err != nil {// 如果获取服务endpoint失败,记录错误并返回nilutils.Log.Printf("从etcd获取服务端点失败: %v", err)return nil}// 构造返回的endpoint列表endpoints := make([]string, 0, len(getResponse.Kvs))for _, kv := range getResponse.Kvs {// 从key中提取endpointpath := strings.Split(string(kv.Key), "/")endpoints = append(endpoints, path[len(path)-1])}// 记录获取到的服务endpointutils.Log.Printf("最新的服务端点: %v", endpoints)return endpoints
}// GetServiceEndpoint 根据负载均衡策略从服务端点中选择一个。
// 通过调用负载均衡策略的Take方法,从获取的服务端点列表中选择一个。
//
// 参数:
//   - service: 微服务的名称。
//
// 返回值:
//   - string: 选择的服务端点地址。
func (hub *EtcdServiceHub) GetServiceEndpoint(service string) string {// 获取指定服务的所有端点endpoints := hub.GetServiceEndpoints(service)// 使用负载均衡策略选择一个端点return hub.loadBalancer.Take(endpoints)
}// Close 关闭etcd客户端连接。
// 释放etcd客户端占用的资源,并记录关闭连接的状态。
//
// 返回值:
//   - 无
func (hub *EtcdServiceHub) Close() {// 尝试关闭etcd客户端连接err := hub.client.Close()if err != nil {// 如果关闭连接失败,记录错误日志utils.Log.Printf("关闭etcd客户端连接失败: %v", err)}
}
  • ETCD提供API用于服务的注册与发现。
  • 服务中心的核心是client,用于连接到ETCD。
  • 服务注册后,需要定期上报心跳以保持存活状态。
  • service worker单独部署在服务器上,连接service hub使用单例模式。
  • 通过once实现单例模式,判断是否已创建实例。
  • 使用ETCD new方法连接,传入endpoints和配置信息。
  • 配置中核心是endpoints,需要提供ETCD集群的多个IP。
  • 连接超时设置为3秒,确保连接可靠性。
  • 服务启动时,首先申请租约并获取租约ID。
  • 将服务信息(service name + ip:port)注册到ETCD中,并设置租约。
  • 定期续租以保持服务存活状态。
  • 如果租约ID不存在,则重新注册服务。
  • 提供注销函数,传入service name和endpoint IP。
  • 从ETCD中删除对应的key,完成注销。
  • 服务调用方通过get service函数获取服务列表。
  • 传入service name作为前缀,查询满足前缀的key。
  • 返回所有匹配key的IP列表,供调用方选择。

  • 策略模式
// GetServiceEndpoint 根据负载均衡策略从服务端点中选择一个。
// 通过调用负载均衡策略的Take方法,从获取的服务端点列表中选择一个。
//
// 参数:
//   - service: 微服务的名称。
//
// 返回值:
//   - string: 选择的服务端点地址。
func (hub *EtcdServiceHub) GetServiceEndpoint(service string) string {// 获取指定服务的所有端点endpoints := hub.GetServiceEndpoints(service)// 使用负载均衡策略选择一个端点return hub.loadBalancer.Take(endpoints)
}
  • 由于调用方希望直接获取一台服务器进行接口通信,服务中心通过策略模式,将负载均衡算法的实现交给外部,采用接口方式定义负载均衡策略,并展示了轮询和随机两种简单的负载均衡策略实现,强调了在并发环境下确保累加操作的线程安全性。

在这里插入图片描述

package load_balancer// LoadBalancer 负载均衡接口,定义选择Endpoint的方法
type LoadBalancer interface {// Take 从给定的端点列表中选择一个Take(endpoints []string) string
}import "math/rand"// RandomSelect 负载均衡算法:随机选择法
// 随机选择算法从列表中随机选择一个端点
type RandomSelect struct{}// Take 选择一个Endpoint,根据随机选择算法
func (b *RandomSelect) Take(endpoints []string) string {if len(endpoints) == 0 {return ""}// 从端点列表中随机选择一个索引index := rand.Intn(len(endpoints))return endpoints[index]
}import "sync/atomic"// RoundRobin 负载均衡算法:轮询法
// 轮询法确保每个请求轮流被分配到列表中的每个端点
type RoundRobin struct {acc int64 // 记录累计请求次数
}// Take 选择一个Endpoint,根据轮询算法
func (b *RoundRobin) Take(endpoints []string) string {if len(endpoints) == 0 {return ""}// 线程安全地增加请求次数n := atomic.AddInt64(&b.acc, 1)// 计算要选择的Endpoint的索引index := int(n % int64(len(endpoints)))return endpoints[index]
}

相关文章:

Golang|etcd服务注册与发现 策略模式

etcd 是一个开源的 分布式键值存储系统&#xff08;Key-Value Store&#xff09;&#xff0c;主要用于配置共享和服务发现。 ETCD是一个键值&#xff08;KV&#xff09;数据库&#xff0c;类似于Redis&#xff0c;支持分布式集群。ETCD也可以看作是一个分布式文件系统&#xff…...

深度解析UniApp盲盒系统开发:从源码架构到多端部署全流程

​一、正版盲盒系统的技术选型与源码设计​ ​跨平台开发框架的核心配置​ ​UniApp多端适配方案​ 环境搭建&#xff1a;全局安装vue/cli与npm install -g dcloudio/uni-cli&#xff0c;通过uni -V验证版本&#xff08;需≥3.0&#xff09;。多端编译命令&#xff1a; # 编译微…...

STM32的OLED显示程序亲测可用:适用于多种场景的稳定显示解决方案

STM32的OLED显示程序亲测可用&#xff1a;适用于多种场景的稳定显示解决方案 【下载地址】STM32的OLED显示程序亲测可用 这是一套专为STM32设计的OLED显示程序&#xff0c;经过实际测试&#xff0c;运行稳定可靠。支持多种OLED屏幕尺寸和类型&#xff0c;提供丰富的显示效果&am…...

【AI News | 20250529】每日AI进展

AI Repos 1、WebAgent 阿里巴巴通义实验室近日发布了WebDancer&#xff0c;一款旨在实现自主信息搜索的原生智能体搜索推理模型。WebDancer采用ReAct框架&#xff0c;通过分阶段训练范式&#xff0c;包括浏览数据构建、轨迹采样、监督微调和强化学习&#xff0c;赋予智能体自主…...

Day12 - 计算机网络 - HTTP

HTTP常用状态码及含义&#xff1f; 301和302区别&#xff1f; 301&#xff1a;永久性移动&#xff0c;请求的资源已被永久移动到新位置。服务器返回此响应时&#xff0c;会返回新的资源地址。302&#xff1a;临时性性移动&#xff0c;服务器从另外的地址响应资源&#xff0c;但…...

Linux驱动学习笔记(十)

热插拔 1.热插拔&#xff1a;就是带电插拔&#xff0c;即允许用户在不关闭系统&#xff0c;不切断电源的情况下拆卸或安装硬盘&#xff0c;板卡等设备。热插拔是内核和用户空间之间&#xff0c;通过调用用户空间程序实现交互来实现的&#xff0c;当内核发生了某种热拔插事件时…...

如何优化Elasticsearch的搜索性能?

优化 Elasticsearch 的搜索性能需要从索引设计、查询优化、硬件配置和集群调优等多方面入手。以下是系统化的优化策略和实操建议: 一、索引设计优化 1. 合理设置分片数 分片大小:单个分片建议 10-50GB(超过50GB会影响查询性能)。分片数量: 总分片数 ≤ 节点数 1000(避免…...

TI dsp FSI (快速串行接口)

简介 快速串行接口&#xff08;FSI - Fast Serial Interface &#xff09;模块是一种串行通信外设&#xff0c;能够在隔离设备之间实现可靠的高速通信。在两个没有共同电源和接地连接的电子电路必须交换信息的情况下&#xff0c;电气隔离设备被使用。 虽然隔离设备促进了信号通…...

责任链模式:构建灵活可扩展的请求处理体系(Java 实现详解)

一、责任链模式核心概念解析 &#xff08;一&#xff09;模式定义与本质 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是一种行为型设计模式&#xff0c;其核心思想是将多个处理者对象连成一条链&#xff0c;并沿着这条链传递请求&#xff0c;直到有某…...

nlp中的频率就是权重吗

&#x1f522; 一、“频率”是什么&#xff1f; 在 NLP 中&#xff0c;**词频&#xff08;frequency&#xff09;**通常指的是&#xff1a; 某个单词或 token 在语料库中出现的次数&#xff08;或比例&#xff09; 举例&#xff1a; "The cat sat on the mat. The cat i…...

融智学“新五常”框架:五维方式的重构与协同

融智学“新五常”框架&#xff1a;五维方式的重构与协同 一、理论基底&#xff1a;从传统老五常到当代新五常的范式跃迁 邹晓辉教授提出的新五常&#xff08;生活方式DBA、学习方式DBA、工作方式DBA、旅行方式DBA、娱乐方式DBA&#xff09;&#xff0c;本质是将融智学的核心原…...

wechat-003-学习笔记

1.路由跳转页面&#xff1a;携带的参数会出现在onlaod中的options中。 注意&#xff1a;原生小程序对路由传参的长度也有限制&#xff0c;过长会被截掉。 2.wx.setNavigationBarTitle(Object object) 动态设置当前页面的标题 3.在根目录中的app.json文件中配置 后台播放音乐的能…...

【大模型微调】魔搭社区GPU进行LLaMA-Factory微调大模型自我认知

文章概要&#xff1a; 本文是一篇详细的技术教程&#xff0c;介绍如何使用魔搭社区&#xff08;ModelScope&#xff09;的GPU资源来进行LLaMA-Factory的模型微调。文章分为11个主要步骤&#xff0c;从环境准备到最终的模型测试&#xff0c;系统地介绍了整个微调流程。主要内容包…...

基于MATLAB编程针对NCV检测数据去漂移任务的完整解决方案

以下为针对NCV检测数据去漂移任务的完整解决方案&#xff0c;基于MATLAB编程实现&#xff0c;结构清晰&#xff0c;内容详实&#xff0c;满足技术深度。 NCV信号尾部漂移处理与分析 1. 任务背景与目标 神经传导速度&#xff08;NCV&#xff09;检测信号易受环境干扰与设备漂移…...

【数据结构】哈希表的实现

文章目录 1. 哈希的介绍1.1 直接定址法1.2 哈希冲突1.3 负载因子1.4 哈希函数1.4.1 除法散列法/除留余数法1.4.2 乘法散列法1.4.3 全域散列法 1.5 处理哈希冲突1.5.1 开放地址法1.5.1.1 线性探测1.5.1.2 二次探测1.5.1.3 双重探测1.5.1.4 三种探测方法对比 1.6.3 链地址法 2. 哈…...

永磁同步电机控制算法--基于电磁转矩反馈补偿的新型IP调节器

一、基本原理 先给出IP速度控制器还是PI速度控制器的传递函数&#xff1a; PI调节器 IP调节器 从IP速度控制器还是PI速度控制器的传递函数可以看出&#xff0c;系统的抗负载转矩扰动能力相同,因此虽然采用IP速度控制器改善了转速环的超调问题&#xff0c;但仍然需要通过其他途…...

RabbitMQ 应用 - SpringBoot

以下介绍的是基于 SpringBoot 的 RabbitMQ 开发介绍 Spring Spring AMQP RabbitMQ RabbitMQ tutorial - "Hello World!" | RabbitMQ 工程搭建步骤: 1.引入依赖 2.编写 yml 配置,配置基本信息 3.编写生产者代码 4.编写消费者代码 定义监听类,使用 RabbitListener…...

基于递归思想的系统架构图自动化生成实践

文章目录 一、核心思想解析二、关键技术实现1. 动态布局算法2. 样式规范集成3. MCP服务封装三、典型应用场景四、最佳实践建议五、扩展方向一、核心思想解析 本系统通过递归算法实现了Markdown层级结构到PPTX架构图的自动转换,其核心设计思想包含两个维度: 数据结构递归:将…...

OpenGL Chan视频学习-9 Index Buffers inOpenGL

bilibili视频链接&#xff1a; 【最好的OpenGL教程之一】https://www.bilibili.com/video/BV1MJ411u7Bc?p5&vd_source44b77bde056381262ee55e448b9b1973 函数网站&#xff1a; docs.gl 说明&#xff1a; 1.之后就不再单独整理网站具体函数了&#xff0c;网站直接翻译会…...

《基于AIGC的智能化多栈开发新模式》研究报告重磅发布! ——AI重塑软件工程,多栈开发引领未来

在人工智能技术迅猛发展的浪潮下&#xff0c;软件开发领域正经历一场前所未有的范式革命。在此背景下&#xff0c;由贝壳找房&#xff08;北京&#xff09;科技有限公司、中国信息通信研究院云计算与大数据研究所联合编写&#xff0c;阿里、腾讯、北京大学、南京大学、同济大学…...

热门大型语言模型(LLM)应用开发框架

我们来深入探索这些强大的大型语言模型&#xff08;LLM&#xff09;应用开发框架&#xff0c;并且我会尝试用文本形式描述一些核心的流程图&#xff0c;帮助您更好地理解它们的工作机制。由于我无法直接生成图片&#xff0c;我会用文字清晰地描述流程图的各个步骤和连接。 Lang…...

Nginx安全防护与HTTPS部署实战

目录 前言一. 核心安全配置1. 隐藏版本号2. 限制危险请求方法3. 请求限制&#xff08;CC攻击防御&#xff09;&#xff08;1&#xff09;使用nginx的limit_req模块限制请求速率&#xff08;2&#xff09;压力测试验证 4. 防盗链 二. 高级防护1. 动态黑名单&#xff08;1&#x…...

JAVA重症监护系统源码 ICU重症监护系统源码 智慧医院重症监护系统源码

智慧医院重症监护系统源码 ICU重症监护系统源码 开发语言&#xff1a;JavaVUE ICU护理记录&#xff1a;实现病人数据的自动采集&#xff0c;实时记录监护过程数据。支持主流厂家的监护仪、呼吸机等床旁数字化设备的数据采集。对接检验检查系统&#xff0c;实现自动化录入。喜…...

静态资源js,css免费CDN服务比较

静态资源js,css免费CDN服务比较 分析的 CDN 服务列表&#xff1a; BootCDN (https://cdn.bootcdn.net/ajax/libs)jsDelivr (主域名) (https://cdn.jsdelivr.net/npm)jsDelivr (Gcore 镜像) (https://gcore.jsdelivr.net/npm)UNPKG (https://unpkg.com)ESM (https://esm.sh)By…...

组合型回溯+剪枝

本篇基于b站灵茶山艾府。 77. 组合 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;n 4, k 2 输出&#xff1a; [[2,4],[3,4],[2,3],[1,2],[1,3],[1,4], ]示例 2&#…...

python:机器学习(KNN算法)

本文目录&#xff1a; 一、K-近邻算法思想二、KNN的应用方式&#xff08; 一&#xff09;分类流程&#xff08;二&#xff09;回归流程 三、API介绍&#xff08;一&#xff09;分类预测操作&#xff08;二&#xff09;回归预测操作 四、距离度量方法&#xff08;一&#xff09;…...

【笔记】2025 年 Windows 系统下 abu 量化交易库部署与适配指南

#工作记录 前言 在量化交易的学习探索中&#xff0c;偶然接触到 2017 年开源的 abu 量化交易库&#xff0c;其代码结构和思路对新手理解量化回测、指标分析等基础逻辑有一定参考价值。然而&#xff0c;当尝试在 2025 年的开发环境中部署这个久未更新的项目时&#xff0c;遇到…...

小程序 - 视图与逻辑

个人简介 👨‍💻‍个人主页: 魔术师 📖学习方向: 主攻前端方向,正逐渐往全栈发展 🚴个人状态: 研发工程师,现效力于政务服务网事业 🇨🇳人生格言: “心有多大,舞台就有多大。” 📚推荐学习: 🍉Vue2 🍋Vue3 🍓Vue2/3项目实战 🥝Node.js实战 🍒T…...

ChatGPT Plus/Pro 订阅教程(支持支付宝)

订阅 ChatGPT Plus GPT-4 最简单&#xff0c;成功率最高的方案 1. 登录 chat.openai.com 依次点击 Login &#xff0c;输入邮箱和密码 2. 点击升级 Upgrade 登录自己的 OpenAI 帐户后&#xff0c;点击左下角的 Upgrade to Plus&#xff0c;在弹窗中选择 Upgrade plan。 如果…...

[蓝帽杯 2022 初赛]网站取证_2

一、找到与数据库有关系的PHP文件 打开内容如下&#xff0c;发现数据库密码是函数my_encrypt()返回的结果。 二、在文件夹encrypt中找到encrypt.php,内容如下&#xff0c;其中mcrypt已不再使用&#xff0c;所以使用php>7版本可能没有执行结果&#xff0c;需要换成较低版本…...