
在Mail.ru Group,我们有Tarantool-这是Lua上的这样的应用程序服务器,它也有一个数据库(反之亦然?)。 它既快速又很酷,但是一台服务器的功能仍然不是无限的。 垂直缩放也不是万能的,因此Tarantool具有用于水平缩放的工具-vshard模块
[1] 。 它允许您在多台服务器上分片数据,但是您必须对其进行修改以配置它并固定业务逻辑。
好消息:我们收集了这些锥体(例如
[2] ,
[3] ),并创建了另一个框架,该框架将大大简化此问题的解决方案。
Tarantool Cartridge是用于开发复杂的分布式系统的新框架。 它使您可以专注于编写业务逻辑,而不是解决基础结构问题。 切入点,我将告诉您该框架的组织方式以及如何使用它编写分布式服务。
到底是什么问题?
我们有狼蛛,有狼蛛-您还想要什么?
首先,重点是便利。 通过Lua表配置Vshard配置。 为了使包含多个Tarantool进程的分布式系统正常工作,每个地方的配置都必须相同。 没有人愿意手动执行此操作。 因此,使用了各种脚本Ansible部署系统。
Cartridge本身管理vshard配置;它基于其
自己的分布式配置进行此操作 。 本质上,这是一个简单的YAML文件,其副本存储在Tarantool的每个实例中。 简化之处在于,框架本身会监视其配置,因此各处都相同。
其次,重点再次是方便。 该配置与业务逻辑的开发无关,只会分散程序员的工作量。 当我们讨论项目的体系结构时,大多数时候我们在谈论单个组件及其交互。 现在考虑将集群部署到3个数据中心还为时过早。
我们一遍又一遍地解决了这些问题,在某个时候,我们设法开发出一种方法来简化应用程序的整个生命周期:创建,开发,测试,CI / CD,维护。
弹药筒介绍了每个Tarantool流程的角色概念。 角色是一个概念,使开发人员可以专注于编写代码。 项目中所有可用的角色都可以在Tarantool的一个实例上运行,这足以进行测试。
Tarantool弹药筒的主要特点:
- 自动集群编排;
- 通过新角色扩展应用程序功能;
- 应用程序开发和部署模板;
- 内置自动分片;
- 与Luatest测试框架集成;
- 使用WebUI和API进行集群管理;
- 包装和部署工具。
世界您好!
我渴望展示框架本身,因此让我们将有关体系结构的故事留待以后再讲,从一个简单的框架开始。 假设已经安装了Tarantool本身,剩下要做的就是
$ tarantoolctl rocks install cartridge-cli $ export PATH=$PWD/.rocks/bin/:$PATH
这两个命令将安装命令行实用程序,并允许您从模板创建第一个应用程序:
$ cartridge create --name myapp
这是我们得到的:
myapp/ ├── .git/ ├── .gitignore ├── app/roles/custom.lua ├── deps.sh ├── init.lua ├── myapp-scm-1.rockspec ├── test │ ├── helper │ │ ├── integration.lua │ │ └── unit.lua │ ├── helper.lua │ ├── integration/api_test.lua │ └── unit/sample_test.lua └── tmp/
这是一个带有完成的“ Hello,World!”的git存储库。 应用程序。 让我们立即尝试运行它,预安装依赖项(包括框架本身):
$ tarantoolctl rocks make $ ./init.lua --http-port 8080
因此,我们启动了未来分片应用程序的一个节点。 好奇的外行可以立即打开Web界面,使用鼠标从一个节点配置群集并享受结果,但是现在还太高兴了。 到目前为止,该应用程序尚不知道如何做任何有用的事情,因此,稍后我将告诉您有关部署的信息,现在该编写代码了。
应用开发
试想一下,我们将设计一个项目,该项目应该每天接收,保存数据并生成报告。

我们开始绘制图表,并在其上放置三个组件:网关,存储和调度程序。 我们正在进一步研究架构。 由于我们将vshard用作存储,因此在方案中添加了vshard-router和vshard-storage。 网关和调度程序都不会直接访问存储库,为此而创建了一个路由器。

由于组件看起来很抽象,因此该方案仍不能完全准确地反映我们将在项目中创建的内容。 我们还需要查看如何将其投影到真实的Tarantool上-我们将按过程对组件进行分组。

将vshard-router和网关保持在单独的实例上几乎没有意义。 如果这已经是路由器的职责,为什么我们需要再次遍历网络? 它们必须在同一进程中运行。 也就是说,在一个过程中,网关和vshard.router.cfg均被初始化,并使其在本地交互。
在设计阶段使用三个组件很方便,但是作为开发人员,在我编写代码时,我不想考虑启动三个Tarnatool实例。 我需要运行测试并验证是否正确编写了网关。 或者,也许我想向我的同事展示一个功能。 为什么要为三个副本部署而受苦? 这就是角色概念诞生的方式。 角色是常规的泥ach模块,其生命周期由墨盒管理。 在此示例中,共有四个-网关,路由器,存储,调度程序。 在另一个项目中,可能还有更多。 所有角色都可以在一个过程中启动,这就足够了。

当涉及到部署到阶段或投入运营时,我们将根据硬件功能将每个角色集分配给每个Tarantool流程:

拓扑管理
有关在何处启动角色的信息必须存储在某处。 而这个“某处”就是我上面提到的分布式配置。 其中最重要的是群集拓扑。 这是5个Tarantool进程的3个复制组:

我们不想丢失数据,因此我们会认真对待有关正在运行的进程的信息。 弹药筒通过两阶段提交监视配置。 一旦我们想要更新配置,它将首先检查所有实例的可用性以及它们是否准备接受新配置。 此后,第二阶段将应用配置。 因此,即使一个实例暂时不可用,也不会发生任何可怕的事情。 该配置将根本不适用,并且您会提前看到错误。
此外,在拓扑部分中还指出了这样一个重要的参数,即每个复制组的领导者。 通常,这是正在记录的实例。 其余大多数通常是只读的,尽管可能会有例外。 有时,勇敢的开发人员不怕冲突,可以并行地将数据写入多个副本,但是尽管有很多操作,但有些操作不应该执行两次。 有领导者的迹象。

角色生活
为了使抽象角色在这样的体系结构中存在,框架必须以某种方式对其进行管理。 自然,无需重新启动Tarantool进程即可进行控制。 有4个用于管理角色的回调。 Cartridge本身将根据在分布式配置中所说的内容来调用它们,从而将配置应用于特定角色。
function init() function validate_config() function apply_config() function stop()
每个角色都有一个
init
函数。 启用角色或重新启动Tarantool时,将调用一次。 例如,在那里方便初始化box.space.create,或者调度程序可以启动某些后台光纤,这将以特定间隔进行工作。
init
功能可能还不够。 墨盒允许角色利用其用于存储拓扑的分布式配置。 在相同的配置中,我们可以声明一个新部分,并将业务配置的一部分存储在其中。 在我的示例中,这可以是数据方案,也可以是调度程序角色的调度设置。
每次分布式配置更改时,集群都会调用
validate_config
和
apply_config
。 通过两阶段提交应用配置时,集群将验证每个角色已准备好接受此新配置,并在必要时向用户报告错误。 当所有人都同意配置正常时,将
apply_config
。
角色还具有
stop
方法,该方法需要清除角色的生命体征。 如果我们说不再需要此服务器上的调度程序,则它可以停止以
init
开头的光纤。
角色可以彼此交互。 我们习惯于在Lua中编写函数调用,但是在这种情况下,我们可能没有所需的角色。 为了方便网络访问,我们使用了辅助模块rpc(远程过程调用),该模块是基于Tarantool中内置的标准netbox构建的。 例如,这在您的网关希望直接要求调度程序立即执行工作而不是等待一天的情况下很有用。
另一个要点是确保容错能力。 墨盒使用SWIM协议
[4]监视运行状况。 简而言之,这些进程通过UDP相互交换“谣言”-每个进程将其最新消息告知邻居,然后它们做出响应。 如果没有找到答案,Tarantool会开始怀疑某事不对劲,过了一会儿他背诵死亡并开始告诉所有人这则新闻。

根据此协议,墨盒会组织自动故障转移。 每个进程都会监视其环境,如果领导者突然停止响应,则副本可以自己承担角色,而盒式磁带将相应地配置正在运行的角色。

您在这里必须小心,因为频繁来回切换可能会导致复制期间的数据冲突。 随机打开自动故障转移当然是不值得的。 您需要清楚地了解正在发生的事情,并确保在领导者恢复并将王冠归还给他之后复制不会中断。
从所有的说法来看,这些角色似乎类似于微服务。 从某种意义上讲,它们仅作为Tarantool流程中的模块。 但是存在一些根本差异。 首先,所有项目角色都必须存在于一个代码库中。 而且所有Tarantool进程都应从一个代码库启动,因此不会像我们尝试初始化调度程序时那样令人惊讶,但事实并非如此。 同样,也不允许代码版本有所不同,因为在这种情况下系统的行为很难预测和调试。
与Docker不同,我们不能仅将角色的“映像”带入另一台机器并在其中运行。 我们的角色不像Docker容器那样孤立。 同样,我们不能在同一实例上运行两个相同的角色。 在某种意义上讲,角色是单身人士。 第三,在整个复制组中角色应该相同,因为否则,这将是荒谬的-数据相同,并且配置不同。
部署工具
我答应展示Cartridge如何帮助部署应用程序。 为了使其他人的生活更轻松,该框架打包了RPM软件包:
$ cartridge pack rpm myapp # ./myapp-0.1.0-1.rpm $ sudo yum install ./myapp-0.1.0-1.rpm
已安装的软件包几乎包含您需要的所有内容:应用程序和已安装的lauch依赖项。 Tarantool也将以RPM软件包依赖项的形式出现在服务器上,并且我们的服务已准备就绪。 这是通过systemd完成的,但是首先您需要编写一些配置。 至少指定每个进程的URI。 例如三个就足够了。
$ sudo tee /etc/tarantool/conf.d/demo.yml <<CONFIG myapp.router: {"advertise_uri": "localhost:3301", "http_port": 8080} myapp.storage_A: {"advertise_uri": "localhost:3302", "http_enabled": False} myapp.storage_B: {"advertise_uri": "localhost:3303", "http_enabled": False} CONFIG
这里有一个有趣的细微差别。 我们不仅指定二进制协议端口,还指定包括主机名在内的整个过程的公共地址。 这是必需的,以便群集节点知道如何相互连接。 将地址0.0.0.0用作advertise_uri是个坏主意,它应该是一个外部IP地址,而不是绑定套接字。 没有它,一切都将无法工作,因此Cartridge根本不会让advertise_uri错误的节点启动。
现在配置已准备就绪,您可以开始流程了。 由于常规的systemd单元不允许启动多个进程,因此Cartridge上的应用程序会安装所谓的 实例化单元的工作方式如下:
$ sudo systemctl start myapp@router $ sudo systemctl start myapp@storage_A $ sudo systemctl start myapp@storage_B
在配置中,我们指定了Cartridge为Web界面提供服务的HTTP端口-8080。让我们来看一下:

我们看到这些进程虽然正在运行,但尚未配置。 盒式磁带尚不知道谁应该与谁进行复制,因此无法自行决定,因此正在等待我们采取行动。 我们的选择并不大:新集群的寿命始于第一个节点的配置。 然后,将其余部分添加到集群中,为它们分配角色,并且可以认为在此部署上已成功完成。
长时间工作后,倒一杯您最喜欢的饮料并放松。 该应用程序可以被利用。

总结
结果如何? 尝试,使用,留下反馈,在github上启动票证。
参考文献
[1]
Tarantool»2.2»参考»岩石参考»模块vshard[2]
我们如何基于Tarantool实施Alfa-Bank投资业务的核心[3]
下一代计费架构:过渡到Tarantool[4]
SWIM-集群构建协议[5]
GitHub-tarantool /墨盒-cli[6]
GitHub-tarantool /墨盒