GO 从0开发一个属于自己的网盘-第1天 用户模块-利用雪花算法实现userID的随机生成

578 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

需求背景

在用户注册的时候,需要给用户分配一个 userID ,该 userID 有以下要求:

  1. 全局唯一,不同用户的userID一定不同。
  2. userID 最好的递增,数据库在插入记录时可以保证性能。

一、数据库自增ID

什么是自增ID

自增ID是在设计数据库表的时候将主键设置为自增,当数据库每插入一条数据时,主键的值就自动根据前一天记录的值+1进行填充。

自增ID的好处

自增ID是由数据库层面控制能够保证原子性,插入记录效率高,业务方无需关注具体的实现细节,只需要把相对应的记录往数据库中插入,数据库会自动填充主键的自增ID,业务方可以通过该记录反查得到自增ID的值。

自增ID的坏处

自增ID在分布式数据库时,不能用于数据合并,主键是一个唯一值,当合并时会冲突,解决办法很简单,比如现在有两个数据库,只需要让数据库1的主键是单数递增即1,3,5,7,9...;数据库2偶数递增即2,4,6,8...,在后续合并时主键就不会冲突了。

为什么自增ID不适用于userID:容易泄露系统用户量,而且自增ID也不是一个随机值

二、UUID

什么是UUID

UUID 是通用唯一识别码(Universally Unique Identifier)的缩写,是一种软件建构的标准。UUID 的目的是让所有的分布式系统中所生成的UUID都不一样,这样UUID就具有了全局唯一性。UUID是一个128比特的数值,这个数值可以通过一定的算法计算出来,标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)

UUID的好处

实现简单,有现成的包可以直接调API生成,并且保证全局唯一。

UUID的缺点

生成无序,在数据库插入记录时性能差,

三、雪花算法

什么是雪花算法

雪花算法是Twitter公司发明的一种算法,主要的目的是解决在系统的分布式环境中,ID无重复生成的问题,主要是由64bit的long型生成的全局ID,引入了时间戳和ID保持自增的属性。

image.png 主要分为 5 个部分:

是 1 个 bit:默认是0, 0代表正数,1代表负数 是 41 个 bit:表示的是时间戳。 是 10 个 bit:表示的是5位数据中心id和5位机器id,0000000000, 是 12 个 bit:表示的序号,就是某个机房某台机器上这一毫秒内同时生成的 id 的序号,0000 0000 0000。

雪花算法的优点

雪花算法生成是全局唯一的,分布式系统内不会产生ID碰撞,生成效率高,经测试snowflake每秒能生成26万个自增可排序的ID。并且雪花ID是递增的,在数据库插入时

雪花算法的缺点

雪花算法在时间回溯时可能会生成重复的ID,所以在分布式环境中,需要注意数据中心ID和机器ID不能重复。

四、Golang雪花算法的实现

import (
   "sync"
   "sync/atomic"
   "time"
)

type SnowFlake struct {
   fixedTimestamp int64 // 填充时间戳
   lastTimestamp  int64 // 上次生成的时间戳
   sequenceNumber int64 // 序列号
   machineID      int64 // 数据中心+机器号组成 共十位
   mutex          sync.Mutex
}

// NewSnowFlake machineID
func NewSnowFlake(machineID int64) *SnowFlake {
   return &SnowFlake{
      fixedTimestamp: int64(1653374552582),
      lastTimestamp:  int64(-1),
      sequenceNumber: int64(0),
      machineID:      machineID,
   }
}

func (s *SnowFlake) GenSnowID() int64 {
   s.mutex.Lock()
   defer s.mutex.Unlock()

   currentTimestamp := time.Now().UnixMilli()
   if currentTimestamp == atomic.LoadInt64(&s.lastTimestamp) {
      atomic.AddInt64(&s.sequenceNumber, 1)
      if atomic.LoadInt64(&s.sequenceNumber) > 4096 {
         for currentTimestamp <= atomic.LoadInt64(&s.lastTimestamp) {
            currentTimestamp = time.Now().UnixMilli()
         }
         atomic.StoreInt64(&s.sequenceNumber, 0)
      }
   } else {
      atomic.StoreInt64(&s.sequenceNumber, 0)
   }
   atomic.StoreInt64(&s.lastTimestamp, currentTimestamp)

   return (currentTimestamp-s.fixedTimestamp)<<22 | int64(s.machineID<<12) | s.sequenceNumber
}

五、测试效果

import (
   "fmt"
   "testing"
)

func TestGenID(t *testing.T) {
   m := make(map[int64]int32, 1000)
   flake := NewSnowFlake(1024416752)
   for i := 0; i < 1000; i++ {
      //time.Sleep(1 * time.Second)
      id := flake.GenSnowID()
      if _, ok := m[id]; ok {
         fmt.Println("重复了id:", id)
      } else {
         fmt.Println("初次id:", id)
      }
      m[id] = 1
   }
   fmt.Println("--------------------------")
   fmt.Println(len(m))
}

结果:

image.png