有两个思路:
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冲突。
Snowflake算法采用41bit毫秒时间戳,加上10bit机器ID(最多支持1024台id服务器),加上12bit序列号,理论上最多支持1024台机器每秒生成4096000个序列号。409万个id每秒,在任何交易平台目前都是够用的。

推特的id构成(从最高位往最低位方向):

1位 ,不用。固定是0
41位 ,毫秒时间戳
5位 ,数据中心ID (用于对数据中心进行编码)
5位 ,WORKERID (用于对工作进程进行编码)
12位 ,序列号。用于同一毫秒产生ID的序列 (自增id)
下面是用golang实现的uuid方法(uuid类型为整形):

  1. package main
  2. import (
  3. "fmt"
  4. idworker "github.com/gitstliu/go-id-worker"
  5. )
  6. func main() {
  7. currWoker := &idworker.IdWorker{}
  8. currWoker.InitIdWorker(1000, 1)
  9. newID, err := currWoker.NextId()
  10. if err == nil {
  11. fmt.Println(newID)
  12. }
  13. }

下载库
go get github.com/gitstliu/go-id-worker
下面是在vscode中的调试结果
API server listening at: 127.0.0.1:4442
4917572028174794752
Process exiting with code: 0

用mysql自带的自增id生成全局唯一id

  1. package main
  2. import (
  3. "database/sql"
  4. "errors"
  5. "log"
  6. "time"
  7. "fmt"
  8. _ "github.com/go-sql-driver/mysql"
  9. )
  10. type logger interface {
  11. Error(error)
  12. }
  13. // Logger Log接口,如果设置了Logger,就使用Logger打印日志,如果没有设置,就使用内置库log打印日志
  14. var Logger logger
  15. // ErrTimeOut 获取uid超时错误
  16. var ErrTimeOut = errors.New("get uid timeout")
  17. type Uid struct {
  18. db *sql.DB // 数据库连接
  19. businessId string // 业务id
  20. ch chan int64 // id缓冲池
  21. min, max int64 // id段最小值,最大值
  22. }
  23. // NewUid 创建一个Uid;len:缓冲池大小()
  24. // db:数据库连接
  25. // businessId:业务id
  26. // len:缓冲池大小(长度可控制缓存中剩下多少id时,去DB中加载)
  27. func NewUid(db *sql.DB, businessId string, len int) (*Uid, error) {
  28. lid := Uid{
  29. db: db,
  30. businessId: businessId,
  31. ch: make(chan int64, len),
  32. }
  33. go lid.productId()
  34. return &lid, nil
  35. }
  36. // Get 获取自增id,当发生超时,返回错误,避免大量请求阻塞,服务器崩溃
  37. func (u *Uid) Get() (int64, error) {
  38. select {
  39. case <-time.After(1 * time.Second):
  40. return 0, ErrTimeOut
  41. case uid := <-u.ch:
  42. return uid, nil
  43. }
  44. }
  45. // productId 生产id,当ch达到最大容量时,这个方法会阻塞,直到ch中的id被消费
  46. func (u *Uid) productId() {
  47. u.reLoad()
  48. for {
  49. if u.min >= u.max {
  50. u.reLoad()
  51. }
  52. u.min++
  53. u.ch <- u.min
  54. }
  55. }
  56. // reLoad 在数据库获取id段,如果失败,会每隔一秒尝试一次
  57. func (u *Uid) reLoad() error {
  58. var err error
  59. for {
  60. err = u.getFromDB()
  61. if err == nil {
  62. return nil
  63. }
  64. // 数据库发生异常,等待一秒之后再次进行尝试
  65. if Logger != nil {
  66. Logger.Error(err)
  67. } else {
  68. log.Println(err)
  69. }
  70. time.Sleep(time.Second)
  71. }
  72. }
  73. // getFromDB 从数据库获取id段
  74. func (u *Uid) getFromDB() error {
  75. var (
  76. maxId int64
  77. step int64
  78. )
  79. row := u.db.QueryRow("SELECT max_id, step FROM uid;")
  80. //row = u.db.QueryRow("SELECT max_id, step FROM uid WHERE business_id = ? FOR UPDATE",1)
  81. if err :=row.Scan(&maxId, &step); err != nil{
  82. fmt.Printf("scan failed, err:%v",err)
  83. return err
  84. }
  85. _, err := u.db.Exec("UPDATE uid SET max_id = ?", maxId+step)
  86. if err != nil {
  87. return err
  88. }
  89. u.min = maxId
  90. u.max = maxId + step
  91. return nil
  92. }
  93. const (
  94. DeviceIdBusinessId = "device_id" // 设备id
  95. )
  96. var(
  97. DeviceIdUid *Uid
  98. )
  99. func InitUID(db *sql.DB) {
  100. var err error
  101. DeviceIdUid, err = NewUid(db, DeviceIdBusinessId, 5)
  102. if err != nil {
  103. panic(err)
  104. }
  105. }
  106. func check(err error) {
  107. if err != nil {
  108. panic(err)
  109. }
  110. }
  111. const (
  112. USER_NAME = "root"
  113. PASS_WORD = "123456"
  114. HOST = "localhost"
  115. PORT = "3306"
  116. DATABASE = "test"
  117. CHARSET = "utf8"
  118. )
  119. //
  120. func main() {
  121. // http init
  122. // http api goroutine
  123. url := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s", USER_NAME, PASS_WORD, HOST, PORT, DATABASE, CHARSET)
  124. db, err := sql.Open("mysql", url)
  125. if err != nil {
  126. panic(err)
  127. }
  128. InitUID(db)
  129. for i:=0; i<20; i++ {
  130. id ,err := DeviceIdUid.Get()
  131. if err == nil {
  132. fmt.Println("id=", id)
  133. } else {
  134. fmt.Println(err)
  135. }
  136. }
  137. }

测试结果:

  1. [root@bogon uid]# go build main.go
  2. [root@bogon uid]# ./main
  3. id= 1706
  4. id= 1707
  5. id= 1708
  6. id= 1709
  7. id= 1710
  8. id= 1711
  9. id= 1712
  10. id= 1713
  11. id= 1714
  12. id= 1715
  13. id= 1716
  14. id= 1717
  15. id= 1718
  16. id= 1719
  17. id= 1720
  18. id= 1721
  19. id= 1722
  20. id= 1723
  21. id= 1724
  22. id= 1725
  23. [root@bogon uid]#


以下为uid服务器的架构图:
golang实现全局唯一id snowflake算法 - 图1

原文链接:https://blog.csdn.net/jacky128256/java/article/details/104687772