LinkedIn成立于 2003 年,其目标是连接到您的网络以获得更好的工作机会。第一周只有 2,700 名会员。时间快进了很多年,LinkedIn 的产品组合、会员基础和服务器负载都取得了巨大的增长。
如今,LinkedIn 在全球运营,拥有超过 3.5 亿会员。我们每天每秒都会提供数以万计的网页。我们已经进入了移动时代,移动流量占全球流量的 50% 以上。所有这些请求都从我们的后端系统获取数据,而后端系统每秒处理数百万个查询。
那么,我们是如何到达那里的呢?
多年以前
就像今天许多网站一样,LinkedIn 最初是作为一个单一的整体应用程序完成这一切的。这个应用程序被称为 Leo。它托管所有不同页面的 Web servlet、处理业务逻辑并连接到一些 LinkedIn 数据库。
成员图
作为社交网络要做的第一件事就是管理成员之间的连接。我们需要一个使用图形遍历查询连接数据并驻留在内存中的系统,以实现最高的效率和性能。由于这种不同的使用情况,很明显它需要独立于 Leo 进行扩展,因此我们的会员图表的一个名为 Cloud 的独立系统诞生了 - LinkedIn 的第一个服务。为了使该图服务与 Leo 分开,我们使用 Java RPC 进行通信。
大约在这个时候我们需要搜索功能。我们的会员图服务开始将数据输入到运行Lucene 的新搜索服务中。
副本读取数据库
随着网站的发展,Leo 也在不断发展,其角色和责任也不断增加,自然也增加了其复杂性。当多个 Leo 实例启动时,负载平衡很有帮助。但增加的负载给 LinkedIn 最关键的系统——其会员资料数据库带来了负担。
我们所做的一个简单的修复是经典的垂直扩展 - 投入更多的 CPU 和内存!虽然这赢得了一些时间,但我们需要进一步扩大规模。配置文件数据库同时处理读取和写入流量,因此为了扩展,引入了副本从属数据库。副本数据库是成员数据库的副本,使用最早版本的数据总线(现已开源)保持同步。它们被设置为处理所有读取流量,并构建逻辑来了解何时从副本读取相对于主主数据库是安全(一致)的。
随着网站的流量开始增加,我们的单一整体应用程序 Leo 经常在生产中出现故障,很难排除故障和恢复,也很难发布新代码。高可用性对于 LinkedIn 至关重要。很明显,我们需要“杀死 Leo”并将其分解为许多小型的功能性和无状态服务。
面向服务的架构
工程部门开始提取微服务来保存 API 和业务逻辑,例如我们的搜索、个人资料、通信和群组平台。后来,我们的表示层被提取用于招聘人员产品或公共档案等领域。对于新产品,全新服务是在 Leo 之外创建的。随着时间的推移,每个功能区域都出现了垂直堆栈。
我们构建了前端服务器来从不同域获取数据模型、处理表示逻辑并构建 HTML(通过 JSP)。我们构建了中间层服务来提供对数据模型的 API 访问,并构建后端数据服务来提供对其数据库的一致访问。到 2010 年,我们已经拥有 150 多个独立服务。如今,我们拥有超过 750 项服务。
由于无状态,可以通过启动任何服务的新实例并在它们之间使用硬件负载平衡器来实现扩展。我们积极开始对每个服务进行红线调整,以了解它可以承受多少负载,并构建了早期配置和性能监控功能。
缓存
LinkedIn 正在经历高速增长,需要进一步扩大规模。我们知道可以通过添加更多层缓存来完全减少负载。许多应用程序开始引入中间层缓存层,例如memcache或couchbase。我们还在数据层中添加了缓存,并在适当的时候开始使用带有预先计算结果的Voldemort。
随着时间的推移,我们实际上删除了许多中间层缓存。中间层缓存存储来自多个域的派生数据。虽然缓存一开始看起来是一种减少负载的简单方法,但失效和调用图的复杂性却变得失控。使缓存尽可能靠近数据存储可以保持较低的延迟,使我们能够水平扩展并减少认知负载。
Kafka
为了收集不断增长的数据量,LinkedIn 开发了许多用于流式传输和排队数据的自定义数据管道。例如,我们需要将数据流入数据仓库,我们需要将批量数据发送到我们的Hadoop 工作流程中进行分析,我们收集并聚合每个服务的日志,我们收集页面浏览量等跟踪事件,我们需要对 inMail 消息进行排队系统,每当有人更新个人资料时,我们都需要使我们的人员搜索系统保持最新状态。
随着网站的发展,更多的定制管道出现了。随着站点需要扩展,每个单独的管道也需要扩展。必须付出一些东西。结果是我们的分布式发布-订阅消息平台Kafka的开发。Kafka 成为一个通用管道,围绕提交日志的概念构建,并且在构建时考虑了速度和可扩展性。它使我们能够近乎实时地访问任何数据源,增强我们的 Hadoop 作业能力,使我们能够构建实时分析,极大地提高我们的站点监控和警报能力,并使我们能够可视化和跟踪我们的调用图。如今,Kafka每天处理超过5000 亿个事件。
反转
规模化可以从多个维度来衡量,包括组织。2011 年底,LinkedIn 启动了一项名为Inversion的内部计划。这一举措暂停了功能开发,使整个工程组织能够专注于改进工具和部署、基础设施和开发人员的生产力。它成功地实现了我们构建当今可扩展新产品所需的工程敏捷性。
近代
当我们从 Leo 转型为面向服务的架构时,我们提取的 API 假设是基于 Java 的 RPC,跨团队不一致,与表示层紧密耦合,而且情况只会变得更糟。为了解决这个问题,我们构建了一个名为Rest.li的新 API 模型。Rest.li 是我们向以数据模型为中心的架构迈进的一步,它确保了整个公司一致的无状态 Restful API 模型。
通过使用 HTTP 上的 JSON,我们的新 API 最终使非基于 Java 的客户端变得容易。今天的 LinkedIn 仍然主要是一家 Java 商店,但也有许多使用 Python、Ruby、Node.js 和 C++ 的客户,这些都是内部开发的以及我们收购的技术堆栈。远离 RPC 还使我们摆脱了与表示层的高耦合和许多向后兼容性问题。另外,通过将动态发现 (D2)与 Rest.li 结合使用,我们获得了每个服务 API 的基于自动化客户端的负载平衡、发现和可扩展性。
如今,LinkedIn 在我们的所有数据中心拥有超过 975 个 Rest.li 资源,每天有超过 1000 亿次 Rest.li 调用。
超级积木
面向服务的架构可以很好地解耦域并独立扩展服务。但也有缺点。我们的许多应用程序获取多种类型的不同数据,进而进行数百个下游调用。在考虑所有许多下游调用时,这通常称为“调用图”或“扇出”。例如,任何个人资料页面请求获取的不仅仅是个人资料数据,还包括照片、连接、群组、订阅信息、关注信息、长篇博客文章、图表中的连接度、推荐等。此调用图可能难以管理并且变得越来越不守规矩。
我们引入了超级块的概念 - 具有单一访问 API 的后端服务分组。这使我们能够让特定的团队优化该块,同时检查每个客户端的调用图。
多数据中心
作为一家会员数量快速增长的跨国公司,我们需要扩大规模,超越从一个数据中心提供流量服务的范围。我们几年前就开始努力解决这个问题,首先是通过两个数据中心(洛杉矶和芝加哥)提供公共资料。一旦经过验证,我们就开始增强我们的所有服务,以处理数据复制、来自不同来源的回调、单向数据复制事件以及将用户固定到地理位置接近的数据中心。
我们的许多数据库都在Espresso(一种新的内部多租户数据存储)上运行。Espresso 在构建时就考虑到了多数据中心。它提供主/主支持并处理许多困难的复制。
多个数据中心对于维持“站点正常运行”和高可用性非常重要。您不仅需要避免每个单独服务的任何单点故障,还需要避免整个站点的任何单点故障。如今,LinkedIn 拥有三个主要数据中心,并在全球各地设有其他PoP 。
我们还做了什么?
当然,我们的扩展故事从来没有这么简单。多年来,我们在所有工程和运营团队中做了无数的事情,其中包括一些更大的举措:
我们的许多最关键的系统都有自己丰富的历史和多年来解决规模问题的演变。这包括我们的会员图表服务(我们在 Leo 之外的第一项服务)、搜索(我们的第二项服务)、新闻源、通信平台和会员资料后端。
我们构建了能够实现长期增长的数据基础设施。这首先在 Databus 和 Kafka 中表现得很明显,然后在用于数据流的Samza 、用于存储解决方案的Espresso和 Voldemort、用于我们的分析系统的Pinot以及其他定制解决方案中得到延续。另外,我们的工具已经改进,开发人员可以自动配置此基础设施。
我们使用Hadoop和Voldemort 数据存储开发了一个大规模的离线工作流程,以预先计算数据见解,例如您可能认识的人、相似的个人资料、著名校友和个人资料浏览地图。
我们重新考虑了我们的前端方法,将客户端模板添加到组合中(个人资料页面、大学页面)。这使得应用程序的交互性更强,要求我们的服务器仅发送 JSON 或部分 JSON。另外,模板会缓存在 CDN 和浏览器中。我们还开始使用BigPipe和Play 框架,将我们的模型从线程 Web 服务器更改为非阻塞异步模型。
除了应用程序代码之外,我们还使用 Apache Traffic Server 和 HAProxy 引入了多层代理来处理负载平衡、数据中心固定、安全性、智能路由、服务器端渲染等。
最后,我们通过优化硬件、高级内存和系统调整以及利用更新的 Java 运行时,继续提高服务器的性能。
下一步是什么
LinkedIn 继续快速发展,我们仍有大量工作可以改进。我们正在解决很少有人能够解决的问题。
随手关注或者”在看“,诚挚感谢!