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

引入依赖:

		
			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 {
}

编写 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配置:



    
        
    

    
        
    

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包。

你可能感兴趣的:(高并发与分布式)