我把石头还给石头
让胜利的胜利
今夜青稞只属于她自己
一切都在生长
今夜我只有美丽的戈壁空空
姐姐,今夜我不关心人类,我只想你
1988年7月25日火车经德令哈
— — 海子
一、MySQL主从复制
1. 读写分离的意义
后台管理系统及移动端的用户,在进行数据访问时,都是直接操作数据库MySQL的。
而在当前,MySQL服务器只有一台,那么就可能会存在如下问题:
- 读和写所有压力都由一台数据库承担,压力大
- 数据库服务器磁盘损坏则数据丢失,单点故障
为了解决上述提到的两个问题,可以准备两台MySQL,一台主 Master
服务器,一台从 Slave
服务器,主库的数据变更,需要同步到从库中 (主从复制) 。而用户在访问时,如果是写操作 insert
、update
、delete
,则直接操作主库;如果是读 select
操作,则直接操作从库(在这种读写分离的结构中,从库是可以有多个的),这种结构我们称为 读写分离 。
2. 介绍
MySQL主从复制是一个异步的复制过程,底层是基于Mysql数据库自带的 二进制日志 功能。
就是一台或多台MySQL数据库(slave,即从库)从另一台MySQL数据库(master,即主库)进行日志的复制,然后再解析日志并应用到自身,最终实现 从库 的数据和 主库 的数据保持一致。MySQL主从复制是MySQL数据库自带功能,无需借助第三方工具。
MySQL复制过程分成三步:
MySQL master
将数据变更写入二进制日志binary log
slave
将master
的binary log
拷贝到它的中继日志relay log
slave
重做中继日志中的事件,将数据变更反映它自己的数据
3. 搭建
⑴. 创建两台服务器
提前准备两台服务器,并且在服务器中安装 MySQL
,服务器的信息如下:
数据库 | IP | 数据库版本 |
---|---|---|
Master | 192.168.200.200 | 5.7.25 |
Slave | 192.168.200.201 | 5.7.25 |
登录 mysql:
# 防火墙防开3306端口
firewall-cmd --zone=public --add-port=3306/tcp --permanent
# 查看防火墙开发端口列表
firewall-cmd --zone=public --list-ports
# 启动
systemctl start mysqld
# 登录mysql
mysql -uroot -p
⑵. 主库配置
服务器: 192.168.200.200
①. 修改Mysql数据库的配置文件/etc/my.cnf
log-bin=mysql-bin #[必须]启用二进制日志
server-id=200 #[必须]服务器唯一ID(唯一即可)
②. 重启Mysql服务
# 重启Mysql服务
systemctl restart mysqld
③. 创建数据同步的用户并授权
# 登录mysql,创建用户并授权
GRANT REPLICATION SLAVE ON *.* to 'xiaoming'@'%' identified by 'Root@123456';
# 目前mysql5.7默认密码校验策略等级为 MEDIUM , 该等级要求密码组成为: 数字、小写字母、大写字母 、特殊字符、长度至少8位
上面SQL的作用是创建一个用户 xiaoming ,密码为 Root@123456 ,并且给xiaoming用户授予REPLICATION SLAVE权限。常用于建立复制时所需要用到的用户权限,也就是slave必须被master授权具有该权限的用户,才能通过该用户复制。
④. 登录Mysql数据库,查看master同步状态
# 执行下面SQL,记录下结果中File和Position的值
show master status;
⑶. 从库配置
服务器: 192.168.200.201
①. 修改Mysql数据库的配置文件/etc/my.cnf
server-id=201 #[必须]服务器唯一ID
②. 重启Mysql服务
systemctl restart mysqld
③. 登录Mysql数据库,设置主库地址及同步位置
change master to master_host='192.168.200.200',master_user='xiaoming',master_password='Root@123456',master_log_file='mysql-bin.000001',master_log_pos=154;
start slave;
master_host : 主库的IP地址
master_user : 访问主库进行主从复制的用户名(上面在主库创建的)
master_password : 访问主库进行主从复制的用户名对应的密码
master_log_file : 从哪个日志文件开始同步(上述查询master状态中展示的有)
master_log_pos : 从指定日志文件的哪个位置开始同步(上述查询master状态中展示的有)
④. 查看从数据库的状态
show slave status;
然后通过状态信息中的 Slave_IO_running
和 Slave_SQL_running
可以看出主从同步是否就绪,如果这两个参数全为 Yes
,表示主从同步已经配置完成。
MySQL命令行技巧:
\G : 在MySQL的sql语句后加上\G,表示将查询结果进行按列打印,可以使每个字段打印到单独的行。即将查到的结构旋转90度变成纵向;
⑷. 测试
主从复制的环境,已经搭建好了,接下来,我们可以通过Navicat连接上两台MySQL服务器,进行测试。测试时,我们只需要在主库Master执行操作,查看从库Slave中是否将数据同步过去即可。
二、读写分离案例
1. 背景介绍
面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈。 对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库,主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善。
通过读写分离,就可以降低单台数据库的访问压力, 提高访问效率,也可以避免单机故障。
2. ShardingJDBC介绍
Sharding-JDBC 定位为轻量级Java框架,在Java的 JDBC
层提供的额外服务。 它使用客户端直连数据库,以 jar包
形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC
驱动,完全兼容JDBC
和各种 ORM
框架。使用 Sharding-JDBC 可以在程序中轻松的实现数据库读写分离。
Sharding-JDBC具有以下几个特点:
- 适用于任何基于JDBC的ORM框架: JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC
- 支持任何第三方的数据库连接池: DBCP, C3P0, BoneCP, Druid, HikariCP等
- 支持任意实现JDBC规范的数据库: MySQL,Oracle,SQLServer,PostgreSQL以及任何遵循SQL92标准的数据库。
3. 数据库环境
在主库中创建一个数据库 rw
, 并且创建一张表,该数据库及表结构创建完毕后会自动同步至从数据库,SQL
语句如下:
create database rw default charset utf8mb4;
use rw;
CREATE TABLE `user` (
`id` int(11) 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;
4. 初始工程导入
百度网盘链接(无提取码): https://pan.baidu.com/s/1BkB9pTqnBYpac047f4fTrA?pwd=java
本案例主要是演示一下读写分离操作,可以直接导入资料中提供的demo工程(rw_demo),在demo工程中,我们已经完成了user的增删改查操作。
5. 读写分离配置
⑴. 在pom.xml中增加shardingJdbc的maven坐标
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
⑵. 在application.yml中增加数据源的配置
spring:
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.200.200:3306/rw?characterEncoding=utf-8
username: root
password: root
# 从数据源
slave:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.200.201: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
⑶. 在application.yml中增加配置
spring:
main:
allow-bean-definition-overriding: true
该配置项的目的,就是如果当前项目中存在同名的bean,后定义的bean会覆盖先定义的。
6. 测试
使用 shardingjdbc
来实现读写分离,直接通过上述简单的配置就可以了。
配置完毕之后,就可以重启服务,通过 postman
来访问 controller
的方法,来完成用户信息的增删改查,可以通过 debug
及日志的方式来查看每一次执行增删改查操作,使用的是哪个数据源,连接的是哪个数据库。
⑴. 保存数据
控制台输出日志,可以看到操作master主库:
⑵. 修改数据
控制台输出日志,可以看到操作master主库:
⑶. 查询数据
控制台输出日志,可以看到操作slave主库:
⑷. 删除数据
控制台输出日志,可以看到操作master主库:
三、项目实现读写分离
1. 数据库环境准备
直接使用前面在虚拟机中搭建的主从复制的数据库环境即可。
在主库中创建瑞吉外卖项目的业务数据库reggie, 并导入相关表结构和数据(可以将之前在本地开发时使用的数据库数据导出, 然后导入到服务器中的主库即可)
⑴. 将自己本地的reggie数据库的数据导出SQL文件
这样做的话,我们之前自己开发时,添加的测试数据都还在的,便于测试。
⑵. 在主数据库master中,创建数据库reggie,并导入该SQL文件
master中创建数据库,会自动同步至slave从库
在master的reggie中导入sql文件
2. 读写分离配置
⑴. 在项目的pom.xml增加依赖
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
⑵. 在项目的application.yml中配置数据源相关信息
spring:
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.200.200:3306/reggie?characterEncoding=utf-8
username: root
password: root
# 从数据源
slave:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.200.201:3306/reggie?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
3. 功能测试
配置完毕之后,我们启动项目进行测试,直接访问系统管理后台的页面,然后执行相关业务操作,看控制台输出的日志信息即可。
⑴. 查询操作:
⑵. 更新操作:
⑶. 插入操作:
⑷. 删除操作:
四、Nginx
1. 介绍
Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好,中国大陆使用nginx的网站有:百度、京东、新浪、网易、腾讯、淘宝等。
2. 下载和安装
⑴. 下载
在 Nginx 的官网的下载页面中(http://nginx.org/en/download.html),就展示了当前 Nginx 版本,并提供了下载的连接。 如下:
在本项目中,Nginx 选择的是稳定版本的 1.16
这个版本
百度网盘链接(无提取码): https://pan.baidu.com/s/1BkB9pTqnBYpac047f4fTrA?pwd=java
⑵. 安装
# 安装依赖包: 由于nginx是基于c语言开发的,所以需要安装c语言的编译环境,及正则表达式库等第三方依赖库
yum -y install gcc pcre-devel zlib-devel openssl openssl-devel
# 下载Nginx安装包
yum install wget
wget https://nginx.org/download/nginx-1.16.1.tar.gz
# 解压nginx压缩包
tar -zxvf nginx-1.16.1.tar.gz
# 配置Nginx编译环境
cd nginx-1.16.1
./configure --prefix=/usr/local/nginx
# 编译&安装
make & make install
3. 目录结构
安装完 Nginx 后,我们可以切换到 Nginx 的安装目录 /usr/local/nginx
,先来熟悉一下 Nginx 的目录结构,如下图:
# 将指定的目录以树状结构展示出来
yum install tree
重点目录和文件如下:
目录/文件 | 说明 | 备注 |
---|---|---|
conf | 配置文件的存放目录 | |
conf/nginx.conf | Nginx的核心配置文件 | conf下有很多nginx的配置文件,我们主要操作这个核心配置文件 |
html | 存放静态资源(html, css, ) | 部署到Nginx的静态资源都可以放在html目录中 |
logs | 存放nginx日志(访问日志、错误日志等) | |
sbin/nginx | 二进制文件,用于启动、停止Nginx服务 |
4. 命令
⑴. 常用命令
Nginx 中,二进制可执行文件 nginx
存放在 sbin
目录下,虽然只有一个可执行文件,但是可以通过该指令配合不同的参数达到更加强大的功能。接下来,就演示一下 Nginx 常见指令, 在执行下面的指令时,都需要在 /usr/local/nginx/sbin/
目录下执行。
①. 查看版本
./nginx -v
②. 检查配置文件
修改了 nginx.conf 核心配置文件之后,在启动 Nginx 服务之前,可以先检查一下 conf/nginx.conf 文件配置的是否有错误,命令如下:
./nginx -t
③. 启动
./nginx
启动之后,我们可以通过 ps -ef
指令来查看 nginx 的进程是否存在。
注意: nginx服务启动后,默认就会有两个进程。
启动之后,我们可以直接访问 Nginx 的 80
端口, http://192.168.200.200
# 要想正常访问Nginx,需要关闭防火墙或开放指定端口号,执行的指令如下:
# 关闭防火墙
systemctl stop firewalld
# 开放80端口
firewall-cmd --zone=public --add-port=80/tcp --permanent
firewall-cmd --reload
④. 停止
./nginx -s stop
# 查看nginx的进程
ps -ef|grep nginx
⑤. 重新加载
当修改了 Nginx 配置文件后,需要重新加载才能生效,可以使用下面命令重新加载配置文件:
./nginx -s reload
⑵. 环境变量配置
在上述使用 nginx 命令在进行服务的启动、停止、重新加载时,都需要用到一个指令 nginx ,而这个指令是在 nginx/sbin
目录下的,我们每一次使用这个指令都需要切换到 sbin
目录才可以,使用相对繁琐。可以配置nginx的环境变量,就能在任意目录下都可以执行该指令。
通过 vim 编辑器,打开 /etc/profile
文件, 在 PATH
环境变量中增加 nginx 的 sbin
目录,如下:
修改完配置文件之后,需要执行 source /etc/profile
使文件生效。 接下来,我们就可以在任意目录下执行nginx 的指令了,如:
5. 应用
⑴. 配置文件结构
nginx 的配置文件 conf/nginx.conf
整体上分为三部分: 全局块、events块、http块。
区域 | 职责 |
---|---|
全局块 | 配置和nginx运行相关的全局配置 |
events块 | 配置和网络连接相关的配置 |
http块 | 配置代理、缓存、日志记录、虚拟主机等配置 |
在全局块、events块以及http块中,我们经常配置的是http块
在http块中可以包含多个server块,每个server块可以配置多个location块
⑴. 部署静态资源
①. 介绍
Nginx 可以作为静态web服务器来部署静态资源。这里所说的静态资源是指在服务端真实存在,并且能够直接展示的一些文件,比如常见的html页面、css文件、js文件、图片、视频等资源。
相对于Tomcat,Nginx处理静态资源的能力更加高效,所以在生产环境下,一般都会将静态资源部署到Nginx中。
将静态资源部署到 Nginx 非常简单,只需要将文件复制到 Nginx 安装目录下的 html
目录中即可。
server {
listen 80; #监听端口
server_name localhost; #服务器名称
location / { #匹配客户端请求url
root html; #指定静态资源根目录
index index.html; #指定默认首页
}
}
②. 测试
在资料中,提供了一个静态的 html
文件,需要将这个文件部署到 nginx 中,然后通过 nginx 访问 html
静态资源。
将静态资源上传到 /usr/local/nginx/html 目录
启动nginx
访问: http://192.168.200.200/hello.html
访问: http://192.168.200.200 ,访问该地址,访问的是nginx的默认首页
配置首页
如果我们需要将 hello.html
作为 nginx 的首页,可以修改 location
的 index
指令,配置为hello.html
,如下:
配置完毕后,我们可以通过指令,来检查配置文件是否配置正确: nginx -t
配置文件修改了,我们需要重新加载一下,才可以生效:
nginx -s reload
6. 反向代理
⑴. 正向代理
正向代理服务器是一个位于客户端和原始服务器 origin server 之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。
正向代理的典型用途是为在防火墙内的局域网客户端提供访问Internet的途径。
正向代理一般是在客户端设置代理服务器,通过代理服务器转发请求,最终访问到目标服务器。
⑵. 反向代理
反向代理服务器位于用户与目标服务器之间,但是对于用户而言,反向代理服务器就相当于目标服务器,即用户直接访问反向代理服务器就可以获得目标服务器的资源,反向代理服务器负责将请求转发给目标服务器。用户不需要知道目标服务器的地址,也无须在用户端作任何设定,对于用户来说,访问反向代理服务器是完全无感知的。
那么在本小节,我们就是要使用nginx来作为反向代理服务器使用。 在 nginx 中,我们可以在nginx.conf
中配置反向代理:
```properties
server {
listen 82;
server_name localhost;
location / {
proxy_pass http://192.168.200.201:8080; #反向代理配置,将请求转发到指定服务
}
}
上述配置的含义为: 当我们访问 nginx 的 82
端口时,根据反向代理配置,会将请求转发到 http://192.168.200.201:8080 对 应的服务上。
⑶. 测试
需求: 在 192.168.200.201
这台服务器中部署了java应用,运行端口为 8080
,并提供了一个可访问的链接 /hello
。现在我们需要在访问 nginx 的 82
端口时,通过 nginx 将请求转发到 192.168.200.201:8080
的服务。
在192.168.200.201部署服务并启动
将资料中提供的 helloworld-1.0-SNAPSHOT.jar
上传到服务器端,并通过指令 java -jar helloworld-1.0-SNAPSHOT.jar
运行服务。
在192.168.200.200中的nginx.conf中配置反向代理
进入 nginx 的安装目录,并编辑配置文件 nginx.conf
:
cd /usr/local/nginx/conf/
vim nginx.conf
在http块中,再添加一个server块虚拟主机的配置,监听 82
端口,并配置反向代理 proxy_pass
:
server {
listen 82;
server_name localhost;
location / {
proxy_pass http://192.168.200.201:8080; #反向代理配置,将请求转发到指定服务
}
}
检查配置文件,并重新加载
nginx -t
nginx -s reload
访问
注意: 在访问82端口时,有可能访问不通,原因是以为防火墙中没有开放端口号。我们可以通过两种方式来解决该问题:==
# A. 关闭防火墙
systemctl stop firewalld
# B. 开发指定端口
firewall-cmd --zone=public --add-port=82/tcp --permanent
firewall-cmd --reload
7. 负载均衡
⑴. 概念介绍
早期的网站流量和业务功能都比较简单,单台服务器就可以满足基本需求,但是随着互联网的发展,业务流量越来越大并且业务逻辑也越来越复杂,单台服务器的性能及单点故障问题就凸显出来了,因此需要多台服务器组成应用集群,进行性能的水平扩展以及避免单点故障出现。
- 应用集群: 将同一应用部署到多台机器上,组成应用集群,接收负载均衡器分发的请求,进行业务处理并返回响应数据
- 负载均衡器: 将用户请求根据对应的负载均衡算法分发到应用集群中的一台服务器进行处理
此处的负载均衡器,我们将会使用 Nginx 来实现,而 Nginx 的负载均衡是基于反向代理的,只不过此时所代理的服务器不是一台,而是多台。
⑵. 测试
①. 将资料中提供的两个jar包,上传到192.168.200.201服务器上
jar | 运行端口 | 请求链接 | 响应数据 |
---|---|---|---|
8080 | /hello | 8080 | |
8081 | /hello | 8081 |
我们在测试时,并没有那么多服务器,我们可以在一台服务器中启动多个服务,运行在不同的端口号上进行测试。
②. 运行上传上来的两个jar包,运行端口分别是 8080 , 8081
由于我们执行 java -jar
指令会占用前台窗口,所以我们可以开启两个窗口进行测试。
③. 在nginx中配置负载均衡
打开 nginx 的配置文件 nginx.conf
并增加如下配置:
#upstream指令可以定义一组服务器
upstream targetserver{
server 192.168.200.201:8080;
server 192.168.200.201:8081;
}
server {
listen 8080;
server_name localhost;
location / {
proxy_pass http://targetserver;
}
}
④. 重新加载nginx配置文件,访问
nginx -s reload
测试时,我们直接访问 nginx 的 8080
端口 http://192.168.200.200:8080
, 此时 nginx 会根据负载均衡策略,将请求转发到后面的两台服务器。
在上述的测试过程中,我们看到请求均衡的转发到了 8080
和 8081
,因为模式的负载均衡策略是轮询。
注意: 上述所有涉及到的端口号,都需要在对应的服务器的防火墙中开放,或者彻底关闭防火墙
⑶. 负载均衡策略
处理上述默认的轮询策略以外,在 Nginx 中还提供了其他的负载均衡策略,如下:
名称 | 说明 | 特点 |
---|---|---|
轮询 | 默认方式 | |
weight | 权重方式 | 根据权重分发请求,权重大的分配到请求的概率大 |
ip_hash | 依据ip分配方式 | 根据客户端请求的IP地址计算hash值, 根据hash值来分发请求, 同一个IP发起的请求, 会发转发到同一个服务器上 |
least_conn | 依据最少连接方式 | 哪个服务器当前处理的连接少, 请求优先转发到这台服务器 |
url_hash | 依据url分配方式 | 根据客户端请求url的hash值,来分发请求, 同一个url请求, 会发转发到同一个服务器上 |
fair | 依据响应时间方式 | 优先把请求分发给处理请求时间短的服务器 |
权重的配置:
# upstream指令可以定义一组服务器
upstream targetserver{
server 192.168.200.201:8080 weight=10;
server 192.168.200.201:8081 weight=5;
}
上述配置的 weight
权重是相对的,在上述的配置中,效果就是,在大数据量的请求下,最终 8080
接收的请求数是 8081
的两倍。