如何給30W的APP用戶推送消息?

給30W用戶推送消息並不難,使用第三方的推送能夠說至關簡單,基本上都支持針對app級別的推送,服務端只須要調用一次推送接口,剩下的交給第三方推送便可。但有的場景下,徹底交給第三方是不可行的。好比下面的場景數據庫

1.用戶收到消息後會打開app,打開app就會請求服務器去加載數據,這樣併發請求會很是高,而由於某些緣由服務器承受不住的狀況。服務器

2.推送消息是有針對性的用戶的,但針對的是部分用戶。網絡

3.要推送的消息量比較大,須要數據庫表先存儲,再用其餘軟件去推送。併發

4.數據庫作了主從同步,若是大量的寫入要推送的消息,會致使同步性能極差,其餘數據的同步變得極慢,直接影響用戶體驗。app

概括這個場景起來就兩個問題ide

1.數據庫主從同步,大量數據會致使同步性能問題,絕對要避免大量數據的寫入和刪除和修改。性能

2.同時推送消息給大量用戶,用戶請求併發高,服務器沒法承受,須要分壓,削減波峯。ui

爲了解決第一個問題,採用的方案爲將待推送消息的表拆分爲單獨的數據庫中,而且該數據庫不須要作主從同步。(或者採用其餘方案,例如mongoDB等)spa

怎樣生成待推送消息到推送數據庫表呢?code

這裏須要先說一下推送機制,由於待推送信息寫入到待推送表中後,每條消息有個推送時間(主要知足運營須要,提早準備好內容,指定時間進行推送),真正推送的程序是按照推送時間進行過濾數據進行推送的,當指定的推送時間小於當前時間時,就拿去數據進行推送操做。所以這裏的推送時間就成爲了削減波峯,緩解服務器並不是壓力的關鍵

方案一:傳統方案

1.查詢出推送的目標對象集合(數據量有點大,查詢比較慢,網絡IO問題也嚴重,開發環境須要20秒)

2.根據目標對象集合插入對應的帶推送消息到帶推送表中。(values()(),限制999,超過容易出bug,開發環境須要20秒)

方案二:既然網絡IO有問題,那就不查詢出來,使用insert into select 來作吧。

1 insert into [ak50Push]..ak_push select top 10000 newid(),'00000000-0000-0000-0000-000000000000',1,isnull(sp.tokenId,''),sp.guid,'推送信息',0,isnull(sp.gtClientId,''),isnull(sp.iskeeper,0),getdate() from ak_space sp with(nolock)
2 join (select guid from ak_student t with(nolock) where t.akstustate=1 union all select guid from ak_teacher t with(nolock) where t.akteastate=1) tt on sp.guid=tt.guid where sp.isClientInstall=1 and sp.iskeeper=0 

這個方案並未真正解決問題,由於最終的推送時間都一致了,數據寫入了,但並不是未解決。

方案三:既然這樣,那就用臨時變量+循環來作吧。

 1 declare @targetUser table(tokenId nvarchar(50),gtClientId nvarchar(50),iskeeper tinyint,guid uniqueidentifier PRIMARY KEY)
 2 insert into @targetUser select sp.tokenId,sp.gtClientId,sp.iskeeper,sp.guid from ak_space sp with(nolock) join (select guid from ak_student t with(nolock) where t.akstustate=1 union all select guid from ak_teacher t with(nolock) where t.akteastate=1) tt on sp.guid=tt.guid  where sp.isClientInstall=1 and sp.iskeeper=0 --and ((sp.tokenId is not null and sp.tokenId !='') or (sp.gtClientId is not null and sp.gtClientId!=''))
 3 declare @total int,@perTime int,@perSize int,@index int
 4 declare @pushTime datetime
 5 set @total=(select count(*) from @targetUser)
 6 set @index=1
 7 set @perTime=15
 8 set @perSize=500
 9 set @pushTime='2018-10-10 14:16'
10 while @index<=(@total/@perSize+1)
11 begin
12     insert into [ak50Push]..ak_push select top 500 newid(),'00000000-0000-0000-0000-000000000000',1,isnull(sp.tokenId,''),sp.guid,'推送信息',0,isnull(sp.gtClientId,''),isnull(sp.iskeeper,0),@pushTime from @targetUser sp
13     where not exists (select 1 from [ak50Push]..ak_push p with(nolock) where p.recGuid=sp.guid and p.headGuid='00000000-0000-0000-0000-000000000000')
14     set @index+=1
15     set @pushTime=DATEADD(second,@perTime,@pushTime)
16 end

此方案實現了目標,但執行時間爲40多秒,太慢了。而且發現待推送表的讀取次數很是高。

方案四:既然待推送表的讀取高,那就不讀該表就是了。

 1 declare @targetUser table(tokenId nvarchar(50),gtClientId nvarchar(50),iskeeper tinyint,guid uniqueidentifier PRIMARY KEY)
 2 declare @tempUser  table(tokenId nvarchar(50),gtClientId nvarchar(50),iskeeper tinyint,guid uniqueidentifier PRIMARY KEY)
 3 insert into @targetUser select sp.tokenId,sp.gtClientId,sp.iskeeper,sp.guid from ak_space sp with(nolock) join (select guid from ak_student t with(nolock) where t.akstustate=1 union all select guid from ak_teacher t with(nolock) where t.akteastate=1) tt on sp.guid=tt.guid  where sp.isClientInstall=1 and sp.iskeeper=0 --and ((sp.tokenId is not null and sp.tokenId !='') or (sp.gtClientId is not null and sp.gtClientId!=''))
 4 declare @total int,@perTime int,@perSize int,@index int
 5 declare @pushTime datetime
 6 set @total=(select count(*) from @targetUser)
 7 set @index=1
 8 set @perTime=15
 9 set @perSize=500
10 set @pushTime='2018-10-10 14:16'
11 while @index<=(@total/@perSize+1)
12 begin
13     insert into @tempUser select top 500 * from @targetUser
14     insert into [ak50Push]..ak_push select top 500 newid(),'00000000-0000-0000-0000-000000000000',1,isnull(sp.tokenId,''),sp.guid,'推送信息',0,isnull(sp.gtClientId,''),isnull(sp.iskeeper,0),@pushTime from @tempUser sp
15     delete p1 from @targetUser p1 where exists (select 1 from @tempUser p2 where p1.guid=p2.guid)
16     set @index+=1
17     set @pushTime=DATEADD(second,@perTime,@pushTime)
18 end

存在問題:比方案三的性能還差,這個等了2分多種沒有出結果,直接停掉了。

方案五:最終方案

1 insert into [ak50Push]..ak_push select newid(),'00000000-0000-0000-0000-000000000000',1,isnull(sp.tokenId,''),sp.guid,'推送信息',0,isnull(sp.gtClientId,''),isnull(sp.iskeeper,0),DATEADD(second,sp.timediff,getdate()) from (
2 select sp.tokenId,sp.gtClientId,sp.iskeeper,sp.guid,(ROW_NUMBER() over (order by sp.guid))/500*15 as timediff from ak_space sp with(nolock) join (select guid from ak_student t with(nolock) where t.akstustate=1 union all select guid from ak_teacher t with(nolock) where t.akteastate=1) tt on sp.guid=tt.guid  where sp.isClientInstall=1 and sp.iskeeper=0 --and ((sp.tokenId is not null and sp.tokenId !='') or (sp.gtClientId is not null and sp.gtClientId!=''))
3 )sp

時間上有遞增,而且在開發環境只須要1秒就完成了。

 

本文禁止轉載,請勿複製,全部代碼僅供參考,根據本身的實際狀況進行修改。

相關文章
相關標籤/搜索