• linkedu视频
  • 平面设计
  • 电脑入门
  • 操作系统
  • 办公应用
  • 电脑硬件
  • 动画设计
  • 3D设计
  • 网页设计
  • CAD设计
  • 影音处理
  • 数据库
  • 程序设计
  • 认证考试
  • 信息管理
  • 信息安全
菜单
linkedu.com
  • 网页制作
  • 数据库
  • 程序设计
  • 操作系统
  • CMS教程
  • 游戏攻略
  • 脚本语言
  • 平面设计
  • 软件教程
  • 网络安全
  • 电脑知识
  • 服务器
  • 视频教程
  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号
您的位置:首页 > 程序设计 >JSP > 全局唯一ID生成器浅析IdGen (1)

全局唯一ID生成器浅析IdGen (1)

作者:yucongyuqian的博客 字体:[增加 减小] 来源:互联网 时间:2017-08-07

yucongyuqian的博客通过本文主要向大家介绍了ID生成器等相关知识,希望对您有所帮助,也希望大家支持linkedu.com www.linkedu.com

转载出处。http://www.blogjava.net/bolo
局唯一ID生成器浅析
我们在开发中,有时非常需要一个全局唯一的ID值,不管是业务需求,还是为了以后可能的分表需求,全局唯一值都非常有用,本篇大象就来讲讲这个实现并对ID生成器性能进行一下测试。
大象所讲的这个全局唯一ID生成器,其实是Twitter公开的一个算法,源码是用Scala写的,被国内的开源爱好者改写成了Java版本。
大象将这个类的调用简化了一下,实际使用中还是应该根据机器节点和数据中心节点来配置相关的参数。我这里假设只有一个节点作为ID号的生成器,所以workerId和datacenterId都设为0,当前时间与计算标记时间twepoch(Thu, 04 Nov 2010 01:42:54 GMT)之间的毫秒数是一个38位长度的long值,再左移timestampLeftShift(22位),就得到一个60位长度的long数字,该数字与datacenterId << datacenterIdShift取或,datacenterId最小值为0,最大值为31,所以长度为1-5位,datacenterIdShift是17位,所以结果就是最小值为0,最大值为22位长度的long,同理,workerId << workerIdShift的最大值为17位的long。所以最终生成的会是一个60位长度的long型唯一ID
/**
* 全局唯一ID生成器
*/
public class IdGen {

private long workerId;
private long datacenterId;
private long sequence = 0L;
private long twepoch = 1288834974657L; //Thu, 04 Nov 2010 01:42:54 GMT
private long workerIdBits = 5L; //节点ID长度
private long datacenterIdBits = 5L; //数据中心ID长度
private long maxWorkerId = -1L ^ (-1L << workerIdBits); //最大支持机器节点数0~31,一共32个
private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); //最大支持数据中心节点数0~31,一共32个
private long sequenceBits = 12L; //序列号12位
private long workerIdShift = sequenceBits; //机器节点左移12位
private long datacenterIdShift = sequenceBits + workerIdBits; //数据中心节点左移17位
private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; //时间毫秒数左移22位
private long sequenceMask = -1L ^ (-1L << sequenceBits); //4095
 private long lastTimestamp = -1L;

private static class IdGenHolder {
    private static final IdGen instance = new IdGen();
}

public static IdGen get(){
    return IdGenHolder.instance;
}

public IdGen() {
    this(0L, 0L);
}

public IdGen(long workerId, long datacenterId) {
    if (workerId > maxWorkerId || workerId < 0) {
        throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
    }
    if (datacenterId > maxDatacenterId || datacenterId < 0) {
        throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
    }
    this.workerId = workerId;
    this.datacenterId = datacenterId;
}

public synchronized long nextId() {
    long timestamp = timeGen(); //获取当前毫秒数
    //如果服务器时间有问题(时钟后退) 报错。
    if (timestamp < lastTimestamp) {
        throw new RuntimeException(String.format(
                "Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
    }
    //如果上次生成时间和当前时间相同,在同一毫秒内
    if (lastTimestamp == timestamp) {
        //sequence自增,因为sequence只有12bit,所以和sequenceMask相与一下,去掉高位
        sequence = (sequence + 1) & sequenceMask;
        //判断是否溢出,也就是每毫秒内超过4095,当为4096时,与sequenceMask相与,sequence就等于0
        if (sequence == 0) {
            timestamp = tilNextMillis(lastTimestamp); //自旋等待到下一毫秒
        }
    } else {
        sequence = 0L; //如果和上次生成时间不同,重置sequence,就是下一毫秒开始,sequence计数重新从0开始累加
    }
    lastTimestamp = timestamp;
    // 最后按照规则拼出ID。
    // 000000000000000000000000000000000000000000  00000            00000       000000000000
// time                                                               datacenterId   workerId    sequence
     return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift)
            | (workerId << workerIdShift) | sequence;
}

protected long tilNextMillis(long lastTimestamp) {
    long timestamp = timeGen();
    while (timestamp <= lastTimestamp) {
        timestamp = timeGen();
    }
    return timestamp;
}

protected long timeGen() {
    return System.currentTimeMillis();
}

}
//idGen必须用final修饰才能应用
final IdGen idGen = IdGen.get();
——接下来我再写个测试类,看下并发情况下,1秒钟可以生成多少个ID。我测试用的电脑CPU为I5-4210U,内存8G,JDK为1.7.0_79,系统是64位WIN 7,使用-server模式。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.junit.Test;

public class GeneratorTest {

@Test
public void testIdGenerator() {
    long avg = 0;
    for (int k = 0; k < 10; k++) {
        List<Callable<Long>> partitions = new ArrayList<Callable<Long>>();
       //idGen必须用final修饰才能应用
        final IdGen idGen = IdGen.get();
        for (int i = 0; i < 1400000; i++) {
            partitions.add(new Callable<Long>() {
                @Override
                public Long call() throws Exception {
                    return idGen.nextId();
                }
            });
        }
        ExecutorService executorPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        try {
            long s = System.currentTimeMillis();
            executorPool.invokeAll(partitions, 10000, TimeUnit.SECONDS);
            long s_avg = System.currentTimeMillis() - s;
            avg += s_avg;
            System.out.println("完成时间需要: " + s_avg / 1.0e3 + "秒");
            executorPool.shutdown();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    System.out.println("平均完成时间需要: " + avg / 10 / 1.0e3 + "秒");
}

}

运行10次,平均下来,每次1.038秒生成140万个ID,除了第1次时间在3秒左右和第2次1.6秒左右,其余8次都在0.7秒左右。如果使用更好的硬件,测试数据肯定会更好。因此从大的方向上看,单节点的ID生成器基本上可以满足我们的需要了。
需要注意的是,该值只是一个唯一值,但并不能保证会是一个顺序值,就是说两个ID之间可能会跳一些数字,所以对于一些有特殊需求的业务来说请注意这个差异。
本文为菠萝大象原创,如要转载出处。http://www.blogjava.net/bolo
分享到:QQ空间新浪微博腾讯微博微信百度贴吧QQ好友复制网址打印

您可能想查找下面的文章:

相关文章

  • 2017-05-11jsp页面中窗口关闭,退出的方式分享
  • 2017-05-11Eclipse中使用ANT
  • 2017-05-11JSP+MySQL实现网站的登录与注册小案例
  • 2017-05-11浅谈JSP serverlet的区别与联系
  • 2017-05-11Java多线程编程之限制优先级
  • 2017-05-11jsp页面中EL表达式被当成字符串处理不显示值问题的解决方法
  • 2017-05-11七、HTTP应答状态
  • 2017-05-11处理jsp显示文字过长问题的解决方法
  • 2017-05-11下载完成后页面不自动关闭的方法
  • 2017-05-11【算法】扑克发牌算法实现

文章分类

  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号

最近更新的内容

    • jsp中为表格添加水平滚动条的实现方法
    • jsp分页显示完整实例
    • 写一个对搜索引擎友好的文章SEO分页类
    • Jsp中的table多表头导出excel文件具体实现
    • 用JSP下载word文件(不会直接用IE打开)
    • JSP生成WORD文档,EXCEL文档及PDF文档的方法
    • JSP中的编译指令和动作指令的两点区别
    • 用简单的代码来实现文件上传
    • JSP数据库操作例程(Use Bean)
    • JSP连接MySql/MS SQL Server/Oracle数据库连接方法[整理]

关于我们 - 联系我们 - 免责声明 - 网站地图

©2020-2025 All Rights Reserved. linkedu.com 版权所有