SnowFlake 高可用的ID 生产方案
SnowFlake
Twitter的雪花算法SnowFlake,使用Java语言实现。
SnowFlake算法用来生成64位的ID,刚好可以用long整型存储,能够用于分布式系统中生产唯一的ID, 并且生成的ID有大致的顺序。 在这次实现中,生成的64位ID可以分成5个部分:
0 - 41位时间戳 - 5位数据中心标识 - 5位机器标识 - 12位序列号
5位数据中心标识跟5位机器标识这样的分配仅仅是当前实现中分配的,如果业务有其实的需要,可以按其它的分配比例分配,如10位机器标识,不需要数据中心标识。
具体说明可以参考文章: http://www.wolfbe.com/detail/201611/381.html
/** * twitter的snowflake算法 -- java实现 * * @author beyond * @date 2016/11/26 */ public class SnowFlake { /** * 起始的时间戳 */ private final static long START_STMP = 1480166465631L; /** * 每一部分占用的位数 */ private final static long SEQUENCE_BIT = 12; //序列号占用的位数 private final static long MACHINE_BIT = 5; //机器标识占用的位数 private final static long DATACENTER_BIT = 5;//数据中心占用的位数 /** * 每一部分的最大值 */ private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT); private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT); private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); /** * 每一部分向左的位移 */ private final static long MACHINE_LEFT = SEQUENCE_BIT; private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT; private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT; private long datacenterId; //数据中心 private long machineId; //机器标识 private long sequence = 0L; //序列号 private long lastStmp = -1L;//上一次时间戳 public SnowFlake(long datacenterId, long machineId) { if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) { throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0"); } if (machineId > MAX_MACHINE_NUM || machineId < 0) { throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0"); } this.datacenterId = datacenterId; this.machineId = machineId; } /** * 产生下一个ID * * @return */ public synchronized long nextId() { long currStmp = getNewstmp(); if (currStmp < lastStmp) { throw new RuntimeException("Clock moved backwards. Refusing to generate id"); } if (currStmp == lastStmp) { //相同毫秒内,序列号自增 sequence = (sequence + 1) & MAX_SEQUENCE; //同一毫秒的序列数已经达到最大 if (sequence == 0L) { currStmp = getNextMill(); } } else { //不同毫秒内,序列号置为0 sequence = 0L; } lastStmp = currStmp; return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分 | datacenterId << DATACENTER_LEFT //数据中心部分 | machineId << MACHINE_LEFT //机器标识部分 | sequence; //序列号部分 } private long getNextMill() { long mill = getNewstmp(); while (mill <= lastStmp) { mill = getNewstmp(); } return mill; } private long getNewstmp() { return System.currentTimeMillis(); } public static void main(String[] args) { SnowFlake snowFlake = new SnowFlake(2, 3); for (int i = 0; i < (1 << 12); i++) { System.out.println(snowFlake.nextId()); } } }
美团 leaf
https://github.com/Meituan-Dianping/leaf
# Leaf > There are no two identical leaves in the world. > > 世界上没有两片完全相同的树叶。 > > — 莱布尼茨 [中文文档](./README_CN.md) | [English Document](./README.md) ## Introduction Leaf 最早期需求是各个业务线的订单ID生成需求。在美团早期,有的业务直接通过DB自增的方式生成ID,有的业务通过redis缓存来生成ID,也有的业务直接用UUID这种方式来生成ID。以上的方式各自有各自的问题,因此我们决定实现一套分布式ID生成服务来满足需求。具体Leaf 设计文档见:[ leaf 美团分布式ID生成服务 ](https://tech.meituan.com/MT_Leaf.html ) 目前Leaf覆盖了美团点评公司内部金融、餐饮、外卖、酒店旅游、猫眼电影等众多业务线。在4C8G VM基础上,通过公司RPC方式调用,QPS压测结果近5w/s,TP999 1ms。 ## Quick Start ### 使用starter注解启动leaf https://github.com/Meituan-Dianping/Leaf/blob/feature/spring-boot-starter/README_CN.md ### Leaf Server 我们提供了一个基于spring boot的HTTP服务来获取ID #### 配置介绍 Leaf 提供两种生成的ID的方式(号段模式和snowflake模式),你可以同时开启两种方式,也可以指定开启某种方式(默认两种方式为关闭状态)。 Leaf Server的配置都在leaf-server/src/main/resources/leaf.properties中 | 配置项 | 含义 | 默认值 | | ------------------------- | ----------------------------- | ------ | | leaf.name | leaf 服务名 | | | leaf.segment.enable | 是否开启号段模式 | false | | leaf.jdbc.url | mysql 库地址 | | | leaf.jdbc.username | mysql 用户名 | | | leaf.jdbc.password | mysql 密码 | | | leaf.snowflake.enable | 是否开启snowflake模式 | false | | leaf.snowflake.zk.address | snowflake模式下的zk地址 | | | leaf.snowflake.port | snowflake模式下的服务注册端口 | | #### 号段模式 如果使用号段模式,需要建立DB表,并配置leaf.jdbc.url, leaf.jdbc.username, leaf.jdbc.password 如果不想使用该模式配置leaf.segment.enable=false即可。 ##### 创建数据表 ```sql CREATE DATABASE leaf CREATE TABLE `leaf_alloc` ( `biz_tag` varchar(128) NOT NULL DEFAULT '', `max_id` bigint(20) NOT NULL DEFAULT '1', `step` int(11) NOT NULL, `description` varchar(256) DEFAULT NULL, `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`biz_tag`) ) ENGINE=InnoDB; insert into leaf_alloc(biz_tag, max_id, step, description) values('leaf-segment-test', 1, 2000, 'Test leaf Segment Mode Get Id') ``` ##### 配置相关数据项 在leaf.properties中配置leaf.jdbc.url, leaf.jdbc.username, leaf.jdbc.password参数 #### Snowflake模式 算法取自twitter开源的snowflake算法。 如果不想使用该模式配置leaf.snowflake.enable=false即可。 ##### 配置zookeeper地址 在leaf.properties中配置leaf.snowflake.zk.address,配置leaf 服务监听的端口leaf.snowflake.port。 #### 运行Leaf Server ##### 打包服务 ```shell git clone git@github.com:Meituan-Dianping/Leaf.git //按照上面的号段模式在工程里面配置好 cd leaf mvn clean install -DskipTests cd leaf-server ``` ##### 运行服务 *注意:首先得先配置好数据库表或者zk地址* ###### mvn方式 ```shell mvn spring-boot:run ``` ###### 脚本方式 ```shell sh deploy/run.sh ``` ##### 测试 ```shell #segment curl http://localhost:8080/api/segment/get/leaf-segment-test #snowflake curl http://localhost:8080/api/snowflake/get/test ``` ##### 监控页面 号段模式:http://localhost:8080/cache ### Leaf Core 当然,为了追求更高的性能,需要通过RPC Server来部署Leaf 服务,那仅需要引入leaf-core的包,把生成ID的API封装到指定的RPC框架中即可。 ### 注意事项 注意现在leaf使用snowflake模式的情况下 其获取ip的逻辑直接取首个网卡ip【特别对于会更换ip的服务要注意】避免浪费workId