高并发分布式下的生成id策略,雪花算法

我们在做生成id算法的时候一般都要求满足以下几点
1.全局唯一
2.趋势递增
3.单调递增
4.信息安全
5.含时间戳
基于以上要求,雪花算法是一个非常不错的选择

概述

而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移到Cassandra(由Facebook开发一套 开源分布式NoSQL数据库系统)因
为Cassandra没有顺序ID生成机制,所以开发了这样一套 全局唯一-ID生成服务。

Twitter的分布式雪花算法SnowFlake,经测试snowflake每秒能够产生26万个自增可排序的ID

  • 1、twitter的SnowFlake 生成ID能够按照时间有序生成
  • 2、SnowFlake算法生 成id的结果是一个64bit大小的整数, 为- 个Long型(转换成字符串后长度最多19)。
  • 3、分布式系统内不会产生ID碰撞(由datacenter数据中心和workerld机器id作区分)并且效率较高。

分布式系统中,有一些需要使用全局唯一-ID的场景, 生成ID的基本要求

  • 1.在分布式的环境下必须全局且唯一。
  • 2.-般都需要单调递增,因为一般唯- -ID都 会存到数据库,而Innodb的特性就是将内容存储在主键索引树上的叶子节点,而且是从左往右,递增的,所以考虑到数据库性能,一般生成的id也最好是单调递增。为了防止ID冲突可以使用36位的UUID,但是UUID有一 -些缺点, 首先他相对比较长,另外UUID一般是无序的

算法核心

雪花算法的核心组成:在这里插入图片描述

号段解析:

  • 1bit,
    不用,因为二进制中最高位是符号位,1表示负数,0表示正数。
    生成的id- - 般都是用整数,所以最高位固定为0。
    41bit-时间戳,用来记录时间戳,亳秒级。
  • 41位可以表示2^{41}-1个数字,
    如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是: 0至2^{41}-1, 减1是因为可表示的数值范围是从0开始算的,而不是1。也就是说41位可以表示2^{41}-1个毫秒的值,转化成单位年则是69.73年
  • 10bit-工作机器id,用来记录工作机器id。
    • 可以部署在2^{10} =1024个节点,包括5位datacenterld和5位workerld
    • 5位(bit) 可以表示的最大正整数是2^{5}-1=31,即可以用0、1、2、3、…1这32个数字,来表示不同的datecenterld或workerld
  • 12bit-序列号,序列号,用来记录同毫秒内产生的不同id。
    12位(bit) 可以表示的最大正整数是2^{12}-1 = 4095, 即可以用0、1、2、3、…4094这4095个数字,来表示同一-机 器同- -时间 截(亳秒)内产生的4095个ID序号。

特点

1.所有生成的id有递增趋势
2.整个分布式系统不会产生重复id

其他生成id策略的缺点

  • UUID只能保证唯一性,但是性能和效率都不高。
    原因:UUID会生成36为的字符串,8-4-4-12的形式,数据库本身压力就很大,每次新增数据需要重新维护索引,造成的性能消耗十分严重。而且UUID是无序的,无法确定生成id的时间,当然我们可以在数据库存一下创建时间
  • mysql自增,可以保证唯一性,递增,但是也会出现问题
    mysql递增id的内核是replace into,也就是数据库替换新增,先尝试插入,如果数据库的主键或者唯一索引值重复,则替换老数据。而且性能不高,容易替换掉老数据。为了避免单机故障,就要在布集群,集群要自己做自增步长和号码间断。
  • 使用redis写一个新增id策略,有不少公司是这么做的。
    这么做就会导致我们为了生成id可能要特意搭建以下redis数据库,对于配置redis也是相对来说比较麻烦的。redis和上述mysql自增一样都要设置自增步长,而且还要设置key的过期时间,当然默认是不过期。

测试使用的代码

package com.jingchu.snowflake;

/**
 * @description: 雪花算法生成id代码
 * @author: JingChu
 * @createtime :2020-08-02 14:08:01
 **/
public class MySnowflake {
    /**
     * 起始的时间戳
     */
    private final static long twepoch = 1557825652094L;

    /**
     * 每一部分占用的位数
     */
    private final static long workerIdBits = 5L;
    private final static long datacenterIdBits = 5L;
    private final static long sequenceBits = 12L;

    /**
     * 每一部分的最大值
     */
    private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    private final static long maxSequence = -1L ^ (-1L << sequenceBits);

    /**
     * 每一部分向左的位移
     */
    private final static long workerIdShift = sequenceBits;
    private final static long datacenterIdShift = sequenceBits + workerIdBits;
    private final static long timestampShift = sequenceBits + workerIdBits + datacenterIdBits;

    private long datacenterId; // 数据中心ID
    private long workerId; // 机器ID
    private long sequence = 0L; // 序列号
    private long lastTimestamp = -1L; // 上一次时间戳

    public MySnowflake(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 (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & maxSequence;
            if (sequence == 0L) {
                timestamp = tilNextMillis();
            }
        } else {
            sequence = 0L;
        }
        lastTimestamp = timestamp;

        return (timestamp - twepoch) << timestampShift // 时间戳部分
                | datacenterId << datacenterIdShift // 数据中心部分
                | workerId << workerIdShift // 机器标识部分
                | sequence; // 序列号部分
    }

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

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

    public static void main(String[] args) {
        MySnowflake mySnowflake = new MySnowflake(0,0);
        for(int i=0;i<10;i++){
            long id =mySnowflake.nextId();
            System.out.println("id: \t"+id);
        }
    }
}

结果:在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值