分布式部署ID全局配置之雪花算法

本文详细介绍了Twitter的雪花算法(SnowFlake),用于生成全局唯一且趋势递增的分布式ID。讨论了ID生成的硬性要求,如全局唯一、趋势递增和信息安全。阐述了雪花算法的时间戳、工作机器ID和序列号的设计,以及如何根据业务需求调整算法参数。文章还提供了Java实现雪花算法的建议和不同场景下的优化方案。
摘要由CSDN通过智能技术生成

分布式部署ID全局配置之雪花算法

前言

为什么需要分布式全局唯一ID 以及分布式ID的业务需求?

  • 在复杂分布式系统中,往往需要对大量对数据和消息进行标识
  • 如在美团、支付、餐饮 中 系统的数据日渐增长,对数据分库分表需要有一个唯一来标识一条数据或消息
  • 此时一个能够生成全局唯一ID的系统是非常有必要的
ID生成规则部分硬性要求
  • 全局唯一 :不能出现重复的ID,要 唯一标识
  • 趋势递增 :在Mysql 的InnoDB引擎使用的是聚集索引,由于多数RDBMS 使用的是Btree数据结构来存储数据,在主键的选择上面我们应该尽量使用有序的主键保证数据写入
  • 单调递增 :保证下一个ID一定大于上一个ID,例如事物版本号,增量消息
  • 信息安全 :如果ID是连续的,恶意用户的扒取数据就非常容易来,直接按照顺序下载指定的URL,如果是订单号就更危险来,竞争对手可以知道我们一天的单量,所以在一些应用场景下,需要ID不规则
  • 含时间戳 :这样就能够在开发中快速了解这个分布式id的生成时间

ID生成系统的可用性要求
  • 高可用 :发一个获取分布式ID的请求,服务器就要保证99.99%的情况下给我创建一个唯一分布式ID
  • 低延迟 :发一个获取分布式ID的请求,服务器就是要快,极速
  • 高QPS :假如并发一口气10万个创建分布式ID请求同时杀过来,服务器要顶的住一下子成功创建10w个分布式ID
我们平时的方案

UUID 、 数据库自增主键 、基于Redis 生成全局ID策略

弊端

UUID 不能生成顺序,递增的数据,并且长,不是很推荐

数据库自增,集群多的情况下,扩容简直就是噩梦

Redis 使用Redis INCR 和 INCRBY 实现

snowflake(雪花算法)

Twitter的分布式自增ID算法:snowflake(雪花算法)

概述

最初 Twitter把存储系统从Mysql 迁移到 Cassandra (由Facebook 开发一套开源分布式Nosql系统) 因为Cassandra没有顺序ID生成机制,所以开发成了这样一套全局唯一 ID生成服务

Twitter 的分布式雪花算法SnowFlake , 经测试 snowflake 每秒能产出26 万个自增可排序的ID

  1. twitter的SnowFlake生成ID能够按照时间有序生成
  2. SnowFlake 算法生成id 的结果是一个64 bit 大小的整数,为一个Long 型(转换成字符后长度19位)
  3. 分布式系统不会产生ID碰撞(由datacenter 和 workerld 区分)并且效率较高

雪花算法原理讲解

在这里插入图片描述

  1. 1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0。
  2. 41bit-时间戳,用来记录时间戳,毫秒级。
    - 41位可以表示[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oll7dpVz-1605695234754)(https://math.jianshu.com/math?formula=2%5E%7B41%7D-1)]个数字,
    - 如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是:0 至 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LCzpD94C-1605695234762)(https://math.jianshu.com/math?formula=2%5E%7B41%7D-1)],减1是因为可表示的数值范围是从0开始算的,而不是1。
    - 也就是说41位可以表示[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xEQQjn42-1605695234764)(https://math.jianshu.com/math?formula=2%5E%7B41%7D-1)]个毫秒的值,转化成单位年则是[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZUNl2pqO-1605695234768)(https://math.jianshu.com/math?formula=(2%5E%7B41%7D-1)]%20%2F%20(1000%20*%2060%20*%2060%20*%2024%20*365)%20%3D%2069)年
  3. 10bit-工作机器id,用来记录工作机器id。
    - 可以部署在[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J7uNLXEq-1605695234771)(https://math.jianshu.com/math?formula=2%5E%7B10%7D%20%3D%201024)]个节点,包括5位datacenterId和5位workerId
    - 5位(bit)可以表示的最大正整数是[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-egRhWNu3-1605695234774)(https://math.jianshu.com/math?formula=2%5E%7B5%7D-1%20%3D%2031)],即可以用0、1、2、3、…31这32个数字,来表示不同的datecenterId或workerId
  4. 12bit-序列号,序列号,用来记录同毫秒内产生的不同id。
    - 12位(bit)可以表示的最大正整数是[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0YyvYCu9-1605695234774)(D:%5Ccustersofeware%5Ctypora%5Cimage%5Cmath)],即可以用0、1、2、3、…4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。

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

SnowFlake可以保证:

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

twitter的雪花算法:https://github.com/twitter-archive/snowflake

GitHub上java版的雪花算法:
https://github.com/beyondfengyu/SnowFlake/blob/master/SnowFlake.java

使用建议

1、改进

其实雪花算法就是把id按位打散,然后再分成上面这几块,用位来表示状态,这其实就是一种思想。
所以咱们实际在用的时候,也不必非得按照上面这种分割,只需保证总位数在64位即可

如果你的业务不需要69年这么长,或者需要更长时间
用42位存储时间戳,(1L << 42) / (1000L * 60 * 60 * 24 * 365) = 139年
用41位存储时间戳,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年
用40位存储时间戳,(1L << 40) / (1000L * 60 * 60 * 24 * 365) = 34年
用39位存储时间戳,(1L << 39) / (1000L * 60 * 60 * 24 * 365) = 17年
用38位存储时间戳,(1L << 38) / (1000L * 60 * 60 * 24 * 365) = 8年
用37位存储时间戳,(1L << 37) / (1000L * 60 * 60 * 24 * 365) = 4年

如果你的机器没有那么1024个这么多,或者比1024还多
用7位存储机器id,(1L << 7) = 128
用8位存储机器id,(1L << 8) = 256
用9位存储机器id,(1L << 9) = 512
用10位存储机器id,(1L << 10) = 1024
用11位存储机器id,(1L << 11) = 2048
用12位存储机器id,(1L << 12) = 4096
用13位存储机器id,(1L << 13) = 8192

如果你的业务,每个机器,每毫秒最多也不会4096个id要生成,或者比这个还多
用8位存储随机序列,(1L << 8) = 256
用9位存储随机序列,(1L << 9) = 512
用10位存储随机序列,(1L << 10) = 1024
用11位存储随机序列,(1L << 11) = 2048
用12位存储随机序列,(1L << 12) = 4096
用13位存储随机序列,(1L << 13) = 8192
用14位存储随机序列,(1L << 14) = 16384
用15位存储随机序列,(1L << 15) = 32768
注意,随机序列建议不要太大,一般业务,每毫秒要是能产生这么多id,建议在机器id上增加位

如果你的业务量很小,比如一般情况下每毫秒生成不到1个id,此时可以将随机序列设置成随机开始自增
比如从0到48随机开始自增,算是一种优化建议

如果你有多个业务,也可以拿出来几位来表示业务,比如用最后4位,支持16种业务的区分

如果你的业务特别复杂,可以考虑128位存储,不过这样的话,也可以考虑使用uuid了,但uuid无序,这个有序

如果你的业务很简单,甚至可以考虑32位存储,时间戳改成秒为单位…

2、总结:

合理的根据自己的实际情况去设计各个唯一条件的组合,雪花算法只是提供了一种相对合理的方式。
雪花算法这种用位来表示状态的,我们还可以用在其他方面,比如数据库存储,可以用更小的空间去表示不同的状态位
包括各种底层的比如序列化,也是有用到拆解位,充分利用存储

算法实现

经过学习,本人写了两种方式去获取全局ID。

方式1:使用工具类进行获取ID

package com.lyj.demo.utils;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadLocalRandom;

import static java.util.concurrent.Executors.newFixedThreadPool;

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

/**
 * @author
 * @date 2020/11/18 10:14
 * 雪花算法分布式唯一ID生成工具
 */
public class SnowflakeIdUtil {
   

    private static final Logger logger = LoggerFactory.getLogger(SnowflakeIdUtil.class);

    /** 开始时间戳 */
    private final long startTimeStamp = 1605665795726L;

    /** 机器ID所占位数 */
    private final long workIdBits = 5L;

    /** 数据标志Id所占位数 */
    private final long dataCenterIdBits = 5L;

    /** 支持的机器最大ID,结果是31,这里受机器设置的ID所占位数大小变化 */
    private final long maxSupportWorkId = -1L ^ (-1L << workIdBits);

    /** 支持的最大数据标识ID,结果是31,这里受数据标识ID所占位数大小变化 */
    private final long maxSupportDataCenterId = -1L ^ (-1L << dataCenterIdBits);

    /** 序列号ID所占位数 */
    private final long sequenceBits = 12L;

    /** 工作厂房ID向左移12位 */
    private final long workIdLeftShift = sequenceBits;

    /** 数据标识ID向左移17位(12+5) */
    private final long dataCenterIdLeftShift 
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凌兮~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值