读写分离
理论基础
问题描述
在当前系统中,后台管理系统及移动端的用户在进行数据访问时都是直接操作数据库MySQL的。
而MySQL只有一台,那么就可能会存在如下问题:
读(select)和写(insert update delete)所有压力都由一台数据库承担,压力大
数据库服务器磁盘损坏则数据丢失,单点故障
解决方案
为了解决上述提到的两个问题,我们可以准备多台MySQL,其中一台主(Master)服务器,多台从(Slave)服务器,同时对外提供服务。
主数据库负责数据的写入操作,而从数据库则负责数据的查询操作,我们把这种思路称之为读写分离,
而在这里存在一个问题,就是主库中的数据要实时的向各个从库进行同步,以保证整个数据系统的完整性,我们把这个过程称为主从复制。
主从复制
介绍
MySQL数据库默认是支持主从复制的,底层基于MySQL数据库自带的 二进制日志 功能实现。
二进制日志: 也成为binlog,这是MySQL自带的一种日志功能,它会记录下所有的增删改语句,但是不包括查询语句。
主从复制原理:
MySQL的主从复制其实就是由主库生成binlog,里面记录用户对数据库产生影响的操作。
从库负责读取这个日志,并进行解析得到对应的SQL语句,然后在自己所在的服务器上执行,就可以得到跟主库一致的数据了。
MySQL复制过程分成三步:
master将数据变更写入二进制日志
slave将master的二进制日志拷贝到它的中继日志(relay log)
slave重做中继日志中的事件,生成对应的数据存储到自己的数据区
实现(根据视频来做)
实现MySQL的主从复制其实非常简单,只需要对MySQL进行简单的配置即可
① 准备服务器
提前准备两台服务器,并且在服务器中安装MySQL,服务器的信息如下:
数据库 | IP | 数据库版本 |
---|---|---|
Master | 192.168.136.135 | 5.5.49-log |
Slave | 192.168.136.136 | 5.5.49-log |
② 防火墙设置
分别修改两台服务器的防火墙策略,放行3306端口
③ 主库配置
下面操作仅仅在master库上执行
# 1. 修改mysql的配置文件/etc/my.cnf,保证下面两项
vim /etc/my.cnf
[mysqld]
log-bin=mysql-bin #[必须]启用二进制日志(当前默认已开启)
server-id=1 #[必须]服务器唯一ID(当前默认就是1)
# 2. 重启mysql服务
systemctl restart mysql
# 3. 登录mysql
mysql -uroot -proot
# 4. 在mysql命令行中,创建数据同步的用户并授权
# 本操作会在mysql中创建一个账户xiaoming,密码为Root@123456,并且给xiaoming用户授予REPLICATION SLAVE权限。
# 常用于建立复制时所需要用到的用户权限,也就是slave必须被master授权具有该权限的用户,才能通过该用户复制。
GRANT REPLICATION SLAVE ON *.* to 'xiaoming'@'%' identified by 'Root@123456';
# 在mysql命令行中,查看master同步状态
show master status;
④ 从库配置
下面操作仅仅在slave库上执行
# 1. 修改mysql的配置文件/etc/my.cnf,保证下面两项
vim /etc/my.cnf
[mysqld]
log-bin=mysql-bin #[必须]启用二进制日志(当前默认已开启)
server-id=2 #[必须]服务器唯一ID(当前默认1,必须手动修改成2)
# 2. 重启mysql服务
systemctl restart mysql
# 3. 登录mysql
mysql -uroot -proot
# 4. 在mysql命令行中,执行复制的操作
# 注意,这条命令中各项参数,需要根据实际情况进行修改
change master to
master_host='192.168.136.128', -- 主节点ip地址(需要改)
master_user='xiaoming', -- 主节点创建的用于主从复制的账号
master_password='Root@123456', -- 上面账号的密码
master_log_file='mysql-bin.000004', -- 从主节点哪个日志文件开始同步(需要改)
master_log_pos=198; -- 从主节点日志文件的哪个位置开始同步(需要改)
# 5. 在mysql命令行中,开始复制操作
# 启动I/O线程从主库读取binlog,并存储到relaylog中继日志文件中。
# 启动SQL线程读取中继日志,解析后在从库重放。
start slave;
# 6. 在mysql命令行中,查看主从复制的状态
show slave status;
测试
到此为止,主从复制环境已经搭建好了,接下来,我们可以连接上两台MySQL服务器进行测试。
测试时,我们只需要在主库Master执行操作,查看从库Slave中是否将数据同步过去即可。
① 在master中创建数据库itcast, 刷新slave查看是否可以同步过去
② 在master的itcast数据下创建user表, 刷新slave查看是否可以同步过去
③ 在master的user表中插入一条数据, 刷新slave查看是否可以同步过去
读写分离
背景介绍
面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈。
通过读写分离,可以有效的降低单台数据库的访问压力, 提高访问效率,也可以避免单机故障。
主从复制已经完成了,那么在项目中,如何通过java代码来完成读写分离呢,
如何在执行select的时候查询从库,而在执行insert、update、delete的时候,操作主库呢?
这个时候,我们就需要介绍一个新的技术 ShardingJDBC。
ShardingJDBC
Sharding-JDBC定位为轻量级Java框架,完全兼容JDBC和各种ORM框架,可以在程序中轻松的实现数据库读写分离。具有以下几个特点:
-
适用于任何基于JDBC的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
-
支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
-
支持任意实现JDBC规范的数据库,目前支持MySQL,Oracle,SQLServer,PostgreSQL以及任何遵循SQL92标准的数据库。
实现
① 数据库环境
在主库中创建一个数据库rw, 并且创建一张表, 该数据库及表结构创建完毕后会自动同步至从数据库,SQL语句如下:
create database rw;
use rw;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`address` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
② 创建rw-demo工程,导入依赖
<properties>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/>
</parent>
<dependencies>
<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>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.2.3</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>
③ 创建实体类
package com.itheima.domain;
import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable {
private Long id;
private String name;
private int age;
private String address;
}
④ 创建Mapper
package com.itheima.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.domain.User;
public interface UserMapper extends BaseMapper<User> {
}
⑤ 创建Servie接口
package com.itheima.service;
import com.itheima.domain.User;
import java.util.List;
public interface UserService {
//查询所有
List<User> findAll();
//保存
void insert(User user);
//更新
void update(User user);
//删除
void delete(Long id);
}
⑥ 创建service实现类
package com.itheima.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.itheima.domain.User;
import com.itheima.mapper.UserMapper;
import com.itheima.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public List<User> findAll() {
return userMapper.selectList(new QueryWrapper<User>());
}
@Override
public void insert(User user) {
userMapper.insert(user);
}
@Override
public void update(User user) {
userMapper.updateById(user);
}
@Override
public void delete(Long id) {
userMapper.deleteById(id);
}
}
⑦ 创建Controller
package com.itheima.controller;
import com.itheima.domain.User;
import com.itheima.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class UserController {
@Autowired
private UserService userService;
//查询
@RequestMapping("/user/findAll")
public List<User> findAll() {
return userService.findAll();
}
//保存
@RequestMapping("/user/insert")
public void insert(User user) {
userService.insert(user);
}
//修改
@RequestMapping("/user/update")
public void update(User user) {
userService.update(user);
}
//删除
@RequestMapping("/user/delete")
public void update(Long id) {
userService.delete(id);
}
}
⑧ 创建主类
package com.itheima;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.itheima.mapper")
public class RwApplication {
public static void main(String[] args) {
SpringApplication.run(RwApplication.class, args);
}
}
⑨ 添加配置文件
在application.yml中增加配置
spring:
shardingsphere:
datasource:
names:
master,slave
# 主数据源
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.136.129:3306/rw?characterEncoding=utf-8
username: root
password: root
# 从数据源
slave:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.136.130:3306/rw?characterEncoding=utf-8
username: root
password: root
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: #该配置项的目的,就是如果当前项目中存在同名的bean,后定义的bean会覆盖先定义的。
allow-bean-definition-overriding: true
⑩ 测试
新增:
http://localhost:8080/user/insert?name=tom&age=10&address=beijing
查询:
http://localhost:8080/user/findAll
修改
删除
shardingJDBC工作流程
项目整改
在项目父工程中的pom.xml增加依赖
<!--shardingJdbc-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
** 在项目的application.yml中配置数据源相关信息**
server:
port: 8080
spring:
application:
name: reggie-web-manage # 应用名称
servlet:
multipart:
max-request-size: 100MB # 最大请求文件大小,默认10MB
max-file-size: 100MB # 单个请求文件大小,默认1MB
redis:
host: localhost
port: 6379
database: 0
shardingsphere:
datasource:
names:
master,slave
master: #主数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.136.129:3306/reggie-rw?characterEncoding=utf-8
username: root
password: root
slave: #从数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.136.130:3306/reggie-rw?characterEncoding=utf-8
username: root
password: root
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
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:/mappers/**.xml
global-config:
db-config:
id-type: ASSIGN_ID # id生成策略类型
reggie:
oss:
key: LTAI5tNmH22y9C7AxdFEgdNv
secret: AHGC0JSnBuYtYg3BnNvyTuwlwyl27n
endpoint: oss-cn-beijing.aliyuncs.com
bucket: tanhua-gxm
url: https://tanhua-gxm.oss-cn-beijing.aliyuncs.com
测试
启动项目,执行增删改查操作,观察日志