給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秒就完成了。
本文禁止轉載,請勿複製,全部代碼僅供參考,根據本身的實際狀況進行修改。