Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

新增通过雪花算法来生成id #30

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.xiaoju.uemc.tinyid.base.exception;

/**
* @author zhangbingbing
* @Description 系统时间回调异常
* @date 2020/10/16
*/
public class SystemClockCallbackException extends RuntimeException {

private static final long serialVersionUID = -6264588182225994225L;

public SystemClockCallbackException(String msg) {
super(msg);
}

public SystemClockCallbackException(String msg, Throwable cause) {
super(msg, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package com.xiaoju.uemc.tinyid.base.generator.impl;

import com.xiaoju.uemc.tinyid.base.exception.SystemClockCallbackException;
import com.xiaoju.uemc.tinyid.base.generator.IdGenerator;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

/**
* 雪花算法实现:0(固定位)+41(当前时间戳-startAt)+10(机器号)+12(序号)
*
* @author zhangbingbing
* @date 2020/10/16
*/
public class SnowflakeIdGenerator implements IdGenerator {

private static final Logger logger = Logger.getLogger(SnowflakeIdGenerator.class.getName());
/**
* 2020-10-15 00:00:00 (GMT+8) 毫秒
*/
private final long startAt = 1602691200000L;
private final int workIdBits = 10;
private final int maxWorkId = ~(-1 << workIdBits);
private final int sequenceBits = 12;
private final int workIdShift = sequenceBits;
private final int timestampShift = workIdBits + sequenceBits;
private final int sequenceMask = ~(-1 << sequenceBits);
private final Random random = new Random();
private final int workId;
private int sequence = 0;
private long lastTimestamp = -1L;

public SnowflakeIdGenerator(int workId) {
if (workId > maxWorkId) {
throw new IllegalStateException("the work id " + workId + " greater than max work Id " + maxWorkId);
}
this.workId = workId;
SnowflakeIdGenerator.logger.info("snowflake work id " + workId);
}

@Override
public Long nextId() {
return doNextId();
}

@Override
public List<Long> nextId(Integer batchSize) {
return doNextIds(batchSize);
}


private synchronized long doNextId() {
long now = now();
// 时钟回调了
if (now < lastTimestamp) {
long offset = lastTimestamp - now;
if (offset > 5) {
throw new SystemClockCallbackException("system clock callback slow " + offset);
}
try {
this.wait(offset << 1);
} catch (InterruptedException e) {
throw new SystemClockCallbackException("system clock callback slow " + offset, e);
}
}
if (now == lastTimestamp) {
sequence = (sequence + 1) & sequenceMask;
// 该毫秒内的sequence已经用完了
if (sequence == 0) {
sequence = random.nextInt(100);
now = tillNextMill(lastTimestamp);
}
}
// 从新的毫秒开始
if (now > lastTimestamp) {
sequence = random.nextInt(100);
}
lastTimestamp = now;
return toId(lastTimestamp, workId, sequence);
}

private synchronized List<Long> doNextIds(int batchSize) {
if ((batchSize & sequenceMask) == 0) {
throw new IllegalArgumentException("batch size " + batchSize);
}
long now = now();
if (now < lastTimestamp) {
long offset = lastTimestamp - now;
if (offset > 5) {
throw new SystemClockCallbackException("system clock callback slow " + offset);
}
try {
this.wait(offset << 1);
} catch (InterruptedException e) {
throw new SystemClockCallbackException("system clock callback slow " + offset, e);
}
}
List<Long> nextIds = new ArrayList<>(batchSize);
while (nextIds.size() < batchSize) {
// 在本毫秒
if (now == lastTimestamp) {
sequence = (sequence + 1) & sequenceMask;
// 本毫秒内的sequence用完了
if (sequence == 0) {
sequence = random.nextInt(100);
now = tillNextMill(lastTimestamp);
}
nextIds.add(toId(now, workId, sequence));
continue;
}
// 在新的毫秒
if (now > lastTimestamp) {
sequence = random.nextInt(100);
int loop = batchSize - nextIds.size();
for (int i = 0; i < loop; i++) {
sequence = sequence + 1;
nextIds.add(toId(now, workId, sequence));
}
}
}
lastTimestamp = now;
return nextIds;
}

private long toId(long timestamp, int workId, int sequence) {
return ((timestamp - startAt) << timestampShift) | (workId << workIdShift) | sequence;
}

/**
* 等待下个毫秒,防止等待期间系统时钟被回调,导致方法一直轮询
*/
private long tillNextMill(long lastTimestamp) {
long timestamp;
long offset;
while (true) {
timestamp = now();
offset = lastTimestamp - timestamp;
if (offset < 0) {
return timestamp;
}
if (offset >= 5) { // 系统时钟回调时间大于5ms
throw new SystemClockCallbackException("timestamp check error,last timestamp " + lastTimestamp + ",now " + timestamp);
}
if (offset >= 2) { // 系统时钟回调时间大于等于2ms
try {
TimeUnit.MILLISECONDS.sleep(offset);
} catch (InterruptedException ignore) {
}
}
}
}

private long now() {
return System.currentTimeMillis();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.xiaoju.uemc.tinyid.base.util;

import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

/**
* @author zhangbingbing
* @date 2020/10/16
*/
public enum IPUtil {
;

public static String getHostAddress() throws SocketException {
return IPUtil.getHostAddress(null).get(0);
}

/**
* 获取已激活网卡的ip地址
*
* @param interfaceName 网卡地址,null则获取所有
* @return List<IP>
*/
public static List<String> getHostAddress(String interfaceName) throws SocketException {
List<String> ips = new ArrayList<>(5);
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while (networkInterfaces.hasMoreElements()) {
NetworkInterface networkInterface = networkInterfaces.nextElement();
Enumeration<InetAddress> allAddress = networkInterface.getInetAddresses();
while (allAddress.hasMoreElements()) {
InetAddress address = allAddress.nextElement();
if (address.isLoopbackAddress()) {
continue;
}
if (address instanceof Inet6Address) {
continue;
}
String hostAddress = address.getHostAddress();
if (null == interfaceName) {
ips.add(hostAddress);
} else if (interfaceName.equals(networkInterface.getDisplayName())) {
ips.add(hostAddress);
}
}
}
return ips;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.xiaoju.uemc.tinyid.base.generator.impl;

import org.junit.Assert;
import org.junit.Test;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;

/**
* @author zhangbingbing
* @date 2020/10/16
* @see SnowflakeIdGenerator 测试用例
*/
public class SnowflakeIdGeneratorTest {

private final SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1);
private final SnowflakeIdGenerator idGenerator2 = new SnowflakeIdGenerator(2);

@Test
public void testNextId() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(2);
final int size = 5000;
final Set<Long> idSet = new HashSet<>(size);
final Set<Long> idSet2 = new HashSet<>(size);
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < size; i++) {
idSet.add(idGenerator.nextId());
}
latch.countDown();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < size; i++) {
idSet2.add(idGenerator2.nextId());
}
latch.countDown();
}
}).start();
latch.await();
Assert.assertEquals(size, idSet.size());
Assert.assertEquals(size, idSet2.size());
idSet.removeAll(idSet2);
// idSet idSet2没有重复的id
Assert.assertEquals(size, idSet.size());
}

@Test
public void testNextIds() {
Set<Long> idSet = new HashSet<>(5000);
for (int i = 0; i < 5; i++) {
idSet.addAll(idGenerator.nextId(1000));
}
Assert.assertEquals(5000, idSet.size());
}

}
6 changes: 6 additions & 0 deletions tinyid-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

<properties>
<spring.version>1.5.9.RELEASE</spring.version>
<curator.version>2.6.0</curator.version>
</properties>

<dependencies>
Expand Down Expand Up @@ -69,6 +70,11 @@
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${curator.version}</version>
</dependency>
</dependencies>

<profiles>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.xiaoju.uemc.tinyid.server.common.annotation;

import org.springframework.context.annotation.Conditional;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(ModuleCondition.class)
public @interface Module {

/**
* 前缀
*/
String prefix() default "";

/**
* 匹配的值
*/
String[] value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.xiaoju.uemc.tinyid.server.common.annotation;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.MultiValueMap;

public class ModuleCondition implements Condition {

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
final MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(Module.class.getName());
if (attributes == null) {
return true;
}
String prefix = "";
Object prefixValue = attributes.getFirst("prefix");
if (prefixValue != null) {
prefix = prefixValue.toString();
}
final Environment environment = context.getEnvironment();
for (Object value : attributes.get("value")) {
String[] moduleName = (String[]) value;
for (String module : moduleName) {
String propertyName;
if (prefix.equals("")) {
propertyName = module;
} else {
propertyName = prefix + "." + module;
}
if (environment.getProperty(propertyName, boolean.class, false)) {
return true;
}
}
}
return false;
}
}
Loading