<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>SeekRVL 的笔记</title>
  
  
  <link href="https://blog.nerdhan.top/atom.xml" rel="self"/>
  
  <link href="https://blog.nerdhan.top/"/>
  <updated>2026-06-04T17:49:38.832Z</updated>
  <id>https://blog.nerdhan.top/</id>
  
  <author>
    <name>SeekRVL</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>服务端开发者如何理解 AI Agent：从 ReAct 到 AgentOps</title>
    <link href="https://blog.nerdhan.top/2026/06/02/agent-react-to-agentops/"/>
    <id>https://blog.nerdhan.top/2026/06/02/agent-react-to-agentops/</id>
    <published>2026-06-01T16:33:14.000Z</published>
    <updated>2026-06-04T17:49:38.832Z</updated>
    
    <content type="html"><![CDATA[<img src="/images/agent-react-cover.webp" alt="服务端系统里的 Agent Runtime" width="1672" height="941" loading="eager" style="max-width:100%;height:auto"><p>最近顺着 AI Agent 这条线做了一轮梳理。</p><p>起点是 ReAct 论文，后面一路看到了 CoT、Tool Calling、MCP、Skill、LangChain、LangGraph、多 Agent 架构，以及最近常被提到的 AgentOps。</p><p>这篇不是教程，也不是路线图，更像是一份面向服务端开发者的读书笔记。</p><p>我想回答的是一个更朴素的问题：</p><p>如果一个服务端开发者想理解 Agent 开发，大概可以从哪些概念开始看？这些概念和已有的工程经验之间，能不能建立一些对应关系？</p><p>看下来以后，我的感受是：Agent 开发里确实有很多新东西，但也没有完全脱离传统软件工程。</p><p>比如函数注册、协议设计、状态管理、异常处理、权限控制、日志追踪、服务治理，这些词换一个上下文，仍然能解释 Agent 系统里的很多问题。</p><p>区别在于，系统里多了一个会推理、会选择工具、但输出不完全稳定的组件。</p><p>这篇文章就沿着这个观察，把几个常见概念串起来。</p><h2 id="1-ReAct-不是前端-React"><a href="#1-ReAct-不是前端-React" class="headerlink" title="1. ReAct 不是前端 React"></a>1. ReAct 不是前端 React</h2><p>这里的 ReAct 不是前端框架 React，而是 Reason + Act。</p><p>它来自 LLM Agent 领域，核心思想是：不要让模型一次性回答问题，而是让模型在“思考、行动、观察、再思考”的闭环里解决问题。</p><p>一个典型的 ReAct 循环大概是：</p><figure class="highlight text"><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">Thought: 我需要先查什么？</span><br><span class="line">Action: Search[...]</span><br><span class="line">Observation: 外部环境返回了什么？</span><br><span class="line">Thought: 基于新信息，我下一步该做什么？</span><br><span class="line">Action: Lookup[...]</span><br><span class="line">Observation: 又得到一条信息</span><br><span class="line">Final Answer: 给出最终答案</span><br></pre></td></tr></table></figure><p>它的关键价值不是代码多复杂，而是把 Agent 的基本工作循环标准化了：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Thought -&gt; Action -&gt; Observation -&gt; Thought -&gt; ... -&gt; Final Answer</span><br></pre></td></tr></table></figure><img src="/images/agent-react-1.webp" alt="图 1：ReAct 最小闭环" width="1672" height="941" loading="lazy" style="max-width:100%;height:auto"><p>模型不再只是一次性生成文本，而是在外部环境反馈中逐步逼近答案。</p><p>论文里的几个实验场景也很典型。</p><p>HotpotQA 和 FEVER 偏知识推理、检索、事实验证。ALFWorld 和 WebShop 偏环境交互、决策、行动。</p><p>这说明 ReAct 想解决的不是“让模型回答得更像人”，而是让模型具备一种最小的行动闭环：先判断，再动作，拿到反馈后继续判断。</p><h2 id="2-CoT-是想，ReAct-是边想边做"><a href="#2-CoT-是想，ReAct-是边想边做" class="headerlink" title="2. CoT 是想，ReAct 是边想边做"></a>2. CoT 是想，ReAct 是边想边做</h2><p>CoT，也就是 Chain-of-Thought，解决的是“让模型产生中间推理过程”的问题。</p><p>原始 CoT 是一种 prompting 技术：在 prompt 里给模型几个带推理过程的示例，让模型模仿这种推理格式。</p><p>后来 reasoning model 出现后，CoT 更像模型内部的计算过程。用户不一定能看到完整推理链，但模型会在内部做更多中间计算。</p><p>ReAct 和 CoT 的关系可以这样看：</p><figure class="highlight text"><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">CoT: Thought -&gt; Thought -&gt; Thought -&gt; Answer</span><br><span class="line">ReAct: Thought -&gt; Action -&gt; Observation -&gt; Thought -&gt; Action -&gt; Observation -&gt; Answer</span><br></pre></td></tr></table></figure><p>CoT 让模型想清楚。</p><p>ReAct 让模型边想边做，并把外部世界的反馈纳入下一步判断。</p><p>这一步很重要。</p><p>因为现实业务里的很多问题，不是靠模型“脑内想一想”就能解决的。它必须查数据库、调接口、看日志、读文档、比对状态、执行动作。</p><p>Agent 之所以成为 Agent，不是因为它会说话，而是因为它能在一个受控环境里行动。</p><h2 id="3-Agent-调工具，可以拆成普通调用链路"><a href="#3-Agent-调工具，可以拆成普通调用链路" class="headerlink" title="3. Agent 调工具，可以拆成普通调用链路"></a>3. Agent 调工具，可以拆成普通调用链路</h2><p>第一次看到 Agent 调工具，很多人会觉得神奇：</p><p>模型怎么知道要调用哪个函数？</p><p>又怎么知道参数应该怎么传？</p><p>但从服务端视角拆开看，它仍然是一条可以理解的调用链路。</p><p>模型本身不会真正执行工具。它只是根据你提供的工具描述和参数 schema，生成一个结构化意图：</p><figure class="highlight text"><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">我要调用 query_order</span><br><span class="line">参数是 &#123;&quot;order_id&quot;: &quot;123&quot;&#125;</span><br></pre></td></tr></table></figure><p>实际执行函数的是 Agent Runtime，也就是你的代码或框架。</p><p>完整链路大概是：</p><figure class="highlight text"><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><br><span class="line">-&gt; 模型生成 tool call</span><br><span class="line">-&gt; Runtime 解析工具名和参数</span><br><span class="line">-&gt; 从 Tool Registry 找到真实函数</span><br><span class="line">-&gt; 执行函数</span><br><span class="line">-&gt; 把函数结果作为 Observation 写回上下文</span><br><span class="line">-&gt; 模型继续推理或给出最终答案</span><br></pre></td></tr></table></figure><img src="/images/agent-react-2.webp" alt="图 2：Agent 调工具的运行时结构" width="1672" height="941" loading="lazy" style="max-width:100%;height:auto"><p>伪代码也很直观：</p><figure class="highlight python"><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">while</span> <span class="literal">True</span>:</span><br><span class="line">    output = llm(messages, tools=tool_schemas)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> output.final_answer:</span><br><span class="line">        <span class="keyword">return</span> output.final_answer</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> call <span class="keyword">in</span> output.tool_calls:</span><br><span class="line">        tool_name = call.name</span><br><span class="line">        tool_args = call.arguments</span><br><span class="line">        tool_result = tool_registry[tool_name](**tool_args)</span><br><span class="line"></span><br><span class="line">        messages.append(&#123;</span><br><span class="line">            <span class="string">&quot;role&quot;</span>: <span class="string">&quot;tool&quot;</span>,</span><br><span class="line">            <span class="string">&quot;name&quot;</span>: tool_name,</span><br><span class="line">            <span class="string">&quot;content&quot;</span>: tool_result,</span><br><span class="line">        &#125;)</span><br></pre></td></tr></table></figure><p>所以，一个最小 Agent Runtime 本质上就是几件事：</p><ul><li>函数注册表</li><li>工具描述</li><li>Tool Call 解析器</li><li>调用执行器</li><li>上下文维护器</li></ul><p>模型负责判断“下一步该调用什么”。</p><p>代码负责执行真实动作。</p><p>这就是 Agent 调工具的本质。</p><h2 id="4-Function-Calling-解决的是协议稳定性"><a href="#4-Function-Calling-解决的是协议稳定性" class="headerlink" title="4. Function Calling 解决的是协议稳定性"></a>4. Function Calling 解决的是协议稳定性</h2><p>早期 ReAct 常见的是文本协议：</p><figure class="highlight text"><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">Action: query_order</span><br><span class="line">Action Input: &#123;&quot;order_id&quot;: &quot;123&quot;&#125;</span><br></pre></td></tr></table></figure><p>这种方式能跑，但有一个问题：不稳定。</p><p>模型可能多写一句解释，可能少写一个字段，可能输出不合法 JSON。只要格式漂移，解析器就会出问题。</p><p>Function Calling &#x2F; Tool Calling 做的事情，是把这一步升级成模型 API 原生支持的结构化协议。</p><p>开发者用 JSON Schema 描述工具，模型返回结构化 tool call，而不是随便写一段文本。</p><p>它的核心价值不是让模型真的调用函数。</p><p>模型仍然不执行函数。真正执行函数的仍然是 Runtime。</p><p>Function Calling 的价值在于，让模型用稳定、机器可读、schema 约束的方式表达：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">我现在要调用哪个工具，以及参数是什么。</span><br></pre></td></tr></table></figure><p>所以 ReAct 和 Function Calling 不是替代关系。</p><p>它们解决的是两个层面的问题：</p><ul><li>ReAct 解决“什么时候思考，什么时候行动”</li><li>Function Calling 解决“行动指令如何稳定表达”</li></ul><p>一个是推理-行动范式，一个是工具调用接口协议。</p><img src="/images/agent-react-3.webp" alt="图 3：ReAct 与 Function Calling 的层级关系" width="1672" height="941" loading="lazy" style="max-width:100%;height:auto"><h2 id="5-LangChain、LangGraph-和多-Agent"><a href="#5-LangChain、LangGraph-和多-Agent" class="headerlink" title="5. LangChain、LangGraph 和多 Agent"></a>5. LangChain、LangGraph 和多 Agent</h2><p>如果只是手写一个最小 ReAct Agent，100 到 200 行 Python 就能跑起来。</p><p>但一旦进入试生产，真正消耗时间的通常不是 agent loop，而是这些问题：</p><ul><li>工具粒度怎么设计</li><li>工具 schema 怎么定义</li><li>Observation 怎么压缩</li><li>失败后怎么重试</li><li>执行多少步应该停止</li><li>哪些操作需要人工确认</li><li>每次运行怎么记录 trace</li><li>如何构建评测集</li></ul><p>这也是 LangChain 和 LangGraph 的价值所在。</p><p>LangChain 更像 AI 应用工具箱，提供模型集成、Prompt Template、Tool、Retriever、Memory、Output Parser 等组件。</p><p>LangGraph 更像有状态图执行引擎，适合复杂流程、多 Agent 协作、条件分支、循环推理、人工审批、中断恢复和长任务。</p><p>一句话概括：</p><figure class="highlight text"><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">LangChain 提供组件。</span><br><span class="line">LangGraph 负责编排。</span><br></pre></td></tr></table></figure><p>至于多 Agent，不应该一开始就上。</p><p>更稳妥的演进路线是：</p><figure class="highlight text"><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">单 Agent + 多 Tools</span><br><span class="line">-&gt; 复杂后拆 Specialist Agent</span><br><span class="line">-&gt; 需要协作时加 Supervisor / Router</span><br><span class="line">-&gt; 高频稳定流程沉淀为 Workflow / Graph</span><br></pre></td></tr></table></figure><img src="/images/agent-react-4.webp" alt="图 4：从单 Agent 到多 Agent / Workflow 的演进路线" width="1672" height="941" loading="lazy" style="max-width:100%;height:auto"><p>Tool 是能力原子。</p><p>Agent 是任务专家。</p><p>Supervisor 是任务调度器。</p><p>Workflow 是确定性流程。</p><p>服务端开发者应该很熟悉这个判断：不是所有逻辑都应该拆成微服务，也不是所有流程都应该交给模型动态决策。</p><p>稳定、高频、规则明确的流程，更适合 workflow。</p><p>开放、不稳定、需要判断的问题，才适合交给 Agent。</p><h2 id="6-Tool、MCP、Skill-怎么理解"><a href="#6-Tool、MCP、Skill-怎么理解" class="headerlink" title="6. Tool、MCP、Skill 怎么理解"></a>6. Tool、MCP、Skill 怎么理解</h2><p>这几个概念也很容易混在一起。</p><p>可以这样区分：</p><ul><li>Tool：Agent 运行时可调用的函数、API 或动作</li><li>MCP：把外部工具、资源、Prompt 标准化暴露给 Agent 的协议</li><li>Skill：把某类任务的方法论、流程、规范、资源和脚本打包成能力包</li></ul><p>一个粗略类比：</p><figure class="highlight text"><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">Tool 是可执行函数。</span><br><span class="line">MCP 是接入协议。</span><br><span class="line">Skill 是操作手册和能力包。</span><br><span class="line">Agent 是读手册并决定怎么执行的人。</span><br></pre></td></tr></table></figure><p>所以 Skill 不一定等于 Tool。</p><p>一个 Skill 可能只是告诉 Agent 应该按什么流程做事，也可能附带脚本、模板和资源。</p><p>对工程师来说，可以把 Tool 看作函数，把 MCP 看作标准化接口层，把 Skill 看作可复用的任务上下文包。</p><h2 id="7-从服务端开发视角看，需要补哪些拼图"><a href="#7-从服务端开发视角看，需要补哪些拼图" class="headerlink" title="7. 从服务端开发视角看，需要补哪些拼图"></a>7. 从服务端开发视角看，需要补哪些拼图</h2><p>如果从服务端开发的视角看 Agent 开发，有一部分能力是可以迁移的。</p><p>比如：</p><ul><li>API 设计</li><li>异步任务</li><li>状态管理</li><li>权限控制</li><li>日志追踪</li><li>错误处理</li><li>数据建模</li><li>服务治理</li><li>工程部署</li></ul><p>但也有一些新问题需要单独补课。</p><p>我目前看到的主要是这些：</p><ul><li>怎么把业务任务拆成 Agent 可以执行的结构</li><li>怎么设计稳定的 Tool schema</li><li>怎么控制上下文和 Observation</li><li>怎么建设 eval set</li><li>怎么记录 trace，并用 trace 调试行为</li><li>怎么判断哪些流程该 workflow 化，哪些可以交给 Agent 判断</li><li>怎么设计权限、审批、幂等和回滚</li><li>怎么控制成本、延迟和模型 fallback</li></ul><p>这些问题不一定都要在第一天解决。</p><p>但如果一个 Agent 真要进入业务系统，它们迟早会出现。</p><p>这也是我觉得服务端开发经验有用的地方。</p><p>服务端工程本来就经常要处理不稳定的外部依赖、复杂的业务状态和不可控的用户输入。</p><p>只不过这一次，被接入系统的外部能力变成了 LLM 和一组可被模型选择的工具。</p><h2 id="8-AgentOps：我目前看到的生产化问题"><a href="#8-AgentOps：我目前看到的生产化问题" class="headerlink" title="8. AgentOps：我目前看到的生产化问题"></a>8. AgentOps：我目前看到的生产化问题</h2><p>看 Agent 系统的生产化讨论时，经常会遇到 AgentOps 这个词。</p><p>它还没有像 DevOps、MLOps 那样完全标准化。我的理解是，它现在更像一组正在成型的工程实践。</p><p>当 Agent 不只是回答问题，而是开始检索数据、调用工具、执行动作、影响业务系统时，你就不能再把它当成一个普通接口。</p><p>你需要知道它每一步做了什么，为什么这么做，调用了什么工具，花了多少钱，哪里失败了，能不能回滚，下次怎么避免同类错误。</p><p>所以我会先把 AgentOps 理解成：</p><blockquote><p>面向生产 Agent 的运行、观测、评测和治理能力。</p></blockquote><img src="/images/agent-react-5.webp" alt="图 5：AgentOps 六层控制面" width="1672" height="941" loading="lazy" style="max-width:100%;height:auto"><p>它不是单个工具，也不是简单的日志平台，而是一组围绕 Agent 生命周期的工程能力。</p><p>第一层是配置控制。</p><p>Prompt、Tool、Skill、模型选择、路由策略、权限策略，都不应该散落在代码里。</p><p>它们应该可配置、可版本化、可灰度、可回滚。</p><p>否则每一次改 prompt、改工具描述、换模型、调路由规则，都要重新发版，迭代会非常重。</p><p>第二层是运行观测。</p><p>传统服务只看日志、指标、链路追踪还不够。</p><p>Agent 需要的是 Agent Observability。</p><p>一次完整运行里，至少要记录这些东西：</p><ul><li>用户输入</li><li>被选中的 prompt 和版本</li><li>模型请求和响应</li><li>每一次工具调用</li><li>工具入参和返回</li><li>Observation 如何进入上下文</li><li>中间状态如何变化</li><li>最终输出</li><li>人工修正</li><li>业务结果</li><li>token、成本、延迟和错误</li></ul><p>普通日志只能告诉你“系统有没有报错”。</p><p>Agent trace 要回答的是：“这个 Agent 为什么会这样做”。</p><p>第三层是评测体系。</p><p>没有 eval，就没有 Agent 迭代。</p><p>因为 Agent 的失败经常不是代码异常，而是“看起来成功，实际上做错了”。</p><p>比如工具调用成功了，但选错了工具；回答生成了，但漏掉关键约束；流程跑完了，但业务结果不合格。</p><p>这类问题不能只靠线上反馈，也不能靠人工偶尔看几条日志。</p><p>你需要把真实任务沉淀成 golden tasks，把失败案例沉淀成 regression set。每次改 prompt、换模型、改 tool schema、调整 workflow，都要跑一遍评测。</p><p>对 Agent 来说，eval 就是 CI。</p><p>第四层是发布治理。</p><p>传统代码发布有灰度、回滚、监控、告警。</p><p>Agent 也需要类似机制，而且要更细。</p><p>因为 Agent 的变化不只来自代码，还来自 prompt、模型版本、工具返回、知识库内容、上下文策略和路由策略。</p><p>成熟一点的发布流程应该包括：</p><ul><li>shadow test：先旁路运行，不影响真实结果</li><li>canary：只放给小比例流量</li><li>A&#x2F;B test：比较不同 prompt、模型或 workflow</li><li>rollback：质量下降时快速切回上一版本</li><li>freeze：出现风险时冻结高危工具或写操作</li><li>human approval：敏感动作必须人工确认</li></ul><img src="/images/agent-react-6.webp" alt="图 6：AgentOps 生命周期" width="1672" height="941" loading="lazy" style="max-width:100%;height:auto"><p>第五层是权限和安全。</p><p>Agent 一旦能调用工具，就不只是“生成文本的模型”。</p><p>它变成了一个有行动能力的系统参与者。</p><p>所以工具权限必须最小化，写操作必须受控，高危动作必须审批，敏感数据必须脱敏，所有关键动作必须可审计。</p><p>这部分不能靠 prompt 里的“请你谨慎操作”解决。</p><p>它应该由系统权限、工具网关、审批流和审计日志来保证。</p><p>第六层是多模型和成本控制。</p><p>生产里的模型供应商、价格、延迟、稳定性都会变化。</p><p>成熟系统最好抽象出 Model Gateway &#x2F; Model Router，支持 primary model、fallback model、timeout fallback、error fallback、cost-based routing、task-based routing、A&#x2F;B test、shadow test 和灰度切换。</p><p>不是所有任务都需要最强模型。</p><p>简单分类、格式转换、信息抽取，可以走便宜模型；复杂推理、关键决策、长上下文分析，再走更强模型。</p><p>这不是省钱小技巧，而是生产系统的稳定性设计。</p><p>所以我不太把 AgentOps 当成一个新名词看。</p><p>它更像是在提醒我们：Agent 不是一次性交付的功能，而是一套需要持续观察、评估、约束、回滚和迭代的概率系统。</p><p>如果说 ReAct 解决的是 Agent 的最小行动闭环，那么 AgentOps 关注的是 Agent 进入生产之后的长期运行问题。</p><h2 id="9-总结"><a href="#9-总结" class="headerlink" title="9. 总结"></a>9. 总结</h2><p>如果用工程师能理解的话说，Agent 不一定要被理解成一个全新的工程门类。</p><p>它更像是在传统业务系统里接入了一个会推理、会选择工具、但输出不完全稳定的决策组件。</p><p>围绕这个组件，服务端开发者可以先关注这些问题：</p><ul><li>用 Tool schema 约束动作</li><li>用 Runtime 执行真实函数</li><li>用 Workflow 固化稳定流程</li><li>用 Trace 记录运行过程</li><li>用 Eval 衡量效果</li><li>用权限和审批控制风险</li><li>用 fallback 管住成本和稳定性</li></ul><p>这也是我目前理解 AI Agent 开发的一条线索：</p><p>先理解 ReAct 的思考-行动闭环，再理解工具调用的运行时机制，最后把它落到工程系统的稳定性、可控性和可持续迭代能力上。</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;img src=&quot;/images/agent-react-cover.webp&quot; alt=&quot;服务端系统里的 Agent Runtime&quot; width=&quot;1672&quot; height=&quot;941&quot; loading=&quot;eager&quot;</summary>
        
      
    
    
    
    <category term="AI" scheme="https://blog.nerdhan.top/categories/AI/"/>
    
    
    <category term="AI Agent" scheme="https://blog.nerdhan.top/tags/AI-Agent/"/>
    
    <category term="ReAct" scheme="https://blog.nerdhan.top/tags/ReAct/"/>
    
    <category term="LLM" scheme="https://blog.nerdhan.top/tags/LLM/"/>
    
    <category term="AgentOps" scheme="https://blog.nerdhan.top/tags/AgentOps/"/>
    
  </entry>
  
  <entry>
    <title>https 从一次SSL证书验证失败讲起</title>
    <link href="https://blog.nerdhan.top/2026/02/14/https-SSL%E8%AF%81%E4%B9%A6%E9%AA%8C%E8%AF%81%E5%A4%B1%E8%B4%A5/"/>
    <id>https://blog.nerdhan.top/2026/02/14/https-SSL%E8%AF%81%E4%B9%A6%E9%AA%8C%E8%AF%81%E5%A4%B1%E8%B4%A5/</id>
    <published>2026-02-14T10:15:43.000Z</published>
    <updated>2026-06-04T17:49:38.832Z</updated>
    
    <content type="html"><![CDATA[<h2 id="报警响起，但是…"><a href="#报警响起，但是…" class="headerlink" title="报警响起，但是…?"></a>报警响起，但是…?</h2><p>日常我们的KA会上传菜单，其中自然包含很多菜品的图片 ，某天，后天检测到大量的图片拉取失败。</p><p>随便找到一张图片的URL，在服务器上进行请求，出现如下错误。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">curl</span>: (<span class="number">60</span>) <span class="variable constant_">SSL</span> certificate <span class="attr">problem</span>: certificate has expired</span><br></pre></td></tr></table></figure><p>然而使用 MacBook 本地 <code>curl</code> 是成功的，在chrome浏览器也实验成功。</p><p>观察可以发现，在服务器上 ping 对方 cdn 域名，和在mac 本地 ping 对方 cdn 域名，返回的 IP 是不同的，难道是 cdn 问题？</p><p><img src="https://github.com/Qinzhehan52/blog-backup/blob/master/source/images/image.png?raw=true" alt="image.png"></p><p><img src="https://github.com/Qinzhehan52/blog-backup/blob/master/source/images/image%201.png?raw=true" alt="image.png"></p><p>那假如我们在 MacBook 修改 Hosts文件强行将解析指向 IP 呢？ 也是成功的。</p><p><img src="https://github.com/Qinzhehan52/blog-backup/blob/master/source/images/image%202.png?raw=true" alt="image.png"></p><p>有的同学认为，SSL证书验证，和客户端没有关系，一定是服务端的问题，这种观点正确吗 ？</p><h2 id="回顾-https-，也许是因为它？"><a href="#回顾-https-，也许是因为它？" class="headerlink" title="回顾 https ，也许是因为它？"></a>回顾 https ，也许是因为它？</h2><p>我们想象一下，如果我们自己去设计 https ，在验证一个我们不认识的站点的证书时，大概是要依赖一个权威机构的给我们的”名单”，一旦这个”名单”不再准确时，就会发生误判。而事实也的确如此。</p><p>SSL证书验证的流程是 :</p><p><img src="https://github.com/Qinzhehan52/blog-backup/blob/master/source/images/image%203.png?raw=true" alt="image.png"></p><p>在3.过程中，客户端验证服务器的证书，检查：</p><ul><li>证书是否由受信任的 CA 签发</li><li>证书是否在有效期内</li><li>证书上的公钥是否与服务器的主机名匹配</li></ul><p>而为了完成这些验证，客户端需要有一个预安装的受信任的根证书列表。这些根证书由证书颁发机构（CA）签发，CA 是负责验证和颁发数字证书的机构。</p><p>由于不同的操作系统和浏览器在其信任存储中维护各自的根证书列表，我们在使用不同的客户端访问相同的https资源时，会得到不同的验证结果。</p><p>举个例子：如果我们手头有事多年前的Chrome浏览器，它大概率是不能访问目前很多合法的https网络服务的，因为他的根证书列表太老了，不包含目前很多流行的根证书。</p><p><img src="https://github.com/Qinzhehan52/blog-backup/blob/master/source/images/image%204.png?raw=true" alt="image.png"></p><p>难道说，我们这次也遇到了这个问题？</p><h2 id="快速止血"><a href="#快速止血" class="headerlink" title="快速止血"></a>快速止血</h2><p>快速恢复的方案大概有三个：</p><ol><li>使用 <code>InsecureSkipVerify</code> ，跳过 SSL 验证；</li><li>更新系统的CA证书列表；</li><li>不更新系统的CA证书列表而是在 golang 代码中指定 CA 证书列表。</li></ol><p>对比方案优劣，第二个方案被我们首先放弃，更新系统CA证书是一个系统级操作，其影响面是更大的，且运维操作的步骤也非常繁琐。</p><p>使用方案三，如果我们可以找到可信的CA证书列表，方案三更适宜作为长期方案，但寻找可信CA证书列表以及进行代码开发部署都不是短时间内最好的选择。</p><p>幸运的是，之前的开发同学由于担忧第三方图床技术良莠不齐，已经在代码中实现了通过 <code>apollo</code> 配置控制指定域名的图床使用<code>InsecureSkipVerify</code> 的功能。通过修改配置我们实现快速止血。</p><h2 id="回到现场"><a href="#回到现场" class="headerlink" title="回到现场"></a>回到现场</h2><p>为了探究问题的根源，并且得到长期的解决方案，我们继续对这个图床进行研究。</p><p>（此处将使用与当时同样使用了Let’s Encrypt证书的另一网站继续演示）</p><p><img src="https://github.com/Qinzhehan52/blog-backup/blob/master/source/images/image%205.png?raw=true" alt="image.png"></p><p><img src="https://github.com/Qinzhehan52/blog-backup/blob/master/source/images/image%206.png?raw=true" alt="image.png"></p><p>如此巧合，我们要访问的网站，<em><strong>最近更新了SSL证书</strong></em>，使用了 Let’s Encrypt 颁发的证书，这家 CA 频发兼容性问题。</p><p>在Let’s Encrypt官网，一份公告说明最近确实发生了些什么。</p><p><img src="https://github.com/Qinzhehan52/blog-backup/blob/master/source/images/image%207.png?raw=true" alt="image.png"></p><p>使用Chrome查看该图床证书的详细信息，可以看到其证书CA确实是ISRG Root X1。</p><p><img src="https://github.com/Qinzhehan52/blog-backup/blob/master/source/images/6009e546-24f4-4235-aeea-9134f84bf230.png?raw=true" alt="image.png"></p><p>不同的软件会使用CA证书列表进行SSL验证，比如 <code>curl</code> 使用 <code>nss</code>， 而 <code>wget</code> 使用 <code>openssl</code>，我们运行在服务器上的 golang 服务 <code>http.Client</code> 默认使用系统 CA 证书来验证 SSL 证书。</p><p>那么我们的服务器到底对 ISRG Root X1 兼容行如何？</p><p>我们使用的 docker 镜像基于 CentOS7.9，实际是一个非常老的版本，其内置的 <code>curl</code> <code>wget</code> 以及 <code>ca-certificates</code> 版本都很陈旧，对于较新的CA机构，兼容性比较差。</p><p>下面的图片分别展示了<code>curl</code> <code>wget</code>  <code>ca-certificates</code> 版本依赖的 <code>nss</code> <code>openssl</code> 版本。</p><p><img src="https://github.com/Qinzhehan52/blog-backup/blob/master/source/images/image%208.png?raw=true" alt="image.png"></p><p><img src="https://github.com/Qinzhehan52/blog-backup/blob/master/source/images/image%209.png?raw=true" alt="image.png"></p><p><img src="https://github.com/Qinzhehan52/blog-backup/blob/master/source/images/image%2010.png?raw=true" alt="image.png"></p><p>在 Let’ Encrypt 官网的兼容性文档中，明确了支持 ISRG Root X1 的各种平台版本，其中 <code>NSS</code> &gt; 3.26, 明显我们的镜像不满足，对于 RHEL &gt;&#x3D; 6.10, 7.4 (<a href="https://src.fedoraproject.org/rpms/ca-certificates/c/02204a071d2effe7cdb840c1a2763bcdc396c4be">with updates applied</a>), 8+ 这一项，我们进入后可以看到</p><blockquote><p>Platforms that trust ISRG Root X1</p><ul><li>Windows &gt;&#x3D; <a href="https://learn.microsoft.com/en-us/security/trusted-root/participants-list">XP SP3, Server 2008</a> (unless <a href="https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-r2-and-2008/cc733922%28v=ws.10%29">Automatic Root Certificate Updates</a> have been disabled)</li><li>macOS &gt;&#x3D; <a href="https://support.apple.com/en-us/103425">10.12.1 Sierra</a></li><li>iOS &gt;&#x3D; <a href="https://support.apple.com/en-us/HT207177">10</a></li><li>Android &gt;&#x3D; <a href="https://android.googlesource.com/platform/system/ca-certificates/+/android-7.1.1_r15">7.1.1</a></li><li>Firefox &gt;&#x3D; <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1204656">50.0</a></li><li>Ubuntu &gt;&#x3D; <a href="https://launchpad.net/ubuntu/+source/ca-certificates/20161102">12.04 Precise Pangolin</a> (with updates applied)</li><li>Debian &gt;&#x3D; <a href="https://tracker.debian.org/news/812114/accepted-ca-certificates-20161102-source-all-into-unstable/">8 &#x2F; Jessie</a> (with updates applied)</li><li>RHEL &gt;&#x3D; 6.10, 7.4 (<a href="https://src.fedoraproject.org/rpms/ca-certificates/c/02204a071d2effe7cdb840c1a2763bcdc396c4be">with updates applied</a>), 8+</li><li>Java &gt;&#x3D; <a href="https://www.oracle.com/java/technologies/javase/7u151-relnotes.html">7u151</a>, <a href="https://www.oracle.com/java/technologies/javase/8u141-relnotes.html">8u141</a>, <a href="https://www.oracle.com/java/technologies/javase/9-all-relnotes.html#JDK-8177539">9+</a></li><li>NSS &gt;&#x3D; <a href="https://nss-crypto.org/reference/security/nss/legacy/nss_releases/nss_3.26_release_notes/index.html">3.26</a></li><li>Chrome &gt;&#x3D; <a href="https://chromium.googlesource.com/chromium/src/+/main/net/data/ssl/chrome_root_store/faq.md#when-are-these-changes-taking-place">105</a> (earlier versions use the operating system trust store)</li><li>PlayStation &gt;&#x3D; <a href="https://web.archive.org/web/20210306180757/https://www.sie.com/content/dam/corporate/jp/guideline/PS4_Web_Content-Guidelines_e.pdf">PS4 v8.0.0</a></li></ul></blockquote><p>而 <code>openssl</code> 1.0x版本不支持 ISRG Root X1 的 case，在 stackOverFlow medium askUbuntu Github 等技术网站也有无数的吐槽。</p><p>到此，可以得出结论，我们的服务器 golang 服务，<code>wget</code> <code>curl</code>，均不支持 ISRG Root X1。</p><h2 id="引申阅读"><a href="#引申阅读" class="headerlink" title="引申阅读"></a>引申阅读</h2><p><a href="https://www.secrss.com/articles/71306"><strong>苹果大幅缩短安全证书有效期引发众怒</strong></a></p><p><strong>突发事件！Apple Music等核心服务因SSL&#x2F;TLS证书过期发生中断</strong></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本篇通过一个案例，我们回顾了下 https 的验证流程，了解了工具的异同、软件版本的差异，带来的SSL验证结果的不同。</p><p>软件工程、网络工程发展至今，有了很多规范和广泛被应用的基础建设，然而这些都是人的创造物，没有100%的正确率。我们大概率不是 0 day 的发现者，但是一根绳可能以 n 种方式绊倒不同的人。</p><h3 id="经验教训"><a href="#经验教训" class="headerlink" title="经验教训"></a>经验教训</h3><ul><li>在使用第三方服务时，要充分考虑证书兼容性问题</li><li>系统基础设施要保持更新，避免使用过于陈旧的版本</li><li>在开发时预留降级和配置开关，方便应急处理</li></ul>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h2 id=&quot;报警响起，但是…&quot;&gt;&lt;a href=&quot;#报警响起，但是…&quot; class=&quot;headerlink&quot; title=&quot;报警响起，但是…?&quot;&gt;&lt;/a&gt;报警响起，但是…?&lt;/h2&gt;&lt;p&gt;日常我们的KA会上传菜单，其中自然包含很多菜品的图片</summary>
        
      
    
    
    
    <category term="Network" scheme="https://blog.nerdhan.top/categories/Network/"/>
    
    
    <category term="HTTPS" scheme="https://blog.nerdhan.top/tags/HTTPS/"/>
    
    <category term="SSL" scheme="https://blog.nerdhan.top/tags/SSL/"/>
    
  </entry>
  
  <entry>
    <title>elastic-search-php超时控制探索</title>
    <link href="https://blog.nerdhan.top/2021/11/03/elastic-search-php%E8%B6%85%E6%97%B6%E6%8E%A7%E5%88%B6%E6%8E%A2%E7%B4%A2/"/>
    <id>https://blog.nerdhan.top/2021/11/03/elastic-search-php%E8%B6%85%E6%97%B6%E6%8E%A7%E5%88%B6%E6%8E%A2%E7%B4%A2/</id>
    <published>2021-11-03T06:42:22.000Z</published>
    <updated>2026-06-04T17:49:38.832Z</updated>
    
    <content type="html"><![CDATA[<ul><li>&#x2F;vendor&#x2F;es-client&#x2F;elasticsearch&#x2F;src&#x2F;Elasticsearch&#x2F;Connections&#x2F;Connection.php:169<br><img src="/images/image2021-5-21_16-9-10.png" alt="此处将调用ringPHP的curlHandler"></li></ul><p>此处将调用ringPHP的curlHandler<br><a href="https://ringphp.readthedocs.io/en/latest/client_handlers.html">https://ringphp.readthedocs.io/en/latest/client_handlers.html</a></p><p>根据ringPHP文档，我们可以在此处设法传递我们想要的curl配置<br>那么，在自己封装框架内，适合的地方开放一个可设置curl参数的接口即可为es定义一个某集群请求全局的超时控制</p><p><img src="/images/image2021-5-21_16-9-49.png" alt="ringPHP"></p><ul><li>es-client&#x2F;elasticsearch&#x2F;src&#x2F;Elasticsearch&#x2F;Client.php:936<br><img src="/images/image2021-5-21_16-13-48.png" alt="Client.php"><br><img src="/images/image2021-5-21_16-14-53.png" alt="Client.php"><br><img src="/images/image2021-5-21_16-15-58.png" alt="Client.php"><br><img src="/images/image2021-5-21_16-16-16.png" alt="Client.php"></li></ul>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;ul&gt;
&lt;li&gt;&amp;#x2F;vendor&amp;#x2F;es-client&amp;#x2F;elasticsearch&amp;#x2F;src&amp;#x2F;Elasticsearch&amp;#x2F;Connections&amp;#x2F;Connection.php:169&lt;br&gt;&lt;img</summary>
        
      
    
    
    
    <category term="Backend" scheme="https://blog.nerdhan.top/categories/Backend/"/>
    
    
    <category term="PHP" scheme="https://blog.nerdhan.top/tags/PHP/"/>
    
    <category term="Elasticsearch" scheme="https://blog.nerdhan.top/tags/Elasticsearch/"/>
    
  </entry>
  
  <entry>
    <title>macos mojave zlib安装</title>
    <link href="https://blog.nerdhan.top/2020/01/15/macos%20mojave%20zlib%E5%AE%89%E8%A3%85/"/>
    <id>https://blog.nerdhan.top/2020/01/15/macos%20mojave%20zlib%E5%AE%89%E8%A3%85/</id>
    <published>2020-01-15T10:43:18.000Z</published>
    <updated>2026-06-04T17:49:38.832Z</updated>
    
    <content type="html"><![CDATA[<h2 id="遇到的问题"><a href="#遇到的问题" class="headerlink" title="遇到的问题"></a>遇到的问题</h2><p>编译安装 <code>php7.2</code> 时，<code>configure</code> 显示找不到 <code>zlib</code></p><h2 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h2><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">installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg -target /</span><br></pre></td></tr></table></figure><p>执行这条命令后反馈安装失败，重新执行 <code>configure</code> 仍找不到 <code>zlib</code></p><p>先执行</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"><span class="built_in">rm</span> -f /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg</span><br></pre></td></tr></table></figure><p>  再执行 </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">xcode-select --install</span><br></pre></td></tr></table></figure><p>再执行一次</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">installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg -target /</span><br></pre></td></tr></table></figure><p>  成功</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h2 id=&quot;遇到的问题&quot;&gt;&lt;a href=&quot;#遇到的问题&quot; class=&quot;headerlink&quot; title=&quot;遇到的问题&quot;&gt;&lt;/a&gt;遇到的问题&lt;/h2&gt;&lt;p&gt;编译安装 &lt;code&gt;php7.2&lt;/code&gt; 时，&lt;code&gt;configure&lt;/code&gt; 显示找不到</summary>
        
      
    
    
    
    <category term="Env Setup" scheme="https://blog.nerdhan.top/categories/Env-Setup/"/>
    
    
    <category term="PHP" scheme="https://blog.nerdhan.top/tags/PHP/"/>
    
    <category term="macOS" scheme="https://blog.nerdhan.top/tags/macOS/"/>
    
  </entry>
  
  <entry>
    <title>charlies代理问题</title>
    <link href="https://blog.nerdhan.top/2019/10/16/charlies%E4%BB%A3%E7%90%86%E9%97%AE%E9%A2%98/"/>
    <id>https://blog.nerdhan.top/2019/10/16/charlies%E4%BB%A3%E7%90%86%E9%97%AE%E9%A2%98/</id>
    <published>2019-10-16T10:35:26.000Z</published>
    <updated>2026-06-04T17:49:38.832Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>同时使用 <code>shadowsocks(r)/v2ray</code> 与 <code>charles</code> 时，<code>charles map remote</code> 功能工作不正常。</p><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><ol><li><p>手机&#x2F;mac 关闭 <code>shadowsocks(r)/v2ray</code>, 相当于去掉这两个软件设置的系统代理</p></li><li><p>打开 <code>charles</code>, 确认 <code>enable transparent HTTP proxying/ macOS proxy</code> 功能打开</p></li><li><p>确认手机<code>wifi-高级-代理</code> 或mac <code>网络偏好设置-高级-代理</code> 相关配置正常，如自动代理配置、网页代理、安全网页代理</p></li></ol>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h2 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h2&gt;&lt;p&gt;同时使用 &lt;code&gt;shadowsocks(r)/v2ray&lt;/code&gt; 与 &lt;code&gt;charles&lt;/code&gt;</summary>
        
      
    
    
    
    <category term="软件使用" scheme="https://blog.nerdhan.top/categories/%E8%BD%AF%E4%BB%B6%E4%BD%BF%E7%94%A8/"/>
    
    
    <category term="Charles" scheme="https://blog.nerdhan.top/tags/Charles/"/>
    
    <category term="Proxy" scheme="https://blog.nerdhan.top/tags/Proxy/"/>
    
  </entry>
  
  <entry>
    <title>daemon守护进程创建</title>
    <link href="https://blog.nerdhan.top/2019/06/24/daemon%E5%AE%88%E6%8A%A4%E8%BF%9B%E7%A8%8B%E5%88%9B%E5%BB%BA/"/>
    <id>https://blog.nerdhan.top/2019/06/24/daemon%E5%AE%88%E6%8A%A4%E8%BF%9B%E7%A8%8B%E5%88%9B%E5%BB%BA/</id>
    <published>2019-06-24T07:34:55.000Z</published>
    <updated>2026-06-04T17:49:38.832Z</updated>
    
    <content type="html"><![CDATA[<h2 id="php创建守护进程代码"><a href="#php创建守护进程代码" class="headerlink" title="php创建守护进程代码"></a>php创建守护进程代码</h2><figure class="highlight php"><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="meta">&lt;?php</span></span><br><span class="line">    <span class="comment">// 一次fork  </span></span><br><span class="line">    <span class="variable">$pid</span> = <span class="title function_ invoke__">pcntl_fork</span>();</span><br><span class="line">    <span class="keyword">if</span> ( <span class="variable">$pid</span> &lt; <span class="number">0</span> ) &#123;</span><br><span class="line">        <span class="keyword">exit</span>( <span class="string">&#x27; fork error. &#x27;</span> );</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span>( <span class="variable">$pid</span> &gt; <span class="number">0</span> ) &#123;</span><br><span class="line">        <span class="keyword">exit</span>( <span class="string">&#x27; parent process. &#x27;</span> );</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 将当前子进程提升会会话组组长 这是至关重要的一步 </span></span><br><span class="line">    <span class="keyword">if</span> ( ! <span class="title function_ invoke__">posix_setsid</span>() ) &#123;</span><br><span class="line">        <span class="keyword">exit</span>( <span class="string">&#x27; setsid error. &#x27;</span> );</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 二次fork</span></span><br><span class="line">    <span class="variable">$pid</span> = <span class="title function_ invoke__">pcntl_fork</span>();</span><br><span class="line">    <span class="keyword">if</span>( <span class="variable">$pid</span> &lt; <span class="number">0</span> )&#123;</span><br><span class="line">        <span class="keyword">exit</span>( <span class="string">&#x27; fork error. &#x27;</span> );</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span>( <span class="variable">$pid</span> &gt; <span class="number">0</span> ) &#123;</span><br><span class="line">        <span class="keyword">exit</span>( <span class="string">&#x27; parent process. &#x27;</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">    <span class="keyword">for</span>( <span class="variable">$i</span> = <span class="number">1</span> ; <span class="variable">$i</span> &lt;= <span class="number">100</span> ; <span class="variable">$i</span>++ )&#123;</span><br><span class="line">        <span class="title function_ invoke__">sleep</span>( <span class="number">1</span> );</span><br><span class="line">        <span class="title function_ invoke__">file_put_contents</span>( <span class="string">&#x27;daemon.log&#x27;</span>, <span class="variable">$i</span>, FILE_APPEND );</span><br><span class="line">    &#125;</span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure><h2 id="两次fork的作用"><a href="#两次fork的作用" class="headerlink" title="两次fork的作用"></a>两次fork的作用</h2><h3 id="第一次fork"><a href="#第一次fork" class="headerlink" title="第一次fork"></a>第一次fork</h3><p>确保当前进程不是 <code>session leader</code>,  <code>session leader</code> 调用 <code>setsid</code> 不会产生新的 <code>session</code>，下面是 gnu 对 <code>setsid</code> 与 <code>controlling session</code> 关系的解释，子进程在调用 <code>setsid</code> 之后成为了一个新 <code>session</code> 的 <code>leader</code> 并且不再与之前的 <code>controlling session</code> 关联。</p><blockquote><p>One of the attributes of a process is its controlling terminal. Child processes created with fork inherit the controlling terminal from their parent process. In this way, all the processes in a session inherit the controlling terminal from the session leader. A session leader that has control of a terminal is called the controlling process of that terminal.</p></blockquote><blockquote><p>You generally do not need to worry about the exact mechanism used to allocate a controlling terminal to a session, since it is done for you by the system when you log in.</p></blockquote><blockquote><p><strong>An individual process disconnects from its controlling terminal when it calls setsid to become the leader of a new session.</strong> See Process Group Functions.</p></blockquote>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h2 id=&quot;php创建守护进程代码&quot;&gt;&lt;a href=&quot;#php创建守护进程代码&quot; class=&quot;headerlink&quot; title=&quot;php创建守护进程代码&quot;&gt;&lt;/a&gt;php创建守护进程代码&lt;/h2&gt;&lt;figure class=&quot;highlight</summary>
        
      
    
    
    
    <category term="Backend" scheme="https://blog.nerdhan.top/categories/Backend/"/>
    
    
    <category term="PHP" scheme="https://blog.nerdhan.top/tags/PHP/"/>
    
    <category term="Linux" scheme="https://blog.nerdhan.top/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>cmux 多协议服务失败debug记录</title>
    <link href="https://blog.nerdhan.top/2019/06/16/cmux%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/"/>
    <id>https://blog.nerdhan.top/2019/06/16/cmux%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/</id>
    <published>2019-06-16T03:26:27.000Z</published>
    <updated>2026-06-04T17:49:38.832Z</updated>
    
    <content type="html"><![CDATA[<h2 id="cmux-是什么"><a href="#cmux-是什么" class="headerlink" title="cmux 是什么"></a>cmux 是什么</h2><blockquote><p>cmux is a generic Go library to multiplex connections based on their payload. Using cmux, you can serve gRPC, SSH, HTTPS, HTTP, Go RPC, and pretty much any other protocol on the same TCP listener.</p></blockquote><h2 id="cmux-使用过程中的问题"><a href="#cmux-使用过程中的问题" class="headerlink" title="cmux 使用过程中的问题"></a>cmux 使用过程中的问题</h2><p><strong>现象</strong>：</p><p>我使用一个tcp链接，同时支持 <code>websocket</code>, <code>http</code>, <code>grpc</code> 协议，发现 <code>websocket</code> 服务总是握手失败，而且每次都会打一条 <code>http</code> 请求的日志。</p><p><strong>分析</strong>: </p><p><code>websocket</code> 协议握手过程客户端主要是在 <code>http header</code> 加入 <code>Upgrade: websocket</code>，服务端使用 <code>cmux</code> 也是通过匹配这条<code>http header</code> 实现。</p><p>出现问题的 <code>golang</code> 代码如下：</p><figure class="highlight golang"><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">tcpm := cmux.New(l)</span><br><span class="line">grpc1 := tcpm.MatchWithWriters(cmux.HTTP2MatchHeaderFieldPrefixSendSettings(<span class="string">&quot;content-type&quot;</span>, <span class="string">&quot;application/grpc&quot;</span>))</span><br><span class="line">http1 := tcpm.Match(cmux.HTTP1Fast())</span><br><span class="line">http2 := tcpm.Match(cmux.HTTP2())</span><br><span class="line">wsl := tcpm.Match(cmux.HTTP1HeaderField(<span class="string">&quot;Upgrade&quot;</span>, <span class="string">&quot;websocket&quot;</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment">//开始服务</span></span><br><span class="line"><span class="keyword">go</span> serveGPRC(grpc1)</span><br><span class="line"><span class="keyword">go</span> serveWS(wsl)</span><br><span class="line"><span class="keyword">go</span> serveHTTP(http1)</span><br><span class="line"><span class="keyword">go</span> serveHTTP(http2)</span><br></pre></td></tr></table></figure><p>对比 <code>cmux</code> 库自带的 example 没有很大的区别, <strong>但是通过注释我们可以注意到， <code>Match</code> 的顺序很重要</strong></p><p>仔细对比之后发现的确两段 <code>Match</code> 的顺序有所不同，无论是 <code>grpc</code> 请求还是 <code>websocket</code> 的握手请求，都只是特殊化的 <code>http</code> 请求，使用 <code>cmux</code> 时要注意 <code>Match</code> 的顺序，避免服务端将 <code>grpc</code> 请求和  <code>websocket</code> 的握手请求当做 <code>http2</code> <code>http1</code> 请求处理。</p><figure class="highlight golang"><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></pre></td><td class="code"><pre><span class="line">m := cmux.New(l)</span><br><span class="line"></span><br><span class="line"><span class="comment">// We first match the connection against HTTP2 fields. If matched, the</span></span><br><span class="line"><span class="comment">// connection will be sent through the &quot;grpcl&quot; listener.</span></span><br><span class="line">grpcl := m.Match(cmux.HTTP2HeaderFieldPrefix(<span class="string">&quot;content-type&quot;</span>, <span class="string">&quot;application/grpc&quot;</span>))</span><br><span class="line"><span class="comment">//Otherwise, we match it againts a websocket upgrade request.</span></span><br><span class="line">wsl := m.Match(cmux.HTTP1HeaderField(<span class="string">&quot;Upgrade&quot;</span>, <span class="string">&quot;websocket&quot;</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment">// Otherwise, we match it againts HTTP1 methods. If matched,</span></span><br><span class="line"><span class="comment">// it is sent through the &quot;httpl&quot; listener.</span></span><br><span class="line">httpl := m.Match(cmux.HTTP1Fast())</span><br><span class="line"><span class="comment">// If not matched by HTTP, we assume it is an RPC connection.</span></span><br><span class="line">rpcl := m.Match(cmux.Any())</span><br><span class="line"></span><br><span class="line"><span class="comment">// Then we used the muxed listeners.</span></span><br><span class="line"><span class="keyword">go</span> serveGRPC(grpcl)</span><br><span class="line"><span class="keyword">go</span> serveWS(wsl)</span><br><span class="line"><span class="keyword">go</span> serveHTTP(httpl)</span><br><span class="line"><span class="keyword">go</span> serveRPC(rpcl)</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h2 id=&quot;cmux-是什么&quot;&gt;&lt;a href=&quot;#cmux-是什么&quot; class=&quot;headerlink&quot; title=&quot;cmux 是什么&quot;&gt;&lt;/a&gt;cmux 是什么&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;cmux is a generic Go library to</summary>
        
      
    
    
    
    <category term="golang" scheme="https://blog.nerdhan.top/categories/golang/"/>
    
    
    <category term="golang" scheme="https://blog.nerdhan.top/tags/golang/"/>
    
    <category term="cmux" scheme="https://blog.nerdhan.top/tags/cmux/"/>
    
  </entry>
  
  <entry>
    <title>科学上网n步曲</title>
    <link href="https://blog.nerdhan.top/2019/06/12/%E7%A7%91%E5%AD%A6%E4%B8%8A%E7%BD%91n%E6%AD%A5%E6%9B%B2/"/>
    <id>https://blog.nerdhan.top/2019/06/12/%E7%A7%91%E5%AD%A6%E4%B8%8A%E7%BD%91n%E6%AD%A5%E6%9B%B2/</id>
    <published>2019-06-12T06:02:50.000Z</published>
    <updated>2026-06-04T17:49:38.832Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一-shadowsockr-socks5代理-搭建"><a href="#一-shadowsockr-socks5代理-搭建" class="headerlink" title="一. shadowsockr (socks5代理) 搭建"></a>一. shadowsockr (socks5代理) 搭建</h2><p>参考博客<a href="https://www.flyzy2005.com/fan-qiang/shadowsocks/install-shadowsocks-in-one-command/">一键脚本搭建SS&#x2F;搭建SSR服务并开启BBR加速</a></p><p><strong>访问需要配置host</strong> <code>104.27.133.214 www.flyzy2005.com</code></p><ol><li>下载一键安装脚本 <code>git clone -b master https://github.com/flyzy2005/ss-fly</code></li><li>运行安装脚本 <em>ubuntu系统目前默认shell是<code>dash</code>,注意切换</em></li></ol><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">$ ss-fly/ss-fly.sh -ssr</span><br></pre></td></tr></table></figure><span id="more"></span><ol start="3"><li>按照提示输入所需参数即可，完成后终端显示内容如下</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></pre></td><td class="code"><pre><span class="line">Congratulations, ShadowsocksR server install completed!</span><br><span class="line">Your Server IP        :你的服务器ip</span><br><span class="line">Your Server Port      :你的端口</span><br><span class="line">Your Password         :你的密码</span><br><span class="line">Your Protocol         :你的协议</span><br><span class="line">Your obfs             :你的混淆</span><br><span class="line">Your Encryption Method:your_encryption_method</span><br><span class="line"> </span><br><span class="line">Welcome to visit:https://shadowsocks.be/9.html</span><br><span class="line">Enjoy it!</span><br></pre></td></tr></table></figure><ol start="4"><li>一些管理命令</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># ssr略有不同 注意调整</span></span><br><span class="line"><span class="comment">#启动：</span></span><br><span class="line">/etc/init.d/shadowsocks start</span><br><span class="line"><span class="comment">#停止：</span></span><br><span class="line">/etc/init.d/shadowsocks stop</span><br><span class="line"><span class="comment">#重启：</span></span><br><span class="line">/etc/init.d/shadowsocks restart</span><br><span class="line"><span class="comment">#状态：</span></span><br><span class="line">/etc/init.d/shadowsocks status</span><br><span class="line"> </span><br><span class="line"><span class="comment">#配置文件路径：</span></span><br><span class="line">/etc/shadowsocks.json</span><br><span class="line"><span class="comment">#日志文件路径：</span></span><br><span class="line">/var/log/shadowsocks.log</span><br><span class="line"><span class="comment">#代码安装目录：</span></span><br><span class="line">/usr/local/shadowsocks</span><br></pre></td></tr></table></figure><h2 id="二-socks5-转-http-代理"><a href="#二-socks5-转-http-代理" class="headerlink" title="二. socks5 转 http 代理"></a>二. socks5 转 http 代理</h2><h3 id="cow"><a href="#cow" class="headerlink" title="cow"></a>cow</h3><p>cow项目首页具有完善的wiki，可以参考以ss提供的socks5代理搭建http代理<a href="https://github.com/cyfdecyf/cow">cow on github</a></p><h3 id="polipo"><a href="#polipo" class="headerlink" title="polipo"></a>polipo</h3><p><a href="https://zxc0328.github.io/2017/03/26/proxy-for-terminal/">mac 安装polipo和使用</a></p><h4 id="补充内容"><a href="#补充内容" class="headerlink" title="补充内容"></a>补充内容</h4><p>自定义polipo运行的端口，类似文中修改parentrProxy</p><figure class="highlight plist"><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="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span>?&gt;</span></span><br><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">plist</span> <span class="keyword">PUBLIC</span> <span class="string">&quot;-//Apple//DTD PLIST 1.0//EN&quot;</span> <span class="string">&quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">plist</span> <span class="attr">version</span>=<span class="string">&quot;1.0&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">dict</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">key</span>&gt;</span>Label<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">string</span>&gt;</span>homebrew.mxcl.polipo<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">key</span>&gt;</span>RunAtLoad<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">true</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">key</span>&gt;</span>KeepAlive<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">true</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">key</span>&gt;</span>ProgramArguments<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">array</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">string</span>&gt;</span>/usr/local/opt/polipo/bin/polipo<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">string</span>&gt;</span>socksParentProxy=127.0.0.1:1080<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">      <span class="comment">&lt;!-- 修改运行端口 --&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">string</span>&gt;</span>proxyPort=7777<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">array</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- Set `ulimit -n 65536`. The default macOS limit is 256, that&#x27;s</span></span><br><span class="line"><span class="comment">         not enough for Polipo (displays &#x27;too many files open&#x27; errors).</span></span><br><span class="line"><span class="comment">         It seems like you have no reason to lower this limit</span></span><br><span class="line"><span class="comment">         (and unlikely will want to raise it). --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">key</span>&gt;</span>SoftResourceLimits<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dict</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">key</span>&gt;</span>NumberOfFiles<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">integer</span>&gt;</span>65536<span class="tag">&lt;/<span class="name">integer</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dict</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">dict</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">plist</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="三-终端使用代理"><a href="#三-终端使用代理" class="headerlink" title="三. 终端使用代理"></a>三. 终端使用代理</h2><h3 id="终端中代理http请求"><a href="#终端中代理http请求" class="headerlink" title="终端中代理http请求"></a>终端中代理http请求</h3><h4 id="临时使用"><a href="#临时使用" class="headerlink" title="临时使用"></a>临时使用</h4><p>proxychains 其实并不能把socks5代理转成http代理，但是要把他写在前面。对于npm git wget等操作，使用proxychains即可使其通过socks5代理成功访问目标地址。其原理是Hook 了 sockets 相关的操作，让普通程序的 sockets 数据走 SOCKS&#x2F;HTTP 代理。</p><h5 id="mac安装proxychains4"><a href="#mac安装proxychains4" class="headerlink" title="mac安装proxychains4"></a>mac安装proxychains4</h5><ol><li>重启到安全模式，终端执行</li></ol><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">csrutil <span class="built_in">disable</span></span><br></pre></td></tr></table></figure><ol start="2"><li>重启，终端执行</li></ol><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">brew install proxychains-ng <span class="comment">#安装</span></span><br></pre></td></tr></table></figure><ol start="3"><li>修改配置,其中socks5代理可以换成<code>cow</code>或<code>polipo</code>的http代理</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">cp /usr/local/etc/proxychains.conf ~/.proxychains/proxychains.conf</span><br><span class="line">echo &#x27;socks5  127.0.0.1 1086&#x27; &gt;&gt; ~/.proxychains/proxychains.conf</span><br></pre></td></tr></table></figure><p><strong>NOTICE: go get操作使用<code>proxychains</code>会报错</strong></p><h4 id="长期使用"><a href="#长期使用" class="headerlink" title="长期使用"></a>长期使用</h4><h2 id="shadowsocksr-proxychains-polipo-解决方案"><a href="#shadowsocksr-proxychains-polipo-解决方案" class="headerlink" title="shadowsocksr + proxychains + polipo 解决方案"></a>shadowsocksr + proxychains + polipo 解决方案</h2><p>这套方案的好处是具备 http 和 socks5 两套代理，基本可以应对各种情况下的需求，尤其是本地的 golang 开发环境比较特殊，不适合安装<code>cow</code></p><h3 id="几条方便科学上网的alias"><a href="#几条方便科学上网的alias" class="headerlink" title="几条方便科学上网的alias"></a>几条方便科学上网的alias</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"><span class="built_in">alias</span> fuckgfw=<span class="string">&quot;export http_proxy=127.0.0.1:8123 https_proxy=127.0.0.1:8123&amp;&amp; echo &#x27;&gt; You are out!&#x27;&quot;</span></span><br><span class="line"><span class="built_in">alias</span> unfuckgfw=<span class="string">&quot;unset http_proxy https_proxy &amp;&amp; echo &#x27;&gt; Welcome inside ;)&#x27;&quot;</span></span><br><span class="line"><span class="built_in">alias</span> myip=<span class="string">&quot;curl cip.cc&quot;</span></span><br><span class="line"><span class="built_in">alias</span> fq=<span class="string">&quot;proxychains4&quot;</span></span><br></pre></td></tr></table></figure><h3 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h3><p>2019年，70年大庆，使用的<code>ssr</code>挂了一大批，目前使用<code>v2ray</code>进行科学上网。</p><p><code>v2ray</code>的服务端搭建也是专门的脚本的，<code>windows android macos</code> 都有好用的第三方具备<code>GUI</code>的客户端。</p><p><code>v2ray</code>客户端一般自带了<code>sock5</code>和 <code>http</code>代理。</p><blockquote><p>ps: <code>ios</code>上可以使用<code>shadowracket</code>等APP作为<code>v2ray</code>客户端</p></blockquote><p><em>下面是一些相关资源 ：</em></p><ul><li><a href="https://github.com/233boy/v2ray">v2ray服务端脚本</a></li><li><a href="https://github.com/2dust/v2rayN">v2ray windows 客户端(.net framework 4.6)</a></li><li><a href="https://github.com/yanue/V2rayU">v2ray macos客户端</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;一-shadowsockr-socks5代理-搭建&quot;&gt;&lt;a href=&quot;#一-shadowsockr-socks5代理-搭建&quot; class=&quot;headerlink&quot; title=&quot;一. shadowsockr (socks5代理) 搭建&quot;&gt;&lt;/a&gt;一. shadowsockr (socks5代理) 搭建&lt;/h2&gt;&lt;p&gt;参考博客&lt;a href=&quot;https://www.flyzy2005.com/fan-qiang/shadowsocks/install-shadowsocks-in-one-command/&quot;&gt;一键脚本搭建SS&amp;#x2F;搭建SSR服务并开启BBR加速
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;访问需要配置host&lt;/strong&gt; &lt;code&gt;104.27.133.214 www.flyzy2005.com&lt;/code&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;下载一键安装脚本 &lt;code&gt;git clone -b master https://github.com/flyzy2005/ss-fly&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;运行安装脚本	 &lt;em&gt;ubuntu系统目前默认shell是&lt;code&gt;dash&lt;/code&gt;,注意切换&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;$ ss-fly/ss-fly.sh -ssr&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    <category term="软件使用" scheme="https://blog.nerdhan.top/categories/%E8%BD%AF%E4%BB%B6%E4%BD%BF%E7%94%A8/"/>
    
    
    <category term="shadowsocks" scheme="https://blog.nerdhan.top/tags/shadowsocks/"/>
    
    <category term="shadowsocksr" scheme="https://blog.nerdhan.top/tags/shadowsocksr/"/>
    
    <category term="科学上网" scheme="https://blog.nerdhan.top/tags/%E7%A7%91%E5%AD%A6%E4%B8%8A%E7%BD%91/"/>
    
  </entry>
  
  <entry>
    <title>windows terminal尝试</title>
    <link href="https://blog.nerdhan.top/2019/05/25/windows%20terminal%E5%B0%9D%E8%AF%95/"/>
    <id>https://blog.nerdhan.top/2019/05/25/windows%20terminal%E5%B0%9D%E8%AF%95/</id>
    <published>2019-05-25T10:53:05.000Z</published>
    <updated>2026-06-04T17:49:38.832Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前置工作（环境搭建）"><a href="#前置工作（环境搭建）" class="headerlink" title="前置工作（环境搭建）"></a>前置工作（环境搭建）</h2><ul><li><p>系统版本在 Windows 1903 (build &gt;&#x3D; 10.0.18362.0) 以上</p></li><li><p>安装Visual Studio 2017 或 2019</p></li><li><p>clone 代码到本地 <a href="https://github.com/microsoft/terminal.git">https://github.com/microsoft/terminal.git</a></p></li></ul><span id="more"></span><h2 id="开始编译"><a href="#开始编译" class="headerlink" title="开始编译"></a>开始编译</h2><ul><li><p>用 Visual Studio 2019打开 terminal 本地 Repo, 自动弹出了需要安装的依赖项，下载安装，需要比较长时间的等待</p></li><li><p>用Visual Studio 2019编译时需要设置下项目重定向，打开OpenConsole.sln</p></li></ul><p><img src="https://github.com/Qinzhehan52/blog-backup/blob/master/source/images/QQ20190525230101.jpg?raw=true" alt="设置项目重定向"></p><ul><li>设置编译选项</li></ul><p><img src="https://github.com/Qinzhehan52/blog-backup/blob/master/source//images/QQ20190525230409.jpg?raw=true" alt="设置编译选项"></p><h3 id="报错"><a href="#报错" class="headerlink" title="报错"></a>报错</h3><blockquote><p>无法打开包括文件: “wil&#x2F;Common.h</p></blockquote><p>解决方案：在terminal项目根目录下执行  <code>git submodule update --init --recursive</code>, 安装所有terminal项目的 <code>submodule</code></p><h2 id="成功效果预览"><a href="#成功效果预览" class="headerlink" title="成功效果预览"></a>成功效果预览</h2><p><img src="https://github.com/Qinzhehan52/blog-backup/blob/master/source/images/QQ20190525231337.jpg?raw=true" alt="成功效果预览"></p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前置工作（环境搭建）&quot;&gt;&lt;a href=&quot;#前置工作（环境搭建）&quot; class=&quot;headerlink&quot; title=&quot;前置工作（环境搭建）&quot;&gt;&lt;/a&gt;前置工作（环境搭建）&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;系统版本在 Windows 1903 (build &amp;gt;&amp;#x3D; 10.0.18362.0) 以上&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;安装Visual Studio 2017 或 2019&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;clone 代码到本地 &lt;a href=&quot;https://github.com/microsoft/terminal.git&quot;&gt;https://github.com/microsoft/terminal.git&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="软件使用" scheme="https://blog.nerdhan.top/categories/%E8%BD%AF%E4%BB%B6%E4%BD%BF%E7%94%A8/"/>
    
    
    <category term="Windows" scheme="https://blog.nerdhan.top/tags/Windows/"/>
    
    <category term="Terminal" scheme="https://blog.nerdhan.top/tags/Terminal/"/>
    
  </entry>
  
  <entry>
    <title>mbp 外接显示器开启HiDPI</title>
    <link href="https://blog.nerdhan.top/2018/11/27/mbp%E5%A4%96%E6%8E%A5%E5%B1%8F%E5%B9%95%E5%BC%80%E5%90%AFHiDPI/"/>
    <id>https://blog.nerdhan.top/2018/11/27/mbp%E5%A4%96%E6%8E%A5%E5%B1%8F%E5%B9%95%E5%BC%80%E5%90%AFHiDPI/</id>
    <published>2018-11-27T06:02:50.000Z</published>
    <updated>2026-06-04T17:49:38.832Z</updated>
    
    <content type="html"><![CDATA[<h1 id="mbp-外接显示器开启HiDPI"><a href="#mbp-外接显示器开启HiDPI" class="headerlink" title="mbp 外接显示器开启HiDPI"></a>mbp 外接显示器开启HiDPI</h1><h2 id="1-首先，打开系统分区权限："><a href="#1-首先，打开系统分区权限：" class="headerlink" title="1. 首先，打开系统分区权限："></a>1. 首先，打开系统分区权限：</h2><h3 id="查看SIP状态"><a href="#查看SIP状态" class="headerlink" title="查看SIP状态"></a>查看SIP状态</h3><blockquote><p>在终端中输入csrutil status，就可以看到是enabled还是disabled。</p></blockquote><h3 id="关闭SIP"><a href="#关闭SIP" class="headerlink" title="关闭SIP"></a>关闭SIP</h3><blockquote><ul><li>重启MAC，按住cmd+R直到屏幕上出现苹果的标志和进度条，进入Recovery模式；</li><li>在屏幕最上方的工具栏找到实用工具（左数第3个），打开终端，输入：csrutil disable；</li><li>关掉终端，重启mac；</li><li>重启以后可以在终端中查看状态确认。</li></ul></blockquote><span id="more"></span><h3 id="开启SIP"><a href="#开启SIP" class="headerlink" title="开启SIP"></a>开启SIP</h3><blockquote><p>与关闭的步骤类似，只是在S2中输入csrutil enable即可。<a href="https://www.jianshu.com/p/fe78d2036192">转自简书 Mac开启关闭SIP（系统完整性保护）</a></p></blockquote><h2 id="2-获得显示器的-VendorID-和-ProductID-（制造商ID-和-产品ID），在终端运行："><a href="#2-获得显示器的-VendorID-和-ProductID-（制造商ID-和-产品ID），在终端运行：" class="headerlink" title="2. 获得显示器的 VendorID 和 ProductID （制造商ID 和 产品ID），在终端运行："></a>2. 获得显示器的 VendorID 和 ProductID （制造商ID 和 产品ID），在终端运行：</h2><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">&gt; ioreg -lw0 | grep IODisplayPrefsKey | grep -o <span class="string">&#x27;/[^/]\+&quot;$&#x27;</span></span><br><span class="line"></span><br><span class="line">/AppleBacklightDisplay-610-a034<span class="string">&quot;</span></span><br><span class="line"><span class="string">/AppleDisplay-30ae-61a4&quot;</span></span><br></pre></td></tr></table></figure><blockquote><p>其中第一行610代表的是mac内置显示器，第二行即我们的外接显示器，VendorID&#x3D;30ae，ProductID&#x3D;61a4</p></blockquote><h2 id="3-生成我们需要的配置文件"><a href="#3-生成我们需要的配置文件" class="headerlink" title="3. 生成我们需要的配置文件"></a>3. 生成我们需要的配置文件</h2><p>   使用该网页生成器填写所需信息，生成配置并下载<a href="https://comsysto.github.io/Display-Override-PropertyList-File-Parser-and-Generator-with-HiDPI-Support-For-Scaled-Resolutions/">HiDPI配置生成器</a></p><h2 id="4-复制配置到系统目录"><a href="#4-复制配置到系统目录" class="headerlink" title="4. 复制配置到系统目录"></a>4. 复制配置到系统目录</h2><ul><li><p>OS X 10.11及以上</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">DIR=/System/Library/Displays/Contents/Resources/Overrides</span><br></pre></td></tr></table></figure></li><li><p>OS X 10.10及以下</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">DIR=/System/Library/Displays/Overrides</span><br></pre></td></tr></table></figure></li><li><p>把 VID 和 PID 替换成上面获得的VendorID和ProductID</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></pre></td><td class="code"><pre><span class="line">CONF=<span class="variable">$&#123;DIR&#125;</span>/DisplayVendorID-<span class="variable">$&#123;VID&#125;</span>/DisplayProductID-<span class="variable">$&#123;PID&#125;</span></span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">mkdir</span> -p <span class="variable">$&#123;DIR&#125;</span>/DisplayVendorID-<span class="variable">$&#123;VID&#125;</span></span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">cp</span> 配置文件 <span class="variable">$&#123;CONF&#125;</span></span><br></pre></td></tr></table></figure></li></ul><h2 id="5-打开系统HIDPI设置"><a href="#5-打开系统HIDPI设置" class="headerlink" title="5. 打开系统HIDPI设置"></a>5. 打开系统HIDPI设置</h2><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"><span class="built_in">sudo</span> defaults write /Library/Preferences/com.apple.windowserver DisplayResolutionEnabled -bool YES</span><br></pre></td></tr></table></figure><h2 id="6-重启并使用RDM调整分辨率"><a href="#6-重启并使用RDM调整分辨率" class="headerlink" title="6. 重启并使用RDM调整分辨率"></a>6. 重启并使用RDM调整分辨率</h2><p> <a href="http://avi.alkalay.net/software/RDM/">下载RDM</a></p><p>fix…</p>]]></content>
    
    
    <summary type="html">&lt;h1 id=&quot;mbp-外接显示器开启HiDPI&quot;&gt;&lt;a href=&quot;#mbp-外接显示器开启HiDPI&quot; class=&quot;headerlink&quot; title=&quot;mbp 外接显示器开启HiDPI&quot;&gt;&lt;/a&gt;mbp 外接显示器开启HiDPI&lt;/h1&gt;&lt;h2 id=&quot;1-首先，打开系统分区权限：&quot;&gt;&lt;a href=&quot;#1-首先，打开系统分区权限：&quot; class=&quot;headerlink&quot; title=&quot;1. 首先，打开系统分区权限：&quot;&gt;&lt;/a&gt;1. 首先，打开系统分区权限：&lt;/h2&gt;&lt;h3 id=&quot;查看SIP状态&quot;&gt;&lt;a href=&quot;#查看SIP状态&quot; class=&quot;headerlink&quot; title=&quot;查看SIP状态&quot;&gt;&lt;/a&gt;查看SIP状态&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;在终端中输入csrutil status，就可以看到是enabled还是disabled。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;关闭SIP&quot;&gt;&lt;a href=&quot;#关闭SIP&quot; class=&quot;headerlink&quot; title=&quot;关闭SIP&quot;&gt;&lt;/a&gt;关闭SIP&lt;/h3&gt;&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;重启MAC，按住cmd+R直到屏幕上出现苹果的标志和进度条，进入Recovery模式；&lt;/li&gt;
&lt;li&gt;在屏幕最上方的工具栏找到实用工具（左数第3个），打开终端，输入：csrutil disable；&lt;/li&gt;
&lt;li&gt;关掉终端，重启mac；&lt;/li&gt;
&lt;li&gt;重启以后可以在终端中查看状态确认。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="软件使用" scheme="https://blog.nerdhan.top/categories/%E8%BD%AF%E4%BB%B6%E4%BD%BF%E7%94%A8/"/>
    
    
    <category term="macOS" scheme="https://blog.nerdhan.top/tags/macOS/"/>
    
    <category term="HiDPI" scheme="https://blog.nerdhan.top/tags/HiDPI/"/>
    
  </entry>
  
  <entry>
    <title>docker 部署 LNMP</title>
    <link href="https://blog.nerdhan.top/2018/10/29/docker%E9%83%A8%E7%BD%B2lnmp/"/>
    <id>https://blog.nerdhan.top/2018/10/29/docker%E9%83%A8%E7%BD%B2lnmp/</id>
    <published>2018-10-29T06:02:50.000Z</published>
    <updated>2026-06-04T17:49:38.832Z</updated>
    
    <content type="html"><![CDATA[<h1 id="docker-部署-LNMP"><a href="#docker-部署-LNMP" class="headerlink" title="docker 部署 LNMP"></a>docker 部署 LNMP</h1><h2 id="docker-简介"><a href="#docker-简介" class="headerlink" title="docker 简介"></a>docker 简介</h2><h3 id="什么是docker"><a href="#什么是docker" class="headerlink" title="什么是docker"></a>什么是docker</h3><blockquote><p>Docker 使用 Google 公司推出的 <a href="https://golang.org/">Go 语言</a> 进行开发实现，基于 Linux 内核的 <a href="https://zh.wikipedia.org/wiki/Cgroups">cgroup</a>，<a href="https://en.wikipedia.org/wiki/Linux_namespaces">namespace</a>，以及<a href="https://en.wikipedia.org/wiki/Aufs">AUFS</a> 类的 <a href="https://en.wikipedia.org/wiki/Union_mount">Union FS</a> 等技术，对进程进行封装隔离，属于 <a href="https://en.wikipedia.org/wiki/Operating-system-level_virtualization">操作系统层面的虚拟化技术</a>。由于隔离的进程独立于宿主和其它的隔离的进程，因此也称其为容器。</p></blockquote><blockquote><p>Docker 在容器的基础上，进行了进一步的封装，从文件系统、网络互联到进程隔离等等，极大的简化了容器的创建和维护。使得 Docker 技术比虚拟机技术更为轻便、快捷。</p></blockquote><blockquote><p>下面的图片比较了 Docker 和传统虚拟化方式的不同之处。传统虚拟机技术是虚拟出一套硬件后，在其上运行一个完整操作系统，在该系统上再运行所需应用进程；而容器内的应用进程直接运行于宿主的内核，容器内没有自己的内核，而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。</p></blockquote><span id="more"></span><p><img src="https://yeasy.gitbooks.io/docker_practice/introduction/_images/virtualization.png" alt="ä¼ ç&quot;èæå"></p><p><img src="https://yeasy.gitbooks.io/docker_practice/introduction/_images/docker.png" alt="Docker"></p><h3 id="为什么要用docker"><a href="#为什么要用docker" class="headerlink" title="为什么要用docker"></a>为什么要用docker</h3><h4 id="企业应用"><a href="#企业应用" class="headerlink" title="企业应用"></a>企业应用</h4><ul><li>更高效的利用系统资源</li><li>更快速的启动时间</li><li>一致的运行环境</li><li>持续交付和部署</li><li>更轻松的迁移</li><li>更轻松的维护和拓展</li></ul><table><thead><tr><th>特性</th><th>容器</th><th>虚拟机</th></tr></thead><tbody><tr><td>启动</td><td>秒级</td><td>分钟级</td></tr><tr><td>硬盘使用</td><td>一般为 <code>MB</code></td><td>一般为 <code>GB</code></td></tr><tr><td>性能</td><td>接近原生</td><td>弱于</td></tr><tr><td>系统支持量</td><td>单机支持上千个容器</td><td>一般几十个</td></tr></tbody></table><h4 id="个人开发"><a href="#个人开发" class="headerlink" title="个人开发"></a>个人开发</h4><ul><li>便于搭建部署 （通过下载并修改其他优秀的docker项目）</li><li>便于备份迁移 （可将docker配置备份于repo，在各个云主机、vps及自己的笔记本上一致）</li><li>便于实验新特性 （建议在云主机上进行，镜像有一定体积，云主机网速较快）</li></ul><h4 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h4><blockquote><p>镜像：我们都知道，操作系统分为内核和用户空间。对于 Linux 而言，内核启动后，会挂载 <code>root</code> 文件系统为其提供用户空间支持。而 Docker 镜像（Image），就相当于是一个 <code>root</code> 文件系统。</p><p>Docker 镜像是一个特殊的文件系统，除了提供容器运行时所需的程序、库、资源、配置等文件外，还包含了一些为运行时准备的一些配置参数（如匿名卷、环境变量、用户等）。镜像不包含任何动态数据，其内容在构建之后也不会被改变。</p></blockquote><blockquote><p>容器：镜像（<code>Image</code>）和容器（<code>Container</code>）的关系，就像是面向对象程序设计中的 <code>类</code> 和 <code>实例</code> 一样，镜像是静态的定义，容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。</p><p>按照 Docker 最佳实践的要求，容器不应该向其存储层内写入任何数据，容器存储层要保持无状态化。所有的文件写入操作，都应该使用 数据卷（Volume）、或者绑定宿主目录，在这些位置的读写会跳过容器存储层，直接对宿主（或网络存储）发生读写，其性能和稳定性更高。<em><em>分离服务和数据</em></em></p><p>数据卷的生存周期独立于容器，容器消亡，数据卷不会消亡。因此，使用数据卷后，容器删除或者重新运行之后，数据却不会丢失。</p></blockquote><blockquote><p>仓库：镜像构建完成后，可以很容易的在当前宿主机上运行，但是，如果需要在其它服务器上使用这个镜像，我们就需要一个集中的存储、分发镜像的服务，<a href="https://yeasy.gitbooks.io/docker_practice/repository/registry.html">Docker Registry</a> 就是这样的服务。</p></blockquote><h2 id="docker-compose-简介"><a href="#docker-compose-简介" class="headerlink" title="docker-compose 简介"></a>docker-compose 简介</h2><h3 id="docker-三剑客"><a href="#docker-三剑客" class="headerlink" title="docker 三剑客"></a>docker 三剑客</h3><ul><li>compose</li><li>machine</li><li>swarm</li></ul><h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><blockquote><p><code>Compose</code> 项目是 Docker 官方的开源项目，负责实现对 Docker 容器集群的快速编排。</p><p><code>Compose</code> 定位是 「定义和运行多个 Docker 容器的应用（Defining and running multi-container Docker applications）。</p></blockquote><blockquote><p><code>Compose</code> 中有两个重要的概念：</p><ul><li>服务 (<code>service</code>)：一个应用的容器，实际上可以包括若干运行相同镜像的容器实例。</li><li>项目 (<code>project</code>)：由一组关联的应用容器组成的一个完整业务单元，在 <code>docker-compose.yml</code> 文件中定义。</li></ul></blockquote><blockquote><p><code>Compose</code> 项目由 Python 编写，实现上调用了 Docker 服务提供的 API 来对容器进行管理。（也就是说compose只是包装了docker的一些API，便于使用，并没有改变docker的性质）</p></blockquote><h2 id="使用他人的-dockerfile-docker-compose-yml"><a href="#使用他人的-dockerfile-docker-compose-yml" class="headerlink" title="使用他人的 dockerfile&#x2F;docker-compose.yml"></a>使用他人的 dockerfile&#x2F;docker-compose.yml</h2><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><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></pre></td><td class="code"><pre><span class="line">version: &quot;3&quot;</span><br><span class="line">services:</span><br><span class="line">  nginx:</span><br><span class="line">    image: nginx:alpine #指定镜像</span><br><span class="line">    ports: #端口映射</span><br><span class="line">      - &quot;80:80&quot;</span><br><span class="line">      - &quot;443:443&quot;</span><br><span class="line">    volumes: #数据卷挂载</span><br><span class="line">      - ./www/:/var/www/html/:rw</span><br><span class="line">      - ./conf/conf.d:/etc/nginx/conf.d/:ro</span><br><span class="line">      - ./conf/nginx.conf:/etc/nginx/nginx.conf:ro</span><br><span class="line">      - ./log/:/var/log/dnmp/:rw</span><br><span class="line">    networks: #网络</span><br><span class="line">      - net-php</span><br><span class="line"></span><br><span class="line">  php:</span><br><span class="line">    build: ./php/php72 #这里与指定镜像不同的是，指定了一个构建docker镜像的目录，根据目录下的dockerfile构建镜像</span><br><span class="line">    expose:</span><br><span class="line">      - &quot;9000&quot;</span><br><span class="line">    volumes:</span><br><span class="line">      - ./www/:/var/www/html/:rw</span><br><span class="line">      - ./conf/php.ini:/usr/local/etc/php/php.ini:ro</span><br><span class="line">      - ./conf/php-fpm.conf:/usr/local/etc/php-fpm.d/www.conf:rw</span><br><span class="line">      - ./log/:/var/log/dnmp/:rw</span><br><span class="line">    networks:</span><br><span class="line">      - net-php</span><br><span class="line">      - net-mysql</span><br><span class="line">      - net-redis</span><br><span class="line"></span><br><span class="line">  mysql:</span><br><span class="line">    image: mysql:8.0</span><br><span class="line">    ports:</span><br><span class="line">      - &quot;3306:3306&quot;</span><br><span class="line">    volumes:</span><br><span class="line">      - ./conf/mysql.cnf:/etc/mysql/conf.d/mysql.cnf:ro</span><br><span class="line">      - ./mysql/:/var/lib/mysql/:rw</span><br><span class="line">    networks:</span><br><span class="line">      - net-mysql</span><br><span class="line">    environment:</span><br><span class="line">      MYSQL_ROOT_PASSWORD: &quot;123456&quot;</span><br><span class="line"></span><br><span class="line">  redis:</span><br><span class="line">    image: redis:4.0</span><br><span class="line">    command: redis-server /usr/local/etc/redis/redis.conf --requirepass mypassword #指定镜像运行时使用的命令</span><br><span class="line">    networks:</span><br><span class="line">      - net-redis</span><br><span class="line">    ports:</span><br><span class="line">      - &quot;6379:6379&quot;</span><br><span class="line">    volumes:</span><br><span class="line">      - ./conf/redis.conf:/usr/local/etc/redis/redis.conf</span><br><span class="line"></span><br><span class="line">networks:</span><br><span class="line">  net-php:</span><br><span class="line">  net-mysql:</span><br><span class="line">  net-redis:</span><br></pre></td></tr></table></figure><h2 id="部署及使用"><a href="#部署及使用" class="headerlink" title="部署及使用"></a>部署及使用</h2><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">&gt; docker-compose up [-d]</span><br><span class="line">&gt; docker-compose logs</span><br><span class="line">&gt; docker-compose inspect</span><br><span class="line">&gt; docker-compose ps</span><br><span class="line">&gt; docker inspect [container]</span><br></pre></td></tr></table></figure><h3 id="参考示例"><a href="#参考示例" class="headerlink" title="参考示例"></a>参考示例</h3><p><a href="https://github.com/Qinzhehan52/dnmp">github dnmp</a></p><h2 id="docker实验新特性（基于资源分离的特性）"><a href="#docker实验新特性（基于资源分离的特性）" class="headerlink" title="docker实验新特性（基于资源分离的特性）"></a>docker实验新特性（基于资源分离的特性）</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">&gt; docker run --name test-mysql -e MYSQL_ROOT_PASSWORD=mypassword -d mysql:5.7</span><br><span class="line">&gt; docker <span class="built_in">exec</span> -it test-mysql bash</span><br></pre></td></tr></table></figure><p>参考链接：<a href="https://yeasy.gitbooks.io/docker_practice/content/">Docker—从入门到实践</a></p>]]></content>
    
    
    <summary type="html">&lt;h1 id=&quot;docker-部署-LNMP&quot;&gt;&lt;a href=&quot;#docker-部署-LNMP&quot; class=&quot;headerlink&quot; title=&quot;docker 部署 LNMP&quot;&gt;&lt;/a&gt;docker 部署 LNMP&lt;/h1&gt;&lt;h2 id=&quot;docker-简介&quot;&gt;&lt;a href=&quot;#docker-简介&quot; class=&quot;headerlink&quot; title=&quot;docker 简介&quot;&gt;&lt;/a&gt;docker 简介&lt;/h2&gt;&lt;h3 id=&quot;什么是docker&quot;&gt;&lt;a href=&quot;#什么是docker&quot; class=&quot;headerlink&quot; title=&quot;什么是docker&quot;&gt;&lt;/a&gt;什么是docker&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;Docker 使用 Google 公司推出的 &lt;a href=&quot;https://golang.org/&quot;&gt;Go 语言&lt;/a&gt; 进行开发实现，基于 Linux 内核的 &lt;a href=&quot;https://zh.wikipedia.org/wiki/Cgroups&quot;&gt;cgroup&lt;/a&gt;，&lt;a href=&quot;https://en.wikipedia.org/wiki/Linux_namespaces&quot;&gt;namespace&lt;/a&gt;，以及&lt;a href=&quot;https://en.wikipedia.org/wiki/Aufs&quot;&gt;AUFS&lt;/a&gt; 类的 &lt;a href=&quot;https://en.wikipedia.org/wiki/Union_mount&quot;&gt;Union FS&lt;/a&gt; 等技术，对进程进行封装隔离，属于 &lt;a href=&quot;https://en.wikipedia.org/wiki/Operating-system-level_virtualization&quot;&gt;操作系统层面的虚拟化技术&lt;/a&gt;。由于隔离的进程独立于宿主和其它的隔离的进程，因此也称其为容器。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Docker 在容器的基础上，进行了进一步的封装，从文件系统、网络互联到进程隔离等等，极大的简化了容器的创建和维护。使得 Docker 技术比虚拟机技术更为轻便、快捷。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;下面的图片比较了 Docker 和传统虚拟化方式的不同之处。传统虚拟机技术是虚拟出一套硬件后，在其上运行一个完整操作系统，在该系统上再运行所需应用进程；而容器内的应用进程直接运行于宿主的内核，容器内没有自己的内核，而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="docker" scheme="https://blog.nerdhan.top/categories/docker/"/>
    
    
    <category term="docker" scheme="https://blog.nerdhan.top/tags/docker/"/>
    
    <category term="php" scheme="https://blog.nerdhan.top/tags/php/"/>
    
  </entry>
  
</feed>
