zoukankan      html  css  js  c++  java
  • 美团Leaf分布式ID生成策略

    ID生成原则

    全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求。

    趋势递增:在MySQL InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用B-tree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。

    信息安全:如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要ID无规则、不规则。

    segment号段模式

    重要字段说明:biz_tag用来区分业务,max_id表示该biz_tag目前所被分配的ID号段的最大值,step表示每次分配的号段长度。

    test_tag在第一台Leaf机器上是1~1000的号段,当这个号段用完时,会去加载另一个长度为step=1000的号段,假设另外两台号段都没有更新,这个时候第一台机器新加载的号段就应该是3001~4000。同时数据库对应的biz_tag这条数据的max_id会从3000被更新成4000,更新号段的SQL语句如下:

    UPDATE table SET max_id=max_id+step WHERE biz_tag=xxx
    SELECT tag, max_id, step FROM table WHERE biz_tag=xxx

    实时监控:

    优点:

    • ID号码是趋势递增的8byte的64位数字,满足上述数据库存储的主键要求。
    • 容灾性高:Leaf服务内部有号段缓存,即使DB宕机,短时间内Leaf仍能正常对外提供服务。
    • 可以自定义max_id的大小,多个服务器不会出现主键重复。
    • 热加载序列分类,Schedule定时器。
    • 加锁控制,线程安全。

    缺点:

    • 初始化策略不友好,服务重启后id可能会由10跳到2001。
    • 当号段使用完之后还是会hang在更新数据库的I/O上,tg999数据会出现偶尔的尖刺。

    Snowflake模式

    image

    弱依赖ZooKeeper

    除了每次会去ZK拿数据以外,也会在本机文件系统上缓存一个workerID文件。当ZooKeeper出现问题,恰好机器出现问题需要重启时,能保证服务能够正常启动。这样做到了对三方组件的弱依赖。一定程度上提高了SLA

    解决时钟问题

    因为这种方案依赖时间,如果机器的时钟发生了回拨,那么就会有可能生成重复的ID号,需要解决时钟回退的问题。

    由于强依赖时钟,对时间的要求比较敏感,在机器工作时NTP同步也会造成秒级别的回退,建议可以直接关闭NTP同步。要么在时钟回拨的时候直接不提供服务直接返回ERROR_CODE,等时钟追上即可。或者做一层重试,然后上报报警系统,更或者是发现有时钟回拨之后自动摘除本身节点并报警,如下:

    public synchronized Result get(String key) {
            long timestamp = timeGen();
            if (timestamp < lastTimestamp) {
                long offset = lastTimestamp - timestamp;
                if (offset <= 5) {
                    try {
                        wait(offset << 1);
                        timestamp = timeGen();
                        if (timestamp < lastTimestamp) {
                            return new Result(-1, Status.EXCEPTION);
                        }
                    } catch (InterruptedException e) {
                        LOGGER.error("wait interrupted");
                        return new Result(-2, Status.EXCEPTION);
                    }
                } else {
                    return new Result(-3, Status.EXCEPTION);
                }
            }
            if (lastTimestamp == timestamp) {
                sequence = (sequence + 1) & sequenceMask;
                if (sequence == 0) {
                    //seq 为0的时候表示是下一毫秒时间开始对seq做随机
                    sequence = RANDOM.nextInt(100);
                    timestamp = tilNextMillis(lastTimestamp);
                }
            } else {
                //如果是新的ms开始
                sequence = RANDOM.nextInt(100);
            }
            lastTimestamp = timestamp;
         //时间戳偏移+workId偏移+随机数
    long id = ((timestamp - twepoch) << timestampLeftShift) | (workerId << workerIdShift) | sequence; return new Result(id, Status.SUCCESS); }
  • 相关阅读:
    JAVA LinkedList和ArrayList的使用及性能分析
    学习笔记—Node中的模块调试
    学习笔记—Node的核心模块
    学习笔记—Node中VM模块详解
    学习笔记—Node中require的实现
    入园了
    【引用】asp.net服务器推送(ServerPush)和客户端拉拽技术
    ajax xmlHttp.responseXML取不到值问题备忘
    oracle实时插值速度突然变慢问题解决办法
    [转帖 作者: fuyuncat 来源: www.HelloDBA.com ]Oracle IO问题解析
  • 原文地址:https://www.cnblogs.com/sjp007/p/10495490.html
Copyright © 2011-2022 走看看