大数据笔记--Zookeeper(第三篇)

目录

一、选举机制 

1、概述

2、细节

二、ZAB协议

1、概述

2、原子广播

3、原子广播的过程

4、查看日志的方式

5、崩溃恢复

三、Zookeeper-其他

1、observer-观察者

2、特征

3、zookeeper集群操作

四、PAXOS算法

1、概述

五、AVRO 

1、概述

2、序列化

3、AVRO序列化举例

4、RPC


一、选举机制 

1、概述

当一个zookeeper集群刚启动的时候,会自动的进入选举状态,此时所有的服务器都会推荐自己成为leader,并且还会把自己的选举信息发送给其他的节点。

当节点收到其他节点发过来的选举信息之后,会两两比较,经过多轮比较,最后胜出的节点当leader。

2、细节

选举信息包含:

        a. 当前节点的最大事务id
        b. 选举编号,即myid
        c. 逻辑时钟值-控制选举的轮数。

比较原则:

        a. 先比较最大事务id,谁大谁赢
        b. 如果事务id一样,则比较myid,谁大谁赢。
        c. 如果一个节点胜过了一半及以上的节点,这个节点才能成为leader-过半性

在zookeeper中,不存在单点故障的说法,如果leader宕机了,那么整个集群会选举一个新的leader继续对外提供服务

如果leader节点宕机之后重新启动,那么这个时候看做一个新的节点加入集群,所以此时这个节点的状态一定是follower

如果一个集群已经选举出来一个leader,为了维护集群的稳定性,无论新添加的节点的事务id或者myid是多少,都不会触发重新选举,新添的节点只能是follower

如果集群中出现了多个leader,这种现象叫做脑裂。

zookeeper中脑裂出现的条件:

        a. 集群产生分裂
        b. 分裂之后产生了选举

解决上图方法:

在zookeeper中,如果存活的(可以相互通信的)节点数量不足一半,则这些节点不选举,同时也不对外提供服务-过半性

 zookeeper会对每一次选举出来的leader分配一个全局递增的编号,称之为epochid,当zookeeper发现存在多个leader的时候,那么会自动的将epochid较小的节点的状态切换成follower。利用这种方案可以保证整个集群中只存在唯一的leader。

集群中节点的状态:

        a. looking/voting:选举状态
        b. follower:追随者/跟随者
        c. leader:领导者
        d. observer:观察者

zookeeper并不是主从的框架,其中leader和follower指的是节点的状态而不是角色

二、ZAB协议

1、概述

ZAB(Zookeeper Atomic Broadcast)协议是专门为zookeeper设计的用于进行原子广播崩溃恢复的一套协议。

ZAB是基于2PC算法设计实现的,利用了过半性+PAXOS进行了改进

2、原子广播

作用:保证集群节点之间的数据一致性,即访问任意一个节点,获取到的数据都是相同的。

原子广播就是基于2PC算法设计实现的

2PC-two Phase Commit-二阶段提交。顾名思义,将一个请求拆分成了两个阶段

①、请求阶段

②、提交阶段:如果协调者收到所有的参与者返回的yes, 那么就认为这个请求可以执行,那么就会命令所有的参与者执行这个请求

③、 中止阶段:只要协调者没有收到所有参与者返回的yes,那么就认为这个请求无法执行,也就会要求所有的参与者放弃刚才的请求

 

 协调者没有收到yes,就会被认为是no

在2PC算法中,要么是执行请求-提交阶段要么是执行请求-中止阶段

2PC的核心思想就是“一票否决

2PC算法很容易理解,并且也很容易实现,但是在分布式环境中,2PC是的成功率很低。效率很低。也因此zookeeper的原子广播在2pc的算法基础上加上了过半性。

3、原子广播的过程

请求首先交给我们的leader执行;

当leader接收到请求之后,会先记录到本地日志中(log.xxxx),如果记录成功,leader将请求放到队列中发送给follower

当follower收到队列,将请求从队列取出,然后记录本地日志文件中

记录成功返回给leader一个yes,否则返回no

如果leder收到yes,就会命令所有节点执行请求,如果leader没有收到一半以上的yes,就会认为这个请求不能执行,要求所有节点删除刚才的记录。

4、查看日志的方式

①、将zookeeper安装目录下lib目录中的zookeeper-3.5.7.jar和slf4J.api-1.7.25.jar和zookeeper-jute-3.5.7.jar拷贝到log日志所在目录下

 cd /home/software/zookeeper-3.5.7/lib/

 cp slf4j-api-1.7.25.jar zookeeper-3.5.7.jar zookeeper-jute-3.5.7.jar  /home/software/zookeeper-3.5.7/tmp/version-2/

②、通过命令来查看:

java -cp .:zookeeper-3.5.7.jar:slf4j-api-1.7.25.jar:zookeeper-jute-3.5.7.jar 
org.apache.zookeeper.server.LogFormatter log.600000001

③、snapshot.xxx文件是一个快照文件,用于记录zookeeper的树结构,查看方式:

java -cp .:zookeeper-3.5.7.jar:slf4j-api-1.7.25.jar:zookeeper-jute-3.5.7.jar 
org.apache.zookeeper.server.SnapshotFormatter snapshot.0

④、follower存在日志记录失败的可能,例如文件被占用,磁盘损坏,磁盘已满。都可能导致日志文件记录失败,这些原因不是zookeeper的原因,zookeeper是无法解决的

⑤、如果follower记录失败,那么会给leader发送请求,leader收到请求之后,会将欠缺的事务放到队列中返回给follower要求follower补齐事务。

5、崩溃恢复

当leader宕机之后,整个zookeeper集群并不会就此停止,而是选举一个新的leader出来,然后继续对外提供服务

作用:避免单点故障

在zookeeper集群中,会对每一次选举出来的leader分配一个全局递增的唯一编号,这个编号我们把它叫做epochid,当leader被选举出来之后,这个leader就会将epochid分发给每一个follower,follower收到这个epochid就会将它存储在acceptedEpoch文件中

在Zookeeper集群中,事务id(zxid)实际上是由64位二进制(16位16进制)数字组成,分成了两个部分,高(前)32位实际上就是epochid,低32位才是实际的事务id,

例如:0x600000002表示的就是第6任leader被选举出来之后产生的第2个写操作。

在zookeeper的bin目录下连接客户端

./zkCli.sh
我们创建一个节点

create /log

get -s /log

        当一个节点宕机重启之后,这个节点会先找到当前节点的最大事务id,找到之后会向leader所在的节点发送请求,比较事务id是不是一致的。

        leader收到请求,会比较两个事务id是否一致,如果一致,说明节点在宕机期间没有发生写操作,如果不一致,那么leader就会将欠缺的事务放到队列中发送给follower要求补齐到和整个集群一致的水平。follower在补齐事务的过程中不对外提供服务

三、Zookeeper-其他

1、observer-观察者

①、observer在zookeeper中既不参与选举也不参与投票,但是会监听选举和投票的结果,但是根据结果进行指定的操作。

②、observer可以大概理解为没有选举权和投票权的follower-只有干活的义务没有选举的权利

③、在实际开发过程中,当集群规模庞大(节点个数比较多)或者网络环境一般的时候,会将一个集群中90%-97%的节点设置为observer,例如100个节点组成的服务器,我们可以将91个节点设置为observer

④、由于observer不参与投票也不参与选举,所以observer存活与否并不会影响整个集群是否对外提供服务。

假设现在有个21个节点的集群。其中1leader,6follower,14observer。

如果其中4个follower宕机了。即便observer存活也不会对外提供服务。

但是如果leader和follower全部存活即便14个observer全部宕机,也能对外提供服务。

在zookeeper集群中,过半性是以能够参加选举或者投票的节点的个数来决定的。

⑤、observer的配置

a. 进入conf

b. 找到zoo.cfg

c. 添加:
        peerType=observer
        server.1=192.168.186.128 :2888:3888
        server.2=192.168.186.129 :2888:3888:observer
        server.3=192.168.186.130 :2888:3888

2、特征

i、 过半性:过半选举,过半存活,过半操作。
ii、数据一致性,从任意节点获取数据,拿到的数据都是一样的
iii、原子性:一个操作要么所有节点都执行,要么都不执行
iv、顺序性:所有节点获取到的请求顺序是一致的-队列
v、可靠性:主要指的就是崩溃恢复
iv、实时性:在网络条件比较好的时候,可以对zookeeper来实现实时监控

配置信息:

3、zookeeper集群操作

实际上利用nc来实现,nc全程就是netcat,底层是通过发送TCP的请求来发送命令

安装nc:

a. 进入software
b. 下载nc包  

wget http://vault.centos.org/6.6/os/x86_64/Packages/nc-1.84-22.el6.x86_64.rpm
c. 安装nc:rpm -ivh nc-1.84-22.el6.x86_64.rpm

安装依赖错误可以强制安装,后面加上--force --nodeps

集群指令:

a. 查看节点是否存活:echo ruok|nc 192.168.186.129 2181

        i. 如果提示ruok is not executed because it is not in the whitelist.
        ii. 需要在配置文件conf下的zoo.cfg当中最后一行添加 4lw.commands.whitelist=*

b. 查看节点状态:echo stat|nc 192.168.186.129 2181
c. 查看节点的配置信息 echo conf|nc 192.168.186.129 2181 

四、PAXOS算法

分布式一致性算法

1、概述

        Paxos 是一个一致性算法,这个算法解决的问题是一个分布式系统如何就某个值(决议)达成一致。

        一个典型的场景是, 在一个分布式数据库系统中,如果各节点的初始状态一致,每个节点执行相同的操作,那么 最后能得到一个一致的状态。为保证数据的一致性,需要在每一条指令上执行一个“一致性算法”以保证每个节点看到的数据一致 

我们可以结合买票来理解,12306有很多服务器,我们要保证卖出去的票不能重复,那就要保证数据一致,最终就是为了数据的一致性。

五、AVRO 

1、概述

AVRO是apache提供的用于进行序列化和RPC的一套框架

AVRO原来是hadoop的子工程,后来被独立成为一个顶级工程

2、序列化

本上是序列化就是对数据的转化:按照指定规则将对象转化成指定的格式的数据

目的/意义:存储和传输

序列化的衡量标准:

a. 对内存和CPU的耗费以及转化时间

b. 序列化之后产生数据量的大小

c. 序列化机制能否跨平台。:在现在的开发过程中,一个项目往往不是单一语言完成的,而是多语言复合的结果,所以我们就需要考虑数据在不同语言之间的传输
        i. 数据想要在不同语言之间进行传输,那么就需要做到与语言无关:数字,布尔值,字符串
        ii. 好的序列化机制就需要做到跨语言。即这个序列化机制需要做到将对象转化成与语言无关的数据格式

d. AVRO将对象转化成字符串来进行传输,本质上就是转化成了json

3、AVRO序列化举例

在实际开发中,我们基本不会做这些工作,这都是底层封装好的。

首先在idea创建一个maven工程

然后再pox.xml添加如下配置

<dependencies>
        <dependency>
            <groupId>org.apache.avro</groupId>
            <artifactId>avro</artifactId>
            <version>1.8.2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13-rc-1</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.avro</groupId>
                <artifactId>avro-maven-plugin</artifactId>
                <version>1.8.2</version>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>schema</goal>
                        </goals>
                        <configuration>
                            <sourceDirectory>${project.basedir}/src/main/avro/</sourceDirectory>
                            <outputDirectory>${project.basedir}/src/main/java/</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

 在main文件下创建一个avro文件夹,在里面创建一个Student.avsc文件

里面写入内容如下:

//--需要这个文件定义avro格式
{
    "namespace":"org.example.pojo",//--相当于java中的package
    "type":"record",//--java中的class关键字
    "name":"Student",//--相当于java中定义了一个类叫Student
    "fields":
    [
        {"name":"name","type":"string"},//--相当于String name
        {"name":"age","type":"int"}, //--相当于 int age
        {"name":"gender","type":"string"},
        {"name":"height","type":"double"},
        {"name":"weight","type":"double"}
    ]
}

然后再maven进行编译

这时就会出现Student类,我们再创建一个test文件夹用于测试

代码如下:

package org.example.test;

import org.apache.avro.file.DataFileReader;
import org.apache.avro.file.DataFileWriter;
import org.apache.avro.io.DatumReader;
import org.apache.avro.io.DatumWriter;
import org.apache.avro.specific.SpecificDatumReader;
import org.apache.avro.specific.SpecificDatumWriter;
import org.example.pojo.Student;
import org.junit.Test;

import java.io.File;
import java.io.IOException;

public class AvroDemo01 {
    @Test
  public void create(){
      //--方式1,先创建对象再赋值
      Student s1=new Student();
      s1.setName("zhangsan");
      s1.setAge(18);
      s1.setGender("female");
      s1.setHeight(167.5);
      s1.setWeight(57.8);
      System.out.println(s1);
      //--方式二:在创建对象的时候给值
        Student s2=new Student("lisi",22,"male",178.5,89.5);
        System.out.println(s2);
        //--方式三:适合于枚举创建
        Student s3=new Student();
        s3.put(0,"wangwu");
        s3.put(1,34);
        s3.put(2,"male");
        s3.put(3,178.6);
        s3.put(4,76.5);
        System.out.println(s3);
        //方式4:适用于反射
        Student s4=new Student();
        s4.put("name","zhaoliu");
        s4.put("age",16);
        s4.put("gender","male");
        s4.put("height",189.4);
        s4.put("weight",55.5);
        System.out.println(s4);
        //--方式5:建造者模式
        Student s5=Student.newBuilder().setName("bob").setAge(45).
                setGender("male").setHeight(177.6).setWeight(67.4).build();
        System.out.println(s5);
        //--方式6:建造者模式
        Student s6=Student.newBuilder(s5).setName("jerry").build();
        System.out.println(s6);
  }
    //--序列化
    @Test
    public void serial() throws IOException {
      //--创建对象
        Student s1=new Student("bob",15,"male",178.6,98.7);
        Student s2=new Student("jerry",25,"female",168.6,95.7);
        Student s3=new Student("liming",56,"male",178.8,88.7);
        //--创建一个序列化流
        DatumWriter<Student> dw=new SpecificDatumWriter<>(Student.class);
        //--创建文件流
        DataFileWriter<Student> dfw=new DataFileWriter<>(dw);
        //--通过对象获取约束
        dfw.create(Student.SCHEMA$,new File("D:\\a.data"));
        //--序列化对象
        dfw.append(s1);
        dfw.append(s2);
        dfw.append(s3);
        //--关流
        dfw.close();
  }
  //--反序列化
    @Test
    public  void deSerial() throws IOException {
      //--创建反序列化流
        DatumReader<Student> dr=new SpecificDatumReader<>(Student.class);
        //--创建文件流
        DataFileReader<Student> dfr=new DataFileReader<Student>(new File("D:\\a.data"),dr);
        //--为了方便操作,将反序列化流设计成了迭代器模式‘
        while (dfr.hasNext()){
            Student s = dfr.next();
            System.out.println(s);
        }
        //--关流
        dfr.close();

    }
}

4、RPC

RPC(Remote Procedure Call-远程过程调用)是指允许程序员在一个节点调用另一个节点上的方法而不用显示的实现这个方法

RPC最早是Nelson在1981年提出来的,是分布式底层重要组成之一。

特点:简洁,高效,易懂

RPC中通过存根(Stub)来统一结构

RPC协议是传输层协议,通常用于服务器于服务器之间的通信。速度要快于htpp协议

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是小先生

知识是无价的,白嫖也可以的。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值