瑞吉外卖实战项目全攻略——优化篇第二天
该系列将记录一份完整的实战项目的完成过程,该篇属于优化篇第二天,主要负责完成读写分离问题
案例来自B站黑马程序员Java项目实战《瑞吉外卖》,请结合课程资料阅读以下内容
该篇我们将完成以下内容:
- Mysql主从复制
- 读写分离案例
- 项目实现读写分离
Mysql主从复制
该小节会介绍Linux系统上Mysql的主从复制的全流程
主从复制介绍
Mysql主从复制是一个异步的复制过程,底层是基于Mysql数据库自带的二进制日志功能
我们将一台或多台从库(slave)从一台主库(master)进行日志复制然后解析日志并应用到本身,最终实现多台数据库保持数据一致
我们先给出主从复制的流程图:

下面我们简单介绍一下流程:
- master将改变记录到二进制文件(Binary log)
- slave将master的binary log拷贝到它的中继日志(relay log)
- slave重做中继日志的事件,将改变应用到自己的数据库中
主库只能有一个数据库,但从库可以有多个数据库
主从复制前置条件
我们要完成Mysql数据库的主从复制,就需要拥有两台数据库,我们通常采用虚拟机来准备
我们在前面的Linux课程中已经配置了一台虚拟机,我们只需要将该虚拟机进行克隆即可获得另一台相同的虚拟机:

但是需要注意的是我们克隆后的虚拟机的IP和UUID是相同的,我们需要将他们修改过来:
- 修改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
- 修改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
- 保证数据信息一致
目前我们的两台数据库中的信息要保持一致,否则我们在后续操作时会导致错误
主从复制操作实现
下面我们分别从主库和从库两方面来配置主从复制条件
主从复制主库操作实现
下面我们开始主库的配置条件:
- 修改Mysql数据库的配置文件/etc/my.cnf
# 进入配置文件 vim /etc/my.cnf # 在[mysqld]下面复制下面两句即可 [mysqld] log-bin=mysql-bin # 启动二进制日志 server-id=128 # 设置服务器唯一ID
- 重启mysql服务
# 重启mysql服务 systemctl restart mysqld
- 登录数据库,执行下面SQL语句
# 登录数据库 mysql -uroot -p123456 # 执行下列语句(生成一个用户,使其具有查询日志的权力) GRANT REPLICATION SLAVE ON *.* to 'xiaoming'@'%' identified by 'Root@123456';
- 登录数据库,执行下面SQL语句,记录信息
# 执行语句(你将会看到日志相关信息,接下来不要对数据库操作,因为操作会导致日志信息改变) show master status; # 你将会看到File和Position信息,该页面不要改变
主从复制从库操作实现
下面我们开始从库的配置条件:
- 修改Mysql数据库的配置文件/etc/my.cnf
# 进入配置文件 vim /etc/my.cnf # 在[mysqld]下面复制下面两句即可 [mysqld] server-id=129 # 设置服务器唯一ID
- 重启mysql服务
# 重启mysql服务 systemctl restart mysqld
- 登录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;
- 查看是否连接成功
# 查看语句 show slave startsG; # 我们只需要关注三个点:(为下述即为成功) 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规范的数据库
读写分离入门案例
下面我们将通过一个简单的案例来完成读写分离的学习:
- 数据库信息准备
我们在主库中设计了一个rw数据库,并设计了一张User表作为我们的案例实体类

- 初始工程的搭建
我们直接使用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; } }
- 导入Sharding-JDBC的maven坐标
<?xml version="1.0" encoding="UTF-8"?> <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> <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>
- 配置数据库相关信息
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
- 允许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覆盖)
- 实际测试
前面的步骤完成后,我们的读写分离案例就算正式完成了
下面我们只需要开启项目,并按照其Controller的URL进行访问,查看数据库信息即可
项目实现读写分离
最后我们将读写分离在我们的瑞吉外卖实战项目中实现:
数据库数据准备
由于我们之前的项目都是在本地数据库进行测试,所以我们需要将数据重新载入主库数据库中(资料中包含sql语句):

项目实现读写分离
我们来到项目中,完成具体的读写分离操作:
- 导入Sharding-JDBC的maven坐标
<?xml version="1.0" encoding="UTF-8"?> <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> <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>
- 在配置文件中书写读写分离原则和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:编程内容实战项目瑞吉外卖Codereggieimgs
至此我们的Sharding-JDBC的读写分离就完成了
项目测试读写分离
最后我们可以对读写分离操作进行简单的测试
首先我们需要开启相关需要的设备:
- 虚拟机以及数据库
- 本地Redis数据库
我们开启项目后,主要进行三方面测试:
- 无论何时,主库与从库的数据保持一致
- 进行增删改操作时,所进行的数据库操作是针对主库的数据库操作
- 进行查询操作时,所进行的数据库操作是针对从库的数据库操作
结束语
该篇内容到这里就结束了,希望能为你带来帮助~
附录
该文章属于学习内容,具体参考B站黑马程序员的Java项目实战《瑞吉外卖》
这里附上视频链接:项目优化Day2-01-本章内容介绍_哔哩哔哩_bilibili