雪花算法(snowflake) :分布式环境,生成全局唯一的订单号 | CSDN 博文精选

戳蓝字“CSDN云计算”关注我们哦!
640?wx_fmt=jpeg
作者 |  琦彦 
责编 | 阿秃
转自 | CSDN 博客

snowflake方案

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。

这种方案大致来说是一种以划分命名空间(UUID也算,由于比较常见,所以单独分析)来生成ID的一种算法,这种方案把64-bit分别划分成多段,分开来标示机器、时间等。

其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号,最后还有一个符号位,永远是0。

比如在snowflake中的64-bit分别表示如下图(图片来自网络)所示:

640?wx_fmt=png

整个结构是64位,所以我们在Java中可以使用long来进行存储。该算法实现基本就是二进制操作,单机每秒内理论上最多可以生成1024*(2^12),也就是409.6万个ID(1024 X 4096 = 4194304)

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。10-bit机器可以分别表示1024台机器。如果我们对IDC划分有需求,还可以将10-bit分5-bit给IDC,分5-bit给工作机器。这样就可以表示32个IDC,每个IDC下可以有32台机器,可以根据自身需求定义。
12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号。12个自增序列号可以表示2^12个ID,理论上snowflake方案的QPS约为409.6w/s,这种分配方式可以保证在任何一个IDC的任何一台机器在任意毫秒内生成的ID都是不同的。

加起来刚好64位,为一个Long型。

优点:

  • 整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。

  • 毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。

  • 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。

  • 可以根据自身业务特性分配bit位,非常灵活。


缺点:

  • 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。


针对此,美团做出了改进:https://github.com/Meituan-Dianping/Leaf

SnowFlake算法代码

public class SnowflakeIdWorker {	
// ==============================Fields==================	
/** 开始时间截 (2019-08-06) */	
private final long twepoch = 1565020800000L;	/** 机器id所占的位数 */	
private final long workerIdBits = 5L;	/** 数据标识id所占的位数 */	
private final long datacenterIdBits = 5L;	/** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */	
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);	/** 支持的最大数据标识id,结果是31 */	
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);	/** 序列在id中占的位数 */	
private final long sequenceBits = 12L;	/** 机器ID向左移12位 */	
private final long workerIdShift = sequenceBits;	/** 数据标识id向左移17位(12+5) */	
private final long datacenterIdShift = sequenceBits + workerIdBits;	/** 时间截向左移22位(5+5+12) */	
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;	/** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */	
private final long sequenceMask = -1L ^ (-1L << sequenceBits);	/** 工作机器ID(0~31) */	
private long workerId;	/** 数据中心ID(0~31) */	
private long datacenterId;	/** 毫秒内序列(0~4095) */	
private long sequence = 0L;	/** 上次生成ID的时间截 */	
private long lastTimestamp = -1L;	//==============================Constructors====================	
/**	* 构造函数	* @param workerId 工作ID (0~31)	* @param datacenterId 数据中心ID (0~31)	*/	
public SnowflakeIdWorker(long workerId, long datacenterId) {	
if (workerId > maxWorkerId || workerId < 0) {	
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));	}	
if (datacenterId > maxDatacenterId || datacenterId < 0) {	
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));	}	
this.workerId = workerId;	
this.datacenterId = datacenterId;	}	// ==============================Methods=================================	
/**	* 获得下一个ID (该方法是线程安全的)	* @return SnowflakeId	*/	
public synchronized long nextId() {	
long timestamp = timeGen();	//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常	
if (timestamp < lastTimestamp) {	
throw new RuntimeException(	String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));	}	//如果是同一时间生成的,则进行毫秒内序列	
if (lastTimestamp == timestamp) {	sequence = (sequence + 1) & sequenceMask;	
//毫秒内序列溢出	
if (sequence == 0) {	
//阻塞到下一个毫秒,获得新的时间戳	timestamp = tilNextMillis(lastTimestamp);	}	}	
//时间戳改变,毫秒内序列重置	
else {	sequence = 0L;	}	//上次生成ID的时间截	lastTimestamp = timestamp;	//移位并通过或运算拼到一起组成64位的ID	
return ((timestamp - twepoch) << timestampLeftShift) //	| (datacenterId << datacenterIdShift) //	| (workerId << workerIdShift) //	| sequence;	}	/**	* 阻塞到下一个毫秒,直到获得新的时间戳	* @param lastTimestamp 上次生成ID的时间截	* @return 当前时间戳	*/	
protected long tilNextMillis(long lastTimestamp) {	
long timestamp = timeGen();	
while (timestamp <= lastTimestamp) {	timestamp = timeGen();	}	
return timestamp;	}	/**	* 返回以毫秒为单位的当前时间	* @return 当前时间(毫秒)	*/	
protected long timeGen() {	
return System.currentTimeMillis();	}	//==============================Test=============================================	
/** 测试 */	
public static void main(String[] args) {	SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);	
for (int i = 0; i < 1000; i++) {	
long id = idWorker.nextId();	System.out.println(Long.toBinaryString(id));	System.out.println(id);	}	}	
}

snowflake实现方式1

apache.commons.lang3包
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.6</version></dependency>
读取配置文件:https://blog.csdn.net/fly910905/article/details/78737323

工具类

package com.datalook.util.common;	import org.apache.commons.lang3.time.DateFormatUtils;	import java.util.Date;	
/**	* 	* @Title:  订单号生成	* @ClassName:OrderIdUtils.java	* @Description:	*	* @Copyright 2016-2017  - Powered By 研发中心	* @author: 王延飞	* @date:2018年3月22日 下午7:43:30	* @version V1.0	*/	
public class OrderIdUtils {	// 最近的时间戳	
private long lastTimestamp=0;	
//机器id 2位	
private final String machineId;	
// 0,并发控制	
private long sequence = 0L;	
// 序列号的最大值	
private final int sequenceMax = 9999;	public OrderIdUtils(String machineId) {	
this.machineId = machineId;	}	/**	* 生成订单号	*/	
public synchronized String nextId(){	Date now=new Date();	String time= DateFormatUtils.format(now,"yyMMddHHmmssSSS");	
long timestamp = now.getTime();	
if (this.lastTimestamp == timestamp) {	
// 如果上一个timestamp与新产生的相等,则sequence加一(0-4095循环);	
// 对新的timestamp,sequence从0开始	
this.sequence = this.sequence + 1 % this.sequenceMax;	
if (this.sequence == 0) {	
// 重新生成timestamp	timestamp = this.tilNextMillis(this.lastTimestamp);	}	} else {	
this.sequence = 0;	}	
this.lastTimestamp= timestamp;	StringBuilder sb=new StringBuilder(time).append(machineId).append(leftPad(sequence,4));	
return sb.toString();	}	/**	* 补码	* @param i	* @param n	* @return	*/	
private String leftPad(long i,int n){	String s = String.valueOf(i);	StringBuilder sb=new StringBuilder();	
int c=n-s.length();	c=c<0?0:c;	
for (int t=0;t<c;t++){	sb.append("0");	}	
return sb.append(s).toString();	}	/**	* 等待下一个毫秒的到来, 保证返回的毫秒数在参数lastTimestamp之后	*/	
private long tilNextMillis(long lastTimestamp) {	
long timestamp = System.currentTimeMillis();	
while (timestamp <= lastTimestamp) {	timestamp = System.currentTimeMillis();	}	
return timestamp;	}	// 这里读取的是配置文件	
// 机器id(我这里是01,正式环境建议使用机器IP)	
// 注意:分布式环境,注意每台机器的id要保证不同;也可以使用机器ip,映射成一个数字编号(如01:192.168.55.12)	
private static String myid= SysConstant.LOCAL_MACHINE_ID;	// 示例	
private static OrderIdUtils instance = new OrderIdUtils(myid);	
public static OrderIdUtils getInstance() {	
return instance;	}	/**	* 	* @Title: 获取订单号	* @return String	* @Description:	*	* @author: 王延飞	* @date: 2018年3月22日 下午7:56:56	*/	
public static  String getOrderNumber() {	OrderIdUtils orderId = OrderIdUtils.getInstance();	String nextId = orderId.nextId();	return nextId;	}	/**	* 调用	*/	
public static void main(String[] args) {	OrderIdUtils orderId= OrderIdUtils.getInstance();	String nextId = orderId.nextId();	
int length = nextId.length();	System.out.println(nextId);	System.out.println(length);	}	
}

snowflake实现方式2

引入hutool依赖

<dependency>	
<groupId>cn.hutool</groupId>	
<artifactId>hutool-captcha</artifactId>	
<version>${hutool.version}</version>	
</dependency>

ID 生成器

public class IdGenerator {	private long workerId = 0;	@PostConstruct	void init() {	try {	workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());	log.info("当前机器 workerId: {}", workerId);	} catch (Exception e) {	log.warn("获取机器 ID 失败", e);	workerId = NetUtil.getLocalhost().hashCode();	log.info("当前机器 workerId: {}", workerId);	}	}	/**	* 获取一个批次号,形如 2019071015301361000101237	* <p>	* 数据库使用 char(25) 存储	*	* @param tenantId 租户ID,5 位	* @param module   业务模块ID,2 位	* @return 返回批次号	*/	public synchronized String batchId(int tenantId, int module) {	String prefix = DateTime.now().toString(DatePattern.PURE_DATETIME_MS_PATTERN);	return prefix + tenantId + module + RandomUtil.randomNumbers(3);	}	@Deprecated	public synchronized String getBatchId(int tenantId, int module) {	return batchId(tenantId, module);	}	/**	* 生成的是不带-的字符串,类似于:b17f24ff026d40949c85a24f4f375d42	*	* @return	*/	public String simpleUUID() {	return IdUtil.simpleUUID();	}	/**	* 生成的UUID是带-的字符串,类似于:a5c8a5e8-df2b-4706-bea4-08d0939410e3	*	* @return	*/	public String randomUUID() {	return IdUtil.randomUUID();	}	private Snowflake snowflake = IdUtil.createSnowflake(workerId, 1);	public synchronized long snowflakeId() {	return snowflake.nextId();	}	public synchronized long snowflakeId(long workerId, long dataCenterId) {	Snowflake snowflake = IdUtil.createSnowflake(workerId, dataCenterId);	return snowflake.nextId();	}	/**	* 生成类似:5b9e306a4df4f8c54a39fb0c	* <p>	* ObjectId 是 MongoDB 数据库的一种唯一 ID 生成策略,	* 是 UUID version1 的变种,详细介绍可见:服务化框架-分布式 Unique ID 的生成方法一览。	*	* @return	*/	public String objectId() {	return ObjectId.next();	}	}	

640?wx_fmt=png

扫描下方二维码,阅读作者大大原文哟

640?wx_fmt=png

640?wx_fmt=jpeg

福利
扫描添加小编微信,备注“姓名+公司职位”,入驻【CSDN博客】,加入【云计算学习交流群】,和志同道合的朋友们共同打卡学习!

640?wx_fmt=jpeg

推荐阅读:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/520861.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMQ+haproxy+keeplived 高可用负载均衡高可用镜像集群队列_01

文章目录一、RabbitMQ 集群节点说明二、服务器hosts文件统一修改三、RabbitMQ 镜像集群队列搭建部署一、RabbitMQ 集群节点说明 服务器IPhostname节点说明端口管控台地址账号密码192.168.0.115mq-01rabbitmq master5672http://192.168.0.115:15672guestguest192.168.0.117mq-0…

vue click事件_Vue.js---实现前后端分离架构中前端页面搭建(二)

【Vue.js实现前后端分离架构中前端页面搭建】九、Vue的事件处理Vue的事件都是使用 v-on:事件类型 进行绑定。也可以使用事件类型进行操作。其中事件类型和之前学习jQuery中事件名称是一样。示例中都是以点击事件为例。1. 直接操作属性值代码示例中通过点击按钮对counter值加一。…

边缘计算的五个大坑,里面一定有你跳过的

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | 砍柴网责编 | 阿秃在规模和增速方面&#xff0c;美国市场调研公司CB Insights估算&#xff0c;到2023年全球边缘计算行业&#xff0c;整体市场容量有望达到340亿美元。Technavio估计&#xff0c;从2018年到2022年&#xff0…

黑科技揭秘:眼科大夫如何应用5G+8K完成远程会诊?

秋季苹果新品发布会带来了iPhone迄今最大的显示屏&#xff0c;超视网膜显示屏比以往的iPhone屏幕更加清晰绚丽。随着影像技术的不断发展&#xff0c;多种多样的信息元素&#xff0c;通过一块块屏幕与我们交互&#xff0c;不管是手机手屏还是电视&#xff0c;不管是液晶还是OLED…

基于TableStore的物联网元数据管理

背景 常见的企业级无线接入方案有两种&#xff0c;分别被称作廋AP和胖AP。瘦AP&#xff08;ACAP&#xff09;架构为比较传统的企业级无线接入方案&#xff0c;主要优点就是漫游体验好&#xff0c;但是AC宕机的话会导致所属的AP全部无法工作。对于大型的办公场所&#xff0c;漫…

BDTC 2019 | 七个开发者能干多大的事?​

2015年&#xff0c;马云带领阿里巴巴集团的高管拜访了位于芬兰游戏公司supercell这家公司开发出了《部落战争》、《皇室战争》、《海岛奇兵》等App端知名游戏图片来自多玩BBS社区但是&#xff0c;这么知名的游戏公司开发团队当时却不足7人&#xff01;整个团队好像cell一样&…

Mac 神兵利器(三) 使用Intellij IDEA打造全栈IDE

前言 作为一个开发者&#xff0c;包括职业与业余&#xff0c;相信大家都在使用集成开发环境IDE。作为专业的开发者&#xff0c;相信大家都是Full Stack Developer&#xff0c;意味着我们的日常开发通常都会涉及多种编程语言比如Java、Python、Shell、Golang、大前端等&#xf…

阿里云与MongoDB达成战略合作,成“唯一”;苹果将推出三款5G版iPhone;谷歌正式推出 TensorFlow 企业版……...

戳蓝字“CSDN云计算”关注我们哦&#xff01;嗨&#xff0c;大家好&#xff0c;重磅君带来的【云重磅】特别栏目&#xff0c;如期而至&#xff0c;每周五第一时间为大家带来重磅新闻。把握技术风向标&#xff0c;了解行业应用与实践&#xff0c;就交给我重磅君吧&#xff01;重…

RabbitMQ 镜像集群队列_集群高可用篇_03

文章目录一、普通集群搭建1. 停止 全部 MQ服务节点2. 文件(.erlang.cookie)同步3. 组成集群操作3. slave 加入集群操作4. 查看集群状态5. 访问管控台界面二、配置镜像队列2.1. 镜像队列思路2.2. 策略执行2.3. 登录管控台查看配置的策略信息一、普通集群搭建 1. 停止 全部 MQ服…

MaxCompute Tunnel上传典型问题场景

数据问题 Q&#xff1a;使用Tunnel Java SDK上传数据&#xff0c;上传数据可以自动分配到各个分区吗&#xff1f; A&#xff1a;目前Tunnel是无法自动上传数据并自动分配到各个分区的&#xff1a;每一次上传只支持数据上传到一张表或表的一个分区&#xff0c;有分区的表一定要…

开箱即用的安全方案:MaxCompute数据安全方案介绍

MaxCompute 是一个支持多租户的统一大数据处理平台&#xff0c;不同的用户对数据安全需求不尽相同。为了满足不同租户对数据安全的灵活需求&#xff0c;MaxCompute 支持项目空间级别的安全配置&#xff0c;ProjectOwner 可以定制适合自己的外部账号支持和鉴权模型并且在某种程度…

RabbitMQ+haproxy+keeplived 高可用负载均衡+镜像集群模式_集成负载均衡组件 Ha-Proxy_02

服务器IPhostname节点说明端口管控台地址账号密码192.168.0.115mq-01rabbitmq master5672http://192.168.0.115:15672guestguest192.168.0.117mq-02rabbitmq slave5672http://192.168.0.117:15672guestguest192.168.0.118mq-03rabbitmq slave5672http://192.168.0.118:15672gue…

腾讯云连续三年登上KVM开源贡献榜,引领KVM技术标准!

近日在KVM社区最为重要和权威的大会KVM Forum上&#xff0c;2019年全球企业对KVM的贡献排名正式对外公布&#xff0c;腾讯云凭借本年度40个patch的贡献名列全球第七&#xff0c;连续三年成为国内唯一登榜的云计算服务商。 对此腾讯云虚拟化资深研发专家李万鹏介绍&#xff0c;腾…

异构计算:软硬件结合全栈助力AI大爆发

9月20日上午&#xff0c;杭州云栖小镇E1-2会场&#xff0c;备受业界关注的2018年杭州云栖大会异构计算专场召开。 近年来&#xff0c;人工智能持续爆发&#xff0c;对算力提出了更高的要求。异构计算作为大计算时代的解决方案&#xff0c;意在打破传统通用计算的限制&#xff…

弹性计算平台技术:云服务器“安全”“稳定”“弹性”的基石

9月19日上午9点&#xff0c;杭州云栖小镇E1-3会场&#xff0c;2018年杭州云栖大会弹性计算平台技术专场拉开帷幕。 弹性计算系列产品是云时代的基石产品之一&#xff0c;一直备受外界关注。作为弹性计算团队在本届云栖大会的开场大戏&#xff0c;平台技术专场吸引了超过200位与…

RabbitMQ+haproxy+keeplived 高可用负载均衡+镜像集群模式_集成高性能高可用组件 Keepalived_03

服务器IPhostname节点说明端口管控台地址账号密码192.168.0.115mq-01rabbitmq master5672http://192.168.0.115:15672guestguest192.168.0.117mq-02rabbitmq slave5672http://192.168.0.117:15672guestguest192.168.0.118mq-03rabbitmq slave5672http://192.168.0.118:15672gue…

AirPods Pro 到底「Pro」在哪里?

2019 嵌入式智能国际大会即将来袭&#xff01;购票官网&#xff1a;https://dwz.cn/z1jHouwEAirPods Pro一出&#xff0c;又被网友疯狂恶搞。有吐槽AirPods Pro&#xff0c;只是AirPods的复制版本的。有吐槽AirPods Pro&#xff0c;像个吹风机的。有吐槽像植物大战僵尸的。2018…

python pow和**_「Python学习笔记」Python函数高级应用

Python, 函数本身也是一个对象函数既可以赋值&#xff0c;也可以用作其他函数的参数&#xff0c;还可作为其他函数的返回值。使用函数变量Python的函数也是一种值&#xff1a;所有函数都是function对象&#xff0c;这意味着可以把函数本身赋值给变量&#xff0c;就像把整数、浮…

黑科技揭秘:如何通过阿里云超算,使得汽车仿真效率提升25%

在汽车行业&#xff0c;过去有一句俗话&#xff0c;一辆车从设计到下线&#xff0c;“至少要11辆真实碰撞试验”&#xff0c;今天&#xff0c;在现代化的汽车制造业&#xff0c;通过长期发展的设计和仿真软件&#xff0c;几乎所有的环节&#xff0c;都可以做到设计与仿真一体化…

更改应用程序图标_苹果更新 TestFlight 应用图标,增加更多拟真细节

TestFlight 是苹果公司针对开发者分发测试软件的应用&#xff0c;开发者可通过 TestFlight 向最多1万名测试者分发待测试应用。测试者在测试使用软件的同时&#xff0c;还可以向开发者提供使用意见或遇到的问题。最近&#xff0c;标志情报局发现&#xff0c;TestFlight 应用程序…