分布式系统中获取唯一ID原来这么简单?

目录

一、分布式系统中常见的需要获取唯一ID的场景

二、获取唯一ID的方式

三、SnowFlake 雪花算法

四、代码实现


一、分布式系统中常见的需要获取唯一ID的场景

  • 微服务中多个服务完成业务逻辑,需要保持某个业务字段唯一
  1. 订单模块,多个服务配合完成整套业务,包括用户,库存等等。订单ID需要唯一的业务单号。
  • 分表分表,保证主键唯一。

二、获取唯一ID的方式

  •   使用UUID获取全局唯一的标识码,32位标识码  

  缺点:完全无序,作为数据库唯一标识码,数据库B+Tree结构,节点可能在还没饱和的时候就分裂,无法有效的利用节点,大大的降低了数据的插入效率。

  • 数据库自增主键。

假设名为table的表有如下结构:

id        feild

35        a

每一次生成ID的时候,访问数据库,执行下面的语句:

    begin;

    REPLACE INTO table ( feild )  VALUES ( 'a' );

    SELECT LAST_INSERT_ID();

    commit;

这样每次都可以获取最新的ID作为标识,但是这样也加大了数据的单表的压力,一张表承载了全部主键生成机制,万一数据库死掉,整个业务也就崩盘。为了减轻数据库的压力,可以分库分表。比如,后端通过代理动态的取获取主键,分A,B,C三个库,每个库有个主键生成表,A:begin:1,add:3   B: begin:2,add:3, C:begin:3,add:3,这样 A : 1,4,7  B : 2,5,8  C:3,6,9 可以减轻数据库压力,也可以获取不冲突的主键ID。

三、SnowFlake 雪花算法

  •  SnowFlake算法的优点:

        1.生成ID时不依赖于DB,完全在内存生成,高性能高可用。
        2.ID呈趋势递增,后续插入索引树的时候性能较好。

  • SnowFlake算法的缺点:

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

  • 应用场景 

        1、数据库表主键:很多DBA在大型生产应用禁用auto_increment的ID,这时可以选snowflake替代。 
        2、TraceId:分布式系统追踪,希望用一个ID贯穿所有子系统来追踪分布式交互过程。也有系统产生一个Exception,我们 需要对Exception编号等。 
        3、摇一摇/抢红包ID:摇一摇的特点是活动促销的时候,短时间内访问特别大,需要一个高性能的ID生成器。

     

  • SnowFlake所生成的ID一共分成四部分:

    1.第一位:占用1bit,其值始终是0,没有实际作用。

    2.时间戳 : 占用41bit,精确到毫秒,一般实现上不会存储当前的时间戳,而是时间戳的差值(当前时间-固定的开始时间),这样可以使产生的ID从更小值开始;41位的时间戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年

    3.工作机器id: 占用10bit,其中高位5bit是数据中心ID(datacenterId),低位5bit是工作节点ID(workerId),做多可以容纳1024个节点。

    4.序列号: 占用12bit,这个值在同一毫秒同一节点上从0开始不断累加,最多可以累加到4095。

四、代码实现


public class SnowFlakeUtils {
    // 起始的时间戳
    private final static long START_STMP = 1480166465631L;
    // 每一部分占用的位数,就三个
    private final static long SEQUENCE_BIT = 12;// 序列号占用的位数
    private final static long MACHINE_BIT = 5; // 机器标识占用的位数
    private final static long DATACENTER_BIT = 5;// 数据中心占用的位数
    // 每一部分最大值
    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
    // 每一部分向左的位移
    private final static long MACHINE_LEFT = SEQUENCE_BIT;
    private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
    private long datacenterId; // 数据中心
    private long machineId; // 机器标识
    private long sequence = 0L; // 序列号
    private long lastStmp = -1L;// 上一次时间戳
 
    public SnowFlakeUtils(long datacenterId, long machineId) {
        if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
            throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
        }
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
        }
        this.datacenterId = datacenterId;
        this.machineId = machineId;
    }
    //产生下一个ID
    public synchronized long nextId() {
        long currStmp = getNewstmp();
        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过  这个时候应当抛出异常
        if (currStmp < lastStmp) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
        }
 
        if (currStmp == lastStmp) {
            //if条件里表示当前调用和上一次调用落在了相同毫秒内,只能通过第三部分,序列号自增来判断为唯一,所以+1.
            sequence = (sequence + 1) & MAX_SEQUENCE;
            //同一毫秒的序列数已经达到最大,只能等待下一个毫秒
            if (sequence == 0L) {
                currStmp = getNextMill();
            }
        }
        //时间戳改变,毫秒内序列重置
        else {
            //不同毫秒内,序列号置为0
            //执行到这个分支的前提是currTimestamp > lastTimestamp,说明本次调用跟上次调用对比,已经不再同一个毫秒内了,这个时候序号可以重新回置0了。
            sequence = 0L;
        }
 
        lastStmp = currStmp;
        //就是用相对毫秒数、机器ID和自增序号拼接
        //移位  并通过  或运算拼到一起组成64位的ID
        return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分
                | datacenterId << DATACENTER_LEFT      //数据中心部分
                | machineId << MACHINE_LEFT            //机器标识部分
                | sequence;                            //序列号部分
    }
 
    private long getNextMill() {
        long mill = getNewstmp();
        //使用while循环等待直到下一毫秒。
        while (mill <= lastStmp) {
            mill = getNewstmp();
        }
        return mill;
    }
 
    private long getNewstmp() {
        return System.currentTimeMillis();
    }


  public static void main(String[] args) {
        // 构造方法设置机器码:第9个机房的第20台机器
        SnowFlakeUtils  snowFlake = new SnowFlakeUtils(9, 20);
        //循环生成2^12个ID
        for(int i =0; i <(1<< 12); i++){
            System.out.println(snowFlake.nextId());
        }
  }


 
}


 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
V3.0 Build 080102 释放时间:08.01.02 10:04 更新说明: 1)新架构引入节点概念进行模块高级组合部署,以前的模块即可以独立部署,也可以一个节点下部署多个模块混合使用,实现一个节点频道需要多种内容模型组成的特殊应用; 2)重新设计研发会员心,新的会员心比以往版本更灵活,可以部署到前使用。同时优化会员心程序降低系统负载; 3)重新设计研发会员类型,管理员将可以无限自定义会员类别及类别所需要的特殊属性; 4)完善会员积分功能; 5)完善会员组功能,每种会员组可以划分权限到某节点或者模块; 6)改善优化了模块安装; 7)优化安装程序,支持多种字符集安装; BBWPS系统功能介绍: 1、真正意义的平系统 BBWPS平做为整个系统核心,可无限扩展/接纳平功能及应用模块功能;平与应用模块完全分离,给您充分的自我管理权限。 支持跨操作系统(如Windows、Linux、FreeBSD、Solaris等)部署,推荐使用FreeBSD/Linux+PHP+Mysql环境,既能拥有高效的系统级功能,又无需承担购买底层系统费用,更无底层系统软件版权纠纷。 2、应用模块“随需安装” 基于BBWPS平领先的设计理念和技术优势,独家做到任何一个应用模块与系统平完全分离,您可根据自己的需要选择安装应用模块,开创应用模块“随需安装”新体验;干净的平让您无需为多余不用的模块烦恼,多达30余个官方开发的应用模块供您部署,同时将开放系统平接口,支持第三方伙伴开发的功能模块部署使用。 各模块均支持设置独立域名访问,支持模块复制,且2.0版本以后的平不再需要修改模块配置文件即可完成模块复制安装。 3、高负载性能 1)系统级高级设计 BBWPS系统除支持一服务器/空间安装所有模块的原始部署,更创新开发系统平与应用模块分布式和嵌套式两种高级部署;您可用多个服务器联合部署BBWPS系统,大幅度提升系统负载性能,。若一个系统不具有分步式部署功能,可承载的访问量根本不足以做门户级网站应用。无论是集部署、分步式还是嵌套式部署,依然只需要一个管理后即可管理所有模块。 2)数据库级高级设计 自BBWPS系统第一版发布起,各应用模块就采用了独立数据表设计;该设计方法能极大的提升数据库的响应效率,35万条数据、大小达到460M的单张数据表,依然保持高速的查询响应。 3)程序级高级设计 自BBWPS系统第一版发布起,各应用模块就设计了高速缓冲系统;通过该缓冲系统,大幅度减少数据库的频繁请求,稳定、快速的支持系统对外提供高速访问服务。 4、数据通讯 BBWPS系统支持平与应用模块、各应用模块之间彼此进行高速数据通讯,达到数据交换功能;比如网站首页自定义提取各应用模块数据、应用模块页面显示其他模块信息等,其数据交换能力及效率远非目前采用JS脚本做数据通讯的方式可比。 各模块交换数据以后,可自定义模版控制显示效果,而不受数据原始模块模版控制,满足自由交换自由显示。 5、安全机制 1)通讯安全 BBWPS系统自2.0版本起,完全自己设计安全校验机制,不采用PHP程序自身的安全校验功能;管理员可设置通讯密钥,结合随机N次加密,达到每个BBWPS系统安装后的通讯机制都是唯一的。只要修改默认通信密钥,官方也无法破解您网站的通讯控制信息。 2)灵活的验证码功能 系统自动支持普通阿拉伯数字和图片数字2种方式的验证码机制,如果使用图片数字验证码功能,您还可以自己设计数字图片,以和官方标准程序有所区别。进一步提高验证码破解难度,防止外部软件恶意注册/登录。 验证码图片目录位于:bbwps目录/function/img/,文件:0.gif~9.gif,图片大小:建议不超过18*25像素。 3)支持修改管理后地址,只有“自己人”知道后管理入口。 6、强大的会员系统 1)支持自定义会员类别功能,如您可以定义会员类别为个人、商户、介等类别;用户注册以后所选择类别自动与相应会员组关联获取该会员组权限。 会员类别数量无限制。 2)首创智能无级虚拟会员组功能,管理员只需要在系统平定义会员组,可自动识别各应用模块的子会员组设置,进行组合设置。例如文章模块有特权会员:WZVIP1和WZVIP2,分类信息模块有特权会员:FLVIP1,房产模块有特权会员;FCVIP1,管理员可设置系统平会员组VIP1组合WZVIP1、FLVIP1特权,VIP2组合WZVIP2、FCVIP1特权,达到不同的会员组可拥有多个模块下不同的特权会员功能。同时会员管理更方便,管理员无需到各模块下管理特权会员,只需在系统平管理即可。 平会员组数量无限制。 本特色功能在第三方伙伴开发模块时可大量降低会员功能方面的开发时间。 3)自带通行证功能,任何应用模块安装以后能非常方便的继承平会员信息,充分保证您网站任何阶段拥有的会员信息不被浪费;完美整合Discuz!、PHPWind论坛,会员采取注册实时同步方式;保证后续取消整合功能后会员数据不受任何一方系统约束; 通行证支持跨域名,会员在任何一个模块域名下登录后,所有模块均接收/认可该会员登录状态;无论模块是集安装还是分布式多域名安装均完美支持。 4)支持站内短消息 系统后可设置:是否开启站内短消息功能、信箱容量、语音提示次数及声音类别 系统后可向会员群发短消息 会员前可设置好友认证方式、资料查看授权 会员前可向好友、陌生会员ID发送短消息,提供草稿箱、发件箱、垃圾箱消息管理,提供消息发送后状态跟踪,提供通讯录/黑名单管理 5)支持会员邮件群发 BBWPS系统利用SMTP功能外发邮件,支持邮件群发,支持读取外部地址列表群发邮件;同时BBWPS提供读取外部会员数据库自动产生地址列表功能。 7、支持会员积分功能,可与第三方在线支付系统接口;提供虚拟银行系统,支持虚拟充值卡购卡/充值业务。 8、强大的广告发布/管理系统,支持代码、文字、图片、Flash 4种类型;通过定义广告标签即可将广告显示到网站任何页面/位置。 9、支持网站首页自定义,允许设置网站首页地址为系统大首页、各模块首页,或自定义URL地址/页面;提供全站Google地图生成。 10、支持网站流量统计。 ---------- 其他30余个应用模块请查看各自模块功能介绍 ---------- 安装使用: 安装其他应用模块,必须先装平系统;请对照安装说明完成安装。
什么是Zookeeper Zookeeper是一个分布式开源框架,提供了协调分布式应用的基本服务,它向外部应用暴露一组通用服务——分布式同步(Distributed Synchronization)、命名服务(Naming Service)、集群维护(Group Maintenance)等,简化分布式应用协调及其管理的难度,提供高性能的分布式服务。ZooKeeper本身可以以单机模式安装运行,不过它的长处在于通过分布式ZooKeeper集群(一个Leader,多个Follower),基于一定的策略来保证ZooKeeper集群的稳定性和可用性,从而实现分布式应用的可靠性。 1、Zookeeper是为别的分布式程序服务的 2、Zookeeper本身就是一个分布式程序(只要有半数以上节点存活,zk就能正常服务) 3、Zookeeper所提供的服务涵盖:主从协调、服务器节点动态上下线、统一配置管理、分布式共享锁、统> 一名称服务等 4、虽然说可以提供各种服务,但是zookeeper在底层其实只提供了两个功能: 管理(存储,读取)用户程序提交的数据(类似namenode存放的metadata);  并为用户程序提供数据节点监听服务; Zookeeper集群机制 Zookeeper集群的角色: Leader 和 follower  只要集群有半数以上节点存活,集群就能提供服务 Zookeeper特性 1、Zookeeper:一个leader,多个follower组成的集群 2、全局数据一致:每个server保存一份相同的数据副本,client无论连接到哪个server,数据都是一致的 3、分布式读写,更新请求转发,由leader实施 4、更新请求顺序进行,来自同一个client的更新请求按其发送顺序依次执行 5、数据更新原子性,一次数据更新要么成功,要么失败 6、实时性,在一定时间范围内,client能读到最新数据 Zookeeper数据结构 1、层次化的目录结构,命名符合常规文件系统规范(类似文件系统)    2、每个节点在zookeeper叫做znode,并且其有一个唯一的路径标识  3、节点Znode可以包含数据和子节点(但是EPHEMERAL类型的节点不能有子节点) 节点类型  a、Znode有两种类型: 短暂(ephemeral)(create -e /app1/test1 “test1” 客户端断开连接zk删除ephemeral类型节点)  持久(persistent) (create -s /app1/test2 “test2” 客户端断开连接zk不删除persistent类型节点) b、Znode有四种形式的目录节点(默认是persistent ) PERSISTENT  PERSISTENT_SEQUENTIAL(持久序列/test0000000019 )  EPHEMERAL  EPHEMERAL_SEQUENTIAL c、创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护          d、在分布式系统,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序 Zookeeper应用场景 数据发布与订阅(配置心) 发布与订阅模型,即所谓的配置心,顾名思义就是发布者将数据发布到ZK节点上,供订阅者动态获取数据,实现配置信息的集式管理和动态更新。例如全局的配置信息,服务服务框架的服务地址列表等就非常适合使用。 负载均衡 这里说的负载均衡是指软负载均衡。在分布式环境,为了保证高可用性,通常同一个应用或同一服务的提供方都会部署多份,达到对等服务。而消费者就须要在这些对等的服务选择一个来执行相关的业务逻辑,其比较典型的是消息间件的生产者,消费者负载均衡。 消息间件发布者和订阅者的负载均衡,linkedin开源的KafkaMQ和阿里开源的 metaq都是通过zookeeper来做到生产者、消费者的负载均衡。这里以metaq为例如讲下: 生产者负载均衡:metaq发送消息的时候,生产者在发送消息的时候必须选择一broker上的一个分区来发送消息,因此metaq在运行过程,会把所有broker和对应的分区信息全部注册到ZK指定节点上,默认的策略是一个依次轮询的过程,生产者在通过ZK获取分区列表之后,会按照brokerId和partition的顺序排列组织成一个有序的分区列表,发送的时候按照从头到尾循环往复的方式选择一个分区来发送消息。 消费负载均衡: 在消费过程,一个消费者会消费一个或多个分区
生成年月日加6位流水唯一 ID,可以考虑使用 Snowflake 算法。Snowflake 算法是 Twitter 开源的分布式 ID 生成算法,使用一个 64 位的 long 型数字作为全局唯一 ID。具体实现可以使用 Java 来完成。 在 Java ,可以使用 Snowflake 算法实现一个分布式 ID 生成器,具体步骤如下: 1. 定义一个 Snowflake 类,包含以下属性: - 起始的时间戳(epoch) - 机器 ID - 序列号 2. 在 Snowflake 类实现一个 nextId() 方法,该方法包含以下步骤: - 获取当前时间戳(毫秒级) - 如果当前时间戳小于上一次生成 ID 的时间戳,则说明系统时钟回退过,抛出异常 - 如果当前时间戳等于上一次生成 ID 的时间戳,则将序列号加 1 - 如果当前时间戳大于上一次生成 ID 的时间戳,则将序列号重置为 0,并更新上一次生成 ID 的时间戳为当前时间戳 - 生成一个 64 位的 long 型数字,其高位为当前时间戳,间位为机器 ID,低位为序列号 - 返回生成的 ID 3. 在应用程序使用 Snowflake 类的 nextId() 方法生成唯一 ID。 由于 Snowflake 算法生成的 ID 包含时间戳信息,因此可以保证生成的 ID 有序递增。同时,Snowflake 算法使用了位运算和异或运算,可以保证在分布式环境下生成的 ID 不重复。 如果要使用 Snowflake 算法实现分布式并发,需要注意以下几点: - 每个机器的机器 ID 必须唯一 - 同一机器上的线程调用 nextId() 方法时,需要考虑线程安全问题 - 不同机器上的应用程序调用 nextId() 方法时,需要考虑网络延迟和时钟不同步问题 以上是使用 Java 实现年月日加6位流水唯一 ID 的思路,具体实现可以参考 Snowflake 算法的开源实现。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值