V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
NoKey
V2EX  ›  程序员

雪花算法生成 ID,如何便捷的生成机器 ID

  •  
  •   NoKey · 2022-09-26 12:02:00 +08:00 · 4324 次点击
    这是一个创建于 549 天前的主题,其中的信息可能已经有所发展或是发生改变。

    打算用雪花算法生成 ID

    这里面有个机器 ID

    如果我们部署多个服务的话

    在没有注册中心的场景下,怎么能够方便的生成 ID 啊

    请大家赐教。

    23 条回复    2022-09-29 15:38:06 +08:00
    TsubasaHanekaw
        1
    TsubasaHanekaw  
       2022-09-26 12:05:07 +08:00
    网卡 mac 啊,硬盘序列号啊 这种
    qq976739120
        2
    qq976739120  
       2022-09-26 12:08:25 +08:00
    用各个硬件的唯一标识号
    hzj629206717
        3
    hzj629206717  
       2022-09-26 12:11:57 +08:00
    也不是很复杂,需要额外引入 Redis 或 MySQL ,每个实例去抢占一个 machine id 并 keep alive 。剩下的就是实现的可靠性了。
    changdy
        4
    changdy  
       2022-09-26 12:19:49 +08:00
    楼上几个 回答是有偏颇的, 如果要做到 服务无感,随时扩容,缩容 ,是需要 一个类似注册中心的 .

    我们之前的做法是 固定部署几个实例 ,每个实例的序号就是 机器 id. 当然这也会有问题,

    比如项目部署接单会出现 旧的实例没完全下线,新的已经上线了.还有就是无法随时扩容 .

    这个其实也是服务治理中的一环了.
    wu00
        5
    wu00  
       2022-09-26 12:51:41 +08:00   ❤️ 2
    10bit 机器码,便捷就集群内网 IP % 1024 ,量不大没啥问题。
    严谨点就按#3 #4 说的注册中心
    git00ll
        6
    git00ll  
       2022-09-26 13:18:08 +08:00
    redis 存一个自增的序号
    lmshl
        7
    lmshl  
       2022-09-26 13:44:00 +08:00
    UUID v1 满足不了么?

    “版本 1 - UUID 是根据时间和 节点 ID (通常是 MAC 地址)生成;” -- 维基百科
    wxw752
        8
    wxw752  
       2022-09-26 13:47:20 +08:00
    @lmshl 主要是雪花生成出来的 是纯数字 ID
    lmshl
        9
    lmshl  
       2022-09-26 13:51:33 +08:00
    UUID 也是数字啊,一个 128 位大整数

    @wxw752
    sardina
        10
    sardina  
       2022-09-26 14:11:34 +08:00
    @lmshl 一个自增 一个不是自增 差太多了
    LeegoYih
        11
    LeegoYih  
       2022-09-26 14:14:36 +08:00
    实际上直接用随机数都行,主键冲突了大不了抛“服务过于繁忙”错误,发生的概率非常小。

    雪花算法要保证服务器时间一致,还有时间回拨问题,而且数值还很大,JS 直接用会丢精度。
    不如数据库用维护序号,每次获取一批 ID 缓存到客户端。



    BSON ObjectID 比 UUID 好用一点
    Bromine0x23
        12
    Bromine0x23  
       2022-09-26 14:40:36 +08:00
    说到 UUID 的话,有 V6 V7 两个草案阶段的变种是自增的
    Jooooooooo
        13
    Jooooooooo  
       2022-09-26 14:46:26 +08:00
    不要太复杂, 直接用个 mysql 主键维护一下.

    每个机器启动的时候都去库里拿一下自己的 id, 如果没有就 insert.

    但这里得注意, 如果机器码是三位的, 那集群下机器数量不能超过 1000, 还有废弃机器占用 id 的问题.
    wangritian
        14
    wangritian  
       2022-09-26 15:28:04 +08:00
    k8s 有状态应用的 hostname 或 redis 的 incr (程序版本为 key )
    changdy
        15
    changdy  
       2022-09-26 19:09:24 +08:00
    @wu00
    也不大行 ,8 个节点的时候 都不重复的概率大概就是 3% 发布 100 次可能就,期望就是 3 了.
    ScepterZ
        16
    ScepterZ  
       2022-09-26 19:24:45 +08:00
    容器编号如何
    odirus
        17
    odirus  
       2022-09-26 19:33:19 +08:00
    我们刚好有这个功能:
    1. 提供一个服务端来下发未使用的 workerId ;
    2. 客户端获取到 workerId 后定期上报心跳到服务端;
    3. 服务端维护 workerId 的心跳信息(并针对异常心跳做断言,提前发现问题),一段时间内没心跳后标记为未使用;

    当然还有一些细节:
    1. 要考虑某个 Pod 反复重启的情况;
    2. 引入命名空间的概念,针对数据不会交叉的场景隔离命名空间,不然 workerId 根本不够用;
    3. 我们在 MySQL 还实现了一条有意思的 SQL:通过无锁的方式获取未被使用的 workerId ,返回范围在[0-MAX],不用做取模等;
    xuanbg
        18
    xuanbg  
       2022-09-26 20:23:08 +08:00
    首选注册中心分配,部署多个服务没有注册中心有些不可思议啊。
    zibber
        19
    zibber  
       2022-09-26 21:14:53 +08:00
    在 etcd 维护一个 key, pod 启动的时候写入 etcd, pod 停止的时候从 etcd 删掉
    cp19890714
        20
    cp19890714  
       2022-09-27 09:53:35 +08:00
    使用 Redis ,几行代码即可。
    ```
    public class DistributedWorkerIdGenerator {
    private static final String ID_WORKER_ID_INCREMENT_KEY = "ID_WORKER_ID_INCREMENT";
    private static final int MAX_WORKER_ID = 64;
    private static final int EXPIRE_SECONDS = 3600;
    private static final String ID_WORKER_ID_PREFIX = "ID_WORKER_ID_";

    /**
    * 获取新的 workId
    * 1.维护自增数字, 数字每次增加 1
    * 2.如果 workerId 已经存在, 则循环获取新的 workerId
    *
    * @param redisTemplate
    * @return workId
    */
    Long acquireId(RedisTemplate<String, Object> redisTemplate) {
    RedisAtomicLong redisAtomicLong = new RedisAtomicLong(ID_WORKER_ID_INCREMENT_KEY, redisTemplate.getConnectionFactory());
    Long incrementIndex;
    Long workerId;
    for (int i = 0; i < MAX_WORKER_ID; i++) {
    incrementIndex = redisAtomicLong.getAndIncrement();
    workerId = incrementIndex % MAX_WORKER_ID;
    if (redisTemplate.opsForValue().setIfAbsent(ID_WORKER_ID_PREFIX + workerId, workerId, EXPIRE_SECONDS, TimeUnit.SECONDS)) {
    return workerId;
    }
    }
    throw new ApplicationException("Generate workId failed");
    }

    /**
    * 续签 workerId
    * 服务实例需要通过定时任务续签, 定时任务的时间间隔需要小于过期时间
    *
    * @param redisTemplate
    * @param workerId
    */
    void renewalId(RedisTemplate<String, Object> redisTemplate, Long workerId) {
    redisTemplate.expire(ID_WORKER_ID_PREFIX + workerId, EXPIRE_SECONDS, TimeUnit.SECONDS);
    }
    ```
    Marcoo
        21
    Marcoo  
       2022-09-27 16:18:51 +08:00
    我们是使用 Redis ,起一个自增一个 然后检查对应 key (有 5 分钟失效时间)是否存在 如果存在则再自增。
    然后维持好心跳,一分钟发次心跳,续 5 分钟的失效时间
    NoKey
        22
    NoKey  
    OP
       2022-09-28 10:57:58 +08:00
    @Marcoo 自增到 workID 最大值了怎么处理?回头来?或者场景上就不可能达到最大值
    Marcoo
        23
    Marcoo  
       2022-09-29 15:38:06 +08:00
    @NoKey 是的 有个最大值 到最大值了就再从 0 开始 就这样一直循环下去。反正我们的实例数不多 基本上很少会冲突的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1460 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 23:50 · PVG 07:50 · LAX 16:50 · JFK 19:50
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.