看起来简单的通知推送服务,真的简单吗
背景
目前OneAlert提供短信、邮件、电话、APP四种通知通道,其中前三种的使用量最高(90%以上的用户),因此靠谱的第三方推送提供商至关重要。经过对各种三方推送服务的公司调研,目前锁定了阿里大鱼[2]、容联云[3]、云片[4]、云之讯[5]、SendCloud[6],这5家平台提供商。
首先我们来分析一下接入三方后的通讯模型
注:配额这里是指三方服务商的发送限制,比如每小时最多每个电话拨打几次
我们作为一个服务提供商,必然要确保用户的告警可以准确、准时、不漏的投递给用户,这个问题看起来一目了然,没什么难度,其实不然,我们分析下以上通信模型中的几种情况:
s1:发送成功
最好的情况(这里由于无法真正的监控第三方是否真正投递到了用户,不过根据以往的工单经验,丢失的概率很小)
s2:超出配额
换个重发
超出配额,那就换个重发呗?这是下意识的解决思路,我们看看有什么问题:
独立状态服务
很直观的发现,重试的次数有可能会很多,这非常影响推送实时性。马上,我们又会想到可以对每次超出配额的情况进行缓存,提炼一个状态服务,如下:
问题到此为止,一切美好已经发生… 遗憾的是,该状态服务几乎是不可用的。我们可以进一步思考:
当第一次配额超出的情况发生时,按照上面的设计,该状态服务会缓存下来;
当第二次推送来临时,会首先请求状态服务,拿到配额超出的那个服务商,排除掉它,使用其他服务商发送
当第三次推送来临时,会首先请求状态服务,拿到配额超出的那个服务商,排除掉它,使用其他服务商发送
…
当第n次推送…
此时问题一目了然,某个三方服务商第一次超出配额后就不再被请求了,这显然不是我们要的。如果我们试图及时、恰当的让某超出配额的状态失效掉,问题是我们如何知道什么时间或者哪一次请求时让其失效呢?
小结
目前看来,针对简单的请求-重试模型是很难解决我们的问题的。
换个思路
上面所有的方案每次发送都是无状态的,如果我们对每个时间段每次发送用的哪个服务商记录下来,结合配额限制,这样是不是可以做到避免处罚超出配额的问题呢?
我们来分析下:
嗯… 此时貌似已经解决了问题。不过从设计来看,NotifySender职责太多了,既要负责发送消息,又得记录发送状态,不符合单一职责原则。如果将来对三方服务商做增加、下线操作,还要动NotifySender,显然是不合理的。
小结
通过有状态的发送,解决了配额问题,但NotifySender职责太多,不利于拓展,需要再次设计。
最后方案
事实上,我们需要一个这样的服务,它能根据三方服务商目前的配额情况,自动的给我们的消息路由到合适的服务商。
实质上,对于每次发送「计数」这件事,其实可以换一个角度思考,我们只要确保在单位时间内,按照配额作为权重将消息分发给服务商即可,这样也能确保不会触发超出配额异常。
由于篇幅关系,这里不对轮询算法进行展开讨论,而且本身算法不是重点,此类算法网上一搜一大堆,笔者也不想赘述,重要的是我们对业务场景进行实质分析。
ok,直接给出结论:加权轮询算法(如果读者有兴趣,可以给我发邮件,我们一起讨论: P)
对调度服务的补充解释:
我们选择将服务商信息存放在配置中心里,主要考虑了以下几点:
- 如果服务商将来由于各种原因维护,我们只需要从配置中心去掉这个服务商即可,NotifySender不需要做任何改动
- 如果服务商将来更改了配额,我们只需要在配置中心重新配置一下这个服务商的配额信息即可,NotifySender也不需要做任何改动