MySQL 主從複製配置指導及 PHP 讀寫分離源碼分析

開發環境

master環境:ubuntu16.04.5LTS/i5/8G/500G/64位/mysql5.7.23/php7/apache2

slave環境:kvm虛擬機/ubuntu14.04.01/1G/30G/mysql5.7.23

 

主從複製讀寫分離原理

  • 主從複製:

    主服務器數據庫的每次操作都會記錄在二進制日誌文件 A 中。從服務器的I/O線程到主服務器中讀取 A ,並將 A 內容寫入到自己本地的中繼日誌 B 中。然後從服務器的SQL線程會根據 B 中的內容執行SQL語句從而完成數據的複製。

    可做數據的備份、架構拓展、讀寫分離。

 

  • 讀寫分離:

    讀寫分離就是在主服務器上修改,數據會同步到從服務器,從服務器只能提供讀取數據,不能寫入,實現備份的同時也實現了數據庫性能的優化,以及提升了服務器安全。

    可以實現讀操作的負載均衡,適合讀操作遠遠大於寫操作的業務模型。

 

1 準備

1、KVM安裝虛擬機

參考文章:三步玩轉ubuntu kvm虛擬機系統

2、虛擬機安裝mysq

參考文章:linux centos7下源碼 tar安裝mysql5.7.22

 

2 配置主從複製

2.1 配置主服務器

主機mysql配置文件在 /etc/mysql/mysql.conf.d/mysqld.cnf 打開新終端 執行 sudo -s 執行 vi /etc/mysql/mysql.conf.d/mysqld.cnf //修改配置文件 在[mysqld]模塊裏註釋掉bind-address,用來允許遠程訪問數據庫(默認是註釋的) 在[mysqld]模塊添加如下代碼:

server-id = 1 #server-id 服務器唯一標識
log_bin = master-bin #log_bin 啓動MySQL二進制日誌
log_bin_index = master-bin.index
binlog_do_db = myslave #binlog_do_db 指定記錄二進制日誌的數據庫
binlog_ignore_db = mysql #binlog_ignore_db 指定不記錄二進制日誌的數據庫

保存退出 mysql -u root -p 登入主服務器數據庫 數據庫模式執行 grant replication slave,reload,super on . to slave@slaveip identified by 'password' //建 立一個帳戶slave,並且只能允許從slaveip這個地址上來登陸,密碼是你設置的password。 執行 exit //退出mysql模式 執行 service mysql restart //重啓數據庫(mysqld 則 service mysqld restart) 執行 mysql -u root -p //重新進入mysql 執行 FLUSH TABLES WITH READ LOCK; // 鎖定主數據庫 數據庫模式執行 show master status\G; 記住查詢出來的file和position的值,在後面的從庫配置會用到 UNLOCK TABLES; //解鎖主數據庫

2.2 配置從服務器

虛擬機mysql配置文件在 /etc/my.cnf 執行 vi /etc/my.cnf //編輯從數據庫配置 在該文件後直接添加如下([mysqld模塊]):

server-id = 2
replicate-do-db =myslave
relay-log = slave-relay-bin
relay-log-index = slave-relay-bin.index

保存退出 執行service mysql restart //重啓數據庫(mysqld 則 service mysqld restart) 執行 mysql -u root -p

change master to master_host='masterip',master_port=3306,master_user='slave',master_password='password',master_log_file='master-bin.000002',master_log_pos=1528;

master_host爲主服務器ip,master_port爲主服務器端口(默認3306),master_user爲主服務器grant replication的用戶名,master_password爲對應的密碼password,master_log_file爲主服務器操作2.1最後一步的file值,master_log_pos爲相應的position值

執行 start slave; //開啓數據同步 執行 show slave status\G; //查看同步狀態 若slave_io_running爲connecting時,可能是slave鏈接master服務器的用戶名/密碼錯誤或者網絡連接失敗。 若slave_io_running和slave_sql_running都是yes時,說明同步成功。此時主服務器數據庫的增刪改查都會被實時同步到從服務器數據庫。

若有其他slave服務器需要配置,重複2.2,將server-id依次設置成3、4、5..即可

2.3 驗證主從複製結果

  • 首先,我們在從服務器show slave status\G 查看同步狀態,若slave_io_running和slave_sql_running都是yes時,說明同步成功。

  • 進一步檢驗:master中進入mysql執行 use myslave; 執行

    CREATE TABLE sla_student (id int(10) unsigned NOT NULL AUTO_INCREMENT,name varchar(255) NOT NULL, PRIMARY KEY (id)) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8創建了一個sla_student表,這時候我們取slave中,mysql模式下(root賬戶或新創建的test都行): 執行 use myslave; 執行 show tables; 發現也多了一個sla_student表。我們如果在master表中進行insert,slave中數據也會做相應改動哦。如果直接在slave表中進行數據insert,master是不會添加數據的,因爲我們沒有配置雙主相互複製

3 實現讀寫分離、讀負載均衡

3.1 讀寫分離

  • 讀寫分離實現:當我們的sql比較簡單時(只是簡單的select、update、insert、delete),判斷sql是否以 ”select“爲開頭,是則連接從庫;否則連接主庫。

​ 關鍵代碼如下:

'mdb' => [   //主數據庫
'dbcharset' => 'utf8',
'dbhost' => 'localhost',     
'dbuser' => 'test',
'dbpw' => '123456',
'dbname' => 'myslave',
'dbport' => NULL,
'tbpre' => '',
'slaves_balancer' => ['slave1'=>1],     
],
​
'slave1'=>[   //從數據庫slave1
'dbhost' => '192.168.xxx.xx1',
'dbuser' => 'test',
'dbpw' => '123456',
'dbcharset' => 'utf8',
'dbname' => 'myslave',
'tbpre' => '',
],

 

 if(strtoupper(substr(ltrim($sql), 0 , 6)) === 'SELECT'){
    $this->slaveConn();   //連接從庫slave1
 }else{
    $this->masterConn();  //連接主庫mdb
 }

 

3.2 檢驗讀寫分離結果

3.2.1 驗證寫操作

  • 程序中:

    //每次query打印出當前host
    var_dump($this->conn->host_info);
    ​
    ...
    ​
    //插入數據
    $tb = new table('sla_student');
    echo print_r($tb->insert(['name'=>'testNm']),1).PHP_EOL;
  • 1、將mdb數據庫密碼修改爲錯誤的,slave1正常

    //mdb
    'dbpw' => '123455',

    返回:

Caught Exception: Error! Can not connect to mysql Database.

  • 2、將mdb數據庫密碼修改爲正常,slave1正常

    //mdb
    'dbpw' => '123456',

    返回:

    string(9) "localhost"

    1

  • 3、將slave1的密碼改爲錯誤的,mdb正常

    //slave1
    'dbpw' => '123455',

    返回:

    string(9) "localhost"

    1

 

  • 小結根據打印,寫操作走的localhost,當從庫連接失敗對寫操作無影響。

     

3.2.2 驗證讀操作

  • 程序中:

  //每次query打印出當前host
  var_dump($this->conn->host_info);
  
  ...
  
  //讀取一條數據
  $tb = new table('sla_student');
  echo print_r($tb->fetchRow(),1).PHP_EOL;
  • 1、將slave1的密碼改爲錯誤的,mdb正常

    //slave1
    'dbpw' => '123455',

    返回:

Caught Exception: Error! Can not connect to mysql Database.

  • 2、將slave1的密碼改爲正常的,mdb正常

    //slave1
    'dbpw' => '123456',

    返回

    string(15) "192.168.xxx.xx1"

    Array( [id] => 1 [name] => zhangsan)

 

  • 小結:當從數據庫連接正常時,重複十次查詢,打印結果都顯示走的從數據庫。

 

3.3 讀負載均衡

這裏也只是講一下思路。

  • 讀負載均衡解析:程序中,當我們連接從庫時,可以加一個讀均衡器,用均衡器隨機得出我們連接選擇。我們給性能較好的slave分配更高的權重,這樣就會有更高的機率連接該機器使其更好地發揮作用。

  • 讀負載均衡實現:當我們選擇連接slave機器的時候,先讀一下配置文件,查看針對該表設置的各個slave機器的權重:

     

關鍵代碼如下:

'mdb' => [   //主數據庫
'dbcharset' => 'utf8',
'dbhost' => 'localhost',     
'dbuser' => 'test',
'dbpw' => '123456',
'dbname' => 'myslave',
'dbport' => NULL,
'tbpre' => '',
'slaves_balancer' => ['slave1'=>3,'slave2'=>1],    //讀負載均衡器 
],
​
'slave1'=>[   //從數據庫slave1
'dbhost' => '192.168.xxx.xx1',
'dbuser' => 'test',
'dbpw' => '123456',
'dbcharset' => 'utf8',
'dbname' => 'myslave',
'tbpre' => '',
],
​
'slave2'=>[   //從數據庫slave2
'dbhost' => '127.0.0.1',
'dbuser' => 'test',
'dbpw' => '123456',
'dbcharset' => 'utf8',
'dbname' => 'myslave',
'tbpre' => '',
],

 

 

這裏我們給讀負載均衡器增加了slave2並且給了其權重1,slave1的權重爲3;

注意:slave2我沒有配置,所以給其設置iphost爲127.0.0.1,不同於於mdb中的localhost的是,var_dump打印出的host爲'127.0.0.1',而不是'localhost'。測試效果一樣。

```
'slaves_balancer' => ['slave1'=>3,'slave2'=>1],  //讀負載均衡器
```

進而:

```
$gravity = ['slave1'=>3,'slave2'=>1];
```

我們遍歷這個gravity:

    $pick = '';
    foreach ($gravity as $sid => $weight)
    {
        $total_weight += $weight;
        $weights[$total_weight] = $sid;
    }
​
    $rand_weight = mt_rand(1, $total_weight);
​
    foreach ($weights as $weight => $sid)
    {
        if ($rand_weight <= $weight)
        {
            $pick = $sid; break;
        }
    }

最終我們得到的$pick就是我們最終的連接選擇

3.4 檢驗讀負載均衡結果

  • 程序中:

​
  //打印每次訪問slave2次數和查詢總次數的比
  var_dump($this->slave2Tms/$this->queryTimes);
  ...
  
  //讀取一條數據,循環1000次
   $tb = new table('sla_student');
   for($i=0;$i<1000;$i++){
       echo print_r($tb->fetchRow(),1).PHP_EOL;
   }

返回

...Array( [id] => 1 [name] => zhangsan)

float(0.25150300601202)

Array( [id] => 1 [name] => zhangsan)

float(0.25225225225225)

Array( [id] => 1 [name] => zhangsan)

我們看到,slave2的訪問概率到1000次訪問時爲0.252接近1/4。正好符合我們讀負載均衡器 設置的1:3的概率。

  • 我們改動slave2值爲7時,得到

...float(0.72417251755266)

Array( [id] => 1 [name] => zhangsan)

float(0.72444889779559)

Array( [id] => 1 [name] => zhangsan)

float(0.72472472472472)

Array( [id] => 1 [name] => zhangsan)

接近1000時概率爲0.72也符合期望。

 

結尾

主從複製可以幫助我們實現讀寫分離,讀操作又可以進一步實現爲讀負載均衡,其實我們還可以利用DNS 負載均衡並添加一臺監控設備實現 雙機熱備(基於 Discuz!X 的雙機熱備部署方案 )。

更多精彩期待我們共同的探索。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章