分布式主键解决方案之--Snowflake雪花算法

0--前言

  对于分布式系统环境,主键ID的设计很关键,什么自增intID那些是绝对不用的,比较早的时候,大部分系统都用UUID/GUID来做为主键优势是方便又能解决问题,缺点是插入时由于UUID/GUID的不规则致使每插入一条数据就须要从新排列一次,性能低下;也有人提出用UUID/GUID转long的方式,能够很明确的告诉你,这种方式long不能保证惟一,大并发下会有重复long出现,因此也不可取,这个主键设计问题曾经是不少公司系统设计的一个头疼点,因此大部分公司愿意牺牲一部分性能而直接采用简单粗暴的UUID/GUID来做为分布式系统的主键;java

  twitter开源了一个snowflake算法,俗称雪花算法;就是为了解决分布式环境下生成不一样ID的问题;该算法会生成19位的long型有序数字,MySQL中用bigint来存储(bigint长度为20位);该算法应该是目前分布式环境中主键ID最好的解决方案之一了;算法

1--snowflake雪花算法实现

  好,废话很少说,直接上算法实现并发

 1 package com.anson;  2 
 3 import java.lang.management.ManagementFactory;  4 import java.net.InetAddress;  5 import java.net.NetworkInterface;  6 
 7 //雪花算法代码实现
 8 public class IdWorker {  9     // 时间起始标记点,做为基准,通常取系统的最近时间(一旦肯定不能变更)
 10     private final static long twepoch = 1288834974657L;  11     // 机器标识位数
 12     private final static long workerIdBits = 5L;  13     // 数据中心标识位数
 14     private final static long datacenterIdBits = 5L;  15     // 机器ID最大值
 16     private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);  17     // 数据中心ID最大值
 18     private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);  19     // 毫秒内自增位
 20     private final static long sequenceBits = 12L;  21     // 机器ID偏左移12位
 22     private final static long workerIdShift = sequenceBits;  23     // 数据中心ID左移17位
 24     private final static long datacenterIdShift = sequenceBits + workerIdBits;  25     // 时间毫秒左移22位
 26     private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;  27 
 28     private final static long sequenceMask = -1L ^ (-1L << sequenceBits);  29     /* 上次生产id时间戳 */
 30     private static long lastTimestamp = -1L;  31     // 0,并发控制
 32     private long sequence = 0L;  33 
 34     private final long workerId;  35     // 数据标识id部分
 36     private final long datacenterId;  37 
 38     public IdWorker(){  39         this.datacenterId = getDatacenterId(maxDatacenterId);  40         this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);  41  }  42     /**
 43  * @param workerId  44  * 工做机器ID  45  * @param datacenterId  46  * 序列号  47      */
 48     public IdWorker(long workerId, long datacenterId) {  49         if (workerId > maxWorkerId || workerId < 0) {  50             throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));  51  }  52         if (datacenterId > maxDatacenterId || datacenterId < 0) {  53             throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));  54  }  55         this.workerId = workerId;  56         this.datacenterId = datacenterId;  57  }  58     /**
 59  * 获取下一个ID  60  *  61  * @return
 62      */
 63     public synchronized long nextId() {  64         long timestamp = timeGen();  65         if (timestamp < lastTimestamp) {  66             throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));  67  }  68 
 69         if (lastTimestamp == timestamp) {  70             // 当前毫秒内,则+1
 71             sequence = (sequence + 1) & sequenceMask;  72             if (sequence == 0) {  73                 // 当前毫秒内计数满了,则等待下一秒
 74                 timestamp = tilNextMillis(lastTimestamp);  75  }  76         } else {  77             sequence = 0L;  78  }  79         lastTimestamp = timestamp;  80         // ID偏移组合生成最终的ID,并返回ID
 81         long nextId = ((timestamp - twepoch) << timestampLeftShift)  82                 | (datacenterId << datacenterIdShift)  83                 | (workerId << workerIdShift) | sequence;  84 
 85         return nextId;  86  }  87 
 88     private long tilNextMillis(final long lastTimestamp) {  89         long timestamp = this.timeGen();  90         while (timestamp <= lastTimestamp) {  91             timestamp = this.timeGen();  92  }  93         return timestamp;  94  }  95 
 96     private long timeGen() {  97         return System.currentTimeMillis();  98  }  99 
100     /**
101  * <p> 102  * 获取 maxWorkerId 103  * </p> 104      */
105     protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) { 106         StringBuffer mpid = new StringBuffer(); 107  mpid.append(datacenterId); 108         String name = ManagementFactory.getRuntimeMXBean().getName(); 109         if (!name.isEmpty()) { 110             /*
111  * GET jvmPid 112              */
113             mpid.append(name.split("@")[0]); 114  } 115         /*
116  * MAC + PID 的 hashcode 获取16个低位 117          */
118         return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1); 119  } 120 
121     /**
122  * <p> 123  * 数据标识id部分 124  * </p> 125      */
126     protected static long getDatacenterId(long maxDatacenterId) { 127         long id = 0L; 128         try { 129             InetAddress ip = InetAddress.getLocalHost(); 130             NetworkInterface network = NetworkInterface.getByInetAddress(ip); 131             if (network == null) { 132                 id = 1L; 133             } else { 134                 byte[] mac = network.getHardwareAddress(); 135                 id = ((0x000000FF & (long) mac[mac.length - 1]) 136                         | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6; 137                 id = id % (maxDatacenterId + 1); 138  } 139         } catch (Exception e) { 140             System.out.println(" getDatacenterId: " + e.getMessage()); 141  } 142         return id; 143  } 144 }

 

3--测试

package com.anson; /** * @description: TODO * @author: anson * @Date: 2019/10/7 22:16 * @version: 1.0 */
public class snow { public static  void main(String[] args) throws Exception { try { IdWorker idw = new IdWorker(1,1); long ids = idw.nextId(); for(int i=0;i<10000;i++) { ids = idw.nextId(); System.out.println(ids); } } catch (Exception ex) { } } }

结果以下图:app

 

 程序生成了19位的有序数字,这个既解决了分布式ID生成惟一性问题,也解决了性能问题,建议系统ID设计都采用该算法生成。less

相关文章
相关标签/搜索