因为 MySQL 本身支持 auto_increment 操作,很自然地,我们会想到借助这个特性来实现这个功能。 Flicker 在解决全局 ID 生成方案里就采用了 MySQL 自增长 ID 的机制(auto_increment + replace into + MyISAM)。一个生成 64 位 ID 方案具体就是这样的:
创建单独的数据库 (eg:ticket),然后创建一个表:
1 2 3 4 5 6
CREATE TABLE Tickets64 ( id bigint(20) unsigned NOT NULL auto_increment, stub char(1) NOT NULL default '', PRIMARY KEY (id), UNIQUE KEY stub (stub) ) ENGINE=MyISAM
当我们插入记录后,执行SELECT * from Tickets64,查询结果就是这样的:
1 2 3 4 5
+-------------------+------+ | id | stub | +-------------------+------+ | 72157623227190423 | a | +-------------------+------+
在我们的应用端需要做下面这两个操作,在一个事务会话里提交:
1 2
REPLACE INTO Tickets64 (stub) VALUES ('a'); SELECT LAST_INSERT_ID();
12 位的计数顺序号(12 位的计数顺序号支持每个节点每毫秒产生 4096 个 ID 序号) 最高位是符号位,始终为 0。 维度1:时间:用前面 41 bit 来表示时间,精确到毫秒,可以表示69年的数据 维度2:机器 ID:用10 bit 来表示,也就是说可以部署 1024 台机器 维度3:序列数:用 12 bit 来表示,意味着每台机器,每毫秒最多可以生成 4096个 ID
优点:高性能,低延迟;独立的应用;按时间有序。充分把信息保存到 ID 里。 缺点:需要独立的开发和部署,结构略复杂。
/** * Snowflake idworker ,from twitter snowflake project . */ public class IdWorker {
protected static final Logger LOGGER = LoggerFactory.getLogger(IdWorker.class); private long workerId; private long datacenterId; private long sequence = 0L;
private long twepoch = 1451577600000L;
private long workerIdBits = 5L; private long datacenterIdBits = 5L; private long maxWorkerId = ~(-1L << workerIdBits); private long maxDatacenterId = ~(-1L << datacenterIdBits); private long sequenceBits = 12L;
private long workerIdShift = sequenceBits; private long datacenterIdShift = sequenceBits + workerIdBits; private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; private long sequenceMask = ~(-1L << sequenceBits);
/** * @param workerId 5 bits worker id ,from 0 to 31 * @param datacenterId 5 bits data center id ,from 0 to 31 */ public IdWorker(long workerId, long datacenterId) { // sanity check for workerId 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; LOGGER.info(String.format("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d", timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId)); }
public synchronized long nextId() { long timestamp = timeGen();
if (timestamp < lastTimestamp) { LOGGER.error(String.format("clock is moving backwards. Rejecting requests until %d.", 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;//this is twitter version sequence = random.nextInt(10);// optimize the random number of 0~9 to prevent the id distribution is not balanced }
lastTimestamp = timestamp;
long identity; if (datacenterId != -1 && workerId != -1) { identity = (datacenterId << datacenterIdShift) | (workerId << workerIdShift); } else { //get id from InstanceIdManager identity = InstanceIdProxy.getInstanceId(); }