背景

Medium 是一个网络。这是一个分享有价值的故事和想法的地方,在这里人们可以保持想法不断的前行,大家已经在那里花费了 14 亿分钟(换句话说是 2600 年)的阅读时间。

每月的独立用户超过了 2500 万,每周会新增数以万计的新帖子。但是我们心目中的 Medium 应该是以观点量来衡量是否成功,而不是阅读量;不会关心作者的资历,而更关心想法是否有价值;应该能更方便的为大家推送有价值的内容。

我领导了整个工程团队。我之前是 Google 的一名软件工程师,从事 Google+ 和 Gmail 的开发,共同创建了 Closure 项目。在那段时间,我经历过滑雪板比赛,飞机跳伞,还有丛林冒险。

工程团队

我为我的这个团队骄傲。团队里每个人都是天才,大家都充满求知的渴望,为了这个让人激动的事情聚在一起。在我们看来,经历过不同的磨练可以让你成为更有经验的工程师。有兴趣的话可以看看我们其他的价值观。

在如何安排自己的工作上,团队有很高的自由度,不过作为公司,我们会设立季度目标,鼓励迭代冲刺。我们使用 GitHub 做代码 review 和 bug跟踪,用 Google Apps 管理邮件、文档和电子表格。我们是 Slack 的重度用户 (以及 slack bots),有不少团队使用 Trello。

初始的技术栈

刚开始的时候我们把服务部署到 EC2 上。主服务是用 Node.js 写的,每次发布的时候,会合并到DynamoDB。

还有一个 node 服务器用于图片处理,调用 GraphicsMagick 来做具体的复杂的工作。另一个服务被用作 SQS 队列处理,负责后台任务。

我们的 email 使用 SES,静态资源放在 S3 上,CDN 使用 CloudFront,使用 nginx 作为反向代理。另外,使用 Datadog 做监控,PagerDuty 做报警。

网站使用 TinyMCE 作为编辑器。发布之前,我们已经在使用 Closure 编译器和部分 Closure 库,不过模板用的是 Handlebars。

当前的技术栈

Medium 这样的站点看上去似乎很简单,不过背后的复杂程度足以让人惊讶。这还仅仅只是一个博客网站么?要是这样的话,你用 Rails 几天就能搭建一个出来。:)

闲话少说,我们从最底层说起。

产品环境

目前我们运行在 Amazon 的虚拟私有云上。我们使用 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 作为数据仓库,它提供了可伸缩的存储和处理系统,我们其他的工具就运行在其上。我们持续的把核心数据(例如用户、文章)从 Dynamo 导入到 Redshift,以及把行为日志 (例如:文章阅读、翻页等等) 从 S3 导入到 Redshift。

我们使用 Conduit 来对任务做调度,这是一个内部工具,可以管理计划、数据依赖,还可以进行监控。我们的任务调度模型是基于断言的,只有一个的所有的依赖都满足了,这个任务才会被执行(例如,依赖全天行为日志的每日任务)。对于产品,这方面被证明是非常重要的:数据的生产者和消费者互相解耦,简化配置,系统状态可预知和易调试。

尽管对我们来说在 Redshift 上运行 SQL 查询良好,我们还是需要将数据不断输入输出 Redshift。我们越来越转向 ETL 的 Apache Spark,这是因为它的灵活性与规模增长的能力。随着时间的推移,Spark 可能会成为我们数据管道的首选工具。

我们使用协议缓冲(Protocol Buffers)对我们的模式(模式演化规则)来保持所有层的分布式系统同步,包括移动应用,web 服务,和数据仓库。使用自定义选项,我们标注模式与表名和索引等配置细节,验证约束最大长度的字符串,或者控制接受数据控制的范围。

人们也需要保持移动和 web 应用程序同步,开发人员使得所有日志一样,产品研究员可以以同样的方式解释字段。我们帮助我们的成员在数据处理模式规范上的工作,并严格记录字段的消息,发布文档生成的原型(.proto)。

图像

我们的图片服务器现在是用 Go 写的,并使用了瀑布策略处理图像。服务器使用 groupcache,它提供了 memcache 的替代方案,来减少重复的工作。支持内存中的缓存是一个持久的 S3 缓存,然后来处理图像处理需求。这让我们的设计师可以在不同平台上,灵活地改变图像的表示和优化,而不必做大的批处理缩放图像作业。

现在主要用于调整和裁剪,早期版本的网站允许颜色清洗、模糊和其他图像效果。处理动态 gif 一直是一个巨大的头痛的问题,这应该又是另一篇文章了。

文本截图

完整的文本截图功能由一个小型的 Go 服务器驱动,使用 PhantomJS 作为渲染引擎。

我一直想把渲染引擎转换为 Pango,但在实践中,将图片嵌入 HTML 的方式更为灵活和方便。这项功能的使用频率意味着我们可以很简单地处理吞吐量。

自定义域名

我们允许用户对他们的 Medium 作品设置自定义域名。我们想让单点登录和 HTTPS 无处不在,所以让它开始工作不是件小事。我们有一组 HAProxy 服务器专门用于管理证书和导向主应用服务器的流量。在设置域名时还需要一些手动操作,但是我们通过自定义整合 Namecheap 已经自动化了很大一部分。证书的规定和公开链接是由一个专用服务处理的。

Web 前端

在网页上,我们倾向于电子化。我们有自己的单页应用程序框架,使用闭包作为标准库。我们使用闭包模板在客户端和服务器渲染,我们使用闭包编译器压缩代码并把它分割成模块。编辑器是我们网页应用最复杂的部分,Nick 写出了 iOS 系统。

iOS 系统

我们的应用程序都是本地下载好的,很少使用网络视图。

在 iOS 中,我们使用国产构架和内置构件的混合。在网络层,我们使用 NSURLSession 提出请求,使用 Mantle 来把 JSON 解析成模型。 我们有一个建立在 NSKeyedArchiver 上的缓存层。我们有一个通用的方法把项目列入不同的列表,同一列表中的项目有共同特征。这就能让我们快速建立不同类型内容的列表。后视图是用 UICollectionView 自定义布局构建的。我们使用共享组件来渲染完整的文章和后预览。

Medium 的员工尽一切努力来尽快开发和推出新的应用。我们发布更新的节奏是受限于 Appstore 的审阅周期的,但我们正尽己所能地推进这一进程,即使只有很少的更新。

测试我们使用 XCTest 和 OCMock.

Android

对于 Android,我们目前会保持 SDK 和 support 库都是最新的。我们没有使用综合性的框架,而是倾向于为重复的问题建立一致性的模式。我们使用 guava 来弥补 Java 缺失的功能。不过有的情况下,我们会使用以特定问题为目标的第三方库。我们的 API 的返回结果使用了 protocol buffer 协议,在 app 里会生成这些对象。

我们使用 mockito 和 robolectric。我们会为动态编写高层级的测试用例 — 在我们刚开始添加界面或者准备重构的时候会创建一些简单的版本。在我们不断重现 bug 后,测试用例会越来越多,帮我们避免代码功能回退。我们编写底层的测试用例来验证单个类的细节 — 在实现新功能后,通过这些用例能看到类之间是如何交互的。

所有提交会自动推送到 play store,用于 alpha 版本,Medium 的员工可以立刻使用到。(这里包括另一个最受欢迎的 app,我们的内部的 Medium  版本—  Hatch)。大多数周五,我们会把最后一个 alpha 版本发布到 beta 组,让大家在周末去使用。接下来在周一产品会由 beta 变成正式版本。因为最新代码一直保持可发布状态,所以一旦我们发现一个 bug,我们可以在正式产品上立刻修复。如果不放心某个新的功能,可以让 beta 版测试的时间稍微长一些;如果感觉好的话,发布也可以更频繁一些。

A|B 测试 & 功能标志

我们所有的客户端都使用服务器提供的功能标志,它被叫做变体(variants),使用 A|B 测试来保护未完成的功能。
杂项

有很多在产品边缘的其他东西,我在上面没有提及:Algolia 提供我们迭代搜索相关的功能,SendGrid为出入站的邮件设计,Urban Airship 为的是通知功能,SQS 是为了处理队列,Bloomd 是 bloom 过滤器,PubSubHubbub 和 Superfeedr 是为了 RSS,等等,等等。

编译,测试,部署工作流

我们拥抱持续集成和交付,尽可能快地推动绿色(部署)。Jenkins 管理所有这些过程。

过去我们在使用中建立我们的系统,因此我们不会为一个新工程迁移而到 Pants。

我们有一个组合单元测试和 HTTP 级功能测试。所有的提交必须要经过测试才可以合并。我们工作的团队在盒子内使用 Cluster Runner 分配测试,并让其更快。还能很好地与GitHub 集成。

我们尽可能快地部署过渡环境——目前大约 15 分钟——然后给候选的产品使用。主要的 app 服务正常部署在一天五次左右,但是有时候可能多达 10 次。

我们做蓝色/绿色部署。在生产环境中我们发送流量给 canary 实例,并在发布与部署之前发布过程监控错误率。Rollbacks 已经内置了 DNS 转换。

下一步要做的

我们现在正在开始做用于作者和发布商的支付功能。这是一个全新领域的项目,我们现在基本上已经清楚如何去做了。我们认为将来需要更多的方式来产生内容,我们希望支付的手段能激励产生更多高质量的内容和价值。

加入 Medium

我们长期对那些有消费领域经验的任务驱动的工程师有很大兴趣。我们对你了解哪门编程语言不很关心,因为我们认为好的工程师可以很快学会新的技能,但是我们期望你是求知欲高、敏锐的、坚定的和忘我投入的。也就是说,不管是iOS,Android,Node 还是 Go 的经验,都可以来试试。

我们还在扩充我们的产品科学团队,因此还需要构建数据流处理和大型分析系统方面有经验的人。

另外,我还在找一些工程师 leader,可以帮我们在团队扩充的时候管理好团队。他们需要对组织管理很感兴趣,乐于实践,愿意为团队奉献。

文章转载自 开源中国社区[https://www.oschina.net]

帮助 Medium 阅读时间达到 2600 年的技术栈相关推荐

  1. 山月大佬做了个极客时间返现平台及技术栈介绍

    客时间给广大码农提供了非常优质的关于互联网技术的内容,其中价格也很公道. 有电商的地方就有渠道,而我作为一个渠道商可以从中获利中间差价. 于是山月今天心血来潮做了一个返现平台,方便大家从中购买,并且省 ...

  2. pmp 成本估算准确高_如何更准确地估算JavaScript中文章的阅读时间

    pmp 成本估算准确高 by Pritish Vaidya 通过Pritish Vaidya 准确估算JavaScript中篇文章的阅读时间 (Accurate estimation of read ...

  3. 基于用户点击偏好和阅读满意度的个性化新闻推荐技术

    点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入! 建模用户的兴趣对于进行精准的新闻推荐至关重要.现有新闻推荐方法通常会根据用户的点击行为来推断其兴趣.但是,用户点击感兴趣的新闻标题后,可 ...

  4. 语音识别学习笔记(三)【动态时间归正的识别技术】

    语音识别学习笔记(三)[动态时间归正的识别技术] 1.概述  在语音识别中,简单的将输入模板和参考模板进行比较存在很大的缺陷,因为语音信号具有很大的随机性,即便是同一个人在不同时刻说同一句话,也不可能 ...

  5. 用三年时间培养 1W 名高素质技术人才,这是一个什么样的计划?

    前言 2020年长沙发出软件再出发号召,同时发布了<长沙市软件和信息技术服务业发展三年(2020-2022 年)行动计划>.当软件产业的生态逐渐建立,企业孵化培育的土壤逐渐肥沃,长沙软件产 ...

  6. 黑马程序员—时间一点一点的过去+技术一点一点的增长=成功就业

    文章来源:黑马程序员,黑马论坛       前天刚刚面试完.分数没有达到预想的水平..哎.       黑友们.我想你们有好多肯定学历都是很低.而且也都是零基础吧?对不对?       我先做一下自我 ...

  7. 在博文顶部添加文章字数及阅读时间信息:阅读本文需要xx分钟

    1.在博客园设置中,页首Html代码中添加js代码 2.js代码如下 <script type="text/javascript"> $("#cnblogs_ ...

  8. html打开后阅读时间到期,订单到期后-提示信息界面.html

     订单到期后-提示信息界面 $axure.utils.getTransparentGifPath = function() { return 'resources/images/transparen ...

  9. rabbitmq中默认unack超时时间_RabbitMQ 与 Kafka 的技术差异以及使用注意点

    导言 作为一个有丰富经验的微服务系统架构师,经常有人问我,"应该选择RabbitMQ还是Kafka?".基于某些原因, 许多开发者会把这两种技术当做等价的来看待.的确,在一些案例场 ...

最新文章

  1. mongodb的安装以及客户端
  2. js会用php处理吗,js怎么跟php结合使用
  3. 【408预推免复习】计算机组成原理之CPU的结构和功能
  4. Java NIO学习系列五:I/O模型
  5. Java上机操作练习题-助力期末
  6. Java NIO系列教程(十 五)Java NIO Path
  7. Redis在windows下安装过程
  8. centos 6.5 安装 mongodb
  9. Qt工作笔记-自定义打印及存日志及stderr转stdout(Linux程序调试技巧,提高开发效率)
  10. mongodb 集群shard_mongodb集群构建方案(二)
  11. mongoDB之find()
  12. 软件测试算是后端吗,软件测试--前后端数据交互
  13. [乐意黎原创]]CuteFTP 操作文件时,中文文件名显示乱码的解决
  14. 在淘宝里,他们总结的一些前端Tips
  15. Halcon区域形状特征-area_center、area_holes、select_shape、inner_circle和smallest_rectangle2算子
  16. 实体书店不断萎缩 路在何方?
  17. 多多客api_拼多多推广接口多多客API的简单使用
  18. android中如何打开指定卡上的数据连接开关,Android 代码控制手机数据网络的开关(5.0以上)...
  19. 【论文阅读】DouZero: Mastering DouDizhu with Self-Play Deep Reinforcement Learning
  20. Linux课程笔记 硬盘介绍及硬盘分区

热门文章

  1. php如何让B链接在当前页面打,javascript - 如何实现点击链接 A 弹出窗口 X,点击链接 B 继续在弹出窗口 X (刷新)打开?...
  2. python统计各分数段人数并可根据选择绘制不同的图形_python习题整理
  3. odis工程师一键导入导出匹配数据信息功能_机械重复做了这么久,才发现竟然可以批量用户导入导出...
  4. .net前台ajax,asp.net利用Ajax和Jquery在前台向后台传参数并返回值
  5. java 关闭阻塞线程池_如果优雅地关闭ExecutorService提供的java线程池
  6. php cms 选择哪个好?
  7. 青少年python编程竞赛加分_《笨方法学Python》加分题17
  8. lua怎么嵌入php,linux下安装php的lua扩展
  9. 大一计算机文化基础上机,计算机文化基础上机试题
  10. php与tp5,PHP开发(33)-ThinkPHP5.0(5)命名空间与TP5-PhpStorm