美文网首页
golang雪花算法SnowFlake解决时间回拨问题

golang雪花算法SnowFlake解决时间回拨问题

作者: 成功的失败者 | 来源:发表于2020-06-16 19:23 被阅读0次
    package main
    
    import (
        "errors"
        "fmt"
        "sync"
        "time"
    )
    
    const (
        workerBits uint8 = 10 // 每台机器(节点)的id位数 10位最大可以有2^10=1024个节点(0-1023)
        numberBits uint8 = 22 // 表示每个集群下的每个节点,1秒内可生成的id序号的二进制位数 即每秒最多可生成 2^22=4194303个唯一id(0-4194303)
        // 这里求最大值使用了位运算
        workerMax   int64 = ^(-1 << workerBits)     // 节点id的最大值,用于防止溢出
        numberMax   int64 = ^(-1 << numberBits)     // 每个节点,1秒内可生成的id序号最大值
        timeShift   uint8 = workerBits + numberBits // 时间戳向左的偏移量
        workerShift uint8 = numberBits              // 节点id向左的偏移量
        // 31位字节作为时间戳数值的话 大约68年就会用完
        // 假如你2010年1月1日开始开发系统 如果不减去2010年1月1日的时间戳 那么白白浪费40年的时间戳啊!
        // 这个一旦定义且开始生成id后千万不要改了 不然可能会生成相同的id
        epoch int64 = 1589904000 //这个是我在写epoch这个变量时的时间戳(秒)
    )
    
    // 定义一个woker工作节点所需要的基本参数
    type Worker struct {
        mu        sync.Mutex     // 添加互斥锁 确保并发安全
        timestamp int64          // 记录时间戳
        workerId  int64          // 该节点的id
        number    int64          // 当前毫秒已经生成的id序列号(从0开始累加) 1秒内最多生成4194304个id
        idMap     map[int64]bool //生成的所有不重复的id map,用于测试校验是否产生重复id,正式环境不需要
        flag      bool           //用于测试时间回拨字段,正式环境不需要
    }
    
    // @desc 初始化一个节点
    // @auth liuguoqiang 2020-06-16
    // @param
    // @return
    func NewWorker(workerId int64) (*Worker, error) {
        // 要先检测workerId是否在上面定义的范围内
        if workerId < 0 || workerId > workerMax {
            return nil, errors.New("workId is invalidate")
        }
        return &Worker{
            timestamp: 0,
            workerId:  workerId,
            number:    0,
            idMap:     make(map[int64]bool),
            flag:      false,
        }, nil
    }
    
    // @desc 获取id
    // @auth liuguoqiang 2020-06-16
    // @param
    // @return
    func (w *Worker) GetId() int64 {
        //解决并发安全
        w.mu.Lock()
        defer w.mu.Unlock()
        //获取生成时的时间戳
        now := w.Now()
        if now == w.timestamp {
            w.number++
            //这里要判断,当前工作节点是否在1秒内已经生成numberMax个id
            if w.number > numberMax {
                //如果当前工作节点在1秒内生成的id已经超过上限 需要等待1秒再继续生成
                w.number = 0
                for now <= w.timestamp {
                    now = w.Now()
                }
                w.timestamp = now
            }
        } else if now > w.timestamp {
            //如果当前时间与工作节点上一次生成id的时间不一致 则需要重置工作节点生成id的序号
            w.number = 0
            //将机器上一次生成id的时间更新为当前时间
            w.timestamp = now
        } else {
            for now < w.timestamp {
                now = w.Now()
            }
            w.number++
            //这里要判断,当前工作节点是否在1秒内已经生成numberMax个id
            if w.number > numberMax {
                //如果当前工作节点在1秒内生成的id已经超过上限 需要等待1秒再继续生成
                w.number = 0
                for now <= w.timestamp {
                    now = w.Now()
                }
                w.timestamp = now
            }
        }
        //第一段 now - epoch 为该算法目前已经奔跑了秒
        //如果在程序跑了一段时间修改了epoch这个值 可能会导致生成相同的id
        id := int64((now-epoch)<<timeShift | (w.workerId << workerShift) | (w.number))
        //检查是否有重复的id正式环境不需要
        if _, ok := w.idMap[id]; ok {
            fmt.Printf("重复id:%#v,%d,%d,%#v\n", id, w.number, w.timestamp, w.flag)
        }
        w.idMap[id] = true
        return id
    }
    
    // @desc 获取当前时间
    // @auth liuguoqiang 2020-06-16
    // @param
    // @return
    func (w *Worker) Now() int64 {
        //模拟时间回拨10秒,正式环境不需要
        if w.flag {
            return time.Now().Unix() - 10
        }
        if w.number == numberMax/2 && !w.flag {
            w.flag = true
            return time.Now().Unix() - 10
        }
        return time.Now().Unix()
    }
    
    func main() {
        // 生成节点实例
        worker, err := NewWorker(1)
        if err != nil {
            fmt.Println(err)
            return
        }
        count := 40000000
        for i := 0; i < count; i++ {
            _ = worker.GetId()
        }
        i := 0
        for _, _ = range worker.idMap {
            i++
        }
        fmt.Println("id总数", count)
        fmt.Println("不重复id数量", i)
    }
    
    
    总结:

    实际生产环境中单节点每秒4194304个id完全足够,最多可以支持1024个节点


    github地址:snowflake

    相关文章

      网友评论

          本文标题:golang雪花算法SnowFlake解决时间回拨问题

          本文链接:https://www.haomeiwen.com/subject/qporxktx.html