大数据学习 - socket

一个socket小Demo

server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ServiceServer {

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

// 创建一个serverSocket 绑定到本机的8080端口上
ServerSocket server = new ServerSocket();
server.bind(new InetSocketAddress("localhost", 8080));

// 接收客户端的连接请求, accpet时一个阻塞方法, 会一直等待到有客户端连接
while(true){
Socket socket = server.accept();
new Thread(new ServiceServerTask(socket)).start();
}


}

}

client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ServiceClient {

public static void main(String[] args) throws IOException {
// 和服务器发送请求建立了连接
Socket socket = new Socket("localhost", 8080);
// 从socket获取输入输出流
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();

PrintWriter pw = new PrintWriter(os);
pw.println("hello, server");
pw.flush();

BufferedReader br = new BufferedReader(new InputStreamReader(is));
String result = br.readLine();
System.out.println(result);

socket.close();

}

}

task

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class ServiceServerTask implements Runnable {

private Socket socket;

public ServiceServerTask(Socket socket){
this.socket = socket;
}

// 业务逻辑, 跟客户端进行数据交互
@Override
public void run() {
InputStream is = null;
OutputStream os = null;
try {
// 从socket连接中获取到与client之间的网络通信输入输出流
is = socket.getInputStream();
os = socket.getOutputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 从网络通信输入流中读取客户端发送过来的数据
// socketinputstream的读数据的方法都是阻塞的
String param = br.readLine();
GetDataServiceImpl getDataService = new GetDataServiceImpl();
String result = getDataService.getData(param);
// 将返回数据写道socket的输出流中, 以发送客户端
PrintWriter pw = new PrintWriter(os);
pw.println(result);
pw.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if(is != null){
is.close();
}
if(os != null) {
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

}

处理类

1
2
3
4
5
6
7
public class GetDataServiceImpl {

public String getData(String param){
return "OK: " + param;
}

}
评论

大数据学习 - 反射

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Data
public class Person implements Serializable {

private static final long serialVersionUID = 5772917160911675752L;

public Person(String name, Integer age){
this.name = name;
this.age = age;
}

private Person(String name){
this.name = name;
}

public Person(){

}

private String name;

private Integer age;

public String phone;

public void toStringCustom(){
System.out.println(name);
System.out.println(age);
System.out.println(phone);
}

}

常用反射方法Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
public class ReflectTest {

private String className = null;

private Class personClass = null;

@Before
public void init() throws ClassNotFoundException {
className = "com.jinzl.reflect.Person";
personClass = Class.forName(className);
}

@Test
public void getClassName(){
System.out.println(personClass);
// 获取某个class文件对象的另一种方式
System.out.println(Person.class);
}

/**
* 创建一个class文件表示的实例对象, 底层会调用无参构造方法
*/
@Test
public void getNewInstance() throws IllegalAccessException, InstantiationException {
System.out.println(personClass.newInstance());
}

/**
* 反射调用公有有参构造方法
*/
@Test
public void getPublicConstructorTest() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor constructor = personClass.getConstructor(String.class, Integer.class);
Person person = (Person) constructor.newInstance("jinzili", 18);
System.out.println(person);
}

/**
* 反射调用私有有参构造方法
*/
@Test
public void getPrivateConstructorTest() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor constructor = personClass.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
Person person = (Person) constructor.newInstance("jinzili");
System.out.println(person);
}

/**
* 访问非私有的成员变量
*/
@Test
public void getPublicField() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Constructor constructor = personClass.getConstructor(String.class, Integer.class);
Person person = (Person) constructor.newInstance("jinzili", 18);

Field field = personClass.getField("phone");
field.set(person, "18721317074");
System.out.println(person);
}

/**
* 访问私有的成员变量
*/
@Test
public void getPrivateField() throws Exception {
Constructor constructor = personClass.getConstructor(String.class, Integer.class);
Person person = (Person) constructor.newInstance("jinzili", 18);

Field field = personClass.getDeclaredField("name");
field.setAccessible(true);
field.set(person, "jinzili");
System.out.println(person);
}

/**
* 获取非私有的成员函数
*/
@Test
public void getPublicMethod() throws Exception{
Object obj = personClass.newInstance();
Object object = personClass.getMethod("toStringCustom").invoke(obj);
System.out.println(object);
}

/**
* 获取私有的成员函数
*/
@Test
public void getPrivateMethod() throws Exception{
Constructor constructor = personClass.getConstructor(String.class, Integer.class);
Object obj = constructor.newInstance("jinzili", 18);
Method method = personClass.getDeclaredMethod("getName");
method.setAccessible(true);
Object value = method.invoke(obj);
System.out.println(value);
}

/**
* 其他方法
*/
@Test
public void getOtherMethod() throws Exception{
// 当前加载这个class文件的哪个类加载器对象
System.out.println(personClass.getClassLoader());
// 获取某个类实现的所有接口
Class[] interfaces = personClass.getInterfaces();
for(Class _interface : interfaces){
System.out.println(_interface);
}
// 反射当前这个类的直接父类
System.out.println(personClass.getGenericSuperclass());
// 判断当前的Class对象表示是否是数组
System.out.println(personClass.isArray());
System.out.println(new String[3].getClass().isArray());
// 判断当前的Class对象表示是否是枚举
System.out.println(personClass.isEnum());
// 判断当前的Class对象表示是否是接口
System.out.println(personClass.isInterface());
}

}
评论

大数据学习 - JMS

什么是JMS

JMS即Java消息服务, Java Message Service,应用程序接口是一个Java平台中关于面向消息中间件的API,用于两个程序之间或分布式系统中发送消息,进行异步通信,它是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。

体系架构

JMS由一下几个元素组成

  • 提供者Provider,提供者可以是Java平台的JMS实现,也可以是非Java平台的面向消息中间件的适配器
  • 客户,生产或消费基于消息的Java的应用程序或对象
  • 生产者,创建并发送消息的JMS客户
  • 消费者,接受消息的JMS客户
  • 消息,包括可以在JMS客户之间传递的数据的对象
  • 队列,一个容纳那些被发送的等待阅读的消息的区域,与队列名字所暗示的意思不同,消息的接受顺序并不一定要与消息的发送顺序相同,一旦一个消息被阅读,该消息将被从队列中移走
  • 主题,一种支持发送消息给多个订阅者的机制

    JMS应用程序结构支持两种模型

  • 点对点或队列模型
    这种模式只有一个消费者获得消息,生产者不需要在接收者消费该消息期间处于运行状态,接受者也同样不需要在消息发送时处于运行状态,每个成功处理的消息都由接收者签收
  • 发布者/订阅者模型
    支持向一个特定的消息主题发布消息,0或多个订阅者可能对接收来自特定消息主题的消息感兴趣,这种模式发布者和订阅者彼此不知道对方,这种模式多个消费者可以获得消息,在发布者和订阅者之间存在时间依赖性,发布者需要建立一个订阅,以便客户能够订阅。订阅者必须保持持续的活动状态以接收消息,除非订阅者建立了持久的订阅,在那种情况下,订阅者未连接时发布的消息将在订阅者重新连接时重新发布。
评论

大数据学习 - Java并发包

Java并发包介绍

JDK5.0以后的版本都引入了高级并发特性,大多数的特性在java.util.concurrent包中,是专门用于多线程编程的,充分利用了现代多处理器和多核心系统的功能以编写大规模并发应用程序。主要包含原子量,并发集合,同步器,可重入锁,并对线程池的构造提供了支持。

线程池

  1. Single Thread Executor:只有一个线程的线程池,因此所有提交的任务都是顺序执行。Executors.newSingleThreadExecurot()
  2. Cached Thread Pool:线程池具有很多线程需要同时执行,老的可用线程将被新的任务出发重新执行,如果线程超过60秒内没执行,那么将被终止并从池中删除。Execurots.newCachedThreadPool()
  3. Fixed Thread Pool:拥有固定线程数的线程池,如果没有任务执行,那么线程会一直等待。Executors.newFixedThreadPool(4),构造函数中的4是线程池大小,可以根据需要配置。获取cpu数量:

    1
    int cpuNums = Runtime.getRuntime().availableProcessors()
  4. Scheduled Thread Pool:用来调度即将执行的任务的线程池。Executors.newScheduledThreadPool()。

  5. Single Thread Scheduled Pool:只有一个线程,用来调度任务在指定时间执行。Executors.newSingleThreadScheduledExecutor()

runnable和callable

  1. runnable的run方法不会有任何返回结果, 所以主线程无法获得任务线程的返回值
  2. callable的call方法可以返回结果, 但是主线程在获取时会被阻塞, 需要等待任务线程返回才能拿到结果。

并发包消息队列BlockingQueue

BlockingQueue是java.util.concurrent下的主要用来控制线程同步的工具。
主要的方法是: put, take 阻塞存取。add, poll 非阻塞存取。

插入

  • add(anObject):把anObject加到BlockingQueue里, 即如果BlockingQueue可以容纳, 则返回true, 否则抛出
  • offer(anObject):如果可能的话, 将anObject插入, 返回true, 如果不可以容纳则返回false
  • put(anObject):插入anObject, 如果BlockQueue没有空间, 则调用此方法的线程被阻断知道BlockingQueue里面有空间再继续。

    读取

  • poll(time):读取BlockingQueue里首位对象, 若不能立即取出, 则可以等time参数规定的时间, 取不到返回null
  • take():读取BlockingQueue首位对象, 若BlockingQueue为空, 阻断, 进入等待状态直到有对象可以读取

    其他

  • int remaingCapacity():返回队列剩余的容量
  • boolean remove(Object o):从队列移除元素, 如果存在, 即移除一个或多个, 队列有改变了返回true
  • boolean contains(Object o):查看队列是否存在这个元素, 存在返回true
  • int drainTo(Collection<? super E> c):移除队列中所有的可用元素, 并将它们添加到给定的collection中
  • int drainTo(Collection<? super E> c, int maxElements):和上面方法的区别在于制定了移动的数量

BlockingQueue两种实现类

  • ArrayBlockingQueue:一个由数组支持的有界阻塞队列, 规定大小的BlockingQueue, 其构造函数必须带一个in参数来知名其大小, 其所含的对象是以FIFO顺序排序的。
  • LinkedBlockingQueue: 大小不定的BlockingQueue, 若其构造函数带一个规定大小的参数, 生成的BlockingQueue有大小限制, 若不带大小参数, 所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定, 同样也是FIFO。

区别

  • 它们背后所用的数据结构不一样,导致LinkedBlockingQueue的数据吞吐量要大于ArrayBockingQueue,但在线程数量很大时其性能的可预见性低于ArrayBlockingQueue
  • 队列大小有所不同,ArrayBlockingQueue是有界的必须指定大小,而linkedBlockingQueue可以是有界的也可以是无界的(Integer.MAX_VALUE),对于后者,当添加速度大于移除速度时,在无界的情况下,可能会造成内存泄漏
  • 由于ArrayBlockingQueue采用的数组作为数据存储容器,因此在插入或删除元素时不会产生或销毁任何额外的对象实例,而LinkedBlockingQueue则会生成一个额外的Node对象。这可能在长时间内需要高效并发地处理大批量数据时,对于GC可能存在较大影响
  • 两者的实现队列添加或移除的锁不一样,ArrayBlockingQueue实现的队列中的锁是没有分离的,即添加操作和移除操作采用的同一个ReentrantLock,而LinkedBlockingQueue实现的队列中的锁是分离的,其添加采用的putLock,移除采用的则是takeLock,这样能大大提高队列的吞吐量,也意味着在高并发的情况下生产者和消费者可以并行地操作队列的数据,以此来提高整个队列的并发性能。

    总结

  1. 不应用线程池的缺点
  • 新建线程的开销,线程虽然比进程要轻量许多,但对于JVM来说,新建一个线程的代价还是挺大的,绝不同于新建一个对象
  • 资源消耗量,没有一个池来限制线程的数量,会导致线程的数量直接取决于应用的并发量,这样有潜在的线程数巨大的可能,那么消耗的资源类将是巨大的
  • 稳定性,当线程数量超过系统资源所能承受的成都,稳定性就会成问题
  1. 制定线程策略
    在每个需要多线程处理的地方,不管并发量有多大,需要考虑线程的执行策略
  • 任务以什么顺序执行
  • 可以有多少个任务并发执行
  • 可以有多少个任务进入等待执行队列
  • 系统过载的时候,应该放弃哪些任务?如何通知到应用程序?
  • 一个任务的执行前后应该做什么处理
  1. 线程池饱和策略

除了CachedThreadPool其他线程池都有饱和的可能,当饱和以后就需要相应的策略处理请求线程的任务,比如,达到上限时通过ThreadPoolExecutor.setRejectedExecutionHandler方法设置一个拒绝任务的策略JDK提供了AbortPolicy, CallerRunsPolicy, DiscardPolicy, DiscardOldestPolicy

  1. 线程无依赖性

多线程任务设计上尽量使得个任务是独立无依赖的,两个方面
1、线程之间的依赖性。如果线程有依赖可能会造成死锁或饥饿
2、调用者与线程的依赖性,调用者得监视线程的完成情况,影响并发量

评论

大数据学习 - 多线程

什么是进程

不同的应用程序运行的过程中都需要在内存中分配自己独立的运行空间,彼此之间不会相互的影响,我们把每个独立应用程序在内存的独立空间成为当前应用程序运行的一个进程。

什么是线程

位于进程中,负责当前进程中的某个具备独立运行资格的空间,进程是负责整个程序的运行,而线程是程序中具体的某个独立功能的运行。一个进程中至少应该有一个线程。

Java实现多线程的两种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ThreadDemo {

public static class ThreadExtend extends Thread{

@Override
public void run() {
System.out.println("继承Thread方式使用多线程...");
}
}

public static class ThreadImplements implements Runnable{

@Override
public void run() {
System.out.println("实现Runnable方式使用多线程...");
}
}

public static void main(String[] args) {
ThreadExtend threadExtend = new ThreadExtend();
threadExtend.start();

Thread threadImplements = new Thread(new ThreadImplements());
threadImplements.start();

}
}

Java 同步关键词解释

synchronized

简单用法:
synchronzied(一个任意的对象[锁]){
doSomething();
}

synchonzied的缺陷:
如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:

  • 获取锁的线程执行完了该代码块,然后线程释放锁
  • 线程执行发生异常,此时JVM会让线程自动释放锁

lock和synchronized的区别

  • lock不是Java语言内置的,synchronzied时Java语言的关键字,因此是内置特性。lock是一个类,通过这个类可以实现同步访问。
  • lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用,而lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

lock和synchronized的选择

  • lock是一个借口,而synchronized是Java的中的关键字,synchronized是内置的语言实现
  • synchronized在发生异常时,会自动释放线程占有的锁,因此不会因为异常导致死锁现象发生。而lock在发生异常时,入股哦没有主动通过unlock()去释放锁,则很有可能造成死锁现象,因为使用lock时需要在finally块中释放锁。
  • lock可以让等到锁的线程响应中断,而synchronized却不幸,使用synchronized时,等待的线程会一直等待下去,不能够响应中断。
  • 通过lock可以知道有没有成功获取锁,而synchronized却无法办到。
  • lock可以提高多个线程进行读操作的效率(读写锁)
    在性能上来说,如果竞争资源不激烈,两者的性能时差不多的,而当竞争资源非常激烈时,此时lock的性能要远远优于synchronized,所以,具体使用时要根据实际情况选择。
评论

大数据学习 - zookeeper简单命令及java api

bash cmd

  • 1、使用ls命令来查看当前zk中所包含的内容

    1
    ls /
  • 2、创建一个新的znode。这个命令创建了一个新的znode节点 “zk”以及与它关联的字符串。

    1
    create /zk
  • 3、运行get命令来确认znode是否包含我们所创建的字符串

    1
    get /zk
  • 4、通过set命令来对zk所关联的字符串进行设置

    1
    set /zk "test"
  • 5、删除znode

    1
    delete /zk
  • 6、删除节点

    1
    rmr /zk

java api

org.apache.zookeeper.Zookeeper时客户端入口类, 负责建立与server的会话, 提供了几类主要方法:

功能 描述
create 在本地目录树中创建一个节点
delete 删除一个节点
exists 是否存在目标节点
get/set data 从目标姐弟啊读取/写数据
get/set ACL 获取/设置目标节点访问控制列表信息
get children 检索目标节点的子节点
sync 等待要被传送的数据

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
private static final String CONNECT_STRING = "bigdata1:2181,bigdata2:2181,bigdata3:2181";

private static final Integer SESSION_TIMEOUT = 2000;

private ZooKeeper zkClient = null;

/**
* 初始化client
*/
@Before
public void init() throws IOException {
zkClient = new ZooKeeper(CONNECT_STRING, SESSION_TIMEOUT, (event) -> {
// 收到时间通知后的回调函数 是我们自己的事件处理逻辑
// 初始化时会收到一次
System.out.println(event.getType() + "---" + event.getPath());
try {
// 重新执行监听
zkClient.getChildren("/", true);
} catch (Exception e) {
e.printStackTrace();
}
});
}

/**
* 数据的增删改查
*/
// 创建数据
@Test
public void create() throws KeeperException, InterruptedException {
// 路径 数据 权限 类型
String create = zkClient.create("/idea",
"hello world".getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
System.out.println("create: " + create);
}

// 子节点是否存在
@Test
public void exists() throws KeeperException, InterruptedException {
Stat stat = zkClient.exists("/idea", false);
System.out.println(stat == null ? "not exists" : "exists");
}

// 获取子节点数据
@Test
public void getChildren() throws KeeperException, InterruptedException {
// 路径 是否监听数据变化
List<String> childs = zkClient.getChildren("/", true);
childs.forEach(System.out::println);
}

// 获取节点数据
@Test
public void get() throws KeeperException, InterruptedException {
byte[] data = zkClient.getData("/idea", false, null);
System.out.println(new String(data));
}

// 删除节点数据
@Test
public void delete() throws KeeperException, InterruptedException {
// 参数2, 指定要删除的版本, -1表示删除所有版本
zkClient.delete("/idea", -1);
}

// 设置节点数据
@Test
public void set() throws KeeperException, InterruptedException {
zkClient.setData("/idea", "hello world 2".getBytes(), -1);
}
评论

大数据学习 - zookeeper简介

概念简介

zookeeper(zk)是一个分布式协调服务,zk本身就是一个分布式程序,在底层其实只提供了两个功能:

  • 管理(存储、读取)用户程序提交的数据
  • 提供数据节点监听服务

常用的应用场景有

  • 服务器状态的动态通知
  • 配置文件管理
  • 分布式共享锁
  • 名称服务

zk集群有两个角色:leader和follower(observer)
只要集群中半数以上节点存活,集群就能提供服务。

特性

  1. 一个leader、多个follower组成的集群
  2. 全局数据一致, 每个server保存一份相同的数据副本, client无论连接到哪个server, 数据都是一致的(强一致性)
  3. 分布式读写, 更新请求转发, 由leader实施
  4. 更新请求顺序进行, 来自同一个client的更新请求按其发送顺序依次执行
  5. 数据更新原子性, 一次数据更新要么成功, 要么失败
  6. 实时性, 在一定时间范围内, client能读到最新数据

数据结构

  1. 层次化的目录结构, 明明符合常规文件系统规范
  2. 每个节点在zk中叫做znode, 并且有一个唯一的路径标识
  3. 节点znode可以包含数据和子节点(但是EPHEMERAL类型的节点不能有子节点)
  4. 客户端应用可以在节点上设置监视器

节点类型

  1. znode有两种类型
    短暂(ephemeral): 断开连接自动删除
    持久(persistent): 断开连接不删除
  2. znode有4中形式的目录节点
  • PERSISTENT(default)
  • PERSISTENT_SEQUENTIAL(持久序列/test0000000019)
  • EPHEMERAL
  • EPEMERAL_SEQUENTIAL
  1. SEQUENTIAL表示序列节点, 创建znode时设置顺序标识, znode名称后会附加一个值, 顺序时一个单调递增的计数器, 又父节点维护
  2. 在分布式系统中, 顺序号可以被用于为所有的时间进行全局排序, 这样客户端可以通过顺序号推断时间的顺序。

zk原理

zk虽然在配置文件并没有指定leader和follower, 但是, zk工作时, 是有一个节点为leader, 其他则为follower, leader是通过内部的选举机制临时产生的。

zk的选举机制(全新集群使用paxos)

以一个简单的例子来说明整个选举的过程.
假设有五台服务器组成的zookeeper集群,它们的id从1-5,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的.假设这些服务器依序启动,来看看会发生什么.
1) 服务器1启动,此时只有它一台服务器启动了,它发出去的报没有任何响应,所以它的选举状态一直是LOOKING状态
2) 服务器2启动,它与最开始启动的服务器1进行通信,互相交换自己的选举结果,由于两者都没有历史数据,所以id值较大的服务器2胜出,但是由于没有达到超过半数以上的服务器都同意选举它(这个例子中的半数以上是3),所以服务器1,2还是继续保持LOOKING状态.
3) 服务器3启动,根据前面的理论分析,服务器3成为服务器1,2,3中的老大,而与上面不同的是,此时有三台服务器选举了它,所以它成为了这次选举的leader.
4) 服务器4启动,根据前面的分析,理论上服务器4应该是服务器1,2,3,4中最大的,但是由于前面已经有半数以上的服务器选举了服务器3,所以它只能接收当小弟的命了.
5) 服务器5启动,同4一样,当小弟.

非全新集群的选举机制

那么,初始化的时候,是按照上述的说明进行选举的,但是当zookeeper运行了一段时间之后,有机器down掉,重新选举时,选举过程就相对复杂了。
需要加入数据id、leader id和逻辑时钟。
数据id:数据新的id就大,数据每次更新都会更新id。
Leader id:就是我们配置的myid中的值,每个机器一个。
逻辑时钟:这个值从0开始递增,每次选举对应一个值,也就是说: 如果在同一次选举中,那么这个值应该是一致的 ; 逻辑时钟值越大,说明这一次选举leader的进程更新.
选举的标准就变成:

1、逻辑时钟小的选举结果被忽略,重新投票

2、统一逻辑时钟后,数据id大的胜出

3、数据id相同的情况下,leader id大的胜出

根据这个规则选出leader。

安装

环境部署

至少3台有jdk环境的机器(虚拟机也可)

下载

两种方法,官网下载使用ftp工具传输到机器上或直接使用wget

1
wget https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.4.13/zookeeper-3.4.13.tar.gz

解压并重命名

1
2
tar -zxvf zookeeper-3.4.13.tar.gz
mv zookeeper-3.4.13 zookeeper

修改环境变量

  1. su -root,切换到root用户
  2. vim /etx/profile
  3. 添加内容

    1
    2
    export ZOOKEEPER_HOME=/home/hadoop/zookeeper
    export PATH=$PATH:$ZOOKEEPER_HOME/bin
  4. 重新加载文件

1
source /etc/profile
  1. 切换回hadoop用户
  2. 注意,所有的zk机器都需要修改

修改配置文件

  1. 在zk文件夹中操作

    1
    2
    cd zookeeper/conf
    cp zoo_sample.cfg zoo.cfg
  2. vim zoo.cfg 添加内容

1
2
3
4
5
dataDir=/home/hadoop/zookeeper/data
dataLogDir=/home/hadoop/zookeeper/log
server.1=slave1:2888:3888 (主机名, 心跳端口、数据端口)
server.2=slave2:2888:3888
server.3=slave3:2888:3888
  1. 创建文件夹
1
2
3
cd /home/hadoop/zookeeper/
mkdir -m 755 data
mkdir -m 755 log
  1. 在data文件下新建myid文件, 内容为
1
echo 1 >> myid

复制修改好的zk文件夹到其他机器上

1
2
scp -r /home/hadoop/zookeeper hadoop@slave2:/home/hadoop/
scp -r /home/hadoop/zookeeper hadoop@slave3:/home/hadoop/

并且要记得修改myid文件:
slave2上内容修改为2
slave3上内容修改为3

启动

zkServer.sh start

评论

Node - a different Node.js version using 异常

今天在更新博客的时候,突然报了异常,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Error: The module '/usr/local/lib/node_modules/hexo-cli/node_modules/dtrace-provider/build/Release/DTraceProviderBindings.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 57. This version of Node.js requires
NODE_MODULE_VERSION 59. Please try re-compiling or re-installing
the module (for instance, using `npm rebuild` or `npm install`).
at Object.Module._extensions..node (module.js:689:18)
at Module.load (module.js:573:32)
at tryModuleLoad (module.js:513:12)
at Function.Module._load (module.js:505:3)
at Module.require (module.js:604:17)
at require (internal/module.js:11:18)
at Object.<anonymous> (/usr/local/lib/node_modules/hexo-cli/node_modules/dtrace-provider/dtrace-provider.js:18:23)
at Module._compile (module.js:660:30)
at Object.Module._extensions..js (module.js:671:10)
at Module.load (module.js:573:32)
at tryModuleLoad (module.js:513:12)
at Function.Module._load (module.js:505:3)
at Module.require (module.js:604:17)
at require (internal/module.js:11:18)
at Object.<anonymous> (/usr/local/lib/node_modules/hexo-cli/node_modules/bunyan/lib/bunyan.js:79:18)
at Module._compile (module.js:660:30)

看样子是有一个模块因为被不同版本的node给build了,估计是前一阵子更新mac系统版本的时候更新了node版本。
关于node我也不是很懂,只能求助于谷歌- -
最后终于找到了一个暴力的解决方法:
1、cd 到 /usr/local/lib/node_modules/hexo-cli/
2、干掉node_modules文件夹
3、npm install
4、cd 到 hexo_project
5、干掉node_modules文件夹
6、npm install

搞定 收工~

评论

Java类加载机制(二)

类加载

先来看个栗子🌰:

1
2
3
4
5
6
7
@Test
public void test(){
ClassLoader loader = Thread.currentThread().getContextClassLoader();
System.out.println(loader);
System.out.println(loader.getParent());
System.out.println(loader.getParent().getParent());
}

输出为:

1
2
3
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@5f5a92bb
null

可以看出,没有获取到ExtClassLoader的父Loader,原因是Bootstrap Loader(引导类加载器)是用C语言实现的,找不到一个确定的返回父Loader的方式,于是返回为null。

站在Java虚拟机的角度来说,只存在两种不同的类加载器:

  • 启动类加载器:使用C++实现(仅限于Hotspot),是虚拟机的一部分
  • 所有其他类加载器:这些加载器都由Java语言实现,独立于虚拟机之外,并且全部继承自抽象类java.lang.ClassLoader,这些类加载器需要由启动类加载器加载到内存中之后才能去加载其他的类。

站在开发人员的角度来说,大致可分为三类类加载器:

  • 启动类加载器:Bootstrap Loader,负责加载存放在JDK/jre/lib下,或被-Xbootclasspath参数指定的路径中,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被BootstrapClassLaoder加载)。启动类加载器是无法被Java程序直接引用的。
  • 扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载JDK/jre/lib/ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发人员可以直接使用扩展类加载器。
  • 应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径所指定的类,开发人员可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

如果有必要,还可以假如自定义的类加载器。

ExtClassLoader和APPClassLoader并不是继承关系,它们都继承同一个类URLClassLoader。

JVM类加载机制:

  • 全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
  • 父类委托:先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径加载该类
  • 缓存机制:缓存机制会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效

image

双亲委派机制

为什么需要双亲委派机制?

先说明一下,jvm判断两个类相同的前提是这两个类都是同一个加载器进行加载的,如果使用不同的类加载器进行加载同一个类,那在jvm中会认为它们是不同的。所以如果没有双亲委派机制,如果我们在rt.jar中随便找一个类,如java.util.HashMap,那么我们同样也可以写一个一样的类,也叫java.util.HashMap存放在我们自己的路径下,那么这两个类采用的是不同的类加载器,程序中就会出现两个不同的HashMap类。总结来说,1、系统类防止内存中出现多芬同样的字节码 2、保证Java程序安全稳定运行

双亲委派的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,所以所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

评论

Java类加载机制

类的加载

Java类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向外部提供了访问方法区内的数据结构的接口。

类的生命周期

image
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的生命周期包括:加载、验证、准备、解析、初始化、使用和卸载7个阶段。其中验证、准备、解析统称为连接。
加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班的开始,而解析则不一定,它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java的运行时绑定。

加载

加载阶段,虚拟机需要完成以下3件事情:

  • 通过一个类的全限定名来获取定义此类的二进制字节流

加载.class文件的方式

  1. 从本地系统中直接加载
  2. 通过网络下载
  3. 从zip,jar等归档文件中加载.class文件
  4. 从专有数据库中提取
  5. 将Java源文件动态编译为.class文件
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法去这个类的各种数据的访问入口

加载和连接阶段的部分内容是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始(如一部分字节码文件格式验证),但是这些动作仍然属于连接阶段的内容,这两个阶段的开始时间仍然保持着固定的先后顺序。

加载阶段既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器。

加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class对象,这样便可以通过该对象访问方法区中的这些数据。

连接

验证

验证是连接阶段的第一步,这一步的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

主要完成4个检验动作:

  • 文件格式验证:验证自己留是否符合Class文件格式的规范,例如是否以0xCAFABABE开头(传说中的魔数)、主次版本号是否在当前虚拟机的处理范围之内、常量池中的敞亮是否有不被支持的类型。
  • 元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言的规范;例如这个类是否有父类(除了java.lang.Object)
  • 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的
  • 符号引用验证:确保解析动作能正确执行

验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

准备

为类的静态变量分配内存,并将其初始化为默认值,正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。

有几点需要注意:

  • 这时候进行内存分配的仅包括类变量(static),不包括实例变量,实例变量将在对象实例化时随着对象一块分配在Java堆中。
  • 这是所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是代码中被显示赋予的值。

假如一个类变量的定义为:
public static int value = 7
那么在准备阶段过后value的初始值为0,而不是7,这时候还未执行任何Java方法,把value赋值为7的动作将在初始化阶段才会执行。特殊情况:加载final关键词后,即当类字段的字段属性是ConstantValue时,会在准备阶段初始化为指定的值,所以标注final后,value的值在此阶段初始化为7而不是0。

解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述模板,可以是任何字面量。
直接引用就是直接指向模板的指针、相对偏移量或一个简洁定位到目标的句柄。

初始化

为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。对类变量进行初始值设定有两种方式:

  • 声明类变量是指定初始值
  • 使用静态代码块为类变量指定初始值

JVM初始化步骤:

  1. 假如这个类还没有被加载和连接,则程序先加载并连接该类
  2. 假如该类的直接父类还没有被初始化,则先初始化其直接父类
  3. 假如类中有初始化语句,则系统依次执行这些初始化语句

类初始化时机(只有当对类的主动使用的时候才会导致类的初始化):

  • 创建类的实例,new Example()
  • 访问某个类或接口的静态变量,或者对该静态变量进行赋值
  • 调用类的静态方法
  • 反射(Class.forName(“…”))
  • 初始化某个类的子类,则其父类也会被初始化
  • Java虚拟机启动时被标明为启动类的类,直接使用java.exe命令来运行某个主类
评论