Golang Go语言中一个兼容 Redis 协议的 ID 生成器
idgo 简介
1. idgo 特点
idgo 是一个利用 MySQL 批量生成 ID 的 ID 生成器, 主要有以下特点:
- 生成的 ID 是顺序递增的。
- 每次通过事务批量取 ID,性能较高,且不会对 MySQL 造成压力。
- 当 ID 生成器服务崩溃后,可以继续生成有效 ID,避免了 ID 回绕的风险。
- 服务端模拟 Redis 协议,通过
GET
和SET
获取和设置 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 目前只支持四个 redis 命令:
1 . SET key value,通过这个操作设置 id 生成器的初始值。
例如: SET abc 123
2. GET key,通过该命令获取 id 。
3. EXISTS key,查看一个 key 是否存在。
4. DEL key,删除一个 key 。
3. 安装和使用 idgo
- 安装 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
Golang Go语言中一个兼容 Redis 协议的 ID 生成器
一个兼容 Redis 协议的 ID 生成器
更多关于Golang Go语言中一个兼容 Redis 协议的 ID 生成器的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
这与直接用 redis ,有什么区别?或者说有什么优势?
应该是实现一个精简版的 redis ,只用于生成自增 ID 吧。
一点都没精简, mysql 比 redis 还重
如果是生成 id 依赖 mysql ,那么为什么不直接用 mysql
如果生成 id 不依赖 mysql ,只是拿 mysql 当存储,那么为啥要用 mysql
建议去除 mysql,至于唯一 id 生成可以采用 mysql uuid_short 函数实现
还不如装个 redis 来 INCR 呢
直接用 redis ,一旦机器宕机, id 生成器对应的 key 计数器有可能没有及时保存到磁盘(在内存中),然后重启机器从 rdb 中恢复的话, id 会回绕。重新生成已经生成过的 id 。
还有个原因就是想生成的 ID 是数字,然后这样用于 MySQL 分表比较分表。
开发 idgo 确实是有补齐 kingshard 分表的目的。至于是否集成,我还在考虑。:)
用事务怎么生成 id ?能不能具体说一下?
说明里只说了常用的方法是什么,没说自己的方法是什么,这也是我看完之后第一个疑问
redis 可以不用 snapshot 模式,可以用 append only file ,不用担心宕机数据丢失。
我觉得你的 proxy 可以用我们的方案配置直接存储到 etcd 中去.
恩,但主要我们线上的机器都是 RDB 方式。而且 AOF 也有可能丢失 1s 的数据。基于这个考虑放弃了用 redis 生成 id 的方案。
通过事务的方式 update 唯一的一条记录,比如将 1000 修改为 2000 。那么然后 idgo 就可以在本地分配 1000-2000 之间的 id 了。
你们的方案在哪?我参考一下。:)
MySQL 起来后加一个固定的偏移(比如 1000 )后可以保证不会重现重复的 ID 。因为只可能丢失固定的一段 id 值。
idgo.go 121 行 的 defer 是不是应该放在 err 条件前面一行
rows, err := tx.Query(selectForUpdate)
if err != nil {
tx.Rollback()
return 0, err
}
defer rows.Close() //line 121
就是通过 etcd watch 实现配置自动重加载
sqlserver 有 sequence 这个东西, mysql 还真没什么好替代品。赞楼主!
谢谢
了解了,之前项目做个类似的功能,只是 mc+mysql+少量代码实现了类似功能。
感觉你的这个项目得基于 mysql 有些麻烦,考虑过加入持久化吗?
基于 mysql 是可以利用事务批量申请 ID 。
redis 也可以批量 incr ,把 redis 配置的 appendfsync 参数改为 always ,可以替代该方案中的 mysql 事务。
在Go语言中实现一个兼容Redis协议的ID生成器,可以通过集成现有的Redis客户端库(如go-redis
)来实现。这种方法不仅利用了Redis的高性能和分布式特性,还能确保ID生成的全局唯一性和顺序性。
首先,你需要安装go-redis
库:
go get github.com/go-redis/redis/v8
然后,可以创建一个简单的ID生成器服务。这里,我们可以使用Redis的INCR
或INCRBY
命令来生成递增的ID。如果你需要更复杂的ID格式(如带有时间戳和机器标识),可以自定义生成逻辑。
示例代码如下:
package main
import (
"context"
"fmt"
"log"
"github.com/go-redis/redis/v8"
)
func main() {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379", // Redis地址
Password: "", // 无密码
DB: 0, // 使用默认DB
})
ctx := context.Background()
id, err := rdb.Incr(ctx, "id_generator").Result()
if err != nil {
log.Fatal(err)
}
fmt.Println("Generated ID:", id)
}
上述代码连接到一个本地的Redis实例,并使用INCR
命令生成一个递增的ID。你可以根据需求调整Redis连接配置和ID生成策略。此外,考虑到并发和性能,可以进一步优化Redis连接池和错误处理逻辑。