picaj的分享

Linux企业服务(下)

一、Nginx

1、Nginx介绍

Nginx 是免费的、开源的、⾼性能的HTTP和反向代理服务器、邮件代理服务器、以及TCP/UDP代理服务器 解决C10K问题(10K Connections)

1.1、Nginx所具备的功能
1.1.1、web服务相关的功能支持
1.2、基本特征

2、Nginx架构和进程结构

2.1、Nginx架构

简单来讲,Nginx采用了master-worker架构,由master进程负责通信和调度,woker进程响应具体的请求

image-165

2.2、Nginx进程结构

常见web请求处理机制

Nginx是多进程组织模型,而且是一个由Master主进程和Worker工作进程组成。

image-166

2.2.1、主进程(master process)的功能:
2.2.2、工作进程(worker process的功能:

如下图所示:

image-167

Master进程工作细节:

image-168

2.3、Nginx进程间通信

工作进程是由主进程生成的,主进程由root启用,主进程使用fork()函数,在Nginx服务器启动过程中主进程根据配置文件决定启动工作进程的数量,然后建立一张全局的工作表用于存放当前未退出的所有的工作进程,主进程生成工作进程后会将新生成的工作进程加入到工作进程表中,并建立一个单向的管道并将其传递给工作进程,该管道与普通的管道不同,它是由主进程指向工作进程的单项通道,包含了主进程向工作进程发出的指令、工作进程ID、工作进程在工作进程表中的索引和必要的文件描述符等信息,单向管道,工作进程只能监听内容之后读取指令。主进程与外界通过信号机制进行通信,当接收到需要处理的信号时,它通过管道向相关的工作进程发送正确的指令,每个工作进程都有能力捕获管道中的可读事件,当管道中有可读事件的时候,工作进程就会从管道中读取并解析指令,然后采取相应的执行动作,这样就完成了主进程与工作进程的交互。

工作进程之间的通信原理基本上和主进程与工作进程之间的通信是一样的,只要工作进程之间能够取得彼此的信息,建立管道即可通信,但是由于工作进程之间是完全隔离的,因此一个进程想要知道另外一个进程的状态信息就只能通过主进程来设置了。

为了实现工作进程之间的交互,主进程在生成工作进程之后,在工作进程表中进行遍历,将该新进程的ID以及针对该进程建立的管道句柄传递给工作进程中的其他进程,为工作进程之间的通信做准备,当工作进程1向工作进程2发送指令的时候,首先在主进程给它的其他工作进程工作信息中找到2的进程ID,然后将正确的指令写入指向进程2的管道,工作进程2捕获到管道中的事件后,解析指令并进行相关操作,这样就完成了工作进程之间的通信。

如下图所示:

image-169

2.4、连接建立和请求处理过程
2.5、HTTP协议处理过程

image-170

3、Nginx模块介绍

nginx常见模块类型:

部分第三方模块官方文档:http://nginx.org/en/docs/

4、Nginx安装和部署

Nginx版本分为Mainline version(主要开发版本)、Stable version(当前最新稳定版)、Legacy versions(旧的稳定版)

官方下载地址:http://nginx.org/en/download.html

对于Nginx的安装,我们常用的方法为yum在线安装源码包编译安装

其中:

4.1、YUM部署Nginx
  1. 查看当前yum源中Nginx的版本,Base基本库中一般情况下没有nginx安装包,对于Redhat系列发行版,我们需要先安装红帽提供的EPEL扩展软件仓库。
[root@localhost ~]# yum install -y epel-release
[root@localhost ~]# yum info nginx
可安装的软件包
名称    :nginx
架构    :x86_64
时期       :1
版本    :1.20.1
发布    :10.el7
大小    :588 k
源    :epel/x86_64
简介     A high performance web server and reverse proxy server
网址    :https://nginx.org
协议     BSD
描述     Nginx is a web server and a reverse proxy server for HTTP, SMTP, POP3 and
         : IMAP protocols, with a strong focus on high concurrency, performance and low
         : memory usage.
[root@localhost ~]# yum -y install nginx
  1. 通过systemctl启动nginx服务,并且查看端口号
[root@localhost ~]# systemctl start nginx
[root@localhost ~]# systemctl enable nginx        # enable 开机自启动
[root@localhost ~]# ss -nlt
State       Recv-Q Send-Q Local Address:Port               Peer Address:Port   
LISTEN      0      128         *:80                      *:*
LISTEN      0      128         *:22                      *:*
LISTEN      0      100    127.0.0.1:25                      *:*                
LISTEN      0      128        :::22                     :::*
LISTEN      0      100       ::1:25                     :::*

# 检查80端口是否启动
  1. 访问测试:

最好是关闭防火墙和selinux后进行

[root@localhost ~]# systemctl stop firewalld
[root@localhost ~]# setenforce 0

在浏览器中输入IP地址:192.168.88.140

image-171

看到上述页面,说明已经成功部署好了nginx 的环境。

4.2、官方源码编译部署Nginx

官方源码包地址:http://nginx.org/download/

查看官方编译指令:

[root@localhost ~]# nginx -V
4.2.1、编译

通过编译的方式安装nginx前,建议先移除之前yum安装的nginx,防止多个nginx之间相互冲突~

yum remove -y nginx
  1. 从官网下载源码包,这里以1.20.0为例:
[root@localhost ~]# wget http://nginx.org/download/nginx-1.20.0.tar.gz -P /usr/local/src/
[root@localhost ~]# cd /usr/local/src
# 解压
[root@localhost src]# tar xzvf nginx-1.20.0.tar.gz 
[root@localhost src]# cd nginx-1.20.0
  1. 查看编译帮助
[root@localhost nginx-1.20.0]# ./configure --help
  1. 开始编译

源码安装需要提前准备标准的编译器,GCC的全称是(GNU Compiler collection),其有GNU开发,并以GPL即LGPL许可,是自由的类UNIX即苹果电脑Mac OS X操作系统的标准编译器,因为GCC原本只能处理C语言,所以原名为GNU C语言编译器,后来得到快速发展,可以处理C++,Fortran, pascal, objective-C, java以及Ada等其他语言,此外还需要Automake:工具,以完成自动创建Makefile的工作,Nginx的一些模块需要依赖第三方库,比如: pcre (支持rewrite) ,zlib (支持gzip模块) 和openssI (支持ssI模块) 等

# 先安装编译环境
[root@localhost nginx-1.20.0]# yum -y install gcc pcre-devel openssl-devel zlib-devel
[root@localhost nginx-1.20.0]# useradd -r -s /sbin/nologin nginx
[root@localhost nginx-1.20.0]# ./configure --prefix=/apps/nginx \
--user=nginx \
--group=nginx \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_stub_status_module \
--with-http_gzip_static_module \
--with-pcre \
--with-stream \
--with-stream_ssl_module \
--with-stream_realip_module \
--with-file-aio
[root@localhost nginx-1.20.0]# make -j 2 && make install
  1. 善后工作
[root@localhost nginx-1.20.0]# chown -R nginx.nginx /apps/nginx
# 创建连接文件,使得可以全局使用nginx命令
[root@localhost nginx-1.20.0]# ln -s /apps/nginx/sbin/nginx /usr/bin/
[root@localhost nginx-1.20.0]# nginx -v

可以使用tree命令查看目录结构:

[root@localhost nginx-1.20.0]# yum install -y tree
[root@localhost nginx-1.20.0]# tree /apps/nginx -C -L 1
/apps/nginx
├── conf
├── html
├── logs
└── sbin
4.2.2、启动和停止Nginx
[root@localhost nginx-1.20.0]# nginx 
[root@localhost nginx-1.20.0]# nginx -s stop

浏览器中访问测试:

防火墙和SElinux不要忘记了~

image-172

4.2.3、配置Nginx自启动

创建service文件,这样可以把nginx交由systemctl来管理。

# 复制同一版本的nginx的yum安装生成的service文件
[root@localhost ~]# vim /usr/lib/systemd/system/nginx.service
[Unit]
Description=The nginx HTTP and reverse proxy server
Documentation=http://nginx.org/en/docs/
After=network.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/apps/nginx/run/nginx.pid
ExecStart=/apps/nginx/sbin/nginx -c /apps/nginx/conf/nginx.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID

[Install]
WantedBy=multi-user.target


# 在配置文件中制定进程id存放的文件路径
[root@localhost ~]# mkdir /apps/nginx/run/
[root@localhost ~]# vim /apps/nginx/conf/nginx.conf
pid        /apps/nginx/run/nginx.pid;
4.2.4、验证Nginx服务自启动
[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl enable --now nginx
[root@localhost ~]# ll /apps/nginx/run
总用量 4
-rw-r--r--. 1 root root 5 5月  24 10:07 nginx.pid

5、Nginx配置详解

5.1、主配置文件 nginx.conf

nginx的主配置文件中主要由4部分构成

main block:主配置段,既全局配置段,对http,mail都有效
# 事件驱动相关的配置
event {
  ...
}
# http/https协议相关配置段
http {
  ...
}
# 默认配置文件不包括下面两个块
# mail协议相关配置段
mail {
  ...
}
# stream服务器相关配置段
stream {
  ...
}
# 导入其他路径的配置文件
include /apps/nginx/conf.d/*.conf
5.2、配置文件默认字段及格式说明

image-173

image-174

5.3、全局配置

Main全局配置段常见的配置指令分类

5.3.1、CPU性能相关
user    nginx nginx;    #启动Nginx工作进程的用户和组
worker_processes [number | auto];   #启动Nginx工作进程的数量,一般设为和CPU核心数相同
worker_cpu_affinity 0001 0010 0100 1000;  # 将Nginx工作进程绑定到指定的CPU核心,默认Nginx是不进行进程绑定的,绑定并不是意味着当前nginx进程独占一核心CPU,但是可以保障此进程不会运行在其他核心上,这就极大减少了nginx的工作进程在不同的cpu核心上的来回跳转,减少了cpu对进程的资源分配与回收以及内存管理等,因此可以有效的提升nginx服务器的性能。
CPU MASK:0001    0号CPU
          0010    1号CPU
          0100    2号CPU
          1000    3号CPU
[root@localhost ~]# watch -n.5 'ps axo pid,cmd,psr |grep nginx'
# 查看nginx进程对应的CPU
  6834 nginx: master process /apps   2
 47621 nginx: worker process         0
 47622 nginx: worker process         1
 47623 nginx: worker process         2
 47624 nginx: worker process         3
[root@localhost ~]# while true;do ab -c 1000 -n 2000 http://127.0.0.1/;done
# 压力测试 要先yum -y install httpd-tools

案例-修改cpu的数量

  1. 使用top命令查看虚拟机中cpu的核心数

image-175

  1. 修改nginx.conf,在全局配置中增加对cpu的控制字段
[root@localhost nginx-1.20.0]# vim /apps/nginx/conf/nginx.conf
#user  nobody;
worker_processes  4;
worker_cpu_affinity 0001 0010 0100 1000;

# 这里配置的时候,注意自己虚拟机上cpu的数量,不要超过自己虚拟机cpu核心数

#重载配置
[root@localhost nginx-1.20.0]# nginx -t
[root@localhost nginx-1.20.0]# systemctl restart nginx
  1. 查看nginx进程所使用对应的cpu
[root@localhost nginx-1.20.0]# watch -n.5 'ps axo pid,cmd,psr |grep nginx'
# 查看nginx进程对应的CPU
  6834 nginx: master process /apps   2
 47621 nginx: worker process         0
 47622 nginx: worker process         1
 47623 nginx: worker process         2
 47624 nginx: worker process         3
5.3.2、错误日志记录配置
# 错误日志记录配置,语法:error_log file [debug | info | notice | warn | error | crit | alert |emerg]
# error_log logs/error.log;
# error_log logs/error.log notice;
error_log /app/nginx/logs/error.log error;
5.3.3、工作优先级与文件并发数
worker_priority 0;   #工作进程优先级(-20~19)
worker_rlimit_nofile 65536;     #所有worker进程能打开的文件数量上线,包括:Nginx的所有连接(例如与代理服务器的连接等),而不仅仅是与客户端的连接,另一个考虑因素是实际的并发连接数不能超过系统级别的最大打开文件数的限制,最好与ulimit -n的值保持一致
[root@localhost ~]# vim /etc/security/limits.conf
*        soft    nofile    102400
*        hard    nofile    102400


# 案例-修改优先级
root@localhost nginx-1.20.0]# vim /apps/nginx/conf/nginx.conf
worker_priority -20;

#重载配置
[root@localhost nginx-1.20.0]# nginx -t
[root@localhost nginx-1.20.0]# systemctl restart nginx

# 查看优先级
[root@localhost ~]# watch -n.5 'ps axo pid,cmd,psr,nice |grep nginx'
Every 0.5s: ps axo pid,cmd,psr,nice |grep nginx                   Mon Jul 15 08:11:44 2024

 22950 nginx: master process /apps   1   0
 22951 nginx: worker process         0 -20
 22952 nginx: worker process         1 -20
 22953 nginx: worker process         2 -20
 22954 nginx: worker process         3 -20
 23004 grep nginx                    3   0
5.3.4、其他优化配置
daemon off;    # 前台运行nginx服务,用于测试、docker等环境
master_process off|on;    # 是否开启Nginx的master-worker工作模式,仅用于开发调试场景,默认为on
events {
  worker_connections 65536;    # 设置单个工作进程的最大并发连接数
  use epoll;    # 使用epoll事件驱动,Nginx支持众多的事件驱动,比如:select、poll、epoll,只能设置在events模块中
  accept_mutex on;  # on为同一时刻一个请求轮流由work进程处理,而防止被同时唤醒所有worker,避免多个睡眠进程被唤醒的设置,默认为off,新请求会唤醒所有worker进程,此过程也被称为"惊群",因此nginx刚安装完以后要进行适当的优化,建议设置为on
  multi_accept on;  # on时Nginx服务器的每个工作进程可以同时接受多个新的网络连接,此指令默认为off,即默认为一个工作进程只能一次接受一个新的网络连接,打开后几个同时接受多个,建议设置为on
}
[root@localhost ~]# while true;do ab -c 5000 -n 10000 http://127.0.0.1/;sleep 0.5;done

[root@localhost ~]# tail /apps/nginx/logs/error.log
2021/05/24 12:35:53 [crit] 6828#0: *10996 open() "/apps/nginx/html/index.html" failed (24: Too many open files), client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.0", host: "127.0.0.1"
2021/05/24 12:35:53 [crit] 6828#0: *10996 open() "/apps/nginx/html/50x.html" failed (24: Too many open files), client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.0", host: "127.0.0.1"

[root@localhost ~]# vim /etc/security/limits.conf
*        -       nproc     100000
[root@localhost ~]# vim /apps/nginx/conf/nginx.conf
worker_rlimit_nofile 65536;
events {
    worker_connections  10240;
}
[root@localhost ~]# systemctl restart nginx
5.4、HTTP配置块

http协议相关的配置结构

http {
  ...
  ... #各server的公共配置
  server {    # 每个server用于定义一个虚拟主机,第一个server为默认虚拟服务器
    ...
  }
  server {
    ...
    server_name    # 虚拟主机名
    root      # 主目录
    alias      # 路径别名
    location [OPERATOR] URL {    # 指定URL的特性
      ...
      if CONDOTION {
        ...
      }
    }
  }
}

http协议配置说明

http {
  include mime.types;    # 导入支持的文件类型,是相对于/apps/nginx/conf的目录
  default_type  application/octet-stream;    # 除mime.types中文件类型外,设置其他文件默认类型,访问其他类型时会提示下载不匹配的类型文件

    # 日志配置部分
    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';
    #access_log  logs/access.log  main;
    # 自定义优化参数
    sendfile on;
    #tcp_nopush on;#在开启了sendfile的情况下,合并请求后统一发送给客户端
    #tcp_nodelay off;#在开启了keeplived模式下的连接是否启用TCP_NODELAY选项,当为off时,延迟0.2s发送,默认on时,不延迟
    #keepalive_timeout 0;
    keepalive_timeout 65 65;# 设置会话保持时间,第二个值为响应首部:keep-Alived:timeout=65,可以和第一个值不同
    #gzip on;#开启文件压缩

    server {
      listen 80;# 设置监听端口
      server_name localhost;# 设置server name,可以空格隔开写多个,并支持正则表达式,如:*.iproute.cn
    }
}
5.4.1、MIME
# 在响应报文中将指定的文件扩展名映射至MIME对应的类型
include            /etc/nginx/mime.types;
default_type        application/octet-stream;
types {
  text/html html;
  images/gif gif;
  images/jpeg jpg;
}

范例:识别php文件为text/html

[root@localhost ~]# cat << eof > /apps/nginx/html/test.php
> <?php
> phpinfo();
> ?>
> eof
[root@localhost ~]# curl 127.0.0.1/test.php -I
HTTP/1.1 200 OK
Server: nginx/1.20.0
Date: Mon, 24 May 2021 06:05:05 GMT
Content-Type: application/octet-stream
Content-Length: 20
Last-Modified: Mon, 24 May 2021 06:04:49 GMT
Connection: keep-alive
ETag: "60ab4201-14"
Accept-Ranges: bytes
[root@localhost ~]# vim /apps/nginx/conf/nginx.conf
http {
    include       mime.types;
    default_type  text/html;
...
[root@localhost ~]# nginx -s reload
[root@localhost ~]# curl 127.0.0.1/test.php -I
5.4.2、指定响应报文server首部

默认情况下的响应报文

[root@localhost nginx-1.20.0]#  curl -I 127.0.0.1
HTTP/1.1 200 OK
Server: nginx/1.20.0
Date: Tue, 16 Jul 2024 11:35:41 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 16 Jul 2024 11:32:31 GMT
Connection: keep-alive
ETag: "66965a4f-264"
Accept-Ranges: bytes

如何让响应报文中不要显示nginx的版本号

添加配置如下:

# 配置如下字段:
charset utf-8;

# 是否在响应报文的Server首部显示nginx版本
server_tokens on | off |build | string

# 具体配置
[root@localhost conf]# vim /apps/nginx/conf/nginx.conf
    server {
        listen       80;
        server_name  localhost;
        charset utf-8;
        server_tokens off;


# 重启nginx服务
[root@localhost conf]# systemctl restart nginx

再次访问测试:

[root@localhost nginx-1.20.0]# curl -I 127.0.0.1
HTTP/1.1 200 OK
Server: nginx
Date: Tue, 16 Jul 2024 11:39:35 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 612
Last-Modified: Tue, 16 Jul 2024 11:32:31 GMT
Connection: keep-alive
ETag: "66965a4f-264"
Accept-Ranges: bytes

这样以来,别人访问的时候就看不到我们使用的nginx的版本,防止他人根据某个版本的漏洞进行攻击

5.5、server核心配置示例

基于不同的IP、不同的端口以及不同的域名实现不同的虚拟主机,依赖于核心模块ngxhttp core_module实现。

5.5.1、PC站与手机站(虚拟主机)
[root@localhost ~]# mkdir /apps/nginx/conf.d
[root@localhost ~]# vim /apps/nginx/conf/nginx.conf
http {
...
    include /apps/nginx/conf.d/*.conf;
}
[root@localhost ~]# vim /apps/nginx/conf.d/pc.conf
server {
    listen 80;
    server_name www.test.com;
    location / {
        root /apps/nginx/html/pc;
    }
}
[root@localhost ~]# mkdir -p /apps/nginx/html/pc
[root@localhost ~]# echo "hello world" > /apps/nginx/html/pc/index.html
[root@localhost ~]# systemctl reload nginx
[root@localhost conf]# vim /etc/hosts
192.168.175.10 www.test.com
[root@localhost ~]# vim /apps/nginx/conf.d/mobile.conf
server {
    listen 80;
    server_name m.test.com;
    location / {
        root /apps/nginx/html/mobile;
    }
}
[root@localhost ~]# mkdir -p /apps/nginx/html/mobile
[root@localhost ~]# echo "hello mobile" > /apps/nginx/html/mobile/index.html
[root@localhost ~]# systemctl reload nginx
[root@localhost conf]# vim /etc/hosts
192.168.175.10 m.test.com
5.5.2、ROOT

root:指定web的家目录,在定义location的时候,文件的绝对路径等于root+location

案例:

[root@localhost conf]# vim /apps/nginx/conf.d/test.conf
server {
  listen 80;
  server_name a.test.com;
  location / {
    root /apps/nginx/html/www;
  }
  location /about {
    root /apps/nginx/html/about;    # 这样写其实访问的是/apps/nginx/html/下的about的about  /apps/nginx/html/about/about
  }
}
[root@localhost conf]# vim /etc/hosts
192.168.175.10 a.test.com
[root@localhost ~]# mkdir -p /apps/nginx/html/about
[root@localhost ~]# echo "about" > /apps/nginx/html/about/index.html
# 重启Nginx并访问测试,404
[root@localhost conf]# vim /apps/nginx/conf.d/test.conf
server {
  listen 80;
  server_name a.test.com;
  location / {
    root /apps/nginx/html/www;
  }

  location /about {
    root /apps/nginx/html; 
    #这样就好了
  }
}
# 重启Nginx并访问测试
[root@localhost ~]# systemctl reload nginx
5.5.3、Alias

alias:定义路径别名,会把访问的路径重新定义到其指定的路径,文档映射的另一种机制;仅能用于location上下文,此指令使用较少

案例:

[root@localhost conf]# vim /apps/nginx/conf.d/test.conf

server {
  listen 80;
  server_name a.test.com;
  location / {
    root /apps/nginx/html/www;
  }

  location /about/ {    
  # 使用alias的时候uri后面加了斜杠,下面的路径也必须加,不然403错误
    alias /apps/nginx/html/about/;    
    # 当访问about的时候,会显示alias定义的/apps/nginx/html/about/里面的内容
  }
}
# 重启Nginx并访问测试
[root@localhost ~]# systemctl reload nginx
[root@localhost about]# curl a.test.com/about/
5.5.4、Location的详细使用

在一个server中location配置段可存在多个,用于实现从uri到文件系统的路径映射; nginx会根据用户请求的URI来检查定义所有的location,按一定的优先级找出一 个最佳匹配,然后应用其配置。

在没有使用正则表达式的时候,nginx会先在server中的多个location选取匹配度最高的一个uri, uri是用户请求的字符串,即域名后面的web文件路径,然后使用该location模块中的正则ur|和字符串,如果匹配成功就结束搜索,并使用此location处理此请求。

location官方帮助:https://nginx.org/en/docs/http/ngx_http_core_module.html#location

语法规则

location [ = | ~ | ~* | ^~ ] uri { ... }
匹配正则 解释
= 用于标准uri前,需要请求字串与uri精确匹配,大小敏感,如果匹配成功就停止向下匹配并立即处理请求
^~ 用于标准uri前,表示包含正则表达式,并且适配以指定的正则表达式开头,对URI的最左边部分做匹配检查,不区分字符大小写
~ 用于标准uri前,表示包含正则表达式,并且区分大小写
~* 用于标准uri前,表示包含正则表达式, 并且不区分大写
不带符号 匹配起始于此uri的所有的uri
\ 用于标准uri前,表示包含正则表达式并且转义字符。可以将.*等转义为普通符号
= ^~ ~/~* 不带符号

官方范例

The “/” request will match configuration A

the “/index.html” request will match configuration B

the “/documents/document.html” request will match configuration C

the “/images/1.gif” request will match configuration D

the “/documents/1.jpg” request will match configuration E

location = / {
    [ configuration A ]
}
location / {
    [ configuration B ]
}
location /documents/ {
    [ configuration C ]
}
location ^~ /images/ {
    [ configuration D ]
}
location ~* \.(gif|jpg|jpeg)$ {
    [ configuration E ]
}
server {
  listen 80;
  server_name a.test.com;
  location / {
    root /apps/nginx/html/www;
  }
  location /about {
    root /apps/nginx/html/www;
  }
  location ^~ /images/{

  }
  location ~* \.(gif|jpg|jpeg)$ {
    root /apps/nginx/html/www/images;
  }
}

在Nginx的配置中,\/在正则表达式中有以下不同的作用:

\

用于转义字符:

/

用于路径分隔:

总结如下:

精确匹配

[root@localhost conf.d]# vim /apps/nginx/conf.d/test.conf

server {
  listen 80;
  server_name a.test.com;
  location / {
    root /apps/nginx/html/www;
  }

  location = /logo.jpg {
    root /apps/nginx/html/images;
  }
}
[root@localhost conf.d]# mkdir -p /apps/nginx/html/images
[root@localhost conf.d]#cd /apps/nginx/html/images && wget https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png 
[root@localhost images]# mv PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png logo.jpg
[root@localhost images]# systemctl restart nginx
[root@localhost images]# curl http://a.test.com/logo.jpg
访问http://a.test.com/logo.jpg测试即可

区分大小写

[root@localhost conf.d]# vim /apps/nginx/conf.d/test.conf
server {
  listen 80;
  server_name a.test.com;
  location ~ /A.?\.jpg {
#A.jpg
# Aa
# AB
    index index.html;
    root /apps/nginx/html/images;
    }
}

[root@localhost images]# mv logo.jpg Aa.jpg
[root@localhost images]# systemctl restart nginx
http://a.test.com/Aa.jpg  这里可以匹配到正常访问
[root@localhost images]# mv Aa.jpg aa.jpg
修改文件为小写之后再访问因为大小写敏感就访问不了了

不区分大小写

[root@localhost conf.d]# vim /apps/nginx/conf.d/test.conf
server {
  listen 80;
  server_name a.test.com;

  location ~* /A.?\.jpg {
    index index.html;
    root /apps/nginx/html/images;
  }
}


# 这里虽然规则写的大写A但是去访问http://a.test.com/aa.jpg正常可以访问到
# URI开始
[root@localhost conf.d]# vim /apps/nginx/conf.d/test.conf
server {
  listen 80;
  server_name a.test.com;
  location ^~ /images {
      index index.html;
      root /apps/nginx/html;
  }
}

访问http://a.test.com/images/aa.jpg

文件名后缀

location ~* \.(gif|jpg|jpeg|bmp|png|tiff|tif|ico|wmf|js|css)$ {
  index index.html;
  root /apps/nginx/html/images/;
}
http://a.test.com/aa.jpg可以访问到

优先级

[root@localhost conf.d]# mkdir - p /apps/nginx/html/static{1,2,3}
[root@localhost conf.d]# vim /apps/nginx/conf.d/test.conf
location = /1.jpg {
  index index.html;
  root /apps/nginx/html/static1;
}
location /1.jpg {
  index index.html;
  root /apps/nginx/html/static2;
}
location ~* \.(gif|jpg|jpeg|bmp|png|tiff|tif|ico|wmf|js|css)$ {
  index index.html;
  root /apps/nginx/html/static3;
}
[root@localhost conf.d]# wget -O /apps/nginx/html/static1/1.jpg https://dummyimage.com/600x100/000/fff&text=static1 
[root@localhost conf.d]# wget -O /apps/nginx/html/static2/1.jpg https://dummyimage.com/600x200/000/fff&text=static2 
[root@localhost conf.d]# wget -O /apps/nginx/html/static3/1.jpg https://dummyimage.com/600x300/000/fff&text=static3
重启nginx测试访问http:a.test.com/1.jpg

生产使用案例

location = /index.html {
  ....
}
location / {
  ...
}
location ^~ /static/ {
  ...
}
location ~* \.(gif|jpg|jpeg|png|css|js|ico)$ {
  ...
}
location ~* /app1 {
  ...
}
location ~* /app2 {
  ...
}
5.5.5、Nginx四层访问控制

访问控制基于模块ngx_http_access_module实现,可以通过匹配客户端源IP地址进行限制

注意:如果能在防火墙设备控制最好就不要在nginx上配置,可以更好的节约资源

官方帮助:https://nginx.org/en/docs/http/ngx_http_access_module.html

范例:

[root@www static3]# vim /apps/nginx/conf.d/test.conf
server {
  listen 80;
  server_name a.test.com;
  allow 192.168.112.0/24;
  deny all;
 location / {
    root /apps/nginx/html/www;
 location = /1.jpg {
  index index.html;
  root /apps/nginx/html/static1;
  allow 192.168.175.10;
  deny all;
}
}
}
禁止了主机访问,虚拟机本机可以访问刚才的1.jpg
5.5.6、Nginx账户认证功能

由ngx_http_auth_basic_module模块提供此功能

官方帮助:https://nginx.org/en/docs/http/ngx_http_auth_basic_module.html

# 案例-基于用户的账户验证
  1. 创建用户密码文件htpasswd
# htpasswd命令是由httpd-tools软件包提供
[root@localhost ~]# yum install -y httpd-tools
[root@localhost ~]# htpasswd -cb /apps/nginx/conf/.htpasswd user1 123
[root@localhost ~]# htpasswd -b /apps/nginx/conf/.htpasswd user2 123
# -c  创建用户
# -b  非交互方式提交密码
  1. 检查创建好的密码本
[root@localhost ~]# tail /apps/nginx/conf/.htpasswd
user1:$apr1$N5FQRfTZ$41cCA49sQCSA9z71hlO6n.
user2:$apr1$/NXK7rS7$1AUTuESFJEQW490XjTs851
  1. 编辑子配置文件test.conf
[root@www static3]# vim /apps/nginx/conf.d/test.conf
server {
  listen 80;
  server_name a.test.com;
  auth_basic "login password";
  auth_basic_user_file /apps/nginx/conf/.htpasswd;
location = /1.jpg {
  index index.html;
  root /apps/nginx/html/static1;
}
}
[root@www static3]# systemctl restart nginx
auth_basic "login password";
auth_basic_user_file /apps/nginx/conf/.htpasswd;
5.5.7、自定义错误页面

定义错误页,以指定的响应状态码进行响应,可用位置: http, server, location, if in location

erro_page code ... [=[response]] uri;

官方示例:

[root@www static3]# vim /apps/nginx/conf/nginx.conf
server {
listen 80;
server_name www.example.com;
error_page 500 502 503 504 /error.html;
location = /error.html {
  root html;
}

范例:

[root@www static3]# vim /apps/nginx/conf.d/test.conf
server {
  listen 80;
  server_name a.test.com;
  auth_basic "login password";
  auth_basic_user_file /apps/nginx/conf/.htpasswd;
  error_page 404 /40x.html;
location = /1.jpg {
  index index.html;
  root /apps/nginx/static1;
}
location /40x.html{
 root /apps/nginx/html;
}
}
[root@www html]# echo "<h1>404 not found</h1>" > /apps/nginx/html/40x.html

范例:

error_page 404 /index.html;
# 如果404,就跳转到主页
5.5.8、自定义错误日志

可以自定义错误日志

Syntax: error_log file [level];
Default:
error_log logs/error.log error;
Context: main, http, mail, stream, server, location
level: debug, info, notice, warn, error, crit, alert, emerg

范例:

[root@www static3]# vim /apps/nginx/conf/nginx.conf
server{
...
error_page 500 502 503 504 404 /error.html;
access_log /apps/nginx/logs/a_test_access.log;
error_log /apps/nginx/logs/a_test_error.log;
location = /error.html {
  root html;
}
5.5.9、长连接配置
[root@www html]# vim /apps/nginx/conf/nginx.conf

keepalive_timeout timeout [header_timeout];
# 设定保持连接超时时长,0表示禁止长连接,默认为75s,通常配置在http字段作为站点全局配置
keepalive_requests number;
# 在一次长连接上所允许请求的资源的最大数量,默认为100次,建议适当调大,比如:500

范例:

[root@www html]# vim /apps/nginx/conf/nginx.conf
http {
...
keepalive_requests 3;
keepalive_timeout 65 60;
# 开启长连接后,返回客户端的会话保持时间为60s,单次长连接累计请求达到指定次数请求或65秒就会被断开,后面的60为发送给客户端应答报文头部中显示的超时时间设置为60s,如不设置客户端将不显示超时时间。
keep-Alive:timeout=60;
# 浏览器收到的服务器返回的报文
# 如果设置为keepalive_timeout 0表示关闭会话保持功能,将如下显示:
Connection:close  # 浏览器收到的服务器返回的报文
# 使用命令测试
[root@www html]# telnet a.test.com 80
Trying 192.168.175.10...
Connected to a.test.com.
Escape character is '^]'.
GET / HTTP/1.1
HOST: a.test.com
5.5.10、作为下载服务器

ngx_http_autoindex_module模块处理以斜杠字符"/"结尾的请求,并生成目录列表可以做为下载服务配置使用

官方文档:https://nginx.org/en/docs/http/ngx_http_autoindex_module.html

相关指令:

autoindex on|off;
# 自动文件索引功能,默认off
autoindex_exact_size on|off;
# 计算文件确切大小(单位bytes),off显示大概大小(单位K、M),默认on
autoindex_localtime on|off;
# 显示本机时间而非GMT(格林威治)时间,默认off
autoindex_format html|xml|json|jsonp;
# 显示索引的页面分割,默认html
limit_rate rate;
# 限制响应客户端传输速率(除GET和HEAD以外的所有方法),单位B/s,既bytes/second,默认值0,表示无限制,此指令由ngx_http_core_module提供

范例:实现下载站点

[root@localhost ~]# mkdir -p /apps/nginx/html/www/download
[root@www ~]# cd /apps/nginx/html/www/download
[root@www download]# touch f1
[root@www download]# touch f2
[root@localhost ~]# vim /apps/nginx/conf.d/www.conf
server {
  listen 80;
  server_name file.test.com;
  location /download {
      autoindex on;    
      # 自动索引功能,开启才会展示出文件列表
      autoindex_exact_size off;    
      # 关闭详细文件大小统计,让文件大小显示MB,GB单位,默认为b
      autoindex_localtime on;    
      # on表示显示本机时间
      limit_rate 1024k;      
      # 限速,默认不限速
      root /apps/nginx/html/www;
  }
}


# 修改windwos里面的hosts
192.168.88.140 file.test.com
测试http://file.test.com/download/
5.5.11、作为上传服务器
client_max_body_size 1m;
# 设置允许客户端上传单个文件的最大值,默认值为1m,上传文件超过此值会出现413错误
client_body_buffer_size size;
# 用户接受每个客户端请求报文的body部分的缓冲区大小;默认16k;超出此大小时,其将被暂存到磁盘上client_body_temp_path指定所定义的位置
client_body_temp_path path [level1 [level 2 [level 3]]];
# 设定存储客户端请求报文的body部分的临时存储路径及子目录结构和数量,目录名为16进制的数字,使用hash之后的值从后往前截取1位、2位、2位作为目录名
# 1级目录占1位16进制,即2^4=16个目录 0-f
# 2级默认占2位16进制,即2^8=256个目录 00-ff
# 3级目录占2位16进制,即2^8=256个目录
#因为如果所有上传的文件都放在一个文件夹下,不仅很容易文件名冲突,并且容易导致一个文件夹特别大。所以有必要创建子目录这里的level1,2,3如果有值就代表存在一级,二级,三级子目录。目录名是由数字进行命名的,所以这里的具体的值就是代表目录名的数字位数,比如如果如下设置
#client_body_temp_path  /spool/nginx/client_temp 3 2;
#可能创建的文件路径为
#/spool/nginx/client_temp/702/45/00000123457
[root@localhost ~]# md5sum f1
d41d8cd98f00b204e9800998ecf8427e  f1
[root@localhost ~]# md5sum f2
b026324c6904b2a9cb4b88d6d61c81d1  f2

配置示例:但是上传操作不太好展示,因为需要从前端通过POST和GET来提交上传的内容

server {
        listen       80;
        server_name  upload.test.com;

        location /upload {
            # 上传目录
            root /apps/nginx/upload;
            # 允许上传的文件大小
            client_max_body_size 2m;
            # 处理上传请求
            location ~ ^/upload/(?P<file>.*) {
                limit_rate 1m;
                limit_rate_after 10m;
                client_body_temp_path /tmp/nginx_upload;
                client_body_in_file_only clean;

                # 保存上传的文件
                alias /apps/nginx/upload/$file;
            }
        }
    }
5.5.12、其他配置
keepalive_disable none | browser ...;
limit_except method ... { ... }  # 仅用于location
method: GET, HEAD, POST, PUT, DELETE, MKCOL, COPY, MOVE, OPTIONS, PROPFIND, PROPPATCH, LOCK, UNLOCK, PATCH
[root@www conf.d]# vim /apps/nginx/conf.d/www.conf
location /download {
    root /apps/nginx/html/www;
    autoindex on;
    autoindex_exact_size off;
    autoindex_localtime on;
    limit_except POST {          #相当于只允许底下允许列表里的使用除了post外的其他方法
    allow 192.168.112.1;         #只有浏览器可以使用除了post外的其他方法(get delete),其他人只能用post
    deny all;
}
}
[root@www conf.d]# systemctl restart nginx


# 观察现象
curl file.test.com/download
curl file.test.com/download -X POST -d "hello"
两次报错不一致,一个403一个404

# 
cd /apps/nginx/html/www
touch f1
touch f2
vim /apps/nginx/conf.d/www.conf

将    root /apps/nginx/html/www;改成    alias /apps/nginx/html/www;
浏览器测试http://file.test.com/download/,能否访问到get请求
aio on | off;
[root@localhost nginx-1.18.0]# cd /usr/local/src/nginx-1.18.0 
[root@localhost nginx-1.18.0]# ./configure --prefix=/apps/nginx \
--user=nginx \
--group=nginx \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_stub_status_module \
--with-http_gzip_static_module \
--with-pcre \
--with-stream \
--with-stream_ssl_module \
--with-stream_realip_module \
--with-file-aio
[root@localhost nginx-1.18.0]# make -j 2 && make install
[root@www nginx-1.18.0]# nginx -V
nginx version: slsnginx/1.18.0
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC)
built with OpenSSL 1.0.2k-fips  26 Jan 2017
TLS SNI support enabled
configure arguments: --prefix=/apps/nginx --user=nginx --group=nginx --with-http_ssl_module --with-http_v2_module --with-http_realip_module --with-http_stub_status_module --with-http_gzip_static_module --with-pcre --with-stream --with-stream_ssl_module --with-stream_realip_module --with-file-aio
#支持file-aio了
[root@localhost nginx-1.18.0]# vim /apps/nginx/conf.d/www.conf
server {
  listen 80;
  server_name file.test.com;
  aio on;
...
}
directio size | off;
open_file_cache on;    # 是否缓存打开过的文件信息
open_file_cache max=N [inactive=time];
#nginx可以缓存以下三种信息:
#1. 文件元数据,文件的描述符,文件大小和最近一次的修改时间
#2. 打开的目录结构
#3. 没有找到的或者没有权限访问的文件相关信息
max=N;    # 可缓存的缓存项上限数量;达到上限后会使用LRU(Least recently used,最近最少使用)算法实现管理
inactive=time;    # 缓存项的非活动时长,在此处指定的时长内未被命中的或命中的次数少于open_file_cache_min_uses指令所指定的次数的缓存项即为非活动项,将被删除
open_file_cache_valid time;  #缓存项有效性的检查验证频率,默认值为60s
open_file_cache_errors on | off;   #是否缓存查找时发生错误的文件一类的信息,默认值为off
open_file_cache_min_uses number;   # open_file_cache指令的inactive参数指定的时长内,至少被命中此处指定的次数方可被归类为活动项,默认值为1

范例:

open_file_cache max=10000 inactive=60s;
open_file_cache_vaild 60s;
open_file_cache_min_uses 5;
open_file_cache_errors off;

6、Nginx高级配置

6.1、Nginx状态页

基于nginx模块ngxhttp_stub_status module实现,在编译安装nginx的时候需要添加编译参数--with-http_stub_status_module,否则配置完成之后监测会是提示语法错误

注意:状态页显示的是整个服务器的状态,而非虚拟主机的状态

[root@www ~]# vim /apps/nginx/conf.d/www.conf
server {
    listen 80;
    server_name status.test.com;

    location /status {
        stub_status on;
        # 开启nginx状态页显示
        allow 192.168.88.0/24;
        deny all;
    }
}

[root@www ~]# vim /etc/hosts
192.168.88.140 status.test.com
[root@www ~]# curl http://status.test.com/status

image-176

6.2、Nginx第三方模块

第三方模块是对nginx的功能扩展,第三方模块需要在编译安装Nginx的时候使用参数--add-module=PATH指定路径添加,有的模块是由公司的开发人员针对业务需求定制开发的,有的模块是开源爱好者开发好之后上传到github进行开源的模块,nginx支持第三方模块需要从源码重新编译支持.

比如:开源的echo模块:https://github.com/openresty/echo-nginx-module

[root@www nginx-1.18.0]# vim /apps/nginx/conf.d/echo.conf
server {
    listen 80;
    server_name echo.test.com;

    location /main {
        index index.html;
        default_type text/html;
        echo "hello world,main-->";
        echo $remote_addr;
        echo_reset_timer;
        # 将计时器开始时间重置为当前时间

        echo_location /sub1;
        echo_location /sub2;
        echo "took $echo_timer_elapsed sec for total.";
}
    location /sub1 {
        echo_sleep 2;
        echo hello;
}
    location /sub2 {
        echo_sleep 1;
        echo world;
}
}


# 重新编译nginx,以前的配置文件不会丢失
[root@localhost ~]# cd /usr/local/src
[root@localhost src]# wget https://github.com/openresty/echo-nginx-module/archive/refs/heads/master.zip
[root@www src]# unzip master.zip
[root@www src]# mv echo-nginx-module-master echo-nginx-module
[root@localhost src]# cd nginx-1.18.0
[root@localhost nginx-1.18.0]# ./configure --prefix=/apps/nginx \
--user=nginx \
--group=nginx \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_stub_status_module \
--with-http_gzip_static_module \
--with-pcre \
--with-stream \
--with-stream_ssl_module \
--with-stream_realip_module \
--add-module=/usr/local/src/echo-nginx-module
[root@localhost nginx-1.18.0]# make -j 2 && make install


# 进行访问测试
[root@www ~]# echo "192.168.88.140 echo.test.com" >> /etc/hosts
[root@www ~]# curl echo.test.com/main
hello world,main-->
192.168.88.140
hello
world
took 3.008 sec for total.
6.2.1、内置变量

官方文档:https://nginx.org/en/docs/varindex.html

常用内置变量

$remote_addr;
# 存放了客户端的地址,注意是客户端的公网IP
$proxy_add_x_forwarded_for;
# 此变量表示将客户端IP追加请求报文中X-Forwarded-For首部字段,多个IP之间用逗号分隔,如果请求中没有X-Forwarder-For,就使用$remote_addr
$args;
# 变量中存放了URL中的参数
$document_root;
# 保存了针对当前资源的系统根目录
$document_uri;
# 保存了当前请求中不包含参数的URI,注意是不包含请求的指令,比如/img/logo.png
$host;
# 存放了请求的host名称
limit_rate 10240;
echo $limit_rate;
# 如果nginx服务器使用limit_rate配置了显示网络速率,则会显示,如果没有设置,则显示0
$remote_port;
# 客户端请求Nginx服务器时随机打开的端口,这是每个客户端自己的端口
$remote_user;
# 已经经过Auth Basic Module验证的用户名
$request_body_file;
# 做反向代理时发给后端服务器的本地资源的名称
$request_method;
# 请求资源的方式,GET/PUT等等
$request_filename;
# 当前请求的资源文件的磁盘路径,由root或alias指令与URL请求生成的文件绝对路径
# /apps/nginx/html/www/index.html
$request_uri;
# 包含请求参数的原始URI,不包含主机名,相当于:$document_uri?$args
$scheme;
# 请求的协议,例如:http,https,ftp等等
$server_protocol;
# 保存了客户端请求资源使用的协议版本,例如:HTTP/1.0,HTTP/1.1,HTTP/2.0等等
$server_addr;
# 保存了服务器的IP地址
$server_name;
# 请求的服务器的主机名
$server_port;
# 请求的服务器的端口号
$http_<name>
# name为任意请求报文首部字段,表示记录请求报文的首部字段
$http_user_agent;
# 客户端浏览器的详细信息
$http_cookie;
# 客户端的cookie信息
$cookie_<name>
# name为任意请求报文首部字段cookie的key名
6.2.2、自定义变量

假如需要自定义变量名和值,使用指令set $variable value;

语法格式:

Syntax:set $varible value;
Default: -
Context: server, location, if

范例:

[root@localhost ~]# vim /apps/nginx/conf.d/www.conf
set $name zs;
echo $name;
set $my_port $server_port;
echo $my_port;
echo "$setver_name:$server_port";
6.3、Nginx自定义访问日志

访问日志是记录客户端即用户的具体请求内容信息,全局配置模块中的error_log是记录nginx服务器运行时的日志保存路径和记录日志的level,因此有着本质的区别,而且Nginx的错误日志一般只有一个, 但是访问日志可以在不同server中定义多个,定义一个日志需要使用access_log指定日志的保存路径,使用log_format指定日志的格式,格式中定义要保存的具体日志内容。

访问日志由ngx_http_log_module 模块实现

官方帮助文档:http://nginx.org/en/docs/http/ngx_http_log_module.html

语法格式:

Syntax:    access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
access_log off;
Default:    
access_log logs/access.log combined;
Context:    http, server, location, if in location, limit_except
6.3.1、自定义默认格式日志

如果是要保留日志的源格式,只是添加相应的日志内容,则配置如下:

[root@www ~]# vim /apps/nginx/conf/nginx.conf
log_format  nginx_format1  '$remote_addr - $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for"'
                  '$server_name:$server_port';
access_log  logs/access.log  nginx_format1;
# 重启nginx并访问测试日志格式
# /apps/nginx/logs/access.log
6.3.2、自定义json格式日志

Nginx的默认访问日志记录内容相对比较单一, 默认的格式也不方便后期做日志统计分析,生产环境中通常将nginx日志转换为json日志,然后配合使用ELK做日志收集-统计-分析。

log_format access_json '{"@timestamp":"$time_iso8601",'
  '"host":"$server_addr",'
  '"clientip":"$remote_addr",'
  '"size":$body_bytes_sent,'
  '"responsetime":$request_time,'
  '"upstreamhost":"$upstream_response_time",'
  '"upstreamhost":"$upstream_addr",'
  '"http_host":"$host",'
  '"uri":"$uri",'
  '"xff":"$http_x_forwarded_for",'
  '"referer":"$http_referer",'
  '"tcp_xff":"$proxy_protocol_addr",'
  '"status":"$status"}';
access_log  logs/access.log  access_json;
6.4、Nginx压缩功能

Nginx支持对指定类型的文件进行压缩,然后再传输给客户端,而且压缩还可以设置压缩比例,压缩后的文件大小将比源文件显著变小,这样有助于降低出口带宽的利用率,不过会占用相应的CPU资源。

Nginx对文件的压缩功能是依赖于模块ngx_http_gzip_module

官方文档:https://nginx.org/en/docs/http/ngx_http_gzip_module.html

# 启用或禁用gzip压缩,默认关闭
gzip on | off;
# 压缩比由低到高1到9,默认为1
gzip_comp_level level;
# 禁用IE6 gzip功能
gzip_disable "MSIE [1-6]\.";
# gzip压缩的最小文件,小于设置值的文件将不会压缩
gzip_min_length 1k;
# 启用压缩功能时,协议的最小版本,默认HTTP/1.1
gzip_http_version 1.0 | 1.1;
# 指定Nginx服务需要向服务器申请的缓存空间的个数和大小,平台不同,默认:32 4k或者 16 8k;
gzip_buffers number size;
# 指明仅对哪些类型的资源执行压缩操作,默认为gzip_types text/html,不用显示指定,否则出错
gzip_types mime-type ...;
# 如果启用压缩,是否在相应报文首部插入"vary: Accept-Encoding",一般建议打开
gzip_vary on | off
# 重启nginx并进行访问测试压缩功能
[root@localhost ~]# curl -I --compressed 127.0.0.1
6.4.1、案例-压缩对比
  1. 编写配置文件,创建两个虚拟机主机:site1和site2,其中site1开启gzip压缩,site2不开启
[root@www site1]# vim /apps/nginx/conf.d/test.conf
server {
        listen 80;
        server_name www.site1.com;
        gzip on;
        gzip_comp_level 9;
        gzip_vary on;
        gzip_types text/html;
        location / {
                index index.html;
                root /data/site1;
        }
}
server {
        listen 80;
        server_name www.site2.com;
        location / {
                root /data/site2;
        }
}


# 创建网站目录和文件
[root@localhost ~]# mkdir -p /data/{site1,site2}
[root@localhost ~]# yum install -y tree
[root@localhost ~]# tree /etc/ > /data/site1/site1.html
[root@localhost ~]# tree /etc/ > /data/site2/site2.html
  1. 通过curl --compressed访问测试
[root@www site1]# curl -I --compressed http://www.site1.com/site1.html
HTTP/1.1 200 OK
Server: nginx/1.20.0
Date: Thu, 18 Jul 2024 08:40:04 GMT
Content-Type: text/html
Last-Modified: Thu, 18 Jul 2024 08:38:22 GMT
Connection: keep-alive
Vary: Accept-Encoding
ETag: W/"6698d47e-1ee6a"
Content-Encoding: gzip

[root@www site1]# curl -I --compressed http://www.site2.com/site2.html
HTTP/1.1 200 OK
Server: nginx/1.20.0
Date: Thu, 18 Jul 2024 08:40:45 GMT
Content-Type: text/html
Content-Length: 126570
Last-Modified: Thu, 18 Jul 2024 08:38:09 GMT
Connection: keep-alive
ETag: "6698d471-1ee6a"
Accept-Ranges: bytes

其中site1站可以看到响应报文中提示Content-Encoding: gzip

6.5、HTTPS功能

Web网站的登录页面都是使用https加密传输的,加密数据以保障数据的安全,HTTPS能够加密信息,以免敏感信息被第三方获取,所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用HTTPS协议,HTTPS其实是有两部分组成: HTTP + SSL/ TLS,也就是在HTTP上又加了一层处理加密信息的模块。服务端和客户端的信息传输都会通过TLS进行加密,所以传输的数据都是加密后的数据。

image-177

6.5.1、HTTPS配置参数

nginx的https功能基于模块ngx_http_ssl_module实现,因此如果是编译安装的nginx要使用参数ngx_http_ssl_module开启ssI功能,但是作为nginx的核心功能,yum安装的nginx默认就是开启的,编译安装的nginx需要指定编译参数--with-http_ssl_module开启

官方文档:https://nginx.org/en/docs/http/ngx_http_ssl_module.html

配置参数如下:

ssl on | off;
listen 443 ssl;
# 为指定的虚拟主机配置是否启用ssl功能,此功能在1.15.0废弃,使用listen [ssl]替代。
ssl_certificate /path/to/file;
# 当前虚拟主机使用使用的公钥文件,一般是crt文件
ssl_certificate_key /path/to/file;
# 当前虚拟主机使用的私钥文件,一般是key文件
ssl_protocols [SSLv2] [SSLv3] [TLSv1] [TLSv1.1] [TLSv1.2];
# 支持ssl协议版本,早期为ssl现在是TSL,默认为后三个
ssl_session_cache off | none | [builtin[:size]] [shared:name:size];
# 配置ssl缓存
    off: 
    # 关闭缓存
    none: 
    # 通知客户端支持ssl session cache,但实际不支持
    builtin[:size]    # 使用OpenSSL内建缓存,为每worker进程私有
    [shared:name:size]    # 在各worker之间使用一个共享的缓存,需要定义一个缓存名称和缓存空间大小,一兆可以存储4000个会话信息,多个虚拟主机可以使用相同的缓存名称。
ssl_session_timeout time;
# 客户端连接可以复用ssl session cache中缓存的有效时长,默认5m
6.5.2、自签名证书
cd /apps/nginx
mkdir certs && cd certs
openssl req -newkey rsa:4096 -nodes -sha256 -keyout ca.key -x509 -days 3650 -out ca.crt
openssl req -newkey rsa:4096 -nodes -sha256 -keyout iproute.cn.key -out iproute.cn.csr
openssl x509 -req -days 36500 -in iproute.cn.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out iproute.cn.crt
cat iproute.cn.crt ca.crt > iproute.crt
openssl x509 -in iproute.cn.crt -noout -text
6.5.3、Nginx证书配置
vim /apps/nginx/conf/nginx.conf
server {
  listen 80;
  listen 443 ssl;
  ssl_certificate /apps/nginx/certs/iproute.crt;
  ssl_certificate_key /apps/nginx/certs/iproute.cn.key;
  ssl_session_cache shared:sslcache:20m;
  ssl_session_timeout 10m;
  root /apps/nginx/html;
}
6.6、虚拟主机
[root@www conf.d]# ll /apps/nginx/conf.d
总用量 12
-rw-r--r--. 1 root root 107 9月  25 16:45 bbs.conf
-rw-r--r--. 1 root root 109 9月  25 16:45 blog.conf
-rw-r--r--. 1 root root 107 9月  25 16:44 www.conf
[root@www conf.d]# cat *.conf
server {
listen      *:8080;
server_name bbs.eagle.com;
location / {
root /apps/html/bbs;
index index.html;
}
}

server {
listen      *:8080;
server_name blog.eagle.com;
location / {
root /apps/html/blog;
index index.html;
}
}

server {
listen      *:8080;
server_name www.eagle.com;
location / {
root /apps/html/www;
index index.html;
}
}

7、LNMP架构概述

7.1、什么是LNMP

LNMP是一套技术的组合,L=Linux、N=Nginx、M~=MySQL、P=PHP

7.2、LNMP架构是如何工作的

image-178

7.3、Nginx与fastcgi详细工作流程

image-179

  1. 用户通过http协议发起请求,请求会先抵达LNMP架构中的nginx;
  2. nginx会根据用户的请求进行location规则匹配;
  3. location如果匹配到请求是静态,则由nginx读取本地直接返回;
  4. location如果匹配到请求是动态,则由nginx将请求转发给fastcgi协议;
  5. fastcgi收到请求交给php-fpm管理进程,php-fpm管理进程接收到后会调用具体的工作进程wrapper;
  6. wrapper进程会调用PHP程序进行解析,如果只是解析代码,php直接返回;
  7. 如果有查询数据库操作,则由php连接数据库(用户 密码 ip)发起查询的操作;
  8. 最终数据由mysql-->php-->php-fpm-->fastcgi-->nginx-->http-->user

8、LNMP架构环境部署

8.1、安装nginx

在Centos7默认的软件仓库中默认是没有nginx的,我们需要安装一个扩展软件仓库epel-release才可以安装

# 安装扩展软件源
yum -y install epel-release

# 安装nginx
yum -y install nginx

# 启动nginx并且设置为开机自启动
systemctl start nginx
systemctl enable nginx

# 检查nginx是否成功安装
nginx -v
8.1.1、修改nginx用户

为了安全和方便后面的php进程的权限管理,这边建议将nginx的用户改为www用户

# 创建www用户组
groupadd www -g 666

# 添加www用户
useradd www -u 666 -g 666 -s /sbin/nologin -M

# 修改配置切换nginx运行用户为www
sed -i '/^user/c user www;' /etc/nginx/nginx.conf

# 重启nginx服务
systemctl restart nginx

# 检查nginx运行的用户
ps aux |grep nginx
8.2、安装php

Centos7软件仓库中默认自带的php版本很老,并不能支持运行比较新的网站,所以我们需要添加一个php官方的软件仓库,才可以正确安装。

# 安装webtstic软件仓库,官方仓库的链接附上,但是并不推荐,因为国外的资源比较卡
# rpm -Uvh https://mirror.webtatic.com/yum/el7/webtatic-release.rpm

# 导入英格提供的php源,这个比较快
vim /etc/yum.repos.d/eagle.repo
[eagle]
name=Eagle lab
baseurl=http://file.eagleslab.com:8889/%E8%AF%BE%E7%A8%8B%E7%9B%B8%E5%85%B3%E8%BD%AF%E4%BB%B6/%E4%BA%91%E8%AE%A1%E7%AE%97%E8%AF%BE%E7%A8%8B/Centos7%E6%BA%90/
gpgcheck=0
enabled=1

# 安装php环境,php所需的组件比较多,我们可以一次性安装全面了
yum -y install php71w php71w-cli php71w-common php71w-devel php71w-embedded php71w-gd php71w-mcrypt php71w-mbstring php71w-pdo php71w-xml php71w-fpm php71w-mysqlnd php71w-opcache php71w-pecl-memcached php71w-pecl-redis php71w-pecl-mongodb
8.2.1、切换php用户

配置php-fpm用于与nginx的运行用户保持一致

sed -i '/^user/c user = www' /etc/php-fpm.d/www.conf
sed -i '/^group/c group = www' /etc/php-fpm.d/www.conf
8.2.2、启动php-fpm

启动并且加入开机自启动

systemctl start php-fpm
systemctl enable php-fpm
8.3、安装Mariadb数据库
# 安装mariadb数据库软件
yum install mariadb-server mariadb -y

# 启动数据库并且设置开机自启动
systemctl start mariadb
systemctl enable mariadb

# 设置mariadb的密码
mysqladmin password '123456'

# 验证数据库是否工作正常
mysql -uroot -p123456 -e "show databases;"
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| test               |
+--------------------+

9、LNMP架构环境配置

9.1、设置fastcgi服务器的地址
Syntax: fastcgi_pass address;
Default:-
Context:location,if in location
#语法示例
fastcgi_pass location:9000;
fastcgi_pass unix:/tmp/fastcgi.socket;
9.2、设置fastcgi默认的首页文件
Syntax: fastcgi_index name;
Default:-
Context:http,server,location
9.3、通过fastcgi_param设置变量
Syntax: fastcgi_param parameter value [if_not_empty];
Default:-
Context:http,server,location
#语法示例
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /code$fastcgi_script_name;
9.4、php探针测试

为了测试php环境是否正常,我们可以编写一个php文件,然后查看是否运行正常来进行判断

# 首先为php探针创建一个虚拟主机
vim /etc/nginx/conf.d/php.conf
server {
        listen 80;
        server_name php.iproute.cn;
        root /code;

        location / {
                index index.php index.html;
         }

        location ~ \.php$ {
                fastcgi_pass 127.0.0.1:9000;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include fastcgi_params;
         }
}

# 测试nginx配置是否正确
nginx -t

# 重启nginx服务
systemctl restart nginx

编写php文件,在php文件中编写如下代码

vim /code/info.php
<?php
    phpinfo();
?>

在浏览器中访问,可以得到如下的结果

image-180

image-181

9.5、测试数据库连接

为了确保php能正确访问数据库,我们可以编写如下php代码用于验证数据库是否正确连接

vim /code/mysqli.php
<?php
    $servername = "localhost";
    $username = "root";
    $password = "123456";

    // 创建连接
    $conn = mysqli_connect($servername, $username, $password);

    // 检测连接
    if (!$conn) {
         die("Connection failed: " . mysqli_connect_error());
    }
    echo "连接MySQL...成功!";
?>

使用浏览器访问,可以得到数据库连接的结果

image-182

9.6、安装phpmyadmin

为了方便的使用数据库,我们可以安装数据库图形化管理工具phpmyadmin

# 为数据库管理工具创建虚拟主机
vim /etc/nginx/conf.d/mysql.conf
server {
        listen 80;
        server_name mysql.iproute.cn;
        root /code/phpmyadmin;

        location / {
                index index.php index.html;
         }

        location ~ \.php$ {
                fastcgi_pass 127.0.0.1:9000;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include fastcgi_params;
         }
}

# 检查nginx配置文件,并且重启
nginx -t
systemctl restart nginx

# 下载phpmyadmin源码
wget https://files.phpmyadmin.net/phpMyAdmin/5.1.1/phpMyAdmin-5.1.1-all-languages.zip

# 解压软件包,并且重命名
unzip phpMyAdmin-5.1.1-all-languages.zip
mv phpMyAdmin-5.1.1-all-languages phpmyadmin

# 添加session文件夹权限
chown www.www /var/lib/php/session

下面浏览器访问phpmyadmin页面

image-183

输入数据库用户名root和密码123456就可以进入图形化数据库管理页面了

image-184

10、安装博客系统

10.1、部署虚拟主机
# 为博客创建虚拟主机
vim /etc/nginx/conf.d/typecho.conf
server {
        listen 80;
        server_name blog.iproute.cn;
        root /code/typecho;
        index index.php index.html;

        location ~ .*\.php(\/.*)*$ {
                root /code/typecho;
                fastcgi_pass   127.0.0.1:9000;
                fastcgi_index  index.php;
                fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include fastcgi_params;
        }
}

# 检查nginx配置,并且重启nginx
nginx -t
systemctl restart nginx

# 下载源代码然后解压重命名
mkdir code/typecho
cd /code/typecho
wget http://file.eagleslab.com:8889/%E8%AF%BE%E7%A8%8B%E7%9B%B8%E5%85%B3%E8%BD%AF%E4%BB%B6/%E4%BA%91%E8%AE%A1%E7%AE%97%E8%AF%BE%E7%A8%8B/%E8%AF%BE%E7%A8%8B%E7%9B%B8%E5%85%B3%E6%96%87%E4%BB%B6/typecho.zip

unzip typecho.zip
10.2、创建数据库

点击数据库

image-185

输入数据库名之后,就可以点击创建

image-186

10.3、安装博客系统

下面就可以开始进入网站安装的部分了,访问博客系统页面

image-187

填写数据库密码和网站后台管理员密码

image-188

点击开始安装之后,会出现了如下页面,这个是因为php的用户是www用户,而/code/typecho文件夹是root用户的,所以这个网站根本没有权限保存数据相关的配置到文件夹中

image-189

方法一:直接将typecho文件夹赋予www权限

方法二:手动去帮助网站创建网站没有权限的配置文件,下面将会演示方法二

直接在/code/typecho下创建config.inc.php文件,然后将网页提示内容写入这个文件中

vim /code/typecho/config.inc.php
复制网页上的内容进去

配置文件创建完成之后,可以点击创建完毕,继续安装>>

下面是安装成功的页面

image-190

10.4、切换主题

默认的主题如下,界面比较的简洁,我们可以给这个网站替换主题,也可以借此加深熟悉我们对Linux命令行的熟练程度

image-191

打开官方主题站:https://typecho.me/

第三方主题商店:https://www.typechx.com/

这边以这个主题为例

image-192

点击模板下载

image-193

点击下载压缩包

image-194

将主题上传到博客主题的目录/code/typecho/usr/themes

image-195

# 解压压缩包,并且将主题文件夹重命名
unzip typecho-theme-sagiri-master.zip
mv typecho-theme-sagiri-master sagiri

# 可以删除旧的压缩包文件
rm -rf typecho-theme-sagiri-master.zip

进入网站后台切换主题,在地址后面加上/admin就可以进入后台登录页面了

image-196

启用我们刚刚安装的主题

image-197

访问网页前端,查看最终的效果

image-198

11、安装网盘

11.1、部署虚拟主机
# 为博客创建虚拟主机
vim /etc/nginx/conf.d/kod.conf
server {
        listen 80;
        server_name pan.iproute.cn;
        root /code/kod;
        index index.php index.html;

        location ~ .*\.php(\/.*)*$ {
                root /code/kod;
                fastcgi_pass   127.0.0.1:9000;
                fastcgi_index  index.php;
                fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include fastcgi_params;
        }
}

# 检查nginx配置,并且重启nginx
nginx -t
systemctl restart nginx

# 下载源代码然后解压重命名
mkdir /code/kod
cd /code/kod
wget https://static.kodcloud.com/update/download/kodbox.1.23.zip
unzip kodbox.1.23.zip
11.2、创建数据库

点击数据库

image-199

输入数据库名之后,就可以点击创建

image-200

11.3、安装网盘系统

浏览器访问此站点,我们发现目录权限,这个比较重要

image-201

# 设置权限
chown -R www.www /code/kod

添加完成之后,刷新页面,可以看到所有条件都已经符合,就可以直接点击下一步了

image-202

填写数据库密码和数据库名

image-203

设置系统密码

image-204

完成网站安装

image-205

下面根据自己的喜好,进行简单的设置就可以正常使用啦!

image-206

我们也可以直接在这个上面编辑Linux上的文件,比如我们之前创建的php文件

image-207

二、MySQL数据库

1、数据库介绍

1.1、什么是数据

数据(data)是事实或观察的结果,是对客观事物的逻辑归纳,是用于表示客观事物的未经加工的的原始素材。

数据可以是连续的值,比如声音、图像,称为模拟数据。也可以是离散的,如符号、文字,称为数字数据。

在计算机系统中,数据以二进制信息单元0,1的形式表示。

数据的定义:数据是指对客观事件进行记录并可以鉴别的符号,是对客观事物的性质、状态以及相互关系等进行记载的物理符号或这些物理符号的组合。它是可识别的、抽象的符号。

1.2、什么是数据库

数据库就是存储和管理数据的仓库,数据按照一定的格式进行存储,用户可以对数据库中的数据进行增加修改、删除、查询等操作。数据库的分为关系型数据库和非关系型数据库。

关系型数据库是指采用了关系模型来组织数据的数据库,简单来说就是二维表格模型,好比Excel文件中的表格,强调使用表格的方式存储数据。关系型数据库核心元素:数据行、数据列、数据表、数据库(数据表的集合),非关系型数据库强调Key-Value的方式存储数据。

常用关系数据库:MySQL、SQLite、Oracle等 常见非关系数据库:MongoDB、Redis

数据库特点:持久化存储、读写速度极高、保证数据的有效性

关系型数据库管理系统是为管理关系型数据库而设计的软件系统,如果要使用关系型数据库就需要安装数据库管理系统,其实就是一个应用软件。关系型数据库管理系统分为关系型数据库服务端软件和关系型数据库客户端软件。 关系型数据库服务端软件主要负责管理不同的数据库,而每个数据库里面会有一系列数据文件,数据文件是用来存储数据的,其实数据库就是一系列数据文件的集合。 关系型数据库客户端软件主要负责和关系型数据库服务端软件进行通信,向服务端传输数据或者从服务端获取数据。

1.2.1、RDMS与NoSQL对比

功能对比

关系型数据库(sql/RDBMS) 非关系型数据库
强大的查询功能 ×
强一致性 ×
二级索引(目录) ×
灵活模式 ×
扩展性 ×
性能 ×

特点对比

1.3、MySQL数据库

MySQL是一个关系型数据库管理系统由瑞典 MySQL AB公司开发,属于Oracle旗下产品。MySQL是最流行的关系型数据库管理系统之一,在WEB应用方面,MySQL是最好的RDBMS (Relational Database Management System,关系数据库管理系统)应用软件之一。

数据库管理员(Database Administrator,简称*DBA*):是从事管理和维护数据库管理系统(DBMS)的相关工作人员的统称,属于运维工程师的一个分支,主要负责业务数据库从设计、测试到部署交付的全生命周期管理。

1.3.1、DBA工作内容

如下图所示,分别是开发DBA和运维DBA:

image-208

包括但不限于以下工作内容:

2、MySQL的安装

2.1、安装方式
2.2、源码安装
wget https://downloads.mysql.com/archives/get/p/23/file/mysql-5.6.40.tar.gz
tar xzvf mysql-5.6.40.tar.gz
cd mysql-5.6.40
yum install -y ncurses-devel libaio-devel cmake gcc gcc-c++ glibc
useradd mysql -s /sbin/nologin -M
[root@localhost mysql-5.6.40]# mkdir /application
[root@localhost mysql-5.6.40]# cmake . -DCMAKE_INSTALL_PREFIX=/application/mysql-5.6.38 \
-DMYSQL_DATADIR=/application/mysql-5.6.38/data \
-DMYSQL_UNIX_ADDR=/application/mysql-5.6.38/tmp/mysql.sock \
-DDEFAULT_CHARSET=utf8 \
-DDEFAULT_COLLATION=utf8_general_ci \
-DWITH_EXTRA_CHARSETS=all \
-DWITH_INNOBASE_STORAGE_ENGINE=1 \
-DWITH_FEDERATED_STORAGE_ENGINE=1 \
-DWITH_BLACKHOLE_STORAGE_ENGINE=1 \
-DWITHOUT_EXAMPLE_STORAGE_ENGINE=1 \
-DWITH_ZLIB=bundled \
-DWITH_SSL=bundled \
-DENABLED_LOCAL_INFILE=1 \
-DWITH_EMBEDDED_SERVER=1 \
-DENABLE_DOWNLOADS=1 \
-DWITH_DEBUG=0


[root@localhost mysql-5.6.40]# echo $?    #查看上条命令是否运行正确,0表示正确
[root@localhost mysql-5.6.40]# make
[root@localhost mysql-5.6.40]# make install
ln -s /application/mysql-5.6.38/ /application/mysql
cd /application/mysql/support-files/
cp my-default.cnf /etc/my.cnf
cp:是否覆盖"/etc/my.cnf" y
cp mysql.server /etc/init.d/mysqld
cd /application/mysql/scripts/
yum -y install autoconf
./mysql_install_db --user=mysql --basedir=/application/mysql --datadir=/application/mysql/data/
mkdir /application/mysql/tmp
chown -R mysql.mysql /application/mysql*
/etc/init.d/mysqld start
vim /etc/profile.d/mysql.sh
export PATH="/application/mysql/bin:$PATH"
source /etc/profile
vim /usr/lib/systemd/system/mysqld.service
[Unit]
Description=MySQL Server
Documentation=man:mysqld(8)
Documentation=https://dev.mysql.com/doc/refman/en/using-systemd.html
After=network.target
After=syslog.target
[Install]
WantedBy=multi-user.target
[Service]
User=mysql
Group=mysql
ExecStart=/application/mysql/bin/mysqld --defaults-file=/etc/my.cnf
LimitNOFILE = 5000

vim /etc/my.cnf
 basedir = /application/mysql/
 datadir = /application/mysql/data

systemctl daemon-reload
systemctl start mysqld
systemctl enable mysqld
mysqladmin -uroot password '123456'
mysql -uroot -p123456
mysql> show databases;
mysql> \q
Bye
2.3、二进制安装

当前运维和开发最常见的做法是二进制安装mysql

wget https://downloads.mysql.com/archives/get/p/23/file/mysql-5.6.40-linux-glibc2.12-x86_64.tar.gz
tar xzvf mysql-5.6.40-linux-glibc2.12-x86_64.tar.gz
mkdir /application
mv mysql-5.6.40-linux-glibc2.12-x86_64 /application/mysql-5.6.40
ln -s /application/mysql-5.6.40 /application/mysql #为了后续写的脚本,方便监控mysql
cd /application/mysql/support-files    #该文件夹有mysql初始化(预设)配置文件,覆盖文件是因为注释更全。
cp my-default.cnf /etc/my.cnf
cp:是否覆盖"/etc/my.cnf" y
cp mysql.server /etc/init.d/mysqld    #mysql.server包含如何启动mysql的脚本命令,让系统知道通过该命令启动mysql时的动作,该目录存放系统中各种服务的启动/停止脚本
cd /application/mysql/scripts
useradd mysql -s /sbin/nologin -M
 yum -y install autoconf    #不然会报错
./mysql_install_db --user=mysql --basedir=/application/mysql --data=/application/mysql/data
vim /etc/profile.d/mysql.sh    #写到环境变量的子配置文件内,未修改前只能使用/bin/mysql的命令才能启用,而不能全局使用
export PATH="/application/mysql/bin:$PATH"

source /etc/profile    #否则要重启系统才生效
sed -i 's#/usr/local#/application#g' /etc/init.d/mysqld /application/mysql/bin/mysqld_safe

此时不可以通过systemctl命令启动,只能通过/etc/init.d/mysql start启动(nginx也是,如果此时通过这样的命令启动Nginx,会导致systemctl start Nginx失败,因为冲突。)

vim /usr/lib/systemd/system/mysqld.service
[Unit]
Description=MySQL Server
Documentation=man:mysqld(8)
Documentation=https://dev.mysql.com/doc/refman/en/using-systemd.html
After=network.target
After=syslog.target
[Install]
WantedBy=multi-user.target
[Service]
User=mysql
Group=mysql
ExecStart=/application/mysql/bin/mysqld --defaults-file=/etc/my.cnf    #强制从my.cnf读配置,不然会从多个路径读配置
LimitNOFILE = 5000
server_id = 1    #用作主从的时候生效

vim /etc/my.cnf
 basedir = /application/mysql/
 datadir = /application/mysql/data

systemctl daemon-reload
systemctl start mysqld
systemctl enable mysqld
#此时ss -ntl 可以看到3306端口
mysqladmin -uroot password '123456'
mysql -uroot -p123456
2.4、客户端工具
2.4.1、mysql

用来连接管理数据库

2.4.2、mysqladmin
[root@localhost ~]# mysqladmin -uroot -p123456 create hellodb
[root@localhost ~]# mysqladmin -uroot -p123456 drop hellodb
[root@localhost ~]# mysqladmin -uroot -p123456 ping     # 检查服务端状态的
[root@localhost ~]# mysqladmin -uroot -p123456 status     # 服务器运行状态
[root@localhost ~]# mysqladmin -uroot -p123456 status     # 服务器状态 --sleep 2 --count 10 每两秒钟显示⼀次。服务器实时状态⼀共显示10次
Uptime:是mysql正常运行的时间。
Threads:指开启的会话数。
Questions: 服务器启动以来客户的问题(查询)数目  (应该是只要跟mysql作交互:不管你查询表,还是查询服务器状态都问记一次)。
Slow queries:按字面意思是慢查询的意思,不知道mysql认为多久才足够算为长查询,这个先放着。
Opens: 服务器已经打开的数据库表的数量
Flush tables: 服务器已经执行的flush ...、refresh和reload命令的数量。
open tables:通过命令是用的 数据库的表的数量,以服务器启动开始。
Queries per second avg:select语句平均查询时间
[root@localhost ~]# mysqladmin -uroot -p123456 extended-status 显示状态变量
[root@localhost ~]# mysqladmin -uroot -p123456 variables 显示服务器变量
[root@localhost ~]# mysqladmin -uroot -p123456 flush-privileges 数据库重读授权表,等同于reload
[root@localhost ~]# mysqladmin -uroot -p123456 flush-tables 关闭所有已经打开的表
[root@localhost ~]# mysqladmin -uroot -p123456 flush-threds 重置线程池缓存
[root@localhost ~]# mysqladmin -uroot -p123456 flush-status 重置⼤多数服务器状态变量
[root@localhost ~]# mysqladmin -uroot -p123456 flush-logs ⽇志滚动。主要实现⼆进制和中继⽇志滚动
[root@localhost ~]# mysqladmin -uroot -p123456 flush-hosts 清楚主机内部信息
[root@localhost ~]# mysqladmin -uroot -p123456 kill 杀死线程
[root@localhost ~]# mysqladmin -uroot -p123456 refresh 相当于同时执⾏flush-hosts flush-logs
[root@localhost ~]# mysqladmin -uroot -p123456 shutdown 关闭服务器进程
[root@localhost ~]# mysqladmin -uroot -p123456 version 服务器版本以及当前状态信息
[root@localhost ~]# mysqladmin -uroot -p123456 start-slave 启动复制,启动从服务器复制线程
[root@localhost ~]# mysqladmin -uroot -p123456 stop-slave 关闭复制线程
2.4.3、mysqldump
mysqldump -uroot -p --all-databases > /backup/mysqldump/all.db
# 备份所有数据库
mysqldump -uroot -p test > /backup/mysqldump/test.db
# 备份指定数据库
mysqldump -uroot -p  mysql db event > /backup/mysqldump/2table.db
# 备份指定数据库指定表(多个表以空格间隔)
mysqldump -uroot -p test --ignore-table=test.t1 --ignore-table=test.t2 > /backup/mysqldump/test2.db
# 备份指定数据库排除某些表
mysqladmin -uroot -p create db_name 
mysql -uroot -p  db_name < /backup/mysqldump/db_name.db
# 注:在导入备份数据库前,db_name如果没有,是需要创建的; 而且与db_name.db中数据库名是一样的才可以导入。
mysql > use db_name
mysql > source /backup/mysqldump/db_name.db
# source也可以还原数据库
2.5、图形化工具Navicate

在windows上使用navicate工具的时候,需要先登录到MySQL中,授权来自windows的IP访问:

[root@localhost ~]# grant all privileges on *.* to root@"192.168.88.%" identified by "123";

然后打开navicate,输入数据库的连接信息,连接到数据库中

image-209

3、MySQL架构

3.1、客户端与服务器模型

image-210

[root@localhost ~]# mysql -uroot -p123456 -h127.0.0.1
Warning: Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.6.40 MySQL Community Server (GPL)
Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> status
--------------
mysql  Ver 14.14 Distrib 5.6.40, for linux-glibc2.12 (x86_64) using  EditLine wrapper
Connection id:          4
Current database:
Current user:           root@localhost
SSL:                    Not in use
Current pager:          stdout
Using outfile:          ''
Using delimiter:        ;
Server version:         5.6.40 MySQL Community Server (GPL)
Protocol version:       10
Connection:             127.0.0.1 via TCP/IP
Server characterset:    latin1
Db     characterset:    latin1
Client characterset:    utf8
Conn.  characterset:    utf8
TCP port:               3306
Uptime:                 1 hour 55 min 9 sec
Threads: 2  Questions: 18  Slow queries: 0  Opens: 67  Flush tables: 1  Open tables: 60  Queries per second avg: 0.002
* 套接字连接方式
    * `mysql -uroot -p123456 -S /application/mysql/tmp/mysql.sock`
3.2、MySQL服务器构成
3.2.1、mysqld服务结构

image-211

3.2.2、mysql逻辑结构

MySQL的逻辑对象:做为管理人员或者开发人员操作的对象

最直观的数据:二维表,必须用库来存放

image-212

mysql逻辑结构与Linux系统对比

use test;

create table db01(id int);

MySQL Linux
目录
show databases; ls -l /
use mysql cd /mysql
文件
show tables; ls
二维表=元数据+真实数据行 文件=文件名+文件属性
3.3.3、mysql的物理结构

myisam:

mysql> show create table mysql.user\G;
...
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='Users and global privileges'

image-213

innodb:

image-214

image-215

3.3.4、段、区、页(块)

举例:假设有一个简单的 C 程序,它包含以下几个部分:

  1. 程序代码
  2. 全局变量
  3. 局部变量

在内存中,这个程序会被划分成几个段:

  1. 代码段: 存放程序的机器指令
  2. 数据段: 存放全局变量
  3. BSS段: 存放未初始化的全局变量
  4. 栈段: 存放函数的局部变量和调用信息

这些段是由编译器或链接器根据程序的结构划分的逻辑内存单元。

接下来,操作系统会将这些段映射到物理内存的区域上。操作系统会把物理内存划分成很多个相同大小(通常为 4KB)的区块,称为。操作系统会根据程序的内存需求,将程序的段映射到这些区块上。

当程序执行时,它会访问虚拟内存地址。操作系统会将虚拟内存地址转换为物理内存地址,并将相应的页(通常也是 4KB)加载到物理内存中。这就是的概念。

例如,当程序访问一个全局变量时,操作系统会先将包含该变量的页加载到物理内存的某个区中,然后程序就可以访问这个变量了。

总结一下:

  1. : 程序的逻辑内存单元,由编译器或链接器划分
  2. : 物理内存的基本单位,由操作系统管理
  3. : 虚拟内存的基本单位,由操作系统管理并映射到物理内存的区上

4、Mysql用户权限管理

4.1、Mysql用户基础操作

先truncate mysql.user;再重启mysql发现进不去了

mysqladmin -uroot password '123456'
* 忘记root密码
/etc/init.d/mysqld stop
mysqld_safe --skip-grant-tables --skip-networking    #skip-networking禁止掉3306端口,不允许网络登录
# 修改root密码
update user set password=PASSWORD('123456') where user='root' and host='localhost';
flush privileges;
4.2、用户管理
mysql> create user user01@'192.168.175.%' identified by '123456';
mysql> select user,host from mysql.user;
mysql> select user();
mysql> select database();
mysql> drop user user01@'192.168.175.%';
mysql> set password=PASSOWRD('123456')
mysql> update user set password=PASSWORD('user01') where user='root' and host='localhost';
mysql> grant all privileges on *.* to user01@'192.168.175.%' identified by '123456';
4.3、用户权限介绍
INSERT,SELECT, UPDATE, DELETE, CREATE, DROP, RELOAD, SHUTDOWN,  PROCESS, FILE, REFERENCES, INDEX, ALTER, SHOW DATABASES, SUPER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, CREATE TABLESPACE
  1. INSERT: 用于向数据库表中插入新数据。
  2. SELECT: 用于从数据库表中查询和检索数据。
  3. UPDATE: 用于更新数据库表中的现有数据。
  4. DELETE: 用于从数据库表中删除数据。
  5. CREATE: 用于创建数据库对象,如表、视图、存储过程等。
  6. DROP: 用于删除数据库对象,如表、视图、存储过程等。
  7. RELOAD: 用于重新加载 MySQL 服务器的配置文件。
  8. SHUTDOWN: 用于关闭 MySQL 服务器。
  9. PROCESS: 用于查看和管理 MySQL 服务器的进程。
  10. FILE: 用于读写服务器上的文件。
  11. REFERENCES: 用于创建外键约束。
  12. INDEX: 用于创建和管理数据库索引。
  13. ALTER: 用于修改数据库对象的结构,如表、视图、存储过程等。
  14. SHOW DATABASES: 用于查看当前 MySQL 服务器上的所有数据库。
  15. SUPER: 是一种特殊的权限,允许用户执行一些高级操作,如查看和杀死其他用户的进程。
  16. CREATE TEMPORARY TABLES: 用于创建临时表。
  17. LOCK TABLES: 用于手动锁定数据库表。
  18. EXECUTE: 用于执行存储过程和函数。
  19. REPLICATION SLAVE: 用于配置数据库复制时的从库权限。
  20. REPLICATION CLIENT: 用于获取数据库复制状态信息。
  21. CREATE VIEW: 用于创建数据库视图。
  22. SHOW VIEW: 用于查看数据库视图的定义。
  23. CREATE ROUTINE: 用于创建存储过程和函数。
  24. ALTER ROUTINE: 用于修改存储过程和函数。
  25. CREATE USER: 用于创建新的数据库用户账号。
  26. EVENT: 用于创建和管理数据库事件。
  27. TRIGGER: 用于创建和管理数据库触发器。
  28. CREATE TABLESPACE: 用于创建数据库表空间。

  29. 每次设定只能有一个属主,没有属组或其他用户的概念

grant     all privileges    on     *.*    to   user01@''192.168.175.%''  identified by    ''123'';
               --权限          --作用对象          --归属                 --密码

作用对象分解

企业中权限的设定

grant select,update,delete,insert on *.* to 'user01'@'192.168.175.%' identified by '123456';

实验思考问题

--创建wordpress数据库
create database wordpress;
--使用wordpress库
use wordpress;
--创建t1、t2表
create table t1 (id int);
create table t2 (id int);
--创建blog库
create database blog;
--使用blog库
use blog;
--创建t1表
create table tb1 (id int);

授权

grant select on *.* to 'wordpress'@'10.0.0.5%' identified by '123';
grant insert,delete,update on wordpress.* to 'wordpress'@'10.0.0.5%' identified by '123';
grant all on wordpress.t1 to 'wordpress'@'10.0.0.5%' identified by '123';

5、Mysql启动关闭流程

image-216

/usr/lib/systemd/system/mysqld.service告知系统从哪执行mysqld

mysld_safe是个脚本,mysqld才是binary

/etc/init.d/mysqld start ------> mysqld_safe ------> mysqld
/etc/init.d/mysqld stop 
mysqladmin -uroot -p123456 shutdown
kill -9 pid ?
killall mysqld ?
pkill mysqld ?
pkill mysqld_safe

出现问题:

  1. 如果在业务繁忙的情况下,数据库不会释放pid和sock文件
  2. 号称可以达到和Oracle一样的安全性,但是并不能100%达到
  3. 在业务繁忙的情况下,丢数据(补救措施,高可用)

6、Mysql实例初始化配置

image-217

--skip-grant-tables 
--skip-networking
--datadir=/application/mysql/data
--basedir=/application/mysql
--defaults-file=/etc/my.cnf
--pid-file=/application/mysql/data/db01.pid
--socket=/application/mysql/data/mysql.sock
--user=mysql
--port=3306
--log-error=/application/mysql/data/db01.err
#cmake:
socket=/application/mysql/tmp/mysql.sock
#命令行:
--socket=/tmp/mysql.sock
#配置文件:
/etc/my.cnf中[mysqld]标签下:socket=/opt/mysql.sock
#default参数:
--defaults-file=/tmp/a.txt配置文件中[mysqld]标签下:socket=/tmp/test.sock

7、MySQL多实例配置

7.1、多实例
7.2、实战配置
#创建数据目录
mkdir -p /data/330{7..9}
#创建配置文件
touch /data/330{7..9}/my.cnf
touch /data/330{7..9}/mysql.log
#编辑3307配置文件
vim /data/3307/my.cnf
[mysqld]
basedir=/application/mysql
datadir=/data/3307/data
socket=/data/3307/mysql.sock
log_error=/data/3307/mysql.log
log-bin=/data/3307/mysql-bin
server_id=7
port=3307
[client]
socket=/data/3307/mysql.sock
#编辑3308配置文件
vim /data/3308/my.cnf
[mysqld]
basedir=/application/mysql
datadir=/data/3308/data
socket=/data/3308/mysql.sock
log_error=/data/3308/mysql.log
log-bin=/data/3308/mysql-bin
server_id=8
port=3308
[client]
socket=/data/3308/mysql.sock
#编辑3309配置文件
vim /data/3309/my.cnf
[mysqld]
basedir=/application/mysql
datadir=/data/3309/data
socket=/data/3309/mysql.sock
log_error=/data/3309/mysql.log
log-bin=/data/3309/mysql-bin
server_id=9
port=3309
[client]
socket=/data/3309/mysql.sock
#初始化3307数据,每条命令需要间隔若干秒,否则失败
/application/mysql/scripts/mysql_install_db \
--user=mysql \
--defaults-file=/data/3307/my.cnf \
--basedir=/application/mysql --datadir=/data/3307/data
#初始化3308数据,每条命令需要间隔若干秒,否则失败
/application/mysql/scripts/mysql_install_db \
--user=mysql \
--defaults-file=/data/3308/my.cnf \
--basedir=/application/mysql --datadir=/data/3308/data
#初始化3309数据,每条命令需要间隔若干秒,否则失败
/application/mysql/scripts/mysql_install_db \
--user=mysql \
--defaults-file=/data/3309/my.cnf \
--basedir=/application/mysql --datadir=/data/3309/data
#修改目录权限
chown -R mysql.mysql /data/330*
#启动多实例
mysqld_safe --defaults-file=/data/3307/my.cnf &
mysqld_safe --defaults-file=/data/3308/my.cnf &
mysqld_safe --defaults-file=/data/3309/my.cnf &
#设置每个实例的密码
#mysqladmin -S /data/3307/mysql.sock -uroot password '123456'
#查看server_id
mysql -S /data/3307/mysql.sock -e "show variables like 'server_id'"
mysql -S /data/3308/mysql.sock -e "show variables like 'server_id'"
mysql -S /data/3309/mysql.sock -e "show variables like 'server_id'"
# 进入单独的mysql实例
mysql -S /data/3307/mysql.sock -uroot
# 关闭实例,如果设置了密码登录就需要密码了 用参数-p
mysqladmin -S /data/3307/mysql.sock -uroot shutdown
mysqladmin -S /data/3308/mysql.sock -uroot shutdown
mysqladmin -S /data/3309/mysql.sock -uroot shutdown

8、SQL语句

8.1、DDL数据定义语句
create database db01;
--创建数据库
create database DB01;
--数据库名区分大小写(注意windows里面不区分)
show variables like 'lower_case_table_names';
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| lower_case_table_names | 0     |
+------------------------+-------+

/*
具体lower_case_table_names 取值详解如下:
0:区分大小写
1:不区分大小写,存储的时候,将表名都转换成小写
2:不区分大小写,存储的时候,表名跟建表时大小写保持一致
*/

show databases;
--查看数据库(DQL)
show create database db01;
--查看创建数据库语句
help create database;
--查看创建数据库语句帮助
create database db02 charset utf8;
--创建数据库的时候添加属性
drop database db02;
--删除数据库db02
alter database db01 charset utf8;
show create database db01;
help create table;
--查看创表语句的帮助

use db01;
--进入库中
create table student(
sid int,
sname varchar(20),
sage tinyint,
sgender enum('m','f'),
comtime datetime
);
--创建表,并且定义每一列
int 整数 -231~230
tinyint 整数 -128~127
varchar 字符类型(可变长)
char 字符类型(定长)
enum 枚举类型
datetime 时间类型 年月日时分秒
--删除表
drop table student;

--带数据属性创建学生表
create table student(
sid int not null primary key auto_increment comment '学号',
sname varchar(20) not null comment '学生姓名',
sgender enum('m','f') not null default 'm' comment '学生性别',
cometime datetime not null comment '入学时间'
)charset utf8 engine innodb;

--查看建表语句
show create table student;
--查看表
show tables;
--查看表中列的定义信息
desc student;
not null 不允许是空
primary key 主键(唯一且非空)
auto_increment 自增,此列必须是primary key或者unique key
unique key 单独的唯一的
default 默认值
unsigned 非负数
comment 注释
drop table student;
alter table student rename stu;
--修改表名
alter table stu add age int;
--添加列和列数据类型的定义
alter table stu add test varchar(20),add qq int;
--添加多个列
alter table stu add classid varchar(20) first;
--指定位置进行添加列(表首)
alter table stu add phone int after age;
--指定位置进行添加列(指定列)
alter table stu drop qq;
--删除指定的列及定义
alter table stu modify sid varchar(20);
--修改列及定义(列属性)
alter table stu change phone telphone char(20);
--修改列及定义(列名及属性)
8.2、DCL数据控制语言
grant all on *.* to root@'192.168.175.%' identified by '123456'
--授予root@'192.168.175.%'用户所有权限(非超级管理员)
grant all on *.* to root@'192.168.175.%' identified by '123456' with grant option;
--授权一个超级管路员
--max_queries_per_hour:一个用户每小时可发出的查询数量
--max_updates_per_hour:一个用户每小时可发出的更新数量
--max_connections_per_hour:一个用户每小时可连接到服务器的次数
--max_user_connections:允许同时连接数量
revoke select on *.* from root@'192.168.175.%';
--收回select权限
show grants for root@'192.168.175.%';
--查看权限
8.3、DML数据操作语言
insert into stu valus('linux01',1,NOW(),'zhangsan',20,'m',NOW(),110,123456);
--基础用法,插入数据
insert into stu(classid,birth.sname,sage,sgender,comtime,telnum,qq) values('linux01',1,NOW(),'zhangsan',20,'m',NOW(),110,123456);
--规范用法,插入数据
insert into stu(classid,birth.sname,sage,sgender,comtime,telnum,qq) values('linux01',1,NOW(),'zhangsan',20,'m',NOW(),110,123456),
('linux02',2,NOW(),'zhangsi',21,'f',NOW(),111,1234567);
--插入多条数据
insert into stu(classid,birth.sname,sage,sgender,comtime,qq) values('linux01',1,NOW(),'zhangsan',20,'m',NOW(),123456)
--少了电话
update student set sgender='f';
--不规范
update student set sgender='f' where sid=1;
--规范update修改
update student set sgender='f' where 1=1;
--如果非要全表修改
update mysql.user set password=PASSWORD('123456') where user='root' and host='localhost';
--修改密码,需要刷新权限flush privileges
delete from student;
--不规范
delete from student where sid=3;
--规范删除(危险)
truncate table student;
--DDL清空表中的内容,恢复表原来的状态,自增重新从1开始,否则delete依旧正常增长
alter table student add status enum(1,0) default 1;
--额外添加一个状态列
update student set status='0' where sid=1;
--使用update
select * from student where status=1;
--应用查询存在的数据
8.4、DQL数据查询语言
mysql -uroot -p123 < world.sql
--或者mysql> \. /root/world.sql
--常用用法
select countrycode,district from city;
--常用用法
select countrycode from city;
--查询单列
select countrycode,district from city limit 2;
select id,countrycode,district from city limit 2,2;   --从第二个开始继续显示2个
--行级查询
select name,population from city where countrycode='CHN';
--条件查询
select name,population from city where countrycode='CHN' and district='heilongjiang';
--多条件查询
select name,population,countrycode from city where countrycode like '%H%' limit 10;
--模糊查询
select id,name,population,countrycode from city order by countrycode limit 10;
--排序查询(顺序)
select id,name,population,countrycode from city order by countrycode desc limit 10;
--排序查询(倒序)
select * from city where population>=1410000;
--范围查询(>,<,>=,<=,<>)
select * from city where countrycode='CHN' or countrycode='USA';
--范围查询OR语句
select * from city where countrycode in ('CHN','USA');
--范围查询IN语句
select country.name,city.name,city.population,country.code from city,country where city.countrycode=country.code and city.population < 100;
--多表查询

9、字符集定义

image-218

show charset;
show collation;

字符集设置

source /etc/sysconfig/i18n
echo $LANG
cmake . 
-DDEFAULT_CHARSET=utf8 \
-DDEFAULT_COLLATION=utf8_general_ci \
-DWITH_EXTRA_CHARSETS=all \
# 在编译的时候指定
[mysqld]
character-set-server=utf8
# 在配置文件中指定
mysql> create database db01 charset utf8 default collate = utf8_general_ci;
# 建库的时候
mysql>  CREATE TABLE `test` (
`id` int(4) NOT NULL AUTO_INCREMENT,
`name` char(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
# 建表的时候
mysql> alter database db01 CHARACTER SET utf8 collate utf8_general_ci;
mysql> alter table t1 CHARACTER SET utf8;
# 修改字符集

10、MySQL数据类型

10.1、数据类型介绍
类型 说明
整数 tinyint 极小整数数据类型(0-255)
整数 smallint 较小整数数据类型(-2^15到2^15-1)
整数 mediumint 中型整数数据类型
整数 int 常规(平均)大小的整数数据类型(-2^31到2^31-1)
整数 bigint 较大整数数据类型
浮点数 float 小型单精度(四个字节)浮点数
浮点数 double 常规双精度(八个字节)浮点数
定点数 decimal 包含整数部分、小数部分或同时包括二者的精确值数值
BIT BIT 位字段值
类型 说明
文本 char 固定长度字符串,最多为255个字符
文本 varchar 可变长度字符串,最多为65535个字符
文本 tinytext 可变长字符串,最多为255个字符
文本 text 可变长字符串,最多为65535个字符
文本 mediumtext 可变长字符串,最多为16777215个字符
文本 longtext 可变长字符串,最多为4294967295个字符
整数 enum 由一组固定的合法值组成的枚举
整数 set 由一组固定的合法值组成的集
类型 说明
二进制 binary 类似于char(固定长度)类型,但存储的是二进制字节字符串,而不是非二进制字符串
二进制 varbinary 类似于varchar(可变长度)类型,
BLOB tinyblob 最大长度为255个字节的BLOB列
BLOB blob 最大长度为65535个字节的BLOB列
BLOB MEDIUDMBLOB 最大长度为16777215个字节的BLOB列
BLOB longblob 最大长度为4294967295个自己的blob列
10.2、列属性介绍
数据类型 属性 说明
数值 unsigned 禁止使用负值
仅整数 auto_increment 生成包含连续唯一整数值的序列
字符串 character set 指定要使用的字符集
字符串 collate 指定字符集整理
字符串 binary 指定二进制整理
全部* Null或not Null 指定列是否可以包含NULL值
全部 Default 如果未为新记录指定值,则为其提供默认值

11、索引介绍

11.1、索引类型介绍

image-219

image-220

11.2、索引管理
alter table test add index index_name(name);
--创建索引
create index index_name on test(name);
--创建索引
desc table;
--查看索引
show index from table;
--查看索引
alter table test drop key index_name;
--删除索引
alter table student add unique key uni_xxx(xxx);
--添加主键索引(略)
--添加唯一性索引
select count(*) from city;
--查看表中数据行数
select count(distinct(name)) from city;
--查看去重数据行数
11.3、前缀索引和联合索引
11.3.1、前缀索引
alter table test add index idx_name(name(10));
--比如表里很多城市以D开头,Dalas Des'Moine Denver Detroit,以字母D建立的索引会加快检索速度
11.3.2、联合索引
create table people (id int,name varchar(20),age tinyint,money int ,gender enum('m','f'));
--创建people表
alter table people add index  idx_gam(gender,age,money);
--创建联合索引
11.4、explain详解

EXPLAIN 是 SQL 中一个非常有用的关键字,它可以帮助我们分析和优化 SQL 查询的执行计划。使用 EXPLAIN 语句可以获取 SQL 查询的执行计划,了解查询是如何执行的,从而帮助我们识别并优化查询性能瓶颈。

下面我们来详细讲解 EXPLAIN 语句的用法和输出含义:

  1. 使用方法:

  2. 在 SQL 语句前加上 EXPLAIN 关键字即可,例如: EXPLAIN SELECT * FROM users WHERE id = 1;

mysql> explain select name,countrycode from city where id=1;
  1. 输出信息:

  2. id: 查询编号,表示查询执行的顺序。如果有子查询,每个子查询都会分配一个单独的 id。

  3. select_type: 查询类型,包括 SIMPLE、PRIMARY、SUBQUERY 等。
  4. table: 正在访问的数据表名称。
  5. partitions: 匹配的分区情况。
  6. type: 访问类型,包括 system、const、eq_ref、ref、range、index、ALL 等,访问类型由快到慢依次排列。
  7. possible_keys: 可能使用的索引。
  8. key: 实际使用的索引名称。
  9. key_len: 使用索引的长度。
  10. ref: 列上的比较操作。
  11. rows: 估计要读取的行数。
  12. filtered: 条件过滤后剩余的行占原始行的比例。
  13. Extra: 一些额外的信息,如 Using index、Using where 等。

  14. 应用场景:

  15. 查看查询计划,了解 MySQL 是如何执行 SQL 查询的。

  16. 分析查询瓶颈,如果 typeALL 表示全表扫描,可能需要优化索引。
  17. 检查是否使用了正确的索引,possible_keyskey 列可以告诉你 MySQL 选择了哪个索引。
  18. 估算查询的行数,rows 列给出了 MySQL 估计的行数,可以帮助判断查询的效率。
11.4.1、MySQL查询数据的方式
11.4.2、index
11.4.3、range
mysql> alter table city add index idx_city(population);
mysql> explain select * from city where population>30000000;
11.4.4、ref
mysql> alter table city drop key idx_code;
mysql> explain select * from city where countrycode='chn';
mysql> explain select * from city where countrycode in ('CHN','USA');
mysql> explain select * from city where countrycode='CHN' union all select * from city where countrycode='USA';
11.4.5、eq_ref
join B 
on A.sid=B.sid
11.4.6、const、system
mysql> explain select * from city where id=1000;
11.4.7、NULL
mysql> explain select * from city where id=1000000000000000000000000000;
11.4.8、Extra(扩展)

在 MySQL 中,当你执行一条 SQL 语句时,结果集通常包含一个名为 "Extra" 的列,它提供了一些额外的信息,可以帮助我们更好地理解 SQL 语句的执行过程。下面是一些常见的 "Extra" 信息及其含义:

  1. No index used
  2. 表示在执行 SQL 语句时,数据库没有使用任何索引。这可能会降低查询性能。
  3. Using index
  4. 表示在执行 SQL 语句时,数据库使用了索引来优化查询。这通常意味着查询性能较好。
  5. Using index condition
  6. 表示在执行 SQL 语句时,数据库使用了索引条件下推(Index Condition Pushdown)技术来优化查询。这可以减少不必要的行访问。
  7. Using index for group-by
  8. 表示在执行 GROUP BY 语句时,数据库使用了索引来优化分组操作。
  9. Using temporary
  10. 表示在执行 SQL 语句时,数据库创建了一个临时表来存储中间结果。这可能会降低查询性能。
  11. Using filesort
  12. 表示在执行 SQL 语句时,数据库需要对结果集进行额外的排序操作。这可能会降低查询性能。
  13. Using join buffer (Block Nested Loop)
  14. 表示在执行 JOIN 查询时,数据库使用了连接缓冲区来优化连接操作。
  15. Impossible WHERE noticed after reading const tables
  16. 表示在执行 SQL 语句时,数据库发现 WHERE 条件永远为 false,因此直接返回空结果集。
  17. No tables used
  18. 表示在执行 SQL 语句时,数据库没有访问任何表,例如执行 SELECT 1;
  19. Select tables optimized away
    • 表示在执行 SQL 语句时,数据库优化掉了部分表的访问,例如在某些情况下,数据库可以直接从索引中获取所需的数据,而无需访问表本身。

这些 "Extra" 信息可以帮助我们了解 SQL 语句的执行过程,识别潜在的性能问题,并针对性地优化查询。在优化 SQL 语句时,我们应该关注那些可能降低性能的信息,例如 "Using temporary"、"Using filesort" 等

mysql> explain select * from city where countrycode='CHN' order by population;
mysql> explain select * from city where population>30000000 order by population;
mysql> select * from city where population=2870300 order by population;
--key_len: 越小越好
--前缀索引去控制,rows: 越小越好
11.5、建立索引的原则(规范)

例如: 学生表中学号是具有唯一性的字段。为该字段建立唯一性索引可以很快的确定某个学生的信息。 如果使用姓名的话,可能存在同名现象,从而降低查询速度。 主键索引和唯一键索引,在查询中使用是效率最高的。

select count(*) from world.city;
select count(distinct countrycode) from world.city;
select count(distinct countrycode,population ) from world.city;
--注意:如果重复值较多,可以考虑采用联合索引

重点关注:

select * from table;
select  * from tab where 1=1;
--全表扫描
--情况1
select * from table;
--全表扫描
selec  * from tab  order by  price  limit 10;
--需要在price列上建立索引
--情况2
select * from table where name='zhangsan'; 
--name列没有索引
--1、换成有索引的列作为查询条件
--2、将name列建立索引
mysql> explain select * from city where population>3000 order by population;
--如果业务允许,可以使用limit控制
--结合业务判断,有没有更好的方式。如果没有更好的改写方案就尽量不要在mysql存放这个数据了,放到redis里面。
--错误的例子:select * from test where id-1=9; 
--正确的例子:select * from test where id=10;
mysql> create table test (id int ,name varchar(20),telnum varchar(10));
--例如列类型为整形,非要在检索条件中用字符串
mysql> insert into test values(1,'zs','110'),(2,'l4',120),(3,'w5',119),(4,'z4',112);
mysql> explain select * from test where telnum=120;
mysql> alter table test add index idx_tel(telnum);
mysql> explain select * from test where telnum=120;
mysql> explain select * from test where telnum=120;
mysql> explain select * from test where telnum='120';
mysql> select * from tab where telnum <> '1555555';
mysql> explain select * from tab where telnum <> '1555555';
EXPLAIN  SELECT * FROM teltab WHERE telnum IN ('110','119');
--改写成
EXPLAIN SELECT * FROM teltab WHERE telnum='110'
UNION ALL
SELECT * FROM teltab WHERE telnum='119'
--走range索引扫描
EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '31%';
--不走索引
EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '%110';

%linux%类的搜索需求,可以使用Elasticsearch -------> ELK

CREATE TABLE t1 (id INT,NAME VARCHAR(20),age INT ,sex ENUM('m','f'),money INT);
ALTER TABLE t1 ADD INDEX t1_idx(money,age,sex);
DESC t1
SHOW INDEX FROM t1
--走索引的情况测试
EXPLAIN SELECT NAME,age,sex,money FROM t1 WHERE money=30 AND age=30  AND sex='m';
--部分走索引
EXPLAIN SELECT NAME,age,sex,money FROM t1 WHERE money=30 AND age=30;
EXPLAIN SELECT NAME,age,sex,money FROM t1 WHERE money=30  AND sex='m'; 
--不走索引
EXPLAIN SELECT  NAME,age,sex,money FROM t1 WHERE age=20
EXPLAIN SELECT NAME,age,sex,money FROM t1 WHERE age=30 AND sex='m';
EXPLAIN SELECT NAME,age,sex,money FROM t1 WHERE sex='m';

12、MySQL的存储引擎

12.1、存储引擎简介

Mysql的数据用不同的技术存储在文件(或内存)中,不同的技术拥有不同的存储机制、索引技巧、锁定水平,通过选择不同的技术获得不同的功能,从而改善应用的整体功能,而这些不同的技术和功能就是存储引擎(也称作表引擎)

Mysql支持的存储引擎:InnoDB、MyISAM、MEMORY、CSV、BLACKHOLE、FEDERATED、MRG_MYISAM、 ARCHIVE、PERFORMANCE_SCHEMA。其中NDB和InnoDB提供事务安全表,其他存储引擎都是非事务安全表

image-221

12.2、MySQL自带的存储引擎类型
12.3、不同存储引擎介绍
12.3.1、InnoDB

MySql 5.6 版本默认的存储引擎。InnoDB 是一个事务安全的存储引擎,它具备提交、回滚以及崩溃恢复的功能以 保护用户数据。InnoDB 的行级别锁定以及 Oracle 风格的一致性无锁读提升了它的多用户并发数以及性能。

InnoDB 将用户数据存储在聚集索引中以减少基于主键的普通查询所带来的 I/O 开销。为了保证数据的完整性,

InnoDB 还支持外键约束。

应用场景:并发高、数据一致、更新和删除操作较多、事务的完整提交和回滚

innodb体系结构:

innodb主要包括 内存池、后台线程以及存储文件

内存池是由多个内存块组成的,主要包括缓存磁盘数据,redolog 缓冲等

后台线程则包括了: Master Thread、IO Thread、Purge Thread等

由 InnoDB 存储引擎实现的表的存储结构文件一般包括表结构文件(.frm)、共享表空间文件(ibdata1)、独占表空间文件(ibd)以及日志文件(redo 文件等)等。

12.3.2、MyISAM(读、插入操作)

MyISAM既不支持事务、也不支持外键、其优势是访问速度快,但是表级别的锁定限制了它在读写负载方面的性能, 因此它经常应用于只读或者以读为主的数据场景。

应用场景:读操作和插入操作为主

12.3.3、Memory(内存存储数据,快速定位记录)

在内存中存储所有数据,应用于对非关键数据由快速查找的场景。Memory类型的表访问数据非常快,因为它的数据 是存放在内存中的,并且默认使用HASH索引,但是一旦服务关闭,表中的数据就会丢失

应用场景:快速定位记录

12.3.4、BLACKHOLE(只接收不保存数据)

黑洞存储引擎,类似于 Unix 的 /dev/null,Archive 只接收但却并不保存数据。对这种引擎的表的查询常常返 回一个空集。这种表可以应用于 DML 语句需要发送到从服务器,但主服务器并不会保留这种数据的备份的主从配置 中。

12.3.5、CSV

它的表真的是以逗号分隔的文本文件。CSV 表允许你以 CSV 格式导入导出数据,以相同的读和写的格式和脚本和应用交互数据。由于 CSV 表没有索引,你最好是在普通操作中将数据放在 InnoDB 表里,只有在导入或导出阶段 使用一下 CSV 表。

12.3.6、NDB

(又名 NDBCLUSTER)——这种集群数据引擎尤其适合于需要最高程度的正常运行时间和可用性的应用。注意:NDB 存 储引擎在标准 MySql 5.6 版本里并不被支持。

目前能够支持 MySql 集群的版本有:基于 MySql 5.1 的 MySQL Cluster NDB 7.1;基于 MySql 5.5 的 MySQL Cluster NDB 7.2;基于 MySql 5.6 的 MySQL Cluster NDB 7.3。同样基于 MySql 5.6 的 MySQL Cluster NDB 7.4

目前正处于研发阶段。

12.3.7、Merge(超大规模数据场景)

允许 MySql DBA 或开发者将一系列相同的 MyISAM 表进行分组,并把它们作为一个对象进行引用。适用于超大规 模数据场景,如数据仓库。

12.3.8、Federated(分布式)

提供了从多个物理机上联接不同的 MySql 服务器来创建一个逻辑数据库的能力。适用于分布式或者数据市场的场景。

12.3.9、Example(保存例子)

这种存储引擎用以保存阐明如何开始写新的存储引擎的 MySql 源码的例子。它主要针对于有兴趣的开发人员。这 种存储引擎就是一个啥事也不做的 "存根"。你可以使用这种引擎创建表,但是你无法向其保存任何数据,也无法从 它们检索任何索引。

12.3.10、经典面试题:InnoDB和MyISAM区别

两种存储引擎的大致区别表现在:

12.4、查看当前MySQL的存储引擎
mysql> show engines
--查看当前MySQL支持的存储引擎类型
mysql> select table_schema,table_name,engine from information_schema.tables where engine='innodb';
--查看innodb的表有哪些
mysql> select table_schema,table_name,engine from information_schema.tables where engine='myisam';
--查看myisam的表有哪些
#进入mysql目录
[root@localhost~l]# cd /application/mysql/data/mysql
#查看所有user的文件
[root@localhost mysql]# ll user.*
-rw-rw---- 1 mysql mysql 10684 Mar  6  2017 user.frm
-rw-rw---- 1 mysql mysql   960 Aug 14 01:15 user.MYD
-rw-rw---- 1 mysql mysql  2048 Aug 14 01:15 user.MYI
#进入word目录
[root@localhost world]# cd /application/mysql/data/world/
#查看所有city的文件
[root@localhost world]# ll city.*
-rw-rw---- 1 mysql mysql   8710 Aug 14 16:23 city.frm
-rw-rw---- 1 mysql mysql 688128 Aug 14 16:23 city.ibd
12.5、innodb存储引擎的简介
功能 支持 功能 支持
存储限制 64TB 索引高速缓存
MVCC 数据高速缓存
B树索引 自适应散列索引
群集索引 复制
压缩数据 更新数据字典
加密数据 地理空间数据类型
查询高速缓存 地理空间索引
事务 全文搜索索引
锁定粒度 群集数据库
外键 备份和恢复
文件格式管理 快速索引创建
多个缓冲区池 performance_schema
更改缓冲 自动故障恢复
SELECT @@default_storage_engine;
--查询默认存储引擎
SHOW CREATE TABLE City
SHOW TABLE STATUS LIKE 'CountryLanguage'
--查看表的存储引擎
[mysqld]
default-storage-engine=<Storage Engine>
--在配置文件的[mysqld]标签下添加
SET @@storage_engine=<Storage Engine>
--在MySQL命令行中临时设置
CREATE TABLE t (i INT) ENGINE = <Storage Engine>;
--建表的时候指定存储引擎
12.6、【实战】存储引擎切换
#[root@db01 ~]# mysqldump -uroot -p123 -A --triggers -R --master-data=2 >/tmp/full.sql
#由于没有开启bin logging所以去掉 --master-data=2
[root@db01 ~]# mysqldump -uroot -p123 -A --triggers -R >/tmp/full.sql
[root@db01 ~]# sed -i 's#ENGINE=MYISAM#ENGINE=INNODB#g' /tmp/full.sql
12.7、表空间介绍

image-222

[root@localhost ~]# ll /application/mysql/data/
-rw-rw----. 1 mysql mysql 12582912 6月   8 09:43 ibdata1
# 物理查看
mysql> show variables like '%path%';
innodb_data_file_path = ibdata1:12M:autoextend
#编辑配置文件
[root@db01 ~]# vim /etc/my.cnf
[mysqld]
#注意,idata1文件已存在,可能超过50M导致mysqld重启不成功,建议重命名了再重启。
innodb_data_file_path=ibdata1:50M;ibdata2:50M:autoextend
[root@localhost ~]# ll /application/mysql/data/world/
-rw-rw----. 1 mysql mysql 589824 6月   6 10:23 city.ibd
# 物理查看
mysql> show variables like '%per_table%';
innodb_file_per_table = ON
12.8、【实战】数据库服务损坏
[root@db01 ~]# cp -r /application/mysql/data/world/ /data/3307/data/
[root@db01 ~]# chown -R mysql.mysql /application/mysql/data/world
  1. 启动新数据库
#pkill mysqld 先干掉mysql
[root@db01 ~]# mysqld_safe --defaults-file=/data/3307/my.cnf &
  1. 登陆数据库查看
mysql> show databases;
  1. 查询表中数据
mysql> select * from city;
ERROR 1146 (42S02): Table 'world.city' doesnot exist
--先 use world;
  1. 找到以前的表结构在新库中创建表,此处演示用的show命令实际需要从原来的开发文档查看
mysql> show create table world.city;
--删掉外键创建语句
CREATE TABLE `city` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `Name` char(35) NOT NULL DEFAULT '',
  `CountryCode` char(3) NOT NULL DEFAULT '',
  `District` char(20) NOT NULL DEFAULT '',
  `Population` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`ID`),
  KEY `CountryCode` (`CountryCode`),
  KEY `idx_city` (`Population`,`CountryCode`),
    CONSTRAINT `city_ibfk_1` FOREIGN KEY (`CountryCode`) REFERENCES `country` (`Code`)
) ENGINE=InnoDB AUTO_INCREMENT=4080 DEFAULT CHARSET=latin1;


mysql> CREATE TABLE `city_new` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `Name` char(35) NOT NULL DEFAULT '',
  `CountryCode` char(3) NOT NULL DEFAULT '',
  `District` char(20) NOT NULL DEFAULT '',
  `Population` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`ID`),
  KEY `CountryCode` (`CountryCode`)
 --KEY `idx_city` (`Population`,`CountryCode`),
 --干掉外键的约束,否则失败
    #CONSTRAINT `city_ibfk_1` FOREIGN KEY (`CountryCode`) REFERENCES `country` (`Code`)
) ENGINE=InnoDB AUTO_INCREMENT=4080 DEFAULT CHARSET=latin1;
  1. 删除表空间文件
mysql> alter table city_new discard tablespace;
  1. 拷贝旧表空间文件
[root@db01 world]# cp /data/3307/data/world/city.ibd /data/3307/data/world/city_new.ibd
  1. 授权
[root@db01 world]# cd /data/3307/data/world
[root@db01 world]# chown -R mysql.mysql *
  1. 导入表空间
mysql> alter table city_new import tablespace;
mysql> alter table city_new rename city;

13、MySQL事物

13.1、事务

image-223

START TRANSACTION(或 BEGIN):显式开始一个新事务
SAVEPOINT:分配事务过程中的一个位置,以供将来引用
COMMIT:永久记录当前事务所做的更改
ROLLBACK:取消当前事务所做的更改
ROLLBACK TO SAVEPOINT:取消在 savepoint 之后执行的更改
RELEASE SAVEPOINT:删除 savepoint 标识符
SET AUTOCOMMIT:为当前连接禁用或启用默认 autocommit 模式
begin;
sql1
sql2
sql3
...
commit;
begin;
sql1
sql2
sql3
...
rollback;
--默认自动提交,关闭后需要命令commit才生效,此时另一个客户端除非重新登录才能看到变化(隔离性)
--开启两个窗口,不同update,不同时间commit演示
mysql> show variables like 'autocommit';
--查看自动提交
mysql> set autocommit=0;
--临时关闭
[root@db01 world]# vim /etc/my.cnf
[mysqld]
autocommit=0
--永久关闭
mysql> create table stu(id int,name varchar(10),sex enum('f','m'),money int);
mysql> begin;
mysql> insert into stu(id,name,sex,money) values(1,'zhang3','m',100), (2,'zhang4','m',110);
mysql> commit;
--事务回滚
mysql> begin;
mysql> update stu set name='zhang3';
mysql> delete from stu;
mysql> rollback;
13.1.1、事务日志redo
update t1 set num=2 where num=1;
13.1.2、事务日志undo
[root@db01 data]# ll /application/mysql/data/
-rw-rw---- 1 mysql mysql 50331648 Aug 15 06:34 ib_logfile0
-rw-rw---- 1 mysql mysql 50331648 Mar  6  2021 ib_logfile1
# redo位置
[root@db01 data]# ll /application/mysql/data/
-rw-rw---- 1 mysql mysql 79691776 Aug 15 06:34 ibdata1
-rw-rw---- 1 mysql mysql 79691776 Aug 15 06:34 ibdata2
# undo位置
13.1.3、事务中的锁
mysql> show variables like '%iso%';
--查看隔离级别
[mysqld]
transaction_isolation=read-uncommit
--修改隔离级别为RU
mysql> use test
mysql> select * from stu;
mysql> insert into stu(id,name,sex,money) values(2,'li4','f',123);
[mysqld]
transaction_isolation=read-commit
--修改隔离级别为RC

14、MySQL日志管理

14.1、日志简介
日志文件 选项 文件名,表名 程序
错误 --log-error host_name.err
常规 --general_log host_name.log general_log
慢速查询 --slow_query_log --long_query_time host_name-slow.log mysqldumpslow
二进制 --log-bin --expire-logs-days host_name-bin.000001 mysqlbinlog
审计 --audit_log --audit_log_file ... audit.log
14.2、错误日志
[root@db01 ~]# vim /etc/my.cnf
# 编辑配置文件
[mysqld]
log_error=/application/mysql/data/$hostname.err
mysql> show variables like 'log_error';
# 查看方式
14.3、一般查询日志
[root@db01 ~]# vim /etc/my.cnf
[mysqld]
general_log=on
general_log_file=/application/mysql/data/$hostnamel.log
# 编辑配置文件
mysql> show variables like '%gen%';
# 查看方式
14.4、二进制日志
14.4.1、二进制日志的管理操作实战
[root@db01 data]# vim /etc/my.cnf
[mysqld]
log-bin=mysql-bin
binlog_format=row

注意:在mysql5.7中开启binlog必须要加上server-id。

[root@db01 data]# vim /etc/my.cnf
[mysqld]
log-bin=mysql-bin
binlog_format=row
server_id=1
[root@db01 data]# ll /application/mysql/data/
-rw-rw---- 1 mysql mysql      285 Mar  6  2021 mysql-bin.000001
#物理查看
mysql> show binary logs;
mysql> show master status;
#命令行查看
mysql> show binlog events in 'mysql-bin.000007';
#查看binlog事件
mysql> show master status;
--查看binlog信息
mysql> create database binlog;
--创建一个binlog库
mysql> use binlog
--使用binlog库
mysql> create table binlog_table(id int);
--创建binglog_table表
mysql> show master status;
--查看binlog信息
mysql> insert into binlog_table values(1);
--插入数据1
mysql> show master status;
--查看binlog信息
mysql> commit;
--提交
mysql> show master status;
--查看binlog信息
mysql> insert into binlog_table values(2);
--插入数据2
mysql> insert into binlog_table values(3);
--插入数据3
mysql> show master status;
--查看binlog信息
mysql> commit;
--提交
mysql> delete from binlog_table where id=1;
--删除数据1
mysql> show master status;
--查看binlog信息
mysql> commit;
--提交
mysql> update binlog_table set id=22 where id=2;
--更改数据2为22
mysql> show master status;
--查看binlog
mysql> commit;
--提交
mysql> show master status;
--查看binlog信息
mysql> select * from binlog_table;
--查看数据
mysql> drop table binlog_table;
--删表
mysql> drop database binlog;
--删库
mysql> show binlog events in 'mysql-bin.000013';
# 查看binlog事件
# 使用mysqlbinlog来查看
[root@db01 data]# mysqlbinlog /application/mysql/data/mysql-bin.000001
[root@db01 data]# mysqlbinlog /application/mysql/data/mysql-bin.000001|grep -v SET
[root@db01 data]# mysqlbinlog --base64-output=decode-rows -vvv mysql-bin.000013
# 查看二进制日志后,发现删除开始位置是858
# binlog某些内容是以二进制放进去的,加入base64-output用于解码
[root@db01 data]# mysqlbinlog --start-position=120 --stop-position=858 /application/mysql/data/mysql-bin.000013 > /tmp/binlog.sql
mysql> set sql_log_bin=0;
#临时关闭binlog,必须关闭,否则source倒入的内容也会被记录进binlog,binlog就脏了。
mysql> source /tmp/binlog.sql
#执行sql文件
mysql> show databases;
#查看删除的库
mysql> use binlog
#进binlog库
mysql> show tables;
#查看删除的表
mysql> select * from binlog_table;
#查看表中内容
mysql> flush logs;
--刷新一个新的binlog,原来的bin000X.log作废
mysql> create database db1;
mysql> create database db2;
--创建db1、db2两个库

mysql> use db1
--库db1操作
mysql> create table t1(id int);
--创建t1表
mysql> insert into t1 values(1),(2),(3),(4),(5);
--插入5条数据
mysql> commit;
--提交
mysql> use db2
--库db2操作
mysql> create table t2(id int);
--创建t2表
mysql> insert into t2 values(1),(2),(3);
--插入3条数据
mysql> commit;
--提交
mysql> show binlog events in 'mysql-bin.000014';
--查看binlog事件
[root@db01 data]# mysqlbinlog -d db1 --base64-output=decode-rows -vvv /application/mysql/data/mysql-bin.000014
--查看db1的操作
#临时生效
SET GLOBAL expire_logs_days = 7;
#永久生效
[root@db01 data]# vim /etc/my.cnf
[mysqld]
expire_logs_days = 7
* 使用purge命令删除
PURGE BINARY LOGS BEFORE now() - INTERVAL 3 day;
* 根据文件名删除
PURGE BINARY LOGS TO 'mysql-bin.000010';
* 用reset master
mysql> reset master;
14.5、慢查询日志
[root@db01 ~]# vim /etc/my.cnf
[mysqld]
#指定是否开启慢查询日志
slow_query_log = 1
#指定慢日志文件存放位置(默认在data)
slow_query_log_file=/application/mysql/data/slow.log
#设定慢查询的阀值(默认10s)
long_query_time=0.05
#不使用索引的慢查询日志是否记录到索引
log_queries_not_using_indexes
#查询检查返回少于该参数指定行的SQL不被记录到慢查询日志
min_examined_row_limit=100(鸡肋)
mysql> use world
--进入world库
mysql> show tables;
--查看表
mysql> create table t1 select * from city;
--将city表中所有内容加到t1表中
mysql> desc t1;
--查看t1的表结构
mysql> insert into t1 select * from t1;
mysql> insert into t1 select * from t1;
mysql> insert into t1 select * from t1;
mysql> insert into t1 select * from t1;
--将t1表所有内容插入到t1表中(多插入几次)
mysql> commit;
--提交
mysql> delete from t1 where id>2000;
--删除t1表中id>2000的数据
[root@db01 ~]# cat /application/mysql/data/mysql-db01
--查看慢日志
$PATH/mysqldumpslow -s c -t 10 /database/mysql/slow-log
#输出记录次数最多的10条SQL语句
$PATH/mysqldumpslow -s r -t 10 /database/mysql/slow-log
#得到返回记录集最多的10个查询
$PATH/mysqldumpslow -s t -t 10 -g "left join" /database/mysql/slow-log
#得到按照时间排序的前10条里面含有左连接的查询语句
yum install -y percona-toolkit-3.0.11-1.el6.x86_64.rpm
* 使用percona公司提供的pt-query-digest工具分析慢查询日志
[root@mysql-db01 ~]# pt-query-digest /application/mysql/data/mysql-db01-slow.log
pt-query-digest 的分析结果:

总体统计数据:
Queries examined: 总共分析的查询语句数量
Query time: 总查询时间
Min/Max/Avg query time: 最短、最长和平均查询时间
Variance: 查询时间的方差
Aggregated profile: 按查询类型统计的信息,如 SELECT、INSERT、UPDATE 等
Top Queries:
这部分列出了执行时间最长的前 10 个查询语句。
Rank: 查询在整体中的排名
Query ID: 查询的唯一标识
Response Time: 查询的总响应时间
Calls: 查询被执行的次数
R/Call: 每次调用的平均响应时间
V/M: 查询时间的方差与平均值的比率,反映了查询时间的离散程度
Item: 查询语句的文本
查询语句的详细信息:
Query ID: 查询的唯一标识
Database: 查询所针对的数据库
Users: 执行查询的用户
Hosts: 执行查询的主机
Query: 查询语句的文本
Exec time: 查询的总执行时间
Lock time: 查询获取的锁定时间
Rows sent: 查询返回的行数
Rows examined: 查询扫描的行数
Rows affected: 查询影响的行数
Bytes sent: 查询返回的字节数
Tmp tables: 查询使用的临时表数量
Tmp disk tables: 查询使用的临时磁盘表数量
Scan/Join tables: 查询扫描的表数量

有能力的可以做成可视化界面: Anemometer基于pt-query-digest将MySQL慢查询可视化

慢日志分析工具下载 https://www.percona.com/downloads/percona-toolkit/LATEST/

可视化代码下载 https://github.com/box/Anemometer

image-224

15、备份与恢复

15.1、备份的类型
15.2、备份的方式

SQL软件自带的功能

[root@localhost ~]# vim /etc/my.cnf
[mysqld]
secure_file_priv=/tmp
mysql> select * from world.city into outfile '/tmp/world_city.data';
    * mysqldump
    * replication

通过二进制方式直接拖走所有数据、配置文件

15.3、备份工具
[root@db01 ~]# mysqldump -uroot -p123456 -A > /backup/full.sql
    * 不加参数:单库、单表多表备份
[root@db01 ~]# mysqldump -uroot -p123456 db1 > /backup/db1.sql
[root@mysql-db01 backup]# mysqldump -uroot -p123456 world city > /backup/city.sql
    * -B:指定库备份
[root@db01 ~]# mysqldump -uroot -p123 -B db1 > /backup/db1.sql
[root@db01 ~]# mysqldump -uroot -p123 -B db1 db2 > /backup/db1_db2.sql
    * -F:flush logs在备份时自动刷新binlog(不怎么常用)
[root@db01 backup]# mysqldump -uroot -p123 -A -R –triggers -F > /backup/full_2.sql
    * -d:仅表结构
    * -t:仅数据
[root@db01 backup]# mysqldump -uroot -p123 -A -R --triggers > /backup/full_2.sql
[root@db01 backup]# mysqldump -uroot -p123 -A -R --triggers --master-data=2 –-single-transaction>/backup/full.sql
# 加了文件末尾会多一行:CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000003', MASTER_LOG_POS=120;
# 不加master-data就不会记录binlog的位置,导致后续不利于增量备份
[root@db01 ~]# mysqldump -uroot -p123 -A -R --triggers --master-data=2 –single-transaction|gzip>/backup/full.sql.gz
[root@db01 ~]# gzip -d /backup/full.sql.gz
[root@db01 ~]# zcat /backup/full.sql.gz > linshi.sql
mysqldump -uroot -p3307 -A -R --triggers --master-data=2 --single-transaction |gzip > /tmp/full_$(date +%F).sql.gz
mysql> set sql_log_bin=0;
--先不记录二进制日志
mysql> source /backup/full.sql
--库内恢复操作
[root@db01 ~]# mysql -uroot -p123 < /backup/full.sql
--库外恢复操作
15.4、【实战】企业故障恢复
mysql> flush logs;
--刷新binlog使内容更清晰
mysql> show master status;
--查看当前使用的binlog
mysql> create database backup;
--创建backup库
mysql> use backup
--进入backup库
mysql> create table full select * from world.city;
--创建full表
mysql> create table full_1 select * from world.city;
--创建full_1表
mysql> show tables;
--查看表
[root@db01 ~]# mysqldump -uroot -p123456 -A -R --triggers --master-data=2 --single-transaction|gzip > /backup/full_$(date +%F).sql.gz
mysql> use backup
--进入backup库
mysql> create table new select * from mysql.user;
--创建new表
mysql> create table new_1 select * from world.country;
--创建new_1表
mysql> show tables;
--查看表
mysql> select * from full;
--查看full表中所有数据
mysql> update full set countrycode='CHN' where 1=1;
--把full表中所有的countrycode都改成CHN
mysql> commit;
--提交
mysql> delete from full where id>200;
--删除id大于200的数据
mysql> commit;
--提交
mysql> drop table new;
--删除new表
mysql> show tables;
--查看表
#开启一个新的实例
[root@db02 ~]# mysqld_safe --defaults-file=/data/3307/my.cnf &
#拷贝数据到新库上
[root@db02 ~]# scp /backup/full_2018-08-16.sql.gz root@10.0.0.52:/tmp
#解压全备数据文件,如果用同一个机器的不同实例则不需要这步
[root@db02 ~]# cd /tmp/
#进入tmp目录
[root@db02 tmp]# gzip -d full_2018-08-16.sql.gz
#解压全备数据文件
截取二进制
[root@db02 tmp]# head -50 full_2018-08-16.sql |grep -i 'change master to'
#查看全备的位置点(起始位置点),忽略大小写,假设找到起始的位置为268002
mysql> show binlog events in 'mysql-bin.000017'\G
#生产环境db01找到drop语句执行的位置点(结束位置点)
[root@db01 tmp]#mysqlbinlog -uroot -p123 --start-position=268002 --stop-position=670891 /application/mysql/data/mysql-bin.000004 > /tmp/inc.sql
#截取二进制日志,把备份点和drop直接的信息倒入到inc.sql
[root@db01 tmp]# scp /tmp/inc.sql root@10.0.0.52:/tmp
#发送增量数据到新库

#在新库内恢复数据
mysql> set sql_log_bin=0;
#不记录二进制日志
mysql> source /tmp/full_2018-08-16.sql
#恢复全备数据
mysql> use backup
#进入backup库
mysql> show tables;
# 查看表,此时没有new表
mysql> source /tmp/inc.sql
#恢复增量数据
mysql> show tables;
#查看表,此时有new表
--将现在已经恢复好的数据库backup里的new表备份到/tmp/new.sql文件中
--此时/tmp/目录下有昨天23:00前完整备份文件full_2018-08-16.sql、昨天23:00到drop的增量文件inc.sql。
--通过这两个文件恢复了数据库出事前的所有内容
[root@db02 ~]# mysqldump -uroot -p123 -S /data/3307/mysql.sock backup new > /tmp/new.sql
--导出new表
[root@db02 ~]# scp /tmp/new.sql root@10.0.0.51:/tmp/
--发送到db01的库,此时db01的生产环境backup库里没有new表(被无良开发drop掉了);可直接从new.sql倒入
mysql> use backup
--进入backup库,此时生产环境的backup
mysql> source /tmp/new.sql
--在生产库恢复数据
mysql> show tables;
--查看表
15.5、物理备份(Xtrabackup)
yum -y install epel-release
#安装epel源
yum -y install perl perl-devel libaio libaio-devel perl-Time-HiRes perl-DBD-MySQL
#安装依赖
wget httpss://www.percona.com/downloads/XtraBackup/Percona-XtraBackup-2.4.4/binary/redhat/6/x86_64/percona-xtrabackup-24-2.4.4-1.el6.x86_64.rpm
#下载Xtrabackup
yum localinstall -y percona-xtrabackup-24-2.4.4-1.el6.x86_64.rpm
# 安装
[root@db01 data]# innobackupex --user=root --password=123 /backup
#全备
[root@db01 ~]# innobackupex --user=root --password=123 --no-timestamp /backup/full
#避免时间戳,自定义路径名
[root@db01 backup]# ll /backup/full
#查看备份路径中的内容
-rw-r-----  1 root root       21 Aug 16 06:23 xtrabackup_binlog_info 
#记录binlog文件名和binlog的位置点
-rw-r-----  1 root root      117 Aug 16 06:23 xtrabackup_checkpoints
#备份时刻,立即将已经commit过的内存中的数据页刷新到磁盘
#备份时刻有可能会有其他数据写入,已备走的数据文件就不会再发生变化了
#在备份过程中,备份软件会一直监控着redo和undo,一旦有变化会将日志一并备走
-rw-r-----  1 root root      485 Aug 16 06:23 xtrabackup_info
#备份汇总信息
-rw-r-----  1 root root     2560 Aug 16 06:23 xtrabackup_logfile
#备份的redo文件
* 准备备份
* 将redo进行重做,已提交的写到数据文件,未提交的使用undo回滚,模拟CSR的过程
[root@db01 full]# innobackupex --user=root --password=123 --apply-log /backup/full
[root@db01 full]# /etc/init.d/mysqld stop
#停库
[root@db01 full]# cd /application/mysql
#进入mysql目录
[root@db01 mysql]# rm -fr data/
#删除data目录(在生产中可以备份一下)
[root@db01 mysql]# innobackupex --copy-back /backup/full
#拷贝数据
[root@db01 mysql]# chown -R mysql.mysql /application/mysql/data/
#授权
[root@db01 mysql]# /etc/init.d/mysqld start
#启动MySQL
[root@mysql-db01 ~]#  innobackupex --user=root --password=123 --no-timestamp /backup/full
#不使用之前的全备,执行一次全备
mysql> create database inc1;
mysql> use inc1
mysql> create table inc1_tab(id int);
mysql> insert into inc1_tab values(1),(2),(3);
mysql> commit;
mysql> select * from inc1_tab;
[root@db01 ~]# innobackupex --user=root --password=123 --no-timestamp --incremental --incremental-basedir=/backup/full/ /backup/inc1
参数说明:
--incremental:开启增量备份功能
--incremental-basedir:上一次备份的路径
mysql> create database inc2;
mysql> use inc2
mysql> create table inc2_tab(id int);
mysql> insert into inc2_tab values(1),(2),(3);
mysql> commit;
[root@db01 ~]# innobackupex --user=root --password=123 --no-timestamp --incremental --incremental-basedir=/backup/inc1/ /backup/inc2
[root@db01 ~]# rm -fr /application/mysql/data/
#破坏数据
[root@db01 ~]# innobackupex --apply-log --redo-only /backup/full/
[root@db01 ~]# innobackupex --apply-log --redo-only --incremental-dir=/backup/inc1/ /backup/full/
[root@db01 ~]# innobackupex --apply-log --incremental-dir=/backup/inc2/ /backup/full/
[root@db01 mysql]# innobackupex --apply-log /backup/full/
copy-back
[root@db01 ~]# innobackupex --copy-back /backup/full/
[root@db01 ~]# chown -R mysql.mysql /application/mysql/data/
[root@db01 ~]# /etc/init.d/mysqld start

16、MySQL的主从复制

16.1、主从复制原理

image-225

16.2、【实战】MySQL主从复制

本实例中db01是master,db02是slave

主库操作

[root@db01 ~]# vim /etc/my.cnf
#编辑mysql配置文件
[mysqld]
#在mysqld标签下配置
server_id =1
#主库server-id为1,从库不等于1
log_bin=mysql-bin
#开启binlog日志
[root@db01 ~]# mysqldump -uroot -p123456 -A -R --triggers --master-data=2 --single-transaction > /tmp/full.sql
[root@db01 ~]# scp /tmp/full.sql root@192.168.88.140:/tmp/
[root@db01 ~]# mysql -uroot -p123456
#登录数据库
mysql> grant replication slave on *.* to slave@'192.168.88.%' identified by '123456';
#创建slave用户

从库操作(db02)

  1. 修改配置文件
[root@db02 ~]# vim /etc/my.cnf
#修改db02配置文件
[mysqld]
#在mysqld标签下配置
server_id =5
#主库server-id为1,从库不等于1
log_bin=mysql-bin
#开启binlog日志
[root@db02 ~]# systemctl restart mysqld
  1. 记录主库当前的binlog位置
[root@db01 ~]# mysql -uroot -p123456
mysql> show master status;
  1. 再从库上配置主库等信息
[root@db02 ~]# mysql -uroot -p123456
#登陆数据库
mysql> change master to
-> master_host='192.168.88.136',
-> master_user='slave',
-> master_password='123456',
-> master_log_file='mysql-bin.000001',
-> master_log_pos=120;
#head -50 /tmp/full.sql内的master_log_file与master_log_pos
-># master_auto_position=1;    # 与前两句冲突,表示自动适配master的file和position,MariaDB5.5不支持GTID特性。此时未开启GTID
# master_auto_position=1 表示开启 MySQL 的基于 GTID 的主从复制功能。
# GTID(Global Transaction Identifier)是一种基于事务的复制方式,相比传统的基于文件和位置的复制方式,GTID 复制更加灵活和可靠。
# 当 master_auto_position=1 时,slave 会自动找到主库上最新的 GTID 位置,而不需要手动指定日志文件和位置。这样可以避免主从同步时由于位置不匹配而导致的问题。

开启主从复制

--执行change master to 语句
mysql> start slave;
mysql> show slave status\G
--看到Slave_IO_Running: Yes    Slave_SQL_Running: Yes表示运行成功
--从数据库:/application/mysql/data目录下出现master.info文件,记录了同步的索引号
--测试方法:在主里面库建表插入内容,从里面可以看到主新增的内容表示同步成功。
16.3、主从复制基本故障处理
16.3.1、处理方法一
mysql> stop slave;
--临时停止同步
mysql> set global sql_slave_skip_counter=1;
--将同步指针向下移动一个(可重复操作),告诉这个执行不了的binlog条目不执行,去执行下一条。
mysql> start slave;
--开启同步
16.3.2、处理方法二
[root@db01 ~]# vim /etc/my.cnf
#编辑配置文件
slave-skip-errors=1032,1062,1007
#在[mysqld]标签下添加以下参数

但是以上操作都是有风险存在的

16.3.3、处理方法三
set global read_only=1;
--在命令行临时设置,在从库设置
read_only=1
--在配置文件中永久生效[mysqld]
16.4、延时从库
mysql>stop slave;
--停止主从
mysql>CHANGE MASTER TO MASTER_DELAY = 180;
--设置延时为180秒
mysql>start slave;
--开启主从
mysql> show slave status \G
SQL_Delay: 60
--查看状态

mysql> stop slave;
--停止主从
mysql> CHANGE MASTER TO MASTER_DELAY = 0;
--设置延时为0
mysql> start slave;
--开启主从
16.5、半同步复制

从MYSQL5.5开始,支持半自动复制。之前版本的MySQL Replication都是异步(asynchronous)的,主库在执行完一些事务后,是不会管备库的进度的。如果备库不幸落后,而更不幸的是主库此时又出现Crash(例如宕机),这时备库中的数据就是不完整的。简而言之,在主库发生故障的时候,我们无法使用备库来继续提供数据一致的服务了。

image-226

半同步复制(Semi synchronous Replication)则一定程度上保证提交的事务已经传给了至少一个备库。

IO在收到了读写后会发送ACK报告已经收到了。

image-227

出发点是保证主从数据一致性问题,安全的考虑。

[root@db01 ~]# mysql -uroot -p123456
#登录数据库
mysql> show global variables like 'have_dynamic_loading';
#查看是否有动态支持 have_dynamic_loading=YES
mysql> INSTALL PLUGIN rpl_semi_sync_master SONAME'semisync_master.so';
#安装自带插件
mysql> SET GLOBAL rpl_semi_sync_master_enabled = 1;
#启动插件
mysql> SET GLOBAL rpl_semi_sync_master_timeout = 1000;
#设置超时
[root@db01 ~]# vim /etc/my.cnf
#修改配置文件
[mysqld]
rpl_semi_sync_master_enabled=1
rpl_semi_sync_master_timeout=1000    #多长时间认为超时,单位ms
#在[mysqld]标签下添加如下内容(不用重启库)
mysql> show variables like'rpl%';
mysql> show global status like 'rpl_semi%';
#检查安装

从库安装:

[root@mysql-db02 ~]# mysql -uroot -p123456
#登录数据库
mysql>  INSTALL PLUGIN rpl_semi_sync_slave SONAME'semisync_slave.so';
#安装slave半同步插件
mysql> SET GLOBAL rpl_semi_sync_slave_enabled = 1;
#启动插件
mysql> stop slave io_thread;
mysql> start slave io_thread;
#重启io线程使其生效
[root@mysql-db02 ~]# vim /etc/my.cnf
#编辑配置文件(不需要重启数据库)
[mysqld]
rpl_semi_sync_slave_enabled =1
#在[mysqld]标签下添加如下内容

注:相关参数说明:

测试半同步:

mysql> create database test1;
Query OK, 1 row affected (0.04 sec)
mysql> create database test2;
Query OK, 1 row affected (0.00 sec)
--创建两个数据库,test1和test2
mysql> show global status like 'rpl_semi%';
--查看复制状态,Rpl_semi_sync_master_status状态是ON
+--------------------------------------------+-------+
| Variable_name                              | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_master_clients               | 1     |
| Rpl_semi_sync_master_net_avg_wait_time     | 768   |
| Rpl_semi_sync_master_net_wait_time         | 1497  |
| Rpl_semi_sync_master_net_waits             | 2     |
| Rpl_semi_sync_master_no_times              | 0     |
| Rpl_semi_sync_master_no_tx                 | 0     |
| Rpl_semi_sync_master_status                | ON    |
| Rpl_semi_sync_master_timefunc_failures     | 0     |
| Rpl_semi_sync_master_tx_avg_wait_time      | 884   |
| Rpl_semi_sync_master_tx_wait_time          | 1769  |
| Rpl_semi_sync_master_tx_waits              | 2     |
| Rpl_semi_sync_master_wait_pos_backtraverse | 0     |
| Rpl_semi_sync_master_wait_sessions         | 0     |
--此行显示2,表示刚才创建的两个库执行了半同步
| Rpl_semi_sync_master_yes_tx                | 2     | 
+--------------------------------------------+-------+
14 rows in set (0.06 sec)
mysql> show databases;
--从库查看
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| test               |
| test1              |
| test2              |
+--------------------+
mysql> SET GLOBAL rpl_semi_sync_master_enabled = 0;
--关闭半同步(1:开启 0:关闭)
mysql> show global status like 'rpl_semi%';
--查看半同步状态
+--------------------------------------------+-------+
| Variable_name                              | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_master_clients               | 1     |
| Rpl_semi_sync_master_net_avg_wait_time     | 768   |
| Rpl_semi_sync_master_net_wait_time         | 1497  |
| Rpl_semi_sync_master_net_waits             | 2     |
| Rpl_semi_sync_master_no_times              | 0     |
| Rpl_semi_sync_master_no_tx                 | 0     |
| Rpl_semi_sync_master_status                | OFF   | --状态为关闭
| Rpl_semi_sync_master_timefunc_failures     | 0     |
| Rpl_semi_sync_master_tx_avg_wait_time      | 884   |
| Rpl_semi_sync_master_tx_wait_time          | 1769  |
| Rpl_semi_sync_master_tx_waits              | 2     |
| Rpl_semi_sync_master_wait_pos_backtraverse | 0     |
| Rpl_semi_sync_master_wait_sessions         | 0     |
| Rpl_semi_sync_master_yes_tx                | 2     | 
+--------------------------------------------+-------+
14 rows in set (0.00 sec)

mysql> create database test3;
Query OK, 1 row affected (0.00 sec)
mysql> create database test4;
Query OK, 1 row affected (0.00 sec)
--再一次创建两个库
mysql> show global status like 'rpl_semi%';
--再一次查看半同步状态,此时Rpl_semi_sync_master_status变成OFF
+--------------------------------------------+-------+
| Variable_name                              | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_master_clients               | 1     |
| Rpl_semi_sync_master_net_avg_wait_time     | 768   |
| Rpl_semi_sync_master_net_wait_time         | 1497  |
| Rpl_semi_sync_master_net_waits             | 2     |
| Rpl_semi_sync_master_no_times              | 0     |
| Rpl_semi_sync_master_no_tx                 | 0     |
| Rpl_semi_sync_master_status                | OFF   |
| Rpl_semi_sync_master_timefunc_failures     | 0     |
| Rpl_semi_sync_master_tx_avg_wait_time      | 884   |
| Rpl_semi_sync_master_tx_wait_time          | 1769  |
| Rpl_semi_sync_master_tx_waits              | 2     |
| Rpl_semi_sync_master_wait_pos_backtraverse | 0     |
| Rpl_semi_sync_master_wait_sessions         | 0     |
--此行还是显示2,则证明,刚才的那两条并没有执行半同步否则应该是4
| Rpl_semi_sync_master_yes_tx                | 2     | 
+--------------------------------------------+-------+
14 rows in set (0.00 sec)
--注:不难发现,在查询半同步状态是,开启半同步,查询会有延迟时间,关闭之后则没有

image-228

16.6、过滤复制
[root@db01 data]# vim /data/3307/my.cnf 
replicate-do-db=world
#在[mysqld]标签下添加
mysqladmin -S /data/3307/mysql.sock  shutdown
#关闭MySQL
mysqld_safe --defaults-file=/data/3307/my.cnf &
#启动MySQL
#设置主从的server_id,如法炮制设置主从关系,记得change master to时候加上参数master_port = ‘3306’,
[root@db02 ~]# mysql -uroot -p123 -S /data/3308/mysql.sock 
mysql> use world
mysql> create table t1(id int);
--从库查看结果
[root@db02 ~]# mysql -uroot -p123 -S /data/3307/mysql.sock 
mysql> use world
mysql> show tables;
[root@db02 ~]# mysql -uroot -p123 -S /data/3308/mysql.sock 
mysql> use test
mysql> create table tb1(id int); 
--从库查看结果
[root@db02 ~]# mysql -uroot -p123 -S /data/3307/mysql.sock 
mysql> use test
mysql> show tables;

17、MHA高可用架构

MHA(Master High Availability)目前在MySQL高可用方面是一个相对成熟的解决方案,它由日本DeNA公司youshimaton(现就职于Facebook公司)开发,是一套优秀的作为MySQL高可用性环境下故障切换和主从提升的高可用软件。在MySQL故障切换过程中,MHA能做到在0~30秒之内自动完成数据库的故障切换操作,并且在进行故障切换的过程中,MHA能在最大程度上保证数据的一致性,以达到真正意义上的高可用。

image-229

MHA能够在较短的时间内实现自动故障检测和故障转移,通常在10-30秒以内;在复制框架中,MHA能够很好地解决复制过程中的数据一致性问题,由于不需要在现有的replication中添加额外的服务器,仅需要一个manager节点,而一个Manager能管理多套复制,所以能大大地节约服务器的数量;另外,安装简单,无性能损耗,以及不需要修改现有的复制部署也是它的优势之处。

MHA还提供在线主库切换的功能,能够安全地切换当前运行的主库到一个新的主库中(通过将从库提升为主库),大概0.5-2秒内即可完成。

MHA由两部分组成:MHA Manager(管理节点)和MHA Node(数据节点)。MHA Manager可以独立部署在一台独立的机器上管理多个Master-Slave集群,也可以部署在一台Slave上。当Master出现故障时,它可以自动将最新数据的Slave提升为新的Master,然后将所有其他的Slave重新指向新的Master。整个故障转移过程对应用程序是完全透明的。

17.1、工作流程
  1. 把宕机的master二进制日志保存下来。
  2. 找到binlog位置点最新的slave。
  3. 在binlog位置点最新的slave上用relay log(差异日志)修复其它slave。(因为relay log修复比bin log快,所以不用master的bin log,slave没有bin log)
  4. 将宕机的master上保存下来的二进制日志恢复到含有最新位置点的slave上。
  5. 将含有最新位置点binlog所在的slave提升为master。
  6. 将其它slave重新指向新提升的master,并开启主从复制。

image-230

17.2、MHA工具介绍
masterha_check_ssh 检查MHA的ssh-key
masterha_check_repl 检查主从复制情况
masterha_manger 启动MHA
masterha_check_status 检测MHA的运行状态
masterha_master_monitor 检测master是否宕机
masterha_master_switch 手动故障转移
masterha_conf_host 手动添加server信息
masterha_secondary_check 建立TCP连接从远程服务器
masterha_stop 停止MHA
save_binary_logs 保存宕机的master的binlog
apply_diff_relay_logs 识别relay log的差异
filter_mysqlbinlog 防止回滚事件
purge_relay_logs 清除中继日志
17.3、MHA实验环境
wget https://downloads.mysql.com/archives/get/p/23/file/mysql-5.6.40-linux-glibc2.12-x86_64.tar.gz
tar xzvf mysql-5.6.40-linux-glibc2.12-x86_64.tar.gz
mkdir /application
mv mysql-5.6.40-linux-glibc2.12-x86_64 /application/mysql-5.6.40
ln -s /application/mysql-5.6.40 /application/mysql
cd /application/mysql/support-files
cp my-default.cnf /etc/my.cnf
cp:是否覆盖"/etc/my.cnf" y
cp mysql.server /etc/init.d/mysqld
cd /application/mysql/scripts
useradd mysql -s /sbin/nologin -M
yum -y install autoconf
./mysql_install_db --user=mysql --basedir=/application/mysql --data=/application/mysql/data
vim /etc/profile.d/mysql.sh
export PATH="/application/mysql/bin:$PATH"
source /etc/profile
sed -i 's#/usr/local#/application#g' /etc/init.d/mysqld /application/mysql/bin/mysqld_safe
vim /usr/lib/systemd/system/mysqld.service
[Unit]
Description=MySQL Server
Documentation=man:mysqld(8)
Documentation=https://dev.mysql.com/doc/refman/en/using-systemd.html
After=network.target
After=syslog.target
[Install]
WantedBy=multi-user.target
[Service]
User=mysql
Group=mysql
ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf
LimitNOFILE = 5000
systemctl start mysqld
systemctl enable mysqld
mysqladmin -uroot password '123456'
mysql -uroot -p123456
17.4、基于GTID的主从复制

主库操作

[root@mysql-db01 ~]# vim /etc/my.cnf
#编辑mysql配置文件
[mysqld]
#在mysqld标签下配置
server_id =1
#主库server-id为1,从库不等于1
log_bin=mysql-bin
#开启binlog日志
#如果3台设备是在装了mysql后克隆的,mysql的UUID则相同,需要修改为不同值
[root@mysql-db01 ~]# vim /application/mysql/data/auto.cnf
server-uuid=8108d02e-be0a-11ec-8a15-000c2956d2e2
[root@mysql-db01 ~]# mysql -uroot -p123456
#登录数据库
mysql> grant replication slave on *.* to slave@'192.168.88.%' identified by '123456';
#创建slave用户

从库操作

[root@mysql-db02 ~]# vim /etc/my.cnf
#修改mysql-db02配置文件
[mysqld]
#在mysqld标签下配置
server_id =5
#主库server-id为1,从库必须大于1
log_bin=mysql-bin
#开启binlog日志
[root@mysql-db02 ~]# systemctl restart mysqld
#重启mysql
[root@mysql-db03 ~]# vim /etc/my.cnf
#修改mysql-db03配置文件
[mysqld]
#在mysqld标签下配置
server_id =10
#主库server-id为1,从库必须大于1
log_bin=mysql-bin
#开启binlog日志
[root@mysql-db03 ~]# systemctl restart mysqld
#重启mysql

注:在以往如果是基于binlog日志的主从复制,则必须要记住主库的master状态信息。

mysql> show master status;
+------------------+----------+
| File             | Position |
+------------------+----------+
| mysql-bin.000002 |      120 |
+------------------+----------+

主、从库都要开启GTID

mysql> show global variables like '%gtid%';
#没开启之前先看一下GTID的状态
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| enforce_gtid_consistency | OFF   |
| gtid_executed            |       |
| gtid_mode                | OFF   |
| gtid_owned               |       |
| gtid_purged              |       |
+--------------------------+-------+ 
[root@mysql-db01 ~]# vim /etc/my.cnf
#编辑mysql配置文件(主库从库都需要修改)
[mysqld]
#在[mysqld]标签下添加
gtid_mode=ON
log_slave_updates
#重要:开启slave的binlog同步
enforce_gtid_consistency
#开启GTID特性
[root@mysql-db01 ~]# /etc/init.d/mysqld restart
#重启数据库
mysql> show global variables like '%gtid%';
#检查GTID状态
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| enforce_gtid_consistency | ON    | #执行GTID一致
| gtid_executed            |       |
| gtid_mode                | ON    | #开启GTID模块
| gtid_owned               |       |
| gtid_purged              |       |
+--------------------------+-------+

注:主库从库都需要开启GTID否则在做主从复制的时候就会报错,因为slave可能变成master

[root@mysql-db02 ~]# mysql -uroot -123456
mysql> change master to
-> master_host='10.0.0.51',
-> master_user='rep',
-> master_password='123456',
-> master_auto_position=1;
--如果GTID没有开的话
ERROR 1777 (HY000): CHANGE MASTER TO MASTER_AUTO_POSITION = 1 can only be executed when @@GLOBAL.GTID_MODE = ON.

配置主从复制

[root@mysql-db02 ~]# mysql -uroot -p123456
#登录数据库
mysql> change master to
#配置复制主机信息
-> master_host='10.0.0.51',
#主库IP
-> master_user='rep',
#主库复制用户
-> master_password='123456',
#主库复制用户的密码
-> master_auto_position=1;
#GTID位置点
mysql> start slave;
#开启slave
mysql> show slave status\G
#查看slave状态
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 10.0.0.51
                  Master_User: rep
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000003
          Read_Master_Log_Pos: 403
               Relay_Log_File: mysql-db02-relay-bin.000002
                Relay_Log_Pos: 613
        Relay_Master_Log_File: mysql-bin.000003
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 403
              Relay_Log_Space: 822
              Until_Condition: None
[root@mysql-db02 ~]# mysql -uroot -p123456
#登录从库
mysql> set global relay_log_purge = 0;
#禁用自动删除relay log 功能
mysql> set global read_only=1;
#设置只读
[root@mysql-db02 ~]# vim /etc/my.cnf
#编辑配置文件
[mysqld]
#在mysqld标签下添加
relay_log_purge = 0
#禁用自动删除relay log 永久生效
17.5、部署MHA

环境准备(所有节点)

下载MHA工具包:

cd && wget https://download.s21i.faiusr.com/23126342/0/0/ABUIABBPGAAg3OHUiAYolpPt7AQ.zip?f=mysql-master-ha.zip&v=1628778716
[root@mysql-db01 ~]# yum install perl-DBD-MySQL -y
#安装依赖包
[root@mysql-db01 ~]# unzip mysql-master-ha.zip /home/user1/tools/
[root@mysql-db01 ~]# cd /home/user1/tools/
#进入安装包存放目录
[root@mysql-db01 tools]# ll
mha4mysql-manager-0.56-0.el6.noarch.rpm
mha4mysql-manager-0.56.tar.gz
mha4mysql-node-0.56-0.el6.noarch.rpm
mha4mysql-node-0.56.tar.gz
[root@mysql-db01 tools]# yum install -y  mha4mysql-node-0.56-0.el6.noarch.rpm
Preparing...                ########################################### [100%]
   1:mha4mysql-node         ########################################### [100%]
#安装node包,所有节点都要安装node包
[root@mysql-db01 tools]# mysql -uroot -p123456
#登录数据库,主库,从库会自动同步
mysql> grant all privileges on *.* to mha@'192.168.88.%' identified by 'mha';
#添加mha管理账号
mysql> select user,host from mysql.user;
#查看是否添加成功
mysql> select user,host from mysql.user;
#主库上创建,从库会自动复制(在从库上查看)
[root@mysql-db01 ~]# ln -s /application/mysql/bin/mysqlbinlog /usr/bin/mysqlbinlog
[root@mysql-db01 ~]# ln -s /application/mysql/bin/mysql /usr/bin/mysql
#如果不创建命令软连接,检测mha复制情况的时候会报错,写入环境变量
[root@mysql-db03 ~]# yum -y install epel-release
#使用epel源
[root@mysql-db03 ~]# yum install -y perl-Config-Tiny epel-release perl-Log-Dispatch perl-Parallel-ForkManager perl-Time-HiRes
#安装manager依赖包
[root@mysql-db03 tools]# yum install -y  mha4mysql-manager-0.56-0.el6.noarch.rpm 
Preparing...              ########################################### [100%]
1:mha4mysql-manager       ########################################### [100%]
#安装manager包
[root@mysql-db03 ~]# mkdir -p /etc/mha
#创建配置文件目录
[root@mysql-db03 ~]# mkdir -p /var/log/mha/app1
#创建日志目录
[root@mysql-db03 ~]# vim /etc/mha/app1.cnf
#编辑mha配置文件
[server default]
manager_log=/var/log/mha/app1/manager
manager_workdir=/var/log/mha/app1
master_binlog_dir=/application/mysql/data
user=mha
password=mha
ping_interval=2
repl_password=123456
repl_user=rep
ssh_user=root
[server1]
hostname=10.0.0.51
port=3306
[server2]
candidate_master=1
check_repl_delay=0
hostname=10.0.0.52
port=3306
[server3]
hostname=10.0.0.53
port=3306

配置文件详解

[server default]
manager_workdir=/var/log/masterha/app1
#设置manager的工作目录
manager_log=/var/log/masterha/app1/manager.log 
#设置manager的日志
master_binlog_dir=/data/mysql
#设置master 保存binlog的位置,以便MHA可以找到master的日志,我这里的也就是mysql的数据目录
master_ip_failover_script= /usr/local/bin/master_ip_failover
#设置自动failover时候的切换脚本
master_ip_online_change_script= /usr/local/bin/master_ip_online_change
#设置手动切换时候的切换脚本
password=123456
#设置mysql中root用户的密码,这个密码是前文中创建监控用户的那个密码
user=root
#设置监控用户root
ping_interval=1
#设置监控主库,发送ping包的时间间隔,尝试三次没有回应的时候自动进行failover
remote_workdir=/tmp
#设置远端mysql在发生切换时binlog的保存位置
repl_password=123456
#设置复制用户的密码
repl_user=rep
#设置复制环境中的复制用户名 
report_script=/usr/local/send_report
#设置发生切换后发送的报警的脚本
#一旦MHA到server02的监控之间出现问题,MHA Manager将会尝试从server03登录到server02
secondary_check_script= /usr/local/bin/masterha_secondary_check -s server03 -s server02 --user=root --master_host=server02 --master_ip=192.168.0.50 --master_port=3306
shutdown_script=""
#设置故障发生后关闭故障主机脚本(该脚本的主要作用是关闭主机防止发生脑裂,这里没有使用)
ssh_user=root 
#设置ssh的登录用户名
[server1]
hostname=10.0.0.51
port=3306
[server2]
hostname=10.0.0.52
port=3306
candidate_master=1
#设置为候选master,如果设置该参数以后,发生主从切换以后将会将此从库提升为主库,即使这个主库不是集群中事件最新的slave。
check_repl_delay=0
#默认情况下如果一个slave落后master 100M的relay logs的话,MHA将不会选择该slave作为一个新的master,因为对于这个slave的恢复需要花费很长时间,通过设置check_repl_delay=0,MHA触发切换在选择一个新的master的时候将会忽略复制延时,这个参数对于设置了candidate_master=1的主机非常有用,因为这个候选主在切换的过程中一定是新的master
[root@mysql-db01 ~]# ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa >/dev/null 2>&1
#创建秘钥对
[root@mysql-db01 ~]# ssh-copy-id -i /root/.ssh/id_dsa.pub root@192.168.88.136
[root@mysql-db01 ~]# ssh-copy-id -i /root/.ssh/id_dsa.pub root@192.168.88.140
[root@mysql-db01 ~]# ssh-copy-id -i /root/.ssh/id_dsa.pub root@192.168.88.142
#发送公钥,包括自己
[root@mysql-db03 ~]# masterha_check_ssh --conf=/etc/mha/app1.cnf
#测试ssh
#看到如下字样,则测试成功
Tue Mar  7 01:03:33 2017 - [info] All SSH connection tests passed successfully.
[root@mysql-db03 ~]# masterha_check_repl --conf=/etc/mha/app1.cnf
#测试复制
#看到如下字样,则测试成功
#若不在slave库上创建用户会失败,按理说应该slave会同步master的库但创建slave用户是创建主从关系前
#mysql> grant replication slave on *.* to rep@'10.0.0.%' identified by '123456';
MySQL Replication Health is OK.
[root@mysql-db03 ~]# nohup masterha_manager --conf=/etc/mha/app1.cnf --remove_dead_master_conf --ignore_last_failover < /dev/null > /var/log/mha/app1/manager.log 2>&1 &
#启动
[root@mysql-db03 ~]# masterha_manager --conf=/etc/mha/app1.cnf --remove_dead_master_conf --ignore_last_failover --shutdown
#关闭
[root@mysql-db03 ~]# masterha_check_status --conf=/etc/mha/app1.cnf
#查看mha是否运行正常,正常会显示master的IP
[root@mysql-db02 ~]# mysql -uroot -p123456
#登录数据库(db02)
mysql> show slave status\G
#检查复制情况
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 10.0.0.51
                  Master_User: rep
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000006
          Read_Master_Log_Pos: 191
               Relay_Log_File: mysql-db02-relay-bin.000002
                Relay_Log_Pos: 361
        Relay_Master_Log_File: mysql-bin.000006
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
[root@mysql-db03 ~]# mysql -uroot -p123456
#登录数据库(db03)
mysql> show slave status\G
#检查复制情况
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 10.0.0.51
                  Master_User: rep
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000006
          Read_Master_Log_Pos: 191
               Relay_Log_File: mysql-db03-relay-bin.000002
                Relay_Log_Pos: 361
        Relay_Master_Log_File: mysql-bin.000006
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes

[root@mysql-db01 ~]# /etc/init.d/mysqld stop
#停掉主库
Shutting down MySQL..... SUCCESS!
[root@mysql-db02 ~]# mysql -uroot -p123456
#登录数据库(db02)
mysql> show slave status\G
#查看slave状态
Empty set (0.00 sec)
#db02的slave已经为空
[root@mysql-db03 ~]# mysql -uroot -p123456
#登录数据库(db03)
mysql> show slave status\G
#查看slave状态
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 10.0.0.52
                  Master_User: rep
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000006
          Read_Master_Log_Pos: 191
               Relay_Log_File: mysql-db03-relay-bin.000002
                Relay_Log_Pos: 361
        Relay_Master_Log_File: mysql-bin.000006
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes

此时停掉主库后再开启db01,db01正常了只能通过手动方式以从库身份加入

[root@mysql-db01 ~]# mysql -uroot -123456
change master to
master_host='192.168.88.136',
master_user='slave',
master_password='123456',
master_auto_position=1;
-> start slave;
17.6、配置vIP漂移
[root@mysql-db03 ~]# vim /etc/mha/app1.cnf
#编辑配置文件
[server default]
#在[server default]标签下添加
master_ip_failover_script=/etc/mha/master_ip_failover



#使用MHA自带脚本,在下载的mha文件mysql-master-ha.zip解压后的文件里
#tar xzvf mha4mysql-manager-0.56.tar.gz
#cd mha4mysql-manager-0.56/sample/script
#master-ip-failover文件就是自带的



#编辑脚本,该文件从wget而来
[root@mysql-db03 ~]# vim /etc/mha/master_ip_failover
#根据配置文件中脚本路径编辑
my $vip = '10.0.0.55/24';
my $key = '0';
#网卡名要改对,可能是ens33
my $ssh_start_vip = "/sbin/ifconfig ens33:$key $vip";
my $ssh_stop_vip = "/sbin/ifconfig ens33:$key down"; 
#修改以下几行内容
[root@mysql-db03 ~]# chmod +x /etc/mha/master_ip_failover
#添加执行权限,否则mha无法启动
[root@mysql-db03 ~]# yum install net-tools
#安装IFconfig,每台设备都要安装否则脚本执行失败

* 手动绑定vIP,假设db01是master
[root@mysql-db01 ~]# ifconfig ens33:0 192.168.88.88/24
#绑定vip,第一次要在master上手工配置,后面不需要了
[root@mysql-db01 ~]# ip a |grep eth0
#查看vip
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
   inet 10.0.0.51/24 brd 10.0.0.255 scope global eth0
   inet 10.0.0.55/24 brd 10.0.0.255 scope global secondary eth0:0

*安装dos2unix,因为从wget获取的master_ip_failover在windows下编辑,换行符与linux不一样,需要转换
[root@mysql-db03 mha]# yum install dos2unix
[root@mysql-db03 mha]# dos2unix master_ip_failover

*重启mha

[root@mysql-db03 ~]#masterha_manager --conf=/etc/mha/app1.cnf --remove_dead_master_conf --ignore_last_failover --shutdown
#关闭
[root@mysql-db03 ~]# nohup masterha_manager --conf=/etc/mha/app1.cnf --remove_dead_master_conf --ignore_last_failover < /dev/null > /var/log/mha/app1/manager.log 2>&1 &
#启动
[root@mysql-db03 ~]# masterha_check_status --conf=/etc/mha/app1.cnf
#查看mha是否运行正常,正常会显示master的IP





* 测试ip漂移
#登录db02
[root@mysql-db02 ~]# mysql -uroot -p123456
#查看slave信息
mysql> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 10.0.0.51
                  Master_User: rep
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000007
          Read_Master_Log_Pos: 191
               Relay_Log_File: mysql-db02-relay-bin.000002
                Relay_Log_Pos: 361
        Relay_Master_Log_File: mysql-bin.000007
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
#停掉主库
[root@mysql-db01 ~]# /etc/init.d/mysqld stop
Shutting down MySQL..... SUCCESS!
#在db03上查看从库slave信息
mysql> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 10.0.0.52
                  Master_User: rep
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000006
          Read_Master_Log_Pos: 191
               Relay_Log_File: mysql-db03-relay-bin.000002
                Relay_Log_Pos: 361
        Relay_Master_Log_File: mysql-bin.000006
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
#在db01上查看vip信息
[root@mysql-db01 ~]# ip a |grep eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
inet 10.0.0.51/24 brd 10.0.0.255 scope global eth0
#在db02上查看vip信息
[root@mysql-db02 ~]# ip a |grep eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    inet 10.0.0.52/24 brd 10.0.0.255 scope global eth0
    inet 10.0.0.55/24 brd 10.0.0.255 scope global secondary eth0:0

三、DQL查询练习

1、查询练习

建表,插入数据 新建一个查询用的数据库:selectTest CREATE DATABASE selectTest; 选择该数据库: USE selectTest;

学生表: student 学号 姓名 性别 出生日期 所在班级

CREATE TABLE student(
    s_no VARCHAR(20) PRIMARY KEY COMMENT'学生学号',
    s_name VARCHAR(20) NOT NULL COMMENT'学生姓名 不能为空',
    s_sex VARCHAR(10) NOT NULL COMMENT'学生性别',
    s_birthday DATETIME COMMENT'学生生日',
    s_class VARCHAR(20) COMMENT'学生所在的班级'
)ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

教师表 teacher 教师编号 教师名字 教师性别 出生日期 职称 所在部门

CREATE TABLE teacher(
    t_no VARCHAR(20) PRIMARY KEY COMMENT'教师编号',
    t_name VARCHAR(20) NOT NULL COMMENT'教师姓名',
    t_sex VARCHAR(20) NOT NULL COMMENT'教师性别',
    t_birthday DATETIME COMMENT'教师生日',
    t_rof VARCHAR(20) NOT NULL COMMENT'教师职称',
    t_depart VARCHAR(20) NOT NULL COMMENT'教师所在的部门'
)ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

课程表: course 课程号 课程课程名称 教师编号

CREATE TABLE course(
    c_no VARCHAR(20) PRIMARY KEY COMMENT'课程号',
    c_name VARCHAR(20) NOT NULL COMMENT'课程名称',
    t_no VARCHAR(20) NOT NULL COMMENT'教师编号 外键关联teacher表',
    FOREIGN KEY(t_no) references teacher(t_no)
)ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

成绩表 srore 学号 课程号 成绩 注意:视频中原先只有一个主键s_no ,后来修改了

CREATE TABLE score (
    s_no VARCHAR(20) NOT NULL COMMENT'成绩表的编号 依赖学生学号',
        c_no VARCHAR(20)  NOT NULL COMMENT'课程号 依赖于课程表中的c_id',
    sc_degree decimal,
    foreign key(s_no) references student(s_no),
    foreign key(c_no) references course(c_no),
    PRIMARY KEY(s_no,c_no)
)ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

查看创建的表以及架构

SHOW TABLES;
+----------------------+
| Tables_in_selecttest |
+----------------------+
| course               |
| score                |
| student              |
| teacher              |
+----------------------+

查看student表结构 DESCRIBE student;

+------------+-------------+------+-----+---------+-------+
| Field      | Type        | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| s_no       | varchar(20) | NO   | PRI | NULL    |       |
| s_name     | varchar(20) | NO   |     | NULL    |       |
| s_sex      | varchar(10) | NO   |     | NULL    |       |
| s_birthday | datetime    | YES  |     | NULL    |       |
| s_class    | varchar(20) | YES  |     | NULL    |       |
+------------+-------------+------+-----+---------+-------+

查看teacher表结构

DESCRIBE teacher;
+------------+-------------+------+-----+---------+-------+
| Field      | Type        | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| t_no       | varchar(20) | NO   | PRI | NULL    |       |
| t_name     | varchar(20) | NO   |     | NULL    |       |
| t_sex      | varchar(20) | NO   |     | NULL    |       |
| t_birthday | datetime    | YES  |     | NULL    |       |
| t_rof      | varchar(20) | NO   |     | NULL    |       |
| t_depart   | varchar(20) | NO   |     | NULL    |       |
+------------+-------------+------+-----+---------+-------+

查看course表结构

DESCRIBE course;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| c_no   | varchar(20) | NO   | PRI | NULL    |       |
| c_name | varchar(20) | NO   |     | NULL    |       |
| t_no   | varchar(20) | NO   | MUL | NULL    |       |
+--------+-------------+------+-----+---------+-------+

查看score表结构

DESCRIBE score;
+-----------+---------------+------+-----+---------+-------+
| Field     | Type          | Null | Key | Default | Extra |
+-----------+---------------+------+-----+---------+-------+
| s_no      | varchar(20)   | NO   | PRI | NULL    |       |
| c_no      | varchar(20)   | NO   | MUL | NULL    |       |
| sc_degree | decimal(10,0) | YES  |     | NULL    |       |
+-----------+---------------+------+-----+---------+-------+

向表中添加数据

--学生表数据

INSERT INTO student VALUES('101','曾华','男','1977-09-01','95033');
INSERT INTO student VALUES('102','匡明','男','1975-10-02','95031');
INSERT INTO student VALUES('103','王丽','女','1976-01-23','95033');
INSERT INTO student VALUES('104','李军','男','1976-02-20','95033');
INSERT INTO student VALUES('105','王芳','女','1975-02-10','95031');
INSERT INTO student VALUES('106','陆军','男','1974-06-03','95031');
INSERT INTO student VALUES('107','王尼玛','男','1976-02-20','95033');
INSERT INTO student VALUES('108','张全蛋','男','1975-02-10','95031');
INSERT INTO student VALUES('109','赵铁柱','男','1974-06-03','95031');

--教师表数据
INSERT INTO teacher VALUES('804','李诚','男','1958-12-02','副教授','计算机系');
INSERT INTO teacher VALUES('856','张旭','男','1969-03-12','讲师','电子工程系');
INSERT INTO teacher VALUES('825','王萍','女','1972-05-05','助教','计算机系');
INSERT INTO teacher VALUES('831','刘冰','女','1977-08-14','助教','电子工程系');

--添加课程表
INSERT INTO course VALUES('3-105','计算机导论','825');
INSERT INTO course VALUES('3-245','操作系统','804');
INSERT INTO course VALUES('6-166','数字电路','856');
INSERT INTO course VALUES('9-888','高等数学','831');

--添加成绩表
INSERT INTO score VALUES('103','3-245','86');
INSERT INTO score VALUES('105','3-245','75');
INSERT INTO score VALUES('109','3-245','68');
INSERT INTO score VALUES('103','3-105','92');

INSERT INTO score VALUES('105','3-105','88');
INSERT INTO score VALUES('109','3-105','76');
INSERT INTO score VALUES('103','6-166','85');

INSERT INTO score VALUES('105','6-166','79');
INSERT INTO score VALUES('109','6-166','81');

几张表的数据展现

SELECT * FROM student;
+------+--------+-------+---------------------+---------+
| s_no | s_name | s_sex | s_birthday          | s_class |
+------+--------+-------+---------------------+---------+
| 101  | 曾华   | 男    | 1977-09-01 00:00:00 | 95033   |
| 102  | 匡明   | 男    | 1975-10-02 00:00:00 | 95031   |
| 103  | 王丽   | 女    | 1976-01-23 00:00:00 | 95033   |
| 104  | 李军   | 男    | 1976-02-20 00:00:00 | 95033   |
| 105  | 王芳   | 女    | 1975-02-10 00:00:00 | 95031   |
| 106  | 陆军   | 男    | 1974-06-03 00:00:00 | 95031   |
| 107  | 王尼玛 | 男    | 1976-02-20 00:00:00 | 95033   |
| 108  | 张全蛋 | 男    | 1975-02-10 00:00:00 | 95031   |
| 109  | 赵铁柱 | 男    | 1974-06-03 00:00:00 | 95031   |
+------+--------+-------+---------------------+---------+





teacher  SELECT * FROM teacher;
+------+--------+-------+---------------------+--------+------------+
| t_no | t_name | t_sex | t_birthday          | t_rof  | t_depart   |
+------+--------+-------+---------------------+--------+------------+
| 804  | 李诚   | 男    | 1958-12-02 00:00:00 | 副教授 | 计算机系   |
| 825  | 王萍   | 女    | 1972-05-05 00:00:00 | 助教   | 计算机机系 |
| 831  | 刘冰   | 女    | 1977-08-14 00:00:00 | 助教   | 电子工程系 |
| 856  | 张旭   | 男    | 1969-03-12 00:00:00 | 讲师   | 电子工程系 |
+------+--------+-------+---------------------+--------+------------+

score  SELECT * FROM score;
+------+-------+-----------+
| s_no | c_no  | sc_degree |
+------+-------+-----------+
| 103  | 3-105 |        92 |
| 103  | 3-245 |        86 |
| 103  | 6-166 |        85 |
| 105  | 3-105 |        88 |
| 105  | 3-245 |        75 |
| 105  | 6-166 |        79 |
| 109  | 3-105 |        76 |
| 109  | 3-245 |        68 |
| 109  | 6-166 |        81 |
+------+-------+-----------+

course    SELECT * FROM course;
+-------+------------+------+
| c_no  | c_name     | t_no |
+-------+------------+------+
| 3-105 | 计算机导论 | 825  |
| 3-245 | 操作系统   | 804  |
| 6-166 | 数字电路   | 856  |
| 9-888 | 高等数学   | 831  |
+-------+------------+------+
select * from student;

select s_name,s_sex,s_class from student;

1.筛选出老师属于哪些专业,不能重复
select distinct t_depart from teacher;
mysql> select distinct t_depart from teacher;
+-----------------+
| t_depart        |
+-----------------+
| 计算机系        |
| 电子工程系      |
+-----------------+

4.筛选出score表中60分到80分的学生
select * from score where sc_degree between 60 and 80;
或者
select * from score where sc_degree > 60 and sc_degree <80;

mysql> select * from score where sc_degree between 60 and 80;
+------+-------+-----------+
| s_no | c_no  | sc_degree |
+------+-------+-----------+
| 105  | 3-245 |        75 |
| 105  | 6-166 |        79 |
| 109  | 3-105 |        76 |
| 109  | 3-245 |        68 |
+------+-------+-----------+
4 rows in set (0.00 sec)

5.筛选得到85或86或87分的同学
select * from score where sc_degree = 85 or sc_degree = 86 or sc_degree = 87;
select * from score where sc_degree in(85,86,87);

mysql> select * from score where sc_degree = 85 or sc_degree = 86 or sc_degree = 87;
+------+-------+-----------+
| s_no | c_no  | sc_degree |
+------+-------+-----------+
| 103  | 3-245 |        86 |
| 103  | 6-166 |        85 |
+------+-------+-----------+
2 rows in set (0.00 sec)

6.找到学生,要求班级为95031号或者女生
select * from student where s_class = 95031 or s_sex = '女';
mysql> select * from student where s_class = 95031 or s_sex = '女';
+------+-----------+-------+---------------------+---------+
| s_no | s_name    | s_sex | s_birthday          | s_class |
+------+-----------+-------+---------------------+---------+
| 102  | 匡明      | 男    | 1975-10-02 00:00:00 | 95031   |
| 103  | 王丽      | 女    | 1976-01-23 00:00:00 | 95033   |
| 105  | 王芳      | 女    | 1975-02-10 00:00:00 | 95031   |
| 106  | 陆军      | 男    | 1974-06-03 00:00:00 | 95031   |
| 108  | 张全蛋    | 男    | 1975-02-10 00:00:00 | 95031   |
| 109  | 赵铁柱    | 男    | 1974-06-03 00:00:00 | 95031   |
+------+-----------+-------+---------------------+---------+
6 rows in set (0.00 sec)
1.1、升降序练习(order by,asc升序 desc)
7.以班级号码倒叙排列所有学生
select * from student order by s_class desc;
mysql> select * from student order by s_class desc;
+------+-----------+-------+---------------------+---------+
| s_no | s_name    | s_sex | s_birthday          | s_class |
+------+-----------+-------+---------------------+---------+
| 101  | 曾华      | 男    | 1977-09-01 00:00:00 | 95033   |
| 103  | 王丽      | 女    | 1976-01-23 00:00:00 | 95033   |
| 104  | 李军      | 男    | 1976-02-20 00:00:00 | 95033   |
| 107  | 王尼玛    | 男    | 1976-02-20 00:00:00 | 95033   |
| 102  | 匡明      | 男    | 1975-10-02 00:00:00 | 95031   |
| 105  | 王芳      | 女    | 1975-02-10 00:00:00 | 95031   |
| 106  | 陆军      | 男    | 1974-06-03 00:00:00 | 95031   |
| 108  | 张全蛋    | 男    | 1975-02-10 00:00:00 | 95031   |
| 109  | 赵铁柱    | 男    | 1974-06-03 00:00:00 | 95031   |
+------+-----------+-------+---------------------+---------+
9 rows in set (0.00 sec)

8.对于成绩,以升序排列班级号码,倒叙排列成绩
select * from score order by c_no asc,sc_degree desc;
mysql> select * from score order by c_no asc,sc_degree desc;
+------+-------+-----------+
| s_no | c_no  | sc_degree |
+------+-------+-----------+
| 103  | 3-105 |        92 |
| 105  | 3-105 |        88 |
| 109  | 3-105 |        76 |
| 103  | 3-245 |        86 |
| 105  | 3-245 |        75 |
| 109  | 3-245 |        68 |
| 103  | 6-166 |        85 |
| 109  | 6-166 |        81 |
| 105  | 6-166 |        79 |
+------+-------+-----------+
9 rows in set (0.00 sec)
1.2、统计数量(count)
9.统计班级号为95033的同学总共有多少
select count(*) from student where s_class='95033';
mysql> select count(*) from student where s_class='95033';
+----------+
| count(*) |
+----------+
|        4 |
+----------+
1 row in set (0.00 sec)
1.3、子查询

10.求最高分学生的学号和课程编号,max()

select s_no,c_no from score where sc_degree = (select max(sc_degree) from score);     

mysql> select s_no,c_no from score where sc_degree = (select max(sc_degree) from score);
+------+-------+
| s_no | c_no  |
+------+-------+
| 103  | 3-105 |
+------+-------+
1 row in set (0.00 sec)
1.4、切片(limit)
--展示成绩表中,倒叙排列成绩,只显示学号和班级号,最后切片,只显示第一个
select s_no,c_no from score where sc_degree order by sc_degree desc limit 0,1;
mysql> select s_no,c_no from score where sc_degree order by sc_degree desc limit 0,1;
+------+-------+
| s_no | c_no  |
+------+-------+
| 103  | 3-105 |
+------+-------+
1 row in set (0.00 sec)
1.5、计算平均成绩(avg,group by)
--11.查询每门课的平均成绩
select c_no,avg(sc_degree) from score group by c_no;
mysql> select c_no,avg(sc_degree) from score group by c_no;
+-------+----------------+
| c_no  | avg(sc_degree) |
+-------+----------------+
| 3-105 |        85.3333 |
| 3-245 |        76.3333 |
| 6-166 |        81.6667 |
+-------+----------------+
3 rows in set (0.00 sec)
1.6、分组条件及模糊查询(group by having,like)
--12.查询score表中至少有两名学生选修并以3开头的课程的平均分数

select c_no,avg(sc_degree),count(c_no) from score group by c_no 
having count(c_no) >2 and c_no like '3%';
mysql> select c_no,avg(sc_degree),count(c_no) from score group by c_no 
having count(c_no) >2 and c_no like '3%';
+-------+----------------+-------------+
| c_no  | avg(sc_degree) | count(c_no) |
+-------+----------------+-------------+
| 3-105 |        85.3333 |           3 |
| 3-245 |        76.3333 |           3 |
+-------+----------------+-------------+
2 rows in set (0.00 sec)
1.7、范围查询(between and)
--13.查询分数大于70,小于90的s_no列
select s_no,sc_degree from score where sc_degree >70 and sc_degree <90;
select s_no,sc_degree from score where sc_degree between 70 and 90;

mysql> select s_no,sc_degree from score where sc_degree between 70 and 90;
+------+-----------+
| s_no | sc_degree |
+------+-----------+
| 103  |        86 |
| 103  |        85 |
| 105  |        88 |
| 105  |        75 |
| 105  |        79 |
| 109  |        76 |
| 109  |        81 |
+------+-----------+
7 rows in set (0.00 sec)
1.8、多表查询(寻找相同条件)
14. 查询所有学生的s_name\c_no\sc_degree 
select s_name,c_no,sc_degree from student,score where student.s_no=score.s_no;
15.查询所有学生的s_no, c_name, sc_degree列
select s_no,c_name,sc_degree from score,course where score.c_no=course.c_no;
mysql> select s_no,c_name,sc_degree from score,course where score.c_no=course.c_no;
+------+------------+-----------+
| s_no | c_name     | sc_degree |
+------+------------+-----------+
| 103  | 计算机导论 |        92 |
| 103  | 操作系统   |        86 |
| 103  | 数字电路   |        85 |
| 105  | 计算机导论 |        88 |
| 105  | 操作系统   |        75 |
| 105  | 数字电路   |        79 |
| 109  | 计算机导论 |        76 |
| 109  | 操作系统   |        68 |
| 109  | 数字电路   |        81 |
+------+------------+-----------+
9 rows in set (0.03 sec)
1.9、子查询加分组求平均分(in )
--17. 查询班级是'95031'班学生每门课的平均分
select avg(sc_degree) from score 
where s_no in (select s_no from student where s_class='95031') group by c_no;
1.10、子查询深入
18. 查询选修"3-105"课程的成绩高于'109'分同学'3-105'成绩 的所有同学的记录
(在大家都在选修3-105的背景下 查询 所有 分数 比 学号为"109"还要高的学生信息)
select * from score where c_no='3-105' and sc_degree > 
(select sc_degree from score where c_no='3-105' and s_no='109'); 

mysql> select * from score where c_no='3-105' and sc_degree >
    -> (select sc_degree from score where c_no='3-105' and s_no='109');
+------+-------+-----------+
| s_no | c_no  | sc_degree |
+------+-------+-----------+
| 103  | 3-105 |        92 |
| 105  | 3-105 |        88 |
+------+-------+-----------+
2 rows in set (0.00 sec)
19.查询成绩高于学号为'109',课程号为'3-105'的成绩的所有记录
(不管哪门课,只要比那个分数高就行)
select * from score where sc_degree > 
(select sc_degree from score where c_no='3-105' and s_no='109');

mysql> select * from score where sc_degree > (select sc_degree from score where c_no='3-105' and s_no='109');
+------+-------+-----------+
| s_no | c_no  | sc_degree |
+------+-------+-----------+
| 103  | 3-105 |        92 |
| 103  | 3-245 |        86 |
| 103  | 6-166 |        85 |
| 105  | 3-105 |        88 |
| 105  | 6-166 |        79 |
| 109  | 6-166 |        81 |
+------+-------+-----------+
6 rows in set (0.00 sec)
1.11、年份函数(year)
20.查询所有学号为108.101的同学同年出生的所有学生的s_no,s_name和s_birthday
select s_no,s_name,s_birthday from student
where year(s_birthday) in (select year(s_birthday) from student where s_no in (101,108));

mysql> select s_no,s_name,s_birthday from student
    -> where year(s_birthday) in (select year(s_birthday) from student where s_no in (101,108));
+------+--------+---------------------+
| s_no | s_name | s_birthday          |
+------+--------+---------------------+
| 101  | 曾华   | 1977-09-01 00:00:00 |
| 102  | 匡明   | 1975-10-02 00:00:00 |
| 105  | 王芳   | 1975-02-10 00:00:00 |
| 108  | 张全蛋 | 1975-02-10 00:00:00 |
+------+--------+---------------------+
1.12、嵌套子查询
21. 查询张旭老师上的课的学生的成绩
select t_no from teacher where t_name = '张旭';
select c_no from class where t_no = (select t_no from teacher where t_name = '张旭');
select * from score where c_no =(select c_no from course where t_no = (select t_no from teacher where t_name = '张旭'));
1.13、in的使用
23.查询班级号为95033,95031的学生,并且倒叙排序
select * from student where s_class in (95033,95031) order by s_class;

mysql> select * from student where s_class in (95033,95031) order by s_class;
+------+--------+-------+---------------------+---------+
| s_no | s_name | s_sex | s_birthday          | s_class |
+------+--------+-------+---------------------+---------+
| 102  | 匡明   | 男    | 1975-10-02 00:00:00 | 95031   |
| 105  | 王芳   | 女    | 1975-02-10 00:00:00 | 95031   |
| 106  | 陆军   | 男    | 1974-06-03 00:00:00 | 95031   |
| 108  | 张全蛋 | 男    | 1975-02-10 00:00:00 | 95031   |
| 109  | 赵铁柱 | 男    | 1974-06-03 00:00:00 | 95031   |
| 101  | 曾华   | 男    | 1977-09-01 00:00:00 | 95033   |
| 103  | 王丽   | 女    | 1976-01-23 00:00:00 | 95033   |
| 104  | 李军   | 男    | 1976-02-20 00:00:00 | 95033   |
| 107  | 王尼玛 | 男    | 1976-02-20 00:00:00 | 95033   |
+------+--------+-------+---------------------+---------+
9 rows in set (0.00 sec)
24. 查询存在85分以上成绩的课程c_no
select c_no from score where sc_degree >85;

mysql> select c_no from score where sc_degree >85;
+-------+
| c_no  |
+-------+
| 3-105 |
| 3-105 |
| 3-105 |
| 3-245 |
| 3-105 |
| 3-105 |
+-------+
6 rows in set (0.00 sec)
1.14、综合使用
--25.查出所有'计算机系' 教师所教课程的成绩表
select t_no from teacher where t_depart='计算机系';
select c_no from course where t_no in (select t_no from teacher where t_depart='计算机系');
select * from score where c_no in(select c_no from course where t_no in (select t_no from teacher where t_depart='计算机系'));

mysql> select * from score where c_no in(select c_no from course where t_no in (select t_no from teacher where t_depart='计算机系'));
+------+-------+-----------+
| s_no | c_no  | sc_degree |
+------+-------+-----------+
| 103  | 3-245 |        86 |
| 105  | 3-245 |        75 |
| 109  | 3-245 |        68 |
| 101  | 3-105 |        90 |
| 102  | 3-105 |        91 |
| 103  | 3-105 |        92 |
| 104  | 3-105 |        89 |
| 105  | 3-105 |        88 |
| 109  | 3-105 |        76 |
+------+-------+-----------+
9 rows in set (0.00 sec)
1.15、求并集操作(union)
26.查询'计算机系'与'电子工程系' 不同职称的教师的t_name和rof
select * from teacher where t_depart='计算机系' 
and t_rof not in (select t_rof from teacher where t_depart='电子工程系')
union
select * from teacher where t_depart='电子工程系' 
and t_rof not in (select t_rof from teacher where t_depart='计算机系');

mysql> select * from teacher where t_depart='计算机系' and t_rof not in (select t_rof from teacher where t_depart='电子 工程系')
    -> union
    -> select * from teacher where t_depart='电子工程系' and t_rof not in (select t_rof from teacher where t_depart='计 算机系');
+------+--------+-------+---------------------+--------+------------+
| t_no | t_name | t_sex | t_birthday          | t_rof  | t_depart   |
+------+--------+-------+---------------------+--------+------------+
| 804  | 李诚   | 男    | 1958-12-02 00:00:00 | 副教授 | 计算机系   |
| 856  | 张旭   | 男    | 1969-03-12 00:00:00 | 讲师   | 电子工程系 |
+------+--------+-------+---------------------+--------+------------+
2 rows in set (0.00 sec)

严格正确版:
select t_name,t_rof from teacher where t_depart='计算机系' 
and t_rof not in (select t_rof from teacher where t_depart='电子工程系')
union
select t_name,t_rof from teacher where t_depart='电子工程系' 
and t_rof not in (select t_rof from teacher where t_depart='计算机系');

+--------+--------+
| t_name | t_rof  |
+--------+--------+
| 李诚   | 副教授 |
| 张旭   | 讲师   |
+--------+--------+
2 rows in set (0.00 sec)
1.16、多个值比较多个值:至少(any)
--27, 查询选修编号为"3-105"课程且成绩至少高于选修编号为'3-245'同学的c_no,s_no和sc_degree,并且按照sc_degree从高到地次序排序

select c_no,s_no,sc_degree from score where c_no='3-105' and sc_degree > any(select sc_degree from score where c_no='3-245') order by sc_degree desc;

+-------+------+-----------+
| c_no  | s_no | sc_degree |
+-------+------+-----------+
| 3-105 | 103  |        92 |
| 3-105 | 102  |        91 |
| 3-105 | 101  |        90 |
| 3-105 | 104  |        89 |
| 3-105 | 105  |        88 |
| 3-105 | 109  |        76 |
+-------+------+-----------+
6 rows in set (0.00 sec)
1.17、多个值比较多个值:所有(all)
--28.查询选修编号为"3-105"且成绩高于选修编号为"3-245"课程的同学c_no.s_no和sc_degree
select c_no,s_no,sc_degree from score where c_no='3-105' and sc_degree > all(select sc_degree from score where c_no='3-245');
+-------+------+-----------+
| c_no  | s_no | sc_degree |
+-------+------+-----------+
| 3-105 | 101  |        90 |
| 3-105 | 102  |        91 |
| 3-105 | 103  |        92 |
| 3-105 | 104  |        89 |
| 3-105 | 105  |        88 |
+-------+------+-----------+
5 rows in set (0.00 sec)
1.18、使用别名(as)
29.查询所有教师和同学的 name ,sex, birthday
select s_name as name,s_sex as sex,s_birthday as birthday from student 
union 
select t_name as name,t_sex as sex,t_birthday as birthday from teacher;

mysql> select s_name as name,s_sex as sex,s_birthday as birthday from student
    -> union
    -> select t_name as name,t_sex as sex,t_birthday as birthday from teacher;
+--------+-----+---------------------+
| name   | sex | birthday            |
+--------+-----+---------------------+
| 曾华   | 男  | 1977-09-01 00:00:00 |
| 匡明   | 男  | 1975-10-02 00:00:00 |
| 王丽   | 女  | 1976-01-23 00:00:00 |
| 李军   | 男  | 1976-02-20 00:00:00 |
| 王芳   | 女  | 1975-02-10 00:00:00 |
| 陆军   | 男  | 1974-06-03 00:00:00 |
| 王尼玛 | 男  | 1976-02-20 00:00:00 |
| 张全蛋 | 男  | 1975-02-10 00:00:00 |
| 赵铁柱 | 男  | 1974-06-03 00:00:00 |
| 李诚   | 男  | 1958-12-02 00:00:00 |
| 王萍   | 女  | 1972-05-05 00:00:00 |
| 刘冰   | 女  | 1977-08-14 00:00:00 |
| 张旭   | 男  | 1969-03-12 00:00:00 |
+--------+-----+---------------------+
30.查询所有'女'教师和'女'学生的name,sex,birthday
select s_name as name,s_sex as sex,s_birthday as birthday from student where s_sex='女'
union 
select t_name as name,t_sex as sex,t_birthday as birthday from teacher where t_sex='女';

+------+-----+---------------------+
| name | sex | birthday            |
+------+-----+---------------------+
| 王丽 | 女  | 1976-01-23 00:00:00 |
| 王芳 | 女  | 1975-02-10 00:00:00 |
| 王萍 | 女  | 1972-05-05 00:00:00 |
| 刘冰 | 女  | 1977-08-14 00:00:00 |
+------+-----+---------------------+
4 rows in set (0.00 sec)
1.19、复制表数据作条件查询(别名的使用)
31. 查询成绩比该课程平均成绩低的同学的成绩表

 select * from score a where sc_degree < (select avg(sc_degree) from score b where a.c_no=b.c_no);

 +------+-------+-----------+
| s_no | c_no  | sc_degree |
+------+-------+-----------+
| 105  | 3-245 |        75 |
| 105  | 6-166 |        79 |
| 109  | 3-105 |        76 |
| 109  | 3-245 |        68 |
| 109  | 6-166 |        81 |
+------+-------+-----------+
5 rows in set (0.01 sec)
32.查询所有任课教师的t_name 和 t_depart(要在分数表中可以查得到)
select t_name,t_depart from teacher where t_no in (select t_no from course);

+--------+------------+
| t_name | t_depart   |
+--------+------------+
| 李诚   | 计算机系   |
| 王萍   | 计算机系   |
| 刘冰   | 电子工程系 |
| 张旭   | 电子工程系 |
+--------+------------+
4 rows in set (0.02 sec)
1.20、条件加分组查询
--33.查出至少有2名男生的班号
select s_class from student group by s_class having count(s_sex='男')>1;
select s_class from student where s_sex='男' group by s_class having count(s_no)>1;

mysql> select s_class from student where s_sex='男' group by s_class having count(s_no)>1;
+---------+
| s_class |
+---------+
| 95033   |
| 95031   |
+---------+
2 rows in set (0.00 sec)
1.21、模糊查询取反(not like,%)
34. 查询student 表中不姓"王"的同学的记录
select * from student where s_name not like '王%';

+------+--------+-------+---------------------+---------+
| s_no | s_name | s_sex | s_birthday          | s_class |
+------+--------+-------+---------------------+---------+
| 101  | 曾华   | 男    | 1977-09-01 00:00:00 | 95033   |
| 102  | 匡明   | 男    | 1975-10-02 00:00:00 | 95031   |
| 104  | 李军   | 男    | 1976-02-20 00:00:00 | 95033   |
| 106  | 陆军   | 男    | 1974-06-03 00:00:00 | 95031   |
| 108  | 张全蛋 | 男    | 1975-02-10 00:00:00 | 95031   |
| 109  | 赵铁柱 | 男    | 1974-06-03 00:00:00 | 95031   |
+------+--------+-------+---------------------+---------+
6 rows in set (0.00 sec)
1.22、计算年龄大小(year(now()))
35. 查询student 中每个学生的姓名和年龄(当前时间 - 出生年份)

select s_name,year(now())-year(s_birthday) as age from student;
+--------+------+
| s_name | age  |
+--------+------+
| 曾华   |   43 |
| 匡明   |   45 |
| 王丽   |   44 |
| 李军   |   44 |
| 王芳   |   45 |
| 陆军   |   46 |
| 王尼玛 |   44 |
| 张全蛋 |   45 |
| 赵铁柱 |   46 |
+--------+------+
1.23、最大值最小值(min,max)
--36. 查询student中最大和最小的 s_birthday的值
select min(s_birthday),max(s_birthday) from student;
+---------------------+---------------------+
| min(s_birthday)     | max(s_birthday)     |
+---------------------+---------------------+
| 1974-06-03 00:00:00 | 1977-09-01 00:00:00 |
+---------------------+---------------------+
1 row in set (0.00 sec)
1.24、多字段排序
37. 以班级号和年龄从大到小的顺序查询student表中的全部记录
select * from student order by s_class desc and s_birthday;
+------+--------+-------+---------------------+---------+
| s_no | s_name | s_sex | s_birthday          | s_class |
+------+--------+-------+---------------------+---------+
| 101  | 曾华   | 男    | 1977-09-01 00:00:00 | 95033   |
| 102  | 匡明   | 男    | 1975-10-02 00:00:00 | 95031   |
| 103  | 王丽   | 女    | 1976-01-23 00:00:00 | 95033   |
| 104  | 李军   | 男    | 1976-02-20 00:00:00 | 95033   |
| 105  | 王芳   | 女    | 1975-02-10 00:00:00 | 95031   |
| 106  | 陆军   | 男    | 1974-06-03 00:00:00 | 95031   |
| 107  | 王尼玛 | 男    | 1976-02-20 00:00:00 | 95033   |
| 108  | 张全蛋 | 男    | 1975-02-10 00:00:00 | 95031   |
| 109  | 赵铁柱 | 男    | 1974-06-03 00:00:00 | 95031   |
+------+--------+-------+---------------------+---------+
9 rows in set (0.00 sec)
1.25、子查询练习
38. 查询"男"教师 及其所上的课
select t_no from teacher where t_sex='男';
select * from course where t_no in (select t_no from teacher where t_sex='男');

mysql> select * from course where t_no in (select t_no from teacher where t_sex='男');
+-------+----------+------+
| c_no  | c_name   | t_no |
+-------+----------+------+
| 3-245 | 操作系统 | 804  |
| 6-166 | 数字电路 | 856  |
+-------+----------+------+
2 rows in set (0.00 sec)
39.查询最高分同学的s_no c_no 和 sc_degree;
select max(sc_degree) from score;
select * from score where sc_degree =(select max(sc_degree) from score);
+------+-------+-----------+
| s_no | c_no  | sc_degree |
+------+-------+-----------+
| 103  | 3-105 |        92 |
+------+-------+-----------+
1 row in set (0.00 sec)
40. 查询和"李军"同性别的所有同学的s_name
select s_name from student where s_sex = (select s_sex from student where s_name='李军');

mysql> select s_name from student where s_sex = (select s_sex from student where s_name='李军');
+--------+
| s_name |
+--------+
| 曾华   |
| 匡明   |
| 李军   |
| 陆军   |
| 王尼玛 |
| 张全蛋 |
| 赵铁柱 |
+--------+
7 rows in set (0.00 sec)
41.查询和"李军"同性别并且同班的所有同学的s_name
SELECT s_name, s_sex FROM student WHERE s_sex = (SELECT s_sex FROM student WHERE s_name = '李军') AND s_class = (SELECT s_class FROM student WHERE s_name = '李军');
+--------+
| s_name |
+--------+
| 曾华   |
| 李军   |
| 王尼玛 |
+--------+
3 rows in set (0.00 sec)
42. 查询所有选修'计算机导论'课程的'男'同学的成绩表
select c_no from course where c_name='计算机导论';
select s_no from student where s_sex='男';
select * from score where s_no in (select s_no from student where s_sex='男') and 
c_no in (select c_no from course where c_name='计算机导论');

+------+-------+-----------+
| s_no | c_no  | sc_degree |
+------+-------+-----------+
| 101  | 3-105 |        90 |
| 102  | 3-105 |        91 |
| 104  | 3-105 |        89 |
| 109  | 3-105 |        76 |
+------+-------+-----------+
4 rows in set (0.00 sec)

2、连接查询

image-231

左外连接

左连接

全连接

两张表都没有出现交集的数据集

右连接

右外连接

内连接

2.1、连接查询案例
2.2、内连接(inner join …… on)
mysql> create table card(
    -> id int,
    -> name varchar(20)
    -> );

insert into card values(1,'饭卡');
insert into card values(2,'建行卡');
insert into card values(3,'农行卡');
insert into card values(4,'工商卡');
insert into card values(5,'邮政卡');
mysql> create table person(
    -> id int,
    -> name varchar(20),
    -> cardId int);

insert into person values(1,'张三',1);
insert into person values(2,'李四',3);
insert into person values(3,'王五',6);

person表:

mysql> select * from person;
+------+--------+--------+
| id   | name   | cardId |
+------+--------+--------+
|    1 | 张三   |      1 |
|    2 | 李四   |      3 |
|    3 | 王五   |      6 |
+------+--------+--------+
3 rows in set (0.00 sec)

card表:

mysql> select * from card;
+------+-----------+
| id   | name      |
+------+-----------+
|    1 | 饭卡      |
|    2 | 建行卡    |
|    3 | 农行卡    |
|    4 | 工商卡    |
|    5 | 邮政卡    |
+------+-----------+
5 rows in set (0.00 sec)
2.2.1、内连接查询
select * from person inner join card on person.cardId=card.id;
+------+------+--------+------+--------+
| id   | name | cardId | id   | name   |
+------+------+--------+------+--------+
|    1 | 张三 |      1 |    1 | 饭卡   |
|    2 | 李四 |      3 |    3 | 农行卡 |
+------+------+--------+------+--------+
2 rows in set (0.00 sec)

整个查询的执行过程如下:

  1. person 表中选取所有记录
  2. 对于每个 person 记录,查找 card 表中 id 字段与 person.cardId 相匹配的记录
  3. 将匹配的 personcard 记录组合成一条结果记录返回
2.3、外连接
2.3.1、左外连接(left join ...... on)

左外连接会把左边的表的数据全部取出来 ,右边表如果没有就用NULL补上

mysql> select * from person left join card on person.cardId=card.id;
+------+------+--------+------+--------+
| id   | name | cardId | id   | name   |
+------+------+--------+------+--------+
|    1 | 张三 |      1 |    1 | 饭卡   |
|    2 | 李四 |      3 |    3 | 农行卡 |
|    3 | 王五 |      6 | NULL | NULL   |
+------+------+--------+------+--------+
3 rows in set (0.00 sec)

整个查询的执行过程如下:

  1. person 表中选取所有记录
  2. 对于每个 person 记录,查找 card 表中 id 字段与 person.cardId 相匹配的记录
  3. 将匹配的 personcard 记录组合成一条结果记录返回
  4. 对于无法匹配的 person 记录,用 NULL 填充 card 表的字段
2.3.2、右外连接(right join ...... on)

右外连接会把左边的表的数据全部取出来,左边表如果没有就用NULL补上

mysql> select * from person right join card on person.cardId=card.id;
+------+------+--------+------+--------+
| id   | name | cardId | id   | name   |
+------+------+--------+------+--------+
|    1 | 张三 |      1 |    1 | 饭卡   |
| NULL | NULL |   NULL |    2 | 建行卡 |
|    2 | 李四 |      3 |    3 | 农行卡 |
| NULL | NULL |   NULL |    4 | 工商卡 |
| NULL | NULL |   NULL |    5 | 邮政卡 |
+------+------+--------+------+--------+
5 rows in set (0.00 sec)

整个查询的执行过程如下:

  1. card 表中选取所有记录
  2. 对于每个 card 记录,查找 person 表中 cardId 字段与 card.id 相匹配的记录
  3. 将匹配的 personcard 记录组合成一条结果记录返回
  4. 对于无法匹配的 card 记录,用 NULL 填充 person 表的字段
2.3.3、全连接(full join)
select * from person full join card on person.cardId=card.id;

mysql不支持全连接,换一种方式表达

select * from person left join card on person.cardId=card.id union select * from person right join card on person.cardId=card.id;
+------+------+--------+------+--------+
| id   | name | cardId | id   | name   |
+------+------+--------+------+--------+
|    1 | 张三 |      1 |    1 | 饭卡   |
|    2 | 李四 |      3 |    3 | 农行卡 |
|    3 | 王五 |      6 | NULL | NULL   |
| NULL | NULL |   NULL |    2 | 建行卡 |
| NULL | NULL |   NULL |    4 | 工商卡 |
| NULL | NULL |   NULL |    5 | 邮政卡 |
+------+------+--------+------+--------+
6 rows in set (0.00 sec)

整个查询的执行过程如下:

  1. person 表和 card 表中选取所有记录
  2. 对于每个 person 记录,查找 card 表中 id 字段与 person.cardId 相匹配的记录
  3. 对于每个 card 记录,查找 person 表中 cardId 字段与 card.id 相匹配的记录
  4. 将匹配的 personcard 记录组合成一条结果记录返回
  5. 对于无法匹配的记录,用 NULL 填充相应的字段

四、MongoDB

1、什么是NoSQL

NoSQL,指的是非关系型的数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称。

NoSQL用于超大规模数据的存储。(例如谷歌或Facebook每天为他们的用户收集万亿比特的数据)。这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。

1.1、为什么使用NoSQL

今天我们可以通过第三方平台(如:Google,Facebook等)可以很容易的访问和抓取数据。用户的个人信息,社交网络,地理位置,用户生成的数据和用户操作日志已经成倍的增加。我们如果要对这些用户数据进行挖掘,那SQL数据库已经不适合这些应用了, NoSQL 数据库的发展却能很好的处理这些大的数据。

1.2、NoSQL的优点/缺点
1.3、RDBMS vs NoSQL
1.4、常见的数据库管理系统

DB-Engines排行榜,每月更新一次

1.5、NoSQL数据库四大家族

2、MongDB简介

image-232

Mongo并非芒果(Mango)的意思,而是源于Humongous(巨大的,庞大的)一词

MongoDB是一个基于分布式文件存储的NoSQL数据库,由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。

MongoDB是一个介于关系型数据库和非关系型数据库之间的产品,是非关系型数据库当中功能最丰富,最像关系数据库的。

image-233

image-234

MongoDB使用了BSON(Binary JSON)对象来存储,与JSON格式的键值对(key/value)类似,字段值可以包含其他文档,数组及文档数组。支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系型数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

2.1、MongoDB 支持语言

image-235

2.2、MongoDB 与关系型数据库术语对比

image-236

SQL 术语概念 MongoDB 术语概念
database(数据库) database(数据库)
table(表) collection(集合)
row(行) document or BSON document(文档)
column(列) field(字段)
index(索引) index(索引)
table joins(表连接) 表连接,MongoDB不支持,但是可以文档嵌入文档
primary key(主键) primary key(主键)
2.3、数据类型
数据类型 描述 举例
字符串 utf8字符串都可以表示为字符串类型的数据 {"x":"foobar"}
对象id 对象id是文档的12字节的唯一ID {"X":Objectid()}
布尔值 真或者假:true或者false {"x":true}
数组 值的集合或者列表都可以表示成数组 {"x":["a","b","c"]}
整数 (Int32 Int64 你们就知道有个Int就行了,一般我们用Int32) {"age":18}
null 表示空值或者未定义的对象 {"x":null}
undefined 文档中也可以使用未定义类型 {"x":undefined}

3、部署MongoDB

3.1、下载二进制包

image-237

3.2、安装步骤
mkdir -p /usr/local/mongdb
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-4.4.6.tgz
tar xzvf mongodb-linux-x86_64-rhel70-4.4.6.tgz -C /usr/local/
cd /usr/local/
mv mongodb-linux-x86_64-rhel70-4.4.6/ mongodb
# 创建存放数据的目录
mkdir -p /usr/local/mongodb/data/db
# 创建存放日志的目录
mkdir -p /usr/local/mongodb/logs
# 创建日志记录文件
touch /usr/local/mongodb/logs/mongodb.log
3.3、启动MongoDB
cd /usr/local/mongodb/
bin/mongod \
--dbpath /usr/local/mongodb/data/db/ \
--logpath /usr/local/mongodb/logs/mongodb.log \
--logappend \
--port 27017 \
--bind_ip 0.0.0.0

--dbpath:指定数据文件存放目录 --logpath:指定日志文件,注意是指定文件不是目录 --logappend:使用追加的方式记录日志 --port:指定端口,默认为 27017 --bind_ip:绑定服务 IP,若绑定 127.0.0.1,则只能本机访问,默认为本机地址

# 后台启动
bin/mongod \
--dbpath /usr/local/mongodb/data/db/ \
--logpath /usr/local/mongodb/logs/mongodb.log \
--logappend \
--port 27017 \
--bind_ip 0.0.0.0 \
--fork

通过命令启动的方式并不适合管理,毕竟每次输入命令都需要考虑各参数的配置。我们可以通过配置文件来配置启动参数,然后通过指定配置文件的方式启动服务,这样在管理 MongoDB 上就比较方便了。

vim /usr/local/mongodb/bin/mongodb.conf 
# 数据文件存放目录
dbpath = /usr/local/mongodb/data/db
# 日志文件存放目录
logpath = /usr/local/mongodb/logs/mongodb.log
# 以追加的方式记录日志
logappend = true
# 端口默认为 27017
port = 27017
# 对访问 IP 地址不做限制,默认为本机地址
bind_ip = 0.0.0.0
# 以守护进程的方式启用,即在后台运行
fork = true
bin/mongod -f /usr/local/mongodb/bin/mongodb.conf
vim /usr/lib/systemd/system/mongodb.service
[Unit]
Description=mongodb
After=network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
ExecStart=/usr/local/mongodb/bin/mongod --config /usr/local/mongodb/bin/mongodb.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/usr/local/mongodb/bin/mongod --shutdown --config /usr/local/mongodb/bin/mongodb.conf
PrivateTmp=true

[Install]
WantedBy=multi-user.target

chmod 754 /usr/lib/systemd/system/mongodb.service
systemctl daemon-reload
systemctl restart mongodb
3.4、客户端配置
vim /etc/profile
export PATH=/usr/local/mongodb/bin/:$PATH

source /etc/profile
mongo
# 查看帮助
> help
        db.help()                    help on db methods
        db.mycoll.help()             help on collection methods
        sh.help()                    sharding helpers
        rs.help()                    replica set helpers
        help admin                   administrative help
        help connect                 connecting to a db help
        help keys                    key shortcuts
        help misc                    misc things to know
        help mr                      mapreduce
        show dbs                     show database names
        show collections             show collections in current database
        show users                   show users in current database
        show profile                 show most recent system.profile entries with time >= 1ms
        show logs                    show the accessible logger names
        show log [name]              prints out the last segment of log in memory, 'global' is default
        use <db_name>                set current database
        db.mycoll.find()             list objects in collection mycoll
        db.mycoll.find( { a : 1 } )  list objects in mycoll where a == 1
        it                           result of the last line evaluated; use to further iterate
        DBQuery.shellBatchSize = x   set default number of items to display on shell
        exit                         quit the mongo shell
# 查看版本信息
> db.version()
4.4.6
# 查看数据库
> show dbs;
admin   0.000GB
config  0.000GB
local   0.000GB
3.5、关闭MongoDB
3.5.1、前台启动
Ctrl+c
3.5.2、后台启动
# 命令启动方式的关闭
bin/mongod --dbpath /usr/local/mongodb/data/db/ --logpath /usr/local/mongodb/logs/mongodb.log --logappend --port 27017 --bind_ip 0.0.0.0 --fork --shutdown
# 配置文件启动方式的关闭
bin/mongod -f bin/mongodb.conf --shutdown
3.5.3、kill 命令关闭
# 查看 mongodb 运行的进程信息
ps -ef | grep mongodb
# kill -9 强制关闭
kill -9 pid
3.5.4、MongoDB 函数关闭
# 连接 mongodb
bin/mongo
# 切换 admin 数据库
use admin
# 执行以下函数(2选1)即可关闭服务
db.shutdownServer()
db.runCommand(“shutdown”)

4、数据库操作

4.1、案例需求

存档文章评论的数据库放到MongoDB中,数据结构参考如下

数据库:articledb

字段名称 字段含义 字段类型 备注
_id ID Objectid或string 主键
articleid 文章ID string
content 评论内容 string
userid 评论人id string
nickname 评论人昵称 string
createdatetime 评论的日期时间 date
likenum 点赞数 int
replynum 回复数 int
state 状态 string 0:不可见 1:可见
parentid 上级id string 如果为0表示为文章顶级评论
4.2、选择和创建数据库

选择和创建数据库的语法格式

use --数据库名称

如果数据库不存在则自动创建,例如,以下语句创建spitdb数据库

use articledb

查看有权限查看的所有数据库命令

show dbs
--或
show databases

注意:在MongoDB中,集合只有在内容插入后才会创建!就是说,创建集合(数据表)后要再插入一个文档(记录),集合才会真正创建

查看当前正在使用的数据库命令

db

MongoDB中默认的数据库为test,如果你没有选择数据库,集合将存放在test数据库中。 数据库名必须满足以下条件

  1. 不能是空字符串("")。
  2. 不得含有' '(空格)、.、$、/、\和\0 (空字符)。
  3. 应全部小写。
  4. 最多64字节。

有一些数据库名是保留的,可以直接访问这些有特殊作用的数据库

4.3、数据库的删除

MongoDB删除数据库的语法格式如下

db.dropDatabase()
--主要用来删除已经持久化的数据库
4.4、集合操作

集合,类似于关系型数据库中的表

可以显式的创建,也可以隐式的创建

db.createCollection(name)

参数说明

例如:创建一个名为mycollection的普通集合

db.createCollection("mycollection")

查看当前库中的表,show tables命令

show collections
--或
show tables

集合的命名规范:

  1. 集合名不能是空字符串""。
  2. 集合名不能含有\0字符(空字符),这个字符表示集合名的结尾。
  3. 集合名不能以"system."开头,这是为系统集合保留的前缀。
  4. 用户创建的集合名字不能含有保留字符。有些驱动程序的确支持在集合名里面包含,这是因为某些系统生成的集合中包含该字符。除 非你要访问这种系统创建的集合,否则千万不要在名字里出现$。
  5. 集合的隐式创建
  6. 当向一个集合中插入一个文档的时候,如果集合不存在,则会自动创建集合
  7. 通常我们使用隐式创建文档即可
4.5、集合的删除

集合删除的语法格式如下

db.collection.drop()
--或
db.--集合.drop()

返回值

例如:要删除mycollection集合

db.mycollection.drop()
4.6、文档基本CRUD

文档(document)的数据结构和JSON基本一样

所有存储在集合中的数据都是BSON格式

4.6.1、文档的插入

使用insert()或save()方法向集合中插入文档,语法如下

db.collection.insert(
    <document or array of documents>,
{
      writeConcern: <document>,
      ordered: <boolean>
    }
)
db.comment.insert(
  {
    "articleid":"100000",
    "content":"今天我们来学习mongodb",
    "userid":"1001",
    "nickname":"Aaron",
    "createdatetime":new Date(),
    "likenum":NumberInt(10),
    "state":null
  }
)
WriteResult({ "nInserted" : 1 })
db.collection.insertMany(
  [ <document 1>, <document2>, ... ],
  {
    writeConcern: <document>,
    ordered: <boolean>
  }
)
db.comment.insertMany([
  {
    "_id":"1",
    "articleid":"100001",
    "content":"苍茫的天涯是我的爱",
    "userid":"1002",
    "nickname":"钢铁侠",
    "createdatetime":new Date(),
    "likenum":NumberInt(1000),
    "state":"1"
  },
  {
    "_id":"2",
    "articleid":"100002",
    "content":"绵绵的青山脚下花正开",
    "userid":"1003",
    "nickname":"绿巨人",
    "createdatetime":new Date(),
    "likenum":NumberInt(1000),
    "state":"1"
  },
  {
    "_id":"3",
    "articleid":"100003",
    "content":"什么样的节奏是最呀最摇摆",
    "userid":"1004",
    "nickname":"美国队长",
    "createdatetime":new Date(),
    "likenum":NumberInt(1000),
    "state":"1"
  },
  {
    "_id":"4",
    "articleid":"100004",
    "content":"什么样的歌声才是最开怀",
    "userid":"1005",
    "nickname":"雷神",
    "createdatetime":new Date(),
    "likenum":NumberInt(1000),
    "state":"1"
  }
])
{ "acknowledged" : true, "insertedIds" : [ "1", "2", "3", "4" ] }
4.6.2、文档的基本查询
db.collection.find(<query>, [projection])
db.comment.find()
--查询所有
db.comment.find({})
--查询所有
db.comment.find().pretty()
--并且以JSON格式显示

这里你会发现每条文档会有一个叫_id的字段,这个相当于我们原来关系型数据库中表的主键,当你在插入文档的时候没有指定该字段,Mongodb就会自动创建,其类型是ObjectID类型 如果我们在插入文档记录时指定该字段也可以,器类型可以是ObjectID类型,也可以是MongoDB支持的任意类型。

db.comment.find({userid:'1003'}).pretty()
--查询指定字段
db.comment.findOne({'state':'1'})
--只显示查询到的第一个记录
db.comment.find({"userid":"1002"},{"likenum":1,nickname:1}).pretty()
--只显示likenum和nickname字段,1是显示
db.comment.find({"userid":"1002"},{"likenum":0}).pretty()
--显示的时候隐藏likenum字段,0是不显示
db.comment.find({},{"likenum":1,nickname:1}).pretty()
--查询所有字段,但是只显示likenum和nickname字段
try {
  db.comment.insertMany([
    {
      "_id":"1",
      "articleid":"100001",
      "content":"苍茫的天涯是我的爱",
      "userid":"1002",
      "nickname":"钢铁侠",
      "createdatetime":new Date(),
      "likenum":NumberInt(1000),
      "state":"1"
    },
    {
      "_id":"2",
      "articleid":"100002",
      "content":"绵绵的青山脚下花正开",
      "userid":"1003",
      "nickname":"绿巨人",
      "createdatetime":new Date(),
      "likenum":NumberInt(1000),
      "state":"1"
    },
    {
      "_id":"3",
      "articleid":"100003",
      "content":"什么样的节奏是最呀最摇摆",
      "userid":"1004",
      "nickname":"美国队长",
      "createdatetime":new Date(),
      "likenum":NumberInt(1000),
      "state":"1"
    },
    {
      "_id":"4",
      "articleid":"100004",
      "content":"什么样的歌声才是最开怀",
      "userid":"1005",
      "nickname":"雷神",
      "createdatetime":new Date(),
      "likenum":NumberInt(1000),
      "state":"1"
    }
  ])
}catch(e){
  print(e);
}
4.6.3、文档的更新
db.collection.update(query, update, options)
db.collection.update(
  <query>,
  <update>,
  {
    upsert: <boolean>,
    multi: <boolean>,
    writeConcern: <document>,
    collation: <document>,
    arrayFilters: [ <filterdocument1>, ... ],
    hint: <document|string>
  }
)
db.comment.update({_id:"1"},{likenum:NumberInt(1001)})
--修改_id为1的记录,点赞量为1001
db.comment.find({_id:"1"})
--查看这条记录,发现update是覆盖修改
db.comment.update({_id:"2"},{$set:{likenum:NumberInt(889)}})
--修改_id为2的技能,点赞量为889
db.comment.find({_id:"2"}).pretty()
--查看这条记录,发现修改成功
db.comment.update({likenum:NumberInt(1000)},{$set:{nickname:"灭霸"}})
--默认值修改第一条数据
db.comment.find().pretty()
db.comment.update({likenum:NumberInt(1000)},{$set:{nickname:"灭霸"}},{multi:true})
--修改所有符合条件的记录
db.comment.find().pretty()
db.comment.update({_id:"3"},{$inc:{likenum:NumberInt(1)}})
db.comment.update({_id:"3"},{$inc:{likenum:NumberInt(-1)}})
--把_id为3的点赞数加一
db.comment.find({_id:"3"}).pretty()
4.6.4、删除文档
db.--集合名称.remove(--条件)
db.comment.remove({})
--删除comment集合下的所有数据
db.comment.remove({_id:"1"})
--删除_id=1的记录
4.7、文档的分页查询
4.7.1、统计查询
db.collection.count(query,options)
db.comment.count()
--统计comment集合的所有的记录数
db.comment.count({userid:"1003"})
--统计userid为1003的记录条数
4.7.2、分页列表查询

可以使用limit()方法来读取指定数量的数据,使用skip()方法来跳过指定数量的数据

db.collection.find().limit(number).skip(number)
db.comment.find().limit(3).pretty()
--获取3条记录
db.comment.find().skip(3).pretty()
--从第4个记录开始获取
db.comment.find().skip(0).limit(2)
--第一页
db.comment.find().skip(2).limit(2)
--第二页
db.comment.find().skip(4).limit(2)
--第三页
4.7.3、排序查询

sort()方法对数据进行排序,sort()方法可以通过参数指定排序的字段,并使用1和-1来指定排序的方式,其中1为升序排序,而-1是用于降序排序。

db.collection.find().sort({key:1})
--或
db.collection.find().sort(--排序方式)

对userid降序排序,并对访问量进行升序排序

db.comment.find().sort({userid:-1,likenum:1})
4.8、文档的更多查询
4.8.1、正则的复杂条件查询

MongoDB的模糊查询是通过正则表达式的方式实现的,格式为:

db.collection.find({field:/正则表达式/})
--或
db.--集合.find({字段:/正则表达式/})
db.comment.find({content:/歌声/})
--查找内容里面包含歌声的
db.comment.find({content:/^什么/})
--查找以什么开头的
4.8.2、比较查询
db.comment.find({likenum:{$gt:NumberInt(700)}})
--查询点评数大于700的记录
4.8.3、包含查询

包含使用$in操作符

db.comment.find({userid:{$in:["1003","1004"]}})
--查询评论的集合中userid字段包含1003或1004的文档
db.comment.find({userid:{$nin:["1003","1004"]}})
--不包含
4.8.4、条件连接查询
db.comment.find({$and:[{likenum:{$gte:NumberInt(700)}},{likenum:{$lt:NumberInt(2000)}}]})
--查询评论集合中likenum大于等于700并且小于2000的文档
db.comment.find({$or:[{userid:"1003"},{likenum:{$lt:1000}}]})
--查询评论集合中userid为1003,或者点赞数小于1000的文档记录
4.9、常用命令小结
选择切换数据库:use articledb
插入数据:db.comment.insert({bson数据})
查询所有数据:db.comment.find();
条件查询数据:db.comment.find({条件})
查询符合条件的第一条记录:db.comment.findOne({条件})
查询符合条件的前几条记录:db.comment.find({条件}).limit(条数)
查询符合条件的跳过的记录:db.comment.find({条件}).skip(条数)
修改数据:db.comment.update({条件},{修改后的数据}) 或db.comment.update({条件},{$set:{要修改部分的字段:数据})
修改数据并自增某字段值:db.comment.update({条件},{$inc:{自增的字段:步进值}})
删除数据:db.comment.remove({条件})
统计查询:db.comment.count({条件})
模糊查询:db.comment.find({字段名:/正则表达式/})
条件比较运算:db.comment.find({字段名:{$gt:值}})
包含查询:db.comment.find({字段名:{$in:[值1,值2]}})或db.comment.find({字段名:{$nin:[值1,值2]}})
条件连接查询:db.comment.find({$and:[{条件1},{条件2}]})或db.comment.find({$or:[{条件1},{条件2}]})

5、索引

5.1、概述

索引支持在MongoDB中高效地执行查询。如果没有索引,MongoDB必须执行全集合扫描,即扫描集合中的每个文档,以选择与查询语句 匹配的文档。这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非 常致命的。

如果查询存在适当的索引,MongoDB可以使用该索引限制必须检查的文档数。 索引是特殊的数据结构,它以易于遍历的形式存储集合数据集的一小部分。索引存储特定字段或一组字段的值,按字段值排序。索引项的排 序支持有效的相等匹配和基于范围的查询操作。此外,MongoDB还可以使用索引中的排序返回排序结果

5.2、索引类型
5.2.1、单字段索引
5.2.2、复合索引
5.2.3、其他索引
5.3、索引的管理操作
5.3.1、索引的查看
db.collection.getIndexes()
db.comment.getIndexes()
[ { 
  "v" : 2, 
  "key" : { 
    "_id" : 1 
  }, 
  "name" : "_id_" 
} ]
"v" : 2
"key" : { "_id" : 1 }
"name" : "_id_"
5.3.2、索引的创建
db.collection.createIndex(keys, options)
选项 类型 描述
background 布尔 是否在后台执行创建索引的过程,不阻塞对集合的操作false【默认】
unique 布尔 是否创建具有唯一性的索引 false【默认】
name 字符串 自定义索引名称,如果不指定,mongodb将通过 下划线 连接 索引字段的名称和排序规则 生成一个索引名称。 一旦创建不能修改,只能删除再重新创建
partialFilterExpression Document 仅为集合中符合条件的文档建立索引,降低创建和维护成本
sparse 布尔 仅为集合中具有指定字段的文档建立索引 false 【默认】
expireAfterSeconds integer单位 秒 用于 TTL 索引中 控制 文档保存在集合中的时间
storageEngine Document 指定存储引擎配置
db.comment.createIndex({userid:1})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1
}

索引的键(字段)是 userid,排序方式是升序 (1).
createdCollectionAutomatically 字段为 false,表示集合 comment 已经存在。
numIndexesBefore 为 1,表示之前集合上已经有 1 个索引。
numIndexesAfter 为 2,表示创建索引后集合上总共有 2 个索引。

db.comment.getIndexes()
# 查看创建的索引,默认是升序
[
        {
                "v" : 2,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_"
        },
        {
                "v" : 2,
                "key" : {
                        "userid" : 1
                },
                "name" : "userid_1"
        }
]
db.comment.createIndex({userid:1,nickname:-1})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 2,
    "numIndexesAfter" : 3,
    "ok" : 1
}
db.comment.getIndexes()
# 查看创建的索引
[
    {
        "v" : 2,
        "key" : {
            "userid" : 1,
            "nickname" : -1
        },
        "name" : "userid_1_nickname_-1"
    }
]
5.3.3、索引的移除
db.collection.dropIndex(index)
db.comment.dropIndex({userid:1})
{ "nIndexesWas" : 3, "ok" : 1 }
db.comment.getIndexes()
--再次查看发现已经删除
db.comment.dropIndexes()
--删除所有索引
5.4、索引的使用
5.4.1、执行计划

分析查询性能通常使用执行计划(解释计划、Explain Plan)来查看查询的情况,如查询耗费的时间、是否基于索引查询等。

db.collection.find(query,options).explain(options)
db.comment.find({userid:"1003"}).explain()
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "articledb.comment",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "userid" : {
                "$eq" : "1003"
            }
        },
        "queryHash" : "37A12FC3",
        "planCacheKey" : "37A12FC3",
        "winningPlan" : {
            "stage" : "COLLSCAN",    # 注意这边,现在是全局扫描
            "filter" : {
                "userid" : {
                    "$eq" : "1003"
                }
            },
            "direction" : "forward"
        },
        "rejectedPlans" : [ ]
    },
    "serverInfo" : {
        "host" : "localhost.localdomain",
        "port" : 27017,
        "version" : "4.4.6",
        "gitVersion" : "72e66213c2c3eab37d9358d5e78ad7f5c1d0d0d7"
    },
    "ok" : 1
}
db.comment.createIndex({userid:1})
db.comment.find({userid:"1003"}).explain()
--注意stage从全表扫描变成抓取,性能提升了
5.4.2、涵盖的查询

当查询条件和查询的投影仅包含索引字段时,MongoDB直接从索引返回结果,而不扫描任何文档或将文档带入内存。这些覆盖的查询可以非常有效

db.comment.find({userid:"1003"},{userid:1,_id:0})
{ "userid" : "1003" }

image-238

6、副本集

6.1、简介

MongoDB中的副本集(Replica Set)是一组维护相同数据集的mongod服务。 副本集可提供冗余和高可用性,是所有生产部署的基础。

也可以说,副本集类似于有自动故障恢复功能的主从集群。通俗的讲就是用多台机器进行同一数据的异步同步,从而使多台机器拥有同一数据的多个副本,并且当主库宕掉时在不需要用户干预的情况下自动切换其他备份服务器做主库。而且还可以利用副本服务器做只读服务器,实现读写分离,提高负载。

image-239

6.2、副本集的三个角色

副本集有两种类型三种角色

image-240

6.3、副本集的创建
6.3.1、创建主节点
[root@localhost ~]# mkdir -p /usr/local/mongodb/replica_sets/myrs_27017/log
[root@localhost ~]# mkdir -p /usr/local/mongodb/replica_sets/myrs_27017/data/db
vim /usr/local/mongodb/replica_sets/myrs_27017/mongod.conf
systemLog:
  destination: file
  path: "/usr/local/mongodb/replica_sets/myrs_27017/log/mongod.log"
  logAppend: true
storage:
  dbPath: "/usr/local/mongodb/replica_sets/myrs_27017/data/db"
  journal:
    enabled: true
processManagement:
  fork: true
  pidFilePath: "/usr/local/mongodb/replica_sets/myrs_27017/log/mongod.pid"
net:
  bindIp: localhost,192.168.88.142
  port: 27017
replication:
  replSetName: myrs
[root@localhost ~]# /usr/local/mongodb/bin/mongod -f /usr/local/mongodb/replica_sets/myrs_27017/mongod.conf
6.3.2、创建副本节点
[root@localhost ~]# mkdir -p /usr/local/mongodb/replica_sets/myrs_27018/log
[root@localhost ~]# mkdir -p /usr/local/mongodb/replica_sets/myrs_27018/data/db
vim /usr/local/mongodb/replica_sets/myrs_27018/mongod.conf
systemLog:
    destination: file
    path: "/usr/local/mongodb/replica_sets/myrs_27018/log/mongod.log"
    logAppend: true
storage:
    dbPath: "/usr/local/mongodb/replica_sets/myrs_27018/data/db"
    journal:
        enabled: true
processManagement:
    fork: true
    pidFilePath: "/usr/local/mongodb/replica_sets/myrs_27018/log/mongod.pid"
net:
    bindIp: localhost,192.168.88.142
    port: 27018
replication:
    replSetName: myrs
[root@localhost ~]# /usr/local/mongodb/bin/mongod -f /usr/local/mongodb/replica_sets/myrs_27018/mongod.conf
6.3.3、创建仲裁节点
[root@localhost ~]# mkdir -p /usr/local/mongodb/replica_sets/myrs_27019/log
[root@localhost ~]# mkdir -p /usr/local/mongodb/replica_sets/myrs_27019/data/db
vim /usr/local/mongodb/replica_sets/myrs_27019/mongod.conf
systemLog:
    destination: file
    path: "/usr/local/mongodb/replica_sets/myrs_27019/log/mongod.log"
    logAppend: true
storage:
    dbPath: "/usr/local/mongodb/replica_sets/myrs_27019/data/db"
    journal:
        enabled: true
processManagement:
    fork: true
    pidFilePath: "/usr/local/mongodb/replica_sets/myrs_27019/log/mongod.pid"
net:
    bindIp: localhost,192.168.88.142
    port: 27019
replication:
    replSetName: myrs
[root@localhost ~]# /usr/local/mongodb/bin/mongod -f /usr/local/mongodb/replica_sets/myrs_27019/mongod.conf
6.3.4、检查端口号是否启动
[root@localhost ~]# ss -nlt
State      Recv-Q Send-Q Local Address:Port               Peer Address:Port   
LISTEN     0      128    192.168.88.142:27017                   *:*           
LISTEN     0      128    127.0.0.1:27017                   *:*                
LISTEN     0      128    192.168.88.142:27018                   *:*           
LISTEN     0      128    127.0.0.1:27018                   *:*                
LISTEN     0      128    192.168.88.142:27019                   *:*           
LISTEN     0      128    127.0.0.1:27019                   *:*                
LISTEN     0      128         *:22                      *:*
LISTEN     0      128        :::22                     :::*
6.3.5、初始化配置副本集和主节点
mongo --host=localhost --port=27017
> rs.initiate()
{
    "info2" : "no configuration specified. Using a default configuration for the set",
    "me" : "192.168.175.147:27017",
    "ok" : 1
}
myrs:SECONDARY> 
myrs:PRIMARY> 
--“ok”的值为1,说明创建成功。
--命令行提示符发生变化,变成了一个从节点角色,此时默认不能读写。稍等片刻,回车,变成主节点
myrs:PRIMARY> rs.conf()
{
    "_id" : "myrs",
    "version" : 1,
    "term" : 1,
    "protocolVersion" : NumberLong(1),
    "writeConcernMajorityJournalDefault" : true,
    "members" : [
        {
            "_id" : 0,
            "host" : "192.168.175.147:27017",
            "arbiterOnly" : false,
            "buildIndexes" : true,
            "hidden" : false,
            "priority" : 1,
            "tags" : {

            },
            "slaveDelay" : NumberLong(0),
            "votes" : 1
        }
    ],
    "settings" : {
        "chainingAllowed" : true,
        "heartbeatIntervalMillis" : 2000,
        "heartbeatTimeoutSecs" : 10,
        "electionTimeoutMillis" : 10000,
        "catchUpTimeoutMillis" : -1,
        "catchUpTakeoverDelayMillis" : 30000,
        "getLastErrorModes" : {

        },
        "getLastErrorDefaults" : {
            "w" : 1,
            "wtimeout" : 0
        },
        "replicaSetId" : ObjectId("60d49309bf8b31fd40a975d3")
    }
}
# "_id" : "myrs" :副本集的配置数据存储的主键值,默认就是副本集的名字
# "members" :副本集成员数组,此时只有一个: "host" : "192.168.175.147:27017" ,该成员不是仲裁节点: "arbiterOnly" : false ,优先级(权重值): "priority" : 1
# "settings" :副本集的参数配置。
rs.add("192.168.88.142:27018")
--添加副本节点
rs.addArb("192.168.88.142:27019")
--添加仲裁节点
rs.slaveOk()
6.4、副本集的数据读写操作
[root@localhost ~]# mongo --port 27017
use articledb
db.comment.insert(
  {
    "articleid":"100000",
    "content":"今天我们来学习mongodb",
    "userid":"1001",
    "nickname":"Aaron",
    "createdatetime":new Date(),
    "likenum":NumberInt(10),
    "state":null
  }
)
[root@localhost ~]# mongo --port 27018
use articledb
db.comment.insert(
  {
    "articleid":"100000",
    "content":"今天我们来学习mongodb",
    "userid":"1001",
    "nickname":"Aaron",
    "createdatetime":new Date(),
    "likenum":NumberInt(10),
    "state":null
  }
)
# 无法写入数据
# 这边可以看到报错信息"errmsg" : "not master",
db.comment.find()
# 无法读取数据
# 这边可以看到报错信息"errmsg" : "not master and slaveOk=false",
rs.slaveOk()
db.comment.find()
rs.slaveOk(false)
--取消从节点的数据读取权限
[root@localhost ~]# mongo --port 27019
rs.slaveOk()
show dbs
--这边看到报错,报错信息是"errmsg" : "node is not in primary or recovering state",
6.5、主节点的选举原则
6.6、集群故障分析
6.6.1、副本节点故障
6.6.2、主节点故障
6.6.3、仲裁节点故障
6.6.4、、仲裁节点和主节点故障
6.6.5、仲裁节点和从节点故障
6.6.6、主节点和从节点故障
6.7、master切换

手动停止master节点,在27018上查看master的切换情况

# 停止主节点
myrs:PRIMARY> use admin
switched to db admin
myrs:PRIMARY> db.shutdownServer()

# 在slave上查看
myrs:PRIMARY>  db.isMaster()
{
        "topologyVersion" : {
                "processId" : ObjectId("66a4b62e0ea17be2a1370457"),
                "counter" : NumberLong(7)
        },
        "hosts" : [
                "192.168.88.142:27017",
                "192.168.88.142:27018"
        ],
        "arbiters" : [
                "192.168.88.142:27019"
        ],
        "setName" : "myrs",
        "setVersion" : 3,
        "ismaster" : true,
        "secondary" : false,
        "primary" : "192.168.88.142:27018",
        "me" : "192.168.88.142:27018",
        "electionId" : ObjectId("7fffffff0000000000000002"),
        "lastWrite" : {
                "opTime" : {
                        "ts" : Timestamp(1722071608, 1),
                        "t" : NumberLong(2)
                },
                "lastWriteDate" : ISODate("2024-07-27T09:13:28Z"),
                "majorityOpTime" : {
                        "ts" : Timestamp(1722071420, 1),
                        "t" : NumberLong(1)
                },
                "majorityWriteDate" : ISODate("2024-07-27T09:10:20Z")
        },
        "maxBsonObjectSize" : 16777216,
        "maxMessageSizeBytes" : 48000000,
        "maxWriteBatchSize" : 100000,
        "localTime" : ISODate("2024-07-27T09:13:37.969Z"),
        "logicalSessionTimeoutMinutes" : 30,
        "connectionId" : 16,
        "minWireVersion" : 0,
        "maxWireVersion" : 9,
        "readOnly" : false,
        "ok" : 1,
        "$clusterTime" : {
                "clusterTime" : Timestamp(1722071608, 1),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        },
        "operationTime" : Timestamp(1722071608, 1)
}

7、分片集群

7.1、分片概念

分片(sharding)是一种跨多台机器分布数据的方法,MongoDB使用分片来支持具有非常大的数据集和高吞吐量操作的部署。 分片是将数据拆分,散到不同的机器上,不需要功能强大的大型计算机就可以储存更多的数据,处理更多的负载。有时也叫分区 (partitioning)。

具有大型数据集或高吞吐量应用程序的数据库系统可能会挑战单个服务器的容量。例如,高查询率会耗尽服务器的CPU容量。工作集大小大于系统的RAM会强调磁盘驱动器的I / O容量。

有两种解决系统增长的方法:垂直扩展和水平扩展。

垂直扩展是增加单个服务器的容量,例如使用更强大的CPU,添加更多RAM或增加存储空间量。可用技术的局限性可能会限制单个机器对于给定工作负载而言足够强大。此外,基于云的提供商基于可用的硬件配置具有硬性上限。垂直缩放有实际的最大值。

水平扩展是划分系统数据集并分散加载到多个服务器上,添加其他服务器以根据需要增加容量。虽然单个机器的总体速度或容量可能不高,但每台机器处理整个工作负载的子集,可能提供比单个高速大容量服务器更高的效率。扩展部署容量只需要根据需要添加额外的服务器,这可能比单个机器的高端硬件的总体成本更低。但是这样基础架构和部署维护的复杂性会增加。

7.2、分片集群包含的组件

image-241

7.3、分片集群架构目标

两个分片节点副本集(3+3)+ 一个配置节点副本集(3)+两个路由节点(2),共11个服务节点。

image-242

7.4、分片机制
7.4.1、数据如何切分

基于分片切分后的数据块称为 chunk,一个分片后的集合会包含多个 chunk,每个 chunk 位于哪个分片(Shard) 则记录在 Config Server(配置服务器)上。

Mongos 在操作分片集合时,会自动根据分片键找到对应的 chunk,并向该 chunk 所在的分片发起操作请求。

数据是根据分片策略来进行切分的,而分片策略则由 分片键(ShardKey)+分片算法(ShardStrategy)组成。

MongoDB 支持两种分片算法:哈希分片和范围分片

image-243

image-305

7.4.2、如何保证均衡

数据是分布在不同的 chunk上的,而 chunk 则会分配到不同的分片上,那么如何保证分片上的数据(chunk) 是均衡的呢?

7.5、分片(存储)节点副本集的创建
7.5.1、第一套副本集
mkdir -p /usr/local/mongodb/sharded_cluster/myshardrs01_27018/log \
/usr/local/mongodb/sharded_cluster/myshardrs01_27018/data/db \
/usr/local/mongodb/sharded_cluster/myshardrs01_27118/log \
/usr/local/mongodb/sharded_cluster/myshardrs01_27118/data/db \
/usr/local/mongodb/sharded_cluster/myshardrs01_27218/log \
/usr/local/mongodb/sharded_cluster/myshardrs01_27218/data/db
vim /usr/local/mongodb/sharded_cluster/myshardrs01_27018/mongod.conf
systemLog:
    destination: file
    path: "/usr/local/mongodb/sharded_cluster/myshardrs01_27018/log/mongod.log"
    logAppend: true
storage:
    dbPath: "/usr/local/mongodb/sharded_cluster/myshardrs01_27018/data/db"
    journal:
        enabled: true
processManagement:
    fork: true
    pidFilePath: "/usr/local/mongodb/sharded_cluster/myshardrs01_27018/log/mongod.pid"
net:
    bindIp: localhost,192.168.88.142
    port: 27018
replication:
    replSetName: myshardrs01
sharding:
  clusterRole: shardsvr
    # 分片角色,shardsvr为分片节点,configsvr配置节点
/usr/local/mongodb/bin/mongod -f /usr/local/mongodb/sharded_cluster/myshardrs01_27018/mongod.conf
/usr/local/mongodb/bin/mongod -f /usr/local/mongodb/sharded_cluster/myshardrs01_27118/mongod.conf
/usr/local/mongodb/bin/mongod -f /usr/local/mongodb/sharded_cluster/myshardrs01_27218/mongod.conf
ps -ef |grep mongod
[root@localhost ~]# mongo --port 27018
rs.initiate()
rs.status()
rs.add("192.168.88.142:27118")
rs.addArb("192.168.88.142:27218")
rs.conf()
7.5.2、第二套副本集
mkdir -p /usr/local/mongodb/sharded_cluster/myshardrs02_27318/log \
/usr/local/mongodb/sharded_cluster/myshardrs02_27318/data/db \
/usr/local/mongodb/sharded_cluster/myshardrs02_27418/log \
/usr/local/mongodb/sharded_cluster/myshardrs02_27418/data/db \
/usr/local/mongodb/sharded_cluster/myshardrs02_27518/log \
/usr/local/mongodb/sharded_cluster/myshardrs02_27518/data/db
vim /usr/local/mongodb/sharded_cluster/myshardrs02_27318/mongod.conf
systemLog:
    destination: file
    path: "/usr/local/mongodb/sharded_cluster/myshardrs02_27318/log/mongod.log"
    logAppend: true
storage:
    dbPath: "/usr/local/mongodb/sharded_cluster/myshardrs02_27318/data/db"
    journal:
        enabled: true
processManagement:
    fork: true
    pidFilePath: "/usr/local/mongodb/sharded_cluster/myshardrs02_27318/log/mongod.pid"
net:
    bindIp: localhost,192.168.88.142
    port: 27318
replication:
    replSetName: myshardrs02
sharding:
    clusterRole: shardsvr
    # 分片角色,shardsvr为分片节点,configsvr配置节点
/usr/local/mongodb/bin/mongod -f /usr/local/mongodb/sharded_cluster/myshardrs02_27318/mongod.conf
/usr/local/mongodb/bin/mongod -f /usr/local/mongodb/sharded_cluster/myshardrs02_27418/mongod.conf
/usr/local/mongodb/bin/mongod -f /usr/local/mongodb/sharded_cluster/myshardrs02_27518/mongod.conf
ps -ef |grep mongod
[root@localhost ~]# mongo --port 27318
rs.initiate()
rs.status()
rs.add("192.168.88.142:27418")
rs.addArb("192.168.88.142:27518")
rs.conf()
7.6、配置节点副本集的创建
mkdir -p /usr/local/mongodb/sharded_cluster/myconfigrs_27019/log \
/usr/local/mongodb/sharded_cluster/myconfigrs_27019/data/db \
/usr/local/mongodb/sharded_cluster/myconfigrs_27119/log \
/usr/local/mongodb/sharded_cluster/myconfigrs_27119/data/db \
/usr/local/mongodb/sharded_cluster/myconfigrs_27219/log \
/usr/local/mongodb/sharded_cluster/myconfigrs_27219/data/db
vim /usr/local/mongodb/sharded_cluster/myconfigrs_27019/mongod.conf
systemLog:
    destination: file
    path: "/usr/local/mongodb/sharded_cluster/myconfigrs_27019/log/mongod.log"
    logAppend: true
storage:
    dbPath: "/usr/local/mongodb/sharded_cluster/myconfigrs_27019/data/db"
    journal:
        enabled: true
processManagement:
    fork: true
    pidFilePath: "/usr/local/mongodb/sharded_cluster/myconfigrs_27019/log/mongod.pid"
net:
    bindIp: localhost,192.168.88.142
    port: 27019
replication:
    replSetName: myconfigr
sharding:
    clusterRole: configsvr
    # 分片角色,shardsvr为分片节点,configsvr配置节点
/usr/local/mongodb/bin/mongod -f /usr/local/mongodb/sharded_cluster/myconfigrs_27019/mongod.conf
/usr/local/mongodb/bin/mongod -f /usr/local/mongodb/sharded_cluster/myconfigrs_27119/mongod.conf
/usr/local/mongodb/bin/mongod -f /usr/local/mongodb/sharded_cluster/myconfigrs_27219/mongod.conf
ps -ef |grep mongod
[root@localhost ~]# mongo --port 27019
rs.initiate()
rs.status()
rs.add("192.168.88.142:27119")
rs.add("192.168.88.142:27219")
rs.conf()
7.7、路由节点的创建和操作
7.7.1、第一个路由节点的创建和连接
mkdir -p /usr/local/mongodb/sharded_cluster/mymongos_27017/log
vim /usr/local/mongodb/sharded_cluster/mymongos_27017/mongos.conf
systemLog:
    destination: file
    path: "/usr/local/mongodb/sharded_cluster/mymongos_27017/log/mongod.log"
    logAppend: true
processManagement:
    fork: true
    pidFilePath: "/usr/local/mongodb/sharded_cluster/mymongos_27017/log/mongod.pid"
net:
    bindIp: localhost,192.168.88.142
    port: 27017
sharding:
    configDB: myconfigrs/192.168.88.142:27019,192.168.88.142:27119,192.168.88.142:27219
/usr/local/mongodb/bin/mongos -f /usr/local/mongodb/sharded_cluster/mymongos_27017/mongos.conf
mongo --port 27017
mongos> use aabb
switched to db aabb
mongos> db.aa.insert({aa:"aa"})
WriteCommandError({
    "ok" : 0,
    "errmsg" : "unable to initialize targeter for write op for collection aabb.aa :: caused by :: Database aabb could not be created :: caused by :: No shards found",
    "code" : 70,
    "codeName" : "ShardNotFound",
    "operationTime" : Timestamp(1624668770, 3),
    "$clusterTime" : {
        "clusterTime" : Timestamp(1624668770, 3),
        "signature" : {
            "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
            "keyId" : NumberLong(0)
        }
    }
})
7.7.2、在路由节点上进行分片配置操作
mongos>sh.addShard("myshardrs01/192.168.88.142:27018,192.168.88.142:27118,192.168.88.142:27218")
mongos> sh.status()
mongos>sh.addShard("myshardrs02/192.168.88.142:27318,192.168.88.142:27418,192.168.88.142:27518")
mongos> sh.status()
use admin
db.runCommand({removeShard: "myshardrs02"})
mongos> db.runCommand({removeShard: "myshardrs02"})
{
    "msg" : "removeshard completed successfully",
    "state" : "completed",
    "shard" : "myshardrs02",
    "ok" : 1,
    "operationTime" : Timestamp(1624670397, 2),
    "$clusterTime" : {
        "clusterTime" : Timestamp(1624670397, 2),
        "signature" : {
            "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
            "keyId" : NumberLong(0)
        }
    }
}
mongos> sh.enableSharding("articledb")
sh.shardCollection(namespace, key, unique)

对集合进行分片时,需要选择一个片键(Shard Key) , shard key 是每条记录都必须包含的,且建立了索引的单个字段或复合字段,MongoDB按照片键将数据划分到不同的数据块中,并将数据块均衡地分布到所有分片中。为了按照片键划分数块,MongoDB使用基于哈希的分片方式(随机平均分配)或者基于范围的分片方式(数值大小分配)。用什么字段当片键都可以,如:nickname作为片键,但一定是必填字段。

sh.shardCollection("articledb.comment",{"nickname":"hashed"})
sh.status()
--查看分片状态

image-244

mongos> sh.shardCollection("articledb.author",{"age":1})
--如使用作者年龄字段作为片键,按照年龄的值进行分片

基于范围的分片方式提供了更高效的范围查询,给定一个片键的范围,分发路由可以很简单地确定哪个数据块存储了请求需要的数据,并将请求转发到相应的分片中。不过,基于范围的分片会导致数据在不同分片上的不均衡,有时候,带来的消极作用会大于查询性能的积极作用。比如,如果片键所在的字段是线性增长的,一定时间内的所有请求都会落到某个固定的数据块中,最终导致分布在同一个分片中。在这种情况下,一小部分分片承载了集群大部分的数据,系统并不能很好地进行扩展。

基于哈希的分片方式以范围查询性能的损失为代价,保证了集群中数据的均衡。哈希值的随机性使数据随机分布在每个数据块中,因此也随机分布在不同分片中.但是也正由于随机性,一个范围查询很难确定应该请求哪些分片,通常为了返回需要的结果需要请求所有分片。

如无特殊情况,一般推荐使用 Hash Sharding。

使用 _id 作为片键是一个不错的选择,因为它是必有的,可以使用数据文档 _id 的哈希作为片键。 这个方案能够是的读和写都能够平均分布,并且它能够保证每个文档都有不同的片键所以数据块能够很精细。理想化的 shard key 可以让 documents 均匀地在集群中分布。

7.7.3、分片后插入数据测试
mongos> use articledb
switched to db articledb
mongos> for(var i=1;i<=1000;i++){db.comment.insert({_id:i+"",nickname:"Test"+i})}
WriteResult({ "nInserted" : 1 })
mongos> db.comment.count()
1000
myshardrs01:PRIMARY> use articledb
switched to db articledb
myshardrs01:PRIMARY> db.comment.count()
505
myshardrs02:PRIMARY> use articledb
switched to db articledb
myshardrs02:PRIMARY> db.comment.count()
495
myshardrs02:PRIMARY> db.comment.find()
{ "_id" : "1", "nickname" : "Test1" }
{ "_id" : "3", "nickname" : "Test3" }
{ "_id" : "5", "nickname" : "Test5" }
{ "_id" : "6", "nickname" : "Test6" }
{ "_id" : "7", "nickname" : "Test7" }
{ "_id" : "10", "nickname" : "Test10" }
{ "_id" : "11", "nickname" : "Test11" }
{ "_id" : "12", "nickname" : "Test12" }
{ "_id" : "14", "nickname" : "Test14" }
{ "_id" : "17", "nickname" : "Test17" }
{ "_id" : "22", "nickname" : "Test22" }
{ "_id" : "23", "nickname" : "Test23" }
{ "_id" : "24", "nickname" : "Test24" }
{ "_id" : "28", "nickname" : "Test28" }
{ "_id" : "29", "nickname" : "Test29" }
{ "_id" : "30", "nickname" : "Test30" }
{ "_id" : "34", "nickname" : "Test34" }
{ "_id" : "37", "nickname" : "Test37" }
{ "_id" : "39", "nickname" : "Test39" }
{ "_id" : "44", "nickname" : "Test44" }
mongos> use articledb
switched to db articledb
mongos> for (var i=1;i<=20000;i++){db.author.save({"name":"test"+i,"age":NumberInt(i%120)})}
WriteResult({ "nInserted" : 1 })
mongos> db.author.count()
20000
myshardrs02:PRIMARY> db.author.count()
20000
use config
db.settings.save({_id:"chunksize",value:1})
# 改成1M
db.settings.save({_id:"chunksize",value:64})
7.7.4、再增加一个路由节点
mkdir -p /usr/local/mongodb/sharded_cluster/mymongos_27117/log
vim /usr/local/mongodb/sharded_cluster/mymongos_27117/mongos.conf
systemLog:
    destination: file
    path: "/usr/local/mongodb/sharded_cluster/mymongos_27117/log/mongod.log"
    logAppend: true
processManagement:
    fork: true
    pidFilePath: "/usr/local/mongodb/sharded_cluster/mymongos_27117/log/mongod.pid"
net:
    bindIp: localhost,192.168.88.142
    port: 27117
sharding:
    configDB: myconfigrs/192.168.88.142:27019,192.168.88.142:27119,192.168.88.142:27219
/usr/local/mongodb/bin/mongos -f /usr/local/mongodb/sharded_cluster/mymongos_27117/mongos.conf
mongo --port 27117
mongos> sh.status()

8、安全认证

8.1、MongoDB的用户和角色权限介绍

默认情况下,MongoDB实例启动运行时是没有启用用户访问权限控制的,在实例本机服务器上都可以随意连接到实例进行各种操作,MongoDB不会对连接客户端进行用户验证,这是非常危险的。

为了能保障mongodb的安全可以做以下几个步骤

为了强制开启用户访问控制(用户验证),则需要在MongoDB实例启动时使用选项 --auth 或在指定启动配置文件中添加选项 auth=true

在角色定义时可以包含一个或多个已存在的角色,新创建的角色会继承包含的角色所有的权限。在同一个数据库中,新创建角色可以继承其他角色的权限,在 admin 数据库中创建的角色可以继承在其它任意数据库中角色的权限。

> db.runCommand({ rolesInfo: 1 })
# 查询所有角色权限(仅用户自定义角色) 
> db.runCommand({ rolesInfo: 1, showBuiltinRoles: true })
# 查询所有角色权限(包含内置角色)
> db.runCommand({ rolesInfo: "<rolename>" })
# 查询当前数据库中的某角色的权限 
> db.runCommand({ rolesInfo: { role: "<rolename>", db: "<database>" } }
# 查询其它数据库中指定的角色权限 
> db.runCommand( { rolesInfo: [ "<rolename>", { role: "<rolename>", db: "<database>" }, ... ] } )
# 查询多个角色权限
角色 权限描述
read 可以读取指定数据库中任何数据。
readWrite 可以读写指定数据库中任何数据,包括创建、重命名、删除集合。
readAnyDatabase 可以读取所有数据库中任何数据(除了数据库config和local之外)。
readWriteAnyDatabase 可以读写所有数据库中任何数据(除了数据库config和local之外)。
userAdminAnyDatabase 可以在指定数据库创建和修改用户(除了数据库config和local之外)。
dbAdminAnyDatabase 可以读取任何数据库以及对数据库进行清理、修改、压缩、获取统计信息、执行检查等操作(除了数据库config和local之外)。
dbAdmin 可以读取指定数据库以及对数据库进行清理、修改、压缩、获取统计信息、执行检查等操作。
userAdmin 可以在指定数据库创建和修改用户。
clusterAdmin 可以对整个集群或数据库系统进行管理操作。
backup 备份MongoDB数据最小的权限。
restore 从备份文件中还原恢复MongoDB数据(除了system.profile集合)的权限。
root 超级账号,超级权限
8.2、单实例环境
8.2.1、关闭服务器
mongo --port 27017 
use admin 
db.shutdownServer()
ps -ef |grep mongod
kill -9 54410
# 如果数据损坏,则需要进行如下操作
rm -f xxx/data/db/*.lock
# ./bin/mongod --repair --dbpath=./data/db
# 修复数据
8.2.2、创建超级用户
mongo --port 27017
use admin
db.createUser({user:"myadmin",pwd:"123456",roles: [{role:"userAdminAnyDatabase",db:"admin"}]})
# 创建专门用来管理admin库的账号myadmin,只用来作为用户权限的管理 
db.system.users.find()
# 查看已经创建了的用户的情况
db.dropUser("myadmin")
# 删除用户
db.changeUserPassword("myadmin", "123456")
#修改密码 
db.auth("myadmin","123456")
# 认证密码
8.2.3、创建普通用户

创建普通用户可以在没有开启认证的时候添加,也可以在开启认证之后添加,但开启认证之后,必须使用有操作admin库的用户登录认证后才能操作。底层都是将用户信息保存在了admin数据库的集合system.users中。

use test
db.createUser({user: "test", pwd: "123456", roles: [{ role: "readWrite", db: "test" }]})
db.auth("test","123456")
8.2.4、服务端开启认证和客户端连接登录
use admin
db.shutdownServer()
./bin/mongod -f ./config/mongod.conf --auth
* 在mongod.conf中配置,在命令行中就不需要是使用auth参数了
security: 
    #开启授权认证 
    authorization: enabled
mongod -f ./mongod.conf
mongo --host 127.0.0.1 --port 27000 -u test -p 123456 --authenticationDatabase test
8.3、副本集环境

对于搭建好的mongodb副本集,为了安全,启动安全认证,使用账号密码登录。

image-245

对副本集执行访问控制需要配置两个方面

在keyfile身份验证中,副本集中的每个mongod实例都使用keyfile的内容作为共享密码,只有具有正确密钥文件的mongod或者mongos实例可以连接到副本集。密钥文件的内容必须在6到1024个字符之间,并且在unix/linux系统中文件所有者必须有对文件至少有读的权限。

8.3.1、通过主节点添加一个管理员帐号

只需要在主节点上添加用户,副本集会自动同步

mongo --port 27017
use admin
db.createUser({user:"myroot",pwd:"123456",roles:["root"]})
db.auth("myroot","123456")
use articledb
db.createUser({user: "user01", pwd: "123456", roles: ["readWrite"]})
8.3.2、创建副本集认证的key文件
openssl rand -base64 90 -out /usr/local/mongodb/replica_sets/myrs_27017/mongo.keyfile
chmod 400 ./mongo.keyfile
cp mongo.keyfile /usr/local/mongodb/replica_sets/myrs_27017/ \
/usr/local/mongodb/replica_sets/myrs_27018/ \
/usr/local/mongodb/replica_sets/myrs_27019/
8.3.3、修改配置文件指定keyfile

分别编辑几个服务的mongod.conf文件,添加相关内容

security:
 #KeyFile鉴权文件
 keyFile: /usr/local/mongodb/replica_sets/myrs_27017/mongo.keyfile
 #开启认证方式运行
 authorization: enabled
security:
 #KeyFile鉴权文件
 keyFile: /usr/local/mongodb/replica_sets/myrs_27018/mongo.keyfile
 #开启认证方式运行
 authorization: enabled
security:
 #KeyFile鉴权文件
 keyFile: /usr/local/mongodb/replica_sets/myrs_27019/mongo.keyfile
 #开启认证方式运行
 authorization: enabled
8.3.4、重新启动副本集
/usr/local/mongodb/bin/mongod -f /usr/local/mongodb/replica_sets/myrs_27017/mongod.conf
/usr/local/mongodb/bin/mongod -f /usr/local/mongodb/replica_sets/myrs_27018/mongod.conf
/usr/local/mongodb/bin/mongod -f /usr/local/mongodb/replica_sets/myrs_27019/mongod.conf
8.3.5、检查认证
mongo --port 27017
use admin
db.system.users.find()
db.auth("myroot","123456")
db.system.users.find()

五、Redis

1、缓存的概念

缓存是为了调节速度不一致的两个或多个不同的物质的速度,在中间对速度较快的一方起到一个加速访问速度较慢的一方的作用

比如 CPU 的一级、二级缓存是保存了 CPU 最近经常访问的数据,内存是保存 CPU 经常访问硬盘的数据,而且硬盘也有大小不一的缓存,甚至是物理服务器的 raid 卡有也缓存

为了起到加速 CPU 访问硬盘数据的目的,因为 CPU 的速度太快了, CPU 需要的数据硬盘往往不能在短时间内满足 CPU 的需求,

因此 PCU 缓存、内存、 Raid 卡以及硬盘缓存就在一定程度上满足了 CPU 的数据需求,即 CPU 从缓存读取数据可以大幅提高 CPU 的工作效率。

image-246

1.2、系统缓存
1.2.1、buffer 与 cache

buffer:缓冲也叫写缓冲,一般用于写操作,可以将数据先写入内存再写入磁盘,buffer 一般用于写缓冲,用于解决不同介质的速度不一致的缓冲,先将数据临时写入到里自己最近的地方,以提高写入速度,CPU 会把数据先写到内存的磁盘缓冲区,然后就认为数据已经写入完成看,然后由内核在后续的时间再写入磁盘,所以服务器突然断电会丢失内存中的部分数据。

cache:缓存也叫读缓存,一般用于读操作,CPU 读文件从内存读,如果内存没有就先从硬盘读到内存再读到 CPU,将需要频繁读取的数据放在里自己最近的缓存区域,下次读取的时候即可快速读取。

1.2.2、cache 的保存位置
1.2.3、cache 的特性
1.3、用户层缓存
1.3.1、DNS 缓存
1.4、浏览器缓存过期机制
1.4.1、最后修改时间

系统调用会获取文件的最后修改时间,如果没有发生变化就返回给浏览器304 的状态码,表示没有发生变化,然后浏览器就使用的本地的缓存展示资源。

1.4.2、Etag标记

基于Etag标记是否一直做判断页面是否发生过变化,比如基于Nginx的Etag on来实现

1.4.3、过期时间 expires

以上两种都需要发送请求,即不管资源是否过期都要发送请求进行协商,这样会消耗不必要的时间,因此有了缓存的过期时间

Expire 是 HttpHeader 中代表资源的过期时间,由服务器端设置。如果带有 Expire ,则在 Expire 过期前不会发生 Http 请求,直接从缓存中读取。用户强制 F5 例外

第一次请求资源时,响应报文带有资源的过期时间,默认为30天,当前此方式使用的比较多,但是无法保证客户的时间都是准确并且一致的,因此会加入一个最大生存周期,使用用户本地的时间计算缓存数据是否超过多少天,假如过期时间Expires:为2031年,但是缓存的最大生存周期Cache-Control: max-age=315360000,计算为天等于3650天即10年

1.4.4、混合使用和缓存刷新

通常 Last-Modified,Etag,Expire 是一起混合使用的

缓存刷新

1.4.5、cookie 和 session

Cookie是访问某些网站以后在本地存储的一些网站相关的信息,下次再访问的时候减少一些步骤,比如加密后的账户名密码等信息

Cookies是服务器在客户端浏览器上存储的小段文本并随每一个请求发送至同一个服务器,是一种实现客户端保持状态的方案。

session称为会话信息,位于web服务器上,主要负责访问者与网站之间的交互,当浏览器请求http地址时,可以基于之前的session实现会话保持、session共享等。

1.5、CDN 缓存
1.5.1、什么是CDN

image-247

内容分发网络(Content Delivery Network,CDN)是建立并覆盖在承载网上,由不同区域的服务器组成的分布式网络。将源站资源缓存到全国各地的边缘服务器,利用全球调度系统使用户能够就近获取,有效降低访问延迟,降低源站压力,提升服务可用性。

常见的CDN服务商

1.5.2、用户请求CDN流程

假设您的业务源站域名为www.test.com,域名接入 CDN 开始使用加速服务后,当您的用户发起HTTP 请求时,实际的处理流程如下图所示:

image-248

详细说明如下:

  1. 用户向www.test.com下的某图片资源(如:1.jpg)发起请求,会先向 Local DNS 发起域名解析请求。
  2. 当 Local DNS 解析www.test.com时,会发现已经配置了 CNAMEwww.test.com.cdn.dnsv1.com,解析请求会发送至 Tencent DNS(GSLB),GSLB 为腾讯云自主研发的调度体系,会为请求分配最佳节点 IP。
  3. Local DNS 获取 Tencent DNS 返回的解析 IP。
  4. 用户获取解析 IP。
  5. 用户向获取的 IP 发起对资源 1.jpg 的访问请求。
  6. 若该 IP 对应的节点缓存有 1.jpg,则会将数据直接返回给用户(10),此时请求结束。若该节点未缓存 1.jpg,则节点会向业务源站发起对 1.jpg 的请求(6、7、8),获取资源后,结合用户自定义配置的缓存策略,将资源缓存至节点(9),并返回给用户(10),此时请求结束。

image-249

1.5.3、利用 302 实现转发请求重定向至最优服务器集群

因为中国网络较为复杂,依赖DNS就近解析的调度,仍然会存在部分请求调度失效、调度生效慢等问题。

比如:腾讯云利用在全国部署的302重定向服务器集群,能够为每一个请求实时决策最优的服务器资源,精准解决小运营商的调度问题,提升用户访问质量, 能最快地把用户引导到最优的服务器节点上,避开性能差或者异常的节点。

1.5.4、CDN 分层缓存

提前对静态内容进行预缓存,避免大量的请求回源,导致主站网络带宽被打满而导致数据无法更新,另外CDN可以将数据根据访问的热度不同而进行不同级别的缓存,例如:访问量最高的资源访问CDN 边缘节点的内存,其次的放在SSD或者SATA,再其次的放在云存储,这样兼顾了速度与成本。

比如: 腾讯云CDN节点,根据用户的数据冷热不同,动态的进行识别,按照cache层次进行数据的存储,在访问频率到40%-90%的数据,首先放在OC边缘节点内存cache中,提供8G-64G的数据空间存储;在访问频率到30%-50%的数据,放在OC节点SSD/SATA硬盘cache中,提供1T-15T的数据空间存猪,其他的比较冷的数据,放在云存储中,采用回源拉取的方式进行处理。这样在成本和效率中计算出最优平衡点,为客户提供服务。

1.5.5、CDN主要优势

CDN 有效地解决了目前互联网业务中网络层面的以下问题:

1.6、应用层缓存

Nginx、PHP等web服务可以设置应用缓存以加速响应用户请求,另外有些解释性语言,比如:PHP/Python/Java不能直接运行,需要先编译成字节码,但字节码需要解释器解释为机器码之后才能执行,因此字节码也是一种缓存,有时候还会出现程序代码上线后字节码没有更新的现象。所以一般上线新版前,需要先将应用缓存清理,再上线新版

另外可以利用动态页面静态化技术,加速访问,比如:将访问数据库的数据的动态页面,提前用程序生成静态页面文件html.电商网站的商品介绍,评论信息非实时数据等皆可利用此技术实现

1.7、数据层缓存

分布式缓存服务

数据库

1.8、硬件缓存
1.8.1、CPU缓存

CPU缓存(L1的数据缓存和L1的指令缓存)、二级缓存、三级缓存

image-250

image-251

1.9磁盘相关缓存

2、redis 基础

2.1、redis 简介

短短几年,Redis就有了很大的用户群体,目前国内外使用的公司众多,比如:阿里,百度,新浪微博,知乎网,GitHub,Twitter 等

Redis是一个开源的、遵循BSD协议的、基于内存的而且目前比较流行的键值数据库(key-value database),是一个非关系型数据库,redis 提供将内存通过网络远程共享的一种服务,提供类似功能的还有memcached,但相比memcached,redis还提供了易扩展、高性能、具备数据持久性等功能。

Redis 在高并发、低延迟环境要求比较高的环境使用量非常广泛,目前redis在DB-Engine月排行榜https://db-engines.com/en/ranking 中一直比较靠前,而且一直是键值型存储类的首位

image-252

官网地址:https://redis.io/

2.2、Redis 特性
2.3、单线程

Redis 6.0版本前一直是单线程方式处理用户的请求

image-253

单线程为何如此快?

image-254

注意事项:

2.4、redis 对比 memcached
2.5、redis 典型应用场景

数据更新操作流程:

image-255

数据读操作流程:

image-256

3、Redis 安装及连接

官方下载地址:http://download.redis.io/releases/

3.1、yum安装redis

在centos系统上需要安装epel源

3.1.1、查看yum仓库redis版本
[root@localhost ~]# yum -y install epel-release
[root@localhost ~]# yum info redis
3.1.2、yum安装 redis
[root@localhost ~]# yum -y install redis
[root@localhost ~]# systemctl enable --now redis
[root@localhost ~]# pstree -p |grep redis
[root@localhost ~]# redis-cli
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> info
3.2、编译安装 redis

下载当前最新release版本 redis 源码包

网站:http://download.redis.io/releases/

3.2.1、编译安装
[root@localhost ~]# yum -y install make gcc tcl
[root@localhost ~]# wget http://download.redis.io/releases/redis-5.0.9.tar.gz
[root@localhost ~]# tar xf redis-5.0.9.tar.gz
[root@localhost ~]# cd redis-5.0.9/
[root@localhost redis-5.0.9]# cd src/
[root@localhost src]# make
[root@localhost src]# make PREFIX=/apps/redis install
[root@localhost src]# echo "PATH=/apps/redis/bin:$PATH" > /etc/profile.d/redis.sh
[root@localhost src]# . /etc/profile.d/redis.sh
[root@localhost src]# tree /apps/redis/
/apps/redis/
└── bin
    ├── redis-benchmark
    ├── redis-check-aof
    ├── redis-check-rdb
    ├── redis-cli
    ├── redis-sentinel -> redis-server
    └── redis-server
1 directory, 6 files

redis-benchmark: 这是 Redis 的性能测试工具,用于测试 Redis 服务器的性能。
redis-check-aof: 这个工具用于检查和修复 Redis  Append-Only File (AOF)。AOF  Redis 持久化的一种方式,用于记录所有对 Redis 数据库的写入操作。
redis-check-rdb: 这个工具用于检查和修复 Redis  RDB 文件。RDB  Redis 另一种持久化方式,它会定期将数据库中的数据保存到磁盘上。
redis-cli: 这是 Redis 的命令行客户端工具,可用于连接 Redis 服务器并执行各种操作。
redis-sentinel: 这实际上是一个指向 redis-server 的符号链接。在 Redis 的高可用架构中,Sentinel 是用于监控 Redis 主从复制拓扑并在出现故障时进行自动failover的组件。
redis-server: 这是 Redis 服务器的主程序文件,用于启动和运行 Redis 服务器。

准备相关目录和文件

[root@localhost ~]# mkdir /apps/redis/{etc,log,data,run}
[root@localhost ~]# cp redis-5.0.9/redis.conf /apps/redis/etc/
3.2.2、前台启动 redis

redis-server 是redis 服务器程序

[root@localhost src]# redis-server --help
Usage: ./redis-server [/path/to/redis.conf] [options]
       ./redis-server - (read config from stdin)
       ./redis-server -v or --version
       ./redis-server -h or --help
       ./redis-server --test-memory <megabytes>
Examples:
       ./redis-server (run the server with default conf)
       ./redis-server /etc/redis/6379.conf
       ./redis-server --port 7777
       ./redis-server --port 7777 --replicaof 127.0.0.1 8888
       ./redis-server /etc/myredis.conf --loglevel verbose
Sentinel mode:
       ./redis-server /etc/sentinel.conf --sentinel

前台启动 redis

[root@localhost src]# redis-server /apps/redis/etc/redis.conf
                _._
           _.-``__ ''-._
      _.-``    `.  `_.  ''-._           Redis 5.0.9 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 11791
  `-._    `-._  `-./  _.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |           http://redis.io
  `-._    `-._`-.__.-'_.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |
  `-._    `-._`-.__.-'_.-'    _.-'
      `-._    `-.__.-'    _.-'
          `-._        _.-'
              `-.__.-'
[root@localhost ~]# ss -ntl
State      Recv-Q Send-Q                    Local Address:Port                                   Peer Address:Port
LISTEN     0      128                           127.0.0.1:6379                                              *:*
3.2.3、启动多实例
[root@localhost ~]# mkdir /apps/redis/6380
[root@localhost ~]# cp -ar /apps/redis/* /apps/redis/6380/
[root@localhost ~]# tree -d /apps/redis/6380
/apps/redis/6380
├── 6380
├── bin
├── data
├── etc
├── log
└── run
6 directories
[root@localhost ~]# vim /apps/redis/6380/etc/redis.conf
port 6380
[root@localhost ~]# redis-server --port 6380
[root@localhost ~]# ss -ntl
State      Recv-Q Send-Q Local Address:Port               Peer Address:Port     
LISTEN     0      128          *:6379                     *:*
LISTEN     0      128          *:6380                     *:*
LISTEN     0      128         :::6379                    :::*
LISTEN     0      128         :::6380                    :::*
[root@localhost ~]# redis-cli -p 6380
127.0.0.1:6380> exit
[root@localhost ~]# redis-cli -p 6379
127.0.0.1:6379> exit
3.2.4、解决启动时的三个警告提示
[root@localhost ~]# echo "net.core.somaxconn = 1024" >> /etc/sysctl.conf
[root@localhost ~]# sysctl -p
net.core.somaxconn = 1024
[root@localhost ~]# echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
[root@localhost ~]# sysctl -p
net.core.somaxconn = 1024
vm.overcommit_memory = 1
[root@localhost ~]# echo never > /sys/kernel/mm/transparent_hugepage/enabled
[root@localhost ~]# echo "echo never > /sys/kernel/mm/transparent_hugepage/enabled" >> /etc/rc.d/rc.local
[root@localhost ~]# chmod +x /etc/rc.d/rc.local
3.2.5、创建 redis 用户
[root@localhost ~]# useradd -r -s /sbin/nologin redis
[root@localhost ~]# chown -R redis.redis /apps/redis/
3.2.6、编辑 redis 服务启动文件
[root@localhost ~]# vim /lib/systemd/system/redis.service
[Unit]
Description=Redis persistent key-value database
After=network.target
[Service]
ExecStart=/apps/redis/bin/redis-server /apps/redis/etc/redis.conf --supervised systemd
ExecStop=/bin/kill -s QUIT $MAINPID
Type=notify
User=redis
Group=redis
RuntimeDirectory=redis
RuntimeDirectoryMode=0755
[Install]
WantedBy=multi-user.target
3.2.7、验证 redis 启动
[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl enable --now redis
[root@localhost ~]# ss -ntl
State      Recv-Q Send-Q  Local Address:Port                 Peer Address:Port
LISTEN     0      511         127.0.0.1:6379                            *:*
3.2.8、使用客户端连接 redis
redis-cli -h IP/HOSTNAME -p PORT -a PASSWORD
[root@localhost ~]# redis-cli
127.0.0.1:6379> info
127.0.0.1:6379> exit
3.2.9、创建命令软链接
[root@localhost ~]# ln -s /apps/redis/bin/ /usr/bin/
3.2.10、编译安装后的命令
[root@localhost ~]# ll /apps/redis/bin/
总用量 32772
-rwxr-xr-x. 1 redis redis 4367128 7月   3 21:19 redis-benchmark
-rwxr-xr-x. 1 redis redis 8125424 7月   3 21:19 redis-check-aof
-rwxr-xr-x. 1 redis redis 8125424 7月   3 21:19 redis-check-rdb
-rwxr-xr-x. 1 redis redis 4808096 7月   3 21:19 redis-cli
lrwxrwxrwx. 1 redis redis      12 7月   3 21:19 redis-sentinel -> redis-server
-rwxr-xr-x. 1 redis redis 8125424 7月   3 21:19 redis-server
工具 作用
redis-benchmark redis 性能测试工具
redis-check-aof AOF文件检查工具
redis-check-rdb RDB文件检查工具
redis-cli 客户端工具
redis-sentinel -> redis-server 哨兵,软链接到server
redis-server redis 服务启动命令
3.2.11、一键编译安装Redis脚本
#!/bin/bash
. /etc/init.d/functions
VERSION=redis-5.0.9
DIR1=/apps/redis
PASSWORD=centos
install() {
yum -y install make wget gcc tcl &> /dev/null || { action "安装所需包失败,请检测包或网络配置" false;exit;}
wget http://download.redis.io/releases/${VERSION}.tar.gz &> /dev/null || { action "Redis 源码下载失败" false; exit; }
tar xf $VERSION.tar.gz
cd $VERSION/
make -j 2 &> /dev/null && make PREFIX=${DIR1} install &> /dev/null && action "Redis 编译安装成功" || { action "Redis 编译安装失败" false;exit; }
ln -s ${DIR1}/bin/* /usr/bin/
mkdir -p ${DIR1}/{etc,data,log,run}
cd
cp $VERSION/redis.conf $DIR1/etc
sed -i -e "s/bind 127.0.0.1/bind 0.0.0.0/" -e "/# requirepass/a requirepass ${PASSWORD}" -e "/^dir .*/c dir ${DIR1}/data/" -e "/logfile .*/c logfile ${DIR1}/log/redis_6379.log" -e "/^pidfile .*/c pidfile ${DIR1}/run/redis_6379.pid" ${DIR1}/etc/redis.conf

if id redis &> /dev/null;then
    action "redis 用户已经存在" false
else
    useradd -r -s /sbin/nologin redis
    action "redis 用户创建成功"
fi
chown -R redis.redis ${DIR1}
cat >> /etc/sysctl.conf <<EOF
net.core.somaxconn = 1024
vm.overcommit_memory = 1
EOF
sysctl -p
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo "echo never > /sys/kernel/mm/transparent_hugepage/enabled" >> /etc/rc.d/rc.local
chmod +x /etc/rc.d/rc.local
/etc/rc.d/rc.local
cat > /lib/systemd/system/redis.service <<EOF
[Unit]
Description=Redis persistent key-value database
After=network.target
[Service]
ExecStart=${DIR1}/bin/redis-server ${DIR1}/etc/redis.conf --supervised systemd
ExecStop=/bin/kill -s QUIT \$MAINPID
Type=notify
User=redis
Group=redis
RuntimeDirectory=redis
RuntimeDirectoryMode=0755
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now redis &> /dev/null && action "redis 服务启动成功" || { action "redis 服务启动失败" false;exit; }
}
install
3.3、连接到 Redis

主要分为客户端连接和程序的连接

3.3.1、客户端连接 redis
[root@localhost ~]# redis-cli
127.0.0.1:6379>
[root@localhost ~]# redis-cli -h 192.168.88.140 -p 6379
192.168.175.149:6379>
[root@localhost ~]# vim /apps/redis/etc/redis.conf
requirepass centos
[root@localhost ~]# systemctl restart redis
[root@localhost ~]# redis-cli -h 192.168.88.140 -p 6379 -a centos --no-auth-warning
192.168.88.140:6379>

-a centos: 使用密码 centos 进行身份验证。
--no-auth-warning: 不显示身份验证警告消息。
3.3.2、程序连接 Redis

image-257

3.3.3、shell 连接方式
[root@localhost ~]# vim redis_test.sh
#!/bin/bash
NUM=`seq 1 10000`
PASS=centos
for i in ${NUM};do
redis-cli -h 127.0.0.1 -a "$PASS" --no-auth-warning set key-${i} value-${i}
echo "key-${i} value-${i} 写入完成"
done
echo "一万个key写入到Redis完成"
[root@localhost ~]# vim /apps/redis/etc/redis.conf#这边临时关闭RDB,不然会报错
save ""
#save 900 1
#save 300 10
#save 60 10000
[root@localhost ~]# systemctl restart redis
[root@localhost ~]# time bash redis_test.sh
一万个key写入到Redis完成
real    0m22.874s
user    0m3.885s
sys     0m17.958s
[root@localhost ~]# redis-cli
127.0.0.1:6379> auth centos
OK
127.0.0.1:6379> keys *
127.0.0.1:6379> get key-996
"value-996"
127.0.0.1:6379> flushdb    # 清空当前库的数据
OK
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> flushall    # 清空所有的数据
OK
3.3.4、python 连接方式
[root@localhost ~]# yum -y install python3 python3-redis
[root@localhost ~]# vim redis_test.py
#!/bin/env python3
import redis
#import time
pool = redis.ConnectionPool(host="127.0.0.1",port=6379,password="centos")
r = redis.Redis(connection_pool=pool)
for i in range(100):
    r.set("k%d" % i,"v%d" % i)
# time.sleep(1)
    data=r.get("k%d" % i)
    print(data)
[root@localhost ~]# python3 redis_test.py
[root@localhost ~]# redis-cli -a centos --no-auth-warning
127.0.0.1:6379> get k88
"v88"
3.4、redis 的多实例
[root@localhost ~]# cd /apps/redis/
[root@localhost redis]# mv etc/redis.conf etc/redis_6379.conf
[root@localhost redis]# vim etc/redis_6379.conf
port 6379
pidfile /apps/redis/run/redis_6379.pid
logfile "/apps/redis/log/redis_6379.log"
dbfilename dumpi_6379.rdb
dir /apps/redis/data
appendfilename "appendonlyi_6379.aof"
[root@localhost redis]# mv /lib/systemd/system/redis.service /lib/systemd/system/redis_6379.service
[root@localhost redis]# vim /lib/systemd/system/redis_6379.service
ExecStart=/apps/redis/bin/redis-server /apps/redis/etc/redis_6379.conf --supervised systemd
[root@localhost redis]# systemctl restart redis_6379.service
cp -a etc/redis_6379.conf etc/redis_6380.conf 
sed -i "s/6379/6380/g" etc/redis_6380.conf
cp -a /lib/systemd/system/redis_6379.service /lib/systemd/system/redis_6380.service
sed -i "s/6379/6380/g" /lib/systemd/system/redis_6380.service
systemctl enable --now redis_6380.service
cp -a etc/redis_6379.conf etc/redis_6381.conf
sed -i "s/6379/6381/g" etc/redis_6381.conf
cp -a /lib/systemd/system/redis_6379.service /lib/systemd/system/redis_6381.service
sed -i "s/6379/6381/g" /lib/systemd/system/redis_6381.service
systemctl daemon-reload 
systemctl enable --now redis_6381.service
[root@localhost redis]# ss -ntl
State      Recv-Q Send-Q       Local Address:Port                      Peer Address:Port
LISTEN     0      511                      *:6379                                 *:*
LISTEN     0      511                      *:6380                                 *:*
LISTEN     0      511                      *:6381                                 *:*
[root@localhost redis]# tree
.
├── bin
│   ├── redis-benchmark
│   ├── redis-check-aof
│   ├── redis-check-rdb
│   ├── redis-cli
│   ├── redis-sentinel -> redis-server
│   └── redis-server
├── data
├── etc
│   ├── redis_6379.conf
│   ├── redis_6380.conf
│   └── redis_6381.conf
├── log
│   ├── redis_6379.log
│   ├── redis_6380.log
│   └── redis_6381.log
└── run
    ├── redis_6379.pid
    ├── redis_6380.pid
    └── redis_6381.pid
5 directories, 15 files

4、redis 配置和优化

4.1、redis 主要配置项
bind 0.0.0.0 #监听地址,可以用空格隔开后多个监听IP
protected-mode yes #redis3.2之后加入的新特性,在没有设置bind IP和密码的时候,redis只允许访问127.0.0.1:6379,可以远程连接,但当访问将提示警告信息并拒绝远程访问
port 6379 #监听端口,默认6379/tcp
tcp-backlog 511 #三次握手的时候server端收到client ack确认号之后的队列值,即全连接队列长度
timeout 0 #客户端和Redis服务端的连接超时时间,默认是0,表示永不超时
tcp-keepalive 300 #tcp 会话保持时间300s
daemonize no #默认no,即直接运行redis-server程序时,不作为守护进程运行,而是以前台方式运行,如果想在后台运行需改成yes,当redis作为守护进程运行的时候,它会写一个 pid 到/var/run/redis.pid 文件
supervised no #和OS相关参数,可设置通过upstart和systemd管理Redis守护进程,centos7后都使用systemdpidfile /var/run/redis_6379.pid #pid文件路径,可以修改为/apps/redis/run/redis_6379.pid
loglevel notice #日志级别
logfile "/path/redis.log" #日志路径,示例:logfile"/apps/redis/log/redis_6379.log"
databases 16 #设置数据库数量,默认:0-15,共16个库
always-show-logo yes #在启动redis 时是否显示或在日志中记录记录redis的logo
save 900 1 #在900秒内有1个key内容发生更改,就执行快照机制
save 300 10 #在300秒内有10个key内容发生更改,就执行快照机制
save 60 10000 #60秒内如果有10000个key以上的变化,就自动快照备份
stop-writes-on-bgsave-error yes #默认为yes时,可能会因空间满等原因快照无法保存出错时,会禁止redis写入操作,生产建议为no #此项只针对配置文件中的自动save有效
rdbcompression yes #持久化到RDB文件时,是否压缩,"yes"为压缩,"no"则反之
rdbchecksum yes #是否对备份文件开启RC64校验,默认是开启
dbfilename dump.rdb #快照文件名
dir ./ #快照文件保存路径,示例:dir "/apps/redis/data"
# replicaof <masterip> <masterport> #指定复制的master主机地址和端口,5.0版之前的指令为slaveof
# masterauth <master-password> #指定复制的master主机的密码
replica-serve-stale-data yes #当从库同主库失去连接或者复制正在进行,从机库有两种运行方式:
1、设置为yes(默认设置),从库会继续响应客户端的读请求,此为建议值
2、设置为no,除去特定命令外的任何请求都会返回一个错误"SYNC with master in progress"。
replica-read-only yes #是否设置从库只读,建议值为yes,否则主库同步从库时可能会覆盖数据,造成数据丢失
repl-diskless-sync no #是否使用socket方式复制数据(无盘同步),新slave第一次连接master时需要做数据的全量同步,redis server就要从内存dump出新的RDB文件,然后从master传到slave,有两种方式把RDB文件传输给客户端:
1、基于硬盘(disk-backed):为no时,master创建一个新进程dump生成RDB磁盘文件,RDB完成之后由
父进程(即主进程)将RDB文件发送给slaves,此为默认值
2、基于socket(diskless):master创建一个新进程直接dump RDB至slave的网络socket,不经过主进程和硬盘
#推荐使用基于硬盘(为no),是因为RDB文件创建后,可以同时传输给更多的slave,但是基于socket(为yes), 新slave连接到master之后得逐个同步数据。只有当磁盘I/O较慢且网络较快时,可用diskless(yes),否则一般建议使用磁盘(no)
repl-diskless-sync-delay 5 #diskless时复制的服务器等待的延迟时间,设置0为关闭,在延迟时间内到达的客户端,会一起通过diskless方式同步数据,但是一旦复制开始,master节点不会再接收新slave的复制请求,直到下一次同步开始才再接收新请求。即无法为延迟时间后到达的新副本提供服务,新副本将排队等待下一次RDB传输,因此服务器会等待一段时间才能让更多副本到达。推荐值:30-60
repl-ping-replica-period 10 #slave根据master指定的时间进行周期性的PING master,用于监测master状态,默认10s
repl-timeout 60 #复制连接的超时时间,需要大于repl-ping-slave-period,否则会经常报超时
repl-disable-tcp-nodelay no #是否在slave套接字发送SYNC之后禁用 TCP_NODELAY,如果选择"yes",Redis将合并多个报文为一个大的报文,从而使用更少数量的包向slaves发送数据,但是将使数据传输到slave上有延迟,Linux内核的默认配置会达到40毫秒,如果 "no" ,数据传输到slave的延迟将会减少,但要使用更多的带宽
repl-backlog-size 512mb #复制缓冲区内存大小,当slave断开连接一段时间后,该缓冲区会累积复制副本数据,因此当slave 重新连接时,通常不需要完全重新同步,只需传递在副本中的断开连接后没有同步的部分数据即可。只有在至少有一个slave连接之后才分配此内存空间,建议建立主从时此值要调大一些或在低峰期配置,否则会导致同步到slave失败
repl-backlog-ttl 3600 #多长时间内master没有slave连接,就清空backlog缓冲区
replica-priority 100 #当master不可用,哨兵Sentinel会根据slave的优先级选举一个master,此值最低的slave会当选master,而配置成0,永远不会被选举,一般多个slave都设为一样的值,让其自动选择
#min-replicas-to-write 3 #至少有3个可连接的slave,mater才接受写操作
#min-replicas-max-lag 10 #和上面至少3个slave的ping延迟不能超过10秒,否则master也将停止写操作
requirepass foobared #设置redis连接密码,之后需要AUTH pass,如果有特殊符号,用" "引起来,生产建议设置
rename-command #重命名一些高危命令,示例:rename-command FLUSHALL "" 禁用命令
               #示例: rename-command del areyouok
maxclients 10000 #Redis最大连接客户端
maxmemory <bytes> #redis使用的最大内存,单位为bytes字节,0为不限制,建议设为物理内存一半,8G内存的计算方式8(G)*1024(MB)1024(KB)*1024(Kbyte),需要注意的是缓冲区是不计算在maxmemory内,生产中如果不设置此项,可能会导致BOOM
appendonly no #是否开启AOF日志记录,默认redis使用的是rdb方式持久化,这种方式在许多应用中已经足够用了,但是redis如果中途宕机,会导致可能有几分钟的数据丢失(取决于dump数据的间隔时间),根据save来策略进行持久化,Append Only File是另一种持久化方式,可以提供更好的持久化特性,Redis会把每次写入的数据在接收后都写入 appendonly.aof 文件,每次启动时Redis都会先把这个文件的数据读入内存里,先忽略RDB文件。默认不启用此功能
appendfilename "appendonly.aof" #文本文件AOF的文件名,存放在dir指令指定的目录中
appendfsync everysec #aof持久化策略的配置
#no表示由操作系统保证数据同步到磁盘,Linux的默认fsync策略是30秒,最多会丢失30s的数据
#always表示每次写入都执行fsync,以保证数据同步到磁盘,安全性高,性能较差
#everysec表示每秒执行一次fsync,可能会导致丢失这1s数据,此为默认值,也生产建议值
#同时在执行bgrewriteaof操作和主进程写aof文件的操作,两者都会操作磁盘,而bgrewriteaof往往会涉及大量磁盘操作,这样就会造成主进程在写aof文件的时候出现阻塞的情形,以下参数实现控制
no-appendfsync-on-rewrite no #在aof rewrite期间,是否对aof新记录的append暂缓使用文件同步策略,主要考虑磁盘IO开支和请求阻塞时间。
#默认为no,表示"不暂缓",新的aof记录仍然会被立即同步到磁盘,是最安全的方式,不会丢失数据,但是要忍受阻塞的问题
#为yes,相当于将appendfsync设置为no,这说明并没有执行磁盘操作,只是写入了缓冲区,因此这样并不会造成阻塞(因为没有竞争磁盘),但是如果这个时候redis挂掉,就会丢失数据。丢失多少数据呢?Linux的默认fsync策略是30秒,最多会丢失30s的数据,但由于yes性能较好而且会避免出现阻塞因此比较推荐
#rewrite 即对aof文件进行整理,将空闲空间回收,从而可以减少恢复数据时间
auto-aof-rewrite-percentage 100 #当Aof log增长超过指定百分比例时,重写AOF文件,设置为0表示不自动重写Aof日志,重写是为了使aof体积保持最小,但是还可以确保保存最完整的数据
auto-aof-rewrite-min-size 64mb #触发aof rewrite的最小文件大小
aof-load-truncated yes #是否加载由于某些原因导致的末尾异常的AOF文件(主进程被kill/断电等),建议yes
aof-use-rdb-preamble no #redis4.0新增RDB-AOF混合持久化格式,在开启了这个功能之后,AOF重写产生的文件将同时包含RDB格式的内容和AOF格式的内容,其中RDB格式的内容用于记录已有的数据,而AOF格式的内容则用于记录最近发生了变化的数据,这样Redis就可以同时兼有RDB持久化和AOF持久化的优点(既能够快速地生成重写文件,也能够在出现问题时,快速地载入数据),默认为no,即不启用此功能
lua-time-limit 5000 #lua脚本的最大执行时间,单位为毫秒
cluster-enabled yes #是否开启集群模式,默认不开启,即单机模式
cluster-config-file nodes-6379.conf #由node节点自动生成的集群配置文件名称
cluster-node-timeout 15000 #集群中node节点连接超时时间,单位ms,超过此时间,会踢出集群
cluster-replica-validity-factor 10 #单位为次,在执行故障转移的时候可能有些节点和master断开一段时间导致数据比较旧,这些节点就不适用于选举为master,超过这个时间的就不会被进行故障转移,不能当选master,计算公式:(node-timeout * replica-validity-factor) + repl-pingreplica-period
cluster-migration-barrier 1 #集群迁移屏障,一个主节点至少拥有1个正常工作的从节点,即如果主节点的slave节点故障后会将多余的从节点分配到当前主节点成为其新的从节点。
cluster-require-full-coverage yes #集群请求槽位全部覆盖,如果一个主库宕机且没有备库就会出现集群槽位不全,那么yes时redis集群槽位验证不全,就不再对外提供服务(对key赋值时,会出现CLUSTERDOWN The cluster is down的示,cluster_state:fail,但ping 仍PONG),而no则可以继续使用,但是会出现查询数据查不到的情况(因为有数据丢失)。生产建议为no 
cluster-replica-no-failover no #如果为yes,此选项阻止在主服务器发生故障时尝试对其主服务器进行故障转移。 但是,主服务器仍然可以执行手动强制故障转移,一般为no
#Slow log 是 Redis 用来记录超过指定执行时间的日志系统,执行时间不包括与客户端交谈,发送回复等I/O操作,而是实际执行命令所需的时间(在该阶段线程被阻塞并且不能同时为其它请求提供服务),由于slow log 保存在内存里面,读写速度非常快,因此可放心地使用,不必担心因为开启 slow log 而影响Redis 的速度
slowlog-log-slower-than 10000 #以微秒为单位的慢日志记录,为负数会禁用慢日志,为0会记录每个命令操作。默认值为10ms,一般一条命令执行都在微秒级,生产建议设为1ms
slowlog-max-len 128 #最多记录多少条慢日志的保存队列长度,达到此长度后,记录新命令会将最旧的命令从命令队列中删除,以此滚动删除,即,先进先出,队列固定长度,默认128,值偏小,生产建议设为1000以上
4.2、CONFIG 动态修改配置
redis 127.0.0.1:6379> CONFIG SET parameter value
时间复杂度:O(1)
CONFIG SET 命令可以动态地调整 Redis 服务器的配置(configuration)而无须重启。
redis 127.0.0.1:6379> CONFIG GET slowlog-max-len
1) "slowlog-max-len"
2) "1024"

redis 127.0.0.1:6379> CONFIG SET slowlog-max-len 10086
OK

redis 127.0.0.1:6379> CONFIG GET slowlog-max-len
1) "slowlog-max-len"
2) "10086"
4.2.1、设置连接密码
#设置连接密码
127.0.0.1:6379> CONFIG SET requirepass centos
OK
#查看连接密码
127.0.0.1:6379> CONFIG GET requirepass
1) "requirepass"
2) "centos"
4.2.2、获取当前配置
#奇数行为键,偶数行为值
127.0.0.1:6379> CONFIG GET *
#查看bind
127.0.0.1:6379> CONFIG GET bind
1) "bind"
2) "0.0.0.0"
#有些设置无法修改
127.0.0.1:6379> CONFIG SET bind 127.0.0.1
(error) ERR Unsupported CONFIG parameter: bind
4.2.3、更改最大内存
127.0.0.1:6379> CONFIG SET maxmemory 8589934592
OK
127.0.0.1:6379> CONFIG GET maxmemory
1) "maxmemory"
2) "8589934592"
4.3、慢查询

image-258

config set slowlog-log-slower-than 20000
config set slowlog-max-len 1000
config rewrite
127.0.0.1:6379> config set slowlog-log-slower-than 1
OK
127.0.0.1:6379> config set slowlog-max-len 1000
OK
127.0.0.1:6379> SLOWLOG len
# 获取慢查询日志列表当前的长度
(integer) 2
127.0.0.1:6379> SLOWLOG get
1) 1) (integer) 2
   2) (integer) 1625544379
   3) (integer) 1
   4) 1) "SLOWLOG"
      2) "len"
   5) "127.0.0.1:40294"
   6) ""
2) 1) (integer) 1
   2) (integer) 1625544372
   3) (integer) 4
   4) 1) "config"
      2) "set"
      3) "slowlog-max-len"
      4) "1000"
   5) "127.0.0.1:40294"
   6) ""
3) 1) (integer) 0
   2) (integer) 1625544363
   3) (integer) 3
   4) 1) "config"
      2) "set"
      3) "slowlog-log-slower-than"
      4) "1"
   5) "127.0.0.1:40294"
   6) ""
127.0.0.1:6379> slowlog reset
OK
127.0.0.1:6379> slowlog len
(integer) 0
4.4、redis持久化

Redis 虽然是一个内存级别的缓存程序,也就是redis 是使用内存进行数据的缓存的,但是其可以将内存的数据按照一定的策略保存到硬盘上,从而实现数据持久保存的目的

目前redis支持两种不同方式的数据持久化保存机制,分别是RDB和AOF

image-259

4.4.1、RDB 模式

RDB 模式工作原理

image-260

RDB(Redis DataBase):基于时间的快照,其默认只保留当前最新的一次快照,特点是执行速度比较快,缺点是可能会丢失从上次快照到当前时间点之间未做快照的数据

RDB bgsave 实现快照的具体过程

image-261

[root@localhost redis]# pstree -p |grep redis-server;ll -h /apps/redis/data/
|-redis-server(6831)-+-{redis-server}(6849)
|                    |-{redis-server}(6850)
|                    `-{redis-server}(6851)
|-redis-server(34300)-+-{redis-server}(34301)
|                     |-{redis-server}(34302)
|                     `-{redis-server}(34303)
总用量 204K
-rw-r--r--. 1 redis redis 204K 7月   6 12:28 dumpi_6379.rdb

RDB 相关配置

save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /var/lib/redis    #编译安装,默认RDB文件存放在启动redis的工作目录,建议明确指定存入目录

实现RDB方式

RDB 模式优点

RDB 模式缺点

image-262

  1. 执行bgsave命令,Redis父进程判断当前是否存在正在执行的子进 程,如RDB/AOF子进程,如果存在bgsave命令直接返回。
  2. 父进程执行fork操作创建子进程,fork操作过程中父进程会阻塞,通 过info stats命令查看latest_fork_usec选项,可以获取最近一个fork操作的耗时,单位为微秒。
  3. 父进程fork完成后,bgsave命令返回“Background saving started”信息 并不再阻塞父进程,可以继续响应其他命令。
  4. 子进程创建RDB文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换。执行lastsave命令可以获取最后一次生成RDB的 时间,对应info统计的rdb_last_save_time选项。
  5. 进程发送信号给父进程表示完成,父进程更新统计信息,
4.4.2、AOF 模式

AOF 模式工作原理

image-263

AOF rewrite 重写

将一些重复的,可以合并的,过期的数据重新写入一个新的AOF文件,从而节约AOF备份占用的硬盘空间,也能加速恢复过程

可以手动执行bgrewriteaof 触发AOF,或定义自动rewrite 策略

AOF rewrite 过程

image-264

1.执行AOF重写请求。

2.父进程执行fork创建子进程,开销等同于bgsave过程。

3.1主进程fork操作完成后,继续响应其他命令。所有修改命令依然写 入AOF缓冲区并根据appendfsync策略同步到硬盘,保证原有AOF机制正确 性。

3.2由于fork操作运用写时复制技术,子进程只能共享fork操作时的内 存数据。由于父进程依然响应命令,Redis使用“AOF重写缓冲区”保存这部 分新数据,防止新AOF文件生成期间丢失这部分数据。

4.子进程根据内存快照,按照命令合并规则写入到新的AOF文件。每次批量写入硬盘数据量由配置aof-rewrite-incremental-fsync控制,默认为 32MB,防止单次刷盘数据过多造成硬盘阻塞。

5.1新AOF文件写入完成后,子进程发送信号给父进程,父进程更新 统计信息,具体见info persistence下的aof_*相关统计。

5.2父进程把AOF重写缓冲区的数据写入到新的AOF文件。

5.3使用新AOF文件替换老文件,完成AOF重写。

# 用AOF功能的正确方式
[root@localhost data]# ll
总用量 204
-rw-r--r--. 1 redis redis 207886 7月   6 12:28 dumpi_6379.rdb
[root@localhost data]# redis-cli
127.0.0.1:6379> config get appendonly
1) "appendonly"
2) "no"
127.0.0.1:6379> config set appendonly yes
127.0.0.1:6379> CONFIG REWRITE
OK
[root@localhost data]# ll
总用量 408
-rw-r--r--. 1 redis redis 207886 7月   6 13:45 appendonlyi_6379.aof
-rw-r--r--. 1 redis redis 207886 7月   6 12:28 dumpi_6379.rdb

AOF相关配置

appendonly yes
appendfilename "appendonly-${port}.aof"
appendfsync everysec
dir /path
no-appendfsync-on-rewrite yes
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes

AOF 模式优点

AOF 模式缺点

4.4.3、RDB和AOF 的选择

如果主要充当缓存功能,或者可以承受数分钟数据的丢失, 通常生产环境一般只需启用RDB即可,此也是默认值

如果数据需要持久保存,一点不能丢失,可以选择同时开启RDB和AOF,一般不建议只开启AOF

5、Redis 常用命令

参考链接:http://redisdoc.com/

5.1、INFO
127.0.0.1:6379> info
5.2、SELECT
127.0.0.1:6379> select 0
OK
127.0.0.1:6379> select 15
OK
127.0.0.1:6379[15]> select 16
(error) ERR DB index is out of range
127.0.0.1:6379> info cluster
# Cluster
cluster_enabled:1
127.0.0.1:6379> select 0
OK
127.0.0.1:6379> select 1
(error) ERR SELECT is not allowed in cluster mode
5.3、KEYS
命令 时间复杂度
keys O(n)
dbsize O(1)
del O(1)
exists O(1)
expire O(1)
type O(1)
127.0.0.1:6379[1]> SELECT 0
OK
127.0.0.1:6379> KEYS *    #匹配数据库内所有key
1) "port1"
2) "name"
3) "port2"
4) "port"
127.0.0.1:6379> SELECT 1
OK
127.0.0.1:6379[1]> KEYS *
(empty list or set)
127.0.0.1:6379[1]>
127.0.0.1:6379> SELECT 1
OK
#一次设置4个key
127.0.0.1:6379[1]> MSET one 1 two 2 three 3 four 4
OK
127.0.0.1:6379[1]> KEYS *
1) "two"
2) "one"
3) "four"
5.4、BGSAVE
#交互式执行
127.0.0.1:6379[1]> BGSAVE
Background saving started
[root@centos8 ~]#ll /apps/redis/data/
total 664
-rw-r--r-- 1 redis redis    204 Oct 22 21:30 dump_6379.rdb
#非交互式执行
[root@centos8 ~]#redis-cli -a centos --no-auth-warning bgsave
Background saving started
[root@centos8 ~]#ll /apps/redis/data/
total 664
-rw-r--r-- 1 redis redis    204 Oct 22 21:32 dump_6379.rdb
5.5、DBSIZE
127.0.0.1:6379> DBSIZE
(integer) 8
127.0.0.1:6379> SELECT 1
OK
127.0.0.1:6379[1]> DBSIZE
(integer) 4
127.0.0.1:6379[1]> SELECT 2
OK
127.0.0.1:6379[2]> DBSIZE
(integer) 0
5.6、FLUSHDB
127.0.0.1:6379[2]> SELECT 1
OK
127.0.0.1:6379[1]> DBSIZE
(integer) 4
127.0.0.1:6379[1]> FLUSHDB
OK
127.0.0.1:6379[1]> DBSIZE
(integer) 0
5.7、FLUSHALL
127.0.0.1:6379> FLUSHALL
OK
#生产建议修改配置 /etc/redis.conf,禁用或改为别名
rename-command FLUSHALL ""
5.8、SHUTDOWN

6、redis 数据类型

image-265

image-266

6.1、字符串 string

字符串是所有编程语言中最常见的和最常用的数据类型,而且也是redis最基本的数据类型之一,而且redis 中所有的 key 的类型都是字符串。常用于保存 Session 信息场景,此数据类型比较常用

127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> set title ceo ex 5  #设置自动过期时间为5秒
OK
127.0.0.1:6379> get title
"ceo"
127.0.0.1:6379> get title
(nil)
127.0.0.1:6379> set NAME eagle   #大小写敏感
OK
127.0.0.1:6379> get name
(nil)
#key不存在才设置,相当于add
127.0.0.1:6379> set name eagle
OK
127.0.0.1:6379> get name
"eagle"
127.0.0.1:6379> set name xwz nx
(nil)
127.0.0.1:6379> get name
"eagle"
#key存在才设置,相当于update
127.0.0.1:6379> get name
"eagle"
127.0.0.1:6379> set name xwz xx
OK
127.0.0.1:6379> get name
"xwz"
127.0.0.1:6379> get age
(nil)
127.0.0.1:6379> set age 18 xx
(nil)
127.0.0.1:6379> get age
(nil)
127.0.0.1:6379> get name
"xwz"
127.0.0.1:6379> get name age
(error) ERR wrong number of arguments for 'get' command
127.0.0.1:6379> del name
(integer) 1
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> del age NAME
(integer) 2
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> mget k1 k2
1) "v1"
2) "v2"
127.0.0.1:6379> keys *
1) "k2"
2) "k3"
3) "k1"
127.0.0.1:6379> keys k*
1) "k2"
2) "k3"
3) "k1"
127.0.0.1:6379> append k2 " append new value"
(integer) 19
127.0.0.1:6379> get k2
"v2 append new value"
127.0.0.1:6379> set name eagle
OK
127.0.0.1:6379> getset name xwz
"eagle"
127.0.0.1:6379> get name
"xwz"
127.0.0.1:6379> set name eagle
OK
127.0.0.1:6379> strlen name   #返回字节数
(integer) 5
127.0.0.1:6379> append name " xwz"
(integer) 9
127.0.0.1:6379> get name
"eagle xwz"
127.0.0.1:6379> strlen name
(integer) 9
127.0.0.1:6379> set name 好好学习
OK
127.0.0.1:6379> strlen name
(integer) 12
127.0.0.1:6379> set name eagle ex 5
OK
127.0.0.1:6379> exists name
(integer) 1
127.0.0.1:6379> exists name
(integer) 0
127.0.0.1:6379> ttl age
(integer) -2
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> set name eagle ex 20
OK
127.0.0.1:6379> ttl name
(integer) 18
127.0.0.1:6379> ttl k1
(integer) -1
127.0.0.1:6379> set name eagle ex 20
OK
127.0.0.1:6379> expire name 30
(integer) 1
127.0.0.1:6379> ttl name
(integer) 29
127.0.0.1:6379> ttl name
(integer) 5
127.0.0.1:6379> persist name
(integer) 1
127.0.0.1:6379> ttl name
(integer) -1
127.0.0.1:6379> set num 21
OK
127.0.0.1:6379> incr num
(integer) 22
127.0.0.1:6379> get num
"22"
127.0.0.1:6379> set num 21
OK
127.0.0.1:6379> decr num
(integer) 20
127.0.0.1:6379> get num
"20"

将key对应的数字加decrement(可以是负数)。如果key不存在,操作之前,key就会被置为0。如果key的value类型错误或者是个不能表示成数字的字符串,就返回错误。这个操作最多支持64位有符号的正型数字。

127.0.0.1:6379> set num 30
OK
127.0.0.1:6379> incrby num 20
(integer) 50
127.0.0.1:6379> get num
"50"
127.0.0.1:6379> incrby num -20
(integer) 30
127.0.0.1:6379> get num
"30"
127.0.0.1:6379> get num1
(nil)
127.0.0.1:6379> incrby num1 5
(integer) 5
127.0.0.1:6379> get num1
"5"
6.2、列表 list

image-267

列表是一个双向可读写的管道,其头部是左侧,尾部是右侧,一个列表最多可以包含2^32-1(4294967295)个元素,下标 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。

也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,元素值可以重复,常用于存入日志等场景,此数据类型比较常用

image-268

127.0.0.1:6379> lpush name eagle cs xwz tj
(integer) 4
127.0.0.1:6379> type name
list
127.0.0.1:6379> rpush course linux python go
(integer) 3
127.0.0.1:6379> type course
list
127.0.0.1:6379> lpush name abc
(integer) 5
127.0.0.1:6379> rpush name tom
(integer) 6
127.0.0.1:6379> llen name
(integer) 6

image-269

image-270

127.0.0.1:6379> lpush list1 a b c d
(integer) 4
127.0.0.1:6379> lindex list1 0  #获取0编号的元素
"d"
127.0.0.1:6379> lindex list1 3   #获取3编号的元素
"a"
127.0.0.1:6379> lindex list1 -1   #获取最后一个的元素
"a"
127.0.0.1:6379> lrange list1 1 2
1) "c"
2) "b"
127.0.0.1:6379> lrange list1 0 3  #所有元素
1) "d"
2) "c"
3) "b"
4) "a"
127.0.0.1:6379> lrange list1 0 -1    #所有元素
1) "d"
2) "c"
3) "b"
4) "a"
127.0.0.1:6379> lrange list1 0 -1
1) "d"
2) "c"
3) "b"
4) "a"
127.0.0.1:6379> rpush listkey a b c d e f
(integer) 6
127.0.0.1:6379> lrange listkey 0 -1
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
6) "f"
127.0.0.1:6379> lset listkey 2 redis
OK
127.0.0.1:6379> lrange listkey 0 -1
1) "a"
2) "b"
3) "redis"
4) "d"
5) "e"
6) "f"
127.0.0.1:6379> lpush list1 a b c d
(integer) 4
127.0.0.1:6379> lrange list1 0 3
1) "d"
2) "c"
3) "b"
4) "a"
127.0.0.1:6379> lpop list1   #弹出左边第一个元素,即删除第一个
"d"
127.0.0.1:6379> rpop list1   #弹出右边第一个元素,即删除最后一个
"a"
127.0.0.1:6379> llen list1
(integer) 2
127.0.0.1:6379> lrange list1 0 -1
1) "c"
2) "b"
* LTRIM 对一个列表进行修剪(trim),让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除
127.0.0.1:6379> lrange list1 0 3
1) "d"
2) "c"
3) "b"
4) "a"
127.0.0.1:6379> ltrim list1 1 2
OK
127.0.0.1:6379> lrange list1 0 -1
1) "c"
2) "b"
127.0.0.1:6379> del list1
(integer) 1
127.0.0.1:6379> exists list1
(integer) 0
6.3、集合 set

Set 是 String 类型的无序集合,集合中的成员是唯一的,这就意味着集合中不能出现重复的数据,可以在两个不同的集合中对数据进行对比并取值,常用于取值判断,统计,交集等场景

127.0.0.1:6379> sadd set1 v1
(integer) 1
127.0.0.1:6379> sadd set2 v2 v3
(integer) 2
127.0.0.1:6379> type set1
set
127.0.0.1:6379> type set2
set
127.0.0.1:6379> sadd set1 v1  #已存在的值,无法再次添加
(integer) 0
127.0.0.1:6379> sadd set1 v3 v4 v5
(integer) 3
127.0.0.1:6379> smembers set1
1) "v3"
2) "v5"
3) "v4"
4) "v1"
127.0.0.1:6379> smembers set2
1) "v3"
2) "v2"
127.0.0.1:6379> sadd goods mobile car laptop
(integer) 3
127.0.0.1:6379> srem goods car
(integer) 1
127.0.0.1:6379> smembers goods
1) "mobile"
2) "laptop"
127.0.0.1:6379> smembers set1
1) "v3"
2) "v5"
3) "v4"
4) "v1"
127.0.0.1:6379> smembers set2
1) "v3"
2) "v2"
127.0.0.1:6379> sinter set1 set2
1) "v3"
127.0.0.1:6379> sunion set1 set2
1) "v1"
2) "v4"
3) "v5"
4) "v3"
5) "v2"
127.0.0.1:6379> sdiff set1 set2
1) "v1"
2) "v4"
3) "v5"
6.4、有序集合 sorted set

Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员,不同的是每个元素都会关联一个double(双精度浮点型)类型的分数,redis正是通过该分数来为集合中的成员进行从小到大的排序,有序集合的成员是唯一的,但分数(score)却可以重复,集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1), 集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个成员),经常用于排行榜的场景

127.0.0.1:6379> zadd zset1 1 v1  #分数为1
(integer) 1
127.0.0.1:6379> zadd zset1 2 v2
(integer) 1  
127.0.0.1:6379> zadd zset1 2 v3  #分数可重复,元素值不可以重复
(integer) 1
127.0.0.1:6379> zadd zset1 3 v4
(integer) 1
127.0.0.1:6379> type zset1
zset
127.0.0.1:6379> zadd zset2 1 v1 2 v2 3 v3 4 v4 5 v5  #一次生成多个数据
(integer) 5
127.0.0.1:6379> zadd phb 90 xls 95 tj 30 sg
(integer) 3
127.0.0.1:6379> zrange phb 0 -1
#正序排序后显示集合内所有的key,score从小到大显示
1) "sg"
2) "xls"
3) "tj"
127.0.0.1:6379> zrevrange phb 0 -1
#倒叙排序后显示集合内所有的key,score从大到小显示
1) "tj"
2) "xls"
3) "sg"
127.0.0.1:6379> zrevrange phb 0 -1 withscores
#正序显示指定集合内所有key和得分情况
1) "tj"
2) "95"
3) "xls"
4) "90"
5) "sg"
6) "30"
127.0.0.1:6379> zcard phb
(integer) 3
127.0.0.1:6379> zrange phb 0 2
1) "sg"
2) "xls"
3) "tj"
127.0.0.1:6379> zrange phb 0 -1
1) "sg"
2) "xls"
3) "tj"
127.0.0.1:6379> zadd phb 90 xls 95 tj 30 sg
(integer) 3
127.0.0.1:6379> zrange phb 0 -1
1) "sg"
2) "xls"
3) "tj"
127.0.0.1:6379> zrank phb sg
(integer) 0
127.0.0.1:6379> zrank phb tj
(integer) 2
127.0.0.1:6379> zscore phb tj
"95"
127.0.0.1:6379> zscore phb sg
"30"
127.0.0.1:6379> zrem phb sg tj
(integer) 2
127.0.0.1:6379> zrange phb 0 -1
1) "xls"
6.5、哈希 hash

hash 是一个string类型的字段(field)和值(value)的映射表,Redis 中每个 hash 可以存储 2^32 -1 键值对,类似于字典,存放了多个k/v 对,hash特别适合用于存储对象场景

127.0.0.1:6379> hset 9527 name sg age 30
(integer) 2
127.0.0.1:6379> type 9527
hash
127.0.0.1:6379> hgetall 9527
1) "name"
2) "sg"
3) "age"
4) "30"
#增加字段
127.0.0.1:6379> hset 9527 gender man
(integer) 1
127.0.0.1:6379> hgetall 9527
1) "name"
2) "sg"
3) "age"
4) "30"
5) "gender"
6) "man"
127.0.0.1:6379> hget 9527 name
"sg"
127.0.0.1:6379> hget 9527 gender
"man"
127.0.0.1:6379> hmget 9527 age gender    #获取多个值
1) "30"
2) "man"
127.0.0.1:6379> hdel 9527 age
(integer) 1
127.0.0.1:6379> hget 9527 age
(nil)
127.0.0.1:6379> hmset 1024 name xwz age 18 city changzhou
OK
127.0.0.1:6379> hgetall 1024
1) "name"
2) "xwz"
3) "age"
4) "18"
5) "city"
6) "changzhou"
127.0.0.1:6379> hmget 1024 name city
1) "xwz"
2) "changzhou"
127.0.0.1:6379> hkeys 1024
1) "name"
2) "age"
3) "city"
127.0.0.1:6379> hvals 1024
1) "xwz"
2) "18"
3) "changzhou"
127.0.0.1:6379> hgetall 1024
1) "name"
2) "xwz"
3) "age"
4) "18"
5) "city"
6) "changzhou"
127.0.0.1:6379> del 1024
(integer) 1
127.0.0.1:6379> hmget 1024 name age
1) (nil)
2) (nil)
127.0.0.1:6379> exists 1024
(integer) 0

7、消息队列

image-271

消息队列主要分为两种,这两种模式Redis都支持

7.1、生产者消费者模式

在生产者/消费者(Producer/Consumer)模式下,上层应用接收到的外部请求后开始处理其当前步骤的操作,在执行完成后将已经完成的操作发送至指定的频道(channel,逻辑队列)当中,并由其下层的应用监听该频道并继续下一步的操作,如果其处理完成后没有下一步的操作就直接返回数据给外部请求,如果还有下一步的操作就再将任务发布到另外一个频道,由另外一个消费者继续监听和处理。此模式应用广泛

7.1.1、模式介绍

生产者消费者模式下,多个消费者同时监听一个队列,但是一个消息只能被最先抢到消息的消费者消费,即消息任务是一次性读取和处理,此模式在分布式业务架构中很常用,比较常用的消息队列软件还有RabbitMQ、Kafka、RocketMQ、ActiveMQ等。

image-272

7.1.2、队列介绍

队列当中的消息由不同的生产者写入,也会有不同的消费者取出进行消费处理,但是一个消息一定是只能被取出一次也就是被消费一次。

image-273

[root@localhost ~]# redis-cli
127.0.0.1:6379> auth centos
OK
127.0.0.1:6379> lpush channel1 msg1
(integer) 1
127.0.0.1:6379> lpush channel1 msg2
(integer) 2
127.0.0.1:6379> lpush channel1 msg3
(integer) 3
127.0.0.1:6379> lpush channel1 msg4
(integer) 4
127.0.0.1:6379> lpush channel1 msg5
(integer) 5
127.0.0.1:6379> lrange channel1 0 -1
1) "msg5"
2) "msg4"
3) "msg3"
4) "msg2"
5) "msg1"
127.0.0.1:6379> rpop channel1
"msg1"
127.0.0.1:6379> rpop channel1
"msg2"
127.0.0.1:6379> rpop channel1
"msg3"
127.0.0.1:6379> rpop channel1
"msg4"
127.0.0.1:6379> rpop channel1
"msg5"
127.0.0.1:6379> rpop channel1
(nil)
7.2、发布者订阅模式
7.2.1、模式简介

在发布者订阅者模式下,发布者将消息发布到指定的channel里面,凡是监听该channel的消费者都会收到同样的一份消息,这种模式类似于是收音机的广播模式,即凡是收听某个频道的听众都会收到主持人发布的相同的消息内容。此模式常用于群聊天、群通知、群公告等场景

image-274

127.0.0.1:6379> subscribe channel1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1
127.0.0.1:6379> publish channel1 test1
(integer) 1   #订阅者个数
127.0.0.1:6379> publish channel1 test2
(integer) 1
127.0.0.1:6379> subscribe channel1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1
1) "message"
2) "channel1"
3) "test1"
1) "message"
2) "channel1"
3) "test2"
127.0.0.1:6379> subscribe channel1 channel2
127.0.0.1:6379> psubscribe *   #支持通配符*
127.0.0.1:6379> PSUBSCRIBE chann*   #匹配订阅多个频道
127.0.0.1:6379> unsubscribe channel1
1) "unsubscribe"
2) "channel1"
3) (integer) 0

8、redis 主从复制

虽然Redis可以实现单机的数据持久化,但无论是RDB也好或者AOF也好,都解决不了单点宕机问题,即一旦单台 redis服务器本身出现系统故障、硬件故障等问题后,就会直接造成数据的丢失

此外,单机的性能也是有极限的,因此需要使用另外的技术来解决单点故障和性能扩展的问题。

image-275

8.1、redis 主从复制架构

主从模式(master/slave),可以实现Redis数据的跨主机备份。

程序端连接到高可用负载的VIP,然后连接到负载服务器设置的Redis后端real server,此模式不需要在程序里面配置Redis服务器的真实IP地址,当后期Redis服务器IP地址发生变更只需要更改redis 相应的后端real server即可, 可避免更改程序中的IP地址设置。

8.2、主从复制实现

Redis Slave 也要开启持久化并设置和master同样的连接密码,因为后期slave会有提升为master的可能,Slave 端切换master同步后会丢失之前的所有数据,而通过持久化可以恢复数据

一旦某个Slave成为一个master的slave,Redis Slave服务会清空当前redis服务器上的所有数据并将master的数据导入到自己的内存,但是如果只是断开同步关系后,则不会删除当前已经同步过的数据。

image-276

当配置Redis复制功能时,强烈建议打开主服务器的持久化功能。否则的话,由于延迟等问题,部署的服务应该要避免自动启动。

  1. 假设节点A为主服务器,并且关闭了持久化。并且节点B和节点c从节点A复制数据
  2. 节点A崩溃,然后由自动拉起服务重启了节点A.由于节点A的持久化被关闭了,所以重启之后没有任何数据
  3. 节点B和节点c将从节点A复制数据,但是A的数据是空的,于是就把自身保存的数据副本删除。

在关闭主服务器上的持久化,并同时开启自动拉起进程的情况下,即便使用Sentinel来实现Redis的高可用性,也是非常危险的。因为主服务器可能拉起得非常快,以至于Sentinel在配置的心跳时间间隔内没有检测到主服务器已被重启,然后还是会执行上面的数据丢失的流程。无论何时,数据安全都是极其重要的,所以应该禁止主服务器关闭持久化的同时自动启动。

8.2.1、命令行配置
[root@master ~]# redis-cli -a centos
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:75166c08ad870d6eaa5c27e003bc2b36488a3cae
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379> set key1 v1-master
OK
127.0.0.1:6379> keys *
1) "key1"
127.0.0.1:6379> get key1
"v1-master"
[root@slave1 ~]# redis-cli -a centos --no-auth-warning
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:1a274a8475450314d9ebb232717fa6cc3720e62f
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379> set key1 v1-slave1
OK
127.0.0.1:6379> keys *
1) "key1"
127.0.0.1:6379> get key1
"v1-slave1"
[root@slave2 ~]# redis-cli -a centos --no-auth-warning
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:2b8d846c9f01eea950100d75caf1e8e3600a8d4a
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379> set key1 v1-slave2
OK
127.0.0.1:6379> keys *
1) "key1"
127.0.0.1:6379> get key1
"v1-slave2"
127.0.0.1:6379> replicaof 192.168.175.10 6379
# 仍可使用SLAVEOF MasterIP Port
OK
127.0.0.1:6379> config set masterauth centos
#在slave上设置master的密码,才可以同步
OK
127.0.0.1:6379> info replication
# Replication
role:slave   #角色变为slave
master_host:192.168.175.10 #指向master
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:0
master_link_down_since_seconds:1625794777
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:1a274a8475450314d9ebb232717fa6cc3720e62f
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379> get key1
# 查看数据是否同步成功
"v1-master"
* 在slave2上
127.0.0.1:6379> replicaof 192.168.175.10 6379
OK
127.0.0.1:6379> config set masterauth centos
OK
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.175.10
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:0
master_link_down_since_seconds:1625794971
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:2b8d846c9f01eea950100d75caf1e8e3600a8d4a
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379> get key1
"v1-master"
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.175.30,port=6379,state=online,offset=224,lag=1
slave1:ip=192.168.175.20,port=6379,state=online,offset=224,lag=0
master_replid:d6f1142f22e456f4e4093fcb81648cab127ebc7f
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:224
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:224
127.0.0.1:6379> replicaof no one
OK
127.0.0.1:6379> info replication
# Replication
role:master        #角色变回了master
connected_slaves:0
master_replid:9507932f3addde3a76b51c5d6ecbb5daac398caa
master_replid2:f68659a5c6e8381dbbdd6841f5b89d1a60157af8
master_repl_offset:1064
second_repl_offset:1065
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:1064
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
8.2.2、同步日志
[root@master ~]# tail /apps/redis/log/redis_6379.log
6815:M 09 Jul 2021 09:44:03.398 * Synchronization with replica 192.168.175.20:6379 succeeded
6815:M 09 Jul 2021 09:53:26.312 # Connection with replica 192.168.175.20:6379 lost.
6815:M 09 Jul 2021 09:54:04.298 * Replica 192.168.175.20:6379 asks for synchronization
6815:M 09 Jul 2021 09:54:04.298 * Partial resynchronization not accepted: Replication ID mismatch (Replica asked for '2feb7cc9c49ff2a52f4749a0cd26218d9ca6577a', my replication IDs are 'd6f1142f22e456f4e4093fcb81648cab127ebc7f' and '0000000000000000000000000000000000000000')
6815:M 09 Jul 2021 09:54:04.298 * Starting BGSAVE for SYNC with target: disk
6815:M 09 Jul 2021 09:54:04.299 * Background saving started by pid 7234
7234:C 09 Jul 2021 09:54:04.300 * DB saved on disk
7234:C 09 Jul 2021 09:54:04.300 * RDB: 0 MB of memory used by copy-on-write
6815:M 09 Jul 2021 09:54:04.394 * Background saving terminated with success
6815:M 09 Jul 2021 09:54:04.395 * Synchronization with replica 192.168.175.20:6379 succeeded
[root@slave2 ~]# tail /apps/redis/log/redis_6379.log
6838:S 09 Jul 2021 09:44:02.914 * MASTER <-> REPLICA sync started
6838:S 09 Jul 2021 09:44:02.914 * Non blocking connect for SYNC fired the event.
6838:S 09 Jul 2021 09:44:02.915 * Master replied to PING, replication can continue...
6838:S 09 Jul 2021 09:44:02.916 * Trying a partial resynchronization (request 2b8d846c9f01eea950100d75caf1e8e3600a8d4a:1).
6838:S 09 Jul 2021 09:44:02.918 * Full resync from master: d6f1142f22e456f4e4093fcb81648cab127ebc7f:0
6838:S 09 Jul 2021 09:44:02.918 * Discarding previously cached master state.
6838:S 09 Jul 2021 09:44:02.992 * MASTER <-> REPLICA sync: receiving 196 bytes from master
6838:S 09 Jul 2021 09:44:02.993 * MASTER <-> REPLICA sync: Flushing old data
6838:S 09 Jul 2021 09:44:02.993 * MASTER <-> REPLICA sync: Loading DB in memory
6838:S 09 Jul 2021 09:44:02.993 * MASTER <-> REPLICA sync: Finished with success
8.2.3、修改slave节点配置文件
[root@slave1 ~]# grep replicaof /apps/redis/etc/redis.conf
# Master-Replica replication. Use replicaof to make a Redis instance a copy of
# replicaof <masterip> <masterport>
[root@slave1 ~]# echo "replicaof 192.168.175.10 6379" >> /apps/redis/etc/redis.conf
[root@slave1 ~]# echo "masterauth centos" >> /apps/redis/etc/redis.conf
[root@slave1 ~]# systemctl restart redis
[root@slave2 ~]# echo "replicaof 192.168.175.10 6379" >> /apps/redis/etc/redis.conf
[root@slave2 ~]# echo "masterauth centos" >> /apps/redis/etc/redis.conf
[root@slave2 ~]# systemctl restart redis
# 在master上
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.175.20,port=6379,state=online,offset=4452,lag=1
slave1:ip=192.168.175.30,port=6379,state=online,offset=4452,lag=1
# 在slave1上
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.175.10
master_port:6379
127.0.0.1:6379> get key1
"v1-master"
#在slave2上
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.175.10
master_port:6379
127.0.0.1:6379> get key1
"v1-master"
# 在master上停止服务
[root@master ~]# systemctl stop redis
#在slave上可以看到
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.175.10
master_port:6379
master_link_status:down  #显示down,表示无法连接master
[root@slave1 ~]# tail /apps/redis/log/redis_6379.log
15506:S 09 Jul 2021 10:41:56.295 # Error condition on socket for SYNC: Connection refused
15506:S 09 Jul 2021 10:41:57.311 * Connecting to MASTER 192.168.175.10:6379
15506:S 09 Jul 2021 10:41:57.311 * MASTER <-> REPLICA sync started
15506:S 09 Jul 2021 10:41:57.311 # Error condition on socket for SYNC: Connection refused
15506:S 09 Jul 2021 10:41:58.333 * Connecting to MASTER 192.168.175.10:6379
15506:S 09 Jul 2021 10:41:58.333 * MASTER <-> REPLICA sync started
15506:S 09 Jul 2021 10:41:58.334 # Error condition on socket for SYNC: Connection refused
15506:S 09 Jul 2021 10:41:59.359 * Connecting to MASTER 192.168.175.10:6379
15506:S 09 Jul 2021 10:41:59.359 * MASTER <-> REPLICA sync started
15506:S 09 Jul 2021 10:41:59.360 # Error condition on socket for SYNC: Connection refused
127.0.0.1:6379> set key1 v1-slave1
(error) READONLY You cant write against a read only replica.
8.3、主从复制故障恢复
8.3.1、主从复制故障恢复过程介绍

image-277

image-278

image-279

8.3.2、主从复制故障恢复实现
[root@master ~]# systemctl stop redis
[root@slave1 ~]# redis-cli -a centos --no-auth-warning
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.175.10
master_port:6379
master_link_status:down
127.0.0.1:6379> replicaof no one
OK
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:46804bd951a74f28dbd453d4cab68ca6fbd2e6bc
master_replid2:d6f1142f22e456f4e4093fcb81648cab127ebc7f
master_repl_offset:4648
second_repl_offset:4649
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:827
repl_backlog_histlen:3822
127.0.0.1:6379> set keytest1 vtest1
OK
127.0.0.1:6379> replicaof 192.168.175.20 6379
OK
127.0.0.1:6379> config set masterauth centos
OK
127.0.0.1:6379> set key100 v100
(error) READONLY You cant write against a read only replica.
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.175.20
master_port:6379
master_link_status:up
127.0.0.1:6379> get keytest1
"vtest1"
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.175.30,port=6379,state=online,offset=4836,lag=1
8.4、实现redis的级联复制

image-280

在前面搭建好的一主一从架构中,master和slave1节点无需修改,只需要修改slave2及slave3指向slave1作为master即可

[root@slave2 ~]# redis-cli -a centos
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> REPLICAOF 192.168.175.20 6379
OK
127.0.0.1:6379> config set masterauth centos
OK
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.175.20
master_port:6379
[root@slave3 ~]# redis-cli -a centos
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> replicaof 192.168.175.20 6379
OK
127.0.0.1:6379> config set masterauth centos
OK
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.175.20
master_port:6379
# master设置key
127.0.0.1:6379> set name eagle
OK
127.0.0.1:6379> get name
"eagle"
#在slave上验证key
127.0.0.1:6379> get name
"eagle"
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.175.10
master_port:6379
master_link_status:up
master_last_io_seconds_ago:9  #最近一次与master通信已经过去多少秒
master_sync_in_progress:0  #是否正在与master通信
slave_repl_offset:799  #当前同步的偏移量
slave_priority:100  #slave优先级,master故障后优先级值越小越优先同步
slave_read_only:1
connected_slaves:2
slave0:ip=192.168.175.30,port=6379,state=online,offset=799,lag=0
slave1:ip=192.168.175.40,port=6379,state=online,offset=799,lag=0
master_replid:e262f6e6fdd1d02222afc48ade1860163e3c096b
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:799
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:799
8.5、主从复制优化
8.5.1、主从复制过程

Redis主从复制分为全量同步和增量同步

image-281

首次同步是全量同步,主从同步可以让从服务器从主服务器同步数据,而且从服务器还可再有其它的从服务器,即另外一台redis服务器可以从一台从服务器进行数据同步,redis 的主从同步是非阻塞的,master收到从服务器的psync(2.8版本之前是SYNC)命令,会fork一个子进程在后台执行bgsave命令,并将新写入的数据写入到一个缓冲区中,bgsave执行完成之后,将生成的RDB文件发送给slave,然后master再将缓冲区的内容以redis协议格式再全部发送给slave,slave 先删除旧数据,slave将收到后的RDB文件载入自己的内存,再加载所有收到缓冲区的内容 从而这样一次完整的数据同步

Redis全量复制一般发生在Slave首次初始化阶段,这时Slave需要将Master上的所有数据都复制一份。

image-282

全量同步之后再次需要同步时,从服务器只要发送当前的offset位置(等同于MySQL的binlog的位置)给主服务器,然后主服务器根据相应的位置将之后的数据(包括写在缓冲区的积压数据)发送给从服务器,其再次保存到其内存即可。

image-283

image-284

image-285

image-286

#复制缓冲区大小,建议要设置足够大
rep-backlog-size 1mb
#Redis同时也提供了当没有slave需要同步的时候,多久可以释放环形队列:
repl-backlog-ttl 3600

image-287

image-288

8.5.2、主从同步优化配置

Redis在2.8版本之前没有提供增量部分复制的功能,当网络闪断或者slave Redis重启之后会导致主从之间的全量同步,即从2.8版本开始增加了部分复制的功能。

repl-diskless-sync no # 是否使用无盘同步RDB文件,默认为no,no为不使用无盘,需要将RDB文件保存到磁盘后再发送给slave,yes为支持无盘,支持无盘就是RDB文件不需要保存至本地磁盘,而且直接通过socket文件发送给slave
repl-diskless-sync-delay 5 #diskless时复制的服务器等待的延迟时间
repl-ping-slave-period 10 #slave端向server端发送ping的时间间隔,默认为10秒
repl-timeout 60 #设置主从ping连接超时时间,超过此值无法连接,master_link_status显示为down,并记录错误日志
repl-disable-tcp-nodelay no #是否启用TCP_NODELAY,如设置成yes,则redis会合并小的TCP包从而节省带宽, 但会增加同步延迟(40ms),造成master与slave数据不一致,假如设置成no,则redismaster会立即发送同步数据,没有延迟,yes关注性能,no关注redis服务中的数据一致性
repl-backlog-size 1mb #master的写入数据缓冲区,用于记录自上一次同步后到下一次同步过程中间的写入命令,计算公式:repl-backlog-size = 允许从节点最大中断时长 * 主实例offset每秒写入量,比如master每秒最大写入64mb,最大允许60秒,那么就要设置为64mb*60秒=3840MB(3.8G),建议此值是设置的足够大
repl-backlog-ttl 3600 #如果一段时间后没有slave连接到master,则backlog size的内存将会被释放。如果值为0则表示永远不释放这部份内存。
slave-priority 100 #slave端的优先级设置,值是一个整数,数字越小表示优先级越高。当master故障时将会按照优先级来选择slave端进行恢复,如果值设置为0,则表示该slave永远不会被选择。
min-replicas-to-write 1 #设置一个master的可用slave不能少于多少个,否则master无法执行写
min-slaves-max-lag 20 #设置至少有上面数量的slave延迟时间都大于多少秒时,master不接收写操作(拒绝写入)
8.6、常见主从复制故障汇总
8.6.1、master密码不对

即配置的master密码不对,导致验证不通过而无法建立主从同步关系。

[root@slave1 ~]#tail -f /var/log/redis/redis.log
3939:S 24 Oct 2020 16:13:57.552 # Server initialized
3939:S 24 Oct 2020 16:13:57.552 * DB loaded from disk: 0.000 seconds
3939:S 24 Oct 2020 16:13:57.552 * Ready to accept connections
3939:S 24 Oct 2020 16:13:57.554 * Connecting to MASTER 10.0.0.18:6379
3939:S 24 Oct 2020 16:13:57.554 * MASTER <-> REPLICA sync started
3939:S 24 Oct 2020 16:13:57.556 * Non blocking connect for SYNC fired the event.
3939:S 24 Oct 2020 16:13:57.558 * Master replied to PING, replication can continue...
3939:S 24 Oct 2020 16:13:57.559 # Unable to AUTH to MASTER: -ERR invalid password
8.6.2、Redis版本不一致

不同的redis 大版本之间存在兼容性问题,比如:3和4,4和5之间,因此各master和slave之间必须保持版本一致

image-289

8.6.3、无法远程连接

在开启了安全模式情况下,没有设置bind地址或者密码

[root@slave1 ~]#redis-cli -h 192.168.175.10
192.168.175.10:6379> KEYS *
(error) DENIED Redis is running in protected mode because protected mode is enabled
8.6.4、配置不一致

主从节点的maxmemory不一致,主节点内存大于从节点内存,主从复制可能丢失数据

rename-command 命令不一致,如在主节点定义了fushall,flushdb,从节点没定义,结果执行flushdb,不同步

9、redis 哨兵(Sentinel)

9.1、redis 集群介绍

主从架构无法实现master和slave角色的自动切换,即当master出现redis服务异常、主机断电、磁盘损坏等问题导致master无法使用,而redis主从复制无法实现自动的故障转移(将slave 自动提升为新master),需要手动修改环境配置,才能切换到slave redis服务器,另外也无法横向扩展Redis服务的并行写入性能,当单台Redis服务器性能无法满足业务写入需求的时候,也需要解决以上的两个核心问题

  1. master和slave角色的无缝切换,让业务无感知从而不影响业务使用
  2. 可横向动态扩展Redis服务器,从而实现多台服务器并行写入以实现更高并发的目的。

Redis 集群实现方式

9.2、哨兵 (Sentinel) 工作原理
9.2.1、sentinel 架构和故障转移

image-290

image-291

Sentinel 进程是用于监控redis集群中Master主服务器工作的状态,在Master主服务器发生故障的时候,可以实现Master和Slave服务器的切换,保证系统的高可用,此功能在redis2.6+的版本已引用,Redis的哨兵模式到了2.8版本之后就稳定了下来。一般在生产环境也建议使用Redis的2.8版本的以后版本

哨兵(Sentinel) 是一个分布式系统,可以在一个架构中运行多个哨兵(sentinel) 进程,这些进程使用流言协议(gossip protocols)来接收关于Master主服务器是否下线的信息,并使用投票协议(Agreement Protocols)来决定是否执行自动故障迁移,以及选择哪个Slave作为新的Master

每个哨兵(Sentinel)进程会向其它哨兵(Sentinel)、Master、Slave定时发送消息,以确认对方是否”活”着,如果发现对方在指定配置时间(此项可配置)内未得到回应,则暂时认为对方已离线,也就是所谓的”主观认为宕机” (主观:是每个成员都具有的独自的而且可能相同也可能不同的意识),英文名称:Subjective Down,简称SDOWN

有主观宕机,对应的有客观宕机。当“哨兵群”中的多数Sentinel进程在对Master主服务器做出SDOWN的判断,并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后,得出的Master Server下线判断,这种方式就是“客观宕机”(客观:是不依赖于某种意识而已经实际存在的一切事物),英文名称是:Objectively Down, 简称 ODOWN

通过一定的vote算法,从剩下的slave从服务器节点中,选一台提升为Master服务器节点,然后自动修改相关配置,并开启故障转移(failover)

Sentinel 机制可以解决master和slave角色的自动切换问题,但单个 Master 的性能瓶颈问题无法解决,类似于MySQL中的MHA功能

Redis Sentinel中的Sentinel节点个数应该为大于等于3且最好为奇数

客户端初始化时连接的是Sentinel节点集合,不再是具体的Redis节点,但Sentinel只是配置中心不是代理。

Redis Sentinel 节点与普通redis 没有区别,要实现读写分离依赖于客户端程序

redis 3.0 之前版本中,生产环境一般使用哨兵模式,但3.0后推出redis cluster功能后,可以支持更大规模的生产环境

9.2.2、sentinel中的三个定时任务
9.3、实现哨兵

image-292

# 在所有主从节点执行,这里以master为例
[root@master ~]# vim /apps/redis/etc/redis.conf
bind 0.0.0.0
masterauth "centos"
requirepass centos
[root@master ~]# echo -e "net.core.somaxconn = 1024\nvm.overcommit_memory = 1" >> /etc/sysctl.conf

net.core.somaxconn: 这个参数控制系统同时监听的最大连接数。将其设置为 1024 可以提高系统的并发能力。
vm.overcommit_memory: 这个参数控制内存overcommit策略。设置为 1 表示允许overcommit,即允许进程申请比实际可用内存更多的内存。这在一些内存紧张的场景下会很有用。

[root@master ~]# sysctl -p
net.core.somaxconn = 1024
vm.overcommit_memory = 1
net.core.somaxconn = 1024
vm.overcommit_memory = 1
[root@master ~]# echo never > /sys/kernel/mm/transparent_hugepage/enabled
这条命令是用来禁用 Transparent Huge Pages (THP) 功能。
THP 是一种 Linux 内核特性,可以动态合并内存页面以减少页表的数量,从而提高性能。但是在某些场景下它可能会带来副作用,所以需要禁用。
[root@master ~]# echo "echo never > /sys/kernel/mm/transparent_hugepage/enabled" >> /etc/rc.d/rc.local
[root@master ~]# chmod +x /etc/rc.d/rc.local
[root@master ~]# systemctl restart redis
#在所有从节点执行,这里以slave1为例
[root@slave1 ~]# echo "replicaof 192.168.175.10 6379" >> /apps/redis/etc/redis.conf
[root@slave1 ~]# systemctl restart redis
[root@master ~]# redis-cli
127.0.0.1:6379> auth centos
OK
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.175.20,port=6379,state=online,offset=112,lag=0
slave1:ip=192.168.175.30,port=6379,state=online,offset=112,lag=0
master_replid:ec21203b0f3034282e18d6532055379b3de45f82
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:112
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:112
# 如果是编译安装,在源码目录有sentinel.conf,复制到安装目录即可
[root@master ~]# cp redis-5.0.9/sentinel.conf /apps/redis/etc/
[root@master ~]# grep -Ev "^(#|$)" /apps/redis/etc/sentinel.conf
port 26379
daemonize no
pidfile /var/run/redis-sentinel.pid
logfile ""
dir /tmp  #工作目录
sentinel monitor mymaster 127.0.0.1 6379 2
#指定当前mymaster集群中master服务器的地址和端口
#2为法定人数限制(quorum),即有几个sentinel认为master down了就进行故障转移,一般此值是所有sentinel节点(一般总数是>=3的 奇数,如:3,5,7等)的一半以上的整数值,比如,总数是3,即3/2=1.5,取整为2,是master的ODOWN客观下线的依据
sentinel auth-pass <master-name> <password>
#mymaster集群中master的密码,注意此行要在上面行的下面
sentinel down-after-milliseconds mymaster 30000
#(SDOWN)判断mymaster集群中所有节点的主观下线的时间,单位:毫秒,建议3000
sentinel parallel-syncs mymaster 1
#发生故障转移后,同时向新master同步数据的slave数量,数字越小总同步时间越长,但可以减轻新master的负载压力
sentinel failover-timeout mymaster 180000
#所有slaves指向新的master所需的超时时间,单位:毫秒
sentinel deny-scripts-reconfig yes
#禁止修改脚本
[root@master ~]# grep -Ev "^(#|$)" /apps/redis/etc/sentinel.conf
port 26379
daemonize no
pidfile /apps/redis/run/redis-sentinel.pid
logfile "/apps/redis/log/sentinel.log"
dir /tmp
sentinel monitor mymaster 192.168.88.136 6379 2
sentinel auth-pass mymaster centos
sentinel down-after-milliseconds mymaster 3000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
sentinel deny-scripts-reconfig yes
[root@master ~]# chown redis.redis /apps/redis/etc/sentinel.conf
9.3.1、启动哨兵

三台哨兵服务器都要启动

# 添加哨兵服务
[root@master ~]# cat << EOF > /lib/systemd/system/redis-sentinel.service
[Unit]
Description=Redis Sentinel
After=network.target
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/apps/redis/bin/redis-sentinel /apps/redis/etc/sentinel.conf --supervised systemd
ExecStop=/usr/libexec/redis-shutdown redis-sentinel
Type=notify
User=redis
Group=redis
RuntimeDirectory=redis
RuntimeDirectoryMode=0755
[Install]
WantedBy=multi-user.target
EOF
[root@master ~]# systemctl daemon-reload
# 确保每个哨兵主机myid不同
#在master上
[root@master ~]# systemctl enable --now redis-sentinel
[root@master ~]# grep myid /apps/redis/etc/sentinel.conf
sentinel myid 4e119d69b1b0bee2a660bcb3162b6116907672e3
# 在slave1上
[root@slave1 ~]# systemctl enable --now redis-sentinel
[root@slave1 ~]# grep myid /apps/redis/etc/sentinel.conf
sentinel myid 946a5a7cb7ab39f46043e2db4f2f41ca1a04ec45
#在slave2上
[root@slave2 ~]# systemctl enable --now redis-sentinel
[root@slave2 ~]# grep myid /apps/redis/etc/sentinel.conf
sentinel myid 8d40b041b85bd197b33a34358169ee7b08de0ecc
# 以下内容在服务启动后自动生成,不需要修改
[root@master ~]# grep mymaster /apps/redis/etc/sentinel.conf
# sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
sentinel monitor mymaster 192.168.175.10 6379 2
sentinel down-after-milliseconds mymaster 3000
sentinel auth-pass mymaster centos
# sentinel notification-script mymaster /var/redis/notify.sh
# sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
sentinel config-epoch mymaster 0
# instead of the normal ones. For example if the master "mymaster", and the
# SENTINEL rename-command mymaster CONFIG GUESSME
# SENTINEL rename-command mymaster CONFIG CONFIG
sentinel leader-epoch mymaster 0
sentinel known-replica mymaster 192.168.175.30 6379
sentinel known-replica mymaster 192.168.175.20 6379
sentinel known-sentinel mymaster 192.168.175.30 26379 8d40b041b85bd197b33a34358169ee7b08de0ecc
sentinel known-sentinel mymaster 192.168.175.20 26379 946a5a7cb7ab39f46043e2db4f2f41ca1a04ec45
9.3.2、验证哨兵端口
[root@master ~]# ss -ntl
State      Recv-Q Send-Q       Local Address:Port                      Peer Address:Port
LISTEN     0      511                      *:26379                                *:*
9.3.3、查看哨兵日志
[root@master ~]# tail /apps/redis/log/sentinel.log
18077:X 10 Jul 2021 11:23:34.486 # Current maximum open files is 4096. maxclients has been reduced to 4064 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'.
18077:X 10 Jul 2021 11:23:34.487 * Running mode=sentinel, port=26379.
18077:X 10 Jul 2021 11:23:34.487 # Sentinel ID is 4e119d69b1b0bee2a660bcb3162b6116907672e3
18077:X 10 Jul 2021 11:23:34.487 # +monitor master mymaster 192.168.175.10 6379 quorum 2
18077:X 10 Jul 2021 11:23:37.553 # +sdown slave 192.168.175.30:6379 192.168.175.30 6379 @ mymaster 192.168.175.10 6379
18077:X 10 Jul 2021 11:23:37.553 # +sdown slave 192.168.175.20:6379 192.168.175.20 6379 @ mymaster 192.168.175.10 6379
18077:X 10 Jul 2021 11:33:18.954 * +sentinel sentinel 946a5a7cb7ab39f46043e2db4f2f41ca1a04ec45 192.168.175.20 26379 @ mymaster 192.168.175.10 6379
18077:X 10 Jul 2021 11:33:22.007 # +sdown sentinel 946a5a7cb7ab39f46043e2db4f2f41ca1a04ec45 192.168.175.20 26379 @ mymaster 192.168.175.10 6379
18077:X 10 Jul 2021 11:33:48.674 * +sentinel sentinel 8d40b041b85bd197b33a34358169ee7b08de0ecc 192.168.175.30 26379 @ mymaster 192.168.175.10 6379
18077:X 10 Jul 2021 11:33:51.763 # +sdown sentinel 8d40b041b85bd197b33a34358169ee7b08de0ecc 192.168.175.30 26379 @ mymaster 192.168.175.10 6379
9.3.4、当前sentinel状态

在sentinel状态中尤其是最后一行,涉及到masterIP是多少,有几个slave,有几个sentinels,必须是符合全部服务器数量

[root@master ~]# redis-cli -p 26379
127.0.0.1:26379> info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.175.10:6379,slaves=2,sentinels=3
9.3.5、停止Redis Master测试故障转移
[root@master ~]# killall redis-server
[root@slave1 ~]# redis-cli -a centos -p 26379
127.0.0.1:26379> INFO sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.175.30:6379,slaves=2,sentinels=3
[root@slave1 ~]# grep "^replicaof" /apps/redis/etc/redis.conf
replicaof 192.168.175.30 6379
[root@slave1 ~]# grep monitor /apps/redis/etc/sentinel.conf
sentinel monitor mymaster 192.168.175.30 6379 2
[root@slave2 ~]# redis-cli -a centos --no-auth-warning
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.175.20,port=6379,state=online,offset=289741,lag=1
slave1:ip=192.168.175.10,port=6379,state=online,offset=289741,lag=1
master_replid:de66459a2ecc3f0652fcefc6be633855ccda5c1e
master_replid2:ec21203b0f3034282e18d6532055379b3de45f82
master_repl_offset:289741
second_repl_offset:214083
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:43
repl_backlog_histlen:289699
9.3.6、恢复故障的原master重新加入redis集群
[root@master ~]# systemctl start redis
[root@master ~]#grep "^replicaof" /apps/redis/etc/redis.conf
replicaof 192.168.175.30 6379
[root@master ~]# redis-cli -a centos --no-auth-warning
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.175.30
master_port:6379
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:306155
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:de66459a2ecc3f0652fcefc6be633855ccda5c1e
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:306155
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:261652
repl_backlog_histlen:44504
9.3.7、sentinel 运维
# 指定优先级,值越小sentinel会优先将之选为新的master,默为值为100
[root@master ~]# vim /apps/redis/etc/redis.conf
replica-priority 10
[root@master ~]# systemctl restart redis

[root@master ~]# redis-cli -a centos -p 26379
127.0.0.1:26379> sentinel failover mymaster
OK
127.0.0.1:26379> info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.175.10:6379,slaves=2,sentinels=3
9.3.8、python连接redis

image-293

image-294

image-295

image-296

python 连接Sentinel哨兵

[root@master ~]# yum -y install python3 python3-redis
[root@master ~]# vim sentinel_test.py
#!/usr/bin/python3
import redis
from redis.sentinel import Sentinel
#连接哨兵服务器(主机名也可以用域名)
sentinel = Sentinel([('192.168.175.10', 26379),
             ('192.168.175.20', 26379),
                     ('192.168.175.30', 26379)],
                    socket_timeout=0.5)
redis_auth_pass = 'centos'
#mymaster 是配置哨兵模式的redis集群名称,此为默认值,实际名称按照个人部署案例来填写
#获取主服务器地址
master = sentinel.discover_master('mymaster')
print(master)
#获取从服务器地址
slave = sentinel.discover_slaves('mymaster')
print(slave)
#获取主服务器进行写入
master = sentinel.master_for('mymaster', socket_timeout=0.5,
password=redis_auth_pass, db=0)
w_ret = master.set('name', 'eagle')
#输出:True
#获取从服务器进行读取(默认是round-roubin)
slave = sentinel.slave_for('mymaster', socket_timeout=0.5,
password=redis_auth_pass, db=0)
r_ret = slave.get('name')
print(r_ret)
#输出:eagle
[root@master ~]# python3 sentinel_test.py
('192.168.175.10', 6379)
[('192.168.175.20', 6379), ('192.168.175.30', 6379)]
b'eagle'

10、Redis Cluster

image-297

10.1、Redis Cluster 工作原理

在哨兵sentinel机制中,可以解决redis高可用问题,即当master故障后可以自动将slave提升为master,从而可以保证redis服务的正常使用,但是无法解决redis单机写入的瓶颈问题,即单机redis写入性能受限于单机的内存大小、并发数量、网卡速率等因素。

redis 3.0版本之后推出了无中心架构的redis cluster机制,在无中心的redis集群当中,其每个节点保存当前节点数据和整个集群状态,每个节点都和其他所有节点连接

image-298

10.1.1、Redis cluster 基本架构

假如三个主节点分别是:A,B,C三个节点,采用哈希槽(hash slot)的方式来分配16384个slot的活,它们三个节点分别承担的slot 区间可以是:

image-299

10.1.2、Redis cluster 主从架构

Redis cluster的架构虽然解决了并发的问题,但是又引入了一个新的问题,每个Redis master的高可用如何解决?

那就是对每个master 节点都实现主从复制,从而实现 redis 高可用性

image-300

10.1.3、Redis Cluster 部署架构说明

image-301

image-302

10.1.4、部署方式介绍
10.2、原生命令手动部署
10.2.1、原生命令
10.2.2、实战案例: 利用原生命令手动部署redis cluster

image-303

#在克隆之前将如下操作在模板上操作完成
[root@localhost ~]# vim redis_install.sh
#参照一键编译安装redis脚本
[root@localhost ~]# . redis_install.sh
# 开始编译安装redis
firewall-cmd --add-port=1-65535/tcp --permanent
firewall-cmd --add-port=1-65535/udp --permanent
firewall-cmd --reload
hostnamectl set-hostname master1
[root@master1 ~]# vim /apps/redis/etc/redis.conf
cluster-enabled yes
[root@master1 ~]# systemctl restart redis
# 在任意一节点上和其它所有节点进行meet通信,以master1为例
[root@master1 ~]# redis-cli -h 192.168.175.10 -a centos --no-auth-warning cluster meet 192.168.175.11 6379
OK
[root@master1 ~]# redis-cli -h 192.168.175.10 -a centos --no-auth-warning cluster meet 192.168.175.20 6379
OK
[root@master1 ~]# redis-cli -h 192.168.175.10 -a centos --no-auth-warning cluster meet 192.168.175.21 6379
OK
[root@master1 ~]# redis-cli -h 192.168.175.10 -a centos --no-auth-warning cluster meet 192.168.175.30 6379
OK
[root@master1 ~]# redis-cli -h 192.168.175.10 -a centos --no-auth-warning cluster meet 192.168.175.31 6379
OK
#可以看到所有节点之间可以相互连接通信,以master1为例
[root@master1 ~]# redis-cli -h 192.168.175.10 -a centos --no-auth-warning cluster nodes
76da144ef9ea65664f9ba5ac431ed9e26b07b132 192.168.175.31:6379@16379 master - 0 1625909926530 4 connected
fae9fe337678873ec0f3e0a44c39bd8064c85fb0 192.168.175.30:6379@16379 master - 0 1625909925000 0 connected
5eeb5ce103710ec5108d3257fdcd019b401548d9 192.168.175.11:6379@16379 master - 0 1625909926000 3 connected
3ccb32a3e79572a16a1aa3e8188fff07121b1d1e 192.168.175.20:6379@16379 master - 0 1625909927549 2 connected
48000fa51e653b3dbf3f70829fc9142c40195ff6 192.168.175.10:6379@16379 myself,master - 0 1625909927000 1 connected
e1b815df722f7c202e28215fa23ff20913d88479 192.168.175.21:6379@16379 master - 0 1625909924486 5 connected
[root@master1 ~]# redis-cli -a centos --no-auth-warning set name eagle
(error) CLUSTERDOWN Hash slot not served
[root@master1 ~]# redis-cli -a centos --no-auth-warning cluster info
cluster_state:fail
cluster_slots_assigned:0
cluster_slots_ok:0
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:0
cluster_current_epoch:5
cluster_my_epoch:1
cluster_stats_messages_ping_sent:289
cluster_stats_messages_pong_sent:290
cluster_stats_messages_meet_sent:5
cluster_stats_messages_sent:584
cluster_stats_messages_ping_received:289
cluster_stats_messages_pong_received:294
cluster_stats_messages_meet_received:1
cluster_stats_messages_received:584
[root@master1 ~]# vim addslots.sh
#!/bin/bash
HOST=$1
PORT=$2
START=$3
END=$4
PASS=centos
for slot in `seq ${START} ${END}`;do
    echo "slot: ${slot}"
    redis-cli -h ${HOST} -p ${PORT} -a ${PASS} --no-auth-warning cluster addslots ${slot}
done
* 为三个master分配槽位,共16364/3=5461.33333,平均每个master分配5461个槽位
[root@master1 ~]# bash addslots.sh 192.168.175.10 6379 0 5461
#当master1分配完槽位后,可以看到下面信息
[root@master1 ~]# redis-cli -h 192.168.175.10 -a centos --no-auth-warning cluster info
cluster_state:fail
cluster_slots_assigned:5462
cluster_slots_ok:5462
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:1
cluster_current_epoch:5
cluster_my_epoch:1
cluster_stats_messages_ping_sent:504
cluster_stats_messages_pong_sent:500
cluster_stats_messages_meet_sent:5
cluster_stats_messages_sent:1009
cluster_stats_messages_ping_received:499
cluster_stats_messages_pong_received:509
cluster_stats_messages_meet_received:1
cluster_stats_messages_received:1009
[root@master1 ~]# redis-cli -h 192.168.175.10 -a centos --no-auth-warning cluster nodes
76da144ef9ea65664f9ba5ac431ed9e26b07b132 192.168.175.31:6379@16379 master - 0 1625910366128 4 connected
fae9fe337678873ec0f3e0a44c39bd8064c85fb0 192.168.175.30:6379@16379 master - 0 1625910366000 0 connected
5eeb5ce103710ec5108d3257fdcd019b401548d9 192.168.175.11:6379@16379 master - 0 1625910365000 3 connected
3ccb32a3e79572a16a1aa3e8188fff07121b1d1e 192.168.175.20:6379@16379 master - 0 1625910367145 2 connected
48000fa51e653b3dbf3f70829fc9142c40195ff6 192.168.175.10:6379@16379 myself,master - 0 1625910366000 1 connected 0-5461
e1b815df722f7c202e28215fa23ff20913d88479 192.168.175.21:6379@16379 master - 0 1625910368168 5 connected
# 当所有的三个master分配完槽位后,可以看到如下信息,以master1为例,在其它主机看到的信息一样
[root@master1 ~]# redis-cli -h 192.168.175.10 -a centos --no-auth-warning cluster nodes
76da144ef9ea65664f9ba5ac431ed9e26b07b132 192.168.175.31:6379@16379 master - 0 1625912355000 4 connected
3ccb32a3e79572a16a1aa3e8188fff07121b1d1e 192.168.175.20:6379@16379 master - 0 1625912353000 2 connected 5462-10922
fae9fe337678873ec0f3e0a44c39bd8064c85fb0 192.168.175.30:6379@16379 master - 0 1625912355795 0 connected 10923-16383
5eeb5ce103710ec5108d3257fdcd019b401548d9 192.168.175.11:6379@16379 master - 0 1625912354000 3 connected
48000fa51e653b3dbf3f70829fc9142c40195ff6 192.168.175.10:6379@16379 myself,master - 0 1625912351000 1 connected 0-5461
e1b815df722f7c202e28215fa23ff20913d88479 192.168.175.21:6379@16379 master - 0 1625912354000 5 connected
# 通过上面的cluster nodes 查看master的ID信息,执行下面操作,将对应的slave 指定相应的master节点,实现三对主从节点
[root@master1 ~]# redis-cli -h 192.168.175.11 -a centos --no-auth-warning cluster replicate 48000fa51e653b3dbf3f70829fc9142c40195ff6
OK
[root@master1 ~]# redis-cli -h 192.168.175.21 -a centos --no-auth-warning cluster replicate 3ccb32a3e79572a16a1aa3e8188fff07121b1d1e
OK
[root@master1 ~]# redis-cli -h 192.168.175.31 -a centos --no-auth-warning cluster replicate fae9fe337678873ec0f3e0a44c39bd8064c85fb0
OK
[root@master1 ~]# redis-cli -h 192.168.175.10 -a centos --no-auth-warning cluster slots
1) 1) (integer) 5462
   2) (integer) 10922
   3) 1) "192.168.175.20"
      2) (integer) 6379
      3) "3ccb32a3e79572a16a1aa3e8188fff07121b1d1e"
   4) 1) "192.168.175.21"
      2) (integer) 6379
      3) "e1b815df722f7c202e28215fa23ff20913d88479"
2) 1) (integer) 10923
   2) (integer) 16383
   3) 1) "192.168.175.30"
      2) (integer) 6379
      3) "fae9fe337678873ec0f3e0a44c39bd8064c85fb0"
   4) 1) "192.168.175.31"
      2) (integer) 6379
      3) "76da144ef9ea65664f9ba5ac431ed9e26b07b132"
3) 1) (integer) 0
   2) (integer) 5461
   3) 1) "192.168.175.10"
      2) (integer) 6379
      3) "48000fa51e653b3dbf3f70829fc9142c40195ff6"
   4) 1) "192.168.175.11"
      2) (integer) 6379
      3) "5eeb5ce103710ec5108d3257fdcd019b401548d9"
[root@master1 ~]# redis-cli -c -h 192.168.175.10 -a centos --no-auth-warning set name eagle
OK
[root@master1 ~]# redis-cli -c -h 192.168.175.10 -a centos --no-auth-warning get name
"eagle"
[root@master1 ~]# redis-cli -h 192.168.175.10 -a centos --no-auth-warning get name
(error) MOVED 5798 192.168.175.20:6379
[root@master1 ~]# redis-cli -h 192.168.175.20 -a centos --no-auth-warning get name
"eagle"
[root@master1 ~]# redis-cli -h 192.168.175.30 -a centos --no-auth-warning get name
(error) MOVED 5798 192.168.175.20:6379
10.3、实战案例:基于Redis 5 的 redis cluster 部署

官方文档:https://redis.io/topics/cluster-tutorial

# cluste选项帮助
[root@master1 ~]# redis-cli --cluster help
Usage: redis-trib <command> <options> <arguments ...>
create      host1:port1 ... hostN:portN #创建集群
            --replicas <arg> #指定master的副本数量
check       host:port #检查集群信息
info        host:port #查看集群主机信息
fix         host:port #修复集群
            --timeout <arg>
reshard     host:port #在线热迁移集群指定主机的slots数据
            --from <arg>
            --to <arg>
            --slots <arg>
            --yes
            --timeout <arg>
            --pipeline <arg>
rebalance   host:port #平衡集群中各主机的slot数量
            --weight <arg>
            --auto-weights
            --use-empty-masters
            --timeout <arg>
            --simulate
            --pipeline <arg>
            --threshold <arg>
add-node    new_host:new_port existing_host:existing_port #添加主机到集群
            --slave
            --master-id <arg>
del-node    host:port node_id #删除主机
set-timeout host:port milliseconds #设置节点的超时时间
call        host:port command arg arg .. arg #在集群上的所有节点上执行命令
import      host:port #导入外部redis服务器的数据到当前集群
            --from <arg>
            --copy
            --replace
help        (show this help)
For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.
10.3.1、创建集群
# redis-cli --cluster-replicas 1 表示每个master对应一个slave节点
[root@master1 ~]# redis-cli -a centos --no-auth-warning --cluster create \
192.168.175.10:6379 192.168.175.20:6379 192.168.175.30:6379 \
192.168.175.11:6379 192.168.175.21:6379 192.168.175.31:6379 \
--cluster-replicas 1
# 观察运行结果,可以看到3组master/slave
10.3.2、查看主从状态
[root@master1 ~]# redis-cli -c -h 192.168.175.10 -a centos info replication
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
# Replication
role:master
connected_slaves:0
master_replid:19f199fca5cf6fcd15aa6db3d7482c302923f84d
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
[root@master1 ~]# redis-cli -c -h 192.168.175.11 -a centos info replication
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
# Replication
role:slave
master_host:192.168.175.30
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:1
master_link_down_since_seconds:1625915270
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:d322eda9ed8308deae297ad5920a22e43a1d24ab
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
[root@master1 ~]# redis-cli -a centos cluster nodes
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
449ef170cd83b0d42c47e6504210699770b7a9fa 192.168.175.30:6379@16379 master - 0 1625915361650 3 connected 10923-16383
1329d448ec1f4abcf39ba03cdeed484c869a27b5 192.168.175.10:6379@16379 myself,master - 0 1625915359000 1 connected 0-5460
464d5cbe572a69ae6c95c82f2ce5942dbc8a848a 192.168.175.31:6379@16379 slave f8f0d5dec65001f4082c6db805fff98777636e00 0 1625915361000 6 connected
09f2f31bf45b91a04bab1e798a3f053104edfcd5 192.168.175.21:6379@16379 slave 1329d448ec1f4abcf39ba03cdeed484c869a27b5 0 1625915362668 5 connected
74a4f661379361a38f4bef603bc1807025527b4b 192.168.175.11:6379@16379 slave 449ef170cd83b0d42c47e6504210699770b7a9fa 0 1625915361000 4 connected
f8f0d5dec65001f4082c6db805fff98777636e00 192.168.175.20:6379@16379 master - 0 1625915362000 2 connected 5461-10922
10.3.3、验证集群状态
[root@master1 ~]# redis-cli -a centos --no-auth-warning cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6  #节点数
cluster_size:3  #3个集群
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:342
cluster_stats_messages_pong_sent:362
cluster_stats_messages_sent:704
cluster_stats_messages_ping_received:357
cluster_stats_messages_pong_received:342
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:704
10.3.4、查看集群node对应关系
[root@master1 ~]# redis-cli -a centos --no-auth-warning cluster nodes
449ef170cd83b0d42c47e6504210699770b7a9fa 192.168.175.30:6379@16379 master - 0 1625915444000 3 connected 10923-16383
1329d448ec1f4abcf39ba03cdeed484c869a27b5 192.168.175.10:6379@16379 myself,master - 0 1625915446000 1 connected 0-5460
464d5cbe572a69ae6c95c82f2ce5942dbc8a848a 192.168.175.31:6379@16379 slave f8f0d5dec65001f4082c6db805fff98777636e00 0 1625915446000 6 connected
09f2f31bf45b91a04bab1e798a3f053104edfcd5 192.168.175.21:6379@16379 slave 1329d448ec1f4abcf39ba03cdeed484c869a27b5 0 1625915447384 5 connected
74a4f661379361a38f4bef603bc1807025527b4b 192.168.175.11:6379@16379 slave 449ef170cd83b0d42c47e6504210699770b7a9fa 0 1625915443298 4 connected
f8f0d5dec65001f4082c6db805fff98777636e00 192.168.175.20:6379@16379 master - 0 1625915447000 2 connected 5461-10922
10.3.5、验证集群写入key

image-304

[root@master1 ~]# redis-cli -h 192.168.175.10 -a centos --no-auth-warning set name eagle
(error) MOVED 5798 192.168.175.20:6379  #槽位不在当前node所以无法写入
[root@master1 ~]# redis-cli -h 192.168.175.20 -a centos --no-auth-warning set name eagle
OK
[root@master1 ~]# redis-cli -h 192.168.175.20 -a centos --no-auth-warning get name
"eagle"
10.3.6、模拟master故障
[root@master3 ~]# redis-cli -a centos --no-auth-warning
127.0.0.1:6379> shutdown
not connected> exit
[root@master3 ~]# redis-cli -a centos --no-auth-warning --cluster info 192.168.175.10:6379
Could not connect to Redis at 192.168.175.30:6379: Connection refused
192.168.175.10:6379 (c5fd0661...) -> 3333 keys | 5461 slots | 1 slaves.
192.168.175.20:6379 (1cd30e11...) -> 3341 keys | 5462 slots | 1 slaves.
192.168.175.31:6379 (4e42a7da...) -> 3330 keys | 5461 slots | 0 slaves.        #192.168.175.31为新的master
[OK] 10004 keys in 3 masters.
0.61 keys per slot on average.

六、Nginx反向代理

1、Nginx代理服务

image-306

image-307

image-308

2、Nginx代理服务常见模式

image-309

3、Nginx代理服务支持协议

image-310

image-311

4、Nginx反向代理配置语法

Syntax:    proxy_pass URL;
Default:    —
Context:    location, if in location, limit_except

http://localhost:8000/uri/
http://192.168.56.11:8000/uri/
http://unix:/tmp/backend.socket:/uri/
Syntax:    proxy_redirect default;
proxy_redirect off;proxy_redirect redirect replacement;
Default:    proxy_redirect default;
Context:    http, server, location
Syntax:    proxy_set_header field value;
Default:    proxy_set_header Host $proxy_host;
            proxy_set_header Connection close;
Context:    http, server, location

# 用户请求的时候HOST的值是www.test.com, 那么代理服务会像后端传递请求的还是www.test.com
proxy_set_header Host $http_host;
# 将$remote_addr的值放进变量X-Real-IP中,$remote_addr的值为客户端的ip
proxy_set_header X-Real-IP $remote_addr;
# 客户端通过代理服务访问后端服务, 后端服务通过该变量会记录真实客户端地址
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
//nginx代理与后端服务器连接超时时间(代理连接超时)
Syntax: proxy_connect_timeout time;
Default: proxy_connect_timeout 60s;
Context: http, server, location

//nginx代理等待后端服务器的响应时间
Syntax:    proxy_read_timeout time;
Default:    proxy_read_timeout 60s;
Context:    http, server, location

//后端服务器数据回传给nginx代理超时时间
Syntax: proxy_send_timeout time;
Default: proxy_send_timeout 60s;
Context: http, server, location
//nignx会把后端返回的内容先放到缓冲区当中,然后再返回给客户端,边收边传, 不是全部接收完再传给客户端
Syntax: proxy_buffering on | off;
Default: proxy_buffering on;
Context: http, server, location

//设置nginx代理保存用户头信息的缓冲区大小
Syntax: proxy_buffer_size size;
Default: proxy_buffer_size 4k|8k;
Context: http, server, location

//proxy_buffers 缓冲区
Syntax: proxy_buffers number size;
Default: proxy_buffers 8 4k|8k;
Context: http, server, location
[root@Nginx ~]# vim /etc/nginx/proxy_params
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_connect_timeout 30;
proxy_send_timeout 60;
proxy_read_timeout 60;

proxy_buffering on;
proxy_buffer_size 32k;
proxy_buffers 4 128k;
location / {
    proxy_pass http://127.0.0.1:8080;
    include proxy_params;
}

5、Nginx反向代理场景实践

image-312

[root@web01 ~]# cd /etc/nginx/conf.d/
[root@web01 conf.d]# vim web.conf
server {
    listen 8080;
    server_name localhost;

    location / {
        root /code/8080;
        index index.html;
        allow all;
    }
}
[root@web01 conf.d]# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
[root@web01 conf.d]# systemctl restart nginx
[root@web01 ~]# mkdir -p /code/8080
[root@web01 ~]# echo "listening 8080 ..." > /code/8080/index.html
[root@proxy ~]# cd /etc/nginx/conf.d/
[root@proxy conf.d]# vim proxy_web_node1.conf
server {
    listen 80;
    server_name proxy.test.com;

    location / {
        proxy_pass http://192.168.175.20:8080;
    }
}
[root@proxy conf.d]# nginx -t
[root@proxy conf.d]# systemctl restart nginx

image-313

image-314

[root@proxy conf.d]# vim proxy_web_node1.conf
server {
    listen 80;
    server_name proxy.test.com;

    location / {
        proxy_pass http://192.168.175.20:8080;
        proxy_set_header Host $http_host;
    }
}
server {
    listen 80;
    server_name proxy.test.com;

    location / {
        proxy_pass http://192.168.175.20:8080;
        proxy_set_header Host $http_host;
        proxy_http_version 1.1;
    }
}
server {
    listen 80;
    server_name proxy.test.com;

    location / {
        proxy_pass http://192.168.175.20:8080;
        proxy_set_header Host $http_host;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

七、负载均衡

1、Nginx负载均衡基本概述

1.1、为什么要使用负载均衡

image-315

1.2、四层负载均衡

所谓四层负载均衡指的是OSI七层模型中的传输层,那么传输层Nginx已经能支持TCP/IP的控制,所以只需要对客户端的请求进行TCP/IP协议的包转发就可以实现负载均衡,那么它的好处是性能非常快、只需要底层进行应用处理,而不需要进行一些复杂的逻辑。

image-316

1.3、七层负载均衡

七层负载均衡它是在应用层,那么它可以完成很多应用方面的协议请求,比如我们说的http应用的负载均衡,它可以实现http信息的改写、头信息的改写、安全应用规则控制、URL匹配规则控制、以及转发、rewrite等等的规则,所以在应用层的服务里面,我们可以做的内容就更多,那么Nginx则是一个典型的七层负载均衡SLB

image-317

1.4、四层负载均衡与七层负载均衡区别

四层负载均衡数据包在底层就进行了分发,而七层负载均衡数据包则是在最顶层进行分发、由此可以看出,七层负载均衡效率没有四负载均衡高。

但七层负载均衡更贴近于服务,如:http协议就是七层协议,我们可以用Nginx可以作会话保持,URL路径规则匹配、head头改写等等,这些是四层负载均衡无法实现的。

注意:四层负载均衡不识别域名,七层负载均衡识别域名

2、Nginx负载均衡配置场景

Nginx要实现负载均衡需要用到proxy_pass代理模块配置.

Nginx负载均衡与Nginx代理不同地方在于,Nginx的一个location仅能代理一台服务器,而Nginx负载均衡则是将客户端请求代理转发至一组upstream虚拟服务池.

image-318

2.1、Nginx upstream虚拟配置语法
Syntax: upstream name { ... }
Default: -
Context: http

#upstream例
upstream backend {
    server backend1.example.com       weight=5;
    server backend2.example.com:8080;
    server unix:/tmp/backend3;
    server backup1.example.com:8080   backup;
}
server {
    location / {
        proxy_pass http://backend;
    }
}
2.2、环境准备
角色 地址 主机名
LB01 ens33:192.168.88.10 lb01
web01 ens33:192.168.88.20 web01
web02 ens33:192.168.88.30 web02
2.3、Web01服务器上配置nginx
[root@web01 ~]# cd /etc/nginx/conf.d/
[root@web01 conf.d]# vim node.conf
server {
    listen 80;
    server_name node.test.com;
    location / {
        root /code/node;
        index index.html;
    }
}
[root@web01 conf.d]# mkdir -p /code/node
[root@web01 conf.d]# echo "web01 ..." > /code/node/index.html
[root@web01 conf.d]# systemctl restart nginx
# 关闭防火墙和SElinux
[root@web02 conf.d]# setenforce 0
[root@web02 conf.d]# systemctl stop firewalld
2.4、Web02服务器上配置nginx
[root@web02 ~]# cd /etc/nginx/conf.d/
[root@web02 conf.d]# vim node.conf
server {
    listen 80;
    server_name node.test.com;
    location / {
        root /code/node;
        index index.html;
    }
}
[root@web02 conf.d]# mkdir -p /code/node
[root@web02 conf.d]# echo "web02 ..." > /code/node/index.html
[root@web02 conf.d]# systemctl restart nginx
# 关闭防火墙和SElinux
[root@web02 conf.d]# setenforce 0
[root@web02 conf.d]# systemctl stop firewalld
2.5、配置Nginx负载均衡
[root@lb01 ~]# cd /etc/nginx/conf.d/

[root@lb01 conf.d]# vim /etc/nginx/proxy_params
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_connect_timeout 30;
proxy_send_timeout 60;
proxy_read_timeout 60;

proxy_buffering on;
proxy_buffer_size 32k;
proxy_buffers 4 128k;

[root@lb01 conf.d]# vim node_proxy.conf
upstream node {
    server 192.168.88.20:80;
    server 192.168.88.30:80;
}
server {
    listen 80;
    server_name node.test.com;

    location / {
        proxy_pass http://node;
        include proxy_params;
    }
}

[root@lb01 conf.d]# nginx -t
[root@lb01 conf.d]# systemctl restart nginx

# 配置hosts解析
[root@lb01 conf.d]# vim /etc/hosts
192.168.88.10 node.test.com

windows访问,更改windows下的hosts文件

image-319

image-320

2.6、负载均衡常见典型故障

如果后台服务连接超时,Nginx是本身是有机制的,如果出现一个节点down掉的时候,Nginx会更据你具体负载均衡的设置,将请求转移到其他的节点上,但是,如果后台服务连接没有down掉,但是返回错误异常码了如:504、502、500,这个时候你需要加一个负载均衡的设置,如下:proxy_next_upstream http_500 | http_502 | http_503 | http_504 |http_404;意思是,当其中一台返回错误码404,500...等错误时,可以分配到下一台服务器程序继续处理,提高平台访问成功率。

server {
    listen 80;
    server_name node.test.com;

    location / {
        proxy_pass http://node;
        proxy_next_upstream error timeout http_500 http_502 http_503 http_504 http_404;
    }
}

3、Nginx负载均衡调度算法

调度算法 概述
轮询 按时间顺序逐一分配到不同的后端服务器(默认)
weight 加权轮询,weight值越大,分配到的访问几率越高
ip_hash 每个请求按访问IP的hash结果分配,这样来自同一IP的固定访问一个后端服务器
url_hash 按照访问URL的hash结果来分配请求,是每个URL定向到同一个后端服务器
least_conn 最少链接数,那个机器链接数少就分发
3.1、Nginx负载均衡[rr]轮询具体配置
upstream load_pass {
    server 192.168.88.10:80;
    server 192.168.88.20:80;
}
3.2、Nginx负载均衡[wrr]权重轮询具体配置
upstream load_pass {
    server 192.168.88.10:80 weight=5;
    server 192.168.88.20:80;
}
3.3、Nginx负载均衡ip_hash
#如果客户端都走相同代理, 会导致某一台服务器连接过多
upstream load_pass {
    ip_hash;
    server 192.168.88.10:80;
    server 192.168.88.20:80;
}

4、Nginx负载均衡后端状态

状态 概述
down 当前的server暂时不参与负载均衡
backup 预留的备份服务器
max_fails 允许请求失败的次数
fail_timeout 经过max_fails失败后, 服务暂停时间
max_conns 限制最大的接收连接数
4.1、测试down状态测试该Server不参与负载均衡的调度
upstream load_pass {
    #不参与任何调度, 一般用于停机维护
    server 192.168.88.10:80 down;
}
4.2、backup以及down状态
upstream load_pass {
    server 192.168.88.10:80 backup;
    server 192.168.88.20:80 max_fails=1 fail_timeout=10s;
}

location  / {
    proxy_pass http://load_pass;
    include proxy_params;
}
4.3、max_fails失败次数和fail_timeout多少时间内失败多少次则标记down
upstream load_pass {
    server 192.168.88.10:80;
    server 192.168.88.20:80 max_fails=2 fail_timeout=10s;
}
4.4、max_conns最大TCP连接数
upstream load_pass {
    server 192.168.88.10:80;
    server 192.168.88.20:80 max_conns=1;
}

5、Nginx负载均衡健康检查

[root@lb01 ~]# yum install -y gcc glibc gcc-c++ pcre-devel openssl-devel patch
[root@lb01 ~]# wget http://nginx.org/download/nginx-1.20.1.tar.gz
[root@lb01 ~]# wget https://github.com/yaoweibin/nginx_upstream_check_module/archive/master.zip
[root@lb01 ~]# tar xf nginx-1.20.1.tar.gz
[root@lb01 ~]# unzip master.zip
[root@lb01 ~]# cd nginx-1.20.1
[root@lb01 nginx-1.14.2]# patch -p1 <../nginx_upstream_check_module-master/check_1.20.1+.patch
[root@lb01 nginx-1.14.2]# ./configure --prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--modules-path=/usr/lib64/nginx/modules \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/run/nginx.lock \
--http-client-body-temp-path=/var/cache/nginx/client_temp \
--http-proxy-temp-path=/var/cache/nginx/proxy_temp \
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
--http-scgi-temp-path=/var/cache/nginx/scgi_temp \
--user=nginx --group=nginx --with-compat \
--with-file-aio --with-threads --with-http_addition_module \
--with-http_auth_request_module --with-http_dav_module \
--with-http_flv_module --with-http_gunzip_module \
--with-http_gzip_static_module --with-http_mp4_module \
--with-http_random_index_module --with-http_realip_module \
--with-http_secure_link_module --with-http_slice_module \
--with-http_ssl_module --with-http_stub_status_module \
--with-http_sub_module --with-http_v2_module --with-mail \
--with-mail_ssl_module --with-stream --with-stream_realip_module \
--with-stream_ssl_module --with-stream_ssl_preread_module \
--add-module=/root/nginx_upstream_check_module-master \
--with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -fPIC' \
--with-ld-opt='-Wl,-z,relro -Wl,-z,now -pie'
[root@lb01 nginx-1.14.2]# make -j 2 && make install
[root@lb01 nginx-1.14.2]# nginx -v
nginx version: nginx/1.14.2
[root@lb01 nginx-1.14.2]# mkdir -p /var/cache/nginx/client_temp
[root@lb01 conf.d]# vim node_proxy.conf
upstream node {
    server 192.168.88.10:80 max_fails=2 fail_timeout=10s;
    server 192.168.88.20:80 max_fails=2 fail_timeout=10s;
    check interval=3000 rise=2 fall=3 timeout=1000 type=tcp;
    #interval  检测间隔时间,单位为毫秒
    #rise      表示请求2次正常,标记此后端的状态为up
    #fall      表示请求3次失败,标记此后端的状态为down
    #type      类型为tcp
    #timeout   超时时间,单位为毫秒
}

server {
    listen 80;
    server_name node.test.com;
    location / {
        proxy_pass http://node;
        include proxy_params;
    }

    location /upstream_check {
        check_status;
    }
}

6、Nginx负载均衡会话保持

6.1、会话保持配置

测试网站选择我们之前搭建过的phpmyadmin,所以我们先再web01和web02上部署phpmyadmin

# 导入英格提供的php源,这个比较快
vim /etc/yum.repos.d/eagle.repo
[eagle]
name=Eagle lab
baseurl=http://file.eagleslab.com:8889/%E8%AF%BE%E7%A8%8B%E7%9B%B8%E5%85%B3%E8%BD%AF%E4%BB%B6/%E4%BA%91%E8%AE%A1%E7%AE%97%E8%AF%BE%E7%A8%8B/Centos7%E6%BA%90/
gpgcheck=0
enabled=1

# 安装php环境,php所需的组件比较多,我们可以一次性安装全面了
yum -y install php71w php71w-cli php71w-common php71w-devel php71w-embedded php71w-gd php71w-mcrypt php71w-mbstring php71w-pdo php71w-xml php71w-fpm php71w-mysqlnd php71w-opcache php71w-pecl-memcached php71w-pecl-redis php71w-pecl-mongodb

# 配置php-fpm用于与nginx的运行用户保持一致
sed -i '/^user/c user = nginx' /etc/php-fpm.d/www.conf
sed -i '/^group/c group = nginx' /etc/php-fpm.d/www.conf

# 启动php-fpm
systemctl start php-fpm
[root@web01 ~]# vim /etc/nginx/conf.d/php.conf
server {
    listen 80;
    server_name php.test.com;
    root /code/phpMyAdmin-4.8.4-all-languages;

    location / {
        index index.php index.html;
    }

    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}
[root@web01 ~]# nginx -t
[root@web01 ~]# systemctl restart nginx
[root@web01 ~]# cd /code
[root@web01 code]# wget https://files.phpmyadmin.net/phpMyAdmin/4.8.4/phpMyAdmin-4.8.4-all-languages.zip
[root@web01 code]# unzip phpMyAdmin-4.8.4-all-languages.zip
[root@web01 code]# cd phpMyAdmin-4.8.4-all-languages/
[root@web01 phpMyAdmin-4.8.4-all-languages]# cp config.sample.inc.php config.inc.php
[root@web01 phpMyAdmin-4.8.4-all-languages]# vim config.inc.php
$cfg['Servers'][$i]['host'] = '192.168.88.10';
[root@web01 ~]# chown -R nginx.nginx /var/lib/php/
# 安装mariadb数据库软件
yum install mariadb-server mariadb -y

# 启动数据库并且设置开机自启动
systemctl start mariadb
systemctl enable mariadb

# 设置mariadb的密码
mysqladmin password '123456'

# 验证数据库是否工作正常
mysql -uroot -p123456 -e "show databases;"
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| test               |
+--------------------+

# 创建访问用户
grant all privileges on *.* to root@'192.168.88.%' identified by '123456' with grant option;
flush privileges;
6.2、使用浏览器访问页面,获取cookie信息

image-321

image-322

[root@web01 ~]# ll /var/lib/php/session/
总用量 4
-rw------- 1 www www 2625 7月  11 20:08 sess_aa3bdfab93197f49bbfddb15cb41a595
[root@web01 code]# scp -rp phpMyAdmin-4.8.4-all-languages root@192.168.88.30:/code/
[root@web01 code]# scp /etc/nginx/conf.d/php.conf root@192.168.88.30:/etc/nginx/conf.d/
[root@web02 ~]# systemctl restart nginx
[root@web02 ~]# chown -R nginx. /var/lib/php/
[root@lb01 ~]# vim /etc/nginx/conf.d/proxy_php.com.conf
upstream php {
        server 192.168.88.20:80;
        server 192.168.88.30:80;
}
server {
        listen 80;
        server_name php.test.com;
        location / {
                proxy_pass http://php;
                include proxy_params;
        }
}

[root@lb01 ~]# nginx -t
[root@lb01 ~]# systemctl restart nginx

image-323

6.3、使用redis解决会话登录问题
[root@lb01 ~]# yum -y install redis
[root@lb01 ~]# sed  -i '/^bind/c bind 127.0.0.1 0.0.0.0' /etc/redis.conf
[root@lb01 ~]# systemctl start redis
[root@lb01 ~]# systemctl enable redis
[root@web01 ~]# vim /etc/php.ini
session.save_handler = redis
session.save_path = "tcp://192.168.88.10:6379"
;session.save_path = "tcp://192.168.88.10:6379?auth=centos" #如果redis存在密码,则使用该方式
session.auto_start = 1

[root@web01 ~]# vim /etc/php-fpm.d/www.conf
#注释php-fpm.d/www.conf里面的两条内容,否则session内容会一直写入/var/lib/php/session目录中
;php_value[session.save_handler] = files
;php_value[session.save_path]    = /var/lib/php/session
[root@web01 ~]# systemctl restart php-fpm
[root@web01 ~]# scp /etc/php.ini root@192.168.88.30:/etc/php.ini
[root@web01 ~]# scp /etc/php-fpm.d/www.conf root@192.168.88.30:/etc/php-fpm.d/www.conf
[root@web02 ~]# systemctl restart php-fpm
[root@lb01 ~]# redis-cli
127.0.0.1:6379> keys *
1) "PHPREDIS_SESSION:7e772fe37b1488069e3c54e419df7eda"

image-324

7、Nginx四层负载均衡概述

四层负载均衡是基于传输层协议包来封装的(如:TCP/IP),那我们前面使用到的七层是指的应用层,他的组装在四层的基础之上,无论四层还是七层都是指的OSI网络模型。

四层+七层来做负载均衡,四层可以保证七层的负载均衡的高可用性;如:nginx就无法保证自己的服务高可用,需要依赖LVS或者keepalive。

如:tcp协议的负载均衡,有些请求是TCP协议的(mysql、ssh),或者说这些请求只需要使用四层进行端口的转发就可以了,所以使用四层负载均衡。

7.1、四层+七层构建大规模集群架构使用场

image-325

7.2、四层负载均衡总结
7.3、Nginx四层负载均衡场景实践

Nginx如何配置四层负载均衡

1、通过访问负载均衡的5555端口,实际是后端的web01的22端口在提供服务;

2、通过访问负载均衡的6666端口,实际是后端的mysql的3306端口在提供服务。

[root@lb01 ~]# yum install nginx-mod-stream
[root@lb01 ~]# vim /etc/nginx/nginx.conf
include /etc/nginx/conf.c/*.conf;
#include /etc/nginx/conf.d/*.conf;
[root@lb01 ~]# mkdir /etc/nginx/conf.c
[root@lb01 ~]# vim /etc/nginx/conf.c/lb_domain.conf
stream {
    upstream lb {
            server 192.168.88.20:80 weight=5 max_fails=3 fail_timeout=30s;
            server 192.168.88.30:80 weight=5 max_fails=3 fail_timeout=30s;
    }

    server {
            listen 80;
            proxy_connect_timeout 3s;
            proxy_timeout 3s;
            proxy_pass lb;
    }
}
[root@lb01 ~]# cd /etc/nginx/conf.c/
[root@lb01 conf.c]# vim lb_domain.conf
stream {
    log_format  proxy '$remote_addr $remote_port - [$time_local] $status $protocol '
                  '"$upstream_addr" "$upstream_bytes_sent" "$upstream_connect_time"' ;
    access_log /var/log/nginx/proxy.log proxy;
    upstream lb {
            server 192.168.88.20:80 weight=5 max_fails=3 fail_timeout=30s;
            server 192.168.88.30:80 weight=5 max_fails=3 fail_timeout=30s;
    }

    server {
            listen 80;
            proxy_connect_timeout 3s;
            proxy_timeout 3s;
            proxy_pass lb;
    }
}

[root@lb01 conf.c]# nginx -t
[root@lb01 conf.c]# systemctl restart nginx
[root@lb01 conf.c]# tail -f /var/log/nginx/proxy.log
192.168.88.1 55295 - [11/Jul/2021:23:27:09 +0800] 200 TCP "192.168.88.20:80" "0" "0.001"
192.168.88.1 64066 - [11/Jul/2021:23:27:13 +0800] 200 TCP "192.168.88.30:80" "0" "0.000"
192.168.88.1 58528 - [11/Jul/2021:23:27:16 +0800] 200 TCP "192.168.88.20:80" "0" "0.001"
192.168.88.1 65099 - [11/Jul/2021:23:27:20 +0800] 200 TCP "192.168.88.30:80" "0" "0.000"
192.168.88.1 55919 - [11/Jul/2021:23:27:20 +0800] 200 TCP "192.168.88.20:80" "6460" "0.001"
7.4、Nginx四层负载均衡端口转发
请求负载均衡 5555    --->     192.168.88.20:22;
请求负载均衡 6666    --->     192.168.88.30:3306;
[root@lb01 ~]# vim /etc/nginx/conf.c/lb_domain.conf
stream {
    log_format  proxy '$remote_addr $remote_port - [$time_local] $status $protocol '
                      '"$upstream_addr" "$upstream_bytes_sent" "$upstream_connect_time"' ;
    access_log /var/log/nginx/proxy.log proxy;

#定义转发ssh的22端口
    upstream ssh {
            server 192.168.88.20:22;
    }
#定义转发mysql的3306端口
    upstream mysql {
            server 192.168.88.30:3306;
    }
    server {
            listen 5555;
            proxy_connect_timeout 3s;
            proxy_timeout 300s;
            proxy_pass ssh;
    }

    server {
            listen 6666;
            proxy_connect_timeout 3s;
            proxy_timeout 3s;
            proxy_pass mysql;
    }
}

[root@lb01 ~]# nginx -t
[root@lb01 ~]# setenforce 0
[root@lb01 ~]# systemctl restart nginx

8、Keepalived 高可用基本概述

一般是指2台机器启动着完全相同的业务系统,当有一台机器down机了,另外一台服务器就能快速的接管,对于访问的用户是无感知的。

硬件通常使用 F5

软件通常使用 keepalived

keepalived软件是基于VRRP协议实现的,VRRP虚拟路由冗余协议,主要用于解决单点故障问题

8.1、VRRP原理

9、Keepalived高可用安装配置

9.1、安装keepalived
[root@lb01 ~]# yum install -y keepalived
[root@lb02 ~]# yum install -y keepalived
9.2、配置master
[root@lb02 ~]# rpm -qc keepalived
/etc/keepalived/keepalived.conf
/etc/sysconfig/keepalived
[root@lb01 ~]# vim /etc/keepalived/keepalived.conf
global_defs {                   #全局配置
    router_id lb01              #标识身份->名称
}

vrrp_instance VI_1 {
    state MASTER                #标识角色状态
    interface ens33             #网卡绑定接口
    virtual_router_id 50        #虚拟路由id
    priority 150                #优先级
    advert_int 1                #监测间隔时间
    #use_vmac                  #使用虚拟mac地址,因为路由问题,可能导致原本的IP不可用
    authentication {            #认证
        auth_type PASS          #认证方式
        auth_pass 1111          #认证密码
    }
    virtual_ipaddress {         
        192.168.88.100                #虚拟的VIP地址
    }
}
9.3、配置backup
[root@lb02 ~]# vim /etc/keepalived/keepalived.conf
global_defs {
    router_id lb02
}

vrrp_instance VI_1 {
    state BACKUP        
    interface ens33
    virtual_router_id 50
    priority 100
    advert_int 1
    authentication {    
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.88.100
    }
}
9.4、对比master与Backup的keepalived配置区别
Keepalived配置区别 Master节点配置 Backup节点配置
route_id(唯一标识) router_id lb01 router_id lb02
state(角色状态) state MASTER state BACKUP
priority(竞选优先级) priority 150 priority 100
9.5、启动Master和Backup节点的keepalived
[root@lb01 ~]# systemctl start keepalived
[root@lb01 ~]# systemctl enable keepalived

[root@lb02 ~]# systemctl start keepalived
[root@lb02 ~]# systemctl enable keepalived

10、高可用keepalived抢占式与非抢占式

[root@lb01 ~]# ip addr |grep 192.168.88.100
    inet 192.168.88.100/32 scope global ens33
[root@lb01 ~]# systemctl stop keepalived
[root@lb02 ~]# ip addr |grep 192.168.88.100
    inet 192.168.88.100/32 scope global ens33
[root@lb01 ~]# systemctl start keepalived
[root@lb01 ~]# ip addr |grep 192.168.88.100
    inet 192.168.88.100/32 scope global ens33
Master配置
    vrrp_instance VI_1 {
        state BACKUP
        priority 150
        nopreempt
    }

Backup配置
    vrrp_instance VI_1 {
        state BACKUP
        priority 100
        nopreempt
    }
# 当前master在lb01上
[root@lb01 ~]# ip addr |grep 192.168.88.100
    inet 192.168.88.100/32 scope global ens33

# windows查看mac地址
C:\Users\Aaron>arp -a |findstr 192.168.88.100
  192.168.88.100       00-0c-29-bb-9a-bb     动态
[root@lb01 ~]# systemctl stop keepalived
[root@lb02 ~]# ip addr |grep 192.168.88.100
    inet 192.168.88.100/32 scope global ens33
C:\Users\Aaron>arp -a |findstr 192.168.88.100
  192.168.88.100       00-0c-29-bb-9a-bb     动态

11、高可用keepalived故障脑裂

由于某些原因,导致两台keepalived高可用服务器在指定时间内,无法检测到对方的心跳,各自取得资源及服务的所有权,而此时的两台高可用服务器又都还活着。

11.1、脑裂故障原因
11.2、脑裂故障现象

image-326

image-327

[root@lb01 ~]# ip a |grep 192.168.88.100
    inet 192.168.88.100/24 scope global secondary ens33

[root@lb02 ~]# ip a |grep 192.168.88.100
    inet 192.168.88.100/24 scope global secondary ens33
11.3、解决脑裂故障方案
[root@lb02 ~]# vim check_split_brain.sh
#!/bin/sh
vip=192.168.88.100
lb01_ip=192.168.88.10
while true;do
    ping -c 2 $lb01_ip &>/dev/null
    if [ $? -eq 0 -a `ip add|grep "$vip"|wc -l` -eq 1 ];then
        echo "ha is split brain.warning."
    else
        echo "ha is ok"
    fi
sleep 5
done

12、高可用keepalived与nginx

Nginx默认监听在所有的IP地址上,VIP会飘到一台节点上,相当于那台nginx多了VIP这么一个网卡,所以可以访问到nginx所在机器

但是.....如果nginx宕机,会导致用户请求失败,但是keepalived没有挂掉不会进行切换,所以需要编写一个脚本检测Nginx的存活状态,如果不存活则kill掉keepalived

[root@lb01 ~]# vim check_web.sh
#!/bin/sh
nginxpid=$(ps -C nginx --no-header|wc -l)

#1.判断Nginx是否存活,如果不存活则尝试启动Nginx
if [ $nginxpid -eq 0 ];then
    systemctl start nginx
    sleep 3
    #2.等待3秒后再次获取一次Nginx状态
    nginxpid=$(ps -C nginx --no-header|wc -l) 
    #3.再次进行判断, 如Nginx还不存活则停止Keepalived,让地址进行漂移,并退出脚本  
    if [ $nginxpid -eq 0 ];then
        systemctl stop keepalived
   fi
fi
12.1、在lb01主机的keepalived配置文件中调用此脚本
[root@lb01 ~]# vim /etc/keepalived/keepalived.conf
global_defs {           
    router_id lb01      
}

#每5秒执行一次脚本,脚本执行内容不能超过5秒,否则会中断再次重新执行脚本
vrrp_script check_web {
    script "/root/check_web.sh"
    interval 5
}

vrrp_instance VI_1 {
    state MASTER        
    interface ens33     
    virtual_router_id 50    
    priority 150        
    advert_int 1        
    authentication {    
        auth_type PASS  
        auth_pass 1111  
    }
    virtual_ipaddress { 
        192.168.88.100    
    }
}

#调用并运行脚本
track_script {
    check_web
}

13、集群和分布式

系统性能扩展方式:

垂直扩展不再提及:

13.1、集群 Cluster

Cluster:集群,为解决某个特定问题将多台计算机组合起来形成的单个系统

Cluster分为三种类型:

13.2、分布式系统

分布式存储: Ceph,GlusterFS,FastDFS,MogileFS 分布式计算:hadoop,Spark 分布式常见应用 分布式应用-服务按照功能拆分,使用微服务 分布式静态资源–静态资源放在不同的存储集群上 分布式数据和存储–使用key-value缓存系统 分布式计算–对特殊业务使用分布式计算,比如Hadoop集群

13.3、集群和分布式

集群:同一个业务系统,部署在多台服务器上。集群中,每一台服务器实现的功能没有差别,数据和代 码都是一样的

分布式:一个业务被拆成多个子业务,或者本身就是不同的业务,部署在多台服务器上。分布式中,每 一台服务器实现的功能是有差别的,数据和代码也是不一样的,分布式每台服务器功能加起来,才是完整的业务。

分布式是以缩短单个任务的执行时间来提升效率的,而集群则是通过提高单位时间内执行的任务数来提升效率。 对于大型网站,访问用户很多,实现一个群集,在前面部署一个负载均衡服务器,后面几台服务器完成 同一业务。如果有用户进行相应业务访问时,负载均衡器根据后端哪台服务器的负载情况,决定由给哪一台去完成响应,并且一台服务器垮了,其它的服务器可以顶上来。分布式的每一个节点,都完成不同的业务,如果一个节点垮了,那这个业务可能就会失败。

13.4、集群设计原则

可扩展性—集群的横向扩展能力。小型机横向扩展小,面临淘汰 可用性—无故障时间(SLA) 性能—访问响应时间 容量—单位时间内的最大并发吞吐量(C10K 并发问题) 。LVS内核级,并发好。

13.5、集群设计实现
13.5.1、基础设施层面

提升硬件资源性能—从入口防火墙到后端web server均使用更高性能的硬件资源 多域名—DNS 轮询A记录解析。指向不同IP 访问入口增多 多入口—将A记录解析到多个公网IP入口 多机房—同城+异地容灾 CDN(Content Delivery Network)—基于GSLB(Global Server Load Balance)实现全局负载均衡,如:DNS 就近分配地址,提高效率

13.5.2、业务层面

分层:安全层、负载层、静态层、动态层、(缓存层、存储层)持久化与非持久化 分割:基于功能分割大业务为小服务 分布式:对于特殊场景的业务,使用分布式计算

13.6、LB Cluster 负载均衡集群
13.6.1、按实现方式划分
13.6.2、基于工作的协议层次划分
13.6.3、负载均衡的会话保持
13.7、HA 高可用集群实现

keepalived:vrrp协议 Ais:应用接口规范 heartbeat cman+rgmanager(RHCS) coresync_pacemaker

14、Linux Virtual Server简介

14.1、LVS介绍
14.2、LVS工作原理

VS根据请求报文的目标IP和目标协议及端口将其调度转发至某RS,根据调度算法来挑选RS。LVS是内核级功能,工作在INPUT链的位置,将发往INPUT的流量进行“处理”

查看内核支持LVS

[root@localhost ~]# grep -i -C 10 ipvs /boot/config-3.10.0-957.el7.x86_64
14.3、LVS集群体系架构

image-328

14.4、LVS 功能及组织架构

下面摘自于阿里云:https://help.aliyun.com/document_detail/27543.html?spm=5176.21213303.J_6028563670.7.505a3edaQq9WLJ&scm=20140722.S_help%40%40%E6%96%87%E6%A1%A3%40%4027543.S_0.ID_27543-OR_s%2Bhelpproduct-V_1-P0_0

负载均衡的应用场景为高访问量的业务,提高应用程序的可用性和可靠性。

14.5、LVS集群类型中的术语

VS:Virtual Server,Director Server(DS), Dispatcher(调度器),Load Balancer RS:Real Server(lvs), upstream server(nginx)上游服务器, backend server(haproxy) CIP:Client IP VIP:Virtual serve IP VS外网的IP DIP:Director IP VS内网的IP RIP:Real server IP 访问流程:CIP <–> VIP == DIP <–> RIP

15、LVS 工作模式和相关命令

15.1、LVS集群的工作模式

lvs-nat:修改请求报文的目标IP,多目标IP的DNAT lvs-dr:操纵封装新的MAC地址。MAC头的修改 lvs-tun:在原请求IP报文之外新加一个IP首部。 lvs-fullnat:修改请求报文的源和目标IP

15.1.1、LVS的NAT模式

image-329

lvs-nat:本质是多目标IP的DNAT,通过将请求报文中的目标地址和目标端口修改为某挑出的RS的RIP和PORT实现转发

image-330

image-331

15.1.2、LVS的DR模式

image-332

LVS-DR:Direct Routing,直接路由,LVS默认模式,应用最广泛,通过为请求报文重新封装一个MAC首部 进行转发,源MAC是DIP所在的接口的MAC,目标MAC是某挑选出的RS的RIP所在接口的MAC地址;源 IP/PORT,以及目标IP/PORT均保持不变

image-333

DR模式的特点

在前端网关做静态绑定VIP和Director的MAC地址

此方法一般不使用,过于固定

在RS上使用arptables工具

arptables -A IN -d $VIP -j DROP
arptables -A OUT -s $VIP -j mangle --mangle-ip-s $RIP

在RS上修改内核参数以限制arp通告及应答级别

/proc/sys/net/ipv4/conf/all/arp_ignore
/proc/sys/net/ipv4/conf/all/arp_announce
15.1.3、LVS的TUN模式

image-334

转发方式:不修改请求报文的IP首部(源IP为CIP,目标IP为VIP),而在原IP报文之外再封装一个IP首部(源IP是DIP,目标IP是RIP),将报文发往挑选出的目标RS;RS直接响应给客户端(源IP是VIP,目标IP是CIP)

image-335

TUN模式特点

15.1.4、LVS的FULLNAT模式

image-336

15.1.5、LVS工作模式总结和比较
NAT TUN DR
Real server any Tunneling Non-arp device
Real server network private LAN/WAN LAN
Real Server number low(10-20) High(100) High(100)
Real server gateway load balancer own router own router
优点 端口转换 WAN 性能最好
缺点 性能瓶颈 支持隧道 不支持跨网段
15.2、LVS调试算法

ipvs scheduler:根据其调度时是否考虑各RS当前的负载状态 分为两种:静态方法和动态方法

15.2.1、静态方法
15.2.2、动态方法

主要根据每RS当前的负载状态及调度算法进行调度Overhead=value 较小的RS将被调度

15.2.3、内核版本 4.15版本后新增调度算法
15.3、LVS 相关软件
15.3.1、程序包
[root@localhost ~]# yum -y install ipvsadm
[root@localhost ~]# cat /usr/lib/systemd/system/ipvsadm.service
[Unit]
Description=Initialise the Linux Virtual Server
After=syslog.target network.target

[Service]
Type=oneshot
ExecStart=/bin/bash -c "exec /sbin/ipvsadm-restore < /etc/sysconfig/ipvsadm"
ExecStop=/bin/bash -c "exec /sbin/ipvsadm-save -n > /etc/sysconfig/ipvsadm"
ExecStop=/sbin/ipvsadm -C
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
15.3.2、ipvsadm 命令
#管理集群服务
ipvsadm -A|E -t|u|f service-address [-s scheduler] [-p [timeout]] [-M netmask]
[--pe persistence_engine] [-b sched-flags]
ipvsadm -A #创建集群
ipvsadm -E #修改集群
ipvsadm -D -t|u|f service-address #删除
ipvsadm –C #清空
ipvsadm –R #重载
ipvsadm -S [-n] #保存
ipvsadm -L #查看

#管理集群中的RS
ipvsadm -a|e -t|u|f service-address -r server-address [options]
ipvsadm -d -t|u|f service-address -r server-address
ipvsadm -L|l [options]
ipvsadm -Z [-t|u|f service-address]        #清空计数器

#保存规则
‐S
# ipvsadm ‐S > /path/to/somefile
载入此前的规则:
‐R
# ipvsadm ‐R < /path/form/somefile
service-address:

-t|u|f:
    -t: TCP协议的端口,VIP:TCP_PORT    如:-t 192.168.88.20:80
    -u: UDP协议的端口,VIP:UDP_PORT    如:-u 192.168.88.20:80
    -f:firewall MARK,标记,一个数字
[-s scheduler]:指定集群的调度算法,默认为wlc

server-address:

rip[:port] 如省略port,不作端口映射
选项:
    lvs类型:
        -g: gateway, dr类型,默认
        -i: ipip, tun类型
        -m: masquerade, nat类型
    -w weight:权重
ipvsadm -A -t 192.168.88.100:80 -s wrr        # 创建集群
ipvsadm -D -t 192.168.88.100:80            # 删除集群

ipvsadm -a -t 192.168.88.100:80 -r 192.168.88.20:8080 -m    -w 3
# 往集群中添加RS,并且设置nat,配置权重为3
ipvsadm -d -t 192.168.88.100:80 -r 192.168.88.20:8080
# 删除RS
ipvsadm -L|l [options]
    –numeric, -n:以数字形式输出地址和端口号
    –exact:扩展信息,精确值
    –connection,-c:当前IPVS连接输出
    –stats:统计信息
    –rate :输出速率信息

16、LVS实战案例

16.1、LVS-NAT模式案例

image-337

实验环境说明:

LVS:两张网卡,一张nat网卡做为vIP与客户端建立连接,一张仅主机网卡用于对内部服务器的连接

RS1和RS2:两个RS的网卡都使用仅主机的网卡,并且网关指向LVS的仅主机网卡

[root@lvs ~]# vim /etc/sysctl.conf
net.ipv4.ip_forward = 1
[root@lvs ~]# sysctl -p
net.ipv4.ip_forward = 1

[root@lvs ~]# iptables -t nat -A POSTROUTING -s 192.168.153.0/24 -j MASQUERADE
[root@lvs ~]# iptables -t nat -L -n
[root@rs1 ~]# yum -y install httpd
[root@rs1 ~]# systemctl start httpd
[root@rs1 ~]# systemctl enable httpd
[root@rs1 ~]# echo "RS1 ..." > /var/www/html/index.html
[root@rs1 ~]# firewall-cmd --add-service=http
[root@rs1 ~]# firewall-cmd --add-service=http --permanent

[root@rs2 ~]# yum -y install httpd
[root@rs2 ~]# systemctl start httpd
[root@rs2 ~]# systemctl enable httpd
[root@rs2 ~]# echo "RS2 ..." > /var/www/html/index.html
[root@rs2 ~]# firewall-cmd --add-service=http
[root@rs2 ~]# firewall-cmd --add-service=http --permanent
[root@lvs ~]# ipvsadm -A -t 192.168.88.10:80 -s rr
[root@lvs ~]# ipvsadm -a -t 192.168.88.10:80 -r 192.168.153.20:80 -m
[root@lvs ~]# ipvsadm -a -t 192.168.88.10:80 -r 192.168.153.30:80 -m
[root@lvs ~]# ipvsadm -L -n
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  192.168.88.100:80 rr
  -> 192.168.153.20:80             Masq    1      0          0
  -> 192.168.153.30:80             Masq    1      0          0

image-338

image-339

image-340

16.2、LVS-DR模式案例

image-341

# 由于我们后面做实验要用到ifconfig命令,该命令需要安装net-tools工具。所以我们趁现在有网,可以先安装一下,三台机器都需要安装
[root@lvs ~]# yum install -y net-tools
[root@lvs ~]# iptables -t nat -F
[root@rs1 ~]# ip route
default via 192.168.153.2 dev ens33 proto static metric 100

[root@rs2 ~]# ip route
default via 192.168.153.2 dev ens33 proto static metric 100
[root@rs1 ~]# echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
[root@rs1 ~]# echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce

[root@rs2 ~]# echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
[root@rs2 ~]# echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
[root@rs1 ~]# vim rs.sh
#!/bin/bash
vip=192.168.153.100/32
ifconfig lo:1 $vip
echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/lo/arp_announce
echo "1" >/proc/sys/net/ipv4/conf/all/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/all/arp_announce

[root@rs1 ~]# . rs.sh
# 在RS2上也需要执行
[root@lvs ~]# ifconfig lo:1 192.168.153.100/32
[root@lvs ~]# ipvsadm -A -t 192.168.153.100:80 -s rr
[root@lvs ~]# ipvsadm -a -t 192.168.153.100:80 -r 192.168.153.20
[root@lvs ~]# ipvsadm -a -t 192.168.153.100:80 -r 192.168.153.30
[root@lvs ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  192.168.153.100:80 rr
  -> 192.168.153.20:80             Route   1      0          0
  -> 192.168.153.30:80             Route   1      0          0

image-342

image-343

17、HAProxy介绍

HAProxy是法国开发者威利塔罗(Willy Tarreau)在2000年使用C语言开发的一个开源软件,是一款具备高并发(一万以上)、高性能的TCP和HTTP负载均衡器,支持基于cookie的持久性,自动故障切换,支持正则表达式及web状态统计,目前最新TLS版本为2.0

17.1、HAProxy功能

image-344

17.2、支持功能
17.3、不具备的功能

18、编译安装HAProxy

18.1、解决lua环境

HAProxy 支持基于lua实现功能扩展,lua是一种小巧的脚本语言,于1993年由巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组开发,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

Lua 官网:www.lua.org

18.1.1、Centos 基础环境

参考链接:http://www.lua.org/start.html

image-345

由于CentOS7 之前版本自带的lua版本比较低并不符合HAProxy要求的lua最低版本(5.3)的要求,因此需要编译安装较新版本的lua环境,然后才能编译安装HAProxy,过程如下

[root@localhost ~]# lua -v
Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
[root@localhost ~]# yum -y install gcc readline-devel
[root@localhost ~]# wget http://www.lua.org/ftp/lua-5.3.5.tar.gz
[root@localhost ~]# tar xvf  lua-5.3.5.tar.gz -C /usr/local/src
[root@localhost ~]# cd /usr/local/src/lua-5.3.5
[root@localhost lua-5.3.5]# make linux test
[root@localhost lua-5.3.5]# src/lua -v
Lua 5.3.5  Copyright (C) 1994-2018 Lua.org, PUC-Rio
18.2、编译安装HAProxy
[root@localhost ~]# yum -y install gcc openssl-devel pcre-devel systemd-devel
[root@localhost ~]# wget http://www.haproxy.org/download/2.1/src/haproxy-2.1.3.tar.gz
[root@localhost ~]# tar xvf haproxy-2.1.3.tar.gz  -C /usr/local/src
[root@localhost ~]# cd /usr/local/src/haproxy-2.1.3/
[root@localhost haproxy-2.1.3]# cat README
[root@localhost haproxy-2.1.3]# make -j 2 ARCH=x86_64 TARGET=linux-glibc  \
USE_PCRE=1 \
USE_OPENSSL=1 \
USE_ZLIB=1  \
USE_SYSTEMD=1  \
USE_LUA=1 \
LUA_INC=/usr/local/src/lua-5.3.5/src/  \
LUA_LIB=/usr/local/src/lua-5.3.5/src/
[root@localhost haproxy-2.1.3]# make install PREFIX=/apps/haproxy
[root@localhost haproxy-2.1.3]# ln -s /apps/haproxy/sbin/haproxy /usr/sbin/
[root@localhost ~]# tree -C /apps/haproxy/
/apps/haproxy/
├── doc
│   └── haproxy
│       ├── 51Degrees-device-detection.txt
│       ├── architecture.txt
│       ├── close-options.txt
│       ├── configuration.txt
│       ├── cookie-options.txt
│       ├── DeviceAtlas-device-detection.txt
│       ├── intro.txt
│       ├── linux-syn-cookies.txt
│       ├── lua.txt
│       ├── management.txt
│       ├── netscaler-client-ip-insertion-protocol.txt
│       ├── network-namespaces.txt
│       ├── peers.txt
│       ├── peers-v2.0.txt
│       ├── proxy-protocol.txt
│       ├── regression-testing.txt
│       ├── seamless_reload.txt
│       ├── SOCKS4.protocol.txt
│       ├── SPOE.txt
│       └── WURFL-device-detection.txt
├── sbin
│   └── haproxy
└── share
    └── man
        └── man1
            └── haproxy.1

6 directories, 22 files
18.3、验证HAProxy版本
[root@localhost ~]# which haproxy
/usr/sbin/haproxy
[root@localhost ~]# haproxy -v
HA-Proxy version 2.1.3 2020/02/12 - https://haproxy.org/
Status: stable branch - will stop receiving fixes around Q1 2021.
Known bugs: http://www.haproxy.org/bugs/bugs-2.1.3.html

[root@localhost ~]# haproxy -vv
HA-Proxy version 2.1.3 2020/02/12 - https://haproxy.org/
Status: stable branch - will stop receiving fixes around Q1 2021.
Known bugs: http://www.haproxy.org/bugs/bugs-2.1.3.html
Build options :
  TARGET  = linux-glibc
  CPU     = generic
  CC      = gcc
  CFLAGS  = -m64 -march=x86-64 -O2 -g -fno-strict-aliasing -Wdeclaration-after-statement -fwrapv -Wno-unused-label -Wno-sign-compare -Wno-unused-parameter -Wno-old-style-declaration -Wno-ignored-qualifiers -Wno-clobbered -Wno-missing-field-initializers -Wtype-limits
  OPTIONS = USE_PCRE=1 USE_OPENSSL=1 USE_LUA=1 USE_ZLIB=1 USE_SYSTEMD=1

Feature list : +EPOLL -KQUEUE -MY_EPOLL -MY_SPLICE +NETFILTER +PCRE -PCRE_JIT -PCRE2 -PCRE2_JIT +POLL -PRIVATE_CACHE +THREAD -PTHREAD_PSHARED -REGPARM -STATIC_PCRE -STATIC_PCRE2 +TPROXY +LINUX_TPROXY +LINUX_SPLICE +LIBCRYPT +CRYPT_H -VSYSCALL +GETADDRINFO +OPENSSL +LUA +FUTEX +ACCEPT4 -MY_ACCEPT4 +ZLIB -SLZ +CPU_AFFINITY +TFO +NS +DL +RT -DEVICEATLAS -51DEGREES -WURFL +SYSTEMD -OBSOLETE_LINKER +PRCTL +THREAD_DUMP -EVPORTS

Default settings :
  bufsize = 16384, maxrewrite = 1024, maxpollevents = 200

Built with multi-threading support (MAX_THREADS=64, default=4).
Built with OpenSSL version : OpenSSL 1.0.2k-fips  26 Jan 2017
Running on OpenSSL version : OpenSSL 1.0.2k-fips  26 Jan 2017
OpenSSL library supports TLS extensions : yes
OpenSSL library supports SNI : yes
OpenSSL library supports : SSLv3 TLSv1.0 TLSv1.1 TLSv1.2
Built with Lua version : Lua 5.3.5
Built with network namespace support.
Built with transparent proxy support using: IP_TRANSPARENT IPV6_TRANSPARENT IP_FREEBIND
Built with PCRE version : 8.32 2012-11-30
Running on PCRE version : 8.32 2012-11-30
PCRE library supports JIT : no (USE_PCRE_JIT not set)
Encrypted password support via crypt(3): yes
Built with zlib version : 1.2.7
Running on zlib version : 1.2.7
Compression algorithms supported : identity("identity"), deflate("deflate"), raw-deflate("deflate"), gzip("gzip")

Available polling systems :
      epoll : pref=300,  test result OK
       poll : pref=200,  test result OK
     select : pref=150,  test result OK
Total: 3 (3 usable), will use epoll.

Available multiplexer protocols :
(protocols marked as <default> cannot be specified using 'proto' keyword)
              h2 : mode=HTTP       side=FE|BE     mux=H2
            fcgi : mode=HTTP       side=BE        mux=FCGI
       <default> : mode=HTTP       side=FE|BE     mux=H1
       <default> : mode=TCP        side=FE|BE     mux=PASS

Available services : none

Available filters :
        [SPOE] spoe
        [CACHE] cache
        [FCGI] fcgi-app
        [TRACE] trace
        [COMP] compression
18.4、HAProxy启动脚本
[root@localhost ~]# vim /usr/lib/systemd/system/haproxy.service
[Unit]
Description=HAProxy Load Balancer
After=syslog.target network.target

[Service]
ExecStartPre=/usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg  -c -q
ExecStart=/usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /var/lib/haproxy/haproxy.pid
ExecReload=/bin/kill -USR2 $MAINPID

[Install]
WantedBy=multi-user.target
[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl start haproxy
Job for haproxy.service failed because the control process exited with error code. See "systemctl status haproxy.service" and "journalctl -xe" for details.


[root@localhost ~]# tail /var/log/messages
Jul 13 10:39:33 localhost systemd: Starting HAProxy Load Balancer...
Jul 13 10:39:33 localhost haproxy: [ALERT] 193/103933 (8156) : Cannot open configuration file/directory /etc/haproxy/haproxy.cfg : No such file or directory
Jul 13 10:39:33 localhost systemd: haproxy.service: control process exited, code=exited status=1
Jul 13 10:39:33 localhost systemd: Failed to start HAProxy Load Balancer.
Jul 13 10:39:33 localhost systemd: Unit haproxy.service entered failed state.
Jul 13 10:39:33 localhost systemd: haproxy.service failed.
18.5、配置文件
[root@localhost ~]# tree -C /usr/local/src/haproxy-2.1.3/examples/
/usr/local/src/haproxy-2.1.3/examples/
├── acl-content-sw.cfg
├── content-sw-sample.cfg
├── errorfiles
│   ├── 400.http
│   ├── 403.http
│   ├── 408.http
│   ├── 500.http
│   ├── 502.http
│   ├── 503.http
│   ├── 504.http
│   └── README
├── haproxy.init
├── option-http_proxy.cfg
├── socks4.cfg
├── transparent_proxy.cfg
└── wurfl-example.cfg

1 directory, 15 files
[root@localhost ~]# mkdir  /etc/haproxy
[root@localhost ~]# vim /etc/haproxy/haproxy.cfg
global
    maxconn 100000
    chroot /apps/haproxy
    stats socket /var/lib/haproxy/haproxy.sock mode 600 level admin
    #uid 99
    #gid 99
    user  haproxy
    group haproxy
    daemon
    #nbproc 4
    #cpu-map 1 0
    #cpu-map 2 1
    #cpu-map 3 2
    #cpu-map 4 3
    pidfile /var/lib/haproxy/haproxy.pid
    log 127.0.0.1 local2 info

defaults
    option http-keep-alive
    option  forwardfor
    maxconn 100000
    mode http
    timeout connect 300000ms
    timeout client  300000ms
    timeout server  300000ms

listen stats
    mode http
    bind 0.0.0.0:9999
    stats enable
    log global
    stats uri     /haproxy-status
    stats auth    admin:123456

listen  web_port
    bind 192.168.88.10:80
    mode http
    log global
    server web1  127.0.0.1:8080  check inter 3000 fall 2 rise 5
18.6、启动haproxy
[root@localhost ~]# mkdir  /var/lib/haproxy
[root@localhost ~]# useradd -r -s /sbin/nologin -d /var/lib/haproxy haproxy
[root@localhost ~]# systemctl  enable --now haproxy
18.7、验证haproxy状态

aproxy.cfg文件中定义了chroot、pidfile、user、group等参数,如果系统没有相应的资源会导致haproxy无法启动,具体参考日志文件 /var/log/messages

[root@localhost ~]# systemctl status haproxy haproxy.service - HAProxy Load Balancer
   Loaded: loaded (/usr/lib/systemd/system/haproxy.service; enabled; vendor preset: disabled)
   Active: active (running) since  2021-07-13 10:45:39 CST; 38s ago
  Process: 8360 ExecStartPre=/usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg -c -q (code=exited, status=0/SUCCESS)
 Main PID: 8363 (haproxy)
   CGroup: /system.slice/haproxy.service
           ├─8363 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /var/lib/haproxy/hap...
           └─8365 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /var/lib/haproxy/hap...

7月 13 10:45:39 localhost.localdomain systemd[1]: Starting HAProxy Load Balancer...
7月 13 10:45:39 localhost.localdomain systemd[1]: Started HAProxy Load Balancer.
7月 13 10:45:39 localhost.localdomain haproxy[8363]: [NOTICE] 193/104539 (8363) : New wor...d
7月 13 10:45:39 localhost.localdomain haproxy[8363]: [WARNING] 193/104539 (8365) : Server....
7月 13 10:45:39 localhost.localdomain haproxy[8363]: [ALERT] 193/104539 (8365) : proxy 'w...!
Hint: Some lines were ellipsized, use -l to show in full.

[root@localhost ~]# pstree -p |grep haproxy
           |-haproxy(8363)---haproxy(8365)-+-{haproxy}(8366)
           |                               |-{haproxy}(8367)
           |                               `-{haproxy}(8368)
18.8、查看haproxy的状态页面

image-346

image-347

19、HAProxy基础配置详解

官方文档:http://cbonte.github.io/haproxy-dconv/2.1/configuration.html

HAProxy 的配置文件haproxy.cfg由两大部分组成,分别是global和proxies部分

19.1、global配置

global 配置参数说明

官方文档:http://cbonte.github.io/haproxy-dconv/2.1/configuration.html#3

chroot #锁定运行目录
deamon #以守护进程运行
stats socket /var/lib/haproxy/haproxy.sock mode 600 level admin process 1 #socket文件
user, group, uid, gid  #运行haproxy的用户身份
nbproc    n     #开启的haproxy work 进程数,默认进程数是一个
#nbthread  1    #指定每个haproxy进程开启的线程数,默认为每个进程一个线程,和nbproc互斥(版本有关)
#如果同时启用nbproc和nbthread 会出现以下日志的错误,无法启动服务
Apr  7 14:46:23 haproxy haproxy: [ALERT] 097/144623 (1454) : config : cannot enable multiple processes if multiple threads are configured. Please use either nbproc or nbthread but not both.

cpu-map 1 0     #绑定haproxy 进程至指定CPU,将第一个work进程绑定至0号CPU
maxconn  n      #每个haproxy进程的最大并发连接数
maxsslconn  n   #每个haproxy进程ssl最大连接数,用于haproxy配置了证书的场景下
maxconnrate n   #每个进程每秒创建的最大连接数量
spread-checks n #后端server状态check随机提前或延迟百分比时间,建议2-5(20%-50%)之间,默认值0
pidfile         #指定pid文件路径
log 127.0.0.1  local2 info #定义全局的syslog服务器;日志服务器需要开启UDP协议,最多可以定义两个

多进程和线程

范例:多进程和socket文件

[root@localhost ~]# vim /etc/haproxy/haproxy.cfg
global
    maxconn 100000
    chroot /apps/haproxy
    stats socket /var/lib/haproxy/haproxy.sock1 mode 600 level admin process 1               
    stats socket /var/lib/haproxy/haproxy.sock2 mode 600 level admin process 2
    user  haproxy
    group haproxy
    daemon
    nbproc 2

[root@localhost ~]# systemctl restart haproxy
[root@localhost ~]# pstree -p |grep haproxy
           |-haproxy(8395)-+-haproxy(8399)
           |               `-haproxy(8400)
[root@localhost ~]# ll /var/lib/haproxy/
总用量 4
-rw-r--r--. 1 root root 5 7月  13 10:56 haproxy.pid
srw-------. 1 root root 0 7月  13 10:56 haproxy.sock1
srw-------. 1 root root 0 7月  13 10:56 haproxy.sock2
19.2、日志配置
19.2.1、HAProxy配置
# 在global配置项定义
log 127.0.0.1  local{1-7} info #基于syslog记录日志到指定设备,级别有(err、warning、info、debug)

listen  web_port
  bind 127.0.0.1:80
  mode http
  log global                                #开启当前web_port的日志功能,默认不记录日志
  server web1  127.0.0.1:8080  check inter 3000 fall 2 rise 5

# systemctl  restart haproxy
19.2.2、Rsyslog配置
[root@localhost ~]# vim /etc/rsyslog.conf

ModLoad imudpUDPServerRun 514
......
local3.*    /var/log/haproxy.log
......

# systemctl  restart rsyslog
19.2.3、验证HAProxy日志
[root@localhost ~]# tail -f /var/log/haproxy.log
Aug 14 20:21:06 localhost haproxy[18253]: Connect from 192.168.0.1:3050 to 10.0.0.7:80 (web_host/HTTP)
Aug 14 20:21:06 localhost haproxy[18253]: Connect from 192.168.0.1:3051 to 10.0.0.7:80 (web_host/HTTP)
Aug 14 20:21:06 localhost haproxy[18253]: Connect from 192.168.0.1:3050 to 10.0.0.7:80 (web_host/HTTP)

20、Proxies配置

官方文档:http://cbonte.github.io/haproxy-dconv/2.1/configuration.html#4

defaults [<name>]
frontend <name>
backend <name>
listen <name>

注意:name字段只能使用大小写字母,数字,‘-’(dash),’_‘(underscore),’.’ (dot)和 ‘:'(colon),并且严格区分大小写

image-348

20.1、Proxies配置-defaults
option redispatch       #当server Id对应的服务器挂掉后,强制定向到其他健康的服务器,重新派发
option abortonclose     #当服务器负载很高时,自动结束掉当前队列处理比较久的链接,针对业务情况选择开启
option http-keep-alive  #开启与客户端的会话保持
option  forwardfor      #透传客户端真实IP至后端web服务器
mode http|tcp           #设置默认工作类型,使用TCP服务器性能更好,减少压力
timeout http-keep-alive 120s #session 会话保持超时时间,此时间段内会转发到相同的后端服务器
timeout connect 120s    #客户端请求从haproxy到后端server最长连接等待时间(TCP连接之前),默认单位ms
timeout server  600s    #客户端请求从haproxy到后端服务端的请求处理超时时长(TCP连接之后),默认单位ms,如果超时,会出现502错误,此值建议设置较大些,访止502错误
timeout client  600s    #设置haproxy与客户端的最长非活动时间,默认单位ms,建议和timeout server相同
timeout  check   5s     #对后端服务器的默认检测超时时间
default-server inter 1000 weight 3   #指定后端服务器的默认设置
20.2、Proxies配置-frontend
bind:   #指定HAProxy的监听地址,可以是IPV4或IPV6,可以同时监听多个IP或端口,可同时用于listen字段中

#格式:
bind [<address>]:<port_range> [, ...] [param*]

#注意:如果需要绑定在非本机的IP,需要开启内核参数:net.ipv4.ip_nonlocal_bind=1
listen http_proxy                           #监听http的多个IP的多个端口和sock文件
    bind :80,:443,:8801-8810
    bind 10.0.0.1:10080,10.0.0.1:10443
    bind /var/run/ssl-frontend.sock user root mode 600 accept-proxy

listen http_https_proxy                     #https监听
    bind :80
    bind :443 ssl crt /etc/haproxy/site.pem #公钥和私钥公共文件

listen http_https_proxy_explicit            #监听ipv6、ipv4和unix sock文件
    bind ipv6@:80
    bind ipv4@public_ssl:443 ssl crt /etc/haproxy/site.pem
    bind unix@ssl-frontend.sock user root mode 600 accept-proxy

listen external_bind_app1                   #监听file descriptor
    bind "fd@${FD_APP1}"
frontend  web_port               #可以采用后面形式命名:业务-服务-端口号
    bind :80,:8080
    bind 10.0.0.7:10080,:8801-8810,10.0.0.17:9001-9010
    mode  http|tcp              #指定负载协议类型
    use_backend <backend_name>  #调用的后端服务器组名称
20.3、Proxies配置-backend
mode  http|tcp      #指定负载协议类型,和对应的frontend必须一致
option              #配置选项
server              #定义后端real server

注意:option后面加 httpchk,smtpchk,mysql-check,pgsql-check,ssl-hello-chk方法,可用于实现更多应用层检测功能。

20.3.1、option 配置
check               #对指定real进行健康状态检查,如果不加此设置,默认不开启检查
    addr  <IP>        #可指定的健康状态监测IP,可以是专门的数据网段,减少业务网络的流量
    port  <num>   #指定的健康状态监测端口
    inter <num>   #健康状态检查间隔时间,默认2000 ms
    fall  <num>       #后端服务器从线上转为线下的检查的连续失效次数,默认为3
    rise  <num>       #后端服务器从下线恢复上线的检查的连续有效次数,默认为2
weight  <weight>  #默认为1,最大值为256,0表示不参与负载均衡,但仍接受持久连接
backup              #将后端服务器标记为备份状态,只在所有非备份主机down机时提供服务,类似Sorry Server
disabled            #将后端服务器标记为不可用状态,即维护状态,除了持久模式,将不再接受连接
redirect prefix  http://www.baidu.com/      #将请求临时(302)重定向至其它URL,只适用于http模式
redir http://www.baidu.com                  #将请求临时(302)重定向至其它URL,只适用于http模式
maxconn <maxconn>     #当前后端server的最大并发连接数
backlog <backlog> #当前端服务器的连接数达到上限后的后援队列长度,注意:不支持backend
20.4、frontend+backend配置实例

image-349

[root@node1 ~]# yum -y install httpd
[root@node1 ~]# echo "node1 ..." > /var/www/html/index.html
[root@node1 ~]# systemctl start httpd
[root@node1 ~]# systemctl enable httpd
[root@haproxy ~]# vim /etc/haproxy/haproxy.cfg
global
    maxconn 100000
    chroot /apps/haproxy
    stats socket /var/lib/haproxy/haproxy.sock mode 600 level admin
    #uid 99
    #gid 99
    user  haproxy
    group haproxy
    daemon
    #nbproc 4
    #cpu-map 1 0
    #cpu-map 2 1
    #cpu-map 3 2
    #cpu-map 4 3
    pidfile /var/lib/haproxy/haproxy.pid
    log 127.0.0.1 local2 info

defaults
    option http-keep-alive
    option  forwardfor
    maxconn 100000
    mode http
    timeout connect 300000ms
    timeout client  300000ms
    timeout server  300000ms

listen stats
    mode http
    bind 0.0.0.0:9999
    stats enable
    log global
    stats uri     /haproxy-status
    stats auth    admin:123456

frontend test-http
    bind :80
    mode tcp
    use_backend test-http-nodes

backend test-http-nodes
    mode tcp
    default-server inter 1000 weight 6
    server web1 192.168.88.20:80 check inter 3000 fall 3 rise 5 weight 2 addr 192.168.88.20 port 3306
    server web2 192.168.88.30:80 check inter 3000 fall 3 rise 5

[root@haproxy ~]# systemctl restart haproxy

image-350

image-351

20.5、listen替代frontend+backend

使用listen替换上面的frontend和backend的配置方式,可以简化设置,通常只用于TCP协议的应用

listen  WEB_PORT_80
    bind :80
    mode http
    option  forwardfor
    # 这个选项的作用是启用或禁用将原始客户端的IP地址添加到HTTP请求的X-Forwarded-For头部中。
    server web1   192.168.88.20:80   check inter 3000 fall 3 rise 5
    server web2   192.168.88.30:80   check inter 3000 fall 3 rise 5

[root@haproxy ~]# systemctl restart haproxy
20.6、使用子配置文件保存配置

当业务众多时,将所有配置都放在一个配置文件中,会造成维护困难。可以考虑按业务分类,将配置信息拆分,放在不同的子配置文件中,从而达到方便维护的目的。

[root@haproxy ~]# mkdir /etc/haproxy/conf.d/
[root@haproxy ~]# vim /etc/haproxy/conf.d/test.cfg
listen  WEB_PORT_80
    bind :80
    mode http
    option  forwardfor
    balance roundrobin
    server web1   192.168.88.20:80   check inter 3000 fall 3 rise 5
    server web2   192.168.88.30:80   check inter 3000 fall 3 rise 5
[root@haproxy ~]# vim /lib/systemd/system/haproxy.service
[Unit]
Description=HAProxy Load Balancer
After=syslog.target network.target

[Service]
ExecStartPre=/usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg -f /etc/haproxy/conf.d/ -c -q
ExecStart=/usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -f /etc/haproxy/conf.d/  -p /var/lib/haproxy/haproxy.pid
ExecReload=/bin/kill -USR2 $MAINPID

[Install]
WantedBy=multi-user.target

[root@haproxy ~]# systemctl daemon-reload
[root@haproxy ~]# systemctl restart haproxy

21、HAProxy调度算法

HAProxy通过固定参数balance指明对后端服务器的调度算法,该参数可以配置在listen或backend选项中。

HAProxy的调度算法分为静态和动态调度算法,但是有些算法可以根据参数在静态和动态算法中相互转换。

官方文档:http://cbonte.github.io/haproxy-dconv/2.1/configuration.html#4-balance

21.1、静态算法

静态算法:按照事先定义好的规则轮询公平调度,不关心后端服务器的当前负载、链接数和响应速度等,且无法实时修改权重,只能靠重启HAProxy生效。

21.1.1、static-rr

static-rr:基于权重的轮询调度,不支持权重的运行时利用socat进行动态调整及后端服务器慢启动,其后端主机数量没有限制,相当于LVS中的 wrr

listen  web_host
  bind 10.0.0.7:80,:8801-8810,10.0.0.7:9001-9010
  mode http
  log global
  balance static-rr
  server web1  10.0.0.17:80 weight 1 check inter 3000 fall 2 rise 5
  server web2  10.0.0.27:80 weight 2 check inter 3000 fall 2 rise 5
21.1.2、first

first:根据服务器在列表中的位置,自上而下进行调度,但是其只会当第一台服务器的连接数达到上限,新请求才会分配给下一台服务,因此会忽略服务器的权重设置,此方式使用较少

listen  web_host
  bind 10.0.0.7:80,:8801-8810,10.0.0.7:9001-9010
  mode http
  log global
  balance first
  server web1  192.168.88.20:80 maxconn 2 weight 1 check inter 3000 fall 2 rise 5
  server web2  192.168.88.30:80 weight 1 check inter 3000 fall 2 rise 5

测试访问效果,同时运行下面命令,观察结果

while  true;do  curl http://192.168.88.10/index.html ; sleep 0.1;done
21.2、动态算法

动态算法:基于后端服务器状态进行调度适当调整,优先调度至当前负载较低的服务器,且权重可以在haproxy运行时动态调整无需重启。

21.2.1、roundrobin

roundrobin:基于权重的轮询动态调度算法,支持权重的运行时调整,不同于lvs中的rr轮询模式,HAProxy中的roundrobin支持慢启动(新加的服务器会逐渐增加转发数),其每个后端backend中最多支持4095个real server,支持对real server权重动态调整,roundrobin为默认调度算法

listen  web_host
  bind 10.0.0.7:80,:8801-8810,10.0.0.7:9001-9010
  mode http
  log global
  balance roundrobin
  server web1  10.0.0.17:80 weight 1  check inter 3000 fall 2 rise 5
  server web2  10.0.0.27:80 weight 2  check inter 3000 fall 2 rise 5

支持动态调整权重

# echo "get weight web_host/web1" | socat stdio /var/lib/haproxy/haproxy.sock 
1 (initial 1)

# echo "set weight web_host/web1 3" | socat stdio /var/lib/haproxy/haproxy.sock 

# echo "get weight web_host/web1" | socat stdio /var/lib/haproxy/haproxy.sock 
3 (initial 1)
21.2.2、leastconn

leastconn加权的最少连接的动态,支持权重的运行时调整和慢启动,即当前后端服务器连接最少的优先调度(新客户端连接),比较适合长连接的场景使用,比如:MySQL等场景。

listen  web_host
  bind 10.0.0.7:80,:8801-8810,10.0.0.7:9001-9010
  mode http
  log global
  balance leastconn
  server web1  10.0.0.17:80 weight 1  check inter 3000 fall 2 rise 5
  server web2  10.0.0.27:80 weight 1  check inter 3000 fall 2 rise 5
21.2.3、random

在1.9版本开始增加一个叫做random的负载平衡算法,其基于随机数作为一致性hash的key,随机负载平衡对于大型服务器场或经常添加或删除服务器非常有用,支持weight的动态调整,weight较大的主机有更大概率获取新请求

listen  web_host
  bind 10.0.0.7:80,:8801-8810,10.0.0.7:9001-9010
  mode http
  log global
  balance  random
  server web1  10.0.0.17:80 weight 1  check inter 3000 fall 2 rise 5
  server web2  10.0.0.27:80 weight 1  check inter 3000 fall 2 rise 5
21.3、其他算法

其它算法即可作为静态算法,又可以通过选项成为动态算法

21.3.1、source

源地址hash,基于用户源地址hash并将请求转发到后端服务器,后续同一个源地址请求将被转发至同一个后端web服务器。此方式当后端服务器数据量发生变化时,会导致很多用户的请求转发至新的后端服务器,默认为静态方式,但是可以通过hash-type支持的选项更改

这个算法一般是在不插入Cookie的TCP模式下使用,也可给拒绝会话cookie的客户提供最好的会话粘性,适用于session会话保持但不支持cookie和缓存的场景

源地址有两种转发客户端请求到后端服务器的服务器选取计算方式,分别是取模法和一致性hash

map-base取模法

map-based:取模法,对source地址进行hash计算,再基于服务器总权重的取模,最终结果决定将此请求转发至对应的后端服务器。此方法是静态的,即不支持在线调整权重,不支持慢启动,可实现对后端服务器均衡调度。缺点是当服务器的总权重发生变化时,即有服务器上线或下线,都会因总权重发生变化而导致调度结果整体改变,hash-type 指定的默认值为此算法

所谓取模运算,就是计算两个数相除之后的余数,10%7=3, 7%4=3 map-based算法:基于权重取模,hash(source_ip)%所有后端服务器相加的总权重

listen  web_host
  bind 10.0.0.7:80,:8801-8810,10.0.0.7:9001-9010
  mode tcp
  log global
  balance source
  hash-type map-based 
  server web1  10.0.0.17:80 weight 1  check inter 3000 fall 2 rise 3
  server web2  10.0.0.27:80 weight 1  check inter 3000 fall 2 rise 3

[root@haproxy ~]#echo "set weight web_host/10.0.0.27 10" | socat stdio /var/lib/haproxy/haproxy.sock 
Backend is using a static LB algorithm and only accepts weights '0%' and '100%'.

[root@haproxy ~]#echo "set weight web_host/10.0.0.27 0" | socat stdio /var/lib/haproxy/haproxy.sock 

[root@haproxy conf.d]#echo "get weight web_host/10.0.0.27" | socat stdio /var/lib/haproxy/haproxy.sock 
0 (initial 1)

一致性hash

一致性哈希,当服务器的总权重发生变化时,对调度结果影响是局部的,不会引起大的变动,hash(o)mod n ,该hash算法是动态的,支持使用 socat等工具进行在线权重调整,支持慢启动

1、key1=hash(source_ip)%(2^32) [0---4294967295] 2、keyA=hash(后端服务器虚拟ip)%(2^32) 3、将key1和keyA都放在hash环上,将用户请求调度到离key1最近的keyA对应的后端服务器

listen  web_host
  bind 10.0.0.7:80,:8801-8810,10.0.0.7:9001-9010
  mode tcp
  log global
  balance source
  hash-type consistent
  server web1  10.0.0.17:80 weight 1  check inter 3000 fall 2 rise 5
  server web2  10.0.0.27:80 weight 1  check inter 3000 fall 2 rise 5
21.3.2、uri

基于对用户请求的URI的左半部分或整个uri做hash,再将hash结果对总权重进行取模后,根据最终结果将请求转发到后端指定服务器,适用于后端是缓存服务器场景,默认是静态,也可以通过hash-type指定map-based和consistent,来定义使用取模法还是一致性hash。

注意:此算法是应用层,所有只支持 mode http ,不支持 mode tcp

<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>
左半部分:/<path>;<params>
整个uri:/<path>;<params>?<query>#<frag>
listen  web_host
  bind 10.0.0.7:80,:8801-8810,10.0.0.7:9001-9010
  mode http
  log global
  balance uri
  server web1  10.0.0.17:80 weight 1  check inter 3000 fall 2 rise 5
  server web2  10.0.0.27:80 weight 1  check inter 3000 fall 2 rise 5
listen  web_host
  bind 10.0.0.7:80,:8801-8810,10.0.0.7:9001-9010
  mode http
  log global
  balance uri
  hash-type consistent
  server web1  10.0.0.17:80 weight 1  check inter 3000 fall 2 rise 5
  server web2  10.0.0.27:80 weight 1  check inter 3000 fall 2 rise 5
21.3.3、url_param

url_param对用户请求的url中的 params 部分中的一个参数key对应的value值作hash计算,并由服务器总权重相除以后派发至某挑出的服务器;通常用于追踪用户,以确保来自同一个用户的请求始终发往同一个real server,如果无没key,将按roundrobin算法

假设:
url = http://www.test.com/foo/bar/index.php?key=value

则:
host = "www.test.com"
url_param = "key=value"
listen  web_host
  bind 10.0.0.7:80,:8801-8810,10.0.0.7:9001-9010
  mode http
  log global
  balance url_param  userid     #url_param hash
  server web1 10.0.0.17:80 weight 1  check inter 3000 fall 2 rise 5
  server web2 10.0.0.27:80 weight 1  check inter 3000 fall 2 rise 5
listen  web_host
  bind 10.0.0.7:80,:8801-8810,10.0.0.7:9001-9010
  mode http
  log global
  balance url_param  userid             #对url_param的值取hash
  hash-type consistent
  server web1  10.0.0.17:80 weight 1  check inter 3000 fall 2 rise 5
  server web2  10.0.0.27:80 weight 1  check inter 3000 fall 2 rise 5
21.3.4、hdr

针对用户每个http头部(header)请求中的指定信息做hash,此处由 name 指定的http首部将会被取出并做hash计算,然后由服务器总权重取模以后派发至某挑出的服务器,如无有效的值,则会使用默认的轮询调度。

listen  web_host
  bind 10.0.0.7:80,:8801-8810,10.0.0.7:9001-9010
  mode http
  log global
  balance hdr(User-Agent)
  #balance hdr(host)
  server web1  10.0.0.17:80 weight 1  check inter 3000 fall 2 rise 5
  server web2  10.0.0.27:80 weight 1  check inter 3000 fall 2 rise 5
listen  web_host
  bind 10.0.0.7:80,:8801-8810,10.0.0.7:9001-9010
  mode http
  log global
  balance hdr(User-Agent)
  hash-type consistent
  server web1  10.0.0.17:80 weight 1  check inter 3000 fall 2 rise 5
  server web2  10.0.0.27:80 weight 1  check inter 3000 fall 2 rise 5

rdp-cookie对远windows远程桌面的负载,使用cookie保持会话,默认是静态,也可以通过hash-type指定map-based和consistent,来定义使用取模法还是一致性hash。

listen RDP
  bind 10.0.0.7:3389
  balance rdp-cookie
  mode tcp
  server rdp0 10.0.0.17:3389 check fall 3 rise 5 inter 2000 weight 1
[root@haproxy ~]#cat /etc/haproxy/conf.d/windows_rdp.cfg 
listen magedu_RDP_3389
  bind  172.16.0.100:3389
  balance rdp-cookie
  hash-type consistent
  mode tcp
  server rdp0 10.0.0.200:3389 check fall 3 rise 5 inter 2000 weight 1

[root@haproxy ~]#hostname -I
10.0.0.100 172.16.0.100
21.4、算法总结
static-rr--------->tcp/http  静态
first------------->tcp/http  静态

roundrobin-------->tcp/http 动态
leastconn--------->tcp/http 动态
random------------>tcp/http 动态

以下静态和动态取决于hash_type是否consistent
source------------>tcp/http
Uri--------------->http
url_param--------->http     
hdr--------------->http
rdp-cookie-------->tcp
21.5、各算法使用场景
first       #使用较少

static-rr   #做了session共享的web集群
roundrobin
random

leastconn   #数据库
source      #基于客户端公网IP的会话保持

uri--------------->http  #缓存服务器,CDN服务商,蓝汛、百度、阿里云、腾讯
url_param--------->http 

hdr         #基于客户端请求报文头部做下一步处理

rdp-cookie  #很少使用

22、HAProxy状态页

通过web界面,显示当前HAProxy的运行状态

官方帮助:http://cbonte.github.io/haproxy-dconv/2.1/configuration.html#4-stats%20admin

stats enable                #基于默认的参数启用stats page
stats hide-version          #将状态页中haproxy版本隐藏
stats refresh <delay>         #设定自动刷新时间间隔,默认不自动刷新
stats uri <prefix>        #自定义stats page uri,默认值:/haproxy?stats 
stats realm <realm>       #账户认证时的提示信息,示例:stats realm   HAProxy\ Statistics
stats auth <user>:<passwd>  #认证时的账号和密码,可使用多次,默认:no authentication,可有多行用户
stats admin { if | unless } <cond> #启用stats page中的管理功能
listen stats
  bind :9999
  stats enable
  #stats hide-version 
  stats uri  /haproxy-status
  stats realm HAPorxy\ Stats\ Page
  stats auth haadmin:123456             #两个用户
  stats auth admin:123456
  #stats refresh 30s
  stats admin if TRUE                   #安全原因,不建议打开
pid = 27134 (process #1, nbproc = 1, nbthread = 1) #pid为当前pid号,process为当前进程号,nbproc和nbthread为一共多少进程和每个进程多少个线程
uptime = 0d 0h00m04s #启动了多长时间
system limits: memmax = unlimited; ulimit-n = 200029 #系统资源限制:内存/最大打开文件数/
maxsock = 200029; maxconn = 100000; maxpipes = 0 #最大socket连接数/单进程最大连接数/最大管道数maxpipes
current conns = 2; current pipes = 0/0; conn rate = 2/sec; bit rate = 0.000 kbps #当前连接数/当前管道数/当前连接速率
Running tasks: 1/14; idle = 100 %        #运行的任务/当前空闲率
active UP:                              #在线服务器
backup UP:                              #标记为backup的服务器
active UP, going down:                  #监测未通过正在进入down过程
backup UP, going down:                  #备份服务器正在进入down过程
active DOWN, going up:                  #down的服务器正在进入up过程
backup DOWN, going up:                  #备份服务器正在进入up过程
active or backup DOWN:                  #在线的服务器或者是backup的服务器已经转换成了down状态
not checked:                            #标记为不监测的服务器
active or backup DOWN for maintenance (MAINT) #active或者backup服务器人为下线的
active or backup SOFT STOPPED for maintenance #active或者backup被人为软下线(人为将weight改成0)

image-352

22.1、backend server信息
session rate(每秒的连接会话信息): Errors(错误统计信息):
cur:每秒的当前会话数量 Req:错误请求量
max:每秒新的最大会话数量 conn:错误链接量
limit:每秒新的会话限制量 Resp:错误响应量
sessions(会话信息): Warnings(警告统计信息):
cur:当前会话量 Retr:重新尝试次数
max:最大会话量 Redis:再次发送次数
limit: 限制会话量
Total:总共会话量 Server(real server信息):
LBTot:选中一台服务器所用的总时间 Status:后端机的状态,包括UP和DOWN
Last:和服务器的持续连接时间 LastChk:持续检查后端服务器的时间
Wght:权重
Bytes(流量统计): Act:活动链接数量
In:网络的字节输入总量 Bck:备份的服务器数量
Out:网络的字节输出总量 Chk:心跳检测时间
Dwn:后端服务器连接后都是DOWN的数量
Denied(拒绝统计信息): Dwntme:总的downtime时间
Req:拒绝请求量 Thrtle:server 状态
Resp:拒绝回复量
22.2、利用状态页实现haproxy服务器的健康性检查
[root@haproxy ~]# curl -I  http://admin:123456@192.168.88.10:9999/haproxy-status
HTTP/1.1 200 OK
cache-control: no-cache
content-type: text/html

[root@haproxy ~]# curl -I -u admin:123456 http://192.168.88.10:9999/haproxy-status
HTTP/1.1 200 OK
cache-control: no-cache
content-type: text/html

评论区