最高位是符号位,始终为0,不可用。
41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截) 后得到的值,这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序SnowFlake类的START_STMP属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
10位的机器标识,10位的长度最多支持部署1024个节点。
12位的计数序列号,序列号即一系列的自增id,可以支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号。
加起来刚好64位,为一个Long型。这个算法很简洁,但依旧是一个很好的ID生成策略。其中,10位器标识符一般是5位IDC+5位machine编号,唯一确定一台机器。
算法实现
public classSnowFlake { //起始的时间戳 private final static long START_STMP = 1577808000000L; //2020-01-01 //每一部分占用的位数,就三个 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, longmachineId) { 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 public synchronized longnextId() { long currStmp =timeGen(); if (currStmp <lastStmp) { throw new RuntimeException("Clock moved backwards. Refusing to generate id"); } if (currStmp ==lastStmp) { //if条件里表示当前调用和上一次调用落在了相同毫秒内,只能通过第三部分,序列号自增来判断为唯一,所以+1. sequence = (sequence + 1) &MAX_SEQUENCE; //同一毫秒的序列数已经达到最大,只能等待下一个毫秒 if (sequence == 0L) { currStmp =getNextMill(); } } else{ //不同毫秒内,序列号置为0 //执行到这个分支的前提是currTimestamp > lastTimestamp,说明本次调用跟上次调用对比,已经不再同一个毫秒内了,这个时候序号可以重新回置0了。 sequence = 0L; } lastStmp =currStmp; //就是用相对毫秒数、机器ID和自增序号拼接 return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分 | datacenterId << DATACENTER_LEFT //数据中心部分 | machineId << MACHINE_LEFT //机器标识部分 | sequence; //序列号部分 } private longgetNextMill() { long mill =timeGen(); while (mill <=lastStmp) { mill =timeGen(); } returnmill; } private longtimeGen() { returnSystem.currentTimeMillis(); } }
当增加一秒生成ID的时候就是增加10位的机器标识+12位序列+约2的10次方(1000毫秒),最终就是增加一个2的32次方4 294 967 296就是42亿左右
但是这里有一个坑,雪花算法产生的长整数的精度可能超过javascript能表达的精度,这会导致js获取的id与雪花算法算出来的id不一致,如雪花算法得到的是36594866121080832,但是因为javascript丢失精度后只获取到36594866121080830, 这会导致对数据的所有操作都失效。
解决办法:后端的语言获取到雪花算法的id后将其转换为String类型,这样js也会当做字符串来处理,就不会丢失精度了。
配置方法
@Configuration public class WebMvcConfig implementsWebMvcConfigurer { @Autowired public void configureMessageConverters(List<HttpMessageConverter<?>>converters) { converters.add(toStringConverter()); } /*** BigDecimal Long 转化为String * * @return */@Bean publicMappingJackson2HttpMessageConverter toStringConverter() { MappingJackson2HttpMessageConverter converter = newMappingJackson2HttpMessageConverter(); ObjectMapper mapper = newObjectMapper(); SimpleModule simpleModule = newSimpleModule(); simpleModule.addSerializer(BigDecimal.class, BigDecimalToStringSerializer.instance); simpleModule.addSerializer(Long.class, ToStringSerializer.instance); simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); simpleModule.addSerializer(long.class, ToStringSerializer.instance); mapper.registerModule(simpleModule);
// Include.Include.ALWAYS 默认
// Include.NON_DEFAULT 属性为默认值不序列化
// Include.NON_EMPTY 属性为 空("") 或者为 NULL 都不序列化,则返回的json是没有这个字段的。这样对移动端会更省流量
// Include.NON_NULL 属性为NULL 不序列化
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);// 允许出现特殊字符和转义符
mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); // 允许出现单引号
converter.setObjectMapper(mapper);
returnconverter; } @JacksonStdImpl static class BigDecimalToStringSerializer extendsToStringSerializer { public final static BigDecimalToStringSerializer instance = newBigDecimalToStringSerializer(); publicBigDecimalToStringSerializer() { super(Object.class); } public BigDecimalToStringSerializer(Class<?>handledType) { super(handledType); } @Override public booleanisEmpty(SerializerProvider prov, Object value) { if (value == null) { return true; } String str =((BigDecimal) value).stripTrailingZeros().toPlainString(); returnstr.isEmpty(); } @Override public voidserialize(Object value, JsonGenerator gen, SerializerProvider provider) throwsIOException { gen.writeString(((BigDecimal) value).stripTrailingZeros().toPlainString()); } @Override public JsonNode getSchema(SerializerProvider provider, Type typeHint) throwsJsonMappingException { return createSchemaNode("string", true); } @Override public voidserializeWithType(Object value, JsonGenerator gen, SerializerProvider provider, TypeSerializer typeSer) throwsIOException { //no type info, just regular serialization serialize(value, gen, provider); } } }