面试题总汇

编程知识 更新时间:2023-04-16 01:25:13

一. FTP/TFTP/NFS

1.FTP的传输模式:ASCII传输模式和二进制传输模式。
ASCII传输模式: 假定用户正在拷贝的文件包含的简单ASCII码文本,如果在远程机器上运行的不是UNIX,当文件传输时ftp通常会自
动地调整文件的内容以便于把文件解释成另外那台计算机存储文本文件的格式。但是常常有这样的情况,用户正在传输的文件包含的不是
文本文件,它们可能是程序,数据库,字处理文件或者压缩文件(尽管字处理文件包含的大部分是文本,其中也包含有指示页尺寸,字库
等信息的非打印符)。在拷贝任何非文本文件之前,用binary 命令告诉ftp逐字拷贝,不要对这些文件进行处理,这也是下面要讲的二进
制传输。
二进制传输模式: 在二进制传输中,保存文件的位序,以便原始和拷贝的是逐位一一对应的。即使目的地机器上包含位序列的文件是
没意义的。 如果两台通讯的机器为同一类型,则可优先使用二进制拷贝。
2.FTP的工作方式: Standard (也就是 PORT方式,主动方式)和 Passive (也就是PASV,被动方式)
主动方式: Port模式FTP 客户端首先和FTP服务器的TCP 21端口建立连接,通过这个通道发送命令,客户端需要接收数据的时候在这
个通道上发送PORT命令。 PORT命令包含了客户端用什么端口接收数据。在传送数据的时候,服务器端通过自己的TCP 20端口连接至客
户端的指定端口发送数据。 FTP server必须和客户端建立一个新的连接用来传送数据。
被动方式: Passive模式在建立控制通道的时候和Standard模式类似,但建立连接后发送的不是Port命令,而是Pasv命令。FTP服务
器收到Pasv命令后,随机打开一个高端端口(端口号大于1024)并且通知客户端在这个端口上传送数据的请求,客户端连接FTP服务器此
端口,然后FTP服务器将通过这个端口进行数据的传送,这个时候FTP server不再需要建立一个新的和客户端之间的连接
请注意: PORT模式建立数据传输通道是由服务器端发起的,服务器使用20端口连接客户端的某一个大于1024的端口;在PASV模式
中,数据传输的通道的建立是由FTP客户端发起的,他使用一个大于1024的端口连接服务器的1024以上的某一个端口。 在FTP客户连接
服务器的整个过程中,控制信道是一直保持连接的,而数据传输通道是临时建立的。
3.TFTP工作原理
建立连接:默认情况下,作为 TFTP 服务器的主机 A 会监听 69 端口,当作为客户端的主机 B 想要下载或上传文件时,会向主机 A 的
69 端口发送包含读文件(下载)请求或写文件(上传)请求的数据包。主机 A 收到读写请求后,会打开另外一个随机的端口,通过这个
端口向主机 B 发送确认包、数据包或者错误包。
下载:客户端向服务器的 69 端口(通常情况下)发送一个读请求,服务器收到这个读请求以后,会打开另外一个随机的端口(假设
端口号是 59509),然后在它默认的路径下寻找这个文件,找到这个文件以后,每次读入文件的 512 个字节,通过端口 59509 将这 512
个字节放入数据包中发送给客户端,数据包中还包含了操作码和数据块的编号,块编号从 1 开始计数;客户端收到数据包以后,会向服务
器的 59509 端口发送一个确认包,里面包含了它收到的数据包的块编号;服务器收到确认包以后,继续发送文件的下一个 512 个字节。
如此循环往复,直到文件的末尾,最后一个数据包的数据块的大小会小于 512 个字节,这时服务器就认为传输已经结束,等他接收到这最
后一个数据包的确认包之后就会主动关闭连接。而客户端收到这个小于 512 个字节的数据包后也认为传输已经结束,发送完确认包之后也
会关闭连接。也许会有一种极端情况,就是文件的大小正好是 512 字节的倍数,这样的话,最后一个数据包的大小也是 512 个字节,这
时服务器发送完包含文件数据的数据包以后,还会额外发送一个包含 0 字节的数据包,作为最后一个数据包,这样就可以保证客户端收到
的最后一个数据包的大小总是小于 512 个字节的。也就是说,对于客户端而言,只要它收到的数据包的大小小于 512 个字节,它就认为
传输已经结束,它就会关闭连接。
上传:客户端向服务器的 69 端口(通常情况下)发送一个写请求,服务器收到这个写请求以后,会打开另外一个随机的端口(假设
端口号是 59509),向客户端发送一个确认包,其中块编号是 0,以此来告诉客户端自己已经准备好接收文件,并且告诉客户端自己接收
文件的端口号。然后客户端就开始向服务器的 59509 端口发送数据包,服务器收到数据包后向客户端发送确认包,直到整个文件发送完
毕。这个过程和下载是一样的,只不过双方的角色互换了,客户端成了发数据的一方,而服务器是接收数据的一方。> 4.NFS工作原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fxkm2EOH-1604371435750)(png/image-20200406105340809.png)]

二. rsync

一般企业在使用 rsync 时会配合 inotify 使用,使用 inotify 监听文件修改情况,一旦文件某些 属性改变,就通知 rsync 进行备份

  • inotify + rsync 方式实现数据同步
  • sersync :金山公司周洋在 inotify 软件基础上进行开发的,功能更加强大
  • 工作原理:
    要利用监控服务(inotify),监控同步数据服务器目录中信息的变化
    发现目录中数据产生变化,就利用 rsync 服务推送到备份服务器上
  • inotify
    异步的文件系统事件监控机制,利用事件驱动机制,而无须通过诸如 cron 等的轮询机制来获取事件,
    linux 内核从 2.6.13 起支持 inotify,通过 inotify 可以监控文件系统中添加、删除,修改、移动等各种事件
max_queued_events:inotify事件队列最大长度,如值太小会出现 Event Queue Overflow 误,默认值:16384
max_user_instances:每个用户创建inotify实例最大值,默认值:128 max_user_watches:可以监视的文件数量(单进程),默认值:
8192
[root@centos8 ~]#vim /etc/sysctl.conf
fs.inotify.max_queued_events=66666
fs.inotify.max_user_watches=100000
[root@centos8 ~]#sysctl -p
fs.inotify.max_queued_events = 66666
fs.inotify.max_user_watches = 100000
[root@centos8 ~]#cat /proc/sys/fs/inotify/*
66666
128
100000

inotify-tools 包:

  • inotifywait: 在被监控的文件或目录上等待特定文件系统事件(open ,close,delete 等) 发生,常用于实时同步的目录监控- inotifywatch:收集被监控的文件系统使用的统计数据,指文件系统事件发生的次数统计
    例:使用 inotifywait
#监控一次性事件
inotifywait /data
#持续前台监控
inotifywait -mrq /data
#持续后台监控,并记录日志
inotifywait -o /root/inotify.log -drq /data --timefmt "%Y-%m-%d %H:%M" --format
"%T %w%f event: %e"
#持续前台监控特定事件
inotifywait -mrq /data --timefmt "%F %H:%M" --format "%T %w%f event: %;e" -e
create,delete,moved_to,close_write,attrib

rsync 软件包
rsync,rsync-daemon(CentOS 8)
服务文件:/usr/lib/systemd/system/rsyncd.service
配置文件:/etc/rsyncd.conf 端口:873/tcp
rsync命令

#Local:
rsync [OPTION...] SRC... [DEST]
#Access via remote shell:
Pull:
rsync [OPTION...] [USER@]HOST:SRC... [DEST]
Push:
rsync [OPTION...] SRC... [USER@]HOST:DEST
#Access via rsync daemon:
Pull:
rsync [OPTION...] [USER@]HOST::SRC... [DEST]
rsync [OPTION...] rsync://[USER@]HOST[:PORT]/SRC... [DEST]
Push:
rsync [OPTION...] SRC... [USER@]HOST::DEST
rsync [OPTION...] SRC... rsync://[USER@]HOST[:PORT]/DEST

rsync 有三种工作方式:

  1. 本地文件系统上实现同步。命令行语法格式为上述"Local"段的格式。
  2. 本地主机使用远程 shell 和远程主机通信。命令行语法格式为上述"Access via remote shell"段的格式。
  3. 本地主机通过网络套接字连接远程主机上的 rsync daemon。命令行语法格式为上述 "Access viarsync daemon"段的格式。# 三. 防火墙

防火墙概念

  • 在计算机领域,防火墙(FireWall)就是基于预先定义的安全规则来监视和控制来往的 网络流量的网络安全系统。防火墙的核心是隔离,其
    将受信任的内部网络和不受信任的 外部网络隔离开。内部网络一般是公司的内部局域网,外部网络一般是 Internet。
  • 一般防火墙工作在网络或主机边缘,对进出网络或主机的数据包基于一定的规则检查, 并在匹配某规则时由规则定义的行为进行处理的
    一组功能的组件,基本上的实现都是默认 情况下关闭所有的通过型访问,只开放允许访问的策略。

防火墙分类

  • 按照防火墙的保护范围,防火墙通常被分为:
    网络防火墙: 网络防火墙在两个或更多的网络间监控和过滤流量,运行在网络设备上。 网络防火墙保护的是防火墙某一侧的网络(一
    般是局域网络)。
    主机防火墙: 主机防火墙运行在一般的电脑主机,并控制进出这些电脑的网络流量, 主机防火墙保护的范围是当前主机。
  • 从实现方式上看,防火墙被分为:
    硬件防火墙: 在专用硬件级别实现部分功能的防火墙;另一个部分功能基于软件实现, 如:华为,天融信 Checkpoint,NetScreen

    软件防火墙: 运行于通用硬件平台上的防火墙应用软件,ISA --> Forefront TMG
  • 从工作交互的网络协议层及划分:
    网络层防火墙: 只可以和 OSI 模型下四层的协议交互
    应用层防火墙: 运行应用层防火墙的设备可以叫代理服务器或代理网关,可以与 OSI 的七层协议交互。

Netfilter 中的 hook 函数

  • Netfilter 在内核中选取五个位置放了五个 hook(“勾子”) function(INPUT、OUTPUT、 FORWARD、PREROUTING、
    POSTROUTING),而这五个 hook function 向用户开放, 用户可以通过一个命令工具(iptables)向其写入规则,规则由信息过滤表
    (table)组成, 信息过滤表包含控制 IP 包处理的规则集(ruleset),规则被分组放在(chain)上。
  • 三种数据包流动方向
    流入本机:PREROUTING --> INPUT–>用户空间进程
    流出本机:用户空间进程 -->OUTPUT–> POSTROUTING
    转发:PREROUTING --> FORWARD --> POSTROUTING
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IwJFaoKh-1604371435755)(png/2019123117471156.png)]
    数据包大致传输过程 :
  1. 当一个网络数据包进入网卡时,数据包首先进入 PREROUTING 链,内核根据数据包目的 IP 判断是否需 要转送出去
  2. 如果数据包就是进入本机的,数据包就会沿着图向下移动,到达 INPUT 链。数据包到达 INPUT 链 后,任何进程都会收到它。本机上
    运行的程序可以发送数据包,这些数据包经过 OUTPUT 链,然后 到达 POSTROUTING 链输出3. 如果数据包是要转发出去的,且内核允许转发,数据包就会向右移动,经过 FORWARD 链,然后到 达 POSTROUTING 链输出
iptables -N # 自定义一条新的规则链
iptables -E # 重命名自定义链;引用计数不为 0 的自定义链不能够被重命名,也不能被删除
iptables -X # 删除自定义的空的规则链
iptables -P # 设置默认策略;对 filter 表中的链而言,其默认策略有:
# ACCEPT:接受 DROP:丢弃
iptables -vnL --line-numbers
iptables -A
iptables -I INPUT 2 ...
iptables -D 2
iptables -R
iptables -F # 清空指定规则
# (1) 可以指明规则序号 (2) 可以指明规则本身
iptables -Z # 清零计数
# (iptables 的每条规则都有两个计数器 (1) 匹配到的报文的个数 (2) 匹配到的所有报文的大小之和)

iptables 扩展匹配条件

multiport 扩展 以离散方式定义多端口匹配

iptables -A INPUT -s 172.16.0.0/16 -d 172.16.100.10 -p tcp -m multiport --dports 20:22,80 -j ACCEPT

iprange 扩展 指明连续的(但一般不是整个网络)ip 地址范围 源 IP 地址范围 [!] --src-range from[-to] 目标 IP 地址范围 [!] -- dst-range from[-to]

iptables -A INPUT -d 172.16.1.100 -p tcp --dport 80 -m iprange --src-range 172.16.1.5-172.16.1.10 -j DROP

mac扩展 mac 模块可以指明源 MAC 地址

iptables -A INPUT -s 172.16.0.100 -m mac --mac-source 00:50:56:12:34:56 -j ACCEPT
iptables -A INPUT -s 172.16.0.100 -j REJECT

string 扩展 string 扩展用于对报文中的应用层数据做字符串模式匹配检测

iptables -A OUTPUT -p tcp --sport 80 -m string --algo bm --from 42 --string "google" -j REJECT

time 扩展 根据将报文到达的时间与指定的时间范围进行匹配

iptables -A INPUT -s 172.16.0.0/16 -d 172.16.100.10 -p tcp --dport 80 -m time --timestart 14:30 --timestop 18:30 --weekdays
Sat,Sun --kerneltz -j DROP```
**connlimit 扩展** 根据每客户端 IP 做并发连接数数量匹配可防止 Dos(Denial of Service,拒绝服务)攻击
```bash
iptables -A INPUT -d 172.16.100.10 -p tcp --dport 22 -m connlimit --connlimit-above 2 -j REJECT
# --connlimit-upto # :连接的数量小于等于#时匹配
# --connlimit-above # :连接的数量大于#时匹配

limit 扩展 基于收发报文的速率做匹 令牌桶过滤器

iptables -I INPUT -d 172.16.100.10 -p icmp --icmp-type 8 -m limit --limit 10/minute --limit-burst 5 -j ACCEPT
iptables -I INPUT 2 -p icmp -j REJECT

state 扩展 state 扩展模块,可以根据"连接追踪机制"去检查连接的状态,较耗资源, 会消耗内存,使用 conntrack 机制:追踪本机
上的请求和响应之间的关系

  • 状态类型
    NEW:新发出请求;连接追踪信息库中不存在此连接的相关信息条目,因此,将其 识别为第一次发出的请求
    ESTABLISHED:NEW 状态之后,连接追踪信息库中为其建立的条目失效之前期间内 所进行的通信状态
    RELATED:新发起的但与已有连接相关联的连接,如:ftp 协议中的数据连接与命令 连接之间的关系
    INVALID:无效的连接,如 flag 标记不正确
    UNTRACKED:未进行追踪的连接,如 raw 表中关闭追踪
    已经追踪到的并记录下来的连接信息库 /proc/net/nf_conntrack
    调整连接追踪功能所能够容纳的最大连接数量 /proc/sys/net/netfilter/nf_conntrack_max
    查看连接跟踪有多少条目 /proc/sys/net/netfilter/nf_conntrack_count
    不同的协议的连接追踪时长 /proc/sys/net/netfilter/nf_conntrack_generic_timeout
    state 模块使用格式 :
iptables -A INPUT -d 172.16.1.10 -p tcp -m multiport --dports 22,80 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -s 172.16.1.10 -p tcp -m multiport --sports 22,80 -m state --state ESTABLISHED -j ACCEPT

开放被动模式的 ftp 服务 :

  1. 装载 ftp 连接追踪的专用模块: 跟踪模块路径:/lib/modules/kernelversion/kernel/net/netfilter1
IPTABLES_MODULES=“nf_conntrack_ftp"
modproble nf_conntrack_ftp
  1. 放行请求报文: 命令连接:NEW, ESTABLISHED 数据连接:RELATED, ESTABLISHED
iptables –I INPUT -d LocalIP -p tcp -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -d LocalIP -p tcp --dport 21 -m state --state NEW -j ACCEPT
  1. 放行响应报文
iptables -I OUTPUT -s LocalIP -p tcp -m state --state ESTABLISHED -j ACCEPT

具体操作:

yum install vsftpd
systemctl start vsftpd
modprobe nf_conntrack_ftp
iptables -F
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p tcp --dport 21 -m state --state NEW -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED -j ACCEPT
iptables -P INPUT DROP
iptables -P OUTPUT DROP
iptables -vnL

iptables 规则保存

使用 iptables 命令定义的规则,手动删除之前,其生效期限为 kernel 存活期限
持久保存规则

  • CentOS 7,8 iptables-save > /PATH/TO/SOME_RULES_FILE
  • CentOS 6
# 将规则覆盖保存至/etc/sysconfig/iptables文件中
service iptables save

加载规则

  • CentOS 7 重新载入预存规则文件中规则: iptables-restore < /PATH/FROM/SOME_RULES_FILE -n, --noflush:不清除原有规则 -
    t, --test:仅分析生成规则集,但不提交- CentOS 6
# 会自动从/etc/sysconfig/iptables 重新载入规则
service iptables restart

开机自动重载规则

  • 用脚本保存各 iptables 命令;让此脚本开机后自动运行 /etc/rc.d/rc.local 文件中添加脚本路径 /PATH/TO/SOME_SCRIPT_FILE
  • 用规则文件保存各规则,开机时自动载入此规则文件中的规则 在/etc/rc.d/rc.local 文件添加 iptables-restore < /PATH/FROM/IPTABLES_RULES_FILE
  • 定义 Unit File, CentOS 7,8 可以安装 iptables-services 实现 iptables.service
yum install iptables-services
iptables-save > /etc/sysconfig/iptables
systemctl enable iptables.service

SNAT 实现

SNAT:基于 nat 表的 target,适用于固定的公网 IP
例:10.0.1.0/24 网段的主机访问外部网络时,IP 数据包源地址被替换为 172.18.1.6-172.18.1.6 中的某一个地址

iptables -t nat -A POSTROUTING -s 10.0.1.0/24 ! –d 10.0.1.0/24 -j SNAT --to-source 172.18.1.6-172.18.1.9

另一个 target 为MASQUERADE,适用于动态的公网 IP,如拨号网络

iptables -t nat -A POSTROUTING -s 10.0.1.0/24 ! –d 10.0.1.0/24 -j MASQUERADE

DNAT 实现

DNAT:nat 表的 target,适用于端口映射

  • 例如
iptables -t nat -A PREROUTING -s 0/0 -d 172.18.100.6 -p tcp --dport 22 -j DNAT --to-destination 10.0.1.22
iptables -t nat -A PREROUTING -s 0/0 -d 172.18.100.6 -p tcp --dport 80 -j DNAT --to-destination 10.0.1.22:8080

转发 REDIRECTREDIRECT,是 NAT 表的 target,通过改变目标 IP 和端口,将接受的包转发至同一个主机 的不同端口,可用于 PREROUTING

OUTPUT 链

iptables -t nat -A PREROUTING -d 172.16.100.10 -p tcp --dport 80 -j REDIRECT --to-ports 8080

四. Linux启动流程和内核管理

系统启动流程

Centos6

1.加载BIOS的硬件信息,获取第一个启动设备
2.读取第一个启动设备MBR的引导加载程序(grub)的启动信息
3.加载核心操作系统的核心信息,核心开始解压缩,并尝试驱动所有的硬件设备
4.核心执行init程序,并获取默认的运行信息
5.init程序执行/etc/rc.d/rc.sysinit文件
6.启动核心的外挂模块
7.init执行运行的各个批处理文件(scripts)
8.init执行/etc/rc.d/rc.local
9.执行/bin/login程序,等待用户登录
10.登录之后开始以Shell控制主机

启动过程总结:/sbin/init --> (/etc/inittab) --> 设置默认运行级别 --> 运行系统初始脚本、完成系统初始化 --> (关闭对应下需要关闭 的服务)启动 需要启动服务 --> 设置登录终端
或者:POST --> Boot Sequence(BIOS) --> Boot Loader --> Kernel(ramdisk) --> rootfs --> switchroot --> /sbin/init --> (/etc/inittab, /etc/init/*.conf) --> 设定默认运行级别 --> 系统初始化脚本rc.sysinit --> 关闭或启动对应级别的服务 --> 启动终端

  • 运行级别:为系统运行或维护等目的而设定0-6:7个级别
0:关机
1:单用户模式(root自动登录), single, 维护模式
2:多用户模式,启动网络功能,但不会启动NFS;维护模式
3:多用户模式,正常模式;文本界面
4:预留级别;可同3级别
5:多用户模式,正常模式;图形界面
6:重启
默认级别:3, 5
切换级别:init #
查看级别:
runlevel
who -r
  • /etc/rc.d/rc.sysinit: 系统初始化脚本,主要做以下事务```bash
    (1) 设置主机名
    (2) 设置欢迎信息
    (3) 激活udev和selinux
    (4) 挂载/etc/fstab文件中定义的文件系统
    (5) 检测根文件系统,并以读写方式重新挂载根文件系统
    (6) 设置系统时钟
    (7) 激活swap设备
    (8) 根据/etc/sysctl.conf文件设置内核参数
    (9) 激活lvm及software raid设备
    (10) 加载额外设备的驱动程序
    (11) 清理操作
- grub配置文件:/boot/grub/grub.conf

default=#: 设定默认启动的菜单项;落单项(title)编号从0开始
timeout=#:指定菜单项等待选项选择的时长
splashimage=(hd#,#)/PATH/XPM_FILE:菜单背景图片文件路径
password [–md5] STRING: 启动菜单编辑认证
hiddenmenu:隐藏菜单
title TITLE:定义菜单项“标题”, 可出现多次
root (hd#,#):查找stage2及kernel文件所在设备分区;为grub的根
kernel /PATH/TO/VMLINUZ_FILE [PARAMETERS]:启动的内核
initrd /PATH/TO/INITRAMFS_FILE: 内核匹配的ramfs文件
password [–md5|–encrypted ] STRING: 启动选定的内核或操作系统时进行认证

### Centos7
**CentOS 7 引导顺序**
```bash
UEFi或BIOS初始化,运行POST开机自检
选择启动设备
引导装载程序, centos7是grub2
加载程序的配置文件:
/etc/grub.d/
/etc/default/grub
/boot/grub2/grub.cfg
加载initramfs驱动模块
加载内核选项
内核初始化,centos7使用systemd代替init
执行initrd.target所有单元,包括挂载/etc/fstab
从initramfs根文件系统切换到磁盘根目录
systemd执行默认target配置,配置文件/etc/systemd/system/default.target
systemd执行sysinit.target初始化系统及basic.target准备操作系统
systemd启动multi-user.target下的本机与服务器服务
systemd执行multi-user.target下的/etc/rc.d/rc.local
Systemd执行multi-user.target下的getty.target及登录服务systemd执行graphical需要的服务
  • 手动在grub命令行接口启动系统
grub> root (hd#,#)
grub> kernel /vmlinuz-VERSION-RELEASE ro root=/dev/DEVICE
grub> initrd /initramfs-VERSION-RELEASE.img
grub> boot

grub 配置文件

[root@lab-server2 ~]# ll /boot/grub2/grub.cfg
-rw-r--r--. 1 root root 4465 Dec 31 08:43 /boot/grub2/grub.cfg

破解CentOS7的root口令方法一

1.启动时任意键暂停启动
2.按e键进入编辑模式
3.将光标移动linux16开始的行,添加内核参数rd.break
4.按ctrl-x启动
5.mount –o remount,rw /sysroot
6.chroot /sysroot
7.passwd root
8.touch /.autorelabel
9.exit
10.reboot

破解CentOS7的root口令方法二

1.启动时任意键暂停启动
2.按e键进入编辑模式
3.将光标移动linux16开始的行,改为rw init=/sysroot/bin/sh
4.按ctrl-x启动
5.hchroot /sysroot
6.passwd root
7.touch /.autorelabel
8.exit
9.reboot

修复GRUB2

GRUB“the Grand Unified Bootloader”引导提示时可以使用命令行界面
可从文件系统引导
主要配置文件 /boot/grub2/grub.cfg
修复配置文件
grub2-mkconfig > /boot/grub2/grub.cfg
修复grub
grub2-install /dev/sda BIOS环境
grub2-install UEFI环境
调整默认启动内核
vim /etc/default/grub
GRUB_DEFAULT=0

内核管理

sysctl命令查看当前生效的内核参数

  • 默认配置文件:/etc/sysctl.conf
  • (1) 设置某参数 sysctl -w parameter=VALUE
  • (2) 通过读取配置文件设置参数 sysctl -p [/path/to/conf_file]
  • (3) 查看所有生效参数 sysctl -a

内核常用参数

| 参数 | 对应文件 | 备注



---------------------------------------------------------------------------------------------------------- |
| net.core.somaxconn = 4096 | /proc/sys/net/core/somaxconn | 选项默认值是128,这
个参数用于调节系统同时发起的tcp连接数,在高并发请求中,默认的值可能会导致连接超时或重传,因此,需要结合并发请求数来调节此

|
| vm.swappiness = 1 | /proc/sys/vm/swappiness | 这个参数定义了系统对swap的
使用倾向,centos7默认值为30,值越大表示越倾向于使用swap。可以设为0,这样做并不会禁止对swap的使用,只是最大限度地降低了
使用swap的可能性
|
| net.ipv4.ip_forward = 1 | /proc/sys/net/ipv4/ip_forward | 开启路由转发功能
|
| net.ipv4.icmp_echo_ignore_all = 1 | /proc/sys/net/ipv4/icmp_echo_ignore_all | 禁ping
|
| vm.drop_caches | /proc/sys/vm/drop_caches | 用来控制是否清空文件缓存:
默认是0;1-清空页缓存;2-清空inode和目录树缓存;3-清空所有缓存
|| fs.file-max = 1020000 | /proc/sys/fs/file-max | 系统级别的可打开文件句柄数
|
| ulimit -n | /etc/security/limits.conf | 限制经过PAM模块认证的用户打开的文
件句柄数:* soft nofile 65535 * hard nofile 65535
|
| net.ipv4.tcp_syncookies = 1 | /proc/sys/net/ipv4/tcp_syncookies | 开启SYN Cookies,当
SYN等待队列溢出时,启用cookies来处理,可以防范少量的SYN攻击,默认为0,表示关闭
|
| net.ipv4.tcp_tw_reuse = 1 | /proc/sys/net/ipv4/tcp_tw_reuse | 允许将TIME_WAIT
sockets重新用于新的TCP连接,默认为0,表示关闭
|
| net.ipv4.tcp_tw_recycle = 1 | /proc/sys/net/ipv4/* | 允许将TIME_WAIT sockets快速
回收以便利用,默认为0,表示关闭,需要同时开启 net.ipv4.tcp_timestamps 才能生效
|
| net.ipv4.tcp_timestamps = 1 | /proc/sys/net/ipv4/* | 默认为1
|
| net.ipv4.tcp_fin_timeout = 30 | /proc/sys/net/ipv4/* | 表示如果套接字由本端要求关
闭,这个参数决定了它保持在FIN-WAIT-2状态的时间。默认是60s
|
| net.ipv4.ip_local_port_range = 1024 65500 | /proc/sys/net/ipv4/* | 表示用于向外连接的端口
范围。缺省情况下很小:32768到61000,改为1024到65500
|
| net.ipv4.tcp_max_syn_backlog = 8192 | /proc/sys/net/ipv4/* | 表示SYN队列的长度,默
认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。
|
| net.ipv4.tcp_max_tw_buckets = 5000 | /proc/sys/net/ipv4/* | 表示系统同时保持
TIME_WAIT套接字的最大数量,如果超过这个数字,TIME_WAIT套接字将立刻被清除并打印警告信息。
|
| net.ipv4.ip_nonlocal_bind = 1 | /proc/sys/net/ipv4/ip_nonlocal_bind | 此参数表示是否允许服
务绑定一个本机不存在的IP地址; 使用场景:有些服务需要依赖一个vip才能启动,但是此vip不在本机上,当vip飘移到本机上时才存在;
但是服务又需要提前启动,例如haproxy,nginx等代理需要绑定vip时; 0:默认值,表示不允许服务绑定一个本机不存的地址 1:表示允
许服务绑定一个本机不存在的地址 |
| vm.overcommit_memory = 1 | | vm.overcommit_memory:表示系
统允许内存的分配情况 0:默认值; 表示内核将检查是否有足够的可用内存供应用进程使用; 如果有足够的可用内存,内存申请允
许; 否则,内存申请失败,并把错误返回给应用进程。 1:表示内核允许分配所有的物理内存,而不管当前的内存状态如何。redis要
把此参数设为1; 2: 表示允许分配的内存为:物理内存*vm.overcommit_ratio+交换空间; 与参数vm.overcommit_ratio结合使
用; |
| cat /proc/meminfo | awk ‘{print $1,$2/1024 " Mb"}’ | grep “Commit” | | 查看系统中可提交的内
存和已经申请的内存: CommitLimit:表示系统可分配的内存 Committed_AS:表示系统已经分配的内存
|
| kernel.msgmax | | 进程间的通信需要依靠内核来进行管理;是
通过消息列队来传递消息; 以字节为单位,规定消息的单大值; 默认为65536,即64k; 此值不能超过kernel.msgmnb的值,msgmnb
限定了消息队列的最大值;
|
| kernel.msgmnb | | 以字节为单位,规定了一个消息队列的最大
值; 默认值为:65536,即64k;
|
| kernel.mni | | 指定消息队列的最大个数;
|
| kernel.shmall | | 以为单位,控制共享内存总量;Linux一
个内存页大小为4kb;
|| kernel.shmmax | | 定义单个共享内存段的最大值; shmmax 设
置应该足够大,设置的过低可能会导致需要创建多个共享内存段;
|
| kernel.shmmni | | 定义共享内存段的个数,默认为4096;
|
| net.ipv4.tcp_rmem = 4096/87380/67108864 net.ipv4.tcp_wmem = 4096/65536/67108864 | | 增加tcp
缓冲区大小,tcp_rmem表示接受数据缓冲区范围,tcp_wmem表示发送数据缓冲区范围,单位Byte,最大64M
|
| net.ipv4.tcp_retries2 = 5 | | TCP失败重传次数,默认值15,意味着重传
15次才彻底放弃,可减少到5,以尽早释放内核资源;在通讯过程中(已激活的sock),数据包发送失败后,内核要重试发送多少次后才决
定放弃连接
|

内核编译

1.前提:

(1) 准备好开发环境
(2) 获取目标主机上硬件设备的相关信息
(3) 获取目标主机系统功能的相关信息
例如:需要启用相应的文件系统
(4) 获取内核源代码包
www.kernel

2.开发环境准备

包组
Development Tools
目标主机硬件设备相关信息
CPU:
cat /proc/cpuinfo
x86info -a
lscpu
PCI设备:
lspci
-v
-vv
lsusb
-v
-vv
块设备
lsblk
了解全部硬件设备信息
hal-device:CentOS 6

3.步骤

下载源码文件
.config:准备文本配置文件
make menuconfig:配置内核选项
make [-j #] 或者用两步实现: make -j # bzImage ; make -j # modules
make modules_install:安装模块
make install :安装内核相关文件
安装bzImage为 /boot/vmlinuz-VERSION-RELEASE
生成initramfs文件
编辑grub的配置文件

编译内核两大步

  • 1.配置内核选项
支持“更新”模式进行配置:make help
(a) make config:基于命令行以遍历的方式配置内核中可配置的每个选项
(b) make menuconfig:基于curses的文本窗口界面
(c) make gconfig:基于GTK (GNOME)环境窗口界面
(d) make xconfig:基于QT(KDE)环境的窗口界面
支持“全新配置”模式进行配置
(a) make defconfig:基于内核为目标平台提供的“默认”配置进行配置
(b) make allyesconfig: 所有选项均回答为“yes“
(c) make allnoconfig: 所有选项均回答为“no“
  • 2.编译
全编译:make [-j #]
编译内核的一部分功能:
(a) 只编译某子目录中的相关代码
cd /usr/src/linux
make dir/
(b) 只编译一个特定的模块
cd /usr/src/linux
make dir/file.ko
示例:只为e1000编译驱动:
make drivers/net/ethernet/intel/e1000/e1000.ko

卸载内核

删除/lib/modules/目录下不需要的内核库文件
删除/usr/src/linux/目录下不需要的内核源码
删除/boot目录下启动的内核和内核映像文件
更改grub的配置文件,删除不需要的内核启动列表
centos7:vim /boot/grub2/grub.cfg
:/menuentrycentos8:
rm -f /boot/loader/entries/7e3e9120767340a8bd946a83d7c3b84d-$(uname -r)-80.el8.x86_64.conf

systemd服务

Systemd:系统启动和服务器守护进程管理器,负责在系统启动或运行时,激 活系统资源,服务器进程和其它进程
POST --> Boot Sequence --> Bootloader --> kernel + initramfs(initrd) --> rootfs --> /sbin/init
centos6 --> centos7:

启动:service name start ==> systemctl start name.service
停止:service name stop ==> systemctl stop name.service
重启:service name restart ==> systemctl restart name.service
状态:service name status ==> systemctl status name.service
禁止自动和手动启动: systemctl mask name.service
取消禁止: systemctl unmask name.service
服务查看
查看某服务当前激活与否的状态: systemctl is-active name.service
查看所有已经激活的服务: systemctl list-units --type|-t service
查看所有服务: systemctl list-units --type service --all|-a
重新加载配置 systemctl reload sshd.service
列出活动状态的所有服务单元 systemctl list-units --type=service
列出所有服务单元 systemctl list-units --type=service --all
查看服务单元的启用和禁用状态 systemctl list-unit-files --type=service
列出失败的服务 systemctl --failed --type=service
列出依赖的单元 systemctl list-dependencies sshd
验证sshd服务是否开机启动 systemctl is-enabled sshd
禁用network,使之不能自动启动,但手动可以 systemctl disable network
启用network systemctl enable network
禁用network,使之不能手动或自动启动 systemctl mask network
启用network systemctl unmask network

运行级别

0 ==> runlevel0.target, poweroff.target
1 ==> runlevel1.target, rescue.target
2 ==> runlevel2.target, multi-user.target
3 ==> runlevel3.target, multi-user.target4 ==> runlevel4.target, multi-user.target
5 ==> runlevel5.target, graphical.target
6 ==> runlevel6.target, reboot.target

获取默认运行级别: systemctl get-default
传统命令init,poweroff,halt,reboot都成为systemctl的软链接

关机:systemctl halt、systemctl poweroff
重启:systemctl reboot
挂起:systemctl suspend
休眠:systemctl hibernate
休眠并挂起:systemctl hybrid-sleep

4.5 文件管理

linux文件系统简述

linux文件系统中的文件名称区分大小写,其中以点(.)开头的文件为隐藏文件
文件由两类数据组成:

  • 元数据:metadata
  • 数据:data

linux下的文件命令规则

  • 文件名最长255个字节
  • 包括路径在内文件名最长4095个字节
  • 蓝色–>目录 绿色–>可执行文件 红色–>压缩文件 浅蓝色–>链接文件 灰色–>其他文件 (可以自定义)
  • 除了斜杠和NUL,所有字符都有效.但使用特殊字符的目录名和文件不推荐使用,有些字符需要用引号来引用它们
  • 标准Linux文件系统(如ext4),文件名称大小写敏感
    例如:MAIL, Mail, mail, mAiL

文件系统结构细节

目录功能
/boot引导文件存放目录,内核文件(vmlinuz)、引导加载器(bootloader, grub)都存放于此目录
/bin所有用户使用的基本命令;不能关联至独立分区,OS启动即会用到的程序
/sbin管理类的基本命令;不能关联至独立分区,OS启动即会用到的程序
/lib启动时程序依赖的基本共享库文件以及内核模块文件(/lib/modules)
/lib64专用于x86_64系统上的辅助共享库文件存放位置
/etc配置文件目录
/home/USERNAME普通用户家目录
/root管理员的家目录
/media便携式移动设备挂载点
/mnt临时文件系统挂载点
/dev设备文件及特殊文件存储位置
c: character device,字符设备,线性访问
/opt第三方应用程序的安装位置
/srv系统上运行的服务用到的数据
/tmp临时文件存储位置
/usruniversal shared, read-only data 全局共享的只读文件存放地
/usr/bin: 保证系统拥有完整功能而提供的应用程序
/usr/sbin: centos7上访问/sbin实质是访问 /usr/sbin
/usr/lib:32位使用
/usr/lib64:只存在64位系统
/usr/include: C程序的头文件(header files)
/usr/share:结构化独立的数据,例如doc, man等
/usr/local:第三方应用程序的安装位置:bin, sbin, lib, lib64, etc, share
/varvariable data files 可变文件存放地点
/var/cache: 应用程序缓存数据目录
/var/lib: 应用程序状态信息数据
/var/local:专用于为/usr/local下的应用程序存储可变数据
/var/lock: 锁文件
/var/log: 日志目录及文件
/var/opt: 专用于为/opt下的应用程序存储可变数据
/var/run: 运行中的进程相关数据,通常用于存储进程pid文件
/var/spool: 应用程序数据池
/var/tmp: 保存系统两次重启之间产生的临时数据
/proc用于输出内核与进程信息相关的虚拟文件系统
/sys用于输出当前系统上硬件设备相关信息虚拟文件系统
/selinuxsecurity enhanced Linux,selinux相关的安全策略等信息的存储位置

文件种类

符号文件类型
-普通文件
d目录文件
b块设备
c字符设备
l符号链接文件
p管道文件pipe
s套接字文件socket

各类文件存放地

文件类型存放的文件夹
二进制程序/bin, /sbin, /usr/bin, /usr/sbin, /usr/local/bin, /usr/local/sbin
库文件/lib, /lib64, /usr/lib, /usr/lib64, /usr/local/lib, /usr/local/lib64
配置文件/etc, /etc/DIRECTORY, /usr/local/etc
帮助文件/usr/share/man, /usr/share/doc, /usr/local/share/man, /usr/local/share/doc

绝对路径和相对路径

绝对路径:表示从根开始完整的文件的位置路径,以正斜杠开始,可用于任何想指定一个文件名的时候相对路径:名指定相对于当前工作目录或某目录的位置,不以斜线开始,可以作为一个简短的形式指定一个文件名
查看路径的基名:basename /path/to/somefile
查看某路径的目录名:dirname /path/to/somefile

linux下的inode解释

几乎每个文件系统都会需要大量不同的数据结构来保证其底层对各种文件存储目的的支持;在linux系统中(ext3或者ext4或者xfs文件系
统)就有一个很重要的数据结构叫inode(index node),一个inode包含某个文件或者某个目录的以下信息(也叫元数据):

信息项英文术语
-----------
文件类型(可执行文件,块文件,字符文件等)File types ( executable, block special etc )
文件权限(读,写,执行等)Permissions ( read, write etc )
文件属主全局唯一标识符UID ( Owner )
文件属组全局唯一标识符GID ( Group )
文件大小FileSize/bytes
时间戳:最后访问时间、最后修改时间、最后改变时间和inode号变化信息Time stamps including last access, last modification
and last inode number change.
文件链接数(硬链接和软链接)Number of links ( soft/hard )
文件在磁盘的位置Location of ile on harddisk.
该文件所分配的磁盘块数量
其他信息Some other metadata about file.

(Under ext2, each i-node contains 15 pointers. The first 12 of these pointers (num-bered 0 to 11 in Figure 14-2) point to the
location in the file system of the first 12 blocksof the file. The next pointer is a pointer to a block of pointers that give the
locations ofthe thirteenth and subsequent data blocks of the file.)
在ext2文件系统中,每个i-node包含有15和数据块指针。前12个数据块指针(0-11)指向分配给该文件的前12个数据块在磁盘的位置。接
下来的指针则指向下一个数据块或者指向一个指针块。
inode数据结构被存储在inode表中:由于每个inode代表某个文件的所有属性信息,所以inode表就记录了整个文件系统上的所有文
件的信息(元数据)
linux文件系统中每个目录下的文件被存储成目录项,每一项对应其inode号,通过inode号就可以访问到inode表的某一项,该项就记录了
该文件的元数据。如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hmTqcsX7-1604371435758)(png/20191224193127136.png)]

cp命令与inode

分配一个空闲的inode号,在inode表中生成新条目;在目录中创建一个目录项,将名称与inode编号关联;拷贝数据生成新的文件

rm命令和inode

链接数递减,从而释放的inode号可以被重用,把数据块放在空闲列表中,删除目录项数据实际上不会马上被删除,但当另一个文件使用
数据块时将被覆盖

mv命令和inode

如果mv命令的目标和源在相同的文件系统,作为mv 命令 :- 用新的文件名创建对应新的目录项

  • 删除旧目录条目对应的旧的文件名
  • 不影响inode表(除时间戳)或磁盘上的数据位置:没有数据被移动!
    如果目标和源在一个不同的文件系统, mv相当于cp和rm

硬链接和软链接

硬链接

创建硬链接会增加额外的记录项以引用文件,对应于同一文件系统上同一个物理文件
每个目录引用相同的inode号,创建时链接数递增

  • 硬链接,以文件副本的形式存在。但不占用实际空间。
  • 不允许给目录创建硬链接
  • 硬链接只有在同一个文件系统中才能创建
  • 删除其中一个硬链接文件并不影响其他有相同 inode 号的文件

删除文件时:

rm命令递减计数的链接
文件要存在,至少有一个链接数
当链接数为零时,该文件被删除
硬链接不能跨越驱动器或分区

为文件创建硬链接语法:
~# ln filename [linkname ]

符号(或软)链接

符号链接,有时也称为软链接,是一种特殊的文件类型,其数据是另一文件的名称。
一个符号链接的数据内容是另一个文件的路径:

  • 一个符号链接的数据内容是它引用文件的名称
  • 可以对目录创建软连接
  • 软链接可以对一个不存在的文件名进行链接,硬链接必须要有源文件
  • 可以跨分区创建
  • 指向的是另一个文件的路径
  • 其大小为指向的路径字符串的长度
  • 不增加或减少目标文件inode的引用计数
  • 语法:
~# ln -s filename [linkname]

不论是硬链接或软链接都不会将原本的档案复制一份,只会占用非常少量的磁碟空间## 文件权限

文件权限

属主、属组

目录权限

目录与文件拥有相同的权限方案,只是对3种权限的含义另有所指。
读权限:可列出(比如,通过ls命令)目录之下的内容(即目录下的文件名)。

在实验验证对目录读权限位的操作时,应当了解有些Linux发行版对ls做了别名处理,
命令所携带的一些选项(比如,-F)需要访问目录中文件的i节点信息,而这又需要拥有对
目录的执行权限。为确保使用的是ls命令本身,执行时要给出命令的完整路径名(/bin/ls)

写权限:可在目录内创建、删除文件。注意,要删除文件,对文件本身无需有任何权限。
可执行权限:可访问目录中的文件。因此,有时也将对目录的执行权限称为search(搜索)权限。
访问文件时,需要拥有对路径名所列所有目录的执行权限。例如,想读取文件/home/mtk/x,则需拥有对目录/、/home以及/home/mtk
的执行权限(还要有对文件x自身的读权限)。若当前的工作目录为/home/mtk/sub1,访问相对路径名…/sub2/x时,需握有/home/mtk
和/home/mtk/sub2这两个目录的可执行权限(不必有对/或/home的执行权限)。 拥有对目录的读权限,用户只是能查看目录中的文件
列表。要想访问目录内文件的内容或是这些文件的 i节点信息,还需握有对目录的执行权限。 反之,若拥有对目录的可执行权限,而无读
权限,只要知道目录内文件的名称,仍可对其进行访问,但不能列出目录下的内容(即目录所含的其他文件名)。在控制对公共目录内容
的访问时,这是一种常用技术,简单而且实用。
要想在目录中添加或删除文件,需要同时拥有对该目录的执行和写权限。

五. Linux网络协议与管理

ISO网络7层模型

  • 物理层:其为启动、维护和关闭物理链路规定了:电器特性、机械特性、过程特性和功能特性
  • 数据链路层: 有时也称网络接口层,通常包括操作系统中的设备驱动程序和计算机中对应的网络接口卡
  • 网络层: 有时也称作互联网层,其主要负责处理分组在网络中的活动,例如分组的选路。在TCP/IP协议族中,网络层协议包括IP协议
    (网际协议),ICMP协议(Internet互联网控制报文协议),以及IGMP协议(Internet组管理协议)
  • 传输层: 运输层主要为两台主机上的应用程序提供端到端的通信。在TCP/IP协议族中,有两个互不相同的传输协议:TCP(传输控制协
    议)和UDP(用户数据报协议)
  • 会话层
  • 表示层
  • 应用层: 应用层负责处理特定的应用程序细节## 数据链路层
    在TCP/IP协议族中,数据链路层主要有三个目的
  • (1)为IP模块发送和接收IP数据报;
  • (2)为ARP模块发送ARP请求和接收ARP应答;
  • (3)为RARP发送RARP请求和接收RARP应答。
    环回接口
    大多数的产品都支持环回接口(Loopback Interface),以允许运行在同一台主机上的客户程序和服务器程序通过TCP/IP进行通信。A类
    网络号127就是为环回接口预留的。根据惯例,大多数系统把IP地址127.0.0.1分配给这个接口,并命名为localhost。一个传给环回接口的
    IP数据报不能在任何网络上出现

网络层/IP协议

IP协议
IP是TCP/IP协议族中最为核心的协议。所有的TCP、UDP、ICMP及IGMP数据都以IP数据报格式传输。许多刚开始接触TCP/IP的人对IP提
供不可靠、无连接的数据报传送服务感到 很奇怪。IP提供的是一种不可靠的服务。也就是说,它只是尽可能快地把分组从源结点送到目的
结点,但是并不提供任何可靠性保证。而另一方面,TCP在不可靠的IP层上提供了 一个可靠的运输层。为了提供这种可靠的服务,TCP采
用了超时重传、发送和接收端到端的确认分组等机制

  • **不可靠(unreliable)**的意思是它不能保证IP数据报能成功地到达目的地。IP仅提供最好的传输服务。如果发生某种错误时,如某个
    路由器暂时用完了缓冲区,IP有一个简单的错误处理算法:丢弃该数据报,然后发送ICMP消息报给信源端。任何要求的可靠性必须由上层
    来提供(如TCP)
  • 无连接(connectionless)这个术语的意思是IP并不维护任何关于后续数据报的状态信息。每个数据报的处理是相互独立的。这也
    说明,IP数据报可以不按发送顺序接收。如果一信源向相同的信宿发送两个连续的数据报(先是A,然后是B),每个数据报都是独立地进
    行路由选择,可能选择不同的路线,因此B可能在A到达之前先到达
    IP数据报文格式
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kN8wsfCx-1604371435761)(png/20191025085607291.png)]
    ICMP协议
    Internet控制报文协议,主要用于传递差错报文和其他信息。ICMP报文通常被IP层或者更高层协议(TCP/UDP)使用。某些ICMP报文用来
    将差错报文传给用户进程
    IGMP协议
    Internet组管理协议,用于支持主机和路由器进行多播。它让一个物理网络上的所有系统知道主机当前所在的多播组。多播路由器需要这
    些信息以便知道多播数据报应该向哪些接口转发。和ICMP一样,IGMP也被当作IP层的一部分。IGMP报文通过IP数据报进行传输。不像
    我们已经见到的其他协议,IGMP有固定的报文长度,没有可选数据。IGMP报文通过IP首部中协议字段值为2来指明
    最大传输单元MTU

    以太网和802.3对数据帧的长度都有一个限制,其最大值分别是1500和1492字节。链路层的这个特性称作MTU,最大传输单元。不同类
    型的网络大多数都有一个上限。如果I P层有一个数据报要传,而且数据的长度比链路层的MTU还大,那么IP层就需要进行分片
    (fragmentation),把数据报分成若干片,这样每一片都小于MTU
    路径MTU
    当在同一个网络上的两台主机互相进行通信时,该网络的MTU是非常重要的。但是如果两台主机之间的通信要通过多个网络,那么每个网
    络的链路层就可能有不同的MTU。重要的不是两台主机所在网络的MTU的值,重要的是两台通信主机路径中的最小MTU。它被称作路径
    MTU。两台主机之间的路径MTU不一定是个常数。它取决于当时所选择的路由。而选路不一定是对称的(从A到B的路由可能与从B到A的
    路由不同),因此路径MTU在两个方向上
    不一定是一致的

传输层/TCP协议

TCP协议

尽管TCP和UDP都使用相同的网络层(IP),TCP却向应用层提供与UDP完全不同的服务。TCP提供一种面向连接的、可靠的字节流服务
面向连接
意味着两个使用TCP的应用(通常是一个客户和一个服务器)在彼此交换数据之前必须先建立一个TCP连接。这一过程与打电话很相似,
先拨号振铃,等待对方摘机说“喂”,然后才说明是谁
可靠性的保证

  • 应用数据被分割成TCP认为最适合发送的数据块。这和UDP完全不同,应用程序产生的数据报长度将保持不变。由TCP传递给IP的信息单
    位称为报文段或段(segment)
  • 当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。TCP协
    议使用了自适应的超时及重传策略
  • TCP将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差
    错,TCP将丢弃这个报文段和不确认收到此报文段(希望发端超时并重发)
  • 既然TCP报文段作为IP数据报来传输,而IP数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。如果必要,TCP将对收到的
    数据进行重新排序,将收到的数据以正确的顺序交给应用层 。 既然IP数据报会发生重复,TCP的接收端必须丢弃重复的数据
  • TCP还能提供流量控制。TCP连接的每一方都有固定大小的缓冲空间。TCP的接收端只
    允许另一端发送接收端缓冲区所能接纳的数据。这将防止较快主机致使较慢主机的缓冲
    区溢出
    两个应用程序通过TCP连接交换8bit字节构成的字节流。TCP不在字节流中插入记录标识符。我们将这称为字节流服务
    (bytestreamservice)。如果一方的应用程序先传10字节,又传20字节,再传50字节,连接的另一方将无法了解发方每次发送了多少字
    节。收方可以分4次接收这80个字节,每次接收20字节。一端将字节流放到TCP连接上,同样的字节流将出现在TCP连接的另一端; 另
    外,TCP对字节流的内容不作任何解释。TCP不知道传输的数据字节流是二进制数据,还是ASCII字符、EBCDIC字符或者其他类型数据。
    对字节流的解释由TCP连接双方的应用层
    解释```bash
    这种对字节流的处理方式与Unix操作系统对文件的处理方式很相似。Unix的内核对一个应用读或写的内容不作任何解释,而是交给应用程
    序处理。对Unix的内核来说,它无法区分一个二进制文件与一个文本文件
**TCP报文段被封装在一个IP数据报中** :
![](png/2019102511005542.png)
- 每个TCP段都包含源端和目的端的端口号,用于寻找发端和收端应用进程。这两个值加上IP首部中的源端IP地址和目的端IP地址唯一确
定一个TCP连接, 插口对(socket pair)(包含客户IP地址、客户端口号、服务器IP地址和服务器端口号的四元组)可唯一确定互联网络中
每个TCP连接的双方
- 序号用来标识从TCP发端向TCP收端发送的数据字节流,它表示在这个报文段中的的第一个数据字节。如果将字节流看作在两个应用程
序间的单向流动,则TCP用序号对每个字节进行计数。序号是32bit的无符号数,序号到达232-1后又从0开始
- TCP可以表述为一个没有选择确认或否认的滑动窗口协议(滑动窗口协议用于数据传输)。我们说TCP缺少选择确认是因为TCP首部中
的确认序号表示发方已成功收到字节,但还不包含确认序号所指的字节。当前还无法对数据流中选定的部分进行确认 、
- 在TCP首部中有6个标志比特。它们中的多个可同时被设置为1。
- URG 紧急指针(urgentpointer)有效。
- ACK 确认序号有效。
- PSH 接收方应该尽快将这个报文段交给应用层。
- RST 重建连接。
- SYN 同步序号用来发起一个连接。
- FIN 发端完成发送任务
- TCP的流量控制由连接的每一端通过声明的窗口大小来提供。窗口大小为字节数,起始于确认序号字段指明的值,这个值是接收端正期
望接收的字节。窗口大小是一个16bit字段,因而窗口大小最大为65535字节。新的窗口刻度选项允许这个值按比例变化以提供更大的窗口
- **检验和**:覆盖了整个的TCP报文段:TCP首部和TCP数据。这是一个强制性的字段,一定是由发端计算和存储,并由收端进行验证。
TCP检验和的计算和UDP检验和的计算相似
- 只有当URG标志置1时紧急指针才有效。紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。TCP
的紧急方式是发送端向另一端发送紧急数据的一种方式。
- TCP报文段中的数据部分是可选的。接建立和一个连接终止时,双方交换的报文段仅有TCP首部。如果一方没有数据要发送,也使用没
有任何数据的首部来确认收到的数据。在处理超时的许多情况中,也会发送不带任何数据的报文段
### 三次握手
TCP是一个面向连接的协议。无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接 。
- 1)请求端(通常称为客户)发送一个SYN段指明客户打算连接的服务器的端口,以及初始序号(ISN,在这个例子中为1415531521)。
这个SYN段为报文段1。
- 2)服务器发回包含服务器的初始序号的SYN报文段(报文段2)作为应答。同时,将确认序号设置为客户的ISN加1以对客户的SYN报文段
进行确认。一个SYN将占用一个序号。
- 3)客户必须将确认序号设置为服务器的ISN加1以对服务器的SYN报文段进行确认(报文段3)。通过这三个报文后就建立连接了。这个过
程也称为三次握手(three-way handshake)。
![](png/20191025110255379.png)
### 四次挥手建立一个连接需要三次握手,而终止一个连接要经过4次握手。这由TCP的半关闭(half-close)造成的。既然一个TCP连接是全双工(即
数据在两个方向上能同时传递),因此每个方向必须 单独地进行关闭。这原则就是当一方完成它的数据发送任务后就能发送一个FIN来终
止这个方向连接。当一端收到一个FIN,它必须通知应用层另一端已经终止了那个方向的数据传送。发送FIN通常是应用层进行关闭的结果
。
收到一个FIN只意味着在这一方向上没有数据流动。**一个TCP连接在收到一个FIN后仍能发送数据**。而这对利用半关闭的应用来说是可
能的,尽管在实际应用中只有很少的TCP应用程序这样做。 首先进行关闭的一方(即发送第一个FIN)将执行主动关闭,而另一方(收到
这个FIN)执行被动关闭。通常一方完成主动关闭而另一方完成被动关闭,但也可能双方都执行主动关闭
当服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样, 一个FIN将占用一个序号。同时TCP服
务器还向应用程序(即丢弃服务器)传送一个文件结束符。 接着这个服务器程序就关闭它的连接,导致它的TCP端发送一个FIN(报文段
6),客户必须发回 一个确认,并将确认序号设置为收到序号加1(报文段7)。 发送FIN将导致 应用程序关闭它们的连接,这些FIN的
ACK是由TCP软件自动产生的。连接通常是由客户端发起的, 这样第一个SYN从客户传到服务器。每一端都能主动关闭这个连接(即首先
发送FIN)。然而,一般 由客户端决定何时终止连接,因为客户进程通常由用户交互控制,用户会键入诸如“quit”一样的 命令来终止进
程
#### 2MSL等待状态
TIME_WAIT状态也称为2MSL等待状态。每个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime)。
它是任何报文段被丢弃前在网络内的最长时间。我们知道这个时间是有限的,因为TCP报文段以IP数据报在网络内传输,而IP数据报则有
限制其生存时间的TTL字段
```bash
RFC 793 [Postel 1981c] 指出MSL为2分钟。然而,实现中的常用值是30秒,1分钟,或2分钟。

在实际应用中,对IP数据报TTL的限制是基于跳数,而不是定时器。对一个具体实现所给定的MSL值,处理的原则是:当TCP执行一个主动
关闭,并发回最后一个ACK,该连接必须在TIME_WAIT状态停留的时间为2倍的MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢
失(另一端超时并重发最后的FIN)
这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的插口(客户的IP地址和端口号,服务器的IP地址和端口
号)不能再被使用。这个连接只能在2MSL结束后才能再被使用

UDP协议

UDP是一个简单的面向数据报的运输层协议:进程的每个输出操作都正好产生一个UDP数据报,并组装成一份待发送的IP数据报。这与面
向流字符的协议不同,如TCP,应用程序产生的全体数据与真正发送的单个IP数据报可能没有什么联系。UDP数据报被封装成一份IP数据
报的格式

分用

当目的主机收到一个以太网数据帧时,数据就开始从协议栈中由底向上升,同时去掉各层协议加上的报文首部。每层协议盒都要去检查报
文首部中的协议标识,以确定接收数据的上层协议。这个过程称作分用(Demultiplexing)
2.

PDU

Protocol Data Unit,协议数据单元是指对等层次之间传递的数据单位

  • 物理层的 PDU是数据位 bit- 数据链路层的 PDU是数据帧 frame
  • 网络层的PDU是数据包 packet
  • 传输层的 PDU是数据段 segment
  • 其他更高层次的PDU是消息 message

传统的IP地址分类

  • 具体的网络数、所容纳的主机数及私网地址等如下:
1.A类地址:
0000 0000 - 0111 1111: 1-127
网络数:126, 127
每个网络中的主机数:2^24-2
默认子网掩码:255.0.0.0
私网地址:10.0.0.0
2.B类地址:
1000 0000 - 1011 1111:128-191
网络数:2^14
每个网络中的主机数:2^16-2
默认子网掩码:255.255.0.0
私网地址:172.16.0.0-172.31.0.0
3.C类地址:
1100 0000 - 1101 1111: 192-223
网络数:2^21
每个网络中的主机数:2^8-2
默认子网掩码:255.255.255.0
私网地址:192.168.0.0-192.168.255.0
4.D类地址:
D类地址为组播地址
1110 0000 - 1110 1111: 224-239
  • 特殊地址
0.0.0.0
0.0.0.0不是一个真正意义上的IP地址。它表示所有不清楚的主机和目的网络
255.255.255.255
限制广播地址。对本机来说,这个地址指本网段内(同一广播域)的所有主机
127.0.0.1~127.255.255.254
本机回环地址,主要用于测试。在传输介质上永远不应该出现目的地址为“127.0.0.1”的数据包
224.0.0.0到239.255.255.255
组播地址,224.0.0.1特指所有主机,224.0.0.2特指所有路由器。224.0.0.5指OSPF路由器,地址多用于一些特定的程序以及多媒体程序
169.254.x.x
如果Windows主机使用了DHCP自动分配IP地址,而又无法从DHCP服务器获取地 址,系统会为主机分配这样地址

CIDR表示的网络地址- CIDR全称为:无类域内路由选择(Classless Inter-Domain Routing)

  • 描述IP和网段时,子网掩码一般和IP成对出现,例如:192.168.123.234 255.255.255.0
    但是这种表示方法比较长,另一种比较方便的表示方法就叫CIDR表示法。其直接将子网掩码为1的位数写在IP地址的后面。例如:
    192.168.123.234 255.255.255.0 可以改写为:192.168.123.234/24(24就是子网掩码全为1的位数)。

路由的概念

  • 每个数据包要在不同网络中传输,都需要进行路由,每次路由时都根据路由表中的记录决定下一跳该给谁。
  • 路由分为:
  • 主机路由
  • 网段路由
  • 默认路由

Linux网络管理命令及配置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8R9nvqUn-1604371435763)(png/20191025134727495-1586529686923.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2zmhZeiv-1604371435764)(png/20191025134807866-1586529702535.png)]

多网卡绑定

将多块网卡绑定同一IP地址对外提供服务,可以实现高可用或者负载均衡。直接给两块网卡设置同 一IP地址是不可以的。通过bonding,
虚拟一块网卡对外提供连接,物理网卡的被修改为相同的MAC地址。
Bonding工作模式
| mode

explain
---------------------------------------------------------------------
------------------------------------------------------------------------------------
Mode 0 (balance-rr)
轮询(Round-robin)策略,从头到尾顺序的在每 一个slave接口上面发送数据包。本模式提供负载均衡和容错的能力
Mode 1 (active-backup)
活动-备份(主备)策略,只有一个slave被激活,当且仅当活动的slave接口失败时才会激活其他slave.为了避免交换机发生混乱此时绑定
的MAC地址只有一个外部端口上可见
Mode 3 (broadcast)
广播策略,在所有的slave接口上传送所有的报文,提供容错能力
active-backup、balance-tlb 和 balance-alb 模式不需要交换机的任何特殊配置。其他绑定模式需要配置交换机以便整合链接。如:
Cisco 交换机需要在模式 0、2 和 3 中使用 EtherChannel,但在模式4中需要LACP和EtherChannel

**桥接:**把一台机器上的若干个网络接口“连接”起来。其结果是,其中一个网收到的报文会被复制给其他网口并发送出去。以使得网
口之间的报文能够互相转发。网桥就是这样一个设备,它有若干个网口,并且这些网口是桥接起来的。与网桥相连的主机就能通过交换机
的报文转发而互相通信。## 网络工具

tcpdump使用

tcpdump通过将网络接口设置为混杂模式来嗅探网络数据包,可以抓取目标MAC地址为当前接口及广播MAC地址的数据包。

tcpdump [ -AbdDefhHIJKlLnNOpqStuUvxX# ] [ -B buffer_size ]
[ -c count ]
[ -C file_size ] [ -G rotate_seconds ] [ -F file ]
[ -i interface (指明接口)] [ -j tstamp_type ] [ -m module ] [ -M secret ]
[ --number ] [ -Q|-P in|out|inout ]
[ -r file ] [ -V file ] [ -s snaplen ] [ -T type ] [ -w file (保存到文件)]
[ -W filecount ]
[ -E spi@ipaddr algo:secret,... ]
[ -y datalinktype ] [ -z postrotate-command ] [ -Z user ]
[ --time-stamp-precision=tstamp_precision ]
[ --immediate-mode ] [ --version ]
[ expression ]

使用tcpdump时可以通过expression关键字指明抓取的是什么类型、方向的数据包

options:
-i interface
-w file
-nn: IP和端口数字显示
-A:ASCII码显示
-X:hex和ASCII显示
-XX:包含链路层信息
-v:详细信息 -vv:更详细信息
expression:
type: host/net/port/portrange
direction:src/dst/src or dst/src and dst
protocol: ether/ip/arp/tcp/udp/wlan

tcpdump示例

~# tcpdump -i eth0 # 抓取eth0接口的数据包
~# tcpdump -i eth0 -nn # 第一个n表示将主机名显示为IP数字,第二个n表示将服务端口名显示为数字端口
~# tcpdump -i eth0 -nn port 80 # 抓取访问80端口的数据包
~# tcpdump -i eth0 -nn dst port 80 #
~# tcpdump -i eth0 -nn src port 80 #
~# tcpdump -i eth0 -nn src port 80 and src host 192.168.100.40 #
~# tcpdump -i eth0 -nn src port 80 and src host 192.168.100.40 -A
~# tcpdump -i eth0 -nn src port 80 and src host 192.168.100.40 -X
~# tcpdump -i eth0 -nn src port 80 and src host 192.168.100.40 -XX~# tcpdump -i eth0 -nn src port 80 and src host 192.168.100.40 -vv
~# tcpdump -i eth0 -nn tcp src port 80 and src host 192.168.100.40 -vv #

nc工具使用

hello

nc示例

hello

nmap工具使用

nmap示例

六. 进程管理和任务计划

内核功能

| 内核功能 | 解释


------------------------ |
| 进程调度 | 现在的计算机一般会有多个物理核心用来执行程序的指令。Linux系统是一个抢占式多任务(preemptive
multitasking)的操作系统,多任务就意味着多个进程可以同时驻留在内存中得到CPU的处理,抢占式就意味着是哪个进程使用CPU或者使用
多久的规则由内核进程调度器决定而不是CPU自己决定处理哪个进程 |
| 内存管理 | 目前内存已近越来越大,但是软件体积也同样在增长;计算机的物理内存仍然是比较稀缺的资源,这就需要内核
合理的管理分配内存给系统的各个进程 |
| 提供文件系统功能 | 内核能够在磁盘上创建特定文件系统,以允许文件被创建,复制更新和删除等
|
| 创建和销毁进程 | 内核可以加载某个程序到内存,并为其分配其运行所需要的资源(CPU、内存、对文件的访问等),在内存中运
行的某个程序的实例就叫做进程。当程序被内核销毁或者自己运行结束后,内核还需要保证其使用的资源被释放以便于后续进程重用
|
| 访问设备 | 连接到计算机上的设备(如:麦克风、显示器、键盘鼠标、磁盘和磁带、软盘等)是的计算机内部和计算机之间及
计算机和外部世界可以进行信息交互,允许计算机进行输入、输出等操作。内核为每个上层应用程序提供了一个标准的接口来访问设备,
同时仲裁多个进程对设备的访问 |
| 提供网络功能 | 内核代替用户进程收发网络数据。
|
| 提供系统调用应用程序编程接口 | 进程可以通过一个入口请求内核完成多种操作,该入口就是系统调用
|
| 提供虚拟个人电脑抽象功能 | 在linux系统,多个用户可以同时登陆系统并进行不同的操作,内核提供了一个抽象层来保证各个用户的
操作和对设备的使用的隔离性 |## 程序?进程?线程?
| |


-------------------------------------------------- |
| 程序 | 一般程序以两种形式存在:源代码的形式和二进制可执行程序的形式。源代码是人类可以读的文本(包括一系列的使用列如C语言编
写的语句)。程序要被CPU执行,必须编译链接为计算机可以识别的二进制机器码指令(windows内的.exe文件;linux下的.o文件等);二者
被认为是同义词,都代表程序。 |
| 进程 | 简单的说,一个进程就是一个正在执行的程序的实例。当一个程序被执行时,内核会将该程序的指令加载进内存,分配内存空间给
程序变量并设置相应的数据结构来记录该进程的信息(Linux内核对该种数据结构的实现叫task list,保存有进程ID、退出状态、用户ID和组
ID等)。因此,进程也可以描述为:运行的程序及其包括的数据和操作。 |
| 线程 | 在现代的UNIX实现版中,每个进程可以有多个线程运行。一个理解线程比较好的表述是:共享同一块内存空间和其他属性的轻量
级进程的集合。每个线程都执行同一个代码块并共享相同的数据区域和堆。然而,每个线程都有自己的栈空间(包含本地变量和函数调用连
接信息)。线程之间可以通过其共享的全局变量进行通讯,进程亦可以通过IPC和同步机制进行通讯。 |

用户模式和内核模式

现代处理器架构一般都允许CPU在至少两个不同模式运行:用户模式(user mode)和内核模式 (kernel mode:内核模式有时候也被称为
监管模式),CPU自带的硬件指令允许在两个模式之间切换。 相应地,虚拟内存的某部分可被标记为用户空间(user space)或内核空间
(kernel space)。当在 用户模式运行某进程时CPU只可以访问标记为用户控件的内存区域,尝试访问被标记为内核空间的内存 将会导致一
个硬件异常(hardware exception)错误。

进程的状态

| |

--------------------------------------------------------------------------
创建状态
配。如果创建工作无法完 成,比如资源无法满足,就无法被调度运行,把此时进程所处状态称为创建状态
就绪状态
执行状态
阻塞状态
系统调用
终止状态

状态之间转换的四种情况

| |

--------------------------------------------------------------------
运行to就绪
系统中,当有更高优先级的进程要运行时,该进程就被迫让出CPU,该进程便由执行状态转变为就绪状态
运行to阻塞
阻塞to就绪

Linux的进程状态

  • linux进程类型: 守护进程: daemon,在系统引导过程中启动的进程,和终端无关进程 前台进程:跟终端相关,通过终端启动的进程 注
    意:两者可相互转化
  • linux进程状态: 运行态:running 就绪态:ready 睡眠态: 可中断睡眠态:interruptable 不可中断睡眠态:uninterruptable 停止
    态:stopped,暂停于内存,但不会被调度,除非手动启动 僵死态:zombie,结束进程,父进程结束前,子进程不关闭
    Linux进程优先级
  • 系统优先级:数字越小,优先级越高
  • 0-139:每个优先级有140个运行队列和过期队列
  • 实时优先级: 99-0 值最大优先级最高
  • nice值:-20到19,对应系统优先级100-139

什么是作业?

  • 在Linux中的作业是指正在运行的还没有结束的命令或者任务。Linux是个支持多任务的操作系统, 其允许多个命令同时执行(多核CPU真
    正的同时执行;单核CPU并发执行)。每个正在进行的作业被唯 一的作业号所标识。
  • 要查看和控制作业主要使用以下命令
  • jobs 列出正在运行或者挂起的作业
  • fg 将作业转换为前台作业
  • bg 将作业转换为后台作业
  • stop 挂起作业(Ctrl + z)
  • kill 结束作业(Ctrl + c)
    Linux中某个命令或脚本默认运行时为前台进程,可以使用Ctrl + c结束其;如果发起该命令或者 脚本的终端被关闭,那么该进程也就结
    束;这不利于需要长时间做某事的作业运行,在运行某命令或则 脚本时可以在其后加上&符号来使其以后台作业的方式运行,这样即使使
    用Ctrl+c或者关闭终端也不会 导致作业被终止
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tJpdBK3p-1604371435765)(png/20191027190430520.png)]

Linux任务计划

未来某时间点执行一次的任务

  • 使用at命令来定义未来某时间点执行一次的任务
  • at命令所在包:at

未来周期性执行的任务

  • 计划周期性执行的任务提交给crond,到指定时间会自动运行
  • 系统cron任务:系统维护的周期性作业,位于: /etc/crontab文件内,一般只有root可以更改。
  • 用户cron任务:使用crontab命令来创建用户各自的周期性任务- 日志:/var/log/cron
  • 使用crontab命令来定义周期性计划任务
[root@centos7 ~]#cat /etc/crontab
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
# For details see man 4 crontabs
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
  • 例如:晚上9点10分以用户steve的身份运行echo命令
10 21 * * * steve /bin/echo “Hello there!!
  • 例子:每3小时echo和wall命令
0 */3 * * * steve /bin/echo “Done something!; wall “Done something again!

用户计划任务

  • crontab命令定义
  • 每个用户都有专用的cron任务文件:/var/spool/cron/USERNAME
  • crontab命令:
crontab [-u user] [-l | -r | -e] [-i]
-l 列出所有任务
-e 编辑任务
-r 移除所有任务
-i 同-r一同使用,以交互式模式移除指定任务
-u user 仅root可运行,指定用户管理cron任务

Linux系统性能监控和管理命令

| 命令 | 作用 | 备注 || ------- | ---------------------------------------------------------------------------------------------------------------------------------
----------- | ---- |
| pstree | pstree命令以树状结构显示当前系统进程 | |
| ps | ps是linux系统中查看进程的有力工具之一。man帮助指明其用于报告当前系统所有进程的一个快照
| |
| nice | 使用nice命令指定一个调度优先级来运行某程序 | |
| renice | renice命令可以更改一个正在运行的进程的优先级 | |
| pgrep | pgrep和pkill命令大部分选项相同,也就是大部分功能相同;但是pgrep一般用来基于进程名搜索某进程,
pkill一般基
于进程名来发送相关信号给某进程 | |
| kill | kill命令一般用来结束某进程,但是其还有其他功能,用于发送特定的信号给相关进程来控制某进程
| |
| killall | killall命令单纯的基于进程名来结束某进程 | |
| top | 监控系统进程 | |
| htop | 其使用不同的颜色来标识不同的信息,甚至支持鼠标点击相应的选项;来自EPEL源
| |
| free | 查看内存空间使用情况 | |
| vmstat | 命令查看虚拟内存信息 | |
| iostat | 统计CPU和设备IO信息 | |
| iftop | 显示带宽使用情况 | |
| pmap | 显示某进程对应的内存映射 | |
| dstat | dstat命令用来统计系统资源(代替vmstat和iostat) | |
| iotop | iotop命令用来监视磁盘I/O使用状况 | |
| nload | nload命令查看网络实时吞吐量 | |

  • lsof:列出打开的文件
查看由登陆用户启动而非系统启动的进程
lsof /dev/pts/1
指定进程号,可以查看该进程打开的文件
lsof -p 9527
查看指定程序打开的文件
lsof -c httpd
查看指定用户打开的文件
lsof -u root | more
查看指定目录下被打开的文件
lsof +D /var/log/
lsof +d /var/log/
参数+D为递归列出目录下被打开的文件,参数+d为列出目录下被打开的文件
  • lsof示例
查看所有网络连接
lsof -i –n
lsof -i@127.0.0.1
通过参数-i查看网络连接的情况,包括连接的ip、端口等以及一些服务的连接情况,例如:sshd等。也可以通过
指定ip查看该ip的网络连接情况
查看端口连接情况
lsof -i :80 -n
通过参数-i:端口可以查看端口的占用情况,-i参数还有查看协议,ip的连接情况等查看指定进程打开的网络连接
lsof -i –n -a -p 9527
参数-i、-a、-p等,-i查看网络连接情况,-a查看存在的进程,-p指定进程
查看指定状态的网络连接
lsof -n -P -i TCP -s TCP:ESTABLISHED
-n:no host names, -P:no port names,-i TCP指定协议,-s指定协议状态通过多个参数可以
清晰的查看网络连接情况、协议连接情况等
恢复删除文件
lsof |grep /var/log/messages
rm -f /var/log/messages
lsof |grep /var/log/messages
cat /proc/653/fd/6
况
查看端口连接情况
lsof -i :80 -n
通过参数-i:端口可以查看端口的占用情况,-i参数还有查看协议,ip的连接情况等
查看指定进程打开的网络连接
lsof -i –n -a -p 9527
参数-i、-a、-p等,-i查看网络连接情况,-a查看存在的进程,-p指定进程
查看指定状态的网络连接
lsof -n -P -i TCP -s TCP:ESTABLISHED
-n:no host names, -P:no port names,-i TCP指定协议,-s指定协议状态通过多个参数可以
清晰的查看网络连接情况、协议连接情况等
恢复删除文件
lsof |grep /var/log/messages
rm -f /var/log/messages
lsof |grep /var/log/messages
cat /proc/653/fd/6
cat /proc/653/fd/6 > /var/log/messages

6.5 文本处理三剑客

基本文本处理练习

1、找出ifconfig “网卡名” 命令结果中本机的IPv4地址
ifconfig | head -n2 | tail -n1 | tr -s " " | cut -d" " -f3 # centos7,8
ifconfig | head -n2 | tail -n1 | tr -s " " | cut -d" " -f3 | cut -d: -f2 # centos6
ifconfig | grep -Eo "([0-9]{,3}\.){3}[0-9]{,3}" | head -n1 # centos6,7,8
ifconfig | grep -Eo '\<(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\>' | head -n1
# 精确
2、查出分区空间使用率的最大百分比值
df -h | grep -e /dev/sd -e /dev/nvme | tr -s " " | cut -d" " -f5 | sort -nr | head -n3
3、查出用户UID最大值的用户名、UID及shell类型
getent passwd | sort -t : -k 3 -nr | head -n1 | cut -d: -f1,3,7
getent passwd | sort -t : -k 3 -nr | cut -d: -f1,3,7 | head -n14、查出/tmp的权限,以数字方式显示
stat /tmp/ | head -n4 | tail -n1 | cut -d \( -f2 | cut -d/ -f1
stat /tmp/ | grep -E "(\([0-7]{4})" | grep -Eo [0-9]{4} |head -n1
5、统计当前连接本机的每个远程主机IP的连接数,并按从大到小排序
ss -tun |grep ESTAB | tr -s " " | cut -d" " -f6 | cut -d: -f1 | sort | uniq -c | sort -nr
tr -s " " ":" < ss.log | cut -d: -f6 | sort | uniq -c| sort -nr
ss -tun |grep ESTAB | tr -s " " ":" | cut -d: -f7 | sort | uniq -c | sort -nr

通配符练习

1、显示/var目录下所有以l开头,以一个小写字母结尾,且中间出现至少一位数字的文件或目录
ls -d /var/l*[0-9]*[[:lower:]]
2、显示/etc目录下以任意一位数字开头,且以非数字结尾的文件或目录
ls -d /etc/[[:digit:]]*[^[:digit:]]
3、显示/etc/目录下以非字母开头,后面跟了一个字母及其它任意长度任意字符的文件或目录
ls -d /etc/[^[:alpha:]][[:alpha:]]*
4、显示/etc/目录下所有以rc开头,并后面是0-6之间的数字,其它为任意字符的文件或目录
ls -d /etc/rc[0-6]*
5、显示/etc目录下,所有以.d结尾的文件或目录
ls /etc/*.d -d
6、显示/etc目录下,所有.conf结尾,且以m,n,r,p开头的文件或目录
ls /etc/[mnpr]*.conf -d
7、只显示/root下的隐藏文件和目录
ls -d /root/.* (ls -d .[^.]*)
8、只显示/etc下的非隐藏目录
ls /etc/*/ -d

grep

grep选项

"PATTERN" 为基本正则表达式
grep [OPTIONS] PATTERN [FILE...]
grep [OPTIONS] -e PATTERN ... [FILE...]
grep [OPTIONS] -f FILE ... [FILE...]
-E # 使用扩展正则表达式
-o # 只显示匹配到的字符串而不显示其所在的行的内容
-v # 显示未匹配到的行
-i # 忽略大小写
-q # 静默模式,无论是否匹配到都不打印标准输出
-f file,--file=FILE # 从文件file中读取正则表达式
-e # 使用多个操作来匹配模式
grep -e root -e bash /etc/passwd # 匹配包含root和bash的行
-w # 匹配单词
-c, --count # 匹配到的行计算总数
-s, --no-messages # 对不存在或则不可读文件的错误不打印
-n, --line-number # 对匹配到的行加行号-A NUM, --after-context=NUM # 匹配到某模式时不仅打印匹配到的行还打印其后的NUM行
-B NUM, --before-context=NUM # 匹配到某模式时不仅打印匹配到的行还打印其前的NUM行
-C NUM, -NUM, --context=NUM # 匹配到某模式时不仅打印匹配到的行还打印其前和其后的的NUM行
--color=auto # 对匹配到的文本着色显示
-m # 匹配#次后停止

**正则表达式(REGEXP:Regular Expressions)**是由一类特殊字符及文本字符所编写的模式,其中有些字符(元字符)不表示字符字面意
义,而表示控制或通配的功能
正则表达式引擎:采用不同算法,检查处理正则表达式的软件模块 如:PCRE(Perl Compatible Regular Expressions)

正则表达式元字符分类:

字符匹配
. 匹配任意单个字符
[] 匹配指定范围内的任意单个字符,示例:[wang] [0-9] [a-z] [a-zA-Z]
[^] 匹配指定范围外的任意单个字符
[:alnum:] 字母和数字
[:alpha:] 代表任何英文大小写字符,亦即 A-Z, a-z
[:lower:] 小写字母 [:upper:] 大写字母
[:blank:] 空白字符(空格和制表符)
[:space:] 水平和垂直的空白字符(比[:blank:]包含的范围广)
[:cntrl:] 不可打印的控制字符(退格、删除、警铃...)
[:digit:] 十进制数字 [:xdigit:]十六进制数字
[:graph:] 可打印的非空白字符
[:print:] 可打印字符
[:punct:] 标点符号
匹配次数

用在要指定次数的字符后面,用于指定前面的字符要出现的次数

* 匹配前面的字符任意次,包括0次
贪婪模式:尽可能长的匹配
.* 任意长度的任意字符
\? 匹配其前面的字符0或1次
\+ 匹配其前面的字符至少1次
\{n\} 匹配前面的字符n次
\{m,n\} 匹配前面的字符至少m次,至多n次
\{,n\} 匹配前面的字符至多n次
\{n,\} 匹配前面的字符至少n次
位置锚定```py

^ 行首锚定,用于模式的最左侧
$ 行尾锚定,用于模式的最右侧
^PATTERN$ 用于模式匹配整行
^$ 空行
1*$ 空白行
< 或 \b 词首锚定,用于单词模式的左侧
> 或 \b 词尾锚定,用于单词模式的右侧
<PATTERN> 匹配整个单词

##### 分组
分组:() 将一个或多个字符捆绑在一起,当作一个整体处理如:(root)+
##### 后向引用
引用前面的分组括号中的模式所匹配字符,而非模式本身; 分组括号中的模式匹配到的内容会被正则表达式引擎记录于内部的变量中,这些
变量的命名方式为: \1, \2, \3, … ;
\1 表示从左侧起第一个左括号以及与之匹配右括号之间的模式所匹配到的字符
```bash
示例: \(string1\(string2\)\)
\1 :string1\(string2\)
\2 :string2
或者:\|
a\|b
a或b
C\|cat
C或cat
\(C\|c\)at
Cat或cat
基本正则表达式练习
grep "^[sS].*" /proc/meminfo
grep "^[s]\|^[S].*" /proc/meminfo
grep "^s\|^S.*" /proc/meminfo
2、显示/etc/passwd文件中不以/bin/bash结尾的行
grep -v "\<bin/bash\>$" /etc/passwd
grep -v "\bbin/bash\b$" /etc/passwd
3、显示用户rpc默认的shell程序
getent passwd | grep -w "rpc" | cut -d: -f1,7
grep -w "rpc" /etc/passwd | cut -d: -f1,74、找出/etc/passwd中的两位或三位数
grep --color=auto -o "[0-9]\{2,3\}" /etc/passwd
5、显示CentOS7的/etc/grub2.cfg文件中,至少以一个空白字符开头的且后面有非
空白字符的行
grep "^[[:space:]].*" /etc/grub2.cfg
grep "^ .*" /etc/grub2.cfg
6、找出“netstat -tan”命令结果中以LISTEN后跟任意多个空白字符结尾的行
netstat -tan | grep "LISTEN \+"
7、显示CentOS7上所有UID小于1000以内的用户名和UID
grep --color=auto -v ".*[0-9]\{4\}.*" /etc/passwd | cut -d: -f1,3
8、添加用户bash、testbash、basher、sh、nologin(其shell为/sbin/nologin),找
出/etc/passwd用户名和shell同名的行
grep "^\(\<.*\>\).*/\1$" /etc/passwd
9、利用df和grep,取出磁盘各分区利用率,并从大到小排序
df -h | grep -o "\<[0-9]\{,3\}%" | sort -nr
df -h | grep -o "\<[0-9]\{,3\}%" | sort -nr | cut -d% -f1
扩展正则表达式练习
1、显示三个用户root、mage、wang的uid和默认shell
getent passwd | grep -w -e ^root -e ^mage -e ^wang | cut -d: -f3,7
2、找出/etc/rc.d/init.d/functions文件中行首为某单词(包括下划线)后面跟一
个小括号的行
grep "^\(.*\)_\?\(.*\)()" /etc/rc.d/init.d/functions
3、使用egrep取出/etc/rc.d/init.d/functions中其基名
echo /etc/rc.d/init.d/functions|egrep -o "[^/]+/?$"
4、使用egrep取出上面路径的目录名
echo /etc/rc.d/init.d/functions/ |egrep -o "/.*[^/]"|egrep -o "/.*/"
5、统计last命令中以root登录的每个主机ip地址登录次数
last | grep root | egrep "\b(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\b" | tr -s " "
| cut -d" " -f3 | uniq -c | sort -nr
6、利用扩展正则表达式分别表示0-9、10-99、100-199、200-249、250-255
[0-9]
[1-9][0-9]
1[0-9]{2}
2[0-4][0-9]
25[0-5]
7、显示ifconfig命令结果中所有ipv4地址
ifconfig | egrep "\b(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\b"8、将此字符串:welcome to magedu linux 中的每个字符去重并排序,重复次数多的排到前面
echo "welcome to magedu linux" | grep -o . | sort | uniq -c | sort -nr

SED

sed是一种流编辑器,它一次处理一行内容。处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着
用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。然后读入下行,执行下一个循环。如果没有使诸如‘D’的特殊
命令,那会在两个循环之间清空模式空间,但不会清空保留空间。这样不断重复,直到文件末尾。文件内容并没有改变,除非你使用重定
向存储输出。

基本选项功能和用法

sed [option]... 'script' inputfile...
-n 不输出模式空间内容到屏幕,即不自动打印
-e 多点编辑
-f /PATH/SCRIPT_FILE 从指定文件中读取编辑脚本
-r 支持使用扩展正则表达式
-i.bak 备份文件并原处编辑
script: '地址命令'
注意:sed的脚本、地址定界和命令都一般写在单引号中间
  • 地址定界
(1) 不给地址:对全文进行处理
(2) 单地址:
#:指定的行
$:最后一行
/pattern/:被此处模式所能够匹配到的每一行
(3) 地址范围:
#,#
#,+#
/pat1/,/pat2/ #模式1到模式2之间的行
#,/pat1/
(4) ~:步进
1~2 奇数行
2~2 偶数行
  • 编辑命令
d 删除模式空间匹配的行,并立即启用下一轮循环
p 打印当前模式空间内容,追加到默认输出之后a [\]text 在指定行后面追加文本,支持使用\n实现多行追加
i [\]text 在行前面插入文本
c [\]text 替换行为单行或多行文本
w /path/file 保存模式匹配的行至指定文件
r /path/file 读取指定文件的文本至模式空间中匹配到的行后
= 为模式空间中的行打印行号
! 模式空间中匹配行取反处理
s///
查找替换,支持使用其它分隔符,s@@@,s###
替换标记:
g 行内全局替换
p 显示替换成功的行
w /PATH/FILE 将替换成功的行保存至文件中

示例

sed ‘2p’ /etc/passwd
sed -n ‘2p’ /etc/passwd
sed -n ‘1,4p’ /etc/passwd
sed -n ‘/root/p’ /etc/passwd
sed -n ‘2,/root/p’ /etc/passwd 从2行开始
sed -n ‘/^$/=file 显示空行行号
sed -n -e ‘/^$/p’ -e ‘/^$/=file
sed '/root/a\superman' /etc/passwd 在匹配到的行后追加superman
sed '/root/i\superman' /etc/passwd 在匹配到的行前追加superman
sed '/root/c\superman' /etc/passwd 匹配到的行替换为superman
sed ‘1,10d’ file
nl /etc/passwd | sed ‘2,5d’
nl /etc/passwd | sed ‘2a tea’
sed 's/test/mytest/g' example
sed –n ‘s/root/&superman/p’ /etc/passwd 在root单词后追加superman
sed –n ‘s/root/superman&/p’ /etc/passwd 在root单词前追加superman
sed -e ‘s/dog/cat/’ -e ‘s/hi/lo/’ pets
sed –i.bak ‘s/dog/cat/g’ pets
  • 高级编辑命令
P: 打印模式空间开端至\n内容,并追加到默认输出之前
h: 把模式空间中的内容覆盖至保持空间中
H:把模式空间中的内容追加至保持空间中
g: 从保持空间取出数据覆盖至模式空间
G:从保持空间取出内容追加至模式空间
x: 把模式空间中的内容与保持空间中的内容进行互换
n: 读取匹配到的行的下一行覆盖至模式空间N:读取匹配到的行的下一行追加至模式空间
d: 删除模式空间中的行
D:如果模式空间包含换行符,则删除直到第一个换行符的模式空间中的文本,并不会读取新的输入行,而使用合成的模式空间重新启
动循环。如果模式空间不包含换行符,则会像发出d命令那样启动正常的新循环
高级用法sed示例
sed -n 'n;p' FILE
sed '1!G;h;$!d' FILE
sed‘N;D’FILE
sed '$!N;$!D' FILE
sed '$!d' FILE
sed ‘G’ FILE
sed ‘g’ FILE
sed ‘/^$/d;G’ FILE
sed 'n;d' FILE
sed -n '1!G;h;$p' FILE

练习

1、删除centos7系统/etc/grub2.cfg文件中所有以空白开头的行行首的空白字符
sed -inr 's#^ +(.*)#\1#p' /etc/grub2.cfg
2、删除/etc/fstab文件中所有以#开头,后面至少跟一个空白字符的行的行首的#和空白字符
sed -inr 's#^# +(.*)#\1#p' /etc/grub2.cfg
3、在centos6系统/root/install.log每一行行首增加#号
sed -inr 's#.*#\#&#p' /etc/grub2.cfg
4、在/etc/fstab文件中不以#开头的行的行首增加#号
sed -inr 's#^[^#](.*)#\#\1#p' /etc/grub2.cfg
5、处理/etc/fstab路径,使用sed命令取出其目录名和基名
echo /etc/fstab/ | sed -nr 's#(.*/)([^/]+)/?#\1#p'
echo /etc/fstab/ | sed -nr 's#(.*/)([^/]+)/?#\2#p'
6、利用sed 取出ifconfig命令中本机的IPv4地址
ifconfig eth0|sed -nr '2s@[^0-9]+([.0-9]+).*@\1@p'
7、统计centos安装光盘中Package目录下的所有rpm文件的以.分隔倒数第二个字段的重复次数
ls /misc/cd/{BaseOS,AppStream}/Packages/ | sed -nr 's#.*\.(.*)\.rpm#\1#p' | sort |uniq -c | sort -nr
ls /misc/cd/{BaseOS,AppStream}/Packages/ | rev | cut -d. -f2 | sort | uniq -c | uniq
8、统计/etc/init.d/functions文件中每个单词的出现次数,并排序(用grep和sed两种方法分别实现)
grep -Eow "[^[:punct:]]+([[:alpha:]0-9+])" /etc/init.d/functions | grep -Eow "[[:alpha:]]+|[0-9]+" | wc -l
sed -r "s/[^[:alpha:]]/\n/g" /etc/init.d/functions| sort | uniq -c | sort -nr
9、将文本文件的n和n+1行合并为一行,n为奇数行seq 10 | sed -nr '1~2N;s/\n/ /p'

AWK

基本用法

awk [options] 'program' var=value file…
awk [options] -f programfile var=value file…
awk [options] 'BEGIN{action;… }pattern{action;… }END{action;… }' file ...

工作原理

第一步:执行BEGIN{action;}语句块中的语句
第二步:从文件或标准输入(stdin)读取一行,然后执行pattern{ action;}语句块,
它逐行扫描文件,从第一行到最后一行重复这个过程,直到文件全部被读取完毕。
第三步:当读至输入流末尾时,执行END{action;}语句块
BEGIN语句块在awk开始从输入流中读取行之前被执行,这是一个可选的语句块,
比如变量初始化、打印输出表格的表头等语句通常可以写在BEGIN语句块中
END语句块在awk从输入流中读取完所有的行之后即被执行,比如打印所有行的
分析结果这类信息汇总都是在END语句块中完成,它也是一个可选语句块
pattern语句块中的通用命令是最重要的部分,也是可选的。如果没有提供
pattern语句块,则默认执行{ print },即打印每一个读取到的行,awk读取的每
一行都会执行该语句块

print格式

print item1, item2, …

(1) 逗号分隔符
(2) 输出item可以字符串,也可是数值;当前记录的字段、变量或awk的表达式
(3) 如省略item,相当于print $0
  • 示例:
awk '{print "hello,awk"}'
awk –F: '{print}' /etc/passwd
awk –F: '{print "wang"}' /etc/passwd
awk –F: '{print $1}' /etc/passwd
awk –F: '{print $0}' /etc/passwd
awk –F: '{print $1"\t"$3}' /etc/passwd
grep “?UID”/etc/fstab | awk '{print $2,$4}'
```### awk变量
可以使用内置变量也可以自定义变量
```bash
FS:Field Separator
输入字段分隔符,默认为空白字符
awk -v FS=':' '{print $1,FS,$3}' /etc/passwd
awk –F: '{print $1,$3,$7}' /etc/passwd
OFS:Output Field Separator
输出字段分隔符,默认为空白字符
awk -v FS=':' -v OFS=':' '{print $1,$3,$7}' /etc/passwd
RS:Record Seperator
输入记录分隔符,指定输入时的换行符
awk -v RS=' '{print }’ /etc/passwd
ORS:Output Record Seperator
输出记录分隔符,输出时用指定符号代替换行符
awk -v RS=' ' -v ORS='###' '{print $0}' /etc/passwd
NF:Number Field
字段数量
awk -F:'{print NF}' /etc/fstab 引用变量时,变量前不需加$
awk -F:'{print $(NF-1)}' /etc/passwd
NR:Number Record
记录号
awk '{print NR}' /etc/fstab ; awk END '{print NR}' /etc/fstab
FNR:各文件分别计数,记录号
awk '{print FNR}' /etc/fstab /etc/inittab
FILENAME:当前文件名
awk '{print FILENAME}' /etc/fstab
ARGC:命令行参数的个数
awk '{print ARGC}' /etc/fstab /etc/inittab
awk 'BEGIN {print ARGC}' /etc/fstab /etc/inittab
ARGV:数组,保存的是命令行所给定的各参数
awk 'BEGIN {print ARGV[0]}' /etc/fstab /etc/inittab
awk 'BEGIN {print ARGV[1]}' /etc/fstab /etc/inittab
  • 自定义变量(区分字符大小写)
(1) -v var=value
(2) 在program中直接定义
  • 示例:
awk -v test='hello gawk' '{print test}' /etc/fstab
awk -v test='hello gawk' 'BEGIN{print test}'
awk 'BEGIN{test="hello,gawk";print test}'
awk -F: '{sex=“male”;print $1,sex,age;age=18}' /etc/passwdcat awkscript
{print script,$1,$2}
awk -F: -f awkscript script="awk" /etc/passwd

awk 格式化-printf

格式化输出:printf "FORMAT", item1, item2, ...

(1) 必须指定FORMAT
(2) 不会自动换行,需要显式给出换行控制符,\n
(3) FORMAT中需要分别为后面每个item指定格式符

格式符:与item一一对应

%c:显示字符的ASCII码
%d, %i:显示十进制整数
%e, %E:显示科学计数法数值
%f:显示为浮点数
%g, %G:以科学计数法或浮点形式显示数值
%s:显示字符串
%u:无符号整数
%%:显示%自身

修饰符

#[.#] 第一个数字控制显示的宽度;第二个#表示小数点后精度,%3.1f
- 左对齐(默认右对齐) %-15s
+ 显示数值的正负符号 %+d

printf示例

awk -F: '{printf "%s",$1}' /etc/passwd
awk -F: '{printf "%s\n",$1}' /etc/passwd
awk -F: '{printf "%-20s %10d\n",$1,$3}' /etc/passwd
awk -F: '{printf "Username: %s\n",$1}' /etc/passwd
awk -F: '{printf “Username: %s,UID:%d\n",$1,$3}' /etc/passwd
awk -F: '{printf "Username: %15s,UID:%d\n",$1,$3}' /etc/passwd
awk -F: '{printf "Username: %-15s,UID:%d\n",$1,$3}' /etc/passwd

awk示例

awk '$0~“^root"' /etc/passwd
awk '$0 !~ /root/' /etc/passwd
awk -F: '$3==0' /etc/passwd
awk -F: '$3>=0 && $3<=1000 {print $1}' /etc/passwd
awk -F: '$3==0 || $3>=1000 {print $1}' /etc/passwd
awk -F: '!($3==0) {print $1}' /etc/passwd
awk -F: '!($3>=500) {print $3}' /etc/passwd
awk '/?UID/{print $1}' /etc/fstab
awk '!/?UID/{print $1}' /etc/fstab
awk -F: 'i=1;j=1{print i,j}' /etc/passwd
awk '!0' /etc/passwd ;
awk '!1' /etc/passwd
awk -F: '$3>=1000{print $1,$3}' /etc/passwd
awk -F: '$3<1000{print $1,$3}' /etc/passwd
awk -F: '$NF=="/bin/bash"{print $1,$NF}' /etc/passwd
awk -F: '$NF ~ /bash$/{print $1,$NF}' /etc/passwd
awk -F: ‘/^root\>/,/^nobody\>/{print $1}' /etc/passwd
awk -F: ‘(NR>=10&&NR<=20){print NR,$1}' /etc/passwd
awk -F : 'BEGIN {print "USER USERID"} {print $1":"$3}END{print "END FILE"}' /etc/passwd
awk -F: '{print "USER USERID";print $1":"$3} END{print "END FILE"}' /etc/passwd
awk -F: 'BEGIN{print "USER UID \n--------------- "}{print $1,$3}' /etc/passwd
awk -F: 'BEGIN{print "USER UID \n--------------- "}{print $1,$3}END{print "=============="}' /etc/passwd
seq 10 | awk 'i=0'
seq 10 | awk 'i=1'
seq 10 | awk 'i=!i'
seq 10 | awk '{i=!i;print i}'
seq 10 | awk ‘!(i=!i)'
seq 10 | awk -v i=1 'i=!i'
root@server-ctl:/data/scripts# awk 'BEGIN{i=0;print ++i,i}'
1 1
root@server-ctl:/data/scripts# awk 'BEGIN{i=0;print i++,i}'
0 1
示例:
root@server-ctl:/data/scripts# awk -F: 'BEGIN{printf "SEQ USER UID\n"}(NR>=0&&NR<=5){printf "%d %10s
%6s\n",NR,$1,$3}' /etc/passwd
SEQ USER UID
1 root 0
2 daemon 1
3 bin 2
4 sys 3
5 sync 4
```#### awk if
语法:`if(condition1){statement1}else if(condition2){statement2}else{statement3}`
```bash
awk -F: '{if($3>=1000)print $1,$3}' /etc/passwd
awk -F: '{if($NF=="/bin/bash") print $1}' /etc/passwd
awk '{if(NF>5) print $0}' /etc/fstab
awk -F: '{if($3>=1000) {printf "Common user: %s\n",$1} else {printf "root or Sysuser: %s\n",$1}}' /etc/passwd
awk -F: '{if($3>=1000) printf "Common user: %s\n",$1; else printf "root or Sysuser: %s\n",$1}' /etc/passwd
df -h|awk -F% '/^\/dev/{print $1}'|awk '$NF>=80{print $1,$5}'
awk 'BEGIN{ test=100;if(test>90){print "very good"}
else if(test>60){ print "good"}else{print "no pass"}}'

awk while

awk '/^[[:space:]]*linux16/{i=1;while(i<=NF)
{print $i,length($i); i++}}' /etc/grub2.cfg
awk '/^[[:space:]]*linux16/{i=1;while(i<=NF)
{if(length($i)>=10){print $i,length($i)}; i++}}' /etc/grub2.cfg

do while

awk 'BEGIN{ total=0;i=0;do{ total+=i;i++;}while(i<=100);print i}'

awk for

awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {print $i,length($i)}}' /etc/grub2.cfg

awk 数组

weekdays["mon"]="Monday"
awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";
print weekdays["mon"]}‘
awk '!line[$0]++' dupfile
awk '{!line[$0]++;print $0, line[$0]}' dupfile
awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";for(i in weekdays) {print weekdays[i]}}'
netstat -tan | awk '/^tcp/{state[$NF]++}END{for(i in state) { print i,state[i]}}'
awk '{ip[$1]++}END{for(i in ip) {print i,ip[i]}}' /var/log/httpd/access_log

awk函数- rand():返回0和1之间一个随机数

awk 'BEGIN{srand(); for (i=1;i<=10;i++)print int(rand()*100) }'

  • length([s]):返回指定字符串的长度
  • sub(r,s,[t]):对t字符串搜索r表示模式匹配的内容,并将第一个匹配内容替换为s
    echo "2008:08:08 08:08:08" | awk 'sub(/:/,"-",$1)'
    echo "2008:08:08 08:08:08" | awk '{sub(/:/,"-",$1);print $0}'
  • gsub(r,s,[t]):对t字符串进行搜索r表示的模式匹配的内容,并全部替换为s所表示的内容
    echo "2008:08:08 08:08:08" | awk 'gsub(/:/,"-",$0)'
    echo "2008:08:08 08:08:08" | awk '{gsub(/:/,"-",$0);print $0}'
  • split(s,array,[r]):以r为分隔符,切割字符串s,并将切割后的结果保存至array
    所表示的数组中,第一个索引值为1,第二个索引值为2,…
    netstat -tn | awk '/^tcp\>/{split($5,ip,":");count[ip[1]]++}END{for (i in count) {print i,count[i]}}'

awk使用system命令调用shell命令

七. MySQL

MySQL存储引擎

MySQL支持哪些存储引擎?

SHOW ENGINES\G;查看支持的存储引擎

MariaDB [(none)]> show engines;
+--------------------+---------+----------------------------------------------------------------------------------+--------------+------
+------------+
| Engine | Support | Comment | Transactions | XA | Savepoints |
+--------------------+---------+----------------------------------------------------------------------------------+--------------+------
+------------+
| InnoDB | DEFAULT | Percona-XtraDB, Supports transactions, row-level locking, and foreign keys | YES | YES |
YES |
| MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO |
| MyISAM | YES | Non-transactional engine with good performance and small data footprint | NO | NO |
NO |
| BLACKHOLE | YES | /dev/null storage engine (anything you write to it disappears) | NO | NO | NO
|
| PERFORMANCE_SCHEMA | YES | Performance Schema | NO | NO | NO |
| CSV | YES | Stores tables as CSV files | NO | NO | NO |
| ARCHIVE | YES | gzip-compresses tables for a low storage footprint | NO | NO | NO |
| MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO | NO | NO
|
| FEDERATED | YES | Allows to access tables on other MariaDB servers, supports transactions and more | YES | NO
| YES |
| Aria | YES | Crash-safe tables with MyISAM heritage | NO | NO | NO |
+--------------------+---------+----------------------------------------------------------------------------------+--------------+------
+------------+

最常用的是InnoDB, MySQL5.5版本之后,MySQL的默认内置存储引擎已经是InnoDB

MySQL各存储引擎比较?MyISAM和InnoDB的区别:

  1. InnoDB支持事务,MyISAM不支持。
    对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组
    成一个事务;
  2. InnoDB支持外键约束,而MyISAM不支持。
  3. InnoDB是聚集索引,使用B+Tree作为索引结构,数据文件是和(主键)索引绑在一起的(表数据文件本身就是按B+Tree组织的一个
    索引结构),必须要有主键,通过主键索引效率很高。MyISAM是非聚集索引,也是使用B+Tree作为索引结构,索引和数据文件是分离
    的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
  4. InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上
    述语句时只需要读出该变量即可,速度很快。
  5. Innodb不支持全文索引,而MyISAM支持全文索引,查询效率上MyISAM要高;5.7以后的InnoDB支持全文索引了。
  6. InnoDB支持表、行级锁(默认),而MyISAM支持表级锁。
  7. InnoDB表必须有主键(用户没有指定的话会自己找或生产一个主键),而Myisam可以没有。
  8. Innodb存储文件有frm、ibd,而Myisam是frm、MYD、MYI。
  9. InnoDB读写阻塞与事务隔离级别相关,MyISAM读写相互阻塞,写入不能读,读时不能写。
  10. InnoDB从MySQL5.5后支持全文索引,从MySQL5.5.5开始为默认的数据库引擎
    Innodb:frm是表定义文件,ibd是数据文件。Myisam:frm是表定义文件,myd是数据文件,myi是索引文件。

存储引擎文件说明?

Innodb:frm是表定义文件,ibd是数据文件。所有InnoDB表的数据和索引放置于同一个表空间中
表空间文件:datadir定义的目录下
数据文件:ibddata1, ibddata2, …
每个表单独使用一个表空间存储表的数据和索引
两类文件放在对应每个数据库独立目录中

  • 数据文件(存储数据和索引):tb_name.ibd
  • 表格式定义:tb_name.frm
    启用:innodb_file_per_table=ON (MariaDB 5.5以后版是默认值)
    Myisam:frm是表定义文件,myd是数据文件,myi是索引文件。
  • tbl_name.frm 表格式定义
  • tbl_name.MYD 数据文件
  • tbl_name.MYI 索引文件

适用场景

MyISAM

只读(或者写较少)、表较小(可以接受长时间进行修复操作)

InnoDB

大部分场景## 索引
Mysql选用B+树这种数据结构作为索引,可以提高查询索引时的磁盘IO效率,并且可以提高范围查询的效率,并且B+树里的元素也是有
序的。

InnoDB和MyISAM引擎的B+树索引

通常我们认为B+树的非叶子节点不存储数据,只有叶子节点才存储数据;而B树的非叶子和叶子节点都会存储A据,会导致非叶子节点存
储的索引值会更少,树的高度相对会比B+树高,平均的I/O效率会比较低,所以使用B+树作为索引的数据结构,再加上B+树的叶子节点
之间会有指针相连,也方便进行范围查找。
对于B+树在MyISAM中来说, MyISAM中叶子节点的数据区域存储的是数据记录的地址 MyISAM存储引擎在使用索引查询数据时,
会先根据索引查找到数据地址,再根据地址查询到具体的数据。并且主键索引和辅助索引没有太多区别。
Innodb中的主键索引和实际数据时绑定在一起的,也就是说Innodb的一个表一定要有主键索引,如果一个表没有手动建立主键索引,
Innodb会查看有没有唯一索引,如果有则选用唯一索引作为主键索引,如果连唯一索引也没有,则会默认建立一个隐藏的主键索引(用户
不可见)。另外,Innodb的主键索引要比MyISAM的主键索引查询效率要高(少一次磁盘IO),并且比辅助索引也要高很多。所以,我
们在使用Innodb作为存储引擎时,我们最好:

  1. 手动建立主键索引
  2. 尽量利用主键索引查询

B+树为什么一个节点为1页(16k)就够了?

对着上面Mysql中Innodb中对B+树的实际应用(主要看主键索引),可以发现B+树中的一个节点存储的内容是:

  • 非叶子节点:主键+指针
  • 叶子节点:数据
    那么,假设我们一行数据大小为1K,那么一页就能存16条数据,也就是一个叶子节点能存16条数据;再看非叶子节点,假设主键ID为
    bigint类型,那么长度为8B,指针大小在Innodb源码中为6B,一共就是14B,那么一页里就可以存储16K/14=1170个(主键+指针),那么
    一颗高度为2的B+树能存储的数据为:117016=18720条,一颗高度为3的B+树能存储的数据为:11701170*16=21902400(千万级
    条)。所以在InnoDB中B+树高度一般为1-3层,它就能满足千万级的数据存储。在查找数据时一次页的查找代表一次IO,所以通过主键
    索引查询通常只需要1-3次IO操作即可查找到数据。所以也就回答了我们的问题,1页=16k这么设置是比较合适的,是适用大多数的企业
    的,当然这个值是可以修改的,所以也能根据业务的时间情况进行调整。

索引面试题

Hash索引和B+树所有有什么区别或者说优劣呢?

首先要知道Hash索引和B+树索引的底层实现原理:
hash索引底层就是hash表,进行查找时,调用一次hash函数就可以获取到相应的键值,之后进行回表查询获得实际数据.B+树底层实现是多路
平衡查找树.对于每一次的查询都是从根节点出发,查找到叶子节点方可以获得所查键值,然后根据查询判断是否需要回表查询数据.

  • hash索引进行等值查询更快(一般情况下),但是却无法进行范围查询因为在hash索引中经过hash函数建立索引之后,索引的顺序与原顺序无法保持一致,不能支持范围查询.而B+树的的所有节点皆遵循(左节点
    小于父节点,右节点大于父节点,多叉树也类似),天然支持范围.
  • hash索引不支持使用索引进行排序,原理同上.
  • hash索引不支持模糊查询以及多列索引的最左前缀匹配.原理也是因为hash函数的不可预测.AAAAAAAAB的索引没有相关性.
  • hash索引任何时候都避免不了回表查询数据,而B+树在符合某些条件(聚簇索引,覆盖索引等)的时候可以只通过索引完成查询.
  • hash索引虽然在等值查询上较快,但是不稳定.性能不可预测,当某个键值存在大量重复的时候,发生hash碰撞,此时效率可能极差.而B+树的
    查询效率比较稳定,对于所有的查询都是从根节点到叶子节点,且树的高度较低.
B+ Tree索引和Hash索引区别?
  • 哈希索引适合等值查询,但是无法进行范围查询
  • 哈希索引没办法利用索引完成排序
  • 哈希索引不支持多列联合索引的最左匹配规则
  • 如果有大量重复键值的情况下,哈希索引的效率会很低,因为存在哈希碰撞问题
  • 而B+ 树是一种多路平衡查询树,所以他的节点是天然有序的(左子节点小于父节点、父节点小于右子节点),所以对于范围查询的时
    候不需要做全表扫描
B+ Tree的叶子节点都可以存哪些东西?

InnoDB的B+ Tree可能存储的是整行数据,也有可能是主键的值
两者有什么区别
在 InnoDB 里,索引B+ Tree的叶子节点存储了整行数据的是主键索引,也被称之为聚簇索引。而索引B+ Tree的叶子节点存储了主键的
值的是非主键索引,也被称之为非聚簇索引
在B+树的索引中,叶子节点可能存储了当前的key值,也可能存储了当前的key值以及整行的数据,这就是聚簇索引和非聚簇索引. 在InnoDB
中,只有主键索引是聚簇索引,如果没有主键,则挑选一个唯一键建立聚簇索引.如果没有唯一键,则隐式的生成一个键来建立聚簇索引.
当查询使用聚簇索引时,在对应的叶子节点,可以获取到整行数据,因此不用再次进行回表查询.

聚簇索引和非聚簇索引,在查询数据的时候有区别吗?

聚簇索引查询会更快 ;聚簇索引不是一种单独的索引类型,而是一种数据的存储方式,聚簇索引的顺序,就是数据在硬盘上的物理顺序。
在mysql通常聚簇索引是主键的同义词,每张表只包含一个聚簇索引(其他数据库不一定)。

为什么聚簇索引查询会更快 ?

因为主键索引树的叶子节点直接就是我们要查询的整行数据了。而非主键索引的叶子节点是主键的值,查到主键的值以后,还需要再通过
主键的值再进行一次查询
聚簇索引的优点:
可以把相关数据保存在一起,如:实现电子邮箱时,可以根据用户ID来聚集数据,这样只需要从磁盘读取少量的数据页就能获取某个用户
全部邮件,如果没有使用聚集索引,则每封邮件都可能导致一次磁盘IO。数据访问更快,聚集索引将索引和数据保存在同一个btree中,因此从聚集索引中获取数据通常比在非聚集索引中查找要快。使用覆盖索引
扫描的查询可以直接使用页节点中的主键值。
聚簇索引的缺点:
聚簇数据最大限度地提高了IO密集型应用的性能,但如果数据全部放在内存中,则访问的顺序就没有那么重要了,聚集索引也没有什么优
势了
插入速度严重依赖于插入顺序,按照主键的顺序插入是加载数据到innodb表中速度最快的方式,但如果不是按照主键顺序加载数据,那么
在加载完成后最好使用optimize table命令重新组织一下表。
更新聚集索引列的代价很高,因为会强制innodb将每个被更新的行移动到新的位置。
基于聚集索引的表在插入新行,或者主键被更新导致需要移动行的时候,可能面临页分裂的问题,当行的主键值要求必须将这一行插入到
某个已满的页中时,存储引擎会将该页分裂成两个页面来容纳该行,这就是一次页分裂操作,页分裂会导致表占用更多的磁盘空间。聚集
索引可能导致全表扫描变慢,尤其是行比较稀疏,或者由于页分裂导致数据存储不连续的时候。

非主键索引(非聚簇索引)一定会查询多次吗?

使用覆盖索引可以实现只回表一次;也就是说取决于查询语句所要求的字段是否全部命中了索引,如果全部命中了索引,那么就不必再进行回
表查询.
举个简单的例子,假设我们在员工表的年龄上建立了索引,那么当进行select age from employee where age < 20的查询时,在索引的叶子
节点上,已经包含了age信息,不会再次进行回表查询.

覆盖索引?

覆盖索引(covering index)指一个查询语句的执行只用从索引中就能够取得(查询语句所要求的字段是否全部命中了索引),不必从数
据表中读取。也可以称之为实现了索引覆盖。
当一条查询语句符合覆盖索引条件时,MySQL只需要通过索引就可以返回查询所需要的数据,这样避免了查到索引后再返回表操作,减少
I/O提高效率。

创建联合索引时需要做联合索引的多个字段之间的顺序是如何选择的?

在创建多列索引时,根据业务需求,where子句中使用最频繁的一列放在最左边,因为MySQL索引查询会遵循最左前缀匹配的原则,即最
左优先,在检索数据时从联合索引的最左边开始匹配。所以当我们创建一个联合索引的时候,如(key1,key2,key3),相当于创建了
(key1)、(key1,key2)和(key1,key2,key3)三个索引,这就是最左匹配原则

在建立索引的时候,都有哪些需要考虑的因素呢?

建立索引的时候一般要考虑到字段的使用频率,经常作为条件进行查询的字段比较适合.如果需要建立联合索引的话,还需要考虑联合索引中
的顺序.此外也要考虑其他方面,比如防止过多的所有对表造成太大的压力.这些都和实际的表结构以及查询方式有关.

联合索引是什么?为什么需要注意联合索引中的顺序?

MySQL可以使用多个字段同时建立一个索引,叫做联合索引.在联合索引中,如果想要命中索引,需要按照建立索引时的字段顺序挨个使用,否
则无法命中索引.
具体原因为:MySQL使用索引时需要索引有序,假设现在建立了"name,age,school"的联合索引,那么索引的排序为: 先按照name排序,如果name相同,则
按照age排序,如果age的值也相等,则按照school进行排序.
当进行查询时,此时索引仅仅按照name严格有序,因此必须首先使用name字段进行等值查询,之后对于匹配到的列而言,其按照age字段严格
有序,此时可以使用age字段用做索引查找,以此类推.因此在建立联合索引的时候应该注意索引列的顺序,一般情况下,将查询需求频繁或者字
段选择性高的列放在前面.此外可以根据特例的查询或者表结构进行单独的调整.

MySQL 5.6中,对索引做了哪些优化?

Index Condition Pushdown:索引下推, MySQL 5.6引入了索引下推优化,默认开启,使用SET optimizer_switch =
‘index_condition_pushdown=off’;可以将其关闭。 有了索引下推优化,可以在有like条件查询的情况下,减少回表次数。

有什么手段可以知道查询有没有走索引?

通过explain查看sql语句的执行计划,通过执行计划来分析索引使用情况
MySQL提供了explain命令来查看语句的执行计划,MySQL在执行某个语句之前,会将该语句过一遍查询优化器,之后会拿到对语句的分析,也
就是执行计划,其中包含了许多信息. 可以通过其中和索引有关的信息来分析是否命中了索引,例如possilbe_key,key,key_len等字段,分别说
明了此语句可能会使用的索引,实际使用的索引以及使用的索引长度.

  • 可以通过EXPLAIN来分析索引的有效性,获取查询的执行计划信息,以及用来查看查询优化器是如何执行查询的。
  • EXPLAIN 用法 EXPLAIN SELECT clause
    EXPLAIN输出信息说明 :
    | type | 关联类型或访问类型,即MySQL决定的如何去查询表中的行的方式 |
    | ---- | --------------------------------------------------------- |
    | | |
    | Extra | 额外信息 |
    | ----- | ---------------------------------------------------------- |
    | | Using index:MySQL将会使用覆盖索引,以避免访问表 |
    | | Using where:MySQL服务器将在存储引擎检索后,再进行一次过滤 |
    | | Using temporary:MySQL对结果排序时会使用临时表 |
    | | Using filesort:对结果使用一个外部索引排序 |
  • type列显示的是访问类型,其值有多个。是较为重要的一个指标,结果值从好到坏依次是: system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
  • 一般来说,得保证查询至少达到range级别,最好能达到ref
    type列每个值的意义:
    | 值 | 意义
    |
    | ------ | ----------------------------------------------------------------------------------------------------------------------------------
    -------------------------------------------------------------------------------------------------------------------------- |
    | All | 最坏的情况,全表扫描
    |
    | index | 和全表扫描一样。只是扫描表的时候按照索引次序进行而不是行。主要优点就是避免了排序, 但是开销仍然非常大。如在Extra列
    看到Using index,说明正在使用覆盖索引,只扫描索引的数据,它比按索引次序全表扫描的开销要小很多|
    | range | 范围扫描,一个有限制的索引扫描。key 列显示使用了哪个索引。当使用=、 <>、>、>=、<、<=、IS NULL、<=>、
    BETWEEN 或者 IN 操作符,用常量比较关键字列时,可以使用 range |
    | ref | 一种索引访问,它返回所有匹配某个单个值的行。此类索引访问只有当使用非唯一性索引或唯一性索引非唯一性前缀时才会发
    生。这个类型跟eq_ref不同的是,它用在关联操作只使用了索引的最左前缀,或者索引不是UNIQUE和PRIMARY KEY。ref可以用于使用=
    或<=>操作符的带索引的列。 |
    | eq_ref | 最多只返回一条符合条件的记录。使用唯一性索引或主键查找时会发生 (高效)
    |
    | const | 当确定最多只会有一行匹配的时候,MySQL优化器会在查询前读取它而且只读取一次,因此非常快。当主键放入where子句
    时,mysql把这个查询转为一个常量(高效) |
    | system | 这是const连接类型的一种特例,表仅有一行满足条件。
    |
那么在哪些情况下会发生针对该列创建了索引但是在查询的时候并没有使用呢?

以下情况,MySQL无法使用索引

  • 使用不等于查询,
  • 列参与了数学运算或者函数
  • 在字符串like时左边是通配符.类似于’%aaa’.
  • 当mysql分析全表扫描比使用索引快的时候不使用索引.
  • 当使用联合索引,前面一个条件为范围查询,后面的即使符合最左前缀原则,也无法使用索引.
查询优化器?

一条SQL语句的查询,可以有不同的执行方案,至于最终选择哪种方案,需要通过优化器进行选择,选择执行成本最低的方案。
在一条单表查询语句真正执行之前,MySQL的查询优化器会找出执行该语句所有可能使用的方案,对比之后找出成本最低的方案
这个成本最低的方案就是所谓的执行计划。优化过程大致如下:
1、根据搜索条件,找出所有可能使用的索引
2、计算全表扫描的代价
3、计算使用不同索引执行查询的代价
4、对比各种执行方案的代价,找出成本最低的那一个

查询缓存

查询缓存原理

缓存SELECT操作或预处理查询的结果集和SQL语句,当有新的SELECT语句或预处理查询语句请求,
先去查询缓存,判断是否存在可用的记录集,判断标准:当前的查询与缓存的SQL语句,是否完全一样,区分大小写
查询缓存的优缺点:

  • 不需要对SQL语句做任何解析和执行,当然语法解析必须通过在先,直接从Query Cache中获得查询
    结果,提高查询性能
  • 查询缓存的判断规则,不够智能,也即提高了查询缓存的使用门槛,降低效率
  • 查询缓存的使用,会增加检查和清理Query Cache中记录集的开销哪些查询可能不会被缓存:
  • 查询语句中加了SQL_NO_CACHE参数
  • 查询语句中含有获得值的函数,包含自定义函数,如:NOW() ,CURDATE()、GET_LOCK()、
  • RAND()、CONVERT_TZ()等
  • 对系统数据库的查询:mysql、information_schema 查询语句中使用SESSION级别变量或存储过程中的局部变量
  • 查询语句中使用了LOCK IN SHARE MODE、FOR UPDATE的语句,查询语句中类似SELECT …INTO 导出数据的语句
  • 对临时表的查询操作;存在警告信息的查询语句;不涉及任何表或视图的查询语句;某用户只有列级别权限的查询语句
  • 事务隔离级别为Serializable时,所有查询语句都不能缓存
    查询缓存相关的服务器变量:
  • query_cache_min_res_unit:查询缓存中内存块的最小分配单位,默认4k,较小值会减少浪费,
    但会导致更频繁的内存分配操作,较大值会带来浪费,会导致碎片过多,内存不
  • query_cache_limit:单个查询结果能缓存的最大值,默认为1M,对于查询结果过大而无法缓存的
    语句,建议使用SQL_NO_CACHE
  • query_cache_size:查询缓存总共可用的内存空间;单位字节,必须是1024的整数倍,最小值
    40KB,低于此值有警报
  • query_cache_wlock_invalidate:如果某表被其它的会话锁定,是否仍然可以从查询缓存中返回结
    果,默认值为OFF,表示可以在表被其它会话锁定的场景中继续从缓存返回数据;ON则表示不允
  • query_cache_type:是否开启缓存功能,取值为ON, OFF, DEMAND

并发控制

锁机制

几乎所有需要处理并发读/写的系统都会实现相应的锁系统,一般锁系统包含两类锁:共享锁(shared locks)排他锁(exclusive
locks)
, 或者叫读锁(read locks)写锁(write locks)
乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段
乐观锁: 总是假设最好的情况,认为竞争总是不存在,每次拿数据的时候都认为不会被修改,因此不会先上锁,在最后更新的时候比
较数据有无更新,可通过版本号或CAS实现。 乐观锁(optimistic locking)表现出大胆、务实的态度。使用乐观锁的前提是, 实际应用
当中,发生冲突的概率比较低。他的设计和实现直接而简洁。 目前Web应用中,乐观锁的使用占有绝对优势。
乐观锁使用场景: 用于读比较多的情况,避免了不必要的加锁的开销
悲观锁: 总是假设最坏的情况,认为竞争总是存在,每次拿数据的时候都认为会被修改,因此每次都会先上锁。其他线程阻塞等待释
放锁。 正如其名字,悲观锁(pessimistic locking)体现了一种谨慎的处事态度。其流程如下:

  • 在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。
  • 如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。 具体响应方式由开发者根据实际需要决定。
  • 如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。
  • 其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。
    悲观锁确实很严谨,有效保证了数据的一致性,在C/S应用上有诸多成熟方案。 但是他的缺点与优点一样的明显:
  • 悲观锁适用于可靠的持续性连接,诸如C/S应用。 对于Web应用的HTTP连接,先天不适用。
  • 锁的使用意味着性能的损耗,在高并发、锁定持续时间长的情况下,尤其严重。 Web应用的性能瓶颈多在数据库处,使用悲观锁,进一
    步收紧了瓶颈。- 非正常中止情况下的解锁机制,设计和实现起来很麻烦,成本还很高。
  • 不够严谨的设计下,可能产生莫名其妙的,不易被发现的, 让人头疼到想把键盘一巴掌碎的死锁问题。
    总体来看,悲观锁不大适应于Web应用。
    悲观锁使用场景:用于写比较多的情况,避免了乐观锁不断重试从而降低性能
    注意:乐观锁不能与数据库中的读锁、写锁、行级锁和表级锁等混为一谈。
    锁粒度(Lock Granularity):一种提高共享资源的并发性的做法是让资源具有更多的选择性,或者说以更小的粒度来区分资源;只对包
    含 需要改变的数据的部分进行加锁,而不是锁定整个资源块。但是锁是需要占用系统资源,需要开销的机制; 每个锁操作如:获取锁、检
    查某个锁是否是可用、释放锁等多种都会有额外开销。如果系统花费大量资源 在处理锁机制上而不实实在在的存储数据,那就会适得其
    反,拖累系统。
  • 行级锁:InnoDB存储引擎提供到行级别的锁机制,也就是可以针对某个表的某个行或者某些行施加锁。
  • 表级锁:MyISAM存储引擎提供到表级别的锁机制,也就是可以针对某个表施加锁。
  • 另外在MySQL中支持MVCC:Multiversion Concurrency Control,其允许多个操作执行在某个表的同一行。

MVCC

可以认为MVCC是行级锁的一个变种,它能在大多数情况下避免加锁操作,因此开销更低。无论怎样实现,它们大都实现了非阻塞的读操
作,写操作也只锁定指定的行。MVCC是通过保存数据在某一个时间点的快照来实现的,也就是说无论事务执行多久,每个事务看到的数
据都是一致的。
MVCC(多版本并发控制机制)只在REPEATABLE READ和READ COMMITTED两个隔离级别下工作。其 他两个隔离级别都和MVCC不
兼容,因为READUNCOMMITTED总是读取最新的数据行,而不是符合当前 事务版本的数据行。而SERIALIZABLE则会对所有读取的行都加

事务

在MySQL中,事务(Transactions)表示一组原子性的SQL语句,或一个独立工作单元

事务隔离级别

隔离级别说明
--------------------------------
READ UNCOMMITTED可读取到未提交数据,产生脏读(Dirty reads)
READ COMMITTED可读取到提交数据,但未提交数据不可读,产生不可重复读(Non-repeatable reads),即可读取到多个提交数
据,导致每次读取数据不一致
REPEATABLE READ可重复读,多次读取数据都一致,产生幻读(Phantom reads),即读取过程中,即使有其它提交的事务修改数
据,仍只能读取到未修改前的旧数据。此为MySQL默认设置
SERIALIZABLE可串行化,未提交的读事务阻塞修改事务(加读锁,但不阻塞读事务),或者未提交的修改事务阻塞读事务(加写
锁,其它事务的读,写都不可以执行)。会导致并发性能差
服务器变tx_isolation指定,默认为REPEATABLE-READ,可在GLOBAL和SESSION级进行设置 SET tx_isolation='READ?UNCOMMITTED|READ-COMMITTED|REPEATABLE-READ|SERIALIZABLE'

MySQL并发控制面试题

什么是事务?理解什么是事务最经典的就是转账的栗子,相信大家也都了解,这里就不再说一边了.

事务是一系列的操作,他们要符合ACID特性.最常见的理解就是:事务中的操作要么全部成功,要么全部失败.但是只是这样还不够的.

ACID是什么?可以详细说一下吗?

A=Atomicity
原子性,就是上面说的,要么全部成功,要么全部失败.不可能只执行一部分操作.
C=Consistency
系统(数据库)总是从一个一致性的状态转移到另一个一致性的状态,不会存在中间状态.
I=Isolation
隔离性: 通常来说:一个事务在完全提交之前,对其他事务是不可见的.注意前面的通常来说加了红色,意味着有例外情况.
D=Durability
持久性,一旦事务提交,那么就永远是这样子了,哪怕系统崩溃也不会影响到这个事务的结果.

同时有多个事务在进行会怎么样呢?

多事务的并发进行一般会造成以下几个问题:

  • 脏读: A事务读取到了B事务未提交的内容,而B事务后面进行了回滚.
  • 不可重复读: 当设置A事务只能读取B事务已经提交的部分,会造成在A事务内的两次查询,结果竟然不一样,因为在此期间B事务进行了提交
    操作.
  • 幻读: A事务读取了一个范围的内容,而同时B事务在此期间插入了一条数据.造成"幻觉".

怎么解决并发带来的问题呢?MySQL的事务隔离级别了解吗?

MySQL的四种隔离级别如下:
未提交读(READ UNCOMMITTED)
这就是上面所说的例外情况了,这个隔离级别下,其他事务可以看到本事务没有提交的部分修改.因此会造成脏读的问题(读取到了其他事务未
提交的部分,而之后该事务进行了回滚).这个级别的性能没有足够大的优势,但是又有很多的问题,因此很少使用.
已提交读(READ COMMITTED)
其他事务只能读取到本事务已经提交的部分.这个隔离级别有 不可重复读的问题,在同一个事务内的两次读取,拿到的结果竟然不一样,因为另
外一个事务对数据进行了修改.
REPEATABLE READ(可重复读)
可重复读隔离级别解决了上面不可重复读的问题(看名字也知道),但是仍然有一个新问题,就是 幻读,当你读取id> 10 的数据行时,对涉及到
的所有行加上了读锁,此时例外一个事务新插入了一条id=11的数据,因为是新插入的,所以不会触发上面的锁的排斥,那么进行本事务进行下
一次的查询时会发现有一条id=11的数据,而上次的查询操作并没有获取到,再进行插入就会有主键冲突的问题.SERIALIZABLE(可串行化)
这是最高的隔离级别,可以解决上面提到的所有问题,因为他强制将所以的操作串行执行,这会导致并发性能极速下降,因此也不是很常用.

Innodb使用的是哪种隔离级别呢?

InnoDB默认使用的是可重复读隔离级别.

对MySQL的锁了解吗?

当数据库有并发事务的时候,可能会产生数据的不一致,这时候需要一些机制来保证访问的次序,锁机制就是这样的一个机制.就像酒店的房间,
如果大家随意进出,就会出现多人抢夺同一个房间的情况,而在房间上装上锁,申请到钥匙的人才可以入住并且将房间锁起来,其他人只有等他
使用完毕才可以再次使用.

MySQL都有哪些锁呢?

从锁的类别上来讲,有共享锁和排他锁.
共享锁: 又叫做读锁. 当用户要进行数据的读取时,对数据加上共享锁.共享锁可以同时加上多个.
排他锁: 又叫做写锁. 当用户要进行数据的写入时,对数据加上排他锁.排他锁只可以加一个,他和其他的排他锁,共享锁都相斥.
用上面的例子来说就是用户的行为有两种,一种是来看房,多个用户一起看房是可以接受的. 一种是真正的入住一晚,在这期间,无论是想入住的
还是想看房的都不可以.
锁的粒度取决于具体的存储引擎**,InnoDB实现了行级锁,页级锁,表级锁.**MyISAM锁粒度只到表级锁
他们的加锁开销从大大小,并发能力也是从大到小.

日志管理

MySQL支持丰富的日志类型,包括:

日志类型说明
事务日志transaction log 事务日志的写入类型为“追加”,因此其操作为“顺序IO”;通常也被称为:预写式日志 write ahead
logging
事务日志文件: /var/lib/mysql/ib_logfile0, /var/lib/mysql/ib_logfile1
错误日志error log
通用日志general log
慢查询日志slow query log
二进制日志binary log
中继日志reley log,在主从复制架构中,从服务器用于保存从主服务器的二进制日志中读取的事件
  • 事务日志(transaction log)是事务型存储引擎(InnoDB)自行管理和使用的一种日志,默认在MySQL的数据库所在的文件夹; 实际使用时
    建议和数据文件分开存放
  • 事务日志又分为redo log和undo log

InnoDB事务日志配置

  • InnoDB事务配置项 `SHOW VARIABLES LIKE ‘%innodb_log%’;````ruby
    innodb_log_file_size 50331648 # 定义每个日志文件大小
    innodb_log_files_in_group 2 # 日志组成员个数
    innodb_log_group_home_dir ./ # 事务文件路径,该相对路径是相对于mysql数据库所在目录
    innodb_flush_log_at_trx_commit=0|1|2 # 决定innodb在多I/O操作的事务进行过程中ACID符合度和系统性能的参数默认为1
- 配置innodb处理事务日志的行为优化性能` innodb_flush_log_at_trx_commit `:
![](png/20200410095101415.png)
```bash
1: 默认情况下,日志缓冲区将写入日志文件,并在每次事务后执行刷新到磁盘。 完全遵守ACID特性
innodb_flush_log_at_trx_commit设置为1,同时配合sync_binlog = 1表示最高级别的容错能力
另一项参数:innodb_use_global_flush_log_at_trx_commit的值确定是否可以使用SET语句重置innodb_flush_log_at_trx_commit变
量
0: 提交时没有写磁盘的操作; 而是每秒执行一次将日志缓冲区的提交的事务写入刷新到磁盘。 这样可提
供更好的性能,但服务器崩溃可能丢失最后一秒的事务
2: 每次提交后都会写入OS的缓冲区但是不立即3刷新到磁盘,每秒才会进行一次刷新到磁盘文件中。 性能
比值为0略差一些,但操作系统或停电可能导致最后一秒的交易丢失
3: 模拟MariaDB 5.5组提交(每组提交3个同步),此项MariaDB 10.0支持

错误日志为mysqld启动和关闭过程中输出的事件信息、mysqld运行中产生的错误信息、event scheduler 运行一个event时产生的日
志信息及在主从复制架构中的从服务器上启动从服务器线程时产生的信息
通用日志用于记录对数据库的通用操作,包括错误的SQL语句,通用日志可以保存在文件或者数据库中:file(默认值)或
table(mysql.general_log)
慢查询日志用来记录SQL语句执行时长超出指定时长的操作,这样有利于找到执行速度较慢的语句,以便针对性 优化查询性能。

slow_query_log=ON|OFF #开启或关闭慢查询
long_query_time=N #慢查询的阀值,单位秒
slow_query_log_file=HOSTNAME-slow.log #慢查询日志文件
log_slow_filter = admin,filesort,filesort_on_disk,full_join,full_scan,
query_cache,query_cache_miss,tmp_table,tmp_table_on_disk
#上述查询类型且查询时长超过long_query_time,则记录日志
log_queries_not_using_indexes=ON #不使用索引或使用全索引扫描,不论是否达到慢查询阀值的语
句是否记录日志,默认OFF,即不记录
log_slow_rate_limit = 1 #多少次查询才记录,mariadb特有
log_slow_verbosity= Query_plan,explain #记录内容
log_slow_queries = OFF 同slow_query_log #MariaDB 10.0/MySQL 5.6.1 版后已删除

profile 工具用来显示指明某SQL语句在当前session执行过程中所使用的资源情况的信息。可以使用下面的句 子开启profile功能。其
显示的SQL语句都是在最近发送给服务器执行的,显示SQL语句的多少由 profiling_history_size会话及参数确定。默认为100,设置为0
失能profile功能。

二进制日志- 二进制日志用来记录导致数据改变或潜在导致数据改变的SQL语句,也记录已提交的日志;其不依赖于存储引擎类型

  • 功能:通过“重放”日志文件中的事件来生成数据副本,是数据库恢复必不可少的日志文件
  • 由于二进制日志文件及其重要,建议将二进制日志和数据文件分开存放,最好放到不同磁盘;备份好的二进制文件 放在远程服务器。

二进制日志记录的三种格式

说明
STATEMENT基于"语句"记录:记录语句,默认模式(MariaDB 10.2.3 版本以下),日志量较少
ROW基于"行"记录:记录数据,日志量较大
MIXED混合模式:让系统自行判定该基于哪种方式进行,默认模式(MariaDB 10.2.4及版本以上)

**STATEMENT-BASED:**当使用基于STATEMENT(语句)的二进制日志时,从主到从的复制通过主服务器将SQL语句 写入二进制log。从
服务器将二进制记录的SQL语句在其数据运行来生成原来数据库,该行为对应于基于statement 的二进制日志。
**ROW-BASED:**当使用基于ROW(行/记录)的二进制日志时,主服务器将事务写入二进制log,意味这可以精确的 确定每个表的某个行
是如何变更的。主到从的复制通过将反映每个表的行更改的事务复制到到从服务器并执行 生成原来的表和数据库,该过程就对应于基于
ROW(行/记录)的二进制日志。
**MIXED:**当指定为基于MIXED的二进制日志格式时,默认是使用基于STATEMENT的记录格式;二进制日志自动切换为ROW- BASED
时取决于特定的SQL语句和特定的存储引擎。
MySQL 5.7.7 之前, 基于STATEMENT的二进制格式为默认格式;MySQL 5.7.7 及更新的版本使用ROW-BASED格式为 默认。

  • 二进制文件由两种文件构成,日志文件和索引文件:一个或多个二进制日志文件,一个索引文件;日志文件存放真正的 二进制日志,索
    引文件存放目前的所有二进制文件的文件名列表。
  • 日志文件:mysql|mariadb-bin.000* #二进制格式 如:mariadb-bin.000001*
  • 索引文件:mysql|mariadb-bin.index # 文本格式
    | 变量 | 含义 |
    | ----------------------------------- | --------------------------------------------------------------------------------------------------- |
    | sql_log_bin=ON|OFF | 是否记录二进制日志,默认ON |
    | log_bin=/PATH/TO/BIN_LOG_FILE | 指定文件位置;默认OFF,表示不启用二进制日志功能,上面的项也开启才可指定二进制日志
    位置并生效 |
    | binlog_format=STATEMEN|TROW|MIXED | 二进制日志记录的格式 |
    | max_binlog_size=1073741824 | 单个二进制日志文件的最大体积,到达最大值会自动滚动,默认为1G;文件达到上限时的大小未
    必为指定的精确值 |
    | expire_logs_days=N | 二进制日志可以自动删除的天数。 默认为0,即不自动删除 |
    | sync_binlog=1|0 | 设定是否启动二进制日志即时同步磁盘功能,默认0,由操作系统负责同步日志到磁盘 |
    查看mariadb自行管理使用中的二进制日志文件列表,及大小 SHOW {BINARY | MASTER} LOGS;
    查看使用中的二进制日志文件 SHOW MASTER STATUS
    查看二进制文件中的指定内容 SHOW BINLOG EVENTS [IN 'log_name'] [FROM pos] [LIMIT [offset,] row_count]
  • 切换日志文件 FLUSH LOGS;
  • 切换日志后新生成一个二进制日志,重置日志大小,并记录到mariadb-bin.index文件中

主从复制### 主从复制

主从复制相关概念有:Master server(主服务器)、Slave server(从服务器)、 Binary logs(二进制日志)、Relay logs(中继日志)及相关的
信息文件。主服务器数据变动后,二进制日志记录下来,主服务器的 Binlog Dump 线程将二进制日志 发送给从服务器的 I/O Thread。
接着从服务器 I/O 线程将其写入中继日志 Relay log。后面的 SQL Thread 将中继日志应用到从服务器,执行相应的 SQL 语句生成同步数
据。

  • Binlog dump thread:进行主从复制时主服务器上启动的进程,用于发送二进制日志内容到从服务器, 该进程可以在主服务器上执
    SHOW PROCESSLIST命令看到,叫 Binlog Dump 线程如:Command: Binlog Dump
  • I/O Thread:该线程为从服务器接收主服务器的二进制更新日志的线程,在从服务器上执行START SLAVE命令时其就 会创建该线
    程,该线程连接到主服务器并请求主服务器发送二进制更新的部分日志。 (Master:Binlog Dump Thread ----> binlog ----> Slave:I/O Thread) I/O 线程状态可以使用SHOW SLAVE STATUS命令查看
  • SQL Thread:从服务器创建一个 SQL 线程读取被 I/O 线程写入磁盘的中继日志 Relay log,并执行其中记录的事务,生成数据
  • 级联复制时,中间的服务器配置项
log_bin
log_slave_updates

主从复制,一主多从,主库提供读写功能,从库提供写功能。当一个事务在master 提交成功时,会把binlog文件同步到从库服务器上
落地为relay log给slave端执行,这个过程主库是不考虑从库是否有接收到binlog文件,有可能出现这种情况,当主库commit一个事务
后,数据库发生宕机,刚好它的binlog还没来得及传送到slave端,这个时候选任何一个slave端都会丢失这个事务,造成数据不一致情
况。 为了避免出现主从数据不一致的情况,MySQL引入了半同步复制,添加多了一个从库反馈机制,这个有两种方式设置:

  • 主库执行完事务后,同步binlog给从库,从库ack反馈接收到binlog,主库提交commit,反馈给客户端,释放会话;
  • 主库执行完事务后,主库提交commit ,同步binlog给从库,从库ack反馈接收到binlog,反馈给客户端,释放会话;
    缺点
  • 写操作集中在MASTER服务器上;
  • MASTER宕机后,需要人为选择新主并重新给其他的slave端执行change master to(可自行写第三方工具实现,但是mysql的复制就是
    没提供,所以也算是弊端)
    于是乎,官方感应到民间怨气以及业界压力,于2016年12月12日正式发布了MySQL Group Replication

监控和管理复制

#查看进程信息
SHOW PROCESSLIST;
#查看当前的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
#查看当前锁定的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
#查看当前等锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;#查看错误日志
SHOW GLOBAL VARIABLES LIKE 'log_error'
#查看从节点信息
SHOW SLAVE STATUS;
#查看主节点信息
SHOW MASTER STATUS;
#查看二进制文件和位置
SHOW MASTER LOGS;
#查看插件情况
SHOW PLUGINS;
#查看半同步复制变量和信息
SHOW GLOBAL VARIABLES LIKE '%semi%';
SHOW GLOBAL STATUS LIKE '%semi%';
#查看所有的二进制文件信息
SHOW BINARY LOGS
#查看二进制日志事件
SHOW BINLOG EVENTS

可以使用工具:pt-table-checksum 检查从节点数据是否和主节点数据一致
pt-table-checksum 通过在主节点上执行校验和查询来完成数据复制一致性检查,在数据与主节点 不一致的从节点上的到的结果与主节
点不一致。[DSN]用来指明主节点,如果数据不一致则该工具的退出 状态为非 0 值。

MySQL主从数据不一致原因

  • 主库binlog格式为Statement,同步到从库执行后可能造成主从不一致。
  • 主库执行更改前有执行set sqllogbin=0,会使主库不记录binlog,从库也无法变更这部分数
    据。
  • 从节点未设置只读,误操作写入数据
  • 主库或从库意外宕机,宕机可能会造成binlog或者relaylog文件出现损坏,导致主从不一致
  • 主从实例版本不一致,特别是高版本是主,低版本为从的情况下,主数据库上面支持的功
    能,从数据库上面可能不支持该功能
  • MySQL自身bug导致

主从不一致修复方法

  • 从库重新实现
    虽然这也是一种解决方法,但是这个方案恢复时间比较慢,而且有时候从库也是承担一部分
    的查询操作的,不能贸然重建。
  • 使用percona-toolkit工具辅助
    PT工具包中包含pt-table-checksum和pt-table-sync两个工具,主要用于检测主从是否一致以及修
    复数据不一致情况。这种方案优点是修复速度快,不需要停止主从辅助,缺点是需要知识积累,需要时间去学习,去测试,特别是在生产环境,还是要小心使用
    关于使用方法,可以参考下面链接:https://wwwblogs/feiren/p/7777218.html
  • 手动重建不一致的表
    在从库发现某几张表与主库数据不一致,而这几张表数据量也比较大,手工比对数据不现
    实,并且重做整个库也比较慢,这个时候可以只重做这几张表来修复主从不一致这种方案缺点是在执行导入期间需要暂时停止从库复
    制,不过也是可以接受的

如何避免主从不一致

  • 主库binlog采用ROW格式
  • 主从实例数据库版本保持一致
  • 主库做好账号权限把控,不可以执行set sql_log_bin=0
  • 从库开启只读,不允许人为写入
  • 定期进行主从一致性检验

MySQL Group Replicationn能提供哪些功能?

  • 多主,在同一个group里边的所有实例,每一个实例可以执行写操作,也就是每个实例都执行Read-Write
  • 注意一点,多主情况下,当执行一个事务时,需要确保同个组内的每个实例都认可这个事务无冲突异常,才可以commit,如果设置的
    是单主,其他实例ReadOnly,则不需要进行上面的判断
  • 多主情况下,事务并发冲突问题就凸显出来了,如何避免呢?数据库内部有一个认证程序,当不同实例并发对同一行发起修改,在同个
    组内广播认可时,会出现并发冲突,那么会按照先执行的提交,后执行的回滚
  • 弹性,同个Group Replication中,节点的加入或者移除都是自动调整;如果新加入一个节点,该节点会自动从Group的其他节点同步数
    据,直到与其他节点一致;如果移除一个节点,那么剩下的实例会自动更新,不再向这个节点广播事务操作,当然,这里要注意,假设一
    个Group的节点有n个(max(n)=9,同个Group最多节点数为9),移除或者宕机的节点数应该小于等于 floor((n-1)/2) ,注意是向下取
    整;如果是单主模式,宕机的是单主,则人为选择新主后,其他节点也会自动从新主同步数据。
    故障探测( Failure Detection):
    Group Replication中有一个故障检测机制,会提供某些节点可能死掉的信息,然后广播给同一个Group的各个节点,如果确定宕机,那
    么组内的节点就会与它隔离开来,该节点即无法同步其他节点的传送过来的binlog events,也无法执行任何本地事务。
    配置MGR数据库要求
    innodb引擎
    为什么需要使用innodb引擎呢?在MySQL Group Replication中,事务以乐观形式执行,但是在提交时检查冲突,如果存在冲突,则会
    在某些实例上回滚事务,保持各个实例的数据一致性,那么,这就需要使用到 事务存储引擎,同事Innodb提供一些额外的功能,可以更
    好的管理和处理冲突,所以建议 业务使用表格使用inndb存储引擎,类似于系统表格mysql.user使用MyISAM引擎的表格,因为极少修改
    及添加,极少出现冲突情况。
    主键
    每个需要复制的表格都必须定义一个显式主键,注意跟隐式主键区分(使用Innodb引擎的表格,如果没有指定主键,默认选择第一个非
    空的唯一索引作为主键,如果没有,则自动创建一个6个字节的rowid隐式主键)。这个主键能在冲突发生时启动极其重要的作用,同时,
    能够有效提高relay log的执行效率。
    隔离级别官网建议使用READ COMMITTED级别,除非应用程序依赖于REPLEATABLE READ,RC模式下没有GAP LOCK,比较好支持Innodb本
    身的冲突检测机制何组复制的内部分布式检测机制一起协同工作。不支持SERIALIZABLE隔离级别。
    外键
    不建议使用级联外键,如果旧库本身有外键,业务上无法去除并且使用的是多主模式,那么,请配置
    group_replication_enforce_update_everywhere_check ,强制检查每个组成员的级联检查,避免多主模式下执行级联操作造成的检测
    不到的冲突。

备份和恢复

备份要考虑哪些因素?

1.3.1 恢复点目标(RPO:recovery point objective)和恢复时间目标(RTO:recovery time objective)

  • 在没有严重后果的情况下容忍多少数据丢失?
  • 数据恢复时速度要多快才行?数据库不可访问时,什么时间范围内可以接受?用户能接受的时间长短?
  • 需要恢复什么? 整个服务器?单个数据库?单张表?或者单个语句和事务?

1.3.2 备份策略

  • 温备时加锁多久?
  • 备份产生的负载服务器在该时段是否能够承载?
  • 备份脚本的严格性?

1.3.3 备份和恢复的挑战

  • 某个人可以计划,设计,实施备份;但是灾难恢复时未必是同一个人?
  • 执着于备份,未进行恢复测试和演练?

备份的类型?

完全备份

完全备份:整个数据集,包括配置文件,全部数据库,全部日志。

部分备份

部分备份:只备份数据子集,如部分库或表或者某些SQL语句

增量备份和差异备份

增量备份:仅备份最近一次完全备份或增量备份(如果存在增量)以来变化的数据,备份较快,还原复杂
差异备份:仅备份最近一次完全备份以来变化的数据,备份较慢,还原简单

冷备 温备 热备

冷备:对数据库的读、写操作均不可进行,相当于停数据库;完全停业务;也叫离线备份温备:对数据库的读操作可执行,但写操作不可执行;影响业务
热备:对数据库的读、写操作均可执行;不影响业务 InnoDB:支持以上三种备份方式 MyISAM:只支持冷备和温备,不支持热备

集群

关系型数据库,是建立在关系模型基础上的数据库,其借助于集合代数等数学概念和方法来处理数据库
中的数据。主流的 oracle、DB2、MS SQL Server 和 mysql 都属于这类传统数据库。
NoSQL 数据库,全称为 Not Only SQL,意思就是适用关系型数据库的时候就使用关系型数据库,不适
用的时候也没有必要非使用关系型数据库不可,可以考虑使用更加合适的数据存储。主要分为临时性键值存
储(memcached、Redis)、永久性键值存储(ROMA、Redis)、面向文档的数据库(MongoDB、
CouchDB)、面向列的数据库(Cassandra、HBase),每种 NoSQL 都有其特有的使用场景及优点。
Oracle,mysql 等传统的关系数据库非常成熟并且已大规模商用,为什么还要用 NoSQL 数据库呢?主
要是由于随着互联网发展,数据量越来越大,对性能要求越来越高,传统数据库存在着先天性的缺陷,即单
机(单库)性能瓶颈,并且扩展困难。这样既有单机单库瓶颈,却又扩展困难,自然无法满足日益增长的海量数据存储及其性能要求,所
以才会出现了各种不同的 NoSQL 产品,NoSQL 根本性的优势在于在云计算时代,简单、易于大规模分布式扩展,并且读写性能非常高

数据切分

垂直(纵向)切分

一个数据库由很多表的构成,每个表对应着不同的业务,垂直切分是指按照业务将表进行分类,分布到
不同 的数据库上面,这样也就将数据或者说压力分担到不同的库上面。垂直切分的最大特点就是规则简单,实施也更为方便,尤其适合各
业务之间的耦合度非常低,相互影响很小, 业务逻辑非常清晰的系统。在这种系统中,可以很容易做到将不同业务模块所使用的表分拆到
不同的数据库中。 根据不同的表来进行拆分,对应用程序的影响也更小,拆分规则也会比较简单清晰。
垂直切分的优缺点:

  • 优点:
  • 拆分后业务清晰,拆分规则明确
  • 系统之间整合或扩展容易
  • 数据维护简单
  • 缺点:
  • 部分业务表无法 join,只能通过接口方式解决,提高了系统复杂度;
  • 受每种业务不同的限制存在单库性能瓶颈,不易数据扩展跟性能提高
  • 事务处理复杂
    由于垂直切分是按照业务的分类将表分散到不同的库,所以有些业务表会过于庞大,存在单库读写与存
    储瓶颈,所以就需要水平拆分来做解决。

水平(横向)切分

相对于垂直拆分,水平拆分不是将表做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中 包含一部分数据。简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中
的某些行切分 到一个数据库,而另外的某些行又切分到其他的数据库中。
拆分数据就需要定义分片规则。关系型数据库是行列的二维模型,拆分的第一原则是找到拆分维度。比
如: 从会员的角度来分析,商户订单交易类系统中查询会员某天某月某个订单,那么就需要按照会员结
合日期来拆分, 不同的数据按照会员 ID 做分组,这样所有的数据查询 join 都会在单库内解决;如果从
商户的角度来讲,要查询某 个商家某天所有的订单数,就需要按照商户 ID 做拆分;但是如果系统既想
按会员拆分,又想按商家数据,则会有 一定的困难。如何找到合适的分片规则需要综合考虑衡量。
几种典型的分片规则包括:

  • 按照用户 ID 求模,将数据分散到不同的数据库,具有相同数据用户的数据都被分散到一个库中
  • 按照日期,将不同月甚至日的数据分散到不同的库中
  • 按照某个特定的字段求摸,或者根据特定范围段分散到不同的库中
    既然数据做了拆分有优点也就优缺点。
  • 优点:
  • 拆分规则抽象好,join 操作基本可以数据库做
  • 不存在单库大数据,高并发的性能瓶颈
  • 应用端改造较少
  • 提高了系统的稳定性跟负载能力
  • 缺点:
  • 拆分规则难以抽象
  • 分片事务一致性难以解决
  • 数据多次扩展难度跟维护量极大
  • 跨库 join 性能较差

Mycat

对于架构师来说:可以这么理解 Mycat: Mycat 是一个强大的数据库中间件,不仅仅可以用作读写分离、以及分表分库、容灾备
份,而且可以用于多 租户1应用开发、云平台基础设施、让你的架构具备很强的适
应性和灵活性,借助于即将发布的 Mycat 智能优化模 块,系统的数据访问瓶颈和热点一目了然,根据这些统计分析数据,你可以自动或
手工调整后端存储,将不同的 表映射到不同存储引擎上,而整个应用的代码一行也不用改变。

MySQL高可用

**为什么互联网 企业对于其服务的高可用性要求高?**高可用代表着可以持续的提供服务,持续不断的营收,哪怕一分一秒,对于大企业
来 说都是不可轻易接受的,一个实实在在的例子是:在 2013 年 8 月份,亚马逊全球购物网站Amazon
服务停了仅仅 15 分钟 (包括 web 和移动端服务),结果损失接近 990000 美元,每分钟接近 66000 美元,也即是每分钟损失近 52 万人
民币。 即使对于如此体量的公司也是不小的数目。

MySQL 常见高可用解决方案

  • MMM: Multi-Master Replication Manager for MySQL,Mysql 主主复制管理器是一套灵活的脚本
    程序,基于 perl 实现,用来对 mysql replication 进行监控和故障迁移,并能管理 mysql Master?Master 复制的配置(同一时间只有一个节点是可写的)
    MMM 官网
    源码
  • MHA:Master High Availability,对主节点进行监控,可实现自动故障转移至其它从节点;通过
    提升某一从节点为新的主节点,基于主从复制实现,还需要客户端配合实现,目前 MHA 主要支持
    一主多从的架构,要搭建 MHA,要求一个复制集群中必须最少有三台数据库服务器,一主二从,即一台充当 master,一台充当备用 master,另外一台充当从库,出于机器成本的考虑,淘宝进行了
    改造,目前淘宝 TMHA 已经支持一主一从
    MHA 官网
    源码
  • Galera Cluster:Galera Cluster 集群底层采用 WSREP 技术实现(MySQL extended with the Write Set Replication)通过 WSREP 协
    议在全局实现复制;任何一节点都可读写,不需要主从复制,实现多主读写。

Galera Cluster

  • Galera Cluster:wsrep(MySQL extended with the Write Set Replication)通过 wsrep 协议在全局实现复制;
    任何一节点都可读写,不需要主从复制,实现多主读写。
  • Galera Cluster:集成了 Galera 插件的 MySQL 集群,是一种新型的,数据不共享的,高度冗余的高可用方案,
    目前 Galera Cluster 有两个版本,分别是 Percona Xtradb Cluster 及 MariaDB Cluster,Galera 本身是具有
    多主特性的,即采用 multi-master 的集群架构,是一个既稳健,又在数据一致性、完整性及高性能方面有出色表现
    的高可用解决方案
  • Galera Cluster 优点
  • 多主架构:真正的多点读写的集群,在任何时候读写数据,都是最新的
  • 同步复制:集群不同节点之间数据同步,没有延迟,在数据库挂掉之后,数据不会丢失
  • 并发复制:从节点 APPLY 数据时,支持并行执行,更好的性能
  • 故障切换:在出现数据库故障时,因支持多点写入,切换容易
  • 热插拔:在服务期间,如果数据库挂了,只要监控程序发现的够快,不可服务时间就会非常少。在
  • 节点故障期间,节点本身对集群的影响非常小
  • 自动节点克隆:在新增节点,或者停机维护时,增量数据或者基础数据不需要人工手动备份提供,
  • Galera Cluster 会自动拉取在线节点数据,最终集群会变为一致
  • 对应用透明:集群的维护,对应用程序是透明的
  • Galera Cluster 缺点
  • 由于 DDL 需全局验证通过,则集群性能由集群中最差性能节点决定(一般集群节点配置都是一样的)
  • 新节点加入或延后较大的节点重新加入需全量拷贝数据(SST,State Snapshot Transfer),作为 donor( 贡献者,
    如:同步数据时的提供者)的节点在同步过程中无法提供读写
  • 只支持 innodb 存储引擎的表
Galera Cluster 解决方案包括两个组件

1.Galera replication library (galera-3):

  • Galera replication library 是提供准备,复制和应用事务写集的协议栈库,包含以下组件 :
    | 组件 | 功能 |
    | :-----------------: | :-----------------------------------------------: |
    | wsrep API | wsrep API 指明 DBMS 和复制提供者的职责的接口 |
    | wsrep hooks | wsrep hooks 是 wsrep 在 DBMS 引擎的集成 |
    | Galera provider | Galera provider 针对 Galera 库的 wsrep API 的实现 |
    | certification layer | 认证层:复制准备事务写集和进行认证 |
    | replication | 复制组件,负责管理复制协议和提供全局排序能力 |
    | GCS framework | GCS 框架:为组通讯系统提供插件架构 |
    2.WSREP:MySQL extended with the Write Set Replication
  • wsrep API (write set replication API) 定义了 Galera 复制技术和上层数据库(MariaDB,Percona XtraDB)之间的通用接口,WSREP 是实
    现 galera cluster 的核心。3.在 galera 集群中,服务器通过向集群中的每个节点广播与事务相关的写集并在提交时各节点同时复制该事务。
Galera Cluster 高可用集群解决方案的工作过程

基于认证的复制(Certification-Based Replication)

  • 基于认证的复制使用组通讯技术和事务排序技术来实现同步复制。事务仅仅在某一节点执行,当提交时,集群内的各节点间进行一个协
    调的认证过程来保证全局一致性。节点在各个并发的事务之间建立全局顺序的广播服务的帮助下达成全局一致性。这保证了数据的一致
    性。
  • 也不是所有的数据库系统都可以实现基于认证的复制技术。数据库需要有如下要求:
  • 支持事务的数据库:必须能够回滚未提交的事务。
  • 复制事务必须能够实现原子操作:也就是说在一个事务中对数据库的一连串操作和改变要么发生,要么不发生。
  • 全局顺序性:复制的事务必须被全局排序。也就是说这些事务以同一个顺序在各节点上被执行。
    基于认证的复制如何工作的呢?
    基于认证的复制的核心要义是假设没有任何冲突的情况下,事务按照惯例执行,直到到达提交点。称为乐观执行。当客户端提交某个
    COMMIT 命令,但是在真正的提交改变还未发生之前,该事务所有对数据库的改变将被收集到一个写集(write-set)中,随后该数据库将写
    集发送给该集群中的所有其他节点。之后该写集经历一次决定性的认证测试(使用其主键来测试),这个动作在每个节点都会发生,包括生
    成该写集的节点。这个测试决定了节点是否可以应用该写集。如果测试失败,节点丢弃该写集,集群回滚最初的事务。如果测试成功,那
    么该事务被提交并且写集被应用到该集群的所有节点。
    Galera Cluster 基于认证的复制技术的实现依赖于事务的全局顺序,复制期间,Galera Cluster 给每个事务分配一个全局唯一的序列
    号,当某个事务到达其提交时刻时,节点根据最后一个成功提交的事务序列号来检查其序列号,考虑到在这个时间间隔内(最后一个成功提
    交事务还和当前事务之间)发生的事务没有看到彼此的影响,这两个区域之间的时间间隔是值得关注的。随后将检查此时间间隔内的所有事
    务,以确定主键是否与相关事务冲突。如果检测到冲突,则认证测试失败。由于该过程是不可逆转的,具有决定性的,所以所有的复制节
    点会以相同的顺序接收到事务写集。因此,所有的节点关于该事务的结果达成一致。达成一致后,开始当前事务的节点可以通知集群的客
    户端是否要提交目前的事务。
    SST 复制技术(State Snapshot Transfers)
  • 当一个新的节点加入集群时,它将像集群请求数据。某个节点(此时成 donor,捐赠者),会使用 SST 复制技术提供一个数据全备份给新
    加入的节点(称 joiner)。可以使用wsrep_set_donor参数事先指定某个节点为 donor,不指定,则集群会根据已有信息自动指定一个
    节点作为 donor。组通信模块会监视节点状态,以便进行流控制、状态传输和仲裁计算。它确保显示为 JOINING 状态(正在加入)的节点
    不计入流控制和仲裁。某个节点为 SYNCED 状态时可以作为 donor,joiner 节点会从 synced 节点中选一个来复制数据。其倾向于选择
    有相同 gmcast.segment选项的节点,或者直接选择索引中的第一个。当某个节点被选中后其状态立即变为 DONOR,此时其不再提供
    查询
  • Galera 对于 SST 复制技术支持好几种后端复制方式。使用 wsrep_sst_method 参数来设置 SST 复制方式。
    | | | | | | |
    | ---------- | ------- | ------------ | ---------------------- | -------- | ---------------- |
    | Method | Speed | Blocks Donor | Live Node Availability | Type | DB Root Access |
    | mysqldump | Slow | Blocks | Available | Logical | Donor and Joiner |
    | rsync | Fastest | Blocks | Unavailable | Physical | None |
    | xtrabackup | Fast | Briefly | Unavailable | Physical | Only Donor |
    在下面的例子中,SST 复制方式设置为 rsync,donor 默认。需要注意的是,并没有最好的复制方式可以选择,需要根据业务的需求和规
    模来灵活选择 SST 复制方式。
wsrep_sst_method = rsyncwsrep_sst_donor = "node1, node2"

Galera Cluster 解决方案的实现

  • PXC:Percona XtraDB Cluster,是 Percona 公司对 Galera 的实现
  • 参考仓库
    mirrors.tuna.tsinghua.edu
  • MariaDB Galera Cluster:Mariadb Galera Cluster 是 mariadb 对 WSREP 的实现
  • 参考仓库
    mirrors.tuna.tsinghua.edu
  • 注意:两者都需要至少三个节点,不能安装 mysql server 或 mariadb-server
Percona XtraDB Cluster 实现 Galera cluster

PXC 常用的 4 个端口号

3306:数据库对外服务的端口号
4444:请求SST的端口号
4567:组成员之间进行沟通的端口号
4568:用于传输IST的端口号
PXC 中涉及到的重要概念和核心参数
  • 1.集群中节点的数量:整个集群中节点数量应该控制在最少 3 个、最多 8 个的范围内。最少 3 个节点是为了防止出现脑裂现象,因为只
    有在 2 个节点下才会出现此现象。脑裂现象的标志就是输入任何命令,返回的结果都是 unknown command。节点在集群中,会因新节
    点的加入或故障、同步失效等原因发生状态的切换。
  • 节点状态的变化阶段
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6SrQRi8c-1604371435766)(png/20191207173201611.png)]
    | 状态 | 意义 |
    | :-----: | :-------------------------------------------------------------: |
    | open | 节点启动成功,尝试连接到集群时的状态 |
    | primary | 节点已处于集群中,在新节点加入并选取 donor 进行数据同步时的状态 |
    | joiner | 节点处于等待接收同步文件时的状态 |
    | joined | 节点完成数据同步工作,尝试保持和集群进度一致时的状态 |
    | synced | 节点正常提供服务时的状态,表示已经同步完成并和集群进度保持一致 |
    | donor | 节点处于为新加入的节点提供全量数据时的状态 |
  • 备注:donor 节点就是数据的贡献者,如果一个新节点加入集群,此时又需要大量数据的 SST 数据传输,就有可能因此而拖垮整个集群
    的性能,所以在生产环境中,如果数据量较小,还可以使用 SST 全量数据传输,但如果数据量很大就不建议使用这种方式,可以考虑先建
    立主从关系,然后再加入集群。
  • 3.节点的数据传输方式:
    | | |
    | :—: | :--------------------------------------: || SST | State Snapshot Transfer,全量数据传输 |
    | IST | Incremental State Transfer,增量数据传输 |
  • SST 数据传输有 xtrabackup、mysqldump 和 rsync 三种方式,而增量数据传输就只有一种方式 xtrabackup,但生产环境中一般数据
    量较小时,可以使用 SST 全量数据传输,但也只使用 xtrabackup 方法。
  • 4.GCache 模块:在 PXC 中一个特别重要的模块,它的核心功能就是为每个节点缓存当前最新的写集。如果有新节点加入进来,就可以
    把新数据的增量传递给新节点,而不需要再使用 SST 传输方式,这样可以让节点更快地加入集群中,涉及如下参数:
    | | |
    | :---------------: | :----------------------------------------------------------------------------------------------------------------------
    -----------------------: |
    | gcache.size | 缓存写集增量信息的大小,它的默认大小是 128MB,通过 wsrep_provider_options 参数设置,建议调整为
    2GB~4GB 范围,足够的空间便于缓存更多的增量信息。 |
    | gcache.mem_size | GCache 中内存缓存的大小,适度调大可以提高整个集群的性能
    |
    | gcache.page_siz: | 如果内存不够用(GCache 不足),就直接将写集写入磁盘文件中
    |
    注意:尽管 Galera Cluster 不再需要通过 binlog 的形式进行同步,但还是建议在配置文件中开启二进制日志
    功能,原因是后期如果有新节点需要加入,老节点通过 SST 全量传输的方式向新节点传输数据,很可能会拖垮集群性能,所以让新节点先
    通过 binlog 方式完成同步后再加入集群会是一种更好的选择
    配置示例
root@ubuntu1904:~#ansible websrvs -a 'grep -Ev "^#|^$" /etc/percona-xtradb-cluster.conf.d/wsrepf'
172.20.1.79 | CHANGED | rc=0 >>
[mysqld]
wsrep_provider=/usr/lib64/galera3/libgalera_smm.so
wsrep_cluster_address=gcomm://172.20.1.69,172.20.1.68,172.20.1.67,172.20.1.79
binlog_format=ROW
default_storage_engine=InnoDB
wsrep_slave_threads= 8
wsrep_log_conflicts
innodb_autoinc_lock_mode=2
wsrep_node_address=172.20.1.79
wsrep_cluster_name=pxc-cluster
wsrep_node_name=pxc-cluster-node-4
pxc_strict_mode=ENFORCING
wsrep_sst_method=xtrabackup-v2
172.20.1.67 | CHANGED | rc=0 >>
[mysqld]
wsrep_provider=/usr/lib64/galera3/libgalera_smm.so
wsrep_cluster_address=gcomm://172.20.1.69,172.20.1.68,172.20.1.67,172.20.1.79
binlog_format=ROW
default_storage_engine=InnoDB
wsrep_slave_threads= 8
wsrep_log_conflicts
innodb_autoinc_lock_mode=2
wsrep_node_address=172.20.1.67wsrep_cluster_name=pxc-cluster
wsrep_node_name=pxc-cluster-node-3
pxc_strict_mode=ENFORCING
wsrep_sst_method=xtrabackup-v2
wsrep_sst_auth="sstuser:s3cretPass"
172.20.1.68 | CHANGED | rc=0 >>
[mysqld]
wsrep_provider=/usr/lib64/galera3/libgalera_smm.so
wsrep_cluster_address=gcomm://172.20.1.69,172.20.1.68,172.20.1.67,172.20.1.79
binlog_format=ROW
default_storage_engine=InnoDB
wsrep_slave_threads= 8
wsrep_log_conflicts
innodb_autoinc_lock_mode=2
wsrep_node_address=172.20.1.68
wsrep_cluster_name=pxc-cluster
wsrep_node_name=pxc-cluster-node-2
pxc_strict_mode=ENFORCING
wsrep_sst_method=xtrabackup-v2
wsrep_sst_auth="sstuser:s3cretPass"
172.20.1.69 | CHANGED | rc=0 >>
[mysqld]
wsrep_provider=/usr/lib64/galera3/libgalera_smm.so
wsrep_cluster_address=gcomm://172.20.1.69,172.20.1.68,172.20.1.67,172.20.1.79
binlog_format=ROW
default_storage_engine=InnoDB
wsrep_slave_threads= 8
wsrep_log_conflicts
innodb_autoinc_lock_mode=2
wsrep_node_address=172.20.1.69
wsrep_cluster_name=pxc-cluster
wsrep_node_name=pxc-cluster-node-1
pxc_strict_mode=ENFORCING
wsrep_sst_method=xtrabackup-v2
  • 各项配置含义
    | |
    |
    | :----------------------------: | :-----------------------------------------------------------------------------------------------------------
    ----------------------------------------------------------------------------------------------------------------------------------: |
    | wsrep_provider | 指定 Galera 库路径
    |
    | wsrep_cluster_name | Galera 集群的名称
    |
    | wsrep_cluster_address=gcomm:// | Galera 集群中各节点的地址。组通讯
    协议(Group communication) |
    | wsrep_node_name | 本节点在 Galera 集群中的名称
    || wsrep_node_address | 本节点在 Galera 集群中的通信地
    址 |
    | wsrep_sst_method | state_snapshot_transfer(SST)使用的传输方法,可用方法 mysqldump、rsync 和 xtrabackup,前两者
    在传输时都需要对 Donor 加全局只读锁(FLUSH TABLES WITH READ LOCK),xtrabackup 则不需要(它使用 percona 自己提供的
    backup lock) 强烈建议采用 xtrabackup |
    | wsrep_sst_auth | 在 SST 传输时需要用到的认证凭据,格式
    为:“用户:密码” |
    | pxc_strict_mode | 是否限制 PXC 启用正在试用阶段的功能,
    ENFORCING 是默认值,表示不启用 |
    | binlog_format | 二进制日志的格式。Galera 只支持 row 格式
    的二进制日志 |
    | default_storage_engine | 指定默认存储引擎。Galera 的复制功能
    只支持 InnoDB |
    | innodb_autoinc_lock_mode | 只能设置为 2,设置为 0 或 1 时会无
    法正确处理死锁问题 |

MySQL最佳实践

数据库的正规化分析

数据库规范化,又称数据库或资料库的正规化、标准化,是数据库设计中的一系列原理和技术,以减少
数据库中数据冗余,增进数据的一致性。关系模型的发明者埃德加?科德最早提出这一概念,并于1970
年代初定义了第一范式、第二范式和第三范式的概念
设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数据库,不同的规范要求被称为不同范
式,各种范式呈递次规范,越高的范式数据库冗余越小
目前关系数据库有六种范式
第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴德斯科范
式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)。满足最低要求的范式是第一范式
(1NF)。在第一范式的基础上进一步满足更多规范要求的称为第二范式(2NF),其余范式以次类
推。一般数据库只需满足第三范式(3NF)即可
第一范式:1NF
无重复的列,每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有
多个值或者不能有重复的属性,确保每一列的原子性。除去同类型的字段,就是无重复的列
说明:第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据

第二范式:2NF
属性完全依赖于主键,第二范式必须先满足第一范式,要求表中的每个行必须可以被唯一地区分,通常
为表加上每行的唯一标识PK,非PK的字段需要与整个PK有直接相关性
第三范式:3NF
属性不依赖于其它非主属性,满足第三范式必须先满足第二范式。第三范式要求一个数据表中不包含已
在其它表中已包含的非主关键字信息,非PK的字段间不能有从属关系

面试题

建表时为什么要尽量设定一个主键?主键是数据库确保数据行在整张表唯一性的保障,即使业务上本张表没有主键,也建议添加一个自增长的ID列作为主键.设定了主键之后,在后

续的删改查的时候可能更加快速以及确保操作数据范围安全.

主键使用自增ID还是UUID?

推荐使用自增ID,不要使用UUID.因为在InnoDB存储引擎中,主键索引是作为聚簇索引存在的,也就是说,主键索引的B+树叶子节点上存储了
主键索引以及全部的数据(按照顺序),如果主键索引是自增ID,那么只需要不断向后排列即可,如果是UUID,由于到来的ID与原来的大小不确定,
会造成非常多的数据插入,数据移动,然后导致产生很多的内存碎片,进而造成插入性能的下降.
总之,在数据量大一些的情况下,用自增主键性能会好一些.
关于主键是聚簇索引,如果没有主键,InnoDB会选择一个唯一键来作为聚簇索引,如果没有唯一键,会生成一个隐式的主键.

如果要存储用户的密码散列,应该使用什么字段进行存储?

密码散列,盐,用户身份证号等固定长度的字符串应该使用char而不是varchar来存储,这样可以节省空间且提高检索效率.

八. Ansible

1. Ansible 特性介绍

  • ansible 特性
  • 模块化:调用特定的模块,完成特定任务
  • Paramiko(python 对 ssh 的实现),PyYAML,Jinja2(模板语言)三个关键模块
  • 支持自定义模块,可使用任何编程语言写模块
  • 基于 Python 语言实现
  • 部署简单,基于 python 和 SSH(默认已安装),agentless,无需代理不依赖 PKI(无需 ssl),
    去中心化部署
  • 安全,基于 OpenSSH
  • 幂等性:一个任务执行 1 遍和执行 n 遍效果一样,不因重复执行带来意外情况
  • 支持 playbook 编排任务,YAML 格式,编排任务,支持丰富的数据结构
  • 较强大的多层解决方案 role

2. Ansible 核心

  • Ansible 核心模块包括:
    INVENTRY:ansible 控制和管理的主机清单
    API: 供第三方程序调用的应用程序编程接口
    MODULES:ansible 执行命令的功能模块,多数为内置核心,也可以自定义(基于 Python)
    PLUGINS:模块功能的补充,如连接类的插件、循环插件、过滤插件等。
  • Ansible 命令执行来源
    USER:普通用户,即 SYSTEM ADMINISTRATOR
    PLAYBOOKS:任务剧本(任务集),编排定义 Ansible 任务集的配置文件,由 Ansible 顺序依次执行,通常是 JSON 格式的 YML 文件
    CMDB(配置管理数据库): API 调用
    PUBLIC/PRIVATE CLOUD: API 调用
    USER-> Ansible Playbook -> Ansibile
  • Tips
    执行 ansible 的主机一般称为主控端,中控,master 或堡垒机
    主控端 Python 版本需要 2.6 或以上
    被控端 Python 版本小于 2.4 需要安装 python-simplejson被控端如开启 SELinux 需要安装 libselinux-python
    windows 不能做为主控端

3. Ansible配置与模块

相关配置文件

/etc/ansible/ansible.cfg 主配置文件,配置 ansible 工作特性
/etc/ansible/hosts 主机清单
/etc/ansible/roles/ 存放角色的目录
  • Ansible 默认被控主机清单/etc/ansible/hosts
  • ansible 的主要功用在于批量主机操作,为了便捷地使用其中的部分主机,可以在 inventory file 中将其
    分组命名。默认的 inventory file 为/etc/ansible/hosts,inventory file 可以有多个,且也可以通过Dynamic Inventory 来动态生成
  • 主机清单文件格式
    inventory 文件遵循 INI 文件风格,中括号中的字符为组名。可以将同一个主机同时归并到多个不同的组中。此外, 当如若目标主机使
    用了非默认的 SSH 端口,还可以在主机名称之后使用冒号加端口号来标明如果主机名称遵循相似的命名模式,还可以使用列表的方式标识
    各主机。如下面的例子:
[webwebsrvss]
172.20.1.67
172.20.1.68
172.20.1.69
[webwebsrvss:vars]
web67=172.20.1.67
web68=172.20.1.68
web69=172.20.1.69
[appwebsrvss]
172.20.1.84
172.20.1.86
172.20.1.87
[appwebsrvss:vars]
app84=172.20.1.84
app86=172.20.1.86
app87=172.20.1.87
[dnswebsrvss]
172.20.1.79
172.20.1.88
172.20.1.89
[dnswebsrvss:vars]
dns79=172.20.1.79
dns88=172.20.1.88
dns89=172.20.1.89
```### Ansible 附带的工具
- 安装 Ansible 时会附带一些必要的工具
- 加粗的工具经常使用,其它不常使用
| | |
| :---------------------------: | :--------------------------------------: |
| **/usr/bin/ansible** | 主程序,临时命令执行工具 |
| **/usr/bin/ansible-doc** | 查看配置文档,模块功能查看工具 |
| /usr/bin/ansible-galaxy | 下载/上传优秀代码或 Roles 模块的官网平台 |
| **/usr/bin/ansible-playbook** | 定制自动化任务,编排剧本工具 |
| /usr/bin/ansible-pull | 远程执行命令的工具 |
| /usr/bin/ansible-vault | 文件加密工具 |
| /usr/bin/ansible-console | 基于 Console 界面与用户交互的执行工具 |
- `ansible-doc` 命令,用来显示个模块的帮助,一般直接跟模块名即可
### Ansible 命令
- ansible 命令通过 ssh 协议,实现对远程主机的配置管理、应用部署、任务执行等功能,使用-k 选项来输入远程主机的密码。由于每台
主机的密码可能不一样。建议:使用此工具前,先配置 ansible 主控端能基于密钥认证的方式与各个被管理节点通讯。如下面的脚本可以
实现 ansible 主控机与被控主机基于 Key 验证
范例:利用 sshpass 批量实现基于 key 验证
```bash
#!/bin/bash
#
#*******************************************************************************
#Author: steveli
#QQ: 1049103823
#Data: 2019-12-08
#FileName: key_cert.sh
#URL: https://blog.csdn/YouOops
#Description: Test scrpting.
#Copyright (C): 2019 All rights reserved
#*******************************************************************************
ssh-keygen -f /root/.ssh/id_rsa -P ''
NET=172.20.1
export SSHPASS=stevenux
for IP in {80..99}; do
sshpass -e ssh-copy-id ${NET}.${IP}
done
  • ansible 命令用法
~# ansible <host-pattern> [ options [-m module_name] [-a args] ]
  • options```bash
    –version #显示版本
    -m module #指定模块,默认为command
    -v #详细过程 –vv -vvv更详细
    –list-hosts #显示主机列表,可简写 --list
    -k, --ask-pass #提示输入ssh连接密码,默认Key验证
    -C, --check #检查,并不执行
    -T, --timeout=TIMEOUT #执行命令的超时时间,默认10s
    -u, --user=REMOTE_USER #执行远程执行的用户
    -b, --become #代替旧版的sudo 切换
    –become-user=USERNAME #指定sudo的runas用户,默认为root
    -K, --ask-become-pass #提示输入sudo时的口令
<host-pattern> 用于匹配控制的主机列表,筛选出特定的主机来执行特定任务 :
| 模式 | 意义 | 例子 |
| :--------: | :-------------------------: | :--------------------------------------------------------------------------------------------------
-----------: |
| all | 所有 Inventory 中定义的主机 | ansible all –m ping |
| * | 通配符 | ansible “*” -m ping |
| | | ansible 192.168.1.* -m ping |
| | | ansible “websrvss” -m ping |
| : | 逻辑或关系 | ansible “webwebsrvss:appwebsrvss” -m ping |
| | | ansible “192.168.1.10:192.168.1.20” -m ping |
| :& | 逻辑与关系 | ansible “webwebsrvss:&dbwebsrvss” –m ping #在 webwebsrvss 组并且在
dbwebsrvss 组中的主机 |
| :! | 逻辑非关系 | ansible ‘webwebsrvss:!dbwebsrvss’ –m ping #在 webwebsrvss 组,但不在 dbwebsrvss 组中的
主机。 注意:此处为单引号 |
| 正则表达式 | | ansible “webwebsrvss:&dbwebsrvss” –m ping |
| 正则表达式 | | ansible “~(web \| db).*.magedu” –m ping |
#### ansible 命令执行过程
1. 加载自己的配置文件 默认`/etc/ansible/ansible.cfg`
2. 加载自己对应的模块文件,如:command
3. 通过 ansible 将模块或命令生成对应的临时 py 文件,并将该文件传输至远程服务器的对应执行用户
`$HOME/.ansible/tmp/ansible-tmp-数字/XXX.PY` 文件
4. 给文件加执行权限执行
5. 执行并返回结果
6. 删除临时 py 文件,退出
**ansible 执行后返回状态可以在配置文件自定义`/ect/ansible/ansible.cfg`**
`green`:绿色表示执行成功且不需要做改变
`yellow`:执行成功并且对被控主机有改动
`red`:执行失败
- **ansible 使用范例**
```bash
#以stevenux用户执行ping存活检测ansible all -m ping -u stevenux -k
#以stevenux sudo至root执行ping存活检测
ansible all -m ping -u stevenux -k -b
#以stevenux sudo至steve用户执行ping存活检测
ansible all -m ping -u stevenux -k -b --become-user=steve
#以stevenux sudo至root用户执行ls
ansible all -m command -u stevenux -a 'ls /root' -b --become-user=root -k

Ansible常用模块

command模块:

  • 功能: 在远程主机执行命令,此为默认模块,可忽略-m 选项
  • 注意点: 此命令不支持 $VARNAME < > | ; &等符号和相应功能,用 shell 模块实现
  • 例子:
root@ubuntu1904:~#ansible all -m command -a "echo 'Hello ansibleansible'"
172.20.1.79 | CHANGED | rc=0 >>
Hello ansibleansible
172.20.1.67 | CHANGED | rc=0 >>
Hello ansibleansible
172.20.1.68 | CHANGED | rc=0 >>
Hello ansibleansible

Shell 模块

  • 功能: 和 command 功能类似,使用 shell 来在被控主机执行命令
  • 注意点: 调用 bash 执行命令 类似cat /tmp/test.md | awk -F‘|’ ‘{print $1,$2}’ &> /tmp/example.txt 这些复杂命令,即使使
    用 shell 也可能会失败。解决办法:写到脚本-->copy 到远程主机-->执行;再把 需要的结果 fetch 回来主控机器
  • 例子:
root@ubuntu1904:~#ansible all -m shell -a 'ip addr | sed -nr "s#.(1
72.20.1...).#\1#p"'
172.20.1.67 | CHANGED | rc=0 >>
172.20.1.67
172.20.1.68 | CHANGED | rc=0 >>
172.20.1.68
172.20.1.84 | CHANGED | rc=0 >>
172.20.1.84

Script 模块

  • 功能: 在本地指定脚本,自动传输到远程执行并返回结果
  • 注意点:
  • 例子:
#!/bin/bash
echo `ip a` |sed -nr 's/inet (172.20.*\/..) .*/\1/p'
root@ubuntu1904:~#ansible webwebsrvss -m script -a './script.sh'
172.20.1.67 | CHANGED => {
"changed": true,
"rc": 0,
"stderr": "Shared connection to 172.20.1.67 closed.\r\n",
"stderr_lines": [
"Shared connection to 172.20.1.67 closed."
],
"stdout": "172.20.1.67/16\r\n",
"stdout_lines": [
"172.20.1.67/16" # 返回ip地址
]
}
172.20.1.68 | CHANGED => {
"changed": true,
"rc": 0,
"stderr": "Shared connection to 172.20.1.68 closed.\r\n",
"stderr_lines": [
"Shared connection to 172.20.1.68 closed."
],
"stdout": "172.20.1.68/16\r\n",
"stdout_lines": [
"172.20.1.68/16"
]
}
172.20.1.69 | CHANGED => {
"changed": true,
"rc": 0,
"stderr": "Shared connection to 172.20.1.69 closed.\r\n",
"stderr_lines": [
"Shared connection to 172.20.1.69 closed."
],
"stdout": "172.20.1.69/16\r\n",
"stdout_lines": [
"172.20.1.69/16"
]
}

Copy 模块

  • 功能: 从 ansible 主控端复制文件到远程被控主机
  • 注意点: 文件源在 ansible 主控机,目标是远程被控机
  • 例子:
root@ubuntu1904:~#ansible all -m copy -a 'src=~/script.sh dest=/data/'
172.20.1.67 | CHANGED => {"changed": true,
"checksum": "c3e89fbe08ae1d3edfa22b1c9968abd470ba8e81",
"dest": "/data/script.sh",
"gid": 0,
"group": "root",
"md5sum": "6a33896eae3ee660a6b0c0bb60a5b326",
"mode": "0644",
"owner": "root",
"size": 67,
"src": "/root/.ansible/tmp/ansible-tmp-1575851039.0323763-219776225171533/source",
"state": "file",
"uid": 0
}
......
# 将文本内容直接在被控机生成文件
root@ubuntu1904:~#ansible all -m copy -a 'content="Hello asible" dest=/data/hello.txt'
172.20.1.87 | CHANGED => {
"changed": true,
"checksum": "8754e6aac9f6b9b741c3bdf974e6a33bbc72b321",
"dest": "/data/hello.txt",
"gid": 0,
"group": "root",
"md5sum": "a131edb4211ac4a39aac74d8a04126f6",
"mode": "0644",
"owner": "root",
"size": 12,
"src": "/root/.ansible/tmp/ansible-tmp-1575851292.1085637-80128185007427/source",
"state": "file",
"uid": 0
}
root@ubuntu1904:~#ansible all -a 'cat /data/hello.txt'
172.20.1.89 | CHANGED | rc=0 >>
Hello asible
172.20.1.67 | CHANGED | rc=0 >>
Hello asible
172.20.1.68 | CHANGED | rc=0 >>
Hello asible
# 拷贝/etc/sysconfig/文件夹下的文件,不包括文件夹本身
root@ubuntu1904:~#ansible all -m copy -a 'src=/etc/sysconfig/ dest=/backup'

Fetch 模块

  • 功能: 从远程被控主机拷贝文件到主控端,与 copy 模块方向相反
  • 注意点: 不支持将远程被控机的问价夹拷贝到主控机
  • 例子:
root@ubuntu1904:~#ansible all -m fetch -a 'src=/data/hello.txt dest=/data/'
172.20.1.87 | CHANGED => {
"changed": true,
"checksum": "8754e6aac9f6b9b741c3bdf974e6a33bbc72b321","dest": "/data/172.20.1.87/data/hello.txt",
"md5sum": "a131edb4211ac4a39aac74d8a04126f6",
"remote_checksum": "8754e6aac9f6b9b741c3bdf974e6a33bbc72b321",
"remote_md5sum": null
}
root@ubuntu1904:~#cat /data/172.20.1.67/data/hello.txt
Hello asible

File 模块

  • 功能: 设置远程被控主机的文件、符号链接、文件夹的属性;也可以删除其
  • 注意点: 创建软连接时,目标文件或文件夹是在远程被控主机上,软连接也是位于远程主机
  • 例子:
# 创建文件
root@ubuntu1904:~#ansible all -m file -a 'path=/data/hello.txt state=touch'
# 删除文件
root@ubuntu1904:~#ansible all -m file -a 'path=/data/hello.txt state=absent'
# 修改文件属主和权限
root@ubuntu1904:~#ansible all -m file -a 'path=/data/hello.txt owner=steve mode=0700'
# 创建文件夹
root@ubuntu1904:~#ansible all -m file -a 'path=/data/dir state=directory mode=0700'
# 为远程主机某个文件创建文件夹
root@ubuntu1904:~#ansible all -m file -a 'src=/data/hello.txt dest=/data/hello-link state=link'

unarchive 模块

  • 功能: 将打包压缩的文件解压缩
  • 注意点: 参数 copy=yes 表示将 ansible 主控机的打包压缩文件传输到远程被控机再解压缩;copy=no 表示 直接将远程主机某个压缩包
    解压缩。
  • 常用参数:
  • copy:默认为 yes,当 copy=yes,拷贝的文件是从 ansible 主机复制到远程主机上,如果设置为 copy=no,会在远程主机上寻找
    src 源文件
  • remote_src:和 copy 功能一样且互斥,yes 表示在远程主机,不在 ansible 主机,no 表示文件在 ansible 主机上;官方更推荐使用
    remote_src 参数代替 copy 参数
  • src:源路径,可以是 ansible 主机上的路径,也可以是远程主机上的路径,如果是远程主机上的路 径,则需要设置 copy=no
  • dest:远程主机上的目标路径
  • mode:设置解压缩后的文件权限
  • 例子:
root@ubuntu1904:~#ansible all -m unarchive -a 'src=/data/bar.tar.gz dest=/var/lib/bar'
root@ubuntu1904:~#ansible all -m unarchive -a 'src=/data/bar.zip dest=/data copy=no mode=0700'
root@ubuntu1904:~#ansible all -m unarchive -a 'src=https://suosuoli/god-of-editor.pdf.zip dest=/data

Archive 模块- 功能: 打包压缩,和 unarchive 方向相反。默认,该模块认为压缩的源文件在远程被控主机。 压缩后指定remove=True可以删除源文

件。

  • 注意点:
  • 例子:
root@ubuntu1904:~#ansible all -m archive -a 'path=/data/ dest=/data/dir.tar.bz2 format=bz2 mode=0700'
172.20.1.87 | CHANGED => {
"archived": [],
"arcroot": "/data/",
"changed": true,
"dest": "/data/dir.tar.bz2",
"expanded_exclude_paths": [],
"expanded_paths": [
"/data/"
],
"gid": 0,
"group": "root",
"missing": [],
"mode": "0700",
"owner": "root",
"size": 115,
"state": "file",
"uid": 0
}
......
root@ubuntu1904:~#ansible all -m fetch -a 'src=/data/dir.tar.bz2 dest=/data'
172.20.1.87 | CHANGED => {
"changed": true,
"checksum": "e74d1ab8aad31d62cacad3a65a2a0ccc5623871c",
"dest": "/data/172.20.1.87/data/dir.tar.bz2",
"md5sum": "706f8ee46597a3c62e6c4597f37739a5",
"remote_checksum": "e74d1ab8aad31d62cacad3a65a2a0ccc5623871c",
"remote_md5sum": null
}
......
oot@ubuntu1904:~#ll /data/172.20.1.87/data/
total 8
-rwx------ 1 root root 116 Dec 9 09:04 dir.tar.bz2
-rw-r--r-- 1 root root 12 Dec 9 08:35 hello.txt

Hostname 模块

  • 功能: hostname 模块用来管理主机名
  • 注意点:
  • 例子:
root@ubuntu1904:~#ansible webwebsrvss -m hostname -a 'name=webhost'
172.20.1.69 | CHANGED => {
"ansible_facts": {"ansible_domain": "",
"ansible_fqdn": "webhost",
"ansible_hostname": "webhost",
"ansible_nodename": "webhost"
},
"changed": true,
"name": "webhost"
}
172.20.1.67 | CHANGED => {
"ansible_facts": {
"ansible_domain": "",
"ansible_fqdn": "webhost",
"ansible_hostname": "webhost",
"ansible_nodename": "webhost"
},
"changed": true,
"name": "webhost"
}
172.20.1.68 | CHANGED => {
"ansible_facts": {
"ansible_domain": "",
"ansible_fqdn": "webhost",
"ansible_hostname": "webhost",
"ansible_nodename": "webhost"
},
"changed": true,
"name": "webhost"
}
root@ubuntu1904:~#ansible webwebsrvss -a 'hostname'
172.20.1.68 | CHANGED | rc=0 >>
webhost
172.20.1.69 | CHANGED | rc=0 >>
webhost
172.20.1.67 | CHANGED | rc=0 >>
webhost

Cron 模块

  • 功能: 管理和删除计划任务
  • 注意点:
  • 例子:
#备份数据库脚本
[root@centos8 ~]#cat mysql_backup.sh
mysqldump -A -F --single-transaction --master-data=2 -q -uroot |gzip >
/data/mysql_</span><span class="token function">date</span> +%F_%T<span class="token variable">.sql.gz
#创建任务
ansible 192.168.39.28 -m cron -a 'hour=2 minute=30 weekday=1-5 name="backup
mysql" job=/root/mysql_backup.sh'
ansible websrvs -m cron -a "minute=/5 job='/usr/sbin/ntpdate 172.20.0.1&>/dev/null' name=Synctime"
#禁用计划任务
ansible websrvs -m cron -a "minute=/5 job='/usr/sbin/ntpdate 172.20.0.1
&>/dev/null' name=Synctime disabled=yes"
#启用计划任务
ansible websrvs -m cron -a "minute=*/5 job='/usr/sbin/ntpdate 172.20.0.1
&>/dev/null' name=Synctime disabled=no"
#删除任务
ansible websrvs -m cron -a "name='backup mysql' state=absent"
ansible websrvs -m cron -a ‘state=absent name=Synctime’

Yum 模块

  • 功能: 使用 yum 安装、升级、降级、删除和列出软件包。
  • 注意点: 该软件包只适用于 python2,如果需要 python3 的支持,使用 dnf 模块
  • 例子:
# 安装
root@ubuntu1904:~#ansible webwebsrvss -m yum -a 'name=nginx state=present'
# 删除
root@ubuntu1904:~#ansible webwebsrvss -m yum -a 'name=nginx state=absent'

Service 模块

  • 功能: 控制远程被控机的服务。支持 init 启动服务的系统(包括 BSD init,OpenRC,SysV, Solaris SMF,systemd,upstart)。
  • 注意点:
  • 例子:
root@ubuntu1904:~#ansible webwebsrvss -m service -a 'name=httpd state=started enabled=yes'
root@ubuntu1904:~#ansible webwebsrvss -m service -a 'name=httpd state=stopped'
root@ubuntu1904:~#ansible webwebsrvss -m service -a 'name=httpd state=reloaded'
root@ubuntu1904:~#ansible webwebsrvss -m service -a 'name=httpd state=restarted'

User 模块

  • 功能: 管理用户账号和属性
  • 注意点:
  • 例子:
# 创建系统用户nginx
root@ubuntu1904:~#ansible webwebsrvss -m user -a 'name=nginx groups="root,daemon" system=yes shell=/sbin/nologin
createhome=no home=/data/nginx non_unique=yes'
172.20.1.67 | CHANGED => {
"changed": true,
"comment": "",
"create_home": false,"group": 995,
"groups": "root,daemon",
"home": "/data/nginx",
"name": "nginx",
"shell": "/sbin/nologin",
"state": "present",
"system": true,
"uid": 995
}
172.20.1.69 | CHANGED => {
"changed": true,
"comment": "",
"create_home": false,
"group": 992,
"groups": "root,daemon",
"home": "/data/nginx",
"name": "nginx",
"shell": "/sbin/nologin",
"state": "present",
"system": true,
"uid": 994
}
172.20.1.68 | CHANGED => {
"changed": true,
"comment": "",
"create_home": false,
"group": 995,
"groups": "root,daemon",
"home": "/data/nginx",
"name": "nginx",
"shell": "/sbin/nologin",
"state": "present",
"system": true,
"uid": 995
}
# 删除用户
root@ubuntu1904:~#ansible webwebsrvss -m user -a 'name=nginx state=absent remove=yes'
172.20.1.69 | CHANGED => {
"changed": true,
"force": false,
"name": "nginx",
"remove": true,
"state": "absent",
"stderr": "userdel: nginx mail spool (/var/spool/mail/nginx) not found\nuserdel: nginx home directory (/data/nginx) not
found\n",
"stderr_lines": [
"userdel: nginx mail spool (/var/spool/mail/nginx) not found",
"userdel: nginx home directory (/data/nginx) not found"
]
}
......
```#### Group 模块
- 功能: 管理用户组
- 注意点:
- 例子:
```bash
# 创建组
root@ubuntu1904:~#ansible webwebsrvss -m group -a 'name=nginx gid=123 state=present system=yes'
172.20.1.69 | CHANGED => {
"changed": true,
"gid": 123,
"name": "nginx",
"state": "present",
"system": true
}
172.20.1.67 | CHANGED => {
"changed": true,
"gid": 123,
"name": "nginx",
"state": "present",
"system": true
}
172.20.1.68 | CHANGED => {
"changed": true,
"gid": 123,
"name": "nginx",
"state": "present",
"system": true
}
root@ubuntu1904:~#ansible webwebsrvss -a 'getent group nginx'
172.20.1.69 | CHANGED | rc=0 >>
nginx:x:123:
172.20.1.68 | CHANGED | rc=0 >>
nginx:x:123:
172.20.1.67 | CHANGED | rc=0 >>
nginx:x:123:
# 删除组
root@ubuntu1904:~#ansible webwebsrvss -m group -a 'name=nginx state=absent'

Setup 模块

  • 功能: setup 模块会收集远程被控主机的详细信息,其自带的记录远程主机的变量可以用于 playbook
  • 注意点:
  • 例子:
root@ubuntu1904:~#ansible webwebsrvss -m setup
172.20.1.67 | SUCCESS => {
"ansible_facts": {"ansible_all_ipv4_addresses": [
"172.20.1.67"
],
"ansible_all_ipv6_addresses": [
"fe80::4d59:6b61:25f9:d67d"
],
"ansible_apparmor": {
"status": "disabled"
},
......
root@ubuntu1904:~#ansible websrvs -m setup -a "filter=ansible_nodename"
root@ubuntu1904:~#ansible websrvs -m setup -a "filter=ansible_hostname"
root@ubuntu1904:~#ansible websrvs -m setup -a "filter=ansible_domain"
root@ubuntu1904:~#ansible websrvs -m setup -a "filter=ansible_memtotal_mb"
root@ubuntu1904:~#ansible websrvs -m setup -a "filter=ansible_memory_mb"
root@ubuntu1904:~#ansible websrvs -m setup -a "filter=ansible_memfree_mb"
root@ubuntu1904:~#ansible websrvs -m setup -a "filter=ansible_os_family"
root@ubuntu1904:~#ansible websrvs -m setup -a "filter=ansible_distribution_major_version"
root@ubuntu1904:~#ansible websrvs -m setup -a "filter=ansible_distribution_version"
root@ubuntu1904:~#ansible websrvs -m setup -a "filter=ansible_processor_vcpus"
root@ubuntu1904:~#ansible websrvs -m setup -a "filter=ansible_all_ipv4_addresses"
root@ubuntu1904:~#ansible websrvs -m setup -a "filter=ansible_architecture"
# 一些比较有用的参数
{{ ansible_default_ipv4.address }}
{{ansible_distribution}}
{{ansible_distribution_major_version}}
{{ansible_fqdn}}
{{ansible_hostname}}
{{ansible_machine}}
{{ansible_memtotal_mb}}
{{ansible_memory_mb.nocache.free}}
{{ansible_memory_mb.nocache.used}}
{{ansible_memory_mb.real.total}}
{{ansible_memory_mb.real.free}}
{{ansible_memory_mb.real.used}}
{{ansible_service_mgr}}
{{ansible_processor_cores}}
{{ansible_processor_count}}
{{ansible_processor_threads_per_core}}
{{ansible_pkg_mgr}}

4. Ansible中的Playbook

Playbook 中包含多个 role,每个 role 对应于在远程主机完成某个比较复杂的工作,事先构建的 role 包含各个细分的 task,每个 task 会
调用 ansible 提供的相应模块在远程主机完成部分工作,多个 task 共同完成 role所需要完成的工作。

Playbook 核心元素

  • Hosts 被控制和管理的的远程主机列表
  • Tasks 任务集,每个 task 完成某个简单任务- Variables 内置变量或自定义变量在 playbook 中调用
  • Templates 模板,可替换模板文件中的变量并实现一些简单逻辑的文件
  • Handlersnotify 结合使用,由特定条件触发的操作,满足条件方才执行,否则不执行
  • tags 标签 指定某条任务执行,用于选择运行 playbook 中的部分代码。ansible 具有幂等性,因此会自动跳过没有变化的部分,即便
    如此,有些代码为测试其确实没有发生变化的时间依然会非常地长。此时,如果确信其没有变化,就可以通过 tags 跳过此些代码片断

九. HTTP协议与HTTPD

应用层/HTTP协议

URI和URL

URI: Uniform Resource Identifier 统一资源标识,分为URL和URN
URN: Uniform ResourceNaming,统一资源命名 示例: P2P下载使用的磁力链接是URN的一种实现 magnet:?
xt=urn:btih:660557A6890EF888666
URL: Uniform Resorce Locator,统一资源定位符,用于描述某服务器某特定资源位置 两者区别:URN如同一个人的名称,而URL
代表一个人的住址。换言之,URN定义某事物的身份,而URL提供查找该事物的方法。URN仅用于命名,而不指定地址
URL组成:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FPuitCW3-1604371435767)(png/image-20200411132728557.png)]
scheme:方案,访问服务器以获取资源时要使用哪种协议
user:用户,某些方案访问资源时需要的用户名
password:密码,用户对应的密码,中间用:分隔
Host:主机,资源宿主服务器的主机名或IP地址
port:端口,资源宿主服务器正在监听的端口号,很多方案有默认端口号
path:路径,服务器资源的本地名,由一个/将其与前面的URL组件分隔
params:参数,指定输入的参数,参数为名/值对,多个参数,用;分隔
query:查询,传递参数给程序,如数据库,用?分隔,多个查询用&分隔
frag:片段,一小片或一部分资源的名字,此组件在客户端使用,用#分隔

网站访问量指标

IP(独立IP):即Internet Protocol,指独立IP数。一天内来自相同客户机IP 地址只计算一次,记录远程客户机IP地址的计算机访问网站
的次数,是衡量网站流量的重要指标
PV(访问量): 即Page View, 页面浏览量或点击量,用户每次刷新即被计算一次,PV反映的是浏览某网站的页面数,PV与来访者的数
量成正比,PV并不是页面的来访者数量,而是网站被访问的页面数量UV(独立访客):即Unique Visitor,访问网站的一台电脑为一个访客。一天内相同的客户端只被计算一次。可以理解成访问某网站的电
脑的数量。网站判断来访电脑的身份是通过cookies实现的。如果更换了IP后但不清除cookies,再访问相同网站,该网站的统计中UV数是
不变的

HTTP历史版本

http/0.9
1991,原型版本,功能简陋,只有一个命令GET。GET /index.html ,服务器只能回应HTML格式字符
串,不能回应别的格式
http/1.0
1996年5月,支持cache, MIME( Multipurpose Internet Mail Extensions ), method 每个TCP连接只能发送一个请求,发送数据完毕,连
接就关闭,如果还要请求其他资源,就必须再新建一个连接 引入了POST命令和HEAD命令 头信息是 ASCII 码,后面数据可为任何格式。
服务器回应时会告诉客户端,数据是什么格式,即Content-Type字段的作用。这些数据类型总称为MIME 多用途互联网邮件扩展,每个
值包括一级类型和二级类型,预定义的类型,也可自定义类型, 常见Content-Type值:text/xml image/jpeg audio/mp3
http/1.1
1997年1月,引入了持久连接(persistent connection),即TCP连接默认不关闭,可以被多个请求复
用,不用声明Connection: keep-alive。对于同一个域名,大多数浏览器允许同时建立6个持久连接引入
了管道机制,即在同一个TCP连接里,客户端可以同时发送多个请求,进一步改进了HTTP协议的效率
新增方法:PUT、PATCH、OPTIONS、DELETE 同一个TCP连接里,所有的数据通信是按次序进行的。
服务器只能顺序处理回应,前面的回应慢,会有许多请求排队,造成"队头堵塞"(Head-of-line
blocking) 为避免上述问题,两种方法:一是减少请求数,二是同时多开持久连接。
网页优化技巧,如合并脚本和样式表、将图片嵌入CSS代码、域名分片(domain sharding)等 HTTP
协议不带有状态,每次请求都必须附上所有信息。请求的很多字段都是重复的,浪费带宽,影响速度
缓存处理:在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,
HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None?Match等更多可供选择的缓存头来控制缓存策略
带宽优化及网络连接的使用:HTTP1.0中,存在一些浪费带宽的现象,例如:客户端只是需要某个
对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头
引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),方便了
开发者自由的选择以便于充分利用带宽和连接 错误通知的管理,在HTTP1.1中新增24个状态响应
码,如409(Conflict)表示请求的资源与资源当前状态冲突;410(Gone)表示服务器上的某个
资源被永久性的删除
Host头处理:在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并
没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个
虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应
消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)
长连接:HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在
一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在
HTTP1.1中默认开启Connection: keep-alive,弥补了HTTP1.0每次请求都要创建连接的缺点

HTTP1.0和1.1的问题

HTTP1.x在传输数据时,每次都需要重新建立连接,无疑增加了大量的延迟时间,特别是在移动端
更为突出
HTTP1.x在传输数据时,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份,无
法保证数据的安全性
HTTP1.x在使用时,header里携带的内容过大,增加了传输的成本,并且每次请求header基本不
怎么变化,尤其在移动端增加用户流量
虽然HTTP1.x支持了keep-alive,来弥补多次创建连接产生的延迟,但是keep-alive使用多了同样
会给服务端带来大量的性能压力,并且对于单个文件被不断请求的服务(例如图片存放网站),keep-alive可能会极大的影响性能,因为它在文件被请求之后还保持了不必要的连接很长时间

HTTPS协议

为解决安全问题,网景在1994年创建了HTTPS,并应用在网景导航者浏览器中。 最初,
HTTPS是与SSL一起使用的;在SSL逐渐演变到TLS时(其实两个是一个东西,只是名字不同而已),最
新的HTTPS也由在2000年五月公布的RFC 2818正式确定下来。HTTPS就是安全版的HTTP,目前大型网
站基本实现全站HTTPS

SPDY协议

SPDY:2009年,谷歌研发,综合HTTPS和HTTP两者有点于一体的传输协议,主要特点:
降低延迟,针对HTTP高延迟的问题,SPDY优雅的采取了多路复用(multiplexing)。多路复用通
过多个请求stream共享一个tcp连接的方式,解决了HOL blocking的问题,降低了延迟同时提高了
带宽的利用率
请求优先级(request prioritization)。多路复用带来一个新的问题是,在连接共享的基础之上有
可能会导致关键请求被阻塞。SPDY允许给每个request设置优先级,重要的请求就会优先得到响
应。比如浏览器加载首页,首页的html内容应该优先展示,之后才是各种静态资源文件,脚本文
件等加载,可以保证用户能第一时间看到网页内容
header压缩。HTTP1.x的header很多时候都是重复多余的。选择合适的压缩算法可以减小包的大
小和数量
基于HTTPS的加密协议传输,大大提高了传输数据的可靠性
服务端推送(server push),采用了SPDY的网页,例如网页有一个sytle.css的请求,在客户端收
到sytle.css数据的同时,服务端会将sytle.js的文件推送给客户端,当客户端再次尝试获取sytle.js
时就可以直接从缓存中获取到,不用再发请求了

HTTP2协议

http/2.0:2015年,HTTP2.0是SPDY的升级版

  • 头信息和数据体都是二进制,称为头信息帧和数据帧
  • 复用TCP连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,且不用按顺序一
    一对应,避免了“队头堵塞“,此双向的实时通信称为多工(Multiplexing)
  • 引入头信息压缩机制(header compression),头信息使用gzip或compress压缩后再发送;客户
    端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,不发送同样字
    段,只发送索引号,提高速度
  • HTTP/2 允许服务器未经请求,主动向客户端发送资源,即服务器推送(server push)

HTTP工作机制

一次http事务包括
http请求:http request
http响应:http response
Web资源:web resource, 一个网页由多个资源(文件)构成,打开一个页面,通常会有多个资源展
示出来,但是每个资源都要单独请求。一个“Web 页面”通常并不是单个资源,而是一组资源的集合
资源类型
静态文件:无需服务端做出额外处理 文件后缀:.html, .txt, .jpg, .js, .css, .mp3, .avi一旦创建好,文件不再改变。图片数目多,占用磁盘
空间多,一般使用单独的图片服务器;HTML、CSS、JavaScript:这些文本是文本的,前端工程师可以修改这些文件,但修改次数较少,
一段时间都不变
动态文件:服务端执行程序,返回执行的结果 文件后缀:.php, .jsp ,.asp内容由后台程序动态生成,比如查询数据库后,将查询结果生成
为HTML### 后台应用架构
单体架构JSP、Servlet

  • 不管项目多么大,都打包成一个jar、war部署
  • 服务器有开源的tomcat、jetty。商用的有Jboss、weblogic、websphere、glassfish商用的
    Dubbo分布式服务框架
  • 将单体程序分解成多个功能服务模块,模块间使用Dubbo框架提供的高性能RPC通信
  • 阿里开源贡献给了ASF,目前已经是Apache的顶级项目
  • 内部协调使用Zookeeper,实现服务注册、服务发现。有服务治理
    Spring cloud 微服务
  • 将单体应用拆分为粒度更小的单一功能服务
  • RPC通信
  • 需要更高的运维水平,服务太多了需要服务治理
  • 不同的应用架构,部署方式也有不同。

HTTP 请求访问的完整过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gpmIGyHa-1604371435768)(png/image-20200410170855166.png)]

  1. Web访问响应模型(Web I/O)
  • 单进程I/O模型:启动一个进程处理用户请求,而且一次只处理一个,多个请求被串行响应
  • 多进程I/O模型:并行启动多个进程,每个进程响应一个连接请求
  • 复用I/O结构:启动一个进程,同时响应N个连接请求
  • 复用的多进程I/O模型:启动M个进程,每个进程响应N个连接请求,同时接收M*N个请求
    常用请求Method: GET、POST、HEAD、PUT、DELETE、TRACE、OPTIONS
  1. 处理请求:服务器对请求报文进行解析,并获取请求的资源及请求方法等相关信息,根据方法,资
    源,首部和可选的主体部分对请求进行处理
  2. 访问资源: 服务器获取请求报文中请求的资源web服务器,即存放了web资源的服务器,负责向请
    求者提供对方请求的静态资源,或动态运行后生成的资源
  3. 构建响应报文: 一旦Web服务器识别除了资源,就执行请求方法中描述的动作,并返回响应报文。
    响应报文中 包含有响应状态码、响应首部,如果生成了响应主体的话,还包括响应主体 1)响应实体:
    如果事务处理产生了响应主体,就将内容放在响应报文中回送过去。响应报文中通常包括: 描述了响应
    主体MIME类型的Content-Type首部 描述了响应主体长度的Content-Length 实际报文的主体内容 2)
    URL重定向:web服务构建的响应并非客户端请求的资源,而是资源另外一个访问路径 3)MIME类型:
    Web服务器要负责确定响应主体的MIME类型。多种配置服务器的方法可将MIME类型与资源管理起来
  • 魔法分类:Apache web服务器可以扫描每个资源的内容,并将其与一个已知模式表(被称为魔法文
    件)进行匹配,以决定每个文件的MIME类型。这样做可能比较慢,但很方便,尤其是文件没有标准
    扩展名时
  • 显式分类:可以对Web服务器进行配置,使其不考虑文件的扩展名或内容,强制特定文件或目录
    内容拥有某个MIME类型
  • 类型协商: 有些Web服务器经过配置,可以以多种文档格式来存储资源。在这种情况下,可以配
    置Web服务器,使其可以通过与用户的协商来决定使用哪种格式(及相关的MIME类型)"最好"5. 发送响应报文 Web服务器通过连接发送数据时也会面临与接收数据一样的问题。服务器可能有很多
    条到各个客户端的连接,有些是空闲的,有些在向服务器发送数据,还有一些在向客户端回送响应数
    据。服务器要记录连接的状态,还要特别注意对持久连接的处理。对非持久连接而言,服务器应该在发
    送了整条报文之后,关闭自己这一端的连接。对持久连接来说,连接可能仍保持打开状态,在这种情况
    下,服务器要正确地计算Content-Length首部,不然客户端就无法知道响应什么时候结束
  1. 记录日志 最后,当事务结束时,Web服务器会在日志文件中添加一个条目,来描述已执行的事务

HTTP/1.1版本报文结构

  • 请求报文:请求消息包含以下元素:
  • 首先是HTTP方法,通常是个动词,如:GET,POST或是名词如OPTIONS,HEAD用来定义客户 端想要发出的动作。一般,客户
    端使用GET方法来从服务器获取资源,使用POST方法来 提交HTML表单的值个服务器,其它操作可能需要使用特定的方法。
  • 接着是需要获取的资源的连接,该连接一般不包括协议名(http://)或者域名(wordpress.suosuoli) 或TCP端口(80)。
  • HTTP协议的版本
  • 可选的头部信息,用于向服务器发送附加的信息
  • 也可能带有资源体,一般是涉及到POST方法时向服务器发送资源时会有
  • 响应报文包括以下元素:
  • HTTP协议版本
  • 一个状态码,指明该响应对应的请求成功与否,或则指明为什么
    失败。
  • 一句状态码消息,对该状态码的非权威的描述
  • HTTP响应头部
  • 可选的响应体,包含返回给客户端的资源
    HTTP请求方法,表明客户端希望服务器对资源执行的动作,包括以下:
    GET: 从服务器获取一个资源
    HEAD: 只从服务器获取文档的响应首部
    POST: 向服务器输入数据,通常会再由网关程序继续处理
    PUT: 将请求的主体部分存储在服务器中,如上传文件
    http协议状态码参考资料
    100-101 信息提示
    200-206 成功
    300-307 重定向
    400-415 错误类信息,客户端错误
    500-505 错误类信息,服务器端错误
    常见HTTP状态码
    200: OK 成功,请求数据通过响应报文的entity-body部分发送
    301: Moved Permanently 请求的URL指向的资源已经被删除;但在响应报文中通过首部Location指明了资源现在所处的新位置;
    302: Moved Temporarily 响应报文Location指明资源临时新位置304: Not Modified 客户端发出了条件式请求,但服务器上的资源未曾发生改变,则通过响应此响应状态码通知客户端;
    401: Unauthorized 需要输入账号和密码认证方能访问资源;:
    403: Forbidden 请求被禁止;
    404: Not Found服务器无法找到客户端请求的资源;
    500: Internal Server Error 服务器内部错误;
    502: Bad Gateway 代理服务器从后端服务器收到了一条伪响应,如无法连接到网关;
    503: 服务不可用,临时服务器维护或过载,服务器无法处理请求
    504: 网关超时
    首部的分类
  1. 通用首部:请求报文和响应报文两方都会使用的首部
  2. 请求首部:从客户端向服务器端发送请求报文时使用的首部。补充了请求的附加内容、客户端信
    息、请求内容相关优先级等信息
  3. 响应首部:从服务器端向客户端返回响应报文时使用的首部。补充了响应的附加内容,也会要求客
    户端附加额外的内容信息
  4. 实体首部:针对请求报文和响应报文的实体部分使用的首部。补充了资源内容更新时间等与实体有
    关的的信息
  5. 扩展首部

Apache

Apache特点

  • 高度模块化:core + modules
  • DSO:Dynamic Shared Object 动态加/卸载
  • MPM:multi-processing module 多路处理模块

Apache的几种工作模式?

三种模式都是 httpd 的 MPM 多路处理模块提供的功能,它们的主要区别为是否是基于线程级
的服务和请求处理,以及是否是多进程多线程 I/O 复用。

  1. prefork: 多进程 I/O 模型;在 prefork 模式中,随着 httpd 服务的启动,
    系统会创建一个父进程,该进程负责创建和启动固定数量的子进程且不响应请求,子进程则侦听请求并
    在请求到达时提供服务。Apache httpd 总是会试图维护几个备用或空闲的服务进程,它们
    随时准备为到来的请求提供服务。通过这种方式,客户端不需要等待新的子进程被创建,
    然后他们的请求才能被服务。该模式比较稳定,但是维护了一些空闲进程,会占内存。
  2. worker: 在 worker 模式中,随着 httpd 服务的启动,系统会创建一个 httpd
    主进程,该进程(父进程)负责创建子进程。每个子进程又会创建一个固定数量的服务
    线程(在ThreadsPerChild指令中指定),以及一个监听器线程,监听请求并在请求
    到达时将其传递给服务线程进行处理。Apache HTTP 服务器会维护一个备用或空闲的服务线程池,随时为传入的请求提供服务。这样,客户端就不需要等待新线程或
    新进程的创建,然后才能处理他们的请求。初始启动的进程数量由StartServers指令
    设置。在提供服务期间,服务器会评估所有进程中空闲线程的总数,并 fork(创建)或
    杀死进程,以将这个数保持在MinSpareThreadsMaxSpareThreads指定的范围内。
    这个过程是自动调节的,一般不用手动修改。通过使用线程来服务请求,与基于进程的
    服务相比,它能够使用更少的系统资源来服务大量请求。它通过保持多个进程可用(每个
    进程又有多个线程),在很大程度上保持了基于进程的服务(prefork)的稳定性。
  3. event: 事件驱动模型,基于 worker 模型发展而来。它实现了混合的多进程多线程服务
    模型,httpd 服务会创建一个父进程,该父进程创建多个子进程,每个子进程创建固定数量
    的服务线程(ThreadsPerChild指令中指定),以及一个监听线程(监听请求并在请求到达
    时将其传递给服务线程进行处理)。在该模式中,在客户端完成第一个请求后,可以保持
    连接打开,客户端使用相同的套接字发送更多的请求,这样在创建 TCP 连接时可以节省大量
    开销。然而,Apache HTTP 服务器以往的做法是保持整个子进程/线程等待来自客户端的请求,
    这样会浪费资源,且在高并发下性能跟不上。为了解决这个问题,event 模式的每个进程
    使用一个专用的监听线程来处理:在监听的sockets、处于keep-alive状态的所有sockets
    以及完成大部分工作只剩发送数据给客户端的sockets。这样就提高了在高并发场景下的处理能力。
    APR:Apache portable Run-time libraries,Apache可移植运行库,主要为上层的应用程序提供一个可以
    跨越多操作系统平台使用的底层支持接口库。在早期的Apache版本中,应用程序本身必须能够处理各
    种具体操作系统平台的细节,并针对不同的平台调用不同的处理函数随着Apache的进一步开发,
    Apache组织决定将这些通用的函数独立出来并发展成为一个新的项目。这样,APR的开发就从Apache
    中独立出来,Apache仅仅是使用 APR而已。目前APR主要还是由Apache使用,由于APR的较好的移植
    性,因此一些需要进行移植的C程序也开始使用APR,开源项目:比如用于服务器压力测试的Flood
    loader tester,项目站点:http://httpd.apache/test/flood

十. LAMP/LNMP

十一. LVS

集群:同一个业务系统,部署在多台服务器上。集群中,每一台服务器实现的功能没有差别,数据和代码都是一样的
分布式:一个业务被拆成多个子业务,或者本身就是不同的业务,部署在多台服务器上。分布式中,每一台服务器实现的功能是有差
别的,数据和代码也是不一样的,分布式每台服务器功能加起来,才是完整的业务。一句话:分布式是通过缩短单个任务的执行时间来提高效率,集群是通过增加单位时间内执行的任务数量来提高效率。
查看内核对LVS的支持

grep -i -C 10 ipvs /boot/config-$(uname -r)

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

LVS 集群的工作模式

LVS 有四种工作模式

  • lvs-nat:修改请求报文的目标 IP,多目标 IP 的 DNAT
  • lvs-dr:操纵封装新的 MAC 地址
  • lvs-tun:在原请求 IP 报文之外新加一个 IP 首部
  • lvs-fullnat:修改请求报文的源和目标 IP

LVS 的 NAT 工作模式

vs-nat:本质是多目标 IP 的 DNAT,通过将请求报文中的目标地址和目标端口修改为某挑出的 RS 的 RIP 和 PORT 实现转发,特
点:

  • (1)RIP 和 DIP 应在同一个 IP 网络,且应使用私网地址;RS 的网关要指向 DIP
  • (2)请求报文和响应报文都必须经由 Director 转发,Director 易于成为系统瓶颈
  • (3)支持端口映射,可修改请求报文的目标 PORT
  • (4)VS 必须是 Linux 系统,RS 可以是任意 OS 系统

LVS 的 DR 模式

LVS-DR:Direct Routing,直接路由,LVS 默认模式,应用最广泛,通过为请求报文重新封装一个 MAC 首部进行转发,源 MAC 是
DIP 所在的接口的 MAC,目标 MAC 是某挑选出的 RS 的 RIP 所在接口的 MAC 地址;源IP/PORT,以及目标 IP/PORT 均保持不变
DR 模式的特点

  1. Director 和各 RS 都配置有 VIP
  2. 确保前端路由器将目标 IP 为 VIP 的请求报文发往 Director
    需要在前端网关做静态绑定 VIP 和 Director 的 MAC 地址
    在 RS 上使用 arptables 工具
arptables -A OUT -s $VIP -j mangle --mangle-ip-s $RIP

在 RS 上修改内核参数以限制 arp 通告及应答级别[^1]

/proc/sys/net/ipv4/conf/all/arp_ignore
/proc/sys/net/ipv4/conf/all/arp_announce
  1. RS 的 RIP 可以使用私网地址,也可以是公网地址;RIP 与 DIP 在同一 IP网络;RIP 的网关不能指向 DIP,以确保响应报文不会经由
    Director
  2. RS 和 Director 要在同一个物理网络
  3. 请求报文要经由 Director,但响应报文不经由 Director,而由 RS 直接发往 Client
  4. 不支持端口映射(端口不能修败)
  5. RS 可使用大多数 OS 系统

LVS 的 TUN 模式

转发方式:不修改请求报文的 IP 首部(源 IP 为 CIP,目标 IP 为 VIP),而在原 IP 报文之外再封装一个 IP 首部(源 IP 是 DIP,目标
IP 是 RIP),将报文发往挑选出的目标 RS;RS 直接响应给客户端(源 IP 是 VIP,目标 IP 是 CIP)
TUN 模式特点

  1. DIP, VIP, RIP 可以是公网地址
  2. RS 的网关一般不能指向 DIP
  3. 请求报文要经由 Director,但响应不经由 Director
  4. 不支持端口映射
  5. RS 的 OS 须支持隧道功能

LVS 的 FULLNAT 模式

通过同时修改请求报文的源 IP 地址和目标 IP 地址进行转发
CIP --> DIP
VIP --> RIP
fullnat 模式特点
  1. VIP 是公网地址,RIP 和 DIP 是私网地址,且通常不在同一 IP 网络;因此,
    RIP 的网关一般不会指向 DIP
  2. RS 收到的请求报文源地址是 DIP,因此,只需响应给 DIP;但 Director 还
    要将其发往 Client
  3. 请求和响应报文都经由 Director
  4. 支持端口映射
    注意:此类型 kernel 默认不支持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 头实现转发,支持远距离通信

LVS 调度算法

静态调度算法

Round-Robin Scheduling

循环调度算法:该调度算法是 LVS 最简单的调度策略,其将每个传入的请求发送到其列表的下一个服务器。例如:在三个服务器的集
群中(A、B 和 C 服务器),请求 1将被 调度到服务器 A,请求 2 将被调度到服务器 B,请求 3 将被 C 服务器处理,请求 4 又被调度到服务
器 A。从而让集群中的服务器循环的提供服务,该调度策略平等对待所有服务器,其不考虑到来的连接或请求数量和服务器的响应时间(或
是负载情况),其性能高于传统的 DNS 轮询。

Weighted Round-Robin Scheduling

加权轮询调度算法:该调度策略旨在处理不同的性能的服务器在接收请求和处理请求时的权重呵呵优先顺序。在调度期间,每个服务
器的性能有差异,各自会被分配不同的权重值,一个表示处理能力的整数值。权重较大的服务器比权重小的服务器优先处理新连接,权重
较大的服务器也比权重较小的服务器获得更多的连接,权重相同的服务器获得相同的连接数。例如:RS 服务器分别为 A、B 和 C,它们分
别有权重4、3 和 2,那么一个比较理想的调度序列会是 AABABCABC,可以看到在三次轮询过程中,不同权重的 RS 被分配了不同的连接
数,但是整个过程中各自总的处理连接比例接近 4:3:2(A 处理了 4 次,B 处理了 3 次,C 处理了 2 次)。
在加权轮询调度的实现中,在管理员修改 VS 的服务器调度策略后,会根据具体的配置权重来生成调度序列。基于该序列以轮询调度方式
将不同的网络连接定向到后端不同的 RS 服务器。
当后端 RS 服务器的处理性能不一时,此算法是比单纯的轮询调度算法优异的。但是,如果有不间断的高负载的请求到来时,可能造成各
服务器的动态负载严重不均衡。也就是说,可能会有很大部分的高负载请求都被调度到相同的 RS 上。此时,该 RS的复制将会很高。
而实际上,轮询是加权轮询策略的特例,即是加权轮询的权重值都为 1 的情况。

Destination Hashing Scheduling

目标地址哈希调度: 目标地址散列调度(Destination Hashing Scheduling)算法也是针对目标IP地址的负载均衡,但它是一种静态
映射算法,通过一个散列(Hash)函数将一个目标IP地址映射到一台服务器。 目标地址散列调度算法先根据请求的目标IP地址,作为散
列键(Hash Key)从静态分配的散列表找出对应的服务器,若该服务器是可用的且未超载,将请求发送到该服务器,否则返回空。该调度
策略的典型使用场景是正向代理缓存场景中的负载均衡,如:宽带运营商

Source Hashing Scheduling

源地址散列调度(Source Hashing Scheduling)算法正好与目标地址散列调度算法相反,它根据请求的源IP地址,作为散列键
(Hash Key)从静态分配的散列表找出对应的服务器,若该服务器是可用的且未超载,将请求发送到该服务器,否则返回空。它采用的散列函数与目标地址散列调度算法的相同。
在实际应用中,源地址散列调度和目标地址散列调度可以结合使用在防火墙集群中,它们可以保证整个系统的唯一出入口

动态调度算法

Least-Connection Scheduling

最少连接调度算法:该调度策略将网络连接调度给建立连接数量最少的 RS 服务器。该调度算法会动态的记录各服务器的活动连接
数。如果后端的一群 RS 服务器的处理性能相近,那么最少连接调度策略有助于在请求的负载有大幅变化时平滑分配负载。VS 将把请求定
向到有最少的活动连接的 RS。乍一看,该调度策略应该可以在服务器的处理性能各不相同的情况下表现良好,因为处理性能高的服务器会
接到更多的请求并处理。然而由于 TCP 协议的TIME_WAIT状态导致了其表现并不好。TCP 定义的TIME_WAIT状态为 2 分钟,在这两
分钟内某个繁忙的站点可能已经收到了好几千个请求。例如:RS-A 的处理性能是 RS-B 的两倍,RS-A 可能已经按照正常的速度完成了这
些请求并将它们保持在了TIME_WAIT状态(也就是在 2 分钟内完成了分配的请求),而 RS-B 正挣扎着完成分配到自己的好几千的请求,
在这种情况下就没法均衡负载了。

Weighted Least-Connection Scheduling

加权的最少连接调度算法:该调度策略允许为每个 RS 服务器分配一个性能权重值。具有较高权重值的服务器在任何时候都将接收到
较大百分比的活动连接。VS 管理员可以给每个 RS 服务器分配一个权重值,网络连接会被调度到每个 RS 服务器并且每个服务器的当前活
动连接数的百分比与其权重成比例。
加权的最少连接调度算法相比最少连接算法需要额外的除法来区分不同比例。为了在服务器处理能力接近相同的情况下最小化调度工作的
开销,才有了非加权和加权的最少连接算法。

Locality-Based Least-Connection Scheduling

基于位置的最少连接调度算法:该调度策略专用于目的 IP 的负载均衡。一般被用于缓存集群。该算法通常会将到某个 IP 地址的数据
包定向到负责处理其的服务器,前提是该服务器是活动的并且负载不高。如果该服务过载(活动连接数大于其权重)并且有一个服务器目前
只有一半的负载,那就会将加权的最少连接服务器(一般负载的服务器)分配给该 IP。

Locality-Based Least-Connection with Replication Scheduling

基于位置的带复制的最少连接调度算法:该调度策略也用于目的 IP 的负载均衡。一般用于缓存集群。在以下方面其和 LBLC 调度策略不
同:调度服务器 LVS 会维护某个目的 IP 到可能为其提供服务的服务器节点的映射。发往目标 IP 的请求将被调度到该目标 IP 对应的服务
器节点集中拥有最少连接的节点。如果该服务器集的节点均过载,那么 LVS 会在整个集群中挑选一个最少连接的节点加入到这个服务器集
(可能为该目标IP 服务的节点集合)。

Shortest Expected Delay Scheduling

最短期望时延调度算法:该算法将网络连接调度分配给预期最小时延的服务器。发送到集群中的第 i 个节点最小时延可表示为:(Ci +
1)/Ui。Ci 是第 i 个服务器的连接数,Ui 是第 i 个服务器的权重。

Never Queue Scheduling

不排队算法: 该调度策略在有空闲的服务器时将请求调度到该空闲服务器。如果没有空闲服务器,请求将被调度到最少期望时延的节点
(SED)。

内核版本 4.15 版本后新增调度算法:FO 和 OVF

FO(Weighted Fail Over)调度算法,在此 FO 算法中,会遍历虚拟服务所关联的真实服务
器链表,找到还未过载(未设置 IP_VS_DEST_F_OVERLOAD 标志)的且权重最高的真实服务器,进行调度
OVF(Overflow-connection)调度算法,基于真实服务器的活动连接数量和权重值实现。
将新连接调度到权重值最高的真实服务器,直到其活动连接数量超过权重值位置,之后调
度到下一个权重值最高的真实服务器,在此 OVF 算法中,遍历虚拟服务相关联的真实服务器
链表,找到权重值最高的可用真实服务器。一个可用的真实服务器需要同时满足以下条件:

  • 未过载(未设置 IP_VS_DEST_F_OVERLOAD 标志)
  • 真实服务器当前的活动连接数量小于其权重值
  • 其权重值不为零

ipvsadm

ipvsadm 工具用法

# 管理集群服务
ipvsadm -A|E -t|u|f service-address [-s scheduler] [-p [timeout]] [-M netmask]
[--pe persistence_engine] [-b sched-flags]
ipvsadm -D -t|u|f service-address # 删除
ipvsadm –C # 清空
ipvsadm –R # 重载
ipvsadm -S [-n] # 保存
# 管理集群中的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]

保存规则:建议保存至/etc/sysconfig/ipvsadm

ipvsadm-save > /PATH/TO/IPVSADM_FILE
ipvsadm -S > /PATH/TO/IPVSADM_FILE
systemctl stop ipvsadm.service

重新载入

ipvsadm-restore < /PATH/FROM/IPVSADM_FILE
systemctl restart ipvsadm.service

LVS 借助三方软件实现高可用

如果 LVS 服务器宕机时,整个集群的服务将终止
当整个集群的调度由一台 LVS 主机负负责时,Director 不可用时,整个集群的服务
将会终止,这是常见的 SPOF 单点故障,所以在生产中不会只用一台 LVS,而是使用其它
技术实现 LVS 调度器的高可用,比如:keepalived heartbeat 或者 corosync
当某 RS 宕机时,Director 还会往其调度请求: 如果只使用 LVS 进行调度,当某 RS 宕机时,Director 还会往其调度请求。这回造成部分请
求未被处理,还会使得 LVS 付出额外无用的开销。
解决方案:通过某些方法使得 Director 可以对各个 RS 进行健康状态监测,失败时自动禁用,
恢复后自动添加到集群。
常用的高可用解决方案
keepalived
heartbeat/corosync
ldirectord
检测 RS 的方式
网络层检测,icmp
传输层检测,端口探测
应用层检测,请求某关键资源
RS 全部宕机或者是维护时:可以使用 LVS 调度器来响应客户,告知大致情况,此时的服务称为:
sorry server(道歉服务)

十二. Nginx

十三. HAProxy

十四. Tomcat

目录结构说明

目录该目录下文件说明
bin/存放 Tomcat 的启动、停止等脚本文件和批处理文件
startup.sh/startup.batLinux 下的启动脚本/Win 下的启动脚本
shutdown.sh/shutdown.batLinux 下/Win 下的停止脚本
conf/存放配置文件
catalina存放针对每个虚拟机的 Context 的配置
context.xml用于定义 web 应用都需要加载的 Context 配置,如果 web 应用指定了自己的 context.xml,该文件内容
将被覆盖
catalina.propertiesTomcat 的环境变量,Java 属性的定义文件,用于设定类加载器路径,以及一些与 JVM 调优相关参数
catalina.policyTomcat 运行的安全策略配置
loggging.propertiesTomcat 的日志配置文件,可以通过该文件修改 Tomcat 的日志级别和日志路径等
server.xmlTomcat 的核心配置文件
tomcat-user.xml定义 Tomcat 默认的用户及角色映射信息的配置
web.xmlTomcat 中所有应用默认的部署描述文件,主要定义了基础的 Servlet 和 MIME 映射关系
lib/Tomcat 服务器的依赖包存放处
logs/Tomcat 默认的日志存放目录
webapps/Tomcat 默认的 Web 应用部署目录
work/Web 应用 JSP 代码生成和编译使用的临时目录

顶级组件

  • Server,代表整个 Tomcat 容器
    服务类组件
  • Service,组织 Engine 和 Connector,里面只能包含一个 Engine
    连接器组件
  • Connector,有 HTTP、HTTPS、AJP 协议的连接器
    容器类
  • Engine、Host、Context 都是容器类组件,可以嵌入其它组件,内部配置如何运行应用程序。
    内嵌类:可以内嵌到其他组件内
  • valve、logger、realm、loader、manager 等
    集群类组件
  • listener、cluster

Tomcat架构细节

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9uybRddJ-1604371435769)(png/tomcat-arch.png)]

从请求的处理过程看总体架构

  1. 在图 1 中,HTTP 服务器直接调用具体的业务类,应用程序和服务器是紧耦合的。这种架构下,HTTP 需要接受不同的请求,并根据不
    同的请求调用不同的类来处理请求,HTTP 服务器将会长期高负载工作,而且必须打通和各个类的调用接口。
  2. 在图 2 中,HTTP 服务器不直接调用业务类,而是把请求交给容器来处理,容器通过Servlet 接口调用业务类。因此 Servlet 接口和
    Servlet 容器的出现,使得 HTTP服务器和各业务类解耦。而 Servlet 接口和 Servlet 容器遵循 Servlet 规范。Tomcat 按照 Servlet 规范
    的要求实现了 Servlet 容器,同时他们也具有 HTTP服务器的功能。如果要实现新的业务功能,只要实现一个 Servlet,并将其注册到
    Tomcat(Servlet 容器)中,剩下的就有 Tomcat 处理了。

Servlet 容器工作流程

当用户请求某个资源时,HTTP 服务器会用一个 ServletRequest 对象把客户的请求信息封装起来,然后调用 Servlet 容器的 service 方
法,Servlet 容器拿到请求后,根据请求的 URL 和 Servlet 的映射关系,找到相应的 Servlet,如果 Servlet 还没有被加载,就用反射机制
创建这个 Servlet,并调用 Servlet 的 init 方法来完成初
始化,接着调用 Servlet 的 service 方法来处理请求,把 ServletResponse 对象返回给 HTTP 服务器,HTTP 服务器把响应发送给客户
端。

架构细节

Coyote(connector)连接器Coyote 是 Tomcat 的连接器框架名称,是 Tomcat 服务器提供的供客户端访问的外部接口。客户端通过 Coyote 与服务器建立连接、发

送请求并接受响应。

Coyote 与 Catalina 的交互

Coyote 封装了底层的网络通信(Socket 请求及响应处理),为 Catalina 容器提供了统一的接口,使得 Catalina 容器与具体的请求协议及
IO 操作方式完全解耦。Coyote 将 Socket 输入转换封装为 Request 对象,交由 Catalina 容器进行处理,处理完成后,Catalina 通过
Coyote 提供的 Response 对象将结果写入输出流。
Coyote 作为独立模块,只负责具体的协议和 IO 相关操作,与 Servlet 规范实现没有直接关系,因此即便是 Request 和 Response 对象
也并未实现 Servlet 规范对应的接口,而是在 Catalina 中将它们进一步封装为 ServletRequest 和ServletResponse。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2YwEPjFi-1604371435770)(https://suosuoli/wp-content/uploads/2020/01/Coyote%E4%B8%8ECatalina%E7%9A%84%E4%BA%A4%E4%BA%92.png)]

IO 模型与协议

在 Coyote 中,Tomcat 支持多种 I/O 模块和应用层协议,下表为 Tomcat 支持的 IO
模型 (自 8.9/9.0 版本其,Tomcat 移除了对 BIO 的支持)

IO 模型描述
NIO非阻塞 I/O,采用 Java NIO 类库实现
NIO2异步 I/O,采用 JDK 7 最新的 NIO2 类库实现
APR采用 Apache 可移植运行库实现,是 c/c++编写的本地库。如果选择该模型,需要安装 APR 库

Tomcat 支持的应用层协议:

协议说明
HTTP/1.1这是大部分 web 应用采用的协议
AJP用于和 web 服务器集成(Apache),以实现对静态资源的优化以及集群部署,当前支持 AJP/1.3
HTTP/2HTTP 2.0 大幅度的提升了 web 性能。Tomcat8.5 以及 9.0 版本后支持。

Tomcat 中的协议层关系

应用层HTTPAJPHTTP2
传输层NIONIO2APR

Tomcat 为了实现支持多种 I/O 模型和应用层协议,一个容器可以对接多个连接器。但是单独的连接器或者容器都不能对外提供服务,需
要把它们组合起来才能够工作,组合后的整体叫做 Service 件。值得注意的是,Service 本并没有涉及到请求的处理,只是起到封装
连接器和容器的作用
。Tomcat 服务器可以配置多个 service,这样的设计处于灵活性考虑。这样可以通过在 Tomcat 配置多个
Service,通过不同的端口来访问同一台服务器上部署的不同应用。

连接器组件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xtW0afjq-1604371435770)(https://suosuoli/wp-content/uploads/2020/01/%E8%BF%9E%E6%8E%A5%E5%99%A8%E7%BB%84%E4%BB%B6.png)]
连接器组件中的各个子组件的作用:Endpoint

  1. Endpoint 为 Coyote 的通讯端点,及监听通讯的接口,是具体的 Socket接收和发送处理器,是对传输层的抽象,因此 Endpoint 是用
    来实现 TCP/IP协议的。
  2. Tomcat 并没有 Endpoint 接口,而是提供了一个抽象类 AbstractEndpoint,里面定义了两个内部类:Acceptor 和
    SocketProcessor。Acceptor 用于监听 Socket请求。SocketProcessor 用于处理接收到的 Socket 请求,它实现 Runnable 接口,在
    Run 方法里面调用协议处理组件 Processor 进行处理。为了提高处理性能,SocketProcessor 被提交到线程池来执行。而这个线程池叫
    做执行器(Executor)

    Processor
    Processor 是 Coyote 协议处理接口,Endpoint 实现 ICP/IP 协议,而 Processor则是 HTTP 协议的抽象。Processor 接收来自
    Endpoint 的 Socket,读取字节流解析成 Tomcat Request 和 Response 对象,并通过 Adapter 将其提交到容器处理。
    ProtocolHandler
    ProtocolHandler 是 Coyote 协议接口,通过 Endpoint 和 Processor,实现针对具体协议的处理能力。Tomcat 安装协议和 I/O 提供了
    6 个实现类:AjpNioProtocol、AjpAprProtocol、AjpNio2Protocol、Http11NioProtocol、Http11Nio2Protocol、
    Http11AprProtocol。在配置 tomcat/conf/server.xml 时,至少要要指定具体的ProtocolHandler,当然也可以指定协议名称,如:
    HTTP/1.1,如果按照了 APR,那么
    将使用 Http11AprProtocol,否则,使用 Http11NioProtocol。
    Adapter
    由于协议的不同,客户端发送过来的请求信息也不同。Tomcat 定义了自己的 Request类来封装这些请求信息。ProtocolHandler 接口负
    责解析请求并生成 Tomcat Request 类。但是这个 Request 对象不是标准的 servletRequest ,也就意味着,不能用 Tomcat Request作为
    参数来调用容器。Tomcat 设计者的解决方案是引入 CoyoteAdapter ,这是适配器模式的经典运用,连接器调用 CoyoteAdapter 的
    Service 方法,传入的是 Tomcat Request 对象 ,CoyoteAdapter 负责将 Tomcat Request 转成 servletRequest ,再调用容器的 Service方
    法。

Catalina(servlet container)容器

Catalina 在 Tomcat 组件中的地位

Tomcat 是由一系列可配置的组件构成的 web 容器 ,而 catalina 实质就是 Tomcat 的servlet 容器。Catalina 是 servlet 容器的实现。它
通过松耦合的方式集成 coyote ,以完成按照请求协议进行数据读写。
Tomcat 分层结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gZU39dG0-1604371435771)(https://suosuoli/wp-content/uploads/2020/01/Catalina%E5%9C%B0%E4%BD%8D.png)]
Tomcat 本质上就是一款 servlet 容器,因此 catalina 才是 Tomcat 的核心,其他模块都是为 catalina 提供支撑的。 比如:通过 coyote
模块提供链接通信, Jasper 模块提供JSP 引擎, Naming 提供 JNDI 服务, Juli 提供日志服务。

Catalina 结构

Catalina 的主要组件结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4VCeu7qj-1604371435771)(https://suosuoli/wp-content/uploads/2020/01/Catalina%E7%BB%93%E6%9E%84.png)]
如上图所示,Catalina 负责管理 Server ,而 Server 表示着整个服务器。Server 下面有多个服务 service ,每个服务都包含着多个连接器组
件 connector ( coyote 实现)和一个容器组件 container。在 Tomcat 启动的时候,会初始化一个 catalina 的实例。Catalina各个组件的
作用:
| 组件 | 作用 || :-------- | :-------------------------------------------------------------------------------------------------------------------------------
-------------------------------------- |
| Catalina | 负责解析 romcat 的配置文件,以此来创建服务器 server 组件,并根据命令来对其进行管理
|
| Server | 服务器:表示整个 Catalina servlet 容器以及其它组件,负责组装并启动 servlet 引擎,Tomcat 连接器。server 通过实现
Lifecycle 接口,提供了一种优雅的启动和关闭整个系统的方式 |
| service | 服务是 server 内部的组件,一个 server 包含多个 service。 它将若干个 connector 组件绑定到一个 Container ( Engine)上
|
| Connector | 连接器,处理与客户端的通信,它负责接收客户请求,然后转给相关的容器处理,最后向客户返回响应结果
|
| Container | 容器,负责处理用户的 servlet 请求,并返回对象给 web 用户的模块
|

Container 结构

Tomcat 设计了 4 种容器,分别是 Engine、Host、 Context 和 Wrapper. 这 4 种容器不是平行关系,而是父子关系。Tomcat 通过分层的
架构使得 Servlet 容器具有很好的灵活性。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pou5g98J-1604371435772)(https://suosuoli/wp-content/uploads/2020/01/Container%E7%BB%93%E6%9E%84.png)]
各个组件的含义:

容器组件说明
Engine表示整个 catalina 的 servlet 引擎,用来管理多个虚拟站点, 一个 Service 最多只能有一个 Engine,但是一个引擎可包含多个
Host
Host代表一个虚拟主机,或者说一个站点。可以给 Tomcat 配置多个虚拟主机地址,而一个虚拟主机下可包含多个 Context
Context表示一个 Web 应用程序,一个 web 应用可包含多个 Wrapper
Wrapper表示一个 Servlet,Wrapper 作为容器中的最底层,不能包含子容器

我们也可以再通过 Tomcat 的 server.xm1 配置文件来加深对 Tomcat 容器的理解。Tomcat 采用了组件化的设计,它的构成组件都是可配
置的,其中最外层的是 server ,其他组件按照一定的格式要求配置在这个顶层容器中。配置框架如下:

<Server ... >
<Service ... >
<Connector ... > ... </Connector>
<Connector ... > ... </Connector>
<Engine ... >
<Realm ... > ... </Realm>
<Realm ... > ... </Realm>
<Host ... >
<Valve ... />
</Host>
</Engine>
</Service>
</Server>

Tomcat 启动流程Tomcat 的 UML 启动流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0SbEPioo-1604371435773)(https://suosuoli/wp-content/uploads/2020/01/%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8BUML.png)]
步骤:

  1. 启动 tomcat ,需要调用 bin/startup.sh (在 windows 目录下,需要调用bin/startup.bat) ,在 startup.sh 脚本中,调用了
    catalina.sh。
  2. 在 catalina.sh 脚本文件中,调用了 Bootstrap 中的 main 方法。
  3. 在 Bootstrap 的 main 方法中调用了 init 方法,来创建 catalina 及初始化类加载器。
  4. 在 BootStrap 的 main 方法中调用了 load 方法,在其中又调用了 Catalina的 load 方法。
  5. 在 catalina 的 load 方法中,需要进行一些初始化的工作,并需要构造 Digester对象,用于解析 XML 配置文件。
  6. 然后在调用后续组件的初始化操作…
    最后加载 romcat 的配置文件,初始化容箭组件监听对应的端口号 ,准备接受客户端请求 。

Tomcat 处理请求的流程

设计了这么多层次的容器, Tomcat 是怎么确定每一个请求应该由哪 个 Wrapper 容器里的 servlet 来处理的呢?
答案是:Tomcat 是用 Mapper 组件来完成这个任务的。
Mapper 组件的功能就是将用户请求的 URL 定位到一个 servlet ,它的工作原理是:Mapper 组件里保存了 web 应用的配置信息,其实就是
容器组件与访问路径的映射关系,比如 Host 容器里配置的域名、Context 容器里的 web 应用路径,以及 wrapper 容器里 servlet 映射的路
径,你可以想象这些配置信息就是一个多层次的 Map。
当一个请求到来时, Mapper 组件通过解析请求 URI 里的域名和路径,再到自己保存的Map 里去查找,就能定位到一个 servlet。 请注意,一
个 URL 最后只会定位到一个Wrapper 容器,也就是一个 servlet.
下面的示意图中, 就描述了 当用户请求链接 http://www.suosuoli/articles/tomcat之后, 是如何找到最终处理业务逻辑的 servlet 的
过程。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sGup9VhF-1604371435773)(https://suosuoli/wp?content/uploads/2020/01/%E5%A4%84%E7%90%86http%E8%AF%B7%E6%B1%82%E8%AF%A6%E7%BB%86%E6%B5%81%E7%A8%8B.png)]
上面这幅图只是描述了根据请求的 URI 如何查找到需要执行的 servlet,下面再来解析一下从 Tomcat 的设计架构层面来分析 Tomcat 的
请求处理。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3R3G4Llq-1604371435774)(https://suosuoli/wp?content/uploads/2020/01/%E4%BB%8E%E6%9E%B6%E6%9E%84%E7%9C%8BHTTP%E8%AF%B7%E6%B1%82%E5%A4%84%E7%90%86.png)]
具体步骤如下:

  1. Connector 组件 Endpoint 中的 Acceptor 监听客户端套接字连接并接收 socket。
  2. 将连接交给线程池 Executor 处理 ,开始执行请求响应任务。
  3. Processor 组件读取消息报文,解析请求行、请求体、请求头,封装成 Request 对象。
  4. Mapper 组件根据请求行的 URL 值和请求头的 Host 值匹配由哪个 Host 容器、
    Context 容器、 Wrapper 容器处理请求。
  5. CoyoteAdaptor 组件负责将 Connector 组件和 Engine 容器关联起来 ,把生成的
    Request 对象和响应对象 Response 传递到 Engine 容器中,调用 Pipeline.
  6. Engine 容器的管道开始处理,管道中包含若干个 valve、每个 valve 负责部分处理
    逻辑。执行完 Valve 后会执行基础的 valve,即 StandardEnginevalve ,其负责
    调用 Host 容器的 pipeline.7. Host 容器的管道开始处理,流程类似,最后执行 Context 容器的 Pipeline.
  7. Context 容 器的管道开始处理,流程类似,最后执往 wrapper 容器的 Pipeline。
  8. wrapper 容器的管道开始处理 ,流程类似,最后执行 Wrapper 容器对应的 Servlet
    对象的处理方法。

Jasper 引擎

对于基于 JSP 的 web 应用来说,我们可以直接在 Jsp 页面中编写 Java 代码,添加第三方的标签库,以及使用 EL 表达式。但是无论经过何种
形式的处理,最终输出到客户端的都是标准的 HTMI 页面(包含 js , css… ) ,并不包含任何的 java 相关的语法。也就是说,我们可以把 jsp 看
做是一种运行在服务端的脚本。那么服务器是如何将 Jsp 页面转换为HTML 页面的呢?
Jasper 模块是 Tomcat 的 JsP 核心引擎,我们知道 JSP 本质上是一个 servlet。Tomcat 使用 Jasper 对 Jsp 语法进行解析,生成 servlet 并
生成 Class 字节码,用户在进行访问 jsp 时,会访问 Servlet ,最终将访问的结果直接响应在浏览器端。另外,在运行的时候, Jasper 还会检 Jsp
文件是否修改,如果修改,则会重新编译 Jsp 文件。
一句话:Jasper 解析.JSP页面并生成对应的Servlet,最终编译为.class字节码文件

Tomcat集群

Tomcat 集群介绍

在实际生产环境中,单台 Tomcat 服务器的负载能力或者说并发能力在四五百左右。大部分情况下随着业务增长,访问量的增加(并发量不
止四五百),单台 Tomcat 服务器是无法承受的。这时就需要将多台 Tomcat 服务器组织起来,共同分担负载。
所以在生产环境中,一部分会使用单机部署方式,这样的部署方式在比较小的公司会见到,大部分会使用多机部署。具体的 Tomcat 部署
架构有以下几种:

  1. 单台 Tomcat 服务器部署时称为单机部署
    Tomcat 单独运行,直接接受用户的请求
  2. 单台 Tomcat 服务器加上 Nginx 或者 Httpd 作为反向代理
    反向代理,单机运行,提供了一个 Nginx 作为反向代理,可以做到静态由 nginx
    提供响应,动态 jsp 代理给 Tomcat,如 LNMT 或者 LAMT 架构
  • LNMT:Linux + Nginx + MySQL + Tomcat
  • LAMT:Linux + Apache(Httpd)+ MySQL + Tomcat
  1. 使用 Nginx 反向代理多台 Tomcat 服务器
    前置一台 Nginx,给多台 Tomcat 实例做反向代理和负载均衡调度,Tomcat 上
    部署的纯动态页面更适合
  • LNMT:Linux + Nginx + MySQL + Tomcat
  1. 使用 Nginx 反向代理多台 Tomcat 服务器,在每台 Tomcat 服务器又使用 Nginx
    接收请求,如 LNNMT
  • LNNMT:Linux + Nginx + Nginx + MySQL + Tomcat

负载均衡策略

  1. 轮询
    加权轮询是 upstream 模块默认的负载均衡策略。每个请求会按时间顺序逐一分配到不同的后端服务器。默认情况下每台服务器的权重
    为 1,若服务器硬件性能差别比较大,则需要为不同的服务器分配不同的权重。
upstream serverpool{
server localhost:8000; # server指令指明后端服务器
server localhost:9000;server www.suosuoli weight=1 fail_timeout=5s max_fail=3;
}
server {
listen 88;
server_name localhost;
location / {
proxy_pass http://serverpool/;
}
}

upstream 模块中的 server 指令参数说明:

参数描述
-----
fail_timeout与 max_fails 结合使用
max_fails设置在 fail_timeout 参数设置的时间内最大失败次数,如果在这个时间内,所有针对该服务器的请求都失败了,那么认为该服
务器会被访为是停机了
fail_time服务器会被认为停机的时间长度,默认为 10s
backup标记该服务器为备用服务器。当主服务器停止时,请求会被发送到它这里
down标记服务器永久停机
  1. ip_hash
    指定负载均衡器按照基于客户端 IP 的分配方式,这个方法确保了相同的客户端的请求一直发送到相同的服务器,以保证客服端和服务器的
    session 会话。这样每个访客都固定访问一个后端服务器,可以解决 session 不能跨服务器的问题
upstream serverpool{
1p_hash;
server 192.168.192.122:8080 weight=3;
server 192.168.192.133:8080 weight=1;
}

Tomcat 的 session 共享

当单机的 Tomcat,演化出多机多级部署的时候,一个问题便凸显出来,这就是Session。而这个问题的由来,是由于 HTTP 协议在设计
之初没有想到未来的发展。
HTTP 的无状态、有连接和短连接特性

  • 无状态:指的是服务器端无法知道 2 次请求之间的联系,即使是前后 2 次请求来自同一个浏览器,也没有任何数据能够判断出是同一个
    浏览器的请求。后来可以通过 cookie、session 机制来判断。
  • 浏览器端第一次通过 HTTP 请求服务器端时,在服务器端使用 session 技术,
    就可以在服务器端产生一个随机值即 SessionID 发给浏览器端,浏览器端收到
    后会保持这个 SessionID 在 Cookie 当中,这个 Cookie 值一般不能持久存
    储,浏览器关闭就消失。浏览器在每一次提交 HTTP 请求的时候会把这个
    SessionID 传给服务器端,服务器端就可以通过比对知道当前是谁访问。
  • Session 通常会保存在服务器端内存中,如果没有持久化,则易丢失
  • Session 会定时过期。过期后浏览器如果再访问,服务端发现没有此 ID,将给浏览器端重新发新的 SessionID
  • 更换浏览器也将重新获得新的 SessionID
  • 有连接:是因为 HTTP1.x 基于 TCP 协议,是面向连接的,需要 3 次握手、4 次挥手断开。
  • 短连接:Http 1.1 之前,都是一个请求一个连接,而 Tcp 的连接创建销毁成本高,对服务器有很大的影响。所以,自 Http 1.1 开始,
    支持 keep-alive,默认也开启,一个连接打开后,会保持一段时间(可设置),浏览器再访问该服务器就使用这个 Tcp 连接,减轻了服务
    器压力,提高了效率。
    如果应用需要用户进行登录,Nginx 作为反向代理服务器代理后端 Tomcat 接收请求,那么这个时候,就会出现 Session 相关的问题。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0JO4ra4R-1604371435774)(https://suosuoli/wp?content/uploads/2020/02/Nginx%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E9%BB%98%E8%AE%A4%E8%B0%83%E5%BA%A6.png)]
    解决这种由于反向代理服务而导致的使得同一个客户端的请求找不到自己对应的Session 的问题可以使用 Session 共享机制。有以下几种
    方案:

ip_hash 策略

由一个用户发起的请求,只会被调度到特定的 TomcatA 服务器上进行处理,另一个用户发起的请求只在 TomcatB 上进行处理。那么这
个时候,同一个用户发起的请求,都会通过 nginx 的 ip_hash 策略,将请求转发到其中的一台 Tomcat 上。在 Nginx 的反向代理中
ip_hash 策略也叫 source ip,即源地址哈希;

Session 复制集群

Session 复制的原理是通过 Tomcat 集群的内部多播机制将所有的不同 Session 在集群的所有 Tomcat 主机上复制,即所有 Tomcat 服务
器都存有当前的所有 Session信息。
缺点

  • Tomcat 的同步节点不宜过多,互相即时通信同步 session 需要太多带宽
  • 每一台都拥有全部 session,内存占用太多

Session Server

session 共享服务器,共享的 Session 服务器使用 memcached 或者 redis 做存储,来存储 session 信息,供 Tomcat 服务器查询。
使用不同技术实现了 session 持久机制

  1. session 绑定,基于 IP 或 session cookie 的。其部署简单,尤其基于
    session 黏性的方式,粒度小,对负载均衡影响小。但一旦后端服务器有故障,
    其上的 session 丢失。
  2. session 复制集群,基于 tomcat 实现多个服务器内共享同步所有 session。
    此方法可以保证任意一台后端服务器故障,其余各服务器上还都存有全部 session,
    对业务无影响。但是它基于多播实现心跳,TCP 单播实现复制,当设备节点过多,
    这种复制机制不是很好的解决方案。且并发连接多的时候,单机上的所有 session
    占据的内存空间非常巨大,甚至耗尽内存。
  3. session 服务器,将所有的 session 存储到一个共享的内存空间中,使用多个
    冗余节点保存 session,这样做到 session 存储服务器的高可用,且占据业务服
    务器内存较小。是一种比较好的解决 session 持久的解决方案。
    以上的方法都有其适用性。生产环境中,应根据实际需要合理选择。不过以上这些方法都是在内存中实现了 session 的保持,可以使用数据库或者文件系统,把 session 数据存储起来,持久化。这样服务器
    重启后,也可以重新恢复session 数据。不过 session 数据是有时效性的,是否需要这样做,视情况而定。

Tomcat性能调优

对于系統性能,用户最直观的感受就是系统的加载和操作时间,即用户执行某项操作的耗时。从更为专业的角度上讲,性能测试可以从以下两
个指标量化。

  1. 响应时间:执行某个操作的耗时。大多数情况下,我们需要针对同一个操作测试多次,
    以获取操作的平均响应时间。
  2. 吞吐量:即在给定的时间内,系统支持的事务数量,计算单位为 TPS.
    通常情况下,我们需要借助于一些自动化工具来进行性能测试,因为手动模拟大量用户的并发访问几乎是不可行的,而且现在市面上也有很多
    的性能测试工具可以使用,如:ApacheBench、ApacheJMeter、 WCAT、webpolygraph、LoadRunner。

ApacheBench

ApacheBench(ab)是一款 ApacheServer 基准的测试工具,用户测试 Apache server 的服务能力 (每秒处理请求数), 它不仅可以用户
Apache 的测试。还可以用于测试 TomcatNginx、lighthttp、IIS 等服务器。

  1. 安装
    yum instll httpd-tools
  2. 安装后查看版本号
    ab -V
  3. 测试
    ab -n 1000 -e 100 -p data.json -T applicat1on/json http://1ocalhost:9000/articles/search.do?cat=Tomcat&topic=test
  4. data.json
{ "Tomcat": "test", "Nginx": "ReverseProxy" }

参数说明

参数含义描述
-n在测试会话中所执行的请求个数,默认只执行一-次请求
-c一次产生的请求个数,默认一次一个
-p包含了需要 post 的数据文件
-t测试所进行的最大秒数,默认没有时间限制
-TPOST 数据所需要使用的 Content-Type 头信息

测试结果指标说明

指标含义
---------------------------------------------
Server Software服务器软件
Server Hostname
Server Port端口号
Document Path测试的页面
Document Length测试的页面大小
Concurrency Level并发数
Time taken for tests整个测试持续的时间
Complete requests完成的请求数量
Failed requests失败的请求数量,这里的失败是指请求的连接服务器、发送数据、接收数据等环节发生异
常,以及无响应后超时的情况。
write errors输出错误数量
Total transferred整个场景中的网络传输量,表示所有请求的响应数据长度总和,包括每个 http 响应数据的
头信息和正文数据的长度。
HTML transferred整个场景中的 HTNL 内容传输量,表示所有请求的响应数据中正文数据的总和
Requests per second每秒钟平均处理的请求数(相当于 LR 中的每秒事务数)这便是我们重点关注的吞吐
率,它等于: Complete requests/Time taken for tests
Time per reguest每个线程处理请求平均消耗时间(相当于 LR 中的平均事务响应时间)用户平均请求等待
时间
Transfer rate平均每秒网络上的流星
Percentage of the requests served within a certain time (ms)指定时间里,执行的请求百分比

与tomcat相关的JVM 参数调优
Tomcat 是一款 Java 应用 ,那么 JVM 的配置便与其运行性能密切相关,而 JVM 优化的重点则集中在内存分配和 GC 策略的调整上,因为内
存会直接影响服务的运行效率和吞吐量,JVM 垃圾回收机制则会不同程度地导致程序运行中断。可以根据应用程序的特点,选择不同的垃
圾回收策略,调整 JVM 垃圾回收策略,可以极大减少垃圾回收次数,提升垃圾回收效率,改善程序运行性能。

  1. JVM 内存参数
    | 参數 | 参数作用 | 优化建议 |
    | :------------------- | :----------------------------------------------------- | :----------------------- |
    | -server | 启动 server ,以服务端模式运行 | 服务端模式建议开启 |
    | -Xms | 最小堆内存 | 建议与-Xmx 设置相同 |
    | -Xmx | 最大堆内存 | 建议设置为可用内存的 80% |
    | -XX:Metaspacesize | 元空间初始值 | - |
    | -XX:MaxMetaspaceSize | 元空间最大内存 | 默认无限 |
    | -XX:MaxNewSize | 新生代最大内存 | 默认 16M |
    | -XX:NewRatio | 年轻代和老年代大小比值,取值为整数,默认为 2 | 不建议修改 |
    | -XX:SurvivorRatio | Eden 区与 Survivor 区大小的比值,取值为整数 ,默认为 8 | |
  2. GC 策略
    JVM 垃圾回收性能需要考量以下两个主要的指标:
  • 吞吐量:工作时间(排除 GC 时间)占总时间的百分比,工作时间并不仅是程序运行的时间,还包含内存分配时间。
  • 暂停时间:测试时间段内,由垃圾回收导致的应用程序停止响应次数/时间
    在 sun 公司推出的 HotspotJVM 中,包含以下几种不同类型的垃圾收集器 :
    | 垃圾收集器 | 含义
    |
    | :------------------------------------------ | :---------------------------------------------------------------------------------------------
    --------------------------------------------------------------------------------------------------------------------------------- |
    | 串行收集器(Serial Collector) | 采用单线程执行所有的垃圾回收工作,适用于单核 CPU 服务器 ,无法利用多核硬件的优势
    |
    | 并行收集器(Parallel Collector) | 又称为吞吐量收集器,以并行的方式执行年轻代的垃圾回收,该方式可以显著降低垃圾回收
    的开销(指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态)。适用于多处理器或多线程硬件上运行的数据量较大的应用
    |
    | 并发收集器(Concurrent Collector) | 以并发的方式执行大部分垃圾回收工作,以缩短垃圾回收的暂停时间。适用于那些响应时间优
    先于吞吐量的应用,因为该收集器虽然最小化了暂停时间(指用户线程与垃圾收集线程同时执行,但不一定是并行的,可能会交替进行),但是
    会降低应用程序的性能 |
    | CMS 收集器(Concurrent Mark Sweep Collector) | 并发标记清除收集器,适用于那些更愿意缩短垃圾回收暂停时间并且负担的起与垃
    圾回收共享处理器资源的应用 |
    | G1 收集器(Garbage-First Garbage Collector) | 适用于大容星内存的多核服务器,可以在满足垃圾回收暂停时间目标的同时 ,以最大
    可能性实现高吞吐量(JDK1.7 之后) |
    不同的应用程序,对于垃圾回收会有不同的需求。JVM 会根据运行的平台、服务器资源配置情况选择合适的垃圾收集器、堆内存大小及运
    行时编译器。如无法满足需求,参考以下准则:
    A. 程序数据量较小,选择串行收集器。
    B. 应用运行在单核处理器上且没有暂停时间要求,可交由 JVM 自行选择或选择串行收集器。
    C. 如果考虑应用程序的峰值性能,没有暂停时间要求 ,可以选择并行收集器。
    D. 如果应用程序的响应时间比整体吞吐量更重要,可以选择并发收集器。
  1. GC 参数
    | 参数 | 描述 |
    | :---------------------- | :-----------------------------------------------------------------------------------------------------------------
    ------------------------- |
    | -Xx:+UseSerialGC | 启用串行收集器 |
    | -XX:+UseParallelGC | 启用并行垃圾收集器,配置了该选项,那么-Xx:+UseParallel01dGc 默认启用
    |
    | -XX:+UseParallelOldGC | Ful1GC 采用并行收集,默认禁用。如果设置了-Xx:+UseParallelGC 则自动启用
    |
    | -XX:+UseParNewGC | 年轻代采用并行收集器,如果设置了-XX:+UseConcMarkSweepGC 选项,自动启用
    |
    | -XX:ParallelGCThreads | 年轻代及老年代垃圾回收使用的线程数。默认值依赖于 JVM 使用的 CPU 个数
    |
    | -XX:+UseConcMarkSweepGC | 对于老年代,启用 CMs 垃圾收集器。当并行收集器无法满足应用的延迟需求是 ,推荐使用 CMS 或 G1
    收集器。启用该选项后,-XX:+UseParNewGC 自动启用。 |
    | -XX:+UseG1GC | 启用 G1 收集器。G1 是服务器类型的收集器, 用于多核、大内存的机器。它在保持高吞吐量的情况下,高概率
    满足 Gc 暂停时间的目标。 |

Tomcat 配置调优

调整tomcat/conf/server.xml中关于连接器的配置可以提升应用服务器的性能。| 参数 | 说明


  • |
    | maxConnections | 最大连接数,当到达该值后,服务器接收但不会处理更多的请求,额外的请求将会阻塞直到连接数低于
    maxConnections。可通过 ulimit -a 查看服务器限制。对于 CPU 要求更高(计算型)时,建议不要配置过大;对于 CPU 要求不是特别高时,
    建议配置在 2000 左右。当然这个需要服务 器硬件的支持 |
    | maxThreads | 最大线程数,需要根据服务器的硬件情况,进行一个合理的设置
    |
    | acceptCount | 最大排队等待数,当服务器接收的请求数最到达 maxConnections , 此时 Tomcat 会将后面的请求,存放在任务队列中进
    行排序,acceptCount 指的就是任务队列中排队等待的请求数 。一台 Tomcat 的最 大的请求处理数量,是
    maxConnections+acceptCount。 |

JVM调优

JVM原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UWqDIYJO-1604371435775)(png/2020-01-17-14-10-51.png)]
使用 Java 语言编写.java Source Code 文件,通过 javac 编译成.classByte Code 文件,JVM 就可以识别.class文件,并使用 JIT
compiler 将.class文件编译为机器码后运行。
class loader 类加载器:将所需的类加载到内存,必要时将类实例化成实例。

运行时数据区域(内存模型)

图中中间部分是进程的内存逻辑结构,称为 Jvm 运行时区域,由下面几部分构成:
Method Area方法区:所有线程共享的内存空间,存放已加载的类信息、常量和静态变量。
Heap 堆:所有线程共享的内存空间,存放创建的所有对象。堆是靠 GC 垃圾回收器管理的。
Java Stack栈:每个线程会分配一个栈,存放线程用的本地变量、方法参数和返回值等。
PC 寄存器:PC, 即 Program Counter,每一个线程用于记录当前线程正在执行的字节码指令地址。因为线程需要切换,当一个线程
被切换回来需要执行的时候,知道执行到哪里了。
Native Method Stack本地方法栈:为本地方法执行构建的内存空间,存放本地方法执行时的局部变量、操作数等。
所谓本地方法,简单的说是非 Java 实现的方法,例如操作系统的 C 编写的库提供的本地方法,Java 调用这些本地方法接口执行。但
是要注意,本地方法应该避免直接编程使用,因为 Java 可能跨平台使用,如果用了 Windows API,换到了 Linux 平台部署就有了问题。

堆与栈

堆和栈是程序运行的关键,很有必要把他们的关系说清楚。
栈是运行时的单位,而堆是存储的单位
栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决的是数据存储的问题,即数据怎么放、放在哪儿。在Java中一个线程就会相应有一个线程栈与之对应,这点很容易理解,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈。
而堆则是所有线程共享的。栈因为是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关信息的。包括局部变量、程序运行状
态、方法返回值等等;而堆只负责存储对象信息。
为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗
第一,从软件设计的角度看,栈代表了处理逻辑,而堆代表了数据。这样分开,使得处理逻辑更为清晰。分而治之的思想。这种隔离、模
块化的思想在软件设计的方方面面都有体现。
第二,堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。这种共享的收益是很多的。一方面
这种共享提供了一种有效的数据交互方式(如:共享内存),另一方面,堆中的共享常量和缓存可以被所有栈访问,节省了空间。
第三,栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的
能力。而堆不同,堆中的对象是可以根据需要动态增长的,因此栈和堆的拆分,使得动态增长成为可能,相应栈中只需记录堆中的一个地
址即可。
第四,面向对象就是堆和栈的完美结合。其实,面向对象方式的程序与以前结构化的程序在执行上没有任何区别。但是,面向对象的引
入,使得对待问题的思考方式发生了改变,而更接近于自然方式的思考。当我们把对象拆开,你会发现,对象的属性其实就是数据,存放
在堆中;而对象的行为(方法),就是运行逻辑,放在栈中。我们在编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻
辑。不得不承认,面向对象的设计,确实很美。

JVM垃圾回收

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w6qzzKfP-1604371435776)(png/JVM内存模型.png)]
堆内存在 JVM 使用的内存中占比最大,Heap 堆内存分为:

  1. 新生代:刚刚创建的对象
  2. 伊甸园区
  3. 存活区 Servivor Space:有 2 个存活区,一个是 from 区,一个是 to 区。
    它们大小相等、地位相同、可互换。to 指的是本次复制数据的目标区
  4. 老年代:长时间存活的对象
  5. 持久代:JVM 的类和方法
    假设你是一个普通的 Java 对象,你出生在 Eden 区,在 Eden 区有许多和你差不多的小兄弟、小姐妹,可以把 Eden 区当成幼儿园,在
    这个幼儿园里大家玩了很长时间。Eden 区不能无休止地放你们在里面,所以当年纪稍大,你就要被送到学校去上学,这里假设从小学到
    高中都称为 Survivor 区。开始的时候你在 Survivor 区里面划分出来的的“From”区,读到高年级了,就进了 Survivor 区
    的“To”区,中间由于学习成绩不稳定,还经常来回折腾。直到你 18 岁的时候,高中毕业了,该去社会上闯闯了。于是你就去了年老
    代,年老代里面人也很多。在年老代里,你生活了 20 年 (每次 GC 加一岁),最后寿终正寝,被 GC 回收。有一点没有提,你在年老代遇
    到了一个同学,他的名字叫爱德华 (慕光之城里的帅哥吸血鬼),他以及他的家族永远不会死,那么他们就生活在永生代。

垃圾回收机制

新生代回收

起始时,所有新建对象都出生在 eden,当 eden 满了,启动 GC。这个称为 Young GCMinor GC。先标记 eden 存活对象,然后将
存活对象复制到 s0(假设本次是 s0,也可以是 s1,它们可以调换),eden 剩余所有空间都"清空",GC 回收内存完成。继续新建对象,当
eden 满了,启动 GC。先标记 eden 和 s0 中存活对象,然后将存活对象复制到 s1。将 eden 和 s0 “清空”。继续新建对象,当 eden 满
了,启动 GC。先标记eden 和 s1 中存活对象,然后将存活对象复制到 s0。将 eden 和 s1 “清空”。以后的内存回收重复上面步骤。大多数对象都不会存活很久,而且创建活动非常多,新生代就需要频繁垃圾回收。但是,如果一个对象一直存活,它最后就在 from、to
来回复制,如果 from 区中对象复制次数达到阈值,就直接复制到老年代。##### 老年代回收
进入老年代的数据较少,所以老年代区被占满的速度较慢,所以垃圾回收也不频繁。老年代GC 称为 Old GCMajor GC。由于老年代
对象一般来说存活次数较长,所有较常采用标记-压缩算法。

Full GC

对所有"代"的内存进行垃圾回收
Minor GC 比较频繁,Major GC 较少。但一般 Major GC 时,由于老年代对象也可以引用新生代对象,所以先进行一次 Minor GC,然
后在 Major GC 会提高效率。可以认为回收老年代的时候完成了一次 Full GC。

垃圾回收算法

引用计数

每一个堆内对象上都与一个私有引用计数器,记录着被引用的次数,引用计数清零,该对象所占用堆内存就可以被回收。循环引用的对象
都无法引用计数归零,就无法清除。无法解决循环引用问题。

标记-清除 Mark-Sweep

顾名思义,标记和清除垃圾收集器有两个阶段
1.标记阶段
标记阶段,找到所有可访问对象打个标记。需要注意的是,在标记阶段,会停止应用程序线程,以避免在标记阶段对对象状态进行更改。
2.清除阶段
清理阶段,遍历整个堆,对未标记对象清理。在清除阶段,所有来自标记阶段的未标记对象将从内存中删除,从而释放空间。
从上面的图中可以看出,在清除阶段之后可能存在大量的可用内存区域。但是,由于这些内存为小段的碎片,如果下一个内存分配大于所
有现有的空闲区域,那么它可能会分配失败。因此,需要引入第三个阶段,即压缩阶段。

标记-清除-压缩 Mark-Sweep-Compact

分垃圾标记阶段和内存整理阶段。标记阶段,找到所有可访问对象打个标记。内存清理阶段时,整理时将对象向内存一端移动,整理后存
活对象连续的集中在内存一端。
压缩整理:在扫描阶段之后,所有内存位置将重新安排,以提供更紧凑的内存分配。这种方法
的缺点是 GC 暂停时间增加,因为它需要将所有对象复制到一个新位置并更新对这些对象的
所有引用
标记-清除-压缩算法好处是整理后内存空间连续分配,有大段的连续内存可分配,没有内存碎片。缺点是内存整理过程有时间和回收开
销。

标记-复制 Mark-Copying

先将可用内存分为大小相同两块区域 A 和 B,每次只用其中一块,比如 A。当 A 用完后,则将 A 中存活的对象复制到 B。复制到 B 的时
候连续的使用内存,最后将 A 一次性清除干净。缺点是比较浪费内存,能使用原来一半的内存,因为内存对半划分了,复制过程毕竟也是
有代价。好处是没有碎片,复制过程中保证对象使用连续空间。
这种垃圾回收机制类似于标记-清除,但内存空间分为两部分。最初,将对象分配给一个内存空间(fromspace),并标记活动对象。在复制阶段,被标记的对象被复制到另一个内存空间(tospace),同时被压缩。然后,fromspace 被清空。

分代收集算法

既然上述垃圾回收算法都有优缺点,能不能对不同数据进行区分管理,不同分区对数据实施不同回收策略,分而治之?Tomcat 中 JVM 使
用堆内存的分代回收机制来针对不同数据区域进行内存回收。
Tomcat1.7 及以前,堆内存分为新生代、老年代、持久代。
Tomcat1.8 开始,持久代没有了,取而代之使用 MetaSpace。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ngi6N0Dk-1604371435777)(https://suosuoli/wp-content/uploads/2020/01/JVM%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B.png)]
在分代垃圾回收中,内存空间被划分为不同的代(如年轻代和老年代)。最初,所有的对象将驻留在年轻代。然而,当垃圾收集周期发生
时,在垃圾回收之后存活的对象将被提升到老年代。
一次 Minor Collection 回收(新生代回收)前 :
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N2dkqhlU-1604371435777)(png/before-minor.png)]
一次 Minor Collection 回收后 :
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PufiHR8y-1604371435778)(png/after-minor.png)]
回收后,可以清除年轻代中遗留的对象,因为所有活动对象都将移动到老代中。
老一代的垃圾回收周期比年轻一代的长。这种方法背后的关键思想是,在第一次垃圾收集后存活下来的对象往往寿命更长。因此,可以减
少老一代对象的垃圾收集频率。

GC中Stop the world

对于大多数垃圾回收算法而言,GC 线程工作时,需要停止所有工作的线程,称为Stop The World。GC 完成时,恢复其他工作线程运
行。这也是 JVM 运行中最头疼的问题。

Minor GC和Full GC触发条件

Minor GC 触发条件:当 eden 区满了触发
Full GC 触发条件: 老年代满了,新生代搬向老年代,老年代空间不够,持久代满了
使用 System.gc()手动调用,不推荐

JVM各垃圾回收器特点和区别

垃圾收集器类型

按回收线程数个数分为:

  • 串行垃圾回收器:一个 GC 线程完成回收工作
  • 并行垃圾回收器:多个 GC 线程同时一起完成回收工作,充分利用 CPU 资源
    按工作模式不同:
  • 并发垃圾回收器:让 GC 线程垃圾回收某些阶段可以和工作线程一起进行。
  • 独占垃圾回收器:只有 GC 在工作,STW 一直进行到回收完毕,工作线程才能继续执行。一般情况下,抉择串行还是并行我们大概可以使用以下原则:
  • 客户端或较小程序,内存使用量不大,可以使用串行回收;
  • 对于服务端大型计算,可以使用并行回收;
  • 大型 WEB 应用,用户端不愿意等,尽量少的 STW,可以使用并发回收;

各收集器详解

JVM中的垃圾回收器,主要包括串行回收器、并行回收器以及CMS回收器、G1回收器
1.Serial收集器
Serial收集器是最基本、发展历史最悠久的收集器。是单线程的收集器。它在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收
集完成。 Serial收集器依然是虚拟机运行在Client模式下默认新生代收集器,对于运行在Client模式下的虚拟机来说是一个很好的选择。
2.ParNew收集器
ParNew收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参
数、收集算法、Stop The Worl、对象分配规则、回收策略等都与Serial 收集器完全一样。 ParNew收集器是许多运行在Server模式下的
虚拟机中首选新生代收集器,其中有一个与性能无关但很重要的原因是,除Serial收集器之外,目前只有ParNew它能与CMS收集器配合工
作。
3.Parallel Scavenge(并行回收)收集器
Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器
该收集器的目标是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,
即 吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
4.Serial Old 收集器
Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记整理算法。这个收集器的主要意义也是在于给Client模式
下的虚拟机使用。
如果在Server模式下,主要两大用途:
(1)在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配使用
(2)作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用
5.Parallel Old 收集器
Parallel Old 是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器在1.6中才开始提供。
6.CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网站或者
B/S系统的服务端上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合
这类应用的需求
CMS收集器是基于“标记-清除”算法实现的。它的运作过程相对前面几种收集器来说更复杂一些,整个过程分为4个步骤:(1)初始标记: 标记一下 GC Roots 能直接关联到的对象,速度较快。
(2)并发标记: 进行 GC Roots Tracing,标记出全部的垃圾对象,耗时较长。
(3)重新标记: 修正并发标记阶段引用户程序继续运行而导致变化的对象的标记记录,耗时较短。
(4)并发清除: 用标记-清除算法清除垃圾对象,耗时较长。
其中,初始标记、重新标记这两个步骤仍然需要“Stop The World”.
CMS收集器主要优点:并发收集,低停顿。
CMS三个明显的缺点
(1)CMS收集器对CPU资源非常敏感。CPU个数少于4个时,CMS对于用户程序的影响就可能变得很大,为了应付这种情况,虚拟机提
供了一种称为“增量式并发收集器”的CMS收集器变种。所做的事情和单CPU年代PC机操作系统使用抢占式来模拟多任务机制的思想
(2)CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。在JDK1.5的默认设置
下,CMS收集器当老年代使用了68%的空间后就会被激活,这是一个偏保守的设置,如果在应用中蓝年代增长不是太快,可以适当调高参
数-XX:CMSInitiatingOccupancyFraction的值来提高触发百分比,以便降低内存回收次数从而获取更好的性能,在JDK1.6中,CMS收集
器的启动阀值已经提升至92%。
(3)CMS是基于“标记-清除”算法实现的收集器,手机结束时会有大量空间碎片产生。空间碎片过多,可能会出现老年代还有很大空间
剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前出发FullGC。为了解决这个问题,CMS收集器提供了一个-
XX:+UseCMSCompactAtFullCollection开关参数(默认就是开启的),用于在CMS收集器顶不住要进行FullGC时开启内存碎片合并整理
过程,内存整理的过程是无法并发的,空间碎片问题没有了,但停顿时间变长了。虚拟机设计者还提供了另外一个参数-
XX:CMSFullGCsBeforeCompaction,这个参数是用于设置执行多少次不压缩的Full GC后,跟着来一次带压缩的(默认值为0,标识每次进
入Full GC时都进行碎片整理)
7. G1收集器
G1收集器的优势:
(1)并行与并发
(2)分代收集
(3)空间整理 (标记整理算法,复制算法)
(4)可预测的停顿(G1处处理追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段
内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经实现Java(RTSJ)的来及收集器的特征)
使用G1收集器时,Java堆的内存布局是整个规划为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代
和老年代不再是物理隔离的了,它们都是一部分Region的集合。
G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在真个Java堆中进行全区域的垃圾收集。G1跟踪各个Region
里面的垃圾堆积的价值大小(回收所获取的空间大小以及回收所需要的时间的经验值),在后台维护一个优先列表,每次根据允许的收集
时间,优先回收价值最大的Region(这也就是Garbage-First名称的又来)。这种使用Region划分内存空间以及有优先级的区域回收方
式,保证了G1收集器在有限的时间内可以获取尽量可能高的灰机效率
G1 内存“化整为零”的思路在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会遗漏。
如果不计算维护Remembered Set的操作,G1收集器的运作大致可划分为以下步骤
(1)初始标记: 标记出 GC Roots 直接关联的对象,这个阶段速度较快,需要停止用户线程,单线程执行。
(2)并发标记: 从 GC Root 开始对堆中的对象进行可达新分析,找出存活对象,这个阶段耗时较长,但可以和用户线程并发执行。
(3)最终标记: 修正在并发标记阶段引用户程序执行而产生变动的标记记录。
(4)筛选回收: 筛选回收阶段会对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来指定回收计划(用最少的
时间来回收包含垃圾最多的区域,这就是 Garbage First 的由来——第一时间清理垃圾最多的区块),这里为了提高回收效率,并没有采
用和用户线程并发执行的方式,而是停顿用户线程。

十五. ESXi

15.5 各种缓存

buffer 和 cache的区别

buffer缓冲也叫写缓冲,一般用于写操作,可以将数据先写入内存再写入磁盘,buffer 一般用于写缓冲,用于解决不同介质的
速度不一致的缓冲,先将数据临时写入到距离自己最近的地方,以提高写入速度,CPU 会把数据先写到内存的磁盘缓冲区,然后就认为数
据已经写入完成,然后由内核在后续的时间在写入磁盘,所以服务器突然断电会丢失内存中的部分数据。buffer 内的数据一般会停留一
次,就被移走。buffer意味着数据被存储间隔一段时间后被使用。
cache缓存也叫读缓存,一般用于读操作,CPU 读文件从内存读,如果内存没有就先从硬盘读到内存再读到 CPU,将需要频繁
读取的数据放在里自己最近的缓存区域,下次读取的时候即可快速读取。cache 中的数据则可以部分的常时间驻于其中。cache可以看作
是 buffer 的特例。cache 意味着数据被暂存后被使用多次或用于多种用途。

cache 特性

cache 内的数据都会过期,过期的时间长短由 cache 的设计算法决定
在 CPU 的 L1 和 L2 及 L3 级 cache 中,会将最近经常使用的指令或数据缓存在其中,包括了数据 cache 和指令 cache。其设计使用了多
种算法来淘汰数据或者缓存特定数据,每次 CPU 从 cache 中得到指令或数据就叫命中,不从 cache 取得指令或数据就未命中。cache 的
重要指标之一就是命中率。

DNS 缓存

浏览器对 DNS 记录的缓存时间默认为 60 秒,即 60 秒之内在访问同一个域名就不在进行 DNS 解析: 查看 chrome 浏览器的 DNS 缓
chrome://net-internals/#dns 早期的 chrome 支持管理 DNS 缓存,新的版本已不支持,只有清除 cache 选项
火狐浏览器缓存about:cache

WEB页面缓存

缓存过期机制

使用最后修改时间控制缓存过期server 端系统调用文件的时候会获取文件的最后修改时间,如果没有发生变化就返回给浏览器 304 的状态码,表示没有发生变化,然后浏

览器就使用的本地的缓存展示资源。
如果服务端返回最后修改时间没有发生过变化,则直接使用浏览器的本地缓存,状态码就是 304

Etag 标记控制缓存过期

基于 Etag 标记是否一致做判断页面是否发生过变化,比如基于 Nginx 的配置指令etag on 来实现 Etag 标记。

过期时间控制

以上两种都需要发送请求,即不管资源是否过期都要发送请求进行协商,这样会消耗不必要的时间,因此有了缓存的过期时间,即第一次
请求资源的时候带一个资源的过期时间,默认为 30 天,当前这种方式使用的比表较多,但是无法保证客户的时间都是准确并且一致的,
因此会加入一个最大生存周期,使用用户本地的时间计算缓存数据是否超过多少天,下面的过期时间为 2030 年(2),但是缓存的最大生存
周期计算为天等于 3650 天即 10 年(1),过期时间如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-anP3TJ25-1604371435779)(png/2020-02-11-19-33-34.png)]

CDN 缓存

什么是 CDN?

内容分发网络(Content Delivery Network),通过将服务内容分发至全网加速节点,利用全球调度系统使用户能够就近获取,有效降
低访问延迟,提升服务可用性,CDN第一降低机房的使用带宽,因为很多资源通过 CDN 就直接返回用户了,第二解决不同运营商之间的
互联,因为可以让联通的网络访问联通让电信的网络访问电信,起到加速用户访问的目的, 第三:解决用户访问的地域问题,就近返回用
户资源。

用户请求 CDN 的流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OkvUtnc4-1604371435780)(png/CDN.png)]

CDN 优势

CDN 服务器会提前对静态内容进行预缓存,避免大量的请求回源,导致主站网络带宽被占用而导致数据无法更新,另外 CDN 可以将数据
根据访问的热度不同而进行不同级别的缓存,例如访问量最高的资源访问 CDN 边缘节点的内存,其次的放在 SSD 或者 SATA,再其次的
放在云存储,这样兼顾了速度与成本。缓存到最快的地方如内存,缓存的数据准确命中率高,访问速度就快。
简要的说 CDN 主要有以下特点

  • 调度准确-将用户调度到最近的边缘节点
  • 性能优化-CDN 专门用于缓存响应速度快
  • 安全相关-抵御攻击
  • 节省带宽-由于用户请求由边缘节点响应,因此大幅降低到源站带宽

应用层缓存

Nginx、PHP 等 web 服务可以设置应用缓存以加速响应用户请求,另外有些解释性语言比如 PHP/Python/Java 不能直接运行,需要先
编译成字节码,但字节码需要解释器解释为机器码之后才能执行,因此字节码也是一种缓存,有时候会出现程序代码上线后字节码没有更
新的现象,此时就需要设计相应的应用层缓存来避免此类情况。## cookie 和 session
Cookie 是访问某些网站以后在本地存储的一些网站相关的信息,下次再访问的时候减少一些步骤,比如加密后的账户名密码等信息
Cookies 是服务器在客户端浏览器上存储的小段文本并随每一个请求发送至同一个服务器,是一种实现客户端保持状态的方案。
session 称为会话信息,位于 web 服务器上,主要负责访问者与网站之间的交互,当浏览器请求 http 地址时,可以基于之前的
session 实现会话保持、session 共享等。

session 和 cookie 区别

session 和 cookie 主要区别如下:

  1. Cookie 以文本文件格式存储在浏览器中,而 session 存储在服务端。
  2. cookie 的存储限制了数据量,只允许 4KB,而 session 是无限制的。
  3. cookie 包含在每一个客户端请求报文中,因此容易被人捕获。
  4. cookie 和 session 都可以设置过期时间。

十六. Redis

1.什么是 redis?

Redis 是一个基于内存的高性能 key-value 数据库。

2.Reids 的特点

Redis 本质上是一个 Key-Value 类型的内存数据库,很像 memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把
数据库数据 flush 到硬盘上进行保存。因为是纯内存操作,Redis 的性能非常出色,每秒可以处理超过 10 万次读写操作,是已知性能最快
的 Key-Value DB。
Redis 的出色之处不仅仅是性能,Redis 最大的魅力是支持保存多种数据结构,此外单个 value 的最大限制是 1GB,不像 memcached 只
能保存 1MB 的数据,因此 Redis 可以用来实现很多有用的功能,比方说用他的 List 来做 FIFO 双向链表,实现一个轻量级的高性 能消息
队列服务,用他的 Set 可以做高性能的 tag 系统等等。另外 Redis 也可以对存入的 Key-Value 设置 expire 时间,因此也可以被当作一
个功能加强版的 memcached 来用。
Redis 的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此 Redis 适合的场景主要局限在较小数据量的
高性能操作和运算上。

3.使用 redis 有哪些好处?

  • (1) 速度快,因为数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是 O(1)
  • (2) 支持丰富数据类型,支持 string,list,set,sorted set,hash
1String
常用命令:set/get/decr/incr/mget 等;
应用场景:String 是最常用的一种数据类型,普通的 key/value 存储都可以归为此类;
实现方式:String 在 redis 内部存储默认就是一个字符串,被 redisObject 所引用,当遇到 incr、decr 等操作时会转成数值型进行计
算,此时 redisObject 的 encoding 字段为 int。2Hash
常用命令:hget/hset/hgetall 等
应用场景:我们要存储一个用户信息对象数据,其中包括用户 ID、用户姓名、年龄和生日,通过用户 ID 我们希望获取该用户的姓名或者
年龄或者生日;
实现方式:RedisHash 实际是内部存储的 Value 为一个 HashMap,并提供了直接存取这个 Map 成员的接口。如图所示,Key 是用
户 ID, value 是一个 Map。这个 Map 的 key 是成员的属性名,value 是属性值。这样对数据的修改和存取都可以直接通过其内部 MapKey(Redis 里称内部 Map 的 key 为 field), 也就是通过 key(用户 ID) + field(属性标签) 就可以操作对应属性数据。当前 HashMap 的
实现有两种方式:当 HashMap 的成员比较少时 Redis 为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的
HashMap 结构,这时对应的 value 的 redisObject 的 encoding 为 zipmap,当成员数量增大时会自动转成真正的 HashMap,此时
encoding 为 ht。
hash
3List
常用命令:lpush/rpush/lpop/rpop/lrange 等;
应用场景:Redis list 的应用场景非常多,也是 Redis 最重要的数据结构之一,比如 twitter 的关注列表,粉丝列表等都可以用 Redis 的
list 结构来实现;
实现方式:Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis 内部的
很多实现,包括发送缓冲队列等也都是用的这个数据结构。
4Set
常用命令:sadd/spop/smembers/sunion 等;
应用场景:Redis set 对外提供的功能与 list 类似是一个列表的功能,特殊之处在于 set 是可以自动排重的,当你需要存储一个列表数据,
又不希望出现重复数据时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不
能提供的;
实现方式:set 的内部实现是一个 value 永远为 null 的 HashMap,实际就是通过计算 hash 的方式来快速排重的,这也是 set 能提供判
断一个成员是否在集合内的原因。
5Sorted Set
常用命令:zadd/zrange/zrem/zcard 等;
应用场景:Redis sorted set 的使用场景与 set 类似,区别是 set 不是自动有序的,而 sorted set 可以通过用户额外提供一个优先级
(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择 sorted set
数据结构,比如 twitter 的 public timeline 可以以发表时间作为 score 来存储,这样获取时就是自动按时间排好序的。
实现方式:Redis sorted set 的内部使用 HashMap 和跳跃表(SkipList)来保证数据的存储和有序,HashMap 里放的是成员到 score 的映
射,而跳跃表里存放的是所有的成员,排序依据是 HashMap 里存的 score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上
比较简单。
  • (3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
  • (4) 丰富的特性:可用于缓存,消息,按 key 设置过期时间,过期后将会自动删除

4.redis 相比 memcached 有哪些优势?

  • (1) memcached 所有的值均是简单的字符串,redis 作为其替代者,支持更为丰富的数据类型
  • (2) redis 的速度比 memcached 快很多 (3) redis 可以持久化其数据
    5.Memcache 与 Redis 的区别都有哪些?
  • (1)、存储方式 Memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。Redis 有部份存在硬盘上,这样能保证
    数据的持久性。
  • (2)、数据支持类型 Memcache 对数据类型支持相对简单。Redis 有复杂的数据类型。
  • (3)、使用底层模型不同 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。Redis 直接自己构建了 VM 机制 ,因为一般
    的系统调用系统函数的话,会浪费一定的时间去移动和请求。## 6.redis 适用于的场景?
    Redis 最适合所有数据 in-momory 的场景,如:
  • (1)、会话缓存(Session Cache)
    最常用的一种使用 Redis 的情景是会话缓存(session cache)。用 Redis 缓存会话比其他存储(如 Memcached)的优势在于:Redis
    提供持久化。
  • (2)、全页缓存(FPC)
    除基本的会话 token 之外,Redis 还提供很简便的 FPC 平台。回到一致性问题,即使重启了 Redis 实例,因为有磁盘的持久化,用户也
    不会看到页面加载速度的下降,这是一个极大改进,类似 PHP 本地 FPC。
  • (3)、队列
    Reids 在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得 Redis 能作为一个很好的消息队列平台来使用。Redis 作为队列使
    用的操作,就类似于本地程序语言(如 Python)对 list 的 push/pop 操作。
    如果你快速的在 Google 中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用 Redis 创建非常好的后
    端工具,以满足各种队列需求。例如,Celery 有一个后台就是使用 Redis 作为 broker,你可以从这里去查看。
  • (4),排行榜/计数器
    Redis 在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变
    的非常简单,Redis 只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的 10 个用户–我们称之
    为“user_scores”,我们只需要像下面一样执行即可:
    当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:
    ZRANGE user_scores 0 10 WITHSCORES
    Agora Games 就是一个很好的例子,用 Ruby 实现的,它的排行榜就是使用 Redis 来存储数据的,你可以在这里看到。
  • (5)、发布/订阅
    最后(但肯定不是最不重要的)是 Redis 的发布/订阅功能。发布/订阅的使用场景确实非常多。

7、redis 的缓存失效策略和主键失效机制

作为缓存系统都要定期清理无效数据,就需要一个主键失效和淘汰策略.

  • 在 Redis 当中,有生存期的 key 被称为 volatile。在创建缓存时,要为给定的 key 设置生存期,当 key 过期的时候(生存期为 0),它
    可能会被删除。
  • 1、影响生存时间的一些操作
    生存时间可以通过使用 DEL 命令来删除整个 key 来移除,或者被 SET 和 GETSET 命令覆盖原来的数据,也就是说,修改 key 对应的
    value 和使用另外相同的 key 和 value 来覆盖以后,当前数据的生存时间不同。
    比如说,对一个 key 执行 INCR 命令,对一个列表进行 LPUSH 命令,或者对一个哈希表执行 HSET 命令,这类操作都不会修改
    key 本身的生存时间。另一方面,如果使用 RENAME 对一个 key 进行改名,那么改名后的 key 的生存时间和改名前一样。
    RENAME 命令的另一种可能是,尝试将一个带生存时间的 key 改名成另一个带生存时间的 another_key ,这时旧的
    another_key (以及它的生存时间)会被删除,然后旧的 key 会改名为 another_key ,因此,新的 another_key 的生存时间也和原本的key 一样。使用 PERSIST 命令可以在不删除 key 的情况下,移除 key 的生存时间,让 key 重新成为一个 persistent key 。
  • 2、如何更新生存时间
    可以对一个已经带有生存时间的 key 执行 EXPIRE 命令,新指定的生存时间会取代旧的生存时间。过期时间的精度已经被控制在 1ms 之
    内,主键失效的时间复杂度是 O(1),
    EXPIRE 和 TTL 命令搭配使用,TTL 可以查看 key 的当前生存时间。设置成功返回 1;当 key 不存在或者不能为 key 设置生存时
    间时,返回 0 。
    最大缓存配置
    在 redis 中,允许用户设置最大使用内存大小
    server.maxmemory
    默认为 0,没有指定最大缓存,如果有新的数据添加,超过最大内存,则会使 redis 崩溃,所以一定要设置。redis 内存数据集大
    小上升到一定大小的时候,就会实行数据淘汰策略。

redis 提供 6 种数据淘汰策略:

. volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
. no-enviction(驱逐):禁止驱逐数据
注意这里的 6 种机制,volatile 和 allkeys 规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据,后面的 lru、
ttl 以及 random 是三种不同的淘汰策略,再加上一种 no-enviction 永不回收的策略。
使用策略规则:
1、如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用 allkeys-lru
2、如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用 allkeys-random

三种数据淘汰策略:

ttl 和 random 比较容易理解,实现也会比较简单。主要是 Lru 最近最少使用淘汰策略,设计上会对 ke y 按失效时间排序,然后取
最先失效的 key 进行淘汰

8.为什么 redis 需要把所有数据放到内存中?

Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以 redis 具有快速和数据持久化的特征。如
果不将数据放在内存中,磁盘 I/O 速度为严重影响 redis 的性能。在内存越来越便宜的今天,redis 将会越来越受欢迎。如果设置了最大
使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。

9.Redis 是单进程单线程的

redis 利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销

10.redis 的并发竞争问题如何解决?

Redis 为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis 本身没有锁的概念,Redis 对于多个客户端连接并不存在竞
争,但是在 Jedis 客户端对 Redis 进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是由于客
户端连接混乱造成。对此有 2 种解决方法:

  • 1.客户端角度,为保证每个客户端间正常有序与 Redis 进行通信,对连接进行池化,同时对客户端读写 Redis 操作采用内部锁
    synchronized。
  • 2.服务器角度,利用 setnx 实现锁。注:对于第一种,需要应用程序自己处理资源的同步,可以使用的方法比较通俗,可以使用 synchronized 也可以使用 lock;第二种需
    要用到 Redis 的 setnx 命令,但是需要注意一些问题。

11、redis 常见性能问题和解决方案:

  • 1).Master 写内存快照,save 命令调度 rdbSave 函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服
    务,所以 Master 最好不要写内存快照。
  • 2).Master AOF 持久化,如果不重写 AOF 文件,这个持久化方式对性能的影响是最小的,但是 AOF 文件会不断增大,AOF 文件过大会
    影响 Master 重启的恢复速度。Master 最好不要做任何持久化工作,包括内存快照和 AOF 日志文件,特别是不要启用内存快照做持久化,
    如果数据比较关键,某个 Slave 开启 AOF 备份数据,策略为每秒同步一次。
  • 3).Master 调用 BGREWRITEAOF 重写 AOF 文件,AOF 在重写的时候会占大量的 CPU 和内存资源,导致服务 load 过高,出现短暂服
    务暂停现象。
  • 4). Redis 主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave 和 Master 最好在同一个局域网内。

12.redis 事物的了解 CAS(check-and-set 操作实现乐观锁 )?

和众多其它数据库一样,Redis 作为 NoSQL 数据库也同样提供了事务机制。在 Redis 中MULTI/EXEC/DISCARD/WATCH 这四个命令是
我们实现事务的基石。相信对有关系型数据库开发经验的开发者而言这一概念并不陌生,即便如此,我们还是会简要的列出Redis 中事务
的实现特征:

  • 1). 在事务中的所有命令都将会被串行化的顺序执行,事务执行期间,Redis 不会再为其它客户端的请求提供任何服务,从而保证了事物
    中的所有命令被原子的执行。
  • 2). 和关系型数据库中的事务相比,在 Redis 事务中如果有某一条命令执行失败,其后的命令仍然会被继续执行。
  • 3). 我们可以通过 MULTI 命令开启一个事务,有关系型数据库开发经验的人可以将其理解为"BEGIN TRANSACTION"语句。在该语句之
    后执行的命令都将被视为事务之内的操作,最后我们可以通过执行 EXEC/DISCARD 命令来提交/回滚该事务内的所有操作。这两个 Redis
    命令可被视为等同于关系型数据库中的 COMMIT/ROLLBACK 语句。
  • 4). 在事务开启之前,如果客户端与服务器之间出现通讯故障并导致网络断开,其后所有待执行的语句都将不会被服务器执行。然而如果
    网络中断事件是发生在客户端执行 EXEC 命令之后,那么该事务中的所有命令都会被服务器执行。
  • 5). 当使用 Append-Only 模式时,Redis 会通过调用系统函数 write 将该事务内的所有写操作在本次调用中全部写入磁盘。然而如果在
    写入的过程中出现系统崩溃,如电源故障导致的宕机,那么此时也许只有部分数据被写入到磁盘,而另外一部分数据却已经丢失。Redis
    服务器会在重新启动时执行一系列必要的一致性检测,一旦发现类似问题,就会立即退出并给出相应的错误提示。此时,我们就要充分利
    用 Redis 工具包中提供的 redis-check-aof 工具,该工具可以帮助我们定位到数据不一致的错误,并将已经写入的部分数据进行回滚。修
    复之后我们就可以再次重新启动 Redis 服务器了。

13.WATCH 命令和基于 CAS 的乐观锁?

在 Redis 的事务中,WATCH 命令可用于提供 CAS(check-and-set)功能。假设我们通过 WATCH 命令在事务执行之前监控了多个
Keys,倘若在 WATCH 之后有任何 Key 的值发生了变化,EXEC 命令执行的事务都将被放弃,同时返回 Null multi-bulk 应答以通知调用
者事务执行失败。
例如,我们再次假设 Redis 中并未提供 incr 命令来完成键值的原子性递增,如果要实现该功能,我们只能自行编写相应的代码。其伪码如
下:
val = GET mykey
val = val + 1
SET mykey $val
以上代码只有在单连接的情况下才可以保证执行结果是正确的,因为如果在同一时刻有多个客户端在同时执行该段代码,那么就会出
现多线程程序中经常出现的一种错误场景–竞态争用(race condition)。比如,客户端A和B都在同一时刻读取了mykey的原有值,假设该
值为10,此后两个客户端又均将该值加一后set回Redis服务器,这样就会导致mykey的结果为11,而不是我们认为的12。为了解决类似
的问题,我们需要借WATCH命令的帮助,见如下代码:WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC
和此前代码不同的是,新代码在获取 mykey 的值之前先通过 WATCH 命令监控了该键,此后又将 set 命令包围在事务中,这样就可
以有效的保证每个连接在执行 EXEC 之前,如果当前连接获取的 mykey 的值被其它连接的客户端修改,那么当前连接的 EXEC 命令将执
行失败。这样调用者在判断返回值后就可以获悉 val 是否被重新设置成功。

14.使用过 Redis 分布式锁么,它是什么回事?

先拿 setnx 来争抢锁,抢到之后,再用 expire 给锁加一个过期时间防止锁忘记了释放。
这时候对方会告诉你说你回答得不错,然后接着问如果在 setnx 之后执行 expire 之前进程意外 crash 或者要重启维护了,那会怎么样?
这时候你要给予惊讶的反馈:唉,是喔,这个锁就永远得不到释放了。紧接着你需要抓一抓自己得脑袋,故作思考片刻,好像接下来的结
果是你主动思考出来的,然后回答:我记得 set 指令有非常复杂的参数,这个应该是可以同时把 setnx 和 expire 合成一条指令来用的!
对方这时会显露笑容,心里开始默念:摁,这小子还不错。
15.假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,如果将它们全部找出来?
使用 keys 指令可以扫出指定模式的 key 列表。
对方接着追问:如果这个 redis 正在给线上的业务提供服务,那使用 keys 指令会有什么问题?
这个时候你要回答 redis 关键的一个特性:redis 的单线程的。keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行
完毕,服务才能恢复。这个时候可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,
在客户端做一次去重就可以了
,但是整体所花费的时间会比直接用 keys 指令长。

16.使用过 Redis 做异步队列么,你是怎么用的?

一般使用 list 结构作为队列,rpush 生产消息,lpop 消费消息。当 lpop 没有消息的时候,要适当 sleep 一会再重试。如果对方追问可不
可以不用 sleep 呢?list 还有个指令叫 blpop,在没有消息的时候,它会阻塞住直到消息到来。
如果对方追问能不能生产一次消费多次呢?使用 pub/sub 主题订阅者模式,可以实现 1:N 的消息队列。
如果对方追问 pub/sub 有什么缺点?在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如 rabbitmq 等。
如果对方追问 redis 如何实现延时队列?
我估计现在你很想把面试官一棒打死如果你手上有一根棒球棍的话,怎么问的这么详细。但是你很克制,然后神态自若的回答道:使用
sortedset,拿时间戳作为 score,消息内容作为 key 调用 zadd 来生产消息,消费者用 zrangebyscore 指令获取 N 秒之前的数据轮询进
行处理
。到这里,面试官暗地里已经对你竖起了大拇指。但是他不知道的是此刻你却竖起了中指,在椅子背后。

17.如果有大量的 key 需要设置同一时间过期,一般需要注意什么?

如果大量的 key 过期时间设置的过于集中,到过期的那个时间点,redis 可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,
使得过期时间分散一些。

18.Redis 如何做持久化的?bgsave 做镜像全量持久化,aof 做增量持久化。因为 bgsave 会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要

aof 来配合使用。在 redis 实例重启时,会使用 bgsave 持久化文件重新构建内存,再使用 aof 重放近期的操作指令来实现完整恢复重启
之前的状态。
**对方追问那如果突然机器掉电会怎样?**取决于 aof 日志 sync 属性的配置,如果不要求性能,在每条写指令时都 sync 一下磁盘,就不
会丢失数据。但是在高性能的要求下每次都 sync 是不现实的,一般都使用定时 sync,比如 1s1 次,这个时候最多就会丢失 1s 的数据。
对方追问 bgsave 的原理是什么?你给出两个词汇就可以了,fork 和 cow。fork 是指 redis 通过创建子进程来进行 bgsave 操作,cow
指的是 copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。

19.Pipeline 有什么好处,为什么要用 pipeline?

可以将多次 IO 往返的时间缩减为一次,前提是 pipeline 执行的指令之间没有因果相关性。使用 redis-benchmark 进行压测的时候可以
发现影响 redis 的 QPS 峰值的一个重要因素是 pipeline 批次指令的数目。

20.Redis 的同步机制了解么?

Redis 可以使用主从同步,从从同步。第一次同步时,主节点做一次 bgsave,并同时将后续修改操作记录到内存 buffer,待完成后将
rdb 文件全量同步到复制节点,复制节点接受完成后将 rdb 镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复
制节点进行重放就完成了同步过程。

21.是否使用过 Redis 集群,集群的原理是什么?

Redis Sentinal 着眼于高可用,在 master 宕机时会自动将 slave 提升为 master,继续提供服务。
Redis Cluster 着眼于扩展性,在单个 redis 内存不足时,使用 Cluster 进行分片存储。

22. 什么是缓存击穿、缓存穿透、缓存雪崩?

缓存穿透

缓存穿透,是指查询一个数据库一定不存在的数据。正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经
过期,再对数据库进行查询,并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存。
有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap
中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(我们
采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期
时间会很短,最长不超过五分钟。

缓存击穿

缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿
破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
缓存击穿解决方案:业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即
去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,
当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。

缓存雪崩

缓存雪崩,是指在某一个时间段,缓存集中过期失效。产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假
设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库
而言,就会产生周期性的压力波峰。
比如做电商项目的时候,一般是采取不同分类商品,缓存不同周期。在同一分类中的商品,加上一个随机因子。这样能尽可能分散缓存过
期时间,而且,热门类目的商品缓存时间长一些,冷门类目的商品缓存时间短一些,也能节省缓存服务的资源。
缓存集中失效解决:大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线 程(进程)写,从而避免失效时大量的并发请求落
到底层存储系统上。这里分享一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-
5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

十七. Docker

Docker底层技术

Docker介绍

十八. Zabbix

十九. Gitlab&Jenkins

二十. ELK stack

Elasticsearch

Logstash

Kibana

二十一. 中间件

Zookeeper

1.Zookeeper是什么?

ZooKeeper是源代码开放的分布式协调服务,由雅虎创建,是Google CHubby的实现。
ZooKeeper是一个高性能的分布式数据一致性解决方案,它将那些复杂的、容易出错的分布式一致性服务封装起来,构成一个高效可靠得
原语集,并提供一系列简单易用的接口给用户使用
知识要点:(1)源代码开源
(2)是分布式协调服务,它解决分布式数据一致性问题
A :顺序一致性 B:原子性 C:单一视图
D:可靠性 E:实时性
(3)高性能
(4)我们可以通过挑用ZooKeep提供的接口来解决一些分布式应用中中的实际问题。

2.ZooKeeper的典型应用场景?

1.数据发布/订阅
2.负载均衡
3.命名服务
4.分布式协调和通知

3.ZooKeeper的优势

(1)源代码开源
(2)已经被证实是高性能,易用稳定的工业级产品
(3)有着广泛的应用:Hadoop,HBase,Storm,Solr

4.ZooKeeper的基本概念

1.集群角色

Leader,Follower,Observer
Leader服务器是整个ZooKeeper集群工作机制中的核心
Follower服务器是ZooKeeper集群状态的跟随者
Observer服务器充当一个观察者角色
相关设计模式
Leader,Follower设计模式
Observer 观察者设计模式

2.会话

会话是指客户端和ZooKeeper服务器的连接,Zookeeper中的会话叫Session,客户端与服务器建议一个TCP的长连接来维持一个
Session,客户端在启动的时候所限会与服务器建立一个TCP连接,通过这个连接,客户端能通过心跳检测与服务器保持有效得 会话,也能向ZooKeeper服务器发送请求并获得相应。

3.数据节点

ZooKeeper中的节点有两类
(1)集群中的一台机器为一个节点
(2)数据模型中的数据单元Znode,分为持久节点和临时节点

4.版本

版本类型说明
version当前数据节点数据内容的版本号
cversion当前数据节点数据内容的版本号
aversion当前数据节点ACL(权限控制)变更版本号

5.watcher(观察者)

事件监听器
ZooKeeper允许用户在指定节点上注册一些Watcher,当数据节点发生变化时候,ZooKeeper服务器会把这个变化通知发送给感兴趣的客
户端。

6.ACL权限控制

ACL是Access Control Lists的简写,ZooKeeper采用ACL策略进行权限控制,有以下权限:
CREATE:创建子节点的权限
REAN:获取节点数据和子节点列表的权限
WRITE:更新节点数据的权限
DELETE:删除子节点的权限
ADMIN:设置节点ACL的权限

Kafka

Dubbo

二十二. Kubernetes

二十三. OpenStack

二十四. Shell编程

bash中变量的种类```bash

局部变量:生效范围为当前shell进程;对当前shell之外的其它shell进程,包括当前shell的子shell进程均无效
环境变量:生效范围为当前shell进程及其子进程
本地变量:生效范围为当前shell进程中某代码片断,通常指函数
位置变量:$1, 2 , . . . 来 表 示 , 用 于 让 脚 本 在 脚 本 代 码 中 调 用 通 过 命 令 行 传 递 给 它 的 参 数 特 殊 变 量 : 2, ...来表示,用于让脚本在脚本代码中调用通过命令行传递给它的参数 特殊变量: 2,...?, $0, $, $@, KaTeX parse error: Expected 'EOF', got '#' at position 1: #̲,$
$? 存储脚本、命令或者函数的退出状态值;脚本的退出状态为脚本中最后一条命令的退出状态;函数退出状态也为最后一条命令的退出
状态;一般成功执行退出状态为0;命令执行失败退出状态为1-255之间的整数.
$0 执行脚本时脚本的路径名
$
将所有位置参数视为单个字符串
$@ 每个位置参数存储为单独引用的字符串,分开对待每个位置参数
$# 脚本后所跟的参数的个数
$$ 为PID变量,存储其出现在的脚本所属的进程的进程号

局部变量赋值和引用
```bash
变量赋值:name='value'
(1) 可以是直接字串:name='root'
(2) 变量引用:name="$USER"
(3) 命令引用:name=`COMMAND`
name=$(COMMAND)
变量引用:${name} 或者 $name

bash中的测试

数值测试

-v VAR
测试变量VAR是否设置
示例:判断 NAME 变量是否定义
[ -v NAME ]
  • 数值测试
-gt 是否大于
-ge 是否大于等于
-eq 是否等于
-ne 是否不等于
-lt 是否小于
-le 是否小于等于
```### 字符串测试
```bash
-z "STRING" 字符串是否为空,空为真,不空为假
-n "STRING" 字符串是否不空,不空为真,空为假
= 是否等于
> ascii码是否大于ascii码
< 是否小于
!= 是否不等于
== 左侧字符串是否和右侧的PATTERN相同
=~ 左侧字符串是否能够被右侧的PATTERN所匹配
注意:此表达式用于[[ ]]中,PATTERN为通配符

文件测试

存在性测试
-a FILE:同 -e
-e FILE: 文件存在性测试,存在为真,否则为假
存在性及类别测试
-b FILE:是否存在且为块设备文件
-c FILE:是否存在且为字符设备文件
-d FILE:是否存在且为目录文件
-f FILE:是否存在且为普通文件
-h FILE 或 -L FILE:存在且为符号链接文件
-p FILE:是否存在且为命名管道文件
-S FILE:是否存在且为套接字文件 # 大写s

文件权限测试

文件权限测试:
-r FILE:是否存在且可读
-w FILE: 是否存在且可写
-x FILE: 是否存在且可执行
文件特殊权限测试:
-u FILE:是否存在且拥有suid权限
-g FILE:是否存在且拥有sgid权限
-k FILE:是否存在且拥有sticky权限

文件属性测试

文件大小测试:
-s FILE: 是否存在且非空
文件是否打开:
-t fd: fd 文件描述符是否在某终端已经打开
-N FILE:文件自从上一次被读取之后是否被修改过-O FILE:当前有效用户是否为文件属主
-G FILE:当前有效用户是否为文件属组

双目测试

FILE1 -ef FILE2: FILE1是否是FILE2的硬链接
FILE1 -nt FILE2: FILE1是否新于FILE2(mtime)
FILE1 -ot FILE2: FILE1是否旧于FILE2

组合测试条件

第一种方式:
[ EXPRESSION1 -a EXPRESSION2 ] 并且
[ EXPRESSION1 -o EXPRESSION2 ] 或者
[ ! EXPRESSION ]
取反
-a 和 -o 需要使用测试命令进行,[[ ]] 不支持
第二种方式:
COMMAND1 && COMMAND2 并且,短路与,代表条件性的AND THEN
COMMAND1 || COMMAND2 或者,短路或,代表条件性的OR ELSE
! COMMAND 非
示例:
[ -f “$FILE] && [[$FILE=~ .*\.sh$ ]]
示例:
test "$A" = "$B" && echo "Strings are equal"
test "$A"-eq "$B" && echo "Integers are equal"
[ "$A" = "$B" ] && echo "Strings are equal"
[ "$A" -eq "$B" ] && echo "Integers are equal"
[ -f /bin/cat -a -x /bin/cat ] && cat /etc/fstab
[ -z "$HOSTNAME" -o $HOSTNAME = "localhost.localdomain" ] && hostname suosuoli

防止bash扩展某些特殊字符

反斜线(\)会使随后的字符按原意解释
echo Your cost: \$5.00
Your cost: $5.00
加引号来防止扩展
?单引号(’’)防止所有扩展
?双引号(”“)也可防止扩展,但是以下情况例外:
$(美元符号) 变量扩展
` ` (反引号) 命令替换
\(反斜线) 禁止单个字符扩展
!(叹号) 历史命令替换

bash的配置文件```bash

全局配置:
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc
个人配置:
~/.bash_profile
~/.bashrc

**按功能划分,存在两类:**
- Profile类
```bash
profile类:为交互式登录的shell提供配置
全局:/etc/profile, /etc/profile.d/*.sh
个人:~/.bash_profile
功用:
(1) 用于定义环境变量
(2) 运行命令或脚本
  • Bashrc类
bashrc类:为非交互式和交互式登录的shell提供配置
全局:/etc/bashrc
个人:~/.bashrc
功用:
(1) 定义命令别名和函数
(2) 定义本地变量

shell登录两种方式

交互式登录

(1)直接通过终端输入账号密码登录
(2)使用“su - UserName” 切换的用户
执行顺序:/etc/profile --> /etc/profile.d/*.sh --> ~/.bash_profile -->
~/.bashrc --> /etc/bashrc

非交互式登录

(1)su UserName
(2)图形界面下打开的终端
(3)执行脚本(4)任何其它的bash实例
执行顺序: /etc/profile.d/*.sh --> /etc/bashrc -->~/.bashrc

编辑配置文件后使其生效的方式

修改profile和bashrc文件后需生效
两种方法:
1重新启动shell进程
2使用.命令或source 命令
例:
~# . ~/.bashrc
~# source ~/.bashrc

退出登录时让Bash执行退出任务

任务保存在~/.bash_logout文件中(用户)
在退出登录shell时运行
作用
?创建自动备份
?清除临时文件

set命令和$-变量

root@server-ctl:/data/scripts# echo $-
himBHs
root@server-ctl:/data/scripts# echo $_
himBHs
##########
h:hashall,打开这个选项后,Shell 会将命令所在的路径hash下来,避免每
次都要查询。通过set +h将h选项关闭
i:interactive-comments,包含这个选项说明当前的 shell 是一个交互式的
shell。所谓的交互式shell,在脚本中,i选项是关闭的
m:monitor,打开监控模式,就可以通过Job control来控制进程的停止、继
续,后台或者前台执行等
B:braceexpand,大括号扩展
H:history,H选项打开,可以展开历史列表中的命令,可以通过!感叹号来
完成,例如“!!”返回上最近的一个历史命令,“!n”返回第 n 个历史命令
[root@lab-server2 ~]# set +i # 关闭该功能
[root@lab-server2 ~]# echo $-
hmBH
[root@lab-server2 ~]# set -i # 开启该功能
[root@lab-server2 ~]# echo $-
himBH
[root@lab-server2 ~]# set +h
[root@lab-server2 ~]# echo $-imBH
[root@lab-server2 ~]# set -h
[root@lab-server2 ~]# echo $-
himBH

脚本安全相关

使用set命令可以避免一些灾难性的错误

-u 在扩展一个没有设置的变量时,显示错误信息
等同set -o nounset
-e 如果一个命令返回一个非0退出状态值(失败)就退出
等同set -o errexit

while特殊用法

while read line; do
Do something here...
done < /PATH/FROM/SOMEFILE

上面的用法会依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将整行赋值给变量line在循环体中对其进行处理。

select循环与菜单

select variable in list
do
Do something here...
done

select循环主要用于创建菜单,按数字顺序排列的菜单项将显示在标准错误上,并显示PS3提示符,等待用户输入
用户输入菜单列表对应的某个数字,执行相应的命令,用户输入被保存在内置变量REPLY中
**注意:**select是个无限循环,因此要记住用break命令退出循环,或用exit命令终止脚本。也可以按Ctrl+c退出循环
select经常和case联合使用
例如:

root@server-ctl:/data/scripts# select i in ni wo ta; do echo $i; break ; done
1) ni
2) wo
3) ta
#? 3
ta
root@server-ctl:/data/scripts# select i in ni wo ta; do echo $i; break ; done
1) ni
2) wo3) ta
#? 4
root@server-ctl:/data/scripts# select i in *.sh; do echo $i; break ; done
1) awk_sum_blank_lines.sh
2) awk_sumid.sh
3) check_ip.sh
4) demo_01.sh
5) file_type.sh
6) read.sh
#? 6
read.sh
root@server-ctl:/data/scripts# select i in *.sh; do echo $i; break ; done
1) awk_sum_blank_lines.sh
2) awk_sumid.sh
3) check_ip.sh
4) demo_01.sh
5) file_type.sh
6) read.sh
#? 1
awk_sum_blank_lines.sh

bash中的函数

定义函数

  • 函数由两部分组成:函数名和函数体
  • 帮助:help function
  • 语法一:
f_name()
{
...function body...
}
  • 语法二:function 为定义函数的关键字,不使用圆括号
function f_name
{
...function body...
}
  • 语法三:function 为定义函数的关键字,使用圆括号
function f_name()
{
...function body...}

函数使用

  • 函数的定义和使用:
  • 可在交互式环境下定义函数
  • 可将函数放在脚本文件中作为它的一部分
  • 可放在只包含函数的单独文件中
  • 调用:函数只有被调用才会执行
  • 调用:给定函数名
  • 函数名出现的地方,会被自动替换为函数代码
  • 函数的生命周期:被调用时创建,返回时终止

函数返回值

  • 函数的执行结果返回值:
  • (1) 使用echo等命令进行输出
  • (2) 函数体中调用命令的输出结果
  • 注意:函数的退出状态码不是函数的返回值,退出状态为函数最后的命令的退出状态
    而不是函数体中对数据处理的返回值。1
  • (1) 默认取决于函数中执行的最后一条命令的退出状态码
  • (2) 自定义退出状态码,其格式为:
return 从函数中返回,用最后状态命令决定返回值
return 0 无错误返回
return 1-255 有错误返回

该dir函数将一直保留到用户从系统退出,或执行了如下所示的unset命令

~# unset dir

在脚本中定义及使用函数

  • 函数在使用前必须定义,因此应将函数定义放在脚本开始部分,直至shell首次发现它后才能使用
  • 调用函数仅使用其函数名即可

使用函数文件

  • 可以将经常使用的函数存入函数文件,然后将函数文件载入shell;文件名可任意选取,但最好与相关任务有某种
    联系。例如:functions.main。一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用set
    命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数若要改动函数,首先用unset命令从shell中
    删除函数。改动完毕后,再重新载入此文件### 载入函数
  • 函数文件已创建好后,要将它载入shell
  • 定位函数文件并载入shell的格式如下:
. filename 或 source filename
<> <空格> <文件名> 这里的文件名要带函数文件的完整路径名

执行shell函数

  • 要执行函数,简单地键入函数名即可
  • 如:
[root@centos7 ~]#hello (){ echo 'Hello , im a function!' ; }
[root@centos7 ~]#hello
Hello , im a function!

删除shell函数

  • 现在对函数做一些改动后,需要先删除函数,使其对shell不可用。使用unset命令完成删除函数
  • 命令格式为:
    unset function_name
  • 环境函数:使子进程也可使用
声明:export -f function_name
查看:export -f 或 declare -xf

函数参数

函数可以接受参数:
传递参数给函数:调用函数时,在函数名后面以空白分隔给定参数列表即可
例如:

root@server-ctl:/data/scripts# cat test.sh
#!/bin/bash
fun_hello(){
for i in $@; do
echo $1
shift
echo "hello, im a function.!"
done
}
main(){
fun_hello $@}
main $@
root@server-ctl:/data/scripts# bash test.sh 1 2 3
1
hello, im a function.!
2
hello, im a function.!
3
hello, im a function.!

在函数体中当中,可以在函数体后跟$1, $2,…调用传入的参数;还可以使用$@, $*, $#等特殊变量

函数变量

  • 变量作用域:不同类型的变量作用域不同
  • 环境变量:当前shell和子shell有效
  • 本地变量:只在当前shell进程有效,为执行脚本会启动专用子shell进程;
    因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数
  • 局部变量:函数的生命周期;函数结束时变量被自动销毁
    注意:如果函数中有局部变量,如果其名称同本地变量,使用局部变量
  • 在函数中定义局部变量的方法
local NAME=VALUE

fork炸弹

  • fork炸弹是一种恶意程序,它的内部是一个不断在fork进程的无限循环,实质是一个简单的递归程序。由于程序是递归的,如果没有任何
    限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源
  • bash函数实现
:(){ :|:& };:
bomb() { bomb | bomb & }; bomb
  • bash脚本实现
#!/bin/bash
./$0|./$0&

bash数组

  • 声明数组:
declare -A ARRAY_NAME 关联数组
  • 数组元素的赋值,bash数组支持多种赋值的方式
(1) 一次只赋值一个元素
ARRAY_NAME[INDEX]=VALUE
weekdays[0]="Sunday"
weekdays[4]="Thursday"
(2) 一次赋值全部元素
ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)
(3) 只赋值特定元素
ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)
(4) 交互式数组值对赋值
read -a ARRAY
  • 引用数组
  • 引用数组元素同样存在多种方式
    ${ARRAY_NAME[INDEX]} 引用单个元素的值
    注意:省略[INDEX]表示引用下标为0的元素
    ${ARRAY_NAME[*]} 引用数组所有元素
    ${ARRAY_NAME[@]} 引用数组所有元素
  • 数组的长度(数组中元素的个数)
    ${#ARRAY_NAME[*]}
    ${#ARRAY_NAME[@]}
  • 删除数组中的某元素:导致稀疏格式
    unset ARRAY[INDEX]
  • 删除整个数组
    unset ARRAY
    数组切片:
${ARRAY[@]:offset:number}
offset 要跳过的元素个数
number 要取出的元素个数
  • 取偏移量之后的所有元素
    ${ARRAY[@]:offset}
  • 向数组中追加元素
    ARRAY[${#ARRAY[*]}]=value
  • 关联数组
    declare -A ARRAY_NAME
    ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2'...)
    注意:关联数组必须先声明再调用

bash脚本示例- 生成10个随机数保存于数组中,并找出其最大值和最小值

#!/bin/bash
declare -i min max
declare -a nums
for ((i=0;i<10;i++));do
nums[$i]=$RANDOM
[ $i -eq 0 ] && min=${nums[$i]} && max=${nums[$i]}&& continue
[ ${nums[$i]} -gt $max ] && max=${nums[$i]}
[ ${nums[$i]} -lt $min ] && min=${nums[$i]}
done
echo “All numbers are ${nums[*]}echo Max is $max
echo Min is $min

二十五.Django


  1. [:space:] ↩︎

更多推荐

面试题总汇

本文发布于:2023-04-13 01:47:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/535fdb2f8fe831c6d18680b91a9911bd.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:总汇   面试题

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!

  • 71479文章数
  • 14阅读数
  • 0评论数