go實現分布式唯一ID-snowflake(雪花算法)


package main

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

/*
 * 算法解釋
 * SnowFlake的結構如下(每部分用-分開):<br>
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
 * 1位標識,由於long基本類型在Java中是帶符號的,最高位是符號位,正數是0,負數是1,所以id一般是正數,最高位是0<br>
 * 41位時間截(毫秒級),注意,41位時間截不是存儲當前時間的時間截,而是存儲時間截的差值(當前時間截 - 開始時間截)
 * 得到的值),這里的的開始時間截,一般是我們的id生成器開始使用的時間,由我們程序來指定的(如下的epoch屬性)。
 * 41位的時間截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
 * 10位的數據機器位,可以部署在1024個節點,包括5位datacenterId和5位workerId<br>
 * 12位序列,毫秒內的計數,12位的計數順序號支持每個節點每毫秒(同一機器,同一時間截)產生4096個ID序號<br>
 * 加起來剛好64位,為一個Long型。<br>
 * SnowFlake的優點是,整體上按照時間自增排序,並且整個分布式系統內不會產生ID碰撞(由數據中心ID和機器ID作區分),並且效率較高,經測試,SnowFlake每秒能夠產生26萬ID左右。
 */
const (
	//t := time.Date(2015, 1, 1, 00, 00, 00, 00, time.Local).UnixNano() / 1e6;//獲取時間戳 毫秒
	//開始時間戳 2015-1-1
	epoch int64 = 1420041600000
	// 機器id所占的位數
	workerIdBits int64 = 5
	// 數據標識id所占的位數
	datacenterIdBits int64 = 5
	//支持的最大機器id,結果是31 (這個移位算法可以很快的計算出幾位二進制數所能表示的最大十進制數)
	maxWorkerId int64 = -1 ^ (-1 << workerIdBits)
	// 支持的最大數據標識id,結果是31
	maxDatacenterId int64 = -1 ^ (-1 << datacenterIdBits)
	//序列在id中占的位數
	sequenceBits int64 = 12
	// 機器ID向左移12位
	workerIdShift int64 = sequenceBits;
	// 數據標識id向左移17位(12+5)
	datacenterIdShift int64 = sequenceBits + workerIdBits;
	// 時間截向左移22位(5+5+12)
	timestampLeftShift int64 = sequenceBits + workerIdBits + datacenterIdBits
	// 生成序列的掩碼,這里為4095 (0b111111111111=0xfff=4095)
	sequenceMask int64 = -1 ^ (-1 << sequenceBits)
)

/*
 * 構造
 */
type SnowflakeIdWorker struct {
	mutex sync.Mutex // 添加互斥鎖 確保並發安全
	lastTimestamp int64 // 上次生成ID的時間截
	workerId int64 // 工作機器ID(0~31)
	datacenterId int64 //數據中心ID(0~31)
	sequence int64 // 毫秒內序列(0~4095)
}

/*
 * 創建SnowflakeIdWorker
 * workerId 工作ID (0~31)
 * datacenterId 數據中心ID (0~31)
 */
func createWorker(wId int64,dId int64)(*SnowflakeIdWorker,error){
	if wId < 0 || wId > maxWorkerId {
		return nil, errors.New("Worker ID excess of quantity")
	}
	if dId < 0 || dId > maxDatacenterId {
		return nil, errors.New("Datacenter ID excess of quantity")
	}
	// 生成一個新節點
	return &SnowflakeIdWorker{
		lastTimestamp: 0,
		workerId: wId,
		datacenterId: dId,
		sequence: 0,
	}, nil
}

/*
 * 獲取ID
 */
func (w *SnowflakeIdWorker) nextId() int64 {
	// 保障線程安全 加鎖
	w.mutex.Lock()
	// 生成完成后 解鎖
	defer w.mutex.Unlock()
	// 獲取生成時的時間戳 毫秒
	now := time.Now().UnixNano() / 1e6
	//如果當前時間小於上一次ID生成的時間戳,說明系統時鍾回退過這個時候應當拋出異常
	if now < w.lastTimestamp {
		errors.New("Clock moved backwards")
		//根據需要自定義錯誤碼
		return 3001
	}
	if  w.lastTimestamp == now {
		w.sequence = (w.sequence + 1) & sequenceMask
		if w.sequence == 0 {
			// 阻塞到下一個毫秒,直到獲得新的時間戳
			for now <= w.lastTimestamp {
				now = time.Now().UnixNano() / 1e6
			}
		}
	}else {
		// 當前時間與工作節點上一次生成ID的時間不一致 則需要重置工作節點生成ID的序號
		w.sequence = 0
	}
	// 將機器上一次生成ID的時間更新為當前時間
	w.lastTimestamp = now
	ID := int64((now - epoch) << timestampLeftShift | w.datacenterId << datacenterIdShift | (w.workerId << workerIdShift) | w.sequence)
	return ID
}

/*
 * 將十進制數字轉化為二進制字符串
 */
func convertToBin(num int64) string {
	s := ""
	if num == 0 {
		return "0"
	}
	// num /= 2 每次循環的時候 都將num除以2  再把結果賦值給 num
	for ;num > 0 ; num /= 2 {
		lsb := num % 2
		// 將數字強制性轉化為字符串
		s = strconv.FormatInt(lsb,10) + s
	}
	return s
}

func main() {
	worker,err := createWorker(0,0)
	if err != nil {
		fmt.Println(err)
		return
	}
	ch := make(chan int64)
	count := 100
	// 並發 goroutine ID生成
	for i := 0; i < count; i++ {
		go func() {
			id := worker.nextId()
			ch <- id
		}()
	}
	defer close(ch)
	m := make(map[int64]int)
	for i := 0; i < count; i++  {
		id := <- ch
		// map中存在為id的key,說明生成的 ID有重復
		_, ok := m[id]
		if ok {
			fmt.Println("ID is not unique!")
		}
		// id作為key存入map
		m[id] = i
		fmt.Println(id)
		fmt.Println(convertToBin(id))
	}
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM