博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
分布式 id 生成器
阅读量:3920 次
发布时间:2019-05-23

本文共 8064 字,大约阅读时间需要 26 分钟。

公众号后台回复“学习”,获取作者独家秘制精品资料

640?wx_fmt=png

640?wx_fmt=jpeg

扫描下方海报二维码,试听课程:

640?wx_fmt=jpeg

640?wx_fmt=png

本文来源:

www.juejin.im/post/5d8882d8f265da03e369c063

在高并发或者分表分库情况下怎么保证数据id的幂等性呢?

经常用到的解决方案有以下几种。

微软公司通用唯一识别码(UUID)

这里我们要谈到snowflake算法了

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。

其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号,最后还有一个符号位,永远是0。

snowflake算法所生成的ID结构,如下图:

640?wx_fmt=png

整个结构是64位,所以我们在Java中可以使用long来进行存储。

该算法实现基本就是二进制操作,单机每秒内理论上最多可以生成1024*(2^12),也就是409.6万个ID(1024 X 4096 = 4194304)

  • 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000

  • 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0

  • 41位时间截(毫秒级)。注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)

  • 这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69

  • 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId

  • 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号

  • 加起来刚好64位,为一个Long型。

  • SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高

    经测试,SnowFlake每秒能够产生26万ID左右。

snowFlake算法的优点:

  1. 生成ID时不依赖于DB,完全在内存生成,高性能高可用。

  2. ID呈趋势递增,后续插入索引树的时候性能较好。

SnowFlake算法的缺点:

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

算法代码如下

public class SnowflakeIdWorker { // ==============================Fields==================    /** 开始时间截 (2019-08-06) */    private final long twepoch = 1565020800000L;    /** 机器id所占的位数 */    private final long workerIdBits = 5L;    /** 数据标识id所占的位数 */    private final long datacenterIdBits = 5L;    /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);    /** 支持的最大数据标识id,结果是31 */    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);    /** 序列在id中占的位数 */    private final long sequenceBits = 12L;    /** 机器ID向左移12位 */    private final long workerIdShift = sequenceBits;    /** 数据标识id向左移17位(12+5) */    private final long datacenterIdShift = sequenceBits + workerIdBits;    /** 时间截向左移22位(5+5+12) */    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;    /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */    private final long sequenceMask = -1L ^ (-1L << sequenceBits);    /** 工作机器ID(0~31) */    private long workerId;    /** 数据中心ID(0~31) */    private long datacenterId;    /** 毫秒内序列(0~4095) */    private long sequence = 0L;    /** 上次生成ID的时间截 */    private long lastTimestamp = -1L;     //==============================Constructors====================    /**     * 构造函数     * @param workerId 工作ID (0~31)     * @param datacenterId 数据中心ID (0~31)     */    public SnowflakeIdWorker(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;    }    // ==============================Methods=================================    /**     * 获得下一个ID (该方法是线程安全的)     * @return SnowflakeId     */    public synchronized long nextId() {        long timestamp = timeGen();        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常        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 + 1) & sequenceMask;            //毫秒内序列溢出            if (sequence == 0) {                //阻塞到下一个毫秒,获得新的时间戳                timestamp = tilNextMillis(lastTimestamp);            }        }        //时间戳改变,毫秒内序列重置        else {            sequence = 0L;        }        //上次生成ID的时间截        lastTimestamp = timestamp;        //移位并通过或运算拼到一起组成64位的ID        return ((timestamp - twepoch) << timestampLeftShift) //                | (datacenterId << datacenterIdShift) //                | (workerId << workerIdShift) //                | sequence;    }    /**     * 阻塞到下一个毫秒,直到获得新的时间戳     * @param lastTimestamp 上次生成ID的时间截     * @return 当前时间戳     */    protected long tilNextMillis(long lastTimestamp) {        long timestamp = timeGen();        while (timestamp <= lastTimestamp) {            timestamp = timeGen();        }        return timestamp;    }    /**     * 返回以毫秒为单位的当前时间     * @return 当前时间(毫秒)     */    protected long timeGen() {        return System.currentTimeMillis();    }    //==============================Test=============================================    /** 测试 */    public static void main(String[] args) {        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);        for (int i = 0; i < 1000; i++) {            long id = idWorker.nextId();            System.out.println(Long.toBinaryString(id));            System.out.println(id);        }    }}

快速使用snowflake算法只需以下几步

引入hutool依赖

    
cn.hutoolgroupId>    
hutool-captchaartifactId>    
${hutool.version}version>dependency>

ID 生成器

public class IdGenerator {    private long workerId = 0;    @PostConstruct    void init() {        try {            workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());            log.info("当前机器 workerId: {}", workerId);        } catch (Exception e) {            log.warn("获取机器 ID 失败", e);            workerId = NetUtil.getLocalhost().hashCode();            log.info("当前机器 workerId: {}", workerId);        }    }    /**     * 获取一个批次号,形如 2019071015301361000101237     *@param tenantId 租户ID,5 位@param module 业务模块ID,2 位@return 返回批次号public synchronized String batchId(int tenantId, int module) {return prefix + tenantId + module + RandomUtil.randomNumbers(3);@Deprecatedpublic synchronized String getBatchId(int tenantId, int module) {return batchId(tenantId, module);/**     * 生成的是不带-的字符串,类似于:b17f24ff026d40949c85a24f4f375d42     *     * @return     */public String simpleUUID() {return IdUtil.simpleUUID();/**     * 生成的UUID是带-的字符串,类似于:a5c8a5e8-df2b-4706-bea4-08d0939410e3     *     * @return     */public String randomUUID() {return IdUtil.randomUUID();private Snowflake snowflake = IdUtil.createSnowflake(workerId, 1);public synchronized long snowflakeId() {return snowflake.nextId();public synchronized long snowflakeId(long workerId, long dataCenterId) {return snowflake.nextId();/**     * 生成类似:5b9e306a4df4f8c54a39fb0c     * 	     * ObjectId 是 MongoDB 数据库的一种唯一 ID 生成策略,     * 是 UUID version1 的变种,详细介绍可见:服务化框架-分布式 Unique ID 的生成方法一览。     *     * @return     */    public String objectId() {        return ObjectId.next();    }}
public class IdGeneratorTest {    @Autowired    private IdGenerator idGenerator;    @Test    public void testBatchId() {        for (int i = 0; i < 100; i++) {            String batchId = idGenerator.batchId(1001, 100);            log.info("批次号: {}", batchId);        }    }    @Test    public void testSimpleUUID() {        for (int i = 0; i < 100; i++) {            String simpleUUID = idGenerator.simpleUUID();            log.info("simpleUUID: {}", simpleUUID);        }    }    @Test    public void testRandomUUID() {        for (int i = 0; i < 100; i++) {            String randomUUID = idGenerator.randomUUID();            log.info("randomUUID: {}", randomUUID);        }    }    @Test    public void testObjectID() {        for (int i = 0; i < 100; i++) {            String objectId = idGenerator.objectId();            log.info("objectId: {}", objectId);        }    }    @Test    public void testSnowflakeId() {        ExecutorService executorService = Executors.newFixedThreadPool(20);        for (int i = 0; i < 20; i++) {            executorService.execute(() -> {                log.info("分布式 ID: {}", idGenerator.snowflakeId());            });        }        executorService.shutdown();    }}	@Autowired private IdGenerator idGenerator;即可	order.setId(idGenerator.snowflakeId() + "");

转载地址:http://uaern.baihongyu.com/

你可能感兴趣的文章
.NET Core 3.0 即将结束生命周期,建议迁移 3.1
查看>>
开源、免费、企业级的SiteServer CMS .NET CORE 7.0 预览版发布
查看>>
基于.NET下的人工智能|利用ICSharpCore搭建基于.NET Core的机器学习和深度学习的本地开发环境...
查看>>
【朝夕Net社区技术专刊】Core3.1 WebApi集群实战专题---WebApi环境搭建运行发布部署篇...
查看>>
200行代码,7个对象——让你了解ASP.NET Core框架的本质[3.x版]
查看>>
.NET Core开发实战(第21课:中间件:掌控请求处理过程的关键)--学习笔记(下)...
查看>>
对比Java和.NET多线程编程
查看>>
[头脑风暴] 解读Docker Bridge网络模型
查看>>
集成平台集群任务动态分派
查看>>
【.net core】电商平台升级之微服务架构应用实战
查看>>
【翻译】.NET 5 Preview 1 发布
查看>>
使用GUI工具Portainer.io管控Docker容器
查看>>
Abp vNext发布v2.3!
查看>>
.NET Core开发实战(第27课:定义Entity:区分领域模型的内在逻辑和外在行为)--学习笔记...
查看>>
BeetleX之vue-autoui自匹配UI插件
查看>>
.NET Core开发实战(第28课:工作单元模式(UnitOfWork):管理好你的事务)--学习笔记...
查看>>
如何用 Blazor 实现 Ant Design 组件库?
查看>>
DotNetCore Web应用程序中的Session管理
查看>>
从业务需求抽象成模型解决方案
查看>>
Kafka
查看>>