分布式自增数据库ID


引言

今天在写项目的时候学习了一个用代码编写的自增的数据库ID,其实是一个ID缓冲池。使用了golang中chan类型。

建表

我们希望该ID缓冲池可以为我们其他不同的数据表进行ID的生成,因此需要建一个如下表:

CREATE TABLE `uid` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `business_id` varchar(128) COLLATE utf8mb4_bin NOT NULL COMMENT '业务id',
  `max_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '最大id',
  `step` int(10) unsigned NOT NULL DEFAULT '1000' COMMENT '步长',
  `description` varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT '描述',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_business_id` (`business_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='分布式自增主键';

-- ----------------------------
-- Records of uid
-- ----------------------------
INSERT INTO `uid` VALUES ('1', 'device_id', '1689', '10', '设备id', '2019-10-15 16:42:05', '2020-02-05 06:57:29');
INSERT INTO `uid` VALUES ('2', 'test', '20', '10', '测试', '2020-02-05 06:59:44', '2020-02-05 07:05:12');

代码思路

定义结构体绑定sql

type Uid struct {
    db         *sql.DB
    businessId string
    ch         chan int64//缓冲池的大小
    min, max   int64
}

新建一个UID

// 新建一个Uid并一直生产ID
func NewUid(db *sql.DB, businessId string, len int) (*Uid, error) {
    lid := Uid{
        db:         db,
        businessId: businessId,
        ch:         make(chan int64, len),
    }
    go lid.produceId()
    return &lid, nil
}

生产ID

  • 首先从数据库中加载获得当前数据的最大值

  • 循环生成自增ID

    func (u *Uid) produceId() {
      // 从数据库中获取id
      u.reload()
    
      for {
          if u.min >= u.max {
              // 从数据库中获取id
              u.reload()
          }
          u.min++
          u.ch <- u.min
      }
    }

    在上述代码中当ch中达到了最大容量,会发生阻塞。

数据库中获得ID

获得数据库中的ID,如果获取失败,将停顿一秒,继续尝试获取

func (u *Uid) reload() error {
    var err error
    for {
        err = u.getFromDB()
        if err == nil {
            return nil
        }

        // 如果获取失败,等待
        time.Sleep(time.Second)
    }
}

操作数据库

func (u *Uid) getFromDB() error {
    var (
        maxId int64
        step  int64
    )
    tx, err := u.db.Begin()
    if err != nil {
        return err
    }
    defer tx.Rollback()
    //sql语句
    sqlquery := "select max_id,step from uid where business_id = ? FOR UPDATE "
    err = tx.QueryRow(sqlquery, u.businessId).Scan(&maxId, &step)
    if err != nil {
        return err
    }
    // 更新数据库中uid的最大值
    update := "update uid set max_id = ? where business_id = ?"
    _, err = tx.Exec(update, maxId+step, u.businessId)
    if err != nil {
        return err
    }
    err = tx.Commit()
    if err != nil {
        return err
    }
    u.min = maxId
    u.max = maxId + step
    return nil
}

有了这个数据库自增ID的管理,当我们分布式操作数据库时,就可以保证不会发生冲突了


文章作者: 陌无崖
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 陌无崖 !
 上一篇
红黑树 红黑树
概念引入假如我们遇到一个猜数字的题,即给定一个序列,猜出该序列中的某个数字。一般该序列是有序的,用户猜出一个数字之后提示该数字是大了还是小了。 折半法这种方法最容易想到,每次猜出该序列中的中位数,然后将序列分成了两个序列,这样每猜一次,将排
下一篇 
计算机操作系统——锁的进化 计算机操作系统——锁的进化
导语相信大家都知道金鱼是不知道饥饿的,如果有食物吃,金鱼就会不停的填饱肚子,哪怕被撑死。在计算机中锁的进化可以用金鱼生存的例子来引入。 金鱼生存左一和右尔共同养了一条金鱼,该金鱼每天仅仅喂食一次,如果多喂了一次,鱼会被撑死,如果没有喂金鱼,
  目录