<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>褚成志</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <icon>https://www.chucz.asia/img/favicon.png</icon>
  <id>https://www.chucz.asia/</id>
  <link href="https://www.chucz.asia/" rel="alternate"/>
  <link href="https://www.chucz.asia/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, 褚成志</rights>
  <subtitle>
    <![CDATA[Cloud Native & AGI Enthusiast]]>
  </subtitle>
  <title>褚成志的分享站</title>
  <updated>2026-04-09T16:00:00.000Z</updated>
  <entry>
    <author>
      <name>褚成志</name>
    </author>
    <category term="工具" scheme="https://www.chucz.asia/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="Java" scheme="https://www.chucz.asia/tags/Java/"/>
    <category term="SPI" scheme="https://www.chucz.asia/tags/SPI/"/>
    <category term="Git" scheme="https://www.chucz.asia/tags/Git/"/>
    <category term="IO" scheme="https://www.chucz.asia/tags/IO/"/>
    <category term="JSON" scheme="https://www.chucz.asia/tags/JSON/"/>
    <category term="Spring" scheme="https://www.chucz.asia/tags/Spring/"/>
    <content>
      <![CDATA[<h2 id="SpringAI-MCP介绍"><a href="#SpringAI-MCP介绍" class="headerlink" title="SpringAI MCP介绍"></a>SpringAI MCP介绍</h2><p>Spring AI MCP 为模型上下文协议提供 Java 和Spring 框架集成、它使 SpringAI 应用程序能够通过标准化的接口与不同的数据源和工是进行交互，支持同步和异步通信模式。整体架构如下:</p><p><img src="https://cdn.chucz.asia/blog/c4aae11d0a55e492c020.webp"></p><p>Spring Al 通过以下 Spring Boot 启动器提供 MCP 集成：</p><p>客户端启动器</p><ul><li><p>spring-ai-starter-mcp-client 核心启动器提供 STDIO 和基于 HTTP 的 SSE 支持。</p></li><li><p>spring-ai-starter-mcp-client-webflux 基于WebFlux的SSE流式传输实现</p></li></ul><p>服务端启动器</p><ul><li><p>spring-ai-starter-mcp-server 核心服务器具有 STDIO 传输支持</p></li><li><p>spring-ai-starter-mcp-server-webmvc 基于Spring MVC的SSE流式传输实现</p></li><li><p>spring-ai-starter-mcp-server-webflux 基于WebFlux的SSE流式传输实现</p></li></ul><h2 id="基于stdio标准流"><a href="#基于stdio标准流" class="headerlink" title="基于stdio标准流"></a>基于stdio标准流</h2><h3 id="MCP-服务端"><a href="#MCP-服务端" class="headerlink" title="MCP 服务端"></a>MCP 服务端</h3><p>基于 stdio 的实现是最常见的 MCP客户端方案，它通过标准输入输出流与 MCP 服务器进行通信，这种方式简单直观，能够直接通过进程间通信实现数据交互，避免了额外的网络通信开销，特别适用于本地部署的MCP服务器，可以在司一台机器上启动 MCP 服务器进程，与客户端无缝对接。</p><p><strong>引入依赖</strong></p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">org.springframework.ai</span><br><span class="line">spring-ai-mcp-server-spring-boot-starter</span><br><span class="line">1.0.0-M6</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>配置MCP服务端</strong></p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">application:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">mcp-server</span></span><br><span class="line">  <span class="attr">main:</span></span><br><span class="line">    <span class="attr">web-application-type:</span> <span class="string">none</span> <span class="comment"># 必须禁用web应用类型</span></span><br><span class="line">    <span class="attr">banner-mode:</span> <span class="string">off</span> <span class="comment"># 禁用banner</span></span><br><span class="line">  <span class="attr">ai:</span></span><br><span class="line">    <span class="attr">mcp:</span></span><br><span class="line">      <span class="attr">server:</span></span><br><span class="line">        <span class="attr">stdio:</span> <span class="literal">true</span> <span class="comment"># 启用stdio模式</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">mcp-server</span> <span class="comment"># 服务器名称</span></span><br><span class="line">        <span class="attr">version:</span> <span class="number">0.0</span><span class="number">.1</span> <span class="comment"># 服务器版本</span></span><br></pre></td></tr></table></figure><p><strong>实现MCP工具</strong></p><p>@Tool 是 SpingAI MCP框架中用于快速暴露业务能力为AI 工具的核心注解，该注解实现Java方法与MCP协议工具的自动银蛇，并且可以通过注解的属性description，有助于人工智能模型根据用户输入的信息决定是否调用这些工具，并返回相应的结果.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OpenMeteoService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Tool(description = &quot;根据经纬度获取天气预报&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getAirQuality</span><span class="params">(</span></span><br><span class="line"><span class="params">        <span class="meta">@ToolParameter(description = &quot;纬度，例如：39.9042&quot;)</span> String latitude,</span></span><br><span class="line"><span class="params">        <span class="meta">@ToolParameter(description = &quot;经度，例如：116.4074&quot;)</span> String longitude)</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 模拟数据，实际应用中应调用真实API</span></span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;当前位置（纬度：&quot;</span> + latitude + <span class="string">&quot;，经度：&quot;</span> + longitude + <span class="string">&quot;）的天气信息：\n 多云转阴&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个工具方法主要是用来根据经纬度获取天气预报的，这里为了方便演示，写了模拟数据</p><p><strong>注册MCP工具</strong></p><p>最后向 MCP 服务注册刚刚写的工具：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> ToolCallbackProvider <span class="title function_">serverTools</span><span class="params">(OpenMeteoService openMeteoService)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> MethodToolCallbackProvider.builder().toolObjects(openMeteoService).build();</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>这段代码定义了一个 Spring 的 Bean，用于将查询天气服务 OpenMeteoService 中所有用 @Tool 注解标记的方法注册为工具，供 AI 模型调用。</p><p>ToolCallbackProvider 是Spring Al 中的一个接口，用于定义工具发现机制，主要负责将那些使用</p><p>@Tool 注解标记的方法转换为工具回调对象，并提供给 ChatClient 或ChatModel 使用，以便 AI 模型能够在对话过程中调用这些工具。</p><h3 id="MCP-客户端"><a href="#MCP-客户端" class="headerlink" title="MCP 客户端"></a>MCP 客户端</h3><p><strong>引入依赖</strong></p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">org.springframework.ai</span><br><span class="line">spring-ai-mcp-client-spring-boot-starter</span><br><span class="line">1.0.0-M6</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>配置MCP服务器</strong></p><p>因为服务端是通过 stdio 实现的，需要在 application.yml 中配置MCP服务器的一些参数：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">ai:</span></span><br><span class="line">    <span class="attr">mcp:</span></span><br><span class="line">      <span class="attr">client:</span></span><br><span class="line">        <span class="attr">stdio:</span></span><br><span class="line">          <span class="comment"># 指定MCP服务器配置文件</span></span><br><span class="line">          <span class="attr">servers-configuration:</span> <span class="string">classpath:/mcp-servers-config.json</span></span><br><span class="line">  <span class="attr">mandatory-file-encoding:</span> <span class="string">UTF-8</span></span><br></pre></td></tr></table></figure><p>其中 mcp-servers-config.json 的配置如下：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;mcpServers&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;weatherServer&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;java&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;-Dspring.ai.mcp.server.stdio=true&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;-Dspring.main.web-application-type=none&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;-Dlogging.pattern.console=&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;-jar&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;/Users/gulihua/Documents/mcp-server/target/mcp-server-0.0.1-SNAPSHOT.jar&quot;</span></span><br><span class="line">      <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;env&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>这个配置文件设置了MCP客户端的基本配置，包括 Java 命令参数，服务端 jar 包的绝对路径等，上述的 JSON 配置文件也可以直接写在 apllication.yaml 里，效果是一样的。</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">mcp:</span></span><br><span class="line">      <span class="attr">client:</span></span><br><span class="line">        <span class="attr">stdio:</span></span><br><span class="line">         <span class="attr">connections:</span></span><br><span class="line">           <span class="attr">server1:</span></span><br><span class="line">             <span class="attr">command:</span> <span class="string">java</span></span><br><span class="line">             <span class="attr">args:</span></span><br><span class="line">               <span class="bullet">-</span> <span class="string">-Dspring.ai.mcp.server.stdio=true</span></span><br><span class="line">               <span class="bullet">-</span> <span class="string">-Dspring.main.web-application-type=none</span></span><br><span class="line">               <span class="bullet">-</span> <span class="string">-Dlogging.pattern.console=</span></span><br><span class="line">               <span class="bullet">-</span> <span class="string">-jar</span></span><br><span class="line">               <span class="bullet">-</span> <span class="string">/Users/gulihua/Documents/mcp-server/target/mcp-server-0.0.1-SNAPSHOT.jar</span></span><br></pre></td></tr></table></figure><p>客户端我们使用问里巴巴的通义千问模型，所以引入 spring-ai-alibaba-starter 依赖，如果使用的是其他的模型，也可以使用对应的依赖项，比加 openAI 引入  sprine-ai-openai-spring-boot-starter 这个依赖就行了</p><p>配置大模型的密钥等信息：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">ai:</span></span><br><span class="line">    <span class="attr">dashscope:</span></span><br><span class="line">      <span class="attr">api-key:</span> <span class="string">$&#123;通义千问的key&#125;</span></span><br><span class="line">      <span class="attr">chat:</span></span><br><span class="line">        <span class="attr">options:</span></span><br><span class="line">          <span class="attr">model:</span> <span class="string">qwen-max</span></span><br></pre></td></tr></table></figure><p><strong>初始化聊天客户端</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> ChatClient <span class="title function_">initChatClient</span><span class="params">(ChatClient.Builder chatClientBuilder,</span></span><br><span class="line"><span class="params">                                 ToolCallbackProvider mcpTools)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> chatClientBuilder</span><br><span class="line">    .defaultTools(mcpTools)</span><br><span class="line">    .build();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该代码定义了一个 spring pean，用于初始化一个AI聊天客户端，里面有两个参数，chatcient.Buinider 是 SpnngAI 提供的AI聊天客户端构建器，用于构建 ChatCient实例，是由 Spring AI 自动注入的，另一个是 ToolCallbackProvider，用于从MCP客服端发现并获取AI工具。</p><p>然后就可以通过这个 chatclient 去调用了：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">chatClient.prompt()</span><br><span class="line">.user(request.getContent())</span><br><span class="line">.call()</span><br><span class="line">.content();</span><br></pre></td></tr></table></figure><h2 id="基于SSE"><a href="#基于SSE" class="headerlink" title="基于SSE"></a>基于SSE</h2><h3 id="MCP服务端"><a href="#MCP服务端" class="headerlink" title="MCP服务端"></a>MCP服务端</h3><p>除了基于 stdio 的实现外，Spring Al还提供了基于 Server-Sent vents(SSE)的 MCP客户端方案。相较于 stdio方式，SSE 更适用于远程部署的 MCP 服务器，客户端可以通过标准 HTTP 协议与服务器建立连接，实现单向的实时数据推送。基于 SSE的 MCP 服务器支持被多个客户端的远程调用。</p><p><strong>引入依赖</strong></p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">org.springframework.ai</span><br><span class="line">spring-ai-mcp-server-webflux-spring-boot-starter</span><br><span class="line">1.0.0-M6</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>配置MCP服务端</strong></p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8090</span></span><br><span class="line"></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">application:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">mcp-server</span></span><br><span class="line">  <span class="attr">ai:</span></span><br><span class="line">    <span class="attr">mcp:</span></span><br><span class="line">      <span class="attr">server:</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">mcp-server</span> <span class="comment"># MCP服务器名称</span></span><br><span class="line">        <span class="attr">version:</span> <span class="number">0.0</span><span class="number">.1</span>   <span class="comment"># 服务器版本号</span></span><br></pre></td></tr></table></figure><p>除了引入的依赖包不一样，以及配置文件不同，其他的不需要修改。</p><h3 id="MCP-客户端-1"><a href="#MCP-客户端-1" class="headerlink" title="MCP 客户端"></a>MCP 客户端</h3><p><strong>引入依赖</strong></p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">org.springframework.ai</span><br><span class="line">spring-ai-mcp-client-webflux-spring-boot-starter</span><br><span class="line">1.0.0-M6</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>配置MCP服务器</strong></p><p>因为服务端是通过SSE实现的，需要在 application.yml 中配置MCP服务器的URL端口：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">ai:</span></span><br><span class="line">    <span class="attr">mcp:</span></span><br><span class="line">      <span class="attr">client:</span></span><br><span class="line">        <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">mcp-client</span></span><br><span class="line">        <span class="attr">version:</span> <span class="number">1.0</span><span class="number">.0</span></span><br><span class="line">        <span class="attr">request-timeout:</span> <span class="string">30s</span></span><br><span class="line">        <span class="attr">type:</span> <span class="string">ASYNC</span> <span class="comment"># 类型同步或者异步</span></span><br><span class="line">        <span class="attr">sse:</span></span><br><span class="line">          <span class="attr">connections:</span></span><br><span class="line">            <span class="attr">server1:</span></span><br><span class="line">              <span class="attr">url:</span> <span class="string">http://localhost:8090</span></span><br></pre></td></tr></table></figure><p>和MCP服务端的修改一样，除了依赖和配置的修改，其他的也不需要调整</p><h2 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h2><p>除了上面基础的用法和配置，还应该考虑以下几个方面:</p><ul><li>工具设计</li></ul><p>每个工具方法应具备明确的功能定义及参数说明。</p><ul><li><p>使用 @Tool 注解提供清晰、完整的工具描述，便于自动生成文档或展示给前端。</p></li><li><p>使用 @ToolParameter 注解详细说明每个参数的用途，提升使用者的理解与正确性。</p></li></ul><p>错误处理</p><ul><li><p>应全面捕获并妥善处理可能出现的异常，防止服务崩溃。</p></li><li><p>返回结构化、具备可读性的错误信息，便于客户端识别错误原因并进行相应处理。</p></li></ul><p>性能优化</p><ul><li><p>对于可能耗时的任务，建议使用异步处理机制，避免阻塞主线程，</p></li><li><p>设置合理的超时时间，防止客户端长时间等待，提高系统响应性和稳定性。</p></li></ul><p>安全性考虑</p><ul><li><p>对涉及敏感资源或关键操作的工具方法，应添加严格的权限校验逻辑</p></li><li><p>禁止在工具方法中执行高风险操作(如执行任意系统命令)，以防止安全洞。</p></li></ul><p>部署策略</p><ul><li><p>Stdio 模式：适用于嵌入式场景，可作为客户端的子进程运行，便于集成与资源控制。</p></li><li><p>SSE模式：更适合部署为独立服务，支持多个客户端同时访问，适用于需要持续通信的远程调用场景。</p></li></ul>]]>
    </content>
    <id>https://www.chucz.asia/2026/04/09/Spring%20AI%20%E6%A1%86%E6%9E%B6%E4%B8%AD%E5%A6%82%E4%BD%95%E9%9B%86%E6%88%90%20MCP/</id>
    <link href="https://www.chucz.asia/2026/04/09/Spring%20AI%20%E6%A1%86%E6%9E%B6%E4%B8%AD%E5%A6%82%E4%BD%95%E9%9B%86%E6%88%90%20MCP/"/>
    <published>2026-04-09T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="SpringAI-MCP介绍"><a href="#SpringAI-MCP介绍" class="headerlink" title="SpringAI MCP介绍"></a>SpringAI MCP介绍</h2><p>Spring AI MCP]]>
    </summary>
    <title>Spring AI 框架中如何集成 MCP？</title>
    <updated>2026-04-09T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>褚成志</name>
    </author>
    <category term="工具" scheme="https://www.chucz.asia/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="Java" scheme="https://www.chucz.asia/tags/Java/"/>
    <category term="IO" scheme="https://www.chucz.asia/tags/IO/"/>
    <category term="内存" scheme="https://www.chucz.asia/tags/%E5%86%85%E5%AD%98/"/>
    <category term="文件系统" scheme="https://www.chucz.asia/tags/%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F/"/>
    <category term="安全" scheme="https://www.chucz.asia/tags/%E5%AE%89%E5%85%A8/"/>
    <category term="Shell" scheme="https://www.chucz.asia/tags/Shell/"/>
    <content>
      <![CDATA[<p>这是 Agent 进化的关键一步：<strong>从“只会说话”变成了“真正干活”</strong>。</p><h2 id="Java-实现代码"><a href="#Java-实现代码" class="headerlink" title="Java 实现代码"></a>Java 实现代码</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AgentWithTools</span> &#123;</span><br><span class="line">    <span class="comment">// 配置</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Path</span> <span class="variable">WORKDIR</span> <span class="operator">=</span> Paths.get(System.getProperty(<span class="string">&quot;user.dir&quot;</span>));</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// --- 核心：工具定义与分发 ---</span></span><br><span class="line">    <span class="comment">// 1. 定义工具枚举</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">ToolType</span> &#123;</span><br><span class="line">        BASH(<span class="string">&quot;bash&quot;</span>, <span class="string">&quot;Run a shell command.&quot;</span>),</span><br><span class="line">        READ_FILE(<span class="string">&quot;read_file&quot;</span>, <span class="string">&quot;Read file contents.&quot;</span>),</span><br><span class="line">        WRITE_FILE(<span class="string">&quot;write_file&quot;</span>, <span class="string">&quot;Write content to file.&quot;</span>),</span><br><span class="line">        EDIT_FILE(<span class="string">&quot;edit_file&quot;</span>, <span class="string">&quot;Replace exact text in file.&quot;</span>);</span><br><span class="line">        <span class="comment">// ... 省略构造器</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 2. 工具执行接口</span></span><br><span class="line">    <span class="meta">@FunctionalInterface</span></span><br><span class="line">    <span class="keyword">interface</span> <span class="title class_">ToolExecutor</span> &#123;</span><br><span class="line">        String <span class="title function_">execute</span><span class="params">(Map args)</span> <span class="keyword">throws</span> Exception;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 3. 注册工具处理逻辑</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Map</span> <span class="variable">TOOL_HANDLERS</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        TOOL_HANDLERS.put(ToolType.BASH.name, args -&gt; &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">command</span> <span class="operator">=</span> (String) args.get(<span class="string">&quot;command&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> runBash(command);</span><br><span class="line">        &#125;);</span><br><span class="line">        TOOL_HANDLERS.put(ToolType.READ_FILE.name, args -&gt; &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">path</span> <span class="operator">=</span> (String) args.get(<span class="string">&quot;path&quot;</span>);</span><br><span class="line">            <span class="type">Integer</span> <span class="variable">limit</span> <span class="operator">=</span> (Integer) args.get(<span class="string">&quot;limit&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> runRead(path, limit);</span><br><span class="line">        &#125;);</span><br><span class="line">        TOOL_HANDLERS.put(ToolType.WRITE_FILE.name, args -&gt; &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">path</span> <span class="operator">=</span> (String) args.get(<span class="string">&quot;path&quot;</span>);</span><br><span class="line">            <span class="type">String</span> <span class="variable">content</span> <span class="operator">=</span> (String) args.get(<span class="string">&quot;content&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> runWrite(path, content);</span><br><span class="line">        &#125;);</span><br><span class="line">        TOOL_HANDLERS.put(ToolType.EDIT_FILE.name, args -&gt; &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">path</span> <span class="operator">=</span> (String) args.get(<span class="string">&quot;path&quot;</span>);</span><br><span class="line">            <span class="type">String</span> <span class="variable">oldText</span> <span class="operator">=</span> (String) args.get(<span class="string">&quot;old_text&quot;</span>);</span><br><span class="line">            <span class="type">String</span> <span class="variable">newText</span> <span class="operator">=</span> (String) args.get(<span class="string">&quot;new_text&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> runEdit(path, oldText, newText);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// --- 核心循环 ---</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">agentLoop</span><span class="params">(List&gt; messages)</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            <span class="comment">// ... 省略相同的 LLM 调用、消息追加逻辑</span></span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 4. 执行工具</span></span><br><span class="line">            List&gt; toolResults = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">            List&gt; content = (List&gt;) response.get(<span class="string">&quot;content&quot;</span>);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">for</span> (Map block : content) &#123;</span><br><span class="line">                <span class="keyword">if</span> (<span class="string">&quot;tool_use&quot;</span>.equals(block.get(<span class="string">&quot;type&quot;</span>))) &#123;</span><br><span class="line">                    <span class="type">String</span> <span class="variable">toolName</span> <span class="operator">=</span> (String) block.get(<span class="string">&quot;name&quot;</span>);  <span class="comment">// 关键新增</span></span><br><span class="line">                    <span class="type">String</span> <span class="variable">toolId</span> <span class="operator">=</span> (String) block.get(<span class="string">&quot;id&quot;</span>);</span><br><span class="line">                    <span class="type">Map</span> <span class="variable">inputArgs</span> <span class="operator">=</span> (Map) block.get(<span class="string">&quot;input&quot;</span>);</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// 路由分发</span></span><br><span class="line">                    <span class="type">ToolExecutor</span> <span class="variable">handler</span> <span class="operator">=</span> TOOL_HANDLERS.get(toolName);</span><br><span class="line">                    String output;</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        <span class="keyword">if</span> (handler != <span class="literal">null</span>) &#123;</span><br><span class="line">                            output = handler.execute(inputArgs);</span><br><span class="line">                        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                            output = <span class="string">&quot;Error: Unknown tool &quot;</span> + toolName;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                        output = <span class="string">&quot;Error: &quot;</span> + e.getMessage();</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                    System.out.println(<span class="string">&quot;&gt; &quot;</span> + toolName + <span class="string">&quot;: &quot;</span> + output.substring(<span class="number">0</span>, Math.min(output.length(), <span class="number">100</span>)));</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// ... 省略相同的工具结果构造逻辑</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// ... 省略相同的回传逻辑</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// --- 工具具体实现 ---</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> Path <span class="title function_">safePath</span><span class="params">(String p)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> WORKDIR.resolve(p).normalize();</span><br><span class="line">        <span class="keyword">if</span> (!path.startsWith(WORKDIR)) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IOException</span>(<span class="string">&quot;Path escapes workspace: &quot;</span> + p);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> path;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ... 省略与之前相同的 runBash 实现</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> String <span class="title function_">runRead</span><span class="params">(String pathStr, Integer limit)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> safePath(pathStr);</span><br><span class="line">        <span class="type">String</span> <span class="variable">content</span> <span class="operator">=</span> Files.readString(path);</span><br><span class="line">        <span class="keyword">if</span> (limit != <span class="literal">null</span> &amp;&amp; limit  args) <span class="keyword">throws</span> Exception;</span><br><span class="line">    <span class="comment">// 统一接口：所有工具都实现此方法</span></span><br><span class="line">    <span class="comment">// 参数和返回值标准化</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 工具注册表 - 动态路由</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Map</span> <span class="variable">TOOL_HANDLERS</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> &#123;</span><br><span class="line">    TOOL_HANDLERS.put(<span class="string">&quot;bash&quot;</span>, args -&gt; &#123;</span><br><span class="line">        <span class="comment">// 工具实现1</span></span><br><span class="line">    &#125;);</span><br><span class="line">    TOOL_HANDLERS.put(<span class="string">&quot;read_file&quot;</span>, args -&gt; &#123;</span><br><span class="line">        <span class="comment">// 工具实现2</span></span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="comment">// 注册中心：工具名 -&gt; 实现函数</span></span><br><span class="line">    <span class="comment">// 新增工具只需在这里注册</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><strong>开闭原则</strong>：不修改主循环就能添加新工具</p></li><li><p><strong>统一管理</strong>：所有工具注册、调用逻辑一致</p></li><li><p><strong>类型安全</strong>：通过枚举定义工具，避免硬编码字符串</p></li></ul><h2 id="文件操作工具集"><a href="#文件操作工具集" class="headerlink" title="文件操作工具集"></a>文件操作工具集</h2><p><strong>核心思想</strong>：为Agent提供<strong>文件系统读写能力</strong>，使其能像人类开发者一样操作文件。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> Path <span class="title function_">safePath</span><span class="params">(String p)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> WORKDIR.resolve(p).normalize();</span><br><span class="line">    <span class="keyword">if</span> (!path.startsWith(WORKDIR)) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IOException</span>(<span class="string">&quot;Path escapes workspace: &quot;</span> + p);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> path;</span><br><span class="line">    <span class="comment">// 安全沙箱：确保工具只能操作工作目录内的文件</span></span><br><span class="line">    <span class="comment">// 防止路径逃逸攻击</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> String <span class="title function_">runRead</span><span class="params">(String pathStr, Integer limit)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> safePath(pathStr);</span><br><span class="line">    <span class="type">String</span> <span class="variable">content</span> <span class="operator">=</span> Files.readString(path);</span><br><span class="line">    <span class="keyword">if</span> (limit != <span class="literal">null</span> &amp;&amp; <span class="type">limit</span>  <span class="variable">inputArgs</span> <span class="operator">=</span> (Map) block.get(<span class="string">&quot;input&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 根据工具名查找处理器</span></span><br><span class="line"><span class="type">ToolExecutor</span> <span class="variable">handler</span> <span class="operator">=</span> TOOL_HANDLERS.get(toolName);</span><br><span class="line">String output;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (handler != <span class="literal">null</span>) &#123;</span><br><span class="line">        output = handler.execute(inputArgs);  <span class="comment">// 动态调用</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        output = <span class="string">&quot;Error: Unknown tool &quot;</span> + toolName;</span><br><span class="line">    &#125;</span><br><span class="line">&#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">    output = <span class="string">&quot;Error: &quot;</span> + e.getMessage();  <span class="comment">// 统一错误处理</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><strong>动态分派</strong>：根据LLM选择的工具名调用对应实现</p></li><li><p><strong>统一错误处理</strong>：未知工具、执行异常都有统一格式的返回</p></li><li><p><strong>解耦</strong>：主循环不需要知道具体工具的实现细节</p></li></ul><h2 id="架构对比与价值"><a href="#架构对比与价值" class="headerlink" title="架构对比与价值"></a>架构对比与价值</h2><p><strong>从AgentLoop到AgentWithTools的演进</strong>：</p><table><thead><tr><th>维度</th><th>AgentLoop</th><th>AgentWithTools</th></tr></thead><tbody><tr><td>工具数量</td><td>1个(Bash)</td><td>4+个(可扩展)</td></tr><tr><td>架构设计</td><td>硬编码</td><td>策略模式</td></tr><tr><td>添加新工具</td><td>修改主代码</td><td>注册表添加</td></tr><tr><td>文件操作</td><td>无</td><td>读写编辑</td></tr><tr><td>安全性</td><td>命令检查</td><td>沙箱路径</td></tr><tr><td>代码复用</td><td>低</td><td>高</td></tr></tbody></table><p><strong>核心价值</strong>：</p><ol><li><p><strong>可扩展性</strong>：添加新工具只需在注册表中添加一行</p></li><li><p><strong>维护性</strong>：工具实现与主循环分离</p></li><li><p><strong>安全性</strong>：统一的路径和权限控制</p></li><li><p><strong>专业性</strong>：为开发任务优化的专用工具集</p></li></ol>]]>
    </content>
    <id>https://www.chucz.asia/2026/04/09/%E4%BB%8E0%E5%88%B01%E6%9E%84%E5%BB%BA%E4%B8%80%E4%B8%AAClaudeAgent/</id>
    <link href="https://www.chucz.asia/2026/04/09/%E4%BB%8E0%E5%88%B01%E6%9E%84%E5%BB%BA%E4%B8%80%E4%B8%AAClaudeAgent/"/>
    <published>2026-04-09T16:00:00.000Z</published>
    <summary>
      <![CDATA[<p>这是 Agent 进化的关键一步：<strong>从“只会说话”变成了“真正干活”</strong>。</p>
<h2 id="Java-实现代码"><a href="#Java-实现代码" class="headerlink" title="Java]]>
    </summary>
    <title>【从0到1构建一个ClaudeAgent】工具与执行-工具</title>
    <updated>2026-04-09T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>褚成志</name>
    </author>
    <category term="云原生" scheme="https://www.chucz.asia/categories/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    <category term="Ansible" scheme="https://www.chucz.asia/tags/Ansible/"/>
    <category term="运维自动化" scheme="https://www.chucz.asia/tags/%E8%BF%90%E7%BB%B4%E8%87%AA%E5%8A%A8%E5%8C%96/"/>
    <content>
      <![CDATA[<h3 id="一、系统管理类"><a href="#一、系统管理类" class="headerlink" title="一、系统管理类"></a>一、系统管理类</h3><h4 id="1-计划任务与定时"><a href="#1-计划任务与定时" class="headerlink" title="1. 计划任务与定时"></a>1. 计划任务与定时</h4><ul><li><p><strong>crond</strong>（Linux 定时任务管理）  </p><ul><li>时间规则：<code>minute</code>（分钟）、<code>hour</code>（小时）、<code>day</code>（日期）、<code>month</code>（月份）、<code>week</code>（星期）  </li><li>任务定义：<code>job</code>（要执行的命令&#x2F;脚本，如 <code>&quot;/usr/bin/backup.sh&quot;</code>）  </li><li>状态控制：<code>state</code>（<code>present</code> 新增任务、<code>absent</code> 删除任务 ）  </li><li>场景：周期性日志切割、数据库备份</li></ul></li><li><p><strong>at</strong>（一次性定时任务）  </p><ul><li>时间参数：<code>at_time</code>（指定执行时间，如 <code>&quot;now + 1 hour&quot;</code>、<code>&quot;17:00 tomorrow&quot;</code> ）  </li><li>任务内容：<code>command</code>（要执行的命令，如 <code>&quot;shutdown -r now&quot;</code> ）  </li><li>状态控制：<code>state</code>（<code>present</code> 新增任务、<code>absent</code> 删除任务 ）  </li><li>场景：系统维护前延迟重启</li></ul></li></ul><h4 id="2-用户与组管理"><a href="#2-用户与组管理" class="headerlink" title="2. 用户与组管理"></a>2. 用户与组管理</h4><ul><li><p><strong>user</strong>（用户生命周期管理）  </p><ul><li>基础属性：<code>name</code>（用户名）、<code>uid</code>（用户 ID）、<code>shell</code>（默认 Shell，如 <code>/bin/bash</code> ）、<code>create_home</code>（是否创建家目录，<code>yes/no</code> ）  </li><li>权限与密码：<code>password</code>（加密后的密码，需用 <code>ansible-vault</code> 或 <code>mkpasswd</code> 生成 ）、<code>groups</code>（附加用户组，如 <code>[&quot;wheel&quot;, &quot;docker&quot;]</code> ）  </li><li>状态控制：<code>state</code>（<code>present</code> 创建用户、<code>absent</code> 删除用户 ）  </li><li>高级：<code>expires</code>（账户过期时间，Unix 时间戳 ）、<code>remove</code>（删除用户时是否连带删除家目录，<code>yes/no</code> ）</li></ul></li><li><p><strong>group</strong>（用户组管理）  </p><ul><li>基础属性：<code>name</code>（组名）、<code>gid</code>（组 ID）  </li><li>状态控制：<code>state</code>（<code>present</code> 创建组、<code>absent</code> 删除组 ）  </li><li>扩展：<code>system</code>（是否为系统组，<code>yes/no</code> ，影响组 ID 范围 ）</li></ul></li></ul><h4 id="3-系统配置与初始化"><a href="#3-系统配置与初始化" class="headerlink" title="3. 系统配置与初始化"></a>3. 系统配置与初始化</h4><ul><li><p><strong>sysctl</strong>（内核参数调整）  </p><ul><li>参数设置：<code>name</code>（参数名，如 <code>net.ipv4.ip_forward</code> ）、<code>value</code>（参数值，如 <code>1</code> 开启 IP 转发 ）  </li><li>持久化：<code>state</code>（<code>present</code> 确保参数生效并写入 <code>/etc/sysctl.conf</code> ）  </li><li>场景：调整网络转发、文件描述符限制</li></ul></li><li><p><strong>lineinfile</strong>（精准修改配置文件行）  </p><ul><li>目标文件：<code>path</code>（要修改的文件路径，如 <code>/etc/nginx/nginx.conf</code> ）  </li><li>内容控制：<code>line</code>（要写入的行，如 <code>&quot;worker_processes auto;&quot;</code> ）、<code>regexp</code>（匹配行的正则，用于替换或确保唯一 ）  </li><li>状态：<code>state</code>（<code>present</code> 确保行存在、<code>absent</code> 删除行 ）  </li><li>高级：<code>backrefs</code>（正则匹配时保留原内容 ）、<code>insertafter/insertbefore</code>（指定插入位置，如 <code>EOF</code> 前 ）</li></ul></li></ul><h4 id="4-软件包管理"><a href="#4-软件包管理" class="headerlink" title="4. 软件包管理"></a>4. 软件包管理</h4><ul><li><p><strong>yum</strong>（RHEL&#x2F;CentOS 系列）  </p><ul><li>包操作：<code>name</code>（包名，支持列表&#x2F;通配符，如 <code>[&quot;nginx&quot;, &quot;python*&quot;]</code> ）  </li><li>状态：<code>state</code>（<code>present</code> 安装、<code>latest</code> 升级到最新、<code>absent</code> 卸载 ）  </li><li>缓存：<code>update_cache</code>（<code>yes/no</code> ，是否更新 yum 缓存 ）  </li><li>扩展：<code>disable_gpg_check</code>（跳过 GPG 校验，<code>yes/no</code> ，非安全场景临时用 ）</li></ul></li><li><p><strong>apt</strong>（Debian&#x2F;Ubuntu 系列）  </p><ul><li>包操作：<code>name</code>（包名，如 <code>apache2</code>、<code>mysql-server</code> ）  </li><li>状态：<code>state</code>（<code>present</code> 安装、<code>latest</code> 升级、<code>absent</code> 卸载 ）  </li><li>缓存：<code>update_cache</code>（<code>yes/no</code> ，更新 apt 缓存 ）  </li><li>扩展：<code>force</code>（强制安装&#x2F;覆盖依赖，<code>yes/no</code> ）</li></ul></li><li><p><strong>homebrew</strong>（macOS）  </p><ul><li>包操作：<code>name</code>（brew 包名，如 <code>git</code>、<code>vim</code> ）  </li><li>状态：<code>state</code>（<code>present</code> 安装、<code>latest</code> 升级、<code>absent</code> 卸载 ）  </li><li>扩展：<code>install_options</code>（安装选项，如 <code>--with-python</code> 编译时启用 Python 支持 ）</li></ul></li></ul><h3 id="二、文件操作类"><a href="#二、文件操作类" class="headerlink" title="二、文件操作类"></a>二、文件操作类</h3><h4 id="1-基础文件管理"><a href="#1-基础文件管理" class="headerlink" title="1. 基础文件管理"></a>1. 基础文件管理</h4><ul><li><p><strong>file</strong>（文件&#x2F;目录属性、存在性控制）  </p><ul><li>路径：<code>path</code>（目标路径，如 <code>/opt/app/config</code> ）  </li><li>类型与状态：<code>state</code>（<code>directory</code> 确保目录存在、<code>file</code> 确保文件存在、<code>link</code> 建软链接、<code>absent</code> 删除 ）  </li><li>权限：<code>mode</code>（权限，如 <code>0644</code>、<code>0755</code> ）、<code>owner</code>（属主）、<code>group</code>（属组）  </li><li>高级：<code>attributes</code>（文件系统属性，如 <code>+i</code> 设为只读 ）</li></ul></li><li><p><strong>copy</strong>（本地→远程拷贝文件）  </p><ul><li>源与目标：<code>src</code>（控制节点的源文件&#x2F;目录）、<code>dest</code>（目标主机路径，如 <code>/tmp/upload/</code> ）  </li><li>内容替代：<code>content</code>（直接写入文本内容，替代 <code>src</code> ，适合小配置 ）  </li><li>权限：<code>mode</code>、<code>owner</code>、<code>group</code>（同 <code>file</code> 模块 ）  </li><li>特殊：<code>remote_src</code>（<code>yes</code> 时，<code>src</code> 为目标主机本地路径，实现远程拷贝 ）</li></ul></li><li><p><strong>fetch</strong>（远程→本地拉取文件）  </p><ul><li>源与目标：<code>src</code>（目标主机文件路径，如 <code>/var/log/syslog</code> ）、<code>dest</code>（控制节点存储目录，自动按主机名分级 ）  </li><li>扁平化：<code>flat</code>（<code>yes</code> 时，直接存为 <code>dest</code> 文件名，不分级 ）  </li><li>过滤：<code>fail_on_missing</code>（文件不存在时是否失败，<code>yes/no</code> ）</li></ul></li></ul><h4 id="2-模板与变量渲染"><a href="#2-模板与变量渲染" class="headerlink" title="2. 模板与变量渲染"></a>2. 模板与变量渲染</h4><ul><li><strong>template</strong>（带 Jinja2 渲染的文件拷贝）  <ul><li>源与目标：<code>src</code>（模板文件，含 <code>&#123;&#123; 变量 &#125;&#125;</code> ，如 <code>config.j2</code> ）、<code>dest</code>（目标路径，如 <code>/etc/nginx/nginx.conf</code> ）  </li><li>变量传递：结合 Ansible 变量（Playbook vars、Inventory 变量等 ）  </li><li>权限：<code>mode</code>、<code>owner</code>、<code>group</code>（同 <code>file</code> 模块 ）  </li><li>高级：<code>trim_blocks</code>（去除 Jinja2 块的空行，<code>yes/no</code> ）、<code>lstrip_blocks</code>（左 trim 空格，<code>yes/no</code> ）</li></ul></li></ul><h4 id="3-归档与解压"><a href="#3-归档与解压" class="headerlink" title="3. 归档与解压"></a>3. 归档与解压</h4><ul><li><p><strong>archive</strong>（打包文件&#x2F;目录）  </p><ul><li>源：<code>path</code>（要打包的文件&#x2F;目录，支持通配符，如 <code>/var/log/*.log</code> ）  </li><li>目标：<code>dest</code>（打包后的归档路径，如 <code>/tmp/logs.tar.gz</code> ）  </li><li>格式：<code>format</code>（<code>tar</code>、<code>gz</code>、<code>zip</code> 等 ）  </li><li>过滤：<code>exclude_path</code>（排除的路径，如 <code>&quot;/var/log/old&quot;</code> ）</li></ul></li><li><p><strong>unarchive</strong>（解压归档）  </p><ul><li>源与目标：<code>src</code>（归档文件路径，支持控制节点或远程主机路径 ）、<code>dest</code>（解压目标目录，如 <code>/opt/app</code> ）  </li><li>来源：<code>remote_src</code>（<code>yes</code> 时，<code>src</code> 是目标主机路径；<code>no</code> 时，<code>src</code> 是控制节点路径 ）  </li><li>权限：<code>mode</code>、<code>owner</code>、<code>group</code>（解压后文件权限 ）  </li><li>特殊：<code>extra_opts</code>（解压额外参数，如 <code>tar --strip-components=1</code> 去掉一级目录 ）</li></ul></li></ul><h3 id="三、网络管理类"><a href="#三、网络管理类" class="headerlink" title="三、网络管理类"></a>三、网络管理类</h3><h4 id="1-网络配置（Linux-网络）"><a href="#1-网络配置（Linux-网络）" class="headerlink" title="1. 网络配置（Linux 网络）"></a>1. 网络配置（Linux 网络）</h4><ul><li><p><strong>nmcli</strong>（NetworkManager 管理）  </p><ul><li>连接管理：<code>conn_name</code>（连接名，如 <code>&quot;eth0-static&quot;</code> ）  </li><li>类型：<code>type</code>（<code>ethernet</code>、<code>bridge</code> 等 ）  </li><li>IP 配置：<code>ip4</code>（IP 地址，如 <code>192.168.1.10/24</code> ）、<code>gw4</code>（默认网关，如 <code>192.168.1.1</code> ）  </li><li>DNS：<code>dns4</code>（DNS 服务器，如 <code>8.8.8.8</code> ）  </li><li>状态：<code>state</code>（<code>present</code> 确保存在、<code>absent</code> 删除、<code>up/down</code> 启停 ）  </li><li>高级：<code>master</code>（桥接&#x2F; bonding 主设备，如 <code>&quot;br0&quot;</code> ）</li></ul></li><li><p><strong>network</strong>（传统网络脚本方式，兼容老系统）  </p><ul><li>网卡：<code>name</code>（网卡名，如 <code>eth0</code> ）  </li><li>IP 配置：<code>ip</code>（IP 地址）、<code>netmask</code>（子网掩码）、<code>gateway</code>（网关 ）  </li><li>状态：<code>state</code>（<code>up/down</code> 启停网卡 ）  </li><li>持久化：<code>bootproto</code>（<code>dhcp/static</code> ，控制 <code>/etc/sysconfig/network-scripts</code> 配置 ）</li></ul></li></ul><h4 id="2-网络设备配置（网络设备自动化）"><a href="#2-网络设备配置（网络设备自动化）" class="headerlink" title="2. 网络设备配置（网络设备自动化）"></a>2. 网络设备配置（网络设备自动化）</h4><ul><li><p><strong>ios_config</strong>（Cisco IOS 设备）  </p><ul><li>配置来源：<code>src</code>（本地配置文件，如 <code>ios_config.txt</code> ）、<code>lines</code>（逐行命令，如 <code>&quot;interface GigabitEthernet0/1&quot;</code> ）  </li><li>设备连接：<code>provider</code>（指定连接参数，如 <code>host</code>、<code>username</code>、<code>password</code> ）  </li><li>操作：<code>before</code>（执行配置前的命令，如 <code>&quot;configure terminal&quot;</code> ）、<code>after</code>（执行配置后的命令，如 <code>&quot;write memory&quot;</code> ）  </li><li>替换模式：<code>replace</code>（<code>line</code> 按行替换、<code>block</code> 按块替换 ）  </li><li>回滚：<code>backup</code>（<code>yes</code> 时备份原有配置，用于回滚 ）</li></ul></li><li><p><strong>nxos_config</strong>（Cisco Nexus 设备）  </p><ul><li>类似 <code>ios_config</code>，适配 Nexus OS 特性（如 ACI 配置、Nexus 特有命令 ）  </li><li>连接：<code>provider</code> 或使用 Ansible 网络连接插件</li></ul></li></ul><h4 id="3-网络服务与检测"><a href="#3-网络服务与检测" class="headerlink" title="3. 网络服务与检测"></a>3. 网络服务与检测</h4><ul><li><p><strong>uri</strong>（HTTP&#x2F;HTTPS 服务检测与交互）  </p><ul><li>目标：<code>url</code>（请求地址，如 <code>https://example.com/api</code> ）  </li><li>方法：<code>method</code>（<code>GET</code>、<code>POST</code>、<code>PUT</code>、<code>DELETE</code> 等 ）  </li><li>参数：<code>body</code>（<code>POST/PUT</code> 数据，JSON 或表单，如 <code>&#39;&#123;&quot;key&quot;: &quot;value&quot;&#125;&#39;</code> ）、<code>headers</code>（请求头，如 <code>Content-Type: application/json</code> ）  </li><li>验证：<code>status_code</code>（期望返回状态码，如 <code>200</code> ）、<code>validate_certs</code>（是否校验 SSL 证书，<code>yes/no</code> ）  </li><li>结果：<code>return_content</code>（<code>yes</code> 时返回响应内容 ）</li></ul></li><li><p><strong>ping</strong>（基础网络连通性检测）  </p><ul><li>极简：无特殊参数，返回 <code>pong</code> 表示目标主机可连通  </li><li>扩展：结合 <code>ignore_errors</code> 处理非强制检测场景（如监控任务不中断 Playbook ）</li></ul></li></ul><h3 id="四、应用部署与容器类"><a href="#四、应用部署与容器类" class="headerlink" title="四、应用部署与容器类"></a>四、应用部署与容器类</h3><h4 id="1-容器管理（Docker）"><a href="#1-容器管理（Docker）" class="headerlink" title="1. 容器管理（Docker）"></a>1. 容器管理（Docker）</h4><ul><li><p><strong>docker_image</strong>（镜像管理）  </p><ul><li>镜像：<code>name</code>（镜像名，如 <code>nginx:latest</code> ）  </li><li>状态：<code>state</code>（<code>present</code> 确保存在、<code>absent</code> 删除、<code>build</code> 从 Dockerfile 构建 ）  </li><li>构建：<code>path</code>（Dockerfile 路径，<code>state=build</code> 时用 ）、<code>dockerfile</code>（指定 Dockerfile 名，如 <code>Dockerfile.prod</code> ）  </li><li>推送：<code>push</code>（<code>yes</code> 时推送到镜像仓库 ）、<code>repository</code>（推送的仓库地址，如 <code>docker.io/username/repo</code> ）</li></ul></li><li><p><strong>docker_container</strong>（容器管理）  </p><ul><li>容器：<code>name</code>（容器名，如 <code>webserver</code> ）、<code>image</code>（镜像名，如 <code>nginx:alpine</code> ）  </li><li>网络与端口：<code>ports</code>（端口映射，如 <code>80:8080</code> ）、<code>networks</code>（网络模式，如 <code>bridge</code> ）  </li><li>存储：<code>volumes</code>（数据卷挂载，如 <code>/host/path:/container/path</code> ）  </li><li>环境：<code>env</code>（环境变量，字典形式，如 <code>&#123;&quot;DB_HOST&quot;: &quot;db&quot;&#125;</code> ）  </li><li>状态：<code>state</code>（<code>started</code> 启动、<code>stopped</code> 停止、<code>absent</code> 删除、<code>restarted</code> 重启 ）</li></ul></li></ul><h4 id="2-代码与应用部署"><a href="#2-代码与应用部署" class="headerlink" title="2. 代码与应用部署"></a>2. 代码与应用部署</h4><ul><li><p><strong>git</strong>（代码仓库拉取）  </p><ul><li>仓库：<code>repo</code>（仓库地址，如 <code>https://github.com/user/repo.git</code> ）  </li><li>目标：<code>dest</code>（本地路径，如 <code>/opt/app</code> ）  </li><li>版本：<code>version</code>（分支、标签或提交 ID，如 <code>main</code>、<code>v1.0</code> ）  </li><li>强制：<code>force</code>（<code>yes</code> 时强制拉取覆盖本地修改 ）  </li><li>深度：<code>depth</code>（克隆深度，如 <code>1</code> 只拉取最新提交，加速大仓库 ）</li></ul></li><li><p><strong>pip</strong>（Python 包管理）  </p><ul><li>包：<code>name</code>（包名，如 <code>requests</code>、<code>django</code> ）  </li><li>状态：<code>state</code>（<code>present</code> 安装、<code>latest</code> 升级、<code>absent</code> 卸载 ）  </li><li>版本：<code>version</code>（指定版本，如 <code>django==4.0</code> ）  </li><li>环境：<code>virtualenv</code>（虚拟环境路径，如 <code>/opt/venv</code> ）  </li><li>索引：<code>extra_args</code>（指定 PyPI 源，如 <code>-i https://pypi.tuna.tsinghua.edu.cn/simple</code> ）</li></ul></li></ul><h4 id="3-中间件与服务管理"><a href="#3-中间件与服务管理" class="headerlink" title="3. 中间件与服务管理"></a>3. 中间件与服务管理</h4><ul><li><p><strong>service</strong>（系统服务启停，兼容 Systemd&#x2F;Upstart 等）  </p><ul><li>服务：<code>name</code>（服务名，如 <code>httpd</code>、<code>nginx</code> ）  </li><li>状态：<code>state</code>（<code>started</code> 启动、<code>stopped</code> 停止、<code>restarted</code> 重启、<code>reloaded</code> 重载 ）  </li><li>开机启动：<code>enabled</code>（<code>yes/no</code> ，是否开机自启 ）  </li><li>扩展：<code>args</code>（启动参数，如 <code>--debug</code> ）</li></ul></li><li><p><strong>systemd</strong>（Systemd 专属管理，功能更细）  </p><ul><li>服务：<code>name</code>（服务单元名，如 <code>nginx.service</code> ）  </li><li>状态：<code>state</code>（<code>started</code>、<code>stopped</code> 等，同 <code>service</code> ）  </li><li>开机启动：<code>enabled</code>（<code>yes/no</code> ）、<code>masked</code>（<code>yes</code> 时屏蔽服务，无法启动 ）  </li><li>重载：<code>daemon_reload</code>（<code>yes</code> 时重载 Systemd 单元文件 ）</li></ul></li></ul><h3 id="五、监控与日志类"><a href="#五、监控与日志类" class="headerlink" title="五、监控与日志类"></a>五、监控与日志类</h3><h4 id="1-日志与事件"><a href="#1-日志与事件" class="headerlink" title="1. 日志与事件"></a>1. 日志与事件</h4><ul><li><p><strong>syslog</strong>（写入系统日志）  </p><ul><li>日志内容：<code>msg</code>（日志信息，如 <code>&quot;Ansible 配置完成：nginx 启动&quot;</code> ）  </li><li>日志级别：<code>priority</code>（日志优先级，如 <code>info</code>、<code>warning</code>、<code>error</code> ）  </li><li>日志设备：<code>facility</code>（日志设备，如 <code>user</code>、<code>daemon</code>、<code>local0</code> ）  </li><li>目标主机：<code>host</code>（远程 syslog 服务器地址，默认写入本地 ）</li></ul></li><li><p><strong>logrotate</strong>（日志轮转配置）  </p><ul><li>目标文件：<code>path</code>（要轮转的日志路径，如 <code>/var/log/nginx/access.log</code> ）  </li><li>轮转规则：<code>rotate</code>（保留份数，如 <code>7</code> ）、<code>daily/weekly/monthly</code>（轮转频率 ）  </li><li>压缩：<code>compress</code>（<code>yes/no</code> ，是否压缩旧日志 ）、<code>delaycompress</code>（延迟压缩，保留最新 1 份未压缩 ）  </li><li>触发条件：<code>size</code>（达到指定大小轮转，如 <code>100M</code> ）  </li><li>后置操作：<code>postrotate</code>（轮转后执行的命令，如 &#96;”systemctl reload nginx</li></ul></li></ul><h4 id="2-系统指标与信息采集"><a href="#2-系统指标与信息采集" class="headerlink" title="2. 系统指标与信息采集"></a>2. 系统指标与信息采集</h4><ul><li><p><strong>setup</strong>（采集目标主机 Facts 信息）</p><ul><li>采集范围：默认无参数时，采集全量系统信息（CPU型号&#x2F;核心数、内存总量&#x2F;使用率、磁盘分区、网卡IP、操作系统版本等）</li><li>过滤采集：<code>filter</code>（按规则筛选 Facts，支持通配符，如 <code>ansible_mem*</code> 仅采集内存相关、<code>ansible_eth0</code> 仅采集 eth0 网卡信息、<code>ansible_distribution*</code> 采集系统发行版相关）</li><li>自定义 Facts：<code>fact_path</code>（指定自定义 Facts 文件路径，如 <code>/etc/ansible/facts.d</code>，支持 <code>.ini</code>&#x2F;<code>.json</code>&#x2F;可执行脚本格式，脚本输出需符合 JSON 结构）</li><li>应用场景：基于 Facts 动态适配配置（如根据 <code>ansible_memtotal_mb</code> 设置 JVM 堆内存、根据 <code>ansible_os_family</code> 选择 yum&#x2F;apt 模块）</li></ul></li><li><p><strong>stat</strong>（获取文件&#x2F;目录详细属性）</p><ul><li>目标路径：<code>path</code>（文件&#x2F;目录路径，如 <code>/etc/passwd</code>、<code>/var/log/nginx</code>）</li><li>采集信息：返回 <code>size</code>（大小，单位字节）、<code>mode</code>（权限，如 <code>0o644</code>）、<code>uid</code>&#x2F;<code>gid</code>（属主&#x2F;属组ID）、<code>mtime</code>（最后修改时间）、<code>ctime</code>（最后状态变更时间）、<code>checksum</code>（文件校验和，默认 sha1）、<code>exists</code>（是否存在，布尔值）</li><li>结果处理：<code>register</code>（将采集结果存入变量，用于后续条件判断，如 <code>if stat_result.stat.exists == true</code> 则执行某任务）</li><li>扩展参数：<code>follow</code>（<code>yes/no</code>，是否跟随软链接，默认 <code>no</code>）、<code>get_checksum</code>（<code>yes/no</code>，是否计算文件校验和，默认 <code>yes</code>）、<code>get_mime</code>（<code>yes/no</code>，是否获取 MIME 类型，默认 <code>yes</code>）</li></ul></li><li><p><strong>service_facts</strong>（采集系统服务状态 Facts）</p><ul><li>核心功能：专门采集所有系统服务的运行状态（<code>running</code>&#x2F;<code>stopped</code>&#x2F;<code>failed</code>）和开机启动状态（<code>enabled</code>&#x2F;<code>disabled</code>&#x2F;<code>masked</code>）</li><li>结果存储：信息存入 <code>ansible_facts.services</code> 字典变量，可通过 <code>ansible_facts.services[&#39;nginx.service&#39;].state</code> 获取指定服务运行状态、<code>ansible_facts.services[&#39;sshd.service&#39;].status</code> 获取开机启动状态</li><li>兼容性：自动适配 Systemd、Upstart、SysVinit 等主流服务管理系统，无需手动指定服务类型</li></ul></li></ul><h4 id="3-监控告警与检测"><a href="#3-监控告警与检测" class="headerlink" title="3. 监控告警与检测"></a>3. 监控告警与检测</h4><ul><li><p><strong>wait_for</strong>（等待资源就绪，常用于服务启动监控）</p><ul><li>等待对象：<ul><li>端口监听：<code>port</code>（目标端口，如 <code>80</code>、<code>3306</code>）+ <code>host</code>（目标主机，默认 <code>localhost</code>）</li><li>文件状态：<code>path</code>（目标文件路径，如 <code>/var/run/nginx.pid</code>）</li><li>进程PID：<code>pid</code>（进程ID，需结合 <code>register</code> 从其他任务获取）</li></ul></li><li>超时与重试：<code>timeout</code>（超时时间，默认 300 秒，超时则任务失败）、<code>delay</code>（开始等待前的延迟时间，如 <code>5</code> 秒，避免资源未开始初始化导致误判）、<code>retries</code>（重试次数，默认无限重试直到超时）</li><li>状态判断：<code>state</code>（<code>started</code> 等待端口监听&#x2F;进程启动、<code>present</code> 等待文件存在、<code>absent</code> 等待文件删除）</li><li>应用场景：部署 Nginx 后等待 80 端口就绪再执行健康检查、数据库启动后等待 <code>/var/run/mysqld/mysqld.sock</code> 存在再执行初始化 SQL</li></ul></li><li><p><strong>sensu_check</strong>（Sensu 监控检查配置，社区模块）</p><ul><li>检查基础配置：<code>name</code>（Sensu 检查名称，如 <code>check_nginx_process</code>）、<code>command</code>（监控执行命令，如 <code>check-process.rb -p nginx -w 2 -c 1</code>，表示进程数低于1报警、低于2警告）</li><li>执行频率：<code>interval</code>（检查间隔时间，单位秒，如 <code>60</code> 表示每分钟检查一次）</li><li>告警分发：<code>subscribers</code>（订阅该检查的客户端列表，如 <code>[&quot;web-server&quot;, &quot;app-server&quot;]</code>）、<code>handlers</code>（触发告警时的处理程序，如 <code>email</code>、<code>slack</code>、<code>pagerduty</code>）</li><li>状态控制：<code>state</code>（<code>present</code> 新增检查配置、<code>absent</code> 删除检查配置）</li><li>依赖：需在目标主机提前部署 Sensu Client 并配置与 Sensu Server 通信</li></ul></li></ul><h3 id="六、云服务与存储类"><a href="#六、云服务与存储类" class="headerlink" title="六、云服务与存储类"></a>六、云服务与存储类</h3><h4 id="1-AWS-云服务模块"><a href="#1-AWS-云服务模块" class="headerlink" title="1. AWS 云服务模块"></a>1. AWS 云服务模块</h4><ul><li><p><strong>ec2</strong>（EC2 实例管理）</p><ul><li>实例基础配置：<code>image</code>（AMI 镜像 ID，如 <code>ami-0c55b159cbfafe1f0</code> 对应 Amazon Linux 2）、<code>instance_type</code>（实例规格，如 <code>t2.micro</code>、<code>c5.large</code>）、<code>key_name</code>（SSH 密钥对名称，用于登录实例）</li><li>网络配置：<code>vpc_subnet_id</code>（子网 ID，如 <code>subnet-12345678</code>）、<code>security_group_ids</code>（安全组 ID 列表，如 <code>[&quot;sg-87654321&quot;]</code>）、<code>associate_public_ip_address</code>（<code>yes/no</code>，是否分配公网 IP）</li><li>存储配置：<code>volumes</code>（EBS 卷配置，格式为 <code>[&#123;&quot;device_name&quot;: &quot;/dev/sda1&quot;, &quot;volume_size&quot;: 20, &quot;volume_type&quot;: &quot;gp2&quot;&#125;]</code>，支持 gp2&#x2F;gp3&#x2F;io1 等卷类型）</li><li>状态控制：<code>state</code>（<code>present</code> 创建实例、<code>absent</code> 删除实例、<code>running</code> 启动实例、<code>stopped</code> 停止实例）</li><li>初始化：<code>user_data</code>（实例启动脚本，支持 cloud-init 格式，如 <code>#!/bin/bash\nyum install -y nginx</code>，用于实例初始化）</li></ul></li><li><p><strong>aws_s3</strong>（S3 存储桶与对象管理）</p><ul><li>存储桶操作：<code>bucket</code>（桶名，需全局唯一）、<code>state</code>（<code>present</code> 创建桶、<code>absent</code> 删除桶）、<code>region</code>（AWS 地域，如 <code>us-east-1</code>、<code>ap-beijing-1</code>）、<code>tags</code>（桶标签，如 <code>&#123;&quot;Environment&quot;: &quot;production&quot;, &quot;Project&quot;: &quot;app&quot;&#125;</code>）</li><li>对象操作：<ul><li>上传：<code>mode: put</code> + <code>src</code>（本地文件路径，如 <code>/tmp/data.csv</code>） + <code>object</code>（S3 中对象路径，如 <code>data/202405.csv</code>）</li><li>下载：<code>mode: get</code> + <code>object</code>（S3 对象路径） + <code>dest</code>（本地存储路径，如 <code>/tmp/download.csv</code>）</li><li>删除：<code>mode: delete</code> + <code>object</code>（S3 对象路径）</li><li>列出：<code>mode: list</code> + <code>prefix</code>（对象路径前缀，如 <code>data/2024</code>，仅列出该前缀下的对象）</li></ul></li><li>权限控制：<code>aws_access_key</code>&#x2F;<code>aws_secret_key</code>（AWS 访问密钥，或通过环境变量、IAM 角色自动获取，推荐使用 IAM 角色避免硬编码密钥）</li></ul></li><li><p><strong>rds</strong>（RDS 数据库实例管理）</p><ul><li>实例配置：<code>db_instance_identifier</code>（实例名，如 <code>prod-mysql</code>）、<code>engine</code>（数据库引擎，如 <code>mysql</code>、<code>postgres</code>、<code>sqlserver-ex</code>）、<code>engine_version</code>（引擎版本，如 <code>8.0</code> 对应 MySQL 8.0）</li><li>规格与存储：<code>db_instance_class</code>（实例规格，如 <code>db.t3.small</code>）、<code>allocated_storage</code>（存储大小，单位 GB，如 <code>50</code>）、<code>storage_type</code>（存储类型，如 <code>gp2</code>、<code>io1</code>）</li><li>账号配置：<code>master_username</code>（管理员用户名，如 <code>admin</code>）、<code>master_user_password</code>（管理员密码，建议用 <code>ansible-vault</code> 加密存储）</li><li>网络与安全：<code>vpc_security_group_ids</code>（安全组 ID 列表）、<code>db_subnet_group_name</code>（DB 子网组名，需提前创建）</li><li>状态控制：<code>state</code>（<code>present</code> 创建实例、<code>absent</code> 删除实例、<code>running</code> 启动实例、<code>stopped</code> 停止实例）</li></ul></li></ul><h4 id="2-存储管理"><a href="#2-存储管理" class="headerlink" title="2. 存储管理"></a>2. 存储管理</h4><ul><li><p><strong>mount</strong>（文件系统挂载与持久化）</p><ul><li>挂载基础信息：<code>path</code>（挂载点路径，如 <code>/mnt/data</code>，需提前创建目录）、<code>src</code>（设备&#x2F;共享路径，如 <code>/dev/sdb1</code>（本地磁盘）、<code>//192.168.1.100/share</code>（Windows 共享）、<code>192.168.1.101:/data</code>（NFS 共享））</li><li>文件系统类型：<code>fstype</code>（如 <code>ext4</code>、<code>xfs</code>（本地磁盘）、<code>cifs</code>（Windows 共享）、<code>nfs</code>（NFS 共享））</li><li>状态控制：<ul><li><code>mounted</code>：立即挂载，并写入 <code>/etc/fstab</code> 实现开机自动挂载</li><li><code>unmounted</code>：立即卸载，且从 <code>/etc/fstab</code> 中移除配置</li><li><code>present</code>：仅写入 <code>/etc/fstab</code>，不立即挂载</li><li><code>absent</code>：从 <code>/etc/fstab</code> 中移除配置，不影响当前挂载状态</li></ul></li><li>挂载参数：<code>opts</code>（挂载选项，如 <code>defaults,noatime</code>（本地磁盘，禁用访问时间更新）、<code>username=user,password=pass</code>（CIFS 共享，指定登录账号密码）、<code>rw,sync</code>（NFS 共享，读写&#x2F;同步模式））</li></ul></li><li><p><strong>lvg</strong>（逻辑卷组管理）</p><ul><li>卷组基础：<code>vg</code>（卷组名，如 <code>vg_data</code>）、<code>pvs</code>（物理卷列表，如 <code>[&quot;/dev/sdb&quot;, &quot;/dev/sdc&quot;]</code>，需提前初始化物理卷 <code>pvcreate</code>）</li><li>状态控制：<code>state</code>（<code>present</code> 创建卷组、<code>absent</code> 删除卷组（需先删除逻辑卷）、<code>extended</code> 扩展卷组（添加新物理卷，如 <code>pvs: &quot;/dev/sdd&quot;</code>））</li><li>扩展参数：<code>force</code>（<code>yes/no</code>，扩展卷组时强制添加物理卷，忽略“物理卷属于其他卷组”等警告，默认 <code>no</code>）</li></ul></li><li><p><strong>lvol</strong>（逻辑卷管理）</p><ul><li>逻辑卷基础：<code>lv</code>（逻辑卷名，如 <code>lv_app</code>）、<code>vg</code>（所属卷组名，如 <code>vg_data</code>）</li><li>大小配置：<code>size</code>（逻辑卷大小，支持绝对值（如 <code>50G</code>、<code>100M</code>）和相对值（如 <code>100%FREE</code> 表示使用卷组所有剩余空间、<code>+20G</code> 表示在现有基础上增加 20G））</li><li>状态控制：<code>state</code>（<code>present</code> 创建逻辑卷、<code>absent</code> 删除逻辑卷（需先卸载文件系统）、<code>resized</code> 调整逻辑卷大小（需确保文件系统支持在线扩容，如 ext4&#x2F;xfs））</li><li>扩展参数：<code>force</code>（<code>yes/no</code>，调整大小时强制操作，默认 <code>no</code>；扩容前建议备份数据，缩容需先缩小文件系统）</li></ul></li></ul><h4 id="3-其他云平台模块"><a href="#3-其他云平台模块" class="headerlink" title="3. 其他云平台模块"></a>3. 其他云平台模块</h4><ul><li><p><strong>azure_rm_virtualmachine</strong>（Azure 虚拟机管理）</p><ul><li>实例配置：<code>name</code>（虚拟机名，如 <code>prod-web-01</code>）、<code>resource_group</code>（资源组名，如 <code>prod-resource-group</code>）、<code>vm_size</code>（实例规格，如 <code>Standard_D2s_v3</code>）</li><li>镜像配置：<code>image</code>（镜像参数，格式为 <code>&#123;&quot;publisher&quot;: &quot;Canonical&quot;, &quot;offer&quot;: &quot;UbuntuServer&quot;, &quot;sku&quot;: &quot;20.04-LTS&quot;, &quot;version&quot;: &quot;latest&quot;&#125;</code>，或使用自定义镜像 ID）</li><li>网络配置：<code>virtual_network_name</code>（虚拟网络名）、<code>subnet_name</code>（子网名）、<code>public_ip_allocation_method</code>（公网 IP 分配方式，<code>Dynamic</code> 动态分配、<code>Static</code> 静态分配）</li><li>认证配置：<code>admin_username</code>（管理员用户名）、<code>admin_password</code>（密码，或 <code>ssh_public_keys</code> 指定 SSH 公钥）</li><li>状态控制：<code>state</code>（<code>present</code> 创建实例、<code>absent</code> 删除实例、<code>started</code> 启动实例、<code>stopped</code> 停止实例）</li></ul></li><li><p><strong>google.cloud.gcp_compute_instance</strong>（GCP 计算实例管理）</p><ul><li>实例配置：<code>name</code>（实例名）、<code>zone</code>（可用区，如 <code>us-central1-a</code>）、<code>machine_type</code>（实例类型，如 <code>n1-standard-1</code>）</li><li>镜像配置：<code>boot_disk</code>（启动磁盘，格式为 <code>&#123;&quot;source_image&quot;: &quot;debian-cloud/debian-11&quot;, &quot;size_gb&quot;: 20&#125;</code>，指定镜像和磁盘大小）</li><li>网络配置：<code>network_interfaces</code>（网卡配置，如 <code>[&#123;&quot;network&quot;: &quot;default&quot;, &quot;access_configs&quot;: [&#123;&quot;name&quot;: &quot;External NAT&quot;&#125;]&#125;]</code>，配置网络和公网访问）</li><li>认证配置：<code>service_account_email</code>（服务账号邮箱，用于权限控制）、<code>credentials_file</code>（GCP 凭证文件路径，或通过环境变量 <code>GOOGLE_APPLICATION_CREDENTIALS</code> 指定）</li><li>状态控制：<code>state</code>（<code>present</code> 创建实例、<code>absent</code> 删除实例、<code>running</code> 启动实例、<code>stopped</code> 停止实例）</li></ul></li></ul><h3 id="七、通用工具与命令类"><a href="#七、通用工具与命令类" class="headerlink" title="七、通用工具与命令类"></a>七、通用工具与命令类</h3><h4 id="1-命令执行模块（核心差异：Shell-特性支持）"><a href="#1-命令执行模块（核心差异：Shell-特性支持）" class="headerlink" title="1. 命令执行模块（核心差异：Shell 特性支持）"></a>1. 命令执行模块（核心差异：Shell 特性支持）</h4><ul><li><p><strong>command</strong>（非交互式执行命令，无 Shell 特性）</p><ul><li>命令内容：<code>cmd</code>（执行的命令，如 <code>ls /opt</code>、<code>cat /etc/hosts</code>、<code>systemctl status nginx</code>）</li><li>条件跳过：<code>creates</code>（若指定文件存在则跳过命令，如 <code>creates: /tmp/init.flag</code>，用于避免重复执行初始化命令）、<code>removes</code>（若指定文件不存在则跳过命令，如 <code>removes: /etc/nginx/nginx.conf</code>，用于仅在配置文件存在时执行操作）</li><li>执行环境：<code>chdir</code>（执行命令前切换到目标目录，如 <code>chdir: /opt/app</code>，避免命令路径依赖问题）</li><li>限制：不支持管道（<code>|</code>）、重定向（<code>&gt;</code>&#x2F;<code>&gt;&gt;</code>&#x2F;<code>&lt;</code>）、环境变量（<code>$HOME</code>&#x2F;<code>$PATH</code>）、通配符（<code>*</code>&#x2F;<code>?</code>）等 Shell 特性，需复杂语法时用 <code>shell</code> 模块</li></ul></li><li><p><strong>shell</strong>（交互式执行命令，支持 Shell 特性）</p><ul><li>命令内容：<code>cmd</code>（支持 Shell 语法的命令，如 <code>ps aux | grep nginx</code>（管道）、<code>echo &quot;test&quot; &gt; /tmp/file.txt</code>（重定向）、<code>echo $HOME</code>（环境变量）、<code>ls /opt/*.log</code>（通配符））</li><li>通用参数：<code>chdir</code>、<code>creates</code>、<code>removes</code>（同 <code>command</code> 模块）</li><li>扩展参数：<code>executable</code>（指定 Shell 解释器，如 <code>/bin/bash</code>、<code>/bin/sh</code>，默认使用目标主机 <code>$SHELL</code> 或 <code>/bin/sh</code>）</li><li>风险提示：因支持 Shell 语法，需避免命令注入风险（如不直接拼接用户输入的变量，需用 <code>quote</code> 过滤器转义，如 <code>cmd: &quot;echo &#123;&#123; user_input | quote &#125;&#125;&quot;</code>）</li></ul></li><li><p><strong>raw</strong>（无 Python 依赖，直接执行原始命令）</p><ul><li>适用场景：目标主机未安装 Python 时（Ansible 绝大多数模块依赖 Python，<code>raw</code> 模块通过 SSH 直接执行命令，无需 Python 环境），常用于初始化 Python（如 <code>raw: &quot;yum install -y python3&quot;</code> 或 <code>apt install -y python3</code>）</li><li>命令内容：<code>args</code>（命令字符串，如 <code>raw: &quot;ssh-keygen -t rsa -N &#39;&#39; -f ~/.ssh/id_rsa&quot;</code>）</li><li>限制：不支持 Ansible 变量插值（需手动用 Shell 变量，如 <code>raw: &quot;echo $ENV_VAR&quot;</code>）、无结构化结果返回（仅返回命令输出文本，无法直接提取关键信息）、不支持 <code>creates</code>&#x2F;<code>removes</code> 等条件参数</li></ul></li></ul><h4 id="2-脚本与程序执行"><a href="#2-脚本与程序执行" class="headerlink" title="2. 脚本与程序执行"></a>2. 脚本与程序执行</h4><ul><li><p><strong>script</strong>（在目标主机执行控制节点本地脚本）</p><ul><li>脚本路径：<code>script</code>（控制节点上的脚本路径，如 <code>./scripts/install_nginx.sh</code>、<code>/opt/ansible/scripts/init_db.sh</code>，Ansible 会自动将脚本传输到目标主机临时目录并执行）</li><li>脚本参数：<code>args</code>（传递给脚本的参数，如 <code>args: &quot;arg1 arg2 --env production&quot;</code>，脚本内通过 <code>$1</code>&#x2F;<code>$2</code>&#x2F;<code>$3</code> 接收）</li><li>执行环境：<code>chdir</code>（执行脚本前切换到目标主机的指定目录，如 <code>chdir: /opt/app</code>，脚本内相对路径基于此目录）</li><li>优势：无需手动拷贝脚本到目标主机，减少文件传输步骤；支持任意脚本类型（Shell、Python、Perl 等，需脚本头部指定解释器，如 <code>#!/bin/bash</code>）</li></ul></li><li><p><strong>command_shell</strong>（社区模块，灵活切换执行模式）</p><ul><li>核心参数：<code>command</code>（命令内容）、<code>use_shell</code>（<code>yes/no</code>，是否启用 Shell 特性，默认 <code>no</code>，即 <code>command</code> 模式；设为 <code>yes</code> 则切换为 <code>shell</code> 模式）</li><li>通用参数：<code>chdir</code>、<code>creates</code>、<code>removes</code>（同 <code>command</code>&#x2F;<code>shell</code> 模块）</li><li>应用场景：需根据变量动态切换命令执行模式时（如 <code>use_shell: &quot;&#123;&#123; use_complex_syntax | default('no') &#125;&#125;&quot;</code>，当 <code>use_complex_syntax</code> 为 <code>yes</code> 时启用 Shell 特性）</li></ul></li></ul><h4 id="3-变量与结果处理"><a href="#3-变量与结果处理" class="headerlink" title="3. 变量与结果处理"></a>3. 变量与结果处理</h4><ul><li><p><strong>set_fact</strong>（定义&#x2F;修改 Facts 变量）</p><ul><li>变量定义：支持多种数据类型，格式为 <code>key=value</code>：<ul><li>字符串：<code>set_fact: app_version=&quot;1.0.0&quot;</code></li><li>数字：<code>set_fact: max_connections=1000</code></li><li>布尔值：<code>set_fact: is_production=&#123;&#123; env == 'prod' &#125;&#125;</code>（通过 Jinja2 表达式计算布尔值）</li><li>列表：<code>set_fact: web_servers=[&quot;192.168.1.10&quot;, &quot;192.168.1.11&quot;, &quot;192.168.1.12&quot;]</code></li><li>字典：<code>set_fact: db_config=&#123;&quot;host&quot;: &quot;db.example.com&quot;, &quot;port&quot;: 3306, &quot;user&quot;: &quot;admin&quot;&#125;</code></li></ul></li><li>动态计算：支持 Jinja2 过滤器和表达式，如 <code>set_fact: total_memory_gb=&quot;&#123;&#123; ansible_memtotal_mb | int / 1024 | round(1) &#125;&#125;G&quot;</code>（将内存从 MB 转为 GB 并保留1位小数）</li><li>作用域：定义的变量属于 Facts 范畴，在当前 Play 内所有任务可见，可通过 <code>&#123;&#123; 变量名 &#125;&#125;</code> 直接引用，优先级高于普通 Play 变量</li></ul></li><li><p><strong>debug</strong>（输出调试信息，用于 Playbook 排错）</p><ul><li>输出方式：<ul><li>自定义消息：<code>msg</code>（自定义文本，支持变量插值，如 <code>debug: msg=&quot;当前环境：&#123;&#123; env &#125;&#125;, 应用版本：&#123;&#123; app_version &#125;&#125;&quot;</code>）</li><li>变量详情：<code>var</code>（输出指定变量的完整结构，包括嵌套层级，如 <code>debug: var=db_config</code>，会显示字典 <code>db_config</code> 的所有键值对）</li><li>格式化输出：<code>verbosity</code>（调试级别，0-4，级别越高输出越详细，默认 0；设为 1 时，需执行 Playbook 加 <code>-v</code> 参数才显示，如 <code>ansible-playbook -v site.yml</code>）</li></ul></li><li>应用场景：验证变量值是否符合预期（如检查 Inventory 变量是否正确加载）、查看任务执行结果（如 <code>register</code> 注册的命令输出、<code>stat</code> 模块采集的文件信息）、定位条件判断逻辑问题（如 <code>debug: msg=&quot;条件成立&quot;</code> when: is_production）</li></ul></li></ul><h3 id="七、通用工具与命令类（补充）"><a href="#七、通用工具与命令类（补充）" class="headerlink" title="七、通用工具与命令类（补充）"></a>七、通用工具与命令类（补充）</h3><h4 id="4-条件与循环控制辅助模块"><a href="#4-条件与循环控制辅助模块" class="headerlink" title="4. 条件与循环控制辅助模块"></a>4. 条件与循环控制辅助模块</h4><ul><li><p><strong>with_items&#x2F;with_list（循环迭代，Ansible 2.5+ 推荐用 <code>loop</code> 替代）</strong></p><ul><li><p>核心功能：遍历列表数据，为每个元素执行一次任务，适用于批量操作（如批量安装软件、批量创建文件）</p></li><li><p>基础用法：<code>loop: &quot;&#123;&#123; 列表变量 &#125;&#125;&quot;</code>（替代旧版 <code>with_items</code>），任务内通过 <code>&#123;&#123; item &#125;&#125;</code> 引用当前元素</p><ul><li><p>示例1（批量安装软件）：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">批量安装依赖包</span></span><br><span class="line">  <span class="attr">apt:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; item &#125;&#125;</span>&quot;</span></span><br><span class="line">    <span class="attr">state:</span> <span class="string">present</span></span><br><span class="line">  <span class="attr">loop:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; dependencies &#125;&#125;</span>&quot;</span>  <span class="comment"># dependencies 为列表变量：[&quot;curl&quot;, &quot;git&quot;, &quot;vim&quot;]</span></span><br><span class="line">  <span class="attr">loop_control:</span></span><br><span class="line">    <span class="attr">label:</span> <span class="string">&quot;安装 <span class="template-variable">&#123;&#123; item &#125;&#125;</span>&quot;</span>  <span class="comment"># 自定义循环日志标签，增强可读性</span></span><br></pre></td></tr></table></figure></li><li><p>示例2（批量创建文件）：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">批量创建配置文件</span></span><br><span class="line">  <span class="attr">file:</span></span><br><span class="line">    <span class="attr">path:</span> <span class="string">&quot;/etc/<span class="template-variable">&#123;&#123; item &#125;&#125;</span>&quot;</span></span><br><span class="line">    <span class="attr">state:</span> <span class="string">touch</span></span><br><span class="line">    <span class="attr">mode:</span> <span class="number">0644</span></span><br><span class="line">  <span class="attr">loop:</span> [<span class="string">&quot;app.conf&quot;</span>, <span class="string">&quot;db.conf&quot;</span>, <span class="string">&quot;log.conf&quot;</span>]</span><br></pre></td></tr></table></figure></li></ul></li><li><p>扩展用法：<code>loop: &quot;&#123;&#123; query('inventory_hostnames', 'web_servers') &#125;&#125;&quot;</code>（结合 Inventory 插件，遍历指定组的主机名）</p></li></ul></li><li><p><strong>with_dict&#x2F;loop + dict2items（字典迭代）</strong></p><ul><li><p>核心功能：遍历字典类型数据，获取键（<code>key</code>）和值（<code>value</code>），适用于批量配置键值对相关场景（如批量设置环境变量、批量创建用户并指定 UID）</p></li><li><p>基础用法：通过 <code>loop: &quot;&#123;&#123; 字典变量 | dict2items &#125;&#125;&quot;</code> 转换字典为列表，任务内通过 <code>&#123;&#123; item.key &#125;&#125;</code>&#x2F;<code>&#123;&#123; item.value &#125;&#125;</code> 引用键和值</p><ul><li><p>示例（批量创建用户并指定 UID）：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">批量创建用户（指定</span> <span class="string">UID）</span></span><br><span class="line">  <span class="attr">user:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; item.key &#125;&#125;</span>&quot;</span></span><br><span class="line">    <span class="attr">uid:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; item.value &#125;&#125;</span>&quot;</span></span><br><span class="line">    <span class="attr">state:</span> <span class="string">present</span></span><br><span class="line">  <span class="attr">loop:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; user_uid_map | dict2items &#125;&#125;</span>&quot;</span>  <span class="comment"># user_uid_map 为字典：&#123;&quot;alice&quot;: 1001, &quot;bob&quot;: 1002, &quot;charlie&quot;: 1003&#125;</span></span><br><span class="line">  <span class="attr">loop_control:</span></span><br><span class="line">    <span class="attr">label:</span> <span class="string">&quot;创建用户 <span class="template-variable">&#123;&#123; item.key &#125;&#125;</span>（UID: <span class="template-variable">&#123;&#123; item.value &#125;&#125;</span>）&quot;</span></span><br></pre></td></tr></table></figure></li></ul></li></ul></li><li><p><strong>when（条件判断）</strong></p><ul><li><p>核心功能：根据条件决定任务是否执行，支持布尔值、比较运算、逻辑运算、Facts 变量判断等，实现任务的动态适配</p></li><li><p>常用条件场景：</p><ul><li>基于操作系统：<code>when: ansible_os_family == &quot;Debian&quot;</code>（仅 Debian&#x2F;Ubuntu 执行 apt 安装）、<code>when: ansible_distribution == &quot;CentOS&quot;</code>（仅 CentOS 执行 yum 操作）</li><li>基于变量值：<code>when: env == &quot;production&quot;</code>（生产环境执行某任务）、<code>when: app_version is version(&quot;2.0.0&quot;, &quot;&gt;=&quot;)</code>（版本号大于等于 2.0.0 时执行）</li><li>基于任务结果：<code>when: stat_result.stat.exists</code>（文件存在时执行）、<code>when: command_result.rc == 0</code>（命令执行成功时执行，<code>rc</code> 为返回码，0 表示成功）</li><li>逻辑组合：<code>when: ansible_os_family == &quot;RedHat&quot; and ansible_memtotal_mb &gt; 2048</code>（RedHat 系统且内存大于 2G 时执行）</li></ul></li><li><p>示例（按系统选择包管理器）：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Debian</span> <span class="string">系列安装</span> <span class="string">nginx</span></span><br><span class="line">  <span class="attr">apt:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">    <span class="attr">state:</span> <span class="string">present</span></span><br><span class="line">  <span class="attr">when:</span> <span class="string">ansible_os_family</span> <span class="string">==</span> <span class="string">&quot;Debian&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">RedHat</span> <span class="string">系列安装</span> <span class="string">nginx</span></span><br><span class="line">  <span class="attr">yum:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">    <span class="attr">state:</span> <span class="string">present</span></span><br><span class="line">  <span class="attr">when:</span> <span class="string">ansible_os_family</span> <span class="string">==</span> <span class="string">&quot;RedHat&quot;</span></span><br></pre></td></tr></table></figure></li></ul></li></ul><h4 id="5-加密与安全模块"><a href="#5-加密与安全模块" class="headerlink" title="5. 加密与安全模块"></a>5. 加密与安全模块</h4><ul><li><p><strong>ansible-vault（文件加密，非 Playbook 模块，属 Ansible 命令行工具，常用于加密敏感文件）</strong></p><ul><li>核心功能：加密&#x2F;解密 Ansible 敏感文件（如含密码的变量文件、Inventory 文件），避免敏感信息明文存储</li><li>常用命令：<ul><li>创建加密文件：<code>ansible-vault create secrets.yml</code>（交互式设置密码，创建后通过文本编辑器写入敏感内容）</li><li>编辑加密文件：<code>ansible-vault edit secrets.yml</code>（输入密码后编辑，保存后自动重新加密）</li><li>加密已有明文文件：<code>ansible-vault encrypt plain.yml</code>（将明文文件 <code>plain.yml</code> 加密为加密文件）</li><li>解密文件（临时）：<code>ansible-vault decrypt secrets.yml --output=plain_secrets.yml</code>（解密为明文文件 <code>plain_secrets.yml</code>，谨慎使用）</li><li>执行含加密文件的 Playbook：<code>ansible-playbook -i inventory.yml site.yml --ask-vault-pass</code>（执行时交互式输入 vault 密码）或 <code>--vault-password-file=vault_pass.txt</code>（从文件读取密码，需确保文件权限为 0600）</li></ul></li><li>应用场景：存储数据库密码、云服务密钥、SSH 私钥等敏感信息，如 <code>secrets.yml</code> 中定义 <code>db_password: &quot;EncryptedPassword123&quot;</code>，Playbook 中通过 <code>include_vars: secrets.yml</code> 加载并引用</li></ul></li><li><p><strong>openssl_privatekey（生成 OpenSSL 私钥）</strong></p><ul><li><p>核心功能：在目标主机生成 RSA&#x2F;ECC 私钥，用于 SSL&#x2F;TLS 证书配置（如 Nginx HTTPS、API 服务加密）</p></li><li><p>核心参数：</p><ul><li><code>path</code>：私钥存储路径，如 <code>/etc/nginx/ssl/server.key</code></li><li><code>size</code>：RSA 密钥长度，如 <code>2048</code> 或 <code>4096</code>（推荐 4096 位增强安全性）</li><li><code>type</code>：密钥类型，<code>RSA</code>（默认）或 <code>ECC</code>（椭圆曲线加密，效率更高）</li><li><code>mode</code>：私钥文件权限，如 <code>0600</code>（仅属主可读，必须严格限制权限）</li><li><code>state</code>：<code>present</code>（确保私钥存在，不存在则生成）、<code>absent</code>（删除私钥）</li></ul></li><li><p>示例（生成 Nginx HTTPS 私钥）：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">生成</span> <span class="string">Nginx</span> <span class="string">HTTPS</span> <span class="string">私钥（4096</span> <span class="string">位</span> <span class="string">RSA）</span></span><br><span class="line">  <span class="attr">openssl_privatekey:</span></span><br><span class="line">    <span class="attr">path:</span> <span class="string">/etc/nginx/ssl/server.key</span></span><br><span class="line">    <span class="attr">size:</span> <span class="number">4096</span></span><br><span class="line">    <span class="attr">mode:</span> <span class="number">0600</span></span><br><span class="line">    <span class="attr">state:</span> <span class="string">present</span></span><br></pre></td></tr></table></figure></li></ul></li><li><p><strong>openssl_certificate（生成&#x2F;签署 SSL 证书）</strong></p><ul><li><p>核心功能：生成自签名证书、CSR（证书签名请求）或通过 CA 签署证书，配合 <code>openssl_privatekey</code> 实现 HTTPS 配置</p></li><li><p>核心参数：</p><ul><li><code>path</code>：证书存储路径，如 <code>/etc/nginx/ssl/server.crt</code></li><li><code>privatekey_path</code>：对应私钥路径，如 <code>/etc/nginx/ssl/server.key</code>（与私钥关联）</li><li><code>provider</code>：证书生成方式，<code>selfsigned</code>（自签名，适用于测试&#x2F;内部服务）、<code>csr</code>（生成 CSR 文件，用于向公共 CA 申请证书）、<code>certificate_authority</code>（通过自建 CA 签署证书）</li><li><code>subject</code>：证书主题信息，字典格式，如 <code>&#123;&quot;CN&quot;: &quot;example.com&quot;, &quot;O&quot;: &quot;MyCompany&quot;, &quot;C&quot;: &quot;CN&quot;, &quot;ST&quot;: &quot;Beijing&quot;, &quot;L&quot;: &quot;Beijing&quot;&#125;</code>（CN 为域名，必须与服务访问域名一致）</li></ul></li><li><p>示例（生成自签名 HTTPS 证书）：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">生成</span> <span class="string">Nginx</span> <span class="string">HTTPS</span> <span class="string">自签名证书</span></span><br><span class="line">  <span class="attr">openssl_certificate:</span></span><br><span class="line">    <span class="attr">path:</span> <span class="string">/etc/nginx/ssl/server.crt</span></span><br><span class="line">    <span class="attr">privatekey_path:</span> <span class="string">/etc/nginx/ssl/server.key</span></span><br><span class="line">    <span class="attr">provider:</span> <span class="string">selfsigned</span></span><br><span class="line">    <span class="attr">subject:</span></span><br><span class="line">      <span class="attr">CN:</span> <span class="string">example.com</span></span><br><span class="line">      <span class="attr">O:</span> <span class="string">MyWebService</span></span><br><span class="line">      <span class="attr">C:</span> <span class="string">CN</span></span><br><span class="line">    <span class="attr">state:</span> <span class="string">present</span></span><br></pre></td></tr></table></figure></li></ul></li></ul><h3 id="八、特殊场景模块"><a href="#八、特殊场景模块" class="headerlink" title="八、特殊场景模块"></a>八、特殊场景模块</h3><h4 id="1-Windows-系统管理模块"><a href="#1-Windows-系统管理模块" class="headerlink" title="1. Windows 系统管理模块"></a>1. Windows 系统管理模块</h4><ul><li><p><strong>win_package（Windows 软件安装）</strong></p><ul><li><p>核心功能：在 Windows 主机安装 MSI 安装包或 exe 安装程序，支持静默安装</p></li><li><p>核心参数：</p><ul><li><code>name</code>：软件名称（用于标识已安装状态，如 <code>&quot;7-Zip 23.01 (x64)&quot;</code>）</li><li><code>path</code>：安装程序路径（本地路径或网络共享路径，如 <code>C:\installers\7z2301-x64.msi</code>、<code>\\fileserver\installers\Notepad++.exe</code>）</li><li><code>product_id</code>：MSI 包的 Product ID（可选，用于精准判断是否已安装，可通过 <code>msiexec /i 安装包.msi /qb! /log install.log</code> 安装后从注册表或日志获取）</li><li><code>arguments</code>：安装参数（静默安装参数，如 MSI 用 <code>/qn /norestart</code>，exe 用 <code>/S</code> 或 <code>/verysilent</code>，需参考软件安装文档）</li><li><code>state</code>：<code>present</code>（安装）、<code>absent</code>（卸载，仅支持 MSI 包）</li></ul></li><li><p>示例（安装 7-Zip MSI 包）：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Windows</span> <span class="string">安装</span> <span class="number">7</span><span class="string">-Zip</span></span><br><span class="line">  <span class="attr">win_package:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="number">7</span><span class="string">-Zip</span> <span class="number">23.01</span> <span class="string">(x64)</span></span><br><span class="line">    <span class="attr">path:</span> <span class="string">C:\temp\7z2301-x64.msi</span></span><br><span class="line">    <span class="attr">arguments:</span> <span class="string">/qn</span> <span class="string">/norestart</span>  <span class="comment"># 静默安装，不重启</span></span><br><span class="line">    <span class="attr">state:</span> <span class="string">present</span></span><br></pre></td></tr></table></figure></li></ul></li><li><p><strong>win_service（Windows 服务管理）</strong></p><ul><li><p>核心功能：管理 Windows 系统服务（启动&#x2F;停止&#x2F;重启&#x2F;设置开机启动），类似 Linux 的 <code>service</code> 模块</p></li><li><p>核心参数：</p><ul><li><code>name</code>：服务名称（需用服务的“服务名称”而非“显示名称”，可通过 <code>services.msc</code> 查看，如 <code>&quot;wuauserv&quot;</code> 对应“Windows Update”服务）</li><li><code>state</code>：<code>started</code>（启动）、<code>stopped</code>（停止）、<code>restarted</code>（重启）、<code>reloaded</code>（重载，部分服务支持）</li><li><code>start_mode</code>：开机启动模式，<code>auto</code>（自动）、<code>manual</code>（手动）、<code>disabled</code>（禁用）</li></ul></li><li><p>示例（启动并设置 Windows Update 服务为自动）：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">启动</span> <span class="string">Windows</span> <span class="string">Update</span> <span class="string">服务并设为自动启动</span></span><br><span class="line">  <span class="attr">win_service:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">wuauserv</span></span><br><span class="line">    <span class="attr">state:</span> <span class="string">started</span></span><br><span class="line">    <span class="attr">start_mode:</span> <span class="string">auto</span></span><br></pre></td></tr></table></figure></li></ul></li><li><p><strong>win_copy（Windows 本地&#x2F;远程文件拷贝）</strong></p><ul><li><p>核心功能：在 Windows 主机间或控制节点与 Windows 主机间拷贝文件，类似 Linux 的 <code>copy</code> 模块</p></li><li><p>核心参数：</p><ul><li><code>src</code>：源路径（控制节点路径或 Windows 主机本地路径，如 <code>./files/win_config.ini</code>、<code>C:\temp\old_config.ini</code>）</li><li><code>dest</code>：目标路径（Windows 主机路径，如 <code>C:\ProgramData\app\config.ini</code>）</li><li><code>remote_src</code>：<code>yes/no</code>（<code>yes</code> 表示源路径在目标 Windows 主机本地，<code>no</code> 表示源路径在控制节点）</li><li><code>force</code>：<code>yes/no</code>（<code>yes</code> 表示目标文件存在时强制覆盖，<code>no</code> 表示仅在目标文件不存在时拷贝）</li></ul></li><li><p>示例（从控制节点拷贝配置文件到 Windows 主机）：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">拷贝配置文件到</span> <span class="string">Windows</span> <span class="string">主机</span></span><br><span class="line">  <span class="attr">win_copy:</span></span><br><span class="line">    <span class="attr">src:</span> <span class="string">./files/app_config.ini</span></span><br><span class="line">    <span class="attr">dest:</span> <span class="string">C:\Program</span> <span class="string">Files\MyApp\config.ini</span></span><br><span class="line">    <span class="attr">force:</span> <span class="literal">yes</span></span><br></pre></td></tr></table></figure></li></ul></li></ul><h4 id="2-数据库操作模块"><a href="#2-数据库操作模块" class="headerlink" title="2. 数据库操作模块"></a>2. 数据库操作模块</h4><ul><li><p><strong>mysql_db（MySQL 数据库管理）</strong></p><ul><li><p>核心功能：创建&#x2F;删除 MySQL 数据库、导入 SQL 文件，需提前在目标主机安装 <code>PyMySQL</code> 或 <code>mysqlclient</code> Python 库（通过 <code>pip</code> 模块安装）</p></li><li><p>核心参数：</p><ul><li><code>name</code>：数据库名（如 <code>&quot;app_db&quot;</code>）</li><li><code>state</code>：<code>present</code>（创建数据库）、<code>absent</code>（删除数据库，谨慎使用）、<code>import</code>（导入 SQL 文件）</li><li><code>login_user</code>：MySQL 登录用户名（如 <code>&quot;root&quot;</code>）</li><li><code>login_password</code>：MySQL 登录密码（建议通过 <code>ansible-vault</code> 加密存储）</li><li><code>login_host</code>：MySQL 服务器地址（默认 <code>localhost</code>，远程数据库需指定）</li><li><code>login_port</code>：MySQL 端口（默认 <code>3306</code>）</li><li><code>target</code>：<code>state=import</code> 时，指定 SQL 文件路径（如 <code>/opt/sql/init_db.sql</code>）</li></ul></li><li><p>示例（创建 MySQL 数据库并导入初始化 SQL）：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">安装</span> <span class="string">PyMySQL（MySQL</span> <span class="string">模块依赖）</span></span><br><span class="line">  <span class="attr">pip:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">PyMySQL</span></span><br><span class="line">    <span class="attr">state:</span> <span class="string">present</span></span><br><span class="line"></span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">创建</span> <span class="string">app_db</span> <span class="string">数据库</span></span><br><span class="line">  <span class="attr">mysql_db:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">app_db</span></span><br><span class="line">    <span class="attr">state:</span> <span class="string">present</span></span><br><span class="line">    <span class="attr">login_user:</span> <span class="string">root</span></span><br><span class="line">    <span class="attr">login_password:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; mysql_root_password &#125;&#125;</span>&quot;</span>  <span class="comment"># 加密变量</span></span><br><span class="line">    <span class="attr">login_host:</span> <span class="string">localhost</span></span><br><span class="line"></span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">导入</span> <span class="string">app_db</span> <span class="string">初始化</span> <span class="string">SQL</span></span><br><span class="line">  <span class="attr">mysql_db:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">app_db</span></span><br><span class="line">    <span class="attr">state:</span> <span class="string">import</span></span><br><span class="line">    <span class="attr">target:</span> <span class="string">/opt/sql/init_app_db.sql</span></span><br><span class="line">    <span class="attr">login_user:</span> <span class="string">root</span></span><br><span class="line">    <span class="attr">login_password:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; mysql_root_password &#125;&#125;</span>&quot;</span></span><br></pre></td></tr></table></figure></li></ul></li><li><p><strong>postgresql_db（PostgreSQL 数据库管理）</strong></p><ul><li><p>核心功能：类似 <code>mysql_db</code>，用于 PostgreSQL 数据库的创建&#x2F;删除&#x2F;导入，依赖 <code>psycopg2</code> Python 库</p></li><li><p>核心参数：</p><ul><li><code>name</code>：数据库名（如 <code>&quot;pg_app_db&quot;</code>）</li><li><code>state</code>：<code>present</code>（创建）、<code>absent</code>（删除）、<code>import</code>（导入 SQL）</li><li><code>login_user</code>：PostgreSQL 登录用户（如 <code>&quot;postgres&quot;</code>）</li><li><code>login_password</code>：PostgreSQL 登录密码</li><li><code>login_host</code>：PostgreSQL 服务器地址（默认 <code>localhost</code>）</li><li><code>login_port</code>：PostgreSQL 端口（默认 <code>5432</code>）</li><li><code>target</code>：<code>state=import</code> 时的 SQL 文件路径</li></ul></li><li><p>示例（创建 PostgreSQL 数据库）：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">安装</span> <span class="string">psycopg2（PostgreSQL</span> <span class="string">模块依赖）</span></span><br><span class="line">  <span class="attr">apt:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">python3-psycopg2</span></span><br><span class="line">    <span class="attr">state:</span> <span class="string">present</span></span><br><span class="line">  <span class="attr">when:</span> <span class="string">ansible_os_family</span> <span class="string">==</span> <span class="string">&quot;Debian&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">创建</span> <span class="string">pg_app_db</span> <span class="string">数据库</span></span><br><span class="line">  <span class="attr">postgresql_db:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">pg_app_db</span></span><br><span class="line">    <span class="attr">state:</span> <span class="string">present</span></span><br><span class="line">    <span class="attr">login_user:</span> <span class="string">postgres</span></span><br><span class="line">    <span class="attr">login_password:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; pg_postgres_password &#125;&#125;</span>&quot;</span></span><br></pre></td></tr></table></figure></li></ul></li></ul><h3 id="九、模块使用注意事项与最佳实践"><a href="#九、模块使用注意事项与最佳实践" class="headerlink" title="九、模块使用注意事项与最佳实践"></a>九、模块使用注意事项与最佳实践</h3><h4 id="1-模块依赖检查"><a href="#1-模块依赖检查" class="headerlink" title="1. 模块依赖检查"></a>1. 模块依赖检查</h4><ul><li><p><strong>Python 依赖</strong>：绝大多数 Ansible 模块依赖 Python（如 <code>yum</code>&#x2F;<code>apt</code>&#x2F;<code>file</code>&#x2F;<code>copy</code> 等），目标主机需提前安装 Python（Linux 通常预装，最小化系统需手动安装：CentOS 用 <code>yum install -y python3</code>，Ubuntu 用 <code>apt install -y python3</code>）</p></li><li><p><strong>模块专属依赖</strong>：部分模块需额外安装依赖库，如：</p><ul><li><p>数据库模块：<code>mysql_db</code> 依赖 <code>PyMySQL</code>&#x2F;<code>mysqlclient</code>，<code>postgresql_db</code> 依赖 <code>psycopg2</code></p></li><li><p>云服务模块：<code>aws_s3</code>&#x2F;<code>ec2</code> 依赖 <code>boto3</code>&#x2F;<code>botocore</code>，<code>azure_rm_virtualmachine</code> 依赖 <code>azure-mgmt-compute</code></p></li><li><p>建议在 Playbook 开头通过 <code>pip</code> 模块统一安装依赖，如：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">安装</span> <span class="string">AWS</span> <span class="string">模块依赖</span></span><br><span class="line">  <span class="attr">pip:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">boto3</span></span><br><span class="line">    <span class="attr">state:</span> <span class="string">present</span></span><br></pre></td></tr></table></figure></li></ul></li></ul><h4 id="2-幂等性保障（核心原则）"><a href="#2-幂等性保障（核心原则）" class="headerlink" title="2. 幂等性保障（核心原则）"></a>2. 幂等性保障（核心原则）</h4><ul><li><p><strong>定义</strong>：Ansible 模块需支持“幂等性”——即多次执行同一任务，结果一致且无副作用（如安装软件时，已安装则跳过；创建文件时，已存在则不重复创建）</p></li><li><p><strong>关键参数</strong>：</p><ul><li><code>state: present</code>（确保存在，已存在则跳过）而非 <code>state: latest</code>（强制升级，可能有风险）</li><li><code>creates</code>&#x2F;<code>removes</code>（<code>command</code>&#x2F;<code>shell</code> 模块实现幂等性的关键，避免重复执行命令）</li><li><code>force: no</code>（<code>copy</code>&#x2F;<code>template</code> 模块默认值，仅在源文件变化时覆盖目标文件）</li></ul></li><li><p><strong>反例（非幂等）</strong>：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">非幂等命令（每次执行都会追加内容）</span></span><br><span class="line">  <span class="attr">shell:</span> <span class="string">echo</span> <span class="string">&quot;test&quot;</span> <span class="string">&gt;&gt;</span> <span class="string">/tmp/log.txt</span>  <span class="comment"># 多次执行会重复追加，无幂等性</span></span><br></pre></td></tr></table></figure></li><li><p><strong>正例（幂等）</strong>：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">幂等命令（仅在文件不存在时执行）</span></span><br><span class="line">  <span class="attr">shell:</span> <span class="string">echo</span> <span class="string">&quot;test&quot;</span> <span class="string">&gt;</span> <span class="string">/tmp/log.txt</span></span><br><span class="line">  <span class="attr">creates:</span> <span class="string">/tmp/log.txt</span>  <span class="comment"># 文件存在则跳过，实现幂等性</span></span><br></pre></td></tr></table></figure></li></ul><h4 id="3-安全性最佳实践"><a href="#3-安全性最佳实践" class="headerlink" title="3. 安全性最佳实践"></a>3. 安全性最佳实践</h4><ul><li><strong>敏感信息加密</strong>：使用 <code>ansible-vault</code> 加密含密码、密钥的文件，避免明文存储；执行 Playbook 时通过 <code>--ask-vault-pass</code> 或安全的密码文件（权限 0600）传递密码</li><li><strong>最小权限原则</strong>：避免用 <code>root</code> 执行所有任务，可通过 <code>become: yes</code> 按需提权，或在 Inventory 中指定普通用户+<code>sudo</code> 权限</li><li><strong>避免命令注入</strong>：<code>shell</code> 模块使用变量时，通过 <code>quote</code> 过滤器转义，如 <code>cmd: &quot;echo &#123;&#123; user_input | quote &#125;&#125;&quot;</code></li><li><strong>模块优先于 raw&#x2F;shell</strong>：能用官方模块（如 <code>file</code>&#x2F;<code>copy</code>&#x2F;<code>service</code>）实现的功能，优先不用 <code>raw</code>&#x2F;<code>shell</code>，减少脚本依赖和安全风险</li></ul>]]>
    </content>
    <id>https://www.chucz.asia/2026/04/09/Ansible%E5%B8%B8%E7%94%A8%E6%A8%A1%E5%9D%97%E5%88%86%E7%B1%BB/</id>
    <link href="https://www.chucz.asia/2026/04/09/Ansible%E5%B8%B8%E7%94%A8%E6%A8%A1%E5%9D%97%E5%88%86%E7%B1%BB/"/>
    <published>2026-04-09T06:42:38.000Z</published>
    <summary>
      <![CDATA[<h3 id="一、系统管理类"><a href="#一、系统管理类" class="headerlink" title="一、系统管理类"></a>一、系统管理类</h3><h4 id="1-计划任务与定时"><a href="#1-计划任务与定时"]]>
    </summary>
    <title>Ansible常用模块分类</title>
    <updated>2026-04-09T06:42:38.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>褚成志</name>
    </author>
    <category term="云原生" scheme="https://www.chucz.asia/categories/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    <category term="CDN" scheme="https://www.chucz.asia/tags/CDN/"/>
    <category term="云计算" scheme="https://www.chucz.asia/tags/%E4%BA%91%E8%AE%A1%E7%AE%97/"/>
    <category term="运维" scheme="https://www.chucz.asia/tags/%E8%BF%90%E7%BB%B4/"/>
    <content>
      <![CDATA[<h2 id="视图预览"><a href="#视图预览" class="headerlink" title="视图预览"></a>视图预览</h2><p>阿里云 CDN 每秒访问次数，下行流量，边缘带宽，响应时间，回源带宽，状态码等</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155353896-932477619.png" alt="1655274854069-ce14f790-5aca-40da-aa83-4dd6f639fc05.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155354306-555570770.png" alt="1655274871454-e246991c-0584-4ce3-a859-65c28e7631bc.png"></p><h2 id="版本支持"><a href="#版本支持" class="headerlink" title="版本支持"></a>版本支持</h2><p><font style="color:#595959;">操作系统支持：Linux  </font></p><h2 id="前置条件"><a href="#前置条件" class="headerlink" title="前置条件"></a>前置条件</h2><ul><li>服务器 &lt;<a href="https://www.yuque.com/dataflux/datakit/datakit-install">安装 Datakit</a>&gt;</li><li>服务器 &lt;<a href="https://www.yuque.com/dataflux/func/quick-start">安装 Func 携带版</a>&gt;</li><li>阿里云 RAM 访问控制账号授权</li></ul><h3 id="RAM-访问控制"><a href="#RAM-访问控制" class="headerlink" title="RAM 访问控制"></a>RAM 访问控制</h3><ol><li>登录 RAM 控制台  <a href="https://ram.console.aliyun.com/users">https://ram.console.aliyun.com/users</a></li><li>新建用户：人员管理 - 用户 - 创建用户</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155354676-149418565.png" alt="1627893591261-ed0721f4-85d0-44a2-9b66-fe2bcb3bf41c.png"></p><ol start="3"><li>保存或下载 <strong>AccessKey</strong> <strong>ID</strong> 和 <strong>AccessKey Secret</strong> 的 CSV 文件 (配置文件会用到)</li><li>用户授权 (只读访问所有阿里云资源的权限)</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155354849-611652146.png" alt="1649828381331-297ae158-1055-4865-b195-b99aa9e2a4f4.png"></p><h2 id="安装配置"><a href="#安装配置" class="headerlink" title="安装配置"></a>安装配置</h2><p><font style="color:#595959;">说明：</font></p><ul><li><font style="color:#595959;">示例 Linux 版本为：CentOS Linux release 7.8.2003 (Core)</font></li><li><font style="color:#595959;">通过一台服务器采集所有阿里云 CDN 数据</font></li></ul><h3 id="部署实施"><a href="#部署实施" class="headerlink" title="部署实施"></a>部署实施</h3><h4 id="脚本市场"><a href="#脚本市场" class="headerlink" title="脚本市场"></a>脚本市场</h4><ol><li>登录 Func，地址 <code>http://ip:8088</code></li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155355014-1811225552.png" alt="1639115383741-ad518ea3-5206-4e62-a6a2-d14fdf1b8f4e.png"></p><ol start="2"><li>开启脚本市场，管理 - 实验性功能 - 开启脚本市场模块</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155355194-17993445.png" alt="1639115461724-ce238618-34e5-453a-bed4-18504ad89ecf.png"></p><ol start="3"><li>**依次添加 **三个脚本集<ol><li>观测云集成 (核心包)</li><li>观测云集成 (阿里云-云监控)</li><li>观测云集成 (阿里云-CDN)</li></ol></li></ol><p><em>注：在安装「核心包」后，系统会提示安装第三方依赖包，按照正常步骤点击安装即可</em></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155355399-1010432614.png" alt="1649826266099-5aa76807-cd87-4f0c-8d38-5aeb6a3674b7.png"></p><ol start="4"><li>脚本安装完成后，可以在脚本库中看到所有脚本集</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155355566-1899109434.png" alt="1655274967956-a62681e4-6e26-4e84-b143-a890474e0447.png"></p><h4 id="添加脚本"><a href="#添加脚本" class="headerlink" title="添加脚本"></a>添加脚本</h4><ol><li>开发 - 脚本库 - 添加脚本集</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155355729-399637476.png" alt="1649815907360-7693c6b3-4ded-4f6b-ba2b-224a37cfac54.png"></p><ol start="2"><li>点击该脚本集 - 添加脚本</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155355915-959461458.png" alt="1649816044628-e51205c0-75b5-491c-8a06-ab41e448278e.png"></p><ol start="3"><li>创建 ID 为 main 的脚本</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155356084-1268767413.png" alt="1655274996474-2b1b00f4-7880-489b-aa52-d545c5867c9c.png"></p><ol start="4"><li>添加代码 (需要修改账号配置 <strong>AccessKey ID&#x2F;AccessKey Secret&#x2F;Account Name</strong>)</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">from guance_integration__runner import Runner        # 引入启动器</span><br><span class="line">import guance_aliyun_cdn__main as aliyun_cdn         # 引入阿里云NAT采集器</span><br><span class="line">import guance_aliyun_monitor__main as aliyun_monitor # 引入阿里云云监控采集器</span><br><span class="line"></span><br><span class="line"># 账号配置</span><br><span class="line">account = &#123;</span><br><span class="line">    &#x27;ak_id&#x27;     : &#x27;AccessKey ID&#x27;,</span><br><span class="line">    &#x27;ak_secret&#x27; : &#x27;AccessKey Secret&#x27;,</span><br><span class="line">    &#x27;extra_tags&#x27;: &#123;</span><br><span class="line">        &#x27;account_name&#x27;: &#x27;Account Name&#x27;,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"># 由于采集数据较多，此处需要为函数指定更大的超时时间（单位秒）</span><br><span class="line">@DFF.API(&#x27;执行云资产同步&#x27;, timeout=300)</span><br><span class="line">def run():</span><br><span class="line">    # 采集器配置</span><br><span class="line">    common_aliyun_configs = &#123;</span><br><span class="line">        &#x27;regions&#x27;: [ &#x27;cn-hangzhou&#x27; ], #阿里云CDN对应的地域</span><br><span class="line">    &#125;</span><br><span class="line">    monitor_collector_configs = &#123;</span><br><span class="line">        &#x27;targets&#x27;: [</span><br><span class="line">            &#123; &#x27;namespace&#x27;: &#x27;acs_cdn&#x27;, &#x27;metrics&#x27;: &#x27;ALL&#x27; &#125;, # 采集云监控中CDN所有指标</span><br><span class="line">        ],</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    # 创建采集器</span><br><span class="line">    collectors = [</span><br><span class="line">        aliyun_cdn.DataCollector(account, common_aliyun_configs),</span><br><span class="line">        aliyun_monitor.DataCollector(account, monitor_collector_configs),</span><br><span class="line">    ]</span><br><span class="line"></span><br><span class="line">    # 启动执行</span><br><span class="line">    Runner(collectors).run()</span><br></pre></td></tr></table></figure><ol start="5"><li>**保存 **配置并 <strong>发布</strong></li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155356241-1837128300.png" alt="1649817737331-814ce09c-e8bb-45dc-88c2-4481e95ab99c.png"></p><h4 id="定时任务"><a href="#定时任务" class="headerlink" title="定时任务"></a>定时任务</h4><ol><li>添加自动触发任务，管理 - 自动触发配置 - 新建任务</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155356418-14294007.png" alt="1649817087577-c59c21a6-27cc-468c-a05e-7d4bca51e397.png"></p><ol start="2"><li>自动触发配置，执行函数中添加此脚本，执行频率为 **5分钟  *&#x2F;5 * * * * **(1分钟会被阿里限流)</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155356614-663750542.png" alt="1655275052392-eb45c998-1f45-4f3e-93de-a3387508be3f.png"></p><ol start="3"><li>指标预览</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155356808-1760373115.png" alt="1655275121241-f1d4d9fc-e54c-45b3-8be9-a5e6e4080e77.png"></p><h2 id="场景视图"><a href="#场景视图" class="headerlink" title="场景视图"></a>场景视图</h2><p>&lt;场景 - 新建仪表板 - 内置模板库 - 阿里云 CDN&gt;</p><h2 id="指标详解"><a href="#指标详解" class="headerlink" title="指标详解"></a>指标详解</h2><p>&lt;<a href="https://help.aliyun.com/document_detail/162873.htm?spm=a2c4g.11186623.0.0.43b973c2ZMvsUt#concept-2482416">阿里云 CDN 指标列表</a>&gt;</p><h2 id="常见问题排查"><a href="#常见问题排查" class="headerlink" title="常见问题排查"></a>常见问题排查</h2><ul><li>查看日志：Func 日志路径 &#x2F;usr&#x2F;local&#x2F;dataflux-func&#x2F;data&#x2F;logs&#x2F;dataflux-func.log</li><li>代码调试：选择主函数，直接运行 (可以看到脚本输出)</li></ul><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155356988-809289364.png" alt="1639118030344-b9e32474-c580-45cc-8ea3-67fc962be137.png"></p><ul><li>连接配置：Func 无法连接 Datakit，请检查数据源配置</li></ul><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155357202-1426288045.png" alt="1640337156764-a214db06-5150-472b-8905-03a81b430d86.png"></p>]]>
    </content>
    <id>https://www.chucz.asia/2026/04/09/CDN%E9%83%A8%E7%BD%B2/</id>
    <link href="https://www.chucz.asia/2026/04/09/CDN%E9%83%A8%E7%BD%B2/"/>
    <published>2026-04-09T06:42:38.000Z</published>
    <summary>
      <![CDATA[<h2 id="视图预览"><a href="#视图预览" class="headerlink" title="视图预览"></a>视图预览</h2><p>阿里云 CDN 每秒访问次数，下行流量，边缘带宽，响应时间，回源带宽，状态码等</p>
<p><img]]>
    </summary>
    <title>CDN部署</title>
    <updated>2026-04-09T06:42:38.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>褚成志</name>
    </author>
    <category term="后端" scheme="https://www.chucz.asia/categories/%E5%90%8E%E7%AB%AF/"/>
    <category term="Java" scheme="https://www.chucz.asia/tags/Java/"/>
    <category term="Dubbo" scheme="https://www.chucz.asia/tags/Dubbo/"/>
    <category term="SPI" scheme="https://www.chucz.asia/tags/SPI/"/>
    <category term="微服务" scheme="https://www.chucz.asia/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
    <content>
      <![CDATA[<h1 id="Dubbo-SPI-机制"><a href="#Dubbo-SPI-机制" class="headerlink" title="Dubbo SPI 机制"></a>Dubbo SPI 机制</h1><p><img src="https://cdn.chucz.asia/blog/3703820-20251013011745029-1718408462.jpg" alt="画板"></p>]]>
    </content>
    <id>https://www.chucz.asia/2026/04/09/Dubbo%20SPI%E6%9C%BA%E5%88%B6/</id>
    <link href="https://www.chucz.asia/2026/04/09/Dubbo%20SPI%E6%9C%BA%E5%88%B6/"/>
    <published>2026-04-09T06:42:38.000Z</published>
    <summary>
      <![CDATA[<h1 id="Dubbo-SPI-机制"><a href="#Dubbo-SPI-机制" class="headerlink" title="Dubbo SPI 机制"></a>Dubbo SPI 机制</h1><p><img]]>
    </summary>
    <title>Dubbo SPI机制</title>
    <updated>2026-04-09T06:42:38.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>褚成志</name>
    </author>
    <category term="云原生" scheme="https://www.chucz.asia/categories/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    <category term="阿里云" scheme="https://www.chucz.asia/tags/%E9%98%BF%E9%87%8C%E4%BA%91/"/>
    <category term="监控" scheme="https://www.chucz.asia/tags/%E7%9B%91%E6%8E%A7/"/>
    <category term="网络" scheme="https://www.chucz.asia/tags/%E7%BD%91%E7%BB%9C/"/>
    <content>
      <![CDATA[<h2 id="视图预览"><a href="#视图预览" class="headerlink" title="视图预览"></a>视图预览</h2><p>阿里云 EIP 指标展示，包括网络带宽，网络数据包，限速丢包率，带宽利用率等</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160110674-883238684.png" alt="1640054944189-7f78738d-3e4c-4b5e-9a01-700d02f215d4.png"></p><h2 id="版本支持"><a href="#版本支持" class="headerlink" title="版本支持"></a>版本支持</h2><p><font style="color:#595959;">操作系统支持：Linux</font></p><h2 id="前置条件"><a href="#前置条件" class="headerlink" title="前置条件"></a>前置条件</h2><ul><li>服务器 &lt;<a href="https://www.yuque.com/dataflux/datakit/datakit-install">安装 Datakit</a>&gt;</li><li>服务器 &lt;<a href="https://www.yuque.com/dataflux/func/quick-start">安装 Func 携带版</a>&gt;</li><li>阿里云 RAM 访问控制账号授权</li></ul><h3 id="RAM-访问控制"><a href="#RAM-访问控制" class="headerlink" title="RAM 访问控制"></a>RAM 访问控制</h3><ol><li>登录 RAM 控制台  <a href="https://ram.console.aliyun.com/users">https://ram.console.aliyun.com/users</a></li><li>新建用户：人员管理 - 用户 - 创建用户</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160111035-1590478103.png" alt="1627893591261-ed0721f4-85d0-44a2-9b66-fe2bcb3bf41c.png"></p><ol start="3"><li>保存或下载 <strong>AccessKey</strong> <strong>ID</strong> 和 <strong>AccessKey Secret</strong> 的 CSV 文件 (配置文件会用到)</li><li>用户授权 (云监控只读&#x2F;时序指标数据权限)</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160111376-1942523285.png" alt="1627893756593-a6f5114d-309e-419a-96b2-e3637f0028c7.png"></p><h2 id="安装配置"><a href="#安装配置" class="headerlink" title="安装配置"></a>安装配置</h2><p><font style="color:#595959;">说明：</font></p><ul><li><font style="color:#595959;">示例 Linux 版本为：CentOS Linux release 7.8.2003 (Core)</font></li><li><font style="color:#595959;">通过一台服务器采集所有阿里云 EIP 数据</font></li></ul><h3 id="部署实施"><a href="#部署实施" class="headerlink" title="部署实施"></a>部署实施</h3><h4 id="脚本市场"><a href="#脚本市场" class="headerlink" title="脚本市场"></a>脚本市场</h4><ol><li>登录 Func，地址 <a href="http://ip:8088/">http://ip:8088</a></li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160111828-987278076.png" alt="1639115383741-ad518ea3-5206-4e62-a6a2-d14fdf1b8f4e.png"></p><ol start="2"><li>开启脚本市场，管理 - 实验性功能 - 开启脚本市场模块</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160112030-1405786668.png" alt="1639115461724-ce238618-34e5-453a-bed4-18504ad89ecf.png"></p><ol start="3"><li>载入阿里云数据同步脚本，管理 - 脚本市场 - 阿里云数据同步 (云监控)</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160112258-524148468.png" alt="1639115605557-ad655a12-e075-4853-9e66-d580063bc39a.png"></p><h4 id="添加脚本"><a href="#添加脚本" class="headerlink" title="添加脚本"></a>添加脚本</h4><ol><li>阿里云数据同步 (云监控) - 添加脚本</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160112509-144002274.png" alt="1646811929850-60252f92-950a-4865-84bf-5ff8f0c4d7ea.png"></p><ol start="2"><li>输入标题&#x2F;描述信息</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160112768-1122623865.png" alt="1640052475030-a2af4128-94c2-4244-a44c-3d190f42d735.png"></p><ol start="3"><li>编辑脚本并复制代码，从 (同步阿里云监控数据) 到当前脚本</li><li>修改阿里云账号配置 (Ram 访问控制)</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#x27;aliyun_ak_id&#x27;    : &#x27;AccessKey ID&#x27;,</span><br><span class="line">&#x27;aliyun_ak_secret&#x27;: &#x27;AccessKey Secret&#x27;,</span><br></pre></td></tr></table></figure><ol start="5"><li>修改阿里云 EIP 指标</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#x27;metric_targets&#x27;: [</span><br><span class="line">    &#123;</span><br><span class="line">        &#x27;namespace&#x27;: &#x27;acs_vpc_eip&#x27;,</span><br><span class="line">        &#x27;metrics&#x27;: &#x27;ALL&#x27;,</span><br><span class="line">     &#125;           </span><br><span class="line">                  ]</span><br></pre></td></tr></table></figure><ol start="6"><li>**保存 **配置并 <strong>发布</strong></li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160113000-2097993604.png" alt="1639117330715-7736eb23-9b6e-4c70-a9f7-9a92698c60c7.png"></p><h4 id="定时任务"><a href="#定时任务" class="headerlink" title="定时任务"></a>定时任务</h4><ol><li>添加自动触发任务，管理 - 自动触发配置 - 新建任务</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160113285-378986207.png" alt="1639117392729-b4d53ad3-e62c-487d-89a6-fd1458243682.png"></p><ol start="2"><li>自动触发配置，执行函数中添加此脚本，其他默认即可</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160113533-1209311757.png" alt="1640052618739-22fcd940-6615-4a6d-8d93-119c869d0b9d.png"></p><ol start="3"><li>指标预览</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160113734-777830768.png" alt="1640052659642-9f8a8283-e355-4161-b93a-805b32da0544.png"></p><h2 id="场景视图"><a href="#场景视图" class="headerlink" title="场景视图"></a>场景视图</h2><p>&lt;场景 - 新建仪表板 - 内置模板库 - 阿里云 EIP&gt;</p><h2 id="监控规则"><a href="#监控规则" class="headerlink" title="监控规则"></a>监控规则</h2><p>&lt;监控 - 模板新建 - 阿里云 EIP 检测库&gt;</p><h2 id="指标详解"><a href="#指标详解" class="headerlink" title="指标详解"></a>指标详解</h2><p>&lt;<a href="https://help.aliyun.com/document_detail/162874.htm?spm=a2c4g.11186623.0.0.43b91c279SYf4b#concept-2482420">阿里云 EIP 指标列表</a>&gt;</p><h2 id="故障排查"><a href="#故障排查" class="headerlink" title="故障排查"></a>故障排查</h2><ul><li>查看日志：Func 日志路径 &#x2F;usr&#x2F;local&#x2F;dataflux-func&#x2F;data&#x2F;logs&#x2F;dataflux-func.log</li><li>代码调试：选择主函数，直接运行 (可以看到脚本输出)</li></ul><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160113952-594164110.png" alt="1639118030344-b9e32474-c580-45cc-8ea3-67fc962be137.png"></p><ul><li>连接配置：Func 无法连接 Datakit，请检查数据源配置</li></ul><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160114162-1431037208.png" alt="1640337156764-a214db06-5150-472b-8905-03a81b430d86.png"></p>]]>
    </content>
    <id>https://www.chucz.asia/2026/04/09/EIP%E6%8C%87%E6%A0%87%E7%9B%91%E6%8E%A7/</id>
    <link href="https://www.chucz.asia/2026/04/09/EIP%E6%8C%87%E6%A0%87%E7%9B%91%E6%8E%A7/"/>
    <published>2026-04-09T06:42:38.000Z</published>
    <summary>
      <![CDATA[<h2 id="视图预览"><a href="#视图预览" class="headerlink" title="视图预览"></a>视图预览</h2><p>阿里云 EIP 指标展示，包括网络带宽，网络数据包，限速丢包率，带宽利用率等</p>
<p><img]]>
    </summary>
    <title>EIP指标监控</title>
    <updated>2026-04-09T06:42:38.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>褚成志</name>
    </author>
    <category term="大数据" scheme="https://www.chucz.asia/categories/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
    <category term="Hadoop" scheme="https://www.chucz.asia/tags/Hadoop/"/>
    <category term="HDFS" scheme="https://www.chucz.asia/tags/HDFS/"/>
    <category term="高可用" scheme="https://www.chucz.asia/tags/%E9%AB%98%E5%8F%AF%E7%94%A8/"/>
    <content>
      <![CDATA[<p>high avilability</p><h2 id="HA-概述"><a href="#HA-概述" class="headerlink" title="HA****概述"></a><strong>HA****概述</strong></h2><p>1）所谓HA（High Availablity），即高可用（7*24小时不中断服务）。</p><p>2）实现高可用最关键的策略是消除单点故障。HA严格来说应该分成各个组件的HA机制：<font style="color:#F5222D;background-color:#FADB14;">HDFS</font><font style="color:#F5222D;background-color:#FADB14;">的HA和</font><font style="color:#F5222D;background-color:#FADB14;">YARN</font><font style="color:#F5222D;background-color:#FADB14;">的HA。</font></p><p>3）Hadoop2.0之前，在HDFS集群中NameNode存在单点故障（SPOF）。</p><p>4）NameNode主要在以下两个方面影响HDFS集群</p><pre><code>NameNode机器发生意外，如宕机，集群将无法使用，直到管理员重启NameNode机器需要升级，包括软件、硬件升级，此时集群也将无法使用</code></pre><p>HDFS HA功能通过配置Active&#x2F;Standby两个NameNodes实现在集群中对NameNode的热备来解决上述问题。如果出现故障，如机器崩溃或机器需要升级维护，这时可通过此种方式将NameNode很快的切换到另外一台机器。</p><h2 id="HDFS-HA-工作机制"><a href="#HDFS-HA-工作机制" class="headerlink" title="HDFS-HA****工作机制"></a><strong>HDFS-HA****工作机制</strong></h2><p>通过双NameNode消除单点故障</p><h3 id="HDFS-HA工作要点"><a href="#HDFS-HA工作要点" class="headerlink" title="*HDFS-HA工作要点*"></a>*<em>HDFS-<strong><strong>H</strong></strong>A</em><em><strong>工作</strong></em><em>要点</em>*</h3><ul><li>元数据管理方式需要改变</li></ul><p>内存中各自保存一份元数据；</p><p>Edits日志只有Active状态的NameNode节点可以做写操作；</p><p>两个NameNode都可以读取Edits；</p><p>共享的Edits放在一个共享存储中管理（qjournal和NFS两个主流实现）；</p><ul><li>需要一个状态管理功能模块</li></ul><p>实现了一个zkfailover，常驻在每一个namenode所在的节点，每一个zkfailover负责监控自己所在NameNode节点，利用zk进行状态标识，当需要进行状态切换时，由zkfailover来负责切换，切换时需要防止brain split现象的发生。</p><ul><li>必须保证两个NameNode之间能够ssh无密码登录</li><li>隔离（Fence），即同一时刻仅仅有一个NameNode对外提供服务</li></ul><h3 id="HDFS-HA自动故障转移工作机制"><a href="#HDFS-HA自动故障转移工作机制" class="headerlink" title="*HDFS-HA自动故障转移工作机制*"></a>*<em>HDFS-<strong><strong>H</strong></strong>A</em><em><strong>自动故障转移工作</strong></em><em>机制</em>*</h3><p>前面学习了使用命令hdfs haadmin -failover手动进行故障转移，在该模式下，即使现役NameNode已经失效，系统也不会自动从现役NameNode转移到待机NameNode，下面学习如何配置部署HA自动进行故障转移。自动故障转移为HDFS部署增加了两个新组件：ZooKeeper和ZKFailoverController（ZKFC）进程，如图3-20所示。ZooKeeper是维护少量协调数据，通知客户端这些数据的改变和监视客户端故障的高可用服务。HA的自动故障转移依赖于ZooKeeper的以下功能：</p><p><strong>1</strong>**）****故障检测：**集群中的每个NameNode在ZooKeeper中维护了一个持久会话，如果机器崩溃，ZooKeeper中的会话将终止，ZooKeeper通知另一个NameNode需要触发故障转移。</p><p><strong>2</strong>**）****现役NameNode选择：**ZooKeeper提供了一个简单的机制用于唯一的选择一个节点为active状态。如果目前现役NameNode崩溃，另一个节点可能从ZooKeeper获得特殊的排外锁以表明它应该成为现役NameNode。</p><p><font style="color:#000000;">ZKFC是自动故障转移中的另一个新组件，是ZooKeeper的客户端，也监视和管理NameNode的状态。每个运行NameNode的主机也运行了一个ZKFC进程，ZKFC负责：</font></p><p>**1）****健康监测：**ZKFC使用一个健康检查命令定期地ping与之在相同主机的NameNode，只要该NameNode及时地回复健康状态，ZKFC认为该节点是健康的。如果该节点崩溃，冻结或进入不健康状态，健康监测器标识该节点为非健康的。</p><p><strong>2</strong>**）****ZooKeeper会话管理：**当本地NameNode是健康的，ZKFC保持一个在ZooKeeper中打开的会话。如果本地NameNode处于active状态，ZKFC也保持一个特殊的znode锁，该锁使用了ZooKeeper对短暂节点的支持，如果会话终止，锁节点将自动删除。</p><p>**3）****基于ZooKeeper的选择：**如果本地NameNode是健康的，且ZKFC发现没有其它的节点当前持有znode锁，它将为自己获取该锁。如果成功，则它已经赢得了选择，并负责运行故障转移进程以使它的本地NameNode为Active。故障转移进程与前面描述的手动故障转移相似，首先如果必要保护之前的现役NameNode，然后本地NameNode转换为Active状态。</p><h2 id="HDFS-HA集群配置"><a href="#HDFS-HA集群配置" class="headerlink" title="**HDFS-**HA集群配置"></a>**HDFS-**<strong>HA集群配置</strong></h2><h3 id="环境准备"><a href="#环境准备" class="headerlink" title="环境准备"></a><strong>环境准备</strong></h3><ol><li><p>   修改IP</p></li><li><p>   修改主机名及主机名和IP地址的映射</p></li><li><p>   关闭防火墙</p></li><li><p>   ssh免密登录</p></li><li><p>   安装JDK，配置环境变量等</p></li></ol><h3 id="规划-集群"><a href="#规划-集群" class="headerlink" title="规划****集群"></a><strong>规划****集群</strong></h3><table><thead><tr><th>hadoop102</th><th>hadoop103</th><th>hadoop104</th></tr></thead><tbody><tr><td>NameNode</td><td>NameNode</td><td></td></tr><tr><td>ZKFC</td><td>ZKFC</td><td></td></tr><tr><td>JournalNode</td><td>JournalNode</td><td>JournalNode</td></tr><tr><td>DataNode</td><td>DataNode</td><td>DataNode</td></tr><tr><td>ZK</td><td>ZK</td><td>ZK</td></tr><tr><td></td><td>ResourceManager</td><td></td></tr><tr><td>NodeManager</td><td>NodeManager</td><td>NodeManager</td></tr></tbody></table><h3 id="配置Zookeeper-集群"><a href="#配置Zookeeper-集群" class="headerlink" title="配置Zookeeper****集群"></a><strong>配置Zookeeper****集群</strong></h3><ol><li>   集群规划</li></ol><p>在hadoop102、hadoop103和hadoop104三个节点上部署Zookeeper。</p><ol start="2"><li>   解压安装</li></ol><p>（1）解压Zookeeper安装包到&#x2F;opt&#x2F;module&#x2F;目录下</p><p>[atguigu@hadoop102 software]$ tar -zxvf zookeeper-3.4.14.tar.gz -C &#x2F;opt&#x2F;module&#x2F;</p><p>（2）在&#x2F;opt&#x2F;module&#x2F;zookeeper-3.4.14&#x2F;这个目录下创建zkData</p><p>mkdir -p zkData</p><p>（3）重命名&#x2F;opt&#x2F;module&#x2F;zookeeper-3.4.14&#x2F;conf这个目录下的zoo_sample.cfg为zoo.cfg</p><p>mv zoo_sample.cfg zoo.cfg</p><ol start="3"><li><p>   配置zoo.cfg文件</p><p>  （1）具体配置</p></li></ol><p>dataDir&#x3D;&#x2F;opt&#x2F;module&#x2F;zookeeper-3.4.14&#x2F;zkData</p><pre><code>增加如下配置</code></pre><p>#######################cluster##########################</p><p>server.2&#x3D;hadoop102:2888:3888</p><p>server.3&#x3D;hadoop103:2888:3888</p><p>server.4&#x3D;hadoop104:2888:3888</p><p>（2）配置参数解读</p><p>Server.A&#x3D;B:C:D。</p><p>A是一个数字，表示这个是第几号服务器；</p><p>B是这个服务器的IP地址；</p><p>C是这个服务器与集群中的Leader服务器交换信息的端口；</p><p>D是万一集群中的Leader服务器挂了，需要一个端口来重新进行选举，选出一个新的Leader，而这个端口就是用来执行选举时服务器相互通信的端口。</p><p>集群模式下配置一个文件myid，这个文件在dataDir目录下，这个文件里面有一个数据就是A的值，Zookeeper启动时读取此文件，拿到里面的数据与zoo.cfg里面的配置信息比较从而判断到底是哪个server。</p><ol start="4"><li>   集群操作</li></ol><p>（1）在&#x2F;opt&#x2F;module&#x2F;zookeeper-3.4.14&#x2F;zkData目录下创建一个myid的文件</p><p>touch myid</p><p><font style="color:#FF0000;">添加myid文件，注意一定要在linux里面创建</font><font style="color:#FF0000;">，</font><font style="color:#FF0000;">在notepad++</font><font style="color:#FF0000;">里面</font><font style="color:#FF0000;">很可能乱码</font></p><p>（2）编辑myid文件</p><p>vi myid</p><pre><code>在文件中添加与server对应的编号：如2</code></pre><p>（3）拷贝配置好的zookeeper到其他机器上</p><p>scp -r zookeeper-3.4.14&#x2F; <a href="mailto:&#114;&#x6f;&#x6f;&#x74;&#64;&#x68;&#x61;&#x64;&#x6f;&#111;&#112;&#x31;&#x30;&#51;&#46;&#x61;&#x74;&#103;&#117;&#x69;&#x67;&#117;&#x2e;&#x63;&#111;&#109;&#x3a;&#47;&#x6f;&#x70;&#x74;&#x2f;&#97;&#x70;&#112;&#47;">root@hadoop103.atguigu.com:/opt/app/</a></p><p>scp -r zookeeper-3.4.14&#x2F; <a href="mailto:&#x72;&#x6f;&#111;&#116;&#64;&#x68;&#x61;&#100;&#111;&#x6f;&#112;&#x31;&#x30;&#x34;&#46;&#97;&#116;&#103;&#117;&#x69;&#103;&#x75;&#46;&#99;&#x6f;&#109;&#58;&#x2f;&#x6f;&#112;&#116;&#x2f;&#97;&#112;&#x70;&#x2f;">root@hadoop104.atguigu.com:/opt/app/</a></p><pre><code>并分别修改myid文件中内容为3、4</code></pre><p>（4）分别启动zookeeper</p><p>[root@hadoop102 zookeeper-3.4.14]# bin&#x2F;zkServer.sh start</p><p>[root@hadoop103 zookeeper-3.4.14]# bin&#x2F;zkServer.sh start</p><p>[root@hadoop104 zookeeper-3.4.14]# bin&#x2F;zkServer.sh start</p><p>（5）查看状态</p><p>[root@hadoop102 zookeeper-3.4.14]# bin&#x2F;zkServer.sh status</p><p>JMX enabled by default</p><p>Using config: &#x2F;opt&#x2F;module&#x2F;zookeeper-3.4.14&#x2F;bin&#x2F;..&#x2F;conf&#x2F;zoo.cfg</p><p>Mode: follower</p><p>[root@hadoop103 zookeeper-3.4.14]# bin&#x2F;zkServer.sh status</p><p>JMX enabled by default</p><p>Using config: &#x2F;opt&#x2F;module&#x2F;zookeeper-3.4.14&#x2F;bin&#x2F;..&#x2F;conf&#x2F;zoo.cfg</p><p>Mode: leader</p><p>[root@hadoop104 zookeeper-3.4.5]# bin&#x2F;zkServer.sh status</p><p>JMX enabled by default</p><p>Using config: &#x2F;opt&#x2F;module&#x2F;zookeeper-3.4.14&#x2F;bin&#x2F;..&#x2F;conf&#x2F;zoo.cfg</p><p>Mode: follower</p><h3 id="配置-HDFS-HA集群"><a href="#配置-HDFS-HA集群" class="headerlink" title="配置****HDFS-HA集群"></a><strong>配置****HDFS-HA集群</strong></h3><ol><li><p>   官方地址：<a href="http://hadoop.apache.org/"><u>http://hadoop.apache.org/</u></a></p></li><li><p>   在opt目录下创建一个ha文件夹</p></li></ol><p>mkdir ha</p><ol start="3"><li>   将&#x2F;opt&#x2F;app&#x2F;下的 hadoop-3.1.3拷贝到&#x2F;opt&#x2F;ha目录下</li></ol><p>cp -r hadoop-3.1.3&#x2F; &#x2F;opt&#x2F;ha&#x2F;</p><ol start="4"><li>   配置hadoop-env.sh</li></ol><table><thead><tr><th>export JAVA_HOME&#x3D;&#x2F;opt&#x2F;module&#x2F;jdk1.8.0_144</th></tr></thead></table><ol start="5"><li>   配置core-site.xml</li></ol><table><thead><tr><th><configuration><br/>  <property><br/>    <name>fs.defaultFS</name><br/>    <value>hdfs:&#x2F;&#x2F;mycluster</value><br/>  </property><br/>  <property><br/>    <name>hadoop.data.dir</name><br/>    <value>&#x2F;opt&#x2F;module&#x2F;hadoop-3.1.3&#x2F;data</value><br/>  </property><br/></configuration></th></tr></thead></table><ol start="6"><li>   配置hdfs-site.xml</li></ol><table><thead><tr><th><configuration><br/>  <property><br/>    <name>dfs.namenode.name.dir</name><br/>    <value>file:&#x2F;&#x2F;${hadoop.data.dir}&#x2F;name</value><br/>  </property><br/>  <property><br/>    <name>dfs.datanode.data.dir</name><br/>    <value>file:&#x2F;&#x2F;${hadoop.data.dir}&#x2F;data</value><br/>  </property><br/>  <property><br/>    <name>dfs.nameservices</name><br/>    <value>mycluster</value><br/>  </property><br/>  <property><br/>    <name>dfs.ha.namenodes.mycluster</name><br/>    <value>nn1,nn2, nn3</value><br/>  </property><br/>  <property><br/>    <name>dfs.namenode.rpc-address.mycluster.nn1</name><br/>    <value>hadoop102:8020</value><br/>  </property><br/>  <property><br/>    <name>dfs.namenode.rpc-address.mycluster.nn2</name><br/>    <value>hadoop103:8020</value><br/>  </property><br/>  <property><br/>    <name>dfs.namenode.rpc-address.mycluster.nn3</name><br/>    <value>hadoop104:8020</value><br/>  </property><br/>  <property><br/>    <name>dfs.namenode.http-address.mycluster.nn1</name><br/>    <value>hadoop102:9870</value><br/>  </property><br/>  <property><br/>    <name>dfs.namenode.http-address.mycluster.nn2</name><br/>    <value>hadoop103:9870</value><br/>  </property><br/>  <property><br/>    <name>dfs.namenode.http-address.mycluster.nn3</name><br/>    <value>hadoop104:9870</value><br/>  </property><br/>  <property><br/>    <name>dfs.namenode.shared.edits.dir</name><br/>    <value>qjournal:&#x2F;&#x2F;hadoop102:8485;hadoop103:8485;hadoop104:8485&#x2F;mycluster</value><br/>  </property><br/>  <property><br/>    <name>dfs.client.failover.proxy.provider.mycluster</name><br/>    <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value><br/>  </property><br/>  <property><br/>    <name>dfs.ha.fencing.methods</name><br/>    <value>sshfence</value><br/>  </property><br/>  <property><br/>    <name>dfs.ha.fencing.ssh.private-key-files</name><br/>    <value>&#x2F;home&#x2F;atguigu&#x2F;.ssh&#x2F;id_ecdsa</value><br/>  </property><br/>  <property><br/>    <name>dfs.journalnode.edits.dir</name><br/>    <value>${hadoop.data.dir}&#x2F;jn</value><br/>  </property><br/></configuration></th></tr></thead></table><ol start="7"><li>   拷贝配置好的hadoop环境到其他节点</li></ol><h3 id="启动-HDFS-HA集群"><a href="#启动-HDFS-HA集群" class="headerlink" title="启动****HDFS-HA集群"></a><strong>启动****HDFS-HA集群</strong></h3><ol><li><p>   在各个JournalNode节点上，输入以下命令启动journalnode服务</p><p>  hdfs –daemon start journalnode</p></li><li><p>   在[nn1]上，对其进行格式化，并启动</p><p>  bin&#x2F;hdfs namenode -format</p><p>  hdfs –daemon start namenode</p></li><li><p>   在[nn2]和[nn3]上，同步nn1的元数据信息</p><p>  hdfs namenode -bootstrapStandby</p></li><li><p>   启动[nn2]和[nn3]</p><p>  hdfs –daemon start namenode</p></li><li><p>   查看web页面显示，</p></li></ol><p> </p><p>hadoop102(standby)</p><p> </p><p>hadoop103(standby)</p><p> </p><p> hadoop104(standby)</p><ol start="6"><li><p>   在所有节点上上，启动datanode</p><p>   hdfs –daemon start datanode</p></li><li><p>   将[nn1]切换为Active</p><p>  bin&#x2F;hdfs haadmin -transitionToActive nn1</p></li></ol><p>5. 查看是否Active</p><pre><code>hdfs haadmin -getServiceState nn1</code></pre><h3 id="配置HDFS-HA-自动故障转移"><a href="#配置HDFS-HA-自动故障转移" class="headerlink" title="*配置HDFS-HA*自动故障转移"></a>*<em>配置HDFS-<strong><strong>H</strong></strong>A</em>*<strong>自动故障转移</strong></h3><ol><li><p>   具体配置</p><p>  （1）在hdfs-site.xml中增加</p></li></ol><property><pre><code>&lt;name&gt;dfs.ha.automatic-failover.enabled&lt;/name&gt;&lt;value&gt;true&lt;/value&gt;</code></pre></property><pre><code>（2）在core-site.xml文件中增加</code></pre><property><pre><code>&lt;name&gt;ha.zookeeper.quorum&lt;/name&gt;&lt;value&gt;hadoop102:2181,hadoop103:2181,hadoop104:2181&lt;/value&gt;</code></pre></property><ol start="2"><li><p>   启动</p><p>  （1）关闭所有HDFS服务：</p></li></ol><p>stop-dfs.sh</p><pre><code>（2）启动Zookeeper集群：</code></pre><p>zkServer.sh start</p><pre><code>（3）初始化HA在Zookeeper中状态：</code></pre><p>hdfs zkfc -formatZK</p><pre><code>（4）启动HDFS服务：</code></pre><p>start-dfs.sh</p><ol start="3"><li><p>   验证</p><p>  （1）将Active NameNode进程kill</p></li></ol><p>kill -9 namenode的进程id</p><pre><code>（2）将Active NameNode机器断开网络</code></pre><p>service network stop</p><h2 id="YARN-HA配置"><a href="#YARN-HA配置" class="headerlink" title="YARN**-**HA配置"></a><strong>YARN</strong>**-**<strong>HA配置</strong></h2><h3 id="YARN-HA-工作机制"><a href="#YARN-HA-工作机制" class="headerlink" title="YARN-HA****工作机制"></a><strong>YARN</strong><strong>-HA****工作机制</strong></h3><ol><li>   官方文档：</li></ol><p><a href="http://hadoop.apache.org/docs/r2.7.2/hadoop-yarn/hadoop-yarn-site/ResourceManagerHA.html"><u>http://hadoop.apache.org/docs/r3.1.3/hadoop-yarn/hadoop-yarn-site/ResourceManagerHA.html</u></a></p><ol start="2"><li>   YARN-HA工作机制，如图3-23所示</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005229680-60511967.png" alt="1605275749618-3c1b52c5-d100-4e14-9df2-923f16d1a2c9.png"> </p><p>图3-22  YARN-HA工作机制</p><h3 id="配置YARN-HA-集群"><a href="#配置YARN-HA-集群" class="headerlink" title="配置YARN-HA****集群"></a><strong>配置YARN</strong><strong>-HA****集群</strong></h3><ol><li>   环境准备</li></ol><p>（1）修改IP</p><p>（2）修改主机名及主机名和IP地址的映射</p><p>（3）关闭防火墙</p><p>（4）ssh免密登录</p><p>（5）安装JDK，配置环境变量等</p><pre><code>（6）配置Zookeeper集群</code></pre><ol start="2"><li>   规划集群</li></ol><table><thead><tr><th>hadoop102</th><th>hadoop103</th><th>hadoop104</th></tr></thead><tbody><tr><td>NameNode</td><td>NameNode</td><td></td></tr><tr><td>JournalNode</td><td>JournalNode</td><td>JournalNode</td></tr><tr><td>DataNode</td><td>DataNode</td><td>DataNode</td></tr><tr><td>ZK</td><td>ZK</td><td>ZK</td></tr><tr><td>ResourceManager</td><td>ResourceManager</td><td></td></tr><tr><td>NodeManager</td><td>NodeManager</td><td>NodeManager</td></tr></tbody></table><ol start="3"><li>   具体配置</li></ol><p>（1）yarn-site.xml</p><table><thead><tr><th><configuration><br/> <br/>    <property><br/>        <name>yarn.nodemanager.aux-services</name><br/>        <value>mapreduce_shuffle</value><br/>    </property><br/> <br/>    <!--启用resourcemanager ha--><br/>    <property><br/>        <name>yarn.resourcemanager.ha.enabled</name><br/>        <value>true</value><br/>    </property><br/> <br/>    <!--声明两台resourcemanager的地址--><br/>    <property><br/>        <name>yarn.resourcemanager.cluster-id</name><br/>        <value>cluster-yarn1</value><br/>    </property><br/> <br/>    <property><br/>        <name>yarn.resourcemanager.ha.rm-ids</name><br/>        <value>rm1,rm2</value><br/>    </property><br/> <br/>    <property><br/>        <name>yarn.resourcemanager.hostname.rm1</name><br/>        <value>hadoop102</value><br/>    </property><br/> <br/>    <property><br/>        <name>yarn.resourcemanager.hostname.rm2</name><br/>        <value>hadoop103</value><br/>    </property><br/> <br/>    <!--指定zookeeper集群的地址--> <br/>    <property><br/>        <name>yarn.resourcemanager.zk-address</name><br/>        <value>hadoop102:2181,hadoop103:2181,hadoop104:2181</value><br/>    </property><br/> <br/>    <!--启用自动恢复--> <br/>    <property><br/>        <name>yarn.resourcemanager.recovery.enabled</name><br/>        <value>true</value><br/>    </property><br/> <br/>    <!--指定resourcemanager的状态信息存储在zookeeper集群--> <br/>    <property><br/>        <name>yarn.resourcemanager.store.class</name>     <value>org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore</value><br/></property><br/> <br/></configuration></th></tr></thead></table><pre><code>（2）同步更新其他节点的配置信息</code></pre><ol start="4"><li>   启动hdfs</li></ol><p> </p><p>（1）在各个JournalNode节点上，输入以下命令启动journalnode服务：</p><p>hdfs –daemon start journalnode</p><p>（2）在[nn1]上，对其进行格式化，并启动：</p><p>hdfs namenode -format</p><p>hdfs –daemon start namenode</p><p>（3）在[nn2]上，同步nn1的元数据信息：</p><p>hdfs namenode -bootstrapStandby</p><p>（4）启动[nn2]：</p><p>hdfs –daemon start namenode</p><p>（5）启动所有DataNode</p><p>hdfs –-daemon start datanode</p><p>（6）将[nn1]切换为Active</p><p>hdfs haadmin -transitionToActive nn1</p><ol start="5"><li>   启动YARN</li></ol><p><font style="color:#2F2F2F;">（1）在</font><font style="color:#2F2F2F;">hadoop1</font><font style="color:#2F2F2F;">02中执行：</font></p><p>start-yarn.sh</p><p><font style="color:#2F2F2F;">（2）在</font><font style="color:#2F2F2F;">hadoop103</font><font style="color:#2F2F2F;">中执行：</font></p><p>yarn –daemon start resourcemanager</p><p>（3）查看服务状态，如图3-24所示</p><p>yarn rmadmin -getServiceState rm1</p><h2 id="HDFS-Federation架构设计"><a href="#HDFS-Federation架构设计" class="headerlink" title="HDFS** Federation架构设计**"></a><strong>HDFS</strong>** Federation架构设计**</h2><ol><li>   NameNode架构的局限性</li></ol><p>（1）Namespace（命名空间）的限制</p><p>由于NameNode在内存中存储所有的元数据（metadata），因此单个NameNode所能存储的对象（文件+块）数目受到NameNode所在JVM的heap size的限制。50G的heap能够存储20亿（200million）个对象，这20亿个对象支持4000个DataNode，12PB的存储（假设文件平均大小为40MB）。随着数据的飞速增长，存储的需求也随之增长。单个DataNode从4T增长到36T，集群的尺寸增长到8000个DataNode。存储的需求从12PB增长到大于100PB。</p><p>（2）隔离问题</p><p>由于HDFS仅有一个NameNode，无法隔离各个程序，因此HDFS上的一个实验程序就很有可能影响整个HDFS上运行的程序。</p><pre><code>（3）性能的瓶颈由于是单个NameNode的HDFS架构，因此整个HDFS文件系统的吞吐量受限于单个NameNode的吞吐量。</code></pre><ol start="2"><li>   HDFS Federation架构设计，如图3-25所示</li></ol><p>能不能有多个NameNode</p><p>表3-3</p><table><thead><tr><th>NameNode</th><th>NameNode</th><th>NameNode</th></tr></thead><tbody><tr><td>元数据</td><td>元数据</td><td>元数据</td></tr><tr><td>Log</td><td>machine</td><td>电商数据&#x2F;话单数据</td></tr></tbody></table><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005230039-187970999.png" alt="1605276051626-c21c6e9d-a335-401e-a026-ac76d5ed486d.png"></p><ol start="3"><li>   HDFS Federation应用思考</li></ol><p>不同应用可以使用不同NameNode进行数据管理</p><pre><code>图片业务、爬虫业务、日志审计业务</code></pre><p>Hadoop生态系统中，不同的框架使用不同的NameNode进行管理NameSpace。（隔离性）</p>]]>
    </content>
    <id>https://www.chucz.asia/2026/04/09/Hadoop--HDFS-HA%E9%AB%98%E5%8F%AF%E7%94%A8/</id>
    <link href="https://www.chucz.asia/2026/04/09/Hadoop--HDFS-HA%E9%AB%98%E5%8F%AF%E7%94%A8/"/>
    <published>2026-04-09T06:42:38.000Z</published>
    <summary>
      <![CDATA[<p>high avilability</p>
<h2 id="HA-概述"><a href="#HA-概述" class="headerlink"]]>
    </summary>
    <title>Hadoop--HDFS-HA高可用</title>
    <updated>2026-04-09T06:42:38.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>褚成志</name>
    </author>
    <category term="操作系统" scheme="https://www.chucz.asia/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    <category term="Linux" scheme="https://www.chucz.asia/tags/Linux/"/>
    <category term="CPU" scheme="https://www.chucz.asia/tags/CPU/"/>
    <category term="性能分析" scheme="https://www.chucz.asia/tags/%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90/"/>
    <content>
      <![CDATA[<p><img src="https://cdn.chucz.asia/blog/3703820-20251012213855742-297874762.jpg" alt="画板"></p><p><font style="color:#353535;">Linux 支持远大于 CPU 数量的任务同时运行。系统在很短的时间内，将 CPU 轮流分配给它们，造成多任务同时运行的错觉。</font><font style="color:#F5222D;">过多的上下文切换，会把 CPU 时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上，从而缩短进程真正运行的时间，导致系统的整体性能大幅下降。</font></p><p><font style="color:#353535;">每个进程运行前，系统事先帮它设置好 </font><strong>CPU 寄存器和程序计数器</strong><font style="color:#353535;">（Program Counter，PC）。</font></p><ul><li>CPU 上下文：<ul><li><font style="color:#353535;">CPU 寄存器，是 CPU 内置的容量小、但速度极快的内存。</font></li><li><font style="color:#353535;">程序计数器，则是用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置。</font></li></ul></li><li><font style="color:#353535;">CPU 上下文切换</font><ul><li><font style="color:#353535;">把前一个任务的 CPU 上下文（也就是 CPU 寄存器和程序计数器）保存起来，然后</font><font style="color:#F5222D;">加载新任务的上下文到这些寄存器和程序计数器，最后再跳转到程序计数器所指的新位置</font><font style="color:#353535;">，运行新任务。</font></li><li><font style="color:#353535;">保存下来的上下文，会存储在系统内核中，并在任务重新调度执行时再次加载进来</font></li></ul></li><li><font style="color:#353535;">根据任务的不同， CPU 上下文切换场景</font><ul><li><font style="color:#353535;">进程上下文切换</font></li><li><font style="color:#353535;">线程上下文切换</font></li><li><font style="color:#353535;">中断上下文切换</font></li></ul></li></ul><p><font style="color:#353535;"></font></p><h2 id="系统调用–特权模式切换–同进程CPU上下文切换"><a href="#系统调用–特权模式切换–同进程CPU上下文切换" class="headerlink" title="系统调用–特权模式切换–同进程CPU上下文切换"></a>系统调用–特权模式切换–同进程CPU上下文切换</h2><p><strong><font style="color:#F5222D;">系统调用过程通常称为特权模式切换，而不是上下文切换</font></strong><font style="color:#F5222D;">。</font><font style="color:#F5222D;">但实际上，系统调用过程中，CPU 的上下文切换还是无法避免的。</font></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012213856119-738290831.png" alt="1616894493582-ccd6d11c-d1bb-45ff-8a63-082238ea9d50.png"></p><ul><li>进程的运行空间：<strong><font style="color:#F5222D;">进程在用户空间运行时，被称为进程的用户态，而陷入内核空间的时候，被称为进程的内核态。</font></strong><ul><li>内核空间（Ring 0）具有最高权限，<strong><font style="color:#F5222D;">可以直接访问所有资源</font></strong>；</li><li>用户空间（Ring 3）只能访问受限资源，<strong><font style="color:#F5222D;">不能直接访问内存等硬件设备，必须通过</font><strong><strong><u><font style="color:#F5222D;">系统调用</font></u></strong></strong><font style="color:#F5222D;">陷入到内核中</font></strong>，才能访问这些特权资源。</li></ul></li><li><font style="color:#353535;">系统调用 eg：当我们查看文件内容时，就需要多次系统调用来完成：首先调用 open() 打开文件，然后调用 read() 读取文件内容，并调用 write() 将内容写到标准输出，最后再调用 close() 关闭文件。</font></li><li><font style="color:#FA8919;">一次系统调用的过程，其实是发生了两次 CPU 上下文切换。</font><ul><li>CPU 寄存器里原来用户态的指令位置，需要先保存起来。接着，为了执行内核态代码，CPU 寄存器需要更新为内核态指令的新位置。最后才是跳转到内核态运行内核任务。</li><li>而系统调用结束后，CPU 寄存器需要<strong>恢复</strong>原来保存的用户态，然后再切换到用户空间，继续运行进程。</li></ul></li></ul><h2 id="进程上下文切换"><a href="#进程上下文切换" class="headerlink" title="进程上下文切换"></a>进程上下文切换</h2><h3 id="与系统调用的区别"><a href="#与系统调用的区别" class="headerlink" title="与系统调用的区别"></a>与系统调用的区别</h3><ul><li>进程上下文切换，是指从一个进程切换到另一个进程运行。<strong><font style="color:#F5222D;">进程的切换只能发生在内核态</font></strong><font style="color:#353535;">。所以，进程的上下文不仅包括了</font><strong><font style="color:#F5222D;">虚拟内存、栈、全局变量</font></strong><font style="color:#353535;">等</font><strong><font style="color:#F5222D;">用户空间的资源</font></strong><font style="color:#353535;">，还包括了</font><strong><font style="color:#F5222D;">内核堆栈、寄存器</font></strong><font style="color:#353535;">等</font><strong><font style="color:#F5222D;">内核空间的状态</font></strong><font style="color:#353535;">。</font><ul><li><font style="color:#353535;">比系统调用时多了一步：在</font><font style="color:#F5222D;">保存当前进程的内核状态和 CPU 寄存器之前，需要先把该进程的虚拟内存、栈等保存下来</font><font style="color:#353535;">；而</font><font style="color:#F5222D;">加载了下一进程的内核态后，还需要刷新进程的虚拟内存和用户栈。</font></li></ul></li><li>而系统调用过程中一直是同一个进程在运行。<strong><font style="color:#353535;">不会涉及到</font><strong><strong><font style="color:#F5222D;">虚拟内存</font></strong></strong><font style="color:#353535;">等进程用户态的资源</font></strong><font style="color:#353535;">，也不会切换进程</font></li></ul><h3 id="上下文切换时机–进程调度"><a href="#上下文切换时机–进程调度" class="headerlink" title="上下文切换时机–进程调度"></a>上下文切换时机–进程调度</h3><p><font style="color:#353535;">进程调度的时候，才需要切换上下文。Linux 为每个 CPU 都维护了一个就绪队列，将活跃进程（即正在运行和正在等待 CPU 的进程）按照优先级和等待 CPU 的时间排序，</font><font style="color:#353535;">优先级最高和等待 CPU 时间最长的进程来运行。</font></p><ul><li>时间片耗尽</li><li>**<font style="color:#F5222D;">系统资源不足，</font>**挂起，要等到满足才可以运行</li><li>自主挂起，sleep</li><li><font style="color:#F5222D;">优先级更高的到来时</font></li><li><strong><font style="color:#F5222D;">硬件中断，会被中断挂起，之后执行内核中的中断服务程序</font></strong></li></ul><h2 id="线程上下文切换"><a href="#线程上下文切换" class="headerlink" title="线程上下文切换"></a>线程上下文切换</h2><p><strong>线程是调度的基本单位，而进程则是资源拥有的基本单位</strong><font style="color:#353535;">。</font></p><p><font style="color:#353535;">内核中的任务调度，实际上的调度对象是线程；而进程只是给线程提供了虚拟内存、全局变量等资源。</font></p><ul><li><font style="color:#353535;">前后两个线程属于不同进程。此时，因为资源不共享，所以切换过程就跟进程上下文切换是一样</font></li><li><font style="color:#353535;">前后两个线程属于同一个进程。此时，因为虚拟内存是共享的，所以在切换时，虚拟内存这些资源就保持不动，</font><strong><font style="color:#F5222D;">只需要切换线程的私有数据、寄存器</font></strong><font style="color:#353535;">等不共享的数据。</font></li></ul><h2 id="中断上下文切换–短小快"><a href="#中断上下文切换–短小快" class="headerlink" title="中断上下文切换–短小快"></a>中断上下文切换–短小快</h2><p><strong>中断处理比进程拥有更高的优先级。</strong><font style="color:#353535;">为了快速响应硬件的事件，</font><strong>中断处理会打断进程的正常调度和执行</strong><font style="color:#353535;">，转而调用中断处理程序，响应设备事件。</font></p><p><font style="color:#353535;"></font></p><p><font style="color:#353535;">中断上下文切换并不涉及到进程的用户态。所以，即便中断过程打断了一个正处在用户态的进程，也不需要保存和恢复这个进程的虚拟内存、全局变量等用户态资源。</font></p><p><font style="color:#353535;"></font></p><p><font style="color:#353535;">中断上下文，其实只包括</font><strong><font style="color:#F5222D;">内核态中断服务程序</font></strong><font style="color:#353535;">执行所必需的状态，包括 </font><strong><font style="color:#F5222D;">CPU 寄存器、内核堆栈、硬件中断参数</font></strong><font style="color:#353535;">等。</font></p><h2 id="查看系统的上下文切换情况-vmstat"><a href="#查看系统的上下文切换情况-vmstat" class="headerlink" title="查看系统的上下文切换情况-vmstat"></a>查看系统的上下文切换情况-<font style="color:#353535;">vmstat</font></h2><p><font style="color:#353535;">过多的上下文切换，会把 CPU 时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上</font></p><p><font style="color:#353535;"></font></p><p><font style="color:#353535;">vmstat 是一个常用的系统性能分析工具，主要用来分析系统的内存使用情况，也常用来分析 CPU 上下文切换和中断的次数。</font><strong><font style="color:#F5222D;">系统总体的上下文切换情况:</font></strong></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012213856295-1243991818.png" alt="1616896915546-00533e9e-bb6a-484f-80a4-e0894ca8c212.png"></p><p><font style="color:#353535;">pidstat  </font>-w 参数表示输出进程切换指标，而 -u 参数则表示输出 CPU 使用指标</p><p><font style="color:#353535;">pidstat 默认显示进程的指标数据，加上 -t 参数后，才会输出线程的指标。也就是-wt</font></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012213856465-2145497593.png" alt="1616897089378-d8a3059a-cc83-43a0-99c7-4b59ad6c1378.png"></p><ul><li><font style="color:#353535;">cswch ，表示每秒</font><strong><font style="color:#F5222D;">自愿上下文切换</font></strong><font style="color:#353535;">（voluntary context switches）的次数：</font><strong>进程无法获取所需资源，导致的上下文切换</strong><font style="color:#353535;">。比如说， I&#x2F;O、内存等系统资源不足时，就会发生自愿上下文切换。</font></li><li><font style="color:#353535;">nvcswch ，表示每秒</font><strong><font style="color:#F5222D;">非自愿上下文切换</font></strong><font style="color:#353535;">（non voluntary context switches）的次数：</font><strong>进程由于时间片已到等原因，被系统强制调度，进而发生的上下文切换</strong><font style="color:#353535;">。比如说，大量进程都在争抢 CPU 时，就容易发生非自愿上下文切换。</font></li></ul><h2 id="实验："><a href="#实验：" class="headerlink" title="实验："></a>实验：</h2><p><font style="color:#353535;">sysbench 来模拟系统多线程调度切换的瓶颈情况，</font><font style="color:#353535;">是一个多线程的基准测试工具，一般用来评估不同系统参数下的数据库负载情况。当然，在这次案例中，我们只把它当成一个异常进程来看，作用是模拟上下文切换过多的问题。</font></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># 以 <span class="number">10</span> 个线程运行 <span class="number">5</span> 分钟的基准测试，模拟多线程切换的问题</span><br><span class="line">$ sysbench --threads=<span class="number">10</span> --max-time=<span class="number">300</span> threads run</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">vmstat <span class="number">1</span></span><br><span class="line">procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----</span><br><span class="line"> r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st</span><br><span class="line"> <span class="number">6</span>  <span class="number">0</span>      <span class="number">0</span> <span class="number">6487428</span> <span class="number">118240</span> <span class="number">1292772</span>    <span class="number">0</span>    <span class="number">0</span>     <span class="number">0</span>     <span class="number">0</span> <span class="number">9019</span> <span class="number">1398830</span> <span class="number">16</span> <span class="number">84</span>  <span class="number">0</span>  <span class="number">0</span>  <span class="number">0</span></span><br><span class="line"> <span class="number">8</span>  <span class="number">0</span>      <span class="number">0</span> <span class="number">6487428</span> <span class="number">118240</span> <span class="number">1292772</span>    <span class="number">0</span>    <span class="number">0</span>     <span class="number">0</span>     <span class="number">0</span> <span class="number">10191</span> <span class="number">1392312</span> <span class="number">16</span> <span class="number">84</span>  <span class="number">0</span>  <span class="number">0</span>  <span class="number">0</span></span><br></pre></td></tr></table></figure><ul><li>r 列：就绪队列的长度已经到了 8，远远超过了系统 CPU 的个数 2，所以肯定会有大量的 CPU 竞争。</li><li>us（user）和 sy（system）列：这两列的 CPU 使用率加起来上升到了 100%，其中系统 CPU 使用率，也就是 sy 列高达 84%，说明**<font style="color:#F5222D;"> CPU 主要是被内核占用了。</font>**</li><li>in 列：中断次数也上升到了 1 万左右，说**<font style="color:#F5222D;">明中断处理也是个潜在的问题。</font>**</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"># 每隔 <span class="number">1</span> 秒输出 <span class="number">1</span> 组数据（需要 Ctrl+C 才结束）</span><br><span class="line"># -w 参数表示输出进程切换指标，而 -u 参数则表示输出 CPU 使用指标</span><br><span class="line">$ pidstat -w -u <span class="number">1</span></span><br><span class="line">08:<span class="number">06</span>:<span class="number">33</span>      UID       PID    %usr %system  %guest   %wait    %CPU   CPU  Command</span><br><span class="line">08:<span class="number">06</span>:<span class="number">34</span>        <span class="number">0</span>     <span class="number">10488</span>   <span class="number">30.00</span>  <span class="number">100.00</span>    <span class="number">0.00</span>    <span class="number">0.00</span>  <span class="number">100.00</span>     <span class="number">0</span>  sysbench</span><br><span class="line">08:<span class="number">06</span>:<span class="number">34</span>        <span class="number">0</span>     <span class="number">26326</span>    <span class="number">0.00</span>    <span class="number">1.00</span>    <span class="number">0.00</span>    <span class="number">0.00</span>    <span class="number">1.00</span>     <span class="number">0</span>  kworker/u4:<span class="number">2</span></span><br><span class="line"> </span><br><span class="line">08:<span class="number">06</span>:<span class="number">33</span>      UID       PID   cswch/s nvcswch/s  Command</span><br><span class="line">08:<span class="number">06</span>:<span class="number">34</span>        <span class="number">0</span>         <span class="number">8</span>     <span class="number">11.00</span>      <span class="number">0.00</span>  rcu_sched</span><br><span class="line">08:<span class="number">06</span>:<span class="number">34</span>        <span class="number">0</span>        <span class="number">16</span>      <span class="number">1.00</span>      <span class="number">0.00</span>  ksoftirqd/<span class="number">1</span></span><br><span class="line">08:<span class="number">06</span>:<span class="number">34</span>        <span class="number">0</span>       <span class="number">471</span>      <span class="number">1.00</span>      <span class="number">0.00</span>  hv_balloon</span><br><span class="line">08:<span class="number">06</span>:<span class="number">34</span>        <span class="number">0</span>      <span class="number">1230</span>      <span class="number">1.00</span>      <span class="number">0.00</span>  iscsid</span><br><span class="line">08:<span class="number">06</span>:<span class="number">34</span>        <span class="number">0</span>      <span class="number">4089</span>      <span class="number">1.00</span>      <span class="number">0.00</span>  kworker/<span class="number">1</span>:<span class="number">5</span></span><br><span class="line">08:<span class="number">06</span>:<span class="number">34</span>        <span class="number">0</span>      <span class="number">4333</span>      <span class="number">1.00</span>      <span class="number">0.00</span>  kworker/<span class="number">0</span>:<span class="number">3</span></span><br><span class="line">08:<span class="number">06</span>:<span class="number">34</span>        <span class="number">0</span>     <span class="number">10499</span>      <span class="number">1.00</span>    <span class="number">224.00</span>  pidstat</span><br><span class="line">08:<span class="number">06</span>:<span class="number">34</span>        <span class="number">0</span>     <span class="number">26326</span>    <span class="number">236.00</span>      <span class="number">0.00</span>  kworker/u4:<span class="number">2</span></span><br><span class="line">08:<span class="number">06</span>:<span class="number">34</span>     <span class="number">1000</span>     <span class="number">26784</span>    <span class="number">223.00</span>      <span class="number">0.00</span>  sshd</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"># 每隔 <span class="number">1</span> 秒输出一组数据（需要 Ctrl+C 才结束）</span><br><span class="line"># -wt 参数表示输出线程的上下文切换指标</span><br><span class="line">$ pidstat -wt <span class="number">1</span></span><br><span class="line">08:<span class="number">14</span>:<span class="number">05</span>      UID      TGID       TID   cswch/s nvcswch/s  Command</span><br><span class="line">...</span><br><span class="line">08:<span class="number">14</span>:<span class="number">05</span>        <span class="number">0</span>     <span class="number">10551</span>         -      <span class="number">6.00</span>      <span class="number">0.00</span>  sysbench</span><br><span class="line">08:<span class="number">14</span>:<span class="number">05</span>        <span class="number">0</span>         -     <span class="number">10551</span>      <span class="number">6.00</span>      <span class="number">0.00</span>  |__sysbench</span><br><span class="line">08:<span class="number">14</span>:<span class="number">05</span>        <span class="number">0</span>         -     <span class="number">10552</span>  <span class="number">18911.00</span> <span class="number">103740.00</span>  |__sysbench</span><br><span class="line">08:<span class="number">14</span>:<span class="number">05</span>        <span class="number">0</span>         -     <span class="number">10553</span>  <span class="number">18915.00</span> <span class="number">100955.00</span>  |__sysbench</span><br><span class="line">08:<span class="number">14</span>:<span class="number">05</span>        <span class="number">0</span>         -     <span class="number">10554</span>  <span class="number">18827.00</span> <span class="number">103954.00</span>  |__sysbench</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p><font style="color:#353535;">除了上下文切换频率骤然升高，还有一个指标也有很大的变化，中断次数。</font></p><p><strong><font style="color:#F5222D;"> pidstat 只是一个进程的性能分析工具，并不提供任何关于中断的详细信息</font></strong></p><p><strong><font style="color:#F5222D;">&#x2F;proc 实际上是 Linux 的一个虚拟文件系统，用于内核空间与用户空间之间的通信。&#x2F;proc&#x2F;interrupts 就是这种通信机制的一部分，提供了一个只读的中断使用情况。</font></strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># -d 参数表示高亮显示变化的区域</span><br><span class="line">$ watch -d cat /proc/interrupts</span><br><span class="line">           CPU0       CPU1</span><br><span class="line">...</span><br><span class="line">RES:    <span class="number">2450431</span>    <span class="number">5279697</span>   Rescheduling interrupts</span><br></pre></td></tr></table></figure><p>变化速度最快的是<strong>重调度中断</strong>（RES），这个中断类型表示，唤醒空闲状态的 CPU 来调度新的任务运行。这是多处理器系统（SMP）中，调度器用来分散任务到不同 CPU 的机制，通常也被称为<strong>处理器间中断</strong>（Inter-Processor Interrupts，IPI）。</p><p><font style="color:#353535;">中断升高还是因为过多任务的调度问题，跟前面上下文切换次数的分析结果是一致的。</font></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li><strong><font style="color:#F5222D;">自愿上下文切换变多了，说明进程都在等待资源，有可能发生了 I&#x2F;O 等其他问题；</font></strong></li><li><strong><font style="color:#F5222D;">非自愿上下文切换变多了，说明进程都在被强制调度，也就是都在争抢 CPU，说明 CPU 的确成了瓶颈；</font></strong></li><li><strong><font style="color:#F5222D;">中断次数变多了，说明 CPU 被中断处理程序占用，还需要通过查看 &#x2F;proc&#x2F;interrupts 文件来分析具体的中断类型。</font></strong></li></ul>]]>
    </content>
    <id>https://www.chucz.asia/2026/04/09/CPU%E5%A4%9A%E8%BF%9B%E7%A8%8B%E5%88%87%E6%8D%A2%E5%AF%BC%E8%87%B4%E8%BF%87%E8%BD%BD-CPU%E4%B8%8A%E4%B8%8B%E6%96%87%E5%88%87%E6%8D%A2/</id>
    <link href="https://www.chucz.asia/2026/04/09/CPU%E5%A4%9A%E8%BF%9B%E7%A8%8B%E5%88%87%E6%8D%A2%E5%AF%BC%E8%87%B4%E8%BF%87%E8%BD%BD-CPU%E4%B8%8A%E4%B8%8B%E6%96%87%E5%88%87%E6%8D%A2/"/>
    <published>2026-04-09T06:42:38.000Z</published>
    <summary>
      <![CDATA[<p><img src="https://cdn.chucz.asia/blog/3703820-20251012213855742-297874762.jpg" alt="画板"></p>
<p><font style="color:#353535;">Linux 支持远大于]]>
    </summary>
    <title>CPU多进程切换导致过载-CPU上下文切换</title>
    <updated>2026-04-09T06:42:38.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>褚成志</name>
    </author>
    <category term="云原生" scheme="https://www.chucz.asia/categories/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    <category term="云计算" scheme="https://www.chucz.asia/tags/%E4%BA%91%E8%AE%A1%E7%AE%97/"/>
    <category term="阿里云" scheme="https://www.chucz.asia/tags/%E9%98%BF%E9%87%8C%E4%BA%91/"/>
    <category term="监控" scheme="https://www.chucz.asia/tags/%E7%9B%91%E6%8E%A7/"/>
    <content>
      <![CDATA[<h2 id="视图预览"><a href="#视图预览" class="headerlink" title="视图预览"></a>视图预览</h2><p>阿里云 ECS 指标展示，包括CPU 负载，内存使用，磁盘读写，网络流量等</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155904075-1462609471.png" alt="1640054639961-52bdcf93-d6a0-4d9a-9e9e-8d84488f806a.png"></p><h2 id="版本支持"><a href="#版本支持" class="headerlink" title="版本支持"></a>版本支持</h2><p><font style="color:#595959;">操作系统支持：Linux &#x2F; Windows</font></p><h2 id="前置条件"><a href="#前置条件" class="headerlink" title="前置条件"></a>前置条件</h2><ul><li>服务器 &lt;<a href="https://www.yuque.com/dataflux/datakit/datakit-install">安装 Datakit</a>&gt;</li><li>服务器 &lt;<a href="https://www.yuque.com/dataflux/func/quick-start">安装 Func 携带版</a>&gt;</li><li>阿里云 ECS 安装云监控</li><li>阿里云 RAM 访问控制账号授权</li></ul><h3 id="云监控安装"><a href="#云监控安装" class="headerlink" title="云监控安装"></a>云监控安装</h3><ol><li>登录阿里云监控控制台 <a href="https://cloudmonitor.console.aliyun.com/">https://cloudmonitor.console.aliyun.com/</a></li><li>主机监控 - 点击安装 (建议勾选新建 ECS 自动安装云监控)</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155904430-1325817674.png" alt="1639453479200-8e606097-cc10-4db0-8344-7ff364782d24.png"></p><h3 id="RAM-访问控制"><a href="#RAM-访问控制" class="headerlink" title="RAM 访问控制"></a>RAM 访问控制</h3><ol><li>登录 RAM 控制台  <a href="https://ram.console.aliyun.com/users">https://ram.console.aliyun.com/users</a></li><li>新建用户：人员管理 - 用户 - 创建用户</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155904939-1616146424.png" alt="1627893591261-ed0721f4-85d0-44a2-9b66-fe2bcb3bf41c.png"></p><ol start="3"><li>保存或下载 <strong>AccessKey</strong> <strong>ID</strong> 和 <strong>AccessKey Secret</strong> 的 CSV 文件 (配置文件会用到)</li><li>用户授权 (只读访问所有阿里云资源的权限)</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155905170-1060037641.png" alt="1649828381331-297ae158-1055-4865-b195-b99aa9e2a4f4.png"></p><h2 id="安装配置"><a href="#安装配置" class="headerlink" title="安装配置"></a>安装配置</h2><p><font style="color:#595959;">说明：</font></p><ul><li><font style="color:#595959;">示例 Linux 版本为：CentOS Linux release 7.8.2003 (Core)</font></li><li><font style="color:#595959;">通过一台服务器采集所有阿里云 ECS 数据</font></li></ul><h3 id="部署实施"><a href="#部署实施" class="headerlink" title="部署实施"></a>部署实施</h3><h4 id="脚本市场"><a href="#脚本市场" class="headerlink" title="脚本市场"></a>脚本市场</h4><ol><li>登录 Func，地址 <a href="http://ip:8088/">http://ip:8088</a></li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155905341-1859709078.png" alt="1639115383741-ad518ea3-5206-4e62-a6a2-d14fdf1b8f4e.png"></p><ol start="2"><li>开启脚本市场，管理 - 实验性功能 - 开启脚本市场模块</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155905525-1387300889.png" alt="1639115461724-ce238618-34e5-453a-bed4-18504ad89ecf.png"></p><ol start="3"><li>**依次添加 **三个脚本集<ol><li>观测云集成 (核心包)</li><li>观测云集成 (阿里云-云监控)</li><li>观测云集成 (阿里云-ECS)</li></ol></li></ol><p><em>注：在安装「核心包」后，系统会提示安装第三方依赖包，按照正常步骤点击安装即可</em></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155905712-1173889779.png" alt="1649826257977-79f30b06-c8f4-45c9-939d-b5360741ce0e.png"></p><ol start="4"><li>脚本安装完成后，可以在脚本库中看到所有脚本集</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155905908-2022288415.png" alt="1649815657653-f0d25654-06c5-45da-a67e-5d3aae2934f0.png"></p><h4 id="添加脚本"><a href="#添加脚本" class="headerlink" title="添加脚本"></a>添加脚本</h4><ol><li>开发 - 脚本库 - 添加脚本集</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155906071-438016458.png" alt="1649815907360-7693c6b3-4ded-4f6b-ba2b-224a37cfac54.png"></p><ol start="2"><li>点击该脚本集 - 添加脚本</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155906426-969271673.png" alt="1649816044628-e51205c0-75b5-491c-8a06-ab41e448278e.png"></p><ol start="3"><li>创建 ID 为 main 的脚本</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155906872-493232873.png" alt="1649816136118-fc60f228-277f-47a6-bf2e-3bd168b7e0f0.png"></p><ol start="4"><li>添加代码 (需要修改账号配置 <strong>AccessKey ID&#x2F;AccessKey Secret&#x2F;Account Name</strong>)</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">from guance_integration__runner import Runner        # 引入启动器</span><br><span class="line">import guance_aliyun_ecs__main as aliyun_ecs         # 引入阿里云ECS采集器</span><br><span class="line">import guance_aliyun_monitor__main as aliyun_monitor # 引入阿里云云监控采集器</span><br><span class="line"></span><br><span class="line"># 账号配置</span><br><span class="line">account = &#123;</span><br><span class="line">    &#x27;ak_id&#x27;     : &#x27;AccessKey ID&#x27;,</span><br><span class="line">    &#x27;ak_secret&#x27; : &#x27;AccessKey Secret&#x27;,</span><br><span class="line">    &#x27;extra_tags&#x27;: &#123;</span><br><span class="line">        &#x27;account_name&#x27;: &#x27;Account Name&#x27;,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"># 由于采集数据较多，此处需要为函数指定更大的超时时间（单位秒）</span><br><span class="line">@DFF.API(&#x27;执行云资产同步&#x27;, timeout=300)</span><br><span class="line">def run():</span><br><span class="line">    # 采集器配置</span><br><span class="line">    common_aliyun_configs = &#123;</span><br><span class="line">        &#x27;regions&#x27;: [ &#x27;cn-hangzhou&#x27; ], #阿里云ECS对应的地域</span><br><span class="line">    &#125;</span><br><span class="line">    monitor_collector_configs = &#123;</span><br><span class="line">        &#x27;targets&#x27;: [</span><br><span class="line">            &#123; &#x27;namespace&#x27;: &#x27;acs_ecs_dashboard&#x27;, &#x27;metrics&#x27;: [&#x27;cpu_cores&#x27;,&#x27;cpu_idle&#x27;,&#x27;cpu_system&#x27;,&#x27;cpu_user&#x27;,&#x27;cpu_wait&#x27;,&#x27;disk_readbytes&#x27;,&#x27;disk_readiops&#x27;,&#x27;disk_writebytes&#x27;,&#x27;disk_writeiops&#x27;,&#x27;diskusage_avail&#x27;,&#x27;diskusage_free&#x27;,&#x27;diskusage_total&#x27;,&#x27;diskusage_used&#x27;,&#x27;diskusage_utilization&#x27;,&#x27;fs_inodeutilization&#x27;,&#x27;load_15m&#x27;,&#x27;load_1m&#x27;,&#x27;load_5m&#x27;,&#x27;memory_freespace&#x27;,&#x27;memory_freeutilization&#x27;,&#x27;memory_totalspace&#x27;,&#x27;memory_usedspace&#x27;,&#x27;memory_usedutilization&#x27;,&#x27;net_tcpconnection&#x27;,&#x27;networkin_packages&#x27;,&#x27;networkin_rate&#x27;,&#x27;networkout_packages&#x27;,&#x27;networkout_rate&#x27;] &#125;, </span><br><span class="line">        ],</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    # 创建采集器</span><br><span class="line">    collectors = [</span><br><span class="line">        aliyun_ecs.DataCollector(account, common_aliyun_configs),</span><br><span class="line">        aliyun_monitor.DataCollector(account, monitor_collector_configs),</span><br><span class="line">    ]</span><br><span class="line"></span><br><span class="line">    # 启动执行</span><br><span class="line">    Runner(collectors).run()</span><br></pre></td></tr></table></figure><ol start="5"><li>**保存 **配置并 <strong>发布</strong></li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155907025-1129827841.png" alt="1649817737331-814ce09c-e8bb-45dc-88c2-4481e95ab99c.png"></p><h4 id="定时任务"><a href="#定时任务" class="headerlink" title="定时任务"></a>定时任务</h4><ol><li>添加自动触发任务，管理 - 自动触发配置 - 新建任务</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155907203-1664184197.png" alt="1649817087577-c59c21a6-27cc-468c-a05e-7d4bca51e397.png"></p><ol start="2"><li>自动触发配置，执行函数中添加此脚本，执行频率为 **每分钟 * * * * ***</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155907386-435470873.png" alt="1649817037914-fcb95371-fde5-413f-b326-331edb3ec12c.png"></p><ol start="3"><li>指标预览</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155907598-1713232702.png" alt="1639117771613-a8a6d6a3-174b-46cc-9567-16b9ca111286.png"></p><h2 id="场景视图"><a href="#场景视图" class="headerlink" title="场景视图"></a>场景视图</h2><p>&lt;场景 - 新建仪表板 - 内置模板库 - 阿里云 ECS&gt;</p><h2 id="监控规则"><a href="#监控规则" class="headerlink" title="监控规则"></a>监控规则</h2><p>&lt;监控 - 模板新建 - 阿里云 ECS 检测库&gt;</p><h2 id="指标详解"><a href="#指标详解" class="headerlink" title="指标详解"></a>指标详解</h2><p>&lt;<a href="https://help.aliyun.com/document_detail/162844.htm?spm=a2c4g.11186623.0.0.43b973c2g7MWB8#concept-2482301">阿里云 ECS 指标列表</a>&gt;</p><h2 id="常见问题排查"><a href="#常见问题排查" class="headerlink" title="常见问题排查"></a>常见问题排查</h2><ul><li>查看日志：Func 日志路径 &#x2F;usr&#x2F;local&#x2F;dataflux-func&#x2F;data&#x2F;logs&#x2F;dataflux-func.log</li><li>代码调试：编辑模式选择主函数，直接运行 (可以看到脚本输出)</li></ul><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155907783-1191413946.png" alt="1649817829879-395270fd-3af7-43d5-80d6-f2508df12edb.png"></p><ul><li>连接配置：Func 无法连接 Datakit，请检查数据源配置 (Datakit 需要监听 0.0.0.0)</li></ul><p><img src="https://cdn.chucz.asia/blog/3703820-20251021155907990-150903890.png" alt="1640337156764-a214db06-5150-472b-8905-03a81b430d86.png"></p>]]>
    </content>
    <id>https://www.chucz.asia/2026/04/09/ECS%E7%9B%91%E6%8E%A7/</id>
    <link href="https://www.chucz.asia/2026/04/09/ECS%E7%9B%91%E6%8E%A7/"/>
    <published>2026-04-09T06:42:38.000Z</published>
    <summary>
      <![CDATA[<h2 id="视图预览"><a href="#视图预览" class="headerlink" title="视图预览"></a>视图预览</h2><p>阿里云 ECS 指标展示，包括CPU 负载，内存使用，磁盘读写，网络流量等</p>
<p><img]]>
    </summary>
    <title>ECS监控</title>
    <updated>2026-04-09T06:42:38.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>褚成志</name>
    </author>
    <category term="大数据" scheme="https://www.chucz.asia/categories/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
    <category term="大数据" scheme="https://www.chucz.asia/tags/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
    <category term="Hadoop" scheme="https://www.chucz.asia/tags/Hadoop/"/>
    <category term="HDFS" scheme="https://www.chucz.asia/tags/HDFS/"/>
    <content>
      <![CDATA[<p><font style="color:#F5222D;background-color:#FADB14;">注意机器启动过之后，同步的时候不要同步data文件夹</font></p><p><font style="color:#F5222D;background-color:#FADB14;"></font></p><p>一次写入，多次读出，不支持文件修改。适合数据分析，不适合网盘应用</p><p>分布式存储，文件系统。</p><p>优点：</p><ul><li>高容错性。多复制，丢失自动恢复</li><li>适合大数据，数据以及文件规模</li><li>可以在廉价机器上，多副本来实现高可靠</li></ul><p>缺点：</p><ul><li>不适合低延时数据访问</li><li>无法对小文件高效存储</li><li>不支持并发多线程同时写入、文件随机修改，只支持append</li></ul><h1 id="组成"><a href="#组成" class="headerlink" title="组成"></a>组成</h1><p>nn:master，</p><ul><li>管理HDFS命名空间；</li><li>配置副本策略（放在那个nn节点）；</li><li><font style="color:#F5222D;background-color:#FADB14;">管理数据块Block</font>（DN里面存放的是一个个数据块，不是简单的文件）的映射信息；</li><li>处理客户端读写请求</li></ul><p>dn:slave，nn下达指令，DN执行操作。</p><ul><li>存储实际的数据块，<font style="color:#F5222D;background-color:#FADB14;">数据块的形式存在</font></li><li>执行读写操作。</li></ul><p>client: </p><ul><li><font style="color:#F5222D;background-color:#FADB14;">文件切块，block大小由此处决定，平衡数据存储</font></li><li>与NN交互，获取文件位置信息</li><li>DN交互，获取文件信息</li><li>访问和管理HDFS</li></ul><p>2NN：</p><ul><li>NN助手</li></ul><p>HDFS文件块：</p><p>物理上是分块存储的，大小可以通过配置参数来决定，默认是128M</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005447487-910560158.png" alt="1603193559306-81fcda9c-9072-45f0-901c-a124fcd71e8f.png"></p><h1 id="web页面无法新建文件夹权限问题"><a href="#web页面无法新建文件夹权限问题" class="headerlink" title="web页面无法新建文件夹权限问题"></a>web页面无法新建文件夹权限问题</h1><p><a href="http://hadoop101:9870/explorer.html#/">http://hadoop101:9870/explorer.html#/</a></p><p>在浏览器创建目录和删除目录及文件，是dr.who用户，dr.who其实是hadoop中http访问的静态用户名，并没有啥特殊含义，可以通过修改core-site.xml，配置为当前用户</p><p><font style="color:#0000FF;">&lt;</font><font style="color:#800000;">property</font><font style="color:#0000FF;">&gt;</font></p><p><font style="color:#0000FF;">  &lt;</font><font style="color:#800000;">name</font><font style="color:#0000FF;">&gt;</font>hadoop.http.staticuser.user<font style="color:#0000FF;">&lt;&#x2F;</font><font style="color:#800000;">name</font><font style="color:#0000FF;">&gt;</font></p><p><font style="color:#0000FF;">  &lt;</font><font style="color:#800000;">value</font><font style="color:#0000FF;">&gt;deltaqin</font><font style="color:#0000FF;">&lt;&#x2F;</font><font style="color:#800000;">value</font><font style="color:#0000FF;">&gt;</font></p><p><font style="color:#0000FF;">&lt;&#x2F;</font><font style="color:#800000;">property</font><font style="color:#0000FF;">&gt;</font></p><p><font style="color:#0000FF;"></font></p><p>另外，通过查看hdfs的默认配置hdfs-default.xml发现hdfs默认是开启权限检查的。</p><p>dfs.<font style="color:#FF00FF;">permissions</font>.enabled<font style="color:#808080;">&#x3D;</font>true #是否在HDFS中开启权限检查,默认为true</p><h3 id="解决"><a href="#解决" class="headerlink" title="解决"></a>解决</h3><p>第一种方案</p><p>直接修改&#x2F;user目录的权限设置，操作如下:</p><p>hdfs dfs -chmod -R 755 &#x2F;user</p><p><font style="color:#F5222D;background-color:#FADB14;">第二种方案</font></p><p>在Hadoop的配置文件core-site.xml中增加如下配置：</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005447865-962456361.gif" alt="1605078784553-1d5477b7-651f-4f6a-a05c-2ba8b2857e7c.gif"><font style="color:#008000;"><!--</font><font style="color:#008000;"> 当前用户设置成zls,zls是我的登录用户名 </font><font style="color:#008000;">--></font></p><p><font style="color:#0000FF;">&lt;</font><font style="color:#800000;">property</font><font style="color:#0000FF;">&gt;</font></p><p><font style="color:#0000FF;">  &lt;</font><font style="color:#800000;">name</font><font style="color:#0000FF;">&gt;</font>hadoop.http.staticuser.user<font style="color:#0000FF;">&lt;&#x2F;</font><font style="color:#800000;">name</font><font style="color:#0000FF;">&gt;</font></p><p><font style="color:#0000FF;">  &lt;</font><font style="color:#800000;">value</font><font style="color:#0000FF;">&gt;</font><font style="color:#0000FF;">deltaqin</font><font style="color:#0000FF;">&lt;&#x2F;</font><font style="color:#800000;">value</font><font style="color:#0000FF;">&gt;</font></p><p><font style="color:#0000FF;">&lt;&#x2F;</font><font style="color:#800000;">property</font><font style="color:#0000FF;">&gt;</font></p><p><font style="color:#008000;"><!--</font><font style="color:#008000;"> 不开启权限检查 </font><font style="color:#008000;">--></font></p><p><font style="color:#0000FF;">&lt;</font><font style="color:#800000;">property</font><font style="color:#0000FF;">&gt;</font></p><p><font style="color:#0000FF;">  &lt;</font><font style="color:#800000;">name</font><font style="color:#0000FF;">&gt;</font>dfs.permissions.enabled<font style="color:#0000FF;">&lt;&#x2F;</font><font style="color:#800000;">name</font><font style="color:#0000FF;">&gt;</font></p><p><font style="color:#0000FF;">  &lt;</font><font style="color:#800000;">value</font><font style="color:#0000FF;">&gt;</font>false<font style="color:#0000FF;">&lt;&#x2F;</font><font style="color:#800000;">value</font><font style="color:#0000FF;">&gt;</font></p><p><font style="color:#0000FF;">&lt;&#x2F;</font><font style="color:#800000;">property</font><font style="color:#0000FF;">&gt;</font></p><h1 id="命令行操作"><a href="#命令行操作" class="headerlink" title="命令行操作"></a>命令行操作</h1><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">bin/hadoop fs </span><br><span class="line">bin/hdfs dfs</span><br><span class="line"></span><br><span class="line">hadoop fs </span><br><span class="line">hdfs dfs</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 启动Hadoop集群（方便后续的测试）</span></span><br><span class="line"><span class="comment"># 101</span></span><br><span class="line">sbin/start-dfs.sh</span><br><span class="line"><span class="comment"># 102</span></span><br><span class="line">sbin/start-yarn.sh</span><br><span class="line"></span><br><span class="line"><span class="comment"># -help：输出这个命令参数</span></span><br><span class="line">hadoop fs -<span class="built_in">help</span> <span class="built_in">rm</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">#上传</span></span><br><span class="line"><span class="comment"># -moveFromLocal：从本地剪切粘贴到HDFS</span></span><br><span class="line"><span class="built_in">touch</span> kongming.txt</span><br><span class="line">hadoop fs  -moveFromLocal  ./kongming.txt  /sanguo/shuguo</span><br><span class="line"><span class="comment"># -copyFromLocal：从本地文件系统中拷贝文件到HDFS路径去</span></span><br><span class="line">hadoop fs -copyFromLocal README.txt /</span><br><span class="line"><span class="comment"># -appendToFile：追加一个文件到已经存在的文件末尾</span></span><br><span class="line"><span class="built_in">touch</span> liubei.txt</span><br><span class="line">vi liubei.txt</span><br><span class="line"><span class="comment"># 输入</span></span><br><span class="line">san gu mao lu</span><br><span class="line">hadoop fs -appendToFile liubei.txt /sanguo/shuguo/kongming.txt</span><br><span class="line"><span class="comment"># -put：等同于copyFromLocal</span></span><br><span class="line">hadoop fs -put ./zaiyiqi.txt /user/atguigu/test/</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 下载</span></span><br><span class="line"><span class="comment"># -copyToLocal：从HDFS拷贝到本地</span></span><br><span class="line">hadoop fs -copyToLocal /sanguo/shuguo/kongming.txt ./</span><br><span class="line"><span class="comment"># -get：等同于copyToLocal，就是从HDFS下载文件到本地</span></span><br><span class="line">hadoop fs -get /sanguo/shuguo/kongming.txt ./</span><br><span class="line"><span class="comment"># -getmerge：合并下载多个文件，比如HDFS的目录 /user/atguigu/test下有多个文件:log.1, log.2,log.3,...</span></span><br><span class="line">hadoop fs -getmerge /user/atguigu/test/* ./zaiyiqi.txt</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># HDFS直接操作</span></span><br><span class="line"><span class="comment"># -ls: 显示目录信息</span></span><br><span class="line">hadoop fs -<span class="built_in">ls</span> /</span><br><span class="line"><span class="comment"># -mkdir：在HDFS上创建目录</span></span><br><span class="line">hadoop fs -<span class="built_in">mkdir</span> -p /sanguo/shuguo</span><br><span class="line"><span class="comment"># -cat：显示文件内容</span></span><br><span class="line">hadoop fs -<span class="built_in">cat</span> /sanguo/shuguo/kongming.txt</span><br><span class="line"><span class="comment"># -chgrp 、-chmod、-chown：Linux文件系统中的用法一样，修改文件所属权限</span></span><br><span class="line">hadoop fs  -<span class="built_in">chmod</span>  666  /sanguo/shuguo/kongming.txt</span><br><span class="line">hadoop fs  -<span class="built_in">chown</span>  deltaqin:deltaqin   /sanguo/shuguo/kongming.txt</span><br><span class="line"><span class="comment"># -cp ：从HDFS的一个路径拷贝到HDFS的另一个路径</span></span><br><span class="line">hadoop fs -<span class="built_in">cp</span> /sanguo/shuguo/kongming.txt /zhuge.txt</span><br><span class="line"><span class="comment"># -mv：在HDFS目录中移动文件</span></span><br><span class="line">hadoop fs -<span class="built_in">mv</span> /zhuge.txt /sanguo/shuguo/</span><br><span class="line"><span class="comment"># -tail：显示一个文件的末尾</span></span><br><span class="line">hadoop fs -<span class="built_in">tail</span> /sanguo/shuguo/kongming.txt</span><br><span class="line"><span class="comment"># -rm：删除文件或文件夹</span></span><br><span class="line">hadoop fs -<span class="built_in">rm</span> /user/atguigu/test/jinlian2.txt</span><br><span class="line"><span class="comment"># -rmdir：删除空目录</span></span><br><span class="line">hadoop fs -<span class="built_in">mkdir</span> /test</span><br><span class="line">hadoop fs -<span class="built_in">rmdir</span> /test</span><br><span class="line"><span class="comment"># -du统计文件夹的大小信息</span></span><br><span class="line">hadoop fs -<span class="built_in">du</span> -s -h /user/atguigu/test</span><br><span class="line">2.7 K  /user/atguigu/test</span><br><span class="line"></span><br><span class="line">hadoop fs -<span class="built_in">du</span>  -h /user/atguigu/test</span><br><span class="line">1.3 K  /user/atguigu/test/README.txt</span><br><span class="line">15     /user/atguigu/test/jinlian.txt</span><br><span class="line">1.4 K  /user/atguigu/test/zaiyiqi.txt</span><br><span class="line"></span><br><span class="line"><span class="comment"># -setrep：设置HDFS中文件的副本数量</span></span><br><span class="line">hadoop fs -setrep 10 /sanguo/shuguo/kongming.txt</span><br></pre></td></tr></table></figure><h1 id="客户端操作"><a href="#客户端操作" class="headerlink" title="客户端操作"></a>客户端操作</h1><h2 id="连接和关闭"><a href="#连接和关闭" class="headerlink" title="连接和关闭"></a>连接和关闭</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Before</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">before</span><span class="params">()</span> <span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line"><span class="comment">//        相当于site文件，配置HDFS</span></span><br><span class="line"><span class="comment">//        Configuration configuration = new Configuration();</span></span><br><span class="line"><span class="comment">//        2个副本</span></span><br><span class="line"><span class="comment">//        configuration.set(&quot;dfs.replication&quot;, &quot;2&quot;);</span></span><br><span class="line"><span class="comment">//        128/2 = 64M</span></span><br><span class="line"><span class="comment">//        configuration.set(&quot;dfs.blocksize&quot;, &quot;67108864&quot;);</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">//1. 新建HDFS对象</span></span><br><span class="line">    fileSystem = FileSystem.get(URI.create(<span class="string">&quot;hdfs://hadoop101:8020&quot;</span>),</span><br><span class="line">                                <span class="keyword">new</span> <span class="title class_">Configuration</span>(), <span class="string">&quot;deltaqin&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@After</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">after</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    fileSystem.close();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="基本操作API"><a href="#基本操作API" class="headerlink" title="基本操作API"></a>基本操作API</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 上传</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> IOException</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> InterruptedException</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">put</span><span class="params">()</span> <span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line">    <span class="comment">//        相当于site文件，配置HDFS</span></span><br><span class="line">    <span class="comment">//        Configuration configuration = new Configuration();</span></span><br><span class="line">    <span class="comment">//        2个副本</span></span><br><span class="line">    <span class="comment">//        configuration.set(&quot;dfs.replication&quot;, &quot;2&quot;);</span></span><br><span class="line">    <span class="comment">//        128/2 = 64M</span></span><br><span class="line">    <span class="comment">//        configuration.set(&quot;dfs.blocksize&quot;, &quot;67108864&quot;);</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//2. 操作集群</span></span><br><span class="line">    fileSystem.copyFromLocalFile(</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Path</span>(<span class="string">&quot;/Users/qinzetao/Pictures/QQ20200621-0.jpg&quot;</span>),</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Path</span>(<span class="string">&quot;/1.jpg&quot;</span>));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 下载</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">get</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    fileSystem.copyToLocalFile(</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Path</span>(<span class="string">&quot;/1.jpg&quot;</span>),</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Path</span>(<span class="string">&quot;/Users/qinzetao/Documents/大数据/1_hadoop/代码/Hadoop/hdfs200105&quot;</span>)</span><br><span class="line">    );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 查看文件和文件夹</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> IOException</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">ls</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    FileStatus[] fileStatuses = fileSystem.listStatus(<span class="keyword">new</span> <span class="title class_">Path</span>(<span class="string">&quot;/&quot;</span>));</span><br><span class="line">    <span class="keyword">for</span> (FileStatus fileStatus : fileStatuses) &#123;</span><br><span class="line">        System.out.println(fileStatus.getPath());</span><br><span class="line">        System.out.println(fileStatus.getOwner());</span><br><span class="line">        System.out.println(<span class="string">&quot;=================&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 查看文件</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> IOException</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">lf</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    RemoteIterator&lt;LocatedFileStatus&gt; statusRemoteIterator =</span><br><span class="line">        fileSystem.listFiles(<span class="keyword">new</span> <span class="title class_">Path</span>(<span class="string">&quot;/&quot;</span>), <span class="literal">true</span>);</span><br><span class="line">    <span class="keyword">while</span> (statusRemoteIterator.hasNext()) &#123;</span><br><span class="line">        <span class="type">LocatedFileStatus</span> <span class="variable">fileStatus</span> <span class="operator">=</span> statusRemoteIterator.next();</span><br><span class="line"></span><br><span class="line">        System.out.println(fileStatus.getPath());</span><br><span class="line">        BlockLocation[] blockLocations = fileStatus.getBlockLocations();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; blockLocations.length; i++) &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;第&quot;</span> + i + <span class="string">&quot;块&quot;</span>);</span><br><span class="line">            String[] hosts = blockLocations[i].getHosts();</span><br><span class="line">            <span class="keyword">for</span> (String host : hosts) &#123;</span><br><span class="line">                System.out.print(host + <span class="string">&quot; &quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            System.out.println();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        System.out.println(<span class="string">&quot;===================================&quot;</span>);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 追加</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">append</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">FSDataOutputStream</span> <span class="variable">append</span> <span class="operator">=</span> fileSystem.append(</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Path</span>(<span class="string">&quot;/README.txt&quot;</span>)</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    append.write(<span class="string">&quot;TestAPI&quot;</span>.getBytes());</span><br><span class="line"></span><br><span class="line">    IOUtils.closeStream(append);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 移动</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">mv</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    fileSystem.rename(<span class="keyword">new</span> <span class="title class_">Path</span>(<span class="string">&quot;/1.jpg&quot;</span>),</span><br><span class="line">                      <span class="keyword">new</span> <span class="title class_">Path</span>(<span class="string">&quot;/logs/2.jpg&quot;</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="数据流"><a href="#数据流" class="headerlink" title="数据流"></a>数据流</h1><h2 id="写数据"><a href="#写数据" class="headerlink" title="写数据"></a>写数据</h2><h3 id="完整流程"><a href="#完整流程" class="headerlink" title="完整流程"></a>完整流程</h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005448143-820603135.png" alt="1603273564363-337e2cad-db3d-44e5-a1be-c2f15aedbae3.png"></p><p>注意每一块选择存放在哪些节点是完全独立的过程。</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005448442-609318574.png" alt="1603289601957-eaee2fbb-7a37-4bf5-a4f2-d96caa29ab9f.png"></p><h3 id="节点选择"><a href="#节点选择" class="headerlink" title="节点选择"></a>节点选择</h3><p><font style="color:#F5222D;background-color:#FADB14;">NameNode</font><font style="color:#F5222D;background-color:#FADB14;">会</font><font style="color:#F5222D;background-color:#FADB14;">选择</font><font style="color:#F5222D;background-color:#FADB14;">距离待上传数据最近</font><font style="color:#F5222D;background-color:#FADB14;">距离</font><font style="color:#F5222D;background-color:#FADB14;">的DataNode接收数据</font></p><ul><li>节点距离（网络拓扑距离）：两个节点到达最近的共同祖先的距离总和。</li><li>rack：机架，看做路由器，下面有很多DN</li></ul><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005448640-534415332.png" alt="1603290273477-ff81af91-618f-4414-a001-8276021df114.png"><img src="https://cdn.chucz.asia/blog/3703820-20251013005448839-753452206.png" alt="1603290330852-8bfaf171-977e-4cee-99ed-216b0255aad9.png"></p><h3 id="机架-感知（副本存储节点-选择）"><a href="#机架-感知（副本存储节点-选择）" class="headerlink" title="机架**感知（副本存储节点**选择）"></a><strong>机架**<strong>感知</strong></strong>（副本存储节点**<strong>选择）</strong></h3><p>副本放置策略：<font style="color:#000000;background-color:#FADB14;">For the common case, when the replication factor is three, HDFS’s placement policy is to put one replica on the local machine if the writer is on a datanode, otherwise on a random datanode, another replica on a node in a different (remote) rack, and the last on a different node in the same remote rack</font><font style="color:#000000;">.</font></p><p><font style="color:#000000;"></font></p><ul><li><font style="color:#F5222D;background-color:#FADB14;">第一个副本在Client所处的节点上。如果客户端在集群外，随机选一个。</font></li><li><font style="color:#F5222D;background-color:#FADB14;">第二个副本和第一个副本位于相同机架，随机节点 。</font></li><li><font style="color:#F5222D;background-color:#FADB14;">第三个副本位于不同机架，随机节点。 </font></li></ul><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005449059-581442835.png" alt="1603294115959-72d488ca-4174-4449-9274-49feb843592a.png"></p><h2 id="读数据"><a href="#读数据" class="headerlink" title="读数据"></a>读数据</h2><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005449323-762664333.png" alt="1603273690031-cf68b7e3-9d11-45e0-9855-30d23c347a70.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005449591-1952703742.png" alt="1603294195957-fb8cf1c2-bfb9-40b7-80ba-6e4ef533d620.png"></p><h1 id="NameNode和SecondaryNameNode"><a href="#NameNode和SecondaryNameNode" class="headerlink" title="NameNode和SecondaryNameNode"></a>NameNode和SecondaryNameNode</h1><p>元数据要求读写快，放在内存里面</p><p>涉及到持久化问题，如何提高效率？</p><p>利用多级缓存的思想！！！</p><ul><li><font style="color:#F5222D;">FSImage文件是HDFS中名字节点NameNode上文件&#x2F;目录元数据在特定某一时刻的持久化存储文件。（相当于是内存的镜像）</font></li><li><font style="color:#F5222D;">edits.log记录的是该干什么，不是元数据，元数据是读取这个记录之后读取对应的元数据放到内存里面得到的。</font></li><li><font style="color:#F5222D;">日志和image都在磁盘上，一个是日志，一个是数据</font></li></ul><h2 id="NN"><a href="#NN" class="headerlink" title="NN"></a>NN</h2><p>NN只持久化操作日志，</p><p>edits.log：记录操作，编辑日志</p><p>fsimage：edits持久化，镜像文件</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005449890-1786395236.png" alt="1603273776396-ee7a2e41-c159-478e-868d-d9c3ad4e4b11.png"></p><p>NameNode被格式化之后，将在 $HADOOP_HOME&#x2F;data&#x2F;tmp&#x2F;dfs&#x2F;name&#x2F;current 目录中产生如下文件：</p><ul><li>Fsimage文件：（记录某一时刻内存状态）HDFS文件系统元数据的一个永久性的检查点，其中包含HDFS文件系统的所有目录和文件inode的序列化信息。  </li><li>Edits文件：（记录过程，没有元数据）存放HDFS文件系统的所有更新操作的路径，文件系统客户端执行的所有写操作首先会被记录到Edits文件中。</li><li>seen_txid文件保存的是一个数字，就是最后一个edits_的数字 </li><li><font style="color:#F5222D;">每次NameNode启动的时候都会将Fsimage文件读入内存，加载Edits里面的更新操作</font>，保证内存中的元数据信息是最新的、同步的，可以看成NameNode启动的时候就将Fsimage和Edits文件进行了合并。</li></ul><p><font style="color:#F5222D;background-color:#FADB14;">启动流程</font></p><p><font style="color:#F5222D;background-color:#FADB14;">重启结束之后会触发一次合并，保存为checkpoint</font></p><ul><li><img src="https://cdn.chucz.asia/blog/3703820-20251013005450179-2000856911.png" alt="1603352971665-6a8b9049-b62c-4406-892c-506d51eb6bba.png"></li></ul><h2 id="2NN"><a href="#2NN" class="headerlink" title="2NN"></a>2NN</h2><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005450420-1379662562.png" alt="1603273800838-1bc12f2d-a5af-44b8-8e31-0cc19a50c6cf.png"><img src="https://cdn.chucz.asia/blog/3703820-20251013005450802-306254712.png" alt="1603294276853-6a375035-bed6-401c-a7e1-73d974b6ddfd.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005451116-202947017.png" alt="1603273907292-410af2e5-abef-48d9-9906-57aa9a709e8e.png"></p><h2 id="Fsimage和Edits"><a href="#Fsimage和Edits" class="headerlink" title="Fsimage和Edits"></a>Fsimage和Edits</h2><p>格式化会生成一个空的fsimage,就可以启动了。</p><h3 id="oiv查看Fsimage文件"><a href="#oiv查看Fsimage文件" class="headerlink" title="oiv查看Fsimage文件"></a>oiv查看Fsimage文件</h3><p>oev                  apply the offline edits viewer to an edits file</p><p>oiv                  apply the offline fsimage viewer to an fsimage</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">hdfs oiv -p 文件类型 -i 镜像文件 -o 转换后文件输出路径</span><br><span class="line"></span><br><span class="line"><span class="built_in">pwd</span></span><br><span class="line">/opt/module/hadoop-3.1.3/data/tmp/dfs/name/current</span><br><span class="line">hdfs oiv -p XML -i fsimage_0000000000000000025 -o /opt/module/hadoop-3.1.3/fsimage.xml </span><br><span class="line"><span class="built_in">cat</span> /opt/module/hadoop-3.1.3/fsimage.xml</span><br></pre></td></tr></table></figure><p><font style="color:#F5222D;">记录块信息，几块，多大</font></p><p><font style="color:#F5222D;background-color:#FADB14;">F</font><font style="color:#F5222D;background-color:#FADB14;">simage中只记录由哪些块组成，没有记录块所对应</font><font style="color:#F5222D;background-color:#FADB14;">D</font><font style="color:#F5222D;background-color:#FADB14;">ata</font><font style="color:#F5222D;background-color:#FADB14;">N</font><font style="color:#F5222D;background-color:#FADB14;">ode</font>，为什么？</p><p>在集群刚刚启动后，加载fsimage之后，要求DataNode上报数据块信息，并间隔一段时间后再次上报。（在安全模式里面，由DN主动向NN汇报，不让NN维护可以避免自己拿到的是陈旧的位置，）</p><h3 id="oev查看Edits文件"><a href="#oev查看Edits文件" class="headerlink" title="oev查看Edits文件"></a>oev查看Edits文件</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">hdfs oev -p 文件类型 -i编辑日志 -o 转换后文件输出路径</span><br><span class="line"></span><br><span class="line">hdfs oev -p XML -i edits_0000000000000000012-0000000000000000013 -o /opt/module/hadoop-3.1.3/edits.xml</span><br><span class="line"><span class="built_in">cat</span> /opt/module/hadoop-3.1.3/edits.xml</span><br></pre></td></tr></table></figure><p><font style="color:#F5222D;">内部是一个个的record，记录的是一个个操作</font></p><p>NameNode如何确定下次开机启动的时候合并哪些Edits？</p><h2 id="CheckPoint时间设置"><a href="#CheckPoint时间设置" class="headerlink" title="CheckPoint时间设置"></a><strong>C<strong><strong>heck</strong></strong>Point时间设置</strong></h2><p><strong>通常<strong><strong>情况下，SecondaryNameNode</strong></strong>每隔**<strong>一小时执行一次</strong></strong>。下图所示：**</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005451426-582230969.png" alt="1603352867177-fadd666a-d320-434a-ab89-944ab04dc6e7.png"></p><p>hdfs-default.xml：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;property&gt;</span><br><span class="line">  &lt;name&gt;dfs.namenode.checkpoint.period&lt;/name&gt;</span><br><span class="line">  &lt;value&gt;3600&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br></pre></td></tr></table></figure><p>*<em>一分钟</em><em>*<em>检查一次操作次数，<strong><strong>当操作次数达到</strong></strong>1百万</em>*</em><em>时，SecondaryNameNode</em><em><strong>执行</strong></em><em>一次。</em>*</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&lt;property&gt;</span><br><span class="line">  &lt;name&gt;dfs.namenode.checkpoint.txns&lt;/name&gt;</span><br><span class="line">  &lt;value&gt;1000000&lt;/value&gt;</span><br><span class="line">&lt;description&gt;操作动作次数&lt;/description&gt;</span><br><span class="line">&lt;/property&gt;</span><br><span class="line"> </span><br><span class="line">&lt;property&gt;</span><br><span class="line">  &lt;name&gt;dfs.namenode.checkpoint.check.period&lt;/name&gt;</span><br><span class="line">  &lt;value&gt;60&lt;/value&gt;</span><br><span class="line">&lt;description&gt; 1分钟检查一次操作次数&lt;/description&gt;</span><br><span class="line">&lt;/property &gt;</span><br></pre></td></tr></table></figure><h2 id="（现在都用HA，不用这个）NameNode故障处理"><a href="#（现在都用HA，不用这个）NameNode故障处理" class="headerlink" title="**（现在都用HA，不用这个）NameNode故障处理**"></a>**（现在都用HA，不用这个）<strong><strong>N</strong></strong>ameNode<strong><strong>故障</strong></strong>处理**</h2><p>NameNode故障后，可以采用如下两种方法恢复数据。</p><h3 id="将SecondaryNameNode中数据拷贝到NameNode存储数据的目录"><a href="#将SecondaryNameNode中数据拷贝到NameNode存储数据的目录" class="headerlink" title="将SecondaryNameNode中数据拷贝到NameNode存储数据的目录"></a>将SecondaryNameNode中数据拷贝到NameNode存储数据的目录</h3><p>2NN的数据其实只是NN的部分数据，所以这个一般不再使用</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">kill</span> -9 NameNode进程</span><br><span class="line"><span class="comment"># 删除NameNode存储的数据（/opt/module/hadoop-3.1.3/data/tmp/dfs/name）</span></span><br><span class="line"><span class="built_in">rm</span> -rf /opt/module/hadoop-3.1.3/data/tmp/dfs/name/*</span><br><span class="line"><span class="comment"># 拷贝SecondaryNameNode中数据到原NameNode存储数据目录</span></span><br><span class="line">scp -r atguigu@hadoop104:/opt/module/hadoop-3.1.3/data/tmp/dfs/namesecondary/* ./name/</span><br><span class="line"><span class="comment"># 重新启动NameNode</span></span><br><span class="line">hdfs --daemon start namenode</span><br></pre></td></tr></table></figure><h3 id="使用-importCheckpoint选项启动NameNode守护进程，从而将SecondaryNameNode-中数据拷贝到NameNode目录中。"><a href="#使用-importCheckpoint选项启动NameNode守护进程，从而将SecondaryNameNode-中数据拷贝到NameNode目录中。" class="headerlink" title="使用-importCheckpoint选项启动NameNode守护进程，从而将SecondaryNameNode 中数据拷贝到NameNode目录中。"></a>使用-importCheckpoint选项启动NameNode守护进程，从而将SecondaryNameNode 中数据拷贝到NameNode目录中。</h3><p>修改hdfs-site.xml中的</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&lt;property&gt;</span><br><span class="line">    &lt;name&gt;dfs.namenode.checkpoint.period&lt;/name&gt;</span><br><span class="line">    &lt;value&gt;120&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br><span class="line"> </span><br><span class="line">&lt;property&gt;</span><br><span class="line">    &lt;name&gt;dfs.namenode.name.dir&lt;/name&gt;</span><br><span class="line">    &lt;value&gt;/opt/module/hadoop-3.1.3/data/tmp/dfs/name&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">kill</span> -9 NameNode进程</span><br><span class="line"><span class="comment"># 删除NameNode存储的数据（/opt/module/hadoop-3.1.3/data/tmp/dfs/name）</span></span><br><span class="line"><span class="built_in">rm</span> -rf /opt/module/hadoop-3.1.3/data/tmp/dfs/name/*</span><br><span class="line"><span class="comment"># 如果SecondaryNameNode不和NameNode在一个主机节点上，需要将</span></span><br><span class="line"><span class="comment"># SecondaryNameNode存储数据的目录拷贝到NameNode存储数据的平级目录，并删除in_use.lock文件</span></span><br><span class="line">scp -r atguigu@hadoop104:/opt/module/hadoop-3.1.3/data/tmp/dfs/namesecondary ./</span><br><span class="line"> </span><br><span class="line"><span class="built_in">rm</span> -rf in_use.lock</span><br><span class="line"> </span><br><span class="line"><span class="built_in">pwd</span></span><br><span class="line">/opt/module/hadoop-3.1.3/data/tmp/dfs</span><br><span class="line"> </span><br><span class="line"><span class="built_in">ls</span></span><br><span class="line">data  name  namesecondary</span><br><span class="line"></span><br><span class="line"><span class="comment"># 导入检查点数据（等待一会ctrl+c结束掉）</span></span><br><span class="line">bin/hdfs namenode -importCheckpoint</span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动NameNode</span></span><br><span class="line">hdfs --daemon start namenode</span><br></pre></td></tr></table></figure><h2 id="集群安全模式"><a href="#集群安全模式" class="headerlink" title="集群安全模式"></a>集群安全模式</h2><p>集群状态不正确的时候，就加入，启动的时候也会进入。</p><ul><li><font style="color:#F5222D;">NameNode</font><font style="color:#F5222D;">启动</font></li></ul><p>NameNode启动时，首先将镜像文件（Fsimage）载入内存，并执行编辑日志（Edits）中的各项操作。一旦在内存中成功建立文件系统元数据的映像，则创建一个新的Fsimage文件和一个空的编辑日志。此时，NameNode开始监听DataNode请求。这个过程期间，NameNode一直运行在安全模式，即<font style="color:#F5222D;">NameNode的文件系统对于客户端来说是只读的。 </font></p><ul><li><font style="color:#F5222D;">DataNode启动</font></li></ul><p>系统中的数据块的位置并不是由NameNode维护的，而是以块列表的形式存储在DataNode中。在系统的正常操作期间，NameNode会在内存中保留所有块位置的映射信息。在安全模式下，<font style="color:#F5222D;">各个DataNode会向NameNode发送最新的块列表信息</font>，NameNode了解到足够多的块位置信息之后，即可高效运行文件系统。 </p><ul><li><font style="color:#F5222D;">安全模式退出判断</font></li></ul><p>如果满足“最小副本条件”，NameNode会在30秒钟之后就退出安全模式。所谓的最小副本条件指的是在整个文件系统中99.9%的块满足最小副本级别（默认值：dfs.replication.min&#x3D;1，一块只要有一个副本就可以）。在启动一个刚刚格式化的HDFS集群时，因为系统中还没有任何块，所以NameNode不会进入安全模式。 </p><ul><li>以下内容可以不看：</li></ul><p>集群处于安全模式，不能执行重要操作（写操作）。集群启动完成后，自动退出安全模式。</p><p>（1）bin&#x2F;hdfs dfsadmin -safemode get（功能描述：查看安全模式状态）</p><p>（2）bin&#x2F;hdfs dfsadmin -safemode enter  （功能描述：进入安全模式状态）</p><p>（3）bin&#x2F;hdfs dfsadmin -safemode leave（功能描述：离开安全模式状态）</p><p>（4）bin&#x2F;hdfs dfsadmin -safemode wait（功能描述：等待安全模式状态）</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看当前模式</span></span><br><span class="line">hdfs dfsadmin -safemode get</span><br><span class="line">Safe mode is OFF</span><br><span class="line"><span class="comment"># 先进入安全模式</span></span><br><span class="line">hdfs dfsadmin -safemode enter</span><br><span class="line"><span class="comment"># 创建并执行下面的脚本</span></span><br><span class="line"><span class="comment"># 在/opt/module/hadoop-3.1.3路径上，编辑一个脚本safemode.sh</span></span><br><span class="line"><span class="built_in">touch</span> safemode.sh</span><br><span class="line">vim safemode.sh</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line">hdfs dfsadmin -safemode <span class="built_in">wait</span></span><br><span class="line">hdfs dfs -put /opt/module/hadoop-3.1.3/README.txt /</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">chmod</span> 777 safemode.sh</span><br><span class="line"> </span><br><span class="line">./safemode.sh </span><br><span class="line"></span><br><span class="line"><span class="comment"># 再打开一个窗口，执行</span></span><br><span class="line">hdfs dfsadmin -safemode leave</span><br><span class="line"></span><br><span class="line"><span class="comment"># 观察</span></span><br><span class="line"><span class="comment"># 观察上一个窗口</span></span><br><span class="line">Safe mode is OFF</span><br><span class="line"></span><br><span class="line"><span class="comment"># HDFS集群上已经有上传的数据了。</span></span><br></pre></td></tr></table></figure><h1 id="DataNode"><a href="#DataNode" class="headerlink" title="DataNode"></a>DataNode</h1><ul><li>原理：把文件数据整整齐齐切开之后按照block存放，想要直接自己恢复的话，可以复制出来，使用cat将文件追加，拼接起来之后直接解压就可以获取原始数据</li><li>块也有自己的元数据</li></ul><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005451735-2108188419.png" alt="1603273863314-18220667-53b6-41ec-a2ff-d04bcd3fc4cb.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005452070-142459333.png" alt="1603295948595-b0a28719-0042-4637-9c51-6a346cc3b3df.png"></p><h2 id="保证数据完整性的方法"><a href="#保证数据完整性的方法" class="headerlink" title="保证数据完整性的方法"></a>保证数据完整性的方法</h2><p>（1）当DataNode读取Block的时候，它会计算CheckSum。</p><p>（2）如果计算后的CheckSum，与Block创建时值不一样，说明Block已经损坏。</p><p>（3）Client读取其他DataNode上的Block。</p><p>（4）DataNode在其文件创建后周期验证CheckSum。</p><h2 id="掉线处理（时限设置）"><a href="#掉线处理（时限设置）" class="headerlink" title="掉线处理（时限设置）"></a>掉线处理（时限设置）</h2><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005452324-2088570171.png" alt="1603296050338-47e3ce57-df94-4448-89de-f6adec86f223.png"></p><p>需要注意的是hdfs-site.xml 配置文件中的heartbeat.recheck.interval的单位为<font style="color:#FF0000;">毫秒</font>，dfs.heartbeat.interval的单位为<font style="color:#FF0000;">秒</font>。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&lt;property&gt;</span><br><span class="line">    &lt;name&gt;dfs.namenode.heartbeat.recheck-interval&lt;/name&gt;</span><br><span class="line">    &lt;value&gt;300000&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br><span class="line">&lt;property&gt;</span><br><span class="line">    &lt;name&gt;dfs.heartbeat.interval&lt;/name&gt;</span><br><span class="line">    &lt;value&gt;3&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br></pre></td></tr></table></figure><h2 id="添加新数据-节点"><a href="#添加新数据-节点" class="headerlink" title="添加新数据****节点"></a><strong>添加新数据****节点</strong></h2><p>在原有集群基础上动态添加新的数据节点。</p><p>再克隆一台hadoop104主机，修改IP地址和主机名称，</p><p>-a 包含所有属性以及角色权限复制</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> rsync -av /opt/module hadoop104:/opt</span><br><span class="line"><span class="built_in">sudo</span> rsync -av /etc/profile.d hadoop104:/etc</span><br></pre></td></tr></table></figure><p>删除原来HDFS文件系统留存的文件（&#x2F;opt&#x2F;module&#x2F;hadoop-3.1.3&#x2F;data和log），source一下配置文件 <code>source /etc/profile</code></p><hr><p><strong><font style="color:#F5222D;">注意这里使用的不是群起，所以不需要配置workers</font></strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 停止DataNode</span></span><br><span class="line">hdfs --daemon stop datanode</span><br><span class="line"></span><br><span class="line"><span class="comment"># 直接启动DataNode，即可关联到集群</span></span><br><span class="line">hdfs --daemon start datanode</span><br><span class="line">sbin/yarn-daemon.sh start nodemanager</span><br><span class="line"></span><br><span class="line"><span class="comment"># 在hadoop104上上传文件</span></span><br><span class="line">hadoop fs -put /opt/module/hadoop-3.1.3/LICENSE.txt /</span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果数据不均衡，可以用命令实现集群的再平衡</span></span><br><span class="line">./start-balancer.sh</span><br><span class="line">starting balancer, logging to /opt/module/hadoop-3.1.3/logs/hadoop-atguigu-balancer-hadoop102.out</span><br><span class="line">Time Stamp               Iteration#  Bytes Already Moved  Bytes Left To Move  Bytes Being Moved</span><br></pre></td></tr></table></figure><h2 id="退役旧数据-节点"><a href="#退役旧数据-节点" class="headerlink" title="退役旧数据****节点"></a><strong>退役旧数据****节点</strong></h2><h3 id="添加白-名单"><a href="#添加白-名单" class="headerlink" title="添加白****名单"></a><strong>添加白****名单</strong></h3><p>添加到白名单的主机节点，都允许访问NameNode，不在白名单的主机节点，都会被退出。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在NameNode的/opt/module/hadoop-3.1.3/etc/hadoop目录下创建dfs.hosts文件</span></span><br><span class="line"><span class="built_in">pwd</span></span><br><span class="line">/opt/module/hadoop-3.1.3/etc/hadoop</span><br><span class="line"></span><br><span class="line"><span class="built_in">touch</span> dfs.hosts</span><br><span class="line">vi dfs.hosts</span><br><span class="line"><span class="comment"># 添加如下主机名称（不添加hadoop105）</span></span><br><span class="line">hadoop102</span><br><span class="line">hadoop103</span><br><span class="line">hadoop104</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在NameNode的hdfs-site.xml配置文件中增加dfs.hosts属性</span></span><br><span class="line">&lt;property&gt;</span><br><span class="line">  &lt;name&gt;dfs.hosts&lt;/name&gt;</span><br><span class="line">  &lt;value&gt;/opt/module/hadoop-3.1.3/etc/hadoop/dfs.hosts&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 配置文件分发</span></span><br><span class="line">xsync hdfs-site.xml</span><br><span class="line"><span class="comment"># 刷新NameNode</span></span><br><span class="line">hdfs dfsadmin -refreshNodes</span><br><span class="line">Refresh nodes successful</span><br><span class="line"><span class="comment"># 更新ResourceManager节点</span></span><br><span class="line">yarn rmadmin -refreshNodes</span><br><span class="line">17/06/24 14:17:11 INFO client.RMProxy: Connecting to ResourceManager at hadoop103/192.168.1.103:8033</span><br><span class="line"></span><br><span class="line"><span class="comment"># 在web浏览器上查看</span></span><br><span class="line"><span class="comment"># 如果数据不均衡，可以用命令实现集群的再平衡</span></span><br><span class="line">./start-balancer.sh</span><br><span class="line">starting balancer, logging to /opt/module/hadoop-3.1.3/logs/hadoop-atguigu-balancer-hadoop102.out</span><br><span class="line">Time Stamp               Iteration#  Bytes Already Moved  Bytes Left To Move  Bytes Being Moved</span><br></pre></td></tr></table></figure><h3 id="黑名单-退役"><a href="#黑名单-退役" class="headerlink" title="黑名单****退役"></a><strong>黑名单****退役</strong></h3><p>在黑名单上面的主机都会被强制退出。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在NameNode的/opt/module/hadoop-3.1.3/etc/hadoop目录下创建dfs.hosts.exclude文件</span></span><br><span class="line"><span class="built_in">pwd</span></span><br><span class="line">/opt/module/hadoop-3.1.3/etc/hadoop</span><br><span class="line"></span><br><span class="line"><span class="built_in">touch</span> dfs.hosts.exclude</span><br><span class="line">vi dfs.hosts.exclude</span><br><span class="line"><span class="comment"># 添加如下主机名称（要退役的节点）</span></span><br><span class="line">hadoop105</span><br><span class="line"></span><br><span class="line"><span class="comment"># 在NameNode的hdfs-site.xml配置文件中增加dfs.hosts.exclude属性</span></span><br><span class="line">&lt;property&gt;</span><br><span class="line">&lt;name&gt;dfs.hosts.exclude&lt;/name&gt;</span><br><span class="line">&lt;value&gt;/opt/module/hadoop-3.1.3/etc/hadoop/dfs.hosts.exclude&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 刷新NameNode、刷新ResourceManager</span></span><br><span class="line">hdfs dfsadmin -refreshNodes</span><br><span class="line">Refresh nodes successful</span><br><span class="line"></span><br><span class="line">yarn rmadmin -refreshNodes</span><br><span class="line">17/06/24 14:55:56 INFO client.RMProxy: Connecting to ResourceManager at hadoop103/192.168.1.103:8033</span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查Web浏览器，退役节点的状态为decommission in progress（退役中），说明数据节点正在复制块到其他节点</span></span><br><span class="line"><span class="comment"># 等待退役节点状态为decommissioned（所有块已经复制完成），停止该节点及节点资源管理器。</span></span><br><span class="line"><span class="comment"># 注意：如果副本数是3，服役的节点小于等于3，是不能退役成功的，需要修改副本数后才能退役</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 退役之后就可以关闭，退役完成意味着数据备份完成，</span></span><br><span class="line"><span class="comment"># 注意这里不是群起，群关闭，所以只需要单独在需要操作的机器上关闭</span></span><br><span class="line">hdfs --daemon stop datanode</span><br><span class="line">stopping datanode</span><br><span class="line"></span><br><span class="line">sbin/yarn-daemon.sh stop nodemanager</span><br><span class="line">stopping nodemanager</span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果数据不均衡，可以用命令实现集群的再平衡</span></span><br><span class="line">sbin/start-balancer.sh </span><br><span class="line">starting balancer, logging to /opt/module/hadoop-3.1.3/logs/hadoop-atguigu-balancer-hadoop102.out</span><br><span class="line">Time Stamp               Iteration#  Bytes Already Moved  Bytes Left To Move  Bytes Being Moved</span><br><span class="line"><span class="comment"># 注意：不允许白名单和黑名单中同时出现同一个主机名称。</span></span><br></pre></td></tr></table></figure><h2 id="Datanode多-目录配置"><a href="#Datanode多-目录配置" class="headerlink" title="Datanode多****目录配置"></a><strong>Datanode多****目录配置</strong></h2><p>*<em>DataNode也可以配置成多个目录，<strong><strong>每个</strong></strong>目录存储的数据不一样。<strong><strong>即</strong></strong>：数据不是副本</em>*</p><p>配置namenode所在的机器的hdfs-site.xml，数据就会均匀的放在data&#x2F;data和data&#x2F;data2</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">property</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">name</span>&gt;</span>dfs.datanode.data.dir<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">value</span>&gt;</span>file:///$&#123;hadoop.tmp.dir&#125;/dfs/data1,file:///$&#123;hadoop.tmp.dir&#125;/dfs/data2<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br></pre></td></tr></table></figure><h1 id="Hadoop3新特性"><a href="#Hadoop3新特性" class="headerlink" title="Hadoop3新特性"></a>Hadoop3新特性</h1><ul><li>最低java版本由7升级为8</li><li>引入纠删码，默认3副本，开销较大，只是为了提高容错能力。纠删码在不到百分之50的数据冗余的情况下提供和3副本相同的容错机制，所以使用纠删码作为副本机制的改进</li><li>重写shell脚本。</li></ul>]]>
    </content>
    <id>https://www.chucz.asia/2026/04/09/Hadoop--HDFS/</id>
    <link href="https://www.chucz.asia/2026/04/09/Hadoop--HDFS/"/>
    <published>2026-04-09T06:42:38.000Z</published>
    <summary>
      <![CDATA[<p><font style="color:#F5222D;background-color:#FADB14;">注意机器启动过之后，同步的时候不要同步data文件夹</font></p>
<p><font]]>
    </summary>
    <title>Hadoop--HDFS</title>
    <updated>2026-04-09T06:42:38.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>褚成志</name>
    </author>
    <category term="后端" scheme="https://www.chucz.asia/categories/%E5%90%8E%E7%AB%AF/"/>
    <category term="Elasticsearch" scheme="https://www.chucz.asia/tags/Elasticsearch/"/>
    <category term="搜索引擎" scheme="https://www.chucz.asia/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E/"/>
    <category term="大数据" scheme="https://www.chucz.asia/tags/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
    <content>
      <![CDATA[<h1 id="ElasticSearch的应用场景说明"><a href="#ElasticSearch的应用场景说明" class="headerlink" title="ElasticSearch的应用场景说明"></a>ElasticSearch的应用场景说明</h1><h2 id="全文检索能力"><a href="#全文检索能力" class="headerlink" title="全文检索能力"></a>全文检索能力</h2><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214614079-1224293799.png" alt="1653876850875-fa9b1b05-a2ca-4088-a2a1-1569ce3cfc5c.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214614623-1733986724.png" alt="1653876875462-180b7f84-1014-4f73-824a-632e28c4d09c.png"></p><h2 id="日志存储分析能力"><a href="#日志存储分析能力" class="headerlink" title="日志存储分析能力"></a>日志存储分析能力</h2><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214614941-1929449235.png" alt="1653877095390-cf2cb51b-d735-4821-b7d7-04341d80bebb.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214615204-754768031.png" alt="1653877085961-3e4f883e-6deb-44ff-b4dd-3c0afaeb0b2c.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214615452-1573133038.png" alt="1653877160819-db78872f-0df7-4734-bc41-2ae863f6f0d2.png"></p><h2 id="数据存储（用的比较少）"><a href="#数据存储（用的比较少）" class="headerlink" title="数据存储（用的比较少）"></a>数据存储（用的比较少）</h2><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214615637-676370686.png" alt="1653877267116-d3e4989d-f26d-4f0b-9b25-8bc1c4b5b5fd.png"></p><h1 id="全文检索"><a href="#全文检索" class="headerlink" title="全文检索"></a>全文检索</h1><h2 id="什么是全文检索"><a href="#什么是全文检索" class="headerlink" title="什么是全文检索"></a>什么是全文检索</h2><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214615880-518006112.png" alt="1653877380278-33440c7f-416a-456e-8e0d-049751584977.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214616152-1185177488.png" alt="1653877448509-3926ed99-23b8-4e44-944f-3ff3f22f43de.png"></p><p>存在索引关键字，就是命中文档</p><p>使用关键字就可以搜索对应的文档数据</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214616346-1481982117.png" alt="1653877469999-acfb1e17-9f78-426a-8b6e-89d605215886.png"></p><h2 id="检索算法"><a href="#检索算法" class="headerlink" title="检索算法"></a>检索算法</h2><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214616561-1997199506.png" alt="1653877574684-cdaa37cc-9cfd-4bc3-89f3-ab6f40405d7d.png"></p><h2 id="倒排索引"><a href="#倒排索引" class="headerlink" title="倒排索引"></a>倒排索引</h2><p>先将非结构化数据转换为结构化数据，之后使用关键字建立索引</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214616879-104737163.png" alt="1653877700948-1ae42bf8-2103-453f-89d5-9044fd79b97b.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214617096-2096106591.png" alt="1653877747697-51dd759e-7cba-422e-85a1-92885aa9949f.png"></p><h2 id="全文检索的结构"><a href="#全文检索的结构" class="headerlink" title="全文检索的结构"></a>全文检索的结构</h2><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214617290-43808326.png" alt="1653877798197-5c015e05-0865-4780-a431-2eee6a3a981d.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214617499-1168385466.png" alt="1653877970691-c9e11f4a-8bfd-46a5-903e-99d476cb3bd4.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214617872-1565912800.png" alt="1653877959619-befc863c-7caa-4334-ac22-1d85e5d2d0f9.png"></p><h2 id="索引库结构"><a href="#索引库结构" class="headerlink" title="索引库结构"></a>索引库结构</h2><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214618180-283836457.png" alt="1653878090524-ab6f15d1-5690-4f64-b781-232d6e09ab4d.png"></p><h2 id="检索流程"><a href="#检索流程" class="headerlink" title="检索流程"></a>检索流程</h2><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214618375-744664547.png" alt="1653878209621-56612689-5977-43f4-8e55-f7253f6fe9de.png"></p><h1 id="ElasticSearch-实践与集群架构"><a href="#ElasticSearch-实践与集群架构" class="headerlink" title="ElasticSearch 实践与集群架构"></a>ElasticSearch 实践与集群架构</h1><h2 id="ES-集群架构"><a href="#ES-集群架构" class="headerlink" title="ES 集群架构"></a>ES 集群架构</h2><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214618647-1156323373.png" alt="1653878281148-950aa665-ff8f-48af-9c99-f6aa950bb914.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214618879-1487458532.png" alt="1653878286449-0387caca-9244-4b99-855f-4e81d95c4d95.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214619085-214272091.png" alt="1653878326670-5685c539-1f00-42b3-9143-2ddf37e04603.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214619315-737106671.png" alt="1653878443052-f92a6fe6-f168-409e-a0b2-ff04dde0176d.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214619500-612959074.png" alt="1653878451175-59452a14-22dc-4f59-9f11-7c5f85bfae02.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214619689-388855456.png" alt="1653878503289-ea7858aa-4d35-47cb-835f-e0593295f037.png"></p><h2 id="ES是如何进行分片存储的？"><a href="#ES是如何进行分片存储的？" class="headerlink" title="ES是如何进行分片存储的？"></a>ES是如何进行分片存储的？</h2><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214619868-1107806015.png" alt="1653878474419-569b8fb5-c787-4a98-ad75-7940d856c825.png"></p><p><strong>主分片和副本分片的关系：</strong></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214620146-1606868743.png" alt="1653878953925-89041a74-f744-46b4-8a0d-07231d36ce01.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214620422-496505522.png" alt="1653879034518-d93d430f-e0a1-4dd7-a238-dd3fddb1d810.png"></p><p><strong>数据在主分片上做写入，主从节点都可以进行数据写入和数据的读取。</strong></p><p><strong>主分片是读写，从分片只可以读不可以写。</strong></p><p><strong>分片和节点是不一样的。</strong></p><h2 id="节点类型"><a href="#节点类型" class="headerlink" title="节点类型"></a>节点类型</h2><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214620614-541674096.png" alt="1653878833372-de6e1375-402e-4088-9e3c-2c540fb8eae2.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214620845-1689203325.png" alt="1653878842588-41e23001-bcf0-4d99-b251-fc073a23d76f.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214621041-208264847.png" alt="1653878856370-8454f4c7-e9ea-4cf8-9745-09d756c717d0.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214621215-1318016101.png" alt="1653878869077-2dba8dc2-a0d7-4beb-a02d-f84060338d09.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214621416-660640795.png" alt="1653878904441-49d13a81-18f4-4199-b1dc-a657c559b2c3.png"></p><h2 id="ES集群故障转移"><a href="#ES集群故障转移" class="headerlink" title="ES集群故障转移"></a>ES集群故障转移</h2><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214621654-509457643.png" alt="1653878993171-b9755af5-6442-4c47-9c89-47704420f14f.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214621869-590656375.png" alt="1653879051137-02743f43-87e9-45dd-93e9-610ca4fdaf37.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214622187-1714434999.png" alt="1653879138881-580a520a-367a-4af0-8219-dde4c48b900e.png"></p><h2 id="ES横向扩容能力"><a href="#ES横向扩容能力" class="headerlink" title="ES横向扩容能力"></a>ES横向扩容能力</h2><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214622459-1153728108.png" alt="1653879182938-ea04e330-d7ee-478d-9a11-4e1ea1f245c8.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214622671-2041571920.png" alt="1653879205818-5f6d066b-dcb7-4d6e-bc05-80496f247a52.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214622880-850449402.png" alt="1653879223621-20230aca-5a45-4f81-b6e1-3fb440dffbba.png"></p><h2 id="ES-集群脑裂"><a href="#ES-集群脑裂" class="headerlink" title="ES 集群脑裂"></a>ES 集群脑裂</h2><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214623135-879305294.png" alt="1653879297068-3c684df4-30c6-41d6-a5e5-f116495ac0bd.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214623381-1770141300.png" alt="1653879329092-314d0a01-c989-44af-b702-b9a2e5b476da.png"></p><h2 id="文档读写路由"><a href="#文档读写路由" class="headerlink" title="文档读写路由"></a>文档读写路由</h2><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214623662-2092937807.png" alt="1653879476872-01177eca-e872-465f-be38-b55186f3b580.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214623948-1311693182.png" alt="1653879505156-f92597f4-cd03-4e70-beec-b6ad0892b4da.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214624237-518794713.png" alt="1653879583403-662cad1c-95bb-4728-ac45-2ba167f7ee91.png"></p>]]>
    </content>
    <id>https://www.chucz.asia/2026/04/09/ElasticSearch/</id>
    <link href="https://www.chucz.asia/2026/04/09/ElasticSearch/"/>
    <published>2026-04-09T06:42:38.000Z</published>
    <summary>
      <![CDATA[<h1 id="ElasticSearch的应用场景说明"><a href="#ElasticSearch的应用场景说明" class="headerlink"]]>
    </summary>
    <title>ElasticSearch</title>
    <updated>2026-04-09T06:42:38.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>褚成志</name>
    </author>
    <category term="后端" scheme="https://www.chucz.asia/categories/%E5%90%8E%E7%AB%AF/"/>
    <category term="Java" scheme="https://www.chucz.asia/tags/Java/"/>
    <category term="开发工具" scheme="https://www.chucz.asia/tags/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"/>
    <category term="热部署" scheme="https://www.chucz.asia/tags/%E7%83%AD%E9%83%A8%E7%BD%B2/"/>
    <content>
      <![CDATA[<ul><li>热加载则是在运行时通过重新加载class改变类信息，直接改变程序行为。<ul><li>**主要依赖java的类加载机制，在实现方式可以概括为在容器启动的时候起一条后台线程，定时的检测类文件的时间戳变化，如果类的时间戳变掉了，则将类重新载入。**对比反射机制，反射是在运行时获取类信息，通过动态的调用来改变程序行为；</li></ul></li><li>热部署就是在服务器运行时重新部署项目，<ul><li>直接重新加载整个应用，这种方式会释放内存，比热加载更加干净彻底，但同时也更费时间。</li></ul></li></ul><ol><li>JRebel 加载的速度优于 devtools</li><li>JRebel 不仅仅局限于 Spring Boot 项目，可以用在任何的 Java 项目中。</li><li>devtools 方式的热部署在功能上有限制，方法内的修改可以实现热部署，但新增的方法或者修改方法参数之后热部署是不生效的。</li></ol><h1 id="JRebel"><a href="#JRebel" class="headerlink" title="JRebel"></a>JRebel</h1><p>JRebel 可实现热加载，节省了大量重启时间，提高了个人开发效率。</p><p>虚拟机插件，即时分别看到<strong>类和资源</strong>的变化，直接反应在部署好的应用程序上，从而跳过了构建和部署的过程</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251014224808503-880767066.png" alt="1638962106571-129c794a-fa26-48ea-b974-3a3fa2ebb224.png"></p><h2 id="激活"><a href="#激活" class="headerlink" title="激活"></a><font style="color:rgb(79, 79, 79);">激活</font></h2><p>安装之后会提示输入license激活。使用下面的网址生成服务器地址GUID</p><p><a href="https://www.guidgen.com/">https://www.guidgen.com/</a></p><p><font style="color:rgb(77, 77, 77);">如果失效刷新GUID替换就可以！</font></p><p><font style="color:rgb(77, 77, 77);"></font></p><p><font style="color:rgb(77, 77, 77);">选择Team URL的方式激活</font></p><p><font style="color:rgb(77, 77, 77);">服务器地址：</font><code>&lt;font style=&quot;color:rgb(77, 77, 77);&quot;&gt;https://jrebel.qekang.com/{GUID}&lt;/font&gt;</code></p><p>邮箱：<code>自己的邮箱即可</code></p><p>之后按照提示操作即可。</p><p><font style="color:rgb(77, 77, 77);">通过JRebel启动项目。通过快捷键 </font><strong><font style="color:rgb(77, 77, 77);">Ctrl+shift+F9</font></strong><font style="color:rgb(77, 77, 77);"> 或者 command + S 使得修改生效。</font></p><p><font style="color:rgb(77, 77, 77);"></font></p><h2 id="相关设置"><a href="#相关设置" class="headerlink" title="相关设置"></a><font style="color:rgb(77, 77, 77);">相关设置</font></h2><p>离线工作模式，Work offline</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251014224809086-304317522.png" alt="1638962356999-8e97956c-47b2-48d2-9612-cd5b45b5bd22.png"></p><p>设置自动编译</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251014224809712-582560995.png" alt="1638962407325-8fd959cb-f114-42a9-a5a8-bf42318fea3a.png"></p><h2 id="支持下面的这些类型的文件改变："><a href="#支持下面的这些类型的文件改变：" class="headerlink" title="支持下面的这些类型的文件改变："></a>支持下面的这些类型的文件改变：</h2><ul><li>改变Java classes文件.</li><li>改变框架配置文件 (e.g. Spring XML files and annotations, Struts mappings, etc).</li><li>任何静态资源文件 (e.g. JSPs, HTMLs, CSSs, XMLs, .properties, etc)</li></ul><h1 id="devtool"><a href="#devtool" class="headerlink" title="devtool"></a>devtool</h1><p><a href="https://blog.csdn.net/u013042707/article/details/78648259">https://blog.csdn.net/u013042707/article/details/78648259</a></p><p>原理是在发现代码有更改之后，重新启动应用，但是比速度比手动停止后再启动还要更快，更快指的不是节省出来的手工操作的时间。</p><p>其深层原理是使用了两个ClassLoader，一个Classloader加载那些不会改变的类（第三方Jar包），另一个ClassLoader加载会更改的类，称为 restart ClassLoader,这样在有代码更改的时候，原来的restart ClassLoader 被丢弃，重新创建一个restart ClassLoader，由于需要加载的类相比较少，所以实现了较快的重启时间（5秒以内）。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">       <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-devtools<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">       <span class="tag">&lt;<span class="name">optional</span>&gt;</span>true<span class="tag">&lt;/<span class="name">optional</span>&gt;</span></span><br><span class="line">       <span class="tag">&lt;<span class="name">scope</span>&gt;</span>true<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">finalName</span>&gt;</span>www.fitness.manager.com<span class="tag">&lt;/<span class="name">finalName</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!--用于将应用打成可直接运行的jar（该jar就是用于生产环境中的jar） 值得注意的是，</span></span><br><span class="line"><span class="comment">如果没有引用spring-boot-starter-parent做parent，且采用了上述的第二种方式，</span></span><br><span class="line"><span class="comment">这里也要做出相应的改动 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!--fork:  如果没有该项配置，devtools不会起作用，即应用不会restart --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">fork</span>&gt;</span>true<span class="tag">&lt;/<span class="name">fork</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br></pre></td></tr></table></figure><h1 id="参考链接："><a href="#参考链接：" class="headerlink" title="参考链接："></a>参考链接：</h1><p><a href="https://www.cnblogs.com/sfnz/p/14157833.html?ivk_sa=1024320u">https://www.cnblogs.com/sfnz/p/14157833.html?ivk_sa&#x3D;1024320u</a></p><p><a href="https://blog.csdn.net/lianghecai52171314/article/details/105637251">https://blog.csdn.net/lianghecai52171314/article/details/105637251</a></p><p><a href="https://blog.csdn.net/weixin_44233253/article/details/118788185">https://blog.csdn.net/weixin_44233253&#x2F;article&#x2F;details&#x2F;118788185</a></p>]]>
    </content>
    <id>https://www.chucz.asia/2026/04/09/IDE%E7%83%AD%E5%8A%A0%E8%BD%BD%E4%B8%8E%E7%83%AD%E9%83%A8%E7%BD%B2/</id>
    <link href="https://www.chucz.asia/2026/04/09/IDE%E7%83%AD%E5%8A%A0%E8%BD%BD%E4%B8%8E%E7%83%AD%E9%83%A8%E7%BD%B2/"/>
    <published>2026-04-09T06:42:38.000Z</published>
    <summary>
      <![CDATA[<ul>
<li>热加载则是在运行时通过重新加载class改变类信息，直接改变程序行为。<ul>
<li>**主要依赖java的类加载机制，在实现方式可以概括为在容器启动的时候起一条后台线程，定时的检测类文件的时间戳变化，如果类的时间戳变掉了，则将类重新载入。**对比反射机制，反]]>
    </summary>
    <title>IDE热加载与热部署</title>
    <updated>2026-04-09T06:42:38.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>褚成志</name>
    </author>
    <category term="云原生" scheme="https://www.chucz.asia/categories/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    <category term="监控" scheme="https://www.chucz.asia/tags/%E7%9B%91%E6%8E%A7/"/>
    <category term="Elasticsearch" scheme="https://www.chucz.asia/tags/Elasticsearch/"/>
    <content>
      <![CDATA[<h2 id="视图预览"><a href="#视图预览" class="headerlink" title="视图预览"></a>视图预览</h2><p>阿里云 Elasticsearch 指标展示，包括集群状态，索引QPS，节点 CPU&#x2F;内存&#x2F;磁盘使用率等</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160221865-1493026293.png" alt="1642041545162-a9885716-a959-4a2e-ac77-36315b541791.png"></p><h2 id="版本支持"><a href="#版本支持" class="headerlink" title="版本支持"></a><font style="color:#595959;">版本支持</font></h2><p><font style="color:#595959;">操作系统支持：Linux </font></p><h2 id="前置条件"><a href="#前置条件" class="headerlink" title="前置条件"></a>前置条件</h2><ul><li>服务器 &lt;<a href="https://www.yuque.com/dataflux/datakit/datakit-install">安装 Datakit</a>&gt;</li><li>服务器 &lt;<a href="https://www.yuque.com/dataflux/func/quick-start">安装 Func 携带版</a>&gt;</li><li>阿里云 RAM 访问控制账号授权</li></ul><h3 id="RAM-访问控制"><a href="#RAM-访问控制" class="headerlink" title="RAM 访问控制"></a>RAM 访问控制</h3><ol><li>登录 RAM 控制台  <a href="https://ram.console.aliyun.com/users">https://ram.console.aliyun.com/users</a></li><li>新建用户：人员管理 - 用户 - 创建用户</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160222249-2002060240.png" alt="1627893591261-ed0721f4-85d0-44a2-9b66-fe2bcb3bf41c.png"></p><ol start="3"><li>保存或下载 <strong>AccessKey</strong> <strong>ID</strong> 和 <strong>AccessKey Secret</strong> 的 CSV 文件 (配置文件会用到)</li><li>用户授权 (云监控只读&#x2F;时序指标数据权限)</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160222431-1286392587.png" alt="1627893756593-a6f5114d-309e-419a-96b2-e3637f0028c7.png"></p><h2 id="安装配置"><a href="#安装配置" class="headerlink" title="安装配置"></a>安装配置</h2><p><font style="color:#595959;">说明：</font></p><ul><li><font style="color:#595959;">示例 Linux 版本为：CentOS Linux release 7.8.2003 (Core)</font></li><li><font style="color:#595959;">通过一台服务器采集所有阿里云 Elasticsearch 数据</font></li></ul><h3 id="部署实施"><a href="#部署实施" class="headerlink" title="部署实施"></a>部署实施</h3><h4 id="脚本市场"><a href="#脚本市场" class="headerlink" title="脚本市场"></a>脚本市场</h4><ol><li>登录 Func，地址 <a href="http://ip:8088/">http://ip:8088</a></li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160222648-451209032.png" alt="1639115383741-ad518ea3-5206-4e62-a6a2-d14fdf1b8f4e.png"></p><ol start="2"><li>开启脚本市场，管理 - 实验性功能 - 开启脚本市场模块</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160222949-962626492.png" alt="1639115461724-ce238618-34e5-453a-bed4-18504ad89ecf.png"></p><ol start="3"><li>载入阿里云数据同步脚本，管理 - 脚本市场 - 阿里云数据同步 (云监控)</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160223136-1097599343.png" alt="1639115605557-ad655a12-e075-4853-9e66-d580063bc39a.png"></p><h4 id="添加脚本"><a href="#添加脚本" class="headerlink" title="添加脚本"></a>添加脚本</h4><ol><li>阿里云数据同步 (云监控) - 添加脚本</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160223327-231962787.png" alt="1646811929850-60252f92-950a-4865-84bf-5ff8f0c4d7ea.png"></p><ol start="2"><li>输入标题&#x2F;描述信息</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160223537-1114181733.png" alt="1642041650267-0666ec36-f108-4ca7-958c-40727ec13af5.png"></p><ol start="3"><li>复制代码，从 (同步阿里云监控数据) 到当前脚本</li><li>修改阿里云账号配置 (Ram 访问控制)</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#x27;aliyun_ak_id&#x27;    : &#x27;AccessKey ID&#x27;,</span><br><span class="line">&#x27;aliyun_ak_secret&#x27;: &#x27;AccessKey Secret&#x27;,</span><br></pre></td></tr></table></figure><ol start="5"><li>修改阿里云 Elasticsearch 指标</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#x27;metric_targets&#x27;: [</span><br><span class="line">    &#123;</span><br><span class="line">        &#x27;namespace&#x27;: &#x27;acs_elasticsearch&#x27;,</span><br><span class="line">        &#x27;metrics&#x27;: &#x27;ALL&#x27;</span><br><span class="line">     &#125;           </span><br><span class="line">                  ]</span><br></pre></td></tr></table></figure><ol start="6"><li>**保存 **配置并 <strong>发布</strong></li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160223731-1705374275.png" alt="1639117330715-7736eb23-9b6e-4c70-a9f7-9a92698c60c7.png"></p><h4 id="定时任务"><a href="#定时任务" class="headerlink" title="定时任务"></a>定时任务</h4><ol><li>添加自动触发任务，管理 - 自动触发配置 - 新建任务</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160223907-1900003091.png" alt="1639117392729-b4d53ad3-e62c-487d-89a6-fd1458243682.png"></p><ol start="2"><li>自动触发配置，执行函数中添加此脚本，其他默认即可</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160224094-1985328848.png" alt="1642041790707-27ccc6ef-f338-4314-9766-aba11b0cf934.png"></p><ol start="3"><li>指标预览</li></ol><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160224312-836252580.png" alt="1642041822081-941bb62b-e4ad-4dd0-a70e-eb437fcbc464.png"></p><h2 id="场景视图"><a href="#场景视图" class="headerlink" title="场景视图"></a>场景视图</h2><p>&lt;场景 - 新建仪表板 - 内置模板库 - 阿里云 Elasticsearch&gt;</p><h2 id="监控规则"><a href="#监控规则" class="headerlink" title="监控规则"></a>监控规则</h2><p>&lt;监控 - 模板新建 - 阿里云 Elasticsearch&gt;</p><h2 id="指标详解"><a href="#指标详解" class="headerlink" title="指标详解"></a>指标详解</h2><p>&lt;<a href="https://help.aliyun.com/document_detail/165026.htm?spm=a2c4g.11186623.0.0.43b91c27IVXM20#concept-2495562">阿里云 Elasticsearch 指标列表</a>&gt;</p><h2 id="常见问题排查"><a href="#常见问题排查" class="headerlink" title="常见问题排查"></a>常见问题排查</h2><ul><li>查看日志：Func 日志路径 &#x2F;usr&#x2F;local&#x2F;dataflux-func&#x2F;data&#x2F;logs&#x2F;dataflux-func.log</li><li>代码调试：选择主函数，直接运行 (可以看到脚本输出)</li></ul><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160224534-832979105.png" alt="1639118030344-b9e32474-c580-45cc-8ea3-67fc962be137.png"></p><ul><li>连接配置：Func 无法连接 Datakit，请检查数据源配置</li></ul><p><img src="https://cdn.chucz.asia/blog/3703820-20251021160224737-1152102848.png" alt="1640337156764-a214db06-5150-472b-8905-03a81b430d86.png"></p>]]>
    </content>
    <id>https://www.chucz.asia/2026/04/09/Elasticsearch%E6%8C%87%E6%A0%87%E7%9B%91%E6%8E%A7/</id>
    <link href="https://www.chucz.asia/2026/04/09/Elasticsearch%E6%8C%87%E6%A0%87%E7%9B%91%E6%8E%A7/"/>
    <published>2026-04-09T06:42:38.000Z</published>
    <summary>
      <![CDATA[<h2 id="视图预览"><a href="#视图预览" class="headerlink" title="视图预览"></a>视图预览</h2><p>阿里云 Elasticsearch 指标展示，包括集群状态，索引QPS，节点]]>
    </summary>
    <title>Elasticsearch指标监控</title>
    <updated>2026-04-09T06:42:38.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>褚成志</name>
    </author>
    <category term="后端" scheme="https://www.chucz.asia/categories/%E5%90%8E%E7%AB%AF/"/>
    <category term="算法" scheme="https://www.chucz.asia/tags/%E7%AE%97%E6%B3%95/"/>
    <category term="链表" scheme="https://www.chucz.asia/tags/%E9%93%BE%E8%A1%A8/"/>
    <category term="LeetCode" scheme="https://www.chucz.asia/tags/LeetCode/"/>
    <content>
      <![CDATA[<p>在一次翻转完成之后</p><p>nxt.next &#x3D; cur 这一次翻转的尾节点应指向下一次的头节点</p><p>p0.next &#x3D; pre 上次翻转的尾节点应指向这次翻转的头节点</p><p>p0 变为这次翻转后的尾节点</p>]]>
    </content>
    <id>https://www.chucz.asia/2026/04/09/K%20%E4%B8%AA%E4%B8%80%E7%BB%84%E7%BF%BB%E8%BD%AC%E9%93%BE%E8%A1%A8/</id>
    <link href="https://www.chucz.asia/2026/04/09/K%20%E4%B8%AA%E4%B8%80%E7%BB%84%E7%BF%BB%E8%BD%AC%E9%93%BE%E8%A1%A8/"/>
    <published>2026-04-09T06:42:38.000Z</published>
    <summary>
      <![CDATA[<p>在一次翻转完成之后</p>
<p>nxt.next &#x3D; cur 这一次翻转的尾节点应指向下一次的头节点</p>
<p>p0.next &#x3D; pre 上次翻转的尾节点应指向这次翻转的头节点</p>
<p>p0]]>
    </summary>
    <title>K 个一组翻转链表</title>
    <updated>2026-04-09T06:42:38.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>褚成志</name>
    </author>
    <category term="大数据" scheme="https://www.chucz.asia/categories/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
    <category term="大数据" scheme="https://www.chucz.asia/tags/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
    <category term="Hadoop" scheme="https://www.chucz.asia/tags/Hadoop/"/>
    <category term="MapReduce" scheme="https://www.chucz.asia/tags/MapReduce/"/>
    <content>
      <![CDATA[<p>dr.who是通过http连接的默认用户，可以直接在配置文件里面修改为当前用户，重启之后就可以使用当前用户在页面里面对文件进行相关操作。</p><h1 id="MapReduce概述"><a href="#MapReduce概述" class="headerlink" title="MapReduce概述"></a>MapReduce概述</h1><p>分布式<font style="color:#FF0000;">运算</font>程序的编程框架，是用户开发“基于Hadoop的数据分析应用”的核心框架。</p><p>MapReduce核心功能是将<font style="color:#FF0000;">用户编写的业务逻辑代码</font>和<font style="color:#FF0000;">自带默认组件</font>整合成一个完整的<font style="color:#FF0000;">分布式运算程序</font>，并发运行在一个Hadoop集群上。</p><h2 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h2><p>**MapReduce ****<font style="color:#F5222D;background-color:#FADB14;">易于编程</font>**<font style="color:#FF0000;">它简单的实现一些接口，就可以完成一个分布式程序，</font>这个分布式程序可以分布到大量廉价的PC机器上运行。</p><p><strong>好的扩展性</strong>当你的计算资源不能得到满足的时候，你可以通过<font style="color:#FF0000;">简单的增加机器</font>来扩展它的计算能力。</p><p><strong>高容错性</strong>MapReduce设计的初衷就是使程序能够部署在廉价的PC机器上，这就要求它具有很高的容错性。比如<font style="color:#FF0000;">其中一台机器挂了，它可以把上面的计算任务转移到另外一个节点上运行，不至于这个任务运行失败</font>，而且这个过程不需要人工参与，而完全是由Hadoop内部完成的。</p><p><strong>适合<strong><strong>PB</strong></strong>级以上海量数据的离线处理</strong>可以实现上千台服务器集群并发工作，提供数据处理能力。</p><h2 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h2><p><strong>不擅长实时计算</strong>MapReduce无法像MySQL一样，在毫秒或者秒级内返回结果。</p><p><strong>不擅长流式计算</strong>流式计算的输入数据是动态的，而MapReduce的<font style="color:#FF0000;">输入数据集是静态的</font>，不能动态变化。这是因为MapReduce自身的设计特点决定了数据源必须是静态的。</p><p><strong>不擅长**<strong>DAG</strong></strong>（有向图）计算**多个应用程序存在依赖关系，后一个应用程序的输入为前一个的输出。在这种情况下，MapReduce并不是不能做，而是使用后，<font style="color:#FF0000;">每个</font><font style="color:#FF0000;">MapReduce</font><font style="color:#FF0000;">作业的输出结果都会写入到磁盘，会造成大量的磁盘</font><font style="color:#FF0000;">IO</font><font style="color:#FF0000;">，导致性能非常的低下。</font></p><h2 id="MapReduce核心-思想"><a href="#MapReduce核心-思想" class="headerlink" title="MapReduce核心****思想"></a><strong><font style="color:#F5222D;background-color:#FADB14;">M</font><strong><strong><font style="color:#F5222D;background-color:#FADB14;">apReduce</font></strong></strong><font style="color:#F5222D;background-color:#FADB14;">核心</font>****<font style="color:#F5222D;background-color:#FADB14;">思想</font></strong></h2><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005631510-479134855.png" alt="1603382857992-b099b6b9-6556-4bb1-a1cc-e8da3ba31d61.png"><font style="color:#000000;"></font></p><p><font style="color:#F5222D;background-color:#FADB14;">map阶段</font><font style="color:#000000;">：</font><font style="color:#000000;">MapTask</font><font style="color:#000000;">并发实例，完全并行运行，互不相干。</font></p><p><font style="color:#F5222D;background-color:#FADB14;">reduce阶段</font><font style="color:#000000;">：</font><font style="color:#000000;">ReduceTask</font><font style="color:#000000;">并发实例互不相干，但是他们的数据依赖于上一个阶段的所有</font><font style="color:#000000;">MapTask</font><font style="color:#000000;">并发实例的输出。</font></p><p><font style="color:#000000;"></font></p><p><font style="color:#000000;">MapReduce</font><font style="color:#000000;">编程模型只能包含一个</font><font style="color:#000000;">Map</font><font style="color:#000000;">阶段和一个</font><font style="color:#000000;">Reduce</font><font style="color:#000000;">阶段，如果用户的业务逻辑非常复杂，那就只能多个</font><font style="color:#000000;">M</font><font style="color:#000000;">ap</font><font style="color:#000000;">R</font><font style="color:#000000;">educe程序，串行运行。</font></p><hr><p>一个完整的MapReduce程序在分布式运行时有三类实例进程：</p><p>（1）<strong>MrAppMaster</strong>：负责整个程序的过程调度及状态协调。</p><p>（2）<strong>MapTask</strong>：负责Map阶段的整个数据处理流程。</p><p>（3）<strong>ReduceTask</strong>：负责Reduce阶段的整个数据处理流程。</p><h3 id="直接使用官方的-Wordcount"><a href="#直接使用官方的-Wordcount" class="headerlink" title="直接使用官方的 Wordcount"></a>直接使用官方的 Wordcount</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[deltaqin@hadoop101 mapreduce]$ <span class="built_in">pwd</span>                                                                                                                        </span><br><span class="line">/opt/module/hadoop-3.1.3/share/hadoop/mapreduce</span><br><span class="line">[deltaqin@hadoop101 mapreduce]$ yarn jar hadoop-mapreduce-examples-3.1.3.jar wordcount /test.txt /output</span><br></pre></td></tr></table></figure><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005632195-561498702.png" alt="1605086621095-bb0a0bd5-3782-4721-823e-934d836d3651.png"></p><h2 id="常用数据-序列化类型"><a href="#常用数据-序列化类型" class="headerlink" title="常用数据****序列化类型"></a><strong>常用数据****序列化类型</strong></h2><p><strong><font style="color:#F5222D;background-color:#FADB14;">hadoop包装好的类型，想用必须使用包装好的类型</font></strong></p><hr><table><thead><tr><th><strong>Java类型</strong></th><th><strong>Hadoop Writable类型</strong></th></tr></thead><tbody><tr><td>Boolean</td><td>BooleanWritable</td></tr><tr><td>Byte</td><td>ByteWritable</td></tr><tr><td>Int</td><td>IntWritable</td></tr><tr><td>Float</td><td>FloatWritable</td></tr><tr><td>Long</td><td>LongWritable</td></tr><tr><td>Double</td><td>DoubleWritable</td></tr><tr><td><font style="color:#FF0000;">S</font><font style="color:#FF0000;">tring</font></td><td><font style="color:#FF0000;">Text</font></td></tr><tr><td>Map</td><td>MapWritable</td></tr><tr><td>Array</td><td>ArrayWritable</td></tr></tbody></table><h2 id="自己实现-WordC-ount"><a href="#自己实现-WordC-ount" class="headerlink" title="自己实现 WordC****ount"></a><strong>自己实现 W<strong><strong>ord</strong></strong>C****ount</strong></h2><p><font style="color:#000000;">有Map</font><font style="color:#000000;">类、</font><font style="color:#000000;">Reduce</font><font style="color:#000000;">类和</font><font style="color:#000000;">驱动类。</font><font style="color:#000000;">且</font><font style="color:#000000;">数据的类型是</font><font style="color:#000000;">Hadoop自身</font><font style="color:#000000;">封装的序列化</font><font style="color:#000000;">类型</font><font style="color:#000000;">。</font></p><p><font style="color:#000000;"></font></p><p><font style="color:#000000;">在给定的文本文件中统计输出每一个单词出现的总次数</font></p><p><font style="color:#000000;">按照</font><font style="color:#000000;">M</font><font style="color:#000000;">ap</font><font style="color:#000000;">R</font><font style="color:#000000;">educe编程</font><font style="color:#000000;">规范，分别编写</font><font style="color:#000000;">Mapper，Reducer，Driver。</font></p><h3 id="编写Mapper类"><a href="#编写Mapper类" class="headerlink" title="编写Mapper类"></a>编写Mapper类</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.deltqin;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.IntWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.LongWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Mapper;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 框架拿到内容变成kv给当前程序，输入类型由框架决定</span></span><br><span class="line"><span class="comment">// 将数据一行行，给该程序来处理,如何拿到数据不用管，只需要管业务（框架下编程）</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//LongWritable, Text 输入类型：行号，内容</span></span><br><span class="line"><span class="comment">//Text, IntWritable 输出类型：内容</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// LongWritable：开头在文件中位置，位置索引</span></span><br><span class="line"><span class="comment">// Text： 输入类型</span></span><br><span class="line"><span class="comment">// Text：输出类型</span></span><br><span class="line"><span class="comment">// IntWritable：对应个数</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WordCountMapper</span> <span class="keyword">extends</span> <span class="title class_">Mapper</span>&lt;LongWritable, Text, Text, IntWritable&gt; &#123;</span><br><span class="line"></span><br><span class="line"><span class="comment">//    map里面尽量不要生成对象，垃圾回收压力太大，降低性能</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">Text</span> <span class="variable">word</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Text</span>();</span><br><span class="line">    <span class="keyword">private</span> <span class="type">IntWritable</span> <span class="variable">one</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">IntWritable</span>(<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">//    框架给一些数据，处理之后交还给框架</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">map</span><span class="params">(LongWritable key, Text value, Context context)</span> <span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">String</span> <span class="variable">string</span> <span class="operator">=</span> value.toString();</span><br><span class="line">        String[] words = string.split(<span class="string">&quot; &quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">//        (单词, 1),只是数据类型转变，不负责数</span></span><br><span class="line">        <span class="keyword">for</span> (String word : words)&#123;</span><br><span class="line">            <span class="built_in">this</span>.word.set(word);</span><br><span class="line">            context.write(<span class="built_in">this</span>.word, <span class="built_in">this</span>.one);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="编写Reducer类"><a href="#编写Reducer类" class="headerlink" title="编写Reducer类"></a>编写Reducer类</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.deltqin;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.IntWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Reducer;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WordCountReducer</span> <span class="keyword">extends</span> <span class="title class_">Reducer</span>&lt;Text, IntWritable, Text, IntWritable&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">IntWritable</span> <span class="variable">intWritable</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">IntWritable</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 框架将mapper输出的内容处理，变成（单词，单词所有的1（可迭代变量）），同一单词个数相加</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key 单词</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> values 单词所有的1（可迭代变量）</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> context 任务自己</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> IOException</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> InterruptedException</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">reduce</span><span class="params">(Text key, Iterable&lt;IntWritable&gt; values, Context context)</span> <span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (IntWritable intWritable1: values)&#123;</span><br><span class="line">            sum += intWritable1.get();</span><br><span class="line">        &#125;</span><br><span class="line">        intWritable.set(sum);</span><br><span class="line">        context.write(key, intWritable);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="编写Driver驱动类"><a href="#编写Driver驱动类" class="headerlink" title="编写Driver驱动类"></a>编写Driver驱动类</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.deltqin;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.conf.Configuration;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.fs.Path;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.IntWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Job;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.lib.input.FileInputFormat;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WordCountDriver</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException, ClassNotFoundException, InterruptedException &#123;</span><br><span class="line">        <span class="comment">// 1 获取配置信息以及封装任务job实例</span></span><br><span class="line">        <span class="type">Configuration</span> <span class="variable">configuration</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Configuration</span>();</span><br><span class="line">        <span class="type">Job</span> <span class="variable">job</span> <span class="operator">=</span> Job.getInstance(configuration);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2 设置jar加载路径</span></span><br><span class="line">        job.setJarByClass(WordCountDriver.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3 设置map和reduce类</span></span><br><span class="line">        job.setMapperClass(WordCountMapper.class);</span><br><span class="line">        job.setReducerClass(WordCountReducer.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 4 设置map输出</span></span><br><span class="line">        job.setMapOutputKeyClass(Text.class);</span><br><span class="line">        job.setMapOutputValueClass(IntWritable.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 5 设置最终输出kv类型</span></span><br><span class="line">        job.setOutputKeyClass(Text.class);</span><br><span class="line">        job.setOutputValueClass(IntWritable.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 6 设置输入和输出路径</span></span><br><span class="line">        FileInputFormat.setInputPaths(job, <span class="keyword">new</span> <span class="title class_">Path</span>(args[<span class="number">0</span>]));</span><br><span class="line">        FileOutputFormat.setOutputPath(job, <span class="keyword">new</span> <span class="title class_">Path</span>(args[<span class="number">1</span>]));</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 7 提交</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">result</span> <span class="operator">=</span> job.waitForCompletion(<span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line">        System.exit(result ? <span class="number">0</span> : <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>mvn pacakage 打成jar包，然后拷贝到Hadoop集群中</p><p>在 Hadoop101 执行WordCount程序，jar包提交到集群运行。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn jar  wc.jar com.deltaqin.WordcountDriver /test.txt /output1</span><br></pre></td></tr></table></figure><p>com.deltaqin.WordcountDriver 注意使用类名的全类型引用</p><p>&#x2F;output1 后面是输入文件以及输出文件，输出路径必须是一个不存在的路径</p><h2 id="Hadoop序列化"><a href="#Hadoop序列化" class="headerlink" title="Hadoop序列化"></a>Hadoop序列化</h2><p>序列化不代表持久化</p><ul><li>序列化就是把内存中的对象，转换成字节序列（或其他数据传输协议）以便于存储到磁盘（持久化）和网络传输。序列化可以存储“活的”对象，可以将“活的”对象发送到远程计算机</li><li>反序列化就是将收到字节序列（或其他数据传输协议）或者是磁盘的持久化数据，转换成内存中的对象。</li></ul><p> </p><p><font style="color:#F5222D;">Java的序列化是一个重量级序列化框架（Serializable），一个对象被序列化后，会附带很多额外的信息（各种校验信息，Header，继承体系等）</font>，大数据数据量本来就大，<font style="color:#F5222D;">不便于在网络中高效传输</font>。所以，Hadoop自己开发了一套序列化机制（Writable）。</p><ul><li>紧凑快速：<font style="color:#F5222D;background-color:#FADB14;">只序列化必要的数据，开销小。</font></li><li>可扩展：随着通信协议升级而升级</li><li>互操作：支持多语言的交互</li></ul><p>注意上面的WordCount，<font style="color:#F5222D;background-color:#FADB14;">类型都是明确指定，一一设定的，不像java自己的序列化可以自动识别</font></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 3 设置map和reduce类</span></span><br><span class="line">job.setMapperClass(WordcountMapper.class);</span><br><span class="line">job.setReducerClass(WordcountReducer.class);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 4 设置map输出</span></span><br><span class="line">job.setMapOutputKeyClass(Text.class);</span><br><span class="line">job.setMapOutputValueClass(IntWritable.class);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 5 设置最终输出kv类型</span></span><br><span class="line">job.setOutputKeyClass(Text.class);</span><br><span class="line">job.setOutputValueClass(IntWritable.class);</span><br></pre></td></tr></table></figure><h3 id="自定义bean对象实现序列化接口（Writable）"><a href="#自定义bean对象实现序列化接口（Writable）" class="headerlink" title="自定义bean对象实现序列化接口（Writable）"></a>自定义bean对象实现序列化接口（Writable）</h3><p>统计号码流量</p><p><font style="color:#000000;">输入数据</font><font style="color:#000000;">格式：</font></p><table><thead><tr><th><font style="color:#000000;">7 </font><font style="color:#000000;"></font><font style="color:#000000;">13560436666</font><font style="color:#000000;"></font><font style="color:#000000;">120.196.100.99</font><font style="color:#000000;"></font><font style="color:#000000;"></font><font style="color:#000000;">1116</font><font style="color:#000000;"></font><font style="color:#000000;"></font><font style="color:#000000;"> 954</font><font style="color:#000000;"></font><font style="color:#000000;"></font><font style="color:#000000;"></font><font style="color:#000000;">200</font><br/><font style="color:#000000;">id</font><font style="color:#000000;"></font><font style="color:#000000;">手机</font><font style="color:#000000;">号码</font><font style="color:#000000;"></font><font style="color:#000000;"></font><font style="color:#000000;">网络</font><font style="color:#000000;">ip</font><font style="color:#000000;"></font><font style="color:#000000;"></font><font style="color:#000000;"></font><font style="color:#000000;">上行</font><font style="color:#000000;">流量</font><font style="color:#000000;"> </font><font style="color:#000000;"> </font><font style="color:#000000;">下行</font><font style="color:#000000;">流量</font><font style="color:#000000;">     网络状态</font><font style="color:#000000;">码</font></th></tr></thead></table><p><font style="color:#000000;">期望输出</font><font style="color:#000000;">数据格式</font></p><table><thead><tr><th><font style="color:#000000;">13560436666 </font><font style="color:#000000;"></font><font style="color:#000000;"></font><font style="color:#000000;">1116</font><font style="color:#000000;"></font><font style="color:#000000;"></font><font style="color:#000000;">      954 </font><font style="color:#000000;"></font><font style="color:#000000;"></font><font style="color:#000000;"></font><font style="color:#000000;">2070</font><br/><font style="color:#000000;">手机</font><font style="color:#000000;">号码</font><font style="color:#000000;"></font><font style="color:#000000;"></font><font style="color:#000000;">    </font><font style="color:#000000;">上行</font><font style="color:#000000;">流量</font><font style="color:#000000;"> </font><font style="color:#000000;">       </font><font style="color:#000000;">下行</font><font style="color:#000000;">流量</font><font style="color:#000000;"></font><font style="color:#000000;"></font><font style="color:#000000;">总</font><font style="color:#000000;">流量</font></th></tr></thead></table><ul><li>必须实现Writable接口</li><li>反序列化时，需要反射调用空参构造函数 super(); ，所以必须有空参构造（通过反射构造对象一般会使用无参构造器）</li><li>重写序列化方法 write(DataOutput out) </li><li>重写反序列化方法 readFields(DataInput in)，<font style="color:#FF0000;">注意反序列化的顺序和序列化的顺序完全一致</font></li><li>要想把结果显示在文件中，需要重写toString()，可用”\t”分开，方便后续用。</li><li>如果需要将自定义的bean放在key中传输，则还需要实现Comparable接口，因为MapReduce中的Shuffle过程要求对key必须能排序。</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">compareTo</span><span class="params">(FlowBean o)</span> &#123;</span><br><span class="line"><span class="comment">// 倒序排列，从大到小</span></span><br><span class="line"><span class="keyword">return</span> <span class="built_in">this</span>.sumFlow &gt; o.getSumFlow() ? -<span class="number">1</span> : <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.deltaqin;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.DataInput;</span><br><span class="line"><span class="keyword">import</span> java.io.DataOutput;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Writable;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 1 实现writable接口</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FlowBean</span> <span class="keyword">implements</span> <span class="title class_">Writable</span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> upFlow;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> downFlow;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> sumFlow;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//2  反序列化时，需要反射调用空参构造函数，所以必须有</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">FlowBean</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">FlowBean</span><span class="params">(<span class="type">long</span> upFlow, <span class="type">long</span> downFlow)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>();</span><br><span class="line">        <span class="built_in">this</span>.upFlow = upFlow;</span><br><span class="line">        <span class="built_in">this</span>.downFlow = downFlow;</span><br><span class="line">        <span class="built_in">this</span>.sumFlow = upFlow + downFlow;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">set</span><span class="params">(<span class="type">long</span> upFlow, <span class="type">long</span> downFlow)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.upFlow = upFlow;</span><br><span class="line">        <span class="built_in">this</span>.downFlow = downFlow;</span><br><span class="line">        <span class="built_in">this</span>.sumFlow = upFlow + downFlow;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//3  写序列化方法,将数据写到指定的地方</span></span><br><span class="line">    <span class="comment">// DataOutput 数据的容器</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">write</span><span class="params">(DataOutput out)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        out.writeLong(upFlow);</span><br><span class="line">        out.writeLong(downFlow);</span><br><span class="line">        out.writeLong(sumFlow);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//4 反序列化方法</span></span><br><span class="line">    <span class="comment">//5 反序列化方法读顺序必须和写序列化方法的写顺序必须一致</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">readFields</span><span class="params">(DataInput in)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="built_in">this</span>.upFlow  = in.readLong();</span><br><span class="line">        <span class="built_in">this</span>.downFlow = in.readLong();</span><br><span class="line">        <span class="built_in">this</span>.sumFlow = in.readLong();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> upFlow + <span class="string">&quot;\t&quot;</span> + downFlow + <span class="string">&quot;\t&quot;</span> + sumFlow;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">getUpFlow</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> upFlow;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setUpFlow</span><span class="params">(<span class="type">long</span> upFlow)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.upFlow = upFlow;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">getDownFlow</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> downFlow;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setDownFlow</span><span class="params">(<span class="type">long</span> downFlow)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.downFlow = downFlow;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">getSumFlow</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> sumFlow;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setSumFlow</span><span class="params">(<span class="type">long</span> sumFlow)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.sumFlow = sumFlow;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="编写Mapper类-1"><a href="#编写Mapper类-1" class="headerlink" title="编写Mapper类"></a>编写Mapper类</h3><p>输出类型是 FlowBean</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.deltaqin;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.LongWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Mapper;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FlowCountMapper</span> <span class="keyword">extends</span> <span class="title class_">Mapper</span>&lt;LongWritable, Text, Text, FlowBean&gt;&#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">FlowBean</span> <span class="variable">v</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FlowBean</span>();</span><br><span class="line">    <span class="type">Text</span> <span class="variable">k</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Text</span>();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">map</span><span class="params">(LongWritable key, Text value, Context context)</span><span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 1 获取一行</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">line</span> <span class="operator">=</span> value.toString();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2 切割字段</span></span><br><span class="line">        String[] fields = line.split(<span class="string">&quot;\t&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3 封装对象</span></span><br><span class="line">        <span class="comment">// 取出手机号码</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">phoneNum</span> <span class="operator">=</span> fields[<span class="number">1</span>];</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 取出上行流量和下行流量</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">upFlow</span> <span class="operator">=</span> Long.parseLong(fields[fields.length - <span class="number">3</span>]);</span><br><span class="line">        <span class="type">long</span> <span class="variable">downFlow</span> <span class="operator">=</span> Long.parseLong(fields[fields.length - <span class="number">2</span>]);</span><br><span class="line"></span><br><span class="line">        k.set(phoneNum);</span><br><span class="line">        v.set(downFlow, upFlow);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 4 写出</span></span><br><span class="line">        context.write(k, v);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="编写Reducer类-1"><a href="#编写Reducer类-1" class="headerlink" title="编写Reducer类"></a>编写Reducer类</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.deltaqin;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Reducer;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FlowCountReducer</span> <span class="keyword">extends</span> <span class="title class_">Reducer</span>&lt;Text, FlowBean, Text, FlowBean&gt; &#123;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="type">FlowBean</span> <span class="variable">flowBean01</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FlowBean</span>();</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">reduce</span><span class="params">(Text key, Iterable&lt;FlowBean&gt; values, Context context)</span><span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line"> </span><br><span class="line"><span class="type">long</span> <span class="variable">sum_upFlow</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"><span class="type">long</span> <span class="variable">sum_downFlow</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 1 遍历所用bean，将其中的上行流量，下行流量分别累加</span></span><br><span class="line"><span class="keyword">for</span> (FlowBean flowBean : values) &#123;</span><br><span class="line">sum_upFlow += flowBean.getUpFlow();</span><br><span class="line">sum_downFlow += flowBean.getDownFlow();</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 2 封装对象</span></span><br><span class="line">flowBean01.set(sum_upFlow,sum_downFlow);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3 写出</span></span><br><span class="line">context.write(key, flowBean01);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="编写Driver驱动类-1"><a href="#编写Driver驱动类-1" class="headerlink" title="编写Driver驱动类"></a>编写Driver驱动类</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.mapreduce.flowsum;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.conf.Configuration;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.fs.Path;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Job;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.lib.input.FileInputFormat;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FlowsumDriver</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IllegalArgumentException, IOException, ClassNotFoundException, InterruptedException &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 输入输出路径需要根据自己电脑上实际的输入输出路径设置</span></span><br><span class="line">        args = <span class="keyword">new</span> <span class="title class_">String</span>[] &#123; <span class="string">&quot;e:/input/inputflow&quot;</span>, <span class="string">&quot;e:/output1&quot;</span> &#125;;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 1 获取配置信息，或者job对象实例</span></span><br><span class="line">        <span class="type">Configuration</span> <span class="variable">configuration</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Configuration</span>();</span><br><span class="line">        <span class="type">Job</span> <span class="variable">job</span> <span class="operator">=</span> Job.getInstance(configuration);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 6 指定本程序的jar包所在的本地路径</span></span><br><span class="line">        job.setJarByClass(FlowsumDriver.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2 指定本业务job要使用的mapper/Reducer业务类</span></span><br><span class="line">        job.setMapperClass(FlowCountMapper.class);</span><br><span class="line">        job.setReducerClass(FlowCountReducer.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3 指定mapper输出数据的kv类型</span></span><br><span class="line">        job.setMapOutputKeyClass(Text.class);</span><br><span class="line">        job.setMapOutputValueClass(FlowBean.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 4 指定最终输出的数据的kv类型</span></span><br><span class="line">        job.setOutputKeyClass(Text.class);</span><br><span class="line">        job.setOutputValueClass(FlowBean.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 5 指定job的输入原始文件所在目录</span></span><br><span class="line">        FileInputFormat.setInputPaths(job, <span class="keyword">new</span> <span class="title class_">Path</span>(args[<span class="number">0</span>]));</span><br><span class="line">        FileOutputFormat.setOutputPath(job, <span class="keyword">new</span> <span class="title class_">Path</span>(args[<span class="number">1</span>]));</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 7 将job中配置的相关参数，以及job所用的java类所在的jar包， 提交给yarn去运行</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">result</span> <span class="operator">=</span> job.waitForCompletion(<span class="literal">true</span>);</span><br><span class="line">        System.exit(result ? <span class="number">0</span> : <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="MapReduce-框架原理"><a href="#MapReduce-框架原理" class="headerlink" title="MapReduce****框架原理"></a><strong>MapReduce****框架原理</strong></h1><p><strong>Map阶段：</strong></p><ul><li>MapTask.run 执行map阶段，<ul><li>调用Mapper的map方法</li></ul></li></ul><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005632411-719568009.png" alt="1603429907651-83c58552-730a-4f77-93f0-cbf354996b51.png"></p><h2 id="InputFormat数据输入"><a href="#InputFormat数据输入" class="headerlink" title="InputFormat数据输入"></a><strong>InputForma<strong><strong>t</strong></strong>数据输入</strong><font style="color:#000000;"></font></h2><p>一般分几份之后就会启动多少个MapTask来执行****</p><ul><li><strong>遍历文件，按最小切片大小生成</strong><strong><font style="color:#F5222D;">切片</font>****。</strong></li><li>**数据变成KV：**切片是在客户端完成。对每一个切片获取recordReader，在并行的mapper task完成，recordreader就可以变成KV。<font style="color:#F5222D;">实际切KV是</font><font style="color:#F5222D;">recordReader，不是切片实现的。</font></li><li><strong>输出给mapper</strong></li></ul><hr><h3 id="切片与MapTask并行度决定机制"><a href="#切片与MapTask并行度决定机制" class="headerlink" title="切片与MapTask并行度决定机制****"></a>切片与MapTask并行度决定机制****</h3><p>MapTask的并行度决定Map阶段的任务处理并发度，进而影响到整个Job的处理速度。<font style="color:#FF0000;"></font></p><hr><p>**数据（切）**<strong>块：</strong><font style="color:#000000;">Block</font><font style="color:#000000;">是</font><font style="color:#000000;">HDFS</font><font style="color:#F5222D;background-color:#FADB14;">物理</font><font style="color:#F5222D;background-color:#FADB14;">上</font><font style="color:#000000;">把数据分成一块一块。</font></p><p><strong>数据****切片：</strong><font style="color:#000000;">数据切片</font><font style="color:#000000;">只是</font><font style="color:#000000;">在</font><font style="color:#F5222D;background-color:#FADB14;">逻辑上</font><font style="color:#000000;">对输入</font><font style="color:#000000;">进行分片，</font><font style="color:#000000;">并不会在磁盘上将其切分成片进行存储。</font></p><p><font style="color:#F5222D;background-color:#FADB14;">按照块大小来切分数据（</font><font style="color:#F5222D;background-color:#40A9FF;">物理切块和逻辑切分对应起来</font><font style="color:#F5222D;background-color:#FADB14;">），就避免了原本在这个DN的数据还需要传递到其他DN上，</font><font style="color:#F5222D;background-color:#1890FF;">减少了网络传输</font><font style="color:#F5222D;background-color:#FADB14;">，更多的带宽留给shuffle</font></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005632644-1700275916.png" alt="1603430560784-61f15f3d-06d7-435a-8b48-fadf916da6bc.png"></p><h3 id="J-ob提交流程源码"><a href="#J-ob提交流程源码" class="headerlink" title="J****ob提交流程源码"></a><strong>J****ob提交流程源码</strong></h3><ul><li>jar包</li><li>切片信息</li><li>job配置的xml</li></ul><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005632889-1422828981.png" alt="1603435099140-35c6f83a-f031-43be-b078-ed1a3f0a977b.png"></p><p>调试源码：主要代码片段：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">waitForCompletion()</span><br><span class="line"></span><br><span class="line">submit();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 1建立连接</span></span><br><span class="line">connect();</span><br><span class="line"><span class="comment">// 1）创建提交Job的代理</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Cluster</span>(getConfiguration());</span><br><span class="line"><span class="comment">// （1）判断是本地yarn还是远程</span></span><br><span class="line">initialize(jobTrackAddr, conf); </span><br><span class="line"></span><br><span class="line"><span class="comment">// 2 提交job</span></span><br><span class="line">submitter.submitJobInternal(Job.<span class="built_in">this</span>, cluster)</span><br><span class="line">    <span class="comment">// 1）创建给集群提交数据的Stag路径</span></span><br><span class="line">    <span class="type">Path</span> <span class="variable">jobStagingArea</span> <span class="operator">=</span> JobSubmissionFiles.getStagingDir(cluster, conf);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2）获取jobid ，并创建Job路径</span></span><br><span class="line"><span class="type">JobID</span> <span class="variable">jobId</span> <span class="operator">=</span> submitClient.getNewJobID();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3）拷贝jar包到集群</span></span><br><span class="line">copyAndConfigureFiles(job, submitJobDir);</span><br><span class="line">rUploader.uploadFiles(job, jobSubmitDir);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 4）计算切片，生成切片规划文件</span></span><br><span class="line">writeSplits(job, submitJobDir);</span><br><span class="line">maps = writeNewSplits(job, jobSubmitDir);</span><br><span class="line">input.getSplits(job);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 5）向Stag路径写XML配置文件</span></span><br><span class="line">writeConf(conf, submitJobFile);</span><br><span class="line">conf.writeXml(out);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 6）提交Job,返回提交状态</span></span><br><span class="line">status = submitClient.submitJob(jobId, submitJobDir.toString(), job.getCredentials());</span><br></pre></td></tr></table></figure><h3 id="切片-源码"><a href="#切片-源码" class="headerlink" title="切片****源码"></a><strong>切片****源码</strong></h3><p><font style="color:#F5222D;">对应上面源码解读的第4步。</font></p><p>注意下面的1.1倍长才会切片1倍，怕浪费</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005633111-1378744956.png" alt="1603435284260-081801c4-82b3-4039-8bfb-ae99aedd12ac.png"></p><h3 id="FileInputFormat（抽象父类）切片机制"><a href="#FileInputFormat（抽象父类）切片机制" class="headerlink" title="FileInputFormat（抽象父类）切片机制"></a><strong>FileInputFormat（抽象父类）切片机制</strong></h3><p><strong><font style="color:#F5222D;background-color:#FADB14;">源码里面该抽象父类只实现了getSplits也就是上面的切片过程。</font></strong></p><p><strong><font style="color:#F5222D;background-color:#FADB14;">还有一个createRecordReader没有实现，需要他的特定子类实现，默认使用的是TextInputFormat</font></strong></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005633308-688480048.png" alt="1603435388892-ebe50218-c51b-4842-a7c4-36f3da7d2171.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005633550-468646287.png" alt="1603435411273-3c0a9630-94ac-4a20-ac73-e4060f88f14a.png"></p><h4 id="TextInputFormat的KV"><a href="#TextInputFormat的KV" class="headerlink" title="TextInputFormat的KV"></a>TextInputFormat的KV</h4><p><font style="color:#F5222D;background-color:#FADB14;">TextInputFormat是默认的FileInputFormat实现类。按行读取每条记录。</font>键是存储该行在整个文件中的起始字节偏移量， LongWritable类型。值是这行的内容，不包括任何行终止符（换行符和回车符），Text类型。 </p><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005633780-1424996269.png" alt="1603435913143-b5d9edbd-f427-4990-b449-5f9d9a9460db.png"></p><h4 id="KeyValueTextInputFormat的KV"><a href="#KeyValueTextInputFormat的KV" class="headerlink" title="KeyValueTextInputFormat的KV"></a>KeyValueTextInputFormat的KV</h4><p>每一行一条记录，分隔符分为KV，默认分隔符是tab </p><h4 id="NLineInputFormat的KV"><a href="#NLineInputFormat的KV" class="headerlink" title="NLineInputFormat的KV"></a>NLineInputFormat的KV</h4><p>map处理的不是按照block划分，而是按照指定的行数去划分 </p><h4 id="CombineTextInputFormat切片机制"><a href="#CombineTextInputFormat切片机制" class="headerlink" title="CombineTextInputFormat切片机制"></a>CombineTextInputFormat切片机制</h4><p>框架默认的TextInputFormat切片机制是对任务按文件规划切片，<font style="color:#FF0000;">不管文件多小</font><font style="color:#FF0000;">，</font><font style="color:#FF0000;">都会是一个单独的切</font><font style="color:#FF0000;">片</font>，都会交给一个MapTask，这样如果有大量小文件，就<font style="color:#FF0000;">会</font><font style="color:#FF0000;">产生大量的</font><font style="color:#FF0000;">MapTask</font>，处理效率极其低下。</p><p>CombineTextInputFormat用于小文件过多的场景，它可以将多个小文件从逻辑上规划到一个切片中，这样，多个小文件就可以交给一个MapTask处理。</p><hr><p>CombineTextInputFormat.setMaxInputSplitSize(job, <font style="color:#FF0000;">4194304</font>);&#x2F;&#x2F; 4m</p><p>注意：虚拟存储切片最大值设置最好根据实际的小文件大小情况来设置具体的值。</p><hr><p>生成切片过程包括：虚拟存储过程和切片过程二部分。</p><p>（1）虚拟存储过程：</p><p>将输入目录下所有文件大小，依次和设置的setMaxInputSplitSize值比较，如果不大于设置的最大值，逻辑上划分一个块。如果输入文件大于设置的最大值且大于两倍，那么以最大值切割一块；<font style="color:#FF0000;">当剩余数据大小超过</font><font style="color:#FF0000;">设置的</font><font style="color:#FF0000;">最大值且不大于</font><font style="color:#FF0000;">最大</font><font style="color:#FF0000;">值2倍，此时将文件均分成2个虚拟存储块（防止出现</font><font style="color:#FF0000;">太</font><font style="color:#FF0000;">小切片）</font><font style="color:#FF0000;">。</font></p><p>例如setMaxInputSplitSize值为4M，输入文件大小为8.02M，则先逻辑上分成一个4M。剩余的大小为4.02M，如果按照4M逻辑划分，就会出现0.02M的小的虚拟存储文件，所以将剩余的4.02M文件切分成（2.01M和2.01M）两个文件。</p><p>（2）切片过程：</p><p>（a）判断虚拟存储的文件大小是否大于setMaxInputSplitSize值，大于等于则单独形成一个切片。</p><p>（b）如果不大于则跟下一个虚拟存储文件进行合并，共同形成一个切片。</p><p><font style="color:#FF0000;">（c）</font><font style="color:#FF0000;">测试举例：有4个小文件大小分别为1.7M、5.1M、3.4M以及6.8M这四个小文件，则虚拟存储之后形成6个文件块，大小分别为：</font></p><p><font style="color:#FF0000;">1.7M，（2.55M、2.55M）</font><font style="color:#FF0000;">，</font><font style="color:#FF0000;">3.4M以及（3.4M、3.4M）</font></p><p><font style="color:#FF0000;">最终会形成3个切片，大小分别为</font><font style="color:#FF0000;">：</font></p><p><font style="color:#FF0000;">（1.7+2.55）M</font><font style="color:#FF0000;">，</font><font style="color:#FF0000;">（2.55+3.4）M</font><font style="color:#FF0000;">，</font><font style="color:#FF0000;">（3.4+3.4）M</font></p><h4 id="CombineTextInputFormat案例实操"><a href="#CombineTextInputFormat案例实操" class="headerlink" title="CombineTextInputFormat案例实操"></a>CombineTextInputFormat案例实操</h4><p>将输入的大量小文件合并成一个切片统一处理。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//不做任何处理，运行1.6节的WordCount案例程序，观察切片个数为4。</span></span><br><span class="line"> </span><br><span class="line"><span class="comment">//在WordcountDriver中增加如下代码，运行程序，并观察运行的切片个数为3。</span></span><br><span class="line"><span class="comment">//驱动类中添加代码如下：</span></span><br><span class="line"><span class="comment">// 如果不设置InputFormat，它默认用的是TextInputFormat.class</span></span><br><span class="line">job.setInputFormatClass(CombineTextInputFormat.class);</span><br><span class="line"> </span><br><span class="line"><span class="comment">//虚拟存储切片最大值设置4m</span></span><br><span class="line">CombineTextInputFormat.setMaxInputSplitSize(job, <span class="number">4194304</span>);</span><br><span class="line"><span class="comment">//运行为3个切片。</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">//在WordcountDriver中增加如下代码，运行程序，并观察运行的切片个数为1。</span></span><br><span class="line"><span class="comment">//驱动中添加代码如下：</span></span><br><span class="line"><span class="comment">// 如果不设置InputFormat，它默认用的是TextInputFormat.class</span></span><br><span class="line">job.setInputFormatClass(CombineTextInputFormat.class);</span><br><span class="line"> </span><br><span class="line"><span class="comment">//虚拟存储切片最大值设置20m</span></span><br><span class="line">CombineTextInputFormat.setMaxInputSplitSize(job, <span class="number">20971520</span>);</span><br><span class="line"><span class="comment">//运行如果为1个切片。</span></span><br></pre></td></tr></table></figure><h3 id="自定义inputFormat"><a href="#自定义inputFormat" class="headerlink" title="自定义inputFormat"></a>自定义inputFormat</h3><p>继承一些东西（RecordReader），实现一些，重写一些方法</p><ul><li>初始化</li><li>是否读到</li><li>读取K</li><li>读取V</li><li>获取进度</li><li>关闭****</li></ul><h2 id="Shuffle机制"><a href="#Shuffle机制" class="headerlink" title="Shuffle机制"></a><strong>S<strong><strong>huffle机制</strong></strong></strong></h2><p>Map方法之后，Reduce方法之前的数据处理过程称之为Shuffle。</p><h3 id="MapReduce工作流程"><a href="#MapReduce工作流程" class="headerlink" title="MapReduce工作流程****"></a>MapReduce工作流程****</h3><h4 id="MapTask工作机制"><a href="#MapTask工作机制" class="headerlink" title="MapTask工作机制"></a>MapTask工作机制</h4><p><strong>7往缓存区里面写东西，写满之后才会输出，溢出为文件</strong></p><p><strong><font style="color:#F5222D;background-color:#FADB14;">其中第9步使用的快排，全部在内存完成，</font></strong></p><p><strong><font style="color:#F5222D;background-color:#FADB14;">局部排序可以使用快排</font></strong></p><p><strong><font style="color:#F5222D;background-color:#FADB14;">归并不需要全部在内存里面，两个指针逐渐遍历即可，没必要全进来内存，但是快排必须全部到内存里面，所以这里最后使用归并而不是快排。</font></strong></p><p>最后每个task都输出一个有序文件。</p><p>多个有序文件再归并变成一个有序文件，给reduce <img src="https://cdn.chucz.asia/blog/3703820-20251013005634073-573925083.png" alt="1603435963914-2ab50efa-41ee-438e-8082-cab7ab6362b7.png"><img src="https://cdn.chucz.asia/blog/3703820-20251013005634342-1703844016.png" alt="1603443600046-da256ed5-f5b3-4099-83fd-cbe8028fe9a9.png"></p><ul><li>Read阶段：MapTask通过用户编写的RecordReader，从输入InputSplit中解析出一个个key&#x2F;value。</li><li><font style="color:#000000;">Map</font><font style="color:#000000;">阶段：该节点主要是将解析出的key</font><font style="color:#000000;">&#x2F;value交给</font><font style="color:#000000;">用户编写map()</font><font style="color:#000000;">函数</font><font style="color:#000000;">处理，并产生一系列新的key&#x2F;value。</font></li><li><font style="color:#000000;">Collect收集</font><font style="color:#000000;">阶段：在用户编写map()</font><font style="color:#000000;">函数</font><font style="color:#000000;">中，当数据处理完成后，一般会调用</font><font style="color:#000000;">Out</font><font style="color:#000000;">putCollector.collect()</font><font style="color:#000000;">输出</font><font style="color:#000000;">结果。在</font><font style="color:#000000;">该</font><font style="color:#000000;">函数内部，它会</font><font style="color:#000000;">将</font><font style="color:#000000;">生成的key&#x2F;value</font><font style="color:#000000;">分区</font><font style="color:#000000;">（</font><font style="color:#000000;">调用</font><font style="color:#000000;">Partitioner）</font><font style="color:#000000;">，并写入一个环形内存缓冲区中。</font></li><li><font style="color:#000000;">Spill</font><font style="color:#000000;">阶段：即“</font><font style="color:#000000;">溢</font><font style="color:#000000;">写”</font><font style="color:#000000;">，</font><font style="color:#000000;">当环形缓冲区满后，</font><font style="color:#000000;">MapReduce会</font><font style="color:#000000;">将数据写到本地磁盘上，生成一个临时文件。需要</font><font style="color:#000000;">注意</font><font style="color:#000000;">的是，将数据写入本地磁盘之前，先要对数据进行一次本地排序，并在必要</font><font style="color:#000000;">时</font><font style="color:#000000;">对数据进行合并</font><font style="color:#000000;">、压缩等操作。</font><ul><li><font style="color:#000000;">步骤1：</font><font style="color:#000000;">利用快速排序算法对缓存区内的数据进行排序，排序方式是，先按照分区编号Partition进行排序，然后按照key进行排序。这样</font><font style="color:#000000;">，经过排序后，数据以分区为单位聚集在一起，且同一分区内所有数据按照key有序。</font></li><li><font style="color:#000000;">步骤2：</font><font style="color:#000000;">按照分区编号由小到大依次将每个分区中的数据写入任务工作目录下的临时文件</font><font style="color:#000000;">output&#x2F;</font><font style="color:#000000;">spillN.out</font><font style="color:#000000;">（N</font><font style="color:#000000;">表示当前溢写次数</font><font style="color:#000000;">）中</font><font style="color:#000000;">。如果</font><font style="color:#000000;">用户</font><font style="color:#000000;">设置了Combiner，则写入文件之前，对每个分区中</font><font style="color:#000000;">的数据进行一次聚集操作。</font></li><li><font style="color:#000000;">步骤3：</font><font style="color:#000000;">将分区数据的元</font><font style="color:#000000;">信息</font><font style="color:#000000;">写到内存索引数据结构SpillRecord</font><font style="color:#000000;">中</font><font style="color:#000000;">，其中每个分区的元信息包括在临时文件中的偏移量、压缩前数据大小和压缩后数据大小。如果</font><font style="color:#000000;">当前</font><font style="color:#000000;">内存索引大小超过</font><font style="color:#000000;">1</font><font style="color:#000000;">MB，则将内存索引写到文件output&#x2F;spillN.out.index</font><font style="color:#000000;">中。</font></li></ul></li><li><font style="color:#000000;">Combin</font><font style="color:#000000;">e</font><font style="color:#000000;">阶段</font><font style="color:#000000;">：当所有数据处理完成后，MapTask</font><font style="color:#000000;">对</font><font style="color:#000000;">所有临时文件进行一次合并，以确保最终只会生成一个数据文件。</font></li></ul><p><font style="color:#000000;"></font></p><p><font style="color:#000000;">当</font><font style="color:#000000;">所有数据处理完后，MapTask</font><font style="color:#000000;">会</font><font style="color:#000000;">将所有临时文件合并成一个大文件</font><font style="color:#000000;">，</font><font style="color:#000000;">并</font><font style="color:#000000;">保存</font><font style="color:#000000;">到文件output&#x2F;file.out</font><font style="color:#000000;">中</font><font style="color:#000000;">，同时生成相应的索引文件output&#x2F;file.out.index。在</font><font style="color:#000000;">进行文件合并过程中，MapTask</font><font style="color:#000000;">以</font><font style="color:#000000;">分区为单位进行合并。对于</font><font style="color:#000000;">某个</font><font style="color:#000000;">分区，</font><font style="color:#000000;">它</font><font style="color:#000000;">将采用多轮递归合并的方式</font><font style="color:#000000;">。</font><font style="color:#000000;">每轮</font><font style="color:#000000;">合并</font><font style="color:#000000;">io.sort.factor</font><font style="color:#000000;">（默认10）个</font><font style="color:#000000;">文件，并将产生的文件重新加入待合并</font><font style="color:#000000;">列表</font><font style="color:#000000;">中，对文件排序后，重复以上过程，直到最终得到一个大文件。</font></p><p><font style="color:#000000;"></font></p><p><font style="color:#000000;">让</font><font style="color:#000000;">每个MapTask最终只生成一个数据文件，可避免同时打开大量文件和同时读取大量小文件产生的随机读取</font><font style="color:#000000;">带来</font><font style="color:#000000;">的开销。</font></p><h4 id="ReduceTask工作机制"><a href="#ReduceTask工作机制" class="headerlink" title="ReduceTask工作机制"></a>ReduceTask工作机制</h4><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005634584-1459724152.png" alt="1605197602887-93206d3f-c232-4317-a492-302b8de90eb1.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005634844-1322404004.png" alt="1603435998329-e216d411-1937-4515-a08c-b67e012767d9.png"></p><ul><li>Copy阶段：ReduceTask从各个MapTask上远程拷贝一片数据，并针对某一片数据，如果其大小超过一定阈值，则写到磁盘上，否则直接放到内存中。</li><li>Merge阶段：在远程拷贝数据的同时，ReduceTask启动了两个后台线程对内存和磁盘上的文件进行合并，以防止内存使用过多或磁盘上文件过多。</li><li>Sort阶段：按照MapReduce语义，用户编写reduce()函数输入数据是按key进行聚集的一组数据。为了将key相同的数据聚在一起，Hadoop采用了基于排序的策略。由于各个MapTask已经实现对自己的处理结果进行了局部排序，因此，ReduceTask只需对所有数据进行一次归并排序即可。</li><li>Reduce阶段：reduce()函数将计算结果写到HDFS上。</li></ul><hr><p><strong>设置ReduceTask****并行度（个数）</strong></p><p><font style="color:#000000;">R</font><font style="color:#000000;">educe</font><font style="color:#000000;">T</font><font style="color:#000000;">ask的并行度同样影响整个</font><font style="color:#000000;">J</font><font style="color:#000000;">ob的执行并发度和执行效率，但与</font><font style="color:#000000;">M</font><font style="color:#000000;">ap</font><font style="color:#000000;">T</font><font style="color:#000000;">ask的并发数由切片数决定不同，</font><font style="color:#000000;">R</font><font style="color:#000000;">educe</font><font style="color:#000000;">T</font><font style="color:#000000;">ask数量的决定是可以直接手动设置：</font></p><p>&#x2F;&#x2F; 默认值是1，手动设置为4</p><p>job.setNumReduceTasks(4);</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005635146-427760958.png" alt="1605197673691-bb95b06f-1ee0-4ed8-87c0-2b1ef10f78e0.png"></p><hr><h3 id="shuffle流程"><a href="#shuffle流程" class="headerlink" title="shuffle流程"></a>shuffle流程</h3><p><font style="color:#F5222D;background-color:#FADB14;">shuffle洗牌过程，一共涉及三次排序（用时间换空间，其实快排比归并更快）：</font></p><ul><li><font style="color:#F5222D;background-color:#FADB14;">第一次内存里面的快排</font></li><li><font style="color:#F5222D;background-color:#FADB14;">第二次是一个task里面的归并</font></li><li><font style="color:#F5222D;background-color:#FADB14;">第三次是所有task的有序文件的归并</font></li></ul><p>上面的流程是整个MapReduce最全工作流程，但是Shuffle过程只是从第7步开始到第16步结束，具体Shuffle过程详解，如下：</p><p><font style="color:#F5222D;background-color:#FADB14;">（1）</font><font style="color:#F5222D;background-color:#FADB14;">M</font><font style="color:#F5222D;background-color:#FADB14;">ap</font><font style="color:#F5222D;background-color:#FADB14;">T</font><font style="color:#F5222D;background-color:#FADB14;">ask收集我们的map()方法输出的kv对，放到内存缓冲区中</font></p><p><font style="color:#F5222D;background-color:#FADB14;">（2）从内存缓冲区不断溢出本地磁盘文件，可能会溢出多个文件</font></p><p><font style="color:#F5222D;background-color:#FADB14;">（3）多个溢出文件会被合并成大的溢出文件</font></p><p><font style="color:#F5222D;background-color:#FADB14;">（4）在溢出过程及合并的过程中，都要调用Partit</font><font style="color:#F5222D;background-color:#FADB14;">i</font><font style="color:#F5222D;background-color:#FADB14;">oner进行</font><font style="color:#F5222D;background-color:#1890FF;">分区</font><font style="color:#F5222D;background-color:#FADB14;">和</font><font style="color:#F5222D;background-color:#1890FF;">针对key进行排序</font></p><p><font style="color:#F5222D;background-color:#FADB14;">（5）R</font><font style="color:#F5222D;background-color:#FADB14;">educeTask</font><font style="color:#F5222D;background-color:#FADB14;">根据自己的分区号，去各个</font><font style="color:#F5222D;background-color:#FADB14;">M</font><font style="color:#F5222D;background-color:#FADB14;">ap</font><font style="color:#F5222D;background-color:#FADB14;">T</font><font style="color:#F5222D;background-color:#FADB14;">ask机器上取相应的结果分区数据</font></p><p><font style="color:#F5222D;background-color:#FADB14;">（6）R</font><font style="color:#F5222D;background-color:#FADB14;">educeTask</font><font style="color:#F5222D;background-color:#FADB14;">会取到同一个分区的来自不同</font><font style="color:#F5222D;background-color:#FADB14;">M</font><font style="color:#F5222D;background-color:#FADB14;">ap</font><font style="color:#F5222D;background-color:#FADB14;">T</font><font style="color:#F5222D;background-color:#FADB14;">ask的结果文件，R</font><font style="color:#F5222D;background-color:#FADB14;">educeTask</font><font style="color:#F5222D;background-color:#FADB14;">会将这些文件再进行合并（归并排序）</font></p><p><font style="color:#F5222D;background-color:#FADB14;">（7）合并成大文件后，</font><font style="color:#F5222D;background-color:#FADB14;">S</font><font style="color:#F5222D;background-color:#FADB14;">huffle的过程也就结束了，后面进入</font><font style="color:#F5222D;background-color:#FADB14;">R</font><font style="color:#F5222D;background-color:#FADB14;">educe</font><font style="color:#F5222D;background-color:#FADB14;">T</font><font style="color:#F5222D;background-color:#FADB14;">ask的逻辑运算过程（从文件中取出一个一个的键值对</font><font style="color:#F5222D;background-color:#FADB14;">G</font><font style="color:#F5222D;background-color:#FADB14;">roup，调用用户自定义的reduce()方法）</font></p><p><font style="color:#000000;"></font></p><p><strong>注意</strong><strong>：</strong></p><p><font style="color:#000000;">（1）</font><font style="color:#000000;">S</font><font style="color:#000000;">huffle中的缓冲区大小会影响到</font><font style="color:#000000;">M</font><font style="color:#000000;">ap</font><font style="color:#000000;">R</font><font style="color:#000000;">educe程序的执行效率，原则上说，缓冲区越大，磁盘io的次数越少，执行速度就越快。</font></p><p><font style="color:#000000;">（2）缓冲区的大小可以通过参数调整，参数：io.sort.mb默认100M。</font></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005635430-235858747.png" alt="1603436287685-1022696f-dcb8-4a08-85a8-7d4afd1fbf15.png"></p><p>看转过来的第二行开始位置，reduce处理的时候是拿每一个map的相同分区上的数据来归并。得到的结果再分组给不同的reduce。</p><h3 id="Partition–-分区"><a href="#Partition–-分区" class="headerlink" title="**Partition–**分区"></a>**Partition–**<strong>分区</strong></h3><p>一个map被分为很多区，分区是为了给reduce划分数据。reduce处理的时候也是采用并行的机制，</p><p>在分区之后才会快排。分区的依据就是有多少reduce在工作</p><h4 id="默认分区方式"><a href="#默认分区方式" class="headerlink" title="默认分区方式"></a>默认分区方式</h4><p>key的 hashcode对reduce的个数取余，相同的取余运算结果去往同一个分区。</p><p>默认分区是根据key的hashCode对ReduceTasks个数取模得到的。用户没法控制哪个key存储到哪个分区。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HashPartitioner</span>&lt;K, V&gt; <span class="keyword">extends</span> <span class="title class_">Partitioner</span>&lt;K, V&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getPartition</span><span class="params">(K key, V value, <span class="type">int</span> numReduceTasks)</span> &#123;</span><br><span class="line">        <span class="comment">// 与是为了去负号，除了第一个其余不变，相当于把符号位变成0</span></span><br><span class="line">        <span class="keyword">return</span> (key.hashCode() &amp; Integer.MAX_VALUE) % numReduceTasks;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="自定义分区方式"><a href="#自定义分区方式" class="headerlink" title="自定义分区方式"></a>自定义分区方式</h4><p>自定义类继承Partitioner，重写getPartition()方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomPartitioner</span> <span class="keyword">extends</span> <span class="title class_">Partitioner</span>&lt;Text, FlowBean&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getPartition</span><span class="params">(Text key, FlowBean value, <span class="type">int</span> numPartitions)</span> &#123;</span><br><span class="line">        <span class="comment">// 控制分区代码逻辑</span></span><br><span class="line">        … …</span><br><span class="line">            <span class="keyword">return</span> partition;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005635662-1770660929.png" alt="1603445468121-615df441-45e2-40b2-a2d3-d11749b09796.png"></p><p><strong>Partition分区案例实操</strong></p><p>将统计结果按照手机归属地不同省份输出到不同文件中（分区）<font style="color:#000000;"></font></p><p>手机号136、137、138、139开头都分别放到一个独立的4个文件中，其他开头的放到一个文件中。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.mapreduce.flowsum;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Partitioner;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProvincePartitioner</span> <span class="keyword">extends</span> <span class="title class_">Partitioner</span>&lt;Text, FlowBean&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getPartition</span><span class="params">(Text key, FlowBean value, <span class="type">int</span> numPartitions)</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 1 获取电话号码的前三位</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">preNum</span> <span class="operator">=</span> key.toString().substring(<span class="number">0</span>, <span class="number">3</span>);</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> <span class="variable">partition</span> <span class="operator">=</span> <span class="number">4</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2 判断是哪个省</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="string">&quot;136&quot;</span>.equals(preNum)) &#123;</span><br><span class="line">            partition = <span class="number">0</span>;</span><br><span class="line">        &#125;<span class="keyword">else</span> <span class="keyword">if</span> (<span class="string">&quot;137&quot;</span>.equals(preNum)) &#123;</span><br><span class="line">            partition = <span class="number">1</span>;</span><br><span class="line">        &#125;<span class="keyword">else</span> <span class="keyword">if</span> (<span class="string">&quot;138&quot;</span>.equals(preNum)) &#123;</span><br><span class="line">            partition = <span class="number">2</span>;</span><br><span class="line">        &#125;<span class="keyword">else</span> <span class="keyword">if</span> (<span class="string">&quot;139&quot;</span>.equals(preNum)) &#123;</span><br><span class="line">            partition = <span class="number">3</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> partition;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>在驱动函数中增加自定义数据分区设置和<strong><strong>R</strong></strong>educe<strong><strong>T</strong></strong>ask设置</strong></p><hr><p>不设置的话还是使用默认的hash分区</p><p>  &#x2F;&#x2F; 8 指定自定义数据分区</p><p>        &#x2F;&#x2F; 不设置的话还是使用默认的hash分区</p><p>        job.setPartitionerClass(ProvincePartitioner.class);</p><p>        &#x2F;&#x2F; 9 同时指定相应数量的reduce task</p><p>        job.setNumReduceTasks(5);</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.mapreduce.flowsum;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.conf.Configuration;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.fs.Path;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Job;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.lib.input.FileInputFormat;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FlowsumDriver</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IllegalArgumentException, IOException, ClassNotFoundException, InterruptedException &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 输入输出路径需要根据自己电脑上实际的输入输出路径设置</span></span><br><span class="line">        args = <span class="keyword">new</span> <span class="title class_">String</span>[]&#123;<span class="string">&quot;e:/output1&quot;</span>,<span class="string">&quot;e:/output2&quot;</span>&#125;;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 1 获取配置信息，或者job对象实例</span></span><br><span class="line">        <span class="type">Configuration</span> <span class="variable">configuration</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Configuration</span>();</span><br><span class="line">        <span class="type">Job</span> <span class="variable">job</span> <span class="operator">=</span> Job.getInstance(configuration);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2 指定本程序的jar包所在的本地路径</span></span><br><span class="line">        job.setJarByClass(FlowsumDriver.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3 指定本业务job要使用的mapper/Reducer业务类</span></span><br><span class="line">        job.setMapperClass(FlowCountMapper.class);</span><br><span class="line">        job.setReducerClass(FlowCountReducer.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 4 指定mapper输出数据的kv类型</span></span><br><span class="line">        job.setMapOutputKeyClass(Text.class);</span><br><span class="line">        job.setMapOutputValueClass(FlowBean.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 5 指定最终输出的数据的kv类型</span></span><br><span class="line">        job.setOutputKeyClass(Text.class);</span><br><span class="line">        job.setOutputValueClass(FlowBean.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 8 指定自定义数据分区</span></span><br><span class="line">        <span class="comment">// 不设置的话还是使用默认的hash分区</span></span><br><span class="line">        job.setPartitionerClass(ProvincePartitioner.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 9 同时指定相应数量的reduce task</span></span><br><span class="line">        job.setNumReduceTasks(<span class="number">5</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 6 指定job的输入原始文件所在目录</span></span><br><span class="line">        FileInputFormat.setInputPaths(job, <span class="keyword">new</span> <span class="title class_">Path</span>(args[<span class="number">0</span>]));</span><br><span class="line">        FileOutputFormat.setOutputPath(job, <span class="keyword">new</span> <span class="title class_">Path</span>(args[<span class="number">1</span>]));</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 7 将job中配置的相关参数，以及job所用的java类所在的jar包， 提交给yarn去运行</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">result</span> <span class="operator">=</span> job.waitForCompletion(<span class="literal">true</span>);</span><br><span class="line">        System.exit(result ? <span class="number">0</span> : <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="WritableComparable接口–-排序"><a href="#WritableComparable接口–-排序" class="headerlink" title="**WritableComparable接口–**排序"></a>**WritableComparable接口–**<strong>排序</strong></h3><h4 id="自定义排序-WritableComparable-原理分析"><a href="#自定义排序-WritableComparable-原理分析" class="headerlink" title="自定义排序 WritableComparable 原理分析"></a>自定义排序 WritableComparable 原理分析</h4><p>bean对象做为key传输，需要<font style="background-color:#FADB14;">实现</font><font style="color:#FF0000;background-color:#FADB14;">WritableComparable</font><font style="background-color:#FADB14;">接口重写</font><font style="background-color:#FADB14;">compareTo</font><font style="background-color:#FADB14;">方法</font>，就可以实现排序。</p><p><font style="color:#F5222D;background-color:#FADB14;">只要实现了此接口，就可以动态调用你。</font></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">compareTo</span><span class="params">(FlowBean o)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> result;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 按照总流量大小，倒序排列</span></span><br><span class="line">    <span class="keyword">if</span> (sumFlow &gt; bean.getSumFlow()) &#123;</span><br><span class="line">        result = -<span class="number">1</span>;</span><br><span class="line">    &#125;<span class="keyword">else</span> <span class="keyword">if</span> (sumFlow &lt; bean.getSumFlow()) &#123;</span><br><span class="line">        result = <span class="number">1</span>;</span><br><span class="line">    &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">        result = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="WritableComparable排序案例实操（全排序）"><a href="#WritableComparable排序案例实操（全排序）" class="headerlink" title="WritableComparable排序案例实操（全排序）****"></a>WritableComparable排序案例实操（全排序）****</h4><p>根据案例2.3产生的结果再次对总流量进行排序。<font style="color:#000000;"></font></p><p><font style="color:#000000;">13509468723</font><font style="color:#000000;"></font><font style="color:#000000;">7335</font><font style="color:#000000;"></font><font style="color:#000000;">110349</font><font style="color:#000000;"></font><font style="color:#FF0000;">117684</font></p><p><font style="color:#000000;">13736230513</font><font style="color:#000000;"></font><font style="color:#000000;">2481</font><font style="color:#000000;"></font><font style="color:#000000;">24681</font><font style="color:#000000;"></font><font style="color:#FF0000;">27162</font></p><p><font style="color:#000000;">13956435636</font><font style="color:#000000;"></font><font style="color:#000000;">132</font><font style="color:#000000;"></font><font style="color:#000000;"></font><font style="color:#000000;">1512</font><font style="color:#000000;"></font><font style="color:#FF0000;">1644</font></p><p><font style="color:#000000;">13846544121</font><font style="color:#000000;"></font><font style="color:#000000;">264</font><font style="color:#000000;"></font><font style="color:#000000;"></font><font style="color:#000000;">0</font><font style="color:#000000;"></font><font style="color:#000000;"></font><font style="color:#FF0000;">264</font></p><p><font style="color:#000000;">。。</font><font style="color:#000000;">。</font><font style="color:#000000;"> 。</font><font style="color:#000000;">。。</font></p><hr><p>FlowBean对象在在需求1基础上增加了比较功能</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.mapreduce.sort;</span><br><span class="line"><span class="keyword">import</span> java.io.DataInput;</span><br><span class="line"><span class="keyword">import</span> java.io.DataOutput;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.WritableComparable;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FlowBean</span> <span class="keyword">implements</span> <span class="title class_">WritableComparable</span>&lt;FlowBean&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> upFlow;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> downFlow;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> sumFlow;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 反序列化时，需要反射调用空参构造函数，所以必须有</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">FlowBean</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">FlowBean</span><span class="params">(<span class="type">long</span> upFlow, <span class="type">long</span> downFlow)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>();</span><br><span class="line">        <span class="built_in">this</span>.upFlow = upFlow;</span><br><span class="line">        <span class="built_in">this</span>.downFlow = downFlow;</span><br><span class="line">        <span class="built_in">this</span>.sumFlow = upFlow + downFlow;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">set</span><span class="params">(<span class="type">long</span> upFlow, <span class="type">long</span> downFlow)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.upFlow = upFlow;</span><br><span class="line">        <span class="built_in">this</span>.downFlow = downFlow;</span><br><span class="line">        <span class="built_in">this</span>.sumFlow = upFlow + downFlow;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">getSumFlow</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> sumFlow;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setSumFlow</span><span class="params">(<span class="type">long</span> sumFlow)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.sumFlow = sumFlow;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">getUpFlow</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> upFlow;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setUpFlow</span><span class="params">(<span class="type">long</span> upFlow)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.upFlow = upFlow;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">getDownFlow</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> downFlow;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setDownFlow</span><span class="params">(<span class="type">long</span> downFlow)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.downFlow = downFlow;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 序列化方法</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> out</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> IOException</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">write</span><span class="params">(DataOutput out)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        out.writeLong(upFlow);</span><br><span class="line">        out.writeLong(downFlow);</span><br><span class="line">        out.writeLong(sumFlow);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 反序列化方法 注意反序列化的顺序和序列化的顺序完全一致</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> in</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> IOException</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">readFields</span><span class="params">(DataInput in)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        upFlow = in.readLong();</span><br><span class="line">        downFlow = in.readLong();</span><br><span class="line">        sumFlow = in.readLong();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> upFlow + <span class="string">&quot;\t&quot;</span> + downFlow + <span class="string">&quot;\t&quot;</span> + sumFlow;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">compareTo</span><span class="params">(FlowBean bean)</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> result;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 按照总流量大小，倒序排列</span></span><br><span class="line">        <span class="keyword">if</span> (sumFlow &gt; bean.getSumFlow()) &#123;</span><br><span class="line">            result = -<span class="number">1</span>;</span><br><span class="line">        &#125;<span class="keyword">else</span> <span class="keyword">if</span> (sumFlow &lt; bean.getSumFlow()) &#123;</span><br><span class="line">            result = <span class="number">1</span>;</span><br><span class="line">        &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">            result = <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编写Mapper类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.mapreduce.sort;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.LongWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Mapper;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FlowCountSortMapper</span> <span class="keyword">extends</span> <span class="title class_">Mapper</span>&lt;LongWritable, Text, FlowBean, Text&gt;&#123;</span><br><span class="line"> </span><br><span class="line"><span class="type">FlowBean</span> <span class="variable">bean</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FlowBean</span>();</span><br><span class="line"><span class="type">Text</span> <span class="variable">v</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Text</span>();</span><br><span class="line"> </span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">map</span><span class="params">(LongWritable key, Text value, Context context)</span><span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 1 获取一行</span></span><br><span class="line"><span class="type">String</span> <span class="variable">line</span> <span class="operator">=</span> value.toString();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2 截取</span></span><br><span class="line">String[] fields = line.split(<span class="string">&quot;\t&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3 封装对象</span></span><br><span class="line"><span class="type">String</span> <span class="variable">phoneNbr</span> <span class="operator">=</span> fields[<span class="number">0</span>];</span><br><span class="line"><span class="type">long</span> <span class="variable">upFlow</span> <span class="operator">=</span> Long.parseLong(fields[<span class="number">1</span>]);</span><br><span class="line"><span class="type">long</span> <span class="variable">downFlow</span> <span class="operator">=</span> Long.parseLong(fields[<span class="number">2</span>]);</span><br><span class="line"></span><br><span class="line">bean.set(upFlow, downFlow);</span><br><span class="line">v.set(phoneNbr);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 4 输出</span></span><br><span class="line">context.write(bean, v);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编写Reducer类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.mapreduce.sort;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Reducer;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FlowCountSortReducer</span> <span class="keyword">extends</span> <span class="title class_">Reducer</span>&lt;FlowBean, Text, Text, FlowBean&gt;&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">reduce</span><span class="params">(FlowBean key, Iterable&lt;Text&gt; values, Context context)</span><span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 循环输出，避免总流量相同情况</span></span><br><span class="line">        <span class="keyword">for</span> (Text text : values) &#123;</span><br><span class="line">            context.write(text, key);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编写Driver类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.mapreduce.sort;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.conf.Configuration;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.fs.Path;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Job;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.lib.input.FileInputFormat;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FlowCountSortDriver</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> ClassNotFoundException, IOException, InterruptedException &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 输入输出路径需要根据自己电脑上实际的输入输出路径设置</span></span><br><span class="line">        args = <span class="keyword">new</span> <span class="title class_">String</span>[]&#123;<span class="string">&quot;e:/output1&quot;</span>,<span class="string">&quot;e:/output2&quot;</span>&#125;;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 1 获取配置信息，或者job对象实例</span></span><br><span class="line">        <span class="type">Configuration</span> <span class="variable">configuration</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Configuration</span>();</span><br><span class="line">        <span class="type">Job</span> <span class="variable">job</span> <span class="operator">=</span> Job.getInstance(configuration);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2 指定本程序的jar包所在的本地路径</span></span><br><span class="line">        job.setJarByClass(FlowCountSortDriver.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3 指定本业务job要使用的mapper/Reducer业务类</span></span><br><span class="line">        job.setMapperClass(FlowCountSortMapper.class);</span><br><span class="line">        job.setReducerClass(FlowCountSortReducer.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 4 指定mapper输出数据的kv类型</span></span><br><span class="line">        job.setMapOutputKeyClass(FlowBean.class);</span><br><span class="line">        job.setMapOutputValueClass(Text.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 5 指定最终输出的数据的kv类型</span></span><br><span class="line">        job.setOutputKeyClass(Text.class);</span><br><span class="line">        job.setOutputValueClass(FlowBean.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 6 指定job的输入原始文件所在目录</span></span><br><span class="line">        FileInputFormat.setInputPaths(job, <span class="keyword">new</span> <span class="title class_">Path</span>(args[<span class="number">0</span>]));</span><br><span class="line">        FileOutputFormat.setOutputPath(job, <span class="keyword">new</span> <span class="title class_">Path</span>(args[<span class="number">1</span>]));</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 7 将job中配置的相关参数，以及job所用的java类所在的jar包， 提交给yarn去运行</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">result</span> <span class="operator">=</span> job.waitForCompletion(<span class="literal">true</span>);</span><br><span class="line">        System.exit(result ? <span class="number">0</span> : <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="WritableComparable排序案例实操（区内排序）"><a href="#WritableComparable排序案例实操（区内排序）" class="headerlink" title="WritableComparable排序案例实操（区内排序）"></a>WritableComparable排序案例实操（区内排序）</h4><p><strong>1</strong><strong>）需求</strong></p><p><font style="color:#000000;">要求每个</font><font style="color:#000000;">省份</font><font style="color:#000000;">手机号输出</font><font style="color:#000000;">的文件中按照总流量内部排序。</font></p><p><strong>2）需求****分析</strong></p><p><font style="color:#000000;"></font><font style="color:#000000;">基于前一个需求，</font><font style="color:#000000;">增加</font><font style="color:#000000;">自定义</font><font style="color:#000000;">分区类</font><font style="color:#000000;">，</font><font style="color:#000000;">分区按照省份手机号</font><font style="color:#000000;">设置。</font></p><p>**3）**<strong>案例实操</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">（<span class="number">1</span>）增加自定义分区类</span><br><span class="line"><span class="keyword">package</span> com.atguigu.mapreduce.sort;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Partitioner;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProvincePartitioner</span> <span class="keyword">extends</span> <span class="title class_">Partitioner</span>&lt;FlowBean, Text&gt; &#123;</span><br><span class="line"> </span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getPartition</span><span class="params">(FlowBean key, Text value, <span class="type">int</span> numPartitions)</span> &#123;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 1 获取手机号码前三位</span></span><br><span class="line"><span class="type">String</span> <span class="variable">preNum</span> <span class="operator">=</span> value.toString().substring(<span class="number">0</span>, <span class="number">3</span>);</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="variable">partition</span> <span class="operator">=</span> <span class="number">4</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2 根据手机号归属地设置分区</span></span><br><span class="line"><span class="keyword">if</span> (<span class="string">&quot;136&quot;</span>.equals(preNum)) &#123;</span><br><span class="line">partition = <span class="number">0</span>;</span><br><span class="line">&#125;<span class="keyword">else</span> <span class="keyword">if</span> (<span class="string">&quot;137&quot;</span>.equals(preNum)) &#123;</span><br><span class="line">partition = <span class="number">1</span>;</span><br><span class="line">&#125;<span class="keyword">else</span> <span class="keyword">if</span> (<span class="string">&quot;138&quot;</span>.equals(preNum)) &#123;</span><br><span class="line">partition = <span class="number">2</span>;</span><br><span class="line">&#125;<span class="keyword">else</span> <span class="keyword">if</span> (<span class="string">&quot;139&quot;</span>.equals(preNum)) &#123;</span><br><span class="line">partition = <span class="number">3</span>;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">return</span> partition;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">（<span class="number">2</span>）在驱动类中添加分区类</span><br><span class="line"><span class="comment">// 加载自定义分区类</span></span><br><span class="line">job.setPartitionerClass(ProvincePartitioner.class);</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 设置Reducetask个数</span></span><br><span class="line">job.setNumReduceTasks(<span class="number">5</span>);</span><br></pre></td></tr></table></figure><h3 id="Combiner–合并"><a href="#Combiner–合并" class="headerlink" title="Combiner–合并"></a><strong>Combiner–合并</strong></h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005635914-11535268.png" alt="1605196752290-e9473b75-f7c9-4897-bafa-5f3e79a0587d.png"></p><p><font style="color:#FF0000;">（6）自定义Combiner实现步骤</font></p><p><font style="color:#000000;">自定义一个</font><font style="color:#000000;">C</font><font style="color:#000000;">ombiner继承Reducer，重写</font><font style="color:#000000;">R</font><font style="color:#000000;">educe方法</font></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WordcountCombiner</span> <span class="keyword">extends</span> <span class="title class_">Reducer</span>&lt;Text, IntWritable, Text,IntWritable&gt;&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">reduce</span><span class="params">(Text key, Iterable&lt;IntWritable&gt; values,Context context)</span> <span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 1 汇总操作</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span>(IntWritable v :values)&#123;</span><br><span class="line">            count += v.get();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2 写出</span></span><br><span class="line">        context.write(key, <span class="keyword">new</span> <span class="title class_">IntWritable</span>(count));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><font style="color:#000000;">在</font><font style="color:#000000;">J</font><font style="color:#000000;">ob驱动类中设置：  </font></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">job.setCombinerClass(WordcountCombiner.class);</span><br></pre></td></tr></table></figure><h4 id="Combiner合并案例实操"><a href="#Combiner合并案例实操" class="headerlink" title="Combiner合并案例实操****"></a>Combiner合并案例实操****</h4><p>统计过程中对每一个MapTask的输出进行局部汇总，以减小网络传输量即采用Combiner功能。</p><p><font style="color:#000000;">期望</font><font style="color:#000000;">：</font><font style="color:#000000;">Combine输入数据多，输出时经过合并，输出数据降低。</font></p><p>（1）增加一个WordcountCombiner类继承Reducer</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.mr.combiner;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.IntWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Reducer;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WordcountCombiner</span> <span class="keyword">extends</span> <span class="title class_">Reducer</span>&lt;Text, IntWritable, Text, IntWritable&gt;&#123;</span><br><span class="line"> </span><br><span class="line"><span class="type">IntWritable</span> <span class="variable">v</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">IntWritable</span>();</span><br><span class="line"> </span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">reduce</span><span class="params">(Text key, Iterable&lt;IntWritable&gt; values, Context context)</span> <span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line"> </span><br><span class="line">        <span class="comment">// 1 汇总</span></span><br><span class="line"><span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">for</span>(IntWritable value :values)&#123;</span><br><span class="line">sum += value.get();</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line">v.set(sum);</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 2 写出</span></span><br><span class="line">context.write(key, v);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>（2）在WordcountDriver驱动类中指定Combiner</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 指定需要使用combiner，以及用哪个类作为combiner的逻辑</span></span><br><span class="line">job.setCombinerClass(WordcountCombiner.class);</span><br></pre></td></tr></table></figure><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005636212-469639678.png" alt="1605197135723-0bebe915-608b-426e-b990-c1cf9340978d.png"></p><h2 id="OutputFormat数据输出"><a href="#OutputFormat数据输出" class="headerlink" title="OutputFormat数据输出"></a><strong>O<strong><strong>utputFormat数据</strong></strong>输出</strong></h2><h3 id="OutputFormat接口-实现类"><a href="#OutputFormat接口-实现类" class="headerlink" title="OutputFormat接口****实现类"></a><strong>Out<strong><strong>putFormat</strong></strong>接口****实现类</strong></h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005636447-1399985100.png" alt="1605197780928-ab8a197d-4bdd-4baa-95e0-edccb2d04756.png"></p><h3 id="自定义Out-putFormat"><a href="#自定义Out-putFormat" class="headerlink" title="自定义Out****putFormat"></a><strong>自定义Out****putFormat</strong></h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005636733-748380697.png" alt="1605197888418-ea6c01a6-b763-4b83-89e5-25b523b838d9.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005636995-2128677285.png" alt="1605198011698-39e752bb-212c-456e-aff7-aa4edb914f41.png"></p><p>编写FilterMapper类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.mapreduce.outputformat;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.LongWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.NullWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Mapper;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FilterMapper</span> <span class="keyword">extends</span> <span class="title class_">Mapper</span>&lt;LongWritable, Text, Text, NullWritable&gt;&#123;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">map</span><span class="params">(LongWritable key, Text value, Context context)</span><span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 写出</span></span><br><span class="line">context.write(value, NullWritable.get());</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编写FilterReducer类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.mapreduce.outputformat;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.NullWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Reducer;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FilterReducer</span> <span class="keyword">extends</span> <span class="title class_">Reducer</span>&lt;Text, NullWritable, Text, NullWritable&gt; &#123;</span><br><span class="line"> </span><br><span class="line"><span class="type">Text</span> <span class="variable">k</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Text</span>();</span><br><span class="line"> </span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">reduce</span><span class="params">(Text key, Iterable&lt;NullWritable&gt; values, Context context)</span><span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line"> </span><br><span class="line">       <span class="comment">// 1 获取一行</span></span><br><span class="line"><span class="type">String</span> <span class="variable">line</span> <span class="operator">=</span> key.toString();</span><br><span class="line"> </span><br><span class="line">       <span class="comment">// 2 拼接</span></span><br><span class="line">line = line + <span class="string">&quot;\r\n&quot;</span>;</span><br><span class="line"> </span><br><span class="line">       <span class="comment">// 3 设置key</span></span><br><span class="line">       k.set(line);</span><br><span class="line"> </span><br><span class="line">       <span class="comment">// 4 输出</span></span><br><span class="line">context.write(k, NullWritable.get());</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>（3）自定义一个OutputFormat类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.mapreduce.outputformat;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.NullWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.RecordWriter;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.TaskAttemptContext;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FilterOutputFormat</span> <span class="keyword">extends</span> <span class="title class_">FileOutputFormat</span>&lt;Text, NullWritable&gt;&#123;</span><br><span class="line"> </span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> RecordWriter&lt;Text, NullWritable&gt; <span class="title function_">getRecordWriter</span><span class="params">(TaskAttemptContext job)</span><span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 创建一个RecordWriter</span></span><br><span class="line"><span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">FilterRecordWriter</span>(job);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>（4）编写RecordWriter类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.mapreduce.outputformat;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.fs.FSDataOutputStream;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.fs.FileSystem;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.fs.Path;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.NullWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.RecordWriter;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.TaskAttemptContext;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FilterRecordWriter</span> <span class="keyword">extends</span> <span class="title class_">RecordWriter</span>&lt;Text, NullWritable&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">FSDataOutputStream</span> <span class="variable">atguiguOut</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="type">FSDataOutputStream</span> <span class="variable">otherOut</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">FilterRecordWriter</span><span class="params">(TaskAttemptContext job)</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 1 获取文件系统</span></span><br><span class="line">        FileSystem fs;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            fs = FileSystem.get(job.getConfiguration());</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 2 创建输出文件路径</span></span><br><span class="line">            <span class="type">Path</span> <span class="variable">atguiguPath</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Path</span>(<span class="string">&quot;e:/atguigu.log&quot;</span>);</span><br><span class="line">            <span class="type">Path</span> <span class="variable">otherPath</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Path</span>(<span class="string">&quot;e:/other.log&quot;</span>);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 3 创建输出流</span></span><br><span class="line">            atguiguOut = fs.create(atguiguPath);</span><br><span class="line">            otherOut = fs.create(otherPath);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">write</span><span class="params">(Text key, NullWritable value)</span> <span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 判断是否包含“atguigu”输出到不同文件</span></span><br><span class="line">        <span class="keyword">if</span> (key.toString().contains(<span class="string">&quot;atguigu&quot;</span>)) &#123;</span><br><span class="line">            atguiguOut.write(key.toString().getBytes());</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            otherOut.write(key.toString().getBytes());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">close</span><span class="params">(TaskAttemptContext context)</span> <span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 关闭资源</span></span><br><span class="line">        IOUtils.closeStream(atguiguOut);</span><br><span class="line">        IOUtils.closeStream(otherOut);&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>（5）编写FilterDriver类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.mapreduce.outputformat;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.conf.Configuration;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.fs.Path;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.NullWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Job;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.lib.input.FileInputFormat;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FilterDriver</span> &#123;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 输入输出路径需要根据自己电脑上实际的输入输出路径设置</span></span><br><span class="line">args = <span class="keyword">new</span> <span class="title class_">String</span>[] &#123; <span class="string">&quot;e:/input/inputoutputformat&quot;</span>, <span class="string">&quot;e:/output2&quot;</span> &#125;;</span><br><span class="line"> </span><br><span class="line"><span class="type">Configuration</span> <span class="variable">conf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Configuration</span>();</span><br><span class="line"><span class="type">Job</span> <span class="variable">job</span> <span class="operator">=</span> Job.getInstance(conf);</span><br><span class="line"> </span><br><span class="line">job.setJarByClass(FilterDriver.class);</span><br><span class="line">job.setMapperClass(FilterMapper.class);</span><br><span class="line">job.setReducerClass(FilterReducer.class);</span><br><span class="line"> </span><br><span class="line">job.setMapOutputKeyClass(Text.class);</span><br><span class="line">job.setMapOutputValueClass(NullWritable.class);</span><br><span class="line"></span><br><span class="line">job.setOutputKeyClass(Text.class);</span><br><span class="line">job.setOutputValueClass(NullWritable.class);</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 要将自定义的输出格式组件设置到job中</span></span><br><span class="line">job.setOutputFormatClass(FilterOutputFormat.class);</span><br><span class="line"> </span><br><span class="line">FileInputFormat.setInputPaths(job, <span class="keyword">new</span> <span class="title class_">Path</span>(args[<span class="number">0</span>]));</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 虽然我们自定义了outputformat，但是因为我们的outputformat继承自fileoutputformat</span></span><br><span class="line"><span class="comment">// 而fileoutputformat要输出一个_SUCCESS文件，所以，在这还得指定一个输出目录</span></span><br><span class="line">FileOutputFormat.setOutputPath(job, <span class="keyword">new</span> <span class="title class_">Path</span>(args[<span class="number">1</span>]));</span><br><span class="line"> </span><br><span class="line"><span class="type">boolean</span> <span class="variable">result</span> <span class="operator">=</span> job.waitForCompletion(<span class="literal">true</span>);</span><br><span class="line">System.exit(result ? <span class="number">0</span> : <span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Join多种-应用"><a href="#Join多种-应用" class="headerlink" title="Join多种****应用"></a><strong>Join多种****应用</strong></h2><h3 id="Reduce-Join"><a href="#Reduce-Join" class="headerlink" title="Reduce Join"></a><strong>Reduce Join</strong></h3><p>Map端的主要工作：为来自不同表或文件的key&#x2F;value对，打标签以区别不同来源的记录。然后用连接字段作为key，其余部分和新加的标志作为value，最后进行输出。 </p><p>Reduce端的主要工作：在Reduce端以连接字段作为key的分组已经完成，我们只需要在每一个分组当中将那些来源于不同文件的记录(在Map阶段已经打标志)分开，最后进行合并就ok了。 </p><hr><p><font style="color:#000000;">通过将关联条件作为</font><font style="color:#000000;">M</font><font style="color:#000000;">ap输出的key，将两表满足</font><font style="color:#000000;">J</font><font style="color:#000000;">oin条件的数据并携带数据所来源的文件信息，发往同一个</font><font style="color:#000000;">R</font><font style="color:#000000;">educe</font><font style="color:#000000;">T</font><font style="color:#000000;">ask，在</font><font style="color:#000000;">R</font><font style="color:#000000;">educe中进行数据的串联。</font></p><hr><p>（1）创建商品和订合并后的Bean类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.reducejoin;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.WritableComparable;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">import</span> java.io.DataInput;</span><br><span class="line"><span class="keyword">import</span> java.io.DataOutput;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderBean</span> <span class="keyword">implements</span> <span class="title class_">WritableComparable</span>&lt;OrderBean&gt; &#123;</span><br><span class="line">    <span class="keyword">private</span> String id;</span><br><span class="line">    <span class="keyword">private</span> String pid;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> amount;</span><br><span class="line">    <span class="keyword">private</span> String pname;</span><br><span class="line"> </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> id + <span class="string">&quot;\t&quot;</span> + pname + <span class="string">&quot;\t&quot;</span> + amount;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getId</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> id;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setId</span><span class="params">(String id)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.id = id;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getPid</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> pid;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setPid</span><span class="params">(String pid)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.pid = pid;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getAmount</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setAmount</span><span class="params">(<span class="type">int</span> amount)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.amount = amount;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getPname</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> pname;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setPname</span><span class="params">(String pname)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.pname = pname;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="comment">//按照Pid分组，组内按照pname排序，有pname的在前</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">compareTo</span><span class="params">(OrderBean o)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">compare</span> <span class="operator">=</span> <span class="built_in">this</span>.pid.compareTo(o.pid);</span><br><span class="line">        <span class="keyword">if</span> (compare == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> o.getPname().compareTo(<span class="built_in">this</span>.getPname());</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> compare;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">write</span><span class="params">(DataOutput out)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        out.writeUTF(id);</span><br><span class="line">        out.writeUTF(pid);</span><br><span class="line">        out.writeInt(amount);</span><br><span class="line">        out.writeUTF(pname);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">readFields</span><span class="params">(DataInput in)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        id = in.readUTF();</span><br><span class="line">        pid = in.readUTF();</span><br><span class="line">        amount = in.readInt();</span><br><span class="line">        pname = in.readUTF();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>（2）编写TableMapper类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.reducejoin;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.LongWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.NullWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Mapper;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.lib.input.FileSplit;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderMapper</span> <span class="keyword">extends</span> <span class="title class_">Mapper</span>&lt;LongWritable, Text, OrderBean, NullWritable&gt; &#123;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">private</span> String filename;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">private</span> <span class="type">OrderBean</span> <span class="variable">order</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OrderBean</span>();</span><br><span class="line"> </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">setup</span><span class="params">(Context context)</span> <span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//获取切片文件名</span></span><br><span class="line">        <span class="type">FileSplit</span> <span class="variable">fs</span> <span class="operator">=</span> (FileSplit) context.getInputSplit();</span><br><span class="line">        filename = fs.getPath().getName();</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">map</span><span class="params">(LongWritable key, Text value, Context context)</span> <span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line">        String[] fields = value.toString().split(<span class="string">&quot;\t&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//对不同数据来源分开处理</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="string">&quot;order.txt&quot;</span>.equals(filename)) &#123;</span><br><span class="line">            order.setId(fields[<span class="number">0</span>]);</span><br><span class="line">            order.setPid(fields[<span class="number">1</span>]);</span><br><span class="line">            order.setAmount(Integer.parseInt(fields[<span class="number">2</span>]));</span><br><span class="line">            order.setPname(<span class="string">&quot;&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            order.setPid(fields[<span class="number">0</span>]);</span><br><span class="line">            order.setPname(fields[<span class="number">1</span>]);</span><br><span class="line">            order.setAmount(<span class="number">0</span>);</span><br><span class="line">            order.setId(<span class="string">&quot;&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        context.write(order, NullWritable.get());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>（3）编写TableReducer类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.reducejoin;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.NullWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Reducer;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.util.Iterator;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderReducer</span> <span class="keyword">extends</span> <span class="title class_">Reducer</span>&lt;OrderBean, NullWritable, OrderBean, NullWritable&gt; &#123;</span><br><span class="line"> </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">reduce</span><span class="params">(OrderBean key, Iterable&lt;NullWritable&gt; values, Context context)</span> <span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//第一条数据来自pd，之后全部来自order</span></span><br><span class="line">        Iterator&lt;NullWritable&gt; iterator = values.iterator();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//通过第一条数据获取pname</span></span><br><span class="line">        iterator.next();</span><br><span class="line">        <span class="type">String</span> <span class="variable">pname</span> <span class="operator">=</span> key.getPname();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//遍历剩下的数据，替换并写出</span></span><br><span class="line">        <span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">            iterator.next();</span><br><span class="line">            key.setPname(pname);</span><br><span class="line">            context.write(key,NullWritable.get());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>（4）编写TableDriver类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.reducejoin;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.conf.Configuration;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.fs.Path;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.NullWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Job;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.lib.input.FileInputFormat;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderDriver</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException, ClassNotFoundException, InterruptedException &#123;</span><br><span class="line">        <span class="type">Job</span> <span class="variable">job</span> <span class="operator">=</span> Job.getInstance(<span class="keyword">new</span> <span class="title class_">Configuration</span>());</span><br><span class="line">        job.setJarByClass(OrderDriver.class);</span><br><span class="line"> </span><br><span class="line">        job.setMapperClass(OrderMapper.class);</span><br><span class="line">        job.setReducerClass(OrderReducer.class);</span><br><span class="line">        job.setGroupingComparatorClass(OrderComparator.class);</span><br><span class="line"> </span><br><span class="line">        job.setMapOutputKeyClass(OrderBean.class);</span><br><span class="line">        job.setMapOutputValueClass(NullWritable.class);</span><br><span class="line"> </span><br><span class="line">        job.setOutputKeyClass(OrderBean.class);</span><br><span class="line">        job.setOutputValueClass(NullWritable.class);</span><br><span class="line"> </span><br><span class="line">        FileInputFormat.setInputPaths(job, <span class="keyword">new</span> <span class="title class_">Path</span>(<span class="string">&quot;d:\\input&quot;</span>));</span><br><span class="line">        FileOutputFormat.setOutputPath(job, <span class="keyword">new</span> <span class="title class_">Path</span>(<span class="string">&quot;d:\\output&quot;</span>));</span><br><span class="line"> </span><br><span class="line">        <span class="type">boolean</span> <span class="variable">b</span> <span class="operator">=</span> job.waitForCompletion(<span class="literal">true</span>);</span><br><span class="line"> </span><br><span class="line">        System.exit(b ? <span class="number">0</span> : <span class="number">1</span>);</span><br><span class="line"> </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>缺点：这种方式中，合并的操作是在Reduce阶段完成，Reduce端的处理压力太大，Map节点的运算负载则很低，资源利用率不高，且在Reduce阶段极易产生数据倾斜。 </p><h3 id="Map-Join"><a href="#Map-Join" class="headerlink" title="Map Join****"></a><strong>Map Join</strong>****</h3><p>Map Join适用于一张表十分小、一张表很大的场景。</p><p><font style="color:#000000;"></font></p><p><font style="color:#000000;">在</font><font style="color:#000000;">Reduce</font><font style="color:#000000;">端</font><font style="color:#000000;">处理过多的表，</font><font style="color:#000000;">非常</font><font style="color:#000000;">容易产生数据倾斜。</font><font style="color:#000000;">怎么</font><font style="color:#000000;">办？</font></p><p><font style="color:#000000;">在</font><font style="color:#000000;">M</font><font style="color:#000000;">ap</font><font style="color:#000000;">端缓存多张表，提前</font><font style="color:#000000;">处理业务</font><font style="color:#000000;">逻辑，这样增加Map端业务，减少Reduce端数据的压力，尽可能的减少数据倾斜。</font></p><p><font style="color:#000000;"></font></p><p><font style="color:#000000;">在</font><font style="color:#000000;">Mapper的setup阶段，将文件读取到缓存集合中</font><font style="color:#000000;">。</font></p><p><font style="color:#000000;">在</font><font style="color:#000000;">驱动函数中加载缓存。</font></p><p><font style="color:#000000;"></font></p><p>（1）先在驱动模块中添加缓存文件</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> test;</span><br><span class="line"><span class="keyword">import</span> java.net.URI;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.conf.Configuration;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.fs.Path;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.NullWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Job;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.lib.input.FileInputFormat;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DistributedCacheDriver</span> &#123;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 0 根据自己电脑路径重新配置</span></span><br><span class="line">args = <span class="keyword">new</span> <span class="title class_">String</span>[]&#123;<span class="string">&quot;e:/input/inputtable2&quot;</span>, <span class="string">&quot;e:/output1&quot;</span>&#125;;</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 1 获取job信息</span></span><br><span class="line"><span class="type">Configuration</span> <span class="variable">configuration</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Configuration</span>();</span><br><span class="line"><span class="type">Job</span> <span class="variable">job</span> <span class="operator">=</span> Job.getInstance(configuration);</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 2 设置加载jar包路径</span></span><br><span class="line">job.setJarByClass(DistributedCacheDriver.class);</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 3 关联map</span></span><br><span class="line">job.setMapperClass(DistributedCacheMapper.class);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 4 设置最终输出数据类型</span></span><br><span class="line">job.setOutputKeyClass(Text.class);</span><br><span class="line">job.setOutputValueClass(NullWritable.class);</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 5 设置输入输出路径</span></span><br><span class="line">FileInputFormat.setInputPaths(job, <span class="keyword">new</span> <span class="title class_">Path</span>(args[<span class="number">0</span>]));</span><br><span class="line">FileOutputFormat.setOutputPath(job, <span class="keyword">new</span> <span class="title class_">Path</span>(args[<span class="number">1</span>]));</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 6 加载缓存数据</span></span><br><span class="line">job.addCacheFile(<span class="keyword">new</span> <span class="title class_">URI</span>(<span class="string">&quot;file:///e:/input/inputcache/pd.txt&quot;</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 7 Map端Join的逻辑不需要Reduce阶段，设置reduceTask数量为0</span></span><br><span class="line">job.setNumReduceTasks(<span class="number">0</span>);</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 8 提交</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">result</span> <span class="operator">=</span> job.waitForCompletion(<span class="literal">true</span>);</span><br><span class="line">System.exit(result ? <span class="number">0</span> : <span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>（2）读取缓存的文件数据</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.mapjoin;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">import</span> org.apache.commons.lang.StringUtils;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.fs.FSDataInputStream;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.fs.FileSystem;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.fs.Path;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.IOUtils;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.LongWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.NullWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Mapper;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">import</span> java.io.BufferedReader;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.io.InputStreamReader;</span><br><span class="line"><span class="keyword">import</span> java.net.URI;</span><br><span class="line"><span class="keyword">import</span> java.util.HashMap;</span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MjMapper</span> <span class="keyword">extends</span> <span class="title class_">Mapper</span>&lt;LongWritable, Text, Text, NullWritable&gt; &#123;</span><br><span class="line"> </span><br><span class="line">    <span class="comment">//pd表在内存中的缓存</span></span><br><span class="line">    <span class="keyword">private</span> Map&lt;String, String&gt; pMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">private</span> <span class="type">Text</span> <span class="variable">line</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Text</span>();</span><br><span class="line"> </span><br><span class="line">    <span class="comment">//任务开始前将pd数据缓存进PMap</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">setup</span><span class="params">(Context context)</span> <span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//从缓存文件中找到pd.txt</span></span><br><span class="line">        URI[] cacheFiles = context.getCacheFiles();</span><br><span class="line">        <span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Path</span>(cacheFiles[<span class="number">0</span>]);</span><br><span class="line"> </span><br><span class="line">        <span class="comment">//获取文件系统并开流</span></span><br><span class="line">        <span class="type">FileSystem</span> <span class="variable">fileSystem</span> <span class="operator">=</span> FileSystem.get(context.getConfiguration());</span><br><span class="line">        <span class="type">FSDataInputStream</span> <span class="variable">fsDataInputStream</span> <span class="operator">=</span> fileSystem.open(path);</span><br><span class="line"> </span><br><span class="line">        <span class="comment">//通过包装流转换为reader</span></span><br><span class="line">        <span class="type">BufferedReader</span> <span class="variable">bufferedReader</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BufferedReader</span>(</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">InputStreamReader</span>(fsDataInputStream, <span class="string">&quot;utf-8&quot;</span>));</span><br><span class="line"> </span><br><span class="line">        <span class="comment">//逐行读取，按行处理</span></span><br><span class="line">        String line;</span><br><span class="line">        <span class="keyword">while</span> (StringUtils.isNotEmpty(line = bufferedReader.readLine())) &#123;</span><br><span class="line">            String[] fields = line.split(<span class="string">&quot;\t&quot;</span>);</span><br><span class="line">            pMap.put(fields[<span class="number">0</span>], fields[<span class="number">1</span>]);</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="comment">//关流</span></span><br><span class="line">        IOUtils.closeStream(bufferedReader);</span><br><span class="line"> </span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">map</span><span class="params">(LongWritable key, Text value, Context context)</span> <span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line">        String[] fields = value.toString().split(<span class="string">&quot;\t&quot;</span>);</span><br><span class="line"> </span><br><span class="line">        <span class="type">String</span> <span class="variable">pname</span> <span class="operator">=</span> pMap.get(fields[<span class="number">1</span>]);</span><br><span class="line"> </span><br><span class="line">        line.set(fields[<span class="number">0</span>] + <span class="string">&quot;\t&quot;</span> + pname + <span class="string">&quot;\t&quot;</span> + fields[<span class="number">2</span>]);</span><br><span class="line"> </span><br><span class="line">        context.write(line, NullWritable.get());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="计数器应用"><a href="#计数器应用" class="headerlink" title="计数器应用"></a><strong>计数器应用</strong></h2><p>Hadoop为每个作业维护若干内置计数器，以描述多项指标。例如，某些计数器记录已处理的字节数和记录数，使用户可监控已处理的输入数据量和已产生的输出数据量。</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005637224-2119454353.png" alt="1605198645793-3f014cc6-eb52-4cd3-899b-d94887df194d.png"></p><h2 id="数据清洗（ETL）"><a href="#数据清洗（ETL）" class="headerlink" title="数据清洗（ETL）"></a><strong>数据清洗（ETL）</strong></h2><p><font style="color:#000000;">在</font><font style="color:#000000;">运行</font><font style="color:#000000;">核心业务</font><font style="color:#000000;">MapReduce程序之前，往往要先对数据进行清洗，清理</font><font style="color:#000000;">掉不</font><font style="color:#000000;">符合用户</font><font style="color:#000000;">要求</font><font style="color:#000000;">的数据。</font><font style="color:#000000;">清理</font><font style="color:#000000;">的过程往往只</font><font style="color:#000000;">需要</font><font style="color:#000000;">运行Mapper程序，不需要运行Reduce程序。</font></p><hr><hr><p>（1）编写LogMapper类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.mapreduce.weblog;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.LongWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.NullWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Mapper;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LogMapper</span> <span class="keyword">extends</span> <span class="title class_">Mapper</span>&lt;LongWritable, Text, Text, NullWritable&gt;&#123;</span><br><span class="line"></span><br><span class="line"><span class="type">Text</span> <span class="variable">k</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Text</span>();</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">map</span><span class="params">(LongWritable key, Text value, Context context)</span> <span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 1 获取1行数据</span></span><br><span class="line"><span class="type">String</span> <span class="variable">line</span> <span class="operator">=</span> value.toString();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2 解析日志</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">result</span> <span class="operator">=</span> parseLog(line,context);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3 日志不合法退出</span></span><br><span class="line"><span class="keyword">if</span> (!result) &#123;</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 4 设置key</span></span><br><span class="line">k.set(line);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 5 写出数据</span></span><br><span class="line">context.write(k, NullWritable.get());</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 2 解析日志</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">parseLog</span><span class="params">(String line, Context context)</span> &#123;</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 1 截取</span></span><br><span class="line">String[] fields = line.split(<span class="string">&quot; &quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2 日志长度大于11的为合法</span></span><br><span class="line"><span class="keyword">if</span> (fields.length &gt; <span class="number">11</span>) &#123;</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 系统计数器</span></span><br><span class="line">context.getCounter(<span class="string">&quot;map&quot;</span>, <span class="string">&quot;true&quot;</span>).increment(<span class="number">1</span>);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;<span class="keyword">else</span> &#123;</span><br><span class="line">context.getCounter(<span class="string">&quot;map&quot;</span>, <span class="string">&quot;false&quot;</span>).increment(<span class="number">1</span>);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>（2）编写LogDriver类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.mapreduce.weblog;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.conf.Configuration;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.fs.Path;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.NullWritable;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.io.Text;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.Job;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.lib.input.FileInputFormat;</span><br><span class="line"><span class="keyword">import</span> org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LogDriver</span> &#123;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 输入输出路径需要根据自己电脑上实际的输入输出路径设置</span></span><br><span class="line">        args = <span class="keyword">new</span> <span class="title class_">String</span>[] &#123; <span class="string">&quot;e:/input/inputlog&quot;</span>, <span class="string">&quot;e:/output1&quot;</span> &#125;;</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 1 获取job信息</span></span><br><span class="line"><span class="type">Configuration</span> <span class="variable">conf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Configuration</span>();</span><br><span class="line"><span class="type">Job</span> <span class="variable">job</span> <span class="operator">=</span> Job.getInstance(conf);</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 2 加载jar包</span></span><br><span class="line">job.setJarByClass(LogDriver.class);</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 3 关联map</span></span><br><span class="line">job.setMapperClass(LogMapper.class);</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 4 设置最终输出类型</span></span><br><span class="line">job.setOutputKeyClass(Text.class);</span><br><span class="line">job.setOutputValueClass(NullWritable.class);</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 设置reducetask个数为0</span></span><br><span class="line">job.setNumReduceTasks(<span class="number">0</span>);</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 5 设置输入和输出路径</span></span><br><span class="line">FileInputFormat.setInputPaths(job, <span class="keyword">new</span> <span class="title class_">Path</span>(args[<span class="number">0</span>]));</span><br><span class="line">FileOutputFormat.setOutputPath(job, <span class="keyword">new</span> <span class="title class_">Path</span>(args[<span class="number">1</span>]));</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 6 提交</span></span><br><span class="line">job.waitForCompletion(<span class="literal">true</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005637473-664761047.png" alt="1605198759162-3e4dc63f-21d1-4721-9b24-8c39097d0b18.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005637740-561846797.png" alt="1605198776489-dd8cffcb-ed88-47d7-9af7-1dc0fbdbdf4c.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251013005638030-1152774346.png" alt="1605198794104-fd6a3b1f-adee-4eb0-a69c-dd76d67d915d.png"></p>]]>
    </content>
    <id>https://www.chucz.asia/2026/04/09/Hadoop-MapReduce/</id>
    <link href="https://www.chucz.asia/2026/04/09/Hadoop-MapReduce/"/>
    <published>2026-04-09T06:42:38.000Z</published>
    <summary>
      <![CDATA[<p>dr.who是通过http连接的默认用户，可以直接在配置文件里面修改为当前用户，重启之后就可以使用当前用户在页面里面对文件进行相关操作。</p>
<h1 id="MapReduce概述"><a href="#MapReduce概述" class="headerlink"]]>
    </summary>
    <title>Hadoop-MapReduce</title>
    <updated>2026-04-09T06:42:38.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>褚成志</name>
    </author>
    <category term="操作系统" scheme="https://www.chucz.asia/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    <category term="Linux" scheme="https://www.chucz.asia/tags/Linux/"/>
    <category term="性能分析" scheme="https://www.chucz.asia/tags/%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90/"/>
    <category term="总结" scheme="https://www.chucz.asia/tags/%E6%80%BB%E7%BB%93/"/>
    <content>
      <![CDATA[<h2 id=""><a href="#" class="headerlink" title=""></a><img src="https://cdn.chucz.asia/blog/3703820-20251012214506920-1834230234.png" alt="1625716033169-83d79d81-bcb1-4636-a05e-0b9a415b8a9b.png"></h2><h2 id="分析性能问题"><a href="#分析性能问题" class="headerlink" title="分析性能问题"></a>分析性能问题</h2><p><strong><font style="color:#F5222D;">从系统资源瓶颈的角度来说，USE 法是最为有效的方法，即从使用率、饱和度以及错误数 这三个方面，来分析 CPU、内存、磁盘和文件系统 I&#x2F;O、网络以及内核资源限制等各类软 硬件资源。</font></strong></p><p><strong><font style="color:#F5222D;"></font></strong></p><p><strong><font style="color:#F5222D;">从应用程序瓶颈的角度来说，资源瓶颈跟系统资源瓶颈，本质是一样的。依赖服务瓶颈，你可以使用全链路跟踪系统进行定位。而应用自身的问题，你可以通过系统调用、热点函数，或者应用自身的指标监控以及日志 监控等，进行分析定位。 </font></strong></p><p><strong><font style="color:#F5222D;"></font></strong></p><p><strong><font style="color:#F5222D;">值得注意的是，虽然我把瓶颈分为了系统和应用两个角度，但在实际运行时，这两者往往是 相辅相成、相互影响的。系统是应用的运行环境，系统的瓶颈会导致应用的性能下降；而应 用的不合理设计，也会引发系统资源的瓶颈。我们做性能分析，就是要结合应用程序和操作系统的原理，揪出引发问题的真凶。</font></strong></p><h3 id="系统资源瓶颈"><a href="#系统资源瓶颈" class="headerlink" title="系统资源瓶颈"></a>系统资源瓶颈</h3><p>USE 法，即使用 率、饱和度以及错误数这三类指标来衡量。</p><p>资源列表：</p><ul><li>CPU、内存、磁盘和文件系统以及网络等，都是最常见的硬件资源。</li><li>文件描述符数、连接跟踪数、套接字缓冲区大小等，则是典型的软件资源。</li></ul><p>收到监控系统告警时，就可以对照这些资源列表，再根据指标的不同来进行定位。</p><h3 id="系统资源：CPU-性能分析"><a href="#系统资源：CPU-性能分析" class="headerlink" title="系统资源：CPU 性能分析"></a>系统资源：CPU 性能分析</h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214507276-301009406.png" alt="1625714701744-0de8ccae-70d6-4c5e-a112-d11bfb308dc8.png"></p><p>利用 top、vmstat、pidstat、strace 以及 perf 等几个最常见的工具， 获取 CPU 性能指标后，再结合进程与 CPU 的工作原理，就可以迅速定位出 CPU 性能瓶颈 的来源。</p><p>top、pidstat、vmstat 这类工具所汇报的 CPU 性能指标，都源自 &#x2F;proc 文件系 统（比如 &#x2F;proc&#x2F;loadavg、&#x2F;proc&#x2F;stat、&#x2F;proc&#x2F;softirqs 等）。这些指标，都应该通过监控 系统监控起来。</p><p>收到系统的用户 CPU 使用率过高告警时，从监控系统中直接查询到，导致 CPU 使用率过高的进程；然后再登录到进程所在的 Linux 服务器中，分析该进程的行为。使用 strace，查看进程的系统调用汇总；也可以使用 perf 等工具，找出进程的热点函数；甚至还可以使用动态追踪的方法，来观察进程的当前执行过程，直到确定瓶颈的根 源。</p><h3 id="系统资源：内存性能分析"><a href="#系统资源：内存性能分析" class="headerlink" title="系统资源：内存性能分析"></a>系统资源：内存性能分析</h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214507459-599903193.png" alt="1625714812517-f4e1cdbc-2650-4cf1-92d0-6b849e27b9a4.png"></p><p>free 和 vmstat 输出的性 能指标，确认内存瓶颈；然后，再根据内存问题的类型，进一步分析内存的使用、分配、泄 漏以及缓存等，最后找出问题的来源。</p><p>很多内存的性能指标，也来源于 &#x2F;proc 文件系统（比如 &#x2F;proc&#x2F;meminfo、&#x2F;proc&#x2F;slabinfo 等），它们也都应该通过监控系统监控起来。</p><p>收到内存不足的告警时，首先可以从监控系统中。找出占用内存最多的几个进 程。然后，再根据这些进程的内存占用历史，观察是否存在内存泄漏问题。确定出最可疑的 进程后，再登录到进程所在的 Linux 服务器中，分析该进程的内存空间或者内存分配，最 后弄清楚进程为什么会占用大量内存。</p><h3 id="系统资源：磁盘和文件系统-I-O-性能分析"><a href="#系统资源：磁盘和文件系统-I-O-性能分析" class="headerlink" title="系统资源：磁盘和文件系统 I&#x2F;O 性能分析"></a>系统资源：磁盘和文件系统 I&#x2F;O 性能分析</h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214507687-121001019.png" alt="1625714883480-d8ce8af2-eae7-4265-a913-40e74c34b92e.png"></p><p>iostat ，发现磁盘 I&#x2F;O 存在性能瓶颈（比如 I&#x2F;O 使用率过 高、响应时间过长或者等待队列长度突然增大等）后，可以再通过 pidstat、 vmstat 等， 确认 I&#x2F;O 的来源。接着，再根据来源的不同，进一步分析文件系统和磁盘的使用率、缓存 以及进程的 I&#x2F;O 等，从而揪出 I&#x2F;O 问题的真凶。</p><p>很多磁盘和文件系统的性能指标，也来源于 &#x2F;proc 和 &#x2F;sys 文件 系统（比如 &#x2F;proc&#x2F;diskstats、&#x2F;sys&#x2F;block&#x2F;sda&#x2F;stat 等）。自然，它们也应该通过监控系统 监控起来。</p><p>发现某块磁盘的 I&#x2F;O 使用率为 100% 时，首先可以从监控系统中，找出 I&#x2F;O 最多的进程。然后，再登录到进程所在的 Linux 服务器中，借助 strace、lsof、perf 等工 具，分析该进程的 I&#x2F;O 行为。最后，再结合应用程序的原理，找出大量 I&#x2F;O 的原因。</p><h3 id="系统资源：网络性能分析"><a href="#系统资源：网络性能分析" class="headerlink" title="系统资源：网络性能分析"></a>系统资源：网络性能分析</h3><p>网络接口和内核资源</p><p>从 Linux 网络协议栈的原理来切入。包括应用层、套机字接口、传输层、网络层以及 链路层等。通过使用率、饱和度以及错误数这 几类性能指标，观察是否存在性能问题。</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214507914-1255828851.png" alt="1625714979845-e52430a7-04b1-4207-8f15-0c6e5fa419e0.png"></p><ol><li>在链路层，可以从网络接口的吞吐量、丢包、错误以及软中断和网络功能卸载等角度分 析；</li><li>在网络层，可以从路由、分片、叠加网络等角度进行分析；</li><li>在传输层，可以从 TCP、UDP 的协议原理出发，从连接数、吞吐量、延迟、重传等角度 进行分析； 在应用层，可以从应用层协议（如 HTTP 和 DNS）、请求数（QPS）、套接字缓存等角 度进行分析。</li></ol><p>网络的性能指标也都来源于内核，包括 &#x2F;proc 文件系统（如 &#x2F;proc&#x2F;net）、网络接口以及 conntrack 等内核模块。这些指标同样需要被监控系统监控。</p><p>收到网络不通的告警时，就可以从监控系统中，查找各个协议层的丢包指标，确认丢包所在的协议层。然后，从监控系统的数据中，确认网络带宽、缓冲区、连接跟踪数等 软硬件，是否存在性能瓶颈。最后，再登录到发生问题的 Linux 服务器中，借助 netstat、 tcpdump、bcc 等工具，分析网络的收发数据，并且结合内核中的网络选项以及 TCP 等网 络协议的原理，找出问题的来源。</p><h3 id="应用程序瓶颈"><a href="#应用程序瓶颈" class="headerlink" title="应用程序瓶颈"></a>应用程序瓶颈</h3><p>最典 型的应用程序性能问题，就是**<font style="color:#F5222D;">吞吐量（并发请求数）下降、错误率升高以及响应时间增大</font>**。</p><ul><li>第一种资源瓶颈，其实还是指刚才提到的 CPU、内存、磁盘和文件系统 I&#x2F;O、网络以及内 核资源等各类软硬件资源出现了瓶颈，从而导致应用程序的运行受限。对于这种情况，我们 就可以用前面系统资源瓶颈模块提到的各种方法来分析。</li><li>第二种依赖服务的瓶颈，也就是诸如数据库、分布式缓存、中间件等应用程序，直接或者间 接调用的服务出现了性能问题，从而导致应用程序的响应变慢，或者错误率升高。这说白了 就是跨应用的性能问题，使用全链路跟踪系统，就可以帮你快速定位这类问题的根源。</li><li>最后一种，应用程序自身的性能问题，包括了多线程处理不当、死锁、业务算法的复杂度过 高等等。对于这类问题，观察关键环 节的耗时和内部执行过程中的错误，就可以帮你缩小问题的范围。</li></ul><p>需要应用程序在设计和开发时，就提供出这些指标，以便监控系统可以了解应用程序的内部运行 状态。如果这些手段过后还是无法找出瓶颈，你还可以用系统资源模块提到的各类进程分析工具， 来进行分析定位。</p><h2 id="优化性能问题"><a href="#优化性能问题" class="headerlink" title="优化性能问题"></a>优化性能问题</h2><p>从系统的角度来说，CPU、内存、磁盘和文件系统 I&#x2F;O、网络以及内核数据结构等各类软硬件资源，需要优化。</p><p>从应用程序的角度来说，降低 CPU 使用，减少数据访问和网络 I&#x2F;O，使用缓存、异步处理 以及多进程多线程等，都是常用的性能优化方法。除了这些单机优化方法，调整应用程序的 架构，或是利用水平扩展，将任务调度到多台服务器中并行处理，也是常用的优化思路。</p><p>一定要避免过早优化。性能优化往往会提 高复杂性，这一方面降低了可维护性，另一方面也为适应复杂多变的新需求带来障碍。</p><p>性能优化最好是逐步完善，动态进行；不追求一步到位，而要首先保证，能满足当前的性能要求。</p><p>发现性能不满足要求或者出现性能瓶颈后，再根据性能分析的结果，选择最重 要的性能问题进行优化。</p><h3 id="系统优化"><a href="#系统优化" class="headerlink" title="系统优化"></a>系统优化</h3><h4 id="CPU-优化"><a href="#CPU-优化" class="headerlink" title="CPU 优化"></a>CPU 优化</h4><ul><li>第一种，把进程绑定到一个或者多个 CPU 上，充分利用 CPU 缓存的本地性，并减少进 程间的相互影响。</li><li>第二种，为中断处理程序开启多 CPU 负载均衡，以便在发生大量中断时，可以充分利用 多 CPU 的优势分摊负载。</li><li>第三种，使用 Cgroups 等方法，为进程设置资源限制，避免个别进程消耗过多的 CPU。 同时，为核心应用程序设置更高的优先级，减少低优先级任务的影响。</li></ul><h4 id="内存优化"><a href="#内存优化" class="headerlink" title="内存优化"></a>内存优化</h4><p>可用内存不足、内存泄漏、 Swap 过多、缺页异常过多以及缓存过多等等</p><ul><li>第一种，除非有必要，Swap 应该禁止掉。这样就可以避免 Swap 的额外 I&#x2F;O ，带来内 存访问变慢的问题。</li><li>第二种，使用 Cgroups 等方法，为进程设置内存限制。这样就可以避免个别进程消耗过 多内存，而影响了其他进程。对于核心应用，还应该降低 oom_score，避免被 OOM 杀 死。</li><li>第三种，使用大页、内存池等方法，减少内存的动态分配，从而减少缺页异常。</li></ul><h4 id="磁盘和文件系统-I-O-优化"><a href="#磁盘和文件系统-I-O-优化" class="headerlink" title="磁盘和文件系统 I&#x2F;O 优化"></a>磁盘和文件系统 I&#x2F;O 优化</h4><ul><li>第一种，也是最简单的方法，通过 SSD 替代 HDD、或者使用 RAID 等方法，提升 I&#x2F;O 性能。</li><li>第二种，针对磁盘和应用程序 I&#x2F;O 模式的特征，选择最适合的 I&#x2F;O 调度算法。比如， SSD 和虚拟机中的磁盘，通常用的是 noop 调度算法；而数据库应用，更推荐使用 deadline 算法。</li><li>第三，优化文件系统和磁盘的缓存、缓冲区，比如优化脏页的刷新频率、脏页限额，以及 内核回收目录项缓存和索引节点缓存的倾向等等。</li><li>除此之外，使用不同磁盘隔离不同应用的数据、优化文件系统的配置选项、优化磁盘预读、 增大磁盘队列长度等，也都是常用的优化思路。</li></ul><h4 id="网络优化"><a href="#网络优化" class="headerlink" title="网络优化"></a>网络优化</h4><ul><li>从内核资源和网络协议的角度来说，我们可以对内核选项进行优化，比如：<ul><li>可以增大套接字缓冲区、连接跟踪表、最大半连接数、最大文件描述符数、本地端口范 围等内核资源配额；</li><li>可以减少 TIMEOUT 超时时间、SYN+ACK 重传数、Keepalive 探测时间等异常处理 参数；</li><li>可以开启端口复用、反向地址校验，并调整 MTU 大小等降低内核的负担。</li></ul></li><li>其次，从网络接口的角度来说，我们可以考虑对网络接口的功能进行优化，<ul><li>将原来 CPU 上执行的工作，卸载到网卡中执行，即开启网卡的 GRO、GSO、 RSS、VXLAN 等卸载功能；</li><li>可以开启网络接口的多队列功能，这样，每个队列就可以用不同的中断号，调度到不同 CPU 上执行；</li><li>可以增大网络接口的缓冲区大小以及队列长度等，提升网络传输的吞吐量。 </li><li>在极限性能情况（比如 C10M）下，内核的网络协议栈可能是最主要的性能瓶颈， 所以，一般会考虑绕过内核协议栈。<ul><li>可以使用 DPDK 技术，跳过内核协议栈，直接由用户态进程用轮询的方式，来处理网 络请求。同时，再结合大页、CPU 绑定、内存对齐、流水线并发等多种机制，优化网络 包的处理效率。</li><li>还可以使用内核自带的 XDP 技术，在网络包进入内核协议栈前，就对其进行处理。这 样，也可以达到目的，获得很好的性能。</li></ul></li></ul></li></ul><h3 id="应用程序优化"><a href="#应用程序优化" class="headerlink" title="应用程序优化"></a>应用程序优化</h3><p>系统的软硬件资源，是保证应用程序正常运行的基础，性能优化的最佳位 置，还是应用程序内部。</p><ul><li>系统 CPU 使用率（sys%）过高的问题。有时候出现问题，虽然表面现象是 系统 CPU 使用率过高，应用程序的不合理系统调用才是 罪魁祸首。这种情况下，优化应用程序内部系统调用的逻辑，显然要比优化内核要简单也有 用得多。</li><li>数据库的 CPU 使用率高、I&#x2F;O 响应慢，也是最常见的一种性能问题。并不是因为数据库本身性能不好，而是应用程序不合理的表结构或者 SQL 查询语句导致的。这时候，优化应用程序中数据库表结构的逻辑或者 SQL 语句，显然要比 优化数据库本身，能带来更大的收益。</li></ul><p>所以，在观察性能指标时，先查看应用程序的响应时间、吞吐量以及错误率等指标， 因为它们才是性能优化要解决的终极问题。</p><ul><li>第一，从 CPU 使用的角度来说，简化代码、优化算法、异步处理以及编译器优化等，都 是常用的降低 CPU 使用率的方法，这样可以利用有限的 CPU 处理更多的请求。</li><li>第二，从数据访问的角度来说，使用缓存、写时复制、增加 I&#x2F;O 尺寸等，都是常用的减 少磁盘 I&#x2F;O 的方法，这样可以获得更快的数据处理速度。 </li><li>第三，从内存管理的角度来说，使用大页、内存池等方法，可以预先分配内存，减少内存 的动态分配，从而更好地内存访问性能。</li><li>第四，从网络的角度来说，使用 I&#x2F;O 多路复用、长连接代替短连接、DNS 缓存等方法， 可以优化网络 I&#x2F;O 并减少网络请求数，从而减少网络延时带来的性能问题。</li><li>第五，从进程的工作模型来说，异步处理、多线程或多进程等，可以充分利用每一个 CPU 的处理能力，从而提高应用程序的吞吐能力。</li><li>除此之外，你还可以使用消息队列、CDN、负载均衡等各种方法，来优化应用程序的架 构，将原来单机要承担的任务，调度到多台服务器中并行处理。这样也往往能获得更好的整 体性能。</li></ul><h2 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h2><p>从性能指标出发，根据性能指标的不同，找工具。</p><p>info 可以理解为 man 的详细版本，只有在 man 的输出不太好理解时，才会再去参考 info 文档。</p><p>有些工具不需要额外安装，就可以直接使用，比如内核的 &#x2F;proc 文件系统；</p><p>而有些工具，则需要安装额外的软件包，比如 sar、pidstat、iostat 等。</p><h3 id="CPU-性能工具"><a href="#CPU-性能工具" class="headerlink" title="CPU 性能工具"></a>CPU 性能工具</h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214508151-672223088.png" alt="1625716121515-500db694-11ca-4c8e-acf5-c5cfaa5ee5fe.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214508358-1144027624.png" alt="1625716323977-56662f31-cf73-41c6-8310-acd4ba72dee8.png"></p><h3 id="内存性能工具"><a href="#内存性能工具" class="headerlink" title="内存性能工具"></a>内存性能工具</h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214508563-872761964.png" alt="1625716338019-de04ff64-fb75-4241-a096-644f96d1bc77.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214508750-180904777.png" alt="1625716352381-eeb56175-5c5b-4f3d-b107-b334bea36b44.png"></p><h3 id="磁盘-I-O-性能工具"><a href="#磁盘-I-O-性能工具" class="headerlink" title="磁盘 I&#x2F;O 性能工具"></a>磁盘 I&#x2F;O 性能工具</h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214508958-1440117394.png" alt="1625716363335-e025b522-c544-4366-bbe3-3e77a33202cc.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214509157-1572387911.png" alt="1625716371527-97726463-e349-4498-8ac7-adc9c6c9364e.png"></p><h3 id="网络性能工具"><a href="#网络性能工具" class="headerlink" title="网络性能工具"></a>网络性能工具</h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214509330-332059511.png" alt="1625716380147-874c1db6-740a-4e62-af11-f025e92ad924.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214509524-1140691324.png" alt="1625716392215-96d61c69-1f0d-438a-a493-81366248c495.png"></p><h3 id="基准测试工具"><a href="#基准测试工具" class="headerlink" title="基准测试工具"></a>基准测试工具</h3><p>除了性能分析外，我们还需要对系统性能进行基准测试。比如，使用 fio 工具，测试磁盘 I&#x2F;O 的性能。使用 iperf、pktgen 等，测试网络的性能。使用 ab、wrk 等，测试 Nginx 应用的性能。</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214509753-825178382.png" alt="1625716402991-1d76badd-f1be-4615-809f-c5813d658a2f.png"></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ol><li>从性能瓶颈出发，根据系统和应用程序的运行原理，确认待分析的性能指标。</li><li>选出最合适的性能工具，然后了解并使用工具，从而更快观测到 需要的性能数据。</li></ol><p>当然，正如咱们专栏一直强调的，不要把性能工具当成性能分析和优化的全部。</p><ul><li>一方面，性能分析和优化的核心，是对系统和应用程序运行原理的掌握，而性能工具只是 辅助你更快完成这个过程的帮手。</li><li>另一方面，完善的监控系统，可以提供绝大部分性能分析所需的基准数据。从这些数据中，你很可能就能大致定位出性能瓶颈，也就不用再去手动执行各类工具了。</li></ul>]]>
    </content>
    <id>https://www.chucz.asia/2026/04/09/Linux%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E3%80%81%E8%B0%83%E4%BC%98%E5%A5%97%E8%B7%AF%E4%BB%A5%E5%8F%8A%E5%B7%A5%E5%85%B7%E6%80%BB%E7%BB%93/</id>
    <link href="https://www.chucz.asia/2026/04/09/Linux%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E3%80%81%E8%B0%83%E4%BC%98%E5%A5%97%E8%B7%AF%E4%BB%A5%E5%8F%8A%E5%B7%A5%E5%85%B7%E6%80%BB%E7%BB%93/"/>
    <published>2026-04-09T06:42:38.000Z</published>
    <summary>
      <![CDATA[<h2 id=""><a href="#" class="headerlink" title=""></a><img src="https://cdn.chucz.asia/blog/3703820-20251012214506920-1834230234.png"]]>
    </summary>
    <title>Linux性能分析、调优套路以及工具总结</title>
    <updated>2026-04-09T06:42:38.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>褚成志</name>
    </author>
    <category term="云原生" scheme="https://www.chucz.asia/categories/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    <category term="Kubernetes" scheme="https://www.chucz.asia/tags/Kubernetes/"/>
    <category term="云原生" scheme="https://www.chucz.asia/tags/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    <category term="容器" scheme="https://www.chucz.asia/tags/%E5%AE%B9%E5%99%A8/"/>
    <content>
      <![CDATA[<p>docker-email&#x3D;<a href="mailto:&#50;&#x34;&#x32;&#55;&#x37;&#56;&#x35;&#49;&#x31;&#x36;&#x40;&#x71;&#x71;&#46;&#x63;&#111;&#109;">2427785116@qq.com</a></p><p>##命令格式<br>kubectl create secret docker-registry regcred <br>  –docker-server&#x3D;&lt;你的镜像仓库服务器&gt; <br>  –docker-username&#x3D;&lt;你的用户名&gt; <br>  –docker-password&#x3D;&lt;你的密码&gt; <br>  –docker-email&#x3D;&lt;你的邮箱地址&gt;</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">```yaml</span><br><span class="line">apiVersion: v1</span><br><span class="line">kind: Pod</span><br><span class="line">metadata:</span><br><span class="line">  name: private-nginx</span><br><span class="line">spec:</span><br><span class="line">  containers:</span><br><span class="line">  - name: private-nginx</span><br><span class="line">    image: dockerhub123456wk/redis:v1.0</span><br><span class="line">  imagePullSecrets:</span><br><span class="line">  - name: dockerhub123456wk-docker</span><br></pre></td></tr></table></figure><h1 id="3、Kubernetes卸载"><a href="#3、Kubernetes卸载" class="headerlink" title="3、Kubernetes卸载"></a>3、Kubernetes卸载</h1><blockquote><p>按照顺序执行以下命令：</p></blockquote><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">sudo kubeadm reset -f</span><br><span class="line">sudo rm -rvf $HOME/.kube</span><br><span class="line">sudo rm -rvf ~/.kube/</span><br><span class="line">sudo rm -rvf /etc/kubernetes/</span><br><span class="line">sudo rm -rvf /etc/systemd/system/kubelet.service.d</span><br><span class="line">sudo rm -rvf /etc/systemd/system/kubelet.service</span><br><span class="line">sudo rm -rvf /usr/bin/kube*</span><br><span class="line">sudo rm -rvf /etc/cni</span><br><span class="line">sudo rm -rvf /opt/cni</span><br><span class="line">sudo rm -rvf /var/lib/etcd</span><br><span class="line">sudo rm -rvf /var/etcd</span><br></pre></td></tr></table></figure><blockquote><p>查看是否卸载</p></blockquote><p><code>docker ps</code></p><p>查看是否有关kubernetes的相关容器启动。</p><p>没有则卸载成功。</p>]]>
    </content>
    <id>https://www.chucz.asia/2026/04/09/Kubernetes/</id>
    <link href="https://www.chucz.asia/2026/04/09/Kubernetes/"/>
    <published>2026-04-09T06:42:38.000Z</published>
    <summary>
      <![CDATA[<p>docker-email&#x3D;<a]]>
    </summary>
    <title>Kubernetes</title>
    <updated>2026-04-09T06:42:38.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>褚成志</name>
    </author>
    <category term="操作系统" scheme="https://www.chucz.asia/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    <category term="Linux" scheme="https://www.chucz.asia/tags/Linux/"/>
    <category term="操作系统" scheme="https://www.chucz.asia/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    <category term="总结" scheme="https://www.chucz.asia/tags/%E6%80%BB%E7%BB%93/"/>
    <content>
      <![CDATA[<h1 id="Linux操作系统扫盲汇总"><a href="#Linux操作系统扫盲汇总" class="headerlink" title="Linux操作系统扫盲汇总"></a>Linux操作系统扫盲汇总</h1><h2 id="linux-基本概念概括"><a href="#linux-基本概念概括" class="headerlink" title="linux 基本概念概括"></a>linux 基本概念概括</h2><ul><li><strong>VFS 树</strong><a href="#S6tC0">链接</a>：虚拟文件系统就是一个树，树的根部就是 <code>/</code> , <strong><font style="color:#F5222D;">树上不同的节点，都会指向不同的物理地址</font></strong>（文件系统的目录树的不同节点其实是来自不同的分区），<strong><font style="color:#F5222D;">可以是具体的文件系统，或者网络节点，或者自己虚拟的节点。不同的dev就相当于是挂载到了树上的不同的节点，也就是一个文件夹</font></strong></li><li><strong>FD</strong>：<font style="color:#F5222D;">文件描述符，指向INODE，进程打开文件的时候使用FD找到文件，同时FD是有数量限制的，默认是一个进程1024，可以使用</font><code>&lt;font style=&quot;color:#F5222D;&quot;&gt;ulimit -SHn 65535&lt;/font&gt;</code><font style="color:#F5222D;"> 临时修改，也可以修改文件 </font><code>&lt;font style=&quot;color:#F5222D;&quot;&gt;/etc/security/limits.conf&lt;/font&gt;</code><font style="color:#F5222D;"> 永久生效，在最后一行加入</font><code>&lt;font style=&quot;color:#F5222D;&quot;&gt;- nofile 65535&lt;/font&gt;</code><a href="https://segmentfault.com/a/1190000009724931">参考</a><ul><li><strong><font style="color:#F5222D;background-color:#FADB14;">FD是进程而言的，INODE是文件而言的，多个进程指向同一个文件，就是多个FD指向同一个INODE，</font></strong></li></ul></li><li><strong>指针seek</strong>：<strong>每个应用（进程）读取文件的时候有自己的fd, 有自己的seek 指针</strong></li><li><code>**inode**</code> ： 虚拟文件系统里面的每一个文件都有一个ID，每一个文件打开的时候在内存里面有一个<img src="https://cdn.chucz.asia/blog/3703820-20251012183928159-1398378669.png" alt="1626272959494-9a7f1ec8-f265-4d71-8f40-488555e31875.png"></li><li><code>**/proc**</code> : 内核映射目录，内核的一些属性。<ul><li><font style="color:#F5222D;">系统的变量属性，进程的在这里都会在这里被映射成文件</font></li><li>只有开机之后才存在。</li><li>&#x2F;proc&#x2F;$$ 获取和你当前交互的进程的ID号，$BASHPID 也可以获得</li><li>&#x2F;proc&#x2F;$$&#x2F;fd 目录下是当前进程的所有文件描述符<ul><li>lsof -op $$ 更加细节，查看当前进程打开文件的文件描述符的细节</li></ul></li></ul></li><li>**<font style="color:#F5222D;">page cache</font><strong><strong>：（内核缓冲区就是这个）</strong></strong><font style="color:#F5222D;">优化IO性能，优先走内存。</font>**<strong>会开一个一个页缓存，默认大小是4K。内存里面对数据的缓存，物理内存是一份，两个程序是共享这个pc的，也就是第二个访问的时候是缓存命中了。</strong><ul><li><strong>脏****page cache</strong>：会有一个flush的过程，</li></ul></li><li>**<font style="color:#F5222D;">缓存行</font>****：CPU里面，**<strong>CPU Cache是由最小的存储区块-缓存行(cacheline)组成，缓存行大小通常为64byte。也就是一次读取的数据的大小。</strong><ul><li><strong>一个缓存行对应多个内存块，所以缓存行有一个标志就是组标记tag区分不同的内存块，除了组标记Tag，还有实际数据data以及有效为valid bit，有效位是0的话无论CPU line中是否有数据都会直接访问内存。（具体看在MESI里面起到的作用）</strong></li><li><strong>有</strong>**<font style="color:#F5222D;">伪共享</font>**<strong>的问题，限制一行只放一个数据，就不会因为MESI导致的频繁将数据换入内存的问题发生</strong></li></ul></li><li><font style="color:#F5222D;">进程的NICE</font>：涉及优先级的设置，越小优先级越高（其实是对应的虚拟运行时间越小，完全公平调度算法CFS就会优先调度这个进程。<a href="https://www.processon.com/view/link/60eef701e0b34d06fba955b4">查看关于线程调度</a>）</li><li><strong><font style="color:#F5222D;">Linux内核对进程的完全公平调度CFS算法</font></strong>：是针对CPU普通进程的调度而言的，不涉及<font style="color:#444444;">DeadLine和RealTIME任务，只是Fair任务，大多数的进程（线程（task_struct））都是普通的任务，都使用的是CFS算法，两碗水端平的思想。</font><a href="https://www.processon.com/view/link/60eef701e0b34d06fba955b4"><strong>查看关于线程调度</strong></a><strong>）</strong></li><li><strong>中断：硬中断，软中断</strong></li><li>**异常：**<strong>故障</strong>，操作系统会将控制转移给相应的异常处理程序。如果处理程序能够修正这个错误情况，就将返回到引起异常的指令重新执行。否则，终止该应用程序。</li><li>陷阱：软中断的一种，就是系统调用</li><li>**中断向量表 IVT、中断描述符表 IDT：**中断向量表是一个通用的概念，在不同的架构下有不同的实现，比如在 x86 处理器下的实现是中断描述符表（Interrupt Descriptor Table，IDT）。<ul><li>**<font style="color:#F5222D;">中断向量表（interrupt vector table，IVT）</font><strong>是由一系列中断向量（interrupt vector）组成的列表。每个中断向量都是</strong><font style="color:#F5222D;">一个中断处理程序的入口地址。</font>**中断向量的类型包括：硬件中断、软件中断和处理器异常，这些事件在中断向量表中统一称作中断</li><li>当中断或异常产生时，由硬件负责产生一个中断标记，CPU 根据中断标记获得相应的**<font style="color:#F5222D;">中断向量号</font>****<font style="color:#F5222D;">，然后将其作为偏移，在中断向量表中获得相应的处理程序地址，并执行。</font>**<a href="https://imageslr.com/2020/07/09/trap-interrupt-exception.html"><strong><font style="color:#F5222D;">链接</font></strong></a></li></ul></li></ul><h2 id="系统启动过程"><a href="#系统启动过程" class="headerlink" title="系统启动过程"></a>系统启动过程</h2><ul><li>BIOS uefi 工作</li><li>BIOS 自检：硬件是否有问题</li><li>BIOS 加载bootLoader到内存，<ul><li>BootLoader在硬盘上的位置也是写死的。硬盘的第一个扇区上，也就是主引导记录。<strong><font style="color:#F5222D;">BootLoader决定启动哪一个操作系统</font></strong></li></ul></li><li>读取可配置信息：<ul><li>CMOS ：存可以配置的信息，必须加电，不加电下次开机就没了； 过几年密码也没了，因为自己的电池没电了。</li></ul></li><li><strong><font style="color:#F5222D;">OS的代码放到内存，从OS的指令位置开始执行，之后操作权利放给OS，</font></strong></li></ul><h2 id="CPU和线程和内核"><a href="#CPU和线程和内核" class="headerlink" title="CPU和线程和内核"></a>CPU和线程和内核</h2><p>Register + cache ：存储单元</p><p>ALU：运算单元</p><p>控制单元</p><p>MMU ：内存管理单元，管理虚拟内存</p><h3 id="超线程："><a href="#超线程：" class="headerlink" title="超线程："></a>超线程：</h3><p>一个运算单元对应多组寄存器和PC，不切换线程了，而是直接切换寄存器，没有上下文切换了，</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012183928736-306196169.png" alt="1617440922106-60644071-c3cb-4e53-98aa-5112b4a0f575.png"></p><h3 id="存储器的层次结构"><a href="#存储器的层次结构" class="headerlink" title="存储器的层次结构"></a>存储器的层次结构</h3><p>速度不一样，寄存器，一级缓存（每个核一个），二级缓存（每个核一个），三级缓存（每一个CPU一个），主存（多个CPU共享）</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012183929176-1543075630.png" alt="1617440957228-72d54fe6-f296-4edd-9473-d4392739793d.png"></p><h3 id="CPU的乱序执行"><a href="#CPU的乱序执行" class="headerlink" title="CPU的乱序执行"></a>CPU的乱序执行</h3><p><strong><font style="color:#F5222D;">指令1在读等待（等待内存磁盘返回），指令2不依赖指令1的话，CPU会提高效率让指令2 先执行</font></strong></p><p>禁止指令重排：</p><p>CPU实现：使用内存屏障</p><h4 id="单例为什么要加volatile"><a href="#单例为什么要加volatile" class="headerlink" title="单例为什么要加volatile"></a>单例为什么要加volatile</h4><p>指令重排，提前指向了半初始化状态，</p><h4 id="JVM-规范要求的4个内存屏障"><a href="#JVM-规范要求的4个内存屏障" class="headerlink" title="JVM 规范要求的4个内存屏障"></a>JVM 规范要求的4个内存屏障</h4><p><img src="https://cdn.chucz.asia/blog/3703820-20251012183929705-206205268.png" alt="1617441804847-6d8caace-7f80-4bb6-8537-d75f1699672c.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012183929938-503724633.png" alt="1617441893473-396dc69a-369b-4658-8f51-8e6244bdfcc3.png"></p><p>具体实现的时候可以使用lock指令。</p><h4 id="happens-before–java语言规范"><a href="#happens-before–java语言规范" class="headerlink" title="happens-before–java语言规范"></a>happens-before–java语言规范</h4><p>就是一个保证规则，最后的效果就是看上去是没有重排序的</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012183930276-1660693556.png" alt="1617442017207-3f2892bf-f12f-434c-8241-4418445c3d71.png"></p><h4 id="as-if-serial"><a href="#as-if-serial" class="headerlink" title="as if serial"></a>as if serial</h4><p>像是顺序执行</p><p><strong><font style="color:#F5222D;">不管如何重排序，单线程执行 结果不变，看上去是Serial</font></strong></p><h3 id="用户态和内核态"><a href="#用户态和内核态" class="headerlink" title="用户态和内核态"></a>用户态和内核态</h3><p><strong><font style="color:#F5222D;">CPU分为不同的指令级别，内核态可使用ring0级别的指针，用户态可以使用ring3级别的指令</font></strong></p><p>Linux内核跑在ring 0级，用户程序是ring3级别，<strong><font style="color:#F5222D;">对于系统的关键访问，需要经过kernel的同意，保证系统的健壮性</font></strong></p><p>内核执行的操作–&gt; 200多个系统调用 sendFile read write pthread  fork</p><p>JVM 在 操作系统的角度就是一个普通的程序</p><h3 id="线程和进程和纤程"><a href="#线程和进程和纤程" class="headerlink" title="线程和进程和纤程"></a>线程和进程和纤程</h3><p>进程是资源分配的基本单位，线程是调度的基本单位</p><p>线程就是一个进程的不同执行路径</p><p>在Linux里面线程其实就是一个普通的进程，因为他是通过fork来创建的，只不过和其他进程共享资源（内存空间， 全局数据等等）。其他的操作系统都有自己所谓的LWP实现（Light Weight Process）</p><p>纤程：（<strong>用户空间级别的线程</strong>）线程中的线程，用户态的线程，切换和调度不需要经过OS</p><p>优势：占用资源少，OS的线程是1M，纤程是4K；切换简单不需要经过OS；可以启动好多个10w+，不像CPU多了就全是CPU切换了。</p><p>实现：java中没有内置纤程的支持，需要使用依赖包</p><p>不是对应操作系统里面的重量级线程，而是JVM内部自己调度的线程，原来的线程需要和硬件打交道，切换起来速度比较慢，不然CPU全耗在切换上了。多个纤程对应一个线程，每一个纤程都有自己的栈。</p><h3 id="进程"><a href="#进程" class="headerlink" title="进程"></a>进程</h3><p>Linux中也叫做Task，系统分配资源的基本单位</p><p>资源：独立的地址空间，内核数据结构（进程描述符…）全局变量、数据段…</p><p>进程描述符：PCB（ProcessControlBlock）</p><h4 id="创建和启动"><a href="#创建和启动" class="headerlink" title="创建和启动"></a>创建和启动</h4><p>系统函数fork() exec()</p><p>从A中forkB的话，A就是B的父进程，</p><p>对于主进程 fork()返回新建的子进程ID， 子进程fork()返回0</p><blockquote><p>在语句pid&#x3D;fork()之前，只有一个进程在执行这段代码，但在这条语句之后，就变成两个进程在执行了，这两个进程的代码部分完全相同，将要执行的下一条语句都是if(pid&#x3D;&#x3D;0)……。</p><p>两个进程中，原先就存在的那个被称作“父进程”，新出现的那个被称作“子进程”。父子进程的区别除了进程标志符（process ID）不同外，变量pid的值也不相同，pid存放的是fork的返回值。fork调用的一个奇妙之处就是它仅仅被调用一次，却能够返回两次，它可能有三种不同的返回值：</p><ol><li>在父进程中，fork返回新创建子进程的进程ID；</li><li>在子进程中，fork返回0；</li><li>如果出现错误，fork返回一个负值；</li></ol><p>fork出错可能有两种原因：（1）当前的进程数已经达到了系统规定的上限，这时errno的值被设置为EAGAIN。（2）系统内存不足，这时errno的值被设置为ENOMEM</p></blockquote><h4 id="僵尸进程和孤儿进程"><a href="#僵尸进程和孤儿进程" class="headerlink" title="僵尸进程和孤儿进程"></a>僵尸进程和孤儿进程</h4><ul><li>僵尸进程：每一个父进程都会维护自己子进程的PCB结构，子进程退出之后，父进程释放PCB，但是父进程不释放那么子进程就是一个僵尸进程，此时子进程占用的空间就是PCB的大小<ul><li><img src="https://cdn.chucz.asia/blog/3703820-20251012183930614-1069201814.png" alt="1617427450496-30646dee-291f-40c7-9473-c029c0cf622e.png"></li><li><code>ps -ef | grep defuct</code> <code>defuct 就是僵尸</code></li><li><img src="https://cdn.chucz.asia/blog/3703820-20251012183930993-2064627409.png" alt="1617427413596-0072bc17-6af6-4a4d-b94f-2287c698d6be.png"></li></ul></li><li>孤儿进程：子进程在结束之前，父进程已经退出，孤儿进程就会被1号进程管理，也就是init进程。<ul><li><img src="https://cdn.chucz.asia/blog/3703820-20251012183931257-35867619.png" alt="1617427477379-872e5650-23de-46d7-a4ed-6685aaabd678.png"></li></ul></li></ul><h3 id="内核线程（不重要）"><a href="#内核线程（不重要）" class="headerlink" title="内核线程（不重要）"></a>内核线程（不重要）</h3><p>内核独有，内核启动之后经常需要做一些后台操作，这些由Kernel Thread 来完成只在内核空间运行</p><h3 id="进程调度"><a href="#进程调度" class="headerlink" title="进程调度"></a>进程调度</h3><p>Linux内核什么时候开始运行，运行多长时间，每一个进程有自己对应的调度方案，可以指定，也可以自己实现内核调度方案给内核打补丁。</p><p>单任务独占到多任务分时，原则就是压榨CPU资源</p><p>抢占式：进程调度器强制开始或者暂停，抢占某一进程的执行</p><p>非抢占式：除非进程主动让出（yielding），否则一直运行</p><p>Linux的内核进程调度：Linux2.5 使用经典的Unix O(1) 调度策略，偏向服务器。时间片轮询，对交互不友好。<strong><font style="color:#F5222D;">Linux2.6.23 采用CFS完全公平调度策略算法 Completely Fair Scheduler，按照优先级分配时间片的比例，记录每一个进程 的执行时间，如果有一个进程执行的时间不到他应该分配的比例，优先执行。</font></strong></p><h4 id="进程类型"><a href="#进程类型" class="headerlink" title="进程类型"></a>进程类型</h4><ul><li><strong><font style="color:#F5222D;">IO 密集型：大部分时间都在用于等待IO</font></strong></li><li><strong><font style="color:#F5222D;">CPU密集型：大部分时间计算</font></strong></li></ul><h4 id="进程优先级"><a href="#进程优先级" class="headerlink" title="进程优先级"></a>进程优先级</h4><ul><li><strong><font style="color:#F5222D;">实时进程 &gt; 普通进程（0-99）</font></strong></li><li><strong><font style="color:#F5222D;">普通进程的nice值（-20 - 19）</font></strong></li></ul><h4 id="时间分配"><a href="#时间分配" class="headerlink" title="时间分配"></a>时间分配</h4><ul><li>Linux采用按照优先级的CPU时间比</li><li>其他系统使用按优先级的时间片</li></ul><h4 id="Linux默认的调度策略（看导图）"><a href="#Linux默认的调度策略（看导图）" class="headerlink" title="Linux默认的调度策略（看导图）"></a>Linux默认的调度策略<font style="color:#F5222D;">（看导图）</font></h4><ul><li>**<font style="color:#F5222D;">对于实时的进程：</font>**使用SCHD_FIFO（优先级分高低） 和 SCHED_RR（轮询） 两种<ul><li>FIFO 是等级最高的，除非自己让出CPU否则一直会执行它，也就是除非更高级别的FIFO或者RR抢占它</li><li>RR只是这种线程中是同级别FIFO的平均分配</li></ul></li><li>**<font style="color:#F5222D;">对于普通的进程：</font>**使用CFS：按照优先级分配时间片的比例，记录每一个进程的执行时间，如果有一个进程执行时间不到他应该分配的比例，优先执行</li></ul><p><strong><font style="color:#F5222D;">只有实时的进程主动让出，或者执行完毕之后，普通的进程才有机会运行</font></strong></p><h3 id="中断"><a href="#中断" class="headerlink" title="中断"></a>中断</h3><blockquote><p>硬件的电信号控制了软件的输出</p></blockquote><p>按键–&gt; 中断控制器 –&gt; CPU芯片 –&gt; kernerl –&gt; 中断处理程序 –&gt; 上半场下半场</p><ul><li>CPU芯片 只是知道中断了，不知道中断谁，</li><li>会去操作系统找kernel，让kernel说出是中断谁，interrupt 80 中断，软中断就是80中断</li></ul><p>中断是一个信号，内核暂停处理，不一定会处理。</p><ul><li>硬中断：1号键盘，2号鼠标</li><li>软中断：read 。read、fork ,内核会停下自己，拿到参数去执行返回<ul><li><strong>系统调用</strong>：<strong><font style="color:#F5222D;">interrupt 0x80（C语言层面） 或者 sysenter （原语层面）原语，通过ax寄存器填入调用号（调用号就代表使用的是几号函数，read fork等等）。参数通过bx cx dx si di 传入内核，返回值通过ax 返回</font></strong></li><li><font style="color:#F5222D;">java读网络：</font><strong><font style="color:#F5222D;">jvm read() –&gt; c read() –&gt; </font><strong><strong><font style="color:#F5222D;background-color:#FADB14;">内核空间 也就是inter 80 这个中断</font></strong></strong><font style="color:#F5222D;"> –&gt; system_call() 系统调用处理程序，查看ax参数里面使用的内核函数 </font><strong><strong><font style="color:#F5222D;">–&gt; sys_read()，</font></strong></strong><font style="color:#F5222D;">之后再去剩下的五个</font></strong><font style="color:#F5222D;">bx cx dx si di 读取参数</font><strong><font style="color:#F5222D;"> ，最后将结果写在ax,返回ax，程序去ax读取结果即可 </font></strong></li></ul></li></ul><h3 id="int-0x80"><a href="#int-0x80" class="headerlink" title="int 0x80"></a>int 0x80</h3><p>系统调用会调用这个，这个会导致中断，中断映射表找到 callback的地址开始执行对应的系统调用</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012183931611-1599819509.png" alt="1617503955067-cc9de19e-a070-4e48-8eab-8296d764b629.png"></p><h3 id="虚拟内存管理–MMU"><a href="#虚拟内存管理–MMU" class="headerlink" title="虚拟内存管理–MMU"></a>虚拟内存管理–MMU</h3><h4 id="发展历程"><a href="#发展历程" class="headerlink" title="发展历程"></a>发展历程</h4><p>DOS：同一时间只有一个进程在运行</p><p>win 9x ：多个进程都放入1. 内存撑爆，2. 互相打扰，会访问到别人的空间</p><ul><li><font style="color:#F5222D;">解决内存爆的问题</font>：程序**<font style="background-color:#FADB14;">分页</font>**，内存划分为一个个个page frame页框，固定大小，装入分页之后的程序，页框大小就是4K，内存标准页大小就是4K，<ul><li>局部性原理，时间局部性空间局部性</li><li>内存满了，进入swap交换分区，<a href="%5Bhttps://leetcode-cn.com/problems/lru-cache/%5D(https://leetcode-cn.com/problems/lru-cache/)">LRU算法</a><ul><li>其实就是LinkedHashMap的实现，也就是hashMap保证查找是O1，链表保证删除插入是O1</li><li>还必须是双向链表就是为了避免找左右的时候还需要从头开始遍历</li></ul></li><li>一个进程新建分配资源的时候只是分配了一张表，内存都没分配，记录了程序的页表在硬盘的位置就行了</li><li>缺页中断：内存中没有要使用的页面，产生缺页异常，由内核处理并且</li></ul></li><li><font style="color:#F5222D;">解决相互打扰的问题</font>：<strong><font style="background-color:#FADB14;">虚拟内存</font></strong>。直接访问程序的物理内存地址是比较危险的，为了保证不会互相影响，所以程序使用的空间地址不是物理空间，而是虚拟的地址，A永远访问不了B的地址<ul><li>虚拟空间的大小：寻址空间，64位的操作系统就是 2^ 64的大小，只要自己可以表示就可以，单位是bit</li><li>在程序的角度，进程是独享整个系统+CPU的，每一个进程虚拟的独占整个CPU</li><li><img src="https://cdn.chucz.asia/blog/3703820-20251012183931890-2075520066.png" alt="1617437960230-012ac65e-3a2d-4256-9a91-c1f2603f39d6.png"><ul><li>**<font style="color:#F5222D;">段页式：</font>**进程的虚拟内存是分段的（按照程序功能），分段里面才是分页，需要该页的时候加载到页框</li></ul></li><li><strong>逻辑地址转换为虚拟空间的线性地址再映射到物理地址</strong>：内存映射的线性地址 &#x3D; 基地址+偏移量（逻辑地址），操作系统+MMU来完成线性地址最终找到物理地址的转换<ul><li><img src="https://cdn.chucz.asia/blog/3703820-20251012183932215-80433554.png" alt="1617438537611-c217ab74-6459-4e1e-8b7c-f35e34805e88.png"></li></ul></li></ul></li><li>缺页异常：产生一个软中断去读取数据</li></ul><h4 id="MMU-虚拟内存管理单元"><a href="#MMU-虚拟内存管理单元" class="headerlink" title="MMU 虚拟内存管理单元"></a>MMU 虚拟内存管理单元</h4><p>内存管理单元：使得每一个进程都有自己独立的虚拟地址空间。</p><p>作用：</p><ul><li>地址转换：将线性地址映射为物理地址</li><li>提供硬件机制的内存访问授权</li></ul><p><font style="color:#000000;">大多数使用MMU的机器都采用</font><strong>分页机制</strong><font style="color:#000000;">。虚拟地址空间以</font><strong>页</strong><font style="color:#000000;">为单位进行划分，而相应的物理地址空间也被划分，其使用的单位称为</font><strong>页帧</strong><font style="color:#000000;">，页帧和页必须保持相同，因为</font><strong>内存与外部存储器之间的传输是以页为单位进行传输的</strong><font style="color:#000000;">。</font></p><p><font style="color:#000000;"></font></p><p><font style="color:#000000;">如果处理器启用了MMU，CPU执行单元发出的内存地址将被MMU截获，</font><strong><font style="color:#F5222D;">从CPU到MMU的地址称为虚拟地址</font></strong><font style="color:#000000;">（</font><strong>Virtual Address</strong><font style="color:#000000;">，以下简称</font><strong>VA</strong><font style="color:#000000;">），而</font><strong><font style="color:#F5222D;">MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上，也就是将VA映射成PA</font></strong></p><p><font style="color:#000000;"></font></p><h4 id="快表"><a href="#快表" class="headerlink" title="快表"></a>快表</h4><p>快表其实是对MMU的加速，和MMU交互太慢所以直接和CPU里面的快表交互</p><h2 id="物理内存"><a href="#物理内存" class="headerlink" title="物理内存"></a>物理内存</h2><h3 id="page-cache"><a href="#page-cache" class="headerlink" title="page cache"></a>page cache</h3><p>物理内存上是一个。<strong><font style="color:#F5222D;background-color:#FADB14;">但是可以有多个进程，每一个进程有自己的fd，维护自己的seek，所以页缓存是一份，但是不同的进程之间读取整个文件不会相互影响。（明析fd和INODE的关系，多个fd可以指向一个INODE，fd是针对进程而言的）</font></strong></p><h3 id="DMA-直接存储器访问"><a href="#DMA-直接存储器访问" class="headerlink" title="DMA-直接存储器访问"></a>DMA-直接存储器访问</h3><p>在实现DMA传输时，是由DMA控制器直接掌管总线，因此，存在着一个总线控制权转移问题。</p><p>即DMA传输前，CPU要把总线控制权交给DMA控制器，而在结束DMA传输后，DMA控制器应立即把总线控制权再交回给CPU。一个完整的DMA传输过程必须经过DMA请求、DMA响应、DMA传输、DMA结束4个步骤。</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012183932580-1906956844.png" alt="1617504576348-76eaf47e-4a1d-4bcb-a0de-548645950da4.png"></p><h2 id="虚拟文件系统–VFS树"><a href="#虚拟文件系统–VFS树" class="headerlink" title="虚拟文件系统–VFS树"></a>虚拟文件系统–VFS树</h2><h3 id="df-h-查看虚拟目录树挂载的真正物理地址"><a href="#df-h-查看虚拟目录树挂载的真正物理地址" class="headerlink" title="df -h 查看虚拟目录树挂载的真正物理地址"></a>df -h 查看虚拟目录树挂载的真正物理地址</h3><p><strong>磁盘分区（也就是</strong><strong><font style="color:#F5222D;">文件系统。都按照一定的文件系统规则进行了格式化</font>****）挂载到VFS 树的不同目录节点，</strong></p><p>其中系统启动会将内核的镜像文件系统（图中的&#x2F;dev&#x2F;sda1）加载之后，挂载到虚拟节点 &#x2F;boot 下，会将操作系统（图中的&#x2F;dev&#x2F;mapper&#x2F;centos-root）的文件系统挂载到虚拟节点 &#x2F; 下，所以其实是 &#x2F;dev&#x2F;sda1 的文件系统覆盖了 &#x2F;dev&#x2F;mapper&#x2F;centos-root 对于虚拟节点 &#x2F;boot 的挂载，可以自己手动去除虚拟节点 &#x2F;boot 上文件系统&#x2F;dev&#x2F;sda1 的挂载，得到 &#x2F;boot 就是空的目录了</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012183932859-546730662.png" alt="1617464065507-5ab83057-f707-4829-bfa6-501a803212a2.png"></p><p>在将文件系统挂载到VFS树上的 &#x2F;boot 虚拟节点上，文件夹里面又有了内容：</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012183933198-130158107.png" alt="1617464435006-6a7baa5f-246f-48ae-8a2e-5fd1ae765b6b.png"></p><p><strong><font style="color:#F5222D;">所以虚拟目录树是Linux的一个规范，结构是稳定化的。至于是哪一个文件系统挂载到这个树上的哪个节点是可以灵活改变的！！！</font></strong></p><h2 id="Linux-文件类型"><a href="#Linux-文件类型" class="headerlink" title="Linux 文件类型"></a>Linux 文件类型</h2><p>冯诺依曼计算机：控制器（CPU）存储器（主存内存）输入输出设备（IO设设备）。</p><p><strong><font style="color:#F5222D;">虚拟文件系统的抽象，就是一切皆文件。</font></strong></p><ul><li><code>&lt;font style=&quot;color:#F5222D;&quot;&gt;-&lt;/font&gt;</code><font style="color:#F5222D;"> : 普通文件（可执行文件）</font><font style="color:#F5222D;">TYPE&#x3D;REG</font></li><li><font style="color:#F5222D;">d : 目录   TYPE&#x3D;DIR</font></li><li><font style="color:#F5222D;">b : 块设备  TYPE&#x3D;CHR，可以随意漂移，硬盘等等 （&#x2F;dev目录下）</font></li><li><font style="color:#F5222D;">c : 字符设备，不能随意漂移，键盘网卡都是（&#x2F;dev目录下）</font></li><li><font style="color:#F5222D;">s : socket</font></li><li><font style="color:#F5222D;">p : pipline </font></li><li><font style="color:#F5222D;">l : 连接</font></li><li><font style="color:#F5222D;">eventPoll ： 内存提供的epoll区域</font></li><li><font style="color:#F5222D;">等等</font></li></ul><p><strong><font style="color:#F5222D;">ls -l 之后第一列的内容就是文件的类型，之后的就是文件的权限</font></strong>，以及所属 用户以及所属的用户组，文件大小</p><p>&#x2F;dev 目录下会有c 开头的字符设备和 b 开头的块设备</p>]]>
    </content>
    <id>https://www.chucz.asia/2026/04/09/Linux%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E6%89%AB%E7%9B%B2%E6%B1%87%E6%80%BB/</id>
    <link href="https://www.chucz.asia/2026/04/09/Linux%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E6%89%AB%E7%9B%B2%E6%B1%87%E6%80%BB/"/>
    <published>2026-04-09T06:42:38.000Z</published>
    <summary>
      <![CDATA[<h1 id="Linux操作系统扫盲汇总"><a href="#Linux操作系统扫盲汇总" class="headerlink" title="Linux操作系统扫盲汇总"></a>Linux操作系统扫盲汇总</h1><h2 id="linux-基本概念概括"><a]]>
    </summary>
    <title>Linux操作系统扫盲汇总</title>
    <updated>2026-04-09T06:42:38.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>褚成志</name>
    </author>
    <category term="操作系统" scheme="https://www.chucz.asia/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    <category term="Linux" scheme="https://www.chucz.asia/tags/Linux/"/>
    <category term="文件系统" scheme="https://www.chucz.asia/tags/%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F/"/>
    <category term="磁盘" scheme="https://www.chucz.asia/tags/%E7%A3%81%E7%9B%98/"/>
    <content>
      <![CDATA[<p><strong><font style="color:#F5222D;">磁盘为系统提供了最基本的持久化存储。</font></strong></p><p><strong><font style="color:#F5222D;">文件系统则在磁盘的基础上，提供了一个用来管理文件的树状结构。</font></strong></p><p><strong><font style="color:#F5222D;"></font></strong></p><p><strong><font style="color:#F5222D;">“Linux 一切皆文件”的深刻含义。无论是普通文件和块设备、还是网 络套接字和管道等，它们都通过统一的 VFS 接口来访问。</font></strong></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214306231-33113156.jpg" alt="画板"></p><h2 id="索引节点和目录项"><a href="#索引节点和目录项" class="headerlink" title="索引节点和目录项"></a>索引节点和目录项</h2><p>文件系统，本身是**<font style="color:#F5222D;">对存储设备上的文件 进行组织管理的机制</font><strong>。</strong><font style="color:#F5222D;">组织方式不同，就会形成不 同的文件系统。</font>**</p><p>为方便管理，Linux 文件系统为每个文件都分配两个数据结构，索引节点（index node）和目录项（directory entry）。它们主要用来记录文件的元信息和目录结构。</p><ul><li><strong><font style="color:#F5222D;">索引节点，简称为 inode，用来记录文件的元数据，比如 inode 编号、文件大小、访问 权限、修改日期、数据的位置等。</font><strong>索引节点和文件一一对应，它跟文件内容一样，都会被 持久化存储到磁盘中。所以记住，</strong><font style="color:#F5222D;">索引节点同样占用磁盘空间。</font></strong></li><li><strong><font style="color:#F5222D;">目录项，简称为 dentry，用来记录文件的名字、索引节点指针以及与其他目录项的关联 关系</font></strong>。多个关联的目录项，就构成了文件系统的目录结构。不过，不同于索引节点，<strong><font style="color:#F5222D;">目录 项是由内核维护的一个内存数据结构，所以通常也被叫做目录项缓存。</font></strong></li></ul><h3 id="目录项、索引节点、索引块、数据块以及超级块关系"><a href="#目录项、索引节点、索引块、数据块以及超级块关系" class="headerlink" title="目录项、索引节点、索引块、数据块以及超级块关系"></a>目录项、索引节点、索引块、数据块以及超级块关系</h3><ul><li><strong><font style="color:#F5222D;">索引节点是磁盘上每个文件实体的唯一标志</font></strong></li><li><strong><font style="color:#F5222D;">目录项维护的正是文件系统的树状结构。目录项和索引节点的关系是多对一，你可以简单理解为，一个文件可以有多个别名</font></strong></li><li>举个例子，<strong><font style="color:#F5222D;">通过硬链接为文件创建的别名，就会对应不同的目录项</font></strong>，<strong><font style="color:#F5222D;">不过这些目录项本质上 还是链接同一个文件，所以，它们的索引节点相同。</font></strong></li></ul><p>索引节点、目录项纪录了文件的元数据，以及文件间的目录关系，<strong><font style="color:#F5222D;">磁盘读写的最小单位是扇区，然而扇区只有 512B 大小，如果每次都读写这么小的 单位，效率一定很低。所以，文件系统又把连续的扇区组成了逻辑块，然后每次都以逻辑块 为最小单元，来管理数据。常见的逻辑块大小为 4KB，也就是由连续的 8 个扇区组成。</font></strong> </p><p><a href="https://www.processon.com/view/link/60a511767d9c0830244d41b3">processon</a></p><ul><li>目录项本身就是一个内存缓存，而索引节点则是存储在磁盘中的数据。</li><li>磁盘在执行文件系统格式化时，会被分成三个存储区域，超级块、索引节点区和数据 块区。<ul><li><strong><font style="color:#F5222D;">超级块，存储整个文件系统的状态。</font></strong></li><li><strong><font style="color:#F5222D;">索引节点区，用来存储索引节点。</font></strong></li><li><strong><font style="color:#F5222D;">数据块区，则用来存储文件数据。</font></strong></li></ul></li></ul><h2 id="虚拟文件系统"><a href="#虚拟文件系统" class="headerlink" title="虚拟文件系统"></a>虚拟文件系统</h2><p><strong><font style="color:#F5222D;background-color:#FADB14;">为了支持各种不同的文件系统</font></strong>，Linux 内核**<font style="color:#F5222D;">在用户进程和文件系统的中间，又引入了一个抽 象层，也就是虚拟文件系统 VFS（Virtual File System）。</font>**</p><p><strong><font style="color:#F5222D;">VFS 定义了一组所有文件系统都支持的数据结构和标准接口。 用户进程和内核中的其他子系统，只需要跟 VFS 提供的统一接口进行交互就可以了，而不需要再关心底层各种文件系统的实现细节。</font></strong></p><p>VFS 内部又通过目录项、索引节点、逻辑块以及超级块等数据结构，来管理文件。</p><ul><li><strong>目录项</strong>，记录了文件的名字，以及文件与其他目录项之间的目录关系。</li><li><strong>索引节点</strong>，记录了文件的元数据。</li><li><strong>逻辑块</strong>，是由连续磁盘扇区构成的最小读写单元，用来存储文件数据。</li><li><strong>超级块</strong>，用来记录文件系统整体的状态，如索引节点和逻辑块的使用情况等。</li></ul><p><strong><font style="color:#F5222D;">目录项是一个内存缓存；而超级块、索引节点和逻辑块，都是存储在磁盘中的持久化 数据。</font></strong></p><h3 id="系统调用、VFS、缓存、文件系统以及块存储"><a href="#系统调用、VFS、缓存、文件系统以及块存储" class="headerlink" title="系统调用、VFS、缓存、文件系统以及块存储"></a>系统调用、VFS、缓存、文件系统以及块存储</h3><p><a href="https://www.processon.com/view/link/60a469eb07912915711c171d">processon</a></p><p>文件系统可以分为三类。</p><ul><li>第一类是**<font style="color:#F5222D;">基于磁盘的文件系统</font><strong>，也就是把数据直接</strong><font style="color:#F5222D;">存储在计算机本地挂载的磁盘中</font>**。常见 的 Ext4、XFS、OverlayFS 等，都是这类文件系统。</li><li>第二类是**<font style="color:#F5222D;">基于内存的文件系统</font><strong>，也就是我们常说的</strong><font style="color:#F5222D;">虚拟文件系统</font><strong>。不需要任何磁盘分配存储空间，但会占用内存。我们经常用到的</strong><font style="color:#F5222D;"> &#x2F;proc 文件系统，其实就是一 种最常见的虚拟文件系统</font><strong>。此外，</strong><font style="color:#F5222D;">&#x2F;sys 文件系统也属于这一类，主要向用户空间导出层 次化的内核对象。</font>**</li><li>第三类是**<font style="color:#F5222D;">网络文件系统</font><strong>，也就是</strong><font style="color:#F5222D;">用来访问其他计算机数据的文件系统</font>**，比如 NFS、 SMB、iSCSI 等。</li></ul><p>这些文件系统，要先**<font style="color:#F5222D;">挂载到 VFS 目录树中的某个子目录（称为挂载点）</font><strong>，</strong><font style="color:#F5222D;">然后才能访问其 中的文件。</font>**</p><ul><li><strong><font style="color:#F5222D;">基于磁盘的文件系统 在安装系统时，要先挂载一个根目 录（&#x2F;），</font></strong></li><li><strong><font style="color:#F5222D;">在根目录下再把其他文件系统（比如其他的磁盘分区、&#x2F;proc 文件系统、&#x2F;sys 文 件系统、NFS 等）挂载进来。</font></strong></li></ul><h2 id="文件系统-I-O"><a href="#文件系统-I-O" class="headerlink" title="文件系统 I&#x2F;O"></a>文件系统 I&#x2F;O</h2><p>把文件系统挂载到挂载点后，就能**<font style="color:#F5222D;">通过挂载点，再去访问它管理的文件了</font><strong>。VFS 提供了一组标准的文件访问接口。这些接口</strong><font style="color:#F5222D;">以系统调用的方式，提供给应用程序使用</font>**。</p><p>就拿 cat 命令来说，它首先调用 open() ，打开一个文件；然后调用 read() ，读取文件的 内容；最后再调用 write() ，把文件内容输出到控制台的标准输出中。</p><h3 id="文件-IO分类：缓冲与非缓冲-I-O、直接与非直接-I-O、阻塞与非阻塞-I-O、同步与异步-I-O"><a href="#文件-IO分类：缓冲与非缓冲-I-O、直接与非直接-I-O、阻塞与非阻塞-I-O、同步与异步-I-O" class="headerlink" title="文件 IO分类：缓冲与非缓冲 I&#x2F;O、直接与非直接 I&#x2F;O、阻塞与非阻塞 I&#x2F;O、同步与异步 I&#x2F;O"></a>文件 IO分类：缓冲与非缓冲 I&#x2F;O、直接与非直接 I&#x2F;O、阻塞与非阻塞 I&#x2F;O、同步与异步 I&#x2F;O</h3><h4 id="是否利用标准库缓存：缓冲-I-O-与非缓冲-I-O"><a href="#是否利用标准库缓存：缓冲-I-O-与非缓冲-I-O" class="headerlink" title="是否利用标准库缓存：缓冲 I&#x2F;O 与非缓冲 I&#x2F;O"></a>是否利用标准库缓存：缓冲 I&#x2F;O 与非缓冲 I&#x2F;O</h4><ul><li>缓冲 I&#x2F;O，是指**<font style="color:#F5222D;">利用标准库缓存来加速文件的访问</font><strong>，而</strong><font style="color:#F5222D;">标准库内部再通过系统调度访问文件</font>**。<ul><li>很多程序遇到换行时才真正输出，而换行前的内容，其实就是被标准库暂时缓存了起来。</li></ul></li><li>非缓冲 I&#x2F;O，是指**<font style="color:#F5222D;">直接通过系统调用来访问文件</font>**，不再经过标准库缓存。</li></ul><p>无论缓冲 I&#x2F;O 还是非缓冲 I&#x2F;O，它们**<font style="color:#F5222D;">最终还是要经过系统调用来访问文件</font>**。 系统调用后，还会通过页缓存，来减少磁盘的 I&#x2F;O 操作。</p><h4 id="是否利用操作系统的页缓存：直接-I-O-与非直接-I-O"><a href="#是否利用操作系统的页缓存：直接-I-O-与非直接-I-O" class="headerlink" title="是否利用操作系统的页缓存：直接 I&#x2F;O 与非直接 I&#x2F;O"></a>是否利用操作系统的页缓存：直接 I&#x2F;O 与非直接 I&#x2F;O</h4><ul><li>直接 I&#x2F;O，是指**<font style="color:#F5222D;">跳过操作系统的页缓存，直接跟文件系统交互来访问文件。</font>**<ul><li>需要你在系统调用中，指定 O_DIRECT 标志。如果没有设置过，默认 的是非直接 I&#x2F;O。</li></ul></li><li>非直接 I&#x2F;O 正好相反，<strong><font style="color:#F5222D;">文件读写时，先要经过系统的页缓存，然后再由内核或额外的系 统调用，真正写入磁盘。</font></strong></li></ul><p><strong>直接 I&#x2F;O、非直接 I&#x2F;O，本质上还是和文件系统交互</strong>。如果是在数据库等场景中，你还会看到，<strong>跳过文件系统读写磁盘的情况，也就是我们通常所说的</strong><strong><font style="color:#F5222D;">裸 I&#x2F;O</font>****。</strong></p><h4 id="应用程序是否阻塞自身运行：阻塞-I-O-和非阻塞-I-O"><a href="#应用程序是否阻塞自身运行：阻塞-I-O-和非阻塞-I-O" class="headerlink" title="应用程序是否阻塞自身运行：阻塞 I&#x2F;O 和非阻塞 I&#x2F;O"></a>应用程序是否阻塞自身运行：阻塞 I&#x2F;O 和非阻塞 I&#x2F;O</h4><ul><li>阻塞 I&#x2F;O，是指应用程序执行**<font style="color:#F5222D;"> I&#x2F;O 操作后，如果没有获得响应，就会阻塞当前线程</font>**， 自然就不能执行其他任务。</li><li>非阻塞 I&#x2F;O，是指应用程序执行**<font style="color:#F5222D;"> I&#x2F;O 操作后，不会阻塞当前的线程，可以继续执行其他的任务</font><strong>，随后再通过</strong><font style="color:#F5222D;background-color:#FADB14;">轮询或者事件通知</font>****<font style="color:#F5222D;">的形式，获取调用的结果。</font>**<ul><li>访问管道或者网络套接字时，设置 O_NONBLOCK 标志，就表示用非阻塞方式访 问；而如果不做任何设置，默认的就是阻塞访问。</li><li>非阻塞 I&#x2F;O，通常会跟 select&#x2F;poll 配合，用在网络套接字的 I&#x2F;O 中。</li></ul></li></ul><h4 id="是否等待响应结果：可以把文件-I-O-分为同步和异步-I-O"><a href="#是否等待响应结果：可以把文件-I-O-分为同步和异步-I-O" class="headerlink" title="是否等待响应结果：可以把文件 I&#x2F;O 分为同步和异步 I&#x2F;O"></a>是否等待响应结果：可以把文件 I&#x2F;O 分为同步和异步 I&#x2F;O</h4><ul><li>同步 I&#x2F;O，是指应用程序执行 I&#x2F;O 操作后，要一直等到整个 I&#x2F;O 完成后，才能获得 I&#x2F;O 响应。<ul><li>在操作文件时，如果你设置了 O_SYNC 或者 O_DSYNC 标志，就代表同步 I&#x2F;O。如果设置了 O_DSYNC，就要等文件数据写入磁盘后，才能返回；而 O_SYNC，则是 在 O_DSYNC 基础上，要求文件元数据也要写入磁盘后，才能返回。</li></ul></li><li>异步 I&#x2F;O，是指应用程序执行 I&#x2F;O 操作后，<strong><font style="color:#F5222D;">不用等待完成和完成后的响应</font></strong>，而是继续执行就可以。等到这次 I&#x2F;O 完成后，响应会**<font style="color:#F5222D;">用</font><strong><strong><font style="color:#F5222D;background-color:#FADB14;">事件通知的方式</font></strong></strong><font style="color:#F5222D;">，告诉应用程序。</font>**<ul><li>访问管道或者网络套接字时，设置了 O_ASYNC 选项后，相应的 I&#x2F;O 就是异步 I&#x2F;O。这样，内核会再通过 SIGIO 或者 SIGPOLL，来通知进程文件是否可读写。</li></ul></li></ul><h4 id="同步异步和阻塞非阻塞的区别"><a href="#同步异步和阻塞非阻塞的区别" class="headerlink" title="同步异步和阻塞非阻塞的区别"></a>同步异步和阻塞非阻塞的区别</h4><ul><li>不同角度的 I&#x2F;O 划分方式：根据应用程序**<font style="color:#F5222D;">是否阻塞自身运行</font><strong>或者</strong><font style="color:#F5222D;">I&#x2F;O 响应的通知方式的不同</font>**</li><li><strong><font style="color:#F5222D;">描述的对象不同，阻塞 &#x2F; 非阻塞针对的是 I&#x2F;O 调用者（即应用程序），而同步 &#x2F; 异步针 对的是 I&#x2F;O 执行者（即系统）。</font></strong></li></ul><p><strong><font style="color:#F5222D;"></font></strong></p><h2 id="性能观测"><a href="#性能观测" class="headerlink" title="性能观测"></a>性能观测</h2><h3 id="容量"><a href="#容量" class="headerlink" title="容量"></a>容量</h3><h4 id="文件数据的使用情况"><a href="#文件数据的使用情况" class="headerlink" title="文件数据的使用情况"></a>文件数据的使用情况</h4><p>df 命 令，就能查看文件系统的磁盘空间使用情况</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214306458-344534786.png" alt="1625632858559-3c01e46e-9543-4e5d-9be6-8aea76e22fd2.png"></p><p>总空间用 1K-blocks 的数量来表示，你可以给 df 加上 -h 选项，以获得更好的可读性：</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214306716-961231016.png" alt="1625632865001-d03299f8-7700-4e92-87e6-22abd990706d.png"></p><h4 id="索引节点的使用情况"><a href="#索引节点的使用情况" class="headerlink" title="索引节点的使用情况"></a>索引节点的使用情况</h4><p>除了文件数据，索引节点也占用磁盘空间。 你可以给 df 命令加上 -i 参数，查看索引节点的使用情况：</p><p>索引节点的容量，（也就是 Inode 个数）是在格式化磁盘时设定好的，一般由格式化工具 自动生成。当你发现索引节点空间不足，但磁盘空间充足时，很可能就是过多小文件导致 的。</p><p>一般来说，删除这些小文件，或者把它们移动到索引节点充足的其他磁盘中，就可以 解决这个问题。</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214307004-74425312.png" alt="1625632873397-0ad8b2a8-9258-4b6e-8c24-dc6b68d08165.png"></p><h3 id="缓存"><a href="#缓存" class="headerlink" title="缓存"></a>缓存</h3><p>可以用 free 或 vmstat，来观察页缓存的大小。</p><h4 id="Cache-页缓存和可回收-Slab-缓存的和"><a href="#Cache-页缓存和可回收-Slab-缓存的和" class="headerlink" title="Cache : 页缓存和可回收 Slab 缓存的和"></a>Cache : 页缓存和可回收 Slab 缓存的和</h4><p>free 输出的 Cache，是页缓存和可回收 Slab 缓存的和，可以从 &#x2F;proc&#x2F;meminfo ，直接得到它们的大小：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat /proc/meminfo | grep -E &quot;SReclaimable|Cached&quot;</span><br></pre></td></tr></table></figure><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214307411-1895802445.png" alt="1625633181797-44747833-2511-452c-a11c-971e5378f45d.png"></p><h4 id="Buffer-以及-文件系统中的目录项和索引节点缓存"><a href="#Buffer-以及-文件系统中的目录项和索引节点缓存" class="headerlink" title="Buffer 以及 文件系统中的目录项和索引节点缓存"></a>Buffer 以及 文件系统中的目录项和索引节点缓存</h4><p>文件名以及文件之间的目录关系，都放在目录项缓存中。而这是一个基于内存的 数据结构，会根据需要动态构建。所以，查找文件时，Linux 就会动态构建不在缓存中的目 录项结构，导致 dentry 缓存升高。</p><p>除了目录项缓存增加，Buffer 的使用也会增加。如果你用<code>** vmstat 1 **</code>观察一下，会发 现 Buffer 和 Cache 都在增长，<strong><font style="color:#F5222D;">Buffer 的增长是因为，构建目录项缓存所需的元数据（比如文件名称、索引节点 等），需要从文件系统中读取。</font></strong></p><p>内核使用 Slab 机制，管理目录项和索引节点的缓存。<strong><font style="color:#F5222D;">&#x2F;proc&#x2F;meminfo 只给出了 Slab 的整体大小，具体到每一种 Slab 缓存，还要查看 &#x2F;proc&#x2F;slabinfo 这个文件。</font></strong></p><p>所有目录项和各种文件系统索引节点的缓存情况：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat /proc/slabinfo | grep -E &#x27;^#|dentry|inode&#x27; </span><br></pre></td></tr></table></figure><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214307683-1475169259.png" alt="1625633318341-f6430d7c-7ff2-41b4-a2eb-4651c08ddbb5.png"></p><ul><li>dentry 行表示目录项缓存，</li><li>inode_cache 行，表示 VFS 索引节点缓存，其余 的则是各种文件系统的索引节点缓存。</li></ul><p>&#x2F;proc&#x2F;slabinfo 的列比较多，在实际性能分析中，我 们更常使用 slabtop ，来找到占用内存最多的缓存类型。</p><p>按下 c 按照缓存大小排序，按下 a 按照活跃对象数排序 </p><p><code>slabtop</code> </p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214308241-1160096457.png" alt="1625633645303-156aa7dd-c64b-49c0-8d2c-55e0cebf4a2c.png"></p><h2 id="文件系统总结"><a href="#文件系统总结" class="headerlink" title="文件系统总结"></a>文件系统总结</h2><p><strong>文件系统</strong>：是对存储设备上的文件，进行组织管理的一种机制。</p><p><strong>VFS</strong>：为了支持各类不同的文件系统，Linux 在各种文件系统实现上，抽象了一层虚拟文件系统（VFS）。VFS 定义了一组所有文件系统都支持的数据结构和标准接口。这样，用户进程和内核中的 其他子系统，就只需要跟 VFS 提供的统一接口交互，而不需要关注文件系统的具 体实现；对具体的文件系统来说，只需要按照 VFS 的标准，就可以无缝支持各种应用程 序。</p><hr><p><strong>为了降低慢速磁盘对性能的影响，文件系统又通过页缓存、目录项缓存以及索引节点缓存， 缓和磁盘延迟对应用程序的影响。</strong></p><h2 id="Linux-磁盘分类"><a href="#Linux-磁盘分类" class="headerlink" title="Linux 磁盘分类"></a>Linux 磁盘分类</h2><h3 id="磁盘按照存储介质来分类"><a href="#磁盘按照存储介质来分类" class="headerlink" title="磁盘按照存储介质来分类"></a>磁盘按照存储介质来分类</h3><h4 id="机械和SSD的区别是什么？"><a href="#机械和SSD的区别是什么？" class="headerlink" title="机械和SSD的区别是什么？"></a>机械和SSD的区别是什么？</h4><ul><li>机械磁盘，也称为硬盘驱动器（Hard Disk Driver），通常缩写为 HDD。盘片和读写磁头组成，数据就存储在盘片的环状磁道中。在读写数据前，需要移动读写磁头，定位到数据所在的磁道，然后才能访问数据。<ul><li>如果 I&#x2F;O 请求刚好连续，那就不需要磁道寻址，也就是（顺序）**<font style="color:#F5222D;">连续 I&#x2F;O </font>**的工作原理。</li><li><strong><font style="color:#F5222D;">随机 I&#x2F;O</font></strong>，它需要不停地移动磁头，来定位数据位置，所以读写速度就会比较慢。</li></ul></li><li>固态磁盘（Solid State Disk），通常缩写为 SSD，由固态电子元器件组成。固态磁盘不需要磁道寻址，所以，不管是连续 I&#x2F;O，还是随机 I&#x2F;O 的性能，都比机械磁盘要好得 多。</li></ul><p><strong><font style="color:#F5222D;">（顺序）连续 I&#x2F;O 可以通过预读的方式，来减少 I&#x2F;O 请求的次数，这也是其性能优异的 一个原因。很多性能优化的方案，也都会从这个角度出发，来优化 I&#x2F;O 性能。</font></strong></p><p><strong><font style="color:#F5222D;"></font></strong></p><p>机械磁盘和固态磁盘还分别有一个最小的读写单位。机械磁盘的最小读写单位是扇区，一般大小为 512 字节。而固态磁盘的最小读写单位是页，通常大小是 4KB、8KB 等。</p><p>如果每次都读写 512 字节这么小的单位的话，效率很低。所 以，文件系统会把连续的扇区或页，组成逻辑块，然后以逻辑块作为最小单元来管理数据。 常见的逻辑块的大小是 4KB，也就是说，连续 8 个扇区，或者单独的一个页，都可以组成 一个逻辑块。</p><h4 id="SSD的随机访问性能好吗？"><a href="#SSD的随机访问性能好吗？" class="headerlink" title="SSD的随机访问性能好吗？"></a>SSD的随机访问性能好吗？</h4><p><strong><font style="color:#F5222D;">无论机械磁盘，还是固态磁盘，相同磁盘的随机 I&#x2F;O 都要比连续 I&#x2F;O 慢很多</font></strong>，</p><p>对机械磁盘来说，随机 I&#x2F;O 需要更多的磁头寻道和盘片旋转，它的性能自然要比连续 I&#x2F;O 慢。 </p><p>对固态磁盘来说，虽然它的随机性能比机械硬盘好很多，但**<font style="color:#F5222D;">同样存在“先擦除再写 入”的限制。随机读写会导致大量的垃圾回收</font>**，所以相对应的，随机 I&#x2F;O 的性能比起连 续 I&#x2F;O 来，也还是差了很多。</p><h3 id="磁盘按照接口来分类"><a href="#磁盘按照接口来分类" class="headerlink" title="磁盘按照接口来分类"></a>磁盘按照接口来分类</h3><p>硬盘分为 IDE（Integrated Drive Electronics）、SCSI（Small Computer System Interface） 、SAS（Serial Attached SCSI） 、SATA（Serial ATA） 、FC（Fibre Channel） 等。</p><p>不同的接口，往往分配不同的设备名称。比如， IDE 设备会分配一个 hd 前缀的设备名， SCSI 和 SATA 设备会分配一个 sd 前缀的设备名。如果是多块同类型的磁盘，就会按照 a、b、c 等的字母顺序来编号。</p><h3 id="磁盘按照架构来分类"><a href="#磁盘按照架构来分类" class="headerlink" title="磁盘按照架构来分类"></a>磁盘按照架构来分类</h3><p>当把磁盘接入服务器后，按照不同的使用方式，又可以把它们划 分为多种不同的架构。</p><ul><li>作为独立磁盘设备来使用。往往还会根据需要，划分为不同 的逻辑分区，每个分区再用数字编号。&#x2F;dev&#x2F;sda 还可以分成两 个分区 &#x2F;dev&#x2F;sda1 和 &#x2F;dev&#x2F;sda2。</li><li><strong><font style="color:#F5222D;">多块磁盘组合成一个逻辑磁盘，构成冗余独立磁盘阵列，也就 是 RAID</font></strong>（Redundant Array of Independent Disks），从而可以提高数据访问的性能， 并且**<font style="color:#F5222D;">增强数据存储的可靠性。</font>** <ul><li>RAID0 有最优的读写性能，但不提供数据冗余的功能。</li><li>而其他级别的 RAID，在提供数据冗余的基础上，对读写性能也有一定程度的优化。</li></ul></li><li>最后一种架构，是把这些磁盘组合成一个网络存储集群，再通过 NFS、SMB、iSCSI 等网 络存储协议，暴露给服务器使用。</li></ul><p>其实在 Linux 中，<strong><font style="color:#F5222D;">磁盘实际上是作为一个块设备来管理的，也就是以块为单位读写数据</font></strong>， 并且支持随机读写。<strong><font style="color:#F5222D;">每个块设备都会被赋予两个设备号，分别是主、次设备号。主设备号用 在驱动程序中，用来区分设备类型；而次设备号则是用来给多个同类设备编号。</font></strong></p><h2 id="通用块层"><a href="#通用块层" class="headerlink" title="通用块层"></a>通用块层</h2><p><strong><font style="color:#F5222D;">虚拟文件系统 VFS 类似，为了减小不同块设备的差异带来的影响， Linux 通过一个统一的通用块层，来管理各种不同的块设备。</font></strong></p><p><a href="https://www.processon.com/view/link/60a469eb07912915711c171d">processon</a></p><p>通用块层，其实是处在文件系统和磁盘驱动中间的一个块设备抽象层。它主要有两个功能 。</p><ul><li>第一个功能跟虚拟文件系统的功能类似。向上，为文件系统和应用程序，提供访问块设备 的标准接口；向下，把各种异构的磁盘设备抽象为统一的块设备，并提供统一框架来管理 这些设备的驱动程序。</li><li>第二个功能，通用块层还会给文件系统和应用程序发来的 I&#x2F;O 请求排队，并通过重新排序、请求合并等方式，提高磁盘读写的效率。也就是 I&#x2F;O 调度。</li></ul><h3 id="通用块层的IO调度算法"><a href="#通用块层的IO调度算法" class="headerlink" title="通用块层的IO调度算法"></a>通用块层的IO调度算法</h3><p>事实上，Linux 内核支持四 种 I&#x2F;O 调度算法，分别是 NONE、NOOP、CFQ 以及 DeadLine。</p><ul><li>**第一种 NONE **，更确切来说，并不能算 I&#x2F;O 调度算法。因为它完全不使用任何 I&#x2F;O 调度器，对文件系统和应用程序的 I&#x2F;O 其实不做任何处理，常用在虚拟机中（此时磁盘 I&#x2F;O 调 度完全由物理机负责）。 </li><li>**第二种 NOOP **，是最简单的一种 I&#x2F;O 调度算法。它实际上是一个先入先出的队列，只做一 些最基本的请求合并，常用于 SSD 磁盘。</li><li><strong>第三种 CFQ</strong>（Completely Fair Scheduler）<strong>，也被称为完全公平调度器，<strong>是现在很多发行 版的默认 I&#x2F;O 调度器，它</strong><font style="color:#F5222D;">为每个进程维护了一个 I&#x2F;O 调度队列，并按照时间片来均匀分布 每个进程的 I&#x2F;O 请求。</font><strong>类似于进程 CPU 调度，CFQ 还支持进程 I&#x2F;O 的优先级调度，所以它</strong><font style="color:#F5222D;">适用于运行大量进程 的系统</font></strong>，像是桌面环境、多媒体应用等。</li><li><strong>DeadLine 调度算法</strong>，分别为读、写请求创建了不同的 I&#x2F;O 队列，可以提高机械 磁盘的吞吐量，并确保**<font style="color:#F5222D;">达到最终期限（deadline）的请求被优先处理。</font>**DeadLine 调度算 法，多用在 I&#x2F;O 压力比较重的场景，比如数据库等。</li></ul><h2 id="总结：I-O-栈-文件系统层、通用块层和设备层"><a href="#总结：I-O-栈-文件系统层、通用块层和设备层" class="headerlink" title="总结：I&#x2F;O 栈&#x3D;&#x3D;文件系统层、通用块层和设备层"></a>总结：I&#x2F;O 栈&#x3D;&#x3D;文件系统层、通用块层和设备层</h2><ul><li><strong>文件系统层</strong>，包括虚拟文件系统和其他各种文件系统的具体实现。它为上层的应用程序， 提供标准的文件访问接口；对下会通过通用块层，来存储和管理磁盘数据。</li><li><strong>通用块层</strong>，包括块设备 I&#x2F;O 队列和 I&#x2F;O 调度器。它会对文件系统的 I&#x2F;O 请求进行排队， 再通过重新排序和请求合并，然后才要发送给下一级的设备层。<ul><li><strong><font style="color:#F5222D;">通用块层是 Linux 磁盘 I&#x2F;O 的核心。向上，它为文件系统和应用程序，提供访问了 块设备的标准接口；向下，把各种异构的磁盘设备，抽象为统一的块设备，并会对文件系统 和应用程序发来的 I&#x2F;O 请求进行重新排序、请求合并等，提高了磁盘访问的效率。</font></strong></li></ul></li><li><strong>设备层</strong>，包括存储设备和相应的驱动程序，负责最终物理设备的 I&#x2F;O 操作。</li><li>**存储系统的 I&#x2F;O **，通常是整个系统中最慢的一环。所以， Linux 通过多种缓存机制来优化 I&#x2F;O 效率。<ul><li>为了优化文件访问的性能，会使用页缓存、索引节点缓存、目录项缓存等多种缓存 机制，以减少对下层块设备的直接调用。</li><li>同样，为了优化块设备的访问效率，会使用缓冲区，来缓存块设备的数据。</li></ul></li></ul><h2 id="磁盘-I-O-性能指标"><a href="#磁盘-I-O-性能指标" class="headerlink" title="磁盘 I&#x2F;O 性能指标"></a>磁盘 I&#x2F;O 性能指标</h2><p>使用率、饱 和度、IOPS、吞吐量以及响应时间等。这五个指标，是衡量磁盘性能的基本指标。</p><ul><li>使用率，是指磁盘处理 I&#x2F;O 的时间百分比。过高的使用率（比如超过 80%），通常意味 着磁盘 I&#x2F;O 存在性能瓶颈。<ul><li>使用率只考虑有没有 I&#x2F;O，而不考虑 I&#x2F;O 的大小，换句话说，当使用率是 100% 的时候，磁盘依然有可能接受新的 I&#x2F;O 请求。</li></ul></li><li>饱和度，是指磁盘处理 I&#x2F;O 的繁忙程度。过高的饱和度，意味着磁盘存在严重的性能瓶 颈。当饱和度为 100% 时，磁盘无法接受新的 I&#x2F;O 请求</li><li>IOPS（Input&#x2F;Output Per Second），是指每秒的 I&#x2F;O 请求**<font style="color:#F5222D;">数</font>**。</li><li>吞吐量，是指每秒的 I&#x2F;O 请求**<font style="color:#F5222D;">大小</font>**。</li><li>响应时间，是指 I&#x2F;O 请求从发出到收到响应的间隔时间。</li></ul><p><strong><font style="color:#F5222D;">不要孤立地去比较某一指标，而要结合读写比例、I&#x2F;O 类型（随机还是连续）以及 I&#x2F;O 的大小，综合来分析。</font><strong>在数据库、大量小文件等这类随机读写比较多的场景中，</strong><font style="color:#F5222D;">IOPS 更能反映系统的 整体性能；而在多媒体等顺序读写较多的场景中，吞吐量才更能反映系统的整体性能。</font></strong></p><h2 id="磁盘-I-O-性能工具"><a href="#磁盘-I-O-性能工具" class="headerlink" title="磁盘 I&#x2F;O 性能工具"></a>磁盘 I&#x2F;O 性能工具</h2><h3 id="磁盘-I-O-观测"><a href="#磁盘-I-O-观测" class="headerlink" title="磁盘 I&#x2F;O 观测"></a>磁盘 I&#x2F;O 观测</h3><p>在为应用程序的服务器选型时，要先对磁盘的 I&#x2F;O 性能进行基准测试，以 便可以准确评估，磁盘性能是否可以满足应用程序的需求。推荐用性能测试工具 fio ，来测试磁盘的 IOPS、吞吐量以及响应时间等。</p><p>测试出，不同 I&#x2F;O 大小（一般是 512B 至 1MB 中间的若干值）分别在<strong>随机读、顺序读、随机写、顺序写</strong>等各种场景下的性能情况。 用性能工具得到的这些指标，可以作为后续分析应用程序性能的依据。一旦发生性能问题， 你就可以把它们作为磁盘性能的极限值，进而评估磁盘 I&#x2F;O 的使用情况。</p><h4 id="每块磁盘的使用情况：iostat"><a href="#每块磁盘的使用情况：iostat" class="headerlink" title="每块磁盘的使用情况：iostat"></a>每块磁盘的使用情况：iostat</h4><p>iostat 是最常用的磁盘 I&#x2F;O 性能观测工具，它提供了**<font style="color:#F5222D;">每个磁盘的使用率、IOPS、吞吐量</font>**等 各种常见的性能指标，当然，这些指标实际上来自 <strong><font style="color:#F5222D;">&#x2F;proc&#x2F;diskstats</font></strong>。</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214308533-1606863626.png" alt="1625637054965-b3e03723-e985-48bc-8a49-f3ef339381bd.png"></p><p>-d -x 表示显示所有磁盘 I&#x2F;O 的指标 </p><p>第一列的 Device 表示磁盘设备的名字</p><p>%util ，磁盘 I&#x2F;O 使用率； </p><p>r&#x2F;s+ w&#x2F;s ，就是 IOPS； </p><p>rkB&#x2F;s+wkB&#x2F;s ，就是吞吐量； </p><p>r_await+w_await ，就是响应时间。</p><p>结合请求的大小（ rareq-sz 和 wareq-sz）一起分析。从 iostat 并不能直接得到磁盘饱和度。可以把观测到的，平均请求队列长度或者读写请求完成的等待时间，跟基准测试的结果（比如通过 fio）进行对比，综合评估磁盘的饱和情况。</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214308793-366985677.png" alt="1625637102671-ccae6809-b19d-4f43-8d1c-782042bed965.png"></p><h3 id="进程-I-O-观测"><a href="#进程-I-O-观测" class="headerlink" title="进程 I&#x2F;O 观测"></a>进程 I&#x2F;O 观测</h3><p>iostat 只提供磁盘整体的 I&#x2F;O 性能数据，缺点在于，并不能知道具体是哪些进 程在进行磁盘读写。</p><h4 id="pidstat-实时查看"><a href="#pidstat-实时查看" class="headerlink" title="pidstat 实时查看"></a>pidstat 实时查看</h4><p>pidstat 加上 -d 参数，你就可以 看到进程的 I&#x2F;O 情况，</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214308994-1452136353.png" alt="1625637487789-8ca0439b-18de-4eb4-bd44-ccec061fe085.png"></p><p>实时查看每一秒每个进程的 I&#x2F;O 情况，包括下面这些内容。</p><p>用户 ID（UID）和进程 ID（PID） 。</p><p>每秒读取的数据大小（kB_rd&#x2F;s） ，单位是 KB。</p><p>每秒发出的写请求数据大小（kB_wr&#x2F;s） ，单位是 KB。</p><p>每秒取消的写请求数据大小（kB_ccwr&#x2F;s） ，单位是 KB。</p><h4 id="iotop：I-O-大小对进程排序"><a href="#iotop：I-O-大小对进程排序" class="headerlink" title="iotop：I&#x2F;O 大小对进程排序"></a>iotop：I&#x2F;O 大小对进程排序</h4><p>iotop。它是一个类似于 top 的工具，前两行分别表示，进程的磁盘读写大小总数和磁盘真实的读写大 小总数。因为缓存、缓冲区、I&#x2F;O 合并等因素的影响，它们可能并不相等。</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012214309207-865460675.png" alt="1625638093838-a59303c1-5407-48fa-b691-bf251b5a24d2.png"></p><p>剩下的部分，则是从各个角度来分别表示进程的 I&#x2F;O 情况，包括线程 ID、I&#x2F;O 优先级、每 秒读磁盘的大小、每秒写磁盘的大小、换入和等待 I&#x2F;O 的时钟百分比等。</p>]]>
    </content>
    <id>https://www.chucz.asia/2026/04/09/Linux%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E4%B8%8E%E7%A3%81%E7%9B%98%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86/</id>
    <link href="https://www.chucz.asia/2026/04/09/Linux%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E4%B8%8E%E7%A3%81%E7%9B%98%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86/"/>
    <published>2026-04-09T06:42:38.000Z</published>
    <summary>
      <![CDATA[<p><strong><font style="color:#F5222D;">磁盘为系统提供了最基本的持久化存储。</font></strong></p>
<p><strong><font]]>
    </summary>
    <title>Linux文件系统与磁盘工作原理</title>
    <updated>2026-04-09T06:42:38.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>褚成志</name>
    </author>
    <category term="操作系统" scheme="https://www.chucz.asia/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    <category term="Linux" scheme="https://www.chucz.asia/tags/Linux/"/>
    <category term="文本处理" scheme="https://www.chucz.asia/tags/%E6%96%87%E6%9C%AC%E5%A4%84%E7%90%86/"/>
    <category term="awk" scheme="https://www.chucz.asia/tags/awk/"/>
    <content>
      <![CDATA[<h1 id="Linux-文本编辑三剑客之-awk"><a href="#Linux-文本编辑三剑客之-awk" class="headerlink" title="Linux 文本编辑三剑客之 awk"></a>Linux 文本编辑三剑客之 awk</h1><blockquote><p>Linux 文本处理三剑客是面试和后端工作中较为常见的。需要掌握：</p><ul><li>grep：文本过滤、筛选</li><li>sed：文本编辑加工</li><li>awk：文本格式化输出</li></ul><p>文章只列举常用的，不会完全把手册复述一遍</p></blockquote><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194542112-278054590.jpg" alt="画板"></p><p>本节内容基于正则表达式：</p><h2 id="正则表达式"><a href="#正则表达式" class="headerlink" title="正则表达式"></a>正则表达式</h2><p>借助正则表达式可以<strong>快速匹配、过滤</strong>需要的字符串，在 Linux 上处理大量文本比较高效。</p><ul><li>一次处理一行</li><li>Linux  上只有文本处理工具三剑客（grep、sed、awk）常用，编程语言都有对应支持</li><li>扩展正则是基本正则的补充，一般结合三剑客使用建议直接使用扩展正则的写法，简洁</li></ul><h3 id="基本正则"><a href="#基本正则" class="headerlink" title="基本正则"></a>基本正则</h3><table><thead><tr><th>符号</th><th>作用</th></tr></thead><tbody><tr><td><code>^</code></td><td>模式匹配最左侧，<code>^abc</code>就是<strong>以abc开头</strong></td></tr><tr><td><code>$</code></td><td>模式匹配最右侧，<code>abc$</code>就是<strong>以abc结尾</strong></td></tr><tr><td><code>^$</code></td><td><strong>组合符。空行</strong></td></tr><tr><td><code>.</code></td><td><strong>任意一个且只有一个字符，不匹配空行</strong></td></tr><tr><td><code>*</code></td><td>匹配前一个字符0或多次，不单独使用</td></tr><tr><td><code>.*</code></td><td>组合符。匹配任意多个字符</td></tr><tr><td><code>^.*</code></td><td>组合符。匹配任意多个字符开头</td></tr><tr><td><code>.*$</code></td><td>组合符。匹配任意多个字符结尾</td></tr><tr><td><code>\</code></td><td>特殊字符还原本意，<code>\.</code>是小数点</td></tr><tr><td><code>[abc]</code></td><td>匹配集合内任意字符</td></tr><tr><td><code>[^abc]</code></td><td>匹配集合之外的字符</td></tr><tr><td><code>&lt;&gt;</code></td><td>定位单词的左侧和右侧。<code>&lt;deltaqin&gt;</code> 可以找出 <code>deltaqin nb</code>找不出<code>deltaqinnb</code></td></tr></tbody></table><h3 id="扩展正则"><a href="#扩展正则" class="headerlink" title="扩展正则"></a>扩展正则</h3><table><thead><tr><th>符号</th><th>作用</th></tr></thead><tbody><tr><td><code>+</code></td><td>匹配前面字符一次或多次</td></tr><tr><td><code>[:@]+</code></td><td>组合符。匹配[]内的字符一次或多次</td></tr><tr><td><code>?</code></td><td>匹配前面字符0次或1次</td></tr><tr><td>&#96;</td><td>&#96;</td></tr><tr><td><code>()</code></td><td>分组过滤，括号内是一个整体</td></tr><tr><td><code>a{m,n}</code></td><td>前面的字符最少m最多n次</td></tr><tr><td><code>a{m,}</code></td><td>前面的字符最少m次</td></tr><tr><td><code>a{m}</code></td><td>前面的字符m次</td></tr><tr><td><code>a{,m}</code></td><td>前面的字符最多m次</td></tr></tbody></table><p>具体使用还是要结合三剑客一起</p><h2 id="Why-awk"><a href="#Why-awk" class="headerlink" title="Why awk"></a>Why awk</h2><p><strong>按行</strong>对文本复杂格式化处理，简单使用 <code>grep</code> 或者 <code>sed</code> 就可以，自定义要求高就使用 <code>awk</code>。更像是一门<strong>编程语言，支持判断、数组、循环等等。</strong></p><h2 id="What-awk"><a href="#What-awk" class="headerlink" title="What awk"></a>What awk</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">awk [option] <span class="string">&#x27;[pattern]&#123;action&#125;&#x27;</span> file</span><br><span class="line"><span class="comment"># awk &#x27;BEGIN&#123;action&#125; pattern&#123;action&#125; END&#123;action&#125;&#x27; file</span></span><br></pre></td></tr></table></figure><ul><li>option：可选参数<ul><li><code>-F</code> 指定分隔符，默认空格分割</li><li><code>-v</code> 定义或修改一个awk内部变量</li></ul></li><li>pattern：可以是普通文本字符，也可以是正则表达式<ul><li><code>BEGAIN</code>处理文本前要执行的操作</li><li><code>END</code> 处理文本之后执行的操作</li><li>使用<code>内置变量 + 关系运算符</code>，限制输出内容，例如<code>NR&gt;3</code>、<code>NR!=3</code>、<code>/正则/</code>、<code>!/正则/</code>不匹配正则、<code>/正则/、/正则/</code>范围，<code>条件&amp;&amp;条件</code></li></ul></li><li>action：对文本执行的操作<ul><li>print 自带换行符，</li><li>printf 是没有换行符的。printf需要指定每一个item的输出格式，格式都是<code>%</code>开头，和C语言基本一致，默认右对齐，<code>-</code>变为左对齐</li></ul></li><li>内置变量：<ul><li>行列：<code>$0</code>整行；<code>NF</code>分割完最后一列；<code>(NF-1)</code>分割完倒数第二列；<code>NR</code>当前行号；<code>FNR</code>各文件分别计数的行号</li><li>分隔符：<code>FS</code>字段输入分隔符，默认空格；<code>OFS</code>字段输出分隔符，默认空格；</li><li>换行符：<code>RS</code>输入换行符，替代回车；<code>ORS</code>输出换行符，替代回车</li><li>其余变量：<code>FILENAME</code>文件名；<code>ARGC</code>命令行参数个数；<code>ARGV</code>命令行参数构成的数组；</li></ul></li></ul><p>注意：</p><p><strong>必须外层单引号，内层双引号。</strong></p><h2 id="How-awk"><a href="#How-awk" class="headerlink" title="How awk"></a>How awk</h2><h3 id="使用简单内置变量打印内容"><a href="#使用简单内置变量打印内容" class="headerlink" title="使用简单内置变量打印内容"></a>使用简单内置变量打印内容</h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194542797-1876107131.png" alt="1653215134846-c6cab67b-aeb6-4aa2-a35b-9149477994c5.png"></p><p>输出内容之间加上<code>,</code>否则没有空格</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194543474-62187126.png" alt="1653215145822-73ba0640-a6da-42bf-9511-672fa2ff4c1f.png"></p><p>自定义输出的内容：</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194543836-1841927875.png" alt="1653215283479-f947cc1e-6a2e-4124-bed4-a54de8ccf166.png"></p><p>多个文件的行号分开打印：FNR</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194544423-7315910.png" alt="1653217127201-9db8f2fa-c411-4989-a5db-6201b14b8666.png"></p><h3 id="打印匹配模式的内容"><a href="#打印匹配模式的内容" class="headerlink" title="打印匹配模式的内容"></a>打印匹配模式的内容</h3><p><code>awk &#39;[pattern]{action}&#39; file</code></p><p>打印第5行：</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194544776-2109165578.png" alt="1653215785540-f26f1ab9-f771-480e-9d52-f021ae150d4d.png"></p><p>打印第5行第一列：</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194545058-1269726609.png" alt="1653215814269-8e70eaa1-1934-4c41-b2cb-af24ee236dd7.png"></p><p>打印第5-7行第一列：</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194545277-857317059.png" alt="1653215952435-8bfeab9e-3e42-4306-ae46-7b59790eacee.png"></p><h3 id="行号-NR"><a href="#行号-NR" class="headerlink" title="行号 NR"></a>行号 NR</h3><p>number of row</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194545512-399727206.png" alt="1653216024056-caa90e9a-bab3-4343-88b9-3466ea97abdb.png"></p><h3 id="列数NF"><a href="#列数NF" class="headerlink" title="列数NF"></a>列数NF</h3><p>number of field</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194545805-1053940678.png" alt="1653216172313-21855f22-f2a6-4af9-94d2-3efe7e874970.png"></p><h3 id="指定输入-输出分隔符"><a href="#指定输入-输出分隔符" class="headerlink" title="指定输入&#x2F;输出分隔符"></a>指定输入&#x2F;输出分隔符</h3><p>FS 输入分隔符</p><p>OFS 输出分隔符</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194546202-1879822277.png" alt="1653216263658-319bc783-50b5-4270-8d8b-50460e7bbd1c.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194546506-1029321026.png" alt="1653216416970-e6d26e36-7c4b-4540-a7b8-6b007f84e818.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194546766-1268990000.png" alt="1653216613158-5ca4fc4e-95a2-4d45-ba21-e848a17bdc16.png"></p><h3 id="参数打印"><a href="#参数打印" class="headerlink" title="参数打印"></a>参数打印</h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194547118-196757298.png" alt="1653217530377-f6015640-d09c-4bc9-9350-6a4e9d365d8a.png"></p><h3 id="自定义变量"><a href="#自定义变量" class="headerlink" title="自定义变量"></a>自定义变量</h3><p>全局变量</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194547413-1975657795.png" alt="1653217996890-a6efa425-1536-4cfa-9fcf-f2e0da65183e.png"></p><p>局部变量</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194547643-2056438440.png" alt="1653218096960-494cc79b-6dfa-4d31-8608-07896e3b129f.png"></p><p>shell变量</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194547895-538094315.png" alt="1653218189062-a981154b-14f9-444e-b34c-fa6239fd4732.png"></p><h3 id="printf-格式化输出"><a href="#printf-格式化输出" class="headerlink" title="printf 格式化输出"></a>printf 格式化输出</h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194548150-397246906.png" alt="1653218513324-50cab870-5467-481d-a581-585042e0467b.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194548427-753483909.png" alt="1653218489773-b0066560-564c-4499-b039-ad60bb9c11f6.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194548706-824148061.png" alt="1653218634395-c327cc91-5de1-41af-874d-24d69f516684.png"></p><h3 id="模式使用"><a href="#模式使用" class="headerlink" title="模式使用"></a>模式使用</h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194548930-757759008.png" alt="1653217657252-e50eb1c5-3764-4824-92ed-e83cccb1331a.png"></p><p><strong>指定了条件模式，只有匹配模式的才会执行action</strong></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194549276-69535171.png" alt="1653219197681-af019312-d245-4770-a65e-fd98099e4083.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194549688-2139634966.png" alt="1653219406351-c06a5554-efb4-4987-9009-fe0ddc88d714.png"></p><h3 id="正则条件模式使用"><a href="#正则条件模式使用" class="headerlink" title="正则条件模式使用"></a>正则条件模式使用</h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194549959-401332842.png" alt="1653219729106-f8186980-bd3d-4e95-a6df-70834da9953d.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194550274-840300610.png" alt="1653219689686-60008c42-76e6-4c4e-826c-eb414da03376.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194550607-740139603.png" alt="1653219968325-c37855ff-e75d-443c-97c6-f116c06d69cf.png"></p><p>范围匹配：</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194550873-1585764103.png" alt="1653220317338-fd423001-527d-4ae4-8aa5-9fc90a8c8e6b.png"></p><h2 id="常见使用案例"><a href="#常见使用案例" class="headerlink" title="常见使用案例"></a>常见使用案例</h2><h3 id="查询系统禁止登录的用户"><a href="#查询系统禁止登录的用户" class="headerlink" title="查询系统禁止登录的用户"></a>查询系统禁止登录的用户</h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194551392-1323799805.png" alt="1653220147103-30883a5e-582f-4529-be11-0b49768cccf4.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194551930-2117653148.png" alt="1653220237274-948aeb6a-0828-46b8-8b02-c03a09b9d5fb.png"></p><h3 id="统计日志访客IP数量"><a href="#统计日志访客IP数量" class="headerlink" title="统计日志访客IP数量"></a>统计日志访客IP数量</h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194552428-517643032.png" alt="1653220825290-2a345d5d-4008-4054-b82b-d97fd05c466e.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194552710-830412255.png" alt="1653220906710-00c2e953-0ae9-4809-a89a-f9209128b5f0.png"></p><p><code>sort -n</code>sort 默认从小到大排序，<code>-n</code>逆序</p><p><code>wc -l</code> 统计行数</p><h3 id="访问频繁IP-top3"><a href="#访问频繁IP-top3" class="headerlink" title="访问频繁IP top3"></a>访问频繁IP top3</h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194552957-541501417.png" alt="1653221002137-40c67bd5-306e-435c-ae59-9bd053245b0b.png"></p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194553272-116770060.png" alt="1653221048368-745bf4fb-ea9c-4489-8d96-18cbc644d8ca.png"></p><h3 id="打印所有非系统建立用户的用户名和家目录"><a href="#打印所有非系统建立用户的用户名和家目录" class="headerlink" title="打印所有非系统建立用户的用户名和家目录"></a>打印所有非系统建立用户的用户名和家目录</h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194553537-458731439.png" alt="1653223902996-31883ffc-b448-45e1-8de2-51cbd2fb45ef.png"></p><h3 id="删除文件空白行写到新文件"><a href="#删除文件空白行写到新文件" class="headerlink" title="删除文件空白行写到新文件"></a>删除文件空白行写到新文件</h3><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194553771-543538905.png" alt="1653224233788-4ff52c11-13b9-4509-b8df-ca2eb602f83c.png"></p><h3 id="显示第三列是211的行"><a href="#显示第三列是211的行" class="headerlink" title="显示第三列是211的行"></a>显示第三列是211的行</h3><p><code>$3~</code>指定针对第三列正则匹配</p><p><img src="https://cdn.chucz.asia/blog/3703820-20251012194553985-304274439.png" alt="1653224394154-edac0b2c-2bdb-4f8e-a213-8b7e2697ab29.png"></p>]]>
    </content>
    <id>https://www.chucz.asia/2026/04/09/Linux%E6%96%87%E6%9C%AC%E7%BC%96%E8%BE%91%E4%B8%89%E5%89%91%E5%AE%A2%E4%B9%8Bawk/</id>
    <link href="https://www.chucz.asia/2026/04/09/Linux%E6%96%87%E6%9C%AC%E7%BC%96%E8%BE%91%E4%B8%89%E5%89%91%E5%AE%A2%E4%B9%8Bawk/"/>
    <published>2026-04-09T06:42:38.000Z</published>
    <summary>
      <![CDATA[<h1 id="Linux-文本编辑三剑客之-awk"><a href="#Linux-文本编辑三剑客之-awk" class="headerlink" title="Linux 文本编辑三剑客之 awk"></a>Linux 文本编辑三剑客之]]>
    </summary>
    <title>Linux文本编辑三剑客之awk</title>
    <updated>2026-04-09T06:42:38.000Z</updated>
  </entry>
</feed>
