生成整数自增ID(集群主键生成服务)
在集群的环境中,有这种场景
需要整数自增ID,这个整数要求一直自增,并且需要保证唯一性.
Web服务器集群调用这个整数生成服务,然后根据各种规则,插入指定的数据库.
一般来说,整数自增可以通过几个方式实现.
1.MySQL 单独建一个表,使用Auto_increment特性.
如果需要生成ID,则Insert一个记录,然后获取last_insert_id()得到ID值
这种方式的优点是简单,而且比较快.
缺点是这个表会越来越大,需要定期进行清理.
2.Oracle 序列
优点很明显,足够快,而且不占用空间.
缺点..你需要有Oracle
3.mysql 单行更新自增
需要生成ID的时候,进行如下调用
以上三种数据库方式的效率对比如下(都是测试的虚拟环境,作为趋势参考,数值是每秒生成的ID个数)
4.使用Redis自增
使用两个Redis实例,一个分发奇数ID,一个分发偶数ID
任何一个Redis损坏,都可以切换到另外一个Redis实例.
5.使用程序模拟序列
下面的ID生成服务,初始化先从数据库拿到一段ID,然后分发。
一旦ID耗尽,再从数据库获取一段ID。
可以启动多个ID生成服务,避免单点故障.
ID生成服务本身应该串行化,避免锁竞争.
使用这种自定义序列,1百万ID的生成时间是14s.效果非常明显.
Web服务器集群调用这个整数生成服务,然后根据各种规则,插入指定的数据库.
一般来说,整数自增可以通过几个方式实现.
1.MySQL 单独建一个表,使用Auto_increment特性.
- CREATE TABLE `test` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8
这种方式的优点是简单,而且比较快.
缺点是这个表会越来越大,需要定期进行清理.
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 |
使用两个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); } }