博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
分布式ID
阅读量:6962 次
发布时间:2019-06-27

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

1. 基本解决方案

1.使用数据库自增Id2.使用redis的incr命令3.使用UUID4.Twitter的snowflake算法5.利用zookeeper生成唯一ID6.MongoDB的ObjectId复制代码

1. 数据库自增id

最常见的方式。利用数据库,全数据库唯一。

优点:

1)简单,代码方便,性能可以接受。

2)数字ID天然排序,对分页或者需要排序的结果很有帮助。

缺点:

1)不同数据库语法和实现不同,数据库迁移的时候或多数据库版本支持的时候需要处理。

2)在单个数据库或读写分离或一主多从的情况下,只有一个主库可以生成。有单点故障的风险。

3)在性能达不到要求的情况下,比较难于扩展。

4)如果遇见多个系统需要合并或者涉及到数据迁移会相当痛苦。

5)分表分库的时候会有麻烦。

优化方案:

1)针对主库单点,如果有多个Master库,则每个Master库设置的起始数字不一样,步长一样,可以是Master的个数。比如:Master1 生成的是 1,4,7,10,Master2生成的是2,5,8,11 Master3生成的是 3,6,9,12。这样就可以有效生成集群中的唯一ID,也可以大大降低ID生成数据库操作的负载

2. redis 的incr 命令

当使用数据库来生成ID性能不够要求的时候,我们可以尝试使用Redis来生成ID。这主要依赖于Redis是单线程的,所以也可以用生成全局唯一的ID。可以用Redis的原子操作 INCR和INCRBY(字符串操作)来实现。

可以使用Redis集群来获取更高的吞吐量。假如一个集群中有5台Redis。可以初始化每台Redis的值分别是1,2,3,4,5,然后步长都是5。各个Redis生成的ID为:

A:1,6,11,16,21

B:2,7,12,17,22

C:3,8,13,18,23

D:4,9,14,19,24

E:5,10,15,20,25

这个,随便负载到哪个机确定好,未来很难做修改。但是3-5台服务器基本能够满足器上,都可以获得不同的ID。但是步长和初始值一定需要事先需要了。使用Redis集群也可以方式单点故障的问题。

另外,比较适合使用Redis来生成每天从0开始的流水号。比如订单号=日期+当日自增长号。可以每天在Redis中生成一个Key,使用INCR进行累加

3. UUID

Java 自带的

UUID.randomUUID()复制代码

4. snowflake算法

snowflake 是Twitter开源的分布式ID生成算法,其结果是一个long型的ID;核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit是机器ID),12bit作为每毫秒可以产生4096个ID),最后一个符号位,永远是0

public class SnowFlake {    /**     * 起始时间     */    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_SEQUENCE = -1L^ (-1L << SEQUENCE_BIT);    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);    /**     * 每一部分向左的位移     * @param args     */    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 SnowFlake(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();        if(currStmp < lastStmp) throw new RuntimeException("Clock moved backwards.  Refusing to generate id\"");        if(currStmp == lastStmp){            //相同毫秒内,序列号自增            sequence = (sequence + 1) & MAX_SEQUENCE;            // 同一毫秒的序列数已经达到最大            if(sequence == 0L) currStmp = getNextMill();        } else {            sequence = 0L;//不同毫秒内,序列号置为0        }        lastStmp = currStmp;        return (currStmp - START_STMP) << TIMESTMP_LEFT                | datacenterId << DATACENTER_LEFT                | machineId << MACHINE_LEFT                | sequence;    }    private long getNextMill() {        long mill = getNewstmp();        while (mill <= lastStmp) {            mill = getNewstmp();        }        return mill;    }    private long getNewstmp() {        return System.currentTimeMillis();    }    public static void main(String[] args) {        SnowFlake snowFlake = new SnowFlake(2, 3);        for (int i = 0; i < (1 << 12); i++) {            System.out.println(snowFlake.nextId());        }    }}复制代码

5. zookeeper生成唯一ID

zookeeper主要通过其znode数据版本来生成序列号,可以生成32位和64位的数据版本号,客户端可以使用这个版本号来作为唯一的序列号。 很少会使用zookeeper来生成唯一ID。主要是由于需要依赖zookeeper,并且是多步调用API,如果在竞争较大的情况下,需要考虑使用分布式锁。因此,性能在高并发的分布式环境下,也不甚理想

6. MongoDB的ObjectId

如 4e7020cb7cac81af7136236b

Returns a new ObjectId value. The 12-byte ObjectId value consists of:1.a 4-byte value representing the seconds since the Unix epoch,2.a 3-byte machine identifier,3.a 2-byte process id, and4.a 3-byte counter, starting with a random value.复制代码

1.(Time)时间戳。将刚才生成的objectid的前4位进行提取“4e7020cb”,然后按照十六进制转为十进制,变为“1315971275”,这个数字就是一个时间戳。通过时间戳的转换,就成了易看清的时间格式。

2.(Machine)机器。接下来的三个字节就是“7cac81”,这三个字节是所在主机的唯一标识符,一般是机器主机名的散列值,这样就确保了不同主机生成不同的机器hash值,确保在分布式中不造成冲突,这也就是在同一台机器生成的objectId中间的字符串都是一模一样的原因。

3.(PID)进程ID。上面的Machine是为了确保在不同机器产生的objectId不冲突,而pid就是为了在同一台机器不同的mongodb进程产生了objectId不冲突,接下来的“af71”两位就是产生objectId的进程标识符。

4.(INC)自增计数器。前面的九个字节是保证了一秒内不同机器不同进程生成objectId不冲突,这后面的三个字节“36236b”是一个自动增加的计数器,用来确保在同一秒内产生的objectId也不会发现冲突,允许256的3次方等于16777216条记录的唯一性。

具体客户端实现 自增(INC) 的实现

/*** AtomicInteger 无锁CAS,线程安全*/private static final AtomicInteger NEXT_COUNTER = new AtomicInteger(new SecureRandom().nextInt()); /** * Constructs a new instance using the given date. * * @param date the date */ public ObjectId(final Date date) {    this(dateToTimestampSeconds(date), MACHINE_IDENTIFIER, PROCESS_IDENTIFIER, NEXT_COUNTER.getAndIncrement(), false);}复制代码

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

你可能感兴趣的文章
再战“6.18”销售额榜首,韩都衣舍究竟“凭什么!”
查看>>
看看淘宝的工程师如何评论12306
查看>>
Linux之:最常用的20条命令
查看>>
收藏|Java程序员必看的几本基础书籍和常用工具
查看>>
基于Docker快速搭建Gitlab与Gitlab CI/CD服务
查看>>
黄秀杰教程之--Node使用小程序模板消息
查看>>
React Hooks
查看>>
关于抢购秒杀的实现思路与事例代码
查看>>
ttlsa教程系列之MySQL---mysql数据库监控
查看>>
centos安装pypy(含pypy下载地址)
查看>>
spring 的那些 processors
查看>>
使用kickstart服务全自动安装RHEL7.0系统
查看>>
测试防盗链
查看>>
关于无法启动Task Scheduler 服务的通用解决方案
查看>>
dns解析及安全
查看>>
详解TCP/IP协议的含义及三个参数
查看>>
***的免杀技术
查看>>
Java 图像的模糊与锐化
查看>>
filter实际运用 appScan扫描漏洞处理
查看>>
原创:keil各种错误原因和解决方案--不间断更新
查看>>