顺晟科技
2021-08-28 09:40:59
162
应用程序通常需要全局ID作为数据库主键。在一个节点上很容易全局。在多个节点上呢?
我有两个想法。
1使用散列函数,如sha256,由时间戳、MAC地址、CPU负载、随机数等组成。id足够长,引入了多种不确定性,冲突概率极低,可以被认为是全球性。例如,uuid是这样的。但是,uuid是字符串格式,DB至少占用两倍的空间,DB的索引需要存储和比较,因此存储空间和查询时间都低于整形。这一点在DB的数据条带数越多越明显。
使用2分割方法。每个节点确保其生成的所有id在内部是的。每个节点都有指定的非重复节点编号,并插入到id中,使所有节点的id都是全局的。
使用3 DB中包含的默认密钥性来确保id是的。但是,db的自我增长id必须等到事务提交后,ID才会被认为有效。一些双向参考数据插入后需要重新更新,很麻烦。
第二种方法是Twitter等Snowflake算法,它为每个系统分配的ID,然后通过时间戳ID自身的增加实现全局ID。该方法的优点是,ID生成算法完全没有状态系统,没有网络调用,高效可靠。缺点是,如果id重复,可能会发生ID冲突。
Snowflake算法使用41bit毫秒时间戳、10bit系统id(最多支持1024台ID服务器)和12bit序列号,理论上最多支持1024台系统,每秒生成4096000个序列号。409万个id每秒,在任何交易平台上现在都足够了。
Twitter的id配置(从更高位置到更低方向):
名,没必要。固定值为0
41位,毫秒时间戳
5位,数据中心ID(用于数据中心编码)
5位,工作器id(用于工作器进程编码)
12位,序列号。用于在同一毫秒内生成id的序列(自我递增ID)
以下是用golang实现的uuid方法(uuid类型为整形)。
Package main
Import(
“Fmt”
Id工作器“github.com/gitstliu/go-id-工作器”
)。
Func main() {
curr woker :=id worker . id worker { }
CurrWoker。InitIdWorker(1000,1)
Newid,err 3360=curr woker . nextid()
If err==nil {
Fmt。Println(newID)
}
}
下载库
gogetgithub.com/gitstliu/go-id-worker
以下是vscode的调试结果
Api服务器侦听AT : 127 . 0 . 0 . 133604442
4917572028174794752
Process exiting with code: 0
使用Mysql中包含的自身附加id生成全局id
Package main
Import(
数据库/SQL
“错误”
“日志”
“时间”
“Fmt”
_ ' github.com/go-SQL-driver/MySQL '
)。
Type logger介面{
错误(错误)
}
//Logger Log介面,若已设定Logger,则使用Logger列印记录,若未设定,则使用内建程式库Log列印记录
Var Logger logger
//ErrTimeOut uid获取超时错误
var err time out=errors . new(' get uid time out ')
类型uid结构{
Db *sql。数据库//数据库连接
业务id字符串//业务id
Ch chan int64 //id缓冲池
Min,max int64 //id段最小值,更大值
}
//创建NewUid Uid。Len:缓冲池大小()
//db:数据库连接
//业务id:业务id
//len:缓冲池大小(长度在控制缓存中保留的id数时加载到数据库中)
Funcnew uid (db * sql.db,业务id字符串,lenint) (* uid,错误){
Lid :=Uid{
Db: db,
业务id :业务id,
Ch: make(chan int64,len),
}
Go lid.productId()
Return lid,nil
}
//Get获取自身id,超时时返回错误,防止阻止大量请求,服务器崩溃
Func (u *Uid) Get() (int64,error) {
Select {
case-time . after(1 * time . second)3360
Return 0,ErrTimeOut
case uid :=-u . ch 3360
Return uid,nil
}
}
//productId生产Id,当ch达到更大容量时,将被阻止,直到ch的id被消耗
Func (u * uid)产品id () {
U.reLoad()
For {
If u.min=u.max {
U.reLoad()
}
U.min
U.ch-u.min
}
}
//reLoad从数据库中获取id段,如果失败,则每秒尝试一次
Func (u * uid) reload()错误{
Var err error
For {
Err=u.getFromDB()
If err==nil {
Return nil
}
//数据库出现异常。请等一秒钟后再试一次
If Logger!=nil {
Logger。错误(err)
} else {
Log。Println(err)
}
Time .Sleep(time)。Second)
}
}
//从getFromDB数据库获取id段
Func (u * uid) getfromdb()错误{
Var(
MaxId int64
Step int64
)。
row 3360=u . db . queryrow(' select max _ id,step from uid'))。
//row=u . db . query row(' select max _ id,step from uid where business _ id=?' FOR UPDATE ',1)
If err :=row。Scan(maxId,STEP);Err!=nil{
FMT . PRINTF(‘Scan Failed,err 3360% v’,Err)。
Return err
}
_,err 3360=u . db . exec(' update uid set max _ id=?'),maxId step)
If err!=nil {
Return err
}
U.min=maxId
U.max=maxId step
Return nil
}
Const(
deviceidbusinessid=' device _ id '/设备id
)。
Var(
设备id uid * uid
)。
Func InitUID(db *sql .DB) {
Var err error
Device id uid,err=new uid (db,deviceidbusinessid,5)
If err!=nil {
Panic(err)
}
}
Func check(err error) {
If err!=nil {
Panic(err)
}
}
Const(
User _ name=“根”
PASS_WORD='123456 '
主机=“localhost”
PORT='3306 '
Database=“测试”
CHARSET='utf8 '
)。
//
Func main() {
//http init
//http API goroutine
URL :=fmt . sprintf(' % s 3360% s @ TCP(% s 3360% s)/% s?Charset=% s ',user _ name,pass _ word,host,port,database,Charset)
Db,err 3360=SQL . open(' MySQL ',URL)
If err!=nil {
Panic(err)
}
InitUID(db)
FOR I :=0;I20I {
Id,err:=设备id.get()
If err==nil {
Fmt。Println('id=',id)
} else {
Fmt。Println(err)
}
}
}
测试结果:
[root@bogon uid]# go build main.go
[root@bogon uid]#。/main
Id=1706
Id=1707
Id=1708
Id=1709
Id=1710
Id=1711
Id=1712
Id=1713
Id=1714
Id=1715
Id=1716
Id=1717
Id=1718
Id=1719
Id=1720
Id=1721
Id=1722
Id=1723
Id=1724
Id=1725
[root@bogon uid]#
增强功能:以上是单机版uid生成器。独立流量可能会有瓶颈。因此,这是实际的商业发行期。如果不符合Uid,则设置单独的http服务器go程序,其他业务节点从该节点获取uid段(step需要从5调整到1w),其他节点一次请求就返回1w uid。无论1w uid是否用完,都不再分配给其他节点
09
2022-04
29
2021-08
29
2021-08
28
2021-08
28
2021-08
28
2021-08