基于 Discuz!X 的双机热备部署方案

互联网应用(产品)一旦上线,就会面临可靠性和可扩展性两个永恒的问题。而对于很多创业公司而言,在还没有把用户量做起来之前,产品可靠性的问题则更加突出。笔者认为采用双机热备的方案是对小微应用而言最具性价比的方案。本系列教程将分几个章节由浅入深向读者展示一个完整的双机方案是如何部署并运行的。

1、业务环境系统设计
1.1、整个系统由三台服务器构成,其中两台负责承载生产业务,剩下一台负责监控、备份、执行主备切换;这里的服务器均为运行 Linux 系统的独立服务器,可以是物理主机,也可以是类似阿里云ECS的逻辑主机。
1.2、两台负责承载生产业务的主机,应该采用相同的硬件配置,比如相同的CPU、内存、硬盘、网络带宽;以及相同的业务运行环境,比如相同的LAMP版本,本教程使用的 LAMP 环境为:Ubuntu Server 14.04 LTS 64bit + Apache/2.4.7(Ubuntu)+ MySQL 5.5.49 + PHP 5.5.9;监控主机可以选用相对硬件配置较低的主机,但应该采用相同的业务运行环境。
1.3、正常情况下(双机均正常运行),写业务流量路由到主机,读业务流量路由到备机,监控流量同时访问主机和备机;监控主机采用一定的策略对主机业务和备机业务进行检测,一旦监测到故障应立即采取对应的业务切换策略。
1.4、业务系统必须实现应用服务与数据服务分离,在我们的案例中,应用服务指 Discuz!X,数据服务指 MySQL;双机热备本质上是 MySQL 数据库的双机热备。
1.5、采用 DNS 联动实现负载均衡。1.6、为简单起见,本方案暂不考虑,故障恢复后的自动恢复方案,而由维护工程师手动操作实现。

2、DNS 负载均衡
DNS 作为互联网服务中最为重要的基础服务,除了提供最基本的域名解析服务外,现代 DNS 服务还可以提供基于 IP 的负载均衡服务。本方案采用 DNSPOD 提供的 DNS 服务。

DNSPOD 提供的免费套餐支持 ”同主机、同线路、两条A记录“,翻译一下就是用户访问同一个域名,且用户流量来自相同的运营商线路,可以在 DNSPOD 后台创建两条主机名相同(子域名相同)的 A记录。例如,我们可以在 Dnspod 后台创建如下两条 A 记录:
  1. tt.bacysoft.cn ->(默认线路)-> 10.10.10.1
  2. tt.bacysoft.cn ->(默认线路)-> 10.10.10.2
复制代码
这样,当用户在访问 tt.bacysoft.cn 这个域名的时候,Dnspod 就会自动进行负载均衡,大致按照 1:1 的比例分配访问流量到 10.10.10.1 和 10.10.10.2 这两个 IP。因为是免费套餐,在默认线路下已经无法添加第三个IP,规避的方案是,可以在电信或者联通线路下再增加两条 A 记录,例如:
  1. tt.bacysoft.cn ->(电信线路)-> 10.10.10.3
  2. tt.bacysoft.cn ->(电信线路)-> 10.10.10.4
复制代码
完成后,电信线路过来的访问流量将会分摊到 10.10.10.3 和 10.10.10.4 两个IP,而非电信流量将会分摊到 10.10.10.1 和 10.10.10.2。当然,至于选择电信还是联通,则需要实际的情况而定。

使用 Dnspod 还有一个重要原因就是 Dnspod 提供了完善 API,可以很好的实现 DNS 联动,即监控主机探测到故障后,可以立即通过 API 接口将对应主机的 A 记录禁用,这样后续用户流量将会流向还在正在工作的主机;当故障主机恢复后,通过 API 接口迅速恢复之前禁用的 A 记录,从而实现主备切换,实现真正意义的热备。

具体的 API 操作将在后文相关监控部署的章节详述。

3、MySQL 主从复制
MySQL 的主从复制功能是实现读写分离的关键,其原理是主服务器将写查询以二进制日志的方式记录下来,然后从服务器会自动获取主服务器上的二进制日志中的内容,并在本地执行,从而实现数据从主服务器到从服务器的复制。MySQL 的主从复制有非常多的属性可以设置,可以实现相当复杂的功能,但这些内容已经超出了本文的讨论范围,下文仅给出实现 MySQL 主从复制的最基本配置。

3.1 MySQL 主机配置
配置文件位于 /etc/mysql/my.cnf,主要的配置项是开启 Servier-id,以及开启二进制日志功能(log_bin),最简单的做法,就是找到配置文件中对应的条目,直接去掉语句前面的注释符号'#',参考配置如下:
  1. server-id                = 1
  2. log_bin                = /var/log/mysql/mysql-bin.log
复制代码根据 MySQL 官方手册,开启二进制日志的配置是:
  1. log-bin=mysql-bin
复制代码实测两种方式都OK。另外就是还可以增加 binlog_do_db 属性,用于设定需要开启二进制日志的数据库,通常就是应用业务使用的数据库,例如:
  1. binlog_do_db                = your_database_name
复制代码

3.2 MysQL 从机配置
从机的配置更加简单,最小配置仅需要配置 server-id,只需要保证与主机的 server-id 不重复即可,如果有多台从机,每台机器的 Server-id 都应该是唯一的。从机最小配置如下:
  1. server-id=2
复制代码

3.3 在主机上创建专门用于主从复制操作的帐号,并授予相关权限。可以通过 phpmyadmin 来创建,也可以直接使用 mysql 命令行创建,参考如下:
  1. mysql> CREATE USER 'repl'@'%.mydomain.com' IDENTIFIED BY 'slavepass';
  2. mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%.mydomain.com';
复制代码

3.4 获取主机的二进制日志坐标(位移)
所谓二进制的日志坐标或者位移,其实就是指与当前数据库对应的二进制日志的具体位置,可以理解成每一次对数据库的写操作,都会有一条对应的二进制日志,每一条日志都会有自己的ID,这里的坐标或者位移其实就是日志的ID号,从数据库通过坐标来判断从哪里开始将主数据库的记录同步到本地。可以通过以下步骤获取主数据库的二进制日志坐标:


3.4.1 锁定主数据库,确保执行获取坐标操作的时候,不会产生新的日志,通过 mysql 命令行在主服务器下执行:
  1. mysql> FLUSH TABLES WITH READ LOCK;
复制代码
3.4.2 显示当前日志文件名和日志坐标信息,通过 mysql 命令行在主服务器上执行并得到输出:
  1. mysql > SHOW MASTER STATUS;
  2. +------------------+----------+--------------+------------------+
  3. | File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
  4. +------------------+----------+--------------+------------------+
  5. | mysql-bin.000003 | 73       | test         | manual,mysql     |
  6. +------------------+----------+--------------+------------------+
复制代码此时需要记录的关键数据为 File 和 Position 字段对应的值,这里分别是:mysql-bin.000003 和 73。


3.4.3 解锁主数据库,通过 mysql 命令在主服务器上执行:
  1. mysql> UNLOCK TABLES;
复制代码解锁后,主库可以开始继续响应写操作。

需要注意的是,如果是在已有的数据的数据库上执行本操作,在锁表之前(步骤:3.4.1)应该先通过 mysqldump 或者 phpmyadmin 或者干脆直接 copy 数据库原始文件的方式将已经存在数据同步到从服务器,然后按照本节操作获取主服务器的二进制日志文件名以及对应的日志位置。

3.5 将主服务器信息配置到从服务器上,通过 mysql 命令在从服务器上执行:
  1. mysql> CHANGE MASTER TO
  2.     ->     MASTER_HOST='master_host_name',
  3.     ->     MASTER_USER='replication_user_name',
  4.     ->     MASTER_PASSWORD='replication_password',
  5.     ->     MASTER_LOG_FILE='recorded_log_file_name',
  6.     ->     MASTER_LOG_POS=recorded_log_position;
复制代码五个参数依次是主服务器的主机名(可以是IP,也可以是域名),主服务器上具有复制权限的账户名以及对应的密码(见3.3节),主服务器对应的日志文件名以及日志的坐标(位置或者位移)。

3.6 启动从服务器,通过 mysql 命令在从服务器上执行:
  1. mysql> START SLAVE;
复制代码
如果以上配置没有问题,从服务器就会从主服务器二进制日志的指定位置开始同步数据到本地,如果需要更加复制的同步策略请参考 MySql 的官方文档。

4、应用系统配置(Discuz!X)
在本文之前的几篇教程中我们介绍了 Discuz!X 分布式部署策略、读写分离以及负载均衡的做法,但是为了能够实现快速主备切换,还需要在此基础上做一些调整。调整的主要思路是通过修改 Discuz!X 的配置文件,使用 Define 语句将数据库的主机名与 IP 地址直接对应起来。首先来看看正常运行的状态下,Discuz!X 与数据库的对应关系,见下图:
基于 Discuz!X 的双机热备部署方案_第1张图片
S1 表示 PC 服务器 1,S2 表示 PC 服务器 2;Dz 1 和 Dz 2 分别表示 Discuz!X 应用程序;DB M 表示主数据库服务器,DB S 表示从数据库服务器。正常运行状态下 Dz1 和 Dz2 读取数据时访问 DB S,写数据时访问 DB M,然后 DB S 会自动同步 DB M 的数据到本地。根据图中所示业务模型,整理 DZ1 和 DZ2 的配置文件如下:
  1. define('DB_M', '10.10.10.1');
  2. define('DB_S', '10.10.10.2');
  3.  
  4. $_config['db']['1']['dbhost'] = DB_M;
  5. $_config['db']['1']['slave']['1']['dbhost'] = DB_S;
复制代码注意:这里仅列出与主备切换有关的关键配置,其余必要配置请参考本系列教程的前几篇。


那么如何实现主备切换呢,很简单,只需要在故障发生时,修改 DB_M 或者 DB_S 对应的 IP 地址即可。例如,主服务器故障了,则将:
  1. define('DB_M', '10.10.10.1');
复制代码修改成:
  1. define('DB_M', '10.10.10.2');
复制代码如果反过来,从服务器故障了,那就修改 DB_S 对应的 IP 为 DB_M 的 IP。

为了安全起见,建议专门写一个 shell 脚本执行此更改,由监控服务器在合适的时候远程调用。

5、监控策略
完成以上步骤后,系统还无法实现热备,不论主机还是从机故障都会导致系统服务挂掉,因此必须要有监控服务并与主机和从机实现以及 DNS 系统实现整体联动,才能实现故障时的切换,保证业务系统的稳定性。

5.1、数据库监控
在本文所描述的系统架构中,对主从数据库的监控是最为重要的。为了让监控行为与时间业务行为类似,应该在监控服务器上同样部署 Discuz!X 以及相关的运行环境,但是使用不同的配置文件,例如,在监控 Discuz!X 环境中,主数据库参数配置在 $_config['db']['1'] 下,从数据库则配置在 $_config['db']['2'] 下,在进行业务监控时,每次遍历 $_config['db'] 即可。参考配置如下:
  1. $_config['db']['1']['dbhost'] = 'localhost';
  2. $_config['db']['1']['dbuser'] = 'test';
  3. $_config['db']['1']['dbpw'] = '73yGybhsPVfTAxf3';
  4. $_config['db']['1']['dbcharset'] = 'utf8';
  5. $_config['db']['1']['pconnect'] = '0';
  6. $_config['db']['1']['dbname'] = 'test';
  7. $_config['db']['1']['tablepre'] = 'pre_';
  8.  
  9. $_config['db']['2']['dbhost'] = 'localhost';
  10. $_config['db']['2']['dbuser'] = 'test';
  11. $_config['db']['2']['dbpw'] = '73yGybhsPVfTAxf3';
  12. $_config['db']['2']['dbcharset'] = 'utf8';
  13. $_config['db']['2']['pconnect'] = '0';
  14. $_config['db']['2']['dbname'] = 'test';
  15. $_config['db']['2']['tablepre'] = 'pre_';
复制代码执行遍历操作的代码参考如下:
  1. foreach ($_G['config']['db'] as $key => $value) {
  2.         $db_config['1'] = array(
  3.                 'dbhost' => $value['dbhost'],
  4.                 'dbuser' => $value['dbuser'],
  5.                 'dbpw' => $value['dbpw'],
  6.                 'dbcharset' => $value['dbcharset'],
  7.                 'pconnect' => $value['pconnect'],
  8.                 'dbname' => $value['dbname'],
  9.                 'tablepre' => $value['tablepre'],
  10.         );
  11.  
  12.         DB::init( "db_driver_mysqli", $db_config );
  13.  
  14.         echo "
    ".print_r(C::t("common_member")->fetch(1),1)."
    ";
  15. }
复制代码在执行遍历时候,可以通过 $key 值来判断是否为主服务器,若等于 1 则为主服务器,除测试读操作外,还应该测试写操作。一旦发现主服务器存在写故障或者从服务器存在读故障,并在一定次数尝试均失败后,应该立即执行切换操作。具体的测试次数,应根据实际业务来确定。

5.2、DNS 联动
上文提到本方案使用的是 Dnspod 提供的域名解析服务,并且将应用域名同时指向了我们的两台服务器实现了基于域名的负载均衡,但是当服务器出现故障时,仅仅修改 Discuz!X 配置文件还不够,还必须将指向故障服务器的 DNS A 记录禁用,这样后续用户访问流量才会路由到还在工作的那台设备上。修改 A 记录可以通过 Dnspod 提供的 API 接口实现,参考:https://www.dnspod.cn/docs/records.html#record-modify
根据 Dnspod 文档的,要禁用一条记录仅需将 ‘status’ 参数传入值 ‘disable’ 即可,如果传入 ‘enable’ 则表示启用。因此当故障发生时,再修改完 Discuz!X 配置文件后,还应该立即通过 Dnspod API 将对应的 A 记录禁用。

5.3 监控频率
监控频率越快,发现故障越及时,但同时也会增加系统负载,具体情况要根据自身的业务情况而定。如果监控可以是分钟级别,那么直接使用系统的 cron 任务定时调用指定脚本即可;如果需要秒级甚至毫秒级别的监控,那就需要配合第三方模块来实现了,比如 PHP 的异步扩展 Swoole 就能实现毫秒级别的定时器。

至此,本方案的关键点都已经一一介绍,此方案最大的特点是简单易行,能以最小成本提供基本的主备热备功能。

你可能感兴趣的:(分布式系统,技术杂谈,高并发)