生成整數自增ID(集群主鍵生成服務)


    在集群的環境中,有這種場景
    需要整數自增ID,這個整數要求一直自增,並且需要保證唯一性.


    Web服務器集群調用這個整數生成服務,然后根據各種規則,插入指定的數據庫.
    
    一般來說,整數自增可以通過幾個方式實現.
    1.MySQL 單獨建一個表,使用Auto_increment特性.
  1. CREATE TABLE `test` (
  2.   `id` int(11) NOT NULL AUTO_INCREMENT,
  3.   PRIMARY KEY (`id`)
  4. ) ENGINE=InnoDB DEFAULT CHARSET=utf8
    如果需要生成ID,則Insert一個記錄,然后獲取last_insert_id()得到ID值

    
    這種方式的優點是簡單,而且比較快.
    缺點是這個表會越來越大,需要定期進行清理.

    2.Oracle 序列
    優點很明顯,足夠快,而且不占用空間.
    缺點..你需要有Oracle

    3.mysql 單行更新自增

    需要生成ID的時候,進行如下調用
 

以上三種數據庫方式的效率對比如下(都是測試的虛擬環境,作為趨勢參考,數值是每秒生成的ID個數)
  單線程 5線程 10線程 20線程
MySQL Auto_increment 340-390 277 229 178
Oracle序列 714 555 454 454
MySQL 單行更新 303 136 66 19
 
 
    4.使用Redis自增
    使用兩個Redis實例,一個分發奇數ID,一個分發偶數ID
    任何一個Redis損壞,都可以切換到另外一個Redis實例.

    5.使用程序模擬序列


    下面的ID生成服務,初始化先從數據庫拿到一段ID,然后分發。
    一旦ID耗盡,再從數據庫獲取一段ID。
    可以啟動多個ID生成服務,避免單點故障.
     ID生成服務 本身應該串行化,避免鎖競爭.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class SeqGenerator {
    private static ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

    private static int currentVal = -1;
    private static int maxVal = -1;
    private static int fetchSize = 10000;//每次生成一萬個id

    static{
        try {
            fetchFromDB();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public static int getSeq() throws InterruptedException, ExecutionException {
        Callable<Integer> c = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int result = currentVal;
                if (currentVal > maxVal) {//如果當前值>數據庫最大值,重新生成id
                    fetchFromDB();
                    result = currentVal;
                }
                currentVal++;
                return result;

            }
        };
        Future<Integer> task = singleThreadExecutor.submit(c);
        return task.get().intValue();
    }

    private static void fetchFromDB() throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "xx", "xx");
        connection.setAutoCommit(false);
        Statement st = connection.createStatement();
        ResultSet rs = st.executeQuery("select * from test for update");
        rs.next();
        currentVal = rs.getInt(1) + 1;//當前值
        rs.close();
        st.executeUpdate("update test set id=id+" + fetchSize);//更新db中最大值
        rs = st.executeQuery("select * from test for update");
        rs.next();
        maxVal = rs.getInt(1);//最大值
        connection.commit();
        rs.close();
        st.close();
        connection.close();
    }

    public static void main(String[] args) throws Exception {
        int i = 1000000;
        long start = System.currentTimeMillis();

        while (i > 0) {
            System.out.println(SeqGenerator.getSeq());
            i--;
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
}

 

 

    使用這種自定義序列,1百萬ID的生成時間是14s.效果非常明顯.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM