阿里一面,说说你了解zookeeper的应用场景有哪些?
Posted 隐 风
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了阿里一面,说说你了解zookeeper的应用场景有哪些?相关的知识,希望对你有一定的参考价值。
1、前言
又到了金三银四的时候,大家都按耐不住内心的躁动,我在这里给大家分享下之前面试中遇到的一个知识点(zookeeper应用场景),希望对大家有些帮助。如有不足,欢迎大佬们指点指点。
2、zookeeper简介
ZooKeeper 是分布式应用程序的分布式开源协调服务。它公开了一组简单的api,分布式应用程序可以基于这些api实现更高级别的同步、配置维护、分组和命名服务。它被设计为易于编程,并使用一种数据模型,该模型以熟悉的文件系统目录树结构为风格。它在 Java 中运行,并具有 Java 和 C 的绑定。
众所周知,协调服务很难做好。它们特别容易出现竞争条件和死锁等错误。ZooKeeper背后的动机是减轻分布式应用程序从头开始实现协调服务的负担。
3、zookeeper应用场景
下面的代码都需要一个序列化类,所以放在最前面声明
/**
* @author admin
*/
public class MyZkSerializer implements ZkSerializer
String charset = "UTF-8";
@Override
public Object deserialize(byte[] bytes) throws ZkMarshallingError
try
return new String(bytes, charset);
catch (UnsupportedEncodingException e)
throw new ZkMarshallingError(e);
@Override
public byte[] serialize(Object obj) throws ZkMarshallingError
try
return String.valueOf(obj).getBytes(charset);
catch (UnsupportedEncodingException e)
throw new ZkMarshallingError(e);
3.1 配置中心
3.1.1 什么是配置中心呢?
假设咱们的项目部署在5台机子上形成一个集群,那么这5个实例在启动时读取的配置信息应该是一样的,同时一旦咱们的配置信息更改了,需要马上通知到这5个实例上并生效,这就是配置中心的功能。
3.1.2 zookeeper怎么实现配置中心呢?
必要条件
1、znode能存储数据
2、Watch能监听数据改变
实现方式
- 一个配置项对应一个zNode
// 1 将单个配置放到zookeeper上
public void putZk()
ZkClient client = new ZkClient("192.168.10.11:2181");
client.setZkSerializer(new MyZkSerializer());
String configPath = "/config1";
String value = "1111111";
if (client.exists(configPath))
client.writeData(configPath, value);
else
client.createPersistent(configPath, value);
client.close();
// 需要配置的服务都从zk上取,并注册watch来实时获得配置更新
public void getConfigFromZk()
ZkClient client = new ZkClient("192.168.10.11:2181");
client.setZkSerializer(new MyZkSerializer());
String configPath = "/config1";
String value = client.readData(configPath);
System.out.println("从zk读到配置config1的值为:" + value);
// 监控配置的更新,基于watch实现发布订阅功能
client.subscribeDataChanges(configPath, new IZkDataListener()
@Override
public void handleDataDeleted(String dataPath) throws Exception
// TODO 配置删除业务处理
@Override
public void handleDataChange(String dataPath, Object data) throws Exception
System.out.println("获得更新的配置值:" + data);
);
// 这里只是为演示实时获取到配置值更新而加的等待。实际项目应用中根据具体场景写(可用阻塞方式)
try
Thread.sleep(5 * 60 * 1000);
catch (InterruptedException e)
e.printStackTrace();
- 一个配置文件对应一个zNode
// 将配置文件的内容存放到zk节点上
public void putConfigFile2ZK() throws IOException
File f = new File(this.getClass().getResource("/config.xml").getFile());
FileInputStream fin = new FileInputStream(f);
byte[] datas = new byte[(int) f.length()];
fin.read(datas);
fin.close();
ZkClient client = new ZkClient("192.168.10.11:2181");
client.setZkSerializer(new BytesPushThroughSerializer());
String configPath = "/config2";
if (client.exists(configPath))
client.writeData(configPath, datas);
else
client.createPersistent(configPath, datas);
client.close();
获取整个配置文件的方式跟步骤1类似,只不过需要解析对应的配置文件而已。
3.2 命名服务(注册中心)
3.2.1 什么是注册中心?
注册中心主要存储注册实例应用的名称和ip地址,供其他服务通过RPC来调用,其他服务只关心你的服务名是啥,而不必关心你的服务器地址对不对,有没有上线。
3.2.2 zookeeper怎么实现注册中心呢?
首先是服务发现问题,当一个实例启动后会向zookeeper创建一个临时节点,并存入自己的服务信息(包括应用名和ip等),其他服务通过zookeeper拿到该实例的注册信息即可调用。
一旦该服务宕机了或者主动下线,那么该临时节点则会被删除,其他服务通过watch监听到下线通知,也就不会在去调用该服务。
3.3 Master选举
3.3.1 什么是Master选举?
在一个主从部署的集群里,一般master实例负责所有请求的读写功能,其他slave实例同步master的数据,一旦master节点不可用了,那么就需要从他的slave实例中重新选举一个节点作为master实例。
3.3.2 zookeeper怎么实现Master选举呢?
首先是实例去竞争创建临时决定(Master节点),谁创建成功谁就是master,否则就是slave。
同时所有的实例都需要去servers节点(临时节点)注册自己的服务信息,方便通过该节点获取到所有在线的实例,有点类似注册中心的意思。
下面咱们通过代码来模拟一下master选举
/**
* @author yinfeng
*/
public class Server
private final String cluster;
private final String name;
private final String address;
private final String path, value;
private String master;
public Server(String cluster, String name, String address)
super();
this.cluster = cluster;
this.name = name;
this.address = address;
path = "/" + this.cluster + "Master";
value = "name:" + name + " address:" + address;
final ZkClient client = new ZkClient("192.168.10.11:2181");
client.setZkSerializer(new MyZkSerializer());
final Thread thread = new Thread(() ->
electionMaster(client);
);
thread.setDaemon(true);
thread.start();
/**
* 选举方法
**/
public void electionMaster(ZkClient client)
try
client.createEphemeral(path, value);
master = client.readData(path);
System.out.println(value + "创建节点成功,成为Master");
catch (ZkNodeExistsException e)
master = client.readData(path);
System.out.println("Master为:" + master);
// 为阻塞自己等待而用
final CountDownLatch cdl = new CountDownLatch(1);
// 注册watcher
IZkDataListener listener = new IZkDataListener()
@Override
public void handleDataDeleted(String dataPath) throws Exception
System.out.println("-----监听到节点被删除");
cdl.countDown();
@Override
public void handleDataChange(String dataPath, Object data) throws Exception
;
client.subscribeDataChanges(path, listener);
// 让自己阻塞
if (client.exists(path))
try
cdl.await();
catch (InterruptedException e1)
e1.printStackTrace();
// 醒来后,取消watcher
client.unsubscribeDataChanges(path, listener);
// 递归调自己(下一次选举)
electionMaster(client);
咱们通过启动多个服务来看看是否测试成功
public static void main(String[] args)
// 测试时,依次开启多个Server实例java进程,然后停止获取的master的节点,看谁抢到Master
Server s = new Server("cluster1", "server1", "192.168.1.11:8991");
Server s1 = new Server("cluster1", "server2", "192.168.1.11:8992");
Server s2 = new Server("cluster1", "server3", "192.168.1.11:8993");
Server s3 = new Server("cluster1", "server4", "192.168.1.11:8994");
try
Thread.sleep(100000);
catch (InterruptedException e)
e.printStackTrace();
可以看到功能一切正常
3.4 分布式队列
3.4.1 什么是分布式队列?
队列的定义是先进先出,而在分布式环境下保证先进先出的队列就是分布式队列,有点类似于消息队列。
3.4.2 zookeeper怎么实现分布式队列?
由上图可知,zookeeper主要通过顺序节点来保证队列的先进先出。
3.5 分布式锁
3.5.1 什么是分布式锁?
分布式锁指的是控制分布式系统不同进程共同访问共享资源的一种锁的实现。 如果在不同的系统或同一个系统的不同主机之间共享和竞争某个临界资源,往往需要互斥来防止彼此干扰,避免出现脏数据或非业务数据,保证数据一致性。
3.5.2 zookeeper通过临时节点实现布式锁?
实现原理是zookeeper节点不可重名和watch的监听通知机制,使用临时节点主要是为了避免获取锁的节点由于异常原因无法释放锁而导致出现死锁情况。
竞争锁流程如下图:
代码实现如下
/**
* @author yinfeng
*/
public class ZKDistributeLock implements Lock
private String lockPath;
private ZkClient client;
// 锁重入计数
private ThreadLocal<Integer> reentrantCount = new ThreadLocal<>();
public ZKDistributeLock(String lockPath)
super();
this.lockPath = lockPath;
client = new ZkClient("192.168.10.11:2181");
client.setZkSerializer(new MyZkSerializer());
@Override
public boolean tryLock()
// 锁重入不会阻塞
if (this.reentrantCount.get() != null)
int count = this.reentrantCount.get();
if (count > 0)
this.reentrantCount.set(++count);
return true;
// 创建节点
try
client.createEphemeral(lockPath);
this.reentrantCount.set(1);
catch (ZkNodeExistsException e)
return false;
return true;
@Override
public void unlock()
// 重入释进行放锁处理
if (this.reentrantCount.get() != null)
int count = this.reentrantCount.get();
if (count > 1)
this.reentrantCount.set(--count);
return;
else
this.reentrantCount.set(null);
client.delete(lockPath);
@Override
public void lock()
// 如果获取不到锁,阻塞等待
if (!tryLock())
// 没获得锁,阻塞自己
waitForLock();
// 再次尝试
lock();
private void waitForLock()
final CountDownLatch cdl = new CountDownLatch(1);
IZkDataListener listener = new IZkDataListener()
@Override
public void handleDataDeleted(String dataPath) throws Exception
System.out.println("----收到节点被删除了-------------");
cdl.countDown();
@Override
public void handleDataChange(String dataPath, Object data) throws Exception
;
client.subscribeDataChanges(lockPath, listener);
// 阻塞自己
if (this.client.exists(lockPath))
try
cdl.await();
catch (InterruptedException e)
e.printStackTrace();
// 取消注册
client.unsubscribeDataChanges(lockPath, listener);
@Override
public void lockInterruptibly()
@Override
public boolean tryLock(long time, TimeUnit unit)
return false;
@Override
public Condition newCondition()
return null;
咱们在写个测试类试一下效果,通过多线程来模拟多实例竞争锁
public static void main(String[] args)
// 并发数
int currency = 50;
// 循环屏障
final CyclicBarrier cb = new CyclicBarrier(currency);
// 多线程模拟高并发
for (int i = 0; i < currency; i++)
new Thread(() ->
System.out.println(Thread.currentThread().getName() + "---------我准备好---------------");
// 等待一起出发
try
cb.await();
catch (InterruptedException | BrokenBarrierException e)
e.printStackTrace();
ZKDistributeLock lock = new ZKDistributeLock("/distLock11");
try
lock.lock();
System.out.println(Thread.currentThread().getName() + " 获得锁!");
try
Thread.sleep(1000 * 2);
catch (InterruptedException e)
e.printStackTrace();
finally
lock.unlock();
System.out.println(Thread.currentThread().getName() + " 释放锁!");
).start();
可以看到功能是正常的,但也有个很明显的问题,就是一旦释放锁之后
以上是关于阿里一面,说说你了解zookeeper的应用场景有哪些?的主要内容,如果未能解决你的问题,请参考以下文章