如何给30W的APP用户推送消息?
给30W用户推送消息并不难,使用第三方的推送可以说相当简单,基本上都支持针对app级别的推送,服务端只需要调用一次推送接口,剩下的交给第三方推送即可。但有的场景下,完全交给第三方是不可行的。比如下面的场景
1.用户收到消息后会打开app,打开app就会请求服务器去加载数据,这样并发请求会非常高,而因为某些原因服务器承受不住的情况。
2.推送消息是有针对性的用户的,但针对的是部分用户。
3.要推送的消息量比较大,需要数据库表先存储,再用其他软件去推送。
4.数据库做了主从同步,如果大量的写入要推送的消息,会导致同步性能极差,其他数据的同步变得极慢,直接影响用户体验。
归纳这个场景起来就两个问题。
1.数据库主从同步,大量数据会导致同步性能问题,绝对要避免大量数据的写入和删除和修改。
2.同时推送消息给大量用户,用户请求并发高,服务器无法承受,需要分压,削减波峰。
为了解决第一个问题,采用的方案为将待推送消息的表拆分为单独的数据库中,并且该数据库不需要做主从同步。(或者采用其他方案,例如mongoDB等)
怎样生成待推送消息到推送数据库表呢?
这里需要先说一下推送机制,因为待推送信息写入到待推送表中后,每条消息有个推送时间(主要满足运营需要,提前准备好内容,指定时间进行推送),真正推送的程序是按照推送时间进行过滤数据进行推送的,当指定的推送时间小于当前时间时,就拿去数据进行推送操作。因此这里的推送时间就成为了削减波峰,缓解服务器并非压力的关键。
方案一:传统方案
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秒就完成了。
本文禁止转载,请勿复制,所有代码仅供参考,根据自己的实际情况进行修改。