决策树还是决策层?决策流程适合树

决策流程这个场景,适合树而不是层。树是前后依赖的,层是前后不依赖的、每一层是通用的。规则引擎天生就是规则流,前后有依赖,所以写规则配置就是树的方式。 现象 树的方式对人是友好的,非常直观、快速理解。 如果我想知道我们是否支持 xxx + xxx 的组合,从左到右很快能得到答案。 如果是层,那么你需要知道每一层的逻辑,同时你还需要知道层与层之间的关联。 所以,决策流程这个场景,适合树,而不是层。 我们的场景 我们的场景是通过环境信息(cpu 架构、型号、qemu 版本等)设置 xml 配置。 这个场景推荐用树的方式,核心原因是架构(arm/x86)是"分支决定性条件",后续逻辑完全依赖该分支,树结构能天然贴合独立流程、降低耦合。 逻辑贴合度高:你的需求本质是"架构分流 → 各自校验 qemu 版本 → 各自输出结果",树结构的"分支独立"特性和这个流程完全匹配; 维护成本更低:后续若扩展(比如 arm 新增"内存≥4G"条件、x86 修改 qemu 版本要求为 > 5.0),树结构可直接在对应分支修改,不影响另一分支; 可读性更强:树结构能直观呈现"arm 线"和"x86 线"的完整流程,排查问题时可直接定位到对应分支。 还有关键一点,我们的场景每个流程线能复用的层比较少,比如 arm 直接就是 passthrough,后面流程都是针对 x86 设计的。 树 vs 层 如果是无依赖的过滤,反而适合写代码,比如 k8s apiserver 里的 webhook。 如果是决策流程,其实是规则流程,就适合规则引擎。 因为规则引擎天生的逻辑就是上一个规则结果传入下一个规则结果,它是前后有依赖的。 而无依赖的是独立的,代码更容易实现扩展性、精确性和复杂逻辑,且就算变化也是局部变化。 层结构更适合"条件无依赖、逐步缩小范围“的场景(比如先过滤系统类型,再过滤内存,最后过滤硬盘,每个条件不依赖前一个的具体结果)。 而你的场景中,qemu 层的判断逻辑(“满足版本后输出什么”)完全依赖前一层的架构结果,用层结构会导致: 同一层(qemu 层)需要嵌入"架构 arm"“架构 x86"的分支判断,违背"层是通用过滤"的设计初衷; 新增架构(比如 risc-v)时,需要修改所有后续层的逻辑,扩展性极差。 核心总结 树是前后依赖的; 层是前后不依赖的,每一层是通用的; 规则引擎的设计,天生就是为了规则流,前后就是有依赖的; 所以写规则配置的时候,就是树的方式,很难用层的方式; 层因为其通用,很可能有较大的复杂性,且还需要扩展性,一般适合用代码实现; 但是因为其通用和局部性,修改难度低,用代码反而比用规则引擎简单。 一切都是为了 kiss。 ...

April 19, 2026 · 1 min · 77 words · 文叨叨

mq 相比直接传递消息的优势

mq 不仅是削峰填谷,也不仅是解耦服务,还有降低代码复杂性、增加稳定性的作用。中间件是一个稳定的、没有逻辑的、仅仅存储数据的。 现象 比如我们代码中安置逻辑里的 resource 的同步。 节点定时上报给 zoneleader,为了避免重复上报,节点自身又缓存了资源信息,每次上报前,先计算当前资源信息,然后和上一次缓存的资源信息对比,如果有差异,再汇报。 zoneleader 还会变。 上面两点,带来的复杂性: zoneleader 变了之后,节点得知道; 节点得区分 zoneleader 是否变化了,变了就直接上报资源信息; 节点不知道 zoneleader 用的资源信息是什么,说不定自己的缓存因为各种原因就不对呢; 处理异常情况能力弱。比如 zoneleader 因为个别原因更新失败,后续节点因为缓存的原因就不重试了。 这种方式虽然没有中心化的依赖问题,但是每个功能都需要处理上述类似的问题。 这是一种繁琐的、让代码复杂性大幅膨胀的方式,也是一种功能之间重复的、却不能统一处理的多余。 也是客户端和服务端相互紧密耦合的一种方式。 用中间件的方式 如果用中间件的方式: 客户端可以拉下来和当前资源对比; 服务端定期从中间件拉取资源即可; 中间件是一个稳定的、没有逻辑的、仅仅存储数据的。 所以: 客户端的逻辑只需要客户端处理,不变接口就不会影响服务端; 服务端也一样,自己处理逻辑,不用管客户端; 节点也不用关心 zoneleader 是不是变化了,它只想一个固定的中间件地址推送就行了; zoneleader 变化了,也不用想办法通知到其他节点,存储到中间件就行。 好处: 解耦; 不会相互影响了; 节点也知道当前使用的资源信息是什么样的,可以对比。 中间件依赖本身可以通过各种高可用方式保证其可用性,在现在已经不是什么主要矛盾或问题了。 小结 mq 不仅是削峰填谷,也不仅是解耦服务; 还有降低代码复杂性、增加稳定性的作用; 中间件是一个稳定的、没有逻辑的、仅仅存储数据的; 客户端和服务端通过中间件解耦,互不影响; 中间件依赖可以通过高可用方式保证可用性,不是主要矛盾。

April 18, 2026 · 1 min · 50 words · 文叨叨

check 和修复,要从大流程上就分开

check 有很多项,每个项都有修复。check 是一条线,修复是一条线。不要在 check 中发现某个项有问题就进行修复,而是要等 check 完再调用修复。 核心:check 和修复职责分离 check 有很多项,每个项都有修复。 那么: check 是一条线; 修复是一条线。 不要在 check 中,发现某个项有问题就进行修复,而是要等 check 完,再调用修复。 修复的里面比如也包含 check,但是这不能代替 check 流程。 因为有些情况,只需要 check,最典型的是告警和健康状态。 check 就得响应迅速,迅速告警。 里面掺杂了修复,有几个问题 健康状态不好管理:难道项目修复前是不健康,修复后改成健康,那下一个检查项呢?这样变来变去,很不稳定,上层没法用; 代码零散:就算在流程里更新健康状态,那得多零散,代码处理也不太好; 响应不及时:修复操作有的很耗时,影响 check 的效率。 检测和修复完全的职责分离 健康状态是检测修复。 那么修复的流程,即便修复完成,也不要管理健康状态。 下一次检测流程自然会更新健康状态。 小结 check 和修复要从大流程上就分开; check 是一条线,修复是一条线; 不要在 check 中发现某个项有问题就进行修复,要等 check 完再调用修复; check 就得响应迅速,迅速告警; 检测和修复完全的职责分离,修复完成也不要管理健康状态,下一次检测自然会更新。

April 12, 2026 · 1 min · 51 words · 文叨叨

底层单一,上层组合

底层逻辑尤其要保证单一,一个能力只提供一种方法。想要更多,通过上层组合、上层封装来实现。千万不要把所有需求都放到底层原子能力上。 核心:底层单一,上层组合 底层逻辑尤其要保证单一,一个能力只提供一种方法。 想要更多,那么通过上层组合、上层封装来实现。 千万不要把所有需求都放到底层原子能力上,虽然有些看起来好改,实际就是埋大坑。 符合设计原则、编程哲学是很重要和长久的。 来一个需求找一个好改的方式,这个评估维度是片面和短视的。 一个例子:网卡带宽 这个带宽控制是从精细化到模糊化管理。 从 iaas 的角度,这个管理方式是合理的,能适配多样化产品形态; 从产品的角度,需设计好统一的、标准化的控制方法。 nic 是网卡维度的控制; instance_extra 是云主机维度的控制; server.yaml 是全局控制。 看起来合理,实际没有人捋清楚,看代码看很久。每次梳理了就忘。 合理的方式应该是: 底层保持单一,容易理解; 云主机调整和全局的调整可以通过 api 来实现。 这样比较明确,以后也可以实现大会上说的"功能使用频率统计"。 批量、整体,这类操作,千万不要隐藏在配置或者底层的逻辑中,一定要以功能、api 的方式提供。 这样明确、可量化统计、可观测、可追溯。 小结 底层逻辑尤其要保证单一,一个能力只提供一种方法; 想要更多,通过上层组合、上层封装来实现; 千万不要把所有需求都放到底层原子能力上,那是埋大坑; 批量、整体操作,一定要以功能、api 的方式提供; 这样明确、可量化统计、可观测、可追溯。

April 11, 2026 · 1 min · 36 words · 文叨叨

Hook、插件、Middleware:三种扩展机制的区别

Hook 侧重现有流程上运行时的局部修改,插件侧重模块化功能扩展,Middleware 是流程中可以扩展的横切面。三者核心目标都是不侵入主程序的前提下实现扩展。 Hook vs 插件 Hook 与插件是两种常见的扩展机制,它们在实现方式、应用场景和解决的问题上存在显著差异。 **Hook(钩子)**是一种通过拦截或修改现有流程的执行逻辑,插入自定义代码的机制。 插件是一种模块化的功能扩展机制,通过动态加载的独立模块向主程序注入新功能。 维度 Hook 插件 实现方式 拦截或替换现有流程(如函数调用、事件触发) 动态加载独立模块,通过 API 与主程序交互 侵入性 低(无需修改源码,直接拦截流程) 低(主程序无需依赖插件) 控制粒度 细粒度(可精确到单个函数或事件) 粗粒度(通常针对模块级功能) 动态性 运行时生效(如热补丁) 运行时加载/卸载(如插件管理器) 典型用途 行为拦截、动态修改、事件统一处理 功能扩展、模块化开发、多租户定制 hook 侧重现有流程上运行时的局部修改,插件侧重模块化、功能扩展、架构层的设计。 Middleware vs Hook Middleware(中间件):是一种串联在核心流程上下游的组件,用于对核心操作的输入/输出进行预处理、后处理或增强。它通常形成一条"管道(Pipeline)",请求/数据会按顺序经过多个中间件,最终到达核心逻辑,再沿原路返回。核心目标是解耦通用功能(如日志、认证、缓存)与核心业务逻辑。 Hook(钩子):是在程序特定生命周期节点(如初始化后、销毁前、事件触发时)预留的"接口",允许开发者注册自定义逻辑,在节点触发时自动执行。钩子不直接干预核心流程的输入/输出,而是"挂靠"在关键节点上响应事件。核心目标是提供扩展点,允许开发者在不修改核心代码的前提下,介入程序的生命周期或事件处理。 middleware 是流程中可以扩展横切面,可以扩展多个。hook 是某些固定的点上,留下 hook 口。 middleware 是流程上的扩展; hook 是功能点上的扩展,比如 delete 后,调用一个 hook 做一些其他资源清理或检查,但是这 delete 不一定会调用,不是流程必须的; middleware 就是主流程中的扩展。 Middleware vs 微内核 Middleware 是一种嵌入核心业务流程前后的组件,用于处理通用化、横切性的功能(即"横切关注点")。它的核心作用是增强核心流程的能力,同时保持核心业务逻辑的简洁性。 微内核架构的核心是一个最小化的"内核"(仅包含系统最基础的能力,如模块管理、通信机制),所有业务功能通过插件(Plugin)动态扩展。内核不包含具体业务逻辑,仅负责插件的加载、卸载、通信和生命周期管理。 维度 Middleware 中间件 微内核架构 设计目标 处理"横切关注点",增强核心流程的通用能力 支持功能动态扩展,实现系统模块化和解耦 与核心的关系 嵌入核心流程的执行链中,依赖核心流程存在 插件独立于内核,内核仅负责管理插件(不依赖插件) 调用方式 链式调用(前一个中间件处理后传递给下一个) 插件通过内核注册/调用,插件间通过内核间接通信 动态性 通常静态配置(启动时确定中间件组合),动态性弱 支持运行时动态加载/卸载插件,动态性强 功能定位 聚焦"流程增强"(如日志、认证、缓存) 聚焦"功能扩展"(如新增业务模块、工具) 典型示例 Gin 日志中间件、JWT 认证中间件 VS Code 插件系统、Linux 内核模块 关键区别总结 与核心流程的关系: ...

April 5, 2026 · 1 min · 110 words · 文叨叨

用主流技术:技术是手段,不是目的

跟随主流技术非常重要,不要老用一些偏门的、古怪的技术凸显自己的别具一格。技术是手段,不是目的,所以越主流越好。 核心:跟随主流技术 跟随主流技术,非常重要。 不要老用一些偏门的、古怪的技术,凸显自己的别具一格。 比如,用 lxc 还是 docker。 别说两者底层原理都一样,用 lxc 自身可控性更好。生态和工具的适配完全是天和地的区别,这已经不是跨级别的问题,而是天壤之别。 再比如网络,主流都是用 ovs,所以别人智能网卡、dpu、新的一些设备、网络卸载等,适配的都是 ovs。 你自己搞一套,或者用小众的,别人不适配你啊。 还有国产化,都是靠着主流去适配的。 技术是手段,不是目的 技术是手段,不是目的。 所以,越主流越好。 主流代表着稳定、可靠、被大规模多场景验证过,同时有大量的解决方案和排查知识,也有大量的人才支撑。 手段不用在意创新,只需要稳定、可达、低成本。目的才需要考虑创新,创造价值差异。 小结 跟随主流技术非常重要; 不要老用偏门的、古怪的技术凸显自己的别具一格; 生态和工具的适配是天壤之别; 技术是手段,不是目的,越主流越好; 主流代表稳定、可靠、被大规模验证过,有大量解决方案和人才支撑。

April 4, 2026 · 1 min · 27 words · 文叨叨

耦合的松紧,关键在于变化

什么是耦合,怎么判断有没有耦合?可以从结果反推:A 变化了,B 要不要跟着变化。如果跟着变,那就是耦合了;如果没有跟着变,或者变动很小,那就是松耦合。 核心:耦合的松紧关键在于变化 什么是耦合,怎么判断有没有耦合? 可以从结果反推: A 变化了,B 要不要跟着变化; 如果跟着变,那就是耦合了; 如果没有跟着变,或者变动很小,那就是松耦合。 耦合的松紧关键在于变化。 小结 判断耦合可以从结果反推; A 变化了,B 跟着变,就是耦合; A 变化了,B 不变或变动很小,就是松耦合; 耦合的松紧关键在于变化。

March 29, 2026 · 1 min · 21 words · 文叨叨

开发的时候就要想部署交付:为什么要云原生

开发决定了部署。开发的时候就要想哪种方式部署简单、能标准化部署、能尽量减少环境依赖。这就是为什么要云原生。 核心:开发决定了部署 开发的时候,就要想: 哪种方式部署简单; 哪种方式能标准化部署; 哪种方式能尽量地减少环境依赖,让部署不容易出错。 比如部署 BM,开发的时候不能只想着能把 BM 跑起来,还要想后续部署能否简单、标准化、少定制化配置。 比如动态启动 pxe server(tftp、dhcpd)和静态配置好这两个服务,是否可行,对于部署哪个友好。 当然,当前最好的方式当然是云原生化。 为什么要云原生 部署/交付是一个很重要的事,且很消耗成本,且能算得清的成本的事情。 所以为什么云原生很重要,它把应用交付的效率大大提高了。 而开发决定的是部署交付。 这就是为什么要云原生,就是从第一天开发,就要想着以后是 k8s 上部署交付的。 如果你不想着 k8s 上部署,像我们屎山代码,各种通过 hostname 点对点通信,各种 ssh 执行命令,各种 exec 执行宿主机的命令,后续很难部署到容器中。 云计算 vs 云原生 云计算解决的是 IT 资源的交付效率、基础设施的运维; 云原生解决的是软件交付效率、软件生命周期的运维。 小结 开发决定了部署; 开发的时候就要想哪种方式部署简单、能标准化部署、能尽量减少环境依赖; 部署/交付是很消耗成本、且能算得清成本的事情; 云原生把应用交付的效率大大提高了; 从第一天开发,就要想着以后是 k8s 上部署交付的。

March 15, 2026 · 1 min · 45 words · 文叨叨

封装和独立的区别:复用 vs 管理能力

封装仅仅是为了复用,但是独立有管理能力。封装调用了就得执行,独立有自己的一套管理规则,比如排队、限流、调整优先级、丢弃、转换等。 核心:封装 vs 独立 封装仅仅是为了复用,但是独立有管理能力。 封装:调用了就得执行,里面通过各种条件逻辑控制行为; 独立:有自己的一套管理能力,有自己的一套规则。 请求来了,不是立马执行逻辑,而是先进管理规则。 比如排队、限流、调整优先级、丢弃、转换等等。 独立不一定要独立服务 独立并不是说要一个独立服务,服务内的一个模块、一个库,都可以是独立的。 一个例子:report 服务 应该有个独立的 report。 如果是 report 函数,大家分散调用,调用的频率不一样: A 30 秒调用一次; B 20 秒调用一次; … 这玩意没法统一处理,调整和排查的时候也麻烦。加一个监控项的时候也很麻烦,触发的地方、触发的间隔、获取数据都需要考量。 如果像 k8s 一样统一上报,比如 kubelet 收集 node 信息上报给 k8s,至于收集哪些监控项,kubelet 再提供相应的接口,插件对接 kubelet 的接口即可。 这样的好处是,扩展仅仅是本地收集的处理,上报流程不需要考虑,甚至不需要知道。 核心还是提取不变的,封装变化的。 小结 封装仅仅是为了复用,独立有管理能力; 封装调用了就得执行,独立有自己的一套管理规则; 独立不一定要独立服务,服务内的模块、库都可以是独立的; 统一管理:其他地方过来调用仅仅是一个请求,什么时候调用、如何调用、什么优先级,有自己的管理; 核心是提取不变的,封装变化的。

March 7, 2026 · 1 min · 45 words · 文叨叨

抽象流程还是抽象对象?一写二抄三重构

要不要抽象,关键在于要不要复用。业务流程都是独立的,不需要复用;技术类流程需要复用。一写二抄三重构,第二次大量重复时就可以开始重构了。 现象 写代码的时候,总纠结要不要抽象流程、抽象对象、面向组合。 这样很难,因为没有明确的复用需求,不知道稳定点在哪。 核心:一写二抄三重构 一写,二抄,三重构。 要不要抽象,关键在于要不要复用。 业务流程都是独立的,不需要复用,所以谈不上抽象; 技术类的流程,是需要复用的。 但是没必要第一次就绞尽脑汁抽象流程、抽象对象、面向组合。 第二次是抄还是重构 不是说第二次就一定是抄。 如果是一个方法,抄没问题; 如果是一个流程,一堆方法,第二次抄就不合理了。 这样的后果是第三次重构非常困难,甚至会因为功能的兼容影响没法进行。 如果是大量的话,第二次就可以开始重构了。 抽象流程 vs 抽象对象 k8s 为什么牛逼,原因在于它定义了一套声明式的流程。 我们创建主机、创建备份等,看起来流程不一样,但是上升一个维度,都能用声明式的流程来处理。 但 2026 年更新后的认知是:k8s 是极致的面向对象。 虽然它的控制器是面向流程的,但是控制器仅仅是对象下的血肉。 顶层还是围绕对象设计的; 控制器是属于对象的; k8s 极致面向对象,消除流程,将流程隐藏在对象中。 流程是易变的,对象是相对稳定的,所以不会去抽象流程,而是抽象对象。 一个例子:采集 pv-lv 和 lv info 数据采集、数据同步,涉及到新增和删除。 所以这里的流程最后抽象为: 采集; 从 db 获取; diff 得到 insert 和 delete; 执行 insert 和 delete。 不同的数据对象,提供支持 diff、insert 和 delete 的方法即可。 这个流程也不是一开始就想出来的,也是边写、边思考做出来的。 小结 要不要抽象,关键在于要不要复用; 业务流程独立,不需要复用;技术类流程需要复用; 一写二抄三重构,第二次大量重复时就可以开始重构; 流程是易变的,对象是相对稳定的,所以抽象对象而非流程; 好的抽象是边写边思考做出来的,不是一开始就设计好的。

February 15, 2026 · 1 min · 64 words · 文叨叨