分布式主键解决方案之--Snowflake雪花算法
程序员文章站
2022-07-02 12:47:43
0--前言 对于分布式系统环境,主键ID的设计很关键,什么自增intID那些是绝对不用的,比较早的时候,大部分系统都用UUID/GUID来作为主键,优点是方便又能解决问题,缺点是插入时因为UUID/GUID的不规则导致每插入一条数据就需要重新排列一次,性能低下;也有人提出用UUID/GUID转lon ......
0--前言
对于分布式系统环境,主键id的设计很关键,什么自增intid那些是绝对不用的,比较早的时候,大部分系统都用uuid/guid来作为主键,优点是方便又能解决问题,缺点是插入时因为uuid/guid的不规则导致每插入一条数据就需要重新排列一次,性能低下;也有人提出用uuid/guid转long的方式,可以很明确的告诉你,这种方式long不能保证唯一,大并发下会有重复long出现,所以也不可取,这个主键设计问题曾经是很多公司系统设计的一个头疼点,所以大部分公司愿意牺牲一部分性能而直接采用简单粗暴的uuid/guid来作为分布式系统的主键;
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) { } } }
结果如下图:
程序生成了19位的有序数字,这个既解决了分布式id生成唯一性问题,也解决了性能问题,建议系统id设计都采用该算法生成。
推荐阅读
-
分布式ID: 雪花算法(snowflake)
-
分布式ID生成之雪花算法小白专场
-
分布式主键解决方案之--Snowflake雪花算法
-
一秒可生成500万ID的分布式自增ID算法—雪花算法 (Snowflake,Delphi 版)
-
分布式自增ID生成算法 - 雪花算法(SnowFlake)
-
分布式自增ID算法snowflake(雪花算法)
-
分布式自增ID算法——雪花算法 (snowflake,Java版)
-
分布式主键解决方案之--Snowflake雪花算法
-
分布式ID生成之雪花算法小白专场
-
分布式系统之全局唯一ID生成策略 (UUID,基于mysql的replace into基于redis生成全局唯一id,Twitter的分布式自增id雪花算法 )