搜索引擎项目实战 (二)全局唯一ID生成算法原理与实践 | 青训营笔记

173 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第6篇笔记

在搜索引擎项目的开发过程中,我们遇到了为每条记录生成一个全局唯一ID的问题。这个问题在分布式场景中是一个十分常见的问题,经过调研,总结出常用的唯一ID生成的算法有以下几种:

常见的全局唯一ID生成算法

UUID(Universisally Unique Identifier,通用唯一识别码)

根据实现原理可以大致分为基于时间戳或时钟序列生成、基于名字散列值(MD5/SHA1)生成、基于随机数生成三种类型。 标准格式:32个十六进制数组成的分为5个部分的字符串,各部分数字个数为:8-4-4-4-12 优点有: - 不依赖网络,本地生成 - 速度快(支持高并发) - 有现成的库可供调用 缺点有: - 利用字符串类型存储,数据库查询效率低 -无序

数据库自增ID

顾名思义,数据库每增加一条记录,ID加1

优点:简单、有序 缺点:并发一致性问题,已删除的ID无法复用,数据库故障后无法使用 为了解决并发问题:可以对数据库水平拆分,分别设置初始值和步长。这样为每个数据库产生ID时就不需要同步其他数据库当前的ID了。另外也可以采用预先批量分配ID的方式为每台机器分配一个可以使用的ID区间。

雪花算法

产生以下格式的一个64bit有符号整数,对于同一机器、同一时刻、同一并发序列是唯一的。

1bit41bit10bit12bit
符号位(未使用)时间戳(最长为69年)机器ID序列号(区分同一ms内产生的ID)

在实际应用中也可以根据机器数量、使用年限对上述结构进行适合自身业务的变化。例如百度的UidGenerator和美团的Leaf等都是基于雪花算法结合自身业务需求的产品。 优点:支持高并发,极限QPS可达400w/s

缺点:依赖时钟,如果时钟回拨可能会导致ID重复

雪花算法实践

下面基于雪花算法的原理编码实现一个64位全局唯一ID生成器,使用时先用Init函数产生全局静态变量s并初始化,之后调用静态GetID方法生成ID

type Snowflake struct {
    sync.Mutex           // 互斥锁
    tstamp  int64        // 时间戳/ms
    wid   int64          // 机器编号
    sequence   int64     // 序列号
}

var s *Snowflake
func Init() {
    s = new(Snowflake)
}
func GetID() int64 {
    s.Lock()
    //得到当前ms时间戳
    now := time.Now().UnixNano() / 1000000
    //如果当前ms时间戳等于上一个时间戳则序列号加1
    //如果序列号用尽(等于零),则等下一个ms
    if s.tstamp == now {
        s.sequence = (s.sequence + 1) & 0b111111111111
        if s.sequence == 0 {
            for now <= s.tstamp {
                now = time.Now().UnixNano() / 1000000
            }
        }
    } else {
        s.sequence = 0
    }
    //如果当前时间减去时间原点(2020-01-01 00:00:00)超过最大限制(69年)返回0
    t := now - int64(1577808000000)
    if t > int64(-1 ^ (-1 << 41)) {
        s.Unlock()
        return 0
    }
    s.tstamp = now
    //生成ID
    r := int64((t<<22) | (s.wid<<12) | (s.sequence))
    s.Unlock()
    return r
}

参考文章

cloud.tencent.com/developer/a… 唯一ID生成算法剖析,看看这篇就够了 www.zhangshengrong.com/p/zD1yD94eX… Go语言实现Snowflake雪花算法