Vesta 发号器是一个通用的发号器,它不但可以嵌入在原生 Java 程序中,还可以作为 Restful 服务进行发布,你只需要简单的几个步骤就可以成功搭建 Vesta 的 Rest 服务,并且在任何语言中都可以使用 HTTP 协议来获取全局唯一的 ID。
关于 Vesta 的基本文档可以参考 Vesta 官网的介绍:
目前(20180330) Vesta 官网还是 0.0.1 版本的用法和介绍,本文主要介绍的是未来 0.0.2 版本增加和修改的各项内容。如果你已经在使用或者正在寻找一款稳定的发号器,来试试 Vesta。
Vesta 0.0.2 版本
Vesta 0.0.1 版本已经能提供高效服务,但是对服务器的时钟有一定的要求,如果出现时钟回拨,Vesta 在此期间会拒绝提供服务。
所以 0.0.2 版本首先从解决该问题出发,然后又完善了各个接口的自定义配置和主键元数据的细粒度配置。
1. 通过切换机器ID解决时钟回拨
解决思路:
如果我们针对一个提供服务的Vesta配置多个机器ID,那么当出现时钟回拨时,可以按照机器ID的顺序和一定的规则切换当前的机器ID。这样就能通过机器ID位的不同来避免主键重复。虽然在整体的顺序上会因为时间位出现倒退,对大多数系统来说没有任何影响。
所以这种思路就是为了保证主键不重复,忽略时间对整体顺序的影响。
保证时间有序可以采用缓存来解决,但是对于极低概率的时钟回拨来说,会导致频繁刷新缓存,缓存命中率极低,所以没有提供这种实现。
想要使用切换 ID 的这种策略,可以通过下面的配置启用:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="idService" class="com.robert.vesta.service.impl.MachineIdsIdServiceImpl"
init-method="init">
<property name="machineIdProvider" ref="propertyMachineIdsProvider"/>
</bean>
<bean id="propertyMachineIdsProvider"
class="com.robert.vesta.service.impl.provider.PropertyMachineIdsProvider">
<property name="machineIds" value="1,2,3,4,5"/>
</bean>
</beans>
MachineIdsIdServiceImpl 的功能不只是简单的切换机器ID,在每次切换的时候还会把当前的机器ID和最近的一个时间戳保存到本地文件中,因此当所有机器ID都使用一轮后。本地文件中记录了所有机器ID以及最后使用的时间戳。当时钟再次出现回拨时,会根据这些信息找到一个满足 最后使用的时间戳 < 当前时间戳 的机器 ID。使用这种方式时,基本可以避免各种情况的时钟回拨。
2. 自定义主键配置(IdMeta
)
Vesta 原来提供了两种可选的类型,根据时间的位数和序列号的位数,可分为最大峰值型和最小粒度型。
1. 最大峰值型(seconds):采用秒级有序,秒级时间占用30位,序列号占用20位
字段 |
版本 |
类型 |
生成方式 |
秒级时间 |
序列号 |
机器ID |
---|---|---|---|---|---|---|
位数 | 63 | 62 | 60-61 | 40-59 | 10-39 | 0-9 |
2. 最小粒度型(milliseconds):采用毫秒级有序,毫秒级时间占用40位,序列号占用10位
字段 |
版本 |
类型 |
生成方式 |
毫秒级时间 |
序列号 |
机器ID |
---|---|---|---|---|---|---|
位数 | 63 | 62 | 60-61 | 20-59 | 10-19 | 0-9 |
最大峰值型能够承受更大的峰值压力,但是粗略有序的粒度有点大,最小粒度型有较细致的粒度,但是每个毫秒能承受的理论峰值有限,为1k,同一个毫秒如果有更多的请求产生,必须等到下一个毫秒再响应。
ID类型在配置时指定,需要重启服务才能互相切换。
0.0.2 版本后,可以通过设置 type
为 seconds
(秒) 或 milliseconds
(毫秒)来选择上面两种类型。除了默认的两种方式外,还可以更灵活的自定义,自定义方式如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="idService" class="com.robert.vesta.service.impl.IdServiceImpl"
init-method="init">
<constructor-arg value="seconds"/>
<property name="machineIdProvider" ref="propertyMachineIdProvider"/>
<property name="idMeta" ref="idMeta"/>
</bean>
<bean id="idMeta" class="com.robert.vesta.service.impl.bean.IdMeta">
<constructor-arg value="16"/>
<constructor-arg value="14"/>
<constructor-arg value="30"/>
<constructor-arg value="2"/>
<constructor-arg value="1"/>
<constructor-arg value="1"/>
</bean>
<bean id="propertyMachineIdProvider"
class="com.robert.vesta.service.impl.provider.PropertyMachineIdProvider">
<property name="machineId" value="1"/>
</bean>
</beans>
上述配置中 IdMeta 自定义配置如下:
字段 |
版本 |
类型 |
生成方式 |
秒级时间 |
序列号 |
机器ID |
---|---|---|---|---|---|---|
位数 | 63 | 62 | 60-61 | 30-59 | 16-29 | 0-15 |
特别注意
在选择 seconds
时,如果时间位是 30,使用(1<<30-1)/(365*24*3600)
可以计算出当前配置可以使用多少年(此例是34年,还需要考虑 Timer 中定义的 EPOCH
,这个值 0.0.2 中也能自定义了)。
在选择 milliseconds
时,如果时间位是 30,使用(1<<30-1)/(365*24*3600000)
可以计算出当前配置可以使用多少年(此例是 298 小时,只够使用 12 天,并且由于 EPOCH 的缘故,该配置已经过期了,在启动时会抛出异常)。
计算单机最大理论峰值
例如这里的序列号是 14位,通过 1<<14-1
可以计算出最大峰值为 16383
。
总位数没有限制(<64)
默认提供的两种方式和这里的例子,都采用了64位的ID,实际上通过自定义时,不是必须满足 64 位,通过缩短机器ID、序列号位可以让最终生成的 Long 类型更短。
如果想让生成的 Long 缩小 1000 倍,也就是短 3 位,可以通过 2 的次方计算要缩短几位,缩小1000倍时,最接近的值为 1024,也就是 IdMeta 整体要缩短 10 位才能保证生成的 Long 短 3 位。
当你想要自定义时,一定要根据自己的业务需要和时间、峰值的预期来进行配置。
3. 启动时针对当前配置给出提醒
由于 IdMeta
可以自定义配置了,因此为了避免配置不合理,Vesta 在启动时会输出类似下面的信息:
The current time bit length is 30, the expiration date is Sat Jan 09 13:37:03 CST 2049, this can be used for 11251 days.
如果配置的时间已经过期,会有类似下面的错误信息,并且会终止 Vesta 的运行:
The current timestamp (101641074191 >= 1073741823) has overflowed, Vesta Service will be terminate.
这个提示不仅仅启动时有效,在运行期间如果时间位已经用完,也会抛出相同的异常信息,并且终止运行。
4. 自定义实现
Vesta 提供了以下接口,都可以通过配置替换为自己的实现。
- IdConvert 接口,根据 IdMeta 和 Id 的信息转换为 Long 类型的值。
- IdPopulator 接口,用于根据 Timer 生成 Id 的基础数据。
- ResetPopulator 接口,配合 IdPopulator 使用,当使用可切换机器ID的方式运行时,用于重置 IdPopulator 的状态。
- MachineIdProvider 和 MachineIdsProvider 接口,用于提供机器 ID,第二个用于可切换机器ID时使用。
- Timer 接口,处理时间戳生成的方式。
5. 增加 zookeeper 和 zkclient 依赖
相关代码:
后续 0.0.2 版本打包的时候会包含这两个依赖,因此如果使用 dubbo + zookeeper 提供服务,可以直接配置注册中心。