1. 程式人生 > >mysql主備模式的讀寫分離與主從複製

mysql主備模式的讀寫分離與主從複製

從資料庫層面上對負載做優化的方法各式各樣。從書上看到分表分庫等常見手段,後來學習別人部落格才明白分表分庫各自天生缺點使他無法成為主流而並沒有那麼多人用,正好畢業設計想嘗試一下主從的架構,所以這裡一邊搭建一邊做記錄。

目錄:

1.介紹

2.應用層面實現mysql讀寫分離

3.mycat實現mysql讀寫分離

4.mysql proxy實現mysql讀寫分離

5.amoeba實現mysql讀寫分離

6.mysql的自帶主從複製搭建

<!--分割線-->

1.介紹

讀寫分離: 讓master(主資料庫)來響應事務性操作,讓slave(從資料庫)來響應select非事務性操作,然後再採用主從複製來把master上的事務性操作同步到slave資料庫中。在多讀少寫(大部分web專案)的場景下效能優化明顯。 系統結構

讀寫分離的好處: 增加了資料的冗餘 降低單個節點的負載 優化方式: 可根據實際業務自由變化,比如可以增加從庫對併發效能做橫向擴充套件,比如對於讀寫平衡的專案可以主主互備(相互複製) 由於讀寫分離去除了讀寫鎖的競爭,提升讀寫效率 可以各自根據需求配置或新開發更優化的資料庫引擎,比如讀庫可以使用讀效能更高的MyIsam引擎 由於從庫根據主庫的binlog來資料同步(資料同步是非同步操作不會阻塞業務)

2.應用層實現mysql讀寫分離

實現思路: 其實就是在應用伺服器裡面判斷某一個sql語句是讀還是寫,然後分別傳送到對應的資料庫中去。 例如:在spring專案中配置多個數據源,使用aop切入點根據save*、update*、delete*等方法呼叫master資料來源,根據find*、get*、query*來呼叫slave資料來源。基本上重點就在於動態切換資料來源上面,其他就沒什麼問題。 優缺點
: 優點在於不需要依賴於相關中介軟體,理論上支援各種型別資料庫,多資料來源由程式自動切換 缺點在於運維難以涉足,且增加資料來源不能自動完成 具體實現參考連結 (1)DynamicDataSource
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;  
  
/** 
 * 定義動態資料來源,實現通過整合Spring提供的AbstractRoutingDataSource,只需要實現determineCurrentLookupKey方法即可 
 *  
 * 由於DynamicDataSource是單例的,執行緒不安全的,所以採用ThreadLocal保證執行緒安全,由DynamicDataSourceHolder完成。 
 */  
public class DynamicDataSource extends AbstractRoutingDataSource{  
  
    @Override  
    protected Object determineCurrentLookupKey() {  
        // 使用DynamicDataSourceHolder保證執行緒安全,並且得到當前執行緒中的資料來源key  
        return DynamicDataSourceHolder.getDataSourceKey();  
    }  
  
}  
(2)DynamicDataSourceHolder
/** 
 *  
 * 使用ThreadLocal技術來記錄當前執行緒中的資料來源的key 
 *  
 * @author zhijun 
 * 
 */  
public class DynamicDataSourceHolder {  
      
    //寫庫對應的資料來源key  
    private static final String MASTER = "master";  
  
    //讀庫對應的資料來源key  
    private static final String SLAVE = "slave";  
      
    //使用ThreadLocal記錄當前執行緒的資料來源key  
    private static final ThreadLocal<String> holder = new ThreadLocal<String>();  
  
    /** 
     * 設定資料來源key 
     * @param key 
     */  
    public static void putDataSourceKey(String key) {  
        holder.set(key);  
    }  
  
    /** 
     * 獲取資料來源key 
     * @return 
     */  
    public static String getDataSourceKey() {  
        return holder.get();  
    }  
      
    /** 
     * 標記寫庫 
     */  
    public static void markMaster(){  
        putDataSourceKey(MASTER);  
    }  
      
    /** 
     * 標記讀庫 
     */  
    public static void markSlave(){  
        putDataSourceKey(SLAVE);  
    }  
  
} 

(3)DataSourceAspect
import org.apache.commons.lang3.StringUtils;  
import org.aspectj.lang.JoinPoint;  
  
/** 
 * 定義資料來源的AOP切面,通過該Service的方法名判斷是應該走讀庫還是寫庫 
 *  
 * @author zhijun 
 * 
 */  
public class DataSourceAspect {  
  
    /** 
     * 在進入Service方法之前執行 
     *  
     * @param point 切面物件 
     */  
    public void before(JoinPoint point) {  
        // 獲取到當前執行的方法名  
        String methodName = point.getSignature().getName();  
        if (isSlave(methodName)) {  
            // 標記為讀庫  
            DynamicDataSourceHolder.markSlave();  
        } else {  
            // 標記為寫庫  
            DynamicDataSourceHolder.markMaster();  
        }  
    }  
  
    /** 
     * 判斷是否為讀庫 
     *  
     * @param methodName 
     * @return 
     */  
    private Boolean isSlave(String methodName) {  
        // 方法名以query、find、get開頭的方法名走從庫  
        return StringUtils.startsWithAny(methodName, "query", "find", "get");  
    }  
  
}

(4)jdbc.properties
jdbc.master.driver=com.mysql.jdbc.Driver  
jdbc.master.url=jdbc:mysql://127.0.0.1:3306/mybatis_1128?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true  
jdbc.master.username=root  
jdbc.master.password=123456  
  
  
jdbc.slave01.driver=com.mysql.jdbc.Driver  
jdbc.slave01.url=jdbc:mysql://127.0.0.1:3307/mybatis_1128?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true  
jdbc.slave01.username=root  
jdbc.slave01.password=123456 

(5)spring-context.xml 配置資料量連線池、資料來源
<!-- 配置連線池 -->  
    <bean id="masterDataSource" class="com.jolbox.bonecp.BoneCPDataSource"  
        destroy-method="close">  
        <!-- 資料庫驅動 -->  
        <property name="driverClass" value="${jdbc.master.driver}" />  
        <!-- 相應驅動的jdbcUrl -->  
        <property name="jdbcUrl" value="${jdbc.master.url}" />  
        <!-- 資料庫的使用者名稱 -->  
        <property name="username" value="${jdbc.master.username}" />  
        <!-- 資料庫的密碼 -->  
        <property name="password" value="${jdbc.master.password}" />  
        <!-- 檢查資料庫連線池中空閒連線的間隔時間,單位是分,預設值:240,如果要取消則設定為0 -->  
        <property name="idleConnectionTestPeriod" value="60" />  
        <!-- 連線池中未使用的連結最大存活時間,單位是分,預設值:60,如果要永遠存活設定為0 -->  
        <property name="idleMaxAge" value="30" />  
        <!-- 每個分割槽最大的連線數 -->  
        <property name="maxConnectionsPerPartition" value="150" />  
        <!-- 每個分割槽最小的連線數 -->  
        <property name="minConnectionsPerPartition" value="5" />  
    </bean>  
      
    <!-- 配置連線池 -->  
    <bean id="slave01DataSource" class="com.jolbox.bonecp.BoneCPDataSource"  
        destroy-method="close">  
        <!-- 資料庫驅動 -->  
        <property name="driverClass" value="${jdbc.slave01.driver}" />  
        <!-- 相應驅動的jdbcUrl -->  
        <property name="jdbcUrl" value="${jdbc.slave01.url}" />  
        <!-- 資料庫的使用者名稱 -->  
        <property name="username" value="${jdbc.slave01.username}" />  
        <!-- 資料庫的密碼 -->  
        <property name="password" value="${jdbc.slave01.password}" />  
        <!-- 檢查資料庫連線池中空閒連線的間隔時間,單位是分,預設值:240,如果要取消則設定為0 -->  
        <property name="idleConnectionTestPeriod" value="60" />  
        <!-- 連線池中未使用的連結最大存活時間,單位是分,預設值:60,如果要永遠存活設定為0 -->  
        <property name="idleMaxAge" value="30" />  
        <!-- 每個分割槽最大的連線數 -->  
        <property name="maxConnectionsPerPartition" value="150" />  
        <!-- 每個分割槽最小的連線數 -->  
        <property name="minConnectionsPerPartition" value="5" />  
    </bean>  

<!-- 定義資料來源,使用自己實現的資料來源 -->  
    <bean id="dataSource" class="cn.itcast.usermanage.spring.DynamicDataSource">  
        <!-- 設定多個數據源 -->  
        <property name="targetDataSources">  
            <map key-type="java.lang.String">  
                <!-- 這個key需要和程式中的key一致 -->  
                <entry key="master" value-ref="masterDataSource"/>  
                <entry key="slave" value-ref="slave01DataSource"/>  
            </map>  
        </property>  
        <!-- 設定預設的資料來源,這裡預設走寫庫 -->  
        <property name="defaultTargetDataSource" ref="masterDataSource"/>  
    </bean>  

(6)spring-context.xml
配置事務管理器、自定義切面實現
<!-- 定義事務管理器 -->  
    <bean id="transactionManager"  
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
        <property name="dataSource" ref="dataSource" />  
    </bean>

<!-- 定義事務策略 -->  
    <tx:advice id="txAdvice" transaction-manager="transactionManager">  
        <tx:attributes>  
            <!--定義查詢方法都是隻讀的 -->  
            <tx:method name="query*" read-only="true" />  
            <tx:method name="find*" read-only="true" />  
            <tx:method name="get*" read-only="true" />  
  
            <!-- 主庫執行操作,事務傳播行為定義為預設行為 -->  
            <tx:method name="save*" propagation="REQUIRED" />  
            <tx:method name="update*" propagation="REQUIRED" />  
            <tx:method name="delete*" propagation="REQUIRED" />  
  
            <!--其他方法使用預設事務策略 -->  
            <tx:method name="*" />  
        </tx:attributes>  
    </tx:advice>  

<!-- 定義AOP切面處理器 -->  
    <bean class="cn.itcast.usermanage.spring.DataSourceAspect" id="dataSourceAspect">  
        <!-- 指定事務策略 -->  
        <property name="txAdvice" ref="txAdvice"/>  
        <!-- 指定slave方法的字首(非必須) -->  
        <property name="slaveMethodStart" value="query,find,get"/>  
    </bean>  

(7)DataSourceAspect
import java.lang.reflect.Field;  
import java.util.ArrayList;  
import java.util.List;  
import java.util.Map;  
  
import org.apache.commons.lang3.StringUtils;  
import org.aspectj.lang.JoinPoint;  
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;  
import org.springframework.transaction.interceptor.TransactionAttribute;  
import org.springframework.transaction.interceptor.TransactionAttributeSource;  
import org.springframework.transaction.interceptor.TransactionInterceptor;  
import org.springframework.util.PatternMatchUtils;  
import org.springframework.util.ReflectionUtils;  
  
/** 
 * 定義資料來源的AOP切面,該類控制了使用Master還是Slave。 
 *  
 * 如果事務管理中配置了事務策略,則採用配置的事務策略中的標記了ReadOnly的方法是用Slave,其它使用Master。 
 *  
 * 如果沒有配置事務管理的策略,則採用方法名匹配的原則,以query、find、get開頭方法用Slave,其它用Master。 
 *  
 * @author zhijun 
 * 
 */  
public class DataSourceAspect {  
  
    private List<String> slaveMethodPattern = new ArrayList<String>();  
      
    private static final String[] defaultSlaveMethodStart = new String[]{ "query", "find", "get" };  
      
    private String[] slaveMethodStart;  
  
    /** 
     * 讀取事務管理中的策略 
     *  
     * @param txAdvice 
     * @throws Exception 
     */  
    @SuppressWarnings("unchecked")  
    public void setTxAdvice(TransactionInterceptor txAdvice) throws Exception {  
        if (txAdvice == null) {  
            // 沒有配置事務管理策略  
            return;  
        }  
        //從txAdvice獲取到策略配置資訊  
        TransactionAttributeSource transactionAttributeSource = txAdvice.getTransactionAttributeSource();  
        if (!(transactionAttributeSource instanceof NameMatchTransactionAttributeSource)) {  
            return;  
        }  
        //使用反射技術獲取到NameMatchTransactionAttributeSource物件中的nameMap屬性值  
        NameMatchTransactionAttributeSource matchTransactionAttributeSource = (NameMatchTransactionAttributeSource) transactionAttributeSource;  
        Field nameMapField = ReflectionUtils.findField(NameMatchTransactionAttributeSource.class, "nameMap");  
        nameMapField.setAccessible(true); //設定該欄位可訪問  
        //獲取nameMap的值  
        Map<String, TransactionAttribute> map = (Map<String, TransactionAttribute>) nameMapField.get(matchTransactionAttributeSource);  
  
        //遍歷nameMap  
        for (Map.Entry<String, TransactionAttribute> entry : map.entrySet()) {  
            if (!entry.getValue().isReadOnly()) {//判斷之後定義了ReadOnly的策略才加入到slaveMethodPattern  
                continue;  
            }  
            slaveMethodPattern.add(entry.getKey());  
        }  
    }  
  
    /** 
     * 在進入Service方法之前執行 
     *  
     * @param point 切面物件 
     */  
    public void before(JoinPoint point) {  
        // 獲取到當前執行的方法名  
        String methodName = point.getSignature().getName();  
  
        boolean isSlave = false;  
  
        if (slaveMethodPattern.isEmpty()) {  
            // 當前Spring容器中沒有配置事務策略,採用方法名匹配方式  
            isSlave = isSlave(methodName);  
        } else {  
            // 使用策略規則匹配  
            for (String mappedName : slaveMethodPattern) {  
                if (isMatch(methodName, mappedName)) {  
                    isSlave = true;  
                    break;  
                }  
            }  
        }  
  
        if (isSlave) {  
            // 標記為讀庫  
            DynamicDataSourceHolder.markSlave();  
        } else {  
            // 標記為寫庫  
            DynamicDataSourceHolder.markMaster();  
        }  
    }  
  
    /** 
     * 判斷是否為讀庫 
     *  
     * @param methodName 
     * @return 
     */  
    private Boolean isSlave(String methodName) {  
        // 方法名以query、find、get開頭的方法名走從庫  
        return StringUtils.startsWithAny(methodName, getSlaveMethodStart());  
    }  
  
    /** 
     * 萬用字元匹配 
     *  
     * Return if the given method name matches the mapped name. 
     * <p> 
     * The default implementation checks for "xxx*", "*xxx" and "*xxx*" matches, as well as direct 
     * equality. Can be overridden in subclasses. 
     *  
     * @param methodName the method name of the class 
     * @param mappedName the name in the descriptor 
     * @return if the names match 
     * @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String) 
     */  
    protected boolean isMatch(String methodName, String mappedName) {  
        return PatternMatchUtils.simpleMatch(mappedName, methodName);  
    }  
  
    /** 
     * 使用者指定slave的方法名字首 
     * @param slaveMethodStart 
     */  
    public void setSlaveMethodStart(String[] slaveMethodStart) {  
        this.slaveMethodStart = slaveMethodStart;  
    }  
  
    public String[] getSlaveMethodStart() {  
        if(this.slaveMethodStart == null){  
            // 沒有指定,使用預設  
            return defaultSlaveMethodStart;  
        }  
        return slaveMethodStart;  
    }  
      
}

一主多從支援: 修改上面DynamicDataSource,採用輪詢方式返回讀庫的key
import java.lang.reflect.Field;  
import java.util.ArrayList;  
import java.util.List;  
import java.util.Map;  
import java.util.concurrent.atomic.AtomicInteger;  
  
import javax.sql.DataSource;  
  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;  
import org.springframework.util.ReflectionUtils;  
  
/** 
 * 定義動態資料來源,實現通過整合Spring提供的AbstractRoutingDataSource,只需要實現determineCurrentLookupKey方法即可 
 *  
 * 由於DynamicDataSource是單例的,執行緒不安全的,所以採用ThreadLocal保證執行緒安全,由DynamicDataSourceHolder完成。 
 *  
 * @author zhijun 
 * 
 */  
public class DynamicDataSource extends AbstractRoutingDataSource {  
  
    private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class);  
  
    private Integer slaveCount;  
  
    // 輪詢計數,初始為-1,AtomicInteger是執行緒安全的  
    private AtomicInteger counter = new AtomicInteger(-1);  
  
    // 記錄讀庫的key  
    private List<Object> slaveDataSources = new ArrayList<Object>(0);  
  
    @Override  
    protected Object determineCurrentLookupKey() {  
        // 使用DynamicDataSourceHolder保證執行緒安全,並且得到當前執行緒中的資料來源key  
        if (DynamicDataSourceHolder.isMaster()) {  
            Object key = DynamicDataSourceHolder.getDataSourceKey();   
            if (LOGGER.isDebugEnabled()) {  
                LOGGER.debug("當前DataSource的key為: " + key);  
            }  
            return key;  
        }  
        Object key = getSlaveKey();  
        if (LOGGER.isDebugEnabled()) {  
            LOGGER.debug("當前DataSource的key為: " + key);  
        }  
        return key;  
  
    }  
  
    @SuppressWarnings("unchecked")  
    @Override  
    public void afterPropertiesSet() {  
        super.afterPropertiesSet();  
  
        // 由於父類的resolvedDataSources屬性是私有的子類獲取不到,需要使用反射獲取  
        Field field = ReflectionUtils.findField(AbstractRoutingDataSource.class, "resolvedDataSources");  
        field.setAccessible(true); // 設定可訪問  
  
        try {  
            Map<Object, DataSource> resolvedDataSources = (Map<Object, DataSource>) field.get(this);  
            // 讀庫的資料量等於資料來源總數減去寫庫的數量  
            this.slaveCount = resolvedDataSources.size() - 1;  
            for (Map.Entry<Object, DataSource> entry : resolvedDataSources.entrySet()) {  
                if (DynamicDataSourceHolder.MASTER.equals(entry.getKey())) {  
                    continue;  
                }  
                slaveDataSources.add(entry.getKey());  
            }  
        } catch (Exception e) {  
            LOGGER.error("afterPropertiesSet error! ", e);  
        }  
    }  
  
    /** 
     * 輪詢演算法實現 
     *  
     * @return 
     */  
    public Object getSlaveKey() {  
        // 得到的下標為:0、1、2、3……  
        Integer index = counter.incrementAndGet() % slaveCount;  
        if (counter.get() > 9999) { // 以免超出Integer範圍  
            counter.set(-1); // 還原  
        }  
        return slaveDataSources.get(index);  
    }  
  
}

3.mycat實現mysql讀寫分離

mycat和mysqlproxy類似都是單獨部署一個服務,web專案的sql語句不直接傳送到mysql機器上而是傳送到mycat或者mysqlproxy客戶端,由客戶端通過配置判斷讀寫sql應該分別傳送到那個mysql機器,整個過程mycat作為一個代理。mycat使用比較簡單,下載壓縮包解壓,修改三個配置檔案然後啟動就能用了。當然mycat是一個數據庫中介軟體,用處肯定不止讀寫分離,還可以實現分表分庫等等。 參考連結 下載壓縮包下載連結 解壓目錄
目錄 說明
bin mycat命令,啟動、重啟、停止等
catlet catlet為Mycat的一個擴充套件功能
conf Mycat 配置資訊,重點關注
lib Mycat引用的jar包,Mycat是java開發的
logs 日誌檔案,包括Mycat啟動的日誌和執行的日誌。

配置檔案目錄
檔案 說明
server.xml Mycat的配置檔案,設定賬號、引數等
schema.xml Mycat對應的物理資料庫和資料庫表的配置
rule.xml Mycat分片(分庫分表)規則

讀寫分離配置樣例: server.xml:
<user name="test">
        <property name="password">test</property>  
        <property name="schemas">lunch</property>  
        <property name="readOnly">false</property>  

    </user>
配置了一個賬號test 密碼也是test,針對資料庫lunch,讀寫許可權都有,沒有針對表做任何特殊的許可權。

schema.xml:
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

<!-- 資料庫配置,與server.xml中的資料庫對應 -->
    <schema name="lunch" checkSQLschema="false" sqlMaxLimit="100">
        <table name="lunchmenu" dataNode="dn1"  />
        <table name="restaurant" dataNode="dn1"  />
        <table name="userlunch" dataNode="dn1"  />
        <table name="users" dataNode="dn1"  />
        <table name="dictionary" primaryKey="id" autoIncrement="true" dataNode="dn1"  />

        
    </schema>

<!-- 分庫配置 -->
    <dataNode name="dn1" dataHost="test1" database="lunch" />


<!-- 物理資料庫配置 -->
    <dataHost name="test1" maxCon="1000" minCon="10" balance="1"  writeType="0" dbType="mysql" dbDriver="native">
        <heartbeat>select user();</heartbeat>
        <writeHost host="hostM1" url="192.168.0.2:3306" user="root" password="123456">  
        <readHost host="hostM1" url="192.168.0.3:3306" user="root" password="123456">   
        </readHost>
        </writeHost>
    </dataHost>


</mycat:schema>
balance改為1,表示讀寫分離。
以上配置達到的效果就是102.168.0.2為主庫,192.168.0.3為從庫
mycat服務啟動和停止: 命令在解壓包bin下 ##啟動
mycat start
##停止
mycat stop
##重啟
mycat restart
整合到專案中去: Mycat帶來的最大好處就是使用是完全不用修改原有程式碼的,在mycat通過命令啟動後,你只需要將資料庫連線切換到Mycat的地址就可以了。登入到mycat客戶端之後直接在這裡執行sql指令碼和在mysql中一樣
登入mycat客戶端: mysql -h192.168.0.1 -P8806 -uroot -p123456

4.mysql proxy實現mysql讀寫分離

環境準備: 準備三個centos分別用來安裝mysql主、mysql備、mysqlproxy 安裝mysql: 分別在兩個centos上面mysql,開啟mysql服務 安裝mysqlproxy下載連結 解壓解包:tar zxvf mysql-proxy-0.8.3-linux-glibc2.3-x86-64bit.tar.gz
解壓後文件放到local下:mv mysql-proxy-0.8.3-linux-glibc2.3-x86-64bit /usr/local/mysql-proxy 配置mysqlproxy的主配置檔案: 建立指令碼、日誌目錄: cd /usr/local/mysql-proxy
mkdir lua 
mkdir logs 複製配置檔案和管理指令碼: cp share/doc/mysql-proxy/rw-splitting.lua ./lua 
cp share/doc/mysql-proxy/admin-sql.lua ./lua  建立配置檔案:
vi /etc/mysql-proxy.cnf  配置檔案修改內容如下:
[mysql-proxy]
user=root #執行mysql-proxy使用者
admin-username=proxy #主從mysql共有的使用者
admin-password=123.com #使用者的密碼
proxy-address=192.168.0.204:4000 #mysql-proxy執行ip和埠,不加埠,預設4040
proxy-read-only-backend-addresses=192.168.0.203 #指定後端從slave讀取資料
proxy-backend-addresses=192.168.0.202 #指定後端主master寫入資料

proxy-lua-script=/usr/local/mysql-proxy/lua/rw-splitting.lua #指定讀寫分離配置檔案位置
admin-lua-script=/usr/local/mysql-proxy/lua/admin-sql.lua #指定管理指令碼
log-file=/usr/local/mysql-proxy/logs/mysql-proxy.log #日誌位置
log-level=info #定義log日誌級別,由高到低分別有(error|warning|info|message|debug)
daemon=true #以守護程序方式執行
keepalive=true #mysql-proxy崩潰時,嘗試重啟

儲存修改後的配置檔案,同時設定檔案許可權:
chmod 660 /etc/mysql-porxy.cnf
配置mysqlproxy的讀寫分離配置檔案: vi /usr/local/mysql-proxy/lua/rw-splitting.lua
if not proxy.global.config.rwsplit then
proxy.global.config.rwsplit = {
min_idle_connections = 1, #預設超過4個連線數時,才開始讀寫分離,改為1
max_idle_connections = 1, #預設8,改為1
is_debug = false
}
end
啟動mysqlproxy服務: /usr/local/mysql-proxy/bin/mysql-proxy --defaults-file=/etc/mysql-proxy.cnf
netstat -tupln | grep 4000 #已經啟動
tcp 0 0 192.168.0.204:4000 0.0.0.0:* LISTEN 1264/mysql-proxy 關閉mysql-proxy使用:killall -9 mysql-proxy
讀寫分離測試: 登入主mysql,建立主從mysql機器公用的使用者名稱和密碼(和上面主配置檔案相對應):mysql> grant all on *.* to 'proxy'@'192.168.0.204' identified by '123.com'; 登入mysqlproxy(使用proxy機器的ip以及上面建立的使用者名稱密碼):mysql -u proxy -h 192.168.0.204 -P 4000 -p123.com 從mysqlproxy客戶端建立資料表、插入資料: mysql> create table user (number INT(10),name VARCHAR(255));
mysql> insert into test values(01,'zhangsan');
mysql> insert into user values(02,'lisi');
分別登入主從mysql,確保主從資料一致後(需要搭建主從複製的支援)接下來 登入mysqlproxy分別執行讀、寫操作可以發現上面的建立表、寫資料操作會對映到主mysql中。讀操作被對映到從mysql中 可以檢視架構中中主從機器狀態: mysql -uadmin -padmin -h192.168.0.204:4000 --port=4000

5.amoeba實現mysql讀寫分離


amoeba和mycat一樣也是一個開源的資料庫中間價,是chinese開發的支援的功能也比較多包括負載均衡、高可用性、Query過濾、讀寫分離、可路由相關的query到目標資料庫、可併發請求多臺資料庫合併結果等,使用也很方便直接修改xml配置檔案就能搭建讀寫分離。
下載amoeba下載連結 解壓解包: tar -zxvf amoeba-mysql-binary-2.2.0.tar.gz
解壓後可以重新命名為amoeba並複製到/usr/local下 修改配置檔案: 所有配置檔案都在解壓包的conf下,在amoeba.xml下配置使用者名稱密碼、埠、讀寫區分(見註釋)。dbServers.xml下配置兩個物理mysql機器 amoeba.xml完整配置樣例:
<amoeba:configuration xmlns:amoeba="http://amoeba.meidusa.com/">  
  
    <proxy>  
      
        <!-- service class must implements com.meidusa.amoeba.service.Service -->  
        <service name="Amoeba for Mysql" class="com.meidusa.amoeba.net.ServerableConnectionManager">  
            <!-- 埠 -->  
            <property name="port">8066</property>  
              
            <!-- bind ipAddress -->  
  
            <property name="ipAddress">192.168.168.253</property>  
  
              
            <property name="manager">${clientConnectioneManager}</property>  
              
            <property name="connectionFactory">  
                <bean class="com.meidusa.amoeba.mysql.net.MysqlClientConnectionFactory">  
                    <property name="sendBufferSize">128</property>  
                    <property name="receiveBufferSize">64</property>  
                </bean>  
            </property>  
              
            <property name="authenticator">  
                <bean class="com.meidusa.amoeba.mysql.server.MysqlClientAuthenticator">  
                      
    <!--使用者名稱密碼,使用者客戶端登入-->
                    <property name="user">root</property>  
                      
                    <property name="password">123456</property>  
                      
                    <property name="filter">  
                        <bean class="com.meidusa.amoeba.server.IPAccessController">  
                            <property name="ipFile">${amoeba.home}/conf/access_list.conf</property>  
                        </bean>  
                    </property>  
                </bean>  
            </property>  
              
        </service>  
          
        <!-- server class must implements com.meidusa.amoeba.service.Service -->  
        <service name="Amoeba Monitor Server" class="com.meidusa.amoeba.monitor.MonitorServer">  
            <!-- port -->  
            <!--  default value: random number  
            <property name="port">9066</property>  
            -->  
            <!-- bind ipAddress -->  
            <property name="ipAddress">127.0.0.1</property>  
            <property name="daemon">true</property>  
            <property name="manager">${clientConnectioneManager}</property>  
            <property name="connectionFactory">  
                <bean class="com.meidusa.amoeba.monitor.net.MonitorClientConnectionFactory"></bean>  
            </property>  
              
        </service>  
          
        <runtime class="com.meidusa.amoeba.mysql.context.MysqlRuntimeContext">  
            <!-- proxy server net IO Read thread size -->  
            <property name="readThreadPoolSize">20</property>  
              
            <!-- proxy server client process thread size -->  
            <property name="clientSideThreadPoolSize">30</property>  
              
            <!-- mysql server data packet process thread size -->  
            <property name="serverSideThreadPoolSize">30</property>  
              
            <!-- per connection cache prepared statement size  -->  
            <property name="statementCacheSize">500</property>  
              
            <!-- query timeout( default: 60 second , TimeUnit:second) -->  
            <property name="queryTimeout">60</property>  
        </runtime>  
          
    </proxy>  
      
    <!--   
        Each ConnectionManager will start as thread  
        manager responsible for the Connection IO read , Death Detection  
    -->  
    <connectionManagerList>  
        <connectionManager name="clientConnectioneManager" class="com.meidusa.amoeba.net.MultiConnectionManagerWrapper">  
            <property name="subManagerClassName">com.meidusa.amoeba.net.ConnectionManager</property>  
            <!--   
              default value is avaliable Processors   
            <property name="processors">5</property>  
             -->  
        </connectionManager>  
        <connectionManager name="defaultManager" class="com.meidusa.amoeba.net.MultiConnectionManagerWrapper">  
            <property name="subManagerClassName">com.meidusa.amoeba.net.AuthingableConnectionManager</property>  
              
            <!--   
              default value is avaliable Processors   
            <property name="processors">5</property>  
             -->  
        </connectionManager>  
    </connectionManagerList>  
      
        <!-- default using file loader -->  
    <dbServerLoader class="com.meidusa.amoeba.context.DBServerConfigFileLoader">  
        <property name="configFile">${amoeba.home}/conf/dbServers.xml</property>  
    </dbServerLoader>  
      
    <queryRouter class="com.meidusa.amoeba.mysql.parser.MysqlQueryRouter">  
        <property name="ruleLoader">  
            <bean class="com.meidusa.amoeba.route.TableRuleFileLoader">  
                <property name="ruleFile">${amoeba.home}/conf/rule.xml</property>  
                <property name="functionFile">${amoeba.home}/conf/ruleFunctionMap.xml</property>  
            </bean>  
        </property>  
        <property name="sqlFunctionFile">${amoeba.home}/conf/functionMap.xml</property>  
        <property name="LRUMapSize">1500</property>  
  
        <property name="defaultPool">master</property>  
          
  <!--讀寫分離的配置,這裡設定的是讀庫和寫庫的名稱-->
        <property name="writePool">master</property>  
        <property name="readPool">slave</property>  
  
        <property name="needParse">true</property>  
    </queryRouter>  
</amoeba:configuration> 

dbServers.xml:
<amoeba:dbServers xmlns:amoeba="http://amoeba.meidusa.com/">  
  
        <!--   
            Each dbServer needs to be configured into a Pool,  
            If you need to configure multiple dbServer with load balancing that can be simplified by the following configuration:  
             add attribute with name virtual = "true" in dbServer, but the configuration does not allow the element with name factoryConfig  
             such as 'multiPool' dbServer     
        -->  
          
    <dbServer name="abstractServer" abstractive="true">  
        <factoryConfig class="com.meidusa.amoeba.mysql.net.MysqlServerConnectionFactory">  
            <property name="manager">${defaultManager}</property>  
            <property name="sendBufferSize">64</property>  
            <property name="receiveBufferSize">128</property>  
                  
            <!-- mysql port -->  
            <property name="port">3306</property>  
              
            <!-- mysql schema -->  
            <property name="schema">minishop</property>  
              
            <!-- mysql user -->  
            <property name="user">root</property>  
              
            <!--  mysql password -->  
            <property name="password">123456</property>  
  
        </factoryConfig>  
  
        <poolConfig class="com.meidusa.amoeba.net.poolable.PoolableObjectPool">  
            <property name="maxActive">500</property>  
            <property name="maxIdle">500</property>  
            <property name="minIdle">10</property>  
            <property name="minEvictableIdleTimeMillis">600000</property>  
            <property name="timeBetweenEvictionRunsMillis">600000</property>  
            <property name="testOnBorrow">true</property>  
            <property name="testWhileIdle">true</property>  
        </poolConfig>  
    </dbServer>  
  
    <dbServer name="master"  parent="abstractServer">  
        <factoryConfig>  
            <!-- mysql ip -->  
            <property name="ipAddress">192.168.168.253</property>  
        </factoryConfig>  
    </dbServer>  
  
    <dbServer name="slave"  parent="abstractServer">  
        <factoryConfig>  
            <!-- mysql ip -->  
            <property name="ipAddress">192.168.168.119</property>  
        </factoryConfig>  
    </dbServer>  
      
    <dbServer name="multiPool" virtual="true">  
        <poolConfig class="com.meidusa.amoeba.server.MultipleServerPool">  
            <!-- Load balancing strategy: 1=ROUNDROBIN , 2=WEIGHTBASED , 3=HA-->  
            <property name="loadbalance">1</property>    
            <property name="poolNames">slave1</property>  
        </poolConfig>  
    </dbServer>  
          
</amoeba:dbServers>
接下來和上面類似,直接登入代理客戶端執行sql指令碼

6.mysql的主從複製搭建

mysql自帶的主從複製是slave機器利用mater機器的log日誌來同步更新資料,其中log日誌記錄了master機器對資料表的操作。slave同步資料的整個過程是非同步的。 參考連結 參考連結 參考連結 (1)mysql安裝
首先下載mysql:
官網下載mysql-5.7.21-linux-glibc2.12-x86_64.tar.gz然後使用xftp5上傳至centos的/usr/local/下
解壓:tar -zxvf mysql-5.7.21-linux-glibc2.12-x86_64.tar.gz
重新命名為mysql
新增系統mysql使用者組:sudo groupadd mysql
新增mysql使用者:sudo useradd -r -g mysql mysql
檢視使用者組:id mysql
進入mysql目錄:cd mysql
安裝:bin/mysqld --initialize --user=mysql --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data
末尾會生成臨時密碼:cig%*nktl
配置ssl:bin/mysql_ssl_rsa_setup --datadir=/usr/local/mysql/data
配置my.cnf:
如果mysql/support-files下沒有my-default.cnf則需要我們自己建立(這個檔案是配置檔案,mysql預設從他來啟動,等會兒主從複製也要修改這個檔案)
建立空檔案:touch /etc/my.cnf
編輯:vim /etc/my.cnf(修改basedir、datadir分別為usr/local/mysql/和usr/localmysql/data/。其餘配置先全部註釋掉)
守護程序啟動mysql:
使用bin/mysqld_safe啟動mysql:bin/mysqld_safe &
把mysql新增到後臺執行成為服務:
cp mysql.server /etc/init.d/mysql
chmod +x /etc/init.d/mysql
chkconfig --add mysql //把mysql註冊為開啟啟動項
chkconfig --list mysql 檢視是否新增成功
修改登入密碼:
進入mysql/bin使用剛剛臨時密碼登入:./mysql -uroot -p
mysql> set password=password("root");
修改mysql遠端登陸
use mysql;
update user set host='%' where user='root';
GRANT ALL PRIVILEGES ON *.* TO 'myuser'@'%' IDENTIFIED BY 'mypwd' WITH GRANT OPTION;
FLUSH PRIVILEGES;
exit;
service mysql restart;

(2)準備兩個安裝好mysql服務的centos

(3)配置master

在原有/etc/my,cnf配置上面增加主從複製的配置:

## replication  
server_id=6  
binlog-ignore-db=mysql  
log-bin=master-mysql-bin  
binlog_cache_size=1M  
binlog_format=mixed  
expire_logs_days=7  
slave_skip_errors=1062

引數解釋如下:
serverid 全域性唯一的
binlog-ignore-db=mysql複製過濾,我們不同步mysql系統自帶的資料庫
log-bin=master-mysql-bin 開啟logbin功能並設定logbin檔案的名稱
binlog_format=mixed 混合型複製模式,預設採用基於語句的複製
完整my.cnf樣例:
[client]  
port = 3306  
socket = /usr/local/mysql/mysql.sock  
  
[mysqld]  
character-set-server = utf8  
collation-server = utf8_general_ci  
  
skip-external-locking  
skip-name-resolve  
  
user = mysql  
port = 3306  
basedir = /usr/local/mysql  
datadir = /home/mysql/data  
tmpdir = /home/mysql/temp  
# server_id = .....  
socket = /usr/local/mysql/mysql.sock  
log-error = /home/mysql/logs/mysql_error.log  
pid-file = /home/mysql/mysql.pid  
  
open_files_limit = 10240  
  
back_log = 600  
max_connections=500  
max_connect_errors = 6000  
wait_timeout=605800  
  
#open_tables = 600  
#table_cache = 650  
#opened_tables = 630  
  
max_allowed_packet = 32M  
  
sort_buffer_size = 4M  
join_buffer_size = 4M  
thread_cache_size = 300  
query_cache_type = 1  
query_cache_size = 256M  
query_cache_limit = 2M  
query_cache_min_res_unit = 16k  
  
tmp_table_size = 256M  
max_heap_table_size = 256M  
  
key_buffer_size = 256M  
read_buffer_size = 1M  
read_rnd_buffer_size = 16M  
bulk_insert_buffer_size = 64M  
  
lower_case_table_names=1  
  
default-storage-engine = INNODB  
  
innodb_buffer_pool_size = 2G  
innodb_log_buffer_size = 32M  
innodb_log_file_size = 128M  
innodb_flush_method = O_DIRECT  
  
#####################  
thread_concurrency = 32  
long_query_time= 2  
slow-query-log = on  
slow-query-log-file = /home/mysql/logs/mysql-slow.log    
  
## replication  
server_id=6  
binlog-ignore-db=mysql  
log-bin=master-mysql-bin  
binlog_cache_size=1M  
binlog_format=mixed  
expire_logs_days=7  
slave_skip_errors=1062  
  
[mysqldump]  
quick  
max_allowed_packet = 32M  
  
[mysqld_safe]  
log-error=/var/log/mysqld.log  
pid-file=/var/run/mysqld/mysqld.pid 

重啟mysql服務,並登入客戶端下執行下面命令:
grant replication slave, replication client on *.* to 'root'@'192.168.1.5' identified by  
'root';  

//解釋:賬號和密碼都是root ,允許192.168.1.5這臺機器向master傳送同步請求

重新整理master配置資訊:
flush privileges

檢視master狀態資訊:

show master status

(4)配置slave

原有/etc/my.cnf配置檔案基礎上增加主從複製配置,完整樣例如下:

[client]  
port = 3306  
socket = /usr/local/mysql/mysql.sock  
  
[mysqld]  
character-set-server = utf8  
collation-server = utf8_general_ci  
  
skip-external-locking  
skip-name-resolve  
  
user = mysql  
port = 3306  
basedir = /usr/local/mysql  
datadir = /home/mysql/data  
tmpdir = /home/mysql/temp  
# server_id = .....  
socket = /usr/local/mysql/mysql.sock  
log-error = /home/mysql/logs/mysql_error.log  
pid-file = /home/mysql/mysql.pid  
  
open_files_limit = 10240  
  
back_log = 600  
max_connections=500  
max_connect_errors = 6000  
wait_timeout=605800  
  
#open_tables = 600  
#table_cache = 650  
#opened_tables = 630  
  
max_allowed_packet = 32M  
  
sort_buffer_size = 4M  
join_buffer_size = 4M  
thread_cache_size = 300  
query_cache_type = 1  
query_cache_size = 256M  
query_cache_limit = 2M  
query_cache_min_res_unit = 16k  
  
tmp_table_size = 256M  
max_heap_table_size = 256M  
  
key_buffer_size = 256M  
read_buffer_size = 1M  
read_rnd_buffer_size = 16M  
bulk_insert_buffer_size = 64M  
  
lower_case_table_names=1  
  
default-storage-engine = INNODB  
  
innodb_buffer_pool_size = 2G  
innodb_log_buffer_size = 32M  
innodb_log_file_size = 128M  
innodb_flush_method = O_DIRECT  
  
#####################  
thread_concurrency = 32  
long_query_time= 2  
slow-query-log = on  
slow-query-log-file = /home/mysql/logs/mysql-slow.log    
  
  
## replication  
server_id=5  
binlog-ignore-db=mysql  
log-bin=mysql-slave-bin  
binlog_cache_size = 1M  
binlog_format=mixed  
expire_logs_days=7  
slave_skip_errors=1062  
relay_log=mysql-relay-bin  
log_slave_updates=1  
read_only=1  
  
  
[mysqldump]  
quick  
max_allowed_packet = 32M  
  
[mysqld_safe]  
log-error=/var/log/mysqld.log  
pid-file=/var/run/mysqld/mysqld.pid 

重啟mysql服務,登入客戶端。執行命令連線master(首先在master和slave上面建立相同的資料庫和資料表):
change master to  
master_host='192.168.1.6' ,
master_user='root' ,
master_password='root' ,
master_port=3306 ,
master_log_file='edu-mysql-bin.000002' ,
master_log_pos=427 ,
master_connect_retry=30;

//master_host=master主機的ip地址
//master_user='root',master_password='root',我們剛剛在master有執行過授權的賬號密碼就是這個
//master_port=3306,master資料庫的埠號
//master_log_file='edu-mysql-bin.000002',master_log_pos=427,這個是我們通過show master status看到的position

檢視slave狀態:
show  slave status\G

開始主從複製:

start slave

回到master客戶端檢視slave資訊:

show processlist\G

(5)測試

進入主資料庫伺服器,在表中插入一條資料,然後到從資料庫伺服器中檢視是否含有剛剛插入的資料。