引入依赖:
com.robert.vesta
vesta-service
0.0.1
com.robert.vesta
vesta-intf
0.0.1
jar包需要自己根据源码打包:源码地址:https://github.com/iweisi/vesta-id-generator
也可以下载:https://download.csdn.net/download/lyf_ldh/11303673
或者留言说明
vesta-rest.properties配置:
vesta.machine=1021 # 机器ID
vesta.genMethod=0 # 生成方式,0表示 嵌入发布模式 1表示 中心服务器发布模式 2表示 REST发 布模式
vesta.type=1 # ID类型,1表示最小粒度型 0表示最大峰值
引入 vesta-rest-main.xml
创建一个 Config配置类来将 vesta-rest-main.xml:
@Configuration
@ImportResource( locations = { "classpath:ext/vesta/vesta-rest-main.xml" } )
public class UidConfig {
}
这里面包含的是和 ID生成器相关的几个重要工具接口,主要有:
genId
生成全局唯一 ID号explainId
反解全局唯一 ID号,得到可以解释 ID号含义的 JSON数据makeId
手工制造 ID@Service
public class UidService {
@Resource
private IdService idService;
public long genId() {
return idService.genId();
}
public Id explainId( long id ) {
return idService.expId(id);
}
public long makeId( long version, long type, long genMethod, long machine, long time, long seq ) {
long madeId = -1;
if (time == -1 || seq == -1)
throw new IllegalArgumentException( "Both time and seq are required." );
else if (version == -1) {
if (type == -1) {
if (genMethod == -1) {
if (machine == -1) {
madeId = idService.makeId(time, seq);
} else {
madeId = idService.makeId(machine, time, seq);
}
} else {
madeId = idService.makeId(genMethod, machine, time, seq);
}
} else {
madeId = idService.makeId(type, genMethod, machine, time, seq);
}
} else {
madeId = idService.makeId(version, type, genMethod, time,
seq, machine);
}
return madeId;
}
}
编写测试 Controller:
@RestController
public class UidController {
@Autowired
private UidService uidService;
@RequestMapping(value = "/genid",method = RequestMethod.GET)
public long genId() {
return uidService.genId();
}
@RequestMapping(value ="/expid/{id}",method = RequestMethod.GET)
public Id explainId(@PathVariable("id") long id) {
return uidService.explainId( id );
}
@RequestMapping(value ="/makeid",method = RequestMethod.GET)
public long makeId(
@RequestParam(value = "version", defaultValue = "-1") long version,
@RequestParam(value = "type", defaultValue = "-1") long type,
@RequestParam(value = "genMethod", defaultValue = "-1") long genMethod,
@RequestParam(value = "machine", defaultValue = "-1") long machine,
@RequestParam(value = "time", defaultValue = "-1") long time,
@RequestParam(value = "seq", defaultValue = "-1") long seq) {
return uidService.makeId( version, type, genMethod, machine, time, seq );
}
}
Vesta 0.0.2 版本
Vesta 0.0.1 版本已经能提供高效服务,但是对服务器的时钟有一定的要求,如果出现时钟回拨,Vesta 在此期间会拒绝提供服务。
所以 0.0.2 版本首先从解决该问题出发,然后又完善了各个接口的自定义配置和主键元数据的细粒度配置。
1. 通过切换机器ID解决时钟回拨
解决思路:
如果我们针对一个提供服务的Vesta配置多个机器ID,那么当出现时钟回拨时,可以按照机器ID的顺序和一定的规则切换当前的机器ID。这样就能通过机器ID位的不同来避免主键重复。虽然在整体的顺序上会因为时间位出现倒退,对大多数系统来说没有任何影响。
所以这种思路就是为了保证主键不重复,忽略时间对整体顺序的影响。vesta-rest-main.xml
配置:
MachineIdsIdServiceImpl 的功能不只是简单的切换机器ID,在每次切换的时候还会把当前的机器ID和最近的一个时间戳保存到本地文件中,因此当所有机器ID都使用一轮后。本地文件中记录了所有机器ID以及最后使用的时间戳。当时钟再次出现回拨时,会根据这些信息找到一个满足 最后使用的时间戳 < 当前时间戳 的机器 ID。使用这种方式时,基本可以避免各种情况的时钟回拨。
2. 自定义主键配置(IdMeta)
Vesta 原来提供了两种可选的类型,根据时间的位数和序列号的位数,可分为最大峰值型和最小粒度型。
public enum IdType {
MAX_PEAK("max-peak"),
MIN_GRANULARITY("min-granularity");
private String name;
private IdType(String name) {
this.name = name;
}
......
}
1. 最大峰值型(seconds):采用秒级有序,秒级时间占用30位,序列号占用20位
字段 |
版本 |
类型 |
生成方式 |
秒级时间 |
序列号 |
机器ID |
---|---|---|---|---|---|---|
位数 | 63 | 62 | 60-61 | 30-59 | 10-29 | 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
(毫秒)来选择上面两种类型。
除了默认的两种方式外,还可以更灵活的自定义,自定义方式如下:
上述配置中 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 的缘故,该配置已经过期了,在启动时会抛出异常)。
自定义实现
Vesta 提供了以下接口,都可以通过配置替换为自己的实现。
IdConvert 接口,根据 IdMeta 和 Id 的信息转换为 Long 类型的值。
IdPopulator 接口,用于根据 Timer 生成 Id 的基础数据。
ResetPopulator 接口,配合 IdPopulator 使用,当使用可切换机器ID的方式运行时,用于重置 IdPopulator 的状态。
MachineIdProvider 和 MachineIdsProvider 接口,用于提供机器 ID,第二个用于可切换机器ID时使用。
Timer 接口,处理时间戳生成的方式。
源码分析:
IdServiceImpl通过构造调用父类的构造:
public IdServiceImpl(String type) {
super(type);
this.initPopulator();
}
父类构造:
public AbstractIdServiceImpl(String type) {
this.idType = IdType.parse(type);
}
init-method使用的是父类的方法:
public void init() {
this.machineId = this.machineIdProvider.getMachineId();
if (this.machineId < 0L) {
this.log.error("The machine ID is not configured properly so that Vesta Service refuses to start.");
throw new IllegalStateException("The machine ID is not configured properly so that Vesta Service refuses to start.");
} else {
if (this.idMeta == null) {
this.setIdMeta(IdMetaFactory.getIdMeta(this.idType));
this.setType(this.idType.value());
} else if (this.idMeta.getTimeBits() == 30) {
this.setType(0L);
} else {
if (this.idMeta.getTimeBits() != 40) {
throw new RuntimeException("Init Error. The time bits in IdMeta should be set to 30 or 40!");
}
this.setType(1L);
}
this.setIdConverter(new IdConverterImpl(this.idMeta));
}
}
这个是版本0.0.1中的方法,0.0.2中会对这个方法做改进,对else if增加自定义的判断和对枚举类IdType增加自定类型,这样就能够使用自定义的IdMeta,暂时没找到0.0.2版本,自己根据需要可以对源码进行改进,重新编译为jar包。