天天看點

Redis牛逼!輕松實作實時訂閱推送

前陣子開發了公司領劵中心的項目,這個項目是以redis作為關鍵技術落地的。

先說一下領劵中心的項目吧,這個項目就類似京東app的領劵中心,當然圖是截取京東的,公司的就不截了。。。

Redis牛逼!輕松實作實時訂閱推送

其中有一個功能叫做領劵的訂閱推送。什麼是領劵的訂閱推送?就是使用者訂閱了該劵的推送,在可領取前的一分鐘就要把提醒資訊推送到使用者的app中。

本來這個訂閱功能應該是消息中心那邊做的,但他們說這個短時間内做不了。是以讓我這個負責優惠劵的做了-.-!。

具體方案就是到具體的推送時間點了,coupon系統調用消息中心的推送接口,把資訊推送出去。

下面我們分析一下這個功能的業務情景。

公司目前注冊使用者6000W+,如果有一張無門檻的優惠劵下單立減20元,那麼搶這張劵的人就會比較多,我們保守估計10W+。

我們初定為20W萬人,那麼這20W條推送資訊要在一分鐘推送完成!并且一個使用者是可以訂閱多張劵的。

是以我們知道了這個訂閱功能的有兩個突出的難點:

1、推送的實效性:推送慢了,使用者會抱怨沒有及時通知他們錯過了開搶時機。

2、推送的體量大:爆款的神劵,人人都想搶!

然而推送體量又會影響到推送的實效性。這真是一個讓人頭疼的問題!

那就讓我們把問題一個個解決掉吧!**!**!

推送的實效性的問題:當使用者在領劵中心訂閱了某個劵的領取提醒後,在背景就會生成一條使用者的訂閱提醒記錄,裡面記錄了在哪個時間點給使用者發送推送資訊。

是以問題就變成了系統如何快速實時選出哪些要推送的記錄!

方案1:MQ的延遲投遞。推薦:MQ  到底有啥用?MQ雖然支援消息的延遲投遞但尺度太大1s 5s 10s 30s 1m,用來做精确時間點投遞不行!并且使用者執行訂閱之後又取消訂閱的話,要把發出去的MQ消息delete掉這個操作有點頭大,短時間内難以落地!并且使用者可以取消之後再訂閱,這又涉及到去重的問題。是以MQ的方案否掉。

方案2:傳統定時任務。這個相對來說就簡單一點,用定時任務是去db裡面load使用者的訂閱提醒記錄,從中選出目前可以推送的記錄。但有句話說得好任何脫離實際業務的設計都是耍流氓~。下面我們就分析一下傳統的定時任務到底适不适合我們的這個業務!

能否支援多機同時跑

一般不能,同一時刻隻能單機跑

存儲資料源

一般是mysql或者其它傳統資料庫,并且是單表存儲

頻率

支援秒、分、時、天,一般不能太快

綜上所述我們就知道了一般傳統的定時任務存在以下缺點:

1、性能瓶頸。隻有一台機在處理,在大體量資料面前力不從心!

2、實效性差。定時任務的頻率不能太高,太高會業務資料庫造成很大的壓力!

3、單點故障。萬一跑的那台機挂了,那整個業務不可用了-。- 這是一個很可怕的事情!

是以傳統定時任務也不太适合這個業務。。。

那我們是不是就束手無策了呢?**其實不是的! 我們隻要對傳統的定時任務做一個簡單的改造!就可以把它變成可以同時多機跑,并且實效性可以精确到秒級,并且拒絕單點故障的定時任務叢集!這其中就要借助我們的強大的redis了。**

推薦:史上最全 Redis 高可用解決方案總結

更多可以關注微信公衆号:Java技術棧,在背景回複:redis,可以擷取我整理的 N 篇最新 Redis 教程,都是幹貨。

方案3:定時任務叢集

首先我們要定義定時任務叢集要解決的三個問題:

1、實效性要高

2、吞吐量要大

3、服務要穩定,不能有單點故障

下面是整個定時任務叢集的架構圖。

Redis牛逼!輕松實作實時訂閱推送

架構很簡單:我們把使用者的訂閱推送記錄存儲到redis叢集的sortedSet隊列裡面,并且以提醒使用者提醒時間戳作為score值,然後在我們每個業務server裡面起一個定時器頻率是秒級,我的設定就是1s,然後經過負載均衡之後從某個隊列裡面擷取要推送的使用者記錄進行推送。下面我們分析一下這個架構

1、性能:除去帶寬等其它因素,基本與機器數成線性相關。機器數量越多吞吐量越大,機器數量少時相對的吞吐量就減少。

2、實效性:提高到了秒級,效果還可以接受。

3、單點故障?不存在的!除非redis叢集或者所有server全挂了。。。。

這裡解析一下為什麼用redis?

1、redis 可以作為一個高性能的存儲db,性能要比MySQL好很多,并且支援持久化,穩定性好。為什麼分布式一定要Redis? 值得一讀。

2、redis SortedSet隊列天然支援以時間作為條件排序,完美滿足我們選出要推送的記錄。

ok~既然方案已經有了那如何在一天時間内把這個方案落地呢?是的我設計出這個方案到基本編碼完成,時間就是一天。。。

首先我們以user_id作為key,然後mod隊列數hash到redis SortedSet隊列裡面。為什麼要這樣呢,因為如果使用者同時訂閱了兩張劵并且推送時間很近,這樣的兩條推送就可以合并成一條~,并且這樣hash也相對均勻。下面是部分代碼的截圖:

Redis牛逼!輕松實作實時訂閱推送

然後要決定隊列的數量,一般正常來說我們有多少台處理的伺服器就定義多少條隊列。因為隊列太少,會造成隊列競争,太多可能會導緻記錄得不到及時處理。

然而最佳實踐是隊列數量應該是可動态配置化的,因為線上的叢集機器數是會經常變的。大促的時候我們會加機器是不是,并且業務量增長了,機器數也是會增加是不是~。是以我是借用了淘寶的diamond進行隊列數的動态配置。

Redis牛逼!輕松實作實時訂閱推送

我們每次從隊列裡面取多少條記錄也是可以動态配置的

Redis牛逼!輕松實作實時訂閱推送

這樣就可以随時根據實際的生産情況調整整個叢集的吞吐量, 是以我們的定時任務叢集還是具有一個特性就是支援動态調整。

最後一個關鍵元件就是負載均衡了。這個是非常重要的!因為這個做得不好就會可能導緻多台機競争同時處理一個隊列,影響整個叢集的效率!

在時間很緊的情況下我就用了一個簡單實用的利用redis一個自增key 然後 mod 隊列數量算法。這樣就很大程度上就保證不會有兩台機器同時去競争一條隊列~.

Redis牛逼!輕松實作實時訂閱推送

最後我們算一下整個叢集的吞吐量

10(機器數) * 2000(一次拉取數) = 20000。然後以MQ的形式把消息推送到消息中心,發MQ是異步的,算上其它處理0.5s。

其實發送20W的推送也就是10幾s的事情。

ok~ 到這裡我們整個定時任務叢集就差不多基本落地好了。如果你問我後面還有什麼可以完善的話那就是:

1、加監控, 叢集怎麼可以木有監控呢,萬一出問題有任務堆積怎麼辦~

2、加上可視化界面。

3、最好有智能排程,增加任務優先級。優先級高的任務先運作嘛。

4、資源排程,萬一機器數量不夠,力不從心,優先保證重要任務執行。

目前項目已上前線,運作平穩~