最近上线了一个推送系统。推送系统做用是将短信,邮件,app push等消息触达到用户。目前功能上只实现了短信通道,而且随着业务量的扩大,还须要并行扩展推送能力。 java
公司的对推送系统的应用场景有两个,一个是主动发送,即在某一个时间点集中推送几万条短信。另外一个是用户触发,经过上游系统接收到用户的行为而发送一条短信给用户。产品部门要求每条短信发送出去后,都要具体的执行报告:用户是否收到,收到的时间,失败的缘由等等。因此系统就须要跟踪一次推出去的每条短信的发送状态。 sql
目前系统接入了5个不一样的短信网关。这个5个短信网关其中有4个是经过提供开发包(jar包形式),以socket的方式链接,一个是经过http协议访问,因此对于短信状态的处理也就有两种不一样的形式。 数据库
第一种socket形式。这种开发包的处理方式通常是在第一次发送时与网关创建socket连接,双方在连接中进行通讯。有的开发包里会自动维护该连接的状态,保证不会断掉,一直能够工做。而有的开发包则没有该项功能,须要客户端去维持这个连接。 缓存
若是系统中没有短信上行的需求,能够不作维持,在发送的时候判断一下连接是否可用,若是断掉,从新创建就是了。若是有上行的需求,恰巧链接断掉,那就会收不到网关推送过来的上行信息了,这种状况下必须维持。维持的方式通常都是定时去检查连接是否可用,若是不可用,马上创建恢复。咱们在接某个网关的时候,发现这个连接维持2个小时通常没有问题,若是单纯检查连接,调用方法返回是通的,但实际上已经不可用了,判断不是很准确,因此咱们干脆再也不去检查这个连接,而是每一个一小时发一条短信,用正式的短信发送去维护他,这样比较省事。这里面还有一个小插曲,每小时发送的短信,开始时接收方设置成了10086,后来被网关方发现,说每小时给10086这样的号码发一条不大好,后来又设置成了公司内部的一个测试号码。 app
通常jar包中封装好了短信发送接口,短信报告回调接口,短信上行接口。要想追踪每条短信的发送状态,必须对回调接口的数据进行处理。 socket
已其中一个短信网关的开发包为例。它的整个一条短信发送过程分红了3个处理部分: 测试
一,发送。发送时会获得一个seqId,这个是在客户端标示短信的id 优化
二,第一次回调,网关返回seqId和msgId 两个值。此次回调标志着短信已经进入网关的发送队列里,排队等待发送,msgId是在网关端惟一标示该短信的id url
三,第二次回调,网关返回msgId和status,errMessage三个值,标志着该条短信最终的状态,用户是否收到出错缘由等。 spa
经过上面三个步骤能够看到,必须每次都要进行关联处理,才能保证最后的短信状态。
步骤 | seqId |
msgId |
status |
一 | √ | |
|
二 | √ | √ | |
三 | |
√ | √ |
一,10w端短信由其余系统插入到数据库中,做为一个发送task通知推送系统,每条短信都有一个自增加的smsId。
二,推送系统批量取出10w条短信,调用短信网关,每条短信都获得一个seqId。这样每条短信都须要根据smsId,更新seqId
三,第一次回调,每条短信要根据seqId,更新msgId
三,第二次回调,每条短信要根据msgId,更新最终的状态。
这样算起来10w条短信,最终发送完毕要更新30w次数据,并且这其中有两个要注意的是:1, 三个步骤必须按照顺序执行,不然where语句中须要的条件,数据库里根本尚未插入。2, 10w条瞬间发送,大量的写和回调在短期内就要操做,要保证不会丢失。
针对这个状况咱们采用队列的方式来处理,利用RabbitMQ来缓存全部的数据库操做。RabbitMQ便可以缓存大量的对象不会丢失,并且先进先出,保证了这三步执行的顺序。队列中缓存的对象封装成SmsDto这样一个类,除了必要的字段后,另外增长了一个sqlAction字段,在对象从队列中pop出来后,判断是进行第几步的数据库操做。
public static final int UPDATE_SEQID_STATE_BY_SMSID = 100; public static final int UPDATE_MSGID_BY_SEQID = 101; public static final int UPDATE_STATE_BY_MSGID = 102;这样在发送快速执行的时候,不用担忧数据库短期内的大量写,能够经过队列pop的速度来控制写的速度。
其中系统优化的一个地方是队列pop写,开始的时候是每pop一个smsDto,根据sqlAction就执行一次数据库的操做,这样发现写的速度太慢,产品要求客户能够实时在网页上看到发送的统计结果,而咱们的发送回调常常要写2,3个小时才能从队列里消费完。针对这个状况,就是要批量写数据库,然而又必须严格按照顺序来执行,因此就作了个3个List,根据sqlAction分别将smsDto放入这个三个不一样的list里,在一分钟的间隔里,按照顺序来批量保存在3个List。这样保存的效率大大提升,基本上每条短信均可以实时从网页上看到发送状态。
/** * 批量处理数据库操做 * 必须按照顺序执行 * */ private void batchSave(){ if (!step1List.isEmpty()){ smsService.save(step1List); } if (!step2List.isEmpty()){ smsService.save(step2List); } if (!step3List.isEmpty()){ smsService.save(step3List); } step1List.clear(); step2List.clear(); step3List.clear(); }以上是socket方式发送短信的处理方式。http协议的方式通常是经过在网关设置一个本身系统的回调地址来接收发送报告和短信上行的。这就须要在本身系统实现两个接口来接收返回的信息。
http形式通常在url上能够批多个手机号来发送,相似于http://ip:port/send?content=abc&mobile=m1,m2,m3..咱们使用的网关会在这个请求后返回一个惟一的码,相似于上述的msgId,惟一标示这一批短信的一个id。
在本身系统实现的短信状态回调接口相似于http://ip:port/report?msgId,mobile1,status,time;msgId,mobile2,status,time...这样就能够根据msgId和mobile来惟一肯定数据库中的一条短信记录,来更新发送状态了。
短信网关的调用已经属于古老的成熟的技术了,大致状况也基本相同,一个成熟的网关均可以获得每条短信的发送结果,这些都从他提供的开发包,文档里来肯定具体的实现方式。不一样的可能就是各自在对大量状态更新方面的处理方式,这就须要结合各自的业务须要来实现了。