包含关键字 typecho 的文章

这个工具的核心目标很直接:给定一段多行文本和多条条件,快速得到“保留匹配行”或“删除匹配行”的结果。实现上我把逻辑拆成两层:过滤引擎负责纯文本计算,页面逻辑负责参数整理、错误提示、复制与下载。

在线工具网址:https://see-tool.com/text-line-filter
工具截图:

过滤引擎的输入约定

过滤函数接收三部分数据:原始文本、条件数组、选项对象。选项统一收口后,后续分支会非常清晰。

function filterLines(inputText, filterConditions, options) {
  const {
    filterMode = 'contains', // contains | exact
    useRegex = false,        // 是否启用正则
    ignoreCase = true,       // 是否忽略大小写
    matchAll = false,        // false=任一命中,true=全部命中
    action = 'keep'          // keep | remove
  } = options

  if (!inputText || !filterConditions || filterConditions.length === 0) {
    return []
  }

  const lines = inputText.split('\n')
  const matchedLines = []
  // 后续按行处理
}

这里有两个关键点:

  • 条件以数组传入,避免在核心函数里再做字符串切分。
  • 空输入直接返回空数组,调用方只需要处理“有结果/无结果”两种状态。

单行匹配:普通文本与正则双通道

每一行都会遍历全部条件,得到一个布尔数组 matchResults。这个数组是后面 AND/OR 逻辑的基础。

for (const line of lines) {
  const matchResults = []

  for (const condition of filterConditions) {
    let isMatch = false

    if (useRegex) {
      const flags = ignoreCase ? 'i' : ''
      const regex = new RegExp(condition, flags)
      isMatch = regex.test(line)
    } else {
      const searchText = ignoreCase ? condition.toLowerCase() : condition
      const lineText = ignoreCase ? line.toLowerCase() : line

      if (filterMode === 'contains') {
        isMatch = lineText.includes(searchText)
      } else {
        isMatch = lineText === searchText
      }
    }

    matchResults.push(isMatch)
  }
}

这段实现解决了三个常见需求:

  • 模糊匹配includes 处理“行内包含关键词”。
  • 整行匹配=== 处理“整行完全一致”。
  • 正则匹配:由用户条件直接构造 RegExp,支持复杂表达式。

多条件组合:OR 与 AND

工具支持两种条件关系:

  • matchAll = false:任意一个条件命中即视为命中(OR)
  • matchAll = true:所有条件都命中才算命中(AND)

实现非常直接:

const finalMatch = matchAll
  ? matchResults.every(result => result)
  : matchResults.some(result => result)

有了 finalMatch 后,再叠加动作类型:

if ((action === 'keep' && finalMatch) || (action === 'remove' && !finalMatch)) {
  matchedLines.push(line)
}

这一步把“保留匹配”和“删除匹配”统一到了同一套流程里,避免写两份几乎重复的过滤逻辑。

正则错误处理

正则场景最容易出现语法错误(比如括号未闭合)。引擎层统一用 try/catch 包裹,在异常时抛出可读错误:

try {
  // 过滤主流程
} catch (error) {
  throw new Error('正则表达式语法错误:' + error.message)
}

这样页面层只需要捕获一次并提示用户,不需要关心底层失败细节。

页面逻辑:参数整理与结果回填

页面层主要做四件事:

  1. 校验输入文本和条件是否为空。
  2. 把条件文本按换行拆成数组并过滤空行。
  3. 调用过滤引擎并拿到结果数组。
  4. join('\n') 回填到输出框。

核心调用方式:

const conditions = filterConditionsText
  .split('\n')
  .filter(condition => condition.trim())

const result = TextLineFilter.filterLines(inputText, conditions, {
  filterMode,
  useRegex,
  ignoreCase,
  matchAll,
  action
})

outputText = result.length > 0 ? result.join('\n') : ''

这里刻意让页面层只承担“数据进出”,把匹配规则全部留在引擎层,后续扩展新选项时改动面会更小。

复制与下载的JS闭环

过滤完成后,工具支持直接复制和导出文本。复制优先使用现代剪贴板 API,失败时降级到 textarea + execCommand('copy'),保证更多浏览器可用。

下载则通过 Blob 生成文本文件,并使用临时 <a> 标签触发保存:

const blob = new Blob([outputText], { type: 'text/plain;charset=utf-8' })
const url = URL.createObjectURL(blob)

const a = document.createElement('a')
a.href = url
a.download = `过滤结果-${dateStr}.txt`
a.click()

URL.revokeObjectURL(url)

到这里,文本输入、条件匹配、结果输出、复制下载就形成了完整的功能链路。


一、问题

很多开源框架默认只提供英文 API 文档。对于中文用户而言,阅读这些文档通常需要借助机器翻译或查阅社区文章,这带来了两大痛点:

  • 翻译失真:专有名词翻译不准确,导致语义偏差,增加了理解成本。
  • 文档滞后:一旦代码接口更新,非官方的中文说明往往来不及跟进,导致文档与代码脱节。

LazyLLM 认为:多语言支持不是锦上添花,而是基础能力。

我们既要提供原生的中文使用体验,又要保证文档与代码的严格同步。


二、难点

在工程实现上,维护一套高质量的双语文档系统并不像看起来那么简单,主要面临以下陷阱:

1️⃣源码臃肿与污染

如果将中英文 Docstring 同时写在源码中(例如一段英文后紧跟一段中文),会导致源码文件被大量说明文字淹没,严重影响代码的可读性与维护效率。

2️⃣ 多语言同步维护困难

如果不同语言的文档分散在不同位置(如 Wiki、外部站点),或者由不同的人群(官方 vs 社区)维护,很容易出现结构不一致或版本不同步的问题。

3️⃣ IDE 与包分发的可见性难题

即使在 docs/ 目录下把文档写得再好,如果发布的 Python 包中没有包含原生的 Docstring,开发者在 IDE(如 VS Code, PyCharm)中进行悬浮提示(Hover)或代码补全时,依然只能看到空白或英文,无法享受“原生”的中文开发体验。

综上,“在不污染源码的前提下,实现工程化的原生双语 API 文档”是一个极具挑战的目标。


三、LazyLLM 的解决方案

LazyLLM 的核心思想是“文档独立于实现维护,但在构建与运行时按需注入”。关键策略如下:

  • 源码整洁:源码中不保留任何冗长的 API 文档,保持代码逻辑的纯净。
  • 集中维护:中英文 API 文档以人工校对的形式,统一维护在 lazyllm/docs/*.py 映射文件中。
  • 构建时注入(For IDE/Release):提供工具链,在打包发布或本地开发时,将指定语言的文档自动“回写”入 Python 源码的 \_\_doc\_\_ 中,确保 IDE 的静态分析能正确抓取文档。
  • 运行时注入(For REPL/Runtime):支持运行时挂载,利用环境变量驱动文档的动态加载,使得在线文档站点构建和交互式环境(REPL)体验互为补充。
  • 强制检查(CI Guardrails):集成严格的自动化文档检查机制,确保每一个新增或修改的接口都必须同步更新文档,否则无法通过 CI 验证。

简而言之:API 文档由人工编写在外部(lazyllm/docs/*.py),工程工具负责将其“注入”回代码对象。既保证了源码的轻量化,又提供了原生的用户体验。


四、使用示例与预期产出

4.1 运行时动态插入文档

LazyLLM 利用 Python 的动态特性,允许在导入包时通过环境变量自动加载文档。这不会修改磁盘上的文件,仅影响内存中的对象。

export LAZYLLM_INIT_DOC=True            # 启用文档初始化
export LAZYLLM_LANGUAGE=CHINESE         # 设置语言为中文 (或 ENGLISH)

python                                  # 进入 Python 交互环境
>>> from lazyllm import pipeline        # 导入 pipeline
>>> help(pipeline)                      # 查看帮助文档

可得到如下输出:

Help on class Pipeline in module lazyllm.flow.flow:

class Pipeline(LazyLLMFlowsBase)
 |  Pipeline(*args, post_action=None, auto_capture=False, save_result=None, **kw)
 |  
 |  一个形成处理阶段管道的顺序执行模型。
 |  
 |   ``Pipeline`` 类是一个处理阶段的线性序列,其中一个阶段的输出成为下一个阶段的输入...
 ...

4.2 文档开发与站点构建

开发者或文档贡献者可以通过以下流程参与文档维护和站点生成。

第一步:环境准备

首先安装LazyLLM及其依赖:

git clone https://github.com/LazyAGI/LazyLLM.git
cd LazyLLM
pip install -r requirements.txt
pip install -r docs/requirements.txt    # 安装文档生成工具链依赖

第二步:文档维护

文档主要分为两部分:

  • 教程与指南:Markdown 文件,分别位于 docs/zh 和 docs/en (这部分非本文重点)。
  • API 文档:Python 脚本,维护在 lazyllm/docs/*.py 中(本文的重点)。

我们使用特定的注册函数来关联代码与文档,主要涉及三类内容:

  • 中文文档;
  • 英文文档;
  • 代码示例。
# 示例:为 `Pipeline` 类添加中文文档
add_chinese_doc('Pipeline', """\
一个形成处理阶段管道的顺序执行模型。
...""")

# 示例:为 `Pipeline` 类添加英文文档
add_english_doc('Pipeline', """\
A sequential execution model that forms a pipeline of processing stages.
...""")

# 示例:为 `Pipeline` 添加示例代码
add_example('Pipeline', """\
>>> import lazyllm
>>> ppl = lazyllm.pipeline(
...     stage1=lambda x: x+1,
...     stage2=lambda x: f'get {x}'
... )
>>> ppl(1)
'get 2'
""")

第三步:源码静态注入

为了让 IDE(VS Code, PyCharm)能够显示中文提示,我们需要将文档物理写入到源码文件中。这是一个可逆的操作。

export LAZYLLM_INIT_DOC=True       # 启用文档初始化
export LAZYLLM_LANGUAGE=CHINESE    # 设置语言为中文 (或 ENGLISH)
# 运行注入脚本,这会修改本地的 Python 源码文件
python docs/add_docstrings.py      # 向代码对象**写入**文档

注:此步骤修改了磁盘文件。在提交代码前,通常建议清理或恢复源码,除非是发布流程的一部分。

下图当鼠标悬停在 TrainableModule 上就可以显示出对应的文档:

类似的,当设置语言为英文后完成上述流程,可获得:

第四步:生成文档站点

站点构建脚本会结合静态 Markdown 和动态注入的 API 文档生成完整的 HTML 网站。

# 准备静态资源
cp -r docs/assets docs/zh               # 复制静态资源到中文目录
cp -r docs/assets docs/en               # 复制静态资源到英文目录
python docs/gen_mkdocs_yaml.py          # 根据语言变量生成 mkdocs.yml
mkdocs serve -a localhost:1314          # 启动本地预览

启动后,打开浏览器填入地址就可访问本地部署的文档了:

类似的,当设置语言为英文后完成上述流程,可获得:

第五步:在线双语文档

LazyLLM 利用 [Read the Docs](https://readthedocs.org/) 托管在线文档,为用户提供能够无缝切换的中英双语阅读体验。其双语构建与部署流程如下:

1️⃣项目结构配置

在 Read the Docs 上创建两个独立的项目:主项目(英文版)和子项目(中文版)。

将中文项目配置为英文项目的“Translation”子项目。这样,URL 会根据语言自动路由,例如 /en/latest/和 /zh/latest/。

2️⃣构建环境区分

这是实现双语的关键。我们在 Read the Docs 的后台管理界面中,分别为两个项目配置不同的环境变量:

  • 英文项目:默认配置(或显式设置 LAZYLLM_LANGUAGE=ENGLISH)。
  • 中文项目:显式设置环境变量 LAZYLLM_LANGUAGE=CHINESE。

3️⃣动态构建流程

当 Read the Docs 触发构建时,构建脚本会读取上述环境变量,执行以下差异化操作:

  • 配置文件生成:docs/gen\_mkdocs\_yaml.py 脚本根据语言变量,动态生成对应的 mkdocs.yml(加载中文导航 nav\_zh.yml 或英文导航 nav\_en.yml)。
  • API 文档注入:构建过程中导入 lazyllm 包时,初始化逻辑会根据语言变量,将 lazyllm/docs/*.py 中对应的中文或英文文档注入到内存对象中。
  • 页面渲染:最终,MkDocs 生成器从内存对象中提取出已经是目标语言的 Docstring,渲染成 HTML 页面。

通过这种“同一份代码,不同环境配置”的策略,我们无需维护两份割裂的代码库,即可自动生成完全同步的双语 API 文档站点。

预期效果小结:

  • IDE/REPL:悬浮查看源码时,看到的是当下环境语言对应的原生中文 Docstring。
  • Web 站点:API 文档页面准确显示中文描述(因为构建时注入了中文 Docstring)。
  • 流程一致性:无论 Web 端还是 IDE 端,数据源均来自同一份 lazyllm/docs/*.py,杜绝版本分裂。

五、我们是如何做到的

5.1 主要仓库脚本体系

LazyLLM 的文档工程化主要由以下几个核心脚本支撑:

1. 运行时动态注入 (Runtime Injection)

这是文档与代码解耦的基石。

  • 机制

    Python 允许在运行时修改对象的 \_\_doc\_\_ 属性。在 lazyllm/docs/init.py 中,我们检查 LAZYLLM\_INIT\_DOC 环境变量。如果启用,则调用 utils.py 中的逻辑,利用反射机制(getattr, \_\_dict\_\_)定位到内存中的类或函数,将预加载的文本挂载上去。

  • 优势

    实现“零侵入”。源码在磁盘上保持纯净,适应生产环境对加载速度的极致要求(直接关闭文档加载),同时满足开发环境的文档查阅需求

2. Docstring 注入工具 (Static Injection)

  • 入口

    docs/add_docstrings.py。

  • 核心逻辑

    该工具使用 lazynote.manager.SimpleManager 遍历 lazyllm 包。它支持两种模式:

    Fill(注入):将内存中的文档写入磁盘源码文件。

    Clear(清理):清除源码中的 Docstring,恢复代码的“裸”状态。

  • 作用

    解决了静态分析工具(如 IDE)无法识别运行时修改的问题。它是连接“动态文档”与“静态源码”的桥梁。

3. 自动化配置 (Configuration as Code)

  • mkdocs.yml 不是静态文件,而是由 docs/gen\_mkdocs\_yaml.py 根据 LAZYLLM_LANGUAGE 动态生成。
  • 允许中英文文档拥有完全独立的目录结构(docs/zh vs docs/en)和导航菜单(docs/nav_zh.yml),实现了不同语言版本文档的独立演进能力。

4. 文档一致性校验 (Integrity Check)

为了从机制上杜绝“代码更新但文档滞后”的问题,我们引入了强制性的检查脚本 tests/doc\_check/test\_doc\_api\_check.py。

  • 基于 Inspect 的全量扫描

    脚本利用 Python 标准库 inspect 递归遍历 lazyllm 包下的所有类与公开方法(Public Methods)。它会模拟文档注入过程,然后断言内存中的每一个 API 对象是否存在非空的 \_\_doc\_\_ 属性。

  • 智能继承识别

    检查机制具备语义理解能力。如果子类覆盖了父类方法但未重写文档,或者直接继承了父类行为,检查逻辑会自动回溯父类(Ancestors)的文档状态,确保继承链上的文档完整性,避免误报。

  • CI 流水线集成

    我们将此检查作为不可绕过的关卡集成到了 .github/workflows/main.yml 中。在 doc_check 任务里,任何 Pull Request 如果引入了未编写文档的新接口,CI 将直接失败。这确保了主干分支上的每一行代码,都时刻处于“文档齐备”的状态。

5.2 关键技术点剖析:SimpleManager 的工作原理

SimpleManager 是 LazyLLM 文档注入工具的核心执行器,其主要任务是将内存中动态加载的文档(Runtime Docstring)准确地回写到静态源代码(Static Source Code)中。这一过程并非简单的文本替换,而是涉及AST(抽象语法树)操作与运行时对象映射的复杂协同。

实现代码主要位于 docs/scripts/lazynote/manager/simple.py 及其依赖的 BaseManager 和 BaseEditor 中。我们将从三个维度剖析其关键逻辑:

1. 静态与动态的“双重映射” (The Runtime-Static Bridge)

这是该模块最核心的技术难点:如何知道源码文件中的某一个 def foo(): 对应内存中的哪个对象,从而获取其 docstring?

即使我们在源码中没有写文档,LazyLLM 的启动机制(lazyllm/docs/\_\_init\_\_.py)已经将文档挂载到了内存对象(如 Pipeline 类)的 \_\_doc\_\_ 属性上。SimpleManager 利用 BaseEditor (基于 LibCST) 实现了这种连接:

  • AST 遍历:不仅解析文件结构,还维护上下文(如当前类名)。
  • 运行时反射:在遍历 AST 节点(FunctionDef, ClassDef)时,实时去内存模块中查找对应的 Python 对象。

我们可以从 editor/base.py 中看到这一关键逻辑:

# 伪代码逻辑展示 (提取自 BaseEditor)
def leave_FunctionDef(self, original_node, updated_node):
    # 1. 构建当前函数的全名 (e.g., "Pipeline.flow")
    full_name = f"{self.current_class}.{original_node.name.value}"
    
    # 2. 从加载的 module 中获取真实的运行时对象
    obj = self._get_obj_by_name(full_name) 
    
    # 3. 获取运行时对象上的 docstring (这里包含了注入的中文/英文文档)
    docstring = obj.__doc__ if obj else None
    
    # 4. 更新 AST 节点
    return self._update_node_with_new_docstring(original_node, updated_node, docstring)

通过这种方式,SimpleManager 充当了“搬运工”,把内存里“注入好的文档”搬回了“源代码文件”里。

2. 修改策略模式 (Strategy Pattern)

SimpleManager 本身逻辑非常轻薄,它将具体的修改行为委托给 DocstringHandler。这不仅仅是为了代码整洁,更是为了支持“注入(fill)”、“清除(clear)”等多种操作模式。

在 manager/simple.py 中:

class DocstringHandler:
    @staticmethod
    def handle_fill(old_docstring, node_code):
        # 填充模式:主要用于将内存 docstring 写入文件
        # 如果文件中已有(old_docstring),通常 logic 会保留它;
        # 但结合 BaseEditor 逻辑,如果 Runtime 有值且文件无值,这里即实现了注入。
        if old_docstring:
            return f"{old_docstring}"
        return None

    @staticmethod
    def handle_clear(old_docstring, node_code):
        # 清理模式:返回 None,指示 AST 转换器删除文档节点
        return None

当我们运行 python docs/add_docstrings.py --replace 时,实际上是先执行了一次 clear 策略(清洗源码),再执行一次 fill 策略(从内存回填),从而实现了文档的彻底更新或语言切换。

3. 基于 LibCST 的无损修改

为什么不使用 Python 内置的 ast 模块或正则表达式?

  • 正则表达式:无法处理复杂的嵌套结构和多行字符串。
  • Python \`ast\` 模块:在解析和回写时会丢弃源码中的注释和格式信息(Parsing is destructive)。

SimpleManager 继承自依赖 LibCST (Concrete Syntax Tree) 的架构。LibCST 的最大优势是 保留代码风格(Preserve Formatting)

当插入或修改 Docstring 时,它能智能处理缩进和换行,确保注入文档后的代码依然符合 PEP 8 规范,且不会破坏代码的其他部分(如注释、空行)。

# editor/base.py 中的 update 逻辑
def _update_node_with_new_docstring(self, ..., docstring):
    # 构建新的三引号字符串节点
    new_docstring_node = cst.SimpleStatementLine(...)
    
    # 智能插入到函数体/类定义的开头,处理缩进
    # ...
    return updated_node.with_changes(body=new_body)

SimpleManager 的精妙之处在于它打通了 Source Code (文件) -> AST (结构) -> Runtime Object (内存) -> Source Code 的闭环。

它使得 LazyLLM 能够拥有“一份干净的代码”和“多份丰富的文档”,并在需要时随时将二者融合。

4. 架构与流程可视化 (Architecture & Flow)

为了更直观地理解上述机制,类关系图展示了 SimpleManager 如何继承基础能力,并未借助 LibCST 的 Transformer 机制修改代码;而序列图则揭示了从脚本启动遍历,到 AST 解析,再到从运行时获取文档并回写的完整闭环。

类关系图 (Class Structure)

上图展示了模块间的静态依赖关系:

  • BaseManager 提供了通用的遍历(file traverse)和文件读写能力。
  • SimpleManager 关注于具体的文档生成逻辑(即 gen_docstring),通过委托给 DocstringHandler 实现了注入、清理等不同策略的解耦。
  • BaseEditor 则是 AST 操作的实施者,它继承自 LibCST 的 Transformer,负责深入到代码的类和函数定义中进行精细化修改。

核心执行流程 (Execution Sequence)

上图还原了 add_docstrings.py 运行时的动态过程:

1️⃣启动与遍历

脚本初始化 SimpleManager,开始扫描 lazyllm 包下的所有模块。

2️⃣AST 变换

对每个模块,先解析成 AST 树,然后启动 BaseEditor 访问器遍历树中的每个节点(类或函数)。

3️⃣运行时桥接

这是最关键的一步。BaseEditor 在访问 AST 节点时,使用全限定名(Qualified Name)在内存中查找对应的 Python 对象,并提取其 \_\_doc\_\_ 属性——这个属性里正包含了我们预先注入好的中文或英文文档。

4️⃣回写源码

修改后的 AST 树被转换回代码字符串,并覆盖写入原文件,从而完成了“从内存到文件”的文档固化。


六、总结

LazyLLM 的文档方案通过“运行时动态挂载”和“基于 LibCST 的静态注入”,成功解决了开源界长期存在的双语文档维护难题。

  • 对开发者:源码清爽,无维护负担。
  • 对用户:所有接触点(Web、IDE、REPL)均能获得原生的母语支持。

这不仅仅是翻译工作,更是一次关于“开发者体验(DX)”的工程化实践。


欢迎升级体验 LazyLLM最新版本,请大家去github上点一个免费的star,支持一下~

LazyLLM项目仓库链接🔗:


更多技术内容,欢迎移步 gzh "LazyLLM" 讨论!

“在软件定义一切的时代,项目管理的核心已从单纯的流程管控,转向对智能协作与研发效能的深度赋能。”——改编自《2025全球研发效能白皮书》

随着人工智能技术的爆发式增长,2026年的研发环境发生了翻天覆地的变化。传统的瀑布式或敏捷管理工具已难以满足AI模型训练、大数据处理及复杂算法迭代的需求。新一代项目管理工具必须具备智能化调度、代码级关联、自动化洞察以及跨团队协作的核心能力。本文将中立、客观地盘点九款在2026年表现卓越的项目管理工具,涵盖从老牌劲旅到新兴势力,重点分析其在AI与研发场景下的独特价值,助力团队在智能化浪潮中精准选型。

一、国产深耕与全生命周期管理类

1. 禅道(ZenTao):经典架构的智能化新生

作为国产项目管理软件的常青树,禅道在2026年完成了重要的架构升级。它依然坚持“产品-项目-测试”三位一体的核心理念,完美契合Scrum与DevOps流程,为国内研发团队提供了最熟悉的操作范式。

  • 核心优势:禅道最新的企业版深度集成了AI辅助需求分析功能,能够自动识别需求文档中的逻辑漏洞并生成测试用例建议。其内置的代码仓库关联功能,支持从Git提交记录直接追溯至具体任务,实现了从需求到代码再到测试的研发链路闭环。此外,其私有化部署方案在数据安全性上达到了金融级标准,深受大型国企及科研机构青睐。
  • 适用场景:特别适合中大型软件企业,尤其是那些需要严格遵循研发规范、注重数据隐私与安全、希望引入AI提效但不愿迁移云端的团队。

2. Jira:全球生态的AI增强版

Jira依然是全球研发团队的事实标准。在2026年,Atlassian推出了基于大模型的“Jira Intelligence”引擎,使其在复杂工程管理上更具前瞻性。

  • 核心优势:其强大的插件生态系统允许团队无缝集成各类AI编码助手(如GitHub Copilot等)。Jira的新特性在于预测性交付分析,它能根据历史速率、当前代码复杂度及团队成员负荷,精准预测项目延期风险并提供优化建议。其看板自定义程度极高,支持复杂的泳道规则和自动化工作流,能够适应从微服务架构到单体应用的各种开发模式。
  • 适用场景:适用于跨国团队、开源社区以及已经深度绑定Atlassian生态的企业。对于需要高度定制化流程、全球化协作以及复杂依赖管理的AI研发项目,Jira仍是首选。

3. Azure DevOps:微软生态的一站式解决方案

Azure DevOps在2026年进一步强化了其与Azure云服务的深度整合,成为云原生AI项目的强力助推器。

  • 核心优势:提供了从需求规划、代码托管、CI/CD流水线到测试管理的完整闭环。其AI驱动的代码审查功能能够自动检测潜在的安全漏洞和性能瓶颈。结合Azure ML,项目经理可以直接在看板中监控模型训练任务的状态和资源消耗,实现了算法工程与传统软件工程的统一管理。
  • 适用场景:适合深度使用微软技术栈、依托Azure云平台进行AI模型开发与部署的企业级团队,特别是在云原生和DevSecOps领域有强烈需求的组织。

二、敏捷协作与可视化创新类

4. Trello:轻量级看板的智能进化

Trello以其直观的卡片式看板闻名,2026年的版本引入了“智能卡片”概念,让轻量级管理也能拥有大智慧。

  • 核心优势:通过自然语言处理,用户可以语音指令创建任务,系统自动分配标签、优先级和截止日期。其Butler自动化机器人现在具备学习能力,能根据团队习惯自动归档已完成任务、触发依赖项提醒或在卡片停滞时发送预警。界面简洁,上手零成本。
  • 适用场景:适合初创AI团队、小型算法小组、非技术背景的产品经理进行快速原型管理,或作为大型团队中特定敏捷小队的协作工具。

5. Teambition:阿里生态的高效协同

Teambition依托于阿里云生态,在2026年实现了与通义千问等大模型的深度打通,成为国内混合办公场景下的佼佼者。

  • 核心优势:其最大的亮点是多端实时协同与文档一体化。在研发会议中,Teambition能实时转录语音并自动生成任务待办,直接关联到项目看板。其甘特图功能针对资源负载进行了优化,能直观展示AI算力资源的分配情况,并与钉钉消息无缝联动,确保信息即时触达。
  • 适用场景:广泛适用于使用钉钉办公的企业,特别是需要频繁进行跨部门沟通、文档协作紧密的混合型研发团队,以及追求国产化信创适配的组织。

6. Asana:智能工作流与目标对齐

Asana在2026年强化了其“智能工作流”引擎,专注于将战略目标拆解为可执行的任务。

  • 核心优势:Asana AI能够自动识别任务之间的依赖关系,并在风险出现时重新规划路径。其Goals(目标)可以实时追踪OKR完成情况,将底层代码提交、测试通过率等指标自动汇聚至高层目标视图,确保研发动作与公司战略高度对齐。界面设计极具美感,用户体验流畅。
  • 适用场景:适合注重目标管理、需要清晰可视化项目全景的中大型团队,特别是那些强调跨职能协作(如产品、设计、研发混合编组)的创新型组织。

三、综合平台与新兴智能类

7. Microsoft Project (with Copilot):企业级的严谨规划

Microsoft Project在融入Microsoft 365 Copilot后焕发了第二春,继续统领超大型复杂项目的管理。

  • 核心优势:它保留了最强大的关键路径法(CPM)和资源平衡算法,是处理百万级任务量项目的唯一选择。Copilot的加入使得项目经理可以通过对话生成复杂的排程计划,并自动模拟不同资源投入下的项目结果(What-if分析),为高层决策提供数据支撑。
  • 适用场景:适合超大型AI基础设施建设项目,如数据中心建设、城市大脑开发或长达数年的基础模型研发,这类项目对时间、成本和资源的精确控制要求极高。

8. ClickUp:All-in-One的超级聚合

ClickUp在2026年进一步巩固了其“一个App替代所有”的定位,成为全能型选手的代表。

  • 核心优势:ClickUp AI不仅能写文档,还能自动生成代码片段审查报告和测试脚本。它将文档、目标、聊天、白板和任务整合在一个视图中,极大减少了上下文切换的成本。其仪表盘支持实时连接GitHub/GitLab数据,动态展示研发效能指标(如DORA指标),让管理更加数据化。
  • 适用场景:适合追求极致效率、希望在一个平台上解决所有协作问题的全栈研发团队,尤其是远程办公分布广泛、成员角色多样的初创及成长型企业。

9. Monday.com:可视化工作流的自动化大师

Monday.com以其色彩丰富、高度可视化的界面著称,是数据驱动型管理的典范。

  • 核心优势:2026版的Monday.com增强了数据驱动决策能力。它能将研发过程中的埋点数据、服务器监控指标直接映射到项目面板上,一旦系统报错率超过阈值,自动触发应急任务流程。其公式列和自动化逻辑让非技术人员也能构建复杂的研发管理流,无需编写代码即可实现高度定制。
  • 适用场景:适合重视视觉化管理、需要快速响应市场变化的AI应用层开发团队,以及需要将研发进度以直观图表向高层汇报的场景。

结语

2026年的项目管理工具市场呈现出智能化、垂直化、生态化的三大趋势。无论是坚守经典流程且安全可靠的禅道,还是拥抱全球生态的Jira,亦或是主打轻量智能的Trello与全能聚合的ClickUp,每款工具都在特定的维度上为AI与研发团队提供了强有力的支撑。

选择工具的本质,是选择一种协作文化工作流范式。没有绝对完美的工具,只有最适合团队当前发展阶段、技术栈及安全合规要求的解决方案。建议企业在选型时,充分考量自身的规模、对AI功能的依赖程度以及现有生态兼容性,从而在激烈的技术竞争中构建起高效的研发护城河,推动创新落地。

图片

文末有源码下载链接

    在现代云原生架构中,容器化已经成为应用部署的标准方式。Docker通过“镜像+容器”的机制,让任何人可以轻松地:

  •     打包应用及其所有依赖
  •     所有环境保持一致:你的电脑能跑,服务器也能跑
  •     启动快、占用少、易扩展
  •     配合K8s实现自动扩容、滚动更新

1、为什么微服务都在用Docker?

    Docker出现之前地痛点:

  • Go代码在本机运行正常,部署到服务器后各种依赖缺失
  • 配置环境、装依赖、升级版本非常麻烦
  • 多服务部署搞得像“盘丝洞”,环境乱成一锅粥
  • 回滚、扩容、迁移都很困难

    Docker出现后解决了什么?

  • 将应用、依赖、运行环境一起封装成镜像
  • 镜像是不可变的,哪里都能跑
  • 启动容器只需要几百毫秒
  • 天然适配K8s、CI/CD

    一句话:用Docker = 构建“可复制、可迁移、有保障”地部署流程

2、准备一套完整的web服务

package server
func StartHTTPServer(host string, port int) error {
    zap.L().Info("正在初始化服务器...")
    mode := viper.GetString("server.mode")
    switch mode {
    case "dev":
        gin.SetMode(gin.DebugMode)
    case "prod":
        gin.SetMode(gin.ReleaseMode)
    case "test":
        gin.SetMode(gin.TestMode)
    default:
        gin.SetMode(gin.DebugMode)
    }
    components.Init()         // 初始化各个组件
    migrates.DoMigrate()      // 数据库迁移
    services.InitServices()   // 初始化服务层
    r := routers.InitRouter() // 初始化路由
    s := &http.Server{
        Addr:           fmt.Sprintf(`%s:%d`, host, port),
        Handler:        r,
        ReadTimeout:    time.Duration(viper.GetInt("server.readtimeout")) * time.Second,
        WriteTimeout:   time.Duration(viper.GetInt("server.writertimeout")) * time.Second,
        MaxHeaderBytes: 1 << 20,
    }
    // 启动HTTP服务
    go func() {
        zap.L().Info("HTTP服务器启动了", zap.String("addr", s.Addr))
        if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            zap.L().Fatal("HTTP服务器启动失败", zap.Error(err))
        }
    }()
    //告诉k8s或者docker我准备好了
    services.SetReady()
    // 捕获系统信号
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, os.Interrupt)
    <-quit
    zap.L().Warn("收到关闭信号,正在优雅退出...")
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    s.SetKeepAlivesEnabled(false) // 避免新的连接进来
    if err := s.Shutdown(ctx); err != nil {
        fmt.Println("服务器被强制关闭了:", err)
    }
    zap.L().Info("服务器优雅退出了")
    return nil
}

3、编写Dockerfile(敲黑板)

Go容器镜像最佳实践---多阶段构建(multi-stage build)

# 第一阶段:构建阶段
FROM golang:1.23-alpine AS builder
#LABEL 指令用来给镜像添加一些元数据(metadata),以键值对的形式
LABEL maintainer="Codee君"
#设置容器Go语言环境变量
ENV GO111MODULE=on
ENV GOPROXY=https://goproxy.cn,direct
#为 RUN、CMD、ENTRYPOINT、COPY 和 ADD 设置工作目录,就是切换目录
WORKDIR /go/release
#COPY 拷贝文件或目录到容器中,跟ADD类似,但不具备自动下载或解压的功能
COPY . .
#RUN 构建镜像时运行的指令
#sed 命令用于文本处理,这里是用来替换 Alpine Linux 的软件源镜像为中科大的镜像
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
RUN apk update && apk add tzdata
#安装swago,并自动生成API文档
RUN go install github.com/swaggo/swag/cmd/swag@v1.8.10 
RUN swag init  --parseDependency=true
#go build 编译程序
RUN CGO_ENABLED=0 GOOS=linux go build -p 1 -ldflags="-w -s" -a -installsuffix cgo -o golang_per_day .
# 第二阶段:运行阶段
FROM alpine:latest
# 把第一阶段构建的二进制文件和依赖拷贝到新的镜像中
COPY --from=builder /go/release/golang_per_day /
COPY --from=builder /go/release/configs /configs
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
#EXPOSE 声明容器的服务端口(仅仅是声明)
EXPOSE 8080
#CMD 运行容器时执行的shell命令
CMD ["/golang_per_day_30","server","-c", "/configs/config.yaml"]

为什么要多阶段构建?

  • 避免把整个Go编译环境打进最终的镜像
  • builder阶段 > 1GB
  • alpine阶段 < 100MB
  • 最终镜像只有几十MB,非常适合云原生

4、构建Docker镜像

在项目根目录执行:

$ docker build --no-cache -t imoowi/golang_per_day_30 .

图片

5、把镜像push到仓库,这里用的docker的公共仓库,如果你需要私有仓库,请去其他云厂商购买或者自己搭建。

$ docker login -u 你的用户名

i Info → A Personal Access Token (PAT) can be used instead.
          To create a PAT, visit https://app.docker.com/settings
Password: 输入密码
Login Succeeded
$ docker push imoowi/golang_per_day_30
Using default tag: latest
The push refers to repository [docker.io/imoowi/golang_per_day_30]
68578effd277: Pushed
94ffa90d46ab: Pushed
b7012b08af98: Pushed
0f4970872d39: Pushed
418dccb7d85a: Mounted from library/alpine
latest: digest: sha256:bad95cb900e0758a4cfeb7d8e3fe284911b1d60bd5fd19ab19b69dd2044b5ecb size: 1364

6、运行容器

docker
8080
8080

图片

浏览器访问http://localhost:8080/swagger/index.html

图片

7、用Docker Compose管理多个服务(Go+MySQL+Redis)

7.1 配置docker-compose.yml文件

version: "3"
networks:
    #定义一个名为codee_jun的网络
    codee_jun:
      driver: bridge
services:
  mysql:
    container_name: mysql
    image: mysql:5.7
    ports:
        - 3306:3306
    environment:
        MYSQL_ROOT_PASSWORD: "123456"
    volumes:
        # 数据库挂载目录
        - ./docker-data/mysql/data:/var/lib/mysql
        # 数据库初始化脚本挂载目录
        - ./configs/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    command: [
        '--character-set-server=utf8mb4',
        '--collation-server=utf8mb4_unicode_ci',
        '--max_connections=3000'
    ]
    restart: always
    networks:
        - codee_jun
  redis:
    container_name: redis
    image: redis:latest
    command: redis-server --requirepass 123456
    ports: 
        - 6379:6379
    volumes:
        - ./docker-data/redis/db:/data
    restart: always
    networks:
        - codee_jun
  golang_per_day_30:
    container_name: golang_per_day_30
    build: .
    ports:
      - 8080:8080
    # 依赖的服务,必须先启动依赖的服务,才能启动当前服务
    depends_on:
        - mysql
        - redis
    volumes:
      - ./configs:/configs
    restart: always
    networks:
        - codee_jun

7.2 添加初始化sql

---configs/init.sql
CREATE DATABASE IF NOT EXISTS `golang_per_day` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `golang_per_day`;

7.2 启动服务

$ docker-compose up -d 
[+] Running 4/4
 ✔ Network golang_per_day_30_codee_jun  Created                                                                                                                                   0.1s 
 ✔ Container redis                      Started                                                                                                                                   0.6s 
 ✔ Container mysql                      Started                                                                                                                                   0.6s 
 ✔ Container golang_per_day_30          Started  

7.3 关闭服务

$ docker-compose down
 ✔ Container golang_per_day_30          Removed                                                                                                                                   0.4s 
 ✔ Container mysql                      Removed                                                                                                                                   2.1s 
 ✔ Container redis                      Removed                                                                                                                                   0.5s 
 ✔ Network golang_per_day_30_codee_jun  Removed 

7.4 注意 文件configs/config.docker.yaml 里,mysql和redis的主机地址,现在分别写的是同网络下的服务的名字,而不再是localhost,一定要注意。

#configs/config.docker.yaml
mysql:
  dsn: root:123456@tcp(mysql:3306)/golang_per_day?charset=utf8&parseTime=True&loc=Local&timeout=1000ms
redis:
  addr: "redis:6379"
  password: "123456"
  db: 0
#docker-compose.yml
version: "3"
networks:
    #定义一个名为codee_jun的网络
    codee_jun:
      driver: bridge
services:
  mysql: 这个名字就是配置文件里对应的服务名
  redis:

8、部署到K8S

8.1 Deployment(核心,部署Go服务),文件k8s/deployment.yaml

#k8s/deployment.yaml
apiVersion: apps/v1 #定义应用部署的API版本
kind: Deployment #定义部署应用
metadata: #元数据部分
  name: golang-per-day-30 #这里填写部署名称
  namespace: codee_jun #这里填写命名空间
spec:
  #replicas表示期望运行的Pod副本数量
  replicas: 1 #这里填写副本数量
  #选择器,定义如何选择Pod
  selector:
    matchLabels: #选择标签,匹配具有特定标签的Pod
      app: golang-per-day-30 #这里填写标签名称
  #模板,定义Pod的规格
  template:
    metadata: #Pod的元数据
      labels: #标签部分
        app: golang-per-day-30 #这里填写标签名称
    spec: #Pod的规格部分
      containers: #定义容器
        - name: golang-per-day-30 #这里填写容器名称
          image: golang_per_day_30:latest #这里填写镜像名称
          ports: #定义容器端口
            - containerPort: 8080 #容器内部端口
          volumeMounts: #定义卷挂载
            - name: golang_per_day_30-configmap
              mountPath: /configs/config.docker.yaml #容器内挂载路径
              subPath: config.yaml #子路径
          resources: #定义资源请求和限制
            requests: #资源请求
              cpu: 1000m #请求的CPU资源
              memory: 2Gi #请求的内存资源
            limits: #资源限制
              cpu: 2000m #限制的CPU资源
              memory: 4Gi #限制的内存资源
      volumes:
        - name: golang_per_day_30-configmap #卷名称
          configMap: 
            name: golang-per-day-30-configmap #引用的configmap名称

8.2 服务(Service) (给Pod提供稳定访问),文件k8s/service.yaml

#k8s/service.yaml
apiVersion: v1
kind: Service #定义服务
metadata:
  name: golang-per-day-30 #这里填写服务名称
  namespace: codee_jun #这里填写命名空间
spec: #服务规格
  # externalIPs:
  #   - 123.59.187.137
  ports:
    - protocol: TCP 
      port: 80 #服务端口
      targetPort: 80 #目标端口(容器端口)
  sessionAffinity: ClientIP #会话亲和性设置为ClientIP
  selector: 
    app: golang-per-day-30  #选择标签,匹配具有特定标签的Pod

8.3 ConfigMap(配置文件),文件k8s/configmap.yaml

apiVersion: v1 #定义API版本
kind: ConfigMap #定义资源类型
metadata:
  name: golang-per-day-30-configmap #这里填写ConfigMap名称
  namespace: codee_jun #这里填写命名空间
data:
  config.yaml: |
    server:
      mode: "prod"
      port: 8080
      host: 0.0.0.0
      readtimeout: 60
      writetimeout: 60
    log:
      level: "info"
    mysql:
      dsn: root:123456@tcp(mysql.codee_jun.svc.cluster.local:3306)/golang_per_day?charset=utf8&parseTime=True&loc=Local&timeout=1000ms
    redis:
      addr: "redis.codee_jun.svc.cluster.local:6379"
      password: "123456"
      db: 0
    jwt:
      secret: "golang_per_day_secret_key"
      # 24h表示24小时
      expire: 24h
    ratelimit:
      # 每秒放多少个令牌
      cap: 1000
      # 每秒取多少个令牌
      quantum: 1000

注意:mysql地址变成了mysql.codee\_jun.svc.cluster.local,redis的地址变成了redis.codee\_jun.svc.cluster.local,这是k8s集群内部服务的访问格式,你也可以用云数据库,在这里直接替换即可。

8.4 自动扩容HPA(水平自动扩容),文件k8s/hpa.yaml

apiVersion: autoscaling/v2 #定义API版本
kind: HorizontalPodAutoscaler #定义水平Pod自动扩缩容器
metadata: #元数据
  name: golang-per-day-30-hpa #这里填写HPA名称
  namespace: codee_jun #这里填写命名空间
spec: #HPA规格
  scaleTargetRef:
    apiVersion: apps/v1 #定义API版本
    kind: Deployment #定义资源类型
    name: golang-per-day-30 #这里填写Deployment名称
  minReplicas: 1 #最小副本数
  maxReplicas: 10 #最大副本数
  metrics:  #指标集合
    - type: Resource #资源指标类型
      resource: #资源指标
        name: cpu #资源名称
        target:  #资源目标值
          type: Utilization #CPU利用率目标
          averageUtilization: 80 #CPU利用率目标值
    - type: Resource
      resource:
        name: memory #内存资源名称
        target:
          type: Utilization #内存利用率目标
          averageUtilization: 80 #内存利用率目标值

8.5 Ingress (域名访问), k8s/ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: golang_per_day_30-ingress
  namespace: codee_jun
  annotations:
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-origin: "https://golang_per_day_30.com"
    nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS"
    nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"
    nginx.ingress.kubernetes.io/cors-allow-credentials: "true" # 如果需要凭证
    nginx.ingress.kubernetes.io/proxy-body-size: "1024m"
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-name: "golang_per_day_30-session"
    nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
    nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
spec:
  rules:
    - host: golang_per_day_30.com # 域名
      http: # 协议
        paths: # 路径
          - path: / # 路径
            pathType: Prefix   # 路径类型
            backend: # 后端
              service: # 服务
                name: golang-per-day-30 # 服务名称
                port: # 服务端口
                  number: 8080 # 服务端口号

8.6 部署一条龙

进入k8s目录

kubectl apply -f .

查看部署情况

$ kubectl get pods -n orth -o wide | grep golang_per_day_30
golang_per_day_30-56c4c7f685-2tm7g                    1/1     Running     0          1m10s    172.16.10.47    cn-beijing.10.16.233.81    <none>           <none>
golang_per_day_30-56c4c7f685-8q9zn                    1/1     Running     0          1m10s    172.16.10.91    cn-beijing.10.12.229.121   <none>           <none>
golang_per_day_30-56c4c7f685-mbrnv                    1/1     Running     0          1m10s    172.16.10.16    cn-beijing.10.16.233.81    <none>           <none>  
golang_per_day_30-56c4c7f685-vf8t6                    1/1     Running     0          1m10s    172.16.10.99    cn-beijing.10.12.229.121   <none>           <none>    

查看服务

$ kubectl get svc -n codee_jun | grep golang_per_day_30
golang_per_day_30                       ClusterIP   192.168.1.109    <none>        80/TCP      5m

查看ingress地址

$ kubectl get ingress -n codee_jun | grep golang_per_day_30
golang_per_day_30-ingress   nginx   golang_per_day_30.com   a.b.c.d    8080   10m

测试访问浏览器访问地址http://golang\_per\_day\_30.com,如果域名解析成功,即可访问服务

8.7 滚动更新,Deployment默认就是滚动更新,如何触发滚动更新呢?

    8.7.1 更新镜像

kubectl set image deployment/golang_per_day_30 golang_per_day_30=imoowi/golang_per_day_30:v2

    8.7.2 修改Deployment配置

kubectl apply -f deployment.yaml

    8.7.3 手动重启(强制滚动)

kubectl rollout restart deployment golang_per_day_30

*源码地址*

1、公众号“Codee君”回复“每日一Go”获取源码

2、https://pan.baidu.com/s/1B6pgLWfSgMngVeFfSTcPdg?pwd=jc1s 

人生的成长,也像 K8s 滚动更新:

不是推倒重来,而是在保持生活继续的同时,

慢慢替换掉旧的自己,让更好的版本上线。


如果您喜欢这篇文章,请点赞、推荐+分享给更多朋友,万分感谢!

创始人们!为了更深入与国内的创始人建立更密切的联系,Antler将在北京举办一天的线下活动。主题为 “Disrupt beyond borders”

活动分为两个环节:

1.预热环节: 我们将选出 12 位创始人,获得与 Antler 合伙人面对面,1-1,30 分钟 office hour 的机会,获取对项目的反馈,并有机会被快速推荐获得 Disrupt 的邀请。

2.主题论坛: 围绕“从中国打造全球化产品”展开两场小组讨论,嘉宾包括在该议题上有丰富经验的创始人、运营者和风投人士。

这不是另外一场只有夸夸其谈、商业互吹的「大会」,而是脚踏实地、展望全球的「实战场」,专为那些希望直接对话已功成身退的创始人与实战运营者、获取针对自身创业公司的真实反馈、以及汲取已踏入全球市场的团队毫无保留经验之谈的创始人而设。

你可以期待:

  • 实诚的创业故事
  • 战术级的操作建议
  • 立刻能用的实用框架
  • 来验证你的想法、挑战你的判断、认识那些从 Day 1 就瞄准国际市场的人

🗓日期:3月5日

📍地点:北京 (活动详细地址仅报名后可见)

🖐🏻报名:扫码或到 https\://hdxu.cn/1EPlA

⚡️福利:作为本次活动的合作伙伴,亚马逊云科技将为参加活动的符合条件*的创始人提供最高价值10万美元的免费云资源。

如果感兴趣,请到账号,通过置顶帖完成报名!

✦₊ 关于Antler

自创立之初,Antler (https\://www.antler.co/) 便始终致力于支持全球最具远见的创业者,并深信创新是引领世界迈向更美好未来的核心驱动力。秉承这一信念,Antler 已在全球超过30个城市设立分支机构,并在多个行业及前沿科技领域成功孵化和投资超过 1,600 家初创企业。我们专注于发掘并赋能具有高成长潜力的创新项目,以应对真实且重大的商业机遇与社会挑战。

Disrupt (https\://content.antler.co/zh-cn/disrupt) 是由 Antler 于2024年专为AI创业公司打造的4周加速计划,旨在为中国创业者提供通往国际舞台的桥梁,助力创始人实现更快、更远的发展。下一期 Disrupt 计划将于3月30日至4月24日在越南胡志明市举行。入选团队无需支付任何额外项目费用。若项目在第四周评审会上通过投资委员会审核,即可获得40万美元的投资支持,全程参与均免费。

阅读更多 Voice Agent 学习笔记:了解最懂 AI 语音的头脑都在思考什么

开发者朋友们大家好:

这里是 「RTE 开发者日报」 ,每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE(Real-Time Engagement) 领域内「有话题的 技术 」、「有亮点的 产品 」、「有思考的 文章 」、「有态度的 观点 」、「有看点的 活动 」,但内容仅代表编辑的个人观点,欢迎大家留言、跟帖、讨论。

本期编辑:@瓒an、@鲍勃

01 有话题的技术

1、通义实验室上线语音双模型:支持自然语言指令,自由控制音色与听觉场景

针对生僻字、复杂语句等容易读错的场景专项优化,生僻字读错率从 15.2% 降至 5.3%,复杂文本表现更加稳定,长文本朗读也更稳定流畅。

Fun-AudioGen-VD: 整体表现稳定,Avg-APS 达到 89.25,是该组测试中语音质量最高的模型之一。

3 月 2 日消息,通义实验室正式发布两款支持 FreeStyle 指令生成的语音双模型:Fun-CosyVoice3.5 与 Fun-AudioGen-VD。传统语音生成通常依赖固定的情绪选项或风格模板,而此次技术更新的核心突破在于摆脱预设标签,允许用户通过自然语言指令直接描述并控制声音表达、音色与场景

这两款模型均支持自然语言指令,但在具体应用方向上各有侧重:

  • Fun-CosyVoice3.5:侧重于多语种复刻与精细化表达控制。该模型新增了泰语、印尼语、葡萄牙语和越南语四种小语种。在准确性与性能方面,其生僻字读错率从 15.2% 降至 5.3%;通过强化学习技术调优,提升了韵律表现与复刻音质;同时将首包延迟降低了 35%,优化了实时交互的流畅度。
  • Fun-AudioGen-VD:聚焦于声音设计与场景化音频生成,支持「人物+场景」的一体化塑造。该模型不仅能通过指令精细控制性别、年龄、情绪甚至复杂心理等角色音色特征,还能模拟城市喧嚣、空间回声及老式广播等设备滤镜,打造带有动态互动的沉浸式听觉环境。

指令: 场景是在一家热闹的咖啡馆里。背景能听到磨豆机的嗡嗡声、瓷杯碰撞的清脆声,还有远处模糊的人声。说话人语气很松弛,就像是坐在对面跟你喝下午茶。

合成文本: 哎,你尝尝他家这个新品,味道挺特别的。我刚才还想呢,咱下周要不把老李也约出来?咱三个好久没凑一块儿坐坐了。

这两款模型的上线,使语音生成从基础的功能工具升级为可编排的创作工具。在影视动画、游戏及有声书等实际场景中,创作者能够通过自然语言快速定义目标声音,从而降低录制与调试成本。

相关文档:

https\://help.aliyun.com/zh/model-studio/cosyvoice-clone-api

(@通义实验室)

2、Core AI 框架取代 Core ML?曝苹果 WWDC 26 开发者大会将公布多项 AI 功能

3 月 1 日,彭博社记者马克·古尔曼在最新一期《Power On》通讯中表示,苹果计划在 WWDC 26 开发者大会上发布全新 Core AI 框架,取代现有的 Core ML

古尔曼表示,苹果不太可能一下子就让 Core AI 完全取代 Core ML,而且 6 月的发布会可能只是一次简单的更名,让该框架更加贴近当前的实际用途,毕竟苹果已经为 Core ML 提供大语言模型、扩散模型等集成。

并且,Core AI 框架还将帮助开发者在应用中集成第三方 AI 模型。不过目前我们并不清楚其具体实现方式,理论上来讲 MCP(模型上下文协议)是一种可行方案。

虽然此前曾有传闻称苹果 iOS 27 将类似 Mac OS X Snow Leopard 那样主打「零新功能」,以修复、提升稳定性为主。但苹果仍有可能会在 WWDC 26 上放出基于谷歌 Gemini 训练的 Apple 智能。

(@极客公园)

02 有亮点的产品

1、联想携两款桌面 AI 硬件亮相 MWC 2026:概念机器人与智能时钟重塑办公体验

3 月 2 日,2026 年世界移动通信大会(MWC)在巴塞罗那开幕。联想在现场展出了折叠游戏本、模块化笔记本等多款概念新品,并带来两款面向办公场景的 AI 硬件:桌面 AI 机器人 AI Workmate 与智能桌面时钟 AI Work Companion

这两款概念设备在形态与功能上各有侧重:

  • AI Workmate 桌面机器人:搭载英特尔酷睿 Ultra 处理器与 64GB 内存,正面配有 3.4 英寸 LCD 屏幕以显示表情。它支持语音、手势与本地 AI 处理,能扫描文档生成摘要并辅助制作 PPT。设备头部内置摄像头与微型投影模块,现场演示了投影图片、扫描纸质签名并直接发送打印的办公流程。该原型机主要探索空间与实体 AI 的应用,针对频繁语音交互可能产生的噪音问题,现场有反馈期望未来增加文字交互方式。
  • AI Work Companion 智能时钟:整体造型简约,顶部设有旋钮与自定义按键,正面屏幕主要显示日历与任务清单。它通过 USB-C 供电并可作为拓展坞为其他设备充电。其内置的 AI 「思维气泡」模式支持跨设备同步日程并自动生成每日计划,同时会监测屏幕使用时间、提醒定时休息,并在周末生成任务完成报告。

联想方面表示,这两款概念产品意在展示人工智能如何自然融入日常办公环境,在提升工作效率的同时兼顾健康的办公节奏。

(@AI 星球视界)

2、Timekettle 发布 W4 AI 翻译耳机,以骨传导与智能引擎攻克复杂环境拾音难题

3 月 1 日消息,人工智能翻译技术企业 Timekettle 宣布将首次亮相于 3 月 2 日至 5 日在巴塞罗那举行的 2026 年世界移动通信大会(MWC 2026)。继此前在 CES 和 IFA 等展会上展示创新成果后,Timekettle 此次重点展出了其高响应速度的 W4 AI 翻译耳机。

W4 翻译耳机搭载了全新的 Babel OS 2.0 系统,通过软硬件融合,主要解决跨语言交流中「语音拾取不清晰」和「翻译不准确」两大核心难题。 其系统集成了两项关键技术:

  • AI 骨传导拾音技术(AI Bone-Conduction Pickup):传统翻译耳机依赖空气传导麦克风,在嘈杂环境中容易受干扰。W4 改为直接从用户声带捕捉振动,将语音与环境噪音隔离,确保在机场、展会或繁华街道等复杂场景下依然能精准锁定用户声音。
  • 智能 SOTA 引擎选择器(SOTA Engine Selector):该功能可实时识别语言组合,并自动分配最适合该语种的翻译引擎。每个引擎均针对特定语法结构、表达模式及语境进行过优化,使商务谈判、技术讨论或日常对话的翻译效果更贴近母语使用者的自然表达。

通过构建纯净输入、智能引擎选择与精准输出的闭环系统,W4 提升了在复杂环境下的翻译稳定性。Timekettle 此次在 MWC 2026 的展出,体现了其将前沿技术与实际场景驱动设计相结合的产品思路,进一步推动了跨语言互动的自然性与准确性。

( @PR Newswire)

3、科大讯飞推出 AI 学习机 T90 Pro,主打跨学段诊断与交互式教学

RUNTO 数据显示,2025 年中国学习平板市场全渠道销量达 632.1 万台,同比增长 6.7%。在此市场需求背景下,科大讯飞推出新款AI 学习机 T90 Pro。有别于直接输出计算结果的通用大模型,该设备侧重于学习过程的探究与启发,通过整合学情诊断、辅导教学与反馈激励环节,构建系统化的个性化学习链路。

作为垂直领域的教育硬件,该款学习机的核心功能主要围绕以下三个维度展开:

  • 跨学段学情诊断:设备内置覆盖全国 32 个省级行政区的区域化知识图谱,支持跨年级追溯并定位知识薄弱点。据测试数据,使用该功能进行同章节查漏补缺时,学习耗时可减少 64%,学会率提升 3.1 倍。
  • 交互式辅助教学:搭载名为「晓悦」的 AI 辅导模块。在处理错题时,系统不直接提供最终答案,而是先分析具体的个性化错因,随后通过板书式的互动引导,辅助学生理解相关考点。
  • 学习反馈与管理机制:结合教育心理学理论对学习反馈系统进行设计,在完成练习后提供详尽的具体评价。此外,设备配备的「小飞 AI 学伴」可协助整理错题、报听写,并提供日常的倾听与陪伴功能

该产品的底层技术壁垒依托于科大讯飞在教育领域 22 年的数据沉淀与行业经验。其业务服务覆盖全国超 5 万所学校,并与北师大、华东师大等学术团队开展了深度合作,例如设备内采用的 AI 作文批改技术即与中高考阅卷系统同源。通过将人工智能技术与教育垂直领域的专业知识相融合,科大讯飞在市场端保持了稳定的份额,数据显示其已连续五年位居高端学习机销售额与销量首位。

(@量子位)

4、AI 儿童故事平台 Giant 获 800 万美元融资,AI 对话时长破百万分钟

近日,面向儿童的 AI 互动故事平台 Giant 宣布完成 800 万美元种子轮融资。本轮融资由 Matrix、Decasonic 与 Griffin Gaming Partners 联合领投,Perceptive Ventures 等多家机构跟投。该笔资金将主要用于扩展其互动故事平台。

Giant 成立于 2025 年 5 月,由连续创业者约翰·科布斯(John Kobs)创办。科布斯表示,创办初衷是希望研发一款让孩子从内容消费者转变为创造者的产品。在当前的数字环境中,Giant 试图提供一个安全可靠的空间,用以培养孩子的想象力并塑造品格,让儿童不只是观看故事,而是亲身融入故事之中

目前,该平台主要具备以下三项核心功能:

  • 专属形象生成:可根据儿童的实际照片生成对应的专属卡通人物。
  • 个性化剧集定制:支持孩子自主创作故事情节,并观看以自己名字和兴趣为特色的专属动画。
  • 智能实时互动:支持儿童与故事中的 AI 角色展开直接对话。

运营数据方面,自上线以来,Giant 平台内儿童与 AI 角色的对话总时长已突破 100 万分钟,累计制作个性化剧集超过 20 万集。投资方认为,该产品将儿童创造力的第一性原理与人工智能技术相结合,正在定义一个由 AI 驱动的创意故事讲述新类别。

(@多知)

5、OpenAI 探索成人语音与文本交互:ChatGPT 试水「Naughty Chats」,需自拍验证年龄以规避合规风险

据 Android Authority 挖掘最新的 ChatGPT 安卓应用程序更新代码(版本号 v1.2026.055)发现,OpenAI 可能即将推出一项名为「Naughty Chats」的成人内容模式。此举呼应了该公司首席执行官 Sam Altman 去年 10 月关于放宽成人内容限制的预告。

根据隐藏的代码字符串显示,「此设置允许 ChatGPT 在用户提问时使用更大胆、成人主题的语言」。关于该模式的运行机制,目前透露出以下核心信息:

  • 严格的年龄门槛与验证:该功能仅面向 18 岁及以上用户开放,并要求进行强制年龄验证。OpenAI 近期引入了年龄预测工具,会通过账户活跃时长、常见使用时段、填写的年龄以及其他使用习惯来判定用户年龄的真实性。若未通过该验证,用户将被转入青少年模式。若需进一步确认,用户须通过第三方机构 Persona 上传自拍照完成验证。
  • 主动开启机制:符合 Altman 此前的表态,这种对成人内容友好的模式不会默认启用,只有在用户主动提出要求并开启后才会激活。

目前,该功能似乎仍处于开发阶段,代码中尚未提供明确的激活方式。在当前的人工智能领域,部分其他平台已允许使用成人主题的语言,但一些竞争对手(如 Grok)也曾因缺乏安全护栏、允许生成真实人物的非自愿色情图像而遭到外界批评。相较而言,OpenAI 在放宽限制的同时,正试图通过年龄验证等手段控制合规风险。

( @PCMag)

03 有态度的观点

1、沃尔玛首席人力官莫里斯:美国劳动力需学习中国,那里 5 岁孩子都在学 DeepSeek

据《财富》杂志报道,美国企业正加速推进 AI 培训,以避免劳动力在技术变革中落后。德勤、Verizon 和沃尔玛等大型企业均已启动大规模员工培训计划。

沃尔玛首席人力官唐娜 · 莫里斯在接受《财富》采访时表示,这种趋势不仅关系到企业自身,更关系到美国整体经济竞争力:「看看中国,5 岁的孩子就开始学习 DeepSeek,显而易见,中国对能力建设有多重视。如果美国也全面推动 AI 能力建设,将对经济产生深远影响。」

中国正在系统性推进 AI 教育。北京已要求中小学每学年至少提供 8 小时 AI 课程,内容涵盖聊天机器人使用和 AI 伦理等主题。中国学生整体在校学习时间也普遍高于美国学生。

报道还指出,中国的教育投入正在转化为人才优势。保尔森研究所 2020 年研究显示,全球近三分之一顶级 AI 人才出生于中国,美国科技公司正以高薪争夺这些人才。譬如,Meta 去年 6 月成立 Superintelligence Labs 时,11 名研究人员中有 7 人出生于中国,且全部从美国境外招募。

据 IT 之家了解,美国企业界正日渐将 AI 教育视为战略重点。去年,包括微软 CEO 萨蒂亚 · 纳德拉、DoorDash CEO 徐迅以及 Airbnb CEO 布莱恩 · 切斯基在内的 400 多位企业负责人联名致信美国立法者,呼吁将计算机科学和 AI 纳入所有学生的基础课程体系。「在 AI 时代,我们必须让孩子成为 AI 创造者,而不仅是使用者。计算机科学和 AI 基础是帮助学生在技术时代取得成功的关键,否则将面临落后的风险。」

莫里斯认为,缩小 AI 人才差距的关键在于企业主动投资员工培训:「大型雇主必须积极帮助员工适应 AI 驱动和数字化的工作环境。如果所有企业共同投入培训,整体劳动力竞争力将显著提升。」

她还指出,AI 培训适用于几乎所有岗位:「AI 的独特之处在于它几乎不受岗位限制。不同岗位使用 AI 的方式可能不同,但所有人都应具备这方面的能力。」

(@IT 之家)

04 有看点的活动

1、Open Claw 碰撞场:来北京五道口,和 AI 大佬们一起「碰」出点东西

阅读更多 Voice Agent 学习笔记:了解最懂 AI 语音的头脑都在思考什么

写在最后:

我们欢迎更多的小伙伴参与 「RTE 开发者日报」 内容的共创,感兴趣的朋友请通过开发者社区或公众号留言联系,记得报暗号「共创」。

对于任何反馈(包括但不限于内容上、形式上)我们不胜感激、并有小惊喜回馈,例如你希望从日报中看到哪些内容;自己推荐的信源、项目、话题、活动等;或者列举几个你喜欢看、平时常看的内容渠道;内容排版或呈现形式上有哪些可以改进的地方等。

作者提示: 个人观点,仅供参考

自己用鞭炮战法,每天一般能吃到 1-2 个点的目标收益,然后就退出来不看了。
在想有没有好的办法自动交易?比如网格交易或者量化模型,有没有老哥懂的,免 5 万 0.5etf 手续费,感觉高频交易手续费基本忽略不计,可以搞个 10-20 万这样运行起来。

本文为墨天轮数据库管理服务团队第169期技术分享,内容原创,作者为技术顾问胡振兴,如需转载请联系小墨(VX:modb666)并注明来源。如需查看更多文章可关注【墨天轮】公众号。

image.png

一、安装前准备

1.1 集群规划

主库备库
业务 IP192.168.6.142192.168.6.143
实例名DMGRID1DMGRID2
实例端口52365236
MAL 端口53365336
MAL 守护进程端口54365436
守护进程端口55365536
OGUID4533145331
守护组GRP1GRP1
安装目录/dm8/dmdbms/dm8/dmdbms
实例目录/dm8/dmdbms/data/dm8/dmdbms/data
归档上限5120051200

1.2 集群架构

搭建的主备集群架构如下图:

图片

1.3 切换模式说明

image.png

ARCH\_WAIT\_APPLY 参数,设置为 0:高性能模式;设置为 1:事务一致模式。

  • 故障手动切换情境下 ARCH\_WAIT\_APPLY 只能为 0。故障自动切换情境下 ARCH\_WAIT\_APPLY 可以为 0,也可以为 1。
  • ARCH\_WAIT\_APPLY 参数设置的判断依据为业务是否要查询备机最新数据。如果需要,则配置为 1(较大性能衰减);如果不需要,则配置为 0

二、集群搭建

2.1 配置 主库

2.1.1 初始化实例并备份数据

初始化实例

[dmdba@dm1 ~]$ /dm8/dmdbms/bin/dminit PATH=/dm8/dmdbms/data/ INSTANCE_NAME=DMGRID1 PAGE_SIZE=32 EXTENT_SIZE=32 CASE_SENSITIVE=y DB_NAME=DAMENG INSTANCE_NAME=DMGRID1 PORT_NUM=5236 LOG_SIZE=2048 SYSDBA_PWD=Oracle2025! SYSAUDITOR_PWD=Oracle2025!
  • 启动服务
  • /dm8/dmdbms/bin/dmserver /dm8/dmdbms/data/DAMENG/dm.ini

图片

开启归档

SQL> ALTER DATABASE MOUNT;
SQL> ALTER DATABASE ARCHIVELOG;
SQL> ALTER DATABASE ADD ARCHIVELOG 'DEST=/dm8/dmdbms/data/DAMENG/arch, TYPE=LOCAL, FILE_SIZE=1024, SPACE_LIMIT=51200';
SQL> ALTER DATABASE OPEN;
  • 备份数据
  • SQL> BACKUP DATABASE BACKUPSET '/dm8/dmdbms/data/DAMENG/bak/BACKUP\_FILE';

图片

修改 dm.ini

SQL> SP_SET_PARA_VALUE (2,'PORT_NUM',5236);
SQL> SP_SET_PARA_VALUE (2,'DW_INACTIVE_INTERVAL',60);
SQL> SP_SET_PARA_VALUE (2,'ALTER_MODE_STATUS',0);
SQL> SP_SET_PARA_VALUE (2,'ENABLE_OFFLINE_TS',2);
SQL> SP_SET_PARA_VALUE (2,'MAL_INI',1);
SQL> SP_SET_PARA_VALUE (2,'RLOG_SEND_APPLY_MON',64);

关闭前台实例服务 Ctrl+C

2.1.2 修改 dmarch.ini

[dmdba@dm1 ~]$ vi /dm8/dmdbms/data/DAMENG/dmarch.ini

ARCH_WAIT_APPLY = 0 #0:高性能 1:事务一致
[ARCHIVE_LOCAL]
ARCH_TYPE = LOCAL #本地归档类型
ARCH_DEST = /dm8/dmdbms/data/DAMENG/arch/ #本地归档存放路径
ARCH_FILE_SIZE = 1024 #单个归档大小,单位 MB
ARCH_SPACE_LIMIT = 51200 #归档上限,单位 MB
[ARCHIVE_REALTIME1]
ARCH_TYPE = REALTIME #实时归档类型
ARCH_DEST = DMGRID2 #实时归档目标实例名

2.1.3 创建 dmmal.ini

[dmdba@dm1 ~]$ vi /dm8/dmdbms/data/DAMENG/dmmal.ini

MAL_CHECK_INTERVAL = 10 #MAL 链路检测时间间隔
MAL_CONN_FAIL_INTERVAL = 10 #判定 MAL 链路断开的时间
MAL_TEMP_PATH = /dm8/dmdbms/data/malpath/ #临时文件目录
MAL_BUF_SIZE = 512 #单个 MAL 缓存大小,单位 MB
MAL_SYS_BUF_SIZE = 2048 #MAL 总大小限制,单位 MB
MAL_COMPRESS_LEVEL = 0 #MAL 消息压缩等级,0 表示不压缩
[MAL_INST1]
MAL_INST_NAME = DMGRID1 #实例名,和 dm.ini 的 INSTANCE_NAME 一致
MAL_HOST = 192.168.6.142 #MAL 系统监听 TCP 连接的 IP 地址
MAL_PORT = 5336 #MAL 系统监听 TCP 连接的端口
MAL_INST_HOST = 192.168.6.142 #实例的对外服务 IP 地址
MAL_INST_PORT = 5236 #实例对外服务端口,和 dm.ini 的 PORT_NUM 一致
MAL_DW_PORT = 5436 #实例对应的守护进程监听 TCP 连接的端口
MAL_INST_DW_PORT = 5536 #实例监听守护进程 TCP 连接的端口
[MAL_INST2]
MAL_INST_NAME = DMGRID2
MAL_HOST = 192.168.6.143
MAL_PORT = 5336
MAL_INST_HOST = 192.168.6.143
MAL_INST_PORT = 5236
MAL_DW_PORT = 5436
MAL_INST_DW_PORT = 5536

2.1.4 创建 dmwatcher.ini

[dmdba@dm1 ~]$ vi /dm8/dmdbms/data/DAMENG/dmwatcher.ini

[GRP1]
DW_TYPE = GLOBAL #全局守护类型
DW_MODE = AUTO #MANUAL:故障手切 AUTO:故障自切
DW_ERROR_TIME = 20 #远程守护进程故障认定时间
INST_ERROR_TIME = 20 #本地实例故障认定时间
INST_RECOVER_TIME = 60 #主库守护进程启动恢复的间隔时间
INST_OGUID = 45331 #守护系统唯一 OGUID 值
INST_INI = /dm8/dmdbms/data/DAMENG/dm.ini #dm.ini 文件路径
INST_AUTO_RESTART = 1 #打开实例的自动启动功能
INST_STARTUP_CMD = /dm8/dmdbms/bin/dmserver #命令行方式启动
RLOG_SEND_THRESHOLD = 0 #指定主库发送日志到备库的时间阈值,默认关闭
RLOG_APPLY_THRESHOLD = 0 #指定备库重演日志的时间阈值,默认关闭

2.1.5 拷贝备份文件

拷贝备份文件到备库

[dmdba@dm1 fullback]$ scp * dmdba@192.168.6.143:/dmdata/dm8/backup

图片

2.1.6 注册服务

[root@~]# /dm8/dmdbms/script/root/dm_service_installer.sh -t dmserver -p DMGRID1 -dm_ini /dm8/dmdbms/data/DAMENG/dm.ini -m mount
[root@~]# /dm8/dmdbms/script/root/dm_service_installer.sh -t dmwatcher -p Watcher -watcher_ini /dm8/dmdbms/data/DAMENG/dmwatcher.ini

图片

2.2 配置 备库

2.2.1 初始化实例

图片

2.2.2 恢复数据

/dm8/dmdbms/bin/dmrman CTLSTMT="RESTORE DATABASE '/dm8/dmdbms/data/DAMENG/dm.ini' FROM BACKUPSET '/dm8/dmdbms/data/DAMENG/bak/BACKUP_FILE'"
/dm8/dmdbms/bin/dmrman CTLSTMT="RECOVER DATABASE '/dm8/dmdbms/data/DAMENG/dm.ini' FROM BACKUPSET '/dm8/dmdbms/data/DAMENG/bak/BACKUP_FILE'"
/dm8/dmdbms/bin/dmrman CTLSTMT="RECOVER DATABASE '/dm8/dmdbms/data/DAMENG/dm.ini' UPDATE DB_MAGIC"

2.2.3 替换 dmarch.ini

[dmdba@dm2 DAMENG]$ vi /dm8/dmdbms/data/DAMENG/dmarch.ini
ARCH_WAIT_APPLY = 0 #0:高性能 1:事务一致
[ARCHIVE_LOCAL]
ARCH_TYPE = LOCAL #本地归档类型
ARCH_DEST = /dm8/dmdbms/data/DAMENG/arch/ #本地归档存放路径
ARCH_FILE_SIZE = 1024 #单个归档大小,单位 MB
ARCH_SPACE_LIMIT = 51200 #归档上限,单位 MB
[ARCHIVE_REALTIME1]
ARCH_TYPE = REALTIME #实时归档类型
ARCH_DEST = DMGRID1 #实时归档目标实例名

2.2.4 配置 dm.ini、dmmal.ini 和 dmwatcher.ini

在 备库机器上配置备库的实例名为 DMGRID2,dm.ini 参数修改如下

INSTANCE_NAME = DMGRID2
PORT_NUM = 5236 #数据库实例监听端口
DW_INACTIVE_INTERVAL = 60 #接收守护进程消息超时时间
ALTER_MODE_STATUS = 0 #不允许手工方式修改实例模式/状态/OGUID
ENABLE_OFFLINE_TS = 2 #不允许备库 OFFLINE 表空间
MAL_INI = 1 #打开 MAL 系统
ARCH_INI = 1 #打开归档配置
RLOG_SEND_APPLY_MON = 64 #统计最近 64 次的日志重演信息

配置 dmmal.ini 和 dmwatcher.ini

备库 机器里 dmmal.ini、dmwatcher.ini 与 主库 DMGRID2 的 dmmal.ini、dmwatcher.ini 相同,参照 A 机器 dmmal.ini、dmwatcher.ini 文件进行配置

2.2.5 注册服务
/dm8/dmdbms/script/root/dm_service_installer.sh -t dmserver -p DMGRID2 -dm_ini /dm8/dmdbms/data/DAMENG/dm.ini -m mount
/dm8/dmdbms/script/root/dm_service_installer.sh -t dmwatcher -p Watcher -watcher_ini /dm8/dmdbms/data/DAMENG/dmwatcher.ini
若要删除自启,可利用如下方式:
[root@~]# /dm8/dmdbms/script/root/dm_service_uninstaller.sh -n DmServiceDMGRID2
[root@~]# /dm8/dmdbms/script/root/dm_service_uninstaller.sh -n DmWatcherServiceWatcher

2.3 配置确认监视器

2.3.1 创建 dmmonitor.ini

vi /dm8/dmdbms/bin/dmmonitor.ini

MON_DW_CONFIRM = 1 #0:非确认(故障手切) 1:确认(故障自切)
MON_LOG_PATH = ../log #监视器日志文件存放路径
MON_LOG_INTERVAL = 60 #每隔 60s 定时记录系统信息到日志文件
MON_LOG_FILE_SIZE = 512 #单个日志大小,单位 MB
MON_LOG_SPACE_LIMIT = 2048 #日志上限,单位 MB
[GRP1]
MON_INST_OGUID = 45331 #组 GRP1 的唯一 OGUID 值
MON_DW_IP = 192.168.6.142:5436 #IP 对应 MAL_HOST,PORT 对应 MAL_DW_PORT
MON_DW_IP = 192.168.6.143:5436

三、测试主从同步

主库备库都执行

/dm8/dmdbms/bin/dmserver /dm8/dmdbms/data/DAMENG/dm.ini mount

主库

/dm8/dmdbms/bin/disqlsysdba/'"Oracle2025!**"'
SP_SET_PARA_VALUE(1, 'ALTER_MODE_STATUS', 1);
SP_SET_OGUID(45331);
ALTER DATABASE PRIMARY;
SP_SET_PARA_VALUE(1, 'ALTER_MODE_STATUS', 0);

图片

备库

SP_SET_PARA_VALUE(1, 'ALTER_MODE_STATUS', 1);
SP_SET_OGUID(45331);
ALTER DATABASE STANDBY;
SP_SET_PARA_VALUE(1, 'ALTER_MODE_STATUS', 0);
select status$from v$instance;

主库备库启动datawatcher

/dm8/dmdbms/bin/dmwatcher /dm8/dmdbms/data/DAMENG/dmwatcher.ini

查看数据库状态

SQL> select status$,mode$ from v$instance;

图片

备库

图片

主节点启动monitor
/dm8/dmdbms/bin/dmmonitor /dm8/dmdbms/bin/dmmonitor.ini

图片

测试数据同步

create tablespace test datafile '/dm8/dmdbms/data/DAMENG/test.DBF' size 128 autoextend on maxsize 10240;
create user testdb identified by "Oracle2025!" default tablespace test default index tablespace test;
grant dba to testdb;

主节点登录 testdb用户

图片

备节点查询表

[dmdba@dm2 log]$ disql sysdba/'"Oracle2025!"'

图片


墨天轮从乐知乐享的数据库技术社区蓄势出发,全面升级,提供多类型数据库管理服务。墨天轮数据库管理服务旨在为用户构建信赖可托付的数据库环境,并为数据库厂商提供中立的生态支持。
墨天轮数据库服务官网:https://www.modb.pro/service

找了一下原因,给服务器加日志,又是完善 AI 选节点的重试逻辑,最后发现是提示词的问题fake_sad

AI 在找不到 100% 精准匹配的子节点时,由于“星云”这个词在提示词语境中多次出现,当作是兜底的子节点选择了。这种情况只好在提示词中增加严禁指令,比如:禁止生成 "星云"、"其它"、"未分类" 或任何含义模糊的兜底词作为子节点名称。

在传统的软件开发模式中,我们的潜意识里会这样认为:一个业务对象,就必然对应数据库里的一张物理表。

比如我们要开发一个问卷系统,很自然地会建立 Survey(问卷表)、Question(题目表)、Response(答卷表)。表里定义好具体的列:titleVARCHARscoreIntcreatedAtDateTime。各司其职,结构清晰。当平台只有几十、上百个客户,且他们的业务流程基本一致时,这套做法不仅高效,而且非常优雅。

但是,现代企业级 SaaS(比如低代码平台、极其灵活的 CRM 系统)面临的核心挑战是:极端的个性化诉求规模化。

假如遇到这样的场景,你的平台服务了很多个企业客户(租户)。
A 企业希望在问卷里加一个"所属行业"字段;
B 企业希望加一个"紧急程度"字段;
C 企业甚至想完全新建一个叫"问卷回访跟进"的全新业务模块。
如果坚持"一对象一表"的传统架构,我们很快会遇到下面的问题:

  1. 触发 DDL 风暴 (Data Definition Language Storm):当上百个租户各自在界面上点击"添加字段"时,后台就要向数据库发送几十万条 ALTER TABLE ADD COLUMN 语句。DDL 操作通常会锁表(Metadata Lock),这在处于高并发读写状态的生产型数据库中,无异于自杀。
  2. 运维与管理的无底洞:如果为每个租户单独建表,1 万租户 × 50 张表 = 50 万张表。数据库的数据字典(系统表)会因为海量的元数据而极度膨胀。让备份、升级、统一修改字段都变得很困难。
  3. 隔离性极其脆弱:多租户环境下,由于大家的 Schema 完全长得不一样了,一套统一的代码体系很难去处理所有边缘情况,最后往往陷入"代码里写满 if-else 处理不同租户特殊逻辑"的泥潭。

面对这些困难,业界诞生了一个这样的架构选择:彻底放弃让应用层直接操作数据库结构。

数据库在低代码平台眼中,退化成了一个纯粹的、钝感的数据仓库。
不关心、也不知道具体的业务模型长什么样。
至于"系统里有哪些表、表里有哪些字段"这种原本属于 DBMS 级别的工作,被"上架"到了应用层来管理。

这就是元数据驱动架构(Metadata-driven Architecture)的起点。

用元数据描述结构:通用数据字典 (UDD)

为了在应用层"维护"一套数据库系统的逻辑,我们需要引入一个至关重要的概念:元数据 (Metadata)

如果说普通的业务数据记录的是"张三考了 95 分",那么元数据记录的就是"系统里有一个叫『问卷』的表,并且它有一列叫『分数』"。简单来说,元数据就是"描述数据的数据"

管理这些元数据的数据,我们称之为 通用数据字典 (Universal Data Dictionary, UDD)

它的核心思想是:既然底层数据库不让我们自由建表了,那我们就拿两张普通的表当"户口本",把用户想要的表结构"登记"在册。

具体来说,系统里只需要永远固定存在这两张表:

1. Objects 表(登记"有什么表")

当你在低代码后台点击"新建表单"并命名为"问卷调查"时,底层并没有执行神圣的 CREATE TABLE
系统只是往 Objects 这张表里,像普通记账一样插入了一行:
"嘿,客户 A 新建了一个叫 Survey(问卷)的虚拟表,ID 给它算作 1001 吧。"

2. Fields 表(登记"表里有什么列")

知道了有问卷表,还得知道问卷里有什么字段。Fields 表就是用来记这个的。

每当你在页面上拖拽生成一个"问卷标题"的输入框,系统就往 Fields 表里加上一行:
"客户 A 的 Survey 表里,多了一个叫 Title(标题)的文本字段。并且我规定,填写在这里的内容,未来统一存放到第 0 号储物格(SlotIndex: 0)里。"

理解关键点:在这个体系里,修改系统结构不再是高危的数据库操作(DDL),而变成了最简单的增删改查(CRUD)。表结构,本身也化作了普通的数据。

数据存储: 堆表与弹性列机制

上一节,我么把数据结构建好了。下一个问题是:真正的业务数据,到底存在哪里?

既然应用层不能动态建表,那唯一的解法就是:提前建好一张超级巨大的"万能表",把所有客户、所有表单的数据,全部大杂烩一样强行塞进去。

这张底层物理表,我们管它叫 堆表 (Heap Table)
你可以把它想象成一张行和列都无限向外延展的超级 Excel 表格。

它的结构极其无脑暴力,大致长这个样子:

唯一ID租户是谁这是什么表储物格\_0 (val_0)储物格\_1 (val_1)储物格\_2 (val_2)...
1客户A问卷表用户满意度调查5分进行中...
2客户A订单表iPhone 15 Pro8999元顺丰发货...
3客户B请假表病假老婆不舒服1天...

请仔细观察这张表的最右侧,跟着密密麻麻的 val_0, val_1val_500
这些列被称为 弹性列 (Flex Columns),而且它们全都是兼容性最强的文本类型 (VARCHAR)

堆表本身是一只没有任何感情的吞金兽,它完全不关心自己存的是问卷的名字、手机的价格,还是请假的天数。那到底谁知道 val_1 存的是什么鬼东西?

答案是:上一节讲到的 Fields 字典! 这两者的配合机制(Slot 映射)如下:

  1. 前端发请求:"把客户 A 的 iPhone 订单价格拿给我看看"。
  2. 翻译官(字典表)在脑子里翻译:"iPhone 订单价格... 对客户 A 来说,这玩意儿被登记在 val_1 这个储物格里!"
  3. 翻箱倒柜(抛向数据库):直接用原生 SQL 执行 SELECT val_1 FROM 堆表 WHERE ID = 2

就是这么简单。对底层的关系型数据库来说,这里完全不存在运行时的动态改表,也就是查一个固定列而已。
堆表负责死心塌地屯放数据,字典表作为"翻译密码本"负责解释这行数据到底是什么意思。 两套系统严丝合缝地咬合,完成了极其精彩的欺骗。

全用 VARCHAR 的代价与规范化格式

这种设计带来新的问题:

所有弹性列都是 VARCHAR 字符串,那我们怎么做大于、小于的范围比较?数字 95 和 100 怎么排序?日期 2024-01-01 和 2024-02-01 怎么查区间?

按照常规的字符串字典序逻辑,字符串 "100" 是排在 "95" 前面的(因为首字符 1 小于 9)。如果我们把 Score=100Score=95 原封不动作为字符串存进去,那么 ORDER BY Score DESC 的结果将是彻头彻尾的灾难。

这是所有的元数据架构必须跨过的一道硬核工程门槛。既然底层的原生数值类型和日期类型被我们人为抹杀掉了,我们就必须在应用层用一套严密的逻辑把规则补回来。这套机制被称为 规范化格式 (Canonical Format)

它的核心思想是:在把强类型的业务数据实际写入 val_N 弹性列之前,必须先经过一道编码层,将其强制转换为能够直接用于数据库比较运算符(><BETWEENORDER BY)的、符合标准字典序的字符串。

以下是一些关键的规范化规则:

逻辑类型原始输入值规范化后的字符串表示工程原理与优势
Number95"00000095.000"预先定义好总位数和小数位。左边补零对齐,保证字符串字典序等同于数值大小序。
Number-3"-0000003.000"处理负数时逻辑更复杂(需翻转补码),此处为简化展示,核心目的是解决负数排序。
Date2024-01-15"2024-01-15T00:00:00Z"严格采用 ISO 8601 格式,高位是年,低位是秒。时间维度的顺推恰好也是字典序的顺推。
Booleantrue"1"布尔值直接降维为单字符的 "1""0"

这套设计的本质是一笔架构层面的交易:我们刻意牺牲了底层数据存储的可读性,并付出了序列化/反序列化的计算开销,以此换取了整个平台架构在极大规模下的极致弹性。

在庞大的全是大文本的宽表上做 SQL 索引,其扫描效率和命中率在面临千万级数据时会直线下降。这个被称为"元数据性能税"的问题,是所有成熟架构后续必须攻克的下一个高峰,我们将在后续文章(透视表与查询优化)中集中讨论如何偿还这笔债。


一条数据的完整端到端旅程

我们把上面所有概念串在一起,看看在真实的系统中,一次普通的数据保存到底经历了怎样的一生。

应用场景:某个平台管理员在 Web 前端的问卷管理界面中,提交了一份新问卷的基础信息:{Title: "用户满意度调查", Status: "进行中"}

Step 1 · 流量入口与鉴权 (API Layer)

前端发起了 HTTP 请求。网关拦截后解析出这是 ORG-001 租户的流量,明确目标是要操作 Survey 这个模型。

POST /api/data/Survey
{ "Title": "用户满意度调查", "Status": "进行中" }

Step 2 · 唤醒元数据字典 (Metadata Lookup)

引擎的核心拦截器登场。它拿着 "Survey" 这个关键次去查询(通常是内存缓存里的)Objects 表,得到了这个虚拟模型的内部 ID 为 1001
接着查询 Fields 表,拉取到了这个模型下的所有字段配置及最重要的物理槽位:

逻辑字段名逻辑类型物理层 SlotIndex是否必填
TitleText0Yes
StatusText1No

Step 3 · 运行期强制校验与规范化编码 (Validation & Encoding)

引擎把请求中的 JSON 体拉过来进行对比:

  • "用户满意度调查" → 对应 Title 字段。系统检查其长度、是否符合纯文本规范。因为是 Text 类型,直接保持原样保留为待插入字符串。
  • "进行中" → 对应 Status 字段。校验通过,保持原样。
    (如果包含数字,就会在此处被格式化为前补零字符串)

Step 4 · 虚实转换:拼接最终物理 SQL (SQL Generation)

引擎拿着内存里已经编码好的干净数据,根据字典提供的映射关系,开始拼接能够在底层 PostgreSQL/MySQL 里直接运行的 SQL 文本。注意这里的列名已经被替换成了真正的物理列名 val_0, val_1

INSERT INTO physical_data
  (org_id, obj_id, val_0, val_1, created_at)
VALUES
  ('ORG-001', 1001, '用户满意度调查', '进行中', '2026-03-01T12:00:00Z');

Step 5 · 落盘与响应 (Persistence)

关系型数据库默默地执行了这条标准的 DML 语句,将数据持久化到堆表,并返回写入成功。引擎再将结果封装成 JSON 返回给前端。

回顾这一切:整个请求过程中,没有触发过任何一次 DDL 锁。被外界视作核心支柱的 Survey 这张业务表,自始至终在物理层面都不存在过 —— 它只是配置系统里静静躺着的一行设置。而在平台的用户端视角看来,他们的录入、查询操作,却与使用专属 MySQL 实例并无二致。


小结

元数据驱动的核心并不是消灭了结构,而是做了一次巧妙的维度提升。

我们将传统数据库赖以生存的 Schema 骨架从底层剥离,强行搬到了更高一层的“应用层数据字典”中。这层额外的中间层间接性,赋予了平台很强的生命力和扩展弹性。无论租户是一千、一万还是十万,无论他们想要定义怎样千奇百怪的表单和数据模型,底层物理依然是一张纹丝不动、便于统一治理和灾备的超级宽表。

只要你的系统存在“极端允许用户在运行时定义数据结构”的需求,这套宽表+字典模型几乎是目前工业界唯一可行的顶层解法。

痛点

我用过很多 Mac 清理软件,但是无一例外全部没有留下,因为大多数清理软件都无法解决统一的问题

  1. 不知道哪些文件能删哪些文件不能删
  2. 每次扫描出的东西都一模一样,我得一个个勾选我要删除的,一个个勾选我不想删的.
  3. 功能太复杂,什么智能扫描,什么深度清理,完全搞懂不有什么区别
  4. 扫描完自己弄个图哪些占用多哪些占用少,自己去挑选要删除的东西
  5. 无法自定义扫描,比如我想删除下载文件夹下的 .dmg 文件
  6. 订阅制,UI 非常卡顿,长相非常丑陋,没有用下去的欲望
  7. 等等吧 问题太多了.

为什么选择 Cacheless

Cacheless 基于规则来判断风险等级,你可能不知道什么意思,容我娓娓道来。

risk-level

规则
比如文件大于 100M , 文件后缀为 dmg , 创建时间超过 30 天,这些都是规则

风险等级
Cacheless 基于规则将文件分为不同的风险等级,比如 Safe ,Suggested, Review ,Keep,
Safe:代表可以安全删除
Suggested: 代表建议删除
Review: 需要你自己审核是否可删除
Keep: 保留

如果路径 = Downloads 且扩展名 = dmg → 风险等级 = 建议( Suggested )

内建规则市场

基于上面所述,我建立了自己的几十个各种规则比如:
1. Chrome 浏览器缓存清理规则
2. Xcode DerivedData Cleaner
3. Yarn 缓存清理
4. npm 缓存清理 等等吧

不会定义也没关系,这些规则下载可以直接使用。

rules

AI 分析

即使作为 macOS 软件开发者,Mac 系统中的很多目录我都不知道是干什么用的,更别说普通用户了。
使用 Cacheless 当你点击某一行的时候,AI 会告诉你这个文件夹是干什么的,是否可以删除。

AI 分析

iCloud 同步

所有的规则都可以通过 iCloud 进行同步,多台电脑都可以使用你自己建立好的规则,更巧妙的是即使你的一台电脑叫张三,另一台电脑叫李四,你为张三/Downloads 建立了规则,通过 iCloud 同步后也适用于李四/Downloads 文件夹。

文件标记

如果有的文件是你想保留的,有的文件是你想删除的,不想每次都来回勾选,你可以使用 mark as 功能来讲对应的文件/文件夹标记为对应的风险等级,下载扫描的时候会记住你的标记,不用来来回回勾选。

mark as

官网

https://www.cacheless.app

永久一折优惠
单台设备 $19.99~~ -> $1.99
3 台设备
$39.99~~ -> $3.99

注意: App Store 版本审核中,请下载非 App Store 版本

AI 大模型的加速飞跃,正在重塑整个数据产业的底层逻辑。当文本、图像、音频、视频等非结构化数据以指数级增长,多模态数据库作为连接数据与智能的关键桥梁,正成为企业数智化转型的核心基础设施。

 

与传统数据库相比,多模态数据库能通过统一的架构原生支持关系、向量、地信、全文检索(FTS)等多元数据模型,实现多模态数据的统一存储、管理与关联查询,从根本上解决数据孤岛问题,借助 AI 能力深度挖掘数据价值。

 

腾讯云 TDSQL Boundless 正是在这一趋势下诞生的新一代标杆产品。其基于腾讯云自研的数据库引擎,高度兼容 MySQL。它集极致成本优化、透明弹性伸缩与企业级高可用于一体,让用户无需为业务增长、规模扩张及随之而来的高昂成本与复杂度操心。

 

2026 年 3 月 11 日 19:00,年度首期 DBTalk 直播开启。三位腾讯云数据库技术专家将为您解读:

  • AI 时代数智底座:TDSQL Boundless 多模态数据库;

  • TDSQL Boundless HBase 兼容模式探索;

  • TDSQL Boundless 多模态数据时代的实时分析引擎。

 

从 Boundless 在多模态统一架构演进,到 HTAP 分析引擎关键技术,再到 HBase 生态支持,三位腾讯云数据库技术专家在线拆解关键演进与实践。

3 月 11 日 19:00,扫描下方二维码,或点击链接:https://qdrl.qq.com/nfNXafKw,立即预约直播,一起探讨数智底座的未来!

相关源代码已经开源, 大家可以玩一玩: 仓库地址

做这个的动机在这里: https://www.v2ex.com/t/1191393

故事起源在这里: https://www.v2ex.com/t/1190317

在这里开始发展: https://www.v2ex.com/t/1191065

在这个时间点开始被猪油蒙住了: https://www.v2ex.com/t/1191311

然后就是现在的清醒时刻, 哈哈哈哈.

相关的界面截图:




主要是 PC 端(包括 MacOS/Windows 都适用)。

痛点 1:平时我 MacOS 都是后台开着科学上网,如果用 Spotify 就会加载广告,以至于之前一致用 Youtube Music。
在设置里面代理设置:选择不使用任何代理。即使开了科学上网,只要不用全局模式,应该就能避免广告;

痛点 2:最近听日语歌想看歌词,Spotify 不支持桌面歌词,状态栏歌词
推荐个开源歌词软件 Lyric Fever ,桌面歌词,状态栏歌词,支持翻译。

附软件截图:

状态栏歌词以及控制中心,支持调节音量,上下曲

状态栏歌词

桌面歌词

桌面歌词

3 月 2 日,阿里巴巴将大模型 B 端品牌和 C 端应用品牌统一为千问。千问大模型(Qwen)涵盖基础大模型和专业领域模型,千问 APP 是阿里巴巴在 C 端的旗舰 AI 应用。为了避免之前千问、通义千问、Qwen 等多个名称导致的混淆问题,统一名称之后,阿里巴巴大模型品牌中文为“千问大模型”,英文为“Qwen”。“通义千问”的名称将不再使用。“通义实验室”为阿里巴巴集团旗下 AI 机构的组织名称。

 

3 月 3 日消息,阿里巴巴昨晚再度开源千问 3.5 系列模型,这次是 4 款小尺寸模型。其中最小的 0.8B 和 2B 两款,体积极小、推理速度快,适合移动设备、IoT 边缘设备部署,以及低延时的实时交互场景。4B 模型则适合作为轻量级 Agent 的核心大脑,平衡了性能与资源消耗。9B 模型性能媲美 GPT-OSS-120B,适合需要较高智力水平但受限显存资源的服务器端部署,是性价比极高的通用模型选择。

 

官方表示,四款 Qwen3.5 新模型虽是小尺寸,但均拥有原生多模态能力,以极小的参数量实现了极大的性能提升。模型发布后,迅速引爆 AI 社区,马斯克也火速在社交媒体上点赞评论,称阿里千问模型“智能密度令人印象深刻”。

截至目前,千问 3.5 家族已开源 8 款模型,均以更少参数实现“跨级”性能超越,小尺寸性能媲美中型模型,而中型尺寸拥有顶级模型的智能水平。这正是马斯克说的智能密度。

 

刚刚过去的除夕,千问开源 3.5 系列的第一款模型 Qwen3.5-397-A17B,参数不到 4000 亿,性能即超过万亿参数的 Qwen3-Max 模型且部署成本大幅下降。2 月 25 日,阿里继续开源千问 3.5 系列模型。这次开源三款中等规模的新模型,包括 Qwen3.5-35B-A3B、Qwen3.5-122B-A10B、Qwen3.5-27B。千问 3.5 新模型甚至可直接部署于消费级显卡,对开发者极为友好。基于 Qwen3.5-35B-A3B 的托管模型 Qwen3.5-Flash 已上线阿里云百炼,每百万 Token 输入低至 0.2 元。

 

千问 3.5 模型采用混合注意力机制,结合高稀疏的 MoE 架构创新,并基于更大规模的文本和视觉混合 Token 上训练,新模型以更小的总参数和激活参数量,实现了更大的性能提升。

 

整体看,千问家族迄今已经开源 400 多款大模型。每个系列不仅包括不同尺寸的语言模型,也包括编程、数学、语音、视觉理解、图像生成等类型模型。这种“全尺寸”“全模态”的开源,受到开发者追捧。不同开发者和企业都能根据自己的需求、场景找到一款适配的千问大模型。

 

今年開始報稅了,
隔壁部門技術老弟下午差點崩潰,不管選哪個方案(合併或分開)都需要補繳...
一直在嘟囊差點都有報復心理了...估計是父母還沒到達扶養年紀且未育...

回想自己好像有一年出現過"退稅 1000 多"但後來就沒看見過了 XDDD,
現在只期望自己應繳部分"顯示是 0"就好facepalm

發張咪咪給大家招財~送上 300 金幣大家抽起來!
image

根据紫金山天文台预报,此次月全食的具体时刻(北京时间):半影食始:16 时 43 分;初亏:17 时 50 分;食既:19 时 04 分;食甚:19 时 34 分;生光:20 时 03 分;复圆:21 时 18 分;半影食终:22 时 25 分;最大食分:1.156。

640 (1).jpg
640.jpg

月全食的过程可分为半影食始、初亏、食既、食甚、生光、复圆、半影食终七个阶段。半影食始:月球东边缘与地球半影相外切,此时月球开始逐渐进入地球半影,月球会略微变暗,但是人眼难以察觉。初亏:月球东边缘与地球本影相外切,此时开始月球会逐渐进入地球本影。食既:月球的西边缘与地球本影内切,月球刚好全部进入地球本影内,标志着全食阶段开始。食甚:月球的中心与地球本影的中心最近,这时月球看起来最暗。生光:月球东边缘与地球本影相内切,此时全食阶段结束。复圆:月球的西边缘与地球本影相外切,月球完全离开地球本影。半影食终:月球西边缘与地球半影相外切,月球完全离开地球半影,恢复原来的亮度。月球被食的程度叫“食分”,它等于食甚时月轮边缘深入地球本影最远距离与月球视直经之比。月全食观测方式由于月球在天空中看起来比较大,因此观测月全食并不需要什么特殊设备,就像我们平时赏月一样,用肉眼直接观测就可以。偏食及全食阶段,肉眼可以明显看到月球的变化。如果想观察细节,则可以借助一下望远镜,能够看到月球的环形山及地貌等特征。

一、概述

Spark UI是Apache Spark内置的Web监控界面,为开发者和运维人员提供对Spark应用程序执行过程的实时、可视化洞察。它以直观的方式展示作业(Jobs)、阶段(Stages)、任务(Tasks)、SQL执行计划、Executor资源使用、存储状态及运行时环境等关键信息。通过Spark UI,用户可以快速定位性能瓶颈(如数据倾斜、Shuffle 开销、调度延迟)、分析执行计划、监控资源利用率,并进行有效的调优与故障排查。无论是开发调试还是生产运维,Spark UI都是理解和优化Spark应用不可或缺的核心工具。

二、Spark UI 一级入口

打开Spark UI,就会看到当前的一个Jobs页面,这个页面会记录当前作业中数据的移动,读取等相关动作,除此之外,一级入口还会包括作业运行时的其他属性与指标,主要包括:Stages、Storage、Environment、Executors、SQL 。

一级入口界面如下所示

首先,我们由简入繁,从衡量任务的整体指标依次介绍各个入口的的功能与作用,首先我们先看Executors,先对作业整体的一个计算负载进行了解。

Executors

Executors Tab主要包括两部分,Summary和Executors两部分,其中Summary是所有的Executors度量指标的加和,而Executors则是描述每一个Executor的详细信息,粒度会更细,方便对每个Executor的情况进行了解:

下面我们对Spark UI对Executors提供的Metrics进行介绍,方便我们对每个Executor节点的运行情况有更好的了解:

Spark UI中的Executors界面是监控和诊断Spark应用运行状态的核心窗口之一,它从执行器(Executor)粒度展示了整个集群的资源使用、任务负载和数据分布情况,以及它们对CPU、内存与磁盘等硬件资源的消耗。基于这些信息,我们可以看到不同的Executor的状态,是否有个别的Executor存在负载不均衡的情况,从而快速的定位问题,例如数据倾斜等。

Environment

这里说一下Environment,显而易见,这里主要展示我们的任务的一些配置项,它主要包括五大环境信息:

通过查看Environment的信息,我们可以快速获取当前任务的配置信息,这里主要查看Spark Properties信息,来去判断当前任务的配置项是否符合我们的预期,从而作出适当的调整,优化任务的性能。

Storage

Spark UI中的Storage界面 是用于监控和管理 缓存数据(Persisted/Cached Data)的核心窗口。它直观展示了哪些RDD或DataFrame被缓存、缓存在哪里、占用了多少资源,是优化内存使用和避免OOM(Out of Memory)的关键工具。

Cached Partitions与Fraction Cached分别记录着数据集成功缓存的分区数量,以及这些缓存的分区占所有分区的比例。而当Fraction Cached没有达到100%时,说明该数据集未能完全缓存在内存,参照spark内存管理可知,此时会出现数据换入换出的情况,显性的说明此时需要参与的计算量大,执行内存会占用缓存内存Size in Memory与Size in Disk,则更加直观地展示了数据集缓存在内存与硬盘中的分布。

SQL

Spark UI中的SQL页面(SQL Tab)是Spark SQL / DataFrame作业的核心监控与优化入口。它专为结构化查询设计,将逻辑执行计划、物理执行过程、性能瓶颈和数据流动以可视化方式呈现,是诊断慢查询、验证优化策略、理解AQE行为的“驾驶舱”。

Stages

Spark UI的Stages界面 是性能调优和故障诊断的核心入口。它从Stage(阶段)粒度展示了Spark作业的执行细节,帮助你精准定位慢任务、数据倾斜、资源瓶颈等问题。

Jobs

Spark UI的Jobs界面 是整个Spark应用监控体系的顶层入口,它以Job(作业)为单位,提供全局视角的执行概览,帮助你快速判断应用整体健康状况、识别失败作业、定位性能瓶颈起点。

至此,我们已对Spark UI导航栏中的各个页面进行了不同程度的解析。整体来看,这些页面可分为两类:

  • “详情型”页面:包括Executors、Environment和 Storage: 它们直接展示集群的系统级状态——如计算资源负载分布、运行时环境配置、缓存数据详情等。开发者无需额外跳转,即可快速获取关键的底层信息。
  • “概览 + 下钻型”页面:包括SQL、Jobs 和 Stages: 它们首先以列表形式提供作业或查询的高层汇总视图,若需深入分析执行计划、任务分布、性能瓶颈等细节,则需点击进入对应的二级详情页进行下钻探查。

这种分层设计既支持快速概览,又保留了深度诊断的能力,为开发者提供了从宏观到微观的完整观测路径。

三、Spark UI 二级入口

二级入口指的是需要通过一次超链接点击才能进入的详情页面。对于SQLJobsStages这三个主入口而言,其对应的二级页面通常已包含极为丰富的诊断信息——涵盖查询执行计划、作业生命周期、任务级性能指标等,基本构成了Spark应用的“健康体检报告”核心内容。

接下来,我们将按照SQL → Jobs → Stages的逻辑顺序,依次深入这三个二级详情页,系统性地剖析:

  • 全局DAG执行结构(来自SQL页面);
  • 作业的完整执行流程与依赖关系(来自Jobs页面);
  • 以及各计算阶段(Stage)的资源使用与运行细节(来自Stages页面)。

通过这一层层递进的分析路径,我们将获得对Spark应用执行行为更全面、更深入的洞察。

SQL详情页

通过点击图中的with...可以进入到该作业的详细执行界面:

在数据分析场景中,大部分的操作包括过滤、分组、聚合、关联、排序。所对应的执行计划图中Exchange:代表的是Shuffle操作,Sort:代表的是排序,Aggregate:代表的是数据聚合。

这三类操作是硬件资源(如 CPU、内存、磁盘和网络)的主要消耗者。与此同时,Spark UI也为它们分别提供了丰富的细粒度指标(Metrics),用以精确刻画各类资源的使用情况。接下来,我们将聚焦于这三类操作,深入解析其对应的度量指标,以更好地理解资源消耗模式并指导性能调优。

Exchange

可以看到,针对每一个Exchange操作,Spark UI都提供了全面而细致的指标(Metrics),完整覆盖了Shuffle的整个生命周期——从Shuffle Write到Shuffle Read,从数据规模到处理耗时,关键维度一应俱全。

为了便于理解和参考,我将这些Metrics的含义和作用整理成表格形式:

Sort

可以看到,“Peak memory total”和“Spill size total”这两个数值,足以指导我们更有针对性地去设置spark.executor.memory、spark.memory.fraction、spark.memory.storageFraction,从而使得Execution Memory区域得到充分的保障。

Aggregate

对于Aggregate操作,Spark UI也记录着磁盘溢出与峰值消耗,即Spill size和Peak memory total。这两个数值也为内存的调整提供了依据。

Stages详情页

在所有二级入口中,Stage详情页的信息量可以说是最大的。点进Stage详情页,可以看到它主要包含3大类信息,分别是Stage DAG、Event Timeline与Task Metrics。其中,Task Metrics又分为“Summary”与“Entry details”两部分,提供不同。

Stage DAG

我们首先来看最简单的Stage DAG,如下图所示:

之所以说Stage的DAG相对简单,是因为我们已在SQL页面的二级详情中对完整的执行DAG进行了深入解析。而Stage级别的DAG本质上只是该作业(Job)整体DAG的一个局部片段——它仅对应其中某一个计算阶段。因此,一旦你理解了SQL页面中面向整个Job的完整DAG结构,Stage 层面的DAG自然也就一目了然、无需重复深究了。

Event Timeline

Event Timeline,记录着分布式任务调度与执行的过程中,不同计算环节主要的时间花销。图中的每一个条带,都代表着一个分布式任务,条带由不同的颜色构成。其中不同颜色的矩形,代表不同环节的计算时间。

在理想情况下,Spark任务的时间条带(Timeline)应以绿色部分(Executor Computing Time)为主,这表明大部分时间都用于执行实际的计算逻辑,而系统开销(如调度、I/O、Shuffle 等)被有效控制在较低水平。

然而,实际情况往往更为复杂。你可能会观察到:

  • 深蓝色部分(Scheduler Delay)占比过高:说明任务在等待资源调度上耗费了大量时间;
  • 黄色(Shuffle Write Time)和橙色(Shuffle Read Time)显著膨胀:表明 Shuffle 阶段成为性能瓶颈。

这些现象说明:性能瓶颈并不在计算本身,而是出在调度或数据交换环节。

如何针对性优化?

1. 针对调度延迟高(Scheduler Delay 大)

可参考以下经验公式进行资源配置调整:DP∼MCPD∼CM

其中:

  • DD :数据集大小;
  • PP :并行度(Partition 数量);
  • MM :每个Executor 的内存;
  • CC :每个Executor的CPU核数;

公式含义:每个任务处理的数据量( D/PD/P )应与单个任务可使用的计算资源( M/CM/C,即单位CPU核对应的内存)处于同一数量级。

若D/PD/P远大于M/CM/C,说明任务过重或资源不足,容易导致调度排队;反之则可能资源浪费。通过合理调整并行度PP 、Executor 内存MM和CPU核数CC可有效降低调度开销。

2. 针对 Shuffle 负载重(Shuffle Read/Write 时间长)

当黄色和橙色区域占比过大时,说明任务存在大量跨节点数据交换。此时应考虑:

  • 是否可以使用Broadcast Join?
  • 若其中一张表较小(通常<100MB),可将其广播到各节点,从而完全避免Shuffle。
  • 其他优化手段包括:
  • 合理设置spark.sql.adaptive.enabled启用AQE自动合并小分区;
  • 调整spark.sql.shuffle.partitions避免过多小文件;
  • 使用repartition或coalesce优化数据分布。

Task Metrics

Task Metrics(任务指标)是Spark在每个Task执行完成后收集的一组细粒度性能与资源使用数据,全面刻画了该Task的执行行为。这些指标构成了Spark UI(尤其是 Stages 页面)中各类可视化分析(如时间条带、Shuffle 统计、内存使用等)的底层数据基础,也是进行性能调优、故障排查和资源规划的核心依据。

在分析时,通常先从粗粒度的“Summary Metrics”入手——它是对所有Tasks执行指标的统计汇总,能够快速反映整个Stage的整体表现,随后再深入到细粒度的“Tasks”列表,逐个排查异常或低效的Task,实现精准定位与优化。

Summary Metrics

点击Select All,使当前所有度量值都生效,首先把Metrics整理到表格中:

特别值得关注的是Spill (Memory) 和Spill (Disk) 这两个指标。在Spark执行过程中当用于缓存中间数据的内存结构(如PartitionedPairBuffer或AppendOnlyMap)达到容量上限时,系统会将部分数据“溢出”(spill)到磁盘以释放内存空间。

  • Spill (Memory) 表示这些被溢出的数据在内存中的原始大小(即未落盘前的字节数);
  • Spill (Disk) 则表示这些数据实际写入磁盘后的大小(通常经过序列化和压缩)。

通过计算两者的比值:

Explosion Ratio≈Spill (Memory)Spill (Disk)Explosion Ratio≈Spill (Disk)Spill (Memory)

我们可以得到一个近似的 “数据膨胀系数”(Explosion Ratio)。该系数反映了:单位磁盘存储所对应的实际内存占用。有了这个比率,当我们知道某份中间数据在磁盘上占用了多少空间时,就能反推出它在内存中大概会消耗多少资源。这为精准评估内存需求、预判OOM风险、合理配置Executor内存提供了重要依据。

例如:

  • 若Explosion Ratio = 3.0,意味着磁盘上1GB的spill数据,在内存中实际占用了约3GB;
  • 若该值远大于1,说明数据在内存中高度“膨胀”,需警惕内存压力;
  • 若接近1,则表明序列化/压缩效率高,内存与磁盘占用接近。

Tasks

在介绍完粗粒度的Summary Metrics后,我们进一步深入到更细粒度的Tasks列表。实际上,Tasks表格中展示的许多指标(如 Duration、Shuffle Read Size、GC Time 等)与Summary Metrics中的内容高度一致,其含义完全相同,因此无需重复解释——你可以直接参照前文对Summary Metrics的说明来理解这些字段。

两者的核心区别在于:

  • Summary Metrics提供的是对所有Tasks的聚合统计(例如最小值、中位数、最大值等),用于把握整体趋势;
  • Tasks列表 则逐行展示每个Task的具体指标值,呈现个体行为。

这种细粒度视图特别适用于定位异常任务,例如:

  • 执行时间显著偏长的Task;
  • Shuffle 读取数据量异常高的Task;
  • 发生磁盘 Spill 或 GC 耗时过长的Task。

通过对比单个Task与整体统计的偏差,可以快速识别性能瓶颈或数据倾斜问题,从而实现精准的故障排查与调优。

新增的指标并不多,其中最值得关注的是 Locality Level(本地性级别)

正如调度机制中所讨论的,每个Task在提交时都会携带一个本地性偏好(locality preference) ,用于指示它希望在哪个层级上访问其输入数据——例如是否优先在数据所在的节点、机架或任意节点上执行。Spark调度器会尽可能依据这一偏好,将 Task 分配到靠近其所需数据的 Executor上。

这一机制的核心目标,正是践行Spark的核心设计原则:“数据不动,代码动” 。通过将轻量级的计算逻辑移动到数据所在的位置,而非将大量数据跨网络传输到计算节点,从而显著减少网络 I/O 开销,提升任务执行效率。因此,Locality Level不仅反映了任务调度的亲和性,也是评估数据局部性优化效果的重要依据。

四、实战环节

这里我们选两个典型的例子进行分析调优:

案例一(scan表慢和内存问题)

如上图所示,我们选择运行时间最长的一个Stage进行具体分析调优,点进去进入该Stage内部,如下所示:

观察如上指标会发现两个问题点:

1.这里单个Task处理的数据量为25MB左右,正常来说,一个Task处理的数据量为128-256MB较为合理,这是由于原始表的数据量大,小文件太多,导致分配的Task数据量过大。

2.并且由第二个图可知失败重试的Task的调度等待时间过长,这是由于Task数据量过多,而实际并发只有1000,导致部分Task调度时间太长导致。

对于上述问题,我们可以通过设置表的切片大小缓解问题:

set spark.sql.odps.split.size.du\_shucang.dws\_traffic\_algo\_search\_keyword\_stats_di=512MB添加如上参数后观察效果:

我们第一次将切片大小控制到512MB来减小实际Task数,可以看到相对于上次该Stage会快20min,这里我们还可以继续增大表的切片来迭代看效果,感兴趣的可以自己尝试。

我们在从另外一个角度看待问题,当我们试图减小分配给Stage的Task数量时,那么Task的实际处理数据量就会增大,这里我们可以通过减小"spark.executor.cores"的核数来隐形的增大Task的内存或者调大spark.executor.memory的内存来显性的增大Task的内存,这里由于Task的量级太多,我们选择增大spark.executor.memory来进行优化,效果如下所示:

这里由于本身Task处理的数据量是很小的,所以无法很显著的看到效果,若是出现下图所示的情况,调大内存效果会更显著些:

总结:在Spark中,当读表时由于数据量大,而此时Task处理的数据量又过小时,可以通过set spark.sql.odps. split.size.xxx=xxMB来减小task数量,而通过Spill和Spill Memory Disk来观察其是否需要增大内存或者实际并发量。

案例二(shuffle之后并行度不足)

该case打不开了之前记录的,随便拿了一个还是老样子降序查看最大的。

分析是在Shuffle阶段并行度提升不了。

总结:在Spark中,Join、Group都会在Shuffle阶段产生并行度,这个Shuffle是一个很大的池子,可以利用万能参数进行增加并行度。

  • 读取Shuffle数据的目标大小 ;
  • 提高AQE shuffle默认分区数;

"spark.sql.adaptive.advisoryPartitionSizeInBytes":"64MB";

"spark.sql.adaptive.coalescePartitions.initialPartitionNum":"1000".

注意:有些时候会发现增加了还是没变,那是因为切分还是太大。1,2切分,3调整限制。

"spark.sql.adaptive.advisoryPartitionSizeInBytes":"8KB",

"spark.sql.adaptive.coalescePartitions.minPartitionSize":"8KB"

"spark.sql.adaptive.coalescePartitions.initialPartitionNum":"3000".

五、总结

总的来说,我们通过Spark UI界面去优化亦或者去定位错误,都离不开内存与并行度这两个大的维度,通过相应的Summary Metrics,我们可以很好的定位到问题所在,是内存过小,还是并行度不够,事实上,内存与并行度并不是独立的,而是相互影响,相互制约的一种关系,并行度决定了“有多少个Task同时运行”,而内存决定了“每个 Task能分到多少资源”。

当并行度一定的时候,当我们试图通过调大内存来解决问题而问题并未得到解决的时候,实际上,当内存变大而实际并行度固定时,每个Task所分配的内存就会增大,所带来的额外开销就是做内存穿透的时间增加,从而GC时间增大。

当内存一定时,我们去调大并行度或者增大核数时,每个Task所分配的内存就会变小,就会容易出现内存溢出的风险。

对于一个任务,那我们如何去合理的配置其并行度和内存,这里我们可以看一个任务耗时最长的的几个Stage,假设某个Stage的并行度为5000,而此时所给的参数为executor.memory=12g,executor.cores=4,spark.dynamic

Allocation.maxExecutors=250,此时集群最大并行度250*4=1000,也就是说当前任务需要分5批执行,此时集群中一个Task内存为3g,按照经验论,每Core对应4~8GB内存是合理的,而处理的总并发度通常会是实际并发度的2~3倍为合理,因此,我们将参数设定为 executor.memory=16g, executor.cores=6来进行一个迭代优化。

一个好的Spark作业,Task均衡、无Spill、CPU打满、内存够用,这是我们去做优化的一个理想状态!

往期回顾

1.Sentinel Java客户端限流原理解析|得物技术

2.社区推荐重排技术:双阶段框架的实践与演进|得物技术

3.Flink ClickHouse Sink:生产级高可用写入方案|得物技术

4.服务拆分之旅:测试过程全揭秘|得物技术

5.大模型网关:大模型时代的智能交通枢纽|得物技术

文 /硕

关注得物技术,每周更新技术干货

要是觉得文章对你有帮助的话,欢迎评论转发点赞~

未经得物技术许可严禁转载,否则依法追究法律责任。