有关 VKontakte 架构和工作的常见问题解答

VKontakte 创建的历史可以在维基百科上找到;这是 Pavel 自己讲述的。 看来大家都已经认识她了。 关于 HighLoad++ Pavel 网站的内部、架构和结构 早在2010年就告诉过我。 从那时起,许多服务器已经泄漏,因此我们将更新信息:我们将解剖它,取出内部,称重,并从技术角度审视VK设备。

有关 VKontakte 架构和工作的常见问题解答

阿列克谢·阿库洛维奇 (阿特卡图斯)VKontakte 团队的后端开发人员。 本报告的文字记录是对有关平台、基础设施、服务器以及它们之间交互的常见问题的集体回答,但不是关于开发的,即 关于铁。 另外,关于数据库和 VK 所拥有的内容,关于收集日志和监控整个项目。 细节下切。



四年多来,我一直在处理与后端相关的各种任务。

  • 上传、存储、处理、分发媒体:视频、直播、音频、照片、文档。
  • 基础设施、平台、开发者监控、日志、区域缓存、CDN、专有 RPC 协议。
  • 与外部服务集成:推送通知、外部链接解析、RSS feed。
  • 帮助同事解决各种问题,而这些问题的答案需要深入研究未知的代码。

在此期间,我参与了该网站的许多组件。 我想分享这个经验。

通用架构

像往常一样,一切都从接受请求的服务器或服务器组开始。

前台服务器

前端服务器通过 HTTPS、RTMP 和 WSS 接受请求。

HTTPS - 这些是对网站的主网络版本和移动网络版本的请求:vk.com 和 m.vk.com,以及我们 API 的其他官方和非官方客户端:移动客户端、即时通讯工具。 我们有接待处 RTMP- 具有独立前端服务器的直播流量和 WSS- 流 API 的连接。

对于服务器上的 HTTPS 和 WSS 来说,这是值得的 nginx的。 对于 RTMP 广播,我们最近改用了我们自己的解决方案 基夫,但这超出了报告的范围。 为了容错,这些服务器通告公共 IP 地址并分组运行,这样如果其中一台服务器出现问题,用户请求就不会丢失。 对于 HTTPS 和 WSS,这些相同的服务器会对流量进行加密,以便自行承担部分 CPU 负载。

我们不会进一步讨论 WSS 和 RTMP,而只讨论标准的 HTTPS 请求,这些请求通常与 Web 项目相关。

后端

前端后面通常有后端服务器。 它们处理前端服务器从客户端收到的请求。

kPHP 服务器,HTTP 守护进程正在其上运行,因为 HTTPS 已解密。 kPHP 是一个运行在 预叉型号:启动一个主进程,一堆子进程,将侦听套接字传递给它们,然后它们处理它们的请求。 在这种情况下,进程不会在用户的每个请求之间重新启动,而只是将其状态重置为原始零值状态 - 一个又一个请求,而不是重新启动。

负载分配

我们所有的后端都不是一个可以处理任何请求的巨大机器池。 我们他们 分成不同的组:一般、移动、api、视频、staging...单独一组机器上的问题不会影响所有其他机器。 如果视频出现问题,听音乐的用户甚至不会知道问题所在。 将请求发送到哪个后端由前端的nginx根据配置决定。

指标收集和重新平衡

为了了解每组中需要有多少辆车,我们 不依赖QPS。 后端不同,它们有不同的请求,每个请求计算QPS的复杂度不同。 这就是为什么我们 我们将服务器负载的概念作为一个整体进行操作 - CPU 和性能.

我们有数千台这样的服务器。 每个物理服务器运行一个kPHP组来回收所有核心(因为kPHP是单线程的)。

内容服务器

CS或Content Server是一个存储。 CS 是一个服务器,用于存储文件并处理上传的文件以及主 Web 前端分配给它的各种后台同步任务。

我们有数以万计的物理服务器来存储文件。 用户喜欢上传文件,我们喜欢存储和共享它们。 其中一些服务器被特殊的 pu/pp 服务器关闭。

聚氨酯/聚丙烯

如果您在 VK 中打开网络选项卡,您会看到 pu/pp。

有关 VKontakte 架构和工作的常见问题解答

什么是pu/pp? 如果我们关闭一台又一台服务器,则有两种选项可以将文件上传和下载到关闭的服务器: 通过 http://cs100500.userapi.com/path или 通过中间服务器 - http://pu.vk.com/c100500/path.

Pu是照片上传的历史名称,pp是照片代理。 即一台服务器用于上传照片,另一台服务器用于上传。 现在不仅加载了照片,而且还保留了名称。

这些服务器 终止 HTTPS 会话从存储中删除处理器负载。 此外,由于用户文件是在这些服务器上处理的,因此这些计算机上存储的敏感信息越少越好。 例如,HTTPS 加密密钥。

由于这些机器被我们的其他机器关闭,我们可以不给它们“白色”外部IP,并且 给“灰色”。 通过这种方式,我们节省了 IP 池并保证保护机器免受外部访问 - 根本没有 IP 可以进入其中。

共享 IP 的弹性。 在容错方面,该方案的工作原理是相同的——几台物理服务器有一个公共的物理IP,它们前面的硬件选择将请求发送到哪里。 稍后我会讨论其他选项。

争议点在于,在本案中 客户端保持较少的连接。 如果多台机器有相同的 IP - 具有相同的主机:pu.vk.com 或 pp.vk.com,则客户端浏览器对一台主机的同时请求数量有限制。 但在 HTTP/2 无处不在的时代,我相信这不再那么重要了。

该方案的明显缺点是它必须 泵送所有流量,通过另一台服务器进入存储。 由于我们通过机器泵送流量,因此我们还无法使用相同的方案泵送大量流量,例如视频。 我们直接传输它 - 专门用于视频的单独存储的单独直接连接。 我们通过代理传输较轻的内容。

不久前我们得到了 proxy 的改进版本。 现在我将告诉您它们与普通的有何不同以及为什么这是必要的。

周日

2017年XNUMX月,此前收购了Sun的Oracle, Sun解雇了大量员工。 可以说,此时该公司已不复存在。 在为新系统选择名称时,我们的管理员决定向这家公司致敬,并将新系统命名为Sun。 在我们内部,我们简称她为“太阳”。

有关 VKontakte 架构和工作的常见问题解答

pp 有一些问题。 每组一个 IP - 缓存无效。 多台物理服务器共享一个公共IP地址,无法控制请求将发送到哪台服务器。 因此,如果不同的用户访问同一个文件,那么如果这些服务器上有缓存,则该文件最终会出现在每个服务器的缓存中。 这是一个非常低效的方案,但却无能为力。

最后 - 我们无法分割内容,因为我们无法为该组选择特定的服务器 - 他们有一个共同的 IP。 另外,由于一些内部原因,我们有 不可能在地区安装此类服务器。 他们只站在圣彼得堡。

对于太阳队,我们改变了选拔制度。 现在我们有 选播路由:动态路由、任播、自检守护进程。 每台服务器都有自己的独立 IP,但有一个公共子网。 一切都以这样的方式配置:如果一台服务器发生故障,流量会自动分布到同一组的其他服务器上。 现在可以选择特定的服务器, 无冗余缓存,并且可靠性没有受到影响。

重量支撑。 现在我们可以根据需要安装不同功率的机器,而且,一旦出现临时问题,可以改变正在工作的“太阳”的重量,以减轻它们的负载,让它们“休息”并重新开始工作。

按内容 id 分片。 关于分片的一个有趣的事情是:我们通常对内容进行分片,以便不同的用户通过同一个“sun”访问同一个文件,这样他们就有一个共同的缓存。

我们最近推出了“Clover”应用程序。 这是直播中的在线问答,主持人提出问题,用户实时回答,选择选项。 该应用程序有一个聊天功能,用户可以在其中聊天。 可以同时连接广播 超过100万人。 他们都会编写发送给所有参与者的消息,并且头像会随消息一起出现。 如果十万人来追求一个“太阳”中的一个化身,那么它有时可以滚到云端。

为了承受对同一文件的突发请求,我们针对某种类型的内容启用了一种愚蠢的方案,将文件分布在该地区所有可用的“太阳”上。

阳光从里面来

nginx 上的反向代理,缓存在 RAM 或快速 Optane/NVMe 磁盘上。 例子: http://sun4-2.userapi.com/c100500/path — 指向“太阳”的链接,该链接位于第四个区域,第二个服务器组。 它关闭物理上位于服务器 100500 上的路径文件。

缓存

我们在我们的架构方案中添加了一个节点——缓存环境。

有关 VKontakte 架构和工作的常见问题解答

下面是布局图 区域缓存,大约有20个。 这些是缓存和“太阳”所在的地方,它们可以通过自身缓存流量。

有关 VKontakte 架构和工作的常见问题解答

这是多媒体内容的缓存;这里不存储任何用户数据——仅存储音乐、视频、照片。

为了确定用户所在的区域,我们 我们收集各地区公布的 BGP 网络前缀。 在回退的情况下,如果我们无法通过前缀找到 IP,我们还必须解析 geoip 数据库。 我们通过用户的IP来确定区域。 在代码中,我们可以查看用户的一个或多个区域 - 这些点是他在地理位置上最接近的点。

它是如何工作的呢?

我们按地区统计文件的受欢迎程度。 有多个用户所在的区域缓存和一个文件标识符 - 我们采用这一对并在每次下载时增加评级。

与此同时,恶魔——区域中的服务——时不时地来到 API 中并说:“我是某某缓存,给我一份我所在区域中尚未出现的最流行文件的列表。 ” API 提供一堆按评级排序的文件,守护进程下载它们,将它们带到区域并从那里传送文件。 这是 pu/pp 和 Sun 与缓存之间的根本区别:它们立即通过自身提供文件,即使该文件不在缓存中,并且缓存首先将文件下载到自身,然后开始将其返回。

在这种情况下我们得到 内容更贴近用户 并分散网络负载。 例如,仅在莫斯科缓存中,我们在高峰时段分发的数据量就超过 1 Tbit/s。

但也有问题—— 缓存服务器不是橡胶。 对于超级流行的内容,有时没有足够的网络来容纳单独的服务器。 我们的缓存服务器是 40-50 Gbit/s,但有些内容完全堵塞了这样的通道。 我们正在努力实现该地区流行文件的多个副本的存储。 我希望我们能在今年年底前实施它。

我们研究了总体架构。

  • 接受请求的前端服务器。
  • 处理请求的后端。
  • 由两种类型的代理关闭的存储。
  • 区域缓存。

这张图中缺少什么? 当然是我们存储数据的数据库。

数据库或引擎

我们称它们不是数据库,而是引擎——引擎,因为我们实际上没有普遍接受的意义上的数据库。

有关 VKontakte 架构和工作的常见问题解答

这是必要的措施。。 发生这种情况是因为在 2008-2009 年 VK 流行度爆发式增长时,该项目完全运行在 MySQL 和 Memcache 上,出现了问题。 MySQL 喜欢崩溃和损坏文件,之后就无法恢复,而 Memcache 的性能逐渐下降,必须重新启动。

事实证明,这个日益流行的项目有持久存储,会损坏数据,还有缓存,会减慢速度。 在这种情况下,很难开发一个不断增长的项目。 我们决定尝试重写该项目在我们自己的自行车上关注的关键问题。

解决成功了。 这样做是有机会的,也是极其必要的,因为当时不存在其他扩展方式。 那时还没有一堆数据库,NoSQL 还不存在,只有 MySQL、Memcache、PostrgreSQL——仅此而已。

通用操作。 开发由我们的 C 开发团队领导,一切都以一致的方式完成。 无论使用何种引擎,它们都具有大致相同的写入磁盘的文件格式、相同的启动参数、以相同的方式处理信号,并且在边缘情况和问题的情况下表现大致相同。 随着引擎的增长,管理员操作系统变得方便——没有需要维护的动物园,每一个新的第三方数据库都需要重新学习如何操作,这使得快速、便捷地操作系统成为可能。方便地增加他们的数量。

发动机类型

该团队编写了相当多的引擎。 这里只是其中的一些:朋友、提示、图像、ipdb、信件、列表、日志、memcached、meowdb、新闻、nostradamus、照片、播放列表、pmemcached、沙箱、搜索、存储、喜欢、任务……

对于每个需要特定数据结构或处理非典型请求的任务,C 团队都会编写一个新引擎。 为什么不。

我们有一个单独的引擎 memcached,它与普通的类似,但有很多好东西,并且不会减慢速度。 不是 ClickHouse,但它也可以工作。 单独提供 内存缓存 - 持久化内存缓存,它还可以将数据存储在磁盘上,而且不适合存储在RAM中,以免重新启动时丢失数据。 对于各个任务有不同的引擎:队列、列表、集合——我们的项目需要的一切。

集群

从代码的角度来看,无需将引擎或数据库视为进程、实体或实例。 该代码专门适用于集群和引擎组 - 每个簇一种类型。 假设有一个 memcached 集群 - 它只是一组机器。

代码根本不需要知道服务器的物理位置、大小或数量。 他使用特定的标识符进入集群。

为此,您需要在代码和引擎之间添加一个实体 - 代理.

RPC代理

代理人 连接巴士,几乎整个网站都在其上运行。 同时我们有 没有服务发现 — 相反,该代理有一个配置,它知道所有集群和该集群的所有分片的位置。 这就是管理员所做的。

程序员根本不关心花费多少、在哪里以及花费什么——他们只关心集群。 这让我们有很多。 当接收到请求时,代理会重定向请求,知道在哪里 - 它自己决定这一点。

有关 VKontakte 架构和工作的常见问题解答

在这种情况下,代理是防止服务故障的一个保护点。 如果某个引擎变慢或崩溃,则代理会理解这一点并向客户端做出相应的响应。 这允许您删除超时 - 代码不会等待引擎响应,而是了解它不工作并且需要以某种不同的方式表现。 必须为数据库并不总是有效的事实准备代码。

具体实现

有时我们仍然非常希望有某种非标准的解决方案作为引擎。 同时,决定不使用专门为我们的引擎创建的现成的 rpc-proxy,而是为该任务创建一个单独的代理。

对于 MySQL(我们仍然到处都有),我们使用 db-proxy,对于 ClickHouse - 小猫屋.

它的工作原理一般是这样的。 有一个特定的服务器,它运行 kPHP、Go、Python - 一般来说,任何可以使用我们的 RPC 协议的代码。 该代码在 RPC 代理上本地运行 - 代码所在的每个服务器都运行其自己的本地代理。 根据请求,代理知道要去哪里。

有关 VKontakte 架构和工作的常见问题解答

如果一个引擎想要到另一个引擎,即使它是邻居,也会通过代理,因为邻居可能在另一个数据中心。 引擎不应依赖于了解除自身以外的任何物体的位置——这是我们的标准解决方案。 但当然也有例外:)

所有引擎均按照 TL 方案运行的示例。

memcache.not_found                                = memcache.Value;
memcache.strvalue	value:string flags:int = memcache.Value;
memcache.addOrIncr key:string flags:int delay:int value:long = memcache.Value;

tasks.task
    fields_mask:#
    flags:int
    tag:%(Vector int)
    data:string
    id:fields_mask.0?long
    retries:fields_mask.1?int
    scheduled_time:fields_mask.2?int
    deadline:fields_mask.3?int
    = tasks.Task;
 
tasks.addTask type_name:string queue_id:%(Vector int) task:%tasks.Task = Long;

这是一个二进制协议,最接近的类似物是 原型缓冲区。 该模式预先描述了可选字段、复杂类型 - 内置标量的扩展和查询。 一切都按照这个协议进行。

基于 TCP/UDP 的 TL 上的 RPC...UDP?

我们有一个 RPC 协议,用于执行在 TL 方案之上运行的引擎请求。 这一切都通过 TCP/UDP 连接进行。 TCP很好理解,但是为什么我们经常需要UDP呢?

UDP 有帮助 避免服务器之间大量连接的问题。 如果每台服务器都有一个 RPC 代理,并且一般情况下它可以到任何引擎,那么每台服务器就有数以万计的 TCP 连接。 有负载,但是没用。 在UDP 的情况下,这个问题不存在。

无冗余 TCP 握手。 这是一个典型的问题:当启动新引擎或新服务器时,会立即建立许多 TCP 连接。 对于小型轻量级请求,例如 UDP 有效负载,代码和引擎之间的所有通信都是 两个UDP数据包: 一只朝一个方向飞,第二只朝另一个方向飞。 一次往返 - 代码在没有握手的情况下收到了引擎的响应。

是的,一切正常 丢包率非常低。 该协议支持重传和超时,但是如果我们丢失很多,我们将得到几乎 TCP,这并不是有利的。 我们不会跨洋推动 UDP。

我们有几千台这样的服务器,方案是一样的:每台物理服务器上安装一组引擎。 它们大多是单线程的,以便尽可能快地运行而不会阻塞,并作为单线程解决方案进行分片。 同时,我们没有什么比这些引擎更可靠的了,并且非常关注持久数据存储。

持久数据存储

引擎写入二进制日志。 binlog 是一个文件,在其末尾添加了状态或数据更改的事件。 在不同的解决方案中它的调用方式不同:二进制日志, WAL, AOF,但原理是一样的。

为了防止引擎在重启时多年重新读取整个binlog,引擎会写入 快照 - 当前状态。 如果有必要,他们会先读取它,然后再从 binlog 中读取完毕。 所有二进制日志都根据 TL 方案以相同的二进制格式编写,以便管理员可以使用他们的工具平等地管理它们。 不需要快照。 有一个通用标头,指示谁的快照是 int、引擎的魔力以及哪个主体对任何人都不重要。 这是记录快照的引擎的问题。

我将快速描述操作原理。 有一台运行引擎的服务器。 他打开一个新的空二进制日志进行写入,并写入一个更改事件。

有关 VKontakte 架构和工作的常见问题解答

在某个时刻,他要么决定自己拍摄快照,要么收到信号。 服务器创建一个新文件,将其整个状态写入其中,将当前的 binlog 大小(偏移量)附加到文件末尾,然后继续进一步写入。 不会创建新的 binlog。

有关 VKontakte 架构和工作的常见问题解答

在某个时刻,当引擎重新启动时,磁盘上将会同时存在 binlog 和快照。 引擎读取整个快照并在某个点提升其状态。

有关 VKontakte 架构和工作的常见问题解答

读取创建快照时的位置以及 binlog 的大小。

有关 VKontakte 架构和工作的常见问题解答

读取 binlog 的末尾以获取当前状态并继续写入更多事件。 这是一个简单的方案;我们所有的引擎都按照它工作。

数据复制

因此,我们的数据复制 基于语句的 — 我们在 binlog 中写入的不是任何页面更改,而是 变更请求。 与网络上的非常相似,仅略有修改。

相同的方案不仅用于复制,还用于 创建备份。 我们有一个引擎——一个写入binlog的写入主机。 在管理员设置的任何其他地方,都会复制此二进制日志,就是这样 - 我们有一个备份。

有关 VKontakte 架构和工作的常见问题解答

如果需要 阅读副本为了减少CPU的读取负载,只需启动读取引擎,读取binlog的末尾并在本地执行这些命令。

这里的滞后非常小,并且可以找出副本落后主服务器多少。

RPC代理中的数据分片

分片是如何工作的? 代理如何了解要发送到哪个集群分片? 代码并没有说:“发送 15 个分片!” - 不,这是由代理完成的。

最简单的方案是firstint — 请求中的第一个数字。

get(photo100_500) => 100 % N.

这是一个简单的内存缓存文本协议的示例,但是,当然,查询可以是复杂的和结构化的。 该示例采用查询中的第一个数字以及除以簇大小后的余数。

当我们想要拥有单个实体的数据局部性时,这非常有用。 假设 100 是一个用户或组 ID,我们希望一个实体的所有数据都位于一个分片上以进行复杂查询。

如果我们不关心请求如何在集群中传播,还有另一种选择 - 散列整个分片.

hash(photo100_500) => 3539886280 % N

我们还获得哈希值、除法的余数和分片编号。

只有当我们准备好这样一个事实时,这两个选项才有效:当我们增加集群的大小时,我们会将其拆分或增加数倍。 例如,我们有 16 个分片,我们还不够,我们想要更多 - 我们可以安全地获得 32 个分片而无需停机。 如果我们不想增加倍数,就会出现停机,因为我们无法在没有损失的情况下准确地分割所有内容。 这些选项很有用,但并不总是有用。

如果我们需要添加或删除任意数量的服务器,我们使用 像 Ketama 一样在环上进行一致的哈希处理。 但与此同时,我们完全失去了数据的局部性;我们必须将请求合并到集群,以便每个部分返回自己的小响应,然后将响应合并到代理。

有非常具体的要求。 看起来像这样:RPC代理接收请求,确定去哪个集群并确定分片。 然后,要么有写入主节点,要么,如果集群有副本支持,则它会按需发送到副本。 代理完成这一切。

有关 VKontakte 架构和工作的常见问题解答

日志

我们以多种方式写入日志。 最明显和简单的一个是 将日志写入内存缓存.

ring-buffer: prefix.idx = line

有一个关键前缀 - 日志的名称,一行,还有该日志的大小 - 行数。 我们从 0 到行数减 1 之间取一个随机数。memcache 中的键是与该随机数连接的前缀。 我们将日志行和当前时间保存到该值中。

当需要读取日志时,我们执行 多获取 所有key,按时间排序,从而实时获取生产日志。 当您需要实时调试生产中的某些内容时,可以使用该方案,而不会破坏任何内容,也不会停止或允许到其他机器的流量,但该日志不会持续很长时间。

为了可靠地存储日志,我们有一个引擎 日志引擎。 这正是它被创建并在大量集群中广泛使用的原因。 据我所知,最大的集群可存储 600 TB 的打包日志。

发动机很旧,有些集群已经有6-7年的历史了。 它有一些问题我们正在努力解决,例如我们开始积极使用ClickHouse来存储日志。

ClickHouse中收集日志

该图显示了我们如何走进我们的引擎。

有关 VKontakte 架构和工作的常见问题解答

有一些代码通过 RPC 在本地发送到 RPC 代理,并且它了解去往引擎的位置。 如果我们想在ClickHouse中写入日志,我们需要改变这个方案中的两部分:

  • 用ClickHouse替换一些引擎;
  • 将无法访问 ClickHouse 的 RPC 代理替换为可以通过 RPC 访问的解决方案。

该引擎很简单 - 我们将其替换为 ClickHouse 的服务器或服务器集群。

为了去 ClickHouse,我们做了 小猫屋。 如果我们直接从KittenHouse转到ClickHouse,它就应付不了了。 即使没有请求,它也是由大量机器的 HTTP 连接累加起来的。 为了使该方案发挥作用,请在具有 ClickHouse 的服务器上 引发本地反向代理,其编写方式使其能够承受所需的连接量。 它还可以相对可靠地在自身内部缓冲数据。

有关 VKontakte 架构和工作的常见问题解答

有时我们不想在非标准解决方案中实现RPC方案,例如在nginx中。 因此,KittenHouse具有通过UDP接收日志的能力。

有关 VKontakte 架构和工作的常见问题解答

如果日志的发送者和接收者在同一台机器上工作,则在本地主机内丢失 UDP 数据包的可能性非常低。 作为在第三方解决方案中实现RPC的需要和可靠性之间的折衷,我们简单地使用UDP发送。 稍后我们将回到这个方案。

监控

我们有两种类型的日志:管理员在其服务器上收集的日志和开发人员通过代码编写的日志。 它们对应于两种类型的指标: 系统和产品.

系统指标

它适用于我们所有的服务器 网络数据,它收集统计数据并将其发送到 石墨碳。 因此,ClickHouse 被用作存储系统,而不是 Whisper 等。 如果需要,可以直接从ClickHouse读取,或者使用 格拉法纳 用于指标、图表和报告。 作为开发人员,我们有足够的权限访问 Netdata 和 Grafana。

产品指标

为了方便,我们写了很多东西。 例如,有一组普通函数允许您将 Counts、UniqueCounts 值写入统计数据,然后将其发送到更远的地方。

statlogsCountEvent   ( ‘stat_name’,            $key1, $key2, …)
statlogsUniqueCount ( ‘stat_name’, $uid,    $key1, $key2, …)
statlogsValuetEvent  ( ‘stat_name’, $value, $key1, $key2, …)

$stats = statlogsStatData($params)

随后,我们可以使用排序和分组过滤器,并从统计中执行我们想要的所有操作 - 构建图表、配置看门狗。

我们写得很 许多指标 每天的事件数量为 600 亿到 1 万亿个。 然而,我们想保留它们 至少几年了解指标的趋势。 把它们放在一起是一个我们尚未解决的大问题。 我会告诉你过去几年它是如何运作的。

我们有编写这些指标的函数 到本地内存缓存以减少条目数量。 曾在短时间内在本地推出 统计守护进程 收集所有记录。 接下来,恶魔将指标合并到两层服务器中 日志收集器,它汇总了我们一堆机器的统计数据,这样它们后面的层就不会消失。

有关 VKontakte 架构和工作的常见问题解答

如果有必要,我们可以直接写入日志收集器。

有关 VKontakte 架构和工作的常见问题解答

但是绕过 stas-daemonm 直接从代码写入收集器是一种可扩展性很差的解决方案,因为它增加了收集器的负载。 仅当由于某种原因我们无法在计算机上启动 memcache stats-daemon,或者它崩溃了而我们直接使用时,该解决方案才适用。

接下来,日志收集器将统计数据合并到 喵数据库 - 这是我们的数据库,它也可以存储指标。

有关 VKontakte 架构和工作的常见问题解答

然后我们可以从代码中进行二进制“near-SQL”选择。

有关 VKontakte 架构和工作的常见问题解答

实验

2018 年夏天,我们举办了一次内部黑客马拉松,想到了尝试用可以在 ClickHouse 中存储指标的东西来替换图中的红色部分。 我们在 ClickHouse 上有日志 - 为什么不尝试一下呢?

有关 VKontakte 架构和工作的常见问题解答

我们有一个通过 KittenHouse 写入日志的方案。

有关 VKontakte 架构和工作的常见问题解答

我们决定 在图表中添加另一个“*House”,它将按照我们的代码通过 UDP 写入的格式准确接收指标。 然后这个 *House 将它们变成插入物,比如 KittenHouse 可以理解的原木。 他可以完美地将这些日志传递给ClickHouse,ClickHouse应该能够读取它们。

有关 VKontakte 架构和工作的常见问题解答

带有memcache、stats-daemon 和logs-collectors 数据库的方案被替换为这个方案。

有关 VKontakte 架构和工作的常见问题解答

带有memcache、stats-daemon 和logs-collectors 数据库的方案被替换为这个方案。

  • 这里有一个来自代码的调度,它是在 StatsHouse 本地编写的。
  • StatsHouse 将已转换为 SQL 插入的 UDP 指标批量写入 KittenHouse。
  • KittenHouse 将它们发送到 ClickHouse。
  • 如果我们想读取它们,那么我们可以绕过 StatsHouse 来读取它们 - 使用常规 SQL 直接从 ClickHouse 读取它们。

还是 实验,但我们喜欢它的结果。 如果我们解决了该方案的问题,那么也许我们会完全转向它。 就我个人而言,我希望如此。

方案 不节省铁。 需要更少的服务器,不需要本地统计守护进程和日志收集器,但 ClickHouse 需要比当前方案更大的服务器。 需要更少的服务器,但它们必须更昂贵、更强大.

部署

首先,我们来看一下PHP的部署。 我们正在发展于 混帐: 使用 GitLab и 团队城市 用于部署。 开发分支合并到主分支,从主分支合并到测试分支,然后从测试分支合并到生产分支。

在部署之前,将获取当前生产分支和前一个生产分支,并在其中考虑差异文件 - 更改:创建、删除、更改。 此更改记录在特殊的 Copyfast 引擎的 binlog 中,该引擎可以快速将更改复制到我们的整个服务器群。 这里用的不是直接复制,而是 八卦复制,当一台服务器将更改发送到其最近的邻居时,将更改发送到其邻居时,依此类推。 这使您可以在数十秒内更新整个队列的代码。 当更改到达本地副本时,它将这些补丁应用到其本地副本 本地文件系统。 回滚也是按照同样的方案进行。

我们也大量部署了 kPHP,并且它也有自己的开发 混帐 根据上图。 从此 HTTP 服务器二进制文件,那么我们就无法生成 diff - 发布的二进制文件有数百 MB。 因此,这里还有另一个选择——版本被写入 二进制日志快速复制。 随着每次构建,它都会增加,并且在回滚期间它也会增加。 版本 复制到服务器。 本地 copyfast 看到新版本已进入 binlog,并通过相同的八卦复制,他们为自己获取最新版本的二进制文件,而不会让我们的主服务器感到疲倦,而是小心地在网络上分散负载。 接下来是什么 优雅地重新启动 对于新版本。

对于我们的引擎来说,本质上也是二进制的,该方案非常相似:

  • git master 分支;
  • 二进制输入 deb文件。;
  • 该版本被写入binlog copyfast;
  • 复制到服务器;
  • 服务器取出一个新的.dep;
  • dpkg-i;
  • 优雅地重新启动到新版本。

不同之处在于我们的二进制文件打包在档案中 deb文件。,当抽出它们时 dpkg-i 被放置在系统上。 为什么 kPHP 部署为二进制文件,而引擎部署为 dpkg? 事情就是这样发生的。 它有效 - 不要碰它。

相关链接:

阿列克谢·阿库洛维奇 (Alexey Akulovich) 是作为程序委员会的一员,帮助帮助 PHP 俄罗斯 17月XNUMX日将成为近期PHP开发者最盛大的盛会。 看看我们有一台多么酷的电脑,什么 讲者 (其中两个正在开发 PHP 核心!) - 如果您编写 PHP,这似乎是您不能错过的东西。

来源: habr.com

添加评论