您的位置:首页 > 编程语言 > Java开发

雪花算法(SnowFlake)Java实现

2020-04-07 12:25 2787 查看

雪花算法(SnowFlake)Java实现

算法原理

SnowFlake算法生成id的结果是一个64bit大小的整数,它的结构如下图:

由于在Java中64bit的整数是long类型,所以在Java中SnowFlake算法生成的id就是long来存储的。

SnowFlake可以保证:

所有生成的id按时间趋势递增
整个分布式系统内不会产生重复id(因为有datacenterId和machineId来做区分)

算法实现(Java)

Twitter官方给出的算法实现 是用Scala写的,这里不做分析,可自行查看。

/**
** SnowFlake的结构如下(每部分用-分开):
** 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 -
* 000000000000
** 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0
** 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
** 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T
* = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
** 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>
** 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号
** 加起来刚好64位,为一个Long型。
**
* SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生20多万ID左右。
* @author byran
**/
public class SnowflakeIdWorker {
private static final Logger logger = LoggerFactory.getLogger(SnowflakeIdWorker.class);
/**
* 起始的时间戳
**/
private final static long START_STMP = 1480166465631L;

/**
* 每一部分占用的位数
**/
private final static long SEQUENCE_BIT = 10; //序列号占用的位数
private final static long MACHINE_BIT = 5;   //机器标识占用的位数
private final static long DATACENTER_BIT = 5;//数据中心占用的位数

/**
* 每一部分的最大值
**/
public final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
public final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);

/**
* 每一部分向左的位移
**/
private final static long MACHINE_LEFT = SEQUENCE_BIT;
private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;

private long datacenterId;  //数据中心
private long machineId;     //机器标识
private long sequence = 0L; //序列号
private long lastStmp = -1L;//上一次时间戳

/**
* 根据MAC生成datacenterId,根据MAC + PID生成machineId
**/
public SnowflakeIdWorker() {
long datacenterId = getDatacenterId(MAX_DATACENTER_NUM);
long machineId = getMachineId(datacenterId, MAX_MACHINE_NUM);
check(datacenterId, machineId);
this.datacenterId = datacenterId;
this.machineId = machineId;
}

/**
* datacenterId和machineId可配置
* @param datacenterId
* @param machineId
**/
public SnowflakeIdWorker(long datacenterId, long machineId) {
check(datacenterId, machineId);
this.datacenterId = datacenterId;
this.machineId = machineId;
}

private static void check(long datacenterId, long machineId) {
if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
throw new EompRuntimeException(String.format("datacenterId can't be greater than %s or less than 0", MAX_DATACENTER_NUM));
}
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new EompRuntimeException(String.format("machineId can't be greater than %s or less than 0", MAX_MACHINE_NUM));
}
}

/**
* 产生下一个ID
* @return
**/
public synchronized long nextId() {
long currStmp = getNewstmp();
if (currStmp < lastStmp) {
throw new EompRuntimeException("Clock moved backwards.  Refusing to generate id");
}

if (currStmp == lastStmp) {
//相同毫秒内,序列号自增
sequence = (sequence + 1) & MAX_SEQUENCE;
//同一毫秒的序列数已经达到最大
if (sequence == 0L) {
currStmp = getNextMill();
}
} else {
//不同毫秒内,序列号置为0
sequence = 0L;
}

lastStmp = currStmp;

return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分
| datacenterId << DATACENTER_LEFT       //数据中心部分
| machineId << MACHINE_LEFT             //机器标识部分
| sequence;                             //序列号部分
}

/**
* 阻塞到下一个毫秒,直到获得新的时间戳
* @return 当前时间戳
**/
private long getNextMill() {
long mill = getNewstmp();
while (mill <= lastStmp) {
mill = getNewstmp();
}
return mill;
}

/**
* 返回以毫秒为单位的当前时间
* @return 当前时间(毫秒)
**/
private long getNewstmp() {
return System.currentTimeMillis();
}

/**
* 机器标识
**/
private static long getMachineId(long datacenterId, long maxWorkerId) {
StringBuilder mpid = new StringBuilder();
mpid.append(datacenterId);
String name = ManagementFactory.getRuntimeMXBean().getName();
if (!name.isEmpty()) {
/** GET jvmPid */
mpid.append(name.split("@")[0]);
}
/** MAC + PID 的 hashcode 获取16个低位 */
return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
}

/**
* 数据标识id部分
**/
private static long getDatacenterId(long maxDatacenterId) {
long id = 0L;
try {
InetAddress ip = InetAddress.getLocalHost();
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
if (network == null) {
id = 1L;
} else {
byte[] mac = network.getHardwareAddress();
id = ((0x000000FF & (long) mac[mac.length - 1])
| (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
id = id % (maxDatacenterId + 1);
}
} catch (Exception e) {
logger.error("getDatacenterId exception.", e);
}
return id;
}

/*public static void main(String[] args) {
long datacenterId = getDatacenterId(MAX_DATACENTER_NUM);
long machineId = getMachineId(datacenterId, MAX_MACHINE_NUM);
System.out.println("ip:" + datacenterId + ",processId:" + machineId);
}*/
}

测试类:

public class SnowflakeIdWorkerTest {
public static Set<Long> idSet = new HashSet<>();

public static void main(String[] args) {
SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker(1, 0);
for (long i = 0; i < 1000; i++) {
new Thread(new Worker(snowflakeIdWorker)).start();
}
}

static class Worker implements Runnable {
private SnowflakeIdWorker snowflakeIdWorker;

public Worker(SnowflakeIdWorker snowflakeIdWorker) {
this.snowflakeIdWorker = snowflakeIdWorker;
}

@Override
public void run() {
for (int i = 0; i < 1000; i++) {
Long id = snowflakeIdWorker.nextId();
if (!idSet.add(id)) {
System.err.println("存在重复id:" + id);
}
}
}
}
}
  • 点赞
  • 收藏
  • 分享
  • 文章举报
true2over 发布了6 篇原创文章 · 获赞 6 · 访问量 6022 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: