循環有序的發送通知實現方案

背景

      上週五,同事從21樓下到14樓問我一個問題,他們的產品想實現一個有序並且循環的去發送通知的一個需求。同事一時沒想到比較好的解決方案,便找我來討論一下,有沒有更優的實現方式。於是我們便在14樓討論了一番,得出一個相對比較好的解決方案,在此分享給大家,方便大家遇到類似的問題可以加以借鑑和參考。

需求描述

      假如數據庫有三條待發送的通知模板A,B,C,按日期升序排列,假如排列順序就是ABC,則第一輪發送順序爲ABC,第二輪發送順序爲ABC,第三輪順序爲ABC,依次循環往復A->B->C->A->B->C...如下圖所示:

 

image.png

 

上面只是一種場景,突然有一天產品需要加入一條通知模板D,假如D的排列順序在C之後,這裏只是假設,也可能在ABC任何一個模板的前面,發送順序則爲下圖所示:

image.png

 

對以上發送的要求是必須循環有序的發送,需要支持任意排列順序後依然保持循環發送。

 

問題分析

1.初步分析

        乍一看,這個問題不是很簡單嘛,放隊列不就好了嘛,你長得好看,說什麼都對,好像問題就這樣輕鬆被解決了,哈哈。。。但是具體怎麼實現呢?讓我們再仔細分析分析。

  • 第一步,把模板排好序查出來(SQL根據時間升序,這個大家都會,但是按時間排序我感覺不方便,靈活性比較差,這個自己體會),放到緩存隊列中(這裏的隊列完全可以用ArrayList代替,不一定非用queue,但是queue的好處還是很多滴,例如移除元素的時候就非常非常地方便了)
  • 第二步,現在緩存隊列有了,我們就可以發起推送模板啦,第一輪發送開始,取出第一個A發送,然後取出B發送,再取出C發送,哇,這不就輕鬆實現了嗎?多簡單!

 

不知道大家發現問題沒有?上面的需求很清楚,是循環有序發送。上面的方法貌似沒有實現循環啊,只是實現了有序而已,那麼問題又來了,怎麼實現循環呢?請看深入分析。

 

2.深入分析

        上面分析的方法雖然可以保證順序,但是無法循環發送,這該怎麼辦呢?於是我想到一個隊列好像無法實現循環,那我就用兩個,嘿嘿。。。好像兩個隊列就解決了問題,事實真的是這樣嗎?我們繼續分析,假設我們創建了兩個隊列,一個隊列是發送隊列,另一個是接收隊列。

  • 第一步,創建發送隊列和接收隊列,其目的是爲了實現循環效果。
  • 第二步,我們初始化發送隊列並放到緩存中,同時創建一個空的接收隊列並放在緩存中。
  • 第三步,我們開始發送隊列中的模板信息A,並把它添加到接收隊列中,同時移除發送隊列中的A,模板B和C同理A的發送步驟。
  • 第四步,當發送隊列爲空時,也就是發送隊列中的數據都被髮送完畢,此時,最初的發送隊列變爲了接收隊列,而最初的接收隊列變成了發送隊列,如此交替變換身份,便實現了循環的效果。

 

好像這個方法很完美的實現了上面的需求,循環有序,堪稱完美啊,哈哈。。。不知可否有人注意到兩個隊列輪流交互身份的條件是什麼?(此處大家可以發散一下思維,哈哈。。。這裏我先賣個關子,歡迎留言)

 

3.最終分析

         上面的分析基本上已經可以實現循環有序的發送通知了,但是卻忽略了兩點,那麼是哪兩點呢?咱們接着分析!

  1. 如果產品需要新加入一個模板通知D,那麼該怎麼辦?(換句話說就是D模板怎麼同步到緩存隊列,併爲其排好序)
  2. 緩存隊列所依賴的服務器掛了或者重啓了,那又該怎麼辦?(換句話說就是redis服務器掛了或者重啓了)

 

對於第一個問題,其實還是可以實現的,就是稍微麻煩一點(具體的做法,我這裏不在細細描述了,此處不是重點,感興趣的同學可以自己思考一下,也可以私下找我探討)。

但是第二個問題就比較棘手了,服務器掛了或者重啓了,所有的數據都丟失了,這該怎麼辦?有的人估計會想到Redis可以持久化,即使掛了或者重啓也沒關係,其實這樣可以嗎?確實可以。

 

好像上面的方案確實可以解決可能發生的問題,並實現了需求功能,說實話,上面的方案是可行的,但是我卻沒有推薦同事用,原因是實現起來比較麻煩,那麼有沒有更簡單的方案呢?答案是肯定的。

 

解決方案

        通過前面的層層分析,我們發現一個隊列似乎不能滿足需求,兩個隊列實現起來又比較麻煩,那麼我們可以不做這個需求嗎?哈哈。。。我也不想做,跟產品商量商量砍掉好了,嘿嘿。。。

         我當時思考了一會,覺得肯定有更簡單的辦法,只是沒想到,於是大腦不停旋轉,突然想到了學習數據結構隊列時的實現方式,即循環隊列的實現方式(看過數據結構的應該知道這個),循環隊列的隊頭元素的指針指向下一個元素,下一個元素的指針繼續指向下下一個,依次類推.......隊尾元素的指針又指向隊頭元素,如下圖所示:

image.png

 

由這個啓發,我聯想到當前的需求場景,我們是不是可以仿照循環隊列的實現方式來實現我們的需求呢?答案很明顯是可以的,而且實現起來超簡單,哈哈。。。具體怎麼實現呢?我們一起繼續探索!

        首先我們知道了循環隊列的特點,跟我們的需求很類似,那麼我們就可以創造條件讓我們的通知模板帶有循環特性。

  1. 第一步,我們需要給數據庫通知模板表添加一個是否已發送的標記字段,可以用int型的0,1表示,0代表已發送,1代表待發送;
  2. 第二步,初始化模板信息,排在第一位的爲1,其餘爲0。
  3. 第三步,每次發送模板時只查詢出爲1標記的去發送,同時更新當前所發送模板標記爲0,並且更新排在下一位的模板標記爲1,依次類推。

 

就上面的簡單3步,就輕鬆實現了循環有序發送的需求,並且支持任意排序和追加新的模板進來,即使服務器掛了重啓,也不會影響之前發送的順序,真正達到了靈活多變,這個問題相對比較好的解決了。

 

總結

        雖然這個問題看着挺簡單的,其實細節點還是很多的,一不小心就會掉進坑裏,如果考慮不到緩存會崩,亦或考慮不到新模板添加進來後重排序等問題,亦或是考慮到了沒很好的處理到位,都可能會埋下隱患。還有就是實現方式,一開始我們拿到這個需求進行分析時,可能第一想到的只是一些常規手段,亦或是一些複雜的實現手段,但是仔細想想,可能會有更好的實現方式,當我們覺得實現起來比較麻煩的時候不妨換一種思路,或許會有柳暗花明又一村的感覺。其實上述的場景依然不夠全面,還有很多場景未包含在其中,大家可以再拓展一下想象空間,發散一下思維,歡迎大家留言!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章