生成整数自增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.效果非常明显.
posted @ 2016-02-27 11:18  有梦就能实现  阅读(6774)  评论(0编辑  收藏  举报