V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
qichengzx
V2EX  ›  Go 编程语言

SEQSVR: Go + MySQL 实现的高性能 ID 生成服务

  •  
  •   qichengzx ·
    qichengzx · 2018-07-02 10:42:13 +08:00 · 3099 次点击
    这是一个创建于 2115 天前的主题,其中的信息可能已经有所发展或是发生改变。

    SEQSVR

    Go + MySQL 实现的 ID 生成服务

    GitHub:https://github.com/qichengzx/seqsvr 欢迎 star。

    特性

    • 分布式:可任意横向扩展
    • 高性能:分配 ID 只访问内存(到达上限会请求数据库一次)
    • 易用性:对外提供 HTTP 服务
    • 唯一性:MySQL 自增 ID,永不重复
    • 高可靠:MySQL 持久化

    依赖项

    本项目使用下列优秀的项目作为必要组件。

    安装

    注意:需要在启动之前创建数据库并修改配置文件中数据库的配置。

    go get 方式:

    需保证 $GOPATH/bin 在系统 PATH 中。

    go get github.com/qichengzx/seqsvr
    seqsvr
    

    单独编译:

    git clone [email protected]:qichengzx/seqsvr.git
    cd seqsvr
    go build .
    ./seqsvr
    

    Docker 方式:

    Dockerfile 使用了 Docker 多阶段构建功能,需保证 Docker 版本在 17.05 及以上。详见:Use multi-stage builds

    git clone [email protected]:qichengzx/seqsvr.git
    cd seqsvr
    docker build seqsvr:latest .
    docker run -p 8000:8000 seqsvr:latest
    

    初始化数据库

    数据库名称可以自定义,修改 config.yml 即可。

    然后导入以下 SQL 生成数据表。

    CREATE TABLE `generator_table` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `uuid` char(36) NOT NULL COMMENT '机器识别码',
      PRIMARY KEY (`id`),
      UNIQUE KEY `id_UNIQUE` (`id`),
      UNIQUE KEY `stub_UNIQUE` (`uuid`) USING BTREE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
    

    修改配置

    配置文件使用 YAML 格式。

    #app
    port: ':8000'
    
    #service
    step: 100
    
    #db
    mysql:
      user: 'root'
      password: ''
      host: 'tcp(localhost:3306)'
      database: 'sequence'
    
    

    可修改端口号及 MySQL 的配置。

    使用

    curl http://localhost:8000/new
    
    {"code":0,"msg":"ok","data":{"id":101}}
    

    原理

    本项目设计原理来自 携程技术中心 的干货 | 分布式架构系统生成全局唯一序列号的一个思路

    服务初始化后第一次请求会在 MySQL 数据库中插入一条数据,以生成初始 ID。

    后续的请求,都会在内存中进行自增返回,并且保证返回的 ID 不会超过设置的上限,到达上限后会再次从 MySQL 中更新数据,返回新的初始 ID。

    核心 SQL
    REPLACE INTO `generator_table` (uuid) VALUES ("54f5a3e2-e04c-4664-81db-d7f6a1259d01");
    

    欢迎 star。

    第 1 条附言  ·  2018-07-04 10:08:45 +08:00
    新增使用 db 获取新 ID 时加锁。
    16 条回复    2018-07-03 12:04:02 +08:00
    puritania
        1
    puritania  
       2018-07-02 10:59:52 +08:00 via iPhone   ❤️ 1
    这个同样要单独部署吧,引入单点 mysql 和线程锁增加了复杂性,悲观情况下可能会有很多问题,我觉得这方案跟 snowflake 比还是不行
    wych
        2
    wych  
       2018-07-02 11:39:20 +08:00
    发出来的 ID 是单调递增的么?
    qichengzx
        3
    qichengzx  
    OP
       2018-07-02 11:56:04 +08:00
    @puritania
    感谢回复。
    1.可以单独部署成一个服务,也可以整理成一个包放到已有项目中。项目目前只是以独立服务作为实现。
    2.MySQL 的问题,如果请求量不是特别大应该还好。业务量大也可以通过修改步长或 MySQL 增加机器解决。
    3.根据携程的介绍是 Java 版使用情况还不错,而且个人认为 Go 的协程开销应该比 Java 低很多。

    这个方案与 snowflake 相比,个人觉得原理和实现都比较简单,适合比较初期的项目。

    有说的不对的还请指正。
    qichengzx
        4
    qichengzx  
    OP
       2018-07-02 11:58:17 +08:00
    @wych
    感谢回复。
    是单调递增的。
    glacer
        5
    glacer  
       2018-07-02 14:17:37 +08:00   ❤️ 1
    @qichengzx 通常只有分布式系统(如分布式数据库)才需要用到全局唯一 id,如果是初期的项目,往往根本不需要用分布式系统,根本不存在你这个系统的使用场景啊。
    pathbox
        6
    pathbox  
       2018-07-02 14:53:02 +08:00 via iPhone
    用 redis 的 inc 可以更快
    qiyuey
        7
    qiyuey  
       2018-07-02 15:12:00 +08:00
    @puritania snowflake 解决不了时间偏移的问题吧
    qichengzx
        8
    qichengzx  
    OP
       2018-07-02 15:24:21 +08:00
    @glacer
    感谢回复。
    没有接触过分布式的系统,至少没有参与过。
    写这个的初衷是看到携程的那篇文章,觉得这种方案还挺有意思的,就找时间做了实现,至于实际的使用场景,目前我确实不清楚。
    根据文章的介绍,携程是用在了账号系统中。
    个人觉得比如 MongoDB,没有数字 ID 的系统中,如果要用到数字 ID,这种方案也是适合的。
    qichengzx
        9
    qichengzx  
    OP
       2018-07-02 15:25:57 +08:00
    @pathbox
    感谢回复。

    关于 Redis 的 incr,原文中也有提到。优缺点引用如下:
    优点:
    不依赖于数据库,灵活方便,且性能优于数据库。
    数字 ID 天然排序,对分页或者需要排序的结果很有帮助。
    使用 Redis 集群也可以防止单点故障的问题。

    缺点:

    如果系统中没有 Redis,还需要引入新的组件,增加系统复杂度。
    需要编码和配置的工作量比较大,多环境运维很麻烦,
    在开始时,程序实例负载到哪个 redis 实例一旦确定好,未来很难做修改。
    zhouquan03
        10
    zhouquan03  
       2018-07-02 19:20:19 +08:00
    这样做是有很大风险的。内存进行自增的话,高可用怎么保证?服务挂掉重启怎么保证生成的 id 不重复?

    还不如搭建个高可用的 redis,进行 incr 操作,redis QPS 还远远高于 mysql。
    NUT
        11
    NUT  
       2018-07-02 19:24:56 +08:00
    要说单调递增的 ID 生成,还数 TiDB 的 TSO 牛逼, 设计很轻巧,性能又不差,实现还简单。
    yanaraika
        12
    yanaraika  
       2018-07-02 19:24:57 +08:00
    @zhouquan03 redis 也没法保证生成 id 不重复吧
    yanaraika
        13
    yanaraika  
       2018-07-02 19:30:53 +08:00
    @zhouquan03 除非用基于 Raft 的 floyd,否则 rust-cluster 默认都是最终一致性。但 Raft 引起的性能下降很严重
    puritania
        14
    puritania  
       2018-07-02 19:41:19 +08:00 via iPhone
    @qiyuey #7 系统时间问题我觉得相比引入其他依赖增加调用链路来说出错几率更小。
    qichengzx
        15
    qichengzx  
    OP
       2018-07-03 09:57:45 +08:00
    @zhouquan03 服务挂掉重启后,会从数据库重新写一条记录拿到一个新的 ID 做起点。
    rahuahua
        16
    rahuahua  
       2018-07-03 12:04:02 +08:00
    如果是单台 mysql,肯定不能算高可用,如果要搭集群 mysql,对于项目初期过于复杂了。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3437 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 81ms · UTC 12:04 · PVG 20:04 · LAX 05:04 · JFK 08:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.