概要:借助数据库自增主键实现全局唯一序列号的生成;将自增主键放大后,形成区间号段,在内存中分配,从而避免频繁的IO,当达到号段最大值时,重新从数据库获取号段。
一、搭建测试
-
application.yml配置
server: port: 8080 spring: datasource: username: root password: 123456 url: jdbc:mysql://127.0.0.1:3306/sequence?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC driver-class-name: com.mysql.jdbc.Driver mybatis: mapperLocations: classpath:mapper/*Mapper.xml type-aliases-package: com.example.demo.entity
-
controller测试
package com.example.demo.controller; import com.example.demo.util.SequenceGenerator; import com.example.demo.util.ThreadPoolUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import java.util.*; import java.util.concurrent.*; @RestController @RequestMapping(value = "/sequence") public class SequenceController { @Autowired private SequenceGenerator sequenceGenerator; @GetMapping(value = "/getSequence.htm") public long getSequence(HttpServletRequest request){ ExecutorService executor = ThreadPoolUtil.getInstance().getThreadPoolExecutor(); List<Future> futureList = new ArrayList<>(); Set<String> set = new HashSet<>(); for (int i = 0; i < 2000; i ++) { Future<String> future = executor.submit(new Callable<String>() { @Override public String call() throws Exception { return sequenceGenerator.getSequence(); } }); futureList.add(future); } for (Future future : futureList) { try { String id = (String)future.get(); if (!set.contains(id)) { set.add(id); System.out.println(id); } else { System.out.println("重复序列:" + id); } } catch (Exception e) { e.printStackTrace(); } } return set.size(); } }
-
sequenceGenerator
package com.example.demo.util; import com.example.demo.dao.SequenceDao; import com.example.demo.entity.SequenceEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicLong; @Component public class SequenceGenerator { // 号段范围 private static final int bound = 1000; // 号段最大值 private long maxId; // 记录当前序列号 private final AtomicLong seq = new AtomicLong(); // 许可证管理器 private final Semaphore semaphore = new Semaphore(bound); @Autowired private SequenceDao sequenceDao; @PostConstruct public void init() throws UnknownHostException { // 获取当前IP String ip = InetAddress.getLocalHost().getHostAddress(); SequenceEntity entity = new SequenceEntity(ip); sequenceDao.saveSequence(entity); long id = entity.getId(); seq.set(id * bound); maxId = (id + 1) * bound; } public String getSequence() throws UnknownHostException, InterruptedException { // 获取许可证,此线程会一直阻塞,直到获取这个许可证,或者被中断(抛出InterruptedException异常) semaphore.acquire(); // 获取当前号 long currentSeq = seq.incrementAndGet(); // 当达到号段最大值时,刷新号段 if (currentSeq >= maxId) { flashSequence(currentSeq); } return formatSequence(currentSeq); } private synchronized void flashSequence(long currentSeq) throws UnknownHostException { if (currentSeq >= maxId) { // 获取本机IP String ip = InetAddress.getLocalHost().getHostAddress(); SequenceEntity entity = new SequenceEntity(ip); sequenceDao.saveSequence(entity); long id = entity.getId(); seq.set(id * bound); maxId = (id + 1) * bound; // 当前线程释放号段数个可用的许可证 semaphore.release(bound); } } private String formatSequence(long id){ return "1" + String.format("%016d", id); } }
-
dao
package com.example.demo.dao; import com.example.demo.entity.SequenceEntity; import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Repository; @Repository public interface SequenceDao { void saveSequence(@Param("Sequence") SequenceEntity entity); }
-
threadpool
package com.example.demo.util; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; public class ThreadPoolUtil { private static ExecutorService executor; private static volatile ThreadPoolUtil threadPoolUtil; /** * 核心线程数量 */ private static final int CORE_POOL_SIZE = 10; /** * 最大线程数量 */ private static final int MAXI_NUM_POOLSIZE = 20; /** * 线程存活时间 */ private static final long KEEP_ALIVE_TIME = 5L; /** * 任务队列大小 */ private static final int QUEUE_SIZE = 2000; /** * 阻塞队列 */ private static BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(QUEUE_SIZE); /** * 私有构造器 */ private ThreadPoolUtil(){ executor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXI_NUM_POOLSIZE, KEEP_ALIVE_TIME, TimeUnit.MINUTES, workQueue, new MyThreadFactory(), new MyRejectedExecutionHandler()); } /** * 线程池构建工厂 */ public static ThreadPoolUtil getInstance(){ if (null == threadPoolUtil) { synchronized (ThreadPoolUtil.class) { if (null == threadPoolUtil) { threadPoolUtil = new ThreadPoolUtil(); } } } return threadPoolUtil; } public ExecutorService getThreadPoolExecutor(){ return executor; } /** * 内部类,线程工厂,用于创造线程池所需要的线程 */ class MyThreadFactory implements ThreadFactory { private String threadName = "ThreadPool"; private final AtomicInteger threadNumber = new AtomicInteger(1); private MyThreadFactory () {} @Override public Thread newThread(Runnable r) { String name = threadName + "-" + String.format("%02d", threadNumber.getAndIncrement()); Thread thread = new Thread(r, name); thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { System.out.println("Exception: " + e.getMessage()); } }); return thread; } } /** * 内部类,拒绝任务后的请求处理 */ class MyRejectedExecutionHandler implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println("ThreadPool over"); } } }
-
mapper.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.demo.dao.SequenceDao"> <resultMap id="BaseResultMap" type="com.example.demo.entity.SequenceEntity"> <result column="ID" jdbcType="BIGINT" property="id" /> <result column="IP" jdbcType="VARCHAR" property="ip" /> </resultMap> <insert id="saveSequence" parameterType="com.example.demo.entity.SequenceEntity" useGeneratedKeys="true" keyProperty="Sequence.id"> REPLACE INTO SEQUENCE (IP) VALUES (#{Sequence.ip}) </insert> </mapper>
二、数据库
-
DDL
CREATE TABLE `SEQUENCE` ( `ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '序列号', `IP` varchar(255) NOT NULL COMMENT 'IP地址', PRIMARY KEY (`ID`), UNIQUE KEY `index_ip` (`IP`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
三、测试结果
10000000000216985
10000000000216986
10000000000216987
10000000000216988
10000000000216989
10000000000216990
10000000000216991
10000000000216992
10000000000216993
10000000000216994
......
参考: 分布式全局唯一ID生成方案