能做啥?
能够在分布式场景中为我们在每毫秒里面生成4096个纯数字的有序的唯一id,但只能连续使用69年(代码在69年后就不能保证生成的是唯一id了),并且你的分布式机器小于1024台。当然这里面出现的4096,69,1024都是可以通过参数配置调大调小的。
golang版本实现
最高位是符号位,始终为0,不可用。
41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。
10位的机器标识,10位的长度最多支持部署1024个节点。
12位的计数序列号,序列号即一系列的自增id,可以支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号。
直接上代码
package main
import (
"errors"
"fmt"
"sync"
"time"
)
//你需要先去了解下 golang中 & | << >> 几个符号产生的运算意义
const (
workerBits uint8 = 10 //10bit工作机器的id,如果你发现1024台机器不够那就调大次值
numberBits uint8 = 12 //12bit 工作序号,如果你发现1毫秒并发生成4096个唯一id不够请调大次值
workerMax int64 = -1 ^ (-1 << workerBits)
numberMax int64 = -1 ^ (-1 << numberBits)
timeShift uint8 = workerBits + numberBits
workerShift uint8 = numberBits
// 如果在程序跑了一段时间修改了epoch这个值 可能会导致生成相同的ID,
//这个值请自行设置为你系统准备上线前的精确到毫秒级别的时间戳,因为雪花时间戳保证唯一的部分最多管69年(2的41次方),
//所以此值设置为你当前时间戳能够保证你的系统是从当前时间开始往后推69年
startTime int64 = 1525705533000
)
type Worker struct {
mu sync.Mutex
timestamp int64
workerId int64
number int64
}
func NewWorker(workerId int64) (*Worker, error) {
if workerId < 0 || workerId > workerMax {
return nil, errors.New("Worker ID excess of quantity")
}
// 生成一个新节点
return &Worker{
timestamp: 0,
workerId: workerId,
number: 0,
}, nil
}
func (w *Worker) GetId() int64 {
w.mu.Lock()
defer w.mu.Unlock()
now := time.Now().UnixNano() / 1e6
if w.timestamp == now {
w.number++
if w.number > numberMax {
for now <= w.timestamp {
now = time.Now().UnixNano() / 1e6
}
}
} else {
w.number = 0
w.timestamp = now
}
//以下表达式才是主菜
// (now-startTime)<<timeShift 产生了 41 + (10 + 12)的效应但却并不保证唯一
// | (w.workerId << workerShift) 保证了与其他机器不重复
// | (w.number)) 保证了自己这台机不会重复
ID := int64((now-startTime)<<timeShift | (w.workerId << workerShift) | (w.number))
return ID
}
func main() {
// 生成节点实例,当你分布式的部署你的服务的时候,这个NewWorker的参数记录不同的node配置的值应该不一样
node, err := NewWorker(1)
if err != nil {
panic(err)
}
for {
fmt.Println(node.GetId())
return
}
}
总结
1.自增ID:对于数据敏感场景不宜使用,且不适合于分布式场景。
2.GUID:采用无意义字符串,数据量增大时造成访问过慢,且不宜排序。
- 雪花算法能够满足分布式场景生成纯数字的有序的数字,数据敏感场景也能使用,且纯数字相比字符串的GUID可小8倍+
有疑问加站长微信联系(非本文作者)