疯狂创客圈,一个Java 高并发研习社群博客园 总入口

疯狂创客圈,倾力推出:面试必备 + 面试必备 + 面试必备 的基础原理+实战 书籍 《Netty Zookeeper Redis 高并发实战


@

写在前面

​ 大家好,我是作者尼恩。目前和几个小伙伴一起,组织了一个高并发的实战社群【疯狂创客圈】。正在开始高并发、亿级流程的 IM 聊天程序 学习和实战

​ 前面,已经完成一个高性能的 Java 聊天程序的四件大事:

接下来,需要进入到分布式开发的环节了。 分布式的中间件,疯狂创客圈的小伙伴们,一致的选择了zookeeper,不仅仅是由于其在大数据领域,太有名了。更重要的是,很多的著名框架,都使用了zk。

​ **本篇介绍 ZK 的分布式命名服务 ** 中的 节点命名服务和 snowflake 雪花算法。

1.1.1. 集群节点的命名服务

前面讲到,在分布式集群中,可能需要部署的大量的机器节点。在节点少的受,可以人工维护。在量大的场景下,手动维护成本高,考虑到自动部署、运维等等问题,节点的命名,最好由系统自动维护。

节点的命名,主要是为节点进行唯一编号。主要的诉求是,不同节点的编号,是绝对的不能重复。一旦编号重复,就会导致有不同的节点碰撞,导致集群异常。

有以下两个方案,可供生成集群节点编号:

(1)使用数据库的自增ID特性,用数据表,存储机器的mac地址或者ip来维护。

(2)使用ZooKeeper持久顺序节点的次序特性。来维护节点的编号。

这里,我们采用第二种,通过ZooKeeper持久顺序节点特性,来配置维护节点的编号NODEID。

集群节点命名服务的基本流程是:

(1)启动节点服务,连接ZooKeeper, 检查命名服务根节点根节点是否存在,如果不存在就创建系统根节点。

(2)在根节点下创建一个临时顺序节点,取回顺序号做节点的NODEID。如何临时节点太多,可以根据需要,删除临时节点。

基本的算法,和生成分布式ID的大部分是一致的,主要的代码如下:

package com.crazymakercircle.zk.NameService;

import com.crazymakercircle.util.ObjectUtil;
import com.crazymakercircle.zk.ZKclient;
import lombok.Data;
import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.CreateMode; /**
* create by 尼恩 @ 疯狂创客圈
**/
@Data
public class SnowflakeIdWorker { //Zk客户端
private CuratorFramework client = null; //工作节点的路径
private String pathPrefix = "/test/IDMaker/worker-";
private String pathRegistered = null; public static SnowflakeIdWorker instance = new SnowflakeIdWorker(); private SnowflakeIdWorker() {
instance.client = ZKclient.instance.getClient();
instance.init();
} // 在zookeeper中创建临时节点并写入信息
public void init() { // 创建一个 ZNode 节点
// 节点的 payload 为当前worker 实例 try {
byte[] payload = ObjectUtil.Object2JsonBytes(this); pathRegistered = client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
.forPath(pathPrefix, payload);
} catch (Exception e) {
e.printStackTrace();
} } public long getId() {
String sid=null;
if (null == pathRegistered) {
throw new RuntimeException("节点注册失败");
}
int index = pathRegistered.lastIndexOf(pathPrefix);
if (index >= 0) {
index += pathPrefix.length();
sid= index <= pathRegistered.length() ? pathRegistered.substring(index) : null;
} if(null==sid)
{
throw new RuntimeException("节点ID生成失败");
} return Long.parseLong(sid); }
}

1.1.2. snowflake 的ID算法改造

Twitter的snowflake 算法,是一种著名的分布式服务器用户ID生成算法。SnowFlake算法所生成的ID 是一个64bit的长整形数字。这个64bit被划分成四部分,其中后面三个部分,分别表示时间戳、机器编码、序号。

(1)第一位

占用1bit,其值始终是0,没有实际作用。

(2)时间戳

占用41bit,精确到毫秒,总共可以容纳约69年的时间。

(3)工作机器id

占用10bit,最多可以容纳1024个节点。

(4)序列号

占用12bit,最多可以累加到4095。这个值在同一毫秒同一节点上从0开始不断累加。

总体来说,在工作节点达到1024顶配的场景下,SnowFlake算法在同一毫秒内最多可以生成多少个全局唯一ID呢?这是一个简单的乘法:

同一毫秒的ID数量 = 1024 X 4096 = 4194304

400多万个ID,这个数字在绝大多数并发场景下都是够用的。

snowflake 算法中,第三个部分是工作机器ID,可以结合上一节的命名方法,并通过Zookeeper管理workId,免去手动频繁修改集群节点,去配置机器ID的麻烦。

/**
* create by 尼恩 @ 疯狂创客圈
**/
public class SnowflakeIdGenerator { /**
* 单例
*/
public static SnowflakeIdGenerator instance =
new SnowflakeIdGenerator(); /**
* 初始化单例
*
* @param workerId 节点Id,最大8091
* @return the 单例
*/
public synchronized void init(long workerId) {
if (workerId > MAX_WORKER_ID) {
// zk分配的workerId过大
throw new IllegalArgumentException("woker Id wrong: " + workerId);
}
instance.workerId = workerId;
} private SnowflakeIdGenerator() { } /**
* 开始使用该算法的时间为: 2017-01-01 00:00:00
*/
private static final long START_TIME = 1483200000000L; /**
* worker id 的bit数,最多支持8192个节点
*/
private static final int WORKER_ID_BITS = 13; /**
* 序列号,支持单节点最高每毫秒的最大ID数1024
*/
private final static int SEQUENCE_BITS = 10; /**
* 最大的 worker id ,8091
* -1 的补码(二进制全1)右移13位, 然后取反
*/
private final static long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS); /**
* 最大的序列号,1023
* -1 的补码(二进制全1)右移10位, 然后取反
*/
private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS); /**
* worker 节点编号的移位
*/
private final static long APP_HOST_ID_SHIFT = SEQUENCE_BITS; /**
* 时间戳的移位
*/
private final static long TIMESTAMP_LEFT_SHIFT = WORKER_ID_BITS + APP_HOST_ID_SHIFT; /**
* 该项目的worker 节点 id
*/
private long workerId; /**
* 上次生成ID的时间戳
*/
private long lastTimestamp = -1L; /**
* 当前毫秒生成的序列
*/
private long sequence = 0L; /**
* Next id long.
*
* @return the nextId
*/
public Long nextId() {
return generateId();
}
/**
* 生成唯一id的具体实现
*/
private synchronized long generateId() {
long current = System.currentTimeMillis(); if (current < lastTimestamp) {
// 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过,出现问题返回-1
return -1;
} if (current == lastTimestamp) {
// 如果当前生成id的时间还是上次的时间,那么对sequence序列号进行+1
sequence = (sequence + 1) & MAX_SEQUENCE; if (sequence == MAX_SEQUENCE) {
// 当前毫秒生成的序列数已经大于最大值,那么阻塞到下一个毫秒再获取新的时间戳
current = this.nextMs(lastTimestamp);
}
} else {
// 当前的时间戳已经是下一个毫秒
sequence = 0L;
} // 更新上次生成id的时间戳
lastTimestamp = current; // 进行移位操作生成int64的唯一ID //时间戳右移动23位
long time = (current - START_TIME) << TIMESTAMP_LEFT_SHIFT; //workerId 右移动10位
long workerId = this.workerId << APP_HOST_ID_SHIFT; return time | workerId | sequence;
} /**
* 阻塞到下一个毫秒
*/
private long nextMs(long timeStamp) {
long current = System.currentTimeMillis();
while (current <= timeStamp) {
current = System.currentTimeMillis();
}
return current;
} }

上面的代码中,大量的使用到了位运算。

如果对位运算不清楚,估计很难看懂上面的代码。

这里需要强调一下,-1 的8位二进制编码为 1111 1111,也就是全1。

为什么呢?

因为,8位二进制场景下,-1的原码是1000 0001,反码是 1111 1110,补码是反码加1。计算后的结果是,-1 的二进制编码为全1。16位、32位、64位的-1,二进制的编码也是全1。

上面用到的二进制位移算法,以及二进制按位或的算法,都比较简单。如果不懂,可以去查看java的基础书籍。

总的来说,以上的代码,是一个相对比较简单的snowflake实现版本,关键的算法解释如下:

(1)在单节点上获得下一个ID,使用Synchronized控制并发,而非CAS的方式,是因为CAS不适合并发量非常高的场景。

(2)如果当前毫秒在一台机器的序列号已经增长到最大值4095,则使用while循环等待直到下一毫秒。

(3)如果当前时间小于记录的上一个毫秒值,则说明这台机器的时间回拨了,抛出异常。

SnowFlake算法的优点:

(1)生成ID时不依赖于数据库,完全在内存生成,高性能高可用。

(2)容量大,每秒可生成几百万ID。

(3)ID呈趋势递增,后续插入数据库的索引树的时候,性能较高。

SnowFlake算法的缺点:

(1)依赖于系统时钟的一致性。如果某台机器的系统时钟回拨,有可能造成ID冲突,或者ID乱序。

(2)还有,在启动之前,如果这台机器的系统时间回拨过,那么有可能出现ID重复的危险。

写在最后

​ 下一篇:基于zk,实现分布式锁。


疯狂创客圈 亿级流量 高并发IM 实战 系列

  • Java (Netty) 聊天程序【 亿级流量】实战 开源项目实战


zookeeper snowflake 实战的更多相关文章

  1. Solr+Tomcat+zookeeper部署实战

    一 .安装solr 环境说明:centos 7.3,solr 6.6,zookeeper3.4,Tomcat8.5,jdk1.8 zookeeper的部署请参考:http://www.cnblogs. ...

  2. [Dubbo实战]dubbo + zookeeper + spring 实战 (转)

    这里最熟悉的就是spring了,项目中应用很多.dubbo是一个实现分布式的框架,zookeeper是注册中心.给我的感觉就像多对多关系的两者表,zookeeper相当于第三张表维护关系.下面通过一个 ...

  3. kafka 集群--3个broker 3个zookeeper创建实战

    准备工作: 1. 准备3台机器,IP地址分别为:192.168.0.10,192.168.0.11,192.168.0.12 2. 下载kafka稳定版本,我的版本为:kafka_2.9.2-0.8. ...

  4. ZooKeeper入门实战教程(一)-介绍与核心概念

    1.ZooKeeper介绍与核心概念1.1 简介ZooKeeper最为主要的使用场景,是作为分布式系统的分布式协同服务.在学习zookeeper之前,先要对分布式系统的概念有所了解,否则你将完全不知道 ...

  5. zookeeper简单实战

    一.安装(单机模式.集群模式.伪集群模式) 1:安装JDK 2:解压zk压缩包 3:在conf目录下创建zoo.cfg配置文件. 设置超时时间,快照目录,事务日志文件目录,对外端口,服务IP 4:启动 ...

  6. Springcloud 微服务 高并发(实战1):第1版秒杀

    疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列之15 [博客园总入口 ] 前言 前言 疯狂创客圈(笔者尼恩创建的高并发研习社群)Springcloud 高并发系列文章,将为大家介绍三个版 ...

  7. Zookeeper的java客户端API使用方法(五)

    前面几篇博文,我们简单的介绍了一下zookeeper,如何安装zookeeper集群,以及如何使用命令行等.这篇博文我们重点来看下Zookeeper的java客户端API使用方式. 创建会话 客户端可 ...

  8. Sharding & IDs at Instagram(转)

    英文原文:http://instagram-engineering.tumblr.com/post/10853187575/sharding-ids-at-instagram 译文:http://ww ...

  9. kafka1:Kafka集群部署步骤

    参考: kafka 集群--3个broker 3个zookeeper创建实战 细细品味Kafka_Kafka简介及安装_V1.3http://www.docin.com/p-1291437890.ht ...

随机推荐

  1. IDETalk

    IDETalk插件下载地址 IDETalk是由JetBrains的工程师开发的一款代码级的协同工具,主要是为一个团队在进行相关项目开发时提供代码协同.当前IDETalk只能运行在IDEA下,你可以通过 ...

  2. 解決從Ubuntu 12.04升級至12.10之後的Unity顯示問題

    FROM: http://blog.sina.com.cn/s/blog_97ef3ff4010190pe.html#bsh-75-306370781 今天中午經過系統自帶的“檢查更新”軟件從Ubun ...

  3. 安装jenkins插件的两种方法

    安装jenkins插件有两种方法,一种是在线安装,一种是离线安装.两种方式介绍如下: 1.如果服务器可以上网,那边选择在线安装最好不过了,安装流程为:系统管理----插件管理---选择需要的插件直接安 ...

  4. 控制器View是怎样创建的?

    对于非常多程序猿来说控制器和View的关系肯定有点模糊,对于View的创建肯定有一种说不清道不明的感觉.view仅仅是控制器的一个属性.控制器中有很多对view处理的方法.也就是说得控制器管理view ...

  5. xUtils介绍 -- DbUtils、ViewUtils、HttpUtils、BitmapUtils

    转载注明出处:https://github.com/wyouflf/xUtils xUtils简单介绍 xUtils 包括了非常多有用的android工具. xUtils 支持大文件上传,更全面的ht ...

  6. dr-helper项目设计介绍(一个包括移动端和Web端的点餐管理系统)

    一.源代码路径 https://github.com/weiganyi/dr-helper 二.界面 通过浏览器訪问Web服务,能够看到界面例如以下: ADT-Bundle编译project生成dr- ...

  7. Asp.net管道模型(管线模型)之一发不可收拾

    前言 为什么我会起这样的一个标题,其实我原本只想了解asp.net的管道模型而已,但在查看资料的时候遇到不明白的地方又横向地查阅了其他相关的资料,而收获比当初预想的大了很多. 有本篇作基础,下面两篇就 ...

  8. shell脚本检测网络是否畅通

    shell初始化安装脚本执行时,需从网络上安装一些rpm包,所有需要先检测网络的畅通性, 代码 #检测网络链接&&ftp上传数据 function networkAndFtp() { ...

  9. linux本地文件上传之RZ/SZ和sftp

    将本地的文件上传到服务器或者从服务器上下载文件到本地,rz / sz命令很方便的帮我们实现了这个功能,但是很多Linux系统初始并没有这两个命令. 1.软件安装 (1)编译安装 root 账号登陆后, ...

  10. Android下Slidingmenu和actionbarsherlock的使用

    1 http://blog.csdn.net/wangjinyu501/article/details/9331749  博客很多,推荐此教程,slidingmenu的demo可以演示 2 http: ...