[Golang] 分布式id生成算法SnowFlake

SnowFlake算法是一种以划分命名空间来生成ID的一种算法,生成id的结果是一个64bit大小的整数。

优点:

  • 毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
  • 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。
  • 可以根据自身业务特性分配bit位,非常灵活。

缺点:

  • 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。

实现代码(不同命名空间所占用的位数可根据业务需要自行调整):

import (
    "errors"
    "fmt"
    "sync"
    "time"
)

const (
    twepoch        = int64(1417937700000) // 默认起始的时间戳 1449473700000 。计算时,减去这个值
    DistrictIdBits = uint(5)              //区域 所占用位置
    NodeIdBits     = uint(9)              //节点 所占位置
    sequenceBits   = uint(10)             //自增ID 所占用位置

    /*
     * SnowFlake
     * 1 符号位  |  39 时间戳                                     | 5 区域  |  9 节点       | 10 (毫秒内)自增ID
     * 0        |  0000000 00000000 00000000 00000000 00000000  | 00000  | 000000 000   |  000000 0000
     *
     */
    maxNodeId     = -1 ^ (-1 << NodeIdBits)     //节点 ID 最大范围
    maxDistrictId = -1 ^ (-1 << DistrictIdBits) //最大区域范围

    nodeIdShift        = sequenceBits //左移次数
    DistrictIdShift    = sequenceBits + NodeIdBits
    timestampLeftShift = sequenceBits + NodeIdBits + DistrictIdBits
    sequenceMask       = -1 ^ (-1 << sequenceBits)
    maxNextIdsNum      = 100 //单次获取ID的最大数量
)

type IdWorker struct {
    sequence      int64 //序号
    lastTimestamp int64 //最后时间戳
    nodeId        int64 //节点ID
    twepoch       int64
    districtId    int64
    mutex         sync.Mutex
}

// NewIdWorker new a snowflake id gg object.
func NewIdWorker(NodeId int64) (*IdWorker, error) {
    var districtId int64
    districtId = 1 //暂时默认给1 ,方便以后扩展
    idWorker := &IdWorker{}
    if NodeId > maxNodeId || NodeId < 0 {
        fmt.Sprintf("NodeId Id can't be greater than %d or less than 0", maxNodeId)
        return nil, errors.New(fmt.Sprintf("NodeId Id: %d error", NodeId))
    }
    if districtId > maxDistrictId || districtId < 0 {
        fmt.Sprintf("District Id can't be greater than %d or less than 0", maxDistrictId)
        return nil, errors.New(fmt.Sprintf("District Id: %d error", districtId))
    }
    idWorker.nodeId = NodeId
    idWorker.districtId = districtId
    idWorker.lastTimestamp = -1
    idWorker.sequence = 0
    idWorker.twepoch = twepoch
    idWorker.mutex = sync.Mutex{}
    fmt.Sprintf("worker starting. timestamp left shift %d, District id bits %d, worker id bits %d, sequence bits %d, workerid %d", timestampLeftShift, DistrictIdBits, NodeIdBits, sequenceBits, NodeId)
    return idWorker, nil
}

// timeGen generate a unix millisecond.
func timeGen() int64 {
    return time.Now().UnixNano() / int64(time.Millisecond)
}

// tilNextMillis spin wait till next millisecond.
func tilNextMillis(lastTimestamp int64) int64 {
    timestamp := timeGen()
    for timestamp <= lastTimestamp {
        timestamp = timeGen()
    }
    return timestamp
}

// NextId get a snowflake id.
func (id *IdWorker) NextId() (int64, error) {
    id.mutex.Lock()
    defer id.mutex.Unlock()
    return id.nextid()
}

// NextIds get snowflake ids.
func (id *IdWorker) NextIds(num int) ([]int64, error) {
    if num > maxNextIdsNum || num < 0 {
        fmt.Sprintf("NextIds num can't be greater than %d or less than 0", maxNextIdsNum)
        return nil, errors.New(fmt.Sprintf("NextIds num: %d error", num))
    }
    ids := make([]int64, num)
    id.mutex.Lock()
    defer id.mutex.Unlock()
    for i := 0; i < num; i++ {
        ids[i], _ = id.nextid()
    }
    return ids, nil
}

func (id *IdWorker) nextid() (int64, error) {
    timestamp := timeGen()
    if timestamp < id.lastTimestamp {
        fmt.Sprintf("clock is moving backwards.  Rejecting requests until %d.", id.lastTimestamp)
        return 0, errors.New(fmt.Sprintf("Clock moved backwards.  Refusing to generate id for %d milliseconds", id.lastTimestamp-timestamp))
    }
    if id.lastTimestamp == timestamp {
        id.sequence = (id.sequence + 1) & sequenceMask
        if id.sequence == 0 {
            timestamp = tilNextMillis(id.lastTimestamp)
        }
    } else {
        id.sequence = 0
    }
    id.lastTimestamp = timestamp
    return ((timestamp - id.twepoch) << timestampLeftShift) | (id.districtId << DistrictIdShift) | (id.nodeId << nodeIdShift) | id.sequence, nil
}

Golang可用以下代码获取机器ip,然后传给上面的NewIdWorker()方法。

func getNodeId() int64 {
    addrs, err := net.InterfaceAddrs()

    if err != nil {
        fmt.Printf("Get IP failed:[%v]", err)
    }

    for _, address := range addrs {
        // 检查ip地址判断是否回环地址
        if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
            if ipnet.IP.To4() != nil {
                nodeId := ipToInt64(ipnet.IP) % maxNodeId
                fmt.Printf("Ip address:[%v], nodeId:[%v]", ipnet.IP, nodeId)
                return nodeId
            }
        }
    }
    return rand.Int63() % maxNodeId
}

func ipToInt64(ip net.IP) int64 {
    bits := strings.Split(ip.String(), ".")
    b0, _ := strconv.Atoi(bits[0])
    b1, _ := strconv.Atoi(bits[1])
    b2, _ := strconv.Atoi(bits[2])
    b3, _ := strconv.Atoi(bits[3])

    var sum int64
    sum += int64(b0) << 24
    sum += int64(b1) << 16
    sum += int64(b2) << 8
    sum += int64(b3)
    return sum
}

猜你喜欢

转载自blog.csdn.net/u011331383/article/details/80753007