异度部落格

学习是一种生活态度。


  • 首页

  • 归档

  • 分类

  • 标签

  • 关于

Java ClassLoader分析

发表于 2017-06-11 | 分类于 Programming , Java

什么是ClassLoader

一个Java程序是由若干个class文件组成。当程序在运行时,即会调用该程序的一个入口函数来调用系统的相关功能,而这些功能都被封装在不同的class文件当中,所以经常要从这个class文件中要调用另外一个class文件中的方法,如果另外一个class文件不存在的,则会引发ClassNotFoundException。

程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制(ClassLoader)来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其它class所引用。所以ClassLoader就是用来动态加载class文件到内存当中用的。

Java ClassLoader的体系结构

现在先看下ClassLoader的体系结构:
classload-architecture.png

Bootstrap ClassLoader

BootStrap ClassLoader:称为启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等。

可以通过如下程序获得该classloader所加载的jar

1
System.out.println(System.getProperty("sun.boot.class.path"));

程序输出:

1
/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/sunrsasign.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/classes

如果是scala会在java的基础上额外加载几个jar。

1
2
scala> println(System.getProperty("sun.boot.class.path"))
/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/sunrsasign.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/classes:/Users/zhenlong/DevTools/scala/lib/akka-actor_2.11-2.3.10.jar:/Users/zhenlong/DevTools/scala/lib/config-1.2.1.jar:/Users/zhenlong/DevTools/scala/lib/jline-2.12.1.jar:/Users/zhenlong/DevTools/scala/lib/scala-actors-2.11.0.jar:/Users/zhenlong/DevTools/scala/lib/scala-actors-migration_2.11-1.1.0.jar:/Users/zhenlong/DevTools/scala/lib/scala-compiler.jar:/Users/zhenlong/DevTools/scala/lib/scala-continuations-library_2.11-1.0.2.jar:/Users/zhenlong/DevTools/scala/lib/scala-continuations-plugin_2.11.7-1.0.2.jar:/Users/zhenlong/DevTools/scala/lib/scala-library.jar:/Users/zhenlong/DevTools/scala/lib/scala-parser-combinators_2.11-1.0.4.jar:/Users/zhenlong/DevTools/scala/lib/scala-reflect.jar:/Users/zhenlong/DevTools/scala/lib/scala-swing_2.11-1.0.2.jar:/Users/zhenlong/DevTools/scala/lib/scala-xml_2.11-1.0.4.jar:/Users/zhenlong/DevTools/scala/lib/scalap-2.11.7.jar

Extension ClassLoader

Extension ClassLoader:称为扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar。该类在sun.misc.launcher包中。

App ClassLoader

App ClassLoader:称为应用类加载器,也称为System ClassLoaer,负责加载应用程序CLASSPATH下的所有jar和class文件。该类也在sun.misc.launcher包中。

User-Defined ClassLoader

用户还可以根据需要定义自已的ClassLoader,而这些自定义的ClassLoader都必须继承自java.lang.ClassLoader类.

所有的ClassLoader都必须继承自java.lang.ClassLoader,而Bootstrap ClassLoader却不是。它不是一个普通的Java类,底层由C++编写,已嵌入到了JVM内核当中,当JVM启动后,Bootstrap ClassLoader也随着启动,负责加载完核心类库后,并构造Extension ClassLoader和App ClassLoader类加载器。

ClassLoader加载原理

ClassLoader使用的是双亲委托模型来搜索类的,每个ClassLoader实例都包含(而不是继承)一个父类加载器的引用。虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。

采用双亲委托机制加载类的时候采用如下的几个步骤:

  1. 当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类;
  2. 当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader.
  3. 当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。
  4. 如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则(找到),将为这个类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象;

总之,class的加载顺序为:cache -> parent classloader -> self.

使用双亲委托模型的好处:

  1. 避免重复加载。当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
  2. 提高安全性。如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非你改变JDK中ClassLoader搜索类的默认算法。

在JVM在搜索类的时候,又是如何判定两个class是相同的呢?JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM才认为这两个class是相同的。就算两个class是同一份class字节码,如果被两个不同的ClassLoader实例所加载,JVM也会认为它们是两个不同class。比如有一个Java类SimpleClass.java,javac编译之后生成字节码文件SimpleClass.class,ClassLoaderA和ClassLoaderB这两个类加载器并读取了SimpleClass.class文件,并分别定义出了java.lang.Class实例来表示这个类,对于JVM来说,它们是两个不同的实例对象,但它们确实是同一份字节码文件,如果试图将这个Class实例生成具体的对象进行转换时,就会抛运行时异常java.lang.ClassCaseException,提示这是两个不同的类型。

如何自定义ClassLoader

何时需要自己定制ClassLoader?

  1. 需要加载外部的class而这些class,默认的类加载器是加载不到的。例如,文件系统比较特殊或者需要从网络中加载一个class字节流等。
  2. 需要实现class的隔离性。常用的web服务器,如weblogic、tomcat、jetty等都实现这样的类加载器。这些类加载器主要做到:
    1)实现加载Web应用指定目录下的jar和class。
    2)实现部署在容器中的Web应用程共同使用的类库的共享。
    3)实现部署在容器中各个Web应用程序自己私有类库的相互隔离。

如何自定义ClassLoader

  1. 继承java.lang.ClassLoader
  2. 覆盖findClass()方法

下面是一个自定义ClassLoader的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = this.findLoadedClass(name); // optional
if (clazz == null) {
byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
}
clazz = defineClass(name, classData, 0, classData.length);
}
return clazz;
}

private byte[] loadClassData(String name) {
// TODO: load class data.
return null;
}
}

你好,2016

发表于 2016-01-02

2015,忙碌的一年。工作上的繁忙以及生活上的琐碎,让时间悄然从我们的指缝间流逝。本打算在年底写下这篇总结,可是自己的拖延症又发作了,直接把这篇博文拖入了2016。

回顾

工作

今年(2015)有四分之一的时间都是在Boston度过的,体验了一把国外的生活,同时也开拓了眼界。经历Boston历史上罕见的暴风雪,也经历了只有9度的盛夏。在Garriot大神的带领下,玩转了Boston,学到很多国外的文化。总之Boston之行,是一次不错的体验。

和去年相比,今年整个team的工作内容有所侧重,不再像以前那么繁杂,更多的focus在产品本身。我从产品的一个组件做到另一个组件,从Server做到的底层Engine,从Java做到C++又回到Java。在不同的组件以及技术栈上来回切换着,偶尔也需要support客户去解决一些老产品的问题。年底我被调到另外一个项目组,主要从事和Hadoop以及Spark相关的开发。主要是老板觉得我之前有相关的项目背景比较适合这一块,主要是我个人面对C++有点怂,于是我就这么稀里糊涂地过去了。然后最近几个月,我接手了一些已解散了的团队的组件升级开发,全新的领域全新的技术栈搞得我是焦头烂额,终于在年底前有点眉目,基本完成了。希望后面可以专心玩Hadoop和Spark。

今年无论是我们Team的人员有所变动。Team里往日一起工作的小伙伴,有的离职另谋高就,有的被re-org到其他组去了。也许这些变化在他们那些老人看来早已稀疏平常了吧。我所能做也许就是做好自己,适应变化。望离开的小伙伴们各个都有好前程。

生活

年初和妹纸把婚礼办了,婚礼朴素而平常,感觉两个人只是把一个task勾掉而已,就是累了些,没有太大的感觉。

9月份还和妹纸休了婚假,去了趟向往已久的云南,走的是昆大丽香的经典路线(游记)。第一次长途旅行,第一次自己做攻略,第一次写游记。做攻略、写游记还是挺有成就感的,不仅方便了自己,还帮助了他人。读万卷书不如走万里路,旅行的过程中学到了很多,也交到了许多朋友。同时,我和妹纸也成立了一个家庭旅游基金,把每个月的收入放一部分到这个基金里面,一方面可以控制日常开支,另一方面能够更好的控制旅游预算。

其他方面?生活琐碎一言难尽…

学习

由于自己已经丢了写读书笔记的习惯,也不记得看了那些书。看来写读书笔记的习惯应该要重新拾起来。

这一年,感觉自己技术的水平没有太大提高,技术书籍看的也不多。确切说应该还是看了一些书的,只是实在没有太深的印象了。除了那本《Programming in Scala》,全英文还那么厚,啃到后面实在是要吐了。

反思

不以忙碌为借口 感觉这一年自己一直在忙忙碌碌中度过的,而又有一种一事无成的感觉。最近听到的一句很有道理的话:“忙碌的人往往也是最懒的人”。我一直以忙为借口,忽略身边的许多人和事。许多计划,也因为忙碌而一拖再拖。

遇事莫急躁 不知道是因为忙碌而急躁,还是因为急躁而忙碌。今年总是觉得自己遇事特别容易急躁,无论是在工作中,还是在生活中。学习也没有太多的耐心,总是希望能够快点学习一些高大上或者fashion的技术。直到前几日,和Garriot大神在吃饭的路上聊天,忽然意识到自己现在这个状态有点“浮沙造塔”的感觉。

从计划到习惯 写到这里想起了大学辅导员说过的一些话:“大学生活就是两个字:习惯”,“大学里面除了习惯以外还有两个字:专注”。其实我已经不记得辅导员是在什么样的情况下说了这些话的,不过习惯和专注这两个词我记得很清楚。可惜我在毕业后把这两个词留在的大学。养成一种新的习惯需要多少天?有一种说法是13天,也有一种说法是21天,Anyway,养成才是最重要的。今年我偶尔晚上也会出去跑步或者在家做做俯卧撑什么的,而每当工作一忙,这些事情就被丢在脑后。我也会经常计划着做些什么,然后计划永远赶不上变化。

专注于一件事 其实要做到专注于一件事是很难的,每天有太多的琐事等着我们,而我们却根本没法避免。我想这里提到专注两个字更多指的是在一定时间或者一个阶段仅专注一件事。例如,晚上打算学习或者coding,这个时候就应该避免他人打扰,尤其是不要看手机。再比如说,最近在学习某一门语言或者一项新的技术,就应该沿着某一领域专心的学习下去,而不应该心猿意马,毕竟新技术是永远学不完的。

亲人和朋友 自己觉得这一两年无论是对亲人,还是身边的朋友都冷淡了许多,主动联系也少了。然而,遇到困难的时候,却是多一个朋友多一条路。

展望

本打算把来年的展望写在这里,不过想想还是算了,写在这里不如记在脑子里面来得靠谱一些。我相信2016年必定会比2015年更加精彩,有许多未知的事情等着我们,想想还有点小激动啊。

Sublime Text的Markdown写作

发表于 2015-08-30 | 分类于 Others

Sublime Text是一个十分强大的文本编辑器,它支持Windows,Linux和Mac。Sublime Text在默认情况下就支持Markdown格式,本文想介绍的是如何增强Sublime Text使其更好的Markdown写作。

Package Control

想必大多数SublimeText的用户已经安装了Package Control这个神器了。如未安装,请参考:https://packagecontrol.io/installation

Markdown Preview

markdown preview这个插件可以使用户在浏览器中预览Markdown文件。

按下键Ctrl+Shift+p调出命令面板,找到Package Control: install Pakage这一项。搜索markdown preview,点击安装。

关于快捷键设置,可以在Preferences->Key Binding User中,加入

1
2
3
4
5
6
7
8
9
{ 
"keys": ["alt+m"],
"command": "markdown_preview",
"args":
{
"target": "browser"

}
}

语法高亮

默认SublimeText带的语法高亮对Markdown确实太少了,可以装一个Monokai Extended插件进行增强。在Package Control中搜索Monokai Extended并安装,然后在Preferences->Color Scheme中进行设置。

我的2014

发表于 2015-02-12

貌似已经很久没有写年终总结了,上次写已经是大学时候的事情了。仔细想想,写写总结还是有必要的,也许等二十年后回看这一篇一篇blog,才会想起自己一步一步如何走来的。

在2014年年初的时候,给自己定了个主题词:成长。一年过去了,个人认为自己或多或少还是有些成长的,无论在工作还是生活方面。

工作

这一年的工作中规中矩,虽说不是team里面表现最突出的一个,但至少没拉team的后腿,我已经很欣慰了。工作一年多来,一直觉得老板以及team里的小伙伴都非常nice,工作氛围也很好。上班嘛开心最总要,每天上班跟大家扯扯淡,周三晚上大家打打牌这日子还是不错的。

这一年里,由于项目需要自己的focus也一直在转变,自己就是team里的一块砖,哪里需要哪里搬。不过也学到了很多东西,接触了很多从来没有接触过的领域,如Big Data、Cloud以及Security等。不过,个人感觉很多都是浅尝辄止,希望来年能有一块能长期一直做下去的项目或者领域。

最后恭喜Big Data Discovery 1.0顺利release,说明这一年没瞎忙,散花。

技术

说到技术方面,感觉很难评价。这一年接触到很多新技术,如Hadoop、ZooKeeper以及Spark等最近非常火热的大数据相关的项目。不过,感觉大多都只是涉猎,很难说可以做到深入研究。个人感觉一个是没有实际需求去驱动,另一个就是这些项目都太大了,都不是什么分分钟能搞定的事情。

今年和几个志同道合的小伙伴成立了一个众成技术俱乐部,每个月大家一起交流技术问题,分享学到的东西,感觉还是很不错的。通过和小伙伴的交流和分享明显感觉到自己水平太low,书看太少,以至于都没什么货可以拿出来交流的。

俗话说:三天不读书,智商输给猪。接下来的一年估计多看书多敲代码了,到时候把看过的书单放到明年的总结里面。不过,重点应该还是放在Java、Scala以及分布式系统相关的一些技术上。虽说后面因为项目上的需要,得开始玩C++,可惜明显感觉自己的智商已经跟不上C++发展了,心塞。

生活

这一年,家里发生很多事情,希望接下来的一年一切安好。自己也于2014年12月27日,跟妹纸领了证。2014,爱你一世,从此肩上也多了一份责任,多了一份担当。

#展望

2015年,追求卓越!

Octopress-installation

发表于 2014-10-31 | 分类于 Others

Octopress是一个基于Jekyll静态页面生成器,多数用于Github Pages的生成。本文主要介绍Octopress在Windows以及Linux平台下的安装,本文的安装步骤基于Window 7以及Ubuntu 14.04。

Git安装

Git下载地址:http://git-scm.com/downloads

Ubuntu用户也可执行下面命令:

1
sudo apt-get install git

Ruby安装

Ruby下载地址:http://rubyinstaller.org/downloads/。这里需要注意,所选择的版本应为Ruby1.9.3或者更高,本文使用的是Ruby1.9.3。

Windows用户还需额外安装RubyDevKit,下载地址:http://rubyinstaller.org/downloads/

1
2
3
cd C:/RubyDevKit
ruby dk.rb init
ruby dk.rb install

安装后使用

--version```命令确认所安装的Ruby版本。
1
2
3
4
5
6
7
8
9

# Octopress安装
克隆Octopress源码,安装依赖。

``` bash
git clone git://github.com/imathis/octopress.git octopress
cd octopress
gem install bundler
bundle install

安装Octopress默认主题。

1
rake install

到目前为止,Octopress已经安装完成,可以使用

generate```命令尝试生成静态页面。使用```rake preview```命令到进行预览。
1
2
3
4

# Troubleshooting

Windows用户,在执行```rake generate```命令的过程中,可能会出现类似

warning: cannot close fd before spawn

1
2
3
4
5
6

的警告信息,导致静态页面生成失败。这是主要是由于pygments的版本与Windows不兼容导致的。可以尝试执行以下步骤进行修复:

``` bash
gem uninstall pygments.rb --version ">0.5.0"
gem install pygments.rb --version "=0.5.0"

在项目文件夹下修改Gemfile.lock文件

1
pygments.rb (0.6.0) => pygments.rb (0.5.0)

Zookeeper源码分析-一致性协议Zab

发表于 2014-10-23 | 分类于 Big Data , ZooKeeper

Zookeeper使用了一种称为Zab(Zookeeper Atomic Broadcast)的协议作为其一致性的核心。Zab协议是Paxos协议的一种变形,下面将展示一些协议的核心内容。

考虑到Zookeeper的主要操作数据状态,为了保证一致性,Zookeeper提出了两个安全属性:

  • 全序(Total Order):如果消息A在消息B之前发送,则所有Server应该看到相同结果。
  • 因果顺序(Causal Order):如果消息A在消息B之前发生(A导致了B),并且一起发送,则消息A始终在消息B之前被执行。

为了保证上述两个安全属性,Zookeeper使用了TCP协议和Leader。通过使用TCP协议保证了消息的全序的特性(先发先到),通过Leader解决了因果顺序(先到Leader先执行)。因为有了Leader,Zookeeper的架构就变成为:Master-Slave模式,但在该模式中Master(Leader)会Crash,因此,Zookeeper引入Leader选举算法,以保证系统的健壮性。

当Zookeeper Server收到写操作,Follower会将其转发给Leader,由Leader执行操作。Client可以直接从Follower上读取数据,如果需要读取最新数据,则需要从Leader节点读取,Zookeeper设计的读写比大致为2:1。

Leader执行写操作可以简化为一个两段式提交的transaction:

  1. Leader发送proposal给所有的Follower。
  2. 收到proposal后,Follower回复ACK给Leader,接受Leader的proposal.
  3. 当Leader收到大多数的Follower的ACK后,将commit其proposal。

broadcast

在这个过程中,proposal的确认不需要所有节点都同意,如果有2n+1个节点,那么只要有n个节点同意即可,也就是说Zookeeper允许n个节点down掉。任何两个多数派必然有交集,在Leader切换(Leader down)时,这些交集依然保持着最新的系统状态。如果集群节点个数少于n+1个时,Zookeeper将无法进行同步,也就无法继续工作。

Zab与Paxos

Zab的作者认为Zab与paxos并不相同,只所以没有采用Paxos是因为Paxos保证不了全序顺序:

Because multiple leaders can propose a value for a given instance two problems arise.
First, proposals can conflict. Paxos uses ballots to detect and resolve conflicting proposals.
Second, it is not enough to know that a given instance number has been committed, processes must also be able to figure out which value has been committed.

举个例子。假设一开始Paxos系统中的Leader是P1,他发起了两个事务{t1, v1}(表示序号为t1的事务要写的值是v1)和{t2, v2},过程中Leader挂了。新来个Leader是P2,他发起了事务{t1, v1’}。而后又来个新Leader是P3,他汇总了一下,得出最终的执行序列{t1, v1’}和{t2, v2}。

这样的序列为什么不能满足ZooKeeper的需求呢?ZooKeeper是一个树形结构,很多操作都要先检查才能确定能不能执行,比如P1的事务t1可能是创建节点“/a”,t2可能是创建节点“/a/aa”,只有先创建了父节点“/a”,才能创建子节点“/a/aa”。而P2所发起的事务t1可能变成了创建“/b”。这样P3汇总后的序列是先创建“/b”再创建“/a/aa”,由于“/a”还没建,创建“a/aa”就搞不定了。

为了保证这一点,ZAB要保证同一个leader的发起的事务要按顺序被apply,同时还要保证只有先前的leader的所有事务都被apply之后,新选的leader才能在发起事务。

Zookeeper源码分析-Zookeeper Leader选举算法

发表于 2014-10-18 | 分类于 Big Data , ZooKeeper

当Leader崩溃或者Leader失去大多数的Follower,这时候zk进入恢复模式,恢复模式需要重新选举出一个新的Leader,让所有的Server都恢复到一个正确的状态。Zookeeper中Leader的选举采用了三种算法:

  • LeaderElection
  • FastLeaderElection
  • AuthFastLeaderElection

并且在配置文件中是可配置的,对应的配置项为electionAlg。

背景知识

Zookeeper Server的状态可分为四种:

  • LOOKING:寻找Leader
  • LEADING:Leader状态,对应的节点为Leader。
  • FOLLOWING:Follower状态,对应的节点为Follower。
  • OBSERVING:Observer状态,对应节点为Observer,该节点不参与Leader选举。

成为Leader的必要条件: Leader要具有最高的zxid;当集群的规模是n时,集群中大多数的机器(至少n/2+1)得到响应并follow选出的Leader。

心跳机制:Leader与Follower利用PING来感知对方的是否存活,当Leader无法相应PING时,将重新发起Leader选举。

术语

zxid:zookeeper transaction id, 每个改变Zookeeper状态的操作都会形成一个对应的zxid,并记录到transaction log中。 这个值越大,表示更新越新。

electionEpoch/logicalclock:逻辑时钟,用来判断是否为同一次选举。每调用一次选举函数,logicalclock自增1,并且在选举过程中如果遇到election比当前logicalclock大的值,就更新本地logicalclock的值。

peerEpoch: 表示节点的Epoch。

LeaderElection选举算法

LeaderElection是Fast Paxos最简单的一种实现,每个Server启动以后都询问其它的Server它要投票给谁,收到所有Server回复以后,就计算出zxid最大的哪个Server,并将这个Server相关信息设置成下一次要投票的Server。该算法于Zookeeper 3.4以后的版本废弃。

选举算法流程如下:

  1. 选举线程首先向所有Server发起一次询问(包括自己);
  2. 选举线程收到回复后,验证是否是自己发起的询问(验证xid是否一致),然后获取对方的id(myid),并存储到当前询问对象列表中,最后获取对方提议的leader相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中;
  3. 收到所有Server回复以后,就计算出zxid最大的那个Server,并将这个Server相关信息设置成下一次要投票的Server;
  4. 线程将当前zxid最大的Server设置为当前Server要推荐的Leader,如果此时获胜的Server获得多数Server票数, 设置当前推荐的leader为获胜的Server,将根据获胜的Server相关信息设置自己的状态,否则,继续这个过程,直到leader被选举出来。

leader-election

通过流程分析我们可以得出:要使Leader获得多数Server的支持,则Server总数必须是奇数2n+1,且存活的Server的数目不得少于n+1.

异常问题的处理:

  1. 选举过程中,Server的加入
    当一个Server启动时它都会发起一次选举,此时由选举线程发起相关流程,那么每个 Serve r都会获得当前zxi d最大的哪个Serve r是谁,如果当次最大的Serve r没有获得n/2+1 个票数,那么下一次投票时,他将向zxid最大的Server投票,重复以上流程,最后一定能选举出一个Leader。
  2. 选举过程中,Server的退出
    只要保证n/2+1个Server存活就没有任何问题,如果少于n/2+1个Server 存活就没办法选出Leader。
  3. 选举过程中,Leader死亡
    当选举出Leader以后,此时每个Server应该是什么状态(FLLOWING)都已经确定,此时由于Leader已经死亡我们就不管它,其它的Fllower按正常的流程继续下去,当完成这个流程以后,所有的Fllower都会向Leader发送Ping消息,如果无法ping通,就改变自己的状为(FLLOWING ==> LOOKING),发起新的一轮选举。
  4. 选举完成以后,Leader死亡
    处理过程同上。
  5. 双主问题
    Leader的选举是保证只产生一个公认的Leader的,而且Follower重新选举与旧Leader恢复并退出基本上是同时发生的,当Follower无法ping同Leader是就认为Leader已经出问题开始重新选举,Leader收到Follower的ping没有达到半数以上则要退出Leader重新选举。

FastLeaderElection选举算法

由于LeaderElection收敛速度较慢,所以Zookeeper引入了FastLeaderElection选举算法,FastLeaderElection也成了Zookeeper默认的Leader选举算法。

FastLeaderElection是标准的Fast Paxos的实现,它首先向所有Server提议自己要成为leader,当其它Server收到提议以后,解决 epoch 和 zxid 的冲突,并接受对方的提议,然后向对方发送接受提议完成的消息。FastLeaderElection算法通过异步的通信方式来收集其它节点的选票,同时在分析选票时又根据投票者的当前状态来作不同的处理,以加快Leader的选举进程。

算法流程

数据恢复阶段

每个ZooKeeper Server读取当前磁盘的数据(transaction log),获取最大的zxid。

发送选票

每个参与投票的ZooKeeper Server向其他Server发送自己所推荐的Leader,这个协议中包括几部分数据:

  • 所推举的Leader id。在初始阶段,第一次投票所有Server都推举自己为Leader。
  • 本机的最大zxid值。这个值越大,说明该Server的数据越新。
  • logicalclock。这个值从0开始递增,每次选举对应一个值,即在同一次选举中,这个值是一致的。这个值越大说明选举进程越新。
  • 本机的所处状态。包括LOOKING,FOLLOWING,OBSERVING,LEADING。

处理选票

每台Server将自己的数据发送给其他Server之后,同样也要接受其他Server的选票,并做一下处理。

如果Sender的状态是LOOKING

  • 如果发送过来的logicalclock大于目前的logicalclock。说明这是更新的一次选举,需要更新本机的logicalclock,同事清空已经收集到的选票,因为这些数据已经不再有效。然后判断是否需要更新自己的选举情况。首先判断zxid,zxid大者胜出;如果相同比较leader id,大者胜出。
  • 如果发送过来的logicalclock小于于目前的logicalclock。说明对方处于一个比较早的选举进程,只需要将本机的数据发送过去即可。
  • 如果发送过来的logicalclock等于目前的logicalclock。根据收到的zxid和leader id更新选票,然后广播出去。

当Server处理完选票后,可能需要对Server的状态进行更新:

  • 判断服务器是否已经收集到所有的服务器的选举状态。如果是根据选举结果设置自己的角色(FOLLOWING or LEADER),然后退出选举。
  • 如果没有收到没有所有服务器的选举状态,也可以判断一下根据以上过程之后更新的选举Leader是不是得到了超过半数以上服务器的支持。如果是,那么尝试在200ms内接收下数据,如果没有心数据到来说明大家已经认同这个结果。这时,设置角色然后退出选举。

如果Sender的状态是FOLLOWING或者LEADER

  • 如果LogicalClock相同,将数据保存早recvset,如果Sender宣称自己是Leader,那么判断是不是半数以上的服务器都选举它,如果是设置角色并退出选举。
  • 否则,这是一条与当前LogicalClock不符合的消息,说明在另一个选举过程中已经有了选举结果,于是将该选举结果加入到OutOfElection集合中,根据OutOfElection来判断是否可以结束选举,如果可以也是保存LogicalClock,更新角色,退出选举。

fast-leader-election

具体实现

数据结构

本地消息结构:

1
2
3
4
5
6
7
8
9
10
static public class Notification {
long leader; //所推荐的Server id

long zxid; //所推荐的Server的zxid(zookeeper transtion id)

long epoch; //描述leader是否变化(每一个Server启动时都有一个logicalclock,初始值为0)

QuorumPeer.ServerState state; //发送者当前的状态
InetSocketAddress addr; //发送者的ip地址
}

网络消息结构:

1
2
3
4
5
6
7
8
9
10
11
12
static public class ToSend {

int type; //消息类型
long leader; //Server id
long zxid; //Server的zxid
long epoch; //Server的epoch
QuorumPeer.ServerState state; //Server的state
long tag; //消息编号

InetSocketAddress addr;

}

线程处理

每个Server都一个接收线程池和一个发送线程池, 在没有发起选举时,这两个线程池处于阻塞状态,直到有消息到来时才解除阻塞并处理消息,同时每个Server都有一个选举线程(可以发起选举的线程担任)。

  • 接收线程的处理
    notification: 首先检测当前Server上所被推荐的zxid,epoch是否合法(currentServer.epoch <= currentMsg.epoch && (currentMsg.zxid > currentServer.zxid || (currentMsg.zxid == currentServer.zxid && currentMsg.id > currentServer.id))) 如果不合法就用消息中的zxid,epoch,id更新当前Server所被推荐的值,此时将收到的消息转换成Notification消息放入接收队列中,将向对方发送ack消息。
    ack: 将消息编号放入ack队列中,检测对方的状态是否是LOOKING状态,如果不是说明此时已经有Leader已经被选出来,将接收到的消息转发成Notification消息放入接收对队列

  • 发送线程池的处理
    notification: 将要发送的消息由Notification消息转换成ToSend消息,然后发送对方,并等待对方的回复,如果在等待结束没有收到对方法回复,重做三次,如果重做次还是没有收到对方的回复时检测当前的选举(epoch)是否已经改变,如果没有改变,将消息再次放入发送队列中,一直重复直到有Leader选出或者收到对方回复为止。
    ack: 主要将自己相关信息发送给对方

  • 选举线程的处理
    首先自己的epoch加1,然后生成notification消息,并将消息放入发送队列中,系统中配置有几个Server就生成几条消息,保证每个Server都能收到此消息,如果当前Server的状态是LOOKING就一直循环检查接收队列是否有消息,如果有消息,根据消息中对方的状态进行相应的处理。

AuthFastLeaderElection选举算法

AuthFastLeaderElection算法同FastLeaderElection算法基本一致,只是在消息中加入了认证信息,该算法在最新的Zookeeper中也建议弃用。

Example

下面看一个Leader选举的例子以加深对Leader选举算法的理解。

  1. 服务器1启动,此时只有它一台服务器启动了,它发出去的报没有任何响应,所以它的选举状态一直是LOOKING状态.
  2. 服务器2启动,它与最开始启动的服务器1进行通信,互相交换自己的选举结果,由于两者都没有历史数据,所以id值较大的服务器2胜出,但是由于没有达到超过半数以上的服务器都同意选举它(这个例子中的半数以上是3),所以服务器1,2还是继续保持LOOKING状态.
  3. 服务器3启动,根据前面的理论分析,服务器3成为服务器1,2,3中的Leader,而与上面不同的是,此时有三台服务器选举了它,所以它成为了这次选举的Leader.
  4. 服务器4启动,根据前面的分析,理论上服务器4应该是服务器1,2,3,4中最大的,但是由于前面已经有半数以上的服务器选举了服务器3,所以它只能是Follower.
  5. 服务器5启动,同4一样,Follower.

参考资料

http://blog.csdn.net/xhh198781/article/details/10949697

http://blog.cnsolomo.com/ld/liunx/nginx/264.html

http://blog.sina.com.cn/s/blog_3fe961ae01012jod.html

http://blog.csdn.net/xhh198781/article/details/6619203

http://csrd.aliapp.com/?p=162

http://codemacro.com/2014/10/19/zk-fastleaderelection/

Zookeeper源码分析-Zookeeper角色

发表于 2014-10-18 | 分类于 Big Data , ZooKeeper

在Zookeeper集群中,主要分为三者角色,而每一个节点同时只能扮演一种角色,这三种角色分别是:

  • Leader:接受所有Follower的提案请求并统一协调发起提案的投票,负责与所有的Follower进行内部的数据交换(同步);
  • Follower:直接为客户端服务并参与提案的投票,同时与Leader进行数据交换(同步);
  • Observer:直接为客户端服务但并不参与提案的投票,同时也与Leader进行数据交换(同步);

Follower与Observer并称为Learner。

zookeeper-server-roles

Leader

在Zookeeper集群中,只有一个Leader节点,其主要职责:

  • 恢复数据;
  • 维持与Learner的心跳,接收Learner请求并判断Learner的请求消息类型;

Leader的工作流程简图如下所示,在实际实现中,流程要比下图复杂得多,启动了三个线程来实现功能。

leader-workflow

PING:Learner的心跳。
REQUEST:Follower发送的提议信息,包括写请求及同步请求。
ACK:Follower的对提议的回复,超过半数的Follower通过,则commit该提议。
REVALIDATE:用来延长SESSION有效时间。

Follower

在Zookeeper集群中,follower可以为多个,其主要职责:

  • 向Leader发送请求;
  • 接收Leader的消息并进行处理;
  • 接收Zookeeper Client的请求,如果为写清求,转发给Leader进行处理

Follower的工作流程简图如下所示,在实际实现中,Follower是通过5个线程来实现功能的。

follower-workflow

PING:心跳消息。
PROPOSAL:Leader发起的提案,要求Follower投票。
COMMIT:服务器端最新一次提案的信息。
UPTODATE:表明同步完成。
REVALIDATE:根据Leader的REVALIDATE结果,关闭待revalidate的session还是允许其接受消息。
SYNC:返回SYNC结果到客户端,这个消息最初由客户端发起,用来强制得到最新的更新。

Observer

此处Observer就不再分析,其与follower基本相同,唯一不同就在于Observer不参与投票。

Zookeeper源码分析-Zookeeper Server启动分析

发表于 2014-09-22 | 分类于 Big Data , ZooKeeper

Zookeeper Server的启动入口为org.apache.zookeeper.server.quorum.QuorumPeerMain。Zookeeper的启动模式分为两种:一种为standalone mode;另一种为cluster mode。

  • Standalone模式:当配置文件中仅配置了一台server时,Zookeeper将以standalone模式启动,启动类为ZooKeeperServerMain,此处不作详细分析。
  • Cluster模式:当配置文件中配置了多台server,构建cluster,启动类为QuorumPeer#start()。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public synchronized void start() {
loadDataBase();
cnxnFactory.start();
startLeaderElection();
super.start();
}
```

# 清理DataDir
在Server启动时,根据启动的配置参数启动一个TimeTask用于清理DataDir中的snapshot及对应的transactionlog。由于Zookeeper的任何一个写操作都将在transaction log中留下记录,当写操作达到一定量或者一定时间间隔,Zookeeper将transaction log合并为snapshot。所以随着运行时间的增长生成的transaction log和snapshot将越来越多,所以定期清理是必要的。在DatadirCleanupManager中有两个参数:

- snapRetainCount:清理后保留的snapshot的个数,该参数至少要大于3。
- purgeInterval:定期清理的时间间隔,以小时为单位。

``` java
// Start and schedule the the purge task
DatadirCleanupManager purgeMgr = new DatadirCleanupManager(config
.getDataDir(), config.getDataLogDir(), config
.getSnapRetainCount(), config.getPurgeInterval());
purgeMgr.start();

加载ZKDatabase

1.从snapshot和transaction log中恢复ZKDatabase,并将其载入内存。

1
zkDb.loadDataBase();

2.载入currentEpoch和acceptedEpoch

首先要明白什么是epoch,官方给出的解释是:

The zxid has two parts: the epoch and a counter. In our implementation the zxid is a 64-bit number. We use the high order 32-bits for the epoch and the low order 32-bits for the counter. Because it has two parts represent the zxid both as a number and as a pair of integers, (epoch, count). The epoch number represents a change in leadership. Each time a new leader comes into power it will have its own epoch number.

从currentEpoch文件中读取current epoch;若currentEpoch文件不存在,Zookeeper将从lastProcessedZxid中获取epoch作为current epoch,写入currentEpoch文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
try {
currentEpoch = readLongFromFile(CURRENT_EPOCH_FILENAME);
if (epochOfZxid > currentEpoch && updating.exists()) {
LOG.info("{} found. The server was terminated after " +
"taking a snapshot but before updating current " +
"epoch. Setting current epoch to {}.",
UPDATING_EPOCH_FILENAME, epochOfZxid);
setCurrentEpoch(epochOfZxid);
if (!updating.delete()) {
throw new IOException("Failed to delete " +
updating.toString());
}
}
} catch(FileNotFoundException e) {
// pick a reasonable epoch number
// this should only happen once when moving to a
// new code version
currentEpoch = epochOfZxid;
LOG.info(CURRENT_EPOCH_FILENAME
+ " not found! Creating with a reasonable default of {}. This should only happen when you are upgrading your installation",
currentEpoch);
writeLongToFile(CURRENT_EPOCH_FILENAME, currentEpoch);
}

同理,获取accepted epoch。

1
2
3
4
5
6
7
8
9
10
11
12
try {
acceptedEpoch = readLongFromFile(ACCEPTED_EPOCH_FILENAME);
} catch(FileNotFoundException e) {
// pick a reasonable epoch number
// this should only happen once when moving to a
// new code version
acceptedEpoch = epochOfZxid;
LOG.info(ACCEPTED_EPOCH_FILENAME
+ " not found! Creating with a reasonable default of {}. This should only happen when you are upgrading your installation",
acceptedEpoch);
writeLongToFile(ACCEPTED_EPOCH_FILENAME, acceptedEpoch);
}

启动ServerCnxnFactory线程

ServerCnxnFacotry是管理ServerCnxn处理类的工厂,它负责对connection上数据处理的调度,以及server级别的一些处理,例如关闭指定session等。有关ServerCnxnFactory的实现类,可分为三种情况:

  • NIO模式。这是Zookeeper默认的ServerCnxnFactory实现,其实现类为NIOServerCnxnFactory。
  • Netty模式。在Zookeeper 3.4以后引入了Netty作为Server端连接处理的可选实现。Netty是一套非常高效的异步通信框架。可以通过JVM参数zookeeper.serverCnxnFactory进行配置。
  • 自定义模型。Zookeeper还支持自定类来实现通信,同样可以通过JVM参数zookeeper.serverCnxnFactory进行配置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Static public ServerCnxnFactory createFactory() throws IOException {
String serverCnxnFactoryName =
System.getProperty(ZOOKEEPER_SERVER_CNXN_FACTORY);
if (serverCnxnFactoryName == null) {
serverCnxnFactoryName = NIOServerCnxnFactory.class.getName();
}
try {
return (ServerCnxnFactory) Class.forName(serverCnxnFactoryName)
.newInstance();
} catch (Exception e) {
IOException ioe = new IOException("Couldn't instantiate "
+ serverCnxnFactoryName);
ioe.initCause(e);
throw ioe;
}
}

ServerCnxnFactory的主要职责:

  1. 在单机模式下,引导完成Zookeeper Server的实例化。
  2. 异步接收Client的IO连接,并维护连接的IO操作,这是ServerCnxnFactory的核心功能。ServerCnxnFacotry本身被设计成一个Thread,在完成初始化工作之后,就开始启动自身线程,在线程run方法中,采用NIO的方式Accept客户端连接,创建一个NIOServerCnxn实例,此实例和普通的NIO设计思路一样,它持有当前连接的Channel句柄和Buffer队列,最终将此NIOServerCnxn放入Factory内部的一个set中,以便此后对链接信息进行查询和操作(比如关闭操作,IO中read和write操作等)。

Leader选举

Leader的选举算法有三种:

  • LeaderElection:LeaderElection采用的是fast paxos算法的一种简单实现,目前该算法在3.4以后已经建议弃用。
  • FastLeaderElection:FastLeaderElection是标准的fast paxos的实现,这是目前Zookeeper默认的Leader选举算法。
  • AuthFastLeaderElection:AuthFastLeaderElection算法同FastLeaderElection算法基本一致,只是在消息中加入了认证信息,该算法在最新的Zookeeper中也建议弃用。

Leader选举算法分析详见:Zookeeper源码分析-Zookeeper Leader选举算法

QuorumPeer启动

完成DataDir,ZKDatabase加载以及Leader选举动作后,QuorumPeer完成初始化及启动工作。

参考资料

http://zookeeper.apache.org/doc/r3.4.6/zookeeperInternals.html

http://shift-alt-ctrl.iteye.com/blog/1846507

Zookeeper源码分析-数据模型

发表于 2014-09-14 | 分类于 Big Data , ZooKeeper

本文主要介绍Zookeeper的数据模型,包括Zookeeper的数据视图,节点类型以及节点所包含的信息。

节点

Zookeeper的数据视图采用的是类似Unix的数据视图,但是并没有引入文件系统的相关概念:目录和文件,而是引入了节点的概念,称为Znode。它是Zookeeper最小的组成单元,每个Znode包含三部分组成:

  • data:表示节点所存储的数据,单个节点存储数据不能超过1M。
  • stat:表示节点的信息,例如节点创建时间,节点的版本,节点的权限等信息。
  • children:表是所包含的子节点。

zookeeper-data-model

节点类型

Zookeeper中的节点类型可以分为三种:

  • 持久节点(Perisient Node),这类节点在创建之后将一直存在,知道有客户端对它进行显示删除,也就是说它不会因为创建其的client的关闭而消失。
  • 临时节点(Ephemeral Node),这类节点的生命周期与创建它们的client绑定。也就是说当client的session关闭时,其所创建的临时节点也将被删除。这里有一点需要注意的是session关闭,而不是connection loss。因为zookeeper的client支持session转移,也就是当所连接的server出现网络连接断开或者server down掉的情况,client将自动选择zookeeper集群中的其他节点进行连接,同时将session转移到其他节点上。这种情况只能算connection loss,而不会造成session关闭。除非,该client无法连接所有的节点,支持session timeout。
  • 时序节点(Sequential Node),这类节点在创建节点的时候,zookeeper会自动为其添加一个数字后缀%10d,例如/test-0000000001。不过这类节点是不能单独存在的,需要同持久节点或临时节点组合使用形成:
    • Perisient Sequential Node
    • Ephemeral Sequential Node

节点信息

Znode的信息包含下面一些字段:

czxid

创建这个znode的zxid

mzxid

最后一次修改这个znode的zxid

ctime

该znode创建时间

mtime

该znode最后一次修改的时间

version

该znode的version,也就是该znode的修改次数。

cversion

该znode的子节点的version

aversion

该znode的ACL信息version

ephemeralOwner

如果该znode是ephemeral node,此字段就是对应client的session;否则为0。

dataLength

The length of the data field of this znode.

numChildren

子节点个数


Note:zxid = znode transaction id

查看节点信息,可以使用zkCli中的get命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
[zk: localhost:2181(CONNECTED) 15] get /zk_test
my_data
cZxid = 0x8
ctime = Sun Sep 14 14:43:56 CST 2014
mZxid = 0x8
mtime = Sun Sep 14 14:43:56 CST 2014
pZxid = 0x10
cversion = 2
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 7
numChildren = 2
12…28
Kevin Huang

Kevin Huang

272 日志
29 分类
92 标签
GitHub E-Mail Twitter FB Page StackOverflow
© 2018 Kevin Huang
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.3