
如今,不仅在大型的昂贵项目中,无论何时何地都要求服务的高可用性。 消息“抱歉,正在执行维护”的暂时不可用的站点仍然出现,但通常会引起居高临下的微笑。 此外,您还可以在云中生活,启动另一台服务器时,只需调用一次API,而无需考虑“固定”操作。 而且不再有任何借口为何不能使用群集技术和冗余可靠地构建关键系统。
我们将告诉您为确保服务中数据库的可靠性而考虑采用的解决方案,以及解决方案。 加上一个具有深远影响的演示。
高可用性架构中的传统
在开发各种开源系统的情况下,这甚至更好。 随着需求的增长,旧解决方案被迫添加高可用性技术。 而且它们的质量不同。 下一代解决方案将高可用性作为其体系结构的核心。 例如,MongoDB将集群定位为主要用例。 集群水平扩展,这是该DBMS的强大竞争优势。
回到PostgreSQL。 这是最流行的开源项目之一,其首次发布于上世纪95年。 长期以来,项目团队并未将高可用性视为系统需要解决的任务。 因此,用于创建数据副本的复制技术仅在2006年集成到8.2版中,但已提出(日志传送)。 在2010年,流复制出现在9.0版中,它是创建各种群集的基础。 实际上,这对于在Enterprise SQL或现代NoSQL之后了解PostgreSQL的人们来说是非常令人惊讶的-社区的标准解决方案只是一对具有同步或异步复制的主副本。 同时,该向导在流水线中手动切换,并且还建议独立解决切换客户端的问题。
我们如何决定制作可靠的PostgreSQL,以及为此选择了什么
但是,如果没有大量的项目和工具来帮助构建不需要持续关注的容错解决方案的PostgreSQL,PostgreSQL就不会变得如此流行。 自启动DBaaS以来,
Mail.ru云解决方案 (MCS)中已经提供了具有异步复制功能的单个PostgreSQL服务器和主副本对。
当然,我们希望简化每个人的生活,并提供可用的PostgreSQL安装,它可以作为高度可访问的服务的基础,而您不必在晚上进行不断监控和唤醒就可以进行切换。 在这一部分中,既有经过验证的旧解决方案,又有使用最新发展的新一代实用程序。
如今,高可用性的问题不仅仅在于冗余(毋庸置疑),还在于共识-一种选择领导者的算法(领导者选举)。 通常,发生重大事故的原因不是因为缺少服务器,而是因为共识问题:新负责人没有离开,两名负责人出现在不同的数据中心等。 一个例子是Github MySQL集群崩溃-他们写了
详细的验尸报告 。
这件事的数学基础很严重。 一方面,存在一个
CAP定理 ,该
定理对构建HA解决方案的可能性施加了理论限制,而另一方面,则通过了数学验证的共识确定算法(例如
Paxos和
Raft) 。 在此基础上,有相当受欢迎的DCS(去中心化共识系统)-Zookeeper等,领事。 因此,如果决策系统采用自己编写的某些算法(独立编写),则应格外小心。 在分析了大量系统之后,我们选择了Patroni-一种主要由Zalando开发的开源系统。
作为抒情离题,我将说我们还考虑了多主解决方案,即可以水平缩放以进行记录的群集。 但是,出于两个主要原因,他们决定不进行此类分组。 首先,这种解决方案具有很高的复杂性,因此存在更多的漏洞。 很难为所有情况做出稳定的决定。 其次,在这种情况下,PostgreSQL不再是纯正的(本机),某些功能将不可用,某些应用程序在工作时可能会遇到隐藏的错误。
帕特罗尼
那么Patroni如何工作? 开发人员并未重新发明轮子,而是建议使用一种行之有效的DCS解决方案作为基础。 有关配置同步,领导者选择和法定人数的所有问题都交给了他。 我们为此选择了etcd。
然后Patroni处理PostgreSQL上所有设置和复制设置的正确应用,以及在切换和故障切换时执行命令(即常规和非标准切换向导)。 具体而言,在MCS云中,您可以从向导,同步副本和一个或多个异步副本创建群集。 同步副本的存在确保了至少2台服务器上数据的安全,并且该副本将是主要的“主副本”。
由于etcd部署在同一台服务器上,因此建议将服务器数量设置为3或5,以获得最佳仲裁值。 这样的簇被水平缩放以用于读取(我在上面写过关于缩放以用于写入)。 但是,应该记住,异步副本是滞后的,尤其是在高负载下。
使用此类读取备用副本有理由进行报告或分析任务,并减轻了主服务器的负担。
如果您想自己创建这样的集群,那么您将需要:
- 准备3台或更多服务器,并在它们之间配置IP寻址和防火墙规则;
- 为服务etcd,Patroni,PostgreSQL安装软件包;
- 配置etcd集群;
- 配置patroni服务以与PostgreSQL一起使用。
也就是说,总的来说,您需要正确地编写一打配置文件,并且不要在任何地方犯错误。 为此,绝对值得使用例如Ansible这样的配置管理工具。 但是,仍然没有高度可用的TCP平衡器。 要做到这一点是一项单独的工作。
对于那些需要现成群集但又不想四处寻找的人,我们尝试简化我们的生活,并在Patroni的云中制作了现成群集,可以免费对其进行测试。 除了集群本身,我们还执行了以下操作:
- TCP平衡器 在不同的端口上,它始终分别指向当前的主副本,同步副本或异步副本;
- 用于切换活动Patroni向导的API。
它们可以通过MCS云API和Web控制台连接。
演示版
为了测试MCS云中PostgreSQL集群的功能,让我们看看在发生DBMS问题时实时应用程序的行为。
以下是将记录人工事件并将其报告到屏幕的应用程序的代码。 万一发生错误,它将报告此情况并周期性地继续工作,直到我们使用Ctrl + C组合键将其停止为止。
from __future__ import print_function from datetime import datetime from random import randint from time import sleep import psycopg2 def main(): try: connection = psycopg2.connect(user = "admin", password = "P@ssw0rd", host = "89.208.87.38", port = "5432", database = "myproddb") cursor = connection.cursor() cursor.execute("SELECT version();") record = cursor.fetchone() print("Connection opened to", record[0]) cursor.execute( "INSERT INTO log VALUES ({});".format(randint(1, 10000))) connection.commit() cursor.execute("SELECT COUNT(event_id) from log;") record = cursor.fetchone() print("Logged a value, overall count: {}".format(record[0])) except Exception as error: print ("Error while connecting to PostgreSQL", error) finally: if connection: cursor.close() connection.close() print("Connection closed") if __name__ == '__main__': try: while True: try: print(datetime.now()) main() sleep(3) except Exception as e: print("Caught error:\n", e) sleep(1) except KeyboardInterrupt: print("exit")
应用程序需要PostgreSQL才能工作。 使用API在MCS云中创建集群。 在常规终端中,OS_TOKEN变量包含用于访问API的令牌(您可以使用openstack token issue命令获得该令牌),我们键入以下命令:
创建集群:
cat <<EF > pgc10.json {"cluster":{"name":"postgres10","allow_remote_access":true,"datastore":{"type":"postgresql","version":"10"},"databases":[{"name":"myproddb"}],"users":[{"databases":[{"name":"myproddb"}],"name":"admin","password":"P@ssw0rd"}],"instances":[{"key_name":"shared","availability_zone":"DP1","flavorRef":"d659fa16-c7fb-42cf-8a5e-9bcbe80a7538","nics":[{"net-id":"b91eafed-12b1-4a46-b000-3984c7e01599"}],"volume":{"size":50,"type":"DP1"}},{"key_name":"shared","availability_zone":"DP1","flavorRef":"d659fa16-c7fb-42cf-8a5e-9bcbe80a7538","nics":[{"net-id":"b91eafed-12b1-4a46-b000-3984c7e01599"}],"volume":{"size":50,"type":"DP1"}},{"key_name":"shared","availability_zone":"DP1","flavorRef":"d659fa16-c7fb-42cf-8a5e-9bcbe80a7538","nics":[{"net-id":"b91eafed-12b1-4a46-b000-3984c7e01599"}],"volume":{"size":50,"type":"DP1"}}]}} EOF curl -s -H "X-Auth-Token: $OS_TOKEN" \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -d @pgc10.json https://infra.mail.ru:8779/v1.0/ce2a41bbd1434013b85bdf0ba07c770f/clusters

当集群进入活动状态时,所有字段将接收当前值-集群已准备就绪。
在GUI中:

让我们尝试连接并创建一个表:
psql -h 89.208.87.38 -U admin -d myproddb Password for user admin: psql (11.1, server 10.7) Type "help" for help. myproddb=> CREATE TABLE log (event_id integer NOT NULL); CREATE TABLE myproddb=> INSERT INTO log VALUES (1),(2),(3); INSERT 0 3 myproddb=> SELECT * FROM log; event_id ---------- 1 2 3 (3 rows) myproddb=>

在应用程序中,我们指示用于连接PostgreSQL的当前设置。 我们将指定TCP平衡器的地址,从而无需手动切换到向导的地址。 运行它。 如您所见,事件已成功登录到数据库。

预定主交换机
现在,我们将在计划的向导切换过程中测试应用程序的操作:

我们正在看申请。 我们看到应用程序确实被中断了,但是只需要几秒钟,在这种情况下,最多需要9秒钟。

车坠
现在,让我们尝试模拟当前主机的虚拟机的崩溃。 可以通过Horizon接口简单地关闭虚拟机,只有它会定期关闭。 所有服务(包括Patroni)都将处理此类切换。
我们需要意外的关机。 因此,出于测试目的,我请我们的管理员以紧急方式关闭虚拟机(当前主机)。

同时,我们的应用程序继续工作。 自然,主机的这种紧急开关不会被忽视。
2019-03-29 10:45:56.071234 Connection opened to PostgreSQL 10.7 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-bit Logged a value, overall count: 453 Connection closed 2019-03-29 10:45:59.205463 Connection opened to PostgreSQL 10.7 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-bit Logged a value, overall count: 454 Connection closed 2019-03-29 10:46:02.661440 Error while connecting to PostgreSQL server closed the connection unexpectedly This probably means the server terminated abnormally before or while processing the request. Caught error: local variable 'connection' referenced before assignment ……………………………………………………….. - - 2019-03-29 10:46:30.930445 Error while connecting to PostgreSQL server closed the connection unexpectedly This probably means the server terminated abnormally before or while processing the request. Caught error: local variable 'connection' referenced before assignment 2019-03-29 10:46:31.954399 Connection opened to PostgreSQL 10.7 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-bit Logged a value, overall count: 455 Connection closed 2019-03-29 10:46:35.409800 Connection opened to PostgreSQL 10.7 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-bit Logged a value, overall count: 456 Connection closed ^Cexit
如您所见,该应用程序能够在不到30秒的时间内继续工作。 是的,一定数量的服务用户将有时间注意到问题。 但是,这是严重的服务器故障,这种情况很少发生。 同时,除非该人(管理员)正坐在控制台中准备好切换脚本,否则几乎无法做出反应。
结论
在我看来,这样的集群为管理员提供了巨大的优势。 实际上,数据库服务器的严重故障和故障对于应用程序以及因此对于用户而言不会是显而易见的。 您不必着急修理某些东西,而切换到临时配置,服务器等。 而且,如果您以现成的服务形式在云中使用此解决方案,则无需浪费时间准备它。 可以做一些更有趣的事情。