-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit ddbed26
Showing
9 changed files
with
369 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# go-id-builder | ||
这是一个使用golang开发的ID生成器,它可以提供通过高效的方式产生连续唯一的ID值。在分库分表时可以提供非常有用的帮助。 | ||
# 为什么要做id生成器 | ||
|
||
常常在数据库进行分库分表的需求处理时,需要给表产生一个自增的主键id。单表的时候我们都是通过给表添加一个自增字段来实现的。当需要分表时就会发现这样的方式会出现每个表都有一套自己的自增id。特别是我们需要通过这个ID来实现分表算法时(一般都是id%表数量),那么如何在多表中产生一个连续自增的ID成为一个问题。 | ||
|
||
# 如何实现的 | ||
|
||
go-id-builder使用mysql来做为最大id数的持久化存储。程序在每次启动的时候都会加载数据表中当前的所记录的id类型,将会自动申请1000个(配置文件中可修改)新的id号,加载到一个缓冲通道中,当用户向生成器的api接口发起请求时,从对应的缓冲通道中将数据取出返回给客户端。 | ||
|
||
# 安装方式 | ||
|
||
你可以直接通过下面的命令来直接安装 | ||
|
||
`go get github.com/freshcn/go-id-builder` | ||
|
||
也可以通过项目的`releases`中直接下载二进制包来安装运行 | ||
|
||
# 配置数据库 | ||
|
||
先将`db.sql`中的mysql语句安装到数据库中,并在`app.ini`中配置相应的mysql的数据库连接信息。数据库脚本中默认为表设置的是innodb引擎,你可以按需要修改为你正在使用的其他引擎。 | ||
|
||
数据库中已经默认提供了一个test id名,你可以在运行了程序后通过 | ||
|
||
`http://localhost:3002?name=test` | ||
|
||
来获取到test这个id名所产生的第一个id值。 | ||
|
||
你可以向数据表中添加新的id名来支持新的id值的产生 | ||
|
||
# api说明 | ||
|
||
在程序运行成功后默认会占用系统的3002端口,你可以在配置文件中修改所占用的端口号。 | ||
|
||
你可以通过get或post请求方式来请求接口。允许的参数如下表: | ||
|
||
- name - id值名 | ||
- num - 需要请求的id数量,默认最大为100个,可在配置文件中修改 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# 服务所监听的端口号 | ||
bind=:3002 | ||
# 每次向数据申请的id数量 | ||
per_step=1000 | ||
|
||
# 客户端每次最大申请的ID数量 | ||
max_request_num = 100 | ||
|
||
# mysql相关的配置 | ||
[mysql] | ||
dsn=name:passwd@tcp(yourname:3306)/id_builder?charset=utf8 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"github.com/vaughan0/go-ini" | ||
) | ||
|
||
var config *ini.File | ||
|
||
func init() { | ||
configPath := fmt.Sprintf("%s%capp.ini", runPath(), os.PathSeparator) | ||
tmpFile, err := ini.LoadFile(configPath) | ||
if err != nil { | ||
panic(err) | ||
} | ||
config = &tmpFile | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
-- | ||
-- Table structure for table `idgenerator` | ||
-- | ||
|
||
DROP TABLE IF EXISTS `idgenerator`; | ||
CREATE TABLE `idgenerator` ( | ||
`name` varchar(20) NOT NULL DEFAULT '' COMMENT 'id名', | ||
`id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '当前的最大id', | ||
`desc` varchar(255) NOT NULL DEFAULT '' COMMENT '描述', | ||
`is_del` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', | ||
PRIMARY KEY (`name`) | ||
) ENGINE=InnoDB DEFAULT CHARSET=utf8; | ||
LOCK TABLES `idgenerator` WRITE; | ||
INSERT INTO `idgenerator` VALUES ('test',0,'',0); | ||
UNLOCK TABLES; |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package main | ||
|
||
import ( | ||
"net/http" | ||
"runtime" | ||
"strconv" | ||
"strings" | ||
"time" | ||
) | ||
|
||
func main() { | ||
runtime.GOMAXPROCS(runtime.NumCPU()) | ||
|
||
// 开始启动监听key变化监听 | ||
go watch() | ||
|
||
bind, b := config.Get("", "bind") | ||
if !b { | ||
bind = ":3002" | ||
} | ||
|
||
http.HandleFunc("/", requestId) | ||
http.HandleFunc("/* ", requestId) | ||
|
||
// 开始http处理 | ||
err := http.ListenAndServe(bind, nil) | ||
|
||
if err != nil { | ||
panic(err) | ||
} | ||
} | ||
|
||
// 处理用户的网络请求 | ||
func requestId(w http.ResponseWriter, r *http.Request) { | ||
err := r.ParseForm() | ||
if err != nil { | ||
w.WriteHeader(http.StatusInternalServerError) | ||
w.Write([]byte(err.Error())) | ||
return | ||
} | ||
name := r.Form.Get("name") | ||
|
||
if name == "" { | ||
w.WriteHeader(http.StatusBadRequest) | ||
w.Write([]byte("name can`t be empty")) | ||
return | ||
} | ||
|
||
num := 1 | ||
|
||
if tmpNum := r.Form.Get("num"); tmpNum != "" { | ||
tmpNum2, err := strconv.Atoi(tmpNum) | ||
if err != nil { | ||
w.WriteHeader(http.StatusBadRequest) | ||
w.Write([]byte("num not a int")) | ||
return | ||
} | ||
num = tmpNum2 | ||
} | ||
|
||
if tmpMaxRequestNum, b := config.Get("", "max_request_num"); b { | ||
if tmpMaxRequestNumInt, err := strconv.Atoi(tmpMaxRequestNum); err == nil { | ||
if num > tmpMaxRequestNumInt { | ||
w.WriteHeader(http.StatusBadRequest) | ||
w.Write([]byte("num more than max request id num")) | ||
return | ||
} | ||
} else { | ||
errMessage := err.Error() | ||
w.WriteHeader(http.StatusBadRequest) | ||
w.Write([]byte(errMessage)) | ||
return | ||
} | ||
} | ||
|
||
if value, exists := watchList[name]; exists { | ||
w.WriteHeader(http.StatusOK) | ||
if num > 1 { | ||
arr := make([]string, 0) | ||
for i := 0; i < num; i++ { | ||
// 从通信队列中获取数据时添加5秒的超时时间 | ||
select { | ||
case rs := <-value: | ||
arr = append(arr, strconv.FormatInt(rs, 10)) | ||
case <-time.After(5 * time.Second): | ||
w.WriteHeader(http.StatusInternalServerError) | ||
w.Write([]byte("server create id timeout")) | ||
} | ||
|
||
} | ||
w.Write([]byte(strings.Join(arr, ","))) | ||
} else { | ||
// 从通信队列中获取数据时添加5秒的超时时间 | ||
select { | ||
case rs := <-value: | ||
w.Write([]byte(strconv.FormatInt(rs, 10))) | ||
case <-time.After(5 * time.Second): | ||
w.WriteHeader(http.StatusInternalServerError) | ||
w.Write([]byte("server create id timeout")) | ||
} | ||
|
||
} | ||
return | ||
} | ||
|
||
w.WriteHeader(http.StatusNotFound) | ||
w.Write([]byte("name can`t found")) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package main | ||
|
||
import ( | ||
"database/sql" | ||
"fmt" | ||
|
||
_ "github.com/go-sql-driver/mysql" | ||
) | ||
|
||
var mysql *sql.DB | ||
|
||
// 数据表名 | ||
var tablename string = "idgenerator" | ||
|
||
func init() { | ||
dsn, b := config.Get("mysql", "dsn") | ||
if !b { | ||
panic("没有找到mysql dsn配置") | ||
} | ||
tmpMysql, err := sql.Open("mysql", dsn) | ||
if err != nil { | ||
panic(err) | ||
} | ||
mysql = tmpMysql | ||
} | ||
|
||
// 获取所有的key的名字列表 | ||
func idsList() (arr map[string]int64, err error) { | ||
arr = make(map[string]int64, 0) | ||
rows, err := mysql.Query("select `name`,`id` from `" + tablename + "` where `is_del` = 0") | ||
if err != nil { | ||
return | ||
} | ||
defer rows.Close() | ||
|
||
for rows.Next() { | ||
var name string | ||
var id int64 | ||
if err = rows.Scan(&name, &id); err == nil { | ||
arr[name] = id | ||
} else { | ||
return | ||
} | ||
} | ||
return | ||
} | ||
|
||
// 向name所指定的id中申请新的ID空间 | ||
// 向数据库申请成功后返回新申请到的最大数和申请的数量 | ||
func updateId(name string) (num int64, preStep int64, err error) { | ||
num = 0 | ||
|
||
preStep = getPreStep() | ||
_, err = mysql.Exec("update `"+tablename+"` set id=last_insert_id(id+?) where name=?", preStep, name) | ||
if err != nil { | ||
return | ||
} | ||
|
||
row := mysql.QueryRow(fmt.Sprintf("select last_insert_id() as id from `%s`", tablename)) | ||
if err != nil { | ||
return | ||
} | ||
|
||
err = row.Scan(&num) | ||
if err != nil { | ||
return | ||
} | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package main | ||
|
||
import ( | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"strconv" | ||
) | ||
|
||
// 工具函数集 | ||
|
||
// 返回配置中的每次最大申请ID值 | ||
func getPreStep() int64 { | ||
var preStep int64 = 1000 | ||
tmpPreStep, b := config.Get("", "per_step") | ||
if b { | ||
intPreStep, err := strconv.ParseInt(tmpPreStep, 10, 64) | ||
if err != nil { | ||
return preStep | ||
} | ||
preStep = intPreStep | ||
} | ||
return preStep | ||
} | ||
|
||
// 返回当前的运行目录 | ||
func runPath() string { | ||
path, err := exec.LookPath(os.Args[0]) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
return filepath.Dir(path) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"time" | ||
) | ||
|
||
/* | ||
监听服务器中的key列表的变化情况,当有新的列表被添加进入来的时候,产生新的数据 | ||
*/ | ||
|
||
// id值的队列 | ||
type idChan chan int64 | ||
|
||
// 正在被监听的key列表 | ||
var watchList = make(map[string]idChan) | ||
|
||
// 监听协程 | ||
func watch() { | ||
|
||
for { | ||
makeChan() | ||
for key, value := range watchList { | ||
if int64(len(value)) < getPreStep()/3 { | ||
var max, step int64 = 0, 0 | ||
var err error | ||
for { | ||
max, step, err = updateId(key) | ||
if err != nil { | ||
fmt.Println(err) | ||
time.Sleep(5 * time.Millisecond) | ||
} else { | ||
break | ||
} | ||
} | ||
|
||
if max > 0 { | ||
for i := int64(max - step + 1); i <= max; i++ { | ||
value <- i | ||
} | ||
} | ||
} | ||
} | ||
time.Sleep(3 * time.Second) | ||
} | ||
|
||
} | ||
|
||
// 为数据创建新的监听队列 | ||
func makeChan() { | ||
var list map[string]int64 | ||
var err error | ||
for { | ||
list, err = idsList() | ||
if err != nil { | ||
fmt.Println(err) | ||
time.Sleep(5 * time.Millisecond) | ||
} else { | ||
break | ||
} | ||
} | ||
|
||
// 将不存在于当前监听列表中的数据添加到监听列表中 | ||
for key, _ := range list { | ||
if _, exists := watchList[key]; !exists { | ||
watchList[key] = make(idChan, getPreStep()*2) | ||
} | ||
} | ||
|
||
// 检查存在于当前监听列表中,却已经不再存在数据中的ID | ||
for key, _ := range watchList { | ||
if _, exists := list[key]; !exists { | ||
delete(watchList, key) | ||
} | ||
} | ||
} |