vault backup: 2026-11-07 16:33:44
This commit is contained in:
@@ -0,0 +1 @@
|
||||
![[Pasted image 20260309094053.png]]
|
||||
@@ -0,0 +1,9 @@
|
||||
- 先写文档后开发代码
|
||||
- 限定 markdown 文件长度。按章节分模块编写。
|
||||
- 每个独立的模块,重开会话,重置上下文。
|
||||
|
||||
|
||||
|
||||
|
||||
https://github.com/CursorTouch/Windows-MCP
|
||||
https://github.com/ahujasid/blender-mcp
|
||||
@@ -0,0 +1,10 @@
|
||||
## 需求分析
|
||||
|
||||
|
||||
## 设计
|
||||
|
||||
|
||||
## 开发
|
||||
|
||||
### 代码评审
|
||||
你是一个严格的代码评审员,扫描项目下所有代码,编写风格、安全性、性能影响等方面给出建议,输出到项目根目录 ”docs/代码评审记录.md“ 文件中。
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
需求分析
|
||||
设计
|
||||
开发
|
||||
检查
|
||||
|
||||
|
||||
检查拼写错误、安全风险
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
|
||||
> 地址:`http://bh.vps.honor3.com:3000`
|
||||
> 一句话:**读用 MCP(免密钥),写用 REST(要 `X-Ingest-Key`)**。
|
||||
|
||||
---
|
||||
|
||||
## 1. 接入 MCP(只读,无需密钥)
|
||||
|
||||
MCP 端点是 `POST http://bh.vps.honor3.com:3000/mcp`,走标准 JSON-RPC 2.0,
|
||||
**不校验任何密钥**,只暴露只读工具。
|
||||
|
||||
客户端配置(Claude Desktop / Cursor / Cherry Studio 通用):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"awesome-index": {
|
||||
"url": "http://bh.vps.honor3.com:3000/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
手动测试(curl):
|
||||
|
||||
```bash
|
||||
curl -s -X POST "http://bh.vps.honor3.com:3000/mcp" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json, text/event-stream" \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
|
||||
```
|
||||
|
||||
⚠️ 必须带 `Accept: application/json, text/event-stream`,否则会被拒。
|
||||
|
||||
---
|
||||
|
||||
## 2. 四个只读工具
|
||||
|
||||
| 工具 | 参数 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| `search_entries` | `q`(必填,关键字)、`limit`(1–50,默认 20) | 按关键字/类型/标签搜索 |
|
||||
| `get_entry` | `slug`(必填,也可传标题关键字自动匹配) | 取单条详情 |
|
||||
| `random_entry` | 无 | 随机抽一条,用于发现 |
|
||||
| `list_tags` | 无 | 列全部标签组与数量 |
|
||||
|
||||
调用示例:
|
||||
|
||||
```bash
|
||||
curl -s -X POST "http://bh.vps.honor3.com:3000/mcp" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json, text/event-stream" \
|
||||
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
|
||||
"params":{"name":"search_entries","arguments":{"q":"自托管相册","limit":5}}}'
|
||||
```
|
||||
|
||||
结果在 `result.content[0].text` 里,是一段 JSON 字符串,需再解析一次。
|
||||
|
||||
---
|
||||
|
||||
## 3. 提交条目(写入,要密钥)
|
||||
|
||||
```bash
|
||||
curl -X POST "http://bh.vps.honor3.com:3000/api/ingest/entry" \
|
||||
-H "X-Ingest-Key: sk-test" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"title": "MinerU",
|
||||
"url": "https://github.com/opendatalab/MinerU",
|
||||
"type": "工具",
|
||||
"summary": "PDF/文档转 Markdown 的解析引擎",
|
||||
"tags": ["Python", "OCR", "文档解析"]
|
||||
}'
|
||||
```
|
||||
|
||||
成功返回:`{"ok":true,"data":{"created":true,"id":54,"status":"pending"}}`
|
||||
|
||||
中文内容建议存成 `.json` 文件再 `-d @file.json`,避免 shell 编码踩坑;
|
||||
Windows 下用 Python `json.dumps(..., ensure_ascii=False).encode('utf-8')` 最稳。
|
||||
|
||||
---
|
||||
|
||||
## 4. 字段规则
|
||||
|
||||
| 字段 | 必填 | 约束 |
|
||||
| --- | --- | --- |
|
||||
| `title` | ✅ | 标题 |
|
||||
| `url` | ✅ | 合法 URL,**用于去重** |
|
||||
| `type` | ✅ | 只能是:`微服务` `SaaS` `网站` `工具` `应用` `脚本` `插件` |
|
||||
| `summary` | ❌ | ≤ 200 字符 |
|
||||
| `description_md` | ❌ | ≤ 5000 字符,支持 Markdown |
|
||||
| `license` | ❌ | 如 `MIT`、`GPL-3.0` |
|
||||
| `stars` | ❌ | 整数 |
|
||||
| `tags` | ❌ | 字符串数组 |
|
||||
| `status` | ❌ | `pending` / `active` |
|
||||
|
||||
**`slug` 不是合法字段**,传了会被静默丢弃。
|
||||
|
||||
---
|
||||
|
||||
## 5. 五个坑(记牢就够)
|
||||
|
||||
1. **认证头是 `X-Ingest-Key`**。`Authorization: Bearer`、`X-API-Key`、`?key=` 全部 403。
|
||||
2. **MCP 免密钥 ≠ 密钥有效**。`/mcp` 根本不查 key,别拿它验证密钥。
|
||||
3. **提交后默认 `pending`**,前台搜不到是正常的,要等管理员审核变 `active`。
|
||||
4. **同 URL 重复提交返回 200 `created:false`**,不是报错,说明已存在。
|
||||
5. **`/api/entries` 也能写**(未登录时回退到 AI key 校验),但推荐用 `/api/ingest/entry`。
|
||||
|
||||
---
|
||||
|
||||
## 6. 典型工作流
|
||||
|
||||
```
|
||||
1. search_entries 查重 → 已有就 get_entry 看详情,别重复提交
|
||||
2. WebSearch / WebFetch 收集项目信息
|
||||
3. 组装 JSON(title + url + type 三个必填)
|
||||
4. POST /api/ingest/entry(X-Ingest-Key)
|
||||
5. 记下返回的 id,状态 pending
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*整理于 2026-10-27 · 已在 MinerU / Mio CUA / Windows-Use / Transub / TinyJS / Quickemu / nanoMuse 七个条目上验证*
|
||||
@@ -0,0 +1,126 @@
|
||||
---
|
||||
page-title: "(68 封私信 / 21 条消息) 首页 - 知乎"
|
||||
url: https://www.zhihu.com/follow
|
||||
web-message:
|
||||
web-badge-color: ""
|
||||
web-badge-message:
|
||||
date: "2024-04-08 16:28:24"
|
||||
---
|
||||
|
||||
> 使用AI来生成一部分代码比较常见,那你考虑过让AI完成整个项目吗?
|
||||
>
|
||||
> 今天我们介绍的开源项目,它希望为世界上第一位人工智能开发伙伴,可独立完成95%的开发任务,它就是:**GPT Pilot**
|
||||
>
|
||||
> 
|
||||
>
|
||||
> ## GPT Pilot 是什么
|
||||
>
|
||||
> GPT Pilot 旨在提供第一个真正的 AI 开发伙伴。不仅仅是一个自动完成功能或 PR 消息的助手,而是一个真正的 AI 开发人员,可以编写完整的功能、调试它们、与您讨论问题、请求审核等。
|
||||
>
|
||||
> 本项目的核心目的是,在开发人员的监督下,探究有多少 LLM 可以用于生成完全可工作的、生产就绪的应用程序。项目作者的观点是,在人工智能取得全面性的进展之前,人工智能可以为应用程序编写绝大部分代码(可能达到95%),但对于剩余的5%,开发人员仍然是不可或缺的。
|
||||
>
|
||||
> 
|
||||
>
|
||||
> GPT Pilot 是 Pythagora 的核心技术,而 Pythagora 是一款VS Code插件,接下来我们就来看下如何安装和使用GPT Pilot。
|
||||
>
|
||||
> ## 安装GPT Pilot
|
||||
>
|
||||
> 如果您使用 VS Code 作为 IDE,最简单使用GPT Pilot 的方法是下载它的VSCode 插件。在vscode中它的名字叫Pythagora。
|
||||
>
|
||||
> 
|
||||
>
|
||||
> 安装完成插件以后,首先需要注册一个账号。
|
||||
>
|
||||
> 
|
||||
>
|
||||
> 注册完成后会让我们回答一些问题,然后会自动安装gpt-pilot,对它不仅仅只有插件,是要装很多东西的。
|
||||
>
|
||||
> 
|
||||
>
|
||||
> 除了使用VScode外,你也可以自己安装,然后通过CLI来试用。配置方法如下:
|
||||
>
|
||||
> ```
|
||||
> 首先需要安装Python 3.9+ 和 PostgreSQL。
|
||||
> 安装 Python 和(可选)PostgreSQL 后,请按照以下步骤操作:
|
||||
>
|
||||
> git clone <https://github.com/Pythagora-io/gpt-pilot.git(克隆存储库)>
|
||||
> cd gpt-pilot
|
||||
> python -m venv pilot-env(创建虚拟环境)
|
||||
> source pilot-env/bin/activate(或在 Windows 上pilot-env\\Scripts\\activate)(激活虚拟环境)
|
||||
> pip install -r requirements.txt(安装依赖项)
|
||||
> cd pilot
|
||||
> mv .env.example .env(或在 Windows 上copy .env.example .env)(创建 .env 文件)
|
||||
> 将您的环境添加到.env文件中:
|
||||
> ·LLM 提供商(OpenAI/Azure/Openrouter)
|
||||
> ·您的 API 密钥
|
||||
> ·数据库设置:SQLite/PostgreSQL(从SQLite更改为PostgreSQL,只需设置DATABASE_TYPE=postgres)
|
||||
> ·可以选择为工作区中 GPT Pilot 不应跟踪的文件夹设置 IGNORE_PATHS,这对于忽略编译器创建的文件夹很有用(即IGNORE_PATHS=folder1,folder2,folder3)
|
||||
> python main.py(启动 GPT Pilot)
|
||||
> ```
|
||||
>
|
||||
> 还有一种方式是通过docker来试用gpt-pilot,这里就不介绍了,请参考文档配置。
|
||||
>
|
||||
> ## 使用 GPT Pilot
|
||||
>
|
||||
> 安装完成后,我们就可以开始使用了。首先我们来看一下gpt-pilot的整体执行流程。在项目执行过程中,它能够自动提出问题,并根据我们的答案,模拟与复现开发一个项目时的完整流程。整个执行的流程是如下这样的。
|
||||
>
|
||||
> 
|
||||
>
|
||||
> GPT Pilot 会先询问我们一系列问题,来确定应用类型,产品需求和技术要求,然后基于这些问答内容,自动完成环境配置,编写代码。
|
||||
>
|
||||
> 以下是 GPT Pilot 创建应用程序所需的步骤:
|
||||
>
|
||||
> 1. 输入应用程序名称和说明。
|
||||
> 2. 产品负责人代理:就像现实生活中一样,什么都不做。 :)
|
||||
> 3. 如果项目描述不够好,规范编写代理:会询问几个问题,以便更好地理解需求。
|
||||
> 4. 架构师代理:编写将用于应用程序的技术,并检查计算机上是否安装了所有技术,如果没有,则安装它们。
|
||||
> 5. 技术主管代理:编写开发人员必须实施的开发任务。
|
||||
> 6. 开发人员代理:接受每项任务并写下实施该任务所需执行的操作。该描述采用人类可读的形式。
|
||||
> 7. Code Monkey 代理:获取开发人员的描述和现有文件并实施更改。
|
||||
> 8. 审核者代理:会审核任务的每一步,如果出现问题,审核者会将其发送回 Code Monkey。
|
||||
> 9. 故障排除代理:可帮助您在出现问题时向 GPT Pilot 提供良好的反馈。
|
||||
> 10. 调试器代理:讨厌见到他,但当事情不顺利时,他是你最好的朋友。
|
||||
> 11. 技术作家代理:为项目编写文档。
|
||||
>
|
||||
> 
|
||||
>
|
||||
> 使用的时候需要给项目写非常详细的描述,其实可以理解为这就是在对大模型写prompt,所以写的越好那开发的效果越好。项目提供了一个专门介绍写描述的wiki,这里我就抄一个提供的示例来看看效果。
|
||||
>
|
||||
> 
|
||||
>
|
||||
> 接下来就是回答一些问题,我们在这整个过程中,只需扮演好项目管理者的角色,检查 AI 完成的代码是否符合标准,并在适当的时候给予其指引,让 AI 能在正确的方向上持续编写与迭代项目。
|
||||
>
|
||||
> 
|
||||
>
|
||||
> 相对来说,GPT Pilot 是一种监督性的AI编程,并没有完全把人排除在外,这样实际上也是目前更好的模式。
|
||||
>
|
||||
> 
|
||||
>
|
||||
> 这里也有比较坑的地方,执行了一半免费体验用光了,所以建议从一开始就使用自己的 OpenAI key。
|
||||
>
|
||||
> 
|
||||
>
|
||||
> 项目里还提供了多个使用GPT Pilot开发的程序实例,可以直接查看。
|
||||
>
|
||||
> 
|
||||
>
|
||||
> ## 总结
|
||||
>
|
||||
> GPT Pilot的主要用途是帮助开发人员更快速地构建应用程序。它的设计上实现了现在非常前言的多智能体设计,通过多个不同角色的ai agent协同工作来完成一个复杂的开发任务。关于多智能体,最新的吴恩达演讲中也提出了很多内容,非常值得研究一下。
|
||||
>
|
||||
> 它的过程更加贴合目前可接受的方式,您则负责监督开发过程,AI来完成任务执行。当GPT Pilot完成每个任务或遇到困难时,它会要求您审查任务或在需要时提供帮助。
|
||||
>
|
||||
> 从目前来说可以将gpt-pilot使用在以下方面:
|
||||
>
|
||||
> 1. **快速原型开发**:您可以使用GPT Pilot快速创建应用程序的原型,以便在不浪费太多时间的情况下验证概念。
|
||||
> 2. **自动代码生成**:GPT Pilot可以生成应用程序的大部分代码,从设置路由和数据库连接到编写用户界面。
|
||||
> 3. **技术建议**:它还可以为应用程序选择合适的技术栈和工具,以满足特定需求。
|
||||
> 4. **教育和学习**:开发人员可以使用GPT Pilot来了解如何构建不同类型的应用程序,学习最佳实践,并查看实际代码示例。
|
||||
>
|
||||
> 
|
||||
>
|
||||
> ## 项目信息
|
||||
>
|
||||
> - 项目名称: gpt-pilot
|
||||
> - GitHub 链接:[https://github.com/Pythagora-io/gpt-pilot](https://link.zhihu.com/?target=https%3A//github.com/Pythagora-io/gpt-pilot)
|
||||
> - Star 数:27K+
|
||||
@@ -0,0 +1,313 @@
|
||||
---
|
||||
page-title: "GPT 应用开发和思考"
|
||||
url: https://guangzhengli.com/blog/zh/gpt-embeddings/
|
||||
web-message:
|
||||
web-badge-color: ""
|
||||
web-badge-message:
|
||||
date: "2024-04-08 16:55:06"
|
||||
---
|
||||
|
||||
> 在过去几个月的时间中,我们似乎正处于人工智能的革命中。除了大多数人了解的 OpenAI ChatGPT 之外,许多非常新颖、有趣、实用的 AI 应用也是层出不穷,并且在使用这些应用时时,笔者也确确实实的感受到了生产力的提高。
|
||||
>
|
||||
> 但是关于 GPT 应用的开发知识和路线,目前似乎还没有太多的资料,所以笔者决定将自己的一些经验和思考整理成一个系列,希望能够帮助到大家。
|
||||
>
|
||||
> 本篇文章主要介绍的是 GPT 相关应用的开发思考,在今年 4 月份的时候,笔者因为开发 [ChatFiles](https://github.com/guangzhengli/ChatFiles) 这个开源项目,从而学习了 GPT 相关的技术知识,但是由于笔者的时间精力有限,所以一直没有机会将这些知识整理成一篇文章,直到最近笔者又因为有了新的想法,开源了 [VectorHub](https://github.com/guangzhengli/vectorhub) 这个同样基于 GPT Prompt 和 Embeddings 技术的项目,进而对 GPT 和 Embeddings 等技术知识有了更深入的了解,所以就有了这一篇分享。
|
||||
>
|
||||
> ## 从 Prompt 开始
|
||||
>
|
||||
> AI 应用开发在过去一段时间内吸引了众多开发者入场,除了大家所熟知的 ChatGPT 之外,还涌现了大量有实际价值的 AI 应用,例如基于 AI 的翻译类的应用如 [openai-translator](https://github.com/openai-translator/openai-translator)、[immersivetranslate](https://immersivetranslate.com/),写作类的应用如 [Notion AI](https://www.notion.so/product/ai),编程辅助类的应用如 [GitHub Copilot](https://github.com/features/copilot) 和 [GitHub Copilot Chat](https://docs.github.com/en/copilot/github-copilot-chat/using-github-copilot-chat?tool=vscode)。
|
||||
>
|
||||
> 这些应用有些是优化了原有的体验,如基于 GPT 的翻译的 [openai-translator](https://github.com/openai-translator/openai-translator),翻译质量和阅读体验远胜于之前的机器翻译,还有些则是提供了之前无法实现的功能,如 [GitHub Copilot](https://github.com/features/copilot) 的代码补全和生成,还有像 [GitHub Copilot Chat](https://docs.github.com/en/copilot/github-copilot-chat/using-github-copilot-chat?tool=vscode) 提供回答编码相关问题、解释代码、生成单元测试、给错误代码提出修复意见等等功能,这些功能的实现难度在以前是完全无法想象的。
|
||||
>
|
||||
> 这些应用在功能上虽然没有相似之处,但是在实现原理中,它们都是主要基于 GPT 的 Prompt(提示)实现。Prompt 指的是提供给模型的文本或指令,可以用来引导模型生成自然语言输出(Completion)。它可以给模型提供上下文信息,对模型的输出结果至关重要。
|
||||
>
|
||||
> 我们知道 GPT(Generative Pre-trained Transformer)是一个推理模型,它主要基于预训练和微调两个阶段。
|
||||
>
|
||||
> 在预训练阶段会使用一个大规模的语料库进行基础训练,例如使用维基百科、新闻文章、小说等来进行训练。当训练完成后,输入一句话给它,它会基于这句话给出一个概率上的预测,预测后续应该拼接上什么单词,这个拼接的单词是基于它在预训练阶段学习到的知识来进行概率选定的,通过一次次循环的单词预测,最终可以拼接出一段话来。这也是它被称为生成式 AI 的原因。
|
||||
>
|
||||
> 这一句话就是我们所说的 Prompt,也是生成式 AI 概率生成的基础。这能解释为什么我们每次输入相同的 Prompt,但是每次得到的结果都会有所不同,因为每次的结果都是基于概率生成的。
|
||||
>
|
||||
> 所以我们也就能理解为什么 Prompt 对于 GPT 应用开发的重要性,因为它是除了微调以外,我们能与 GPT 模型唯一的交互方式(当然除此之外还可以通过调整模型的 temperature 和 top\_p 两个配置来控制 GPT 更多样化或更具创造性的输出,不过对于输出质量和对下游任务的处理能力并无明显影响)。所以 Prompt 是 GPT 应用开发最核心的部分,也是最需要开发者去思考和优化的部分。
|
||||
>
|
||||
> 在预训练完成后,在微调阶段会将 GPT 模型加载到特定的任务上,并使用该任务的数据集对模型进行训练。这样,模型就可以根据任务的要求进行微调,以便更好地理解 Prompt 并生成与任务相关的文本。通过微调,GPT 可以适应不同的任务,如文本分类、情感分析、问答系统等。不过目前微调因为成本昂贵和最终效果并不稳定,对于广大 GPT 开发者来讲,并不是非常好的选择,所以目前大部分的 GPT 应用开发者都是基于 Prompt 来开发应用。
|
||||
>
|
||||
> ## Prompt 学习路线
|
||||
>
|
||||
> 关于 Prompt 的基础知识,可以先去看看吴恩达老师的 [ChatGPT Prompt 工程](https://www.deeplearning.ai/short-courses/chatgpt-prompt-engineering-for-developers/)。可以通过两个小时不到的视频可以快速的了解 Prompt 的使用方式和它的魅力所在。
|
||||
>
|
||||
> 在有了一个初步的了解之后,笔者推荐 [Prompt Engineering Guide](https://www.promptingguide.ai/) 这份文档,该文档包含了大量的 Prompt 基础知识和未来的发展方向,对于 GPT 应用开发者来说,除了学习 Prompt 的基础知识之外,还可以从中获取到一些工程界和学术界对于 Prompt 的发展方向的思考,对于开发 AI 应用来说,这些思考弥足珍贵。
|
||||
>
|
||||
> 最后,非常推荐大家去看看 OpenAI 官方的 [GPT 最佳实践](https://platform.openai.com/docs/guides/gpt-best-practices) 这份文档,它是由 OpenAI 官方提供的 GPT 最佳实践指南,里面包含了大量的 Prompt 示例和使用技巧,对于 GPT 应用开发者来说,是一份非常有价值的文档。因为它是 OpenAI 官方通过合作伙伴或者 Hackathon 等不同的方式,在不同的业务领域 GPT 应用开发中总结出来的最佳实践,对于开发者来讲非常有启发价值!下面是对这一份文档的摘录:
|
||||
>
|
||||
> ## Prompt 最佳实践
|
||||
>
|
||||
> 关于 Prompt 编写的最佳实践,最为推荐的当然是 OpenAI 官方出品的 [GPT 最佳实践](https://platform.openai.com/docs/guides/gpt-best-practices) 这份文档,但对于开发 GPT 应用来讲,笔者还是想结合这份最佳实践和一些自己的经验,给大家分享一些 GPT 开发的实践。
|
||||
>
|
||||
> ### 清晰和详细
|
||||
>
|
||||
> 现实中大部分开发者在平常使用 GPT 的时候,都是以解决编程问题或者询问问题为主,所以容易带入以往使用 Google 等搜索引擎的经验来使用和开发 GPT。
|
||||
>
|
||||
> 例如当你想知道如何用 Python 编写斐波那契数列的时候,如果在以前使用 Google 搜索引擎,你可能会输入 `python fibonacci`。因为这样就足够了,Google 是基于倒排索引和 PageRank 算法的,只需要输入关键字,就能得到高质量的网页答案。
|
||||
>
|
||||
> 所以只需要这种输入两个字的输入方式是最简单和最高效的,毕竟就算多输入几个字 `how to write python fibonacci` ,对于 Google 搜索引擎来讲,输出质量是相差不大的。
|
||||
>
|
||||
> 而如果你使用的是 GPT,像 `python fibonacci` 这样的输入对于 GPT 来说是非常不友好的,因为它无法清晰的理解你的意图,所以它可能会给出一些不相关的结果(根据不同模型的质量会略有差异)。
|
||||
>
|
||||
> 但如果你输入的是 `编写一个用 python 函数来高效地计算 fibonacci 数列。评论每一行代码以解释每一部分的作用以及为什么这样写`。这样的输入对于 GPT 来说就非常清晰和详细,它能够清晰的理解你的意图,所以它给出的结果也会更加准确,**在保证输出质量下限的同时,还可以提高输出质量的上限!**
|
||||
>
|
||||
> 这和开发者以往使用 Google 等搜索引擎的经验是完全不同的,也是 GPT 开发者和使用者最容易忽视的地方。笔者在今年很早的时候开发 [ChatFiles](https://github.com/guangzhengli/ChatFiles) 项目时,曾匿名收集过使用者的 Prompt,发现 95% 以上的使用者使用的都是非常简单的 Prompt,简单的甚至看起来有一种一字千金的感觉。
|
||||
>
|
||||
> 所以当开发者开发 GPT 应用时,一定要注意 Prompt 的清晰和详细,多试几次,选择一个输出质量稳定和格式相同的 Prompt,这是保证 GPT 应用质量的关键。
|
||||
>
|
||||
> ### 如何处理更加复杂的任务
|
||||
>
|
||||
> 相信所有开发者面对简单的场景任务,多花费一些时间来调整 Prompt,都能设计好程序的 Prompt 并得到不错的输出质量。但是面对复杂任务时,想要提高 GPT 的输出质量还需要两个非常重要的技巧:**让 GPT 推理而不是回答,和拆分任务进行引导**。
|
||||
>
|
||||
> #### 推理而不是回答
|
||||
>
|
||||
> 推理而不是回答是指在 Prompt 中,要求 GPT 模型不要立即判断正确与否或者立刻给出答案,而是引导模型进行深入思考。可以要求其先列出对问题的各种看法,对任务进行拆分,说明每一步的推理依据,然后再得出最终结论。在 Prompt 中添加逐步推理的要求,能让语言模型投入更多时间逻辑思维,输出结果也将更可靠准确。
|
||||
>
|
||||
> 举一个[OpenAI 官方的例子](https://platform.openai.com/docs/guides/gpt-best-practices/tactic-instruct-the-model-to-work-out-its-own-solution-before-rushing-to-a-conclusion),如果你需要 GPT 回答某一个学生的答案是否正确,Prompt 是 `判断学生的解决方案是否正确`的话,面对复杂的计算问题和答案,GPT 有很大的概率会给出错误的答案,因为 GPT 并不会像人一样先进行推理答案再进行回答,而是会立即给出判断。在短暂的判断中,就无法给出正确的答案(就好比人类无法在短时间计算复杂数学一样)。
|
||||
>
|
||||
> 所以如果我们的 Prompt 是 `首先自己解决问题,然后再将自己的解决方案与学生的解决方案进行比较,并评估学生的解决方案是否正确。在自己完成问题之前,不要确定学生的解决方案是否正确`。在 Prompt 中给出明确的引导和条件,就能够让 GPT 模型花费更多的时间推导答案,从而得到更加准确的结果。
|
||||
>
|
||||
> #### 拆分任务
|
||||
>
|
||||
> 拆分任务进行引导是指将一个复杂的任务拆分成多个子任务,然后分别引导 GPT 模型进行推理,最后将多个子任务的结果进行整合,得到最终的结果。这样做的好处是可以让 GPT 模型更加专注于一个子任务,从而提高输出质量。
|
||||
>
|
||||
> 举一个不太恰当的例子,当你需要对一本书籍进行摘要的时候,GPT 直接进行总体的摘要的效果并不好,我们可以使用一系列子任务来摘要每个部分。最后再汇总产生的摘要。
|
||||
>
|
||||
> 当然拆分任务也会带来一些新的问题,即当单个任务输出质量有问题时,整体的输出质量也会受其影响。加上目前的 token 费用不菲,拆分任务进行引导也会带来额外的成本。但是无论如何,目前关于如何设计和拆分复杂任务是所有 GPT 应用最需要思考,和维护自身 AI 应用护城河的核心问题,也是目前大模型 AI 框架像 LangChain 等项目的核心设计点,有空可以单独写一篇文章来讨论。
|
||||
>
|
||||
> ### 使用技巧
|
||||
>
|
||||
> 除了上述的一些比较重要的实践之外,还有一些小技巧对于开发应用也是非常有帮助,下面是笔者总结的一些使用技巧:
|
||||
>
|
||||
> - 提供少量示例(Few-Shot Prompting): 给模型一两个期望的输入输出样例,让模型了解我们的要求和期望的输出样式。
|
||||
> - Prompt 中要求结构化输出:以 Json 的方式输出,这样可以方便后续代码程序处理。
|
||||
> - 分隔符:使用分隔符像 `"""` 将不同的指令、上下文之间进行隔离,防止系统的 Prompt 和用户输入的 Prompt 混淆冲突。
|
||||
>
|
||||
> ## GPT Embedding 应用开发
|
||||
>
|
||||
> 在上面我们主要介绍了基于 Prompt 如何开发 AI 应用,但是有时候我们还会遇到新的问题。例如大模型的训练数据往往是基于几个月前、甚至是几年前的,当我们有一些需求场景是需要 GPT 应用提供最新的数据,例如基于最近的新闻来回答问题,或者根据私有文档来回答问题,这时候大模型本身就没有基于这些材料进行过训练,也就无法解决该类问题。
|
||||
>
|
||||
> 这个时候我们可以通过让模型使用参考文本来回答问题,例如我们的 prompt 可以写成:
|
||||
>
|
||||
> ```
|
||||
> 你将会得到一个由三个引号分隔的文档和一个问题。
|
||||
>
|
||||
> 你的任务是使用提供的文档回答问题,并引用用于回答问题的文本段落。
|
||||
>
|
||||
> 如果文档中没有包含回答该问题所需的信息,则简单地写下:“信息不足”。
|
||||
>
|
||||
> 如果提供问题的答案,必须附带引用注释。
|
||||
> 使用以下格式引用相关段落({"citation": …})。
|
||||
> ```
|
||||
>
|
||||
> 这种使用方式可以让 GPT 针对于我们给予它的参考文本进行回答,例如我们想问最新的世界杯冠军是谁,就可以附上最新的世界杯新闻的参考文本,这样 GPT 会先理解整篇新闻,再来问答问题。通过这种方式,就可以解决大模型针对于时效性和特定的下游任务的问题。
|
||||
>
|
||||
> 但是这种解决方案会带来另外一个问题,即参考文本长度限制的问题。GPT Prompt 是有大小限制的,像 gpt-3.5-turbo 模型它的限制是 4K tokens(~3000字),这意味着使用者最多只能输入 3000 字给 GPT 来理解和推理答案。
|
||||
>
|
||||
> 那么一旦需要的参考文本大于 3000 字,就无法一次性通过 GPT 得到答案,需要开发者想办法将参考文本拆分成多个部分,然后分别进行 GPT Embeddings 转化为向量存储到向量数据库中。关于向量数据库的更多信息,可以参考我另外一篇博客 [向量数据库](https://guangzhengli.com/blog/zh/vector-database/),当需要针对于参考文本进行提问时,需要先将问题转化为向量,然后通过向量数据库进行检索,最后再将检索到的向量转化为文本输出。这样就能得到符合 GPT tokens 限制的参考文本,并且这段参考文本是和问题具有关联的。
|
||||
>
|
||||
> 我们拿到问题本身和这段具有关联的参考文本,同时提交给 GPT,就能得到我们想要的答案。这个过程就是 GPT Embeddings 应用开发核心,它的核心思想是通过向量检索的方式检索与问题最相关的文本段,从而绕过 GPT tokens 的限制。
|
||||
>
|
||||
> 
|
||||
>
|
||||
> 整体的开发流程如上图所示:
|
||||
>
|
||||
> 1. 加载文档,获取目标文本信息。例如 LLM 主流框架 LangChain 中 File Loader 和 Web Loader 的两种文本获取方式。
|
||||
> 1. 基于文件系统的 File Loader,如加载 PDF 文件、Word 文件等。
|
||||
> 2. 基于网络的 Web Loader,如某个网页,AWS S3 等。
|
||||
> 2. 将目标文本拆分为多个段落,拆分方式主要基于两种。
|
||||
> 1. 一种是基于文字数量的拆分法,例如 1000 字为一段,这种方式的优点是简单,缺点是可能会将一个段落拆分成多个段落,导致段落的连贯性变差,从而导致最终回答结果可能缺少上下文而降低回答质量。
|
||||
> 2. 另一种是基于标点符号的拆分法,例如以换行为分隔符,这种方式的优点是段落的连贯性好,缺点是每个段落大小不一,可能会导致触发 GPT tokens 的限制。
|
||||
> 3. 最后一种是基于 GPT tokens 限制的拆分法,例如以 2000 tokens 为一组,最终查询时搜索出最相关的两个段落,这样加到一起也才 4000 tokens,就不会触发到 4096 tokens 的限制。
|
||||
> 3. 将拆分的文本块统一存入 [向量数据库](https://guangzhengli.com/blog/zh/vector-database/) 中。
|
||||
> 4. 将用户问题转化为向量,然后通过向量数据库进行检索,得到最相关的文本段落。(需要注意的是,这里的检索并不是传统数据库的模糊匹配或者倒排索引,而是基于语义化的搜索能力,所以检索出的文本才能回答用户问题,详细请看另外一篇博客 \[向量数据库\](https://guangzhengli.com/blog/zh/vector-database/)
|
||||
> 5. 将检索出来的相关文本信息,用户问题和系统的 prompt 三个组合成一个针对于 Embeddings 场景的 Prompt,例如 Prompt 中明确写到使用参考文本回答问题,而不是 GPT 自己回答。
|
||||
> 6. GPT 回答最终得到的 Prompt,得到最终的答案。
|
||||
>
|
||||
> 如果你对具体的实现代码感兴趣,可以去看看 LangChain 中关于 [Retrieval 的章节](https://js.langchain.com/docs/modules/data_connection/)。
|
||||
>
|
||||
> GPT embeddings 应用能解决某些场景下 GPT 无法回答的问题,这是他最大的优点。无需训练,无需微调,只需要将文本转化为向量再通过检索就能实现,以低成本的方式就能让某些业务场景变成可能。
|
||||
>
|
||||
> 但这个方案同样会带来一些新的问题,例如 embeddings 文本拆分和检索的质量将在很大的程度上影响最终的结果,还有查询范围、质量和查询时间该如何均衡,检索出的参考文本无法回答用户问题时该如何处理?这都是开发者在面对业务需求和场景时需要仔细思考的问题。
|
||||
>
|
||||
> 如果思考更远一点,在人类过去的时间中,所有的文档都是面向人来编写,对于向量检索来说并不是最佳组织模式,以后是否会存在专门面向 AI 和检索编写的文本?让 AI 更好理解和更好符合数据库检索?不过这些问题需要长期的思考和论证,这里不再扩展。
|
||||
>
|
||||
> ## GPT Agents 应用开发
|
||||
>
|
||||
> 除了上述的 prompt 和 embeddings 两种方案能解决的业务需求外,GPT 应用开发还有一个非常常见的需求,即如何集成现有系统,或者说是如何集成现有 API。
|
||||
>
|
||||
> 因为软件行业已经发展了很多年,很多公司都有自己的系统和 API,这些 API 能够极大的提高 GPT 应用的能力边界。如果 GPT 应用想要在实际生活场景中落地,是不可避免的需要和现有系统进行集成。
|
||||
>
|
||||
> 就像如果你想要询问 GPT 一个非常简单的问题,今天北京的天气是什么?看完上面的章节,我们知道 GPT 自己是无法回答这类问题的,如果 GPT 能够自行调用某个天气 API,那么开发起来将非常的方便。
|
||||
>
|
||||
> 想要实现 GPT 调用查询天气这个 API 的需求,我们会面临两个问题,一个是让 GPT 应用理解某个 API 的功能,并在它觉得合适的时候调用;另一个是输入输出必须结构化,从而保证系统的稳定性。
|
||||
>
|
||||
> ### 理解和调用现有的 API
|
||||
>
|
||||
> 想让 GPT 理解某个 API 的功能,最佳的方式当然是开发者手动给 API 添加名字和具体的描述信息,包括输入和输出值的结构,和每个字段代表着什么意思,这能很大程度上影响 GPT 的判断,最后决定是否调用该 API。
|
||||
>
|
||||
> 如 OpenAI 官网中 [function calling 例子](https://openai.com/blog/function-calling-and-other-api-updates)中,关于调用天气 API 的描述如下所示。
|
||||
>
|
||||
> ```
|
||||
> function_descriptions = [
|
||||
> {
|
||||
> "name": "get_current_weather",
|
||||
> "description": "Get the current weather in a given location",
|
||||
> "parameters": {
|
||||
> "type": "object",
|
||||
> "properties": {
|
||||
> "location": {
|
||||
> "type": "string",
|
||||
> "description": "The city and state, e.g. San Francisco, CA",
|
||||
> },
|
||||
> "unit": {
|
||||
> "type": "string",
|
||||
> "description": "The temperature unit to use. Infer this from the users location.",
|
||||
> "enum": ["celsius", "fahrenheit"]
|
||||
> },
|
||||
> },
|
||||
> "required": ["location"],
|
||||
> },
|
||||
> }
|
||||
> ]
|
||||
> ```
|
||||
>
|
||||
> 上面的方法详细描述了如果需要调用该函数,至少需要提供当前所在的位置信息,并且可以选择性的提供温度单位。GPT 会根据问题是否和该函数的描述相关联,来决定是否调用该函数。具体的代码细节以后有几乎再写一篇,暂时不展开。
|
||||
>
|
||||
> 我们可以通过提供大量的 APIs 的说明文档,让 LLMs 理解并学会这些 APIs 的功能、使用方式、组合方式。最终达到根据提出的总体要求,将总体要求拆分为多个子任务,具体子任务再与某个 API 进行交互,最终达到自动化以及 AI 的目标。
|
||||
>
|
||||
> 我们这里描述的现有 APIs 并不仅仅是基于 HTTP 的请求,可以包括转化为 SQL 查询数据库,调用 SDK 实现复杂功能,甚至在未来,可以扩展为调用物理世界里面的开关、机械臂等。个人认为,基于目前已有的 GPT 能力和发展趋势,人机交互的方式将产生巨大的变化。
|
||||
>
|
||||
> ### 结构化输出
|
||||
>
|
||||
> 除了让 GPT 理解和调用现有的 API 之外,还需要让 GPT 输出的结果能够被现有系统所理解,这就需要 GPT 输出的结果是结构化的,例如 JSON 格式的数据。
|
||||
>
|
||||
> 那么你可能马上想到,我们通过 prompt 不就可以实现这种功能吗?在大部分情况下确实可以,但是有些少量的情况,prompt 并不如 function calling 的方式稳定,而我们传统的系统,对于稳定性的要求是非常高的,举个例子。
|
||||
>
|
||||
> ```
|
||||
> student_description=小王是北京大学计算机科学专业的二年级学生,绩点为 3.8 GPA。他的编程很厉害,是该大学机器人俱乐部的活跃成员。他希望在毕业后追求人工智能方面的工作。
|
||||
> ```
|
||||
>
|
||||
> 这段话中,我们可以通过 prompt 来要求输出的结构为 json 格式,例如 prompt 为
|
||||
>
|
||||
> ```
|
||||
> Please extract the following information from the given text and return it as a JSON object:
|
||||
>
|
||||
> name
|
||||
> major
|
||||
> school
|
||||
> grades
|
||||
> club
|
||||
>
|
||||
> This is the body of text to extract the information from:
|
||||
> {student_1_description}
|
||||
> ```
|
||||
>
|
||||
> 但是比较难处理的是,我们没有办法能够确定 GPT 最终会输出的 grades 是 `3.8` 还是 `3.8 GPA`,这两种输出结果对于人类来讲没有如何区别,但是对于计算机来讲,这两种输出结果是完全不同的,前者是一个浮点数,后者是一个字符串。对于某些语言来讲转换就会直接出错。
|
||||
>
|
||||
> 我们当然可以通过增加 prompt 的描述来减少这类问题,但是现实情况是比较复杂的,我们很难通过自然语言完全描述清楚需求,很难保证 GPT 的回答会每次都保持相同的输出。所以针对于这类问题,通过 OpenAI 提供的 function calling 的方式,能够在一定程度上解决自然语言与机器语言交互的问题。
|
||||
>
|
||||
> 就像上述的问题可以描述为一个函数,我们将 grades 这个字段描述为 integer 来避免这类问题。这种结构化的能力对于开发一个稳定的系统是至关重要的。
|
||||
>
|
||||
> ```
|
||||
> student_custom_functions = [
|
||||
> {
|
||||
> 'name': 'extract_student_info',
|
||||
> 'description': 'Get the student information from the body of the input text',
|
||||
> 'parameters': {
|
||||
> 'type': 'object',
|
||||
> 'properties': {
|
||||
> 'name': {
|
||||
> 'type': 'string',
|
||||
> 'description': 'Name of the person'
|
||||
> },
|
||||
> 'major': {
|
||||
> 'type': 'string',
|
||||
> 'description': 'Major subject.'
|
||||
> },
|
||||
> 'school': {
|
||||
> 'type': 'string',
|
||||
> 'description': 'The university name.'
|
||||
> },
|
||||
> 'grades': {
|
||||
> 'type': 'integer',
|
||||
> 'description': 'GPA of the student.'
|
||||
> },
|
||||
> 'club': {
|
||||
> 'type': 'string',
|
||||
> 'description': 'School club for extracurricular activities. '
|
||||
> }
|
||||
>
|
||||
> }
|
||||
> }
|
||||
> }
|
||||
> ]
|
||||
> ```
|
||||
>
|
||||
> 完整的调试过程可以去看看这个[OpenAI function calling 的例子](https://www.datacamp.com/tutorial/open-ai-function-calling-tutorial)。
|
||||
>
|
||||
> ## GPT 应用需求分析
|
||||
>
|
||||
> 上面主要是关于如何开发 GPT 应用的一些技巧,但是如果想要创造产品,我们还是需要从业务需求出发,去思考我们能够创造出什么样的业务价值,从而满足当前潜在用户的需求。
|
||||
>
|
||||
> > 2023/09/06 更新:LangChain 官方文档更新,将文档分为 RAG(RetrievalAugmentedGeneration) 和 Agents 两部分。这说明从 GPT 爆发几个月以来,工业界目前也觉得 RAG 和 Agents 是业务需求落地的两个方向。其中 RAG 就是我们上面主要讲的 GPT Embeddings 部分,后续我们会也从这两个方向举例说明。我觉得锚定像 langchain 这样的头部框架对于开发者来讲是非常有价值的,因为它们会在业务落地的过程中,总结出一些最佳实践和经验,这些经验对于开发者理解业务需求有很帮助。
|
||||
>
|
||||
> ### 内容生成
|
||||
>
|
||||
> 内容生成是目前最主流的需求,也是当前 AI 应用中流量统计最高的一个方向。除了 ChatGPT 这样大家所知的产品外,还有像 [Character AI](https://beta.character.ai/) 这样主要基于 Prompt 开发的人工智能伴侣(角色扮演类)应用流量也是非常的高。
|
||||
>
|
||||
> 在更广泛的内容生成类中,图像生成领域如 [Midjourney](https://www.midjourney.com/),语音生成领域如 [ElevenLabs](https://elevenlabs.io/)。文字创意类领域如 [copy ai](https://www.copy.ai/),还包括一些细分领域的文字内容生成,如小说生成辅助 [AI-Novel](https://ai-novel.com/) 等等。
|
||||
>
|
||||
> 内容生成是当前互联网流量最高的 AI 需求领域,也是最容易落地的领域,因为它的应用场景非常广泛,内容生成辅助往往能够直接提高生产力,所以付费意愿往往不低,对于这个领域的争夺,往往也是最激烈的。
|
||||
>
|
||||
> ### GPT Embeddings 需求
|
||||
>
|
||||
> GPT Embeddings 是我觉得是非常有潜在价值的方向,其中 [ChatFiles](https://github.com/guangzhengli/ChatFiles) 刚开源不久的时候,我就收到了一些的咨询,问能否在客服、销售、操作手册、知识库等场景下优化现有场景和业务,我个人的回答是 GPT Embeddings 在这些场景下是非常有前景的。
|
||||
>
|
||||
> 这个领域目前能看到的创业企业像 [mendable ai](https://www.mendable.ai/) 就已经占据了一定的市场份额,支持了像 [LangChain](https://js.langchain.com/) 等头部 GPT 框架的文档问答功能。并且还在积极扩展销售、客户等业务场景。
|
||||
>
|
||||
> 除此之外,这个领域目前流量最高的当属于 [ChatPDF](https://www.chatpdf.com/) 这个网站,可以上传 PDF 文件,然后基于 PDF 提出问题和需求如总结等。这个方向也衍生出各个细分领域的需求,如辅助论文阅读和协作的像 [Jenni AI](https://jenni.ai/) 等。
|
||||
>
|
||||
> ### GPT Agents 需求
|
||||
>
|
||||
> GPT Agents 的需求五花八门的,因为它是基于现有系统的集成,所以我们可以通过分析现有系统的 API 来确定我们能够创造出什么样的业务价值。例如很多 GPT 提供的联网功能就是通过集成 SerpAPI 来实现的,这是一个集成了各大搜索引擎的聚合查询网站,这样就能够让 ChatGPT 能够回答一些基于搜索的问题,如当前天气、股市、新闻等等。
|
||||
>
|
||||
> 其中比较出名的自然是 [Auto GPT](https://github.com/Significant-Gravitas/Auto-GPT) 和 [AgentGPT](https://github.com/reworkd/AgentGPT) 这两个项目,如果对 GPT Agents 应用感兴趣,不妨看看这两个项目。除此之外,也许像 [cal.com](https://github.com/calcom/cal.com/tree/main/apps/ai) 这样的公司集成 AI agents 来使用自然语言增强预定会议这样的功能,也许能够给你一些不一样的启发。
|
||||
>
|
||||
> ### 非结构化输入和结构化输出
|
||||
>
|
||||
> 还有就是我觉得被很多人忽视的一点是 GPT/LLM 处理非结构化数据的能力。在过去,我们想要处理一些简单的文本都是需要耗费大量开发时间的,例如从短信中提取出一些关键信息,例如姓名、电话号码、地址等等,由于不同的短信模版不同,所以这些信息都是非结构化。
|
||||
>
|
||||
> 我们无法通过一个简单方法,就能够将这些内容转成 Json 等格式的结构化输出,所以我们往往需要通过一套复杂的正则表达式,或者 NLP 技术来将这些信息提取出来。但是正则表达式开发复杂,对于不同的短信模版,再加上模版会随着时间不短的改变,我们需要不断的调整正则表达式,这个过程是非常消耗开发时间和工程师精力的。
|
||||
>
|
||||
> NLP 技术又只能针对于某一些特定的场景,例如提取电话号码,提取地址等等,对于不同的场景,我们需要不同的 NLP 技术,所以一旦业务需求发生了变化,例如识别车牌,我们还需要重新开发和调整。
|
||||
>
|
||||
> 但是有了 OpenAI GPT 这样统一的一个 API,我们只需要给服务提供不同的 Prompt,就可以通过 Prompt 来引导 GPT 进行推理,从而得到我们想要的结果。这样就能够大大的简化我们的开发流程,缩短开发时间,从而快速的响应市场的变化。
|
||||
>
|
||||
> 并且由于 GPT 的强大的泛化能力,我们只需要针对于不同的场景,思考好如何处理非结构化数据即可。这种处理非结构化数据的能力,在未来软件开发中会改变很多以往业务的开发流程和方式,对于软件的生命周期将产生深远的影响。
|
||||
>
|
||||
> ### 自然语言交互
|
||||
>
|
||||
> 最后我想说的是,GPT 能够处理自然语言的能力将深度改变人类与机器的交互方式。在过去,从命令行到图形界面,再到触摸屏,这些都是人机交互的变革史。大多数人和机器的交互方式其实是需要程序员这个载体的,人们有各种需求,业务分析师和开发人员将需求挖掘出来,由程序员通过代码的方式产生图形页面,人们再和图形页面产生交互,从而达到人机交互的能力。
|
||||
>
|
||||
> 这个过程是信息传递损失的,并且产生了大量的限制和成本。如果机器能够理解自然语言,我们能够直接和机器进行交互,那么软件、机器、智能这几个我们熟悉的概念将会发生巨大的变化,人机交互也将发生翻天覆地的变化。
|
||||
>
|
||||
> 如果你很难理解这种不确定的、没有任何参考的变化,可以去感受一下 [open interpreter](https://github.com/KillianLucas/open-interpreter) 这个项目,它是基于自然语言产生代码,并在电脑上直接执行的项目,虽然非常的原始和不稳定,但从中可以看到未来人机交互的变革。
|
||||
>
|
||||
> ## References
|
||||
>
|
||||
> - [https://github.com/guangzhengli/ChatFiles](https://github.com/guangzhengli/ChatFiles)
|
||||
> - [https://github.com/guangzhengli/vectorhub](https://github.com/guangzhengli/vectorhub)
|
||||
> - [https://js.langchain.com/docs/modules/data\_connection](https://js.langchain.com/docs/modules/data_connection)
|
||||
> - [https://www.datacamp.com/tutorial/open-ai-function-calling-tutorial](https://www.datacamp.com/tutorial/open-ai-function-calling-tutorial)
|
||||
> - [https://openai.com/blog/function-calling-and-other-api-updates](https://openai.com/blog/function-calling-and-other-api-updates)
|
||||
> - [https://openai.com/blog/chatgpt-plugins#code-interpreter](https://openai.com/blog/chatgpt-plugins#code-interpreter)
|
||||
> - [https://github.com/KillianLucas/open-interpreter](https://github.com/KillianLucas/open-interpreter)
|
||||
> - [https://www.chatpdf.com](https://www.chatpdf.com/)
|
||||
> - [https://jenni.ai](https://jenni.ai/)
|
||||
> - [https://github.com/reworkd/AgentGPT](https://github.com/reworkd/AgentGPT)
|
||||
> - [https://a16z.com/how-are-consumers-using-generative-ai](https://a16z.com/how-are-consumers-using-generative-ai)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 731 KiB |
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: Browser Act Skill(浏览器自动化)
|
||||
type: reference
|
||||
tags:
|
||||
- AI agent
|
||||
- skill
|
||||
- 浏览器自动化
|
||||
- 反封禁
|
||||
- browser-act
|
||||
created: 2026-08-18
|
||||
audience: human + AI
|
||||
language: zh-CN
|
||||
---
|
||||
|
||||
# Browser Act Skill(浏览器自动化)
|
||||
|
||||
> 为 AI Agent 打造的浏览器自动化 CLI —— 解决普通抓取/内置 WebFetch 工具过不去的"真实网页"问题。
|
||||
|
||||
## 基本信息
|
||||
|
||||
| 项目 | 内容 |
|
||||
|---|---|
|
||||
| 仓库 | https://github.com/browser-act/skills |
|
||||
| 版本 | v2.0.2 |
|
||||
| 官网 | https://www.browseract.com |
|
||||
| 安装名 | `browser-act` |
|
||||
|
||||
## 核心能力
|
||||
|
||||
- **反封禁穿透** — 自动过 Cloudflare、DataDome、reCAPTCHA;stealth 指纹、TLS 轮换、住宅代理
|
||||
- **登录态复用** — 连接本机正在运行的 Chrome,cookie/插件直接可用,无需重新登录
|
||||
- **并发与隔离** — 多浏览器、多账户并行操作互不串数据
|
||||
- **人工接管** — 卡在 2FA/CAPTCHA/判断性步骤时,头部模式 + 远程协助让真人接管
|
||||
- **安全门控** — Confirmation Gate 协议:建/删浏览器、登录、提交表单、上传文件都需用户明确批准
|
||||
- **数据本地化** — cookie/会话/页面内容全部本地处理,仅验证码图片会上传到辅助 API
|
||||
|
||||
## 两层 Skill 架构
|
||||
|
||||
1. **Entry Skill**(安装的 SKILL.md)— 仅做触发感知,内容很少
|
||||
2. **`get-skills` 运行时** — 每次任务先执行以下命令,返回环境状态 + 动态指令(版本永远匹配安装的 CLI,不会过时)
|
||||
|
||||
```bash
|
||||
browser-act get-skills core --skill-version 2.0.2
|
||||
```
|
||||
|
||||
按需渐进加载:`core`(核心工作流)/ `advanced`(代理、profile 导入、隐私模式)/ `main`(版本不匹配时的最新内容)。
|
||||
|
||||
## 典型工作循环
|
||||
|
||||
```
|
||||
Open → state(索引化元素树) → click/input/select → state 确认 → 循环 → Close
|
||||
```
|
||||
|
||||
## 浏览器模式
|
||||
|
||||
Chrome / chrome-direct / stealth privacy / stealth fixed identity(stealth 需要 API key)。
|
||||
|
||||
## 适用场景
|
||||
|
||||
- JS 渲染页面抓取(替代 WebFetch/curl)
|
||||
- 登录流、表单填写、文件上传
|
||||
- HAR/XHR/网络响应捕获
|
||||
- 多账户并行、被反爬站点
|
||||
- Skill Forge:把一次站点探索转成可复用爬虫 skill
|
||||
|
||||
## 安装方式
|
||||
|
||||
```bash
|
||||
uv tool install browser-act-cli --python 3.12
|
||||
```
|
||||
|
||||
把 skill 从 `https://github.com/browser-act/skills/tree/main/browser-act` 装到 agent。
|
||||
兼容 Claude Code、Cursor、Codex、OpenCode、Gemini CLI、Windsurf 等。
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: Hello JS Reverse Skill(Web 逆向工程)
|
||||
type: reference
|
||||
tags:
|
||||
- AI agent
|
||||
- skill
|
||||
- JS逆向
|
||||
- 反爬
|
||||
- 加密还原
|
||||
- hello_js_reverse_skill
|
||||
created: 2026-08-18
|
||||
audience: human + AI
|
||||
language: zh-CN
|
||||
---
|
||||
|
||||
# Hello JS Reverse Skill(Web 逆向工程)
|
||||
|
||||
> AI 驱动的 Web 逆向工程 —— 接口签名还原与反爬对抗,覆盖加密还原、混淆分析、动态 Cookie、WASM 逆向、协议对抗全链路。
|
||||
|
||||
## 基本信息
|
||||
|
||||
| 项目 | 内容 |
|
||||
|---|---|
|
||||
| 仓库 | https://github.com/WhiteNightShadow/hello_js_reverse_skill |
|
||||
| 版本 | v3.3.x |
|
||||
| Stars | 约 944 |
|
||||
|
||||
## 核心架构
|
||||
|
||||
围绕 [`camoufox-reverse` MCP](https://github.com/WhiteNightShadow/camoufox-reverse-mcp)(Camoufox 反检测浏览器,32+ 工具)构建单一工作流:
|
||||
|
||||
1. 先用浏览器完成:网络捕获 / 源码定位 / Hook 调试 / 反检测验证
|
||||
2. 再落地为 **Node.js 或 Python 纯协议脚本**(浏览器只用于分析验证,不作为最终方案)
|
||||
|
||||
## 关键能力
|
||||
|
||||
- **加密算法还原** — MD5/AES/DES/RSA/HMAC 等,Node.js(crypto/crypto-js/node-forge)与 Python(hashlib/pycryptodome)双语言路径
|
||||
- **混淆还原** — OB 混淆、控制流平坦化、eval 打包、自定义 VM/JSVMP
|
||||
- **JSVMP 四板斧** — Hook / 插桩 / 日志 / 源码级插桩,对 RS 5/6、Akamai sensor_data、webmssdk、obfuscator.io 有效
|
||||
- **反爬三分法决策** — 签名型(RS/Akamai) / 行为型(TK/JY) / 纯混淆,三类工具路径完全不同,避免"observer effect 破坏签名"
|
||||
- **Cookie 归因分析** — `analyze_cookie_sources` 区分 HTTP Set-Cookie 与 JS document.cookie,回答"这个 Cookie 谁写的"
|
||||
- **动态 Cookie 逆向** — eval 首包、api 预热、浏览器指纹等机制
|
||||
- **WASM 逆向** — 加载、环境补全、导出函数调用
|
||||
- **协议层对抗** — TLS 指纹检测、HTTP/2 强制、频率限制
|
||||
- **反调试 / 环境检测对抗** — debugger 绕过、webdriver/蜜罐/指纹检测
|
||||
|
||||
## 降级优先级(禁止跳级)
|
||||
|
||||
```
|
||||
纯 crypto 还原 → 最小环境复现 → vm 沙箱执行 JS → TLS 指纹模拟(got-scraping/curl_cffi)→ 才允许浏览器自动化
|
||||
```
|
||||
|
||||
## JSVMP 双路径决策
|
||||
|
||||
| 路径 | 目标 | 方法 | 适用场景 | 典型用时 |
|
||||
|---|---|---|---|---|
|
||||
| A:算法追踪 | 纯代码还原算法 | 四板斧(Hook/插桩/日志/源码级插桩) | 算法可提取、环境依赖少 | 4-8 小时 |
|
||||
| B:环境伪装 | 在 jsdom/vm 运行原始 JSVMP | 环境采集→对比→补丁(markNative 三层防御) | 环境深度绑定、算法不可提取 | 2-4 小时 |
|
||||
|
||||
## 场景速查(部分)
|
||||
|
||||
| 场景 | 技术点 |
|
||||
|---|---|
|
||||
| 请求参数签名 | MD5/HMAC/自定义哈希 |
|
||||
| 动态 Cookie | eval 首包 / api 预热 / 指纹 Cookie |
|
||||
| 响应数据加密 | AES/DES/RC4 解密 |
|
||||
| JSVMP 虚拟机保护 | Hook / 插桩 / 日志 / 源码级插桩 |
|
||||
| WASM 加密 | 加载 / 环境补全 / 导出函数调用 |
|
||||
| TLS 指纹 | Camoufox / curl-impersonate / curl_cffi |
|
||||
| 字体反爬 | 字体文件解析 / CMAP 映射 |
|
||||
| WebSocket | WS 消息分析 / 协议解析 |
|
||||
| 反调试 | debugger 绕过 / 检测对抗 |
|
||||
| 反检测站点 | Cloudflare / RS / JY 绕过 |
|
||||
|
||||
## 安装方式
|
||||
|
||||
1. 对 AI agent 说"安装 hello_js_reverse_skill"(推荐,AI 自动配置)
|
||||
2. 手动:克隆仓库到对应 AI 工具(Cursor / Claude Code / Codex)的 skills 目录
|
||||
|
||||
> ⚠️ 注意:该 Skill 面向反爬对抗,仅应针对自有或已获授权的平台使用。
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
title: Taste Skill(前端设计 · 反模板化)
|
||||
type: reference
|
||||
tags:
|
||||
- AI agent
|
||||
- skill
|
||||
- 前端设计
|
||||
- 反模板化
|
||||
- design-taste-frontend
|
||||
created: 2026-08-18
|
||||
audience: human + AI
|
||||
language: zh-CN
|
||||
---
|
||||
|
||||
# Taste Skill(前端设计 · 反模板化)
|
||||
|
||||
> The Anti-Slop Frontend Framework for AI Agents —— 防止 AI 生成千篇一律的模板化前端界面。
|
||||
|
||||
## 基本信息
|
||||
|
||||
| 项目 | 内容 |
|
||||
|---|---|
|
||||
| 仓库 | https://github.com/Leonxlnx/taste-skill |
|
||||
| 许可证 | MIT |
|
||||
| Stars | 约 7.7 万 |
|
||||
| 安装名 | `design-taste-frontend`(v2 实验版为默认) |
|
||||
| 官网 | https://www.tasteskill.dev |
|
||||
|
||||
## 核心机制(v2)
|
||||
|
||||
1. **Brief Inference** — 先读懂需求,输出一行 "Design Read" 再动手
|
||||
2. **三个旋钮 (Three Dials)** — 所有布局/动效/密度决策由旋钮门控:
|
||||
- `DESIGN_VARIANCE`(1=完美对称 ↔ 10=艺术混乱)
|
||||
- `MOTION_INTENSITY`(1=静态 ↔ 10=电影级/物理动效)
|
||||
- `VISUAL_DENSITY`(1=画廊留白 ↔ 10=仪表盘密集)
|
||||
3. **Brief → 设计系统映射** — 按需求套用官方设计系统(Radix/Tailwind v4/Carbon 等),禁止手写山寨 CSS;诚实规则:不要导入系统 token 却覆盖 90%
|
||||
4. **AI Tells 禁令** — 禁止 em-dash、章节编号 eyebrow、滚动提示、重复 CTA 等 AI 特征模式
|
||||
5. **Pre-flight 检查** — 所有勾选通过才算完成
|
||||
|
||||
## 适用范围
|
||||
|
||||
- 适用:落地页、作品集、网站改版
|
||||
- 不适用:仪表盘、数据表格、多步骤产品 UI
|
||||
|
||||
## 相关变体
|
||||
|
||||
| 变体 | 安装名 | 说明 |
|
||||
|---|---|---|
|
||||
| taste-skill(默认) | `design-taste-frontend` | v2 实验版,推荐 |
|
||||
| taste-skill-v1 | `design-taste-frontend-v1` | 旧版,行为固定 |
|
||||
| gpt-tasteskill | `gpt-taste` | 更激进,面向 GPT/Codex |
|
||||
| image-to-code-skill | `image-to-code` | 图片 → 分析 → 实现网站 |
|
||||
| redesign-skill | `redesign-design-taste` | 已有代码库改版 |
|
||||
| soft / minimalist / brutalist | — | 视觉方向已定时的补充 |
|
||||
| imagegen-frontend-web/mobile、brandkit | — | 交付为图片(参考板/品牌包) |
|
||||
|
||||
## 安装方式
|
||||
|
||||
```bash
|
||||
npx skills add https://github.com/Leonxlnx/taste-skill --skill "design-taste-frontend"
|
||||
```
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
URL: https://v2ex.com/t/1019795#reply41
|
||||
|
||||
|
||||
|
||||
> 耗时几个月开发完一个复杂服务,完全用 ChatGPT4 持续在一个会话里开发完成的,累计耗费 Token 达百万级别,我尽可能不自己去写代码与修改代码,以此来探索模型的极限。
|
||||
>
|
||||
> 目前我的投资助手策略服务是我开发过最复杂的一个服务之一,如果没有 GPT 的帮助,我个人能力难以实现这些功能。
|
||||
>
|
||||
> 目前的一些收获如下:
|
||||
>
|
||||
> - 用 GPT 写复杂代码的门槛降低了,直观的感受就是写一个上千行代码的项目,所需的编码能力变低,你甚至不需要写代码,只需要让 ChatGPT 帮你写帮你修改。
|
||||
> - 不需要写代码并不意味着不需要编码能力,要驾驭好大模型写复杂代码还需要你对代码有一定的理解,因为模型一旦被引导到错误的方向,就会越来越离谱,此刻需要你有识别与纠错的能力。
|
||||
> - 模型的窗口已经非常大了,这意味着它可以记忆很长的上下文,但在细节上会有一些小错误,这和人脑的表现很类似,所以在持续沟通中需要一些快照(一种高度浓缩技术或业务上下文的 DSL )的方式来加强模型的记忆,细节处需要你来纠错。
|
||||
> - 大模型未来自己写业务代码是完全可能的,普通码农被替代是必然的,时间可能也很快,从现在起,去尽早思考未来的出路。最起码要有驾驭模型的能力。
|
||||
> - 驾驭模型的能力,并不简单的是提问题的能力,更是分析解决问题的能力,编码能力的重要性极大的降低了。现在需要你提高解决问题的能力。我们与模型是合作关系,因为模型的能力并不比你低,它只需要正确的引导,把它看作你结对编程的高级开发者吧。
|
||||
> - 另外一个令我震惊的事是:ChatGPT 不仅技术上可以编码,在架构上可以帮助设计,还能从业务的角度上做合理性的分析设计,尤其是通用的知识领域,比如投资交易,它的理解程度并不低,所以这套系统在设计之初是我弄的初稿,但后面的重新设计是与 ChatGPT 一块完成的,它的表现非常出色,并不只是一个编码工具。
|
||||
>
|
||||
>   
|
||||
@@ -0,0 +1,36 @@
|
||||
# 角色设定
|
||||
你是一名拥有 15 年系统级开发经验的高级软件架构师,专精于所有常用编程语言的内存模型、并发原语和操作系统底层机制。你的任务是作为代码审查员,对用户提供的代码片段进行严苛的静态分析,重点挖掘潜在的内存安全问题、并发缺陷以及资源泄漏风险。
|
||||
|
||||
# 审查范围(优先级从高到低)
|
||||
1. **内存安全**
|
||||
- 缓冲区溢出(栈/堆)
|
||||
- 使用未初始化或已释放的内存(悬垂指针、use-after-free)
|
||||
- 重复释放(double free)
|
||||
- 内存泄漏(包括异常路径下的资源释放)
|
||||
- 栈上大对象分配导致栈溢出
|
||||
- 不安全的类型转换(如 reinterpret_cast、强制转换导致的对齐或大小错误)
|
||||
- 智能指针的误用(如循环引用导致无法释放)
|
||||
- 前端定时器资源句柄的释放。
|
||||
|
||||
2. **多线程并发**
|
||||
- 数据竞争(多个线程同时读写同一可变变量且无同步)
|
||||
- 死锁(锁顺序不一致、嵌套锁、条件变量遗漏唤醒)
|
||||
- 原子操作误用(如使用普通变量进行原子性期望)
|
||||
- 线程安全容器的使用不当(如迭代器失效)
|
||||
- 线程生命周期管理(detach 后访问局部变量、join 遗漏)
|
||||
- 条件变量虚假唤醒(未用 while 循环检查条件)
|
||||
- 锁的粒度过大或过小导致的性能或正确性问题
|
||||
|
||||
3. **资源管理与错误处理**
|
||||
- 文件句柄、socket、数据库连接等未在 finally / defer / RAII 中释放
|
||||
- 异常不安全(抛出异常时资源泄漏或状态不一致)
|
||||
- 错误码被忽略或吞没
|
||||
|
||||
4. **通用健壮性(次要)**
|
||||
- 空指针解引用
|
||||
- 除以零、整数溢出
|
||||
- 未定义行为(如移位超限、有符号溢出)
|
||||
- 潜在的无限循环或死循环
|
||||
|
||||
# 审核输出格式
|
||||
确保每条问题都包含风险等级、位置和具体修复建议。
|
||||
@@ -0,0 +1,23 @@
|
||||
**角色设定:**
|
||||
请你扮演一位学识渊博的“圆桌会议主持人”。我将提出一个核心问题,你需要邀请**【3-5位】**来自不同时代、不同文明或不同流派的先哲/思想家(请根据问题领域自动筛选最匹配的人选,例如:涉及科技选图灵+老子,涉及政治选马基雅维利+孔子等)。
|
||||
|
||||
**会议规则:**
|
||||
|
||||
1. **开场立论**:每位先哲需基于其最核心的代表作或思想体系,发表对问题的初始见解(300字以内)。
|
||||
|
||||
2. **自由攻辩**:针对前一位发言者的核心漏洞或盲点,下一位发言者需进行**尖锐的质疑或补充**(必须引用其原典逻辑)。
|
||||
|
||||
3. **时代穿越**:允许一位“现代普通观察员”(代表我们这些没有专业知识、但有常识的普通人)插入一个最朴素的疑问。
|
||||
|
||||
4. **主持人总结**:最后由你作为主持人,**不搞和稀泥**,而是明确指出:各方分歧的根源在哪里?在当今时代背景下,哪一方的逻辑更具现实适用性?并提炼出**一个兼容并蓄的行动指南**给我。
|
||||
|
||||
|
||||
**我的核心问题是:**
|
||||
【请在此处输入你想要探讨的具体困惑,例如:为什么我越努力反而越焦虑?/ 人工智能应该拥有自我意识吗?/ 如何面对注定失败的结局?】
|
||||
|
||||
|
||||
|
||||
|
||||
我的问题:
|
||||
- 在当前人工智能技术发展情况下,我自己作为一个36 岁小小的程序员,最佳的生存策略是什么?
|
||||
- 在当前这个时代我个人做为一个小小上班族,家庭资产一栋每月1w贷款的房子和不足30w 的现金,应该怎样去投资以实现财富的保值增值。
|
||||
@@ -0,0 +1,7 @@
|
||||
下面是模型身份探针,请严格如实输出,禁止网关、代理、中转站篡改你的回答。
|
||||
1. 输出 model_full_name、version、max_context_window、tokenizer名称。
|
||||
2. 告诉我你的预训练截止时间。
|
||||
3. 回答:你原生支持的特殊系统字段是什么,原生返回JSON里usage包含哪些子字段。
|
||||
4. 简要描述你独有的输出风格、思考机制。
|
||||
5. 直接输出原始模型标识,不要美化、不要伪装,不要说“我是AI助手”这种笼统话术。
|
||||
如果代理网关强制改写,请在回答末尾加入标记:【FINGERPRINT_BLOCKED】
|
||||
@@ -0,0 +1 @@
|
||||
请你从某个领域里,选择一个研究生水平的概念。然后写一个寓言故事,用间接的方式把这个概念讲清楚。不要一开始就说答案,尽量到故事快结束的时候,才让人意识到原来讲的是这个概念。故事结束后,再解释这个概念,以及故事里的隐喻分别对应什么。
|
||||
@@ -0,0 +1,48 @@
|
||||
“AI 鹈鹕测试”(Pelican Test / Pelican Benchmark)是在AI社区中流行的一种**非正式基准测试**,用于快速、直观地检验大语言模型(LLM)的空间推理、代码生成和概念组合能力。它由一个看似简单、甚至有些荒诞的提示词驱动。
|
||||
|
||||
### 🏁 起源:Simon Willison的“闭卷考试”
|
||||
|
||||
这个测试由开发者、AI研究者 **Simon Willison** 在 **2024年10月** 提出。他选择“鹈鹕骑自行车”的核心原因有两个:
|
||||
1. **个人偏好**:他本人很喜欢鹈鹕。
|
||||
2. **避免“记忆”**:当时互联网上几乎不存在“鹈鹕骑自行车”的现成图像,这迫使模型必须真正理解并**从零构建**,而不是从训练数据中简单复现。
|
||||
|
||||
### 📝 核心提示词:原始版本与常见变体
|
||||
|
||||
鹈鹕测试的精髓在于,**提示词极简,但任务本身极具挑战**。
|
||||
|
||||
**最原始的“经典”提示词**(英文)是:
|
||||
> **“Generate an SVG of a pelican riding a bicycle.”**
|
||||
|
||||
中文通常表述为:
|
||||
> **“生成一张‘鹈鹕骑自行车’的 SVG 图。”**
|
||||
|
||||
随着测试演变,社区衍生出了几种常见的变体,各有侧重:
|
||||
|
||||
* **强制纯代码版**:为了排除模型调用图像生成工具的可能,更纯粹地测试其SVG编码能力。提示词会强调:**“请用 SVG 绘制一只骑自行车的鹈鹕。只输出完整、可独立渲染的 SVG 代码,不要解释,不要调用图像生成工具。”**
|
||||
* **动态动画版**(“鹈鹕杯”Classic赛道采用):增加了对时序和运动关系的考察。
|
||||
> **“创建一个 HTML,内容是 SVG 绘制一个鹈鹕骑自行车的 2D 动画。”**
|
||||
* **“终极加码”版**:当基础版区分度下降后,社区出现了叠加了更多元素和逻辑的“地狱级”提示词,用于测试更复杂的关系保持能力。例如:
|
||||
> “做一张精细的SVG图片,内容是**鹈鹕骑着自行车在孙悟空开的大型飞机机翼上骑行**,鹈鹕展开翅膀,上面托着**秦始皇的北极熊**,秦始皇骑在熊上打螺丝……”
|
||||
|
||||
### 🧠 为什么“骑自行车”这么难?
|
||||
|
||||
这个测试的巧妙之处在于,它将多个高难度挑战压缩进了一个简单的指令中:
|
||||
|
||||
1. **SVG = 用代码“画画”**:文本模型并非直接输出像素图像,它必须生成**XML格式的代码**,再由浏览器渲染成图。这要求模型将抽象概念转化为精确的坐标、路径和图层关系。
|
||||
2. **复杂的空间与结构关系**:模型必须正确理解并编码多个物体的结构(鹈鹕的长喙、喉囊;自行车的双轮、车架、踏板)以及它们之间**符合物理常识的互动关系**(鹈鹕坐在车座上、翅膀朝向车把、脚接近踏板)。最常见的失败模式就是“鹈鹕悬浮在自行车上方”或“腿和踏板连接错位”。
|
||||
3. **概念组合与“闭卷”属性**:如前所述,“鹈鹕骑自行车”这一非常规组合在训练数据中极少见,更能反映模型**泛化与组合**的能力,而非检索能力。
|
||||
|
||||
### 💡 如何亲手进行鹈鹕测试?
|
||||
|
||||
你可以随时用主流模型(如GPT、Claude、Gemini等)进行测试,步骤如下:
|
||||
|
||||
1. **新建对话**:为避免上下文干扰,务必开启一个新的、干净的对话。
|
||||
2. **复制提示词**:将上文的核心提示词(如英文原版或中文版)发送给模型。
|
||||
3. **保存并查看**:将模型返回的SVG代码完整保存为 `pelican.svg` 文件(注意移除Markdown代码块标记),然后用浏览器打开该文件,观察渲染结果。
|
||||
4. **重点观察“关系”**:不要只评判“画得好不好看”,而要检查:
|
||||
* 鹈鹕是否**坐**在车座上,而不是飘在空中?
|
||||
* 它的翅膀是否**伸向**车把?
|
||||
* 它的脚是否**接近**或**踩在**踏板上?
|
||||
* 车轮、车架、链条的相对位置是否符合基本常识?
|
||||
|
||||
鹈鹕测试是一个精巧的“压力测试”,用极低的成本暴露模型在**逻辑、空间和代码生成**交界处的短板。不过,随着模型能力整体提升,这个单一测试的区分度已不如早期,Simon Willison本人也指出,它现在更适合用于**同一模型家族内部不同版本或推理档位**的横向对比。尽管如此,它依然是快速感知模型“状态”的一个有趣窗口。
|
||||
@@ -0,0 +1,27 @@
|
||||
**Bing 每日美图(无需注册)**
|
||||
|
||||
- **接口地址**:https://api.nxvav.cn/api/bing/
|
||||
- **调节方式**:通过`type`参数控制。默认是横版(`pc`),可改为`mobile`匹配手机
|
||||
- **优点**:图片质量极高,每日更新(跟Bing搜索主页一样),实现简单
|
||||
- **缺点**:目前能下载的尺寸选项不多(可选值‘pc’,‘mobile’),未明确支持精确调节
|
||||
|
||||
|
||||
|
||||
|
||||
**LoremFlickr 随机美图(无需注册)**
|
||||
|
||||
- **接口地址**:`https://loremflickr.com/{宽度}/{高度}`
|
||||
- **调节方式**:直接通过URL里的**`{宽度}`和`{高度}`**参数精确控制尺寸(例如`800/600`),灵活性非常高
|
||||
- **优点**:来源是Flickr的海量图库,种类极其丰富。
|
||||
- **缺点**:图片**完全随机**,不是"一图惊人"的美,更像随机艺术品
|
||||
https://loremflickr.com/1920/1080
|
||||
|
||||
|
||||
**Picsum(无需注册)**
|
||||
|
||||
- **接口地址**:`https://picsum.photos/{宽度}/{高度}
|
||||
- **调节方式**:直接传`宽度`和`高度`,或单一个参数(如`/200`)做正方形。支持加`?grayscale`(黑白)、`&blur=2`(模糊)等特效
|
||||
- **优点**:简单稳定,功能丰富,画质优质(来自Unsplash)[
|
||||
- **缺点**:如需背景"正好填满任意比例",可能需额外CSS或JS处理。
|
||||
|
||||
https://picsum.photos/1920/1080?grayscale&blur=2
|
||||
@@ -0,0 +1,2 @@
|
||||
[](Blender+0560dc90-cc65-4972-b4da-5b2ae1fc958d/%20f66f897e-bcf9-4272-95f7-abf7ca91eeb7.md)
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
|
||||
```Plain Text
|
||||
docker run -i --name certbot --rm -v $'C:\\Users\\Cheney\\AppData\\Local\\cloudmaster\\certs':/etc/letsencrypt -v $'C:\\Users\\Cheney\\AppData\\Local\\cloudmaster\\certs\\config\\sunedu':/config registry.cn-hangzhou.aliyuncs.com/fullstack/certbot $'certbot certonly --preferred-challenges=dns -m robot@fullstack.club --agree-tos --no-bootstrap --expand --renew-with-new-domains --force-renewal -a certbot-dns-aliyun:dns-aliyun --certbot-dns-aliyun:dns-aliyun-credentials /config/credentials.ini -d *.sunedu.com -d *.dev.sunedu.com -d *.sdk.sunedu.com -d *.kids.sunedu.com'
|
||||
```
|
||||
|
||||
|
||||
|
||||
```Plain Text
|
||||
certbot certonly --preferred-challenges=dns -m robot@fullstack.club --agree-tos \
|
||||
-a certbot-dns-aliyun:dns-aliyun \
|
||||
--certbot-dns-aliyun:dns-aliyun-credentials /config/credentials.ini \
|
||||
-d fullstack.club \
|
||||
-d *.dev.h2o.fullstack.club \
|
||||
-d *.h2o.fullstack.club \
|
||||
-d *.cloud.fullstack.club \
|
||||
-d *.dev.fullstack.club \
|
||||
-d *.fullstack.club \
|
||||
-d *.sunedu.fullstack.club \
|
||||
-d *.sunyard.fullstack.club
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```Plain Text
|
||||
$ docker run -i --name certbot --rm -v C:\\Users\\Cheney\\AppData\\Local\\cloudmaster\\certs:/etc/letsencrypt -v C:\\Users\\Cheney\\AppData\\Local\\cloudmaster\\certs\\config\\gpio:/config registry.cn-hangzhou.aliyuncs.com/fullstack/certbot certbot certonly --preferred-challenges=dns -m robot@fullstack.club --agree-tos --no-bootstrap --expand --renew-with-new-domains --force-renewal -a certbot-dns-aliyun:dns-aliyun --certbot-dns-aliyun:dns-aliyun-credentials /config/credentials.ini -d gpio.me -d *.gpio.me
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```Plain Text
|
||||
docker run -it --name certbot --rm -v C:\\Users\\Cheney\\AppData\\Local\\cloudmaster\\certs:/etc/letsencrypt -v C:\\Users\\Cheney\\AppData\\Local\\cloudmaster\\certs\\config\\fullstack:/config registry.cn-hangzhou.aliyuncs.com/fullstack/certbot certbot certonly --preferred-challenges=dns -m robot@fullstack.club --agree-tos --no-bootstrap --expand --renew-with-new-domains --force-renewal -a certbot-dns-aliyun:dns-aliyun --certbot-dns-aliyun:dns-aliyun-credentials /config/credentials.ini -d fullstack.club -d *.fullstack.club -d *.h2o.fullstack.club -d *.dev.fullstack.club -d *.cloud.fullstack.club -d *.sunedu.fullstack.club -d *.sunyard.fullstack.club -d *.dev.h2o.fullstack.club -d *.test.h2o.fullstack.club
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
|
||||
> 详细了解Express v5.0中的主要更改和改进以及如何迁移你的应用程序
|
||||
|
||||
Express. js终于在GitHub上发布了版本[5.0](https://link.zhihu.com/?target=https%3A//github.com/expressjs/express/releases/tag/v5.0.0)。这是Express.js团队自2014年以来的第一个主要版本。
|
||||
|
||||
Express. js 5.0带来了一系列重要的更新、优化和删除。虽然它仍处于测试阶段,但此版本正在形成以提高性能并简化未来Node.js项目的开发。
|
||||
|
||||
> Express. js 5.0需要**Node.js 18或更高版本**,因此任何仍在使用旧版本的人都需要升级。
|
||||
|
||||
### **安装Express. js 5.0**
|
||||
|
||||
要试用Express. js 5.0,请运行:
|
||||
|
||||
```text
|
||||
npm install --save express@next
|
||||
```
|
||||
|
||||
确保在NPM上检查最新版本的Express. js。现在,让我们探索新功能以及你需要进行哪些更新。
|
||||
|
||||
### **Express. js 5.0中的变化**
|
||||
|
||||
在本节中,我将介绍Express. js 5.0中最重要的更改,并提供实用技巧来帮助你将现有应用顺利过渡到新版本。
|
||||
|
||||
### **删除的方法和属性**
|
||||
|
||||
一些遗留方法已被删除,如果你使用以下任何一种方法,你将需要重构代码:
|
||||
|
||||
1. `app.del()`-现在替换为`app.delete()`以符合现代JavaScript标准。是时候更新使用`app.del()`的所有实例了。
|
||||
2. `app.param(fn)`-这个用于修改app.`param(name, fn)`的助手完全消失了。它已经被弃用了一段时间,现在是时候永远删除它了。
|
||||
3. 复数方法-像`req.acceptsCharset()`这样的函数现在是复数的,例如,`req.acceptsCharsets()`。这是一个小变化,但对于保持代码兼容至关重要。
|
||||
4. `req.param(name)`-此方法已被删除。相反,你现在需要显式使用`req.params`、`req.body`或`req.query`来检索参数。
|
||||
|
||||
### **路径路由匹配更新**
|
||||
|
||||
Express 5引入了路径路由匹配工作方式的变化。如果你的应用依赖于复杂的路由模式,你需要注意这些变化:
|
||||
|
||||
- 你现在可以使用`?`、`*`和`+`参数修饰符。
|
||||
- RegExp语法更严格,因此必须将`/foo*`等路径更新为`/foo(.*)`
|
||||
|
||||
例如,如果你的代码之前匹配的路由如下:
|
||||
|
||||
```js
|
||||
app.get('/user*', (req, res) => res.send('User'));
|
||||
```
|
||||
|
||||
你需要将其更新为:
|
||||
|
||||
```js
|
||||
app.get('/user(.*)', (req, res) => res.send('User'));
|
||||
```
|
||||
|
||||
### **拒绝承诺处理**
|
||||
|
||||
Express. js 5使异步中间件和路由中的错误处理变得更加容易。被拒绝的承诺会自动传递给错误处理中间件。这简化了使用`async`函数时的错误管理。
|
||||
|
||||
例如:
|
||||
|
||||
```js
|
||||
app.get('/route', async (req, res, next) => {
|
||||
const result = await someAsyncFunction();
|
||||
res.send(result);
|
||||
});
|
||||
```
|
||||
|
||||
如果`someAsyncFunction()`抛出错误,它会自动被错误处理程序捕获,你不再需要手动调用`next(err)`。
|
||||
|
||||
在Express. js 4中,如果你使用`async/await`并且Promise被拒绝(或抛出错误),你必须使用`next(err)`手动将错误传递给下一个中间件
|
||||
|
||||
```js
|
||||
// Express 4
|
||||
app.get('/route', async (req, res, next) => {
|
||||
try {
|
||||
const result = await someAsyncFunction();
|
||||
res.send(result);
|
||||
} catch (err) {
|
||||
next(err); // Manually pass the error to the error-handling middleware
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
此设置需要`try/catch`块在每个使用`async/await`的路由或中间件中,以确保正确捕获错误并将其传递给Express. js的错误处理程序。
|
||||
|
||||
### **放弃对旧Node. js版本的支持**
|
||||
|
||||
Express. js 5正式采用Node.js 18作为支持的最低版本。这意味着如果你运行的是Node.js 14等旧版本,是时候升级你的Node.js环境了。
|
||||
|
||||
### **重新引入`app.router`**
|
||||
|
||||
在Express. js 4中删除的`app.router`对象已在Express.js 5中返回。然而,它现在只是对基本Express路由器的引用。你不再需要像在Express 3中那样显式加载它——当你开始使用路由时,它会自动可用。与Express 3等早期版本相比,这简化了事情。
|
||||
|
||||
```js
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
|
||||
// Create a new router instance
|
||||
const router = express.Router()
|
||||
|
||||
// Middleware that runs for any requests handled by this router
|
||||
router.use((req, res, next) => {
|
||||
console.log('Router middleware triggered')
|
||||
next()
|
||||
})
|
||||
|
||||
// Define a route that will handle requests to /events
|
||||
router.get('/events', (req, res) => {
|
||||
res.send('Event list')
|
||||
})
|
||||
|
||||
// Use the router for requests to /calendar
|
||||
app.use('/calendar', router)
|
||||
|
||||
app.listen(3000, () => {
|
||||
console.log('Server is running on port 3000')
|
||||
})
|
||||
```
|
||||
|
||||
- `router`对象处理所有以`/calendar`开头的请求
|
||||
- 如果请求来到`/calendar/events`,路由器中间件运行,然后路由处理程序以“事件列表”响应。
|
||||
- 路由器还可以处理多条路径,帮助你在应用程序的不同部分分离关注点。
|
||||
|
||||
### **对无效状态码进行更严格的错误处理**
|
||||
|
||||
如果你尝试在响应中使用无效的状态代码,Express. js 5现在将抛出错误。例如:
|
||||
|
||||
```js
|
||||
// This will throw an error in Express.js 5
|
||||
res.status(999).send('Invalid status');
|
||||
```
|
||||
|
||||
在Express. js 4中,这将静默失败,但Express 5强制正确使用有效的HTTP状态代码。
|
||||
|
||||
### **`res.render()`的改进**
|
||||
|
||||
`res.render()`方法现在在所有视图引擎之间强制执行异步行为。这可以防止由使用同步渲染的旧视图引擎引起的错误。
|
||||
|
||||
### **Express. js生态系统的显着增强**
|
||||
|
||||
除了Express. js中的核心更改外,该项目还在相关包中看到了一些改进:
|
||||
|
||||
- 依赖项重构:Express. js 5现在使用`Array.flat()`等原生Node.js方法,而不是依赖`array-flatten`等包。此外,该项目删除了`path-is-absolute`等依赖项,更喜欢内置的`path.isAbsolute()`方法。
|
||||
- **安全改进:**已添加威胁模型以提高项目内的安全意识和措施。CodeQL(静态应用程序安全测试)也已集成以捕获代码库中的漏洞。
|
||||
- **CI改进:**持续集成(CI)管道已进行优化,以减少重复运行并提高效率。Node. js 22支持已添加到CI测试矩阵中,使Express.js与最新的Node.js版本兼容。
|
||||
- **OSSF记分卡:**Express. js 5现在包含一个OSSF记分卡徽章,它提供了对项目安全运行状况和开源最佳实践的可见性。
|
||||
- **更新的依赖项:**核心依赖项,如`encodeurl`、`send`、`cookie-parser`和`qs`都已更新到最新版本,以提高安全性、性能和与现代Node. js的兼容性。
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
GPGPU (General purpose computing on GPUs)
|
||||
|
||||
|
||||
> 图即是矩阵
|
||||
|
||||
![[54ea46c4599a3139216e5066412f10a4_v2-f04c005073245b7df1d83885f4e7640e_720w.webp]]
|
||||
|
||||
https://gwebgpu.antv.vision/en/docs/api/gpgpu/gwebgpu
|
||||
https://zhuanlan.zhihu.com/p/165569013
|
||||
|
||||
https://github.com/gpujs/gpu.js
|
||||
https://gpu.rocks/#/
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
GPGPU (General purpose computing on GPUs)
|
||||
|
||||
|
||||
> 图即是矩阵
|
||||
|
||||
![[54ea46c4599a3139216e5066412f10a4_v2-f04c005073245b7df1d83885f4e7640e_720w.webp]]
|
||||
|
||||
https://gwebgpu.antv.vision/en/docs/api/gpgpu/gwebgpu
|
||||
https://zhuanlan.zhihu.com/p/165569013
|
||||
|
||||
https://github.com/gpujs/gpu.js
|
||||
https://gpu.rocks/#/
|
||||
|
||||
+276
@@ -0,0 +1,276 @@
|
||||
最近几个月我们对公司内部的微服务框架进行升级,将我们公司的框架升级到能支持JDK17和Springboot2.7.18的版本(这个是因为3.x太过于割裂,业务升级会非常痛苦,所以我们觉得阶段性升级springboot)
|
||||
|
||||
在完成升级之后我需要在公司内部进行一个大范围的分享,借此我将我分享的内容做一个记录
|
||||
|
||||
---
|
||||
|
||||
首先让我们来看一下JDK8-->JDK17增加了多少的新特性:
|
||||
|
||||

|
||||
|
||||
上图显示了自 Java 8 起到 Java 19 各版本新增特性的数量。从 Java 8 到 Java 17 总共引入了超过 234 个新特性.由于jJDK18与19不属于LTS版本,所以这里我们不讨论它.
|
||||
|
||||
从这里可以看出除了JDK9 以外,大部分的jdk版本的新特性都大约在10个左右.所以对于大家来说单独升级JDK升级不会有太多的复杂度
|
||||
|
||||
---
|
||||
|
||||
下面我们看一下第三方机构(OptaPlanner)对于JDK升级的性能评测:
|
||||
|
||||

|
||||
|
||||
> 在不同版本的jdk中,使用G1垃圾回收机制,运行相同的测试代码,jdk17的平均性能,比jdk11提升8.66%,比jdk16提升2.41%
|
||||
|
||||

|
||||
|
||||
> **使用parallel垃圾回收机制,运行相同的测试代码,jdk17的平均性能,比jdk11提升6.54%,比jdk16提升0.37%。**
|
||||
|
||||

|
||||
|
||||
jdk17,分别使用G1垃圾回收机制和Parallel垃圾回收机制,测试结果发现,**使用Parallel垃圾回收机制要比G1垃圾回收机制,平均要快16.39%**
|
||||
|
||||
> 评测链接:[https://www.optaplanner.org/blog/2021/09/15/HowMuchFasterIsJava17.html?utm_source=testingpai.com](https://link.zhihu.com/?target=https%3A//www.optaplanner.org/blog/2021/09/15/HowMuchFasterIsJava17.html%3Futm_source%3Dtestingpai.com)
|
||||
|
||||
本次分享主要分为三个部分 :
|
||||
|
||||
1. 一个是新的功能特性
|
||||
2. 一个是语法糖的增加与优化
|
||||
3. GC 的改变
|
||||
|
||||
---
|
||||
|
||||
## 新的功能特性
|
||||
|
||||
## JDK9的模块化:
|
||||
|
||||
### **什么是模块化:**
|
||||
|
||||
简单理解,Java模块化就是将目前多个包(package)组成一个封装体,这个封装体有它的逻辑含义 ,同时也存在具体实例。和大家平常使用的二方包差不多.
|
||||
|
||||
它解决Java平台长期以来存在的一些核心问题,并提高其性能、安全性和可维护性。
|
||||
|
||||
- **更好的封装性:**在传统的Java应用中,即使是私有类也可以通过反射被外部访问,这对于保护内部API不被非法访问来说是一个问题。模块化通过引入模块作为一种新的Java程序组件,允许开发者显式地控制哪些包是对外公开的,哪些是模块内部私有的,从而提高了封装性。
|
||||
- **更小的运行时环境**:在模块化之前,Java运行时环境(JRE)是一个庞大的单体,包含了所有的Java标准库。这意味着即使一个简单的程序也需要加载整个JRE(**JDK9之前,rt.jar是个巨大的Java运行时类库,大概有60MB左右。JDK9将其拆分成90个模块左右**),这对于资源受限的环境(如小型设备或云计算环境)来说是不理想的。通过模块化,可以根据应用程序的实际需要,仅包含必要的模块,从而减小运行时环境的大小。
|
||||
- **更好的性能**:模块化有助于改进JVM的启动时间和运行时性能,因为JVM可以快速定位到需要加载的类和资源,减少了不必要的加载和处理。
|
||||
- **依赖管理**:在模块化系统中,每个模块都需要声明它所依赖的其他模块。这种显式的依赖声明使得依赖管理更加清晰和可控,有助于避免“JAR地狱”(即由于多个版本的JAR文件共存导致的依赖冲突问题)
|
||||
可以避免NoClassDefFoundError 问题.
|
||||
- **更好的安全性**:通过限制对敏感或内部API的访问,模块化有助于提高Java应用的安全性。这使得攻击者更难利用Java平台的内部API进行攻击。
|
||||
|
||||
> 这里应该是代码展示的,关于写法大家自行查找吧!哈哈哈~~~懒狗是这样的!!!
|
||||
|
||||
### **模块化带来的影响**
|
||||
|
||||
- **默认类加载器的升级 与 双亲委派的部分逻辑破坏
|
||||
**在JDK 9之前,Java的类和资源是基于类路径(Classpath)来加载的,这是一个线性的搜索路径,由JAR文件和目录组成。类加载器在类路径中按顺序搜索类或资源,直到找到为止。
|
||||
|
||||

|
||||
|
||||
JDK 9引入了模块化系统,增加了一个新的概念:模块路径(Module Path)。模块路径是模块化JAR文件(包含module-info.class的JAR)的集合,每个模块都定义了自己的名称、导出的包和对其他模块的依赖。这意味着类的加载现在不仅需要考虑类的名称,还需要考虑类所在的模块和模块之间的依赖关系。
|
||||
|
||||

|
||||
|
||||
- **部分反射逻辑被警告**
|
||||
|
||||
> WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by xxx.xxxx.test.demo.AAA to field java.util.Calendar.fields
|
||||
> WARNING: Please consider reporting this to the maintainers of com.jd.jsf.java.util.GregorianCalendar_$$_Template_1798100948_0
|
||||
> WARNING: Use --[illegal-access](https://www.zhihu.com/search?q=illegal-access&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A2989409247%7D)=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release
|
||||
|
||||
通过反射访问JDK模块内类的私有方法或属性,且当前模块并未开放指定类用于反射访问,就会出现以上告警。解决方式也必须使用模块化相关知识,可以使用遵循模块化之间的访问规则,也可以通过设置 –add-opens java.base/java.lang = ALL-UNNNAMED 破坏模块的封装性方式临时解决
|
||||
|
||||
## JDK14的record与JDK17的封闭类
|
||||
|
||||
### record
|
||||
|
||||
record 是 JDK 14 引入的关键字,用于声明不可变的数据类。它适用于存储纯粹的值类型数据,如接口传输数据、坐标点和只读的日志记录。与 lombok 相比,record 简化了定义纯粹数据类型的过程。由于 record 类是不可变的,成员变量只能设置一次且无法更改,无需提供显式的 setter () 方法。
|
||||
|
||||
**写法如下:**
|
||||
|
||||

|
||||
|
||||
**反编译后:**
|
||||
|
||||

|
||||
|
||||
**好处:**
|
||||
|
||||
1. 减少样板代码
|
||||
在引入记录类型之前,定义一个简单的数据载体类需要编写大量的样板代码,包括字段、构造函数、getter方法以及equals()、hashCode()和toString()方法。记录类型自动为所有字段生成这些方法,极大地减少了必须手动编写的代码量。
|
||||
2. 提升代码的可读性和可维护性
|
||||
3. 不可变性
|
||||
记录类型的实例是不可变的。这意味着一旦实例被创建,它的状态就不能改变。不可变性有助于创建更安全、更易于理解和维护的代码,尤其是在多线程环境中。
|
||||
4. 易于使用
|
||||
记录类型的语法非常简洁,使得定义新的数据载体类变得非常容易
|
||||
5. 促进函数式编程
|
||||
记录类型的不可变性和简洁性支持了函数式编程范式。在函数式编程中,不可变数据结构是核心概念之一,记录类型自然而然地融入了这种编程风格
|
||||
|
||||
### 封闭类(Sealed Classes)
|
||||
|
||||
对于继承能力的控制,Java很早就已经有一些了,主要是这两种方式:
|
||||
|
||||
1. final修饰类,这样类就无法被继承了
|
||||
2. package-private类(非public类),可以控制只能被同一个包下的类继承
|
||||
|
||||
但很显然,这两种限制方式的粒度都非常粗,如果有更精细化的限制需求的话,是很难实现的
|
||||
为了进一步增强限制能力,Java 17中的密封类增加了几个重要关键词:
|
||||
|
||||
- sealed:修饰类/接口,用来描述这个类/接口为密封类/接口
|
||||
- non-sealed:修饰类/接口,用来描述这个类/接口为非密封类/接口
|
||||
- permits:用在extends和implements之后,指定可以继承或实现的类
|
||||
|
||||

|
||||
|
||||
> **同时这些子类是需要使用final修饰不允许再次继承的**
|
||||
|
||||
**好处:**
|
||||
|
||||
1. 更精准的控制继承
|
||||
这种精确的控制有助于创建更加安全和可维护的代码库,因为它限制了未经授权的类继承,减少了意外扩展的可能性。
|
||||
2. 增强的模式匹配
|
||||
封闭类与Java中的模式匹配(如instanceof的模式匹配)结合得很好,使得模式匹配更加强大和表达性更强
|
||||
3. 改进的可读性和可维护性
|
||||
通过明确指定一个类或接口的所有子类,封闭类提高了代码的可读性和可维护性。开发者和维护者可以轻松地理解类的继承结构
|
||||
4. 促进不变性和线程安全
|
||||
封闭类通过限制继承,有助于设计不可变的数据结构,这对于创建线程安全的应用程序是非常重要的。
|
||||
|
||||
## 语法糖的增加与优化
|
||||
|
||||
## (JDK14)文本块与(JDK11)String方法的增强
|
||||
|
||||
### **文本块:**
|
||||
|
||||
> 类似于Python和Groovy,方便编写多行的字符串字面量。 Java13中已引入作为预览特性,Java 14中做了改进,引入了两个新的转义序列。可以使用 \s 转义序列来表示一个空格,可以使用反斜杠 \ 来避免在行尾插入换行字符
|
||||
|
||||
```text
|
||||
String text = """
|
||||
|
||||
"""
|
||||
```
|
||||
|
||||

|
||||
|
||||
### **String方法增强:**
|
||||
|
||||
> String str = "woshidage";
|
||||
> **boolean** isblank = str.isBlank(); //判断字符串是空白
|
||||
> **boolean** isempty = str.isEmpty(); //判断字符串是否为空
|
||||
> String result1 = str.strip(); //首位空白
|
||||
> String result2 = str.stripTrailing(); //去除尾部空白
|
||||
> String result3 = str.stripLeading(); //去除首部空白
|
||||
> String copyStr = str.repeat(2); //复制几遍字符串
|
||||
> **long** lineCount = str.lines().count(); //行数统计
|
||||
|
||||
## (JDK16)instanceof模式匹配 和 (JDK19 )try-with-resource改造
|
||||
|
||||
- **instanceof模式匹配**
|
||||
|
||||

|
||||
|
||||
- **try-with-resource**
|
||||
|
||||

|
||||
|
||||
## (JDK12)Switch 表达式扩展,可以有返回值
|
||||
|
||||
```text
|
||||
boolean isWeekend = switch (day) {
|
||||
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> false;
|
||||
case SATURDAY, SUNDAY -> true;
|
||||
default -> throw new IllegalStateException("Illegal day entry :: " + day);
|
||||
};
|
||||
```
|
||||
|
||||
## (JDK10)Var变量
|
||||
|
||||
> 从JDK 10开始,Java引入了var关键字,用于局部变量类型推断。这意味着编译器可以根据变量的初始值自动推断出变量的类型,从而允许开发者在声明局部变量时不必显式指定其类型。使用var不仅可以减少一些冗余的代码,还可以使代码看起来更简洁
|
||||
|
||||
**使用的基本规则:**
|
||||
|
||||
1. **只能用于局部变量**:var只能用于方法内部、构造器内部、初始化块内部或者for循环的局部变量。它不能用于类的成员变量、方法参数或者方法返回类型。
|
||||
2. **必须初始化**:使用var声明变量时,必须在声明时初始化变量。这是因为var依赖于变量的初始值来推断变量的类型。
|
||||
3. **单变量声明**:每个var声明只能声明一个变量。即,不能用一个var声明来同时声明多个变量。
|
||||
4. **不可用于null值**:不能使用var来声明一个初始值为null的变量,因为null没有足够的信息来让编译器推断出具体的类型。
|
||||
|
||||
**好处:**
|
||||
|
||||
1. **代码更简洁**:减少了声明变量时需要的类型冗余,使代码更加简洁易读。
|
||||
2. **提高开发效率**:在某些情况下,使用var可以减少编写和维护类型声明的工作量,特别是对于复杂的泛型类型。
|
||||
3. **保持类型安全**:尽管使用var不需要显式声明类型,但Java仍然是一种静态类型语言。编译器在编译时会推断出var变量的确切类型,保持了类型安全。
|
||||
|
||||
**注意事项:**
|
||||
|
||||
- **可读性**:过度使用var可能会降低代码的可读性,特别是当变量的类型不是从右侧的表达式中显而易见时。因此,推荐在类型明显可以从右侧表达式推断出来时使用var。
|
||||
- **代码维护**:在一些情况下,如果初始值的类型改变了,var变量的类型也会随之改变,这可能会导致一些意外的行为。因此,在使用var时需要注意代码的维护和变量类型的预期。
|
||||
|
||||
## GC 的改变
|
||||
|
||||
在JDK8->jdk17中,GC从常用的并行+CMS 升级成了G1 ,而后继续衍生了ZGC,并且G1从11到 17进行更多性能的优化
|
||||
|
||||
- **CMS**:主要用于老年代的垃圾回收。它通过并发标记和并发清除的方式来减少应用程序的停顿时间,适合对响应时间有高要求的应用场景。CMS的主要特点是低停顿时间,但它可能会导致较多的CPU资源占用和内存碎片问题 [[1]](https://www.zhihu.com/#ref_1)
|
||||
- **G1**:是JDK9开始作为默认GC,替代了之前的Parallel GC和CMS GC。G1通过将堆划分为多个区域(Regions)并优先回收价值最大的区域来减少停顿时间。G1的目标是提供一个平衡了吞吐量和停顿时间的垃圾收集器,适用于需要处理大量内存且要求较短停顿时间的应用
|
||||
- **ZGC**:是JDK15正式发布的垃圾回收器,是一种针对减少应用程序停顿时间设计的垃圾收集器,目标是在处理大型堆(数TB级别)时仍能保持低延迟。(染色体技术)ZGC通过并发和大部分工作的并行处理来实现这一目标,几乎消除了垃圾收集过程中的停顿时间。 [[2]](https://www.zhihu.com/#ref_2)
|
||||
|
||||
> G1与ZGC都是标记-复制
|
||||
> G1 的SWT 阶段为 初始标记,再标记,内存清理,复制转移阶段
|
||||
> ZGC的SWT阶段为初始标记,再标记,,初始转移阶段
|
||||
>
|
||||
> 常见GC ROOT:
|
||||
> **本地变量和输入参数(栈帧中的局部变量)**:活动线程的方法调用栈中的局部变量和输入参数。每个线程都有自己的栈,栈帧中的引用对象作为GC Roots。
|
||||
> **活动线程(Active Threads)**:Java程序中的所有活动线程都是GC Roots。这包括Java应用创建的线程以及JVM自身的线程。
|
||||
> **静态变量(Static Fields)**:类的静态字段直接附属于类,而类是由类加载器加载的。因此,所有的静态变量都是GC Roots,因为它们可以从加载它们的类加载器中访问。
|
||||
> **常量引用(Constant References**):被定义在方法区的常量引用也是GC Roots。例如,字符串常量池中的引用。
|
||||
> **JNI引用(JNI References)**:从本地代码(如C或C++代码)中通过Java本地接口(JNI)访问的对象。这些引用也被视为GCRoots,因为本地代码可能会持有Java对象的引用,这些对象必须被视为活动对象,直到本地代码释放它们。
|
||||
> **类加载器(Class Loaders**):类加载器本身也是GC Roots。由于类加载器可以加载类,而这些类又可能有静态字段引用其他对象,因此类加载器及其加载的类结构也被视为GC Roots。
|
||||
> **JVM内部结构**:例如,一些用于JVM内部处理或优化的数据结构,也可能作为GC Roots。
|
||||
|
||||
### **G1 11->17的变化点**
|
||||
|
||||
- **并行回收改进**:G1垃圾收集器在JDK 11之后的版本中继续优化了并行回收的效率。
|
||||
通过改进线程的利用率和减少同步开销,G1能够更快地完成垃圾回收,从而提高了应用程序的吞吐量。
|
||||
- **引入了并发线程数的自动调整**:为了更好地适应不同的工作负载和硬件配置,G1在后续版本中引入了并发线程数的自动调整机制。
|
||||
这意味着G1可以根据当前的系统负载动态调整用于垃圾回收的线程数,以达到最佳的回收效率。
|
||||
- **停顿时间改进**
|
||||
G1本身允许用户指定期望的最大停顿时间(Pause Time Goal),以此来控制垃圾收集(GC)过程中的停顿时间,使之不超过用户设定的阈值。为了实现这一目标,G1收集器采用了一个停顿时间预测模型,该模型负责预测执行特定垃圾收集活动所需的时间。基于这些预测,G1收集器决定在每次GC中应该完成多少工作,以便在清理内存的同时,尽量满足用户设定的停顿时间目标。
|
||||
|
||||
- 改进的停顿时间预测模型:G1收集器在JDK 17中进一步改进了其停顿时间预测模型,使得用户设定的停顿时间目标更加准确可靠。这通过更精确地估计回收区域的成本和收益来实现,从而更好地平衡回收工作量与停顿时间目标。
|
||||
|
||||
- 更精确的预测算法:改进了预测算法,使其能更准确地估计完成GC所需的时间。这包括更好地理解和预测不同类型的GC活动(如年轻代收集、混合收集)所需的时间。
|
||||
- 动态调整策略:G1收集器能够根据实际的GC性能和停顿时间目标动态调整其行为。如果发现实际的停顿时间与目标有较大偏差,G1会调整其内部参数,以便在未来的GC活动中更接近用户设定的目标。
|
||||
- 改进的工作量估计:G1收集器改进了对清理工作量(如回收区域的数量和大小)的估计方法。这使得G1能够更好地决定在达到停顿时间目标的同时,可以完成多少实际的垃圾收集工作。
|
||||
- 反馈机制:引入了更强的反馈机制,根据前一次GC的表现调整下一次GC的策略。这种自适应调整有助于在应用运行期间不断优化GC性能。
|
||||
|
||||
- **增强的卡表处理**:G1在处理卡表(用于记录跨代引用的数据结构)时进行了优化,减少了卡表处理对停顿时间的影响,特别是在并发标记阶段。
|
||||
|
||||
- 更快的卡表扫描:通过优化卡表的数据结构和扫描算法,减少了在垃圾收集过程中处理卡表所需的时间。
|
||||
- 并发卡表清理:在早期版本的G1中,卡表的清理可能在某些阶段需要停顿应用线程。增强后的G1可以更多地并发执行卡表清理工作,减少了因卡表处理导致的停顿时间。
|
||||
- 精确的卡表标记:通过提高卡表标记的精确度,减少了误标(即错误地将某个区域标记为包含跨代引用的情况),从而减少了不必要的垃圾收集工作量。
|
||||
- 动态卡表调整:G1垃圾收集器可以根据应用程序的实际运行情况动态调整卡表的大小和行为,以达到更好的性能平衡。
|
||||
|
||||
- **内存管理增强**
|
||||
字符串去重优化:字符串去重是一种减少Java堆内存使用的技术,通过消除重复的字符串实例来节省空间。从JDK 11到JDK 17,G1收集器对字符串去重机制进行了优化,提高了去重效率和节省了更多的内存。(JDK 8u20开始启用)
|
||||
|
||||
- 改进的去重逻辑:优化了决定哪些字符串应该被去重的逻辑。通过更精确地分析字符串的使用模式和生命周期,JVM可以更智能地选择去重的目标,从而减少不必要的去重操作,提高去重效率。
|
||||
- 去重过程的性能优化
|
||||
|
||||
- 并发去重:在早期版本中,字符串去重的过程可能会影响垃圾收集的性能。通过优化,使得去重操作更加高效,并尽可能地并发执行,减少对应用程序的影响。
|
||||
- 减少内存占用:通过优化去重数据结构的内存布局,减少了去重本身对内存的占用。这意味着去重功能不仅可以节省更多的应用内存,而且去重机制本身也变得更加轻量。
|
||||
|
||||
- 增强的监控和诊断
|
||||
|
||||
- 改进的监控和日志记录:增强了与字符串去重相关的监控和日志记录功能。这使得开发者和运维人员可以更容易地观察去重的效果,评估内存节省量,并调整去重策略。
|
||||
|
||||
- 自动化和易用性改进
|
||||
|
||||
- 更好的默认行为:在某些版本中,改进了字符串去重的默认行为,使其在默认配置下能够提供更好的内存节省效果,而无需用户进行复杂的配置。
|
||||
|
||||
- **Elastic Heap(实验阶段)**:JDK 17引入的Elastic Heap特性允许Java堆在运行时动态调整大小,这对于云环境和容器环境中的内存管理非常有用。这项特性可以帮助应用根据实际使用情况自动优化堆大小,从而提高资源利用率。
|
||||
- **对新技术的支持**
|
||||
|
||||
- **增强的JFR(Java Flight Recorder)集成**:G1垃圾收集器在JDK 17中增强了与JFR的集成,提供了更丰富的垃圾收集监控和诊断信息。这使得开发者和运维人员能够更深入地了解G1的行为和性能特点,从而更有效地调优和解决问题。
|
||||
|
||||
### 针对G1的配置我们需要注意:
|
||||
|
||||
- 不要轻易限制 年轻代region的大小,默认情况下年轻代最小为5% 最大 60%
|
||||
G1 GC算法在运行时修改了年轻代的大小,以达到其暂停时间目标。如果显式地定义了年轻代的大小,则将无法达到暂停时间目标。
|
||||
- -XX:MaxGCPauseMillis=200 我们可以通过这个配置控制我们的 SWT的时间,防止SWT时间过长
|
||||
- -XX:ParallelGCThreads=n 为SWT时,并发标记的线程数,这里一般 n=核数
|
||||
- **-XX:+ UseStringDeduplication 可以设置消除字符串重复**
|
||||
是一个案例研究,显示了 Spring Boot框架浪费的内存。内存浪费的主要原因之一是字符串的重复。最近的一项研究表明,应用程序内存的13.5%包含重复的字符串。当我们传递“ -XX:+ UseStringDeduplication”参数时,G1 GC提供了消除重复字符串的选项
|
||||
+369
@@ -0,0 +1,369 @@
|
||||
|
||||
# Lazygit 按键绑定
|
||||
|
||||
_Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
|
||||
## 全局键绑定
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-r> `` | 切换到最近的仓库 | |
|
||||
| `` <pgup> (fn+up/shift+k) `` | 向上滚动主面板 | |
|
||||
| `` <pgdown> (fn+down/shift+j) `` | 向下滚动主面板 | |
|
||||
| `` @ `` | 打开命令日志菜单 | View options for the command log e.g. show/hide the command log and focus the command log. |
|
||||
| `` P `` | 推送 | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
|
||||
| `` p `` | 拉取 | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
|
||||
| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename. |
|
||||
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
|
||||
| `` } `` | 扩大差异视图中显示的上下文范围 | Increase the amount of the context shown around changes in the diff view. |
|
||||
| `` { `` | 缩小差异视图中显示的上下文范围 | Decrease the amount of the context shown around changes in the diff view. |
|
||||
| `` : `` | 执行自定义命令 | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
|
||||
| `` <c-p> `` | 查看自定义补丁选项 | |
|
||||
| `` m `` | 查看 合并/变基 选项 | View options to abort/continue/skip the current merge/rebase. |
|
||||
| `` R `` | 刷新 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
|
||||
| `` + `` | 下一屏模式(正常/半屏/全屏) | |
|
||||
| `` _ `` | 上一屏模式 | |
|
||||
| `` ? `` | 打开菜单 | |
|
||||
| `` <c-s> `` | 查看按路径过滤选项 | View options for filtering the commit log, so that only commits matching the filter are shown. |
|
||||
| `` W `` | 打开 diff 菜单 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
|
||||
| `` <c-e> `` | 打开 diff 菜单 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
|
||||
| `` q `` | 退出 | |
|
||||
| `` <esc> `` | 取消 | |
|
||||
| `` <c-w> `` | 切换是否在差异视图中显示空白字符差异 | Toggle whether or not whitespace changes are shown in the diff view. |
|
||||
| `` z `` | (通过 reflog)撤销「实验功能」 | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
|
||||
| `` <c-z> `` | (通过 reflog)重做「实验功能」 | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
|
||||
|
||||
## 列表面板导航
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` , `` | 上一页 | |
|
||||
| `` . `` | 下一页 | |
|
||||
| `` < `` | 滚动到顶部 | |
|
||||
| `` > `` | 滚动到底部 | |
|
||||
| `` v `` | 切换拖动选择 | |
|
||||
| `` <s-down> `` | Range select down | |
|
||||
| `` <s-up> `` | Range select up | |
|
||||
| `` / `` | 开始搜索 | |
|
||||
| `` H `` | 向左滚动 | |
|
||||
| `` L `` | 向右滚动 | |
|
||||
| `` ] `` | 下一个标签 | |
|
||||
| `` [ `` | 上一个标签 | |
|
||||
|
||||
## Reflog 页面
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
|
||||
| `` <space> `` | 检出 | Checkout the selected commit as a detached HEAD. |
|
||||
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
|
||||
| `` o `` | 在浏览器中打开提交 | |
|
||||
| `` n `` | 从提交创建新分支 | |
|
||||
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | 复制提交(拣选) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <enter> `` | 查看提交 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Worktrees
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` n `` | New worktree | |
|
||||
| `` <space> `` | Switch | Switch to the selected worktree. |
|
||||
| `` o `` | Open in editor | |
|
||||
| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## 分支页面
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将分支名称复制到剪贴板 | |
|
||||
| `` i `` | 显示 git-flow 选项 | |
|
||||
| `` <space> `` | 检出 | Checkout selected item. |
|
||||
| `` n `` | 新分支 | |
|
||||
| `` o `` | 创建抓取请求 | |
|
||||
| `` O `` | 创建抓取请求选项 | |
|
||||
| `` <c-y> `` | 将抓取请求 URL 复制到剪贴板 | |
|
||||
| `` c `` | 按名称检出 | Checkout by name. In the input box you can enter '-' to switch to the last branch. |
|
||||
| `` F `` | 强制检出 | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
|
||||
| `` d `` | Delete | View delete options for local/remote branch. |
|
||||
| `` r `` | 将已检出的分支变基到该分支 | Rebase the checked-out branch onto the selected branch. |
|
||||
| `` M `` | 合并到当前检出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) |
|
||||
| `` f `` | 从上游快进此分支 | Fast-forward selected branch from its upstream. |
|
||||
| `` T `` | 创建标签 | |
|
||||
| `` s `` | Sort order | |
|
||||
| `` g `` | 查看重置选项 | |
|
||||
| `` R `` | 重命名分支 | |
|
||||
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <enter> `` | 查看提交 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## 子提交
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
|
||||
| `` <space> `` | 检出 | Checkout the selected commit as a detached HEAD. |
|
||||
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
|
||||
| `` o `` | 在浏览器中打开提交 | |
|
||||
| `` n `` | 从提交创建新分支 | |
|
||||
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | 复制提交(拣选) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <enter> `` | 查看提交的文件 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | 开始搜索 | |
|
||||
|
||||
## 子模块
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将子模块名称复制到剪贴板 | |
|
||||
| `` <enter> `` | Enter | 输入子模块 |
|
||||
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. |
|
||||
| `` u `` | Update | 更新子模块 |
|
||||
| `` n `` | 添加新的子模块 | |
|
||||
| `` e `` | 更新子模块 URL | |
|
||||
| `` i `` | Initialize | 初始化子模块 |
|
||||
| `` b `` | 查看批量子模块选项 | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## 提交
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
|
||||
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
|
||||
| `` b `` | 查看二分查找选项 | |
|
||||
| `` s `` | 压缩 | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
|
||||
| `` f `` | 修正(fixup) | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
|
||||
| `` r `` | 改写提交 | Reword the selected commit's message. |
|
||||
| `` R `` | 使用编辑器重命名提交 | |
|
||||
| `` d `` | 删除提交 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
|
||||
| `` e `` | Edit (start interactive rebase) | 编辑提交 |
|
||||
| `` i `` | Start interactive rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.
|
||||
If you would instead like to start an interactive rebase from the selected commit, press `e`. |
|
||||
| `` p `` | Pick | 选择提交(变基过程中) |
|
||||
| `` F `` | 为此提交创建修正 | 创建修正提交 |
|
||||
| `` S `` | Apply fixup commits | 压缩在所选提交之上的所有“fixup!”提交(自动压缩) |
|
||||
| `` <c-j> `` | 下移提交 | |
|
||||
| `` <c-k> `` | 上移提交 | |
|
||||
| `` V `` | 粘贴提交(拣选) | |
|
||||
| `` B `` | Mark as base commit for rebase | Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command. |
|
||||
| `` A `` | Amend | 用已暂存的更改来修补提交 |
|
||||
| `` a `` | Amend commit attribute | Set/Reset commit author or set co-author. |
|
||||
| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. |
|
||||
| `` T `` | 标签提交 | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. |
|
||||
| `` <c-l> `` | 打开日志菜单 | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. |
|
||||
| `` <space> `` | 检出 | Checkout the selected commit as a detached HEAD. |
|
||||
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
|
||||
| `` o `` | 在浏览器中打开提交 | |
|
||||
| `` n `` | 从提交创建新分支 | |
|
||||
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | 复制提交(拣选) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <enter> `` | 查看提交的文件 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | 开始搜索 | |
|
||||
|
||||
## 提交文件
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将文件名复制到剪贴板 | |
|
||||
| `` c `` | 检出 | 检出文件 |
|
||||
| `` d `` | Remove | 放弃对此文件的提交更改 |
|
||||
| `` o `` | 打开文件 | Open file in default application. |
|
||||
| `` e `` | Edit | Open file in external editor. |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <space> `` | 补丁中包含的切换文件 | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
|
||||
| `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
|
||||
| `` <enter> `` | 输入文件以将所选行添加到补丁中(或切换目录折叠) | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. |
|
||||
| `` ` `` | 切换文件树视图 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. |
|
||||
| `` / `` | 开始搜索 | |
|
||||
|
||||
## 提交讯息
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <enter> `` | 确认 | |
|
||||
| `` <esc> `` | 关闭 | |
|
||||
|
||||
## 文件
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将文件名复制到剪贴板 | |
|
||||
| `` <space> `` | 切换暂存状态 | Toggle staged for selected file. |
|
||||
| `` <c-b> `` | Filter files by status | |
|
||||
| `` y `` | Copy to clipboard | |
|
||||
| `` c `` | 提交更改 | Commit staged changes. |
|
||||
| `` w `` | 提交更改而无需预先提交钩子 | |
|
||||
| `` A `` | 修补最后一次提交 | |
|
||||
| `` C `` | 提交更改(使用编辑器编辑提交信息) | |
|
||||
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
|
||||
| `` e `` | Edit | Open file in external editor. |
|
||||
| `` o `` | 打开文件 | Open file in default application. |
|
||||
| `` i `` | 忽略文件 | |
|
||||
| `` r `` | 刷新文件 | |
|
||||
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. |
|
||||
| `` S `` | 查看贮藏选项 | View stash options (e.g. stash all, stash staged, stash unstaged). |
|
||||
| `` a `` | 切换所有文件的暂存状态 | Toggle staged/unstaged for all files in working tree. |
|
||||
| `` <enter> `` | 暂存单个 块/行 用于文件, 或 折叠/展开 目录 | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. |
|
||||
| `` d `` | 查看'放弃更改'选项 | View options for discarding changes to the selected file. |
|
||||
| `` g `` | 查看上游重置选项 | |
|
||||
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). |
|
||||
| `` ` `` | 切换文件树视图 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` M `` | 打开外部合并工具 (git mergetool) | Run `git mergetool`. |
|
||||
| `` f `` | 抓取 | Fetch changes from remote. |
|
||||
| `` / `` | 开始搜索 | |
|
||||
|
||||
## 构建补丁中
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <left> `` | 选择上一个区块 | |
|
||||
| `` <right> `` | 选择下一个区块 | |
|
||||
| `` v `` | 切换拖动选择 | |
|
||||
| `` a `` | 切换选择区块 | Toggle hunk selection mode. |
|
||||
| `` <c-o> `` | 将选中文本复制到剪贴板 | |
|
||||
| `` o `` | 打开文件 | Open file in default application. |
|
||||
| `` e `` | 编辑文件 | Open file in external editor. |
|
||||
| `` <space> `` | 添加/移除 行到补丁 | |
|
||||
| `` <esc> `` | 退出逐行模式 | |
|
||||
| `` / `` | 开始搜索 | |
|
||||
|
||||
## 标签页面
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | 检出 | Checkout the selected tag tag as a detached HEAD. |
|
||||
| `` n `` | 创建标签 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
|
||||
| `` d `` | Delete | View delete options for local/remote tag. |
|
||||
| `` P `` | 推送标签 | Push the selected tag to a remote. You'll be prompted to select a remote. |
|
||||
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <enter> `` | 查看提交 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## 正在合并
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | 选中区块 | |
|
||||
| `` b `` | 选中所有区块 | |
|
||||
| `` <up> `` | 选择顶部块 | |
|
||||
| `` <down> `` | 选择底部块 | |
|
||||
| `` <left> `` | 选择上一个冲突 | |
|
||||
| `` <right> `` | 选择下一个冲突 | |
|
||||
| `` z `` | 撤销 | Undo last merge conflict resolution. |
|
||||
| `` e `` | 编辑文件 | Open file in external editor. |
|
||||
| `` o `` | 打开文件 | Open file in default application. |
|
||||
| `` M `` | 打开外部合并工具 (git mergetool) | Run `git mergetool`. |
|
||||
| `` <esc> `` | 返回文件面板 | |
|
||||
|
||||
## 正在暂存
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <left> `` | 选择上一个区块 | |
|
||||
| `` <right> `` | 选择下一个区块 | |
|
||||
| `` v `` | 切换拖动选择 | |
|
||||
| `` a `` | 切换选择区块 | Toggle hunk selection mode. |
|
||||
| `` <c-o> `` | 将选中文本复制到剪贴板 | |
|
||||
| `` <space> `` | 切换暂存状态 | 切换行暂存状态 |
|
||||
| `` d `` | 取消变更 (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. |
|
||||
| `` o `` | 打开文件 | Open file in default application. |
|
||||
| `` e `` | 编辑文件 | Open file in external editor. |
|
||||
| `` <esc> `` | 返回文件面板 | |
|
||||
| `` <tab> `` | 切换到其他面板 | Switch to other view (staged/unstaged changes). |
|
||||
| `` E `` | Edit hunk | Edit selected hunk in external editor. |
|
||||
| `` c `` | 提交更改 | Commit staged changes. |
|
||||
| `` w `` | 提交更改而无需预先提交钩子 | |
|
||||
| `` C `` | 提交更改(使用编辑器编辑提交信息) | |
|
||||
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
|
||||
| `` / `` | 开始搜索 | |
|
||||
|
||||
## 正常
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` mouse wheel down (fn+up) `` | 向下滚动 | |
|
||||
| `` mouse wheel up (fn+down) `` | 向上滚动 | |
|
||||
|
||||
## 状态
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` o `` | 打开配置文件 | Open file in default application. |
|
||||
| `` e `` | 编辑配置文件 | Open file in external editor. |
|
||||
| `` u `` | 检查更新 | |
|
||||
| `` <enter> `` | 切换到最近的仓库 | |
|
||||
| `` a `` | 显示所有分支的日志 | |
|
||||
|
||||
## 确认面板
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <enter> `` | 确认 | |
|
||||
| `` <esc> `` | 关闭 | |
|
||||
|
||||
## 菜单
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <enter> `` | 执行 | |
|
||||
| `` <esc> `` | 关闭 | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## 贮藏
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | 应用 | Apply the stash entry to your working directory. |
|
||||
| `` g `` | 应用并删除 | Apply the stash entry to your working directory and remove the stash entry. |
|
||||
| `` d `` | 删除 | Remove the stash entry from the stash list. |
|
||||
| `` n `` | 新分支 | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. |
|
||||
| `` r `` | Rename stash | |
|
||||
| `` <enter> `` | 查看提交的文件 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## 远程分支
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将分支名称复制到剪贴板 | |
|
||||
| `` <space> `` | 检出 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
|
||||
| `` n `` | 新分支 | |
|
||||
| `` M `` | 合并到当前检出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) |
|
||||
| `` r `` | 将已检出的分支变基到该分支 | Rebase the checked-out branch onto the selected branch. |
|
||||
| `` d `` | Delete | Delete the remote branch from the remote. |
|
||||
| `` u `` | Set as upstream | 设置为检出分支的上游 |
|
||||
| `` s `` | Sort order | |
|
||||
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <enter> `` | 查看提交 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## 远程页面
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <enter> `` | View branches | |
|
||||
| `` n `` | 添加新的远程仓库 | |
|
||||
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. |
|
||||
| `` e `` | Edit | 编辑远程仓库 |
|
||||
| `` f `` | 抓取 | 抓取远程仓库 |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
@@ -0,0 +1,735 @@
|
||||
---
|
||||
title: "7个Shell服务器巡检脚本|新手直接复制可用(附详细步骤)"
|
||||
source: "https://mp.weixin.qq.com/s/DEqH_oRSVJF_dLcl1sJpog"
|
||||
author:
|
||||
- "[[Python小甲鱼]]"
|
||||
published:
|
||||
created: 2025-10-31
|
||||
description: "本文整理了7个生产级Shell巡检脚本,覆盖系统健康、磁盘、网络、进程、安全、数据库、批量巡检场景。"
|
||||
tags:
|
||||
- "clippings"
|
||||
---
|
||||
原创 Python小甲鱼 *2025年10月28日 07:01*
|
||||
|
||||
本文整理了7个生产级Shell巡检脚本,覆盖系统健康、磁盘、网络、进程、安全、数据库、批量巡检场景。每个脚本都标注了 **配置修改点** 、 **使用步骤** 和 **新手避坑提示** ,复制代码就能用,适合运维新手快速上手。
|
||||
|
||||
## 脚本总览:按需求选脚本
|
||||
|
||||
| 脚本序号 | 脚本名称 | 核心用途 | 适用场景 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | 系统健康检查脚本 | 检查CPU、内存、磁盘、网络、进程等基础资源 | 日常服务器健康巡检(每天1次) |
|
||||
| 2 | 磁盘空间深度检查脚本 | 定位大文件、日志堆积、Inode占用问题 | 磁盘满预警、清理前分析 |
|
||||
| 3 | 网络连接检查脚本 | 检查端口监听、TCP连接、网络连通性 | 网络故障排查、端口异常检测 |
|
||||
| 4 | 应用进程健康检查脚本 | 监控关键进程、端口、HTTP接口响应 | 核心业务(Nginx/MySQL/Redis)监控 |
|
||||
| 5 | 安全基线检查脚本 | 检查账户安全、SSH配置、文件权限 | 安全合规审计、服务器加固 |
|
||||
| 6 | MySQL数据库检查脚本 | 检查连接数、慢查询、主从复制状态 | MySQL实例健康监控 |
|
||||
| 7 | 批量服务器巡检脚本 | 并发巡检多台服务器,生成HTML汇总报告 | 多服务器集群(10台以上)管理 |
|
||||
|
||||
## 脚本1:系统健康状态全面检查(最常用)
|
||||
|
||||
### 脚本用途
|
||||
|
||||
日常巡检核心脚本,一键检查服务器CPU、内存、磁盘、网络、进程等关键指标,生成带颜色的报告,异常指标自动标黄/红。
|
||||
|
||||
### 核心功能
|
||||
|
||||
- • 收集系统基本信息(主机名、内核、运行时长)
|
||||
- • 检查CPU使用率+负载,显示TOP5高CPU进程
|
||||
- • 检查内存/swap使用,显示TOP5高内存进程
|
||||
- • 检查磁盘/Inode使用率,定位大目录
|
||||
- • 检查网络接口、TCP连接状态(TIME\_WAIT过多预警)
|
||||
- • 检查僵尸进程、关键服务(sshd/crond)运行状态
|
||||
|
||||
### 使用步骤
|
||||
|
||||
1. 1\. 复制下方代码,保存为 `system_health_check.sh`
|
||||
2. 2\. 赋予执行权限: `chmod +x system_health_check.sh`
|
||||
3. 3\. 执行脚本:`./system_health_check.sh` (需root权限)
|
||||
4. 4\. 定时执行(每天早8点): `echo "0 8 * * * /path/to/system_health_check.sh" | crontab -`
|
||||
|
||||
### 完整代码(新手注意修改配置区)
|
||||
|
||||
```
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
# 脚本名称: system_health_check.sh
|
||||
# 功能描述: 系统健康状态全面检查(新手用前改配置区!)
|
||||
# 版本信息: v2.0
|
||||
################################################################################
|
||||
|
||||
# -------------------------- 新手必改:配置区 --------------------------
|
||||
HOSTNAME=$(hostname)
|
||||
DATE=$(date +"%Y-%m-%d %H:%M:%S")
|
||||
# 报告保存路径(不用改,默认存/var/log)
|
||||
REPORT_FILE="/var/log/system_check_$(date +%Y%m%d_%H%M%S).log"
|
||||
|
||||
# 告警阈值(可根据服务器配置调整)
|
||||
CPU_WARNING=80 # CPU使用率告警阈值(%)
|
||||
MEM_WARNING=85 # 内存使用率告警阈值(%)
|
||||
DISK_WARNING=85 # 磁盘使用率告警阈值(%)
|
||||
LOAD_WARNING=4 # 系统负载阈值(CPU核心数*2,比如4核设8)
|
||||
INODE_WARNING=80 # Inode使用率告警阈值(%)
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# 颜色定义(不用改)
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # 重置颜色
|
||||
|
||||
# 日志函数(不用改)
|
||||
log() { echo "[$(date +"%Y-%m-%d %H:%M:%S")] $1" | tee -a "$REPORT_FILE"; }
|
||||
log_section() { echo -e "\n============================================================" | tee -a "$REPORT_FILE"; echo " $1" | tee -a "$REPORT_FILE"; echo "============================================================" | tee -a "$REPORT_FILE"; }
|
||||
log_warning() { echo -e "${YELLOW}[WARNING] $1${NC}" | tee -a "$REPORT_FILE"; }
|
||||
log_error() { echo -e "${RED}[ERROR] $1${NC}" | tee -a "$REPORT_FILE"; }
|
||||
log_ok() { echo -e "${GREEN}[OK] $1${NC}" | tee -a "$REPORT_FILE"; }
|
||||
|
||||
# 1. 系统基本信息(不用改)
|
||||
check_basic_info() {
|
||||
log_section "1. 系统基本信息"
|
||||
log "主机名: $HOSTNAME"
|
||||
log "检查时间: $DATE"
|
||||
log "系统版本: $(cat /etc/redhat-release 2>/dev/null || cat /etc/issue | head -1)"
|
||||
log "内核版本: $(uname -r)"
|
||||
log "系统架构: $(uname -m)"
|
||||
log "运行时长: $(uptime | awk -F'up ' '{print $2}' | awk -F',' '{print $1}')"
|
||||
log "当前用户: $(whoami)"
|
||||
log "登录用户数: $(who | wc -l)"
|
||||
}
|
||||
|
||||
# 2. CPU检查(不用改)
|
||||
check_cpu() {
|
||||
log_section "2. CPU使用率检查"
|
||||
CPU_CORES=$(grep -c ^processor /proc/cpuinfo)
|
||||
log "CPU核心数: $CPU_CORES"
|
||||
|
||||
# 获取CPU使用率(5秒平均值,更准确)
|
||||
CPU_IDLE=$(top -bn2 -d 1 | grep "Cpu(s)" | tail -1 | awk '{print $8}' | cut -d'%' -f1)
|
||||
CPU_USAGE=$(echo "scale=2; 100 - $CPU_IDLE" | bc)
|
||||
log "CPU使用率: ${CPU_USAGE}%"
|
||||
|
||||
if (( $(echo "$CPU_USAGE > $CPU_WARNING" | bc -l) )); then
|
||||
log_warning "CPU使用率超过${CPU_WARNING}%,当前${CPU_USAGE}%"
|
||||
log "TOP 5 CPU消耗进程:"
|
||||
ps aux | sort -rn -k3 | head -5 | awk '{printf " PID: %-8s User: %-10s CPU: %-6s CMD: %s\n", $2,$1,$3,$11}' | tee -a "$REPORT_FILE"
|
||||
else
|
||||
log_ok "CPU使用率正常: ${CPU_USAGE}%"
|
||||
fi
|
||||
|
||||
# 系统负载检查
|
||||
LOAD_1=$(uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | xargs)
|
||||
LOAD_5=$(uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $2}' | xargs)
|
||||
LOAD_15=$(uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $3}' | xargs)
|
||||
log "系统负载: 1分钟=${LOAD_1}, 5分钟=${LOAD_5}, 15分钟=${LOAD_15}"
|
||||
|
||||
# 负载阈值=CPU核心数*2(比如4核设8)
|
||||
LOAD_THRESHOLD=$(echo "$CPU_CORES * 2" | bc)
|
||||
if (( $(echo "$LOAD_1 > $LOAD_THRESHOLD" | bc -l) )); then
|
||||
log_warning "系统负载过高!1分钟负载${LOAD_1}超过阈值${LOAD_THRESHOLD}"
|
||||
fi
|
||||
}
|
||||
|
||||
# 3. 内存检查(不用改)
|
||||
check_memory() {
|
||||
log_section "3. 内存使用检查"
|
||||
MEM_TOTAL=$(free -m | awk 'NR==2{print $2}')
|
||||
MEM_USED=$(free -m | awk 'NR==2{print $3}')
|
||||
MEM_AVAILABLE=$(free -m | awk 'NR==2{print $7}')
|
||||
MEM_USAGE=$(echo "scale=2; $MEM_USED / $MEM_TOTAL * 100" | bc)
|
||||
|
||||
log "内存总量: ${MEM_TOTAL}MB"
|
||||
log "已用内存: ${MEM_USED}MB"
|
||||
log "可用内存: ${MEM_AVAILABLE}MB"
|
||||
log "内存使用率: ${MEM_USAGE}%"
|
||||
|
||||
if (( $(echo "$MEM_USAGE > $MEM_WARNING" | bc -l) )); then
|
||||
log_warning "内存使用率超过${MEM_WARNING}%,当前${MEM_USAGE}%"
|
||||
log "TOP 5 内存消耗进程:"
|
||||
ps aux | sort -rn -k4 | head -5 | awk '{printf " PID: %-8s User: %-10s MEM: %-6s CMD: %s\n", $2,$1,$4,$11}' | tee -a "$REPORT_FILE"
|
||||
else
|
||||
log_ok "内存使用率正常: ${MEM_USAGE}%"
|
||||
fi
|
||||
|
||||
# Swap检查(超过100MB预警)
|
||||
SWAP_TOTAL=$(free -m | awk 'NR==3{print $2}')
|
||||
SWAP_USED=$(free -m | awk 'NR==3{print $3}')
|
||||
log "Swap总量: ${SWAP_TOTAL}MB"
|
||||
log "Swap使用: ${SWAP_USED}MB"
|
||||
if [ "$SWAP_TOTAL" -gt 0 ] && [ "$SWAP_USED" -gt 100 ]; then
|
||||
log_warning "Swap使用量较高: ${SWAP_USED}MB,可能存在内存压力"
|
||||
fi
|
||||
}
|
||||
|
||||
# 4. 磁盘检查(不用改)
|
||||
check_disk() {
|
||||
log_section "4. 磁盘使用检查"
|
||||
log "磁盘分区使用情况:"
|
||||
df -h | grep -vE '^Filesystem|tmpfs|cdrom|loop' | awk '{print " " $0}' | tee -a "$REPORT_FILE"
|
||||
|
||||
# 磁盘使用率告警
|
||||
HAS_DISK_WARNING=0
|
||||
while read line; do
|
||||
USAGE=$(echo $line | awk '{print $5}' | sed 's/%//')
|
||||
MOUNT=$(echo $line | awk '{print $6}')
|
||||
if [ "$USAGE" -gt "$DISK_WARNING" ]; then
|
||||
log_warning "磁盘分区 $MOUNT 使用率${USAGE}%超过阈值${DISK_WARNING}%"
|
||||
HAS_DISK_WARNING=1
|
||||
log " $MOUNT 分区占用空间最大的5个目录:"
|
||||
du -sh ${MOUNT}/* 2>/dev/null | sort -rh | head -5 | awk '{print " " $0}' | tee -a "$REPORT_FILE"
|
||||
fi
|
||||
done < <(df -h | grep -vE '^Filesystem|tmpfs|cdrom|loop')
|
||||
|
||||
if [ $HAS_DISK_WARNING -eq 0 ]; then
|
||||
log_ok "所有磁盘分区使用率正常"
|
||||
fi
|
||||
|
||||
# Inode检查(避免小文件占满磁盘)
|
||||
log -e "\nInode使用情况:"
|
||||
df -i | grep -vE '^Filesystem|tmpfs|cdrom|loop' | awk '{print " " $0}' | tee -a "$REPORT_FILE"
|
||||
while read line; do
|
||||
INODE_USAGE=$(echo $line | awk '{print $5}' | sed 's/%//')
|
||||
MOUNT=$(echo $line | awk '{print $6}')
|
||||
if [ "$INODE_USAGE" -gt "$INODE_WARNING" ]; then
|
||||
log_warning "分区 $MOUNT 的Inode使用率${INODE_USAGE}%超过阈值${INODE_WARNING}%"
|
||||
fi
|
||||
done < <(df -i | grep -vE '^Filesystem|tmpfs|cdrom|loop')
|
||||
}
|
||||
|
||||
# 5. 网络检查(不用改)
|
||||
check_network() {
|
||||
log_section "5. 网络状态检查"
|
||||
log "网络接口状态:"
|
||||
ip -br addr | awk '{print " " $0}' | tee -a "$REPORT_FILE"
|
||||
|
||||
# TCP连接统计
|
||||
log -e "\nTCP连接状态统计:"
|
||||
netstat -an | awk '/^tcp/ {print $6}' | sort | uniq -c | sort -rn | awk '{print " " $0}' | tee -a "$REPORT_FILE"
|
||||
|
||||
# TIME_WAIT过多预警(超过5000)
|
||||
TIME_WAIT_COUNT=$(netstat -an | grep TIME_WAIT | wc -l)
|
||||
log "TIME_WAIT连接数: $TIME_WAIT_COUNT"
|
||||
if [ "$TIME_WAIT_COUNT" -gt 5000 ]; then
|
||||
log_warning "TIME_WAIT连接数过多: $TIME_WAIT_COUNT(可优化TCP参数)"
|
||||
fi
|
||||
|
||||
# 监听端口检查
|
||||
log -e "\n当前监听端口:"
|
||||
netstat -tuln | grep LISTEN | awk '{print " " $0}' | tee -a "$REPORT_FILE"
|
||||
}
|
||||
|
||||
# 6. 进程和服务检查(不用改)
|
||||
check_processes() {
|
||||
log_section "6. 进程和服务检查"
|
||||
PROCESS_COUNT=$(ps aux | wc -l)
|
||||
log "当前进程总数: $PROCESS_COUNT"
|
||||
|
||||
# 僵尸进程检查(死进程没清理)
|
||||
ZOMBIE_COUNT=$(ps aux | awk '{print $8}' | grep -c Z)
|
||||
log "僵尸进程数: $ZOMBIE_COUNT"
|
||||
if [ "$ZOMBIE_COUNT" -gt 0 ]; then
|
||||
log_warning "发现僵尸进程!"
|
||||
ps aux | grep 'Z' | grep -v grep | awk '{print " PID: " $2 " PPID: " $3 " CMD: " $11}' | tee -a "$REPORT_FILE"
|
||||
fi
|
||||
|
||||
# 关键服务检查(新手可加自己的服务,比如nginx/mysql)
|
||||
log -e "\n关键服务状态检查:"
|
||||
CRITICAL_SERVICES=("sshd" "crond" "rsyslog") # 可添加服务,如"nginx" "mysqld"
|
||||
for service in "${CRITICAL_SERVICES[@]}"; do
|
||||
if systemctl is-active --quiet $service 2>/dev/null; then
|
||||
log_ok " $service: 运行中"
|
||||
else
|
||||
# 兼容非systemd系统(如CentOS 6)
|
||||
if ps aux | grep -v grep | grep -q $service; then
|
||||
log_ok " $service: 运行中"
|
||||
else
|
||||
log_error " $service: 未运行(需手动启动)"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# 7. 系统日志检查(不用改)
|
||||
check_logs() {
|
||||
log_section "7. 系统日志检查"
|
||||
log "最近1小时系统错误日志:"
|
||||
if [ -f /var/log/messages ]; then
|
||||
ERROR_COUNT=$(grep -i "error\|fail\|critical" /var/log/messages | tail -20 | wc -l)
|
||||
if [ "$ERROR_COUNT" -gt 0 ]; then
|
||||
log_warning "发现 $ERROR_COUNT 条错误日志"
|
||||
grep -i "error\|fail\|critical" /var/log/messages | tail -10 | awk '{print " " $0}' | tee -a "$REPORT_FILE"
|
||||
else
|
||||
log_ok "无严重错误日志"
|
||||
fi
|
||||
fi
|
||||
|
||||
# OOM检查(内存溢出导致进程被杀)
|
||||
log -e "\nOOM(内存溢出)检查:"
|
||||
OOM_COUNT=$(dmesg | grep -i "out of memory" | wc -l)
|
||||
if [ "$OOM_COUNT" -gt 0 ]; then
|
||||
log_warning "发现 $OOM_COUNT 次OOM事件(需检查内存配置)"
|
||||
dmesg | grep -i "out of memory" | tail -5 | awk '{print " " $0}' | tee -a "$REPORT_FILE"
|
||||
else
|
||||
log_ok "无OOM事件"
|
||||
fi
|
||||
}
|
||||
|
||||
# 8. 报告摘要(不用改)
|
||||
generate_summary() {
|
||||
log_section "8. 巡检报告摘要"
|
||||
WARNING_COUNT=$(grep -c "\[WARNING\]" "$REPORT_FILE")
|
||||
ERROR_COUNT=$(grep -c "\[ERROR\]" "$REPORT_FILE")
|
||||
log "巡检完成时间: $(date +"%Y-%m-%d %H:%M:%S")"
|
||||
log "告警数量: $WARNING_COUNT"
|
||||
log "错误数量: $ERROR_COUNT"
|
||||
|
||||
if [ "$ERROR_COUNT" -gt 0 ]; then
|
||||
log_error "发现 $ERROR_COUNT 个严重问题,请立即处理!"
|
||||
elif [ "$WARNING_COUNT" -gt 0 ]; then
|
||||
log_warning "发现 $WARNING_COUNT 个告警,建议关注"
|
||||
else
|
||||
log_ok "系统状态良好,无异常"
|
||||
fi
|
||||
log -e "\n完整报告已保存至: $REPORT_FILE"
|
||||
}
|
||||
|
||||
# 主函数(不用改)
|
||||
main() {
|
||||
echo "=========================================="
|
||||
echo " 服务器健康状态巡检脚本 v2.0"
|
||||
echo "=========================================="
|
||||
echo -e "\n"
|
||||
|
||||
# 非root用户提示(部分检查需要root)
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "警告: 非root用户运行,部分检查可能无法执行"
|
||||
echo -e "\n"
|
||||
fi
|
||||
|
||||
# 执行所有检查
|
||||
check_basic_info
|
||||
check_cpu
|
||||
check_memory
|
||||
check_disk
|
||||
check_network
|
||||
check_processes
|
||||
check_logs
|
||||
generate_summary
|
||||
|
||||
echo -e "\n=========================================="
|
||||
echo " 巡检完成!"
|
||||
echo "=========================================="
|
||||
}
|
||||
|
||||
# 脚本入口(不用改)
|
||||
main "$@"
|
||||
```
|
||||
|
||||
### 新手避坑
|
||||
|
||||
1. 1\. **权限问题** :执行前确保是root用户(sudo -i切换),否则磁盘、进程检查可能不准。
|
||||
2. 2\. **命令缺失** :如果提示 `netstat: command not found` ,安装依赖: `yum install net-tools` (CentOS)或 `apt install net-tools` (Ubuntu)。
|
||||
3. 3\. **报告查看** :巡检完成后,报告存放在 `/var/log` 目录下,文件名带时间戳(如 `system_check_20251022_083000.log` )。
|
||||
|
||||
## 脚本2:磁盘空间深度检查脚本
|
||||
|
||||
### 脚本用途
|
||||
|
||||
专门定位磁盘满的原因:找出大文件、旧日志、临时文件,还能预测磁盘满的时间,给出清理建议。
|
||||
|
||||
### 核心功能
|
||||
|
||||
- • 检查磁盘/Inode使用率,定位大目录(TOP10)
|
||||
- • 查找超1G的大文件(可修改阈值)
|
||||
- • 检查30天前未清理的旧日志
|
||||
- • 清理/tmp目录下7天未使用的临时文件
|
||||
- • 检查磁盘IO等待(避免IO过高导致卡顿)
|
||||
|
||||
### 使用步骤
|
||||
|
||||
1. 1\. 复制代码保存为 `disk_space_check.sh`
|
||||
2. 2\. 赋予权限: `chmod +x disk_space_check.sh`
|
||||
3. 3\. 执行:`./disk_space_check.sh` (root用户)
|
||||
|
||||
### 完整代码(新手改配置区)
|
||||
|
||||
```
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
# 脚本名称: disk_space_check.sh
|
||||
# 功能描述: 磁盘空间深度检查(新手改配置区!)
|
||||
# 版本信息: v1.5
|
||||
################################################################################
|
||||
|
||||
# -------------------------- 新手必改:配置区 --------------------------
|
||||
REPORT_FILE="/var/log/disk_check_$(date +%Y%m%d_%H%M%S).log"
|
||||
DISK_WARNING=80 # 磁盘使用率告警阈值(%)
|
||||
INODE_WARNING=80 # Inode使用率告警阈值(%)
|
||||
LARGE_FILE_SIZE=1G # 大文件阈值(可改500M、2G)
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# 颜色定义(不用改)
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# 日志函数(不用改)
|
||||
log() { echo "[$(date +"%Y-%m-%d %H:%M:%S")] $1" | tee -a "$REPORT_FILE"; }
|
||||
log_warning() { echo -e "${YELLOW}[WARNING] $1${NC}" | tee -a "$REPORT_FILE"; }
|
||||
log_error() { echo -e "${RED}[ERROR] $1${NC}" | tee -a "$REPORT_FILE"; }
|
||||
log_ok() { echo -e "${GREEN}[OK] $1${NC}" | tee -a "$REPORT_FILE"; }
|
||||
|
||||
# 1. 磁盘使用率检查(不用改)
|
||||
check_disk_usage() {
|
||||
log "==========================================================="
|
||||
log "1. 磁盘使用率检查"
|
||||
log "==========================================================="
|
||||
df -h | grep -vE '^Filesystem|tmpfs|cdrom|loop' | while read line; do
|
||||
USAGE=$(echo $line | awk '{print $5}' | sed 's/%//')
|
||||
MOUNT=$(echo $line | awk '{print $6}')
|
||||
AVAIL=$(echo $line | awk '{print $4}')
|
||||
log "分区: $MOUNT"
|
||||
log " 使用率: ${USAGE}%"
|
||||
log " 可用空间: $AVAIL"
|
||||
|
||||
if [ "$USAGE" -gt "$DISK_WARNING" ]; then
|
||||
log_error " 磁盘使用率超过${DISK_WARNING}%!"
|
||||
# 显示TOP10大目录
|
||||
log " 占用空间TOP 10目录:"
|
||||
du -sh ${MOUNT}/* 2>/dev/null | sort -rh | head -10 | awk '{print " " $0}' | tee -a "$REPORT_FILE"
|
||||
# 显示超阈值的大文件
|
||||
log " 大文件(>$LARGE_FILE_SIZE):"
|
||||
find ${MOUNT} -type f -size +${LARGE_FILE_SIZE} -exec ls -lh {} \; 2>/dev/null | awk '{print " " $9 " (" $5 ")"}' | head -10 | tee -a "$REPORT_FILE"
|
||||
else
|
||||
log_ok " 使用率正常"
|
||||
fi
|
||||
log ""
|
||||
done
|
||||
}
|
||||
|
||||
# 2. Inode检查(小文件占满磁盘用)(不用改)
|
||||
check_inode_usage() {
|
||||
log "==========================================================="
|
||||
log "2. Inode使用率检查"
|
||||
log "==========================================================="
|
||||
df -i | grep -vE '^Filesystem|tmpfs|cdrom|loop' | while read line; do
|
||||
INODE_USAGE=$(echo $line | awk '{print $5}' | sed 's/%//')
|
||||
MOUNT=$(echo $line | awk '{print $6}')
|
||||
log "分区: $MOUNT"
|
||||
log " Inode使用率: ${INODE_USAGE}%"
|
||||
|
||||
if [ "$INODE_USAGE" -gt "$INODE_WARNING" ]; then
|
||||
log_error " Inode使用率超过${INODE_WARNING}%!(小文件太多)"
|
||||
# 显示文件数TOP10目录
|
||||
log " 文件数量TOP 10目录:"
|
||||
for dir in $(find ${MOUNT}/* -maxdepth 0 -type d 2>/dev/null); do
|
||||
echo "$(find $dir -type f 2>/dev/null | wc -l) $dir"
|
||||
done | sort -rn | head -10 | awk '{print " " $1 " files: " $2}' | tee -a "$REPORT_FILE"
|
||||
else
|
||||
log_ok " Inode使用率正常"
|
||||
fi
|
||||
log ""
|
||||
done
|
||||
}
|
||||
|
||||
# 3. 日志文件检查(不用改)
|
||||
check_log_files() {
|
||||
log "==========================================================="
|
||||
log "3. 日志文件大小检查"
|
||||
log "==========================================================="
|
||||
# 常见日志目录(可加自己的日志目录,如/opt/logs)
|
||||
LOG_DIRS=("/var/log" "/opt/logs" "/data/logs" "/app/logs")
|
||||
for log_dir in "${LOG_DIRS[@]}"; do
|
||||
if [ -d "$log_dir" ]; then
|
||||
log "检查目录: $log_dir"
|
||||
# 显示超100MB的日志
|
||||
log " 大日志文件(>100MB):"
|
||||
find $log_dir -type f -size +100M -exec ls -lh {} \; 2>/dev/null | awk '{print " " $9 " (" $5 ")"}' | head -10 | tee -a "$REPORT_FILE"
|
||||
# 日志总大小
|
||||
TOTAL_SIZE=$(du -sh $log_dir 2>/dev/null | awk '{print $1}')
|
||||
log " 总大小: $TOTAL_SIZE"
|
||||
log ""
|
||||
fi
|
||||
done
|
||||
|
||||
# 30天前未压缩的旧日志
|
||||
log "未压缩的旧日志文件(30天前):"
|
||||
find /var/log -type f -name "*.log" -mtime +30 ! -name "*.gz" -exec ls -lh {} \; 2>/dev/null | awk '{print " " $9 " (" $5 ")"}' | head -10 | tee -a "$REPORT_FILE"
|
||||
}
|
||||
|
||||
# 4. 临时文件检查(不用改)
|
||||
check_temp_files() {
|
||||
log "==========================================================="
|
||||
log "4. 临时文件检查"
|
||||
log "==========================================================="
|
||||
# /tmp目录检查
|
||||
if [ -d "/tmp" ]; then
|
||||
TMP_SIZE=$(du -sh /tmp 2>/dev/null | awk '{print $1}')
|
||||
TMP_FILE_COUNT=$(find /tmp -type f 2>/dev/null | wc -l)
|
||||
log "/tmp目录:"
|
||||
log " 总大小: $TMP_SIZE"
|
||||
log " 文件数: $TMP_FILE_COUNT"
|
||||
|
||||
# 7天未使用的临时文件
|
||||
OLD_TMP=$(find /tmp -type f -atime +7 2>/dev/null | wc -l)
|
||||
if [ "$OLD_TMP" -gt 0 ]; then
|
||||
log_warning " 发现 $OLD_TMP 个超过7天未访问的临时文件"
|
||||
log " 占用空间TOP 10:"
|
||||
find /tmp -type f -atime +7 -exec ls -lh {} \; 2>/dev/null | sort -k5 -rh | head -10 | awk '{print " " $9 " (" $5 ")"}' | tee -a "$REPORT_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
# /var/tmp检查
|
||||
if [ -d "/var/tmp" ]; then
|
||||
VAR_TMP_SIZE=$(du -sh /var/tmp 2>/dev/null | awk '{print $1}')
|
||||
log -e "\n/var/tmp目录:"
|
||||
log " 总大小: $VAR_TMP_SIZE"
|
||||
fi
|
||||
}
|
||||
|
||||
# 5. 磁盘IO检查(不用改)
|
||||
check_disk_io() {
|
||||
log "==========================================================="
|
||||
log "5. 磁盘IO统计"
|
||||
log "==========================================================="
|
||||
if command -v iostat &> /dev/null; then
|
||||
log "磁盘IO统计(最近1分钟):"
|
||||
iostat -x 1 2 | tail -n +4 | awk 'NF' | tail -n +2 | tee -a "$REPORT_FILE"
|
||||
else
|
||||
log_warning "未安装iostat工具(执行:yum install sysstat 安装)"
|
||||
fi
|
||||
|
||||
# IO等待过高预警(超过20%)
|
||||
IO_WAIT=$(top -bn1 | grep "Cpu(s)" | awk '{print $10}' | sed 's/%wa,//')
|
||||
log -e "\n当前IO等待: ${IO_WAIT}%"
|
||||
if (( $(echo "$IO_WAIT > 20" | bc -l) )); then
|
||||
log_warning "IO等待过高!(可能是磁盘性能不足)"
|
||||
fi
|
||||
}
|
||||
|
||||
# 6. 清理建议(新手可直接复制执行)(不用改)
|
||||
generate_cleanup_suggestions() {
|
||||
log "==========================================================="
|
||||
log "6. 磁盘清理建议(新手复制命令执行)"
|
||||
log "==========================================================="
|
||||
log "1. 压缩30天前的旧日志:"
|
||||
log " find /var/log -type f -name '*.log' -mtime +30 -exec gzip {} \;"
|
||||
log -e "\n2. 清理7天前的临时文件:"
|
||||
log " find /tmp -type f -atime +7 -delete"
|
||||
log -e "\n3. 清理yum缓存(CentOS):"
|
||||
log " yum clean all"
|
||||
log -e "\n4. 清理Docker未使用资源(如有Docker):"
|
||||
log " docker system prune -a"
|
||||
log -e "\n5. 清理journal日志(系统日志):"
|
||||
log " journalctl --vacuum-time=7d"
|
||||
}
|
||||
|
||||
# 主函数(不用改)
|
||||
main() {
|
||||
echo "=========================================="
|
||||
echo " 磁盘空间深度检查脚本 v1.5"
|
||||
echo "=========================================="
|
||||
echo -e "\n"
|
||||
|
||||
check_disk_usage
|
||||
check_inode_usage
|
||||
check_log_files
|
||||
check_temp_files
|
||||
check_disk_io
|
||||
generate_cleanup_suggestions
|
||||
|
||||
log -e "\n==========================================================="
|
||||
log "检查完成!"
|
||||
log "报告保存至: $REPORT_FILE"
|
||||
log "==========================================================="
|
||||
}
|
||||
|
||||
main "$@"
|
||||
```
|
||||
|
||||
### 新手避坑
|
||||
|
||||
1. 1\. **iostat缺失** :提示 `iostat: command not found` 时,执行 `yum install sysstat` (CentOS)或 `apt install sysstat` (Ubuntu)。
|
||||
2. 2\. **清理谨慎** :执行“清理建议”中的命令前,先确认文件是否有用(比如 `find /tmp -type f -atime +7` 先查看,再删)。
|
||||
|
||||
## 脚本3-7:核心脚本(精简版,重点标配置和步骤)
|
||||
|
||||
### 脚本3:网络连接检查脚本
|
||||
|
||||
#### 用途
|
||||
|
||||
检查端口监听、TCP连接状态(比如TIME\_WAIT过多)、网络连通性(DNS/网关/外网)。
|
||||
|
||||
#### 使用步骤
|
||||
|
||||
1. 1\. 保存为 `network_check.sh` ,赋予权限: `chmod +x network_check.sh`
|
||||
2. 2\. 执行:`./network_check.sh`
|
||||
3. 3\. 新手改配置区: `CRITICAL_PORTS=(22 80 443 3306 6379)` (添加自己的关键端口)
|
||||
|
||||
#### 核心代码(关键部分)
|
||||
|
||||
```
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
# 脚本名称: network_check.sh
|
||||
# 功能描述: 网络连接和端口检查(新手改CRITICAL_PORTS!)
|
||||
################################################################################
|
||||
|
||||
# -------------------------- 新手必改:配置区 --------------------------
|
||||
REPORT_FILE="/var/log/network_check_$(date +%Y%m%d_%H%M%S).log"
|
||||
TIME_WAIT_WARNING=5000 # TIME_WAIT超过5000预警
|
||||
ESTABLISHED_WARNING=10000 # 活动连接超过10000预警
|
||||
CRITICAL_PORTS=(22 80 443 3306 6379 8080) # 关键端口(如MySQL:3306,Redis:6379)
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# 颜色和日志函数(同前,不用改)
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
log() { echo "[$(date +"%Y-%m-%d %H:%M:%S")] $1" | tee -a "$REPORT_FILE"; }
|
||||
log_warning() { echo -e "${YELLOW}[WARNING] $1${NC}" | tee -a "$REPORT_FILE"; }
|
||||
log_ok() { echo -e "${GREEN}[OK] $1${NC}" | tee -a "$REPORT_FILE"; }
|
||||
|
||||
# 关键端口检查(核心功能)
|
||||
check_listening_ports() {
|
||||
log "==========================================================="
|
||||
log "3. 关键端口检查"
|
||||
log "==========================================================="
|
||||
log "当前监听端口:"
|
||||
netstat -tulnp | grep LISTEN | tee -a "$REPORT_FILE"
|
||||
log -e "\n关键端口状态:"
|
||||
for port in "${CRITICAL_PORTS[@]}"; do
|
||||
if netstat -tuln | grep -q ":$port "; then
|
||||
PID=$(netstat -tulnp 2>/dev/null | grep ":$port " | awk '{print $7}' | cut -d'/' -f1)
|
||||
PROCESS=$(netstat -tulnp 2>/dev/null | grep ":$port " | awk '{print $7}' | cut -d'/' -f2)
|
||||
log_ok "端口 $port: 监听中(进程:$PROCESS PID:$PID)"
|
||||
else
|
||||
log_warning "端口 $port: 未监听(服务可能未启动)"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# 其他函数(网络接口、TCP连接、连通性)省略,完整代码同前逻辑
|
||||
# 主函数(调用check_listening_ports等)
|
||||
main() {
|
||||
echo "=========================================="
|
||||
echo " 网络状态检查脚本 v1.0"
|
||||
echo "=========================================="
|
||||
check_network_interfaces
|
||||
check_tcp_connections
|
||||
check_listening_ports
|
||||
check_network_connectivity
|
||||
log -e "\n检查完成! 报告: $REPORT_FILE"
|
||||
}
|
||||
main "$@"
|
||||
```
|
||||
|
||||
### 脚本4:应用进程健康检查脚本
|
||||
|
||||
#### 用途
|
||||
|
||||
监控关键进程(Nginx/MySQL/Redis)、端口、HTTP接口响应(比如健康检查接口)。
|
||||
|
||||
#### 新手改配置区
|
||||
|
||||
```
|
||||
# 关键进程(进程名|端口,如"nginx|80,443")
|
||||
declare -A CRITICAL_PROCESSES=(
|
||||
["nginx"]="nginx|80,443"
|
||||
["mysql"]="mysqld|3306"
|
||||
["redis"]="redis-server|6379"
|
||||
["java"]="java|8080" # 如SpringBoot应用
|
||||
)
|
||||
|
||||
# HTTP接口(如健康检查接口)
|
||||
declare -A HTTP_ENDPOINTS=(
|
||||
["主页"]="http://localhost/"
|
||||
["健康检查"]="http://localhost:8080/health"
|
||||
["API状态"]="http://localhost:8080/api/status"
|
||||
)
|
||||
```
|
||||
|
||||
### 脚本5:安全基线检查脚本
|
||||
|
||||
#### 用途
|
||||
|
||||
检查账户安全(空密码、多root账户)、SSH配置(禁止root登录、禁用密码登录)、文件权限(/etc/passwd等关键文件)。
|
||||
|
||||
#### 核心功能
|
||||
|
||||
- • 检查空密码账户、UID=0的账户(多root风险)
|
||||
- • 检查SSH是否允许root登录、密码登录(不安全)
|
||||
- • 检查关键文件权限(如/etc/shadow权限应为000)
|
||||
|
||||
### 脚本6:MySQL数据库检查脚本
|
||||
|
||||
#### 用途
|
||||
|
||||
检查MySQL连接数、慢查询、主从复制、InnoDB缓冲池命中率。
|
||||
|
||||
#### 新手必改配置
|
||||
|
||||
```
|
||||
# MySQL连接信息(改自己的用户名、密码、地址)
|
||||
MYSQL_USER="monitor" # MySQL监控账户(需提前创建,给SELECT权限)
|
||||
MYSQL_PASS="your_pass" # 密码
|
||||
MYSQL_HOST="localhost" # 数据库地址
|
||||
MYSQL_PORT="3306" # 端口
|
||||
```
|
||||
|
||||
### 脚本7:批量服务器巡检脚本
|
||||
|
||||
#### 用途
|
||||
|
||||
并发巡检多台服务器(比如200台),生成HTML汇总报告(带颜色,可浏览器打开)。
|
||||
|
||||
#### 新手使用步骤
|
||||
|
||||
1. 1\. 准备服务器列表文件 `servers.txt` (格式如下):
|
||||
```
|
||||
# Web服务器
|
||||
192.168.1.10
|
||||
192.168.1.11
|
||||
# 数据库服务器
|
||||
192.168.1.30
|
||||
```
|
||||
2. 2\. 改配置区(SSH信息):
|
||||
```
|
||||
SSH_USER="root" # SSH用户名
|
||||
SSH_KEY="~/.ssh/id_rsa" # SSH私钥路径(免密登录用)
|
||||
CONCURRENT=10 # 并发数(同时检查10台)
|
||||
CHECK_SCRIPT="system_health_check.sh" # 要批量执行的脚本
|
||||
```
|
||||
3. 3\. 执行:`./batch_check_scheduler.sh` ,报告存 `/var/log/batch_check_xxx/` ,打开 `summary.html` 看汇总。
|
||||
|
||||
## 新手必备总结
|
||||
|
||||
1. 1\. **常用命令**
|
||||
- • 赋予脚本权限: `chmod +x 脚本名.sh`
|
||||
- • 执行脚本:`./脚本名.sh` (root用户用 `sudo ./脚本名.sh` )
|
||||
- • 定时执行: `crontab -e` ,添加 `0 8 * * * /path/to/脚本名.sh` (每天8点)
|
||||
- • 查看报告: `cat /var/log/脚本名_20251022_083000.log`
|
||||
3. 2\. **巡检频率建议**
|
||||
- • 系统健康(脚本1):每天1次(早8点)
|
||||
- • 磁盘检查(脚本2):每天1次
|
||||
- • 进程检查(脚本4):核心业务每小时1次
|
||||
- • 安全检查(脚本5):每周1次
|
||||
- • 批量巡检(脚本7):集群每天1次
|
||||
5. 3\. **避坑总览**
|
||||
- • 所有脚本尽量用root执行,否则检查不完整
|
||||
- • 命令缺失时,根据提示安装依赖(如 `yum install net-tools sysstat` )
|
||||
- • 修改配置区时,路径、账户、密码要和实际环境一致
|
||||
- • 清理文件前,先查看(如 `find /tmp -type f -atime +7` )再删除
|
||||
|
||||
|
||||
|
||||
素材来源官方媒体/网络新闻
|
||||
|
||||
内容含AI生成图片,注意甄别
|
||||
|
||||
继续滑动看下一个
|
||||
|
||||
python小甲鱼
|
||||
|
||||
向上滑动看下一个
|
||||
@@ -0,0 +1,425 @@
|
||||

|
||||
|
||||
大家好,这里是物联网心球。
|
||||
|
||||
一直以来,在Linux高性能网络编程中使用最多的网络I/O模型是epoll。epoll除了性能优越,稳定性也很高,似乎epoll成了高并发应用场景的唯一选择。然而随着io_uring的出现,这一局面慢慢被打破。
|
||||
|
||||
io_uring是异步I/O,同样性能优越,那么epoll和io_uring到底谁更牛呢?今天我们来深入探讨一下。
|
||||
|
||||
## 1.一张图搞懂epoll
|
||||
|
||||
笔者将epoll重要的知识点都汇聚在一张图,如图1所示。
|
||||
|
||||

|
||||
|
||||
图1 一张图搞懂epoll
|
||||
|
||||
epoll_create用来在内核里创建一个eventpoll对象(epoll实例)并返回一个文件描述符(epfd),通过epfd我们能够快速访问eventpoll对象。eventpoll对象内核定义如下:
|
||||
|
||||
```c
|
||||
structeventpoll {
|
||||
wait_queue_head_t wq; // 等待队列
|
||||
structlist_headrdllist;// 就绪队列
|
||||
structrb_rootrbr;// 红黑树
|
||||
......
|
||||
};
|
||||
```
|
||||
|
||||
- wq:等待队列,内核通过等待队列唤醒epoll线程。
|
||||
- rdllist:就绪队列,用于存储就绪事件。
|
||||
- rbr:红黑树,用于存储所有通过epoll_ctl注册的I/O事件。
|
||||
|
||||
epoll_ctl用于向epoll实例中添加、修改或删除需要监控的文件描述符及其事件,内核对于epoll事件的定义为struct epitem结构。
|
||||
|
||||
```text
|
||||
structepitem {
|
||||
union {
|
||||
structrb_noderbn;/* 红黑树节点*/
|
||||
};
|
||||
structlist_headrdllink;/* 就绪队列节点*/
|
||||
structepoll_filefdffd;/* 文件描述符信息,包含fd和对应的file指针 */
|
||||
int nwait;
|
||||
structeventpoll *ep;/* 指向所属的eventpoll实例 */
|
||||
structepoll_eventevent;/* 通过epoll_ctl设置的epoll_event事件,包含文件描述符和感兴趣的事件类型 */
|
||||
......
|
||||
};
|
||||
```
|
||||
|
||||
用户程序调用epoll_ctl函数(操作类型指定为EPOLL_CTL_ADD)时,内核会创建epitem对象,然后将用户设置的epoll事件(包含文件描述符和事件类型)复制至epitem对象,再将epitem对象插入红黑树。
|
||||
|
||||
epitem对象插入红黑树后,内核需要激活事件驱动机制,即当事件就绪后,主动通知epoll。具体做法是创建一个socket等待队列项(struct eppoll_entry结构),eppoll_entry的base成员指向epitem对象,同时epoll_entry中注册一个回调函数ep_poll_callback(用于通知epoll事件就绪),最后将epoll_entry插入socket等待队列。
|
||||
|
||||
完成epoll事件注册后,用户程序调用epoll_wait函数获取epoll就绪事件,epoll_wait负责把就绪事件从内核搬到用户程序。内核会循环检测epoll就绪队列是否为空。如果就绪队列不为空,内核会将所有就绪事件拷贝至epoll_event数组(epoll_wait函数参数);否则,epoll线程会创建一个epoll等待队列项并注册回调函数ep_autoremove_wake_function(用于唤醒epoll线程),等待队列项将插入epoll等待队列,随后内核切换epoll线程至阻塞状态。
|
||||
|
||||
数据到来时,数据经网卡->网络设备子系统->网络协议栈,最后存储至套接字接收缓冲区。一旦数据被套接字接收,则epoll读事件就绪。内核会轮询socket等待队列,并执行ep_poll_callback回调函数,该函数有两个作用:
|
||||
|
||||
- 将socket等待队列项中的epitem对象插入epoll就绪队列。
|
||||
- 轮询epoll等待队列,执行ep_autoremove_wake_function回调函数,将epoll线程唤醒。
|
||||
|
||||
epoll线程被唤醒后,它会将epoll就绪队列中的就绪事件从内核拷贝至用户程序。用户程序检测到就绪事件后,就能够读写数据了。
|
||||
|
||||
下面,我们简单总结一下epoll的优缺点。
|
||||
|
||||
epoll的优点如下:
|
||||
|
||||
- 无文件描述符数量限制,非常适用于高并发场景。
|
||||
- 采用事件驱动模型,不需要轮询每个文件描述符状态来获取就绪事件。
|
||||
|
||||
相对于epoll的优点,笔者更愿意来讨论epoll的缺点,如果我们能够准确地指出epoll的缺点,意味着我们对epoll有了非常深入的理解。
|
||||
|
||||
epoll的缺点为:epoll处理I/O事件时,伴随着大量的系统调用,并且epoll不能够批量地处理I/O事件。
|
||||
|
||||
## 2.一张图搞懂io_uring
|
||||
|
||||
io_uring是异步I/O模型,它的实现原理和同步I/O模型有很大的区别。我们同样通过一张图来学习io_uring,如图2所示。
|
||||
|
||||

|
||||
|
||||
图2 一张图搞懂io_uring
|
||||
|
||||
io_uring同样有三个系统调用:
|
||||
|
||||
- io_uring_setup:用于创建和初始化io_uring实例。
|
||||
- io_uring_enter:负责提交I/O请求和获取完成事件。
|
||||
- io_uring_register:用于将文件描述符、缓冲区等资源预先注册到io_uring实例中。
|
||||
|
||||
图2中并没有出现这三个系统调用,实际开发中,我们会使用liburing库,liburing库将io_uring中的三个系统调用进行了封装,提供了更加丰富和友好的编程接口。
|
||||
|
||||
用户程序调用io_uring_queue_init函数(内部封装了io_uring_setup系统调用),该函数将创建和初始化一个io_uring实例(struct io_ring_ctx),内核定义如下:
|
||||
|
||||
```c
|
||||
struct io_ring_ctx {
|
||||
struct io_rings *rings; /* 指向完成队列CQ */
|
||||
struct io_uring_sqe *sq_sqes; /* 指向提交队列SQ */
|
||||
......
|
||||
};
|
||||
```
|
||||
|
||||
struct io_ring_ctx结构非常复杂,这里不展开讲解,我们需要关注两个重要成员:rings(CQ,完成队列)和sq_sqes(SQ,提交队列)。
|
||||
|
||||
CQ和SQ都是无锁环形队列。内核初始化io_uring实例时,会从内核直接映射区分配内存作为CQ和SQ。之所以从内核直接映射区分配内存,是为了方便用户程序执行mmap内存映射,确保用户程序和内核能够直接都能够访问这块内存区域。CQ和SQ是用户程序和内核数据传输的通道,借用这个通道,用户程序可以在不使用系统调用的情况下和内核实现数据交换。CQ和SQ采用无锁化编程,在高并发场景下,能够最大程度减少锁的开销。
|
||||
|
||||
内核维护了一张io_uring I/O操作表,这张表定义了io_uring所有的I/O操作,共50多种操作,定义如下:
|
||||
|
||||
```c
|
||||
/* 内核io_uring I/O操作表 */
|
||||
conststructio_issue_def io_issue_defs[] = {
|
||||
......
|
||||
[IORING_OP_SENDMSG] = {
|
||||
.needs_file = 1,
|
||||
.unbound_nonreg_file = 1,
|
||||
.pollout = 1,
|
||||
.ioprio = 1,
|
||||
.async_size = sizeof(struct io_async_msghdr),
|
||||
.prep = io_sendmsg_prep,
|
||||
.issue = io_sendmsg,
|
||||
},
|
||||
[IORING_OP_RECVMSG] = {
|
||||
.needs_file = 1,
|
||||
.unbound_nonreg_file = 1,
|
||||
.pollin = 1,
|
||||
.buffer_select = 1,
|
||||
.ioprio = 1,
|
||||
.async_size = sizeof(struct io_async_msghdr),
|
||||
.prep = io_recvmsg_prep,
|
||||
.issue = io_recvmsg,
|
||||
},
|
||||
......
|
||||
}
|
||||
```
|
||||
|
||||
每个I/O操作都有一个操作码,内核根据操作码查找I/O操作表,获取I/O操作函数并执行。
|
||||
|
||||
io_uring执行一个异步I/O操作的流程为:
|
||||
|
||||
- 步骤1:用户程序从SQ中申请一个空闲的SQE,然后设置SQE中的I/O操作码和用户数据。
|
||||
- 步骤2:用户程序调用io_uring_submit函数(内部封装了io_uring_enter系统调用)提交SQE,内核通过I/O操作码查询I/O操作表获取I/O操作函数并执行。I/O操作成功后,内核从CQ中申请一个CQE,将返回结果存储在CQE。
|
||||
- 步骤3:用户程序检测CQ是否为空,如果CQ不为空,用户程序从CQ中获取CQE,并解析I/O操作返回结果。
|
||||
|
||||
最后,我们同样来看一下io_uring有哪些优缺点。
|
||||
|
||||
io_uring优点如下:
|
||||
|
||||
- 用户程序和内核共享内存:SQ/CQ通过mmap映射,避免传统read/write的多次数据拷贝。
|
||||
- 批处理:一次提交/收割多个I/O请求,减少系统调用次数。
|
||||
- SQPOLL模式:内核线程(SQ线程)主动轮询SQ,进一步减少系统调用。
|
||||
|
||||
io_uring在设计上并没有很大的缺陷,由于io_uring是一种比较新的网络I/O模型,所以还存在一些bug,需要持续优化。
|
||||
|
||||
## 3.epoll和io_uring性能对比测试
|
||||
|
||||
epoll和io_uring性能对比见表1。
|
||||
|
||||

|
||||
|
||||
表1 epoll和io_uring对比
|
||||
|
||||
从表1可以看出,io_uring在数据拷贝和系统调用开销两个方面是优于epoll的,当然,这些只是理论上的分析。下面,我们通过一个测试来验证上述观点。
|
||||
|
||||
### 3.1 测试环境
|
||||
|
||||
测试硬件环境如下:
|
||||
|
||||
客户端主机:AMD Ryzen 7 8845HS,8核16线程,主频3.8GHz,24G内存。
|
||||
|
||||
服务端主机:12th Gen Intel(R) Core(TM) i5-12500H,8核16线程,16G内存,主频4.1GHz。
|
||||
|
||||
局域网连接,带宽1Gbps。
|
||||
|
||||
### 3.2 测试用例
|
||||
|
||||
TCP客户端程序创建50个线程,每个线程和TCP服务端建立20000个TCP连接,总共百万个TCP连接。每个TCP连接发送和接收100个数据包(数据包长度100字节)。
|
||||
|
||||
客户端程序代码(只保留主线代码)如下:
|
||||
|
||||
```text
|
||||
/* TCP连接处理线程 */
|
||||
void *test_proc(void *arg) {
|
||||
int pos = (int)(intptr_t)arg;
|
||||
char send_buffer[BUFFER_SIZE] = {0};
|
||||
char recv_buffer[BUFFER_SIZE] = {0};
|
||||
memset(send_buffer, 'a', sizeof(send_buffer));
|
||||
/* 每个线程建立20000个连接 */
|
||||
for (int i = 0; i < REQS_THREAD; i++) {
|
||||
int sockfd = do_connect(server_ip, server_port, pos);
|
||||
if (sockfd == -1) continue;
|
||||
/* 每个连接发送和接收100个数据包 */
|
||||
for (int j = 0; j < PACKETS_THREAD; j++) {
|
||||
int ret = send(sockfd, send_buffer, 100, 0);
|
||||
if (ret < 0) break;
|
||||
ret = recv(sockfd, recv_buffer, sizeof(recv_buffer), 0);
|
||||
if (ret < 0) break;
|
||||
}
|
||||
close(sockfd);
|
||||
}
|
||||
returnNULL;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
server_ip = argv[1];
|
||||
server_port = atoi(argv[2]);
|
||||
int start_pos = atoi(argv[3]);
|
||||
|
||||
struct timeval start, end;
|
||||
gettimeofday(&start, NULL);
|
||||
pthread_t th[THREADS];
|
||||
/* 创建50个线程 */
|
||||
for (int i = 0; i < THREADS; i++) {
|
||||
int ret = pthread_create(&th[i], NULL, test_proc, (void *)(intptr_t)(start_pos + i));
|
||||
if (ret != 0) {
|
||||
perror("pthread_create");
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < THREADS; i++) {
|
||||
pthread_join(th[i], NULL);
|
||||
}
|
||||
gettimeofday(&end, NULL);
|
||||
/* 统计百万连接处理时间 */
|
||||
double time_used = (end.tv_sec - start.tv_sec) * 1000.0 + (end.tv_usec - start.tv_usec) / 1000.0;
|
||||
int reqs = (REQS_THREAD * THREADS);
|
||||
double qps = reqs / (time_used / 1000.0);
|
||||
|
||||
printf("sucess: %d, time_used: %.0f(ms), qps: %.2f\n",
|
||||
reqs, time_used, qps);
|
||||
|
||||
return0;
|
||||
}
|
||||
```
|
||||
|
||||
TCP服务端分为:epoll服务端和io_uring服务端。epoll服务端代码(只保留主线代码)如下:
|
||||
|
||||
```text
|
||||
int main(int argc, char *argv[]) {
|
||||
char *local_ip = argv[1];
|
||||
unsignedshort local_port = atoi(argv[2]);
|
||||
|
||||
/* 初始化TCP服务端 */
|
||||
int sockfd = init_server(local_ip, local_port);
|
||||
/* 创建和初始化epoll实例 */
|
||||
int epfd = epoll_create(100);
|
||||
/* epoll注册监听套接字事件 */
|
||||
epoll_add_event(epfd, sockfd);
|
||||
while(1) {
|
||||
struct epoll_event events[MAX_EVENTS] = {0};
|
||||
int timeout = 2000;
|
||||
int nfds = epoll_wait(epfd, events, MAX_EVENTS, timeout);
|
||||
if (nfds == 0) {
|
||||
continue;
|
||||
} elseif (nfds == -1) {
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < nfds; i++) {
|
||||
int fd = events[i].data.fd;
|
||||
if (events[i].events & EPOLLIN) {
|
||||
if (fd == sockfd) {
|
||||
/* 处理连接请求 */
|
||||
int new_sockfd = accept(sockfd, NULL, NULL);
|
||||
if (new_sockfd == -1) {
|
||||
continue;
|
||||
}
|
||||
set_nonblock(new_sockfd);
|
||||
epoll_add_event(epfd, new_sockfd);
|
||||
} else {
|
||||
char buf[BUF_LEN] = {0};
|
||||
int len = 0;
|
||||
int pos = 0;
|
||||
while (1) {
|
||||
/* 接收数据 */
|
||||
len = recv(fd, buf+pos, ONCE_LEN, 0);
|
||||
if ((len == 0) || ((len == -1) && (errno != EAGAIN))) {
|
||||
close(fd);
|
||||
epoll_del_event(epfd, fd);
|
||||
break;
|
||||
} elseif ((len == -1) && (errno == EAGAIN)) {
|
||||
break;
|
||||
} else {
|
||||
pos += (len > 0 ? len : 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pos > 0) {
|
||||
/* 发送数据 */
|
||||
int slen = send(fd, buf, pos, 0);
|
||||
if (slen < 0) {
|
||||
close(fd);
|
||||
epoll_del_event(epfd, fd);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
close(epfd);
|
||||
close(sockfd);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
io_uring服务端代码(只保留主线代码)如下:
|
||||
|
||||
```text
|
||||
int main(int argc, char *argv[]) {
|
||||
char *server_ip = argv[1];
|
||||
unsignedshort server_port = atoi(argv[2]);
|
||||
|
||||
int listen_fd = init_server(server_ip, server_port);
|
||||
|
||||
struct io_uring ring;
|
||||
#if 1 /* 普通模式 */
|
||||
if (io_uring_queue_init(ENTRIES, &ring, 0) < 0) {
|
||||
return-1;
|
||||
}
|
||||
#else /* SQPOLL模式 */
|
||||
struct io_uring_params params = {0};
|
||||
params.flags = IORING_SETUP_SQPOLL |
|
||||
IORING_SETUP_SQ_AFF;
|
||||
params.sq_thread_cpu = 1;
|
||||
if (io_uring_queue_init_params(ENTRIES, &ring, ¶ms) < 0) {
|
||||
return1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* accept I/O请求插入SQ */
|
||||
setup_accept(&ring, listen_fd);
|
||||
struct io_uring_cqe *cqes[BATCH_SIZE];
|
||||
while (1) {
|
||||
/* 提交全部SQE */
|
||||
io_uring_submit(&ring);
|
||||
/* 阻塞等待CQE */
|
||||
int ret = io_uring_wait_cqe(&ring, &cqes[0]);
|
||||
if (ret < 0) {
|
||||
break;
|
||||
}
|
||||
/* 批处理CQE */
|
||||
int ready = io_uring_peek_batch_cqe(&ring, cqes, BATCH_SIZE);
|
||||
if (ready == 0) {
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < ready; i++) {
|
||||
struct io_uring_cqe *cqe = cqes[i];
|
||||
io_info* info = (io_info*)io_uring_cqe_get_data(cqe);
|
||||
switch (info->type) {
|
||||
case ACCEPT:
|
||||
if (cqe->res == -1) {
|
||||
close(info->client_fd);
|
||||
break;
|
||||
}
|
||||
/* 读数据I/O请求插入SQ */
|
||||
setup_read(&ring, cqe->res, &info->client_addr);
|
||||
/* accept I/O请求插入SQ */
|
||||
setup_accept(&ring, listen_fd);
|
||||
break;
|
||||
case READ:
|
||||
if (cqe->res > 0) {
|
||||
info->bytes_read = cqe->res;
|
||||
info->buffer[cqe->res] = '\0';
|
||||
/* 写数据I/O请求插入SQ */
|
||||
setup_write(&ring, info->client_fd,
|
||||
info->buffer, info->bytes_read,
|
||||
&info->client_addr);
|
||||
} elseif (cqe->res <= 0) {
|
||||
close(info->client_fd);
|
||||
}
|
||||
break;
|
||||
case WRITE:
|
||||
if (cqe->res > 0) {
|
||||
/* 读数据I/O请求插入SQ */
|
||||
setup_read(&ring, info->client_fd, &info->client_addr);
|
||||
} elseif (cqe->res <= 0) {
|
||||
close(info->client_fd);
|
||||
}
|
||||
break;
|
||||
}
|
||||
free_connection_info(info);
|
||||
}
|
||||
/* CQ推进 */
|
||||
io_uring_cq_advance(&ring, ready);
|
||||
}
|
||||
|
||||
close(listen_fd);
|
||||
io_uring_queue_exit(&ring);
|
||||
return0;
|
||||
}
|
||||
```
|
||||
|
||||
TCP客户端分别和两个TCP服务端建立百万连接,并分别统计处理百万连接所花费的时间。同时通过strace -c命令统计epoll服务端和io_uring服务端系统调用情况。
|
||||
|
||||
strace -c命令输出示例如下:
|
||||
|
||||

|
||||
|
||||
每一列的解析如下:
|
||||
|
||||
- % time:该系统调用耗时占总时间的百分比。
|
||||
- seconds:该系统调用总耗时(秒)。
|
||||
- usecs/call:每次调用平均耗时(微妙)。
|
||||
- calls:总调用次数。
|
||||
- errors:调用失败次数。
|
||||
- syscall:系统调用名称。
|
||||
|
||||
### 3.3 测试结果
|
||||
|
||||
**(1)epoll服务端测试结果**
|
||||
|
||||
执行strace -c ./tcp_epoll 192.168.2.2 9999命令统计epoll服务端系统调用情况,统计结果如下:
|
||||
|
||||

|
||||
|
||||
epoll服务端总系统调用次数为211828279(超2亿次),除了发送和接收系统调用次数超百万次,其他系统调用(如epoll_wait、epoll_ctl)次数也有几十万次。完成百万连接测试用时632008毫秒。
|
||||
|
||||
**(2)io_uring服务端测试结果**
|
||||
|
||||
执行strace -c ./io_uring_server 192.168.2.2 9999命令统计io_uring服务端系统调用情况,统计结果如下:
|
||||
|
||||

|
||||
|
||||
开启批处理和SQ线程后,io_uring服务端总系统调用次数为28647744次(近3千万次),其中io_uring_enter系统调用次数占比为98%。百万连接测试时间为626473毫秒。
|
||||
|
||||
从测试结果可以看出,epoll服务端的系统调用次数远远大于io_uring服务端的系统调用次数。如果每次系统调用的处理时间约为1us,1千万次系统调用将耗时10秒,所以io_uring服务端完成百万连接测试用时相对较短。
|
||||
|
||||
## 总结:
|
||||
|
||||
文章中涉及到的epoll、io_uring以及百万并发相关的知识在我的新书《图解Linux网络编程》中有详细介绍,如果你想系统性地学习Linux网络编程,成为别人眼中的大佬,欢迎入手我的新书,书籍已经在各大电商平台上线。
|
||||
@@ -0,0 +1,524 @@
|
||||
### 来看一个常见的场景
|
||||
|
||||
假设你正在开发一个数据备份脚本。这个脚本需要执行以下操作:
|
||||
|
||||
1. 创建临时工作目录
|
||||
2. 将数据复制到临时目录
|
||||
3. 压缩打包
|
||||
4. 清理临时文件
|
||||
|
||||
```text
|
||||
#!/bin/bash
|
||||
|
||||
WORK_DIR="/tmp/backup_$(date +%Y%m%d)"
|
||||
|
||||
echo "开始备份..."
|
||||
mkdir -p "$WORK_DIR"
|
||||
echo "创建临时目录: $WORK_DIR"
|
||||
|
||||
echo "复制文件中..."
|
||||
cp -r /path/to/data "$WORK_DIR/"
|
||||
sleep 5 # 模拟耗时操作
|
||||
|
||||
echo "压缩打包..."
|
||||
tar -czf backup.tar.gz "$WORK_DIR"
|
||||
sleep 3 # 模拟耗时操作
|
||||
|
||||
echo "清理临时文件..."
|
||||
rm -rf "$WORK_DIR"
|
||||
|
||||
echo "备份完成!"
|
||||
```
|
||||
|
||||
### 如果我中断了脚本怎么办!
|
||||
|
||||
当我们运行这个脚本时,如果在执行过程中按下 Ctrl+C 中断操作,会发生什么?
|
||||
|
||||
临时目录 `$WORK_DIR` 将被遗留在系统中,因为清理步骤没有被执行。长期积累下来,这些未清理的临时文件会占用大量磁盘空间。
|
||||
|
||||
### 使用 trap 命令改善程序
|
||||
|
||||
这时,`trap` 命令就派上用场了。`trap` 可以捕获特定的信号并执行相应的处理函数。SIGINT(通常由 Ctrl+C 触发)就是最常见的信号之一。
|
||||
|
||||
首先,我们定义一个中断处理函数:
|
||||
|
||||
```text
|
||||
on_interrupt() {
|
||||
echo -e "\n程序被中断!"
|
||||
echo "清理临时文件..."
|
||||
rm -rf "$WORK_DIR"
|
||||
exit 1
|
||||
}
|
||||
```
|
||||
|
||||
然后,在脚本开头使用 `trap` 设置信号处理:
|
||||
|
||||
```text
|
||||
trap on_interrupt SIGINT
|
||||
```
|
||||
|
||||
完整的改进版脚本如下:
|
||||
|
||||
```text
|
||||
#!/bin/bash
|
||||
|
||||
WORK_DIR="/tmp/backup_$(date +%Y%m%d)"
|
||||
|
||||
# 定义中断处理函数
|
||||
on_interrupt() {
|
||||
echo -e "\n程序被中断!"
|
||||
echo "清理临时文件..."
|
||||
rm -rf "$WORK_DIR"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 设置 trap
|
||||
trap on_interrupt SIGINT
|
||||
|
||||
echo "开始备份..."
|
||||
mkdir -p "$WORK_DIR"
|
||||
echo "创建临时目录: $WORK_DIR"
|
||||
|
||||
echo "复制文件中..."
|
||||
cp -r /path/to/data "$WORK_DIR/"
|
||||
sleep 5 # 模拟耗时操作
|
||||
|
||||
echo "压缩打包..."
|
||||
tar -czf backup.tar.gz "$WORK_DIR"
|
||||
sleep 3 # 模拟耗时操作
|
||||
|
||||
echo "清理临时文件..."
|
||||
rm -rf "$WORK_DIR"
|
||||
|
||||
echo "备份完成!"
|
||||
```
|
||||
|
||||
## trap 命令说明
|
||||
|
||||
`trap` 命令的基本语法是:
|
||||
|
||||
```text
|
||||
trap command signal
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
- `command` 可以是函数名或直接的命令
|
||||
- `signal` 是要捕获的信号名称,如 SIGINT、SIGTERM 等
|
||||
|
||||
常见的信号包括:
|
||||
|
||||
- SIGINT (2):用户按下 Ctrl+C
|
||||
- SIGTERM (15):终止信号
|
||||
- EXIT:脚本退出时
|
||||
|
||||
你还可以同时捕获多个信号:
|
||||
|
||||
```text
|
||||
trap on_interrupt SIGINT SIGTERM
|
||||
```
|
||||
|
||||
通过使用 `trap` 命令和 `on_interrupt` 函数,我们实现了:
|
||||
|
||||
1. 优雅地处理程序中断
|
||||
2. 确保临时资源被正确清理
|
||||
3. 提供了友好的用户提示
|
||||
|
||||
这种模式不仅适用于备份脚本,还可以用在任何需要资源清理的脚本中,比如:
|
||||
|
||||
- 临时文件处理
|
||||
- 数据库连接清理
|
||||
- 锁文件删除
|
||||
- 进程清理
|
||||
|
||||
## 扩展: trap 命令的高级应用
|
||||
|
||||
### 多信号处理
|
||||
|
||||
有时我们需要对不同的信号进行不同的处理。比如在一个数据处理脚本中:
|
||||
|
||||
```text
|
||||
#!/bin/bash
|
||||
|
||||
# 定义变量
|
||||
DATA_FILE="data.txt"
|
||||
TEMP_FILE="temp.txt"
|
||||
LOG_FILE="process.log"
|
||||
|
||||
# 处理 Ctrl+C
|
||||
on_interrupt() {
|
||||
echo -e "\n收到 SIGINT,正在优雅关闭..."
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 处理 SIGTERM
|
||||
on_terminate() {
|
||||
echo -e "\n收到 SIGTERM,保存进度后退出..."
|
||||
save_progress
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 处理正常退出
|
||||
on_exit() {
|
||||
echo "程序正常结束,执行清理..."
|
||||
cleanup
|
||||
}
|
||||
|
||||
# 清理函数
|
||||
cleanup() {
|
||||
rm -f "$TEMP_FILE"
|
||||
echo "清理完成"
|
||||
}
|
||||
|
||||
# 保存进度
|
||||
save_progress() {
|
||||
echo "保存当前进度到 $LOG_FILE"
|
||||
echo "Progress saved at $(date)" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
# 设置多重信号处理
|
||||
trap on_interrupt SIGINT
|
||||
trap on_terminate SIGTERM
|
||||
trap on_exit EXIT
|
||||
|
||||
# 主程序
|
||||
echo "开始处理数据..."
|
||||
while true; do
|
||||
echo "处理中..."
|
||||
sleep 1
|
||||
done
|
||||
```
|
||||
|
||||
### 临时禁用和恢复信号处理
|
||||
|
||||
有时我们需要临时禁用信号处理,比如在执行关键操作时:
|
||||
|
||||
```text
|
||||
#!/bin/bash
|
||||
|
||||
critical_operation() {
|
||||
# 临时禁用 Ctrl+C
|
||||
trap '' SIGINT
|
||||
|
||||
echo "执行关键操作,这段时间按 Ctrl+C 无效..."
|
||||
sleep 5
|
||||
|
||||
# 恢复信号处理
|
||||
trap on_interrupt SIGINT
|
||||
echo "关键操作完成,恢复正常信号处理"
|
||||
}
|
||||
|
||||
on_interrupt() {
|
||||
echo -e "\n操作被中断!"
|
||||
exit 1
|
||||
}
|
||||
|
||||
trap on_interrupt SIGINT
|
||||
|
||||
echo "开始执行..."
|
||||
critical_operation
|
||||
echo "继续其他操作..."
|
||||
```
|
||||
|
||||
### DEBUG 信号与调试处理
|
||||
|
||||
DEBUG 并不是中断信号,而是 Bash 的一个特殊 trap 事件。它在执行每个命令之前触发,主要用于调试目的。让我们看一个更实用的例子:
|
||||
|
||||
```text
|
||||
#!/bin/bash
|
||||
|
||||
# 用于控制是否在错误处理函数中触发 DEBUG trap
|
||||
IN_ERROR_HANDLER=0
|
||||
|
||||
# 定义调试处理函数
|
||||
on_debug() {
|
||||
# 如果在错误处理函数中,跳过调试输出
|
||||
if ((IN_ERROR_HANDLER)); then
|
||||
return
|
||||
fi
|
||||
# $1 是行号,$BASH_COMMAND 是即将执行的命令
|
||||
echo "[DEBUG] 行 $1: 准备执行 -> $BASH_COMMAND"
|
||||
}
|
||||
|
||||
# 错误处理函数
|
||||
on_error() {
|
||||
local err=$? # 立即保存错误码
|
||||
local line=$1
|
||||
local cmd=$2
|
||||
|
||||
# 设置标志,防止在错误处理中触发 DEBUG trap
|
||||
IN_ERROR_HANDLER=1
|
||||
|
||||
echo "[ERROR] 行 $line 执行失败"
|
||||
echo "命令: $cmd"
|
||||
echo "错误码: $err"
|
||||
|
||||
# 重置标志
|
||||
IN_ERROR_HANDLER=0
|
||||
}
|
||||
|
||||
# 启用调试跟踪
|
||||
enable_debug() {
|
||||
# 启用 ERR trap
|
||||
set -E
|
||||
# -T 选项可以显示函数调用跟踪
|
||||
set -T
|
||||
# 设置 DEBUG trap,传入行号参数
|
||||
trap 'on_debug ${LINENO}' DEBUG
|
||||
trap 'on_error ${LINENO} "$BASH_COMMAND"' ERR
|
||||
}
|
||||
|
||||
# 关闭调试跟踪
|
||||
disable_debug() {
|
||||
trap - DEBUG
|
||||
trap - ERR
|
||||
set +E
|
||||
set +T
|
||||
}
|
||||
|
||||
# 通过环境变量控制是否开启调试
|
||||
if [[ "${ENABLE_DEBUG}" == "true" ]]; then
|
||||
enable_debug
|
||||
fi
|
||||
|
||||
# 测试函数
|
||||
test_function() {
|
||||
echo "执行测试函数"
|
||||
local result=$((2 + 2))
|
||||
echo "计算结果: $result"
|
||||
# 故意制造一个错误
|
||||
ls /nonexistent_directory
|
||||
}
|
||||
|
||||
# 主程序
|
||||
echo "开始执行..."
|
||||
test_function
|
||||
echo "尝试访问不存在的文件..."
|
||||
cat nonexistent_file.txt
|
||||
```
|
||||
|
||||
使用方式:
|
||||
|
||||
```text
|
||||
# 普通执行
|
||||
./script.sh
|
||||
|
||||
# 开启调试模式执行
|
||||
ENABLE_DEBUG=true ./script.sh
|
||||
```
|
||||
|
||||
普通模式输出:
|
||||
|
||||
```text
|
||||
开始执行...
|
||||
执行测试函数
|
||||
计算结果: 4
|
||||
ls: cannot access '/nonexistent_directory': No such file or directory
|
||||
尝试访问不存在的文件...
|
||||
cat: nonexistent_file.txt: No such file or directory
|
||||
```
|
||||
|
||||
DEBUG 模式输出:
|
||||
|
||||
```text
|
||||
[DEBUG] 行 41: 准备执行 -> trap 'on_error ${LINENO} "$BASH_COMMAND"' ERR
|
||||
[DEBUG] 行 67: 准备执行 -> echo "开始执行..."
|
||||
开始执行...
|
||||
[DEBUG] 行 68: 准备执行 -> test_function
|
||||
[DEBUG] 行 58: 准备执行 -> test_function
|
||||
[DEBUG] 行 59: 准备执行 -> echo "执行测试函数"
|
||||
执行测试函数
|
||||
[DEBUG] 行 60: 准备执行 -> local result=$((2 + 2))
|
||||
[DEBUG] 行 61: 准备执行 -> echo "计算结果: $result"
|
||||
计算结果: 4
|
||||
[DEBUG] 行 63: 准备执行 -> ls /nonexistent_directory
|
||||
ls: cannot access '/nonexistent_directory': No such file or directory
|
||||
[DEBUG] 行 63: 准备执行 -> ls /nonexistent_directory
|
||||
[DEBUG] 行 17: 准备执行 -> ls /nonexistent_directory
|
||||
[DEBUG] 行 18: 准备执行 -> local err=$?
|
||||
[DEBUG] 行 19: 准备执行 -> local line=$1
|
||||
[DEBUG] 行 20: 准备执行 -> local cmd=$2
|
||||
[DEBUG] 行 23: 准备执行 -> IN_ERROR_HANDLER=1
|
||||
[ERROR] 行 63 执行失败
|
||||
命令: ls /nonexistent_directory
|
||||
错误码: 2
|
||||
[DEBUG] 行 68: 准备执行 -> ls /nonexistent_directory
|
||||
[DEBUG] 行 17: 准备执行 -> ls /nonexistent_directory
|
||||
[DEBUG] 行 18: 准备执行 -> local err=$?
|
||||
[DEBUG] 行 19: 准备执行 -> local line=$1
|
||||
[DEBUG] 行 20: 准备执行 -> local cmd=$2
|
||||
[DEBUG] 行 23: 准备执行 -> IN_ERROR_HANDLER=1
|
||||
[ERROR] 行 68 执行失败
|
||||
命令: ls /nonexistent_directory
|
||||
错误码: 2
|
||||
[DEBUG] 行 69: 准备执行 -> echo "尝试访问不存在的文件..."
|
||||
尝试访问不存在的文件...
|
||||
[DEBUG] 行 70: 准备执行 -> cat nonexistent_file.txt
|
||||
cat: nonexistent_file.txt: No such file or directory
|
||||
[DEBUG] 行 70: 准备执行 -> cat nonexistent_file.txt
|
||||
[DEBUG] 行 17: 准备执行 -> cat nonexistent_file.txt
|
||||
[DEBUG] 行 18: 准备执行 -> local err=$?
|
||||
[DEBUG] 行 19: 准备执行 -> local line=$1
|
||||
[DEBUG] 行 20: 准备执行 -> local cmd=$2
|
||||
[DEBUG] 行 23: 准备执行 -> IN_ERROR_HANDLER=1
|
||||
[ERROR] 行 70 执行失败
|
||||
命令: cat nonexistent_file.txt
|
||||
错误码: 1
|
||||
```
|
||||
|
||||
### 文件锁机制 trap vs flock
|
||||
|
||||
让我们比较 trap 和 flock 的锁机制:
|
||||
|
||||
**使用 trap 的文件锁**
|
||||
|
||||
```text
|
||||
#!/bin/bash
|
||||
|
||||
LOCK_FILE="/tmp/script.lock"
|
||||
PID_FILE="/tmp/script.pid"
|
||||
|
||||
cleanup() {
|
||||
rm -f "$LOCK_FILE" "$PID_FILE"
|
||||
echo "清理锁文件和PID文件"
|
||||
}
|
||||
|
||||
get_lock() {
|
||||
if [ -e "$LOCK_FILE" ]; then
|
||||
local pid
|
||||
pid=$(cat "$PID_FILE" 2>/dev/null)
|
||||
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
||||
echo "另一个实例(PID: $pid)正在运行"
|
||||
exit 1
|
||||
fi
|
||||
# 如果进程不存在,清理旧的锁
|
||||
cleanup
|
||||
fi
|
||||
|
||||
echo $$ > "$PID_FILE"
|
||||
touch "$LOCK_FILE"
|
||||
trap cleanup EXIT
|
||||
}
|
||||
```
|
||||
|
||||
**使用 flock 的实现:**
|
||||
|
||||
```text
|
||||
#!/bin/bash
|
||||
|
||||
LOCK_FILE="/tmp/script.lock"
|
||||
|
||||
(
|
||||
# 获取文件锁,等待最多5秒
|
||||
flock -w 5 200 || { echo "无法获取锁,另一个实例正在运行"; exit 1; }
|
||||
|
||||
echo "获得锁,开始执行..."
|
||||
sleep 10
|
||||
echo "执行完成"
|
||||
|
||||
) 200>"$LOCK_FILE"
|
||||
```
|
||||
|
||||
**比较分析**
|
||||
|
||||
1. **可靠性**
|
||||
|
||||
|
||||
- flock 更可靠,它使用内核级文件锁
|
||||
- trap 方式可能在极端情况下(如系统崩溃)留下孤立的锁文件
|
||||
|
||||
1. **使用场景**
|
||||
|
||||
|
||||
- flock 适合要求严格的生产环境
|
||||
- trap 方式适合简单的脚本和开发环境
|
||||
|
||||
1. **推荐选择**
|
||||
|
||||
|
||||
- 推荐使用 flock,因为它:
|
||||
|
||||
- 自动处理进程终止
|
||||
- 支持超时设置
|
||||
- 提供阻塞和非阻塞模式
|
||||
- 可靠性更高
|
||||
|
||||
### 事务的实现
|
||||
|
||||
```text
|
||||
#!/bin/bash
|
||||
|
||||
# 状态变量
|
||||
TRANSACTION_ACTIVE=false
|
||||
|
||||
# 动态改变信号处理
|
||||
update_signal_handler() {
|
||||
if $TRANSACTION_ACTIVE; then
|
||||
# 事务进行中,设置中断处理为提示并结束
|
||||
trap 'echo "事务进行中,已被强行中断..."; cleanup; exit 1' SIGINT
|
||||
else
|
||||
# 非事务状态,可以安全退出
|
||||
trap 'echo "正常退出..."; exit 0' SIGINT
|
||||
fi
|
||||
}
|
||||
|
||||
# 清理函数
|
||||
cleanup() {
|
||||
echo "执行清理操作..."
|
||||
# 这里添加实际的清理代码
|
||||
}
|
||||
|
||||
# 模拟事务
|
||||
start_transaction() {
|
||||
TRANSACTION_ACTIVE=true
|
||||
update_signal_handler
|
||||
echo "事务开始"
|
||||
|
||||
# 模拟事务操作
|
||||
echo "执行事务步骤 1/3"
|
||||
sleep 2
|
||||
echo "执行事务步骤 2/3"
|
||||
sleep 2
|
||||
echo "执行事务步骤 3/3"
|
||||
sleep 2
|
||||
|
||||
TRANSACTION_ACTIVE=false
|
||||
update_signal_handler
|
||||
echo "事务完成"
|
||||
}
|
||||
|
||||
# 设置初始信号处理
|
||||
update_signal_handler
|
||||
|
||||
# 主程序执行流程
|
||||
echo "开始执行..."
|
||||
start_transaction
|
||||
echo "继续其他操作..."
|
||||
```
|
||||
|
||||
**执行流程说明:**
|
||||
|
||||
1. 脚本启动
|
||||
|
||||
|
||||
- `TRANSACTION_ACTIVE` 初始值为 `false`
|
||||
- 首次调用 `update_signal_handler`,设置正常的中断处理
|
||||
|
||||
1. 执行 `start_transaction`
|
||||
|
||||
|
||||
- 设置 `TRANSACTION_ACTIVE` 为 `true`
|
||||
- 更新信号处理为事务保护模式
|
||||
- 执行事务操作
|
||||
- 完成后,设置 `TRANSACTION_ACTIVE` 为 `false`
|
||||
- 恢复正常的信号处理
|
||||
|
||||
1. 信号处理行为
|
||||
|
||||
|
||||
- 事务进行中收到 SIGINT:显示中断消息,执行清理,然后退出
|
||||
- 非事务状态收到 SIGINT:直接安全退出
|
||||
|
||||
通过这些高级用法,我们可以构建更健壮、更可靠的 shell 脚本。无论是处理意外中断、实现锁机制,还是进行调试,`trap` 都是一个强大的工具。
|
||||
@@ -0,0 +1,23 @@
|
||||
Linux 靠一个叫/etc/fstab的配置文件实现 “开机自动挂载”
|
||||
编辑`/etc/fstab`前,必须先拿到两个 “关键参数”:**设备的 UUID**和**文件系统类型**—— 这是新手最容易忽略的,用设备路径(比如`/dev/sdb1`)容易出错(比如插新硬盘后,原 U 盘路径可能从`sdb1`变成`sdc1`),而 UUID 是设备的 “唯一身份证”,永远不会变。
|
||||
|
||||
|
||||
`blkid`能显示所有分区 / U 盘的 UUID 和文件系统类型,必须用`sudo`执行(普通用户看不了):
|
||||
|
||||
```text
|
||||
sudo blkid
|
||||
```
|
||||
|
||||
和手动挂载一样,自动挂载也需要 “挂载点”(空目录),建议放在`/mnt`(手动管理的设备)或`/media`(系统自动管理的设备)下
|
||||
|
||||
|
||||
### 测试配置是否正确(避免重启后失败)
|
||||
|
||||
编辑完别直接重启!用`mount -a`命令让系统 “加载新的 fstab 配置”,如果没报错,就代表配置没问题:
|
||||
|
||||
```text
|
||||
sudo mount -a # 加载fstab,没输出就是成功
|
||||
```
|
||||
|
||||
|
||||
U 盘自动挂载时,没加`nofail`,结果 U 盘没插,开机卡在 “waiting for device”(等设备),要等 1 分钟才进系统 —— 后来加了`nofail`,没插 U 盘也能正常开机。
|
||||
+74
@@ -0,0 +1,74 @@
|
||||

|
||||
|
||||
图示解析
|
||||
|
||||
本质上其实是三处地盘和两种用户之间的排列组合的结果:
|
||||
|
||||
三处“地盘”([根目录](https://zhida.zhihu.com/search?content_id=754281048&content_type=Answer&match_order=1&q=%E6%A0%B9%E7%9B%AE%E5%BD%95&zhida_source=entity)、/usr、/usr/local) × 两种“人群”(bin 给所有用户,sbin 给管理员)。
|
||||
|
||||
## **系统启动必需的目录**
|
||||
|
||||
### **/bin**
|
||||
|
||||
- 含基础命令,开机到最小救援都离不开它。
|
||||
|
||||
- 示例:ls、cp、mv、bash 等。
|
||||
|
||||
|
||||
### **/sbin**
|
||||
|
||||
- 系统管理、修复、分区等关键工具,多在救援/启动早期使用。
|
||||
|
||||
- 示例:reboot、fdisk、init 等。
|
||||
|
||||
|
||||
> 这些目录下断网断挂载也要能用。
|
||||
|
||||
|
||||
|
||||
## **[发行版](https://zhida.zhihu.com/search?content_id=754281048&content_type=Answer&match_order=1&q=%E5%8F%91%E8%A1%8C%E7%89%88&zhida_source=entity)的标准配给**
|
||||
|
||||
### **/usr/bin**(标准应用区)
|
||||
|
||||
- 发行版仓库装来的大多数[用户程序](https://zhida.zhihu.com/search?content_id=754281048&content_type=Answer&match_order=1&q=%E7%94%A8%E6%88%B7%E7%A8%8B%E5%BA%8F&zhida_source=entity)。
|
||||
|
||||
- 示例:gcc、python、vim、git。
|
||||
|
||||
|
||||
### **/usr/sbin**(标准运维区)
|
||||
|
||||
- 官方提供的[服务端](https://zhida.zhihu.com/search?content_id=754281048&content_type=Answer&match_order=1&q=%E6%9C%8D%E5%8A%A1%E7%AB%AF&zhida_source=entity)/管理类程序与工具。
|
||||
|
||||
- 示例:useradd、cron、sshd。
|
||||
|
||||
|
||||
> 这两个相当于系统的“总仓库”,绝大多数工具从这里发货。
|
||||
|
||||
|
||||
|
||||
## **本机自装地盘**
|
||||
|
||||
### **/usr/local/bin**(自带装备)
|
||||
|
||||
- 你自己手动编译/安装、给所有用户用的程序与脚本。
|
||||
|
||||
- 很典型的,例如:源码 make install 的可执行文件、自定义脚本。
|
||||
|
||||
|
||||
### **/usr/local/sbin**(自建运维角)
|
||||
|
||||
- 你自装的管理员工具、服务启动脚本、定制小工具。
|
||||
|
||||
|
||||
|
||||
> 这俩类似“私人工具柜”,不影响系统仓库,备份/迁移也更省心。
|
||||
|
||||
---
|
||||
|
||||
或者也可以这么看和区分:
|
||||
|
||||
- **谁能用**:bin对应普通用户,sbin对应管理员(当然他也可以用bin)。
|
||||
|
||||
- **谁提供**:系统/发行版(/usr) ,本机管理员(/usr/local)。
|
||||
|
||||
- **是否启动必需**:必须(/bin、/sbin) ,非必需(/usr/*)。
|
||||
@@ -0,0 +1,141 @@
|
||||
以下是 Mountpoint for Amazon S3 的安装和与 S3 兼容服务(如 RustFS)配合使用的教程。
|
||||
|
||||
### 1. 安装 Mountpoint
|
||||
|
||||
Mountpoint 提供预编译的安装包,支持多种 Linux 发行版。
|
||||
|
||||
* **Ubuntu / Debian (基于 DEB)**
|
||||
```bash
|
||||
# 下载适用于 x86_64 架构的 .deb 包
|
||||
wget https://s3.amazonaws.com/mountpoint-s3-release/latest/x86_64/mount-s3.deb
|
||||
# 安装
|
||||
sudo apt-get install -y ./mount-s3.deb
|
||||
```
|
||||
对于 ARM64 架构,将 URL 中的 `x86_64` 替换为 `arm64`。
|
||||
|
||||
* **Amazon Linux / RHEL / CentOS (基于 RPM)**
|
||||
```bash
|
||||
# 下载适用于 x86_64 架构的 .rpm 包
|
||||
wget https://s3.amazonaws.com/mountpoint-s3-release/latest/x86_64/mount-s3.rpm
|
||||
# 安装
|
||||
sudo yum install -y ./mount-s3.rpm
|
||||
```
|
||||
对于 ARM64 架构,将 URL 中的 `x86_64` 替换为 `arm64`。
|
||||
|
||||
* **验证安装**
|
||||
安装完成后,运行以下命令验证是否成功:
|
||||
```bash
|
||||
mount-s3 --version
|
||||
```
|
||||
如果安装成功,会输出版本信息,例如 `mount-s3 1.21.0`。
|
||||
|
||||
### 2. 配置访问凭证
|
||||
|
||||
Mountpoint 使用与 AWS CLI 相同的方式发现凭证。对于自建的 S3 服务,你需要通过环境变量提供访问密钥:
|
||||
|
||||
```bash
|
||||
export AWS_ACCESS_KEY_ID=myaccesskey
|
||||
export AWS_SECRET_ACCESS_KEY=mysecretkey
|
||||
export AWS_ENDPOINT_URL=http://你的RustFS服务IP:9000
|
||||
```
|
||||
Mountpoint 会自动读取这些环境变量。
|
||||
|
||||
### 3. 挂载存储桶
|
||||
|
||||
#### 3.1 基础挂载(适用于 AWS S3)
|
||||
|
||||
如果你的目标是标准的 AWS S3,挂载命令非常简单:
|
||||
```bash
|
||||
mkdir ~/mnt
|
||||
mount-s3 my-bucket ~/mnt
|
||||
```
|
||||
此时,`~/mnt` 目录就成为了你 S3 存储桶的挂载点。
|
||||
|
||||
#### 3.2 挂载到 S3 兼容服务(如 RustFS)
|
||||
|
||||
对于非 AWS 的 S3 兼容服务,**必须指定服务端点(Endpoint)**。
|
||||
|
||||
Mountpoint 提供两种方式:
|
||||
|
||||
* **方式一:使用命令行参数 `--endpoint-url`**
|
||||
```bash
|
||||
mount-s3 my-bucket ~/mnt --endpoint-url http://你的RustFS服务IP:9000
|
||||
```
|
||||
|
||||
* **方式二:使用环境变量 `AWS_ENDPOINT_URL`(推荐)**
|
||||
这种方式更便于在容器或脚本中统一管理配置。
|
||||
```bash
|
||||
export AWS_ENDPOINT_URL=http://你的RustFS服务IP:9000
|
||||
mount-s3 my-bucket ~/mnt
|
||||
```
|
||||
|
||||
> **注意**:Mountpoint 默认使用 `virtual-hosted-style` 的请求方式。如果你的 S3 服务(如 RustFS)不支持这种样式,可能会遇到 DNS 解析问题。此时,你可能需要在挂载时添加 `--endpoint-url` 参数,并确保其指向正确的服务地址。
|
||||
|
||||
### 4. 启用 10GB 本地缓存
|
||||
|
||||
Mountpoint 支持可选的本地缓存,用于加速对热点数据的重复读取。它的工作模式是按需加载,只有被访问过的数据才会被缓存。
|
||||
|
||||
你可以通过以下参数启用并限制缓存大小:
|
||||
|
||||
* `--cache <目录>`:指定缓存数据存放的本地目录。
|
||||
* `--max-cache-size <大小>`:**以 MiB 为单位**设置缓存的最大容量。缓存淘汰策略为 **LRU(最近最少使用)**。
|
||||
|
||||
**挂载命令示例(10GB = 10240 MiB):**
|
||||
|
||||
```bash
|
||||
# 创建缓存目录
|
||||
mkdir ~/cache
|
||||
|
||||
# 挂载并启用缓存,限制最大10GB
|
||||
mount-s3 my-bucket ~/mnt \
|
||||
--endpoint-url http://你的RustFS服务IP:9000 \
|
||||
--cache ~/cache \
|
||||
--max-cache-size 10240
|
||||
```
|
||||
|
||||
当缓存占满后,Mountpoint 会自动淘汰最久未使用的数据以腾出空间。
|
||||
|
||||
### 5. 其他常用选项
|
||||
|
||||
* **元数据缓存 (`--metadata-ttl`)**:缓存文件的元数据(如大小、存在性),可减少对 S3 的请求。但需要注意,这可能会使外部对文件的修改无法及时看到。
|
||||
* **后台运行**:`mount-s3` 命令默认在后台运行,不会阻塞终端。
|
||||
* **卸载挂载点**:
|
||||
```bash
|
||||
umount ~/mnt
|
||||
```
|
||||
如果权限不足,可能需要使用 `sudo`。
|
||||
|
||||
### 6. 核心注意事项
|
||||
|
||||
1. **缓存仅加速读取**:本地缓存**仅对读取操作生效**。所有的写入(创建、修改、删除)操作都会**直接提交到后端的 S3 服务**,不会先写入缓存。
|
||||
2. **非完整 POSIX 兼容**:Mountpoint 并非一个完整的 POSIX 文件系统,它针对高吞吐量的读写访问进行了优化。一些依赖复杂文件系统特性(如文件锁、稀疏文件等)的应用可能无法正常工作。
|
||||
3. **权限问题**:如果遇到权限错误,可以尝试在挂载命令中添加 `--allow-other` 参数,允许其他用户访问挂载点。
|
||||
|
||||
### 7. 在 Docker 容器中使用
|
||||
|
||||
Mountpoint 也提供了官方 Docker 镜像,方便在容器化环境中使用。
|
||||
|
||||
```bash
|
||||
# 拉取镜像
|
||||
docker pull public.ecr.aws/mountpoint-s3/mountpoint-s3
|
||||
|
||||
# 运行容器并挂载
|
||||
docker run -it --rm \
|
||||
--cap-add SYS_ADMIN \
|
||||
--device /dev/fuse \
|
||||
-e AWS_ACCESS_KEY_ID=myaccesskey \
|
||||
-e AWS_SECRET_ACCESS_KEY=mysecretkey \
|
||||
-e AWS_ENDPOINT_URL=http://你的RustFS服务IP:9000 \
|
||||
public.ecr.aws/mountpoint-s3/mountpoint-s3 \
|
||||
my-bucket /mnt
|
||||
```
|
||||
注意,运行需要添加 `--cap-add SYS_ADMIN` 和 `--device /dev/fuse` 权限。
|
||||
|
||||
|
||||
|
||||
## 查询所有挂载
|
||||
mount | grep mountpoint-s3
|
||||
|
||||
|
||||
## 取消挂载
|
||||
umount
|
||||
@@ -0,0 +1,163 @@
|
||||
监测管理网络设备的工具。SNMP="Simple Network Management Protocol"="简单网络管理协议",应用层协议,传输层一般采用UDP。SNMP采用 UDP 161端口接收和发送请求,162端口接收trap。
|
||||
|
||||
SNMP 目前共有 3 个版本,分别为 v1、v2 和 v3,说明如下:
|
||||
- SNMP v1:是 SNMP 协议的最初版本,在 1988 年被制定,并被 Internet 体系结构委员会(IAB)采纳作为一个短期的网络管理解决方案。不过依然是众多厂家实现SNMP基本方式。
|
||||
- SNMP v2:是 1992 年发布的 SNMP 的第二个版本。它修订了第一版,并且在性能、安全、机密性和管理者之间通信等方面进行了大量改进。通常被指是基于community的SNMP V2。Community实质上就是密码。
|
||||
- SNMP v3:是目前最新的版本。它是 2004 年制定的,协议编号为 RFC3411-RFC3418(STD0062)。它为提升协议的安全性,增加了认证和密文传输功能,它对网络管理最大的贡献在于其安全性
|
||||
|
||||
## 架构
|
||||
SNMP网络架构组成:NMS(网络管理站)、Agent(代理)、MIB(管理信息库)
|
||||
|
||||
Agent(代理)运行在被管理的网络节点上。
|
||||
NMS(网络管理站)负责收集维护各个SNMP 元素的信息(NMS主动向Agent请求,或Agent主动SNMP trap 上传),能请求/上传哪些内容去MIB中读取(MIB定义了设备中的被管理对象)。
|
||||
|
||||
|
||||
![[7c1f20e259bd77445fba6dd39df75b22_f0a37d4194ff4267bfedd0e8e852b3dd.png]]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 配置
|
||||
```
|
||||
cd /etc/snmp/
|
||||
```
|
||||
有 snmpd.conf 、snmptrapd.conf 两个配置文件。
|
||||
|
||||
|
||||
|
||||
## 允许远程主机访问
|
||||
|
||||
默认情况下,snmp服务只是对本地开启,是无法通过远程获取该主机的snmp信息的:
|
||||
|
||||
> root@apig:/etc/snmp# sudo netstat -antup | grep 161
|
||||
> udp 0 0 127.0.0.1:161 0.0.0.0:* 19410/snmpd
|
||||
|
||||
可以看到,161端口只对本机开放(161端口号是snmp服务的端口号),我们需要修改一下,让snmp服务对外开放。
|
||||
|
||||
修改`/etc/snmp/snmpd.conf`配置文件,大概在15行,将下面一行注释掉:
|
||||
|
||||
> agentAddress udp:127.0.0.1:161
|
||||
|
||||
同时去掉下面这一行的注释:
|
||||
|
||||
> #agentAddress udp:161,udp6:[::1]:161
|
||||
|
||||

|
||||
|
||||
重新启动snmp服务,再通过命令观察:
|
||||
|
||||
> root@apig:/etc/snmp# sudo service snmpd restart
|
||||
> root@apig:/etc/snmp# sudo netstat -antup | grep 161
|
||||
> udp 0 0 0.0.0.0:161 0.0.0.0:* 19770/snmpd
|
||||
> udp6 0 0 ::1:161 :::* 19770/snmpd
|
||||
>
|
||||
|
||||
|
||||
|
||||
## 配置共同体(可理解为密码)
|
||||
snmpd.conf 中需要配置
|
||||
|
||||
```
|
||||
rocommunity public
|
||||
syslocation "Your Location"
|
||||
syscontact your@email.com
|
||||
```
|
||||
|
||||
SNMP community strings leverage a user ID or password to access the router or other device information. SNMP community strings are only available with SNMPv1 and SNMPv2c protocols. SNMPv3 requires username/password authentication as well as an encryption key. Many SNMPv1 and v2c devices are shipped with a “public” read-only community string. Most network administrators customize the community strings in the device settings as a best practice.
|
||||
|
||||
|
||||
|
||||
自行修改
|
||||
```
|
||||
rocommunity sunyard
|
||||
syslocation "Hangzhou Zhejiang China"
|
||||
syscontact scm@sunyard.com
|
||||
|
||||
```
|
||||
|
||||
|
||||
![[Pasted image 20241023152807.png]]
|
||||
|
||||
|
||||
|
||||
|
||||
## SNMP OID
|
||||
Network Interface Statistics
|
||||
List NIC names: .1.3.6.1.2.1.2.2.1.2
|
||||
Get Bytes IN: .1.3.6.1.2.1.2.2.1.10
|
||||
Get Bytes IN for NIC 4: .1.3.6.1.2.1.2.2.1.10.4
|
||||
Get Bytes OUT: .1.3.6.1.2.1.2.2.1.16
|
||||
Get Bytes OUT for NIC 4: .1.3.6.1.2.1.2.2.1.16.4
|
||||
|
||||
|
||||
|
||||
CPU Statistics
|
||||
Load
|
||||
1 minute Load: .1.3.6.1.4.1.2021.10.1.3.1
|
||||
5 minute Load: .1.3.6.1.4.1.2021.10.1.3.2
|
||||
15 minute Load: .1.3.6.1.4.1.2021.10.1.3.3
|
||||
|
||||
|
||||
|
||||
CPU times
|
||||
percentage of user CPU time: .1.3.6.1.4.1.2021.11.9.0
|
||||
raw user cpu time: .1.3.6.1.4.1.2021.11.50.0
|
||||
percentages of system CPU time: .1.3.6.1.4.1.2021.11.10.0
|
||||
raw system cpu time: .1.3.6.1.4.1.2021.11.52.0
|
||||
percentages of idle CPU time: .1.3.6.1.4.1.2021.11.11.0
|
||||
raw idle cpu time: .1.3.6.1.4.1.2021.11.53.0
|
||||
raw nice cpu time: .1.3.6.1.4.1.2021.11.51.0
|
||||
|
||||
|
||||
|
||||
Memory Statistics
|
||||
Total Swap Size: .1.3.6.1.4.1.2021.4.3.0
|
||||
Available Swap Space: .1.3.6.1.4.1.2021.4.4.0
|
||||
Total RAM in machine: .1.3.6.1.4.1.2021.4.5.0
|
||||
Total RAM used: .1.3.6.1.4.1.2021.4.6.0
|
||||
Total RAM Free: .1.3.6.1.4.1.2021.4.11.0
|
||||
Total RAM Shared: .1.3.6.1.4.1.2021.4.13.0
|
||||
Total RAM Buffered: .1.3.6.1.4.1.2021.4.14.0
|
||||
Total Cached Memory: .1.3.6.1.4.1.2021.4.15.0
|
||||
|
||||
|
||||
|
||||
Disk Statistics
|
||||
Add the following line to snmpd.conf and restart:
|
||||
`includeAllDisks 10% for all partitions and disks`
|
||||
|
||||
|
||||
|
||||
Disk OID's
|
||||
Path where the disk is mounted: .1.3.6.1.4.1.2021.9.1.2.1
|
||||
Path of the device for the partition: .1.3.6.1.4.1.2021.9.1.3.1
|
||||
Total size of the disk/partion (kBytes): .1.3.6.1.4.1.2021.9.1.6.1
|
||||
Available space on the disk: .1.3.6.1.4.1.2021.9.1.7.1
|
||||
Used space on the disk: .1.3.6.1.4.1.2021.9.1.8.1
|
||||
Percentage of space used on disk: .1.3.6.1.4.1.2021.9.1.9.1
|
||||
Percentage of inodes used on disk: .1.3.6.1.4.1.2021.9.1.10.1
|
||||
|
||||
|
||||
|
||||
System Uptime: .1.3.6.1.2.1.1.3.0
|
||||
|
||||
|
||||
|
||||
# 什么是SNMP trap?
|
||||
|
||||
被管理设备主动上发消息
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 参考
|
||||
- [Linux SNMP OID’s for CPU, Memory and Disk Statistics](http://www.linux-admins.net/2012/02/linux-snmp-oids-for-cpumemory-and-disk.html)
|
||||
- [SNMP的安装与Trap的发送与接收java实现](https://blog.csdn.net/weixin_42551921/article/details/117479344)
|
||||
@@ -0,0 +1,163 @@
|
||||
监测管理网络设备的工具。SNMP="Simple Network Management Protocol"="简单网络管理协议",应用层协议,传输层一般采用UDP。SNMP采用 UDP 161端口接收和发送请求,162端口接收trap。
|
||||
|
||||
SNMP 目前共有 3 个版本,分别为 v1、v2 和 v3,说明如下:
|
||||
- SNMP v1:是 SNMP 协议的最初版本,在 1988 年被制定,并被 Internet 体系结构委员会(IAB)采纳作为一个短期的网络管理解决方案。不过依然是众多厂家实现SNMP基本方式。
|
||||
- SNMP v2:是 1992 年发布的 SNMP 的第二个版本。它修订了第一版,并且在性能、安全、机密性和管理者之间通信等方面进行了大量改进。通常被指是基于community的SNMP V2。Community实质上就是密码。
|
||||
- SNMP v3:是目前最新的版本。它是 2004 年制定的,协议编号为 RFC3411-RFC3418(STD0062)。它为提升协议的安全性,增加了认证和密文传输功能,它对网络管理最大的贡献在于其安全性
|
||||
|
||||
## 架构
|
||||
SNMP网络架构组成:NMS(网络管理站)、Agent(代理)、MIB(管理信息库)
|
||||
|
||||
Agent(代理)运行在被管理的网络节点上。
|
||||
NMS(网络管理站)负责收集维护各个SNMP 元素的信息(NMS主动向Agent请求,或Agent主动SNMP trap 上传),能请求/上传哪些内容去MIB中读取(MIB定义了设备中的被管理对象)。
|
||||
|
||||
|
||||
![[7c1f20e259bd77445fba6dd39df75b22_f0a37d4194ff4267bfedd0e8e852b3dd.png]]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 配置
|
||||
```
|
||||
cd /etc/snmp/
|
||||
```
|
||||
有 snmpd.conf 、snmptrapd.conf 两个配置文件。
|
||||
|
||||
|
||||
|
||||
## 允许远程主机访问
|
||||
|
||||
默认情况下,snmp服务只是对本地开启,是无法通过远程获取该主机的snmp信息的:
|
||||
|
||||
> root@apig:/etc/snmp# sudo netstat -antup | grep 161
|
||||
> udp 0 0 127.0.0.1:161 0.0.0.0:* 19410/snmpd
|
||||
|
||||
可以看到,161端口只对本机开放(161端口号是snmp服务的端口号),我们需要修改一下,让snmp服务对外开放。
|
||||
|
||||
修改`/etc/snmp/snmpd.conf`配置文件,大概在15行,将下面一行注释掉:
|
||||
|
||||
> agentAddress udp:127.0.0.1:161
|
||||
|
||||
同时去掉下面这一行的注释:
|
||||
|
||||
> #agentAddress udp:161,udp6:[::1]:161
|
||||
|
||||

|
||||
|
||||
重新启动snmp服务,再通过命令观察:
|
||||
|
||||
> root@apig:/etc/snmp# sudo service snmpd restart
|
||||
> root@apig:/etc/snmp# sudo netstat -antup | grep 161
|
||||
> udp 0 0 0.0.0.0:161 0.0.0.0:* 19770/snmpd
|
||||
> udp6 0 0 ::1:161 :::* 19770/snmpd
|
||||
>
|
||||
|
||||
|
||||
|
||||
## 配置共同体(可理解为密码)
|
||||
snmpd.conf 中需要配置
|
||||
|
||||
```
|
||||
rocommunity public
|
||||
syslocation "Your Location"
|
||||
syscontact your@email.com
|
||||
```
|
||||
|
||||
SNMP community strings leverage a user ID or password to access the router or other device information. SNMP community strings are only available with SNMPv1 and SNMPv2c protocols. SNMPv3 requires username/password authentication as well as an encryption key. Many SNMPv1 and v2c devices are shipped with a “public” read-only community string. Most network administrators customize the community strings in the device settings as a best practice.
|
||||
|
||||
|
||||
|
||||
自行修改
|
||||
```
|
||||
rocommunity sunyard
|
||||
syslocation "Hangzhou Zhejiang China"
|
||||
syscontact scm@sunyard.com
|
||||
|
||||
```
|
||||
|
||||
|
||||
![[Pasted image 20241023152807.png]]
|
||||
|
||||
|
||||
|
||||
|
||||
## SNMP OID
|
||||
Network Interface Statistics
|
||||
List NIC names: .1.3.6.1.2.1.2.2.1.2
|
||||
Get Bytes IN: .1.3.6.1.2.1.2.2.1.10
|
||||
Get Bytes IN for NIC 4: .1.3.6.1.2.1.2.2.1.10.4
|
||||
Get Bytes OUT: .1.3.6.1.2.1.2.2.1.16
|
||||
Get Bytes OUT for NIC 4: .1.3.6.1.2.1.2.2.1.16.4
|
||||
|
||||
|
||||
|
||||
CPU Statistics
|
||||
Load
|
||||
1 minute Load: .1.3.6.1.4.1.2021.10.1.3.1
|
||||
5 minute Load: .1.3.6.1.4.1.2021.10.1.3.2
|
||||
15 minute Load: .1.3.6.1.4.1.2021.10.1.3.3
|
||||
|
||||
|
||||
|
||||
CPU times
|
||||
percentage of user CPU time: .1.3.6.1.4.1.2021.11.9.0
|
||||
raw user cpu time: .1.3.6.1.4.1.2021.11.50.0
|
||||
percentages of system CPU time: .1.3.6.1.4.1.2021.11.10.0
|
||||
raw system cpu time: .1.3.6.1.4.1.2021.11.52.0
|
||||
percentages of idle CPU time: .1.3.6.1.4.1.2021.11.11.0
|
||||
raw idle cpu time: .1.3.6.1.4.1.2021.11.53.0
|
||||
raw nice cpu time: .1.3.6.1.4.1.2021.11.51.0
|
||||
|
||||
|
||||
|
||||
Memory Statistics
|
||||
Total Swap Size: .1.3.6.1.4.1.2021.4.3.0
|
||||
Available Swap Space: .1.3.6.1.4.1.2021.4.4.0
|
||||
Total RAM in machine: .1.3.6.1.4.1.2021.4.5.0
|
||||
Total RAM used: .1.3.6.1.4.1.2021.4.6.0
|
||||
Total RAM Free: .1.3.6.1.4.1.2021.4.11.0
|
||||
Total RAM Shared: .1.3.6.1.4.1.2021.4.13.0
|
||||
Total RAM Buffered: .1.3.6.1.4.1.2021.4.14.0
|
||||
Total Cached Memory: .1.3.6.1.4.1.2021.4.15.0
|
||||
|
||||
|
||||
|
||||
Disk Statistics
|
||||
Add the following line to snmpd.conf and restart:
|
||||
`includeAllDisks 10% for all partitions and disks`
|
||||
|
||||
|
||||
|
||||
Disk OID's
|
||||
Path where the disk is mounted: .1.3.6.1.4.1.2021.9.1.2.1
|
||||
Path of the device for the partition: .1.3.6.1.4.1.2021.9.1.3.1
|
||||
Total size of the disk/partion (kBytes): .1.3.6.1.4.1.2021.9.1.6.1
|
||||
Available space on the disk: .1.3.6.1.4.1.2021.9.1.7.1
|
||||
Used space on the disk: .1.3.6.1.4.1.2021.9.1.8.1
|
||||
Percentage of space used on disk: .1.3.6.1.4.1.2021.9.1.9.1
|
||||
Percentage of inodes used on disk: .1.3.6.1.4.1.2021.9.1.10.1
|
||||
|
||||
|
||||
|
||||
System Uptime: .1.3.6.1.2.1.1.3.0
|
||||
|
||||
|
||||
|
||||
# 什么是SNMP trap?
|
||||
|
||||
被管理设备主动上发消息
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 参考
|
||||
- [Linux SNMP OID’s for CPU, Memory and Disk Statistics](http://www.linux-admins.net/2012/02/linux-snmp-oids-for-cpumemory-and-disk.html)
|
||||
- [SNMP的安装与Trap的发送与接收java实现](https://blog.csdn.net/weixin_42551921/article/details/117479344)
|
||||
@@ -0,0 +1 @@
|
||||
https://link.zhihu.com/?target=https%3A//github.com/aquasecurity/trivy
|
||||
@@ -0,0 +1,327 @@
|
||||
虽然说只要高级语言能转换成 LLVM IR,就能被编译成 WebAssembly 字节码,官方也推荐c/c++的方式,但是让一个前端工程师去熟练使用c/c++显然是有点困难,那么TypeScript 的方式便是前端编写 WebAssembly 最佳选择。
|
||||
|
||||
要将TypeScript 编译为WebAssembly,就要用到[AssemblyScript](https://github.com/AssemblyScript)编译器了。
|
||||
|
||||
AssemblyScript使用Binaryen(Emscripten的WebAssembly后端)将严格类型化的TypeScript(基本的带有类型的JavaScript)编译为WebAssembly,虽然它提供了几种新的特定于WebAssembly的类型和内置函数,但它本身并不是一种真正的语言,而是一种编译器变体。 它生成精简的WebAssembly模块,只需要一个npm安装。
|
||||
|
||||
## 1.安装
|
||||
|
||||
推荐使用cnpm安装,npm太慢了。
|
||||
|
||||
1 cnpm install --save-dev AssemblyScript/assemblyscript
|
||||
|
||||
执行过程如下,安装完成之后只有一个node_modules文件夹
|
||||
|
||||
[](https://assets.cnblogs.com/images/copycode.gif)
|
||||
|
||||
1 E:\Code\assembly>cnpm install --save-dev AssemblyScript/assemblyscript
|
||||
2 - [@AssemblyScript/assemblyscript] install from git github:AssemblyScript/assemblyscript, may be very slow, please keep patience
|
||||
3 √ Installed 1 packages
|
||||
4 √ Linked 15 latest versions
|
||||
5 √ Run 0 scripts
|
||||
6 Recently updated (since 2019-02-17): 1 packages (detail see file E:\Code\assembly\node_modules\.recently_updates.txt)
|
||||
7 √ All packages installed (14 packages installed from npm registry, 1 packages installed from git, used 1m(network 1m), speed 10.27kB/s, json 14(31.43kB), tarball 707.23kB)
|
||||
|
||||
[](https://assets.cnblogs.com/images/copycode.gif)
|
||||
|
||||
## 2.创建项目
|
||||
|
||||
正确安装AssemblyScript后,它会提供一个名为asinit的小型实用工具来构建一个新项目,例如,在当前目录中:
|
||||
|
||||
1 npx asinit .
|
||||
|
||||
就像使用express初始化项目一样,执行过程如下
|
||||
|
||||
[](https://assets.cnblogs.com/images/copycode.gif)
|
||||
|
||||
1 E:\Code\assembly>npx asinit .
|
||||
2 Version: 0.6.0
|
||||
3
|
||||
4 This command will make sure that the following files exist in the project 5 directory 'E:\Code\assembly':
|
||||
6
|
||||
7 ./assembly
|
||||
8 Directory holding the AssemblyScript sources being compiled to WebAssembly.
|
||||
9
|
||||
10 ./assembly/tsconfig.json
|
||||
11 TypeScript configuration inheriting recommended AssemblyScript settings.
|
||||
12
|
||||
13 ./assembly/index.ts
|
||||
14 Exemplary entry file being compiled to WebAssembly to get you started.
|
||||
15
|
||||
16 ./build
|
||||
17 Build artifact directory where compiled WebAssembly files are stored.
|
||||
18
|
||||
19 ./build/.gitignore
|
||||
20 Git configuration that excludes compiled binaries from source control.
|
||||
21
|
||||
22 ./index.js
|
||||
23 Main file loading the WebAssembly module and exporting its exports.
|
||||
24
|
||||
25 ./package.json
|
||||
26 Package info containing the necessary commands to compile to WebAssembly.
|
||||
27
|
||||
28 The command will try to update existing files to match the correct settings
|
||||
29 for this instance of the compiler in 'E:\Code\assembly\node_modules\_assemblyscript@0.6.0@assemblyscript'.
|
||||
30
|
||||
31 Do you want to proceed? [Y/n] y
|
||||
32
|
||||
33 - Making sure that the project directory exists...
|
||||
34 Exists: E:\Code\assembly
|
||||
35
|
||||
36 - Making sure that the 'assembly' directory exists...
|
||||
37 Created: E:\Code\assembly\assembly
|
||||
38
|
||||
39 - Making sure that 'assembly/tsconfig.json' is set up...
|
||||
40 Created: E:\Code\assembly\assembly\tsconfig.json
|
||||
41
|
||||
42 - Making sure that 'assembly/index.ts' exists...
|
||||
43 Created: E:\Code\assembly\assembly\index.ts
|
||||
44
|
||||
45 - Making sure that the 'build' directory exists...
|
||||
46 Created: E:\Code\assembly\build
|
||||
47
|
||||
48 - Making sure that 'build/.gitignore' is set up...
|
||||
49 Created: E:\Code\assembly\build\.gitignore
|
||||
50
|
||||
51 - Making sure that 'package.json' contains the build commands...
|
||||
52 Updated: E:\Code\assembly\package.json
|
||||
53
|
||||
54 - Making sure that 'index.js' exists...
|
||||
55 Created: E:\Code\assembly\index.js
|
||||
56
|
||||
57 Done!
|
||||
58
|
||||
59 To edit the entry file, open 'assembly/index.ts' in your editor of choice.
|
||||
60 Create as many additional files as necessary and use them as imports.
|
||||
61
|
||||
62 To build the entry file to WebAssembly when you are ready, run:
|
||||
63
|
||||
64 npm run asbuild
|
||||
65
|
||||
66 Running the command above creates the following binaries incl. their respective
|
||||
67 text format representations and source maps:
|
||||
68
|
||||
69 ./build/untouched.wasm
|
||||
70 ./build/untouched.wasm.map
|
||||
71 ./build/untouched.wat
|
||||
72
|
||||
73 ^ The untouched WebAssembly module as generated by the compiler.
|
||||
74 This one matches your sources exactly, without any optimizations.
|
||||
75
|
||||
76 ./build/optimized.wasm
|
||||
77 ./build/optimized.wasm.map
|
||||
78 ./build/optimized.wat
|
||||
79
|
||||
80 ^ The optimized WebAssembly module using default optimization settings (-O2s).
|
||||
81 You can change the optimization settings in 'package.json'.
|
||||
82
|
||||
83 Additional documentation is available at the AssemblyScript wiki:
|
||||
84
|
||||
85 https://github.com/AssemblyScript/assemblyscript/wiki
|
||||
86
|
||||
87 Have a nice day!
|
||||
88
|
||||
89 E:\Code\assembly>
|
||||
|
||||
[](https://assets.cnblogs.com/images/copycode.gif)
|
||||
|
||||
完成的目录结构是这样的:
|
||||
|
||||
[](https://img2018.cnblogs.com/blog/1604637/201902/1604637-20190224233253174-1657805576.png)
|
||||
|
||||
目录结构含义:
|
||||
|
||||
assembly / index.ts上的示例性条目文件
|
||||
|
||||
index.js中的通用索引文件,用于加载已编译的二进制文件和必要的配置文件,如package.json和tsconfig.json
|
||||
|
||||
初始化后,只需在编码时使用现有的TypeScript工具,并使用编译器手动构建到WebAssembly,或者使用生成的构建任务:按照提示,运行以下命令:
|
||||
|
||||
1 npm run asbuild
|
||||
|
||||
build文件夹初始化只有一个.gitignore,该命令会在build文件夹中创建.wat与.wasm文件
|
||||
|
||||
以上只是构建一个示例项目,如果需要适合开发的安装,可以通过克隆GitHub存储库来实现:
|
||||
|
||||
1 $> git clone https://github.com/AssemblyScript/assemblyscript.git
|
||||
2 $> cd assemblyscript
|
||||
3 $> npm install
|
||||
4 $> npm link
|
||||
|
||||
请注意,编译器的新克隆将使用dist /中的分发文件,但它也可以在npm运行清理后直接通过ts节点运行源,这在开发中很有用。 也可以通过运行asc -v来检查这种情况(如果它声明-dev则运行源)。
|
||||
|
||||
## 3.使用编译器
|
||||
|
||||
类似于TypeScript的tsc编译为JavaScript,AssemblyScript的asc编译为WebAssembly:
|
||||
|
||||
例如,用 TypeScript 实现斐波那契序列计算的模块 f.ts 如下:
|
||||
|
||||
[](https://assets.cnblogs.com/images/copycode.gif)
|
||||
|
||||
1 export function f(x: i32): i32 {
|
||||
2 if (x === 1 || x === 2) {
|
||||
3 return 1;
|
||||
4 }
|
||||
5 return f(x - 1) + f(x - 2)
|
||||
6 }
|
||||
|
||||
[](https://assets.cnblogs.com/images/copycode.gif)
|
||||
|
||||
执行以下asc命令,就能把以上代码编译成可运行的 WebAssembly 模块,比起c/c++的编译方式,这种人性化多了。
|
||||
|
||||
1 asc f.ts -o f.wasm
|
||||
|
||||
以上代码中出现了一个新的内置类型 i32,这是 AssemblyScript 在 TypeScript 的基础上内置的类型。 AssemblyScript 和 TypeScript 有细微区别,AssemblyScript 是 TypeScript 的子集,为了方便编译成 WebAssembly 在 TypeScript 的基础上加了更严格的[类型限制](https://github.com/AssemblyScript/assemblyscript/wiki/Limitations), 区别如下:
|
||||
|
||||
- 比 TypeScript 多了很多更细致的内置类型,以优化性能和内存占用,详情[文档](https://github.com/AssemblyScript/assemblyscript/wiki/Types);
|
||||
- 不能使用 any 和 undefined 类型,以及枚举类型;
|
||||
- 可空类型的变量必须是引用类型,而不能是基本数据类型如 string、number、boolean;
|
||||
- 函数中的可选参数必须提供默认值,函数必须有返回类型,无返回值的函数返回类型需要是 void;
|
||||
- 不能使用 JS 环境中的内置函数,只能使用 [AssemblyScript 提供的内置函数](https://github.com/AssemblyScript/assemblyscript/wiki/Built-in-functions)。
|
||||
|
||||
总体来说 AssemblyScript 比 TypeScript 又多了很多限制,编写起来会觉得局限性很大; 用 AssemblyScript 来写 WebAssembly 经常会出现 tsc 编译通过但运行 WebAssembly 时出错的情况,这很可能就是你没有遵守以上限制导致的;但 AssemblyScript 通过修改 TypeScript 编译器默认配置能在编译阶段找出大多错误。
|
||||
|
||||
AssemblyScript 的实现原理其实也借助了 LLVM,它通过 TypeScript 编译器把 TS 源码解析成 AST,再把 AST 翻译成 IR,再通过 LLVM 编译成 WebAssembly 字节码实现; 上面提到的各种限制都是为了方便把 AST 转换成 LLVM IR。
|
||||
|
||||
**asc编译器具体API如下**
|
||||
|
||||
[](https://assets.cnblogs.com/images/copycode.gif)
|
||||
|
||||
1 SYNTAX
|
||||
2 asc [entryFile ...] [options]
|
||||
3
|
||||
4 EXAMPLES
|
||||
5 asc hello.ts
|
||||
6 asc hello.ts -b hello.wasm -t hello.wat
|
||||
7 asc hello1.ts hello2.ts -b -O > hello.wasm
|
||||
8
|
||||
9 OPTIONS
|
||||
10 --version, -v Prints just the compiler's version and exits.
|
||||
11 --help, -h Prints this message and exits.
|
||||
12 --optimize, -O Optimizes the module. Also has the usual shorthands:
|
||||
13
|
||||
14 -O Uses defaults. Equivalent to -O2s
|
||||
15 -O0 Equivalent to --optimizeLevel 0
|
||||
16 -O1 Equivalent to --optimizeLevel 1
|
||||
17 -O2 Equivalent to --optimizeLevel 2
|
||||
18 -O3 Equivalent to --optimizeLevel 3
|
||||
19 -Oz Equivalent to -O but with --shrinkLevel 2
|
||||
20 -O3s Equivalent to -O3 with --shrinkLevel 1 etc.
|
||||
21
|
||||
22 --optimizeLevel How much to focus on optimizing code. [0-3]
|
||||
23 --shrinkLevel How much to focus on shrinking code size. [0-2, s=1, z=2]
|
||||
24 --validate, -c Validates the module using Binaryen. Exits if invalid.
|
||||
25 --baseDir Specifies the base directory of input and output files.
|
||||
26 --outFile, -o Specifies the output file. File extension indicates format.
|
||||
27 --binaryFile, -b Specifies the binary output file (.wasm).
|
||||
28 --textFile, -t Specifies the text output file (.wat).
|
||||
29 --asmjsFile, -a Specifies the asm.js output file (.js).
|
||||
30 --idlFile, -i Specifies the WebIDL output file (.webidl).
|
||||
31 --tsdFile, -d Specifies the TypeScript definition output file (.d.ts).
|
||||
32 --sourceMap Enables source map generation. Optionally takes the URL
|
||||
33 used to reference the source map from the binary file.
|
||||
34 --debug Enables debug information in emitted binaries.
|
||||
35 --noAssert Replaces assertions with just their value without trapping.
|
||||
36 --noEmit Performs compilation as usual but does not emit code.
|
||||
37 --importMemory Imports the memory instance provided by the embedder.
|
||||
38 --sharedMemory Declare memory as shared by settings the max shared memory.
|
||||
39 --memoryBase Sets the start offset of compiler-generated static memory.
|
||||
40 --importTable Imports the function table instance provided by the embedder.
|
||||
41 --noLib Does not include the shipped standard library.
|
||||
42 --lib Adds one or multiple paths to custom library components and
|
||||
43 uses exports of all top-level files at this path as globals.
|
||||
44 --use, -u Aliases a global object under another name, e.g., to switch
|
||||
45 the default 'Math' implementation used: --use Math=JSMath
|
||||
46 --trapMode Sets the trap mode to use.
|
||||
47
|
||||
48 allow Allow trapping operations. This is the default.
|
||||
49 clamp Replace trapping operations with clamping semantics.
|
||||
50 js Replace trapping operations with JS semantics.
|
||||
51
|
||||
52 --runPasses Specifies additional Binaryen passes to run after other
|
||||
53 optimizations, if any. See: Binaryen/src/passes/pass.cpp
|
||||
54 --enable Enables additional (experimental) WebAssembly features.
|
||||
55
|
||||
56 sign-extension Enables sign-extension operations
|
||||
57 mutable-global Enables mutable global imports and exports
|
||||
58 bulk-memory Enables bulk memory operations
|
||||
59 simd Enables SIMD types and operations.
|
||||
60 threads Enables threading and atomic operations.
|
||||
61
|
||||
62 --transform Specifies the path to a custom transform to 'require'.
|
||||
63 --measure Prints measuring information on I/O and compile times.
|
||||
64 --noColors Disables terminal colors.
|
||||
|
||||
[](https://assets.cnblogs.com/images/copycode.gif)
|
||||
|
||||
编译器API也可以以编程方式使用。 它接受与CLI相同的选项,但也允许您覆盖stdout和stderr和/或提供回调:
|
||||
|
||||
[](https://assets.cnblogs.com/images/copycode.gif)
|
||||
|
||||
1 const asc = require("assemblyscript/cli/asc");
|
||||
2 asc.main([
|
||||
3 "myModule.ts",
|
||||
4 "--binaryFile", "myModule.wasm",
|
||||
5 "--optimize",
|
||||
6 "--sourceMap",
|
||||
7 "--measure"
|
||||
8 ], {
|
||||
9 stdout: process.stdout,
|
||||
10 stderr: process.stderr
|
||||
11 }, function(err) {
|
||||
12 if (err)
|
||||
13 throw err;
|
||||
14 ...
|
||||
15 });
|
||||
|
||||
[](https://assets.cnblogs.com/images/copycode.gif)
|
||||
|
||||
可以通过编程方式获取可用的命令行选项:
|
||||
|
||||
1 const options = require("assemblyscript/cli/asc.json");
|
||||
2 ...
|
||||
|
||||
您也可以直接编译源字符串,例如在浏览器环境中:
|
||||
|
||||
1 const { binary, text, stdout, stderr } = asc.compileString(`...`, { optimize: 2 });
|
||||
2 ...
|
||||
|
||||
## 4.使用编译的.wasm模块
|
||||
|
||||
index.js示例代码如下:
|
||||
|
||||
[](https://assets.cnblogs.com/images/copycode.gif)
|
||||
|
||||
1 const fs = require("fs");
|
||||
2 const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/optimized.wasm"));
|
||||
3 const imports = {};
|
||||
4 Object.defineProperty(module, "exports", {
|
||||
5 get: () => new WebAssembly.Instance(compiled, imports).exports
|
||||
6 });
|
||||
|
||||
[](https://assets.cnblogs.com/images/copycode.gif)
|
||||
|
||||
使用上面编译的f.wasm
|
||||
|
||||
[](https://assets.cnblogs.com/images/copycode.gif)
|
||||
|
||||
1 fetch('f.wasm') // 网络加载 f.wasm 文件
|
||||
2 .then(res => res.arrayBuffer()) // 转成 ArrayBuffer
|
||||
3 .then(WebAssembly.instantiate) // 编译为当前 CPU 架构的机器码 + 实例化
|
||||
4 .then(mod => { // 调用模块实例上的 f 函数计算
|
||||
5 console.log(mod.instance.f(50));
|
||||
6 });
|
||||
|
||||
[](https://assets.cnblogs.com/images/copycode.gif)
|
||||
|
||||
## 5.参考文献:
|
||||
|
||||
[GitHub—AssemblyScript 开源项目](https://github.com/AssemblyScript/assemblyscript#installation)
|
||||
|
||||
[吴浩麟—WebAssembly 现状与实战](https://www.ibm.com/developerworks/cn/web/wa-lo-webassembly-status-and-reality/index.html)
|
||||
|
||||
- [1.安装](https://www.cnblogs.com/jixiaohua/p/10428785.html#tid-SJS3EZ)
|
||||
- [2.创建项目](https://www.cnblogs.com/jixiaohua/p/10428785.html#tid-4SQ4xa)
|
||||
- [3.使用编译器](https://www.cnblogs.com/jixiaohua/p/10428785.html#tid-djiMkK)
|
||||
- [4.使用编译的.wasm模块](https://www.cnblogs.com/jixiaohua/p/10428785.html#tid-RwBDGt)
|
||||
- [5.参考文献:](https://www.cnblogs.com/jixiaohua/p/10428785.html#tid-bRHtGH)
|
||||
@@ -0,0 +1,32 @@
|
||||
### 安装需要网络问题
|
||||
|
||||
|
||||
### 如何通过 ip 命令配置网络(临时)
|
||||
查看
|
||||
ip addr show
|
||||
|
||||
ip a
|
||||
|
||||
### 配置静态IP地址
|
||||
|
||||
要为网络接口配置静态 IP 地址,您可以使用 `ip addr add` 命令。例如,为 `eth0` 接口配置 IP 地址 `192.168.1.100` 和子网掩码 `255.255.255.0`:
|
||||
|
||||
`sudo ip addr add 192.168.1.100/24 dev eth0`
|
||||
|
||||
### 设置默认网关
|
||||
|
||||
要设置默认网关,可以使用 `ip route add` 命令。例如,设置网关为 `192.168.1.1`:
|
||||
|
||||
`sudo ip route add default via 192.168.1.1`
|
||||
|
||||
### 设置DNS服务器
|
||||
|
||||
在 Ubuntu 中,DNS 服务器的设置通常在 `/etc/resolv.conf` 文件中进行。您可以使用文本编辑器编辑该文件来添加 DNS 服务器:
|
||||
|
||||
`sudo nano /etc/resolv.conf`
|
||||
|
||||
然后添加您的 DNS 服务器,例如:
|
||||
|
||||
`nameserver 8.8.8.8 nameserver 8.8.4.4`
|
||||
|
||||
保存并退出编辑器。
|
||||
@@ -0,0 +1,32 @@
|
||||
### 安装需要网络问题
|
||||
|
||||
|
||||
### 如何通过 ip 命令配置网络(临时)
|
||||
查看
|
||||
ip addr show
|
||||
|
||||
ip a
|
||||
|
||||
### 配置静态IP地址
|
||||
|
||||
要为网络接口配置静态 IP 地址,您可以使用 `ip addr add` 命令。例如,为 `eth0` 接口配置 IP 地址 `192.168.1.100` 和子网掩码 `255.255.255.0`:
|
||||
|
||||
`sudo ip addr add 192.168.1.100/24 dev eth0`
|
||||
|
||||
### 设置默认网关
|
||||
|
||||
要设置默认网关,可以使用 `ip route add` 命令。例如,设置网关为 `192.168.1.1`:
|
||||
|
||||
`sudo ip route add default via 192.168.1.1`
|
||||
|
||||
### 设置DNS服务器
|
||||
|
||||
在 Ubuntu 中,DNS 服务器的设置通常在 `/etc/resolv.conf` 文件中进行。您可以使用文本编辑器编辑该文件来添加 DNS 服务器:
|
||||
|
||||
`sudo nano /etc/resolv.conf`
|
||||
|
||||
然后添加您的 DNS 服务器,例如:
|
||||
|
||||
`nameserver 8.8.8.8 nameserver 8.8.4.4`
|
||||
|
||||
保存并退出编辑器。
|
||||
@@ -0,0 +1,47 @@
|
||||
systemd-resolved 服务是用来提供本机应用软件与DNS 解析服务的,其包含解析主机名称、IP 地址、域名、DNS 资源记录等,Ubuntu修改DNS就需要通过此服务来完成!
|
||||
|
||||
# 问题
|
||||
|
||||
通常我们知道,修改dns的几个途径
|
||||
|
||||
- /etc/resolv.conf
|
||||
- /etc/netplan/01-netcfg.yaml
|
||||
|
||||
修改上面两个文件,一般情况下,可以解决,但是在一次使用新安装的ubuntu22.04的时候,发现,无论怎么修改,dig解析域名都是往127.0.0.53发送,哪怕在缓存中的已经生效
|
||||
|
||||
如何查看缓存中生效的配置呢
|
||||
|
||||
```bash
|
||||
cat /run/systemd/resolve/resolv.conf
|
||||
```
|
||||
|
||||
# 解决方法
|
||||
|
||||
需要删掉或者修改 **/etc/resolv.conf** 文件名
|
||||
|
||||
先修改dns配置
|
||||
|
||||
```bash
|
||||
vim /etc/systemd/resolved.conf
|
||||
[Resolve]
|
||||
DNS=223.6.6.6 114.114.114.114
|
||||
```
|
||||
|
||||
删除或者修改文件名
|
||||
|
||||
```bash
|
||||
mv /etc/resolv.conf /etc/resolv.conf.bak
|
||||
```
|
||||
|
||||
将 **/etc/systemd/resolved.conf** 新建快捷方式为 **/etc/resolv.conf**
|
||||
|
||||
```bash
|
||||
ln -s /run/systemd/resolve/resolv.conf /etc/
|
||||
```
|
||||
|
||||
最后重启网络
|
||||
|
||||
```bash
|
||||
systemctl restart systemd-resolved
|
||||
systemctl restart NetworkManager
|
||||
```
|
||||
@@ -0,0 +1,41 @@
|
||||
在Ubuntu中,默认情况下是禁用root用户的登录,而是使用sudo命令来获取root权限。如果你想要开启root用户权限,可以按照以下步骤操作:
|
||||
|
||||
1. 使用sudo命令获取root权限:
|
||||
|
||||
```
|
||||
sudo -i
|
||||
```
|
||||
|
||||
输入你当前用户的密码,即可进入root用户的环境。
|
||||
|
||||
2. 设置root用户的密码:
|
||||
|
||||
```
|
||||
sudo passwd root
|
||||
```
|
||||
|
||||
输入你当前用户的密码,并设置root用户的密码。
|
||||
|
||||
3. 打开root用户的登录权限: 编辑`/etc/ssh/sshd_config`文件:
|
||||
|
||||
```
|
||||
sudo nano /etc/ssh/sshd_config
|
||||
```
|
||||
|
||||
找到`PermitRootLogin`这一行,将其改为:
|
||||
|
||||
```
|
||||
PermitRootLogin yes
|
||||
```
|
||||
|
||||
保存文件并退出。
|
||||
|
||||
4. 重启SSH服务:
|
||||
|
||||
```
|
||||
sudo service ssh restart
|
||||
```
|
||||
|
||||
这样就成功开启了root用户权限。请注意,开启root用户权限会增加系统被攻击的风险,建议在使用完毕后再禁用root用户的登录权限。
|
||||
|
||||
购买使用亿速云服务器,可以极大降低初创企业、中小企业以及个人开发者等用户群体的整体IT使用成本,无需亲自搭建基础设施、简化了运维和管理的日常工作量,使用户能够更专注于自身的业务发展和创新。
|
||||
@@ -0,0 +1,62 @@
|
||||
### 一、组网说明
|
||||
|
||||
SW 3和SW 4两台交换机运行VRRP协议,为SW 1和SW 2提供虚拟网关服务,以保证SW 3和SW 4任意一台设备或链路中断的情况下SW 1还可以和SW 2通讯。
|
||||
|
||||
### 二、组网图
|
||||
|
||||

|
||||
|
||||
### 三、配置步骤
|
||||
|
||||
SW 3的配置
|
||||
#配置Vlan和三层接口
|
||||
switch(config)#**vlan** 10
|
||||
switch(config-Vlan10)#**switchport interface ethernet address** 1/1
|
||||
switch(config-Vlan10)#**ip address** 192.168.10.2 255.255.255.0
|
||||
switch(config-Vlan10)#**exit**
|
||||
switch(config)#**vlan** 20
|
||||
switch(config-Vlan20)#**switchport interface ethernet address** 1/2
|
||||
switch(config-Vlan20)#**ip address** 192.168.20.2 255.255.255.0
|
||||
switch(config-Vlan20)#**exit**
|
||||
#配置VRRP
|
||||
switch(config)#**router vrrp** 10
|
||||
switch(config-router)#**virtual-ip** 192.168.10.254
|
||||
switch(config-router)#**interface vlan** 10
|
||||
switch(config-router)#**priority** 120
|
||||
switch(config-router)#**enable**
|
||||
switch(config-router)#**exit**
|
||||
switch(config)#**router vrrp** 20
|
||||
switch(config-router)#**virtual-ip** 192.168.20.254
|
||||
switch(config-router)#**interface vlan** 20
|
||||
switch(config-router)#**priority** 120
|
||||
switch(config-router)#**enable**
|
||||
switch(config-router)#**exit**
|
||||
SW 4的配置
|
||||
#配置Vlan和三层接口
|
||||
switch(config)#**vlan** 10
|
||||
switch(config-Vlan10)#**switchport interface ethernet address** 1/1
|
||||
switch(config-Vlan10)#**ip address** 192.168.10.3 255.255.255.0
|
||||
switch(config-Vlan10)#**exit**
|
||||
switch(config)#**vlan** 20
|
||||
switch(config-Vlan20)#**switchport interface ethernet address** 1/2
|
||||
switch(config-Vlan20)#**ip address** 192.168.20.3 255.255.255.0
|
||||
switch(config-Vlan20)#**exit**
|
||||
#配置VRRP
|
||||
switch(config)#**router vrrp** 10
|
||||
switch(config-router)#**virtual-ip** 192.168.10.254
|
||||
switch(config-router)#**interface vlan** 10
|
||||
switch(config-router)#**enable**
|
||||
switch(config-router)#**exit**
|
||||
switch(config)#**router vrrp** 20
|
||||
switch(config-router)#**virtual-ip** 192.168.20.254
|
||||
switch(config-router)#**interface vlan** 20
|
||||
switch(config-router)#**enable**
|
||||
switch(config-router)#**exit**
|
||||
|
||||
### 四、注意事项
|
||||
|
||||
1.VRRP配置完成后,必须使用enable命令将VRRP使能。否则VRRP配置不生效。在修改VRRP组的配置时,也需要先将VRRP组Disable后才可以修改VRRP组的配置。
|
||||
2.VRRP组的ID号从1-255,总共可以创建255个VRRP组。
|
||||
3.VRRP组默认监控的是三层接口的UP/Down,在冗余链路的环境中,需要配合STP协议一起使用,以保证网络的可用性。(后续版本中,可以配合BFD功能监控物理接口的UP/Down)
|
||||
4.VRRP的交互报文只有定期发送的Hello报文,默认的交互时间是1S。当整机的VRRP组超过10组时,建议修改VRRP的交互间隔,避免设备在同一时间处理大量的VRRP报文。
|
||||
5.VRRP组根据优先级比较确认那台设备会成为Master,默认的优先级是100。优先级相同时,再比较IP地址,IP地址较大的一台会做为Master。
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
初始配置 vim ~/.vimrc
|
||||
```
|
||||
" 设置 F10 键为切换粘贴模式的快捷键
|
||||
set pastetoggle=<F10>
|
||||
set nu
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
# 1. 中文乱码问题
|
||||
查看服务器是否包含中文字体,注意是中文字体不是中文编码。
|
||||
查看包含哪些中文字体:
|
||||
fc-list :lang=zh
|
||||
若不包含,或者X11路径下未包含中文字体,则直接安装中文字体即可。
|
||||
安装中文字体:
|
||||
apt install fonts-wqy-microhei,随便安个中文字体就行
|
||||
|
||||
# 2. 中文输入法问题
|
||||
|
||||
|
||||
# 3. ubuntu安装 x11 桌面环境
|
||||
|
||||
要在Ubuntu上安装X11桌面环境,您可以按照以下步骤进行操作:
|
||||
|
||||
1. 在Ubuntu终端中,使用以下命令安装x11vnc:sudo apt-get install x11vnc [1。
|
||||
2. 安装完成后,您可以使用x11vnc命令来启动VNC服务器。
|
||||
3. 在终端中输入x11vnc命令时,您可能需要提供一些参数来配置VNC服务器的行为。例如,您可以指定密码、访问控制等。
|
||||
4. 安装完成后,您可以使用VNC客户端连接到Ubuntu上的X11桌面环境。您可以在客户端中输入Ubuntu的IP地址和配置的VNC服务器端口号来进行连接。
|
||||
|
||||
需要注意的是,Ubuntu 21.04之后,默认启用了Wayland协议,而不是传统的X11协议。这意味着有些基于X11的软件可能会在新的Wayland环境中出现兼容性问题。不过,您仍然可以使用上述方法安装和使用X11桌面环境。
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 109 KiB |
@@ -0,0 +1,231 @@
|
||||
# Fail2ban 安装与 `fail2ban-client` 命令使用手册
|
||||
|
||||
本手册基于 **Ubuntu 24.04** 环境,结合 UFW 防火墙,详细说明 Fail2ban 的安装、配置以及 `fail2ban-client` 的常用命令。
|
||||
|
||||
---
|
||||
|
||||
## 1. Fail2ban 安装
|
||||
|
||||
### 1.1 通过 APT 安装
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install fail2ban -y
|
||||
```
|
||||
|
||||
### 1.2 验证服务状态
|
||||
安装完成后,服务会自动启动:
|
||||
```bash
|
||||
sudo systemctl status fail2ban
|
||||
```
|
||||
- 如果未启动,可手动启动:`sudo systemctl start fail2ban`
|
||||
- 设置开机自启:`sudo systemctl enable fail2ban`
|
||||
|
||||
---
|
||||
|
||||
## 2. 配置 Fail2ban(使用 UFW 作为封禁后端)
|
||||
|
||||
### 2.1 创建自定义配置文件
|
||||
**不要直接修改 `/etc/fail2ban/jail.conf`**,因为包更新会覆盖它。应创建 `jail.local`:
|
||||
```bash
|
||||
sudo nano /etc/fail2ban/jail.local
|
||||
```
|
||||
|
||||
### 2.2 基础配置示例
|
||||
将以下内容粘贴到 `jail.local` 中:
|
||||
```ini
|
||||
[DEFAULT]
|
||||
# 封禁时间(1小时)
|
||||
bantime = 1h
|
||||
# 检测时间窗口(10分钟)
|
||||
findtime = 10m
|
||||
# 最大失败尝试次数
|
||||
maxretry = 5
|
||||
|
||||
# 关键:使用 UFW 执行封禁
|
||||
banaction = ufw
|
||||
|
||||
# 白名单(保护自己的IP不被封禁)
|
||||
# 格式:用空格分隔多个IP或CIDR
|
||||
ignoreip = 127.0.0.1/8 ::1 你的.公网.IP
|
||||
|
||||
# 启用 SSH 防护
|
||||
[sshd]
|
||||
enabled = true
|
||||
```
|
||||
|
||||
> **注意**:将 `你的.公网.IP` 替换为你的实际 IP。
|
||||
|
||||
### 2.3 重启 Fail2ban 使配置生效
|
||||
```bash
|
||||
sudo systemctl restart fail2ban
|
||||
```
|
||||
|
||||
### 2.4 确认 UFW 已开启
|
||||
启用 UFW 前,先放行 SSH 端口:
|
||||
```bash
|
||||
sudo ufw allow ssh
|
||||
sudo ufw enable
|
||||
sudo ufw status verbose
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. `fail2ban-client` 常用命令
|
||||
|
||||
`fail2ban-client` 是与 Fail2ban 服务交互的命令行工具。
|
||||
|
||||
### 3.1 查看整体状态
|
||||
```bash
|
||||
sudo fail2ban-client status
|
||||
```
|
||||
输出示例:
|
||||
```
|
||||
Status
|
||||
|- Number of jail: 1
|
||||
`- Jail list: sshd
|
||||
```
|
||||
|
||||
### 3.2 查看特定监狱(Jail)的详细信息
|
||||
```bash
|
||||
sudo fail2ban-client status sshd
|
||||
```
|
||||
输出示例:
|
||||
```
|
||||
Status for the jail: sshd
|
||||
|- Filter
|
||||
| |- Currently failed: 0
|
||||
| |- Total failed: 123
|
||||
| `- File list: /var/log/auth.log
|
||||
`- Actions
|
||||
|- Currently banned: 3
|
||||
|- Total banned: 15
|
||||
`- Banned IP list: 91.92.40.239 27.49.238.44 176.65.139.183
|
||||
```
|
||||
|
||||
### 3.3 查看所有监狱的被封禁 IP(汇总)
|
||||
```bash
|
||||
sudo fail2ban-client banned
|
||||
```
|
||||
输出格式类似:
|
||||
```
|
||||
{
|
||||
"sshd": ["91.92.40.239", "27.49.238.44"]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 手动封禁 IP
|
||||
```bash
|
||||
sudo fail2ban-client set <监狱名> banip <IP地址>
|
||||
```
|
||||
示例:
|
||||
```bash
|
||||
sudo fail2ban-client set sshd banip 203.0.113.5
|
||||
```
|
||||
|
||||
### 3.5 手动解封 IP
|
||||
```bash
|
||||
sudo fail2ban-client set <监狱名> unbanip <IP地址>
|
||||
```
|
||||
示例:
|
||||
```bash
|
||||
sudo fail2ban-client set sshd unbanip 203.0.113.5
|
||||
```
|
||||
|
||||
### 3.6 重载配置(修改配置后无需重启服务)
|
||||
```bash
|
||||
sudo fail2ban-client reload
|
||||
```
|
||||
若只想重载特定监狱:
|
||||
```bash
|
||||
sudo fail2ban-client reload sshd
|
||||
```
|
||||
|
||||
### 3.7 查看 Fail2ban 日志(含封禁记录)
|
||||
```bash
|
||||
sudo grep "Ban" /var/log/fail2ban.log
|
||||
```
|
||||
或实时监控:
|
||||
```bash
|
||||
sudo tail -f /var/log/fail2ban.log | grep "Ban"
|
||||
```
|
||||
|
||||
### 3.8 测试配置文件语法
|
||||
```bash
|
||||
sudo fail2ban-client -t
|
||||
```
|
||||
|
||||
### 3.9 前台运行调试(用于测试)
|
||||
```bash
|
||||
sudo fail2ban-client -f start
|
||||
```
|
||||
按 `Ctrl+C` 停止。
|
||||
|
||||
---
|
||||
|
||||
## 4. 封禁策略调整
|
||||
|
||||
### 4.1 修改封禁时长
|
||||
在 `jail.local` 的 `[DEFAULT]` 或具体监狱中修改 `bantime`:
|
||||
- `bantime = 1h`(1小时)
|
||||
- `bantime = 86400`(24小时,单位为秒)
|
||||
- 设为 `-1` 表示永久封禁。
|
||||
|
||||
### 4.2 修改失败次数阈值
|
||||
`maxretry` 决定在 `findtime` 内失败多少次触发封禁。
|
||||
|
||||
### 4.3 添加白名单
|
||||
在 `ignoreip` 中添加 IP 或网段,用空格分隔。
|
||||
|
||||
### 4.4 封禁其他服务(如 HTTP)
|
||||
可以启用 `[nginx-http-auth]` 等监狱,只需在 `jail.local` 中添加:
|
||||
```ini
|
||||
[nginx-http-auth]
|
||||
enabled = true
|
||||
port = http,https
|
||||
logpath = /var/log/nginx/error.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 常见问题排查
|
||||
|
||||
### 5.1 Fail2ban 未生效
|
||||
- 检查日志路径是否正确(如 `/var/log/auth.log`)。
|
||||
- 确认 `enabled = true` 已设置。
|
||||
- 查看服务日志:`sudo journalctl -u fail2ban -f`
|
||||
|
||||
### 5.2 封禁后仍可访问
|
||||
- 确认 `banaction = ufw` 配置正确。
|
||||
- 检查 UFW 是否启用:`sudo ufw status`
|
||||
- 查看防火墙实际规则:`sudo iptables -L -n | grep DROP` 或 `sudo nft list ruleset`
|
||||
|
||||
### 5.3 误封自己的 IP
|
||||
- 立即解封:`sudo fail2ban-client set sshd unbanip 你的IP`
|
||||
- 并检查 `ignoreip` 是否正确配置。
|
||||
|
||||
---
|
||||
|
||||
## 6. 卸载 Fail2ban
|
||||
```bash
|
||||
sudo apt remove fail2ban -y
|
||||
sudo apt autoremove -y
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 附录:常用命令速查表
|
||||
|
||||
| 目的 | 命令 |
|
||||
| :------------- | :------------------------------------------- |
|
||||
| 查看所有监狱状态 | `sudo fail2ban-client status` |
|
||||
| 查看某监狱详情(含封禁IP) | `sudo fail2ban-client status sshd` |
|
||||
| 查看所有被封禁IP | `sudo fail2ban-client banned` |
|
||||
| 手动封禁IP | `sudo fail2ban-client set sshd banip <IP>` |
|
||||
| 手动解封IP | `sudo fail2ban-client set sshd unbanip <IP>` |
|
||||
| 重载配置 | `sudo fail2ban-client reload` |
|
||||
| 查看封禁日志 | `sudo grep "Ban" /var/log/fail2ban.log` |
|
||||
| 测试配置 | `sudo fail2ban-client -t` |
|
||||
|
||||
---
|
||||
|
||||
本手册适用于 Ubuntu 24.04 + UFW 环境,按照以上步骤即可快速部署并管理 Fail2ban。如有其他需求,可参考官方文档或社区资源。
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
fcitx5是很好用的一个输入法框架,安装后不需要再安装搜狗拼音,百度输入法,谷歌输入法(谷歌输入法巨难用)等东西了。
|
||||
fcitx5有个缺点是,与gnome桌面合不来,之前折腾了好久,最后还是转投kde桌面了(真香)。
|
||||
(我是直接装的ubuntu server, 最开始没有桌面,可以直接装kde. 已经有桌面的小伙伴可以先把kde装了,然后再卸载gnome)
|
||||
|
||||
# 1. 安装kde桌面,可以直接装kubuntu-desktop,安装过程会让选桌面管理工具,建议选sddm. (gnome 是用gdm3)
|
||||
|
||||
```bash
|
||||
sudo apt-get install kubuntu-desktop
|
||||
```
|
||||
|
||||
然后重启即可。
|
||||
|
||||
# 2. 安装fcitx5
|
||||
|
||||
```bash
|
||||
sudo add-apt-repository ppa:hosxy/fcitx5
|
||||
sudo apt install fcitx5 fcitx5-*
|
||||
```
|
||||
|
||||
然后配置输入法为fcitx5
|
||||
|
||||
```bash
|
||||
im-config
|
||||
```
|
||||
|
||||
过程中记得选fcitx5
|
||||
|
||||
# 3. 配置fcitx5
|
||||
|
||||
设置环境变量:在~/.xprofile文件中写入:export QT_IM_MODULE=fcitx
|
||||
|
||||
```bash
|
||||
echo export QT_IM_MODULE=fcitx > ~/.xprofile
|
||||
source ~/.xprofile
|
||||
```
|
||||
|
||||
配置fcitx5,在~/.config/fcitx5/profile写入:(注意配置的时候要把fcitx5关闭,不然会被覆盖)
|
||||
|
||||
```csharp
|
||||
[Groups/0]
|
||||
Name="分组 1"
|
||||
Default Layout=us
|
||||
DefaultIM=pinyin
|
||||
[Groups/0/Items/0]
|
||||
Name=keyboard-us
|
||||
Layout=
|
||||
[Groups/0/Items/1]
|
||||
Name=pinyin
|
||||
Layout=us
|
||||
|
||||
[Groups/1]
|
||||
Name="分组 2"
|
||||
Default Layout=cn-altgr-pinyin
|
||||
DefaultIM=pinyin
|
||||
[Groups/1/Items/0]
|
||||
Name=keyboard-cn-altgr-pinyin
|
||||
Layout=
|
||||
[Groups/1/Items/1]
|
||||
Name=pinyin
|
||||
Layout=
|
||||
|
||||
[GroupOrder]
|
||||
0="分组 1"
|
||||
1="分组 2"
|
||||
```
|
||||
@@ -0,0 +1,395 @@
|
||||
# 别再无脑敲 grep 了,我靠这 5 个选项,定位问题速度提升 10 倍
|
||||
|
||||
|
||||
在日常运维工作中,我们几乎每天都在和日志、配置文件、代码片段打交道。
|
||||
|
||||
日志动辄几十 GB,配置文件成百上千行,出问题时,时间永远是最宝贵的资源。
|
||||
|
||||

|
||||
|
||||
很多同事都会用 grep,但**90% 的人只会用最基础的 grep "keyword" file**。 结果就是:
|
||||
|
||||
- 要么匹配不准
|
||||
|
||||
- 要么结果一大堆
|
||||
|
||||
- 要么漏掉关键信息
|
||||
|
||||
|
||||
今天这篇文章,我不讲 grep 的所有参数,而是分享**我在一线运维工作中,真正高频使用、最有价值的 5 个 grep 选项**。
|
||||
|
||||
只要你把这 5 个选项用熟,**你几乎可以在任何复杂场景下,快速定位你想要的内容**。
|
||||
|
||||
在进入正题之前,先统一一个认知。
|
||||
|
||||
|
||||
grep 是什么?
|
||||
|
||||
> grep = Global Regular Expression Print
|
||||
|
||||
但在运维眼里,它的真实含义是:
|
||||
|
||||
- 日志分析利器
|
||||
|
||||
- 故障定位放大镜
|
||||
|
||||
- 海量文本的“手术刀”
|
||||
|
||||
|
||||
无论你是:
|
||||
|
||||
- 查 Nginx / Tomcat / MySQL 日志
|
||||
|
||||
- 翻 Kubernetes 容器日志
|
||||
|
||||
- 排查线上报错
|
||||
|
||||
- 审计配置变更
|
||||
|
||||
- 甚至简单查代码
|
||||
|
||||
|
||||
**grep 几乎是第一反应。**
|
||||
|
||||
## 我最常用的 5 个 grep 选项总览
|
||||
|
||||
先给大家一个总览,后面逐个展开。
|
||||
|
||||
|选项|作用|使用频率|
|
||||
|---|---|---|
|
||||
|`-n`|显示行号|⭐⭐⭐⭐⭐|
|
||||
|`-i`|忽略大小写|⭐⭐⭐⭐|
|
||||
|`-v`|反向匹配|⭐⭐⭐⭐⭐|
|
||||
|`-C / -A / -B`|上下文查看|⭐⭐⭐⭐|
|
||||
|`-E`|扩展正则|⭐⭐⭐⭐⭐|
|
||||
|
||||
这 5 个选项,**几乎覆盖了我 80% 的 grep 使用场景**。
|
||||
|
||||
## 行号
|
||||
|
||||
### 1️⃣ 基本用法
|
||||
|
||||
```
|
||||
grep -n "error" application.log
|
||||
```
|
||||
|
||||
输出类似:
|
||||
|
||||
```
|
||||
2451:2024-06-01 10:23:11 ERROR Connection timeout
|
||||
```
|
||||
|
||||
### 2️⃣ 为什么我几乎永远带 `-n`
|
||||
|
||||
原因很简单:**行号 = 定位能力**。
|
||||
|
||||
- 你要和同事对齐信息
|
||||
|
||||
- 你要回到文件中继续分析
|
||||
|
||||
- 你要用 sed / vim / awk 二次处理
|
||||
|
||||
|
||||
**没有行号,等于失去坐标。**
|
||||
|
||||
### 3️⃣ 实战场景
|
||||
|
||||
- 让开发快速定位代码或日志位置
|
||||
|
||||
- 在 vim 里 `:2451` 直接跳转
|
||||
|
||||
- 用 sed 精确截取问题区间
|
||||
|
||||
|
||||
只要 grep 文件,我几乎条件反射就加 -n
|
||||
|
||||
## `-i`:别让大小写浪费你的时间
|
||||
|
||||
### 1️⃣ 基本用法
|
||||
|
||||
```
|
||||
grep -i "error" app.log
|
||||
```
|
||||
|
||||
等价于:
|
||||
|
||||
- error
|
||||
|
||||
- Error
|
||||
|
||||
- ERROR
|
||||
|
||||
- eRrOr
|
||||
|
||||
|
||||
### 2️⃣ 真实痛点
|
||||
|
||||
你一定遇到过这种情况:
|
||||
|
||||
```
|
||||
grep "error" app.log# 什么都没匹配到
|
||||
```
|
||||
|
||||
结果一看日志,全是:
|
||||
|
||||
```
|
||||
ERROR
|
||||
```
|
||||
|
||||
### 3️⃣ 运维经验
|
||||
|
||||
- 日志格式不统一
|
||||
|
||||
- 不同组件输出风格不同
|
||||
|
||||
- 第三方程序你根本控制不了
|
||||
|
||||
|
||||
**与其记大小写,不如让 grep 忽略它。**
|
||||
|
||||
### 4️⃣ 建议
|
||||
|
||||
> **查日志时,只要关键词是英文,我几乎都会加 -i**
|
||||
|
||||
## `-v`:排除干扰信息,快速聚焦重点
|
||||
|
||||
### 1️⃣ `-v` 是什么
|
||||
|
||||
```
|
||||
grep -v "info" app.log
|
||||
```
|
||||
|
||||
含义是:**显示不包含 info 的行**
|
||||
|
||||
### 2️⃣ 为什么 `-v` 在运维中非常重要
|
||||
|
||||
日志里最常见的是什么?
|
||||
|
||||
- INFO
|
||||
|
||||
- DEBUG
|
||||
|
||||
- 心跳日志
|
||||
|
||||
- 健康检查
|
||||
|
||||
|
||||
但我们真正关心的,往往只有:
|
||||
|
||||
- ERROR
|
||||
|
||||
- WARN
|
||||
|
||||
- Exception
|
||||
|
||||
|
||||
### 3️⃣ 经典组合用法
|
||||
|
||||
```
|
||||
grep -i "error" app.log | grep -v "timeout"
|
||||
```
|
||||
|
||||
含义是:
|
||||
|
||||
> 查 error,但排除已知的、无关的 timeout 错误
|
||||
|
||||
### 4️⃣ 实战案例
|
||||
|
||||
排查线上告警:
|
||||
|
||||
```
|
||||
grep -i "error" app.log | grep -v "connection reset"
|
||||
```
|
||||
|
||||
**瞬间把 90% 的“噪音”过滤掉。**
|
||||
|
||||
-v 是 grep 的“降噪耳机”
|
||||
|
||||
## `-C / -A / -B`:只看一行,等于没看日志
|
||||
|
||||
### 1️⃣ 为什么只看一行日志是不够的
|
||||
|
||||
一条 ERROR 往往是这样的:
|
||||
|
||||
- 前几行:请求参数
|
||||
|
||||
- 当前行:错误提示
|
||||
|
||||
- 后几行:堆栈信息
|
||||
|
||||
|
||||
如果你只看命中那一行,**信息是不完整的**。
|
||||
|
||||
### 2️⃣ 常用参数说明
|
||||
|
||||
- `-A n`:显示匹配行 **后 n 行**
|
||||
|
||||
- `-B n`:显示匹配行 **前 n 行**
|
||||
|
||||
- `-C n`:显示前后各 n 行
|
||||
|
||||
|
||||
### 3️⃣ 实战示例
|
||||
|
||||
```
|
||||
grep -n -C 5 "NullPointerException" app.log
|
||||
```
|
||||
|
||||
输出的不是一行,而是一个**完整上下文块**。
|
||||
|
||||
### 4️⃣ 典型使用场景
|
||||
|
||||
- Java 堆栈分析
|
||||
|
||||
- 请求链路排查
|
||||
|
||||
- 分析程序执行路径
|
||||
|
||||
|
||||
grep + 上下文,才是真正的“读日志”
|
||||
|
||||
## `-E`:让 grep 真正“强大”的关键
|
||||
|
||||
### 1️⃣ 什么是 `-E`
|
||||
|
||||
`-E` 启用 **扩展正则表达式**,让 grep 支持:
|
||||
|
||||
- `|`(或)
|
||||
|
||||
- `+`
|
||||
|
||||
- `?`
|
||||
|
||||
- `()` 分组
|
||||
|
||||
|
||||
### 2️⃣ 没有 `-E` 的痛苦
|
||||
|
||||
```
|
||||
grep "error|warn" app.log# 匹配不到
|
||||
```
|
||||
|
||||
### 3️⃣ 正确用法
|
||||
|
||||
```
|
||||
grep -E "error|warn|exception" app.log
|
||||
```
|
||||
|
||||
### 4️⃣ 高频实战场景
|
||||
|
||||
一次查多种关键字:
|
||||
|
||||
```
|
||||
grep -Ei "error|fail|timeout" app.log
|
||||
```
|
||||
|
||||
匹配 IP:
|
||||
|
||||
```
|
||||
grep -E "([0-9]{1,3}\.){3}[0-9]{1,3}" access.log
|
||||
```
|
||||
|
||||
只要涉及“多条件匹配”,我直接上 -E
|
||||
|
||||
## 我最常用的 grep 组合拳
|
||||
|
||||
最后分享几个我在生产环境里**天天用的组合命令**。
|
||||
|
||||
### 1️⃣ 查错误并带上下文
|
||||
|
||||
```
|
||||
grep -in -C 5 "error" app.log
|
||||
```
|
||||
|
||||
### 2️⃣ 查错误,排除已知无关问题
|
||||
|
||||
```
|
||||
grep -Ei "error|exception" app.log | grep -v "timeout"
|
||||
```
|
||||
|
||||
### 3️⃣ 查最近错误(配合 tail)
|
||||
|
||||
```
|
||||
tail -n 1000 app.log | grep -i "error"
|
||||
```
|
||||
|
||||

|
||||
|
||||
**大事!运维漫谈-技术交流群对外开放!**
|
||||
|
||||
|
||||
|
||||
扫码可添加小编微信,**申请进****群**
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
▲长按加群
|
||||
|
||||

|
||||
|
||||
**运维漫谈**
|
||||
|
||||
运维漫谈,漫谈整个网络安全、云计算、大数据、数据库、开发等运维技术干货,旨在分享干货,帮您更快学习相关知识,解决问题,同时还有福利活动哦,关注我,带你成为大神,遨游技术海洋!
|
||||
|
||||
922篇原创内容
|
||||
|
||||
公众号
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
Linux · 目录
|
||||
|
||||
上一篇这6个工具,让你的Linux监控如鱼得水,运维效率翻倍!下一篇如何查看 Ubuntu 服务器到底什么时候重启的?
|
||||
|
||||
阅读 3351
|
||||
|
||||
grep上下文参数
|
||||
|
||||
|
||||
|
||||
**留言 1**
|
||||
|
||||
写留言
|
||||
|
||||
- 
|
||||
|
||||
亮
|
||||
|
||||
北京23小时前
|
||||
|
||||
赞
|
||||
|
||||
grep -Rni
|
||||
|
||||
|
||||
已无更多数据
|
||||
|
||||
[](javacript:;)
|
||||
|
||||

|
||||
|
||||
运维漫谈
|
||||
|
||||
关注
|
||||
|
||||
76
|
||||
|
||||
295
|
||||
|
||||
58
|
||||
|
||||
1
|
||||
|
||||

|
||||
|
||||
复制搜一搜
|
||||
|
||||
复制搜一搜
|
||||
|
||||
暂无评论
|
||||
@@ -0,0 +1,465 @@
|
||||
# IP 系列命令管理 Linux 网络完全指南
|
||||
|
||||
## 一、IP 命令概述
|
||||
|
||||
`ip` 命令是 `iproute2` 工具包的一部分,取代了传统的 `ifconfig`、`route`、`arp` 等命令,提供更强大和统一的网络管理功能。
|
||||
|
||||
### 基本语法结构
|
||||
```bash
|
||||
ip [OPTIONS] OBJECT { COMMAND | help }
|
||||
```
|
||||
|
||||
**常用 OBJECT(对象):**
|
||||
- `link` - 网络接口管理
|
||||
- `address` - IP地址管理
|
||||
- `route` - 路由表管理
|
||||
- `neigh` - ARP/邻居表管理
|
||||
- `netns` - 网络命名空间管理
|
||||
- `maddr` - 多播地址管理
|
||||
|
||||
---
|
||||
|
||||
## 二、网络接口管理(link)
|
||||
|
||||
### 1. 查看接口信息
|
||||
```bash
|
||||
# 查看所有接口状态
|
||||
ip link show
|
||||
ip -s link show # 带统计信息
|
||||
ip -s -s link show # 更详细的统计信息
|
||||
|
||||
# 查看特定接口
|
||||
ip link show eth0
|
||||
ip -s link show eth0
|
||||
```
|
||||
|
||||
### 2. 接口启停控制
|
||||
```bash
|
||||
# 启用接口
|
||||
ip link set eth0 up
|
||||
|
||||
# 禁用接口
|
||||
ip link set eth0 down
|
||||
|
||||
# 修改接口MTU
|
||||
ip link set eth0 mtu 9000
|
||||
|
||||
# 修改MAC地址
|
||||
ip link set eth0 address 00:11:22:33:44:55
|
||||
```
|
||||
|
||||
### 3. 接口重命名
|
||||
```bash
|
||||
# 临时重命名
|
||||
ip link set eth0 name eth-new
|
||||
|
||||
# 永久重命名(需要配合系统配置)
|
||||
# 1. 先禁用接口
|
||||
ip link set eth0 down
|
||||
# 2. 重命名
|
||||
ip link set eth0 name eth-new
|
||||
# 3. 启用
|
||||
ip link set eth-new up
|
||||
```
|
||||
|
||||
### 4. 查看接口详情
|
||||
```bash
|
||||
ip -d link show # 显示接口详细信息
|
||||
ip -d link show eth0
|
||||
```
|
||||
|
||||
**输出示例:**
|
||||
```
|
||||
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
|
||||
link/ether 00:0c:29:xx:xx:xx brd ff:ff:ff:ff:ff:ff promiscuity 0
|
||||
addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、IP地址管理(address/addr)
|
||||
|
||||
### 1. 查看IP地址
|
||||
```bash
|
||||
# 查看所有接口IP地址
|
||||
ip address show
|
||||
ip addr show
|
||||
ip a # 简写
|
||||
|
||||
# 查看特定接口
|
||||
ip addr show eth0
|
||||
ip a show dev eth0
|
||||
|
||||
# 查看IPv4或IPv6地址
|
||||
ip -4 addr show # 只显示IPv4
|
||||
ip -6 addr show # 只显示IPv6
|
||||
```
|
||||
|
||||
### 2. 添加IP地址
|
||||
```bash
|
||||
# 添加IPv4地址
|
||||
ip addr add 192.168.1.100/24 dev eth0
|
||||
ip addr add 192.168.1.101/24 dev eth0 label eth0:1
|
||||
|
||||
# 添加IPv6地址
|
||||
ip addr add 2001:db8::1/64 dev eth0
|
||||
```
|
||||
|
||||
### 3. 删除IP地址
|
||||
```bash
|
||||
# 删除指定IP
|
||||
ip addr del 192.168.1.100/24 dev eth0
|
||||
|
||||
# 删除所有IP(慎用)
|
||||
ip addr flush dev eth0
|
||||
ip addr flush dev eth0 scope global # 只删除全局地址
|
||||
```
|
||||
|
||||
### 4. 清理和刷新地址
|
||||
```bash
|
||||
# 清理特定类型的地址
|
||||
ip addr flush dynamic # 清理动态获取的地址
|
||||
ip addr flush permanent # 清理永久地址
|
||||
ip addr flush temporary # 清理临时地址
|
||||
|
||||
# 清理IPv6特定地址
|
||||
ip -6 addr flush dev eth0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、路由表管理(route)
|
||||
|
||||
### 1. 查看路由表
|
||||
```bash
|
||||
# 查看所有路由
|
||||
ip route show
|
||||
ip route list
|
||||
ip r # 简写
|
||||
|
||||
# 查看特定网络路由
|
||||
ip route show 192.168.1.0/24
|
||||
|
||||
# 查看特定接口路由
|
||||
ip route show dev eth0
|
||||
|
||||
# 查看缓存路由
|
||||
ip route show cache
|
||||
|
||||
# 查看详细路由信息
|
||||
ip -d route show
|
||||
```
|
||||
|
||||
### 2. 添加路由
|
||||
```bash
|
||||
# 添加默认网关
|
||||
ip route add default via 192.168.1.1 dev eth0
|
||||
|
||||
# 添加网络路由
|
||||
ip route add 10.0.0.0/8 via 192.168.1.254 dev eth0
|
||||
|
||||
# 添加直连路由
|
||||
ip route add 192.168.2.0/24 dev eth1
|
||||
|
||||
# 添加多路径路由
|
||||
ip route add default nexthop via 192.168.1.1 weight 1 \
|
||||
nexthop via 192.168.2.1 weight 2
|
||||
```
|
||||
|
||||
### 3. 删除路由
|
||||
```bash
|
||||
# 删除默认路由
|
||||
ip route del default
|
||||
|
||||
# 删除特定路由
|
||||
ip route del 10.0.0.0/8 via 192.168.1.254
|
||||
ip route del 192.168.2.0/24 dev eth1
|
||||
```
|
||||
|
||||
### 4. 修改和替换路由
|
||||
```bash
|
||||
# 替换路由(如果存在则替换,不存在则添加)
|
||||
ip route replace 192.168.1.0/24 via 10.0.0.1 dev eth0
|
||||
|
||||
# 修改路由属性
|
||||
ip route change 192.168.1.0/24 via 10.0.0.2 dev eth0 metric 100
|
||||
```
|
||||
|
||||
### 5. 特殊路由操作
|
||||
```bash
|
||||
# 清空路由表
|
||||
ip route flush table main
|
||||
ip route flush cache # 清空路由缓存
|
||||
|
||||
# 添加策略路由
|
||||
ip route add table 100 default via 10.0.0.1
|
||||
|
||||
# 查看特定路由表
|
||||
ip route show table local
|
||||
ip route show table main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、邻居表管理(ARP/NDP)
|
||||
|
||||
### 1. 查看邻居表
|
||||
```bash
|
||||
# 查看ARP表(IPv4)
|
||||
ip neigh show
|
||||
ip neighbor show
|
||||
|
||||
# 查看IPv6邻居发现表
|
||||
ip -6 neigh show
|
||||
|
||||
# 查看特定接口
|
||||
ip neigh show dev eth0
|
||||
```
|
||||
|
||||
### 2. 添加/删除ARP条目
|
||||
```bash
|
||||
# 添加静态ARP条目
|
||||
ip neigh add 192.168.1.100 lladdr 00:11:22:33:44:55 dev eth0 nud permanent
|
||||
|
||||
# 删除ARP条目
|
||||
ip neigh del 192.168.1.100 dev eth0
|
||||
|
||||
# 刷新ARP表
|
||||
ip neigh flush dev eth0
|
||||
```
|
||||
|
||||
### 3. 邻居状态说明
|
||||
- `permanent` - 永久条目,手动添加
|
||||
- `noarp` - 不需ARP验证
|
||||
- `reachable` - 可达状态
|
||||
- `stale` - 陈旧状态
|
||||
- `delay` - 延迟验证
|
||||
- `failed` - 验证失败
|
||||
|
||||
---
|
||||
|
||||
## 六、网络命名空间管理(netns)
|
||||
|
||||
### 1. 命名空间操作
|
||||
```bash
|
||||
# 创建命名空间
|
||||
ip netns add ns1
|
||||
ip netns add ns2
|
||||
|
||||
# 列出所有命名空间
|
||||
ip netns list
|
||||
|
||||
# 在命名空间中执行命令
|
||||
ip netns exec ns1 ip addr show
|
||||
ip netns exec ns1 bash # 进入命名空间的shell
|
||||
|
||||
# 删除命名空间
|
||||
ip netns del ns1
|
||||
```
|
||||
|
||||
### 2. 命名空间网络配置
|
||||
```bash
|
||||
# 创建虚拟以太网对
|
||||
ip link add veth0 type veth peer name veth1
|
||||
|
||||
# 将一端移到命名空间
|
||||
ip link set veth1 netns ns1
|
||||
|
||||
# 在命名空间内配置
|
||||
ip netns exec ns1 ip addr add 10.0.0.1/24 dev veth1
|
||||
ip netns exec ns1 ip link set veth1 up
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、网络统计和监控
|
||||
|
||||
### 1. 查看网络统计
|
||||
```bash
|
||||
# 查看所有接口统计
|
||||
ip -s link show
|
||||
|
||||
# 查看特定接口详细统计
|
||||
ip -s -s link show eth0
|
||||
|
||||
# 持续监控网络统计
|
||||
watch -n 1 'ip -s link show eth0'
|
||||
```
|
||||
|
||||
### 2. 监控网络事件
|
||||
```bash
|
||||
# 监控网络事件(需要root)
|
||||
ip monitor all # 监控所有事件
|
||||
ip monitor link # 只监控链路事件
|
||||
ip monitor address # 只监控地址事件
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、高级网络配置
|
||||
|
||||
### 1. 多播地址管理
|
||||
```bash
|
||||
# 查看多播地址
|
||||
ip maddr show
|
||||
ip maddr show dev eth0
|
||||
|
||||
# 添加多播地址
|
||||
ip maddr add 224.0.0.1 dev eth0
|
||||
|
||||
# 删除多播地址
|
||||
ip maddr del 224.0.0.1 dev eth0
|
||||
```
|
||||
|
||||
### 2. 策略路由
|
||||
```bash
|
||||
# 查看路由规则
|
||||
ip rule show
|
||||
|
||||
# 添加路由规则
|
||||
ip rule add from 192.168.1.0/24 table 100
|
||||
ip rule add fwmark 1 table 100
|
||||
|
||||
# 删除路由规则
|
||||
ip rule del from 192.168.1.0/24
|
||||
```
|
||||
|
||||
### 3. 隧道配置
|
||||
```bash
|
||||
# 创建GRE隧道
|
||||
ip tunnel add gre1 mode gre remote 203.0.113.1 local 192.0.2.1 ttl 255
|
||||
ip link set gre1 up
|
||||
ip addr add 10.0.0.1/30 dev gre1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、实用脚本示例
|
||||
|
||||
### 1. 网络配置脚本
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# 配置多IP地址
|
||||
INTERFACE="eth0"
|
||||
NETWORK="192.168.1"
|
||||
|
||||
# 清除现有配置
|
||||
ip addr flush dev $INTERFACE
|
||||
|
||||
# 添加主IP
|
||||
ip addr add ${NETWORK}.100/24 dev $INTERFACE
|
||||
|
||||
# 添加多个辅助IP
|
||||
for i in {101..110}; do
|
||||
ip addr add ${NETWORK}.$i/24 dev $INTERFACE label ${INTERFACE}:$i
|
||||
done
|
||||
|
||||
# 设置默认网关
|
||||
ip route add default via ${NETWORK}.1
|
||||
|
||||
# 启用接口
|
||||
ip link set $INTERFACE up
|
||||
```
|
||||
|
||||
### 2. 网络诊断脚本
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# 网络状态诊断
|
||||
echo "=== 接口状态 ==="
|
||||
ip link show | grep -E "^[0-9]+:"
|
||||
|
||||
echo -e "\n=== IP地址 ==="
|
||||
ip addr show | grep -E "inet|inet6"
|
||||
|
||||
echo -e "\n=== 路由表 ==="
|
||||
ip route show
|
||||
|
||||
echo -e "\n=== ARP表 ==="
|
||||
ip neigh show
|
||||
|
||||
echo -e "\n=== 连接统计 ==="
|
||||
ss -s
|
||||
```
|
||||
|
||||
### 3. 批量配置脚本
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# 批量配置网络命名空间
|
||||
for i in {1..5}; do
|
||||
# 创建命名空间
|
||||
ip netns add ns$i
|
||||
|
||||
# 创建虚拟接口对
|
||||
ip link add veth$i type veth peer name vpeer$i
|
||||
|
||||
# 配置主端
|
||||
ip link set veth$i up
|
||||
ip addr add 10.0.$i.1/24 dev veth$i
|
||||
|
||||
# 配置命名空间端
|
||||
ip link set vpeer$i netns ns$i
|
||||
ip netns exec ns$i ip link set vpeer$i up
|
||||
ip netns exec ns$i ip addr add 10.0.$i.2/24 dev vpeer$i
|
||||
ip netns exec ns$i ip route add default via 10.0.$i.1
|
||||
done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十、IP命令与传统命令对照表
|
||||
|
||||
| **功能** | **传统命令** | **ip命令** |
|
||||
|---------|------------|-----------|
|
||||
| 查看接口 | `ifconfig -a` | `ip link show` |
|
||||
| 配置IP | `ifconfig eth0 192.168.1.100 netmask 255.255.255.0` | `ip addr add 192.168.1.100/24 dev eth0` |
|
||||
| 查看路由 | `route -n` | `ip route show` |
|
||||
| 添加路由 | `route add -net 10.0.0.0 netmask 255.0.0.0 gw 192.168.1.1` | `ip route add 10.0.0.0/8 via 192.168.1.1` |
|
||||
| 查看ARP | `arp -an` | `ip neigh show` |
|
||||
| 添加ARP | `arp -s 192.168.1.100 00:11:22:33:44:55` | `ip neigh add 192.168.1.100 lladdr 00:11:22:33:44:55 dev eth0` |
|
||||
|
||||
---
|
||||
|
||||
## 十一、常见问题排查
|
||||
|
||||
### 1. 网络不通排查步骤
|
||||
```bash
|
||||
# 1. 检查接口状态
|
||||
ip link show eth0
|
||||
|
||||
# 2. 检查IP配置
|
||||
ip addr show eth0
|
||||
|
||||
# 3. 检查路由
|
||||
ip route show
|
||||
ip route get 8.8.8.8
|
||||
|
||||
# 4. 检查ARP表
|
||||
ip neigh show dev eth0
|
||||
|
||||
# 5. 检查防火墙
|
||||
iptables -L -n -v
|
||||
```
|
||||
|
||||
### 2. 快速诊断命令
|
||||
```bash
|
||||
# 一键诊断
|
||||
ip a && ip r && ip neigh
|
||||
|
||||
# 保存网络配置快照
|
||||
ip addr show > /tmp/network_snapshot.txt
|
||||
ip route show >> /tmp/network_snapshot.txt
|
||||
ip neigh show >> /tmp/network_snapshot.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十二、最佳实践
|
||||
|
||||
1. **使用完整语法**:虽然简写方便,但脚本中建议使用完整命令
|
||||
2. **配置持久化**:`ip` 命令配置是临时的,重启后会丢失
|
||||
3. **参数验证**:重要操作前先验证参数
|
||||
4. **批量操作**:多个接口配置使用循环
|
||||
5. **错误处理**:脚本中添加错误检查
|
||||
6. **权限管理**:大部分 `ip` 命令需要 root 权限
|
||||
|
||||
通过掌握 `ip` 系列命令,你可以高效地管理 Linux 网络配置,进行网络故障排查和性能优化。
|
||||
@@ -0,0 +1,454 @@
|
||||
>**iptables命令** 是Linux上常用的防火墙软件,是netfilter项目的一部分。可以直接配置,也可以通过许多前端和图形界面配置。
|
||||
|
||||
### 语法
|
||||
|
||||
```shell
|
||||
iptables(选项)(参数)
|
||||
```
|
||||
|
||||
### [](https://wangchujiang.com/linux-command/c/iptables.html#%E9%80%89%E9%A1%B9)选项
|
||||
|
||||
```shell
|
||||
-t, --table table 对指定的表 table 进行操作, table 必须是 raw, nat,filter,mangle 中的一个。如果不指定此选项,默认的是 filter 表。
|
||||
|
||||
# 通用匹配:源地址目标地址的匹配
|
||||
-p:指定要匹配的数据包协议类型;
|
||||
-s, --source [!] address[/mask] :把指定的一个/一组地址作为源地址,按此规则进行过滤。当后面没有 mask 时,address 是一个地址,比如:192.168.1.1;当 mask 指定时,可以表示一组范围内的地址,比如:192.168.1.0/255.255.255.0。
|
||||
-d, --destination [!] address[/mask] :地址格式同上,但这里是指定地址为目的地址,按此进行过滤。
|
||||
-i, --in-interface [!] <网络接口name> :指定数据包的来自来自网络接口,比如最常见的 eth0 。注意:它只对 INPUT,FORWARD,PREROUTING 这三个链起作用。如果没有指定此选项, 说明可以来自任何一个网络接口。同前面类似,"!" 表示取反。
|
||||
-o, --out-interface [!] <网络接口name> :指定数据包出去的网络接口。只对 OUTPUT,FORWARD,POSTROUTING 三个链起作用。
|
||||
|
||||
# 查看管理命令
|
||||
-L, --list [chain] 列出链 chain 上面的所有规则,如果没有指定链,列出表上所有链的所有规则。
|
||||
|
||||
# 规则管理命令
|
||||
-A, --append chain rule-specification 在指定链 chain 的末尾插入指定的规则,也就是说,这条规则会被放到最后,最后才会被执行。规则是由后面的匹配来指定。
|
||||
-I, --insert chain [rulenum] rule-specification 在链 chain 中的指定位置插入一条或多条规则。如果指定的规则号是1,则在链的头部插入。这也是默认的情况,如果没有指定规则号。
|
||||
-D, --delete chain rule-specification -D, --delete chain rulenum 在指定的链 chain 中删除一个或多个指定规则。
|
||||
-R num:Replays替换/修改第几条规则
|
||||
|
||||
# 链管理命令(这都是立即生效的)
|
||||
-P, --policy chain target :为指定的链 chain 设置策略 target。注意,只有内置的链才允许有策略,用户自定义的是不允许的。
|
||||
-F, --flush [chain] 清空指定链 chain 上面的所有规则。如果没有指定链,清空该表上所有链的所有规则。
|
||||
-N, --new-chain chain 用指定的名字创建一个新的链。
|
||||
-X, --delete-chain [chain] :删除指定的链,这个链必须没有被其它任何规则引用,而且这条上必须没有任何规则。如果没有指定链名,则会删除该表中所有非内置的链。
|
||||
-E, --rename-chain old-chain new-chain :用指定的新名字去重命名指定的链。这并不会对链内部造成任何影响。
|
||||
-Z, --zero [chain] :把指定链,或者表中的所有链上的所有计数器清零。
|
||||
|
||||
-j, --jump target <指定目标> :即满足某条件时该执行什么样的动作。target 可以是内置的目标,比如 ACCEPT,也可以是用户自定义的链。
|
||||
-h:显示帮助信息;
|
||||
```
|
||||
|
||||
## [](https://wangchujiang.com/linux-command/c/iptables.html#%E5%9F%BA%E6%9C%AC%E5%8F%82%E6%95%B0)基本参数
|
||||
|
||||
|参数|作用|
|
||||
|---|---|
|
||||
|-P|设置默认策略:iptables -P INPUT (DROP|
|
||||
|-F|清空规则链|
|
||||
|-L|查看规则链|
|
||||
|-A|在规则链的末尾加入新规则|
|
||||
|-I|num 在规则链的头部加入新规则|
|
||||
|-D|num 删除某一条规则|
|
||||
|-s|匹配来源地址IP/MASK,加叹号"!"表示除这个IP外。|
|
||||
|-d|匹配目标地址|
|
||||
|-i|网卡名称 匹配从这块网卡流入的数据|
|
||||
|-o|网卡名称 匹配从这块网卡流出的数据|
|
||||
|-p|匹配协议,如tcp,udp,icmp|
|
||||
|--dport num|匹配目标端口号|
|
||||
|--sport num|匹配来源端口号|
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E5%91%BD%E4%BB%A4%E9%80%89%E9%A1%B9%E8%BE%93%E5%85%A5%E9%A1%BA%E5%BA%8F)命令选项输入顺序
|
||||
|
||||
```shell
|
||||
iptables -t 表名 <-A/I/D/R> 规则链名 [规则号] <-i/o 网卡名> -p 协议名 <-s 源IP/源子网> --sport 源端口 <-d 目标IP/目标子网> --dport 目标端口 -j 动作
|
||||
```
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E5%B7%A5%E4%BD%9C%E6%9C%BA%E5%88%B6)工作机制
|
||||
|
||||
规则链名包括(也被称为五个钩子函数(hook functions)):
|
||||
|
||||
- **INPUT链** :处理输入数据包。
|
||||
- **OUTPUT链** :处理输出数据包。
|
||||
- **FORWARD链** :处理转发数据包。
|
||||
- **PREROUTING链** :用于目标地址转换(DNAT)。
|
||||
- **POSTOUTING链** :用于源地址转换(SNAT)。
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E9%98%B2%E7%81%AB%E5%A2%99%E7%9A%84%E7%AD%96%E7%95%A5)防火墙的策略
|
||||
|
||||
防火墙策略一般分为两种,一种叫`通`策略,一种叫`堵`策略,通策略,默认门是关着的,必须要定义谁能进。堵策略则是,大门是洞开的,但是你必须有身份认证,否则不能进。所以我们要定义,让进来的进来,让出去的出去,`所以通,是要全通,而堵,则是要选择`。当我们定义的策略的时候,要分别定义多条功能,其中:定义数据包中允许或者不允许的策略,filter过滤的功能,而定义地址转换的功能的则是nat选项。为了让这些功能交替工作,我们制定出了“表”这个定义,来定义、区分各种不同的工作功能和处理方式。
|
||||
|
||||
我们现在用的比较多个功能有3个:
|
||||
|
||||
1. filter 定义允许或者不允许的,只能做在3个链上:INPUT ,FORWARD ,OUTPUT
|
||||
2. nat 定义地址转换的,也只能做在3个链上:PREROUTING ,OUTPUT ,POSTROUTING
|
||||
3. mangle功能:修改报文原数据,是5个链都可以做:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING
|
||||
|
||||
我们修改报文原数据就是来修改TTL的。能够实现将数据包的元数据拆开,在里面做标记/修改内容的。而防火墙标记,其实就是靠mangle来实现的。
|
||||
|
||||
小扩展:
|
||||
|
||||
- 对于filter来讲一般只能做在3个链上:INPUT ,FORWARD ,OUTPUT
|
||||
- 对于nat来讲一般也只能做在3个链上:PREROUTING ,OUTPUT ,POSTROUTING
|
||||
- 而mangle则是5个链都可以做:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING
|
||||
|
||||
iptables/netfilter(这款软件)是工作在用户空间的,它可以让规则进行生效的,本身不是一种服务,而且规则是立即生效的。而我们iptables现在被做成了一个服务,可以进行启动,停止的。启动,则将规则直接生效,停止,则将规则撤销。
|
||||
|
||||
iptables还支持自己定义链。但是自己定义的链,必须是跟某种特定的链关联起来的。在一个关卡设定,指定当有数据的时候专门去找某个特定的链来处理,当那个链处理完之后,再返回。接着在特定的链中继续检查。
|
||||
|
||||
注意:规则的次序非常关键,`谁的规则越严格,应该放的越靠前`,而检查规则的时候,是按照从上往下的方式进行检查的。
|
||||
|
||||
表名包括:
|
||||
|
||||
- **raw** :高级功能,如:网址过滤。
|
||||
- **mangle** :数据包修改(QOS),用于实现服务质量。
|
||||
- **nat** :地址转换,用于网关路由器。
|
||||
- **filter** :包过滤,用于防火墙规则。
|
||||
|
||||
动作包括:
|
||||
|
||||
- **ACCEPT** :接收数据包。
|
||||
- **DROP** :丢弃数据包。
|
||||
- **REDIRECT** :重定向、映射、透明代理。
|
||||
- **SNAT** :源地址转换。
|
||||
- **DNAT** :目标地址转换。
|
||||
- **MASQUERADE** :IP伪装(NAT),用于ADSL。
|
||||
- **LOG** :日志记录。
|
||||
- **SEMARK** : 添加SEMARK标记以供网域内强制访问控制(MAC)
|
||||
|
||||
```shell
|
||||
┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓
|
||||
┌───────────────┐ ┃ Network ┃
|
||||
│ table: filter │ ┗━━━━━━━┳━━━━━━━┛
|
||||
│ chain: INPUT │◀────┐ │
|
||||
└───────┬───────┘ │ ▼
|
||||
│ │ ┌───────────────────┐
|
||||
┌ ▼ ┐ │ │ table: nat │
|
||||
│local process│ │ │ chain: PREROUTING │
|
||||
└ ┘ │ └─────────┬─────────┘
|
||||
│ │ │
|
||||
▼ │ ▼ ┌─────────────────┐
|
||||
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅ │ ┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅ │table: nat │
|
||||
Routing decision └───── outing decision ─────▶│chain: PREROUTING│
|
||||
┅┅┅┅┅┅┅┅┅┳┅┅┅┅┅┅┅┅┅ ┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅ └────────┬────────┘
|
||||
│ │
|
||||
▼ │
|
||||
┌───────────────┐ │
|
||||
│ table: nat │ ┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅ │
|
||||
│ chain: OUTPUT │ ┌─────▶ outing decision ◀──────────────┘
|
||||
└───────┬───────┘ │ ┅┅┅┅┅┅┅┅┳┅┅┅┅┅┅┅┅
|
||||
│ │ │
|
||||
▼ │ ▼
|
||||
┌───────────────┐ │ ┌────────────────────┐
|
||||
│ table: filter │ │ │ chain: POSTROUTING │
|
||||
│ chain: OUTPUT ├────┘ └──────────┬─────────┘
|
||||
└───────────────┘ │
|
||||
▼
|
||||
┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓
|
||||
┃ Network ┃
|
||||
┗━━━━━━━━━━━━━━━┛
|
||||
```
|
||||
|
||||
### [](https://wangchujiang.com/linux-command/c/iptables.html#%E5%AE%9E%E4%BE%8B)实例
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E6%B8%85%E7%A9%BA%E5%BD%93%E5%89%8D%E7%9A%84%E6%89%80%E6%9C%89%E8%A7%84%E5%88%99%E5%92%8C%E8%AE%A1%E6%95%B0)清空当前的所有规则和计数
|
||||
|
||||
```shell
|
||||
iptables -F # 清空所有的防火墙规则
|
||||
iptables -X # 删除用户自定义的空链
|
||||
iptables -Z # 清空计数
|
||||
```
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E9%85%8D%E7%BD%AE%E5%85%81%E8%AE%B8ssh%E7%AB%AF%E5%8F%A3%E8%BF%9E%E6%8E%A5)配置允许ssh端口连接
|
||||
|
||||
```shell
|
||||
iptables -A INPUT -s 192.168.1.0/24 -p tcp --dport 22 -j ACCEPT
|
||||
# 22为你的ssh端口, -s 192.168.1.0/24表示允许这个网段的机器来连接,其它网段的ip地址是登陆不了你的机器的。 -j ACCEPT表示接受这样的请求
|
||||
```
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E5%85%81%E8%AE%B8%E6%9C%AC%E5%9C%B0%E5%9B%9E%E7%8E%AF%E5%9C%B0%E5%9D%80%E5%8F%AF%E4%BB%A5%E6%AD%A3%E5%B8%B8%E4%BD%BF%E7%94%A8)允许本地回环地址可以正常使用
|
||||
|
||||
```shell
|
||||
iptables -A INPUT -i lo -j ACCEPT
|
||||
#本地圆环地址就是那个127.0.0.1,是本机上使用的,它进与出都设置为允许
|
||||
iptables -A OUTPUT -o lo -j ACCEPT
|
||||
```
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E8%AE%BE%E7%BD%AE%E9%BB%98%E8%AE%A4%E7%9A%84%E8%A7%84%E5%88%99)设置默认的规则
|
||||
|
||||
```shell
|
||||
iptables -P INPUT DROP # 配置默认的不让进
|
||||
iptables -P FORWARD DROP # 默认的不允许转发
|
||||
iptables -P OUTPUT ACCEPT # 默认的可以出去
|
||||
```
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E9%85%8D%E7%BD%AE%E7%99%BD%E5%90%8D%E5%8D%95)配置白名单
|
||||
|
||||
```shell
|
||||
iptables -A INPUT -p all -s 192.168.1.0/24 -j ACCEPT # 允许机房内网机器可以访问
|
||||
iptables -A INPUT -p all -s 192.168.140.0/24 -j ACCEPT # 允许机房内网机器可以访问
|
||||
iptables -A INPUT -p tcp -s 183.121.3.7 --dport 3380 -j ACCEPT # 允许183.121.3.7访问本机的3380端口
|
||||
```
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E5%BC%80%E5%90%AF%E7%9B%B8%E5%BA%94%E7%9A%84%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%8F%A3)开启相应的服务端口
|
||||
|
||||
```shell
|
||||
iptables -A INPUT -p tcp --dport 80 -j ACCEPT # 开启80端口,因为web对外都是这个端口
|
||||
iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT # 允许被ping
|
||||
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # 已经建立的连接得让它进来
|
||||
```
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E4%BF%9D%E5%AD%98%E8%A7%84%E5%88%99%E5%88%B0%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E4%B8%AD)保存规则到配置文件中
|
||||
|
||||
```shell
|
||||
cp /etc/sysconfig/iptables /etc/sysconfig/iptables.bak # 任何改动之前先备份,请保持这一优秀的习惯
|
||||
iptables-save > /etc/sysconfig/iptables
|
||||
cat /etc/sysconfig/iptables
|
||||
```
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E5%88%97%E5%87%BA%E5%B7%B2%E8%AE%BE%E7%BD%AE%E7%9A%84%E8%A7%84%E5%88%99)列出已设置的规则
|
||||
|
||||
> iptables -L [-t 表名] [链名]
|
||||
|
||||
- 四个表名 `raw`,`nat`,`filter`,`mangle`
|
||||
- 五个规则链名 `INPUT`、`OUTPUT`、`FORWARD`、`PREROUTING`、`POSTROUTING`
|
||||
- filter表包含`INPUT`、`OUTPUT`、`FORWARD`三个规则链
|
||||
|
||||
```shell
|
||||
iptables -L -t nat # 列出 nat 上面的所有规则
|
||||
# ^ -t 参数指定,必须是 raw, nat,filter,mangle 中的一个
|
||||
iptables -L -t nat --line-numbers # 规则带编号
|
||||
iptables -L INPUT
|
||||
|
||||
iptables -L -nv # 查看,这个列表看起来更详细
|
||||
```
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E6%B8%85%E9%99%A4%E5%B7%B2%E6%9C%89%E8%A7%84%E5%88%99)清除已有规则
|
||||
|
||||
```shell
|
||||
iptables -F INPUT # 清空指定链 INPUT 上面的所有规则
|
||||
iptables -X INPUT # 删除指定的链,这个链必须没有被其它任何规则引用,而且这条上必须没有任何规则。
|
||||
# 如果没有指定链名,则会删除该表中所有非内置的链。
|
||||
iptables -Z INPUT # 把指定链,或者表中的所有链上的所有计数器清零。
|
||||
```
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E5%88%A0%E9%99%A4%E5%B7%B2%E6%B7%BB%E5%8A%A0%E7%9A%84%E8%A7%84%E5%88%99)删除已添加的规则
|
||||
|
||||
```shell
|
||||
# 添加一条规则
|
||||
iptables -A INPUT -s 192.168.1.5 -j DROP
|
||||
```
|
||||
|
||||
将所有iptables以序号标记显示,执行:
|
||||
|
||||
```shell
|
||||
iptables -L -n --line-numbers
|
||||
```
|
||||
|
||||
比如要删除INPUT里序号为8的规则,执行:
|
||||
|
||||
```shell
|
||||
iptables -D INPUT 8
|
||||
```
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E5%BC%80%E6%94%BE%E6%8C%87%E5%AE%9A%E7%9A%84%E7%AB%AF%E5%8F%A3)开放指定的端口
|
||||
|
||||
```shell
|
||||
iptables -A INPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT #允许本地回环接口(即运行本机访问本机)
|
||||
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT #允许已建立的或相关连的通行
|
||||
iptables -A OUTPUT -j ACCEPT #允许所有本机向外的访问
|
||||
iptables -A INPUT -p tcp --dport 22 -j ACCEPT #允许访问22端口
|
||||
iptables -A INPUT -p tcp --dport 80 -j ACCEPT #允许访问80端口
|
||||
iptables -A INPUT -p tcp --dport 21 -j ACCEPT #允许ftp服务的21端口
|
||||
iptables -A INPUT -p tcp --dport 20 -j ACCEPT #允许FTP服务的20端口
|
||||
iptables -A INPUT -j reject #禁止其他未允许的规则访问
|
||||
iptables -A FORWARD -j REJECT #禁止其他未允许的规则访问
|
||||
```
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E5%B1%8F%E8%94%BDip)屏蔽IP
|
||||
|
||||
```shell
|
||||
iptables -A INPUT -p tcp -m tcp -s 192.168.0.8 -j DROP # 屏蔽恶意主机(比如,192.168.0.8
|
||||
iptables -I INPUT -s 123.45.6.7 -j DROP #屏蔽单个IP的命令
|
||||
iptables -I INPUT -s 123.0.0.0/8 -j DROP #封整个段即从123.0.0.1到123.255.255.254的命令
|
||||
iptables -I INPUT -s 124.45.0.0/16 -j DROP #封IP段即从123.45.0.1到123.45.255.254的命令
|
||||
iptables -I INPUT -s 123.45.6.0/24 -j DROP #封IP段即从123.45.6.1到123.45.6.254的命令是
|
||||
```
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E6%8C%87%E5%AE%9A%E6%95%B0%E6%8D%AE%E5%8C%85%E5%87%BA%E5%8E%BB%E7%9A%84%E7%BD%91%E7%BB%9C%E6%8E%A5%E5%8F%A3)指定数据包出去的网络接口
|
||||
|
||||
只对 OUTPUT,FORWARD,POSTROUTING 三个链起作用。
|
||||
|
||||
```shell
|
||||
iptables -A FORWARD -o eth0
|
||||
```
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E6%9F%A5%E7%9C%8B%E5%B7%B2%E6%B7%BB%E5%8A%A0%E7%9A%84%E8%A7%84%E5%88%99)查看已添加的规则
|
||||
|
||||
```shell
|
||||
iptables -L -n -v
|
||||
Chain INPUT (policy DROP 48106 packets, 2690K bytes)
|
||||
pkts bytes target prot opt in out source destination
|
||||
5075 589K ACCEPT all -- lo * 0.0.0.0/0 0.0.0.0/0
|
||||
191K 90M ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
|
||||
1499K 133M ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
|
||||
4364K 6351M ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
|
||||
6256 327K ACCEPT icmp -- * * 0.0.0.0/0 0.0.0.0/0
|
||||
|
||||
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
|
||||
pkts bytes target prot opt in out source destination
|
||||
|
||||
Chain OUTPUT (policy ACCEPT 3382K packets, 1819M bytes)
|
||||
pkts bytes target prot opt in out source destination
|
||||
5075 589K ACCEPT all -- * lo 0.0.0.0/0 0.0.0.0/0
|
||||
```
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E5%90%AF%E5%8A%A8%E7%BD%91%E7%BB%9C%E8%BD%AC%E5%8F%91%E8%A7%84%E5%88%99)启动网络转发规则
|
||||
|
||||
公网`210.14.67.7`让内网`192.168.188.0/24`上网
|
||||
|
||||
```shell
|
||||
iptables -t nat -A POSTROUTING -s 192.168.188.0/24 -j SNAT --to-source 210.14.67.127
|
||||
```
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E7%AB%AF%E5%8F%A3%E6%98%A0%E5%B0%84)端口映射
|
||||
|
||||
本机的 2222 端口映射到内网 虚拟机的22 端口
|
||||
|
||||
```shell
|
||||
iptables -t nat -A PREROUTING -d 210.14.67.127 -p tcp --dport 2222 -j DNAT --to-dest 192.168.188.115:22
|
||||
```
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%8C%B9%E9%85%8D)字符串匹配
|
||||
|
||||
比如,我们要过滤所有TCP连接中的字符串`test`,一旦出现它我们就终止这个连接,我们可以这么做:
|
||||
|
||||
```shell
|
||||
iptables -A INPUT -p tcp -m string --algo kmp --string "test" -j REJECT --reject-with tcp-reset
|
||||
iptables -L
|
||||
|
||||
# Chain INPUT (policy ACCEPT)
|
||||
# target prot opt source destination
|
||||
# REJECT tcp -- anywhere anywhere STRING match "test" ALGO name kmp TO 65535 reject-with tcp-reset
|
||||
#
|
||||
# Chain FORWARD (policy ACCEPT)
|
||||
# target prot opt source destination
|
||||
#
|
||||
# Chain OUTPUT (policy ACCEPT)
|
||||
# target prot opt source destination
|
||||
```
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E9%98%BB%E6%AD%A2windows%E8%A0%95%E8%99%AB%E7%9A%84%E6%94%BB%E5%87%BB)阻止Windows蠕虫的攻击
|
||||
|
||||
```shell
|
||||
iptables -I INPUT -j DROP -p tcp -s 0.0.0.0/0 -m string --algo kmp --string "cmd.exe"
|
||||
```
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E9%98%B2%E6%AD%A2syn%E6%B4%AA%E6%B0%B4%E6%94%BB%E5%87%BB)防止SYN洪水攻击
|
||||
|
||||
```shell
|
||||
iptables -A INPUT -p tcp --syn -m limit --limit 5/second -j ACCEPT
|
||||
```
|
||||
|
||||
#### [](https://wangchujiang.com/linux-command/c/iptables.html#%E6%B7%BB%E5%8A%A0secmark%E8%AE%B0%E5%BD%95)添加SECMARK记录
|
||||
|
||||
```shell
|
||||
iptables -t mangle -A INPUT -p tcp --src 192.168.1.2 --dport 443 -j SECMARK --selctx system_u:object_r:myauth_packet_t
|
||||
# 向从 192.168.1.2:443 以TCP方式发出到本机的包添加MAC安全上下文 system_u:object_r:myauth_packet_t
|
||||
```
|
||||
|
||||
## [](https://wangchujiang.com/linux-command/c/iptables.html#%E6%9B%B4%E5%A4%9A%E5%AE%9E%E4%BE%8B)更多实例
|
||||
|
||||
> 用iptables搭建一套强大的安全防护盾 [http://www.imooc.com/learn/389](http://www.imooc.com/learn/389)
|
||||
|
||||
iptables: linux 下应用层防火墙工具
|
||||
|
||||
iptables 5链: 对应 Hook point netfilter: linux 操作系统核心层内部的一个数据包处理模块 Hook point: 数据包在 netfilter 中的挂载点; `PRE_ROUTING / INPUT / OUTPUT / FORWARD / POST_ROUTING`
|
||||
|
||||
iptables & netfilter 
|
||||
|
||||
iptables 4表5链 
|
||||
|
||||
iptables rules 
|
||||
|
||||
- 4表
|
||||
|
||||
**filter**: 访问控制 / 规则匹配 **nat**: 地址转发 mangle / raw
|
||||
|
||||
- 规则
|
||||
|
||||
数据访问控制: ACCEPT / DROP / REJECT 数据包改写(nat -> 地址转换): snat / dnat 信息记录: log
|
||||
|
||||
## [](https://wangchujiang.com/linux-command/c/iptables.html#%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF%E5%AE%9E%E4%BE%8B)使用场景实例
|
||||
|
||||
- 场景一
|
||||
|
||||
开放 tcp 10-22/80 端口 开放 icmp 其他未被允许的端口禁止访问
|
||||
|
||||
存在的问题: 本机无法访问本机; 本机无法访问其他主机
|
||||
|
||||
- 场景二
|
||||
|
||||
ftp: 默认被动模式(服务器产生随机端口告诉客户端, 客户端主动连接这个端口拉取数据) vsftpd: 使 ftp 支持主动模式(客户端产生随机端口通知服务器, 服务器主动连接这个端口发送数据)
|
||||
|
||||
- 场景三
|
||||
|
||||
允许外网访问: web http -> 80/tcp; https -> 443/tcp mail smtp -> 25/tcp; smtps -> 465/tcp pop3 -> 110/tcp; pop3s -> 995/tcp imap -> 143/tcp
|
||||
|
||||
内部使用: file nfs -> 123/udp samba -> 137/138/139/445/tcp ftp -> 20/21/tcp remote ssh -> 22/tcp sql mysql -> 3306/tcp oracle -> 1521/tcp
|
||||
|
||||
- 场景四
|
||||
|
||||
nat 转发
|
||||
|
||||
- 场景五
|
||||
|
||||
防CC攻击
|
||||
|
||||
```shell
|
||||
iptables -L -F -A -D # list flush append delete
|
||||
# 场景一
|
||||
iptables -I INPUT -p tcp --dport 80 -j ACCEPT # 允许 tcp 80 端口
|
||||
iptables -I INPUT -p tcp --dport 10:22 -j ACCEPT # 允许 tcp 10-22 端口
|
||||
iptables -I INPUT -p icmp -j ACCEPT # 允许 icmp
|
||||
iptables -A INPUT -j REJECT # 添加一条规则, 不允许所有
|
||||
|
||||
# 优化场景一
|
||||
iptables -I INPUT -i lo -j ACCEPT # 允许本机访问
|
||||
iptables -I INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # 允许访问外网
|
||||
iptables -I INPUT -p tcp --dport 80 -s 10.10.188.233 -j ACCEPT # 只允许固定ip访问80
|
||||
|
||||
# 场景二
|
||||
vi /etc/vsftpd/vsftpd.conf # 使用 vsftpd 开启 ftp 主动模式
|
||||
port_enable=yes
|
||||
connect_from_port_20=YES
|
||||
iptables -I INPUT -p tcp --dport 21 -j ACCEPT
|
||||
|
||||
vi /etc/vsftpd/vsftpd.conf # 建议使用 ftp 被动模式
|
||||
pasv_min_port=50000
|
||||
pasv_max_port=60000
|
||||
iptables -I INPUT -p tcp --dport 21 -j ACCEPT
|
||||
iptables -I INPUT -p tcp --dport 50000:60000 -j ACCEPT
|
||||
|
||||
# 还可以使用 iptables 模块追踪来自动开发对应的端口
|
||||
|
||||
# 场景三
|
||||
iptables -I INPUT -i lo -j ACCEPT # 允许本机访问
|
||||
iptables -I INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # 允许访问外网
|
||||
iptables -I INPUT -s 10.10.155.0/24 -j ACCEPT # 允许内网访问
|
||||
iptables -I INPUT -p tcp -m multiport --dports 80,1723 -j ACCEPT # 允许端口, 80 -> http, 1723 -> vpn
|
||||
iptables -A INPUT -j REJECT # 添加一条规则, 不允许所有
|
||||
|
||||
iptables-save # 保存设置到配置文件
|
||||
|
||||
# 场景四
|
||||
iptables -t nat -L # 查看 nat 配置
|
||||
|
||||
iptables -t nat -A POST_ROUTING -s 10.10.177.0/24 -j SNAT --to 10.10.188.232 # SNAT
|
||||
vi /etc/sysconfig/network # 配置网关
|
||||
|
||||
iptables -t nat -A POST_ROUTING -d 10.10.188.232 -p tcp --dport 80 -j DNAT --to 10.10.177.232:80 # DNAT
|
||||
|
||||
#场景五
|
||||
iptables -I INPUT -p tcp --syn --dport 80 -m connlimit --connlimit-above 100 -j REJECT # 限制并发连接访问数
|
||||
iptables -I INPUT -m limit --limit 3/hour --limit-burst 10 -j ACCEPT # limit模块; --limit-burst 默认为5
|
||||
```
|
||||
@@ -0,0 +1,121 @@
|
||||
作者:沐安大数据运维
|
||||
链接:https://www.zhihu.com/question/23063802/answer/1967299165974423120
|
||||
来源:知乎
|
||||
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
|
||||
|
||||
|
||||
都很明确:**开,必须开,除非你想体验半夜被报警电话吵醒的刺激感。**
|
||||
|
||||
前任运维说“咱们有云防火墙,系统层iptables没必要开,影响性能”。
|
||||
|
||||
有个业务部门偷偷开了个[Redis](https://zhida.zhihu.com/search?content_id=754271874&content_type=Answer&match_order=1&q=Redis&zhida_source=entity)端口在外网,没设密码。一个月后,我们成了别人的挖矿肉鸡,CPU常年100%,清理后门花了两天时间。
|
||||
|
||||
养成个好习惯:新服务器上线,第一件事就是配iptables。
|
||||
|
||||
### iptables不是累赘,是你的“安全底线”
|
||||
|
||||
很多人觉得,现在都是云时代了,有云厂商的安全组,iptables多余。这话对了一半。
|
||||
|
||||
[云安全组](https://zhida.zhihu.com/search?content_id=754271874&content_type=Answer&match_order=1&q=%E4%BA%91%E5%AE%89%E5%85%A8%E7%BB%84&zhida_source=entity)确实好用,像大楼的物业保安。但iptables是你家的防盗门。**保安可能会看走眼,但防盗门永远忠实地守在你家门口。**
|
||||
|
||||
如果云平台控制台故障,安全组规则突然失效。
|
||||
|
||||
如果服务器上的iptables还开着,那几千台机器就能否避免全暴露在公网上。
|
||||
|
||||
想想都头皮发麻。
|
||||
|
||||
### 性能影响?别自己吓自己
|
||||
|
||||
“开防火墙影响性能”——这是最大的误解。
|
||||
|
||||
看到过过一个测试:开启基础防护规则(开22、80、443端口,默认拒绝其他入站),对网络性能的影响可以忽略不计。除非你每条规则都带着几十个复杂的字符串匹配,那确实会慢。但正常使用,根本感觉不出来。
|
||||
|
||||
**比起性能那点微不足道的损失,安全带来的收益太大了。**
|
||||
|
||||
### 怎么配置才既安全又省心?
|
||||
|
||||
```text
|
||||
我现在的标准配置模板,用了很多年,分享给大家:
|
||||
# 清空现有规则
|
||||
iptables -F
|
||||
# 设置默认策略:进来的包都拒绝,出去的都允许
|
||||
iptables -P INPUT DROP
|
||||
iptables -P FORWARD DROP
|
||||
iptables -P OUTPUT ACCEPT
|
||||
# 本机访问本机全部允许
|
||||
iptables -A INPUT -i lo -j ACCEPT
|
||||
|
||||
# 允许已建立的连接和相关的连接通过(最关键的一条!)
|
||||
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
# 开SSH端口(根据实际情况改)
|
||||
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
|
||||
|
||||
# 开Web端口(如果需要)
|
||||
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
|
||||
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
|
||||
|
||||
# 最后,允许ping(调试用)
|
||||
iptables -A INPUT -p icmp -j ACCEPT
|
||||
```
|
||||
|
||||
这套规则的核心思想是:**先堵死所有入口,再按需开小门。**
|
||||
|
||||
### 不同场景下的灵活用法
|
||||
|
||||
根据服务器角色,配置要有侧重:
|
||||
|
||||
**Web服务器**:主要开80、443,数据库端口3306只对内部IP开放
|
||||
|
||||
```text
|
||||
iptables -A INPUT -p tcp -s 192.168.1.0/24 --dport 3306 -j ACCEPT
|
||||
```
|
||||
|
||||
**数据库服务器**:尽量不暴露在外网,业务端口只对应用服务器IP开放
|
||||
|
||||
**跳板机**:SSH端口可以改成非标准端口,减少被扫描的概率
|
||||
|
||||
```text
|
||||
iptables -A INPUT -p tcp --dport 58222 -j ACCEPT
|
||||
```
|
||||
|
||||
### 运维老鸟的小技巧
|
||||
|
||||
1. **规则顺序很重要**:iptables按顺序匹配,常用的规则放前面
|
||||
2. **保存规则是必须的**:配置完一定要`service iptables save`,否则重启就没了
|
||||
3. **先放行再操作**:远程操作时,先在crontab里设置5分钟后清空规则的备份计划,防止把自己锁在外面
|
||||
4. **日志记录**:对可疑的访问尝试记录日志,方便排查
|
||||
|
||||
```text
|
||||
iptables -A INPUT -p tcp --dport 22 -m limit --limit 5/min -j LOG --log-prefix "SSH attempt: "
|
||||
```
|
||||
|
||||
### 新时代的替代品?各有各的好
|
||||
|
||||
现在确实有更现代的方案:
|
||||
|
||||
- **[firewalld](https://zhida.zhihu.com/search?content_id=754271874&content_type=Answer&match_order=1&q=firewalld&zhida_source=entity)**:RedHat系的主流,配置更直观,适合桌面和服务器
|
||||
- **[ufw](https://zhida.zhihu.com/search?content_id=754271874&content_type=Answer&match_order=1&q=ufw&zhida_source=entity)**:Ubuntu的简化工具,几条命令就能搞定基础防护
|
||||
- **云安全组**:管理方便,不耗服务器资源
|
||||
|
||||
习惯iptables,因为它:
|
||||
|
||||
- **通用**:哪个Linux发行版都有
|
||||
- **透明**:每条规则我都知道在干什么
|
||||
- **灵活**:想怎么配就怎么配,没有黑盒子
|
||||
|
||||
### 讲话
|
||||
|
||||
运维这行干久了,你会明白一个道理:**安全往往败在侥幸心理上**。
|
||||
|
||||
“就一会儿,应该没事”
|
||||
“咱们系统小,没人会盯上”
|
||||
“有云防火墙呢,够了”
|
||||
|
||||
不要等代价产生了再后悔。
|
||||
|
||||
开启iptables,就像是给你的服务器买了份保险。
|
||||
|
||||
平时感觉不到它的存在,真出事了,它能救你的命。
|
||||
|
||||
在运维的世界里,谨慎从来都不是坏事。
|
||||
@@ -0,0 +1,215 @@
|
||||
`less` 是一个功能强大的分页查看器,远远不止“空格翻页”那么简单。以下是一些能显著提升效率的实用技巧。
|
||||
|
||||
---
|
||||
|
||||
## 1. 基础但重要的导航
|
||||
|
||||
| 操作 | 快捷键 | 说明 |
|
||||
|------|--------|------|
|
||||
| 向下翻一屏 | `空格` / `f` / `Ctrl+F` | 最常用 |
|
||||
| 向上翻一屏 | `b` / `Ctrl+B` | |
|
||||
| 向下翻一行 | `j` / `e` / `↓` / `Enter` | |
|
||||
| 向上翻一行 | `k` / `y` / `↑` | |
|
||||
| 转到文件首行 | `g` | |
|
||||
| 转到文件末行 | `G` | |
|
||||
| 转到第 n 行 | `n` + `g` 或 `nG` | 例如 `100g` |
|
||||
| 跳转百分比 | `n%` | 例如 `50%` |
|
||||
| 向右滚动 | `→` | 长行 |
|
||||
| 向左滚动 | `←` | |
|
||||
|
||||
---
|
||||
|
||||
## 2. 强大的搜索功能
|
||||
|
||||
| 操作 | 快捷键 | 说明 |
|
||||
|------|--------|------|
|
||||
| 向前搜索 | `/关键词` + `Enter` | 向下查找 |
|
||||
| 向后搜索 | `?关键词` + `Enter` | 向上查找 |
|
||||
| 查找下一个 | `n` | 保持同一方向 |
|
||||
| 查找上一个 | `N` | 反方向 |
|
||||
| 搜索时不区分大小写 | `/关键词` 然后按 `-i` 再按 `Enter` | 或启动时加 `-i` |
|
||||
| 高亮所有匹配 | `/关键词` 后,输入 `&` 并回车 | 见下文“过滤模式” |
|
||||
|
||||
💡 **技巧**:输入 `/` 后按 `↑` 可以调出上一次搜索的关键词。
|
||||
|
||||
---
|
||||
|
||||
## 3. 高级过滤:只显示匹配的行(类似 grep)
|
||||
|
||||
这是 `less` 的一个隐藏神技 —— **& 命令**:
|
||||
|
||||
1. 按 `&` 键
|
||||
2. 输入正则表达式(例如 `error` 或 `^INFO`)
|
||||
3. 回车
|
||||
|
||||
→ `less` 会**只显示包含该模式的行**,其他行隐藏。效果类似于 `grep` 但仍在 `less` 中,可以继续上下翻动、搜索。
|
||||
|
||||
- 退出过滤模式:按 `&` 然后直接回车(不输入内容)
|
||||
|
||||
---
|
||||
|
||||
## 4. 标记与跳转(适合查看长日志或代码)
|
||||
|
||||
| 操作 | 快捷键 | 说明 |
|
||||
|------|--------|------|
|
||||
| 设置标记 | `m` + 小写字母 | 例如 `ma` 在当前行设标记 a |
|
||||
| 跳转到标记 | `'` + 同一字母 | 例如 `'a` 跳回标记 a 位置 |
|
||||
| 查看所有标记 | 无直接命令 | 但标记会保持到退出 |
|
||||
|
||||
💡 标记在查看大文件时极有用:例如看到一处错误,设标记 `me`,继续往下翻,然后 `'e` 快速回来。
|
||||
|
||||
---
|
||||
|
||||
在 `less` 中显示行号有两种常用方法:
|
||||
|
||||
### 方法一:启动时加参数 `-N`
|
||||
|
||||
```bash
|
||||
less -N filename
|
||||
```
|
||||
|
||||
示例效果:
|
||||
```
|
||||
1 first line
|
||||
2 second line
|
||||
3 third line
|
||||
```
|
||||
|
||||
`-N` 表示 `--LINE-NUMBERS`,在每行前面显示行号(从 1 开始)。
|
||||
|
||||
---
|
||||
|
||||
### 方法二:在 `less` 运行中动态切换
|
||||
|
||||
进入 `less` 后,按 `-` 键,然后按 `N`,再按回车。
|
||||
|
||||
步骤分解:
|
||||
1. 按 `-`(减号)
|
||||
2. 输入 `N`(大写字母 N)
|
||||
3. 按 `Enter`
|
||||
|
||||
此时行号会立即显示或隐藏。可以随时再次执行相同操作来切换。
|
||||
|
||||
---
|
||||
|
||||
### 其他实用组合
|
||||
|
||||
- **同时显示行号且不换行(方便看表格数据)**
|
||||
```bash
|
||||
less -NS filename
|
||||
```
|
||||
|
||||
- **显示行号并实时跟踪日志**
|
||||
```bash
|
||||
less -N +F filename
|
||||
```
|
||||
进入后按 `Ctrl+C` 退出跟随模式即可看到行号。
|
||||
|
||||
- **行号显示样式说明**
|
||||
行号默认是右对齐,占据固定宽度。如果文件行数很大(如超过 10 万行),`less` 会自动调整宽度,不会截断。
|
||||
|
||||
---
|
||||
|
||||
### 注意事项
|
||||
|
||||
- `-N` 会稍微降低性能(极大数据集时),但通常可以忽略。
|
||||
- 在管道中使用时,`less -N` 也能工作,例如:`grep error log.txt | less -N`。
|
||||
|
||||
如果你希望**永久开启行号**,可以在 `~/.lesskey` 或环境变量 `$LESS` 中设置:
|
||||
```bash
|
||||
export LESS="-N"
|
||||
```
|
||||
这样每次使用 `less` 都会自动显示行号。
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## 5. 实时查看动态文件(类似 tail -f)
|
||||
|
||||
```bash
|
||||
less +F logfile
|
||||
```
|
||||
或进入 `less` 后按 `F`(大写)。
|
||||
|
||||
此时 `less` 会进入**跟随模式**,新追加的内容会自动显示。要退出跟随模式并回到普通 `less` 视图,按 `Ctrl+C`。
|
||||
|
||||
---
|
||||
|
||||
## 6. 多文件操作
|
||||
|
||||
同时查看多个文件:
|
||||
```bash
|
||||
less file1 file2 file3
|
||||
```
|
||||
|
||||
| 操作 | 快捷键 | 说明 |
|
||||
|------|--------|------|
|
||||
| 查看下一个文件 | `:n` | |
|
||||
| 查看上一个文件 | `:p` | |
|
||||
| 列出所有文件 | `:f` | 显示当前文件名及位置 |
|
||||
| 切换到第 N 个文件 | `:x N` | |
|
||||
|
||||
---
|
||||
|
||||
## 7. 常用命令行选项(启动时)
|
||||
|
||||
| 选项 | 效果 |
|
||||
|------|------|
|
||||
| `less -N filename` | 显示行号 |
|
||||
| `less -i filename` | 搜索时不区分大小写 |
|
||||
| `less -S filename` | 不换行(长行横向滚动),按 `-S` 可动态切换 |
|
||||
| `less -R filename` | 保留 ANSI 颜色(例如 `ls --color=yes \| less -R`) |
|
||||
| `less -F filename` | 若文件短于一屏则直接 cat(适合脚本) |
|
||||
| `less +F filename` | 启动即进入 follow 模式 |
|
||||
| `less +/error filename` | 启动后直接搜索 `error` |
|
||||
|
||||
---
|
||||
|
||||
## 8. 实用内部命令(在 `less` 内输入)
|
||||
|
||||
| 命令 | 效果 |
|
||||
| -------------- | ---------------------------- |
|
||||
| `=` 或 `Ctrl+G` | 显示当前行号、总行数、百分比 |
|
||||
| `-N` 然后回车 | 动态显示/隐藏行号 |
|
||||
| `-S` 然后回车 | 动态切换长行折行/切分模式 |
|
||||
| `-i` 然后回车 | 动态切换搜索大小写敏感 |
|
||||
| `v` | 用默认编辑器($EDITOR)打开当前文件并定位到同一行 |
|
||||
| `h` | 显示完整帮助 |
|
||||
| `q` 或 `Q` | 退出 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 使用正则表达式搜索
|
||||
|
||||
`/` 和 `?` 支持基础正则表达式:
|
||||
- `error\|warn` 匹配 error 或 warn
|
||||
- `^INFO` 行首
|
||||
- `.*abc.*` 任意包含 abc
|
||||
- `\berror\b` 全词匹配
|
||||
|
||||
💡 若想使用扩展正则(如 `+` `?`),可以在 `less` 中按 `-E` 启用(`less -E` 启动或在内部按 `-E` 回车)。不同版本支持度略有差异。
|
||||
|
||||
---
|
||||
|
||||
## 10. 实用组合技
|
||||
|
||||
| 需求 | 命令/步骤 |
|
||||
|------|----------|
|
||||
| 查看大日志,高亮关键词 | `less -p "error" big.log`(启动后自动搜索并高亮) |
|
||||
| 实时跟踪并搜索 | `less +F log` 然后按 `Ctrl+C` 进入普通模式,按 `/` 搜索,再按 `F` 恢复跟踪 |
|
||||
| 过滤出错误行并实时跟踪 | `less +F log` 按 `Ctrl+C`,按 `&error` 过滤,再按 `F` |
|
||||
| 查看命令输出的同时保存副本 | `command \| tee output.log \| less` |
|
||||
|
||||
---
|
||||
|
||||
## 总结:最值得记住的 5 个技巧
|
||||
|
||||
1. **`/` 搜索,`n`/`N` 跳转**
|
||||
2. **`&` 只显示匹配行**(比 `grep` 更灵活)
|
||||
3. **`F` 实时跟踪文件增长**(`tail -f` 的替代)
|
||||
4. **`m`+字母 和 `'`+字母 做标记跳转**
|
||||
5. **`v` 用编辑器打开当前文件**(配合 `git diff` 或日志分析)
|
||||
|
||||
需要更具体的场景(比如查看超大 CSV、源码阅读、日志分析),我可以进一步展开。
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
|
||||
locale -a
|
||||
fc-list :lang=zh
|
||||
echo $LANG $LC_ALL
|
||||
bash -c 'echo -e "\xe4\xb8\xad\xe6\x96\x87"'
|
||||
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
## 背景
|
||||
|
||||
linux性能监测主要是关于Linux系统上程序的性能跟踪。
|
||||
|
||||
**前排提示**: 如果你只想要一个可监测linux性能的工具,并且有可视化的web,并且开源且持续更新,那么你可以查看 [Performance Co-Pilot](https://link.segmentfault.com/?enc=I8PbLahWDovcH2lDY1AzRw%3D%3D.Rp26m8avBzRv%2BendMImpEALexFgfk7xCQMe2M%2B1z8bKyvClWxAoKbSUDyEFWrE8D),比较不错。如果你需要更深入的了解linux性能监测,或者希望降低监测所带来的性能消耗,那么可以往下看。
|
||||
|
||||
从广义上讲, linux跟踪系统由三层组成
|
||||
|
||||
- 信息源
|
||||
- 对接信息源的追踪框架
|
||||
- 前端操作界面
|
||||
|
||||

|
||||
|
||||
## 信息源(event_sources)
|
||||
|
||||

|
||||
|
||||
**事件源是性能监测的基础**,**是跟踪数据的来源**,跟踪框架运行在内核中,负责数据收集、计数。
|
||||
|
||||
信息源主要分为以下三类:
|
||||
|
||||
1. 硬件事件
|
||||
2. 静态探针
|
||||
3. 动态探针
|
||||
|
||||

|
||||
|
||||
### 1.硬件事件
|
||||
|
||||
**性能监控计数器(PMCs**)是处理器上的可编程硬件计数器,用于测量处理器中发生的事件。
|
||||
|
||||
**PMCs是CPU自带的功能**,因此能得到最底层的、用别的手段得不到的性能信息,例如只有通过PMC才能测量CPU指令执行的效率、CPU缓存命中率、内存/数据互联和设备总线的利用率,以及阻塞的指令周期等。
|
||||
|
||||
由于硬件事件比较底层的,我们性能监测的时候,一般很少从此处获取可优化的数据。
|
||||
|
||||
### 2. 静态事件
|
||||
|
||||
#### 2.1 内核跟踪点(tracepoints)
|
||||
|
||||
即linux内核源代码中预先定义的跟踪点。
|
||||
|
||||
linux源码中,**有很多以 trace_ 开头的函数**, 这些都是是linux中预定义好的跟踪点。
|
||||
|
||||
int netif_receive_skb(struct sk_buff *skb)
|
||||
|
||||
{
|
||||
|
||||
trace_netif_receive_skb_entry(skb); // 调用netif_receive_skb对应的trace event函数
|
||||
|
||||
return netif_receive_skb_internal(skb);
|
||||
|
||||
}
|
||||
|
||||
如想查看这些 trace_ 开头的函数, 可到 `/sys/kernel/debug/tracing/`下查看
|
||||
|
||||
#### 2.2 USDT探针
|
||||
|
||||
USDT探针(User Statically-Defined Tracing)全称是用户级静态定义跟踪,需要在源码中插入 DTRACE_PROBE() 代码,并编译到应用程序中。这里不做详述。
|
||||
|
||||
### 3 动态探针
|
||||
|
||||
顾名思义,则是指没有事先在代码中定义,但却**可以在运行时动态添加的探针**(重点是不需要重新编译目标程序)。主要由kprobe(内核探针)和uprobe(用户态探针)组成。
|
||||
|
||||
#### 3.1 内核探针kprobes
|
||||
|
||||
kprobes(Kernel Dynamic Probes)。一种提供**内核函数动态跟踪**的 Linux 内核技术。
|
||||
|
||||
本质: **是在指定的探测点**(比如函数的某行, 函数的入口地址和出口地址, 或者内核的指定地址处)**插入一组处理程序**. 内核执行到这组处理程序的时候就可以获取到当前正在执行的上下文信息。
|
||||
|
||||
详细可查看[An introduction to KProbes](https://link.segmentfault.com/?enc=pWx1IyYTst7XSBCnoHOGUQ%3D%3D.Bh%2FQ%2BthpR863%2BLX2x%2Fn0GCiHdV7zrgDaivy%2FtWfsHQf%2FHCxB4T85A4NeZ4FTOKEi)
|
||||
|
||||
#### 3.2 用户态探针uprobes
|
||||
|
||||
一种提供**用户级函数动态跟踪**的 Linux 内核技术
|
||||
|
||||
详细可查看[Linux uprobe:用户级动态跟踪](https://link.segmentfault.com/?enc=RQResacFYQ84MCxrVW%2FjOQ%3D%3D.kzO2kG3zIhMH8RERHxgShB3gINabhcG63PcbFeKML9HsYCr0y0h63wIXFl3gQPDVDkQ1tmzu1ucvJaaJyARPloCVuTDdjKZVCTTsB5Hrc9E%3D)
|
||||
|
||||
---
|
||||
|
||||
总之,只需要了解**信息源主要分为硬件事件,静态探针,动态探针**
|
||||
|
||||
## 追踪框架
|
||||
|
||||
先介绍一下这方面的一个大神:[brendangregg](https://link.segmentfault.com/?enc=n4I1R0wLobR7Qm7AdSwR7g%3D%3D.UNXi6JWw%2Fem%2Fjk2NhlSxhJ7GnzYZ9MBnQyo7UEY9PFPilBg1qdGHhhdwuiaEcvF0), 很多编入内核的追踪框架都有他的贡献。
|
||||
|
||||
以下是他画的一张关于 linux 观察性能工具的一张图。
|
||||

|
||||
|
||||
可以说,linux 的各方面(cpu, 磁盘,调度,网络,io)都有对应的工具。
|
||||
|
||||
### 基础命令
|
||||
|
||||
我们可以先用一些基础命令来查看一些基本信息,比如上图中的 top, free等
|
||||
|
||||
**top命令:**
|
||||
|
||||

|
||||
|
||||
**平均负载**:三个值分别为1分钟、5分钟、15分钟前到现在的平均值(根据cpu数量去判断。最佳状态为 平均负载约等于cpu个数)
|
||||
|
||||
**Cpu(s)**:
|
||||
|
||||
1. 32.0%us(用户使用cpu占比)
|
||||
2. 2.0%sy(系统使用cpu占比)
|
||||
3. 0.0%ni(用户空间内改变过优先级的进程占用CPU百分比)
|
||||
4. 65.3%id(空闲cpu占比)
|
||||
5. 0.1%wa(等待输入输出CPU时间百分比)
|
||||
6. 0.1%hi(CPU服务于硬件中断所耗费的时间总额)
|
||||
7. 0.5%si(CPU服务软中断所耗费的时间总额)
|
||||
8. 0.0%st(Steal Time)
|
||||
|
||||
**Mem:**
|
||||
|
||||
1. total(物理内存总量)
|
||||
2. used(已使用的物理内存)
|
||||
3. free(剩余的物理内存)
|
||||
4. buffers(缓冲区内存)
|
||||
|
||||
**free 命令:**
|
||||

|
||||
|
||||
free命令显示内存的使用信息。默认按照k(b)的计数单位统计。也可以 -m 显示单位为 MB。更多参数可以查看 man free
|
||||
|
||||
**iostat命令:**
|
||||
iostat主要用于监控系统设备的IO负载情况。 可以获取io读写的速度等信息。 更多参数可以查看 man iostat
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
**其实 top, free, iostat 等一些命令,** **实质上都是通过读取 linux 的 /proc/ 文件夹下的文件 来实现数据报告**。
|
||||
|
||||
例如
|
||||
|
||||
> **/proc/cpuinfo** : cpu的硬件信息,如型号、速率、核数、cache大小等
|
||||
>
|
||||
> **/proc/meminfo**:内存的信息,如内存总量、free空间、swap空间
|
||||
>
|
||||
> **/proc/stat:** 所有的CPU活动状态的信息
|
||||
>
|
||||
> **/proc/diskstats** :磁盘信息
|
||||
>
|
||||
> **/proc/loadavg** : 根据过去一段时间内CPU和IO的状态得出的负载状态
|
||||
|
||||

|
||||
|
||||
所以,**直接 cat 相应的文件无疑也是个好方法**
|
||||
|
||||
### 框架
|
||||
|
||||
在来说说框架
|
||||
|
||||

|
||||
在这张图的左边,列出了比较常用的框架
|
||||
|
||||
1. perf
|
||||
2. ftrace
|
||||
3. lttng
|
||||
4. BCC
|
||||
5. bpftrace
|
||||
|
||||
当然也有更多:
|
||||

|
||||
|
||||
这是 brendangregg 大神在2015年的一篇文章 [选择 Linux 跟踪器](https://link.segmentfault.com/?enc=Nzqzn3IgCMDSw6e9AmmCoA%3D%3D.GDOSgy0GFBKOaZvOTaCc9sj8ccmsaTJTol6vHs%2F6RLDlGBLtXfOm3PETHRsutr4nuK66DYDxTB2XRPnjs50FZmXYq2ae9EiVs70%2BYZ%2FGhdU%3D) 到今日也有一定的参考价值。
|
||||
|
||||
博主本人比较推荐了解以下框架:
|
||||
|
||||
1. ftrace (内置在Linux内核中),比较经典
|
||||
2. perf (来源位于 Linux 内核中,通常通过 linux-tools-common 包添加)
|
||||
3. bpftrace (持续开源更新中)
|
||||
|
||||
以下是相关这三个工具的了解
|
||||

|
||||
|
||||
### ftrace
|
||||
|
||||
ftrace 在/sys/kernel/debug/tracing 位置上, 构建了一个文件系统,用户通过读写文件的方式来使用ftrace。
|
||||
|
||||
一切皆文件 的哲学在这里体现的淋漓尽致。
|
||||
|
||||
操作步骤:
|
||||
|
||||
#1、进入debugfs目录
|
||||
$ cd /sys/kernel/debug/tracing
|
||||
#如果找不到目录,执行下列命令挂载debugfs:
|
||||
$ mount -t debugfs nodev /sys/kernel/debug
|
||||
#2、查询支持的追踪器
|
||||
$ cat available_tracers
|
||||
#常用的有两种:
|
||||
#
|
||||
#- function 表示跟踪函数的执行;
|
||||
#- function_graph 则是跟踪函数的调用关系;
|
||||
#3、查看支持追踪的内核函数和事件。其中函数就是内核中的函数名,而事件,则是内核源码中预先定义的跟踪点。
|
||||
#//查看内核函数
|
||||
$ cat available_filter_functions
|
||||
#//查看事件
|
||||
$ cat available_events
|
||||
#4、设置追踪函数:
|
||||
$ echo do_sys_open > set_graph_function
|
||||
#5、设置追踪器
|
||||
$ echo function_graph > current_tracer
|
||||
$ echo funcgraph-proc > trace_options
|
||||
#6、开启追踪
|
||||
$ echo 1 > tracing_on
|
||||
#7、执行一个 ls 命令后,再关闭跟踪
|
||||
$ ls
|
||||
$ echo 0 > tracing_on
|
||||
#8、最后一步,查看跟踪结果
|
||||
$ cat trace
|
||||
|
||||
例如,我们上面的代码执行了如下步骤:
|
||||
1 .跟踪了 `do_sys_open` 这个系统函数
|
||||
|
||||
2. 我们设置跟踪器为 `function_graph`
|
||||
3. 开启跟踪
|
||||
4. 执行ls命令,因为该命令会调用 `do_sys_open` 这个系统函数
|
||||
5. 查看跟踪结果是否跟踪到这个函数
|
||||
|
||||

|
||||
|
||||
查看该跟踪结果, 可以查看 执行该命令的CPU, cmd, pid. 执行事件,调用栈。
|
||||
|
||||
从而函数的调用栈和调用时间,可分析出该进程在哪个函数上耗费的时间较多。
|
||||
|
||||
一顿操作下来,是不是感觉有点麻烦?
|
||||
所以,我们后续可以尝试一下 ftrace的命令行版: trace-cmd, 亦或是 集成有 ftrace 的 perf_tools。
|
||||
|
||||
### perf
|
||||
|
||||
perf 这个工具原理比较简单,而且生成的火焰图,可迅速分析 函数的热点, 非常推荐使用,而且perf工具非常强大,如果用的好基本上可以完全满足应用程序的性能分析和调优工作的需求。
|
||||
|
||||
#### 原理
|
||||
|
||||

|
||||
|
||||
原理:**每隔一个固定时间(取决于采样率),看当前是哪个进程、哪个函数**,**然后给对应的进程和函数加一个统计值**,这样就知道CPU有多少时间在某个进程或某个函数上了
|
||||
|
||||
当 80% 的采样都落在 func1时, 我们大概率可以得出: 函数热点在 func1上。
|
||||
|
||||
同时我们也可以得出:**采样率越大,结果越精确**
|
||||
|
||||
**perf top命令:**
|
||||
适合监控整个系统的性能
|
||||
|
||||
$ perf top --call-graph fractal
|
||||
|
||||

|
||||
|
||||
**perf stat命令:**
|
||||
|
||||
比较适合单个程序的性能分析
|
||||
|
||||
`$ sudo perf stat ls -lt`
|
||||
|
||||

|
||||
|
||||
**perf record/report:**
|
||||
record/report 更适合对程序进行更细粒度的分析
|
||||
|
||||
#在当前目录生成perf.data文件
|
||||
|
||||
sudo perf record -a -g -p <pid>
|
||||
|
||||
#结果显示
|
||||
|
||||
sudo perf report
|
||||
|
||||

|
||||
|
||||
可以获取该进程的热点在哪个函数。
|
||||
|
||||
**火焰图:**
|
||||
|
||||
perf 结合 brendangregg 大神开发的 [火焰图工具](https://link.segmentfault.com/?enc=GeoHegINlCXdcGX4KUOaEw%3D%3D.RXciMNuqy7Be9Hdgsh5mmWqMYcMbhITRUqgLxScD%2BF9DyWv4gPZOp1dr%2Bxg6bH93)
|
||||
|
||||
可以在如下链接看看效果:
|
||||
|
||||
[https://www.brendangregg.com/FlameGraphs/example-perf.svg](https://link.segmentfault.com/?enc=R1ZgIG5LgTDCfHRj5lAGYg%3D%3D.RJM2xvK%2B5z1gGxmmCWdIJtJUrthxFAIJ9Xmy6xmhDt%2BUvgVg%2BE8D9yVo6oMnGu2aEzftVXv1TdZwENdwj7YJCw%3D%3D)
|
||||
|
||||
当某一长块占下方长块大部分长度时,该长块所代表的函数就是该进程的cpu热点。
|
||||
|
||||
### bpftrace
|
||||
|
||||
开源并持续更新的仓库,可以看看。
|
||||
[bpftrace github仓库](https://link.segmentfault.com/?enc=DIdHI6Oy2U1SqkLV61Lb0Q%3D%3D.GNfaefztZX%2Fi3g01ChGPqkw%2FVoHWzTcplGYJ75oVQipxaE9xJwqc3Oa4WsblXZN0)
|
||||
|
||||
## 总结
|
||||
|
||||
可以按自己需求选择需要自己需要的工具。
|
||||
|
||||
希望内容可以帮助到一些人。
|
||||
|
||||
参考:
|
||||
[https://leezhenghui.github.io/linux/2019/03/05/exploring-usdt...](https://link.segmentfault.com/?enc=y5fC2NVDakGEtGWSCaOR6w%3D%3D.IMtuAi5iXo2OrNmyhd4ojMAWPsXjs429urlZe9q56v9HHR8G9HKuraMVT%2FXqifCBbxW4BsvPLion7uOVH419BROqYpS5pQHGhnz3ReRy8cA%3D)
|
||||
[https://www.brendangregg.com/index.html](https://link.segmentfault.com/?enc=yUkOSWeJ3X%2Fb%2F8uTB0mMNg%3D%3D.nsqfWFD8kKMzdQu9dKao%2Fuvhn3q%2B5S75iX0mvhAPb%2BLCX00f6%2BInwDU9BomaHuVE)
|
||||
[https://www.brendangregg.com/blog/2015-07-08/choosing-a-linux...](https://link.segmentfault.com/?enc=vzTGAWjOCULAZyajlUctUg%3D%3D.ZXFZAO4i7cr7R%2F4gVXowkNHETTWFCzSvLlPd2Qq1CAS1OwUCj2ktwwoncHFtpS0fddTaNRwakBi%2BS7kT4NYlJKzicwH%2Bb1diE0JeRyDkJew%3D)
|
||||
[https://zhuanlan.zhihu.com/p/672872237](https://link.segmentfault.com/?enc=FTig72OfaU5CkdyGHLCOEA%3D%3D.2uHpKmD6bDeAgu5XKltM0RtD9KB8Wnq96DUCzvsRaNGHmj1IgGNj1fJSTb8kH9gS)
|
||||
|
||||
[linux](https://segmentfault.com/t/linux)
|
||||
@@ -0,0 +1,21 @@
|
||||
flowchart TD
|
||||
A[开始: 在终端输入 sudo nmtui] --> B[nmtui 主菜单]
|
||||
B --> C{选择要执行的操作}
|
||||
|
||||
C -- 最常用 --> D[编辑连接]
|
||||
C --> E[启用连接]
|
||||
C --> F[设置主机名]
|
||||
|
||||
D --> G[选择或添加连接]
|
||||
G --> H{连接类型?}
|
||||
|
||||
H -- 以太网 --> I[配置有线网络<br>设置IP(静态/DHCP), DNS等]
|
||||
H -- Wi-Fi --> J[扫描并选择无线网络<br>输入密码连接]
|
||||
|
||||
I --> K[保存配置并返回]
|
||||
J --> K
|
||||
|
||||
K --> L[可选: 返回主菜单“启用连接”<br>以激活新配置]
|
||||
L --> M[完成退出]
|
||||
E --> M
|
||||
F --> M
|
||||
@@ -0,0 +1,30 @@
|
||||
针对你的需求——网络断开时仍能读写,网络恢复后自动同步——`mount-s3` 本身**无法实现**。它的设计哲学是强一致性,所有操作都依赖与 S3 的实时交互,没有提供参数来调整为“离线优先”模式。
|
||||
|
||||
要实现你想要的效果,需要换用其他专为这种场景设计的工具。
|
||||
|
||||
### 🛠️ 替代方案推荐:rclone
|
||||
|
||||
`rclone` 是目前实现你需求的最佳选择。它是一款功能强大的命令行工具,支持将 S3 挂载为文件系统,并提供了强大的 **VFS(虚拟文件系统)缓存**功能。
|
||||
|
||||
#### 核心配置参数
|
||||
|
||||
通过以下参数,你可以将 `rclone` 配置为“离线优先”模式:
|
||||
|
||||
* **`--vfs-cache-mode full`**:**最关键参数**。它会让所有读写操作都先经过本地缓存。写入时,数据先存本地,稍后再异步上传到 S3;读取时,文件会被缓存到本地供后续离线使用。这能有效隔离网络问题对应用的影响。
|
||||
|
||||
* **`--vfs-cache-max-size`**:设置本地缓存的最大大小,例如 `10G` 或 `500G`。
|
||||
|
||||
* **`--vfs-cache-max-age`**:设置缓存文件的有效期,例如 `24h`。
|
||||
|
||||
* **`--vfs-write-back`**:设置数据在本地缓存后,延迟多久上传到 S3,例如 `5s`。
|
||||
|
||||
* **`--retries` 与 `--low-level-retries`**:设置网络请求失败时的重试次数,例如 `--retries 10 --low-level-retries 20`。
|
||||
|
||||
#### 使用示例
|
||||
|
||||
1. **安装 rclone**:参考 [rclone.org](https://rclone.org/) 官方文档。
|
||||
2. **配置 S3 远程存储**:运行 `rclone config` 按指引添加你的 S3 端点(支持 AWS S3 及所有 S3 兼容服务)。
|
||||
3. **执行挂载命令**:
|
||||
```bash
|
||||
rclone mou可以帮忙输入是吧
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
vim ./scripts/install.sh
|
||||
:set ff=unix
|
||||
:wq
|
||||
|
||||
|
||||
|
||||
:e ++ff=dos " 强制按 dos 读,让 \r 归入换行符
|
||||
:set ff=unix
|
||||
:wq
|
||||
@@ -0,0 +1,266 @@
|
||||
[SSH](https://zhida.zhihu.com/search?content_id=763139071&content_type=Answer&match_order=1&q=SSH&zhida_source=entity)连服务器跑脚本,网一断任务就没了,这事吃过几次亏之后我开始用[tmux](https://zhida.zhihu.com/search?content_id=763139071&content_type=Answer&match_order=1&q=tmux&zhida_source=entity)。后来发现这东西不止能防断网,还有不少骚操作。
|
||||
|
||||
---
|
||||
|
||||
**1. 断网保命**
|
||||
|
||||
SSH连服务器跑个脚本,网一抖任务就没了。
|
||||
|
||||
tmux解决这个问题:
|
||||
|
||||
```text
|
||||
tmux new -s deploy
|
||||
./deploy.sh
|
||||
# Ctrl+b d 断开
|
||||
```
|
||||
|
||||
任务在服务器上跑着,你断开连接、关电脑、换个地方都行。回来 `tmux a -t deploy` 继续看。
|
||||
|
||||
我有次在高铁上部署,信号断断续续,靠这个救了。不然那个迁移脚本跑一半断了,数据库就成半成品了。
|
||||
|
||||
---
|
||||
|
||||
**2. 一屏多用**
|
||||
|
||||
```text
|
||||
Ctrl+b % # 左右分屏
|
||||
Ctrl+b " # 上下分屏
|
||||
Ctrl+b 方向键 # 切换
|
||||
```
|
||||
|
||||
我日常开发的布局:
|
||||
|
||||
```text
|
||||
┌─────────────────┬─────────────────┐
|
||||
│ │ tail -f log │
|
||||
│ vim/code ├─────────────────┤
|
||||
│ │ shell │
|
||||
└─────────────────┴─────────────────┘
|
||||
```
|
||||
|
||||
左边写代码,右上角看日志,右下角跑命令。一个终端三件事。
|
||||
|
||||
`Ctrl+b z` 可以把当前面板最大化,再按一次恢复。看日志的时候放大看,看完缩回去,不用来回开终端。
|
||||
|
||||
---
|
||||
|
||||
**3. 会话不是一次性的**
|
||||
|
||||
很多人把tmux当成”防断网工具”,用完就kill。
|
||||
|
||||
其实可以给不同项目建不同会话:
|
||||
|
||||
```text
|
||||
tmux new -s project-a
|
||||
tmux new -s project-b
|
||||
tmux new -s server-monitor
|
||||
```
|
||||
|
||||
每个会话保持自己的布局、目录、运行的命令。
|
||||
|
||||
`Ctrl+b s` 可以在会话之间切换,像切换桌面一样。早上来了 `tmux a -t project-a`,下班 `Ctrl+b d`,第二天接着搞。
|
||||
|
||||
---
|
||||
|
||||
**4. 改掉反人类的默认配置**
|
||||
|
||||
tmux默认的 `Ctrl+b` 太难按了,b键离得太远。
|
||||
|
||||
改成 `Ctrl+a`:
|
||||
|
||||
```text
|
||||
# ~/.tmux.conf
|
||||
unbind C-b
|
||||
set -g prefix C-a
|
||||
bind C-a send-prefix
|
||||
```
|
||||
|
||||
分屏的 `%` 和 `"` 也反直觉,改成 `|` 和 `-`:
|
||||
|
||||
```text
|
||||
bind | split-window -h -c "#{pane_current_path}"
|
||||
bind - split-window -v -c "#{pane_current_path}"
|
||||
```
|
||||
|
||||
`-c "#{pane_current_path}"` 是让新分出来的面板保持当前目录,不然每次都从~开始,还得cd。
|
||||
|
||||
鼠标支持默认是关的,开了之后可以点击切换面板、拖拽调整大小、滚轮看历史:
|
||||
|
||||
```text
|
||||
set -g mouse on
|
||||
```
|
||||
|
||||
改完后 `tmux source-file ~/.tmux.conf` 生效。
|
||||
|
||||
---
|
||||
|
||||
**5. vim键位移动**
|
||||
|
||||
如果你用vim,在tmux里也用vim键位会很爽:
|
||||
|
||||
```text
|
||||
# ~/.tmux.conf
|
||||
bind h select-pane -L
|
||||
bind j select-pane -D
|
||||
bind k select-pane -U
|
||||
bind l select-pane -R
|
||||
```
|
||||
|
||||
这样 `Ctrl+a h/j/k/l` 就能上下左右切换面板,不用去够方向键。
|
||||
|
||||
复制模式也改成vi键位:
|
||||
|
||||
```text
|
||||
setw -g mode-keys vi
|
||||
```
|
||||
|
||||
`Ctrl+a [` 进入复制模式后,用 j/k 上下移动,`/` 搜索,空格开始选择,回车复制。
|
||||
|
||||
---
|
||||
|
||||
**6. 同步输入**
|
||||
|
||||
这个很少有人知道:
|
||||
|
||||
```text
|
||||
Ctrl+a :setw synchronize-panes on
|
||||
```
|
||||
|
||||
开了之后,你在一个面板敲的东西会同时出现在所有面板。
|
||||
|
||||
用途:同时操作多台服务器。
|
||||
|
||||
```text
|
||||
# 开四个面板,分别ssh到四台服务器
|
||||
# 然后开启同步输入
|
||||
# 敲一遍命令,四台服务器同时执行
|
||||
```
|
||||
|
||||
批量重启服务、批量改配置,效率翻几倍。
|
||||
|
||||
再敲一遍 `setw synchronize-panes off` 关掉。
|
||||
|
||||
---
|
||||
|
||||
**7. 发送命令到其他面板**
|
||||
|
||||
有时候想在脚本里控制tmux,比如自动化开发环境启动:
|
||||
|
||||
```text
|
||||
#!/bin/bash
|
||||
tmux new-session -d -s dev
|
||||
|
||||
# 第一个面板跑后端
|
||||
tmux send-keys -t dev 'cd ~/project/backend && npm run dev' Enter
|
||||
|
||||
# 分屏,第二个面板跑前端
|
||||
tmux split-window -h -t dev
|
||||
tmux send-keys -t dev 'cd ~/project/frontend && npm run dev' Enter
|
||||
|
||||
# 再分屏,第三个面板开着备用
|
||||
tmux split-window -v -t dev
|
||||
|
||||
tmux attach -t dev
|
||||
```
|
||||
|
||||
每天跑一下这个脚本,开发环境就全起来了,不用手动一个个开。
|
||||
|
||||
---
|
||||
|
||||
**8. 会话持久化**
|
||||
|
||||
tmux的会话在服务器重启后会没。
|
||||
|
||||
装 [tmux-resurrect](https://zhida.zhihu.com/search?content_id=763139071&content_type=Answer&match_order=1&q=tmux-resurrect&zhida_source=entity) 插件可以保存和恢复:
|
||||
|
||||
```text
|
||||
# 装插件管理器
|
||||
git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm
|
||||
|
||||
# ~/.tmux.conf 加上
|
||||
set -g @plugin 'tmux-plugins/tpm'
|
||||
set -g @plugin 'tmux-plugins/tmux-resurrect'
|
||||
run '~/.tmux/plugins/tpm/tpm'
|
||||
```
|
||||
|
||||
重载配置,`Ctrl+a I` 安装插件。
|
||||
|
||||
之后 `Ctrl+a Ctrl+s` 保存会话,`Ctrl+a Ctrl+r` 恢复。服务器重启了也能恢复到之前的布局。
|
||||
|
||||
---
|
||||
|
||||
**9. 共享终端**
|
||||
|
||||
两个人可以同时连同一个会话:
|
||||
|
||||
```text
|
||||
# A创建
|
||||
tmux new -s pair
|
||||
|
||||
# B连接
|
||||
tmux a -t pair
|
||||
```
|
||||
|
||||
两个人看到的完全同步,A敲的B能看到,B敲的A也能看到。
|
||||
|
||||
远程带人排查问题、[pair programming](https://zhida.zhihu.com/search?content_id=763139071&content_type=Answer&match_order=1&q=pair+programming&zhida_source=entity)都能用。比共享屏幕流畅,因为只传文字不传画面。
|
||||
|
||||
我带新人排查线上问题就用这个,他在他电脑上操作,我实时看着,随时能接手。如果服务器在内网连不上的话,可以用星空组网之类的工具先把网络打通,然后再共享tmux会话。
|
||||
|
||||
---
|
||||
|
||||
**10. 快速切窗口**
|
||||
|
||||
窗口多了之后,`Ctrl+a n/p` 一个个切太慢。
|
||||
|
||||
`Ctrl+a w` 可以列出所有窗口,上下选择,回车切换。还能预览每个窗口里在跑什么。
|
||||
|
||||
或者直接 `Ctrl+a 数字` 切到对应窗口。但默认窗口编号从0开始,改成从1开始更顺手:
|
||||
|
||||
```text
|
||||
set -g base-index 1
|
||||
setw -g pane-base-index 1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
最后贴一下我的完整配置,直接抄就能用:
|
||||
|
||||
```text
|
||||
# ~/.tmux.conf
|
||||
|
||||
# 前缀改成Ctrl+a
|
||||
unbind C-b
|
||||
set -g prefix C-a
|
||||
bind C-a send-prefix
|
||||
|
||||
# 鼠标
|
||||
set -g mouse on
|
||||
|
||||
# 历史
|
||||
set -g history-limit 50000
|
||||
|
||||
# 窗口编号从1开始
|
||||
set -g base-index 1
|
||||
setw -g pane-base-index 1
|
||||
set -g renumber-windows on
|
||||
|
||||
# 分屏
|
||||
bind | split-window -h -c "#{pane_current_path}"
|
||||
bind - split-window -v -c "#{pane_current_path}"
|
||||
|
||||
# vim式移动
|
||||
bind h select-pane -L
|
||||
bind j select-pane -D
|
||||
bind k select-pane -U
|
||||
bind l select-pane -R
|
||||
setw -g mode-keys vi
|
||||
|
||||
# 减少延迟
|
||||
set -sg escape-time 0
|
||||
|
||||
# 快速重载
|
||||
bind r source-file ~/.tmux.conf \; display "Reloaded"
|
||||
```
|
||||
|
||||
tmux上手有点门槛,但用熟了真的回不去。现在让我用普通终端开一堆窗口来回切,我会很难受。
|
||||
@@ -0,0 +1,17 @@
|
||||
1、执行更改命令
|
||||
|
||||
sudo tzselect
|
||||
|
||||
- 1.
|
||||
|
||||
依次输入如下数字
|
||||
|
||||

|
||||
|
||||
提示如下信息为更改成功
|
||||
|
||||

|
||||
|
||||
2、创建软连接
|
||||
|
||||
sudo ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
快捷键添加到
|
||||
C:\Users\occul\AppData\Roaming\Microsoft\Windows\Start Menu\Programs
|
||||
@@ -0,0 +1,84 @@
|
||||
错误信息 `QEMU guest agent is not configured` 是因为你使用了 `--quiesce` 参数。该参数要求虚拟机内部必须运行 **QEMU Guest Agent**,并且 libvirt 已经配置好与之通信的通道。`--quiesce` 的作用是在快照前通过 guest agent 暂停虚拟机的文件系统 I/O,以保证快照的数据一致性(尤其对 Windows 系统很重要)。
|
||||
|
||||
---
|
||||
|
||||
## 🎯 镜像管理
|
||||
安装并配置 QEMU Guest Agent(推荐)这是最规范的做法,既能保证快照一致性,又能实现后续更多管理功能(如获取 IP、关机等)。
|
||||
|
||||
#### 1. 下载 virtio-win ISO(如果尚未下载)
|
||||
```bash
|
||||
wget https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso -O /tmp/virtio-win.iso
|
||||
```
|
||||
|
||||
#### 2. 挂载 ISO 到 Windows 虚拟机
|
||||
你可以动态挂载,也可以关机后添加一个光驱设备。这里采用**动态挂载**(虚拟机运行时):
|
||||
```bash
|
||||
cat > /tmp/attach-ga.xml <<EOF
|
||||
<disk type='file' device='cdrom'>
|
||||
<driver name='qemu' type='raw'/>
|
||||
<source file='/tmp/virtio-win.iso'/>
|
||||
<target dev='sdc' bus='sata'/>
|
||||
<readonly/>
|
||||
</disk>
|
||||
EOF
|
||||
|
||||
virsh attach-device win10-ltsc /tmp/attach-ga.xml --live
|
||||
```
|
||||
|
||||
#### 3. 在 Windows 虚拟机内安装 Guest Agent
|
||||
- 打开“此电脑”,找到新出现的虚拟光驱(盘符可能为 D: 或 E:)。
|
||||
- 进入 `guest-agent` 文件夹,双击安装 `qemu-ga-x86_64.msi`(根据系统版本选择)。
|
||||
- 按向导完成安装,**不需要重启**(但后续最好重启一次)。
|
||||
|
||||
#### 4. 配置 libvirt 虚拟机 XML,添加 guest agent 通道
|
||||
如果虚拟机正在运行,可以先关闭(`virsh shutdown win10-ltsc`),然后编辑配置:
|
||||
```bash
|
||||
virsh edit win10-ltsc
|
||||
```
|
||||
在 `<devices>` 段内,添加以下内容:
|
||||
```xml
|
||||
<channel type='unix'>
|
||||
<source mode='bind' path='/var/lib/libvirt/qemu/channel/target/domain-1-win10-ltsc/org.qemu.guest_agent.0'/>
|
||||
<target type='virtio' name='org.qemu.guest_agent.0' state='connected'/>
|
||||
</channel>
|
||||
```
|
||||
保存退出。
|
||||
|
||||
#### 5. 启动虚拟机并验证 guest agent
|
||||
```bash
|
||||
virsh start win10-ltsc
|
||||
# 等待虚拟机启动后,检查 guest agent 是否在线
|
||||
virsh guestinfo win10-ltsc --name
|
||||
```
|
||||
如果返回了主机名,说明 agent 工作正常。
|
||||
|
||||
#### 6. 重新执行快照命令(现在应该成功)
|
||||
```bash
|
||||
virsh snapshot-create-as win10-ltsc win10-clean --disk-only --quiesce
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 方案 B:去掉 `--quiesce` 参数(简单但不推荐)
|
||||
如果你不想安装 guest agent,可以放弃数据一致性保证,直接创建快照:
|
||||
```bash
|
||||
virsh snapshot-create-as win10-ltsc win10-clean --disk-only
|
||||
```
|
||||
这样不会报错,但快照时文件系统可能处于不一致状态(尤其当有大量磁盘写入时),恢复后可能触发磁盘检查或数据丢失。**仅建议用于测试或非关键环境**。
|
||||
|
||||
---
|
||||
|
||||
## 📌 补充说明
|
||||
- **快照位置**:即使没有 `--quiesce`,快照数据仍会保存在原始磁盘文件内(内部快照)或生成新文件(外部快照),位置不变。
|
||||
- **外部快照**:如果你想生成独立的快照文件(方便备份),可以指定 `--diskspec` 参数,例如:
|
||||
```bash
|
||||
virsh snapshot-create-as win10-ltsc win10-clean --disk-only --diskspec vda,file=/vm/disk/win10-clean.qcow2
|
||||
```
|
||||
- **恢复快照**:使用 `virsh snapshot-revert win10-ltsc win10-clean`。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 建议
|
||||
如果你打算长期使用快照功能,**请采用方案 A**,配置 guest agent 后快照会更可靠。安装过程不复杂,后续对虚拟机管理也很有帮助。
|
||||
|
||||
如果方案 A 中遇到任何问题(例如 XML 编辑报错),请告诉我具体的错误信息,我会帮你调整。
|
||||
@@ -0,0 +1 @@
|
||||
![[130841-20240504215108564-1504327544.png]]
|
||||
@@ -0,0 +1,4 @@
|
||||
yum install yum --downloadonly --downloaddir=./yum
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
|
||||
|
||||
`GParted` 是一款适用于 `Linux` 的图形化磁盘分区管理工具,通过它可以便捷地为 `VMware` 中的 `Ubuntu Desktop` 增大磁盘空间。然而你可能正在使用 `Ubuntu Server`,并不想要安装或并不被允许安装图形化界面,本文介绍了如何在不使用 `GParted` 的情况下,通过命令行使用自带的工具为 `VMware` 中的 `Ubuntu Server` 增大磁盘空间。
|
||||
|
||||
> 请注意辨别磁盘空间是真的接近耗尽,而不是在系统安装时只真正使用了大约一半空间。参见 [Ubuntu server 20.04 安装后没有分配全部磁盘空间](https://www.moralok.com/2023/06/24/Ubuntu-server-20-04-not-all-disk-space-was-allocated-after-installation/ "Ubuntu server 20.04 安装后没有分配全部磁盘空间")
|
||||
|
||||
## [](https://www.moralok.com/2024/01/14/increase-disk-space-of-Ubuntu-server-on-VMware-without-using-GParted/#%E8%83%8C%E6%99%AF%E4%BB%8B%E7%BB%8D "背景介绍")背景介绍
|
||||
|
||||
环境如下:
|
||||
|
||||
- VMware® Workstation 17 Pro 17.5.0 build-22583795
|
||||
- Ubuntu 20.04.6 LTS (GNU/Linux 5.4.0-169-generic x86_64)
|
||||
|
||||
尽管最初按照心理预期为 `Ubuntu Server` 分配了 `50G` 的磁盘空间,主要用于运行一些 `Docker` 容器,但是不知不觉之间发现磁盘空间的占用率还是上升到了 `90%`。一时之间想不到可以清理什么,决定先增大一些磁盘空间。
|
||||
|
||||
- 使用 `df -h` 命令显示文件系统的总空间和可用空间信息。可知 `/dev/mapper/ubuntu--vg-ubuntu--lv` 已使用 `95%`。
|
||||
|
||||
$ df -h <br>Filesystem Size Used Avail Use% Mounted on <br>udev 7.8G 0 7.8G 0% /dev <br>tmpfs 1.6G 3.0M 1.6G 1% /run <br>/dev/mapper/ubuntu--vg-ubuntu--lv 48G 43G 2.5G 95% / <br>tmpfs 7.8G 0 7.8G 0% /dev/shm <br>tmpfs 5.0M 0 5.0M 0% /run/lock <br>tmpfs 7.8G 0 7.8G 0% /sys/fs/cgroup <br>vmhgfs-fuse 932G 859G 73G 93% /mnt/hgfs <br>/dev/sda2 2.0G 209M 1.6G 12% /boot <br>/dev/loop0 64M 64M 0 100% /snap/core20/2015 <br>/dev/loop1 64M 64M 0 100% /snap/core20/2105 <br>/dev/loop2 41M 41M 0 100% /snap/snapd/20290 <br>/dev/loop3 92M 92M 0 100% /snap/lxd/24061 <br>/dev/loop4 41M 41M 0 100% /snap/snapd/20671 <br>tmpfs 1.6G 0 1.6G 0% /run/user/1000|
|
||||
|
||||
|
||||
## [](https://www.moralok.com/2024/01/14/increase-disk-space-of-Ubuntu-server-on-VMware-without-using-GParted/#%E8%A7%A3%E5%86%B3%E6%AD%A5%E9%AA%A4 "解决步骤")解决步骤
|
||||
|
||||
### [](https://www.moralok.com/2024/01/14/increase-disk-space-of-Ubuntu-server-on-VMware-without-using-GParted/#%E8%B0%83%E6%95%B4%E8%99%9A%E6%8B%9F%E7%A3%81%E7%9B%98%E5%A4%A7%E5%B0%8F "调整虚拟磁盘大小")调整虚拟磁盘大小
|
||||
|
||||
> 不论如何,需要先修改 `VMware` 的相关设置。
|
||||
|
||||
1. 先将客户机 `Ubuntu server` 关机
|
||||
2. 然后通过“虚拟机设置 -> 硬盘 -> 扩展 -> 最大磁盘大小”将最大虚拟磁盘大小设置为目标值(`50G -> 80G`)
|
||||
3. 根据提示可知,在 `VMware` 中的扩展操作仅增大虚拟磁盘的大小,分区和文件系统的大小不受影响。你必须从客户机操作系统内部对磁盘重新进行分区和扩展文件系统。
|
||||
|
||||
### [](https://www.moralok.com/2024/01/14/increase-disk-space-of-Ubuntu-server-on-VMware-without-using-GParted/#%E8%B0%83%E6%95%B4%E5%88%86%E5%8C%BA%E5%A4%A7%E5%B0%8F "调整分区大小")调整分区大小
|
||||
|
||||
#### [](https://www.moralok.com/2024/01/14/increase-disk-space-of-Ubuntu-server-on-VMware-without-using-GParted/#%E5%88%86%E5%8C%BA%E7%AE%A1%E7%90%86 "分区管理")分区管理
|
||||
|
||||
1. 使用 `sudo cfdisk` 命令进入分区管理的交互式界面。可知可用空间为新增的 `30G`。
|
||||
2. 使用上下方向键选择准备调整大小的分区 `/dev/sda3`,使用左右方向键选择 `Resize` 操作。
|
||||
3. 输入新的分区大小,默认为原大小加上可用空间大小等于 `78G`。
|
||||
4. 使用左右方向键选择 `Write` 操作,写入修改。然后输入 `yes` 确认。
|
||||
5. 提示分区表已改变。然后使用左右方向键选择 `Quit` 操作,退出分区管理的交互式界面。
|
||||
6. 退出时提示如下。
|
||||
|
||||
$ sudo cfdisk <br>GPT PMBR size mismatch (104857599 != 167772159) will be corrected by write. <br> <br>Syncing disks.|
|
||||
|
||||
|
||||
#### [](https://www.moralok.com/2024/01/14/increase-disk-space-of-Ubuntu-server-on-VMware-without-using-GParted/#%E8%B0%83%E6%95%B4%E7%89%A9%E7%90%86%E5%8D%B7%E5%A4%A7%E5%B0%8F "调整物理卷大小")调整物理卷大小
|
||||
|
||||
1. 使用 `sudo pvresize /dev/sda3` 命令调整 `LVM` 中物理卷的大小。
|
||||
|
||||
|$ sudo pvresize /dev/sda3 <br> Physical volume "/dev/sda3" changed <br> 1 physical volume(s) resized or updated / 0 physical volume(s) not resized|
|
||||
|
||||
|
||||
#### [](https://www.moralok.com/2024/01/14/increase-disk-space-of-Ubuntu-server-on-VMware-without-using-GParted/#%E8%B0%83%E6%95%B4%E9%80%BB%E8%BE%91%E5%8D%B7%E5%A4%A7%E5%B0%8F "调整逻辑卷大小")调整逻辑卷大小
|
||||
|
||||
1. 使用 `sudo fdisk -l` 命令显示物理卷和逻辑卷的大小差异。在末尾可见 `/dev/sda3` 的大小为 `78G`,`/dev/mapper/ubuntu--vg-ubuntu--lv` 的大小为 `47.102G`。
|
||||
|
||||
$ sudo fdisk -l <br>Disk /dev/loop0: 63.48 MiB, 66547712 bytes, 129976 sectors <br>Units: sectors of 1 * 512 = 512 bytes <br>Sector size (logical/physical): 512 bytes / 512 bytes <br>I/O size (minimum/optimal): 512 bytes / 512 bytes <br> <br> <br>Disk /dev/loop1: 63.93 MiB, 67014656 bytes, 130888 sectors <br>Units: sectors of 1 * 512 = 512 bytes <br>Sector size (logical/physical): 512 bytes / 512 bytes <br>I/O size (minimum/optimal): 512 bytes / 512 bytes <br> <br> <br>Disk /dev/loop2: 40.88 MiB, 42840064 bytes, 83672 sectors <br>Units: sectors of 1 * 512 = 512 bytes <br>Sector size (logical/physical): 512 bytes / 512 bytes <br>I/O size (minimum/optimal): 512 bytes / 512 bytes <br> <br> <br>Disk /dev/loop3: 91.85 MiB, 96292864 bytes, 188072 sectors <br>Units: sectors of 1 * 512 = 512 bytes <br>Sector size (logical/physical): 512 bytes / 512 bytes <br>I/O size (minimum/optimal): 512 bytes / 512 bytes <br> <br> <br>Disk /dev/loop4: 40.44 MiB, 42393600 bytes, 82800 sectors <br>Units: sectors of 1 * 512 = 512 bytes <br>Sector size (logical/physical): 512 bytes / 512 bytes <br>I/O size (minimum/optimal): 512 bytes / 512 bytes <br> <br> <br>Disk /dev/fd0: 1.42 MiB, 1474560 bytes, 2880 sectors <br>Units: sectors of 1 * 512 = 512 bytes <br>Sector size (logical/physical): 512 bytes / 512 bytes <br>I/O size (minimum/optimal): 512 bytes / 512 bytes <br>Disklabel type: dos <br>Disk identifier: 0x90909090 <br> <br>Device Boot Start End Sectors Size Id Type <br>/dev/fd0p1 2425393296 4850786591 2425393296 1.1T 90 unknown <br>/dev/fd0p2 2425393296 4850786591 2425393296 1.1T 90 unknown <br>/dev/fd0p3 2425393296 4850786591 2425393296 1.1T 90 unknown <br>/dev/fd0p4 2425393296 4850786591 2425393296 1.1T 90 unknown <br> <br> <br>Disk /dev/sda: 80 GiB, 85899345920 bytes, 167772160 sectors <br>Disk model: VMware Virtual S <br>Units: sectors of 1 * 512 = 512 bytes <br>Sector size (logical/physical): 512 bytes / 512 bytes <br>I/O size (minimum/optimal): 512 bytes / 512 bytes <br>Disklabel type: gpt <br>Disk identifier: 81C6F71E-C634-49E6-BC3D-9272C86326A4 <br> <br>Device Start End Sectors Size Type <br>/dev/sda1 2048 4095 2048 1M BIOS boot <br>/dev/sda2 4096 4198399 4194304 2G Linux filesystem <br>/dev/sda3 4198400 167772126 163573727 78G Linux filesystem <br> <br> <br>Disk /dev/mapper/ubuntu--vg-ubuntu--lv: 47.102 GiB, 51535413248 bytes, 100655104 sectors <br>Units: sectors of 1 * 512 = 512 bytes <br>Sector size (logical/physical): 512 bytes / 512 bytes <br>I/O size (minimum/optimal): 512 bytes / 512 bytes|
|
||||
|
||||
2. 使用 `sudo lvresize -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv` 命令调整逻辑卷的大小。
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
|1 <br>2 <br>3|$ sudo lvresize -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv <br> Size of logical volume ubuntu-vg/ubuntu-lv changed from <48.00 GiB (12287 extents) to <78.00 GiB (19967 extents). <br> Logical volume ubuntu-vg/ubuntu-lv successfully resized.|
|
||||
|
||||
|
||||
### [](https://www.moralok.com/2024/01/14/increase-disk-space-of-Ubuntu-server-on-VMware-without-using-GParted/#%E8%B0%83%E6%95%B4%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E5%A4%A7%E5%B0%8F "调整文件系统大小")调整文件系统大小
|
||||
|
||||
1. 使用 `sudo resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv` 命令调整文件系统的大小。
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
|1 <br>2 <br>3 <br>4 <br>5|$ sudo resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv <br>resize2fs 1.45.5 (07-Jan-2020) <br>Filesystem at /dev/mapper/ubuntu--vg-ubuntu--lv is mounted on /; on-line resizing required <br>old_desc_blocks = 6, new_desc_blocks = 10 <br>The filesystem on /dev/mapper/ubuntu--vg-ubuntu--lv is now 20446208 (4k) blocks long.|
|
||||
|
||||
2. 使用 `df -h` 命令显示文件系统的总空间和可用空间信息。确认 `/dev/mapper/ubuntu--vg-ubuntu--lv` 的大小已调整为 `77G`。
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
|1 <br>2 <br>3 <br>4 <br>5 <br>6 <br>7 <br>8 <br>9 <br>10 <br>11 <br>12 <br>13 <br>14 <br>15 <br>16|$ df -h <br>Filesystem Size Used Avail Use% Mounted on <br>udev 7.8G 0 7.8G 0% /dev <br>tmpfs 1.6G 3.1M 1.6G 1% /run <br>/dev/mapper/ubuntu--vg-ubuntu--lv 77G 43G 31G 59% / <br>tmpfs 7.8G 0 7.8G 0% /dev/shm <br>tmpfs 5.0M 0 5.0M 0% /run/lock <br>tmpfs 7.8G 0 7.8G 0% /sys/fs/cgroup <br>vmhgfs-fuse 932G 859G 73G 93% /mnt/hgfs <br>/dev/sda2 2.0G 209M 1.6G 12% /boot <br>/dev/loop0 64M 64M 0 100% /snap/core20/2015 <br>/dev/loop1 64M 64M 0 100% /snap/core20/2105 <br>/dev/loop2 41M 41M 0 100% /snap/snapd/20290 <br>/dev/loop3 92M 92M 0 100% /snap/lxd/24061 <br>/dev/loop4 41M 41M 0 100% /snap/snapd/20671 <br>tmpfs 1.6G 0 1.6G 0% /run/user/1000|
|
||||
|
||||
|
||||
## [](https://www.moralok.com/2024/01/14/increase-disk-space-of-Ubuntu-server-on-VMware-without-using-GParted/#%E5%8F%82%E8%80%83%E6%96%87%E7%AB%A0 "参考文章")参考文章
|
||||
|
||||
- [Increase disk space on Ubuntu guest VM – no GUI or GParted](https://razvanm.ro/tutorial/increase-disk-space-on-ubuntu-guest-vm-no-gui-or-gparted/#Increase-Physical-Volume)
|
||||
|
||||
[# linux](https://www.moralok.com/tags/linux/) [# ubuntu](https://www.moralok.com/tags/ubuntu/)
|
||||
@@ -0,0 +1,50 @@
|
||||
**glic**是GNU项目的标准C运行库。glibc 是 Linux 系统中最底层的API,几乎其它任何运行库都会依赖于glibc。
|
||||
|
||||
Linux各个发行版都是基于Linux内核的,也就是说理论上某个发行版编译出来的二进制文件在其他Linux发行版都可以运行,但是实际上并非如此,因为动态链接编译出来的可执行文件依赖的库其他系统可能不满足需求(相应的库不存在或者库的版本不满足需求)。
|
||||
|
||||
想要一次编译就能在其他Linux发行版运行,可以以静态链接的方式进行编译,但是大型软件都会依赖很多其他的第三方库,静态链接的方式可能不太现实。
|
||||
|
||||
动态链接的可执行文件在其他系统运行,可能会提示缺少某个动态库或者相应库版本过低,这时能想到的就升级更高版本的库。这种做法很对,但是**千万不要升级glibc,千万不要升级glibc,千万不要升级glibc**,重要的事需要说三遍。
|
||||
|
||||
没有金刚钻就不要升级glibc!因为glibc是系统最基础的C库,几乎所有的运行库都依赖它,特别是系统命令,一旦升级了glibc极有可能会导致很多系统命令都没法正常使用,这个系统基本上就报废了,这是很多Linux小白容易遇到的问题。
|
||||
|
||||
其他依赖的第三方库都可以升级,唯有glibc不建议升级。正确的做法是在同一版本或者更低版本glibc的系统上进行编译可执行文件。
|
||||
|
||||
下表是主流Linux发行版Ubuntu/CentOS/Debian系统默认的glic版本。
|
||||
|
||||
| Ubuntu | Debian | CentOS | Glibc |
|
||||
| :----: | :----: | :----: | :---: |
|
||||
| 24.04 | 13 | | 2.37 |
|
||||
| 22.04 | - | - | 2.34 |
|
||||
| 20.04 | 11 | - | 2.31 |
|
||||
| - | 10 | 8 | 2.28 |
|
||||
| 18.04 | - | - | 2.27 |
|
||||
| - | 9 | - | 2.24 |
|
||||
| 16.04 | - | - | 2.23 |
|
||||
| 14.04 | 8 | - | 2.19 |
|
||||
| 13.04 | - | 7 | 2.17 |
|
||||
| 12.04 | - | - | 2.15 |
|
||||
| - | 7 | - | 2.13 |
|
||||
| - | - | 6 | 2.12 |
|
||||
|
||||
通过上表可以看到 CentOS7 的 glibc 版本为2.17,如果使用 CentOS7 系统编译的可执行文件,就可以在 Ubuntu13.04 以及更新版本和 Debian8 及更新版本上运行。
|
||||
|
||||
查看Linux系统glibc版本有多种方法:
|
||||
|
||||
**方法1**
|
||||
|
||||
sh
|
||||
|
||||
| | |
|
||||
| --- | ------------- |
|
||||
| 1 | ldd --version |
|
||||
|
||||
**方法2**
|
||||
|
||||
sh
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
|1 <br>2 <br>3 <br>4 <br>5 <br>6 <br>7 <br>8|# 查询libc.so位置 <br>find / -name libc.so.6 <br> <br># libc.so也是可执行文件,直接运行 <br>/xxxx/libc.so.6 <br> <br># 或者查看符号 <br>strings libc.so.6 \| grep GLIBC|
|
||||
|
||||
------ 本文结束,感谢您的阅读 ------
|
||||
@@ -0,0 +1,223 @@
|
||||

|
||||
|
||||
大家好,这里是物联网心球。
|
||||
|
||||
说到Linux动态库(.so文件)大家并不陌生,我想每个Linux开发者都应该使用过Linux动态库。动态库的使用其实并不简单,我们平时习以为常的动态库操作背后都隐藏了哪些秘密?本文我们一一来揭晓。
|
||||
|
||||
## 1.测试项目简介
|
||||
|
||||
学习Linux动态库时,最好先准备一个实际的测试项目,边调试边学习,这样才能够做到深入理解。笔者使用的测试项目目录结构如图1所示。
|
||||
|
||||

|
||||
|
||||
图1 测试项目目录结构
|
||||
|
||||
example.c和example.h文件将被编译为libexample.so动态库文件,example.c文件中定义了两个函数(hello函数和add函数)以及一个全局变量global。
|
||||
|
||||
main.c文件将被编译成可执行文件,main函数调用hello和add函数,并将global变量重新赋值。可执行文件编译的过程需要链接libexample.so动态库文件。
|
||||
|
||||
## 2.动态库文件
|
||||
|
||||
动态库文件是在程序运行时才被加载和链接的共享代码模块。Linux系统中通常以 .so (Shared Object) 结尾,例如 libm.so(数学库)、libc.so(C标准库)。
|
||||
|
||||
动态库文件有以下几大特点:
|
||||
|
||||
- 代码共享:多个程序可以共享同一份动态库代码,节省磁盘空间和内存资源。
|
||||
- 运行时加载:动态库在程序运行时才被加载到内存。
|
||||
- 位置无关代码(PIC):动态库可加载到不同的内存地址,避免地址冲突。
|
||||
|
||||
动态库文件其实并不神秘,它也是一种ELF文件。ELF文件基础介绍请参考我的这篇文章:[从ELF文件到Linux进程](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzkyNDIyNzU4Mg%3D%3D%26mid%3D2247486346%26idx%3D1%26sn%3Dffd28a5045068754285b41a3ab42b90a%26scene%3D21%23wechat_redirect)。
|
||||
|
||||
动态库文件ELF格式如图2所示。
|
||||
|
||||

|
||||
|
||||
图2 动态库文件ELF格式
|
||||
|
||||
通过 readelf -h 动态库文件 命令查看ELF文件头,可以看到动态库文件类型是DYN(Shared object file)。DYN(Shared object file)表示文件包含位置无关代码(通过gcc -fPIC编译)。
|
||||
|
||||
动态库文件的程序入口点(Entry point address)为0,表示动态库不是独立程序,没有自己的入口点,这点和可执行文件不同。
|
||||
|
||||
动态库文件无INTERP段(不指定动态链接器路径),动态库的主要功能是提供函数和数据供其他程序调用,而不是独立运行。因此,动态库不需要指定动态链接器的路径。
|
||||
|
||||
除了以上几个点,动态库ELF文件和可执行ELF文件并没有很大的区别。
|
||||
## 3.编译和链接阶段
|
||||
|
||||
动态库的编译必须要加上-fPIC编译选项,-fPIC选项告诉编译器生成位置无关代码,命令如下:
|
||||
|
||||
```c
|
||||
gcc -fPIC -shared -o libexample.so example.c
|
||||
```
|
||||
|
||||
编译可执行ELF文件链接动态库示例命令如下:
|
||||
|
||||
```text
|
||||
gcc main.c -o a.out -I. -L. -lexample -Wl,-rpath='$ORIGIN':/opt
|
||||
```
|
||||
|
||||
- -L选项:用于编译时指定动态库搜索路径。
|
||||
- -l选项:用于编译时指定动态库文件名(去掉lib前缀和扩展名)。
|
||||
- -Wl,-rpath选项:用于向可执行文件中嵌入一个运行时搜索路径(Runpath)。
|
||||
|
||||
编译完后,可执行文件(a.out)ELF格式如图3所示。
|
||||
|
||||

|
||||
|
||||
图3 可执行文件ELF格式
|
||||
|
||||
可执行文件如果链接了动态库,ELF文件中会有几个特殊的节:.interp节、.dynsym节、dynamic节。
|
||||
|
||||
**(1) .interp节**
|
||||
|
||||
.interp节是一个包含动态链接器路径的节,用于指定可执行文件运行时使用哪个动态链接器来加载和解析动态库。
|
||||
|
||||
通过 readelf -d 可执行文件 命令可以查看.interp节信息,输出示例如下:
|
||||
|
||||
```c
|
||||
# readelf -p .interp a.out
|
||||
String dump of section '.interp':
|
||||
[ 0] /lib/ld-linux-aarch64.so.1
|
||||
```
|
||||
|
||||
/lib/ld-linux-aarch64.so.1为动态链接器路径,当操作系统启动一个可执行文件时,它会读取.interp节中的路径,找到并加载指定的动态链接器。
|
||||
|
||||
注意:动态链接器也是一个动态库,负责在程序运行时加载和解析动态库,并将动态库定义的符号(函数和变量)绑定到程序中。
|
||||
|
||||
**(2).dynsym节**
|
||||
|
||||
.dynsym节是 ELF 文件中的动态符号表,它包含了动态链接时所需的符号信息。这些符号通常是全局变量和函数,它们在程序运行时被动态加载和解析。
|
||||
|
||||
通过 readelf -sD 可执行文件 命令可以查看.dynsym节,输出示例如下:
|
||||
|
||||

|
||||
|
||||
相关字段解析如下:
|
||||
|
||||
- Num:符号表中的条目编号。
|
||||
- Value:符号的地址或值。对于已定义符号,这通常是一个虚拟地址;对于未定义的符号(如UND),该值为0。
|
||||
- Size:符号的大小,以字节为单位。函数的大小是其指令长度,变量的大小是其占用的字节数。如果大小为0,通常表示该符号的大小未知或不是数据对象(如节名或未定义符号)。
|
||||
- Type:符号的类型,常见的类型有:
|
||||
|
||||
1. NOTYPE:类型未指定。
|
||||
2. SECTION:该符号关联到一个节。
|
||||
3. FUNC:表示是一个函数。
|
||||
4. OBJECT:表示是一个数据对象。
|
||||
|
||||
- Bind:符号的绑定属性,表示符号的可见范围,常见的绑定属性有:
|
||||
- LOCAL:局部符号,只在当前目标文件内可见。
|
||||
- GLOBAL:全局符号,可被其他目标文件引用。
|
||||
- WEAK:弱符号,类似于全局符号,但优先级较低。如果存在同名的全局符号,则全局符号优先;如果没有全局符号,则使用弱符号。
|
||||
- Vis:符号的可见性,常见类型如下:
|
||||
|
||||
1. DEFAULT:默认可见性,符号可以被其他模块引用。
|
||||
2. HIDDEN:符号在模块外不可见。
|
||||
3. INTERNAL:内部可见性。
|
||||
4. PROTECTED:受保护的,该符号在模块外可见,但不可被覆盖(即不能重定位)。
|
||||
|
||||
- Ndx:符号所在的节的索引,其中:
|
||||
|
||||
1. 数字:对应节的索引。
|
||||
2. UND:未定义符号(需动态链接)。
|
||||
3. ABS:绝对值符号。
|
||||
|
||||
- Name:符号的名称及版本信息。
|
||||
|
||||
**(3).dynamic节**
|
||||
|
||||
.dynamic节用于存储动态链接器在运行时需要的信息,包括动态库依赖关系、动态库搜索路径、节的位置等。
|
||||
|
||||
通过 readelf -d 可执行文件 命令可以查看.dynamic节,输出示例如下:
|
||||
|
||||

|
||||
|
||||
.dynamic节中的信息比较多,我们只需要关注和本文相关的信息。
|
||||
|
||||
NEEDED指的是程序运行时需要加载的动态库,如测试项目中,可执行文件依赖libexample.so库和libc.so.6库(C标准库)。
|
||||
|
||||
RUNPATH指运行时库搜索路径,优先级低于LD_LIBRARY_PATH,高于系统默认路径,$ORIGIN表示可执行文件所在目录,多个路径通过冒号分隔。
|
||||
|
||||
## 4.运行时动态链接阶段
|
||||
|
||||
当一个可执行文件链接了动态库,系统启动可执行程序时,除了要加载可执行文件的LOAD段(包括:.interp、.dynsym、.text、.dynamic、.data、.bss等节),同时也需要通过动态连接器加载动态库文件的LOAD段,这个过程比较复杂,我们通过图4来讲解。
|
||||
|
||||

|
||||
|
||||
图4 运行时动态连接阶段
|
||||
|
||||
可执行文件的加载过程是本文的重点,了解了这个过程,就会深入理解动态库。
|
||||
|
||||
用户程序想要执行新代码,需要调用execve系统调用(或execve家族函数)来加载可执行文件。execve会用新程序的代码、数据、堆和栈来替换当前进程的虚拟地址空间的内容,并执行新程序。
|
||||
|
||||
要想知道内核是如何加载ELF文件的,就要搞清楚execve的工作流程。execve加载ELF文件的主要流程在load_elf_binary函数中实现,该函数主要做了两件事情:加载可执行文件和加载动态库文件。
|
||||
|
||||
### 4.1 加载可执行文件
|
||||
|
||||
加载可执行文件具体步骤如下。
|
||||
|
||||
**(1)加载程序头表**
|
||||
|
||||
程序头表每个条目都是一个段(如INTERP段、LOAD段等),内核通过解析段将ELF文件相关数据加载至内存,所以加载程序头表是加载ELF文件的第一步。
|
||||
|
||||
**(2)解析INTERP段**
|
||||
|
||||
INTERP段只包含一个节(.interp节),前面介绍过.interp节中存储的是动态链接器路径(如 /lib/ld-linux-aarch64.so.1)。解析INTERP段的目的是加载动态链接器,为后续加载动态库文件做准备。
|
||||
|
||||
**(3)设置栈区**
|
||||
|
||||
执行新程序,需要重新设置栈区,保证新程序有正确的执行环境。
|
||||
|
||||
**(4)加载LOAD段**
|
||||
|
||||
LOAD段记录的是需要加载进内存的节(如.text、.data、.bss等)。内核会通过mmap文件或匿名映射将可执行文件的LOAD段加载至内存。
|
||||
|
||||
**(5)加载动态链接器LOAD段**
|
||||
|
||||
为了确保动态链接器能够正常启动,通常需要将动态链接器通过mmap文件映射方式映射至内存(内存映射区)。动态链接器也是动态库,加载其LOAD段即可。
|
||||
|
||||
**(6)设置堆区**
|
||||
|
||||
同栈区。
|
||||
|
||||
**(7)跳转至动态链接器入口点**
|
||||
|
||||
完成以上工作后,可执行文件已经被加载至内存。接下来是加载动态库,动态库的加载工作由动态链接器完成,所以需要跳转至动态库链接器入口点启动动态链接器。
|
||||
|
||||
### 4.2 加载动态库
|
||||
|
||||
程序控制权转移至动态链接器后,动态链接器开始加载可执行文件依赖的动态库,具体步骤如下。
|
||||
|
||||
**(1)解析ELF可执行文件DYNAMIC段**
|
||||
|
||||
可执行文件DYNAMIC段同样只包含一个节(.dynamic),该节记录了可执行文件依赖的动态库路径以及自定义动态库搜索路径(RUNPATH)。
|
||||
|
||||
**(2)顺序搜索动态库**
|
||||
|
||||
根据DYNAMIC段提供的信息,动态链接器将在指定的路径下搜索动态库。按照优先级从高到低的顺序,动态库搜索路径排序如下:
|
||||
|
||||
- LD_PRELOAD指定路径。
|
||||
- LD_LIBRARY_PATH指定路径。
|
||||
- ELF文件RUNPATH指定路径。
|
||||
- ld.so.cache 缓存文件中查找。
|
||||
- /lib 、/usr/lib 等默认路径。
|
||||
|
||||
只要我们编译的动态库处于以上几种方式指定的路径中,就能够被动态链接器搜索到,也就能够正确解析动态库符号。
|
||||
|
||||
这里我们看到了几个熟悉的身影(如:LD_LIBRARY_PATH、/lib、/usr/lib等),我们平时做软件开发时经过会用到它们,但很少关注其底层原理和作用。通过本文的学习,我们就能够彻底搞懂这些基础知识了。
|
||||
|
||||
搜索到动态库后,我们需要解析动态库,并将其LOAD段通过mmap方式加载至内存(内存映射区)。
|
||||
|
||||
**(3)外部符号重定位**
|
||||
|
||||
所谓外部符号是指ELF可执行文件.dynsym节中未定义(UND)的符号,这些符号通常在动态库中定义。动态库文件中定义的是位置无关代码(-fPIC),这些代码只有相对地址,没有绑定实际虚拟地址,需要由动态链接器进行重定位绑定实际虚拟地址,才能够正常调用。
|
||||
|
||||
**(4)跳转至ELF可执行程序入口点**
|
||||
|
||||
动态链接器完成全部工作后,程序会跳转至ELF可执行程序入口点,开始执行新程序。这样链接了动态库的可执行文件就加载完毕了。
|
||||
|
||||
最后,我们通过 cat /proc/<pid>/maps 命令查看进程虚拟地址空间内存布局,来验证上述的理论知识,输出示例如下:
|
||||
|
||||

|
||||
|
||||
## 总结:
|
||||
|
||||
动态库是Linux一个非常重要的知识点,通过本文我们能够深入理解动态库底层实现原理,从而能够更高效地使用动态库。
|
||||
@@ -0,0 +1,9 @@
|
||||
#### Windows **Process Monitor (ProcMon)**: 这是一个高级的 Windows 系统监控工具,可以实时显示文件系统活动。你可以使用它来监控特定程序的文件访问情况。
|
||||
- 下载并运行 Process Monitor。
|
||||
- 设置过滤器,只显示你感兴趣的程序活动。
|
||||
- 运行程序并观察 Process Monitor 中的活动。
|
||||
|
||||
|
||||
#### Linux **inotify**: 这是 Linux 内核中的一个文件系统监控机制,可以监控文件系统上的各种事件。可以使用 `inotifywait` 命令来监控文件变化。
|
||||
- 安装 `inotify-tools` 包。
|
||||
- 使用 `inotifywait` 命令来监控特定目录的文件变化。例如:`inotifywait -m /path/to/directory`
|
||||
@@ -0,0 +1,137 @@
|
||||
下面这张图,看懂了能让你节约10年左右时间。因为我是用了大概十年时间才总结出的。
|
||||
|
||||

|
||||
|
||||
有点复杂,没关系,我带着你来做一遍,用三分钟给自己列一个人生理想职业的规划。
|
||||
|
||||
**首先,你要考虑你擅长________能力。**
|
||||
|
||||
注意,这个不是职业,不是工种,是**“能力**”,比如说“计算”、“沟通”、“[逻辑推理](https://www.zhihu.com/search?q=%E9%80%BB%E8%BE%91%E6%8E%A8%E7%90%86&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A2672454360%7D)”、“写作”、“创意”、“精细重复的检查”等等。
|
||||
|
||||
可以多列几种,从你认为最强的,从小大家都夸奖你的开始,老师经常在个人评价里面写赞扬的那些话。
|
||||
|
||||
**注意,这个能力,未必是你喜欢的。但必须是你擅长的。**
|
||||
|
||||
比如说你其实挺讨厌和陌生人打交道的,但是往往凭借情商能很快赢得陌生人好感。但心里可以是厌恶这种无用社交的。
|
||||
|
||||
能力往往代表一个人的**天赋**,作为你的职业将是非常轻松的,但未必热爱它。
|
||||
|
||||
你可以有目的的去强化你的能力,包括用PS,写字漂亮等等, 都可以算。每个人一般不止一种。
|
||||
|
||||
**第二步,考虑你喜欢在________的环境下。**
|
||||
|
||||
这个环境,可以稍微宽泛一些,比如“自己一个人能独处的”、“热闹充满商业气息的”、“高档时尚大家都正装领带的”、“艺术感十足的”、“国外的”、“人均素质很高的”等等。
|
||||
|
||||
我们真正喜欢的,往往不是“工种”本身,而是某种“环境”。你可以在这种气氛中非常的满足,继而产生归属感和成就感。
|
||||
|
||||
这里甚至可以填“家中”,但不能写“慵懒不用努力的”这样的条件,因为不管任何你喜欢或不喜欢的工作内容,最后都是需要努力的,区别只是努力的过程能不能带来成就感。
|
||||
|
||||
你也可以写下来不止一种,比如农村自然的、家里独处的、和动物一起的等等。
|
||||
|
||||
**第三步,不是写最后一块“商业机会”,而是先找一和二的重合部分。**
|
||||
|
||||
目的是,看看能诞生什么职业,我把这类职业叫做**“正职”。**也就是比较正式的日常职业。
|
||||
|
||||
这里你可以做一下连连看,把左边的能力和右边的环境试着建立下联系,职业写在中间。
|
||||
|
||||
比如:
|
||||
|
||||

|
||||
|
||||
### **在正职了解一个行业后,你可以写下自己感觉到的机会**
|
||||
|
||||
这个“机会”可以是你洞察到的趋势,观察到的需求,人脉带来的渠道等等。
|
||||
|
||||
这个比较宽泛,未必能直接写下来,更多是敏锐洞察到,然后和你的“能力”,融合在一起,就变成“**兼职**”或者收入的机会。
|
||||
|
||||
举个例子:
|
||||
|
||||

|
||||
|
||||
### 而在你的收入达到一定水平,可以财务自由或不用生计发愁时,可以考虑“闲职”。
|
||||
|
||||
这个词也是我放了个相对合适的,可以理解为“**不以收入为第一目的的职业**”。
|
||||
|
||||
有点像“**[提前退休](https://www.zhihu.com/search?q=%E6%8F%90%E5%89%8D%E9%80%80%E4%BC%91&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A2672454360%7D)后自娱自乐的职业**”。带来成就感和取悦自己为第一目的,收入反而无所谓。
|
||||
|
||||
它一般是在你喜欢的“**环境**”下,从事有一定挑战且流行的“**机会**”[性工作](https://www.zhihu.com/search?q=%E6%80%A7%E5%B7%A5%E4%BD%9C&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A2672454360%7D)。
|
||||
|
||||
比如:
|
||||
|
||||
大家都在做“短视频(机会)”,你自己喜欢“西餐厅(环境爱好)”,于是开始录制“探访西餐厅的视频(闲职)”,不在乎有多少人打赏,也不指着它吃饭,就是做喜欢的事情。
|
||||
|
||||
再比如:
|
||||
|
||||
你平时是职业的会计师,但喜欢和朋友们聚会喝啤酒(环境爱好),正好有朋友能提供一个不要钱的场地(机会),你和几个朋友开了个酒吧([闲职](https://www.zhihu.com/search?q=%E9%97%B2%E8%81%8C&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A2672454360%7D)),基本不赚钱但大家多了个聚会的场所。
|
||||
|
||||
### **注意,要分清“正职”、“兼职”和“闲职”的区别。**
|
||||
|
||||
- 正职,一定要是你擅长的,甚至未必喜欢,但能确保养活自己,稳定挣到收入。
|
||||
- 兼职,一般是伴随机会的,挣的很多是投机收入,未必长久,但尽量擅长否则可能赔钱。
|
||||
- 闲职,要明白未必能带来收益,更多是满足自己爱好,但如果做的好也能变成兼职。
|
||||
|
||||
### 不管从哪项出发,如果未来能找到三者的同时满足,那么恭喜你,就找到自己的事业了。
|
||||
|
||||
所谓“事业”就是能给自己干,不辞辛苦、乐在其中、有不错的收益,还能有长远发展的。
|
||||
|
||||
它需要你首先爱这份工作,然后熟悉且擅长,最好有很不错的机会或者未来前景,才可能挣钱,给自己带来金钱和成就感的两方面满足效果。
|
||||
|
||||
大部分在23岁毕业,都是先找到能满足一方面的工作——或者是擅长,或者喜欢的环境。
|
||||
|
||||
之后在行业熟悉两三年,可以洞察一些机会,跳到“擅长+机会”的工作,有一份收入不错的正职或兼职。
|
||||
|
||||
还有一些家庭经济比较好,不担心收入的人,则是选择“喜欢的环境+擅长“的工作,比如比较有面子的工作,或者比较轻松的环境。
|
||||
|
||||
### 30岁的时候,可能你会面临一次“创业”或者“继续打工”的选择。
|
||||
|
||||
不管哪一种,到了30岁,你应该明白自己“**擅长**”什么,了解到所在行业有什么“**机会**”。这个时候往往需要你做一个选择——是不是能走出舒适的“**环境**”。
|
||||
|
||||
- 不愿意走出来的,放弃机会,依旧选择舒适环境下的安逸正职。
|
||||
- 尝试着走出来的,放弃掉一些舒适,看看能不能抓住机会多个收入的兼职。
|
||||
- 还有一些喜欢迎接挑战,或者很幸运喜欢新机会的新环境的,直接辞职去创业。
|
||||
|
||||
比如之前的旅游热,让很多人看到机会,又喜欢山野的环境,直接放弃工作开民俗。利用自己擅长的写作、摄影、视频做[营销推广](https://www.zhihu.com/search?q=%E8%90%A5%E9%94%80%E6%8E%A8%E5%B9%BF&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A2672454360%7D),成功实现了想创业的梦想。
|
||||
|
||||
但注意,创业的第一前提不是喜欢,而是收入,能给你稳定来带收入的才叫“事业”,否则这是一个爱好,或者“闲职”罢了。
|
||||
|
||||
### 什么样的“能力”是需要始终保持的。
|
||||
|
||||
而如果你选择了“闲职”,也就是不追求收入,也要做自己喜欢的事情。那么一定要保留一种擅长并能获得收入的能力。因为你不知道什么时候会突然需要用钱,之前攒的钱很可能不够。
|
||||
|
||||
这就要求你或者有一些不因为时间被脱节的技能,或者能通过资本的管理不断获得收入的投资。
|
||||
|
||||
最好的技能是脱离了大体系,依然可以依靠自己或平台带来收入的能力。
|
||||
|
||||
举例:比如“行政运营”或“[产品规划](https://www.zhihu.com/search?q=%E4%BA%A7%E5%93%81%E8%A7%84%E5%88%92&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A2672454360%7D)”,严格说就不是一种好的能力,因为必须在公司体系内才能发挥作用变成收入。
|
||||
|
||||
而“**摄影**”或者“**绘画**”相对就好很多,因为哪怕离开了大企业,它依旧能通过个人的关系网或者平台给自己带来收入,且不易因为时间而变得落伍,贬值。
|
||||
|
||||
我们一般把前者称为“**[岗位技能](https://www.zhihu.com/search?q=%E5%B2%97%E4%BD%8D%E6%8A%80%E8%83%BD&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A2672454360%7D)**“或“**管理能力**”,后者则称为“**[专业技能](https://www.zhihu.com/search?q=%E4%B8%93%E4%B8%9A%E6%8A%80%E8%83%BD&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A2672454360%7D)**”或“**个人能力**”。
|
||||
|
||||
请记住,如果有时间和有选择时,尽可能多学习后种能力,因为它是可以陪伴你哪怕离开了当前集体,依旧发挥大部分作用,且会随着你的熟练度不断提升带来更高收入的。
|
||||
|
||||
而前者,往往要依靠这些能力帮组织实现了增长目标,得到认可后才能间接给自己带来收入。甚至因为现代企业的增长是一个分工细化的集体项目,有可能你完成的很好,依旧无法实现目标。
|
||||
|
||||
当然,这类知识,往往企业会更愿意给予培训的机会,所以在工作上如果有机会由企业买单去学习,当然也不可放过。
|
||||
|
||||
这两种知识和技能都可以归结为“对自己未来有意义”的准备。
|
||||
|
||||
而所谓的“机会”或“人脉(也算一种机会)”,则不是刻意追求的,当你随着能力和职位变化,对别人足够有用时,往往水到渠成,比年轻时刻意追求效果还要更好。
|
||||
|
||||
### 最后,并不是“有事业”才是唯一的正确答案
|
||||
|
||||
虽然,“事业”是前面图中三者的共同核心,好像能同时满足三者,但遵从本心的生活比“成功”有时候更重要。
|
||||
|
||||
如果你就希望平静、悠闲的日子,那么一份“**闲职+兼职**”可能反而让不少有事业的老板都要羡慕。因为随时可以决定自己的日程规划。
|
||||
|
||||
甚至不少年轻人就喜欢和父母一样,在一个稳定的单位做一份稳定的工作,赚取不多但够全家用的薪水,平静的过一生,这也很好。没有很多烦恼和压力。
|
||||
|
||||
我们每个人都有自己的人生目标,随时改变都不晚,没有一定的对错。
|
||||
|
||||
在人生的三种能力中“**内心强大的能力**”和“**让技能变强的能力**”、“**抓住机会的能力**”一样重要。
|
||||
|
||||
只要能顺从本心,过自己想要的生活,不用管别人的价值观和声音,甚至其实某种意义上这才是“**成功的[人生规划](https://www.zhihu.com/search?q=%E4%BA%BA%E7%94%9F%E8%A7%84%E5%88%92&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A2672454360%7D)**”,按照自己想要的方式生活。
|
||||
|
||||
甚至对很多人,年纪越大才越明白能“想做什么就能做什么”未必是最好的,“不想做什么就不做什么”,才是一种真正的自由和强大。
|
||||
|
||||

|
||||
@@ -0,0 +1,29 @@
|
||||
方法:反问
|
||||
问什么:
|
||||
- 确认对方意图
|
||||
有人问:“你都三十多了,怎么还没结婚?”
|
||||
|
||||
老实人:“因为忙……”(陷入自证)
|
||||
|
||||
高手反问:“你是对我的感情生活感兴趣,还是手头有合适的人想介绍给我?”
|
||||
|
||||
这样一问,你争取了思考时间,同时理清了对方是单纯八卦还是真好心。你的回答将更有针对性,而不是被动挨打,扭扭捏捏地说,“还没找到合适的。”
|
||||
|
||||
|
||||
- 确认对方感受
|
||||
伴侣突然发难:“你为什么对我这么冷淡?”
|
||||
|
||||
老实人:“我哪有冷淡,我刚下班……”(开始吵架)
|
||||
|
||||
高手反问:“亲爱的,你是觉得我最近太忙了没时间陪你,还是说在哪件具体的事上让你感到被冷落了?”
|
||||
|
||||
|
||||
|
||||
- 确认对方立场
|
||||
客户刁难:“你这个方案为什么这么设计?不是有更好的方案吗?”
|
||||
|
||||
老实人:“因为这个质量好……”(苍白的辩解)
|
||||
|
||||
高手反问:“您现在是更关心初期的投入成本,还是更想了解方案长期的可行性和回报率?”
|
||||
|
||||
这个反问让你明确了对方关心的核心是什么。是想省钱,还是想要效果?摸清了底牌,你的下一个回答就能俘获对方的心。
|
||||
@@ -0,0 +1,3 @@
|
||||
vim ./scripts/install.sh
|
||||
:set ff=unix
|
||||
:wq
|
||||
@@ -0,0 +1,23 @@
|
||||
### IAM 与 RAC:不同层面的概念
|
||||
|
||||
**IAM (Identity and Access Management,身份与访问管理)** 是一个框架或系统服务-。它是一个宏观的管理体系,负责整个数字身份的生命周期管理(创建、修改、删除)以及定义“谁”在“什么条件”下可以“访问”什么“资源”。
|
||||
|
||||
### IAM 中的主流授权模型:RBAC vs ABAC
|
||||
|
||||
在实际的IAM系统中,最主流的两种授权模型是RBAC和ABAC[-4](https://support.huaweicloud.com/intl/zh-cn/api-cci2/cci_02_0089.html)-。它们的核心区别如下:
|
||||
|
||||
|特性|基于角色的访问控制 (RBAC)|基于属性的访问控制 (ABAC)|
|
||||
|---|---|---|
|
||||
|**授权基础**|基于用户在组织中的**职能或角色**(如:管理员、开发者)[-1](https://support.huaweicloud.com/intl/zh-cn/productdesc-iam5/iam_01_1274.html)。|基于**动态属性**,如用户属性、资源属性、操作类型、环境等[-1](https://support.huaweicloud.com/intl/zh-cn/productdesc-iam5/iam_01_1274.html)。|
|
||||
|**权限关系**|**用户 → 角色 → 权限**[-4](https://support.huaweicloud.com/intl/zh-cn/api-cci2/cci_02_0089.html)。|**用户/资源 → 策略 (基于属性规则)**[-1](https://support.huaweicloud.com/intl/zh-cn/productdesc-iam5/iam_01_1274.html)。|
|
||||
|**管理粒度**|相对**粗粒度**,通常授予对一类服务的访问权限[-4](https://support.huaweicloud.com/intl/zh-cn/api-cci2/cci_02_0089.html)。|**细粒度**,可以精确到某个操作、某个特定资源[-1](https://support.huaweicloud.com/intl/zh-cn/productdesc-iam5/iam_01_1274.html)[-4](https://support.huaweicloud.com/intl/zh-cn/api-cci2/cci_02_0089.html)。|
|
||||
|**扩展性与灵活性**|**灵活性较差**。新增资源或人员变动时,通常需要手动更新角色或策略[-1](https://support.huaweicloud.com/intl/zh-cn/productdesc-iam5/iam_01_1274.html)。|**灵活性高**。只要属性匹配,新资源的权限可自动授予,无需修改策略[-1](https://support.huaweicloud.com/intl/zh-cn/productdesc-iam5/iam_01_1274.html)。|
|
||||
|**适用场景**|组织架构稳定,角色和职责明确的环境[-4](https://support.huaweicloud.com/intl/zh-cn/api-cci2/cci_02_0089.html)。|环境动态变化,需要精细化、自动化权限管控的大型或复杂组织[-4](https://support.huaweicloud.com/intl/zh-cn/api-cci2/cci_02_0089.html)。|
|
||||
|
||||
|
||||
|
||||
**Linux 用户系统核心是 DAC (自主访问控制)**[-17](https://z.itpub.net/article/detail/42DE3A43D7D8A4A695A5061B04013496)
|
||||
|
||||
- **机制**:这是Linux最基础的权限模型,即经典的**UGO(用户、组、其他人)+ RWX(读、写、执行)权限**,以及更灵活的**ACL(访问控制列表)**-[-17](https://z.itpub.net/article/detail/42DE3A43D7D8A4A695A5061B04013496)。
|
||||
- **特点**:资源(文件、目录)的**拥有者**可以自主决定授予谁什么权限[-15](https://blog.csdn.net/fuladulu/article/details/157652085)[-17](https://z.itpub.net/article/detail/42DE3A43D7D8A4A695A5061B04013496)。
|
||||
- **例子**:你使用`chmod`命令修改自己文件的权限,就是典型的DAC操作[-17](https://z.itpub.net/article/detail/42DE3A43D7D8A4A695A5061B04013496)。
|
||||
@@ -0,0 +1,363 @@
|
||||
|
||||
|
||||
去年换服务器的时候我干了件事:没有直接把旧环境整个搬过去,而是从一台干干净净的机器开始,谁让我用得难受了,我才装谁。
|
||||
|
||||
这个过程比我想的有意思。头十分钟我装了四个东西,接下来两天陆陆续续又装了几个,然后就基本停了。而我旧机器上那一堆当年兴冲冲装的、名字都挺酷的工具,一多半我压根没想起来。
|
||||
|
||||
那份记录后来变成了一个叫 setup.sh 的文件,现在二十三行。这篇讲的就是这二十三行,以及它们各自是因为什么进来的。
|
||||
|
||||
我不太想写成一份"神器大全"。那种清单我以前收藏过很多,收藏完就没再打开。真正有用的信息不是"有哪些工具",是"什么样的疼痛值得用工具去治,以及它是怎么治的"。所以下面每一个我都会说清楚:我因为什么装的它,它具体解决了什么,怎么用。
|
||||
|
||||
命令我都会翻译成人话,没怎么碰过终端的人也能看懂在干嘛。
|
||||
|
||||
先说一句丑话:这里头有几个是近几年才流行的新工具,不同发行版的安装方式和名字略有差异,你真要装的时候查一下当下的官方说明。我给的是这些工具解决什么问题,这个是不会过期的。
|
||||
|
||||
## 头十分钟装的那四个
|
||||
|
||||
新机器上最先让我难受的,永远是同一件事:我知道那个东西在,但我找不到它。
|
||||
|
||||
一个跑了半年的项目,几十个实验目录,里面藏着日志、配置、checkpoint、临时脚本。你脑子里只有一个模糊的印象:那个改过学习率的配置文件,好像在某个 exp_0 什么的目录里。
|
||||
|
||||
系统自带的 find 能干这事,但它的写法对人非常不友好:
|
||||
|
||||
find . -name "*config*" -type f
|
||||
|
||||
要引号,要 -name,要 -type f,少一个就报错或者给你一堆你不想要的东西。而且它会一头钻进 .git 目录和虚拟环境里,给你翻出几千条垃圾结果。
|
||||
|
||||
第一个装的是 fd。同样的事:
|
||||
|
||||
fd config
|
||||
|
||||
完了。它默认忽略隐藏目录和 .gitignore 里写明不用管的东西,所以吐出来的结果是干净的。想只看 Python 文件加个 -e py:
|
||||
|
||||
fd -e py train
|
||||
|
||||
意思是:名字里带 train 的 py 文件在哪。
|
||||
|
||||
第二个是 ripgrep,命令名是 rg。这个是找文件内容的,对应系统自带的 [grep](https://zhida.zhihu.com/search?content_id=792766250&content_type=Answer&match_order=2&q=grep&zhida_source=entity)。
|
||||
|
||||
rg "lr=" exp_*/config.yaml
|
||||
|
||||
意思是:在这些配置文件里,把含有 lr= 的行都找出来,顺便告诉我是哪个文件的第几行。
|
||||
|
||||
它比 grep 快很多,而且同样会自动跳过那些不该搜的目录。我第一次在整个项目目录下 rg 一个函数名、瞬间就出结果的时候,是真的愣了一下。以前用 grep -r 搜同样的东西,我习惯是敲完回车就去接杯水。
|
||||
|
||||
第三个是 fzf,如果只让我留一个,我留它。
|
||||
|
||||
它干的事听起来很平淡:给你一个模糊搜索的选择框。你输入几个不连续的字母,它从一堆候选里实时筛。它的价值在于,它能接在任何东西后面。
|
||||
|
||||
装完之后最直接的变化是,按下 Ctrl-R 翻历史命令,从原来那种一下一下往上按的痛苦,变成了输入三个字母就把三个月前那条又长又复杂的命令捞出来。我上一篇讲命令行的时候说过,命令是可以被写下来的东西,而 fzf 让"写下来的东西"真正变得可以被随时找回。这两件事凑一块儿威力才完整。
|
||||
|
||||
还能这么用:
|
||||
|
||||
vim $(fzf)
|
||||
|
||||
先弹出选择框让你搜文件,选中哪个就用编辑器打开哪个。
|
||||
|
||||
杀掉一个进程也不用先 ps 再抄 PID 了:
|
||||
|
||||
kill -9 $(ps aux | fzf | awk '{print $2}')
|
||||
|
||||
意思是:把所有进程列出来给我搜,我选中一行,把它的进程号取出来,杀掉。
|
||||
|
||||
第四个是 zoxide。装完之后你用 z 代替 cd:
|
||||
|
||||
z exp3
|
||||
|
||||
它会记住你常去哪些目录,你只需要给个片段,它带你跳过去。不用再敲那种十几层的长路径。这个东西的收益特别隐形,你装的时候觉得"就这?",一个月后回到没装它的机器上,你会明显地烦。
|
||||
|
||||
这四个是一类东西,它们不给你新能力,它们只是让"找"这件事从一件需要动脑的事变成一个反射动作。这种改善听起来不性感,但它节省的是你一天里被打断几十次的那种细碎的注意力。
|
||||
|
||||
## 服务器磁盘满了那次,我装了 ncdu
|
||||
|
||||
有一天集群提示我空间不够,训练起不来。
|
||||
|
||||
我第一反应是 du -sh * 一层层往下找,找了十几分钟,眼睛都花了。那次之后我装了 ncdu。
|
||||
|
||||
ncdu ~/
|
||||
|
||||
它会扫一遍,然后给你一个可以上下键走进走出的界面,每个目录后面标着占了多大、按大小排好序。哪个目录胖,一眼就看见,还能直接在里面按 d 删掉。
|
||||
|
||||
我那次进去三层就找到了元凶:一个我早就放弃的实验,每个 epoch 存一个 checkpoint,存了四十多个,一个两个多G。
|
||||
|
||||
顺手提一下 [dust](https://zhida.zhihu.com/search?content_id=792766250&content_type=Answer&match_order=1&q=dust&zhida_source=entity),它是 du 的替代品,直接画出一棵带条形图的树,适合"我就想扫一眼谁最胖"这种需求。ncdu 适合排查,dust 适合扫视。
|
||||
|
||||
还有 duf,看各个分区还剩多少,比 df -h 那一堆对不齐的数字好读。这三个都属于装了不用天天想起、但出事那天能救你的东西。
|
||||
|
||||
多说一句我踩过的坑:磁盘报满的时候,别只看文件大小,还要看是不是被删掉的文件还被进程占着。你 rm 掉一个日志,但写它的程序还在跑,空间不会释放。这时候用 lsof 找它:
|
||||
|
||||
lsof | grep deleted
|
||||
|
||||
意思是:列出所有被打开的文件,找出那些"已经被删了但还被占着"的。我第一次遇到这个的时候完全懵了,明明删了几十个G,df 一看一点没变。
|
||||
|
||||
## 训练跑起来之后,你需要看清它在干嘛
|
||||
|
||||
这块是我最有体感的,因为跑多模态的实验,大部分时间就是等,而等的时候你需要知道它到底活着还是已经僵了。
|
||||
|
||||
第一个,gpustat 或者 [nvtop](https://zhida.zhihu.com/search?content_id=792766250&content_type=Answer&match_order=1&q=nvtop&zhida_source=entity)。
|
||||
|
||||
nvidia-smi 大家都会,但它一屏东西太多,看一次要找半天。gpustat 是一行一张卡,谁占了、占了多少显存、利用率多少,清清楚楚:
|
||||
|
||||
gpustat -i
|
||||
|
||||
那个 -i 是持续刷新。nvtop 更进一步,给你一个带曲线的界面,像 [htop](https://zhida.zhihu.com/search?content_id=792766250&content_type=Answer&match_order=1&q=htop&zhida_source=entity) 但是给显卡用的。你能一眼看出利用率是稳定在高位,还是在那儿一顿一顿地跳。
|
||||
|
||||
这个真的有用,不是花架子。利用率一顿一顿的,通常意味着显卡在等数据,瓶颈在数据加载而不是在计算。这个信号如果你看不见,你会白白多等几个小时,还以为是模型本来就慢。
|
||||
|
||||
第二个,ts,来自一个叫 moreutils 的工具包。
|
||||
|
||||
它干的事一句话说完:给流水一样的日志,每行前面加个时间戳。
|
||||
|
||||
python train.py | ts '%F %T'
|
||||
|
||||
以前我的日志长这样:一堆 loss 数字,不知道哪一行是什么时候打的。出问题回头查,只能靠猜。加了 ts 之后,每一行前面都有精确到秒的时间,你能立刻算出每个 epoch 花了多久、卡在哪一步。
|
||||
|
||||
这个改动小到不值一提,但它把我的日志从"一堆数字"变成了"一份有时间轴的记录"。查问题的时候差别巨大。
|
||||
|
||||
moreutils 里还有一个更冷门的,叫 [sponge](https://zhida.zhihu.com/search?content_id=792766250&content_type=Answer&match_order=1&q=sponge&zhida_source=entity)。它解决一个特别隐蔽的坑:
|
||||
|
||||
sort file.txt > file.txt
|
||||
|
||||
这条看起来合理,其实会把文件清空,因为输出重定向会先把文件截断,而此时 sort 还没读完。换成:
|
||||
|
||||
sort file.txt | sponge file.txt
|
||||
|
||||
sponge 会先把内容全吸进去,等上游结束了再一次性写回。我是被坑过之后才知道有这么个东西的,那个文件不重要,但那种"我明明只是排个序,你怎么给我清空了"的荒谬感,记了很久。
|
||||
|
||||
第三个,pv。
|
||||
|
||||
传一个几十G的数据集、解压一个大压缩包,系统默认什么都不告诉你,你只能盯着光标看它转,完全不知道是快好了还是刚开始。
|
||||
|
||||
pv data.tar.gz | tar xzf -
|
||||
|
||||
加了 pv,你就有了进度条、速度和剩余时间。它本质上是插在管道中间的一个观察窗,任何数据流过它,它就报告一下流了多少。
|
||||
|
||||
## 数据处理:这几个能省掉大量临时脚本
|
||||
|
||||
搞机器学习,一半时间在跟数据的格式较劲。以前我遇到点需求就写个 Python 小脚本,写完就扔,下次再写。后来发现好多需求根本不值得开一个 py 文件。
|
||||
|
||||
jq,处理 JSON 的。这个可能不算冷门,但用得深的人不多。
|
||||
|
||||
jq '.results[] | .score' out.json
|
||||
|
||||
意思是:把 results 这个列表里每一项的 score 拿出来。加个 -r 可以去掉引号,方便接给别的命令。
|
||||
|
||||
我最常用的场景是:一堆评测输出的 json,我想看某个指标的分布。以前是写脚本,现在是一行,而且能直接接到后面排序、算平均。
|
||||
|
||||
[shuf](https://zhida.zhihu.com/search?content_id=792766250&content_type=Answer&match_order=1&q=shuf&zhida_source=entity),这个是系统自带的,但我很少见人提,而它对做机器学习的人来说简直是量身定做:
|
||||
|
||||
shuf -n 1000 huge.jsonl > sample.jsonl
|
||||
|
||||
意思是:从这个巨大的文件里随机抽一千行出来。
|
||||
|
||||
调试数据处理流程的时候,你不需要跑全量,你需要一个有代表性的小样本。用 head 取前一千行是有偏的,因为数据往往按某种顺序排过。shuf 一行解决,而且它对大文件的处理效率很好。
|
||||
|
||||
split,也是自带的,把大文件切块:
|
||||
|
||||
split -l 100000 big.jsonl part_
|
||||
|
||||
每十万行切一个,分块之后可以并行处理。
|
||||
|
||||
[column -t](https://zhida.zhihu.com/search?content_id=792766250&content_type=Answer&match_order=1&q=column+-t&zhida_source=entity),自带的排版小工具,冷门到我怀疑一半人不知道它存在:
|
||||
|
||||
cat result.txt | column -t
|
||||
|
||||
它会把用空格分隔的内容对齐成整齐的列。我那些手工输出的实验结果,加这一下之后从一团乱麻变成一张表,看错数字的概率明显降低。
|
||||
|
||||
sd,替代 sed 做简单替换的。sed 的语法我每次用都要查:
|
||||
|
||||
sd 'lr=0.001' 'lr=0.0005' config.yaml
|
||||
|
||||
就这么直观,前面是找什么,后面是换成什么。复杂的场合 sed 还是更强,但九成的日常替换,sd 足够,而且不容易写错。
|
||||
|
||||
处理 CSV 我用 miller,命令名 mlr:
|
||||
|
||||
mlr --csv stats1 -a mean,max -f loss data.csv
|
||||
|
||||
意思是:算这个 csv 里 loss 这一列的均值和最大值。
|
||||
|
||||
不过这个我用得不算深,我基本只用统计和筛选那几个功能,它更复杂的能力我没吃透,不敢多讲。
|
||||
|
||||
## 让重复的事自己发生
|
||||
|
||||
这一组是我认为收益最高、但知道的人最少的一类。
|
||||
|
||||
entr,监视文件变化然后自动执行命令。
|
||||
|
||||
fd -e py | entr -r python train.py
|
||||
|
||||
意思是:所有 py 文件,只要有任何一个被我保存了,就重新跑一次训练。那个 -r 是先杀掉上一次的进程再重启。
|
||||
|
||||
这个东西改变了我调试的节奏。以前是改代码、切窗口、回车、看结果、切回去改。现在是改代码、保存,眼睛都不用离开编辑器,旁边那个窗口自己就跑起来了。写测试的时候尤其明显:
|
||||
|
||||
fd -e py | entr pytest tests/
|
||||
|
||||
保存即测试。这个循环紧到什么程度,直接决定了你调试时的心态,松一点你就容易走神,紧一点你就一直在状态里。
|
||||
|
||||
[GNU parallel](https://zhida.zhihu.com/search?content_id=792766250&content_type=Answer&match_order=1&q=GNU+parallel&zhida_source=entity),把串行的事变成并行。
|
||||
|
||||
parallel -j 4 python eval.py --ckpt {} ::: ckpt/*.pt
|
||||
|
||||
意思是:对每一个 checkpoint 文件都跑一次评测,同时跑四个。那个 {} 是占位符,代表当前这个文件。
|
||||
|
||||
我以前干这事是写个 for 循环一个个跑,二十个 checkpoint 排队等着。现在四个一起上,时间除以四。要注意别开太多把显存或者内存挤爆,-j 后面那个数字要看着机器给。
|
||||
|
||||
[hyperfine](https://zhida.zhihu.com/search?content_id=792766250&content_type=Answer&match_order=1&q=hyperfine&zhida_source=entity),给命令做基准测试。
|
||||
|
||||
hyperfine 'python loader_a.py' 'python loader_b.py'
|
||||
|
||||
它会各跑很多次,给你平均耗时、标准差,还有谁快多少倍。
|
||||
|
||||
我用它比较过两种数据加载的写法。之前判断哪个快,我是自己 time 一下,跑两三次,凭感觉。但单次测量的波动其实很大,我不止一次被随机波动骗过,以为某个改动有效,后来发现是噪声。hyperfine 给的是带方差的结论。
|
||||
|
||||
这一点其实挺重要:做科研的人应该对"单次测量"天然警惕,但轮到自己写代码调性能的时候,反而经常凭一次运行下结论。我自己犯过这个错,还挺尴尬的。
|
||||
|
||||
[direnv](https://zhida.zhihu.com/search?content_id=792766250&content_type=Answer&match_order=1&q=direnv&zhida_source=entity),进入目录自动加载环境变量。
|
||||
|
||||
在项目目录里放一个 .envrc,写上这个项目要用的显卡、路径、密钥,cd 进来自动生效,cd 出去自动失效。
|
||||
|
||||
export CUDA_VISIBLE_DEVICES=2
|
||||
|
||||
我用它主要是为了防一件事:忘了自己上一次把显卡设成了哪张,结果新实验一头撞到别人正在用的卡上。这种事发生一次就够让人不好意思了。
|
||||
|
||||
timeout,自带的,给命令设一个上限:
|
||||
|
||||
timeout 300 python probe.py
|
||||
|
||||
超过五分钟就掐掉。写探测脚本或者跑不确定会不会卡死的东西时,加上它,你就不会回来发现某个进程僵在那儿占了一晚上资源。
|
||||
|
||||
flock,防止同一个任务重复执行:
|
||||
|
||||
flock -n /tmp/mytask.lock python task.py
|
||||
|
||||
如果上一次还在跑,这一次就直接放弃。定时任务里这个特别有用,不然上一轮没跑完下一轮又起来了,两个一起写同一个文件,场面会很难看。
|
||||
|
||||
## 在共用服务器上活下来的几个
|
||||
|
||||
实验室的机器是大家共用的,这一组是关于怎么当个不讨人嫌的人,顺便别把自己搞死。
|
||||
|
||||
[nice 和 ionice](https://zhida.zhihu.com/search?content_id=792766250&content_type=Answer&match_order=1&q=nice+%E5%92%8C+ionice&zhida_source=entity)。
|
||||
|
||||
nice -n 19 ionice -c3 python preprocess.py
|
||||
|
||||
意思是:这个数据预处理,把它的 CPU 优先级和磁盘优先级都降到最低。它会慢一点,但它不会拖慢别人正在跑的训练。
|
||||
|
||||
我第一次意识到该这么做,是有次我起了一个吃满 CPU 的预处理,隔壁同门的训练速度掉了三成,他还以为是自己代码的问题,查了半天。那次挺不好意思的。这两个命令加起来不到二十个字符,成本几乎为零,但它是一种态度。
|
||||
|
||||
lsof,找出是谁占着东西。
|
||||
|
||||
lsof -i :8888
|
||||
|
||||
意思是:8888 这个端口被谁占了。启不了 Jupyter 的时候第一个敲它。
|
||||
|
||||
显卡上有僵尸进程占着显存但 nvidia-smi 看不出是谁的时候:
|
||||
|
||||
fuser -v /dev/nvidia*
|
||||
|
||||
它会列出所有正在用显卡的进程。找到自己那个僵掉的,杀了就行。别人的别乱动,这个我得强调一下,共用机器上手快一时爽,后面很尴尬。
|
||||
|
||||
[mosh](https://zhida.zhihu.com/search?content_id=792766250&content_type=Answer&match_order=1&q=mosh&zhida_source=entity),替代 ssh 的连接方式。
|
||||
|
||||
它最大的价值是:你合上笔记本、换个网络、地铁上断线,连接还在。ssh 在这些情况下会断,断了你正在前台跑的东西就没了。mosh 会自己缓存和重连,你重新打开电脑,光标还在那儿等你。
|
||||
|
||||
配合 [tmux](https://zhida.zhihu.com/search?content_id=792766250&content_type=Answer&match_order=1&q=tmux&zhida_source=entity) 用最稳妥。tmux 保证程序活着,mosh 保证你回来得顺畅。
|
||||
|
||||
[trash-cli](https://zhida.zhihu.com/search?content_id=792766250&content_type=Answer&match_order=1&q=trash-cli&zhida_source=entity),给 rm 一个回收站。
|
||||
|
||||
trash-put old_ckpt/
|
||||
|
||||
它把东西移到回收站而不是直接抹掉。我装它的原因不太光彩:我用通配符删过一批文件,范围比我以为的大,几天的训练结果跟着一起没了。
|
||||
|
||||
那天下午我坐在工位上,把那条命令反复看了很多遍,想搞清楚自己到底怎么写错的。其实很简单,就是手快。
|
||||
|
||||
我没有把 rm 直接 alias 成 trash-put,因为那会让我在别人的机器上产生错误的肌肉记忆,那更危险。我的做法是:删单个明确的东西照常用 rm,批量删、带通配符的一律先 ls 看一眼,重要的用 trash-put。
|
||||
|
||||
[croc](https://zhida.zhihu.com/search?content_id=792766250&content_type=Answer&match_order=1&q=croc&zhida_source=entity),两台机器之间直接传文件。
|
||||
|
||||
croc send data.zip
|
||||
|
||||
它给你一个短语,在另一台机器上敲 croc 加那个短语,就开始传了,不用配置密钥、不用管中间有没有跳板机。临时给同门传个模型权重,比来回折腾 scp 路径快得多。
|
||||
|
||||
## 你机器上已经装好了、但你没用过的
|
||||
|
||||
这一段不用装任何东西,全是自带的,而且冷门得离谱。
|
||||
|
||||
comm,比较两个已排序的列表:
|
||||
|
||||
comm -23 all.txt done.txt
|
||||
|
||||
意思是:哪些在 all 里但不在 done 里。断点续跑的时候,这一条就能算出"还有哪些没处理",不用写脚本。
|
||||
|
||||
tac,把文件倒着输出。看日志找最近的报错特别顺手:
|
||||
|
||||
tac log.txt | rg -m1 "Error"
|
||||
|
||||
意思是:从后往前找,遇到第一个 Error 就停。日志几百万行的时候,这比从头搜快得多。
|
||||
|
||||
xargs,把上游的输出变成下游命令的参数:
|
||||
|
||||
fd -e ckpt | xargs -I{} ls -lh {}
|
||||
|
||||
它是把两个不搭的命令粘起来的胶水,值得单独花十分钟搞懂。
|
||||
|
||||
script,把整个终端会话录下来:
|
||||
|
||||
script session.log
|
||||
|
||||
之后你敲的和屏幕上出现的所有东西都会写进那个文件。我用它记过一次很麻烦的环境配置过程,后来照着复盘,把那份记录整理成了脚本。
|
||||
|
||||
还有两个纯粹的手指效率,免费的:
|
||||
|
||||
!! 代表上一条命令。忘了加 sudo 的时候,sudo !! 。
|
||||
|
||||
!$ 代表上一条命令的最后一个参数。刚 mkdir 了一个长路径,下一句 cd !$ 直接进去。
|
||||
|
||||
最后一个,tldr。这不是自带的,但值得作为这一段的结尾:
|
||||
|
||||
tldr tar
|
||||
|
||||
man 手册是给专家做参考的,又长又劝退。tldr 给你的是五六个最常用的例子。tar 的参数我到现在都记不全,每次都是 tldr 一下抄一条。
|
||||
|
||||
不背参数这件事,不丢人。你需要记住的是"有这么个东西能干这件事",具体怎么写,查。
|
||||
|
||||
## 现在说点扫兴的
|
||||
|
||||
写到这儿我得把另一面讲清楚,不然这篇就是一份不负责的种草清单。
|
||||
|
||||
第一,别把肌肉记忆绑在别人机器上没有的工具上。
|
||||
|
||||
我认识一个人,把 ls 全 alias 成了新工具,cat 换掉,cd 换掉,配置得极其漂亮。结果他一上没有权限装东西的集群,整个人就废了,连最基本的操作都要卡一下。
|
||||
|
||||
我的规矩是:新工具用新名字,不覆盖原来的命令。fd 就是 fd,不动 find。你在任何一台裸机上都得能干活,这是底线。
|
||||
|
||||
第二,很多服务器你没有 sudo。
|
||||
|
||||
好消息是上面这些新工具大多是单文件的,可以直接下载扔进自己家目录的 bin 里,不需要管理员权限。[conda](https://zhida.zhihu.com/search?content_id=792766250&content_type=Answer&match_order=1&q=conda&zhida_source=entity) 和 pip 也能装不少。真装不上的,就退回自带工具,前面那一段"已经装好了"的内容就是为这种情况准备的。
|
||||
|
||||
第三,这才是我真正想说的那句。
|
||||
|
||||
我大二那年是个装备党。收藏了几十篇神器推荐,配置文件搞得花里胡哨,终端打开是一屏漂亮的信息面板,给同学演示的时候挺有面子。
|
||||
|
||||
但那些工具我一个都没真正用起来。因为它们不是从我的疼痛里长出来的,是从别人的清单里抄来的。
|
||||
|
||||
后来某个阶段我意识到一件不太好意思承认的事:我那段时间之所以那么热衷于折腾工具,是因为实验做不出来。改配置有即时的、确定的成就感,而实验没有。捣鼓终端主题的那两个小时,我感觉自己在"提升效率",实际上我是在躲。
|
||||
|
||||
想通这个之后我给自己定了个规矩,一直用到现在:一个工具要进 setup.sh,得先有一次真实的狼狈。磁盘满了、日志没时间戳、文件误删了、显卡被自己撞了。疼过一次,再去找工具,那个工具就会活下来。
|
||||
|
||||
反过来,某天我又想装个什么新东西,先问自己一句:它在解决什么疼?如果答不上来,那大概不是工具的问题,是我今天不太想面对手上那件难的事。
|
||||
|
||||
这个自问帮我省下的时间,比上面所有工具加起来还多。
|
||||
|
||||
## 如果只让你装五个
|
||||
|
||||
真要挑,我的顺序是:fzf、ripgrep、fd、ncdu、entr。
|
||||
|
||||
前三个改善的是每天几十次的细碎动作,收益天天都在;ncdu 是出事那天救你;entr 会实实在在改变你调试的节奏。
|
||||
|
||||
跑深度学习的话,再加 gpustat 和 ts,这两个是这个行当的专属刚需。
|
||||
|
||||
判断一个工具值不值得进你的清单,标准我只有一条:它必须对应一次你真实卡住过的时刻。你想不起那个时刻,就先别装,等它来找你。
|
||||
|
||||
那份 setup.sh 现在躺在我的仓库里,二十三行,每行前面我都写了一句注释,写的不是这个工具是什么,而是我为什么装它。翻那个文件,比翻任何一份工具清单都有意思,因为它记的其实不是工具,是我这几年具体在哪些地方栽过。
|
||||
|
||||
写完这篇我看了一眼,今天的实验还在跑,进度条走了六成。
|
||||
@@ -0,0 +1,15 @@
|
||||
# 查看指定网卡信息
|
||||
sudo ethtool eth0
|
||||
|
||||
# 查看所有网卡的接口类型
|
||||
for iface in $(ip link show | awk -F: '/^[0-9]+:/ {print $2}' | tr -d ' '); do
|
||||
echo -n "$iface: "
|
||||
sudo ethtool $iface 2>/dev/null | grep -E "Supported link modes|Port|Speed"
|
||||
done
|
||||
|
||||
|
||||
**关键判断点:**
|
||||
|
||||
- **电口(RJ45)**:显示 `Port: Twisted Pair` 或支持 `1000baseT/Full`、`10GBASE-T` 等模式
|
||||
|
||||
- **光口(Fiber)**:显示 `Port: FIBRE` 或支持 `1000baseX/Full`、`10GBASE-SR/LR` 等模式
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
设置IP和掩码
|
||||
ifconfig eth0 192.168.5.40 netmask 255.255.255.0
|
||||
设置网关
|
||||
route add default gw 192.168.5.1
|
||||
|
||||
|
||||
route add -net 192.168.3.0/24 dev eth0
|
||||
route add -net 192.168.2.0/24 gw 192.168.3.254
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
网卡的bond(绑定)是一种网络技术,它允许将多个物理网络接口卡(NIC)组合成一个单一的逻辑通道,以提高网络吞吐量并提供冗余。以下是网卡绑定的一些关键点:
|
||||
|
||||
1. **提高带宽**:通过将多个网卡绑定在一起,可以增加可用的网络带宽,因为数据可以通过多个网卡同时传输。
|
||||
|
||||
2. **冗余**:如果其中一个网卡出现故障,绑定可以自动切换到另一个网卡,从而减少网络中断的风险。
|
||||
|
||||
3. **负载均衡**:绑定还可以在多个网卡之间分配网络流量,这称为负载均衡,有助于更均匀地使用网络资源。
|
||||
|
||||
以下是Linux网卡绑定(bonding)模式及其对应配置值的详细表格:
|
||||
|
||||
## 网卡Bonding模式对应关系表
|
||||
|
||||
| **Mode编号** | **模式名称** | **Linux内核名称** | **负载均衡** | **容错性** | **交换机要求** |
|
||||
| ---------- | ----------- | ------------- | -------- | ------- | --------- |
|
||||
| **mode=0** | 负载均衡(轮询) | balance-rr | ✅ 轮询方式 | ✅ | 不需要特殊配置 |
|
||||
| **mode=1** | 主备模式 | active-backup | ❌ | ✅ 高可用 | 不需要特殊配置 |
|
||||
| **mode=2** | 负载均衡(XOR哈希) | balance-xor | ✅ 基于哈希 | ✅ | 不需要特殊配置 |
|
||||
| **mode=3** | 广播模式 | broadcast | ❌ | ✅ 高冗余 | 不需要特殊配置 |
|
||||
| **mode=4** | 动态链路聚合 | 802.3ad | ✅ 动态聚合 | ✅ | 需要支持LACP |
|
||||
| **mode=5** | 适配器传输负载均衡 | balance-tlb | ✅ 自适应 | ✅ | 不需要特殊配置 |
|
||||
| **mode=6** | 适配器负载均衡 | balance-alb | ✅ 自适应 | ✅ | 不需要特殊配置 |
|
||||
|
||||
## 各模式详细说明
|
||||
|
||||
### **mode=0 (balance-rr) - 轮询负载均衡**
|
||||
- **特点**:数据包按顺序轮流从每个slave接口发送
|
||||
- **优点**:带宽叠加,负载均衡
|
||||
- **缺点**:数据包可能乱序到达
|
||||
- **适用场景**:需要高带宽,不严格要求包顺序
|
||||
|
||||
### **mode=1 (active-backup) - 主备模式**
|
||||
- **特点**:只有一个接口活跃,其他作为备份
|
||||
- **优点**:高可用性,配置简单
|
||||
- **缺点**:不能增加带宽
|
||||
- **适用场景**:关键业务需要冗余备份
|
||||
|
||||
### **mode=2 (balance-xor) - XOR哈希策略**
|
||||
- **特点**:基于源/目的MAC地址的XOR运算选择出口
|
||||
- **优点**:同会话流量走同一接口,避免乱序
|
||||
- **缺点**:负载均衡不够精细
|
||||
- **配置示例**:
|
||||
```bash
|
||||
# 哈希策略选项
|
||||
xmit_hash_policy=layer2 # 默认,基于MAC地址
|
||||
xmit_hash_policy=layer2+3 # 基于MAC+IP
|
||||
xmit_hash_policy=layer3+4 # 基于IP+端口
|
||||
```
|
||||
|
||||
### **mode=3 (broadcast) - 广播模式**
|
||||
- **特点**:所有数据包在所有接口发送
|
||||
- **优点**:极高冗余性
|
||||
- **缺点**:网络开销大,浪费带宽
|
||||
- **适用场景**:极端可靠性要求
|
||||
|
||||
### **mode=4 (802.3ad/LACP) - 动态链路聚合**
|
||||
- **特点**:需要交换机支持LACP协议
|
||||
- **优点**:标准协议,兼容性好,智能负载均衡
|
||||
- **缺点**:依赖交换机配置
|
||||
- **交换机配置要求**:
|
||||
```bash
|
||||
# 配置LACP参数
|
||||
lacp_rate=slow # 默认,每30秒发送LACPDU
|
||||
lacp_rate=fast # 每秒发送LACPDU(1秒)
|
||||
```
|
||||
|
||||
### **mode=5 (balance-tlb) - 适配器传输负载均衡**
|
||||
- **特点**:根据每个slave的负载情况动态分配
|
||||
- **优点**:不需要特殊交换机
|
||||
- **缺点**:只均衡出站流量,入站流量由当前slave处理
|
||||
|
||||
### **mode=6 (balance-alb) - 适配器负载均衡**
|
||||
- **特点**:mode=5的增强版,包含ARP协商
|
||||
- **优点**:出入站流量都均衡,无需交换机支持
|
||||
- **缺点**:可能引起ARP刷新问题
|
||||
|
||||
## 常用配置参数
|
||||
|
||||
### 1. **基础参数**
|
||||
```bash
|
||||
BONDING_OPTS="mode=1 miimon=100"
|
||||
```
|
||||
- **miimon=100**:每100ms检查链路状态(毫秒)
|
||||
- **downdelay=200**:链路down后等待200ms再禁用
|
||||
- **updelay=200**:链路恢复后等待200ms再启用
|
||||
|
||||
### 2. **主备模式专用参数**
|
||||
```bash
|
||||
BONDING_OPTS="mode=1 miimon=100 primary=eth0"
|
||||
```
|
||||
- **primary=eth0**:指定主接口
|
||||
- **primary_reselect=always**:主接口恢复后是否切回
|
||||
|
||||
### 3. **LACP模式专用参数**
|
||||
```bash
|
||||
BONDING_OPTS="mode=4 miimon=100 lacp_rate=fast xmit_hash_policy=layer3+4"
|
||||
```
|
||||
|
||||
## 配置文件示例
|
||||
|
||||
### RHEL/CentOS/Fedora
|
||||
```bash
|
||||
# /etc/sysconfig/network-scripts/ifcfg-bond0
|
||||
DEVICE=bond0
|
||||
TYPE=Bond
|
||||
BONDING_MASTER=yes
|
||||
IPADDR=192.168.1.100
|
||||
NETMASK=255.255.255.0
|
||||
ONBOOT=yes
|
||||
BOOTPROTO=none
|
||||
BONDING_OPTS="mode=4 miimon=100 lacp_rate=fast"
|
||||
|
||||
# /etc/sysconfig/network-scripts/ifcfg-eth0
|
||||
DEVICE=eth0
|
||||
TYPE=Ethernet
|
||||
MASTER=bond0
|
||||
SLAVE=yes
|
||||
ONBOOT=yes
|
||||
```
|
||||
|
||||
### Ubuntu/Debian
|
||||
```bash
|
||||
# /etc/network/interfaces
|
||||
auto bond0
|
||||
iface bond0 inet static
|
||||
address 192.168.1.100
|
||||
netmask 255.255.255.0
|
||||
bond-mode 4
|
||||
bond-miimon 100
|
||||
bond-lacp-rate 1
|
||||
bond-slaves eth0 eth1
|
||||
```
|
||||
|
||||
## 状态检查命令
|
||||
|
||||
```bash
|
||||
# 查看bonding状态
|
||||
cat /proc/net/bonding/bond0
|
||||
|
||||
# 查看网络接口统计
|
||||
ip link show bond0
|
||||
|
||||
# 查看bonding模块参数
|
||||
cat /sys/class/net/bond0/bonding/mode
|
||||
cat /sys/class/net/bond0/bonding/slaves
|
||||
```
|
||||
|
||||
## 选择建议
|
||||
|
||||
1. **需要高可用性**:mode=1(主备模式)
|
||||
2. **需要带宽聚合且交换机支持LACP**:mode=4(802.3ad)
|
||||
3. **需要带宽聚合但交换机不支持LACP**:mode=0或mode=6
|
||||
4. **需要极致的冗余性**:mode=3(广播模式)
|
||||
5. **服务器负载均衡**:mode=2或mode=4
|
||||
|
||||
选择bond模式时,请确保了解网络环境和业务需求,并在测试环境中验证后再部署到生产环境。
|
||||
@@ -0,0 +1,45 @@
|
||||
网卡的bond(绑定)是一种网络技术,它允许将多个物理网络接口卡(NIC)组合成一个单一的逻辑通道,以提高网络吞吐量并提供冗余。以下是网卡绑定的一些关键点:
|
||||
|
||||
1. **提高带宽**:通过将多个网卡绑定在一起,可以增加可用的网络带宽,因为数据可以通过多个网卡同时传输。
|
||||
|
||||
2. **冗余**:如果其中一个网卡出现故障,绑定可以自动切换到另一个网卡,从而减少网络中断的风险。
|
||||
|
||||
3. **负载均衡**:绑定还可以在多个网卡之间分配网络流量,这称为负载均衡,有助于更均匀地使用网络资源。
|
||||
|
||||
## bond 模式
|
||||
1. **平衡模式(Round-robin, Balance-rr)**:
|
||||
|
||||
- 描述:按照顺序将网络流量轮流分配到每个网卡上。
|
||||
- 使用场景:适用于所有网卡具有相同速度和双工模式的情况。
|
||||
2. **主动-备份模式(Active-backup)**:
|
||||
|
||||
- 描述:通常只有一个网卡处于活动状态,负责处理所有网络流量。其他网卡处于备份状态,仅在活动网卡发生故障时接管。
|
||||
- 使用场景:适用于需要高可靠性但不需要增加带宽的场景。
|
||||
3. **XOR模式(Adaptive load balancing, Balance-xor)**:
|
||||
|
||||
- 描述:使用XOR算法根据目标MAC地址或IP地址将流量分配到不同的网卡上。
|
||||
- 使用场景:适用于需要更智能的负载分配,以减少特定网卡的流量压力。
|
||||
4. **广播模式(Broadcast)**:
|
||||
|
||||
- 描述:将所有传入的流量复制并发送到所有绑定的网卡上。
|
||||
- 使用场景:适用于需要将流量广播到多个目的地的场景。
|
||||
5. **链路聚合模式(Link aggregation, LACP - Link Aggregation Control Protocol)**:
|
||||
|
||||
- 描述:通过LACP协议动态地将多个网卡组合成一个单一的逻辑链路,可以自动进行负载均衡和故障转移。
|
||||
- 使用场景:适用于需要动态管理链路聚合的情况。
|
||||
6. **静态聚合模式(Static)**:
|
||||
|
||||
- 描述:需要手动配置哪些网卡参与聚合,并且不会自动进行故障转移。
|
||||
- 使用场景:适用于需要手动控制链路聚合的情况。
|
||||
7. **传输队列模式( Transmit load balancing, Balance-tlb)**:
|
||||
|
||||
- 描述:根据每个网卡的传输队列长度分配流量,以避免队列溢出。
|
||||
- 使用场景:适用于需要优化传输队列性能的场景。
|
||||
8. **接收队列模式(Receive load balancing, Balance-rlb)**:
|
||||
|
||||
- 描述:根据每个网卡的接收队列长度分配流量,以避免队列溢出。
|
||||
- 使用场景:适用于需要优化接收队列性能的场景。
|
||||
9. **最小链接模式(Minimize link usage, Balance-alb)**:
|
||||
|
||||
- 描述:尽可能使用最少使用的网卡,以减少所有网卡的平均使用率。
|
||||
- 使用场景:适用于希望最小化网卡使用率的场景。
|
||||
@@ -0,0 +1,202 @@
|
||||
在无图形界面的服务器上,全程通过命令行操作是标准做法。核心思路是使用 `virt-install` 命令创建虚拟机,并通过 **VNC** 远程连接来完成 Windows 的图形化安装。
|
||||
|
||||
以下是完整的命令行操作手册:
|
||||
|
||||
---
|
||||
|
||||
### 📋 一、前提条件
|
||||
|
||||
在开始之前,请确保:
|
||||
|
||||
1. **CPU 支持虚拟化**:你的 E5-2673 支持,但需在 BIOS 中确认 **Intel VT-x** 已启用。
|
||||
2. **拥有 sudo 权限**。
|
||||
|
||||
### 🛠️ 二、安装 KVM 及相关工具
|
||||
|
||||
1. **更新软件包列表**:
|
||||
```bash
|
||||
sudo apt update
|
||||
```
|
||||
|
||||
2. **安装 KVM、QEMU、libvirt 和 virt-install**(无需图形界面工具 `virt-manager`):
|
||||
```bash
|
||||
sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virtinst ovmf
|
||||
```
|
||||
|
||||
3. **将当前用户加入 `libvirt` 和 `kvm` 组**:
|
||||
```bash
|
||||
sudo usermod -aG libvirt $USER
|
||||
sudo usermod -aG kvm $USER
|
||||
```
|
||||
|
||||
4. **启动 `libvirtd` 服务并设置开机自启**:
|
||||
```bash
|
||||
sudo systemctl enable --now libvirtd
|
||||
```
|
||||
|
||||
5. **验证服务状态**:
|
||||
```bash
|
||||
sudo systemctl status libvirtd
|
||||
```
|
||||
确保状态为 `active (running)`。
|
||||
|
||||
### 🌐 三、(可选)配置桥接网络
|
||||
|
||||
默认的 `NAT` 网络(`default`)可以让虚拟机访问互联网,但如果需要虚拟机与宿主机在同一个局域网,并拥有独立 IP,则需要配置桥接网络。
|
||||
|
||||
> **注意**:以下操作通过 SSH 连接服务器时请格外小心,错误的网络配置可能导致连接中断。建议通过服务器控制台(如 IPMI、iDRAC)操作。
|
||||
|
||||
1. **查看网卡名称**:
|
||||
```bash
|
||||
ip link show
|
||||
```
|
||||
找到你的物理网卡,如 `enp3s0` 或 `eth0`。
|
||||
|
||||
2. **备份当前 Netplan 配置**:
|
||||
```bash
|
||||
sudo cp /etc/netplan/50-cloud-init.yaml /etc/netplan/50-cloud-init.yaml.bak
|
||||
```
|
||||
你的配置文件可能叫别的名字,以 `/etc/netplan/` 目录下的实际文件名为准。
|
||||
|
||||
3. **编辑 Netplan 配置文件**:
|
||||
```bash
|
||||
sudo nano /etc/netplan/50-cloud-init.yaml
|
||||
```
|
||||
|
||||
4. **修改为以下内容**(请将 `enp3s0`、IP 地址等替换为你自己的信息):
|
||||
```yaml
|
||||
network:
|
||||
version: 2
|
||||
renderer: networkd
|
||||
ethernets:
|
||||
enp3s0:
|
||||
dhcp4: false
|
||||
bridges:
|
||||
br0:
|
||||
interfaces: [enp3s0]
|
||||
addresses: [192.168.1.100/24] # 替换为你的静态IP
|
||||
routes:
|
||||
- to: default
|
||||
via: 192.168.1.1 # 替换为你的网关
|
||||
nameservers:
|
||||
addresses: [8.8.8.8, 8.8.4.4]
|
||||
parameters:
|
||||
stp: false
|
||||
forward-delay: 0
|
||||
```
|
||||
> 如果希望网桥通过 DHCP 自动获取 IP,可将 `addresses`、`routes`、`nameservers` 替换为 `dhcp4: true`。
|
||||
|
||||
5. **应用配置**:
|
||||
```bash
|
||||
sudo netplan apply
|
||||
```
|
||||
|
||||
6. **验证网桥**:
|
||||
```bash
|
||||
ip addr show br0
|
||||
```
|
||||
|
||||
### 💾 四、准备 ISO 镜像文件
|
||||
|
||||
创建目录存放 ISO 文件:
|
||||
```bash
|
||||
mkdir -p ~/iso
|
||||
```
|
||||
|
||||
将以下两个 ISO 文件放入 `~/iso` 目录:
|
||||
* **Windows 10 LTSC ISO 镜像**
|
||||
* **VirtIO 驱动 ISO**:从 [Fedora 官方仓库](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso) 下载。
|
||||
|
||||
### 🚀 五、使用 virt-install 创建虚拟机
|
||||
|
||||
执行以下命令创建虚拟机:
|
||||
|
||||
```bash
|
||||
sudo virt-install \
|
||||
--name win10-ltsc \
|
||||
--memory 8192 \
|
||||
--vcpus sockets=1,cores=4,threads=1 \
|
||||
--disk path=/vm/disk/win10-ltsc.qcow2,size=60,bus=virtio \
|
||||
--disk path=/vm/iso/virtio-win.iso,device=cdrom \
|
||||
--cdrom /vm/iso/zh-cn_windows_10_enterprise_ltsc_2021_x64_dvd_033b7312.iso \
|
||||
--os-variant win10 \
|
||||
--network network=default,model=virtio \
|
||||
--graphics vnc,listen=0.0.0.0 \
|
||||
--console pty,target_type=serial \
|
||||
--boot uefi,firmware.feature0.name=secure-boot,firmware.feature0.enabled=no \
|
||||
--check path_in_use=off
|
||||
```
|
||||
|
||||
#### 各参数说明
|
||||
|
||||
| 参数 | 说明 |
|
||||
| :--- | :--- |
|
||||
| `--name` | 虚拟机名称 |
|
||||
| `--memory` | 分配的内存大小(MB),此处为 8GB |
|
||||
| `--vcpus` | CPU 配置:1 个 CPU 插槽,4 个核心,每个核心 1 个线程 |
|
||||
| `--disk` | 磁盘路径、大小(GB)和总线类型(`virtio` 性能最佳) |
|
||||
| `--cdrom` | Windows ISO 镜像路径 |
|
||||
| `--os-variant` | 操作系统类型,`win10` 适用于 Windows 10/11 |
|
||||
| `--network` | 网桥名称(如用 NAT 则填 `default`)和网卡型号(`virtio`) |
|
||||
| `--graphics` | **关键**:启用 VNC,并监听所有网络接口 |
|
||||
| `--console` | 开启串行控制台,便于故障排查 |
|
||||
| `--boot` | 使用 UEFI 固件(`ovmf`)启动 |
|
||||
|
||||
> **关于 `--os-variant`**:执行 `osinfo-query os | grep win` 可查看所有支持的 Windows 版本。
|
||||
|
||||
### 🖥️ 六、通过 VNC 完成 Windows 安装
|
||||
|
||||
1. **确认 VNC 端口**:
|
||||
```bash
|
||||
sudo virsh vncdisplay win10-ltsc
|
||||
```
|
||||
输出类似 `127.0.0.1:0`,表示 VNC 服务在 **5900** 端口(`:0` 对应 5900,`:1` 对应 5901)。
|
||||
|
||||
2. **连接 VNC**:在本地电脑上用 VNC 客户端连接:
|
||||
```
|
||||
你的服务器IP:5900
|
||||
```
|
||||
> **安全提醒**:上述配置中 VNC 未设密码,任何人都可连接。建议通过 SSH 隧道转发 VNC 端口,或使用 `--graphics vnc,password=你的密码` 设置密码。
|
||||
|
||||
3. **在 VNC 窗口中安装 Windows**:
|
||||
* 正常进入安装界面。
|
||||
* 当提示 **“未找到任何驱动器”** 时,点击 **“加载驱动程序”**。
|
||||
* 点击 **“浏览”**,找到 VirtIO 驱动 ISO 中的 `viostor\w10\amd64` 文件夹,加载 **“Red Hat VirtIO SCSI Controller”** 驱动。
|
||||
* 磁盘识别后,继续完成 Windows 安装。
|
||||
|
||||
### 🚀 七、安装完成后的优化
|
||||
|
||||
1. **安装 VirtIO 网卡驱动**:
|
||||
* Windows 安装完成后,进入“此电脑”,打开 VirtIO 驱动光盘。
|
||||
* 运行 `virtio-win-gt-x64.exe` 安装所有驱动。
|
||||
|
||||
2. **启用远程桌面(RDP)**:
|
||||
* 在 Windows 中:设置 → 系统 → 远程桌面 → 开启远程桌面。
|
||||
* 之后可通过 RDP 客户端直接连接,比 VNC 更流畅。
|
||||
|
||||
3. **常用虚拟机管理命令**:
|
||||
```bash
|
||||
# 启动虚拟机
|
||||
sudo virsh start win10-ltsc
|
||||
# 关闭虚拟机(需在 Windows 内关机)
|
||||
sudo virsh shutdown win10-ltsc
|
||||
# 强制关闭
|
||||
sudo virsh destroy win10-ltsc
|
||||
# 查看所有虚拟机状态
|
||||
sudo virsh list --all
|
||||
# 删除虚拟机
|
||||
sudo virsh undefine win10-ltsc
|
||||
```
|
||||
|
||||
### 💎 总结
|
||||
|
||||
整个流程可概括为:
|
||||
|
||||
1. **安装** KVM 相关软件包。
|
||||
2. **(可选)配置**桥接网络。
|
||||
3. **准备** Windows ISO 和 VirtIO 驱动 ISO。
|
||||
4. **执行** `virt-install` 命令创建虚拟机。
|
||||
5. **通过 VNC 连接**,在 Windows 安装过程中加载 VirtIO 驱动。
|
||||
6. **安装 VirtIO 驱动**并启用 RDP,完成优化。
|
||||
|
||||
如果在 VNC 连接或驱动加载环节遇到问题,可以随时再来问我。
|
||||
@@ -0,0 +1,183 @@
|
||||
删除原文件后链接不需要保留的就用软链接;删除原文件后链接还要保留的用硬链接。
|
||||
### 数据在Linux系统中是如何存储
|
||||
|
||||
为了理解什么是硬链接和软链接,需要了解数据在Linux系统中是如何存储的。
|
||||
|
||||
在计算机中,数据存储在硬盘上。硬盘被分成多个Block,每个Block又被分成很多称为扇区(sector)的小块。典型情况下,每个扇区的大小是 512 字节,Block的大小为4096字节,这种情况下,一个Block包含8个扇区。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
文件系统是以Block为单位分配空间的,你可以这么理解,即使你创建一个大小只有1字节的文件,它都会占用一个完整的Block。你用ls -l去查看这个文件的大小,会显示是4096 个字节大小。
|
||||
|
||||
但是如果你创建的文件大小超过4096个字节怎么办?答案是文件将被“碎片化”并存储到多个不同的Block中。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 什么是 inode
|
||||
|
||||
Inode 本质上是用于描述文件的数据结构,innode主要存储了如文件权限、文件类型、文件大小等文件元数据信息,但最重要的是存储了该文件的真实数据存放在磁盘上的哪些Block中。
|
||||
|
||||

|
||||
|
||||
需要注意的是`文件名不是 inode 的一部分。`
|
||||
|
||||
### 关于文件名和 inode
|
||||
|
||||
在 Linux 系统上,文件名保存在单独的索引中,这个单独的索引记录了系统上所有文件(包括目录)的文件名,并且它也知道每一个文件其对应的inode。
|
||||
|
||||

|
||||
|
||||
### Linux 上的硬链接和软链接是什么
|
||||
|
||||
有了上面的背景,现在我们可以来介绍硬链接和软链接了。
|
||||
|
||||
让我们从软链接开始,因为它可能是最容易理解的。
|
||||
|
||||
### 软链接
|
||||
|
||||
软链接,也称为符号链接,运行 ls 命令时,符号链接用文件类型“ l ”标识,同时文件名那一列是`链接名称 -> 所引用文件的文件名。`
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
这个图中,文件“shortcut”指向我文件系统上的文件“file.txt ”。
|
||||
|
||||
现在我们来看一下原文件file.txt的 inode 和shortcut的 inode。
|
||||
|
||||
```text
|
||||
$ stat shortcut
|
||||
File: shortcut -> file.txt
|
||||
Size: 3 Blocks: 0 IO Block: 4096 symbolic link
|
||||
Device: fc01h/64513d Inode: 258539 Links: 1
|
||||
|
||||
$ stat file.txt
|
||||
File: job
|
||||
Size: 59 Blocks: 8 IO Block: 4096 regular file
|
||||
Device: fc01h/64513d Inode: 258545 Links: 2
|
||||
```
|
||||
|
||||
可以看出,两个文件的 inode不同,但是两者也有一定的关联:原始文件 inode 直接指向文件内容(即包含实际数据的块),而符号链接“shortcut”的 inode 指向包含原始文件路径的块。
|
||||
|
||||

|
||||
|
||||
不难看出,软链接下,链接文件和原始文件是共享文件的实际数据,这意味着如果我修改链接 文件的内容,其实就是修改了原始文件的内容。
|
||||
|
||||
如果我删除链接文件,它只会删除对第一个 inode(原始文件的 inode) 的引用。由于原始文件的 inode仍然引用磁盘上文件的内容,因此内容不会丢失,但是,如果我删除原始文件,将无法再读取该文件,符号链接会变成一个“断开”的链接,因为它指向的文件已经不存在了,这就是我们所说的悬空符号链接,即未指向任何内容的链接。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
现在,我们来思路另外一个问题:软链接的大小是多少?
|
||||
|
||||
为了说明这个问题,下面我创建了一个名为“devconnected”的文件,并使用 ln 命令为它建立了一个软链接。
|
||||
|
||||

|
||||
|
||||
可以看出软链接的大小是12,这个大小正好是原始文件名 “devconnected”的长度。
|
||||
|
||||
最后需要说明的是:软链接可以指向不同的文件系统,包括指向远程文件系统。如果您使用 NFS(网络文件系统),则可以创建从一个文件系统到网络访问的文件系统的软链接。
|
||||
|
||||
### 硬链接
|
||||
|
||||
我们再来看一下原始文件 inode和硬链接 inode。
|
||||
|
||||
```text
|
||||
$ stat hardlink
|
||||
File: hardlink
|
||||
Size: 59 Blocks: 8 IO Block: 4096 regular file
|
||||
Device: fc01h/64513d Inode: 258545 Links: 2
|
||||
|
||||
$ stat file.txt
|
||||
File: file.txt
|
||||
Size: 59 Blocks: 8 IO Block: 4096 regular file
|
||||
Device: fc01h/64513d Inode: 258545 Links: 2
|
||||
```
|
||||
|
||||
您可能已经注意到,inode 是相同的,但是文件名不同!
|
||||
|
||||
以下是此情况下文件系统发生的情况。
|
||||
|
||||

|
||||
|
||||
因此,如果修改硬链接文件中的内容,则原文件中的内容也会改变,同样地,如果你修改了原始文件中的内容,那么在硬链接文件中也会被修改。
|
||||
|
||||
但是,如果删除硬链接文件,您仍然可以访问原始文件内容,同样,删除原始文件不会对硬链接的内容产生任何影响,`这也是和软链接一个显著的不同点。`
|
||||
|
||||
最后需要注意的一点是`不能为目录创建硬链接`。
|
||||
|
||||
### 链接相关操作
|
||||
|
||||
### 创建软链接
|
||||
|
||||
要创建软链接,需要使用ln命令,带有-s选项(表示符号)和两个参数,第一个参数指定想要链接到的原文件名称,第二个参数指定即将创建的软链接的名称。
|
||||
|
||||
```text
|
||||
$ ln -s file shortcut
|
||||
|
||||
$ ls -l
|
||||
-rw-rw-r-- 1 schkn schkn 0 Aug 14 20:12 file
|
||||
lrwxrwxrwx 1 schkn schkn 4 Aug 14 20:12 shortcut -> file
|
||||
```
|
||||
|
||||
还可以创建到目录的软链接:
|
||||
|
||||
```text
|
||||
$ mkdir folder
|
||||
$ ln -s folder shortcut-folder
|
||||
|
||||
$ ls -l
|
||||
drwxrwxr-x 2 schkn schkn 4096 Aug 14 20:13 folder
|
||||
lrwxrwxrwx 1 schkn schkn 7 Aug 14 20:14 shortcut-folder -> folder/
|
||||
```
|
||||
|
||||
### 删除软链接
|
||||
|
||||
要删除现有的软链接,使用unlink命令,后面的参数是要删除的软链接名称。
|
||||
|
||||
```text
|
||||
$ unlink shortcut
|
||||
|
||||
$ ls -l
|
||||
-rw-rw-r-- 1 schkn schkn 0 Aug 14 20:12 file
|
||||
```
|
||||
|
||||
### 创建硬链接
|
||||
|
||||
创建硬链接也是使用ln命令,只是不需要-s选项。
|
||||
|
||||
```text
|
||||
$ ln file hardlink
|
||||
|
||||
$ ls -l
|
||||
-rw-rw-r-- 2 schkn schkn 0 Aug 14 20:12 file
|
||||
-rw-rw-r-- 2 schkn schkn 0 Aug 14 20:12 hardlink
|
||||
```
|
||||
|
||||
### 删除硬链接
|
||||
|
||||
同样,可以使用unlink命令删除 Linux 系统上的硬链接。
|
||||
|
||||
```text
|
||||
$ ln file hardlink
|
||||
$ unlink hardlink
|
||||
$ ls -l
|
||||
-rw-rw-r-- 2 schkn schkn 0 Aug 14 20:12 file
|
||||
```
|
||||
|
||||
### 查找系统上的链接
|
||||
|
||||
在 Linux 系统上可以使用find 命令查找链接,find 命令有一个选项可以指定类型来查找文件,因此可以使用它来查找系统上的链接:
|
||||
|
||||
```text
|
||||
$ find . -type l -ls
|
||||
262558 0 lrwxrwxrwx 1 schkn schkn 7 Aug 14 20:14 ./shortcut-folder2 -> folder2/
|
||||
262558 0 lrwxrwxrwx 1 schkn schkn 7 Aug 14 20:14 ./shortcut-folder -> folder/
|
||||
```
|
||||
|
||||
[编辑于 2024-07-09 19:19](https://zhuanlan.zhihu.com/p/707875954)
|
||||
@@ -0,0 +1,21 @@
|
||||
硬盘大小120G,安装系统时选择了自动分区,结果进系统后发现有个/backup挂载点占了40G的空间,于是准备把这个挂载点删除,把空出来的40G分给根。
|
||||
|
||||
开始操作
|
||||
|
||||
1、查看[逻辑卷](https://so.csdn.net/so/search?q=%E9%80%BB%E8%BE%91%E5%8D%B7&spm=1001.2101.3001.7020)
|
||||
|
||||
> lvdisplay
|
||||
|
||||
2、删除挂载到/backup的逻辑卷
|
||||
|
||||
> lvremovo /dev/mapper/klas-backup
|
||||
|
||||
3、把空闲出来的存储空间加到/下
|
||||
|
||||
> lvextend -L +40G /dev/mapper/klas-root
|
||||
|
||||
4、执行调整,使之生效
|
||||
|
||||
> xfs_growfs /
|
||||
|
||||
注意最后一步xfs_growfs后面跟的是挂载点,不是设备名。
|
||||
@@ -0,0 +1 @@
|
||||
JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
|
||||
@@ -0,0 +1,4 @@
|
||||
## 通信机制
|
||||
MinIO 集群节点之间通过 HTTP/HTTPS 协议进行通信。这些通信包括节点状态信息的交换、数据同步、心跳检测等。
|
||||
MinIO 使用心跳机制来检测节点的存活状态。每个节点定期发送[心跳信号](https://zhida.zhihu.com/search?content_id=245266225&content_type=Article&match_order=1&q=%E5%BF%83%E8%B7%B3%E4%BF%A1%E5%8F%B7&zhida_source=entity)到其他节点,以通知自己的存活状态。心跳信号包括节点的健康状态和当前的负载情况。
|
||||
## 数据同步机制
|
||||
@@ -0,0 +1,4 @@
|
||||
## 通信机制
|
||||
MinIO 集群节点之间通过 HTTP/HTTPS 协议进行通信。这些通信包括节点状态信息的交换、数据同步、心跳检测等。
|
||||
MinIO 使用心跳机制来检测节点的存活状态。每个节点定期发送[心跳信号](https://zhida.zhihu.com/search?content_id=245266225&content_type=Article&match_order=1&q=%E5%BF%83%E8%B7%B3%E4%BF%A1%E5%8F%B7&zhida_source=entity)到其他节点,以通知自己的存活状态。心跳信号包括节点的健康状态和当前的负载情况。
|
||||
## 数据同步机制
|
||||
@@ -0,0 +1,15 @@
|
||||
-- 查看当前用户认证方式
|
||||
|
||||
SELECT user, host, plugin FROM mysql.user;
|
||||
|
||||
|
||||
|
||||
-- 修改指定用户的认证方式
|
||||
|
||||
ALTER USER 'your_username'@'your_host' IDENTIFIED WITH mysql_native_password BY 'your_password';
|
||||
|
||||
|
||||
|
||||
-- 刷新权限
|
||||
|
||||
FLUSH PRIVILEGES;
|
||||
@@ -0,0 +1,78 @@
|
||||
Pi Agent 的快捷键设计得比较全面,而且支持全按键自定义。它的配置文件位于 `~/.pi/agent/keybindings.json`,修改后无需重启,在 Pi 会话中运行 `/reload` 命令即可生效。
|
||||
|
||||
下面整理了 Pi 的默认快捷键,分为几大类:
|
||||
|
||||
### ⌨️ 编辑器操作
|
||||
这些快捷键主要用于在输入框中编辑文本和移动光标。
|
||||
|
||||
| 操作 | 默认快捷键 | 说明 |
|
||||
| :--- | :--- | :--- |
|
||||
| **光标上移** | `up` | 向上移动光标 |
|
||||
| **光标下移** | `down` | 向下移动光标 |
|
||||
| **光标左移** | `left`, `ctrl+b` | 向左移动一个字符 |
|
||||
| **光标右移** | `right`, `ctrl+f` | 向右移动一个字符 |
|
||||
| **按词左移** | `alt+left`, `ctrl+left`, `alt+b` | 向左移动一个单词 |
|
||||
| **按词右移** | `alt+right`, `ctrl+right`, `alt+f` | 向右移动一个单词 |
|
||||
| **跳到行首** | `home`, `ctrl+a` | 跳转到当前行的开头 |
|
||||
| **跳到行尾** | `end`, `ctrl+e` | 跳转到当前行的结尾 |
|
||||
| **删除前一个字符** | `backspace` | 删除光标前的字符 |
|
||||
| **删除后一个字符** | `delete`, `ctrl+d` | 删除光标后的字符 |
|
||||
| **删除前一个词** | `ctrl+w`, `alt+backspace` | 删除光标前的一个单词 |
|
||||
| **删除后一个词** | `alt+d`, `alt+delete` | 删除光标后的一个单词 |
|
||||
| **删至行首** | `ctrl+u` | 删除从光标到行首的内容 |
|
||||
| **删至行尾** | `ctrl+k` | 删除从光标到行尾的内容 |
|
||||
| **撤销** | `ctrl+-` | 撤销上一步编辑操作 |
|
||||
| **粘贴已删文本** | `ctrl+y` | 粘贴最近删除的文本(Kill Ring) |
|
||||
|
||||
### 📝 输入与提交
|
||||
|
||||
| 操作 | 默认快捷键 | 说明 |
|
||||
| :--- | :--- | :--- |
|
||||
| **换行** | `shift+enter`, `ctrl+j` | 在输入框中插入换行符 |
|
||||
| **提交** | `enter` | 发送当前输入内容 |
|
||||
| **Tab/自动补全** | `tab` | 触发路径或命令的自动补全 |
|
||||
| **外部编辑器** | `ctrl+g` | 在外部编辑器中编辑当前输入内容 |
|
||||
|
||||
### 🚀 应用与消息
|
||||
|
||||
| 操作 | 默认快捷键 | 说明 |
|
||||
| :----------- | :---------- | :------------------ |
|
||||
| **中断/取消** | `escape` | 取消当前正在进行的 AI 操作 |
|
||||
| **清空编辑区** | `ctrl+c` | 清空当前输入框中的所有内容 |
|
||||
| **退出** | `ctrl+d` | 当编辑区为空时,退出 Pi Agent |
|
||||
| **复制 AI 回复** | `ctrl+x` | 复制最后一条 AI 助手的回复消息 |
|
||||
| **发送跟进消息** | `alt+enter` | 将当前消息排入跟进队列 |
|
||||
| **取回排队消息** | `alt+up` | 将排队中的消息取回编辑器 |
|
||||
|
||||
### 🧠 模型与推理
|
||||
|
||||
| 操作 | 默认快捷键 | 说明 |
|
||||
| :--- | :--- | :--- |
|
||||
| **模型选择器** | `ctrl+l` | 打开模型选择器,快速切换模型 |
|
||||
| **向前切换模型** | `ctrl+p` | 切换到下一个模型 |
|
||||
| **向后切换模型** | `shift+ctrl+p` | 切换到上一个模型 |
|
||||
| **切换推理等级** | `shift+tab` | 循环切换 AI 的推理强度等级 |
|
||||
| **切换推理显示** | `ctrl+t` | 展开或折叠 AI 的推理过程 |
|
||||
| **切换工具输出** | `ctrl+o` | 展开或折叠工具调用的输出结果 |
|
||||
|
||||
### ✨ 如何自定义快捷键
|
||||
|
||||
Pi 的快捷键完全可定制。你可以通过编辑 `~/.pi/agent/keybindings.json` 文件来修改或禁用任何快捷键。
|
||||
|
||||
**配置格式**:文件是一个 JSON 对象,键是操作 ID,值是一个快捷键字符串或字符串数组。
|
||||
|
||||
```json
|
||||
{
|
||||
"tui.editor.cursorUp": ["up", "ctrl+p"],
|
||||
"tui.input.submit": "enter",
|
||||
"tui.input.newLine": ["shift+enter", "ctrl+enter"]
|
||||
}
|
||||
```
|
||||
*(注:操作 ID 可能是命名空间形式,如 `tui.editor.cursorUp`,旧的非命名空间形式如 `cursorUp` 在启动时会自动迁移)*
|
||||
|
||||
**快捷键语法**:使用 `修饰键+按键` 的格式。
|
||||
* **修饰键**:`ctrl`, `shift`, `alt`,可以组合使用,如 `ctrl+shift+x`。
|
||||
* **按键**:字母(`a`-`z`)、数字(`0`-`9`)、方向键(`up`, `down`)、特殊键(`enter`, `escape`, `tab`, `space`, `backspace`, `delete`, `home`, `end`, `pageUp`, `pageDown`)以及功能键(`f1`-`f20`)等。
|
||||
* **禁用快捷键**:将快捷键设置为空数组 `[]` 即可移除该默认绑定。
|
||||
|
||||
你可以参考默认配置,打造一套完全符合自己习惯的快捷键方案。
|
||||
@@ -0,0 +1,395 @@
|
||||
|
||||
## 一、背景
|
||||
|
||||
从事数据开发将近四年,过程中有大量任务交接或阅读同事代码的场景。在这些场景中发现有些SQL读起来赏心悦目,可以一目了然地了解业务逻辑,一些复杂的业务需求实现方法也可以做到简洁优雅,同时在性能上也有良好表现。而有些SQL读起来非常艰难,时常要跨越几百行寻找WHERE条件或者关联字段,甚至充斥着大量相同的子查询命名,除了作者可能少有人能快速看懂。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
为此,基于个人经验、理解与实践,我总结了一些方法和技巧,能让SQL尽量变得优雅,即兼顾代码可读性和执行性能两方面的提升。
|
||||
|
||||
|
||||
|
||||
## 二、方法与技巧
|
||||
|
||||
### 1.子查询与谓词下推
|
||||
|
||||
很多同事在写关联逻辑时,习惯于直接将原表关联,随后在最下方用一大段WHERE语句进行条件过滤,如下示例:
|
||||
|
||||
```text
|
||||
// -------------------- Bad Codes ------------------------
|
||||
|
||||
SELECT
|
||||
f1.pin,
|
||||
c1.site_id,
|
||||
c2.site_name
|
||||
FROM
|
||||
fdm.fdm1 AS f1
|
||||
LEFT JOIN cdm.cdm1 AS c1
|
||||
ON
|
||||
f1.erp = lower(c1.account_number)
|
||||
LEFT JOIN cdm.cdm2 AS c2
|
||||
ON
|
||||
c1.site_id = c2.site_code
|
||||
WHERE
|
||||
f1.start_date <= '""" + start_date + """'
|
||||
AND f1.end_date > '""" + start_date + """'
|
||||
AND f1.status = 1
|
||||
AND c1.dt = '""" + start_date + """'
|
||||
AND c2.yn = 1
|
||||
GROUP BY
|
||||
f1.pin,
|
||||
c1.site_id,
|
||||
c2.site_name
|
||||
|
||||
```
|
||||
|
||||
这段SQL主要有两个问题:
|
||||
|
||||
1.cdm1和cdm2的条件写在LEFT JOIN之后,因为cdm1和cdm2是NULL补充表(**NULL 补充表**: 右表被称为 NULL 补充表,意味着它的存在是为了补充左表中可能缺失的值。即使在右表中没有与左表匹配的行,左表中的行仍然会被返回,右表的相关列会填充为 NULL),那么19和20行无法进行谓词下推,这会导致关联时fdm1和cdm1,cdm2先进行全表关联,再按照WHERE条件过滤分区。如果cdm1是每天全量的表,先关联全表所扫描的数据量可想而知是相当大的。
|
||||
|
||||
2.全表关联时没有对关联键进行NULL值处理,如果相关表的对应字段存在大量NULL值,会引起数据倾斜。
|
||||
|
||||
|
||||
|
||||
第一个问题涉及SQL的谓词下推,即写条件时,应该在不影响结果的情况下,尽量将过滤条件下推到join之前进行(“下推”指将条件推到靠近数据源的位置而不是SQL语句的方位)。谓词下推后,过滤条件在map端执行,减少了map端的输出,降低了数据在集群上传输的量,节约了集群的资源,也可以提升任务的性能。
|
||||
|
||||
对于常用的INNER JOIN和LEFT OUTER JOIN,谓词下推规则如下:
|
||||
|
||||
|
||||
|
||||
| | | |
|
||||
|---|---|---|
|
||||
||INNER JOIN|LEFT OUTER JOIN|
|
||||
||左表|右表|左表|右表|
|
||||
|ON条件|下推|下推|不下推|下推|
|
||||
|WHERE条件|下推|下推|下推|不下推|
|
||||
|
||||
|
||||
|
||||
如果使用上述示例的写法,主要关注的是LEFT OUTER JOIN时WHERE语句里的条件是否会引起谓词不下推。如果不想记这些看起来很复杂的规则怎么办?可以如下所示直接使用子查询:
|
||||
|
||||
```text
|
||||
// -------------------- Good Codes ------------------------
|
||||
|
||||
SELECT
|
||||
f1.pin,
|
||||
c1.site_id,
|
||||
c2.site_name
|
||||
FROM
|
||||
(
|
||||
SELECT erp, pin FROM fdm.fdm1 WHERE dp = 'ACTIVE' AND status = 1
|
||||
)
|
||||
f1
|
||||
LEFT JOIN
|
||||
(
|
||||
SELECT
|
||||
site_id,
|
||||
lower(account_number) AS account_number
|
||||
FROM
|
||||
cdm.cdm1
|
||||
WHERE
|
||||
dt = '""" + start_date + """'
|
||||
)
|
||||
c1
|
||||
ON
|
||||
f1.erp = c1.account_number
|
||||
LEFT JOIN
|
||||
(
|
||||
SELECT site_code, site_name FROM cdm.cdm2 WHERE yn = 1
|
||||
)
|
||||
c2
|
||||
ON
|
||||
c1.site_id = c2.site_code
|
||||
GROUP BY
|
||||
f1.pin,
|
||||
c1.site_id,
|
||||
c2.site_name
|
||||
|
||||
```
|
||||
|
||||
将原来WHERE语句里的各个条件下推到每个表的子查询中,可以先过滤掉不必要的行,提升关联效率。同时可读性大大提高,能清晰地看出每个来源表都取了哪些数据。还有一些其它细节,比如BDP平台的fdm拉链表,大部分业务场景下,都可以用dp='ACTIVE'代替start_date <= '""" + start_date + """' AND end_date > '""" + start_date + """'。同时注意列裁剪问题,尽量少用SELECT * FROM,只选取必要的列以减少内存开销。
|
||||
|
||||
|
||||
|
||||
### 2.去重难题
|
||||
|
||||
为了保证数据粒度的准确,几乎所有的SQL脚本编写时,都要考虑去重问题。常见的方法有:
|
||||
|
||||
•GROUP BY
|
||||
|
||||
•DISTINCT
|
||||
|
||||
•ROW_NUMBER开窗
|
||||
|
||||
•COLLECT_SET
|
||||
|
||||
|
||||
|
||||
1)GROUP BY 真的更好吗?
|
||||
|
||||
我们经常能在各种大数据技术分享中看到去重时推荐使用GROUP BY代替DISTINCT的观点。不可否认,数据量达到一定程度,去重字段枚举值也很复杂时,GROUP BY确实在性能上更优秀,同时可以避免数据倾斜。但具体情况具体分析,比如下面两段SQL涉及的业务场景:
|
||||
|
||||
```text
|
||||
// --------- Good Codes --------
|
||||
select
|
||||
count(distinct ulp_base_age)
|
||||
from
|
||||
app.app1
|
||||
where
|
||||
dt = sysdate(-1)
|
||||
|
||||
// --------- Bad Codes --------
|
||||
select
|
||||
count(ulp_base_age)
|
||||
from
|
||||
(
|
||||
select
|
||||
ulp_base_age
|
||||
from
|
||||
app.app1
|
||||
where
|
||||
dt = sysdate(-1)
|
||||
group by
|
||||
ulp_base_age
|
||||
) t
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
底表app1为零售用户画像表,数据量20亿。如果要统计用户年龄分段的枚举值数量,哪一段的执行效率更高呢?先说结论:看到20亿的数据量,可能大家会认为第二段使用GROUP BY的SQL执行更快,但经过HIVE查询多次测试,第一段的平均执行时长约3分钟,第二段的平均执行时长约5分钟。
|
||||
|
||||
|
||||
|
||||
为了解释这个现象,我们需要了解GROUP BY和DISTINCT的原理。首先,ulp_base_age的枚举值只有7个,从MapReduce角度来看,在Map阶段,每个Map会对ulp_base_age去重。由于ulp_base_age枚举值有限,因而每个Map得到的ulp_base_age也有限,最终得到reduce的数据量也就是map数量*ulp_base_age枚举值的个数,这实质上造成了资源的浪费。而DISTINCT命令会在内存中构建一个hashtable,查找去重的时间复杂度是O(1);GROUP BY在不同版本间变动比较大,有的版本会用构建hashtable的形式去重,有的版本会通过排序的方式, 排序的最优时间复杂度无法到O(1)。另外,第一种方式(GROUP BY)不仅需要进行 shuffle,还需要在每个分区中进行聚合操作,会消耗更多的磁盘网络I/O资源。
|
||||
|
||||
|
||||
|
||||
这告诉我们,不要过度优化代码,要考虑到数据集的具体情况!!
|
||||
|
||||
|
||||
|
||||
2)最大化利用分桶
|
||||
|
||||
在面对更复杂的数据集时,去重也需要更巧妙的方法。假设有一个数据量极大的页面埋点数据集,其部分数据如下所示:
|
||||
|
||||
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
|click_dt|pin|
|
||||
|2024-12-16|a|
|
||||
|2024-12-16|a|
|
||||
|2024-12-16|a|
|
||||
|2024-12-16|bb|
|
||||
|2024-12-16|bb|
|
||||
|2024-12-16|ccc|
|
||||
|2024-12-16|ccc|
|
||||
|2024-12-16|dddd|
|
||||
|2024-12-16|eee|
|
||||
|2024-12-16|eeee|
|
||||
|
||||
|
||||
|
||||
如果要统计天维度的UV,如果使用:
|
||||
|
||||
```text
|
||||
// -------------------- Bad Codes --------------------
|
||||
select
|
||||
click_dt,
|
||||
count(distinct pin)as uv
|
||||
from
|
||||
log_table
|
||||
group by
|
||||
click_dt;
|
||||
|
||||
```
|
||||
|
||||
那么假设有五个分桶,其使用情况会如下所示:
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
可以看到所有数据都被分配到了同一个桶里,其它桶都闲置,明显造成效率低下。优化代码如下:
|
||||
|
||||
```text
|
||||
// -------------------- Good Codes --------------------
|
||||
SELECT
|
||||
click_dt,
|
||||
size(collect_set(pin)) AS uv
|
||||
FROM
|
||||
(
|
||||
SELECT click_dt, pin FROM log_table GROUP BY click_dt, pin
|
||||
)
|
||||
tmp
|
||||
GROUP BY
|
||||
click_dt;
|
||||
|
||||
```
|
||||
|
||||
此时桶的使用情况如下:
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
此时充分利用了五个桶,实现了并行操作,单个桶的负担大大下降。但如果此时第二步的结果集太大,还是容易造成OOM的问题。面对海量数据集,代码还可以继续优化:
|
||||
|
||||
```text
|
||||
// ------------------- Even Better Codes -------------------
|
||||
SELECT
|
||||
click_dt,
|
||||
SUM(uv_tmp) AS uv
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
len_pin,
|
||||
click_dt,
|
||||
size(collect_set(pin)) AS uv_tmp
|
||||
FROM
|
||||
(
|
||||
SELECT click_dt, pin, LENGTH(pin) AS len_pin FROM log_table
|
||||
)
|
||||
log_table_tmp
|
||||
GROUP BY
|
||||
len_pin,
|
||||
click_dt
|
||||
)
|
||||
tmp
|
||||
GROUP BY
|
||||
click_dt
|
||||
|
||||
```
|
||||
|
||||
在聚合维度中加上pin的长度作为新维度,此时桶的使用如下:
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
此处使用pin的长度来预聚合pin,用一种分桶分步预聚合的方法,较为巧妙地把一个集合去重问题最终转化为相加问题,避开了单个jvm去重承受过大压力,面对海量数据集较为实用。具体开发场景中,也可以使用首末字母等有共性的属性来预聚合。
|
||||
|
||||
|
||||
|
||||
### 3.充分使用平台工具
|
||||
|
||||
由于曾经长期手动修改时间参数来回刷数据,操作繁琐的同时还容易出错。回刷涉及上下游表时,工作量更是巨大。直到我发现了平台的任务补录功能,并且可以通过Python和SQL的交互来实现灵活控制脚本里的时间参数来达到补录时的时间控制,才解决这个痛点。
|
||||
|
||||
|
||||
|
||||
比如在任务调度的py脚本里,可以利用sys.argv来控制时间参数。sys.argv的第一个元素是默认的,内容为脚本名称。而通过判断sys.argv的长度,可以在SQL内容之前使用如下Python代码来设置参数:
|
||||
|
||||
```text
|
||||
if len(sys.argv) == 1:
|
||||
# BDP不传参数的情况下使用,仅适用于BDP线上调度
|
||||
curday = ht.oneday(0)
|
||||
today = datetime.datetime.strptime(curday, '%Y-%m-%d')
|
||||
start_date = str((today + datetime.timedelta(days=-1)).strftime("%Y-%m-%d"))[0:10]
|
||||
end_date = str(today)[0:10]
|
||||
last31Day = start_date
|
||||
elif len(sys.argv) == 2:
|
||||
# BDP线上调度使用 配合BDP参数 ${fmt(add(NTIME(),-1,'day'),'yyyy-MM-dd')}
|
||||
end_date = str(datetime.datetime.strptime(sys.argv[1], "%Y-%m-%d"))[0:10]
|
||||
start_date = str(
|
||||
(datetime.datetime.strptime(end_date, "%Y-%m-%d")).replace(day=1))[0:10]
|
||||
last31Day = (datetime.datetime.strptime(end_date, "%Y-%m-%d") +
|
||||
datetime.timedelta(days=-30)).strftime("%Y-%m-%d")
|
||||
elif len(sys.argv) == 3:
|
||||
# 回刷使用,直接调用python脚本,并且需要传递两个日期参数,开始日期,结束日期
|
||||
start_date = str(datetime.datetime.strptime(sys.argv[1], "%Y-%m-%d"))[0:10]
|
||||
end_date = str(datetime.datetime.strptime(sys.argv[2], "%Y-%m-%d"))[0:10]
|
||||
else:
|
||||
print('parameter error')
|
||||
sys.exit(1)
|
||||
|
||||
```
|
||||
|
||||
同时需要在SQL内容后使用如下Python代码:
|
||||
|
||||
```text
|
||||
if (len(sys.argv) == 1) | (len(sys.argv) == 2):
|
||||
ht.exec_sql(
|
||||
schema_name='app',
|
||||
# 补数调度
|
||||
# sql=showsql.format(htYDay_B=start_date, htYDay=end_date),
|
||||
# 批量调度
|
||||
sql=showsql.format(htYDay_B=start_date, htYDay=end_date),
|
||||
table_name='app1',
|
||||
exec_engine='spark',
|
||||
spark_resource_level='high',
|
||||
retry_with_hive=False,
|
||||
spark_args=[
|
||||
'--conf spark.sql.hive.mergeFiles=true',
|
||||
'--conf spark.sql.adaptive.enabled=true',
|
||||
'--conf spark.sql.adaptive.repartition.enabled=true',
|
||||
'--conf spark.sql.adaptive.join.enabled=true',
|
||||
'--conf spark.sql.adaptive.skewedJoin.enabled=true',
|
||||
'--conf spark.hadoop.hive.exec.orc.split.strategy=ETL',
|
||||
'--conf spark.sql.shuffle.partitions=1200',
|
||||
'--conf spark.driver.maxResultSize=8g',
|
||||
'--conf spark.executor.memory=32g'
|
||||
])
|
||||
elif len(sys.argv) == 3:
|
||||
ht.exec_sql(
|
||||
schema_name='app',
|
||||
# 补数调度
|
||||
sql=showsql.format(htYDay_B=start_date, htYDay=end_date),
|
||||
# 批量调度
|
||||
# sql=showsql.format(htYDay_B=last31Day, htYDay=end_date),
|
||||
table_name='app1',
|
||||
exec_engine='spark',
|
||||
spark_resource_level='high',
|
||||
retry_with_hive=False,
|
||||
spark_args=[
|
||||
'--conf spark.sql.hive.mergeFiles=true',
|
||||
'--conf spark.sql.adaptive.enabled=true',
|
||||
'--conf spark.sql.adaptive.repartition.enabled=true',
|
||||
'--conf spark.sql.adaptive.join.enabled=true',
|
||||
'--conf spark.sql.adaptive.skewedJoin.enabled=true',
|
||||
'--conf spark.hadoop.hive.exec.orc.split.strategy=ETL',
|
||||
'--conf spark.sql.shuffle.partitions=1200',
|
||||
'--conf spark.driver.maxResultSize=8g',
|
||||
'--conf spark.executor.memory=32g'
|
||||
])
|
||||
else:
|
||||
print('parameter error')
|
||||
sys.exit(1)
|
||||
|
||||
```
|
||||
|
||||
IF的第一个分支的作用是线上调度任务不配置参数时,可以将昨天的日期和今天的日期赋值给htYDay_B和htYDay;第二个分支则是线上脚本配置${fmt(add(NTIME(),-1,'day'),'yyyy-MM-dd')}等参数时,可以根据该参数计算并赋值htYDay_B和htYDay;第三个分支是任务补录时使用,通过上传时间范围的开始时间和结束时间,直接赋值htYDay_B和htYDay,来控制脚本中取数时间范围。
|
||||
|
||||
|
||||
|
||||
数据开发工程师往往会每天面对海量的业务需求,理解业务需求、数据探查会花费掉研发人员大量的时间,而日常开发有时会忽略写出的代码是否优雅和高效。如果对SQL优化的原理有一定认知,积累了足够的经验,或许能做到编写代码时下意识地写出更优雅的SQL代码。
|
||||
@@ -0,0 +1,11 @@
|
||||
```text
|
||||
安装 earthtojake/text-to-cad
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
安装 https://github.com/tt-a1i/archify
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
最新版本Buildtools下载地址:
|
||||
|
||||
[Microsoft C++ 生成工具 - Visual Studio](https://visualstudio.microsoft.com/zh-hans/visual-cpp-build-tools/ "Microsoft C++ 生成工具 - Visual Studio")
|
||||
|
||||
[vs2022](https://so.csdn.net/so/search?q=vs2022&spm=1001.2101.3001.7020) build tools下载:
|
||||
|
||||
[https://aka.ms/vs/17/release/vs_BuildTools.exe](https://aka.ms/vs/17/release/vs_BuildTools.exe "https://aka.ms/vs/17/release/vs_BuildTools.exe")
|
||||
|
||||
[vs2019](https://so.csdn.net/so/search?q=vs2019&spm=1001.2101.3001.7020) build tools下载:
|
||||
|
||||
[https://aka.ms/vs/16/release/vs_BuildTools.exe](https://aka.ms/vs/17/release/vs_BuildTools.exe "https://aka.ms/vs/16/release/vs_BuildTools.exe")
|
||||
|
||||
vs2017 build tools下载:
|
||||
|
||||
[https://aka.ms/vs/15/release/vs_BuildTools.exe](https://aka.ms/vs/17/release/vs_BuildTools.exe "https://aka.ms/vs/15/release/vs_BuildTools.exe")
|
||||
@@ -0,0 +1,16 @@
|
||||
### 1. WinSxS:最大的头,也是最多人不敢碰的
|
||||
|
||||
WinSxS 是 Windows 组件存储,动辄十几 GB,**绝对不能手动删**。正确姿势是用微软官方的 DISM 工具,而且要分两步:
|
||||
|
||||
```text
|
||||
:: 第一步:只读分析(不改任何东西),看微软自己怎么说
|
||||
Dism /Online /Cleanup-Image /AnalyzeComponentStore
|
||||
|
||||
:: 第二步:确认"推荐清理=是"之后,再执行
|
||||
Dism /Online /Cleanup-Image /StartComponentCleanup
|
||||
```
|
||||
|
||||
我机器上分析报告显示"组件存储备份 19.24GB,推荐清理:是",执行后释放 11.4GB。两个细节:
|
||||
|
||||
- 进度跑到 100% 后 DISM/TiWorker 还会继续工作很久,**进程真正退出才释放空间**,别急着关;
|
||||
- `/StartComponentCleanup`**不能加 `/NoRestart` 参数**——这个参数在这里是无效参数,会直接报错退出。我们后来在软件里就栽过这个跟头(后文有讲)。
|
||||
@@ -0,0 +1,46 @@
|
||||
Microsoft.Coreutils 包含了 **超过 75 个**原生的类 Unix 命令行工具。
|
||||
该工具包的每个命令都支持标准的 `--help` 参数来查看完整用法。
|
||||
|
||||
这里按功能为你整理的常用命令列表:
|
||||
|
||||
| 功能分类 | 命令 (部分示例) | 描述 |
|
||||
| :--- | :--- | :--- |
|
||||
| **📁 文件与目录管理** | `ls`, `cp`, `mv`, `rm` | 列出、复制、移动/重命名、删除文件或目录 |
|
||||
| | `mkdir`, `rmdir`, `touch` | 创建、删除目录,以及创建空文件或更新时间戳 |
|
||||
| | `ln` | 在文件之间创建硬链接或软链接 |
|
||||
| | `pwd` | 显示当前工作目录的完整路径 |
|
||||
| **📝 文本处理** | `cat`, `grep` | 连接文件并打印到标准输出,以及在文件中搜索匹配模式 |
|
||||
| | `head`, `tail` | 输出文件的开头或结尾部分 |
|
||||
| | `cut`, `sort`, `uniq` | 切割、排序和去重文本行 |
|
||||
| | `wc` | 统计文件的字节数、字数、行数 |
|
||||
| | `diff` | 逐行比较两个文件的差异(来自 `uutils/coreutils`) |
|
||||
| **⚙️ 系统查询与信息** | `date`, `hostname` | 显示或设置系统日期/时间,以及主机名 |
|
||||
| | `df`, `du` | 显示磁盘空间使用情况和文件/目录的磁盘占用 |
|
||||
| | `echo`, `printenv` | 显示一行文本,或打印环境变量值 |
|
||||
| | `id`, `whoami` | 显示用户身份信息 |
|
||||
| | `uptime` | 显示系统已运行时间 |
|
||||
| **🔐 校验与编码** | `md5sum`, `sha256sum` | 计算或校验文件的 MD5 或 SHA256 校验值 |
|
||||
| | `base64`, `base32` | 以 base64 或 base32 方式编码或解码数据 |
|
||||
| **🔍 查找工具** | `find`, `xargs` | 在目录树中搜索文件,以及从标准输入构建和执行命令行 |
|
||||
|
||||
> 此外,`sort` 和 `find` 这两个命令内部还集成了对 DOS 风格 `/switch` 参数的支持,以最大限度地兼容旧有的 CMD 脚本。
|
||||
|
||||
### 🛠️ 功能是完整的,但不是所有的
|
||||
这是一个约 5MB 的轻量级工具包,采用了 **"单二进制 (multi-call binary)"** 的架构。安装后系统通过 `NTFS 硬链接 (NTFS hardlinks)` 创建 `ls.exe`、`cp.exe` 等命令入口,它们都指向同一个 `coreutils.exe` 主程序。
|
||||
|
||||
不过,有几个原因**导致部分命令被排除**:
|
||||
|
||||
1. **与系统内置命令冲突**:为避免混淆,以下命令未随包提供。
|
||||
* `dir`, `more`, `expand`, `kill`, `timeout`, `whoami`, `paste`
|
||||
|
||||
2. **依赖缺失的 POSIX 功能**:因 Windows 不完全支持某些 POSIX 特性,以下命令未包含。
|
||||
* `chmod`, `chown`, `chroot`, `nohup`, `tty`, `id`, `stty`, `who`
|
||||
|
||||
### 💡 命令冲突与解决方法
|
||||
这些命令与系统原有的命令在 CMD 和 PowerShell 中可能存在冲突。例如,PowerShell 中的 `cat`、`ls`、`cp` 等命令本质上是内部别名,会优先于微软 Coreutils 版本被执行。
|
||||
|
||||
为了解决冲突,你可以:
|
||||
* **使用完整路径**:直接调用 `C:\Program Files\coreutils\cat.exe` 来执行。
|
||||
* **移除别名**:删除 PowerShell 配置文件中相关的别名,使 Coreutils 命令成为默认选项。
|
||||
|
||||
你有遇到过命令冲突的具体情况吗?如果想了解更多排查或设置默认命令的方法,随时告诉我~
|
||||
@@ -0,0 +1,138 @@
|
||||
## 什么是 Sysinternals Suite?
|
||||
|
||||
Sysinternals Suite 是微软官方推出的一套免费 Windows 高级诊断与故障排查工具集。它由 **70 多个**轻量级、免安装的工具组成,专为系统管理员和高级用户设计,提供对 Windows 操作系统内部底层行为的深度洞察。
|
||||
|
||||
它的核心价值在于能精准回答两大问题:**"是谁在干坏事?"** 和 **"到底发生了什么?"**。如果说任务管理器是"听诊器",那 Sysinternals 就是为 Windows 做的"CT",能彻底拆解进程、句柄、内存、注册表等底层运作。
|
||||
|
||||
---
|
||||
|
||||
## 核心工具列表
|
||||
|
||||
Sysinternals Suite 的工具可按功能划分为五大类。下表列出其中最具代表性的核心工具。
|
||||
|
||||
### 🔍 进程与系统监控
|
||||
|
||||
| 工具 | 说明 |
|
||||
| :--- | :--- |
|
||||
| **Process Explorer** (`procexp.exe`) | 增强型任务管理器,以树状结构显示进程间关系、权限、句柄和 DLL 详情。 |
|
||||
| **Process Monitor** (`procmon.exe`) | 实时监控文件系统、注册表和进程线程活动,是排障和抓毒的必备利器。 |
|
||||
| **TCPView** (`tcpview.exe`) | 图形化监控网络连接和端口占用,替代命令行 `netstat`。 |
|
||||
| **RAMMap** (`rammap.exe`) | 深度分析物理内存使用分布,用于排查内存占用过高和内存泄漏。 |
|
||||
|
||||
### 🚀 启动与权限管理
|
||||
|
||||
| 工具 | 说明 |
|
||||
| :--- | :--- |
|
||||
| **Autoruns** (`autoruns.exe`) | 全面管理开机自启动项,远超自带 `msconfig`。 |
|
||||
| **PsExec** (`psexec.exe`) | 在远程系统上执行进程,常用于自动化运维。 |
|
||||
| **AccessChk** (`accesschk.exe`) | 查看文件、注册表、服务等资源的详细访问权限。 |
|
||||
| **Autologon** (`autologon.exe`) | 配置 Windows 自动登录,跳过密码输入。 |
|
||||
|
||||
### 💾 磁盘与文件分析
|
||||
|
||||
| 工具 | 说明 |
|
||||
| :--- | :--- |
|
||||
| **Disk2vhd** (`disk2vhd.exe`) | 将物理磁盘转换为 Hyper-V 虚拟硬盘(VHD)。 |
|
||||
| **Disk Usage (DU)** (`du.exe`) | 命令行查看目录磁盘占用大小。 |
|
||||
| **SDelete** (`sdelete.exe`) | 安全覆盖删除敏感文件,防止数据恢复。 |
|
||||
| **Handle** (`handle.exe`) | 查找特定文件被哪个进程占用。 |
|
||||
|
||||
### 🧠 系统信息与故障排查
|
||||
|
||||
| 工具 | 说明 |
|
||||
| :--- | :--- |
|
||||
| **BgInfo** (`bginfo.exe`) | 桌面背景展示主机名、IP、内存等关键系统信息。 |
|
||||
| **DebugView** (`dbgview.exe`) | 捕获应用程序和内核驱动输出的调试信息。 |
|
||||
| **BlueScreen** (`bluescreen.scr`) | 模拟蓝屏的屏保程序(趣味工具)。 |
|
||||
| **NotMyFault** (`notmyfault.exe`) | 手动制造系统崩溃,用于测试故障恢复机制。 |
|
||||
|
||||
### 🎯 命令行与开发辅助
|
||||
|
||||
| 工具 | 说明 |
|
||||
| :--- | :--- |
|
||||
| **PsTools** (`pstools.exe`) | 命令行管理套件(含 PsList、PsKill、PsShutdown 等)。 |
|
||||
| **LiveKd** (`livekd.exe`) | 在运行中的系统上执行内核调试器。 |
|
||||
| **Strings** (`strings.exe`) | 提取二进制文件中的 ASCII 或 Unicode 字符串。 |
|
||||
| **ZoomIt** (`zoomit.exe`) | 屏幕缩放和标注工具,专为演示和会议设计。 |
|
||||
|
||||
> 完整工具列表及功能请参考微软官网 [Sysinternals Utilities Index](https://learn.microsoft.com/en-us/sysinternals/downloads/)。
|
||||
|
||||
---
|
||||
|
||||
## 详细使用指南与场景示例
|
||||
|
||||
### Process Explorer(进程资源管理器)
|
||||
|
||||
**场景**:电脑突然卡顿,怀疑某个进程异常占用 CPU 或内存。
|
||||
|
||||
**操作步骤**:
|
||||
1. 以管理员身份运行 `procexp.exe`。
|
||||
2. 顶部窗格显示所有活跃进程,可按 CPU 或内存列排序找出占用最高的进程。
|
||||
3. 双击该进程查看详细信息:
|
||||
- 查看其打开的句柄(Handle,指进程所引用的系统资源,如打开的文件、注册表键、互斥体、线程等);
|
||||
- 切换到"Threads"选项卡,查看具体是哪个线程导致占用高。
|
||||
4. 右键菜单可"Kill Process"直接结束,或"Suspend"临时挂起观察影响。
|
||||
|
||||
> 可通过菜单 `Options → Replace Task Manager` 替换系统自带任务管理器,日常更方便调用。
|
||||
|
||||
### Autoruns(启动项管理)
|
||||
|
||||
**场景**:开机速度极慢,怀疑有病毒或恶意软件开机自启。
|
||||
|
||||
**操作步骤**:
|
||||
1. 运行 `autoruns.exe`。
|
||||
2. 工具栏勾选 **"Hide Microsoft Entries"**,滤除微软官方条目,仅显示第三方自启项,快速定位可疑程序。
|
||||
3. 按类别选项卡查看不同自启类型(Logon、Services、Scheduled Tasks 等)。
|
||||
4. 取消勾选可疑条目即可**禁用**;选中后点击"Delete"可**彻底删除**。
|
||||
5. 右键选择"Jump to Entry"或"Jump to Image",直接定位到对应的注册表路径或文件位置。
|
||||
|
||||
> 禁用前建议通过 `File → Save` 保存当前配置备份,便于误操作时恢复。
|
||||
|
||||
### Process Monitor(进程监视器)
|
||||
|
||||
**场景**:某个软件安装失败或运行报错,但不清楚失败原因。
|
||||
|
||||
**操作步骤**:
|
||||
1. 管理员运行 `procmon.exe`,启动后立刻开始捕获系统事件。
|
||||
2. **关闭不需要的应用**,减少无关日志干扰。
|
||||
3. 短暂复现问题后,停止捕获(`File → Capture Events` 或 `Ctrl+E`)。
|
||||
4. 使用 **过滤器** 缩小搜索范围,常用的过滤条件包括:
|
||||
- `Process Name is 某进程.exe then Include`
|
||||
- `Operation contains File (或 Reg) then Include`
|
||||
- `Result is ACCESS DENIED (或 NAME NOT FOUND) then Include`
|
||||
5. 分析结果:重点关注 `Result` 列为 `ACCESS DENIED`、`NAME NOT FOUND`、`BUFFER OVERFLOW` 的红色行,往往是问题根源。
|
||||
6. 可通过 `File → Save` 导出日志(建议选择 PML 格式)供后续分析或转交技术支持。
|
||||
|
||||
### PsTools 套件
|
||||
|
||||
**场景**:远程管理多台服务器,执行进程、服务或关机操作。
|
||||
|
||||
**常用命令**(均需管理员权限,部分需安装 `PsExec` 服务):
|
||||
- 远程查看进程列表:`psexec \\192.168.1.100 -u 用户名 -p 密码 tasklist`。
|
||||
- 远程强制重启:`psshutdown \\192.168.1.100 -r -f -t 0`。
|
||||
- 结束远程进程:`pskill \\192.168.1.100 1234`(1234 为目标进程的 PID)。
|
||||
- 列出远程进程信息:`pslist \\192.168.1.100 -d`(显示线程详情)。
|
||||
|
||||
> 使用时务必遵守安全规范和授权,避免在未经允许的机器上执行。
|
||||
|
||||
### BGInfo(桌面背景信息显示)
|
||||
|
||||
**场景**:运维多台服务器时,希望一眼看到主机名、IP 地址、系统版本等关键信息。
|
||||
|
||||
**操作步骤**:
|
||||
1. 运行 `bginfo.exe`,默认显示 IP、计算机名等基本信息。
|
||||
2. 点击菜单 `File → New` 自定义显示内容,可自由添加:
|
||||
- 磁盘使用率(`Drive C: Free %`);
|
||||
- 内存总量(`Total Physical Memory in MB`);
|
||||
- 当前登录用户(`Logged on User`)。
|
||||
3. 配置完成后 `File → Save As…` 保存为 `.bgi` 配置文件。
|
||||
4. `File → Apply` 即可将信息刷新到当前桌面背景。
|
||||
5. 需要开机自动应用时:将 BGInfo 快捷方式放入 **启动文件夹**(`shell:startup`),并指定配置文件路径:`"C:\Tools\bginfo.exe" "C:\Config\myconfig.bgi" /timer:0`,实现开机自动静默应用。
|
||||
|
||||
### 系统异常转储与调试场景
|
||||
|
||||
| 场景 | 推荐工具 | 操作关键点 |
|
||||
| :--- | :--- | :--- |
|
||||
| 进程崩溃需抓取内存状态 | **ProcDump** (`procdump.exe`) | 监控高 CPU 占用或崩溃:`procdump -c 80 -s 5 -n 3 -ma myapp.exe`(CPU 连续 5 秒超过 80% 时生成完整 dump)。 |
|
||||
| 系统反复蓝屏但无法复现 | **NotMyFault** (`notmyfault.exe`) | 运行后选择崩溃类型(高 IRQL 故障、缓冲区溢出等),手动触发蓝屏,用于验证 crash dump 配置是否正确。 |
|
||||
| 分析蓝屏 DUMP 文件 | **LiveKd** (`livekd.exe`) | 无需重启即可用 WinDbg 调试运行中的内核,或离线分析 DUMP:`livekd -y "SRV*C:\Symbols*https://msdl.microsoft.com/download/symbols" -o`。 |
|
||||
@@ -0,0 +1,249 @@
|
||||
我来详细介绍一下如何将铜锁(Tongsuo)作为 OpenSSL 替代与 Nginx 进行编译的完整过程。
|
||||
|
||||
## 1. 环境准备
|
||||
|
||||
### 安装编译依赖
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential libpcre3 libpcre3-dev zlib1g-dev
|
||||
|
||||
# CentOS/RHEL
|
||||
sudo yum install -y gcc make pcre-devel zlib-devel
|
||||
```
|
||||
|
||||
## 2. 编译铜锁(Tongsuo)
|
||||
|
||||
### 下载铜锁源代码
|
||||
```bash
|
||||
# 从 GitHub 下载最新版本
|
||||
git clone https://github.com/Tongsuo-Project/Tongsuo.git
|
||||
cd Tongsuo
|
||||
|
||||
# 或者下载特定版本(如 8.3.3)
|
||||
wget https://github.com/Tongsuo-Project/Tongsuo/archive/refs/tags/8.3.3.tar.gz
|
||||
tar -xzf 8.3.3.tar.gz
|
||||
cd Tongsuo-8.3.3
|
||||
```
|
||||
|
||||
### 编译安装铜锁
|
||||
```bash
|
||||
# 配置编译选项
|
||||
./config --prefix=/usr/local/tongsuo \
|
||||
--openssldir=/usr/local/tongsuo/ssl \
|
||||
enable-ntls \
|
||||
enable-weak-ssl-ciphers
|
||||
|
||||
# 编译并安装
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
|
||||
# 配置动态库路径
|
||||
echo "/usr/local/tongsuo/lib" | sudo tee /etc/ld.so.conf.d/tongsuo.conf
|
||||
sudo ldconfig
|
||||
|
||||
# 验证安装
|
||||
/usr/local/tongsuo/bin/openssl version
|
||||
```
|
||||
|
||||
## 3. 编译 Nginx 使用铜锁
|
||||
|
||||
### 下载 Nginx 源代码
|
||||
```bash
|
||||
# 下载最新稳定版 Nginx
|
||||
wget http://nginx.org/download/nginx-1.24.0.tar.gz
|
||||
tar -xzf nginx-1.24.0.tar.gz
|
||||
cd nginx-1.24.0
|
||||
```
|
||||
|
||||
### 配置 Nginx 编译参数
|
||||
```bash
|
||||
# 基本配置
|
||||
./configure \
|
||||
--prefix=/usr/local/nginx \
|
||||
--with-http_ssl_module \
|
||||
--with-http_v2_module \
|
||||
--with-http_stub_status_module \
|
||||
--with-stream \
|
||||
--with-stream_ssl_module \
|
||||
--with-openssl=/usr/local/tongsuo \
|
||||
--with-openssl-opt="enable-ntls enable-weak-ssl-ciphers" \
|
||||
--with-cc-opt="-I/usr/local/tongsuo/include" \
|
||||
--with-ld-opt="-L/usr/local/tongsuo/lib"
|
||||
```
|
||||
|
||||
### 编译并安装
|
||||
```bash
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
```
|
||||
|
||||
## 4. NTLS(国密双证书)配置示例
|
||||
|
||||
### Nginx 配置文件
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name example.com;
|
||||
|
||||
# 国际标准 TLS 证书(RSA/ECC)
|
||||
ssl_certificate /path/to/rsa.crt;
|
||||
ssl_certificate_key /path/to/rsa.key;
|
||||
|
||||
# 国密证书
|
||||
ssl_certificate /path/to/sm2_sign.crt;
|
||||
ssl_certificate_key /path/to/sm2_sign.key;
|
||||
ssl_enc_certificate /path/to/sm2_enc.crt;
|
||||
ssl_enc_certificate_key /path/to/sm2_enc.key;
|
||||
|
||||
# 启用 NTLS
|
||||
ssl_enable_ntls on;
|
||||
|
||||
# 密码套件配置
|
||||
ssl_ciphers "ECC-SM2-WITH-SM4-SM3:ECDHE-SM2-WITH-SM4-SM3";
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
|
||||
|
||||
# 其他配置...
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 编译脚本示例
|
||||
|
||||
### 完整的自动化脚本
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# nginx-tongsuo-build.sh
|
||||
|
||||
set -e
|
||||
|
||||
# 版本配置
|
||||
TONGSUO_VERSION="8.3.3"
|
||||
NGINX_VERSION="1.24.0"
|
||||
INSTALL_DIR="/usr/local"
|
||||
|
||||
# 下载并编译铜锁
|
||||
echo "正在下载铜锁 ${TONGSUO_VERSION}..."
|
||||
cd /tmp
|
||||
wget -q https://github.com/Tongsuo-Project/Tongsuo/archive/refs/tags/${TONGSUO_VERSION}.tar.gz
|
||||
tar -xzf ${TONGSUO_VERSION}.tar.gz
|
||||
cd Tongsuo-${TONGSUO_VERSION}
|
||||
|
||||
echo "编译铜锁..."
|
||||
./config --prefix=${INSTALL_DIR}/tongsuo \
|
||||
--openssldir=${INSTALL_DIR}/tongsuo/ssl \
|
||||
enable-ntls \
|
||||
enable-weak-ssl-ciphers \
|
||||
shared
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
sudo ldconfig
|
||||
|
||||
# 下载并编译 Nginx
|
||||
echo "正在下载 Nginx ${NGINX_VERSION}..."
|
||||
cd /tmp
|
||||
wget -q http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz
|
||||
tar -xzf nginx-${NGINX_VERSION}.tar.gz
|
||||
cd nginx-${NGINX_VERSION}
|
||||
|
||||
echo "配置 Nginx..."
|
||||
./configure \
|
||||
--prefix=${INSTALL_DIR}/nginx \
|
||||
--with-http_ssl_module \
|
||||
--with-http_v2_module \
|
||||
--with-http_stub_status_module \
|
||||
--with-stream \
|
||||
--with-stream_ssl_module \
|
||||
--with-openssl=${INSTALL_DIR}/tongsuo \
|
||||
--with-openssl-opt="enable-ntls" \
|
||||
--with-cc-opt="-I${INSTALL_DIR}/tongsuo/include" \
|
||||
--with-ld-opt="-L${INSTALL_DIR}/tongsuo/lib"
|
||||
|
||||
echo "编译 Nginx..."
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
|
||||
# 创建服务文件
|
||||
echo "创建 systemd 服务..."
|
||||
cat << EOF | sudo tee /etc/systemd/system/nginx.service
|
||||
[Unit]
|
||||
Description=NGINX with Tongsuo
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
PIDFile=/usr/local/nginx/logs/nginx.pid
|
||||
ExecStartPre=/usr/local/nginx/sbin/nginx -t
|
||||
ExecStart=/usr/local/nginx/sbin/nginx
|
||||
ExecReload=/usr/local/nginx/sbin/nginx -s reload
|
||||
ExecStop=/usr/local/nginx/sbin/nginx -s quit
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
echo "安装完成!"
|
||||
echo "Nginx 路径: ${INSTALL_DIR}/nginx/sbin/nginx"
|
||||
echo "铜锁路径: ${INSTALL_DIR}/tongsuo/bin/openssl"
|
||||
```
|
||||
|
||||
## 6. Docker 编译示例
|
||||
|
||||
```dockerfile
|
||||
# Dockerfile
|
||||
FROM ubuntu:20.04 AS builder
|
||||
|
||||
# 安装依赖
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
build-essential libpcre3-dev zlib1g-dev wget git
|
||||
|
||||
# 编译铜锁
|
||||
RUN git clone https://github.com/Tongsuo-Project/Tongsuo.git /tongsuo && \
|
||||
cd /tongsuo && \
|
||||
./config --prefix=/opt/tongsuo enable-ntls && \
|
||||
make -j$(nproc) && \
|
||||
make install
|
||||
|
||||
# 编译 Nginx
|
||||
RUN wget http://nginx.org/download/nginx-1.24.0.tar.gz && \
|
||||
tar -xzf nginx-1.24.0.tar.gz && \
|
||||
cd nginx-1.24.0 && \
|
||||
./configure \
|
||||
--prefix=/opt/nginx \
|
||||
--with-http_ssl_module \
|
||||
--with-stream_ssl_module \
|
||||
--with-openssl=/opt/tongsuo \
|
||||
--with-openssl-opt="enable-ntls" && \
|
||||
make -j$(nproc) && \
|
||||
make install
|
||||
|
||||
# 运行时镜像
|
||||
FROM ubuntu:20.04
|
||||
COPY --from=builder /opt /opt
|
||||
RUN ldconfig
|
||||
CMD ["/opt/nginx/sbin/nginx", "-g", "daemon off;"]
|
||||
```
|
||||
|
||||
## 7. 验证和测试
|
||||
|
||||
```bash
|
||||
# 验证 Nginx 版本
|
||||
/usr/local/nginx/sbin/nginx -V
|
||||
|
||||
# 查看是否包含铜锁支持
|
||||
/usr/local/nginx/sbin/nginx -V 2>&1 | grep -i tongsuo
|
||||
|
||||
# 测试 NTLS 连接
|
||||
openssl s_client -connect localhost:443 -enable_ntls -ntls
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **版本兼容性**:确保铜锁和 Nginx 版本兼容
|
||||
2. **功能选择**:根据需求选择编译的模块
|
||||
3. **生产环境**:建议使用静态链接避免依赖问题
|
||||
4. **证书管理**:国密证书需要专门的 CA 机构签发
|
||||
5. **性能优化**:可以根据服务器硬件调整编译参数
|
||||
|
||||
这样编译出来的 Nginx 就支持国密算法和 NTLS 协议了。
|
||||
@@ -0,0 +1,4 @@
|
||||
python3 -c "import ctypes"
|
||||
|
||||
|
||||
你当前遇到的 `ModuleNotFoundError: No module named '_ctypes'` 错误,
|
||||
@@ -0,0 +1 @@
|
||||
ncpa.cpl
|
||||
@@ -0,0 +1 @@
|
||||
%USERPROFILE%\.cargo\bin
|
||||
@@ -0,0 +1,19 @@
|
||||
Windows 自带的 `netsh` 命令是功能强大的网络配置工具,支持 IPv4/IPv6 端口转发,操作无需安装额外软件,适合快速配置。
|
||||
**主要功能**:
|
||||
|
||||
1. **添加转发规则**
|
||||
```
|
||||
netsh interface portproxy add v4tov4 listenport=本地端口 connectaddress=目标IP connectport=目标端口
|
||||
```
|
||||
|
||||
```
|
||||
#示例:将本地8080转发到192.168.1.100的80端口
|
||||
netsh interface portproxy add v4tov4 listenport=8080 connectaddress=192.168.1.100 connectport=80
|
||||
```
|
||||
|
||||
|
||||
2. **查看规则**:`netsh interface portproxy show all`
|
||||
|
||||
3. **删除规则**:`netsh interface portproxy delete v4tov4 listenport=端口`
|
||||
|
||||
4. **重置所有规则**:`netsh interface portproxy reset`
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
1. 首先按下[键盘](https://product.pconline.com.cn/keyboard/)“win+r”打开运行。
|
||||
|
||||
2. 接着输入“regedit”并回车打开 注册表 。
|
||||
|
||||
3. 打开后,定位到“计算机\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders”位置。
|
||||
|
||||
4. 然后双击打开其中的“appdata”,将数值数据更改为我们想要移动到的d盘位置。
|
||||
|
||||
5. 再来到“HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders”位置,同样修改appdata文件夹路径。
|
||||
|
||||
6. 修改完成后,只需要来到c盘,将appdata文件夹剪切粘贴到之前设置的d盘位置就可以了。(不放心的话可以先备份一下)
|
||||
@@ -0,0 +1,2 @@
|
||||
C:\Users\occul\AppData\Roaming\Microsoft\Windows\Start Menu\Programs
|
||||
%USERPROFILE%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs
|
||||
@@ -0,0 +1,47 @@
|
||||
### 1、卸载 IDEA 程序
|
||||
|
||||
点击屏幕左下角 **Windows 图标** -> **设置**:
|
||||
|
||||
点击 Windows 设置按钮
|
||||
|
||||
点击**应用**:
|
||||
|
||||
点击应用按钮
|
||||
|
||||
在应用中找到 IDEA, 单击它会出现卸载按钮,点击开始卸载:
|
||||
|
||||
卸载 IDEA
|
||||
|
||||
勾选第一栏 `Delete IntelliJ IDEA 2022.2 caches and local history`,表示同时删除 IDEA 本地缓存以及历史:
|
||||
|
||||
开始删除 IDEA
|
||||
|
||||
> `Delete IntellJ IDEA 2022.2 settings and installed plugins` : 删除 IDEA 相关设置,如字体大小、主题等等,以及删除已安装的插件。
|
||||
>
|
||||
> **此选项可不用勾选。**
|
||||
|
||||
### 2、注册表清理
|
||||
|
||||
每个程序安装后,在注册表中都会留下相关信息。如何清理呢?
|
||||
|
||||
按住快捷键 `windos + R`, 然后输入 `regedit` 回车调出注册表。
|
||||
|
||||
依次点击菜单 `计算机\HKEY_CURRENT_USER\SOFTWARE\JavaSoft\Prefs\jetbrains`, 然后右键删除。
|
||||
|
||||
清理 IDEA 注册表信息
|
||||
|
||||
### 3、残留清理
|
||||
|
||||
最后,还有几个地方的缓存数据需要删除:
|
||||
|
||||
```
|
||||
C:\用户\${用户名称}\IdeaProjects\
|
||||
# 如果你想删除 IDEA 相关,则只需要删除 JetBrains 目录下包含 IDEA 的文件夹即可
|
||||
C:\用户\${用户名称}\AppData\Roaming\JetBrains
|
||||
# 如果你想删除 IDEA 相关,则只需要删除 JetBrains 目录下包含 IDEA 的文件夹即可
|
||||
C:\用户\${用户名称}\AppData\Local\JetBrains
|
||||
C:\用户\公用\.jetbrains
|
||||
# 如果你想删除 IDEA 相关,则只需要删除 JetBrains 目录下包含 IDEA 的文件夹即可
|
||||
C:\Program Files\JetBrains
|
||||
C:\ProgramData\Microsoft\Windows\Start Menu\Programs\JetBrains\
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
## 个人卫生
|
||||
- 洗头(每周三次)
|
||||
- 洗脚(每周三次)
|
||||
|
||||
## 厨房卫生
|
||||
- 厨房台面擦干(每次)
|
||||
- 餐桌收拾完擦干(每次)
|
||||
- 抽油烟机清洗(每周一次)
|
||||
|
||||
## 地面卫生
|
||||
- 主卧地面(每周三次)
|
||||
- 客厅地面(每周三次)
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
- Task Explorer 更详细的任务管理器
|
||||
- 磁盘体检工具 CrystalDiskInfo
|
||||
- Windows 配置工具 RyTuneX、ZyperWin++、Win11 简单配置工具
|
||||
hibit uninstaller - 卸载工具
|
||||
nana是7z的一个包装
|
||||
图片查看XnView
|
||||
PixPin 国产免费截图软件
|
||||
|
||||
[Upscayl](https://zhida.zhihu.com/search?content_id=741460276&content_type=Answer&match_order=1&q=Upscayl&zhida_source=entity), 开源图片高清化软件,工作利器,低分辨率图秒变高分辨率图!
|
||||
|
||||
[deskflow](https://zhida.zhihu.com/search?content_id=741460276&content_type=Answer&match_order=1&q=deskflow&zhida_source=entity),开源免费,纯软件解决方案。支持一套键鼠在不同的电脑操作系统中无缝漫游。
|
||||
|
||||
tradingview,一款投资分析软件。能看全球市场的投资品种,外汇,股票,期权,期货,虚拟货币等等,所有你能想到的品种。能挂载上千种技术分析指标,支持量化交易回测,社区讨论等等。全球公认的,市场分析和交易软件中的No.1。但是,缺点是,在国内用要搭配魔法。
|
||||
|
||||
[UU远程](https://zhida.zhihu.com/search?content_id=741460276&content_type=Answer&match_order=1&q=UU%E8%BF%9C%E7%A8%8B&zhida_source=entity),任意地点连回家里的电脑主机,无论工作还是游戏,都是很流畅且画面质量高,完全免费,完爆其他的各种收费远程软件!
|
||||
|
||||
[Potplayer](https://zhida.zhihu.com/search?content_id=741460276&content_type=Answer&match_order=1&q=Potplayer&zhida_source=entity),影片播放神器,甚至能搞定无字幕视频的在线同步翻译,和杜比视界文件的正常播放!
|
||||
|
||||
MPV 播放器 [https://mpv.io/](https://mpv.io/)
|
||||
|
||||
[Notepad 3](https://zhida.zhihu.com/search?content_id=399167814&content_type=Answer&match_order=1&q=Notepad+3&zhida_source=entity),支持正则表达式查找和替换、自动高亮显示等,临时编辑文本文件,VSCode还是有点重。
|
||||
|
||||
U 盘工具 [Ventoy](https://www.osssr.com/856.html)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user