手持pos機開發(fā)源碼

 新聞資訊3  |   2023-09-15 11:50  |  投稿人:pos機之家

網(wǎng)上有很多關(guān)于手持pos機開發(fā)源碼,分布式 ID 生成系統(tǒng) Leaf 的設(shè)計思路的知識,也有很多人為大家解答關(guān)于手持pos機開發(fā)源碼的問題,今天pos機之家(m.afbey.com)為大家整理了關(guān)于這方面的知識,讓我們一起來看下吧!

本文目錄一覽:

1、手持pos機開發(fā)源碼

手持pos機開發(fā)源碼

小伙伴們好呀,我是 4ye,今天來分享下最近研究的分布式 ID 生成系統(tǒng) —— Leaf ,一起來思考下這個分布式ID的設(shè)計吧

什么是分布式ID?

ID 最大的特點是 唯一

而分布式 ID,就是指分布式系統(tǒng)下的 ID,它是 全局唯一 的。

為啥需要分布式ID呢?

這就和 唯一 息息相關(guān)了。

比如我們用 MySQL 存儲數(shù)據(jù),一開始數(shù)據(jù)量不大,但是業(yè)務(wù)經(jīng)過一段時間的發(fā)展,單表數(shù)據(jù)每日劇增,最終突破 1000w,2000w …… 系統(tǒng)開始變慢了,此時我們已經(jīng)嘗試了 優(yōu)化索引讀寫分離 ,升級硬件,升級網(wǎng)絡(luò) 等操作,但是 單表瓶頸 還是來了,我們只能去 分庫分表 了。

而問題也隨著而來了,分庫分表后,如果還用 數(shù)據(jù)庫自增ID 的方式的話,那么在用戶表中,就會出現(xiàn) 兩個不同的用戶有相同的ID 的情況,這個是不能接受的。

分布式ID全局唯一 的特點,正是我們所需要的。

分布式ID的生成方式UUID數(shù)據(jù)庫自增ID (MySQL,Redis)雪花算法

基本就上面幾種了,UUID 的最大缺點就是太長,36個字符長度,而且無序,不適合。

而其他兩種的缺點還有辦法補救,可能這也是 Leaf 提供這兩種生成 ID 方式的原因。

項目簡介

Leaf ,分布式 ID 生成系統(tǒng),有兩種生成 ID 的方式:

號段模式Snowflake模式號段模式

數(shù)據(jù)庫自增ID 的基礎(chǔ)上進行優(yōu)化

增加一個 segement ,減少訪問數(shù)據(jù)庫的次數(shù)。雙 Buffer 優(yōu)化,提前緩存下一個 Segement,降低網(wǎng)絡(luò)請求的耗時(降低系統(tǒng)的TP999指標)

來自美團技術(shù)團隊

biz_tag用來區(qū)分業(yè)務(wù),max_id表示該biz_tag目前所被分配的ID號段的最大值,step表示每次分配的號段長度

沒優(yōu)化前,每次都從 db 獲取,現(xiàn)在獲取的頻率和 step 字段相關(guān)。

雙 Buffer 優(yōu)化思路

號段模式源碼解讀

segmentService 構(gòu)造方法

作用

配置 dataSource設(shè)置 mybatis實例化 SegmentIDGenImpl執(zhí)行 init 方法

這段代碼我也忘了 哈哈,已經(jīng)多久沒直接用 mybatis 了,還是重新去官網(wǎng)翻看的。

mybatis 官網(wǎng)例子

實例化 SegmentIDGenImpl 時,其中有兩個變量要留意下

SEGMENT_DURATION,智能調(diào)節(jié) step 的關(guān)鍵cache ,其中 SegmentBuffer 是雙 Buffer 的關(guān)鍵設(shè)計。

這里先不展開,看看 init 方法先。

SegmentIDGenImpl init 方法

作用

執(zhí)行 updateCacheFromDb 方法開后臺線程,每分鐘執(zhí)行一次 updateCacheFromDb() 方法

顯然,核心在 updateCacheFromDb

updateCacheFromDb 方法

這里就直接看源碼和我加的注釋

private void updateCacheFromDb() { logger.info("update cache from db"); StopWatch sw = new Slf4JStopWatch(); try { // 執(zhí)行 SELECT biz_tag FROM leaf_alloc 語句,獲取所有的 業(yè)務(wù)字段。 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 創(chuàng)建緩存 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"); } }

執(zhí)行完后,會出現(xiàn)這樣的 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 方法結(jié)束后,會將 initOk 設(shè)置為 true

項目啟動完畢后,我們就可以調(diào)用這個 API 了。

如圖,訪問 LeafController 中的 Segment API,可以獲取到一個 id。

SegmentIDGenImpl get 方法

可以看到,init 不成功會報錯。

以及會直接從 cache 中查找這個 key(biz_tag) , 沒有的話會報錯。

拿到這個 SegmentBuffer 時,還得看看它 init 了 沒有,沒有的話用雙檢查鎖的方式去更新

先來看下一眼 SegmentBuffer 的結(jié)構(gòu)

SegmentBuffer 類?updateSegmentFromDb 方法

這里就是更新緩存的方法了,主要是更新 Segment 的 value , max,step 字段。

可以看到有三個 if 分支,下面展開說

分支一:初始化

第一次,buffer 還沒 init,如上圖,執(zhí)行完后會更新 SegmentBuffer 的 step 和 minStep 字段。

分支二:第二次更新

這里主要是更新這個 updateTimestamp ,它的作用看分支三

分支三:剩下的更新

這里就比較有意思了,就是說如果這個號段在 15分鐘 內(nèi)用完了,那么它會擴大這個 step (不超過 10w),創(chuàng)建一個更大的 MaxId ,降低訪問 DB 的頻率。

那么,到這里,我們完成了 updateSegmentFromDb 方法,更新了 Segment 的 value , max,step 字段。

但是,我們不是每次 get 都走上面的流程,它還得走這個緩存方法

?getIdFromSegmentBuffer 方法

顯然,這是另一個重點。

如圖,在死循環(huán)中,先獲取讀鎖,拿到當前的號段 Segment,進行判斷

使用超過 10% 就開新線程去更新下一個號段沒超過則將 value (AtomicLong 類型)+1 ,小于 maxId 則直接返回。

這里要重點留意 讀寫鎖的使用 ,比如 開新線程時,使用了這個 寫鎖 ,里面的 nextReady 等變量使用了 volatile 修飾

這里的核心就是切換 Segment。

至此,號段模式結(jié)束。

優(yōu)缺點

信息安全如果ID是連續(xù)的,惡意用戶的扒取工作就非常容易做了,直接按照順序下載指定URL即可;如果是訂單號就更危險了,競對可以直接知道我們一天的單量。所以在一些應(yīng)用場景下,會需要ID無規(guī)則、不規(guī)則。—— 《Leaf——美團點評分布式ID生成系統(tǒng)》

美團

可以看到,這個號段模式的最大弊端就是 信息不安全,所以在使用時得三思,能不能用到這些業(yè)務(wù)中去。

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 持久順序節(jié)點的特性自動對 snowflake 節(jié)點配置 wokerID,不用手動配置。

時鐘回撥問題

img

Snowflake模式源碼解讀

這部分源碼就不一一展開了,直接展示核心代碼

SnowflakeZookeeperHolder init 方法

這里要注意調(diào)整這個 connectionTimeoutms 和 sessionTimeoutMs ,不然兩種模式都啟動的話,這個 zk 的 session 可能會超時,造成啟動失敗。

圖中流程

看看 zk 節(jié)點存不存在,不存在就創(chuàng)建同時將 worker id 保存到本地。創(chuàng)建定時任務(wù),更新 znode。

znode

worker Id

定時任務(wù)

SnowflakeIDGenImpl get 方法

這里直接看代碼和注釋了

@Override public synchronized Result get(String key) { long timestamp = timeGen(); // 發(fā)生了回撥,此刻時間小于上次發(fā)號時間 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 模式也了解完畢了。

總結(jié)

看完上面兩種模式,我覺得兩種模式都有它適用的場景,號段模式更適合對內(nèi)使用(比如 用戶ID),而如果你這個 ID 會被用戶看到,暴露出去有其他風險(比如爬蟲惡意爬取等),那就得多斟酌了,。而訂單號 就更適合用 snowflake 模式。

分布式ID 的特點

全局唯一趨勢遞增可反解(可選)信息安全(可選)參考資料Github 地址:https://github.com/Meituan-Dianping/Leaf/blob/master/README_CN.mdLeaf——美團點評分布式ID生成系統(tǒng):https://tech.meituan.com/2017/04/21/mt-leaf.html分布式id生成方案總結(jié):https://www.cnblogs.com/javaguide/p/11824105.html

喜歡的小伙伴記得關(guān)注點點贊哦,全網(wǎng)同名[狗頭]

以上就是關(guān)于手持pos機開發(fā)源碼,分布式 ID 生成系統(tǒng) Leaf 的設(shè)計思路的知識,后面我們會繼續(xù)為大家整理關(guān)于手持pos機開發(fā)源碼的知識,希望能夠幫助到大家!

轉(zhuǎn)發(fā)請帶上網(wǎng)址:http://m.afbey.com/newstwo/112401.html

你可能會喜歡:

版權(quán)聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻,該文觀點僅代表作者本人。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔相關(guān)法律責任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權(quán)/違法違規(guī)的內(nèi)容, 請發(fā)送郵件至 babsan@163.com 舉報,一經(jīng)查實,本站將立刻刪除。