程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> MYSQL數據庫 >> MySQL綜合教程 >> MySQL垂直切分(讀書筆記整理)

MySQL垂直切分(讀書筆記整理)

編輯:MySQL綜合教程

MySQL垂直切分(讀書筆記整理)


1,垂直拆分

相對於水平拆分來說,垂直拆分比較容易實現一些,垂直拆分的意思是把數據庫中不同的業務數據拆分到不同的數據庫中。垂直拆分能很好的起到分散數據庫壓力的作用。業務模塊不明晰,耦合(表關聯)度比較高的系統不適合使用這種拆分方式。

有得用戶查詢積分快,有的用戶查詢自己的訂單很快,但是查詢自己的用戶信息很慢,為什麼?

2,垂直切分的優點

◆ 數據庫的拆分簡單明了,拆分規則明確;
◆ 應用程序模塊清晰明確,整合容易;
◆ 數據維護方便易行,容易定位;


<版權所有,文章允許轉載,但必須以鏈接方式注明源地址,否則追究法律責任!>
原博客地址: http://blog.csdn.net/mchdba/article/details/46278687
原作者:黃杉 (mchdba)


3,垂直切分的缺點

◆ 部分表關聯無法在數據庫級別完成,需要在程序中完成;
◆ 對於訪問極其頻繁且數據量超大的表仍然存在性能平靜,不一定能滿足要求;
◆ 事務處理相對更為復雜;
◆ 切分達到一定程度之後,擴展性會遇到限制;
◆ 過讀切分可能會帶來系統過渡復雜而難以維護。

針對於垂直切分可能遇到數據切分及事務問題,在數據庫層面實在是很難找到一個較好的處理方案。實際應用案例中,數據庫的垂直切分大多是與應用系統的模塊相對應,同一個模塊的數據源存放於同一個數據庫中,可以解決模塊內部的數據關聯問題。而模塊與模塊之間,則通過應用程序以服務接口方式來相互提供所需要的數據。雖然這樣做在數據庫的總體操作次數方面確實會有所增加,但是在系統整體擴展性以及架構模塊化方面,都是有益的。

可能在某些操作的單次響應時間會稍有增加,但是系統的整體性能很可能反而會有一定的提升。而擴展瓶頸問題。

4,拆分規則

根據模塊拆分,比如用戶模塊、訂單模塊、日志模塊,系統參數模塊
用戶模塊的表:uc_user表;uc_user_info表;uc_action表;uc_profile表
訂單模塊表:order_action表;order_list表;shop表;order表;
日志模塊:order_log表;uc_log表;plocc_log表;
系統參數模塊:plocc_parameter表;

模塊模塊之間都一看都是有聯系的,這些都是用到用戶的,那麼都和用戶模塊有關聯

如下圖所示:
這裡寫圖片描述


<喎?http://www.Bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KCjxoMSBpZD0="5垂直拆分演示">5,垂直拆分演示

5.1准備mysql環境

創建多實例參考:http://blog.csdn.net/mchdba/article/details/45798139

建庫sql語句:

--1 用戶模塊 3307端口數據庫
    create database user_db;
    CREATE TABLE user_db.`uc_user` (
      `user_id` bigint(20) NOT NULL,
      `uc_name` varchar(200) DEFAULT NULL,
      `created_time` datetime DEFAULT NULL,
      PRIMARY KEY (`user_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

    grant insert,update,delete,select on user_db.* to tim@'192.168.%' identified by 'timgood2013';

-- 查詢
    mysql --socket=/usr/local/mysql3307/mysql.sock -e "select * from user_db.uc_user;";


--2 訂單模塊 3308端口數據庫
    create database order_db;
    CREATE TABLE order_db.`order` (
      `order_id` bigint(20) NOT NULL,
      `shop_id` varchar(200) DEFAULT NULL,
      `created_time` datetime DEFAULT NULL,
      `user_id` bigint(20) not null,
      PRIMARY KEY (`order_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

    grant insert,update,delete,select on order_db.* to tim@'192.168.%' identified by 'timgood2013';
-- 查詢
    mysql --socket=/usr/local/mysql3308/mysql.sock -e "select * from order_db.order;";


--3 日志模塊 3309端口數據庫
    create database log_db;
    CREATE TABLE log_db.`order_log` (
      `orlog_id` bigint(20) NOT NULL,
      `order_id` varchar(200) DEFAULT NULL,
      `created_time` datetime DEFAULT NULL,
      `user_id` bigint(20) not null,
      `action` varchar(2000) not null,
      PRIMARY KEY (`orlog_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

    grant insert,update,delete,select on log_db.* to tim@'192.168.%' identified by 'timgood2013';

-- 查詢   
    mysql --socket=/usr/local/mysql3309/mysql.sock -e "select * from log_db.order_log;";

--4 參數模塊 33310端口數據庫
    create database plocc_db;
    CREATE TABLE plocc_db.`plocc_parameter` (
      `plocc_id` bigint(20) NOT NULL,
      `pname` varchar(200) DEFAULT NULL,
      `created_time` datetime DEFAULT NULL,
      `status` bigint(20) not null,
      `creator` bigint not null,
      PRIMARY KEY (`plocc_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

    grant insert,update,delete,select on plocc_db.* to tim@'192.168.%' identified by 'timgood2013';

--

5.2 演示java代碼

package mysql;

import java.math.BigInteger;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class MySQLTest2 {

    public static long user_id;
    public static void main(String[] args) {

        MySQLTest2 mt=new MySQLTest2();

        /* 錄入用戶數據
        BigInteger user_id0 = new BigInteger("10001");
        Connection conn=mt.getConn("user_db");
        mt.insertUser(conn, bi, "tim--"+user_id0.longValue());
        */

        /* 錄入日志數據
        Connection conn2=mt.getConn("log_db");
        BigInteger user_id = new BigInteger("10001");
        BigInteger order_id = new BigInteger("20150531001");
        BigInteger orlog_id = new BigInteger("20150531001");
        mt.insertLog(conn2, user_id,order_id , orlog_id, "create a order for tim");
        */

        //錄入訂單數據
        Connection conn3=mt.getConn("order_db");
        BigInteger user_id2 = new BigInteger("10001");
        BigInteger order_id2 = new BigInteger("20150531001");
        BigInteger shop_id2 = new BigInteger("20150531001");
        mt.insertOrder(conn3,order_id2 , shop_id2, user_id2);



    }




    // 獲取數據庫的連接,如果擴展的話,可以單獨做一個接口提供給程序員來調用它
    // String type:連接的數據類型
    public Connection getConn(String type ) {
        String port="3307";
        if (type=="user_db" ){
             port="3307";
        }else if(type=="order_db"){
             port="3308";
        }else if(type=="log_db"){
             port="3309";
        }else if(type=="plocc_db") {
            port="3310";
        }else{
            port="3311";
        }

        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        String url = "jdbc:mysql://192.168.52.130:"+port+"/"+type;
        try {
            conn = DriverManager.getConnection(url, "tim", "timgood2013");
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("the current db is :"+url);
        return conn;
    }

    // 獲取日期字符串
    public String getTimeByCalendar(){
        /*
        Calendar cal = Calendar.getInstance();
        int year = cal.get(Calendar.YEAR);//獲取年份
        int month=cal.get(Calendar.MONTH);//獲取月份
        int day=cal.get(Calendar.DATE);//獲取日
        int hour=cal.get(Calendar.HOUR);//小時
        int minute=cal.get(Calendar.MINUTE);//分           
        int second=cal.get(Calendar.SECOND);//秒
        String strdate=year+"-"+month+"-"+day+" "+hour+":"+minute+":"+second;
        */
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//設置日期格式
        System.out.println(df.format(new Date()));// new Date()為獲取當前系統時間
        return df.format(new Date());
    }


    // 開始錄入用戶數據
    public int insertUser(Connection cnn,BigInteger user_id,String name){
        String sql="insert into user_db.uc_user(user_id,uc_name,created_time)values(?,?,?)";
        int i=0;  
        long uid = user_id.longValue();

        Connection conn=cnn;
        try{  
            PreparedStatement preStmt =conn.prepareStatement(sql);  
            preStmt.setLong(1, uid);
            preStmt.setString(2,name);
            preStmt.setString(3,getTimeByCalendar());

            i=preStmt.executeUpdate();  
        }  
        catch (SQLException e)  
        {  
            e.printStackTrace();  
        }  
        return i;//返回影響的行數,1為執行成功  

    }

    // 開始錄入日志數據
    public int insertLog(Connection cnn,BigInteger user_id,BigInteger order_id,BigInteger orlog_id,String action){
        String sql="insert into log_db.order_log(orlog_id,order_id,created_time,user_id,action)values(?,?,?,?,?)";
        int i=0;  

        Connection conn=cnn;
        try{  
            PreparedStatement preStmt =conn.prepareStatement(sql);  
            preStmt.setLong(1, user_id.longValue());
            preStmt.setLong(2, order_id.longValue());
            preStmt.setString(3,getTimeByCalendar());
            preStmt.setLong(4, orlog_id.longValue());
            preStmt.setString(5,action);            

            i=preStmt.executeUpdate();  
        }  
        catch (SQLException e)  
        {  
            e.printStackTrace();  
        }  
        return i;//返回影響的行數,1為執行成功  

    }


    // 開始錄入訂單數據
    public int insertOrder(Connection cnn,BigInteger order_id,BigInteger shop_id,BigInteger user_id){
        String sql="insert into order_db.order(order_id,shop_id,created_time,user_id)values(?,?,?,?)";
        int i=0;  

        Connection conn=cnn;
        try{  
            PreparedStatement preStmt =conn.prepareStatement(sql);  
            preStmt.setLong(1, order_id.longValue());
            preStmt.setLong(2, shop_id.longValue());
            preStmt.setString(3,getTimeByCalendar());
            preStmt.setLong(4, user_id.longValue());

            i=preStmt.executeUpdate();  
        }  
        catch (SQLException e)  
        {  
            e.printStackTrace();  
        }  
        return i;//返回影響的行數,1為執行成功  

    }   


    //開始錄入參數數據
    public int insertPlocc(){
        int i=0;
        return i;
    }
}


6,水平拆分和垂直拆分的結合

我們分別,了解了“垂直”和“水平”這兩種切分方式的實現以及切分之後的架構信息,同時也分析了兩種架構各自的優缺點。但是在實際的應用場景中,除了那些負載並不是太大,業務邏輯也相對較簡單的系統可以通過上面兩種切分方法之一來解決擴展性問題之外,恐怕其他大部分業務邏輯稍微復雜一點,系統負載大一些的系統,都無法通過上面任何一種數據的切分方法來實現較好的擴展性,而需要將上述兩種切分方法結合使用,不同的場景使用不同的切分方法。

將結合垂直切分和水平切分各自的優缺點,進一步完善我們的整體架構,讓系統的擴展性進一步提高。

一般來說,我們數據庫中的所有表很難通過某一個(或少數幾個)字段全部關聯起來,所以很難簡單的僅僅通過數據的水平切分來解決所有問題。而垂直切分也只能解決部分問題,對於那些負載非常高的系統,即使僅僅只是單個表都無法通過單台數據庫主機來承擔其負載。我們必須結合“垂直”和“水平”兩種切分方式同時使用,充分利用兩者的優點,避開其缺點。

每一個應用系統的負載都是一步一步增長上來的,在開始遇到性能瓶頸的時候,大多數架構師和DBA 都會選擇先進行數據的垂直拆分,因為這樣的成本最先,最符合這個時期所追求的最大投入產出比。然而,隨著業務的不斷擴張,系統負載的持續增長,在系統穩定一段時期之後,經過了垂直拆分之後的數據庫集群可能又再一次不堪重負,遇到了性能瓶頸。

這時候我們該如何抉擇?是再次進一步細分模塊呢,還是尋求其他的辦法來解決?如果我們再一次像最開始那樣繼續細分模塊,進行數據的垂直切分,那我們可能在不久的將來,又會遇到現在所面對的同樣的問題。而且隨著模塊的不斷的細化,應用系統的架構也會越來越復雜,整個系統很可能會出現失控的局面。

這時候我們就必須要通過數據的水平切分的優勢,來解決這裡所遇到的問題。而且,我們完全不必要在使用數據水平切分的時候,推倒之前進行數據垂直切分的成果,而是在其基礎上利用水平切分的優勢來避開垂直切分的弊端,解決系統復雜性不斷擴大的問題。而水平拆分的弊端(規則難以統一)也已經被之前的垂直切分解決掉了,讓水平拆分可以進行的得心應手。

對於我們的示例數據庫,假設在最開始,我們進行了數據的垂直切分,然而隨著業務的不斷增長,數據庫系統遇到了瓶頸,我們選擇重構數據庫集群的架構。如何重構?考慮到之前已經做好了數據的垂直切分,而且模塊結構清晰明確。而業務增長的勢頭越來越猛,即使現在進一步再次拆分模塊,也堅持不了太久。我們選擇了在垂直切分的基礎上再進行水平拆分。

在經歷過垂直拆分後的各個數據庫集群中的每一個都只有一個功能模塊,而每個功能模塊中的所有表基本上都會與某個字段進行關聯。如用戶模塊全部都可以通過用戶ID 進行切分,群組討論模塊則都通過群組ID 來切分,相冊模塊則根據相冊ID 來進切分,最後的事件通知信息表考慮到數據的時限性(僅僅只會訪問最近某個事件段的信息),則考慮按時間來切分。

如下圖所示:
這裡寫圖片描述

實際上,在很多大型的應用系統中,垂直切分和水平切這兩種數據的切分方法基本上都是並存的,而且經常在不斷的交替進行,以不斷的增加系統的擴展能力。我們在應對不同的應用場景的時候,也需要充分考慮到這兩種切分方法各自的局限,以及各自的優勢,在不同的時期(負載壓力)使用不同的結合方式。

聯合切分的優點
◆ 可以充分利用垂直切分和水平切分各自的優勢而避免各自的缺陷;
◆ 讓系統擴展性得到最大化提升;
聯合切分的缺點
◆ 數據庫系統架構比較復雜,維護難度更大;
◆ 應用程序架構也相對更復雜;


7,數據切分整合方案

通過數據庫的數據切分可以極大的提高系統的擴展性。但是,數據庫中的數據在經過垂直和(或)水平切分被存放在不同的數據庫主機之後,應用系統面臨的最大問題就是如何來讓這些數據源得到較好的整合,可能這也是很多讀者朋友非常關心的一個問題。這一節我們主要針對的內容就是分析可以使用的各種可以幫助我們實現數據切分以及數據整合的整體解決方案。

數據的整合很難依靠數據庫本身來達到這個效果,雖然MySQL 存在Federated 存儲引擎,可以解決部分類似的問題,但是在實際應用場景中卻很難較好的運用。那我們該如何來整合這些分散在各個MySQL 主機上面的數據源呢?
總的來說,存在兩種解決思路:
1. 在每個應用程序模塊中配置管理自己需要的一個(或者多個)數據源,直接訪問各個數據庫,在模塊內完成數據的整合;
2. 通過中間代理層來統一管理所有的數據源,後端數據庫集群對前端應用程序透明;可能90%以上的人在面對上面這兩種解決思路的時候都會傾向於選擇第二種,尤其是系統不斷變得龐大復雜的時候。確實,這是一個非常正確的選擇,雖然短期內需要付出的成本可能會相對更大一些,但是對整個系統的擴展性來說,是非常有幫助的。

可能90%以上的人在面對上面這兩種解決思路的時候都會傾向於選擇第二種,尤其是系統不斷變得龐大復雜的時候。確實,這是一個非常正確的選擇,雖然短期內需要付出的成本可能會相對更大一些,但是對整個系統的擴展性來說,是非常有幫助的。所以,對於第一種解決思路我這裡就不准備過多的分析,下面我重點分析一下在第二種解決思路中的一些解決方案。

7.1 自行開發中間代理層

在決定選擇通過數據庫的中間代理層來解決數據源整合的架構方向之後,有不少公司(或者企業)選擇了通過自行開發符合自身應用特定場景的代理層應用程序。通過自行開發中間代理層可以最大程度的應對自身應用的特定,最大化的定制很多個性化需求,在面對變化的時候也可以靈活的應對。這應該說是自行開發代理層最大的優勢了。

當然,選擇自行開發,享受讓個性化定制最大化的樂趣的同時,自然也需要投入更多的成本來進行前期研發以及後期的持續升級改進工作,而且本身的技術門檻可能也比簡單的Web 應用要更高一些。所以,在決定選擇自行開發之前,還是需要進行比較全面的評估為好。由於自行開發更多時候考慮的是如何更好的適應自身應用系統,應對自身的業務場景,所以這裡也不好分析太多。後面我們主要分析一下當前比較流行的幾種數據源整合解決方案。

7.2利用MySQL Proxy 實現數據切分及整合

MySQL Proxy 是MySQL 官方提供的一個數據庫代理層產品,和MySQL Server 一樣,同樣是一個基於GPL 開源協議的開源產品。可用來監視、分析或者傳輸他們之間的通訊信息。他的靈活性允許你最大限度的使用它,目前具備的功能主要有連接路由,Query 分析,Query 過濾和修改,負載均衡,以及基本的HA 機制等。
實際上,MySQL Proxy 本身並不具有上述所有的這些功能,而是提供了實現上述功能的基礎。要實現這些功能,還需要通過我們自行編寫LUA 腳本來實現。MySQL Proxy 實際上是在客戶端請求與MySQL Server 之間建立了一個連接池。所有客戶端請求都是發向MySQL Proxy,然後經由MySQL Proxy 進行相應的分析,判斷出是讀操作還是寫操作,分發至對應的MySQL Server 上。對於多節點Slave 集群,也可以起做到負載均衡的效果。

7.3利用Amoeba 實現數據切分及整合

Amoeba 是一個基於Java 開發的,專注於解決分布式數據庫數據源整合Proxy 程序的開源框架,基於GPL3 開源協議。目前,Amoeba 已經具有Query 路由,Query 過濾,讀寫分離,負載均衡以及HA 機制等相關內容。Amoeba 主要解決的以下幾個問題:
1. 數據切分後復雜數據源整合;
2. 提供數據切分規則並降低數據切分規則給數據庫帶來的影響;
3. 降低數據庫與客戶端的連接數;
4. 讀寫分離路由;

我們可以看出,Amoeba 所做的事情,正好就是我們通過數據切分來提升數據庫的擴展性所需要的。

7.4 利用HiveDB 實現數據切分及整合

和前面的MySQL Proxy 以及Amoeba 一樣,HiveDB 同樣是一個基於Java 針對MySQL數據庫的提供數據切分及整合的開源框架,只是目前的HiveDB 僅僅支持數據的水平切分。主要解決大數據量下數據庫的擴展性及數據的高性能訪問問題,同時支持數據的冗余及基本的HA 機制。
HiveDB 的實現機制與MySQL Proxy 和Amoeba 有一定的差異,他並不是借助MySQL的Replication 功能來實現數據的冗余,而是自行實現了數據冗余機制,而其底層主要是基於Hibernate Shards 來實現的數據切分工作。

在HiveDB 中,通過用戶自定義的各種Partition k e y s(其實就是制定數據切分規則),將數據分散到多個MySQL Server 中。在訪問的時候,在運行Query 請求的時候,會自動分析過濾條件,並行從多個MySQL Server 中讀取數據,並合並結果集返回給客戶端應用程序。

單純從功能方面來講,HiveDB 可能並不如MySQL Proxy 和Amoeba 那樣強大,但是其數據切分的思路與前面二者並無本質差異。此外,HiveDB 並不僅僅只是一個開源愛好者所共享的內容,而是存在商業公司支持的開源項目。

7.5 其他實現數據切分及整合的解決方案

除了上面介紹的幾個數據切分及整合的整體解決方案之外,還存在很多其他同樣提供了數據切分與整合的解決方案。如基於MySQL Proxy 的基礎上做了進一步擴展的HSCALE,通過Rails 構建的Spock Proxy,以及基於Pathon 的Pyshards 等等。不管大家選擇使用哪一種解決方案,總體設計思路基本上都不應該會有任何變化,那就是通過數據的垂直和水平切分,增強數據庫的整體服務能力,讓應用系統的整體擴展能力盡可能的提升,擴展方式盡可能的便捷。

只要我們通過中間層Proxy 應用程序較好的解決了數據切分和數據源整合問題,那麼數據庫的線性擴展能力將很容易做到像我們的應用程序一樣方便,只需要通過添加廉價的PC Server 服務器,即可線性增加數據庫集群的整體服務能力,讓數據庫不再輕易成為應用系統的性能瓶頸。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved