原文: https://nickcraver.com/blog/2016/02/17/stack-overflow-the-architecture-2016-edition/
作者:Nick Craver
译者:Negan.L
Stack Overflow: 2016年度的架构
这是讲述Stack Overflow的架构的一系列文章的第一篇#1。欢迎。
上一篇 (#0): Stack Overflow:一个技术解构下一篇 (#2): Stack Overflow:硬件-2016版本
为了知道这些东西“做”了什么,让我们从Stack Overflow的日均更新开始。你可以从先前2013年11月的数据开始比较,这是从2013年11月12日至2016年2月9日不同的每日数据:
- 209,420,973 (+61,336,090) 向负载均衡的HTTP请求
- 66,294,789 (+30,199,477) 页面加载次数
- 1,240,266,346,053 (+406,273,363,426) 字节 (1.24 TB) HTTP流量发送量
- 569,449,470,023 (+282,874,825,991) 字节 (569 GB) 总共收到的
- 3,084,303,599,266 (+1,958,311,041,954) 字节 (3.08 TB) 总共发送的
- 504,816,843 (+170,244,740) SQL查询 (仅从 HTTP 请求)
- 5,831,683,114 (+5,418,818,063) Redis点击量
- 17,158,874 (2013未追踪) 弹性搜索
- 3,661,134 (+57,716) 标签引擎请求
- 607,073,066 (+48,848,481) 毫秒 (168 小时) 运行SQL查询消耗时长
- 10,396,073 (-88,950,843) 毫秒 (2.8 小时) 在Redis点击消耗时长
- 147,018,571 (+14,634,512) 毫秒 (40.8 小时) 在标签引擎上花费的请求时长
- 1,609,944,301 (-1,118,232,744) 毫秒 (447 小时) 在处理ASP.net上花费的时长
- 22.71 (-5.29) 毫秒 平均消耗 (19.12 毫秒 在ASP.Net中) 49,180,275 个问题页面中交付
- 11.80 (-53.2) 毫秒 平均消耗 (8.81 毫秒 在 ASP.Net上) 6,370,076 个主页中交付
你可能想要知道相较于2013年( 757 小时) ASP.Net急剧降低处理时间的原因尽管有每天61 百万更多的请求。那是由于在2015年初硬件的升级以及应用他们自己内在的大量性能提升。 请不要忘了:性能仍然还是一个特性。 如果你好奇关于更多硬件细节于我所提供的-不要害怕。下篇文章将是一个附录关于硬件的详细规格,用来运行这些网站的所有服务。
所以,这两年都更新了些什么?代替了一些服务和网络设备之外,没有太多变化。这里列举了运行这些网站需要的*硬件列表(注意与2013年的不同):
- 4台微软SQL服务器 (其中有两台是新硬件)
- 11台 IIS网络服务器 (新硬件)
- 2台Redis服务器(新硬件)
- 3台标签引擎服务器(其中两台是新硬件)
- 3台弹性搜索服务器(与之前一样)
- 4 台HAProxy 负载均衡器(添加了两台支持CloudFlare)
- 2套网络(每个包括了一台Nexus 5596核心+2232TM Fabric扩展器,每个地方都升级至10Gbps)
- 2台Fortinet 800C防火墙(替代Cisco 5525-X ASAs)
- 2 台Cisco ASR-1001路由器(替代Cisco 3945路由器)
- 2 台Cisco ASR-1001-x路由器 (新加入的!)
我们需要什么设备去运行Stack Overflow?从2013年以来并没有改变太多,但是由于上文提到的优化和新硬件,我们降低到只需要1台网页服务器。我们无意中如此测试,成功了几次。要清楚的是:我说它能运行,并不是说这是一个好方法。无论何时,这很有趣。
现在,我们有一些基线数据的范围。让我们一起来看那些精致的网页页面。几乎没有系统以完全隔离的形式存在(我们的系统也不例外),构建决策没有适应整体的大局观经常没有意义。这就是目标,掌控大局。许多后续文章将在专业领域做深度介绍。这将是一个关于只是突出硬件的逻辑概述;下一篇文章将讲述硬件细节。
为了让你看到的这几天的硬件的样子,这里有一些我在2015年2月升级期间从机架A拍摄的图片(与机架B类似)
…如果你对这些事情想深入了解,这里有那一周的完整的256张图片相册 (没错这个数字是故意的).现在,让我们在这个布局继续深入下去,这是一个使用中的主要系统逻辑概述。
基本规则
这里有一些全球适用的规则所以我不再重复每一步:
- 所有东西都有冗余
- 所有服务器和网络设备必须至少需要2x 10Gbps的连接
- 所有的服务器需要2台从2个UPS单元的通过能源供应在2 发台电机和2台实用供应的支持下的电源供给
- 所有服务器需要一个在机架A和B之间的冗余搭配
- 所有服务器和服务需要通过另一个数据中心(Colorado)保留双倍冗余,尽管我主要讨论纽约的相关
- 所有的东西都有冗余
互联网
首先你需要找到我们的网站-通过DNS。这需要快速定位,所以我们 使用了CloudFlare(目前)因为他们有几乎全球各地的DNS服务器。我们通过一个API更新了我们的DNS记录,他们做DNS的“托管”。但是我们一直有根深蒂固的信任危机, 我们也仍然拥有我们自己的DNS服务器. 当灾难性事故出现了 (也许是 GPL, Punyon 或者缓存造成的) ,人们仍然需要编程转移他们的注意力, 我们将会将这些服务器打开。
在你找到我们的秘密藏身之处之后,来自 4个P中的一个ISP (在纽约的Level 3, Zayo, Cogent, 和Lightower)和 通过四台周边路由器中的一台通过HTTP访问.为了最有效的连接到我们,我们通过控制流量和提供一些道路使用BGP(非常标准) 对等我们的ISP . 这些 ASR-1001和ASR-1001-X路由是两套的,每一个通过active/active fashion服务2个ISP,所以这里我们有冗余的地方。尽管他们都有同样的10Gbps的物理带宽,外部流量与连接外部虚拟局域网是分离独立的,进入负载均衡。
这里需要提到的是,在我们的两台数据中心之中有一台10Gbps的MPLS,但是它不是直接参与服务网站。这用来数据备份和突然需要快速恢复的情况. “但是Nick, 那可不是冗余!” 技术上说,这是对的 (非常正确), 这是一个单点故障. 但是等一下! 我们维护了另外两台故障转移可用的OSPF路由 (成本MPLS第一1, 这两台是第二和第三) 经由我们的ISP.之前提到的路由组与科罗拉多的相关路由组相连,和它们流量进行负载均衡在故障转移.我们可以在这两台路由组之间搭建互联,这样就有四条通路, 不过,不管怎么样,继续往下说.
负载均衡(HAProxy)
负载均衡使用了在CentOS 7上运行的HAProxy 1.5.15,我们最喜欢的Linux. TLS (SSL)的流量也被导入至HAProxy.我们将热切找寻在HAProxy 1.7 上有HTTP/2的支持.
不像其他服务有双路10Gbps LACP的网络连接,每一个负载均衡有两对10Gps的带宽:一个用来服务外部网络一个为了服务DMZ.这些盒子运行64GB或更多的内存来更有效地处理SSL当我们在内存中可以重复利用更多TLS会话缓存时,在相同客户端的后续连接时会更少重复计算.这意味着我们可以更快更便宜地重启会话.由于内存相当便宜,这是一个很容易作出的选择.
负载均衡的配置相当容易.我们监听不同IP的网站(大多数为了证书和DNS管理而考虑) 基于主机名路由到不同后端 .唯一需要注意的是速率限制和一些主机名的捕获(从我们的web层发送)导入到HAProxy syslog信息,我们可以为每个单独请求记录性能指标,稍后将会讨论.
网络层 (IIS 8.5, ASP.Net MVC 5.2.3, 和 .Net 4.6.1)
负载均衡将流量分发到 9台服务器我们称作 “主服务器” (01-09) 和 2 台“开发/meta” (10-11,临时环境) 网络服务器. 主服务器运行Stack Overflow,Careers和所有Stack Exchange网站.除了meta.*.com和meta.stackexchange.com这些至少需要两台服务器.主要Q&A应用自身是多租户的.这意味着一个单独的应用服务所有的Q&A网站.换句话说:我们可以用一个应用池在一个服务器中运行整个Q&A网络 .其他像 Careers, API v2, Mobile API等应用是分开的.下面列出了IIS主要的开发层面:
这就是Stack Overflow在网络层的分布在Opserver中 (我们内部的监控面板):
…下面是这些web服务器资源利用情况:
我还将在未来几个章节进一步讲述, 但是重点是:滚动生成, 净空, 和冗余.
服务层(IIS, ASP.Net MVC 5.2.3, .Net 4.6.1,和 HTTP.SYS)
在这些web服务器之后是类似于服务层.它也在Windows 2012R2上的IIS 8.5上运行. 这个层在内部运行服务来支持网络层的产品和其他内部系统. 两个重要服务是:“栈服务” ,运行标签引擎 以及基于http.sys
(不在 IIS之后) 和Providence API (基于IIS). 有趣的事实: 我不得不在这两个进程分别设置关联性连接不同的sockets因为栈服务只是每两分钟刷新问题列表时频繁访问L2和L3缓存.
这些服务器对于标签引擎和后端API做大量工,我们需要冗余, 但是不是 9重冗余. 举个例子, 从数据库每n分钟加载所有文章和他们的标签(当前是2分钟) 代价不便宜. 我们不想在网络层上加载9次; 3次足够并且给了我们足够的安全.我们也在硬件上也不同配置这些盒子,更好的不同计算标签引擎的加载特征的最佳化以及灵活的索引工作 (在这一层运行). 标签引擎是一个复杂的主题将有一篇专门文章讲述. 大概的就是:当你访问 /questions/tagged/java
, 你将使用标签引擎来看到匹配的问题. 除了 /search,它处理了所有的标签匹配
, 所以,新导航等用这些服务获取数据.
缓存 & 发布/订阅 (Redis)
我们使用Redis完成一些事情,它坚若磐石,尽管每月ops达到160百万,所有事例CPU占用低于2% .通常更低:
我们在Redis中有一个L1/L2的缓存系统. “L1” 是在网络服务或无论应用是否正在使用的HTTP缓存. “L2”是从Redis中取回数据.我们的数据经由protobuf-dot-net 用Protobuf格式保存,由Marc Gravell 编写.对客户端来说.我们使用StackExchange.Redis-内部开发并且开源.当一个网页服务错过L1和L2上的缓存,它就从源头获取数据 (数据库查询,API调用等)将这些结果放入本地缓存和Redis上.下一个服务想要数据可能错过L1,但是将在L2或者配置说明上找到,节省了数据库查询和API的调用.
我们也运行了很多Q&A站点,所以每一个站点都有他自己的L1/L2缓存: 通过L1的key前缀和L2/Redis通过数据库ID搜索.我们将在未来的文章深入探索.
2个主要的Redis服务器 (主/伺)同时运行所有的网站事例, 我们也有一台用2台以及更多的专用服务器进行机器学习事例 (由于内存).这用来在主页上推荐问题,更好的匹配等.这是一个平台叫做Providence, 点击这里查看Kevin Montrose描述的相关文章.
主Redis服务器有256GB的内存(大概需要使用90GB)并且 Providence服务器 有384GB 内存(大概使用 125GB).
配置服务不只是为了缓存,它也用来发布和订阅机制--一个服务可以发布一条信息所有其他订阅者收到—包括用配置服务伺服务器的下载流客户端.我们用这个机制来清理在其他服务器上的L1缓存在网络层上保持一致性,但是还有更大的用处:websockets.
Websockets (NetGain)
我们使用websockets来向用户实时推送更新比如顶部通知,投票计数,新导航计数,新回答和评论以及其他一些东西
Socket服务器自身使用原生sockets运行网络层.这是一个在我们的开源库上非常精简的应用:StackExchange.NetGain. 在峰值时,我们大约同时打开了500,000 websocket 连接.有许多浏览器.有趣的事实是:他们中的一些浏览器一直打开持续了18个月,我们不知道为什么.有些人应该核对那些开发者是否还活着,这就是这周的websocket 并发的模式:
为什么是websockets? 因为它们比我们的转态范围更高效.我们可以简单的利用更少的资源推送更多数,对用户也更加快捷.但是它们不是没有问题的- 临时端口以及在负载均衡的上有文件句柄耗尽等一些有趣的问题.我们将会讨论这些问题
搜索(检索)
剧透:这里没有太多令人兴奋的内容.网络层使用非常轻量级的高性能StackExchange.Elastic客户端,通过Elasticsearch 1.4进行出色的vanilla搜索 .不像大多数的事情,我们没有打算开源这个客户端,因为它只是暴露了我们使用的API的非常小的子集.我强烈相信发布这个利大于弊,只会让开发者困惑.我们使用检索来执行/search, 计算相关问题以及建议,当被用户问到时.
每一个 Elastic簇 (每个数据中心都有一个)有3个节点,每个网站有它自己的索引. Careers 还有几个额外的索引. 让我们的配置有点与elastic标准不同的是:我们的3个服务器簇的SSD存储,192GB的内存,双通道10Gbps的网络带宽比平均水平有点高
相同应用领域(我们对.Net Core皱起了眉头…)在栈服务器上托管着标签引擎也继续索引条目在Elasticsearch上.我们的确做了一些简单的技巧比如在SQL Server(数据源)上的 ROWVERSION 与Elastic“最后未知” 文档相比较.由于它的行为像一个序列,我们可以简单的抓取和索引任何条目,如果发生变更.
我们使用Elasticsearch 而不是SQL全文搜索的主要原因是可测量性和更好的性价比. SQL CPU 相当贵, Elastic的更便宜并且在这几天有更多功能.为什么不用 Solr?我们想要通过整个网络搜索(一次搜索许多索引),在决策的时候Solr还不支持.我们不用2.x的原因是主要”类型”的改变,意味着我们需要重新索引所有内容我没有足够的时间改变需求以及迁移计划
数据库 (SQL Server)
我们使用SQL Server作为我们独立的可靠数据来源.所有Elastic和Redis数据都来自SQL Server.我们运行2个SQL Server簇拥有AlwaysOn可用组.这些簇中每一个都有一个主服务器 (几乎加载所有的负载)和一个在New York的备份. 另外,他们还有1个备份在Colorado (我们的DR数据中心).所有的备份都是异步的.
第一个簇是 Dell R720xd服务器, 每一个都是384GB内存 ,4TB的PCIe SSD 空间, 和 2x 12 核心.托管着Stack Overflow, 网站 (坏名字,之后会解释), PRIZM,和 Mobile数据库.
第二个簇是Dell R730xd服务器,每一个都是768GB内存, 6TB PCIe SSD 空间,和2x 8核心.这个簇运行其他的所有东西.这张列表包括Careers, Open ID, Chat,我们的异常日志和每个其他的 Q&A 站点 (比如 Super User, Server Fault, 等).
在数据库层中的CPU利用我们希望保持低的占用,但是事实上有点高在由于一些我们正在处理的计划的缓存问题的情况.截至到现在, NY-SQL02 和04是主服务器, 01 和 03 是伺服务器,在今天因为一些SSD的更新我们刚刚重启下面是之前24小时的图示:
我们使用SQL非常简单.简单就快.进款一些查询很频繁,我们的SQL的内在互动还是很vanilla.我们有一些遗留的Linq2Sql,但是所有新的开发使用Dapper,我们的开源Micro-ORM使用POCOs让我用另一种方式解释: Stack Overflow在数据库中只有1个存储过程 我打算将最后的部分也换成代码.
库
好了,让我们改变装备可以更加直接帮助你.我在上面提到了一些,我将提供一张列表关于开源的.Net库我们所维护的.我们开源了它们因为他们没有核心商业价值但是可以帮助这个世界上的开发者,我希望你能找到一些有用的:
- Dapper (.Net Core) – 高性能 Micro-ORM 用于 ADO.Net
- StackExchange.Redis – 高性能Redis客户端
- MiniProfiler -轻量级分析工具 我们的每张页面都有运行(也支持Ruby, Go, 和Node)
- Exceptional – 错误日志 用于 SQL, JSON, MySQL等
- Jil – 高性能 JSON (de)序列化/反序列化工具
- Sigil - A .Net CIL 生成助手(适用于C# 不够快)
- NetGain -高性能 websocket 服务器
- Opserver - 监控面板,可以直接在大多数系统中执行轮询,并从Orion、Bosun或WMI中提取信息
- Bosun – 后台监控系统,用Go写的
下一篇是详细的运行我们的代码的现在的硬件配置列表,在那之后,我们按照列表继续,保持关注!