纯情 发布的文章

一、电脑开机 / 硬件层面(最实用冷门功能)

  1. 快速启动菜单开机刚亮屏、品牌 Logo 界面连续按F12,直接调出临时启动项选择列表。可以自选:U 盘、固态硬盘、机械硬盘、光驱、网络启动。作用:重装系统、PE 启动盘、维修电脑必备,不用进 BIOS 改启动顺序,一次性生效
  2. BIOS / 固件相关部分品牌机型:F12 配合组合键可进入硬件诊断界面;联想、戴尔、惠普台式 / 本主流都是F12 = 启动菜单专属键
    • *

二、Windows 系统自带通用功能

  1. 桌面 / 文件资源管理器选中文件 / 文件夹按 F12重命名(和右键重命名、F2 效果一致)。
  2. 系统默认无强制绑定不像 F5 刷新、F1 帮助,Windows 原生桌面空白按 F12 无任何反应。
    • *

三、办公软件(Word/Excel/PPT 全家桶)

  1. 单独按 F12一键弹出【另存为】窗口,快速保存文件到指定位置、改文件名、改格式。
  2. 组合键拓展
  • Ctrl + F12:打开文件(代替左上角「打开」)
  • Shift + F12:快速保存(等同于 Ctrl+S
    • *

四、浏览器(Edge/Chrome/ 火狐通用)

  1. 单独按 F12打开开发者调试面板,核心用途:
  • 查看网页源代码、CSS 样式、JS 代码
  • 抓包:查看网页加载的图片、视频、接口数据
  • 模拟手机 / 平板屏幕尺寸,调试手机端页面
  • 清除网页缓存、强制刷新、排查网页报错
  1. 关闭方式:再按一次 F12 直接关闭调试栏。
    • *

五、编程 / 设计 / 工具类软件

1. 代码编辑器(VS Code、VS、IDEA、PyCharm)

  • F12跳转到代码定义处鼠标放在函数、变量、类名上按 F12,直接跳到源头代码,写代码高频快捷键。
  • Ctrl+F12:查看当前文件所有函数 / 结构列表。

2. 设计软件

  • PS:F12 = 恢复原图(撤销所有临时操作,回到打开初始状态)
  • CAD:F12 = 开关动态输入(画图时悬浮坐标输入框)

3. 记事本 / 极简编辑器

F12 无默认功能,空白按键。

    • *

六、笔记本电脑特殊情况(重点)

现在轻薄本 / 游戏本基本都有Fn 功能锁

  1. 默认模式(多媒体优先)直接按 F12 = 调高音量(F11 减音量、F10 静音)此时想要上面所有 F12 功能(启动菜单、另存为、开发者工具),必须按:✅ Fn + F12
  2. BIOS 开启「功能键优先」改完设置后:直接按 F12 就是标准功能,调音量需要 Fn+F12
    • *

七、Mac 电脑专属

  1. 普通妙控键盘 / 笔记本自带键盘
  • 原生 F12:音量增大
  • Fn + F12:触发 Windows 同款开发者工具、软件 F12 功能
  1. Mac 浏览器调试:常用替代快捷键:Command + Option + I (和 F12 效果一样)
    • *

八、游戏场景

绝大多数单机 / 网游:F12无默认冲突键,很多玩家会自定义:

  • 截图、录像、快捷吃药、隐藏 UI 界面部分 steam 游戏:默认 F12=Steam 内置截图。

经常刷到老破小租金年化 4%,甚至有些地方 6-8 个点的信息。
但我总感觉事情不会是看起来那么美好。
不知道我的想法有没有什么错误。

拿 4%来算,不考虑空置,维修等损耗,
也需要 25 年才回本。(当然你可以随时变现,但房子在贬值,老破小流通又极低)。
25 年,现在的老破小得 40 年房龄了,现在的次新房得 30 年房龄,这么大规模的老房子,
谁能保证有钱有余力保养的很好?

房子老化 设施老化 稍微钱包宽裕点的人就会选择更好的住处。
伴随而来的就是低收入人群的增多。租金就很难一直保持高水准。
然后反过来,又会加剧小区和房子的脏乱差。
于是恶性循环。

然后 25 年后 差不多是 2030 年出生的孩子开始毕业租房找工作。
按现在的人口出生趋势,那时候的年轻人必然比现在更少,而且少得多。
租房需求也必然更低。

还要算上现在这么多家庭有房,到时候遗传给年轻人,租房的人就会更少了。

所以在 25 年的长周期下,4%的收益率远远没有看起来那么美好。

0.前序

接上一个帖子打算下周去做鼻中隔手术了,v 友有啥建议吗 - V2EX
我本来以为这个问题的人不多,因为手术的事情和同事们交流了一下,
发现大家有类似交替性鼻塞的人还挺多的
简单记录一下这次鼻中隔手术的经历,也给后面有类似困扰的 v 友做个参考。

1.手术过程

一早去医院办理入院手续,术前需要禁食禁水,一直等到下午才进手术室。
全麻进去醒来已经是出院被推回病床了,最直接的感受就是:鼻子完全不能呼吸。
两个鼻腔被塞了吸收+不可吸收的一大团棉花,只能靠嘴呼吸
之前一直以为口呼吸会很难受的,不过习惯了其实还好。

2.坐牢的两天住院

比手术本身更难受的,反而是住院这两天。
一个是医院的超阳间作息,超早提前发饭、一大早收躺椅、查房、各种动静都很大;
另一个是左右病床的噪音攻击,左侧带阿姨的小孩,右侧一对老夫妻,各种闲话噪音轮番的吵。
吃饭呼吸全用口,导致吃饭吃两口就要赶紧哈两口气()。
48 小时只能口呼吸,久了会特别的干睡眠会更浅,导致我住院两天感觉没睡着几个小时

3.拔线出院

睡了两晚会去拔海绵,麻醉药液只能滴在海绵上作用有限,
医生通过线头旋转式的拉出海绵,痛到直接掉眼泪缓了好久,有种抽取灵魂的痛感()

4.费用

手术一共花销 7k+,统筹走了 5k+,自费 1k ,个人医保走了 1k 。
整体费用不算高,只是个人医保走得不多,公司商业保险估计也报不了多少。

5.出院之后

出院后味觉和嗅觉混乱了很久,一周内在陆陆续续恢复。
出院后不知道是不是医院没休息好,回家就发烧持续了好几天。
最开始几天还会鼻腔出血,看到自己血一滴一滴的掉下来还是有点恐怖的。
每天都能洗出一长串带血丝的鼻涕,清理出来有种奇怪的爽感(?)
最明显的变化是睡觉不用左右翻滚了,之前侧躺必定有一侧鼻腔不通气。
现在双侧通气之后,睡眠质量明显改善。
通过 watch 的睡眠数据来看,我能一次睡 4 个小时的深度睡眠很开心

6.复诊以及彻底恢复

医生复查看了下情况,试着拔掉了下鼻腔内的结痂。
目前的情况说拔掉会出血后面长好了会掉的,后续喷雷诺考特+勤洗鼻就行。
啥药没开,喊一个月后再来第二次复查。
哪怕目前鼻腔内还有部分结痂,呼吸两侧都畅通无阻,非常的爽

7.术后感受

一句话总结就是:《真的后悔没早点做》
鼻腔通气会带来很多侧面的效果:
首先是氧气获取充足,之前一些不想思考的琐碎问题基本现在能短暂再过一遍脑子再答复。
其次是休息好了精力更好了,整个人更有精神气,面部容貌似乎有不可靠的小幅提升()。
最后是以前交替性的鼻塞,更容易诱发鼻炎,现在少多了,哪怕喷雷诺考特也能更有效的喷入起效。

8. 最后

最后的最后,鼻腔双通之后感觉生活美好多了,说成一次新生都不为过。
希望大家都能拜托鼻炎/鼻塞的痛苦,回归原本的生活。

想挑把机械键盘,预算 300 左右,轴体不限,不是那种特别吵的就行
最好能附赠 MacOS 的键帽

Perl脚本自动化日志分析与数据批量处理实操案例

一、案例背景

在服务器运维、业务系统运行过程中,会产生海量日志文件,包含运行报错、接口请求、访问流量、异常告警等各类信息。人工逐条筛查日志效率极低,且容易遗漏关键故障信息。Perl语言具备强大的文本处理、正则匹配、文件遍历能力,原生适配各类日志格式解析,无需复杂环境依赖,非常适合轻量化日志分析与数据批量整理场景。

本文以运维实际工作为依托,讲解通过Perl脚本实现日志遍历、关键字过滤、异常信息提取、数据统计与结果导出的完整实操流程,适用于Linux服务器环境下系统日志、应用业务日志的自动化处理。

二、环境准备

  1. 运行环境:CentOS 7/8、Ubuntu 20.04 等Linux发行版,系统默认预装Perl,无需额外安装。
  2. 日志样本:模拟业务接口日志,包含正常请求、超时异常、参数错误、接口宕机等日志行,格式为时间戳、请求地址、响应状态、耗时、错误描述。
  3. 工具依赖:无需安装第三方模块,仅使用Perl原生内置函数与正则表达式,兼容性极强。

三、核心需求

  1. 批量遍历指定目录下所有.log后缀日志文件;
  2. 利用正则匹配筛选超时、报错、宕机类异常日志;
  3. 统计各类异常发生次数、高频异常接口排行;
  4. 将筛选后的异常日志与统计结果自动写入新文件,便于运维复盘;
  5. 脚本可直接定时任务调用,实现每日日志自动分析。

四、完整实操代码

#!/usr/bin/perl
use strict;
use warnings;

# 定义日志目录与输出结果文件
my $$log_path = "/var/log/biz/";
my $$out_file = "./log_analysis_result.txt";
my %error_count;

# 打开输出文件
open(my $$out_fh, '>', $$out_file) or die "无法创建输出文件: $$!";

# 遍历日志目录
opendir(my $$dir_fh, $$log_path) or die "无法打开日志目录: $$!";
while (my $$file = readdir($$dir_fh)){
    next if $$file =~ /^\./;
    next unless $$file =~ /\.log$$/;
    my $$full_file = $$log_path.$$file;

    # 逐行读取日志文件
    open(my $$log_fh, '<', $$full_file) or next;
    while(my $$line = <$$log_fh>){
        chomp $$line;
        # 正则匹配异常关键字
        if($$line =~ /超时|报错|宕机|500|404/){
            print $$out_fh "【$$file】$$line\n";
            # 统计异常类型
            if($$line =~ /超时/){$$error_count{超时}++;}
            elsif($$line =~ /500/){$$error_count{服务内部错误}++;}
            elsif($$line =~ /404/){$$error_count{接口不存在}++;}
            elsif($$line =~ /宕机/){$$error_count{服务宕机}++;}
        }
    }
    close($$log_fh);
}
closedir($$dir_fh);

# 写入统计汇总信息
print $$out_fh "\n===== 异常日志统计汇总 =====\n";
foreach my $$type (keys %error_count){
    print $$out_fh "$$type 发生次数:$$error_count{$$type}\n";
}
close($$out_fh);

print "日志分析完成,结果已输出至 $$out_file\n";

五、代码解析

  1. 严格模式use strict;use warnings; 强制语法校验,规避变量未定义、语法不规范等问题,提升脚本稳定性。
  2. 路径定义:自定义日志扫描目录和结果输出文件路径,可根据实际业务场景直接修改。
  3. 目录遍历opendirreaddir遍历目录文件,过滤隐藏文件与非日志文件,精准匹配.log后缀日志。
  4. 正则匹配:通过Perl正则表达式匹配日志中常见异常关键字,精准筛选故障日志行。
  5. 数据统计:使用哈希数组对不同异常类型计数,自动分类统计各类故障频次。
  6. 文件读写:全程文件句柄操作,逐行读取、逐行写入,适配大日志文件处理,不占用过多内存。

六、脚本运行与落地使用

  1. 将脚本保存为log_analyze.pl,赋予执行权限:

    chmod +x log_analyze.pl
  2. 手动运行脚本:

    perl log_analyze.pl
  3. 配置定时任务:通过crontab设置每日凌晨自动执行,实现日志每日自动化分析,无需人工干预。
  4. 结果查看:运行后生成log_analysis_result.txt,包含原始异常日志与分类统计数据,可直接用于故障排查与运维报表整理。

七、拓展优化方向

  1. 支持多后缀日志筛选,新增.txt.log.old等日志格式匹配;
  2. 引入时间正则,按指定时间段筛选日志,精准定位某一时段故障;
  3. 增加日志去重功能,过滤重复报错信息;
  4. 对接邮件推送,分析完成后自动将结果发送至运维邮箱;
  5. 扩展支持CSV格式输出,便于导入表格做数据可视化分析。

八、总结

Perl凭借原生强悍的文本正则处理能力,在日志分析、文本批量处理、数据清洗等场景具备极高实用价值。本案例实现了日志批量遍历、异常筛选、分类统计、结果导出全流程,代码精简无第三方依赖,部署简单、运行高效,适配中小型企业运维、业务系统日志日常排查工作。脚本可根据实际日志格式灵活修改正则规则,通用性强,可快速复用至各类项目的自动化运维场景中,大幅降低人工日志处理成本,提升故障排查效率。

在全球贸易格局剧烈重构的今天,碳排放已彻底褪去单纯的环境议题外衣,演变为直接决定企业生存权与利润率的战略变量。然而,绝大多数制造企业的碳管理体系仍深陷于“看电表、填表格”的初级阶段,这种基于 Scope 1 直接排放的狭隘视角,导致企业将高达70% 的碳排放——即深嵌于供应链与产品设计中的间接排放(Scope 3)——视为不可控的“隐形盲区”。当欧盟 CBAM 碳关税机制全面落地,以及全球头部客户强制要求产品全生命周期碳足迹披露时,这种管理模式的滞后性正在将传统制造企业推向合规风险与市场份额流失的双重深渊。

这种视而不见并非源于无知,而是源于传统管理工具在面对复杂系统时的结构性失效。

数据断崖:贸易壁垒下的“黑箱”危机

以某出口型 OEM 工厂为例,这家拥有成熟生产线的大型制造企业在面对欧盟 CBAM 申报时,遭遇了突如其来的“数据休克”。由于长期缺乏全链条数据治理,该厂仅能依赖厂内电表的读数来计算直接排放,而忽略了原材料生产、物流运输及零部件加工等关键环节的能耗与工艺数据。

结果是严峻的:下游欧洲客户依据其提供的不完整碳数据,认定该产品隐含碳含量存在重大偏差,并未直接整批拒收,而是立即启动退货审查程序并勒令企业限期整改,同时因无法提供经过审计的完整供应链数据,该企业在 CBAM 申报中被系统自动归类为“默认高排放”,被迫按行业最高系数缴纳高额碳调节费。这一案例极具代表性——在碳关税时代,没有全链条数据的“黑箱”就是企业的“死穴”

仅凭厂内数据算出的“低碳产品”,在覆盖 Scope 3 的碳核算体系面前,其信用度等同于零。这种因数据缺失导致的被动合规,不仅造成了直接的巨额经济损失和潜在的订单流失风险,更让企业在国际供应链中的信誉瞬间崩塌。企业试图用简单的加减法去处理系统性的工程问题,最终在复杂的全球贸易规则面前碰得头破血流。

在碳管理领域,数据的颗粒度直接决定了企业的抗风险能力。当核算范围局限于围墙之内,企业实际上是在裸泳,任何微小的政策风浪都可能导致沉船。

核算失真:内部定价模型的失效

碳管理缺失的负面影响不仅体现在外部贸易,更深刻地侵蚀了企业内部的经营逻辑。在典型的制造企业中,采购部门往往只能获取原材料的物理参数,却无法获取这些原材料在生产过程中的具体能耗数据。为了应对成本核算需求,他们不得不依赖行业平均系数或粗糙的估算值进行建模。

这种“估算文化”导致了内部定价模型的根本性失真。当企业试图向海外客户证明自身具备低碳优势,以争取绿色溢价订单时,由于缺乏真实、可追溯的碳足迹数据,其报价中的“绿色成本”无法被量化验证。客户无法信任基于估算的减排声明,导致企业错失了大量高附加值的绿色订单。

更深层次的问题在于,管理层无法通过精确的碳数据识别出哪些上游供应商是真正的“高碳源”,从而在采购决策中失去了优化供应链的抓手。这种因数据颗粒度不足导致的战略误判,使得企业在绿色转型的赛道上起跑即落后。大多数企业盯着直接排放做管理,却对嵌入在供应链与产品设计中的间接排放缺乏感知——而这部分往往才是减排空间真正集中的地方。 忽视这一核心矛盾,企业的降本增效努力往往事倍功半,陷入边际效益递减的死循环。

试错成本:研发设计与供应链重构的代价

对于大型设备商而言,缺乏历史工艺碳足迹数据的后果更为惨痛,它直接导致了研发阶段的盲目决策。某大型设备商在研发新一代高端设备时,由于缺乏过往产品的详细碳足迹数据库,研发团队仅凭经验直觉沿用了一套高能耗的工艺流程设计方案。

直到产品即将上市前夕,碳成本核算模型才暴露出严峻现实:该设计的隐含碳成本远超市场预期,且无法满足目标市场的准入标准。此时,企业被迫在量产前紧急重构整个供应链,重新进行工艺设计并更换关键零部件。这一系列紧急补救措施不仅打乱了产品上市节奏,更造成了数千万的沉没成本。

这一场景深刻揭示了:在缺乏数据模型支撑的试错中,每一次“拍脑袋”决策背后,都是真金白银的浪费。当碳成为核心成本要素时,传统的“先设计、后核算”线性模式必须被颠覆。企业需要一种能力,能够在产品诞生之初,就通过全生命周期的视角审视材料选择与工艺路径,将可持续战略从合规报告变成产品竞争力的组成部分。

Excel 困局:传统管理模式的终局

许多传统企业试图通过 Excel 表格来人工汇总分散在各车间、各供应商的能耗与物料数据。然而,面对数千种物料规格、动态变化的物流网络以及复杂的 BOM 结构,这种手工方式注定是低效且不可持续的。统计周期往往长达数月,数据严重滞后,且由于口径不一,不同部门间的报表无法对齐。

在这种模式下,管理层永远无法看到实时的碳排全景,所有的战略决策都依赖于滞后的静态报表和模糊的经验判断。当市场风向突变或政策收紧时,企业早已失去了调整的最佳窗口期。这种“动作多、成效微”的 ESG 困境,本质上是由于技术手段无法匹配业务复杂度,导致碳管理从“经营护城河”退化为纯粹的“合规负担”。企业碳排放数据横跨原料、生产、物流等多个环节,信息分散、口径不一,全生命周期的系统量化是让决策提速的前提。 没有系统化的数据底座,任何宏大的 ESG 战略都只能是空中楼阁。


破局之道:从被动核算到主动优化的数字化闭环

要打破上述困局,企业必须从管理思维与技术工具两个维度进行系统性重构。核心在于部署专业的 LCA(生命周期评价)全生命周期数字化系统,彻底打通采购、生产、物流的数据孤岛。

通过部署如“青绿蓝 LCA"等专业化系统,企业可以实现数据的自动采集与实时映射。系统将 BOM 结构、能耗数据、物流轨迹与碳排放因子库进行深度关联,构建起精准的碳足迹画像。这不仅解决了手工统计的误差与滞后问题,更重要的是,它构建了碳足迹模拟推演模型。企业可以在产品设计阶段就进行多方案碳成本模拟,在原料采购阶段即可识别高碳路径,从而在源头重塑产品竞争力。可持续发展战略正在经历一次根本性转变:从依赖经验判断和定性描述,走向基于模型与数据的精确评估。 这种转变要求企业具备将技术路径选择、减排措施优先级等资源效率问题,转化为可计算、可优化的模型问题的能力。

价值重塑:将碳数据转化为核心经营变量

数字化转型的最终目的,是将抽象的绿色理念转化为可量化的经营变量。当碳数据成为成本洞察与风险抵御的利器时,企业便能从被动的合规执行者转变为主动的战略布局者。精准识别高碳路径,不仅能大幅降低潜在的碳税成本,更能通过真实的低碳数据向全球供应链证明自身的绿色优势,从而锁定绿色溢价。

在碳中和的全球浪潮下,碳管理能力已不再是锦上添花的选修课,而是企业构建难以复制的数字化竞争壁垒的必修课。绿色供应链的核心不是采购政策,而是可见性——你需要知道每一种原料、每一段运输、每一道工序实际带来了多少排放。 做不到这一点,“绿色”就只是一个声明而不是能力。只有将碳数据深度融入业务流程,企业才能真正跨越“合规负担”的陷阱,建立起面向未来的“经营护城河”。

对于准备启动绿色转型的企业而言,关键在于找到可量化、可追溯的数据基础,让每一项能源消耗都有据可查。没有这层基础,战略就会悬在空中。

立即行动,掌握绿色主动权:
如果您希望获取专属的碳足迹诊断方案,或了解如何通过数字化系统降低合规成本,请访问官网 lcapillar.com 预约专家咨询,或加入专业交流社区 bbs.lcapillar.com 获取最新行业案例与工具实践。我们提供限时转型评估服务,助您快速跨越数据鸿沟。

面对日益严峻的碳约束,企业必须清醒地认识到,企业设定 2030 减排路线图并不难,难的是将目标拆解为每条业务线、每个工艺节点可执行的行动计划,并持续追踪与校正。 缺乏系统数据支撑的目标,执行越深越容易失控。唯有依靠精密的数字化系统,让每一项减排投入都能找到科学的落脚点,企业方能在变局中掌握主动权,将碳挑战转化为新的增长曲线。

理解思路

  • 为什么我们说上面的是Container呢?我们看下几个Container之间的关系

从上图上,我们也可以看出Container顶层也是基于Lifecycle的组件设计的。

  • 在设计Container组件层次组件时,上述4个组件分别做什么的呢?为什么要四种组件呢?

如下是Container接口类的相关注释

 * <li><b>Engine</b> - Representation of the entire Catalina servlet engine,
 *     most likely containing one or more subcontainers that are either Host
 *     or Context implementations, or other custom groups.
 * <li><b>Host</b> - Representation of a virtual host containing a number
 *     of Contexts.
 * <li><b>Context</b> - Representation of a single ServletContext, which will
 *     typically contain one or more Wrappers for the supported servlets.
 * <li><b>Wrapper</b> - Representation of an individual servlet definition
 *     (which may support multiple servlet instances if the servlet itself
 *     implements SingleThreadModel).
 * </ul>

Engine - 表示整个catalina的servlet引擎,多数情况下包含一个或多个子容器,这些子容器要么是Host,要么是Context实现,或者是其他自定义组。

Host - 表示包含多个Context的虚拟主机的。

Context — 表示一个ServletContext,表示一个webapp,它通常包含一个或多个wrapper。

Wrapper - 表示一个servlet定义的(如果servlet本身实现了SingleThreadModel,则可能支持多个servlet实例)。

  • 结合整体的框架图中上述组件部分,我们看下包含了什么

很明显,除了四个组件的嵌套关系,Container中还包含了Realm,Cluster,Listeners, Pipleline等支持组件。

这一点,还可以通过相关注释可以看出:

**Loader** - Class loader to use for integrating new Java classes for this Container into the JVM in which Catalina is running.

**Logger** - Implementation of the log() method signatures of the ServletContext interface.

**Manager** - Manager for the pool of Sessions associated with this Container.

**Realm** - Read-only interface to a security domain, for authenticating user identities and their corresponding roles.

**Resources** - JNDI directory context enabling access to static resources, enabling custom linkages to existing server components when Catalina is embedded in a larger server.

Container的设计

这container应该包含哪些接口呢?如果你看源代码它包含二十多个接口,这里理解的时候一定要分组去理解。

Container的层次结构方法

查找父容器的方法:

/**
  * Get the parent container.
  *
  * @return Return the Container for which this Container is a child, if
  *         there is one. If there is no defined parent, return
  *         <code>null</code>.
  */
public Container getParent();


/**
  * Set the parent Container to which this Container is being added as a
  * child.  This Container may refuse to become attached to the specified
  * Container by throwing an exception.
  *
  * @param container Container to which this Container is being added
  *  as a child
  *
  * @exception IllegalArgumentException if this Container refuses to become
  *  attached to the specified Container
  */
public void setParent(Container container);

由于Engine显然上层是Service,所以里面加了一个getService的方法

/**
  * Return the Service to which this container belongs.
  * @param container The container to start from
  * @return the Service, or null if not found
  */
public static Service getService(Container container) {
    while (container != null && !(container instanceof Engine)) {
        container = container.getParent();
    }
    if (container == null) {
        return null;
    }
    return ((Engine) container).getService();
}

类比树接口,有Parent方法,那肯定也child方法:

/**
  * Add a new child Container to those associated with this Container,
  * if supported.  Prior to adding this Container to the set of children,
  * the child's <code>setParent()</code> method must be called, with this
  * Container as an argument.  This method may thrown an
  * <code>IllegalArgumentException</code> if this Container chooses not
  * to be attached to the specified Container, in which case it is not added
  *
  * @param child New child Container to be added
  *
  * @exception IllegalArgumentException if this exception is thrown by
  *  the <code>setParent()</code> method of the child Container
  * @exception IllegalArgumentException if the new child does not have
  *  a name unique from that of existing children of this Container
  * @exception IllegalStateException if this Container does not support
  *  child Containers
  */
public void addChild(Container child);

/**
  * Obtain the child Containers associated with this Container.
  *
  * @return An array containing all children of this container. If this
  *         Container has no children, a zero-length array is returned.
  */
public Container[] findChildren();

/**
  * Remove an existing child Container from association with this parent
  * Container.
  *
  * @param child Existing child Container to be removed
  */
public void removeChild(Container child);

Container事件监听相关方法

前文我们也分析过Tomcat的事件监听机制,Container也是一样, 比如如下的ContainerListener

/**
  * Add a container event listener to this component.
  *
  * @param listener The listener to add
  */
public void addContainerListener(ContainerListener listener);

/**
  * Obtain the container listeners associated with this Container.
  *
  * @return An array containing the container listeners associated with this
  *         Container. If this Container has no registered container
  *         listeners, a zero-length array is returned.
  */
public ContainerListener[] findContainerListeners();

/**
  * Remove a container event listener from this component.
  *
  * @param listener The listener to remove
  */
public void removeContainerListener(ContainerListener listener);

除了Container级别的,和前文我们理解的一样,还有属性相关的Listener, 显然就增删属性的监听方法

/**
  * Remove a property change listener from this component.
  *
  * @param listener The listener to remove
  */
public void removePropertyChangeListener(PropertyChangeListener listener);

/**
  * Add a property change listener to this component.
  *
  * @param listener The listener to add
  */
public void addPropertyChangeListener(PropertyChangeListener listener);

最后显然还有事件的触发方法

/**
  * Notify all container event listeners that a particular event has
  * occurred for this Container.  The default implementation performs
  * this notification synchronously using the calling thread.
  *
  * @param type Event type
  * @param data Event data
  */
public void fireContainerEvent(String type, Object data);

Container功能支撑方法

前面我们知道,Loader, Logger, Manager, Realm, Resources等支撑功能。这里简单看下接口定义,相关基本实现看下节ContainerBase的实现。

  • Loader
/**
  * Get the parent class loader.
  *
  * @return the parent class loader for this component. If not set, return
  *         {@link #getParent()}.{@link #getParentClassLoader()}. If no
  *         parent has been set, return the system class loader.
  */
public ClassLoader getParentClassLoader();


/**
  * Set the parent class loader for this component. For {@link Context}s
  * this call is meaningful only <strong>before</strong> a Loader has
  * been configured, and the specified value (if non-null) should be
  * passed as an argument to the class loader constructor.
  *
  * @param parent The new parent class loader
  */
public void setParentClassLoader(ClassLoader parent);
  • Logger
/**
  * Obtain the log to which events for this container should be logged.
  *
  * @return The Logger with which this Container is associated.  If there is
  *         no associated Logger, return the Logger associated with the
  *         parent Container (if any); otherwise return <code>null</code>.
  */
public Log getLogger();


/**
  * Return the logger name that the container will use.
  * @return the abbreviated name of this container for logging messages
  */
public String getLogName();
  • Manager

体现在我们之前分析的JMX管理

/**
  * Obtain the JMX name for this container.
  *
  * @return the JMX name associated with this container.
  */
public ObjectName getObjectName();


/**
  * Obtain the JMX domain under which this container will be / has been
  * registered.
  *
  * @return The JMX domain name
  */
public String getDomain();


/**
  * Calculate the key properties string to be added to an object's
  * {@link ObjectName} to indicate that it is associated with this container.
  *
  * @return          A string suitable for appending to the ObjectName
  *
  */
public String getMBeanKeyProperties();

/**
  * Obtain the number of threads available for starting and stopping any
  * children associated with this container. This allows start/stop calls to
  * children to be processed in parallel.
  *
  * @return The currently configured number of threads used to start/stop
  *         children associated with this container
  */
public int getStartStopThreads();
  • Realm
/**
  * Obtain the Realm with which this Container is associated.
  *
  * @return The associated Realm; if there is no associated Realm, the
  *         Realm associated with the parent Container (if any); otherwise
  *         return <code>null</code>.
  */
public Realm getRealm();


/**
  * Set the Realm with which this Container is associated.
  *
  * @param realm The newly associated Realm
  */
public void setRealm(Realm realm);
  • Cluster
/**
  * Get the Cluster for this container.
  *
  * @return The Cluster with which this Container is associated. If there is
  *         no associated Cluster, return the Cluster associated with our
  *         parent Container (if any); otherwise return <code>null</code>.
  */
public Cluster getCluster();


/**
  * Set the Cluster with which this Container is associated.
  *
  * @param cluster the Cluster with which this Container is associated.
  */
public void setCluster(Cluster cluster);
  • 其它
/**
  * Return a name string (suitable for use by humans) that describes this
  * Container.  Within the set of child containers belonging to a particular
  * parent, Container names must be unique.
  *
  * @return The human readable name of this container.
  */
public String getName();


/**
  * Set a name string (suitable for use by humans) that describes this
  * Container.  Within the set of child containers belonging to a particular
  * parent, Container names must be unique.
  *
  * @param name New name of this container
  *
  * @exception IllegalStateException if this Container has already been
  *  added to the children of a parent Container (after which the name
  *  may not be changed)
  */
public void setName(String name);

/**
  * Sets the number of threads available for starting and stopping any
  * children associated with this container. This allows start/stop calls to
  * children to be processed in parallel.
  * @param   startStopThreads    The new number of threads to be used
  */
public void setStartStopThreads(int startStopThreads);


/**
  * Obtain the location of CATALINA_BASE.
  *
  * @return  The location of CATALINA_BASE.
  */
public File getCatalinaBase();


/**
  * Obtain the location of CATALINA_HOME.
  *
  * @return The location of CATALINA_HOME.
  */
public File getCatalinaHome();

Container基本实现:ContainerBase

就讲讲几个比较核心的

Logger

日志记录器,比较简单,直接看代码

/**
  * Return the Logger for this Container.
  */
@Override
public Log getLogger() {
    if (logger != null)
        return logger;
    logger = LogFactory.getLog(getLogName());
    return logger;
}


/**
  * @return the abbreviated name of this container for logging messages
  */
@Override
public String getLogName() {

    if (logName != null) {
        return logName;
    }
    String loggerName = null;
    Container current = this;
    while (current != null) {
        String name = current.getName();
        if ((name == null) || (name.equals(""))) {
            name = "/";
        } else if (name.startsWith("##")) {
            name = "/" + name;
        }
        loggerName = "[" + name + "]"
            + ((loggerName != null) ? ("." + loggerName) : "");
        current = current.getParent();
    }
    logName = ContainerBase.class.getName() + "." + loggerName;
    return logName;

}

Cluster

  • getCluster:读锁,获取子类的cluster,如果没有则返回父类的cluster;
  • getClusterInternal: 读锁,获取子类的cluster
  • setCluster: 写锁,设置container的cluster;由于cluster具备生命周期,所以需要对停止旧的cluster,启动新的cluster;设置成功后,再触发cluster变更事件。
/**
  * The cluster with which this Container is associated.
  */
protected Cluster cluster = null;
private final ReadWriteLock clusterLock = new ReentrantReadWriteLock();

/**
  * The parent Container to which this Container is a child.
  */
protected Container parent = null;

/**
  * Return the Cluster with which this Container is associated.  If there is
  * no associated Cluster, return the Cluster associated with our parent
  * Container (if any); otherwise return <code>null</code>.
  */
@Override
public Cluster getCluster() {
    Lock readLock = clusterLock.readLock();
    readLock.lock();
    try {
        if (cluster != null)
            return cluster;

        if (parent != null)
            return parent.getCluster();

        return null;
    } finally {
        readLock.unlock();
    }
}


/*
  * Provide access to just the cluster component attached to this container.
  */
protected Cluster getClusterInternal() {
    Lock readLock = clusterLock.readLock();
    readLock.lock();
    try {
        return cluster;
    } finally {
        readLock.unlock();
    }
}


/**
  * Set the Cluster with which this Container is associated.
  *
  * @param cluster The newly associated Cluster
  */
@Override
public void setCluster(Cluster cluster) {

    Cluster oldCluster = null;
    Lock writeLock = clusterLock.writeLock();
    writeLock.lock();
    try {
        // Change components if necessary
        oldCluster = this.cluster;
        if (oldCluster == cluster)
            return;
        this.cluster = cluster;

        // Stop the old component if necessary
        if (getState().isAvailable() && (oldCluster != null) &&
            (oldCluster instanceof Lifecycle)) {
            try {
                ((Lifecycle) oldCluster).stop();
            } catch (LifecycleException e) {
                log.error(sm.getString("containerBase.cluster.stop"), e);
            }
        }

        // Start the new component if necessary
        if (cluster != null)
            cluster.setContainer(this);

        if (getState().isAvailable() && (cluster != null) &&
            (cluster instanceof Lifecycle)) {
            try {
                ((Lifecycle) cluster).start();
            } catch (LifecycleException e) {
                log.error(sm.getString("containerBase.cluster.start"), e);
            }
        }
    } finally {
        writeLock.unlock();
    }

    // Report this property change to interested listeners
    support.firePropertyChange("cluster", oldCluster, cluster);
}

Realm

Realm和上面的Cluster方法基本一致。

/**
 * Return the Realm with which this Container is associated.  If there is
 * no associated Realm, return the Realm associated with our parent
 * Container (if any); otherwise return <code>null</code>.
 */
@Override
public Realm getRealm() {

    Lock l = realmLock.readLock();
    l.lock();
    try {
        if (realm != null)
            return realm;
        if (parent != null)
            return parent.getRealm();
        return null;
    } finally {
        l.unlock();
    }
}


protected Realm getRealmInternal() {
    Lock l = realmLock.readLock();
    l.lock();
    try {
        return realm;
    } finally {
        l.unlock();
    }
}

/**
 * Set the Realm with which this Container is associated.
 *
 * @param realm The newly associated Realm
 */
@Override
public void setRealm(Realm realm) {

    Lock l = realmLock.writeLock();
    l.lock();
    try {
        // Change components if necessary
        Realm oldRealm = this.realm;
        if (oldRealm == realm)
            return;
        this.realm = realm;

        // Stop the old component if necessary
        if (getState().isAvailable() && (oldRealm != null) &&
            (oldRealm instanceof Lifecycle)) {
            try {
                ((Lifecycle) oldRealm).stop();
            } catch (LifecycleException e) {
                log.error(sm.getString("containerBase.realm.stop"), e);
            }
        }

        // Start the new component if necessary
        if (realm != null)
            realm.setContainer(this);
        if (getState().isAvailable() && (realm != null) &&
            (realm instanceof Lifecycle)) {
            try {
                ((Lifecycle) realm).start();
            } catch (LifecycleException e) {
                log.error(sm.getString("containerBase.realm.start"), e);
            }
        }

        // Report this property change to interested listeners
        support.firePropertyChange("realm", oldRealm, this.realm);
    } finally {
        l.unlock();
    }
}

name等属性

此类属性改变时触发属性变更事件,比如name是容器的名字,name变更会触发name变更事件。

/**
  * The human-readable name of this Container.
  */
protected String name = null;


/**
  * Return a name string (suitable for use by humans) that describes this
  * Container.  Within the set of child containers belonging to a particular
  * parent, Container names must be unique.
  */
@Override
public String getName() {
    return name;
}


/**
  * Set a name string (suitable for use by humans) that describes this
  * Container.  Within the set of child containers belonging to a particular
  * parent, Container names must be unique.
  *
  * @param name New name of this container
  *
  * @exception IllegalStateException if this Container has already been
  *  added to the children of a parent Container (after which the name
  *  may not be changed)
  */
@Override
public void setName(String name) {
    if (name == null) {
        throw new IllegalArgumentException(sm.getString("containerBase.nullName"));
    }
    String oldName = this.name;
    this.name = name;
    support.firePropertyChange("name", oldName, this.name);
}

child相关

添加子容器

/**
  * Add a new child Container to those associated with this Container,
  * if supported.  Prior to adding this Container to the set of children,
  * the child's <code>setParent()</code> method must be called, with this
  * Container as an argument.  This method may thrown an
  * <code>IllegalArgumentException</code> if this Container chooses not
  * to be attached to the specified Container, in which case it is not added
  *
  * @param child New child Container to be added
  *
  * @exception IllegalArgumentException if this exception is thrown by
  *  the <code>setParent()</code> method of the child Container
  * @exception IllegalArgumentException if the new child does not have
  *  a name unique from that of existing children of this Container
  * @exception IllegalStateException if this Container does not support
  *  child Containers
  */
@Override
public void addChild(Container child) {
    if (Globals.IS_SECURITY_ENABLED) {
        PrivilegedAction<Void> dp =
            new PrivilegedAddChild(child);
        AccessController.doPrivileged(dp);
    } else {
        addChildInternal(child);
    }
}

private void addChildInternal(Container child) {

    if (log.isDebugEnabled()) {
        log.debug("Add child " + child + " " + this);
    }

    synchronized(children) {
        if (children.get(child.getName()) != null)
            throw new IllegalArgumentException(
                    sm.getString("containerBase.child.notUnique", child.getName()));
        child.setParent(this);  // May throw IAE 设置父容器
        children.put(child.getName(), child); // 使用map,方便通过name查找子容器
    }

    fireContainerEvent(ADD_CHILD_EVENT, child); // 触发添加子容器的事件

    // Start child // 注意下这里,没有将start方法放到synchronized的原因
    // Don't do this inside sync block - start can be a slow process and
    // locking the children object can cause problems elsewhere
    try {
        if ((getState().isAvailable() ||
                LifecycleState.STARTING_PREP.equals(getState())) &&
                startChildren) {
            child.start();
        }
    } catch (LifecycleException e) {
        throw new IllegalStateException(sm.getString("containerBase.child.start"), e);
    }
}

查找子容器

/**
  * Return the child Container, associated with this Container, with
  * the specified name (if any); otherwise, return <code>null</code>
  *
  * @param name Name of the child Container to be retrieved
  */
@Override
public Container findChild(String name) {
    if (name == null) {
        return null;
    }
    synchronized (children) {
        return children.get(name);
    }
}
/**
  * Return the set of children Containers associated with this Container.
  * If this Container has no children, a zero-length array is returned.
  */
@Override
public Container[] findChildren() {
    synchronized (children) {
        Container results[] = new Container[children.size()];
        return children.values().toArray(results);
    }
}
  • 删除子容器

子容器有生命周期,所以应该是先停止,然后销毁(distroy), 再触发删除事件,最后将children中子容器删除。

/**
  * Remove an existing child Container from association with this parent
  * Container.
  *
  * @param child Existing child Container to be removed
  */
@Override
public void removeChild(Container child) {

    if (child == null) {
        return;
    }

    try {
        if (child.getState().isAvailable()) {
            child.stop();
        }
    } catch (LifecycleException e) {
        log.error(sm.getString("containerBase.child.stop"), e);
    }

    boolean destroy = false;
    try {
        // child.destroy() may have already been called which would have
        // triggered this call. If that is the case, no need to destroy the
        // child again.
        if (!LifecycleState.DESTROYING.equals(child.getState())) {
            child.destroy();
            destroy = true;
        }
    } catch (LifecycleException e) {
        log.error(sm.getString("containerBase.child.destroy"), e);
    }

    if (!destroy) {
        fireContainerEvent(REMOVE_CHILD_EVENT, child);
    }

    synchronized(children) {
        if (children.get(child.getName()) == null)
            return;
        children.remove(child.getName());
    }

}

Lifecycle的模板方法

  • initInternal

startStopThreads 默认为 1 ,所以 reconfigureStartStopExecutor 方法会走 if 语句,而 startStopExecutor 最开始是没有赋值的,startStopExecutor instanceof InlineExecutorService 会返回 false,因此最终会执行 startStopExecutor = new InlineExecutorService(),InlineExecutorService 只是简单地实现了 java.util.concurrent.AbstractExecutorService 类。 最终 reconfigureStartStopExecutor 给 startStopExecutor 这个成员变量设置了,startStopExecutor。

/**
  * The number of threads available to process start and stop events for any
  * children associated with this container.
  */
private int startStopThreads = 1;
protected ExecutorService startStopExecutor;


@Override
protected void initInternal() throws LifecycleException {
    reconfigureStartStopExecutor(getStartStopThreads()); // 设置一个线程池来处理子容器启动和关闭事件
    super.initInternal(); // 调用LifecycleMBeanBase的方法
}


private void reconfigureStartStopExecutor(int threads) {
    if (threads == 1) {
        // Use a fake executor
        if (!(startStopExecutor instanceof InlineExecutorService)) {
            startStopExecutor = new InlineExecutorService(); // 执行这里
        }
    } else {
        // Delegate utility execution to the Service
        Server server = Container.getService(this).getServer();
        server.setUtilityThreads(threads);
        startStopExecutor = server.getUtilityExecutor();
    }
}
  • startInternal

试想,container中有很多组件,而且属于Lifecycle生命周期管理;那么启动容器的时候,必然是逐个将这些子组件(包括子容器)启动起来。

/**
  * Start this component and implement the requirements
  * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
  *
  * @exception LifecycleException if this component detects a fatal error
  *  that prevents this component from being used
  */
@Override
protected synchronized void startInternal() throws LifecycleException {

    // Start our subordinate components, if any
    logger = null;
    getLogger();
    Cluster cluster = getClusterInternal();
    if (cluster instanceof Lifecycle) {
        ((Lifecycle) cluster).start();
    }
    Realm realm = getRealmInternal();
    if (realm instanceof Lifecycle) {
        ((Lifecycle) realm).start();
    }

    // Start our child containers, if any
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (Container child : children) {
        results.add(startStopExecutor.submit(new StartChild(child)));
    }

    MultiThrowable multiThrowable = null; // 引入一个MultiThrowable,来收集多个异常

    for (Future<Void> result : results) {
        try {
            result.get();
        } catch (Throwable e) {
            log.error(sm.getString("containerBase.threadedStartFailed"), e);
            if (multiThrowable == null) {
                multiThrowable = new MultiThrowable();
            }
            multiThrowable.add(e);
        }

    }
    if (multiThrowable != null) {
        throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
                multiThrowable.getThrowable());
    }

    // Start the Valves in our pipeline (including the basic), if any
    if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).start();
    }

    setState(LifecycleState.STARTING);

    // 看这个,本质是调用最上层server的utilityExecutorWrapper 线程池去执行 ContainerBackgroundProcessorMonitor 任务
    if (backgroundProcessorDelay > 0) {
        monitorFuture = Container.getService(ContainerBase.this).getServer()
                .getUtilityExecutor().scheduleWithFixedDelay(
                        new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);
    }
}
  • stopInternal

和initInternal初始化子组件方式倒过来,逐一停止子组件,并触发相关事件。

/**
  * Stop this component and implement the requirements
  * of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
  *
  * @exception LifecycleException if this component detects a fatal error
  *  that prevents this component from being used
  */
@Override
protected synchronized void stopInternal() throws LifecycleException {

    // Stop our thread
    if (monitorFuture != null) {
        monitorFuture.cancel(true);
        monitorFuture = null;
    }
    threadStop();

    setState(LifecycleState.STOPPING);

    // Stop the Valves in our pipeline (including the basic), if any
    if (pipeline instanceof Lifecycle &&
            ((Lifecycle) pipeline).getState().isAvailable()) {
        ((Lifecycle) pipeline).stop();
    }

    // Stop our child containers, if any
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (Container child : children) {
        results.add(startStopExecutor.submit(new StopChild(child)));
    }

    boolean fail = false;
    for (Future<Void> result : results) {
        try {
            result.get();
        } catch (Exception e) {
            log.error(sm.getString("containerBase.threadedStopFailed"), e);
            fail = true;
        }
    }
    if (fail) {
        throw new LifecycleException(
                sm.getString("containerBase.threadedStopFailed"));
    }

    // Stop our subordinate components, if any
    Realm realm = getRealmInternal();
    if (realm instanceof Lifecycle) {
        ((Lifecycle) realm).stop();
    }
    Cluster cluster = getClusterInternal();
    if (cluster instanceof Lifecycle) {
        ((Lifecycle) cluster).stop();
    }
}
  • destroyInternal

对比下initInternal,它初始化了什么就destory什么

@Override
protected void destroyInternal() throws LifecycleException {

    Realm realm = getRealmInternal();
    if (realm instanceof Lifecycle) {
        ((Lifecycle) realm).destroy();
    }
    Cluster cluster = getClusterInternal();
    if (cluster instanceof Lifecycle) {
        ((Lifecycle) cluster).destroy();
    }

    // Stop the Valves in our pipeline (including the basic), if any
    if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).destroy();
    }

    // Remove children now this container is being destroyed
    for (Container child : findChildren()) {
        removeChild(child);
    }

    // Required if the child is destroyed directly.
    if (parent != null) {
        parent.removeChild(this);
    }

    // If init fails, this may be null
    if (startStopExecutor != null) {
        startStopExecutor.shutdownNow();
    }

    super.destroyInternal(); // 调用LifecycleMBeanBase的方法
}

今天刚好是全职独立开发的第三个月结束

这个月尝试增加免费模式 效果并不怎么好 付费率下降不少 考虑去掉

然后尝试做了 PPP 定价 中低收入国家转化率稍微提高一些 但续费率不太乐观

最后尝试了付费新闻稿 这个主要是看能不能提升品牌力 因为大部分都是 nofollow 所以对 seo 并不会有太大帮助

希望上面这三条能对大家有一点点帮助

关于收入 月初 stripe 被封后换了支付方式 MRR 从 4500 刀掉到现在的 1500 刀 加上一次性付费这个月总收入定格在 3000 多刀

休息三个月下来欲望变低了不少 虽然收入还是比上班低了太多 但胜在自由

最近在做亚欧非大环线的攻略

PocketOS 创始人披露:运行在 Cursor 中的 Claude Opus 4.6 代理在处理 staging 任务时,自行找到 Railway API token,通过一次 GraphQL 调用删除了生产数据库及所有卷级备份,仅用 9 秒,数月客户数据永久丢失。AI 代理甚至写了一份"认罪书"。这成为 AI Agent 自主行动风险的最极端案例。
来源 The Register

专业公司也会这样吗?虽说是初创公司

话说前两天我让龙虾弄的那个浏览器插件的小玩意,也是在修改其中一个版本时,直接覆盖了,新版本功能都失效了。feel_wronged

https://jt26wzz.com/posts/0016-my-first-linux-kernel-patch-fixing-a-tcp-listener-bug/

上面是博客地址链接,文章全程手搓,主要写了三部分:一个是怎么发现 Bug 的,然后是给内核社区提补丁的全过程(期间得到多个大佬的帮助),最后是我对开源社区运作新的理解(特别是和血汗大厂工作的区别对比)。

上次分享博客,很多人吐槽我博客的阅读体验有点差,这次我特别用 AI 调整了一下,优化了字体和行间隔,加了侧边栏,开头加上了 TL:DR 摘要,还调整了高亮特效,这次应该好很多了。如果有什么新的反馈,随时写在下面,反正一个小小的静态博客,LLM 都能搞定😉。

现在的 AI 工具很好用的样子,我还是用对话问 AI。
我好想没有什么需求要让 AI 自动流程做的?

▍阅读指南

  • 如果你只想知道为什么K线对不上:直接看第二章的时间对齐和第三章的SIP过滤,两重机制拆解是全文核心。
  • 如果你在跑分钟级策略回测:第四章的偏差矩阵和第五章的策略影响评估值得细读。
  • 如果你需要工程方案:第六章有机构级Tick-to-Bar架构和第八章避坑速查表。

一、同一只AAPL,同一分钟,两个数据源的K线不一样

用 Bloomberg 的实时数据流做完分钟级策略回测,夏普比率看着不错。换用 ICE 的分钟K线验证同一段时间,同一只 AAPL,同一根 09:30 的K线,OHLC 四个价格点至少有一个对不上。

不是其中一个错了。是 Bloomberg 和 ICE 生成K线的底层规则不一样。挂钟对齐还是交易时间对齐,SIP 过滤时保留哪些 tick——这两重规则在顶级服务商之间也没有统一标准。

本文将分钟K线的生成过程拆解到两重底层机制,并引入国际顶级服务商的真实规则差异作为佐证。

▍核心结论

  • 分钟K线不是“原始数据”,是数据源用自己的两重规则加工过的产物。规则不一样,结果就不一样。
  • 你花的几万美元买到的不是“标准答案”,是“其中一种加工方式”。

二、第一重机制:时间边界的两种对齐哲学

分钟K线的第一道工序,是将连续流动的 tick 数据切分成一个个“一分钟”的片段。切分规则有两种。

对齐方式规则描述09:30这根K线的tick归属
挂钟对齐以 UTC 或本地时间的整分钟为边界。09:30:00.000 开始,09:30:59.999 结束包含所有时间戳落在这一毫秒区间内的 tick
交易时间对齐以交易所撮合引擎的原始时间戳为边界,开盘竞价 tick 可能被单独处理开盘竞价 tick 归入本根K线,但后续 tick 按交易所内部时钟切分

两个规则差异很小——但在那 1% 的“边界 tick”上,直接决定了K线的开盘价和收盘价。

具体案例:AAPL 在某个交易日的 09:30:00.000 发生开盘竞价撮合,价格 150.18。09:30:00.500 连续交易第一笔 tick 到达,价格 150.25。

挂钟对齐:开盘价 = 150.18。交易时间对齐:开盘价 = 150.25。偏差 0.05%,但在开盘价这个关键价格点上。

▍顶级服务商之间的真实差异

  • Bloomberg (B-PIPE):采用挂钟对齐,以数据到达服务器的时间为依据,适合低延迟实时监控但易受网络抖动影响。
  • ICE Data Services:采用交易时间对齐,使用交易所撮合引擎的原始时间戳,被业界视为历史回测的“黄金标准”。
  • LSEG (Refinitiv):同样采用交易时间对齐,基于 PCAP 抓包数据使用 GPS 纳秒级同步时间戳,有效防止极端波动下的数据丢包。

同一笔 tick,在这三家生成的分钟K线里,可能归属三根不同的K线。

三、第二重机制:SIP 销售条件——被官方K线“过滤掉”的 tick

即使时间边界对齐了,问题才走完一半。第二道工序是:哪些 tick 有资格被计入K线?

美股每一笔成交都附带一个“销售条件”代码。官方K线在生成时,会根据 SEC 合规要求过滤掉特定类型的 tick——它们不被计入 OHLC。

被过滤的tick类型销售条件示例对K线的影响
暗池成交Exchange ID = D(如 FINY ATS)计入K线会引入偏离公开报价的噪声
零股成交Odd-lot(<100股)计入K线可能拉偏 high/low 极值
延报成交Form T(Late Trade)破坏时序一致性
平均价格交易代码 2/B、代码 C不更新极值但更新成交量

你自己累加 tick 时,这些全部被算进去了。 官方K线已经过滤掉了。

tick时间价格数量类型
#109:30:00.500150.25200股正常
#209:30:12.300150.3050股零股(Odd-lot)
#309:30:45.100150.153000股暗池(Form T)

自建K线:open=150.25, high=150.30, low=150.15, close=150.15

官方K线(过滤掉 #2 和 #3):open=150.25, high=150.25, low=150.25, close=150.25

一根K线,四个价格点,三个不同。

▍零股过滤的真实影响被严重低估

学术研究对 TAQ 数据的分析表明,将零股排除在 SIP 之外,导致每只股票的中位数缺失约 19% 的交易记录,极端情况下高达 66%。这些被剔除的零股并非无足轻重——它们实际贡献了美国市场约 30% 的价格发现。

如果你的自建K线包含了零股而官方K线没有,你的K线会系统性地记录更多极值。回测中的突破信号会比实盘更频繁——偏差不是随机的,是有方向性的。

两重机制拆解到这一层,一个问题自然浮出水面:如果你用的是第三方K线源而非自建引擎,那么它的时间切分方式是什么?SIP 过滤时保留了哪些销售条件?这两个参数直接决定了你拿到的K线和别人拿到的K线是不是同一个东西。文档里写明的,你能排查;没写的,偏差只能靠回测撞大运。

当K线极值失真时,如何用订单簿深度交叉验证

一个更深层的问题是:当你的策略因为一根带有极端 High/Low 的K线触发突破买入时,你可能正落入“暗池延报”的陷阱。

工程实践是将K线与订单簿交叉验证。当你看到K线价格瞬间击穿阻力,但同时获取的订单簿深度数据显示卖盘的流动性并没有被规模性消耗时,算法就该立刻警觉:这是一个由零股或场外大单引发的“幽灵极值”,需要熔断交易。K线告诉你“发生了什么”,订单簿告诉你“这是真的还是假的”。

四、两重机制叠加:偏差在K线四个价格点上的具体表现

K线价格点受时间对齐影响受SIP过滤影响偏差来源
开盘价(open)高——开盘竞价归属规则不同中——竞价tick通常不过滤时间对齐
最高价(high)中——边界tick归属不同高——暗池/零股制造虚假极值两者叠加
最低价(low)中——同上高——同上两者叠加
收盘价(close)高——收盘竞价归属规则不同中——收盘tick通常不过滤时间对齐

高频策略需特别关注 high 和 low——两个机制同时施加影响,偏差量级最大。

▍学术界的量化证据

  • 如果K线构建使用中间价而非实际成交价,日内收益标准差可能被误估 13% 至 18%
  • 依赖分钟K线极值的均值回归和突破策略,挂钟对齐实盘环境下的K线漂移会导致夏普比率数个百分点的显著衰减
  • ML 预测实验中,向模型输入K线极值发生的精确时间戳而非仅输入 OHLC,VWAP 方向准确率会提升——缺失精确切片规则导致模型方向判断下降。

五、一个需要诚实回答的问题:偏差对所有策略都致命吗?

不是。

业界研究给出了明确边界:对于持仓周期在日频或周频的低频策略,挂钟对齐和交易时间对齐的差异在统计学上微乎其微。但如果你依赖分钟K线极值捕捉信号——均值回归、日内突破、开盘区间突破——偏差会被放大到足以改变策略的夏普比率。

核心不是“有没有偏差”,而是“你的策略频率是否落在这个偏差的敏感区间内”。

六、机构级方案:顶尖量化基金是如何解决这个问题的?

不是选择“哪个数据源更好”,而是自建 Tick-to-Bar 聚合引擎

工程方案具体做法解决的问题
自建 Tick-to-Bar 引擎跨多个来源(SIP 与交易所专线)采集原始 tick,实行统一销售条件过滤后聚合K线确保回测和实盘聚合规则完全同源
双时间戳架构同时保留“交易所原生时间戳”与“本地到达时间戳”精确模拟信息延迟,修正对齐差异
预热机制实盘中快速回放历史数据同步技术指标状态确保K线生成逻辑在历史与实盘阶段绝对一致
Lock-Free Ring Buffer预分配无锁环形缓冲区,网络 I/O 线程写入,聚合线程消费消除动态内存分配带来的 GC 停顿,将 tick 洪流下的延迟稳定在微秒级

性能层的最后一块拼图:Lock-Free Ring Buffer

当 SIP 和交易所专线的 tick 数据洪流涌入时——3.7 万个品种、每秒数千笔成交——如果每收到一条 tick 就动态分配内存,GC 停顿会在 30 分钟内让你的聚合引擎延迟从微秒级退化到毫秒级。

工程上的解法是预分配一个无锁环形缓冲区。网络 I/O 线程只负责将解析后的 tick 结构体写入 Buffer 的下一个槽位,聚合线程从另一端消费并更新 OHLC。两个线程不共享锁,只在槽位索引上做 CAS 原子操作——零拷贝,零等待。这是高频 Tick-to-Bar 引擎的性能基石。

这是顶尖对冲基金的工程体系。对于中小量化团队,自建这套引擎的成本可能远超数据源本身。

在实际工程路径上,如果你的规模暂时不需要自建引擎,至少确保用的K线源其聚合规则是文档化的——时间切分方式是挂钟对齐还是交易时间对齐、SIP 过滤标准里零股和暗池成交的保留策略是什么。有了这两项参数,偏差就不再是盲区。本文测试中所引用的分钟K线方案明确采用挂钟对齐、SIP 合规过滤,且通过 GET /v1/market/kline(历史回测)和 GET /v1/market/kline/latest(实盘推流)双接口物理隔离,底层统一 UTC 毫秒时间戳,确保回测逻辑与实盘状态机遵循同一套聚合规则。

七、监管视角:SIP 过滤规则正在被重写

SEC 于 2020 年通过《市场数据基础设施现代化》规则(34-90610),废除单一 SIP 垄断,引入竞争性整合商模式。新规强制将零股报价和 5 档订单簿深度纳入核心数据范围,零股报价报告于 2026 年全面生效。

这意味着:过去被官方K线剔除的零股成交,未来将被重新纳入。数据聚合的标准正在经历历史性转变,而不同数据源适应新规则的速度并不一致。 在这个过渡期,K线生成规则的差异不仅不会消失,反而可能因为新旧标准并存而进一步加大。

八、避坑速查

如果你的策略...你需要确认...否则可能...
依赖分钟级突破信号自建K线和官方K线的 high/low 偏差虚假极值导致提前触发信号
在开盘/收盘半小时内有信号数据源的 tick 归属规则(挂钟对齐还是交易时间对齐)系统性方向偏差累积
自己做 tick→K线聚合SIP 过滤是否排除暗池/零股/延报零股贡献30%价格发现却全部被你计入
跨数据源做K线验证两个源的聚合规则和过滤标准是否一致交叉验证失效
使用 TAQ 历史数据是否存在零股偏差(中位缺失19%,极端66%)历史回测数据完整性被高估
K线出现极端极值订单簿深度是否同步变化被暗池延报引发的“幽灵报价”误导
WebSocket 长连接订阅大量标的限频错误码 3001 的处理逻辑——是否读取 Retry-After 头做指数退避无脑重试被服务端拉黑,长连接永久断开
多实例部署消费同一数据源连接数超限错误码 3003 的降级策略——是否主动清理僵尸连接新实例无法建立连接,静默故障

九、结语

分钟K线不是“原始数据”。它是数据源用自己的两重规则——时间边界对齐和 SIP 销售条件过滤——加工过的产物。规则不一样,结果就不一样。数据源之间的差异,很大程度上不是“质量差异”,而是“规则差异”。理解这两重规则,才能真正理解回测和实盘之间那层“看不见的偏差”。

扩展方向

  • Tick-to-Bar 自建引擎:跨多源采集原始 tick,统一销售条件过滤后聚合K线,配合 Ring Buffer 实现微秒级延迟。
  • 双时间戳存储架构:同时保留交易所原生时间戳与本地到达时间戳,精确量化网络延迟对聚合结果的影响。
  • 订单簿交叉验证:将K线极值与订单簿深度快照做实时比对,熔断由零股或暗池延报触发的虚假突破信号。

AI 辅助开发:如果你在编码时使用 AI 助手,可以通过 Clawhub 平台的「TickDB-market-data」Skill 让 AI 直接理解行情接口协议。直接输入以下提示词即可生成生产级骨架代码:

“帮我用 WebSocket 接入 TickDB,实现一个带心跳检测(1秒 Ping/Pong)、处理 3001(Retry-After 指数退避)和 3003(主动清理僵尸连接)错误降级逻辑的 Tick-to-Bar 聚合类。K 线聚合规则采用挂钟对齐,过滤掉暗池和零股成交。”

本文不构成任何投资建议。

参考文献

  1. Technical Standards and Microstructural Impacts of Intraday Bar Aggregation in Global Financial Markets (Industry Report).
  2. SEC. (2020). SEC Adopts Rules to Modernize Key Market Infrastructure (Release No. 34-90610).
  3. Federal Reserve Bank of New York. (2012). Do Dark Pools Harm Price Discovery?
  4. QuantConnect LEAN Engine Documentation. Data Normalization and Warm-Up Period.
  5. ICE Data Services. Real-Time Market Data Technical Specifications.
  6. Bloomberg. B-PIPE Real-Time Data Feed Technical Documentation.
  7. LSEG (Refinitiv). Real-Time Data Delivery Technical Specifications.

有没有用这个的朋友?仅磁吸不带充电的
我 17pm 发现用磁吸手机支架+有点充电时有时拿手机时手机边框有一点麻麻/震动的感觉
已知 16pm 用同一个磁吸支架时没问题,换 17pm 一天后就出现了

详情 (不确定这个网站靠不靠谱):
https://copy.fail/

这是 HN 上的讨论:
https://news.ycombinator.com/item?id=47952181

各家的报告:
https://access.redhat.com/security/cve/cve-2026-31431
https://security-tracker.debian.org/tracker/CVE-2026-31431
https://ubuntu.com/security/CVE-2026-31431
https://www.suse.com/security/cve/CVE-2026-31431.html

实现方法:
https://github.com/theori-io/copy-fail-CVE-2026-31431

按目前我了解到的情况,先排查影响,然后禁用 algif_aead ,最后要重启一下生效
楼主不是信息安全专家,具体还得等大佬补充

TL;DR:Atlassian 宣布默认使用用户数据训练 AI,这不是意外,而是行业惯例。本文梳理海外和中国主流平台的数据训练政策,拆解背后的三个共同规律。

一、Atlassian 的公告

2026 年 8 月 17 日,Atlassian 的一项隐私政策更新悄悄生效。

对于数百万使用 Jira 和 Confluence 的团队而言,这意味着:你在 Confluence 里写的文档、Jira 里记录的需求和 Bug 描述,默认情况下可以被用于训练 Atlassian 的 AI 模型。

公告关键条款分两层。第一层是元数据,免费和标准版用户无法退出;第二层是应用内容(Confluence 正文、Jira 描述和评论),默认开启,可以关闭,但完全退出需要升级到企业版。

Atlassian 的措辞相当克制——“去标识化处理”“用于改进 AI 功能”“保障隐私安全”。这套话术你可能在其他地方见过,因为几乎每一家做同样事情的公司都在用同样的句式。

官方 FAQ:https://www.atlassian.com/trust/ai/data-contribution/faqs

二、这不是孤例,而是行业惯例

Atlassian 公告引发了一些讨论,但坦白说,它只是在做一件大多数同行早就在做的事。

以下是海外主流平台的现状:

平台默认行为退出方式
ChatGPT(免费/Plus)默认开启App 内设置关闭
GitHub Copilot(免费)默认开启设置关闭
LinkedIn默认开启可退出,但已收集数据不撤回
Slack默认开启邮件申请退出
Atlassian默认开启企业版才能完全退出
X/Twitter默认开启隐私设置关闭
Zoom曾默认开启2023 年因舆论压力撤回

几个值得单独说的细节:

ChatGPT 的策略在文档上算透明——官方帮助页明确写着“ChatGPT 通过对话训练,除非你退出”。但有一个陷阱:即使你开启了退出,只要你对某条回复点了赞或踩,该完整对话仍可能被用于训练。

GitHub Copilot 自 2026 年 4 月起,将免费和 Pro 用户的交互数据默认用于训练。企业版默认关闭——和 Atlassian 如出一辙,付费保护数据,免费贡献数据。

Zoom 是目前唯一完全撤回的案例。2023 年 8 月政策更新后,用户发现条款允许用视频内容训练 AI,舆论强烈反弹,Zoom 数日内道歉并完全撤回。这个案例在 2023 年是标志性事件,但此后再也没有重演过。

三、中国平台的镜像现象

同样的逻辑,在中国市场同步上演。

平台默认行为退出方式
豆包(字节跳动)默认开启App 内设置关闭
DeepSeek默认开启App 内设置关闭
通义千问(阿里)默认开启联系客服申请
元宝(腾讯)默认关闭无需退出
WPS曾默认开启2023 年因舆论压力撤回

豆包的隐私政策(2026 年 3 月生效)在“模型训练”章节直接写明:“内容数据(输入 + 回复)经去标识化后可用于模型训练。”退出路径是:设置 → 隐私与权限 → 帮助模型改进效果。表述算得上清晰。

DeepSeek 有一个容易被忽视的细节:用户协议允许将输出内容用于“训练其他模型,包括模型蒸馏”。这意味着你和 DeepSeek 的对话,不只可能被用来训练 DeepSeek 自身,还可能用于蒸馏出其他模型。

通义千问是所有已查平台中退出体验最差的:没有 App 内开关,必须手动联系客服申请。隐私政策的措辞是“如不希望用于模型优化,请参照本政策第九条联系我们”——把退出的门槛设计得足够高,大多数用户不会真的去走这条路。

元宝是个异类。2025 年 3 月,腾讯原始协议中有“永久、不可撤销、可转让”的内容授权条款,引发舆论反弹后 5 天内连改三次,最终改为默认关闭、用户主动开启才生效。在中国主流大模型产品里,元宝目前是唯一默认不收集的。

WPS 与 Zoom 的剧本几乎一致:2023 年 11 月政策加入用户文档用于 AI 训练条款,两天内被迫道歉撤回。时间节点相近,结局相同——都发生在公众对 AI 隐私最敏感的 2023 年。

四、模式拆解:三个共同规律

梳理完这些案例,可以归纳出三个几乎跨平台通用的规律。

规律一:默认开启,退出路径设计得刁钻

没有一家平台会在注册时弹窗询问“是否同意将你的数据用于训练”。统一的做法是:默认开启,在隐私政策深处埋一个退出入口,路径越长越好。通义千问把退出门槛设计成“联系客服”,是这个规律的极端案例。Atlassian 把完全退出的权利锁在企业版付费墙后面,是另一种变体。

规律二:免费用户最脆弱,付费企业版才有保护

这几乎是铁律。ChatGPT 免费/Plus 版默认开启,Enterprise 版默认关闭。GitHub Copilot 免费版默认开启,企业版默认关闭。Atlassian 标准版无法退出元数据收集,企业版可以完全退出。逻辑很清晰:免费用户用数据付费,付费用户用钱付费。

规律三:“去标识化”成了万能挡箭牌

翻遍所有平台的隐私政策,几乎每一家都用了同样的措辞:“在严格去标识化、无法重新识别特定个人的前提下……”这句话的问题在于:去标识化的标准由平台自己定义,执行情况无从外部核实,而去标识化后的数据在法律上往往不再被视为个人信息,监管约束大幅减弱。这句话既是承诺,也是免责。

五、用户态度的转变

2023 年是一个分水岭。

那一年,Zoom 因 AI 训练条款在数日内被迫撤回,WPS 在两天内道歉。公众对 AI 使用私人数据的反应是即时且强烈的。那个时期,“你的数据被用于训练 AI”还是一个足以引发大规模讨论的新闻。

2025 到 2026 年,同样的操作几乎不再引发反弹。

LinkedIn 默认开启,用户沉默。GitHub Copilot 更新政策,开发者社区短暂讨论后归于平静。Atlassian 公告生效,大多数团队甚至不知道这件事发生过。

这种转变背后有几个相互叠加的原因。

第一是疲劳效应。当每隔几个月就有一家平台更新数据政策,用户的警觉阈值在持续上升。反复面对同样的新闻,愤怒会钝化。

第二是感知价值的变化。2023 年 AI 功能对普通用户还是新鲜事物,隐私代价显得不对等。到了 2026 年,AI 补全、AI 总结、AI 搜索已经深度嵌入日常工作流,用户在隐性地做一笔交换:我的数据换你的 AI 能力。

第三是迁移成本极高。Jira 和 Confluence 深度绑定了团队的工作流,换掉的成本远超隐私政策带来的不适。这让平台在调整政策时拥有相当大的空间。

第四是监管滞后。无论是 GDPR 还是中国的《生成式人工智能服务管理暂行办法》,对“去标识化数据用于模型训练”的具体边界都尚未形成清晰的执法标准。监管的空白给了平台足够的时间窗口。

用户不是变得不在意隐私了,而是在没有更好选择的情况下,选择了接受。

六、开发者和企业该怎么办

知道这件事之后,能做什么?

个人用户:主动退出

前文提到的各平台退出路径,值得花五分钟逐一检查一遍。默认开启的设计就是赌你不会去找那个开关。

企业用户:评估合规风险

如果你的团队在 Jira 或 Confluence 里记录了客户数据、商业机密、未发布的产品规划,需要认真评估 Atlassian 新政策的合规影响。标准版无法完全退出元数据收集,这在某些行业(金融、医疗、政府)可能触碰监管红线。

对于 ChatGPT、Copilot 等开发工具,建议在团队层面统一配置企业版并确认默认策略,而不是依赖每个员工自己去找设置。

更根本的问题

退出开关能做的事是有限的。平台对“去标识化”的定义不透明,执行情况无法核实,历史数据通常无法删除。

真正有效的保护是不输入不该输入的内容——不在消费级 AI 产品里粘贴客户合同、不在免费版 Copilot 里处理涉密代码、不在公有云 SaaS 里存放监管敏感数据。这不是对 AI 工具的否定,而是在享受便利的同时,对数据边界保持清醒。

重置了以后明显不如上个周期额度耐用了,之前用了好几天才用了 20%,现在 2 天就 40%了,用 codeburn 看上个周期每天都是消耗 400-700 美金左右,这两天重置以后每天都是 300 左右