成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

再有人問你如何實現訂單到期關閉,就把這篇文章發給他!

開發 前端
Redission中定義了分布式延遲隊列RDelayedQueue,這是一種基于我們前面介紹過的zset結構實現的延時隊列,它允許以指定的延遲時長將元素放到目標隊列中。

?在電商、支付等系統中,一般都是先創建訂單(支付單),再給用戶一定的時間進行支付,如果沒有按時支付的話,就需要把之前的訂單(支付單)取消掉。

這種類似的場景有很多,還有比如到期自動收貨、超時自動退款、下單后自動發送短信等等都是類似的業務問題。

本文就從這樣的業務問題出發,探討一下都有哪些技術方案,這些方案的實現細節,以及相關的優缺點都有什么?

因為本文要講的內容比較多,涉及到11種具體方案,受篇幅限制,這篇文章主要是講方案,不會涉及到具體的代碼實現。 因為只要方案搞清楚了,代碼實現不是難事兒。

一、被動關閉

在解決這類問題的時候,有一種比較簡單的方式,那就是通過業務上的被動方式來進行關單操作。

簡單點說,就是訂單創建好了之后。我們系統上不做主動關單,什么時候用戶來訪問這個訂單了,再去判斷時間是不是超過了過期時間,如果過了時間那就進行關單操作,然后再提示用戶。

圖片

這種做法是最簡單的,基本不需要開發定時關閉的功能,但是他的缺點也很明顯,那就是如果用戶一直不來查看這個訂單,那么就會有很多臟數據冗余在數據庫中一直無法被關單。

還有一個缺點,那就是需要在用戶的查詢過程中進行寫的操作,一般寫操作都會比讀操作耗時更長,而且有失敗的可能,一旦關單失敗了,就會導致系統處理起來比較復雜。

所以,這種方案只適合于自己學習的時候用,任何商業網站中都不建議使用這種方案來實現訂單關閉的功能。

二、定時任務

定時任務關閉訂單,這是很容易想到的一種方案。

具體實現細節就是我們通過一些調度平臺來實現定時執行任務,任務就是去掃描所有到期的訂單,然后執行關單動作。

圖片

這個方案的優點也是比較簡單,實現起來很容易,基于Timer、ScheduledThreadPoolExecutor、或者像xxl-job這類調度框架都能實現,但是有以下幾個問題:

1、時間不精準。 一般定時任務基于固定的頻率、按照時間定時執行的,那么就可能會發生很多訂單已經到了超時時間,但是定時任務的調度時間還沒到,那么就會導致這些訂單的實際關閉時間要比應該關閉的時間晚一些。

2、無法處理大訂單量。 定時任務的方式是會把本來比較分散的關閉時間集中到任務調度的那一段時間,如果訂單量比較大的話,那么就可能導致任務執行時間很長,整個任務的時間越長,訂單被掃描到時間可能就很晚,那么就會導致關閉時間更晚。

3、對數據庫造成壓力。 定時任務集中掃表,這會使得數據庫IO在短時間內被大量占用和消耗,如果沒有做好隔離,并且業務量比較大的話,就可能會影響到線上的正常業務。

4、分庫分表問題。 訂單系統,一旦訂單量大就可能會考慮分庫分表,在分庫分表中進行全表掃描,這是一個極不推薦的方案。

所以,定時任務的方案,適合于對時間精確度要求不高、并且業務量不是很大的場景中。如果對時間精度要求比較高,并且業務量很大的話,這種方案不適用。

三、JDK自帶的延遲隊列

有這樣一種方案,他不需要借助任何外部的資源,直接基于應用自身就能實現,那就是基于JDK自帶的DelayQueue來實現

DelayQueue是一個無界的BlockingQueue,用于放置實現了Delayed接口的對象,其中的對象只能在其到期時才能從隊列中取走。

基于延遲隊列,是可以實現訂單的延遲關閉的,首先,在用戶創建訂單的時候,把訂單加入到DelayQueue中,然后,還需要一個常駐任務不斷的從隊列中取出那些到了超時時間的訂單,然后在把他們進行關單,之后再從隊列中刪除掉。

這個方案需要有一個線程,不斷的從隊列中取出需要關單的訂單。一般在這個線程中需要加一個while(true)循環,這樣才能確保任務不斷的執行并且能夠及時的取出超時訂單。

使用DelayQueue實現超時關單的方案,實現起來簡單,必須要依賴第三方的框架和類庫,JDK原生就支持了。

當然這個方案也不是沒有缺點的,首先,基于DelayQueue的話,需要把訂單放進去,那如果訂單量太大的話,可能會導致OOM的問題;另外,DelayQueue是基于JVM內存的,一旦機器重啟了,里面的數據就都沒有了。雖然我們可以配合數據庫的持久化一起使用。而且現在很多應用都是集群部署的,那么集群中多個實例上的多個DelayQueue如何配合是一個很大的問題。

所以,基于JDK的DelayQueue方案只適合在單機場景、并且數據量不大的場景中使用,如果涉及到分布式場景,那還是不建議使用。

四、Netty的時間輪

還有一種方式,和上面我們提到的JDK自帶的DelayQueue類似的方式,那就是基于時間輪實現。

為什么要有時間輪呢?主要是因為DelayQueue插入和刪除操作的平均時間復雜度——O(nlog(n)),雖然已經挺好的了,但是時間輪的方案可以將插入和刪除操作的時間復雜度都降為O(1)。

時間輪可以理解為一種環形結構,像鐘表一樣被分為多個 slot。每個 slot 代表一個時間段,每個 slot 中可以存放多個任務,使用的是鏈表結構保存該時間段到期的所有任務。時間輪通過一個時針隨著時間一個個 slot 轉動,并執行 slot 中的所有到期任務。

圖片

基于Netty的HashedWheelTimer可以幫助我們快速的實現一個時間輪,這種方式和DelayQueue類似,缺點都是基于內存、集群擴展麻煩、內存有限制等等。

但是他相比DelayQueue的話,效率更高一些,任務觸發的延遲更低。代碼實現上面也更加精簡。

所以,基于Netty的時間輪方案比基于JDK的DelayQueue效率更高,實現起來更簡單,但是同樣的,只適合在單機場景、并且數據量不大的場景中使用,如果涉及到分布式場景,那還是不建議使用。

五、Kafka的時間輪

既然基于Netty的時間輪存在一些問題,那么有沒有其他的時間輪的實現呢?

還真有的,那就是Kafka的時間輪,Kafka內部有很多延時性的操作,如延時生產,延時拉取,延時數據刪除等,這些延時功能由內部的延時操作管理器來做專門的處理,其底層是采用時間輪實現的。

而且,為了解決有一些時間跨度大的延時任務,Kafka 還引入了層級時間輪,能更好控制時間粒度,可以應對更加復雜的定時任務處理場景;

Kafka 中的時間輪的實現是 TimingWheel 類,位于 kafka.utils.timer 包中?;贙afka的時間輪同樣可以得到O(1)時間復雜度,性能上還是不錯的。

基于Kafka的時間輪的實現方式,在實現方式上有點復雜,需要依賴kafka,但是他的穩定性和性能都要更高一些,而且適合用在分布式場景中。

六、RocketMQ延遲消息

相比于Kafka來說,RocketMQ中有一個強大的功能,那就是支持延遲消息。

延遲消息,當消息寫入到Broker后,不會立刻被消費者消費,需要等待指定的時長后才可被消費處理的消息,稱為延時消息。

有了延遲消息,我們就可以在訂單創建好之后,發送一個延遲消息,比如20分鐘取消訂單,那就發一個延遲20分鐘的延遲消息,然后在20分鐘之后,消息就會被消費者消費,消費者在接收到消息之后,去關單就行了。

但是,RocketMQ的延遲消息并不是支持任意時長的延遲的,它只支持:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h這幾個時長。(商業版支持任意時長)

可以看到,有了RocketMQ延遲消息之后,我們處理上就簡單很多,只需要發消息,和接收消息就行了,系統之間完全解耦了。但是因為延遲消息的時長受到了限制,所以并不是很靈活。

如果我們的業務上,關單時長剛好和RocketMQ延遲消息支持的時長匹配的話,那么是可以基于RocketMQ延遲消息來實現的。否則,這種方式并不是最佳的。

七、RabbitMQ死信隊列

延遲消息不僅在RocketMQ中支持,其實在RabbitMQ中也是可以實現的,只不過其底層是基于死信隊列實現的。

當RabbitMQ中的一條正常的消息,因為過了存活時間(TTL過期)、隊列長度超限、被消費者拒絕等原因無法被消費時,就會變成Dead Message,即死信。

當一個消息變成死信之后,他就能被重新發送到死信隊列中(其實是交換機-exchange)。

那么基于這樣的機制,就可以實現延遲消息了。那就是我們給一個消息設定TTL,然但是并不消費這個消息,等他過期,過期后就會進入到死信隊列,然后我們再監聽死信隊列的消息消費就行了。

而且,RabbitMQ中的這個TTL是可以設置任意時長的,這就解決了RocketMQ的不靈活的問題。

但是,死信隊列的實現方式存在一個問題,那就是可能造成隊頭阻塞,因為隊列是先進先出的,而且每次只會判斷隊頭的消息是否過期,那么,如果隊頭的消息時間很長,一直都不過期,那么就會阻塞整個隊列,這時候即使排在他后面的消息過期了,那么也會被一直阻塞。

基于RabbitMQ的死信隊列,可以實現延遲消息,非常靈活的實現定時關單,并且借助RabbitMQ的集群擴展性,可以實現高可用,以及處理大并發量。他的缺點第一是可能存在消息阻塞的問題,還有就是方案比較復雜,不僅要依賴RabbitMQ,而且還需要聲明很多隊列(exchange)出來,增加系統的復雜度

八、RabbitMQ插件

其實,基于RabbitMQ的話,可以不用死信隊列也能實現延遲消息,那就是基于rabbitmq_delayed_message_exchange插件,這種方案能夠解決通過死信隊列實現延遲消息出現的消息阻塞問題。但是該插件從RabbitMQ的3.6.12開始支持的,所以對版本有要求。

圖片

這個插件是官方出的,可以放心使用,安裝并啟用這個插件之后,就可以創建x-delayed-message類型的隊列了。

前面我們提到的基于私信隊列的方式,是消息先會投遞到一個正常隊列,在TTL過期后進入死信隊列。但是基于插件的這種方式,消息并不會立即進入隊列,而是先把他們保存在一個基于Erlang開發的Mnesia數據庫中,然后通過一個定時器去查詢需要被投遞的消息,再把他們投遞到x-delayed-message隊列中。

基于RabbitMQ插件的方式可以實現延遲消息,并且不存在消息阻塞的問題,但是因為是基于插件的,而這個插件支持的最大延長時間是(2^32)-1 毫秒,大約49天,超過這個時間就會被立即消費。但是他基于RabbitMQ實現,所以在可用性、性能方便都很不錯

九、Redis過期監聽

很多用過Redis的人都知道,Redis有一個過期監聽的功能,

在 redis.conf 中,加入一條配置notify-keyspace-events Ex開啟過期監聽,然后再代碼中實現一個KeyExpirationEventMessageListener,就可以監聽key的過期消息了。

這樣就可以在接收到過期消息的時候,進行訂單的關單操作。

這個方案不建議大家使用,是因為Redis官網上明確的說過,Redis并不保證Key在過期的時候就能被立即刪除,更不保證這個消息能被立即發出。所以,消息延遲是必然存在的,隨著數據量越大延遲越長,延遲個幾分鐘都是常事兒。?

而且,在Redis 5.0之前,這個消息是通過PUB/SUB模式發出的,他不會做持久化,至于你有沒有接到,有沒有消費成功,他不管。也就是說,如果發消息的時候,你的客戶端掛了,之后再恢復的話,這個消息你就徹底丟失了。(在Redis 5.0之后,因為引入了Stream,是可以用來做延遲消息隊列的。)

十、Redis的zset

雖然基于Redis過期監聽的方案并不完美,但是并不是Redis實現關單功能就不完美了,還有其他的方案。

我們可以借助Redis中的有序集合——zset來實現這個功能。

zset是一個有序集合,每一個元素(member)都關聯了一個 score,可以通過 score 排序來取集合中的值。

我們將訂單超時時間的時間戳(下單時間+超時時長)與訂單號分別設置為 score 和 member。這樣redis會對zset按照score延時時間進行排序。然后我們再開啟redis掃描任務,獲取”當前時間 > score”的延時任務,掃描到之后取出訂單號,然后查詢到訂單進行關單操作即可。

使用redis zset來實現訂單關閉的功能的優點是可以借助redis的持久化、高可用機制。避免數據丟失。但是這個方案也有缺點,那就是在高并發場景中,有可能有多個消費者同時獲取到同一個訂單號,一般采用加分布式鎖解決,但是這樣做也會降低吞吐型。?

但是,在大多數業務場景下,如果冪等性做得好的,多個消費者取到同一個訂單號也無妨。

十一、Redisson

上面這種方案看上去還不錯,但是需要我們自己基于zset這種數據結構編寫代碼,那么有沒有什么更加友好的方式?

有的,那就是基于Redisson。

Redisson是一個在Redis的基礎上實現的框架,它不僅提供了一系列的分布式的Java常用對象,還提供了許多分布式服務。

圖片

Redission中定義了分布式延遲隊列RDelayedQueue,這是一種基于我們前面介紹過的zset結構實現的延時隊列,它允許以指定的延遲時長將元素放到目標隊列中。

其實就是在zset的基礎上增加了一個基于內存的延遲隊列。當我們要添加一個數據到延遲隊列的時候,redission會把數據+超時時間放到zset中,并且起一個延時任務,當任務到期的時候,再去zset中把數據取出來,返回給客戶端使用。

大致思路就是這樣的,感興趣的大家可以看一看RDelayedQueue的具體實現。

基于Redisson的實現方式,是可以解決基于zset方案中的并發重復問題的,而且還能實現方式也比較簡單,穩定性、性能都比較高。

總結

我們介紹了11種實現訂單定時關閉的方案,其中不同的方案各自都有優缺點,也各自適用于不同的場景中。那我們嘗試著總結一下:

實現的復雜度上(包含用到的框架的依賴及部署):?

Redission > RabbitMQ插件 > RabbitMQ死信隊列 > RocketMQ延遲消息 ≈ Redis的zset > Redis過期監聽 ≈ kafka時間輪 > 定時任務 > Netty的時間輪 > JDK自帶的DelayQueue > 被動關閉

方案的完整性:?

Redission ≈ RabbitMQ插件 > kafka時間輪 > Redis的zset ≈ RocketMQ延遲消息 ≈ RabbitMQ死信隊列 > Redis過期監聽 > 定時任務 > Netty的時間輪 > JDK自帶的DelayQueue > 被動關閉

不同的場景中也適合不同的方案:?

  • 自己玩玩:被動關閉
  • 單體應用,業務量不大:Netty的時間輪、JDK自帶的DelayQueue、定時任務
  • 分布式應用,業務量不大:Redis過期監聽、RabbitMQ死信隊列、Redis的zset、定時任務
  • 分布式應用,業務量大、并發高:Redission、RabbitMQ插件、kafka時間輪、RocketMQ延遲消息

總體考慮的話,考慮到成本,方案完整性、以及方案的復雜度,還有用到的第三方框架的流行度來說,個人比較建議優先考慮Redission+Redis、RabbitMQ插件、Redis的zset、RocketMQ延遲消息等方案。?

責任編輯:武曉燕 來源: Hollis
相關推薦

2018-08-07 16:01:32

synchronizevolatilefinal

2022-09-26 10:09:08

MVCC控制并發

2020-12-11 08:23:06

JavaMemory Mode內存模型

2018-07-03 14:54:25

Java內存模型

2018-12-07 09:31:52

分布式鎖服務框架分布式系統

2021-02-22 13:32:19

MySQLSQL索引

2021-05-31 09:42:48

MySQL隔離級別

2022-05-23 09:41:27

分庫分表數據庫算法

2019-11-05 14:06:07

MySQLB+索引

2021-03-08 10:25:37

MySQL數據庫索引

2019-11-04 15:00:50

MySQL索引B+樹

2021-03-08 12:47:42

MySQL查詢數據

2022-10-21 16:39:56

JDK優化

2023-12-11 08:32:58

數據庫DruidDBA

2020-04-20 13:11:21

HashMap底層存儲

2020-04-28 09:15:58

HashMapJava數組

2019-09-19 14:03:32

B樹節點數據結構

2019-12-10 09:08:29

分布式開源RocketMQ

2018-04-13 16:24:59

區塊鏈數據庫去中心化

2019-04-15 14:40:46

消息隊列Java編程
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 超碰激情| 日韩精品一区二区三区视频播放 | 国产香蕉视频在线播放 | 国产欧美一区二区三区久久人妖 | 精品综合视频 | 一区二区av | 欧美日韩国产在线 | 色综合色综合 | 国产亚洲一区二区在线观看 | 国产超碰人人爽人人做人人爱 | 羞羞视频网站 | 欧美一区二区 | aⅴ色国产 欧美 | 久久久久综合 | 成人精品一区二区三区四区 | 精品国产一区二区三区久久 | 久久国产精品视频 | 中文字幕亚洲国产 | 亚洲成av人片在线观看 | 天堂久久天堂综合色 | 亚洲精品免费视频 | 99国产精品久久久久老师 | 伊人性伊人情综合网 | www国产亚洲精品久久网站 | 久久久久国产一区二区三区 | 国产免费人成xvideos视频 | 午夜精品一区二区三区在线视频 | 一本一道久久a久久精品蜜桃 | 欧美一级做a爰片免费视频 国产美女特级嫩嫩嫩bbb片 | 小草久久久久久久久爱六 | 久久久精品综合 | 成人三级av | 亚洲一区二区中文字幕 | 成人国产精品入口免费视频 | 久久久精品视频一区二区三区 | 蜜桃特黄a∨片免费观看 | 国产精品一区二区三区在线 | 亚洲精品一区二区网址 | 中文字幕日韩一区 | 91人人视频在线观看 | 91精品久久久久久久久 |