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

一个兼容 Redis 协议的 ID 生成器

  •  1
     
  •   flikecn ·
    flike · 2016-04-07 13:55:28 +08:00 · 2292 次点击
    这是一个创建于 3151 天前的主题,其中的信息可能已经有所发展或是发生改变。

    idgo 简介

    1. idgo 特点

    idgo 是一个利用 MySQL 批量生成 ID 的 ID 生成器, 主要有以下特点:

    • 生成的 ID 是顺序递增的。
    • 每次通过事务批量取 ID,性能较高,且不会对 MySQL 造成压力。
    • 当 ID 生成器服务崩溃后,可以继续生成有效 ID,避免了 ID 回绕的风险。
    • 服务端模拟 Redis 协议,通过GETSET获取和设置 key 。不必开发专门的获取 ID 的 SDK ,直接使用 Reids 的 SDK 就可。

    业界已经有利于 MySQL 生成 ID 的方案,都是通过:

    REPLACE INTO Tickets64 (stub) VALUES ('a');
    SELECT LAST_INSERT_ID();
    

    这种方式生成 ID 的弊端就是每生成一个 ID 都需要查询一下 MySQL,当 ID 生成过快时会对 MySQL 造成很大的压力。这正式我开发这个项目的原因。服务端兼容 Redis 协议是为了避免单独开发和 idgo 通信的 SDK 。

    2. idgo 架构

    idgo 和前端应用是才有 redis 协议通信的,然后每个id_key是存储在 MySQL 数据库中,每个 key 会在 MySQL 中生成一张表,表中只有一条记录。这样做的目的是保证当 idgo 由于意外崩溃后,id_key对应的值不会丢失,这样就避免产生了 id 回绕的风险。 idgo_arch

    idgo 目前只支持四个 redis 命令:

    1 . SET key value,通过这个操作设置 id 生成器的初始值。
    例如: SET abc 123
    2. GET key,通过该命令获取 id 。
    3. EXISTS key,查看一个 key 是否存在。
    4. DEL key,删除一个 key 。
    

    3. 安装和使用 idgo

    1. 安装 idgo
    	1. 安装 Go 语言环境( Go 版本 1.3 以上),具体步骤请 Google 。
    	2. 安装 godep 工具, go get github.com/tools/godep 。 
    	2. git clone https://github.com/flike/idgo src/github.com/flike/idgo
    	3. cd src/github.com/flike/idgo
    	4. source ./dev.sh
    	5. make
    	6. 设置配置文件
    	7. 运行 idgo 。./bin/idgo -config=etc/idgo.toml
    

    设置配置文件(etc/idgo.toml):

    #idgo 的 IP 和 port
    addr="127.0.0.1:6389"
    #log_path: /Users/flike/src 
    #日志级别
    log_level="debug"
    
    [storage_db]
    mysql_host="127.0.0.1"
    mysql_port=3306
    db_name="idgo_test"
    user="root"
    password=""
    max_idle_conns=64
    

    操作演示:

    #启动 idgo
    ➜  idgo git:(master) ✗ ./bin/idgo -config=etc/idgo.toml
    2016/04/07 11:51:20 - INFO - server.go:[62] - [server] "NewServer" "Server running" "netProto=tcp|address=127.0.0.1:6389" req_id=0
    2016/04/07 11:51:20 - INFO - main.go:[80] - [main] "main" "Idgo start!" "" req_id=0
    
    #启动一个客户端连接 idgo
    ➜  ~  redis-cli -p 6389
    redis 127.0.0.1:6389> get abc
    (integer) 0
    redis 127.0.0.1:6389> set abc 100
    OK
    redis 127.0.0.1:6389> get abc
    (integer) 101
    redis 127.0.0.1:6389> get abc
    (integer) 102
    redis 127.0.0.1:6389> get abc
    (integer) 103
    redis 127.0.0.1:6389> get abc
    (integer) 104
    redis 127.0.0.1:6389>
    
    

    4. 压力测试

    压测环境

    |类别|名称| |---|---| |OS |CentOS release 6.4| |CPU |Common KVM CPU @ 2.13GHz| |RAM |2GB| |DISK |50GB| |Mysql |5.1.73|

    本地 mac 连接远程该 MySQL 实例压测 ID 生成服务。 每秒中可以生成 20 多万个 ID 。性能方面完全不会有瓶颈。

    5.ID 生成服务宕机后的恢复方案

    当 idgo 服务意外宕机后,可以切从库,然后将 idgo 对应的 key 加上适当的偏移量。

    License

    MIT

    开源地址: https://github.com/flike/idgo

    30 条回复    2016-04-08 14:39:54 +08:00
    flikecn
        1
    flikecn  
    OP
       2016-04-07 14:04:30 +08:00
    一个兼容 Redis 协议的 ID 生成器
    surfire91
        2
    surfire91  
       2016-04-07 14:49:52 +08:00
    这与直接用 redis ,有什么区别?或者说有什么优势?
    moro
        3
    moro  
       2016-04-07 14:54:59 +08:00
    @surfire91 应该是实现一个精简版的 redis ,只用于生成自增 ID 吧。
    skydiver
        4
    skydiver  
       2016-04-07 14:57:27 +08:00
    @moro 一点都没精简, mysql 比 redis 还重
    skydiver
        5
    skydiver  
       2016-04-07 15:02:42 +08:00
    如果是生成 id 依赖 mysql ,那么为什么不直接用 mysql
    如果生成 id 不依赖 mysql ,只是拿 mysql 当存储,那么为啥要用 mysql
    noahzh
        6
    noahzh  
       2016-04-07 15:06:46 +08:00
    建议去除 mysql,至于唯一 id 生成可以采用 mysql uuid_short 函数实现
    shiny
        7
    shiny  
       2016-04-07 15:08:05 +08:00
    还不如装个 redis 来 INCR 呢
    flikecn
        8
    flikecn  
    OP
       2016-04-07 15:15:22 +08:00
    @surfire91 直接用 redis ,一旦机器宕机, id 生成器对应的 key 计数器有可能没有及时保存到磁盘(在内存中),然后重启机器从 rdb 中恢复的话, id 会回绕。重新生成已经生成过的 id 。
    flikecn
        9
    flikecn  
    OP
       2016-04-07 15:17:09 +08:00
    @skydiver 用 MySQL 的原因就是将 ID 值存在 MySQL 中,宕机重启后,这个值不会回退。兼容 Redis 协议的目的是不想让客户端实现一个 SDK 和 idgo 通信。可以直接用 redis 的 sdk 直连 idgo 。
    flikecn
        10
    flikecn  
    OP
       2016-04-07 15:18:51 +08:00
    @noahzh 还有个原因就是想生成的 ID 是数字,然后这样用于 MySQL 分表比较分表。
    flikecn
        11
    flikecn  
    OP
       2016-04-07 15:19:33 +08:00
    @skydiver 生成 ID 依赖于 MySQL 事务,以事务的方式实现批量生成 ID
    noahzh
        12
    noahzh  
       2016-04-07 15:21:16 +08:00
    @flikecn 那个生成就是数字,也是连续的,我也是用来数据库分表的,可以直接集成到你的 proxy 里.
    flikecn
        13
    flikecn  
    OP
       2016-04-07 15:22:56 +08:00
    @noahzh 开发 idgo 确实是有补齐 kingshard 分表的目的。至于是否集成,我还在考虑。:)
    skydiver
        14
    skydiver  
       2016-04-07 15:32:23 +08:00
    @flikecn 用事务怎么生成 id ?能不能具体说一下?

    说明里只说了常用的方法是什么,没说自己的方法是什么,这也是我看完之后第一个疑问
    skydiver
        15
    skydiver  
       2016-04-07 15:34:28 +08:00
    @flikecn redis 可以不用 snapshot 模式,可以用 append only file ,不用担心宕机数据丢失。
    noahzh
        16
    noahzh  
       2016-04-07 15:46:31 +08:00
    @flikecn 我觉得你的 proxy 可以用我们的方案配置直接存储到 etcd 中去.
    flikecn
        17
    flikecn  
    OP
       2016-04-07 16:01:07 +08:00
    @skydiver 恩,但主要我们线上的机器都是 RDB 方式。而且 AOF 也有可能丢失 1s 的数据。基于这个考虑放弃了用 redis 生成 id 的方案。
    flikecn
        18
    flikecn  
    OP
       2016-04-07 16:04:18 +08:00
    @skydiver 通过事务的方式 update 唯一的一条记录,比如将 1000 修改为 2000 。那么然后 idgo 就可以在本地分配 1000-2000 之间的 id 了。
    flikecn
        19
    flikecn  
    OP
       2016-04-07 16:04:56 +08:00
    @noahzh 你们的方案在哪?我参考一下。:)
    skydiver
        20
    skydiver  
       2016-04-07 16:09:19 +08:00
    @flikecn 所以是每 1000 条修改一次数据库?中间如果挂掉怎么办
    flikecn
        21
    flikecn  
    OP
       2016-04-07 16:32:49 +08:00
    @skydiver MySQL 起来后加一个固定的偏移(比如 1000 )后可以保证不会重现重复的 ID 。因为只可能丢失固定的一段 id 值。
    moro
        22
    moro  
       2016-04-07 17:41:37 +08:00
    idgo.go 121 行 的 defer 是不是应该放在 err 条件前面一行

    rows, err := tx.Query(selectForUpdate)
    if err != nil {
    tx.Rollback()
    return 0, err
    }
    defer rows.Close() //line 121
    noahzh
        23
    noahzh  
       2016-04-07 21:29:06 +08:00
    @flikecn 就是通过 etcd watch 实现配置自动重加载
    detailyang
        24
    detailyang  
       2016-04-07 23:35:14 +08:00
    嘿嘿,搭车发下曾花了一个晚上改的,线上稳定跑了一年的、用 redis 改的发号器。。。原理跟你说的一样,开启 aof always ,高可用是靠 lvs 做的四层的负载均衡, 缺点嘛不是严格的单调递增。。。因为每个实例的增长序列不同, https://github.com/detailyang/id-generator
    jsq2627
        25
    jsq2627  
       2016-04-08 02:15:45 +08:00 via iPhone
    sqlserver 有 sequence 这个东西, mysql 还真没什么好替代品。赞楼主!
    flikecn
        26
    flikecn  
    OP
       2016-04-08 10:13:33 +08:00 via iPhone
    @detailyang 赞赞,我看看。
    flikecn
        27
    flikecn  
    OP
       2016-04-08 10:13:53 +08:00 via iPhone
    @jsq2627 谢谢
    surfire91
        28
    surfire91  
       2016-04-08 11:02:08 +08:00
    @flikecn 了解了,之前项目做个类似的功能,只是 mc+mysql+少量代码实现了类似功能。
    感觉你的这个项目得基于 mysql 有些麻烦,考虑过加入持久化吗?
    flikecn
        29
    flikecn  
    OP
       2016-04-08 13:41:11 +08:00 via iPhone
    @surfire91 基于 mysql 是可以利用事务批量申请 ID 。
    zeayes
        30
    zeayes  
       2016-04-08 14:39:54 +08:00
    @flikecn redis 也可以批量 incr ,把 redis 配置的 appendfsync 参数改为 always ,可以替代该方案中的 mysql 事务。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   927 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 20:36 · PVG 04:36 · LAX 12:36 · JFK 15:36
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.