Web项目中后台进程的应用
实在没精力想一个劲爆的Title了😂😂😂
书接上回, 小白用加持各种缓存解决了访问数激增带来的访问变慢的问题后, 好日子没过几天, 又出了新的问题. 某日豆瓣的API挂掉了, 然后连带着小白的系统也跟着挂了. 在从零开始构建那一章里我们设计了在用ISBN查询图书信息的时候, 是通过豆瓣的API远程的获取对应ISBN号的图书信息, 在通过HTTP客户端访问远程信息的时候一般会加上超时时间, 万一网络环境不好访问不到呢, 过了超时时间就会报错, 作为一个能徒手从零写完整个系统的小白呢, 很自然的用了常用的超时参数20秒, 但是小白的服务器只有4个核, 当超过4个人同时在通过ISBN查询的时候, 就把小白的系统挂住了.
很多时候, 我们需要在逻辑里加入 1. 需要长时间运行的逻辑(比如, 压缩图片, 压缩视频) 2. 需要长时间或者不定时间IO等待的逻辑(比如请求站外的资源, 接口)的时候, 我们不能把这些逻辑写到控制器逻辑中, 因为这些操作都会导致控制器挂起, 然后后果就和小白的应用一样了.
学过多线程的同学这个时候会灵光一闪, 诶, 我用多线程, 遇到这种逻辑就新建一个线程, 然后放到线程里执行, 怎么样? 听起来是个不错的主意, 但是, 当你要重启服务的时候就要小心了, 万一还有现成没有结束呢, 服务就被重启了, 然后就杯具了.
所以为了和Web服务的进程里解藕出来, 我们需要把这些操作放到额外的进程里, 并且将任务数据持久化(调用的Action和参数), 这样当后端进程隔屁重启后, 还可以重新执行没有完成的任务. 当任务执行失败之后, 还可以重新执行任务, 能够设置重试的次数, 当失败超过次数后就终止执行.
比如Python的Celery, huey, Java下用Mule + Quartz or EJB Scheduling + HornetMQ, Ruby的Sidekiq或者DelayedJob.
在引入这些后端服务管理框架之后, 我们还需要用引入消息系统来对接服务管理和Web主进程.然后项目结构就是下面这样子.
加入任务后的系统结构上图的结构是任务从控制器到消息对列, 然后在到任务管理的过程. 但是因为是通过消息中间件来传递请求的, 所以返回值也只能是以异步的形式反馈回去. 但是我们用的大多数Web框架都是同步的, 比如Django, Flask, Rails, PHP... 而我们的大多数请求也是同步的, 只有极少部分逻辑有异步的需求, 比如取返回值.
所以针对Web和App两个不同的场景, 我们有两种不同的方式.
基于Web的异步任务返回
鉴于Web的特殊性, 我们加入了一个异步的Web服务框架来实现了一个Longpulling的订阅服务.结构如下
加入异步框架后的结构在向后台服务提交执行的请求后, 生成一个任务的unique的ID返回给网页, 网页上通过Ajax带着这个ID去向异步Web服务订阅这个ID的频道, 当后台任务执行结束的时候, 给这个频道发一个消息, 然后页面就能在第一时间得到执行完毕的消息了, 然后再通过Ajax带着频道ID去获取返回值的接口获取返回值就好了.
APP后端的异步任务返回
手机的消息最好是通过手机操作系统自身的消息机制来返回, 比如苹果的APNS, 或者安卓官方的C2DM. 但是鉴于特殊国情, 国内的话, 有一大堆三方的推送平台, 我也不造那家比那家好用多少, 你可以自行斟酌.
App后端的推送方案要注意的一点是不管是三方推送也好, APNS也好, 都不能保证100%的到达率. 所以消息外发的任务也最好持久化, 然后通过Feedback机制确保消息到达.
经过上述的一套组合拳后, 小白的网站又可以健康稳步发展了. 不过在上面我们提到了异步Web框架, 这是前面没有涉及到的, 所以下一章我们来讨论: 异步模式
to be continue...