MySQL 主從配置和基於Spring 的讀寫分離

MySQL 主從配置。

環境說明: 在windows10 系統上,用VMware創建了兩個虛擬機(一主一從),採用的Linux操作系統是CentOS 6.5 32位,MySQL 服務器的版本是5.6。

  1. 在VMware上創建兩個CentOS 6.5的服務器。在VMware上創建虛擬機需要注意網絡模式爲橋接模式,並且創建的兩臺服務器在同一網段,否則兩臺CentOS服務器無法相互訪問。
  2. 採用CentOS 自帶的yum源安裝MySQL。由於要安裝定製版本,所以需要在yum上做一些修改。
    a:進入CentOS 命令行,vim /etc/yum.repos.d/mysql-community.repo,
    b:編輯
[mysql56-community]
name=MySQL 5.6 Community Server
baseurl=http://repo.mysql.com/yum/mysql-5.6-community/el/6/$basearch/
enabled=1
gpgcheck=0

,
c: 查看可用yum源 yum repolist enabled | grep mysql
d: yum install mysql-community-server。然後就會下載並執行安裝。
3. 在另一臺服務器上執行相同的操作。
4. 修改兩臺服務器的root密碼,並且允許遠程登錄。
5. 在主服務器上配置MySQl 。
a: vim /etc/my.cnf
b:
[mysqld]
log-bin=mysql-bin

server-id爲一個int類型的數字,只要和從服務器的server-id不同即可
6. 在從服務器上配置MySQL
[mysqld]
server-id=2

7. 在主服務器上創建一個用戶,用於從服務器讀取主服務器的log文件。
mysql> CREATE USER 'repl'@'host_ip' IDENTIFIED BY 'slavepass';
mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'host_ip';

8. 在主服務器上執行
mysql> FLUSH TABLES WITH READ LOCK;
mysql > SHOW MASTER STATUS;
+——————+———-+————–+——————+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+——————+———-+————–+——————+
| mysql-bin.000003 | 73 | test | manual,mysql |
+——————+———-+————–+——————+
記住File 列和Position列的值,將會在從服務器上配置時使用。
9. 在從服務器上
mysql> CHANGE MASTER TO
MASTER_HOST='master_ip',
MASTER_USER='userName',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='log_file',
MASTER_LOG_POS= position;

這裏的LOG_FILE就是上面的File列的值,LOG_POS就是上面Position列的值。

  1. 在從服務器上執行 mysql> start slave
  2. 在從服務器上執行show slave status \G;,查看到兩個yes就表示主從配置已經成功這裏寫圖片描述

至此就已經在兩臺CentOS上安裝了相同版本的MySQL服務器並且完成了主從配置。

Spring讀寫分離
github源代碼

本文描述的是使用基於Spring AOP 實現應用層的讀寫分離。數據庫查詢,尤其是多表關聯的複雜查詢對服務器的性能損耗遠遠大於數據庫的插入操作,且日常生產環境中查詢的頻率遠大於增刪改。因此爲了緩解數據庫壓力,將對數據庫的讀壓力分流到從服務器,讓主數據庫專注於增刪改更加適合於高併發。普通的WEB應用分Controller,Service,Dao三層。事務控制一般在Service層。在Service層的代碼中,基於方法級別的讀寫分離是最理想的一種狀態。在Service層的代碼中,如果某個方法只涉及到查詢,那麼則分流到從數據庫中,如果涉及到增,刪,改則分流到主數據庫中。
1. 創建Maven Web工程。pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.tianxingshuke</groupId>
    <artifactId>MySQLReadAndWrite</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>MySQLReadAndWrite</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring.version>4.1.6.RELEASE</spring.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>${spring.version}</version>
    </dependency>
        <!-- https://mvnrepository.com/artifact/com.jolbox/bonecp -->
        <dependency>
            <groupId>com.jolbox</groupId>
            <artifactId>bonecp</artifactId>
            <version>0.8.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.26</version>
        </dependency>
    </dependencies>
</project>

  1. 定義一個註解類,用於標識Service層的方法查詢操作從數據庫,涉及到增刪改的操作主數據庫。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
    String value();
}

3.定義一個切面類,用於在編譯時織入到Service層中。當Service存在超類並且超類方法中有對應的DataSource的註解時,將註解中的值取出,並返回給spring AbstractRoutingDataSource 子類determineCurrentLookupKey方法作爲返回值。

public class HandleDataSource {
    public static final ThreadLocal<String> holder = new ThreadLocal<String>();

    public static void putDataSource(String datasource){
        holder.set(datasource);
    }

    public static String getDataSource(){
        return holder.get();
    }
}
public class DataSourceAspect {
    public void pointCut() {
    };

    public void before(JoinPoint point) {
        Object target = point.getTarget();
        System.out.println(target.toString());
        String method = point.getSignature().getName();
        System.out.println(method);
        Class<?>[] classz = target.getClass().getInterfaces();
        Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
        try {
            Method m = classz[0].getMethod(method, parameterTypes);
            System.out.println(m.getName());
            if (m != null && m.isAnnotationPresent(DataSource.class)) {
                DataSource data = m.getAnnotation(DataSource.class);
                HandleDataSource.putDataSource(data.value());
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class ChooseDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        // TODO Auto-generated method stub
        return HandleDataSource.getDataSource();
    }
}

4 . 配置文件 applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd"
    default-lazy-init="true">
    <!-- 主庫數據源 -->
    <bean id="writeDataSource" class="com.jolbox.bonecp.BoneCPDataSource"
        destroy-method="close">
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl"
            value="jdbc:mysql://192.168.1.127:3306/mydb?autoReconnect=true" />
        <property name="username" value="root" />
        <property name="password" value="yaoyuan" />
        <property name="partitionCount" value="4" />
        <property name="releaseHelperThreads" value="3" />
        <property name="acquireIncrement" value="2" />
        <property name="maxConnectionsPerPartition" value="40" />
        <property name="minConnectionsPerPartition" value="20" />
        <property name="idleMaxAgeInSeconds" value="60" />
        <property name="idleConnectionTestPeriodInSeconds" value="60" />
        <property name="poolAvailabilityThreshold" value="5" />
    </bean>

    <!-- 從庫數據源 -->
    <bean id="readDataSource" class="com.jolbox.bonecp.BoneCPDataSource"
        destroy-method="close">
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl"
            value="jdbc:mysql://192.168.1.107:3306/mydb?autoReconnect=true" />
        <property name="username" value="root" />
        <property name="password" value="yaoyuan" />
        <property name="partitionCount" value="4" />
        <property name="releaseHelperThreads" value="3" />
        <property name="acquireIncrement" value="2" />
        <property name="maxConnectionsPerPartition" value="40" />
        <property name="minConnectionsPerPartition" value="20" />
        <property name="idleMaxAgeInSeconds" value="60" />
        <property name="idleConnectionTestPeriodInSeconds" value="60" />
        <property name="poolAvailabilityThreshold" value="5" />
    </bean>

    <!-- transaction manager, 事務管理 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>


    <!-- 註解自動載入 -->
    <context:annotation-config />

    <!--enale component scanning (beware that this does not enable mapper scanning!) -->
    <context:component-scan base-package="com.tianxingshuke" />

    <!-- enable transaction demarcation with annotations -->
    <tx:annotation-driven />

 <bean id="userDao" class="com.tianxingshuke.MySQLReadAndWrite.UserDao">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="dataSource" class="com.tianxingshuke.MySQLReadAndWrite.ChooseDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <!-- write -->
                <entry key="write" value-ref="writeDataSource" />
                <!-- read -->
                <entry key="read" value-ref="readDataSource" />
            </map>

        </property>

    </bean>

    <!-- 激活自動代理功能 -->
    <aop:aspectj-autoproxy proxy-target-class="true" />

    <!-- 配置數據庫註解aop -->
    <bean id="dataSourceAspect" class="com.tianxingshuke.MySQLReadAndWrite.DataSourceAspect" />
    <aop:config>
        <aop:aspect id="c" ref="dataSourceAspect">
            <aop:pointcut id="tx"
                expression="execution(* com.tianxingshuke.MySQLReadAndWrite.*.*(..))" />
            <aop:before pointcut-ref="tx" method="before" />
        </aop:aspect>
    </aop:config>
    <!-- 配置數據庫註解aop -->
</beans>

5 . 測試

public class User {
    private int id;
    private String name;
    public User(){}
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "User [id=" + id + ", name=" + name + "]";
    }
}
public interface UserDaoInterface {
    @DataSource(value = "write")
    public int addUser(User user);
    @DataSource(value="read")
    public List<User> getUser();
    @DataSource(value="write")
    public int delete();
}
public class UserDao implements UserDaoInterface{

    private JdbcTemplate jdbcTemplate;

    public void setDataSource(javax.sql.DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }
    public int addUser(User user) {
        return jdbcTemplate.update("insert into user values (null,?)", user.getName());
    }
    public List<User> getUser(){
        return jdbcTemplate.query("select id,name from user", new UserMapper());
    }
    private static final class UserMapper implements RowMapper<User> {
        public User mapRow(ResultSet rs, int rowNum) throws SQLException {
            User user = new User();
            user.setId(rs.getInt("id"));
            user.setName(rs.getString("name"));
            return user;
        }
    }
    public int delete() {
        int result = jdbcTemplate.update("delete from user");
        return result;
    }
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:/applicationContext.xml"})
public class AppTest {
    @Resource 
    UserDao userDao;

    @Test
    public void testGetTest(){
        List<User> list = userDao.getUser();
        for(User user : list){
            System.out.println(user.toString());
        }
    }

    @Test
    public void testMyTest(){
        User user = new User();
        user.setName("xxxxxxxx");
        int result = userDao.addUser(user);
        System.out.println(result);
    }

    @Test
    public void testDelete(){
        int delete = userDao.delete();
        System.out.println(delete);
    }
}

通過上面的介紹,可以完成MySQL的主從配置和應用層的讀寫分離。

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