瑞吉外卖优化02-主从复制+Nginx

本笔记源自黑马程序员的视频课程——《瑞吉外卖》,总结了课程笔记、相关知识点以及可能遇到的问题解决方案,并且增加了课程中未实现的功能,供读者参考。笔记全面且条理清晰,希望帮助读者学习和理解这个外卖项目。
本项目全部笔记见:外卖项目笔记合集

1. 读写分离

问题说明

图片

以两台数据库为例:

图片

读写分离,降低单台数据库的访问压力。

1.1 MySQL主从复制

1.1.1 介绍

MySQL主从复制是一个异步的复制过程,底层是基于Mysql数据库自带的二进制日志功能。就是一台或多台MySQL数据库(slave,即从库)从另一台MySQL数据库(master,即主库)进行日志的复制然后再解析日志并应用到自身,最终实现从库的数据和主库的数据保持一致。MySQL主从复制是MySQL数据库自带功能,无需借助第三方工具。

图片

MySQL复制过程分成三步:

  • master将改变记录到二进制日志(binary log)
  • slave将master的binary log拷贝到它的中继日志(relay log)
  • slave重做中继日志中的事件,将改变应用到自己的数据库中

1.1.2 配置-前置条件

克隆虚拟机的方法见此笔记

提前准备好两台服务器,分别安装Mysql并启动服务、用navicat连接上。

登录mysql:mysql -u root -p

navicat连接方法:

  • 打开navicat,新建连接(Connection - mySQL),连接名任意,主机或IP地址填虚拟机端的IP地址196.168.xxx.xxx,用户名root,密码xxx。
  • 将虚拟机端端口3306开放,firewall-cmd --zone=public --add-port=3306/tcp --permanent以及firewall-cmd --reload
  • 在navicat双击新建的连接,即可连接上。

1.1.3 配置-主库Master

第一步:修改Mysql数据库的配置文件 vim /etc/my.cnf

1
2
3
[mysqld]
log-bin=mysql-bin #[必须]启用二进制日志
server-id=100 #[必须]服务器唯一ID。多台数据库服务器的server-id是不同的就行

第二步:重启Mysql服务

systemctl restart mysqld

第三步:登录Mysql数据库mysql -uroot -p,执行下面SQL

GRANT REPLICATION SLAVE ON *.* to 'xiaoming'@'%' identified by 'Root@123456';

上面SQL的作用是创建一个用户xiaoming,密码为Root@123456,并且给xiaoming用户授予REPLICATION SLAVE权限。常用于建立复制时所需要用到的用户权限,也就是slave必须被master授权具有该权限的用户,才能通过该用户复制。

注:MySQL8不能使用上面的一句SQL,需要先创建用户再授权,如下:

1、CREATE USER 'xiaoming' IDENTIFIED BY 'Root@123456';

2、GRANT REPLICATION SLAVE ON *.* TO 'xiaoming'@'%';

3、FLUSH PRIVILEGES;

第四步:执行SQL show master status;,记录File和Position的值

图片

注:上面SQL的作用是查看Master的状态,执行完此SQL后,不要再执行任何操作

1.1.4 配置-从库Slave

第一步:修改Mysql数据库的配置文件,也是 vim /etc/my.cnf

1
2
[mysqld]
server-id=101 #[必须]服务器唯一ID

第二步:重启Mysql服务

systemctl restart mysqld

第三步:登录Mysql数据库 mysql -uroot -p,执行下面SQL

1
2
3
4
5
change master to
master_host='192.168.188.100',master_user='xiaoming',master_password='Root@123456',master_log_file='mysql-bin.000003',master_log_pos=441;

start slave;

master_host是主库ip;master_user是刚才创建的用户名;master_password是刚才创建的用户密码;master_log_file是主库执行show master status;之后显示的文件名;master_log_pos是显示的位置。

第四步:登录Mysql数据库,执行下面SQL,查看从数据库的状态show slave status;

图片

注意这两项(分别对应Slave_IO_Running 和 Slave_SQL_Running)应该都是yes。如果不是,需要改auto.cnf,见克隆虚拟机 第7条。

1.2 读写分离案例

1.2.1 背景

面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈。对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库从库,主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善。

图片

1.2.2 Sharing-JDBC介绍

Sharding-JDBC定位为轻量级Java框架,在Java的JDBC层提供的额外服务。它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。

使用Sharding-JDBC可以在程序中轻松的实现数据库读写分离。

  • 适用于任何基于JDBC的ORM框架,如: JPA,Hibernate,Mybatis,Spring JDBC Template或直接使用JDBC。
  • 支持任何第三方的数据库连接池,如:DBCP,C3PO,BoneCP,Druid,HikariCP等。
  • 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer,PostgreSQL以及任何遵循SQL92标准的数据库。

1.2.3 入门案例

准备工作

1、实现主从复制(1.1节)

2、在主库创建rw数据库,从库也能看到该数据库。在rw数据库中新建数据表user。

图片

使用Sharding-JDBC实现读写分离步骤

1、导入maven坐标

1
2
3
4
5
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>

2、在配置文件中配置读写分离规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
spring:
shardingsphere:
datasource:
names:
master,slave # 名字自定即可。需要在master-data-source-name、slave-data-source-names中对应上
# 主数据源
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.50.158:3306/rw?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username:
password:
# 从数据源
slave:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.50.99:3306/rw?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username:
password:
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。可以在控制台输出sql

3、在配置文件中配置允许bean定义覆盖配置项

1
2
3
spring:
main:
allow-bean-definition-overriding: true

后创建的dataSource会覆盖先创建的dataSource,则不会冲突。

1.3 项目实现读写分离

1.3.1 环境准备(主从复制)

直接使用我们前面在虚拟机中搭建的主从复制的数据库环境即可。

在主库中,创建瑞吉外卖项目的业务数据库reggie,并导入相关表结构和数据。

1、使用navicat工具,在主库新建数据库:

图片

2、空白处点右键,选择运行sql文件,导入表结构和数据:

图片

3、redis数据库要打开

1.3.2 代码构造

在项目中加入Sharding-JDBC实现读写分离步骤,和1.2.3节步骤一样。新建一个分支v1.1,在该分支上操作:

1、导入maven坐标

2、在配置文件中配置读写分离规则

3、在配置文件中配置允许bean定义覆盖配置项

测试没问题之后,将该v1.1分支提交推送到远程仓库。

图片

回到主分支后,将v1.1分支合并至主分支。

图片

2. Nginx

2.1 Nginx概述

Nginx是一款轻量级的web服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好,中国大陆使用nginx的网站有:百度、京东、新浪、网易、腾讯、淘宝等。

Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点开发的,第一个公开版本0.1.e发布于2004年10月4日。

2.1.1 下载安装

可以到Nginx官方网站下载Nginx的安装包,地址为: https://nginx.org/en/download.html

为虚拟机安装Nginx过程:

1、安装依赖包yum -y install gcc pcre-devel zlib-devel openssl openssl-devel

2、下载Nginx安装包wget https://nginx.org/download/nginx-1.16.1.tar.gz (需要先yum install wget)

3、解压tar -zxvf nginx-1.16.1.tar.gz

4、cd nginx-1.16.1

5、./configure --prefix=/usr/local/nginx 将nginx安装到指定目录/usr/local/nginx(先提前创建好该目录mkdir -p /usr/local/nginx

6、make && make install 先编译,后安装

注:上面第2步,我是在本地从网络下载,再远程传到虚拟机的。本地下载下来的安装包是.tar格式,因此第3步,使用tar -xf nginx-1.16.1.tar命令。后续步骤相同。

2.1.2 Nginx目录结构

安装完Nginx后,我们先来熟悉—下Nginx的目录结构,如图:

图片

重点目录/文件:

conf/nginx.conf nginx配置文件
html 存放静态文件 (htmI、css、Js等)
logs 日志目录,存放日志文件
sbin/nginx 二进制文件,用于启动、停止Nginx服务

2.2 Nginx命令

2.2.1 查看版本

查看Nginx版本,可以先cd /usr/local/nginx/sbin,然后在sbin目录下使用命令:./nginx -v

2.2.2 检查配置文件正确性

在启动Nginx服务之前,可以先检查一下conf/nginx.conf文件配置是否有错误,命令为:./nginx -t

图片

2.2.3 启动和停止nginx服务

在sbin目录下执行以下命令

1、启动Nginx服务:./nginx

在浏览器输入服务器地址,默认端口80。如果访问不到,关闭防火墙systemctl stop firewalld

图片

2、启动完成后可以查看Nginx进程:ps -ef|grep nginx

3、停止Nginx服务:./nginx -s stop

2.2.4 重新加载配置文件

当修改Nginx配置文件(/usr/local/nginx/conf目录下的nginx.conf)后,需要重新加载才能生效,可以使用命令重新加载配置文件:./nginx -s reload

配置环境变量:

将nginx二进制文件路径配置到系统环境变量中,这样在sbin目录之外的其他目录下,也可以使用nginx命令。

1、vim /etc/profile

2、在PATH中追加路径

PATH=/usr/local/nginx/sbin:$JAVA_HOME/bin:$PATH

3、source /etc/profile 使之生效

2.3 Nginx配置文件结构

Nginx配置文件(conf/nginx.conf)整体分为三部分:

  • 全局块 和Nginx运行相关的全局配置
  • events块 和网络连接相关的配置
  • http块 代理、缓存、日志记录、虚拟主机配置
    • http全局块
    • Server块
      • Server全局块
      • location块

注意:http块中可以配置多个Server块,每个Server块中可以配置多个location块。

图片

2.4 Nginx具体应用

2.4.1 部署静态资源

Nginx可以作为静态web服务器来部署静态资源。静态资源指在服务端真实存在并且能够直接展示的一些文件,比如常见的html页面、css文件、js文件、图片、视频等资源。

相对于Tomcat,Nginx处理静态资源的能力更加高效,所以在生产环境下,一般都会将静态资源部署到Nginx中。

将静态资源部署到Nginx非常简单,只需要将文件复制到Nginx安装目录(/usr/local/nginx)下的html目录中即可。

Nginx配置文件(conf/nginx.conf)中的server块:

1
2
3
4
5
6
7
server {
listen 80; #监听端口
server_name localhost; #服务器名称
location / { #匹配客户端请求url。什么请求由该location块处理
root html; #指定静态资源根目录
index index.html index.htm; #指定默认首页
}

注:server块可以配置多个。

2.4.2 反向代理

1、正向代理vs反向代理

正向代理

是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。

例如,通过代理服务器访问谷歌:

图片

正向代理的典型用途是为在防火墙内的局域网客户端提供访问lnternet的途径。

正向代理一般是在客户端设置代理服务器(客户端知道有代理服务器存在),通过代理服务器转发请求,最终访问到目标服务器。

反向代理

反向代理服务器位于用户与目标服务器之间,但是对于用户而言,反向代理服务器就相当于目标服务器,即用户直接访问反向代理服务器就可以获得目标服务器的资源,反向代理服务器负责将请求转发给目标服务器。

(客户端不需要知道反向代理服务器存在)用户不需要知道目标服务器的地址,也无须在用户端作任何设定。

图片

来自网友的总结,可供参考:
正向代理是客户端的代理,帮助客户端访问其无法访问的服务器资源。
反向代理是服务器的代理,帮助服务器做负载均衡,安全防护等。

2、配置反向代理

图片

步骤:

  • 在反向代理服务器192.168.138.100中安装、启动nginx。

  • 在IDEA中编写java程序。此处编写了简单的HelloController。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @RestController
    @RequestMapping("/hello")
    public class HelloController {
    @GetMapping
    public String hello(){
    System.out.println("hello world...");
    return "8080";
    }
    }
  • 将程序打jar包,传至web服务器192.168.138.101。

  • 在web服务器192.168.138.101中部署该java程序 java -jar helloworld-1.0-SNAPSHOT.jar

  • 配置反向代理

    • 在反向代理服务器192.168.138.100中,在/usr/local/nginx/conf下,修改配置文件vim nginx.conf,将下面配置加入配置文件。
    1
    2
    3
    4
    5
    6
    7
    8
    server {
    listen 82;
    server_name localhost;

    location / {
    proxy_pass http://192.168.138.101:8080; #反向代理配置,将请求转发至指定服务
    }
    }
    • 重新加载配置文件nginx -s reload
  • 请求反向代理服务器的82端口192.168.138.100:82/hello(记得关闭82端口防火墙),就会将请求转发至192.168.138.101:8080/hello。

图片

2.4.3 负载均衡

负载均衡介绍

早期的网站流量和业务功能都比较简单,单台服务器就可以满足基本需求。但是随着互联网的发展,业务流量越来越大,并且业务逻辑也越来越复杂,单合服务器的性能及单点故障问题就凸显出来了。因此需要多台服务器组成应用集群,进行性能的水平扩展以及避免单点故障出现。

  • 应用集群:将同一应用部署到多台机器上,组成应用集群。接收负载均衡器分发的请求,进行业务处理并返回响应数据。
  • 负载均衡器:根据对应的负载均衡算法,将用户请求分发到应用集群中的一台服务器进行处理。

图片

配置负载均衡

步骤:

  • 在IDEA中编写另一个java程序。此处HelloController方法返回值改为8081,并修改application.yml中的端口号为8081。将此java程序打包为helloworld-1.0-SNAPSHOT2.jar。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @RestController
    @RequestMapping("/hello")
    public class HelloController {
    @GetMapping
    public String hello(){
    System.out.println("hello world...");
    return "8081";
    }
    }
  • 将jar包传至web服务器192.168.138.101。

  • 在web服务器192.168.138.101中部署该java程序 java -jar helloworld-1.0-SNAPSHOT2.jar,用192.168.138.101:8081和前面已经部署的192.168.138.101:8080代表两个服务器。

  • 配置负载均衡

    • 在nginx服务器192.168.138.100中,在/usr/local/nginx/conf下,修改配置文件vim nginx.conf,将下面配置加入配置文件。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      upstream targetserver{    # upstream指令可以定义一组服务器
      server 192.168.138.101:8080;
      server 192.168.138.101:8081;
      }

      server {
      listen 8080;
      server_name localhost;
      location / {
      proxy_pass http://targetserver;
      }
      }

      其中,upstream是固定指令;targetserver表示一组服务器集群,可修改名字,只要与proxy_pass中相对应即可。

    • 重新加载配置文件nginx -s reload

  • 当请求nginx服务器8080端口(192.168.138.100:8080/hello),就会根据负载均衡算法,转发至targetserver其中的一台服务器。(记得关闭web服务器192.168.138.101的8081端口防火墙)

负载均衡策略:

图片

例如:

1
2
3
4
upstream targetserver{    # upstream指令可以定义一组服务器
server 192.168.138.101:8080 weight=10;
server 192.168.138.101:8081 weight=5;
}

权重越大,被分发到该服务器的几率越大。