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

avatar
作者
筋斗云
阅读量:3

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)]

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

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!