關於定時發短信業務的討論
事情的起因
需求:在每次線下活動的開始的前一天晚上七點給報名參加價值研習社的用戶發一條通知短信用戶記得準時參加活動。
備註:因爲我們的業務併發不是很大,所以很多場景並沒有考慮到併發情況下的一些問題,這個需求正好通過crontab執行,並且加上服務器的自動彈性伸縮,所以相當於模擬了一次併發的業務場景。
先簡單介紹一下數據庫的表結構:
這幾個方案都依賴每天晚上七點執行一次corntab。
方案1
根據開講時間查詢活動表是否有滿足條件的線下活動,如果有的話,再通過活動id關聯到簽到表過濾出send_sms字段爲0的uid並關聯用戶表拿出手機號等信息。發送完成後再統一更新send_sms字段。
缺點:在併發業務場景下,可能會產生髒讀的情況,造成發送多次短信的情況。
方案2
與方案1很相似,唯一的區別就是查詢的時候開啓事務用SELECT ... FOR UPDATE ,這種查詢語句的區別就是在SELECT
的時候把結果行上鎖,從而就能避免髒讀,然後再同一個事務中UPDATE
send_sms字段,最後commit
。
缺點:由於發短信不是數據庫操作,不可回滾。所以如果執行的過程中發生回滾,就會出現短信已經發出去了,但是數據庫發生回滾,send_sms字段置爲了0,這就產生了矛盾。而且如果是個耗時的任務可能會出現死鎖的問題。
以下就是執行的邏輯
BEGIN;
SELECT ... FOR UPDATE;
UPDATE ... SET send_sms = 1;
COMMIT;
方案3
與方案2很相似,唯一的區別就是一條一條的取數據上鎖,然後更新send_sms
字段。
缺點:要寫一個循環一直去查詢滿足條件但還未發送短信的用戶。處理不好容易產生死循環以及死鎖的問題。
方案4
這是我目前能想到的最佳方案,直接用SELECT
語句選出所有滿足條件的手機號碼以及短信內容,放入Queue
中,然後實現對Queue
的處理。處理如下:先用SELECT ... FOR UPDATE
判斷send_sms
字段的值,如果爲0,那就執行發短信,然後更新send_sms
字段爲1,最後COMMIT
。這樣就可以避免多次執行發短信。
總結:對於這種對實時性要求沒那麼高的業務場景用Queue
還是非常便利的,讓Queue
一條一條的處理,在複雜的系統中還起到了削峯和解耦的作用。大家在工作中有哪些對Queue
的應用呢?歡迎留言,一起討論!
大家對上面的這些方案有什麼建議呢?歡迎留言討論!