要求:
数据库自增ID
单体应用广泛使用,最简单的实现方式是使用数据库的主键id自增策略,如 MySQL
的 auto_increment
。如果两台数据库分别设置不同步长,可以生成不重复ID,从而实现高可用。
优点 :
- 非常简单,利用现有数据库系统的功能实现,成本小,有DBA专业维护。
缺点 :
- 强依赖 DB,当 DB 异常时整个系统不可用,属于致命问题。
- ID发号性能瓶颈限制在单台 MySQL 的读写性能。
- 分库分表后,同一数据表的自增 ID 容易重复。
- 数据容易被爬虫获取,直接按照顺序下载指定URL即可,比如单日销量等
Redis实现ID
通过 Redis 的 INCR / INCRBY
自增原子操作命令,能保证生成的ID肯定是唯一有序的,本质上实现方式与数据库一致。
优点 :
缺点 :
- 由于redis是内存的KV数据库,即使有AOF和RDB,但是依然会存在数据丢失,导致获取的最新ID不准确,有可能会造成ID重复。
适用场景:
- 比较适合计数场景,如埋点、用户访问量等。
UUID
UUID 包含网卡 MAC 地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素。其标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符,例如:550e8400-e29b-41d4-a716-446655440000。
优点:
缺点:
- 不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。
- 作为主键时会存在一些问题,如做DB主键的场景下,UUID就非常不适用:
- 对MySQL索引不利:如果作为数据库主键,在InnoDB引擎下,UUID的无序性可能会引起数据位置频繁变动,严重影响性能。
- 官方有明确的建议主键要尽量越短越好,36个字符长度的 UUID 也不符合要求。
- 信息不安全:基于 MAC 地址生成 UUID 的算法可能会造成 MAC 地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。
- UUID的无序性可能会引起数据位置频繁变动,严重影响性能。
适用场景:
对存储空间没有严格性能要求的都能够适用,比如各种链路追踪、日志存储等。
MongoDB的ObjectId
MongoDB 的主键类型 ObjectID 也是一种ID生成方案,如:5349b4ddd2781d08c09890f3, 它看起来是一个包含24个字符的字符串,实际采用12个字节来存储。
,可以容纳更多的信息。
### snowflake算法生成ID
snowflake结构图 - 1bit:符号位,固定是0,表示全部ID都是正整数
- 41bit:毫秒数时间差,从指定的日期算起,够用69年,我们知道用Long类型表示的时间戳是从1970-01-01 00:00:00开始算起的,这里的时间戳可以指定日期,如2020-01-01 00:00:00
- 10bit:机器ID,有异地部署,多集群的也可以配置,需要线下规划好各地机房,各集群,各实例ID的编号
- 12bit:序列ID,前面都相同的话,最多可以支持到4096个
优点: - 毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
- 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。
- 可以根据自身业务特性分配bit位,非常灵活。
缺点: - 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。
代码地址 :
https://github.com/pengfeidai/go-snowflake1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| const ( epoch = int64(1577808000000) timestampBits = uint(41) datacenteridBits = uint(5) workeridBits = uint(5) sequenceBits = uint(12) timestampMax = int64(-1 ^ (-1 << timestampBits)) datacenteridMax = int64(-1 ^ (-1 << datacenteridBits)) workeridMax = int64(-1 ^ (-1 << workeridBits)) sequenceMask = int64(-1 ^ (-1 << sequenceBits)) workeridShift = sequenceBits datacenteridShift = sequenceBits + workeridBits 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 { s.sequence = (s.sequence + 1) & sequenceMask if s.sequence == 0 { for now <= s.timestamp { now = time.Now().UnixNano() / 1000000 } } } else { s.sequence = 0 } t := now - epoch if t > timestampMax { s.Unlock() log.Printf("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 }
|
执行结果:
大公司 snowflake 使用
百度UidGenerator、美团 Leaf-snowflake 等
https://tech.meituan.com/2017/04/21/mt-leaf.html