MySQL集群讀寫分離的自定義實現

来源:https://www.cnblogs.com/wy123/archive/2019/08/03/11295852.html
-Advertisement-
Play Games

基於MySQL Router可以實現高可用,讀寫分離,負載均衡之類的,MySQL Router可以說是非常輕量級的一個中間件了。看了一下MySQL Router的原理,其實並不複雜,原理也並不難理解,其實就是一個類似於VIP的代理功能,其中一個MySQL Router有兩個埠號,分別是對讀和寫的轉 ...


基於MySQL Router可以實現高可用,讀寫分離,負載均衡之類的,MySQL Router可以說是非常輕量級的一個中間件了。
看了一下MySQL Router的原理,其實並不複雜,原理也並不難理解,其實就是一個類似於VIP的代理功能,其中一個MySQL Router有兩個埠號,分別是對讀和寫的轉發。
至於選擇哪個埠號,需要在申請連接的時候自定義選擇,換句話說就是在生成連接字元串的時候,要指明是讀操作還是寫操作,然後由MySQL Router轉發到具體的伺服器上。

引用這裡的話說就是
一般來說,通過不同埠實現讀/寫分離,並非好方法,最大的原因是需要在應用程式代碼中指定這些連接埠。
但是,MySQL Router只能通過這種方式實現讀寫分離,所以MySQL Router拿來當玩具玩玩就好。其原理參考下圖,相關安裝配置等非常簡單

 

其實暫不論“MySQL Router拿來當玩具玩玩就好”,類似需要自己指定埠(或者說指定讀寫)來實現讀寫分離這種方式,自己完全可以實現,又何必用一個中間件呢?
對於MySQL Router來說,它自己本身又是單點的,還要考慮Router自身的高可用(解決了一個問題的同時又引入一個問題)。
很早之前就在想,可不可以嘗試不藉助中間件,也就無需關註中間件自身的高可用,自己實現讀寫分離呢?


對於最簡單的master-salve複製的集群方式的讀寫分離,
可以集群中的不同節點指定不同的優先順序,把master伺服器的優先順序指定到最高,其餘兩個指定成一個較低的優先順序
對於應用程式發起的請求,需要指明是讀還是寫,如果是寫操作,就指定到master上執行,如果是讀操作,就隨機地指向slave操作,完全可以在連接層就實現類似於MySQL Router的功能。
其實非常簡單,花不了多久就可以實現類似這麼一個功能,在連接層實現讀寫分離,高可用,負載均衡,demo一個代碼實現。

如下簡單從資料庫連接層實現了讀寫分離以及負載均衡。
1,寫請求指向連接字元串中最高優先順序的master,如果指定的最高優先順序實例不可用,這裡假如是實現了故障轉移,依次尋找次優先順序的實例
2,slave複製master的數據,讀請求隨機指向不同的slave,一旦某個slave不可用,繼續尋找其他的slave
3,維護一個連接池,連接一律從連接池中獲取。

故障轉移可以獨立實現,不需要在連接層做,連接層也不是做故障轉移的。這樣一旦發生故障,只要實現了故障轉移,應用程式端可以不用做任何修改。

# -*- coding: utf-8 -*-
import pymysql
import random
from DBUtils.PooledDB import PooledDB
import socket


class MySQLRouter:

    operation = None
    conn_list = []

    def __init__(self, *args, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

    # 探測實例埠號
    @staticmethod
    def get_mysqlservice_status(host,port):
        mysql_stat = None
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        result = s.connect_ex((host, int(port)))
        # port os open
        if (result == 0):
            mysql_stat = 1
        return mysql_stat

    def get_connection(self):
        if not conn_list:
            raise("no config error")

        conn = None
        current_conn = None
        # 依據節點優先順序排序
        self.conn_list.sort(key=lambda k: (k.get('priority', 0)))
        #寫或者未定義請求,一律指向高優先順序的伺服器,可讀寫
        if(self.operation.lower() == "write") or not self.operation:
            for conn in conn_list:
                # 如果最高優先順序的主節點不可達,這裡假設成功實現了故障轉移,繼續找次優先順序的實例。
                if self.get_mysqlservice_status(conn["host"], conn["port"]):
                    current_conn = conn
                    break
                else:
                    continue
        #讀請求隨機指向不同的slave
        elif(self.operation.lower() == "read"):
            #隨機獲取除了最該優先順序節點之外的節點
            conn_read_list = conn_list[1:len(conn_list)]
            random.shuffle(conn_read_list)
            for conn in conn_read_list:
                #如果不可達,繼續尋找其他除了主節點之外的節點
                if self.get_mysqlservice_status(conn["host"], conn["port"]):
                    current_conn = conn
                    break
                else:
                    continue
        try:
            #從連接池中獲取當前連接
            if (current_conn):
                pool = PooledDB(pymysql,20, host=current_conn["host"], port=current_conn["port"], user=current_conn["user"], password=current_conn["password"],db=current_conn["database"])
                conn = pool.connection()
        except:
            raise

        if not conn:
            raise("create connection error")

        return conn;


if __name__ == '__main__':

    #定義三個實例
    conn_1 = {'host': '127.0.0.1', 'port': 3306, 'user': 'root', 'password': 'root',"database":"db01","priority":100}
    conn_2 = {'host': '127.0.0.1', 'port': 3307, 'user': 'root', 'password': 'root',"database":"db01","priority":200}
    conn_3 = {'host': '127.0.0.1', 'port': 3308, 'user': 'root', 'password': 'root',"database":"db01","priority":300}
    
    conn_list = []
    conn_list.append(conn_1)
    conn_list.append(conn_2)
    conn_list.append(conn_3)

    print("####execute update on master####")
    myrouter = MySQLRouter(conn_list=conn_list, operation="write")
    conn = myrouter.get_connection()
    cursor = conn.cursor()
    cursor.execute("update t01 set update_date = now() where id = 1")
    conn.commit()
    cursor.close()
    conn.close()

    print("####loop execute read on slave,query result####")
    #迴圈讀,判斷讀指向哪個節點。
    for loop in range(10):
        myrouter = MySQLRouter(conn_list = conn_list,operation = "read")
        conn = myrouter.get_connection()
        cursor = conn.cursor()
        cursor.execute("SELECT id,cast(update_date as char), CONCAT('instance port is: ', CAST( @@PORT AS CHAR)) AS port FROM t01;")
        result = cursor.fetchone()
        print(result)
        cursor.close()
        conn.close()

這裡用過伺服器的一個優先順序,將寫請求指向最高優先順序的master伺服器,讀請求隨機指向非最高優先順序的slave,
對於更新請求,都在master上執行,slave複製了master的數據,每次讀到的數據都不一樣,並且每次都請求的執行,基本上都隨機地指向了兩台slave伺服器
通過查詢返回一個埠號,來判斷讀請求是否平均分散到了不通的slave端。

與“MySQL Router拿來當玩具玩玩就好”相比,這裡的實現一樣low,因為對數據的請求需要請求明確指定是讀還是寫。

不過,對於自動讀寫分離,無非是一個SQL語句執行的是的讀或寫判斷問題,並非難事,這個需要解析請求的SQL是讀的還是寫的問題。
某些資料庫中間件可以實現自動的讀寫分離,但是要明白,對於那些支持自動讀寫分離的中間件,往往是要受到一定的約束的,比如不能用存儲過程什麼的,為什麼呢?
還是上面提到的SQL解析的問題,因為一旦使用了存儲過程,無法解析出來這個SQL到底是執行的是讀還是寫,最起碼不是太直接。
對於SQL讀寫的判斷,也就是維護一個讀或者寫的枚舉正則表達式,非讀即寫,只是要格外關註這個讀寫的判斷的效率問題。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 環境:CentOS7 一.本地源的yum源的搭建 (一)添加新的yum源配置文件iso.repo(名字可以自己命名,但是尾碼必須是repo結尾) 註意:目錄 /etc/yum.repos.d 下的 .repo 文件將會被yum工具讀取 建議:只保留一個 .repo的文件。 [root@uplooki ...
  • CentOS6.4系統上安裝KVM虛擬機 備註:以下操作說明是經過實驗驗證後總結出來的筆錄,有需要的朋友可以進行參考,以下是基於VMware12.5.2虛擬機版本上安裝的實驗環境。 一、安裝KVM 1. kvm需要有 CPU 的支持(Intel VT 或 AMD SVM)。輸入命令:egrep '^ ...
  • 最近公司內部一個需求:必須 Linux建個 ntp server ,並且 Windows可以net time \\ip 訪問。 摸索之後,開工: ########### ntp server ip a # 本機IP 192.168.52.5 yum install -y ntp # 安裝服務 ntp ...
  • 購買 Hostwinds VPS 首先確認不要使用任何代理,網路是什麼 IP 就是什麼 IP ,不然可能需要人工審核,導致 Hostwinds VPS 購買顯示 "Pending" 狀態, 不能即時創建服務激活。 1、通過 Hostwinds 優惠鏈接進入Hostwinds 首頁,選擇 “VPS” ...
  • Linux常用目錄——存放 /bin 所有用戶可以使用的可執行文件 /sbin 新管理員使用的執行文件 /boot Linux內核映像文件和與引導載入有關的文件 /dev 設備文件 /etc 系統配置文件 /mnt 掛載點,常用於掛載文件系統 /lib 共用庫文件 /proc 基於記憶體的文件系統,用 ...
  • CRT遠程連接centos7,連接超時 問題原因: 宿主機(win10)和虛擬機(centos7)不在同一個網段 在宿主機無法ping通虛擬機, 首先在cmd視窗ipconfig查看一下vmnet的ip地址 然後在centos使用命令ifconfig查看ip地址 現在宿主機和虛擬機在同一個網段,所以 ...
  • 本文是記錄一下學習docker的過程,希望可以幫助到入門的朋友。 系統:ubuntu16.04 docker:18.09 打開官網:https://docs.docker.com/install/linux/docker-ce/ubuntu/ OS requirements To install D ...
  • 1 拋棄舊文化,迎接Linux命令新文化 Linux第一步,從Windows思維,切換到Linux的“命令行+文件”模式 在Linux中,做什麼都有相應命令。一般就在bin或者sbin目錄下,數量繁多。如果你事先不知道該用哪個命令,很難通過枚舉的方式找到。因此,在這樣沒有統一入口的情況下,就需要你對 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...