分布式ID生成策略

news2024/11/25 23:01:12

文章目录

  • 分布式ID必要性
  • 1.UUID
  • 2.基于DB的自增主键方案
  • 3.数据库多主模式
  • 4.号段模式
  • 5.Redis
  • 6.Zookeeper
  • 7.ETCD
  • 8.雪花算法
  • 9.百度(Uidgenerator)
  • 10.美团(Leaf)
  • 11.滴滴(TinyID)

分布式ID必要性

业务量小于500W的时候单独一个mysql即可提供服务,再大点的时候就进行读写分离也可以应付过来。

但当主从同步也扛不住的是就需要分表分库了,但分库分表后需要有一个唯一ID来标识一条数据,数据库的自增ID显然不能满足需求;

特别一点的如订单、优惠券也都需要有唯一ID做标识。

此时一个能够生成全局唯一ID的系统是非常必要的。

那么这个全局唯一ID就叫分布式ID,分布式ID需满足哪些条件?

条件解释
全局唯一基本要求就是必须保证ID是全局性唯一的。
高性能高可用低延时,ID生成响应要快。
高可用无限接近于100%的可用性
好接入遵循拿来主义原则,在系统设计和实现上要尽可能的简单
趋势递增最好趋势递增,这个要求就得看具体业务场景了,一般不严格要求

比如我们的身份证,就完美地满足了分布式ID的特性:

组成特点
2位省级或直辖市编码全局唯一
4位地区编码非唯一
8位出生日期非唯一(时钟回退),递增
顺序码(2位出生序列+1位性别)非唯一,递增
1位校验码验证组合序列的正确性

一言以蔽之: 全局唯一 + 局部唯一 + 按情况递增 全局唯一 + 局部唯一 + 按情况递增 全局唯一+局部唯一+按情况递增

不过由于身份证是18位的10进制数最多表示 1 0 18 10^{18} 1018条数据,这在计算机的二进制条件下是远远不够的。

1.UUID

UUID (Universally Unique Identifier),通用唯一识别码。

UUID是基于当前时间、计数器(counter)和硬件标识(通常为无线网卡的MAC地址)等数据计算生成的。

UUID由以下几部分的组合:

  1. 当前日期和时间,UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同。
  2. 时钟序列。
  3. 全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。

UUID 是由一组32位数的16进制数字所构成「 8 16 = 128 b i t 8^{16}=128bit 816=128bit」,以连字号分隔的五组来显示,形式为 8-4-4-4-12,总共有 36个字符(即三十二个英数字母和四个连字号)。

import java.util.UUID;

public class UU_ID {
    public static void main(String[] args) {
    	// ef0d86ea-b95d-4f31-bf22-9c8a0f39bc0a 基于随机数的UUID
        String uuid = UUID.randomUUID().toString().replace("-", "-");
        System.out.println(uuid);
    }
}
优点缺点
简单、代码方便没有排序,无法保证趋势递增
性能好,全球唯一使用字符存储,查询效率低
本地生成无网络消耗占用空间大,传输效率低

如果需求是只保证唯一性,那么UUID也是可以使用的,但是按照上面的分布式id的要求, UUID其实是不能做成分布式id的,原因如下:

  1. 首先分布式id一般都会作为主键,但是安装mysql官方推荐主键要尽量越短越好,UUID每一个都很长,所以不是很推荐。
  2. 既然分布式id是主键,然后主键是包含索引的,然后mysql的索引是通过b+树来实现的,每一次新的UUID数据的插入,为了查询的优化,都会对索引底层的b+树进行修改,因为UUID数据是无序的,所以每一次UUID数据的插入都会对主键生成的b+树进行很大的修改,带来很大压力,这一点很不好。
  3. 信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。

对于第二条:UUID的无序产生的IO压力,可以将UUID作为逻辑主键,物理主键仍然使用自增ID进行解决。

UUID生成策略:

UUID Version 1:基于时间的UUID

基于时间的UUID通过计算当前时间戳、随机数和机器MAC地址得到。
由于在算法中使用了MAC地址,这个版本的UUID可以保证在全球范围的唯一性。
但与此同时,使用MAC地址会带来安全性问题,这就是这个版本UUID受到批评的地方。
如果应用只是在局域网中使用,也可以使用退化的算法,以IP地址来代替MAC地址--Java的UUID往往是这样实现的(当然也考虑了获取MAC的难度)

UUID Version 2:DCE安全的UUID

DCE(Distributed Computing Environment)安全的UUID和基于时间的UUID算法相同,但会把时间戳的前4位置换为POSIX的UID或GID。
这个版本的UUID在实际中较少用到。

UUID Version 3:基于名字的UUID(MD5)

基于名字的UUID通过计算名字和名字空间的MD5散列值得到。
这个版本的UUID保证了:相同名字空间中不同名字生成的UUID的唯一性;
不同名字空间中的UUID的唯一性;相同名字空间中相同名字的UUID重复生成是相同的。

UUID Version 4:随机UUID

根据随机数,或者伪随机数生成UUID。
这种UUID产生重复的概率是可以计算出来的,
但随机的东西就像是买彩票:你指望它发财是不可能的,但狗屎运通常会在不经意中到来。

UUID Version 5:基于名字的UUID(SHA1)

和版本3的UUID算法类似,只是散列值计算使用SHA1(Secure Hash Algorithm 1)算法。

2.基于DB的自增主键方案

针对表结构的主键,我们常规的操作是在创建表结构的时候给对应的ID设置 auto_increment也就是自增选项。

但是这种方式我们清楚在单个数据库的场景中我们是可以这样做的,但如果是在分库分表的环境下。

直接利用单个数据库的自增肯定会出现问题。

因为ID要唯一,但是分表分库后只能保证一个表中的ID的唯一,而不能保证整体的ID唯一。

在这里插入图片描述

上面的情况我们可以通过单独创建主键维护表来处理。

在这里插入图片描述

举个例子来看看:

创建一个表结构

CREATE TABLE `sequence_id`
(
    `id`          bigint(20) unsigned NOT NULL auto_increment,
    `value`       char(10)            NOT NULL default '',
    `update_time` timestamp           NOT NULL DEFAULT CURRENT_TIMESTAMP on UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8;

然后我们通过更新ID操作来获取ID信息

BEGIN;

REPLACE INTO sequence_id(value) VALUES ('values');
SELECT last_insert_id();

COMMIT;
优点缺点
实现简单,ID单调自增DB单点存在宕机风险
数值类型查询速度快无法扛住高并发场景

3.数据库多主模式

单点数据库方式存在明显的性能问题,可以对数据库进行高可以优化,担心一个主节点挂掉没法使用,可以选择做双主模式集群,也就是两个MySQL实例都能单独生产自增的ID。

查看主键自增的属性

show variables like '%increment%'
Variable_nameValue
auto_increment_increment1 (步长)
auto_increment_offset1(起始值)
div_precision_increment4
innodb_autoextend_increment64

我们可以设置主键自增的步长从2开始,这样两个MySQL实例的自增ID分别就是:

在这里插入图片描述

但是如果两个还是无法满足咋办呢?

增加第三台MySQL实例需要人工修改一、二两台MySQL实例的起始值和步长,把第三台机器的ID起始生成位置设定在比现有最大自增ID的位置远一些。

但必须在一、二两台MySQL实例ID还没有增长到第三台MySQL实例的起始ID值的时候,否则自增ID就要出现重复了,必要时可能还需要停机修改。

所以这种在并发量比较高的情况下,如何保证扩展性其实会是一个问题。

在高并发情况下无能为力,依旧无法满足高并发场景。

4.号段模式

号段模式是当下分布式ID生成器的主流实现方式之一,号段模式可以理解为从数据库批量的获取自增ID,每次从数据库取出一个号段范围。

例如 (1,1000] 代表1000个ID,具体的业务服务将本号段,生成1~1000的自增ID并加载到内存。

表结构如下:

CREATE TABLE id_generator (
  id int(10) NOT NULL,
  max_id bigint(20) NOT NULL COMMENT '当前最大id',
  step int(20) NOT NULL COMMENT '号段的布长',
  biz_type    int(20) NOT NULL COMMENT '业务类型',
  version int(20) NOT NULL COMMENT '版本号',
  PRIMARY KEY (`id`)
) 

字段说明:

biz_type :代表不同业务类型

max_id :当前最大的可用id

step :代表号段的长度

version :是一个乐观锁,每次都更新version,保证并发时数据的正确性

等这批号段ID用完,再次向数据库申请新号段,对 max_id 字段做一次 update 操作,update max_id = max_id + step,update 成功则说明新号段获取成功,新的号段范围是(max_id, max_id +step]

update id_generator set max_id  = ${ max_id + step }, version = ${ version + 1 } 
where version = ${ version } and biz_type = 1;

在这里插入图片描述

由于多业务端可能同时操作,所以采用版本号version乐观锁方式更新,这种分布式ID生成方式不强依赖于数据库,不会频繁的访问数据库,对数据库的压力小很多。

但同样也会存在一些缺点比如:服务器重启,单点故障会造成ID不连续。

5.Redis

基于全局唯一ID的特性,我们可以通过RedisINCR命令来生成全局唯一ID。

在这里插入图片描述

同样使用Redis也有对应的缺点:

  1. RDB 数据备份存在丢失数据的问题,理论上ID可能重复。
  2. ID 生成的持久化问题,加上AOF 性能也会存在损耗。
  3. 如果Redis宕机了怎么进行恢复,单个节点宕机问题。

当然针对故障问题我们可以通过Redis集群来处理,比如我们有三个Redis的Master节点。

可以初始化每台Redis的值分别是1,2,3;然后分别把分布式ID的KEY用Hash Tags固定每一个master节点,步长就是master节点的个数。各个Redis生成的ID为:

  • A:1,4,7

  • B:2,5,8

  • C:3,6,9

优点缺点
不依赖于数据库,灵活方便,且性能优于数据库如果没有Redis数据库,需要安装配置,增加复杂度
数字ID有序,对分页处理和排序都很友好分布式环境下,数据一致性策略需要考虑
防止了Redis的单机故障集群节点确定是3个后,后面调整不是很友好

Redis分布式ID的简单案例

/**
 *  Redis 分布式ID生成器
 */
@Component
public class RedisDistributedId {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private static final long BEGIN_TIMESTAMP = 1659312000l;

    /**
     * 生成分布式ID
     * 符号位    时间戳[31位]  自增序号【32位】
     * @param item
     * @return
     */
    public long nextId(String item){
        // 1.生成时间戳
        LocalDateTime now = LocalDateTime.now();
        // 格林威治时间差
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        // 我们需要获取的 时间戳 信息
        long timestamp = nowSecond - BEGIN_TIMESTAMP;
        // 2.生成序号 --》 从Redis中获取
        // 当前当前的日期
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        // 获取对应的自增的序号
        Long increment = redisTemplate.opsForValue().increment("id:" + item + ":" + date);
        return timestamp << 32 | increment;
    }

}

6.Zookeeper

原理:利用zookeeper中的顺序节点的特性,制作分布式的序列号生成器(ID生成器)。

  • 在ID下创建持久的顺序节点。
  • 获取返回的节点名称。
  • 删除该节点。
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;


public class DistributedIDGenerator {

    private static final String ZK_ADDRESS = "localhost:2181";
    private static final String ZK_PATH = "/distributed_id";

    public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
        // 创建 ZooKeeper 客户端
        ZooKeeper zooKeeper = new ZooKeeper(ZK_ADDRESS, 5000, event -> {
            if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {
                System.out.println("ZooKeeper connected.");
            }
        });

        // 确保父节点存在
        if (zooKeeper.exists(ZK_PATH, false) == null) {
            zooKeeper.create(ZK_PATH, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }

        // 创建顺序节点
        String sequenceNodePath = zooKeeper.create(ZK_PATH + "/id-", "data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);

        // 获取节点名称
        String id = sequenceNodePath.substring(ZK_PATH.length() + 1);

        // 删除节点
        zooKeeper.delete(sequenceNodePath, -1);

        // 输出生成的 ID
        System.out.println("Generated ID: " + id);

        // 关闭 ZooKeeper 客户端
        zooKeeper.close();
    }
}

7.ETCD

ETCD 生成全局唯一 ID 的原理:

  • 在 ETCD 中,每个事务(tx)都有一个唯一的事务 ID,称为 main ID。这个 main ID 是全局递增且不重复的。
  • 一个事务可以包含多个修改操作(如 putdelete),每个操作称为一个 revision(修订)。这些修订共享同一个 main ID
  • 在一个事务内,连续的多个修改操作会被从 0 开始递增编号,这个编号称为 sub ID。每个 revision(修订) 都有一个唯一的 「main ID,sub ID」 组成唯一标识。

Revision的定义:

// A revision indicates modification of the key-value space.
// The set of changes that share same main revision changes the key-value space atomically. type revision struct {
// main is the main revision of a set of changes that happen atomically. main int64
// sub is the the sub revision of a change in a set of changes that happen
// atomically. Each change has different increasing sub revision in that
// set. sub int64 }

8.雪花算法

Snowflake,雪花算法是有Twitter开源的分布式ID生成算法,以划分命名空间的方式将 64bit 位分割成了多个部分,每个部分都有具体的不同含义。

在Java中64Bit位的整数是Long类型,所以在Java中Snowflake算法生成的ID就是long来存储的,具体如下:

在这里插入图片描述

  • 第一部分:占用1bit,第一位为符号位,无意义。因为二进制里第一个 bit 位如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。

  • 第二部分:41位的时间戳。41bit位可以表示 2 41 2 ^ {41} 241 个数,每个数代表的是毫秒,那么雪花算法的时间年限是 ( 2 41 ) / ( 1000 × 60 × 60 × 24 × 365 ) = 69 (2^{41})/(1000×60×60×24×365)=69 (241)/(1000×60×60×24×365)=69年。

  • 第三部分:5位的机房id。意思就是最多代表 2 5 2 ^ 5 25 个机房(32 个机房)。

  • 第四部分:5位的机器id。每个机房里可以有 2 5 2 ^ 5 25 个机器(32 台机器),也可以根据自己公司的实际情况确定。

  • 第五部分:12bit位是自增序列。可以表示 2 12 = 4096 2^{12}=4096 212=4096个数,一秒内可以生成4096个ID。

案例代码:

package com.boge.vip.utils;

/**
 * Twitter_Snowflake
 * SnowFlake的结构如下(每部分用-分开):
 * 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位的数据机器位(机房和机器不再区分统一占用10bit),可以部署在1024个节点,包括5位datacenterId和5位workerId
 * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号
 * 加起来刚好64位,为一个Long型。
 * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
 */
public class SnowflakeIdWorker {

    // ==============================Fields===========================================
    /**
     * 开始时间截 (2020-11-03,一旦确定不可更改,否则时间被回调,或者改变,可能会造成id重复或冲突)
     */
    private final long twepoch = 1604374294980L;

    /**
     * 机器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=====================================

    /**
     * 构造函数
     *
     */
    public SnowflakeIdWorker() {
        this.workerId = 0L;
        this.datacenterId = 0L;
    }

    /**
     * 构造函数
     *
     * @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();
    }

    /**
     * 随机id生成,使用雪花算法
     *
     * @return
     */
    public static String getSnowId() {
        SnowflakeIdWorker sf = new SnowflakeIdWorker();
        String id = String.valueOf(sf.nextId());
        return id;
    }

    //=========================================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(id);
        }
    }
}


优点缺点
不依赖于数据库,完全在内存中生成,性能好。严重依赖与系统时间的一致性,
每秒中能生成数百万的自增ID,容量大。如果系统时间被回调,或者改变,
存入数据库中,索引效率高,ID自增。可能会造成id冲突或者重复。

实际生产环境中我们应该怎么来应用雪花算法来实现分布式ID——实际中我们的机房并没有那么多,我们可以改进改算法,将10bit的机器id优化成业务表或者和我们系统相关的业务。

在这里插入图片描述

时针回拨问题【20:28 --》20:26】 【20:28 --》 20:30】:

问题场景解决方案?
回拨时间很短:(<=100毫秒)直接阻塞100毫秒
回拨时间适中:(>100毫秒 <500毫秒)维护这500毫秒时间戳的最大ID信息
回拨时间比较长:(>=500毫秒 <1000毫秒)通过分布式ID服务器的轮询处理
回拨时间很长:(>=1000毫秒)直接下线

9.百度(Uidgenerator)

源码地址:https://github.com/baidu/uid-generator。

中文文档地址:https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md

UidGenerator是百度开源的Java语言实现,基于Snowflake算法的唯一ID生成器。

它是分布式的,并克服了雪花算法的并发限制。

UidGenerator以组件形式工作在应用项目中,支持自定义workerId位数和初始化策略,从而适用于docker等虚拟化环境下实例自动重启、漂移等场景。

在实现上,UidGenerator通过借用未来时间来解决sequence天然存在的并发限制;

采用RingBuffer来缓存已生成的UID,并行化UID的生产和消费,同时对CacheLine补齐,避免了由RingBuffer带来的硬件级「伪共享」问题,最终单机QPS可达600万。

其实现原理和雪花算法并无二致,自定义号段,并且采用RingBuffer作为缓冲 从而提升性能。

需要的环境:JDK8+,MySQL(用于分配WorkerId)。

百度的Uidgenerator对结构做了部分的调整,具体如下:

在这里插入图片描述

UidGenerator的时间部分只有28位,这就意味着UidGenerator默认只能承受8.5年( 2 28 − 1 / 86400 / 365 2^{28}-1/86400/365 2281/86400/365)。

也可以根据你业务的需求,UidGenerator可以适当调整delta secondsworker node idsequence占用位数。

摘自官网 CachedUidGenerator

RingBuffer环形数组,数组每个元素成为一个slot。RingBuffer容量,默认为Snowflake算法中sequence最大值,且为 2 N 2^N 2N。可通过boostPower配置进行扩容,以提高RingBuffer 读写吞吐量。

Tail指针、Cursor指针用于环形数组上读写slot:

  • Tail指针
    表示Producer生产的最大序号(此序号从0开始,持续递增)。Tail不能超过Cursor,即生产者不能覆盖未消费的slot。当Tail已赶上curosr,此时可通过rejectedPutBufferHandler指定PutRejectPolicy

  • Cursor指针
    表示Consumer消费到的最小序号(序号序列与Producer序列相同)。Cursor不能超过Tail,即不能消费未生产的slot。当Cursor已赶上tail,此时可通过rejectedTakeBufferHandler指定TakeRejectPolicy

在这里插入图片描述

CachedUidGenerator采用了双RingBuffer,Uid-RingBuffer用于存储Uid、Flag-RingBuffer用于存储Uid状态(是否可填充、是否可消费)

由于数组元素在内存中是连续分配的,可最大程度利用CPU cache以提升性能。但同时会带来「伪共享」FalseSharing问题,为此在Tail、Cursor指针、Flag-RingBuffer中采用了CacheLine 补齐方式。

在这里插入图片描述

RingBuffer填充时机

  • 初始化预填充
    RingBuffer初始化时,预先填充满整个RingBuffer.

  • 即时填充
    Take消费时,即时检查剩余可用slot量(tail- cursor),如小于设定阈值,则补全空闲slots。阈值可通过paddingFactor来进行配置,请参考Quick Start中CachedUidGenerator配置

  • 周期填充
    通过Schedule线程,定时补全空闲slots。可通过scheduleInterval配置,以应用定时填充功能,并指定Schedule时间间隔

10.美团(Leaf)

由美团开发,开源项目链接:https://github.com/Meituan-Dianping/Leaf,相关博客链接:https://tech.meituan.com/2017/04/21/mt-leaf.html。

Leaf同时支持号段模式snowflake算法模式,可以切换使用。

ID号码是趋势递增的8byte的64位数字,满足上述数据库存储的主键要求。

Leaf的snowflake模式依赖于ZooKeeper,不同于原始snowflake算法也主要是在workId的生成上,Leaf中workId是基于ZooKeeper的顺序Id来生成的,每个应用在使用Leaf-snowflake时,启动时都会都在Zookeeper中生成一个顺序Id,相当于一台机器对应一个顺序节点,也就是一个workId。

leaf.snowflake.enable=true
leaf.snowflake.enable=true
leaf.snowflake.enable=true

Leaf的号段模式是对直接用数据库自增ID充当分布式ID的一种优化,减少对数据库的频率操作。

相当于从数据库批量的获取自增ID,每次从数据库取出一个号段范围,例如 (1,1000] 代表1000个ID,业务服务将号段在本地生成1~1000的自增ID并加载到内存。

特性:

  1. 全局唯一,绝对不会出现重复的ID,且ID整体趋势递增。

  2. 高可用,服务完全基于分布式架构,即使MySQL宕机,也能容忍一段时间的数据库不可用。

  3. 高并发低延时,在CentOS 4C8G的虚拟机上,远程调用QPS可达5W+,TP99在1ms内。

  4. 接入简单,直接通过公司RPC服务或者HTTP调用即可接入。

Leaf 采用双 buffer 的方式,它的服务内部有两个号段缓存区segment

当前号段已消耗10%时,还没能拿到下一个号段,则会另启一个更新线程去更新下一个号段,如果下个号段准备好了则切换到下个号段为当前segment接着下发,循环往复。

在这里插入图片描述

  • 每个biz-tag都有消费速度监控,通常推荐segment长度设置为服务高峰期发号QPS(秒处理事务数)的600倍(10分钟),这样即使DB宕机,Leaf仍能持续发号10-20分钟不受影响。
  • 每次请求来临时都会判断下个号段的状态,从而更新此号段,所以偶尔的网络抖动不会影响下个号段的更新。

简而言之就是Leaf保证了总是会多缓存两个号段,即便哪一时刻数据库挂了,也会保证发号服务可以正常工作一段时间。

--数据库表结构
CREATE TABLE `leaf_alloc` (
  `biz_tag` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '',
  `max_id` bigint NOT NULL DEFAULT '1',
  `step` int NOT NULL,
  `description` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`biz_tag`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

配置:

leaf.name=leaf-server
leaf.segment.enable=true
leaf.jdbc.url=jdbc:mysql://192.168.150.101:3306/sl_leaf?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
leaf.jdbc.username=root
leaf.jdbc.password=123
leaf.snowflake.enable=false

11.滴滴(TinyID)

由滴滴开发,开源项目链接:https://github.com/didi/tinyid。

Tinyid是在美团(Leaf)的leaf-segment算法基础上升级而来,不仅支持了数据库多主节点模式,还提供了tinyid-client客户端的接入方式,使用起来更加方便。

但和美团(Leaf)不同的是,Tinyid只支持号段一种模式不支持雪花模式。

Tinyid提供了两种调用方式,一种基于Tinyid-server提供的http方式,另一种Tinyid-client客户端方式。

每个服务获取一个号段(1000,2000](2000,3000](3000,4000]

特性:

  1. 全局唯一的long型ID

  2. 趋势递增的id

  3. 提供 http 和 java-client 方式接入

  4. 支持批量获取ID

  5. 支持生成 1,3,5,7,9… 序列的ID

  6. 支持多个db的配置

适用场景不适用场景
只关心ID是数字像类似于订单ID的业务
趋势递增的系统因生成的ID大部分是连续的
可以容忍ID不连续容易被扫库
可以容忍ID的浪费或者推算出订单量等信息

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

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

相关文章

视频智能分析平台LiteAIServer烟火识别软件引领烟火检测与识别的智能新纪元

随着人工智能技术的飞速进步&#xff0c;视频智能分析技术正以前所未有的深度和广度渗透至安全防护、环境监测等多个关键领域。其中&#xff0c;烟火识别软件LiteAIServer凭借其卓越的烟火检测与识别算法&#xff0c;成为了业界瞩目的焦点。 一、烟火检测&#xff1a;守护公共安…

汽车及零配件企业海量文件数据如何管

汽车行业特点 汽车行业是工业企业皇冠上的一颗明珠&#xff0c;在国民经济中占据着举足轻重的地位。汽车行业具备技术密集、创新速度快、供应链复杂等特点&#xff0c;具体体现为&#xff1a; 技术密集&#xff1a;汽车行业是技术密集型行业&#xff0c;覆盖机械、电子、软件、…

【CSS3】css开篇基础(4)

1.❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; Hello, Hello~ 亲爱的朋友们&#x1f44b;&#x1f44b;&#xff0c;这里是E绵绵呀✍️✍️。 如果你喜欢这篇文章&#xff0c;请别吝啬你的点赞❤️❤️和收藏&#x1f4d6;&#x1f4d6;。如果你对我的…

cpp的string类

本篇将讲述string类中的各种重要和常用函数&#xff08;string()、begin&#xff08;&#xff09;、rbegin&#xff08;&#xff09;、cbegin&#xff08;&#xff09;、crbegin&#xff08;&#xff09;、end&#xff08;&#xff09;、rend&#xff08;&#xff09;、cend&am…

20241024拿掉飞凌OK3588-C的开发板linux R4启动时的LOGO

20241024拿掉飞凌OK3588-C的开发板linux R4启动时的LOGO 2024/10/24 14:20 缘起&#xff1a;公司的产品可能要卖到北边/敏感地区。 开机肯定有飞凌的LOGO。 要么是公司的LOGO&#xff0c;要么是中性的&#xff1a;全黑色或者是一朵花【稍微有点品味】。 所以要拿掉uboot/Kernel…

汽车级DC-DC转换器英飞凌TLF35584

上汽荣威都在用的汽车级DC-DC转换器英飞凌TLF35584 今天平台君从IPBrain数据库中给大家带来的一款由Infineon(英飞凌)推出的一款多路输出安全电源芯片,具备高可靠性和安全性。适用于汽车电子系统中的多种应用场景,如车身控制、安全气囊、防抱死制动系统,电子稳定控制系统等。…

Unity2D 人物爬楼梯

unity3d 中 实现 2d角色爬梯子功能。_哔哩哔哩_bilibiliunity3d 中 实现 2d角色爬梯子功能。, 视频播放量 2598、弹幕量 3、点赞数 28、投硬币枚数 18、收藏人数 83、转发人数 19, 视频作者 Fss1975, 作者简介 &#xff0c;相关视频&#xff1a;寻路算法 在unity3d 中的演示&am…

repo将每个仓库回退到第一个commit的状态

文章目录 1. 获取所有仓库的列表2. 回退每个仓库到第一个 commit3. 确认状态注意事项 在使用 repo 和 git 管理 AOSP 代码时&#xff0c;如果你想将每个仓库都回退到其第一个 commit 的状态&#xff0c;你可以按照以下步骤进行操作&#xff1a; 1. 获取所有仓库的列表 首先&…

Windows Python安装和配置教程

文章目录 一&#xff0c;下载方式&#xff08;一&#xff09;官网下载注&#xff1a;下载选项说明注&#xff1a;查看本机操作系统位数步骤 &#xff08;二&#xff09;网盘下载 二&#xff0c;安装三&#xff0c;测试安装效果&#xff08;一&#xff09;检测安装配置&#xff…

新书速览|Spring+Spring MVC+MyBatis从零开始学(视频教学版)(第3版)

《SpringSpring MVCMyBatis从零开始学&#xff08;视频教学版&#xff09;&#xff08;第3版&#xff09;》 1 本书内容 SSM是当前使用广泛的Java Web开发框架。《SpringSpring MVCMyBatis从零开始学&#xff08;视频教学版&#xff09;&#xff08;第3版&#xff09;》由浅入…

【福建医科大学附属第一医院-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞 …

浅谈BIM+GIS在管廊机电监控与运维管控系统中的应用

文&#xff1a;安科瑞郑桐 摘要&#xff1a;本文提出了一种城市综合管廊监控与报警集成管控平台的架构&#xff0c;并对以BIMGIS为核心的系统功能进行了研究和分析&#xff0c;通过GIS实现对管廊整体及设备设施的全局定位及管理&#xff0c;利用BIM实现对管廊设备的空间定位&a…

第6篇:无线与移动网络

目录 引言 6.1 无线网络的基础概念 6.2 无线局域网&#xff08;WLAN&#xff09;与IEEE 802.11 6.3 蓝牙与无线个域网&#xff08;WPAN&#xff09; 6.4 无线城域网&#xff08;WMAN&#xff09;与WiMax 6.5 ZigBee与智能家居 6.6 移动蜂窝网络&#xff08;3G/4G/5G&…

SpringColoud GateWay 核心组件

优质博文&#xff1a;IT-BLOG-CN 【1】Route路由&#xff1a; Gateway的基本构建模块&#xff0c;它由ID、目标URL、断言集合和过滤器集合组成。如果聚合断言结果为真&#xff0c;则匹配到该路由。 Route路由-动态路由实现原理&#xff1a; 配置变化Apollo 服务地址实例变化…

AMD XILINX 20nm器件价格上调25%

随着市场回暖&#xff0c;台积电也在调整价格策略&#xff0c;近期台积电上调了20nm的出厂价格。 据相关消息显示&#xff0c;AMD为了保障持续的供货和服务&#xff0c;也计划将20nm器件的价格统一上调25%&#xff0c;预计将于11月发布正式的涨价通知&#xff0c;并于2025年Q1开…

七,Linux基础环境搭建(CentOS7)- 安装Scala和Spark

Linux基础环境搭建&#xff08;CentOS7&#xff09;- 安装Scala和Spark 大家注意以下的环境搭建版本号&#xff0c;如果版本不匹配有可能出现问题&#xff01; 一、Scala下载及安装 Scala是一门多范式的编程语言&#xff0c;一种类似java的编程语言&#xff0c;设计初衷是实现…

基于Java+SpringBoot+Vue的宠物咖啡馆平台的设计与实现

基于JavaSpringBootVue的宠物咖啡馆平台的设计与实现 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末附源码下载链接&#…

ubuntu编译kaldi和vosk

文章目录 前言一、开源框架的选取二、kaldi编译三、编译vosk方案一方案二 前言 由于工作需要语音识别的功能&#xff0c;环境是在linux arm版上&#xff0c;所以想先在ubuntu上跑起来看一看&#xff0c;就找了一下语音识别的开源框架&#xff0c;选中了vosk这个开源库&#xf…

微软:全球每天网络攻击超6亿次

《2024年微软数字防御报告》揭示了一个复杂的全球网络安全格局&#xff0c;每天发生超过6亿次网络攻击。报告强调了勒索软件、网络钓鱼和身份泄露事件的增加&#xff0c;以及网络犯罪团伙和国家行为者之间的合作。它强调了人工智能在攻击和防御中的重要作用&#xff0c;敦促组织…

基于SpringBoot的“高校校园点餐系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“高校校园点餐系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 前台首页功能界面图 用户注册、登录界面图 我…