电商平台 lnmp 架构之 mysql 优化

Posted 123坤

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了电商平台 lnmp 架构之 mysql 优化相关的知识,希望对你有一定的参考价值。

1. Mysql 的主从复制

  1. 关于复制
    在如今互联网环境中,复制一般分为传统的复制和大趋势下的组复制,而传统的复制又分为主从复制(异步复制)和半同步复制;
    主从复制有原始slave通过Position复制master的二进制日志文件,也有通过GTID的方式进行全局复制,GTID较前者的好处在于出现某个mysql宕掉后,GTID模式会在全局中自动寻找下一个,而不用手动配置。
    主从复制有一个比较致命问题,在生成二进制日志文件后,尤其在一些极端条件下,无法100%确认被slave端IO进程成功复制。
    而半同步复制中的AFTER_COMMIT恰好解决了这一问题,在slave端IO复制后会返回ACK,但是还有一个问题,在该模式下,插入一个数据时,不论IO线程复制成功与否,在本地都会查询到该数据,这在金融行业是致命的。
    半同步复制在AFTER_SYNC模式下会在IO线程返回ACK后才会把数据写道本地,当然也可以设置这个等待时间为无穷大,强制数据的一致性。
    MySQL主从异步复制是最常见的复制场景。数据的完整性依赖于主库BINLOG的不丢失,只要主库的BINLOG不丢失,那么就算主库宕机了,我们还可以通过BINLOG把丢失的部分数据通过手工同步到从库上去。
    传统的MySQL复制采用主从的方式进行,可以一主一从也可以一主多从,主库执行一个事务,提交后稍后异步的传送到从库中,如果是基于语句的复制则会重新执行,如果是基于行的负责则会应用日志,同时是shared-nothing的架构,即所有服务器拥有同样的数据复制
    传统的数据主从复制均属于异步复制,从库起IO线程连接主库,获取主库二进制日志写到本地中继日志,并更新master-info文件(存放主库相关信息),从库再利用SQL线程执行中继日志。
    补充:为了保证Binlog的安全,MySQL引入sync_binlog参数来控制BINLOG刷新到磁盘的频率。

  2. 复制的原理
    Mysql 中有一种日志叫做 bin 日志(二进制日志)。这个日志会记录下所有修改了数据库的SQL 语句(insert,update,delete,create/alter/drop table, grant 等等)。
    主从复制的原理其实就是把主服务器上的 bin 日志复制到从服务器上执行一遍,这样从服务器上的数据就和主服务器上的数据相同了。

  3. 复制过程
    主节点必须启用二进制日志,记录任何修改了数据库数据的事件。
    从节点开启一个线程(I/O Thread)把自己扮演成 mysql 的客户端,通过 mysql 协议,请求主节点的二进制日志文件中的事件。
    主节点启动一个线程(dump Thread),检查自己二进制日志中的事件,跟对方请求的位置对比,如果不带请求位置参数,则主节点就会从第一个日志文件中的第一个事件一个一个发送给从节点。
    从节点接收到主节点发送过来的数据把它放置到中继日志(Relay log)文件中。并记录该次请求到主节点的具体哪一个二进制日志文件内部的哪一个位置。
    从节点启动另外一个线程(sql Thread ),把 Relay log 中的事件读取出来,并在本地再执行一次。

  4. 复制过程中的线程
    从节点:
    I/O Thread: 从 Master 节点请求二进制日志事件,并保存于中继日志中。
    Sql Thread: 从Relay log 中读取日志事件并在本地完成重放。
    主节点:
    Dump Thread:为每个 Slave 的 I/O Thread 启动一个 dump 线程,用于向从节点发送二进制事件。

思考:从节点需要建立二进制日志文件吗?
看情况,如果从节点需要作为其他节点的主节点时,是需要开启二进制日志文件的。这种情况叫做级联复制。如果只是作为从节点,则不需要创建二进制文件。

  1. 配置
    此处可以在server2上直接将之前server1编译的数据复制过去,也可以用rpm 包来安装。

此处选择用源码包复制:

[root@server1 mysql]# du -sh .
2.0G	.
[root@server1 mysql]# scp -r /usr/local/mysql/ root@172.25.25.2:/usr/local/
[root@server1 ~]# scp /etc/my.cnf root@172.25.25.2:/etc/  
[root@server1 ~]# scp /etc/init.d/mysqld root@172.25.25.2:/etc/init.d/  

将 server2 中的mysql 写入环境变量中,并且新建需要运行mysql 的用户,

[root@server2 bin]# pwd
/usr/local/mysql/bin
[root@server2 bin]# cd
[root@server2 ~]# vim .bash_profile 
[root@server2 ~]# source .bash_profile
[root@server2 ~]# tail -n3 .bash_profile
PATH=$PATH:$HOME/bin:/usr/local/mysql/bin

export PATH
[root@server2 ~]# useradd -M -d /usr/local/mysql/data/ -s /sbin/nologin mysql
[root@server2 ~]# id mysql
uid=1000(mysql) gid=1000(mysql) groups=1000(mysql)

然后清理之前的数据,并完成初始化;

[root@server2 ~]# cd /usr/local/mysql/data/
[root@server2 data]# rm -fr *
[root@server2 ~]# mysqld --initialize --user=mysql
#生成随机数密码,记住随机数密码,重复执行会报错
[root@server2 ~]# /etc/init.d/mysqld start
Starting MySQL.Logging to '/usr/local/mysql/data/server2.err'.
 SUCCESS! 
# 启动mysql,并使用随机数密码修改mysql密码(切记不要重复启动,否则会报错)
[root@server2 ~]# mysql_secure_installation 
#执行安全初始化脚本,全部回车即可

此时server2 上的mysql 数据库就已经配置完成并且启动;此时启动server1 上的数据库,来保证两个数据库之间数据的同步;

以下的配置可以参考官网: https://dev.mysql.com/doc/refman/5.7/en/replication-howto-masterbaseconfig.html

主从复制(master&slave)

master 节点:
在mysql 8.0以后是默认支持主从复制的,在当前的版本中需要在配置文件中增添信息;

[root@server1 ~]# vim /etc/my.cnf
[root@server1 ~]# cat /etc/my.cnf
[mysqld]
datadir=/usr/local/mysql/data
socket=/usr/local/mysql/data/mysql.sock
symbolic-links=0

server-id=1			#加入id(server_id值为0时,副本拒绝连接到源。)
log-bin=mysql-bin	##启动二进制日志
[root@server1 ~]# /etc/init.d/mysqld restart
Shutting down MySQL.. SUCCESS! 
Starting MySQL. SUCCESS! 
[root@server1 ~]# ls /usr/local/mysql/data/
auto.cnf         ibdata1           mysql-bin.index     server1.err
ca-key.pem       ib_logfile0       mysql.sock          server1.pid
ca.pem           ib_logfile1       mysql.sock.lock     server-cert.pem
client-cert.pem  ibtmp1            performance_schema  server-key.pem
client-key.pem   mysql             private_key.pem     sys
ib_buffer_pool   mysql-bin.000001  public_key.pem

mysql -p 创建用于复制的用户

[root@server1 ~]# mysql -p

mysql> CREATE USER 'repl'@'%' IDENTIFIED BY 'redhat';
##%代表除本机外的所有用户,方便测试
Query OK, 0 rows affected (0.01 sec)

mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
Query OK, 0 rows affected (0.01 sec)

mysql> show master status;	#查看主库的状态
+------------------+----------+--------------+------------------+-------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 |      595 |              |                  |                   |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)

slave
在server2上添加id 信息并重启mysqld 重载配置文件;

[root@server2 ~]# vim /etc/my.cnf
[root@server2 ~]# tail -n1 /etc/my.cnf
server-id=2
[root@server2 ~]# /etc/init.d/mysqld restart
Shutting down MySQL. SUCCESS! 
Starting MySQL. SUCCESS! 

注: server_id:是必须的,而且唯一。

mysql -p 建立副本

[root@server2 ~]# mysql -p
mysql> CHANGE MASTER TO MASTER_HOST='172.25.25.1',
MASTER_USER='repl',
MASTER_PASSWORD='redhat',
MASTER_LOG_FILE='mysql-bin.000001',	#以server1上二进制日志文件为准
MASTER_LOG_POS=595;	#Position
Query OK, 0 rows affected, 2 warnings (0.04 sec)

mysql> start slave;
Query OK, 0 rows affected (0.00 sec)

mysql> show slave status\\G;	
#查看到Slave_IO_Running: yes Slave_SQL_Running: Yes
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 172.25.25.1
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000001
          Read_Master_Log_Pos: 595
               Relay_Log_File: server2-relay-bin.000002
                Relay_Log_Pos: 320
        Relay_Master_Log_File: mysql-bin.000001
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 595
              Relay_Log_Space: 529
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 1
                  Master_UUID: 18bc9556-c7a4-11eb-a8a3-525400759ebf
             Master_Info_File: /usr/local/mysql/data/master.info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
           Master_Retry_Count: 86400
                  Master_Bind: 
      Last_IO_Error_Timestamp: 
     Last_SQL_Error_Timestamp: 
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
           Retrieved_Gtid_Set: 
            Executed_Gtid_Set: 
                Auto_Position: 0
         Replicate_Rewrite_DB: 
                 Channel_Name: 
           Master_TLS_Version: 
1 row in set (0.00 sec)

ERROR: 
No query specified

测试:在主节点中插入数据,在从节点中查看。
加入数据:

mysql> create database westos;
Query OK, 1 row affected (0.01 sec)

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| westos             |
+--------------------+
5 rows in set (0.00 sec)

在slave 中查看;

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| westos             |
+--------------------+
5 rows in set (0.00 sec)

2. mysql 一主两从

上面做了mysql 的主从复制,接下来对其更进一步的扩展,实现一主两从。

下来的操作主要是针对mysql ,可以先将nginxphp关闭,节省资源消耗;

[root@server1 ~]# systemctl stop nginx
[root@server1 ~]# systemctl stop php-fpm
[root@server1 ~]# systemctl disable php-fpm
Removed symlink /etc/systemd/system/multi-user.target.wants/php-fpm.service.
[root@server1 ~]# systemctl disable nginx
Removed symlink /etc/systemd/system/multi-user.target.wants/nginx.service.

先在server3上配置数据库,此时可以和上面创建server2时一样,将已经存在的数据库复制一份;

[root@server2 local]# scp -r mysql/ root@172.25.25.3:/usr/local/
[root@server2 local]# scp /etc/my.cnf root@172.25.25.3:/etc/
[root@server2 local]# scp /etc/init.d/mysqld root@172.25.25.3:/etc/init.d/

[root@server3 ~]# vim /etc/my.cnf
[root@server3 ~]# tail -n1 /etc/my.cnf
server-id=3
[root@server3 data]# pwd 
/usr/local/mysql/data
[root@server3 data]# rm -fr *
[root@server3 ~]# useradd -M  -d /usr/local/mysql/ -s /sbin/nologin mysql
[root@server3 ~]# id mysql
uid=1000(mysql) gid=1000(mysql) groups=1000(mysql)
[root@server3 ~]# mysqld --initialize --user=mysql
[root@server3 ~]# /etc/init.d/mysqld start
[root@server3 ~]# mysql_secure_installation 

此时,新加的数据库和之前的数据库信息并不同步;需要将其同步;

[root@server1 ~]# mysql -p

Type 'help;' or '\\h' for help. Type '\\c' to clear the current input statement.

mysql> show database;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'database' at line 1
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| westos             |
+--------------------+
5 rows in set (0.01 sec)

mysql> use westos;
Database changed
mysql> show tables;
Empty set (0.00 sec)

mysql> create table user_tb (
    -> username varchar(25) not null,
    -> password varchar(50) not null);
Query OK, 0 rows affected (0.03 sec)

mysql> insert into user_tb values ('user','1111');
Query OK, 1 row affected (0.01 sec)

mysql> insert into user_tb values ('user2','222');
Query OK, 1 row affected (0.01 sec)

此时由于server2 是server1的从库,会同步信息;但是server3是新加入的并不是从库,此时并不会同步数据;需要做备份。

[root@server1 ~]# mysqldump -uroot -pwestos westos > dump.db
#将 westos的库信息,导出做备份。
mysqldump: [Warning] Using a password on the command line interface can be insecure.
[root@server1 ~]# scp dump.db root@172.25.25.3:~

[root@server3 ~]# cat dump.db | grep DROP
DROP TABLE IF EXISTS `user_tb`;
[root@server3 ~]# mysqladmin create westos -pwestos
# 创库
[root@server3 ~]# mysql -pwestos westos < dump.db 
#将备份数据导入库中
[root@server3 ~]# mysql -p
Enter password: 

mysql> select * from westos.user_tb;
+----------+----------+
| username | password |
+----------+----------+
| user     | 1111     |
| user2    | 222      |
+----------+----------+
2 rows in set (0.00 sec)

导入备份数据之后,此时登陆数据库查看其库的信息。

注:在生产环境中一定要注意用mysqldump 的方式是先删除在复制的过程,而且生产环境中备份数据库时可能要锁表。

此时server1是主数据库,server2为从数据库,在此基础上加入server3。
有两种方案:
1)server2和3都作为server1的从库,但是会加大server1的负载;
2)将server3作为server2的从数据库,因此server2既是从库也是主库。以达到宏观上的负载均衡。

此处选择第二种方法,将server3作为 server2的从库;来缓解主库的压力。

先在 server2上修改配置信息;

[root@server2 ~]# vim /etc/my.cnf
[root@server2 ~]# cat /etc/my.cnf
[mysqld]
datadir=/usr/local/mysql/data
socket=/usr/local/mysql/data/mysql.sock
symbolic-links=0
server-id=2
lob-slave-updates	#将修改操作写入二进制日志中
log-bin=mysql-bin	#启用二进制日志
[root@server2 ~]# /etc/init.d/mysqld restart
[root@server2 ~]# ls /usr/local/mysql/data/
auto.cnf         ib_logfile1       performance_schema        server2-relay-bin.index
ca-key.pem       ibtmp1            private_key.pem           server-cert.pem
ca.pem           master.info       public_key.pem            server-key.pem
client-cert.pem  mysql             relay-log.info            sys
client-key.pem   mysql-bin.000001  server2.err               westos
ib_buffer_pool   mysql-bin.index   server2.pid
ibdata1          mysql.sock        server2-relay-bin.000009
ib_logfile0      mysql.sock.lock   server2-relay-bin.000010

重启之后,此时server2-relay-bin.000009server2-relay-bin.0000010是从 IO 读取的server1 的二进制信息,mysql-bin.000001是生成的二进制日志,供server3读取。

对从库进行授权;

[root@server2 ~]# mysql -p

mysql> CREATE USER 'repl'@'%' IDENTIFIED BY 'redhat';
Query OK, 0 rows affected (0.01 sec)

mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
Query OK, 0 rows affected (0.00 sec)
mysql> SHOW MASTER STATUS;
+------------------+----------+------------以上是关于电商平台 lnmp 架构之 mysql 优化的主要内容,如果未能解决你的问题,请参考以下文章

电商平台 lnmp 架构之 nginx 优化

电商平台 lnmp 架构之 mysql 高速缓存--redis

电商平台 lnmp 架构之 mysql 高速缓存--redis

电商平台 lnmp 架构之 nginx+tomcat

电商平台 lnmp 架构之 nginx+tomcat

LNMP平台搭建及优化