我们如何确保CityMobile的增长

图片

我叫Ivan,我是Citimobil服务器开发负责人。今天,我将讨论什么是服务器开发,我们遇到了什么问题以及我们打算如何开发。

开始成长


很少有人知道CityMobile已经存在了13年之久。曾经是一家仅在莫斯科工作的小公司。开发人员很少,他们非常了解系统的工作原理-因为他们自己创建了系统。出租车市场才刚刚开始发展,其负担便是一分钱。我们甚至没有提供容错和扩展的任务。

在2018年,Mail.Ru集团投资了CityMobile,我们开始迅速发展。没有时间重写平台,或者至少没有进行重大重构,有必要开发其功能以赶上我们的主要竞争对手并迅速雇用人员。当我加入公司时,只有20个开发人员参与了后端,几乎所有开发人员都处于试用状态。因此,我们决定“采摘低垂的果实”:进行简单的更改以产生巨大的效果。

那时,我们在PHP中有一个整体,在Go上有3个服务,还有一个MySQL正在访问的主要数据库(这些服务用作Redis和EasticSearch的存储库)。逐渐地,随着负载的增加,繁重的系统请求开始降低基础速度。

怎么办呢?

首先,我们采取了明显的步骤:将奴隶投入生产。但是,如果有很多沉重的要求,他会接受吗?同样明显的是,随着大量分析报告的请求,奴隶将开始落后。大量的奴隶积压可能会对整个CityMobile的性能产生不利影响。结果,我们为分析人员放置了另一个奴隶。它的滞后永远不会导致生产问题。但是,在我们看来,这还不够。我们在Clickhouse中从MySQL编写了一个表复制器。如今,分析使用的是专为OLAP设计的堆栈,可以独立运行。

以这种形式,系统工作了一段时间,然后开始出现对硬件要求更高的功能。每周都有越来越多的请求:没有一个新的记录,甚至没有一个星期过去。此外,我们在系统下放置了一颗定时炸弹。以前,我们只有一个故障点-MySQL主数据库,但增加了一个从数据库,就有两个这样的故障:主数据库和从数据库。这些机器中任何一个的故障都将导致系统完全故障。

为了防止这种情况,我们开始使用本地代理执行运行状况检查从属。这使我们可以使用许多从站而无需更改代码。我们引入了对每个从站的状态及其常规指标的定期自动检查:

  • 奴隶滞后
  • 端口可用性;
  • 锁数等

如果超过某个阈值,系统将从负载中移除从站。但同时,最多只能撤出一半的奴隶,因此,由于其余奴隶的负担增加,他们无法为自己安排停机时间。作为代理,我们使用了HAProxy,并立即在积压中包括了一个切换到ProxySQL的计划。这个选择有些奇怪,但是我们的管理员已经在使用HAProxy方面拥有丰富的经验,而且问题很严重,需要尽早解决。因此,我们为从站创建了一个故障安全系统,该系统可以轻松扩展。尽管她很简单,但她从未让我们失望。

进一步增长


随着业务的发展,我们在系统中发现了另一个瓶颈。随着外部条件的变化(例如,大范围地区开始降雨),出租车订单数量迅速增长。在这种情况下,驾驶员没有足够的时间做出足够迅速的反应,并且汽车短缺。分发订单时,它们在一个循环中给MySQL从站造成了负载。

我们找到了一个成功的解决方案-Tarantool。为此很难重写系统,因此我们以不同的方式解决了该问题:使用mysql-tarantool-replication工具将某些表从MySQL复制到Tarantool。在汽车短缺期间出现的所有阅读要求,我们开始在Tarantool广播,从那时起,我们就不再担心雷暴和飓风!而且,我们可以更轻松地解决问题,即可以轻松解决问题:我们立即安装了多个副本,这些副本可通过HAProxy访问运行状况检查。每个Tarantool实例由一个单独的复制器复制。令人高兴的是,我们还在此部分代码中解决了奴隶滞后的问题:从MySQL到Tarantool的复制比从MySQL到MySQL的复制要快得多。

但是,我们的主要基础仍然是一个失败点,并且无法扩展到记录操作上。我们开始以这种方式解决这个问题。

首先,那时我们已经开始积极创建新服务(例如,我的同事已经编写了有关反欺诈的服务)。此外,服务立即需要存储的可伸缩性。对于Redis,我们开始仅使用Redis集群,而对于Tarantool-Vshard。在使用MySQL的地方,我们开始将Vitess用于新逻辑。这样的数据库可立即分拆,因此记录几乎没有问题,如果它们突然出现,则可以通过添加服务器来轻松解决它们。现在,我们仅将Vitess用于非关键服务并研究陷阱,但是将来,它将在所有MySQL数据库中使用。

其次,由于很难为已经存在的逻辑实现Vitess,因此我们以一种更简单,虽然不太通用的方式进行工作:我们开始将主数据库逐表分布在不同的服务器上。我们非常幸运:事实证明,记录上的主要负载是由对主要功能并不重要的表创建的。而且,当我们制作此类表时,我们不会创建其他业务失败点。对我们来说,主要的敌人是代码中使用JOIN的表之间的强大连接性(每个JOIN都有50-60个表)。我们无情地削减了他们。

现在是时候回顾设计高负载系统的两个非常重要的模式了:

  • Graceful degradation. , - . , , , , .. , .
  • Circuit breaker. , . , , , . ? ( - graceful degradation). , FPM- , . - ( ) , . , - , ( ).

因此,我们至少开始扩展规模,但是仍然存在失败的地方。

然后,我们决定转向半同步复制(并成功实现了它)。它有什么特点?如果在正常的异步复制过程中,数据中心中的清洁器在服务器上倒了一桶水,那么最后的事务将没有时间复制到从属服务器,并且会丢失。我们必须确保在这种情况下,其中一个奴隶成为新主人之后,我们不会出现严重问题。结果,我们决定完全不丢失任何事务,为此,我们使用了半同步复制。现在,从服务器可以滞后,但是即使主数据库服务器被破坏,有关所有事务的信息也将存储在至少一个从服务器上。

这是迈向成功的第一步。第二步是使用Orchestrator实用程序。我们还不断监视系统中的所有MySQL。如果主库失败,则自动化将使主库成为最新的从库(考虑到半同步复制,它将包含所有事务),并将整个写入负载切换到该主库。因此,我们现在可以重温清洁女工和一桶水的故事。

下一步是什么?

当我来到CityMobile时,我们拥有三项服务和一个整体。如今,它们有20多个,而阻碍我们增长的主要因素是我们仍然拥有一个统一的基础。我们英勇奋战,并将其划分为不同的基地。

我们如何进一步发展?


扩展微服务集。这将解决我们今天面临的许多问题。例如,团队不再只有一个知道整个系统设备的人。而且由于快速增长,我们并不总是拥有最新的文档,并且很难维护它,因此对于初学者来说很难深入研究事务。而且,如果系统将包含大量服务,那么为每个服务编写文档将变得无比轻松。一次性学习的代码量大大减少了。

去吧我真的很喜欢这种语言,但是我始终认为将工作代码从一种语言重写为另一种语言是不切实际的。但是最近,经验表明,PHP库,即使是标准库和最受欢迎的库也不是最高质量的库。也就是说,我们修补了许多库。假设SRE团队修补了用于与RabbitMQ交互的标准库:事实证明,超时这样的基本功能不起作用。 SRE团队越深入,我了解这些问题,就越清楚地表明,很少有人考虑过PHP超时,很少有人关心测试库,很少有人考虑锁。为什么这成为我们的问题?因为Go解决方案更易于维护。

Go还有什么让我印象深刻?奇怪的是,在上面写非常简单。此外,Go使创建各种平台解决方案变得容易。该语言具有一组非常强大的标准工具。如果由于某种原因我们的后端突然开始减速,请转到特定的URL,然后您可以查看所有统计信息-内存分配图,以了解进程空闲的位置。而且在PHP中,识别性能问题更加困难,

此外,Go具有出色的linters程序,这些程序会自动为您查找最常见的错误。其中的大多数内容在“ Go的50种阴影”一文中进行了描述,而短绒棉可以完美地检测到它们。

继续分片基地。我们将在所有服务上切换至Vitess。

将PHP整体转换为redis-cluster。在我们的服务中,redis-cluster非常出色。不幸的是,用PHP实现它比较困难。巨石使用redis-cluster不支持的命令(这很好,这样的命令带来的问题多于收益)。

我们将调查RabbitMQ的问题。人们认为RabbitMQ不是最可靠的软件。我们将研究这个问题,找到并解决问题。也许我们会考虑改用Kafka或Tarantool。

All Articles