瑞吉外賣實戰項目全攻略——優化篇第二天

瑞吉外賣實戰項目全攻略——優化篇第二天

該系列將記錄一份完整的實戰項目的完成過程,該篇屬於優化篇第二天,主要負責完成讀寫分離問題

案例來自B站黑馬程式設計師Java項目實戰《瑞吉外賣》,請結合課程資料閱讀以下內容

該篇我們將完成以下內容:

  • Mysql主從複製
  • 讀寫分離案例
  • 項目實現讀寫分離

Mysql主從複製

該小節會介紹Linux系統上Mysql的主從複製的全流程

主從複製介紹

Mysql主從複製是一個非同步的複製過程,底層是基於Mysql資料庫自帶的二進位日誌功能

我們將一台或多台從庫(slave)從一台主庫(master)進行日誌複製然後解析日誌並應用到本身,最終實現多台資料庫保持數據一致

我們先給出主從複製的流程圖:

下面我們簡單介紹一下流程:

  • master將改變記錄到二進位文件(Binary log)
  • slave將master的binary log拷貝到它的中繼日誌(relay log)
  • slave重做中繼日誌的事件,將改變應用到自己的資料庫中

主庫只能有一個資料庫,但從庫可以有多個資料庫

主從複製前置條件

我們要完成Mysql資料庫的主從複製,就需要擁有兩台資料庫,我們通常採用虛擬機來準備

我們在前面的Linux課程中已經配置了一台虛擬機,我們只需要將該虛擬機進行克隆即可獲得另一台相同的虛擬機:

但是需要注意的是我們克隆後的虛擬機的IP和UUID是相同的,我們需要將他們修改過來:

  1. 修改IP

如果你的虛擬機沒有設置靜態IP,那麼你生成的克隆虛擬機將不存在這個問題,但你仍需要重設UUID

如果你的虛擬機設置了靜態IP,那麼克隆機的IP將不做改變,我們這裡需要修改克隆機的IP

首先重新生成MAC地址,雙擊網路配置器,點擊高級,生成MAC地址:

然後進入虛擬機中,輸入以下命令查看ip存放目錄:

# 查看ip存放目錄
ifconfig

進入ip存放目錄,修改ip:

# 進入目錄
cd /etc/sysconfig/network-scripts

# 訪問文件修改ip
vi ifcfg-ens32

進入後我們需要查找這兩部分:

# 是否為靜態IP
BOOTPROTO='static'

# 將IP改為其他IP
IPADDR='192.168.44.129'

最後重啟網卡,查看當前IP即可:

# 重啟網卡
service network restart

# 查看IP
ifconfig
  1. 修改UUID

我們的克隆機需要修改UUID,否則無法顯示主從複製操作

首先我們進入mysql,自動生成一個UUID複製下來:

# 進入mysql
mysql -uroot -p123456

# 生成UUID
select uuid();

然後查找mysql安裝地址並進入配置文件中:

# 查找mysql安裝地址
show variables like 'datadir';

# 進入配置文件
vim /var/lib/mysql/auto.cnf

將配置文件中UUID刪除,並複製為剛剛生成的UUID即可:

[auto]
server-uuid=7c872b6f-538d-11ed-b0fd-000c299055af

最後重啟伺服器並重啟mysql即可:

# 重啟伺服器
systemctl restart network

# 重啟mysql
systemctl restart mysqld
  1. 保證數據資訊一致

目前我們的兩台資料庫中的資訊要保持一致,否則我們在後續操作時會導致錯誤

主從複製操作實現

下面我們分別從主庫和從庫兩方面來配置主從複製條件

主從複製主庫操作實現

下面我們開始主庫的配置條件:

  1. 修改Mysql資料庫的配置文件/etc/my.cnf
# 進入配置文件
vim /etc/my.cnf

# 在[mysqld]下面複製下面兩句即可
[mysqld]
log-bin=mysql-bin # 啟動二進位日誌
server-id=128 # 設置伺服器唯一ID
  1. 重啟mysql服務
# 重啟mysql服務
systemctl restart mysqld
  1. 登錄資料庫,執行下面SQL語句
# 登錄資料庫
mysql -uroot -p123456

# 執行下列語句(生成一個用戶,使其具有查詢日誌的權力)
GRANT REPLICATION SLAVE ON *.* to 'xiaoming'@'%' identified by 'Root@123456';
  1. 登錄資料庫,執行下面SQL語句,記錄資訊
# 執行語句(你將會看到日誌相關資訊,接下來不要對資料庫操作,因為操作會導致日誌資訊改變)
show master status;

# 你將會看到File和Position資訊,該頁面不要改變

主從複製從庫操作實現

下面我們開始從庫的配置條件:

  1. 修改Mysql資料庫的配置文件/etc/my.cnf
# 進入配置文件
vim /etc/my.cnf

# 在[mysqld]下面複製下面兩句即可
[mysqld]
server-id=129 # 設置伺服器唯一ID
  1. 重啟mysql服務
# 重啟mysql服務
systemctl restart mysqld
  1. 登錄mysql資料庫,執行下列語句
# 登錄資料庫
mysql -uroot -p123456

# 執行下列語句(使用該用戶查詢日誌,注意內容是需要修改的)
# master_host主庫IP,master_user主庫用戶,master_password主庫用戶密碼,master_log_file,master_log_pos為日誌資訊
change master to
master_host='192.168.44.128',master_user='xiaoming',master_password='Root@123456',master_log_file='mysql-bin.000001',master_log_pos=439;

# 輸入後執行以下語句開啟slave
start slave;

# 如果顯示slave衝突(如果你之前執行過slave),使用下列方法結束之前slave
stop slave;
  1. 查看是否連接成功
# 查看語句
show slave starts\G;

# 我們只需要關注三個點:(為下述即為成功)
Slave_IO_State: Waiting for master to send event
Slave_IO_Running: Yes
Slave_SQL_Running: Yes

主從複製數據測試

我們直接來到主機資料庫,連接上兩個資料庫的資訊

我們對主庫進行增刪改操作,其中我們的從庫進行刷新後也出現相應數據即為成功

讀寫分離案例

我們將通過一個簡單的案例來講解讀寫分離

讀寫分離介紹

面對日益增加的系統訪問量,資料庫的吞吐量面臨著巨大瓶頸

對於同一時刻有著大量並發操作和較少的寫操作類型的應用系統來說,我們將資料庫拆分為主庫和從庫

其中主庫負責事務性的增刪改操作,從庫負責處理查詢操作,能夠有效的避免數據更新導致的行鎖,使整個系統的查詢性得到巨大提升

Sharding-JDBC介紹

Sharding-JDBC定位為輕量級Java架構,在Java的JDBC層提供的額外服務

它使用客戶端直連資料庫,以jar包形式提供服務,無需額外部署和依賴,可以理解為增強版的JDBC驅動,輕鬆實現讀寫分離

其中Sharding-JDBC具有以下特點:

  • 適用於任何基於JDBC的ORM框架
  • 支援任何第三方的資料庫連接池
  • 支援任意實現JDBC規範的資料庫

讀寫分離入門案例

下面我們將通過一個簡單的案例來完成讀寫分離的學習:

  1. 資料庫資訊準備

我們在主庫中設計了一個rw資料庫,並設計了一張User表作為我們的案例實體類

  1. 初始工程的搭建

我們直接使用springboot創建一個簡單的案例,其中我們完成user的各層級的搭建,並書寫了簡單的Controller方法:

package com.itheima.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.itheima.entity.User;
import com.itheima.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.sql.DataSource;
import java.util.List;

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private UserService userService;

    @PostMapping
    public User save(User user){
        userService.save(user);
        return user;
    }

    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id){
        userService.removeById(id);
    }

    @PutMapping
    public User update(User user){
        userService.updateById(user);
        return user;
    }

    @GetMapping("/{id}")
    public User getById(@PathVariable Long id){
        User user = userService.getById(id);
        return user;
    }

    @GetMapping("/list")
    public List<User> list(User user){
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(user.getId() != null,User::getId,user.getId());
        queryWrapper.eq(user.getName() != null,User::getName,user.getName());
        List<User> list = userService.list(queryWrapper);
        return list;
    }
}
  1. 導入Sharding-JDBC的maven坐標
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="//maven.apache.org/POM/4.0.0"
         xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="//maven.apache.org/POM/4.0.0 //maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.5</version>
        <relativePath/>
    </parent>
    <groupId>com.itheima</groupId>
    <artifactId>rw_demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--sharding-JDBC坐標-->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.0.0-RC1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</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>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.23</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.4.5</version>
            </plugin>
        </plugins>
    </build>
</project>
  1. 配置資料庫相關資訊
server:
  port: 8080
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID
spring:
  shardingsphere: # 以下均為資料庫資訊,我們不需要再配置Durid數據
    datasource:
      names: # 這裡是所使用資料庫的名稱(可以自行定義,但需要與下述數據符合)
        master,slave
      # 主數據源
      master: # 正常配置資訊
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.44.128:3306/rw?characterEncoding=utf-8
        username: root
        password: 123456
      # 從數據源
      slave: # 正常配置資訊
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.44.129:3306/rw?characterEncoding=utf-8
        username: root
        password: 123456
    masterslave:
      # 讀寫分離配置
      load-balance-algorithm-type: round_robin #輪詢,當存在多個從庫時,查詢操作按正常的順序按個查詢訪問
      # 最終的數據源名稱
      name: dataSource
      # 主庫數據源名稱
      master-data-source-name: master
      # 從庫數據源名稱列表,多個逗號分隔
      slave-data-source-names: slave
    props:
      sql:
        show: true #開啟SQL顯示,默認false
  1. 允許bean定義覆蓋
server:
  port: 8080
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID
spring:
  shardingsphere: # 以下均為資料庫資訊,我們不需要再配置Durid數據
    datasource:
      names: # 這裡是所使用資料庫的名稱(可以自行定義,但需要與下述數據符合)
        master,slave
      # 主數據源
      master: # 正常配置資訊
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.44.128:3306/rw?characterEncoding=utf-8
        username: root
        password: 123456
      # 從數據源
      slave: # 正常配置資訊
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.44.129:3306/rw?characterEncoding=utf-8
        username: root
        password: 123456
    masterslave:
      # 讀寫分離配置
      load-balance-algorithm-type: round_robin #輪詢,當存在多個從庫時,查詢操作按正常的順序按個查詢訪問
      # 最終的數據源名稱
      name: dataSource
      # 主庫數據源名稱
      master-data-source-name: master
      # 從庫數據源名稱列表,多個逗號分隔
      slave-data-source-names: slave
    props:
      sql:
        show: true #開啟SQL顯示,默認false
  main:
    allow-bean-definition-overriding: true # 允許bean定義覆蓋(我們兩個資料庫會多次創建bean,這裡需要允許bean覆蓋)
  1. 實際測試

前面的步驟完成後,我們的讀寫分離案例就算正式完成了

下面我們只需要開啟項目,並按照其Controller的URL進行訪問,查看資料庫資訊即可

項目實現讀寫分離

最後我們將讀寫分離在我們的瑞吉外賣實戰項目中實現:

資料庫數據準備

由於我們之前的項目都是在本地資料庫進行測試,所以我們需要將數據重新載入主庫資料庫中(資料中包含sql語句):

項目實現讀寫分離

我們來到項目中,完成具體的讀寫分離操作:

  1. 導入Sharding-JDBC的maven坐標
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="//maven.apache.org/POM/4.0.0"
         xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="//maven.apache.org/POM/4.0.0 //maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>


    <groupId>com.xyl</groupId>
    <artifactId>mydelivery</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

        <!--阿里雲簡訊服務-->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
            <version>4.5.16</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
            <version>2.1.0</version>
        </dependency>

        <!--Redis坐標-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!--Cache坐標-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

        <!--Sharding-jdbc坐標-->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.0.0-RC1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
        </dependency>

        <!--         將對象 轉化為JSON格式-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</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>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.23</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.4.5</version>
            </plugin>
        </plugins>
    </build>

</project>
  1. 在配置文件中書寫讀寫分離原則和Bean定義覆蓋原則
server:
  port: 8080
spring:
  application:
    name: qiuluo
  shardingsphere:
    datasource:
      names:
        master,slave
      # 主數據源
      master:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.44.128:3306/reggie?characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
        username: root
        password: 123456
      # 從數據源
      slave:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.44.129:3306/reggie?characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
        username: root
        password: 123456
    masterslave:
      # 讀寫分離配置
      load-balance-algorithm-type: round_robin #輪詢
      # 最終的數據源名稱
      name: dataSource
      # 主庫數據源名稱
      master-data-source-name: master
      # 從庫數據源名稱列表,多個逗號分隔
      slave-data-source-names: slave
    props:
      sql:
        show: true #開啟SQL顯示,默認false
  main:
    allow-bean-definition-overriding: true # 允許bean定義覆蓋
  redis:
    host: localhost
    port: 6379
    # password: 123456
    database: 0
  cache:
    redis:
      time-to-live: 180000 # 注意單位是毫秒

mybatis-plus:
  configuration:
    #在映射實體或者屬性時,將資料庫中表名和欄位名中的下劃線去掉,按照駝峰命名法映射
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID
reggie:
  path: E:\編程內容\實戰項目\瑞吉外賣\Code\reggie\imgs\

至此我們的Sharding-JDBC的讀寫分離就完成了

項目測試讀寫分離

最後我們可以對讀寫分離操作進行簡單的測試

首先我們需要開啟相關需要的設備:

  • 虛擬機以及資料庫
  • 本地Redis資料庫

我們開啟項目後,主要進行三方面測試:

  • 無論何時,主庫與從庫的數據保持一致
  • 進行增刪改操作時,所進行的資料庫操作是針對主庫的資料庫操作
  • 進行查詢操作時,所進行的資料庫操作是針對從庫的資料庫操作

結束語

該篇內容到這裡就結束了,希望能為你帶來幫助~

附錄

該文章屬於學習內容,具體參考B站黑馬程式設計師的Java項目實戰《瑞吉外賣》

這裡附上影片鏈接:項目優化Day2-01-本章內容介紹_嗶哩嗶哩_bilibili

Exit mobile version