这是我参与「第三届青训营 -后端场」笔记创作活动的第 3 篇笔记
引言
大家在做抖音项目的时候,数据库中的id是如何设计的呢?我觉得首先要考虑的就是id能存的最大值,其次就是如何保证id的唯一性,性能和趋势递增也是一个需要考虑的点。所以我们选用的是bigint类型存储+雪花算法生成。
雪花算法的核心思想就是用一个64bit的整型作为唯一id,长整型中包含毫秒级时间戳,机器编号,机器内序号。
SnowFlake(雪花算法)
全局唯一,并且有序按时间递增,同时占用空间少,生成的id仅仅是19位的整形数字,正好契合mysql的bigint数据类型,简直完美。
数据结构:
1bit-符号位
因为二进制中最高位是符号位,1表示负数,0表示正数。
生成的id一般都是用整数,所以最高位固定为0。
41bit-时间戳
用来记录时间戳,毫秒级。
41位可以表示2^(41)-1个数字,
如果只用来表示正整数(计算机中正数包含0) ,可以表示的数值范围是: 0至2^(41)-1,
减1是因为可表示的数值范围是从0开始算的,而不是1。也就是说41位可以表示2^(41)-1个毫秒的值,转化成单位年则是(2"41)-1)/(1000 "60 "60 -24 "365) =69年。
10bit-工作机器id
用来记录工作机器id.可以部署在2^(10)= 1024个节点,它包括5位datacenterld和5位。
workerld
5bit可以表示的最大正整数是245-1=31,即可以用0,1,2,3…31这32个数字,来表示不同的datecenterld或workerld。
12bit-序列号
序列号,用来记录同毫秒内产生的不同id。
12bit可以表示的最大正整数是2^(12)-1=4095,即可以用0、1.2.3. 4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。
SnowFlake可以保证:所有生成的id按时间趋势递增,整个分布式系统内不会产重复id (因为有datacenterld和workerld来做区分)
优点:
(1)高性能高可用:生成时不依赖于数据库,完全在内存中生成。
(2)容量大:每秒中能生成数百万的自增ID。
(3)ID自增:存入数据库中,索引效率高。
缺点:
依赖与系统时间的一致性,如果系统时间被回调,或者改变,可能会造成id冲突或者重复。可能在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,有时候会出现不是全局递增的情况(此缺点可以忽略, 一般分布式ID只要求趋势递增,并不会严格要求递增, 90%的需求都只要求趋势递增)
Golang实现雪花算法
思路:
-
序列号 12位,如果同一毫秒内产生的序列号超过4096个,就等到下一毫秒再生成。同一时间的序列号从0开始递增;
-
机器号 10位,序列号占用12位空间,所以左移 12 位;
-
时间戳 41位,时间戳和0x1FFFFFFFFFF做 与运算 ,取时间戳的低 41 位,左移22位;
-
以上三者做 或运算 得到生成的id。
package util
import (
"errors"
"sync"
"time"
)
const (
twepoch = int64(1483228800000) //开始时间截 (2017-01-01)
workeridBits = uint(10) //机器id所占的位数
sequenceBits = uint(12) //序列所占的位数
workeridMax = int64(-1 ^ (-1 << workeridBits)) //支持的最大机器id数量
sequenceMask = int64(-1 ^ (-1 << sequenceBits)) //
workeridShift = sequenceBits //机器id左移位数
timestampShift = sequenceBits + workeridBits //时间戳左移位数
)
// A Snowflake struct holds the basic information needed for a snowflake generator worker
type Snowflake struct {
sync.Mutex
timestamp int64
workerid int64
sequence int64
}
// NewNode returns a new snowflake worker that can be used to generate snowflake IDs
func NewSnowflake(workerid int64) (*Snowflake, error) {
if workerid < 0 || workerid > workeridMax {
return nil, errors.New("workerid must be between 0 and 1023")
}
return &Snowflake{
timestamp: 0,
workerid: workerid,
sequence: 0,
}, nil
}
// Generate creates and returns a unique snowflake ID
func (s *Snowflake) Generate() int64 {
s.Lock()
now := time.Now().UnixNano() / 1000000
if s.timestamp == now {
s.sequence = (s.sequence + 1) & sequenceMask
if s.sequence == 0 {
for now <= s.timestamp {
now = time.Now().UnixNano() / 1000000
}
}
} else {
s.sequence = 0
}
s.timestamp = now
r := int64((now-twepoch)<<timestampShift | (s.workerid << workeridShift) | (s.sequence))
s.Unlock()
return r
}