接上篇:一套方案轻松实现Zookeeper服务器动态上下线和分布式锁

本文介绍了ZooKeeper在分布式应用中的关键作用,如数据发布/订阅、命名服务、集群管理等,并详细阐述了如何利用其特性实现服务器动态上下线监控和分布式锁,展示了通过ZooKeeper创建临时顺序节点实现分布式锁的示例。
摘要由CSDN通过智能技术生成

ZooKeeper是⼀个典型的发布/订阅模式的分布式数据管理与协调框架,我们可以使⽤它来进⾏分布式 数据的发布与订阅。另⼀⽅⾯,通过对ZooKeeper中丰富的数据节点类型进⾏交叉使⽤,配合Watcher 事件通知机制,可以⾮常⽅便地构建⼀系列分布式应⽤中都会涉及的核⼼功能,如数据发布/订阅、命名 服务、集群管理、Master选举、分布式锁和分布式队列等。那接下来就针对这些典型的分布式应⽤场景 来做下介绍。

Zookeeper的两⼤特性:

===============

  • 1.客户端如果对Zookeeper的数据节点注册Watcher监听,那么当该数据节点的内容或是其⼦节点 列表发⽣变更时,Zookeeper服务器就会向订阅的客户端发送变更通知。

  • 2.对在Zookeeper上创建的临时节点,⼀旦客户端与服务器之间的会话失效,那么临时节点也会被 ⾃动删除

利⽤其两⼤特性,可以实现集群机器存活监控系统,若监控系统在/clusterServers节点上注册⼀个 Watcher监听,那么但凡进⾏动态添加机器的操作,就会在/clusterServers节点下创建⼀个临时节 点:/clusterServers/[Hostname],这样,监控系统就能够实时监测机器的变动情况。

服务器动态上下线监听:

===========

分布式系统中,主节点会有多台,主节点可能因为任何原因出现宕机或者下线,⽽任意⼀台客户端都要 能实时感知到主节点服务器的上下线。

思路分析:

接上篇:一套方案轻松实现Zookeeper服务器动态上下线和分布式锁

具体实现:

服务端:

// 1 连接ZK

// 2 创建临时顺序节点,数据内容写ip和端口

// 3 创建时间服务线程

import org.I0Itec.zkclient.ZkClient;

//服务端主要提供了client需要的一个时间查询服务,服务端向zk建立临时节点

public class Server {

//获取zkclient

ZkClient zkClient = null;

private void connectZk() {

// 创建zkclient

zkClient = new ZkClient(“linux121:2181,linux122:2181”);

//创建服务端建立临时节点的目录

if (!zkClient.exists(“/servers”)) {

zkClient.createPersistent(“/servers”);

}

}

//告知zk服务器相关信息

private void saveServerInfo(String ip, String port) {

final String sequencePath = zkClient.createEphemeralSequential(“/servers/server”, ip + “:” + port);

System.out.println(“----->>> ,服务器:” + ip + “:” + port + “,向zk保存信息成功,成功上线可以接受client查询”);

}

public static void main(String[] args) {

//准备两个服务端启动上线(多线程模拟,一个线程代表一个服务器)

final Server server = new Server();

server.connectZk();

server.saveServerInfo(args[0], args[1]);

//提供时间服务的线程没有启动,创建一个线程类,可以接收socket请求

new TimeService(Integer.parseInt(args[1])).start();

}

}

服务端提供时间查询的线程类:

import java.io.IOException;

import java.io.OutputStream;

import java.net.ServerSocket;

import java.net.Socket;

import java.util.Date;

//提供时间查询服务

public class TimeService extends Thread {

private int port = 0;

public TimeService(int port) {

this.port = port;

}

@Override

public void run() {

//通过socket与client进行交流,启动serversocket监听请求

try {

//指定监听的端口

final ServerSocket serverSocket = new ServerSocket(port);

//保证服务端一直运行

while (true) {

final Socket socket = serverSocket.accept();

//不关心client发送内容,server只考虑发送一个时间值

final OutputStream out = socket.getOutputStream();

out.write(new Date().toString().getBytes());

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

client端:

import org.I0Itec.zkclient.IZkChildListener;

import org.I0Itec.zkclient.ZkClient;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.net.Socket;

import java.util.ArrayList;

import java.util.List;

import java.util.Random;

// 注册监听zk指定目录,

//维护自己本地一个servers信息,收到通知要进行更新

//发送时间查询请求并接受服务端返回的数据

public class Client {

//获取zkclient

ZkClient zkClient = null;

//维护一个serversi 信息集合

ArrayList infos = new ArrayList();

private void connectZk() {

// 创建zkclient

zkClient = new ZkClient(“linux121:2181,linux122:2181”);

//第一次获取服务器信息,所有的子节点

final List childs = zkClient.getChildren(“/servers”);

for (String child : childs) {

//存储着ip+port

final Object o = zkClient.readData(“/servers/” + child);

infos.add(String.valueOf(o));

}

//对servers目录进行监听

zkClient.subscribeChildChanges(“/servers”, new IZkChildListener() {

public void handleChildChange(String s, List children) throws Exception {

//接收到通知,说明节点发生了变化,client需要更新infos集合中的数据

ArrayList list = new ArrayList();

//遍历更新过后的所有节点信息

for (String path : children) {

final Object o = zkClient.readData(“/servers/” + path);

list.add(String.valueOf(o));

}

//最新数据覆盖老数据

infos = list;

System.out.println(“–》接收到通知,最新服务器信息为:” + infos);

}

});

}

//发送时间查询的请求

public void sendRequest() throws IOException {

//目标服务器地址

final Random random = new Random();

final int i = random.nextInt(infos.size()); //随机选择一个服务器

final String ipPort = infos.get(i);

final String[] arr = ipPort.split(“:”);

//建立socket连接

final Socket socket = new Socket(arr[0], Integer.parseInt(arr[1]));

final OutputStream out = socket.getOutputStream();

final InputStream in = socket.getInputStream();

//发送数据

out.write(“query time”.getBytes());

out.flush();

//接收返回结果

final byte[] b = new byte[1024];

in.read(b);//读取服务端返回数据

System.out.println(“client端接收到server:+” + ipPort + “+返回结果:” + new String(b));

//释放资源

in.close();

out.close();

socket.close();

}

public static void main(String[] args) throws InterruptedException {

final Client client = new Client();

client.connectZk(); //监听器逻辑

while (true) {

try {

client.sendRequest(); //发送请求

} catch (IOException e) {

e.printStackTrace();

try {

client.sendRequest();

} catch (IOException e1) {

e1.printStackTrace();

}

}

//每隔几秒中发送一次请求到服务端

Thread.sleep(2000);

}

}

}

分布式锁:

=====

1.什么是锁:

=======

  • 在单机程序中,当存在多个线程可以同时改变某个变量(可变共享变量)时,为了保证线程安全 (数据不能出现脏数据)就需要对变量或代码块做同步,使其在修改这种变量时能够串⾏执⾏消除并 发修改变量。

  • 对变量或者堆代码码块做同步本质上就是加锁。⽬的就是实现多个线程在⼀个时刻同⼀个代码块只 能有⼀个线程可执⾏

2. 分布式锁

========

分布式的环境中会不会出现脏数据的情况呢?类似单机程序中线程安全的问题。观察下⾯的例⼦:

接上篇:一套方案轻松实现Zookeeper服务器动态上下线和分布式锁

上⾯的设计是存在线程安全问题。

问题:

假设Redis ⾥⾯的某个商品库存为 1;此时两个⽤户同时下单,其中⼀个下单请求执⾏到第 3 步,更新 数据库的库存为 0,但是第 4 步还没有执⾏。

⽽另外⼀个⽤户下单执⾏到了第 2 步,发现库存还是 1,就继续执⾏第 3 步。但是商品库存已经为0, 所以如果数据库没有限制就会出现超卖的问题。

解决方案:

接上篇:一套方案轻松实现Zookeeper服务器动态上下线和分布式锁

公司业务发展迅速,系统应对并发不断提⾼,解决⽅案是要增加⼀台机器,结果会出现更⼤的问题:

接上篇:一套方案轻松实现Zookeeper服务器动态上下线和分布式锁

接上篇:一套方案轻松实现Zookeeper服务器动态上下线和分布式锁

利⽤Zookeeper可以创建临时带序号节点的特性来实现⼀个分布式锁。

分布式锁的作⽤:在整个系统提供⼀个全局、唯⼀的锁,在分布式系统中每个系统在进⾏相关操作的时 候需要获取到该锁,才能执⾏相应操作。

zk实现分布式锁:

=========

利⽤Zookeeper可以创建临时带序号节点的特性来实现⼀个分布式锁。

实现思路:

  • 锁就是zk指定⽬录下序号最⼩的临时序列节点,多个系统的多个线程都要在此⽬录下创建临时的顺 序节点,因为Zk会为我们保证节点的顺序性,所以可以利⽤节点的顺序进⾏锁的判断。

  • 每个线程都是先创建临时顺序节点,然后获取当前⽬录下最⼩的节点(序号),判断最⼩节点是不是 当前节点,如果是那么获取锁成功,如果不是那么获取锁失败。

  • 获取锁失败的线程获取当前节点上⼀个临时顺序节点,并对对此节点进⾏监听,当该节点删除的时 候(上⼀个线程执⾏结束删除或者是掉线zk删除临时节点)这个线程会获取到通知,代表获取到了 锁。

流程图:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

如果觉得本文对你有帮助的话,不妨给我点个赞,关注一下吧!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!**

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

如果觉得本文对你有帮助的话,不妨给我点个赞,关注一下吧!

[外链图片转存中…(img-UbZkDcFa-1712929727931)]

[外链图片转存中…(img-ewnQt6dq-1712929727931)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 21
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值