团购产品 网站建设,结合公众号小店做网站,供求信息免费发布,怎样做企业手机网站首页文章目录 前言1. 自增ID#xff08;Auto-Increment#xff09;2. GUID (Globally Unique Identifier)3. 雪花算法#xff08;Snowflake#xff09;处理时钟回拨的方法1. 简单等待2. 配置时钟回拨安全窗口3. 使用不同的机器 ID 小结稳定的雪花算法实现方案示例实现1. 定义雪… 文章目录 前言1. 自增IDAuto-Increment2. GUID (Globally Unique Identifier)3. 雪花算法Snowflake处理时钟回拨的方法1. 简单等待2. 配置时钟回拨安全窗口3. 使用不同的机器 ID 小结稳定的雪花算法实现方案示例实现1. 定义雪花算法类2. 使用 Redis 或数据库实现分布式唯一 ID 解释小结其他方法总结 前言
数据库主键的设计是数据库架构中的一个重要环节不同的主键生成策略适用于不同的场景和需求 以下是几种常见的主键设计方法及其优缺点比较
1. 自增IDAuto-Increment
优点:
实现简单数据库自动管理无需开发者介入。递增的特性使得数据插入速度快因为插入总是发生在索引的末尾。易于理解和使用便于查询和排序。
缺点:
分布式系统中难以保证全局唯一因为每个节点的计数器独立增长。数据泄露风险自增ID容易暴露数据库的规模和增长速度。如果发生大量删除操作可能导致主键ID不连续影响美观但不影响功能。
2. GUID (Globally Unique Identifier)
优点:
全球唯一无论在任何系统、任何地点生成都能保证唯一性。无需依赖数据库可以在客户端生成适合分布式系统。支持提前生成ID有利于并行处理和离线操作。
缺点:
长度较大通常为32字符占用更多的存储空间和索引空间。无序的特性可能导致索引碎片降低插入性能。不易读不便于人工识别和调试。
3. 雪花算法Snowflake
雪花算法Snowflake Algorithm是一种用于生成唯一ID的算法最初由Twitter公司开发。它是为了解决分布式系统中生成全局唯一ID的需求而设计的。在分布式系统中如果不同节点生成的ID可能会发生冲突这就需要一种机制来保证生成的ID在整个系统中唯一。
雪花算法的设计考虑了以下几个因素
时间戳Timestamp使用当前时间来确保生成的ID是递增的这样可以保证生成的ID是有序的。机器IDMachine ID将机器的唯一标识比如机器的MAC地址作为一部分ID确保不同机器生成的ID不会冲突。序列号Sequence Number用来解决同一毫秒内生成多个ID时的冲突问题。
Java中如何使用雪花算法来设计数据库主键呢下面是一个简单的示例
public class SnowflakeIdGenerator {// 定义机器ID可以通过配置文件或其他方式设置private long machineId;// 定义序列号private long sequence 0L;// 定义初始时间戳private long twepoch 1622874000000L; // 2021-06-05 00:00:00// 定义各部分占位数private long machineIdBits 5L;private long maxMachineId -1L ^ (-1L machineIdBits);private long sequenceBits 12L;private long sequenceMask -1L ^ (-1L sequenceBits);// 定义机器ID左移位数private long machineIdShift sequenceBits;// 定义时间戳左移位数private long timestampLeftShift sequenceBits machineIdBits;// 上次生成ID的时间戳private long lastTimestamp -1L;public SnowflakeIdGenerator(long machineId) {if (machineId maxMachineId || machineId 0) {throw new IllegalArgumentException(Machine ID cant be greater than maxMachineId or less than 0);}this.machineId machineId;}public synchronized long nextId() {long timestamp timeGen();if (timestamp lastTimestamp) {throw new RuntimeException(Clock moved backwards. Refusing to generate id for (lastTimestamp - timestamp) milliseconds);}if (lastTimestamp timestamp) {sequence (sequence 1) sequenceMask;if (sequence 0) {// 当同一毫秒内的序列号超过上限时等待下一毫秒timestamp tilNextMillis(lastTimestamp);}} else {sequence 0L;}lastTimestamp timestamp;return ((timestamp - twepoch) timestampLeftShift) | (machineId machineIdShift) | sequence;}private long tilNextMillis(long lastTimestamp) {long timestamp timeGen();while (timestamp lastTimestamp) {timestamp timeGen();}return timestamp;}private long timeGen() {return System.currentTimeMillis();}public static void main(String[] args) {SnowflakeIdGenerator idGenerator new SnowflakeIdGenerator(1); // 传入机器IDfor (int i 0; i 10; i) {System.out.println(idGenerator.nextId());}}
}在这个示例中我们通过nextId()方法来生成雪花算法生成的唯一ID。首先我们需要设置一个机器ID确保不同的机器有不同的ID。然后调用nextId()方法即可生成一个唯一的ID这个ID包含了时间戳、机器ID和序列号三部分。最后我们可以将生成的ID作为数据库表的主键。
值得注意的是雪花算法生成的ID是趋势递增的因此在数据库中使用时可能会带来一定的优势比如辅助索引的性能优化。但也要注意在高并发情况下可能出现的一些问题比如时钟回拨等。
优点:
结合了自增ID和GUID的优点生成的ID是趋势递增的且全局唯一。高性能适用于分布式环境能够按需分配workerId和数据中心id保证唯一性。ID较短一般为64位相比GUID节省存储空间。有序性有助于索引优化。
缺点:
需要一个中心节点或者多个但需要协调来生成ID有一定的运维成本。时钟回拨问题可能会影响ID的生成需要特殊处理。
然而雪花算法依赖于时间戳因此时钟回拨clock rollback会对其造成问题。
处理时钟回拨的方法
1. 简单等待
当检测到时钟回拨时直接等待直到时间回到正确的时间。这是最简单的处理方式但会导致 ID 生成暂停一段时间。
public class SnowflakeIdGenerator {private long lastTimestamp -1L;public synchronized long nextId() {long timestamp timeGen();// 如果当前时间小于上一次生成ID的时间戳说明系统时钟回拨if (timestamp lastTimestamp) {// 等待直到时钟追上while (timestamp lastTimestamp) {timestamp timeGen();}}lastTimestamp timestamp;return generateId(timestamp);}private long timeGen() {return System.currentTimeMillis();}private long generateId(long timestamp) {// 生成ID的逻辑return timestamp;}
}2. 配置时钟回拨安全窗口
允许一定范围内的时钟回拨在这个范围内继续生成 ID但如果超出这个范围则抛出异常或采取其他措施。
public class SnowflakeIdGenerator {private long lastTimestamp -1L;private static final long MAX_BACKWARD_MS 5L; // 允许的最大时钟回拨时间public synchronized long nextId() {long timestamp timeGen();if (timestamp lastTimestamp) {long offset lastTimestamp - timestamp;if (offset MAX_BACKWARD_MS) {// 等待直到时钟追上try {Thread.sleep(offset 1);} catch (InterruptedException e) {throw new RuntimeException(e);}timestamp timeGen();if (timestamp lastTimestamp) {throw new RuntimeException(Clock moved backwards. Refusing to generate id);}} else {throw new RuntimeException(Clock moved backwards. Refusing to generate id);}}lastTimestamp timestamp;return generateId(timestamp);}private long timeGen() {return System.currentTimeMillis();}private long generateId(long timestamp) {// 生成ID的逻辑return timestamp;}
}3. 使用不同的机器 ID
在分布式系统中每台机器有唯一的机器 ID。当检测到时钟回拨时改变机器 ID 来避免冲突。这种方法需要协调机器 ID 的分配。
public class SnowflakeIdGenerator {private long lastTimestamp -1L;private long machineId;private static final long MAX_MACHINE_ID 1023L;public SnowflakeIdGenerator(long machineId) {if (machineId 0 || machineId MAX_MACHINE_ID) {throw new IllegalArgumentException(Machine ID out of range);}this.machineId machineId;}public synchronized long nextId() {long timestamp timeGen();if (timestamp lastTimestamp) {machineId (machineId 1) MAX_MACHINE_ID;if (machineId 0) {// 如果机器ID回到0说明时钟回拨过大拒绝生成IDthrow new RuntimeException(Clock moved backwards. Refusing to generate id);}timestamp timeGen();}lastTimestamp timestamp;return generateId(timestamp, machineId);}private long timeGen() {return System.currentTimeMillis();}private long generateId(long timestamp, long machineId) {// 生成ID的逻辑包含时间戳和机器IDreturn (timestamp 22) | (machineId 12);}
}小结
简单等待当检测到时钟回拨时等待直到时钟恢复到正确时间。这种方法简单但会导致 ID 生成暂停。时钟回拨安全窗口允许一定范围内的时钟回拨如果超出这个范围则抛出异常或采取其他措施。不同的机器 ID当检测到时钟回拨时改变机器 ID 来避免冲突。这种方法需要协调机器 ID 的分配。
稳定的雪花算法实现方案
以下是一个经过优化的方案涵盖时钟回拨问题、分布式系统中的唯一性问题和高可用性问题
机器 ID 和数据中心 ID通过配置不同的机器 ID 和数据中心 ID 来确保分布式系统中的唯一性。时钟回拨处理使用递增序列和缓存的时间戳来处理时钟回拨问题。高可用性结合 Redis 或数据库来生成分布式唯一 ID。
示例实现
1. 定义雪花算法类
public class SnowflakeIdGenerator {private static final long EPOCH 1609459200000L; // 自定义纪元时间2021-01-01private static final long DATA_CENTER_ID_BITS 5L;private static final long MACHINE_ID_BITS 5L;private static final long SEQUENCE_BITS 12L;private static final long MAX_DATA_CENTER_ID ~(-1L DATA_CENTER_ID_BITS);private static final long MAX_MACHINE_ID ~(-1L MACHINE_ID_BITS);private static final long MAX_SEQUENCE ~(-1L SEQUENCE_BITS);private static final long MACHINE_ID_SHIFT SEQUENCE_BITS;private static final long DATA_CENTER_ID_SHIFT SEQUENCE_BITS MACHINE_ID_BITS;private static final long TIMESTAMP_SHIFT SEQUENCE_BITS MACHINE_ID_BITS DATA_CENTER_ID_BITS;private final long dataCenterId;private final long machineId;private long sequence 0L;private long lastTimestamp -1L;public SnowflakeIdGenerator(long dataCenterId, long machineId) {if (dataCenterId MAX_DATA_CENTER_ID || dataCenterId 0) {throw new IllegalArgumentException(String.format(DataCenter ID cant be greater than %d or less than 0, MAX_DATA_CENTER_ID));}if (machineId MAX_MACHINE_ID || machineId 0) {throw new IllegalArgumentException(String.format(Machine ID cant be greater than %d or less than 0, MAX_MACHINE_ID));}this.dataCenterId dataCenterId;this.machineId machineId;}public synchronized long nextId() {long timestamp timeGen();if (timestamp lastTimestamp) {throw new RuntimeException(Clock moved backwards. Refusing to generate id for (lastTimestamp - timestamp) milliseconds);}if (timestamp lastTimestamp) {sequence (sequence 1) MAX_SEQUENCE;if (sequence 0) {timestamp tilNextMillis(lastTimestamp);}} else {sequence 0L;}lastTimestamp timestamp;return ((timestamp - EPOCH) TIMESTAMP_SHIFT)| (dataCenterId DATA_CENTER_ID_SHIFT)| (machineId MACHINE_ID_SHIFT)| sequence;}private long tilNextMillis(long lastTimestamp) {long timestamp timeGen();while (timestamp lastTimestamp) {timestamp timeGen();}return timestamp;}private long timeGen() {return System.currentTimeMillis();}
}2. 使用 Redis 或数据库实现分布式唯一 ID
为了进一步提高高可用性和唯一性可以结合 Redis 或数据库实现分布式唯一 ID 生成。这里是一个使用 Redis 的示例
import redis.clients.jedis.Jedis;public class DistributedIdGenerator {private final SnowflakeIdGenerator snowflakeIdGenerator;private final Jedis jedis;public DistributedIdGenerator(long dataCenterId, long machineId, String redisHost, int redisPort) {this.snowflakeIdGenerator new SnowflakeIdGenerator(dataCenterId, machineId);this.jedis new Jedis(redisHost, redisPort);}public long nextId() {long id snowflakeIdGenerator.nextId();String key snowflake: id;while (jedis.exists(key)) {id snowflakeIdGenerator.nextId();key snowflake: id;}jedis.setex(key, 3600, 1); // 设置过期时间避免长期存储return id;}
}解释 基本雪花算法 EPOCH自定义的纪元时间。DATA_CENTER_ID_BITS、MACHINE_ID_BITS 和 SEQUENCE_BITS数据中心 ID、机器 ID 和序列号的位数。nextId 方法生成唯一 ID并处理时钟回拨问题。 分布式唯一 ID 使用 Redis 确保 ID 唯一性在生成 ID 后将其存储在 Redis 中检查是否重复。jedis.setex(key, 3600, 1)使用带过期时间的键来避免长期存储。 时钟回拨处理 当检测到时钟回拨时抛出异常或等待时间前进。使用 tilNextMillis 方法等待直到时间前进。
小结
这种方案结合了雪花算法的高性能和 Redis 的分布式存储能力解决了时钟回拨问题并确保在分布式环境下生成唯一 ID。通过这些措施可以实现一个稳定、高效的分布式唯一 ID 生成系统。
其他方法
复合主键结合多个字段作为主键适用于表中没有自然唯一标识符的场景。但增加了查询和维护的复杂性。业务相关ID如订单号易于理解且与业务紧密相关但可能需要额外的逻辑来保证唯一性且扩展性较差。
总结
选择哪种主键生成策略取决于具体的应用场景
对于单体应用或简单的分布式系统自增ID可能是最简单高效的选择。在分布式系统中尤其是跨多个数据中心时雪花算法因其高性能和全局唯一性成为优选。当全局唯一性是首要考虑因素且对存储空间不太敏感时GUID是合适的选择。具体场景下也可以根据业务需求考虑复合主键或业务相关ID的方案。