ฐานข้อมูล MySQL / MariaDB การจำลองแบบ Master-Slave

การจำลองแบบ Master-Slave ใช้เพื่อแก้ปัญหาต่าง ๆ เกี่ยวกับประสิทธิภาพการทำงาน รองรับการสำรองข้อมูลของฐานข้อมูลที่แตกต่างกันและเป็นส่วนหนึ่งของโซลูชันขนาดใหญ่เพื่อลดความล้มเหลวของระบบ ช่วยให้สามารถจำลองข้อมูลจากเซิร์ฟเวอร์ฐานข้อมูล (master) ไปยังเซิร์ฟเวอร์ฐานข้อมูลอย่างน้อยหนึ่งเซิร์ฟเวอร์ (slave) master ทำการ log การอัปเดตซึ่งจะส่งผลไปยัง slave ซึ่ง slave จะแสดงข้อความที่ระบุว่าได้รับการอัปเดตสำเร็จซึ่งอนุญาตให้ส่งการอัปเดตในภายหลัง การจำลองแบบ Master-Slave สามารถเป็นแบบซิงโครนัสหรืออะซิงโครนัสก็ได้ ความแตกต่างเป็นเพียงช่วงเวลาของการเผยแพร่การเปลี่ยนแปลง หากการเปลี่ยนแปลงเกิดขึ้นกับ master และ slave ในเวลาเดียวกันจะเป็นแบบซิงโครนัส หากการเปลี่ยนแปลงถูกจัดคิวและเขียนในภายหลังจะเป็นแบบอะซิงโครนัส

เป้าหมายการใช้งานสำหรับการจำลองแบบในฐานข้อมูล MariaDB และ MySQL ประกอบด้วย:

  • Scale-out โซลูชัน 
  • ความปลอดภัยของข้อมูล
  • การวิเคราะห์
  • การกระจายข้อมูลทางไกล

คุณสามารถใช้การจำลองแบบดังกล่าวและใช้ประโยชน์จากมันได้อย่างไร

  • Backups: เพื่อใช้การจำลองแบบเป็นโซลูชันสำรองจำลองข้อมูลจาก master ไปยัง slave  แล้วสำรองข้อมูล slave ซึ่ง slave สามารถหยุดชั่วคราวและปิดเครื่องได้โดยไม่ส่งผลกระทบต่อการทำงานของ master เพื่อให้คุณสามารถสร้าง snapshot ของข้อมูล “live” ที่มีประสิทธิภาพได้มิฉะนั้นจะต้องปิดตัว master
  • Scale-out: คุณสามารถใช้การจำลองแบบเป็น scale-out โซลูชัน นั่นคือที่ที่คุณต้องการแบ่งภาระงานคิวรี่ฐานข้อมูลจำนวนมากในเซิร์ฟเวอร์ฐานข้อมูลหลายตัวภายในข้อจำกัดที่สมเหตุสมผล เนื่องจากการจำลองแบบทำงานจากการแจกจ่าย master หนึ่งไปยัง slave หนึ่งตัวขึ้นไป ใช้สำหรับ scale-out จะทำงานได้ดีที่สุดใน Env ที่คุณมีจำนวนการอ่านสูง และการเขียน / อัปเดตน้อย
  • Spreading the load: อาจมีสถานการณ์เมื่อคุณมี master เดียวและต้องการจำลองฐานข้อมูลที่แตกต่างกันไปยัง slave ที่แตกต่างกัน ตัวอย่างเช่นคุณอาจต้องการกระจายข้อมูลการขายไปยังแผนกที่จำเป็นเท่านั้นเพื่อช่วยกระจายภาระระหว่างการวิเคราะห์ข้อมูล
  • Increasing the performance: เมื่อจำนวน slave ที่เชื่อมต่อกับ master เพิ่มขึ้น ภาระงานนั้นแม้ว่าจะน้อยที่สุด แต่ก็เพิ่มขึ้นเช่นกันเนื่องจาก slave แต่ละตัวใช้การเชื่อมต่อ client กับ master เนื่องจาก slave แต่ละตัวต้องได้รับสำเนาทั้งหมดของไบนารี log ภาระงานของเครือข่ายบน master อาจเพิ่มขึ้นและสร้างปัญหาคอขวด หากคุณใช้ slave จำนวนมากที่เชื่อมต่อกับ master หนึ่งตัวและ master  ตัวนั้นก็กำลังดำเนินการตามคำขอ (เช่น เป็นส่วนหนึ่งของ scale-out โซลูชัน) คุณอาจต้องการปรับปรุงประสิทธิภาพของกระบวนการจำลองแบบ วิธีหนึ่งในการปรับปรุงประสิทธิภาพของกระบวนการจำลองแบบคือการสร้างโครงสร้างการจำลองแบบที่ลึกขึ้น ซึ่งช่วยให้ master สามารถจำลองเป็น slave เพียงตัวเดียวและสำหรับ slave ที่เหลือสามารถเชื่อมต่อกับ slave ตัวหลักสำหรับข้อกำหนดในการจำลองแบบได้
  • Failover alleviating: คุณสามารถตั้งค่า master และ slave (หรือ slave หลายตัว) และเขียนสคริปต์ที่คอยตรวจสอบ master เพื่อตรวจสอบว่ามันขึ้นหรือไม่ จากนั้นสั่งให้แอปพลิเคชันและ slave เปลี่ยน master ในกรณีที่เกิดความล้มเหลว
  • Security: คุณสามารถใช้ SSL เพื่อเข้ารหัสการถ่ายโอนไบนารี log ที่จำเป็นในระหว่างการจำลองแบบ แต่ทั้ง master และ slave ต้องรองรับการเชื่อมต่อเครือข่าย SSL หากโฮสต์ใดไม่รองรับการเชื่อมต่อ SSL การจำลองแบบผ่านการเชื่อมต่อ SSL จะไม่สามารถทำได้ การตั้งค่าการจำลองแบบโดยใช้การเชื่อมต่อ SSL คล้ายกับการตั้งค่าเซิร์ฟเวอร์และไคลเอนต์โดยใช้ SSL คุณต้องได้รับ (หรือสร้าง) ใบรับรองความปลอดภัยที่เหมาะสมซึ่งคุณสามารถใช้ได้กับ master และใบรับรองที่คล้ายกัน (จากผู้ออกใบรับรองเดียวกัน) ใน slave แต่ละตัว 

ตอนนี้เรามาดูตัวอย่างง่าย ๆ ของวิธีกำหนดค่าการจำลองแบบ Master-Slave บน Ruk-Com PaaS กัน

คุณสามารถตั้งค่าคลัสเตอร์ฐานข้อมูลได้สองวิธี:

  • automatically (ผ่านวิดเจ็ต one-click)
  • manually (ทำตามคำแนะนำทีละขั้นตอน)

การติดตั้ง One-Click

คุณสามารถรับคลัสเตอร์ฐานข้อมูล MySQL หรือ MariaDB ที่มีประเภทการจำลองแบบที่ต้องการ (รวมถึง master-slave one) และทำงานภายในไม่กี่นาทีโดยใช้วิดเจ็ตติดตั้ง Ruk-Com one-click

ขั้นตอนนี้ง่ายมากเพียงระบุอีเมลของคุณและยืนยันผ่านการแจ้งเตือน อ่านบทความ MariaDB / MySQL Auto-Clustering สำหรับข้อมูลเพิ่มเติมเกี่ยวกับแพ็คเกจที่ติดตั้งและตัวเลือกการปรับแต่งที่พร้อมใช้งาน

การติดตั้งด้วยตนเอง

หากคุณต้องการกำหนดค่าการจำลองแบบ Master-Slave ด้วยตนเองเพื่อรับตัวจำลอง Slave เพิ่มเติมหรือระบุการกำหนดค่าที่กำหนดเองบางอย่าง โปรดปฏิบัติตามคำแนะนำด้านล่าง

การสร้าง Env

เคล็ดลับ : คำแนะนำด้านล่างนี้เหมาะสำหรับเซิร์ฟเวอร์ฐานข้อมูล MySQL และ MariaDB อย่างยิ่ง

ก่อนอื่นต้องสร้าง Env 2 ตัวใน Ruk-Com สำหรับฐานข้อมูล master และฐานข้อมูล slave 

  1. ล็อกอินเข้าสู่แดชบอร์ด Ruk-Com แล้วคลิกปุ่ม New Environment


  2. ใน Env Topology wizard ให้เลือก MariaDB (หรือ MySQL) เป็นฐานข้อมูลที่คุณต้องการใช้ ตั้งค่าลิมิต cloudlet และพิมพ์ชื่อของ Env ตัวแรกของคุณตัวอย่างเช่น masterbase



    รอสักครู่เพื่อให้ Env ถูกสร้างขึ้น


  3. ในทำนองเดียวกันให้สร้าง Env ขึ้นมาใหม่ด้วย MariaDB หรือเพียงแค่ทำการโคลน มาตั้งชื่อว่า slavebase จะอยู่ใน hardnode อื่นซึ่งมีความปลอดภัยและเชื่อถือได้มากขึ้นสำหรับการจัดเก็บข้อมูล

    ตอนนี้คุณมี Env 2 ตัวที่เหมือนกันกับทั้งสองฐานข้อมูล

กำหนดค่าฐานข้อมูล Master

มากำหนดค่า master base กันเลย

  1. คลิกปุ่ม Config สำหรับฐานข้อมูล master


  2. ไปที่ไฟล์ my.cnf และเพิ่มคุณสมบัติต่อไปนี้ตามที่แสดงด้านล่าง:
server-id = 1  
log-bin = mysql-bin  
binlog-format = mixed



เราใช้รูปแบบ “mixed” binlog (binlog-format = mixed) เพื่อให้สามารถจำลองการทำงานด้วย foreign keys ได้

หมายเหตุ : อย่าใช้รูปแบบ “statement” binlog มิฉะนั้นคุณจะได้รับข้อผิดพลาดในภายหลัง!

  1. Save การเปลี่ยนแปลงและ Restart MariaDB เพื่อใช้พารามิเตอร์ที่กำหนดค่าใหม่


  2. คลิกปุ่ม Open in Browser สำหรับ MariaDB, Ruk-Com จะส่งอีเมลถึงคุณพร้อมข้อมูลรับรองไปยังฐานข้อมูลเพื่อเข้าสู่ระบบโดยใช้ข้อมูลรับรองเหล่านี้
  3. ไปที่แท็บ User accounts แล้วคลิก Add user account


  4. ระบุ name และ password สำหรับผู้ใช้การจำลองแบบ slave 

ตอนนี้เลื่อนลงและทำติ๊กเครื่องหมายที่ replication client และ replication slave ตรงส่วนของ  administration privileges

คลิก Go ที่อยู่ด้านล่างของหน้า

  1. สลับไปที่แท็บ Status เพื่อให้แน่ใจว่าการจำลองถูกกำหนดค่าอย่างถูกต้อง

ให้ความสนใจกับค่า Log File และ Position เนื่องจากจะต้องใช้ในภายหลังเพื่อตั้งค่าฐานข้อมูล Slave

กำหนดค่าฐานข้อมูล Slave

กลับไปที่แดชบอร์ด Ruk-Com และกำหนดค่าฐานข้อมูล Slave

  1. คลิกปุ่ม Config สำหรับ Slavebase Env


  2. ไปที่ไฟล์ my.cnf และเพิ่มสตริงต่อไปนี้:
server-id = 2  
slave-skip-errors = all



เราอนุญาตให้ slave base ข้ามข้อผิดพลาดทั้งหมดจาก master (slave-skip-errors = all) เพื่อไม่ให้หยุดการทำงานของ normal slave ในกรณีที่เกิดข้อผิดพลาดบน master base

หมายเหตุ : ไม่แนะนำให้ใช้การข้ามนี้ในระหว่างขั้นตอนการพัฒนาเนื่องจากช่วยในการค้นหาและแก้ไขข้อบกพร่อง อย่างไรก็ตามในการใช้งานจริง (เมื่อโค้ดของคุณได้รับการทดสอบแล้ว) จะช่วยหลีกเลี่ยงการไม่ซิงโครไนซ์สำหรับปัญหาเล็กน้อยใน master node

  1. จากนั้นเปิดไฟล์ /etc/phpMyAdmin/config.inc.php และใส่ต่อท้ายด้วยโค้ดถัดไป:
$cfg['AllowArbitraryServer'] = true;

  1. Save  การเปลี่ยนแปลงและ Restart เซิร์ฟเวอร์ฐานข้อมูล slave เพื่อใช้พารามิเตอร์คอนฟิกูเรชันใหม่


  2. มากำหนดค่าเซิร์ฟเวอร์ฐานข้อมูล slave ผ่าน Web SSH client แบบบิวท์อิน เชื่อมต่อกับฐานข้อมูลของคุณโดยใช้ข้อมูลประจำตัวจากอีเมลที่ได้รับหลังจากการสร้างโหนด
mysql -u root -p


  1. ให้รายละเอียดการจำลองแบบของ master
CHANGE MASTER TO 
MASTER_HOST='node275500-masterbase.jelastic.com',
MASTER_USER='slave',
MASTER_PASSWORD='passw0rd',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=853;

อย่าลืมแทนที่ค่าของตัวเลือกในตัวอย่างด้านบนด้วยข้อมูลที่ถูกต้อง:

  • MASTER_HOST – URL หรือ IP การจำลองแบบของ master
  • MASTER_USER – ผู้ใช้การจำลองแบบ
  • MASTER_PASSWORD – รหัสผ่านของผู้ใช้การจำลอง
  • MASTER_LOG_FILE – ไฟล์ log ของ master (ดูขั้นตอนสุดท้ายของส่วนการกำหนดค่า master)
  • MASTER_LOG_POS – ตำแหน่ง log ของ master (ดูขั้นตอนสุดท้ายของส่วนการกำหนดค่า master)


  1. ตอนนี้คุณสามารถเริ่มการจำลองแบบ slave ด้วยคำสั่งที่เหมาะสม:
start slave;


เคล็ดลับ : หากคุณต้องการให้แน่ใจว่าทุกอย่างได้รับการกำหนดค่าอย่างถูกต้องให้เข้าสู่ระบบในส่วนของฐานข้อมูล slave สำหรับแอดมิน และไปที่แท็บ Status

ตรวจสอบผลลัพธ์

เราต้องตรวจสอบให้แน่ใจว่าการจำลองแบบ master-slave ใช้งานได้กับฐานข้อมูลของเราแล้ว

  1. มาสร้างฐานข้อมูลใหม่ (เช่น Ruk-Com) ใน master base ของเรา


  2. ไปที่ slave base และคุณจะเห็นว่าฐานข้อมูลใหม่ถูกจำลองสำเร็จแล้ว

การเชื่อมต่อกับ Master-Slave

นี่คือ 2 ตัวอย่างของการเชื่อมต่อกับฐานข้อมูล master และฐานข้อมูล slave จากแอปพลิเคชัน Java และ PHP

  1. ตัวอย่างเช่นคุณสามารถดูโค้ดของแอปพลิเคชัน Java ของคุณซึ่งเชื่อมต่อกับฐานข้อมูล master และฐานข้อมูล slave Database_config.cfg:
master_host=jdbc:mysql://mariadb-master-host/mysql
master_username=root
master_password=abcABC123

slave_host=jdbc:mysql://mariadb-slave-host/mysql
slave_username=root
slave_password=abcABC123

driver=com.mysql.jdbc.Driver

Dbmanager.java:

package com.jelastic.test;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

public class DbManager {

    private final static String createDatabase = "CREATE SCHEMA IF NOT EXISTS jelastic";
    private final static String showDatabases = "SHOW DATABASES";

    public Connection createMasterConnection() throws IOException, ClassNotFoundException, SQLException {
       Connection masterConnection;
       Properties prop = new Properties();
       prop.load(new FileInputStream(System.getProperty("user.home") + "/database_config.cfg"));
       String master_host = prop.getProperty("master_host").toString();
       String master_username = prop.getProperty("master_username").toString();
       String master_password = prop.getProperty("master_password").toString();
       String driver = prop.getProperty("driver").toString();

       Class.forName(driver);
       masterConnection = DriverManager.getConnection(master_host, master_username, master_password);
       return masterConnection;
    }

    public Connection createSlaveConnection() throws IOException, ClassNotFoundException, SQLException {
       Connection slaveConnection;
       Properties prop = new Properties();
       prop.load(new FileInputStream(System.getProperty("user.home") + "/database_config.cfg"));
       String slave_host = prop.getProperty("slave_host").toString();
       String slave_username = prop.getProperty("slave_username").toString();
       String slave_password = prop.getProperty("slave_password").toString();
       String driver = prop.getProperty("driver").toString();

       Class.forName(driver);
       slaveConnection = DriverManager.getConnection(slave_host, slave_username, slave_password);
       return slaveConnection;
    }

    public boolean runSqlStatementOnMaster() {
       boolean execute = false;
       Statement statement = null;
       try {
           statement = createMasterConnection().createStatement();
           execute = statement.execute(createDatabase);
       } catch (IOException ex) {
         Logger.getLogger(DbManager.class.getName()).log(Level.SEVERE, null, ex);
       } catch (ClassNotFoundException ex) {
         Logger.getLogger(DbManager.class.getName()).log(Level.SEVERE, null, ex);
       } catch (SQLException ex) {
           ex.printStackTrace();
       } finally {
           if (statement != null) {
               try {
                   statement.close();
               } catch (SQLException e) {
                   e.printStackTrace();
               }
           }
       }
       return execute;
    }
    
    public List<String> runSqlStatementOnSlave() {
       List<String> stringList = new ArrayList<String>();
       Statement statement = null;
       ResultSet resultSet = null;
       try {
           statement = createSlaveConnection().createStatement();
           resultSet = statement.executeQuery(showDatabases);
           while (resultSet.next()) {
               stringList.add(resultSet.getString(1));
           }
       } catch (IOException ex) {
         Logger.getLogger(DbManager.class.getName()).log(Level.SEVERE, null, ex);
       } catch (ClassNotFoundException ex) {
         Logger.getLogger(DbManager.class.getName()).log(Level.SEVERE, null, ex);
       } catch (SQLException ex) {
           ex.printStackTrace();
       } finally {
           if (resultSet != null) {
               try {
                   resultSet.close();
               } catch (SQLException e) {
                   e.printStackTrace();
               }
           }
           if (statement != null) {
               try {
                   statement.close();
               } catch (SQLException e) {
                   e.printStackTrace();
               }
           }
       }
       return stringList;
    }
}
  1. การเชื่อมต่อกับฐานข้อมูล master และฐานข้อมูล slave สำหรับแอปพลิเคชัน PHP
<?php
/* Master settings */
$master_server = "xx.xxx.x.xx";
$master_username = "root";
$master_password = "abcABC123";

/* Slave settings */
$slave_server = "xx.xxx.x.xx";
$slave_username = "root";
$slave_password = "abcABC123";

$link_to_master = mysqli_connect(
$master_server,
$master_username,
$master_password,
'mysql');

if (!$link_to_master) {
printf("Unable to connect master database server. Error: %s\n", mysqli_connect_error());
exit;
}

$link_to_slave = mysqli_connect(
$slave_server,
$slave_username,
$slave_password,
'mysql');

if (!$link_to_slave) {
printf("Unable to connect slave database server. Error: %s\n", mysqli_connect_error());
exit;
}

print("
Creating database with name Jelastic on Master node ");

$result = mysqli_query($link_to_master, 'CREATE DATABASE JelasticX');

sleep (3);

print("
Checking if created database was replciated to slave ");

if ($result = mysqli_query($link_to_slave, 'SHOW DATABASES LIKE "JelasticX"')) {
$result_text = mysqli_fetch_array($result);
print ("
Replicated database is ".$result_text[0]);
}

mysqli_close($link_to_master);
mysqli_close($link_to_slave);
?>

ตอนนี้คุณมีการจำลองฐานข้อมูลของคุณเองในระบบคลาวด์แล้ว ขอให้สนุกไปกับมัน!