├── .gitignore ├── .travis.yml ├── README.md ├── app.ini ├── config.go ├── db.sql ├── go-id-builder.go ├── model.go ├── tools.go └── watch.go /.gitignore: -------------------------------------------------------------------------------- 1 | go-id-builder -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.12.x 5 | 6 | before_install: 7 | - go get github.com/mitchellh/gox 8 | - go get 9 | 10 | install: 11 | - gox -osarch=darwin/386 12 | - gox -osarch=darwin/amd64 13 | - gox -osarch=linux/amd64 14 | - gox -osarch=linux/386 15 | - gox -osarch=windows/amd64 16 | - gox -osarch=windows/386 17 | 18 | script: 19 | - ls -l|grep go-id-builder 20 | 21 | before_deploy: 22 | - zip -q darwin_386.zip go-id-builder_darwin_386 README.md app.ini db.sql 23 | - zip -q darwin_amd64.zip go-id-builder_darwin_amd64 README.md app.ini db.sql 24 | - zip -q linux_386.zip go-id-builder_linux_386 README.md app.ini db.sql 25 | - zip -q linux_amd64.zip go-id-builder_linux_amd64 README.md app.ini db.sql 26 | - zip -q windows_386.zip go-id-builder_windows_386.exe README.md app.ini db.sql 27 | - zip -q windows_amd64.zip go-id-builder_windows_amd64.exe README.md app.ini db.sql 28 | deploy: 29 | provider: releases 30 | api_key: "$GH_TOKEN" 31 | file: 32 | - darwin_386.zip 33 | - darwin_amd64.zip 34 | - linux_386.zip 35 | - linux_amd64.zip 36 | - windows_386.zip 37 | - windows_amd64.zip 38 | skip_cleanup: true 39 | on: 40 | tags: true 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-id-builder 2 | [![travis-ci](https://travis-ci.org/freshcn/go-id-builder.svg?branch=master)](https://travis-ci.org/freshcn/go-id-builder) 3 | 4 | 这是一个使用golang开发的ID生成器,它可以提供通过高效的方式产生连续唯一的ID值。在分库分表时可以提供非常有用的帮助。 5 | # 为什么要做id生成器 6 | 7 | 常常在数据库进行分库分表的需求处理时,需要给表产生一个自增的主键id。单表的时候我们都是通过给表添加一个自增字段来实现的。当需要分表时就会发现这样的方式会出现每个表都有一套自己的自增id。特别是我们需要通过这个ID来实现分表算法时(一般都是id%表数量),那么如何在多表中产生一个连续自增的ID成为一个问题。 8 | 9 | # 如何实现的 10 | 11 | go-id-builder使用mysql来做为最大id数的持久化存储。程序在每次启动的时候都会加载数据表中当前的所记录的id类型,将会自动申请1000个(配置文件中可修改)新的id号,加载到一个缓冲通道中,当用户向生成器的api接口发起请求时,从对应的缓冲通道中将数据取出返回给客户端。 12 | 13 | # 安装方式 14 | 15 | 你可以直接通过下面的命令来直接安装 16 | 17 | `go get github.com/freshcn/go-id-builder` 18 | 19 | 将 `db.sql`中的数据表结构导入到你的 MYSQL 数据库中,在 app.ini 中配置你的mysql 数据的连接信息。 20 | 21 | 也可以通过项目的`releases` https://github.com/freshcn/go-id-builder/releases 中直接下载二进制包来安装运行 22 | 23 | # 配置数据库 24 | 25 | 先将`db.sql`中的mysql语句安装到数据库中,并在`app.ini`中配置相应的mysql的数据库连接信息。数据库脚本中默认为表设置的是innodb引擎,你可以按需要修改为你正在使用的其他引擎。 26 | 27 | 数据库中已经默认提供了一个test id名,你可以在运行了程序后通过 28 | 29 | `http://localhost:3002?name=test` 30 | 31 | 来获取到test这个id名所产生的第一个id值。 32 | 33 | 你可以向数据表中添加新的id名来支持新的id值的产生 34 | 35 | # api说明 36 | 37 | 在程序运行成功后默认会占用系统的3002端口,你可以在配置文件中修改所占用的端口号。 38 | 39 | 你可以通过get或post请求方式来请求接口。允许的参数如下表: 40 | 41 | - name - id值名 42 | - num - 需要请求的id数量,默认最大为100个,可在配置文件中修改 43 | -------------------------------------------------------------------------------- /app.ini: -------------------------------------------------------------------------------- 1 | # 服务所监听的端口号 2 | bind=:3002 3 | # 每次向数据申请的id数量 4 | per_step=1000 5 | 6 | # 客户端每次最大申请的ID数量 7 | max_request_num = 100 8 | 9 | # mysql相关的配置 10 | [mysql] 11 | dsn=name:passwd@tcp(yourname:3306)/id_builder?charset=utf8 12 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "github.com/vaughan0/go-ini" 7 | ) 8 | 9 | var config *ini.File 10 | 11 | func init() { 12 | configPath := fmt.Sprintf("%s%capp.ini", runPath(), os.PathSeparator) 13 | tmpFile, err := ini.LoadFile(configPath) 14 | if err != nil { 15 | panic(err) 16 | } 17 | config = &tmpFile 18 | } 19 | -------------------------------------------------------------------------------- /db.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- Table structure for table `idgenerator` 3 | -- 4 | 5 | DROP TABLE IF EXISTS `idgenerator`; 6 | CREATE TABLE `idgenerator` ( 7 | `name` varchar(20) NOT NULL DEFAULT '' COMMENT 'id名', 8 | `id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '当前的最大id', 9 | `desc` varchar(255) NOT NULL DEFAULT '' COMMENT '描述', 10 | `is_del` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', 11 | PRIMARY KEY (`name`) 12 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 13 | LOCK TABLES `idgenerator` WRITE; 14 | INSERT INTO `idgenerator` VALUES ('test',0,'',0); 15 | UNLOCK TABLES; 16 | -------------------------------------------------------------------------------- /go-id-builder.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "runtime" 6 | "strconv" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | runtime.GOMAXPROCS(runtime.NumCPU()) 13 | 14 | // 开始启动监听key变化监听 15 | go watch() 16 | 17 | bind, b := config.Get("", "bind") 18 | if !b { 19 | bind = ":3002" 20 | } 21 | 22 | http.HandleFunc("/", requestID) 23 | http.HandleFunc("/* ", requestID) 24 | 25 | // 开始http处理 26 | err := http.ListenAndServe(bind, nil) 27 | 28 | if err != nil { 29 | panic(err) 30 | } 31 | } 32 | 33 | // 处理用户的网络请求 34 | func requestID(w http.ResponseWriter, r *http.Request) { 35 | err := r.ParseForm() 36 | if err != nil { 37 | w.WriteHeader(http.StatusInternalServerError) 38 | w.Write([]byte(err.Error())) 39 | return 40 | } 41 | name := r.Form.Get("name") 42 | 43 | if name == "" { 44 | w.WriteHeader(http.StatusBadRequest) 45 | w.Write([]byte("name can`t be empty")) 46 | return 47 | } 48 | 49 | num := 1 50 | 51 | if tmpNum := r.Form.Get("num"); tmpNum != "" { 52 | tmpNum2, err := strconv.Atoi(tmpNum) 53 | if err != nil { 54 | w.WriteHeader(http.StatusBadRequest) 55 | w.Write([]byte("num not a int")) 56 | return 57 | } 58 | num = tmpNum2 59 | } 60 | 61 | if tmpMaxRequestNum, b := config.Get("", "max_request_num"); b { 62 | if tmpMaxRequestNumInt, err := strconv.Atoi(tmpMaxRequestNum); err == nil { 63 | if num > tmpMaxRequestNumInt { 64 | w.WriteHeader(http.StatusBadRequest) 65 | w.Write([]byte("num more than max request id num")) 66 | return 67 | } 68 | } else { 69 | errMessage := err.Error() 70 | w.WriteHeader(http.StatusBadRequest) 71 | w.Write([]byte(errMessage)) 72 | return 73 | } 74 | } 75 | 76 | if value, exists := watchList[name]; exists { 77 | w.WriteHeader(http.StatusOK) 78 | if num > 1 { 79 | arr := make([]string, 0) 80 | for i := 0; i < num; i++ { 81 | // 从通信队列中获取数据时添加5秒的超时时间 82 | select { 83 | case rs := <-value: 84 | arr = append(arr, strconv.FormatInt(rs, 10)) 85 | case <-time.After(5 * time.Second): 86 | w.WriteHeader(http.StatusInternalServerError) 87 | w.Write([]byte("server create id timeout")) 88 | } 89 | 90 | } 91 | w.Write([]byte(strings.Join(arr, ","))) 92 | } else { 93 | // 从通信队列中获取数据时添加5秒的超时时间 94 | select { 95 | case rs := <-value: 96 | w.Write([]byte(strconv.FormatInt(rs, 10))) 97 | case <-time.After(5 * time.Second): 98 | w.WriteHeader(http.StatusInternalServerError) 99 | w.Write([]byte("server create id timeout")) 100 | } 101 | 102 | } 103 | return 104 | } 105 | 106 | w.WriteHeader(http.StatusNotFound) 107 | w.Write([]byte("name can`t found")) 108 | } 109 | -------------------------------------------------------------------------------- /model.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | 7 | _ "github.com/go-sql-driver/mysql" 8 | ) 9 | 10 | var mysql *sql.DB 11 | 12 | // 数据表名 13 | var tablename = "idgenerator" 14 | 15 | func init() { 16 | dsn, b := config.Get("mysql", "dsn") 17 | if !b { 18 | panic("没有找到mysql dsn配置") 19 | } 20 | tmpMysql, err := sql.Open("mysql", dsn) 21 | if err != nil { 22 | panic(err) 23 | } 24 | mysql = tmpMysql 25 | } 26 | 27 | // 获取所有的key的名字列表 28 | func idsList() (arr map[string]int64, err error) { 29 | arr = make(map[string]int64, 0) 30 | rows, err := mysql.Query("select `name`,`id` from `" + tablename + "` where `is_del` = 0") 31 | if err != nil { 32 | return 33 | } 34 | defer rows.Close() 35 | 36 | for rows.Next() { 37 | var name string 38 | var id int64 39 | if err = rows.Scan(&name, &id); err == nil { 40 | arr[name] = id 41 | } else { 42 | return 43 | } 44 | } 45 | return 46 | } 47 | 48 | // 向name所指定的id中申请新的ID空间 49 | // 向数据库申请成功后返回新申请到的最大数和申请的数量 50 | func updateID(name string) (num int64, preStep int64, err error) { 51 | num = 0 52 | 53 | preStep = getPreStep() 54 | _, err = mysql.Exec("update `"+tablename+"` set id=last_insert_id(id+?) where name=?", preStep, name) 55 | if err != nil { 56 | return 57 | } 58 | 59 | row := mysql.QueryRow(fmt.Sprintf("select last_insert_id() as id from `%s`", tablename)) 60 | if err != nil { 61 | return 62 | } 63 | 64 | err = row.Scan(&num) 65 | if err != nil { 66 | return 67 | } 68 | return 69 | } 70 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "os/exec" 6 | "path/filepath" 7 | "strconv" 8 | ) 9 | 10 | // 工具函数集 11 | 12 | // 返回配置中的每次最大申请ID值 13 | func getPreStep() int64 { 14 | var preStep int64 = 1000 15 | tmpPreStep, b := config.Get("", "per_step") 16 | if b { 17 | intPreStep, err := strconv.ParseInt(tmpPreStep, 10, 64) 18 | if err != nil { 19 | return preStep 20 | } 21 | preStep = intPreStep 22 | } 23 | return preStep 24 | } 25 | 26 | // 返回当前的运行目录 27 | func runPath() string { 28 | path, err := exec.LookPath(os.Args[0]) 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | return filepath.Dir(path) 34 | } 35 | -------------------------------------------------------------------------------- /watch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | /* 9 | 监听服务器中的key列表的变化情况,当有新的列表被添加进入来的时候,产生新的数据 10 | */ 11 | 12 | // id值的队列 13 | type idChan chan int64 14 | 15 | // 正在被监听的key列表 16 | var watchList = make(map[string]idChan) 17 | 18 | // 监听协程 19 | func watch() { 20 | 21 | for { 22 | makeChan() 23 | for key, value := range watchList { 24 | if int64(len(value)) < getPreStep()/3 { 25 | var max, step int64 = 0, 0 26 | var err error 27 | for { 28 | max, step, err = updateID(key) 29 | if err != nil { 30 | fmt.Println(err) 31 | time.Sleep(5 * time.Millisecond) 32 | } else { 33 | break 34 | } 35 | } 36 | 37 | if max > 0 { 38 | for i := int64(max - step + 1); i <= max; i++ { 39 | value <- i 40 | } 41 | } 42 | } 43 | } 44 | time.Sleep(3 * time.Second) 45 | } 46 | 47 | } 48 | 49 | // 为数据创建新的监听队列 50 | func makeChan() { 51 | var list map[string]int64 52 | var err error 53 | for { 54 | list, err = idsList() 55 | if err != nil { 56 | fmt.Println(err) 57 | time.Sleep(5 * time.Millisecond) 58 | } else { 59 | break 60 | } 61 | } 62 | 63 | // 将不存在于当前监听列表中的数据添加到监听列表中 64 | for key := range list { 65 | if _, exists := watchList[key]; !exists { 66 | watchList[key] = make(idChan, getPreStep()*2) 67 | } 68 | } 69 | 70 | // 检查存在于当前监听列表中,却已经不再存在数据中的ID 71 | for key := range watchList { 72 | if _, exists := list[key]; !exists { 73 | delete(watchList, key) 74 | } 75 | } 76 | } 77 | --------------------------------------------------------------------------------