0%

架构师学习 第3篇

设计模式 (Design Patterns)

  • 核心概念与原则

    • 定义:一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
    • 目的:提高代码的可重用性,使代码更容易被他人理解,保证代码可靠性。
    • 核心原则
      • 针对接口编程:客户无须知道对象的特定类型,只需知道对象有客户所期望的接口。
      • 优先使用对象组合:优先使用对象组合(黑箱复用),而不是类继承(白箱复用)。
    • MVC模式案例:Smalltalk中的MVC(模型/视图/控制器)体现了观察者、组合和策略模式的综合应用。
  • 一、创建型模式 (Creational Patterns)

    • 关注点:对象的创建过程,将对象的创建与使用分离。
    • 1. 工厂模式 (Factory)
      • **简单工厂 (Simple Factory)**:(非GoF标准,但常用) 定义一个用于创建对象的接口,由工厂类决定创建哪一种产品实例(如“司机开车”的例子)。
      • **工厂方法 (Factory Method)**:定义创建对象的接口,让子类决定实例化哪一个类。使实例化延迟到子类。
      • **抽象工厂 (Abstract Factory)**:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
    • 2. 单例模式 (Singleton)
      • 定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
      • 实现方式
        • 饿汉式:类加载时初始化。
        • 懒汉式:第一次使用时初始化(需注意线程同步)。
        • 注册表方式:通过HashMap维护实例。
    • 3. 建造者模式 (Builder)
      • 定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
      • 角色:指导者 (Director)、抽象建造者 (Builder)、具体建造者 (ConcreteBuilder)、产品 (Product)。
    • 4. 原型模式 (Prototype)
      • 定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
      • 特点:利用Java的clone()方法,分为深克隆和浅克隆。
  • 二、结构型模式 (Structural Patterns)

    • 关注点:类或对象的组合,形成更大的结构。
    • 1. 适配器模式 (Adapter)
      • 定义:将一个类的接口转换成客户希望的另外一个接口,解决接口不兼容问题。
      • 分类:类适配器(继承)、对象适配器(组合)。
    • 2. 桥接模式 (Bridge)
      • 定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
      • 应用:如Java AWT框架,将组件与其在不同操作系统下的实现分离。
    • 3. 组合模式 (Composite)
      • 定义:将对象组合成树形结构以表示“部分-整体”的层次结构,使用户对单个对象和组合对象的使用具有一致性。
      • 应用:文件系统、JUnit中的TestCase与TestSuite。
    • 4. 装饰模式 (Decorator)
      • 定义:动态地给一个对象添加一些额外的职责。比生成子类更为灵活。
      • 特点:透明围栏,客户分不出组件和装饰后的组件的区别。
    • 5. 外观/门面模式 (Facade)
      • 定义:为子系统中的一组接口提供一个一致的界面,定义高层接口使子系统更易使用。
      • 目的:降低客户与子系统之间的耦合。
    • 6. 享元模式 (Flyweight)
      • 定义:运用共享技术有效地支持大量细粒度的对象。
      • 关键:区分内蕴状态(共享)和外蕴状态(不共享)。
    • 7. 代理模式 (Proxy)
      • 定义:为其他对象提供一种代理以控制对这个对象的访问。
      • 类型:远程代理、虚拟代理、保护代理、智能引用等。
  • 三、行为型模式 (Behavioral Patterns)

    • 关注点:对象间的交互和职责分配。
    • 1. 责任链模式 (Chain of Responsibility)
      • 定义:使多个对象都有机会处理请求,将这些对象连成一条链,并沿着这条链传递请求,直到有对象处理它。
    • 2. 命令模式 (Command)
      • 定义:将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化;支持排队、日志和撤销操作。
    • 3. 解释器模式 (Interpreter)
      • 定义:给定一个语言,定义它的文法表示,并定义一个解释器来解释语言中的句子。
    • 4. 迭代器模式 (Iterator)
      • 定义:提供一种方法顺序访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。
    • 5. 中介者/调停者模式 (Mediator)
      • 定义:用一个中介对象来封装一系列的对象交互,使各对象不需要显式地相互引用。
    • 6. 备忘录模式 (Memento)
      • 定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
    • 7. 观察者模式 (Observer)
      • 定义:定义对象间的一种一对多的依赖关系,当一个对象状态改变时,所有依赖者都得到通知并自动更新。
      • 模型:推模型(广播详情) vs 拉模型(观察者主动获取)。
    • 8. 状态模式 (State)
      • 定义:允许一个对象在其内部状态改变时改变它的行为。
      • 对比:与策略模式结构相似,但意图不同(状态是内在变化,策略是外部选择)。
    • 9. 策略模式 (Strategy)
      • 定义:定义一系列算法,把它们一个个封装起来,并且使它们可相互替换。
    • 10. 模板方法模式 (Template Method)
      • 定义:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。
    • 11. 访问者模式 (Visitor)
      • 定义:表示一个作用于某对象结构中的各元素的操作,使你可以在不改变各元素的类的前提下定义新操作。
      • 机制:依赖于“双重分派”技术。

常用的设计模式(10个)

一、 创建型模式 (Creational Patterns)

这类模式主要关注对象的创建过程,旨在将对象的创建与使用分离。

1. 单例模式 (Singleton)

  • 概念:保证一个类仅有一个实例,并提供一个访问它的全局访问点。通常用于代表系统中本质上唯一的组件。
  • 示例
    • 系统资源管理:如文件系统、打印机假脱机程序或窗口管理器,在系统中通常只应有一个实例存在。
    • 代码实现:可以通过私有化构造函数,并提供一个静态方法(如 getInstance)来返回唯一的实例(可以是饿汉式或懒汉式实现)。

2. 工厂方法模式 (Factory Method)

  • 概念:定义一个用于创建对象的接口,让子类决定实例化哪一个类。这使得一个类的实例化延迟到其子类。
  • 示例
    • 文档应用框架:一个抽象的 Application 类负责管理文档,但它不知道具体的文档类(如 DrawingDocumentTextDocument)。它定义一个 CreateDocument 的工厂方法,由子类来实现具体的文档创建逻辑。
    • 暴发户坐车:在这个例子中,工厂方法模式用来创建不同品牌的汽车(如奔驰、宝马),不同的司机子类(工厂子类)负责创建对应的汽车实例。

3. 抽象工厂模式 (Abstract Factory)

  • 概念:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。主要用于处理产品族的问题。
  • 示例
    • 多视感标准 UI:支持多种界面风格(如 Motif 和 Presentation Manager)的工具包。定义一个 WidgetFactory 接口,包含创建滚动条、窗口、按钮的操作。具体的子类 MotifWidgetFactory 创建 Motif 风格的组件,而 PMWidgetFactory 创建 PM 风格的组件,客户仅需通过抽象接口与工厂交互。

二、 结构型模式 (Structural Patterns)

这类模式关注类和对象的组合。

4. 适配器模式 (Adapter)

  • 概念:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
  • 示例
    • 绘图编辑器:想要复用一个已有的 TextView 类来显示文本,但它的接口与编辑器期望的 Shape 接口不匹配。可以定义一个 TextShape 类(适配器),它继承 Shape 的接口并持有一个 TextView 的实例,将 Shape 的请求(如 BoundingBox)转换为 TextView 的对应操作(如 GetExtent)。
    • USB 转接口:给只提供 USB 充电口的 MP3 播放器配一个充电器转接头,使其能通过普通电源充电。

5. 装饰模式 (Decorator)

  • 概念:动态地给一个对象添加一些额外的职责。相比生成子类,这种方式更为灵活。
  • 示例
    • 图形界面组件:一个文本显示视图 TextView 缺省没有滚动条。如果需要添加滚动条或边框,无需创建子类,而是将 TextView 放入 ScrollDecoratorBorderDecorator 中。对客户而言,装饰后的对象仍是可视组件,但拥有了新功能。
    • JUnit 测试TestDecorator 可以给测试用例添加额外行为,例如 RepeatedTest 装饰器可以让一个测试用例重复运行多次。

6. 代理模式 (Proxy)

  • 概念:为其他对象提供一种代理以控制对这个对象的访问。代理可以在访问实体前进行预处理或控制。
  • 示例
    • **图片懒加载 (虚代理)**:文档编辑器打开包含大型图片的文档时,为了速度不立即加载图片,而是先创建一个 ImageProxy 替代。只有当用户滚动到该图片需要显示时,代理才真正创建并加载图像对象。
    • **权限控制 (保护代理)**:在论坛系统中,通过代理对象判断用户权限(如注册用户与游客),控制是否允许执行“发帖”等操作。

7. 组合模式 (Composite)

  • 概念:将对象组合成树形结构以表示“部分-整体”的层次结构,使用户对单个对象和组合对象的使用具有一致性。
  • 示例
    • 图形系统Picture(组合对象)可以包含 LineRectangle(基本对象)或其他 Picture。用户可以对整个 Picture 调用 Draw 操作,它会自动递归调用所有子部件的 Draw
    • JUnitTestSuite 可以包含多个 TestCase 或其他 TestSuite,运行 TestSuite 时会自动运行其包含的所有测试。

三、 行为型模式 (Behavioral Patterns)

这类模式关注对象间的通信、职责分配和算法封装。

8. 策略模式 (Strategy)

  • 概念:定义一系列算法,把它们封装起来,并且使它们可相互替换。该模式让算法独立于使用它的客户而变化。
  • 示例
    • 文本换行算法:一个文本排版系统可能支持多种换行策略(如简单换行、TeX 优化换行、数组式换行)。将这些算法封装在不同的 Compositor 子类中,排版对象 Composition 可以根据需要动态切换使用的策略。
    • 布局管理器:Java AWT 中的 LayoutManager 接口有多种实现(FlowLayout, GridLayout),容器将布局行为委托给具体的策略对象。

9. 观察者模式 (Observer)

  • 概念:定义对象间的一种一对多的依赖关系,当一个对象状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
  • 示例
    • 数据与图表:一个电子表格数据对象(目标)可能有多个展示图表(观察者,如柱状图、饼图)。当数据改变时,数据对象通知所有图表,图表自动重绘以反映最新数据。
    • JUnitTestResult 维护一个 TestListener 列表。当测试失败或结束时,它会通知所有注册的监听器(如打印结果的界面)。

10. 模板方法模式 (Template Method)

  • 概念:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
  • 示例
    • 打开文档:抽象类 Application 定义了 OpenDocument 的流程(检查文档、创建对象、读取数据),其中具体的步骤如 DoCreateDocumentDoRead 由子类实现,但整体流程由父类控制。
    • JUnit 测试运行TestCase 类定义了 runBare 方法,依次执行 setUp(初始化)、runTest(运行测试)、tearDown(清理)。用户只需要重写这三个步骤的具体实现,而无需改变执行顺序。

资料

https://refactoringguru.cn/design-patterns
https://design-patterns.readthedocs.io/zh-cn/latest/
https://jueee.github.io/design-patterns/

AI Code Spec-kit

https://github.com/github/spec-kit

我选用的Cursut-agent,我的建议是一定要按照官方的这几个命令顺序执,按顺序执行你才能真正体现这个工具的强大之处:

按顺序执行

生成结果

我个人觉得,从0到1开始一个项目,有这个是很省心的。

  • 想的全面
  • 代码结构化
  • 使用得当不容易出现屎山代码

写在前面

因为研发进入公司后都给开了cursor帐号,虽然大多数人都知道cursor也简单试用过但是可能没有花心思研究它,所以导致最近一个多月用下来我发现大家的使用方式存在较大问题,且token消耗很快,简单说就是大多使用的时候都毫无技巧,就力大砖飞的方式跟对话框干上了的感觉。

所以我利用站会的一点时间简单给大家讲了一下使用技巧,让大家能把cursor用的更好同时也能适当降低token消耗。

Cursor 有哪些使用技巧?

核心目标:用最少的 Request 消耗,换取最准确的代码产出。
核心原则:AI 不懂就问(Interactive Feedback),人要想好再说(PVE 模式)。

🚀 第一部分:省流攻略 (Save Requests & Tokens)

1. 拒绝 “Auto” 模式,手动分级

痛点:默认的 “Auto” 模式经常在简单问题上杀鸡用牛刀,浪费宝贵的 Fast Request。

  • 最佳实践
    • **日常开发 (80%)**:强制使用 GPT-4o-mini 或 Claude 3.5 Haiku。用于改 Bug、写注释、简单函数。
    • **攻坚时刻 (20%)**:手动切换到 Claude 3.5 Sonnet。仅用于架构设计、复杂重构。
  • 操作:使用快捷键 Cmd + / (Mac) 或 Ctrl + / (Win) 快速切换模型,不要依赖自动路由 1。

2. 启用 Interactive Feedback (MCP) —— 关键技巧

痛点:需求模糊时,AI 靠猜。猜错 = 重写 = 浪费 2-3 次 Request。
原理:利用 MCP 协议,让 AI 在同一个请求内暂停并向你提问,直到确认清楚再写代码。

  • 最佳实践
    • 配置 interactive-feedback-mcp 服务。
    • Rule设置:在 .cursorrules 中加入:“如果指令不明确,必须调用 interactive_feedback 询问,禁止盲目猜测。”
  • 收益:将原本需要 3 轮“生成-纠错-再生成”的交互,压缩为 1 次请求 [User Input, 13]。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 1. clone interactive-feedback-mcp
git clone https://github.com/noopstudios/interactive-feedback-mcp.git
cd interactive-feedback-mcp

# 2. 使用 Python 3.11 创建标准虚拟环境
/opt/homebrew/bin/python3.11 -m venv .venv

# 3. 激活环境(为了确保 pip 指向正确)
source .venv/bin/activate

# 4. 新建requirements.txt,安装依赖
pip install requirements.txt

# 5. 检查是否能成功运行
./.venv/bin/python feedback_ui.py --project-directory "." --prompt "环境安装完毕测试" --output-file "debug_output.json"

# 6. cursor配置mcp
"interactive-feedback-mcp": {
"command": "/xxxx/interactive-feedback-mcp/.venv/bin/python",
"args": [
"/xxxx/interactive-feedback-mcp/server.py"
],
"cwd": "/xxxx/interactive-feedback-mcp",
"timeout": 600,
"autoApprove": [
"interactive_feedback"
]
}

3. PVE 工作流 (Plan-Verify-Execute)

痛点:想到哪写到哪(Vibe Coding),导致 AI 反复修改同一段代码,Token 爆炸。

  • 最佳实践
    • **P (Plan)**:先用免费模型(或 mini)让 AI 列出修改计划。
    • **V (Verify)**:人工确认计划无误。
    • **E (Execute)**:开启 Composer (Cmd+I),一次性把确认好的计划发给 Sonnet 执行。
  • 收益:一次成型,拒绝返工 3。

⚡ 第二部分:上下文工程 (Context Management)

1. 必须配置 .cursorignore

痛点:AI 读了 node_modules 或 lock 文件,导致 Token 耗尽且回答变慢。

  • 操作
    • 在根目录新建 .cursorignore。
    • 填入:node_modules/, dist/, *.json (大文件), logs/。
    • 效果:强制 AI 只看源码,Token 节省 30% 以上 4。

2. 精通 .cursorrules (配置即代码)

痛点:每次都要重复喊“用 TypeScript”、“不要用 Class 组件”。

  • 操作
    • 根目录新建 .cursorrules 文件。
    • 由简入繁
      1. 角色:You are a senior expert in [技术栈].
      2. 绝对禁止:No class components. No placeholder comments.
      3. 格式:Only output changed lines. (只输出修改行,极大节省 Output Token) 6。

3. “一次性” 会话原则

痛点:在一个 Chat 窗口聊几天,上下文充满了过时的旧代码,导致 AI 变笨且极其费钱。

  • 操作
    • Task 完成即销毁:一个功能点 = 一个 Chat。功能做完,立刻 Cmd+L 开新窗口。
    • 及时 Unpin:如果文件不再需要修改,立刻取消 Pin 状态 8。

🛠️ 第三部分:核心工具与技巧 (Tools & Tricks)

1. Composer (Cmd + I) > Chat (Cmd + L)

  • Chat 是咨询师(只看不写):用于问原理、查 Bug。
  • Composer 是工程师(直接写):用于多文件编辑
  • 技巧
    • Combo 连招:不要分三次改。直接在 Composer 输入:“在 User 表加字段,同步更新 API DTO,并修改前端类型定义。” —— 3 个文件变动,只需 1 次 Request 1。

2. 善用 Plan Mode (Shift + Tab)

  • 场景:大功能开发。
  • 操作:在 Composer 输入框按 Shift + Tab 进入 Plan Mode。AI 会先扫描代码库,生成 Markdown 格式的待办清单,你确认后它再动手。这比直接写代码更稳,更省重试次数。

📝 总结:开发者自查清单

在开始 coding 前,请确认:

  1. [ ] 模型对了吗? 简单问题切回 mini 了吗?
  2. [ ] 规则有了吗? .cursorrules 和 .cursorignore 配置了吗?
  3. [ ] 模式选对了吗? 多文件修改是否使用了 Composer?
  4. [ ] 反馈机制:是否启用了 Interactive Feedback 以防止 AI 瞎猜?
  5. [ ] 会话清理:上个任务结束了吗?结束了请开新 Chat。

参考资料

  1. Pricing | Cursor Docs
  2. Models | Cursor Docs
  3. In Cursor, Context is King - DEV Community
  4. Codebase Indexing | Cursor Docs
  5. Ignore files | Cursor Docs
  6. How to Use Cursor More Efficiently - r/ChatGPTCoding
  7. .cursorrules · GitHub Gist
  8. Understanding Cursor Token Usage - Reddit
  9. 4 hacks to turbocharge your Cursor productivity | LaunchDarkly

数学 交易

引言

说出来不怕笑话,我从小数学就差。

高中那会儿,数学老师看着我直摇头,我也确实没那个基因。从兴趣盎然到索然无味,只用了半个学期。谁知道工作这么多年后,我又对数学这玩意儿产生了兴趣——又菜又爱说的就是我。

这篇文章是因为最近看了 The Math of Trading - The Shmuts,很有感触,所以梳理一篇。


赌场为什么永远赚钱?

先问个问题:赌场靠什么赚钱?

你可能觉得靠运气、靠荷官的手法、靠游戏规则不公平。

其实都不是。赌场赚钱的秘密就一个字:

赌场不在乎某一局谁赢谁输。他们知道的是:只要玩的人够多,数学上的优势就会让他们稳赚不赔。

举个简单的例子,抛硬币。正面你赢100块,反面你输100块。长期玩下来,大家都不输不赢。

但如果规则改成:正面你赢90块,反面你输100块呢?

你可能觉得”才差10块而已”,但数学不会撒谎。长期玩下来,赌场稳赚。

这就是为什么赌场:

  • 欢迎:你玩100把,每把下注5块
  • 拒绝:你只玩1把,下注100万

因为单把赌博,概率优势还没来得及发挥作用。赌场怕的不是你赢,是怕你在概率发挥作用之前就不玩了。

把赌场思维换个场景,就是:那些懂数学的人,不和老天赌单次输赢,他们赌的是长期概率。


一个公式,看清所有赌局

交易里有个概念叫”期望值”(Expected Value),公式简单得要命:

1
期望值 = (赢的概率 × 赢的时候拿多少) - (输的概率 × 输的时候赔多少)

别被公式吓到,看个例子。

游戏A:抛硬币,正面赢3块,反面输2块

  • 期望值 = (50% × 3) - (50% × 2) = 1.5 - 1 = +0.5块

这个游戏值得玩,长期玩下去你稳赚。

游戏B:抛硬币,正面赢1块,反面输2块

  • 期望值 = (50% × 1) - (50% × 2) = 0.5 - 1 = -0.5块

这个游戏别玩,长期玩下去你稳输。

这两个游戏都是50%胜率,但结果天差地别。所以关键不是胜率,是赢了拿多少、输了赔多少。


赢大赔小,胜率低也能赚钱

这里有个很多人不知道的事:你不需要高胜率也能赚钱

看个极端例子:

游戏C

  • 10%概率赢1000块
  • 90%概率输100块

期望值 = (10% × 1000) - (90% × 100) = 100 - 90 = +10块

胜率只有10%,但长期玩下去,你每把平均赚10块。

反过来呢?

游戏D

  • 90%概率赢100块
  • 10%概率输2000块

期望值 = (90% × 100) - (10% × 2000) = 90 - 200 = -110块

胜率高达90%,但长期玩下去,你每把平均输110块。

生活中处处如此。

  • 创业:失败率高,但成功一次收益大
  • 稳定工作:成功率高,但每次收益有限
  • 婚姻:离婚率不低,但找到一个好伴侣收益无限

所以别盯着胜率看,要算算期望值。


为什么短期结果说明不了问题

这里有个坑,很多人踩过。

你玩个游戏,连赢10把,觉得自己找到了规律。结果呢?接下来连输20把,心态崩了。

问题出在哪儿?样本量太小

抛硬币,你抛10次可能8次都是正面。但这不代表硬币有问题。你抛1000次还能800次正面,那才有问题。

这个道理叫”大数定律”:玩的次数越多,结果就越接近真实的概率。

生活中到处都是这个坑:

  • 某个股票连涨3天,你觉得会继续涨——赌徒谬误
  • 某个方法试了2次没效果,你就放弃了——样本太小
  • 看到别人成功一次,你就想模仿——幸存者偏差

要判断一个东西到底行不行,得看够大的样本。专业交易员都要模拟交易几个月到一年半,就是为了积累足够的样本量。

急着下结论,那是跟自己过不去。


活着才能赢

有个概念叫”破产风险”(Risk of Ruin),简单说就是:你把钱亏完的概率。

举个栗子:

你有1万块本金,玩一个期望值为正的游戏。

玩法A:每次押100块

  • 就算连输100次,你也只是把本金输光,不会倒欠
  • 破产风险几乎为零

玩法B:每次押5000块

  • 连输2次,游戏就结束了
  • 破产风险明显上升

看出来了吗?同样的游戏,玩法不同,结果天差地别。

这就是为什么老手常说”活得久比赚得多重要”。死在半路上,后面再大的机会也跟你没关系了。

巴菲特是怎么说的?

“投资第一条原则:不要亏钱。”
“投资第二条原则:记住第一条。”

不是因为他胆小,是因为他懂这个数学:亏50%需要涨100%才能回本。


亏损的代价,比你想的大得多

刚才说了:亏50%需要涨100%才能回本。

不是50%,是100%。

1
2
3
4
5
亏10%需要涨11%回本
亏25%需要涨33%回本
亏50%需要涨100%回本
亏75%需要涨300%回本
亏90%需要涨900%回本

这个数学事实意味着什么?

大亏损对复利的伤害是毁灭性的。

看个对比:

  • 策略A:每年稳定赚15%
  • 策略B:一年赚50%,下一年亏30%,这样循环

十年下来:

  • 策略A翻了4倍
  • 策略B只翻了1.8倍

策略B看起来更刺激,但长期输给了稳扎稳打的策略A。

这让我想起一句话:那些追涨杀跌、大起大落的操作,长期来看跑不过稳定的龟速增长。


写在最后

数学这东西,从小我就怕。

但现在我发现,它其实挺有意思的。它不会骗人,也不会跟你耍心眼。它只是静静地告诉你:这样做会赢,那样做会输。

不管你炒股不炒股,这些数学思维都用得上:

  1. 别盯着单次输赢 —— 看长期期望值
  2. 别急着下结论 —— 样本量要够大
  3. 别孤注一掷 —— 活着才能赢
  4. 别忽视大亏损 —— 回本的代价比你想象的大

当然,道理都懂,做到很难。人性这东西,才是最大的对手。

但至少,从今天开始,你可以少一点”我觉得”,多一点”算算看”。


参考文章:The Math of Trading - The Shmuts

架构师学习 第2篇

示例

构建一个在线电子商务系统(E-Shop)的设计示例。这个示例将从宏观的战略设计(如何划分系统边界)到微观的战术设计(代码层面的核心元素),全面展示 DDD 的核心思想。

1. 战略设计:界定的上下文 (Bounded Contexts)

DDD 的首要原则是软件必须植根于领域,并且模型需要有清晰的边界。

场景: 假设我们要构建一个大型在线商店,不仅涉及用户下单,还需要处理库存发货和销售报表。

传统问题: 许多团队会试图创建一个包含所有属性(如用户、订单、商品、库存、报表)的单一庞大模型。这会导致模型臃肿,不同职能的团队互相干扰。

DDD 解决方案:
我们将系统划分为两个独立的界定的上下文(Bounded Contexts),:

  1. 在线交易上下文(E-Shop Context): 关注客户下单、购物车、结账。这里的“商品”关注价格和描述。
  2. 报表上下文(Reporting Context): 关注销售趋势、库存周转。这里的“商品”可能只关注销售数量和成本,不需要描述信息。

核心思想:

  • 消除歧义: 同一个词(如“商品”)在不同上下文中可能有不同的含义和属性。通过划分上下文,我们可以保证模型在各自边界内的纯洁性和一致性。
  • 上下文映射(Context Map): 我们定义这两个上下文的关系。例如,报表系统需要从交易系统获取数据,它们可能通过客户-供应商(Customer-Supplier)模式交互,或者通过防崩溃层(Anticorruption Layer)来转换数据,确保报表系统的模型不受交易系统模型变更的直接破坏,。

2. 战术设计:领域模型的核心要素

在“在线交易上下文”内部,我们使用战术模式来构建领域模型。

A. 通用语言 (Ubiquitous Language)

开发人员与业务专家(如销售经理)共同制定一套语言。

  • 示例: 大家不再说“插入一条记录到订单表”,而是统一说“提交订单(Submit Order)”。
  • 价值: 这消除了沟通障碍,代码中的类名和方法名将直接反映业务意图,。

B. 分层架构 (Layered Architecture)

为了隔离关注点,我们将系统分为四层,:

  1. 用户界面层: 展示商品页面,接收用户点击。
  2. 应用层: 协调任务(如“协调结账流程”),但不包含业务逻辑。
  3. 领域层(核心): 包含 OrderCustomer 等业务对象和规则。这是软件的心脏
  4. 基础设施层: 处理数据库持久化、发送邮件等技术实现。

C. 实体 (Entities) 与 值对象 (Value Objects)

这是领域模型的基本构建块。

  • 实体(Entity):

    • 示例: Order(订单)。
    • 设计理由: 订单有生命周期(从创建到支付到发货),并且需要被追踪。即使两个订单的内容完全一样,只要 ID 不同,它们就是不同的对象。因此,Order 是一个实体,必须有唯一的标识符(Identity),。
  • 值对象(Value Object):

    • 示例: Address(送货地址)。
    • 设计理由: 我们只关心地址的属性(街道、城市),而不关心它的唯一标识。如果两个客户住在同一地址,这在业务上是等价的。Address 应该是不可变的(Immutable),如果客户搬家了,我们是用一个新的 Address 对象替换旧的,而不是修改旧对象,。

D. 聚合 (Aggregates)

为了保证数据一致性,我们需要划定修改数据的边界。

  • 示例: 一个 Order(订单)可能包含多个 OrderItem(订单项)。
  • 设计: Order 是这个聚合的根(Aggregate Root)
  • 规则: 外部对象只能引用根(Order),不能直接引用内部的 OrderItem。如果想修改某个订单项的数量,必须通过根的方法(如 order.updateItemQuantity())来进行。这确保了订单总价等不变量(Invariants)在修改过程中始终保持一致,。

E. 服务 (Services)

有些动作不属于特定的对象。

  • 示例: CheckoutService(结账服务)或 FundTransferService(转账服务)。
  • 设计理由: 结账可能涉及订单状态更新、库存扣减、支付网关调用等。这些行为放入 OrderCustomer 都不合适,因此我们创建一个无状态的领域服务来封装这些操作,。

F. 资源库 (Repositories)

为了解耦领域模型与数据库。

  • 示例: OrderRepository
  • 设计: 领域层只定义接口 findOrder(id),不关心底层是 SQL Server 还是 Oracle。资源库负责从数据库中检索数据并将其重建为领域对象(如 Order 聚合)。这让开发人员可以像从内存集合中获取对象一样获取领域对象,而无需在业务逻辑中编写 SQL,。

3. 系统运作流程示例

结合上述概念,一个“用户修改收货地址”的业务场景在代码设计中如下流转:

  1. 应用层接收请求,调用资源库CustomerRepository)。
  2. 资源库利用基础设施层从数据库检索数据,重建 Customer 聚合(实体)。
  3. 应用层调用 Customer 实体的业务方法(如 customer.moveTo(newAddress))。
  4. 在方法内部,Customer 实体将旧的 Address 值对象替换为新的 Address 值对象
  5. 资源库将更新后的 Customer 聚合保存回数据库。

总结与比喻

这个设计示例体现了 DDD 的核心:通过将软件实现与业务领域模型紧密绑定,来应对复杂性。

为了巩固理解,我们可以用书中提到的汽车制造来类比这个系统设计:

  • 领域模型就像是汽车的设计蓝图。工人在造车前必须先有精准的图纸,同样,开发软件前必须先理解并建模业务领域。
  • 实体与聚合就像是汽车的发动机和底盘。它们是核心部件,有独立的标识和生命周期,必须作为一个整体来组装和维护。
  • 分层架构就像是汽车的不同系统(传动系统、电子系统、内饰)。内饰(UI)的变化不应直接影响发动机(领域逻辑)的运作。
  • 通用语言就像是工程师团队之间的技术术语。如果有人把“方向盘”叫成“转弯器”,制造过程就会混乱;同样,代码必须精确使用业务术语。

学习资料

https://github.com/Sairyss/domain-driven-hexagon?tab=readme-ov-file

架构师学习 第1篇

写在前面

以前,我相信代码即真理。作为一名专注于实现的技术专家,我聚焦于代码写得足够好,我就牛逼。直到我加入这家初创公司,成为研发负责人,现实给了我更复杂的挑战。

我们没有预算去组建豪华的架构团队,我不得不从微观的代码世界抬起头,开始被迫去思考那些我不曾涉足的宏大命题:技术选型、系统边界、网络安全、以及如何在资源捉襟见肘时支撑复杂的业务增长。

这不是一个“资深架构师下凡”的故事,而是一个“写代码的手艺人被迫去画图纸”的记录。

正因为我没有科班架构师的思维定式,我是带着“代码的触觉”去搭建系统的。我更加警惕过度设计,更加关注落地的成本。在本书/本文中,你看到的不仅是技术的选型,更是一个工程师在理想与现实、代码洁癖与商业速度之间,无数次权衡后的真实思考。

这一系列风格大概率就会以记录或碎碎念的方式呈现,估计没什么章法。

一、 硬核内功:分布式与数据密集型系统

  1. 必读神书:
    • 《数据密集型应用系统设计》 (Designing Data-Intensive Applications - DDIA)
      • 评价: 架构领域的“圣经”。Martin Kleppmann 把分布式系统、数据库原理、一致性哈希、CAP 理论讲得极为透彻。
      • 你的关注点: 不要只看结论,要看它对不同存储引擎(B-Tree vs LSM-Tree)、事务隔离级别、流处理的深度剖析。这对于你理解 Minio、SeaweedFS 这类分布式存储的底层逻辑至关重要。

[Image of Designing Data-Intensive Applications book cover]

  1. 现代架构模式:
    • 《软件架构:架构模式、特征及实践》 (Fundamentals of Software Architecture)
      • 评价: O’Reilly 出品的红皮书。它系统性地定义了架构风格(微内核、微服务、事件驱动、基于空间等)及其适用场景。
      • 核心价值: 帮你建立系统的“评估维度”(如可扩展性、弹性、性能、成本),学会用雷达图来做技术选型。

二、 方法论:如何驾驭业务复杂度

架构师不仅要懂技术,更要懂业务。

  1. 领域驱动设计 (DDD):

    • 推荐阅读: 也就是 Eric Evans 的蓝皮书(太晦涩,建议当字典查)或者 Vaughn Vernon 的《实现领域驱动设计》(红皮书,更实战)。
    • 关键点: 限界上下文(Bounded Context)、聚合根(Aggregate Root)、防腐层(ACL)。
    • 实战意义: 当你在做 SaaS 或 CRM 系统时,DDD 能帮你厘清微服务的边界,避免微服务变成“分布式单体”。
  2. 可视化与沟通:C4 模型

    • 架构师的一大工作是沟通。UML 太重,白板太乱。
    • C4 Model (Context, Containers, Components, Code): 由 Simon Brown 提出。它像谷歌地图一样,从宏观(系统全貌)到微观(类图)分层展示。
    • 建议: 以后做设计评审(Design Review),尝试用 C4 画图,你的专业度会瞬间提升。

三、 破局:AI Native 架构与 LLM 集成

作为现在的架构师,如果不考虑 AI,设计就是过时的。你需要思考如何把 LLM 融入现有架构。

  1. RAG (检索增强生成) 与 Agent 架构:

    • 不要只盯着模型微调,重点关注 向量数据库 (Vector DB) 的选型与 Context Window 的管理。
    • 学习 LangChainLangGraph 的设计理念(尽管你可能不直接用 Python 写生产代码,但思想通用)。
    • 思考题: 如何设计一个架构,既能处理传统的 CRUD 业务,又能低延迟地响应 AI 推理请求?如何处理 AI 的非确定性输出?
  2. 架构演进:

    • DevOps 转向 **Platform Engineering (平台工程)**。
    • 关注 IDP (Internal Developer Portal) 的构建,让开发人员自助服务,架构师负责制定标准和“铺路”。

四、 软技能:决策与文档

架构师是技术团队的政委。

  1. ADR (Architecture Decision Records):

    • 强烈推荐: 开始在你的项目中使用 ADR。
    • 是什么: 记录每一个架构决策的背景、选项、决策结果、后果(好的和坏的)。
    • 为什么: 解决“为什么当初那个傻X选了这个方案”的问题。它是架构师的“免责声明”和团队的知识资产。
  2. 技术影响力:

    • 阅读 **《技术管理模式:像在谷歌一样进行软件工程》 (Software Engineering at Google)**。了解大规模团队如何做代码评审、发布管理和知识共享。

五、 极简资源清单 (High Signal/Noise Ratio)

为了节省你的时间,我只推荐最高质量的信息源:

  • InfoQ (架构师特刊): 依然是国内质量较高的架构案例来源,关注大厂的复盘。
  • High Scalability (Blog): 虽然更新慢了,但以前的 Case Study(如 WhatsApp, Netflix 架构)是经典。
  • ThoughtWorks 技术雷达: 每半年看一次,了解什么是 Hold(别碰),什么是 Adopt(该用了)。
  • Hacker News: 保持对全球前沿技术的敏感度。

学习资料

https://refactoring.guru/design-patterns/catalog
https://github.com/Sairyss/domain-driven-hexagon
https://archguard.org/book-list
https://www.infoq.cn/article/crafting-architectural-diagrams/
https://www.infoq.cn/article/C4-architecture-model/
https://c4model.com/introduction

RAG 第一篇

后续团队需要做企业大脑,会用到RAG,先比较完整的了解下RAG,把零碎知识补的完整一些,做个记录。

### 标准RAG(Retrieval-Augmented Generation)的运行流程图

标准RAG系统的运行流程可以分为两大阶段:索引阶段(Indexing Phase)(预处理数据)和运行时阶段(Runtime Phase)(查询处理)。以下是详细的流程描述,我会结合常见的流程图示例来说明。RAG的核心是先从外部知识库中检索相关信息,然后用这些信息增强LLM的生成,以减少幻觉并提供更准确的回答。

这个流程图展示了RAG的高层结构:从数据源到最终输出的完整路径。

1. 索引阶段(Indexing Phase):预构建知识库

这一阶段发生在系统启动前,用于将外部数据转化为可检索的形式。通常是离线过程,目的是创建向量数据库以支持高效查询。

  • 步骤1: 数据采集(Data Ingestion)
    从各种来源(如文档、数据库、网页)收集原始数据。这些数据可以是结构化(e.g., SQL表)或非结构化(e.g., PDF、文本文件)。
  • 步骤2: 数据分块(Chunking)
    将长文档切分成小块(chunks),通常每个chunk 100-500 tokens,以匹配嵌入模型的输入限制。分块策略包括固定长度、按句子或语义分块(e.g., 使用LLM检测自然断点)。
  • 步骤3: 嵌入生成(Embedding Generation)
    使用嵌入模型(如BERT、OpenAI的text-embedding-ada-002)将每个chunk转化为向量表示(dense vector,e.g., 768维)。这捕捉语义相似性。
  • 步骤4: 索引存储(Indexing and Storage)
    将向量和对应的原始chunk存储到向量数据库(Vector DB,如FAISS、Pinecone、Milvus)。同时可能添加元数据(如来源、时间戳)。索引使用近似最近邻(ANN)算法如HNSW来加速检索。

这一阶段的输出是一个可查询的向量数据库。

这个图更详细地展示了索引和检索的子步骤,包括分块、嵌入和重排序。

2. 运行时阶段(Runtime Phase):实时查询处理

这是用户交互时的核心流程,对应你之前描述的“检索 + 生成”。

  • 步骤1: 用户输入(User Query Input)
    用户提供查询(query),e.g., “什么是RAG?”。
  • 步骤2: 查询嵌入(Query Embedding)
    使用相同的嵌入模型将query转化为向量。
  • 步骤3: 检索(Retrieval)
    在向量数据库中计算query向量与所有chunk向量的相似度(e.g., 余弦相似度)。返回Top-K个最相似的chunks(原始文本 + 元数据)。可选:重排序(Re-ranking)使用另一个模型(如Cross-Encoder)进一步过滤,提高相关性。
  • 步骤4: 提示构建(Prompt Construction)
    将检索到的Top-K chunks(原始文本)与query拼接成一个Prompt。模板示例:
    1
    2
    3
    4
    系统提示: 你是一个助手,使用以下上下文回答问题。
    上下文: [chunk1] [chunk2] ... [chunkK]
    问题: [query]
    回答:
    注意:这里不直接传递向量,只传递文本。Prompt长度需控制在LLM上下文窗口内(e.g., 8K-128K tokens)。
  • 步骤5: 生成(Generation)
    将Prompt输入LLM(e.g., GPT-4、LLaMA),LLM基于Prompt生成回答。生成过程使用解码算法如beam search或greedy decoding。
  • 步骤6: 输出响应(Output Response)
    返回生成的文本给用户。可选:后处理,如引用来源或验证事实。

结构化和非结构化数据的融合,以及Prompt + Context的输入到LLM。

详细流程的潜在变体和优化

  • 高级检索:可结合关键字搜索(BM25)与向量搜索的混合检索(Hybrid Search),或使用查询重写(Query Rewriting)来扩展query。
  • 多跳检索:对于复杂查询,多次检索(e.g., 先检索实体,再检索关系)。
  • 性能考虑:检索延迟通常<1s,生成取决于LLM大小。常见问题:噪声chunk(无关信息)导致Prompt过长,可用压缩(如摘要)优化。
  • 工具与框架:实现时常用LangChain、Haystack或LlamaIndex。嵌入模型:Sentence Transformers;向量DB:Weaviate。

总结

一句话概括RAG就像给AI装了个”外挂搜索引擎”,问啥先从知识库里找答案,再让AI组织回答。

整个流程就两步大动作:

1️⃣ 准备阶段(一次性干完)

  • 把一堆文档(PDF、网页、数据库)切成小块(像切西瓜一样)
  • 给每块内容**打个”指纹”**(向量嵌入,类似数字DNA)
  • 把这些”指纹+原文”存进搜索引擎(向量数据库)

比喻:就像给图书馆每本书都贴上标签,方便以后快速找书。

2️⃣ 回答问题时(每次提问都这样)

1
2
3
4
用户问问题 → AI先去"图书馆"翻书 → 找到相关内容 → 组织答案给用户
↓ ↓ ↓ ↓
"什么是RAG?" 搜"指纹"匹配 挑出3-5段 "RAG是检索增强生成..."
找相关段落 最相关的书 用这些内容回答

详细拆解

  1. 你问问题 → AI把你的问题也打个”指纹”
  2. AI去搜 → 在图书馆里找跟你问题”指纹”最像的几本书
  3. 挑重点 → 不把整本书都拿出来,只拿相关段落
  4. 拼答案 → 把这些段落+你的问题一起给AI,让它重新组织回答
  5. 输出 → AI用找到的”参考资料”给你准确回答

关键点:

  • 向量(指纹)只用来”找书”不直接给AI看
  • AI真正读的是书里的原文,不是那些数字指纹
  • 找书快(秒级),读懂写回答慢(几秒到几十秒)

为啥要这样?

  • 不RAG:AI只能凭记忆瞎编,容易胡说八道
  • 有RAG:AI像查字典一样先找事实,再组织语言回答

通俗理解:RAG就是让AI**”不瞎编,先查资料”**,回答前先翻书确认事实!

参考文档

https://www.6clicks.com/resources/blog/understanding-rag-retrieval-augmented-generation-explained
https://danielp1.substack.com/p/navigating-retrieval-augmented-generation

架构师学习 第0篇

写在前面

这是一条非常值得尊敬的道路。既然你明确拒绝“速成”,那就意味着你已经做好了打持久战的准备,这正是成为一名真正优秀架构师的前提。

架构师的成长不是线性的,而是螺旋上升的。我为你规划了一条从“点(代码)”到“线(模块)”再到“面(系统)”最后到“体(业务与人)”的系统进阶路径。


第一阶段:筑基——代码与微观设计(The Code)

目标: 写出哪怕过了一年别人也能看懂、敢修改的代码。如果地基不稳,上层架构设计得再漂亮也是空中楼阁。

  1. 编程范式与设计模式:
    • 内容: 除了你之前关注的 23种设计模式,还要深入理解 面向对象编程 (OOP)函数式编程 (FP) 的本质区别与结合。
    • 必修: SOLID 原则(架构师的宪法)。
    • 书籍: 《设计模式》(GoF)、《代码整洁之道》、《重构》。
  2. 数据结构与算法的工程应用:
    • 内容: 不仅仅是刷 LeetCode,而是理解时间/空间复杂度对系统性能的深远影响。
    • 场景: 为什么要用 B+ 树做数据库索引?为什么 Redis 用跳表?HashMap 的扩容机制如何影响延迟?
  3. 语言底层机制:
    • 内容: 无论你用 Java、Go 还是 Python,必须精通至少一门语言的底层。包括内存模型(Memory Model)、垃圾回收(GC)、并发模型(Thread vs Goroutine)。

第二阶段:深入——系统原理与中间件(The System)

目标: 不再把数据库和消息队列当“黑盒”使用,而是理解其内部原理,知道它们的极限在哪里。

  1. 操作系统与网络:
    • 内容: I/O 模型(BIO/NIO/AIO/Epoll/IO_URING),TCP/IP 协议栈(三次握手、拥塞控制),Zero-Copy 技术。
    • 思考: 为什么 Nginx 性能这么高?Netty 是怎么做到高并发的?
  2. 数据库内核级原理:
    • 内容: 事务隔离级别(脏读/幻读底层实现)、锁机制(MVCC)、存储引擎(LSM-Tree vs B-Tree)、WAL(预写日志)。
    • 必修书籍: 《数据密集型应用系统设计》 (DDIA) —— 再次强调,这是此阶段的必读书。
  3. 分布式理论基石:
    • 内容: CAP 定理(不仅仅是背概念,而是理解权衡)、BASE 理论、分布式共识算法(Paxos, Raft, ZAB)。
    • 场景: 当网络分区发生时,你的系统是保可用性(AP)还是保一致性(CP)?

第三阶段:宏观——架构模式与业务建模(The Architecture)

目标: 跳出技术细节,开始从业务视角拆解系统,解决复杂性问题。

  1. 架构风格演进:

    • 内容: 单体 -> SOA -> 微服务 -> Service Mesh -> Serverless。
    • 关键: 重点不是学习怎么搭建微服务,而是学习什么时候不该用微服务。理解每种架构风格的优缺点(Trade-offs)。
    • 书籍: 《软件架构模式》(O’Reilly)、《微服务架构设计模式》。
  2. 领域驱动设计 (DDD):

    • 内容: 战略设计(限界上下文、通用语言、子域划分)与战术设计(聚合根、实体、值对象)。
    • 价值: 这是架构师与业务方沟通的桥梁,解决“业务复杂性”的终极武器。
    • 书籍: 《领域驱动设计》(蓝皮书 - 难读但经典)、《实现领域驱动设计》(红皮书 - 实战推荐)。
  3. 高可用与高并发架构:

    • 内容: 缓存策略(穿透/雪崩/击穿)、分库分表、读写分离、异地多活、限流熔断降级。
    • 实战: 试着去推演像“双11”秒杀系统或 12306 订票系统的架构设计。

第四阶段:落地——工程化与治理(The Engineering)

目标: 架构不仅是画图,更是要保证系统能稳定、高效地运行和迭代。

  1. DevOps 与 SRE(站点可靠性工程):
    • 内容: CI/CD 流水线设计、容器化(Docker/K8s)、可观测性(Logging, Tracing, Metrics - 如 Prometheus/Grafana/ELK)。
    • 思维: 架构设计必须包含“可测试性”和“可监控性”。
  2. 技术选型与决策:
    • 能力: 如何在两个看起来差不多的技术(比如 RabbitMQ vs Kafka,PostgreSQL vs MySQL)中做选择?需要建立多维度的评估模型(成本、运维难度、社区活跃度、适用场景)。
  3. 遗留系统治理:
    • 内容: 如何在不停止业务的情况下重构一个跑了10年的老系统?这是最考验架构师功力的地方。

第五阶段:升维——软技能与商业思维(The Wisdom)

目标: 从“解决技术问题”转变为“解决商业问题”。

  1. 沟通与领导力:
    • 架构师需要向老板解释为什么这个功能要开发一个月,向团队解释为什么要用这个新技术。
    • 学会画图(UML, C4 Model),学会写文档(ADR)。
  2. 商业敏感度:
    • 技术是为了服务业务的。理解成本(Cloud Cost Optimization)、理解上市时间(Time to Market)。
    • 最高境界: 甚至能指出业务流程的不合理之处,用技术反向驱动业务创新。

总结:如何执行?

不要试图并行学习所有内容。建议采用 “T型人才” 策略:

  1. 竖线(深): 先在某一个领域扎得足够深(比如你精通 Java 并发编程,或者精通 MySQL 调优)。这能建立你的技术自信和威信。
  2. 横线(广): 然后慢慢扩展知识面,了解前端、运维、大数据、AI 等领域的基础概念。

现在的你,既然想从设计模式入手,那就先在第一阶段扎实待上几个月,把《Head First 设计模式》吃透,并在工作中强迫自己去识别和重构坏代码。

这条路很长,很苦,但风景最好。加油。

Linux netplan

初创团队,各方面有限,我们是saas+硬件,但是我们只有单个公网IP、一个一级域名,所以为了短时间适配生产、研发、测试三个环境,同时支持SaaS+硬件通信,我们需要做前端入口的流量管理,团队的小伙伴选择了OPNsense。
这一篇是我为了了解该技术方案而简单整理的。

# 解决Ubuntu Server 24.04删除网卡后的Netplan问题

引言

在Ubuntu Server 24.04中,Netplan是默认的网络配置工具,使用YAML文件管理网络设置。最近,我在虚拟机中配置了双网卡(一张内网,一张外网),但删除一张网卡后,网络无法正常工作。经过调试,我通过手动更新Netplan配置文件解决了问题,以下是我的经验分享。

问题描述

我的虚拟机最初配置了两张网卡:enp0s3(外网,静态IP)用于访问外部网络,enp0s8(内网,DHCP)用于本地通信。删除enp0s8后,运行netplan apply没有生效,ip a显示enp0s3未正确分配IP。日志(journalctl -u systemd-networkd)提示Netplan仍尝试配置已删除的网卡。

1
2
4: ens38: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 00:30:26:8c:78:57 brd ff:ff:ff:ff:ff:ff

解决方案

以下是解决步骤:

  1. 启动网卡
1
ip link set ens38 up
  1. 检查现有Netplan配置
    查看/etc/netplan/目录中的配置文件(通常为00-installer-config.yaml)。原始配置如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    network:
    version: 2
    ethernets:
    enp0s3:
    dhcp4: no
    addresses: [172.162.1.100/24]
    gateway4: 172.162.1.1
    nameservers:
    addresses: [8.8.8.8, 8.8.4.4]
    enp0s8:
    dhcp4: yes
  2. 更新配置文件
    删除enp0s8相关配置,仅保留enp0s3。修改后的文件如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    network:
    version: 2
    ethernets:
    enp0s3:
    dhcp4: no
    addresses: [172.162.1.100/24]
    gateway4: 172.162.1.1
    nameservers:
    addresses: [8.8.8.8, 8.8.4.4]

    注意:确保YAML缩进为2个空格,避免格式错误。

  3. 验证并应用配置
    检查配置语法:

    1
    sudo netplan --debug apply

    确认无错误后,应用配置:

    1
    sudo netplan apply
  4. 测试网络
    验证接口状态和连通性:

    1
    2
    ip a
    ping 8.8.8.8
  5. 检查日志
    如果仍不生效,查看日志:

    1
    journalctl -u systemd-networkd

总结

删除虚拟机网卡后,Netplan不会自动更新配置,需手动移除无效网卡的配置条目。关键是检查YAML文件、确保格式正确,并使用netplan --debug apply定位问题。在多网卡场景下,建议定期验证网卡名称(ip link)和配置文件一致性。遇到类似问题?欢迎留言分享你的经验!

网络安全 软路由

初创团队,各方面有限,我们是saas+硬件,但是我们只有单个公网IP、一个一级域名,所以为了短时间适配生产、研发、测试三个环境,同时支持SaaS+硬件通信,我们需要做前端入口的流量管理,团队的小伙伴选择了OPNsense。
这一篇是我为了了解该技术方案而简单整理的。

OPNsense的防火墙模块基于FreeBSD的pf(Packet Filter),提供了强大的NAT功能,包括Port Forward(转发规则)、Outbound NAT(出站NAT)和NPTv6(IPv6前缀转换)。从我的截图可以看到,Port Forward页面列出了WAN到LAN的TCP流量规则(如Web服务和RDP),而Outbound NAT页面显示了自动生成的出站规则。这些功能让我在单一公网IP下实现了多环境隔离和外部访问。
NAT策略:Port Forward与Outbound NAT的协同
最初我以为Port Forward能解决所有需求,但实践证明,仅靠它处理入站流量是不够的。Outbound NAT才是出站流量的关键,尤其在多VLAN和硬件通信场景中,两者需协同工作。

Port Forward:

用途:处理入站流量,将公网端口映射到内部IP。例如,我配置了WAN:443到192.168.10.10:443,让外部通过prod.example.com访问生产环境。
局限:不管理出站流量,硬件向SaaS发送数据时需依赖Outbound NAT。
配置:Firewall → NAT → 转发,添加规则(Protocol: TCP;Destination: WAN address:443;Redirect to: 192.168.10.10:443)。

Outbound NAT:

位置:Firewall → NAT → Outbound,当前为自动模式,自动为WAN出站流量分配公网IP。
优化:切换到手动模式,为每个VLAN设置规则,确保出站流量隔离。
我的经验:自动模式曾因端口冲突导致硬件API请求失败,切换到手动后问题解决。

找到并配置Outbound NAT
Outbound NAT是管理出站流量的关键,位于Firewall → NAT → Outbound页面。从我的截图可以看到,默认使用“自动生成规则”,为LAN和Loopback网段分配WAN地址。但对于复杂场景,我切换到手动模式以满足需求。

手动配置:
点击“切换到手动规则”(Switch to Manual Outbound NAT rule generation),保存。
添加规则:Interface: WAN;Source: 192.168.10.0/24(生产),Translation: WAN地址,描述:“Production Outbound”。
依次为研发(192.168.20.0/24)和测试(192.168.30.0/24)设置规则。
硬件场景:为生产VLAN的硬件(如192.168.10.10)添加规则,确保其API请求(如curl https://prod.example.com/api/v1/data)顺利出站。

优化:启用NAT Reflection(Firewall → Advanced),让内部设备用公网IP访问暴露服务。
经验:备份配置(System → Config History)后切换模式,避免误操作。日志监控(Firewall → Log Files)帮助我定位流量问题。

下一步行动项

实战配置:综合NAT策略

入站:Port Forward规则处理prod.example.com的HTTPS请求,映射到生产环境的Web服务器。
出站:手动Outbound NAT为每个VLAN配置规则,保障出站流量隔离。我的硬件通过生产VLAN的规则上传数据,测试显示吞吐量稳定。
硬件场景:结合Port Forward和Outbound NAT,硬件既能接收SaaS命令(入站),又能上传数据(出站),单IP利用率显著提升。
NPTv6(未来扩展):当前用IPv4,但NPTv6为IPv6网络的前缀转换提供了可能,适合ISP支持IPv6时升级。

硬件通信:NAT与子域名的结合
我的SaaS硬件通过prod.example.com与服务通信,NAT策略确保其双向通信。

入站:Port Forward映射WAN:443到192.168.10.10:443,配合HAProxy根据子域名分发流量。
出站:Outbound NAT规则让硬件出站请求使用公网IP,日志显示连接正常。
安全:用别名(Firewall → Aliases)定义硬件IP范围,限制未授权访问。
实践:我用curl测试硬件请求,确认数据成功上传到SaaS。

VPN: 团队vpn后续我会研究是否能用OPNsense