2-18 6,393 views
商业转载请联系作者获得授权,非商业转载请注明出处。
作者:甘醇
链接:http://www.zhihu.com/question/26533799/answer/33154508
来源:知乎
不过看到话题标签有Redis,猜测题主想问的应该是现在很广泛使用的消息队列(MQ)。这里的消息不只是简单的文本信息,也可以是序列化后的对象。现在比较流行的开源消息队列系统有Beanstalkd,RabbitMQ,Redis(可以作为队列系统使用)等,其核心作用都是先将消息数据通过系统接口按顺序放入队列(暂存于内存),需要时再按放入的顺序依次取出以作后续处理。
就拿我前段时间做的邮件发送系统举例:
系统以HTTP协议提供服务,对外提供的主要功能是发送邮件,API地址为 /api/msg.send。使用者只要以POST请求此地址,传入subject,body,recipient这三个参数,即可发送一封邮件。
方案1: 不使用数据库,不使用队列
请求接口时,控制器直接调用sendmail或外部smtp服务器发送邮件,整个发送过程HTTP请求处于等待状态,待邮件发送完成,返回发送结果(只是调用发送程序是否成功的结果,不是邮件真正的发送结果,真正发送结果需要分析邮件日志)
这个方案最简单直接,平时发送少量邮件是可以的,但是缺点也很明显:
1. 因为接口调用时整个邮件发送过程都是同步进行的,每次请求都要等待邮件发送端完成处理,必然导致每次调用接口的等待时间增长。
2. 当系统接口并发请求较高时,系统可用性不仅受限于WebServer的处理能力,还完全受限于邮件发送端软件的处理能力,其中任一环节故障就会导致整个系统无法提供服务。
3. 若邮件发送端软件出现故障(比如SMTP连接超时),导致某次请求时邮件发送失败,那这封邮件内容便彻底丢失了,系统没有任何存留,不能实现自动重发。
方案2: 使用数据库,不使用队列
请求接口时,将要发送的邮件信息存入数据库,表结构如下:
id | subject | body | recipient | sent_at | failed_at | failed_times
然后在服务器上运行一个定时任务,每秒一次读取 sent_at=0 && failed_times < 10 的记录,随后调用邮件发送端发送邮件,成功后把sent_at设为当前时间,失败后设置failed_at并累加failed_times。 方案2已经用到了类似队列的思想。
相对于方案1的提升:
1. 去掉了同步发送邮件的操作,接口请求响应会快很多
2. 邮件发送失败后可以重发,邮件不会丢失
3. 当邮件发送端完全失效后系统也可以接受邮件发送请求,待发送端恢复后可以继续发送邮件。
但还是存在缺点:
每次请求都会写一次数据库,当大并发量或者大数据量(一次请求包含100万个收件人)时,数据库负载过高影响稳定性,同时也会严重增加接口的响应时间(一下子写入100万条记录不是闹着玩的)
方案3: 数据库 + 队列
请求接口时,将要发送的邮件信息以JSON格式存入队列系统,放入的单个消息形如:
{
“subject” : “今天没吃药”,
“body” : “感觉自己”,
“recipient” : “mengmeng@da.com”
}
现在的队列基本上都是内存队列,数据存取非常快,一瞬间写入100万条数据再也不是难事。
随后,在服务器上运行一个常驻进程任务(Worker),实时监听队列中是否有新的消息(Job,此处指邮件信息)。当新消息进入时,从队列中取出消息,调用邮件发送端完成处理,发送成功后将此消息销毁并将消息内容插入数据库(同方案2),如果发送失败,将此消息重新放入队列,并加入一个60秒的延时标记,意味60秒后再取出处理。
这样改进后整个系统的吞吐量和响应速度将大大提升,而且同时也让系统支持了分布式运行的能力。每个Worker进程都可以视为一个处理节点,倘若把worker分散到不同的服务器上,便实现整个系统的分布式处理了,这也是队列的一个重要特性之一。