背景

前段时间学习了zookeeper后,在新的项目中刚好派上了用场,我在项目中主要负责分布式任务调度模块的开发,对我自己来说是个不小的挑战。

分布式的任务调度,技术上我们选择了zookeeper,具体的整个分布式任务调度的架构选择会另起一篇文章进行介绍。

本文主要是介绍自己在项目中zookeeper的一些扩展使用,希望可以对大家有所帮助。

项目中使用的zookeeper版本3.3.3,对应的文档地址: http://zookeeper.apache.org/doc/trunk/

扩展一:优先集群

先来点背景知识:

1.zookeeper中的server机器之间会组成leader/follower集群,1:n的关系。采用了paxos一致性算法保证了数据的一致性,就是leader/follower会采用通讯的方式进行投票来实现paxns。

2.zookeeper还支持一种observer模式,提供只读服务不参与投票,提升系统,对应文档: http://zookeeper.apache.org/doc/trunk/zookeeperObservers.html

我们项目特性的决定了我们需要进行跨机房操作,比如杭州,美国,香港,青岛等多个机房之间进行数据交互。

跨机房之间对应的网络延迟都比较大,比如中美机房走海底光缆有ping操作200ms的延迟,杭州和青岛机房有70ms的延迟。

为了提升系统的网络性能,我们在部署zookeeper网络时会在每个机房部署节点,多个机房之间再组成一个大的网络保证数据一致性。(zookeeper千万别再搞多个集群)

最后的部署结构就会是:

  • 杭州机房  >=3台 (构建leader/follower的zk集群)
  • 青岛机房  >=1台 (构建observer的zk集群)
  • 美国机房  >=1台 (构建observer的zk集群)
  • 香港机房  >=1台 (构建observer的zk集群)


 
一句话概括就是: 在单个机房内组成一个投票集群,外围的机房都会是一个observer集群和投票集群进行数据交互。 这样部署的一些好处,大家可以细细体会一下
 
针对这样的部署结构,我们会引入一个优先集群问题: 比如在美国机房的机器需要优先去访问本机房的zk集群,访问不到后才去访问杭州机房。 
默认在zookeeper3.3.3的实现中,认为所有的节点都是对等的。并没有对应的优先集群的概念,单个机器也没有对应的优先级的概念。
 
扩展代码:(比较暴力,采用反射的方式改变了zk client的集群列表)
  • 先使用美国机房的集群ip初始化一次zk client
  • 通过反射方式,强制在初始化后的zk client中的server列表中又加入杭州机房的机器列表
1.ZooKeeper zk = null;
2. try {
3. zk = new ZooKeeper(cluster1, sessionTimeout, new AsyncWatcher() {
4.
5. public void asyncProcess(WatchedEvent event) {
6. //do nothing
7. }
8.
9. });
10. if (serveraddrs.size() > 1) {
11. // 强制的声明accessible
12. ReflectionUtils.makeAccessible(clientCnxnField);
13. ReflectionUtils.makeAccessible(serverAddrsField);
14. // 添加第二组集群列表
15. for (int i = 1; i < serveraddrs.size(); i++) {
16. String cluster = serveraddrs.get(i);
17. // 强制获取zk中的地址信息
18. ClientCnxn cnxn = (ClientCnxn) ReflectionUtils.getField(clientCnxnField, zk);
19. List<InetSocketAddress> serverAddrs = (List<InetSocketAddress>) ReflectionUtils
20. .getField(serverAddrsField, cnxn);
21. // 添加第二组集群列表
22. serverAddrs.addAll(buildServerAddrs(cluster));
23. }
24. }
25. }

扩展二:异步Watcher处理

最早在看zookeeper的代码时,一直对它的watcher处理比较满意,使用watcher推送数据可以很方便的实现分布式锁的功能。

zookeeper的watcher实现原理也挺简单的,就是在zookeeper client和zookeeper server上都保存一份对应的watcher对象。每个zookeeper机器都会有一份完整的node tree数据和watcher数据,每次leader通知follower/observer数据发生变更后,每个zookeeper server会根据自己节点中的watcher事件推送给响应的zookeeper client,每个zk client收到后再根据内存中的watcher引用,进行回调。

这里会有个问题,就是zk client在处理watcher时,回凋的过程是一个串行的执行过程,所以单个watcher的处理慢会影响整个列表的响应。

可以看一下ClientCnxn类中的EventThread处理,该线程会定时消费一个queue的数据,挨个调用processEvent(Object event) 进行回调处理。

扩展代码:

1.public abstract class AsyncWatcher implements Watcher {
2.
3. private static final int DEFAULT_POOL_SIZE = 30;
4. private static final int DEFAULT_ACCEPT_COUNT = 60;
5.
6. private static ExecutorService executor = new ThreadPoolExecutor(
7. 1,
8. DEFAULT_POOL_SIZE,
9. 0L,
10. TimeUnit.MILLISECONDS,
11. new ArrayBlockingQueue(
12. DEFAULT_ACCEPT_COUNT),
13. new NamedThreadFactory(
14. "Arbitrate-Async-Watcher"),
15. new ThreadPoolExecutor.CallerRunsPolicy());
16.
17. public void process(final WatchedEvent event) {
18. executor.execute(new Runnable() {//提交异步处理
19.
20. @Override
21. public void run() {
22. asyncProcess(event);
23. }
24. });
25.
26. }
27.
28. public abstract void asyncProcess(WatchedEvent event);
29.
30.}
说明:
  • zookeeper针对watcher的调用是以单线程串行的方式进行处理,容易造成堵塞影响,monitor的数据同步及时性
  • AsyncWatcher为采取的一种策略为当不超过acceptCount=60的任务时,会采用异步线程的方式处理。如果超过60任务,会变为原先的单线程串行的模式

扩展三:重试处理

这个也不多说啥,看一下相关文档就清楚了

需要特殊处理下ConnectionLoss的异常,一种可恢复的异常。
 
重试处理:

1.public interface ZooKeeperOperation<T> {
2.
3. public T execute() throws KeeperException, InterruptedException;
4.}
5.
6.
7./**
8. * 包装重试策略
9. */
10. public <T> T retryOperation(ZooKeeperOperation<T> operation) throws KeeperException,
11. InterruptedException {
12. KeeperException exception = null;
13. for (int i = 0; i < maxRetry; i++) {
14. try {
15. return (T) operation.execute();
16. } catch (KeeperException.SessionExpiredException e) {
17. logger.warn("Session expired for: " + this + " so reconnecting due to: " + e, e);
18. throw e;
19. } catch (KeeperException.ConnectionLossException e) { //特殊处理Connection Loss
20. if (exception == null) {
21. exception = e;
22. }
23. logger.warn("Attempt " + i + " failed with connection loss so "
24. + "attempting to reconnect: " + e, e);
25.
26. retryDelay(i);
27. }
28. }
29.
30. throw exception;
31. }

注意点:Watcher原子性

在使用zookeeper的过程中,需要特别注意一点就是注册对应watcher事件时,如果当前的节点已经满足了条件,比如exist的watcher,它不会触发你的watcher,而会等待下一次watcher条件的满足。

它的watcher是一个一次性的监听,而不是一个永久的订阅过程。所以在watcher响应和再次注册watcher过程并不是一个原子操作,编写多线程代码和锁时需要特别注意

总结

zookeepr是一个挺不错的产品,源代码写的也非常不错,大量使用了queue和异步Thread的处理模式,真是一个伟大的产品。

云栖社区站内文章,如需转载,请保留作者和出处(云栖社区),并邮件通知云栖社区(yqeditor@list.alibaba-inc.com)。

zookeeper项目使用几点小结的更多相关文章

  1. 使用javac编译zookeeper项目

    这里记录zookeeper编译源代码上的一些细节的问题. 网上不少关于如何使用ant eclipse来构建zookeeper对应的eclipse工程的记录.这里就不再过多赘述.只做简单阐述. 这里主要 ...

  2. springmvc 项目完整示例03 小结

    利用spring 创建一个web项目 大致原理 利用spring的ioc 原理,例子中也就是体现在了配置文件中 设置了自动扫描注解 配置了数据库信息等 一般一个项目,主要有domain,dao,ser ...

  3. 第一个dubbo+zookeeper项目例子

    公司项目要用这两个东西,于是打算学习它. 首先我的理解dubbo是什么?zookeeper是什么?为什要这么搞. 项目分层: 传统的,mvc -->垂直架构(将模块抽取成单独项目,项目互相调用) ...

  4. maven dubbo zookeeper 项目搭建(有效)jar包非war测试

    zookeeper安装以及dubbo-admin.war(管理端)配置启动,本章省略,参考其他内容 这里主要说服务提供者和消费者 项目结构: 1)服务端 DemoServer.java package ...

  5. .Net 项目代码风格要求小结

    代码风格没有正确与否,重要的是整齐划一,这是我拟的一份<.Net 项目代码风格要求>,供大家参考. 1. C# 代码风格要求1.1注释 类型.属性.事件.方法.方法参数,根据需要添加注释. ...

  6. git文件夹下项目更改ip地址小结

    在我们开发的过程中,经常切换项目IP地址是很正常的,之前弄过一次,没有记住,现在简单的总结下: 找到要切换IP地址的项目,点击鼠标右键,弹出下图: 打开该项目的路径后,双击打开该项目,具体参考自己项目 ...

  7. Zookeeper项目开发环境搭建(Eclipse\MyEclipse + Maven)

    写在前面的话 可详细参考,一定得去看 HBase 开发环境搭建(Eclipse\MyEclipse + Maven) 我这里,相信,能看此博客的朋友,想必是有一定基础的了.我前期写了大量的基础性博文. ...

  8. eclipse maven jdk1.8 还原站点项目红感叹号总是小结

    问题背景有三 maven 默认是jdk1.5jdk1.8 目录文件夹不全操作: 在项目上右击-> build path-->config build path-->libraries ...

  9. 【Zookeeper学习】Apache Zookeeper项目简介

    正在撰写,稍后来访……

随机推荐

  1. Oracle Schema Objects(Schema Object Storage And Type)

    One characteristic of an RDBMS is the independence of physical data storage from logical data struct ...

  2. 2017Summmer_上海金马五校 F题,G题,I题,K题,J题

    以下题目均自己搜 F题  A序列 一开始真的没懂题目什么意思,还以为是要连续的子串,结果发现时序列,简直智障,知道题意之后,好久没搞LIS,有点忘了,复习一波以后,直接双向LIS,处理处两个数组L和R ...

  3. git学习(5)分支管理(续)

    git学习(5)分支管理(续) 1.解决冲突 冲突的产生 如我们在新建分支和原来master分支上对同一文件做了修改并提交,在合并分支的时候就会遇到冲突 比如我新建了分支myBranch,在这个分支上 ...

  4. 大文本 mysql es

    大文本 mysql  es mysql  id longText  ---> es  longText mysqlId 大文本先入mysql,再同步至es: 文本查询逻辑交由es实现: mysq ...

  5. 通过js获取UserAgent写入数据库 js传值至php

    借助cookie,cookie是js和php互相传值的纽带.

  6. Spark源码分析 – DAGScheduler

    DAGScheduler的架构其实非常简单, 1. eventQueue, 所有需要DAGScheduler处理的事情都需要往eventQueue中发送event 2. eventLoop Threa ...

  7. SpringBoot 与缓存

    1. JSR107 Java Caching 定义了5个核心接口: CachingProvider:定义了创建,配置,获取,管理和控制多个CacheManager; CacheManager:定义了创 ...

  8. D. Two Paths---cf14D(树的直径)

    题目链接:http://codeforces.com/problemset/problem/14/D 题意:有n个city ; n-1条路:求断开一条路之后分成的两部分所构成的树的直径的积最大是多少: ...

  9. linux下安装mysql(mariadb)

    yum安装软件(官网很慢) yum install mariadb 发现版本如下,版本特别低,且安装包特别小, mariadb x86_64 :-.el7_5 base 8.9 M .我们可以配置ma ...

  10. Python之OS模块函数

    函数列表: 1 os.sep:取代操作系统特定的路径分隔符 os.name:指示你正在使用的工作平台.比如对于Windows,它是'nt',而对于Linux/Unix用户,它是'posix'. os. ...