Java實(shí)現(xiàn)雪花算法(snowflake)
本文主要介紹了Java實(shí)現(xiàn)雪花算法(snowflake),分享給大家,具體如下:

簡單描述
最高位是符號位,始終為0,不可用。
- 41位的時(shí)間序列,精確到毫秒級,41位的長度可以使用69年。時(shí)間位還有一個(gè)很重要的作用是可以根據(jù)時(shí)間進(jìn)行排序。注意,41位時(shí)間截不是存儲當(dāng)前時(shí)間的時(shí)間截,而是存儲時(shí)間截的差值(當(dāng)前時(shí)間截 - 開始時(shí)間截) 后得到的值,這里的的開始時(shí)間截,一般是我們的id生成器開始使用的時(shí)間,由我們程序來指定的(如下下面程序SnowFlake類的START_STMP屬性)。41位的時(shí)間截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
- 10位的機(jī)器標(biāo)識,10位的長度最多支持部署1024個(gè)節(jié)點(diǎn)。
- 12位的計(jì)數(shù)序列號,序列號即一系列的自增id,可以支持同一節(jié)點(diǎn)同一毫秒生成多個(gè)ID序號,12位的計(jì)數(shù)序列號支持每個(gè)節(jié)點(diǎn)每毫秒產(chǎn)生4096個(gè)ID序號。
加起來剛好64位,為一個(gè)Long型。這個(gè)算法很簡潔,但依舊是一個(gè)很好的ID生成策略。其中,10位器標(biāo)識符一般是5位IDC+5位machine編號,唯一確定一臺機(jī)器。
算法實(shí)現(xiàn)
public class SnowFlake {
// 起始的時(shí)間戳
private final static long START_STMP = 1577808000000L; //2020-01-01
// 每一部分占用的位數(shù),就三個(gè)
private final static long SEQUENCE_BIT = 12; //序列號占用的位數(shù)
private final static long MACHINE_BIT = 5; //機(jī)器標(biāo)識占用的位數(shù)
private final static long DATACENTER_BIT = 5; //數(shù)據(jù)中心占用的位數(shù)
// 每一部分最大值
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; //數(shù)據(jù)中心
private long machineId; //機(jī)器標(biāo)識
private long sequence = 0L; //序列號
private long lastStmp = -1L; //上一次時(shí)間戳
public SnowFlake(long datacenterId, long machineId) {
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;
}
//產(chǎn)生下一個(gè)ID
public synchronized long nextId() {
long currStmp = timeGen();
if (currStmp < lastStmp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id");
}
if (currStmp == lastStmp) {
//if條件里表示當(dāng)前調(diào)用和上一次調(diào)用落在了相同毫秒內(nèi),只能通過第三部分,序列號自增來判斷為唯一,所以+1.
sequence = (sequence + 1) & MAX_SEQUENCE;
//同一毫秒的序列數(shù)已經(jīng)達(dá)到最大,只能等待下一個(gè)毫秒
if (sequence == 0L) {
currStmp = getNextMill();
}
} else {
//不同毫秒內(nèi),序列號置為0
//執(zhí)行到這個(gè)分支的前提是currTimestamp > lastTimestamp,說明本次調(diào)用跟上次調(diào)用對比,已經(jīng)不再同一個(gè)毫秒內(nèi)了,這個(gè)時(shí)候序號可以重新回置0了。
sequence = 0L;
}
lastStmp = currStmp;
//就是用相對毫秒數(shù)、機(jī)器ID和自增序號拼接
return (currStmp - START_STMP) << TIMESTMP_LEFT //時(shí)間戳部分
| datacenterId << DATACENTER_LEFT //數(shù)據(jù)中心部分
| machineId << MACHINE_LEFT //機(jī)器標(biāo)識部分
| sequence; //序列號部分
}
private long getNextMill() {
long mill = timeGen();
while (mill <= lastStmp) {
mill = timeGen();
}
return mill;
}
private long timeGen() {
return System.currentTimeMillis();
}
}
當(dāng)增加一秒生成ID的時(shí)候就是增加10位的機(jī)器標(biāo)識+12位序列+約2的10次方(1000毫秒),最終就是增加一個(gè)2的32次方4 294 967 296就是42億左右
但是這里有一個(gè)坑,雪花算法產(chǎn)生的長整數(shù)的精度可能超過javascript能表達(dá)的精度,這會(huì)導(dǎo)致js獲取的id與雪花算法算出來的id不一致,如雪花算法得到的是36594866121080832,但是因?yàn)閖avascript丟失精度后只獲取到36594866121080830, 這會(huì)導(dǎo)致對數(shù)據(jù)的所有操作都失效。
解決辦法:后端的語言獲取到雪花算法的id后將其轉(zhuǎn)換為String類型,這樣js也會(huì)當(dāng)做字符串來處理,就不會(huì)丟失精度了。
配置方法
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(toStringConverter());
}
/**
* BigDecimal Long 轉(zhuǎn)化為String
*
* @return
*/
@Bean
public MappingJackson2HttpMessageConverter toStringConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
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 默認(rèn)
// Include.NON_DEFAULT 屬性為默認(rèn)值不序列化
// Include.NON_EMPTY 屬性為 空("") 或者為 NULL 都不序列化,則返回的json是沒有這個(gè)字段的。這樣對移動(dòng)端會(huì)更省流量
// 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);// 允許出現(xiàn)特殊字符和轉(zhuǎn)義符
mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); // 允許出現(xiàn)單引號
converter.setObjectMapper(mapper);
return converter;
}
@JacksonStdImpl
static class BigDecimalToStringSerializer extends ToStringSerializer {
public final static BigDecimalToStringSerializer instance = new BigDecimalToStringSerializer();
public BigDecimalToStringSerializer() {
super(Object.class);
}
public BigDecimalToStringSerializer(Class<?> handledType) {
super(handledType);
}
@Override
public boolean isEmpty(SerializerProvider prov, Object value) {
if (value == null) {
return true;
}
String str = ((BigDecimal) value).stripTrailingZeros().toPlainString();
return str.isEmpty();
}
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
gen.writeString(((BigDecimal) value).stripTrailingZeros().toPlainString());
}
@Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException {
return createSchemaNode("string", true);
}
@Override
public void serializeWithType(Object value, JsonGenerator gen,
SerializerProvider provider, TypeSerializer typeSer)
throws IOException {
// no type info, just regular serialization
serialize(value, gen, provider);
}
}
}
到此這篇關(guān)于Java實(shí)現(xiàn)雪花算法(snowflake)的文章就介紹到這了,更多相關(guān)Java 雪花算法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
通過實(shí)例學(xué)習(xí)Spring @Required注釋原理
這篇文章主要介紹了通過實(shí)例學(xué)習(xí)Spring @Required注釋原理,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
使用JavaConfig代替xml實(shí)現(xiàn)Spring配置操作
這篇文章主要介紹了使用JavaConfig代替xml實(shí)現(xiàn)Spring配置操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
logback.xml動(dòng)態(tài)配置程序路徑的操作
這篇文章主要介紹了logback.xml動(dòng)態(tài)配置程序路徑的操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02
使用springmvc參數(shù)接收boolean類型參數(shù)的問題
這篇文章主要介紹了使用springmvc參數(shù)接收boolean類型參數(shù)的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
mybatisplus解除分頁限制的實(shí)現(xiàn)
這篇文章主要介紹了mybatisplus解除分頁限制的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
Spring動(dòng)態(tài)代理實(shí)現(xiàn)日志功能詳解
這篇文章主要為大家詳細(xì)介紹了Spring動(dòng)態(tài)代理實(shí)現(xiàn)日志功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08

