Snowflake算法,天然支持分布式,可以生成:唯一性强、性能高、ID较短可读性的ID,一般结合:数据中心ID、不同机房ID等信息,确定唯一性。
一般可以结合Redis 实现订单号生成可以增加缓存的机制,通过预分配批次 ID,减少频繁生成的性能开销。
1. 唯一性
使用 UUID、Snowflake 算法等方案保证唯一性是合理的:
- UUID:虽然能够保证唯一性,但它的长度较长(36位),可读性较差,不适合对订单号有较高可读性要求的场景。
- Snowflake:这是大多数高并发分布式系统中常用的分布式 ID 生成方案。它提供了较短的长度(通常64位整数),而且包含时间戳等信息,适合高并发环境。对于订单号来说更合适。
- 推荐使用 Snowflake 算法,它生成的 ID 具有时间序列性,性能高,唯一性强,同时 ID 较短,符合性能和可读性需求。
- 可以对 Snowflake 算法生成的 ID 进行一定的格式化,例如转为字符串或者增加特定前缀。
2. 数据量和可扩展性
- 数据量预留位数:提到预留位数是一个合理的考虑。订单号的长度需要在兼顾数据量和性能的基础上,选择合适的位数。
- 可扩展性:使用 Redis 或分布式 ID 生成器来支撑高并发和分布式部署是有效的。Snowflake 算法天然支持分布式,因此可以满足高并发场景下的唯一性需求。
- 如果是多数据中心或多机房,可能需要考虑不同机房的
workerId
,确保全局唯一性。 - 使用 Redis 实现订单号生成可以增加缓存的机制,通过预分配批次 ID,减少频繁生成的性能开销。
3. 可读性
设计订单号的可读性是根据具体业务需求决定的,如果业务希望通过订单号看到一些信息,可以考虑加入时间戳、用户ID等信息。
- 通过适当的字段(如前缀、日期、分表结果)可以提高订单号的可读性。
- 业务类型前缀:通过2位数字区分业务类型可以有效增强可读性,比如 “01” 表示某种业务,“02” 表示另一种业务。
- 日期时间:增加时间戳信息(如年、月、日)有助于管理和查询。常见的格式如
yyyyMMddHHmmss
可以被转为较短的整型或字符串。
4. 分表字段
订单号中加入分表字段用于分库分表是合理的。可以使用 userId
或时间戳来决定数据的路由。
- 使用
userId
或订单创建时间等来生成 hash 值,以此决定分库分表位置。
5. 高性能
- 性能优化:通过 Redis 预分配 ID 批次或者使用 Snowflake 这样的分布式 ID 生成算法,能够有效降低 ID 生成对性能的影响。
- 异步处理:如果生成过程涉及到较复杂的运算(例如订单号的部分字段需要依赖外部服务计算),可以考虑将订单号生成和分配异步化处理,降低接口的延迟。
6. 高可用性
通过分布式多节点部署、负载均衡等手段确保高可用性是非常合理的。对于 Redis,可以采用主从架构、哨兵模式等方式保证服务的高可用性。
改进建议:
- 使用 多副本部署(如 Redis 哨兵模式或集群模式),确保服务的高可用。
- 可以结合 Spring Cloud、Dubbo 等分布式框架,利用分布式 ID 生成器进行分布式部署。
代码Demo
以 Snowflake 算法 为例,设计一个高并发场景下的订单号生成服务。假设我们使用 Java 进行开发,结合 Redis 缓存进行批量 ID 分配,以提高性能。
1. 使用 Snowflake 生成订单号
public class SnowflakeIdGenerator {
private final long twepoch = 1288834974657L; // 开始时间戳 (可以自定义)
private final long workerIdBits = 5L; // 机器ID所占位数
private final long datacenterIdBits = 5L; // 数据中心ID所占位数
private final long maxWorkerId = -1L ^ (-1L << workerIdBits); // 支持的最大机器ID
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); // 支持的最大数据中心ID
private final long sequenceBits = 12L; // 序列在ID中占的位数
private final long workerIdShift = sequenceBits; // 机器ID向左移位数
private final long datacenterIdShift = sequenceBits + workerIdBits; // 数据中心ID向左移位数
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; // 时间戳左移位数
private final long sequenceMask = -1L ^ (-1L << sequenceBits); // 生成序列的掩码
private long workerId; // 机器ID
private long datacenterId; // 数据中心ID
private long sequence = 0L; // 毫秒内序列(0~4095)
private long lastTimestamp = -1L; // 上次生成ID的时间戳
public SnowflakeIdGenerator(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;
}
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id");
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift)
| sequence;
}
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
protected long timeGen() {
return System.currentTimeMillis();
}
}
2. 使用 Redis 缓存进行 ID 批量生成
@Service
public class OrderIdService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String ORDER_ID_KEY = "order:id";
// 获取订单号
public String generateOrderId() {
SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1); // 假设数据中心ID为1,机器ID为1
String orderId = String.valueOf(idGenerator.nextId());
// 生成订单号后缓存到 Redis
redisTemplate.opsForList().leftPush(ORDER_ID_KEY, orderId);
return orderId;
}
// 批量获取订单号
public List<String> generateBatchOrderIds(int batchSize) {
SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1);
List<String> orderIds = new ArrayList<>();
for (int i = 0; i < batchSize; i++) {
orderIds.add(String.valueOf(idGenerator.nextId()));
}
// 批量缓存到 Redis
redisTemplate.opsForList().leftPushAll(ORDER_ID_KEY, orderIds);
return orderIds;
}
}
3. 示例订单号格式
订单号可以由业务类型、日期、唯一 ID 等组成,格式如下:
{业务类型}-{日期}-{Snowflake生成的唯一ID}
例如:01-20230908-1234567890123456
这种设计可以保证订单号的唯一性,同时增强可读性和分布式高并发支持。
总结
通过使用 Snowflake 算法和 Redis 的缓存预分配批量 ID,可以提升系统的性能、扩展性和可用性。