全局唯一序列號的生成

概要:藉助數據庫自增主鍵實現全局唯一序列號的生成;將自增主鍵放大後,形成區間號段,在內存中分配,從而避免頻繁的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生成方案

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章