徒手教你使用zookeeper编写服务发现

goim 徒手搭建 goim搭建 基于docker安装kafka和redis zookeeper version: '3.1' networks: default: external: name: zookeeper_network services: zoo1: image: zookeeper restart: always container_name: zoo1 hostname: zoo1 ports: - 2181:2181 v 阅读详情

640?wx_fmt=png

zookeeper是一个强一致【不严格】的分布式数据库,由多个节点共同组成一个分布式集群,挂掉任意一个节点,数据库仍然可以正常工作,客户端无感知故障切换。客户端向任意一个节点写入数据,其它节点可以立即看到最新的数据。

640?wx_fmt=png

zookeeper的内部是一个key/value存储引擎,key是以树状的形式构成了一个多级的层次结构,每一个节点既可以存储数据,又可以作为一个目录存放下一级子节点。

640?wx_fmt=png

zookeeper提供了创建/修改/删除节点的api,如果父节点没有创建,字节点会创建失败。如果父节点还有子节点,父节点不可以被删除。

zookeeper和客户端之间以socket形式进行双向通讯,客户端可以主动调用服务器提供的api,服务器可以主动向客户端推送事件。有多种事件可以watch,比如节点的增删改,子节点的增删改,会话状态变更等。

zookeeper的事件有传递机制,字节点的增删改触发的事件会向上层依次传播,所有的父节点都可以收到字节点的数据变更事件,所以层次太深/子节点太多会给服务器的事件系统带来压力,节点分配要做好周密的规划。

zookeeper满足了CAP定理的分区容忍性P和强一致性C,牺牲了高性能A【可用性蕴含性能】。zookeeper的存储能力是有限的,当节点层次太深/子节点太多/节点数据太大,都会影响数据库的稳定性。所以zookeeper不是一个用来做高并发高性能的数据库,zookeeper一般只用来存储配置信息。

zookeeper的读性能随着节点数量的提升能不断增加,但是写性能会随着节点数量的增加而降低,所以节点的数量不宜太多,一般配置成3个或者5个就可以了。

640?wx_fmt=png

图中可以看出当服务器节点增多时,复杂度会随之提升。因为每个节点和其它节点之间要进行p2p的连接。3个节点可以容忍挂掉1个节点,5个节点可以容忍挂掉2个节点。

客户端连接zookeeper时会选择任意一个节点保持长链接,后续通信都是通过这个节点进行读写的。如果该节点挂了,客户端会尝试去连接其它节点。

服务器会为每个客户端连接维持一个会话对象,会话的ID会保存在客户端。会话对象也是分布式的,意味着当一个节点挂掉了,客户端使用原有的会话ID去连接其它节点,服务器维持的会话对象还继续存在,并不需要重新创建一个新的会话。

如果客户端主动发送会话关闭消息,服务器的会话对象会立即删除。如果客户端不小心奔溃了,没有发送关闭消息,服务器的会话对象还会继续存在一段时间。这个时间是会话的过期时间,在创建会话的时候客户端会提供这个参数,一般是10到30秒。

也许你会问连接断开了,服务器是可以感知到的,为什么需要客户端主动发送关闭消息呢?

因为服务器要考虑网络抖动的情况,连接可能只是临时断开了。为了避免这种情况下反复创建和销毁复杂的会话对象以及创建会话后要进行的一系列事件初始化操作,服务器会尽量延长会话的生存时间。

zookeeper的节点可以是持久化(Persistent)的,也可以是临时(Ephermeral)的。所谓临时的节点就是会话关闭后,会话期间创建的所有临时节点会立即消失。一般用于服务发现系统,将服务进程的生命期和zookeeper子节点的生命期绑定在一起,起到了实时监控服务进程的存活的效果。

zookeeper还提供了顺序节点。类似于mysql里面的auto_increment属性。服务器会在顺序节点名称后自动增加自增的唯一后缀,保持节点名称的唯一性和顺序性。

还有一种节点叫着保护(Protected)节点。这个节点非常特殊,但是也非常常用。在应用服务发现的场合时,客户端创建了一个临时节点后,服务器节点挂了,连接断开了,然后客户端去重连到其它的节点。因为会话没有关闭,之前创建的临时节点还存在,但是这个时候客户端却无法识别去这个临时节点是不是自己创建的,因为节点内部并不存储会话ID字段。所以客户端会在节点名称上加上一个GUID前缀,这个前缀会保存在客户端,这样它就可以在重连后识别出哪个临时节点是自己之前创建的了。

接下来我们使用Go语言实现一下服务发现的注册和发现功能。

640?wx_fmt=png

如图所示,我们要提供api.user这样的服务,这个服务有3个节点,每个节点有不一样的服务地址,这3个节点各自将自己的服务注册进zk,然后消费者进行读取zk得到api.user的服务地址,任选一个节点地址进行服务调用。为了简单化,这里就没有提供权重参数了。在一个正式的服务发现里一般都有权重参数,用于调整服务节点之间的流量分配。

go get github.com/samuel/go-zookeeper/zk

首先我们定义一个ServiceNode结构,这个结构数据会存储在节点的data中,表示服务发现的地址信息。

type ServiceNode struct {
	Name string `json:"name"` // 服务名称,这里是user
	Host string `json:"host"`
	Port int    `json:"port"`
}

在定义一个服务发现的客户端结构体SdClient。

type SdClient struct {
	zkServers []string // 多个节点地址
	zkRoot    string // 服务根节点,这里是/api
	conn      *zk.Conn // zk的客户端连接
}

编写构造器,创建根节点

func NewClient(zkServers []string, zkRoot string, timeout int) (*SdClient, error) {
	client := new(SdClient)
	client.zkServers = zkServers
	client.zkRoot = zkRoot
	// 连接服务器
	conn, _, err := zk.Connect(zkServers, time.Duration(timeout)*time.Second)
	if err != nil {
		return nil, err
	}
	client.conn = conn
	// 创建服务根节点
	if err := client.ensureRoot(); err != nil {
		client.Close()
		return nil, err
	}
	return client, nil}// 关闭连接,释放临时节点func (s *SdClient) Close() {
	s.conn.Close()
}

func (s *SdClient) ensureRoot() error {
	exists, _, err := s.conn.Exists(s.zkRoot)
	if err != nil {
		return err
	}
	if !exists {
		_, err := s.conn.Create(s.zkRoot, []byte(""), 0, zk.WorldACL(zk.PermAll))
		if err != nil && err != zk.ErrNodeExists {
			return err
		}
	}
	return nil
}

值得注意的是代码中的Create调用可能会返回节点已存在错误,这是正常现象,因为会存在多进程同时创建节点的可能。如果创建根节点出错,还需要及时关闭连接。我们不关心节点的权限控制,所以使用zk.WorldACL(zk.PermAll)表示该节点没有权限限制。Create参数中的flag=0表示这是一个持久化的普通节点。

接下来我们编写服务注册方法

func (s *SdClient) Register(node *ServiceNode) error {
	if err := s.ensureName(node.Name); err != nil {
		return err
	}
	path := s.zkRoot + "/" + node.Name + "/n"
	data, err := json.Marshal(node)
	if err != nil {
		return err
	}
	_, err = s.conn.CreateProtectedEphemeralSequential(path, data, zk.WorldACL(zk.PermAll))
	if err != nil {
		return err
	}
	return nil}func (s *SdClient) ensureName(name string) error {
	path := s.zkRoot + "/" + name
	exists, _, err := s.conn.Exists(path)
	if err != nil {
		return err
	}
	if !exists {
		_, err := s.conn.Create(path, []byte(""), 0, zk.WorldACL(zk.PermAll))
		if err != nil && err != zk.ErrNodeExists {
			return err
		}
	}
	return nil
}

先要创建/api/user节点作为服务列表的父节点。然后创建一个保护顺序临时(ProtectedEphemeralSequential)子节点,同时将地址信息存储在节点中。什么叫保护顺序临时节点,首先它是一个临时节点,会话关闭后节点自动消失。其它它是个顺序节点,zookeeper自动在名称后面增加自增后缀,确保节点名称的唯一性。同时还是个保护性节点,节点前缀增加了GUID字段,确保断开重连后临时节点可以和客户端状态对接上。

接下来我们实现消费者获取服务列表方法

func (s *SdClient) GetNodes(name string) ([]*ServiceNode, error) {
	path := s.zkRoot + "/" + name
	// 获取字节点名称
	childs, _, err := s.conn.Children(path)
	if err != nil {
		if err == zk.ErrNoNode {
			return []*ServiceNode{}, nil
		}
		return nil, err
	}
	nodes := []*ServiceNode{}
	for _, child := range childs {
		fullPath := path + "/" + child
		data, _, err := s.conn.Get(fullPath)
		if err != nil {
			if err == zk.ErrNoNode {
				continue
			}
			return nil, err
		}
		node := new(ServiceNode)
		err = json.Unmarshal(data, node)
		if err != nil {
			return nil, err
		}
		nodes = append(nodes, node)
	}
	return nodes, nil
}

获取服务节点列表时,我们先获取字节点的名称列表,然后依次读取内容拿到服务地址。因为获取字节点名称和获取字节点内容不是一个原子操作,所以在调用Get获取内容时可能会出现节点不存在错误,这是正常现象。

将以上代码凑在一起,一个简单的服务发现包装就实现了。

最后我们看看如果使用以上代码,为了方便起见,我们将多个服务提供者和消费者写在一个main方法里。

func main() {
        // 服务器地址列表
	servers := []string{"192.168.0.101:2118", "192.168.0.102:2118", "192.168.0.103:2118"}
	client, err := NewClient(servers, "/api", 10)
	if err != nil {
		panic(err)
	}
	defer client.Close()
	node1 := &ServiceNode{"user", "127.0.0.1", 4000}
	node2 := &ServiceNode{"user", "127.0.0.1", 4001}
	node3 := &ServiceNode{"user", "127.0.0.1", 4002}
	if err := client.Register(node1); err != nil {
		panic(err)
	}
	if err := client.Register(node2); err != nil {
		panic(err)
	}
	if err := client.Register(node3); err != nil {
		panic(err)
	}
	nodes, err := client.GetNodes("user")
	if err != nil {
		panic(err)
	}
	for _, node := range nodes {
		fmt.Println(node.Host, node.Port)
	}
}

值得注意的是使用时一定要在进程退出前调用Close方法,否则zookeeper的会话不会立即关闭,服务器创建的临时节点也就不会立即消失,而是要等到timeout之后服务器才会清理。

640?wx_fmt=jpeg

阅读相关文章,关注公众号【码洞】

拿起键盘就是干:跟我一起徒手开发一套分布式IM系统 1、引言 老读者应该还记得我在去年国庆节前分享过一篇《技术干货:从零开始,教你设计一个百万级的消息推送系统》,虽然我在文中有贴一些伪代码,依然有些朋友希望能直接分享一些可以运行的源码。好吧,质疑我穷我无话可说(因为是真穷。。),怀疑我撸码的能力那是绝对不行,所以这次准备拉起键盘大干一场——徒手撸套分布式IM出来!^_^! 本文记录了我开发的一款面向IM学习者的 IM系统——CIM(全称:CRO... 阅读详情

相关推荐

go-zookeeper:Go的本机ZooKeeper客户端。 该项目不再维护。 请改用https:github.comgo-zookeeperzk

本机Go Zookeeper客户端库 执照 3句BSD。 请参阅许可文件。 该存储库不再维护 请使用主动维护派生。

大数据Zookeeper系列之Zookeeper分布式协调服务部署

一、部署准备 1. 安装介质 zookeeper-3.4.13:http://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.4.13/zookeeper-3.4.13.tar.gz 2. 主机规划 192.168.233.134 host14 192.168.233.133 host15 192.168.233.136 host16 二、...

飞翔的博客 43万+

OpenPCDet安装及其3D检测算法实现

OpenPCDet及pointpillar复现

weixin_58207716的博客 1万+

zookeeper的介绍和用docker搭建zookeeper集群,以及Go语言使用zookeeper

Apache ZooKeeper 是 Apache 软件基金会的一个软件项目,为大型分布式系统提供开源分布式配置服务、同步服务和命名注册。ZooKeeper原本是Hadoop的一个子项目,但现在它本身已经是一个顶级项目了。zookeeper是经典的分布式数据一致性解决方案,致力于为分布式应用提供一个高性能,高可用,且具有严格顺序访问控制能力的分布式协调存储服务。

m0_52530105的博客 4003

售货机非现金支付开发指南:MDB协议下VPOS刷卡器的那些坑

本文深入探讨了MDB协议下非现金支付设备的开发实践,重点解析了NAYAX VPOS刷卡器的集成难点与解决方案。从RS232通信参数配置到Always idle功能实现,提供了一套完整的售货机支付系统开发指南,帮助开发者规避常见陷阱,提升设备兼容性与交易稳定性。

weixin_29258297的博客 137

医疗信创国产化替代目前面临哪些难题?如何利用国产虚拟化技术促进医院信创建设

业务系统众多,存在大量信创改造模糊地带,既有老系统,也有新系统,故障点多,管理难度高,IT人力资源不足,其对硬件的支撑能力也提出了越来越高的要求,由此造成硬件成本、管理成本以及能耗成本都非常高。

云宏winhong的博客 2662

【GO】27. zookeeper golang库go-zookeeper实例

Golang 操作 zookeeper 使用库:https://github.com/samuel/go-zookeeper 文档地址:http://godoc.org/github.com/samuel/go-zookeeper/zk 连接zk server package main import ( "fmt" "github.com/samuel/go-zookeeper/zk" "time" ) func main() { // 创建zk连接地址 hosts := [].

chen_peng7的博客 1700

SpringCloudGateway网关高频20个面试题整理

Spring Cloud Gateway 是基于 Spring 5、Project Reactor 和 Spring Boot 2 构建的 API 网关,提供动态路由、监控、弹性、安全等核心功能。Spring Cloud Gateway 是 Spring Cloud 生态中的 API 网关,用于微服务架构中的请求路由、负载均衡、限流、鉴权等。实现@Component@Override// 逻辑处理。

u012024132的专栏 1114

Golang分布式应用之ZooKeeper

本文分析了ZooKeeper在分布式锁、服务发现等场景上的实现方式,并对比了与etcd的差异点。

Qinng的博客 1728

MEG预处理-Brainstorm

Brainstorm下载和安装 去Brainstorm官网进行下载:打开网页后,点击左侧Download;在跳出来的界面中进行简单的注册,注册完成后可以进行下载 解压后得到“brainstorm3”的文件夹,里面包含Brainstorm工具包的所有文件,把这个文件夹放在某个路径如"E:\matlab_tools\brainstorm3"即可 同样在某个路径建立空的文件夹命名为:brainstorm_db用于工具包运行过程中自动存储生成的文件,所以这个文件中生成的数据不要手动移动删除 在Matlab中,把.

AxeChen的博客 6127

WSL子系统启动报错 Wsl/Service/CreateInstance/CreateVm/HCS_E_SERVICE_NOT_AVAILABLE

WSL子系统启动报错 :Wsl/Service/CreateInstance/CreateVm/HCS_E_SERVICE_NOT_AVAILABLE 于是分析原因,最终发现是Windows功能中的虚拟机平台功能不知道什么时候被取消了。

Linux I Tell U 6万+

mysql 服务发现_如何实现一个简单的服务发现框架?

zookeeper是一个强一致(不严格)的分布式数据库,由多个节点共同组成一个分布式集群,挂掉任意一个节点,数据库仍然可以正常工作,客户端无感知故障切换。客户端向任意一个节点写入数据,其它节点可以立即看到最新的数据。zookeeper的内部是一个key/value存储引擎,key是以树状的形式构成了一个多级的层次结构,每一个节点既可以存储数据,又可以作为一个目录存放下一级子节点。zookeeper...

weixin_39603598的博客 315

RSL-RL:开源人形机器人强化学习控制研究库

摘要:RSL-RL是一款专为机器人领域设计的开源强化学习库,其核心特点是紧凑性、可修改性和高性能。该库聚焦PPO和BC两大主流算法,并整合了对称性增强、好奇心驱动等机器人专用技术。通过纯GPU训练优化,RSL-RL在大型仿真中展现出卓越的吞吐性能,已成功应用于腿足运动控制、全身协调等多个机器人研究场景。其轻量级架构包含运行器、算法和网络三个可独立修改的组件,支持TensorBoard等主流日志工具。该框架特别适合需要快速原型开发的机器人研究者,但不适用于通用机器学习基准测试。开源地址:https://git

2506_90492529的博客 1680

就是刚,徒手给面试官写了一个 RPC 框架

点击“终码一生”,关注,置顶公众号 每日技术干货,第一时间送达! 本文主要论述的是“RPC 实现原理”,那么首先明确一个问题什么是 RPC 呢?RPC 是 Remote Procedure Call 的缩写,即,远程过程调用。RPC 是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而开发人员无需额外地为这个交互编程。 值得注意是,两个或多个应用程序都分布在不同的服务器上,它们之间的调用都像是本地方法调用一样。接下来我们便来分析一下一次 RPC 调用发生了些什么?

终码一生 179

别再让 AI 瞎写 SAP 代码了!这套 Claude Code Skill 的验证机制才是关键

本文深入解析了专为SAP CAP开发设计的AI辅助工具sap-cap-capire的技术架构。该工具采用四层设计:协议层集成MCP和LSP实现实时模型访问与语法检查;智能层通过4个专业化Agent(数据建模、服务开发等)分工协作;验证层确保代码质量;知识层存储文档模板。这种工程化设计解决了通用AI在CAP开发中面临的上下文不足、专业性缺失和代码质量保证等核心问题,通过本地化服务和实时反馈机制显著提升了开发效率。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 883

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

徒手搭建部署最新版的dubbo-admin

徒手搭建部署最新版的dubbo-admin,快来跟着试试吧!

dgdf123的博客 382

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

徒手撸了一个RPC框架,理解更透彻了,代码已上传github,自取~

点击上方 "zhisheng"关注,星标或置顶一起成长Flink 从入门到精通系列文章一、前言前段时间看到一篇不错的文章《看了这篇你就会手写RPC框架了》,于是便来了兴...

http://www.54tianzhisheng.cn/ 550

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

NB!谷歌大神徒手撸ZK+Dubbo笔记,分布式技术不再难“对付”!!

ZooKeeper是一个开源分布式应用程序协调服务,提供分布式数据一致性解决方案,分布式应用程序可以实现数据发布订阅、负载均衡、命名服务、集群管理分布式锁、分布式队列等功能,在大数据、分布式系统或架构中占有很重要的协调位置。

weixin_66896902的博客 154

是谁,又被分布式锁给锁住了?(下)

但是一般在使用 Redis 的时候,都会采用主从集群 + 哨兵的方式去部署,这样做可以保证 Redis 服务的高可用,即使主节点挂了,也可以在从节点里面再选一个出来,作为主节点。举个例子,客户端在某个 Redis 实例加锁成功,但是因为网络问题,一直没有返回成功的信息,导致客户端误认为这个实例加锁失败,如果最后这个锁没有被释放,那么锁将一直残留在这个 Redis 实例当中。的值是否小于锁的过期时间,也就是判断,在后面的实例成功加锁之后,前面加的锁有没有过期,如果没过期,那么就认为加锁成功。

Gary_Tam的博客 410
上一篇: 天下无难试之HashMap面试刁难大全
下一篇: 你没读过的Jetty使用入门
码洞
博客等级 码龄10年 41粉丝 137原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值