解决方案
UUID
UUIDs are 128-bit hexadecimal numbers that are globally unique. The chances of the same UUID getting generated twice is negligible.
优点:
- 性能非常高:本地生成,没有网络消耗。
缺点:
- 不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。
- 信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露。
- ID不适用作为DB主键。
MongoDB’s ObjectId
MongoDB’s ObjectIDs are 12-byte (96-bit) hexadecimal numbers that are made up of -
- 4-byte epoch timestamp in seconds,
- 3-byte machine identifier,
- 2-byte process id, and
- 3-byte counter, starting with a random value.
Twitter Snowflake
Twitter snowflake is a dedicated network service for generating 64-bit unique IDs at high scale. The IDs generated by this service are roughly time sortable.
The IDs are made up of the following components:
- Epoch timestamp in millisecond precision - 41 bits (gives us 69 years with a custom epoch)
- Configured machine id - 10 bits (gives us up to 1024 machines)
- Sequence number - 12 bits (A local counter per machine that rolls over every 4096)
理论上snowflake方案的QPS约为409.6w/s
优点:
- 毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
- 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。
- 可以根据自身业务特性分配bit位,非常灵活。
缺点:
- 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。
可行的解决方案:
- 利用 ZooKeeper 等协调服务管理时间
- 关闭 NTP 服务
Snowflake 的各种语言实现
表示 timestamp 的41位,可以支持我们使用69年。当然,我们的时间毫秒计数不会真的从 1970 年开始记,那样我们的系统跑到 2039/9/7 23:47:35 就不能用了,所以这里的 timestamp 实际上只是相对于某个时间的增量,比如我们的系统上线是 2019-01-01,那么我们可以把这个 timestamp 当作是从 2019-01-01 00:00:00.000的偏移量。
python
"""
┌─┐┌┐┌┌─┐┬ ┬┌─┐┬ ┌─┐┬┌─┌─┐
└─┐││││ ││││├┤ │ ├─┤├┴┐├┤
└─┘┘└┘└─┘└┴┘└ ┴─┘┴ ┴┴ ┴└─┘
"""
import time
import random
import threading
class Snowflake(object):
# 22 bit = 2 region + 8 worker + 12 sequence (< 4096)
region_id_bits = 2
worker_id_bits = 8
sequence_bits = 12
MAX_REGION_ID = -1 ^ (-1 << region_id_bits)
MAX_WORKER_ID = -1 ^ (-1 << worker_id_bits)
SEQUENCE_MASK = -1 ^ (-1 << sequence_bits)
WORKER_ID_SHIFT = sequence_bits
REGION_ID_SHIFT = sequence_bits + worker_id_bits
TIMESTAMP_LEFT_SHIFT = sequence_bits + worker_id_bits + region_id_bits
def __init__(self, worker_id, region_id=0):
self.twepoch = 1495977602000
self.last_timestamp = -1
self.sequence = 0
# assert 0 <= worker_id <= Snowflake.MAX_WORKER_ID
# assert 0 <= region_id <= Snowflake.MAX_REGION_ID
self.worker_id = worker_id
self.region_id = region_id
self.lock = threading.Lock()
def generate(self, bus_id=None):
return self.next_id(
True if bus_id is not None else False, bus_id if bus_id is not None else 0
)
def next_id(self, is_padding, bus_id):
with self.lock:
timestamp = self.get_time()
padding_num = self.region_id
if is_padding:
padding_num = bus_id
if timestamp < self.last_timestamp:
try:
raise ValueError(
"Clock moved backwards. Refusing to"
"generate id for {0} milliseconds.".format(
self.last_timestamp - timestamp
)
)
except ValueError as error:
print(error)
if timestamp == self.last_timestamp:
self.sequence = (self.sequence + 1) & Snowflake.SEQUENCE_MASK
if self.sequence == 0:
timestamp = self.tail_next_millis(self.last_timestamp)
else:
self.sequence = random.randint(0, 9)
self.last_timestamp = timestamp
res_id = (
(timestamp - self.twepoch) << Snowflake.TIMESTAMP_LEFT_SHIFT
| (padding_num << Snowflake.REGION_ID_SHIFT)
| (self.worker_id << Snowflake.WORKER_ID_SHIFT)
| self.sequence
)
return res_id
def tail_next_millis(self, last_timestamp):
timestamp = self.get_time()
while timestamp <= last_timestamp:
timestamp = self.get_time()
return timestamp
def get_time(self):
return int(time.time() * 1000)
if __name__ == "__main__":
print(Snowflake(0).generate())
golang
package main
import (
"fmt"
"github.com/bwmarrin/snowflake"
)
func main() {
// Create a new Node with a Node number of 1
node, err := snowflake.NewNode(1)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(stepMask)
// Generate a snowflake ID.
id := node.Generate()
// Print out the ID in a few different ways.
fmt.Printf("ID : %d\n", id)
// Print out the ID's sequence number
fmt.Printf("ID Step : %d\n", id.Step())
}
java
import java.net.NetworkInterface;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.Enumeration;
/**
* Distributed Sequence Generator.
* Inspired by Twitter snowflake: https://github.com/twitter/snowflake/tree/snowflake-2010
*
* This class should be used as a Singleton.
* Make sure that you create and reuse a Single instance of SequenceGenerator per node in your distributed system cluster.
*/
public class SequenceGenerator {
private static final int TOTAL_BITS = 64;
private static final int EPOCH_BITS = 42;
private static final int NODE_ID_BITS = 10;
private static final int SEQUENCE_BITS = 12;
private static final int maxNodeId = (int)(Math.pow(2, NODE_ID_BITS) - 1);
private static final int maxSequence = (int)(Math.pow(2, SEQUENCE_BITS) - 1);
// Custom Epoch (January 1, 2015 Midnight UTC = 2015-01-01T00:00:00Z)
private static final long CUSTOM_EPOCH = 1420070400000L;
private final int nodeId;
private volatile long lastTimestamp = -1L;
private volatile long sequence = 0L;
// Create SequenceGenerator with a nodeId
public SequenceGenerator(int nodeId) {
if(nodeId < 0 || nodeId > maxNodeId) {
throw new IllegalArgumentException(String.format("NodeId must be between %d and %d", 0, maxNodeId));
}
this.nodeId = nodeId;
}
// Let SequenceGenerator generate a nodeId
public SequenceGenerator() {
this.nodeId = createNodeId();
}
public synchronized long nextId() {
long currentTimestamp = timestamp();
if(currentTimestamp < lastTimestamp) {
throw new IllegalStateException("Invalid System Clock!");
}
if (currentTimestamp == lastTimestamp) {
sequence = (sequence + 1) & maxSequence;
if(sequence == 0) {
// Sequence Exhausted, wait till next millisecond.
currentTimestamp = waitNextMillis(currentTimestamp);
}
} else {
// reset sequence to start with zero for the next millisecond
sequence = 0;
}
lastTimestamp = currentTimestamp;
long id = currentTimestamp << (TOTAL_BITS - EPOCH_BITS);
id |= (nodeId << (TOTAL_BITS - EPOCH_BITS - NODE_ID_BITS));
id |= sequence;
return id;
}
// Get current timestamp in milliseconds, adjust for the custom epoch.
private static long timestamp() {
return Instant.now().toEpochMilli() - CUSTOM_EPOCH;
}
// Block and wait till next millisecond
private long waitNextMillis(long currentTimestamp) {
while (currentTimestamp == lastTimestamp) {
currentTimestamp = timestamp();
}
return currentTimestamp;
}
private int createNodeId() {
int nodeId;
try {
StringBuilder sb = new StringBuilder();
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while (networkInterfaces.hasMoreElements()) {
NetworkInterface networkInterface = networkInterfaces.nextElement();
byte[] mac = networkInterface.getHardwareAddress();
if (mac != null) {
for(int i = 0; i < mac.length; i++) {
sb.append(String.format("%02X", mac[i]));
}
}
}
nodeId = sb.toString().hashCode();
} catch (Exception ex) {
nodeId = (new SecureRandom().nextInt());
}
nodeId = nodeId & maxNodeId;
return nodeId;
}
}