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

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

  •  
  •   NoKey · 248 天前 · 3404 次点击
    这是一个创建于 248 天前的主题,其中的信息可能已经有所发展或是发生改变。

    打算用雪花算法生成 ID

    这里面有个机器 ID

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

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

    请大家赐教。

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

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

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

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

    “版本 1 - UUID 是根据时间和 节点 ID (通常是 MAC 地址)生成;” -- 维基百科
    wxw752
        8
    wxw752  
       248 天前
    @lmshl 主要是雪花生成出来的 是纯数字 ID
    lmshl
        9
    lmshl  
       248 天前
    UUID 也是数字啊,一个 128 位大整数

    @wxw752
    sardina
        10
    sardina  
       248 天前
    @lmshl 一个自增 一个不是自增 差太多了
    LeegoYih
        11
    LeegoYih  
       248 天前
    实际上直接用随机数都行,主键冲突了大不了抛“服务过于繁忙”错误,发生的概率非常小。

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



    BSON ObjectID 比 UUID 好用一点
    Bromine0x23
        12
    Bromine0x23  
       248 天前
    说到 UUID 的话,有 V6 V7 两个草案阶段的变种是自增的
    Jooooooooo
        13
    Jooooooooo  
       248 天前
    不要太复杂, 直接用个 mysql 主键维护一下.

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

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

    当然还有一些细节:
    1. 要考虑某个 Pod 反复重启的情况;
    2. 引入命名空间的概念,针对数据不会交叉的场景隔离命名空间,不然 workerId 根本不够用;
    3. 我们在 MySQL 还实现了一条有意思的 SQL:通过无锁的方式获取未被使用的 workerId ,返回范围在[0-MAX],不用做取模等;
    xuanbg
        18
    xuanbg  
       248 天前
    首选注册中心分配,部署多个服务没有注册中心有些不可思议啊。
    zibber
        19
    zibber  
       248 天前
    在 etcd 维护一个 key, pod 启动的时候写入 etcd, pod 停止的时候从 etcd 删掉
    cp19890714
        20
    cp19890714  
       247 天前
    使用 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  
       247 天前
    我们是使用 Redis ,起一个自增一个 然后检查对应 key (有 5 分钟失效时间)是否存在 如果存在则再自增。
    然后维持好心跳,一分钟发次心跳,续 5 分钟的失效时间
    NoKey
        22
    NoKey  
    OP
       246 天前
    @Marcoo 自增到 workID 最大值了怎么处理?回头来?或者场景上就不可能达到最大值
    Marcoo
        23
    Marcoo  
       245 天前
    @NoKey 是的 有个最大值 到最大值了就再从 0 开始 就这样一直循环下去。反正我们的实例数不多 基本上很少会冲突的
    关于   ·   帮助文档   ·   博客   ·   nftychat   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1101 人在线   最高记录 5634   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 39ms · UTC 23:09 · PVG 07:09 · LAX 16:09 · JFK 19:09
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.