一个兼容 Redis 协议的 ID 生成器 - V2EX
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 2729 次点击
    这是一个创建于 3550 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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  
    PRO
       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     Solana     2764 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 02:20 PVG 10:20 LAX 18:20 JFK 21:20
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86