- Linux企业服务(下)
- 一、Nginx
- 二、MySQL数据库
- 1、数据库介绍
- 2、MySQL的安装
- 3、MySQL架构
- 4、Mysql用户权限管理
- 5、Mysql启动关闭流程
- 6、Mysql实例初始化配置
- 7、MySQL多实例配置
- 8、SQL语句
- 9、字符集定义
- 10、MySQL数据类型
- 11、索引介绍
- 12、MySQL的存储引擎
- 13、MySQL事物
- 14、MySQL日志管理
- 15、备份与恢复
- 16、MySQL的主从复制
- 17、MHA高可用架构
- 三、DQL查询练习
- 1、查询练习
- 1.1、升降序练习(order by,asc升序 desc)
- 1.2、统计数量(count)
- 1.3、子查询
- 1.4、切片(limit)
- 1.5、计算平均成绩(avg,group by)
- 1.6、分组条件及模糊查询(group by having,like)
- 1.7、范围查询(between and)
- 1.8、多表查询(寻找相同条件)
- 1.9、子查询加分组求平均分(in )
- 1.10、子查询深入
- 1.11、年份函数(year)
- 1.12、嵌套子查询
- 1.13、in的使用
- 1.14、综合使用
- 1.15、求并集操作(union)
- 1.16、多个值比较多个值:至少(any)
- 1.17、多个值比较多个值:所有(all)
- 1.18、使用别名(as)
- 1.19、复制表数据作条件查询(别名的使用)
- 1.20、条件加分组查询
- 1.21、模糊查询取反(not like,%)
- 1.22、计算年龄大小(year(now()))
- 1.23、最大值最小值(min,max)
- 1.24、多字段排序
- 1.25、子查询练习
- 2、连接查询
- 1、查询练习
- 四、MongoDB
- 五、Redis
- 1、缓存的概念
- 2、redis 基础
- 3、Redis 安装及连接
- 4、redis 配置和优化
- 5、Redis 常用命令
- 6、redis 数据类型
- 7、消息队列
- 8、redis 主从复制
- 9、redis 哨兵(Sentinel)
- 10、Redis Cluster
- 六、Nginx反向代理
- 七、负载均衡
- 1、Nginx负载均衡基本概述
- 2、Nginx负载均衡配置场景
- 3、Nginx负载均衡调度算法
- 4、Nginx负载均衡后端状态
- 5、Nginx负载均衡健康检查
- 6、Nginx负载均衡会话保持
- 7、Nginx四层负载均衡概述
- 8、Keepalived 高可用基本概述
- 9、Keepalived高可用安装配置
- 10、高可用keepalived抢占式与非抢占式
- 11、高可用keepalived故障脑裂
- 12、高可用keepalived与nginx
- 13、集群和分布式
- 14、Linux Virtual Server简介
- 15、LVS 工作模式和相关命令
- 16、LVS实战案例
- 17、HAProxy介绍
- 18、编译安装HAProxy
- 19、HAProxy基础配置详解
- 20、Proxies配置
- 21、HAProxy调度算法
- 22、HAProxy状态页
Linux企业服务(下)
一、Nginx
1、Nginx介绍
Nginx 是免费的、开源的、⾼性能的HTTP和反向代理服务器、邮件代理服务器、以及TCP/UDP代理服务器 解决C10K问题(10K Connections)
1.1、Nginx所具备的功能
- 静态的web资源服务器html,图⽚,js,css,txt等静态资源
- 结合FastCGI/uWSGI/SCGI等协议反向代理动态资源请求
- http/https协议的反向代理
- imap4/pop3协议的反向代理
- tcp/udp协议的请求转发(反向代理)
1.1.1、web服务相关的功能支持
- 虚拟主机(server)
- ⽀持 keep-alive 和管道连接(利用⼀个连接做多次请求)
- 访问⽇志(⽀持基于⽇志缓冲提⾼其性能)
- url rewirte
- 路径别名
- 基于IP及用户的访问控制
- ⽀持速率限制及并发数限制
- 重新配置和在线升级而无需中断客⼾的⼯作进程
1.2、基本特征
- 模块化设计,较好的扩展性
- ⾼可靠性 远远超过apache
- ⽀持热部署:不停机更新配置⽂件,升级版本,更换⽇志⽂件
- 低内存消耗:10000个keep-alive连接模式下的⾮活动连接,仅需2.5M内存
- event-driven,aio,mmap(内存映射),sendfile
2、Nginx架构和进程结构
2.1、Nginx架构
简单来讲,Nginx采用了master-worker架构,由master进程负责通信和调度,woker进程响应具体的请求
2.2、Nginx进程结构
常见web请求处理机制
- 多进程方式:服务器每接收到一个客户端请求就有服务器的主进程生成一个子进程响应客户端,直到用户关闭连接,这样的优势是处理速度快。子进程之间相互独立,但是如果访问过大会导致服务器资源耗尽而无法提供请求。
- 多线程方式:与多进程方式类似,但是每收到一个客户端请求会有服务进程派生出一个线程来个客户方进行交互,一个线程的开销远远小于一个进程,因此多线程方式在很大程度减轻了web服务器对系统资源的要求,但是多线程也有自己的缺点。即当多个线程位于同一个进程内工作的时候,可以相互访问同样的内存地址空间,所以他们相互影响,一旦主进程挂掉则所有子线程都不能工作了,IIS服务器使用了多线程的方式,需要间隔一段时间就重启一次才能稳定。
Nginx是多进程组织模型,而且是一个由Master主进程和Worker工作进程组成。
2.2.1、主进程(master process)的功能:
- 对外接口:接收外部的操作(信号)
- 对内转发:根据外部的操作的不同,通过信号管理worker
- 监控:监控worker进程的运行状态,worker进程异常终止后,自动重启worker进程
- 读取Nginx配置文件并验证其有效性和正确性
- 建立、绑定和关闭socket连接
- 按照配置生成、管理和结束工作进程
- 接受外界指令,比如重启、升级及退出服务器等指令
- 不中断服务,实现平滑升级,重启服务并应用新的配置
- 开启日志文件,获取文件描述符
- 不中断服务,实现平滑升级,升级失败进行回滚处理
- 编译和处理perl脚本
2.2.2、工作进程(worker process的功能:
- 所有Worker进程都是平等的
- 实际处理:网络请求,由Worker进程处理
- Worker进程数量:在nginx.conf 中配置,一般设置为核心数,充分利用CPU资源,同时,避免进程数量过多,避免进程竞争CPU资源,增加
- 上下文切换的损耗
- 接受处理客户的请求
- 将请求依次送入各个功能模块进行处理
- I/O调用,获取响应数据
- 与后端服务器通信,接收后端服务器的处理结果
- 缓存数据,访问缓存索引,查询和调用缓存数据
- 发送请求结果,响应客户的请求
- 接收主程序指令,比如重启、升级和退出等
如下图所示:
Master进程工作细节:
2.3、Nginx进程间通信
工作进程是由主进程生成的,主进程由root启用,主进程使用fork()函数,在Nginx服务器启动过程中主进程根据配置文件决定启动工作进程的数量,然后建立一张全局的工作表用于存放当前未退出的所有的工作进程,主进程生成工作进程后会将新生成的工作进程加入到工作进程表中,并建立一个单向的管道并将其传递给工作进程,该管道与普通的管道不同,它是由主进程指向工作进程的单项通道,包含了主进程向工作进程发出的指令、工作进程ID、工作进程在工作进程表中的索引和必要的文件描述符等信息,单向管道,工作进程只能监听内容之后读取指令。主进程与外界通过信号机制进行通信,当接收到需要处理的信号时,它通过管道向相关的工作进程发送正确的指令,每个工作进程都有能力捕获管道中的可读事件,当管道中有可读事件的时候,工作进程就会从管道中读取并解析指令,然后采取相应的执行动作,这样就完成了主进程与工作进程的交互。
工作进程之间的通信原理基本上和主进程与工作进程之间的通信是一样的,只要工作进程之间能够取得彼此的信息,建立管道即可通信,但是由于工作进程之间是完全隔离的,因此一个进程想要知道另外一个进程的状态信息就只能通过主进程来设置了。
为了实现工作进程之间的交互,主进程在生成工作进程之后,在工作进程表中进行遍历,将该新进程的ID以及针对该进程建立的管道句柄传递给工作进程中的其他进程,为工作进程之间的通信做准备,当工作进程1向工作进程2发送指令的时候,首先在主进程给它的其他工作进程工作信息中找到2的进程ID,然后将正确的指令写入指向进程2的管道,工作进程2捕获到管道中的事件后,解析指令并进行相关操作,这样就完成了工作进程之间的通信。
如下图所示:
2.4、连接建立和请求处理过程
- Nginx启动时,Master 进程,加载配置文件
- Master进程,初始化监听的socket
- Master进程,fork 出多个Worker进程
- Worker进程,竞争新的连接,获胜方通过三次握手,建立Socket连接,并处理请求
2.5、HTTP协议处理过程
3、Nginx模块介绍
nginx常见模块类型:
- 核心模块:是Nginx服务器正常运行必不可少的模块,提供错误日志记录、配置文件解析、事件驱动机制、进程管理等核心功能
- 标准HTTP模块:提供HTTP协议解析相关的功能,比如:端口配置、网页编码设置、HTTP响应头设置 等等
- 可选HTTP模块:主要用于扩展标准的HTTP功能,让Nginx能处理一些特殊的服务, 比如: Flash 多媒体传输、解析GeolP请求、网络传输压缩、安全协议SSL支持等
- 邮件服务模块:主要用于支持Nginx的邮件服务,包括对POP3协议、IMAP 协议和SMTP协议的支持
- Stream服务模块:实现反向代理功能包括TCP协议代理
- 第三方模块:是为了扩展Nginx服务器应用,完成开发者自定义功能,比如: Json支持、 Lua 支持等
部分第三方模块官方文档:http://nginx.org/en/docs/
4、Nginx安装和部署
Nginx版本分为Mainline version(主要开发版本)、Stable version(当前最新稳定版)、Legacy versions(旧的稳定版)
官方下载地址:http://nginx.org/en/download.html
对于Nginx的安装,我们常用的方法为yum在线安装和源码包编译安装
其中:
- yum的版本比较旧
- 编译安装可以更方便自定义相关路径
- 使用源码编译可以自定义相关功能,更方便业务的上的使用
4.1、YUM部署Nginx
- 查看当前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
- 通过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端口是否启动
- 访问测试:
最好是关闭防火墙和selinux后进行
[root@localhost ~]# systemctl stop firewalld
[root@localhost ~]# setenforce 0
在浏览器中输入IP地址:192.168.88.140
看到上述页面,说明已经成功部署好了nginx 的环境。
4.2、官方源码编译部署Nginx
官方源码包地址:http://nginx.org/download/
查看官方编译指令:
[root@localhost ~]# nginx -V
4.2.1、编译
通过编译的方式安装nginx前,建议先移除之前yum安装的nginx,防止多个nginx之间相互冲突~
yum remove -y nginx
- 从官网下载源码包,这里以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
- 查看编译帮助
[root@localhost nginx-1.20.0]# ./configure --help
- 开始编译
源码安装需要提前准备标准的编译器,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
- 善后工作
[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
- nginx完成安装以后,有四个主要的目录
- conf:保存nginx所有的配置文件,其中nginx.conf是nginx服务器的最核心最主要的配置文件,其他的.conf则是用来配置nginx相关的功能的,例如fastcgi功能使用的是fastcgi. conf和fastcgi.params两个文件,配置文件一般都有个样板配置文件,是文件名. default结尾,使用的使用将其复制为并将default去掉即可。
- html:目录中保存了nginx服务器的web文件,但是可以更改为其他目录保存web文件,另外还有一个50x的web文件是默认的错误页面提示页面。
- logs:用来保存ngi nx服务器的访问日志错误日志等日志,logs目录可以放在其他路径,比如/var/logs/nginx里面。
- sbin:保存nginx二进制启动脚本,可以接受不同的参数以实现不同的功能。
可以使用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不要忘记了~
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配置详解
- nginx官方帮助文档
- http://nginx.org/en/docs/
- tengine帮助文档
- http://tengine.taobao.org/documentation.html
- Nginx的配置文件的组成部分
- 主配置文件:nginx.conf
- 子配置文件:xxx/conf.d/*.conf
- fastcgi, uwsgi, scgi等协议相关的配置文件
- mime.types:支持的mime类型,MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型,MIME消息能包含文本、图像、音频、视频以及其他应用程序专用的数据,是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一-些媒体文件打开方式。
- MIME参考文档:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types
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、配置文件默认字段及格式说明
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的数量
- 使用top命令查看虚拟机中cpu的核心数
- 修改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
- 查看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;
}
- 创建pc网站配置
[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的配置中,\
和/
在正则表达式中有以下不同的作用:
\
用于转义字符:
- 在正则表达式中,一些字符有特殊含义,如
.
、*
、+
等。要匹配这些字符本身,需要在前面加上\
进行转义。 - 例如,要匹配一个字面量的
.
字符,需要使用\.
。
/
用于路径分隔:
- 在Nginx的location块中,
/
用于表示URL路径的层级关系。 - 例如,
location /admin {...}
表示匹配以/admin
开头的URL路径。
总结如下:
\
主要用于在正则表达式中对特殊字符进行转义,确保匹配字面量字符。/
主要用于表示URL路径的层级关系,在location块中使用。
精确匹配
- 精确匹配logo
[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测试即可
区分大小写
~
实现区分大小写的匹配,对于以下location的意思是该图片文件名称第一个字母必须是大写的A,第二个字母可以随意。并且后缀必须是.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
修改文件为小写之后再访问因为大小写敏感就访问不了了
不区分大小写
- ~*用来对用户请求的uri做模糊匹配,uri中无论都是大写、都是小写或者大小写混合,此模式也都会匹配,通常使用此模式匹配用户request中的静态资源,并继续做下一步操作,此方式使用较多
- 注意:此方式中,对Linux文件系统上的文件仍然是区分大小写的,如果磁盘文件不存在,仍会提示404
[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
- 它表示如果URL路径以
/images
开头,那么就使用这个location块,而不会继续尝试其他更具体的正则表达式匹配。 - 也就是说,这个location块具有更高的优先级,一旦匹配成功就不再进行其他正则表达式的匹配。
文件名后缀
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可以访问到
~*
表示使用大小写不敏感的正则表达式进行匹配。\.
表示匹配字面量的.
字符。(gif|jpg|jpeg|bmp|png|tiff|tif|ico|wmf|js|css)
是一个组合正则表达式,表示匹配这些文件扩展名。$
表示匹配以这些扩展名结尾的URL路径。
优先级
[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 =
-->location ^~ 路径
-->location ~,~* 正则
-->location 完整路径
-->location 部分起始路径
>/
生产使用案例
- 直接匹配网站根会加速Nginx访问处理(小窍门)
location = /index.html {
....
}
location / {
...
}
- 静态资源配置方法1
location ^~ /static/ {
...
}
- 静态资源配置方案2,应用较多
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
# 案例-基于用户的账户验证
- 创建用户密码文件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 非交互方式提交密码
- 检查创建好的密码本
[root@localhost ~]# tail /apps/nginx/conf/.htpasswd
user1:$apr1$N5FQRfTZ$41cCA49sQCSA9z71hlO6n.
user2:$apr1$/NXK7rS7$1AUTuESFJEQW490XjTs851
- 编辑子配置文件
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";
- 这行配置开启了基本的HTTP身份验证功能,提示信息为"login password"。
auth_basic_user_file /apps/nginx/conf/.htpasswd;
- 这行配置指定了存储用户名和密码的文件路径为
/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请求
- 是否启用asynchronous file I/O(AIO)功能,需要编译开启
--with-file-aio
- 在读取文件的时候使用异步可以提高效率
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的时候,直接写磁盘,而非写缓存
directio size | off;
- Nginx支持对磁盘中的文件进行缓存
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
- Active connections: 2 表示Nginx正在处理的活动连接数2个。
- server 2 表示Nginx启动到现在共处理了2个连接
- accepts 2 表示Nginx启动到现在共成功创建2次握手
- handled requests 1 表示总共处理了 1 次请求
- Reading:Nginx 读取到客户端的 Header 信息数,数值越大,说明排队越长,性能不足
- Writing:Nginx 返回给客户端 Header 信息数,数值越大,说明访问量越大
- Waiting:Nginx 已经处理完正在等候下一次请求指令的驻留链接(开启keep-alive的情况下,这个值等于Active-(Reading+Writing))
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、案例-压缩对比
- 编写配置文件,创建两个虚拟机主机: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
- 通过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进行加密,所以传输的数据都是加密后的数据。
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、自签名证书
- 生成ca证书
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架构是如何工作的
- 首先nginx服务是不能请求动态请求,那么当用户发起动态请求时,nginx无法处理
- 当用户发起http请求,请求会被nginx处理,如果是静态资源请求nginx则直接返回,如果是动态请求nginx则通过fastcgi协议转交给后端的PHP程序处理
7.3、Nginx与fastcgi详细工作流程
- 用户通过http协议发起请求,请求会先抵达LNMP架构中的nginx;
- nginx会根据用户的请求进行location规则匹配;
- location如果匹配到请求是静态,则由nginx读取本地直接返回;
- location如果匹配到请求是动态,则由nginx将请求转发给fastcgi协议;
- fastcgi收到请求交给php-fpm管理进程,php-fpm管理进程接收到后会调用具体的工作进程wrapper;
- wrapper进程会调用PHP程序进行解析,如果只是解析代码,php直接返回;
- 如果有查询数据库操作,则由php连接数据库(用户 密码 ip)发起查询的操作;
- 最终数据由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架构环境配置
- 在将nginx与PHP集成的过程中,需要先了解fastcgi代理配置语法
9.1、设置fastcgi服务器的地址
- 该地址可以指定为域名或IP地址,以及端口
Syntax: fastcgi_pass address;
Default:-
Context:location,if in location
#语法示例
fastcgi_pass location:9000;
fastcgi_pass unix:/tmp/fastcgi.socket;
9.2、设置fastcgi默认的首页文件
- 需要结合fastcgi_param一起设置
Syntax: fastcgi_index name;
Default:-
Context:http,server,location
9.3、通过fastcgi_param设置变量
- 将设置的变量传递到后端的fastcgi服务器
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();
?>
在浏览器中访问,可以得到如下的结果
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...成功!";
?>
使用浏览器访问,可以得到数据库连接的结果
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页面
输入数据库用户名root
和密码123456
就可以进入图形化数据库管理页面了
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、创建数据库
点击数据库
输入数据库名之后,就可以点击创建
10.3、安装博客系统
下面就可以开始进入网站安装的部分了,访问博客系统页面
填写数据库密码和网站后台管理员密码
点击开始安装之后,会出现了如下页面,这个是因为php的用户是www用户,而/code/typecho文件夹是root用户的,所以这个网站根本没有权限保存数据相关的配置到文件夹中
方法一:直接将typecho文件夹赋予www权限
方法二:手动去帮助网站创建网站没有权限的配置文件,下面将会演示方法二
直接在/code/typecho下创建config.inc.php
文件,然后将网页提示内容写入这个文件中
vim /code/typecho/config.inc.php
复制网页上的内容进去
配置文件创建完成之后,可以点击创建完毕,继续安装>>
下面是安装成功的页面
10.4、切换主题
默认的主题如下,界面比较的简洁,我们可以给这个网站替换主题,也可以借此加深熟悉我们对Linux命令行的熟练程度
打开官方主题站:https://typecho.me/
第三方主题商店:https://www.typechx.com/
这边以这个主题为例
点击模板下载
点击下载压缩包
将主题上传到博客主题的目录/code/typecho/usr/themes
# 解压压缩包,并且将主题文件夹重命名
unzip typecho-theme-sagiri-master.zip
mv typecho-theme-sagiri-master sagiri
# 可以删除旧的压缩包文件
rm -rf typecho-theme-sagiri-master.zip
进入网站后台切换主题,在地址后面加上/admin
就可以进入后台登录页面了
启用我们刚刚安装的主题
访问网页前端,查看最终的效果
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、创建数据库
点击数据库
输入数据库名之后,就可以点击创建
11.3、安装网盘系统
浏览器访问此站点,我们发现目录权限,这个比较重要
# 设置权限
chown -R www.www /code/kod
添加完成之后,刷新页面,可以看到所有条件都已经符合,就可以直接点击下一步了
填写数据库密码和数据库名
设置系统密码
完成网站安装
下面根据自己的喜好,进行简单的设置就可以正常使用啦!
我们也可以直接在这个上面编辑Linux上的文件,比如我们之前创建的php文件
二、MySQL数据库
1、数据库介绍
1.1、什么是数据
数据(data)是事实或观察的结果,是对客观事物的逻辑归纳,是用于表示客观事物的未经加工的的原始素材。
数据可以是连续的值,比如声音、图像,称为模拟数据。也可以是离散的,如符号、文字,称为数字数据。
在计算机系统中,数据以二进制信息单元0,1的形式表示。
数据的定义:数据是指对客观事件进行记录并可以鉴别的符号,是对客观事物的性质、状态以及相互关系等进行记载的物理符号或这些物理符号的组合。它是可识别的、抽象的符号。
1.2、什么是数据库
数据库就是存储和管理数据的仓库,数据按照一定的格式进行存储,用户可以对数据库中的数据进行增加修改、删除、查询等操作。数据库的分为关系型数据库和非关系型数据库。
关系型数据库是指采用了关系模型来组织数据的数据库,简单来说就是二维表格模型,好比Excel文件中的表格,强调使用表格的方式存储数据。关系型数据库核心元素:数据行、数据列、数据表、数据库(数据表的集合),非关系型数据库强调Key-Value的方式存储数据。
常用关系数据库:MySQL、SQLite、Oracle等 常见非关系数据库:MongoDB、Redis
数据库特点:持久化存储、读写速度极高、保证数据的有效性
关系型数据库管理系统是为管理关系型数据库而设计的软件系统,如果要使用关系型数据库就需要安装数据库管理系统,其实就是一个应用软件。关系型数据库管理系统分为关系型数据库服务端软件和关系型数据库客户端软件。 关系型数据库服务端软件主要负责管理不同的数据库,而每个数据库里面会有一系列数据文件,数据文件是用来存储数据的,其实数据库就是一系列数据文件的集合。 关系型数据库客户端软件主要负责和关系型数据库服务端软件进行通信,向服务端传输数据或者从服务端获取数据。
1.2.1、RDMS与NoSQL对比
功能对比
关系型数据库(sql/RDBMS) | 非关系型数据库 | |
---|---|---|
强大的查询功能 | √ | × |
强一致性 | √ | × |
二级索引(目录) | √ | × |
灵活模式 | × | √ |
扩展性 | × | √ |
性能 | × | √ |
特点对比
- 关系型数据库(RDBMS)的特点:
- 二维表
- 典型产品Oracle传统企业,MySQL互联网企业
- 数据存取是通过SQL语句(Structured Query Language结构化查询语言)
- 最大特点数据安全性方面强(ACID)
- 非关系型数据库(NoSQL:Not only SQL)的特点:
- 不是否定关系型数据库,而是做关系型数据库的补充
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:
包括但不限于以下工作内容:
- 数据管理
- 对库和表的创建
- 基本的数据增删改查
- 用户管理
- root,运维用户ops,程序连接用户(只读用户,读写用户)
- 以及对于不同用户的权限管理
- 集群管理
- 数据备份、恢复
- 逻辑备份、物理备份、冷备、热备、温备、全备、增量备份、差异备份等
- 健康及状态监控
- 进程,端口、集群状态、主从复制 延时情况、SQL读写速率等
2、MySQL的安装
2.1、安装方式
- rpm、yum安装
- 安装方便、安装速度快、无法定制
- 二进制
- 不需要安装,解压即可使用,不能定制功能
- 编译安装
- 可定制,安装慢
- 四个步骤
- 解压(tar)
- 生成(./configure) cmake
- 编译(make)
- 安装(make install)
2.2、源码安装
- MySQL版本选择:
- https://downloads.mysql.com/archives/community/
- 5.6:GA(稳定版) 6-12个月 小版本是偶数版是稳定版,奇数版本是开发版
- 5.7:选择5.17版本以上,支持MGR(MySQL自带的高可用)
- 下载源码,并且配置编译环境
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
- 创建mysql用户
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
- systemd管理mysql
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
- 设置mysql开机启动,并且开始mysql服务
systemctl start mysqld
systemctl enable mysqld
- 设置mysql密码,并且登录测试
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 #否则要重启系统才生效
- 需要注意,官方编译的二进制包默认是在
/usr/local
目录下的,我们需要修改配置文件
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失败,因为冲突。)
- 创建systemd管理文件,并且测试是否正常使用
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
- “强制回应 (Ping)”服务器
- 关闭服务器
- 创建和删除数据库
- 显示服务器和版本信息
- 显示或重置服务器状态变量
- 设置口令
- 重新刷新授权表
- 刷新日志文件和高速缓存
- 启动和停止复制
[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,输入数据库的连接信息,连接到数据库中
3、MySQL架构
3.1、客户端与服务器模型
-
mysql是一个典型的C/S服务结构
-
mysql自带的客户端程序(/application/mysql/bin)
- mysql
- mysqladmin
-
mysqldump
-
mysqld一个二进制程序,后台的守护进程
-
单进程
-
多线程
-
应用程连接MySQL方式
-
TCP/IP的连接方式,远程方式,PHP应用可安装于另一台机器
测试需要服务器数据库
mysql> grant all privileges on . to root@'192.168.128.%' identified by '123456';
客户端数据库
yum -y install mariadb #安装客户端
mysql -uroot -p123456 -h192.168.128.X -P3306
-
socket,默认连接方式,本地连接,快速无需建立TCP连接,通过/application/mysql/tmp下的mysql.sock文件
mysql -uroot -p123456 -h127.0.0.1 #TCP连接方式
[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服务结构
- 实例=mysqld后台守护进程+Master Thread +干活的Thread+预分配的内存
- 公司=老板+经理+员工+办公室
- 连接层
- 验证用户的合法性(ip,端口,用户名)
- 提供两种连接方式(socket,TCP/IP)
- 验证操作权限
- 提供一个与SQL层交互的专用线程
- SQL层
- 接受连接层传来的SQL语句
- 检查语法
- 检查语义(DDL,DML,DQL,DCL)
- 解析器,解析SQL语句,生成多种执行计划
- 优化器,根据多种执行计划,选择最优方式
- 执行器,执行优化器传来的最优方式SQL
- 提供与存储引擎交互的线程
- 接收返回数据,优化成表的形式返回SQL
- 将数据存入缓存
- 记录日志,binlog
- 存储引擎
- 接收上层的执行结构
- 取出磁盘文件和相应数据
- 返回给SQL层,结构化之后生成表格,由专用线程返回给客户端
3.2.2、mysql逻辑结构
MySQL的逻辑对象:做为管理人员或者开发人员操作的对象
- 库
- 表:元数据+真实数据行
- 元数据:列+其它属性(行数+占用空间大小+权限)
- 列:列名字+数据类型+其他约束(非空、唯一、主键、非负数、自增长、默认值)
最直观的数据:二维表,必须用库来存放
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的物理结构
- MySQL的最底层的物理结构是数据文件,也就是说,存储引擎层,打交道的文件,是数据文件。
- 存储引擎分为很多种类
- 不同存储引擎的区别:存储方式、安全性、性能
myisam:
- mysql自带的表部分就是使用的myisam
mysql> show create table mysql.user\G;
...
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='Users and global privileges'
innodb:
- 自己创建一个表,在编译的时候已经默认指定使用innodb
3.3.4、段、区、页(块)
- 段:理论上一个表就是一个段,由多个区构成,(分区表是一个分区一个段)
- 区:物理内存管理的基本单位,操作系统将物理内存划分为多个区块
- 页:页是虚拟内容管理的基本单位,与
区
构成映射关系
举例:假设有一个简单的 C 程序,它包含以下几个部分:
- 程序代码
- 全局变量
- 局部变量
- 栈
在内存中,这个程序会被划分成几个段:
- 代码段: 存放程序的机器指令
- 数据段: 存放全局变量
- BSS段: 存放未初始化的全局变量
- 栈段: 存放函数的局部变量和调用信息
这些段是由编译器或链接器根据程序的结构划分的逻辑内存单元。
接下来,操作系统会将这些段映射到物理内存的区域上。操作系统会把物理内存划分成很多个相同大小(通常为 4KB)的区块,称为区。操作系统会根据程序的内存需求,将程序的段映射到这些区块上。
当程序执行时,它会访问虚拟内存地址。操作系统会将虚拟内存地址转换为物理内存地址,并将相应的页(通常也是 4KB)加载到物理内存中。这就是页的概念。
例如,当程序访问一个全局变量时,操作系统会先将包含该变量的页加载到物理内存的某个区中,然后程序就可以访问这个变量了。
总结一下:
- 段: 程序的逻辑内存单元,由编译器或链接器划分
- 区: 物理内存的基本单位,由操作系统管理
- 页: 虚拟内存的基本单位,由操作系统管理并映射到物理内存的区上
4、Mysql用户权限管理
4.1、Mysql用户基础操作
-
Linux用户的作用
-
登录系统
-
管理系统文件
-
Linux用户管理
-
创建用户:useradd adduser
- 删除用户:userdel
-
修改用户:usermod
-
Mysql用户的作用
-
登录Mysql数据库
-
管理数据库对象
-
Mysql用户管理
-
创建用户:create user
- 删除用户:delete user drop user
-
修改用户:update
-
用户的定义
-
username@'主机域'
- 主机域:可以理解为是Mysql登录的白名单
-
主机域格式:
- 10.1.1.12
- 10.1.0.1%
- 10.1.0.%
- 10.1.%.%
- %
- localhost
- 192.168.1.1/255.255.255.0
-
刚装完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
- INSERT: 用于向数据库表中插入新数据。
- SELECT: 用于从数据库表中查询和检索数据。
- UPDATE: 用于更新数据库表中的现有数据。
- DELETE: 用于从数据库表中删除数据。
- CREATE: 用于创建数据库对象,如表、视图、存储过程等。
- DROP: 用于删除数据库对象,如表、视图、存储过程等。
- RELOAD: 用于重新加载 MySQL 服务器的配置文件。
- SHUTDOWN: 用于关闭 MySQL 服务器。
- PROCESS: 用于查看和管理 MySQL 服务器的进程。
- FILE: 用于读写服务器上的文件。
- REFERENCES: 用于创建外键约束。
- INDEX: 用于创建和管理数据库索引。
- ALTER: 用于修改数据库对象的结构,如表、视图、存储过程等。
- SHOW DATABASES: 用于查看当前 MySQL 服务器上的所有数据库。
- 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: 用于创建数据库表空间。
-
每次设定只能有一个属主,没有属组或其他用户的概念
grant all privileges on *.* to user01@''192.168.175.%'' identified by ''123'';
--权限 --作用对象 --归属 --密码
作用对象分解
- . [当前MySQL实例中所有库下的所有表]
- wordpress.* [当前MySQL实例中wordpress库中所有表(单库级别)]
- wordpress.user [当前MySQL实例中wordpress库中的user表(单表级别)]
企业中权限的设定
- 开发人员说:给我开一个用户
- 沟通
- 你需要对哪些库、表进行操作
- 你从哪里连接过来
- 用户名有没有要求
- 密码要求
- 发邮件
- 一般给开发创建用户权限
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';
- 一个客户端程序使用wordpress用户登陆到10.0.0.51的MySQL后,
- 对t1表的管理能力?
- 对t2表的管理能力?
- 对tb1表的管理能力?
- 解
- 同时满足1,2,3,最终权限是1+2+3
- 同时满足了1和2两个授权,最终权限是1+2
- 只满足1授权,所以只能select
- 结论
- 如果在不同级别都包含某个表的管理能力时,权限是相加关系。
- 但是我们不推荐在多级别定义重复权限。
- 最常用的权限设定方式是单库级别授权,即:wordpress.*
5、Mysql启动关闭流程
- 启动
/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
出现问题:
- 如果在业务繁忙的情况下,数据库不会释放pid和sock文件
- 号称可以达到和Oracle一样的安全性,但是并不能100%达到
- 在业务繁忙的情况下,丢数据(补救措施,高可用)
6、Mysql实例初始化配置
- 在启动一个实例的时候,必须要知道如下的问题
- 我不知道我的程序在哪?
- 我也不知道我将来启动后去哪找数据库?
- 将来我启动的时候启动信息和错误信息放在哪?
- 我启动的时候sock文件pid文件放在哪?
- 我启动,你们给了我多少内存?
- ......若干问题
- 预编译:cmake去指定,硬编译到程序当中去
- 在命令行设定启动初始化配置
--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
- 初始化配置文件(/etc/my.cnf)
- --defaults-file:默认配置文件
- 如果使用./bin/mysqld_safe 守护进程启动mysql数据库时,使用了 --defaults-file=<配置文件的绝对路径>参数,这时只会使用这个参数指定的配置文件。
#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
- 优先级结论
- 命令行
- defaults-file
- 配置文件
- 预编译
- 初始化配置文件功能
- 影响实例的启动(mysqld)
- 影响到客户端
- 配置标签分类
- [client]所有客户端程序
- [server]所有服务器程序
7、MySQL多实例配置
7.1、多实例
- 多套后台进程+线程+内存结构
- 多个配置文件
- 多端口
- 多socket文件
- 多个日志文件
- 多个server_id
- 多套数据
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语句
- SQL是结构化的查询语句
- SQL的种类
- DDL:数据定义语句
- DCL:数据控制语言
- DML:数据操作语言
- DQL:数据查询语言
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数据控制语言
- 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依旧正常增长
- 使用伪删除
- 有时候一些重要数据不能直接删除,只能伪删除,因为以后还得使用呢
- 使用update代替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数据查询语言
- select:基础用法
- 演示用的SQL文件下载:https://download.s21i.faiusr.com/23126342/0/0/ABUIABAAGAAgzcXwhQYozuPv2AE?f=world.sql&v=1622942413
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、字符集定义
- 什么是字符集(Charset)
- 字符集:是一个系统支持的所有抽象字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。
- MySQL数据库的字符集
- 字符集(CHARACTER)
- 校对规则(COLLATION)
- MySQL中常见的字符集
- UTF8
- LATIN1
- GBK
- 常见校对规则
- ci:大小写不敏感
- cs或bin:大小写敏感
- 我们可以使用以下命令查看
show charset;
show collation;
字符集设置
- 操作系统级别
source /etc/sysconfig/i18n
echo $LANG
- Mysql实例级别
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、数据类型介绍
-
四种主要类别
-
数值
- 字符
- 二进制
-
时间
-
数据类型的ABC要素
-
Appropriate(适当)
- Brief(简洁)
-
Complete(完整)
-
数值数据类型
-
使用数值数据类型时的注意事项
- 数据类型所标识的值的范围
- 列值所需的空间量
- 列精度和范围(浮点数和定点数)
-
数值数据类型的类
-
整数:整数
-
浮点数:小数
-
定点数:精确值数值
-
BIT:位字段值
-
类 | 类型 | 说明 |
---|---|---|
整数 | 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 | 由一组固定的合法值组成的集 |
-
文本:真实的非结构化字符串数据类型
-
整数:结构化字符串类型
-
二进制字符串数据类型
-
字节序列
- 二进制位按八位分组
- 存储二进制值
- 编译的计算机程序和应用程序
- 图像和声音文件
-
字符二进制数据类型的类
-
二进制:固定长度和可变长度的二进制字符串
-
BLOB:二进制数据的可变长度非结构化集合
-
类 | 类型 | 说明 |
---|---|---|
二进制 | binary | 类似于char(固定长度)类型,但存储的是二进制字节字符串,而不是非二进制字符串 |
二进制 | varbinary | 类似于varchar(可变长度)类型, |
BLOB | tinyblob | 最大长度为255个字节的BLOB列 |
BLOB | blob | 最大长度为65535个字节的BLOB列 |
BLOB | MEDIUDMBLOB | 最大长度为16777215个字节的BLOB列 |
BLOB | longblob | 最大长度为4294967295个自己的blob列 |
- 时间数据类型
10.2、列属性介绍
-
列属性的类别
-
数值:适用于数值数据类型(BIT 除外)
-
字符串:适用于非二进制字符串数据类型
-
常规:适用于所有数据类型
数据类型 | 属性 | 说明 |
---|---|---|
数值 | unsigned | 禁止使用负值 |
仅整数 | auto_increment | 生成包含连续唯一整数值的序列 |
字符串 | character set | 指定要使用的字符集 |
字符串 | collate | 指定字符集整理 |
字符串 | binary | 指定二进制整理 |
全部* | Null或not Null | 指定列是否可以包含NULL值 |
全部 | Default | 如果未为新记录指定值,则为其提供默认值 |
11、索引介绍
- 索引就好比一本书的目录,它能让你更快的找到自己想要的内容。
- 让获取的数据更有目的性,从而提高数据库检索数据的性能。
11.1、索引类型介绍
- BTREE:B+树索引
- HASH:HASH索引
- FULLTEXT:全文索引
- RTREE:R树索引
- B+树
- B*树
11.2、索引管理
-
索引建立在表的列上(字段)的。
-
在where后面的列建立索引才会加快查询速度。
-
pages<---索引(属性)<----查数据。
-
索引分类:
-
主键索引
:
- 就像是一本书的目录页,可以快速定位到某一页的内容。
- 每一行数据都有一个独一无二的标识(主键),建立主键索引可以迅速找到对应的行。
- 主键索引是最重要的索引类型,能大大提高查询效率。
-
普通索引
:
- 就像是在一本书的页边做了一些标记,方便以后快速找到感兴趣的内容。
- 普通索引不要求值必须唯一,可以建立在单个列或多个列上。
- 普通索引可以提高查询速度,但更新和插入数据时会稍微损耗一些性能。
-
唯一索引
:
- 可以认为是一种特殊的主键,它要求索引列的值必须是唯一的。
- 唯一索引既可以提高查询效率,又能确保数据的完整性,防止出现重复数据。
- 不过,与主键不同的是,唯一索引允许出现空值。
-
添加索引:
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、前缀索引
- 根据字段的前N个字符建立索引
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
语句的用法和输出含义:
-
使用方法:
-
在 SQL 语句前加上
EXPLAIN
关键字即可,例如:EXPLAIN SELECT * FROM users WHERE id = 1;
mysql> explain select name,countrycode from city where id=1;
-
输出信息:
-
id
: 查询编号,表示查询执行的顺序。如果有子查询,每个子查询都会分配一个单独的 id。 select_type
: 查询类型,包括 SIMPLE、PRIMARY、SUBQUERY 等。table
: 正在访问的数据表名称。partitions
: 匹配的分区情况。type
: 访问类型,包括 system、const、eq_ref、ref、range、index、ALL 等,访问类型由快到慢依次排列。possible_keys
: 可能使用的索引。key
: 实际使用的索引名称。key_len
: 使用索引的长度。ref
: 列上的比较操作。rows
: 估计要读取的行数。filtered
: 条件过滤后剩余的行占原始行的比例。-
Extra
: 一些额外的信息,如 Using index、Using where 等。 -
应用场景:
-
查看查询计划,了解 MySQL 是如何执行 SQL 查询的。
- 分析查询瓶颈,如果
type
是ALL
表示全表扫描,可能需要优化索引。 - 检查是否使用了正确的索引,
possible_keys
和key
列可以告诉你 MySQL 选择了哪个索引。 - 估算查询的行数,
rows
列给出了 MySQL 估计的行数,可以帮助判断查询的效率。
11.4.1、MySQL查询数据的方式
- 全表扫描(在explain语句结果中type为ALL,例如select * from XXX)
- 业务确实要获取所有数据
- 不走索引导致的全表扫描
- 没索引
- 索引创建有问题
- 语句有问题
- 生产中,mysql在使用全表扫描时的性能是极其差的,所以MySQL尽量避免出现全表扫描
- 索引扫描
- 常见的索引扫描类型
- index
- range
- ref
- eq_ref
- const
- system
- null
- 从上到下,性能从最差到最好,我们认为至少要达到range级别
11.4.2、index
- Full Index Scan,index与ALL区别为index类型只遍历索引树。
11.4.3、range
- 索引范围扫描,对索引的扫描开始于某一点,返回匹配值域的行。显而易见的索引范围扫描是带有between或者where子句里带有<,>查询。
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
- 类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件A
join B
on A.sid=B.sid
11.4.6、const、system
- 当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。
- 如将主键置于where列表中,MySQL就能将该查询转换为一个常量
mysql> explain select * from city where id=1000;
11.4.7、NULL
- MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。
mysql> explain select * from city where id=1000000000000000000000000000;
11.4.8、Extra(扩展)
在 MySQL 中,当你执行一条 SQL 语句时,结果集通常包含一个名为 "Extra" 的列,它提供了一些额外的信息,可以帮助我们更好地理解 SQL 语句的执行过程。下面是一些常见的 "Extra" 信息及其含义:
- No index used
- 表示在执行 SQL 语句时,数据库没有使用任何索引。这可能会降低查询性能。
- Using index
- 表示在执行 SQL 语句时,数据库使用了索引来优化查询。这通常意味着查询性能较好。
- Using index condition
- 表示在执行 SQL 语句时,数据库使用了索引条件下推(Index Condition Pushdown)技术来优化查询。这可以减少不必要的行访问。
- Using index for group-by
- 表示在执行 GROUP BY 语句时,数据库使用了索引来优化分组操作。
- Using temporary
- 表示在执行 SQL 语句时,数据库创建了一个临时表来存储中间结果。这可能会降低查询性能。
- Using filesort
- 表示在执行 SQL 语句时,数据库需要对结果集进行额外的排序操作。这可能会降低查询性能。
- Using join buffer (Block Nested Loop)
- 表示在执行 JOIN 查询时,数据库使用了连接缓冲区来优化连接操作。
- Impossible WHERE noticed after reading const tables
- 表示在执行 SQL 语句时,数据库发现 WHERE 条件永远为 false,因此直接返回空结果集。
- No tables used
- 表示在执行 SQL 语句时,数据库没有访问任何表,例如执行
SELECT 1;
。 - Select tables optimized away
- 表示在执行 SQL 语句时,数据库优化掉了部分表的访问,例如在某些情况下,数据库可以直接从索引中获取所需的数据,而无需访问表本身。
这些 "Extra" 信息可以帮助我们了解 SQL 语句的执行过程,识别潜在的性能问题,并针对性地优化查询。在优化 SQL 语句时,我们应该关注那些可能降低性能的信息,例如 "Using temporary"、"Using filesort" 等
- 如果出现Using filesort请检查order by ,group by ,distinct,join 条件列上没有索引
mysql> explain select * from city where countrycode='CHN' order by population;
- 当order by语句中出现Using filesort,那就尽量让排序值在where条件中出现
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;
--注意:如果重复值较多,可以考虑采用联合索引
- 为经常需要排序、分组和联合操作的字段建立索引
- 经常需要ORDER BY、GROUP BY、DISTINCT和UNION等操作的字段,排序操作会浪费很多时间。
- 如果为其建立索引,可以有效地避免排序操作
- 为常作为查询条件的字段建立索引
- 如果某个字段经常用来做查询条件,那么该字段的查询速度会影响整个表的查询速度。
- 因此,为这样的字段建立索引,可以提高整个表的查询速度。
- 如果经常作为条件的列,重复值特别多,可以建立联合索引
- 尽量使用前缀来索引
- 如果索引字段的值很长,最好使用值的前缀来索引。例如,TEXT和BLOG类型的字段,进行全文检索会很浪费时间。如果只检索字段的前面的若干个字符,这样可以提高检索速度。
- 限制索引的数目
- 索引的数目不是越多越好。每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。
- 修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变得很浪费时间。
- 删除不再使用或者很少使用的索引
- 表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。
重点关注:
- 没有查询条件,或者查询条件没有建立索引
select * from table;
select * from tab where 1=1;
--全表扫描
- 在业务数据库中,特别是数据量比较大的表,是没有全表扫描这种需求。
- 对用户查看是非常痛苦的。
- 对服务器来讲毁灭性的。
- SQL改写成以下语句
--情况1
select * from table;
--全表扫描
selec * from tab order by price limit 10;
--需要在price列上建立索引
--情况2
select * from table where name='zhangsan';
--name列没有索引
--1、换成有索引的列作为查询条件
--2、将name列建立索引
- 查询结果集是原表中的大部分数据,应该是25%以上
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';
- <> ,not in 不走索引
mysql> select * from tab where telnum <> '1555555';
mysql> explain select * from tab where telnum <> '1555555';
- 单独的>,<,in 有可能走,也有可能不走,和结果集有关,尽量结合业务添加limit
- or或in尽量改成union
EXPLAIN SELECT * FROM teltab WHERE telnum IN ('110','119');
--改写成
EXPLAIN SELECT * FROM teltab WHERE telnum='110'
UNION ALL
SELECT * FROM teltab WHERE telnum='119'
- like "%_" 百分号在最前面不走索引
--走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提供事务安全表,其他存储引擎都是非事务安全表
- 文件系统:
- 操作系统组织和存取数据的一种机制。
- 文件系统是一种软件。
- 文件系统类型:ext2 3 4 ,xfs 数据
- 不管使用什么文件系统,数据内容不会变化
- 不同的是,存储空间、大小、速度。
- MySQL引擎:
- 可以理解为,MySQL的“文件系统”,只不过功能更加强大。
- MySQL引擎功能:
- 除了可以提供基本的存取功能,还有更多功能事务功能、锁定、备份和恢复、优化以及特殊功能
- 总之,存储引擎的各项特性就是为了保障数据库的安全和性能设计结构。
12.2、MySQL自带的存储引擎类型
- MySQL 提供以下存储引擎:
- InnoDB
- MyISAM
- MEMORY
- ARCHIVE
- FEDERATED
- EXAMPLE
- BLACKHOLE
- MERGE
- NDBCLUSTER
- CSV
- 还可以使用第三方存储引擎:
- MySQL当中插件式的存储引擎类型
- MySQL的两个分支
- perconaDB
- mariaDB
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索引,但是一旦服务关闭,表中的数据就会丢失
应用场景:快速定位记录
- MEMORY是MySQL中一类特殊的存储引擎。它使用存储在内存中的内容来创建表,而且数据全部放在内存中。这些特性与前面的两个很不同。
- 每个基于MEMORY存储引擎的表实际对应一个磁盘文件。该文件的文件名与表名相同,类型为frm类型。该文件中只存储表的结构。而其数据文件,都是存储在内存中,这样有利于数据的快速处理,提高整个表的效率。值得注意的是,服务器需要有足够的内存来维持MEMORY存储引擎的表的使用。如果不需要了,可以释放内存,甚至删除不需要的表。
- MEMORY默认使用哈希索引。速度比使用B型树索引快。当然如果你想用B型树索引,可以在创建索引时指定。
- 注意,MEMORY用到的很少,因为它是把数据存到内存中,如果内存出现异常就会影响数据。如果重启或者关机,所有数据都会消失。因此,基于MEMORY的表的生命周期很短,一般是一次性的。
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区别
两种存储引擎的大致区别表现在:
- InnoDB支持事务,MyISAM不支持,这一点是非常之重要。事务是一种高级的处理方式,如在一些列增删改中只要哪个出错还可以回滚还原,而MyISAM就不可以了。
- MyISAM适合查询以及插入为主的应用。
- InnoDB适合频繁修改以及涉及到安全性较高的应用。
- InnoDB支持外键,MyISAM不支持。
- 从MySQL5.5.5以后,InnoDB是默认引擎。
- InnoDB不支持FULLTEXT类型的索引。
- InnoDB中不保存表的行数,如select count(*) from table时,InnoDB需要扫描一遍整个表来计算有多少行,但是 MyISAM只要简单的读出保存好的行数即可。注意的是,当count()语句包含where条件时MyISAM也需要扫描整个表。
- 对于自增长的字段,InnoDB中必须包含只有该字段的索引,但是在MyISAM表中可以和其他字段一起建立联合索引。
- DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的 删除,效率非常慢。MyISAM则会重建表。
- InnoDB支持行锁(某些情况下还是锁整表,如 update table set a=1 where user like '%lee%'。
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的表有哪些
- innodb和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存储引擎的简介
-
在MySQL5.5版本之后,默认的存储引擎,提供高可靠性和高性能。
-
优点:
-
事务安全(遵从 ACID)
-
MVCC(Multi-Versioning Concurrency Control,多版本并发控制)
-
InnoDB 行级别锁定,另一种DB是表级别的
-
Oracle 样式一致非锁定读取
-
表数据进行整理来优化基于主键的查询
-
支持外键引用完整性约束
-
大型数据卷上的最大性能
-
将对表的查询与不同存储引擎混合
-
出现故障后快速自动恢复
-
用于在内存中缓存数据和索引的缓冲区池
功能 | 支持 | 功能 | 支持 |
---|---|---|---|
存储限制 | 64TB | 索引高速缓存 | 是 |
MVCC | 是 | 数据高速缓存 | 是 |
B树索引 | 是 | 自适应散列索引 | 是 |
群集索引 | 是 | 复制 | 是 |
压缩数据 | 是 | 更新数据字典 | 是 |
加密数据 | 是 | 地理空间数据类型 | 是 |
查询高速缓存 | 是 | 地理空间索引 | 否 |
事务 | 是 | 全文搜索索引 | 是 |
锁定粒度 | 行 | 群集数据库 | 否 |
外键 | 是 | 备份和恢复 | 是 |
文件格式管理 | 是 | 快速索引创建 | 是 |
多个缓冲区池 | 是 | performance_schema | 是 |
更改缓冲 | 是 | 自动故障恢复 | 是 |
-
innodb核心特性
-
MVCC
- 事务
- 行级锁
- 热备份
-
Crash Safe Recovery(自动故障恢复)
-
查看存储引擎
-
使用 SELECT 确认会话存储引擎
SELECT @@default_storage_engine;
--查询默认存储引擎
- 使用 SHOW 确认每个表的存储引擎
SHOW CREATE TABLE City
SHOW TABLE STATUS LIKE 'CountryLanguage'
--查看表的存储引擎
- 存储引擎的设置
- 在启动配置文件中设置服务器存储引擎
[mysqld]
default-storage-engine=<Storage Engine>
--在配置文件的[mysqld]标签下添加
- 使用 SET 命令为当前客户机会话设置
SET @@storage_engine=<Storage Engine>
--在MySQL命令行中临时设置
- 在 CREATE TABLE 语句指定
CREATE TABLE t (i INT) ENGINE = <Storage Engine>;
--建表的时候指定存储引擎
12.6、【实战】存储引擎切换
- 项目背景:
- 公司原有的架构:一个展示型的网站,LAMT,MySQL5.1.77版本(MYISAM),50M数据量。
- 小问题不断:
- 表级锁:对表中任意一行数据修改类操作时,整个表都会锁定,对其他行的操作都不能同时进行。
- 不支持故障自动恢复(CSR):当断电时有可能会出现数据损坏或丢失的问题。
- 解决方案:
- 提建议将现有的MYISAM引擎替换为Innodb,将版本替换为5.6.38
- 如果使用MYISAM会产生”小问题”,性能安全不能得到保证,使用innodb可以解决这个问题。
- 5.1.77版本对于innodb引擎支持不够完善,5.6.38版本对innodb支持非常完善了。
- 实施过程和注意要素
- 备份生产库数据(mysqldump)
#[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
- 准备一个5.6.38版本的新数据库
- 对备份数据进行处理(将engine字段替换)
[root@db01 ~]# sed -i 's#ENGINE=MYISAM#ENGINE=INNODB#g' /tmp/full.sql
- 将修改后的备份恢复到新库
- 应用测试环境连接新库,测试所有功能
- 停应用,将备份之后的生产库发生的新变化,补偿到新库
- 应用割接到新数据库
12.7、表空间介绍
- 5.5版本以后出现共享表空间概念
- 表空间的管理模式的出现是为了数据库的存储更容易扩展
- 5.6版本中默认的是独立表空间
- 共享表空间
[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
- 5.6版本中默认存储
- 系统数据
- undo
- 临时表
- 5.7版本中默认会将undo和临时表独立出来,5.6版本也可以独立,只不过需要在初始化的时候进行配置
- 共享表空间扩展配置方法
#编辑配置文件
[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
- 启动新数据库
#pkill mysqld 先干掉mysql
[root@db01 ~]# mysqld_safe --defaults-file=/data/3307/my.cnf &
- 登陆数据库查看
mysql> show databases;
- 查询表中数据
mysql> select * from city;
ERROR 1146 (42S02): Table 'world.city' doesnot exist
--先 use world;
- 找到以前的表结构在新库中创建表,此处演示用的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;
- 删除表空间文件
mysql> alter table city_new discard tablespace;
- 拷贝旧表空间文件
[root@db01 world]# cp /data/3307/data/world/city.ibd /data/3307/data/world/city_new.ibd
- 授权
[root@db01 world]# cd /data/3307/data/world
[root@db01 world]# chown -R mysql.mysql *
- 导入表空间
mysql> alter table city_new import tablespace;
mysql> alter table city_new rename city;
13、MySQL事物
13.1、事务
- 事务的定义
- 主要针对DML语句(update,delete,insert)一组数据操作执行步骤,这些步骤被视为一个工作单元
- 用于对多个语句进行分组
- 可以在多个客户机并发访问同一个表中的数据时使用
- 所有步骤都成功或都失败
- 如果所有步骤正常,则执行
- 如果步骤出现错误或不完整,则取消
- 交易的概念
- 物与物的交换(古代)
- 货币现金与实物的交换(现代1)
- 虚拟货币与实物的交换(现代2)
- 虚拟货币与虚拟实物交换(现代3)
- 事务ACID特性
- Atomic(原子性)
- 所有语句作为一个单元全部成功执行或全部取消。
- Consistent(一致性)
- 如果数据库在事务开始时处于一致状态,则在执行该事务期间将保留一致状态。
- Isolated(隔离性)
- 事务之间不相互影响。
- Durable(持久性)
- 事务成功完成后,所做的所有更改都会准确地记录在数据库中。所做的更改不会丢失。
- 事务的控制语句
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终端查看,分别在执行完语句以及commit后查看
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;
- 事务隐式提交情况
- 现在版本在开启事务时,不需要手工begin,只要你输入的是DML语句,就会自动开启事务。
- 有些情况下事务会被隐式提交
- 在事务运行期间,手工执行begin的时候会自动提交上个事务
- 在事务运行期间,加入DDL、DCL操作会自动提交上个事务
- 在事务运行期间,执行锁定语句(lock tables、unlock tables)
- load data infile
- select for update
- 在autocommit=1的时候
13.1.1、事务日志redo
- redo,顾名思义“重做日志”,是事务日志的一种。
- 在事务ACID过程中,实现的是“D”持久化的作用。
- 特性:WAL(Write Ahead Log)日志优先写
- REDO:记录的是,内存数据页的变化过程
- REDO工作过程
update t1 set num=2 where num=1;
- 执行步骤
- 首先将t1表中num=1的行所在数据页加载到内存中buffer page
- MySQL实例在内存中将num=1的数据页改成num=2
- num=1变成num=2的变化过程会记录到,redo内存区域,也就是redo buffer page中 commit;
- 提交事务执行步骤
- 当敲下commit命令的瞬间,MySQL会将redo buffer page写入磁盘区域redo log
- 当写入成功之后,commit返回ok
13.1.2、事务日志undo
- undo,顾名思义“回滚日志”,是事务日志的一种。
- 在事务ACID过程中,实现的是“A”原子性的作用。当然CI的特性也和undo有关
- redo和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位置
- 在MySQL5.6版本中undo是在ibdata文件中,在MySQL5.7版本会独立出来。
13.1.3、事务中的锁
-
“锁”顾名思义就是锁定的意思
-
在事务ACID特性过程中,“锁”和“隔离级别”一起来实现“I”隔离性的作用。
-
排他锁:保证在多事务操作时,数据的一致性。
-
共享锁:保证在多事务工作期间,数据查询时不会被阻塞。
-
多版本并发控制(MVCC)
-
只阻塞修改类操作,不阻塞查询类操作
-
乐观锁的机制(谁先提交谁为准)
-
锁的粒度
-
MyIsam:低并发锁(表级锁)
-
Innodb:高并发锁(行级锁)
-
事务的隔离级别
-
四种隔离级别
-
READ UNCOMMITTED(独立提交)
- 允许事务查看其他事务所进行的未提交更改,一个用户update命令后未commit,另一个用户看到的是修改的内存里的,即使没有写入硬盘
-
READ COMMITTED
- 允许事务查看其他事务所进行的已提交更改
-
REPEATABLE READ
**
- 确保每个事务的 SELECT 输出一致,一个用户update命令即使commit。另一个用户看到的未修改的,除非重新登录或者commit+
- InnoDB 的默认级别
-
SERIALIZABLE
- 将一个事务的结果与其他事务完全隔离,一个用户update命令后未commit,另一个用户即使select都看不到。
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、错误日志
- 记录mysql数据库的一般状态信息及报错信息,是我们对于数据库常规报错处理的常用日志。
- 默认位置
- $MYSQL_HOME/data/
- 开启方式
- MySQL安装完后默认开启
[root@db01 ~]# vim /etc/my.cnf
# 编辑配置文件
[mysqld]
log_error=/application/mysql/data/$hostname.err
mysql> show variables like 'log_error';
# 查看方式
14.3、一般查询日志
- 记录mysql所有执行成功的SQL语句信息,可以做审计用,但是我们很少开启。
- 默认位置
- $MYSQL_HOME/data/
- y开启方式
- MySQL安装完之后默认不开启
[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、二进制日志
-
记录已提交的DML事务语句,并拆分为多个事件(event)来进行记录
-
记录所有DDL、DCL等语句
-
总之,二进制日志会记录所有对数据库发生修改的操作
-
二进制日志模式
-
statement:语句模式
- row:行模式,即数据行的变化过程
- mixed:以上两者的混合模式。
-
企业推荐使用row模式
-
二进制日志模式优缺点
-
statement模式
- 优点:简单明了,容易被看懂,就是sql语句,记录时不需要太多的磁盘空间
- 缺点:记录不够严谨
-
row模式
记录了底层操作的所有事情
- 优点:记录更加严谨
- 缺点:有可能会需要更多的磁盘空间,不太容易被读懂
-
binlog的作用
-
如果我拥有数据库搭建开始所有的二进制日志,那么我可以把数据恢复到任意时刻
- 数据的备份恢复
- 数据的复制
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事件
- 事件介绍
- 在binlog中最小的记录单元为event
- 一个事务会被拆分成多个事件(event)
- 事件(event)特性
- 每个event都有一个开始位置(start position)和结束位置(stop position)。
- 所谓的位置就是event对整个二进制的文件的相对位置。
- 对于一个二进制日志中,前120个position是文件格式信息预留空间。
- MySQL第一个记录的事件,都是从120开始的。
- row模式下二进制日志分析及数据恢复
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;
#查看表中内容
- 只查看某个数据库的binlog文件
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的操作
- 刷新binlog日志
- flush logs;
- 重启数据库时会刷新
- 二进制日志上限(max_binlog_size)
- 删除二进制日志
- 原则
- 在存储能力范围内,能多保留则多保留
- 基于上一次全备前的可以选择删除
- 删除方式
- 根据存在时间删除日志
#临时生效
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、慢查询日志
- 是将mysql服务器中影响数据库性能的相关SQL语句记录到日志文件
- 通过对这些特殊的SQL语句分析,改进以达到提高数据库性能的目的
- 默认位置:
- MYSQL_HOME/data/MYSQLHOME/data/hostname-slow.log
- 开启方式(默认没有开启)
[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
--查看慢日志
- 使用mysqldumpslow命令来分析慢查询日志
$PATH/mysqldumpslow -s c -t 10 /database/mysql/slow-log
#输出记录次数最多的10条SQL语句
- 参数说明:
- -s:
- 是表示按照何种方式排序,c、t、l、r分别是按照记录次数、时间、查询时间、返回的记录数来排序,ac、at、al、ar,表示相应的倒叙;
- -t:
- 是top n的意思,即为返回前面多少条的数据;
- -g:
- 后边可以写一个正则匹配模式,大小写不敏感的;
$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
15、备份与恢复
- 备份的原因
- 第一个是保护公司的数据.
- 第二个是让网站能7*24小时提供服务(用户体验)。
- 备份就是为了恢复。
- 尽量减少数据的丢失(公司的损失)
15.1、备份的类型
- 冷备份:
- 这些备份在用户不能访问数据时进行,因此无法读取或修改数据。这些脱机备份会阻止执行任何使用数据的活动。这些类型的备份不会干扰正常运行的系统的性能。但是,对于某些应用程序,会无法接受必须在一段较长的时间里锁定或完全阻止用户访问数据。
- 温备份:
- 这些备份在读取数据时进行,但在多数情况下,在进行备份时不能修改数据本身。这种中途备份类型的优点是不必完全锁定最终用户。但是,其不足之处在于无法在进行备份时修改数据集,这可能使这种类型的备份不适用于某些应用程序。在备份过程中无法修改数据可能产生性能问题。
- 热备份:
- 这些动态备份在读取或修改数据的过程中进行,很少中断或者不中断传输或处理数据的功能。使用热备份时,系统仍可供读取和修改数据的操作访问。
15.2、备份的方式
- 逻辑备份
SQL软件自带的功能
- 基于SQL语句的备份
- binlog
- into outfile
- mysqldump
[root@localhost ~]# vim /etc/my.cnf
[mysqld]
secure_file_priv=/tmp
mysql> select * from world.city into outfile '/tmp/world_city.data';
* mysqldump
* replication
- 物理备份
通过二进制方式直接拖走所有数据、配置文件
- 基于数据文件的备份
- Xtrabackup(percona公司)
15.3、备份工具
- 备份策略
- 全量备份 full
- 增量备份 increamental
- 备份工具
- mysqldump(逻辑)
- mysql原生自带很好用的逻辑备份工具
- mysqlbinlog(逻辑)
- 实现binlog备份的原生态命令
- xtrabackup(物理)
- precona公司开发的性能很高的物理备份工具
- 备份工具使用
- mysqldump
- 连接服务端参数(基本参数):-u -p -h -P -S
- -A, --all-databases:全库备份
[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:仅数据
- 备份额外扩展项
- -R, --routines:备份存储过程和函数数据
- --triggers:备份触发器数据
[root@db01 backup]# mysqldump -uroot -p123 -A -R --triggers > /backup/full_2.sql
- mysqldump特殊参数
- -x:锁表备份(myisam温备份)
- --single-transaction:快照备份
[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的位置,导致后续不利于增量备份
- gzip:压缩备份
[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
- mysqldump的恢复
mysql> set sql_log_bin=0;
--先不记录二进制日志
mysql> source /backup/full.sql
--库内恢复操作
[root@db01 ~]# mysql -uroot -p123 < /backup/full.sql
--库外恢复操作
- 注意
- mysqldump在备份和恢复时都需要MySQL实例启动为前提
- 一般数据量级100G以内,大约15-30分钟可以恢复(PB、EB就需要考虑别的方式)
- mysqldump是以覆盖的形式恢复数据的
15.4、【实战】企业故障恢复
- 背景:
- 正在运行的网站系统,MySQL数据库,数据量25G,日业务增量10-15M。
- 备份策略:
- 每天23:00,计划任务调用mysqldump执行全备脚本
- 故障时间点:
- 上午10点开发人员误删除一个核心业务表,需要恢复
- 思路
- 停业务避免数据的二次伤害!
- 找一个临时的库,恢复前一天的全备
- 截取前一天23:00到第二天10点误删除之间的binlog,恢复到临时库
- 测试可用性和完整性
- 开启业务前的两种方式
- 直接使用临时库顶替原生产库,前端应用割接到新库
- 将误删除的表单独导出,然后导入到原生产环境
- 开启业务
- 故障模拟
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)
- 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
# 安装
- 备份方式(物理备份)
- 对于非innodb表(比如myisam)是直接锁表cp数据文件,属于一种温备。
- 对于innodb的表(支持事务),不锁表,cp数据页最终以数据文件方式保存下来,并且把redo和undo一并备走,属于热备方式。
- 备份时读取配置文件/etc/my.cnf,需要注明socket=/application/mysql/tmp/mysql.sock文件的位置
- 全量备份
[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
- 恢复备份
- 前提1:被恢复的目录是空的
- 前提2:被恢复的数据库的实例是关闭的
[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/
#破坏数据
- 准备备份
- full+inc1+inc2
- 需要将inc1和inc2按顺序合并到full中
- 分步骤进行--apply-log
- 第一步:在全备中apply-log时,只应用redo,不应用undo
[root@db01 ~]# innobackupex --apply-log --redo-only /backup/full/
- 第二步:合并inc1合并到full中,并且apply-log,只应用redo,不应用undo
[root@db01 ~]# innobackupex --apply-log --redo-only --incremental-dir=/backup/inc1/ /backup/full/
- 第三步:合并inc2合并到full中,redo和undo都应用
[root@db01 ~]# innobackupex --apply-log --incremental-dir=/backup/inc2/ /backup/full/
- 第四步:整体full执行apply-log,redo和undo都应用
[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、主从复制原理
- 复制是 MySQL 的一项功能,允许服务器将更改从一个实例复制到另一个实例。
- 主服务器将所有数据和结构更改记录到二进制日志中。
- 从属服务器从主服务器请求该二进制日志并在本地应用其内容。
- IO:请求主库,获取上一次执行过的新的事件,并存放到relaylog
- SQL:从relaylog中将sql语句翻译给从库执行
- 主从复制的前提
- 两台或两台以上的数据库实例
- 主库要开启二进制日志
- 主库要有复制用户
- 主库的server_id和从库不同
- 从库需要在开启复制功能前,要获取到主库之前的数据(主库备份,并且记录binlog当时位置)
- 从库在第一次开启主从复制时,时必须获知主库:ip,port,user,password,logfile,pos
- 从库要开启相关线程:IO、SQL
- 从库需要记录复制相关用户信息,还应该记录到上次已经从主库请求到哪个二进制日志
- 从库请求过来的binlog,首先要存下来,并且执行binlog,执行过的信息保存下来
- 主从复制涉及到的文件和线程
- 主库:
- 主库binlog:记录主库发生过的修改事件
- dump thread:给从库传送(TP)二进制日志线程
- 从库:
- relay-log(中继日志):存储所有主库TP过来的binlog事件,在SQL thread执行完毕,数据持久化后清空,但写入relaylog.info文件
- relaylog.info:类似于master的binlog文件
- master.info:存储复制用户信息,上次请求到的主库binlog位置点
- IO thread:接收主库发来的binlog日志,也是从库请求主库的线程
- SQL thread:执行主库TP过来的日志
- 原理
- 通过change master to语句告诉从库主库的ip,port,user,password,file,pos
- 从库通过start slave命令开启复制必要的IO线程和SQL线程
- 从库通过IO线程拿着change master to用户密码相关信息,连接主库,验证合法性
- 从库连接成功后,会根据binlog的pos问主库,有没有比这个更新的
- 主库接收到从库请求后,比较一下binlog信息,如果有就将最新数据通过dump线程给从库IO线程
- 从库通过IO线程接收到主库发来的binlog事件,存储到TCP/IP缓存中,并返回ACK更新master.info
- 将TCP/IP缓存中的内容存到relay-log中
- SQL线程读取relay-log.info,读取到上次已经执行过的relay-log位置点,继续执行后续的relay-log日志,执行完成后,更新relay-log.info
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)
- 修改配置文件
[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
- 记录主库当前的binlog位置
[root@db01 ~]# mysql -uroot -p123456
mysql> show master status;
- 再从库上配置主库等信息
[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、主从复制基本故障处理
- IO线程报错
- user password ip port
- 网络:不同,延迟高,防火墙
- 没有跳过反向解析[mysqld]里面加入skip-name-resolve
- 请求binlog
- binlog不存在或者损坏
- 更新relay-log和master.info
- SQL线程
- relay-log出现问题
- 从库做写入了
- 操作对象已存在(create)
- 操作对象不存在(insert update delete drop truncate alter)
- 约束问题、数据类型、列属性
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、延时从库
- 普通的主从复制可能存在不足
- 逻辑损坏怎么办?
- 不能保证主库的操作,从库一定能做
- 高可用?自动failover?
- 过滤复制
- 企业中一般会延时3-6小时
- 延时从库配置方法
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(例如宕机),这时备库中的数据就是不完整的。简而言之,在主库发生故障的时候,我们无法使用备库来继续提供数据一致的服务了。
半同步复制(Semi synchronous Replication)则一定程度上保证提交的事务已经传给了至少一个备库。
IO在收到了读写后会发送ACK报告已经收到了。
出发点是保证主从数据一致性问题,安全的考虑。
- 半同步复制开启方法
- 安装(主库)
[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]标签下添加如下内容
注:相关参数说明:
- rpl_semi_sync_master_timeout=milliseconds
- 设置此参数值(ms),为了防止半同步复制在没有收到确认的情况下发生堵塞,如果Master在超时之前没有收到任何确认,将恢复到正常的异步复制,并继续执行没有半同步的复制操作。
- rpl_semi_sync_master_wait_no_slave={ON|OFF}
- 如果一个事务被提交,但Master没有任何Slave的连接,这时不可能将事务发送到其它地方保护起来。默认情况下,Master会在时间限制范围内继续等待Slave的连接,并确认该事务已经被正确的写到磁盘上。
- 可以使用此参数选项关闭这种行为,在这种情况下,如果没有Slave连接,Master就会恢复到异步复制。
测试半同步:
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)
--注:不难发现,在查询半同步状态是,开启半同步,查询会有延迟时间,关闭之后则没有
16.6、过滤复制
- 主库:
- 白名单:只记录白名单中列出的库的二进制日志
- binlog-do-db
- 黑名单:不记录黑名单列出的库的二进制日志
- binlog-ignore-db
- 从库:
- 白名单:只执行白名单中列出的库或者表的中继日志
- --replicate-do-db=test
- --replicate-do-table=test.t1
- --replicate-wild-do-table=test.t2
- 黑名单:不执行黑名单中列出的库或者表的中继日志
- --replicate-ignore-db
- --replicate-ignore-table
- --replicate-wild-ignore-table
- 复制过滤配置
[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能在最大程度上保证数据的一致性,以达到真正意义上的高可用。
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、工作流程
- 把宕机的master二进制日志保存下来。
- 找到binlog位置点最新的slave。
- 在binlog位置点最新的slave上用relay log(差异日志)修复其它slave。(因为relay log修复比bin log快,所以不用master的bin log,slave没有bin log)
- 将宕机的master上保存下来的二进制日志恢复到含有最新位置点的slave上。
- 将含有最新位置点binlog所在的slave提升为master。
- 将其它slave重新指向新提升的master,并开启主从复制。
17.2、MHA工具介绍
-
MHA软件由两部分组成,Manager工具包和Node工具包
-
Manager工具包主要包括以下几个工具
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 |
- Node工具包主要包括以下几个工具
save_binary_logs | 保存宕机的master的binlog |
---|---|
apply_diff_relay_logs | 识别relay log的差异 |
filter_mysqlbinlog | 防止回滚事件 |
purge_relay_logs | 清除中继日志 |
-
MHA优点总结
-
Masterfailover and slave promotion can be done very quickly
- 自动故障转移快
- Mastercrash does not result in data inconsistency
- 主库崩溃不存在数据一致性问题
- Noneed to modify current MySQL settings (MHA works with regular MySQL)
- 不需要对当前mysql环境做重大修改
- Noneed to increase lots of servers
- 不需要添加额外的服务器(仅一台manager就可管理上百个replication)
- Noperformance penalty
- 性能优秀,可工作在半同步复制和异步复制,当监控mysql状态时,仅需要每隔N秒向master发送ping包(默认3秒),所以对性能无影响。你可以理解为MHA的性能和简单的主从复制框架性能一样。
- Works with any storage engine
- 只要replication支持的存储引擎,MHA都支持,不会局限于innodb
17.3、MHA实验环境
- 搭建三台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
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的主从复制
- 先决条件
- 主库和从库都要开启binlog
- 主库和从库server-id不同
- 要有主从复制用户
主库操作
- 修改配置文件
[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
- 创建主从复制用户,每台设备都要配置,因为slave有可能变成master
[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复制情况的时候会报错,写入环境变量
- 部署管理节点(mha-manager:mysql-db03)最好用第四个节点安装mha-manager
[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
- 配置ssh信任(所有节点)
[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.
- 启动MHA
[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
- 切换master测试
[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漂移
- VIP漂移的两种方式
- 通过keepalived的方式,管理虚拟IP的漂移,与后面Nginx负载均衡有关
- 通过MHA自带脚本方式,管理虚拟IP的漂移
- MHA脚本方式
- 修改配置文件,failover文件master_ip_failover
[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、连接查询
左外连接
左连接
全连接
两张表都没有出现交集的数据集
右连接
右外连接
内连接
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)
整个查询的执行过程如下:
- 从
person
表中选取所有记录 - 对于每个
person
记录,查找card
表中id
字段与person.cardId
相匹配的记录 - 将匹配的
person
和card
记录组合成一条结果记录返回
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)
整个查询的执行过程如下:
- 从
person
表中选取所有记录 - 对于每个
person
记录,查找card
表中id
字段与person.cardId
相匹配的记录 - 将匹配的
person
和card
记录组合成一条结果记录返回 - 对于无法匹配的
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)
整个查询的执行过程如下:
- 从
card
表中选取所有记录 - 对于每个
card
记录,查找person
表中cardId
字段与card.id
相匹配的记录 - 将匹配的
person
和card
记录组合成一条结果记录返回 - 对于无法匹配的
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)
整个查询的执行过程如下:
- 从
person
表和card
表中选取所有记录 - 对于每个
person
记录,查找card
表中id
字段与person.cardId
相匹配的记录 - 对于每个
card
记录,查找person
表中cardId
字段与card.id
相匹配的记录 - 将匹配的
person
和card
记录组合成一条结果记录返回 - 对于无法匹配的记录,用
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
- NoSQL
- 代表着不仅仅是SQL
- 没有声明性查询语言
- 没有预定义的模式
- 键-值对存储,列存储,文档存储,图形数据库
- 最终一致性,而非ACID属性
- 非结构化和不可预知的数据
- CAP定理
- 高性能,高可用性和可伸缩性
- RDBMS
- 高度组织化结构化数据
- 结构化查询语言(SQL) (SQL)
- 数据和关系都存储在单独的表中。
- 数据操纵语言,数据定义语言
- 严格的一致性
- 基础事务
1.4、常见的数据库管理系统
DB-Engines排行榜,每月更新一次
1.5、NoSQL数据库四大家族
- 键值(Key-Value)存储数据库
- 特点
- 键值数据库就想传统语言中使用的哈希表,通过key添加、查询或者删除数据
- 优点
- 查询速度更快
- 缺点
- 数据无结构化,通常只被当做字符串或者二进制数据存储
- 应用场景
- 内容缓存、用户信息比如会话、配置信息、购物车等等,主要用于处理大量数据的高访问负债
- NoSQL代表
- Redis、Memcached...
- 文档型数据库
- 特点
- 文档数据库将数据以文档的形式储存,类似JSON,是一系列数据项的集合。每个数据项都有一个名称与对应的值,值既可以是简单的数据类型,如字符串、数字和日期等;也可以是复杂的类型,如有序列表和关联对象。
- 优点
- 数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构
- 应用场景
- 日志、web应用等
- NoSQL代表
- MongoDB、CouchDB...
- 列数据库
- 特点
- 列存储数据库将数据存储在列族中,将多个列集合成一个列族,键仍然存在,但是他们的特点是指向多个列。举个例子,如果我们有一个Person类,我们通常会一起查询他们的姓名和年龄而不是薪资。这种情况下,姓名和年龄就会被放入一个列族中,而薪资则在另一个列族中
- 优点
- 列存储查找速度快,可扩展性强,更容容易进行分布式扩展,适用于分布式的文件系统,应对分布式存储的海量数据
- 缺点
- 查询性能不高,缺乏统一的查询语法
- 应用场景
- 日志、分布式文件系统(对象存储)、推荐画像、时空数据、消息/订单等等。
- NoSQL代表
- Cassandra、HBase....
- 图形(Graph)数据库
- 特点
- 图形数据库允许我们将数据以图的方式存储
- 优点
- 图形相关算法,比如最短路径寻址,N度关系查找等
- 缺点
- 很多时候需要对整个图做计算才能提出需求的信息,分布式的群集方案不好做,处理超级节点乏力,没有分片存储机制,国内社区不活跃
- 应用场景
- 社交网络,推荐系统等,专注于构建关系图谱
- NoSQL代表
- Neo4j、Infinite Graph...
2、MongDB简介
Mongo并非芒果(Mango)的意思,而是源于Humongous(巨大的,庞大的)一词
MongoDB是一个基于分布式文件存储的NoSQL数据库,由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。
MongoDB是一个介于关系型数据库和非关系型数据库之间的产品,是非关系型数据库当中功能最丰富,最像关系数据库的。
MongoDB使用了BSON(Binary JSON)对象来存储,与JSON格式的键值对(key/value)类似,字段值可以包含其他文档,数组及文档数组。支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系型数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
2.1、MongoDB 支持语言
2.2、MongoDB 与关系型数据库术语对比
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、下载二进制包
- 下载地址:https://www.mongodb.com/try/download/community
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
- MongoDB 的默认启动方式为前台启动。所谓的前台启动就是 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,则只能本机访问,默认为本机地址
- 所谓的后台启动就是以守护进程的方式启动 MongoDB。命令中添加 --fork 即可
# 后台启动
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 上就比较方便了。
- 在 bin 目录下增加一个 mongodb.conf 配置文件
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
- 配置systemd服务
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、后台启动
- 使用 --shutdown 参数即可关闭
# 命令启动方式的关闭
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 命令关闭
- 通过 kill -9 的方式强制关闭进程,一般这种方式都不怎么推荐使用
# 查看 mongodb 运行的进程信息
ps -ef | grep mongodb
# kill -9 强制关闭
kill -9 pid
3.5.4、MongoDB 函数关闭
- 连接到 MongoDB 服务后,切换到 admin 数据库,并使用相关函数关闭服务
# 连接 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数据库中。 数据库名必须满足以下条件
- 不能是空字符串("")。
- 不得含有' '(空格)、.、$、/、\和\0 (空字符)。
- 应全部小写。
- 最多64字节。
有一些数据库名是保留的,可以直接访问这些有特殊作用的数据库
- admin:从权限的角度来看,这是'root'数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。
- local:这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合
- config:当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息
4.3、数据库的删除
MongoDB删除数据库的语法格式如下
db.dropDatabase()
--主要用来删除已经持久化的数据库
4.4、集合操作
集合,类似于关系型数据库中的表
可以显式的创建,也可以隐式的创建
- 显式的创建(了解)
db.createCollection(name)
参数说明
- name:要创建的集合名称
例如:创建一个名为mycollection
的普通集合
db.createCollection("mycollection")
查看当前库中的表,show tables命令
show collections
--或
show tables
集合的命名规范:
- 集合名不能是空字符串""。
- 集合名不能含有\0字符(空字符),这个字符表示集合名的结尾。
- 集合名不能以"system."开头,这是为系统集合保留的前缀。
- 用户创建的集合名字不能含有保留字符。有些驱动程序的确支持在集合名里面包含,这是因为某些系统生成的集合中包含该字符。除 非你要访问这种系统创建的集合,否则千万不要在名字里出现$。
- 集合的隐式创建
- 当向一个集合中插入一个文档的时候,如果集合不存在,则会自动创建集合
- 通常我们使用隐式创建文档即可
4.5、集合的删除
集合删除的语法格式如下
db.collection.drop()
--或
db.--集合.drop()
返回值
- 如果成功删除选定集合,则drop()方法返回true,否则返回false
例如:要删除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>
}
)
- 参数
- document
- 要插入到集合中的文档或文档数组。(json格式)
- writeConcern
- 插入选择的性能与可靠性级别
- ordered
- 是否排序
db.comment.insert(
{
"articleid":"100000",
"content":"今天我们来学习mongodb",
"userid":"1001",
"nickname":"Aaron",
"createdatetime":new Date(),
"likenum":NumberInt(10),
"state":null
}
)
- 提示
- comment集合如果不存在,则会隐式创建
- mongo中的数字,默认情况下是double类型,如果要存整数型,必须使用函数NumberInt
- 插入当前日志使用new Date()
- 插入的数据没有指定_id,会自动生成主键值
- 如果某字段没值,可以复制为null,或不写该字段
- 执行成功后会出现如下成功提示
WriteResult({ "nInserted" : 1 })
- 批量插入
db.collection.insertMany(
[ <document 1>, <document2>, ... ],
{
writeConcern: <document>,
ordered: <boolean>
}
)
- 参数
- document
- 要插入到集合中的文档或文档数组。(json格式)
- writeConcern
- 插入选择的性能与可靠性级别
- ordered
- 是否排序
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])
- 参数
- 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 catch进行异常捕捉处理,测试的时候可以不处理
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>
}
)
- 参数
- query
- 更新的选择条件
- update
- 要应用的修改
- upsert
- 可选,如果设置为true,则在没有与查询条件匹配的文档时创建新文档,默认为false,在没有与查询条件匹配的情况下不会插入新文档
- multi
- 可选,如果设置为true,则更新符合查询条件的多个文档,如果设置为false,则更新一个文档,默认值为false
- writeConcern
- 可选,表示写问题的文档,抛出异常的级别
- collation
- 可选,指定要用于操作的校对规则例如字母大小写和重音标记规则
- arrayFilters
- hint
- 可选,指定用于支持查询索引的文档或字符串
- 覆盖修改
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()
--查看这条记录,发现修改成功
- 批量修改
- 修改所有点赞量为1000的昵称为灭霸
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()
- 列值增长的修改
- 如果我们想实现对某列值在原有值的基础上进行增加或减少,可以使用$inc运算符来实现
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)
- 参数
- 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})
- 提示:skip(),limilt(),sort()三个放在一起执行的时候,执行的顺序是先sort(),然后是skip(),最后是显示的limit(),和命令编写顺序无关
4.8、文档的更多查询
4.8.1、正则的复杂条件查询
MongoDB的模糊查询是通过正则表达式的方式实现的,格式为:
db.collection.find({field:/正则表达式/})
--或
db.--集合.find({字段:/正则表达式/})
- 案例
db.comment.find({content:/歌声/})
--查找内容里面包含歌声的
db.comment.find({content:/^什么/})
--查找以什么开头的
4.8.2、比较查询
- 比较查询使用的符号
- $gt:大于
- $lt:小于
- $gte:大于等于
- $lte:小于等于
- $ne:不等于
- 示例
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、单字段索引
- MongoDB支持在文档的单个字段上创建用户定义的升序/降序索引,称为单字段索引(Single Field Index)
- 对于单个字段索引和排序操作,索引键的排序顺序(即升序或降序)并不重要,因为MongoDB可以在任何方向上遍历索引。
5.2.2、复合索引
- MongoDB还支持多个字段的用户定义索引,即复合索引(Compound Index)
- 复合索引中列出的字段顺序具有重要意义,例如,如果符合索引由{userid: 1, score: -1}组成,则索引首先按userid正序排序,然后在每个userid的值内,再按照score倒序排序。
5.2.3、其他索引
- 地理空间索引(Geospatial Index)、文本索引(Text Indexes)、哈希索引(Hashed Indexes)
5.3、索引的管理操作
5.3.1、索引的查看
db.collection.getIndexes()
- 示例
db.comment.getIndexes()
[ {
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
} ]
"v" : 2
- 这表示索引的版本号是 2。MongoDB 从 3.0 版本开始使用新的索引版本格式。
"key" : { "_id" : 1 }
- 这个字段描述了索引的键(字段)。在这里是
_id
字段,排序方式是升序 (1
).
"name" : "_id_"
- 这是索引的名称。MongoDB 会自动为
_id
字段创建一个名为_id_
的索引。
5.3.2、索引的创建
db.collection.createIndex(keys, options)
- 参数
- keys
- 包含字段和值对的文档,其中字段是索引键,描述该字段的索引类型
- 对于字段的上升索引,请指定为1,对于降序指定为-1,比如{字段,1}
- options
- 可选,包含一组控制索引创建的选项的文档
选项 | 类型 | 描述 |
---|---|---|
background | 布尔 | 是否在后台执行创建索引的过程,不阻塞对集合的操作false【默认】 |
unique | 布尔 | 是否创建具有唯一性的索引 false【默认】 |
name | 字符串 | 自定义索引名称,如果不指定,mongodb将通过 下划线 连接 索引字段的名称和排序规则 生成一个索引名称。 一旦创建不能修改,只能删除再重新创建 |
partialFilterExpression | Document | 仅为集合中符合条件的文档建立索引,降低创建和维护成本 |
sparse | 布尔 | 仅为集合中具有指定字段的文档建立索引 false 【默认】 |
expireAfterSeconds | integer单位 秒 | 用于 TTL 索引中 控制 文档保存在集合中的时间 |
storageEngine | Document | 指定存储引擎配置 |
- 单字段索引示例:对userid字段建立索引
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"
}
]
- 复合索引:对userid和nickname同时建立复合(Compound)索引
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)
- 参数
- 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
}
- 现在我们给userid加上索引
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" }
6、副本集
6.1、简介
MongoDB中的副本集(Replica Set)是一组维护相同数据集的mongod服务。 副本集可提供冗余和高可用性,是所有生产部署的基础。
也可以说,副本集类似于有自动故障恢复功能的主从集群。通俗的讲就是用多台机器进行同一数据的异步同步,从而使多台机器拥有同一数据的多个副本,并且当主库宕掉时在不需要用户干预的情况下自动切换其他备份服务器做主库。而且还可以利用副本服务器做只读服务器,实现读写分离,提高负载。
- 冗余和数据可用性
- 复制提供冗余并提高数据可用性。 通过在不同数据库服务器上提供多个数据副本,复制可提供一定级别的容错功能,以防止丢失单个数据库服务器。 在某些情况下,复制可以提供增加的读取性能,因为客户端可以将读取操作发送到不同的服务上,在不同数据中心维护数据副本可以增加分布式应用程序的数据位置和可用性。您还可以为专用目的维护其他副本,例如灾难恢复,报告或备份。
- MongoDB中的复制
- 副本集是一组维护相同数据集的mongod实例。副本集包含多个数据承载节点和可选的一个仲裁节点。 在承载数据的节点中,一个且仅一个成员被视为主节点,而其他节点被视为次要(从)节点。 主节点接收所有写操作。 副本集只能有一个主要能够确认具有{w:“most”}写入关注的写入; 虽然在某些情况下,另一个mongod实例可能暂时认为自己也是主要的。主要记录其操作日志中的数据集的所有更改,即oplog。
- 主从复制和副本集区别
- 主从集群和副本集最大的区别就是副本集没有固定的“主节点”;整个集群会选出一个“主节点”,当其挂掉后,又在剩下的从节点中选中其他节点为“主节点”,副本集总有一个活跃点(主、primary)和一个或多个备份节点(从、secondary)。
6.2、副本集的三个角色
副本集有两种类型三种角色
- 两种类型
- 主节点(Primary)类型:数据操作的主要连接点,可读写
- 次要(辅助、从)节点(Secondaries)类型:数据冗余备份节点,可以读或选举
- 三种角色
- 主要成员(Primary):主要接收所有写操作。就是主节点。
- 副本成员(Replicate):从主节点通过复制操作以维护相同的数据集,即备份数据,不可写操作,但可以读操作(但需要配置)。是默认的一种从节点类型
- 仲裁者(Arbiter):不保留任何数据的副本,只具有投票选举作用,当然也可以将仲裁服务器维护为副本集的一部分,即副本成员同时也可以是仲裁者。也是一种从节点类型。
- 建议
- 如果你的副本+主节点的个数是偶数,建议加一个仲裁者,形成奇数,容易满足大多数的投票。
- 如果你的副本+主节点的个数是奇数,可以不加仲裁者。
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、初始化配置副本集和主节点
- 使用客户端命令连接任一个节点,但这里尽量要连接主节点(27017节点)
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、主节点的选举原则
- MongoDB在副本集中,会自动进行主节点的选举,主节点选举的触发条件
- 主节点故障
- 主节点网络不可达(默认心跳信息为10秒)
- 人工干预(rs.stepDown(600))
- 一旦触发选举,就要根据一定规则来选择主节点。
- 选举规则是根据票数来决定谁获胜
- 票数最高,且获得了“大多数”成员的投票支持的节点获胜。
- “大多数”的定义为:假设复制集内投票成员时N,则大多数为N/2+1。例如:3个投票成员,则大多数的值是2。当复制集内存活成员数量不足大多数时,整个复制集将无法选举primary,复制集将无法提供写服务,处于只读状态。
- 若票数相同,且都获得了“大多数”成员的投票支持的,数据新的节点获胜。
- 数据的新旧是通过操作日志oplog来对比的。
- 在获得票数的时候,优先级(priority)参数影响重大。
- 可以通过设置优先级(priority)来设置额外票数。优先级即权重,取值为0-1000,相当于而我增加0-1000的票数,优先级的值越大,就越可能获得多数成员的投票(votes)数。指定较高的值可使成员更有资格成员主要成员,更低的值可使成员更不符合条件。
- 默认情况下,优先级的值是1
6.6、集群故障分析
6.6.1、副本节点故障
- 主节点和仲裁节点对副本节点的心跳失败。因为主节点还在,因此,没有触发投票选举。
- 如果此时,在主节点写入数据。再启动从节点,会发现,主节点写入的数据,会自动同步给从节点。
- 此时:不影响正常使用
6.6.2、主节点故障
- 从节点和仲裁节点对主节点的心跳失败,当失败超过10秒,此时因为没有主节点了,会自动发起投票。
- 而副本节点只有一台,因此,候选人只有一个就是副本节点,开始投票。
- 仲裁节点向副本节点投了一票,副本节点本身自带一票,因此共两票,超过了“大多数”
- 27019是仲裁节点,没有选举权,27018不向其投票,其票数是0.
- 最终结果,27018成为主节点。具备读写功能。
- 再启动 27017主节点,发现27017变成了从节点,27018仍保持主节点。
- 登录27017节点,发现是从节点了,数据自动从27018同步。
- 此时:不影响正常使用
6.6.3、仲裁节点故障
- 主节点和副本节点对仲裁节点的心跳失败。因为主节点还在,因此,没有触发投票选举。
- 此时:不影响正常使用
6.6.4、、仲裁节点和主节点故障
- 副本集中没有主节点了,导致此时,副本集是只读状态,无法写入。
- 因为27017的票数,没有获得大多数,即没有大于等于2,它只有默认的一票(优先级是1)
- 如果要触发选举,随便加入一个成员即可。
- 如果只加入 27019仲裁节点成员,则主节点一定是27017,因为没得选了,仲裁节点不参与选举,但参与投票。
- 如果只加入 27018节点,会发起选举。因为27017和27018都是一票,则按照谁数据新,谁当主节点。
- 此时:影响正常使用,需要处理
6.6.5、仲裁节点和从节点故障
- 10秒后,27017主节点自动降级为副本节点。(服务降级)
- 副本集不可写数据了,已经故障了。
- 此时:影响正常使用,需要处理
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、分片集群包含的组件
- 分片(存储):每个分片包含分片数据的子集。每个分片都可以部署为副本集。
- mongos(路由):mongos充当查询路由器,在客户端应用程序和分片集群之间提供接口。
- config servers(“调度”的配置):配置服务器存储群集的元数据和配置设置。 从MongoDB 3.4开始,必须将配置服务器部署为副本集(CSRS)。
7.3、分片集群架构目标
两个分片节点副本集(3+3)+ 一个配置节点副本集(3)+两个路由节点(2),共11个服务节点。
7.4、分片机制
7.4.1、数据如何切分
基于分片切分后的数据块称为 chunk,一个分片后的集合会包含多个 chunk,每个 chunk 位于哪个分片(Shard) 则记录在 Config Server(配置服务器)上。
Mongos 在操作分片集合时,会自动根据分片键找到对应的 chunk,并向该 chunk 所在的分片发起操作请求。
数据是根据分片策略来进行切分的,而分片策略则由 分片键(ShardKey)+分片算法(ShardStrategy)组成。
MongoDB 支持两种分片算法:哈希分片和范围分片
7.4.2、如何保证均衡
数据是分布在不同的 chunk上的,而 chunk 则会分配到不同的分片上,那么如何保证分片上的数据(chunk) 是均衡的呢?
- A 全预分配,chunk 的数量和 shard 都是预先定义好的,比如 10个shard,存储1000个chunk,那么每个shard 分别拥有100个chunk。此时集群已经是均衡的状态(这里假定)
- B 非预分配,这种情况则比较复杂,一般当一个 chunk 太大时会产生分裂(split),不断分裂的结果会导致不均衡;或者动态扩容增加分片时,也会出现不均衡的状态。 这种不均衡的状态由集群均衡器进行检测,一旦发现了不均衡则执行 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配置节点
- 27118和27218配置文件省略
- 启动第一套副本集:一主一副本一仲裁
/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、第二套副本集
- 跟第一套副本集一样:端口27318、27418、27518,副本集名称:myshardrs02
- 准备存放数据和日志的目录
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配置节点
- 27418和27518配置文件省略
- 启动第一套副本集:一主一副本一仲裁
/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
- 建立配置文件,注意这边配置文件和上面不太一样,分片角色设为配置节点,sharding: clusterRole: configsvr
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配置节点
- 27119和27219配置文件省略
- 启动副本集:
/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
- 启动mongos
/usr/local/mongodb/bin/mongos -f /usr/local/mongodb/sharded_cluster/mymongos_27017/mongos.conf
- 客户端登录mongos
- 此时,写不进去数据,如果写数据会报错;原因: 通过路由节点操作,现在只是连接了配置节点,还没有连接分片数据节点,因此无法写入业务数据。
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()
- 移除分片
- 注意:如果只剩下最后一个shard,是无法删除的。移除时会自动转移分片数据,需要一个时间过程。完成后,再次执行删除分片命令才能真正删除。
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)
}
}
}
- 开启分片功能:sh.enableSharding("库名")、sh.shardCollection("库名.集合名",{"key":1})
mongos> sh.enableSharding("articledb")
- 集合分片 对集合分片,你必须使用 sh.shardCollection() 方法指定集合和分片键。
sh.shardCollection(namespace, key, unique)
- 参数
- namespace
- 要分片共享的目标集合的命名空间
- key
- 用作分片键的索引规范文档
- unique
- 当值为true情况下,片键字段上会限制为确保是唯一索引
对集合进行分片时,需要选择一个片键(Shard Key) , shard key 是每条记录都必须包含的,且建立了索引的单个字段或复合字段,MongoDB按照片键将数据划分到不同的数据块中,并将数据块均衡地分布到所有分片中。为了按照片键划分数块,MongoDB使用基于哈希的分片方式(随机平均分配)或者基于范围的分片方式(数值大小分配)。用什么字段当片键都可以,如:nickname作为片键,但一定是必填字段。
- 分片规则一:哈希策略
- MongoDB计算一个字段的哈希值,并用这个哈希值来创建数据块。在使用基于哈希分片的系统中,拥有”相近”片键的文档很可能不会存储在同一个数据块中,因此数据的分离性更好一些。
- 使用nickname作为片键,根据其值的哈希值进行数据分片
sh.shardCollection("articledb.comment",{"nickname":"hashed"})
sh.status()
--查看分片状态
- 分片规则二:范围策略
- 对于基于范围的分片,MongoDB按照片键的范围把数据分成不同部分。假设有一个数字的片键,想象一个从负无穷到正无穷的直线,每一个片键的值都在直线上画了一个点.MongoDB把这条直线划分为更短的不重叠的片段,并称之为数据块,每个数据块包含了片键在一定范围内的数据。在使用片键做范围划分的系统中,拥有”相近”片键的文档很可能存储在同一个数据块中,因此也会存储在同 一个分片中。
mongos> sh.shardCollection("articledb.author",{"age":1})
--如使用作者年龄字段作为片键,按照年龄的值进行分片
- 一个集合只能指定一个片键,否则报错
- 一旦对一个集合分片,分片键和分片值就不可改变。 如:不能给集合选择不同的分片键、不能更新分片键的值。
- 根据age索引进行分配数据
基于范围的分片方式提供了更高效的范围查询,给定一个片键的范围,分发路由可以很简单地确定哪个数据块存储了请求需要的数据,并将请求转发到相应的分片中。不过,基于范围的分片会导致数据在不同分片上的不均衡,有时候,带来的消极作用会大于查询性能的积极作用。比如,如果片键所在的字段是线性增长的,一定时间内的所有请求都会落到某个固定的数据块中,最终导致分布在同一个分片中。在这种情况下,一小部分分片承载了集群大部分的数据,系统并不能很好地进行扩展。
基于哈希的分片方式以范围查询性能的损失为代价,保证了集群中数据的均衡。哈希值的随机性使数据随机分布在每个数据块中,因此也随机分布在不同分片中.但是也正由于随机性,一个范围查询很难确定应该请求哪些分片,通常为了返回需要的结果需要请求所有分片。
如无特殊情况,一般推荐使用 Hash Sharding。
使用 _id 作为片键是一个不错的选择,因为它是必有的,可以使用数据文档 _id 的哈希作为片键。 这个方案能够是的读和写都能够平均分布,并且它能够保证每个文档都有不同的片键所以数据块能够很精细。理想化的 shard key 可以让 documents 均匀地在集群中分布。
7.7.3、分片后插入数据测试
- 测试一(哈希规则),登录mongs后,向comment循环插入1000条数据做测试
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
- js的语法,因为mongo的shell是一个JavaScript的shell
- 从路由上插入的数据,必须包含片键,否则无法插入
- 分别登录两个片的主节点,统计文档数量
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" }
- 测试二(范围规则),登录mongs后,向author循环插入20000条测试数据
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
- 发现所有的数据都集中在了一个分片副本上
- 如果发现没有分片:
- 系统繁忙,正在分片中
- 数据块(chunk)没有填满,默认的数据块尺寸是64M,填满后才会向其他片的数据库填充数据,为了测试可以改小,但是生产环境请勿改动
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
- 启动mongos
/usr/local/mongodb/bin/mongos -f /usr/local/mongodb/sharded_cluster/mymongos_27117/mongos.conf
- 客户端登录mongos
- 使用mongo客户端登录27117,发现,第二个路由无需配置,因为分片配置都保存到了配置服务器中了
mongo --port 27117
mongos> sh.status()
8、安全认证
8.1、MongoDB的用户和角色权限介绍
默认情况下,MongoDB实例启动运行时是没有启用用户访问权限控制的,在实例本机服务器上都可以随意连接到实例进行各种操作,MongoDB不会对连接客户端进行用户验证,这是非常危险的。
为了能保障mongodb的安全可以做以下几个步骤
- 使用新的端口,默认的27017端口如果一旦知道了ip就能连接上,不太安全。
- 设置mongodb的网络环境,最好将mongodb部署到公司服务器内网,这样外网是访问不到的。公司内部访问使用vpn等。
- 开启安全认证。认证要同时设置服务器之间的内部认证方式,同时要设置客户端连接到集群的账号密码认证方式。
为了强制开启用户访问控制(用户验证),则需要在MongoDB实例启动时使用选项 --auth 或在指定启动配置文件中添加选项 auth=true
- 角色
- 在MongoDB中通过角色对用户授予相应数据库资源的操作权限,每个角色当中的权限可以显式指定,也可以通过继承其他角色的权限,或者两都都存在的权限。
- 权限
- 权限由指定的数据库资源(resource)以及允许在指定资源上进行的操作(action)组成。
- 资源(resource)包括:数据库、集合、部分集合和集群
- 操作(action)包括:对资源进行的增、删、改、查(CRUD)操作
在角色定义时可以包含一个或多个已存在的角色,新创建的角色会继承包含的角色所有的权限。在同一个数据库中,新创建角色可以继承其他角色的权限,在 admin 数据库中创建的角色可以继承在其它任意数据库中角色的权限。
- 常用的内置角色
- 数据库用户角色:read、readWrite
- 所有数据库用户角色:readAnyDatabase、readWriteAnyDatabase、
- userAdminAnyDatabase、dbAdminAnyDatabase
- 数据库管理角色:dbAdmin、dbOwner、userAdmin
- 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager
- 备份恢复角色:backup、restore
- 超级用户角色:root
- 内部角色:system
> 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、关闭服务器
- 如果修改配置文件,就需要重新启动mongodb
- 标准的关闭方法(数据不容易出错)
mongo --port 27017
use admin
db.shutdownServer()
- 快速关闭方法(快速,简单,数据可能会出错)
- 通过系统的kill命令直接杀死进程
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()
- 开启认证
- 方式一:在命令行中使用auth参数
./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副本集,为了安全,启动安全认证,使用账号密码登录。
对副本集执行访问控制需要配置两个方面
- 副本集和共享集群的各个节点成员之间使用内部身份验证,可以使用密钥文件或x.509证书。密钥文件比较简单,本文使用密钥文件,官方推荐如果是测试环境可以使用密钥文件,但是正式环境,官方推荐x.509证书。原理就是,集群中每一个实例彼此连接的时候都检验彼此使用的证书的内容是否相同。只有证书相同的实例彼此才可以访问
- 使用客户端连接到mongodb集群时,开启访问授权。对于集群外部的访问。如通过可视化客户端,或者通过代码连接的时候,需要开启授权。
在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文件
- 生成一个key文件到当前文件夹中
- 可以使用任何方法生成密钥文件。例如,以下操作使用openssl生成密码文件,然后使用chmod来更改文件权限,仅为文件所有者提供读取权限
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 的工作效率。
1.2、系统缓存
1.2.1、buffer 与 cache
buffer:缓冲也叫写缓冲,一般用于写操作,可以将数据先写入内存再写入磁盘,buffer 一般用于写缓冲,用于解决不同介质的速度不一致的缓冲,先将数据临时写入到里自己最近的地方,以提高写入速度,CPU 会把数据先写到内存的磁盘缓冲区,然后就认为数据已经写入完成看,然后由内核在后续的时间再写入磁盘,所以服务器突然断电会丢失内存中的部分数据。
cache:缓存也叫读缓存,一般用于读操作,CPU 读文件从内存读,如果内存没有就先从硬盘读到内存再读到 CPU,将需要频繁读取的数据放在里自己最近的缓存区域,下次读取的时候即可快速读取。
1.2.2、cache 的保存位置
- 客户端:浏览器
- 内存:本地服务器、远程服务器
- 硬盘:本机硬盘、远程服务器硬盘
1.2.3、cache 的特性
- 自动过期:给缓存的数据加上有效时间,超出时间后自动过期删除
- 过期时间:强制过期,源网站更新图片后 CDN 是不会更新的,需要强制使图片缓存过期
- 命中率:即缓存的读取命中率
1.3、用户层缓存
1.3.1、DNS 缓存
- 默认为 60 秒,即 60 秒之内再访问同一个域名就不再进行 DNS 解析
- 查看 chrome 浏览器的 DNS 缓存:chrome://net-internals/#dns
- DNS 预获取,仅在HTML5中支持,当一个页面中包含多个域名的时候浏览器会先尝试解析域名并进行缓存,之后再使用的时候即可直接使用不需要再进行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 是一起混合使用的
- 特别是 Last-Modified 和 Expire 经常一起使用,因为 Expire 可以让浏览器完全不发起 Http 请求,而当浏览器强制 F5 的时候又有 Last-Modified ,这样就很好的达到了浏览器段缓存的效果。
- Etag 和 Expire 一起使用时,先判断 Expire ,如果已经过期,再发起 Http 请求,如果 Etag 变化了,则返回 200 响应。如果 Etag 没有变化,则返回 304 响应。
- Last-Modified,Etag,Expires 三个同时使用时。先判断 Expire ,然后发送 Http 请求,服务器先判断 last-modified ,再判断 Etag ,必须都没有过期,才能返回 304 响应。
缓存刷新
- 第一次访问,获取最新数据,返回 200响应码
- 鼠标点击二次访问 (Cache),输入地址后回车,浏览器对所有没有过期的内容直接使用本地缓存。
- F5或点刷新按钮, 会向服务器发送请求缓存协商信息,last-modified和etag会有影响,但expires本地过期时间不受影响,无变化返回304
- 按shift+F5强制刷新,所有缓存不再使用,直接连接服务器,获取最新数据,返回200响应码
1.4.5、cookie 和 session
Cookie是访问某些网站以后在本地存储的一些网站相关的信息,下次再访问的时候减少一些步骤,比如加密后的账户名密码等信息
Cookies是服务器在客户端浏览器上存储的小段文本并随每一个请求发送至同一个服务器,是一种实现客户端保持状态的方案。
session称为会话信息,位于web服务器上,主要负责访问者与网站之间的交互,当浏览器请求http地址时,可以基于之前的session实现会话保持、session共享等。
1.5、CDN 缓存
1.5.1、什么是CDN
内容分发网络(Content Delivery Network,CDN)是建立并覆盖在承载网上,由不同区域的服务器组成的分布式网络。将源站资源缓存到全国各地的边缘服务器,利用全球调度系统使用户能够就近获取,有效降低访问延迟,降低源站压力,提升服务可用性。
常见的CDN服务商
- 百度CDN:https://cloud.baidu.com/product/cdn.html
- 阿里CDN:https://www.aliyun.com/product/cdn?spm=5176.8269123.416540.50.728y8n
- 腾讯CDN:https://www.qcloud.com/product/cdn
- 腾讯云CDN收费介绍:https://cloud.tencent.com/document/product/228/2949
1.5.2、用户请求CDN流程
假设您的业务源站域名为www.test.com,域名接入 CDN 开始使用加速服务后,当您的用户发起HTTP 请求时,实际的处理流程如下图所示:
详细说明如下:
- 用户向www.test.com下的某图片资源(如:1.jpg)发起请求,会先向 Local DNS 发起域名解析请求。
- 当 Local DNS 解析www.test.com时,会发现已经配置了 CNAMEwww.test.com.cdn.dnsv1.com,解析请求会发送至 Tencent DNS(GSLB),GSLB 为腾讯云自主研发的调度体系,会为请求分配最佳节点 IP。
- Local DNS 获取 Tencent DNS 返回的解析 IP。
- 用户获取解析 IP。
- 用户向获取的 IP 发起对资源 1.jpg 的访问请求。
- 若该 IP 对应的节点缓存有 1.jpg,则会将数据直接返回给用户(10),此时请求结束。若该节点未缓存 1.jpg,则节点会向业务源站发起对 1.jpg 的请求(6、7、8),获取资源后,结合用户自定义配置的缓存策略,将资源缓存至节点(9),并返回给用户(10),此时请求结束。
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 有效地解决了目前互联网业务中网络层面的以下问题:
- 用户与业务服务器地域间物理距离较远,需要进行多次网络转发,传输延时较高且不稳定。
- 用户使用运营商与业务服务器所在运营商不同,请求需要运营商之间进行互联转发。
- 业务服务器网络带宽、处理能力有限,当接收到海量用户请求时,会导致响应速度降低、可用性降低。
- 利用CDN防止和抵御DDos等攻击,实现安全保护
1.6、应用层缓存
Nginx、PHP等web服务可以设置应用缓存以加速响应用户请求,另外有些解释性语言,比如:PHP/Python/Java不能直接运行,需要先编译成字节码,但字节码需要解释器解释为机器码之后才能执行,因此字节码也是一种缓存,有时候还会出现程序代码上线后字节码没有更新的现象。所以一般上线新版前,需要先将应用缓存清理,再上线新版
另外可以利用动态页面静态化技术,加速访问,比如:将访问数据库的数据的动态页面,提前用程序生成静态页面文件html.电商网站的商品介绍,评论信息非实时数据等皆可利用此技术实现
1.7、数据层缓存
分布式缓存服务
- Redis
- Memcached
数据库
- MySQL 查询缓存
- innodb缓存、MyISAM缓存
1.8、硬件缓存
1.8.1、CPU缓存
CPU缓存(L1的数据缓存和L1的指令缓存)、二级缓存、三级缓存
1.9磁盘相关缓存
- 磁盘缓存:Disk Cache
- 磁盘阵列缓存:Raid Cache,可使用电池防止断电丢失数据
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 中一直比较靠前,而且一直是键值型存储类的首位
官网地址:https://redis.io/
2.2、Redis 特性
- 速度快: 10W QPS,基于内存,C语言实现
- 单线程
- 持久化
- 支持多种数据结构
- 支持多种编程语言
- 功能丰富: 支持Lua脚本,发布订阅,事务,pipeline等功能
- 简单: 代码短小精悍(单机核心代码只有23000行左右),单线程开发容易,不依赖外部库,使用简单
- 主从复制
- 支持高可用和分布式
2.3、单线程
Redis 6.0版本前一直是单线程方式处理用户的请求
单线程为何如此快?
- 纯内存
- 非阻塞
- 避免线程切换和竞态消耗
注意事项:
- 一次只运行一条命令
- 拒绝长(慢)命令:keys, flushall, flushdb, slow lua script, mutil/exec, operate big value(collection)
- 其实不是单线程: 早期版本是单进程单线程,3版本后实际还有其它的线程, fysnc file descriptor,close file descriptor
2.4、redis 对比 memcached
- 支持数据的持久化:可以将内存中的数据保持在磁盘中,重启redis服务或者服务器之后可以从备份文件中恢复数据到内存继续使用
- 支持更多的数据类型:支持string(字符串)、hash(哈希数据)、list(列表)、set(集合)、zset(有序集合)
- 支持数据的备份:可以实现类似于数据的master-slave模式的数据备份,另外也支持使用快照+AOF
- 支持更大的value数据:memcache单个key value最大只支持1MB,而redis最大支持512MB(生产不建议超过2M,性能受影响)
- 在Redis6版本前,Redis 是单线程,而memcached是多线程,所以单机情况下没有memcached 并发高,性能更好,但redis 支持分布式集群以实现更高的并发,单Redis实例可以实现数万并发
- 支持集群横向扩展:基于redis cluster的横向扩展,可以实现分布式集群,大幅提升性能和数据安全性
- 都是基于 C 语言开发
2.5、redis 典型应用场景
- Session 共享:常见于web集群中的Tomcat或者PHP中多web服务器session共享
- 缓存:数据查询、电商网站商品信息、新闻内容
- 计数器:访问排行榜、商品浏览数等和次数相关的数值统计场景
- 微博/微信社交场合:共同好友,粉丝数,关注,点赞评论等
- 消息队列:ELK的日志缓存、部分业务的订阅发布系统
- 地理位置: 基于GEO(地理信息定位),实现摇一摇,附近的人,外卖等功能
数据更新操作流程:
数据读操作流程:
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、启动多实例
- 刚刚启动的案例是6379端口,我们可以在6380端口上开启第二个redis服务
- 为6380准备相关目录和文件
[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
- 前端启动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、解决启动时的三个警告提示
- tcp-backlog
- backlog参数控制的是三次握手的时候server端收到client.ack确认号之后的队列值,即全连接队列
[root@localhost ~]# echo "net.core.somaxconn = 1024" >> /etc/sysctl.conf
[root@localhost ~]# sysctl -p
net.core.somaxconn = 1024
- vm.overcommit_memory
- 查看警告信息有提示,建议将其值改为1
- 0 表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。
- 1 表示内核允许分配所有的物理内存,而不管当前的内存状态如何
- 2 表示内核允许分配超过所有物理内存和交换空间总和的内存
[root@localhost ~]# echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
[root@localhost ~]# sysctl -p
net.core.somaxconn = 1024
vm.overcommit_memory = 1
- transparent huge pages
- 警告:您在内核中启用了透明大页面(THP,不同于一般内存页的4k为2M)支持。 这将在Redis中造成延迟和内存使用问题。 要解决此问题,请以root 用户身份运行命令
echo never> /sys/kernel/mm/transparent_hugepage/enabled
,并将其添加到您的/etc/rc.local
中,以便在重启后保留设置。禁用THP后,必须重新启动Redis。
[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
- 再次启动redis可以看到警告消除,建议在其它redis服务器上做以上配置
3.2.5、创建 redis 用户
[root@localhost ~]# useradd -r -s /sbin/nologin redis
[root@localhost ~]# chown -R redis.redis /apps/redis/
3.2.6、编辑 redis 服务启动文件
- 复制其它主机yum安装生成的redis.service文件,进行修改
[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
- redis 支持多种开发语言访问https://redis.io/clients
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 连接方式
- python 多种开发库,可以支持连接redis
- 安装python运行环境
[root@localhost ~]# yum -y install python3 python3-redis
- 编写python程序
[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 的多实例
- 测试环境中经常使用多实例,需要指定不同实例的相应的端口,配置文件,日志文件等相关配置
- 以编译安装为例实现 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
- 按照同样的方式,启动6380和6381实例
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 主要配置项
- 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 动态修改配置
- config 命令用于查看当前redis配置、以及不重启redis服务实现动态更改redis配置等
- 注意:不是所有配置都可以动态修改,且此方式无法持久保存
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、慢查询
- 可以通过config set命令动态修改参数,并使配置持久化到配置文件中
config set slowlog-log-slower-than 20000
config set slowlog-max-len 1000
config rewrite
- 获取慢查询日志,将慢查询的时间改为1微秒
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) ""
- 可以看到每个慢查询日志有4个属性组成,分别是慢查询日志的标识id、发生时间戳、命令耗时、执行命令和参数
- 慢查询日志重置
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
4.4.1、RDB 模式
RDB 模式工作原理
RDB(Redis DataBase):基于时间的快照,其默认只保留当前最新的一次快照,特点是执行速度比较快,缺点是可能会丢失从上次快照到当前时间点之间未做快照的数据
RDB bgsave 实现快照的具体过程
- Redis从master主进程先fork出一个子进程,使用写时复制机制,子进程将内存的数据保存为一个临时文件
- 当数据保存完成之后再将上一次保存的RDB文件替换掉,然后关闭子进程,这样可以保证每一次做RDB快照保存的数据都是完整的
- 因为直接替换RDB文件的时候,可能会出现突然断电等问题,而导致RDB文件还没有保存完整就因为突然关机停止保存,而导致数据丢失的情况.后续可以手动将每次生成的RDB文件进行备份,这样可以最大化保存历史数据
[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方式
- bgsave:异步后台执行,不影响其它命令的执行
- 自动: 制定规则,自动执行
RDB 模式优点
- RDB快照保存了某个时间点的数据,可以通过脚本执行redis指令bgsave(非阻塞,后台执行)或者save(会阻塞写操作,不推荐)命令自定义时间点备份,可以保留多个备份,当出现问题可以恢复到不同时间点的版本,很适合备份,并且此文件格式也支持有不少第三方工具可以进行后续的数据分析
- 比如: 可以在最近的24小时内,每小时备份一次RDB文件,并且在每个月的每一天,也备份一个ROB文件。这样的话,即使遇上问题,也可以随时将数据集还原到不同的版本。
- RDB可以最大化Redis的性能,父进程在保存 RDB文件时唯一要做的就是fork出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘工/0操作。
- RDB在大量数据,比如几个G的数据,恢复的速度比AOF的快
RDB 模式缺点
- 不能实时保存数据,可能会丢失自上一次执行RDB备份到当前的内存数据
- 如果你需要尽量避免在服务器故障时丢失数据,那么RDB不适合你。虽然Redis允许你设置不同的保存点(save point)来控制保存RDB文件的频率,但是,因为RDB文件需要保存整个数据集的状态,所以它并不是一个轻松的操作。因此你可能会至少5分钟才保存一次RDB文件。在这种情况下,一旦发生故障停机,你就可能会丢失好几分钟的数据。
- 当数据量非常大的时候,从父进程fork子进程进行保存至RDB文件时需要一点时间,可能是毫秒或者秒,取决于磁盘IO性能
- 在数据集比较庞大时,fork()可能会非常耗时,造成服务器在一定时间内停止处理客户端﹔如果数据集非常巨大,并且CPU时间非常紧张的话,那么这种停止时间甚至可能会长达整整一秒或更久。虽然 AOF重写也需要进行fork(),但无论AOF重写的执行间隔有多长,数据的持久性都不会有任何损失。
- 执行bgsave命令,Redis父进程判断当前是否存在正在执行的子进 程,如RDB/AOF子进程,如果存在bgsave命令直接返回。
- 父进程执行fork操作创建子进程,fork操作过程中父进程会阻塞,通 过info stats命令查看latest_fork_usec选项,可以获取最近一个fork操作的耗时,单位为微秒。
- 父进程fork完成后,bgsave命令返回“Background saving started”信息 并不再阻塞父进程,可以继续响应其他命令。
- 子进程创建RDB文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换。执行lastsave命令可以获取最后一次生成RDB的 时间,对应info统计的rdb_last_save_time选项。
- 进程发送信号给父进程表示完成,父进程更新统计信息,
4.4.2、AOF 模式
AOF 模式工作原理
- AOF:AppendOnylFile,按照操作顺序依次将操作追加到指定的日志文件末尾
- AOF 和 RDB 一样使用了写时复制机制,AOF默认为每秒钟 fsync一次,即将执行的命令保存到AOF文件当中,这样即使redis服务器发生故障的话最多只丢失1秒钟之内的数据,也可以设置不同的fsync策略always,即设置每次执行命令的时候执行fsync,fsync会在后台执行线程,所以主线程可以继续处理用户的正常请求而不受到写入AOF文件的I/O影响
- 同时启用RDB和AOF,进行恢复时,默认AOF文件优先级高于RDB文件,即会使用AOF文件进行恢复
- 注意: AOF 模式默认是关闭的,第一次开启AOF后,并重启服务生效后,会因为AOF的优先级高于RDB,而AOF默认没有文件存在,从而导致所有数据丢失
AOF rewrite 重写
将一些重复的,可以合并的,过期的数据重新写入一个新的AOF文件,从而节约AOF备份占用的硬盘空间,也能加速恢复过程
可以手动执行bgrewriteaof 触发AOF,或定义自动rewrite 策略
AOF rewrite 过程
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 模式优点
- 数据安全性相对较高,根据所使用的fsync策略(fsync是同步内存中redis所有已经修改的文件到存储设备),默认是appendfsync everysec,即每秒执行一次 fsync,在这种配置下,Redis 仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据( fsync会在后台线程执行,所以主线程可以继续努力地处理命令请求)
- 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中不需要seek, 即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,可以通过 redis-check-aof 工具来解决数据一致性的问题
- Redis可以在 AOF文件体积变得过大时,自动地在后台对AOF进行重写,重写后的新AOF文件包含了恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的,因为Redis在创建新 AOF文件的过程中,append模式不断的将修改数据追加到现有的 AOF文件里面,即使重写过程中发生停机,现有的 AOF文件也不会丢失。而一旦新AOF文件创建完毕,Redis就会从旧AOF文件切换到新AOF文件,并开始对新AOF文件进行追加操作。
- AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,也可以通过该文件完成数据的重建。
- AOF文件有序地保存了对数据库执行的所有写入操作,这些写入操作以Redis协议的格式保存,因此 AOF文件的内容非常容易被人读懂,对文件进行分析(parse)也很轻松。导出(export)AOF文件也非常简单:举个例子,如果你不小心执行了FLUSHALL.命令,但只要AOF文件未被重写,那么只要停止服务器,移除 AOF文件末尾的FLUSHAL命令,并重启Redis ,就可以将数据集恢复到FLUSHALL执行之前的状态。
AOF 模式缺点
- 即使有些操作是重复的也会全部记录,AOF 的文件大小要大于 RDB 格式的文件
- AOF 在恢复大数据集时的速度比 RDB 的恢复速度要慢
- 根据fsync策略不同,AOF速度可能会慢于RDB
- bug 出现的可能性更多
4.4.3、RDB和AOF 的选择
如果主要充当缓存功能,或者可以承受数分钟数据的丢失, 通常生产环境一般只需启用RDB即可,此也是默认值
如果数据需要持久保存,一点不能丢失,可以选择同时开启RDB和AOF,一般不建议只开启AOF
5、Redis 常用命令
参考链接:http://redisdoc.com/
5.1、INFO
- 显示当前节点redis运行状态信息
127.0.0.1:6379> info
5.2、SELECT
- 切换数据库,相当于在MySQL的 USE DBNAME 指令
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
- 注意: 在 redis cluster 模式下不支持多个数据库,会出现下面错误
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
- 查看当前库下的所有key,此命令慎用!
命令 | 时间复杂度 |
---|---|
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
- 手动在后台执行RDB持久化操作
#交互式执行
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
- 返回当前库下的所有key 数量
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
- 强制清空当前库中的所有key,此命令慎用!
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
- 强制清空当前redis服务器所有数据库中的所有key,即删除所有数据,此命令慎用!
127.0.0.1:6379> FLUSHALL
OK
#生产建议修改配置 /etc/redis.conf,禁用或改为别名
rename-command FLUSHALL ""
5.8、SHUTDOWN
- SHUTDOWN 命令执行以下操作:
- 停止所有客户端
- 如果有至少一个保存点在等待,执行 SAVE 命令
- 如果 AOF 选项被打开,更新 AOF 文件
- 关闭 redis 服务器(server)
- 如果持久化被打开的话, SHUTDOWN 命令会保证服务器正常关闭而不丢失任何数据。
- 另一方面,假如只是单纯地执行 SAVE 命令,然后再执行 QUIT 命令,则没有这一保证因为在执行SAVE 之后、执行 QUIT 之前的这段时间中间,其他客户端可能正在和服务器进行通讯,这时如果执行QUIT 就会造成数据丢失。
6、redis 数据类型
6.1、字符串 string
字符串是所有编程语言中最常见的和最常用的数据类型,而且也是redis最基本的数据类型之一,而且redis 中所有的 key 的类型都是字符串。常用于保存 Session 信息场景,此数据类型比较常用
- 添加一个key
- set 指令可以创建一个key 并赋值, 使用格式
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)
- 获取一个key的内容
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
- 删除一个和多个key
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
- 批量设置多个key
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
- 批量获取多个key
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"
- 返回字符串 key 对应值的字节数
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
- 判断 key 是否存在
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
- 查看 key 的过期时间
- ttl key #查看key的剩余生存时间,如果key过期后,会自动删除
- -1 #返回值表示永不过期,默认创建的key是永不过期,重新对key赋值,也会从有剩余生命周期变成永不过期
- -2 #返回值表示没有此key
- num #key的剩余有效期
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
- 重新设置key的过期时间
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
- 取消key的过期时间
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
- 数值递增
- 利用INCR命令簇(INCR, DECR, [INCRBY],DECRBY)来把字符串当作原子计数器使用
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
列表是一个双向可读写的管道,其头部是左侧,尾部是右侧,一个列表最多可以包含2^32-1(4294967295)个元素,下标 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,元素值可以重复,常用于存入日志等场景,此数据类型比较常用
- 列表特点:
- 有序
- 可重复
- 左右都可以操作
- 生成列表并插入数据
- LPUSH和RPUSH都可以插入列表
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
- 获取列表指定位置数据
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"
- 删除list
127.0.0.1:6379> del list1
(integer) 1
127.0.0.1:6379> exists list1
(integer) 0
6.3、集合 set
Set 是 String 类型的无序集合,集合中的成员是唯一的,这就意味着集合中不能出现重复的数据,可以在两个不同的集合中对数据进行对比并取值,常用于取值判断,统计,交集等场景
- 集合特点
- 无序
- 无重复
- 集合间操作
- 生成集合key
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"
- 获取集合的交集
- 交集:已属于A且属于B的元素称为A与B的交集
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"
- 获取集合的并集
- 并集:已属于A或属于B的元素称为A与B的并集
127.0.0.1:6379> sunion set1 set2
1) "v1"
2) "v4"
3) "v5"
4) "v3"
5) "v2"
- 获取集合的差集
- 差集:已属于A而不属于B的元素称为A与B的差(集)
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多亿个成员),经常用于排行榜的场景
- 有序集合特点
- 有序
- 无重复元素
- 每个元素是由score和value组成
- score 可以重复
- value 不可以重复
- 生成有序集合
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特别适合用于存储对象场景
- 生成 hash key
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"
- 获取hash key的对应字段的值
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"
- 删除一个hash key 的对应字段
127.0.0.1:6379> hdel 9527 age
(integer) 1
127.0.0.1:6379> hget 9527 age
(nil)
- 批量设置hash key的多个field和value
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"
- 获取hash中指定字段的值
127.0.0.1:6379> hmget 1024 name city
1) "xwz"
2) "changzhou"
- 获取hash中的所有字段名field
127.0.0.1:6379> hkeys 1024
1) "name"
2) "age"
3) "city"
- 获取hash key对应所有field的value
127.0.0.1:6379> hvals 1024
1) "xwz"
2) "18"
3) "changzhou"
- 获取指定hash key 的所有field及value
127.0.0.1:6379> hgetall 1024
1) "name"
2) "xwz"
3) "age"
4) "18"
5) "city"
6) "changzhou"
- 删除 hash
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、消息队列
- 消息队列: 把要传输的数据放在队列中
- 功能: 可以实现多个系统之间的解耦,异步,削峰/限流等
- 常用的消息队列应用: kafka,rabbitMQ,redis
消息队列主要分为两种,这两种模式Redis都支持
- 生产者/消费者模式
- 发布者/订阅者模式
7.1、生产者消费者模式
在生产者/消费者(Producer/Consumer)模式下,上层应用接收到的外部请求后开始处理其当前步骤的操作,在执行完成后将已经完成的操作发送至指定的频道(channel,逻辑队列)当中,并由其下层的应用监听该频道并继续下一步的操作,如果其处理完成后没有下一步的操作就直接返回数据给外部请求,如果还有下一步的操作就再将任务发布到另外一个频道,由另外一个消费者继续监听和处理。此模式应用广泛
7.1.1、模式介绍
生产者消费者模式下,多个消费者同时监听一个队列,但是一个消息只能被最先抢到消息的消费者消费,即消息任务是一次性读取和处理,此模式在分布式业务架构中很常用,比较常用的消息队列软件还有RabbitMQ、Kafka、RocketMQ、ActiveMQ等。
7.1.2、队列介绍
队列当中的消息由不同的生产者写入,也会有不同的消费者取出进行消费处理,但是一个消息一定是只能被取出一次也就是被消费一次。
- 生产者发布消息
[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的消费者都会收到同样的一份消息,这种模式类似于是收音机的广播模式,即凡是收听某个频道的听众都会收到主持人发布的相同的消息内容。此模式常用于群聊天、群通知、群公告等场景
- Publisher:发布者
- Subscriber:订阅者
- Channel:频道
- 订阅者监听频道
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服务器本身出现系统故障、硬件故障等问题后,就会直接造成数据的丢失
此外,单机的性能也是有极限的,因此需要使用另外的技术来解决单点故障和性能扩展的问题。
8.1、redis 主从复制架构
主从模式(master/slave),可以实现Redis数据的跨主机备份。
程序端连接到高可用负载的VIP,然后连接到负载服务器设置的Redis后端real server,此模式不需要在程序里面配置Redis服务器的真实IP地址,当后期Redis服务器IP地址发生变更只需要更改redis 相应的后端real server即可, 可避免更改程序中的IP地址设置。
- 一个master可以有多个slave
- 一个slave只能有一个master
- 数据流向是单向的,master到slave
8.2、主从复制实现
Redis Slave 也要开启持久化并设置和master同样的连接密码,因为后期slave会有提升为master的可能,Slave 端切换master同步后会丢失之前的所有数据,而通过持久化可以恢复数据
一旦某个Slave成为一个master的slave,Redis Slave服务会清空当前redis服务器上的所有数据并将master的数据导入到自己的内存,但是如果只是断开同步关系后,则不会删除当前已经同步过的数据。
当配置Redis复制功能时,强烈建议打开主服务器的持久化功能。否则的话,由于延迟等问题,部署的服务应该要避免自动启动。
- 假设节点A为主服务器,并且关闭了持久化。并且节点B和节点c从节点A复制数据
- 节点A崩溃,然后由自动拉起服务重启了节点A.由于节点A的持久化被关闭了,所以重启之后没有任何数据
- 节点B和节点c将从节点A复制数据,但是A的数据是空的,于是就把自身保存的数据副本删除。
在关闭主服务器上的持久化,并同时开启自动拉起进程的情况下,即便使用Sentinel来实现Redis的高可用性,也是非常危险的。因为主服务器可能拉起得非常快,以至于Sentinel在配置的心跳时间间隔内没有检测到主服务器已被重启,然后还是会执行上面的数据丢失的流程。无论何时,数据安全都是极其重要的,所以应该禁止主服务器关闭持久化的同时自动启动。
8.2.1、命令行配置
- 启用主从同步
- 默认redis 状态为master,需要转换为slave角色并指向master服务器的IP+PORT+Password
- REPLICAOF MASTER_IP PORT 指令可以启用主从同步复制功能,早期版本使用 SLAVEOF 指令
- 在master上设置key1
[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"
- 以下都在slave上执行,登录,设置key1
[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"
- 在第二个slave2上,也设置相同的key1,但值不同
[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"
- 在所有的slave上设置master的IP和端口,4.0版本之前的指令为slaveof
- 在slave1上
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"
- 在master上可以看到所有slave的信息
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
- 删除主从同步
- REPLICAOF NO ONE 指令可以取消主从复制
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
- 在master上看到slave数量减少
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
8.2.2、同步日志
- 在 master 上观察日志
[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
- 在 slave 节点观察日志
[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节点配置文件
- 在slave1上
[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
- 在slave2上
[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和slave查看状态
# 在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的redis服务:systemctl stop redis,在slave上可以看到以下现象
# 在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
- slave 观察日志
[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
- slave 状态只读无法写入数据
127.0.0.1:6379> set key1 v1-slave1
(error) READONLY You cant write against a read only replica.
8.3、主从复制故障恢复
8.3.1、主从复制故障恢复过程介绍
- slave 节点故障和恢复
- client指向另一个从节点即可,并及时修复故障从节点
- master 节点故障和恢复
- 需要提升slave为新的master
- master故障后,只能手动提升一个slave为新master,不支持自动切换。master的切换会导致master_replid发生变化,slave之前的master_replid就和当前master不一致从而会引发所有slave的全量同步
8.3.2、主从复制故障恢复实现
- 假设当前主节点192.168.175.10故障,提升192.168.175.20为新的master
[root@master ~]# systemctl stop redis
- 查看slave1
[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
- 停止slave1(192.168.175.20)同步并提升为新的master
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
- 修改所有的slave指向新的master节点
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"
- 在新master(192.168.175.20)上可看到slave
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的级联复制
在前面搭建好的一主一从架构中,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,观察是否同步
# 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"
- 在中间那个slave(即级联salve1 192.168.175.20)上查看状态
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主从复制分为全量同步和增量同步
- 全量复制过程
首次同步是全量同步,主从同步可以让从服务器从主服务器同步数据,而且从服务器还可再有其它的从服务器,即另外一台redis服务器可以从一台从服务器进行数据同步,redis 的主从同步是非阻塞的,master收到从服务器的psync(2.8版本之前是SYNC)命令,会fork一个子进程在后台执行bgsave命令,并将新写入的数据写入到一个缓冲区中,bgsave执行完成之后,将生成的RDB文件发送给slave,然后master再将缓冲区的内容以redis协议格式再全部发送给slave,slave 先删除旧数据,slave将收到后的RDB文件载入自己的内存,再加载所有收到缓冲区的内容 从而这样一次完整的数据同步
Redis全量复制一般发生在Slave首次初始化阶段,这时Slave需要将Master上的所有数据都复制一份。
- 增量复制过程
全量同步之后再次需要同步时,从服务器只要发送当前的offset位置(等同于MySQL的binlog的位置)给主服务器,然后主服务器根据相应的位置将之后的数据(包括写在缓冲区的积压数据)发送给从服务器,其再次保存到其内存即可。
- 主从同步完整过程
- 从服务器连接主服务器,发送PSYNC命令
- 主服务器接收到PSYNC命令后,开始执行BGSAVE命令生成RDB快照文件并使用缓冲区记录此后执行的所有写命令
- 主服务器BGSAVE执行完后,向所有从服务器发送RDB快照文件,并在发送期间继续记录被执行的写命令
- 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照至内存
- 主服务器快照发送完毕后,开始向从服务器发送缓冲区中的写命令
- 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令
- 后期同步会先发送自己slave_repl_offset位置,只同步新增加的数据,不再全量同步
- 复制缓冲区(环形队列)配置参数
#复制缓冲区大小,建议要设置足够大
rep-backlog-size 1mb
#Redis同时也提供了当没有slave需要同步的时候,多久可以释放环形队列:
repl-backlog-ttl 3600
- 避免全量复制
- 第一次全量复制不可避免,后续的全量复制可以利用小主节点(内存小),业务低峰时进行全量
- 节点运行 run-id 不匹配:主节点重启会导致RUNID变化,可能会触发全量复制,可以利用故障转移,例如哨兵或集群,而从节点重新启动,不会导致全量复制
- 复制积压缓冲区不足: 当主节点生成的新数据大于缓冲区大小,从节点恢复和主节点连接后,会导致全量复制.解决方法将repl-backlog-size 调大
- 避免复制风暴
- 单节点复制风暴
- 当主节点重启,多从节点复制
- 解决方法:更换复制拓扑
- 单机器复制风暴
- 机器宕机后,大量全量复制
- 解决方法:主节点分散多机器
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之间必须保持版本一致
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服务器性能无法满足业务写入需求的时候,也需要解决以上的两个核心问题
- master和slave角色的无缝切换,让业务无感知从而不影响业务使用
- 可横向动态扩展Redis服务器,从而实现多台服务器并行写入以实现更高并发的目的。
Redis 集群实现方式
- 客户端分片: 由应用决定将不同的KEY发送到不同的Redis服务器
- 代理分片: 由代理决定将不同的KEY发送到不同的Redis服务器,代理程序如:codis,twemproxy等
- Redis Cluster
9.2、哨兵 (Sentinel) 工作原理
9.2.1、sentinel 架构和故障转移
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中的三个定时任务
- 每10秒每个sentinel对master和slave执行info
- 发现slave节点
- 确认主从关系
- 每2秒每个sentinel通过master节点的channel交换信息(pub/sub)
- 通过sentinel_hello频道交互
- 交互对节点的“看法”和自身信息
- 每1秒每个sentinel对其他sentinel和redis执行ping
9.3、实现哨兵
- 哨兵的准备实现主从复制架构
- 哨兵的前提是已经实现了一个redis的主从复制的运行环境,从而实现一个一主两从基于哨兵的高可用redis架构
- 注意: master 的配置文件中masterauth 和slave 都必须相同
# 在所有主从节点执行,这里以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
- master服务器状态
[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实际上是一个特殊的redis服务器,有些redis指令支持,但很多指令并不支持.默认监听在26379/tcp端口
- 哨兵可以不和Redis服务器部署在一起,但一般部署在一起,所有redis节点使用相同的配置文件
# 如果是编译安装,在源码目录有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
#禁止修改脚本
- 三个哨兵服务器的配置都如下,以master为例
[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
- 故障转移后的redis配置文件会被自动修改
[root@slave1 ~]# grep "^replicaof" /apps/redis/etc/redis.conf
replicaof 192.168.175.30 6379
- 哨兵配置文件的sentinel monitor IP 同样也会被修改
[root@slave1 ~]# grep monitor /apps/redis/etc/sentinel.conf
sentinel monitor mymaster 192.168.175.30 6379 2
- 新的master 状态
[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
- 客户端链接sentinel工作原理
- 选举出一个sentinel
- 由这个sentinel 通过masterName 获取master节点信息
- sentinel 发送role指令确认mater的信息
- 客户端订阅sentinel的相关频道,获取新的master 信息变化,并自动连接新的master
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
10.1、Redis Cluster 工作原理
在哨兵sentinel机制中,可以解决redis高可用问题,即当master故障后可以自动将slave提升为master,从而可以保证redis服务的正常使用,但是无法解决redis单机写入的瓶颈问题,即单机redis写入性能受限于单机的内存大小、并发数量、网卡速率等因素。
- 早期Redis 分布式集群部署方案
- 客户端分区:由客户端程序决定key写分配和写入的redis node,但是需要客户端自己处理写入分配、高可用管理和故障转移等
- 代理方案:基于三方软件实现redis proxy,客户端先连接至代理层,由代理层实现key的写入分配,对客户端来说是有比较简单,但是对于集群管理节点增减相对比较麻烦,而且代理本身也是单点和性能瓶颈。
redis 3.0版本之后推出了无中心架构的redis cluster机制,在无中心的redis集群当中,其每个节点保存当前节点数据和整个集群状态,每个节点都和其他所有节点连接
- Redis Cluster特点如下
- 所有Redis节点使用(PING机制)互联
- 集群中某个节点的是否失效,是由整个集群中超过半数的节点监测都失效,才能算真正的失效
- 客户端不需要proxy即可直接连接redis,应用程序中需要配置有全部的redis服务器IP
- redis cluster把所有的redis node 平均映射到 0-16383个槽位(slot)上,读写需要到指定的redis node上进行操作,因此有多少个redis node相当于redis 并发扩展了多少倍,每个redis node 承担16384/N个槽位
- Redis cluster预先分配16384个(slot)槽位,当需要在redis集群中写入一个key -value的时候,会使用CRC16(key) mod 16384之后的值,决定将key写入值哪一个槽位从而决定写入哪一个Redis节点上,从而有效解决单机瓶颈。
10.1.1、Redis cluster 基本架构
假如三个主节点分别是:A,B,C三个节点,采用哈希槽(hash slot)的方式来分配16384个slot的活,它们三个节点分别承担的slot 区间可以是:
- 节点A覆盖:0-5460
- 节点B覆盖:5461-10922
- 节点C覆盖:10923-16383
10.1.2、Redis cluster 主从架构
Redis cluster的架构虽然解决了并发的问题,但是又引入了一个新的问题,每个Redis master的高可用如何解决?
那就是对每个master 节点都实现主从复制,从而实现 redis 高可用性
10.1.3、Redis Cluster 部署架构说明
- 环境A:3台服务器,每台服务器启动6379和6380两个redis 服务实例,适用于测试环境
- 环境B:6台服务器,分别是三组master/slave,适用于生产环境
10.1.4、部署方式介绍
- redis cluster 有多种部署方法
- 原生命令安装
- 理解Redis Cluster架构
- 生产环境不使用
- 官方工具安装
- 高效、准确
- 生产环境可以使用
- 自主研发
- 可以实现可视化的自动化部署
10.2、原生命令手动部署
- 在所有节点安装redis,并配置开启cluster功能
- 各个节点执行meet,实现所有节点的相互通信
- 为各个master 节点指派槽位范围
- 指定各个节点的主从关系
10.2.1、原生命令
- 集群
- cluster info :打印集群的信息
- cluster nodes :列出集群当前已知的所有节点( node),以及这些节点的相关信息。
- 节点
- cluster meet :将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。
- cluster forget :从集群中移除 node_id 指定的节点。
- cluster replicate :将当前从节点设置为 node_id 指定的master节点的slave节点。只能针对slave节点操作。
- cluster saveconfig :将节点的配置文件保存到硬盘里面。
- 槽(slot)
- cluster addslots [slot ...] :将一个或多个槽( slot)指派( assign)给当前节点。
- cluster delslots [slot ...] :移除一个或多个槽对当前节点的指派。
- cluster flushslots :移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。
- cluster setslot node :将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给
- 另一个节点,那么先让另一个节点删除该槽>,然后再进行指派。
- cluster setslot migrating :将本节点的槽 slot 迁移到 node_id 指定的节点中。
- cluster setslot importing :从 node_id 指定的节点中导入槽 slot 到本节点。
- cluster setslot stable :取消对槽 slot 的导入( import)或者迁移( migrate)。
- 键
- cluster keyslot :计算键 key 应该被放置在哪个槽上。
- cluster countkeysinslot :返回槽 slot 目前包含的键值对数量。
- cluster getkeysinslot :返回 count 个 slot 槽中的键 。
10.2.2、实战案例: 利用原生命令手动部署redis cluster
- 准备6台虚拟机
- 在所有节点安装redis并启动cluster功能
#在克隆之前将如下操作在模板上操作完成
[root@localhost ~]# vim redis_install.sh
#参照一键编译安装redis脚本
[root@localhost ~]# . redis_install.sh
# 开始编译安装redis
- 将克隆过的主机的IP地址配置完成,防火墙放行对应的端口号
firewall-cmd --add-port=1-65535/tcp --permanent
firewall-cmd --add-port=1-65535/udp --permanent
firewall-cmd --reload
hostnamectl set-hostname master1
- 启用redis集群支持
[root@master1 ~]# vim /apps/redis/etc/redis.conf
cluster-enabled yes
[root@master1 ~]# systemctl restart redis
- 执行 meet 操作实现相互通信
# 在任意一节点上和其它所有节点进行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
- 由于没有分配槽位,无法创建key
[root@master1 ~]# redis-cli -a centos --no-auth-warning set name eagle
(error) CLUSTERDOWN Hash slot not served
- 查看当前状态,以master1为例
[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
- 为每个master 节点指派槽位范围
[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"
- 验证 redis cluster 访问
- -c 表示以集群方式连接
[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.
- 创建 redis cluster集群的环境准备
- 每个redis 节点采用相同的硬件配置、相同的密码、相同的redis版本
- 所有redis服务器必须没有任何数据
- 准备6台主机,开启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
- 查看指定master节点的slave节点信息
[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
- redis cluster 写入key
[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故障
- 对应的slave节点自动提升为新master
- 模拟master3节点出故障,需要相应的数秒故障转移时间
[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代理服务
- 代理一词往往并不陌生, 该服务我们常常用到如(代理理财、代理租房、代理收货等等)
- 在没有代理模式的情况下,客户端和Nginx服务端,都是客户端直接请求服务端,服务端直接响应客户端。
- 那么在互联网请求里面,客户端往往无法直接向服务端发起请求,那么就需要用到代理服务,来实现客户端和服务通信
2、Nginx代理服务常见模式
- Nginx作为代理服务,按照应用场景模式进行总结,代理分为正向代理、反向代理
- 正向代理与反向代理的区别
- 区别在于形式上服务的”对象”不一样
- 正向代理代理的对象是客户端,为客户端服务
- 反向代理代理的对象是服务端,为服务端服务
3、Nginx代理服务支持协议
- Nginx作为代理服务,可支持的代理协议非常的多
- 如果将Nginx作为反向代理服务,常常会用到如下几种代理协议
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/
- url跳转修改返回Location[不常用]
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;
- 代理到后端的TCP连接、响应、返回等超时时间
//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
- proxy_buffer代理缓冲区
//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
- 常用优化配置
- Proxy代理网站常用优化配置如下,将配置写入新文件,调用时使用include引用即可
[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时调用方便后续多个Location重复使用
location / {
proxy_pass http://127.0.0.1:8080;
include proxy_params;
}
5、Nginx反向代理场景实践
- Nginx反向代理配置实例
- web01服务器,配置一个网站,监听在8080
[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
- proxy代理服务,配置监听80端口,使能够通过代理服务器访问到后端的192.168.175.20的8080端口站点内容
[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
- 存在的问题,通过抓包可以看到客户端是使用域名对网站进行访问的,但是代理却是使用的IP地址加端口号
- 当访问80端口的时候,没有域名的情况下,默认会去找排在最上面的那个配置文件。
- 所以我们需要解决这个问题,保留住最开始的请求头部信息。
- 修改配置文件,使用
proxy_set_header
模块
[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;
}
}
- 使用http1.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;
}
}
- 在生产环境中,我们必须要记录客户端的来源IP,如果所有的访问日志,全都来源于代理,那么我们根本不知道都有哪些地区的用户访问了我们什么页面。
- 还需要使用
proxy_set_header
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、为什么要使用负载均衡
- 当我们的Web服务器直接面向用户,往往要承载大量并发请求,单台服务器难以负荷,我使用多台Web服务器组成集群,前端使用Nginx负载均衡,将请求分散的打到我们的后端服务器集群中,实现负载的分发。那么会大大提升系统的吞吐率、请求性能、高容灾
- 往往我们接触的最多的是
SLB(Server Load Balance)
负载均衡,实现最多的也是SLB
、那么SLB
它的调度节点和服务节点通常是在一个地域里面。那么它在这个小的逻辑地域里面决定了他对部分服务的实时性、响应性是非常好的。 - 所以说当海量用户请求过来以后,它同样是请求调度节点,调度节点将用户的请求转发给后端对应的服务节点,服务节点处理完请求后在转发给调度节点,调度节点最后响应给用户节点。这样也能实现一个均衡的作用,那么Nginx则是一个典型的
SLB
- 负载均衡的叫法有很多
- 负载均衡
- Load Balance
- LB
- 公有云中叫法
- SLB 阿里云负载均衡
- QLB 青云负载均衡
- CLB 腾讯云负载均衡
- ULB ucloud负载均衡
- 常见的负载均衡的软件
- Nginx
- Haproxy
- LVS
1.2、四层负载均衡
所谓四层负载均衡指的是OSI
七层模型中的传输层,那么传输层Nginx已经能支持TCP/IP的控制,所以只需要对客户端的请求进行TCP/IP协议的包转发就可以实现负载均衡,那么它的好处是性能非常快、只需要底层进行应用处理,而不需要进行一些复杂的逻辑。
1.3、七层负载均衡
七层负载均衡它是在应用层,那么它可以完成很多应用方面的协议请求,比如我们说的http应用的负载均衡,它可以实现http信息的改写、头信息的改写、安全应用规则控制、URL匹配规则控制、以及转发、rewrite等等的规则,所以在应用层的服务里面,我们可以做的内容就更多,那么Nginx则是一个典型的七层负载均衡SLB
1.4、四层负载均衡与七层负载均衡区别
四层负载均衡数据包在底层就进行了分发,而七层负载均衡数据包则是在最顶层进行分发、由此可以看出,七层负载均衡效率没有四负载均衡高。
但七层负载均衡更贴近于服务,如:http协议就是七层协议,我们可以用Nginx可以作会话保持,URL路径规则匹配、head头改写等等,这些是四层负载均衡无法实现的。
注意:四层负载均衡不识别域名,七层负载均衡识别域名
2、Nginx负载均衡配置场景
Nginx要实现负载均衡需要用到proxy_pass
代理模块配置.
Nginx负载均衡与Nginx代理不同地方在于,Nginx的一个location
仅能代理一台服务器,而Nginx负载均衡则是将客户端请求代理转发至一组upstream虚拟服务池.
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文件
- 打开浏览器访问:http://node.test.com
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
- ip_hash: 根据客户端IP地址进行哈希,确保来自同一IP的客户端总是被转发到同一台后端服务器
- 具体配置不能和weight一起使用。
#如果客户端都走相同代理, 会导致某一台服务器连接过多
upstream load_pass {
ip_hash;
server 192.168.88.10:80;
server 192.168.88.20:80;
}
4、Nginx负载均衡后端状态
- 后端Web服务器在前端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负载均衡健康检查
- 在Nginx官方模块提供的模块中,没有对负载均衡后端节点的健康检查模块,但可以使用第三方模块。
nginx_upstream_check_module
来检测后端服务的健康状态。 - 第三方模块项目地址:https://github.com/yaoweibin/nginx_upstream_check_module
- 安装依赖包
[root@lb01 ~]# yum install -y gcc glibc gcc-c++ pcre-devel openssl-devel patch
- 下载nginx源码包以及nginx_upstream_check模块第三方模块
[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
- 解压nginx源码包以及第三方模块
[root@lb01 ~]# tar xf nginx-1.20.1.tar.gz
[root@lb01 ~]# unzip master.zip
- 进入nginx目录,打补丁(nginx的版本是1.14补丁就选择1.14的,p1代表在nginx目录,p0是不在nginx目录)
[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负载均衡会话保持
- 在使用负载均衡的时候会遇到会话保持的问题,可通过如下方式进行解决。
- 使用nginx的ip_hash,根据客户端的IP,将请求分配到对应的IP上
- 基于服务端的session会话共享(NFS,MySQL,memcache,redis,file)
- 在解决负载均衡会话问题,我们需要了解session和cookie的区别。
- 浏览器端存的是cookie每次浏览器发请求到服务端时,报文头是会自动添加cookie信息的。
- 服务端会查询用户的cookie作为key去存储里找对应的value(session)
- 同一域名下的网站的cookie都是一样的,所以无论几台服务器,无论请求分配到哪一台服务器上同一用户的cookie是不变的。也就是说cookie对应的session也是唯一的。所以,这里只要保证多台业务服务器访问同一个共享存储服务器(NFS,MySQL,memcache,redis,file)就行了。
6.1、会话保持配置
测试网站选择我们之前搭建过的phpmyadmin,所以我们先再web01和web02上部署phpmyadmin
- 配置php环境(web01和web02都要)
# 导入英格提供的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
- 配置Nginx(web01和web02都要)
[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
- 安装phpmyadmin (web01和web02都要)
[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
- 配置phpmyadmin连接远程的数据库,这个数据库可以用代理服务器安装后做测试(web01和web02都要)
[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';
- 配置权限(web01和web02都要)
[root@web01 ~]# chown -R nginx.nginx /var/lib/php/
- 在lb01上部署mariadb数据库,提供phpmyadmin访问
# 安装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信息
- 查看服务器上的session
[root@web01 ~]# ll /var/lib/php/session/
总用量 4
-rw------- 1 www www 2625 7月 11 20:08 sess_aa3bdfab93197f49bbfddb15cb41a595
- 将web01上配置好的phpmyadmin以及nginx的配置文件推送到web02主机上
[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/
- 在web02上重启Nginx服务
[root@web02 ~]# systemctl restart nginx
- 在web02上配置权限
[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
- 使用负载均衡的轮询功能之后,会发现,如果将session保存在本地文件的话,永远都登录不上去。
6.3、使用redis解决会话登录问题
- 安装redis内存数据库
[root@lb01 ~]# yum -y install redis
- 配置redis监听在0.0.0.0网段上
[root@lb01 ~]# sed -i '/^bind/c bind 127.0.0.1 0.0.0.0' /etc/redis.conf
- 启动redis
[root@lb01 ~]# systemctl start redis
[root@lb01 ~]# systemctl enable redis
- php配置session连接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
- 重启php-fpm
[root@web01 ~]# systemctl restart php-fpm
- 将web01上配置好的文件推送到web02
[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
- 在web02上重启php-fpm
[root@web02 ~]# systemctl restart php-fpm
- redis查看数据
[root@lb01 ~]# redis-cli
127.0.0.1:6379> keys *
1) "PHPREDIS_SESSION:7e772fe37b1488069e3c54e419df7eda"
7、Nginx四层负载均衡概述
四层负载均衡是基于传输层协议包来封装的(如:TCP/IP),那我们前面使用到的七层是指的应用层,他的组装在四层的基础之上,无论四层还是七层都是指的OSI网络模型。
四层+七层来做负载均衡,四层可以保证七层的负载均衡的高可用性;如:nginx就无法保证自己的服务高可用,需要依赖LVS或者keepalive。
如:tcp协议的负载均衡,有些请求是TCP协议的(mysql、ssh),或者说这些请求只需要使用四层进行端口的转发就可以了,所以使用四层负载均衡。
7.1、四层+七层构建大规模集群架构使用场
7.2、四层负载均衡总结
- 四层负载均衡仅能转发TCP/IP协议、UDP协议、通常用来转发端口,如:tcp/22、udp/53;
- 四层负载均衡可以用来解决七层负载均衡端口限制问题;(七层负载均衡最大使用65535个端口号)
- 四层负载均衡可以解决七层负载均衡高可用问题;(多台后端七层负载均衡能同事的使用)
- 四层的转发效率比七层的高得多,但仅支持tcp/ip协议,不支持http和https协议;
- 通常大并发场景通常会选择使用在七层负载前面增加四层负载均衡。
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;
}
}
- 四层负载均衡开启日志
- 四层负载均衡是没有access的日志的,因为在nginx.conf的配置中,access的日志格式是配置在http下的,而四层负载均衡配置实在http以外的
- 如果需要日志则需要配置在stream下面
[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
- 浏览器访问域名或者IP,查看日志
[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四层负载均衡端口转发
- 使用nginx四层负载均衡实现tcp的转发
请求负载均衡 5555 ---> 192.168.88.20:22;
请求负载均衡 6666 ---> 192.168.88.30:3306;
- 配置nginx四层负载均衡实现tcp的转发
[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原理
- VRRP协议是一种容错的主备模式的协议,保证当主机的下一跳路由出现故障时,由另一台路由器来代替出现故障的路由器进行工作,通过VRRP可以在网络发生故障时透明的进行设备切换而不影响主机之间的数据通信。
- 虚拟路由器:VRRP组中所有的路由器,拥有虚拟的IP+MAC(00-00-5e-00-01-VRID)地址
- 主路由器:虚拟路由器内部通常只有一台物理路由器对外提供服务,主路由器是由选举算法产生,对外提供各种网络功能。
- 备份路由器:VRRP组中除主路由器之外的所有路由器,不对外提供任何服务,只接受主路由的通告,当主路由器挂掉之后,重新进行选举算法接替master路由器。
- 选举机制
- 优先级
- 抢占模式下,一旦有优先级高的路由器加入,即成为Master
- 非抢占模式下,只要Master不挂掉,优先级高的路由器只能等待
- 三种状态
- Initialize状态:系统启动后进入initialize状态
- Master状态
- Backup状态
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抢占式与非抢占式
- 由于节点1的优先级高于节点2,所以VIP在节点1上面
[root@lb01 ~]# ip addr |grep 192.168.88.100
inet 192.168.88.100/32 scope global ens33
- 关闭节点1的keepalived
[root@lb01 ~]# systemctl stop keepalived
- 节点2联系不上节点1,主动接管VIP
[root@lb02 ~]# ip addr |grep 192.168.88.100
inet 192.168.88.100/32 scope global ens33
- 此时重新启动Master上的keepalived,会发现VIP被强行抢占
[root@lb01 ~]# systemctl start keepalived
[root@lb01 ~]# ip addr |grep 192.168.88.100
inet 192.168.88.100/32 scope global ens33
- 配置非抢占式
- 两个节点的state都必须配置为BACKUP
- 两个节点都必须加上配置 nopreempt
- 其中一个节点的优先级必须要高于另外一个节点的优先级。
- 两台服务器都角色状态启用nopreempt后,必须修改角色状态统一为BACKUP,唯一的区分就是优先级。
Master配置
vrrp_instance VI_1 {
state BACKUP
priority 150
nopreempt
}
Backup配置
vrrp_instance VI_1 {
state BACKUP
priority 100
nopreempt
}
- 通过windows的arp去验证,是否会切换MAC地址
# 当前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 动态
- 关闭lb01的keepalive
[root@lb01 ~]# systemctl stop keepalived
- lb02接管vIP
[root@lb02 ~]# 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 动态
11、高可用keepalived故障脑裂
由于某些原因,导致两台keepalived高可用服务器在指定时间内,无法检测到对方的心跳,各自取得资源及服务的所有权,而此时的两台高可用服务器又都还活着。
11.1、脑裂故障原因
- 服务器网线松动等网络故障
- 服务器硬件故障发生损坏现象而崩溃
- 主备都开启firewalld防火墙
11.2、脑裂故障现象
- 正常情况下backup以监听为主,所以抓包会看到只有master在发送vrrp的数据包
- 打开设备的防火墙,查看抓包情况,可以看到两台设备认为自己是master
[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、解决脑裂故障方案
- 如果发生脑裂,则随机kill掉一台即可
- 在backup上编写检测脚本, 测试如果能ping通master并且backup节点还有vIP的话则认为产生了脑裂
[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、集群和分布式
系统性能扩展方式:
- Scale UP:垂直扩展,向上扩展,增强,性能更强的计算机运行同样的服务,成本高。
- Scale Out:水平扩展,向外扩展,增加设备,并行地运行多个服务调度分配问题,Cluster
垂直扩展不再提及:
- 随着计算机性能的增长,其价格会成倍增长
- 单台计算机的性能是有上限的,不可能无限制地垂直扩展
- 多核CPU意味着即使是单台计算机也可以并行的。
13.1、集群 Cluster
Cluster:集群,为解决某个特定问题将多台计算机组合起来形成的单个系统
Cluster分为三种类型:
- LB:Load Balancing,负载均衡。调度负载,按照算法调度。
- HA:High Availiablity,高可用,避免SPOF(single Point Of failure)(单点失败)
- MTBF:Mean Time Between Failure 平均无故障时间
- MTTR:Mean Time To Restoration( repair)平均恢复前时间
- A=MTBF/(MTBF+MTTR) (0,1):99%,99.5%,99.9%,99.99%,99.999%
- HPC:High-performance computing,高性能 www.top500.org
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、按实现方式划分
- 硬件(大公司)
- F5 Big-IP
- Citrix Netscaler
- A10
- 软件(小公司)
- lvs:Linux Virtual Server,阿里四层SLB (Server Load Balance)使用下四层功能:物理层 数据链路层
- nginx:支持七层调度,阿里七层SLB使用Tengine
- haproxy:支持七层调度
- ats:Apache Traffic Server,yahoo捐助给apache
- perlbal:Perl 编写
- pound
13.6.2、基于工作的协议层次划分
- 传输层(通用):DNAT和DPORT
- LVS:Linux Virtual Server
- nginx:stream
- haproxy:mode tcp
- 应用层(专用):针对特定协议,常称为 proxy server
- http:nginx, httpd, haproxy(mode http), …
- fastcgi:nginx, httpd, …
- mysql:mysql-proxy, …
13.6.3、负载均衡的会话保持
- session sticky:同一用户调度固定服务器
- Source IP:LVS sh算法(对某一特定服务而言)
- Cookie
- session replication:每台服务器拥有全部session
- session multicast cluster (内存消耗大)
- session server:专门的session服务器
- Memcached,Redis (只放session,共享)也存在单点失败,即也要做集群哨兵机制
13.7、HA 高可用集群实现
keepalived:vrrp协议 Ais:应用接口规范 heartbeat cman+rgmanager(RHCS) coresync_pacemaker
14、Linux Virtual Server简介
14.1、LVS介绍
- LVS:Linux Virtual Server,负载调度器,内核集成,章文嵩(花名 正明), 阿里的四层SLB(Server Load Balance)是基于LVS+keepalived实现。
- LVS 官网:http://www.linuxvirtualserver.org/
- LVS 相关术语
- VS: Virtual Server,负责调度
- RS: Real Server,负责真正提供服务
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集群体系架构
14.4、LVS 功能及组织架构
负载均衡的应用场景为高访问量的业务,提高应用程序的可用性和可靠性。
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模式
lvs-nat:本质是多目标IP的DNAT,通过将请求报文中的目标地址和目标端口修改为某挑出的RS的RIP和PORT实现转发
- RIP和DIP应在同一个IP网络,且应使用私网地址;RS的网关要指向DIP
- 请求报文和响应报文都必须经由Director转发,Director易于成为系统瓶颈
- 支持端口映射,可修改请求报文的目标PORT
- VS必须是Linux系统,RS可以是任意OS系统
15.1.2、LVS的DR模式
LVS-DR:Direct Routing,直接路由,LVS默认模式,应用最广泛,通过为请求报文重新封装一个MAC首部 进行转发,源MAC是DIP所在的接口的MAC,目标MAC是某挑选出的RS的RIP所在接口的MAC地址;源 IP/PORT,以及目标IP/PORT均保持不变
DR模式的特点
- Director和各RS都配置有VIP
- 确保前端路由器将目标IP为VIP的请求报文发往Director三种方法
在前端网关做静态绑定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
- RS的RIP可以使用私网地址,也可以是公网地址;RIP与DIP在同一IP网络;RIP的网关不能指向DIP,以确保响应报文不会经由Director
- RS和Director要在同一个物理网络
- 请求报文要经由Director,但响应报文不经由Director,而由RS直接发往Client
- 不支持端口映射(端口不能修改)
- RS可使用大多数OS系统
15.1.3、LVS的TUN模式
转发方式:不修改请求报文的IP首部(源IP为CIP,目标IP为VIP),而在原IP报文之外再封装一个IP首部(源IP是DIP,目标IP是RIP),将报文发往挑选出的目标RS;RS直接响应给客户端(源IP是VIP,目标IP是CIP)
TUN模式特点
- DIP, VIP, RIP可以是公网地址
- RS的网关一般不能指向DIP
- 请求报文要经由Director,但响应不经由Director
- 不支持端口映射
- RS的OS须支持隧道功能
15.1.4、LVS的FULLNAT模式
- 通过同时修改请求报文的源IP地址和目标IP地址进行转发
- CIP --> DIP
- VIP --> RIP
- fullnat模式特点:
- VIP是公网地址,RIP和DIP是私网地址,且通常不在同一IP网络;因此,RIP的网关一般不会指向DIP
- RS收到的请求报文源地址是DIP,因此,只需响应给DIP;但Director还要将其发往Client
- 请求和响应报文都经由Director
- 支持端口映射
- 注意:此类型kernel默认不支持
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 | 性能最好 |
缺点 | 性能瓶颈 | 支持隧道 | 不支持跨网段 |
- lvs-nat与lvs-fullnat:
- 请求和响应报文都经由Director
- lvs-nat:RIP的网关要指向DIP
- lvs-fullnat:RIP和DIP未必在同一IP网络,但要能通信
- lvs-dr与lvs-tun:
- 请求报文要经由Director,但响应报文由RS直接发往Client
- lvs-dr:通过封装新的MAC首部实现,通过MAC网络转发
- lvs-tun:通过在原IP报文外封装新IP头实现转发,支持远距离通信
15.2、LVS调试算法
ipvs scheduler:根据其调度时是否考虑各RS当前的负载状态 分为两种:静态方法和动态方法
15.2.1、静态方法
- 仅根据算法本身进行调度
- RR:roundrobin,轮询 不考虑机器的性能好坏,轮询调度
- WRR:Weighted RR,加权轮询 权重越高,优先级高,调度更多资源给它 优先级高
- SH:Source Hashing,实现session sticky,源IP地址hash;将来自于同一个IP地址的请求始终发往第一次挑中的RS,从而实现会话绑定
- DH:Destination Hashing;目标地址哈希,第一次轮询调度至RS,后续将发往同一个目标地址的请求始终转发至第一次挑中的RS,典型使用场景是正向代理缓存场景中的负载均衡,如:宽带运营商
15.2.2、动态方法
主要根据每RS当前的负载状态及调度算法进行调度Overhead=value 较小的RS将被调度
- LC:least connections 适用于长连接应用(最少连接 )
Overhead=activeconns *256+inactiveconns
- WLC:Weighted LC,默认调度方法(加权最少连接 )
Overhead=(activeconns * 256+inactiveconns)/weight
- SED:Shortest Expection Delay,初始连接高权重优先(最少期望延迟)
Overhead=(activeconns+1)*256/weight
- NQ:Never Queue,第一轮均匀分配,后续SED(从不排队调度方法)
- LBLC:Locality-Based LC,动态的DH算法,使用场景:根据负载状态实现正向代理
- LBLCR:LBLC with Replication,带复制功能的LBLC,解决LBLC负载不均衡问题,从负载重的复制到负载轻的RS
15.2.3、内核版本 4.15版本后新增调度算法
- FO(Weighted Fail Over)调度算法,在此FO算法中,遍历虚拟服务所关联的真实服务器链表,找到还未过载(未设置IP_VS_DEST_F_OVERLOAD标志)的且权重最高的真实服务器,进行调度。方便调式上下线。
- OVF(Overflow-connection)调度算法,基于真实服务器的活动连接数量和权重值实现。将新连接调度到权重值最高的真实服务器,直到其活动连接数量超过权重值位置,之后调度到下一个权重值最高的真实服务器,在此OVF算法中,遍历虚拟服务相关联的真实服务器链表,找到权重值最高的可用真实服务器。一个可用的真实服务器需要同时满足以下条件:
- 未过载(未设置IP_VS_DEST_F_OVERLOAD标志)
- 真实服务器当前的活动连接数量小于其权重值
- 其权重值不为零
15.3、LVS 相关软件
15.3.1、程序包
- Unit File: ipvsadm.service
- 主程序:/usr/sbin/ipvsadm
- 规则保存工具:/usr/sbin/ipvsadm-save
- 规则重载工具:/usr/sbin/ipvsadm-restore
- 配置文件:/etc/sysconfig/ipvsadm-config
[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核心功能:
- 集群服务管理:增、删、改
- 集群服务的RS管理:增、删、改
- 查看
#管理集群服务
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模式案例
实验环境说明:
LVS:两张网卡,一张nat网卡做为vIP与客户端建立连接,一张仅主机网卡用于对内部服务器的连接
RS1和RS2:两个RS的网卡都使用仅主机的网卡,并且网关指向LVS的仅主机网卡
- 开启LVS流量转发功能,并且配置NAT
[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
- 先搭建web服务器
[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
- 配置LVS
[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
- 验证结果
- 抓包可以观察到客户端都是与LVS进行通信,LVS对流量进行NAT
16.2、LVS-DR模式案例
- 做这个实验之前,需要清空LVS之前配置的NAT规则
# 由于我们后面做实验要用到ifconfig命令,该命令需要安装net-tools工具。所以我们趁现在有网,可以先安装一下,三台机器都需要安装
[root@lvs ~]# yum install -y net-tools
[root@lvs ~]# iptables -t nat -F
- 修改RS1和RS2的网关地址
[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
- 在RS上修改内核参数以限制arp通告及应答级别
[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
- 给RS1和RS2设置VIP 192.168.153.100
[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上也需要执行
- 配置LVS服务器
[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
- 验证,当多次访问192.168.153.100的时候可以得到不同的主机回复
17、HAProxy介绍
HAProxy是法国开发者威利塔罗(Willy Tarreau)在2000年使用C语言开发的一个开源软件,是一款具备高并发(一万以上)、高性能的TCP和HTTP负载均衡器,支持基于cookie的持久性,自动故障切换,支持正则表达式及web状态统计,目前最新TLS版本为2.0
17.1、HAProxy功能
17.2、支持功能
- TCP 和 HTTP反向代理
- SSL/TSL服务器
- 可以针对HTTP请求添加cookie,进行路由后端服务器
- 可平衡负载至后端服务器,并支持持久连接
- 支持所有主服务器故障切换至备用服务器
- 支持专用端口实现监控服务
- 支持停止接受新连接请求,而不影响现有连接
- 可以在双向添加,修改或删除HTTP报文首部
- 响应报文压缩
- 支持基于pattern实现连接请求的访问控制
- 通过特定的URI为授权用户提供详细的状态信息
- 支持http反向代理
- 支持动态程序的反向代理
- 支持基于数据库的反向代理
17.3、不具备的功能
- 正向代理--squid,nginx
- 缓存代理--varnish
- web服务--nginx、tengine、apache、php、tomcat
- UDP--目前不支持UDP协议
- 单机性能--相比LVS性能较差
18、编译安装HAProxy
- 编译安装HAProxy 2.0 LTS版本,更多源码包下载地址:http://www.haproxy.org/download/
- centos7自带的源中yum安装的版本是较老的1.5的版本
18.1、解决lua环境
HAProxy 支持基于lua实现功能扩展,lua是一种小巧的脚本语言,于1993年由巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组开发,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
Lua 官网:www.lua.org
- Lua 应用场景
- 游戏开发
- 独立应用脚本
- Web 应用脚本
- 扩展和数据库插件,如MySQL Proxy
- 安全系统,如入侵检测系统
18.1.1、Centos 基础环境
参考链接:http://www.lua.org/start.html
由于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
- HAProxy 2.0以上版本编译参数
[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
- 参考INSTALL文件进行编译安装
[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的状态页面
- 浏览器访问
19、HAProxy基础配置详解
官方文档:http://cbonte.github.io/haproxy-dconv/2.1/configuration.html
HAProxy 的配置文件haproxy.cfg由两大部分组成,分别是global和proxies部分
- global:全局配置段
- 进程及安全配置相关的参数
- 性能调整相关参数
- Debug参数
- proxies:代理配置段
- defaults:为frontend, backend, listen提供默认配置
- frontend:前端,相当于nginx中的server {}
- backend:后端,相当于nginx中的upstream {}
- listen:同时拥有前端和后端配置
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日志
- 重启syslog服务并访问app页面,然后验证是否生成日志
[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、backend和listen生效,可以多个name也可以没有name
frontend <name>
- 前端servername,类似于Nginx的一个虚拟主机 server和LVS服务集群。
backend <name>
- 后端服务器组,等于nginx的upstream和LVS中的RS服务器
listen <name>
- 将frontend和backend合并在一起配置,相对于frontend和backend配置更简洁,生产常用
注意:name字段只能使用大小写字母,数字,‘-’(dash),’_‘(underscore),’.’ (dot)和 ‘:'(colon),并且严格区分大小写
20.1、Proxies配置-defaults
- 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
- 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
- 定义一组后端服务器,backend服务器将被frontend进行调用。
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配置实例
- node1和node2搭建一个网站
[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
- haproxy配置文件
[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
- 验证结果
- 因为检查node1的3306接口并没有启动,所以node1始终是down的
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/
- 创建子配置文件,注意:必须为cfg后缀
[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
- 添加子配置目录到unit文件中
[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>
- uri 取模法配置示例
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
- uri 一致性hash配置示例
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"
- url_param取模法配置示例
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
- url_param一致性hash配置示例
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计算,然后由服务器总权重取模以后派发至某挑出的服务器,如无有效的值,则会使用默认的轮询调度。
- hdr取模法配置示例
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
- 一致性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)
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.5、rdp-cookie
rdp-cookie对远windows远程桌面的负载,使用cookie保持会话,默认是静态,也可以通过hash-type指定map-based和consistent,来定义使用取模法还是一致性hash。
- rdp-cookie取模法配置示例
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
- rdp-cookie一致性hash配置示例
[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)
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服务器的健康性检查
- 通过curl 命令对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
评论区