如何给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秒就完成了。

 

本文禁止转载,请勿复制,所有代码仅供参考,根据自己的实际情况进行修改。

posted @ 2018-10-10 15:35  MyFirstHome  阅读(753)  评论(0编辑  收藏  举报