数据库自增ID
基于MySQL,最简单的方法是使用auto_increment 来生成全局唯一递增ID,但最致命的问题是在高并发情况下,数据库压力大,DB单点存在宕机风险。
数据库多主模式
针对上面方式的缺点,我们可以使用数据库主从模式来做高可用方面的优化,比如双主模式,两个MySQL设置不同的初始值及步长:
MySQL1:
set @@auto_increment_offset = 1; -- 起始值
set @@auto_increment_increment = 2; -- 步长
MySQL2:
set @@auto_increment_offset = 2; -- 起始值
set @@auto_increment_increment = 2; -- 步长
如果后续需要增加第三台MySQL实例,则需要人工修改一、二两台MySQL实例的起始值和步长,把第三台机器的ID起始生成位置设定在比现有最大自增ID的位置远一些,但必须在一、二两台MySQL实例ID还没有增长到第三台MySQL实例的起始ID值的时候,否则自增ID就要出现重复了,必要时可能还需要停机修改。
这种方式解决了DB单点的问题,但不利于后续扩容,而且实际上单个数据库自身压力还是大,依旧无法满足高并发场景。
以上方式都是每次访问db,每次都只生成一个id,这样的话,频繁生成id,频繁访问数据库,压力同样很大,所以我们可以从获取id的角度优化,每次访问数据库,可以获取一批id,从而减少对数据库的压力。
数据库号段模式
一批id,我们可以看成是一个id范围,例如(1000,2000],这个1000到2000也可以称为一个"号段",我们一次向db申请一个号段,加载到内存中,然后采用自增的方式来生成id,这个号段用完后,再次向db申请一个新的号段,这样对db的压力就减轻了很多,同时内存中直接生成id,性能则提高了很多。那么保存db号段的表该怎设计呢?
CREATE TABLE id_generator (
id int(10) NOT NULL AUTO_INCREMENT,
max_id bigint(20) NOT NULL COMMENT '当前最大id',
step int(20) NOT NULL COMMENT '号段的布长',
biz_type int(20) NOT NULL COMMENT '业务类型',
version int(20) NOT NULL COMMENT '版本号',
PRIMARY KEY (`id`)
)
- max_id代表当前最大的可用id
- biz_type,这个代表业务类型,不同的业务的id隔离
- step代表号段的长度
- version是一个乐观锁,每次更新都加上version,能够保证并发更新的正确性
那么我们可以通过如下几个步骤来获取一个可用的号段,
- 查询当前的max_id信息:select id, biz_type, max_id, step, version from id_generator where biz_type=0;
- 计算新的max_id: new_max_id = max_id + step
- 更新DB中的max_id:update id_generator set max_id=#{new_max_id} , verison=version+1 where max_id=#{max_id} and version=#{version}
- 如果更新成功,则可用号段获取成功,新的可用号段为(max_id, new_max_id]
- 如果更新失败,则号段可能被其他线程获取,回到步骤1,进行重试
核心代码如下:
@Override
public List<Long> generateSerialId(Integer type) {
List<Long> idList = new ArrayList<>();
for (int i = 0; i < BATCH_SIZE; i++) {
Long id = generateId(type);
idList.add(id);
}
return idList;
}
@Transactional
@Override
public Long generateId(Integer type) {
//可能失败,需要重试
for (int i = 0; i < TRY_TIMES; i++) {
QueryWrapper<IdGenerator> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(IdGenerator::getBizType,type);
IdGenerator idGenerator = idGeneratorMapper.selectOne(queryWrapper);
if (idGenerator == null) {
try {
idGenerator = new IdGenerator().setBizType(0).setMaxId(1L).setStep(2).setVersion(0);
idGeneratorMapper.insert(idGenerator);
return idGenerator.getMaxId();
} catch (Exception e) {
log.error("失败了");
}
} else {
try {
Long oldMax =idGenerator.getMaxId();
Long newMax = oldMax + idGenerator.getStep();
LambdaUpdateWrapper<IdGenerator> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(IdGenerator::getBizType,type).eq(IdGenerator::getVersion,idGenerator.getVersion()).eq(IdGenerator::getMaxId,oldMax);
idGenerator.setMaxId(newMax);
idGenerator.setVersion(idGenerator.getVersion() + 1);
int row = idGeneratorMapper.update(idGenerator, updateWrapper);
if(row == 1){
return idGenerator.getMaxId();
}else{
log.error("失败了");
}
} catch (Exception e) {
log.error("失败了");
}
}
}
return null;
}
完整代码地址: