运维知识结构
2020-12-16 17:50:24 3 举报
AI智能生成
运维知识结构
作者其他创作
大纲/内容
知识结构
负载均衡器
Nginx
upstream
绑定 Nginx 进程到不同的 CPU 上,可以看到不同个nginx 在不同的cpu上面的利用率都差不多
worker_processes 2; # 2核CPU的配置worker_cpu_affinity 01 10;worker_processes 4; # 4核CPU的配置worker_cpu_affinity 0001 0010 0100 1000;worker_processes 8; # 8核CPU的配置worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 1000000;
健康检查(NGINX/LVS)
四层
基于 TCP
4层TCP模式下的的健康检查仅仅使用TCP的三次握手实现,不会生成应用日志。
TCP监听的检查机制如下:
1. LVS节点服务器根据监听的健康检查配置,向后端ECS的内网IP+【健康检查端口】发送TCP SYN数据包。
2. 后端ECS收到请求后,如果相应端口正在正常监听,则会返回SYN+ACK数据包。
3. 如果在【响应超时时间】之内,LVS节点服务器没有收到后端ECS返回的数据包,则认为服务无响应,判定健康检查失败;并向后端ECS发送RST数据包中断TCP连接。
4. 如果在【响应超时时间】之内,LVS节点服务器成功收到后端ECS返回的数据包,则认为服务正常运行,判定健康检查成功,而后向后端ECS发送RST数据包中断TCP连接。
注意: 正常的TCP三次握手,LVS节点服务器在收到后端ECS返回的SYN+ACK数据包后,会进一步发送ACK数据包,随后立即发送RST数据包中断TCP连接。该实现机制可能会导致后端ECS认为相关TCP连接出现异常(非正常退出),并在业务软件如Java连接池等日志中抛出相应的错误信息,如Connection reset by peer。解决方案: TCP监听采用HTTP方式进行健康检查。 在后端ECS配置了获取客户端真实IP后,忽略来自前述负载均衡服务地址段相关访问导致的连接错误。
基于 UDP
UDP监听的检查机制如下:
1. LVS节点服务器根据监听的健康检查配置,向后端ECS的内网IP+【健康检查端口】发送UDP报文。
2. 如果后端ECS相应端口未正常监听,则系统会返回类似返回 port XX unreachable的ICMP报错信息;反之不做任何处理。
3. 如果在【响应超时时间】之内,LVS节点服务器收到了后端ECS返回的上述错误信息,则认为服务异常,判定健康检查失败。
4. 如果在【响应超时时间】之内,LVS节点服务器没有收到后端ECS返回的任何信息,则认为服务正常,判定健康检查成功。
说明:当前UDP协议服务健康检查可能存在服务真实状态与健康检查不一致的问题:如果后端ECS是Linux服务器,在大并发场景下,由于Linux的防ICMP攻击保护机制,会限制服务器发送ICMP的速度。此时,即便服务已经出现异常,但由于无法向前端返回port XX unreachable报错信息,会导致负载均衡由于没收到ICMP应答进而判定健康检查成功,最终导致服务真实状态与健康检查不一致。解决方案:负载均衡通过发送您指定的字符串到后端服务器,必须得到指定应答后才认为检查成功。但该实现机制需要客户端程序配合应答。
七层
HTTP/HTTPS
健康检查导致大量日志
使用7层HTTP/HTTPS负载均衡模式时,健康检查由HTTP Head请求实现, 后端服务器的应用日志会记录相应的健康检查请求信息,可能导致大量的日志信息。
调整检查频率
关闭健康检查页面的应用日志
在后端服务器上新建一个健康检查站点和健康检查页面,并关闭日志记录
风险:如果健康检查的站点正常,但是业务站点出现异常时,健康检查则无法检测到业务站点的异常。
配置
如果您的业务对负载敏感性高,高频率的健康检查探测可能会对正常业务访问造成影响。您可以结合业务情况,通过降低健康检查频率、增大健康检查间隔、七层检查修改为四层检查等方式,来降低对业务的影响。但为了保障业务的持续可用,不建议关闭健康检查。
白名单
限制客户端上传文件大小
client_max_body_size 5m;
防盗链 ngx_http_referer_module模块
- none:请求标头中缺少“Referer”字段,也就是说Referer为空,浏览器直接访问的时候Referer一般为空。
- blocked: Referer”字段出现在请求标头中,但其值已被防火墙或代理服务器删除; 这些值是不以“http://” 或 “https://” 开头的字符串;
- server_names: 服务器名称,也就是域名列表。
gzip压缩
# gzip# 开启gzipgzip on;# 启用gzip压缩的最小文件,小于设置值的文件将不会压缩gzip_min_length 1k;#设置压缩缓冲区大小,此处设置为16个8K内存作为压缩结果流缓存gzip_buffers 16 8k;# 无条件启用压缩gzip_proxied any;# gzip 压缩级别,1-10,数字越大压缩的越好,也越占用CPU时间gzip_comp_level 6;# 进行压缩的文件类型。javascript有多种形式。其中的值可以在 mime.types 文件中找到。gzip_types text/plain text/css application/javascript application/x-javascript application/xhtml+xml text/xml application/xml application/xml+rss image/jpg image/jpeg image/png image/gif image/x-icon;# 是否在http header中添加Vary: Accept-Encoding,建议开启gzip_vary on;# 禁用IE 6 gzipgzip_disable \"MSIE [1-6].(?!.*SV1)\";
rewrite
(1)last:相当于apache里的(L)标记,表示完成rewrite匹配。(浏览器地址栏URL地址不变)
(2)break:本条规则匹配完成后,终止匹配,不再匹配后面的规则。(浏览器地址栏URL地址不变)
(3)redirect:返回302临时重定向,浏览器地址栏会显示跳转后的URL地址。
(4)permanent:返回301永久重定向,浏览器地址栏会显示跳转后的URL地址。
跨域
代码里面加(开发做)
CORS
在后端服务器设置 HTTP 响应头,把你需要允许访问的域名加入 Access-Control-Allow-Origin 中。
jsonp
把后端根据请求,构造 json 数据,并返回,前端用 jsonp 跨域。
nginx里配
1. 在 enable-cors.conf 文件中设置 cors :
# allow origin listset $ACAO '*';# set single originif ($http_origin ~* (www.helloworld.com)$) { set $ACAO $http_origin;}if ($cors = \"trueget\") { add_header 'Access-Control-Allow-Origin' \"$http_origin\
2. 接下来,在你的服务器中 include enable-cors.conf 来引入跨域配置:
upstream front_server{ server www.helloworld.com:9000;}upstream api_server{ server www.helloworld.com:8080;}server { listen 80; server_name www.helloworld.com; location ~ ^/api/ { include enable-cors.conf; proxy_pass http://api_server; rewrite \"^/api/(.*)$\" /$1 break; } location ~ ^/ { proxy_pass http://front_server; }}
开启 CORS 示例:https://enable-cors.org/server_nginx.html
ws/wss支持
#support websocket comunicationproxy_set_header Upgrade $http_upgrade;proxy_set_header Connection \"upgrade\";proxy_pass http://wee_admin;
lua + redis 做验签,登陆认证
Location 匹配顺序
LVS
概念介绍
lvs 类型
NAT
换 RS的 IP
在高负载应用场景中,DS很可能成为系统性能瓶颈。
DR
Direct Routing(DR)
Director只负责处理入站的请求报文,不负责响应报文的处理
TUN: IP隧道
压测:七层比四层多了一个处理环节,因此,七层性能没有四层性能好
压测建议
1. 压测负载均衡转发能力建议使用短连接 。
一般来说压测除了验证会话保持和均衡性等功能外,主要想验证负载均衡的转发能力,因此使用短连接比较合适,用于测试负载均衡和后端服务器的处理能力。使用短连接测试时,需要注意客户端端口不足的问题。
2. 压测负载均衡吞吐量建议使用长连接,用于测试带宽上限或特殊业务。
压测工具的超时时间建议设置为一个较小值,如5秒。超时时间太大的话,测试结果会体现在平均响应时间加长,不利于判断压测水位是否已到达。超时时间调小,测试结果会体现在成功率上,便于快速判断压测水位。
3. 后端服务器提供一个静态网页用于压测,以避免应用逻辑带来的损耗。
4. 压测时,监听配置建议如下:
不开启会话保持功能,否则压力会集中在个别后端服务器。
关闭健康检查功能,减少健康检查对后端服务器的访问请求。
性能测试服务的5000并发规格能够提供5个及5个以上的公网IP。
造成七层压测性能低的原因
1. 客户端端口不足
在进行压力测试时,客户端端口不足会导致建立连接失败。负载均衡会默认抹除TCP连接的timestamp属性,Linux协议栈的tw_reuse(time_wait 状态连接复用)无法生效,time_wait状态连接堆积导致客户端端口不足。解决方法:客户端使用长连接代替短连接。使用RST报文断开连接,即socket设置SO_LINGER属性。
2. 后端服务器accept队列满 。
后端服务器accept队列满,导致后端服务器不回复syn_ack报文,客户端超时。解决方法:默认net.core.somaxconn的值为128,执行sysctl -w net.core.somaxconn=1024命令更改net.core.somaxconn的值,并重启后端服务器上的应用。
3. 后端服务器连接过多。
由于架构设计的原因,使用七层负载均衡时,用户长连接经过Tengine后变成短连接,可能导致后端服务器连接过多,从而表现为压测性能低。
4. 后端服务器依赖的应用成为瓶颈。
请求经过负载均衡到达后端服务器后,后端服务器本身负载正常,但由于所有的后端服务器上的应用又依赖其它应用,例如数据库,当数据库成为瓶颈时,也会引起性能降低。
5. 后端服务器的健康检查状态异常。
在压测时,容易忽略后端服务器的健康检查状态,如果有后端服务器健康检查失败或者健康检查状态经常跳跃(好到坏,又从坏到好,反复变化),也会导致压测性能低。
压测工具
Apache ab在大量并发场景下存在3s、6s、9s阶梯式停顿的现象。Apache ab会通过判断content length来确定请求是否成功,而负载均衡挂载多台后端服务器时,返回的content length会不一致,导致测试结果有误。
CICD
Jenkins
在Kubernetes服务上快速搭建Jenkins环境并完成应用构建到部署的流水线作业
Gitlab
maven
nexus
Docker 相关
docker
三大特性
namespace:用来修改进程视图的主要方法,“障眼法”
六类
PID
进程编号
pid=1
Mount
挂载点(文件系统)
用于让被隔离进程只看到当前 Namespace 里的挂载点信息
UTS
主机名和域名
IPC
信号量、消息队列与共享内存
Network
\t网络设备、网络栈、端口等
用于让被隔离进程看到当前 Namespace 里的网络设备和配置
User
用户与组
不可被 namespace 化的
时间
改容器里面,外面也会改
/proc 文件,记录当前内核运行状态的一系列特殊文件
在容器中执行 top 其实是查看的宿主机的信息
top是从/proc/stats目录下获取数据
原因:/proc 文件系统不了解 Cgroups 限制的存在
修复办法:lxcfs
可使容器中的 top 正确查看容器内的资源使用情况
cgroup:用来制造约束的主要手段(Linux Control Group)
mount -t cgroup
CPU
cfs_period
默认的 100 ms(100000 us)
cfs_quota
默认没有任何限制(即:-1)
cpu.cfs_quota_us
task
cpuset
为进程分配单独的 CPU 核和对应的内存节点
MEM
memory
为进程设定内存使用的限制
磁盘
blkio
网络带宽
rootfs(根文件系统)
chroot
只是一个操作系统所包含的文件、配置和目录,并不包括操作系统内核,在 Linux 操作系统中,这两部分是分开存放的,操作系统只有在开机启动时才会加载指定版本的内核镜像
一致性
UnionFS
只读层(ro+wh)
Init层(ro+wh)
可读可写(rw)
Copy-on-Write
1、新数据会直接存放在最上面的Container层。
2、修改现有的数据会先从Image层将数据复制到容器层,修改后的数据直接保存在Container层,Image层保持不变。
3、如果多个层中有命名相同的文件,用户只能看到最上层中的文件。
核心原理: 为待创建的用户进程进行以下操作
1.启用 Linux Namespace 配置
2.设定指定的 Cgroups 参数
3.切换进程的根目录(Change Root)
网络
网桥:起到虚拟交换机作用的网络设备,通过使用一种名叫Veth Pair的虚拟设备连接到网桥,VethPair 成对出现,一张在容器中,一张连接网桥
当你遇到容器连不通“外网”的时候,你都应该先试试 docker0 网桥能不能 ping 通,然后查看一下跟 docker0 和 Veth Pair 设备相关的 iptables 规则是不是有异常,往往就能够找到问题的答案了。
图解docker
进程关系模型
镜像仓库并发下载的压力
缓存
p2p 网络下载
后端存储使用 s3,私有使用 hdfs
前端使用负载均衡
采取了预热模式,线下构建好镜像后就直接异步分发到线上各地域的mirror中,分批发布中第一批机器在执行时,第二批机器就提前去pull镜像。
阿里云开源了蜻蜓项目:Dragonfly 说明:https://yq.aliyun.com/articles/670136
github地址:https://github.com/dragonflyoss/Dragonfly
基础镜像预分发,如果扩容时,基础镜像已经在本地了,就只需要拉取业务镜像的部分,可以明显提升速度,所以预先把基础镜像分发到所有的服务器上去
镜像加速架构
dfget
压测对比
对于Dragonfly而言,无论有多少客户端下载文件,平均下载时间大约是12秒。而当你有更多的客户时,wget时间就会增加。由1200个客户端,文件源崩溃,它不能服务于任何客户端。
kubernetes
启动一个 POD 过程
核心功能全景图
网络插件
flannel
基于网桥的网络方案
UDP:是 Flannel 项目最早支持的一种方式,却也是性能最差的
基于 Flannel UDP 模式的跨主通信的基本原理
实际上,相比于两台宿主机之间的直接通信,基于 Flannel UDP 模式的容器通信多了一个额外的步骤,即 flanneld 的处理过程。而这个过程,由于使用到了 flannel0 这个 TUN 设备,仅在发出 IP 包的过程中,就需要经过三次用户态与内核态之间的数据拷贝,如下所示:
TUN 设备示意图
第一次:用户态的容器进程发出的 IP 包经过 docker0 网桥进入内核态;
第二次:IP 包根据路由表进入 TUN(flannel0)设备,从而回到用户态的 flanneld 进程;
第三次:flanneld 进行 UDP 封包之后重新进入内核态,将 UDP 包通过宿主机的 eth0 发出去。
此外,我们还可以看到,Flannel 进行 UDP 封装(Encapsulation)和解封装(Decapsulation)的过程,也都是在用户态完成的。在 Linux 操作系统中,上述这些上下文切换和用户态操作的代价其实是比较高的,这也正是造成 Flannel UDP 模式性能不好的主要原因。
我们在进行系统级编程的时候,有一个非常重要的优化原则,就是要减少用户态到内核态的切换次数,并且把核心的处理逻辑都放在内核态进行
vxlan:即 Virtual Extensible LAN(虚拟可扩展局域网),是 Linux 内核本身就支持的一种网络虚似化技术。所以说,VXLAN 可以完全在内核态实现上述封装和解封装的工作,从而通过与前面相似的“隧道”机制,构建出覆盖网络(Overlay Network)。
VXLAN 的覆盖网络的设计思想是:在现有的三层网络之上,“覆盖”一层虚拟的、由内核 VXLAN 模块负责维护的二层网络,使得连接在这个 VXLAN 二层网络上的“主机”(虚拟机或者容器都可以)之间,可以像在同一个局域网(LAN)里那样自由通信。当然,实际上,这些“主机”可能分布在不同的宿主机上,甚至是分布在不同的物理机房里。
而为了能够在二层网络上打通“隧道”,VXLAN 会在宿主机上设置一个特殊的网络设备作为“隧道”的两端。这个设备就叫作 VTEP,即:VXLAN Tunnel End Point(虚拟隧道端点)。而 VTEP 设备的作用,其实跟前面的 flanneld 进程非常相似。只不过,它进行封装和解封装的对象,是二层数据帧(Ethernet frame);而且这个工作的执行流程,全部是在内核里完成的(因为 VXLAN 本身就是 Linux 内核中的一个模块)。
基于 Flannel VXLAN 模式的跨主通信的基本原理
可以看到,图中每台宿主机上名叫 flannel.1 的设备,就是 VXLAN 所需的 VTEP 设备,它既有 IP 地址,也有 MAC 地址。
纯三层的网络方案
host-gw
Flannel host-gw 示意图
当你设置 Flannel 使用 host-gw 模式之后,flanneld 会在宿主机上创建一条路由规则
host-gw 模式的工作原理,其实就是将每个 Flannel 子网(Flannel Subnet,比如:10.244.1.0/24)的“下一跳”,设置成了该子网对应的宿主机的 IP 地址。也就是说,这台“主机”(Host)会充当这条容器通信路径里的“网关”(Gateway)。这也正是“host-gw”的含义。
Flannel host-gw 模式必须要求集群宿主机之间是二层连通的。
host-gw模式下,容器通信的过程就免除了额外的封包和解包带来的性能损耗。根据实际的测试,host-gw 的性能损失大约在 10% 左右,而其他所有基于 VXLAN“隧道”机制的网络方案,性能损失都在 20%~30% 左右。
calico
实际上,Calico 项目提供的网络解决方案,与 Flannel 的 host-gw 模式,几乎是完全一样的。也就是说,Calico 也会在每台宿主机上,添加一个格式如下所示的路由规则: < 目的容器 IP 地址段 > via < 网关的 IP 地址 > dev eth0
不同于 Flannel 通过 Etcd 和宿主机上的 flanneld 来维护路由信息的做法,Calico 项目使用了一个“重型武器”来自动地在整个集群中分发路由信息。这个“重型武器”,就是 BGP。
除了对路由信息的维护方式之外,Calico 项目与 Flannel 的 host-gw 模式的另一个不同之处,就是它不会在宿主机上创建任何网桥设备
calico架构的三个组成部分
1. Calico 的 CNI 插件。这是 Calico 与 Kubernetes 对接的部分。
2. Felix。它是一个 DaemonSet,负责在宿主机上插入路由规则(即:写入 Linux 内核的 FIB 转发信息库),以及维护 Calico 所需的网络设备等工作。
3. BIRD。它就是 BGP 的客户端,专门负责在集群里分发路由规则信息。
Calico 工作原理
可以看到,Calico 的 CNI 插件会为每个容器设置一个 Veth Pair 设备,然后把其中的一端放置在宿主机上(它的名字以 cali 前缀开头)。路由如下: 10.233.2.3 dev cali5863f3 scope link
需要注意的是,在大规模集群里,三层网络方案在宿主机上的路由规则可能会非常多,这会导致错误排查变得困难。此外,在系统故障的时候,路由规则出现重叠冲突的概率也会变大。
基于上述原因,如果是在公有云上,由于宿主机网络本身比较“直白”,我一般会推荐更加简单的 Flannel host-gw 模式。
但不难看到,在私有部署环境里,Calico 项目才能够覆盖更多的场景,并为你提供更加可靠的组网方案和架构思路。
canel
service
由 kube-proxy 组件,加上 iptables 来共同实现的
userspace: 几乎没人用了
iptables
访问 Service VIP 的 IP 包经过上述 iptables 处理之后,就已经变成了访问具体某一个后端 Pod 的 IP 包了。不难理解,这些 Endpoints 对应的 iptables 规则,正是 kube-proxy 通过监听 Pod 的变化事件,在宿主机上生成并维护的。
kube-proxy 通过 iptables 处理 Service 的过程,其实需要在宿主机上设置相当多的 iptables 规则。而且,kube-proxy 还需要在控制循环里不断地刷新这些规则来确保它们始终是正确的。
不难想到,当你的宿主机上有大量 Pod 的时候,成百上千条 iptables 规则不断地被刷新,会大量占用该宿主机的 CPU 资源,甚至会让宿主机“卡”在这个过程中。所以说,一直以来,基于 iptables 的 Service 实现,都是制约 Kubernetes 项目承载更多量级的 Pod 的主要障碍。
ipvs
IPVS 模式的工作原理,其实跟 iptables 模式类似。当我们创建了前面的 Service 之后,kube-proxy 首先会在宿主机上创建一个虚拟网卡(叫作:kube-ipvs0),并为它分配 Service VIP 作为 IP 地址,如下所示:
而接下来,kube-proxy 就会通过 Linux 的 IPVS 模块,为这个 IP 地址设置三个 IPVS 虚拟主机,并设置这三个虚拟主机之间使用轮询模式 (rr) 来作为负载均衡策略。我们可以通过 ipvsadm 查看到这个设置,如下所示:
# ipvsadm -ln IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 10.102.128.4:80 rr -> 10.244.3.6:9376 Masq 1 0 0 -> 10.244.1.7:9376 Masq 1 0 0 -> 10.244.2.3:9376 Masq 1 0 0
而相比于 iptables,IPVS 在内核中的实现其实也是基于 Netfilter 的 NAT 模式,所以在转发这一层上,理论上 IPVS 并没有显著的性能提升。但是,IPVS 并不需要在宿主机上为每个 Pod 设置 iptables 规则,而是把对这些“规则”的处理放到了内核态,从而极大地降低了维护这些规则的代价。这也正印证了我在前面提到过的,“将重要操作放入内核态”是提高性能的重要手段。
不过需要注意的是,IPVS 模块只负责上述的负载均衡和代理功能。而一个完整的 Service 流程正常工作所需要的包过滤、SNAT 等操作,还是要靠 iptables 来实现。只不过,这些辅助性的 iptables 规则数量有限,也不会随着 Pod 数量的增加而增加。
所以,在大规模集群里,我非常建议你为 kube-proxy 设置–proxy-mode=ipvs 来开启这个功能。它为 Kubernetes 集群规模带来的提升,还是非常巨大的。
从外部访问 service
1. NodePort
在 NodePort 方式下,Kubernetes 会在 IP 包离开宿主机发往目的 Pod 时,对这个 IP 包做一次 SNAT 操作,如下所示:
-A KUBE-POSTROUTING -m comment --comment \"kubernetes service traffic requiring SNAT\" -m mark --mark 0x4000/0x4000 -j MASQUERADE
可以看到,这条规则设置在 POSTROUTING 检查点,也就是说,它给即将离开这台主机的 IP 包,进行了一次 SNAT 操作,将这个 IP 包的源地址替换成了这台宿主机上的 CNI 网桥地址,或者宿主机本身的 IP 地址(如果 CNI 网桥不存在的话)。
当然,这个 SNAT 操作只需要对 Service 转发出来的 IP 包进行(否则普通的 IP 包就被影响了)。而 iptables 做这个判断的依据,就是查看该 IP 包是否有一个“0x4000”的“标志”。你应该还记得,这个标志正是在 IP 包被执行 DNAT 操作之前被打上去的。
2. LoadBalancer: 适用于公有云
在公有云提供的 Kubernetes 服务里,都使用了一个叫作 CloudProvider 的转接层,来跟公有云本身的 API 进行对接。所以,在上述 LoadBalancer 类型的 Service 被提交后,Kubernetes 就会调用 CloudProvider 在公有云上为你创建一个负载均衡服务,并且把被代理的 Pod 的 IP 地址配置给负载均衡服务做后端。
3. ExternalName: Kubernetes 在 1.7 之后支持的一个新特性
指定了一个 externalName=my.database.example.com 的字段。 YAML 文件里不需要指定 selector。
此外,Kubernetes 的 Service 还允许你为 Service 分配公有 IP 地址,比如备注里这个例子:
在上述 Service 中,我为它指定的 externalIPs=80.11.12.10,那么此时,你就可以通过访问 80.11.12.10:80 访问到被代理的 Pod 了。不过,在这里 Kubernetes 要求 externalIPs 必须是至少能够路由到一个 Kubernetes 的节点。
排错
k8s debug service 思路 官网
1. service 没法通过 DNS 访问到
你就需要区分到底是 Service 本身的配置问题,还是集群的 DNS 出了问题。一个行之有效的方法,就是检查 Kubernetes 自己的 Master 节点的 Service DNS 是否正常:
# 在一个 Pod 里执行$ nslookup kubernetes.defaultServer: 10.0.0.10Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.localName: kubernetes.defaultAddress 1: 10.0.0.1 kubernetes.default.svc.cluster.local
如果上面访问 kubernetes.default 返回的值都有问题,那你就需要检查 kube-dns 的运行状态和日志了。否则的话,你应该去检查自己的 Service 定义是不是有问题。
2. Service 没办法通过 ClusterIP 访问到
首先应该检查的是这个 Service 是否有 Endpoints:
需要注意的是,如果你的 Pod 的 readniessProbe 没通过,它也不会出现在 Endpoints 列表里。
而如果 Endpoints 正常,那么你就需要确认 kube-proxy 是否在正确运行。在我们通过 kubeadm 部署的集群里,你应该看到 kube-proxy 输出的日志如备注所示:
如果 kube-proxy 一切正常,你就应该仔细查看宿主机上的 iptables 了。一个 iptables 模式的 Service 对应的规则包括
1. KUBE-SERVICES 或者 KUBE-NODEPORTS 规则对应的 Service 的入口链,这个规则应该与 VIP 和 Service 端口一一对应;
2. KUBE-SEP-(hash) 规则对应的 DNAT 链,这些规则应该与 Endpoints 一一对应;
3. KUBE-SVC-(hash) 规则对应的负载均衡链,这些规则的数目应该与 Endpoints 数目一致;
4. 如果是 NodePort 模式的话,还有 POSTROUTING 处的 SNAT 链。
通过查看这些链的数量、转发目的地址、端口、过滤条件等信息,你就能很容易发现一些异常的蛛丝马迹。
3. 还有一种典型问题,就是 Pod 没办法通过 Service 访问到自己
这往往就是因为 kubelet 的 hairpin-mode 没有被正确设置。你只需要确保将 kubelet 的 hairpin-mode 设置为 hairpin-veth 或者 promiscuous-bridge 即可。
hairpin-veth
其中,在 hairpin-veth 模式下,你应该能看到 CNI 网桥对应的各个 VETH 设备,都将 Hairpin 模式设置为了 1,如下所示:
for d in /sys/devices/virtual/net/cni0/brif/veth*/hairpin_mode; do echo \"$d = $(cat $d)\"; done/sys/devices/virtual/net/cni0/brif/veth4bfbfe74/hairpin_mode = 1/sys/devices/virtual/net/cni0/brif/vethfc2a18c5/hairpin_mode = 1
promiscuous-bridge
而如果是 promiscuous-bridge 模式的话,你应该看到 CNI 网桥的混杂模式(PROMISC)被开启,如下所示:
ifconfig cni0 |grep PROMISCUP BROADCAST RUNNING PROMISC MULTICAST MTU:1460 Metric:1
网络排查实例: https://mp.weixin.qq.com/s/2dvahn8twJJKlCqigvG7mA
ingress
支持灰度、金丝雀发布
利用阿里云容器服务的Ingress功能,实现灰度发布和蓝绿发布
阿里云容器服务Kubernetes的 Ingress 功能提供的4种流量切分方式
灰度发布中的A/B 测试:
基于Request Header的流量切分
header:基于请求头,支持正则匹配和完整匹配。
基于Cookie的流量切分
基于cookie,支持正则匹配和完整匹配。
基于Query Param的流量切分
基于请求参数,支持正则匹配和完整匹配。
配置示例
# 请求头中满足foo正则匹配^bar$的请求被转发到新版本服务new-nginx中new-nginx: header(\"foo\
示例 2
满足foo=bar的客户端请求仅允许50%的流量被路由到新版本服务
# curl -H \"Host: www.example.com\" -H \"foo: bar\" http://<EXTERNAL_IP>
蓝绿发布:
基于服务权重的流量切分
通过阿里云K8S Ingress Controller实现路由配置的动态更新
service与 ingress 对比
目前,Ingress 只能工作在七层,而 Service 只能工作在四层。所以当你想要在 Kubernetes 里为应用进行 TLS 配置等 HTTP 相关的操作时,都必须通过 Ingress 来进行。
当然,正如同很多负载均衡项目可以同时提供七层和四层代理一样,将来 Ingress 的进化中,也会加入四层代理的能力。这样,一个比较完善的“反向代理”机制就比较成熟了。
而 Kubernetes 提出 Ingress 概念的原因其实也非常容易理解,有了 Ingress 这个抽象,用户就可以根据自己的需求来自由选择 Ingress Controller。比如,如果你的应用对代理服务的中断非常敏感,那么你就应该考虑选择类似于 Traefik 这样支持“热加载”的 Ingress Controller 实现。
部署k8s方式
kubeadm
# 创建一个 Master 节点$ kubeadm init
# 将一个 Node 节点加入到当前集群中$ kubeadm join <Master 节点的 IP 和端口 >
高可用:https://juejin.im/post/5c6a7343e51d4501333ff686 https://blog.51cto.com/billy98/2350660
--experimental-control-plane
kubespray
kops
二进制
ansible 一键部署
k8s-ansible
存储
子主题
Rook 项目:基于 Ceph
PV(PV 没有名称空间)
nfs
PVC
StorageClass
k8s 提供了 provisioner 来动态创建 PV,不仅大大节省了时间,而且还可以根据不同的 StorageClasses 封装不同类型的存储供 PVC 使用
provisioner 插件(默认不支持 nfs)
provisioner
小知识点:
1. Master当 Worker 用
kubectl taint nodes node1 foo=bar:NoSchedule
2. 证书到期怎么办
更新已经过期的证书
当集群证书已过期时,通过kubectl或api接口调用的方式与集群apiserver的通讯都将被禁止,因此我们无法通过模板部署的方式来自动更新集群各节点上的相应过期证书;此时集群管理员可以登录至集群各节点,通过如下docker run启动容器的方式执行目标节点的证书更新任务。
更新Master节点已过期的证书
1.以root权限登录任意Master节点。
2.在任意目录下执行以下命令,更新Master节点已经过期的证书。
$ docker run -it --privileged=true -v /:/alicoud-k8s-host --pid host --net host \\ registry.cn-hangzhou.aliyuncs.com/acs/cert-rotate:v1.0.0 /renew/upgrade-k8s.sh --role master
3.在集群的每个Master节点,重复上述步骤,完成所有Master节点已过期的证书更新。
更新Worker节点已过期的证书
2.执行以下命令,获取集群rootCA私钥。
$ cat /etc/kubernetes/pki/ca.key
3.执行以下命令,获取通过base64编码后集群的根私钥。
若获取的集群rootCA私钥,有一行空行,执行以下命令:
$ sed '1d' /etc/kubernetes/pki/ca.key| base64 -w 0
若获取的集群rootCA私钥,无空行,执行以下命令:
$ cat /etc/kubernetes/pki/ca.key | base64 -w 0
4.以root权限登录任意Worker节点。
5.在任意目录下执行如下命令,更新Worker节点已经过期的证书。
$ docker run -it --privileged=true -v /:/alicoud-k8s-host --pid host --net host \\ registry.cn-hangzhou.aliyuncs.com/acs/cert-rotate:v1.0.0 /renew/upgrade-k8s.sh --role node --rootkey ${base64CAKey}
说明 ${base64CAKey}为步骤3获取的通过base64编码后的集群根私钥。
6.在集群的每个Worker节点,重复上述步骤,完成所有Worker节点已过期的证书更新。
更新即将过期的证书
命令行自动更新所有节点证书
$ curl http://aliacs-k8s-cn-hangzhou.oss-cn-hangzhou.aliyuncs.com/public/cert-update/renew.sh | bash
执行结果
1.执行以下命令,查看集群master和worker节点状态。
$ kubectl get nodes
2.执行以下命令,当master节点对应的SUCCESSFUL均为1,worker节点对应的SUCCESSFUL为集群worker节点数时,所有证书完成更新。
$ kubectl get job –nkube-system
手动更新worker节点证书
3. init containers 的用法: 启动时检查依赖,如果不满足,轮询等待,而不是直接退出
init containers
4. 配置restart policy
Always: 总是自动重启OnFailure:异常退出才自动重启 (进程退出状态非0)Never:永远不重启
5. 配置Liveness Probe和Readiness Probe
Liveness Probe 存活性探测
Pod处于Running状态和Pod能正常提供服务是完全不同的概念,一个Running状态的Pod,里面的进程可能发生了死锁而无法提供服务。但是因为Pod还是Running的,Kubernetes也不会自动重启这个Pod。所以我们要在所有Pod上配置Liveness Probe,探测Pod是否真的存活,是否还能提供服务。如果Liveness Probe发现了问题,Kubernetes会重启Pod
Readiness Probe 就绪性探测
用于探测Pod是不是可以对外提供服务。应用启动过程中需要一些时间完成初始化,在这个过程中是没法对外提供服务的,通过Readiness Probe,可以告诉Ingress或者Service能不能把流量转发给这个Pod上。当Pod出现问题的时候,Readiness Probe能避免新流量继续转发给这个Pod。
6. 确保不存在SPOF(Single Point of Failure)
如果应用只有一个实例,当实例失败的时候,虽然Kubernetes能够重启实例,但是中间不可避免地存在一段时间的不可用。甚至更新应用,发布一个新版本的时候,也会出现这种情况。在Kubernetes里,尽量避免直接使用Pod,尽可能使用Deployment/StatefulSet,并且让应用的Pod在两个以上。
7. 每个进程一个容器
判断Pod整体的资源占用会变复杂,不方便实施上面提到resource limit
容器内只有一个进程的情况,进程挂了,外面的容器引擎可以清楚的感知到,然后重启容器。如果容器内有多个进程,某个进程挂了,容器未必受影响,外部的容器引擎感知不到容器内有进程退出,也不会对容器做任何操作,但是实际上容器已经不能正常工作了。
8. pause容器
主要为每个业务容器提供以下功能:
在pod中担任Linux命名空间共享的基础;
启用pid命名空间,开启init进程。
9. QoS(服务质量等级)
Guaranteed:Pod 里的每个容器都必须有内存/CPU 限制和请求,而且值必须相等。
Burstable:Pod 里至少有一个容器有内存或者 CPU 请求且不满足 Guarantee 等级的要求,即内存/CPU 的值设置的不同。
BestEffort:容器必须没有任何内存或者 CPU 的限制或请求。
10. kubernetes滚动升级的过程
阿里云k8s文档 https://www.alibabacloud.com/help/zh/doc-detail/95782.htm
小工具
Kaniko:无需高权限即可在Kubernetes与Google Container Builder中构建容器镜像,也可以构建镜像推送私有仓库 kaniko -f `pwd`/Dockerfile -c `pwd` --destination=${ORIGIN_REPO}/${REPO}:${IMAGE_TAG}
github 地址
安全层面考虑
证书保证不泄露
使用命名空间建立安全边界
阻止 Pod 中的容器以 root 用户运行
容器是否可以写入 root 文件系统
基于角色的访问控制(RBAC)
限制对Kubernetes节点的管理访问
NetworkPolicy
针对 pod的资源限制 cpu mem
Istio
https://learn.openshift.com/servicemesh
日志收集
Elasticsearch
集群优化
1.单独创建用户及用户组 elastic
[root@elastic ~]# groupadd elastic[root@elastic ~]# useradd elastic -g elastic
2.修改最大文件打开数
[root@elastic ~]# vim /etc/security/limits.conf# 在文件末尾添加以下内容 root soft nofile 65535root hard nofile 65535* soft nofile 65535* hard nofile 65535
3.修改虚拟内存大小
临时修改,重启失效sysctl -w vm.max_map_count=262144
写入文件,永久生效[root@elastic ~]# vim /etc/sysctl.conf vm.max_map_count = 262144[root@elastic ~]# sysctl -p
4.禁用所有文件交换
临时修改sudo swapoff -a
永久禁用编辑 /etc/fstab 文件并注释掉包含 swap 的任何行
5.配置 `swappiness`
# 直接修改,重启失效[root@elastic ~]# sysctl -w vm.swappiness=1
# 写入文件,永久修改[root@elastic ~]# vim /etc/sysctl.conf vm.swappiness = 1[root@elastic ~]# sysctl -p
6.修改 elastic 用户可以创建的线程数至少为 4096
[root@elastic ~]# vim /etc/security/limits.conf# 在文件末尾添加以下内容 root soft nofile 65535root hard nofile 65535* soft nofile 65535* hard nofile 65535[root@elastic ~]# vim /etc/security/limits.d/20-nproc.conf# Default limit for number of user's processes to prevent# accidental fork bombs.# See rhbz #432903 for reasoning.* soft nproc 4096root soft nproc unlimited
7.配置内存锁
[root@elastic ~]# vim /etc/security/limits.conf# 在文件末尾添加以下内容 # allow user 'elastic' mlockallelastic soft memlock unlimitedelastic hard memlock unlimited#保存、退出、重新登录才可生效[root@elastic ~]# ulimit -l unlimited
8.JVM配置建议
Xmx 和 Xms 设置成一样
Xmx 不要超过机器内存的 50%
不要超过 30GB https://www.elastic.co/cn/blog/a-heap-of-trouble
9.配置 TLS 、密码
TLS
bin/elasticsearch-certutil cert -out config/elastic-certificates.p12 -pass \"\"
# 打开文件 `config/elasticsearch.yaml`。将下列代码行粘贴到文件末尾。xpack.security.enabled: truexpack.security.transport.ssl.enabled: truexpack.security.transport.ssl.verification_mode: certificatexpack.security.transport.ssl.keystore.path: elastic-certificates.p12xpack.security.transport.ssl.truststore.path: elastic-certificates.p12
将主节点的配置目录完全复制到节点的配置目录中
密码
bin/elasticsearch-setup-passwords auto
分片副本设置
shard数量(包括副本),尽可能匹配节点数,等于或者是节点数的整数倍
通常建议单节点上同一索引的 shard 数不要超过 5 个
对于日志分析场景或超大索引,建议单 shard 大小不要超过 100G
Kibana
Logstash
配置文件
input { redis { data_type => \"list\" key => \"filebeat\" host => \"r-2ze3fa9fbcd4d544.redis.rds.aliyuncs.com\" port => 6379\tpassword => \"Tiangwang20190220+*-@$\" db => 13 threads => 5 }}filter {}output { elasticsearch {\thosts => [\"172.17.80.172:9221\
Filebeat
读文件输出 console
filebeat.inputs:- type: container paths: - '/var/lib/docker/containers/75bb4da821a578405927e08a32195ccbc64131eb34b9a27371572c57987290f4/*.log' #- '/var/lib/docker/containers/*/*.log' encoding: utf-8 json.keys_under_root: true json.add_error_key: true json.message_key: message json.ignore_decoding_error: true# tail_files: true multiline.pattern: '^[0-9]{4}-[0-9]{2}-[0-9]{2}' multiline.negate: true multiline.match: after multiline.timeout: 10soutput.console: pretty: true#output.redis:# hosts: [\"140.143.19.138:6379\"]# password: \"iotbus_redis_root\"# key: \"filebeat\"# db: 13# timeout: 5# worker: 1# max_retries: 3processors:- add_host_metadata: netinfo.enabled: true- add_cloud_metadata: ~- add_docker_metadata: ~
读文件输出 redis
filebeat.inputs:- type: container paths: - '/var/lib/docker/containers/*/*.log' encoding: utf-8 json.keys_under_root: true json.add_error_key: true json.message_key: message json.ignore_decoding_error: true tail_files: true multiline.pattern: '^[0-9]{4}-[0-9]{2}-[0-9]{2}' multiline.negate: true multiline.match: after multiline.timeout: 10soutput.redis: hosts: [\"r-2ze3fa9fbcd4d544.redis.rds.aliyuncs.com:6379\"] password: \"Tiangwang20190220+*-@$\" key: \"filebeat\" db: 13 timeout: 20 worker: 1 max_retries: 3processors:- add_host_metadata: netinfo.enabled: true- add_cloud_metadata: ~- add_docker_metadata: ~
kafka
服务发现框架选型
zookeeper
1.为什么必须是 357
2.选主过程
【分布式】Zookeeper的Leader选举
3.配置文件
consul
问题处理
docker 中 consul集群问题处理
生态
registrator: 独立于服务注册表的自动服务注册/注销组件
quickstart
consul-template
consul-template github
etcd
etcd官方文档中文版
confd
优化建议
SSD
etcd默认情况下强制将可配置的存储大小配额设置为2GB
建议 5 个节点,允许坏两个,不建议超过 7
硬件建议
官方硬件建议
v2与 v3 差别
v2
v3
使用gRPC作为其传输,而不是像v2那样的RESTful接口
v3 auth具有基于连接的身份验证,而不是v2的每请求身份验证速度较慢
每个连接验证,而不是每个请求
为gRPC API实施的用户ID +基于密码的身份验证
身份验证策略更改后,必须刷新身份验证
它的功能应该与v2一样简单实用
与v2的目录结构不同,v3提供了一个平面键空间。权限检查将作为间隔匹配提供
它应该比v2 auth具有更强的一致性保证
配置文件参数
ETCD 3.3.12 配置文件
Member flags
–name:
-data-dir:
数据目录的路径。默认值:“$ {name} .etcd”env变量:ETCD_DATA_DIR
-wal-dir:
专用wal目录的路径。如果设置了此标志,则etcd会将WAL文件写入walDir而不是dataDir。这允许使用专用磁盘,并有助于避免日志记录和其他IO操作之间的竞争。默认:“”env变量:ETCD_WAL_DIR
–snapshot-count:
触发快照到磁盘的已提交事务数。默认值:“100000”env变量:ETCD_SNAPSHOT_COUNT
–heartbeat-interval:
心跳间隔的时间(以毫秒为单位)。默认值:“100”env变量:ETCD_HEARTBEAT_INTERVAL
–election-timeout:
选举超时的时间(以毫秒为单位)。默认值:“1000”env变量:ETCD_ELECTION_TIMEOUT
–listen-peer-urls:
* 侦听 peer 流量的URL列表。此标志告诉etcd接受来自其他 peer 的指定 scheme://IP:port 组合的传入请求。Scheme可以是http或https。或者,使用unix://<file-path>或unixs://<file-path>用于unix插槽。如果将0.0.0.0指定为IP,则etcd将侦听所有接口上的给定端口。如果给出IP地址和端口,etcd将监听给定的端口和接口。可以使用多个URL来指定要监听的多个地址和端口。etcd将响应来自任何列出的地址和端口的请求。* 默认值:\"http://localhost:2380\"* env变量:ETCD_LISTEN_PEER_URLS* 例如:\"http://10.0.0.1:2380\"* 无效示例:\"http://example.com:2380\"(域名对于绑定无效)
–listen-client-urls:
* 要监听客户端流量的URL列表。此标志告诉etcd接受来自客户端的指定 scheme://IP:port 组合的传入请求。Scheme可以是http或https。或者,使用unix://<file-path>或unixs://<file-path>用于unix插槽。如果将0.0.0.0指定为IP,则etcd将侦听所有接口上的给定端口。如果给出IP地址和端口,etcd将监听给定的端口和接口。可以使用多个URL来指定要监听的多个地址和端口。etcd将响应来自任何列出的地址和端口的请求。* 默认值:“ http://localhost:2379”* env变量:ETCD_LISTEN_CLIENT_URLS* 例如:“ http://10.0.0.1:2379”* 无效示例:“ http://example.com:2379”(域名对于绑定无效)
–max-snapshots:
要保留的最大快照文件数(0不受限制)默认值:5env变量:ETCD_MAX_SNAPSHOTSWindows上的用户默认值不受限制,建议手动清除至5(或某些安全偏好)。
–max-wals:
要保留的最大wal文件数(0不受限制)默认值:5env变量:ETCD_MAX_WALSWindows上的用户默认值不受限制,建议手动清除至5(或某些安全偏好)。
–cors:
以逗号分隔的CORS原始白名单(跨域资源共享)。默认:“”env变量:ETCD_CORS
–quota-backend-bytes:
当后端大小超过给定配额时(0默认为低空间配额),引发警报。默认值:0env变量:ETCD_QUOTA_BACKEND_BYTES
–backend-batch-limit:
BackendBatchLimit是提交后端事务之前的最大操作。默认值:0env变量:ETCD_BACKEND_BATCH_LIMIT
–backend-batch-interval:
BackendBatchInterval是提交后端事务之前的最长时间。默认值:0env变量:ETCD_BACKEND_BATCH_INTERVAL
–max-txn-ops:
事务中允许的最大操作数。默认值:128env变量:ETCD_MAX_TXN_OPS
–max-request-bytes:
服务器将接受的最大客户端请求大小(字节)。默认值:1572864 ----> 1.5MBenv变量:ETCD_MAX_REQUEST_BYTES
–grpc-keepalive-min-time:
客户端在ping服务器之前应等待的最短持续时间间隔。默认值:5senv变量:ETCD_GRPC_KEEPALIVE_MIN_TIME
–grpc-keepalive-interval:
服务器到客户端ping的频率持续时间,以检查连接是否处于活动状态(0表示禁用)。默认值:2henv变量:ETCD_GRPC_KEEPALIVE_INTERVAL
–grpc-keepalive-timeout:
关闭非响应连接之前的额外持续等待时间(0表示禁用)。默认值:20秒env变量:ETCD_GRPC_KEEPALIVE_TIMEOUT
Clustering flags
Proxy flags
Security flags
Logging flags
Unsafe flags
Miscellaneous flags
Profiling flags
Auth flags
Experimental flags
运维指南
etcd 运维指南
两阶段配置修改保持集群安全
阶段1 - 通知新配置群集
为了添加成员到 etcd 集群,发起一个 API 调用来请求要添加一个新成员到集群。这是添加新成员到现有集群的唯一方法。当集群同意配置修改时 API 调用返回。
阶段 2 - 启动新成员
备份恢复
https://www.maideliang.com/index.php/archives/25/
etcd集群备份和数据恢复以及优化运维
K8S中使用了不同的 api 版本
k8s实践11:etcd集群数据备份恢复
flannel当前版本 (v0.10.0) 操作etcd使用的是v2的API
flannel 使用的etcd的 v2 接口
kubernetes操作etcd使用的v3的API
推荐在 Kubernetes 集群中使用 Etcd v3,v2 版本已在 Kubernetes v1.11 中弃用
常见故障
少数跟随者失败
当少于一半的跟随者失败时, etcd 集群依然可以接收请求并让程序没有任何大的中断。例如,两个跟随者失败将不会影响一个五个成员的 etcd 集群运作。但是,客户端将失去到失败成员的连通性。对于读请求客户端类库应该通过自动重新连接到其他成员来对用户隐藏这些中断。运维人员应该预期其他成员上的系统负载会因为重新连接而提升。
Leader 失败
当 leader 失败时, etcd 集群自动选举一个新的 leader 。选举不会在 leader 失败之后立即发生。大约需要一个选举超时的时间来选举新的 leader ,因为失败选取模型是基于超时的。在 leader 选举的期间, 集群不能处理任何写。在选举期间发送的写请求将排队等待处理知道新的 leader 被选举出来。已经发送给旧有 leader 但是还没有提交的写请求可能会丢失。新的 leader 有能力重新写入从之前 leader 而来的任何未提交的条目。从用户的角度,在新的 leader 选举之后某些写请求可能超时。无论如何,已提交的请求从来不会丢失。新的 leader 自动延长所有的租约(lease)。这个机制保证足浴将不会在准许的 TTL 之前过期,即使它是被旧有的 leader 准许。
多数失败
当群集的大多数成员发生故障时,etcd群集将失败,并且无法接受更多写入。一旦大多数成员可用,etcd集群只能从多数失败中恢复。如果大多数成员无法重新联机,则运营商必须启动灾难恢复才能恢复群集。一旦大多数成员工作,etcd集群将自动选择一个新的领导者并返回健康状态。新领导者会自动延长所有租约的超时时间。此机制确保由于服务器端不可用而没有租约到期。
网络分区
网络分区类似于次要粉丝故障或领导者故障。网络分区将etcd集群分为两部分; 一个成员占多数,另一个成员少数。多数方成为可用集群,少数方面不可用; 在etcd中没有“裂脑”。如果领导者占多数,那么从多数人的角度来看,失败是少数追随者的失败。如果领导者处于少数派一方,则领导者失败。少数派领导人下台,多数党选举新领导人。一旦网络分区清除,少数民族自动从多数方面识别领导者并恢复其状态
启动期间失败
集群启动时仅仅当所有要求的成员都成功启动才视为成功。如果在启动期间发生任何失败,在所有成员上删除数据目录并用新的集群记号(cluster-token)或者新的发现记号(discovery token)重新启动集群。当然,可以像恢复运行中的集群那样恢复失败的启动集群。但是,它大多数情况下总是比启动一个新的花费更多时间和资源来恢复集群,因为没有任何数据要恢复。
读写数据流程
etcd 是一个强一致性的分布式 KV 存储,所谓强一致性,简单来说就是一个写操作成功后,从任何一个节点读出来的数据都是最新值,而不会出现写数据成功后读不出来或者读到旧数据的情况。etcd 通过 raft 协议来实现 leader 选举、配置变更以及保证数据读写的一致性。下面简单介绍下 etcd 的读写流程:
写数据流程(以 leader 节点为例,见上图):
1.etcd 任一节点的 etcd server 模块收到 Client 写请求(如果是 follower 节点,会先通过 Raft 模块将请求转发至 leader 节点处理)。2.etcd server 将请求封装为 Raft 请求,然后提交给 Raft 模块处理。3.leader 通过 Raft 协议与集群中 follower 节点进行交互,将消息复制到follower 节点,于此同时,并行将日志持久化到 WAL。4.follower 节点对该请求进行响应,回复自己是否同意该请求。5.当集群中超过半数节点((n/2)+1 members )同意接收这条日志数据时,表示该请求可以被Commit,Raft 模块通知 etcd server 该日志数据已经 Commit,可以进行 Apply。6.各个节点的 etcd server 的 applierV3 模块异步进行 Apply 操作,并通过 MVCC 模块写入后端存储 BoltDB。7.当 client 所连接的节点数据 apply 成功后,会返回给客户端 apply 的结果。
读数据流程:
* etcd 任一节点的 etcd server 模块收到客户端读请求(Range 请求)* 判断读请求类型,如果是串行化读(serializable)则直接进入 Apply 流程* 如果是线性一致性读(linearizable),则进入 Raft 模块* Raft 模块向 leader 发出 ReadIndex 请求,获取当前集群已经提交的最新数据 Index* 等待本地 AppliedIndex 大于或等于 ReadIndex 获取的 CommittedIndex 时,进入 Apply 流程* Apply 流程:通过 Key 名从 KV Index 模块获取 Key 最新的 Revision,再通过 Revision 从 BoltDB 获取对应的 Key 和 Value。
对比
表格
性能
当服务节点数越来越多时,服务注册中心的性能会成为瓶颈,这时候就需要通过水平扩容来提升服务注册中心集群的性能。* 对于那些采用了类 Paxos 协议的强一致性的组件,如ZooKeeper,由于每次写操作需要过半的节点确认。水平扩容不能提升整个集群的写性能,只能提升整个集群的读性能。* 而对于采用最终一致性的组件来说,水平扩容可以同时提升整个集群的写性能和读性能。
etcd与 consul 对比
etcd和Consul解决了不同的问题。如果寻找分布式一致键值存储,etcd是比Consul更好的选择。如果寻找端到端集群服务发现,etcd将没有足够的功能; 选择Kubernetes,Consul或SmartStack。
数据库
MySQL
1.知识点
binlog 格式
1). ROW
2). Statement
3). MiXED
修改方式
通过my.cnf配置文件修改和`set global binlog_format='ROW/STATEMENT/MIXED'` 进行修改;命令行 `show variables like 'binlog_format'` 命令查看binglog格式;
2.主从原理
复制的基本过程如下:从节点上的I/O 进程连接主节点,并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容;主节点接收到来自从节点的I/O请求后,通过负责复制的I/O进程根据请求信息读取指定日志指定位置之后的日志信息,返回给从节点。返回信息中除了日志所包含的信息之外,还包括本次返回的信息的bin-log file 的以及bin-log position;从节点的I/O进程接收到内容后,将接收到的日志内容更新到本机的relay log中,并将读取到的binary log文件名和位置保存到master-info 文件中,以便在下一次读取的时候能够清楚的告诉Master“我需要从某个bin-log 的哪个位置开始往后的日志内容,请发给我”;Slave 的 SQL线程检测到relay-log 中新增加了内容后,会将relay-log的内容解析成在主节点上实际执行过的操作,并在本数据库中执行。
3.事物
四个特性(ACID)
原子性(Atomicity,或称不可分割性)
一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性(Consistency)
在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
隔离性(Isolation,又称独立性)
数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
事务隔离分级别
对比图:
读未提交(Read uncommitted)
- 所有事务都可以看到其他未提交事务的执行结果- 本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少- 该级别引发的问题是——脏读(Dirty Read):读取到了未提交的数据
读提交(read committed)
可重复读(repeatable read)
- 这是MySQL的默认事务隔离级别- 它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行- 此级别可能出现的问题——幻读(Phantom Read):当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行- InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决幻读问题;InnoDB还通过间隙锁解决幻读问题
串行化(Serializable)
在 MySQL 命令行的默认设置下,事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作。因此要显式地开启一个事务务须使用命令 BEGIN 或 START TRANSACTION,或者执行命令 SET AUTOCOMMIT=0,用来禁止使用当前会话的自动提交。
持久性(Durability)
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
4.多活
5.备份
mysqldump
--extended-insert=true 导出一条 sql ,source 时候提高速度
--master-data=2
--flush-logs
--lock-all-tables
--opt
--default-character-set=utf8
--triggers -R --hex-blob
--events
xtrabackup
只能备份InnoDB和XtraDB两种数据表,而不能备份MyISAM数据表
innobackupex
全备./innobackupex --user=root --password=bfd123 /opt/backup/full/准备./innobackupex --apply-log /opt/backup/full/2016-11-15_06-19-00/恢复./innobackupex --copy-back /opt/backup/full/2016-11-15_06-19-00/
Redis
1.存储
RDB
服务器配置自动触发
多少秒内至少达到多少写操作】就开启RDB数据同步。
# 900s内至少达到一条写命令save 900 1# 300s内至少达至10条写命令save 300 10# 60s内至少达到10000条写命令save 60 10000
RDB默认生成的文件名为dump.rdb
# 是否压缩rdb文件rdbcompression yes# rdb文件的名称dbfilename redis-6379.rdb# rdb文件保存目录dir ~/redis/
优点
1.与AOF方式相比,通过rdb文件恢复数据比较快。
2.rdb文件非常紧凑,适合于数据备份。
3.通过RDB进行数据备,由于使用子进程生成,所以对Redis服务器性能影响较小。
缺点
1.如果服务器宕机的话,采用RDB的方式会造成某个时段内数据的丢失,比如我们设置10分钟同步一次或5分钟达到1000次写入就同步一次,那么如果还没达到触发条件服务器就死机了,那么这个时间段的数据会丢失。
2.使用save命令会造成服务器阻塞,直接数据同步完成才能接收后续请求。
3.使用bgsave(异步)命令在forks子进程时,如果数据量太大,forks的过程也会发生阻塞,另外,forks子进程会耗费内存。
bgsave的原理是什么?你给出两个词汇就可以了,fork和cow。fork是指redis通过创建子进程来进行bgsave操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。
生成rdb文件的过程
1. 生成临时rdb文件,并写入数据。
2.完成数据写入,用临时文代替代正式rdb文件。
3.删除原来的db文件。
AOF(Append-only file)
开启AOF持久化方式(默认不开启)
# 开启aof机制appendonly yes# aof文件名appendfilename \"appendonly.aof\
三种写入策略
always
客户端的每一个写操作都保存到aof文件当,这种策略很安全,但是每个写请注都有IO操作,所以也很慢。
everysec(默认)
appendfsync的默认写入策略,每秒写入一次aof文件,因此,最多可能会丢失1s的数据。
no
Redis服务器不负责写入aof,而是交由操作系统来处理什么时候写入aof文件。更快,但也是最不安全的选择,不推荐使用。
AOF文件重写
两种重写方式:
配置文件参数 no-appendfsync-on-rewrite
通过在redis.conf配置文件中的选项no-appendfsync-on-rewrite可以设置是否开启重写,这种方式会在每次fsync时都重写,影响服务器性以,因此默认值为no,不推荐使用。
# 默认不重写aof文件no-appendfsync-on-rewrite no
bgrewriteaof命令
客户端向服务器发送bgrewriteaof命令,也可以让服务器进行AOF重写。
# 让服务器异步重写追加aof文件命令> bgrewriteaof
重写aof文件的好处
压缩aof文件,减少磁盘占用量。
将aof的命令压缩为最小命令集,加快了数据恢复的速度。
AOF只是追加日志文件,因此对服务器性能影响较小,速度比RDB要快,消耗的内存较少。
AOF方式生成的日志文件太大,即使通过AFO重写,文件体积仍然很大。
恢复数据的速度比RDB慢。
AOF文件损坏
在写入aof日志文件时,如果Redis服务器宕机,则aof日志文件文件会出格式错误,在重启Redis服务器时,Redis服务器会拒绝载入这个aof文件,可以通过以下步骤修复aof并恢复数据。
1. 备份现在aof文件,以防万一。
2. 使用redis-check-aof命令修复aof文件,该命令格式如下:
# 修复aof日志文件$ redis-check-aof -fix file.aof
3. 重启Redis服务器,加载已经修复的aof文件,恢复数据。
两种存储方式对比
对比图
在应用时,要根本自己的实际需求,选择RDB或者AOF,其实,如果想要数据足够安全,可以两种方式都开启,但两种持久化方式同时进行IO操作,会严重影响服务器性能,因此有时候不得不做出选择。
当RDB与AOF两种方式都开启时,Redis会优先使用AOF日志来恢复数据,因为AOF保存的文件比RDB文件更完整。
如果你只是单纯把Redis作为缓存服务器,那么可以完全不用考虑持久化,但是,在如今的大多数服务器架构中,Redis的单单只是扮演一个缓存服务器的角色,还可以作为数据库,保存我们的业务数据,此时,我们则需要好好了解有关Redis持久化策略的区别与选择。
2.集群
着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。
3.哨兵
着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。
4. 安全层面考虑
密码认证
限制一些上锁 或者造成阻塞的操作 keys
Redis的KEYS命令引起宕机事件
keys *进行模糊匹配引发 Redis 锁,造成 Redis 锁住,CPU 飙升,引起了所有调用链路的超时并且卡住,等 Redis 锁的那几秒结束,所有的请求流量全部请求到 RDS 数据库中,使数据库产生了雪崩,使数据库宕机。
改进方案
* 所有线上操作,全部要经过运维通过后方可执行,运维部门逐步快速收回各项权限* 新增 Redis 实例,进行分离* 如果有使用类似 keys 正则命令需求,使用 scan 命令代替
线上禁止使用 monitor 命令
禁止生产环境使用 monitor 命令,monitor 命令在高并发条件下,会存在内存暴增和影响 Redis 性能的隐患
禁用命令
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。
keys
客户端连接后可查看所有存在的键
flushdb
清空数据库
flushall
清空所有记录,数据库
config
客户端连接后可配置服务器
禁用方法
配置文件底部增加
完全禁用
rename-command FLUSHALL \"\"rename-command FLUSHDB \"\"rename-command CONFIG \"\"rename-command KEYS \"\"
如果想要保留命令,但是不能轻易使用,可以重命名命令来设定:重命名之后的名字越复杂越好
rename-command FLUSHALL \"\"rename-command FLUSHDB \"\"rename-command CONFIG \"\"rename-command KEYS helloworld
127.0.0.1:6379> helloworld(error) ERR wrong number of arguments for 'keys' command
阿里云上的 REDIS 有危险的命令已经在系统层面禁掉了
Redis 4.0的Lazyfree机制可以避免del、flushdb、flushall、rename等命令引起的redis-server阻塞,提高服务稳定性
5.redis 主从复制原理
6. redis 提供 6种数据淘汰策略
1. volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰4. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰6. no-enviction(驱逐):禁止驱逐数据
7.迁移
1. 使用AOF文件进行迁移
开启现有Redis实例的AOF功能(如果实例已经启用AOF功能则忽略此步骤)。
# redis-cli -h old_instance_ip -p old_instance_port config set appendonly yes
通过AOF文件将数据导入到新的云数据库Redis版实例(假定生成的AOF文件名为 appendonly.aof)。
# redis-cli -h aliyun_redis_instance_ip -p 6379 -a password --pipe < appendonly.aof
注意 如果原有的Redis实例不需要一直开启AOF,可在导入完成后通过以下命令关闭。
# redis-cli -h old_instance_ip -p old_instance_port config set appendonly no
2.使用redis-shake进行迁移
redis-shake是阿里云自研的开源工具,支持对Redis数据进行解析(decode)、恢复(restore)、备份(dump)、同步(sync/rump)。
在rump模式下,redis-shake以SCAN的方式从源端Redis获取全量数据,写入到目的端,实现数据迁移。这种迁移方式不依赖于SYNC或PSYNC,对Redis服务性能的影响小,支持Redis集群,可以广泛应用于自建Redis、云Redis之间的迁移。
rump模式不支持增量数据迁移,建议您先停止源端Redis的写入再进行迁移,防止数据不一致。
在sync模式下,redis-shake使用SYNC或PSYNC命令将数据从源端Redis同步到目的端Redis,支持全量数据同步和增量数据同步,增量同步在全量同步完成后自动开始。
云数据库Redis集群版目前无法作为sync模式的源端。
在dump模式下,redis-shake可以将Redis数据库的数据保存到RDB文件中,通过RDB文件可以实现数据恢复或者迁移。
迁移模式:
rump
redis-shake以SCAN的方式从源端Redis获取全量数据,写入到目的端,实现数据迁移。
# ./redis-shake -type=rump -conf=redis-shake.conf
restore
redis-shake可以将RDB文件中保存的数据恢复到Redis实例中,实现数据恢复或者迁移。
./redis-shake -type=restore -conf=redis-shake.conf
备份模式:
dump
redis-shake可以将Redis数据库的数据保存到RDB文件中,通过RDB文件可以实现数据恢复或者迁移。
./redis-shake -type=dump -conf=redis-shake.conf
https://github.com/alibaba/RedisShake
3.使用阿里云DTS进行迁移
4.校验迁移后的数据
如果Redis迁移的过程出现异常,源端与目的端Redis的数据将会不一致。使用redis-full-check进行校验能够找出异常数据,为数据对齐提供可靠依据。redis-full-check是阿里云自研的Redis数据校验工具,能够提取源端和目的端的数据进行多轮差异化比较,并将比较结果记录在一个SQLite3数据库中,从而达到全量数据校验的目的。
./redis-full-check -s \"<Redis集群地址1连接地址:Redis集群地址1端口号;Redis集群地址2连接地址:Redis集群地址2端口号;Redis集群地址3连接地址:Redis集群地址3端口号>\" -p <Redis集群密码> -t <Redis连接地址:Redis端口号> -a <Redis密码> --comparemode=1 --comparetimes=1 --qps=10 --batchcount=100 --sourcedbtype=1 --targetdbfilterlist=0
https://github.com/alibaba/RedisFullCheck
MongoDB
主从
分片
去重脚本
【解决】MongoDB 线上业务处理,数据去重脚本实现
文档型存储数据库
基于日志的异步复制
BASE:相对于事物的 ACID 特性,NoSQL 数据库保证的是 BASE 特性。BASE 是最终一致性和软事物。
脚本语言
shell
sed
提取字符串
echo here365test |sed 's/[a-z]*\\([1-9]*\\)[a-z]*/\\1/g'
awk
按最后一列求和
awk 'BRGIN{total=0} {total+=$NF} END{print total}' 2.txt
awk '{total+=$NF} END{print total}' 2.txt
列求最大值
cat you.txt |awk 'BEGIN{a=0}{if ($1>a) a=$1 fi}END{print a}'
设定一个变量开始为0,遇到比该数大的值,就赋值给该变量,直到结束。
求平均值
cat data|awk '{sum+=$1} END {print \"Average = \
set 设置了当前shell进程的本地变量,本地变量只在当前shell的进程内有效,不会被子进程继承和传递。env 仅为将要执行的子进程设置环境变量。export 将一个shell本地变量提升为当前shell进程的环境变量,从而被子进程自动继承,但是export的变量无法改变父进程的环境变量。source 运行脚本的时候,不会启用一个新的shell进程,而是在当前shell进程环境中运行脚本。exec 运行脚本或命令的时候,不会启用一个新的shell进程,并且exec后续的脚本内容不会得到执行,即当前shell进程结束了。
set -e
设置该选项后,当脚本中任何以一个命令执行返回的状态码不为0时就退出整个脚本(默认脚本运行中某行报错会继续往下执行)
set -u
设置该选项后,当脚本在执行过程中尝试使用未定义过的变量时,报错并退出运行整个脚本(默认会把该变量的值当作空来处理),这个感觉也非常有用,有些时候可能在脚本中变量很多很长,疏忽了把某个变量的名字写错了,这时候运行脚本不会报任何错误,但结果却不是你想要的,排查起来很是头疼,使用这个选项就完美的解决了这个问题。
set -x
默认情况下,脚本执行后,屏幕只显示运行结果,没有其他内容。加上之后执行过程会显示
set -o pipefail
设置了这个选项以后,包含管道命令的语句的返回值,会变成最后一个返回非零的管道命令的返回值。
set +x
表示关闭
数组列表
字典
lua
http://wiki.jikexueyuan.com/list/lua/
python
自动化部署
ansible
用法示例:
ansible web -m command -a 'echo lyj|passwd --stdin lyj2'
ansible all -m ping 返回 pong
常用模块
command(默认模块): 绝大多数系统命令,可能会不支持管道
ping 模块:检测主机存活
shell 模块: 调用bash执行命令
copy 模块:\t复制本地文件至远程服务器,并且能够改属性等
[root@node-1 ~]# ansible db -m copy -a \"src=/root/1.sh dest=/tmp/1.sh owner=lyj backup=yes mode=755\" 如果有修改的话,将原来文件命名改成以时间戳为结尾,然后传入一个新的文件
cron模块:crontab 定时执行任务的模块
[root@node-1 ~]# ansible web -m cron -a 'minute=\"*/5\" job=\"/usr/sbin/ntpdate ntp1.aliyun.com &>/dev/null; /usr/sbin/hwclock -w\" name=SynctimeLYJ'[root@node-1 ~]# crontab -l#Ansible: SynctimeLYJ*/5 * * * * /usr/sbin/ntpdate ntp1.aliyun.com &>/dev/null; /usr/sbin/hwclock -w#删除名称为SynctimeLYJ的定时任务[root@node1 ~]# ansible lyj-web -m cron -a 'state=absent name=SynctimeLYJ'
file 模块:设置文件属性
[root@node1 ~]# ansible web -m file -a 'path=/root/1.sh mode=755' # 修改权限为 755[root@node1 ~]# ansible web -m file -a 'path=/tmp/lyj owner=lyj group=root mode=0644 state=touch' # 创建文件[root@node1 ~]# ansible lyj-web -m file -a 'src=/tmp/lyj dest=/tmp/xxx state=link' # 给lyj创建链接文件为xxx src只用于创建链接 [root@node1 ~]# ansible lyj-web -m file -a 'path=/tmp/dir state=directory mode=777' # 创建目录[root@node1 ~]# ansible lyj-web -m file -a 'path=/tmp/lyj state=absent' # 删除文件[root@node1 ~]# ansible lyj-web -m file -a 'path=/tmp/dir state=absent' # 删除目录[root@node1 ~]# ansible lyj-web -m file -a 'path=/tmp/xxx state=absent' # 删除链接
hostname模块:
[root@node1 ~]# ansible 1.1.1.101 -m hostname -a \"name=node1\" 改主机名
pip 模块:python的包管理工具
[root@node1 ~]# ansible lyj-web -m pip -a 'name=bottle' 装这个包
yum 模块:安装软件
[root@node-1 ~]# ansible 1.1.1.103 -m yum -a 'name=httpd state=latest'
state:present/latest用于安装包,absent用于remove安装包
service 模块:
[root@node-1 ~]# ansible 1.1.1.103 -m service -a 'enabled=true name=httpd state=started' # 启动、开机自启[root@node-1 ~]# ansible 1.1.1.103 -m service -a 'name=httpd state=stopped' # 停止[root@node-1 ~]# ansible 1.1.1.103 -m command -a \"systemctl status httpd.service\
user模块:
[root@node-1 ~]# ansible 1.1.1.103 -m user -a 'name=lyj001 home=/tmp/lyj001 shell=/bin/bash uid=2000 comment=\"test user\" group=lyj'[root@node-3 tmp]# finger lyj001Login: lyj001 \t\t\tName: test userDirectory: /tmp/lyj001 \tShell: /bin/bashNever logged in.No mail.No Plan.[root@node-3 tmp]# cat /etc/passwd|grep lyj001lyj001:x:2000:1001:test user:/tmp/lyj001:/bin/bash
script模块: 将本地脚本复制到被管理主机上进行运行
[root@node-1 ~]# cat date.sh#!/bin/bashdate > /tmp/lyj.date[root@node-1 ~]# ansible 1.1.1.102 -m script -a 'date.sh'1.1.1.102 | CHANGED => { \"changed\
setup模块: 用于模块收集、查看被管理主机的facts。可基于特定值做条件判断,比如系统版本
[root@node-1 ~]# ansible 1.1.1.101 -m setup -a 'filter=ansible_nodename'1.1.1.101 | SUCCESS => { \"ansible_facts\": { \"ansible_nodename\": \"node-1\
命令介绍:
ansible-playbook:剧本
YAML语法简介:
YAML:\"YAML Ain't a Markup Language\"\t1)\t首先需要以\"---\"(三个减号)开始,且需顶行首写(建议下面空一行)。\t2)\t次行开始正常写 Playbook 的内容,但笔者一般建议写明该 Playbook 的功能\t3)\t使用#号注释代码。\t4)\t缩进必须是统一的,不能空格和tab混用\t5)\t缩进的级别也必须是一致的,同样的缩进代表同样的级别,程序判断配置的级别是通过缩进结合换行来实现的\t6)\tYAML文件内容和linux系统大小写判断方式保持一致,是区块大小写的,k/v的值均需要大小写敏感\t7)\tk/v的值可同行写也可换行写。同行使用:分割,换行写需要以 - 分割\t8)\t一个完整的代码块功能需最少元素需包括:name tasks\t9)\t一个name只能包括一个 tasks
示例:
安装 nginx
安装 nginx 根据文件触发handlers
ansible-galaxy: 查找安装 roles
# 列出所有已安装的galaxyansible-galaxy list# 安装galaxyansible-galaxy install geerlingguy.redis# 删除galaxyansible-galaxy remove geerlingguy.redis
ansible-doc
#列出支持的模块ansible-doc -l#模块功能说明ansible-doc -s yum
ansible-vault 加解密相关
ansible-vault encrypt hello.yml\t#加密ansible-vault decrypt hello.yml\t#解密ansible-vault view hello.yml\t#查看内容
ansible-console: 交互式工具
cd web # 切换 web 主机组forks 2 # 设置并发线程数为 2list # 查看当前分组的所有主机列表? #列出所有可用模块help service # 查看 service 模块用法service name=httpd state=started # 启动 httpd 服务
saltstack
系统指标
内存
内存映射
物理内存(主存)
大多数计算机用的主存都是动态随机访问内存(DRAM)。只有内核才可以直接访问物理内存
虚拟内存
内核空间
用户空间
进程在用户态时,只能访问用户空间内存;只有进入内核态后,才可以访问内核空间内存**。
内存分配与回收
分配
malloc() 是 C 标准库提供的内存分配函数
brk()
对小块内存(小于 128K),C 标准库使用 brk() 来分配,也就是通过移动堆顶的位置来分配内存。这些内存释放后并不会立刻归还系统,而是被缓存起来,这样就可以重复使用。
mmap()
而大块内存(大于 128K),则直接使用内存映射 mmap() 来分配,也就是在文件映射段找一块空闲内存分配出去。
回收
free()
unmap()
在发现内存紧张时,系统就会通过一系列机制来回收内存
回收缓存,比如使用 LRU(Least Recently Used)算法,回收最近使用最少的内存页面;
回收不常访问的内存,把不常用的内存通过交换分区直接写到磁盘中;写入swap
回收不常访问的内存时,会用到交换分区(以下简称 Swap)。Swap 其实就是把一块磁盘空间当成内存来用。它可以把进程暂时不用的数据存储到磁盘中(这个过程称为换出),当进程访问这些内存时,再从磁盘读取这些数据到内存中(这个过程称为换入)
通常只在内存不足时,才会发生 Swap 交换。并且由于磁盘读写的速度远比内存慢,Swap 会导致严重的内存性能问题。
杀死进程,内存紧张时系统还会通过 OOM(Out of Memory),直接杀掉占用大量内存的进程。
OOM 是内核的一种保护机制。它监控进程的内存使用情况,并且使用 oom_score 为每个进程的内存使用情况进行评分:
1. 一个进程消耗的内存越大,oom_score 就越大;
也就越容易被 OOM 杀死,从而可以更好保护系统
2. 一个进程运行占用的 CPU 越多,oom_score 就越小。
echo -16 > /proc/$(pidof sshd)/oom_adj
查看内存使用情况
free
- 第一列,total 是总内存大小;- 第二列,used 是已使用内存的大小,包含了共享内存;- 第三列,free 是未使用内存的大小;- 第四列,shared 是共享内存的大小;- 第五列,buff/cache 是缓存和缓冲区的大小;- 最后一列,available 是新进程可用内存的大小。
最后一列的可用内存 available 。available 不仅包含未使用内存,还包括了可回收的缓存,所以一般会比未使用内存更大。不过,并不是所有缓存都可以回收,因为有些缓存可能正在使用中。
top
- VIRT 是进程虚拟内存的大小,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内。- RES 是常驻内存的大小,也就是进程实际使用的物理内存大小,但不包括 Swap 和共享内存。- SHR 是共享内存的大小,比如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段等。- %MEM 是进程使用物理内存占系统总内存的百分比。
除了要认识这些基本信息,在查看 top 输出时,你还要注意两点。第一,虚拟内存通常并不会全部分配物理内存。从上面的输出,你可以发现每个进程的虚拟内存都比常驻内存大得多。第二,共享内存 SHR 并不一定是共享的,比方说,程序的代码段、非共享的动态链接库,也都算在 SHR 里。当然,SHR 也包括了进程间真正共享的内存。所以在计算多个进程的内存使用时,不要把所有进程的 SHR 直接相加得出结果。
buffer:缓冲(用于平滑流速、衔接不同流速的设备)减小短期内突发I/O的影响,起到流量整形的作用
内核缓冲区用到的内存,对应的是 /proc/meminfo 中的 Buffers 值
cache:快取、缓存(用于减少低速设备的访问、提高高速设备的效率)是系统两端处理速度不匹配时的一种折衷策略。因为CPU和memory之间的速度差异越来越大,所以人们充分利用数据的局部性(locality)特征,通过使用存储系统分级(memory hierarchy)的策略来减小这种差异带来的影响。
内核页缓存和 Slab 用到的内存,对应的是 /proc/meminfo 中的 Cached 与 SReclaimable 之和
buddy 用于以页为单位分配内存
slab分配器分配内存以Byte为单位
/proc/meminfo
Buffers
对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB 左右)。这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把多次小的写合并成单次大的写等等。
Cached
是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。这样,下次访问这些文件数据时,就可以直接从内存中快速获取,而不需要再次访问缓慢的磁盘。
SReclaimable
是 Slab 的一部分。Slab 包括两部分,其中的可回收部分,用 SReclaimable 记录;而不可回收部分,用 SUnreclaim 记录。
读写请求中的 buffer/cache
写文件时会用到 Cache 缓存数据,而写磁盘则会用到 Buffer 来缓存数据。
读文件时数据会缓存到 Cache 中,而读磁盘时数据会缓存到 Buffer 中
Buffer 既可以用作“将要写入磁盘数据的缓存”,也可以用作“从磁盘读取数据的缓存”。
Cache 既可以用作“从文件读取数据的页缓存”,也可以用作“写文件的页缓存”。
总结:Buffer 是对磁盘数据的缓存,而 Cache 是文件数据的缓存,它们既会用在读请求中,也会用在写请求中。
使用 buffer/cache 的好处
从写的角度来说,不仅可以优化磁盘和文件的写入,对应用程序也有好处,应用程序可以在数据真正落盘前,就返回去做其他工作。
从读的角度来说,不仅可以提高那些频繁访问数据的读取速度,也降低了频繁 I/O 对磁盘的压力。
缓存命中率
指直接通过缓存获取数据的请求次数,占所有数据请求次数的百分比
命中率越高,表示使用缓存带来的收益越高,应用程序的性能也就越好
查看系统缓存命中情况的工具 bcc
bcc
cachestat 提供了整个操作系统缓存的读写命中情况。
cachetop 提供了每个进程的缓存命中情况。
手动释放缓存
echo 1 > /proc/sys/vm/drop_caches:表示清除 page cache。echo 2 > /proc/sys/vm/drop_caches:表示清除回收 slab 分配器中的对象(包括目录项缓存和 inode 缓存)。slab 分配器是内核中管理内存的一种机制,其中很多缓存数据实现都是用的 page cache。echo 3 > /proc/sys/vm/drop_caches:表示清除 page cache 和 slab 分配器中的缓存对象。
内存泄漏
内存泄露到底是怎么发生的
栈内存由系统自动分配和管理。一旦程序运行超出了这个局部变量的作用域,栈内存就会被系统自动回收,所以不会产生内存泄漏的问题。
堆内存由应用程序自己来分配和管理。除非程序退出,这些堆内存并不会被系统自动释放,而是需要应用程序明确调用库函数 free()来释放它们。如果应用程序没有正确释放堆内存,就会造成内存泄漏。
只读段,包括程序的代码和常量,由于是只读的,不会再去分配新的内存,所以也不会产生内存泄漏。
数据段,包括全局变量和静态变量,这些变量在定义时就已经确定了大小,所以也不会产生内存泄漏。
最后一个内存映射段,包括动态链接库和共享内存,其中共享内存由程序动态分配和管理。所以,如果程序在分配后忘了回收,就会导致跟堆内存类似的泄漏问题。
危害:内存泄漏的危害非常大,这些忘记释放的内存,不仅应用程序自己不能访问,系统也不能把它们再次分配给其他应用。内存泄漏不断累积,甚至会耗尽系统内存。
系统最终可以通过 OOM (Out of Memory)机制杀死进程,但进程在 OOM 前,可能已经引发了一连串的反应,导致严重的性能问题。
其他需要内存的进程,可能无法分配新的内存;内存不足,又会触发系统的缓存回收以及 SWAP 机制,从而进一步导致 I/O 的性能问题等等。
发生内存泄漏之后该如何排查和定位
vmstat 3 #每隔三秒输出一组数据,可以观察内存的变化情况
用 top 或 ps 来观察进程的内存使用情况,然后找出内存使用一直增长的进程,最后再通过 pmap [PID] 查看进程的内存分布。
memleak 可以跟踪系统或指定进程的内存分配、释放请求,然后定期输出一个未释放内存和相应调用栈的汇总情况(默认 5 秒)
容器中排查
在容器外部构建相同路径的文件以及依赖库。$ docker cp app:/app /app$ /usr/share/bcc/tools/memleak -p $(pidof app) -a
valgrind :valgrind不能指定PID分析,所以一般是通过监控系统发现有内存问题后,到线下用 valgrind 分析。
应用 Valgrind 发现 Linux 程序的内存问题
valgrind不是实时的查看命令,把检查结果存入到一个文本中。valgrind --tool=memcheck --leak-check=full --xtree-leak=yes --show-mismatched-frees=yes test.txt
总结: 应用程序可以访问的用户内存空间,由只读段、数据段、堆、栈以及文件映射段等组成。其中,堆内存和内存映射,需要应用程序来动态管理内存段,所以我们必须小心处理。不仅要会用标准库函数 malloc()来动态分配内存,还要记得在用完内存后,调用库函数 _free() 来 _ 释放它们。
malloc() 和 free() 通常并不是成对出现,而是需要你,在每个异常处理路径和成功路径上都释放内存 。
在多线程程序中,一个线程中分配的内存,可能会在另一个线程中访问和释放。
更复杂的是,在第三方的库函数中,隐式分配的内存可能需要应用程序显式释放。
java 性能调优 https://my.oschina.net/feichexia/blog/196575
JVM性能调优监控工具jps、jstack、jmap、jhat、jstat、hprof使用详解
swap
脏页:大部分文件页,都可以直接回收,以后有需要时,再从磁盘重新读取就可以了。而那些被应用程序修改过,并且暂时还没写入磁盘的数据(也就是脏页),就得先写入磁盘,然后才能进行内存释放。
一般可以通过两种方式写入磁盘
可以在应用程序中,通过系统调用 fsync ,把脏页同步到磁盘中;
也可以交给系统,由内核线程 pdflush 负责这些脏页的刷新。
Swap 把不常访问的内存(脏页、匿名页)先写到磁盘中,然后释放这些内存,给其他更需要的进程使用。再次访问这些内存时,重新从磁盘读入内存就可以了。
原理:把一块磁盘空间或者一个本地文件,当成内存来使用。它包括换出和换入两个过程。
换出,就是把进程暂时不用的内存数据存储到磁盘中,并释放这些数据占用的内存。
换入,则是在进程再次访问这些内存的时候,把它们从磁盘读到内存中来。
总结: 内存回收机制。这些回收的内存既包括了文件页,又包括了匿名页。- 对文件页的回收,当然就是直接回收缓存,或者把脏页写回磁盘后再回收。- 而对匿名页的回收,其实就是通过 Swap 机制,把它们写入磁盘后再释放内存。在内存资源紧张时,Linux 通过直接内存回收和定期扫描的方式,来释放文件页和匿名页,以便把内存分配给更需要的进程使用。文件页的回收比较容易理解,直接清空缓存,或者把脏数据写回磁盘后,再释放缓存就可以了。而对不常访问的匿名页,则需要通过 Swap 换出到磁盘中,这样在下次访问的时候,再次从磁盘换入到内存中就可以了。开启 Swap 后,你可以设置 /proc/sys/vm/min_free_kbytes ,来调整系统定期回收内存的阈值,也可以设置 /proc/sys/vm/swappiness ,来调整文件页和匿名页的回收倾向。
/proc/sys/vm/swappiness ,用来调整使用 Swap 的积极程度,swappiness 的范围是 0-100,数值越大,越积极使用 Swap,也就是更倾向于回收匿名页;数值越小,越消极使用 Swap,也就是更倾向于回收文件页。虽然 swappiness 的范围是 0-100,不过要注意,这并不是内存的百分比,而是调整 Swap 积极程度的权重,即使你把它设置成 0,当剩余内存 + 文件页小于页高阈值时,还是会发生 Swap。
为什么一般应用要关闭 swap?
对 k8s 来说。一个是性能问题,开启swap会严重影响性能(包括内存和I/O);另一个是管理问题,开启swap后通过cgroups设置的内存上限就会失效。
当 Swap 变高时,你可以用 sar、/proc/zoneinfo、/proc/pid/status 等方法,查看系统和进程的内存使用情况,进而找出 Swap 升高的根源和受影响的进程。
# 间隔 1 秒输出一组数据# -r 表示显示内存使用情况,-S 表示显示 Swap 使用情况$ sar -r -S 1
降低 swap 的使用提高性能
禁止 Swap,现在服务器的内存足够大,所以除非有必要,禁用 Swap 就可以了。随着云计算的普及,大部分云平台中的虚拟机都默认禁止 Swap。
如果实在需要用到 Swap,可以尝试降低 swappiness 的值,减少内存回收时 Swap 的使用倾向。
响应延迟敏感的应用,如果它们可能在开启 Swap 的服务器中运行,你还可以用库函数 mlock() 或者 mlockall() 锁定内存,阻止它们的内存换出。
内存性能指标
工具篇
根据指标找工具
根据工具找指标
如何迅速定位内存问题?
1. 先用 free 和 top,查看系统整体的内存使用情况。2. 再用 vmstat 和 pidstat,查看一段时间的趋势,从而判断出内存问题的类型。3. 最后进行详细分析,比如内存分配分析、缓存 / 缓冲区分析、具体进程的内存使用分析等。
内存优化:
保证应用程序的热点数据放到内存中,并尽量减少换页和交换
优化思路
1. 最好禁止 Swap。如果必须开启 Swap,降低 swappiness 的值,减少内存回收时 Swap 的使用倾向。2. 减少内存的动态分配。比如,可以使用内存池、大页(HugePage)等。3. 尽量使用缓存和缓冲区来访问数据。比如,可以使用堆栈明确声明内存空间,来存储需要缓存的数据;或者用 Redis 这类的外部缓存组件,优化数据的访问。4. 使用 cgroups 等方式限制进程的内存使用情况。这样,可以确保系统内存不会被异常进程耗尽。5. 通过 /proc/pid/oom_adj ,调整核心应用的 oom_score。这样,可以保证即使内存紧张,核心应用也不会被 OOM 杀死。
统计所有进程的物理内存使用量
# 使用 grep 查找 Pss 指标后,再用 awk 计算累加值[root@jikeshijian ~]# grep Pss /proc/[1-9]*/smaps | awk '{total+=$2}; END {printf \"%d kB\\
每个进程的 PSS ,是指把共享内存平分到各个进程后,再加上进程本身的非共享内存大小的和。就像文档中的这个例子,一个进程的非共享内存为 1000 页,它和另一个进程的共享进程也是 1000 页,那么它的 PSS=1000/2+1000=1500 页。这样,你就可以直接累加 PSS ,不用担心共享内存重复计算的问题了。
CPU 性能优化
CPU 性能优化实战
知识点
平均负载与CPU使用率的关系
* CPU 密集型进程,使用大量 CPU 会导致平均负载升高,此时这两者是一致的。* I/O 密集型进程,等待 I/O 也会导致平均负载升高,但 CPU 使用率不一定很高。* 大量等待 CPU 的进程调度也会导致平均负载升高,此时的 CPU 使用率也会比较高。
当发现负载高的时候,你可以使用 mpstat、pidstat等工具,辅助分析负载的来源
CPU 上下文切换
进程上下文切换
线程上下文切换
中断上下文切换
怎么查看系统的上下文切换情况
vmstat: 查看系统的总体的上下文切换情况
需要特别关注的四列内容cs(context switch) 表示每秒上下文切换的次数in(interrupt)表示每秒中断次数r(Running or Runnable)表示就绪队列的长度,也就是正在运行和等待CPU的进程数b(Blocked)表示处于不可中断睡眠状态的进程数
pidstat:查看每个进程的详细情况 pidstat -w 1
需要重点关注两列内容cswch ,表示每秒自愿上下文切换(voluntary context switches)的次数nvcswch ,表示每秒非自愿上下文切换(non voluntary context switches)的次数自愿上下文切换:进程无法获取所需资源(比如说, I/O、内存等系统资源不足)非自愿上下文切换:进程由于时间片已到等原因,被系统强制调度
/proc/interrupts: 查看中断类型的方式
# -d 参数表示高亮显示变化的区域[root@jikeshijian ~]# watch -d cat /proc/interrupts CPU0 CPU1...RES: 2450431 5279697 Rescheduling interrupts...
CPU 使用率总结
CPU 使用率是最直观和最常用的系统性能指标,更是我们在排查性能问题时,通常会关注的第一个指标。所以我们更要熟悉它的含义,尤其要弄清楚用户(%user)、Nice(%nice)、系统(%system) 、等待 I/O(%iowait) 、中断(%irq)以及软中断(%softirq)这几种不同 CPU 的使用率。比如说:* 用户 CPU 和 Nice CPU 高(us和ni高),说明用户态进程占用了较多的 CPU,所以应该着重排查进程的性能问题。* 系统 CPU 高(sy高),说明内核态占用了较多的 CPU,所以应该着重排查内核线程或者系统调用的性能问题。* I/O 等待 CPU 高(wa高),说明等待 I/O 的时间比较长,所以应该着重排查系统存储是不是出现了 I/O 问题。* 软中断和硬中断高(hi和si高),说明软中断或硬中断的处理程序占用了较多的 CPU,所以应该着重排查内核中的中断服务程序。碰到 CPU 使用率升高的问题,你可以借助 top、pidstat 等工具,确认引发 CPU 性能问题的来源;再使用 perf 等工具,排查出引起性能问题的具体函数。
不可中断状态和僵尸状态
不可中断状态:表示进程正在和硬件交互,为了保护进程数据和硬件的一致性,系统不允许其他进程或中断打断这个进程。进程长时间处于不可中断状态,通常表示系统有 I/O 性能问题。
僵尸进程: 表示进程已经退出,但它的父进程还没有回收子进程占用的资源。短暂的僵尸状态我们通过不必理会,但进程长时间处于僵尸状态,就应该注意了,可能有应用程序没有正常处理子进程的退出。
找到父进程解决 pstree
根据工具查指标
如何快速分析出系统CPU的性能瓶颈?
1、自愿上下文切换变多了,说明进程都在等待资源,有可能发生I/O问题2、非自愿上下文切换变多了,说明进程都在被强制调度,也就是都在争抢cpu,说明cpu的确成为瓶颈3、中断次数变多,说明cpu被中断程序占用,还需要通过查看/proc/interrupts文件来分析具体的中断类型
磁盘IO
每个文件的数据结构
索引节点(index node)
简称为 inode,用来记录文件的元数据,比如 inode 编号、文件大小、访问权限、修改日期、数据的位置等。索引节点和文件一一对应,它跟文件内容一样,都会被持久化存储到磁盘中。所以记住,索引节点同样占用磁盘空间。
目录项(directory entry)
简称为 dentry,用来记录文件的名字、索引节点指针以及与其他目录项的关联关系。多个关联的目录项,就构成了文件系统的目录结构。不过,不同于索引节点,目录项是由内核维护的一个内存数据结构,所以通常也被叫做目录项缓存。
索引节点是每个文件的唯一标志,而目录项维护的正是文件系统的树状结构。目录项和索引节点的关系是多对一,你可以简单理解为,一个文件可以有多个别名。
常见 I/O分类
根据是否利用标准库缓存
缓冲 I/O,是指利用标准库缓存来加速文件的访问,而标准库内部再通过系统调度访问文件。
非缓冲 I/O,是指直接通过系统调用来访问文件,不再经过标准库缓存。
注意,这里所说的“缓冲”,是指标准库内部实现的缓存。比方说,你可能见到过,很多程序遇到换行时才真正输出,而换行前的内容,其实就是被标准库暂时缓存了起来。无论缓冲 I/O 还是非缓冲 I/O,它们最终还是要经过系统调用来访问文件。而根据内容,我们知道,系统调用后,还会通过页缓存,来减少磁盘的 I/O 操作。
根据是否利用操作系统的页缓存
直接 I/O,是指跳过操作系统的页缓存,直接跟文件系统交互来访问文件。
非直接 I/O 正好相反,文件读写时,先要经过系统的页缓存,然后再由内核或额外的系统调用,真正写入磁盘。想要实现直接 I/O,需要你在系统调用中,指定 O_DIRECT 标志。如果没有设置过,默认的是非直接 I/O。
不过要注意,直接 I/O、非直接 I/O,本质上还是和文件系统交互。如果是在数据库等场景中,你还会看到,跳过文件系统读写磁盘的情况,也就是我们通常所说的裸 I/O。
根据应用程序是否阻塞自身运行
阻塞 I/O,是指应用程序执行 I/O 操作后,如果没有获得响应,就会阻塞当前线程,自然就不能执行其他任务。
非阻塞 I/O,是指应用程序执行 I/O 操作后,不会阻塞当前的线程,可以继续执行其他的任务,随后再通过轮询或者事件通知的形式,获取调用的结果。比方说,访问管道或者网络套接字时,设置 O_NONBLOCK 标志,就表示用非阻塞方式访问;而如果不做任何设置,默认的就是阻塞访问。
根据是否等待响应结果
同步 I/O,是指应用程序执行 I/O 操作后,要一直等到整个 I/O 完成后,才能获得 I/O 响应
异步 I/O,是指应用程序执行 I/O 操作后,不用等待完成和完成后的响应,而是继续执行就可以。等到这次 I/O 完成后,响应会用事件通知的方式,告诉应用程序。举个例子,在操作文件时,如果你设置了 O_SYNC 或者 O_DSYNC 标志,就代表同步 I/O。如果设置了 O_DSYNC,就要等文件数据写入磁盘后,才能返回;而 O_SYNC,则是在 O_DSYNC 基础上,要求文件元数据也要写入磁盘后,才能返回。再比如,在访问管道或者网络套接字时,设置了 O_ASYNC 选项后,相应的 I/O 就是异步 I/O。这样,内核会再通过 SIGIO 或者 SIGPOLL,来通知进程文件是否可读写。你可能发现了,这里的好多概念也经常出现在网络编程中。比如非阻塞 I/O,通常会跟 select/poll 配合,用在网络套接字的 I/O 中。
常用命令
容量
df
df -h /dev/sda1
df -i /dev/sda1
索引节点的容量,(也就是 Inode 个数)是在格式化磁盘时设定好的,一般由格式化工具自动生成。当你发现索引节点空间不足,但磁盘空间充足时,很可能就是过多小文件导致的。
文件系统缓存
/proc/slabinfo
这个界面中,dentry 行表示目录项缓存,inode_cache 行,表示 VFS 索引节点缓存,其余的则是各种文件系统的索引节点缓存。
slabtop 来找到占用内存最多的缓存类型
从这个结果你可以看到,在我的系统中,目录项和索引节点占用了最多的 Slab 缓存。不过它们占用的内存其实并不大,加起来也只有 23MB 左右。
磁盘性能指标
使用率
是指磁盘处理 I/O 的时间百分比。过高的使用率(比如超过 80%),通常意味着磁盘 I/O 存在性能瓶颈。
这里要注意的是,使用率只考虑有没有 I/O,而不考虑 I/O 的大小。换句话说,当使用率是 100% 的时候,磁盘依然有可能接受新的 I/O 请求。使用率是从时间角度衡量I/O,但是磁盘还可以支持并行写,所以即使使用率100%,有可能还可以接收新的I/O(不饱和)
饱和度
是指磁盘处理 I/O 的繁忙程度。过高的饱和度,意味着磁盘存在严重的性能瓶颈。当饱和度为 100% 时,磁盘无法接受新的 I/O 请求。
IOPS(Input/Output Per Second)
是指每秒的 I/O 请求数
吞吐量
是指每秒的 I/O 请求大小。
响应时间
是指 I/O 请求从发出到收到响应的间隔时间。
场景
不要孤立地去比较某一指标,而要结合读写比例、I/O 类型(随机还是连续)以及 I/O 的大小,综合来分析。
举个例子,在数据库、大量小文件等这类随机读写比较多的场景中,IOPS 更能反映系统的整体性能;而在多媒体等顺序读写较多的场景中,吞吐量才更能反映系统的整体性能。
图示
性能测试工具
fio
测试磁盘的 IOPS、吞吐量以及响应时间等核心指标
需要你测试出,不同 I/O 大小(一般是 512B 至 1MB 中间的若干值)分别在随机读、顺序读、随机写、顺序写等各种场景下的性能情况。
观测方法
磁盘 I/O 观测
iostat 是最常用的磁盘 I/O 性能观测工具,它提供了每个磁盘的使用率、IOPS、吞吐量等各种常见的性能指标,当然,这些指标实际上来自 /proc/diskstats。
指标解读
图解
这些指标中,你要注意:%util ,就是我们前面提到的磁盘 I/O 使用率;r/s+ w/s ,就是 IOPS;rkB/s+wkB/s ,就是吞吐量;r_await+w_await ,就是响应时间。在观测指标时,也别忘了结合请求的大小( rareq-sz 和 wareq-sz)一起分析。你可能注意到,从 iostat 并不能直接得到磁盘饱和度。事实上,饱和度通常也没有其他简单的观测方法,不过,你可以把观测到的,平均请求队列长度或者读写请求完成的等待时间,跟基准测试的结果(比如通过 fio)进行对比,综合评估磁盘的饱和情况。
进程 I/O 观测
pidstat
从 pidstat 的输出你能看到,它可以实时查看每个进程的 I/O 情况,包括下面这些内容。用户 ID(UID)和进程 ID(PID) 。每秒读取的数据大小(kB_rd/s) ,单位是 KB。每秒发出的写请求数据大小(kB_wr/s) ,单位是 KB。每秒取消的写请求数据大小(kB_ccwr/s) ,单位是 KB。块 I/O 延迟(iodelay),包括等待同步块 I/O 和换入块 I/O 结束的时间,单位是时钟周期。除了可以用 pidstat 实时查看,根据 I/O 大小对进程排序,也是性能分析中一个常用的方法。这一点,我推荐另一个工具, iotop。它是一个类似于 top 的工具,你可以按照 I/O 大小对进程排序,然后找到 I/O 较大的那些进程。
iotop
从这个输出,你可以看到,前两行分别表示,进程的磁盘读写大小总数和磁盘真实的读写大小总数。因为缓存、缓冲区、I/O 合并等因素的影响,它们可能并不相等。剩下的部分,则是从各个角度来分别表示进程的 I/O 情况,包括线程 ID、I/O 优先级、每秒读磁盘的大小、每秒写磁盘的大小、换入和等待 I/O 的时钟百分比等。
1.“狂打日志”的场景时,你可以用 iostat、strace、lsof 等工具来定位狂打日志的进程,找出相应的日志文件,再通过应用程序的接口,调整日志级别来解决问题。
bcc工具
filetop:可以查看进程操作的文件名称等信息
对系统调用 write() 追踪结果
opensnoop:甚至连操作的文件路径也有.
对系统调用 open() 的追踪结果
strace默认不跟踪子线程的系统调用
在strace -p PID后加上-f,多进程和多线程都可以跟踪
pstree -hp <PID>
瓶颈是由于大量读写磁盘,改进方案将数据写到内存中
nsenter 工具,可以进入容器命名空间
nsenter
# 由于这两个容器共享同一个网络命名空间,所以我们只需要进入 app 的网络命名空间即可$ PID=$(docker inspect --format {{.State.Pid}} app)# -i 表示显示网络套接字信息$ nsenter --target $PID --net -- lsof -iCOMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAMEredis-ser 9085 systemd-network 6u IPv4 15447972 0t0 TCP localhost:6379 (LISTEN)redis-ser 9085 systemd-network 8u IPv4 15448709 0t0 TCP localhost:6379->localhost:32996 (ESTABLISHED)python 9181 root 3u IPv4 15448677 0t0 TCP *:http (LISTEN)python 9181 root 5u IPv4 15449632 0t0 TCP localhost:32996->localhost:6379 (ESTABLISHED)
如何快速定位 IO 问题
1. 先用 iostat 发现磁盘 I/O 性能瓶颈;2. 再借助 pidstat ,定位出导致瓶颈的进程;3. 随后分析进程的 I/O 行为;4. 最后,结合应用程序的原理,分析这些 I/O 的来源。
性能优化
1.fio 性能测试
# 随机读fio -name=randread -direct=1 -iodepth=64 -rw=randread -ioengine=libaio -bs=4k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=/dev/sdb# 随机写fio -name=randwrite -direct=1 -iodepth=64 -rw=randwrite -ioengine=libaio -bs=4k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=/dev/sdb# 顺序读fio -name=read -direct=1 -iodepth=64 -rw=read -ioengine=libaio -bs=4k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=/dev/sdb# 顺序写fio -name=write -direct=1 -iodepth=64 -rw=write -ioengine=libaio -bs=4k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=/dev/sdb
direct,表示是否跳过系统缓存。上面示例中,我设置的 1 ,就表示跳过系统缓存。iodepth,表示使用异步 I/O(asynchronous I/O,简称 AIO)时,同时发出的 I/O 请求上限。在上面的示例中,我设置的是 64。rw,表示 I/O 模式。我的示例中, read/write 分别表示顺序读 / 写,而 randread/randwrite 则分别表示随机读 / 写。ioengine,表示 I/O 引擎,它支持同步(sync)、异步(libaio)、内存映射(mmap)、网络(net)等各种 I/O 引擎。上面示例中,我设置的 libaio 表示使用异步 I/O。bs,表示 I/O 的大小。示例中,我设置成了 4K(这也是默认值)。filename,表示文件路径,当然,它可以是磁盘路径(测试磁盘性能),也可以是文件路径(测试文件系统性能)。示例中,我把它设置成了磁盘 /dev/sdb。不过注意,用磁盘路径测试写,会破坏这个磁盘中的文件系统,所以在使用前,你一定要事先做好数据备份。
测试报告
需要我们重点关注的是
slat
是指从 I/O 提交到实际执行 I/O 的时长(Submission latency)
clat
是指从 I/O 提交到 I/O 完成的时长(Completion latency)
lat
指的是从 fio 创建 I/O 到 I/O 完成的总时长
bw
它代表吞吐量。在示例中,你可以看到,平均吞吐量大约是 16 MB(17005 KiB/1024)
iops
其实就是每秒 I/O 的次数,上面示例中的平均 IOPS 为 4250
这里需要注意的是,对同步 I/O 来说,由于 I/O 提交和 I/O 完成是一个动作,所以 slat 实际上就是 I/O 完成的时间,而 clat 是 0。而从示例可以看到,使用异步 I/O(libaio)时,lat 近似等于 slat + clat 之和。
事实上,slat、clat、lat 都是指 I/O 延迟(latency)
应用程序优化
第一,可以用追加写代替随机写,减少寻址开销,加快 I/O 写的速度。
第二,可以借助缓存 I/O ,充分利用系统缓存,降低实际 I/O 的次数。
第三,可以在应用程序内部构建自己的缓存,或者用 Redis 这类外部缓存系统。这样,一方面,能在应用程序内部,控制缓存的数据和生命周期;另一方面,也能降低其他应用程序使用缓存对自身的影响。
第四,在需要频繁读写同一块磁盘空间时,可以用 mmap 代替 read/write,减少内存的拷贝次数。
第五,在需要同步写的场景中,尽量将写请求合并,而不是让每个请求都同步写入磁盘,即可以用 fsync() 取代 O_SYNC。
第六,在多个应用程序共享相同磁盘时,为了保证 I/O 不被某个应用完全占用,推荐你使用 cgroups 的 I/O 子系统,来限制进程 / 进程组的 IOPS 以及吞吐量。
最后,在使用 CFQ 调度器时,可以用 ionice 来调整进程的 I/O 调度优先级,特别是提高核心应用的 I/O 优先级。ionice 支持三个优先级类:Idle、Best-effort 和 Realtime。其中, Best-effort 和 Realtime 还分别支持 0-7 的级别,数值越小,则表示优先级别越高。
文件系统优化
第一,你可以根据实际负载场景的不同,选择最适合的文件系统。比如 Ubuntu 默认使用 ext4 文件系统,而 CentOS 7 默认使用 xfs 文件系统。相比于 ext4 ,xfs 支持更大的磁盘分区和更大的文件数量,如 xfs 支持大于 16TB 的磁盘。但是 xfs 文件系统的缺点在于无法收缩,而 ext4 则可以。
第二,在选好文件系统后,还可以进一步优化文件系统的配置选项,包括文件系统的特性(如 ext_attr、dir_index)、日志模式(如 journal、ordered、writeback)、挂载选项(如 noatime)等等。比如, 使用 tune2fs 这个工具,可以调整文件系统的特性(tune2fs 也常用来查看文件系统超级块的内容)。 而通过 /etc/fstab ,或者 mount 命令行参数,我们可以调整文件系统的日志模式和挂载选项等。
第三,可以优化文件系统的缓存。比如,你可以优化 pdflush 脏页的刷新频率(比如设置 dirty_expire_centisecs 和 dirty_writeback_centisecs)以及脏页的限额(比如调整 dirty_background_ratio 和 dirty_ratio 等)。再如,你还可以优化内核回收目录项缓存和索引节点缓存的倾向,即调整 vfs_cache_pressure(/proc/sys/vm/vfs_cache_pressure,默认值 100),数值越大,就表示越容易回收。
最后,在不需要持久化时,你还可以用内存文件系统 tmpfs,以获得更好的 I/O 性能 。tmpfs 把数据直接保存在内存中,而不是磁盘中。比如 /dev/shm/ ,就是大多数 Linux 默认配置的一个内存文件系统,它的大小默认为总内存的一半。
磁盘优化
第一,最简单有效的优化方法,就是换用性能更好的磁盘,比如用 SSD 替代 HDD。
第二,我们可以使用 RAID ,把多块磁盘组合成一个逻辑磁盘,构成冗余独立磁盘阵列。这样做既可以提高数据的可靠性,又可以提升数据的访问性能。
第三,针对磁盘和应用程序 I/O 模式的特征,我们可以选择最适合的 I/O 调度算法。比方说,SSD 和虚拟机中的磁盘,通常用的是 noop 调度算法。而数据库应用,我更推荐使用 deadline 算法。
第四,我们可以对应用程序的数据,进行磁盘级别的隔离。比如,我们可以为日志、数据库等 I/O 压力比较重的应用,配置单独的磁盘。
第五,在顺序读比较多的场景中,我们可以增大磁盘的预读数据,比如,你可以通过下面两种方法,调整 /dev/sdb 的预读大小。调整内核选项 /sys/block/sdb/queue/read_ahead_kb,默认大小是 128 KB,单位为 KB。使用 blockdev 工具设置,比如 blockdev --setra 8192 /dev/sdb,注意这里的单位是 512B(0.5KB),所以它的数值总是 read_ahead_kb 的两倍。
第六,我们可以优化内核块设备 I/O 的选项。比如,可以调整磁盘队列的长度 /sys/block/sdb/queue/nr_requests,适当增大队列长度,可以提升磁盘的吞吐量(当然也会导致 I/O 延迟增大)。
最后,要注意,磁盘本身出现硬件错误,也会导致 I/O 性能急剧下降,所以发现磁盘性能急剧下降时,你还需要确认,磁盘本身是不是出现了硬件错误。比如,你可以查看 dmesg 中是否有硬件 I/O 故障的日志。 还可以使用 badblocks、smartctl 等工具,检测磁盘的硬件问题,或用 e2fsck 等来检测文件系统的错误。如果发现问题,你可以使用 fsck 等工具来修复。
性能指标
带宽
表示链路的最大传输速率,单位通常为 b/s (比特 / 秒)。
表示单位时间内成功传输的数据量,单位通常为 b/s(比特 / 秒)或者 B/s(字节 / 秒)。吞吐量受带宽限制,而吞吐量 / 带宽,也就是该网络的使用率
延时
表示从网络请求发出后,一直到收到远端响应,所需要的时间延迟。在不同场景中,这一指标可能会有不同含义。比如,它可以表示,建立连接需要的时间(比如 TCP 握手延时),或一个数据包往返所需的时间(比如 RTT)
PPS(Packet Per Second)
是 Packet Per Second(包 / 秒)的缩写,表示以网络包为单位的传输速率。PPS 通常用来评估网络的转发能力,比如硬件交换机,通常可以达到线性转发(即 PPS 可以达到或者接近理论最大值)。而基于 Linux 服务器的转发,则容易受网络包大小的影响。
网络的可用性(网络能否正常通信)
并发连接数(TCP 连接数量)
丢包率(丢包百分比)
重传率(重新传输的网络包比例)
网络配置
ipconfig/ip: ifconfig 和 ip 命令输出的指标基本相同,只是显示格式略微不同。比如,它们都包括了网络接口的状态标志、MTU 大小、IP、子网、MAC 地址以及网络包收发的统计信息。
特别关注
第一,网络接口的状态标志。ifconfig 输出中的 RUNNING ,或 ip 输出中的 LOWER_UP ,都表示物理网络是连通的,即网卡已经连接到了交换机或者路由器中。如果你看不到它们,通常表示网线被拔掉了。
第二,MTU 的大小。MTU 默认大小是 1500,根据网络架构的不同(比如是否使用了 VXLAN 等叠加网络),你可能需要调大或者调小 MTU 的数值。
第三,网络接口的 IP 地址、子网以及 MAC 地址。这些都是保障网络功能正常工作所必需的,你需要确保配置正确。
第四,网络收发的字节数、包数、错误数以及丢包情况,特别是 TX 和 RX 部分的 errors、dropped、overruns、carrier 以及 collisions 等指标不为 0 时,通常表示出现了网络 I/O 问题。其中:
errors 表示发生错误的数据包数,比如校验错误、帧同步错误等;dropped 表示丢弃的数据包数,即数据包已经收到了 Ring Buffer,但因为内存不足等原因丢包;overruns 表示超限数据包数,即网络 I/O 速度过快,导致 Ring Buffer 中的数据包来不及处理(队列满)而导致的丢包;carrier 表示发生 carrirer 错误的数据包数,比如双工模式不匹配、物理电缆出现问题等;collisions 表示碰撞数据包数。
套接字信息
netstat 或者 ss ,来查看套接字、网络栈、网络接口以及路由表的信息。
更推荐,使用 ss 来查询网络的连接信息,因为它比 netstat 提供了更好的性能(速度更快)
netstat是遍历/proc下面每个PID目录,ss直接读/proc/net下面的统计信息。
ss快的秘诀在于它利用到了TCP协议栈中tcp_diag。tcp_diag是一个用于分析统计的模块,可以获得Linux内核中第一手的信息,这就确保了ss的快捷高效。当然,如果你的系统中没有tcp_diag,ss也可以正常运行,只是效率会变得稍慢(但仍然比 netstat要快)。
接收队列(Recv-Q)和发送队列(Send-Q)需要你特别关注,它们通常应该是 0。当你发现它们不是 0 时,说明有网络包的堆积发生。当然还要注意,在不同套接字状态下,它们的含义不同
套接字处于连接状态(Established)时
Recv-Q 表示套接字缓冲还没有被应用程序取走的字节数(即接收队列长度)
Send-Q 表示还没有被远端主机确认的字节数(即发送队列长度)
当套接字处于监听状态(Listening)时,
Recv-Q 表示 syn backlog 的当前值。
Send-Q 表示最大的 syn backlog 值
而 syn backlog 是 TCP 协议栈中的半连接队列长度,相应的也有一个全连接队列(accept queue),它们都是维护 TCP 状态的重要机制。
顾名思义,所谓半连接,就是还没有完成 TCP 三次握手的连接,连接只进行了一半,而服务器收到了客户端的 SYN 包后,就会把这个连接放到半连接队列中,然后再向客户端发送 SYN+ACK 包。
而全连接,则是指服务器收到了客户端的 ACK,完成了 TCP 三次握手,然后就会把这个连接挪到全连接队列中。这些全连接中的套接字,还需要再被 accept() 系统调用取走,这样,服务器就可以开始真正处理客户端的请求了。
协议栈统计信息
使用 netstat 或 ss ,也可以查看协议栈的信息
这些协议栈的统计信息都很直观。ss 只显示已经连接、关闭、孤儿套接字等简要统计,而 netstat 则提供的是更详细的网络协议栈信息。比如,上面 netstat 的输出示例,就展示了 TCP 协议的主动连接、被动连接、失败重试、发送和接收的分段数量等各种信息。
网络吞吐和 PPS
给 sar 增加 -n 参数就可以查看网络的统计信息,比如网络接口(DEV)、网络接口错误(EDEV)、TCP、UDP、ICMP 等等。执行下面的命令,你就可以得到网络接口统计信息:
其中,Bandwidth 可以用 ethtool 来查询,它的单位通常是 Gb/s 或者 Mb/s,不过注意这里小写字母 b ,表示比特而不是字节。我们通常提到的千兆网卡、万兆网卡等,单位也都是比特。如下你可以看到,我的 eth0 网卡就是一个千兆网卡:
ethtool eth0 | grep Speed\tSpeed: 1000Mb/s
连通性和延时
通常使用 ping ,来测试远程主机的连通性和延时,而这基于 ICMP 协议
ping 的输出,可以分为两部分。第一部分,是每个 ICMP 请求的信息,包括 ICMP 序列号(icmp_seq)、TTL(生存时间,或者跳数)以及往返延时。第二部分,则是三次 ICMP 请求的汇总。比如上面的示例显示,发送了 3 个网络包,并且接收到 3 个响应,没有丢包发生,这说明测试主机到 114.114.114.114 是连通的;平均往返延时(RTT)是 244ms,也就是从发送 ICMP 开始,到接收到 114.114.114.114 回复的确认,总共经历 244ms。
查看ip_conntrack实时连接状态信息,安装iptstate工具
C10K 和 C1000K
C10K 和 C1000K 的首字母 C 是 Client 的缩写。C10K 就是单机同时处理 1 万个请求(并发连接 1 万)的问题,而 C1000K 也就是单机支持处理 100 万个请求(并发连接 100 万)的问题。
C10K
两种 I/O 事件通知的方式:水平触发和边缘触发,它们常用在套接字接口的文件描述符中。
水平触发(LT):只要文件描述符可以非阻塞地执行 I/O ,就会触发通知。也就是说,应用程序可以随时检查文件描述符的状态,然后再根据状态,进行 I/O 操作。
边缘触发(ET):只有在文件描述符的状态发生改变(也就是 I/O 请求达到)时,才发送一次通知。这时候,应用程序需要尽可能多地执行 I/O,直到无法继续读写,才可以停止。如果 I/O 没执行完,或者因为某种原因没来得及处理,那么这次通知也就丢失了。
实现I/O 多路复用
第一种,使用非阻塞 I/O 和水平触发通知,比如使用 select 或者 poll。
第二种,使用非阻塞 I/O 和边缘触发通知,比如 epoll。
epoll 使用红黑树,在内核中管理文件描述符的集合,这样,就不需要应用程序在每次操作时都传入、传出这个集合。
epoll 使用事件驱动的机制,只关注有 I/O 事件发生的文件描述符,不需要轮询扫描整个集合。
第三种,使用异步 I/O(Asynchronous I/O,简称为 AIO)
工作模型优化
第一种,主进程 + 多个 worker 子进程,这也是最常用的一种模型
一种通用模型
主进程执行 bind() + listen() 后,创建多个子进程;
然后,在每个子进程中,都通过 accept() 或 epoll_wait() ,来处理相同的套接字。
最常用的反向代理服务器 Nginx 就是这么工作的。它也是由主进程和多个 worker 进程组成。主进程主要用来初始化套接字,并管理子进程的生命周期;而 worker 进程,则负责实际的请求处理。我画了一张图来表示这个关系。
Nginx 在每个 worker 进程中,都增加一个了全局锁(accept_mutex)。这些 worker 进程需要首先竞争到锁,只有竞争到锁的进程,才会加入到 epoll 中,这样就确保只有一个 worker 子进程被唤醒。
第二种,监听到相同端口的多进程模型
C1000K
基于 I/O 多路复用和请求处理的优化,C10K 问题很容易就可以解决。很快,原来的 C10K 已经不能满足需求,所以又有了 C100K 和 C1000K,也就是并发从原来的 1 万增加到 10 万、乃至 100 万。从 1 万到 10 万,其实还是基于 C10K 的这些理论,epoll 配合线程池,再加上 CPU、内存和网络接口的性能和容量提升。大部分情况下,C100K 很自然就可以达到。
首先从物理资源使用上来说,100 万个请求需要大量的系统资源。比如,
假设每个请求需要 16KB 内存的话,那么总共就需要大约 15 GB 内存。
而从带宽上来说,假设只有 20% 活跃连接,即使每个连接只需要 1KB/s 的吞吐量,总共也需要 1.6 Gb/s 的吞吐量。千兆网卡显然满足不了这么大的吞吐量,所以还需要配置万兆网卡,或者基于多网卡 Bonding 承载更大的吞吐量。
其次,从软件资源上来说,大量的连接也会占用大量的软件资源,比如文件描述符的数量、连接状态的跟踪(CONNTRACK)、网络协议栈的缓存大小(比如套接字读写缓存、TCP 读写缓存)等等。
最后,大量请求带来的中断处理,也会带来非常高的处理成本。这样,就需要多队列网卡、中断负载均衡、CPU 绑定、RPS/RFS(软中断负载均衡到多个 CPU 核上),以及将网络包的处理卸载(Offload)到网络设备(如 TSO/GSO、LRO/GRO、VXLAN OFFLOAD)等各种硬件和软件的优化。
C1000K 的解决方法,本质上还是构建在 epoll 的非阻塞 I/O 模型上。只不过,除了 I/O 模型之外,还需要从应用程序到 Linux 内核、再到 CPU、内存和网络等各个层次的深度优化,特别是需要借助硬件,来卸载那些原来通过软件处理的大量功能。
C10M
Linux 内核协议栈做了太多太繁重的工作。从网卡中断带来的硬中断处理程序开始,到软中断中的各层网络协议处理,最后再到应用程序,这个路径实在是太长了,就会导致网络包的处理优化,到了一定程度后,就无法更进一步了。要解决这个问题,最重要就是跳过内核协议栈的冗长路径,把网络包直接送到要处理的应用程序那里去。这里有两种常见的机制,DPDK 和 XDP。
第一种机制,DPDK,是用户态网络的标准。它跳过内核协议栈,直接由用户态进程通过轮询的方式,来处理网络接收。
在 PPS 非常高的场景中,查询时间比实际工作时间少了很多,绝大部分时间都在处理网络包;而跳过内核协议栈后,就省去了繁杂的硬中断、软中断再到 Linux 网络协议栈逐层处理的过程,应用程序可以针对应用的实际场景,有针对性地优化网络包的处理逻辑,而不需要关注所有的细节。此外,DPDK 还通过大页、CPU 绑定、内存对齐、流水线并发等多种机制,优化网络包的处理效率。
第二种机制,XDP(eXpress Data Path),则是 Linux 内核提供的一种高性能网络数据路径。它允许网络包,在进入内核协议栈之前,就进行处理,也可以带来更高的性能。XDP 底层跟我们之前用到的 bcc-tools 一样,都是基于 Linux 内核的 eBPF 机制实现的。
你可以看到,XDP 对内核的要求比较高,需要的是 Linux 4.8 以上版本,并且它也不提供缓存队列。基于 XDP 的应用程序通常是专用的网络应用,常见的有 IDS(入侵检测系统)、DDoS 防御、cilium容器网络插件等。
性能指标评估
TCP/UDP 性能
iperf 或者 netperf
server 端
# -s 表示启动服务端,-i 表示汇报间隔,-p 表示监听端口$ iperf3 -s -i 1 -p 10000
client
# -c 表示启动客户端,192.168.0.30 为目标服务器的 IP# -b 表示目标带宽 (单位是 bits/s)# -t 表示测试时间# -P 表示并发数,-p 表示目标服务器监听端口$ iperf3 -c 192.168.0.30 -b 1G -t 15 -P 2 -p 10000
稍等一会儿(15 秒)测试结束后,回到目标服务器server,查看 iperf 的报告:
[ ID] Interval Transfer Bandwidth...[SUM] 0.00-15.04 sec 0.00 Bytes 0.00 bits/sec sender[SUM] 0.00-15.04 sec 1.51 GBytes 860 Mbits/sec receiver
最后的 SUM 行就是测试的汇总结果,包括测试时间、数据传输量以及带宽等。按照发送和接收,这一部分又分为了 sender 和 receiver 两行。从测试结果你可以看到,这台机器 TCP 接收的带宽(吞吐量)为 860 Mb/s, 跟目标的 1Gb/s 相比,还是有些差距的。
HTTP 性能
ab、webbench
ab 的测试结果分为三个部分,分别是请求汇总、连接时间汇总还有请求延迟汇总。
应用负载性能
为了得到应用程序的实际性能,就要求性能工具本身可以模拟用户的请求负载,而 iperf、ab 这类工具就无能为力了。幸运的是,我们还可以用 wrk、TCPCopy、Jmeter 或者 LoadRunner 等实现这个目标。
像 Jmeter 或者 LoadRunner(商业产品),则针对复杂场景提供了脚本录制、回放、GUI 等更丰富的功能,使用起来也更加方便。
tcpdump 和 Wireshark
tcpdump -nn udp port 53 or host 35.190.27.188
-nn ,表示不解析抓包中的域名(即不反向解析)、协议以及端口号。udp port 53 ,表示只显示 UDP 协议的端口号(包括源端口和目的端口)为 53 的包。host 35.190.27.188 ,表示只显示 IP 地址(包括源地址和目的地址)为 35.190.27.188 的包。这两个过滤条件中间的“ or ”,表示或的关系,也就是说,只要满足上面两个条件中的任一个,就可以展示出来。
tcpdump选项类
tcpdump过滤表达式
tcpdump 的输出格式
时间戳 协议 源地址. 源端口 > 目的地址. 目的端口 网络包详细信息
wireshark
wireshark讲解
通过 tcpdump 生成文件,然后用 wireshark 打开
tcpdump -nn udp port 53 or host 35.190.27.188 -w ping.pcap
再用 Wireshark 打开 ping.pcap
三次握手四次挥手
网络性能优化
先要获得网络基准测试报告,然后通过相关性能工具,定位出网络性能瓶颈。再接下来的优化工作,就是水到渠成的事情了
Linux 系统的网络协议栈和网络收发流程
优化角度
应用程序
从网络 I/O 的角度来说
第一种是最常用的 I/O 多路复用技术 epoll,主要用来取代 select 和 poll。这其实是解决 C10K 问题的关键,也是目前很多网络应用默认使用的机制。
第二种是使用异步 I/O(Asynchronous I/O,AIO)。AIO 允许应用程序同时发起很多 I/O 操作,而不用等待这些操作完成。等到 I/O 完成后,系统会用事件通知的方式,告诉应用程序结果。不过,AIO 的使用比较复杂,你需要小心处理很多边缘情况。
从进程的工作模型来说
第一种,主进程 + 多个 worker 子进程。其中,主进程负责管理网络连接,而子进程负责实际的业务处理。这也是最常用的一种模型。
第二种,监听到相同端口的多进程模型。在这种模型下,所有进程都会监听相同接口,并且开启 SO_REUSEPORT 选项,由内核负责,把请求负载均衡到这些监听进程中去。
应用层的网络协议优化
使用长连接取代短连接,可以显著降低 TCP 建立连接的成本。在每秒请求次数较多时,这样做的效果非常明显。
使用内存等方式,来缓存不常变化的数据,可以降低网络 I/O 次数,同时加快应用程序的响应速度。
使用 Protocol Buffer 等序列化的方式,压缩网络 I/O 的数据量,可以提高应用程序的吞吐。
使用 DNS 缓存、预取、HTTPDNS 等方式,减少 DNS 解析的延迟,也可以提升网络 I/O 的整体速度。
套接字
套接字可以屏蔽掉 Linux 内核中不同协议的差异,为应用程序提供统一的访问接口。每个套接字,都有一个读写缓冲区。
读缓冲区,缓存了远端发过来的数据。如果读缓冲区已满,就不能再接收新的数据。
写缓冲区,缓存了要发出去的数据。如果写缓冲区已满,应用程序的写操作就会被阻塞。
为了提高网络的吞吐量,你通常需要调整这些缓冲区的大小
增大每个套接字的缓冲区大小 net.core.optmem_max;
增大套接字接收缓冲区大小 net.core.rmem_max 和发送缓冲区大小 net.core.wmem_max;
增大 TCP 接收缓冲区大小 net.ipv4.tcp_rmem 和发送缓冲区大小 net.ipv4.tcp_wmem。
套接字的内核选项列表
注意:
tcp_rmem 和 tcp_wmem 的三个数值分别是 min,default,max,系统会根据这些设置,自动调整 TCP 接收 / 发送缓冲区的大小。
udp_mem 的三个数值分别是 min,pressure,max,系统会根据这些设置,自动调整 UDP 发送缓冲区的大小。
当然,表格中的数值只提供参考价值,具体应该设置多少,还需要你根据实际的网络状况来确定。比如,发送缓冲区大小,理想数值是吞吐量 * 延迟,这样才可以达到最大网络利用率。
套接字接口还提供了一些配置选项,用来修改网络连接的行为:
为 TCP 连接设置 TCP_NODELAY 后,就可以禁用 Nagle 算法;
为 TCP 连接开启 TCP_CORK 后,可以让小包聚合成大包后再发送(注意会阻塞小包的发送);
使用 SO_SNDBUF 和 SO_RCVBUF ,可以分别调整套接字发送缓冲区和接收缓冲区的大小。
传输层
TCP 优化
TCP 提供了面向连接的可靠传输服务。要优化 TCP,我们首先要掌握 TCP 协议的基本原理,比如流量控制、慢启动、拥塞避免、延迟确认以及状态流图等
分情况说明:
第一类,在请求数比较大的场景下,你可能会看到大量处于 TIME_WAIT 状态的连接,它们会占用大量内存和端口资源。这时,我们可以优化与 TIME_WAIT 状态相关的内核选项,比如采取下面几种措施。
增大处于 TIME_WAIT 状态的连接数量 net.ipv4.tcp_max_tw_buckets ,并增大连接跟踪表的大小 net.netfilter.nf_conntrack_max。
减小 net.ipv4.tcp_fin_timeout 和 net.netfilter.nf_conntrack_tcp_timeout_time_wait ,让系统尽快释放它们所占用的资源。
开启端口复用 net.ipv4.tcp_tw_reuse。这样,被 TIME_WAIT 状态占用的端口,还能用到新建的连接中。
增大本地端口的范围 net.ipv4.ip_local_port_range 。这样就可以支持更多连接,提高整体的并发能力。
增加最大文件描述符的数量。你可以使用 fs.nr_open 和 fs.file-max ,分别增大进程和系统的最大文件描述符数;或在应用程序的 systemd 配置文件中,配置 LimitNOFILE ,设置应用程序的最大文件描述符数。
第二类,为了缓解 SYN FLOOD 等,利用 TCP 协议特点进行攻击而引发的性能问题,你可以考虑优化与 SYN 状态相关的内核选项,比如采取下面几种措施。
增大 TCP 半连接的最大数量 net.ipv4.tcp_max_syn_backlog ,或者开启 TCP SYN Cookies net.ipv4.tcp_syncookies ,来绕开半连接数量限制的问题(注意,这两个选项不可同时使用)。
减少 SYN_RECV 状态的连接重传 SYN+ACK 包的次数 net.ipv4.tcp_synack_retries。
第三类,在长连接的场景中,通常使用 Keepalive 来检测 TCP 连接的状态,以便对端连接断开后,可以自动回收。但是,系统默认的 Keepalive 探测间隔和重试次数,一般都无法满足应用程序的性能要求。所以,这时候你需要优化与 Keepalive 相关的内核选项,比如:
缩短最后一次数据包到 Keepalive 探测包的间隔时间 net.ipv4.tcp_keepalive_time;
缩短发送 Keepalive 探测包的间隔时间 net.ipv4.tcp_keepalive_intvl;
减少 Keepalive 探测失败后,一直到通知应用程序前的重试次数 net.ipv4.tcp_keepalive_probes。
TCP优化表格
增大 TCP 半连接的最大数量 net.ipv4.tcp_max_syn_backlog ,或者开启 TCP SYN Cookies net.ipv4.tcp_syncookies ,来绕开半连接数量限制的问题(注意,这两个选项不可同时使用)
注意:如果同时使用不同优化方法,可能会产生冲突。
比如,就像网络请求延迟的案例中我们曾经分析过的,服务器端开启 Nagle 算法,而客户端开启延迟确认机制,就很容易导致网络延迟增大。
另外,在使用 NAT 的服务器上,如果开启 net.ipv4.tcp_tw_recycle ,就很容易导致各种连接失败。实际上,由于坑太多,这个选项在内核的 4.1 版本中已经删除了。
UDP优化
UDP 提供了面向数据报的网络协议,它不需要网络连接,也不提供可靠性保障。所以,UDP 优化,相对于 TCP 来说,要简单得多。这里我也总结了常见的几种优化方案。
优化方案
增大套接字缓冲区大小以及 UDP 缓冲区范围;
增大本地端口号的范围;
根据 MTU 大小,调整 UDP 数据包的大小,减少或者避免分片的发生
网络层
网络层,负责网络包的封装、寻址和路由,包括 IP、ICMP 等常见协议。在网络层,最主要的优化,其实就是对路由、 IP 分片以及 ICMP 等进行调优。
优化方案:
第一种,从路由和转发的角度出发,你可以调整下面的内核选项。
在需要转发的服务器中,比如用作 NAT 网关的服务器或者使用 Docker 容器时,开启 IP 转发,即设置 net.ipv4.ip_forward = 1。
调整数据包的生存周期 TTL,比如设置 net.ipv4.ip_default_ttl = 64。注意,增大该值会降低系统性能。
开启数据包的反向地址校验,比如设置 net.ipv4.conf.eth0.rp_filter = 1。这样可以防止 IP 欺骗,并减少伪造 IP 带来的 DDoS 问题。
第二种,从分片的角度出发,最主要的是调整 MTU(Maximum Transmission Unit)的大小。
通常,MTU 的大小应该根据以太网的标准来设置。以太网标准规定,一个网络帧最大为 1518B,那么去掉以太网头部的 18B 后,剩余的 1500 就是以太网 MTU 的大小。
在使用 VXLAN、GRE 等叠加网络技术时,要注意,网络叠加会使原来的网络包变大,导致 MTU 也需要调整。
比如,就以 VXLAN 为例,它在原来报文的基础上,增加了 14B 的以太网头部、 8B 的 VXLAN 头部、8B 的 UDP 头部以及 20B 的 IP 头部。换句话说,每个包比原来增大了 50B。
所以,我们就需要把交换机、路由器等的 MTU,增大到 1550, 或者把 VXLAN 封包前(比如虚拟化环境中的虚拟网卡)的 MTU 减小为 1450。
另外,现在很多网络设备都支持巨帧,如果是这种环境,你还可以把 MTU 调大为 9000,以提高网络吞吐量。
第三种,从 ICMP 的角度出发,为了避免 ICMP 主机探测、ICMP Flood 等各种网络问题,你可以通过内核选项,来限制 ICMP 的行为。
比如,你可以禁止 ICMP 协议,即设置 net.ipv4.icmp_echo_ignore_all = 1。这样,外部主机就无法通过 ICMP 来探测主机。
还可以禁止广播 ICMP,即设置 net.ipv4.icmp_echo_ignore_broadcasts = 1。
链路层
链路层负责网络包在物理网络中的传输,比如 MAC 寻址、错误侦测以及通过网卡传输网络帧等。自然,链路层的优化,也是围绕这些基本功能进行的。接下来,我们从不同的几个方面分别来看。
分情况说明
由于网卡收包后调用的中断处理程序(特别是软中断),需要消耗大量的 CPU。所以,将这些中断处理程序调度到不同的 CPU 上执行,就可以显著提高网络吞吐量。这通常可以采用下面两种方法。
比如,你可以为网卡硬中断配置 CPU 亲和性(smp_affinity),或者开启 irqbalance 服务。
再如,你可以开启 RPS(Receive Packet Steering)和 RFS(Receive Flow Steering),将应用程序和软中断的处理,调度到相同 CPU 上,这样就可以增加 CPU 缓存命中率,减少网络延迟。
另外,现在的网卡都有很丰富的功能,原来在内核中通过软件处理的功能,可以卸载到网卡中,通过硬件来执行。
TSO(TCP Segmentation Offload)和 UFO(UDP Fragmentation Offload):在 TCP/UDP 协议中直接发送大包;而 TCP 包的分段(按照 MSS 分段)和 UDP 的分片(按照 MTU 分片)功能,由网卡来完成 。
GSO(Generic Segmentation Offload):在网卡不支持 TSO/UFO 时,将 TCP/UDP 包的分段,延迟到进入网卡前再执行。这样,不仅可以减少 CPU 的消耗,还可以在发生丢包时只重传分段后的包。
LRO(Large Receive Offload):在接收 TCP 分段包时,由网卡将其组装合并后,再交给上层网络处理。不过要注意,在需要 IP 转发的情况下,不能开启 LRO,因为如果多个包的头部信息不一致,LRO 合并会导致网络包的校验错误。
GRO(Generic Receive Offload):GRO 修复了 LRO 的缺陷,并且更为通用,同时支持 TCP 和 UDP。
RSS(Receive Side Scaling):也称为多队列接收,它基于硬件的多个接收队列,来分配网络接收进程,这样可以让多个 CPU 来处理接收到的网络包。
VXLAN 卸载:也就是让网卡来完成 VXLAN 的组包功能。
最后,对于网络接口本身,也有很多方法,可以优化网络的吞吐量。
比如,你可以开启网络接口的多队列功能。这样,每个队列就可以用不同的中断号,调度到不同 CPU 上执行,从而提升网络的吞吐量。
再如,你可以增大网络接口的缓冲区大小,以及队列长度等,提升网络传输的吞吐量(注意,这可能导致延迟增大)。
你还可以使用 Traffic Control 工具,为不同网络流量配置 QoS。
TCP状态详解
状态
LISTENING:侦听来自远方的TCP端口的连接请求.
服务端需要打开一个socket进行监听,状态为LISTEN。
当提供的服务没有被连接时就处于LISTENING状态
SYN-SENT:客户端SYN_SENT状态:
再发送连接请求后等待匹配的连接请求:客户端通过应用程序调用connect进行active open.于是客户端tcp发送一个SYN以请求建立一个连接.之后状态置为SYN_SENT. /*The socket is actively attempting to establish a connection. 在发送连接请求后等待匹配的连接请求 */ 当请求连接时客户端首先要发送同步信号给要访问的机器,此时状态为SYN_SENT,如果连接成功了就变为ESTABLISHED,正常情况下SYN_SENT状态非常短暂。
SYN_SENT数量很多
一是你要访问的网站不存在或线路不好,
二是用扫描软件扫描一个网段的机器,也会出出现很多SYN_SENT,
三是可能中了病毒了,例如中了\"冲击波\",病毒发作时会扫描其它机器,这样会有很多SYN_SENT出现。
SYN-RECEIVED:服务器端状态SYN_RCVD
再收到和发送一个连接请求后等待对方对连接请求的确认 当服务器收到客户端发送的同步信号时,将标志位ACK和SYN置1发送给客户端,此时服务器端处于SYN_RCVD状态,如果连接成功了就变为ESTABLISHED,正常情况下SYN_RCVD状态非常短暂。
SYN_RCVD数量很多
那你的机器有可能被SYN Flood的DoS(拒绝服务攻击)攻击了。
ESTABLISHED:代表一个打开的连接。
ESTABLISHED状态是表示两台机器正在传输数据,观察这个状态最主要的就是看哪个程序正在处于ESTABLISHED状态。 服务器出现很多ESTABLISHED状态: netstat -nat |grep 9502或者使用lsof -i:9502可以检测到。
当客户端未主动close的时候就断开连接:即客户端发送的FIN丢失或未发送。
这时候若客户端断开的时候发送了FIN包,则服务端将会处于CLOSE_WAIT状态;
这时候若客户端断开的时候未发送FIN包,则服务端处还是显示ESTABLISHED状态;
结果客户端重新连接服务器。
而新连接上来的客户端(也就是刚才断掉的重新连上来了)在服务端肯定是ESTABLISHED; 如果客户端重复的上演这种情况,那么服务端将会出现大量的假的ESTABLISHED连接和CLOSE_WAIT连接。 最终结果就是新的其他客户端无法连接上来,但是利用netstat还是能看到一条连接已经建立,并显示ESTABLISHED,但始终无法进入程序代码。
FIN-WAIT-1:等待远程TCP连接中断请求,或先前的连接中断请求的确认
如果服务器出现shutdown再重启,使用netstat -nat查看,就会看到很多FIN-WAIT-1的状态。就是因为服务器当前有很多客户端连接,直接关闭服务器后,无法接收到客户端的ACK。
FIN-WAIT-2:从远程TCP等待连接中断请求
CLOSE-WAIT:等待从本地用户发来的连接中断请求
CLOSING:等待远程TCP对连接中断的确认
比较少见./* Both sockets are shut down but we still don't have all our data sent. 等待远程TCP对连接中断的确认 */
LAST-ACK:等待原来的发向远程TCP的连接中断请求的确认
使用并发压力测试的时候,突然断开压力测试客户端,服务器会看到很多LAST-ACK。
TIME-WAIT:等待足够的时间以确保远程TCP接收到连接中断请求的确认
在主动关闭端接收到FIN后,TCP就发送ACK包,并进入TIME-WAIT状态。/* The socket is waiting after close to handle packets still in the network.等待足够的时间以确保远程TCP接收到连接中断请求的确认 */ TIME_WAIT等待状态,这个状态又叫做2MSL状态,说的是在TIME_WAIT2发送了最后一个ACK数据报以后,要进入TIME_WAIT状态,这个状态是防止最后一次握手的数据报没有传送到对方那里而准备的(注意这不是四次握手,这是第四次握手的保险状态)。这个状态在很大程度上保证了双方都可以正常结束,但是,问题也来了。由于插口的2MSL状态(插口是IP和端口对的意思,socket),使得应用程序在2MSL时间内是无法再次使用同一个插口的,对于客户程序还好一些,但是对于服务程序,例如httpd,它总是要使用同一个端口来进行服务,而在2MSL时间内,启动httpd就会出现错误(插口被使用)。为了避免这个错误,服务器给出了一个平静时间的概念,这是说在2MSL时间内,虽然可以重新启动服务器,但是这个服务器还是要平静的等待2MSL时间的过去才能进行下一次连接。
CLOSED:没有任何连接状态
被动关闭端在接受到ACK包后,就进入了closed的状态。连接结束./* The socket is not being used. 没有任何连接状态 */
TCP状态迁移路线
客户端
CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED
服务器
CLOSED->LISTEN->SYN_RCVD->ESTABLISHED->CLOSE_WAIT->LAST_ACK->CLOSED
答疑:
1、最大连接数是不是受限于 65535 个端口
我们知道,无论 TCP 还是 UDP,端口号都只占 16 位,也就说其最大值也只有 65535。那是不是说,如果使用 TCP 协议,在单台机器、单个 IP 地址时,并发连接数最大也只有 65535 呢?对于这个问题,首先你要知道,Linux 协议栈,通过五元组来标志一个连接(即协议,源 IP、源端口、目的 IP、目的端口)。明白了这一点,这个问题其实就有了思路。我们应该分客户端和服务器端,这两种场景来分析。对客户端来说,每次发起 TCP 连接请求时,都需要分配一个空闲的本地端口,去连接远端的服务器。由于这个本地端口是独占的,所以客户端最多只能发起 65535 个连接。对服务器端来说,其通常监听在固定端口上(比如 80 端口),等待客户端的连接。根据五元组结构,我们知道,客户端的 IP 和端口都是可变的。如果不考虑 IP 地址分类以及资源限制,服务器端的理论最大连接数,可以达到 2 的 48 次方(IP 为 32 位,端口号为 16 位),远大于 65535。所以,综合来看,客户端最大支持 65535 个连接,而服务器端可支持的连接数是海量的。当然,由于 Linux 协议栈本身的性能,以及各种物理和软件的资源限制等,这么大的连接数,还是远远达不到的(实际上,C10M 就已经很难了)。
2、DNS慢
抓包看哪里时间慢
比如 PTR。正是两次 PTR 请求没有得到响应而超时导致的。PTR 反向地址解析的目的,是从 IP 地址反查出域名,但事实上,并非所有 IP 地址都会定义 PTR 记录,所以 PTR 查询很可能会失败。
3、DDOS:分布式拒绝服务攻击
攻击类型
第一种,耗尽带宽。无论是服务器还是路由器、交换机等网络设备,带宽都有固定的上限。带宽耗尽后,就会发生网络拥堵,从而无法传输其他正常的网络报文。
第二种,耗尽操作系统的资源。网络服务的正常运行,都需要一定的系统资源,像是 CPU、内存等物理资源,以及连接表等软件资源。一旦资源耗尽,系统就不能处理其他正常的网络连接。
第三种,消耗应用程序的运行资源。应用程序的运行,通常还需要跟其他的资源或系统交互。如果应用程序一直忙于处理无效请求,也会导致正常请求的处理变慢,甚至得不到响应。
比如,构造大量不同的域名来攻击 DNS 服务器,就会导致 DNS 服务器不停执行迭代查询,并更新缓存。这会极大地消耗 DNS 服务器的资源,使 DNS 的响应变慢。
SYN Flood攻击
即客户端构造大量的 SYN 包,请求建立 TCP 连接;
而服务器收到包后,会向源 IP 发送 SYN+ACK 报文,并等待三次握手的最后一次 ACK 报文,直到超时
这种等待状态的 TCP 连接,通常也称为半开连接。由于连接表的大小有限,大量的半开连接就会导致连接表迅速占满,从而无法建立新的 TCP 连接。
查看半连接状态命令:查看 TCP 半开连接的方法,关键在于 SYN_RECEIVED 状态的连接。我们可以使用 netstat ,来查看所有连接的状态,不过要注意,SYN_REVEIVED 的状态,通常被缩写为 SYN_RECV。
防止 syn flood方法
# -n 表示不解析名字,-p 表示显示连接所属进程$ netstat -n -p | grep SYN_REC
IP固定
$ iptables -I INPUT -s 192.168.0.2 -p tcp -j REJECT
IP不固定
# 限制 syn 并发数为每秒 1 次$ iptables -A INPUT -p tcp --syn -m limit --limit 1/s -j ACCEPT# 限制单个 IP 在 60 秒新建立的连接数为 10$ iptables -I INPUT -p tcp --dport 80 --syn -m recent --name SYN_FLOOD --update --seconds 60 --hitcount 10 -j REJECT
TCP优化
调整半连接状态
查看半连接数量
sysctl net.ipv4.tcp_max_syn_backlognet.ipv4.tcp_max_syn_backlog = 256
默认 256
调整半连接数量
sysctl -w net.ipv4.tcp_max_syn_backlog=1024net.ipv4.tcp_max_syn_backlog = 1024
调整重连次数
连接每个 SYN_RECV 时,如果失败的话,内核还会自动重试,并且默认的重试次数是 5 次。你可以执行下面的命令,将其减小为 1 次:
sysctl -w net.ipv4.tcp_synack_retries=1net.ipv4.tcp_synack_retries = 1
TCP SYN Cookies
TCP SYN Cookies 也是一种专门防御 SYN Flood 攻击的方法。SYN Cookies 基于连接信息(包括源地址、源端口、目的地址、目的端口等)以及一个加密种子(如系统启动时间),计算出一个哈希值(SHA1),这个哈希值称为 cookie。
然后,这个 cookie 就被用作序列号,来应答 SYN+ACK 包,并释放连接状态。当客户端发送完三次握手的最后一次 ACK 后,服务器就会再次计算这个哈希值,确认是上次返回的 SYN+ACK 的返回包,才会进入 TCP 的连接状态。
因而,开启 SYN Cookies 后,就不需要维护半开连接状态了,进而也就没有了半连接数的限制。
注意,开启 TCP syncookies 后,内核选项 net.ipv4.tcp_max_syn_backlog 也就无效了。
开启命令
sysctl -w net.ipv4.tcp_syncookies=1net.ipv4.tcp_syncookies = 1
注意,上述 sysctl 命令修改的配置都是临时的,重启后这些配置就会丢失。所以,为了保证配置持久化,你还应该把这些配置,写入 /etc/sysctl.conf 文件中。比如:
cat /etc/sysctl.confnet.ipv4.tcp_syncookies = 1net.ipv4.tcp_synack_retries = 1net.ipv4.tcp_max_syn_backlog = 1024
不过要记得,写入 /etc/sysctl.conf 的配置,需要执行 sysctl -p 命令后,才会动态生效。
只能缓解,无法彻底解决
购买专业的流量清洗设备和网络防火墙,在网络入口处阻断恶意流量,只保留正常流量进入数据中心的服务器中
在 Linux 服务器中,你可以通过内核调优、DPDK、XDP 等多种方法,来增大服务器的抗攻击能力,降低 DDoS 对正常服务的影响。而在应用程序中,你可以利用各级缓存、 WAF、CDN 等方式,缓解 DDoS 对应用程序的影响。
4、为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?
http://jm.taobao.org/2017/06/08/20170608/#more
5、为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?
这是因为虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到ESTABLISH状态那样):一方面是可靠的实现TCP全双工连接的终止,也就是当最后的ACK丢失后,被动关闭端会重发FIN,因此主动关闭端需要维持状态信息,以允许它重新发送最终的ACK。另一方面,但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。TCP在2MSL等待期间,定义这个连接(4元组)不能再使用,任何迟到的报文都会丢弃。设想如果没有2MSL的限制,恰好新到的连接正好满足原先的4元组,这时候连接就可能接收到网络上的延迟报文就可能干扰最新建立的连接。
6、tcp包超过最大 MTU:
通过案例来学习 MSS、MTU: http://jm.taobao.org/2017/07/27/20170727/#more 由于包的大小超过网卡设置的最大 MTU 导致失败重传
MSS–最大传输包
MSS+包头就是MTU(最大传输单元)
如果MTU过大可能在传输的过程中被卡住过不去造成卡死(这个大小的包一直传输不过去),跟丢包还不一样。
如果本机的MTU比网关的MTU大,大的数据报就会被拆开来传送,这样会产生很多数据报碎片,增加丢包率,降低网络速度。把本机的MTU设成比网关的MTU小或相同,就可以减少丢包。
虚拟机内 Ping 时,包大小最大为 1472 bytes,1472 + 8 bytes ICMP包头+ 20 bytes IP 包头=1500 bytes
几种常见的最大传输单元
如何探测远端网口支持的最大 MTU?
windows
命令格式:ping -f -l 1472 Destination-IP-f :表示不分片-l :是小写的L,表示包的大小
linux
命令格式:ping -M do -s 1472 Destination-IP-s :表示 ICMP 包大小-M do :表示不分片
MacOS
命令格式:ping -s 1472 -D Destination-IP-s :表示 ICMP 包大小-D :表示不分片
dmesg查看问题主机看到了这样一些信息
2016-08-08T08:15:27.125951+00:00 server kernel: openvswitch: ens2f0.627: dropped over-mtu packet: 1428 > 14002016-08-08T08:15:27.536517+00:00 server kernel: openvswitch: ens2f0.627: dropped over-mtu packet: 1428 > 1400
修改mtu到1500
修改
ifconfig eth0 mtu 1500
查看
root@localhost# ifconfig |grep 1500
7、优酷+淘宝活动性能优化实战: http://jm.taobao.org/2017/05/04/20170504/#more
8、A 服务内网调用 B 服务,中间经过防火墙,每天约 1000 万连接,每天约 100 个连接超时,如何排查问题?
1.查看报错日志
2.通过抓包的方式,但是抓的过程要把 tcp 的包拆解然后过滤进行筛查。比如根据 MTU、MSS
区块链
技术上三大特点
1.分布式账本数据库
2.加密算法
3.共识算法
1.pow
2.pos
3.dpos
4.poa
记账
物联网 IOT
mqtt
现阶段要求每个微消息队列 MQTT 实例(网关实例)必须绑定一个存储实例(消息队列 RocketMQ 实例)使用,后续会开放非持久化使用模式(直推模式,消息不持久化)。
消息存储实例目前只支持消息队列 RocketMQ 类型的后端存储实例,后续会支持消息队列 Kafka 和消息队列 AMQP(RabbitMQ)等其他类型的存储实例。
秉承单一职责的原则,微消息队列 MQTT 在设计上是一个面向移动互联网和 IoT 领域的无状态网关,只关心海量移动端设备的接入、管理和消息传输,消息数据的存储则都会路由给后端存储产品,例如传统的消息中间件消息队列 RocketMQ、消息队列 Kafka 等产品。
开源产品 ematt
emqtt 官网
消息队列
rabbitmq
密码加解密
加密
对称加密DES
通信双方协定,用同一把钥匙🔑来进行通信双方的加解密
缺点:少部分人使用还是挺好的,但是如果涉及到很多用户的话,要生成很多的秘钥和分发,而且安全性不是很高
DES(Data Encryption Standard):数据加密标准,速度较快,适用于加密大量数据的场合。
3DES(Triple DES):是基于DES,对一块数据用三个不同的密钥进行三次加密,强度更高。
非对称加密RSA
通信的双方各自分别生成两个秘钥,一个公钥一个私钥,公钥完全公开,谁都可以看到,私钥自己保存,通信的过程中,双方分别用对方的公钥进行加密,然后用自己的私钥进行解密
公钥加密,私钥解密,私钥签名,公钥验签
RSA:由 RSA 公司发明,是一个支持变长密钥的公共密钥算法,需要加密的文件块的长度也是可变的。RSA在国外早已进入实用阶段,已研制出多种高速的RSA的专用芯片。
DSA(Digital Signature Algorithm):数字签名算法,是一种标准的 DSS(数字签名标准),严格来说不算加密算法。
ECC(Elliptic Curves Cryptography):椭圆曲线密码编码学。ECC和RSA相比,具有多方面的绝对优势,主要有:抗攻击性强。相同的密钥长度,其抗攻击性要强很多倍。计算量小,处理速度快。ECC总的速度比RSA、DSA要快得多。存储空间占用小。ECC的密钥尺寸和系统参数与RSA、DSA相比要小得多,意味着它所占的存贮空间要小得多。这对于加密算法在IC卡上的应用具有特别重要的意义。带宽要求低。当对长消息进行加解密时,三类密码系统有相同的带宽要求,但应用于短消息时ECC带宽要求却低得多。带宽要求低使ECC在无线网络领域具有广泛的应用前景。
散列算法
散列算法,又称哈希函数,是一种单向加密算法。在信息安全技术中,经常需要验证消息的完整性,散列(Hash)函数提供了这一服务,它对不同长度的输入消息,产生固定长度的输出。这个固定长度的输出称为原输入消息的\"散列\"或\"消息摘要\"(Message digest)。散列算法不算加密算法,因为其结果是不可逆的,既然是不可逆的,那么当然不是用来加密的,而是签名。
MD5:MD5是一种不可逆的加密算法,目前是最牢靠的加密算法之一,尚没有能够逆运算的程序被开发出来,它对应任何字符串都可以加密成一段唯一的固定长度的代码。
其他
BASE64
其实不是安全领域下的加密解密算法,只能算是一个编码算法,通常用于把二进制数据编码为可写的字符形式的数据,对数据内容进行编码来适合传输(可以对img图像编码用于传输)。这是一种可逆的编码方式。编码后的数据是一个字符串,其中包含的字符为:A-Z、a-z、0-9、+、/,共64个字符(26 + 26 + 10 + 1 + 1 = 64,其实是65个字符,“=”是填充字符。Base64要求把每三个8Bit的字节转换为四个6Bit的字节(3*8 = 4*6 = 24),然后把6Bit再添两位高位0,组成四个8Bit的字节,也就是说,转换后的字符串理论上将要比原来的长1/3。原文的字节最后不够3个的地方用0来补足,转换时Base64编码用=号来代替。这就是为什么有些Base64编码会以一个或两个等号结束的原因,中间是不可能出现等号的,但等号最多只有两个。其实不用\"=\"也不耽误解码,之所以用\"=\",可能是考虑到多段编码后的Base64字符串拼起来也不会引起混淆。)Base64编码是从二进制到字符的过程,像一些中文字符用不同的编码转为二进制时,产生的二进制是不一样的,所以最终产生的Base64字符也不一样。例如\"上网\"对应utf-8格式的Base64编码是\"5LiK572R\", 对应GB2312格式的Base64编码是\"yc/N+A==\"。标准的Base64并不适合直接放在URL里传输,因为URL编码器会把标准Base64中的“/”和“+”字符变为形如“%XX”的形式,而这些“%”号在存入数据库时还需要再进行转换,因为ANSI SQL中已将“%”号用作通配符。为解决此问题,可采用一种用于URL的改进Base64编码,它不在末尾填充'='号,并将标准Base64中的“+”和“/”分别改成了“-”和“_”,这样就免去了在URL编解码和数据库存储时所要作的转换,避免了编码信息长度在此过程中的增加,并统一了数据库、表单等处对象标识符的格式。另有一种用于正则表达式的改进Base64变种,它将“+”和“/”改成了“!”和“-”,因为“+”,“*”以及前面在IRCu中用到的“[”和“]”在正则表达式中都可能具有特殊含义。此外还有一些变种,它们将“+/”改为“_-”或“._”(用作编程语言中的标识符名称)或“.-”(用于XML中的Nmtoken)甚至“_:”(用于XML中的Name)。
HTTPS
URL表明它使用了HTTP,但HTTPS存在不同于HTTP的默认端口及一个加密/身份验证层(在HTTP与TCP之间),提供了身份验证与加密通讯方法,现在它被广泛用于万维网上安全敏感的通讯,例如交易支付方面。它的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。
Git
获取最新一次 commit 的获取short commit id
git rev-parse --short HEAD
防止配置信息用户名密码等关键信息泄露
将数据库连接信息等敏感配置从项目中剥离;
数据库增加 IP 白名单连接限制;
最小权限原则:每个账号只配置所必需的权限,避免删表删库等高危操作;
定期修改数据库账号、密码。
使用 Nacos 配置管理模块,将敏感配置信息都存放到 Nacos 中
面试注意
1.谈薪资
2.切记不要答非所问
结论在先,观点在后
问题不明确时候,复述一遍,谈自己的理解,请对方确认
要有逻辑、要有调理
3.离职原因
主要是因为职业发展与预期有了偏颇
1. 现有公司限制了我的发展,团队规模比较小,做的东西比较单一
2. 目前我的职业规划是想找一个规模大一点的有运维团队且有实力的公司去做 k8s 方向
4.职业意愿
目前想做 k8s 相关的方向,但也很乐意接受公司的其他安排,我的学习能力强,上手快
5.加班情况
自己责任范围内的工作,不能算是加班。
6. 优缺点
认真负责,喜欢和善于学习新东西,保持好奇心和热情
我喜欢独立工作,而不喜欢主管领导在我的工作中安排一切。
对准时要求严格
7.职业规划--3 年
k8s 运维技能提升
运维开发,有开发技能之后,能更容易找到程序运行过程中容易出现瓶颈的地方
架构师
0 条评论
回复 删除
下一页