分布式环境下数字序列号生成服务

在分布式系统中,生成具有唯一性的短数字序列号是一项挑战。本文探讨了三种解决方案:1) 利用数据库自增特性,2) 使用分布式缓存如Redis获取原子序列号,以及3) 结合数据库乐观锁和应用分段来确保序列号的唯一性。每种方法都有其优缺点,如数据库自增的简单性但灵活性不足,Redis的灵活性但可能的数据丢失风险,以及乐观锁结合应用分段的灵活性和顺序性问题。
摘要由CSDN通过智能技术生成

背景

日常应用中,常常会碰到获取唯一序列号的需求。比如,唯一的订单编号,唯一的流水编号等等。

很多语言已经有了获取GUID或UUID的实现,直接使用工具就可以获取。但是GUID,UUID里面是数字和字母的字符串组合,虽然能保证唯一,但是长度很长,而且不好记忆。在很多场景下,作为唯一编号向外暴露对人类来说并不友好。

使用有规律的字母+数字适合向外暴露,那么如何保证生成对数字不重复,而且是在分布式环境下?

解决方案

使用数据库自增长特性

  • 使用方法
    利用数据库特性,设置id为自增型,数据写入后自动填入。

  • 优点
    实现简单,结果可靠

  • 缺点
    使用起来不灵活,为了保证安全,唯一序列号需要在数据插入后才能得到。很多情况下,我们希望这个序列号在数据写入数据库前就能够使用

使用分布式缓存

  • 使用方法
    在redis内对特定key使用incr命令,原子获取唯一序列号

  • 优点
    实现简单,使用灵活

  • 缺点
    依赖缓存,如果缓存数据丢失可能会有重复id

利用数据库乐观锁+应用分段

  • 使用方法
    在数据库中维护一个key + 开始数字 + 步长数这样一个数据结构,应用通过调用数据库乐观锁方式获取并占用一段数字,应用内通过维护这一段数字实现唯一序号

  • 优点
    使用灵活,可靠性高

  • 缺点
    不能保证序列号顺序输出

  • 实现

定义Sequence类

public class Sequence {
   
  
  
可以使用Snowflake算法来生成分布式数字ID。Snowflake算法可以生成一个64位的ID,其中包含了时间戳、机器ID和序列号。下面是一个Java实现的示例代码,可以生成15位的分布式数字ID: ```java public class SnowflakeIdWorker { // 开始时间戳(2022-01-01 00:00:00) private final long twepoch = 1640995200000L; // 机器ID所占的位数 private final long workerIdBits = 5L; // 数据标识ID所占的位数 private final long datacenterIdBits = 5L; // 支持的最大机器ID,结果是31 private final long maxWorkerId = ~(-1L << workerIdBits); // 支持的最大数据标识ID,结果是31 private final long maxDatacenterId = ~(-1L << datacenterIdBits); // 序列号所占的位数 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 private final long sequenceMask = ~(-1L << sequenceBits); private long workerId; private long datacenterId; private long sequence = 0L; private long lastTimestamp = -1L; 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; } /** * 产生下一个ID * * @return SnowflakeId */ public synchronized long nextId() { long timestamp = timeGen(); 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; } lastTimestamp = timestamp; // 时间戳部分 long id = (timestamp - twepoch) << timestampLeftShift; // 数据标识ID部分 id |= datacenterId << datacenterIdShift; // 机器ID部分 id |= workerId << workerIdShift; // 序列号部分 id |= sequence; return id; } /** * 等待下一个毫秒的到来 * * @param lastTimestamp 上次生成ID的时间戳 * @return 当前时间戳 */ private long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } /** * 获当前的时间戳 * * @return 当前时间戳 */ private long timeGen() { return System.currentTimeMillis(); } } ``` 使用示例: ```java SnowflakeIdWorker idWorker = new SnowflakeIdWorker(1, 1); long id = idWorker.nextId(); System.out.println(id); ``` 输出结果为一个15位的数字ID,例如: ``` 246168155236608 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值