Zookeeper使用详解(进阶篇),满满的干货~

2024-01-12 19:48

本文主要是介绍Zookeeper使用详解(进阶篇),满满的干货~,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

创作不易,如果觉得这篇文章对你有帮助,欢迎各位老铁点个赞呗,您的支持是我创作的最大动力!

本系列主要总结下Zookeeper的基础使用,笔者准备写四篇文章:

博文内容资源链接
Linux下搭建Zookeeper运行环境https://blog.csdn.net/smilehappiness/article/details/105933433
Zookeeper入门,一篇就够啦https://blog.csdn.net/smilehappiness/article/details/105933292
Zookeeper客户端ZkClient、Curator的使用,史上最详细的教程来啦~https://blog.csdn.net/smilehappiness/article/details/105938058
Zookeeper使用详解(进阶篇)https://blog.csdn.net/smilehappiness/article/details/105938119

文章目录

  • 1 前言
  • 2 Zookeeper集群部署
    • 2.1 为什么需要集群部署
    • 2.2 Zookeeper集群特点
    • 2.3 Zookeeper集群角色分工
      • 2.3.1 Zookeeper中的事务(非事务)请求
      • 2.3.2 Zookeeper集群中的角色
        • 2.3.2.1 Leader角色
        • 2.3.2.2 Follower角色
        • 2.3.2.3 Observer角色
      • 2.3.3 Zookeeper集群配置
      • 2.3.4 Zookeeper集群配置Observer模式
      • 2.3.5 Zookeeper中Observer角色作用
  • 3 Zookeeper之ACL
    • 3.1 什么是ACL
    • 3.2 Zookeeper节点权限模式
    • 3.3 Zookeeper节点操作权限
    • 3.4 设置Zookeeper节点权限
      • 3.4.1 命令行操作方式
        • 3.4.1.1 命令行操作第一种方式(推荐)
        • 3.4.1.2 命令行操作第二种方式
      • 3.4.2 使用代码操作设置节点权限
        • 3.4.2.1 设置节点权限
        • 3.4.2.2 使用有权限的用户访问
    • 3.5 设置Zookeeper节点超级用户权限
      • 3.5.1 背景
      • 3.5.2 对超级用户的密码加密
      • 3.5.3 超级用户加入到服务端启动参数
      • 3.5.4 在命令行客户端使用超级用户登陆
  • 4 Zookeeper的Leader选举
    • 4.1 服务器初始化启动的时候进行Leader选举
      • 4.1.1 选举过程分析
      • 4.1.2 集群服务状态说明
    • 4.2 服务器运行期间的Leader选举
  • 5 分布式系统生成全局唯一ID
    • 5.1 背景
    • 5.2 解决方案
      • 5.2.1 UUID方案
      • 5.2.2 数据库自增ID方案
      • 5.2.3 Redis方案(推荐)
      • 5.2.4 基于Twiitter的snowflake算法
      • 5.2.5 基于MongoDB的ObjectID
      • 5.2.6 基于Zookeeper的方案(推荐)
        • 5.2.6.1 通过持久化的顺序节点实现全局唯一Id
        • 5.2.6.2 基于Zookeeper节点版本号实现全局唯一Id
    • 5.3 小结
  • 6 总结

1 前言

上一篇,主要介绍了几种zk客户端的使用,可以方便操作Zookeeper的zNode节点,本文是Zookeeper系列的最后一篇文章了,将主要介绍一些Zookeeper的高级使用,以及一些Zookeeper使用场景的总结,希望对老铁们有所帮助。

2 Zookeeper集群部署

2.1 为什么需要集群部署

Zookeeper作为一个服务,由于网络、机器等原因,服务本身也可能发生故障,所以我们需要将Zookeeper进行集群,避免单点故障问题,以保证zookeeper本身的高可用性;

2.2 Zookeeper集群特点

zookeeper有两种工作的模式,一种是单机方式,另一种是集群方式

Zookeeper集群特点:
集群中,只要有超过半数的zk集群服务是正常工作的,那么整个集群对外就是可用的。

也就是说如果有2个zookeeper,那么只要有1个故障了,zookeeper就不能用了,因为可用数1没有过半,所以2个zookeeper不是高可用的,因为不能容忍任何1台发生故障。

同理,如果部署了3台zookeeper服务,一个服务故障了,还剩下2个正常的服务,这时候正常的服务2个,超过半数了(1.5),所以3个zookeeper服务才是高可用的,因为能容忍1台服务发生故障。

如果是4台、5台、6台呢?那么分别能容忍1,2,2 台发生故障。

经过一系列推算,得出结论:
Zookeeper服务集群,需要奇数(3,5,7,9…)台服务器实现,太少了不能集群,太多了造成资源浪费。

2.3 Zookeeper集群角色分工

Zookeeper集群有以下三个角色:
一个leader(领导者),一个follower(跟随者),一个observer(观察者),zk服务通过不同的角色,来执行不同的任务。

2.3.1 Zookeeper中的事务(非事务)请求

介绍三个角色之前,先补充两个概念:

  • 事务请求
    在zk中,那些会改变服务器状态的请求称为事务请求(比如说:创建节点更新数据删除节点创建zk连接会话等等
  • 非事务请求
    从zk读取数据,但是不对状态进行任何修改的请求称为非事务请求

2.3.2 Zookeeper集群中的角色

2.3.2.1 Leader角色

Zookeeper服务器中Leader是zk集群工作的核心,其主要工作有两个:

  • 事务请求的唯一调度者和处理者,保证集群事务处理的顺序性
  • 集群内部各个zk服务之间的调度者
2.3.2.2 Follower角色

Follower是Zookeeper集群的跟随者,其主要工作有三个:

  • 处理客户端非事务性请求,转发事务请求给Leader服务器(事务请求都由Leader处理)
  • 参与事务请求的投票,这里事务请求的投票指的是Leader事务操作后,会通知所有的Follower服务,只有过半的服务接收成功后,该次Leader的事务操作才有效,也即是保证了数据的一致性
  • 参与Leader选举投票
2.3.2.3 Observer角色

Observer充当观察者角色,是一种特殊的跟随者,主要工作:

  • 观察zk集群的最新状态变化,并将这些状态同步过来
  • 对于非事务请求可以进行独立的处理,对于事务请求,则会转发给Leader服务器进行处理
  • Observer不会参与任何形式的投票,包括事务请求的投票和Leader选举的投票

2.3.3 Zookeeper集群配置

一般情况下,业务量不是很大的情况下,配置一台Leader两台Follow两台Observer就足够了。

部署3台Zookeeper:
没有安装Zookeeper服务的童鞋们,可以参考本系列文章中的第一节Linux下搭建Zookeeper运行环境进行基础环境的配置。

  • 每台zookeeper服务中,conf目录下的zoo_sample.cfg配置文件复制一份,改名为zoo.cfg并配置:

    # The number of milliseconds of each tick
    tickTime=2000
    # The number of ticks that the initial 
    # synchronization phase can take
    initLimit=10
    # The number of ticks that can pass between 
    # sending a request and getting an acknowledgement
    syncLimit=5
    # the directory where the snapshot is stored.
    # do not use /tmp for storage, /tmp here is just 
    # example sakes.
    dataDir=/usr/local/apache-zookeeper-3.6.1-01/data
    # the port at which the clients will connect
    clientPort=2191
    # the maximum number of client connections.
    # increase this if you need to handle more clients
    #maxClientCnxns=60
    #
    # Be sure to read the maintenance section of the 
    # administrator guide before turning on autopurge.
    #
    # http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
    #
    # The number of snapshots to retain in dataDir
    #autopurge.snapRetainCount=3
    # Purge task interval in hours
    # Set to "0" to disable auto purge feature
    #autopurge.purgeInterval=1## Metrics Providers
    #
    # https://prometheus.io Metrics Exporter
    #metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
    #metricsProvider.httpPort=7000
    #metricsProvider.exportJvmInfo=true#zookeeper内嵌的server服务器的端口,默认是8080
    admin.serverPort=3181server.1=localhost:2666:3666
    server.2=localhost:2777:3777
    server.3=localhost:2888:3888
    

    以上配置,比单机版Zookeeper服务,做了以下几个修改:

    dataDir=/usr/local/apache-zookeeper-3.6.1-01/data
    clientPort=2191#zookeeper内嵌的server服务器的端口,默认是8080
    #admin.serverPort=3181 (zookeeper从3.5.x版本开始会占用8080端口,通过此配置修改掉默认的8080,避免和tomcat端口冲突)
    admin.serverPort=3181# 如果是同一台服务器部署三个Zookeeper服务,需要设置不同的端口(2666表示与集群中的Leader服务器交换信息的端口(因为只有一个Leader,所以2666/2777/2888三个端口中,只会有一个端口被占用),3666表示Leader服务宕机时,专门用来进行选举Leader的端口)
    server.1=localhost:2666:3666
    server.2=localhost:2777:3777
    server.3=localhost:2888:3888
    

    当然了,如果你是在三台Linux机器部署的Zookeeper服务,端口可以重复:

    server.1=LinuxOneIP:2666:3666
    server.2=LinuxTwoIP:2666:3666
    server.3=LinuxThreeIP:2666:3666
    

    以上参数说明:

    server.A = B:C:D
    # 可以使用下面的方式表达
    server.serverid = serverhost:leader_listent_port:quorum_port
    

    A是一个数字,用来表示这个是第几台服务器或者第几台zk服务
    B表示这个服务器的ip地址
    C表示该端口用于集群成员的信息交换,表示的是该服务器与集群中的Leader服务器交换信息的端口
    D是在Leader服务宕机时,专门用来进行选举Leader的端口

  • 每台zk服务中,分别创建一个dataDir目录,比如我创建的目录如下(自己根据情况设置即可):

    /usr/local/apache-zookeeper-3.6.1-01/data
    /usr/local/apache-zookeeper-3.6.1-02/data
    /usr/local/apache-zookeeper-3.6.1-03/data
    
  • 每个zk服务的data目录中,都创建一个名为myid的文件,3个文件的内容写入1、2、3,表示分别对应前边的server.1、server.2和server.3

    如果你这个dataDir目录下原来运行有产生数据,最好删除一下,否则可能出现未知问题

最后,附上笔者集群测试时的三个zk服务的配置:

	dataDir=/usr/local/apache-zookeeper-3.6.1-01/dataclientPort=2191#zookeeper内嵌的server服务器的端口,默认是8080#admin.serverPort=3181 (zookeeper从3.5.x版本开始会占用8080端口,通过此配置修改掉默认的8080,避免和tomcat端口冲突)admin.serverPort=3181# 如果是同一台服务器部署三个Zookeeper服务,需要设置不同的端口(2666表示与集群中的Leader服务器交换信息的端口,3666表示Leader服务宕机时,专门用来进行选举Leader的端口)server.1=localhost:2666:3666server.2=localhost:2777:3777server.3=localhost:2888:3888
	dataDir=/usr/local/apache-zookeeper-3.6.1-02/dataclientPort=2192admin.serverPort=3182server.1=localhost:2666:3666server.2=localhost:2777:3777server.3=localhost:2888:3888
	dataDir=/usr/local/apache-zookeeper-3.6.1-03/dataclientPort=2193admin.serverPort=3183server.1=localhost:2666:3666server.2=localhost:2777:3777server.3=localhost:2888:3888

至此一个zookeeper的集群就搭建完成。

验证集群:
zk服务启动后,使用以下命令查看服务的状态:
./zkServer.sh status
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
出现以上截图所示,有一个Leader,两个Follower,说明集群配置成功。

2.3.4 Zookeeper集群配置Observer模式

新部署两台Zookeeper服务,并设置为观察者Observer角色,在任何想变成observer模式的配置文件中加入如下配置:

peerType=observer

另外,在所有其他的zk server的conf配置文件中,配置成observer模式的server的那行配置追加:observer,例如:

peerType=observerserver.1=localhost:2666:3666
server.2=localhost:2777:3777
server.3=localhost:2888:3888
server.4=localhost:2999:3999:observer

现在最终版的zk配置,五台Zookeeper集群服务配置如下:

	dataDir=/usr/local/apache-zookeeper-3.6.1-01/dataclientPort=2191#zookeeper内嵌的server服务器的端口,默认是8080#admin.serverPort=3181 (zookeeper从3.5.x版本开始会占用8080端口,通过此配置修改掉默认的8080,避免和tomcat端口冲突)admin.serverPort=3181# 如果是同一台服务器部署三个Zookeeper服务,需要设置不同的端口(2666表示与集群中的Leader服务器交换信息的端口,3666表示Leader服务宕机时,专门用来进行选举Leader的端口)server.1=localhost:2666:3666server.2=localhost:2777:3777server.3=localhost:2888:3888server.4=localhost:2999:3999:observerserver.5=localhost:2901:3901:observer
	dataDir=/usr/local/apache-zookeeper-3.6.1-02/dataclientPort=2192admin.serverPort=3182server.1=localhost:2666:3666server.2=localhost:2777:3777server.3=localhost:2888:3888server.4=localhost:2999:3999:observerserver.5=localhost:2901:3901:observer
	dataDir=/usr/local/apache-zookeeper-3.6.1-03/dataclientPort=2193admin.serverPort=3183server.1=localhost:2666:3666server.2=localhost:2777:3777server.3=localhost:2888:3888server.4=localhost:2999:3999:observerserver.5=localhost:2901:3901:observer
	dataDir=/usr/local/apache-zookeeper-3.6.1-04/dataclientPort=2194admin.serverPort=3184peerType=observerserver.1=localhost:2666:3666server.2=localhost:2777:3777server.3=localhost:2888:3888server.4=localhost:2999:3999:observerserver.5=localhost:2901:3901:observer
	dataDir=/usr/local/apache-zookeeper-3.6.1-05/dataclientPort=2195admin.serverPort=3185peerType=observerserver.1=localhost:2666:3666server.2=localhost:2777:3777server.3=localhost:2888:3888server.4=localhost:2999:3999:observerserver.5=localhost:2901:3901:observer

2.3.5 Zookeeper中Observer角色作用

通过以上内容的介绍,可以知道Zookeeper的zNode节点的变更,是要过半数投票通过才可以变更成功(保证数据的一致性),随着机器的添加,因为网络消耗等原因必定导致投票成本增加,从而导致写性能的下降。

Observer是一种新型的Zookeeper节点,能够帮助解决上述问题,提供ZooKeeper的可扩展性。

Observer不參与投票,仅仅是简单的接收投票结果。因此我们添加再多的Observer,也不会影响集群的写性能。

除了这个区别,其它的和Follower基本上一样。

比如:zkClient都能够连接到他们(Observer),而且都能够发送读写请求给他们,收到写请求都会上报到Leader。

Observer优势:
由于它不參与投票,所以他们不属于ZooKeeper集群的关键部位,即使他们Failed宕机了,或者从集群中断开,也不会影响zk集群的高可用性。

3 Zookeeper之ACL

3.1 什么是ACL

访问控制列表(ACL)是一种基于包过滤的访问控制技术,它可以根据设定的条件对接口上的数据包进行过滤,允许其通过或丢弃。访问控制列表被广泛地应用于路由器和三层交换机,借助于访问控制列表,可以有效地控制用户对网络的访问,从而最大程度地保障网络安全。 百度百科

ACL (Access Control List),Zookeeper作为一个分布式协调框架,其内部存储的都是一些关于分布式系统运行时状态的元数据,默认情况下,所有应用都可以读写任何节点,在现如今复杂的网络应用中,这很明显不太安全,任何应用都会有一些用户权限,来保证系统的安全访问,所以,Zookeeper也通过ACL访问控制机制,来解决访问权限问题。

3.2 Zookeeper节点权限模式

Zookeeper节点权限模式,即是Scheme

开发中,最多使用的有以下四种节点权限模式:

  • ip: ip模式通过ip地址粒度进行权限控制模式,例如配置了:192.168.1.101,即表示权限控制都是针对这个ip地址的(实际开发中用的比较少)

  • digest:digest是最常用的权限控制模式,也是开发中用的最多的一种Scheme方式。
    该方式采用username:password形式的权限标识进行权限配置,ZK会对形成的权限标识先后进行两次加密处理,分别是SHA-1加密算法和Base64编码

  • world: world是一种最开放的权限控制模式,也是默认的权限模式,这种模式可以看做特殊的digest,它仅仅是一个标识而已,有个唯一的id。 anyone表示所有人。
    在这里插入图片描述

  • auth:不使用任何id,代表任何已认证的用户

3.3 Zookeeper节点操作权限

Zookeeper节点操作权限:
权限,就是指那些通过权限校验后,才可以被允许执行的操作,在ZK中,对数据的操作权限分为以下五大类:createdeletereadwriteadmin
也就是 增、删、改、查、管理权限,这5种权限简写就是cdrwa(每个单词的首字符缩写)

各权限含义说明:

  • create: 创建子节点的权限
  • delete: 删除节点的权限
  • read: 读取节点数据的权限
  • write: 修改节点数据的权限
  • admin: 设置子节点权限的权限

3.4 设置Zookeeper节点权限

3.4.1 命令行操作方式

对命令行操作不熟悉的童鞋们,可以参考我的博文https://blog.csdn.net/smilehappiness/article/details/105933292 5.2节 命令行客户端 脑补一下。

首先需要使用命令行连接Zookeeper:
./zkCli.sh -server ip:2181

如果对节点设置了权限,访问时会没有权限,如:

Authentication is not valid : /root

所以需要以下方式,才能进行节点的操作。

3.4.1.1 命令行操作第一种方式(推荐)

这种方式,是使用明文的方式创建节点用户权限。

在命令行,使用以下命令:

  • 创建hello节点:
    create /hello

  • 增加一个认证用户
    语法:addauth scheme auth
    如:addauth digest zhaoliu:123456789

  • 设置权限
    语法:setAcl [-s] [-v version] [-R] path acl(setAcl /path auth:用户名:密码明文:权限)
    如: setAcl /hello auth:zhaoliu:123456789:cdrwa

  • 查看Acl设置
    语法: getAcl [-s] path
    如: getAcl /root

3.4.1.2 命令行操作第二种方式

这种方式,是使用密文的方式创建节点用户权限。

生成密文:

DigestAuthenticationProvider.generateDigest("wangwu:123456")//wangwu:HVn0+DK8rXzHTPwF2BhajA/Cn1M=

在命令行,使用以下命令:

  • 创建test节点:
    create /test

  • 设置权限
    setAcl /test digest:用户名:密码密文:权限
    如:setAcl /test digest:wangwu:HVn0+DK8rXzHTPwF2BhajA/Cn1M=:cdrwa
    这里的加密规则是先SHA1加密,然后base64编码

  • 使用wangwu用户,明文密码的方式登录
    addauth digest wangwu:123456

  • 查看Acl设置
    语法: getAcl [-s] path
    如: getAcl /test

3.4.2 使用代码操作设置节点权限

使用代码的方式,可以在创建节点的时候设置节点权限,也可以修改节点权限的归属,下面,举一些示例,帮助老铁们理解。

3.4.2.1 设置节点权限

开始连接Zookeeper客户端对象时,不设置用户权限,然后通过以下代码设置节点用户访问权限后,再次连接时,就不能访问了(NoAuthException: KeeperErrorCode = NoAuth for /root),此时需要设置权限,才可以连接客户端。

  • 在创建节点的时候设置节点权限:
    【代码示例】

    Id zhangsan = new Id("digest", DigestAuthenticationProvider.generateDigest("zhangsan:123456"));
    Id lisi = new Id("digest", DigestAuthenticationProvider.generateDigest("lisi:654321"));List<ACL> aclList = new ArrayList<>();
    //赋值权限(可选值:READ、WRITE、CREATE、DELETE、ADMIN、ALL,即:cdrwa)
    aclList.add(new ACL(ZooDefs.Perms.ADMIN, zhangsan));
    aclList.add(new ACL(ZooDefs.Perms.READ, lisi));//创建节点时,就指定节点所属用户和权限
    String node = client.create().creatingParentsIfNeeded().withACL(aclList).forPath(nodePath, data.getBytes());
    System.out.println(node);
    
  • 修改节点权限的归属:
    【代码示例】

    Id wangwu= new Id("digest", DigestAuthenticationProvider.generateDigest("wangwu:123456"));
    Id zhaoliu= new Id("digest", DigestAuthenticationProvider.generateDigest("zhaoliu:654321"));List<ACL> aclList = new ArrayList<>();
    //赋值权限(可选值:READ、WRITE、CREATE、DELETE、ADMIN、ALL,即:cdrwa)
    aclList.add(new ACL(ZooDefs.Perms.ALL, wangwu));
    aclList.add(new ACL(ZooDefs.Perms.WRITE, zhaoliu));//把ROOT_NODE节点设置两个用户的权限,修改该节点所属用户以及权限
    client.setACL().withACL(aclList).forPath(ROOT_NODE);            
    
3.4.2.2 使用有权限的用户访问

上面步骤设置完权限之后,不设置用户,就不能访问到刚才那个节点了

连接zk客户端对象时,需要设置节点用户权限:

	/*** <p>* 创建Curator连接对象* <p/>** @param* @return* @Date 2020/6/21 12:29*/public static void connectCuratorClient() {//创建zookeeper连接client = CuratorFrameworkFactory.builder().connectString(ZK_ADDRESS).sessionTimeoutMs(10000).connectionTimeoutMs(15000).retryPolicy(retry)//用户认证.authorization("digest", "zhangsan:123456".getBytes()).build();//启动客户端(Start the client. Most mutator methods will not work until the client is started)client.start();System.out.println("zookeeper初始化连接成功:" + client);}
  • 完整代码示例如下:
package cn.smilehappiness.acl;import org.apache.commons.lang3.StringUtils;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.data.Stat;
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;/*** <p>* Curator客户端ACl权限访问控制* <p/>** @author smilehappiness* @Date 2020/6/21 20:58*/
public class ZookeeperCuratorAcl {/*** 客户端连接地址*/private static final String ZK_ADDRESS = "ip:2181";/*** 客户端根节点*/private static final String ROOT_NODE = "/root";/*** 客户端子节点*/private static final String ROOT_NODE_CHILDREN = "/hello/children";/*** 创建zookeeper连接实例*/private static CuratorFramework client = null;/*** 重试策略* n:最多重试次数* sleepMsBetweenRetries:重试时间间隔,单位毫秒*/private static final RetryPolicy retry = new RetryNTimes(3, 2000);static {// 创建Curator连接对象connectCuratorClient();}/*** <p>* 创建Curator连接对象* 针对设置过权限的节点,需要使用用户登录,否则会没有权限(org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /hello/children)* <p/>** @param* @return* @Date 2020/6/21 12:29*/public static void connectCuratorClient() {//创建zookeeper连接client = CuratorFrameworkFactory.builder().connectString(ZK_ADDRESS).sessionTimeoutMs(50000).connectionTimeoutMs(100000).retryPolicy(retry)//用户认证.authorization("digest", "zhangsan:123456789".getBytes()).build();//启动客户端(Start the client. Most mutator methods will not work until the client is started)client.start();System.out.println("zookeeper初始化连接成功:" + client);}public static void main(String[] args) throws Exception {//打印密文//printPwd();//创建节点createNode(ROOT_NODE_CHILDREN, "root data");//使用权限最大的zhangsan用户,把/hello/children节点,给zhaoliu用户设置读的权限,修改该节点所属用户以及权限,这样zhaoliu就可以读取节点了/*Id zhaoliu = new Id("digest", DigestAuthenticationProvider.generateDigest("zhaoliu:123456789"));List<ACL> aclList = new ArrayList<>();//赋值权限(可选值:READ、WRITE、CREATE、DELETE、ADMIN、ALL,即:cdrwa)aclList.add(new ACL(ZooDefs.Perms.READ, zhaoliu));aclList.add(new ACL(ZooDefs.Perms.WRITE, zhaoliu));client.setACL().withACL(aclList).forPath(ROOT_NODE_CHILDREN);*///获取节点数据(zhaoliu只有写的权限,只有zhangsan用户,有所有的权限,可以读写改删)getNode(ROOT_NODE_CHILDREN);//设置(修改)节点数据(lisi用户没有修改权限:NoAuthException: KeeperErrorCode = NoAuth for /hello/children)updateNode(ROOT_NODE_CHILDREN, "update curator data");//删除指定节点(这个在原生zk里面,是不能直接删除有子节点的数据的)deleteNode(ROOT_NODE_CHILDREN);}/*** <p>* 打印密文* <p/>** @param* @return void* @Date 2020/6/21 22:13*/private static void printPwd() throws NoSuchAlgorithmException {System.out.println(DigestAuthenticationProvider.generateDigest("wangwu:123456"));//wangwu:HVn0+DK8rXzHTPwF2BhajA/Cn1M=}/*** <p>* 创建节点,并支持赋值数据内容* <p/>** @param nodePath* @param data* @return void* @Date 2020/6/21 12:39*/private static void createNode(String nodePath, String data) throws Exception {if (StringUtils.isBlank(nodePath)) {System.out.println("节点【" + nodePath + "】不能为空");return;}//对节点是否存在进行判断,否则会报错:【NodeExistsException: KeeperErrorCode = NodeExists for /root】Stat exists = client.checkExists().forPath(nodePath);if (null != exists) {System.out.println("节点【" + nodePath + "】已存在,不能新增");return;} else {System.out.println(StringUtils.join("节点【", nodePath, "】不存在,可以新增节点!"));}//创建节点,并为当前节点赋值内容if (StringUtils.isNotBlank(data)) {List<ACL> aclList = new ArrayList<>();Id zhangsan = new Id("digest", DigestAuthenticationProvider.generateDigest("zhangsan:123456789"));Id lisi = new Id("digest", DigestAuthenticationProvider.generateDigest("lisi:123456789"));Id zhaoliu = new Id("digest", DigestAuthenticationProvider.generateDigest("zhaoliu:123456789"));//赋值权限(可选值:READ、WRITE、CREATE、DELETE、ADMIN、ALL,即:cdrwa)aclList.add(new ACL(ZooDefs.Perms.ALL, zhangsan));aclList.add(new ACL(ZooDefs.Perms.READ, lisi));aclList.add(new ACL(ZooDefs.Perms.WRITE, zhaoliu));//创建节点时,就指定节点所属用户和权限String node = client.create().creatingParentsIfNeeded().withACL(aclList).forPath(nodePath, data.getBytes());System.out.println(node);}}/*** <p>* 获取节点数据* <p/>** @param nodePath* @return void* @Date 2020/6/21 13:13*/private static void getNode(String nodePath) throws Exception {//获取某个节点数据byte[] bytes = client.getData().forPath(nodePath);System.out.println(StringUtils.join("节点:【", nodePath, "】,数据:", new String(bytes)));}/*** <p>* 设置(修改)节点数据* <p/>** @param nodePath* @param data* @return void* @Date 2020/6/21 13:46*/private static void updateNode(String nodePath, String data) throws Exception {//指定版本号,更新节点,更新的时候如果指定数据版本的话,那么需要和zookeeper中当前数据的版本要一致,-1表示匹配任何版本Stat stat = client.setData().withVersion(-1).forPath(nodePath, data.getBytes());System.out.println(stat);}/*** <p>* 删除指定节点* <p>* <p/>** @param nodePath* @return void* @Date 2020/6/21 13:50*/private static void deleteNode(String nodePath) throws Exception {//级联删除节点(如果当前节点有子节点,子节点也可以一同删除)client.delete().deletingChildrenIfNeeded().forPath(nodePath);}}

3.5 设置Zookeeper节点超级用户权限

因为测试的时候,有个/root节点,设置了权限,然后忘记了是哪个用户了,死活就删除不掉了,只能设置一个超级用户权限,来进行节点的操作。

3.5.1 背景

Zookeeper管理员会因为某些客户端对某些节点设置了权限,而导致在紧急的情况下无法修改这些节点感到困扰。在这种情况下,管理员可以通过Zookeeper超级用户模式访问这些节点,一旦设置了超级权限访问节点,使用超级用户登录后,后续的操作就不需要Check ACL了。

使用超级用户模式,可以通过Zookeeper的zookeeper.DigestAuthenticationProvider.superDigest参数开启。

3.5.2 对超级用户的密码加密

使用org.apache.zookeeper.server.auth.DigestAuthenticationProvider生成superDigest:

public void generate() {  try {  System.out.println(DigestAuthenticationProvider.generateDigest("super:superpw"));  } catch (NoSuchAlgorithmException e) {  // TODO Auto-generated catch block  e.printStackTrace();  }  
}  

输出super:superpw对应的加密参数为: super:g9oN2HttPfn8MMWJZ2r45Np/LIA=

3.5.3 超级用户加入到服务端启动参数

在zookeeper服务端的zkEnv.sh环境变量中加入以下参数,开启超级用户,重启zookeeper服务端:

SERVER_JVMFLAGS="-Dzookeeper.DigestAuthenticationProvider.superDigest=super:g9oN2HttPfn8MMWJZ2r45Np/LIA= $SERVER_JVMFLAGS"  

3.5.4 在命令行客户端使用超级用户登陆

使用客户端登陆到zookeeper服务端:
zkCli.sh -server ip:2181

然后执行命令切换到超级用户:
addauth digest super:superpw

获取Acl设置:
getAcl /root

'digest,'zhangsan:jA/7JI9gsuLp0ZQn5J5dcnDQkHA=: a
'digest,'lisi:c86tNrln9GqhiYsCu/4W34U1vt4=: r

这样就可以删除带有权限控制的节点了:
delete /root 或者 deleteall /root
注意: 这两种方式,都不能删除拥有子节点的那个节点,如果有子节点,必须先删除子节点,再删除父节点。

原文链接:https://www.jianshu.com/p/373d52375a65

4 Zookeeper的Leader选举

Zookeeper集群时,肯定会进行master、follower节点的选择,那么,zk的选举机制是怎么样的呢?

Leader选举是保证分布式数据一致性的关键所在,当Zookeeper集群中的一台服务器出现以下两种情况之一时,会进行Leader选举。

  • 服务器初始化启动的时候
  • 服务器运行期间无法和Leader保持连接(也即是Leader宕机了)

下面对每种类型的选举进行分析。

注:这里的分析以上边第二节的集群示例进行分析。

4.1 服务器初始化启动的时候进行Leader选举

进行Leader选举的基础条件: 需要有两个及以上的zookeeper,下面以3台机器组成的zookeeper集群为例进行分析。

在集群初始化阶段,当有一台服务器zookeeper1启动时,其单独无法进行Leader选举
当第二台服务器zookeeper2启动时,此时两台机器(超过半数)可以相互通信,每台机器都试图找到Leader,于是进入Leader选举过程,此时即使可以构成集群,但是不是高可用的,因为一旦一台机器挂了,Zookeeper服务就不可用了,至少3台,才可以构成有效的集群。

4.1.1 选举过程分析

  • 每个zookeeper服务都会发起一个投票,由于是启动初始化的情况,zookeeper1服务和zookeeper2服务都会将自己作为Leader服务器来进行投票,每次投票会包含所推选的服务器的myid(集群时创建的myid)和ZXID(全局节点事务id),使用(ZXID,myid)来表示,此时zookeeper1的投票为 (0, 1),zookeeper2的投票为 (0, 2),然后各自将这个投票信息发送给集群中其他机器。

  • 集群中的每台服务器收到投票后,首先判断该投票的有效性,如:检查是否是本轮投票是否来自LOOKING状态的服务器。

  • 处理投票,针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK。
    PK规则如下:
    优先检查ZXID,ZXID比较大的服务器优先作为Leader(开始的时候ZXID相等,都是0,后续节点被操作后,ZXID会变化)。如果ZXID相同,那么就比较myid,myid大的服务器作为Leader服务器

    对于zookeeper1而言,它的投票是 (0, 1),接收zookeeper2的投票为 (0, 2),首先会比较两者的ZXID,初始值相等,ZXID都是0,然后比较myid,此时zookeeper2的myid最大,zookeeper2成为leader,同时每个zookeeper更新自己的投票为 (0,2),然后重新投票(这里需要看是否过半数,是否有效),对于zookeeper2而言,其无须更新自己的投票,只是再次向集群中所有机器发送上一次投票信息即可。

  • 统计投票,每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于zookeeper1、zookeeper2而言,都统计出集群中已经有两台机器接受了 (0,2) 的投票信息, 此时便认为已经选出了Leader

  • 改变服务器状态,一旦确定了Leader,每台服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader,就变更为LEADING

4.1.2 集群服务状态说明

  • LOOKING
    寻找leader状态,当前服务器处于该状态时,它会认为当前集群中没有leader,因此需要进入leader选举状态

  • FOLLOWING
    跟随者状态,表示当前服务器的角色是Follower角色

  • LEADING
    领导者状态,表示当前服务器是Leader

  • OBSERVING
    观察者状态,表示当前服务器角色是Observer

4.2 服务器运行期间的Leader选举

在Zookeeper运行期间,Leader与非Leader服务器各司其职,如果有非Leader服务器宕机或新加入,此时不会影响Leader,但是一旦Leader服务器宕机,那么整个集群将暂停对外服务,进入新一轮Leader选举,其过程和启动时期的Leader选举过程基本一致。

假设正在运行的有zookeeper1、zookeeper2、zookeeper3三台服务器,当前Leader是zookeeper3,若某一时刻Leader宕机了,此时便开始Leader选举,选举过程如下:

  • 变更状态,Leader宕机后,余下的服务器都会将自己的服务器状态变更为LOOKING,然后开始进入Leader选举过程

  • 每个zookeeper会发出一个投票,在运行期间,每个服务器上的ZXID可能不同,此时假定zookeeper1的ZXID为100,zookeeper2的ZXID为101。在第一轮投票中,zookeeper1和zookeeper2都会将票投给自己,产生投票(100, 1),(101, 2),然后各自将投票信息发送给集群中的其他机器。

  • 接收来自各个服务器的投票,与自己的投票进行PK比较,此过程与启动时相同

  • 处理投票,与启动时过程相同,此时(zookeeper2将会成为Leader)

  • 再次投票统计投票信息,统计出集群中是否有一半以上的机器接受了(101,2) 的投票信息,这个与始化启动时过程相同

  • 改变服务器的状态,与始化启动时过程相同

5 分布式系统生成全局唯一ID

5.1 背景

在大型分布式系统中,我们经常需要生成全局唯一标识,来保证系统单号的唯一性,比如:银行中商户订单号,银行支付接口时唯一编号、红包、优惠券等等。

系统中生成分布式ID的要求:

  • 全局唯一,不能重复(基本要求)
  • 递增,下一个ID大于上一个ID(有些需求)
  • 信息安全,非连续ID,避免恶意用户/竞争对手发现ID规则,从而猜出下一个ID或者根据ID总量猜出业务总量
  • 高可用,不能故障,可用性4个9或者5个9(99.99%、99.999%)
  • 高QPS,性能不能太差,否则容易造成线程堵塞
  • 平均延迟尽可能低

5.2 解决方案

解决方案有很多,本文主要介绍下基于Zookeeper的解决方案,其他的几种,后续有机会再详细介绍。

5.2.1 UUID方案

可以使用UUID的方式,生成全局唯一Id,可以线程安全的。

使用UUID的方法:

UUID.randomUUID()

这种方式虽然可以生成安全的全局唯一Id,但是,也只能满足一般的场景:

  • UUID太长,很多场景不适用
  • 有些场景希望id是数字的,UUID就不适用(有时候需要全局唯一编号是有序的)
  • 可读性不好

5.2.2 数据库自增ID方案

  • 数据库自增ID
  • 在创建表结构的时候,可以使用auto_increment标识生成自增的主键id(MySQL数据库)
  • 可以基于序列,生成自增的主键id(Oracle数据库)

这种方式也是可以的,但是有一些弊端:

  • 这种基于数据库的ID生成方案,比较依赖数据库单机的读写性能;(高并发条件下性能不是很好,数据库服务响应慢)
  • 对数据库依赖较大,数据库易发生性能瓶颈问题

5.2.3 Redis方案(推荐)

这种方案还是不错的,目前互联网公司使用的还是比较多的,后续会详细介绍这种方案,本文主要还是以Zookeeper为主线,实现全局唯一Id。

简单说下思路:
通过Redis原子操作命令INCRINCRBY(redis自增)实现递增,同时可使用Redis集群提高吞吐量,集群后每台Redis的初始值为1,2,3,4,5,步长为5

A:16111621
B:27121722
C:38131823
D:49141924
E:510152025

5.2.4 基于Twiitter的snowflake算法

https://github.com/twitter/snowflake
这里不详细介绍,有兴趣的童鞋们,可以去看一些,网上有教程,可以百度一下。

5.2.5 基于MongoDB的ObjectID

基于MongoDB,也可以实现,后续有机会,会更新完善下。

5.2.6 基于Zookeeper的方案(推荐)

ZooKeeper作为一个分布式的,开放源码的分布式应用程序协调服务,实现这种全局唯一Id,自然是比较容易的。

基于Zookeeper,大概有两种方案:

  • 通过持久化的顺序节点实现
  • 通过节点版本号

下面,基于Zookeeper,详细介绍下如何实现全局唯一Id的生成。

5.2.6.1 通过持久化的顺序节点实现全局唯一Id

思路: 每次创建节点时,创建一个带有序列号的,持久化的节点,这样,就可以实现全局惟一的编号。

代码示例如下:

package cn.smilehappiness.generatorID;import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;import java.util.concurrent.*;/*** <p>* 分布式下,基于Zookeeper生成全局唯一Id(基于Zookeeper序列号,通过持久化的顺序节点实现)* <p/>** @author smilehappiness* @Date 2020/6/21 17:14*/
public class GeneratorIdSequential {/*** 客户端连接地址*/private static final String ZK_ADDRESS = "ip:2181";/*** 创建zookeeper连接实例*/private static CuratorFramework client = null;/*** 客户端根节点*/private static final String ROOT_NODE = "/root";/*** 客户端子节点*/private static final String ROOT_NODE_CHILDREN = "/root/uniqueId/id";/*** 重试策略* n:最多重试次数* sleepMsBetweenRetries:重试时间间隔,单位毫秒*/private static final RetryPolicy retry = new RetryNTimes(3, 2000);static {// 创建Curator连接对象connectCuratorClient();}/*** <p>* 创建Curator连接对象* <p/>** @param* @return* @Date 2020/6/21 12:29*/public static void connectCuratorClient() {//创建zookeeper连接client = CuratorFrameworkFactory.newClient(ZK_ADDRESS, retry);//启动客户端(Start the client. Most mutator methods will not work until the client is started)client.start();System.out.println("zookeeper初始化连接成功:" + client);}private static String idGenerator() throws Exception {Stat stat = client.checkExists().forPath(ROOT_NODE_CHILDREN);//如果节点不存在if (null == stat) {String node = client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT_SEQUENTIAL).forPath(ROOT_NODE_CHILDREN);//System.out.println("node = " + node);//避免占用zookeeper空间,可以再返回有序节点,并处理完之后,把当前节点删除一下//client.delete().guaranteed().forPath(node);return node.replace(ROOT_NODE_CHILDREN, "");}return null;}/*** <p>* 全局唯一id生成方法测试-单线程* <p/>** @param* @return void* @Date 2020/6/21 18:12*/private static void testGeneratorIdSingle() throws Exception {for (int i = 0; i < 10; i++) {//通过生成器,生成唯一idString uniqueId = idGenerator();System.out.println("uniqueId = " + uniqueId);}}/*** <p>* 全局唯一id生成方法测试-多线程* <p/>** @param* @return void* @Date 2020/6/21 18:30*/private static void testGeneratorIdMultiThread() {//循环次数int count = 10;ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("uniqueId-generator-%d").build();ExecutorService executorService = new ThreadPoolExecutor(4, 2 * 4 + 1, 30, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1000), threadFactory);//设置倒计数器CountDownLatch downLatch = new CountDownLatch(count);for (int i = 0; i < count; i++) {executorService.execute(() -> {try {//通过生成器,生成唯一idString uniqueId = idGenerator();System.out.println("当前线程名称:【" + Thread.currentThread().getName() + "】,生成的全局唯一uniqueId:【" + uniqueId + "】");//每生成一次,就减一downLatch.countDown();} catch (Exception e) {e.printStackTrace();}});}try {downLatch.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("全局唯一id生成成功啦...");executorService.shutdown();}public static void main(String[] args) throws Exception {//testGeneratorIdSingle();long startTime = System.currentTimeMillis();testGeneratorIdMultiThread();long endTime = System.currentTimeMillis();System.out.println("耗时:【" + (endTime - startTime) / 1000 + "】秒");}}
5.2.6.2 基于Zookeeper节点版本号实现全局唯一Id

思路: 每次为节点赋值的时候,可以拿到Stat对象,通过该对象可以获取节点版本号,这样也可以实现全局唯一Id,因为没操作一次节点,这个版本号也是自增的。(基于当前时间+版本号实现

【代码示例】

package cn.smilehappiness.generatorID;import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.apache.commons.lang3.StringUtils;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;/*** <p>* 分布式下,基于Zookeeper生成全局唯一Id(基于Zookeeper版本号实现)* <p/>** @author smilehappiness* @Date 2020/6/21 19:01*/
public class GeneratorIdVersion {/*** 客户端连接地址*/private static final String ZK_ADDRESS = "ip:2181";/*** 创建zookeeper连接实例*/private static CuratorFramework client = null;/*** 客户端子节点*/private static final String ROOT_NODE_CHILDREN = "/root/uniqueId-version";/*** 重试策略* n:最多重试次数* sleepMsBetweenRetries:重试时间间隔,单位毫秒*/private static final RetryPolicy retry = new RetryNTimes(3, 2000);/*** 线程池对象*/private static ThreadPoolExecutor threadPoolExecutor;static {//初始化线程池对象ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("uniqueId-generator-%d").build();threadPoolExecutor = new ThreadPoolExecutor(8, 2 * 8 + 1, 30, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1000), threadFactory);//当前队列任务执行情况ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);executor.scheduleAtFixedRate(() -> {System.out.println("任务总线程数:【" + threadPoolExecutor.getTaskCount() + "】");System.out.println("执行完成线程数:【" + threadPoolExecutor.getCompletedTaskCount() + "】");System.out.println("当前活动线程数:【" + threadPoolExecutor.getActiveCount() + "】");System.out.println("当前剩余线程数:【" + threadPoolExecutor.getQueue().size() + "】");}, 1, 2, TimeUnit.SECONDS);// 创建Curator连接对象connectCuratorClient();}public static void main(String[] args) throws Exception {//testGeneratorIdSingle();long startTime = System.currentTimeMillis();testGeneratorIdMultiThread();long endTime = System.currentTimeMillis();System.out.println("耗时:【" + (endTime - startTime) / 1000 + "】秒");}/*** <p>* 创建Curator连接对象* <p/>** @param* @return* @Date 2020/6/21 12:29*/public static void connectCuratorClient() {//创建zookeeper连接client = CuratorFrameworkFactory.newClient(ZK_ADDRESS, retry);//启动客户端(Start the client. Most mutator methods will not work until the client is started)client.start();System.out.println("zookeeper初始化连接成功:" + client);}private static String idGenerator() throws Exception {//如果节点不存在if (null == client.checkExists().forPath(ROOT_NODE_CHILDREN)) {String node = client.create().creatingParentsIfNeeded()//创建持久化节点,可以省略.withMode(CreateMode.PERSISTENT).forPath(ROOT_NODE_CHILDREN);System.out.println("node = " + node);}//基于日期+zk版本号,生成全局唯一IdString DATE_DAY_PATTERN = "yyyyMMdd";String basePrefix = format(new Date(), DATE_DAY_PATTERN);Stat stat = client.setData().withVersion(-1).forPath(ROOT_NODE_CHILDREN);//返回赋值操作节点后,当前节点的版本号return basePrefix + stat.getVersion();}/*** <p>* 全局唯一id生成方法测试-单线程* <p/>** @param* @return void* @Date 2020/6/21 18:12*/private static void testGeneratorIdSingle() throws Exception {for (int i = 0; i < 10; i++) {//通过生成器,生成唯一idString uniqueId = idGenerator();System.out.println("生成的序列号uniqueId = " + uniqueId);}}/*** <p>* 全局唯一id生成方法测试-多线程* <p/>** @param* @return void* @Date 2020/6/21 18:30*/private static void testGeneratorIdMultiThread() {//等待时间final long awaitTime = 10 * 1000;//循环次数int count = 20;//设置倒计数器CountDownLatch countDownLatch = new CountDownLatch(1);for (int i = 0; i < count; i++) {try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}//提交线程到线程池去执行(这里可以替换为lambda表达式)threadPoolExecutor.submit(new Runnable() {@Overridepublic void run() {try {//等待,保证所有的线程就位,但是不运行,countDownLatch.await();System.out.println("Thread Name:【" + Thread.currentThread().getName() + "】, current time: " + System.currentTimeMillis());//执行业务代码try {//Thread.sleep(1000);//System.currentTimeMillis()或者i++,++i等多种写法,在多线程环境下生成惟一的Id都是有问题的,所以这里使用zk的版本号作为唯一编号String uniqueId = idGenerator();System.out.println("生成的序列号uniqueId = " + uniqueId);} catch (Throwable e) {System.out.println("Thread:" + Thread.currentThread().getName() + e.getMessage());}} catch (InterruptedException e) {e.printStackTrace();}}});}// countDown()表示倒计算器-1,这里8个线程同时开始执行,那么就可以达到并发效果countDownLatch.countDown();// 关闭线程池的代码try {// 传达完毕信号threadPoolExecutor.shutdown();// 所有的任务都结束的时候,返回TRUE,如果未执行完毕,处于阻塞状态(注意:这里的awaitTime要根据实际情况设置合适的等待时间)if (!threadPoolExecutor.awaitTermination(awaitTime, TimeUnit.MILLISECONDS)) {// 超时的时候向线程池中所有的线程发出中断(interrupted)threadPoolExecutor.shutdownNow();}} catch (InterruptedException e) {// awaitTermination方法被中断的时候,也中止线程池中全部线程的执行,System.out.println("awaitTermination interrupted: " + e);threadPoolExecutor.shutdownNow();}}/*** <p>* 将日期格式化为String* <p/>** @param date* @param pattern* @return java.lang.String* @Date 2020/6/21 19:08*/public static String format(Date date, String pattern) {if (date == null) {return null;} else {if (StringUtils.isBlank(pattern)) {pattern = "yyyy-MM-dd HH:mm:ss";}SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);return dateFormat.format(date);}}/*** <p>* 将日期格式化为Date* <p/>** @param strDate* @param pattern* @return java.lang.String* @Date 2020/6/21 19:08*/public static Date parse(String strDate, String pattern) {if (StringUtils.isBlank(strDate)) {return null;} else {try {return (new SimpleDateFormat(pattern)).parse(strDate);} catch (ParseException var3) {return null;}}}}

单线程执行结果:

zookeeper初始化连接成功:org.apache.curator.framework.imps.CuratorFrameworkImpl@754ba872
生成的序列号uniqueId = 2020062111
生成的序列号uniqueId = 2020062112
生成的序列号uniqueId = 2020062113
生成的序列号uniqueId = 2020062114
生成的序列号uniqueId = 2020062115
生成的序列号uniqueId = 2020062116
生成的序列号uniqueId = 2020062117
生成的序列号uniqueId = 2020062118
生成的序列号uniqueId = 2020062119
生成的序列号uniqueId = 2020062120

5.3 小结

以上主要介绍了几种分布式环境下,全局唯一Id的生成方式。详细介绍了基于Zookeeper的方案,其中也使用了两种多线程的方式,来模拟Id的唯一性,以上测试实例说明,在多线程环境下,基于Zookeeper的两种方案,都是可靠的。

6 总结

本系列Zookeeper文章就讲到这里了,后续使用到了再进行补充吧,感谢老铁们的用心阅读,希望对你有所帮助。

写博客是为了记住自己容易忘记的东西,另外也是对自己工作的总结,希望尽自己的努力,做到更好,大家一起努力进步!

如果有什么问题,欢迎大家评论,一起探讨,代码如有问题,欢迎各位大神指正!

给自己的梦想添加一双翅膀,让它可以在天空中自由自在的飞翔!

这篇关于Zookeeper使用详解(进阶篇),满满的干货~的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/598963

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Zookeeper安装和配置说明

一、Zookeeper的搭建方式 Zookeeper安装方式有三种,单机模式和集群模式以及伪集群模式。 ■ 单机模式:Zookeeper只运行在一台服务器上,适合测试环境; ■ 伪集群模式:就是在一台物理机上运行多个Zookeeper 实例; ■ 集群模式:Zookeeper运行于一个集群上,适合生产环境,这个计算机集群被称为一个“集合体”(ensemble) Zookeeper通过复制来实现

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]