StackOverflow 这么大,它的架构是怎么样的
原文地
【伯乐在线补充】:Nick Craver 是 StackOverflow 的软件工程师 & 网站可靠性工程师。
这是「解密 Stack Overflow 架构」系列的第一篇,本系列会有非常多的内容。欢迎阅读并保持关注。
为了便于理解本文涉及到的东西到底都干些了什么,让我先从 Stack Overflow 每天平均统计量的变化开始。下面的数据数来自 2013 年 11 月 12 日的统计:
负载均衡器接受了148,084,833次HTTP请求
其中36,095,312次是加载页面
833,992,982,627 bytes (776 GB) 的HTTP流量用于发送
总共接收了286,574,644,032 bytes (267 GB) 数据
总共发送了1,125,992,557,312 bytes (1,048 GB) 数据
334,572,103次SQL查询(仅包含来自于HTTP请求的)
412,865,051次Redis请求
3,603,418次标签引擎请求
耗时558,224,585 ms (155 hours) 在SQL查询上
耗时99,346,916 ms (27 hours) 在Redis请求上
耗时132,384,059 ms (36 hours) 在标签引擎请求上
耗时2,728,177,045 ms (757 hours) 在ASP.Net程序处理上
节选自@蒋生武 翻译的《StackOverflow 这么大,究竟用在什么硬件设备?》
下方数据是到 2016 年 2 月 9 日时,统计数字发生的变化,你可以比较一下:
负载均衡器收到的 HTTP 请求:209,420,973 (+61,336,090)
66,294,789 (+30,199,477) 其中的页面加载数量
发送的 HTTP 数据量:1,240,266,346,053 (+406,273,363,426) bytes (1.24 TB)
总共接收的数据量:569,449,470,023 (+282,874,825,991) bytes (569 GB)
总共发送的数据量:3,084,303,599,266 (+1,958,311,041,954) bytes (3.08 TB)
SQL 查询(仅来自于 HTTP 请求):504,816,843 (+170,244,740)
Redis 缓存命中数:5,831,683,114 (+5,418,818,063)
Elastic 搜索数量:17,158,874 (not tracked in 2013)
标签引擎(Tag Engine)请求数:3,661,134 (+57,716)
运行 SQL 查询累计消耗的时间:607,073,066 (+48,848,481) ms (168 hours)
Redis 缓存命中消耗的时间:10,396,073 (-88,950,843) ms (2.8 hours)
标签引擎请求消耗的时间:147,018,571 (+14,634,512) ms (40.8 hours)
在 ASP.Net 处理中消耗的时间:1,609,944,301 (-1,118,232,744) ms (447 hours)
22.71 (-5.29) ms 49,180,275 个问题页面平均的渲染时间(其中 19.12 ms 消耗在 ASP.Net 中)
11.80 (-53.2) ms 6,370,076 个首页的平均渲染时间(其中 8.81 ms 消耗在 ASP.Net 中)
你可能会好奇为什么 ASP.Net 在每天多处理6100万次请求的情况下,处理时间却减少了757个小时(相比于在2013 年)。这主要归功于在 2015 年初的时候我们对服务器进行的升级,以及大量的应用内的性能优化工作。别忘了:性能依然是个卖点。如果你对具体的硬件配置细节更加好奇的话,别担心,我很快就会在下一篇文章中以附录的形式给出运行这些网站所用的服务器的具体硬件配置细节(到时候我会更新这个链接)。
所以这两年来到底发生了哪些变化?不太多,只是替换掉一些服务器和网络设备而已。下面是今天运行网站所用的服务器概览(注意和 2013 年相比有什么变化)
4 台 Microsoft SQL Server 服务器(其中 2 台使用了新的硬件)
11 台 IIS Web 服务器(新的硬件)
2 台 Redis 服务器(新的硬件)
3 台标签引擎服务器(其中 2 台使用了新的硬件)
3 台 Elasticsearch 服务器(同上)
4 台 HAProxy 负载均衡服务器(添加了 2 台,用于支持 CloudFlare)
2 台网络设备(Nexus 5596 核心 + 2232TM Fabric Extender,所有设备都升级到 10Gbps 带宽)
2 台 Fortinet 800C 防火墙(取代了 Cisco 5525-X ASAs)
2 台 Cisco ASR-1001 路由器(取代了 Cisco 3945 路由器)
2 台 Cisco ASR-1001-x 路由器(新的!)
为了支撑Stack Overflow的运行,我们需要些什么?从 2013 年至今并没有太多的变化,不过因为优化以及上面提到的新硬件设备,我们现在只需要一台 web 服务器了。我们已经无意中测试过这种情况了,成功了好几次。请注意:我只是说这是可行的,我可没说这是个好主意。不过每次发生这种情况的时候都还挺有意思的。
现在我们已经对服务器缩放的想法有了一些基线数字,来看看我们是如何制作这些炫酷网页的。很少有系统是完全独立存在的(当然我们的也不例外),如果没有一个全局眼光能把这些部分集成在一起的话,架构规划的意义就要大打折扣了。我们的目标,就是把握全局。后续会有很多文章深入到每个特定的领域中。本文只是一个关于重点硬件的逻辑结构概要,下一篇文章会包含这些硬件的具体细节。
如果你们想看看今天这些硬件到底长什么样子的话,这里有几张在 2015 年 2 月升级服务器的时候,我拍摄的机柜A的照片(机柜B和它是完全一样的):
如果你想看更多这种东西的话,这里是那一周升级过程中完整的 256 张照片的相册(没错,这个数字就是故意的哈哈)。现在,让我们来深入架构布局。以下是现有的主要系统的逻辑架构概要:
(点击看大图)
基本原则
以下是一些通行的原则,不需要再依次介绍它们了:
所有东西都有冗余备份。
所有的服务器和网络设备之间都至少有两个 10Gbps 带宽的连接。
所有服务器都有两路电源,通过两个 UPS 单元组、背后的两台发电机、两台电网电压前馈来提供电力。
所有服务器都有一个冗余备份分别位于机柜A和机柜B中。
所有服务器和服务都有双份的冗余备份,放在另外一个数据中心(位于科罗拉多),虽然这里我主要是在介绍纽约的情况。
所有东西都有冗余备份。
互联网
首先你得找到我们的网站,这是 DNS 的事儿。查找网站速度得快,所以我们现在把这事儿包给了 CloudFlare,因为他们有遍布在全球各个角落的 DNS 服务器。我们通过 API 来更新 DNS 记录,他们负责“管理”DNS。不过以我们的小人之心,因为还是有根深蒂固的信任问题,所以我们依然还是拥有自己的 DNS 服务器。当世界末日的时候——可能因为 GPL、Punyon(译注:Stack Overflow 团队的一员)或者缓存问题——而人们依然想要通过编程来转移注意力的话,我们就会切换到自己的 DNS 服务器。
你的浏览器找到了我们的藏身之所之后,来自我们四家网络服务供应商(纽约的 Level 3、Zayo、Cogent 和 Lightower)的 HTTP 流量就会进入我们四台先进的路由器之一。我们使用边界网关协议(BGP,非常标准的协议)来对等处理来自网络供应商的流量,以此来对其进行控制,并提供最高效的通路来访问我们的服务。这些 ASR-1001 和 ASR-1001-X 路由器被分为两组,每组应都使用双活的模式(active/active)来处理来自两家网络供应商的流量——在这里是有冗余备份的。虽然都是拥有同样的物理 10Gbps 的带宽,来自外部的流量还是和外部 VLAN 的流量独立开来,分别接入负载均衡。在流量通过路由器之后,你就会来到负载均衡器了。
我想现在可能是时候提到我们在两个数据中心之间拥有 10Gbps 带宽的 MPLS,虽然这其实和网站服务没什么直接关系。我们使用这种技术来进行数据的异地复制和快速恢复,来应对某些突发情况。“不过 Nick,这里面可没有冗余!”好吧,从技术角度上你说的没错(正面意义上的没错),在这个层面上它确实是单点故障。不过等等!通过网络供应商,我们还额外拥有两个 OSPF 故障转移路由(MPLS是第一选择,出于成本考虑这个是第二和第三选择)。之前提到的每组设备都会相应地接入科罗拉多的数据中心,在故障转移的情况下来对网络流量进行负载均衡。当然我们本可以让这两组设备互相之间都连接在一起,这样就有四组通路了,不过管它呢,让我们继续。
负载均衡(HAProxy)
负载均衡通过 HAProxy 1.5.15 实现,运行在 CentOS 7 上(我们最喜欢的 Linux 版本)。并在HAProxy上加入TLS(SSL)安全传输协议。我们还在密切关注 HAProxy 1.7,它马上就会提供对 HTTP/2 协议的支持。
和其他拥有双路 10Gbps LACP 网络连接的服务器不同,每台负载均衡都拥有两套 10Gbps 的连接:其中一套对应外部网络,另一套对应 DMZ。这些服务器拥有 64GB 或者更多的内存,来更有效地处理 SSL 协议层。当我们可以在内存中缓存和重用更多的 TLS 会话的时候,在连接到同一个客户端时就会少消耗一些计算资源。这意味着我们能够以更快、更便宜的方式来还原会话。内存是如此廉价,所以这是个很容易做出的抉择。
负载均衡本身搭建起来很容易。我们在多个不同的 IP(主要出于证书和 DNS 管理的考虑)上监听不同的网站,然后将流量路由到不同的后端(主要基于host header)。我们在这里做的唯一值得一提的事情就是限速和抓取部分 header 信息(来自 web 层)记录到 HAProxy 的系统日志消息中,通过这种方式我们可以记录每个请求的性能指标。我们会在后面详细提到这一点。
Web 层(IIS 8.5、ASP.Net MVC 5.2.3 和 .Net 4.6.1)
负载均衡将流量分配到 9 台我们所谓的主 web 服务器(01-09)中和 2 台开发 web 服务器(10-11,我们的测试环境)。主服务器运行着 Stack Overflow、Careers 以及所有的 Stack Exchange 网站,除此之外的 meta.stackoverflow.com 和 meta.stackexchange.com 在是运行在另外两台服务器上的。主要的 Q&A 应用本身就是多租户(multi-tenant)形式的,也就是说一个单独应用处理了所有 Q&A 网站的请求。换句话说,我们可以在一台服务器的一个应用程序池上,运行整个的 Q&A 应用。其它的应用比如 Careers、API v2、Mobile API 等等,都是独立的。下面是主服务器和开发服务器的 IIS 中看到的内容:
下面是在 Opserver(我们内部的监控仪表板)中看到的 Stack Overflow 的 web 层分布情况:
(点击查看大图)
还有下面这个是这些 web 服务器的资源消耗情况(译注:不是说好的 11 台么):
(点击查看大图)
我会在后续的文章中详细提到为什么我们过度提供了这么多资源,重点在于:滚动构建(rolling build)、留有余地、冗余。
服务层(IIS、ASP.Net MVC 5.2.3、.NET 4.6.1 和 HTTP.SYS)
紧挨着web层的是服务层。它们同样运行在 Windows 2012R2 的 IIS 8.5 之上。这一层运行一些内部服务,对生产环境的 web 层和其他内部系统提供支持。两个主要的服务包括:“Stack Server”,其中运行着标签引擎,是基于 http.sys的(背后并非是 IIS);Providence API(基于IIS)。一个有趣的事实:我不得不对着两个进程进行相关性设置,让它们连接到不同的 socket 上,因为 Stack Server 在以两分钟为间隔刷新问题列表的时候,会非常频繁的访问 L2 和 L3 级缓存。
运行这些服务的机器对于标签引擎和后端的 API 有着举足轻重的意义,因此它们必须是冗余的,不过并不需要 9 倍的冗余。举例来说,我们会每隔 n 分钟(目前是两分钟)就从数据库中加载所有文章及其标签,这个操作消耗并不低。我们可不想在 web 层把这个加载操作重复 9 次,3 次对我们来说就足够安全了。我们同样会对这些服务器采用不同的硬件配置,以便针对标签引擎和 elastic 索引作业(同样运行在这一层中)的计算和数据加载的特征进行更好的优化。“标签引擎”本身就是一个相对复杂的话题,会在专门的文章中进行介绍。基本的原理是:当你访问地址 /questions/tagged/java 的时候,你会访问标签引擎来获取与之匹配的问题。该引擎处理了除 /search 之外的所有标签匹配工作,所以包括新的导航在内的所有地方都是通过这个服务来获取数据的。
缓存 & 发布/订阅(Redis)
我们在一些地方使用了 Redis,它拥有坚如磐石般地稳定性。尽管每个月的操作有 1600 亿次之多,每个实例的 CPU 也不会超过 2%,通常会更低:
(点击查看大图)
我们借助 Redis 用于 L1/L2 级别的缓存系统。“L1”级是 HTTP 缓存,在 web 服务器或者任何类似的应用程序中起作用。“L2”级则是当上一级缓存失效之后,通过 Redis 获取数据。我们的数据是以 Protobuf 格式储存的,通过 Marc Gravell 编写的 protobuf-dot-net 实现。对于 Redis 客户端,我们使用了 StackExchange.Redis 库,这是一个内部开发的开源库。如果一台 web 服务器在 L1 和 L2 缓存中都没有命中,它就会从其数据源中获取数据(数据库查询、API 调用等等),然后将结果保存到本地缓存和 Redis 中。下一台服务器在获取同样数据的时候,可能会在 L1 缓存中缺失,但是它会在 L2/Redis 中获取到数据,省去了数据库查询或者 API 调用的操作。
我们同样运行着很多 Q&A 站点,每个站点都有其自己的 L1/L2 缓存:在 L1 缓存中使用 key 作为前缀,在 L2/Redis 缓存中使用数据库 ID。我们会在未来的文章中深入探讨这个话题。
除了运行着所有站点实例的两台主要的 Redis 服务器(一主一从)之外,我们还利用另外两台专用的从服务器搭建了一个用于机器学习的的实例(主要出于内存考虑)。这组服务器用来提供首页上的问题推荐、做出更优的工作职位匹配等服务。这个平台称为 Providence,Kevin Montrose 曾撰文描述过它。
主要的 Redis 服务器拥有 256GB 内存(大约使用了 90GB),Providence 服务器拥有 384GB 内存(大约使用了 125GB)。
Redis 并非只用来做缓存,它同样拥有一套发布和订阅机制,一台服务器可以发布一条消息,其他的订阅服务器可以收到该消息(包括 Redis 从服务器上的下游客户端)。我们利用这个机制来清除其他服务上的 L1 缓存,用来保持 web 服务器上的缓存一致性。不过它还有另外一个重要的用途:websocket。
Websockets(NetGain)
我们使用 websocket 向用户推送实时的更新内容,比如顶部栏中的通知、投票数、新导航数、新的答案和评论等等。
socket 服务器本身在 web 层上运行,使用原生的 socket。这是一个基于我们的开源库实现的非常小型的应用程序:StackExchange.NetGain。在高峰时刻,我们大约有 50 万个并发的 websocket 连接,这可是一大堆浏览器。一个有趣的事实:其中一些浏览器已经打开超过 18 个月了,得找人去看看那些开发者是不是还活着。下面这张图是本周 websocket 并发量的模式:
(点击查看大图)
为什么用 websocket?在我们这个规模下,它比轮询要有效率得多。通过这种方式,我们可以简单地使用更少资源来推送更多数据,而且对用户而言实时性也更高。不过这种方式也并非没有问题:临时端口、负载均衡上的文件句柄耗尽,都是非常有趣的问题,我们稍后会提到它们。
搜索(Elasticsearch)
剧透:这里没多少让人兴奋的东西。web层使用了Elasticsearch 1.4 ,并实现了超轻量级、高性能的 StackExchange.Elastic 客户端。和大多数东西不同的是,我们并没有计划把这部分内容开源,简单来说,是因为它只暴露了非常少量的我们需要使用的 API 的子集。我确信把它公开出来是得不偿失的,只会让开发者感到困惑。我们在这些地方用到了 elastic:/search、计算相关问题、提问时给出相关建议。
每个 Elastic 集群(每个数据中心各有一个)包含 3 个节点,每个站点都拥有各自的索引。Careers 站点还有一些额外的索引。在 elastic 圈子里,我们的配置中稍微不那么标准的地方是,我们 3 台服务器的集群比通常的配置要更强大一些:每台服务器都使用了 SSD 存储、192GB 内存、双路 10Gbps 带宽的网络。
在 Stack Server 的同一个应用程序域(没错,我们在这个地方被 .Net Core 折腾惨了)里面还宿主着标签引擎,它同样使用了 Elasticsearch 进行连续索引。这里我们用了些小花招,比如使用 SQL Server(数据来源)中的 ROWVERSION 和 Elastic 中的“最后位置”文档进行比较。因为从表观上看它是顺序的,这样如果内容在最后一次访问后被修改的话,我们就很容易对其进行抓取和索引了。
我们使用 Elasticsearch 代替如 SQL 全文检索这类技术的主要原因,就是它的可扩展性和性价比。SQL 的 CPU 相对而言非常昂贵,而 Elastic 则便宜得多,并且最近有了非常多的新特性。为什么不用 Solr?我们需要在整个网络中进行搜索(同时有多个索引),在我们进行决策的时候 Solr 还不支持这种场景。我们还没有使用 2.x 版本的原因,是因为 2.x 版本中类型(types)有了很大的变化,这意味着想要升级的话我们得重新索引所有内容。我只是没有足够的时间来制定需求变更和迁移的计划。
数据库(SQL Server)
我们使用 SQL Server 作为单一的数据源(single source of truth)。Elastic 和 Redis 中的所有数据都来自 SQL Server。我们有两个 SQL Server 集群,并配置了 AlwaysOn 可用性组。每个集群都在纽约有一台主服务器(承担了几乎全部负载)和一台副本服务器,此外还有一个在科罗拉多(我们的灾备数据中心)的副本服务器。所有的复制操作都是异步的。
第一个集群是一组 Dell R720xd 服务器,每台拥有 384GB 内存,4TB 空间的 PCIe SSD,和两个 12 核 CPU。它包含了 Stack Overflow、Sites(这是个坏名字,稍后我会解释它)、PRIZM 以及 Mobile 的数据库。
第二个集群是一组 Dell R730xd 服务器,每台拥有 768GB 内存,6TB 空间的 PCIe SSD,和两个 8 核 CPU。这个集群包含了所有其它数据库,包括 Careers、Open ID、Chat、异常日志,以及其他的 Q&A 网站(比如 Super User、Server Fault 等)。
在数据库层上,我们希望让 CPU 利用率保持在一个非常低的级别,不过实际上在一些计划缓存问题(我们正在排查)发生的时候,CPU 占用率会稍高一些。目前,NY-SQL02 和 04 是主服务器,01 和 03 是副本服务器,我们今天因为 SSD 升级刚刚重启过它们。以下是它们在过去 24 小时内的表现:
(点击查看大图)
我们对 SQL 的使用非常简单。简单就意味着快速。虽然有些查询语句会很变态,我们对 SQL 本身的交互还是通过相当原生的方式进行的。我们有一些遗留的 Linq2Sql,不过所有新开发的内容都使用了 Dapper,这是我们开源的微型 ORM 框架,使用了 POCO。让我换一种方式来解释一下:Stack Overflow 的数据库中只有一个存储过程,而且我打算把这个最后残留的存储过程也干掉,换成代码。
库
好吧让我们换个思路,这里是更直接能帮到你的东西。在前面我已经提到过一些了,不过我会给出一个列表,其中包含了很多由我们维护的、大家都在使用的开源 .Net 类库。我们把它们开源,因为其中并不涉及到核心的商业价值,但是可以帮助世界上的开发者们。我希望你们如今能用到它们:
StackOverflow 这么大,它的架构是怎么样的的更多相关文章
- hadoop大数据技术架构详解
大数据的时代已经来了,信息的爆炸式增长使得越来越多的行业面临这大量数据需要存储和分析的挑战.Hadoop作为一个开源的分布式并行处理平台,以其高拓展.高效率.高可靠等优点越来越受到欢迎.这同时也带动了 ...
- 【大数据】大数据处理-Lambda架构-Kappa架构
大数据处理-Lambda架构-Kappa架构 elasticsearch-head Elasticsearch-sql client NLPchina/elasticsearch-sql: Use S ...
- Google大数据技术架构探秘
原文地址:https://blog.csdn.net/bingdata123/article/details/79927507 Google是大数据时代的奠基者,其大数据技术架构一直是互联网公司争相学 ...
- 量化派基于Hadoop、Spark、Storm的大数据风控架构--转
原文地址:http://www.csdn.net/article/2015-10-06/2825849 量化派是一家金融大数据公司,为金融机构提供数据服务和技术支持,也通过旗下产品“信用钱包”帮助个人 ...
- 还在羡慕BAT等公司的大流量的架构吗,commonrpc 是一个以netty 传输协议框架为基础(支持FTP)
还在羡慕BAT等公司的大流量的架构吗?让你的java系统引用解耦,互相独立,commonrpc 就可以办到.commonrpc 是一个以netty 传输协议框架为基础, 自定义 spring shce ...
- [原创]lvs+ospf+nginx实现高可用大流量web架构
lvs+ospf+nginx实现高可用大流量web架构配置总概述 架构图: 配置如下: .quagga之zebra配置: # cat /etc/quagga/zebra.conf ! ! Zebra ...
- 知名大厂如何搭建大数据平台&架构
今天我们来看一下淘宝.美团和滴滴的大数据平台,一方面进一步学习大厂大数据平台的架构,另一方面也学习大厂的工程师如何画架构图.通过大厂的这些架构图,你就会发现,不但这些知名大厂的大数据平台设计方案大同小 ...
- DW(一):大数据DW架构参考
DW一直以来是企业信息与决策支持系统的核心组件,随着各类日志.社交.传感等非结构化数据的加入,企业内部数据按指数级增长,传统DW已经达到一个关键临界点——需要大量的资源投入到硬件.优化.支持和维护中, ...
- 大数据Lambda架构
1 Lambda架构介绍 Lambda架构划分为三层.各自是批处理层,服务层,和加速层.终于实现的效果,能够使用以下的表达式来说明. query = function(alldata) 1.1 批处理 ...
随机推荐
- 搭建Git服务器(转载)
转自:http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000/00137583770360 ...
- tinymce 富文本编辑器 编写资料
tinymce官方文档: 粘贴图片插件 博客搬运地址 使用Blob获取图片并二进制显示实例页面 tinymce自动调整插件 是时候掌握一个富文本编辑器了——TinyMCE(1) XMLHttpRequ ...
- hdu1814Peaceful Commission(2-SAT)
Peaceful Commission Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Oth ...
- [Swift通天遁地]七、数据与安全-(15)使用单元测试进行代码的性能分析
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...
- SQL Server语言 函数以及SQL编程
1.数学函数:操作一个数据,返回一个结果 --去上限: ceiling ☆select ceiling(price) from car --去下限:floor ☆select floor(price) ...
- jQuery制作顶部与左侧锚点板块定位功能带动画跳转特效
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- Zynq7000系列之芯片系统结构概述
相比较经典的FPGA,Zynq7000系列最大的特点是将处理系统PS和可编程资源PL分离开来,固化了PS系统的存在,实现了真正意义上的SOC(System On Chip). 1. Zynq7000 ...
- Lazarus Coolbar and AnchroDocking
在lazarus1.6里加载了AnchroDocking后,Coolbar突然不见了,找了好久没找到,原来在这里! 在AnchroDocking中可能是为了界面的最大化,默认是开始Toolbar 而关 ...
- 查找java文件
想要在eclipse里找一个类文件,可以用快捷键CTRL + SHIFT + R,但是有些文件是jar包里的类文件,可以用下图方法,创建一个变量,然后按住CTRL键,点击类名称 这样就找到jar文件了
- php第十八节课
PDO 对不同的数据库连接使用 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "ht ...