不能错过的分布式ID生成器(Leaf-(1),2021年五面蚂蚁

* 雪花算法模式
     * @param key
     * @return
     */
    @RequestMapping(value = “/api/snowflake/get/{key}”)
    public String getSnowflakeId(@PathVariable(“key”) String key) {
        return get(key, snowflakeService.getId(key));
    }

private String get(@PathVariable(“key”) String key, Result id) {
        Result result;
        if (key == null || key.isEmpty()) {
            throw new NoKeyException();
        }
        result = id;
        if (result.getStatus().equals(Status.EXCEPTION)) {
            throw new LeafServerException(result.toString());
        }
        return String.valueOf(result.getId());
    }
}

访问:`http://127.0.0.1:8080/api/segment/get/leaf-segment-test`,结果正常返回,感觉没毛病,但当查了一下数据库表中数据时发现了一个问题。![](https://upload-images.jianshu.io/upload_images/24195226-c4b0cf5741e07497.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

![](https://upload-images.jianshu.io/upload_images/24195226-8d588bfb7301e49c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


 通常在用号段模式的时候,取号段的时机是在前一个号段消耗完的时候进行的,可刚刚才取了一个ID,数据库中却已经更新了`max_id`,也就是说`leaf`已经多获取了一个号段,这是什么鬼操作?![](https://upload-images.jianshu.io/upload_images/24195226-ee84a1cac4cb70e6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


**`Leaf`为啥要这么设计呢?**

`Leaf` 希望能在DB中取号段的过程中做到无阻塞!

当号段耗尽时再去DB中取下一个号段,如果此时网络发生抖动,或者DB发生慢查询,业务系统拿不到号段,就会导致整个系统的响应时间变慢,对流量巨大的业务,这是不可容忍的。

所以`Leaf`在当前号段消费到某个点时,就异步的把下一个号段加载到内存中。而不需要等到号段用尽的时候才去更新号段。这样做很大程度上的降低了系统的风险。

**那么`某个点`到底是什么时候呢?**

这里做了一个实验,号段设置长度为`step=10`,`max_id=1`![](https://upload-images.jianshu.io/upload_images/24195226-d7fa46e3cc4d7507.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


 当我拿第一个ID时,看到号段增加了,1/10![](https://upload-images.jianshu.io/upload_images/24195226-b8636c64173fa09f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


![](https://upload-images.jianshu.io/upload_images/24195226-1fd16cc3bebc47e2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

 当我拿第三个Id时,看到号段又增加了,3/10 ![](https://upload-images.jianshu.io/upload_images/24195226-af6967c1697878d3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


 ![](https://upload-images.jianshu.io/upload_images/24195226-da0cae72b23cb38a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


 `Leaf`采用`双buffer`的方式,它的服务内部有两个号段缓存区`segment`。当前号段已消耗10%时,还没能拿到下一个号段,则会另启一个更新线程去更新下一个号段。

简而言之就是`Leaf`保证了总是会多缓存两个号段,即便哪一时刻数据库挂了,也会保证发号服务可以正常工作一段时间。

![](https://upload-images.jianshu.io/upload_images/24195226-1820a430c0378709.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


 通常推荐号段(`segment`)长度设置为服务高峰期发号QPS的600倍(10分钟),这样即使DB宕机,Leaf仍能持续发号10-20分钟不受影响。

**优点:**

*   Leaf服务可以很方便的线性扩展,性能完全能够支撑大多数业务场景。
*   容灾性高:Leaf服务内部有号段缓存,即使DB宕机,短时间内Leaf仍能正常对外提供服务。

**缺点:**

*   ID号码不够随机,能够泄露发号数量的信息,不太安全。
*   DB宕机会造成整个系统不可用(用到数据库的都有可能)。

#### 二、Leaf-snowflake

`Leaf-snowflake`基本上就是沿用了snowflake的设计,ID组成结构:`正数位`(占1比特)+ `时间戳`(占41比特)+ `机器ID`(占5比特)+ `机房ID`(占5比特)+ `自增值`(占12比特),总共64比特组成的一个Long类型。

`Leaf-snowflake`不同于原始snowflake算法地方,主要是在workId的生成上,`Leaf-snowflake`依靠`Zookeeper`生成`workId`,也就是上边的`机器ID`(占5比特)+ `机房ID`(占5比特)。`Leaf`中workId是基于ZooKeeper的`顺序Id`来生成的,每个应用在使用Leaf-snowflake时,启动时都会都在Zookeeper中生成一个顺序Id,相当于一台机器对应一个顺序节点,也就是一个workId。
![](https://upload-images.jianshu.io/upload_images/24195226-6f390f068592a680.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

 `Leaf-snowflake`启动服务的过程大致如下:

*   启动Leaf-snowflake服务,连接Zookeeper,在leaf_forever父节点下检查自己是否已经注册过(是否有该顺序子节点)。
*   如果有注册过直接取回自己的workerID(zk顺序节点生成的int类型ID号),启动服务。
*   如果没有注册过,就在该父节点下面创建一个持久顺序节点,创建成功后取回顺序号当做自己的workerID号,启动服务。

但`Leaf-snowflake`对Zookeeper是一种弱依赖关系,除了每次会去ZK拿数据以外,也会在本机文件系统上缓存一个`workerID`文件。一旦ZooKeeper出现问题,恰好机器出现故障需重启时,依然能够保证服务正常启动。

启动`Leaf-snowflake`模式也比较简单,起动本地ZooKeeper,修改一下项目中的`leaf.properties`文件,关闭`leaf.segment模式`,启用`leaf.snowflake`模式即可。

leaf.segment.enable=false
#leaf.jdbc.url=jdbc:mysql://127.0.0.1:3306/xin-master?useUnicode=true&characterEncoding=utf8
#leaf.jdbc.username=junkang
#leaf.jdbc.password=junkang

leaf.snowflake.enable=true
leaf.snowflake.zk.address=127.0.0.1
leaf.snowflake.port=2181


/**
     * 雪花算法模式
     * @param key
     * @return
     */
    @RequestMapping(value = “/api/snowflake/get/{key}”)
    public String getSnowflakeId(@PathVariable(“key”) String key) {
        return get(key, snowflakeService.getId(key));
    }


测试一下,访问:`http://127.0.0.1:8080/api/snowflake/get/leaf-segment-test`

![](https://img-blog.csdnimg.cn/img_convert/a71a56bbade873aae1e4e6891c74c195.png)


 **优点:**

*   ID号码是趋势递增的8byte的64位数字,满足上述数据库存储的主键要求。

**缺点:**

*   依赖ZooKeeper,存在服务不可用风险(实在不知道有啥缺点了)

### 三、Leaf监控

请求地址:`http://127.0.0.1:8080/cache`

针对服务自身的监控,Leaf提供了Web层的内存数据映射界面,可以实时看到所有号段的下发状态。比如每个号段双buffer的使用情况,当前ID下发到了哪个位置等信息都可以在Web界面上查看。



### 最后

**我还通过一些渠道整理了一些大厂真实面试主要有:蚂蚁金服、拼多多、阿里云、百度、唯品会、携程、丰巢科技、乐信、软通动力、OPPO、银盛支付、中国平安等初,中级,高级Java面试题集合,附带超详细答案,希望能帮助到大家。**

**[资料领取方式:戳这里免费下载](https://gitee.com/vip204888/java-p7)**

![新鲜出炉的蚂蚁金服面经,熬夜整理出来的答案,已有千人收藏](https://img-blog.csdnimg.cn/img_convert/470967199b426c7b8f63ac6d4c6362af.png)

**还有专门针对JVM、SPringBoot、SpringCloud、数据库、Linux、缓存、消息中间件、源码等相关面试题。**

![新鲜出炉的蚂蚁金服面经,熬夜整理出来的答案,已有千人收藏](https://img-blog.csdnimg.cn/img_convert/272189bc53725870642191272149e844.png)

..(img-JfDKYSB4-1628607810823)]

**还有专门针对JVM、SPringBoot、SpringCloud、数据库、Linux、缓存、消息中间件、源码等相关面试题。**

[外链图片转存中...(img-y6UH6gmR-1628607810825)]

# 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值