分布式ID生成策略

文章目录

  • 分布式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 基于随机数的UUIDString 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 = InnoDBAUTO_INCREMENT = 1DEFAULT 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 {@Autowiredprivate 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);// 输出生成的 IDSystem.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位的IDreturn ((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.rhkb.cn/news/453602.html

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

相关文章

浏览器播放rtsp视频流解决方案

方案一: html5 websocket_rtsp_proxy 实现视频流直播 实现原理 实现步骤 服务器安装streamedian服务器 客户端通过video标签播放 <video id"test_video" controls autoplay></video><script src"free.player.1.8.4.js"></script&g…

openresty通过header_filter_by_lua记录特定的请求头和特定的响应头到日志文件

有时我们希望记录特定的请求头信息和特定的响应头信息,以便能够通过关联请求信息和响应头信息,来实现记录请求和响应的对应关系。这里通过逐步尝试和优化的方式进行尝试。具体包括将需要的请求头和响应头组织到一条日志记录,输出到单独的错误日志文件记录等的配置尝试。 1.…

HAL+M4学习记录_8

一、TIM的HAL库用法 这里记录学习HAL库开发TIM 1.1 定时中断基本结构 这里给出定时中断的基本结构 基本步骤如下 开启时钟选择时基单元时钟源配置时基单元配置输出中断控制&#xff0c;允许更新中断输出到NVIC配置NVIC&#xff0c;打开定时器中断通道运行控制编写中断服务函…

为什么九齐单片机中不能使用bit?

如果开发环境不支持bit 定义static unsigned char task_720ms_flag 0;还可以用什么方式替代bit定义标志&#xff0c;使其占用内存空间小。 如果开发环境不支持位定义&#xff0c;可以使用 unsigned char 的多个状态位来替代。可以将多个标志合并到一个 unsigned char 中&…

Vue - Element 选择器 el-select 既可以选择下拉又可以手动输入文本功能(手动输入的值只能是数字 并且支持4位小数)

Vue - Element 选择器 el-select 既可以选择下拉又可以手动输入文本功能&#xff08;手动输入的值只能是数字 并且支持4位小数&#xff09; 备注 filterable 下拉框开启快速搜索功能 no-match-text 当输入的内容在下拉框中找不到时&#xff1b;下拉框提示的文字 handFocus 触发…

软件测试快速入门:测试对象、过程模型、生命周期与测试用例

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

redis集群介绍

Redis集群是一种分布式存储系统&#xff0c;它通过将数据分散存储在多个Redis节点上来实现可扩展性和高可用性。每个节点都是一个独立的Redis服务器实例&#xff0c;它们通过网络相互连接&#xff0c;共同协作以提供数据服务。 在Redis集群中&#xff0c;数据被划分为多个槽&am…

【Vercel】Vercel静态部署踩坑

背景 在现代的软件开发中&#xff0c;自动化部署是一个不可或缺的环节。Vercel作为一个流行的前端部署平台&#xff0c;提供了与GitHub的无缝集成&#xff0c;使得开发者能够在每次提交代码后自动触发部署流程。然而&#xff0c;自动化部署过程中可能会遇到一些挑战&#xff0…

自动化工具:Ansible

目录 一、运维自动化工具有哪些 二、Ansible 概述 1、Ansible 概念 2、Ansible 特点 3、Ansible 工作流程 三、安装部署Ansible 1、环境部署 2、管理节点安装 Ansible 3、查看Ansible相关文件 4、配置主机清单 5、免密管理 ssh-keygen 5.1、测试连通性 5.2、简洁输…

IPC通信-消息队列

使用消息队列实现两个进程的相互通信 #include<myhead.h>//定义结构体存储信息种类和信息正文 typedef struct {long mtype; //信息类型char mtext[128]; //信息正文 }msgbuf;//宏定义信息正文的大小 #define MESIZE sizeof(msgbuf)-sizeof(long)typedef struct sockad…

vscode:创建fastapi项目

1.选择py解释器 或者 uvicorn main:app --reload

java游戏网站源码

题目&#xff1a;java游戏网站源码 编号B22A390 主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Mysql|大数据|SSM|SpringBoot|Vue|Jsp|MYSQL等)、学习资料、JAVA源码、技术咨询 文末联系获取 感兴趣可以先收藏起来&#xff0c;以防走丢&#xff0c;有任何选题、文档编…

雷池WAF自动化实现安全运营实操案例终极篇

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

Unity DOTS中的Archetype与Chunk

Unity DOTS中的Archetype与Chunk 在Unity中&#xff0c;archetype&#xff08;原型&#xff09;用来表示一个world里具有相同component类型组合的entity。也就是说&#xff0c;相同component类型的entity在Unity内部会存储到一起&#xff0c;共享同一个archetype。 使用这样的设…

React是如何工作的?

从编写组件到最后屏幕生成界面&#xff0c;如上图所示&#xff0c;我们现在需要知道的就是后面几步是如何运行的。 概述 这张图解释了 React 渲染过程的几个阶段&#xff1a; 渲染触发&#xff1a;通过更新某处的状态来触发渲染。渲染阶段&#xff1a;React 调用组件函数&…

智能优化算法-生物地理学算法(BBO)(附源码)

目录 1.内容介绍 2.部分代码 3.实验结果 4.内容获取 1.内容介绍 生物地理学优化算法 (Biogeography-Based Optimization, BBO) 是一种基于生物地理学原理的元启发式优化算法&#xff0c;由Dan Simon于2008年提出。BBO通过模拟物种在不同栖息地之间的迁移过程来搜索最优解&…

Dongle Sentinal在Jenkins下访问不了的问题

背景&#xff1a; 工作站部署的jenkins的脚本无法正常打包&#xff0c;定位后发现是本地获取不了license&#xff0c;但是使用usb over network的远程license都能获取并正常打包 分析&#xff1a; 获取不了license的原因是本地无法识别dongle。根据提供信息&#xff0c;之前…

卡特兰数解释相关的样例以及补充例题

目录 拓展的场景分析 1.圆上连接线段 2.二叉树问题 3.多边形划分三角形问题 补充的例题 P1976 鸡蛋饼 P1722 矩阵 II 通过取模处理判断选择用哪个式子​编辑 P2532 [AHOI2012] 树屋阶梯 P3978 [TJOI2015] 概率论 拓展的场景分析 1.圆上连接线段 一个圆上有2*n个点&am…

nginx中的HTTP 负载均衡

HTTP 负载均衡&#xff1a;如何实现多台服务器的高效分发 为了让流量均匀分配到两台或多台 HTTP 服务器上&#xff0c;我们可以通过 NGINX 的 upstream 代码块实现负载均衡。 方法 在 NGINX 的 HTTP 模块内使用 upstream 代码块对 HTTP 服务器实施负载均衡&#xff1a; upstr…

基于微博评论的自然语言处理情感分析

目录 一、项目概述 二、需要解决的问题 三、数据预处理 1、词汇表构建&#xff08;vocab_creat.py&#xff09; 2、数据集加载&#xff08;load_dataset.py&#xff09; 四、模型构建&#xff08;TextRNN.py&#xff09; 1、嵌入层&#xff08;Embedding Layer&#xff…