您的位置:首页 > 其它

常用的分布式ID生成策略

2017-02-24 00:00 309 查看
分布式的唯一ID是一个系统设计过程中经常遇到的问题。生成分布式ID的策略有多种,应用场景也不尽相同。下面简单介绍一下一些常用的分布式ID生成策略:

数据库自增ID

UUID

Redis

Snowflake

(一)数据库自增ID

通过定义数据库的自增ID进行实现。

优点:

简单,使用数据库现成的实现。

ID唯一,不会重复。

ID递增,有序增长,可设置步长。

缺点:

存在单点写入问题。通常在数据库主-从架构的情况下,如果主库挂了,切换到从库的话,很有可能生成重复的ID,导致数据错乱。

扩展性差并存在自增上限。

优化:

在写多库的情况下,可通过设置不同步长避免冲突。如A库(1, 3, 5, 7) 、B库(2, 4, 6, 8)

可以通过集中式的ID生成。如利用一张表记录当前ID的最大值(如:10),每次集中取一批ID(如:11,12,13,14,15,16),当这批ID用完后,更新最大ID为16,再取下一批。

(二)UUID

通过本地代码或者数据库生成。

优点:

全球唯一,扩展性强,理论上不会出现重复。

性能好,本地可以生成,不需要远程交互。

缺点:

通常是字符串存储,ID较长,作为主键建立索引查询时性能差。

ID随机生成,没有连续性。

优化:

将uuid转成int64。这样既减少空间也能优化查询效率。

private static BigInteger getBigIntegerFromUuid() {
UUID id = UUID.randomUUID();
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(id.getMostSignificantBits());
bb.putLong(id.getLeastSignificantBits());
return new BigInteger(1, bb.array());
}


(三)Redis

通过redis的incrby或者lua方式进行生成ID。

(四)Snowflake

Snowflake算法源自Twitter,主要应用于解决分布式的场景下的唯一ID。

优点:

本地生成, 扩展性好,性能优异。

生成的ID简单、有序。

缺点:

使用时间进行计算,不同机器的时钟可能会造成生成的ID并不一定严格有序。

改进:

可以根据实际的需求对算法进行调整。

下面简单介绍一下Snowflake的基本原理。

snowflake使用64bit的存储空间,正好是一个长整数的存储空间。

时间前缀(42) + 机房标识(5) + 主机标识(5) + 顺序号(12)

时间前缀:

使用秒数记录。需要设置一个起始时间点(如:2016-01-01 00:00:00)。那么生成ID时,将当前的秒数-起始时间的秒数。年T = (1L << 42) / (1000L * 60 * 60 * 24 * 365) = 139.46,表示这个算法可以支持使用139.46年。

机房标识:

可以用于表示机房编号之类。按(1L << 5) = 32计算,可以支持32个机房。

主机标识:

可以用于表示主机编号之类。按(1L << 5) = 32计算,可以支持32个主机。综合机房标识和主机标识,一共可以支持使用1024台主机。

顺序号:

按(1L << 12) = 4096计算,每秒可以生成4096个ID。再综合机房、主机、顺序号,1024*4096约400万,即每秒可以生成400万个不重复的ID。

参考:https://github.com/twitter/snowflake

注意:可以根据实际情况对snowflake的不同组成位数进行调整。

如实际上机器只需要10台的话,那机房和主机标识加起来4位即可。

如当机器少,但是要支持每秒生成的ID更多时,也可以调整顺序号的位数。

参考中的代码使用scala编写,下面是java版本的翻译:

package com.zheng.coderepo.snowflake;

/**
* Created by zhangchaozheng on 17-2-24.
*/
public class IdGen {
private final long twepoch = 1288834974657L;
private final long workerIdBits = 5L;
private final long datacenterIdBits = 5L;
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
private final long sequenceBits = 12L;
private final long workerIdShift = sequenceBits;
private final long datacenterIdShift = sequenceBits + workerIdBits;
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final long sequenceMask = -1L ^ (-1L << sequenceBits);

private long workerId;
private long datacenterId;
private long sequence = 0L;
private long lastTimestamp = -1L;

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

lastTimestamp = timestamp;

return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
}

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

protected long timeGen() {
return System.currentTimeMillis();
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  snowflake 分布式ID