简单描述
最高位是符号位,始终为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 <
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
}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也会当做字符串来处理,就不会丢失精度了。
配置方法
@Configurationpublic class WebMvcConfig implementsWebMvcConfigurer {
@Autowiredpublic void configureMessageConverters(List>converters) {
converters.add(toStringConverter());
}/*** BigDecimal Long 转化为String
*
*@return
*/@BeanpublicMappingJackson2HttpMessageConverter 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;
}
@JacksonStdImplstatic class BigDecimalToStringSerializer extendsToStringSerializer {public final static BigDecimalToStringSerializer instance = newBigDecimalToStringSerializer();publicBigDecimalToStringSerializer() {super(Object.class);
}public BigDecimalToStringSerializer(Class>handledType) {super(handledType);
}
@Overridepublic booleanisEmpty(SerializerProvider prov, Object value) {if (value == null) {return true;
}
String str=((BigDecimal) value).stripTrailingZeros().toPlainString();returnstr.isEmpty();
}
@Overridepublic voidserialize(Object value, JsonGenerator gen, SerializerProvider provider)throwsIOException {
gen.writeString(((BigDecimal) value).stripTrailingZeros().toPlainString());
}
@Overridepublic JsonNode getSchema(SerializerProvider provider, Type typeHint) throwsJsonMappingException {return createSchemaNode("string", true);
}
@Overridepublic voidserializeWithType(Object value, JsonGenerator gen,
SerializerProvider provider, TypeSerializer typeSer)throwsIOException {//no type info, just regular serialization
serialize(value, gen, provider);
}
}
}