雪花算法【snowflake】

简介: 雪花算法【snowflake】

雪花算法

在这里插入图片描述
为什么需要分布式全局唯一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 区分)并且效率较高
结构

在这里插入图片描述

号段解析:

1bit ,

  • 不用,因为二进制中最高位是符号位,毫秒级,生成的id一般用整数,所以最高位 0

41bit - 时间戳,用来记录时间戳,毫秒级,

  • 41位可以表示 2 ^ {41}-1个数字
  • 如果只用来表示正整数(计算机中正整数包含0)。可以表示数值范围:0 至 2^{41}-1 , 减1 是因为表示的数值是从0开始算的 ,而不是1.
  • 也就是说 41 位可以表示 2 ^ {41}-1 个毫秒的值,装换成单位年则 (2^{41}-1)/ (1000 60 60 24 365)=69年

10bit- 工作机器ID,用来记录工作机器ID

  • 可以部署在 2^{10} = 1024 个节点,包括5位 datacenterId 和 5位的 workeId
  • 5位(bit)可以表示的最大正整数是 2 ^ {5}-1 =31 , 即可以用0、1、2、3....31这32个数字,来表示不同的datacenterId 或者 workId

12 bit -序列号,序列号,用来记录同毫秒内产生的不同的id。

  • 12位可以表示的最大正整数是2^{12}-1 = 4095 即可以用0、1、2、34094 这 4095个数字来表示同一机器同一时间(毫秒)产生的4095个ID序号

SnowFlake可以保证

  • 所有生成的id 按时间趋势递增
  • 整个分布式系统内不会产生重复的id
源码

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

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

java版❄️雪花算法

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 < 10; i++) {
            long id = idWorker.nextId();
            System.out.println(Long.toBinaryString(id));
            System.out.println(id);
        }
    }
}
springboot整合雪花算法
  1. 新建项目snowflake
  2. pom
<dependencies>
    <!--hutool 引入糊涂工具包,测试雪花算法-->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-captcha</artifactId>
        <version>5.3.8</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
  1. yml
server:
  port: 7777
  1. 新建 utils包 IdGeneratorSnowflake 类
@Slf4j
@Component
public class IdGeneratorSnowflake {

    private long workerId = 0;  //第几号机房
    private long datacenterId = 1;  //第几号机器
    private Snowflake snowflake = IdUtil.createSnowflake(workerId, datacenterId);

    @PostConstruct  //构造后开始执行,加载初始化工作
    public void init(){
        try{
            //获取本机的ip地址编码
            workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());
            log.info("当前机器的workerId: " + workerId);
        }catch (Exception e){
            e.printStackTrace();
            log.warn("当前机器的workerId获取失败 ----> " + e);
            workerId = NetUtil.getLocalhostStr().hashCode();
        }
    }

    public synchronized long snowflakeId(){
        return snowflake.nextId();
    }

    public synchronized long snowflakeId(long workerId, long datacenterId){
        Snowflake snowflake = IdUtil.createSnowflake(workerId, datacenterId);
        return snowflake.nextId();
    }

    //测试
    public static void main(String[] args) {
        System.out.println(new IdGeneratorSnowflake().snowflakeId());   //1277896081711169536
    }
}
  1. 新建service包 OrderService 接口 与 service.impl包 OrderServiceImpl 实现OrderService的接口
public interface OrderService {

    String getIDBySnowFlake();
}
@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private IdGeneratorSnowflake idGenerator;

    public String getIDBySnowFlake() {
        //新建线程池(5个线程)
        ExecutorService threadPool = Executors.newFixedThreadPool(5);

        for (int i = 1; i <= 20; i++) {
            threadPool.submit(() -> {
                System.out.println(idGenerator.snowflakeId());
            });
        }

        threadPool.shutdown();

        return "hello snowflake";
    }
}
  1. 新建 controller 包 OrderController
@RestController
public class OrderController {

    @Autowired
    private OrderService orderService;

    @RequestMapping("/snowflake")
    public String index(){
        return orderService.getIDBySnowFlake();
    }

}
  1. 主启动类
@SpringBootApplication
public class MainApp {
    public static void main(String[] args) {
        SpringApplication.run(MainApp.class, args);
    }
}

启动项目 浏览器输入http://localhost:7777/snowflake

在这里插入图片描述

优缺点

在这里插入图片描述

解决时钟回拨问题

  • 百度开源的分布式唯一ID生成器 UidGenerator
  • 美团开源的分布式ID生成系统 Leaf
相关文章
|
6月前
|
算法 Java
雪花算法生成id
雪花算法生成id
|
算法 安全 NoSQL
雪花算法的实现原理
一位工作4年的小伙伴,去某东面试时被问到这样一道题,说请你简述一下雪花算法的实现原理。屏幕前的小伙伴,如果你遇到这个问题,你会怎么回答?
215 0
|
缓存 算法 JavaScript
分享一个开源一个新的雪花算法(雪花漂移)
  IdGenerator介绍   用一种全新的雪花漂移算法,让ID更短、生成速度更快。   核心在于缩短ID长度的同时,还能保持每毫秒并发处理量(50W/0.1s),且能保持伸缩能力。   需求来源   1.作为架构设计的你,想要解决数据库主键唯一的问题。   2.你希望这个主键是用最少的存储空间,索引速度更快。   3.你还会考虑在分库分表(合库合表)的时候,主键值能直接使用,并能反映业务时序。
793 0
|
存储 算法 Java
雪花算法(snowflake) :分布式环境,生成全局唯一的订单号
雪花算法(snowflake) :分布式环境,生成全局唯一的订单号
1584 0
雪花算法(snowflake) :分布式环境,生成全局唯一的订单号
|
5月前
|
算法 数据中心 C++
基于C++雪花算法工具类Snowflake -来自chatGPT
基于C++雪花算法工具类Snowflake -来自chatGPT
|
4月前
|
存储 算法 Java
分布式自增ID算法---雪花算法(SnowFlake)Java实现
分布式自增ID算法---雪花算法(SnowFlake)Java实现
287 0
|
6月前
|
缓存 算法 关系型数据库
深度思考:雪花算法snowflake分布式id生成原理详解
雪花算法snowflake是一种优秀的分布式ID生成方案,其优点突出:它能生成全局唯一且递增的ID,确保了数据的一致性和准确性;同时,该算法灵活性强,可自定义各部分bit位,满足不同业务场景的需求;此外,雪花算法生成ID的速度快,效率高,能有效应对高并发场景,是分布式系统中不可或缺的组件。
1647 2
深度思考:雪花算法snowflake分布式id生成原理详解
|
6月前
|
算法 Java 数据中心
分布式ID生成系统之雪花算法详解
在当今的云计算和微服务架构盛行的时代,分布式系统已成为软件开发的重要组成部分。随着系统规模的扩大和业务的复杂化,对数据一致性和唯一性的要求也越来越高,尤其是在全局唯一标识符(ID)的生成上。因此,分布式ID生成系统应运而生,成为保证数据唯一性和提高系统可扩展性的关键技术之一。雪花算法(Snowflake)是Twitter开源的一种算法,用于生成64位的全局唯一ID,非常适用于分布式系统中生成唯一标识符。下面我们将深入探讨雪花算法的原理、结构和实现方式。
258 2
 分布式ID生成系统之雪花算法详解
|
算法
雪花算法(分布式自增长ID)
雪花算法(分布式自增长ID)
80 0
|
算法 中间件
SnowFlake 雪花算法和原理(分布式 id 生成算法)
SnowFlake 雪花算法和原理(分布式 id 生成算法)
207 0
下一篇
无影云桌面