【微服务之分布式全局Id】分布式全局ID生成

news2025/1/20 1:11:48

分布式全局ID解决方案

1、UUID

最容易想到的就是 UUID (Universally Unique Identifier) 了, UUID 的标准型式包含 32 个 16 进制数字,以连字号分为五段,形式为 8-4-4-4-12 的 36 个字符,这个是 Java 自带的,用着也简单,最大的优势就是本地生成,没有网络消耗。

1、字符串太长,对于 MySQL 而言,不利于索引。

2、UUID 的随机性对于 I/O 密集型的应用非常不友好!「它会使得聚簇索引的插入变得完全随机,使得数据没有任何聚集特性。」

3、信息不安全:基于 MAC 地址生成 UUID 的算法可能会造成 MAC 地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。

2、雪花算法SNOWFLAKE 

雪花算法是由 Twitter 公布的分布式主键生成算法,它能够保证不同进程主键的不重复性,以及相同进程主键的有序性。在同一个进程中,它首先是通过时间位保证不重复,如果时间相同则是通过序列位保证。

用法如下:

IdWorker idWorker = new IdWorker(0, 0);
for (int i = 0; i < 1000; i++) {
    System.out.println(idWorker.nextId());
}
public class IdWorker {
    // 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
    private final static long twepoch = 1288834974657L;
    // 机器标识位数
    private final static long workerIdBits = 5L;
    // 数据中心标识位数
    private final static long datacenterIdBits = 5L;
    // 机器ID最大值
    private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
    // 数据中心ID最大值
    private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    // 毫秒内自增位
    private final static long sequenceBits = 12L;
    // 机器ID偏左移12位
    private final static long workerIdShift = sequenceBits;
    // 数据中心ID左移17位
    private final static long datacenterIdShift = sequenceBits + workerIdBits;
    // 时间毫秒左移22位
    private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
 
    private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
    /* 上次生产id时间戳 */
    private static long lastTimestamp = -1L;
    // 0,并发控制
    private long sequence = 0L;
 
    private final long workerId;
    // 数据标识id部分
    private final long datacenterId;
 
    public IdWorker(){
        this.datacenterId = getDatacenterId(maxDatacenterId);
        this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
    }
 
    /**
     * @param workerId
     *            工作机器ID
     * @param datacenterId
     *            序列号
     */
    public IdWorker(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;
    }
 
    /**
     * 获取下一个ID
     *
     * @return
     */
    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) {
            // 当前毫秒内,则+1
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                // 当前毫秒内计数满了,则等待下一秒
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }
        lastTimestamp = timestamp;
        // ID偏移组合生成最终的ID,并返回ID
        long nextId = ((timestamp - twepoch) << timestampLeftShift)
                | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift) | sequence;
 
        return nextId;
    }
 
    private long tilNextMillis(final long lastTimestamp) {
        long timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }
 
    private long timeGen() {
        return System.currentTimeMillis();
    }
 
    /**
     * <p>
     * 获取 maxWorkerId
     * </p>
     */
    protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
        StringBuffer mpid = new StringBuffer();
        mpid.append(datacenterId);
        String name = ManagementFactory.getRuntimeMXBean().getName();
        if (!name.isEmpty()) {
            /*
             * GET jvmPid
             */
            mpid.append(name.split("@")[0]);
        }
        /*
         * MAC + PID 的 hashcode 获取16个低位
         */
        return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
    }
 
    /**
     * <p>
     * 数据标识id部分
     * </p>
     */
    protected static long getDatacenterId(long maxDatacenterId) {
        long id = 0L;
        try {
            InetAddress ip = InetAddress.getLocalHost();
            NetworkInterface network = NetworkInterface.getByInetAddress(ip);
            if (network == null) {
                id = 1L;
            } else {
                byte[] mac = network.getHardwareAddress();
                id = ((0x000000FF & (long) mac[mac.length - 1])
                        | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
                id = id % (maxDatacenterId + 1);
            }
        } catch (Exception e) {
            System.out.println(" getDatacenterId: " + e.getMessage());
        }
        return id;
    }
}

3、 LEAF

Leaf 是美团开源的分布式 ID 生成系统,最早期需求是各个业务线的订单 ID 生成需求。在美团早期,有的业务直接通过 DB 自增的方式生成 ID,有的业务通过 Redis 缓存来生成 ID,也有的业务直接用 UUID 这种方式来生成 ID。以上的方式各自有各自的问题,因此美团决定实现一套分布式 ID 生成服务来满足需求目前 Leaf 覆盖了美团点评公司内部金融、餐饮、外卖、酒店旅游、猫眼电影等众多业务线。在4C8G VM 基础上,通过公司 RPC 方式调用,QPS 压测结果近 5w/s,TP999 1ms(TP=Top Percentile,Top 百分数,是一个统计学里的术语,与平均数、中位数都是一类。TP50、TP90 和 TP99 等指标常用于系统性能监控场景,指高于 50%、90%、99% 等百分线的情况)。目前 LEAF 的使用有两种不同的思路,号段模式和 SNOWFLAKE 模式,你可以同时开启两种方式,也可以指定开启某种方式(默认两种方式为关闭状态)
 

4、reids生成

  1. 利用了 redis 单线程,保证原子性,不依赖数据库,生成id可以自增,天然排序等特性
  2. 注意点redis集群时多个写库,注意设置步长,与生成的id起始值
  3. 利用 RedisAtomicLong 调用 RedisAtomicLong () 方法

例:

生成订单号:一般需要存在Long类型中,正好Long类型是64位,所以将第一位永远设置成0,表示正数。后面31位表示时间戳,可以表示的数字为2的31次方(0-2147483648),单位秒,再后面的32位可以表示成2的32次方的订单号(0-4294967296)。这种思想主要是借鉴雪花算法的原理。

解释
1.符号位:1bit,永远为0,表示正数
2.时间戳:31bit,最大2147483648秒,大概69年
3.序列号:32bit,最大4294967296,表示一秒中内能生成的不同的订单数(接近43亿)
一般一秒中能产生43亿个不一样的订单号,基本满足各种电商场景了。

java代码实现

@Component
public class RedisIdMaker {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 时间戳开始时间,从2022年1月1号0点0时0分开始
     */
    private static final Long START_TIME = 1640995200L;

    /**
     * 订单生成数量  每天最多2的31次方个订单数量
     */
    private static final int COUNT_BITS = 32;

    private static final String ORDER_COUNT_KEY = "order:";


    /**
     * 根据redis生成唯一订单号
     *
     * @return
     */
    public Long generateNextId() {
        // 获取当前时间
        LocalDateTime now = LocalDateTime.now();
        long currentStamp = now.toEpochSecond(ZoneOffset.UTC);
        // 获取当前时间戳(秒)
        long timeStamp = currentStamp - START_TIME;
        // 组装成key=order:2022:01:01(组装成这种形式方便日后根据日期统计当天的订单数量)
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm"));
        String redisKey = ORDER_COUNT_KEY + date;
        // 订单自增长
        long orderCount = stringRedisTemplate.opsForValue().increment(redisKey);
        // 返回唯一订单号(拼接而来的)
        return timeStamp << COUNT_BITS | orderCount;
    }

    /**
     * 获取2022年1月1号0点0时0分的时间戳
     * @param args
     */
    public static void main(String[] args) {
        LocalDateTime startLocalTime = LocalDateTime.of(2022, 1, 1, 0, 0, 0);
        long startTime = startLocalTime.toEpochSecond(ZoneOffset.UTC);
        System.out.println(startTime);
        LocalDateTime now = LocalDateTime.now();

        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm"));
        System.out.println(date);
    }
}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/93077.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DBCO-PEG-Silane|DBCO-PEG-SIL|二苯并环辛炔-聚乙二醇-硅烷

DBCO-PEG-Silane&#xff0c;DBCO 试剂是一类点击化学标记试剂&#xff0c;含有非常活泼的 DBCO&#xff08;&#xff08;二苯并环辛炔&#xff09;基团&#xff0c;DBCO 试剂可以通过无铜点击化学与叠氮化物标记的分子或生物分子发生反应。DBCO 点击化学可以在水性缓冲液中运行…

用噪点滤镜回忆童年电视机的雪花屏

介绍 相信很多人80,90后的同学对童年里电视机的突然出现刺啦刺啦的雪花屏记忆犹新&#xff0c;本期将用 pixi.js 来完成一个电视机播放动漫然后突然出现雪花屏的动画&#xff0c;里面主要讲解了如何使用pixi.js播放帧动画和如何用噪点滤镜制造雪花屏。 演示 正文 初始化渲染…

web前端网页设计期末课程大作业:关于城市旅游的HTML网页设计 ——北京

&#x1f468;‍&#x1f393;学生HTML静态网页基础水平制作&#x1f469;‍&#x1f393;&#xff0c;页面排版干净简洁。使用HTMLCSS页面布局设计,web大学生网页设计作业源码&#xff0c;这是一个不错的旅游网页制作&#xff0c;画面精明&#xff0c;排版整洁&#xff0c;内容…

微服务框架 SpringCloud微服务架构 多级缓存 48 多级缓存 48.2 OpenResty 快速入门

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 多级缓存 文章目录微服务框架多级缓存48 多级缓存48.2 OpenResty 快速入门48.2.1 直接开干48 多级缓存 48.2 OpenResty 快速入门 48.2.1 直…

Linux(三) makefile与gdb调试

makefile mkefile文件中定义了一系列的规则来指定&#xff0c;哪些文件需要线编译&#xff0c;哪些后编译&#xff0c;哪些需要重新编译&#xff0c;甚至进行更复杂的功能操作&#xff0c;因为makefile就像一个Shell脚本一样&#xff0c;其中也可以执行操作系统的命令。 mkef…

java计算机毕业设计基于安卓Android的教学考勤系统APP

项目介绍 首先,论文一开始便是清楚的论述了系统的研究内容。其次,剖析系统需求分析,弄明白“做什么”,分析包括业务分析和业务流程的分析以及用例分析,更进一步明确系统的需求。然后在明白了系统的需求基础上需要进一步地设计系统,主要包罗软件架构模式、整体功能模块、数据库设…

4个表格1个工具,解决客户的分类及管理

1897年&#xff0c;意大利经济学者帕累托发现&#xff1a;“社会上20%的人占有80%的社会财富”。 后来这一法则被发现可以适用到很多领域&#xff0c;包括客户管理。具体解释为“一家企业80%的收益来源于20%的客户”&#xff0c;即20%客户创造了企业80%的收益。 由于20%的客户…

分布式事务处理方案大 PK

[toc] 说好了写 TienChin 项目的&#xff0c;最近这个分布式事务算是一个支线任务吧&#xff0c;今天是再来一个短篇和小伙伴们总结一下分布式事务。 首先先说一个大原则&#xff1a;分布式事务能不用就不要用&#xff0c;毕竟这个用起来还是有一些麻烦的。当然&#xff0c;不…

B/S端界面控件DevExtreme内置的图标库介绍

DevExtreme拥有高性能的HTML5 / JavaScript小部件集合&#xff0c;使您可以利用现代Web开发堆栈&#xff08;包括React&#xff0c;Angular&#xff0c;ASP.NET Core&#xff0c;jQuery&#xff0c;Knockout等&#xff09;构建交互式的Web应用程序&#xff0c;该套件附带功能齐…

闯关

我回来啦&#xff01;停更的几个月&#xff0c;我生了个娃。6.7斤的小虎妞&#xff0c;健健康康、白白胖胖。她现在已经五个多月了&#xff0c;能抬头、会翻身、会咯咯咯地笑、能拿住小玩具、还能摘我的眼镜……过程挺曲折的。不夸张地说&#xff0c;鬼门关溜达了一圈。好在&am…

邀请函小程序开发,减少设计制作局限性

随着社交平台和互联网技术的发展&#xff0c;很多产品的类型都在不断地发生着改变&#xff0c;就连邀请产品现在都出现了电子版的邀请函&#xff0c;颠覆了我们对于传统纸质邀请函的认知。无论是在日常生活学习还是工作中我们都会用到邀请函&#xff0c;而现在越来越多的人倾向…

Android逆向中常用工具和命令

Android逆向中常用工具和命令 Wifi ADB Google Store wifi ADB 当没有数据线时&#xff0c;可以开启wifiADB adb connect 192.168.0.101:5555开始食用 android 运行shell命令 https://github.com/termux/termux-app/releases ADB shell 截图 vim ~/.bash_profile curre…

CAD怎么添加打印机设备?CAD打印机添加步骤

CAD打印时未在系统中找到合适的打印机该怎么办呢&#xff1f;CAD怎么添加打印机设备&#xff1f;本文小编就以浩辰CAD软件为例来给大家分享一下CAD添加打印机设备的详细操作步骤吧&#xff01; CAD添加打印机设备步骤&#xff1a; 首先在浩辰CAD中打开图纸文件&#xff0c;然后…

Speckle+IFC.js:开源BIM

2021年12 月底&#xff0c;我有机会参加了一个关于两个开源工具的网络研讨会&#xff1a;Speckle 和 IFC.js。 该网络研讨会是由一个名为 Agile BIM 的社区组织的&#xff0c;我对此也一无所知。 然而&#xff0c;我所知道的事实是开源软件开发在任何领域的重要性&#xff0c;开…

C#基于ASP.NET的社区人口管理系统

论文阐述了社区人口信息管理系统的设计与实现&#xff0c;并对该系统的需求分析及系统需要实现的设计方法作了介绍。该系统的基本功能包括用户登录&#xff0c;管理员信息管理&#xff0c;常住人口管理&#xff0c;迁出人口信息管理&#xff0c;迁入人口信息管理&#xff0c;查…

Docker安装RabbitMq延迟队列插件

// todo 文章目录一&#xff1a;下载延迟队列插件1. 地址2. 把刚刚下载的插件拖拽至虚拟机中二&#xff1a; 进入容器执行延迟队列插件1. 延迟插件拷贝到容器内部2. 进入容器&#xff0c;让插件生效3. 再次查看交换机类型一&#xff1a;下载延迟队列插件 1. 地址 &#xff1a…

20221216英语学习

今日新词&#xff1a; duplicate v.复制; 被复制; 复写; 复印; 重复; 使加倍; 使成双 September n.九月 scandal n.丑事&#xff0c;丑闻&#xff0c;丑行 considerate adj.考虑周到的&#xff0c;体贴的&#xff0c;体谅的 report n.报告, 报道, 汇报, 调查报告 across …

前端实现分页打印(一)

实现页面为结算单&#xff0c;也页面由固定头部&#xff0c;订单信息&#xff0c;产品列表&#xff0c;金额汇总&#xff0c;订单明细 其中产品列表需要动态计算分压&#xff0c;订单明细由于存在多个子单&#xff0c;订单收费项目可配置化&#xff0c;导致也存在多个分页需要处…

Apereo Cas在项目中接入

1.介绍 Apereo CAS的一个功能就是单点登录&#xff0c;统一的登录登出接口与页面&#xff0c;让系统中的模块只需要关注在业务点&#xff0c;而把安全认证的功能交给统一认证来做。所以客户端的集成主要是单点登录的集成&#xff0c;客户端指定需要做安全认证的页面&#xff0…

8年软件测试工程师感悟—我亲身经历的2022年软件质量工作

这两天和朋友谈到软件测试的发展&#xff0c;其实软件测试已经在不知不觉中发生了非常大的改变&#xff0c;前几年的软件测试行业还是一个风口&#xff0c;随着不断地转行人员以及毕业的大学生疯狂地涌入软件测试行业&#xff0c;目前软件测试行业“缺口”已经基本饱和。当然&a…