Commit 4b2bffa3 authored by zhiguo.liu's avatar zhiguo.liu

# 发号器功能增强,对时间回撤有更强的处理能力。

1. 使用缓存存储 120 秒内的计数器。当时间回撤低于 120 秒时,到缓存中找计数器。
2. 使用 AtomicInteger 替换 synchronized 关键字保证原子性。
3. 现在性能大约每秒 13w
parent f3e63dad
package cn.quantgroup.xyqb.Utils; package cn.quantgroup.xyqb.Utils;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate;
...@@ -9,21 +14,30 @@ import javax.annotation.PostConstruct; ...@@ -9,21 +14,30 @@ import javax.annotation.PostConstruct;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/** /**
* 注意事项: * 注意事项:
* 1. 使用前在配置文件内配置 DATA_CENTER_ID * 1. 使用前在配置文件内配置 DATA_CENTER_ID
* * <p>
* * <p>
* Created by zhiguo.liu on 2017/5/18. * Created by zhiguo.liu on 2017/5/18.
*/ */
@Component @Component
public class IDGenerator { public class IDGenerator {
private static final Logger LOGGER = LoggerFactory.getLogger(IDGenerator.class);
private static final String REDIS_WORK_ID_KEY = "GLOBAL:WORK:ID:"; private static final String REDIS_WORK_ID_KEY = "GLOBAL:WORK:ID:";
private static final String ID_FORMAT = "yyyyMMddHHmmss"; private static final String ID_FORMAT = "yyyyMMddHHmmss";
private static Lock lock = new ReentrantLock();
// data center,默认为 1 // data center,默认为 1
private static int DATA_CENTER_ID = 1; private static int DATA_CENTER_ID = 1;
private static int WORKER_ID = 0; private static int WORKER_ID = 0;
...@@ -32,15 +46,31 @@ public class IDGenerator { ...@@ -32,15 +46,31 @@ public class IDGenerator {
// 最高每秒发号 100w // 最高每秒发号 100w
private static final int MAX_COUNT = 999999; private static final int MAX_COUNT = 999999;
private static long COUNTER = 0L; private static AtomicInteger COUNTER = new AtomicInteger(0);
private static long MAX_TIME_SECOND = 0L; private static long MAX_TIME_SECOND;
// 当前时间戳字符串
private static String CURRENT_TIME_STR;
// Worker ID 字符串 // Worker ID 字符串
private static String WORKER_ID_STR; private static String WORKER_ID_STR;
// data center 字符串 // data center 字符串
private static String DATA_CENTER_STR; private static String DATA_CENTER_STR;
// 最长回退时间,120 秒
private static int MAX_BACK_SECOND = 120;
static{
Date now = new Date();
Long timeSecond = now.getTime() / 1000;
MAX_TIME_SECOND = timeSecond;
}
private static LoadingCache cache = CacheBuilder.newBuilder()
.expireAfterWrite(MAX_BACK_SECOND, TimeUnit.SECONDS)
.build(new CacheLoader<Long, AtomicInteger>() {
@Override
public AtomicInteger load(Long key) throws Exception {
return new AtomicInteger(0);
}
});
@Autowired @Autowired
private StringRedisTemplate redis; private StringRedisTemplate redis;
...@@ -53,7 +83,6 @@ public class IDGenerator { ...@@ -53,7 +83,6 @@ public class IDGenerator {
@PostConstruct @PostConstruct
public void init() { public void init() {
WORKER_ID = (int) (redis.opsForValue().increment(REDIS_WORK_ID_KEY + DATA_CENTER_ID, 1) % MAX_WORK_ID); WORKER_ID = (int) (redis.opsForValue().increment(REDIS_WORK_ID_KEY + DATA_CENTER_ID, 1) % MAX_WORK_ID);
WORKER_ID_STR = String.format("%04d", WORKER_ID); WORKER_ID_STR = String.format("%04d", WORKER_ID);
DATA_CENTER_STR = String.format("%03d", DATA_CENTER_ID); DATA_CENTER_STR = String.format("%03d", DATA_CENTER_ID);
} }
...@@ -62,26 +91,48 @@ public class IDGenerator { ...@@ -62,26 +91,48 @@ public class IDGenerator {
/** /**
* 1. 需要获取 dataCenterId 和 workeId * 1. 需要获取 dataCenterId 和 workeId
*/ */
public synchronized static String getId(String prefix) { public static String getId(String prefix) {
Date now = new Date(); Date now = new Date();
Long timeSecond = now.getTime() / 1000; Long timeSecond = now.getTime() / 1000;
Integer counter = 0;
if (timeSecond > MAX_TIME_SECOND) { if (timeSecond > MAX_TIME_SECOND) {
MAX_TIME_SECOND = timeSecond; lock.lock();
CURRENT_TIME_STR = new SimpleDateFormat(ID_FORMAT, Locale.SIMPLIFIED_CHINESE).format(now); if(timeSecond > MAX_TIME_SECOND){
COUNTER = 0; cache.put(MAX_TIME_SECOND, COUNTER);
COUNTER = new AtomicInteger(0);
MAX_TIME_SECOND = timeSecond;
}
lock.unlock();
}
if(timeSecond == MAX_TIME_SECOND){
counter = COUNTER.incrementAndGet();
}
// 时间回退时到 cache 里拿,或者直接抛出错误
if (timeSecond < MAX_TIME_SECOND) {
if (timeSecond + MAX_BACK_SECOND < MAX_TIME_SECOND) {
throw new RuntimeException("时间回撤, 请稍后再试");
}
try {
AtomicInteger historyCounter = (AtomicInteger) cache.get(timeSecond);
counter = historyCounter.incrementAndGet();
} catch (ExecutionException e) {
LOGGER.error("取出缓存时出错");
}
} }
// 达到计数器上上限,或时间回退时, 休眠半秒并重试 // 达到计数器上上限, 休眠半秒并重试
if (COUNTER >= MAX_COUNT || timeSecond < MAX_TIME_SECOND) { if (counter >= MAX_COUNT) {
try { try {
Thread.sleep(500); Thread.sleep(500);
return getId(prefix); return getId(prefix);
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); LOGGER.error("发号器休眠时发生错误:{}", e);
} }
} }
return prefix + CURRENT_TIME_STR + DATA_CENTER_STR + WORKER_ID_STR + String.format("%06d", ++COUNTER); String currentTimeStr = new SimpleDateFormat(ID_FORMAT, Locale.SIMPLIFIED_CHINESE).format(now);
return prefix + currentTimeStr + DATA_CENTER_STR + WORKER_ID_STR + String.format("%06d", counter);
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment