網上有很多關于手持pos機開發源碼,分布式 ID 生成系統 Leaf 的設計思路的知識,也有很多人為大家解答關于手持pos機開發源碼的問題,今天pos機之家(www.tonybus.com)為大家整理了關于這方面的知識,讓我們一起來看下吧!
本文目錄一覽:
手持pos機開發源碼
小伙伴們好呀,我是 4ye,今天來分享下最近研究的分布式 ID 生成系統 —— Leaf ,一起來思考下這個分布式ID的設計吧
什么是分布式ID?ID 最大的特點是 唯一
而分布式 ID,就是指分布式系統下的 ID,它是 全局唯一 的。
為啥需要分布式ID呢?這就和 唯一 息息相關了。
比如我們用 MySQL 存儲數據,一開始數據量不大,但是業務經過一段時間的發展,單表數據每日劇增,最終突破 1000w,2000w …… 系統開始變慢了,此時我們已經嘗試了 優化索引, 讀寫分離 ,升級硬件,升級網絡 等操作,但是 單表瓶頸 還是來了,我們只能去 分庫分表 了。
而問題也隨著而來了,分庫分表后,如果還用 數據庫自增ID 的方式的話,那么在用戶表中,就會出現 兩個不同的用戶有相同的ID 的情況,這個是不能接受的。
而 分布式ID全局唯一 的特點,正是我們所需要的。
分布式ID的生成方式UUID數據庫自增ID (MySQL,Redis)雪花算法基本就上面幾種了,UUID 的最大缺點就是太長,36個字符長度,而且無序,不適合。
而其他兩種的缺點還有辦法補救,可能這也是 Leaf 提供這兩種生成 ID 方式的原因。
項目簡介Leaf ,分布式 ID 生成系統,有兩種生成 ID 的方式:
號段模式Snowflake模式號段模式在 數據庫自增ID 的基礎上進行優化
增加一個 segement ,減少訪問數據庫的次數。雙 Buffer 優化,提前緩存下一個 Segement,降低網絡請求的耗時(降低系統的TP999指標)來自美團技術團隊
biz_tag用來區分業務,max_id表示該biz_tag目前所被分配的ID號段的最大值,step表示每次分配的號段長度
沒優化前,每次都從 db 獲取,現在獲取的頻率和 step 字段相關。
雙 Buffer 優化思路
號段模式源碼解讀
segmentService 構造方法作用
配置 dataSource設置 mybatis實例化 SegmentIDGenImpl執行 init 方法這段代碼我也忘了 哈哈,已經多久沒直接用 mybatis 了,還是重新去官網翻看的。
mybatis 官網例子
實例化 SegmentIDGenImpl 時,其中有兩個變量要留意下
SEGMENT_DURATION,智能調節 step 的關鍵cache ,其中 SegmentBuffer 是雙 Buffer 的關鍵設計。這里先不展開,看看 init 方法先。
SegmentIDGenImpl init 方法作用
執行 updateCacheFromDb 方法開后臺線程,每分鐘執行一次 updateCacheFromDb() 方法顯然,核心在 updateCacheFromDb
updateCacheFromDb 方法這里就直接看源碼和我加的注釋
private void updateCacheFromDb() { logger.info("update cache from db"); StopWatch sw = new Slf4JStopWatch(); try { // 執行 SELECT biz_tag FROM leaf_alloc 語句,獲取所有的 業務字段。 List<String> dbTags = dao.getAllTags(); if (dbTags == null || dbTags.isEmpty()) { return; } // 緩存中的 biz_tag List<String> cacheTags = new ArrayList<String>(cache.keySet()); // 要插入的 db 中的 biz_tag Set<String> insertTagsSet = new HashSet<>(dbTags); // 要移除的緩存中的 biz_tag Set<String> removeTagsSet = new HashSet<>(cacheTags); // 緩存中有的話,不用再插入,從 insertTagsSet 中移除 for (int i = 0; i < cacheTags.size(); i++) { String tmp = cacheTags.get(i); if (insertTagsSet.contains(tmp)) { insertTagsSet.remove(tmp); } } // 為新增的 biz_tag 創建緩存 SegmentBuffer for (String tag : insertTagsSet) { SegmentBuffer buffer = new SegmentBuffer(); buffer.setKey(tag); Segment segment = buffer.getCurrent(); segment.setValue(new AtomicLong(0)); segment.setMax(0); segment.setStep(0); cache.put(tag, buffer); logger.info("Add tag {} from db to IdCache, SegmentBuffer {}", tag, buffer); } // db中存在的,從要移除的 removeTagsSet 移除。 for (int i = 0; i < dbTags.size(); i++) { String tmp = dbTags.get(i); if (removeTagsSet.contains(tmp)) { removeTagsSet.remove(tmp); } } // 從 cache 中移除不存在的 bit_tag。 for (String tag : removeTagsSet) { cache.remove(tag); logger.info("Remove tag {} from IdCache", tag); } } catch (exception e) { logger.warn("update cache from db exception", e); } finally { sw.stop("updateCacheFromDb"); } }
執行完后,會出現這樣的 log
Add tag leaf-segment-test from db to IdCache, SegmentBuffer SegmentBuffer{key='leaf-segment-test', segments=[Segment(value:0,max:0,step:0), Segment(value:0,max:0,step:0)], currentPos=0, nextReady=false, initOk=false, threadRunning=false, step=0, minStep=0, updatetimestamp=0}
最后 init 方法結束后,會將 initOk 設置為 true。
項目啟動完畢后,我們就可以調用這個 API 了。
如圖,訪問 LeafController 中的 Segment API,可以獲取到一個 id。
SegmentIDGenImpl get 方法可以看到,init 不成功會報錯。
以及會直接從 cache 中查找這個 key(biz_tag) , 沒有的話會報錯。
拿到這個 SegmentBuffer 時,還得看看它 init 了 沒有,沒有的話用雙檢查鎖的方式去更新
先來看下一眼 SegmentBuffer 的結構
SegmentBuffer 類?updateSegmentFromDb 方法這里就是更新緩存的方法了,主要是更新 Segment 的 value , max,step 字段。
可以看到有三個 if 分支,下面展開說
分支一:初始化第一次,buffer 還沒 init,如上圖,執行完后會更新 SegmentBuffer 的 step 和 minStep 字段。
分支二:第二次更新這里主要是更新這個 updateTimestamp ,它的作用看分支三
分支三:剩下的更新這里就比較有意思了,就是說如果這個號段在 15分鐘 內用完了,那么它會擴大這個 step (不超過 10w),創建一個更大的 MaxId ,降低訪問 DB 的頻率。
那么,到這里,我們完成了 updateSegmentFromDb 方法,更新了 Segment 的 value , max,step 字段。
但是,我們不是每次 get 都走上面的流程,它還得走這個緩存方法
?getIdFromSegmentBuffer 方法顯然,這是另一個重點。
如圖,在死循環中,先獲取讀鎖,拿到當前的號段 Segment,進行判斷
使用超過 10% 就開新線程去更新下一個號段沒超過則將 value (AtomicLong 類型)+1 ,小于 maxId 則直接返回。這里要重點留意 讀寫鎖的使用 ,比如 開新線程時,使用了這個 寫鎖 ,里面的 nextReady 等變量使用了 volatile 修飾
這里的核心就是切換 Segment。
至此,號段模式結束。
優缺點信息安全:如果ID是連續的,惡意用戶的扒取工作就非常容易做了,直接按照順序下載指定URL即可;如果是訂單號就更危險了,競對可以直接知道我們一天的單量。所以在一些應用場景下,會需要ID無規則、不規則?!?《Leaf——美團點評分布式ID生成系統》
美團
可以看到,這個號段模式的最大弊端就是 信息不安全,所以在使用時得三思,能不能用到這些業務中去。
Snowflake模式雪花算法,核心就是將 64bit 分段,用來表示時間,機器,序列號等。
41-bit的時間可以表示(1L<<41)/(1000L360024*365)=69年的時間,10-bit機器可以分別表示1024臺機器。
12個自增序列號可以表示2^12個ID,理論上snowflake方案的QPS約為 2^12 * 1000 = 409.6w/s
這里使用 Zookeeper 持久順序節點的特性自動對 snowflake 節點配置 wokerID,不用手動配置。
時鐘回撥問題
img
Snowflake模式源碼解讀這部分源碼就不一一展開了,直接展示核心代碼
SnowflakeZookeeperHolder init 方法這里要注意調整這個 connectionTimeoutms 和 sessionTimeoutMs ,不然兩種模式都啟動的話,這個 zk 的 session 可能會超時,造成啟動失敗。
圖中流程
看看 zk 節點存不存在,不存在就創建同時將 worker id 保存到本地。創建定時任務,更新 znode。znode
worker Id
定時任務
SnowflakeIDGenImpl get 方法這里直接看代碼和注釋了
@Override public synchronized Result get(String key) { long timestamp = timeGen(); // 發生了回撥,此刻時間小于上次發號時間 if (timestamp < lastTimestamp) { long offset = lastTimestamp - timestamp; if (offset <= 5) { try { //時間偏差大小小于5ms,則等待兩倍時間 wait(offset << 1); timestamp = timeGen(); //還是小于,拋異常并上報 if (timestamp < lastTimestamp) { return new Result(-1, Status.EXCEPTION); } } catch (InterruptedException e) { LOGGER.error("wait interrupted"); return new Result(-2, Status.EXCEPTION); } } else { return new Result(-3, Status.EXCEPTION); } } if (lastTimestamp == timestamp) { // sequenceMask = ~(-1L << 12 ) = 4095 二進制即 12 個1 sequence = (sequence + 1) & sequenceMask; if (sequence == 0) { //seq 為0的時候表示是下一毫秒時間開始對seq做隨機 sequence = RANDOM.nextInt(100); timestamp = tilNextMillis(lastTimestamp); } } else { //如果是新的ms開始 sequence = RANDOM.nextInt(100); } lastTimestamp = timestamp; // timestampLeftShift = 22, workerIdShift = 12 long id = ((timestamp - twepoch) << timestampLeftShift) | (workerId << workerIdShift) | sequence; return new Result(id, Status.SUCCESS); } protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } protected long timeGen() { return System.currentTimeMillis(); }API 效果
生成 ID
反解 ID
至此,這個 Snowflake 模式也了解完畢了。
總結看完上面兩種模式,我覺得兩種模式都有它適用的場景,號段模式更適合對內使用(比如 用戶ID),而如果你這個 ID 會被用戶看到,暴露出去有其他風險(比如爬蟲惡意爬取等),那就得多斟酌了,。而訂單號 就更適合用 snowflake 模式。
分布式ID 的特點
全局唯一趨勢遞增可反解(可選)信息安全(可選)參考資料Github 地址:https://github.com/Meituan-Dianping/Leaf/blob/master/README_CN.mdLeaf——美團點評分布式ID生成系統:https://tech.meituan.com/2017/04/21/mt-leaf.html分布式id生成方案總結:https://www.cnblogs.com/javaguide/p/11824105.html喜歡的小伙伴記得關注點點贊哦,全網同名[狗頭]
以上就是關于手持pos機開發源碼,分布式 ID 生成系統 Leaf 的設計思路的知識,后面我們會繼續為大家整理關于手持pos機開發源碼的知識,希望能夠幫助到大家!
