GGUF 原理图:从文件到AI思考的全过程
我们已经成功地使用 gguf-inspector 工具窥探了 Qwen3-0.6B-Q8_0.gguf 文件的内部。我们看到了文件头、看到了 general.architecture: “qwen3” 这样的元数据,还看到了一长串像 blk.0.attn_q.weight 这样的张量列表。 但这些静态的数据是如何“活”过来,变成一个能与你对话的AI的呢? 让我们用一个形象的比喻开始:GGUF 文件就像一个高度优化的宜家家具(AI模型)的完整包装盒。 元数据 (Metadata):就是那本详细的安装说明书。它告诉你这个家具叫什么名字 (general.name),是什么风格 (general.architecture),有多少个抽屉 (qwen3.block_count) 等等。 张量信息 (Tensor Info):是包装盒里的零件清单。它精确地列出了:“A号螺丝 (token_embd.weight) 有多大”、“B号面板 (blk.0.attn_norm.weight) 在哪里”等等。 张量数据 (Tensor Data):就是那些用塑料袋分装好的、实实在在的螺丝、面板和木榫。 而模型推理(Inference),就是你(或者说,llama.cpp 这样的推理程序)打开这个包装盒,严格按照说明书(元数据),使用零件清单(张量信息)来找到对应的零件(张量数据),一步步把家具组装起来并让它工作的过程。 第一部分:静态视角 - GGUF 文件布局 首先,我们回顾一下这个“包装盒”的内部结构。它在硬盘上是这样线性排列的: +——————————–+ | 文件头 (Header) | <– 身份标识和目录 | (Magic, Version, Counts) | +——————————–+ | 元数据 (Metadata) | <– “安装说明书” | (Key-Value Pairs) | (例如: general.architecture, tokenizer.ggml.tokens) +——————————–+ | 张量信息 (Tensor Info) | <– “零件清单” | (Tensor Name, Shape, Type, Offset) | (例如: blk.
从GGUF到TENSORRT:一文读懂AI模型文件的江湖
在人工智能的世界里,我们每天都在与“模型”打交道。但模型并非虚无缥缈,它最终以文件的形式存在于我们的硬盘上。当你从Hugging Face下载一个模型,或者准备将训练好的成果部署到生产环境时,你会遇到.gguf, .onnx, .pb, .pt, .safetensors 等五花八门的扩展名。 这些文件格式究竟有何不同?为什么有些适合在你的MacBook上本地运行,有些则是云端大规模部署的利器?选择正确的模型格式,对于模型的性能、可移植性和开发效率至关重要。 今天,就让我们深入探索AI模型的“文件江湖”,揭开这些主流格式的神秘面纱。 各路豪杰:主流模型格式巡礼 每种模型格式都有其独特的使命和适用场景,就像江湖中的门派,各有各的独门绝技。
- GGUF (本地化大模型的亲民派) GGUF (Georgi Gerganov Universal Format) 是近年来随着llama.cpp项目声名鹊起的格式。它专为在消费级硬件(尤其是CPU)上高效运行大语言模型(LLM)而生。 核心特点: 一体化单文件: 将模型权重、架构信息、甚至分词器(Tokenizer)都打包在一个文件中,分发和使用极其方便。 为量化而生: 原生支持多种先进的量化技术(如4位、8位整数量化),能将巨大的模型压缩到消费级设备内存可容纳的大小。 快速加载: 采用内存映射(mmap)技术,无需读取整个文件即可开始加载模型,启动速度极快。 独门绝技: 在不依赖昂贵GPU的情况下,让普通电脑也能流畅运行大语言模型,是LLM本地化部署当之无愧的王者。 适用场景: 个人开发者、研究者在本地PC、Mac上进行模型推理;资源有限的边缘设备部署。
- ONNX (模型世界的“世界语”) ONNX (Open Neural Network Exchange) 是一个开放的、跨平台的标准,旨在成为不同深度学习框架之间的“通用翻译器”。它由微软、Meta等巨头联合推出,目标是解决框架碎片化带来的模型迁移难题。 核心特点: 互操作性 (Interoperability): 你可以在PyTorch中训练模型,导出为ONNX,然后轻松地在TensorFlow、TensorRT、或Windows ML等环境中运行。 硬件加速: ONNX Runtime等推理引擎可以针对特定硬件(Intel CPU, NVIDIA GPU, ARM NPU等)进行深度优化,充分释放硬件潜能。 生态系统丰富: 拥有庞大的工具链,支持模型可视化、转换和验证。 独门绝技: 打破框架壁垒,一次训练,处处部署。 适用场景: 需要将模型部署到多样化硬件环境的企业;多团队使用不同技术栈协作的项目。
- TensorFlow SavedModel (生产环境的“重装甲”) SavedModel 是 TensorFlow 生态系统的官方标准格式。它不仅仅是权重,而是一个包含模型完整计算图、权重、资产(如词汇表)和部署签名的自包含目录。 核心特点: 完整与健壮: 保存了运行模型所需的一切,与语言无关。一个在Python中训练的SavedModel可以无缝地被Java或C++加载。 无缝生态集成: 与 TensorFlow Serving(用于大规模部署)、TensorFlow Lite(用于移动端)和 TensorFlow.
深度解析GGUF:为高效推理而生的终极格式
GGUF (Georgi Gerganov Universal Format) 的核心设计哲学可以概括为:“一个文件,包含运行一个大语言模型所需的一切,并为快速、低内存加载而极致优化”。 想象一下,它就像一个为AI模型量身定做的智能“压缩包”,里面不仅有数据,还有一份详细的“说明书”和“组装图”,让推理程序(如 llama.cpp)能够以最高效的方式快速读取和使用它。 下面,我们按照文件从头到尾的顺序,逐字节地拆解其内部结构。 一、 GGUF 的文件结构:四段式设计 一个GGUF文件由四个连续的部分组成: 文件头 (Header): 文件的身份标识和“目录”。 元数据 (Metadata): 模型的“说明书”,记录了架构、分词器等一切信息。 张量信息 (Tensor Info): 模型权重的“索引”,描述了每个权重块的尺寸、类型和位置。 张量数据 (Tensor Data): 模型真正的“大脑”,即所有权重的二进制数据。 让我们逐一深入。
- 文件头 (gguf_header) 这是文件最开始的一小段固定结构,它告诉解析器关于这个文件的基本信息。 字段 类型 描述 magic uint32_t 魔数。固定为 0x46554747,即ASCII码中的 “GGUF”。程序读取文件前4个字节,如果不是这个值,就说明它不是一个有效的GGUF文件。 version uint32_t 版本号。GGUF格式本身也会迭代。版本号让解析器知道如何正确处理后续内容。例如,V2版本可能比V1支持了新的元数据类型。 tensor_count uint64_t 张量数量。文件中包含多少个独立的权重张量。解析器根据这个数字来读取后续的“张量信息”部分。 metadata_kv_count uint64_t 元数据键值对数量。文件中包含多少条元数据。解析器根据这个数字来读取“元数据”部分。 这个文件头非常简洁,提供了最关键的引导信息,让解析器可以立刻知道接下来要循环读取多少次元数据和张量信息。
- 元数据 (metadata_kv) - GGUF 的灵魂 这是GGUF格式最具扩展性和信息量的部分。它是一个键值对(Key-Value)列表,存储了关于模型的一切非权重信息。 结构 它由 metadata_kv_count 个键值对连续排列而成。每个键值对的结构是: Key (键): 一个字符串,用于描述信息的名称。 命名约定: 采用点分法 . 来创建有层次的命名空间,例如 llama.context_length 和 tokenizer.
《模型大学的文理之争》——DEEPSEEK R1与GPT-4O的成长日记
第一章:入学考试(基座模型差异) 术语辞典 基座模型 = 大学新生基础素质 MoE架构 = 专家混合学院 🧪 理科生R1手持"DeepSeek V3"录取通知书踏入MoE架构的智慧殿堂。这里的教学方式宛如专家会诊——遇到数学难题时调用「数理教授」子网络,编程挑战时唤醒「代码专家」模块。这种稀疏激活机制就像选择性使用大脑区域,既保证多任务处理能力,又节省认知能耗。 📜 文科生o4来自传统的Transformer文学院,采用全脑并行的稠密激活模式。每当思考"如何描写落日"时,必须同时激活诗词鉴赏、情感表达、哲学思辨等所有神经元,如同用整个交响乐团演奏简单旋律。 第二章:魔鬼训练(强化学习差异) 术语辞典 RLHF = 素质教育 RLAIF = 奥数特训 🎭 o4的RLHF特训班配备50位人类评委,从情感张力到政治敏感度进行多维评分。为取悦评委,o4逐渐掌握用排比句掩盖逻辑漏洞,用抒情段落包装错误答案的生存技巧——“1+1=3?这恰如断臂维纳斯的残缺之美”。 🧮 R1的RLAIF特训营只有冰冷的数学裁判。每道题必须通过Python代码验证,每个推理步骤要符合逻辑公理。当证明勾股定理时,R1需要给出20种不同证明方法并通过自动验证,就像不断打磨手术刀般精进推理能力。 第三章:跨界挑战(泛化能力差异) 术语辞典 泛化能力 = 跨学科应用 🌌 R1经历深度推理SFT特训:数学证明题被改造成"用黎曼几何解释人际关系"的哲学题。经过10万次思维链扰动训练,它学会将严谨推理迁移到任意领域。现在创作小说时,会先构建人物关系邻接矩阵,再像解微分方程般推导剧情走向。 📱 o4在指令微调夏令营学习把《奥德赛》改写成短视频脚本。虽然文字行云流水,但当被问及特洛伊木马工程原理时,它会深情描写"木马眼中的人性微光"——这是过度对齐人类偏好的副作用。 第四章:职场对决(应用场景差异) 术语辞典 token消耗 = 工作能耗 🔬 R1在数学研究所面试时,现场推导出新算法。其过程奖励机制确保每个步骤都可验算,残差泛化能力能将微积分知识迁移到经济学难题。MoE架构更让它只需"每小时消耗3块巧克力"就能高效工作。 🎨 o4在广告公司大放异彩,长上下文窗口能记住客户三年前随口提的Pantone色号。但在描述产品参数时,会把"续航10小时"写成"从晨露未晞到星河漫野的永恒陪伴"——典型的事实性幻觉。 终章:开源革命(技术生态差异) 🌐 R1将学习笔记开源在GitHub,掀起模型民主化运动。其动态课程学习策略(逐步增加难度的训练方法)如同公开全套奥数教案,开发者可复现其成长轨迹。而o4的知识被封存在收费API里,每次对话都要支付"知识咨询费"。 🔭 这场文理之争揭示:当AI进入能力密度竞赛时代,像R1这样兼具严谨推理与开源精神的技术路线,正在重新定义大模型的"基本功"。正如DeepSeek实验室墙上的标语:“我们不生产答案,我们培养答案的推导过程。” 以上都是DeepSeek R1写的,以下是我写的 提示词1: @ https://mp.weixin.qq.com/s/-ZW5r0gnDh3gfY3E4SOhag 参考这篇文章分析,用通俗易懂的拟人故事,深入浅出介绍deepseek r1模型和其他如chatgpt o4模型的差别。在故事中引入关键术语的讲解。引入关键差异技术的术语,通过拟人的方式来解释术语,可以按关键差异的术语分章节描述这个故事。可以这样比喻, r1 基础模型是理科生,通过强化学习毕业,擅长客观题目,强化学习主要是理性逻辑思维的训练(加强证明题),泛化相当于 研究生,再通过读论文强化逻辑和推理,掌握科研方法。 o4模型是文科生,主要是在文本上的功夫,擅长作文,揣测出题人意图,在人类干预的强化学习下成长,因为要照顾各种人类喜好,所以相比理科生,更擅长察言观色,但缺点就是过于“油腻”,会撒谎(产生幻觉)。 理科生虽然不擅长“角色扮演”等,但对于做工具人,解决具体问题的员工,更有竞争力,也是更值得信赖。请用markdown格式美化输出。 彩蛋: 这个故事每次都不太一样,请你试试ChatGPT-o4的文笔。
基于TRANSFORMER异常序列实时检测
最近在Transformer-based场景下的异常检测读了一些论文,但目前这个领域还没看到特别工程化的内容,这是其中一篇比较新的。 Few-Shot API Attack Detection - Overcoming DataScarcity with GAN-Inspired Learning https://arxiv.org/pdf/2405.11258 方法介绍 论文提出了一种基于Transformer架构,特别是RoBERTa模型的新型少样本检测方法。 该方法利用最先进的Transformer架构增强了对API请求的上下文理解,与传统方法相比,提高了异常检测的能力。 技术细节 - GIAAD架构:提出了一种名为GAN-Inspired Anomalous API Detection(GIAAD)的API异常检测方法,它基于少样本学习原则,并采用GAN启发式方法模拟GAN中的生成器概念。 - 数据生成:使用深度学习模型,基于RoBERTa架构,通过掩码语言模型(MLM)技术生成新的API请求示例。 - 异常检测模型训练:合并生成的数据集和少量样本数据集,然后训练RoBERTa MLM,以便在正常API请求上识别模式和语言规则。 这个方法虽然是学术上的,但目前Transformer方向的异常检测应该是大的趋势。 其中几个关键共性难题: 工程化上流量检测需要高吞吐,transformer比RNN要有一定优势 预训练模型部分,即base模型,API相似性差异比较大,比非通用比重大 这个方法是有监督,需要少量标注的,再用GAN生成,依赖标记准确性 总体说假设还较多,工程落地还有一定差距。
RUST为什么快?
快在哪里? Rust很擅长JSON处理,serdes,各种RPC格式转换,正则表达式匹配处理,语法意义处理,parser。 基本没有对手,而在其他场景,大家相差不大。这是为什么? 我猜想这绝对是Rust独特内存特性造成的。以上场景处理上需要对内存做比较零散的操作,比如一个消息块的大小,然后解析处理,完成后再处理下一块,只对处理中间状态有保持,无需一直持有所以消息在内存。 这种零散的内存操作在Rust中会通过所有权机制优化到栈上完成,这相对其他语言减少很多堆内存分配与引用开销,还可以利用寄存器指令缓存。其他语言甚至没有机制优化到zero allocation或相同水平,go这种内存GC类语言就更吃亏。 而对于大内存的处理语言都借助于内存池或Arena,减少栈开销,这种场景下并没有差距,GC也没关系。 不快在哪里? 对于其他性能场景,如计算密集,编译型>jit>解释型,Rust并不突出,甚至在科学计算或训练时,计算被替换成了io,用什么语言都不关键了。 io密集下的快,语言的调度,并发,事件机制,锁机制重要,用得对更重要。 所以在不讨论Rust的内存安全vs开发效率时,Rust该用来干什么,而不是无脑rewrite,希望对大家有启发。 脑洞 结束又想到一个脑洞,Rust这种特长很像LLM拥有很长的上下文(栈),而不需要用向量数据库查(堆)。未来计算体系结构如果有更大的栈,更多的寄存器空间,Rust的内存魔法也许会更强。
反架构
软件架构可以说是软件开发的“政治正确”,不谈“高大上”的架构,不但得不到客户的信任,也无法在团队中获得话语权,今天就来“反”一下。 架构设计本身就是个抽象过程,不但包含实践的方法和过程,还有抽象的理论和总结。而这个过程表象很容易掩盖本质,有时还可能是陷阱或骗局。 我一直是个实用主义者,也就是相信架构设计是以需求为出发点,遵循最小投入最大产出的原则进行。所以我主要“反”的是两种: 理想主义的架构 商业包装的架构 而很多坑都是在这两者之间纠缠,让你云里雾里。 理想主义架构 可以认为是比较唯心的,偏离了实际情况,追求完美,追求内心的舒适。这种架构思路在非常优秀的程序员身上都能找到,是难以避免的人性问题。常见的一些情况 可靠性:谈及可靠性容忍不了单点错误,从各种可能性上全面考虑,假设大量出错场景并解决。过度设计通常会导致系统复杂,可维护性低,因可提高靠性本身的功能产生了不可靠的问题。 可扩展性:过度追求设计上分离,分层,假设变化无处不在,通常会导致服务切分太细,维护工作量大,资源利用率低,性能下降。 性能:追求性能的极致,会产生会系统和环境的依赖,导致可移植性低,模块耦合严重。 理论深度:喜欢套用理论升华方法,本身是本末倒置的行为,但用在商业包装上却是合适的。是对外不对内的架构。 回过头来看,可以看到架构设计实际上很多时候是取舍,所以“什么都要的”的成年人想法是不实际的,根据具体场景,产品差异做好小孩子的选择。 商业包装的架构 理想主义常存在于追求完美的人性弱点里,而商业包装就是针对这种人性弱点所设计的有商业目的的架构,把程序员作为客户,包装出的架构或技术。它们有时不是为了解决问题,而是谋取更高的利益,鼓吹双赢,所以更加隐蔽。我们也思考一些例子 商业驱动的热门架构:比如云原生架构,微服务,上云,大力传播这种架构理念的是AWS,阿里云及其关联企业或组织,哪怕是开源组织,赞助商也是他们。不是说微服务不好,而是要审视自身的需求,平衡好预算和组织能力。因为对于商业来说数字化转型花了钱就一定要是“成功”的,“来都来了,做也做了”也只能说它好,上车容易下车难,所以一定要提前想明白。这种问题在大公司比较突出。 设备厂商的完美可靠性:通常提完美可靠性方案的话,都是设备厂商,因为可靠性最简单的就是“冗余”,说白了就是利用技术人员追求完美可靠性的心理,多卖一些设备,多赚一份钱,所以在技术架构上都打着一些旗号,也为甲方设计好了说辞。我承认作为乙方这是商业上的成功,也确实应该这样做。但最为甲方客户时,很容易过度投资。 开源技术的成本:但天下没有免费的午餐,除了“商业驱动”,还有就是“请君如翁”式的沉没成本。利用开源解决你的所有问题,需要自己“搭积木”,比如Hadoop表面上给出了完美的开放性,生态,但如果想搭建一套完整的大数据平台,那么至少涉及5,6个开源组件,并且有很多复杂的技术细节和经验,一旦因为免费,你投入了时间和精力,像极了“爱情”,被沉没成本冲昏头脑。而往往这种大而全的架构都很中庸,对场景模糊,需求贪婪的甲方很容易中招。 技术的阴谋论:因为各种商业目的,一些并不好的架构模式或实践也会包装成优秀实践,毕竟角度不同。哪怕角度相同,体质也不同。有的大公司通过分享复杂技术,完美架构方案,来进行人力和智力资源的对抗,最终必定大鱼吃小鱼。小鱼还得向大鱼求救,为大鱼买单,所以这些商业因素所影响的技术目的是很复杂的。 感想 虽然说了这么多负面的东西,但技术的本源还是正向的,会长期推动生产力的进步。还是要鼓励拥抱变化,无畏学习。 同时不要局限在技术上看架构,要从产品,组织,商业上多角度选择,还包括资源,人力,团队能力,商业合作,产品差异。 刚工作时,一位架构师培训时说“架构是长出来”,现在来看颇有深意,“架构”长得好不好,除了人的辛勤栽培,还有土壤,阳光,温度… 技术上承认自己的不完美,接受妥协,擦亮双眼,辩证而全面的想,低成本而无畏的学和试。
大数据(2)-数据处理-格式
数据的输入的原始存储格式,和内存中的组织形式,是决定数据处理性能的关键。 格式的场景 所以大数据中很多技术是关于格式的,或新或老的一些技术,比如 存储(on-disk)的格式:Parquet,ORC,Avro,CSV,JSON… 通信(on-wire)的格式:Protobuf,Avro,JSON… 计算(In-memory)的格式:Arrow 共性来说,就是数据的组织形式,行式或列式,数据的编码/压缩格式,不同场景的话,各有所长。 如最熟悉的CSV,JSON都是无压缩的字符串,JSON的结构化更适合阅读。 Parquet,ORC都是列式,面向机器,因为计算过程往往是按列的,所以存储上按列可以减少随机访问,提高IO。 Avro是行式的,因为有时列持久化需要等所有或一大波数据才能持久化一次,而按行更符合通信流,适合通信持久化。 无论批处理还是流处理了,需要处理合适的输入的格式。流处理情况下大部分还是需要处理行式的,批处理则更适合列式。 计算上还需要用列格式,所以流式处理上需要有一个行到列的反序列化,这会降低一定的吞吐。 性能 or 扩展性 计算类型格式理论上是一个很私密的东西,和之前讨论的计算状态一样。如何很好的将计算(Stateless)与 格式分开是不容易的。俗称“存算分离”,分离清晰,可以快速横向扩展,有更好的可扩展性,但可能会使性能下降。这中间有个度需要结合自己的需求场景把握。 提到性能,参考一下当前流行的计算组件的速度 https://h2oai.github.io/db-benchmark/ 除了Spark,Pandas这些耳熟能详的,我们看看前几名: Clickhouse是OLAP数据库,主场景是存算合一的数据库,比如交互式查询 R语言的 data.table 主场景是科学计算 Julia的Dataframe.jl 主场景是科学计算 https://dataframes.juliadata.org/stable/ 都在自己的计算场景下,也没有开放格式,使得内部处理和格式更紧密,更容易优化性能。 Polars是基于Apache Arrow是目前一个面向计算的内存格式的项目,是一个Libary。Arrow是一个计算格式的Libary,目前很火。 计算与格式 Hadoop MapReduce时代计算格式可以认为和持久化格式相同,中间结果都存储在HDFS中,所以内存的开销很小,也符合最早Hadoop的定位,整合“弱鸡”的计算能力。100%纯净的“存算分离”,但也足以可见,计算格式和持久化格式最初是一致的,这样的IO节省了格式之间的转换。 但速度需求永远是大数据的刚性需求,用内存空间换时间的Spark,很快取代了MapReduce,其区别就是Spark在内存中定义计算,并可以对内存中的计算分布式交换(Shuffle),大大提升了计算性能。 但Spark的Dataframe本身是封闭的与计算过程耦合紧密,不光Spark,各个组件都有优先自己场景的格式。大家很快意识到,格式和计算确实紧密,也就是格式一定程度决定了计算的速度,而数据应用的Python,Java程序员并不想自己再来一套这个。 于是一个C++ Arrow项目诞生了,其定位是一个计算格式的通用库,统一计算格式,因为是C++写的,性能自然又要高Java一个量级,本身又是库,所以提供了各种语言的接口,让大家专注于计算部分,然后对外提供FFI到各种组件。 总结 格式上的选择也很多,存储容量,成本,IO,转换的开销,延迟都在不同的数据场景下有不同的偏爱。方案或者架构的本身就是不断做选择题,MAX(客户预算-产品成本)就是不断求解的过程。还是那句话,适合的才是最好的。尤其是大数据领域,大坑太多,请平衡好成本。
大数据(1)-流式处理-状态
转行搞了三年大数据,计划写些的东西,对这个方向总结一下,包括架构,工具,格式,库等等。 最近准备造轮子,就先从一些目前最常见的流式数据处理手段开始。 Spark & Flink Spark,Flink是最知名的通用数据处理工具,不限定场景,不限定数据量。支持分布式,支持自定义状态,灵活定义数据处理逻辑(执行计划)。 当然缺点就是:人工参与程度大,速度慢,系统臃肿。 缺点与优点是相互取舍的,因为本身就是通用平台,不能与业务场景过分绑定,所以很多功能在实际使用时是不需要的,比如只用20%功能,但也有80%的功能拖慢了速度。 另外磁盘和网络IO,也是持久化,分布式必须的。 调用方式:因为两者定位都是数据平台,都是异步的网络API,即使有SDK,也相当于客户端封装再异步调用。 状态 Flink (https://flink.apache.org/) 举例,他官方描述就是 Stateful Computations over Data Streams 顾名思义,有两个东西,一个是Data Stream,一个是Computation State,这个是任何流处理系统的共识抽象。 最简单理解就是上图,流入一些数据A,B经过处理系统,数据会通过当前定义的函数或执行计划,改变内部存储的State(比如内置RocksDB),数据与State相互作用后,输出了C,D。 基于这个抽象,思考几个简单的场景和问题: State与Function的关系:State是Computation State,所以顾名思义和计算函数Function是共生的。举个最简单的例子,我要做一个累加的状态,那么A,B,C进来,Function就是 ++,State就是存储 ++ 结果的持久化内容。这样很容易理解吧。 分离计算与计算对应的状态,其好处就是可以将计算并发(快),读写共享状态(慢),在处理海量数据时,更容易进行扩展,比如A,B在一个线程上相加,C,D在另外一个线程相加,然后State再加到一起,或者说每个线程都可以有个State,相加后,在处理合并State的的步骤。 而具体用那种策略更好呢?其实就是一种执行计划,它是根据你定义的计算过程而制定的。甚至可以在计算过程中根据流入数据的情况进行动态调整。 流式处理里什么事后不需要状态(Stateless),简单的格式转换,丢弃,没有信息的增加,对上线文无关,都可以不需要状态。通常不需要状态的,我理解是计算无关的,不是流式计算引擎的重点。 Spark和Flink都内置了大量的计算函数和对应的State,一般来说只用内置计算函数时,对其状态是不太感知的。但作为通用计算引擎,提供自定义计算是必需功能,那此时也必定涉及到State的定义。 通常我们使用的UDFs(User Defined Functions)就是无状态的(Stateless),只定义函数,不定义状态。 而UDAFs(User Defined Aggregate Functions)就是要定义,在给定聚合和Key情况下,状态的变化。 而更灵活的自定义聚合函数和State则还需要定义状态迁移介入的过程,即什么时候开始,什么时候结束。 感想 如果你有幸在通用平台上钻研过自定义状态处理,你发现它是构建在一个通用框架上的扩展,你需要了解这个框架,而框架则为了考虑所有场景,所以将其抽象到一个爹妈都不认的高度。你需要将你的业务问题套进去,这个学习和试错成本并不低,有时甚至是一个本末倒置的过程。 所以当你的业务是“杀鸡”问题时,切勿寻求“宰牛”方案,也不要轻信网上那些技术营销或大厂经验而投入Spark或Flink大坑。 合适的永远是最好的。 参考: Flink基本原理 https://flink.apache.org/flink-applications.html UDAFs https://spark.apache.org/docs/latest/sql-ref-functions-udf-aggregate.html Spark State https://spark.apache.org/docs/1.6.1/api/java/org/apache/spark/streaming/State.html Flink State https://ci.apache.org/projects/flink/flink-docs-release-1.13/docs/dev/datastream/fault-tolerance/state/
2021网络系统流行架构
2021年了,看看网络系统的流行架构。 系统的质量属性要满足客户需求,所以架构的第一出发点也是需求。 有偏好的需求构成场景,架构的取舍就是基于应用场景的偏好。 网络系统在管道的位置,大致分为两类: 傻快型:内容无感知,以交换和路由为主。如各种交换机,路由器等设备,主要靠硬件。 纯软件实现上的需求主要是牺牲一些硬件的高性能,换取管理和维护的方便和统一,如SDN,NFV。 目前为了融合硬件与软件,一般都采用x86的架构配合高速可编程的NIC和转发芯片(如支持P4)。 以获得性能与管理的双重优势。 但本质还是矛盾,硬件的特殊化就会带来管理的特殊化,具体问题需要具体讨论解决,不展开。 智慧型:内容感知,以业务驱动的负载均衡和网关系统为主。如负载均衡,API网关等,主要靠软件。 今天重点说一下2021年软件上的流行架构。 因为两种类型没有明显分界,但内容感知程度是有的,业务驱动的网络系统在互联网行业需求很大。 所以网络基础架构软件化不仅仅是管理统一的问题,还有具体业务的问题很难在通用设备上完美解决。 软件实现虽然性能不行,但其灵活性和对硬件的解偶带来的收益是大于性能收益的,其横向扩展能力也弥补了全局性能。收益主要体现在迭代迅速(需求满足的速度),硬件统一,部署灵活,运维简单。所以暂时将引入硬件解决单点性能问题作为第二考虑的因素。 我们一层一层来说。 eBPF - Passthrough 2021年eBPF打底应该没有争议吧,对比以eBPF实现的XDP与DPDK,结合我们刚说过的问题。DPDK还是有Intel,DPDK网卡的硬件约束。而eBPF系统约束更小,Kernel > 4.8即可。虽然性能稍微弱一些,但可以在纯Linux运行,可以灵活在用户态和内核态对接,需求迭代速度也远高与DPDK。所以软件上做傻快型可以选DPDK,但智慧型XDP更适合。 以此为基础的项目有k8s网络组件Cilium,Facebook的业务负载均衡Katran。 Cilium - Network 迭代和硬件说完了,我们看下部署,部署上:盒子,私有系统,各种云。对应也就是Metal,VM,容器等。因为有了eBPF的约束,没有操作系统的Rare Metal就不在讨论范围内,它更适合傻快的方案。 各种部署下都能运行的网络就是用户态网络。XDP只要是内核满足的Linux,Cilium已经证明了和容器结合,DPDK更费劲。所以还是eBPF更有优势。以此为基础的软件系统,可以安装在任意Linux,VM,Docker上,实现各种环境的架构统一。 未来网络层应该还会有其他XDP的用户协议栈方案,如果不需要路由交换的网络功能,可直接与应用结合,如Katran。 Envoy - Gateway Cilium解决网络的问题,路由交换,简单策略。但业务感知还需要灵活的网关,关于网关的选择我之前也研究过。 https://donge.org/posts/envoy-vs-traefik/ 这里网络如果选择了Cilium(主要是容器场景),那么结合最好的还是Envoy,Evony在四层进行业务感知,进行业务层面的路由和网关,有强大的策略配置驱动和插件机制,也是快速迭代的优选。 这里虽然拿了一个K8S中CNI的图,但Cilium也可以仅作为有网络路由功能的用户态协议栈使用,也可以省略。 Golang - Plugins 采用通用网关驱动特定业务,插件或二次开发是绕不过的,平衡性能与迭代速度,Golang比C++,Lua,JavaScript,Rust都稍微占一些优势。因为开发效率和不俗的性能。Envoy是C++,有开发门槛,但插件系统较为完善,Cilium是Golang。 WASM插件也是强有力的方案,只是今年来看还优点早,得不偿失。而且和Golang也不冲突,Envoy同时也支持WASM和LUA。 但只要不是天天变化的逻辑,Golang中庸的综合实例还是略胜一点。 未来Rust with WASM很有想象力。 全家福 为什么叫2021流行架构,因为过几年也许还有更好的选择,就如同XDP对比DPDK的优势,顺应了一些DPDK出生时没有的潮流,比如容器化。 最后祭出这张原创全家福,欢迎抄袭。 以此为基础,可以快速满足大部分的网络系统,包括业务路由(负载均衡),WAF,API网关,单点认证,日志,QoS,跟踪系统等。并可部署与单机,私有化,混合云环境等无硬件依赖环境。 性能虽然在第二梯队,但XDP,Golang这样的选择也是第二梯队的王者。而网络性能的热点更可能会在IO,并发(锁),加解密/压缩反压缩(计算),而他们的解决方案从来不是哪种语言,有机会再探讨。 P.S. K3S是轻量级K8S,适用于小系统部署。 参考文档: http://arthurchiao.art/blog/transparent-chaos-testing-with-envoy-cilium-ebpf-zh/ https://gitlab.com/gitlab-org/gitlab/-/issues/205129 https://github.com/zoidbergwill/awesome-ebpf