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