全局ID生成器
是一种在分布式系统下用来生成全局唯一ID的工具。
一般要满足下列特性:
如果使用数据库自增ID就存在一些问题:
- ID的规律太明显
- 受单表数据量的限制
场景分析一:
如果我们的id具有太明显的规则,用户或者说商业对手很容易猜测出来我们的一些敏感信息,比如商城在一天时间内,卖出了多少单,这明显不合适。
场景分析二:
随着我们商城规模越来越大,mysql的单表的容量不宜超过500W,数据量过大之后,我们要进行拆库拆表,但拆分表了之后,他们从逻辑上讲他们是同一张表,所以他们的id是不能一样的, 于是乎我们需要保证id的唯一性。
ID的组成部分如下:
ID的组成部分:符号位:1bit,永远为0
时间戳:31bit,以秒为单位,可以使用69年
序列号:32bit,秒内的计数器,支持每秒产生2^32个不同ID
代码如下:
package com.hmdp.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
/**
* @author 胡子龙
* @date 2024/1/2 17:07
**/
@Component
public class RedisIdWorker {
/**
* 开始时间戳(可以在下方main方法中生成一个)
*/
private static final long BEGIN_TIMESTAMP = 1672738510L;
/**
* 序列号占用的位数
*/
private static final int COUNT_BITS = 32;
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 根据当前时间生成一个开始时间戳
*
* @param args
*/
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.of(2023, 1, 3, 9, 35, 10);
long second = now.toEpochSecond(ZoneOffset.UTC);
System.out.println(second);
}
/**
* 利用Redis自增ID生成全局唯一ID
*
* @param keyPrefix key前缀
* @return 全局唯一ID
*/
public long nextId(String keyPrefix) {
// 1 生成时间戳
// 1.1 获取当前时间
LocalDateTime now = LocalDateTime.now();
// 1.2 将当前时间转为UTC基准的秒数
long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
// 1.3 当前时间减去开始时间,获得一个时间戳
long timestamp = nowSecond - BEGIN_TIMESTAMP;
// 2 生成序列号,因为Redis的Key是用冒号(:)分割的,使用以下格式可以统计每天生成的ID数量,例如:incr:shop:2024:01:03
// 2.1 生成当前日期,精确到天
String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
// 2.2 自增长生成序列号
long incrementCount = stringRedisTemplate.opsForValue().increment("incr:" + keyPrefix + ":" + date);
// 3 组合生成全局唯一ID,将时间戳左移32位,再和序列号做”或“运算。
return timestamp << COUNT_BITS | incrementCount;
}
}