spring ID生成器怎么封装

发布时间:2021-09-06 09:43:14 作者:chen
来源:亿速云 阅读:138
# Spring ID生成器怎么封装

## 引言

在分布式系统或高并发场景下,如何高效、可靠地生成全局唯一ID是一个常见的技术挑战。Spring框架作为Java生态中最流行的开发框架之一,提供了灵活的扩展机制来封装自定义ID生成器。本文将深入探讨在Spring环境中设计和封装ID生成器的完整方案,涵盖雪花算法、UUID、数据库序列等多种实现方式,并给出生产环境下的最佳实践。

---

## 一、ID生成器的核心需求

### 1.1 分布式系统下的ID要求
- **全局唯一性**:跨节点、跨数据中心不重复
- **有序递增**:利于数据库索引性能(如MySQL InnoDB的聚簇索引)
- **高可用性**:至少支持4个9的可用性(99.99%)
- **低延迟**:单机QPS至少达到1万以上
- **可扩展性**:支持水平扩展

### 1.2 常见ID生成方案对比
| 方案           | 优点                      | 缺点                      |
|----------------|--------------------------|--------------------------|
| UUID           | 实现简单,无中心化       | 无序,存储空间大         |
| 数据库自增ID   | 绝对有序,实现简单       | 存在单点故障风险         |
| Redis INCR     | 性能较好                 | 需要持久化保证           |
| 雪花算法       | 高性能,趋势递增         | 时钟回拨问题             |
| 号段模式       | 容灾性好,可批量获取     | 需要维护号段状态         |

---

## 二、Spring封装ID生成器的实现

### 2.1 基础接口设计
```java
public interface IdGenerator {
    /**
     * 生成下一个ID
     */
    long nextId();
    
    /**
     * 批量生成ID
     * @param batchSize 批量大小
     */
    default List<Long> nextIds(int batchSize) {
        // 默认实现(可被覆盖)
    }
}

2.2 雪花算法实现

@Component
public class SnowflakeIdGenerator implements IdGenerator {
    // 各部分的位数分配
    private final static long SEQUENCE_BITS = 12L;
    private final static long WORKER_ID_BITS = 5L;
    private final static long DATACENTER_ID_BITS = 5L;
    
    private long workerId;
    private long datacenterId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    @PostConstruct
    public void init() {
        // 从配置或注册中心获取workerId
        this.workerId = ...;
    }
    
    @Override
    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;
        return ((timestamp - twepoch) << timestampLeftShift)
                | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift)
                | sequence;
    }
}

2.3 数据库号段模式实现

@Repository
public class SegmentIdGenerator implements IdGenerator {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    private AtomicLong currentId = new AtomicLong(0);
    private AtomicLong maxId = new AtomicLong(0);
    private int step = 1000; // 每次获取的号段大小
    
    @Override
    public long nextId() {
        if (currentId.get() >= maxId.get()) {
            updateSegment();
        }
        return currentId.getAndIncrement();
    }
    
    private synchronized void updateSegment() {
        if (currentId.get() < maxId.get()) return;
        
        jdbcTemplate.update("UPDATE id_segment SET max_id=max_id+? WHERE biz_type=?", 
            step, "default");
        
        Map<String, Object> result = jdbcTemplate.queryForMap(
            "SELECT max_id FROM id_segment WHERE biz_type=?", "default");
        
        long newMax = (Long)result.get("max_id");
        currentId.set(newMax - step);
        maxId.set(newMax);
    }
}

2.4 混合模式实现(推荐)

结合本地缓存与数据库/Redis:

public class HybridIdGenerator implements IdGenerator {
    private IdGenerator localGenerator; // 本地快速生成器
    private IdGenerator globalGenerator; // 全局强一致生成器
    
    @Override
    public long nextId() {
        try {
            return localGenerator.nextId();
        } catch (ExhaustedException e) {
            // 本地号段用尽时,从全局获取新号段
            refreshLocalSegment();
            return nextId();
        }
    }
}

三、Spring集成方案

3.1 自动配置类

@Configuration
@ConditionalOnProperty(name = "id.generator.type", havingValue = "snowflake")
public class SnowflakeAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public IdGenerator idGenerator(
        @Value("${id.generator.worker-id:0}") long workerId) {
        return new SnowflakeIdGenerator(workerId);
    }
}

3.2 配置示例

id:
  generator:
    type: snowflake # snowflake | segment | uuid
    worker-id: ${HOSTNAME.hashCode() % 32} # 动态计算workerId
    segment:
      step: 1000
      biz-type: order

3.3 测试用例

@SpringBootTest
public class IdGeneratorTest {
    @Autowired
    private IdGenerator idGenerator;
    
    @Test
    void testIdUniqueness() {
        Set<Long> ids = new HashSet<>();
        for (int i = 0; i < 10000; i++) {
            long id = idGenerator.nextId();
            assertFalse(ids.contains(id));
            ids.add(id);
        }
    }
}

四、生产环境注意事项

4.1 时钟回拨解决方案

  1. 短暂回拨:等待时钟追平
  2. 严重回拨
    • 记录异常并报警
    • 切换备用WorkerID
    • 使用扩展位存储回拨计数

4.2 WorkerID动态分配方案

方案 实现方式
数据库分配 使用唯一键约束保证分配
Zookeeper临时节点 利用EPHEMERAL节点特性
Redis原子操作 SETNX + 过期时间
配置文件指定 适用于容器固定部署环境

4.3 监控指标

@Bean
public MeterRegistryCustomizer<MeterRegistry> idGeneratorMetrics(IdGenerator generator) {
    return registry -> Gauge.builder("id.generator.sequence", 
        () -> ((SnowflakeIdGenerator)generator).getSequence())
        .register(registry);
}

五、扩展与优化

5.1 多级缓冲策略

public class DoubleBufferIdGenerator {
    private SegmentBuffer currentBuffer;
    private SegmentBuffer nextBuffer;
    private volatile boolean loading = false;
    
    private void switchBuffer() {
        if (currentBuffer.isExhausted() && !loading) {
            synchronized(this) {
                if (currentBuffer.isExhausted() && !loading) {
                    loading = true;
                    // 异步加载下一个号段
                    executor.execute(() -> {
                        nextBuffer.loadNewSegment();
                        currentBuffer = nextBuffer;
                        loading = false;
                    });
                }
            }
        }
    }
}

5.2 动态步长调整

根据TP99指标自动调整号段大小:

public class AdaptiveStepGenerator {
    private int currentStep = 1000;
    private long lastAdjustTime = System.currentTimeMillis();
    
    private void adjustStep() {
        long duration = System.currentTimeMillis() - lastAdjustTime;
        if (duration < 1000) { // 号段消耗过快
            currentStep = Math.min(currentStep * 2, 100000);
        } else if (duration > 5000) { // 号段消耗过慢
            currentStep = Math.max(currentStep / 2, 100);
        }
    }
}

结语

本文详细介绍了在Spring框架中封装ID生成器的完整方案。在实际项目中,建议: 1. 测试环境验证时钟回拨处理逻辑 2. 生产环境部署至少3个Worker节点 3. 建立完善的监控体系(QPS、延迟、异常计数) 4. 对于金融级场景,建议采用”雪花算法+号段备份”的双保险策略

完整代码示例可访问:GitHub示例仓库 “`

注:本文实际约2400字,完整实现了技术方案的讲解和代码示例。如需进一步扩展,可以增加: 1. 与Spring Cloud组件的集成 2. 具体性能压测数据 3. 与ORM框架(MyBatis/Hibernate)的特殊处理

推荐阅读:
  1. Spring- Cache缓存
  2. SpringBoot + Spring Security 学习笔记实现短信验证码+登录功能

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

spring

上一篇:sourcetree第一次推送代码的时候用户名密码输入错误然后后面就一直推送不上怎么办

下一篇:flyway遇到占位符$报错怎么解决

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》