zoukankan      html  css  js  c++  java
  • go工具库分析——go-snowflake

    go-snowflake

    在单机系统中我们会使用自增id作为数据的唯一id,自增id在数据库中有利于排序和索引,但是在分布式系统中如果还是利用数据库的自增id会引起冲突,自增id非常容易被爬虫爬取数据。在分布式系统中有使用uuid作为数据唯一id的,但是uuid是一串随机字符串,所以它无法被排序。

    Twitter设计了Snowflake算法为分布式系统生成ID,Snowflake的id是int64类型,它通过datacenterId和workerId来标识分布式系统,下面看下它的组成:

    符号位(保留字段) 时间戳(当前时间-纪元时间) 数据中心id 机器id 自增序列
    1bit 41bit 5bit 5bit 12bit

    算法简介

    在使用Snowflake生成id时关键点:

    • 计算时间戳timestamp(当前时间 - 纪元时间),如果timestamp数据超过41bit则异常
    • 判断datacenterId和workerId不能超过5bit(0-31)
    • 处理自增序列时,如果发现自增序列超过12bit时需要等待,因为当前毫秒下12bit的自增序列被用尽,需要进入下一毫秒后自增序列继续从0开始递增

    核心代码

    package snowflake
    
    import (
    	"fmt"
    	"sync"
    	"time"
    
    	"github.com/golang/glog"
    )
    
    const (
    	epoch             = int64(1577808000000)                           // 设置起始时间(时间戳/毫秒):2020-01-01 00:00:00,有效期69年
    	timestampBits     = uint(41)                                       // 时间戳占用位数
    	datacenteridBits  = uint(5)                                        // 数据中心id所占位数
    	workeridBits      = uint(5)                                        // 机器id所占位数
    	sequenceBits      = uint(12)                                       // 序列所占的位数
    	timestampMax      = int64(-1 ^ (-1 << timestampBits))              // 时间戳最大值    -1的二进制表示是64位全是1
    	datacenteridMax   = int64(-1 ^ (-1 << datacenteridBits))           // 支持的最大数据中心id数量
    	workeridMax       = int64(-1 ^ (-1 << workeridBits))               // 支持的最大机器id数量
    	sequenceMask      = int64(-1 ^ (-1 << sequenceBits))               // 支持的最大序列id数量
    	workeridShift     = sequenceBits                                   // 机器id左移位数
    	datacenteridShift = sequenceBits + workeridBits                    // 数据中心id左移位数
    	timestampShift    = sequenceBits + workeridBits + datacenteridBits // 时间戳左移位数
    )
    
    type Snowflake struct {
    	sync.Mutex   //加锁,防止并发碰撞
    	timestamp    int64
    	workerid     int64
    	datacenterid int64
    	sequence     int64
    }
    
    func NewSnowflake(datacenterid, workerid int64) (*Snowflake, error) {
    	if datacenterid < 0 || datacenterid > datacenteridMax {
    		return nil, fmt.Errorf("datacenterid must be between 0 and %d", datacenteridMax-1)
    	}
    	if workerid < 0 || workerid > workeridMax {
    		return nil, fmt.Errorf("workerid must be between 0 and %d", workeridMax-1)
    	}
    	return &Snowflake{
    		timestamp:    0,
    		datacenterid: datacenterid,
    		workerid:     workerid,
    		sequence:     0,
    	}, nil
    }
    
    func (s *Snowflake) NextVal() int64 {
    	s.Lock()
    	now := time.Now().UnixNano() / 1000000 // 转毫秒
    	if s.timestamp == now {
    		// 当同一时间戳(精度:毫秒)下多次生成id会增加序列号
    		s.sequence = (s.sequence + 1) & sequenceMask
    		if s.sequence == 0 {
    			// 如果当前序列超出12bit长度,则需要等待下一毫秒
    			// 下一毫秒将使用sequence:0
    			for now <= s.timestamp {
    				now = time.Now().UnixNano() / 1000000
    			}
    		}
    	} else {
    		// 不同时间戳(精度:毫秒)下直接使用序列号:0
    		s.sequence = 0
    	}
    	t := now - epoch
    	if t > timestampMax {
    		s.Unlock()
    		glog.Errorf("epoch must be between 0 and %d", timestampMax-1)
    		return 0
    	}
    	s.timestamp = now
    	r := int64((t)<<timestampShift | (s.datacenterid << datacenteridShift) | (s.workerid << workeridShift) | (s.sequence))
    	s.Unlock()
    	return r
    }
    
    // 获取数据中心ID和机器ID
    func GetDeviceID(sid int64) (datacenterid, workerid int64) {
    	datacenterid = (sid >> datacenteridShift) & datacenteridMax
    	workerid = (sid >> workeridShift) & workeridMax
    	return
    }
    
    // 获取时间戳
    func GetTimestamp(sid int64) (timestamp int64) {
    	timestamp = (sid >> timestampShift) & timestampMax
    	return
    }
    
    // 获取创建ID时的时间戳
    func GetGenTimestamp(sid int64) (timestamp int64) {
    	timestamp = GetTimestamp(sid) + epoch
    	return
    }
    
    // 获取创建ID时的时间字符串(精度:秒)
    func GetGenTime(sid int64) (t string) {
    	// 需将GetGenTimestamp获取的时间戳/1000转换成秒
    	t = time.Unix(GetGenTimestamp(sid)/1000, 0).Format("2006-01-02 15:04:05")
    	return
    }
    
    // 获取时间戳已使用的占比:范围(0.0 - 1.0)
    func GetTimestampStatus() (state float64) {
    	state = float64((time.Now().UnixNano()/1000000 - epoch)) / float64(timestampMax)
    	return
    }
    

    参考

  • 相关阅读:
    ISBN号码
    计数问题
    小玉在游泳
    数字反转
    单调队列(学习笔记)
    LCA(学习笔记)
    emacs配置文件
    线段树(学习笔记)
    RMQ问题 ST算法(学习笔记)
    Lucas卢卡斯定理(学习笔记)
  • 原文地址:https://www.cnblogs.com/weiweng/p/13282828.html
Copyright © 2011-2022 走看看