在現實生活中,不少場景都須要ID生成器,好比說電商平臺的訂單號生成、銀行的叫號系統等。針對不用的業務需求,ID生成策略也不同,好比電商平臺的訂單號能夠由時間序列組成,銀行的叫號系統則是天然數自增序列。對於自增序列的ID生成器,在多併發環境下,爲保證嚴格的自增,經常能夠經過鎖來保證。html
設想一下,若是咱們想在應用層面本身實現一個自增序列的ID生成器(其實本質上咱們須要實現的是一個getNextValue方法),怎麼作?對不少人來講,可能首先想到的是直接用i++這樣語法層面的語句,可是必需要對方法加鎖才行,由於i++不是一個原子操做。還有另一個辦法,就是利用java的AtomicInteger類,AtomicInteger的實現不是基於鎖,而是基於CAS(Compare and Swap),在某些場景下,效率要比加鎖的方式高,參考(漫畫:什麼是 CAS 機制?)。java
上面介紹的語言層面的支持更多的是一些理論層面的東西,經常適用於單機系統,若是要應用到實際的軟件系統中,還須要考慮不少其餘方面,好比說自增序列的持久化、分佈式系統中如何生成自增序列。sql
在分佈式系統中,如何實現ID生成器,有不少辦法,有興趣的童鞋能夠自行網上搜索。下面主要分析JPA的ID生成器是如何依賴於數據庫的鎖實現的。
數據庫
其實不少分佈式場景下的需求和功能,都仍是依賴於數據庫的基本功能來實現,以前寫的一篇文章(liquibase和flyway中分佈式鎖實現的區別?)就介紹了在flyway中如何利用數據庫的排他鎖實現分佈式鎖。然而,大量依賴數據庫也可能致使數據庫成爲一個單點性能瓶頸,這時候每每就須要考慮一些方案來減輕這個瓶頸,好比說分庫分表(如今流行的微服務架構就是一個High-level的分庫分表的實踐)。架構
JPA的@GeneratedValue和@TableGenerator兩個Annotation能夠直接用來生成自增序列,而且會把當前的序列存在數據庫中,JPA如今流行的兩個provider(eclipselink和hibernate)在實現上,有殊途同歸之處,都是依賴的數據庫的排他鎖。併發
那麼eclipselink是如何實現的呢?就像上面提到的,本質上就是實現了一個getNextValue方法,只是這裏加的鎖是數據的排他鎖,而不是語言層面的鎖,以下圖所示。eclipse
這裏數據庫排他鎖工做的基本原理是:在一個事務中,當update一條記錄時,會在當前記錄上加一個排他鎖(或者整個表上),只有事務結束(commit或者rollback)以後,纔會釋放這個鎖;這時其餘阻塞的事務就繼續執行。參考以下代碼:分佈式
Connection c = null; try { Class.forName("org.postgresql.Driver"); c = DriverManager.getConnection("jdbc:postgresql://localhost:5432/postgres","postgres", "postgres"); } catch (Exception e) { e.printStackTrace(); System.err.println(e.getClass().getName()+": "+e.getMessage()); System.exit(0); } c.setAutoCommit(false); String sql1 = "update sequence set seq_count = 35 where id_generation_category='t1'"; PreparedStatement preparedStatement1 = c.prepareStatement(sql1); preparedStatement1.executeUpdate(); String sql2 = "select * from sequence where id_generation_category='t1'"; ResultSet rs = preparedStatement2.executeQuery(); while(rs.next()) { int seq_count = rs.getInt("seq_count"); System.out.println("seq_count: " + seq_count); } try{ c.commit(); }catch (SQLException e){ c.rollback(); } finally { preparedStatement1.close(); preparedStatement2.close(); c.close(); }
Hibernate的實現相似,具體能夠參考文章(https://dzone.com/articles/hibernate-identity-sequence),能夠看到Hibernate採用的是select for update語句顯示加排他鎖的方式,和前面寫的一篇文章(liquibase和flyway中分佈式鎖實現的區別?)flyway加鎖的方式同樣。ide
上面提到,實現自增序列也能夠不用加鎖,java語言層面提供的AtomicInteger類就是採用不加鎖的方法,而是採用的CAS(Compare and Swap)。那麼在分佈式環境下,ID生成器是否是也能夠採用CAS呢?這篇文章(淺談CAS在分佈式ID生成方案上的應用 | 架構師之路)就簡單介紹瞭如何採用CAS實現分佈式ID生成器。微服務
關於各類鎖概念的解釋,推薦兩篇文章:(Java 中15種鎖的介紹:公平鎖,可重入鎖,獨享鎖,互斥鎖,樂觀鎖,分段鎖,自旋鎖等等),(Java中的鎖原理、鎖優化、CAS、AQS詳解!)