分库分表时用Redis自增实现计数实现分布式id

随着用户量增长,原有系统面临性能瓶颈。新建系统中,通过Redis的自增功能来生成不同数据库间的唯一ID,确保了分布式环境下的ID唯一性。在获取ID时,若Redis操作失败,则采取随机数策略作为备用方案。

最近公司项目用户量越来越大,之前的老系统由于设计不合理,已经不能满足目前的需要。现在做了一个新系统,两个系统不同的库,同时在运行。涉及到表id的问题,为了解决这个问题,使用redis来实现分布式id ,具体代码如下:

1,设置一个key实现计数器功能,每取值一次调一次这个方法进行加1操作

public void incr(Integer dbIndex, String key) throws Exception {
    Jedis jedis = null;
    try {
        jedis = jedisPool.getResource();
        if (dbIndex != null) {
            jedis.select(dbIndex);
        }
        jedis.incr(key);
    
<think> 我们参考了引用[1]和[2]中的实现方法,利用秒级间戳和RedisID实现分布式全局ID。核心思想是将64位ID分为两部分:高32位为间戳(秒级),低32位为Redis序列号。 ### 实现步骤 1. **间戳部分(高32位)**: - 使用当前间减去一个固定的起始间(如2022-01-01 00:00:00)的秒数差值,作为间戳部分。 - 例如:当前间为2025-01-01 00:00:00,起始间为2022-01-01 00:00:00,则间戳差值为94694400秒(3年)。 2. **序列号部分(低32位)**: - 使用Redis的`INCR`命令生成自序列号。为了确保每天重新开始计数,我们在Redis的key中加入日期(精确到天)。 - 例如:key为`icr:order:20250101`,表示2025年1月1日的订单ID序列。 3. **ID拼接**: - 将间戳左移32位,然后与序列号进行按位或运算,得到最终的64位ID。 ### 代码实现(基于引用[1][2]优化) ```java @Component public class RedisIdGenerator { // 起始间戳(2022-01-01 00:00:00 UTC) private static final long BEGIN_TIMESTAMP = 1640995200L; // 序列号占用位数(32位) private static final int COUNT_BITS = 32; private final StringRedisTemplate stringRedisTemplate; public RedisIdGenerator(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } public long nextId(String keyPrefix) { // 1. 生成当前间戳(秒级) LocalDateTime now = LocalDateTime.now(); long nowSecond = now.toEpochSecond(ZoneOffset.UTC); long timestamp = nowSecond - BEGIN_TIMESTAMP; // 2. 生成序列号(使用日期确保每天重置) String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd")); // Redis key格式:icr:{业务前缀}:{日期},例如 icr:order:2025:01:01 String redisKey = "icr:" + keyPrefix + ":" + date; long sequence = stringRedisTemplate.opsForValue().increment(redisKey); // 3. 拼接ID间戳左移32位后与序列号组合) return (timestamp << COUNT_BITS) | sequence; } } ``` ### 关键点解析 1. **间戳选择**: - 使用秒级间戳可支持约136年的间范围(32位最大值为4294967295秒≈136年)[^1]。 - 起始间`BEGIN_TIMESTAMP`可根据实际业务调整。 2. **Redis序列号管理**: - 序列号按天存储,每天自动重置(例如`icr:order:20250101`),避免序列号溢出问题[^2]。 - 单业务单日理论最大ID数为$2^{32}-1$(约42.9亿),满足绝大多数场景。 3. **并发性能**: - Redis的`INCR`命令是原子操作,确保分布式环境下序列号唯一[^5]。 - 实测性能:单Redis节点可支撑5万+/秒ID生成(引用[5]中200线程并发测试)。 ### 问题解决方案 1. **间回拨问题**: - 若服务器间被回调,可能导致ID重复。 - 解决方案:在Redis中记录最后一次生成ID间戳,每次生成校验当前间是否小于上次记录间[^2]。示例: ```java String lastTimeKey = "last_timestamp:" + keyPrefix; Long lastTimestamp = stringRedisTemplate.opsForValue().get(lastTimeKey); if (lastTimestamp != null && nowSecond < lastTimestamp) { throw new IllegalStateException("间回拨异常!"); } stringRedisTemplate.opsForValue().set(lastTimeKey, String.valueOf(nowSecond)); ``` 2. **单日ID超42亿**: - 当单日ID量超过42亿,序列号会溢出(从0重新开始),导致ID重复。 - 解决方案: - 方案一:缩短间粒度(如改用小间戳),将ID结构改为`28位小差 + 36位序列号`。 - 方案二:扩展Redis序列号存储(使用`long`类型),并调整位运算为`(timestamp << 36) | (sequence & 0xFFFFFFFFFL)`[^3]。 ### 适用场景与限制 - **推荐场景**: - 电商订单系统(日均ID量<10亿)。 - 用户注册、日志追踪等需要趋势递ID的业务。 - **不适用场景**: - 超高并发系统(如物联网设备接入,ID需求量>1万/秒),需改用更高性能方案(如雪花算法变体)。 - 严格单调递场景(Redis序列号每日重置会导致ID不连续)。 > **对比雪花算法**:本方案无需分配workerId,但依赖Redis可用性。若Redis宕机,需启用备用ID生成器(如数据库自ID)[^4]。 --- ### §§相关问题§§ 1. 如何验证生成的ID分布式环境下全局唯一? 2. 间戳单位改为毫秒后如何避免序列号溢出? 3. Redis Cluster模式下如何保证自序列的连续性? 4. 此方案在高并发场景下如何优化Redis性能? </think> ### 基于秒级间戳和RedisID分布式全局ID实现方案 #### 一、核心实现原理 1. **ID结构设计(64位)**: - **高32位**:秒级间戳,存储从固定起始间(如2022-01-01)到当前间的秒数差值 - **低32位**:Redis生成的序列号,按日期分区确保每日重置 $$ \text{ID} = (\text{timestamp} \ll 32) \mid \text{sequence} $$ 2. **关键特性**: - **趋势递**:间戳保证ID间单调递 - **全局唯一**:Redis的`INCR`命令原子性确保序列号不重复 - **每日重置**:序列号key包含日期(如`icr:order:20250101`),避免溢出[^1][^2] #### 二、完整实现代码(Java) ```java @Component public class RedisSecondsIdGenerator { // 起始间戳(2022-01-01 00:00:00 UTC) private static final long BEGIN_TIMESTAMP = 1640995200L; // 序列号占用位数 private static final int COUNT_BITS = 32; private final StringRedisTemplate stringRedisTemplate; public RedisSecondsIdGenerator(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } public long nextId(String businessKey) { // 1. 计算秒级间戳差值 LocalDateTime now = LocalDateTime.now(); long currentSeconds = now.toEpochSecond(ZoneOffset.UTC); long timestamp = currentSeconds - BEGIN_TIMESTAMP; // 高32位[^1][^2] // 2. 生成日期分区序列号 String dateKey = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd")); String redisKey = "icr:" + businessKey + ":" + dateKey; Long sequence = stringRedisTemplate.opsForValue().increment(redisKey); // 低32位 // 3. 位运算组合ID return (timestamp << COUNT_BITS) | sequence; } } ``` #### 三、核心机制详解 1. **日期分区序列号**: - Redis Key格式:`icr:{业务前缀}:{日期}` - 示例:`icr:order:20250101`(2025年1月1日的订单ID序列) - 优势:天然避免序列号溢出,单日理论上限42亿ID 2. **间戳处理**: - 使用秒级间戳(非毫秒),支持更长间跨度 - 间范围计算: $$ \text{最大年限} = \frac{2^{32}}{365 \times 24 \times 3600} \approx 136 \text{年} $$ 3. **分布式保障**: - Redis的`INCR`命令是原子操作,多节点并发安全 - 业务键隔离(如`order`, `user`)支持多业务并行[^3] #### 四、关键问题解决方案 1. **间回拨问题**: ```java // 在Redis存储最后有效间戳 String lastTimeKey = "last_valid_time:" + businessKey; Long lastTimestamp = stringRedisTemplate.opsForValue().get(lastTimeKey); if (lastTimestamp != null && currentSeconds < Long.parseLong(lastTimestamp)) { throw new TimeReverseException("系统间回拨异常"); } stringRedisTemplate.opsForValue().set(lastTimeKey, String.valueOf(currentSeconds)); ``` 2. **单日ID超42亿**: - 方案1:缩短间粒度(改用小间戳) ```java // 改为小分区:yyyy:MM:dd:HH String hourKey = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH")); ``` - 方案2:扩展序列号位宽(需调整ID结构) ```java // 新结构:28位小间戳 + 36位序列号 return (hourTimestamp << 36) | (sequence & 0xFFFFFFFFFL); ``` 3. **Redis高可用**: | 方案 | 实现方式 | 适用场景 | |---------------------|----------------------------|--------------| | Redis Sentinel | 主从自动故障转移 | 高可用场景 | | Redis Cluster | 数据分片存储 | 超大规模系统 | | 多实例分片 | 按业务键哈希到不同实例 | 低成本方案[^5]| #### 五、性能优化建议 1. **连接池配置**: ```yaml # Spring Boot配置 spring.redis.lettuce.pool: max-active: 16 max-idle: 8 min-idle: 4 ``` 2. **批量预生成ID**: ```java // 一次性获取1000个ID public List<Long> batchNextId(String businessKey, int batchSize) { String redisKey = ... // 构建key Long endSeq = stringRedisTemplate.opsForValue() .increment(redisKey, batchSize); List<Long> ids = new ArrayList<>(); for (long seq = endSeq - batchSize + 1; seq <= endSeq; seq++) { ids.add((timestamp << COUNT_BITS) | seq); } return ids; } ``` 3. **本地缓存优化**: - 在应用层缓存100~1000个ID,减少Redis访问频次 - 使用双Buffer机制:一个Buffer消费,另一个异步填充 > **性能基准**:单Redis节点可达5万+ ID/秒(引用[5]中200线程测试),延<2ms[^5] --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值