欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-04-14 20:46:12
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)
        {

        }

    }
}

结果如下图:

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

 

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