zoukankan      html  css  js  c++  java
  • 分布式ID生成器-雪花算法(snowflake)

    背景

    当下绝大部分互联网公司采用的是分布式的架构系统,而分布式系统中有一些场景需要使用到全局性唯一ID,例如:订单编号、付款单编号、交易流水号等等,在这之前,我们可以使用UUID、数据库自增ID等去实现它,但是要么生成的ID是无序的,要么ID生成效率低下。
    所以在该背景下,twitter公司提出了snowflake算法,最初Twitter把存储系统从MySQL迁移到Cassandra,因为Cassandra没有顺序ID生成机制,为了满足Twitter每秒上万条消息的请求,每条消息都必须分配一条唯一的id,这些id还需要一些大致的顺序(方便客户端排序),并且在分布式系统中不同机器产生的id必须不同,所以twitter开发了这样一套全局唯一ID生成服务。

    介绍
    snowflake算法生成的64位ID = 1位符号位 + 41位时间戳 + 10位机器id + 12位序列号。


     
    64位ID组成结构.png

    1、符号位:一般默认为0,表明该ID为正数。

    2、时间戳:该时间戳并不是指当前的时间戳,而是当前时间戳-初始时间戳(初始时间戳离当前时间越近越好,而且必须小于当前时间戳)的值。这样做的好处是有更多的数值可以使用,最多可以使用(1L << 41)/ (1000毫秒 * 60秒 * 60 分钟 * 24小时 * 365天) = 69年。

    3、机器id:取值在0 ~ 1023之间,最多支持1024个节点。
    假设你们公司的机器节点超过了1024个,而你们的业务没有达到1毫秒需要2408个序列号的并发的情况下,可以将序列号的位数减少,给机器id增加位数。

    4、序列号,取值在0~4096之间,最多支持一毫秒内生成4096个序列号。
    假设你们公司的机器节点没有超过512个,而你们的业务1毫秒需要超过4096个序列号的并发的情况下,可以将机器id的位数减少,给序列号增加位数。

    优点:
    1、完全基于内存,ID生成效率高。
    2、生成的ID基于时间戳和序列号,具有有序性的特点,方便排序、查询。
    3、可以根据生产机器节点数和业务并发量的情况,调整ID的生成策略。

    代码:

    package com.jiepos.api.demo;
    
    /**
     * 雪花算法
     * 64位ID = 1位符号位(固定为0,表示正数) + 41位时间戳 + 10位工作机器id + 12位序列号
     * @author shuyan.qi
     * @date 2020/5/3 9:42 下午
     */
    public class SnowFlakeID {
        //12位序列号
        private long sequence;//序列号
        private long sequenceBits = 12L;//序列号位数
        private long maxSequence = -1 ^ (-1 << sequenceBits);//序列号最大值
    
        //10位工作机器id = 5位机房id + 5位机器id
        private long workerId;//机器id
        private long workerIdBits = 5L;//机器id位数
        private long maxWorkerId = -1 ^ (-1 << workerIdBits);//机器id最大值
        private long workerIdMoveBits = sequenceBits;//机器id左移位数 = 序列号位数
        private long workerIdAfterMove = 0L;//左移后的机器id
    
        private long workerCenterId;//机房id
        private long workerCenterIdBits = 5L;//机房id位数
        private long maxWorkerCenterId = -1 ^ (-1 << workerCenterIdBits);//机房id最大值
        private long workerCenterIdMoveBits = workerIdBits + workerIdMoveBits;//机房id左移位数 = 机器id位数 + 序列号位数
        private long workerCenterIdAfterMove = 0L;//左移后的机房id
    
        //41位时间戳
        private long lastTimestamp = -1L;//默认-1L
        private long initTimestamp = 1588518046057L;//初始时间戳
        private long timestampMoveBits = workerCenterIdBits + workerCenterIdMoveBits;//时间戳左移位数 = 机房id位数 + 机器id位数 + 序列号位数
    
        public SnowFlakeID(long workerCenterId , long workerId){
            if(workerCenterId < 0 || workerCenterId > maxWorkerCenterId){
                throw new IllegalArgumentException("workerCenterId is illegal");
            }
            if(workerId < 0 || workerId > maxWorkerId){
                throw new IllegalArgumentException("workerId is illegal");
            }
            this.workerCenterId = workerCenterId;
            this.workerId = workerId;
            this.workerCenterIdAfterMove = this.workerCenterId << this.workerCenterIdMoveBits;
            this.workerIdAfterMove = this.workerCenterId << this.workerCenterIdMoveBits;
        }
    
       /**
         * 生成ID的核心方法
         */
        public synchronized long nextId(){
            long currentTimestamp = timestamp();
            if(currentTimestamp < lastTimestamp){
                String s = String.format("currentTimestamp is earlier than lastTimestamp,lastTimestamp=%s,currentTimestamp=%s",lastTimestamp,currentTimestamp);
                System.out.println(s);
                //throw new RuntimeException(s);
                // 时钟回拨后手动拨正。
                // 因为依赖lastTimestamp,所以重启后第一次就发生时钟回拨的情况无法处理。
                // 可以将lastTimestamp存放到redis之类第三方缓存中,但这样生成id的效率会降低,请开发者根据实际情况去选择。
                currentTimestamp = lastTimestamp;
            }
            if(currentTimestamp == lastTimestamp){
                //同一时间戳,序列号加1
                sequence = (sequence + 1) & maxSequence;
                if(sequence == 0L){
                    //如果序列号加1后的值为0,表示当前时间戳内的序列号已用完,需要获取下一个时间戳
                    currentTimestamp = nextTimestamp(currentTimestamp);
                }
            }else{
                sequence = 0L;//不同时间戳,重置序列号
            }
            lastTimestamp = currentTimestamp;//更新成功生成id的最新时间戳
            return ((currentTimestamp - initTimestamp) << timestampMoveBits) | workerCenterIdAfterMove | workerIdAfterMove | sequence;
        }
    
        /**
         * 获取timestamp的下一毫秒数
         * @param timestamp 当前毫秒数
         * @return
         */
        public long nextTimestamp(long timestamp){
            long timestamp1 = 0L;
            do{
                timestamp1 = timestamp();
            }while (timestamp >= timestamp1);
            return timestamp1;
        }
    
        /**
         * 获取当前时间戳
         * @return
         */
        public long timestamp(){
            return System.currentTimeMillis();
        }
    
        public static void main(String[] args) throws InterruptedException {
    
         /*
            //测试并发
            Map<String,Object> map = new ConcurrentHashMap<>();
            SnowFlakeID snowFlakeID = new SnowFlakeID(1, 1);
            for(int i = 0;i < 100;i++){
                new Thread(() -> {
                    try {
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    for (int j = 0 ;j < 100000;j++){
                        map.put(String.valueOf(snowFlakeID.nextId()),1);
                    }
                }).start();;
            }
    
            for(;;){
                TimeUnit.SECONDS.sleep(1);
                System.out.println("size="+map.size());
            }
    
        */
    
    
         /*
            //测试速度
            SnowFlakeID snowFlakeID = new SnowFlakeID(1, 1);
            long startTime = System.currentTimeMillis();
            for(int i = 0;i < 3000000;i++){
                snowFlakeID.nextId();
            }
            System.out.println("耗时:"+(System.currentTimeMillis() - startTime)/1000.0d + "秒");
         */
        }
    


    作者:钢铁加鲁鲁_d59c
    链接:https://www.jianshu.com/p/d230443d0e60
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    Objective-C实用类和协议
    KVC(Key-Value-Coding)和KVO(Key-Value-Observer)
    Xcode
    IOS模拟器
    沙盒机制
    UIScrollView
    NSPredicate
    输入控件适应键盘
    10步成为专业iOS开发者——新手向,从零起步
    2015 年五大移动端设计趋势
  • 原文地址:https://www.cnblogs.com/shoshana-kong/p/14923459.html
Copyright © 2011-2022 走看看