全局唯一id生成器----Vesta

引入依赖:

		<dependency>
			<groupId>com.robert.vesta</groupId>
			<artifactId>vesta-service</artifactId>
			<version>0.0.1</version>
		</dependency>
		<dependency>
			<groupId>com.robert.vesta</groupId>
			<artifactId>vesta-intf</artifactId>
			<version>0.0.1</version>
		</dependency>

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

<?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
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:vesta-rest.properties"/>
  </bean>

  <bean id="idService" class="com.robert.vesta.service.factory.IdServiceFactoryBean"
    init-method="init">
    <property name="providerType" value="PROPERTY"/>
    <property name="type" value="${vesta.type}"/>
    <property name="genMethod" value="${vesta.genMethod}"/>
    <property name="machineId" value="${vesta.machine}"/>
  </bean>

</beans>

创建一个 Config配置类来将 vesta-rest-main.xml:

@Configuration
@ImportResource( locations = { "classpath:ext/vesta/vesta-rest-main.xml" } )
public class UidConfig {
}

编写 Vesta Service

这里面包含的是和 ID生成器相关的几个重要工具接口,主要有:

  • genId 生成全局唯一 ID号
  • explainId 反解全局唯一 ID号,得到可以解释 ID号含义的 JSON数据
  • makeId 手工制造 ID
  • transTime对产生的日期反解
@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配置:

<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 原来提供了两种可选的类型,根据时间的位数和序列号的位数,可分为最大峰值型和最小粒度型。

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
位数636260-6130-5910-290-9

2. 最小粒度型(milliseconds):采用毫秒级有序,毫秒级时间占用40位,序列号占用10位

字段版本类型生成方式毫秒级时间序列号机器ID
位数636260-6120-5910-190-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="max-peak"/>
        <property name="machineIdProvider" ref="propertyMachineIdProvider"/>
    </bean>


    <bean id="propertyMachineIdProvider" 
          class="com.robert.vesta.service.impl.provider.PropertyMachineIdProvider">
        <property name="machineId" value="1"/>
    </bean>

</beans>

除了默认的两种方式外,还可以更灵活的自定义,自定义方式如下:

<?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">
        <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
位数636260-6130-5916-290-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包。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值