1. 程式人生 > >Spring-boot+分散式下高效能全域性物件唯一ID生成器程式碼+例項演示

Spring-boot+分散式下高效能全域性物件唯一ID生成器程式碼+例項演示

     最近做起了資料優化的工作,主要是針對物件模型資料的匯入,這裡透漏下成效:利用mybatis的批量插入,1000條資料的匯入(insert)只用了52ms甚至根據機子的效能可以更快,當然這只是針對insert層的優化,還有一個更要命更耗時的就是,物件主鍵ID的獲取,如果採用資料庫自帶的自增主鍵的話,這種情況是很糟糕的,一旦涉及併發,必死無疑,當然這個概率比中彩票要高的很多,我不打算採用,採用了另一個,就是為物件建立一個全域性序列,即使在併發狀態下,資料庫也會針對當前序列建立一個序列鎖,保證併發或多執行緒下不會有相同的序列值產生,但是這樣太耗時間了,假如我匯入了1萬個物件,那麼就需要開一萬個session連線資料庫,以select獲得當前物件渴望的序列值,而且為了避免mybatis將select的結果放入快取導致後續取的序列值一樣(物件主鍵ID一樣的話,必然在insert的時候造成主鍵約束),還得每次select的時候清空mybatis的快取,這又是一筆開銷,剛開始沒辦法,採用的就是這種方法,結果導致:

     通過AOP切面技術,對單次請求的100萬條資料的後臺所對應的方法呼叫情況進行了一次統計,發現,整個百萬級資料的匯入insert假如消耗了1s的話,那麼這個百萬條資料的select操作可能要100s,沒錯,select比insert還要耗時,這不是吹噓,這就是赤裸裸的挑釁啊,怎麼辦?

     我們想一下,資料庫既然能創造出序列值,每次select的時候都會拿到不一樣的序列值,我們是不是可以在我們的專案中,將這種序列值模擬建立到記憶體中呢,如果這樣的話,效能肯定會大大提升,一是避免了mybatis開session訪問資料庫的開銷,二是避免了在併發下遇到序列鎖而造成的開銷

    話不多說,先來演示下,從資料庫拿1000個序列值的耗時情況(對照上面說的,結合下面的案列,我們來一起分析一下)


主要看結果,過程忽略,走一波測試,貼出結果:


上面的val真的是從資料庫裡面取的,真的是很耗時的:


再來看另一種方法,時間有限,不囉嗦了,直接上網上搜的一大神的demo:


IdWorker.java

import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;

/**
 * <p>名稱:IdWorker.java</p>
 * <p>描述:分散式自增長ID</p>
 * <pre>
 *     Twitter的 Snowflake JAVA實現方案
 * </pre>
 * 核心程式碼為其IdWorker這個類實現,其原理結構如下,我分別用一個0表示一位,用—分割開部分的作用:
 * 1||0---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000
 * 在上面的字串中,第一位為未使用(實際上也可作為long的符號位),接下來的41位為毫秒級時間,
 * 然後5位datacenter標識位,5位機器ID(並不算識別符號,實際是為執行緒標識),
 * 然後12位該毫秒內的當前毫秒內的計數,加起來剛好64位,為一個Long型。
 * 這樣的好處是,整體上按照時間自增排序,並且整個分散式系統內不會產生ID碰撞(由datacenter和機器ID作區分),
 * 並且效率較高,經測試,snowflake每秒能夠產生26萬ID左右,完全滿足需要。
 * <p>
 * 64位ID (42(毫秒)+5(機器ID)+5(業務編碼)+12(重複累加))
 */
public class IdWorker {
    // 時間起始標記點,作為基準,一般取系統的最近時間(一旦確定不能變動)
    private final static long twepoch = 1288834974657L;
    // 機器標識位數
    private final static long workerIdBits = 5L;
    // 資料中心標識位數
    private final static long datacenterIdBits = 5L;
    // 機器ID最大值
    private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
    // 資料中心ID最大值
    private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    // 毫秒內自增位
    private final static long sequenceBits = 12L;
    // 機器ID偏左移12位
    private final static long workerIdShift = sequenceBits;
    // 資料中心ID左移17位
    private final static long datacenterIdShift = sequenceBits + workerIdBits;
    // 時間毫秒左移22位
    private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
    /* 上次生產id時間戳 */
    private static long lastTimestamp = -1L;
    // 0,併發控制
    private long sequence = 0L;

    private final long workerId;
    // 資料標識id部分
    private final long datacenterId;

    public IdWorker(){
        this.datacenterId = getDatacenterId(maxDatacenterId);
        this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
    }
    /**
     * @param workerId
     *            工作機器ID
     * @param datacenterId
     *            序列號
     */
    public IdWorker(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;
    }
    /**
     * 獲取下一個ID
     *
     * @return
     */
    public synchronized long nextId() {
        long timestamp = timeGen();
        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;
        }
        lastTimestamp = timestamp;
        // ID偏移組合生成最終的ID,並返回ID
        long nextId = ((timestamp - twepoch) << timestampLeftShift)
                | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift) | sequence;

        return nextId;
    }

    private long tilNextMillis(final long lastTimestamp) {
        long timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }

    /**
     * <p>
     * 獲取 maxWorkerId
     * </p>
     */
    protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
        StringBuffer mpid = new StringBuffer();
        mpid.append(datacenterId);
        String name = ManagementFactory.getRuntimeMXBean().getName();
        if (!name.isEmpty()) {
         /*
          * GET jvmPid
          */
            mpid.append(name.split("@")[0]);
        }
      /*
       * MAC + PID 的 hashcode 獲取16個低位
       */
        return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
    }

    /**
     * <p>
     * 資料標識id部分
     * </p>
     */
    protected static long getDatacenterId(long maxDatacenterId) {
        long id = 0L;
        try {
            InetAddress ip = InetAddress.getLocalHost();
            NetworkInterface network = NetworkInterface.getByInetAddress(ip);
            if (network == null) {
                id = 1L;
            } else {
                byte[] mac = network.getHardwareAddress();
                id = ((0x000000FF & (long) mac[mac.length - 1])
                        | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
                id = id % (maxDatacenterId + 1);
            }
        } catch (Exception e) {
            System.out.println(" getDatacenterId: " + e.getMessage());
        }
        return id;
    }

    public static void main(String[] args) {
//        IdWorker idWorker = new IdWorker(31,31);
//        System.out.println("idWorker="+idWorker.nextId());
          IdWorker id = new IdWorker(0,1);
//        System.out.println("id="+id.nextId());
//        System.out.println(id.datacenterId);
//        System.out.println(id.workerId);
    
        for(int i=0;i<9000;i++){
        	System.err.println(id.nextId());
        }
    }


內部main測試下,是否可以跑通


怎麼還牽扯到分散式呢? 因為,不同的tomcat部署該專案,是可以配置當前專案裡這個IdWorker的機器ID(workID)和資料標識ID(datacenterID)的,比如我們在我們的Spring-Boot裡面如下配置:


然後在我們的Service裡面,通過註解拿到這兩個值


      還是要強調下這兩個值,這兩個值的組合直接作用在IdWorker的物件上,這就使不同的機器即使同一時間獲取ID,也會不一樣,具體可以自己下來進行一番測試,由於時間倉促,下面直接進行Service層IDWorker的效能測試


       不要問我效能確實提升了不少但是我拿到了這些ID有什麼用? 自己下去嘗試吧。

相關推薦

Spring-boot+分散式高效能全域性物件唯一ID生成器程式碼+例項演示

     最近做起了資料優化的工作,主要是針對物件模型資料的匯入,這裡透漏下成效:利用mybatis的批量插入,1000條資料的匯入(insert)只用了52ms甚至根據機子的效能可以更快,當然這只是針對insert層的優化,還有一個更要命更耗時的就是,物件主鍵ID的獲取,

2020最新的Spring Boot 分散式鎖的具體實現(內附程式碼

# 前言 面試總是會被問到有沒有用過分散式鎖、redis 鎖,大部分讀者平時很少接觸到,所以只能很無奈的回答 “沒有”。本文通過 Spring Boot 整合 redisson 來實現分散式鎖,並結合 demo 測試結果。 **首先看下大佬總結的圖** ![](https://img2020.cnbl

Spring Boot 工程整合全域性唯一ID生成器 Vesta

本文內容腦圖如下: 文章共 760字,閱讀大約需要 2分鐘 ! 概 述 在前一篇文章 《Spring Boot工程整合全域性唯一ID生成器 UidGenerator》 中給大家推薦了一款由百度開發的基於 Snowflake演算法實現的全域性唯一ID生成器 Uid

spring-boot框架的websocket服務

spring-boot websocket 這幾天在做web端實時展示服務端日誌文件新增內容的功能。要滿足實時的需求,我選擇的方案是在web端跟服務端建立一個websocket鏈接,由服務端通過tail -f 命令將文件新增內容發送給web端。關於websocket的介紹,可以參考這篇博文:http:

spring boot使用java配置全域性事務

首先在pom檔案中引入aop的jar包  <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-sta

Spring Boot 普通類呼叫Bean物件的一種方式

有時我們有一些特殊的需要,可能要在一個不被Spring管理的普通類中去呼叫Spring管理的bean物件的一些方法,比如一般SpringMVC工程在controller中通過 @Autowired private TestService testService; 注入Te

Elastic Job 入門教程(二)— Spring Boot框架是實現Elastic Job 指令碼作業(Script Job)

在Elastic Job 入門教程(一)— 與Spring Boot整合這篇文章中,我們簡單介紹了Spring Boot與Elastic Job 的整合,並簡單實現了SimpleJob型別作業。本章,我

spring boot 環境websocket 開發簡單示例

示例如下,需要注意的是, 類上需要打上@Scope("prototype")註解,否則socket就是單例的 socket的configurator屬性的配置類需要實現ApplicationContextAware,和程式碼裡面一樣的配置之後,server裡面才能成功注入spring

Spring Cloud -- 分散式服務註冊的地位和原理(5)

之前講過,為什麼需要註冊中心,當多個服務的時候如果還是需要靜態配置,既容易出錯有浪費人力,所以要使用註冊中心。那麼客戶端是怎麼從註冊中心發現服務的呢? 客戶端發現 客戶端從註冊中心獲取到服務清單後,使用輪詢,隨機,或者雜湊等機制(其實就是負載均衡的機制)從清單中獲取服務B的地址 然後在

spring Boot環境dubbo+zookeeper的一個基礎講解與示例

一、  Dubbo 概述 1.    為什麼需要Dubbo         網際網路的發展,網站應用的規模不斷擴大,常規的垂直應用架構已無法應對,分散式服務架構以及流動計算架構勢在必行,Dubbo是一個分散式服務框架,在這種情況下誕生的。現在核心業務抽取出來,作為獨立的服務,

Spring Boot 2 Webflux的全域性異常處理

本文首先將會回顧Spring 5之前的SpringMVC異常處理機制,然後主要講解Spring Boot 2 Webflux的全域性異常處理機制。 SpringMVC的異常處理 Spring 統一異常處理有 3 種方式,分別為: 使用 @ExceptionHandler 註解 實現 Handl

spring-boot 分散式定時任務鎖shedlock

先做翻譯: shedlock只做一件事,就是確保計劃任務最多同時執行一次;如果正在一個節點上執行任務,它將獲取一個鎖,以防止從另一個節點(或執行緒)執行相同任務。請注意,過去一個任務已在一個節點上執行,則其他節點上的執行不會等待,只會跳過它;           目前

Spring Boot框架使用WebSocket實現聊天功能

上一篇部落格我們介紹了在Spring Boot框架下使用WebSocket實現訊息推送,訊息推送是一對多,伺服器發訊息傳送給所有的瀏覽器,這次我們來看看如何使用WebSocket實現訊息的一對一發送,模擬的場景就是利用網頁來實現兩個人線上聊天。OK,那我們來看看這個要怎麼

spring boot 使用jackson 將資料物件轉換成json字串

首先在pom.xml中新增jackson依賴: <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</

Spring Boot 2使用Feign找不到@EnableFeignClients的解決辦法

最近在實踐Spring Boot 2+Spring Cloud(Finchley.M9),在用到Feign的時候發現@EnableFeignClients註解開不了,獨立使用Feign是可以的,但就是開啟不了Spring對Feign的支援.經過一番摸索終於把問題

Spring Boot 2.0版本 全域性配置跨域請求支援

Spring Boot 2.0以前全域性配置跨域主要是繼承WebMvcConfigurerAdapter @Configuration public class CorsConfig extends W

Spring-Boot--日誌操作【全域性異常捕獲訊息處理☞日誌控制檯輸出+日誌檔案記錄】

       最好的演示說明,不是上來就貼配置檔案和程式碼,而是,先來一波配置檔案的註釋,再來一波程式碼的測試過程,最後再出個技術在專案中的應用效果,這樣的循序漸進的方式,才會讓讀者更加清楚的理解一項技

初學spring boot 記錄過程,新建一個web專案(一)

只勾選web依賴就可以了,沒有勾選模板,因為不會模板語法。。。。pom.xml檔案<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0

Spring Boot學習分享(三)——全域性異常監控實現

Spring Boot實現全域性異常監控並且能夠處理ajax和web兩種請求 Spring Boot提供了@ControllerAdvice以及@RestControllerAdvice兩個註解來實現全域性異常監控,再通過@ExceptionHan

Spring Boot 分散式Session狀態儲存Redis

在使用spring boot做負載均衡的時候,多個app之間的session要保持一致,這樣負載到不同的app時候,在一個app登入之後,而打到另外一臺伺服器的時候,session丟失。 常規的解決方案都是使用:如apache使用mod_jk.conf。 在開發s