Source code analysis of mybatis plus global unique ID generator (snowflakeid)

Ge Yiwu 2022-01-26 17:11:44 阅读数:504

source code analysis mybatis global

mybatis-plus Globally unique ID The transmitter (SnowFlakeID) The source code parsing

One 、 Preface

Guarantee in distributed scenario ID The only basic requirement is the overall situation ,UUID Often used as a unique ID Generation strategy for , But it is not suitable for database primary key ( There is no order ); snow ID(SnowFlake) yes twitter Internal distributed projects of the company ID generating algorithm , After open source, it is widely praised by large domestic factories . and mybatis-plus( following abbreviation MP) Default ID The generator uses the snowflake algorithm com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator.

Two 、 Snowflake algorithm

1.SnowflakeId structure

0 - 0000000000 0000000000 0000000000 0000000000 0 - 0000000000 - 000000000000
// Spare bit (1) - Time bit (41) - datacenterId(5)+workId(5) - Sequence bit (12)

2. Lack of snowflake algorithm and optimization method

  • Clock back

MP Snowflake algorithm : If the clock callback offset is less than or equal to 5 millisecond , Is waiting for 5x2=10 millisecond ; Otherwise, report the error directly .
Recommended for production environments ntp Synchronize time and enable fine-tuning Limit .
If the time difference is large, you must pass ntpdate What should I do to force synchronization time ?( Use MP Snowflake algorithm )
In this case , The application can be started by rewriting ,JVM PID It's going to change , The machine position changes .

  • workerId Difficult to maintain

MP Snowflake algorithm :datacenterId By machine MAC Address determination ;workId from JVM PID decision (String name = ManagementFactory.getRuntimeMXBean().getName();). Of course , You can also pass application.yml Profile Settings datacenterId And workId or Customize ID generator .

3、 ... and 、mybatis-plus

1. Call chain

 Call chain

com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration#sqlSessionFactory SqlSessionFactory factory bean Inject
=> com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean#afterPropertiesSet
=> com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean#buildSqlSessionFactory
=> com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)
=> com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator#DefaultIdentifierGenerator() Default ID Producer
=> com.baomidou.mybatisplus.core.toolkit.Sequence#Sequence() Snowflake algorithm

2. Core code

/* * Copyright (c) 2011-2020, baomidou ([email protected]). * <p> * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * <p> * https://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */
package com.baomidou.mybatisplus.core.toolkit;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.concurrent.ThreadLocalRandom;
/** * Distributed, efficient and orderly ID Production of black Technology (sequence) * * <p> Optimize open source projects :https://gitee.com/yu120/sequence</p> * * @author hubin * @since 2016-08-18 */
public class Sequence {

private static final Log logger = LogFactory.getLog(Sequence.class);
/** * Time start mark point , As a benchmark , Generally, the latest time of the system ( Once it is determined that it cannot be changed ) */
private final long twepoch = 1288834974657L;
/** * Number of machine marks */
private final long workerIdBits = 5L;
private final long datacenterIdBits = 5L;
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
/** * Auto increment in milliseconds */
private final long sequenceBits = 12L;
private final long workerIdShift = sequenceBits;
private final long datacenterIdShift = sequenceBits + workerIdBits;
/** * Time stamp left move bit */
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
private final long workerId;
/** * Data identification ID part */
private final long datacenterId;
/** * concurrency control */
private long sequence = 0L;
/** * Last production ID Time stamp */
private long lastTimestamp = -1L;
public Sequence() {

this.datacenterId = getDatacenterId(maxDatacenterId);
this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
}
/** * There are reference constructors * * @param workerId Work the machine ID * @param datacenterId Serial number */
public Sequence(long workerId, long datacenterId) {

Assert.isFalse(workerId > maxWorkerId || workerId < 0,
String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
Assert.isFalse(datacenterId > maxDatacenterId || datacenterId < 0,
String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/** * obtain maxWorkerId */
protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {

StringBuilder mpid = new StringBuilder();
mpid.append(datacenterId);
String name = ManagementFactory.getRuntimeMXBean().getName();
if (StringUtils.isNotBlank(name)) {

/* * GET jvmPid */
mpid.append(name.split(StringPool.AT)[0]);
}
/* * MAC + PID Of hashcode obtain 16 Low position */
return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
}
/** * Data identification id part */
protected static long getDatacenterId(long maxDatacenterId) {

long id = 0L;
try {

InetAddress ip = InetAddress.getLocalHost();
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
if (network == null) {

id = 1L;
} else {

byte[] mac = network.getHardwareAddress();
if (null != mac) {

id = ((0x000000FF & (long) mac[mac.length - 2]) | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6;
id = id % (maxDatacenterId + 1);
}
}
} catch (Exception e) {

logger.warn(" getDatacenterId: " + e.getMessage());
}
return id;
}
/** * Get the next one ID * * @return next ID */
public synchronized long nextId() {

long timestamp = timeGen();
// Leap second 
if (timestamp < lastTimestamp) {

long offset = lastTimestamp - timestamp;
if (offset <= 5) {

try {

wait(offset << 1);
timestamp = timeGen();
if (timestamp < lastTimestamp) {

throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
}
} catch (Exception e) {

throw new RuntimeException(e);
}
} else {

throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
}
}
if (lastTimestamp == timestamp) {

// In the same milliseconds , The serial number is increasing 
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {

// The number of sequences in the same millisecond has reached the maximum 
timestamp = tilNextMillis(lastTimestamp);
}
} else {

// In different milliseconds , The serial number is set to 1 - 3 random number 
sequence = ThreadLocalRandom.current().nextLong(1, 3);
}
lastTimestamp = timestamp;
// Time stamp part | Data center part | Machine identification part | Serial number part 
return ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift)
| sequence;
}
protected long tilNextMillis(long lastTimestamp) {

long timestamp = timeGen();
while (timestamp <= lastTimestamp) {

timestamp = timeGen();
}
return timestamp;
}
protected long timeGen() {

return SystemClock.now();
}
}

Summary of technical points :

  • An operation ( Globally unique ID reversible , The timestamp can be extracted reversely 、 Data Center ID、 machine ID、 Serial number )

  • workerId machine ID (JVM PID)

adopt String name = ManagementFactory.getRuntimeMXBean().getName(); Get the current process ID, Example return value [email protected], among 19480 It's the process ID, Just intercept !

  • SystemClock Custom clock

com.baomidou.mybatisplus.core.toolkit.SystemClock
High concurrency scenarios System.currentTimeMillis() There are performance issues , Start a daemon thread in the background ( Every millisecond ) Update the clock .

  • In different milliseconds , The serial number is set to 1 - 2 random number

sequence = ThreadLocalRandom.current().nextLong(1, 3);
What does this code do ? It's not that you should just sequence Reset to 0 Do you ?
Add the above code , To avoid concurrency when it is not too high ( Concurrency per millisecond is less than or equal to 1), Add strange 、 Even randomness , Break up the data , It is convenient to take the rest ( sub-treasury 、 Common operations of sub table ).
Reference resources Sequence

Pay tribute to the big man

Java Use snowflakes id

copyright:author[Ge Yiwu],Please bring the original link to reprint, thank you. https://en.javamana.com/2022/01/202201261711416088.html