0
点赞
收藏
分享

微信扫一扫

算法系列:Twitter-Snowflake[雪花算法],64位自增ID算法详解

今天给大家分享一篇关于雪花算法的文章,希望对你有所帮助!

Twitter-Snowflake算法产生的背景相当简单,为了满足Twitter每秒上万条消息的请求,每条消息都必须分配一条唯一的id,这些id还需要一些大致的顺序(方便客户端排序),并且在分布式系统中不同机器产生的id必须不同。

Snowflake算法核心

时间戳工作机器id序列号组合在一起。


算法系列:Twitter-Snowflake[雪花算法],64位自增ID算法详解_自增


除了最高位bit标记为不可用以外,其余三组bit占位均可浮动,看具体的业务需求而定。默认情况下41bit的时间戳可以支持该算法使用到2082年,10bit的工作机器id可以支持1023台机器,序列号支持1毫秒产生4095个自增序列id。下文会具体分析。


Snowflake – 时间戳

这里时间戳的细度是毫秒级,具体代码如下,建议使用64位linux系统机器,因为有vdso,gettimeofday()在用户态就可以完成操作,减少了进入内核态的损耗。


1

2

3

4

5

6



​uint64_t generateStamp()​

​{​

​timeval tv;​

​gettimeofday(&tv, 0);​

​return​​​ ​​(uint64_t)tv.tv_sec * 1000 + (uint64_t)tv.tv_usec / 1000;​

​}​


默认情况下有41个bit可以供使用,那么一共有T(1llu << 41)毫秒供你使用分配,年份 = T / (3600 * 24 * 365 * 1000) = 69.7年。如果你只给时间戳分配39个bit使用,那么根据同样的算法最后年份 = 17.4年。

Snowflake – 工作机器id

严格意义上来说这个bit段的使用可以是进程级,机器级的话你可以使用MAC地址来唯一标示工作机器工作进程级可以使用IP+Path来区分工作进程。如果工作机器比较少,可以使用配置文件来设置这个id是一个不错的选择,如果机器过多配置文件的维护是一个灾难性的事情。

这里的解决方案是需要一个工作id分配的进程,可以使用自己编写一个简单进程来记录分配id,或者利用Mysql auto_increment机制也可以达到效果。

算法系列:Twitter-Snowflake[雪花算法],64位自增ID算法详解_序列号_02


工作进程与工作id分配器只是在工作进程启动的时候交互一次,然后工作进程可以自行将分配的id数据落文件,下一次启动直接读取文件里的id使用。

PS:这个工作机器id的bit段也可以进一步拆分,比如用前5个bit标记进程id,后5个bit标记线程id之类:D

Snowflake – 序列号

序列号就是一系列的自增id(多线程建议使用atomic),为了处理在同一毫秒内需要给多条消息分配id,若同一毫秒把序列号用完了,则“等待至下一毫秒”。


1

2

3

4

5

6

7

8



​uint64_t waitNextMs(uint64_t lastStamp)​

​{​

​uint64_t cur = 0;​

​do​​​ ​​{​

​cur = generateStamp();​

​} ​​​​while​​​ ​​(cur <= lastStamp);​

​return​​​ ​​cur;​

​}​



总体来说,是一个很高效很方便的GUID产生算法,一个int64_t字段就可以胜任,不像现在主流128bit的GUID算法,即使无法保证严格的id序列性,但是对于特定的业务,比如用做游戏服务器端的GUID产生会很方便。另外,在多线程的环境下,序列号使用atomic可以在代码实现上有效减少锁的密度。

参考资料:https://github.com/twitter/snowflake

最后附上一段完整代码供大家参考:

java版本
/**
* Twitter的分布式自增ID雪花算法snowflake
* @author MENG
* @create 2018-08-23 10:21
**/
public class SnowFlake {


/**
* 起始的时间戳
*/
private final static long START_STMP = 1480166465631L;
//private final static long START_STMP = 1480166465631L;


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


/**
* 每一部分的最大值
*/
private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
private 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;//上一次时间戳


public SnowFlake(long datacenterId, long machineId) {
if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
}
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
}
this.datacenterId = datacenterId;
this.machineId = machineId;
}


/**
* 产生下一个ID
*
* @return
*/
public synchronized long nextId() {
long currStmp = getNewstmp();
if (currStmp < lastStmp) {
throw new RuntimeException("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; //序列号部分
}


private long getNextMill() {
long mill = getNewstmp();
while (mill <= lastStmp) {
mill = getNewstmp();
}
return mill;
}


private long getNewstmp() {
return System.currentTimeMillis();
}


public static void main(String[] args) {
SnowFlake snowFlake = new SnowFlake(1, 1);


long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
System.out.println(snowFlake.nextId());
}


System.out.println(System.currentTimeMillis() - start);




}
}
PHP版本:
//https://learnku.com/articles/32575
//defined('BASEPATH') or exit('No direct script access allowed');
/**
*
* 雪花全局唯一 ID 生成类
* 修改了机器id和序列号自动获取 理论上毫秒可生成 1024*4096个唯一id
* 组成: <毫秒级时间戳+机器id+序列号>
* 41bit的时间戳 可以支持该算法使用到2082年,
* 10bit的机器id 可以支持1023台机器,
* 12bit序列号 可以支持1毫秒产生4095个自增序列id
*
*/
class Snowflake
{
const EPOCH = 1479533469598; //开始时间,固定一个小于当前时间的毫秒数
//const EPOCH = 1479533; //开始时间,固定一个小于当前时间的毫秒数
const max12bit = 4095;
const max41bit = 1099511627775;
static $LAST_TIME_MID = 0; // 上次的机器id
static $SEQUENCE_NUM = 0; //
public static function createOnlyId()
{
// 时间戳 42字节
$time = floor(microtime(true) * 1000);
// 当前时间 与 开始时间 差值
$time -= self::EPOCH;
// 二进制的 毫秒级时间戳
$base = decbin(self::max41bit + $time);
// 获取序列数
self::get_num();
// 获取机器id
self::get_mid();
// 机器id 10 字节
$machineid = str_pad(decbin(self::$LAST_TIME_MID), 10, "0", STR_PAD_LEFT);
// 序列数 12字节
$random = str_pad(decbin(self::$SEQUENCE_NUM), 12, "0", STR_PAD_LEFT);
// 拼接
$base = $base . $machineid . $random;
if (strpos(PHP_OS, "WIN") !== false)
{
//win下会科学计数 格式化
return number_format(bindec($base), 0, '', ''); //输出16位id 4369796497932289
}
return bindec($base); // 输出19位id 4940315248386113537
}
//机器id 10字节 1-1023
public static function get_mid()
{
if (self::$SEQUENCE_NUM >= 4095)
{
self::$LAST_TIME_MID++;
if (self::$LAST_TIME_MID > 1023)
{
self::$LAST_TIME_MID = 0;
}
}
}
//12字节 1-4095
public static function get_num()
{
self::$SEQUENCE_NUM++;
if (self::$SEQUENCE_NUM > 4095)
{
self::$SEQUENCE_NUM = 0;
}
}
}
for($i=1;$i<=100000;$i++)
{
var_dump(Snowflake::createOnlyId());
// string(19) "5018983989168308224"
}

更多的其他版本可以留言或者自行搜索了解!

举报

相关推荐

0 条评论