Rio 说
首发于Rio 说
知乎是怎样发邮件的?

知乎是怎样发邮件的?

不少人都很好奇知乎是怎样发送邮件的。下面就给大家简单介绍一下知乎的邮件处理流程吧!


电子邮件系统非常复杂,历史包袱也非常多。自己搭建邮件服务器常常是件吃力不讨好的事情。所以对于小团队而言,最好的办法是将这部分工作外包出去给专业的服务商做。过去几年中知乎尝试过 Sendgrid、Postmark、Amazon SES 等服务,但因为这样那样的问题都不是很满意【详见 知乎的邮件系统为什么不再用 Amazon SES 了?】。几番选择比较下来,目前在用的比较满意的两家服务商是 Mailgun [mailgun.com/] 和 Sendcloud [sendcloud.sohu.com/]。同时使用两个服务商给我们提供了一定的冗余,遇到其中一家偶尔故障的时候不至于太影响我们的邮件投递。

知乎的邮件分为两大类:网站用户行为产生的事务性邮件(transactional mail,比如通知、密码重置确认信等)和定期生成的批量邮件(bulk mail,比如每周精选、知乎圆桌等)。为了保证投递质量,这类邮件要分开用不同的域名和 IP 地址发送。这些信息需要在 Mailgun 和 Sendcloud 的管理界面中先配置好。


我们用 Go 语言 [golang.org/] 写了一个处理邮件的服务,名叫 Postbox。下面是 Postbox 邮件发送的业务流程图:


Postbox 对知乎的内部应用提供了一个基于 HTTP 的 RPC 接口。知乎网站和管理后台按照预先设计的模板生成好邮件后,会以表单的形式 post 到 Postbox RPC 服务器。为了保证服务的高可用,我们在两台不同的服务器上部署了 Postbox RPC,前面用 HAProxy 进行负载均衡。这样做的好处是我们可以将其中一台停机升级,一切正常后再对另外一台做同样操作,过程中服务不会中断。


Postbox RPC 服务器接到客户端的邮件请求后,会把请求中相应的参数提取出来(比如发件人、收件人、主题、正文等),然后把它序列化为一个 JSON 字符串,压缩后存储在 Beanstalkd 队列中。知乎网站的事务性邮件的产生速率波动比较大,比如一个热门问题新增了一条答案后会通知所有关注者,会在短时间内产生大量的邮件请求,而邮件处理的速度是有恒定上限的,使用队列可以缓冲掉二者的临时矛盾。由于 Beanstalkd 队列的任务位于内存中,压缩可以有效减缓积压的邮件任务造成的内存压力。

Postbox 的另外一部分进程 mailer 订阅了 Beanstalkd 中的指定队列,并从中取出邮件任务,随机选择一个服务商,并且根据所选服务商的 API 要求生成合规的请求,然后通过 HTTP 提交过去。至此,邮件就已经离开我们的系统,转由服务商来处理,最终送达用户邮箱。

但任务并没有到此结束。邮件发出去后,并不一定都能正确送达,而是有可能因为各种原因送达失败(比如邮件地址不对,或被接受方系统判定为垃圾拒收等)。我们需要搜集这些反馈信息并进行相应的处理。过程如下图所示:

Mailgun 和 Sendcloud 在收到接受方邮件系统的确认或者出错弹回信息后,会通过 HTTP 请求的形式通知我们。所以 Postbox 的另外一个功能是提供 webhook 处理这些事件通知。服务商的事件通知发往我们的 webhook,在对这些通知进行基本的合法性验证后,webhook 会将事件的关键信息提取出来(比如邮件发给谁?发送成功还是失败了?如果失败,是什么原因?)并转交给相应的应用程序进行后续处理(比如邮件地址不正确导致的发送失败,则需要告知用户修改注册邮箱;如果是对方举报垃圾邮件,则需要将对方的邮件地址从我们的推送系统中去掉等等)。

看似简单的业务流程,要保证高效率、高可用还是有不少的挑战的,而做好监控则是必不可少的步骤。在 Postbox 服务的主要路径我们都实时采集各种关键指标【比如吞吐率、延迟、失败率等】,并汇总到 Graphite 里面查看和监控异常。




相关阅读

编辑于 2013-10-29

文章被以下专栏收录