2025-2-24 高级运维面试题-linux部分

经过本人为期一个半月的不懈努力,累计面试了二十多家公司,共计约五十余场面试,考察的面试题超两百道,积累了许多宝贵的面试经验。现在,我将这些面试题以及我个人的应对心得精心整理成一份面试攻略分享给大家,快来一起测测自己能回答多少道面试题吧。

面试基本信息 #

面试岗位:运维工程师(容器与ES方向)、运维开发工程师、SRE工程师 工作经验:5年 薪资范围:年薪45万左右,base北京 面试时间:7月初-8月中旬 主要公司:

  • 互联网公司:字节、京东、百度、网易、蚂蚁金服、小米、滴滴、去哪儿、猎豹移动、商汤、旷视、智谱华章、马蜂窝、竞技世界
  • 国企子公司:电信、联通、建行、中石化

面试题汇总 #

其中☆表示多次出现过的高频面试题,已经按分类整理。

Linux #

grep sed awk cut组合使用☆ #

grepsedawkcut 都是 Linux 系统中常用的文本处理工具,通常可以结合使用以实现更复杂的文本处理任务。这里是一些常见的组合使用示例:

1. 使用 grep + cut:筛选和提取字段 #

假设你有一个文件 data.txt,其内容如下:

Name, Age, Department
Alice, 30, HR
Bob, 25, Engineering
Charlie, 35, Marketing

如果你只想提取 NameAge 字段,可以使用 grepcut 的组合:

grep -v "Name" data.txt | cut -d ',' -f 1,2

解释:

  • grep -v "Name":去除第一行标题。
  • cut -d ',' -f 1,2:按照逗号分隔,提取第一列和第二列。

输出:

Alice, 30
Bob, 25
Charlie, 35

2. 使用 grep + awk:筛选和处理文本 #

假设你要根据某个条件(如年龄大于 30)筛选数据,并进行一些进一步的处理:

grep -v "Name" data.txt | awk -F ',' '$2 > 30 {print $1, $2}'

解释:

  • grep -v "Name":去除标题行。
  • awk -F ',' '$2 > 30 {print $1, $2}':以逗号作为分隔符,筛选年龄大于 30 的行,并输出姓名和年龄。

输出:

Charlie 35

3. 使用 sed + awk:修改并处理文本 #

假设你想把所有姓名前面加上 “Mr.” 并筛选年龄大于 30 的人:

sed 's/^/Mr. /' data.txt | awk -F ',' '$2 > 30 {print $1, $2}'

解释:

  • sed 's/^/Mr. /':在每一行的开头加上 “Mr. “。
  • awk -F ',' '$2 > 30 {print $1, $2}':筛选年龄大于 30 的人并输出姓名和年龄。

输出:

Mr. Charlie 35

4. 使用 grep + sed + awk:综合使用 #

你可以将这几种工具结合起来,进行更复杂的处理。例如,从文件中找出所有包含 “HR” 的行,去掉空格并提取姓名:

grep "HR" data.txt | sed 's/ //g' | awk -F ',' '{print $1}'

解释:

  • grep "HR":查找包含 “HR” 的行。
  • sed 's/ //g':删除行中的所有空格。
  • awk -F ',' '{print $1}':提取第一列(姓名)。

输出:

Alice

总结 #

  • grep 用于筛选文本。
  • cut 用于提取特定的字段或列。
  • awk 用于更复杂的模式匹配和处理,可以在行级别进行操作。
  • sed 用于文本的替换、删除等操作。

这些工具各有优势,可以根据需要灵活组合,完成各种文本处理任务。

http错误码和原因 #

HTTP 错误码用于表示 HTTP 请求的状态。它们按类别分为五个类:1xx(信息类)、2xx(成功类)、3xx(重定向类)、4xx(客户端错误类)、5xx(服务器错误类)。以下是常见的 HTTP 错误码及其含义和原因:

1. 1xx - 信息类 #

这些状态码表示服务器已收到请求,客户端需要继续处理。

  • 100 Continue:客户端应继续发送请求的剩余部分。
  • 101 Switching Protocols:服务器接受客户端的协议切换请求。

2. 2xx - 成功类 #

表示请求已经成功被服务器处理。

  • 200 OK:请求成功,通常在 GET 或 POST 请求后返回。
  • 201 Created:请求成功并且服务器创建了新资源。
  • 202 Accepted:请求已接受,但未处理完成。
  • 204 No Content:请求成功,但没有返回任何内容。

3. 3xx - 重定向类 #

这些状态码表示客户端需要采取额外的操作才能完成请求。

  • 301 Moved Permanently:资源已永久移动到新位置。
  • 302 Found:资源临时移动到新位置(常用于临时重定向)。
  • 303 See Other:请求应使用 GET 方法访问另一个资源。
  • 304 Not Modified:请求的资源未修改,客户端可以使用缓存。
  • 307 Temporary Redirect:请求的资源临时移动到新位置。
  • 308 Permanent Redirect:请求的资源永久移动到新位置。

4. 4xx - 客户端错误类 #

这些状态码表示客户端请求有错误,通常是由于客户端的请求无效或格式不正确。

  • 400 Bad Request:请求格式不正确或缺少必需的参数。
  • 401 Unauthorized:请求需要用户身份验证,通常用于未授权的 API 请求。
  • 402 Payment Required:此状态码保留供将来使用,目前大多不常见。
  • 403 Forbidden:服务器理解请求,但拒绝执行。通常是因为权限问题。
  • 404 Not Found:请求的资源未找到。
  • 405 Method Not Allowed:请求方法(如 GET、POST 等)不允许访问该资源。
  • 406 Not Acceptable:服务器无法生成符合请求头中 “Accept” 字段的响应。
  • 407 Proxy Authentication Required:需要代理认证。
  • 408 Request Timeout:客户端请求超时,服务器未在规定时间内接收到请求。
  • 409 Conflict:请求导致资源冲突,通常用于资源的创建或修改操作。
  • 410 Gone:请求的资源不再可用,并且不再会恢复。
  • 411 Length Required:请求缺少 “Content-Length” 头部。
  • 412 Precondition Failed:请求头中的某些条件未满足。
  • 413 Payload Too Large:请求体太大,服务器无法处理。
  • 414 URI Too Long:请求的 URI 太长,服务器无法处理。
  • 415 Unsupported Media Type:请求的媒体类型不受支持。
  • 416 Range Not Satisfiable:请求的范围无效,服务器无法提供部分资源。
  • 417 Expectation Failed:服务器无法满足 “Expect” 请求头中的期望。

5. 5xx - 服务器错误类 #

这些状态码表示服务器在处理请求时发生了错误,通常是服务器问题。

  • 500 Internal Server Error:服务器遇到错误,无法完成请求。
  • 501 Not Implemented:服务器不支持请求的方法或功能。
  • 502 Bad Gateway:服务器作为网关或代理时,从上游服务器收到无效响应。
  • 503 Service Unavailable:服务器当前无法处理请求,通常由于过载或维护。
  • 504 Gateway Timeout:服务器作为网关或代理时,未从上游服务器收到及时响应。
  • 505 HTTP Version Not Supported:服务器不支持请求的 HTTP 版本。

总结 #

  • 2xx:请求成功。
  • 3xx:请求需要重定向。
  • 4xx:客户端错误(例如请求不合法或权限问题)。
  • 5xx:服务器错误(服务器处理请求时发生了问题)。

这些错误码帮助开发者理解请求失败的原因,方便进行调试或错误处理。

3.长连接、短连接、WebSocket区别和使用场景 #

长连接、短连接和 WebSocket 都是网络通信中的不同连接方式,它们在使用时有不同的特点和应用场景。下面我们来详细讨论它们的区别和适用场景。

1. 短连接(Short Connection) #

特点: #

  • 每次请求都建立新的连接:每次客户端发送请求时,都会和服务器建立一个新的连接,处理完请求后即关闭连接。
  • 资源消耗大:每次请求都要进行连接的建立和销毁,频繁的连接和断开会增加网络延迟和资源消耗,尤其是在高频次请求的情况下。
  • 典型协议:HTTP/1.x、SMTP(邮件)、FTP 等。

使用场景: #

  • 简单的请求-响应模式:比如网页浏览、提交表单等,一次请求一次响应。
  • 不需要持续通信的场景:例如用户查询信息,数据更新不频繁,服务器负载较低的情况。

优点: #

  • 连接生命周期短,不会占用太多服务器资源。
  • 适合低频请求的场景。

缺点: #

  • 每次请求都需要建立连接,较高的延迟和资源消耗。
  • 无法实现实时通信或长时间的保持连接。

2. 长连接(Long Connection) #

特点: #

  • 建立连接后保持长时间不关闭:客户端和服务器在建立连接后,连接会一直保持,直到客户端或服务器主动关闭连接。
  • 减少建立连接的开销:在连接存活期间,客户端可以发送多个请求,避免了频繁的连接和断开的开销。
  • 典型协议:HTTP/1.1 的持久连接(通过 Connection: keep-alive 头控制)、TCP 连接。

使用场景: #

  • 需要多次请求的场景:如在一个页面加载过程中,多个资源(图片、JS、CSS 文件等)需要多次请求服务器,这时保持一个持久的连接可以降低延迟。
  • 实时性要求不高的长时间会话:比如在线聊天室、数据推送等场景。

优点: #

  • 避免了频繁的连接建立和断开,减少了延迟。
  • 更适合多次请求的交互。

缺点: #

  • 长时间占用连接资源,可能导致服务器负载增加。
  • 如果连接不稳定,可能会导致连接中断。

3. WebSocket #

特点: #

  • 全双工通信:WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,客户端和服务器可以随时发送数据到对方。
  • 低延迟、实时通信:建立 WebSocket 连接后,客户端和服务器之间可以进行实时的双向数据交换,适合高频率和低延迟的场景。
  • 持久连接:WebSocket 一旦建立连接后会持续存在,直到客户端或服务器主动关闭连接。
  • 典型协议:WebSocket 协议(ws://wss://)。

使用场景: #

  • 实时通讯应用:如在线聊天、即时消息、股票行情、多人在线游戏、社交网络等。
  • 需要实时推送数据的场景:例如实时更新、推送通知、直播数据流、物联网设备管理等。

优点: #

  • 低延迟,适合实时性要求高的应用。
  • 通过一个连接可以发送和接收数据,减少了频繁建立连接的开销。
  • 服务器和客户端可以主动推送消息到对方。

缺点: #

  • 需要持续保持连接,可能会消耗较多的资源。
  • 对于 WebSocket 的支持较为有限,需要服务器和客户端同时支持 WebSocket 协议。
  • 因为 WebSocket 是持续连接,在某些网络环境下可能会遇到连接稳定性问题。

总结比较 #

特性/协议短连接长连接WebSocket
连接类型每次请求建立一次连接一次连接可多次使用持久连接,双向通信
连接生命周期短暂的连接和断开持久连接持久连接
资源消耗高(频繁连接和断开)低(减少连接建立的开销)适中(长时间占用连接)
适用场景简单的请求-响应,低频请求多次请求需要保持连接,实时性要求不高实时通信,低延迟数据交换,高频数据推送
通信方式单向(客户端请求,服务器响应)单向(客户端请求,服务器响应)双向(客户端和服务器都能发送消息)
协议支持HTTP/1.x,SMTP,FTP 等HTTP/1.1,TCP 等WebSocket

适用场景总结: #

  • 短连接:适用于简单的请求-响应场景,连接生命周期短,适合低频交互,不需要持久连接的场合。
  • 长连接:适用于需要多次请求的场景,或者是可以接受一定延迟但要求持续连接的应用。
  • WebSocket:适用于实时通信、低延迟和高频数据交换的场景,如在线游戏、即时聊天、股票行情推送等。

nginx性能优化有哪些方式☆ #

Nginx 是一个高性能的 Web 服务器和反向代理服务器,广泛用于负载均衡、反向代理、静态文件服务等场景。为了提高 Nginx 的性能,可以通过多个方面进行优化。下面列出了一些常见的 Nginx 性能优化方式:

1. 调整工作进程和连接数 #

Nginx 的性能很大程度上取决于工作进程(worker process)和每个工作进程的连接数。合理的配置可以提高并发处理能力。

  • worker_processes:设置 Nginx 启动的工作进程数。通常设置为 CPU 核心数,或者根据服务器的负载情况调整。

    worker_processes  4;  # 根据 CPU 核心数设置
    
  • worker_connections:设置每个工作进程可以打开的最大连接数。此值与客户端并发连接数密切相关。调整时要考虑系统的最大文件描述符限制。

    worker_connections 1024;  # 根据实际需求调整
    
  • worker_rlimit_nofile:增加工作进程的文件描述符限制,防止在高并发下达到文件描述符限制。

    worker_rlimit_nofile 65535;
    

2. 启用 keepalive 持久连接 #

keepalive 连接可以减少建立和关闭连接的开销。可以通过设置合适的超时时间来优化连接复用。

  • keepalive_timeout:设置保持连接的最大时间。在处理高并发请求时,合理的保持连接超时能减少连接建立的次数。

    keepalive_timeout  65;  # 默认是 75s,适当调小可以减少空闲连接占用
    
  • keepalive_requests:限制每个连接的请求数,避免占用过多资源。

    keepalive_requests 10000;  # 每个连接最多处理 10000 个请求
    

3. 启用 GZIP 压缩 #

开启 GZIP 压缩可以有效减少传输数据量,提升带宽利用率和加载速度。

  • gzip:开启 GZIP 压缩。

    gzip  on;
    gzip_comp_level 6;  # 设置压缩级别,1-9,数字越大压缩越强
    gzip_min_length 1000;  # 只有内容长度大于 1000 字节的响应才会被压缩
    gzip_proxied any;  # 启用对代理请求的压缩
    gzip_types text/plain text/css application/javascript application/json application/xml text/javascript application/xml+rss image/svg+xml;  # 指定需要压缩的文件类型
    

4. 优化缓存策略 #

合理的缓存策略可以极大提升访问速度,减少对后端服务器的压力。

  • 开启缓存:通过 proxy_cache 配置启用反向代理缓存,可以缓存静态文件和动态内容,减少对后端服务器的请求。

    proxy_cache_path /tmp/cache levels=1:2 keys_zone=my_cache:10m inactive=60m max_size=1g;
    proxy_cache_key "$scheme$request_method$host$request_uri";
    
  • 设置缓存过期时间:使用 expires 设置静态资源的缓存时间,减少对后端的请求。

    location /static/ {
        expires 30d;  # 设置缓存 30 天
    }
    

5. 负载均衡优化 #

对于 Nginx 的反向代理,合理的负载均衡配置可以提高并发性能,避免单一节点过载。

  • 负载均衡算法:Nginx 支持多种负载均衡算法,如轮询、最少连接、IP 哈希等,可以根据实际需求选择合适的算法。

    upstream backend {
        least_conn;  # 使用最少连接负载均衡算法
        server backend1.example.com;
        server backend2.example.com;
    }
    
  • 健康检查:定期检查后端服务器的健康状况,避免将流量引导到故障的服务器。

    upstream backend {
        server backend1.example.com;
        server backend2.example.com;
        # 设置失败请求重试次数
        server backend3.example.com max_fails=3 fail_timeout=30s;
    }
    

6. 减少 DNS 查询延迟 #

Nginx 可以缓存 DNS 查询结果,减少频繁的 DNS 查询带来的延迟。

  • resolver:设置 DNS 解析器并配置缓存。

    resolver 8.8.8.8 8.8.4.4 valid=10s;  # 设置 DNS 解析服务器及缓存时间
    

7. 提高文件 I/O 性能 #

Nginx 需要处理大量的静态文件,文件 I/O 性能直接影响整体性能。

  • 开启 sendfilesendfile 可以直接从文件系统读取数据并通过网络发送,避免多次复制,提高性能。

    sendfile on;
    tcp_nopush on;  # 配合 sendfile 使用,减少传输时的 TCP 包数量
    
  • aiodirectio:对于大文件,可以开启异步 I/O,进一步提高文件传输效率。

    aio threads;  # 开启异步 I/O
    directio 4m;  # 对大于 4MB 的文件开启直接 I/O
    

8. 调整 TCP 连接优化 #

Nginx 作为 Web 服务器,涉及到大量的 TCP 连接。优化 TCP 连接设置可以提高性能。

  • tcp_nodelay:启用 TCP_NODELAY,关闭 Nagle 算法,减少小数据包的延迟。

    tcp_nodelay on;
    
  • tcp_fin_timeout:控制服务器关闭连接时的等待时间,减小服务器的连接消耗。

    tcp_fin_timeout 30;
    

9. 限制请求速率 #

对于高流量的应用,限制客户端的请求速率可以防止流量洪水并确保公平分配资源。

  • limit_req:限制每秒请求数,防止某些客户端过于频繁地请求。

    limit_req_zone $binary_remote_addr zone=req_limit_per_ip:10m rate=1r/s;  # 每个 IP 最多 1 个请求/秒
    

10. 禁用不必要的模块 #

Nginx 提供了很多模块,某些模块可能在你的应用场景中并不需要。禁用不必要的模块可以减少内存占用和提升性能。

  • 在编译 Nginx 时,禁用不需要的模块。例如,禁用代理模块、邮件代理模块等。

    ./configure --without-http_rewrite_module --without-http_gzip_module
    

11. 监控和日志优化 #

监控 Nginx 的性能并进行实时调整,对于高负载应用尤为重要。

  • 日志优化:精简日志内容,避免记录不必要的请求信息,减少磁盘 I/O 和 CPU 占用。

    access_log /var/log/nginx/access.log combined buffer=32k flush=5m;  # 使用缓冲区来减少磁盘 I/O
    

总结 #

Nginx 性能优化涉及多个方面,包括硬件资源(如 CPU 和内存)、网络配置、负载均衡、缓存策略、TCP 连接优化等。合理配置和调整 Nginx 的工作进程、连接数、缓存机制、负载均衡策略等,可以有效提升 Nginx 的性能,确保在高并发、高流量的场景下仍然能够稳定、高效地工作。

lvs、nginx、haproxy区别和使用场景☆ #

LVS(Linux Virtual Server)、Nginx 和 HAProxy 都是常见的负载均衡技术,它们的功能有一些重叠,但在设计目标、性能、灵活性和使用场景上有所不同。下面将分别介绍它们的特点、优缺点及适用的使用场景。

1. LVS(Linux Virtual Server) #

LVS 是基于 Linux 内核的负载均衡解决方案,常用于高可用性和高性能的负载均衡场景。它主要通过 IP 负载均衡来分发流量。

特点: #

  • 工作在网络层(L4 层):LVS 主要进行基于 IP 地址和端口的负载均衡,通常工作在 OSI 模型的传输层(L4 层)。
  • 高性能:LVS 作为内核级别的负载均衡器,其性能非常高,因为它直接处理数据包转发,避免了应用层的开销。
  • 透明性:LVS 通过 NAT(网络地址转换)、DR(Direct Routing)等技术实现负载均衡,客户端无法察觉负载均衡的存在。
  • 调度算法:LVS 支持多种负载均衡算法,如轮询、加权轮询、最少连接、源地址哈希等。

优点: #

  • 高性能:由于是内核级别的负载均衡,LVS 能提供高吞吐量,适合处理大量并发流量。
  • 透明性:客户端不需要知道负载均衡的存在,提供透明的负载均衡。
  • 高可用性:可以与 Keepalived 配合使用,实现高可用性。

缺点: #

  • 灵活性差:LVS 仅支持 L4 层负载均衡,无法处理基于 HTTP 头、Cookie 等内容的路由,功能上不如应用层负载均衡器灵活。
  • 配置复杂:LVS 的配置和维护相对复杂,需要一定的 Linux 网络知识。

使用场景: #

  • 高性能、大规模流量的负载均衡:适用于需要高吞吐量和低延迟的场景,如大规模的 Web 服务、视频流服务等。
  • 简单的 L4 层负载均衡:如果只需要基于 IP 和端口的负载均衡,而不需要 HTTP 层的复杂路由和控制,LVS 是一个优秀的选择。

2. Nginx #

Nginx 是一个轻量级的 Web 服务器和反向代理服务器,除了处理 HTTP 请求外,还可以进行负载均衡。

特点: #

  • 工作在应用层(L7 层):Nginx 主要提供 HTTP、HTTPS、TCP 和 UDP 的负载均衡服务,通常工作在应用层(L7 层)。
  • 支持多种负载均衡算法:包括轮询、IP 哈希、加权轮询等。
  • 灵活的路由功能:可以基于 HTTP 请求的不同部分(如路径、头部、方法、主机等)进行路由,适合处理复杂的 Web 应用场景。
  • 反向代理功能:Nginx 同时提供反向代理功能,能够在负载均衡的同时,作为 Web 服务器或应用服务器的前端,进行内容缓存、请求处理等。

优点: #

  • 灵活性强:可以根据请求的内容(如 URL、HTTP 头、Cookie 等)做更精细的流量控制。
  • 简单易配置:Nginx 配置简洁,功能强大,社区活跃。
  • 支持 HTTP、HTTPS 和 TCP 负载均衡:除了支持传统的 HTTP 层负载均衡,还支持 TCP/UDP 负载均衡。
  • 高性能:尽管是应用层的负载均衡器,Nginx 依然具有较高的性能,能够处理大量并发请求。

缺点: #

  • 性能相对 LVS 较低:Nginx 的性能虽然很高,但相比于 LVS 的内核级负载均衡,Nginx 可能在处理非常高并发流量时稍显不足。
  • 依赖操作系统的网络栈:Nginx 是在用户态运行的,受限于操作系统的网络栈,相较于 LVS 内核级的处理能力可能稍差。

使用场景: #

  • Web 服务器负载均衡:适用于 Web 服务的负载均衡,尤其是需要对 HTTP 请求进行精细路由和控制的场景。
  • 反向代理:在 Web 应用架构中,Nginx 可以作为反向代理进行负载均衡,同时提供 SSL 终端加密、缓存、限流等功能。
  • 容器化环境中的负载均衡:在 Kubernetes、Docker 等容器化环境中,Nginx 是常用的负载均衡工具。

3. HAProxy #

HAProxy 是一个专注于高性能、高可用性的负载均衡器,广泛用于大规模的 Web 应用和高并发场景。

特点: #

  • 工作在应用层(L7 层)和传输层(L4 层):HAProxy 支持 HTTP、HTTPS、TCP 和 UDP 的负载均衡,可以根据不同层次进行流量转发。
  • 支持丰富的负载均衡算法:HAProxy 支持轮询、最少连接、加权轮询、源 IP 哈希等多种负载均衡策略。
  • 高可用性:HAProxy 支持健康检查、故障转移和高可用配置,能自动检测后端服务器的健康状况,并根据健康状态进行流量分配。
  • 非常高的性能:HAProxy 经过优化,能够处理大量的并发连接,尤其适合高吞吐量的应用。

优点: #

  • 高性能、高并发:HAProxy 在高负载和高并发场景下的表现优异,适合处理大量并发请求。
  • 丰富的配置选项:HAProxy 提供了灵活的配置选项,可以进行精细的负载均衡和流量管理。
  • 强大的健康检查机制:能够实时监控后端服务器的健康状态,保证高可用性。
  • 广泛的应用场景:HAProxy 可用于多种负载均衡场景,包括 Web、数据库、缓存服务等。

缺点: #

  • 配置相对复杂:相比 Nginx,HAProxy 的配置相对复杂,学习曲线较陡。
  • 不支持应用层的一些高级功能:虽然 HAProxy 支持 L7 层负载均衡,但其功能不如 Nginx 灵活,尤其在内容缓存、反向代理等方面,Nginx 提供了更多的高级功能。

使用场景: #

  • 高并发、大流量应用:适用于需要高性能和高可用性的负载均衡,如金融、社交媒体、大型电商网站等。
  • 复杂的负载均衡场景:适合需要多种负载均衡算法、健康检查以及故障转移的场景。
  • TCP 和 HTTP 负载均衡:HAProxy 支持 HTTP 层和 TCP 层的负载均衡,适合 Web 服务和数据库、缓存等应用的负载均衡。

总结比较 #

特性LVSNginxHAProxy
工作层次L4(传输层)L7(应用层)L7(应用层)和 L4(传输层)
性能非常高(内核级负载均衡)高,适合 Web 层负载均衡高,专注于高并发的负载均衡
灵活性低,主要支持 IP 和端口高,支持基于 URL、HTTP 头等路由高,支持多种负载均衡算法
配置复杂度较高较低,配置简洁较高,配置选项丰富
支持的协议TCP/UDPHTTP、HTTPS、TCP、UDPHTTP、HTTPS、TCP、UDP
高可用性需要与 Keepalived 配合使用支持与高可用架构集成支持内建的健康检查和故障转移
使用场景高性能的负载均衡,L4 层Web 应用、反向代理、容器化环境高并发、大流量的 Web 或服务负载均衡

选择建议#

  • LVS:适合大规模、高性能的 L4 层负载均衡,不需要复杂路由规则的场景。

  • Nginx:适合需要高性能并且需要对 HTTP 流量进行细粒度控制的 Web 服务负载均衡。

    HAProxy:适合高并发、复杂负载均衡场景,尤其是需要灵活调度和高可用性的应用。

僵尸进程是什么 #

僵尸进程(Zombie Process) 是指在 Linux 或 Unix 系统中,一个已经完成执行(即已经退出)的进程,但其进程描述符(PID)还没有被父进程(Parent Process)回收的进程。简单来说,僵尸进程是一个“死了但还未被清理”的进程。

为什么会产生僵尸进程? #

当一个进程结束时,它会向父进程发送一个信号(通常是 SIGCHLD)通知它已经结束,并向操作系统申请退出状态信息。这些信息会被存储在进程表中,等待父进程通过调用 wait()waitpid() 系统调用来回收。只有当父进程回收了子进程的退出状态信息(退出码)后,进程表中的条目才会被彻底清除,这时进程才会完全消失。

如果父进程没有回收这些信息(可能是因为父进程没有调用 wait(),或者父进程提前退出等情况),子进程就会成为“僵尸进程”。这些进程会保留在系统的进程表中,占用 PID 号和一些资源,但不再占用 CPU 和内存。

僵尸进程的特征 #

  • 不消耗 CPU 资源:一旦进程终止,僵尸进程就不再执行任何代码,因此不会消耗 CPU。
  • 仍然占用进程表项:尽管已结束执行,僵尸进程仍占用一个进程号(PID),但其状态为 Z(表示 zombie)。
  • 资源被父进程占用:父进程需要回收僵尸进程的退出状态信息。如果父进程不回收,僵尸进程会持续存在。

如何查看僵尸进程? #

可以使用 ps 命令查看僵尸进程。通过添加 -e-aux 参数查看所有进程,然后使用 grep 过滤出状态为 Z 的进程。

ps aux | grep 'Z'   # 查找所有僵尸进程

或者:

ps -e -o pid,stat,cmd | grep 'Z'  # 查看所有进程及其状态,过滤出状态为 Z 的进程

在输出中,STAT 字段显示为 Z,表示该进程是一个僵尸进程。

如何解决僵尸进程? #

  1. 父进程回收子进程

    • 通过让父进程调用 wait()waitpid() 系统调用来回收子进程的退出状态。如果父进程未正确回收,则僵尸进程会持续存在。
    • 如果父进程本身已经退出,操作系统会将这些僵尸进程的父进程设置为 init 进程(PID 1),由 init 进程来回收它们。
  2. 结束父进程

    • 如果父进程不回收子进程,且父进程本身也不再需要运行,可以通过终止父进程来解决。终止父进程后,系统会自动将这些僵尸进程的父进程设置为 init 进程(PID 1),并由 init 进程回收它们。
    kill -9 <父进程PID>
    
  3. 定期清理

    • 在某些情况下,可以通过编写定时脚本,定期检查并清理僵尸进程。

总结 #

  • 僵尸进程是已经退出但没有被回收的进程,其状态是 Z
  • 它们不会占用系统资源(如 CPU 和内存),但会占用 PID 和进程表项。
  • 父进程需要回收子进程的退出信息,否则就会产生僵尸进程。
  • 通过监控和管理父进程的回收操作,可以避免僵尸进程的积累,确保系统资源得到有效利用。

进程、线程、协程区别☆ #

进程(Process)、线程(Thread)和协程(Coroutine) 是操作系统和程序设计中的重要概念,它们之间有着不同的资源管理、执行模型和使用场景。下面是它们之间的主要区别:

1. 进程(Process) #

定义: #

进程是操作系统资源分配的基本单位,它是正在执行的程序的实例。一个进程拥有独立的内存空间、数据段、代码段、堆栈等资源,操作系统为每个进程分配资源和管理进程之间的调度。

特点: #

  • 资源隔离:每个进程有自己的内存空间、文件描述符、堆栈等资源。不同进程之间是相互隔离的,一个进程不能直接访问另一个进程的内存。
  • 较重的开销:创建进程时需要大量的资源和时间开销,因为需要分配独立的内存和资源,并进行调度。
  • 上下文切换:进程切换时,操作系统需要保存当前进程的状态,并恢复下一个进程的状态,代价较大。

使用场景: #

  • 独立执行任务:适用于需要完全隔离的任务,例如运行不同的应用程序或服务。
  • 高隔离性需求:需要高度隔离的任务,比如多用户环境中的不同程序。

2. 线程(Thread) #

定义: #

线程是进程内部的执行单元,是程序执行的最小单位。多个线程可以共享进程的资源(如内存空间、文件描述符等),但每个线程有自己的栈和程序计数器(PC)。线程是操作系统调度的基本单位。

特点: #

  • 共享资源:同一进程中的线程共享进程的内存和资源,可以通过共享数据进行通信(如共享内存)。
  • 轻量级:线程相比进程占用的资源少,创建和销毁的开销小,线程之间的上下文切换比进程之间的切换快。
  • 并发执行:多个线程可以并发执行,适用于多任务处理和 CPU 密集型任务。
  • 同步与竞争:线程共享内存,容易出现同步问题(如数据竞争、死锁等),需要使用锁机制来保证线程安全。

使用场景: #

  • 需要共享内存的并发任务:比如 Web 服务器中的多个请求处理线程。
  • 轻量级任务执行:适用于需要快速响应、资源开销较小的并发任务。
  • 多核 CPU 的并行处理:当系统拥有多核 CPU 时,线程可以在多个核上并行执行,提高效率。

3. 协程(Coroutine) #

定义: #

协程是一种轻量级的线程,可以在单个线程中实现多任务的切换。协程不是由操作系统进行调度,而是由程序员手动控制执行。它的特点是非抢占式,通过显式的 yieldawait 等语法来切换执行。

特点: #

  • 轻量级:协程的创建和销毁开销极小,它们在同一个线程中运行,不需要额外的内存和上下文切换,极大减少了资源消耗。
  • 手动调度:协程由程序控制其执行顺序,协程可以在函数内保存和恢复状态,执行时切换非常快。
  • 非抢占式:与线程的抢占式调度不同,协程的切换是由程序显式控制(如通过 yieldawait 等语法)。这意味着程序可以在任意时刻决定让协程暂停并切换到其他任务。
  • 单线程运行:协程通常在单线程中运行,因此没有线程间的同步问题。

使用场景: #

  • IO 密集型任务:协程非常适合处理大量 IO 操作(如网络请求、文件读写等),因为它们可以在等待 IO 操作时切换到其他任务,充分利用 CPU。
  • 轻量级并发:当需要大量并发任务而不希望开销太大时,协程是一种理想选择。例如,Web 服务中可以使用协程来处理大量的客户端请求。
  • 无需线程上下文切换:适用于任务调度需要非常高效的场景,如一些高并发的服务器。

进程、线程、协程对比 #

特性进程线程协程
创建开销较高,资源分配和上下文切换较慢较低,线程之间共享进程的资源非常低,仅在用户空间调度,无操作系统干预
资源占用独立资源,如内存、文件句柄等共享进程的资源,但每个线程有独立栈共享线程的资源,几乎不占用额外资源
调度方式由操作系统调度(抢占式)由操作系统调度(抢占式)由程序员显式控制(非抢占式)
上下文切换代价大,需要保存和恢复大量状态比进程切换快,但仍然需要上下文切换极快,仅需保存少量状态
并发性能适合独立任务的并发执行适合 CPU 密集型并行任务适合大量轻量级并发任务,尤其是 IO 密集型
同步机制需要进程间通信(IPC)需要使用线程同步机制(如锁)无需同步,避免线程之间的数据竞争
适用场景独立任务,要求高度隔离的环境多任务并行,需共享内存的场景高效处理大量并发 IO 操作,轻量级任务

总结 #

  • 进程:是操作系统资源的基本分配单位,具有独立的内存空间,适用于需要完全隔离的任务,创建和切换开销较大。
  • 线程:是进程内部的执行单位,多个线程共享进程的资源,适用于需要高效并行处理的任务。线程之间的同步是一个常见问题。
  • 协程:是一种轻量级的并发单位,适用于大量 IO 密集型的并发任务,通过手动控制调度提供高效的并发处理。

什么是nginx的异步非阻塞 #

Nginx 的异步非阻塞 是其架构设计的核心特性之一,它使得 Nginx 能够高效地处理大量并发连接,而不需要为每个连接分配独立的线程或进程。理解这一特性有助于更好地理解 Nginx 为什么在处理大量并发请求时比传统的 Web 服务器(如 Apache)更高效。

1. 异步(Asynchronous) #

“异步”指的是处理请求时,Nginx 不会被单一请求的处理所阻塞。换句话说,当一个请求正在等待某些操作(比如从硬盘读取文件或等待数据库响应)时,Nginx 不会停下来等待这个操作完成,而是会去处理其他请求,直到该请求可以继续处理。

示例: #

  • 当用户发送一个 HTTP 请求,Nginx 会将该请求交给工作进程去处理。如果这个请求需要读取磁盘上的文件,而磁盘操作是耗时的,Nginx 不会在此时停止并等待磁盘操作完成。
  • 在等待过程中,Nginx 会处理其他到达的请求,当磁盘操作完成时,再继续处理之前请求的后续操作(比如返回文件内容给客户端)。

2. 非阻塞(Non-blocking) #

“非阻塞”指的是在处理请求时,Nginx 不会因为某个请求的某个阶段操作阻塞(阻止)其他请求的处理。Nginx 的事件循环机制确保了工作进程可以同时处理多个请求。

示例: #

  • 在处理一个请求的某个阶段时(比如读取文件、与数据库交互等),如果该操作需要一些时间,Nginx 不会阻止其他请求的处理。Nginx 会在等待这类操作时,转而处理其他可执行的任务或请求。
  • 这种非阻塞的机制可以让 Nginx 充分利用 CPU 的时间片,不会因为某个慢操作停滞不前,极大地提高了并发处理能力。

3. 事件驱动模型 #

Nginx 的异步非阻塞模式是通过事件驱动模型实现的。具体来说,Nginx 通过一个事件循环来管理多个连接,每个连接对应一个事件。当某个连接上的事件准备好(如数据已经准备好可以读取、可以发送响应等),Nginx 会通知相应的工作进程来处理这个事件,而不需要为每个请求创建一个新的线程或进程。

工作原理: #

  • 事件循环:Nginx 会启动一个事件循环,循环中会监听多个事件(如文件 I/O、网络 I/O 等)。一旦某个事件可以被处理,Nginx 就会去处理它。
  • I/O 多路复用:通过使用系统调用如 epoll(Linux)、kqueue(BSD)或 select,Nginx 能够高效地监听和处理多个 I/O 事件,而不需要创建多个线程或进程来等待每个请求的完成。

4. 单线程处理多个请求 #

Nginx 通常采用单线程处理多个请求,而不是每个请求一个线程。通过非阻塞 I/O 和事件驱动机制,Nginx 可以在一个线程内并发处理成千上万的请求,而不会像传统的多线程模型那样消耗大量的系统资源(如内存、CPU)。这种设计使得 Nginx 在处理高并发时非常高效。

5. 通过工作模式管理多个连接 #

Nginx 的工作模式(worker process)通常采用单线程、多进程模型

  • Master Process:主进程负责管理配置、进程生命周期等,子进程会根据需求启动或关闭。
  • Worker Process:每个工作进程处理客户端请求,处理过程中采用异步非阻塞方式。每个工作进程通常会有一个事件循环,处理多个连接。

6. 典型的异步非阻塞处理流程 #

假设有一个请求需要处理:

  1. 接收请求:Nginx 的 worker 进程接收到客户端的请求,解析请求信息。
  2. 执行任务:如果请求需要进行磁盘 I/O(例如读取文件),Nginx 会发出 I/O 请求,然后立即返回等待其他事件发生。
  3. 继续处理其他请求:在等待 I/O 完成的过程中,Nginx 可以继续处理其他请求,不会因为一个请求的 I/O 操作而阻塞。
  4. 完成任务:一旦磁盘 I/O 完成,Nginx 会继续处理这个请求,并返回响应给客户端。

异步非阻塞的优势 #

  • 高并发处理能力:能够同时处理大量并发请求,尤其是在 I/O 密集型操作中表现尤为出色。
  • 低资源消耗:不需要为每个请求创建独立的线程或进程,从而减少了系统的资源消耗(如内存和 CPU 时间片的浪费)。
  • 高效响应:可以迅速响应请求,特别适合高并发、高流量的场景。

总结 #

  • 异步指的是在等待某个操作(如磁盘 I/O)完成时,Nginx 不会阻塞处理其他请求。
  • 非阻塞指的是 Nginx 不会让任何一个请求阻塞其他请求的处理,能同时处理多个请求。
  • 事件驱动模型I/O 多路复用是实现这一特性的关键技术,使得 Nginx 在高并发、高流量环境下依然能够高效稳定运行。

这些特性使得 Nginx 特别适合用于 Web 服务器、反向代理以及负载均衡等需要高并发处理的场景。

linux网络丢包怎么排查☆ #

在 Linux 中,网络丢包的原因可能是多种多样的,包括硬件故障、网络配置问题、带宽过载、系统资源问题等。以下是排查网络丢包的一些常见方法和工具:

1. 使用 ping 命令测试丢包情况 #

ping 是一个简单的工具,可以帮助检测是否有丢包,并能够提供丢包的百分比。

ping -c 100 <目标IP>
  • -c 100 表示发送 100 个 ICMP 请求包。
  • 如果丢包,ping 会显示丢包的百分比。

2. 检查网络接口的丢包情况 #

通过查看网络接口的统计信息,可以了解丢包的具体情况。使用 ifconfigip -s link 命令来查看网络接口的统计数据。

使用 ifconfig#

ifconfig <网络接口>

检查输出中的 RX errors(接收错误)和 TX errors(发送错误),这两个字段表示接收和发送时的错误数量。如果存在大量的错误,可能是网络丢包的原因。

ip -s link show <网络接口>

ip 命令提供了更详细的统计信息,查看 RX droppedTX dropped 字段,这两个字段分别表示接收和发送过程中丢失的包数。

3. 查看系统日志 #

丢包可能与系统的硬件、网络驱动或其他系统问题相关。检查系统日志中是否有网络相关的错误信息。

dmesg | grep eth  # 检查与以太网相关的日志
  • 如果发现大量的与网卡相关的错误(如“RX errors”或“TX errors”),可能是硬件问题。
  • 也可以检查 /var/log/syslog/var/log/messages 等日志文件。

4. 使用 netstat 查看网络连接情况 #

netstat 命令可以帮助你查看当前网络连接的状态,并能检查是否有异常的连接导致丢包。

netstat -s
  • 查看是否有大量的 TCP 错误或其他协议错误,特别是 TCP 的 Retransmissions(重传次数)和 Segs Out(发出的段数)字段。

5. 使用 traceroute 检查网络路径 #

如果怀疑丢包发生在与外部服务器之间的网络传输过程中,可以使用 traceroute 工具来检查数据包经过的路由路径,以及每跳的丢包情况。

traceroute <目标IP>
  • traceroute 可以帮助你识别哪个路由节点存在丢包。

6. 查看系统资源是否不足 #

网络丢包可能是因为系统的资源不足(如 CPU 或内存)导致网络处理变慢或缓慢。使用以下命令检查系统资源:

CPU 使用情况: #

top  # 或者 htop

查看系统是否有高负载,特别是网络相关的进程是否占用过多 CPU。

内存使用情况: #

free -m

查看系统内存是否已经使用完,尤其是交换分区(swap)是否被过度使用。

磁盘使用情况: #

iostat -x 1

查看磁盘的 I/O 性能,是否出现瓶颈,影响到网络通信。

7. 调整 TCP 缓冲区 #

TCP 缓冲区的设置不当也可能导致丢包,尤其是在高带宽、低延迟的网络环境下。如果丢包发生在长时间的数据传输中,可以尝试调整 TCP 缓冲区大小。

可以通过以下命令查看和调整系统的 TCP 缓冲区:

sysctl -a | grep net.ipv4.tcp_rmem  # 查看接收缓冲区
sysctl -a | grep net.ipv4.tcp_wmem  # 查看发送缓冲区

调整 TCP 接收和发送缓冲区大小:

sysctl -w net.ipv4.tcp_rmem="4096 87380 6291456"
sysctl -w net.ipv4.tcp_wmem="4096 16384 4194304"

8. 使用 ss 工具进行网络连接分析 #

ss 是一个用于查看套接字连接的命令,可以用于检查网络连接的状态和是否有大量的丢包。

ss -s
  • 这将显示各种协议的统计信息,特别是 TCP 连接的状态。

9. 检查网卡驱动和硬件问题 #

如果发现网络丢包是由硬件故障引起的,可以通过以下方法进行排查:

  • 检查网络接口卡(NIC)是否存在硬件故障,尝试更换网卡。
  • 查看网络驱动程序是否有更新,更新驱动程序。
  • 检查网络设备(如交换机、路由器)是否存在故障或配置错误。

10. 网络带宽和流量限制 #

网络带宽不足、流量过载或 QoS(服务质量)策略设置不当,也可能导致丢包。通过以下方式检查带宽和流量限制:

  • 检查是否有带宽限制(如防火墙规则、流量整形等)。
  • 使用 iftopnload 等工具查看当前网络流量的实时状况。
iftop  # 实时流量监控
nload  # 实时网络带宽监控

总结 #

  • 基本排查方法:使用 pingifconfigip -s link 等工具检查丢包情况。
  • 系统资源监控:使用 topfreeiostat 等工具检查系统资源是否正常。
  • 网络路径分析:使用 traceroute 来查看是否有网络路径丢包。
  • 调整参数:调整 TCP 缓冲区等参数可能有助于减少丢包。

通过这些方法,逐步排查网络丢包的原因,通常可以定位到具体问题并进行修复。

常用的性能分析诊断命令☆ #

在 Linux 系统中,进行性能分析和诊断时,有许多命令和工具可以帮助你查看系统资源使用情况、诊断性能瓶颈、排查问题等。以下是一些常用的性能分析和诊断命令:

1. top #

top 是最常用的性能分析工具,它显示实时的系统资源使用情况,包括 CPU、内存、磁盘、网络等。它适用于检查系统的整体状态,特别是资源占用较高的进程。

  • 用法

    top
    
  • 功能

    • 显示进程的实时资源占用情况。
    • 可以按 CPU、内存等资源占用排序。
    • 使用 PM 可以按 CPU 或内存使用量排序。
    • 使用 1 显示每个 CPU 的使用情况。

2. htop #

htoptop 的增强版,提供了更友好的界面和交互方式。它可以显示更多的系统信息,并允许用户通过键盘快捷键方便地进行操作。

  • 用法

    htop
    
  • 功能

    • 显示进程树状图。
    • 可以通过上下箭头选择进程并对其进行操作(如杀死进程)。
    • 支持按 CPU、内存、PID 等排序。

3. vmstat #

vmstat(Virtual Memory Statistics)可以用来报告关于虚拟内存、进程、CPU 活动、磁盘 I/O 等信息。它常用于查看系统性能瓶颈。

  • 用法

    vmstat 1 5  # 每 1 秒输出一次,共输出 5 次
    
  • 功能

    • 输出内存、交换空间、进程、CPU 使用等统计信息。
    • 重点查看 procsmemorycpu 部分,来判断是否存在瓶颈。

4. iostat #

iostat(Input/Output Statistics)可以显示系统的 I/O 性能,帮助诊断磁盘或存储系统的性能问题。它显示设备的 I/O 统计信息、CPU 使用情况等。

  • 用法

    iostat -x 1 5  # 每 1 秒输出一次,共输出 5 次,显示详细统计
    
  • 功能

    • 查看每个磁盘设备的 I/O 活动、吞吐量、I/O 队列长度等。
    • 监控磁盘的延迟、吞吐量等性能数据,帮助诊断存储瓶颈。

5. netstat #

netstat(Network Statistics)用于查看网络连接、路由表、接口统计等信息。它帮助诊断网络连接、带宽使用情况等。

  • 用法

    netstat -tulnp  # 查看所有正在监听的端口及其对应的进程
    
  • 功能

    • -tulnp:显示 TCP、UDP、监听端口和对应的进程。
    • -s:显示网络协议的统计信息,如 TCP 错误、接收/发送丢包等。

6. ss #

ss(Socket Statictics)是一个比 netstat 更快的替代工具,用于显示网络连接、套接字统计等信息。它支持查看 TCP、UDP 等连接状态。

  • 用法

    ss -s  # 显示套接字的总览信息
    ss -tuln  # 查看监听的 TCP/UDP 端口
    
  • 功能

    • 显示当前的网络连接、监听的端口等。
    • 支持筛选显示特定状态的连接,如 ESTAB(已建立连接)或 LISTEN(监听连接)。

7. sar #

sar(System Activity Report)是一个非常强大的性能监控工具,它通过收集系统性能数据来生成报告。它可以显示系统的 CPU 使用、内存、I/O、网络等资源的历史数据。

  • 用法

    sar -u 1 5  # 每 1 秒报告一次 CPU 使用情况,共 5 次
    
  • 功能

    • 显示过去的系统性能历史数据。
    • sar -u:显示 CPU 使用情况。
    • sar -n DEV:查看网络接口统计。
    • sar -r:查看内存使用情况。

8. dstat #

dstat 是一个功能强大的多功能监控工具,它可以同时显示 CPU、内存、磁盘、网络、IO 等多项系统资源的使用情况。

  • 用法

    dstat -cdngy  # 同时查看 CPU、磁盘、网络、磁盘 I/O 等信息
    
  • 功能

    • 提供全面的系统资源使用信息。
    • 支持实时显示各种指标,适用于综合性能监控。

9. strace #

strace 用于追踪系统调用,可以帮助诊断程序在执行过程中遇到的问题,如文件打开、网络请求等系统调用。

  • 用法

    strace -p <PID>  # 跟踪指定进程的系统调用
    
  • 功能

    • 记录进程的系统调用,可以帮助诊断文件 I/O、网络连接等操作是否正常。
    • 适用于调试程序和排查性能瓶颈。

10. perf #

perf 是 Linux 提供的一个性能分析工具,用于分析系统的 CPU 性能、缓存命中率、上下文切换等低级性能指标。

  • 用法

    perf top  # 查看实时的性能瓶颈,类似于 `top`,但可以显示更多的系统信息
    perf record -g ./my_program  # 记录程序的性能数据
    
  • 功能

    • 通过采样来分析程序的 CPU 使用情况。
    • 可以查看哪些函数、代码段消耗了最多的 CPU 时间,帮助找出性能瓶颈。

11. lsof #

lsof(List Open Files)用于列出系统中所有打开的文件,包括网络连接、文件句柄等。可以用于分析文件或网络资源的使用情况。

  • 用法

    lsof -i :80  # 查看占用 80 端口的进程
    lsof -p <PID>  # 查看指定进程打开的文件
    
  • 功能

    • 显示打开的文件及其相关的进程。
    • 查找占用特定端口的进程,诊断端口冲突问题。

12. iotop #

iotop 是一个实时的磁盘 I/O 使用情况监控工具,它可以显示哪些进程在消耗磁盘带宽。

  • 用法

    iotop
    
  • 功能

    • 显示每个进程的磁盘 I/O 使用情况。
    • 帮助排查磁盘 I/O 性能瓶颈。

13. mpstat #

mpstat 用于显示每个 CPU 的使用情况,特别适用于多核 CPU 系统。

  • 用法

    mpstat -P ALL 1 5  # 显示每个 CPU 核心的使用情况
    
  • 功能

    • 显示各个 CPU 核心的负载情况,有助于诊断 CPU 是否存在瓶颈。

总结 #

  • 系统资源使用分析top, htop, vmstat, iostat, dstat
  • 网络性能分析netstat, ss, sar
  • 磁盘 I/O 分析iotop, iostat
  • 应用级性能分析strace, perf
  • 系统调用分析strace, perf
  • 打开文件和资源分析lsof

这些命令和工具可以帮助你从不同角度进行系统性能分析和故障诊断。根据不同的需求和问题,你可以选择合适的工具来深入排查和分析系统性能。

什么是进程中断 #

进程中断是指在操作系统中,进程执行过程中,系统主动暂停其执行,并根据一定的规则进行某些操作(如处理中断请求),然后再恢复进程的执行。中断通常由硬件、操作系统内核或其他软件引发,它会打断当前进程的执行,优先处理某些特定的任务或事件,确保系统能够高效地响应外部或内部事件。

中断是现代操作系统中非常重要的机制,它能够实现高效的资源共享、事件响应和进程调度。

进程中断的种类 #

  1. 硬件中断
    • 由外部硬件设备(如磁盘、键盘、鼠标、网络卡等)发起。
    • 例如,当硬盘完成数据读取时,硬盘控制器会发出中断,通知 CPU 进行下一步操作。
    • 设备中断通常由外部事件触发,像网络卡接收到数据包时,会触发网络中断。
  2. 软件中断
    • 由软件程序发起,通常是为了实现系统调用或请求操作系统服务。
    • 例如,系统调用(如 read(), write())时,程序会发起软件中断,切换到内核模式以执行特权操作。
    • 程序可以使用特殊的指令来发起软件中断,例如 int 0x80 在 Linux 中用于触发系统调用。
  3. 定时器中断
    • 由系统定时器触发,通常用于时间片轮转、进程调度等。
    • 定时器中断是操作系统进行进程管理和资源分配的重要手段。例如,操作系统通过定时器中断来定期检查是否需要切换进程,确保公平的 CPU 时间分配。
  4. 外部中断
    • 外部事件引发的中断,如来自外部设备的中断请求。
    • 例如,按下键盘时,键盘控制器会产生外部中断来通知操作系统读取键盘输入。
  5. 内部中断
    • 由 CPU 内部状态或执行错误触发,例如除零错误、非法指令等。
    • 例如,程序试图除以零时,CPU 会触发一个中断,处理器将跳转到错误处理程序。

进程中断的工作流程 #

  1. 中断请求
    • 中断发生时,外部设备(如硬盘、键盘、网络适配器等)或内部硬件(如计时器)会发出中断请求信号,通知 CPU 需要处理中断。
  2. 保存上下文
    • 当中断发生时,操作系统首先会保存当前进程的执行状态(即上下文),包括程序计数器(PC)、寄存器值、堆栈指针等,以便稍后能够恢复执行。
  3. 中断处理
    • 操作系统内核根据中断类型进行相应的处理中断程序(中断服务程序,ISR)。这可能涉及数据读取、设备驱动程序的调用或执行其他操作。
    • 例如,硬件中断可能导致操作系统读取硬盘数据,定时器中断可能导致操作系统调度下一个进程。
  4. 恢复执行
    • 中断处理完成后,操作系统会恢复被中断进程的上下文,继续执行该进程。中断后的程序通常会从中断发生之前的地方继续执行。

中断的优先级和中断屏蔽 #

在操作系统中,中断通常有优先级的概念。高优先级的中断会先处理,而低优先级的中断会在高优先级中断处理完之后才进行。

  • 中断屏蔽:操作系统可以在某些时刻禁止或屏蔽某些类型的中断,以保证某些重要操作的完成。例如,操作系统在进行进程切换时可能会暂时屏蔽中断,以避免在关键时刻被打断。
  • 嵌套中断:有些情况下,高优先级的中断可以打断低优先级中断的处理过程,称为嵌套中断。操作系统通常会处理完当前高优先级中断后,再去处理低优先级的中断。

进程中断的作用 #

  1. 提高系统响应性
    • 中断机制可以在任何时刻打断正在执行的进程,优先响应外部设备的请求,使得系统能够及时处理硬件设备或外部事件的变化。
    • 例如,在实时操作系统中,中断机制确保能够快速响应外部事件(如传感器数据、用户输入等)。
  2. 实现多任务并行
    • 中断使得操作系统能够迅速切换不同进程的执行状态,配合进程调度算法实现 CPU 时间的公平分配。
    • 例如,当一个进程处于 I/O 阻塞时,操作系统通过中断机制能够迅速调度其他进程,最大化系统资源的利用率。
  3. 设备驱动与外设管理
    • 通过中断,操作系统可以有效地与外设进行交互。当设备完成某项任务时,它会触发中断请求,操作系统可以响应并执行相应操作(如读取数据、发送数据等)。
    • 例如,磁盘 I/O 中断会通知操作系统磁盘读取操作已完成,网络中断通知操作系统收到网络数据包。
  4. 优化系统性能
    • 通过中断,系统可以减少空闲等待的时间,进程不需要轮询硬件状态,而是等到硬件完成任务后再被中断处理。
    • 例如,在网络通信中,进程可以等待中断信号,以避免频繁轮询网络接口。

总结 #

  • 进程中断 是操作系统中一种重要的机制,它能够在进程执行过程中打断当前的执行流,优先处理一些外部或内部的事件,并在中断处理完毕后恢复进程的执行。
  • 中断分为硬件中断、软件中断、定时器中断等多种类型。
  • 通过中断,操作系统可以实现高效的设备管理、进程调度和系统响应,从而提高系统的性能和响应速度。

进程中断是操作系统多任务处理的核心部分,极大地增强了系统的灵活性和响应能力。

什么是软中断、硬中断 #

在计算机系统中,中断 是一种机制,它允许外部或内部事件打断正在运行的程序,以便系统可以处理中断请求。在操作系统中,中断可以分为 软中断硬中断,它们的触发来源和作用有所不同。以下是两者的详细解释:

1. 硬中断 (Hardware Interrupt) #

硬中断 是由硬件设备触发的中断信号,通常是外部设备(如磁盘、网络适配器、键盘、鼠标等)发出的信号,用于通知 CPU 需要处理某个事件。硬中断的目的是让操作系统能够实时响应外部设备的变化或事件,例如数据准备完成、输入信号等。

特点 #

  • 触发来源:硬中断是由外部硬件设备(如外设、控制器等)触发的。
  • 高优先级:硬中断的优先级较高,当硬件设备发出中断信号时,CPU 会停止当前的任务,立即响应中断。
  • 硬件中断号:硬件中断通常有固定的中断号,例如中断控制器会分配不同的中断号来标识不同的设备。
  • 中断处理程序:硬中断会跳转到中断服务例程(ISR,Interrupt Service Routine),在该例程中,操作系统会处理硬件请求(如数据读取、状态更新等)。

示例 #

  • 磁盘 I/O 完成:硬盘在完成数据读写后发出中断,通知操作系统该操作已完成,操作系统会读取数据并继续处理。
  • 键盘输入:键盘输入一个按键时,键盘会发出中断,通知 CPU 处理按键输入事件。
  • 定时器中断:系统定时器发出中断,操作系统会进行进程调度,切换当前进程。

处理流程 #

  1. 硬件设备通过中断控制器向 CPU 发送中断信号。
  2. CPU 停止当前进程,保存当前进程的状态。
  3. CPU 跳转到硬中断的中断服务例程(ISR)来处理该硬件设备的请求。
  4. 处理完成后,CPU 恢复原进程的执行。

2. 软中断 (Software Interrupt) #

软中断 是由程序或操作系统通过软件指令触发的中断,通常用于系统调用或操作系统内核执行一些特权操作。软中断的主要目的是提供一种程序在用户态与内核态之间切换的机制,使得用户程序能够请求操作系统提供的服务。

特点 #

  • 触发来源:软中断由程序发起,通常通过特定的指令(如 int 指令)触发。
  • 低优先级:软中断的优先级较低,一般是在程序主动请求系统服务时触发。
  • 中断号:软中断使用一个软件中断号,通常由操作系统定义,用于区分不同类型的软中断。
  • 系统调用:软中断是系统调用的一部分,用户程序通过触发软中断进入内核态,向操作系统请求服务。

示例 #

  • 系统调用:用户程序调用 read()write() 等系统调用时,操作系统通过软中断进入内核态执行相应的操作。
  • 错误处理:程序发生错误时,操作系统可能通过软中断处理异常,例如除零错误或非法操作。

处理流程 #

  1. 用户程序通过 int 指令或类似机制发出软中断请求。
  2. CPU 暂停当前程序,保存进程的状态。
  3. CPU 跳转到软中断的中断服务例程(通常是操作系统的内核代码)。
  4. 操作系统根据中断号执行相应的系统调用或内核操作。
  5. 处理完成后,操作系统返回用户程序并恢复执行。

硬中断与软中断的区别 #

特性硬中断 (Hardware Interrupt)软中断 (Software Interrupt)
触发方式外部硬件设备触发程序通过特定指令触发
优先级高优先级低优先级
常见触发源硬件设备(如磁盘、网络卡、定时器等)用户程序或操作系统内部
目的响应外部事件或设备请求进入内核态执行系统调用或处理错误
例子磁盘 I/O 完成、定时器中断、键盘输入系统调用(如 read()write()

应用场景和作用 #

  • 硬中断:硬中断主要用于处理外部设备事件,确保操作系统能够及时响应硬件变化,如 I/O 完成、外设信号等。这使得操作系统能够高效地进行多任务处理和设备管理。
  • 软中断:软中断用于程序与操作系统内核之间的交互,提供了用户态和内核态的切换机制。通过软中断,程序能够请求操作系统提供的服务(如文件操作、内存分配等),并且能够处理程序错误或异常。

总结 #

  • 硬中断 由硬件设备触发,用于及时响应外部事件,优先级较高,通常需要立即处理中断请求。
  • 软中断 由程序通过指令触发,用于用户程序与操作系统之间的交互,主要用于系统调用和错误处理。

这两者共同作用,确保了操作系统能够高效地管理硬件资源、响应外部事件,并且提供用户程序与内核之间的协作机制。

什么是不可中断进程 #

不可中断进程(也称为 D状态进程不可打断进程)是指在运行过程中无法被操作系统的调度程序中断或打断的进程。通常,这种进程在执行某些特定操作时,系统不允许其他进程或信号中断其执行,确保其完成当前的任务。

在 Linux 和类 Unix 系统中,进程的状态通常会在 /proc 目录下的进程状态文件中查看,其中有一个字段表示进程的状态。不可中断进程的状态通常是 D,即 不可中断睡眠(Uninterruptible Sleep),表示进程正在等待某些资源(如 I/O 操作、磁盘读取、网络请求等),并且在完成这些操作之前,不能被任何信号打断。

1. 不可中断进程的特点 #

  • 无法响应信号:当一个进程处于不可中断状态时,操作系统不会响应它的信号(比如 SIGKILLSIGTERM 等)。这意味着即使你尝试通过命令(如 kill)终止这个进程,它也无法被中断或杀死,直到进程的当前操作完成。

  • 等待 I/O 操作:不可中断进程通常出现在等待某些硬件 I/O 操作完成时,如硬盘读写、网络通信等。进程处于此状态时,CPU 无法调度它,直到 I/O 操作结束,进程才会恢复执行。

  • 系统资源占用:由于进程在等待 I/O 操作或资源,它会占用一定的系统资源,直到操作完成。

  • 进程状态:通过 pstop 等命令查看进程时,进程会显示为 D 状态。这是不可中断进程的标志。例如:

    ps aux | grep <pid>
    

    在输出中,进程的状态会显示为 D,表示该进程正在不可中断的等待中。

2. 不可中断进程的常见原因 #

不可中断进程通常是由于以下几种原因导致的:

1. I/O 操作 #

  • 当进程在进行硬件 I/O 操作时,比如磁盘读取、网络通信或等待从设备获取数据时,它会进入不可中断状态。
  • 例如,进程请求读取硬盘上的数据时,如果磁盘正在忙碌或者出现延迟,进程可能会进入不可中断状态,直到磁盘操作完成。

2. 文件系统操作 #

  • 如果进程正在访问一个挂载的文件系统(如读取文件),且该文件系统出现问题(如磁盘故障、挂载点不可访问等),进程可能会长时间处于不可中断状态。

3. 设备驱动程序问题 #

  • 如果操作系统或内核中的设备驱动程序存在问题(例如死锁或资源争用),可能导致进程进入不可中断状态,无法完成操作。

4. 网络通信 #

  • 网络相关的操作,如等待网络数据包,尤其是在高延迟或网络中断的情况下,进程可能会长时间处于不可中断状态,直到网络通信恢复。

3. 不可中断进程的处理 #

  • 进程卡死:不可中断进程通常会表现为“卡死”,因为它们无法被杀死或中断,无法响应信号。在这种情况下,如果不可中断进程长时间不恢复,系统管理员需要调查进程为何无法中断,通常需要查看系统日志、硬件状况或相关驱动程序问题。
  • 解决方法
    • 检查硬件或设备问题:如果进程处于不可中断状态且是由硬件问题(如磁盘故障、网络中断等)引起的,解决问题通常需要恢复硬件功能或修复设备问题。
    • 查看内核日志:内核日志文件(如 /var/log/messagesdmesg)可能包含导致进程无法中断的错误信息。例如,如果是因为硬件故障或驱动问题,内核日志中可能有相关错误提示。
    • 重启系统:在某些情况下,无法通过普通手段终止不可中断进程。如果进程导致系统资源耗尽或卡死,可能需要重启计算机来清理这些进程。

4. 不可中断进程的例子 #

以下是一些常见的导致进程进入不可中断状态的情景:

  • 硬盘故障:例如,进程正在读取磁盘数据,而磁盘出现故障或延迟时,进程可能会卡在等待磁盘响应的状态。
  • 网络阻塞:如果进程正在进行网络请求,且网络连接出现问题(例如目标服务器不可达,或者网络拥塞),进程可能进入不可中断状态,直到网络恢复。
  • 驱动程序错误:某些设备驱动程序存在 bug 或设计缺陷时,可能导致进程无法继续执行,直到该驱动的操作完成。

5. 如何排查不可中断进程 #

  • 使用 ps 命令查看进程状态: 你可以使用 pstop 命令查看进程的状态,确认是否有进程处于 D 状态:

    ps aux | grep D
    

    或使用 top 命令查看所有进程的状态:

    top
    

    如果进程处于 D 状态,表示它处于不可中断状态。

  • 查看系统日志: 系统日志中可能有有关进程卡死的原因。例如:

    dmesg | tail -n 100
    
  • 检查硬件设备: 确保没有硬件故障,尤其是磁盘、网络等关键硬件。

  • 重新启动进程: 如果无法终止不可中断进程,可以尝试重新启动相关进程或重启系统来恢复正常运行。

总结 #

不可中断进程是由于进程正在等待某些资源(如 I/O 操作、硬件设备响应等)而不能被中断的进程。它们通常处于 D 状态,且无法响应信号。通常,硬件问题、驱动程序问题或资源争用可能导致进程进入这种状态。在这种情况下,管理员需要调查硬件、驱动或网络等相关问题,必要时重启系统来恢复正常。

什么是栈内存和堆内存 #

栈内存(Stack Memory)和堆内存(Heap Memory)是程序运行过程中用于存储数据的两种不同类型的内存区域,它们有各自不同的管理方式和使用场景。

1. 栈内存 (Stack Memory) #

栈内存是一种存储临时数据的内存区域,通常由操作系统自动管理。当一个程序调用一个函数时,系统会为该函数在栈上分配内存,用于存储局部变量和函数调用的返回地址。栈内存是 LIFO(后进先出) 的结构,也就是说,后入栈的数据会先被弹出。

特点: #

  • 自动管理:栈内存的分配和释放是自动的。当一个函数调用时,局部变量和函数参数会被压入栈中,函数返回时,这些局部变量和参数会自动被弹出栈,内存空间会被释放。
  • 快速分配与释放:栈内存的分配和释放速度非常快,因为它是基于栈顶的操作,分配内存只需要简单地向栈顶“压栈”,释放内存则是“弹栈”。
  • 大小有限:栈内存的大小通常是有限的,一般由操作系统决定。如果栈空间用尽(例如递归调用过深),就会发生 栈溢出(Stack Overflow)错误。
  • 存储数据:栈内存用于存储局部变量、函数参数和函数调用的返回地址等。

适用场景: #

  • 局部变量:函数内部的局部变量会存储在栈内存中。它们的生命周期仅限于函数执行期间,函数执行完毕后,栈中的数据会自动销毁。
  • 函数调用:每次函数调用时,栈会为该函数分配内存空间,保存该函数的局部变量、返回地址等信息。

示例: #

void foo() {
    int x = 10;  // 局部变量 x 存储在栈中
    int y = 20;  // 局部变量 y 存储在栈中
}

在这个例子中,xy 都是局部变量,存储在栈内存中,函数 foo 调用时,它们被压入栈中,函数执行完毕后,它们的内存会被自动释放。


2. 堆内存 (Heap Memory) #

堆内存是一块由程序员控制的内存区域,用于动态分配内存。堆内存的大小在程序运行时可以根据需要动态扩展,适用于存储那些需要在多个函数或不同地方之间共享的较大的数据结构。

特点: #

  • 手动管理:与栈内存不同,堆内存的分配和释放是由程序员手动管理的。在 C/C++ 中,程序员使用 mallocfreenewdelete 等操作来分配和释放堆内存。如果程序员忘记释放堆内存,就会发生 内存泄漏(Memory Leak),导致程序占用越来越多的内存。
  • 较慢的分配与释放:堆内存的分配和释放速度比栈内存慢,因为它需要通过更复杂的算法来管理内存(例如,查找空闲空间、合并已释放内存等)。
  • 较大的内存空间:堆内存的大小通常仅受系统内存大小的限制,相对于栈内存,堆内存更大,因此适合存储大规模数据。
  • 动态内存:堆内存的大小可以在程序运行时动态决定,适合存储那些大小无法在编译时确定的数据。

适用场景: #

  • 动态分配的数据结构:例如,链表、树、图等动态数据结构,它们的大小在程序运行时才能确定,因此需要使用堆内存。
  • 对象:在面向对象编程中,通常使用堆内存来动态创建对象,以便在多个函数之间共享或延长对象的生命周期。

示例: #

void foo() {
    int* ptr = (int*)malloc(sizeof(int));  // 在堆上分配内存
    *ptr = 10;  // 访问堆上的内存
    free(ptr);  // 手动释放堆内存
}

在这个例子中,ptr 是一个指向堆内存的指针,程序使用 malloc 分配了一块内存,并在堆中存储一个整数 10。在使用完堆内存后,程序需要手动调用 free 来释放这块内存。


栈内存与堆内存的区别 #

特性栈内存堆内存
分配方式自动分配,随函数调用和返回自动管理程序员手动分配和释放
内存大小相对较小,有限,通常几 MB 到几十 MB大且灵活,仅受系统内存限制
生命周期函数调用时分配,函数结束时自动销毁持续到程序员显式释放(freedelete
分配与释放速度快速相对较慢
访问速度较快,CPU 缓存优化较慢,堆内存的管理较复杂
适用场景局部变量、函数调用、递归动态分配内存、对象、动态数据结构等
内存管理问题栈溢出(如递归过深)内存泄漏(未释放堆内存)

栈内存与堆内存的选择 #

  • 栈内存:适合用于存储生命周期短、大小固定的局部数据。栈内存是自动管理的,且访问速度非常快,但它的大小是有限的,不适合存储大量的数据或需要在多个函数之间共享的数据。
  • 堆内存:适合用于存储动态分配的、生命周期较长或大小不确定的数据。堆内存的管理较为复杂,程序员需要手动分配和释放内存,且可能会遇到内存泄漏的问题。

总结 #

  • 栈内存 用于存储局部变量和函数调用等临时数据,由系统自动管理,速度快但大小有限。
  • 堆内存 用于存储动态分配的较大数据,程序员需要手动管理,适用于存储动态数据结构和对象。

top 命令里面可以看到进程哪些状态☆ #

在 Linux 系统中,使用 top 命令可以实时查看系统资源的使用情况,包括 CPU、内存、进程等信息。在 top 命令的输出中,进程的状态通常显示在 STAT(状态)这一列中。每个进程有一个对应的状态字母,表示该进程的当前状态。不同的字母代表不同的进程状态。

进程状态的常见字母及其含义: #

  1. R (Running) — 运行中
    • 该进程正在运行或在就绪队列中等待运行。通常,进程正在执行或准备执行。
  2. S (Sleeping) — 睡眠中
    • 该进程处于休眠状态,等待某些事件或条件(如 I/O 完成)。这通常是大部分进程的状态,它们在等待资源时会进入此状态。
  3. D (Uninterruptible Sleep) — 不可中断睡眠
    • 该进程处于不可中断睡眠状态,通常是因为它正在等待 I/O 操作完成,如磁盘读取或网络请求。进程无法被任何信号打断,只有当操作完成时,进程才会继续执行。如果进程长时间处于该状态,可能表示 I/O 阻塞或设备故障。
  4. T (Stopped) — 停止
    • 该进程已经停止运行。可能是因为它接收了 SIGSTOP 信号,或者正在调试过程中。你可以使用 kill 命令发送 SIGCONT 信号恢复该进程。
  5. Z (Zombie) — 僵尸
    • 该进程已经完成执行,但它的父进程尚未调用 wait()waitpid() 来读取它的退出状态。僵尸进程占用进程表项,但不占用 CPU 时间。
  6. I (Idle) — 空闲
    • 在某些系统中,I 状态表示进程处于空闲状态,CPU 处于空闲模式。但一般来说,top 中没有明确的 I 状态,通常会看到 RS
  7. X (Dead) — 死亡
    • 该进程已经死亡并且不再存在,通常系统将其从进程表中移除。X 状态是比较少见的,通常表示系统中某些资源的异常状态。
  8. W (Paging) — 页面调度
    • 进程正在被操作系统调度来进行页面交换(Paging),即进程内存正在被交换到磁盘或从磁盘加载。

top 命令输出中的进程状态字段示例#

PID  USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
1234 user1     20   0  10000  4000  3000 S  1.0  0.1   0:05.67 myapp
5678 user2     20   0  20000  8000  6000 D  0.5  0.2   0:02.14 anotherapp
9012 user3     20   0  15000  6000  5000 Z  0.0  0.0   0:00.01 myzombie

SDZ 状态的具体说明#

  • S(睡眠中):这通常是一个进程在等待某个事件(如等待 I/O)发生时的状态。它可能会在等待磁盘、网络、文件描述符等资源时处于这种状态。大多数进程在没有执行任务时都处于睡眠状态。
  • D(不可中断睡眠):这是进程在执行某些 I/O 操作时,无法被外部信号打断的状态。例如,等待磁盘操作或网络数据传输的完成。进程一旦完成这些操作,它就会变为 S 或其他状态。如果进程长时间处于 D 状态,可能意味着系统的硬件资源(如磁盘、网络等)出现了瓶颈或故障。
  • Z(僵尸进程):僵尸进程是已经结束的进程,但它的父进程没有收集它的退出状态,仍然保留在进程表中。它占用系统资源(进程 ID),但是不占用 CPU 和内存,通常会在父进程调用 wait()waitpid() 时清理掉。

进程状态总结: #

状态字母描述说明
RRunning(运行中)进程正在执行或准备执行。
SSleeping(睡眠中)进程处于等待状态(如 I/O)。
DUninterruptible Sleep(不可中断睡眠)进程在等待 I/O 操作,不能被打断。
TStopped(停止)进程已停止,可以通过 SIGCONT 恢复。
ZZombie(僵尸进程)进程已终止,但父进程未回收退出状态。
IIdle(空闲)进程处于空闲状态。
XDead(死去)进程已死亡,通常系统清理后会消失。
WPaging(页面调度)进程处于页面交换操作中。

其他补充: #

  • top 命令中的进程状态可以通过按下 Shift + P 来查看,也可以通过 Shift + M 查看内存使用情况。
  • 不同的 Linux 发行版和版本的 top 命令可能会有所不同,状态字母可能有细微差异,但大体相同。

理解进程的不同状态可以帮助你诊断系统中的问题,如发现进程长时间处于 DZ 状态,可能需要进一步调查资源瓶颈或系统配置问题。

Linux 系统中/proc是做什么的 #

在 Linux 系统中,/proc 目录是一个虚拟文件系统(virtual filesystem),它提供了内核与系统信息的访问接口。通过 /proc,你可以查看操作系统内核、进程、硬件、内存等的实时信息。实际上,/proc 并不存储真正的文件,而是由内核动态生成的虚拟文件,这些文件的内容反映了当前系统的状态。

/proc 目录的主要作用 #

  1. 提供系统和进程信息 /proc 包含了大量的虚拟文件和目录,用户可以通过查看这些文件来获得系统运行时的各种信息,比如进程信息、内存状态、硬件配置等。
  2. 系统监控和调试 /proc 中的许多文件允许系统管理员和程序员获取系统运行时的状态。这些文件对于性能监控、故障排除以及系统调试非常有用。
  3. 与内核交互 /proc 目录是与内核交互的一个重要接口,可以通过它修改一些内核参数或动态调整系统设置。

/proc 目录中的重要文件和子目录 #

  1. /proc/[pid] 这是进程信息目录,其中 [pid] 是一个数字,表示进程的 ID。每个进程都有一个对应的目录,在该目录下有许多文件,可以获取到进程的详细信息,例如:

    • /proc/[pid]/cmdline:进程的命令行
    • /proc/[pid]/status:进程的状态、内存、IO等信息
    • /proc/[pid]/stat:进程的统计信息,包括 CPU 时间、内存使用等
    • /proc/[pid]/fd/:该进程打开的文件描述符列表
  2. /proc/cpuinfo 包含了有关 CPU 的详细信息,如型号、核数、频率等。查看该文件可以获取当前系统 CPU 的配置信息。

    cat /proc/cpuinfo
    
  3. /proc/meminfo 提供有关内存的详细信息,包括总内存、已用内存、空闲内存、缓存、交换空间等。

    cat /proc/meminfo
    
  4. /proc/uptime 显示系统启动以来的运行时间,以及空闲时间(单位:秒)。

    cat /proc/uptime
    
  5. /proc/partitions 显示磁盘分区信息,包括设备、分区大小等。可以查看系统上的磁盘设备和分区情况。

    cat /proc/partitions
    
  6. /proc/loads 显示系统的负载平均值。包括过去 1 分钟、5 分钟和 15 分钟的平均负载。

    cat /proc/loadavg
    
  7. /proc/filesystems 列出系统支持的文件系统类型,例如 ext4btrfs 等。

    cat /proc/filesystems
    
  8. /proc/net/ 该目录下包含了与网络相关的虚拟文件,提供网络配置、连接信息等。例如:

    • /proc/net/tcp:TCP 连接信息
    • /proc/net/udp:UDP 连接信息
    • /proc/net/dev:网络接口统计信息
  9. /proc/sys/ 包含了可用于调整内核参数的虚拟文件,用户可以在运行时通过修改这些文件来调整系统行为。例如:

    • /proc/sys/net/ipv4/ip_forward:控制 IP 转发
    • /proc/sys/vm/swappiness:控制交换空间的使用策略
    • /proc/sys/kernel/hostname:查看或修改系统主机名

    例如,查看和设置虚拟内存交换策略:

    cat /proc/sys/vm/swappiness
    echo 10 > /proc/sys/vm/swappiness
    
  10. /proc/version 显示当前操作系统内核的版本信息。

    cat /proc/version
    

使用 /proc 的常见场景 #

  • 查看系统资源使用情况:可以通过查看 /proc/meminfo/proc/cpuinfo/proc/uptime 等文件,了解系统的内存、CPU 使用情况以及系统的运行时间。
  • 监控进程信息:每个进程都有对应的 /proc/[pid] 目录,你可以用来实时监控进程的状态、CPU 使用情况、内存使用情况等。
  • 动态调整系统参数:可以通过 /proc/sys/ 下的文件动态修改内核参数或配置,比如改变网络配置、调整虚拟内存管理等。
  • 排查故障和调试:系统管理员可以通过分析 /proc 中的数据来诊断系统问题或监控异常行为,例如检查进程状态、文件系统、网络连接等。

示例 #

  1. 查看 CPU 信息

    cat /proc/cpuinfo
    
  2. 查看内存使用情况

    cat /proc/meminfo
    
  3. 查看当前系统的负载情况

    cat /proc/loadavg
    
  4. 查看当前系统运行时间

    cat /proc/uptime
    
  5. 查看系统支持的文件系统类型

    cat /proc/filesystems
    

总结 #

  • /proc 目录是 Linux 系统中的一个虚拟文件系统,它提供了对系统运行时信息的访问接口。
  • 通过 /proc 中的各种文件,用户和管理员可以获取 CPU、内存、硬盘、进程、网络等方面的实时信息。
  • /proc 目录中的文件通常是内核动态生成的,反映了系统的当前状态。
  • /proc 不仅用于查询信息,也可以用于修改内核参数,实时调整系统的行为。

load和cpu使用率区别 #

在 Linux 系统中,load(负载)**和**CPU 使用率是两个常见的性能指标,它们都涉及到系统的资源使用情况,但它们的含义和作用有所不同。

1. Load(负载) #

负载表示系统在某一时间段内,处于运行或等待执行的进程数量。它反映了系统的工作负荷,可以用来评估系统的“忙碌”程度。负载值不仅仅与 CPU 使用情况有关,还包括 I/O 操作(如磁盘、网络等)和等待队列的进程数量。

负载的关键点: #

  • 负载的计算方式:系统负载通常用一个三元组表示,分别为过去 1 分钟、5 分钟、15 分钟的平均负载。这些值显示了系统负载的趋势。你可以通过 cat /proc/loadavg 来查看当前系统的负载。

  • 负载的含义

    • 如果负载值小于或等于 CPU 核心数,则表明系统的资源还没有被完全占满,能够处理当前的任务。
    • 如果负载值大于 CPU 核心数,则表明系统可能正在过载,可能会有很多进程在等待 CPU 时间片。

    例如:如果你有 4 核 CPU,负载值为 4 表示系统刚好忙碌(每个核心都有任务)。如果负载为 8,表示系统的负载已经超过了 CPU 核心数,可能会有等待的进程。

负载的查看: #

cat /proc/loadavg

输出示例:

0.85 1.12 1.14 2/310 15432

其中:

  • 0.85:过去 1 分钟的平均负载
  • 1.12:过去 5 分钟的平均负载
  • 1.14:过去 15 分钟的平均负载
  • 2/310:表示当前正在运行的进程数(2),以及系统中所有进程的总数(310)

负载值的含义: #

  • 1.00(或 1):系统的负载等于 CPU 核心数,表明系统刚好忙碌。
  • > 1.00(或 CPU 核心数):系统负载高,可能导致任务排队和延迟。
  • < 1.00:系统负载较低,通常表明 CPU 的处理能力有富余。

2. CPU 使用率 #

CPU 使用率指的是 CPU 在某段时间内实际工作的比例,它反映了 CPU 资源的使用情况。CPU 使用率通常包括以下几个方面:

  • 用户空间的 CPU 使用率(user):表示 CPU 在用户程序中执行任务的时间比例。
  • 系统空间的 CPU 使用率(system):表示 CPU 在内核空间中执行任务的时间比例。
  • 空闲 CPU 时间(idle):表示 CPU 没有工作、处于空闲状态的时间比例。
  • 等待 I/O 操作的 CPU 时间(iowait):表示 CPU 在等待 I/O 操作完成时的时间比例。

CPU 使用率的关键点:

  • 实时性:CPU 使用率是一个动态的、实时更新的指标,反映了 CPU 当前的负载状况。
  • 计算方式:CPU 使用率通常通过周期性测量 CPU 的空闲时间和工作时间来计算,表示为一个百分比值。

CPU 使用率的查看: #

可以通过 top 命令来查看 CPU 使用率:

top

top 输出的第一行中,你可以看到 CPU 使用率的各项指标:

%Cpu(s):  1.3 us,  0.3 sy,  0.0 ni, 98.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
  • us(user):用户空间(程序运行)占用的 CPU 时间比例。
  • sy(system):内核空间(系统调用、内核任务)占用的 CPU 时间比例。
  • ni(nice):用户进程的优先级调整部分占用的 CPU 时间比例。
  • id(idle):CPU 空闲的时间比例。
  • wa(iowait):CPU 等待 I/O 操作的时间比例。
  • hi(hardware interrupt):硬件中断占用的 CPU 时间比例。
  • si(software interrupt):软件中断占用的 CPU 时间比例。
  • st(steal):虚拟化环境中,虚拟 CPU 被其他虚拟机抢占的时间比例。

Load 和 CPU 使用率的区别 #

指标Load(负载)CPU 使用率
含义表示系统当前有多少进程在运行或等待运行表示 CPU 实际工作的百分比
单位平均负载值,通常是 1 分钟、5 分钟、15 分钟的平均值百分比(%)
计算依据与 CPU 核心数、进程排队和 I/O 等等待时间有关与 CPU 的使用情况(用户空间、系统空间、空闲等)有关
查看方式cat /proc/loadavguptimetop 命令中的 CPU 使用情况
反映的内容反映系统负载和任务队列的长度反映 CPU 的工作量和空闲情况
影响因素与进程数量、I/O 等相关,负载过高可能导致系统过载与程序运行、系统调用等相关,CPU 使用率过高可能影响系统响应
系统过载情况当负载值 > CPU 核心数时,系统可能过载当 CPU 使用率接近 100% 时,系统可能被过度占用
示例loadavg 值为 2,表示系统有 2 个进程在排队等待 CPUtop 中 CPU 使用率显示 90%,表示 CPU 的 90% 被占用

如何判断系统性能问题 #

  • 高负载: 如果 loadavg 值远高于 CPU 核心数,说明系统的负载很重,可能有很多进程在等待 CPU 时间。如果负载持续很高,可以考虑优化程序或增加硬件资源(如 CPU 核心数)。
  • 高 CPU 使用率: 如果 CPU 使用率接近 100%,且空闲时间很少(id 值低),说明系统的 CPU 资源已被大量占用,可能需要优化 CPU 密集型程序,或者检查是否有不正常的进程。
  • I/O 等待: 如果 CPU 使用率较低,但负载较高,并且 iowait 比例较大,可能是系统存在 I/O 阻塞,导致进程等待磁盘或网络资源。

总结: #

  • Load(负载) 主要反映系统整体的工作负载情况,关注的是有多少进程在等待资源(不仅仅是 CPU),以及这些进程是否被有效调度。
  • CPU 使用率 主要关注 CPU 的实际使用情况,反映了 CPU 资源的忙碌程度,关注的是 CPU 被多大比例占用。

通过结合查看负载和 CPU 使用率,可以更全面地了解系统的性能状况,帮助你判断是否存在瓶颈或资源不足的情况。

MAC地址IP地址如何转换 #

MAC 地址和 IP 地址是两种不同层次的地址,分别工作在不同的网络模型层:

  • MAC 地址(媒体访问控制地址)是数据链路层(OSI 模型的第 2 层)的地址,用于唯一标识网络设备。每个网络接口卡(NIC)都有一个唯一的 MAC 地址,通常由硬件厂商分配。
  • IP 地址是网络层(OSI 模型的第 3 层)的地址,用于标识设备在网络中的位置,允许设备之间进行通信。

MAC 地址和 IP 地址的转换并不是直接相互转换的过程,但它们之间存在联系,尤其在局域网内,MAC 地址通过 ARP(地址解析协议) 可以与 IP 地址进行映射。

如何通过 ARP 转换 MAC 地址和 IP 地址: #

  1. 从 IP 地址获取 MAC 地址

    • 当设备知道目标设备的 IP 地址 时,设备会通过 ARP 请求来寻找目标设备的 MAC 地址
    • 该过程通常如下:
      1. 设备广播一个 ARP 请求,询问“IP 地址为 X.X.X.X 的设备的 MAC 地址是什么?”
      2. 如果该 IP 地址对应的设备在同一局域网内,则目标设备会响应一个 ARP 响应,包含它的 MAC 地址。
      3. 发送方设备收到响应后,将 IP 地址与 MAC 地址进行映射,并将其存储在 ARP 缓存中,以供将来使用。

    命令行示例(Linux):

    arp -n 192.168.1.10
    

    这将显示 IP 地址 192.168.1.10 的 MAC 地址。

  2. 从 MAC 地址获取 IP 地址

    • 如果你知道设备的 MAC 地址,并想找出其对应的 IP 地址,通常可以通过 ARP 缓存来查看。
    • 在本地设备上,你可以查看 ARP 缓存表,其中会列出本地网络中设备的 IP 地址与 MAC 地址的映射。

    命令行示例(Linux):

    arp -a
    

    这将列出所有已知设备的 IP 地址和它们对应的 MAC 地址。

ARP 工作原理: #

ARP(地址解析协议)是一个在局域网内将 IP 地址映射到 MAC 地址的协议。ARP 协议用于查找设备的 MAC 地址,基于其已知的 IP 地址。该协议只在 局域网(LAN)中工作,因为它是基于广播的。

  • 当一个设备发送数据包到网络中的另一个设备时,它首先检查目标设备的 IP 地址。如果目标设备与源设备在同一个子网中,源设备会通过 ARP 查找目标设备的 MAC 地址,并将数据包发送给该设备的 MAC 地址。
  • 如果目标设备的 MAC 地址已经缓存(通过先前的 ARP 请求获得),则直接使用该 MAC 地址发送数据包。
  • 如果缓存没有目标设备的 MAC 地址,设备会通过广播 ARP 请求来查找该设备的 MAC 地址。

例子: #

假设设备 A 想向设备 B 发送数据:

  1. 设备 A 知道设备 B 的 IP 地址,但不知道它的 MAC 地址。
  2. 设备 A 向网络广播 ARP 请求,询问“IP 地址为 192.168.1.10 的设备的 MAC 地址是什么?”
  3. 设备 B 收到 ARP 请求后,发送 ARP 响应,告知设备 A 它的 MAC 地址(例如:00:1A:2B:3C:4D:5E)。
  4. 设备 A 获取到 MAC 地址后,就可以直接向设备 B 的 MAC 地址发送数据包。

总结: #

  • MAC 地址IP 地址 是不同层次的地址,不能直接相互转换。
  • ARP 协议在局域网内用于根据 IP 地址查找设备的 MAC 地址,或根据 MAC 地址查找设备的 IP 地址。
  • 每个设备会通过 ARP 协议自动进行这些转换,通常不需要人工干预。

常见的raid有哪些,使用场景是什么 #

RAID(冗余磁盘阵列)是将多个物理硬盘驱动器(HDD)或固态硬盘(SSD)组合在一起,以提高存储性能、容量和可靠性。RAID 有不同的级别,每种级别都有不同的性能、冗余和成本权衡。以下是常见的 RAID 级别及其使用场景。

常见的 RAID 级别 #

1. RAID 0(条带化) #

  • 特点
    • 数据被分割成多个条带,分别写入不同的磁盘中,所有磁盘共同工作,提供更高的读写性能。
    • 没有冗余机制。如果其中一个硬盘发生故障,所有数据都将丢失。
    • 磁盘的容量等于所有硬盘容量的总和。
  • 使用场景
    • 用于需要高性能、高吞吐量的场景,例如视频编辑、图像处理等,尤其是对数据冗余没有要求的情况下。
    • 不适合存储重要数据,因为没有冗余保护。
  • 优缺点
    • 优点:性能非常高,适合读写密集型工作负载。
    • 缺点:没有冗余,单一硬盘故障会导致数据丢失。

2. RAID 1(镜像) #

  • 特点
    • 数据被复制到两个或多个硬盘中,提供冗余。每个硬盘保存完全相同的数据副本。
    • 具有高度的容错能力,如果其中一个硬盘故障,数据依然可以从另一个硬盘中恢复。
    • 磁盘的总容量是最小硬盘的容量(即,两个 1TB 硬盘组成的 RAID 1 阵列总容量为 1TB)。
  • 使用场景
    • 用于需要高数据可靠性和可用性的环境,例如小型办公室、文件存储服务器、数据库和关键应用。
    • 适合不希望丢失数据的场景,例如个人数据备份、财务信息存储等。
  • 优缺点
    • 优点:数据冗余高,硬盘故障不会导致数据丢失。
    • 缺点:存储效率低,只有一半的磁盘空间可用,性能提升不明显。

3. RAID 5(带奇偶校验的条带化) #

  • 特点
    • 数据和奇偶校验信息分布在所有硬盘上。奇偶校验是数据冗余的一种方式,能够在一个硬盘发生故障时,使用剩余硬盘和奇偶校验数据恢复丢失的数据。
    • 至少需要 3 个硬盘。
    • 存储效率相对较高,比 RAID 1 提供更多的存储空间。
  • 使用场景
    • 用于要求数据冗余和性能平衡的场景,例如中型企业的文件服务器、数据库服务器、Web 服务器等。
    • 适合大多数企业环境,可以容忍一个硬盘故障而不丢失数据。
  • 优缺点
    • 优点:性能和冗余之间有较好平衡,存储效率较高,能够容忍单个硬盘故障。
    • 缺点:写入性能较差,因为需要计算奇偶校验。

4. RAID 6(带双重奇偶校验的条带化) #

  • 特点
    • 数据和双重奇偶校验信息分布在所有硬盘上,能够在两个硬盘同时发生故障时,依然能够恢复数据。
    • 至少需要 4 个硬盘。
    • 提供比 RAID 5 更高的容错能力,但存储效率较低。
  • 使用场景
    • 用于极其重视数据可靠性和容错的环境,例如企业级存储、数据中心、云存储服务等。
    • 适合要求高可用性和容错性的关键应用和数据存储。
  • 优缺点
    • 优点:提供比 RAID 5 更强的冗余能力,能够容忍两个硬盘同时故障。
    • 缺点:存储效率较低,写入性能较差,硬件要求较高。

5. RAID 10(RAID 1+RAID 0,镜像+条带化) #

  • 特点
    • 结合了 RAID 1 和 RAID 0 的优点。首先将硬盘镜像(RAID 1),然后将镜像盘进行条带化(RAID 0)。
    • 提供了高性能和高冗余,但需要至少 4 个硬盘。
    • 数据被镜像并条带化,使其在提高性能的同时保留了冗余。
  • 使用场景
    • 用于需要高性能和高数据可靠性的环境,例如数据库服务器、高流量的 Web 服务器等。
    • 适合需要快速读写和容忍故障的关键任务应用。
  • 优缺点
    • 优点:提供非常高的性能和数据冗余,能够容忍单个硬盘故障。
    • 缺点:存储效率低,需要更多的硬盘,成本较高。

6. RAID 50(RAID 5 + RAID 0,带奇偶校验的条带化) #

  • 特点
    • 将多个 RAID 5 阵列组合成一个 RAID 0 阵列,以获得更好的性能。提供 RAID 5 的冗余和 RAID 0 的性能。
    • 至少需要 6 个硬盘。
    • 提供比单独的 RAID 5 更高的性能,尤其在读写密集型应用中。
  • 使用场景
    • 用于对性能和冗余都有较高要求的环境,如大规模数据存储、数据库存储等。
    • 适合需要大存储容量并且需要在 RAID 5 性能基础上进行进一步优化的场景。
  • 优缺点
    • 优点:提供较高的性能和冗余,适合需要更高吞吐量的场景。
    • 缺点:需要更多的硬盘,复杂度和成本较高。

7. RAID 60(RAID 6 + RAID 0,带双重奇偶校验的条带化) #

  • 特点
    • 将多个 RAID 6 阵列组合成一个 RAID 0 阵列,提供更高的性能和容错能力。能够容忍两个硬盘同时发生故障。
    • 至少需要 8 个硬盘。
    • 提供比 RAID 6 更高的性能,尤其在读写密集型场景下。
  • 使用场景
    • 用于对冗余性和性能要求极高的环境,如大规模企业级存储、数据中心、高可用性应用等。
  • 优缺点
    • 优点:提供非常高的容错能力和性能,能够容忍两个硬盘同时故障。
    • 缺点:需要大量硬盘,存储效率较低,成本较高。

总结: #

  • RAID 0:适用于需要高性能、低成本,但不要求数据冗余的场景。
  • RAID 1:适用于需要高数据可靠性,但对性能要求不高的环境。
  • RAID 5:适用于大多数企业级存储,提供良好的性能与冗余平衡,适合存储大量数据。
  • RAID 6:适用于要求非常高冗余的环境,能够容忍两个硬盘故障。
  • RAID 10:适用于需要高性能和高冗余的环境,特别是数据库和高流量应用。
  • RAID 50/60:适用于对性能和冗余要求较高的企业级存储,适合大规模的数据存储需求。

根据不同的应用场景,选择合适的 RAID 级别可以有效提升系统性能和数据保护能力。

lvm怎么划分 #

LVM(逻辑卷管理,Logical Volume Manager)是 Linux 中的一种逻辑存储管理方式,可以通过将物理卷(Physical Volumes, PV)组合成卷组(Volume Groups, VG),然后再从卷组中划分逻辑卷(Logical Volumes, LV),以实现更加灵活和动态的磁盘管理。

LVM 的基本概念 #

  1. 物理卷(PV): 物理卷是实际的存储设备或存储介质,例如硬盘分区、硬盘或 RAID 阵列。一个物理卷可以是一个完整的硬盘、一个分区或其他支持的存储设备。
  2. 卷组(VG): 卷组是由一个或多个物理卷组成的存储池,提供了一组可用的空间来创建逻辑卷。卷组将多个物理卷的存储空间合并在一起,使得可以像一个大的磁盘一样来管理存储。
  3. 逻辑卷(LV): 逻辑卷是从卷组中划分出来的虚拟磁盘,可以在逻辑卷中存储数据。逻辑卷可以动态调整大小,扩展或缩小,支持灵活的磁盘分区管理。
  4. 物理块(PE): 物理卷上的数据是以固定大小的块(Physical Extents, PE)来划分的。一个物理卷上的 PE 数量决定了卷组中的可用空间。
  5. 逻辑块(LE): 逻辑卷中的数据同样是以固定大小的块(Logical Extents, LE)来划分的。每个逻辑卷上的 LE 是与卷组中的 PE 对应的。

LVM 的常用操作 #

1. 创建物理卷(PV) #

物理卷是物理存储设备上的一个分区或整个磁盘,可以通过 pvcreate 命令来创建。

sudo pvcreate /dev/sdb

这将 /dev/sdb 设备初始化为物理卷。

2. 创建卷组(VG) #

卷组是由多个物理卷组成的,可以通过 vgcreate 命令来创建。

sudo vgcreate my_vg /dev/sdb

这将创建一个名为 my_vg 的卷组,并将 /dev/sdb 添加到该卷组中。

3. 创建逻辑卷(LV) #

逻辑卷是在卷组中创建的,类似于传统的分区,可以通过 lvcreate 命令来创建。

sudo lvcreate -L 10G -n my_lv my_vg

这将创建一个大小为 10GB 的逻辑卷 my_lv,并将其放置在 my_vg 卷组中。

4. 查看 LVM 配置 #

可以通过 vgslvspvs 命令查看卷组、逻辑卷和物理卷的详细信息。

  • 查看卷组:

    sudo vgs
    
  • 查看逻辑卷:

    sudo lvs
    
  • 查看物理卷:

    sudo pvs
    

5. 扩展逻辑卷(LV) #

如果逻辑卷空间不足,可以扩展逻辑卷的大小。可以通过 lvextend 命令增加逻辑卷的大小。

sudo lvextend -L +5G /dev/my_vg/my_lv

这将 my_lv 逻辑卷的大小增加 5GB。

扩展后,还需要扩展文件系统,使用 resize2fs(对于 ext 文件系统)或 xfs_growfs(对于 XFS 文件系统)来调整文件系统的大小:

  • 对于 ext 文件系统:

    sudo resize2fs /dev/my_vg/my_lv
    
  • 对于 XFS 文件系统:

    sudo xfs_growfs /dev/my_vg/my_lv
    

6. 缩小逻辑卷(LV) #

如果需要减少逻辑卷的大小,首先需要先收缩文件系统,然后再缩小逻辑卷。

步骤

  1. 收缩文件系统。

    • 对于 ext 文件系统:

      sudo resize2fs /dev/my_vg/my_lv 5G
      
    • 对于 XFS 文件系统: XFS 不支持直接缩小大小,因此如果要缩小 XFS 文件系统,必须先备份数据,删除逻辑卷,然后再重新创建较小的逻辑卷。

  2. 缩小逻辑卷:

    sudo lvreduce -L 5G /dev/my_vg/my_lv
    

7. 删除逻辑卷(LV) #

当逻辑卷不再需要时,可以通过 lvremove 命令删除逻辑卷。

sudo lvremove /dev/my_vg/my_lv

8. 删除卷组(VG) #

在删除卷组之前,所有逻辑卷必须先被删除。然后可以使用 vgremove 命令删除卷组。

sudo vgremove my_vg

9. 删除物理卷(PV) #

在删除物理卷之前,需要先将其从卷组中移除。然后可以使用 pvremove 命令删除物理卷。

sudo pvremove /dev/sdb

LVM 的优缺点 #

优点

  • 灵活性高:LVM 支持动态调整磁盘分区大小,可以在系统运行时扩展或缩小逻辑卷。
  • 易于管理:卷组和逻辑卷的管理比传统的分区方式更加方便,支持合并多个硬盘空间。
  • 冗余支持:LVM 支持创建镜像(RAID1)或条带化(RAID0)卷,以实现数据冗余和性能优化。
  • 支持快照:LVM 提供了快照功能,方便备份和恢复数据。

缺点

  • 复杂性:LVM 管理较为复杂,初学者可能不太容易理解和配置。
  • 性能开销:由于 LVM 进行逻辑层的抽象,可能会带来一定的性能开销,尤其是在较低规格的硬件上。

总结 #

LVM 是一种非常灵活的磁盘管理方案,适用于动态存储需求的环境。通过物理卷、卷组和逻辑卷的组合,LVM 提供了比传统磁盘分区更高的灵活性和可扩展性。适合用于大规模存储、虚拟化环境或需要频繁调整存储容量的应用场景。

jvm内存如何查看 #

在 Java 程序运行时,JVM(Java Virtual Machine)会分配不同的内存区域来存储代码、数据、堆、栈等信息。了解 JVM 内存的使用情况有助于进行性能优化、内存调优以及排查内存泄漏等问题。

要查看 JVM 内存的使用情况,可以使用多种工具和方法。下面列出了几种常用的查看 JVM 内存的方法。

1. 使用 jps 命令查看 JVM 进程 #

jps(Java Process Status)是一个简单的命令行工具,用于列出当前系统上运行的所有 Java 进程及其进程号(PID)。

jps

这将输出所有正在运行的 Java 进程的 ID 和名称。

2. 使用 jvm 参数查看内存设置 #

JVM 启动时,您可以使用一些启动参数来设置和查看内存配置:

  • -Xms:设置 JVM 初始堆内存大小(单位:字节、KB、MB、GB)。
  • -Xmx:设置 JVM 最大堆内存大小(单位:字节、KB、MB、GB)。
  • -Xmn:设置新生代的大小(单位:字节、KB、MB、GB)。
  • -XX:MetaspaceSize:设置 Metaspace 初始大小。
  • -XX:MaxMetaspaceSize:设置 Metaspace 最大大小。

例如,要查看 JVM 的堆内存设置:

java -Xms512m -Xmx2g -XX:MaxMetaspaceSize=256m -jar your-application.jar

这些参数在程序启动时传递,以便您可以根据需要进行调整。

3. 使用 jstat 命令查看 JVM 内存统计 #

jstat 是 Java 提供的一个监控工具,用于查看 JVM 运行时的各种统计信息,包括内存使用情况。最常见的命令是 jstat -gc,可以查看垃圾收集相关的内存信息。

查看 JVM 的垃圾收集和内存使用情况:

jstat -gc <pid> 1000

解释:

  • <pid>:JVM 进程的进程 ID。
  • 1000:表示每隔 1000 毫秒(即 1 秒)输出一次统计信息。

jstat -gc 输出的字段包括:

  • S0C:第一个 Survivor 区的内存大小。
  • S1C:第二个 Survivor 区的内存大小。
  • EC:Eden 区的内存大小。
  • OC:老年代的内存大小。
  • MC:Metaspace 区的内存大小。
  • YGC:垃圾回收发生的次数(Young GC)。
  • YGCT:Young GC 的总耗时。
  • FGC:垃圾回收发生的次数(Full GC)。
  • FGCT:Full GC 的总耗时。

4. 使用 jmap 命令查看堆内存信息 #

jmap 是另一个用于查看 JVM 内存的工具,主要用于生成堆内存的快照。

jmap -heap <pid>

这将显示堆内存的使用情况,包括堆的初始大小、最大大小、当前大小、各个区域的使用情况(如 Eden 区、老年代、Survivor 区等)以及垃圾回收的相关统计信息。

例如:

Heap Configuration:
   MinHeapFreeRatio           = 40
   MaxHeapFreeRatio           = 70
   MaxHeapSize                = 2147483648 (2048.0MB)
   NewSize                    = 268435456 (256.0MB)
   MaxNewSize                 = 268435456 (256.0MB)
   OldSize                    = 0 (0.0MB)
   NewRatio                   = 2
   SurvivorRatio              = 8
   MetaspaceSize              = 16777216 (16.0MB)
   MaxMetaspaceSize           = 1073741824 (1024.0MB)

Heap Usage:
PS Young Generation:
Eden Space:
   capacity = 33554432 (32.0MB)
   used     = 33123456 (31.6MB)
   free     = 431280 (0.4MB)
   98.8% used
From Space:
   capacity = 8388608 (8.0MB)
   used     = 0 (0.0MB)
   free     = 8388608 (8.0MB)
   0.0% used
To Space:
   capacity = 8388608 (8.0MB)
   used     = 0 (0.0MB)
   free     = 8388608 (8.0MB)
   0.0% used
PS Old Generation:
   capacity = 4294967296 (4096.0MB)
   used     = 500000000 (476.8MB)
   free     = 3794967296 (3600.2MB)
   11.6% used

5. 使用 visualvmjconsole 图形化工具 #

如果您更倾向于图形化界面,可以使用 VisualVMJConsole 工具来查看 JVM 内存和垃圾回收等相关信息。

  • VisualVM:是一个功能强大的监控工具,提供了内存、线程、垃圾收集、CPU 等的可视化图表。可以通过 jvisualvm 启动。
  • JConsole:是一个基于 Java 的监控工具,提供了 CPU、内存、线程和类加载等方面的信息。可以通过 jconsole 启动。

这些工具允许实时监控 JVM 的状态,查看堆内存的使用情况,以及生成堆转储(heap dump)文件用于分析内存泄漏等问题。

6. 使用 -XX:+PrintGCDetails 参数查看垃圾回收日志 #

为了监控 JVM 垃圾回收的详细信息,您可以通过 JVM 启动参数启用垃圾回收日志。

java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:<path-to-log-file> -jar your-application.jar

这将打印出每次垃圾回收的详细信息,包括内存的使用情况、GC 类型、回收前后的内存量等。

7. 查看系统层面的内存使用 #

除了 Java 自身的工具,还可以查看系统层面的内存使用情况。通过 tophtopfree 等工具可以查看系统上所有进程的内存使用情况,其中包括 JVM 占用的内存。

  • top:显示系统的内存使用情况和每个进程的内存占用。
  • htop:是 top 的增强版,提供更友好的交互界面。
  • free:显示系统内存的总量、已用、空闲和交换空间(swap)的情况。

例如:

top -p <pid>

这将只显示指定进程的内存使用情况。

8. 查看 Metaspace 使用情况 #

JVM 在 Java 8 及更高版本使用 Metaspace 来代替永久代(PermGen)。可以通过 -XX:MetaspaceSize-XX:MaxMetaspaceSize 来配置 Metaspace 的大小。

使用 jstat -gc 命令查看 Metaspace 的使用情况,或者在 JVM 启动时使用参数 -XX:+PrintGCDetails 来查看 Metaspace 的 GC 日志。

总结 #

查看 JVM 内存使用情况有多种方法,包括使用命令行工具(如 jpsjstatjmap)、图形化工具(如 VisualVMJConsole)以及通过 JVM 启动参数来获取详细的内存、GC 日志信息。通过这些工具,您可以实时监控 JVM 的内存使用,进行性能调优,定位内存泄漏等问题。

如何管理和优化内核参数 #

管理和优化 Linux 内核参数是确保系统性能、稳定性和安全性的关键操作,尤其是在高负载或特殊场景下(例如大规模并发、网络优化、大数据处理等)。Linux 内核的各项参数影响着系统的行为,包括内存管理、进程调度、文件系统、网络等方面。

1. 查看当前内核参数 #

内核参数通常存储在 /proc/sys/ 目录中,或者通过 sysctl 命令进行管理。可以使用以下命令查看当前的内核参数:

  • 查看当前内核参数值:

    sysctl -a
    

    这将列出所有当前的内核参数及其值。

  • 查看单个内核参数的值:

    sysctl <parameter>
    

    例如,要查看最大文件描述符数:

    sysctl fs.file-max
    
  • 直接查看 /proc/sys/ 下的内容:

    cat /proc/sys/net/core/rmem_max
    

2. 修改内核参数 #

可以通过两种方式来修改内核参数:临时修改和永久修改。

临时修改内核参数 #

临时修改内核参数会在系统重启后失效。可以通过 sysctl 命令直接修改内核参数。

sudo sysctl -w <parameter>=<value>

例如,修改最大文件描述符数:

sudo sysctl -w fs.file-max=2097152

永久修改内核参数 #

要永久修改内核参数,需要将其添加到 /etc/sysctl.conf 文件中。每次系统启动时,sysctl 会自动读取该文件中的设置并应用。

  1. 编辑 /etc/sysctl.conf 文件:

    sudo vi /etc/sysctl.conf
    
  2. 添加或修改需要的内核参数。例如,增加文件描述符的最大值:

    fs.file-max = 2097152
    
  3. 使更改生效:

    sudo sysctl -p
    

你也可以通过直接修改 /etc/sysctl.d/ 目录下的配置文件来管理内核参数。

3. 常用内核参数及其优化 #

a. 内存管理 #

  • vm.swappiness:控制内存交换行为,0-100 范围内,值越小,系统越倾向于使用物理内存而不是交换分区。

    sysctl vm.swappiness=10
    

    对于数据库、内存密集型应用,设置较低的值可以避免过早进行交换。

  • vm.overcommit_memory:控制内核如何处理内存超分配。值为 0 表示内核允许超额分配,值为 1 表示总是允许超额分配,值为 2 表示不允许超额分配。

    sysctl vm.overcommit_memory=2
    
  • vm.dirty_ratiovm.dirty_background_ratio:控制在写回磁盘之前,内存中可以保持的脏页(未同步到磁盘的页面)比例。

    sysctl vm.dirty_ratio=60
    sysctl vm.dirty_background_ratio=10
    

b. 进程调度 #

  • kernel.pid_max:设置系统允许的最大进程号,默认 32768。对高并发系统可进行适当调整。

    sysctl kernel.pid_max=65535
    
  • kernel.threads-max:设置系统允许的最大线程数。

    sysctl kernel.threads-max=1000000
    

c. 网络优化 #

  • net.core.somaxconn:控制系统每个网络接口的最大连接队列长度。可以增加该值来提高处理高并发连接的能力。

    sysctl net.core.somaxconn=65535
    
  • net.ipv4.tcp_fin_timeout:控制 TCP 连接关闭的等待时间,减少连接的长时间保持状态。

    sysctl net.ipv4.tcp_fin_timeout=30
    
  • net.ipv4.tcp_rmemnet.ipv4.tcp_wmem:控制 TCP 接收和发送缓冲区大小。

    sysctl net.ipv4.tcp_rmem="4096 87380 6291456"
    sysctl net.ipv4.tcp_wmem="4096 65536 6291456"
    
  • net.ipv4.ip_local_port_range:设置可用的本地端口范围,对于高并发服务,应该扩大端口范围。

    sysctl net.ipv4.ip_local_port_range="1024 65535"
    

d. 文件系统 #

  • fs.file-max:控制整个系统允许的最大文件句柄数。可以通过增加该值来提高支持大并发文件操作的能力。

    sysctl fs.file-max=2097152
    
  • fs.inotify.max_user_watches:调整可以同时监视的文件数量,适用于需要大量监控文件变化的应用(如文件监控系统)。

    sysctl fs.inotify.max_user_watches=524288
    

e. 安全性设置 #

  • kernel.randomize_va_space:启用或禁用地址空间布局随机化(ASLR),增强系统的安全性。

    sysctl kernel.randomize_va_space=2
    
  • net.ipv4.icmp_echo_ignore_all:如果你想要忽略 ICMP 回显请求(ping 请求),可以禁用 ICMP 响应。

    sysctl net.ipv4.icmp_echo_ignore_all=1
    

4. 监控内核参数 #

  • sysctl -a:查看所有内核参数及其值。
  • vmstat:查看系统的内存、进程、IO 等相关统计信息。
  • iostat:查看系统的 CPU 使用情况和磁盘 I/O 性能。
  • netstat:查看网络连接状态、路由表等信息。
  • top / htop:实时查看系统资源使用情况,包括内存、CPU、负载等。
  • sar:收集、报告和保存系统活动信息,适合做长期性能监控。

5. 调整参数的策略 #

  • 负载高的应用:如果有高并发的 web 服务器,调整网络相关的参数(如 somaxconntcp_fin_timeouttcp_rmem 等)和文件句柄限制(file-max)。
  • 内存密集型应用:可以调整 swappinessvm.dirty_ratioovercommit_memory 等内存管理参数。
  • 系统启动优化:一些内核参数(如 pid_max)可以影响系统的最大进程数和线程数,适当调整可以提升系统的并发能力。
  • 监控与调优:定期使用 sysctltopvmstat 等工具监控内核参数,根据实际负载调整参数。

6. 注意事项 #

  • 内核参数调整需要小心:有些内核参数可能对系统的稳定性和安全性产生影响,修改前最好了解其具体作用。
  • 合理使用内存和 CPU 资源:某些参数(如 swappiness)可以提高系统的响应速度,但可能会增加 I/O 操作的负担,因此需要根据实际应用负载来调整。
  • 定期监控与调整:系统负载和性能需求可能会随时间变化,因此定期评估和调整内核参数非常重要。

通过合理地管理和优化内核参数,能够有效提高系统性能、稳定性并满足特定应用场景的需求。

什么是进程最大数、最大线程数、进程打开的文件数,怎么调整☆ #

在 Linux 系统中,进程最大数最大线程数进程打开的文件数是与系统资源和性能密切相关的重要参数。理解这些参数并适时调整它们有助于提高系统的处理能力,尤其是在高负载或高并发的场景下。

1. 进程最大数(max processes) #

进程最大数是指系统中允许的最大进程数。在 Linux 系统中,进程数受两个因素的限制:

  • 系统级别限制:即整个系统最多能够启动多少个进程。
  • 用户级别限制:即单个用户最多能够启动多少个进程。

查看进程最大数: #

  • 系统级别: 可以查看 /proc/sys/kernel/pid_max 来查看当前系统允许的最大进程号(即最大进程数)。

    cat /proc/sys/kernel/pid_max
    
  • 用户级别: 使用 ulimit 命令查看当前用户的进程限制。

    ulimit -u
    

调整进程最大数: #

  • 临时调整:可以通过 sysctl 命令来调整系统级别的最大进程数。例如,修改最大进程号:

    sudo sysctl -w kernel.pid_max=65535
    
  • 永久调整:修改 /etc/sysctl.conf 文件,添加或修改如下行:

    kernel.pid_max = 65535
    

    然后使用以下命令使其生效:

    sudo sysctl -p
    
  • 用户级别调整:通过修改 /etc/security/limits.conf 文件,可以设置每个用户的最大进程数。例如:

    username soft nproc 4096
    username hard nproc 8192
    

    其中:

    • soft 是软限制,表示普通的进程数限制。
    • hard 是硬限制,表示无法再增加的进程数限制。

    另外,修改 /etc/pam.d/common-session 文件,确保 pam_limits.so 被启用:

    session required pam_limits.so
    

2. 最大线程数(max threads) #

线程是进程内的一个执行单元。一个进程可以拥有多个线程,这些线程共享进程的内存空间和资源。在 Linux 系统中,线程数通常与进程数一起受到限制。

查看最大线程数: #

  • 系统级别限制

    :在 Linux 系统中,线程数的最大限制与进程数相同,因为每个线程也会被当作进程来管理。使用

    ulimit -a
    

    命令查看当前的线程数限制:

    ulimit -a
    

    其中

    max user processes
    

    就是当前用户可创建的最大线程数。

调整最大线程数: #

  • 用户级别调整:同样通过修改 /etc/security/limits.conf 文件来调整线程数的限制。例如:

    username soft nproc 4096
    username hard nproc 8192
    
  • 系统级别调整:如果需要允许系统创建更多的线程,可以增加 kernel.pid_max 参数的值,因为系统会为每个线程分配一个进程号(PID)。请参考前面的 “进程最大数” 部分来调整。

3. 进程打开的文件数(open files) #

每个进程在运行时会打开多个文件,包括普通文件、套接字、管道等。操作系统为每个进程设置了一个 最大打开文件数 限制,防止某个进程占用过多的文件描述符资源。

查看进程最大文件数: #

  • 当前进程的最大文件数限制: 使用 ulimit -n 命令查看当前用户可以打开的最大文件数。

    ulimit -n
    
  • 系统级别限制: 系统有一个全局的最大打开文件数,可以通过查看 /proc/sys/fs/file-max 来查询:

    cat /proc/sys/fs/file-max
    

调整进程打开的文件数: #

  • 临时调整:使用 ulimit 命令临时增加最大文件数。例如,将最大文件数设置为 65535:

    ulimit -n 65535
    
  • 永久调整

    1. 修改 /etc/security/limits.conf 文件,设置最大文件数:

      username soft nofile 65535
      username hard nofile 65535
      
      • soft 是软限制,表示当前会话可以达到的最大文件数。
      • hard 是硬限制,表示最大允许的文件数限制。
    2. 修改 /etc/sysctl.conf 文件,设置系统全局的最大文件数:

      fs.file-max = 2097152
      

      然后使其生效:

      sudo sysctl -p
      
    3. 修改 /etc/pam.d/common-session 文件,确保 pam_limits.so 被启用:

      session required pam_limits.so
      

总结 #

  • 进程最大数:限制系统允许的进程总数,可以通过修改 /proc/sys/kernel/pid_max 来调整。用户级别的限制可以通过 ulimit -u/etc/security/limits.conf 来调整。
  • 最大线程数:线程数受进程最大数的影响,因此也可以通过调整 ulimit -u/etc/security/limits.conf 来管理。需要根据系统资源合理设置。
  • 进程打开的文件数:每个进程可以打开的文件数量是有限制的,可以通过 ulimit -n/etc/security/limits.conf 来调整。系统级别的限制可以通过修改 /proc/sys/fs/file-max 来管理。

合理调整这些限制能够确保系统在高并发、高负载下的稳定性和性能,避免资源耗尽或过度竞争。

du和df统计不一致原因☆ #

du(Disk Usage)和 df(Disk Free)是常用的磁盘空间使用情况统计工具,二者的统计结果可能存在差异,主要原因如下:

1. 统计对象不同 #

  • dfdf 命令显示的是整个文件系统的磁盘使用情况。它基于文件系统的元数据(如文件系统的块大小)来统计磁盘的使用情况,显示的是文件系统的总容量、已用空间和可用空间。

    df 统计的是 文件系统层级的空间使用情况,并且它关注的是每个挂载点的总空间,包括系统预留空间(如 root 用户保留的空间)。

  • dudu 命令显示的是 目录或文件的磁盘空间使用情况,它是通过遍历目录中的文件和子目录来计算空间的。du 统计的是文件和目录实际占用的磁盘空间,包括文件的实际内容,但不会计算文件系统级别的元数据(如目录的大小、inode 信息等)。

2. 文件系统预留空间 #

  • df 命令 会考虑文件系统的预留空间(reserved blocks),通常是 root 用户 用于防止文件系统完全填满,保持系统的稳定性。例如,Ext4 文件系统默认会保留 5% 的磁盘空间供 root 用户使用。df 会显示文件系统总容量、已用空间和可用空间,其中的可用空间是考虑了预留空间之后的剩余空间。
  • du 命令 计算的是实际占用的空间,不会考虑文件系统的保留空间。因此,du 显示的磁盘使用情况可能会比 df 显示的实际占用空间稍低,因为它不包括文件系统中为 root 保留的空间。

3. 软链接和挂载点 #

  • du 会统计 软链接(symbolic links)指向的目标文件和目录的实际使用情况。它默认会将软链接指向的目标文件计入空间占用。这可能会导致 du 显示的空间比 df 更大,因为软链接会被重复计算。
  • df 只计算挂载点的空间,不会考虑挂载点内的其他文件系统。如果你有多个文件系统或挂载点(如挂载了 /home/data 等),df 会分别显示它们的空间占用,而 du 只会显示当前目录下的空间使用情况,不会考虑其他挂载点。

4. 文件系统缓存 #

  • df 统计的是磁盘的实际使用情况,不会受到文件系统缓存的影响。即使文件刚刚被删除,但其空间尚未被回收,df 可能会显示该文件仍占用空间。
  • du 只会统计已实际写入磁盘的数据,不会考虑缓存中的数据。如果有大量的数据被删除但文件系统没有立即回收空间,du 可能会显示文件占用的空间较少,而 df 显示的可用空间较少。

5. 挂载点重复 #

如果文件系统有多个挂载点,例如 /dev, /proc, /sys 等虚拟文件系统,这些文件系统通常并不占用实际磁盘空间。du 会忽略这些挂载点,而 df 可能会显示这些挂载点所占用的空间。

6. 延迟同步(文件删除) #

当你删除文件时,df 显示的空间释放情况是基于文件系统的实际状态,但文件内容可能仍然保留在缓存中,直到真正写入磁盘(例如,缓存被刷新)。因此,du 可能会显示某些文件仍然存在,直到缓存清理,才会在 df 上反映出更准确的空闲空间。

7. du 计算的单位和 df 的单位不同 #

有时候,dudf 的输出单位可能不同,导致看起来的空间占用差异。例如,df 默认显示的是 块(block)单位,而 du 默认显示的是 字节(byte)千字节(KB)。如果不统一单位,可能会觉得统计不一致。

8. NFS 或网络文件系统 #

如果系统中有挂载了 NFS 或其他网络文件系统,dudf 也可能会出现差异,特别是在高延迟或不稳定的网络环境下。df 可能会显示一个较为准确的空间统计,而 du 在网络文件系统中可能由于延迟或未更新的文件系统缓存,导致显示的空间使用不一致。

9. 磁盘碎片 #

虽然现代文件系统通常能较好地处理碎片问题,但磁盘碎片还是可能会影响 dfdu 的统计差异。df 显示的是逻辑块的使用情况,而 du 显示的是文件实际占用的空间。如果文件系统存在碎片,实际磁盘占用空间和文件的逻辑占用空间可能不一致。

解决办法: #

  • 确保使用相同的单位进行比较,例如,使用 df -hdu -h 显示为可读的格式(MB、GB)。
  • 检查文件系统是否有保留空间(通过 df 命令查看)。
  • 检查文件系统挂载点、软链接以及挂载点是否被重复计算。
  • 使用 lsof 检查文件是否已被删除但仍由进程占用。

总结: #

dudf 显示的磁盘使用情况不一致,通常是因为它们统计的方式不同、关注的对象不同,以及文件系统的特性(如缓存、预留空间、软链接等)。通过理解这些差异,可以更好地理解和利用这两个命令。

buffers与cached的区别☆ #

在 Linux 系统中,bufferscached 都与内存的使用相关,它们表示不同类型的缓存,它们的作用和统计方式有所区别。理解这两者的区别有助于更好地理解系统内存的使用情况。

1. buffers #

  • buffers 是指 块设备缓存,即为磁盘(硬盘、SSD、USB 等)块设备上的数据分配的内存。它主要用于缓存 原始块设备的数据,例如用于存放磁盘块的元数据(如文件系统的结构、磁盘块的索引等)。
  • 作用
    • buffers 主要用于优化磁盘 I/O 性能,尤其是文件系统和磁盘之间的数据传输。
    • 它帮助减少对磁盘的直接访问,避免频繁的磁盘操作,从而提高文件系统的访问速度。
  • 举例
    • 当你访问文件时,文件系统需要读取文件的磁盘块,如果这些块已经存在于 buffers 中,系统就可以直接从内存中获取,而不需要重新从磁盘读取。

2. cached #

  • cached 是指 页面缓存,即文件内容缓存。cached 主要用于存储 文件内容,即文件系统中实际存储的文件数据。当系统读取文件内容时,操作系统会将这些数据存储在内存中,供后续访问使用,从而提高文件读取的性能。
  • 作用
    • cached 主要缓存的是文件数据的内容,而不仅仅是文件系统元数据(如目录结构)。
    • 它有助于加速后续对文件的读取操作。例如,当你多次读取同一个文件时,如果文件数据已经被缓存,那么系统可以直接从内存中读取,而不需要再次访问磁盘。
  • 举例
    • 如果你打开一个文件,系统会将该文件的数据存储到 cached 中,下次访问该文件时,系统可以直接从内存中读取,而不需要重新访问磁盘。

3. bufferscached 的区别 #

参数含义缓存对象内存区域
buffers块设备缓存文件系统的元数据(如索引、目录结构)磁盘块级别的数据缓存
cached页面缓存文件数据(文件的内容)文件内容的内存缓存

4. 总结 #

  • buffers:缓存的是磁盘的 元数据(如文件系统结构),优化文件系统的读写性能。
  • cached:缓存的是 文件内容(即文件的实际数据),加速对文件内容的重复读取。

5. 实际影响 #

  • free 命令或 top 命令的输出中,bufferscached 都算作 “使用中的内存”,但它们并不会占用实际的磁盘空间,而是优化系统性能的一部分。
  • cached 的内存通常是最易释放的,当系统需要更多内存时,操作系统可以将 cached 中的数据释放并清空,重新分配内存给其他进程。
  • buffers 通常占用的内存也可以被释放,但它更多的是处理块设备层级的数据传输,涉及更底层的文件系统结构。

6. 释放缓存 #

如果系统内存非常紧张,你可以通过以下命令来释放缓存:

# 清空 pagecache
sudo sync; sudo echo 1 > /proc/sys/vm/drop_caches

# 清空 dentries 和 inodes 缓存
sudo echo 2 > /proc/sys/vm/drop_caches

# 清空 pagecache, dentries 和 inodes 缓存
sudo echo 3 > /proc/sys/vm/drop_caches

这些命令会释放缓存的内存,但会导致性能下降,因为系统将需要重新读取缓存中的数据。

lsof命令使用场景 #

lsof(List Open Files)是一个非常强大的工具,用于列出当前系统中所有打开的文件和正在使用它们的进程。由于 Linux 中的设备、文件、目录、网络连接等都被当作文件处理,lsof 可以用于排查多种问题。以下是 lsof 常见的使用场景:

1. 查看某个进程打开的文件 #

如果你想查看某个进程打开了哪些文件,可以使用 lsof 配合进程ID(PID)。

lsof -p <pid>

使用场景: #

  • 排查某个进程是否打开了特定的文件。
  • 查看进程是否存在文件句柄泄漏的现象。

2. 查看某个用户打开的文件 #

可以使用 lsof 查看某个用户打开的所有文件。

lsof -u <username>

使用场景: #

  • 监控某个用户的文件使用情况。
  • 在需要查找某个用户正在访问的文件时,尤其是多用户系统中,帮助管理员跟踪活动。

3. 查看特定文件的打开情况 #

如果你想查看某个文件是否正在被进程占用,可以直接使用文件名。

lsof <file_path>

使用场景: #

  • 查找某个文件是否被其他进程占用,尤其在需要删除或修改文件时。例如,在删除日志文件之前,可能需要检查文件是否仍然被进程打开。
  • 查找某个特定文件是否被锁定,是否有其他进程在访问它。

4. 查看某个端口被哪个进程占用 #

lsof 也可以列出当前系统中所有的网络连接,并显示是哪个进程在监听特定端口。你可以使用 -i 参数指定特定端口或协议。

lsof -i :<port_number>

例如,查看哪个进程占用了 80 端口:

lsof -i :80

或者查看所有 TCP 连接:

lsof -i tcp

使用场景: #

  • 追踪网络端口的使用情况。
  • 查找哪个进程占用了系统的某个端口,尤其在端口冲突的情况下非常有用。
  • 检查服务是否正常监听对应的端口。

5. 查看文件系统的某个挂载点 #

lsof 可以查看系统中某个挂载点的所有打开文件。例如,查看 /mnt/data 目录下的所有打开文件:

lsof +D /mnt/data

使用场景: #

  • 检查特定挂载点上的文件是否有进程正在访问,尤其在磁盘挂载、卸载前需要确保没有进程正在使用该目录。
  • 调查文件系统挂载点的访问情况,避免数据丢失。

6. 查找删除的文件 #

在 Linux 系统中,如果文件被删除,但进程仍然打开着它,lsof 可以列出这些文件,即使文件已经从文件系统中删除,它仍然存在于进程的文件描述符中,直到进程关闭该文件。

lsof | grep deleted

使用场景: #

  • 调查删除文件后仍然占用磁盘空间的情况。某些进程可能持有已删除文件的文件描述符,导致磁盘空间无法释放。
  • 检查进程是否仍然占用被删除的日志文件,防止磁盘空间被大量占用。

7. 查看文件锁 #

lsof 可以显示由进程持有的文件锁。如果你怀疑某个文件或资源被锁定,可以通过以下命令查看:

lsof -i | grep LOCK

使用场景: #

  • 排查文件锁定问题,特别是在需要访问文件但遇到“文件已被锁定”错误时。
  • 监控进程对文件的锁定操作,避免死锁或锁竞争问题。

8. 查看打开的网络文件(TCP/UDP) #

lsof 可以列出当前系统中所有活动的网络连接,包括 TCP 和 UDP 连接。

lsof -i

使用场景: #

  • 诊断网络问题,查看哪些进程正在通过网络进行通信。
  • 检查系统的开放网络端口和连接状态。
  • 追踪进程是否在访问外部资源。

9. 查找进程占用的文件描述符 #

每个进程都有一个文件描述符的限制,使用 lsof 可以查看进程打开了多少个文件描述符:

lsof -p <pid> | wc -l

使用场景: #

  • 诊断文件描述符耗尽问题。Linux 系统中每个进程都有打开文件数的限制,如果达到最大文件描述符数限制,进程可能无法打开新的文件或套接字。
  • 排查进程是否存在文件描述符泄漏。

10. 监控进程创建的临时文件 #

lsof 可以帮助监控进程创建的临时文件,尤其在高并发系统中,进程可能会大量创建临时文件,但没有及时关闭,导致磁盘空间被占满。

lsof +L1

使用场景: #

  • 检查进程是否创建了大量临时文件,并没有及时清理,导致磁盘空间被占满。

总结 #

lsof 是一个非常强大的工具,能够提供系统中文件和进程的详细信息,常见的使用场景包括:

  • 查找文件是否被占用。
  • 排查文件锁、端口占用等问题。
  • 调查被删除但未释放的文件。
  • 监控进程的资源使用情况。

在系统运维、故障排查和性能优化过程中,lsof 是一个非常实用的工具,能帮助管理员了解系统文件和网络连接的详细使用情况。

Linux中的进程间通信的方式及其使用场景 #

在 Linux 中,进程间通信(IPC,Inter-Process Communication)是指在不同的进程之间传递信息或同步操作的机制。进程间通信非常重要,尤其是在多进程并发执行的环境下,确保数据的传递与协调。Linux 提供了多种进程间通信方式,每种方式适用于不同的场景。

常见的 Linux 进程间通信方式 #

  1. 管道(Pipe)
  2. 命名管道(Named Pipe 或 FIFO)
  3. 消息队列(Message Queue)
  4. 信号(Signal)
  5. 共享内存(Shared Memory)
  6. 信号量(Semaphore)
  7. 套接字(Socket)
  8. 内存映射(Memory Mapping)

1. 管道(Pipe) #

管道是最基本的进程间通信方式之一,它允许一个进程将数据写入管道,另一个进程从管道中读取数据。管道通常是匿名的,且只能在父子进程或兄弟进程之间使用。

使用场景: #

  • 父进程和子进程之间的数据传输。
  • 命令行工具之间的组合使用(例如,ls | grep)。

优点: #

  • 实现简单,通信速度较快。

缺点: #

  • 只能用于同一机器上的进程间通信。
  • 不能在无关进程间通信。

2. 命名管道(FIFO) #

命名管道是管道的一种改进形式,允许具有不同 PID 的进程通过一个指定的文件来进行通信。不同于匿名管道,命名管道在文件系统中有一个名字,进程可以通过这个名字访问管道。

使用场景: #

  • 不同的进程之间,通过文件系统进行通信。
  • 需要多个进程共享数据时使用。

优点: #

  • 可以在无父子关系的进程之间通信。

缺点: #

  • 需要显式创建和管理文件,存在一定的复杂性。

3. 消息队列(Message Queue) #

消息队列是一种先进先出(FIFO)的消息传递机制,允许进程以消息的形式交换数据。消息队列中的消息有一个优先级,可以支持不同进程间异步通信。

使用场景: #

  • 多个进程之间的异步通信,尤其是需要保证消息顺序的场合。
  • 任务调度、事件通知系统等。

优点: #

  • 提供了进程间的异步通信机制。
  • 支持消息优先级,消息传递不会丢失。

缺点: #

  • 较复杂,管理和维护消息队列需要额外的开销。

4. 信号(Signal) #

信号是一种通知进程发生事件的机制,进程通过发送信号来通知另一个进程。信号通常用于处理异步事件或进程控制(如中断、终止等)。

使用场景: #

  • 进程之间的状态变化通知,例如 SIGTERM 用于优雅终止进程,SIGINT 用于中断进程。
  • 异常事件的处理,比如处理定时器到期的信号。

优点: #

  • 实现简单,常用于进程控制。

缺点: #

  • 不能传输大量数据,只适合用于通知事件。
  • 信号的处理非常依赖操作系统。

5. 共享内存(Shared Memory) #

共享内存是一种高效的进程间通信方式,多个进程可以访问同一块内存区域,从而共享数据。共享内存的大小可以很大,因此适合传输大量数据。

使用场景: #

  • 大量数据的高速传输。
  • 多个进程之间的协作操作,例如数据库缓存、图形处理等。

优点: #

  • 性能高,数据传输速度快。
  • 可以共享大量数据。

缺点: #

  • 需要进程之间协调访问(如使用信号量来进行同步)。
  • 需要更复杂的内存管理。

6. 信号量(Semaphore) #

信号量是一种用于控制对共享资源的访问的机制。信号量通常与共享内存一起使用,用于同步进程的执行,防止多个进程同时访问共享资源。

使用场景: #

  • 需要对共享资源进行互斥访问时使用,例如避免多个进程同时写入共享文件。
  • 实现生产者-消费者模型等。

优点: #

  • 用于进程同步和互斥,能有效避免竞态条件。

缺点: #

  • 需要额外的同步机制,管理上较为复杂。

7. 套接字(Socket) #

套接字提供了一种跨进程、跨主机的通信机制,广泛用于网络通信。套接字可以在同一台机器上的进程之间,也可以在不同机器之间进行通信。

使用场景: #

  • 网络通信:客户端与服务器之间的数据传输。
  • 分布式系统、微服务之间的通信。

优点: #

  • 支持跨主机、跨网络的通信。
  • 支持面向连接(TCP)和无连接(UDP)的通信方式。

缺点: #

  • 需要复杂的协议栈支持,可能导致一定的性能开销。

8. 内存映射(Memory Mapping) #

内存映射是一种通过将文件或设备的内容映射到进程的虚拟内存空间中的方式,使得进程能够直接操作内存中的数据,通常通过 mmap 系统调用实现。

使用场景: #

  • 共享数据、文件的映射访问。
  • 需要快速访问大文件或多个进程共享的数据时。

优点: #

  • 高效,支持大规模数据共享。
  • 减少了文件 I/O 操作的开销。

缺点: #

  • 对内存管理有较高要求,可能导致内存泄漏。
  • 映射文件的修改需要同步处理。

总结: #

通信方式适用场景优缺点
管道(Pipe)父子进程间的数据传输,命令行工具组合使用简单、快速,但仅限父子进程和临时通信
命名管道(FIFO)不同进程之间的数据传输支持无亲缘关系进程通信,但需要显式创建和管理文件
消息队列(Message Queue)多进程间的异步通信,需要传递带优先级的消息支持优先级,消息不丢失,但管理较复杂
信号(Signal)异步通知进程事件,如进程控制、异常事件仅限通知,不适合大数据传输,处理依赖操作系统
共享内存(Shared Memory)大量数据高速传输,进程间协作操作(如数据库缓存)性能高,但需要同步机制,复杂的内存管理
信号量(Semaphore)共享资源的同步与互斥访问,避免竞态条件用于同步和互斥,管理复杂
套接字(Socket)网络通信,跨进程、跨主机的数据传输支持远程通信,但需要复杂的协议栈支持
内存映射(Memory Mapping)大文件的高效访问,进程间共享数据高效,减少 I/O 操作,但需要额外的内存管理

不同的进程间通信方式有不同的优势和适用场景,开发人员应根据实际需求选择合适的方式。

Linux中的进程优先级与设置方法 #

在 Linux 中,进程的优先级决定了操作系统调度进程的顺序和分配 CPU 时间的方式。理解进程优先级的设置方法对于优化系统性能和调度效率至关重要。Linux 中有多种方式来设置和调整进程的优先级。

1. 进程优先级的概念 #

Linux 中的进程优先级由两个值来表示:

  • 静态优先级(Static Priority):也叫 “nice” 值,表示进程的调度优先级,范围是 -2019,其中 -20 表示最高优先级,19 表示最低优先级。默认情况下,进程的 nice 值为 0。
  • 动态优先级(Dynamic Priority):操作系统调度器使用的实际优先级,决定了哪个进程将获得 CPU 资源。动态优先级不仅依赖于静态优先级,还会受到进程的 CPU 时间消耗、调度策略等因素的影响。

2. 优先级与 nice #

Linux 中的 nice 值影响进程的优先级。更高的 nice 值意味着更低的优先级,反之,更低的 nice 值(甚至是负值)表示更高的优先级

nice 值的范围: #

  • nice 值的范围是 -2019,其中 -20 为最高优先级,19 为最低优先级。
  • 默认情况下,进程的 nice 值为 0。

3. 查看进程的优先级 #

使用 ps 命令可以查看进程的 nice 值及其优先级(PID 和静态优先级):

ps -eo pid,pri,ni,comm
  • pid:进程 ID
  • pri:动态优先级
  • ni:nice 值
  • comm:进程名

例如:

ps -eo pid,pri,ni,comm

输出可能类似:

  PID  PRI  NI  COMMAND
  1234  20   0  myprocess

4. 修改进程的 nice #

使用 nicerenice 命令来修改进程的 nice 值,从而调整其优先级。

(1) 使用 nice 启动新进程 #

可以使用 nice 命令启动一个新进程,并设置其 nice 值。nice 命令的语法如下:

nice -n <nice_value> <command>

例如,要启动一个进程并将其 nice 值设置为 10(较低的优先级):

nice -n 10 myprocess

(2) 使用 renice 调整已运行进程的 nice#

renice 命令用于修改已经在运行中的进程的 nice 值。语法如下:

renice -n <nice_value> -p <pid>

例如,要将 PID 为 1234 的进程的 nice 值调整为 -5(提高优先级):

renice -n -5 -p 1234

注意:

  • 只有超级用户(root)可以将 nice 值设置为负数(即提高优先级)。
  • 普通用户只能将 nice 值设置为正值(即降低优先级)。

5. 进程调度策略与优先级 #

除了 nice 值,Linux 还使用不同的调度策略来管理进程调度,进程的实际优先级(动态优先级)会受到调度策略的影响。Linux 支持几种调度策略,常见的有:

(1) 实时调度策略 #

  • SCHED_FIFO:先进先出(First In First Out),是最简单的实时调度策略,进程按照它们的到达顺序执行。
  • SCHED_RR:轮转调度(Round Robin),进程按时间片轮流执行,每个进程有固定的时间片。

(2) 普通调度策略 #

  • SCHED_OTHER:常规调度策略,使用的是 Linux 默认的调度策略(CFS 调度器),一般用于非实时进程。

6. 查看进程调度策略 #

可以使用 chrt 命令来查看和设置进程的调度策略及优先级。chrt 命令可以查看实时进程的调度策略和优先级,并允许设置。

(1) 查看进程调度策略 #

chrt -p <pid>

(2) 设置进程的调度策略 #

可以使用 chrt 命令设置实时进程的调度策略和优先级:

chrt -f <priority> <pid>    # 设置为 FIFO 策略
chrt -r <priority> <pid>    # 设置为 RR 策略

其中,<priority> 是优先级数值,通常在 1 到 99 之间(优先级越小越高)。实时调度策略的优先级是独立的,普通调度策略的优先级由 nice 值决定。

7. 优先级调整的使用场景 #

  • 实时任务:如需要对某些任务(例如硬件控制、音频处理等)进行实时调度时,使用 SCHED_FIFOSCHED_RR 策略和较高的优先级。
  • 后台进程:对于不需要占用大量 CPU 时间的后台任务,可以设置较高的 nice 值,降低它们的优先级,避免影响系统响应。
  • 计算密集型任务:对于高优先级的计算密集型任务,可以使用较低的 nice 值,确保它们获得更多的 CPU 时间。

8. 总结 #

在 Linux 系统中,进程的优先级由静态优先级(nice 值)和动态优先级(实际调度优先级)决定。我们可以通过以下方式调整进程优先级:

  • 使用 nice 启动新进程
  • 使用 renice 调整正在运行的进程的 nice
  • 通过调度策略(如 SCHED_FIFOSCHED_RR)来调整实时进程的优先级。

通过合理设置进程的优先级,可以优化系统性能,确保重要任务得到足够的资源,同时避免不必要的资源竞争。

什么是内存分页和分段 #

在 Linux 中,进程的优先级决定了操作系统调度进程的顺序和分配 CPU 时间的方式。理解进程优先级的设置方法对于优化系统性能和调度效率至关重要。Linux 中有多种方式来设置和调整进程的优先级。

1. 进程优先级的概念 #

Linux 中的进程优先级由两个值来表示:

  • 静态优先级(Static Priority):也叫 “nice” 值,表示进程的调度优先级,范围是 -2019,其中 -20 表示最高优先级,19 表示最低优先级。默认情况下,进程的 nice 值为 0。
  • 动态优先级(Dynamic Priority):操作系统调度器使用的实际优先级,决定了哪个进程将获得 CPU 资源。动态优先级不仅依赖于静态优先级,还会受到进程的 CPU 时间消耗、调度策略等因素的影响。

2. 优先级与 nice #

Linux 中的 nice 值影响进程的优先级。更高的 nice 值意味着更低的优先级,反之,更低的 nice 值(甚至是负值)表示更高的优先级

nice 值的范围: #

  • nice 值的范围是 -2019,其中 -20 为最高优先级,19 为最低优先级。
  • 默认情况下,进程的 nice 值为 0。

3. 查看进程的优先级 #

使用 ps 命令可以查看进程的 nice 值及其优先级(PID 和静态优先级):

ps -eo pid,pri,ni,comm
  • pid:进程 ID
  • pri:动态优先级
  • ni:nice 值
  • comm:进程名

例如:

ps -eo pid,pri,ni,comm

输出可能类似:

  PID  PRI  NI  COMMAND
  1234  20   0  myprocess

4. 修改进程的 nice #

使用 nicerenice 命令来修改进程的 nice 值,从而调整其优先级。

(1) 使用 nice 启动新进程 #

可以使用 nice 命令启动一个新进程,并设置其 nice 值。nice 命令的语法如下:

nice -n <nice_value> <command>

例如,要启动一个进程并将其 nice 值设置为 10(较低的优先级):

nice -n 10 myprocess

(2) 使用 renice 调整已运行进程的 nice#

renice 命令用于修改已经在运行中的进程的 nice 值。语法如下:

renice -n <nice_value> -p <pid>

例如,要将 PID 为 1234 的进程的 nice 值调整为 -5(提高优先级):

renice -n -5 -p 1234

注意:

  • 只有超级用户(root)可以将 nice 值设置为负数(即提高优先级)。
  • 普通用户只能将 nice 值设置为正值(即降低优先级)。

5. 进程调度策略与优先级 #

除了 nice 值,Linux 还使用不同的调度策略来管理进程调度,进程的实际优先级(动态优先级)会受到调度策略的影响。Linux 支持几种调度策略,常见的有:

(1) 实时调度策略 #

  • SCHED_FIFO:先进先出(First In First Out),是最简单的实时调度策略,进程按照它们的到达顺序执行。
  • SCHED_RR:轮转调度(Round Robin),进程按时间片轮流执行,每个进程有固定的时间片。

(2) 普通调度策略 #

  • SCHED_OTHER:常规调度策略,使用的是 Linux 默认的调度策略(CFS 调度器),一般用于非实时进程。

6. 查看进程调度策略 #

可以使用 chrt 命令来查看和设置进程的调度策略及优先级。chrt 命令可以查看实时进程的调度策略和优先级,并允许设置。

(1) 查看进程调度策略 #

chrt -p <pid>

(2) 设置进程的调度策略 #

可以使用 chrt 命令设置实时进程的调度策略和优先级:

chrt -f <priority> <pid>    # 设置为 FIFO 策略
chrt -r <priority> <pid>    # 设置为 RR 策略

其中,<priority> 是优先级数值,通常在 1 到 99 之间(优先级越小越高)。实时调度策略的优先级是独立的,普通调度策略的优先级由 nice 值决定。

7. 优先级调整的使用场景 #

  • 实时任务:如需要对某些任务(例如硬件控制、音频处理等)进行实时调度时,使用 SCHED_FIFOSCHED_RR 策略和较高的优先级。
  • 后台进程:对于不需要占用大量 CPU 时间的后台任务,可以设置较高的 nice 值,降低它们的优先级,避免影响系统响应。
  • 计算密集型任务:对于高优先级的计算密集型任务,可以使用较低的 nice 值,确保它们获得更多的 CPU 时间。

8. 总结 #

在 Linux 系统中,进程的优先级由静态优先级(nice 值)和动态优先级(实际调度优先级)决定。我们可以通过以下方式调整进程优先级:

  • 使用 nice 启动新进程
  • 使用 renice 调整正在运行的进程的 nice
  • 通过调度策略(如 SCHED_FIFOSCHED_RR)来调整实时进程的优先级。

通过合理设置进程的优先级,可以优化系统性能,确保重要任务得到足够的资源,同时避免不必要的资源竞争。

如何创建和管理自定义systemd服务 #

在 Linux 系统中,systemd 是现代 Linux 系统的初始化系统和服务管理器,广泛用于启动、停止和管理服务。通过创建和管理自定义 systemd 服务,可以让你以统一的方式控制和监控应用程序或脚本的执行。

1. 创建自定义 systemd 服务 #

(1) 创建服务单元文件 #

一个 systemd 服务是通过创建一个服务单元文件来定义的。服务单元文件通常位于 /etc/systemd/system/ 目录下,文件名以 .service 结尾。

例如,假设我们需要创建一个名为 my-service 的服务,我们需要创建一个名为 my-service.service 的文件。

步骤:

  1. 使用文本编辑器创建服务文件。例如:

    sudo nano /etc/systemd/system/my-service.service
    
  2. 定义服务单元文件内容。一个基本的 .service 文件可以包含以下内容:

    [Unit]
    Description=My Custom Service
    After=network.target
    
    [Service]
    ExecStart=/path/to/your/command_or_script
    Restart=on-failure
    User=myuser
    Group=mygroup
    Environment="ENV_VAR=value"
    
    [Install]
    WantedBy=multi-user.target
    

    解释:

    • [Unit] 部分:定义了服务的描述和依赖关系。After=network.target 表示该服务会在网络服务启动之后执行。
    • [Service] 部分:指定服务启动的命令。ExecStart 定义了启动服务时执行的命令或脚本路径。Restart=on-failure 表示服务失败时会自动重启。UserGroup 指定了运行服务的用户和组。Environment 设置了环境变量。
    • [Install] 部分:定义了服务安装时的目标(即在哪个 runlevel 启动)。multi-user.target 通常表示多用户模式,是大多数服务器的默认目标。

(2) 保存并退出编辑器 #

完成文件编辑后,保存并关闭编辑器(在 nano 中按 Ctrl + X,然后按 YEnter)。

(3) 重新加载 systemd 配置 #

在创建服务单元文件之后,需要重新加载 systemd 配置,以使其识别新的服务。

sudo systemctl daemon-reload

(4) 启动并启用服务 #

启动服务并让其开机自动启动:

sudo systemctl start my-service.service   # 启动服务
sudo systemctl enable my-service.service  # 设置为开机自动启动

(5) 检查服务状态 #

可以使用 systemctl 命令检查服务的状态、日志等信息:

sudo systemctl status my-service.service  # 查看服务状态

2. 管理自定义 systemd 服务 #

(1) 启动和停止服务 #

  • 启动服务:

    sudo systemctl start my-service.service
    
  • 停止服务:

    sudo systemctl stop my-service.service
    
  • 重启服务:

    sudo systemctl restart my-service.service
    

(2) 查看服务状态 #

查看服务的当前状态(是否正在运行、是否存在错误等):

sudo systemctl status my-service.service

输出示例:

● my-service.service - My Custom Service
   Loaded: loaded (/etc/systemd/system/my-service.service; enabled; vendor preset: enabled)
   Active: active (running) since Fri 2025-02-25 12:34:56 UTC; 1h 30min ago
 Main PID: 1234 (your_process)
    Tasks: 1 (limit: 4915)
   Memory: 1.0M
   CGroup: /system.slice/my-service.service
           └─1234 /path/to/your/command_or_script

(3) 查看服务日志 #

systemd 使用 journal 来记录日志。查看服务的日志:

sudo journalctl -u my-service.service

如果需要实时查看日志输出,可以加上 -f 选项:

sudo journalctl -u my-service.service -f

(4) 禁用和删除服务 #

  • 禁用服务(不再开机启动):

    sudo systemctl disable my-service.service
    
  • 删除服务单元文件:

    如果你不再需要此服务,可以删除其单元文件:

    sudo rm /etc/systemd/system/my-service.service
    

    删除后,也需要重新加载 systemd 配置:

    sudo systemctl daemon-reload
    

(5) 修改服务 #

如果需要修改服务的配置,只需要编辑相应的 .service 文件,修改完成后,重新加载 systemd 配置:

sudo systemctl daemon-reload

然后重新启动服务使其生效:

sudo systemctl restart my-service.service

3. 高级配置 #

(1) 设置服务的资源限制 #

[Service] 部分,你可以设置服务的资源限制,例如 CPU、内存、文件句柄等:

[Service]
LimitCPU=50000     # 限制 CPU 使用,单位:毫秒
LimitMEMLOCK=10000 # 限制内存锁定大小
LimitNOFILE=65536  # 限制打开文件数量

(2) 设置服务的定时重启 #

你可以设置服务在某些条件下自动重启:

[Service]
Restart=always     # 总是重启
RestartSec=10      # 重启间隔 10 秒

(3) 使用 ExecStop 指定停止命令 #

你可以指定停止服务时要执行的命令。例如,如果是脚本或程序,可能需要指定停止命令来清理资源:

[Service]
ExecStop=/path/to/stop_script.sh

(4) 使用 AfterBefore 控制服务的启动顺序 #

你可以通过 AfterBefore 配置项控制服务的启动顺序。例如,如果你的服务依赖于网络,你可以在 [Unit] 部分添加:

[Unit]
After=network.target

4. 总结 #

  • 创建自定义 systemd 服务时,首先需要定义 .service 文件,指定服务的启动命令、资源限制等信息。
  • 使用 systemctl 命令来启动、停止、重启和查看服务状态。
  • 可以使用 journalctl 查看服务日志。
  • 服务单元文件可以按需进行修改,修改后使用 systemctl daemon-reload 更新 systemd 配置。

systemd 服务为你的应用提供了一种简洁而强大的管理方式,不仅适用于常见的后台服务,也可以用于定时任务、脚本执行等场景。

Linux内核模块的加载与卸载过程 #

在 Linux 系统中,内核模块(Kernel Module)是一个能够在运行时被加载和卸载的二进制文件,它们提供了内核的扩展功能,比如硬件驱动、文件系统支持等。通过加载和卸载内核模块,可以动态地修改内核的功能,而不需要重新启动系统。下面是内核模块的加载和卸载过程的详细介绍:

1. 内核模块的加载过程 #

(1) 加载内核模块的命令 #

  • insmod:用于加载内核模块。此命令直接加载模块,模块文件必须指定完整路径,且不会自动处理依赖。

    sudo insmod /path/to/module.ko
    
  • modprobe:用于加载内核模块。相比 insmodmodprobe 会自动处理模块依赖关系,推荐使用这个命令来加载模块。

    sudo modprobe module_name
    

    module_name 是模块的名字,不需要包含 .ko 扩展名,modprobe 会自动查找模块文件。

  • lsmod:用于查看当前已加载的所有内核模块。

    lsmod
    

(2) 加载内核模块的过程 #

  1. 检查依赖关系

    • 当执行 modprobe 命令加载一个模块时,系统会首先检查模块的依赖关系。例如,某些模块可能依赖于其他模块。
    • modprobe 会自动加载所需的依赖模块。如果是通过 insmod 命令手动加载模块,则需要手动加载所有的依赖模块。
  2. 模块文件路径

    • modprobe 会在 /lib/modules/$(uname -r)/ 目录下查找内核模块文件,uname -r 获取当前系统的内核版本。模块文件的后缀通常是 .ko
  3. 加载模块到内核空间

    • 内核模块通过 insmodmodprobe 命令被加载到内核中。内核会解析模块文件的 ELF 格式,进行符号解析,初始化模块的入口函数。
    • 如果模块初始化成功,它将注册自己的功能到内核,例如注册硬件驱动、文件系统类型、系统调用等。
  4. 模块加载的检查

    • 可以使用 dmesg 命令查看加载模块时产生的内核日志,检查模块加载的详细信息。
    dmesg | tail
    
  5. 模块文件的缓存

    • 模块一旦加载到内核中,它们将驻留在内存中,并通过 lsmod 命令显示为已加载的模块。系统会保持模块的状态,直到卸载或系统重启。

(3) 查看加载的模块 #

  • 使用 lsmod 查看已加载的内核模块:

    lsmod
    

    该命令会列出所有当前已加载的模块、模块大小以及模块使用情况等信息。

(4) 动态加载模块 #

某些模块可以配置为在特定的事件发生时自动加载。例如,设备插入时自动加载相应的驱动模块,这通过 udev 机制实现。你可以使用 /etc/modules/etc/modprobe.d/ 下的配置文件,指定模块的自动加载。

2. 内核模块的卸载过程 #

(1) 卸载内核模块的命令 #

  • rmmod:用于卸载已加载的内核模块,卸载时会检查该模块是否存在其他依赖模块。如果该模块是被其他模块所依赖,则无法卸载。

    sudo rmmod module_name
    
  • modprobe -r:用于卸载内核模块,modprobe -r 会自动处理依赖关系,卸载时会自动卸载依赖该模块的其他模块。

    sudo modprobe -r module_name
    

(2) 卸载内核模块的过程 #

  1. 检查模块是否被使用

    • 如果模块正在被使用(例如硬件设备驱动正在驱动设备),则无法直接卸载。此时需要先停止相关的服务或卸载相关依赖模块。
  2. 卸载模块

    • 执行 rmmodmodprobe -r 命令卸载模块。卸载模块时,内核会释放该模块占用的资源,并注销它的功能。
  3. 依赖关系处理

    • 如果有其他模块依赖于该模块,modprobe -r 会先卸载依赖模块,再卸载目标模块。而 rmmod 会检查是否有其他模块依赖于目标模块,如果有依赖,则无法卸载。
  4. 检查内核日志

    • 卸载模块的过程会在内核日志中生成相应的记录,可以通过 dmesg 查看卸载过程中的日志信息。
    dmesg | tail
    
  5. 模块缓存

    • 模块卸载后,它会从内核中释放,内核空间中的相应资源将被回收。如果该模块有注册的功能(如设备驱动),这些功能将不再可用。

(3) 查看当前加载的模块 #

使用 lsmod 查看当前加载的所有模块,确认模块是否已成功卸载。

3. 内核模块管理的其他相关命令 #

  • 查看模块的详细信息

    使用 modinfo 命令查看某个模块的详细信息,如版本、依赖关系等。

    modinfo module_name
    

    例如:

    modinfo e1000
    
  • 查找模块文件位置

    使用 modprobe-n 选项来显示模块的加载路径,但不实际加载模块。

    modprobe -n -v module_name
    
  • 查看模块的依赖关系

    使用 lsmodmodinfo 可以查看模块的依赖关系。

4. 自动加载模块 #

内核模块可以配置为在系统启动时或设备连接时自动加载。这通常通过配置 /etc/modules 或在 /etc/modprobe.d/ 目录下创建配置文件来完成。你也可以在 initramfs 配置中设置内核模块的加载。

5. 总结 #

  • 加载模块:使用 insmodmodprobe 命令加载内核模块,modprobe 自动处理依赖关系。
  • 卸载模块:使用 rmmodmodprobe -r 卸载模块,modprobe -r 会自动处理依赖关系。
  • 查看已加载模块:使用 lsmod 查看当前已加载的模块,使用 dmesg 查看内核日志。
  • 内核模块管理:通过 modinfo 查看模块信息,modprobe -n 查看模块路径等。

内核模块的动态加载和卸载为 Linux 系统提供了极大的灵活性,允许你在不重启系统的情况下为系统添加或移除功能。

ansible roles使用场景,现在有多台机器需要批量加入k8s集群,怎么实现☆ #

Ansible Roles 是一种将配置文件、任务、变量等分组的方式,使得管理更为模块化和可重用。在大规模部署和配置环境时,使用 Ansible Roles 可以提高代码的可维护性和复用性。

Ansible Roles 使用场景 #

  • 模块化管理:当你的任务比较复杂时,可以将其拆分成多个独立的角色,每个角色管理一个特定的功能或模块。例如,管理数据库、Web 服务器、Kubernetes 集群等。
  • 提高重用性:角色的代码结构独立,便于重用。例如,一个 Kubernetes 角色可以在不同环境中重复使用,只需要传入不同的变量。
  • 简化配置和管理:通过角色结构,你可以避免重复配置,并且可以专注于某个模块的功能。

Ansible Roles 目录结构 #

假设你要创建一个名为 k8s-cluster 的角色,安装并配置 Kubernetes,角色的目录结构可能如下:

k8s-cluster/
├── defaults/
│   └── main.yml      # 默认变量
├── files/
│   └── ...           # 需要拷贝的文件
├── handlers/
│   └── main.yml      # 处理器,例如服务重启
├── meta/
│   └── main.yml      # 角色的元数据
├── tasks/
│   └── main.yml      # 任务定义
├── templates/
│   └── ...           # Jinja2 模板文件
├── vars/
│   └── main.yml      # 变量文件

如何用 Ansible 批量将多台机器加入 Kubernetes 集群 #

假设有多台机器,你可以使用 Ansible 批量将这些机器加入 Kubernetes 集群。以下是使用 Ansible Roles 来实现这一目标的基本步骤:

1. 创建 Kubernetes 角色 #

k8s-cluster/ 目录下,编写以下内容:

  • defaults/main.yml(定义默认变量)

    # k8s-cluster/defaults/main.yml
    kube_version: "1.24.0"
    api_server: "https://your-k8s-master-ip:6443"
    kubeconfig_path: "/etc/kubernetes/kubelet.conf"
    
  • tasks/main.yml(执行任务)

    # k8s-cluster/tasks/main.yml
    - name: Install Docker
      package:
        name: docker
        state: present
    
    - name: Add Kubernetes repositories
      shell: |
        curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
        echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" > /etc/apt/sources.list.d/kubernetes.list
    
    - name: Install kubelet, kubeadm, and kubectl
      apt:
        name: "{{ item }}"
        state: present
        update_cache: yes
      loop:
        - kubelet={{ kube_version }}
        - kubeadm={{ kube_version }}
        - kubectl={{ kube_version }}
    
    - name: Initialize Kubernetes Cluster (for master node)
      command: kubeadm init --apiserver-advertise-address={{ ansible_host }} --pod-network-cidr=192.168.0.0/16
      when: inventory_hostname == groups['master'][0]
    
    - name: Join Kubernetes Cluster (for worker nodes)
      command: kubeadm join {{ api_server }} --token {{ join_token }} --discovery-token-ca-cert-hash {{ discovery_token_hash }}
      when: inventory_hostname != groups['master'][0]
    
    - name: Configure kubectl for non-root user
      copy:
        src: "/etc/kubernetes/admin.conf"
        dest: "{{ kubeconfig_path }}"
        mode: "0644"
      when: inventory_hostname == groups['master'][0]
    
    - name: Install Calico network plugin
      shell: kubectl apply -f https://docs.projectcalico.org/v3.19/manifests/calico.yaml
      when: inventory_hostname == groups['master'][0]
    

2. 主机清单(Inventory)文件 #

创建一个 Ansible 主机清单文件 hosts.ini,列出所有的 Kubernetes Master 和 Worker 节点。

[master]
master1 ansible_host=master1_ip

[worker]
worker1 ansible_host=worker1_ip
worker2 ansible_host=worker2_ip

3. 创建 Playbook #

编写一个 Playbook 来使用 k8s-cluster 角色来配置节点:

---
- name: Configure Kubernetes Cluster
  hosts: all
  become: true
  roles:
    - k8s-cluster

4. 运行 Playbook #

使用 Ansible 执行 Playbook 来将多台机器加入到 Kubernetes 集群中:

ansible-playbook -i hosts.ini k8s-cluster-setup.yml

这个命令会根据 hosts.ini 中定义的主机信息,批量配置每个节点,并按照定义的角色来安装 Kubernetes 和网络插件。

总结 #

  1. Ansible Roles 使得管理大规模集群和复杂配置变得更简单,可以将不同的配置和功能模块化,便于管理。
  2. 批量加入 Kubernetes 集群:通过使用 Ansible,你可以在多个节点上执行 kubeadm initkubeadm join 命令来批量初始化或加入 Kubernetes 集群。
  3. 自动化运维:使用 Ansible Playbooks 和 Roles 来实现 Kubernetes 集群的自动化管理,降低手动配置的复杂度,提升运维效率。

这样,你就可以使用 Ansible 角色来高效地管理 Kubernetes 集群的节点,批量配置并将机器加入集群。

了解了!如果你需要包括 Docker 和 kube-proxy 的分发步骤,并且要将它们配置成 systemd 服务来管理,下面是一个完整的解决方案。

1. 准备角色和任务 #

(1) 角色目录结构 #

k8s-deploy/
├── defaults/
│   └── main.yml          # 默认变量文件
├── files/
│   ├── kubelet           # 二进制文件
│   ├── kubectl           # 二进制文件
│   ├── kubeconfig        # kubeconfig 文件(包含集群的配置信息)
│   ├── kubelet.crt       # kubelet 证书
│   ├── kubelet.key       # kubelet 私钥
│   ├── kube-proxy        # kube-proxy 二进制文件
│   ├── docker-ce         # Docker 二进制文件(如果使用的是自定义 Docker 版本)
│   ├── etc...            # 其他配置文件
├── tasks/
│   └── main.yml          # 安装和分发文件的任务
├── handlers/
│   └── main.yml          # 处理器(如重启服务等)
└── vars/
    └── main.yml          # 变量文件(如文件路径、Kubernetes 版本等)

(2) 默认变量文件(defaults/main.yml) #

# k8s-deploy/defaults/main.yml
k8s_bin_dir: "/usr/local/bin"
docker_version: "20.10.8"
docker_package: "docker.io"
docker_service: "docker"
kubeconfig_path: "/etc/kubernetes/kubelet.conf"
kube_proxy_bin: "/usr/local/bin/kube-proxy"

(3) 任务文件(tasks/main.yml) #

这部分包括 Docker、kube-proxykubelet 的安装和配置,及其作为 systemd 服务进行管理。

# k8s-deploy/tasks/main.yml

# 1. 安装 Docker
- name: Install Docker
  package:
    name: "{{ docker_package }}"
    state: present

- name: Start and enable Docker service
  systemd:
    name: "{{ docker_service }}"
    state: started
    enabled: true

# 2. 分发 Kubernetes 二进制文件
- name: Distribute kubelet binary
  copy:
    src: "files/kubelet"
    dest: "{{ k8s_bin_dir }}/kubelet"
    mode: '0755'

- name: Distribute kubectl binary
  copy:
    src: "files/kubectl"
    dest: "{{ k8s_bin_dir }}/kubectl"
    mode: '0755'

- name: Distribute kube-proxy binary
  copy:
    src: "files/kube-proxy"
    dest: "{{ k8s_bin_dir }}/kube-proxy"
    mode: '0755'

# 3. 分发证书和配置文件
- name: Distribute kubelet certificate
  copy:
    src: "files/kubelet.crt"
    dest: "/etc/kubernetes/kubelet.crt"
    mode: '0644'

- name: Distribute kubelet private key
  copy:
    src: "files/kubelet.key"
    dest: "/etc/kubernetes/kubelet.key"
    mode: '0600'

- name: Distribute kubeconfig file
  copy:
    src: "files/kubeconfig"
    dest: "{{ kubeconfig_path }}"
    mode: '0644'

# 4. 分发 Docker 配置文件(如果有自定义配置)
- name: Distribute Docker configuration file (if needed)
  copy:
    src: "files/docker_config.json"
    dest: "/etc/docker/daemon.json"
    mode: '0644'
  when: docker_config is defined

# 5. 配置并启动 kubelet 服务
- name: Enable and start kubelet service
  systemd:
    name: kubelet
    enabled: true
    state: started
    daemon_reload: yes

# 6. 配置并启动 kube-proxy 服务
- name: Enable and start kube-proxy service
  systemd:
    name: kube-proxy
    enabled: true
    state: started
    daemon_reload: yes

(4) 处理器(handlers/main.yml) #

# k8s-deploy/handlers/main.yml
- name: Restart kubelet
  systemd:
    name: kubelet
    state: restarted
    daemon_reload: yes

- name: Restart kube-proxy
  systemd:
    name: kube-proxy
    state: restarted
    daemon_reload: yes

2. systemd 服务配置文件 #

为了确保 kubeletkube-proxy 可以作为 systemd 服务管理,你需要为它们创建 systemd 服务文件。这些服务文件将被复制到 /etc/systemd/system/ 目录,并在必要时启动。

(1) kubelet.service 配置 #

创建 kubelet.service 文件:

# /etc/systemd/system/kubelet.service
[Unit]
Description=kubelet: The Kubernetes Node Agent
Documentation=https://kubernetes.io/docs/
After=network.target

[Service]
ExecStart=/usr/local/bin/kubelet --kubeconfig=/etc/kubernetes/kubelet.conf --config=/etc/kubernetes/kubelet.config.yaml
Restart=always
RestartSec=10
LimitNOFILE=65536
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target

(2) kube-proxy.service 配置 #

创建 kube-proxy.service 文件:

# /etc/systemd/system/kube-proxy.service
[Unit]
Description=kube-proxy: The Kubernetes Network Proxy
Documentation=https://kubernetes.io/docs/
After=network.target

[Service]
ExecStart=/usr/local/bin/kube-proxy --kubeconfig=/etc/kubernetes/kubelet.conf
Restart=always
RestartSec=10
LimitNOFILE=65536
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target

3. 主机清单文件 #

确保在 hosts.ini 文件中列出 Master 和 Worker 节点:

[master]
master1 ansible_host=master1_ip

[worker]
worker1 ansible_host=worker1_ip
worker2 ansible_host=worker2_ip

4. Playbook 配置 #

编写 Playbook 来使用 k8s-deploy 角色来安装和配置 Kubernetes。

---
- name: Install and Configure Kubernetes on all nodes
  hosts: all
  become: true
  roles:
    - k8s-deploy

5. 执行 Playbook #

执行 Playbook 将 Kubernetes 配置文件、证书以及二进制文件分发到所有节点,并启动 kubeletkube-proxy 服务:

ansible-playbook -i hosts.ini k8s-deploy.yml

6. 总结 #

  • 分发二进制文件:通过 copy 模块将 kubeletkubectlkube-proxy 等二进制文件分发到每台机器。
  • 分发证书和配置文件:将 kubelet 证书、kubeconfig 配置文件等分发到目标节点,确保它们能够通过 kubeconfig 自动连接到集群。
  • 安装和配置 Docker:通过 package 模块安装 Docker,并根据需要配置 Docker。
  • systemd 服务管理:为 kubeletkube-proxy 创建 systemd 服务文件,并通过 systemd 来管理它们的启动、停止、重启等。
  • 启动服务:通过 systemd 启动 kubeletkube-proxy 服务,使它们自动连接到 Kubernetes 集群。

通过这种方式,你可以确保所有的 Kubernetes 组件(如 kubelet, kube-proxy)以及 Docker 服务都能通过 systemd 来进行管理,提供高效的自动化部署过程。

Docker 二进制文件主要包括以下几个核心组件,它们一起协作完成 Docker 容器的管理和运行:

1. dockerd (Docker Daemon) #

  • 作用dockerd 是 Docker 守护进程,它负责管理 Docker 容器的生命周期,监听 Docker API 请求,创建、启动、停止容器等操作。
  • 路径:通常安装在 /usr/local/bin/dockerd/usr/bin/dockerd

2. docker (Docker CLI) #

  • 作用docker 是 Docker 客户端命令行工具,用户通过该命令行工具与 Docker 守护进程进行交互。它提供了常见的 Docker 命令,如 docker rundocker psdocker build 等。
  • 路径:通常安装在 /usr/local/bin/docker/usr/bin/docker

3. containerd (Container Runtime) #

  • 作用containerd 是容器运行时,Docker 使用它来执行容器的创建、调度、运行和监控。它是 Docker 核心的一个组件,负责容器的生命周期管理。
  • 路径:通常安装在 /usr/local/bin/containerd/usr/bin/containerd

4. runc (Low-level Container Runtime) #

  • 作用runc 是一个低级别的容器运行时,负责容器的创建、启动、停止等操作。它由 Docker 使用,并且也是 Kubernetes 采用的容器运行时之一(通过 containerd 或直接使用)。
  • 路径:通常安装在 /usr/local/bin/runc/usr/bin/runc

5. ctr (Containerd CLI) #

  • 作用ctr 是 containerd 的命令行工具,主要用于与 containerd 交互,执行容器的调试、管理等操作。它比 Docker 命令行工具更底层,通常用于调试和测试。
  • 路径:通常安装在 /usr/local/bin/ctr

6. dockerd 配置文件和插件 #

  • 插件:Docker 的功能通过插件机制进行扩展,插件包括网络插件、存储插件、日志插件等。它们通常位于 /usr/lib/docker//etc/docker/ 目录下。
  • 配置文件:Docker 的配置文件通常位于 /etc/docker/daemon.json,用于配置 Docker 守护进程的行为(例如,指定镜像仓库、日志驱动、存储驱动等)。

7. docker-compose (用于管理多容器应用) #

  • 作用docker-compose 是一个独立的命令行工具,用于定义和管理 Docker 容器的多容器应用(例如,定义应用的服务、网络、卷等)。它通过一个 YAML 文件 docker-compose.yml 来进行容器的编排和管理。
  • 路径:通常安装在 /usr/local/bin/docker-compose/usr/bin/docker-compose

常见安装路径: #

  • dockerddockercontainerdrunc 等二进制文件通常位于 /usr/local/bin//usr/bin/

  • 你可以通过

    which
    

    命令查看这些二进制文件的安装路径:

    which dockerd
    which docker
    which containerd
    which runc
    

Docker 依赖的其他组件: #

  • libcontainer:用于实现容器的基本功能。
  • aufsbtrfsoverlayzfs 等存储驱动:Docker 支持多种存储驱动,每种驱动有不同的性能和特性。
  • network plugins:Docker 网络插件,用于为容器提供网络支持。

总结 #

Docker 的核心二进制文件主要包括 dockerd(守护进程)、docker(命令行客户端)、containerd(容器运行时)和 runc(底层容器运行时)。如果使用二进制方式安装 Docker,通常需要将这些二进制文件放置在合适的路径,并配置相应的 systemd 服务文件来启动和管理它们。

- name: 创建kube_node 相关目录
  file: name={{ item }} state=directory
  with_items:
  - /var/lib/kubelet
  - /var/lib/kube-proxy
  - /etc/cni/net.d
  - /opt/cni/bin

- name: 下载 kubelet,kube-proxy 二进制
  copy: src={{ base_dir }}/bin/{{ item }} dest={{ bin_dir }}/{{ item }} mode=0755
  with_items:
  - kubectl
  - kubelet
  - kube-proxy
  tags: upgrade_k8s

- name: 下载 cni plugins 二进制文件
  copy: src={{ item }} dest=/opt/cni/bin/ mode=0755
  with_fileglob:
  - "{{ base_dir }}/bin/cni-bin/*"

- name: 添加 kubectl 自动补全
  lineinfile:
    dest: ~/.bashrc
    state: present
    regexp: 'kubectl completion'
    line: 'source <(kubectl completion bash) # generated by kubeasz'

##----------kubelet 配置部分--------------
# 创建 kubelet 相关证书及 kubelet.kubeconfig
- import_tasks: create-kubelet-kubeconfig.yml
  tags: force_change_certs

- name: 准备 cni配置文件
  template: src=cni-default.conf.j2 dest=/etc/cni/net.d/10-default.conf

- name: 创建kubelet的配置文件
  template: src=kubelet-config.yaml.j2 dest=/var/lib/kubelet/config.yaml
  tags: upgrade_k8s, restart_node

- name: 检查文件/run/systemd/resolve/resolv.conf
  stat: path="/run/systemd/resolve/resolv.conf"
  register: resolv
  tags: upgrade_k8s, restart_node

- name: 替换resolvConf 配置
  lineinfile:
    dest: /var/lib/kubelet/config.yaml
    state: present
    regexp: 'resolvConf'
    line: 'resolvConf: /run/systemd/resolve/resolv.conf'
  when: "resolv.stat.isreg is defined"
  tags: upgrade_k8s, restart_node

- name: 创建kubelet的systemd unit文件
  template: src=kubelet.service.j2 dest=/etc/systemd/system/kubelet.service
  tags: upgrade_k8s, restart_node

- name: 开机启用kubelet 服务
  shell: systemctl enable kubelet
  ignore_errors: true

- name: 开启kubelet 服务
  shell: systemctl daemon-reload && systemctl restart kubelet
  tags: upgrade_k8s, restart_node, force_change_certs

##-------kube-proxy部分----------------
- name: 分发 kube-proxy.kubeconfig配置文件
  copy: src={{ cluster_dir }}/kube-proxy.kubeconfig dest=/etc/kubernetes/kube-proxy.kubeconfig
  tags: force_change_certs

- name: 替换 kube-proxy.kubeconfig 的 apiserver 地址
  lineinfile:
    dest: /etc/kubernetes/kube-proxy.kubeconfig
    regexp: "^    server"
    line: "    server: {{ KUBE_APISERVER }}"
  tags: force_change_certs

- name: 创建kube-proxy 配置
  template: src=kube-proxy-config.yaml.j2 dest=/var/lib/kube-proxy/kube-proxy-config.yaml
  tags: reload-kube-proxy, restart_node, upgrade_k8s

- name: 创建kube-proxy 服务文件
  template: src=kube-proxy.service.j2 dest=/etc/systemd/system/kube-proxy.service
  tags: reload-kube-proxy, restart_node, upgrade_k8s

- name: 开机启用kube-proxy 服务
  shell: systemctl enable kube-proxy
  ignore_errors: true

- name: 开启kube-proxy 服务
  shell: systemctl daemon-reload && systemctl restart kube-proxy
  tags: reload-kube-proxy, upgrade_k8s, restart_node, force_change_certs

# 轮询等待kube-proxy启动完成
- name: 轮询等待kube-proxy启动
  shell: "systemctl is-active kube-proxy.service"
  register: kubeproxy_status
  until: '"active" in kubeproxy_status.stdout'
  retries: 4
  delay: 2
  tags: reload-kube-proxy, upgrade_k8s, restart_node, force_change_certs

# 轮询等待kubelet启动完成
- name: 轮询等待kubelet启动
  shell: "systemctl is-active kubelet.service"
  register: kubelet_status
  until: '"active" in kubelet_status.stdout'
  retries: 4
  delay: 2
  tags: reload-kube-proxy, upgrade_k8s, restart_node, force_change_certs

- name: 轮询等待node达到Ready状态
  shell: "{{ base_dir }}/bin/kubectl get node {{ K8S_NODENAME }}|awk 'NR>1{print $2}'"
  register: node_status
  until: node_status.stdout == "Ready" or node_status.stdout == "Ready,SchedulingDisabled"
  retries: 8 
  delay: 8
  tags: upgrade_k8s, restart_node, force_change_certs
  connection: local

- block:
  - name: Setting worker role name
    shell: "{{ base_dir }}/bin/kubectl label node {{ K8S_NODENAME }} kubernetes.io/role=node --overwrite"

  - name: Setting master role name
    shell: "{{ base_dir }}/bin/kubectl label node {{ K8S_NODENAME }} kubernetes.io/role=master --overwrite"
    when: "inventory_hostname in groups['kube_master']"

  - name: Making master nodes SchedulingDisabled
    shell: "{{ base_dir }}/bin/kubectl cordon {{ K8S_NODENAME }} "
    when: "inventory_hostname not in groups['kube_node']"
  ignore_errors: true
  connection: local

这个 Ansible Playbook 是用于将一个新节点加入 Kubernetes 集群的过程,涵盖了以下几个步骤:

1. 创建必要的目录 #

  • 创建节点相关目录

    - name: 创建kube_node 相关目录
      file: name={{ item }} state=directory
      with_items:
      - /var/lib/kubelet
      - /var/lib/kube-proxy
      - /etc/cni/net.d
      - /opt/cni/bin
    

    这些目录是 Kubernetes 组件(如

    kubelet
    

    kube-proxy
    

    )运行所必需的,确保新节点有这些目录。

2. 下载二进制文件 #

  • 下载 kubelet, kube-proxy, kubectl

    - name: 下载 kubelet,kube-proxy 二进制
      copy: src={{ base_dir }}/bin/{{ item }} dest={{ bin_dir }}/{{ item }} mode=0755
      with_items:
      - kubectl
      - kubelet
      - kube-proxy
    

    kubelet, kube-proxy, kubectl 等二进制文件分发到目标节点,确保节点能够与 Kubernetes 集群进行通信和管理。

  • 下载 CNI 插件

    - name: 下载 cni plugins 二进制文件
      copy: src={{ item }} dest=/opt/cni/bin/ mode=0755
      with_fileglob:
      - "{{ base_dir }}/bin/cni-bin/*"
    

    CNI 插件用于管理容器网络。

3. 配置文件设置 #

  • 添加 kubectl 自动补全

    - name: 添加 kubectl 自动补全
      lineinfile:
        dest: ~/.bashrc
        state: present
        regexp: 'kubectl completion'
        line: 'source <(kubectl completion bash) # generated by kubeasz'
    

    kubectl
    

    设置自动补全功能,方便后续操作。

4. 配置 Kubelet #

  • 创建 Kubelet 配置文件

    - name: 创建kubelet的配置文件
      template: src=kubelet-config.yaml.j2 dest=/var/lib/kubelet/config.yaml
    

    创建 kubelet 的配置文件,配置节点如何加入 Kubernetes 集群。

  • 配置 DNS 设置

    - name: 替换resolvConf 配置
      lineinfile:
        dest: /var/lib/kubelet/config.yaml
        state: present
        regexp: 'resolvConf'
        line: 'resolvConf: /run/systemd/resolve/resolv.conf'
    

    修改 kubelet 配置文件,确保 DNS 配置正确。

  • 创建 kubeletsystemd 服务文件

    - name: 创建kubelet的systemd unit文件
      template: src=kubelet.service.j2 dest=/etc/systemd/system/kubelet.service
    

    创建 kubeletsystemd 服务文件,以便通过 systemd 管理服务的启动和停止。

  • 启用并启动 kubelet 服务

    - name: 开机启用kubelet 服务
      shell: systemctl enable kubelet
      ignore_errors: true
    
    - name: 开启kubelet 服务
      shell: systemctl daemon-reload && systemctl restart kubelet
    

5. 配置 Kube-proxy #

  • 下载并配置 kube-proxy.kubeconfig

    - name: 分发 kube-proxy.kubeconfig配置文件
      copy: src={{ cluster_dir }}/kube-proxy.kubeconfig dest=/etc/kubernetes/kube-proxy.kubeconfig
    

    配置 kube-proxykubeconfig,使其能够与 Kubernetes 集群进行通信。

  • 创建 kube-proxy 配置文件

    - name: 创建kube-proxy 配置
      template: src=kube-proxy-config.yaml.j2 dest=/var/lib/kube-proxy/kube-proxy-config.yaml
    
  • 创建 kube-proxy 服务文件

    - name: 创建kube-proxy 服务文件
      template: src=kube-proxy.service.j2 dest=/etc/systemd/system/kube-proxy.service
    
  • 启用并启动 kube-proxy 服务

    - name: 开机启用kube-proxy 服务
      shell: systemctl enable kube-proxy
      ignore_errors: true
    
    - name: 开启kube-proxy 服务
      shell: systemctl daemon-reload && systemctl restart kube-proxy
    

6. 服务启动监控与等待 #

  • 轮询等待 kube-proxy 启动完成

    - name: 轮询等待kube-proxy启动
      shell: "systemctl is-active kube-proxy.service"
      register: kubeproxy_status
      until: '"active" in kubeproxy_status.stdout'
      retries: 4
      delay: 2
    
  • 轮询等待 kubelet 启动完成

    - name: 轮询等待kubelet启动
      shell: "systemctl is-active kubelet.service"
      register: kubelet_status
      until: '"active" in kubelet_status.stdout'
      retries: 4
      delay: 2
    
  • 轮询等待节点状态变为 Ready

    - name: 轮询等待node达到Ready状态
      shell: "{{ base_dir }}/bin/kubectl get node {{ K8S_NODENAME }}|awk 'NR>1{print $2}'"
      register: node_status
      until: node_status.stdout == "Ready" or node_status.stdout == "Ready,SchedulingDisabled"
      retries: 8 
      delay: 8
    

7. 标签和调度控制 #

  • 设置节点角色标签

    - name: Setting worker role name
      shell: "{{ base_dir }}/bin/kubectl label node {{ K8S_NODENAME }} kubernetes.io/role=node --overwrite"
    
  • 为主节点设置调度禁用

    - name: Making master nodes SchedulingDisabled
      shell: "{{ base_dir }}/bin/kubectl cordon {{ K8S_NODENAME }} "
      when: "inventory_hostname not in groups['kube_node']"
    

总结 #

这个 Playbook 的步骤主要是:

  1. 创建 Kubernetes 节点所需的目录和配置。
  2. 下载并配置所需的二进制文件(kubeletkube-proxykubectl 和 CNI 插件)。
  3. 配置并启用 kubeletkube-proxy 服务,确保它们在系统启动时自动启动。
  4. 检查并等待 kubeletkube-proxy 启动成功,并确保节点的状态变为 Ready
  5. 设置节点角色标签,并在必要时对主节点执行调度禁用(cordon)操作。

总体来看,它是一个自动化的 Kubernetes 节点加入过程,配置并启动了节点所需的所有服务,使节点能够顺利加入 Kubernetes 集群。