自己做的网站套dedecms教程,腾讯云做视频网站吗,运城建设网站,宁夏建设工程招标投标信息网站为什么需要全局唯一ID
我们这里引用美团 Leaf 的场景介绍#xff1a;在复杂分布式系统中#xff0c;往往需要对大量的数据和消息进行唯一标识。如在美团点评的金融、支付、餐饮、酒店、猫眼电影等产品的系统中#xff0c;数据日渐增长#xff0c;对数据分库分表后需要有一…为什么需要全局唯一ID
我们这里引用美团 Leaf 的场景介绍在复杂分布式系统中往往需要对大量的数据和消息进行唯一标识。如在美团点评的金融、支付、餐饮、酒店、猫眼电影等产品的系统中数据日渐增长对数据分库分表后需要有一个唯一 ID 来标识一条数据或消息数据库的自增 ID 显然不能满足需求特别一点的如订单、骑手、优惠券也都需要有唯一 ID 做标识。此时一个能够生成全局唯一ID 的系统是非常必要的。
UUID
UUID Universally Unique Identifier通用唯一识别码的缩写。UUID是由一组32位数的16进制数字所构成所以UUID理论上的总数为 16322128约等于 3.4 x 10^38。也就是说若每纳秒产生1兆个UUID要花100亿年才会将所有UUID用完。生成的UUID是由 8-4-4-4-12格式的数据组成其中32个字符和4个连字符’ - 一般我们使用的时候会将连字符删除 uuid.toString().replaceAll(“-”,“”)。 UUID的产生方式主要包括以下几种
基于时间戳和MAC地址UUID v1这种方式结合了主机的网络接口卡MAC地址和当前的日期时间来生成UUID。由于包含了MAC地址因此这种方式生成的UUID在一定程度上是可以被跟踪的因为它隐含了生成UUID的设备信息。基于随机数UUID v4这是目前最常用的UUID生成方式。它完全基于随机数或伪随机数没有任何可以识别的个人或组织信息因此是完全匿名的。这也是为什么它被广泛应用于需要高度安全性和隐私保护的场景。基于命名空间的UUIDUUID v3、v5这种方式基于MD5和SHA-1散列算法通过将特定命名空间的标识符和名字进行散列来生成UUID。这种方法允许UUID基于给定的名字和命名空间来生成确保不同命名空间下的UUID是唯一的。基于硬件的UUID某些系统可能还会根据其他硬件信息来生成UUID例如硬盘序列号等。 java实现的uuid为基于随机数使用代码
public static void main(String[] args) {//获取一个版本4根据随机字节数组的UUID。UUID uuid UUID.randomUUID();System.out.println(uuid.toString().replaceAll(-,));
}虽然 UUID 生成方便本地生成没有网络消耗但是使用起来也有一些缺点
不易于存储UUID太长16字节128位通常以36长度的字符串表示很多场景不适用。信息不安全基于MAC地址生成UUID的算法可能会造成MAC地址泄露暴露使用者的位置。对MySQL索引不利如果作为数据库主键在InnoDB引擎下UUID的无序性可能会引起数据位置频繁变动严重影响性能可以查阅 Mysql 索引原理 B树的知识。
使用redis实现
Redis实现分布式唯一ID主要是通过提供像 INCR 和 INCRBY 这样的自增原子命令由于Redis自身的单线程的特点所以能保证生成的 ID 肯定是唯一有序的。以下代码通过引入jedis来实现
import redis.clients.jedis.Jedis;public class UniqueIdGenerator {private static final String REDIS_HOST localhost;private static final int REDIS_PORT 6379;private static final String UNIQUE_ID_KEY unique_id;public static void main(String[] args) {Jedis jedis new Jedis(REDIS_HOST, REDIS_PORT);long uniqueId generateUniqueId(jedis);System.out.println(生成的唯一ID: uniqueId);jedis.close();}private static long generateUniqueId(Jedis jedis) {return jedis.incr(UNIQUE_ID_KEY);}
}
雪花算法-Snowflake
Snowflake雪花算法是由Twitter开源的分布式ID生成算法以划分命名空间的方式将 64-bit位分割成多个部分每个部分代表不同的含义。而 Java中64bit的整数是Long类型所以在 Java 中 SnowFlake 算法生成的 ID 就是 long 来存储的。 具体实现步骤如下
第一位为未使用(符号位表示正数)接下来的41位为毫秒级时间(41位的长度可以使用69年)然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点)最后12位是毫秒内的计数12位的计数顺序号支持每个节点每毫秒产生4096个ID序号
以下是java实现版本
public class Snowflake implements Serializable {private static final long serialVersionUID 1L;/*** 默认的起始时间为Thu, 04 Nov 2010 01:42:54 GMT*/public static long DEFAULT_TWEPOCH 1288834974657L;/*** 默认回拨时间2S*/public static long DEFAULT_TIME_OFFSET 2000L;private static final long WORKER_ID_BITS 5L;// 最大支持机器节点数0~31一共32个SuppressWarnings({PointlessBitwiseExpression, FieldCanBeLocal})private static final long MAX_WORKER_ID -1L ^ (-1L WORKER_ID_BITS);private static final long DATA_CENTER_ID_BITS 5L;// 最大支持数据中心节点数0~31一共32个SuppressWarnings({PointlessBitwiseExpression, FieldCanBeLocal})private static final long MAX_DATA_CENTER_ID -1L ^ (-1L DATA_CENTER_ID_BITS);// 序列号12位表示只允许workId的范围为0-4095private static final long SEQUENCE_BITS 12L;// 机器节点左移12位private static final long WORKER_ID_SHIFT SEQUENCE_BITS;// 数据中心节点左移17位private static final long DATA_CENTER_ID_SHIFT SEQUENCE_BITS WORKER_ID_BITS;// 时间毫秒数左移22位private static final long TIMESTAMP_LEFT_SHIFT SEQUENCE_BITS WORKER_ID_BITS DATA_CENTER_ID_BITS;// 序列掩码用于限定序列最大值不能超过4095private static final long SEQUENCE_MASK ~(-1L SEQUENCE_BITS);// 4095/*** 初始化时间点*/private final long twepoch;private final long workerId;private final long dataCenterId;private final boolean useSystemClock;/*** 允许的时钟回拨毫秒数*/private final long timeOffset;/*** 当在低频模式下时序号始终为0导致生成ID始终为偶数br* 此属性用于限定一个随机上限在不同毫秒下生成序号时给定一个随机数避免偶数问题。br* 注意次数必须小于{link #SEQUENCE_MASK}{code 0}表示不使用随机数。br* 这个上限不包括值本身。*/private final long randomSequenceLimit;/*** 自增序号当高频模式下时同一毫秒内生成N个ID则这个序号在同一毫秒下自增以避免ID重复。*/private long sequence 0L;private long lastTimestamp -1L;/*** 构造使用自动生成的工作节点ID和数据中心ID*/public Snowflake() {this(IdUtil.getWorkerId(IdUtil.getDataCenterId(MAX_DATA_CENTER_ID), MAX_WORKER_ID));}/*** 构造** param workerId 终端ID*/public Snowflake(long workerId) {this(workerId, IdUtil.getDataCenterId(MAX_DATA_CENTER_ID));}/*** 构造** param workerId 终端ID* param dataCenterId 数据中心ID*/public Snowflake(long workerId, long dataCenterId) {this(workerId, dataCenterId, false);}/*** 构造** param workerId 终端ID* param dataCenterId 数据中心ID* param isUseSystemClock 是否使用{link SystemClock} 获取当前时间戳*/public Snowflake(long workerId, long dataCenterId, boolean isUseSystemClock) {this(null, workerId, dataCenterId, isUseSystemClock);}/*** param epochDate 初始化时间起点null表示默认起始日期,后期修改会导致id重复,如果要修改连workerId dataCenterId慎用* param workerId 工作机器节点id* param dataCenterId 数据中心id* param isUseSystemClock 是否使用{link SystemClock} 获取当前时间戳* since 5.1.3*/public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock) {this(epochDate, workerId, dataCenterId, isUseSystemClock, DEFAULT_TIME_OFFSET);}/*** param epochDate 初始化时间起点null表示默认起始日期,后期修改会导致id重复,如果要修改连workerId dataCenterId慎用* param workerId 工作机器节点id* param dataCenterId 数据中心id* param isUseSystemClock 是否使用{link SystemClock} 获取当前时间戳* param timeOffset 允许时间回拨的毫秒数* since 5.8.0*/public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock, long timeOffset) {this(epochDate, workerId, dataCenterId, isUseSystemClock, timeOffset, 0);}/*** param epochDate 初始化时间起点null表示默认起始日期,后期修改会导致id重复,如果要修改连workerId dataCenterId慎用* param workerId 工作机器节点id* param dataCenterId 数据中心id* param isUseSystemClock 是否使用{link SystemClock} 获取当前时间戳* param timeOffset 允许时间回拨的毫秒数* param randomSequenceLimit 限定一个随机上限在不同毫秒下生成序号时给定一个随机数避免偶数问题0表示无随机上限不包括值本身。* since 5.8.0*/public Snowflake(Date epochDate, long workerId, long dataCenterId,boolean isUseSystemClock, long timeOffset, long randomSequenceLimit) {this.twepoch (null ! epochDate) ? epochDate.getTime() : DEFAULT_TWEPOCH;this.workerId Assert.checkBetween(workerId, 0, MAX_WORKER_ID);this.dataCenterId Assert.checkBetween(dataCenterId, 0, MAX_DATA_CENTER_ID);this.useSystemClock isUseSystemClock;this.timeOffset timeOffset;this.randomSequenceLimit Assert.checkBetween(randomSequenceLimit, 0, SEQUENCE_MASK);}/*** 根据Snowflake的ID获取机器id** param id snowflake算法生成的id* return 所属机器的id*/public long getWorkerId(long id) {return id WORKER_ID_SHIFT ~(-1L WORKER_ID_BITS);}/*** 根据Snowflake的ID获取数据中心id** param id snowflake算法生成的id* return 所属数据中心*/public long getDataCenterId(long id) {return id DATA_CENTER_ID_SHIFT ~(-1L DATA_CENTER_ID_BITS);}/*** 根据Snowflake的ID获取生成时间** param id snowflake算法生成的id* return 生成的时间*/public long getGenerateDateTime(long id) {return (id TIMESTAMP_LEFT_SHIFT ~(-1L 41L)) twepoch;}/*** 下一个ID** return ID*/public synchronized long nextId() {long timestamp genTime();if (timestamp this.lastTimestamp) {if (this.lastTimestamp - timestamp timeOffset) {// 容忍指定的回拨避免NTP校时造成的异常timestamp lastTimestamp;} else {// 如果服务器时间有问题(时钟后退) 报错。throw new IllegalStateException(StrUtil.format(Clock moved backwards. Refusing to generate id for {}ms, lastTimestamp - timestamp));}}if (timestamp this.lastTimestamp) {final long sequence (this.sequence 1) SEQUENCE_MASK;if (sequence 0) {timestamp tilNextMillis(lastTimestamp);}this.sequence sequence;} else {// issue#I51EJYif (randomSequenceLimit 1) {sequence RandomUtil.randomLong(randomSequenceLimit);} else {sequence 0L;}}lastTimestamp timestamp;return ((timestamp - twepoch) TIMESTAMP_LEFT_SHIFT)| (dataCenterId DATA_CENTER_ID_SHIFT)| (workerId WORKER_ID_SHIFT)| sequence;}/*** 下一个ID字符串形式** return ID 字符串形式*/public String nextIdStr() {return Long.toString(nextId());}// ------------------------------------------------------------------------------------------------------------------------------------ Private method start/*** 循环等待下一个时间** param lastTimestamp 上次记录的时间* return 下一个时间*/private long tilNextMillis(long lastTimestamp) {long timestamp genTime();// 循环直到操作系统时间戳变化while (timestamp lastTimestamp) {timestamp genTime();}if (timestamp lastTimestamp) {// 如果发现新的时间戳比上次记录的时间戳数值小说明操作系统时间发生了倒退报错throw new IllegalStateException(StrUtil.format(Clock moved backwards. Refusing to generate id for {}ms, lastTimestamp - timestamp));}return timestamp;}/*** 生成时间戳** return 时间戳*/private long genTime() {return this.useSystemClock ? SystemClock.now() : System.currentTimeMillis();}}百度-UidGenerator
百度的 UidGenerator 是百度开源基于Java语言实现的唯一ID生成器是在雪花算法 snowflake 的基础上做了一些改进。UidGenerator以组件形式工作在应用项目中, 支持自定义workerId位数和初始化策略适用于docker等虚拟化环境下实例自动重启、漂移等场景。 在实现上UidGenerator 提供了两种生成唯一ID方式分别是 DefaultUidGenerator 和 CachedUidGenerator官方建议如果有性能考虑的话使用 CachedUidGenerator 方式实现。UidGenerator 依然是以划分命名空间的方式将 64-bit位分割成多个部分只不过它的默认划分方式有别于雪花算法 snowflake。它默认是由 1-28-22-13 的格式进行划分。可根据你的业务的情况和特点自己调整各个字段占用的位数。
第1位仍然占用1bit其值始终是0。第2位开始的28位是时间戳28-bit位可表示2^28个数这里不再是以毫秒而是以秒为单位每个数代表秒则可用1L28/ (360024365) ≈ 8.51 年的时间。中间的 workId 数据中心工作机器可以其他组成方式则由 22-bit位组成可表示 2^22 4194304个工作ID。最后由13-bit位构成自增序列可表示2^13 8192个数。
Medis 薄雾算法工程实践 https://github.com/asyncins/medis 薄雾算法 Mist 由书籍《Python3 反爬虫原理与绕过实战》的作者韦世东综合各大厂的技术点且考虑高性能分布式序列号生成器架构后设计的一款“递增态势、不依赖数据库、高性能且不受时间回拨影响”的全局唯一序列号生成算法。 Medis 是薄雾算法 Mist 的工程实践其名取自 Mist 和 Redis。薄雾算法是一款性能强到令我惊喜的全局唯一 ID 算法。有了 Mist 和 Medis你就拥有了和美团 Leaf、微信 Seqsvr、百度 UIDGenerator 性能相当甚至超过的全局唯一 ID 服务了。相比复杂的 UIDGenerator 双 Buffer 优化和 Leaf-Snowflake薄雾算法 Mist 简单太多了。