分布式自增数据库ID

时间:2022-07-22
本文章向大家介绍分布式自增数据库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的管理,当我们分布式操作数据库时,就可以保证不会发生冲突了

END