pos機接收返回超時,100億訂單超時處理

 新聞資訊2  |   2023-05-21 11:49  |  投稿人:pos機之家

網上有很多關于pos機接收返回超時,100億訂單超時處理的知識,也有很多人為大家解答關于pos機接收返回超時的問題,今天pos機之家(www.tonybus.com)為大家整理了關于這方面的知識,讓我們一起來看下吧!

本文目錄一覽:

1、pos機接收返回超時

pos機接收返回超時

背景:

超時處理,是一個很有技術難度的問題。

所以很多的小伙伴,在寫簡歷的時候,喜歡把這個技術難題寫在簡歷里邊, 體現自己高超的技術水平。

在40歲老架構師 尼恩的讀者社區(50+個)中,尼恩經常指導大家 優化簡歷。

最近,有小伙伴寫簡歷,就寫了這問題:

通過 定時任務+ 數據分片的方式,進行訂單的超時處理。

尼恩去問他,怎么進行分片、怎么進行調度的。

小伙伴就開始一半糊涂、一半清晰。也就是說,他也能講一些細節,比如怎么分片,怎么調度,感覺好像是接觸過。

為啥說一半糊涂呢? 就是他的方案的 空白點太多, 隨便找幾個點發問, 就講不清楚了。

一半糊涂、一半清晰的方案,問題很嚴重,為啥呢? 從面試官的角度來說,這 就是沒有真正干過, 說明是盜用的其他人的方案。

這種案例,在尼恩的招人生涯中見得太多。面試過N多這種看上去牛逼轟轟,實際上 稀里糊涂的面試者,沒有一次讓他們過的。

那么問題來了,訂單的超時處理的方案,具體是什么樣的呢?

這里尼恩給大家做一下系統化、體系化的 訂單的超時處理的方案,使得大家可以充分展示一下大家雄厚的 “技術肌肉”,讓面試官愛到 “不能自已、口水直流”。

也一并把這個題目以及參考答案,收入咱們的《尼恩java面試寶典 PDF》,供后面的小伙伴參考,提升大家的 3高 架構、設計、開發水平。

注:本文以 PDF 持續更新,最新尼恩 架構筆記、面試題 的PDF文件,請到《技術自由圈》公號領取

100億訂單的超時處理難題

100億訂單的超時處理,是一個很有技術難度的問題。

比如,用戶下了一個訂單之后,需要在指定時間內(例如30分鐘)進行支付,在到期之前可以發送一個消息提醒用戶進行支付。

下面是一個訂單的流程:

如上圖所示,實際的生產場景中,一個訂單流程中有許多環節要用到定時處理,比如:

買家超時未付款:超過15分鐘沒有支付,訂單自動取消。商家超時未發貨:商家超過1個月沒發貨,訂單自動取消。買家超時未收貨:商家發貨后,買家沒有在14天內點擊確認收貨,則自動收貨。

海量任務的定時處理方案

基于內存的延遲隊列/優先級隊列處理基于內存的時間輪調度基于分布式隊列延遲消息的定時方案基于分布式K-V組件(如Redis)過期時間的定時方案

一些消息中間件的Broker端內置了延遲消息支持的能力

方案1:基于內存的延時隊列

JDK中提供了一種延遲隊列數據結構DelayQueue,其本質是封裝了PriorityQueue,可以把元素進行排序。

Java 的Timer、JUC的延遲調度,最終都是基于 PriorityQueue。

基于內存的延時隊列進行調度的邏輯,其實比較簡單,具體如下:

把訂單插入DelayQueue中,以超時時間作為排序條件,將訂單按照超時時間從小到大排序。起一個線程不停輪詢隊列的頭部,如果訂單的超時時間到了,就出隊進行超時處理,并更新訂單狀態到數據庫中。為了防止機器重啟導致內存中的DelayQueue數據丟失,每次機器啟動的時候,需要從數據庫中初始化未結束的訂單,加入到DelayQueue中。

基于內存的延時隊列調度的優點和缺點:

優點:

簡單,不需要借助其他第三方組件,成本低。

缺點:

所有超時處理訂單都要加入到DelayQueue中,占用內存大。沒法做到分布式處理,只能在集群中選一臺leader專門處理,效率低。不適合訂單量比較大的場景。

方案2:RocketMQ的定時消息

RocketMQ支持任意秒級的定時消息,如下圖所示:

使用門檻低,只需要在發送消息的時候設置延時時間即可,以 java 代碼為例:

MessageBuilder messageBuilder = null;Long deliverTimeStamp = System.currentTimeMillis() + 10L * 60 * 1000; //延遲10分鐘Message message = messageBuilder.setTopic("topic") //設置消息索引鍵,可根據關鍵字精確查找某條消息。 .setKeys("messageKey") //設置消息Tag,用于消費端根據指定Tag過濾消息。 .setTag("messageTag") //設置延時時間 .setDeliverytimestamp(deliverTimeStamp) //消息體 .setBody("messageBody".getBytes()) .build();SendReceipt sendReceipt = producer.send(message);System.out.println(sendReceipt.getMessageId());

RocketMQ的定時消息是如何實現的呢?

RocketMQ 定時消息的推送,主要分為 : 延遲消息、定時消息。

在 RocketMQ 4.x 版本,使用 延遲消息來實現消息的多個級別延遲消息——粗粒度延遲。在 RocketMQ 5.x 版本,使用定時消息來實現消息的更精準定時消息,——細粒度延遲。

RocketMQ 4.x 版本只支持 延遲消息,有一些局限性。而 RocketMQ 5.x 版本引入了定時消息,彌補了 延遲消息的不足。

RocketMQ 4.x 粗粒度 延遲消息

RocketMQ 的 延遲消息是指 Producer 發送消息后,Consumer 不會立即消費,而是需要等待固定的時間才能消費。

在一些場景下, 延遲消息是很有用的,比如電商場景下關閉 30 分鐘內未支付的訂單。

使用 延遲消息非常簡單,只需要給消息的 delayTimeLevel 屬性賦值就可以。

發送 分級定時消息,參考下面代碼:

Message message = new Message("TestTopic", ("Hello scheduled message " + i).getBytes());//第 3 個級別,10smessage.setDelayTimeLevel(3);producer.send(message);

延遲消息有 18 個級別,如下:

private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";

實現原理

延遲消息的實現原理如下圖:

Producer 把消息發送到 Broker 后,Broker 判斷是否 延遲消息,

如果是,首先會把消息投遞到延時隊列(Topic = SCHEDULE_TOPIC_XXXX,queueId = delayTimeLevel - 1)。

另外,由于18 個級別的延遲,所以定時任務線程池會有 18 個線程來對延時隊列進行調度,每個線程調度一個延時級別,

調度任務把 延遲消息再投遞到原始隊列,這樣 Consumer 就可以拉取到了到期的消息。

RocketMQ 4.x 粗粒度 延遲消息存在不足

RocketMQ 4.x 延遲消息存在著一些不足:

1、延時級別只有 18 個,粒度很粗,并不能滿足所有場景;

"1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";

2、可以通過修改 messageDelayLevel 配置來自定義延時級別,雖然可以修改,但是不靈活

比如一個在大規模的平臺上,延時級別成百上千,而且隨時可能增加新的延時時間;

3.延時時間不準確,后臺的定時線程可能會因為處理消息量大導致延時誤差大。

RocketMQ 5.x 細粒度定時消息

為了彌補 延遲消息的不足,RocketMQ 5.0 引入了細粒度定時消息。

經典的時間輪算法如下:

時間輪類似map,key 為時間的刻度,value為此刻度所對應的任務列表。

一般來說,可以理解為一種環形結構,像鐘表一樣被分為多個 slot 槽位。

每個 slot 代表一個時間段,每個 slot 中可以存放多個任務,使用的是鏈表結構保存該時間段到期的所有任務。

時間輪通過一個時針隨著時間一個個 slot 轉動,并執行 slot 中的所有到期任務。

從內部結構來看,是一個Bucket 數組,每個 Bucket 表示時間輪中一個 slot。

從 Bucket 的結構定義可以看出,Bucket 內部是一個雙向鏈表結構,雙向鏈表的每個節點持有一個 task 對象,task 代表一個定時任務。每個 Bucket 都包含雙向鏈表 head 和 tail 兩個 task 節點,這樣就可以實現不同方向進行鏈表遍歷

時間輪的算法、以及時間輪的演進,非常重要

內容太多,這里不做展開,具體請看尼恩的3高架構筆記《徹底穿透Caffeine底層源碼和架構》PDF,里邊介紹了三個超高并發組件:時間輪、 多級時間輪、條帶環狀結構、MPSC隊列,大家一定認真看看。

RocketMQ 5.X的時間輪

RocketMQ 定時消息引入了秒級的時間輪算法。注意,是 秒級時間輪。

從源碼來看,RocketMQ 定義了一個 7 天的以秒為單位的時間輪

注意:時間刻度為1s,沒有再細,比如 10ms、100ms之類的 。

作為參考下面提供一個一分鐘的,以1s為刻度的時間輪,如下圖:

圖中是一個 60s 的時間輪,每一個槽位是一個鏈表,鏈表里邊的節點,通過TimerLog節點結構來記錄不同時刻的消息。

所以,RocketMQ 使用 TimerWheel 來描述時間輪,TimerWheel 中每一個時間節點是一個 Slot,Slot 保存了這個延時時間的 TimerLog 信息的鏈表。

Slot 數據結構如下圖:

參考下面代碼:

//類 TimerWheelpublic void putSlot(long timeMs, long firstPos, long lastPos, int num, int magic) { localBuffer.get().position(getSlotIndex(timeMs) * Slot.SIZE); localBuffer.get().putLong(timeMs / precisionMs); localBuffer.get().putLong(firstPos); localBuffer.get().putLong(lastPos); localBuffer.get().putInt(num); localBuffer.get().putInt(magic);}

綁定時間輪

時間通過TimerWheel來描述時間輪不同的時刻,

并且,對于所處于同一個刻度的的消息,組成一個槽位的鏈表,每一個定時消息,有一個TimerLog 描述定時相關的信息

TimerLog 有一個核心字段prevPos,同一個時間輪槽位里邊的TimerLog ,會通過prevPos串聯成一個鏈表.

首先看一下 TimerLog 保存的數據結構,如下圖:

參考下面代碼:

//TimerMessageStore類ByteBuffer tmpBuffer = timerLogBuffer;tmpBuffer.clear();tmpBuffer.putInt(TimerLog.UNIT_SIZE); //sizetmpBuffer.putLong(slot.lastPos); //prev postmpBuffer.putInt(magic); //magictmpBuffer.putLong(tmpWriteTimeMs); //currWriteTimetmpBuffer.putInt((int) (delayedTime - tmpWriteTimeMs)); //delayTimetmpBuffer.putLong(offsetPy); //offsettmpBuffer.putInt(sizePy); //sizetmpBuffer.putInt(hashTopicForMetrics(realTopic)); //hashcode of real topictmpBuffer.putLong(0); //reserved value, just set to 0 nowlong ret = timerLog.append(tmpBuffer.array(), 0, TimerLog.UNIT_SIZE);if (-1 != ret) { // If it's a delete message, then slot's total num -1 // TODO: check if the delete msg is in the same slot with "the msg to be deleted". timerWheel.putSlot(delayedTime, slot.firstPos == -1 ? ret : slot.firstPos, ret, isDelete ? slot.num - 1 : slot.num + 1, slot.magic);}

時間輪上的時間指針

時間輪上,會有一個指向當前時間的指針定時地移動到下一個時間(秒級)。

TimerWheel中的每一格代表著一個時刻,同時會有一個firstPos指向槽位鏈的首條TimerLog記錄的地址,一個lastPos指向這個槽位鏈最后一條TimerLog的記錄的地址。

當需要新增一條記錄的時候,

例如現在我們要新增一個 “1-4”。

那么就將新記錄的 prevPos 指向當前的 lastPos,即 “1-3”,然后修改 lastPos 指向 “1-4”。

這樣就將同一個刻度上面的 TimerLog 記錄全都串起來了。

內容太多,這里不做展開,具體請看尼恩的3高架構筆記《徹底穿透Caffeine底層源碼和架構》PDF,里邊介紹了三個超高并發組件:時間輪、 多級時間輪、條帶環狀結構、MPSC隊列,大家一定認真看看。

精準定時消息發送方式

使用 RocketMQ 定時消息時,客戶端定義精準定時消息的示例代碼如下:

//定義消息投遞時間// deliveryTime = 未來的一個時間戳org.apache.rocketmq.common.message.Message messageExt = this.sendMessageActivity.buildMessage(null, Lists.newArrayList( Message.newBuilder() .setTopic(Resource.newBuilder() .setName(TOPIC) .build()) .setSystemProperties(SystemProperties.newBuilder() .setMessageId(msgId) .setQueueId(0) .setMessageType(MessageType.DELAY) .setDeliveryTimestamp(Timestamps.fromMillis(deliveryTime)) .setBornTimestamp(Timestamps.fromMillis(System.currentTimeMillis())) .setBornHost(StringUtils.defaultString(RemotingUtil.getLocalAddress(), "127.0.0.1:1234")) .build()) .setBody(ByteString.copyFromUtf8("123")) .build() ),Resource.newBuilder().setName(TOPIC).build()).get(0);

源碼分析:消息投遞原理

客戶端SDK代碼中,Producer 創建消息時給消息傳了一個系統屬性 deliveryTimestamp,

這個屬性指定了消息投遞的時間,并且封裝到消息的 TIMER_DELIVER_MS 屬性,代碼如下:

protected void fillDelayMessageProperty(apache.rocketmq.v2.Message message, org.apache.rocketmq.common.message.Message messageWithHeader) { if (message.getSystemProperties().hasDeliveryTimestamp()) { Timestamp deliveryTimestamp = message.getSystemProperties().getDeliveryTimestamp(); //delayTime 這個延時時間默認不能超過 1 天,可以配置 long deliveryTimestampMs = Timestamps.toMillis(deliveryTimestamp); validateDelayTime(deliveryTimestampMs); //... String timestampString = String.valueOf(deliveryTimestampMs); //MessageConst.PROPERTY_TIMER_DELIVER_MS="TIMER_DELIVER_MS" MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_TIMER_DELIVER_MS, timestampString); }}

服務端代碼中,Broker 收到這個消息后,判斷到 TIMER_DELIVER_MS 這個屬性是否有值,

如果有,就會把這個消息投遞到 Topic 是 rmq_sys_wheel_timer 的隊列中,這個只有一個分區,queueId 是 0,作為中轉的隊列

中轉的時候,同時會保存原始消息的 Topic、queueId、投遞時間(TIMER_OUT_MS)。

TimerMessageStore 中有個定時任務 TimerEnqueueGetService 會從 rmq_sys_wheel_timer 這個 Topic 中讀取消息,然后封裝 TimerRequest 請求并放到內存隊列 enqueuePutQueue。

一個異步任務TimerEnqueuePutService 從上面的 enqueuePutQueue 取出 TimerRequest 然后封裝成 TimerLog,然后綁定到時間輪

TimerLog 是怎么和時間輪關聯起來的呢?

RocketMQ 使用 TimerWheel 來描述時間輪,

從源碼上看,RocketMQ 定義了一個 7 天的以秒為單位的時間輪。TimerWheel 中每一個時間節點是一個 Slot,Slot 保存了這個延時時間的 TimerLog 信息。

數據結構如下圖:

參考下面代碼:

//類 TimerWheelpublic void putSlot(long timeMs, long firstPos, long lastPos, int num, int magic) { localBuffer.get().position(getSlotIndex(timeMs) * Slot.SIZE); localBuffer.get().putLong(timeMs / precisionMs); localBuffer.get().putLong(firstPos); localBuffer.get().putLong(lastPos); localBuffer.get().putInt(num); localBuffer.get().putInt(magic);}

通過TimerWheel的 putSlot方法,TimerLog 跟 時間輪就綁定起來了,見下圖:

如果時間輪的一個時間節點(Slot)上有一條新的消息到來,那只要新建一個 TimerLog,然后把它的指針指向該時間節點的最后一個 TimerLog,然后把 Slot 的 lastPos 屬性指向新建的這個 TimerLog,如下圖:

時間輪中的 定時消息異步處理總流程

終于,咱們的定時消息進入到時間輪了。

那么,隨著時間刻度的步進, 上面的消息,怎么轉移到原始的topic的 分區呢?

由于 rocketmq的源碼是超高性能的,所以,這里有N個隊列做緩沖,有N個任務

這里用到 5 個定時任務和 3個隊列來實現。

定時消息的處理流程如下圖:

時間輪轉動

轉動時間輪時,TimerDequeueGetService 這個定時任務從當前時間節點(Slot)對應的 TimerLog 中取出數據,封裝成 TimerRequest 放入 dequeueGetQueue 隊列。

CommitLog 中讀取消息

定時任務 TimerDequeueGetMessageService 從隊列 dequeueGetQueue 中拉取 TimerRequest 請求,然后根據 TimerRequest 中的參數去 CommitLog(MessageExt) 中查找消息,查出后把消息封裝到 TimerRequest 中,然后把 TimerRequest 寫入 dequeuePutQueue 這個隊列。

寫入原隊列

定時任務 TimerDequeuePutMessageService 從 dequeuePutQueue 隊列中獲取消息,

把消息轉換成原始消息,投入到原始隊列中,這樣消費者就可以拉取到了。

RocketMQ的定時消息的優點和不足

要注意的地方

對于定時時間的定義,客戶端、Broker 和時間輪的默認最大延時時間定義是不同的,使用的時候需要注意。

RocketMQ的定時消息優點

精度高,支持任意時刻。使用門檻低,和使用普通消息一樣。

RocketMQ的定時消息缺點

時長的使用限制:

定時和延時消息的msg.setStartDeliverTime參數可設置40天內的任何時刻(單位毫秒),超過40天消息發送將失敗。

設置定時和延時消息的投遞時間后,從中轉隊列調度到了原始的消息隊列之后,依然受3天的消息保存時長限制。例如,設置定時消息5天后才能被消費,如果第5天后一直沒被消費,那么這條消息將在第8天被刪除。

海量 消息場景,存儲成本高:

在海量訂單場景中,如果每個訂單需要新增一個定時消息,且不會馬上消費,額外給MQ帶來很大的存儲成本。

同一個時刻大量消息會導致消息延遲:

定時消息的實現邏輯需要先經過定時存儲等待觸發,定時時間到達后才會被投遞給消費者。

因此,如果將大量定時消息的定時時間設置為同一時刻,則到達該時刻后會有大量消息同時需要被處理,會造成系統壓力過大,導致消息分發延遲,影響定時精度。

方案3:Redis的過期監聽

和RocketMQ定時消息一樣,Redis支持過期監聽,也能達到的能力

通過Redis中key的過期事件,作為延遲消息

可以通過Redis中key的過期事件,作為延遲消息。使用Redis進行訂單超時處理的流程圖如下

具體步驟如下:

1.在服務器中 修改redis配置文件, 開啟"notify-keyspace-events Ex"

原來notify-keyspace-events 屬性是" " 空的,我們只需要填上“Ex”就行了

2.監聽key的過期回調

創建一個Redis監控類,用于監控過期的key,該類需繼承KeyExpirationEventMessageListener

public class KeyExpiredListener extends KeyExpirationEventMessageListener { public KeyExpiredListener(RedisMessageListenerContainer listenerContainer) { super(listenerContainer); } @Override public void onMessage(Message message, byte[] pattern) { String keyExpira = message.toString(); System.out.println("監聽到key:" + expiredKey + "已過期"); }}

3.創建Redis配置類 , 裝配這個 監聽器

@Configurationpublic class RedisConfiguration { @Autowired private RedisConnectionFactory redisConnectionFactory; @Bean public RedisMessageListenerContainer redisMessageListenerContainer() { RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer(); redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory); return redisMessageListenerContainer; } @Bean public KeyExpiredListener keyExpiredListener() { return new KeyExpiredListener(this.redisMessageListenerContainer()); }}

Redis過期時間作為延遲消息的原理

每當一個key設置了過期時間,Redis就會把該key帶上過期時間,在redisDb中通過expires字段維護:

typedef struct redisDb { dict *dict; /* 維護所有key-value鍵值對 */ dict *expires; /* 過期字典,維護設置失效時間的鍵 */ ....} redisDb;

這個結構,通過一個 過期字典dict來維護

過期字典dict 本質上是一個鏈表,每個節點的數據結構結構如下:

key是一個指針,指向某個鍵對象。value是一個long long類型的整數,保存了key的過期時間。

為了提升性能,Redis主要使用了定期刪除和惰性刪除策略來進行過期key的刪除

定期刪除:每隔一段時間(默認100ms)就隨機抽取一些設置了過期時間的key,檢查其是否過期,如果有過期就刪除。之所以這么做,是為了通過限制刪除操作的執行時長和頻率來減少對cpu的影響。不然每隔100ms就要遍歷所有設置過期時間的key,會導致cpu負載太大。惰性刪除:不主動刪除過期的key,每次從數據庫訪問key時,都檢測key是否過期,如果過期則刪除該key。惰性刪除有一個問題,如果這個key已經過期了,但是一直沒有被訪問,就會一直保存在數據庫中。

從以上可以知道,Redis過期刪除是不精準的,

在訂單超時處理的場景下,訂單的數據如果沒有去訪問,那么惰性刪除基本上也用不到,

所以,無法保證key在過期的時候可以立即刪除,更不能保證能立即通知。

如果訂單量比較大,那么延遲幾分鐘也是有可能的。

總而言之,Redis過期通知也是不可靠的,Redis在過期通知的時候,

如果應用正好重啟了,那么就有可能通知事件就丟了,會導致訂單一直無法關閉,有穩定性問題。

方案4:超大規模分布式定時批處理 架構

海量訂單過期的 分布式定時批處理解決方案,分為兩步:

step1:通過分布式定時不停輪詢數據庫的訂單,將已經超時的訂單撈出來

step2:分而治之,分發給不同的機器分布式處理

在阿里內部,幾乎所有的業務都使用超大規模分布式定時批處理架構,大致的架構圖如下:

超大規模分布式定時批處理 宏觀架構

前段時間指導簡歷,小伙伴簡歷里邊寫了這個項目,但是對這個項目的思想和精髓沒有理解,導致漏洞百出。

接下來,尼恩首先給大家梳理一下 超大規模分布式定時批處理 宏觀架構。

如何讓超時調度中心不同的節點協同工作,拉取不同的數據?宏觀的架構如下:

調度中心:海量任務調度執行系統

通常的解決方案如

自研分布式調度系統,尼恩曾經自研過 基于DB的分布式調度系統、基于zookeeper 的分布式調度系統開源的分布式調度系統 如 xxl-job,阿里巴巴分布式任務調度系統SchedulerX,不但兼容主流開源任務調度系統,也兼容Spring @Scheduled注解,

優先推薦,是使用最新版本的、基于時間輪的xxl-job,或者基于xxl-job做定制開發,后面估計尼恩可能會通過對 xxl-job的架構和源碼進行介紹。

xxl-job基于 時間輪完成調度,關于時間輪和 高性能多級時間輪,非常重要,但是內容太多,這里不做展開,

具體請看尼恩的3高架構筆記《徹底穿透Caffeine底層源碼和架構》PDF,里邊介紹了三個超高并發組件:時間輪、 多級時間輪、條帶環狀結構、MPSC隊列,大家一定認真看看。

那么在100億級海量訂單超時處理場景中,雖然海量訂單,但是對于調度系統來說,不是海量的。

因為為了給DB降低壓力,訂單是批量處理的,不可能一個訂單一個延遲任務,而是一大批訂單一個延遲任務。

所以,任務的數量級,成數量級的下降。

所以00億級海量訂單超時處理場景的壓力不在調度,而在于 數據庫。

問題的關鍵是如何進行數據分片。

海量數據分片模型

首先來看 DB數據的數據是如何分片的。 DB數據分片模型的架構,本身場景復雜:

分庫分表場景一張大表場景海量數據存儲組件 hbase、hdfs 等等

海量數據分片模型 要解決問題:

首先是復雜的分片模型的問題其次就是數據批處理的過程中,如何減少數據傳輸,提升性能的問題。

第一個問題:復雜的分片模型的問題。

由于 分片模型與數據庫的存儲方案的選型,和分庫分表的設計有關,這里不做展開。

后面給大家講大數據的時候,再展開。

咱們的卷王目標是左手大數據、右手云原生, 大數據是咱們后面卷的重點。

輕量級MapReduce模型

第2個問題:如何減少數據傳輸。

注意,如果數據源在mysql這樣的結構化db,修改訂單的狀態,是可以直接通過結構化sql,進行批量更新的。這個一條sql搞定,是非常簡單的。

但是,既然是海量數據,就不一定在結構化DB,而是在異構的DB(NOSQL)中。

異構DB(NOSQL)就沒有辦法通過結構化sql,進行批量更新了。

很多的NOSQL DB的記錄修改,是需要兩步:

step1:把數據讀取出來,step2:改完之后,再寫入。

所以,如果訂單的數據源,不一定是DB,而且在異構的DB(NOSQL)中, 那么久存在大量的數據傳輸的問題。

100億級訂單規模,基本上會涉及到異構DB,所以阿里的訂單狀態修改,要求既能兼容 結構化DB,又能兼容異構 DB, 那么就存在大量的數據傳輸問題。

為了減少數據傳輸,提升批處理性能的問題。阿里技術團隊,使用了輕量級MapReduce模型。

重量級MapReduce模型

首先看看,什么是MapReduce模型。

傳統的基于hadoop的文件批處理,數據在hdfs文件系統中,讀取到內存之后,再把結果寫入到hdfs,這種基于文件的批處理模式,存在大量的數據傳輸、磁盤IO,性能太低太低,根本不在目前的這個場景考慮之內哈

基于內存的批量數據處理,比如spark中的離線批量處理,采用的內存處理方式,和基于文件的批量處理相比,速度有了質的飛越

在spark基于內存的批處理流程中(這里簡稱為基于內存的MR),首先把數據加載到內存, 進行map轉換、reduce 規約(或者聚合)之后,再把結果通過網絡傳輸到下一個環節。這里,存在著大家的數據傳輸。

所以,不論是內存的批處理、還是基于文件批處理,都需要大量的傳輸數據,

大量的數據傳輸,意味著性能太低, 不適用 海量訂單批處理場景。

如何縮減數據傳輸的閨蜜,避免數據的大規模傳輸呢?

輕量級MapReduce模型

阿里自研了輕量級MapReduce模型,可以簡稱為本地批處理。

所以輕量級MapReduce模型,就是減少數據傳輸,在原來的數據庫里邊進行數據的計算(比如通過SQL語句),不需要把數據加載到內存進行計算。

輕量級MapReduce模型在執行的過程中,計算節點主要管理的是 轉換、規約 的分片規則、執行狀態和結果,對這些任務中的業務數據,計算節點不去讀取計算的目標數據,減少了海量業務數據的反復讀取、寫入。

轉換階段:

輕量級MapReduce模型,通過實現map函數,通過代碼自行構造分片,調度系統將分片平均分給超時中心的不同節點分布式執行。

注意,在MR集群中,分片由Master主節點完成,保存在內存數據庫H2中。工作節點的調度,也是Master完成。

規約階段

通過實現reduce函數,可以做聚合,可以判斷這次跑批有哪些分片跑失敗了,從而通知下游處理。

注意,在MR集群中,work的分片任務執行結果匯報到Master,這些結果保存在內存數據庫H2中,由Master完成規約(或聚合)。

有了這個自研了輕量級MapReduce模型,阿里的超時調度中心可以針對任意異構數據源,簡單幾行代碼就可以實現海量數據秒級別跑批。

定時任務分布式批處理的方案的優勢:

使用定時任務分布式批處理的方案具有如下優勢:

穩定性強:

基于通知的方案(比如MQ和Redis),比較擔心在各種極端情況下導致通知的事件丟了。使用定時任務跑批,只需要保證業務冪等即可,如果這個批次有些訂單沒有撈出來,或者處理訂單的時候應用重啟了,下一個批次還是可以撈出來處理,穩定性非常高。

效率高:

基于MQ的方案,需要一個訂單一個定時消息,consumer處理定時消息的時候也需要一個訂單一個訂單更新,對數據庫tps很高。使用定時任務跑批方案,一次撈出一批訂單,處理完了,可以批量更新訂單狀態,減少數據庫的tps。在海量訂單處理場景下,批量處理效率最高。

可運維:

基于數據庫存儲,可以很方便的對訂單進行修改、暫停、取消等操作,所見即所得。如果業務跑失敗了,還可以直接通過sql修改數據庫來進行批量運維。

成本低:

相對于其他解決方案要借助第三方存儲組件,復用數據庫的成本大大降低。

定時任務分布式批處理的方案的缺點:

但是使用定時任務有個天然的缺點:沒法做到精度很高。

定時任務的延遲時間,由定時任務的調度周期決定。

如果把頻率設置很小,就會導致數據庫的qps比較高,容易造成數據庫壓力過大,從而影響線上的正常業務。

解決方案就是DB解耦:

阿里內部,一般需要解耦單獨超時庫,單獨做訂單的超時調度,不會和業務庫在在一起操作。

同時也有獨立的超時中心,完成 數據的分片,以及跑批任務的調度。

訂單任務調度場景的選型:

(1)超時精度比較高、超時任務不會有峰值壓力的場景

如果對于超時精度比較高,不會有峰值壓力的場景,推薦使用RocketMQ的定時消息解決方案。

(2)超時精度比較低、超時任務不會有峰值壓力的場景(100億級訂單)

在電商業務下,許多訂單超時場景都在24小時以上,對于超時精度沒有那么敏感,并且有海量訂單需要批處理,推薦使用基于定時任務的跑批解決方案。

阿里的100億級訂單超時處理選型,選擇的是后面的方案。

架構的魅力:

通過以上的梳理,大家如果需要把 海量任務定時調度的方案寫入簡歷,再也不會一半清晰、一半糊涂了。

如果還不清楚怎么寫入簡歷,可以來找尼恩進行簡歷指導。保證 脫胎換骨、金光閃閃、天衣無縫。

總之,架構魅力,在于沒有最好的方案,只有更好的方案。

大家如果有疑問,或者更好的方案,可以多多交流,此題,后面的答案,也會不斷的完善和優化。

注:本文以 PDF 持續更新,最新尼恩 架構筆記、面試題 的PDF文件,請到《技術自由圈》公號領取

技術自由的實現路徑 PDF領?。?/strong>

▌實現你的架構自由

《吃透8圖1模板,人人可以做架構》PDF《10Wqps評論中臺,如何架構?B站是這么做的?。?!》PDF《阿里二面:千萬級、億級數據,如何性能優化? 教科書級 答案來了》PDF《峰值21WQps、億級DAU,小游戲《羊了個羊》是怎么架構的?》PDF《100億級訂單怎么調度,來一個大廠的極品方案》PDF《2個大廠 100億級 超大流量 紅包 架構方案》PDF

… 更多架構文章,正在添加中

▌實現你的 響應式 自由

《響應式圣經:10W字,實現Spring響應式編程自由》PDF這是老版本 《Flux、Mono、Reactor 實戰(史上最全)》PDF

▌實現你的 spring cloud 自由

《Spring cloud Alibaba 學習圣經》 PDF《分庫分表 Sharding-JDBC 底層原理、核心實戰(史上最全)》PDF《一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之間混亂關系(史上最全)》PDF

▌實現你的 linux 自由

《Linux命令大全:2W多字,一次實現Linux自由》PDF

▌實現你的 網絡 自由

《TCP協議詳解 (史上最全)》PDF《網絡三張表:ARP表, MAC表, 路由表,實現你的網絡自由??!》PDF

▌實現你的 分布式鎖 自由

《Redis分布式鎖(圖解 - 秒懂 - 史上最全)》PDF《Zookeeper 分布式鎖 - 圖解 - 秒懂》PDF

▌實現你的 王者組件 自由

《隊列之王: Disruptor 原理、架構、源碼 一文穿透》PDF《緩存之王:Caffeine 源碼、架構、原理(史上最全,10W字 超級長文)》PDF《緩存之王:Caffeine 的使用(史上最全)》PDF《Java Agent 探針、字節碼增強 ByteBuddy(史上最全)》PDF

▌實現你的 面試題 自由

4000頁《尼恩Java面試寶典》PDF 40個專題....

注:以上尼恩 架構筆記、面試題 的PDF文件,請到《技術自由圈》公號領取

還需要啥自由,可以告訴尼恩。 尼恩幫你實現.......

以上就是關于pos機接收返回超時,100億訂單超時處理的知識,后面我們會繼續為大家整理關于pos機接收返回超時的知識,希望能夠幫助到大家!

轉發請帶上網址:http://www.tonybus.com/newsone/50996.html

你可能會喜歡:

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