- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) >
- java算法之靜態(tài)內部類(lèi)實(shí)現雪花算法
在生成表主鍵ID時(shí),我們可以考慮主鍵自增 或者 UUID,但它們都有很明顯的缺點(diǎn)
主鍵自增:1、自增ID容易被爬蟲(chóng)遍歷數據。2、分表分庫會(huì )有ID沖突。
UUID: 1、太長(cháng),并且有索引碎片,索引多占用空間的問(wèn)題 2、無(wú)序。
雪花算法就很適合在分布式場(chǎng)景下生成唯一ID,它既可以保證唯一又可以排序。為了提高生產(chǎn)雪花ID的效率,
在這里面數據的運算都采用的是位運算
SnowFlake算法生成ID的結果是一個(gè)64bit大小的整數,它的結構如下圖:
算法描述:
1bit 因為二進(jìn)制中最高位是符號位,1表示負數,0表示正數。生成的ID都是正整數,所以最高位固定為0。
41bit-時(shí)間戳 精確到毫秒級,41位的長(cháng)度可以使用69年。時(shí)間位還有一個(gè)很重要的作用是可以根據時(shí)間進(jìn)行排序。
10bit-工作機器id 10位的機器標識,10位的長(cháng)度最多支持部署1024個(gè)節點(diǎn)。
12bit-序列號 序列號即一系列的自增id,可以支持同一節點(diǎn)同一毫秒生成多個(gè)ID序號。
12位(bit)可以表示的最大正整數是,即可以用0、1、2、3、....4094這4095個(gè)數字,來(lái)表示同一機器同一時(shí)間截(毫秒)內產(chǎn)生的4095個(gè)ID序號。
說(shuō)明 由于在Java中64bit的整數是long類(lèi)型,所以在Java中SnowFlake算法生成的id就是long來(lái)存儲的。
下面生成雪花ID的代碼可以用于線(xiàn)上分布式項目中來(lái)生成分布式主鍵ID,因為設計采用的靜態(tài)內部類(lèi)的單例模式,通過(guò)加synchronized鎖來(lái)保證在
同一個(gè)服務(wù)器線(xiàn)程安全。至于不同服務(wù)器其實(shí)是不相關(guān)的,因為它們的機器碼是不一致的,所以就算同一時(shí)刻兩臺服務(wù)器都產(chǎn)生了雪花ID,那也不會(huì )一樣的。
public class SnowIdUtils { /** * 私有的 靜態(tài)內部類(lèi) */ private static class SnowFlake { /** * 內部類(lèi)對象(單例模式) */ private static final SnowIdUtils.SnowFlake SNOW_FLAKE = new SnowIdUtils.SnowFlake(); /** * 起始的時(shí)間戳 */ private final long START_TIMESTAMP = 1557489395327L; /** * 序列號占用位數 */ private final long SEQUENCE_BIT = 12; /** * 機器標識占用位數 */ private final long MACHINE_BIT = 10; /** * 時(shí)間戳位移位數 */ private final long TIMESTAMP_LEFT = SEQUENCE_BIT + MACHINE_BIT; /** * 最大序列號 (4095) */ private final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT); /** * 最大機器編號 (1023) */ private final long MAX_MACHINE_ID = ~(-1L << MACHINE_BIT); /** * 生成id機器標識部分 */ private long machineIdPart; /** * 序列號 */ private long sequence = 0L; /** * 上一次時(shí)間戳 */ private long lastStamp = -1L; /** * 構造函數初始化機器編碼 */ private SnowFlake() { //模擬這里獲得本機機器編碼 long localIp = 4321; //localIp & MAX_MACHINE_ID最大不會(huì )超過(guò)1023,在左位移12位 machineIdPart = (localIp & MAX_MACHINE_ID) << SEQUENCE_BIT; } /** * 獲取雪花ID */ public synchronized long nextId() { long currentStamp = timeGen(); //避免機器時(shí)鐘回撥 while (currentStamp < lastStamp) { // //服務(wù)器時(shí)鐘被調整了,ID生成器停止服務(wù). throw new RuntimeException(String.format("時(shí)鐘已經(jīng)回撥. Refusing to generate id for %d milliseconds", lastStamp - currentStamp)); } if (currentStamp == lastStamp) { // 每次+1 sequence = (sequence + 1) & MAX_SEQUENCE; // 毫秒內序列溢出 if (sequence == 0) { // 阻塞到下一個(gè)毫秒,獲得新的時(shí)間戳 currentStamp = getNextMill(); } } else { //不同毫秒內,序列號置0 sequence = 0L; } lastStamp = currentStamp; //時(shí)間戳部分+機器標識部分+序列號部分 return (currentStamp - START_TIMESTAMP) << TIMESTAMP_LEFT | machineIdPart | sequence; } /** * 阻塞到下一個(gè)毫秒,直到獲得新的時(shí)間戳 */ private long getNextMill() { long mill = timeGen(); // while (mill <= lastStamp) { mill = timeGen(); } return mill; } /** * 返回以毫秒為單位的當前時(shí)間 */ protected long timeGen() { return System.currentTimeMillis(); } } /** * 獲取long類(lèi)型雪花ID */ public static long uniqueLong() { return SnowIdUtils.SnowFlake.SNOW_FLAKE.nextId(); } /** * 獲取String類(lèi)型雪花ID */ public static String uniqueLongHex() { return String.format("%016x", uniqueLong()); } /** * 測試 */ public static void main(String[] args) throws InterruptedException { //計時(shí)開(kāi)始時(shí)間 long start = System.currentTimeMillis(); //讓100個(gè)線(xiàn)程同時(shí)進(jìn)行 final CountDownLatch latch = new CountDownLatch(100); //判斷生成的20萬(wàn)條記錄是否有重復記錄 final Map<Long, Integer> map = new ConcurrentHashMap(); for (int i = 0; i < 100; i++) { //創(chuàng )建100個(gè)線(xiàn)程 new Thread(() -> { for (int s = 0; s < 2000; s++) { long snowID = SnowIdUtils.uniqueLong(); log.info("生成雪花ID={}",snowID); Integer put = map.put(snowID, 1); if (put != null) { throw new RuntimeException("主鍵重復"); } } latch.countDown(); }).start(); } //讓上面100個(gè)線(xiàn)程執行結束后,在走下面輸出信息 latch.await(); log.info("生成20萬(wàn)條雪花ID總用時(shí)={}", System.currentTimeMillis() - start); } }
從圖中我們可以得出
1、在100個(gè)線(xiàn)程并發(fā)下,生成20萬(wàn)條雪花ID的時(shí)間大概在1.6秒左右,所有所性能還是蠻ok的。
2、生成20萬(wàn)條雪花ID并沒(méi)有一條相同的ID,因為有一條就會(huì )拋出異常了。
我們思考41的二進(jìn)制,最大值也就41位都是1,也就是也就是說(shuō)41位可以表示個(gè)毫秒的值,轉化成單位年則是
年
我們可以通過(guò)代碼泡一下就知道了。
public static void main(String[] args) { //41位二進(jìn)制最小值 String minTimeStampStr = "00000000000000000000000000000000000000000"; //41位二進(jìn)制最大值 String maxTimeStampStr = "11111111111111111111111111111111111111111"; //轉10進(jìn)制 long minTimeStamp = new BigInteger(minTimeStampStr, 2).longValue(); long maxTimeStamp = new BigInteger(maxTimeStampStr, 2).longValue(); //一年總共多少毫秒 long oneYearMills = 1L * 1000 * 60 * 60 * 24 * 365; //算出最大可以多少年 System.out.println((maxTimeStamp - minTimeStamp) / oneYearMills); }
運行結果
所以說(shuō)雪花算法生成的ID,只能保證69年內不會(huì )重復,如果超過(guò)69年的話(huà),那就考慮換個(gè)服務(wù)器部署吧,并且要保證該服務(wù)器的ID和之前都沒(méi)有重復過(guò)。
以上就是java算法之靜態(tài)內部類(lèi)實(shí)現雪花算法的詳細內容,更多關(guān)于java算法的資料請關(guān)注腳本之家其它相關(guān)文章!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。
Copyright ? 2009-2021 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 珠海市特網(wǎng)科技有限公司 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站