java不规则算法_分布式id生成算法 snowflake 详解

背景

在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。如在支付流水号、订单号等,随者业务数据日渐增长,对数据分库分表后需要有一个唯一ID来标识一条数据或消息,数据库的自增ID显然不能满足需求,此时一个能够生成全局唯一ID的系统是非常必要的。

生成的唯一id需要具备哪些条件

全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求。

趋势递增:在MySQL InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用B-tree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。

单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求。

信息安全:如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要ID无规则、不规则。

UUID

关于分布式id,很多人会想到使用UUID,UUID在唯一性上确实可以达到这个目的,但它也存在很大的缺陷

优点:

性能非常高:本地生成,没有网络消耗。

缺点:

不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。

信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。

ID作为主键时在特定的环境会存在一些问题,比如做DB主键的场景下,UUID就非常不适用。(MySQL官方有明确的建议主键要尽量越短越好,36个字符长度的UUID不符合要求。对MySQL索引不利:如果作为数据库主键,在InnoDB引擎下,UUID的无序性可能会引起数据位置频繁变动,严重影响性能。)

snowflake

"世界上没有一片雪花是相同的",这大概是snowflake名字的由来吧。

SnowFlake算法是Twitter设计的一个可以在分布式系统中生成唯一的ID的算法,它可以满足Twitter每秒上万条消息ID分配的请求,这些消息ID是唯一的且有大致的递增顺序。

snowflake算法的原理

SnowFlake算法产生的ID是一个64位的整型,结构如下(每一部分用“-”符号分隔):

330bb01c2820?from=timeline

snowflake.png

需要注意的是64位是二进制,2的64次方 = 18446744073709551616,这就是能表示的id的范围,范围可以通过扩展序列号或则工作机器id来增加id的上限。

1位标识部分

在java中由于long的最高位是符号位,正数是0,负数是1,一般生成的ID为正数,所以为0;

41位时间戳部分

这个是毫秒级的时间,一般实现上不会存储当前的时间戳,而是时间戳的差值(当前时间-固定的开始时间),这样可以使产生的ID从更小值开始;41位的时间戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年;

10位节点部分

Twitter实现中使用前5位作为数据中心标识,后5位作为机器标识,可以部署1024个节点,在Spring Cloud中可以为每一个实例生成唯一的机器识别码,这样就能保证每个实例中生成的id都不一样。

12位序列号部分

支持同一毫秒内同一个节点可以生成4096(2的12次方)个ID,这个同样可以扩展,但其实每毫秒生成4096个id已经能满足大部分场景了。

算法的java代码

/**

* twitter的snowflake算法 -- java实现

*

* @author beyond

* @date 2016/11/26

*/

public class SnowFlake {

/**

* 起始的时间戳(最后的时间 = 当前时间戳 - 起始的时间戳)

*/

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) {

// 校验datacenterId长度超过范围就抛异常

if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {

throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");

}

// 校验machineId长度超过范围就抛异常

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;

//使用二进制的"|"运算符将4部分的值整合成我们需要的id

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(2, 3);

for (int i = 0; i < (1 << 12); i++) {

System.out.println(snowFlake.nextId());

}

}

}

总结

10位节点部分在代码中分成了两个5位节点,看具体需求,也可以用一个10位节点代替。snowflake更多的是提供一种算法思想,具体的id生成逻辑可以在此基础上进一步的优化。

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

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

相关文章

Android中下载、安装和卸载(原)

应用场景&#xff1a;在检查版本更新的时候经常需要从服务器端下载然后安装到手机中 使用工具&#xff1a; XUtils&#xff0c;这个开源的框架真的是需要花大把时间去阅读和理解的&#xff0c;十分有用的&#xff0c;on the way &#xff01; fighting&#xff01; 下载&#x…

Android加载图片OOM错误解决方式

前几天做项目的时候&#xff0c;甲方要求是PAD &#xff08;SAMSUNG P600 10.1寸 2560*1600&#xff09;的PAD上显示高分辨率的大图片。 SQLITE採用BOLD方式存储图片&#xff0c;这个存取过程就不说了哈&#xff0c;网上一大堆。 可是在加载/读取/显示图片的时候会报OOM错误&am…

C# 对Ini文件操作(C# ini文件操作类)

*************************************************** 更多精彩&#xff0c;欢迎进入&#xff1a;http://shop115376623.taobao.com *************************************************** /* C# 对Ini文件操作&#xff08;C# ini文件操作类&#xff09; [IniFiles.cs] 蝶晓…

python对文件进行读写操作

2019独角兽企业重金招聘Python工程师标准>>> python进行文件读写的函数是open或file file_handler open(filename,,mode&#xff09; Table mode 模式描述r以读方式打开文件&#xff0c;可读取文件信息。w以写方式打开文件&#xff0c;可向文件写入信息。如文件存在…

android:contentDescription

android:contentDescription这个属性相信大家并不陌生&#xff0c;在ImageButton的使用过程中如果不添加这个属性会有警告信息。 那么android:contentDescription究竟是干什么的呢&#xff1f;今天查了下资料才知道这个属性的真正作用。 该属性为视力障碍的用户提供方便&#x…

c#中bin,obj,properties文件夹的作用

*************************************************** 更多精彩&#xff0c;欢迎进入&#xff1a;http://shop115376623.taobao.com *************************************************** Bin目录用来存放编译的结果&#xff0c;bin是二进制binrary的英文缩写&#xff0c;因为…

getAttribute实例例java_Java ExifInterface.getAttribute方法代码示例

import android.media.ExifInterface; //导入方法依赖的package包/类public static void copyExif(ExifInterface originalExif, int width, int height, String imageOutputPath) {String[] attributes new String[]{ExifInterface.TAG_APERTURE,ExifInterface.TAG_DATETIME,…

检测SDWebImage有没有缓存图片 IOS 获取网络图片大小

判断图片是否缓存NSURL *url [NSURL URLWithString:[model.content objectForKey:"image"]];//请求网络地址数据的同步方法//因为这个方法在子线程(全局队列)中执行,所以不需要考虑死线程的问题SDWebImageManager *manager [SDWebImageManager sharedManager];[man…

mac 下 使用 java运行 class 文件 总是提示 “错误: 找不到或无法加载主类”的解决方法...

发现问题 切换到mac平台后&#xff0c;突然想写点程序运行在mac下&#xff0c;想到mac自带java&#xff0c;会方便好多。不过在这过程中遇到了麻烦&#xff1a; 总是提示 “错误: 找不到或无法加载主类” 工程结构 查了好久&#xff0c;终于找到原型所在&#xff0c;发现网上很…

[转]VisualStudio如何组织解决方案的目录结构

*************************************************** 更多精彩&#xff0c;欢迎进入&#xff1a;http://shop115376623.taobao.com *************************************************** 解决方案与项目&#xff1a; 从VC6之后VC系列就使用解决方案&#xff08;Solution&…

java几种删除_几种删除Linux目录的方法

在Linux中有很多方法可以删除目录&#xff0c;在图形化界面可以利用文件管理器&#xff0c;或者通过终端删除。本文将介绍在文本界面使用命令删除目录。使用rmdir删除目录Rmdir命令间成“remove directory”&#xff0c;用于删除空目录的命令。例如&#xff0c;删除一个名为“M…

php公钥模数,php – 如何从公共指数和RSA模数生成DER / PEM证书?

众所周知,公钥由公共指数和模数组成.我的问题是&#xff1a;如何从公共指数和RSA模数生成DER / PEM证书&#xff1f;非常感谢你提前.解决方法:使用公共指数和模数,你可能希望做的最好的事情是得到这样的东西&#xff1a;-----BEGIN PUBLIC KEY-----MIGGAoGAfHlcdrcuOK6C02rbGR3…

C# DataTable的詳細用法

*************************************************** 更多精彩&#xff0c;欢迎进入&#xff1a;http://shop115376623.taobao.com *************************************************** DataTable 是一个临时保存数据的网格虚拟表(表示内存中数据的一个表。)。DataTable是A…

【SpringMVC】SpringMVC系列6之@CookieValue 映射请求Cookie 值

6、CookieValue 映射请求Cookie 值 6.1、示例 CookieValue 可让处理方法入参绑定某个 Cookie 值&#xff0c;示例如下&#xff1a;

杭电OJ-2104_hide handkerchief超简洁代码

#include<iostream> using namespace std; int n, m;; int zz(int a, int b) {return b0 ? a: zz(b, a%b); } int main() {while (cin >> n >> m&&n ! -1 && m ! -1)cout << (nb(n, m) 1 ? "YES" : "POOR Haha"…

php 年月日 中文,转换中文日期的PHP程序

转换中文日期的PHP程序本程序将中文日期输出为2001-12-23&#xff0c;并很好解决了“十”的问题&#xff0c;如“十一”和“二十一”中“十”的处理&#xff01;稍加修改可改为函数。跟随小编去看看吧&#xff01;希望对大家有所帮助&#xff01;$str"二零○一年十二月二十…

c# Invoke和BeginInvoke

*************************************************** 更多精彩&#xff0c;欢迎进入&#xff1a;http://shop115376623.taobao.com *************************************************** 转自&#xff1a;http://blog.3snews.net/html/30/34530-27563.html在多线程编程中&am…

Oracle手边常用70则脚本知识汇总

Oracle手边常用70则脚本知识汇总 作者&#xff1a;白宁超 时间&#xff1a;2016年3月4日13:58:36 摘要: 日常使用oracle数据库过程中&#xff0c;常用脚本命令莫不是用户和密码、表空间、多表联合、执行语句等常规操作。另外表的导入导出也很常用&#xff0c;这些脚步命令之前都…

php常见的面试题目

一. 基本知识点1.1 HTTP协议中几个状态码的含义:503 500 401 403 404 200 301 302。。。200 : 请求成功&#xff0c;请求的数据随之返回。301 : 永久性重定向。302 : 暂时行重定向。401 : 当前请求需要用户验证。403 : 服务器拒绝执行请求&#xff0c;即没有权限。404 : 请求失…

php表示私有变量的是,PHP 访问私有和受保护的成员变量

示例反射通常用作软件测试的一部分&#xff0c;例如在运行时创建/实例化模拟对象。这对于在任何给定时间点检查对象的状态也非常有用。这是在单元测试中使用Reflection来验证受保护的类成员是否包含期望值的示例。下面是一个非常基础的汽车课。它具有受保护的成员变量&#xff…