分布式ID生成方案(三):MySQL下不同模式的实现

数据库自增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,能够保证并发更新的正确性

那么我们可以通过如下几个步骤来获取一个可用的号段,

  1. 查询当前的max_id信息:select id, biz_type, max_id, step, version from id_generator where biz_type=0;
  2. 计算新的max_id: new_max_id = max_id + step
  3. 更新DB中的max_id:update id_generator set max_id=#{new_max_id} , verison=version+1 where max_id=#{max_id} and version=#{version}
  4. 如果更新成功,则可用号段获取成功,新的可用号段为(max_id, new_max_id]
  5. 如果更新失败,则号段可能被其他线程获取,回到步骤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;
}

完整代码地址:

号段模式生成ID方案实现

猜你喜欢

转载自blog.csdn.net/u013034223/article/details/105639617
今日推荐