Medium 这样的站点看上去似乎很简单,不过背后的复杂程度足以让人惊讶。这还仅仅只是一个博客网站么?要是这样的话,你用 Rails 几天就能搭建一个出来。:)
闲话少说,我们从最底层说起。
## 产品环境
目前我们运行在 [Amazon 的虚拟私有云](http://stackshare.io/amazon-vpc)上。我们使用 [Ansible ](http://www.oschina.net/p/ansible)做系统管理,可以让我们的配置处于源码控制下,可以通过可控的方式很轻松的进行更新。
我们有着面向服务的架构,在其上运行了大约一打的产品服务(取决于你如何去统计,还有很多小一点的服务)。是否作为独立的服务来部署,主要取决于其功能,服务边界上是否可能产生有依赖的变动,以及对资源的利用。
我们主要的应用服务器仍然写在Node里,它允许我们在供应商和客户之间交换代码,我们用编辑器就要用到它,公布变革也是用它。Node为我们工作得很好,但是在我们把事件循环编写成块的时候,性能问题就浮现了。为了缓和这一问题,我们在每台机器上运行多个实例,并把它们路由到昂贵的端点,以此来分隔它们。我们把它与V8运行环境相挂钩,以深入了解哪个部件运行花费的时间较长。通常它是因为在JSON还原序列化时目标的具体化。
我们用Go编程的时候可以享受到一些辅助服务。我们发现用GO建立、打包和部署很容易。我们喜欢不需用到繁冗并调试虚拟机Java的类型安全。就我个人而言,我更喜欢在团队环境中使用武断的语言。它能提高一致性、减少歧义,最终让你避免作茧自缚。
我们现在使用CloudFlare提供静态资源,尽管我们把5%的流量送到Fastly,同时还送5%的流量到CloudFront来保持它们的缓存热度,以防在紧急情况下万一我们需要割接。最近我们也把CloudFlare用于应用流,基本上是为了DDOS保护。但是对于性能提升我们还是乐见其成的。
我们使用Nginx和HAProxy的结合作为反向代理,达到负载均衡,以实现我们需要的Venn Diagram特性。
我们仍然使用Datadog监测,使用PagerDuty报警,但是我们现在大量使用ELK(Elasticsearch,Logstash,Kibana)调试出现的问题。
## 数据库
DynamoDB仍然是我们基本的数据存储库,但是它仍不完美。我们遇到的常见的问题之一是在重大事件发生时和有百万追随用户时的热键问题。在Dynamo前面我们可以用Redis缓存器,它能通过读取来减轻这些问题。
开发者便利性和产品稳定性二者之间的优化似乎总是存在矛盾,但是我们在努力缩小分歧。
我们开始使用Amazon Aurora获取最新的数据,它的查询和过滤功能比Dynamo更灵活。
我们使用Neo4J来储存代表媒体网络的实体之间的关系,用两个副本来运行一个主本。
人、邮件、标签和合类是图表中的节点。
边界在实体创建的基础上建立。当人们发生以下行为,比如跟随、推荐和强调时,我们按图索骥,过滤并推荐相关的邮件。
## 数据平台
早起我们的数据很匮乏,所以在数据分析基础架构上做了很多投资,为商业和产品上的决策提供帮助。最近以来,我们可以在同样的数据处理流程上为整个产品体系提供更多的反馈,甚至可以运行类似 Explore 这样的数据驱动的功能。
我们使用 [Amazon Redshift](http://stackshare.io/amazon-redshift) 作为数据仓库,它提供了可伸缩的存储和处理系统,我们其他的工具就运行在其上。我们持续的把核心数据(例如用户、文章)从 Dynamo 导入到 Redshift,以及把行为日志 (例如:文章阅读、翻页等等) 从 S3 导入到 Redshift。
我们使用 Conduit 来对任务做调度,这是一个内部工具,可以管理计划、数据依赖,还可以进行监控。我们的任务调度模型是基于断言的,只有一个的所有的依赖都满足了,这个任务才会被执行(例如,依赖全天行为日志的每日任务)。对于产品,这方面被证明是非常重要的:数据的生产者和消费者互相解耦,简化配置,系统状态可预知和易调试。
尽管对我们来说在 Redshift 上运行 SQL 查询良好,我们还是需要将数据不断输入输出 Redshift。我们越来越转向 ETL 的[Apache Spark](http://stackshare.io/spark),这是因为它的灵活性与规模增长的能力。随着时间的推移,Spark 可能会成为我们数据管道的首选工具。
我们使用[协议缓冲(Protocol Buffers)](https://developers.google.com/protocol-buffers/)对我们的模式(模式演化规则)来保持所有层的分布式系统同步,包括移动应用,web 服务,和数据仓库。使用自定义选项,我们标注模式与表名和索引等配置细节,验证约束最大长度的字符串,或者控制接受数据控制的范围。
人们也需要保持移动和 web 应用程序同步,开发人员使得所有日志一样,产品研究员可以以同样的方式解释字段。我们帮助我们的成员在数据处理模式规范上的工作,并严格记录字段的消息,发布文档生成的原型(*.proto*)。
## 图像
我们的图片服务器现在是用 [Go](http://stackshare.io/go) 写的,并使用了瀑布策略处理图像。服务器使用 [groupcache](https://github.com/golang/groupcache),它提供了 memcache 的替代方案,来减少重复的工作。支持内存中的缓存是一个持久的 S3 缓存,然后来处理图像处理需求。这让我们的设计师可以在不同平台上,灵活地改变图像的表示和优化,而不必做大的批处理缩放图像作业。
现在主要用于调整和裁剪,早期版本的网站允许颜色清洗、模糊和其他图像效果。处理动态 gif 一直是一个巨大的头痛的问题,这应该又是另一篇文章了。
### 文本截图
完整的文本截图功能由一个小型的 Go 服务器驱动,使用 [PhantomJS](http://stackshare.io/phantomjs) 作为渲染引擎。
![](https://box.kancloud.cn/2015-11-27_5657fc134768c.png)
我一直想把渲染引擎转换为 Pango,但在实践中,将图片嵌入 HTML 的方式更为灵活和方便。这项功能的使用频率意味着我们可以很简单地处理吞吐量。
## 自定义域名
我们允许用户对他们的 Medium 作品设置自定义域名。我们想让单点登录和 HTTPS 无处不在,所以让它开始工作不是件小事。我们有一组 HAProxy 服务器专门用于管理证书和导向主应用服务器的流量。在设置域名时还需要一些手动操作,但是我们通过自定义整合 [Namecheap](http://stackshare.io/namecheap) 已经自动化了很大一部分。证书的规定和公开链接是由一个专用服务处理的。
## Web 前端
在网页上,我们倾向于电子化。我们有自己的单页应用程序框架,使用闭包作为标准库。我们使用闭包模板在客户端和服务器渲染,我们使用闭包编译器压缩代码并把它分割成模块。编辑器是我们网页应用最复杂的部分,Nick 写出了 iOS 系统。
## iOS 系统
我们的应用程序都是本地下载好的,很少使用网络视图。
在 iOS 中,我们使用国产构架和内置构件的混合。在网络层,我们使用 NSURLSession 提出请求,使用 Mantle 来把 JSON 解析成模型。 我们有一个建立在 NSKeyedArchiver 上的缓存层。我们有一个通用的方法把项目列入不同的列表,同一列表中的项目有共同特征。这就能让我们快速建立不同类型内容的列表。后视图是用 UICollectionView 自定义布局构建的。我们使用共享组件来渲染完整的文章和后预览。
Medium 的员工尽一切努力来尽快开发和推出新的应用。我们发布更新的节奏是受限于 Appstore 的审阅周期的,但我们正尽己所能地推进这一进程,即使只有很少的更新。
测试我们使用 XCTest 和 OCMock.
## Android
对于 Android,我们目前会保持 SDK 和 support 库都是最新的。我们没有使用综合性的框架,而是倾向于为重复的问题建立一致性的模式。我们使用 [guava](http://stackshare.io/guava) 来弥补 Java 缺失的功能。不过有的情况下,我们会使用[以特定问题为目标的第三方库](https://medium.com/medium-eng/medium-android-tools-f827bb96b8e4)。我们的 API 的返回结果使用了 protocol buffer 协议,在 app 里会生成这些对象。
我们使用 [mockito](http://mockito.org/) 和[ robolectric](http://robolectric.org/)。我们会为动态编写高层级的测试用例 — 在我们刚开始添加界面或者准备重构的时候会创建一些简单的版本。在我们不断重现 bug 后,测试用例会越来越多,帮我们避免代码功能回退。我们编写底层的测试用例来验证单个类的细节 — 在实现新功能后,通过这些用例能看到类之间是如何交互的。
所有提交会自动推送到 play store,用于 alpha 版本,Medium 的员工可以立刻使用到。(这里包括另一个最受欢迎的 app,[我们的内部的 Medium 版本— Hatch](https://medium.com/inside/hatching-inside-medium-5ae60292d655))。大多数周五,我们会把最后一个 alpha 版本发布到 [beta 组](https://medium.com/@caramev/welcome-to-the-android-beta-4ca716ef65e4),让大家在周末去使用。接下来在周一产品会由 beta 变成正式版本。因为最新代码一直保持可发布状态,所以一旦我们发现一个 bug,我们可以在正式产品上立刻修复。如果不放心某个新的功能,可以让 beta 版测试的时间稍微长一些;如果感觉好的话,发布也可以更频繁一些。