hibernate使用snowflake演算法進行主鍵ID生成
專案中一般採用hibernate自帶的主鍵生成策略 ,在分散式的高併發專案,可能會出現主鍵重複,所以採用twitter的開源專案snowflake演算法進行主鍵生成。
SnowFlake的結構如下(每部分用-分開):
1位標誌位 41位時間戳 5位機器+5位資料標誌 12位計數器
0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
41位時間戳是因為當前時間減去起始時間的時間戳剛好在2^41範圍內
1位標識,由於long基本型別在Java中是帶符號的,最高位是符號位,正數是0,負數是1,所以id一般是正數,最高位是0
41位時間截(毫秒級),注意,41位時間截不是儲存當前時間的時間截,而是儲存時間截的差值(當前時間截 - 開始時間截)
得到的值),這裡的的開始時間截,一般是我們的id生成器開始使用的時間,由我們程式來指定的(如下下面程式IdWorker 類的startTime屬性)。41位的時間截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
10位的資料機器位,可以部署在1024個節點,包括5位datacenterId和5位workerId
12位序列,毫秒內的計數,12位的計數順序號支援每個節點每毫秒(同一機器,同一時間截)產生4096個ID序號
加起來剛好64位,為一個Long型。
SnowFlake的優點是,整體上按照時間自增排序,並且整個分散式系統內不會產生ID碰撞(由資料中心ID和機器ID作區分),並且效率較高,經測試,SnowFlake每秒能夠產生26萬ID左右。
基本上snowflake演算法只要在不調整機器時間或者在特殊時間(如進行閏秒調整時期將秒數減一)不會出現重複id,且生成速度快。
程式碼部分:首先將snowflake中的官方demo進行適當調整,因為不需要那麼多機器所以將結構調整為 1 43 3 3 14的結構。
SnowFlakeIdWorker類:
/**
* Twitter_Snowflake官網demo格式<br>
* SnowFlake的結構如下(每部分用-分開):<br>
* 1位標誌位 41位時間戳 5位機器+5位資料標誌 12位計數器
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
* 41位時間戳是因為當前時間減去起始時間的時間戳剛好在2^41範圍內
* 1位標識,由於long基本型別在Java中是帶符號的,最高位是符號位,正數是0,負數是1,所以id一般是正數,最高位是0<br>
* 41位時間截(毫秒級),注意,41位時間截不是儲存當前時間的時間截,而是儲存時間截的差值(當前時間截 - 開始時間截)
* 得到的值),這裡的的開始時間截,一般是我們的id生成器開始使用的時間,由我們程式來指定的(如下下面程式IdWorker類的startTime屬性)。41位的時間截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
* 10位的資料機器位,可以部署在1024個節點,包括5位datacenterId和5位workerId<br>
* 12位序列,毫秒內的計數,12位的計數順序號支援每個節點每毫秒(同一機器,同一時間截)產生4096個ID序號<br>
* 加起來剛好64位,為一個Long型。<br>
* SnowFlake的優點是,整體上按照時間自增排序,並且整個分散式系統內不會產生ID碰撞(由資料中心ID和機器ID作區分),並且效率較高,經測試,SnowFlake每秒能夠產生26萬ID左右。
*
* 演算法在的<<表示左移,左移後後面剩下部分會補0,並不是迴圈移位
* 二進位制轉換運算中負數的二進位制需要用補碼錶示
* 補碼 = 反碼 + 1 ; 補碼 = (原碼 - 1)再取反碼
*
* 本程式碼對欄位進行了調整,採用43位時間戳 3位機器標誌、3位資料標誌 14位
*/
public class SnowFlakeIdWorker {
// ==============================Fields===========================================
/** 開始時間截 (2018-01-01) 毫秒級時間戳 */
private final long twepoch = 1514736000000L;
/** 機器id所佔的位數 */
private final long workerIdBits = 3L;
/** 資料標識id所佔的位數 */
private final long datacenterIdBits = 3L;
/**
* 支援的最大機器id,結果是7 (這個移位演算法可以很快的計算出幾位二進位制數所能表示的最大十進位制數)
* -1L ^ (-1L << n)表示佔n個bit的數字的最大值是多少。舉個栗子:-1L ^ (-1L << 2)等於10進位制的3 ,即二進位制的11表示十進位制3。
*/
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
/**
* 支援的最大資料標識id,結果是7
* -1L 原碼 1000 0001 原碼是在符號位加標誌 正數 0 負數1
* 反碼1111 1110 正數原碼是其本身 負數是符號位不變其餘位取反
* 補碼1111 1111 原碼-1再取反
* -1L << 3 1111 1000
* -1L ^ (1111 1000) 1111 1000 ^ 1111 1111 結果為7
* */
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
/** 序列在id中佔的位數 */
private final long sequenceBits = 14L;
/** 機器ID向左移14位 */
private final long workerIdShift = sequenceBits;
/** 資料標識id向左移17位(14+3) */
private final long datacenterIdShift = sequenceBits + workerIdBits;
/** 時間截向左移20位(3+3+14) 機器碼+計數器 */
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
/** 生成序列的掩碼,這裡為16383 */
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
/** 工作機器ID(0~7) */
private long workerId;
/** 資料中心ID(0~7) */
private long datacenterId;
/** 毫秒內序列(0~16383) */
private long sequence = 0L;
/** 上次生成ID的時間截 */
private long lastTimestamp = -1L;
private static String wordid;
private static String dataid;
private static SnowFlakeIdWorker idWorker;
//==============================Constructors=====================================
/**
* 建構函式
* @param workerId 工作ID (0~7)
* @param datacenterId 資料中心ID (0~7)
*/
public SnowFlakeIdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/**
* 提供空構造用於建立物件呼叫方法
*/
public SnowFlakeIdWorker() {}
// ==============================Methods==========================================
/**
* 獲得下一個ID (該方法是執行緒安全的)
* @return SnowflakeId
*/
public synchronized long nextId() {
long timestamp = timeGen();
//如果當前時間小於上一次ID生成的時間戳,說明系統時鐘回退過這個時候應當丟擲異常
if (timestamp < lastTimestamp) {
throw new RuntimeException(
String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
//如果是同一時間生成的,則進行毫秒內序列
if (lastTimestamp == timestamp) {
//序列號 = 上次序列號+1 與 生成序列的掩碼
sequence = (sequence + 1) & sequenceMask;
//毫秒內序列溢位
if (sequence == 0) {
//阻塞到下一個毫秒,獲得新的時間戳
timestamp = tilNextMillis(lastTimestamp);
}
}
//時間戳改變,毫秒內序列重置
else {
sequence = 0L;
}
//上次生成ID的時間截(儲存時間戳)
lastTimestamp = timestamp;
//移位並通過或運算拼到一起組成64位的ID(或運算)
return ((timestamp - twepoch) << timestampLeftShift) //當前時間戳-開始時間戳 左移22位相當於兩個差的22位
| (datacenterId << datacenterIdShift) //資料中心Id左移17位
| (workerId << workerIdShift) // 機器id左移12位
| sequence; //毫秒內序列
}
/**
* 阻塞到下一個毫秒,直到獲得新的時間戳
* @param lastTimestamp 上次生成ID的時間截
* @return 當前時間戳
*/
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* 返回以毫秒為單位的當前時間
* @return 當前時間(毫秒)
*/
protected long timeGen() {
return System.currentTimeMillis();
}
//==============================Test=============================================
/** 測試 */
public static void main(String[] args) {
SnowFlakeIdWorker idWorker = new SnowFlakeIdWorker(1L, 1L);
for (int i = 0; i < 1000; i++) {
long id = idWorker.nextId();
System.out.println(Long.toBinaryString(id));
System.out.println(id);
}
}
/**
* 獲取資料庫中機器相關資訊並返回物件
* @return
*/
public SnowFlakeIdWorker getSnowFlakeIdWorker(){
//載入配置檔案
InputStream is = WeixinConfigUtils.class.getResourceAsStream("/snowflake.properties");
Properties properties = new Properties();
try {
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
throw new BasicRuntimeException("載入snowflake檔案異常" + e.getMessage());
}
wordid = properties.getProperty("worid");
dataid = properties.getProperty("dataid");
//校驗獲取到引數後在進行載入
if (StringUtils.isNotBlank(dataid) && StringUtils.isNotBlank(wordid)) {
idWorker = new SnowFlakeIdWorker(Long.valueOf(dataid), Long.valueOf(dataid));
return idWorker;
}
return null;
}
/**
* 獲取id
*/
public Long getId(){
SnowFlakeIdWorker snowFlakeIdWorker = getSnowFlakeIdWorker();
if (snowFlakeIdWorker != null) {
return snowFlakeIdWorker.nextId();
}
return null;
}
}
配置檔案:
##id生成器snowflake演算法的配置檔案
##機器號 範圍 0-7
worid = 1
##資料來源 用於區分機器上不同應用 範圍 0-7
dataid = 1
hibernate自定義id生成器類:
import java.io.Serializable;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.id.IdentifierGenerator;
/**
* 生成id的方法
* @author yangfuren
* @since 2018年01月10
*/
@Service
@Component
@Transactional
public class GenerateId implements IdentifierGenerator,Configurable{
@Resource
private SessionFactory sessionFactory;
public String workid;
public String dataid;
public SnowFlakeIdWorker snowFlakeIdWorker;
/**
* hibernate自定義主鍵生成規則必須實現 IdentifierGenerator generate 為預設方法
*/
@Override
public Serializable generate(SessionImplementor session, Object object)
throws HibernateException {
Long id = snowFlakeIdWorker.nextId();
if (id != null) {
return id;
}else {
return null;
}
}
/**
* 載入配置檔案中的資料初始化snowflakeworker類
*/
@Override
public void configure(Type type, Properties properties, Dialect d)
throws MappingException {
//載入配置檔案
InputStream is = GenerateId.class.getResourceAsStream("/snowflake.properties");
try {
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
throw new BasicRuntimeException(this,"載入snowflake檔案異常" + e.getMessage());
}
workid = properties.getProperty("workid");
dataid = properties.getProperty("dataid");
if (StringUtils.isNotBlank(dataid) && StringUtils.isNotBlank(workid)) {
snowFlakeIdWorker = new SnowFlakeIdWorker(Long.valueOf(workid), Long.valueOf(dataid));
}
}
public String getWorkid() {
return workid;
}
public void setWorkid(String workid) {
this.workid = workid;
}
public String getDataid() {
return dataid;
}
public void setDataid(String dataid) {
this.dataid = dataid;
}
}
測試實體類中配置:(這裡注意主鍵的配置)
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
@Entity
@Table(name = "test_obj")
public class TestObj implements Serializable{
private static final long serialVersionUID = 1L;
private Long id;
private String name;
**@Id
@GeneratedValue(generator = "idGenerator")
@GenericGenerator(name = "idGenerator", strategy = "com.wellness.platfront.common.util.GenerateId")**
@Column(name = "scheme_model_id", nullable = false)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Column(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
資料庫中測試結果: