基于 Zookeeper 实现服务注册和服务发现

文章目录

  • 前言
  • 声明
  • 前置知识
    • 服务注册和发现
    • Zookeeper
  • 工作原理
  • 实现过程
    • 注册中心
    • 服务注册
    • 服务发现
  • 总结

前言

无论是采用SOA还是微服务架构,都需要使用服务注册和服务发现组件。我刚开始接触 Dubbo 时一直对服务注册/发现以及 Zookeeper 的作用感到困惑,现在看来是因为对分布式系统的理解不够深入,对 Dubbo 和 Zookeeper 的工作原理不够清楚。

本文将基于 Zookeeper 实现服务注册和服务发现功能,如果跟我一样有同样的困惑,希望可以通过本文了解其他组件如何使用 Zookeeper 作为注册中心的工作原理。

声明

文章中所提供的代码仅供参考,旨在帮助缺乏基础知识的开发人员更好地理解服务注册和服务发现的概念。请注意,这些代码并不适用于实际应用中

前置知识

服务注册和发现

在SOA或微服务架构中,由于存在大量的服务以及可能的相互调用,为了更有效地管理这些服务,我们通常需要引入一个统一的地方,即注册中心,来集中管理它们,而注册中心最基本的功能就是服务注册/发现。

  • 服务注册:将该服务实例的元数据(如IP地址、端口号、健康状态等)注册到注册中心,这样其他服务或客户端可以发现和使用该服务。
  • 服务发现:当一个服务需要调用别的服务时,使用静态配置是不可行的,这个时候可以去注册中心获取可用的服务实例并调用。

Zookeeper

Zookeeper 是一个传统的分布式协调服务,它更多的被用来作为一个协调器使用,比如来协调管理 Hadoop 集群、协调 Kafka 的 leader 选举等。

为什么会有组件将其视为一个注册中心使用?我想有几个原因:

  1. Zookeeper 在分布式系统中具有更强的一致性和可靠性,可以确保各个服务的注册信息保持一致。
  2. Zookeeper 使用内存存储数据,具有很高的读写性能。这对于注册中心来说非常关键,因为它需要快速地响应客户端的请求。
  3. Zookeeper 的 Watcher 机制可以让客户端监听指定节点的变化。当某个节点(注册中心)发生变化时,Zookeeper 可以通知其他服务实现实时更新。

工作原理

以下图为例,可以看到 Dubbo 是如何使用 Zookeeper 实现服务注册/发现的。

在这里插入图片描述

  1. 服务提供者向 /dubbo/com.foo.BarService/providers 目录下写入自己的 URL 地址。
  2. 服务消费者订阅 /dubbo/com.foo.BarService/providers 目录下的提供者 URL 地址。并向 /dubbo/com.foo.BarService/consumers 目录下写入自己的 URL 地址。

这里的目录就是 Zookeeper 的数据结构,原理非常简单,本质上就是服务提供者和消费者按照约定在 Zookeeper 上读写数据,同时借用其 Watcher 机制、临时节点和可靠性等特性高效的实现以下功能:

  • 当提供者服务出现断电等异常停机时,注册中心能自动删除提供者信息。
  • 当注册中心重启时,能自动恢复注册数据以及订阅请求。

实现过程

注册中心

下面通过 Zookeeper 的 Java API 实现一个只包含服务注册/发现的注册中心,代码如下:

public class RegistrationCenter {// 连接信息private String connectString = "192.168.10.11:2181,192.168.10.11:2182,192.168.10.11:2183";// 超时时间private int sessionTimeOut = 30000;private final String ROOT_PATH = "/servers";private ZooKeeper client;public RegistrationCenter() {this(null);}public RegistrationCenter(Consumer<List<String>> consumer) {try {getConnection(null == consumer ? null : watchedEvent -> {//监听服务器地址的上下线if (watchedEvent.getType() == Watcher.Event.EventType.NodeChildrenChanged) {try {consumer.accept(subServers());} catch (Exception e) {e.printStackTrace();}}});Stat stat = client.exists(ROOT_PATH, false);if (stat == null) {//创建根节点client.create(ROOT_PATH, ROOT_PATH.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);}} catch (Exception e) {e.printStackTrace();}}/*** @param serverName 将服务器注册到zk集群时,所需的服务名称* @param metadata   服务元数据* @throws Exception*/public void doRegister(String serverName, Metadata metadata) throws Exception {/*** ZooDefs.Ids.OPEN_ACL_UNSAFE: 此权限表示允许所有人访问该节点(服务器)* CreateMode.EPHEMERAL_SEQUENTIAL: 由于服务器是动态上下线的,上线后存在,下线后不存在,所以是临时节点* 而服务器一般都是有序号的,所以是临时、有序的节点.*/String node = client.create(ROOT_PATH + "/" + serverName, metadata.toString().getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);System.out.println(serverName + " 已经上线");}/*** 发现/订阅服务*/public List<String> subServers() throws InterruptedException, KeeperException {List<String> zkChildren = client.getChildren(ROOT_PATH, true);List<String> servers = new ArrayList<>();zkChildren.forEach(node -> {//拼接服务完整信息try {byte[] data = client.getData(ROOT_PATH + "/" + node, false, null);servers.add(new String(data));} catch (Exception e) {e.printStackTrace();}});return servers;}private void getConnection(Watcher watcher) throws IOException {this.client = new ZooKeeper(connectString, sessionTimeOut, watcher);}/*** 服务元数据*/public static class Metadata {public Metadata() {}public Metadata(String ip, int port) {this.ip = ip;this.port = port;}private String ip;private int port;public String getIp() {return ip;}public void setIp(String ip) {this.ip = ip;}public int getPort() {return port;}public void setPort(int port) {this.port = port;}@Overridepublic String toString() {return "{" + "ip='" + ip + '\'' + ", port=" + port + '}';}}
}

该类中两个核心方法:doRegister()subServers() 服务注册和订阅。

  • doRegister()主要是往 Zookeeper 中创建了一个临时节点数据,临时节点的优势就是当服务出现断电等异常停机时,节点会自动删除。
  • subServers()则是去 Zookeeper 读取了所有服务提供者的信息,并且监听了节点状态,当节点发生创建、删除、更新等事件时重新获取服务者的信息,做到数据实时更新。

至此,一个简单的注册中心就完成了,当然,如果要实现一个成熟的注册中心,还要考虑负载均衡、高可用性和容错、服务治理和路由控制等功能,这里先不展开。

服务注册

当有了注册中心,服务提供者就可以调用 doRegister() 进行注册,代码如下:

public class ProviderServer {public static void main(String[] args) throws Exception {RegistrationCenter registrationCenter = new RegistrationCenter();registrationCenter.doRegister("provider", new RegistrationCenter.Metadata("127.0.0.1", 8080));Thread.sleep(Long.MAX_VALUE);}
}

服务发现

同样,服务消费者可以调用 subServers() 发现服务提供者,同时当服务提供者发生变化时会通知到消费者。代码如下:

public class ConsumerServer {public static void main(String[] args) throws Exception {RegistrationCenter registrationCenter = new RegistrationCenter(newServers -> {System.out.println("服务更新了..."+newServers);});List<String> servers = registrationCenter.subServers();System.out.println(servers);Thread.sleep(Long.MAX_VALUE);}
}

总结

服务注册和服务发现功能是为了解决分布式系统中的服务管理和通信问题而设计的,经过不断的发展与负载均衡、健康监测、服务治理和路由控制等功能完善成为一个注册中心。服务注册和服务发现有助于实现系统的弹性和可扩展性,因为新的服务实例可以动态地加入系统,而无需手动配置和修改已有的代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/119680.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Elsevier旗下】中科院1区TOP,影响因子9分+,23天录用!极速见刊!

极速见刊推荐 中科院 1区&#xff08;TOP&#xff09; 出版社&#xff1a;Elsevier 影响因子&#xff1a;IF&#xff08;2022&#xff09;9.0-10.0 期刊分区&#xff1a;JCR1区&#xff0c;中科院1区&#xff08;TOP&#xff09; 检索情况&#xff1a;SCIE 在检&#xff…

微服务之架构演变

随着互联网的发展&#xff0c;网站应用规模不断扩大&#xff0c;网站架构随之不断演变&#xff0c;演变历史大致分为单体应用架构-垂直应用架构-分布式架构-SOA架构-微服务架构-云原生架构 架构演变 单体应用架构 以前网站流量小&#xff0c;只需要一个应用就可以把所有功能…

模拟实现list

目录 list的实现结构节点的实现迭代器的实现第一个模板参数T第二个模板参数Ref第三个模板参数Ptr 实现list中的接口函数插入和删除赋值重载和拷贝构造析构函数 总结 list的实现结构 STL库中的list的结构是双向循环链表&#xff0c;所以我们这里也实现一个双向循环链表 我们这…

中介者模式

1、场景 假如没有总经理。下面三个部门&#xff1a;财务部、市场部、研发部。财务部要发工资&#xff0c;让大家核对公司需要跟市场部和研发部都通气&#xff1b;市场部要接个新项目&#xff0c;需要研发部处理技术、需要财务部出资金。市场部跟各个部门打交道。 虽然只有三个…

无涯教程-JavaScript - DVAR函数

描述 DVAR函数使用与指定条件相匹配的列表或数据库的列中的数字,根据样本估算总体的方差。 语法 DVAR (database, field, criteria)争论 Argument描述Required/Optionaldatabase 组成列表或数据库的单元格范围。 数据库是相关数据的列表,其中相关信息的行是记录,数据的列是…

大数据课程K17——Spark的协同过滤法

文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 了解Spark的协同过滤概念; 一、协同过滤概念 1. 概念 协同过滤是一种借助众包智慧的途径。它利用大量已有的用户偏好来估计用户对其未接触过的物品的喜好程度。其内在思想是相似度的定义…

服务器放在香港好用吗?

​  相较于国内服务器&#xff0c;将网站托管在香港服务器上最直观的好处是备案层面上的。香港服务器上的网站无需备案&#xff0c;因此更无备案时限&#xff0c;购买之后即可使用。 带宽优势 香港服务器的带宽一般分为香港本地带宽和国际带宽、直连中国骨干网 CN2三种。香港…

【python】—— 函数详解

前言&#xff1a; 本期&#xff0c;我们将要讲解的是有关python中函数的相关知识&#xff01;&#xff01;&#xff01; 目录 &#xff08;一&#xff09;函数是什么 &#xff08;二&#xff09;语法格式 &#xff08;三&#xff09;函数参数 &#xff08;四&#xff09;函…

轻量、便捷、高效—经纬恒润AETP助力车载以太网测试

随着自动驾驶技术和智能座舱的不断发展&#xff0c;高宽带、高速率的数据通信对主干网提出了稳定、高效的传输要求&#xff0c;CAN(FD)、LIN已无法充分满足汽车的通信需求。车载以太网作为一种快速且扩展性好的网络技术&#xff0c;已经逐步成为了汽车主干网的首选。 此外&…

面试题汇总

文章目录 一. 腾讯二. 华为三. 快手1. Long 的长度和范围&#xff0c;为什么要减 1 (Java基础)2. 线程池配置无界队列了之后&#xff0c;拒绝策略怎么搞&#xff0c;什么时候用到无界队列 (JUC并发) 四. 美团五. 阿里六. 百度七. 字节八. 大疆1. 为什么创建进程开销比线程大? …

Python之作业(一)

Python之作业&#xff08;一&#xff09; 作业 打印九九乘法表 用户登录验证 用户依次输入用户名和密码&#xff0c;然后提交验证用户不存在、密码错误&#xff0c;都显示用户名或密码错误提示错误3次&#xff0c;则退出程序验证成功则显示登录信息 九九乘法表 代码分析 先…

win | wireshark | 在win上跑lua脚本 解析数据包

前提说明&#xff1a;之前是在linux 系统上配置的&#xff0c;然后现在 在配置lua 脚本 &#xff0c;然后 分析指定协议 的 数据包 其实流程也比较简单&#xff0c;但 逻辑需要缕清来 首先要把你 预先准备的 xxx.lua 文件放到wireshark 的安装文件中&#xff0c;&#xff08;我…

linux深入理解多进程间通信

1.进程间通信 1.1 进程间通信目的 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程资源共享&#xff1a;多个进程之间共享同样的资源。通知事件&#xff1a;一个进程需要向另一个或一组进程发送消息&#xff0c;通知它&#xff08;它们&#xff09;发生了某种事件…

《Linux从练气到飞升》No.20 Linux进程替换

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的…

不同写法的性能差异

“ 达到相同目的,可以有多种写法,每种写法有性能、可读性方面的区别,本文旨在探讨不同写法之间的性能差异 len(str) vs str "" 本部分参考自: [问个 Go 问题&#xff0c;字符串 len 0 和 字符串 "" &#xff0c;有啥区别&#xff1f;](https://segmentf…

WebSocket--技术文档--基本概念--《快速了解WebSocket协议》

阿丹&#xff1a; 不断学习新技术&#xff0c;丰富自己了解更多才能扩展更多世界可能。 官网 WebSocket首页、文档和下载 - HTML5开发相关 - OSCHINA - 中文开源技术交流社区 软件简介 WebSocket 是 HTML5 开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。 WebS…

android:新建工程文件介绍

一、前言当我们新建一个app时会呈现出固定的工程文件&#xff0c;这篇文章介绍新建工程里的文件。 二、介绍 Structure:就是你选择哪个页面就会显示那个页面的结构&#xff0c;就比如说我选择的是MainActivity他就会显示这个页面所使用的方法。 1-2&#xff1a;是android自动生…

什么是架构,架构的本质是什么

不论是开发人员还是架构师&#xff0c;我们都一直在跟软件系统打交道&#xff0c;架构是在工作中出现最频繁的术语之一。那么&#xff0c;到底什么是架构&#xff1f;你可能有自己的答案&#xff0c;也有可能没有答案。对“架构”的理解需要我们不断在实践中思考、归纳、演绎&a…

【ES6】require、export和import的用法

在JavaScript中&#xff0c;require、export和import是Node.js的模块系统中的关键字&#xff0c;用于处理模块间的依赖关系。 1、require&#xff1a;这是Node.js中引入模块的方法。当你需要使用其他模块提供的功能时&#xff0c;可以使用require关键字来引入该模块。例如&…

查询优化器内核剖析之从一个实例看执行计划

学习查询优化器不是我们的目的&#xff0c;而是通过 它&#xff0c;我们掌握 SQL Server 是如何处理我们的 SQL 的&#xff0c;掌握执行计划&#xff0c;掌握为什么产生 I/O 问题&#xff0c; 为什么 CPU 使用老高&#xff0c;为什么你的索引加了不起作用... 如果&#xff0c;…