首页 文章 精选 留言 我的

精选列表

搜索[告别],共849篇文章
优秀的个人博客,低调大师

云计算正在告别DIY时代 阿里云专有云挑起企业级市场大梁

“云计算的DIY时代已经过去”,5月23日在2018云栖大会·武汉峰会上,阿里云专有云事业部总经理马劲表示,DIY式私有云正被时代抛弃,不仅部署费时费力,且难以解决企业云的顽疾。取而代之的是被验证过的公共云架构,这成为企业云平台新标准。 而针对困扰客户的“数据孤岛”、“烟囱式应用”、“割裂的跨部门协作”等问题。阿里云推出了基于专有公共云与“中台架构”的整体解决方案。以真正的云与真正解决客户困境的互联网架构开启企业以云来推进数字化转型的最佳实践。 云计算的DIY时代已经过去 此前,“自主可控的飞天云”与“拿来主义云”成为行业讨论焦点,而当日的武汉峰会上阿里云总裁胡晓明再次提出“拿来主义云盖不出高楼大厦,自主研发的云才能走得更远”。 在实践中,“拿来主义云”很快暴露出没有真正掌握核心技术所带来的种种问题。比如,没有经历大规模的实战检验,稳定性

优秀的个人博客,低调大师

人工智能让你告别打码时代

像人类一样,机器也能识别模糊的面部图像——而且更快 得克萨斯大学和康奈尔科技学院的研究人员称,打码(像素化图像)是模糊身份的狡猾方式,但计算机可被训练得能识别出这些“被保护”的人。 如果攻击者有一套清晰的照片可供练习,被像素化模糊的部分便昭然若揭。只要人工智能(AI)也能有相应的清晰照片集用以训练,面部马赛克作为匿名机制这种事还是让它飘散在风中吧。 听起来似乎没什么大用——人类也能做到同样的事情,但由于计算机化了,便可以自动执行,也就是说可以日夜不停,快速进行。 arXiv科学论文电子预印本库中的一篇论文( http://arxiv.org/pdf/1609.00408v1.pdf )称,像素化(拼接)、模糊(如YouTube上用的一样),甚至加密JPEG系数(被称为P3的机制:“隐私保护照片共享”),也挡不住恢复被保护的图像。 他们认为,人工神经网络可被训练来成功识别人脸、物体和手写签名,即使这些图像用上述模糊技术处理过。基于不同数据集合不同模糊类型,成功率在50%到95%之间。 他们的AI不需要人类来指出重要特征。不仅不需要预先指定相关特性,也不用知道部分加密或模糊化的图像泄露出来的究竟是什么。神经网络会自动发现相关特性,并学会利用隐藏和可见部分之间的联系。 处理照片中唯一的难点,在于攻击者需要巡游社交媒体,搜罗可能出现在给定照片中的可能人脸集合。但对于明星或公众人物来说,这都不是事。 同样的方法,适用于恢复被模糊化了的文本或手写文字,只要攻击者能找到训练数据集(就像图像识别模型的基准一样存在于网上)。该论文还描述了用于训练此模型的神经网络架构,可供意图更好地保护图像隐私的开发者当做参考基准。 本文转自d1net(转载)

优秀的个人博客,低调大师

告别海量数据收集!RoboAug 让机器人操作泛化至数百未知场景

欢迎来到天工造物开源社区!作为具身智能领域的协作枢纽,我们致力于开源开放核心技术,与全球开发者并肩同行,用代码加速通用智能的落地。 面对每天爆发式增长的具身智能 arXiv 论文和行业动态,如何才能不掉队?为了帮你打破信息茧房、过滤无效噪音,我们特别推出全新专栏活动 ------「具身智能半月谈」。每天一期硬核技术文章,不仅深度拆解顶会上的明星论文,更会为你剖析最前沿的具身技术原理。和我们一起"啃"透前沿算法,每天几分钟,精准把握学术与产业的最新风向! 导读 在具身智能与机器人操作领域,如何让机器人学到的技能泛化到未见过、极具挑战的真实场景中一直是一大难题。传统的做法往往需要花费巨大人力物力去收集海量数据,或者依赖极为完美的上游图像识别系统。 近日,由北京人形机器人创新中心联合慕尼黑工业大学、北京大学、北京航空航天大学等顶尖机构的研究团队,提出了一种全新的区域对比数据增强框架------RoboAug。该框架打破了传统数据收集的瓶颈,仅需对单张图像进行简单的边界框标注,即可让机器人操作策略泛化到数百个未见过的全新场景! 项目主页: https://x-roboaug.github.io/ 论文链接:https://arxiv.org/abs/2602.14032 领域瓶颈:面向分布外(ODD)环境的灾难性策略崩溃 长期以来,基于端到端模仿学习(End-to-End Imitation Learning)或视觉-语言-动作模型(VLA)训练的运动控制策略在独立、封闭环境内展现出优异的拟合能力。 然而,受限于有限的离线演示分布,策略很容易对局部视觉快捷特征(Visual Shortcuts)发生过拟合。一旦将其部署于结构或光照产生时变偏移(Distribution Shift)的新环境中(例如背景的域偏差、阴影和多重光源和高强度的外置视觉干扰物聚集),其动作推断的正确率(Zero-Shot Generalization)呈现出灾难性的坍塌失效。 为了克服协变量偏移的问题,工业界主流皆是依赖海量跨实施例(Cross-Embodiment)数据的堆积(如 Open X-Embodiment 等)。但这面临着难以逾越的数据采集和人工遥操作标注壁垒;而现有的基于图像增强技术的语义变换又高度依赖场景内完美的特征隔离和目标检测后处理能力,这使得系统在发生强遮挡或精巧交互的环境中几乎无法实际运行。 范式跃迁:RoboAug 的"化繁为简"与对比范式重塑 为彻底打破具身感知领域这一僵局,RoboAug 创新性地引入了生成式 AI 结合区域对比表征的端到端管线式框架。它以最精简的用户交互介入范式,利用下游自动化的区域提取技术构建具有高语义信息量且几何拓扑守恒的高保真扩增图集。 RoboAug 整个框架主要包含三个极具创新性的阶段: 1、零门槛的区域提取 (Task-Relevant Region Extraction) 传统方法往往依赖繁琐的逐帧标注或高昂的检测器重训练成本,而 RoboAug 提出了一种"免训练(training-free)"的单样本匹配与传播机制。研究人员只需在单张参考图像(Anchor Frame)提供边界框(Bounding Box),系统即调用视觉基础模型(如 GroundingDINO)生成候选框,并基于 DINOv2 提取的特征向量进行余弦相似度匹配,实现高精度的零样本目标重识别。 随后,引入时空一致的分割追踪范式(如 SAM2),将稀疏的包围盒先验转化为稠密的像素级掩码(Pixel-level Masks),并在整段演示轨迹的时间戳上实现自动化传播与对齐。 2、移花接木的语义数据增强 (Semantic Data Augmentation) 传统的基于图像修复(Inpainting)的语义增强方法在遮挡处理上往往会引入几何形变和严重的视觉掩模伪影(Visual Artifacts)。RoboAug 抛弃了该技术路线,而是利用大语言模型(如 ChatGPT)自动扩充了数百个背景描述提示词模板(包含木材质58%、石材质35%及合成材质7%等)。 通过引导 Text-to-Image 生成模型(如 Stable Diffusion)合成高分辨率、多样性的全景背景纹理。最终将预先提取的与任务相关的前景掩码以像素级精度无缝融合(Composite)到新生成的结构背景中,在实现数据规模指数级扩充的同时,零损耗地保护了核心操作对象的物理几何拓扑与位姿。 3、火眼金睛的区域对比策略学习 (Region-Contrastive Policy Learning) 为弥合数据增强与策略优化之间的语义鸿沟,RoboAug 创新性地引入了即插即用的区域对比损失(Region-Contrastive Loss, RCL)。在每次训练迭代中,一方面通过二进制掩码对原图像进行逐元素乘法(Element-wise Product)提取物体级特征(Object-centric Feature);另一方面利用全局特征向量,配合空间自注意力机制(Spatial Self-Attention)弱化掩码黑色遮挡区域引发的无效激活。 通过在同一类别的局部特征间构造正样本对,跨类别特征间构造负样本对,该框架显式地优化了视觉编码器的表征聚类空间。在不改变原始 Backbone 架构的前提下,实现了抑制视觉干扰、聚焦任务关键实体的本质能力提升。 RoboAug-D:以机器人第一视角打造的大规模数据集 这是一个覆盖多种机器人物理平台(涵盖 Single-Arm Franka, Single-Arm/Dual-Arm UR 以及双臂全向底盘平台 AgileX)的第一视角大规模目标检测数据集,提供了极其精细的目标级监督信号: 33 种不同的跨平台操作任务(囊括单臂精细装配到双臂协同动作);依托多视角的连续时间序列,总计采集了 73,749 个有效高维观测帧; 覆盖 46 个具身场景物体类别,并提供了多达 366,835 个高质量 2D 边界框(Bounding Box)标注! 不仅如此,考虑到开源视觉基础模型(VFM)在具身智能特殊视角分布下的固有的端侧部署局限与精度盲区,研究团队还在 RoboAug-D 测试集上全方位评估了各领域最新大模型的零样本(Zero-Shot)定位性能。 通过采用 mAP@0.5 严谨的评价指标,横向对比了 GroundingDINO、LLMDet 等主流开箱即用的感知方案,深刻揭示了这些模型在应对夹爪遮挡、视角形变与动态交互过程中的失效机制,从而为未来用于 Robot-MIND 的闭环多模态大语言模型(VLA)与视觉感知组件提供了一套高规格的泛化能力 Benchmark 靶场。 惊艳的表现:3.5万次真实测试,成功率飙升! 理论必须经过现实的检验,研究团队在三个不同的真实硬件平台上进行了规模宏大的部署实验: 单臂协作机器人 (UR-5e) 移动复合机器人 (AgileX Cobot Magic 2.0) 人形机器人 (天工 2.0 / Tien Kung 2.0) 实验覆盖了包括单臂抓放、抽屉开关、以及高难度的精细双臂协作等多种任务,累计执行了超过 35,000 次真实的物理交互评估! 团队在高度非结构化的、严苛地解耦了环境变量扰动的多模态评测基座上进行了评估实验。针对分布外(OOD)的数据漂移特征------即完全未知的场景纹理、高达20种强弱突变复杂光照环境、伴随至多10类任务无关的密集干扰物(Distractor),模型充分显现了稳健鲁棒的域自适应表现能力。 特别是在复合扰动(Triple-Factor Variation)这一"地狱级"难度下相比仅采集基准演示集,RoboAug 完全压制了各种灾难性遗忘与泛化退化。 UR-5e 单臂协同基线:在强干扰场景下的动作执行鲁棒成功率由 9% 跃迁至 47%; AgileX 具有移动底盘的双臂操作基线:整体闭环序列成功率由 16% 断崖式攀升至 60%; 基于大自由度的天工2.0人形机器人全场景评估:通用操作泛化能力由 19% 跃迁至高达 67% 的成功率! 真机部署表现 场景1:多维复合分布偏移考验(背景纹理换置+高密集 Distractors+全局光照扰动),不论是复杂长时程多步依赖的时序任务,甚至是涉及冗余自由度空间控制的双臂精细协同规划,RoboAug 凭借其强劲的注意力表征,均实现了近乎免疫性的视觉鲁棒位姿估计与动作序列输出! 场景2:边缘形态的灾变性单一变量施压(Ablation under Severe Single-Factor Shift),在测试集外超过 170 个零交集(Zero-overlap)语义背景纹理替换、逾 20 种强空间变换光源照射,乃至于在紧凑的工作空间内密集堆叠超过 10 种具有高度混淆性的物理干扰实体(Distractors)等情况下,RoboAug 有效抑制了对伪特征的拟合,提取并锚定到了高维稳定的语义中心区域! 零样本与基线对比:对抗未规划轨迹的主动纠偏与抗干扰演练(Robust Action Alignment under Visual Perturbations),在前所未闻的高维组合扰动(OOD Combinations)物理交互中,各大纯视觉策略基线模型(Base Policies)往往受累于非因果(Non-causal)特征映射,频繁发生策略遗忘与幻觉动作,表现为严重的空间位姿误判与抓取失效。与之形成鲜明技术代差的是,RoboAug 的表征不为局部噪声或材质偏移所惑,自始至终在闭环控制序列里展现出高频可靠的行动引导。 } 结语 具身智能要真正走进千家万户、应对非结构化的复杂世界,强大的跨场景泛化能力是必不可少的敲门砖。RoboAug 为我们提供了一条高效、低成本的技术捷径。"一图胜千言,一框衍百景",研究团队期待这项技术能加速机器人的规模化落地。

优秀的个人博客,低调大师

告别提示词工程,「上下文工程」才是 AI Agent 的核心竞争力

编者按: 什么样的技能才能真正决定 AI 智能体的成败?是更复杂的算法,还是更精妙的提示词?我们今天为大家带来的文章,作者的观点是:构建强大 AI 智能体的关键已从"提示词工程"转向"上下文工程"。 文章从"上下文"的广义定义出发,详细拆解了影响 AI 决策的七大关键要素,包括系统指令、用户输入、历史对话、长期记忆、外部检索信息、可用工具及输出结构。通过对比"廉价演示项目"与"神奇智能体"的案例,作者生动展现了上下文质量如何决定 AI 的表现 ------ 真正的差距不在于模型本身,而在于是否提供了恰当的上下文支持。作者进一步提出,上下文工程是一套动态流程,需跨领域协作,以结构化的方式整合业务需求与技术实现,确保 LLM 在正确的时间获得正确的信息与工具。 作者 | Philipp Schmid 编译 | 岳扬 上下文工程(Context Engineering)是一个在人工智能领域逐渐走红的新术语。行业内讨论的焦点正从"提示词工程"(prompt engineering)转向一个更广泛、更强大的概念:上下文工程(Context Engineering)。托比·卢克(Tobi Lutke)[1]将其描述为"为任务提供完整的上下文背景,使大语言模型能够合理解决问题的一门艺术",他说得很到位。 随着 Agents 的兴起,将哪些信息输入"有限的工作记忆(limited working memory)"中变得越来越重要。我们观察到,决定一个 Agent 成败的关键因素,通常就在于你提供给它的上下文质量。大多数 Agent 的失败早已不是模型本身的问题,而恰恰是上下文供给的失败。 01 什么是上下文(Context)? 要理解上下文工程(Context Engineering),我们首先必须扩展对"上下文"的定义。它不仅指你发送给 LLM 的单一提示词(prompt)。应该将其视为模型在生成响应前所看到的一切信息。 指令 / 系统提示词(Instructions / System Prompt) : 用于定义模型在对话期间行为的初始指令集,可以/应该包含示例、规则等。 用户提示词(User Prompt) : 来自用户的即时任务或问题。 状态 / 历史(短期记忆)[State / History (short-term Memory] : 当前的对话内容,包括导致此刻结果的"用户与模型的历史回复"。 长期记忆(Long-Term Memory) : 在之前的多次对话中收集的持久性知识库,包含学习到的用户偏好、过往对话摘要、或被明确告知需要记忆以备后续使用的信息。 检索信息(RAG)[Retrieved Information (RAG)] : 外部的、最新的知识,来自文档、数据库或 API 的相关信息,用于回答特定问题。 可用工具(Available Tools) : 所有可调用函数或内置工具的标准化描述(如输入参数、输出格式、功能说明)(例如 check_inventory, send_email)。 结构化输出(Structured Output) : 对模型响应格式的定义,例如一个 JSON 对象。 02 为什么重要?从「廉价的演示项目」到「神奇的智能体产品」 构建真正高效的 AI 智能体的秘诀,与你编写代码的复杂程度关系不大,而与你提供上下文的质量息息相关。 构建智能体,与你编写的代码或使用的框架关系不大。 一个廉价的演示项目和"神奇的智能体"之间的区别,就在于你所提供上下文的质量。假设让一个 AI 助手根据一封简单的邮件来安排会议: Hey, just checking if you're around for a quick sync tomorrow. 嘿,想问一问明天方不方便,我们快速碰个头? "廉价的智能体演示项目"的上下文质量很差。它只看到用户的请求,其他什么都看不到。它的代码可能功能完善,它会调用 LLM 并获得响应,但输出的内容却毫无帮助,且充满机械感: Thank you for your message. Tomorrow works for me. May I ask what time you had in mind? 感谢来信!明天我有空。你想约在几点? "神奇的智能体"则由丰富的上下文驱动。其代码的主要任务并非琢磨如何回应,而是收集 LLM 所需的信息,以便更好地响应用户需求。在调用 LLM 之前,你可以扩展上下文,使其包含: 你的日历信息(显示你日程已满)。 你与此人的过往邮件(用于确定合适的非正式语气)。 你的联系人列表(用于识别 ta 为关键的合作伙伴)。 send_invite 或 send_email 工具。 然后便能生成回应: Hey Jim! Tomorrow's packed on my end, back-to-back all day. Thursday AM free if that works for you? Sent an invite, lmk if it works. 嗨 Jim!明天我这边日程全排满了,从早到晚连轴转。周四上午有空,你看行不?邀请已发,确认下是否合适~ 这种神奇的效果并非源于更聪明的模型或更精巧的算法,而在于为正确的任务提供了恰当的上下文。这就是为什么上下文工程(Context Engineering)非常重要。智能体的失败并非仅仅是模型的失败,本质上是上下文的缺失。 03 从提示词工程到上下文工程 什么是上下文工程?如果说"提示词工程(prompt engineering)"侧重于在单个文本字符串中精心设计一套完美的指令,那么上下文工程(context engineering)的范畴则宽广得多。简而言之: 上下文工程是一门设计和构建动态系统的学科,它能以正确的格式、在正确的时间提供正确的信息与工具,赋予 LLM 完成任务所需的一切资源。 上下文工程是 一套流程,而非某些字符串:上下文不仅是一个静态的提示词模板。它是主 LLM 调用前系统运行所产生的输出。 动态构建的:随任务即时生成,适配用户当下的需求。对某个请求,其上下文可能是日历数据,对另一请求,上下文则可能是邮件记录或网页搜索结果。 在正确的时间提供正确的信息和工具:其核心职责是确保模型不遗漏关键细节(Garbage In, Garbage Out)。这意味着只有在必需且有帮助时才提供知识(信息)与能力(工具)。 注重呈现格式:如何呈现信息很重要。简明扼要的摘要胜过原始数据的堆砌,清晰的工具架构胜过模糊的指令。 04 Summary 构建强大且可靠的 AI 智能体,已经不再需要寻找神奇的提示词或更新模型版本。其核心在于上下文工程,即以正确的格式、在正确的时间提供正确的信息与工具。这是一项跨领域协作的挑战,需要理解业务场景、定义预期输出,并结构化组织所有必要的信息,使 LLM 能够真正"完成任务"。 05 致谢 本综述的完成得益于深度研究(deep research)与人工校验(manual research),并从以下优质资源中汲取了灵感与信息: Tobi Lutke tweet[1] Karpathy tweet[2] The rise of "context engineering"[3] Own your context window[4] Context Engineering by Simon Willison[5] Context Engineering for Agents[6] END 本期互动内容 🍻 ❓你是否有过动态构建上下文的经验?能否分享一个你认为特别成功的案例? 文中链接 [1]https://x.com/tobi/status/1935533422589399127 [2]https://x.com/karpathy/status/1937902205765607626 [3]https://blog.langchain.com/the-rise-of-context-engineering/ [4]https://github.com/humanlayer/12-factor-agents/blob/main/content/factor-03-own-your-context-window.md [5]https://simonwillison.net/2025/Jun/27/context-engineering/ [6]https://rlancemartin.github.io/2025/06/23/context_engineering/ 本文经原作者授权,由 Baihai IDP 编译。如需转载译文,请联系获取授权。 原文链接: https://www.philschmid.de/context-engineering

优秀的个人博客,低调大师

🔥 Solon AI + MCP 实战:5 行代码搞定天气查询,LLM 从此告别数据孤岛

此文参考自:https://www.toutiao.com/article/7505712149642117669/ 一、痛点直击:为什么你的AI模型会“一问三不知”? 当你问 AI “杭州今天会下雨吗”,早期的 AI 应用会回答“请搜索网络”(大概这么个意思)。传统 AI 开发需要为每个工具单独开发接口(如天气API、股票数据、本地文件系统),每对接一个数据源需耗费开发者3-5天时间,且存在以下问题: 重复开发:不同的 AI 应用需各自对接同一套工具(比如对接高德地图接口) 动态能力缺失:无法实时调用最新数据(如每分钟变化的股价) 生态碎片化:不同厂商工具接口互不兼容 技术门槛量化:若企业需对接10个外部工具,传统方案需投入至少10人天,而通过MCP协议可缩短至1人天,效率提升10倍。 二、技术破局:MCP协议如何成为AI世界的“万能插座”? MCP(Model Context Protocol)由 Anthropic 提出,其核心价值在于: 统一接口:像 USB-C 协议统一外设接入,MCP 为 AI 定义标准化的工具调用规范 动态感知:支持多轮对话中实时调用外部资源(如天气API、数据库) 安全隔离:通过本地服务处理敏感数据,避免直接暴露API密钥 技术架构对比 传统方案 MCP方案 每个模型独立对接工具 一次开发,所有MCP兼容模型通用 手动处理HTTP请求/响应 声明式工具描述自动映射 无状态会话 支持会话ID恢复上下文 三、实战教学:5行代码构建天气预报MCP服务 场景设定 为旅游规划AI助手提供实时天气查询能力,用户输入“帮我规划杭州三日游,避开雨天”时,AI自动调用天气数据生成行程。 代码实现(Solon AI 3.3 + JDK 1.8) 第1步:(服务端)定义天气 MCP 服务。(真的只有 “5” 行代码) @McpServerEndpoint(name = "weather-server", sseEndpoint = "/mcp/weather") public class WeatherService { @ToolMapping(description = "获取指定城市的未来三天天气预报") public String getWeather(@Param(description = "城市名称") String city) { return WeatherApi.getForecast(city); // 调用真实天气API } } 第2步:(客户端)AI调用演示 McpClientProvider toolProvider = McpClientProvider.builder() .apiUrl("http://localhost:8080/mcp/weather") .build(); ChatResponse response = chatModel.prompt("杭州明天适合户外活动吗?") .options(o-> o.toolsAdd(toolProvider)) .call(); 四、技术解析:为什么这(服务端)5行代码能颠覆传统开发? @McpServerEndpoint和@ToolMapping注解魔法:将Java方法自动映射为AI可理解的工具描述,减少 80% 的胶水代码 动态协议协商:MCP客户端自动匹配服务端版本,无需手动处理兼容性问题 混合传输模式:支持SSE流式响应(实时天气变化推送)与同步请求 安全隔离层:敏感操作(如数据库访问)仅在服务端执行,客户端无直接权限 性能对比: 指标 传统REST API MCP服务 工具接入耗时 8小时/个 半小时/个 多模型支持 需重复适配 一次开发通用 长上下文支持 无 会话ID保持 五、场景扩展:MCP如何重构AI应用生态? 案例1:智能客服系统 痛点:用户问“我的订单物流到哪了”,客服需手动查询多个系统 MCP方案:对接订单数据库+物流API,AI自动组合数据生成回答,响应速度从2分钟缩短至3秒 案例2:企业知识库问答 痛点:员工查询内部文档需登录多个系统 MCP方案:连接Confluence、GitLab、CRM系统,问答准确率提升65% 案例3:AI编程助手 痛点:开发者需复制代码到IDE调试 MCP方案:直接操作IDE编译部署,操作步骤减少70% 六、未来展望:开发者该如何抓住这波技术红利? 工具升级:将现有REST服务通过OpenRewrite一键转换为MCP服务(节省90%迁移成本) 生态布局:建设垂直领域工具市场(如金融数据、医疗知识库) 架构升级:采用Streamable HTTP协议提升高并发场景稳定性 行动指南: 立即体验:GitHub示例项目 加入 Solon AI 社区获取最新工具包 关注MCP协议2.0升级(支持多模态数据流) 结束语: 当技术门槛从“月级”降至“小时级”,每个开发者都能成为AI生态的构建者。MCP不是又一个昙花一现的概念,而是打开AI普惠时代的钥匙——正如Spring框架重构Java开发,MCP正在重构AI与真实世界的连接方式。 延伸阅读: MCP协议官方文档 Solon AI 深度集成实战 百万级并发MCP服务架构设计

优秀的个人博客,低调大师

速来体验丨1Panel 支持一键部署 MCP Server,告别繁琐配置!

MCP(Model Context Protocol,模型上下文协议)是由人工智能企业Anthropic推出的开放标准,旨在为大语言模型和AI助手提供统一、标准化的接口,让AI可以轻松操作外部工具,完成更加复杂的任务,从而发挥真正的“工具调用”能力。 然而在实际操作过程中,搭建MCP Server需要手动配置大量依赖,部署门槛较高,许多用户难以上手。为了解决这个问题,1Panel v1.10.29 LTS版本推出了原生的MCP Server管理功能,该功能通过容器化方式实现一键部署MCP Server,能够极大简化搭建流程。 本文为您介绍1Panel的MCP Server管理功能的五个特点。 一、零配置一键部署 在1Panel左侧菜单栏中依次选择“AI”→“MCP”,点击“创建MCP Server”按钮并填写启动命令、端口号和所需环境变量等基础配置信息,即可在1Panel管理的服务器上一键部署MCP Server实例,无需在本地拉取代码或手动安装依赖。 1Panel的MCP Server部署流程基于Docker容器运行,隔离性强、兼容性好,真正实现了“零侵入、零配置”的部署体验,大大降低了用户操作门槛。 ▲图1 创建MCP Server 二、客户端信息自动生成 MCP Server部署成功后,1Panel会为每个MCP Server实例自动生成客户端配置信息,包括端口、地址、SSE路径等。点击“配置”按钮,即可快速获取该MCP Server的客户端配置信息。 ▲图2 MCP Server列表 ▲图3 获取MCP Client配置 用户只需要复制客户端配置信息并粘贴至MCP客户端,即可开始使用拥有MCP加成的AI助手。这种方式无需手动查找或配置环境变量,实现了从部署到使用的无缝衔接。 ▲图4 与MCP Client集成 三、支持统一域名与SSE路径 1Panel支持将多个MCP Server实例统一绑定至同一个网站域名,每个实例仅需设置不同的SSE路径进行区分。这意味着用户无需为每个MCP Server单独开放端口,所有服务都可以通过同一个端口对外提供服务。 这种方式不仅简化了公网访问的配置逻辑,也让运维操作更加集中统一。尤其是在大规模部署和企业内部网络的场景下,统一绑定网站域名能够避免暴露过多端口,减少安全风险,进一步提升部署的灵活性、安全性和可维护性。 ▲图5 统一域名与SSE路径 四、白名单访问限制 1Panel支持为每个MCP Server网站配置IP访问白名单,以此有效保障MCP Server的数据安全。用户可以根据实际需求将IP地址或IP段添加至白名单,从而保证只有白名单中的IP能够访问MCP Server网站。与此同时,系统将自动拒绝所有不在白名单中的IP的访问请求。 通过为MCP Server网站配置IP访问白名单,可以有效隔离外部非授权访问,在网络入口层面建立起第一道安全防线。同时配合1Panel的防火墙策略和容器隔离机制,可以显著提升整体系统的安全性与稳定性。 五、支持HTTPS数据加密 此外,1Panel还支持为MCP Server网站启用HTTPS协议,用户只需要上传证书即可开启加密访问,全面保障上下文交互数据的安全性。 总结 使用1Panel来管理MCP Server不仅可以降低部署门槛,也能够收获更好的使用体验,让您的AI工具集成之路更加顺畅!如果您对1Panel的MCP Server管理功能感兴趣,欢迎您部署和体验1Panel。

优秀的个人博客,低调大师

告别手动调度,海豚调度器 3.1.x 集群部署让你轻松管理多机!

转载自第一片心意 1 前言 由于海豚调度器官网的集群部署文档写的较乱,安装过程中需要跳转到很多地方进行操作,所以自己总结了一篇可以直接跟着从头到尾进行操作的文档,以方便后续的部署、升级、新增节点、减少节点的相关操作。 2. 提前准备 2.1. 基础组件 JDK:下载JDK (1.8+),安装并配置 JAVA_HOME 环境变量,并将其下的 bin 目录追加到 PATH 环境变量中。如果你的环境中已存在,可以跳过这步。 二进制包:在下载页面下载 DolphinScheduler 二进制包 数据库:PostgreSQL (8.2.15+) 或者 MySQL (5.7+),两者任选其一即可,如 MySQL 则需要 JDBC Driver 8 版本,可以从中央仓库下载。 注册中心:ZooKeeper (3.4.6+),下载地址。 进程树分析 macOS安装pstree Fedora/Red/Hat/CentOS/Ubuntu/Debian安装psmisc。 注意: DolphinScheduler 本身不依赖 Hadoop、Hive、Spark 等,但如果你运行的任务需要依赖他们,就需要有对应的环境支持。 3. 上传 上传二进制包,并且解压到某个目录,具体目录位置,自己定即可。 要注意目录名称,最好在后面加一些字符,要做到安装目录和二进制包解压目录不同名,以进行区分。 tar -xvf apache-dolphinscheduler-3.1.7-bin.tar.gz mv apache-dolphinscheduler-3.1.7-bin dolphinscheduler-3.1.7-origin 后面的 -origin 表示这是原始的二进制包解压文件,后续有配置改动时,可以修改改目录下的文件,然后重新执行安装脚本。 4. 用户 4.1. 配置用户免密及权限 创建部署用户,并且一定要配置 sudo 免密。以创建 dolphinscheduler 用户为例: # 创建用户需使用 root 登录 useradd dolphinscheduler # 添加密码 echo "dolphinscheduler" | passwd --stdin dolphinscheduler # 配置 sudo 免密 sed -i '$adolphinscheduler ALL=(ALL) NOPASSWD: ALL' /etc/sudoers sed -i 's/Defaults requirett/#Defaults requirett/g' /etc/sudoers # 修改目录权限,使得部署用户对二进制包解压后的 apache-dolphinscheduler-*-bin 目录有操作权限 chown -R dolphinscheduler:dolphinscheduler apache-dolphinscheduler-*-bin 注意: 因为任务执行服务是以 sudo -u {linux-user} 切换不同 linux 用户的方式来实现多租户运行作业,所以部署用户需要有 sudo 权限,而且是免密的。初学习者不理解的话,完全可以暂时忽略这一点。 如果发现 /etc/sudoers 文件中有 “Defaults requirett” 这行,也请注释掉。 4.2. 配置机器SSH免密登陆 由于安装的时候需要向不同机器发送资源,所以要求各台机器间能实现SSH免密登陆。配置免密登陆的步骤如下: su dolphinscheduler ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys # 一定要执行下面这个命令,否则免密登录会失败 chmod 600 ~/.ssh/authorized_keys 注意: 配置完成后,可以通过运行命令 ssh localhost 判断是否成功,如果不需要输入密码就能 ssh登陆,则证明成功。 5. 启动zookeeper 启动集群中的 zookeeper 即可。 6. 修改配置 下面所有的操作,均在 dolphinscheduler 用户下执行。 完成基础环境的准备后,需要根据你的机器环境修改配置文件。配置文件可以在目录 bin/env 中找到,他们分别是 install_env.sh 和 dolphinscheduler_env.sh。 6.1. install_env.sh install_env.sh 文件配置将 DolphinScheduler 安装到哪些机器 ,以及每台机器安装哪些服务。可以在路径 bin/env/ 中找到此文件,之后按照下面的说明修改对应的配置即可。 # --------------------------------------------------------- # INSTALL MACHINE # --------------------------------------------------------- # A comma separated list of machine hostname or IP would be installed DolphinScheduler, # including master, worker, api, alert. If you want to deploy in pseudo-distributed # mode, just write a pseudo-distributed hostname # Example for hostnames: ips="ds1,ds2,ds3,ds4,ds5", Example for IPs: ips="192.168.8.1,192.168.8.2,192.168.8.3,192.168.8.4,192.168.8.5" # 配置海豚调度器要安装到那些机器上 ips=${ips:-"ds01,ds02,ds03,hadoop02,hadoop03,hadoop04,hadoop05,hadoop06,hadoop07,hadoop08"} # Port of SSH protocol, default value is 22. For now we only support same port in all `ips` machine # modify it if you use different ssh port sshPort=${sshPort:-"22"} # A comma separated list of machine hostname or IP would be installed Master server, it # must be a subset of configuration `ips`. # Example for hostnames: masters="ds1,ds2", Example for IPs: masters="192.168.8.1,192.168.8.2" # 配置 master 角色要安装到哪些机器上 masters=${masters:-"ds01,ds02,ds03,hadoop04,hadoop05,hadoop06,hadoop07,hadoop08"} # A comma separated list of machine <hostname>:<workerGroup> or <IP>:<workerGroup>.All hostname or IP must be a # subset of configuration `ips`, And workerGroup have default value as `default`, but we recommend you declare behind the hosts # Example for hostnames: workers="ds1:default,ds2:default,ds3:default", Example for IPs: workers="192.168.8.1:default,192.168.8.2:default,192.168.8.3:default" # 配置 worker 角色要安装到哪些机器上,默认都放到 default 的 worker 分组内,其他分组,可以通过海豚调度器界面进行单独配置 workers=${workers:-"ds01:default,ds02:default,ds03:default,hadoop02:default,hadoop03:default,hadoop04:default,hadoop05:default,hadoop06:default,hadoop07:default,hadoop08:default"} # A comma separated list of machine hostname or IP would be installed Alert server, it # must be a subset of configuration `ips`. # Example for hostname: alertServer="ds3", Example for IP: alertServer="192.168.8.3" # 配置 alert 角色安装到哪个机器上,配置一台机器即可 alertServer=${alertServer:-"hadoop03"} # A comma separated list of machine hostname or IP would be installed API server, it # must be a subset of configuration `ips`. # Example for hostname: apiServers="ds1", Example for IP: apiServers="192.168.8.1" # 配置 api 角色安装到哪个机器上,配置一台机器即可 apiServers=${apiServers:-"hadoop04"} # The directory to install DolphinScheduler for all machine we config above. It will automatically be created by `install.sh` script if not exists. # Do not set this configuration same as the current path (pwd). Do not add quotes to it if you using related path. # 配置安装路径,将会在所有海豚集群的机器上安装服务,一定要和上面解压的二进制包目录区分开,最好带上版本号,以方便后续的升级操作。 installPath=${installPath:-"/opt/dolphinscheduler-3.1.5"} # The user to deploy DolphinScheduler for all machine we config above. For now user must create by yourself before running `install.sh` # script. The user needs to have sudo privileges and permissions to operate hdfs. If hdfs is enabled than the root directory needs # to be created by this user # 部署使用的用户,用上面自己新建的用户即可 deployUser=${deployUser:-"dolphinscheduler"} # The root of zookeeper, for now DolphinScheduler default registry server is zookeeper. # 配置注册到 zookeeper znode 名称,如果配置了多个海豚集群,则需要配置不同的名称 zkRoot=${zkRoot:-"/dolphinscheduler"} 6.2. dolphinscheduler_env.sh 可以在路径 bin/env/ 中找到此文件,该文件用来配置用到的一些环境,按照下面的说明修改对应配置即可: # JDK 路径,一定要修改 export JAVA_HOME=${JAVA_HOME:-/usr/java/jdk1.8.0_202} # 数据库类型,支持 mysql、postgresql export DATABASE=${DATABASE:-mysql} export SPRING_PROFILES_ACTIVE=${DATABASE} # 连接 url,主要修改下面的 hostname,最后配置的是东八区 export SPRING_DATASOURCE_URL="jdbc:mysql://hostname:3306/dolphinscheduler?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai" export SPRING_DATASOURCE_USERNAME=dolphinscheduler # 如果密码比较复杂,则需要前后使用英文单引号括起来 export SPRING_DATASOURCE_PASSWORD='xxxxxxxxxxxxx' export SPRING_CACHE_TYPE=${SPRING_CACHE_TYPE:-none} # 配置各角色 JVM 启动时使用的时区,默认为 -UTC,如果想要完全支持东八区,则设置为 -GMT+8 export SPRING_JACKSON_TIME_ZONE=${SPRING_JACKSON_TIME_ZONE:-GMT+8} export MASTER_FETCH_COMMAND_NUM=${MASTER_FETCH_COMMAND_NUM:-10} export REGISTRY_TYPE=${REGISTRY_TYPE:-zookeeper} # 配置使用的 zookeeper 地址 export REGISTRY_ZOOKEEPER_CONNECT_STRING=${REGISTRY_ZOOKEEPER_CONNECT_STRING:-hadoop01:2181,hadoop02:2181,hadoop03:2181} # 配置使用到的一些环境变量,按照自己的需要进行配置即可,所有需要的组件,都自己安装 export HADOOP_HOME=${HADOOP_HOME:-/opt/cloudera/parcels/CDH/lib/hadoop} export HADOOP_CONF_DIR=${HADOOP_CONF_DIR:-/etc/hadoop/conf} export SPARK_HOME1=${SPARK_HOME1:-/opt/soft/spark1} export SPARK_HOME2=${SPARK_HOME2:-/opt/spark-3.3.2} export PYTHON_HOME=${PYTHON_HOME:-/opt/python-3.9.16} export HIVE_HOME=${HIVE_HOME:-/opt/cloudera/parcels/CDH/lib/hive} export FLINK_HOME=${FLINK_HOME:-/opt/flink-1.15.3} export DATAX_HOME=${DATAX_HOME:-/opt/datax} export SEATUNNEL_HOME=${SEATUNNEL_HOME:-/opt/seatunnel-2.1.3} export CHUNJUN_HOME=${CHUNJUN_HOME:-/opt/soft/chunjun} export PATH=$HADOOP_HOME/bin:$SPARK_HOME1/bin:$SPARK_HOME2/bin:$PYTHON_HOME/bin:$JAVA_HOME/bin:$HIVE_HOME/bin:$FLINK_HOME/bin:$DATAX_HOME/bin:$SEATUNNEL_HOME/bin:$CHUNJUN_HOME/bin:$PATH 6.3. common.properties 从自己的 hadoop 集群上下载 hdfs-site.xml 和 core-site.xml 文件,然后放到 api-server/conf/ 和 worker-server/conf/ 目录下。如果是自己搭建的 apache 的原生集群,则从各个组件的 conf 目录下找,如果是 CDH ,则可以通过 CDH 界面直接下载。 修改 api-server/conf/ 和 worker-server/conf/ 目录下的这个文件,该文件主要用来配置资源上传相关参数,比如将海豚的资源上传到 hdfs 等,按照下面的说明修改即可: # 本地路径,主要用来存放任务运行时的临时文件,要保证用户对该文件具有读写权限,一般保持默认即可,如果后续任务运行报错说是对该目录下的文件没有操作权限,直接将该目录权限修改为 777 即可 data.basedir.path=/tmp/dolphinscheduler # resource view suffixs #resource.view.suffixs=txt,log,sh,bat,conf,cfg,py,java,sql,xml,hql,properties,json,yml,yaml,ini,js # 保存资源的地方,可用值为: HDFS, S3, OSS, NONE resource.storage.type=HDFS # 资源上传的基本路径,必须以 /dolphinscheduler 开头,要保证用户对该目录有读写权限 resource.storage.upload.base.path=/dolphinscheduler # The AWS access key. if resource.storage.type=S3 or use EMR-Task, This configuration is required resource.aws.access.key.id=minioadmin # The AWS secret access key. if resource.storage.type=S3 or use EMR-Task, This configuration is required resource.aws.secret.access.key=minioadmin # The AWS Region to use. if resource.storage.type=S3 or use EMR-Task, This configuration is required resource.aws.region=cn-north-1 # The name of the bucket. You need to create them by yourself. Otherwise, the system cannot start. All buckets in Amazon S3 share a single namespace; ensure the bucket is given a unique name. resource.aws.s3.bucket.name=dolphinscheduler # You need to set this parameter when private cloud s3. If S3 uses public cloud, you only need to set resource.aws.region or set to the endpoint of a public cloud such as S3.cn-north-1.amazonaws.com.cn resource.aws.s3.endpoint=http://localhost:9000 # alibaba cloud access key id, required if you set resource.storage.type=OSS resource.alibaba.cloud.access.key.id=<your-access-key-id> # alibaba cloud access key secret, required if you set resource.storage.type=OSS resource.alibaba.cloud.access.key.secret=<your-access-key-secret> # alibaba cloud region, required if you set resource.storage.type=OSS resource.alibaba.cloud.region=cn-hangzhou # oss bucket name, required if you set resource.storage.type=OSS resource.alibaba.cloud.oss.bucket.name=dolphinscheduler # oss bucket endpoint, required if you set resource.storage.type=OSS resource.alibaba.cloud.oss.endpoint=https://oss-cn-hangzhou.aliyuncs.com # if resource.storage.type=HDFS, the user must have the permission to create directories under the HDFS root path resource.hdfs.root.user=hdfs # if resource.storage.type=S3, the value like: s3a://dolphinscheduler; if resource.storage.type=HDFS and namenode HA is enabled, you need to copy core-site.xml and hdfs-site.xml to conf dir # resource.hdfs.fs.defaultFS=hdfs://bigdata:8020 # whether to startup kerberos hadoop.security.authentication.startup.state=false # java.security.krb5.conf path java.security.krb5.conf.path=/opt/krb5.conf # login user from keytab username login.user.keytab.username=hdfs-mycluster@ESZ.COM # login user from keytab path login.user.keytab.path=/opt/hdfs.headless.keytab # kerberos expire time, the unit is hour kerberos.expire.time=2 # resourcemanager port, the default value is 8088 if not specified resource.manager.httpaddress.port=8088 # if resourcemanager HA is enabled, please set the HA IPs; if resourcemanager is single, keep this value empty yarn.resourcemanager.ha.rm.ids=hadoop02,hadoop03 # if resourcemanager HA is enabled or not use resourcemanager, please keep the default value; If resourcemanager is single, you only need to replace ds1 to actual resourcemanager hostname yarn.application.status.address=http://ds1:%s/ws/v1/cluster/apps/%s # job history status url when application number threshold is reached(default 10000, maybe it was set to 1000) yarn.job.history.status.address=http://hadoop02:19888/ws/v1/history/mapreduce/jobs/%s # datasource encryption enable datasource.encryption.enable=false # datasource encryption salt datasource.encryption.salt=!@#$%^&* # data quality option data-quality.jar.name=dolphinscheduler-data-quality-dev-SNAPSHOT.jar #data-quality.error.output.path=/tmp/data-quality-error-data # Network IP gets priority, default inner outer # Whether hive SQL is executed in the same session support.hive.oneSession=false # use sudo or not, if set true, executing user is tenant user and deploy user needs sudo permissions; if set false, executing user is the deploy user and doesn't need sudo permissions sudo.enable=true setTaskDirToTenant.enable=false # network interface preferred like eth0, default: empty #dolphin.scheduler.network.interface.preferred= # network IP gets priority, default: inner outer #dolphin.scheduler.network.priority.strategy=default # system env path #dolphinscheduler.env.path=dolphinscheduler_env.sh # development state development.state=false # rpc port alert.rpc.port=50052 # set path of conda.sh conda.path=/opt/anaconda3/etc/profile.d/conda.sh # Task resource limit state task.resource.limit.state=false # mlflow task plugin preset repository ml.mlflow.preset_repository=https://github.com/apache/dolphinscheduler-mlflow # mlflow task plugin preset repository version ml.mlflow.preset_repository_version="main" 6.4. application.yaml 需要修改所有角色下 /conf/application.yaml 文件,包括:master-server/conf/application.yaml、worker-server/conf/application.yaml、api-server/conf/application.yaml、alert-server/conf/application.yaml,主要修改的是时区设置,具体修改如下: spring: banner: charset: UTF-8 jackson: # 将时区设置为东八区,只修改这一个地方即可 time-zone: GMT+8 date-format: "yyyy-MM-dd HH:mm:ss" 6.5. service.57a50399.js和service.57a50399.js.gz 这两个文件在 api-server/ui/assets/ 和 ui/assets/ 目录下。 分别切换到这两个目录下,然后分别找到这两个文件,之后通过 vim 命令打开,然后搜索 15e3,找到之后,将其改为 15e5。这修改的是页面响应的超时时间,默认值 15e3 表示 15 秒,我们将其改为 1500 秒,在上传大文件时,不会因为页面超时而报错。 7. 初始化数据库 驱动配置 将 mysql 驱动(8.x)拷贝到海豚调度器每个角色的 lib 目录下,包括:api-server/libs、alert-server/libs、master-server/libs、worker-server/libs、tools/libs。 数据库用户 使用 root 用户登录 mysql,然后执行以下 sql,mysql5 和 mysql8 都支持: create database `dolphinscheduler` character set utf8mb4 collate utf8mb4_general_ci; create user 'dolphinscheduler'@'%' IDENTIFIED WITH mysql_native_password by 'your_password'; grant ALL PRIVILEGES ON dolphinscheduler.* to 'dolphinscheduler'@'%'; flush privileges; 执行数据库升级脚本: bash tools/bin/upgrade-schema.sh 8. 安装 bash ./bin/install.sh 执行该脚本,会将本地的所有文件通过 scp 远程传输给上面配置文件中配置的所有机器,然后停止对应机器上的角色,之后再启动所有机器上的角色。 第一次安装之后,就已经启动了所有的角色,无需再次单独启动任何角色,如果有哪些角色没启动的话,可以去对应的机器上查看对应的日志,看具体是什么问题导致的。 9. 启停服务 # 一键停止集群所有服务 bash ./bin/stop-all.sh # 一键开启集群所有服务 bash ./bin/start-all.sh # 启停 Master bash ./bin/dolphinscheduler-daemon.sh stop master-server bash ./bin/dolphinscheduler-daemon.sh start master-server # 启停 Worker bash ./bin/dolphinscheduler-daemon.sh start worker-server bash ./bin/dolphinscheduler-daemon.sh stop worker-server # 启停 Api bash ./bin/dolphinscheduler-daemon.sh start api-server bash ./bin/dolphinscheduler-daemon.sh stop api-server # 启停 Alert bash ./bin/dolphinscheduler-daemon.sh start alert-server bash ./bin/dolphinscheduler-daemon.sh stop alert-server 一定要注意,必须使用安装海豚调度器的用户执行这些脚本,否则会有一些权限之类的问题。 每个服务在路径 <service>/conf/dolphinscheduler_env.sh 中都有 dolphinscheduler_env.sh 文件,为微服务需求提供便利。这意味着你可以在对应服务中配置 <service>/conf/dolphinscheduler_env.sh,然后通过<service>/bin/start.sh 命令基于不同的环境变量来启动各个服务。但如果使用命令 /bin/dolphinscheduler-daemon.sh start <service> 启动服务器,它将会使用文件 bin/env/dolphinscheduler_env.sh 覆盖 <service>/conf/dolphinscheduler_env.sh ,然后启动服务,这么做是为了减少用户修改配置的成本。 10. 扩容 10.1. 标准方式 参考上面的步骤,进行如下操作: 新节点 安装配置好 JDK。 新建海豚用户(Linux 用户),然后配置免密登录、权限等。 之前安装海豚调度器时解压二进制安装包的机器上。 登录安装海豚的用户。 切换到之前安装海豚调度器时解压二进制安装包,修改配置文件:bin/env/install_env.sh,在该配置文件中,修改需要在新节点上部署的角色。 执行 /bin/install.sh 文件进行安装,该脚本会按照 bin/env/install_env.sh 文件中的配置,将整个目录重新 scp 到所有的机器,之后停止所有机器上的角色,然后再启动所有角色。 该方式的缺点:如果海豚调度器上有很多分钟级别的任务,或者是 flink、spark 之类的实时任务,由于该操作会停止所有的角色,然后启动,这期间会花费一定的时间,在这期间,这些任务可能会由于整个集群的重启,从而异常停止,或者是无法被正常调度起来。但海豚调度器自己实现了自动容错和灾备等功能,所以可以这么操作,最后观察下所有任务执行是否正常。 10.2. 简单方式 参考上面的步骤,进行如下操作: 新节点 安装配置好 JDK。 新建海豚用户(Linux 用户),然后配置免密登录、权限等。 之前安装海豚调度器时解压二进制安装包的机器上。 登录安装海豚的用户。 将之前修改完配置的整个目录直接压缩,然后传输到新节点上。 新节点 在新节点上解压文件,然后将其重命名到之前配置文件 bin/env/install_env.sh 中配置的安装目录下。 登录安装海豚的用户。 需要在新节点部署哪些角色,就启动哪些角色,具体脚本位置:/bin/dolphinscheduler-daemon.sh,启动命令为: ./dolphinscheduler-daemon.sh start master-server ./dolphinscheduler-daemon.sh start worker-server 登录到海豚调度器界面,然后“监控中心”中观察,对应角色在新节点是否启动。 11.缩容 在需要下线的机器上,通过 /bin/dolphinscheduler-daemon.sh 脚本停止机器上所有的角色,停止命令为: ./dolphinscheduler-daemon.sh stop worker-server 登录到海豚调度器界面,然后“监控中心”中观察,刚才机器上停止的角色是否已经消失。 在之前安装海豚调度器时解压二进制安装包的机器上 登录安装海豚的用户。 修改配置文件:bin/env/install_env.sh,在该配置文件中,删除下线角色对应的机器。 12. 升级 按照上面的步骤,一步一步操作即可,对于已经有过的操作,无需二次操作。下面是一些具体的操作步骤: 上传新版二进制包。 解压,解压到和旧版安装目录不同的目录,或者是重命名也可以。 修改配置文件,比较简单的方式是,将上面步骤中涉及到的所有配置文件,从之前安装的目录下拷贝到新版本目录下,替换即可。 将其他节点上部署的一些组件,全部打包,然后解压放到新节点对应的位置。具体需要拷贝哪些组件,可以查看 dolphinscheduler_env.sh 文件中的配置。 配置驱动,参考《初始化数据库》中的步骤。 停止之前的集群。 备份整个数据库。 执行数据库升级脚本,参考《初始化数据库》中的步骤。 执行安装脚本,参考《安装》。 升级完成,登录界面,查看“监控中心”,看所有角色是否都成功启动。 文件转载,请标明出处。欢迎大家一起讨论技术,写的不对的地方还请大家一起讨论。 原文链接:https://blog.csdn.net/u012443641/article/details/131419391 本文由 白鲸开源科技 提供发布支持!

优秀的个人博客,低调大师

浅析MySQL代价模型:告别盲目使用EXPLAIN,提前预知索引优化策略 | 京东云技术团队

背景 在 MySQL 中,当我们为表创建了一个或多个索引后,通常需要在索引定义完成后,根据具体的数据情况执行 EXPLAIN 命令,才能观察到数据库实际使用哪个索引、是否使用索引。这使得我们在添加新索引之前,无法提前预知数据库是否能使用期望的索引。更为糟糕的是,有时甚至在添加新的索引后,数据库在某些查询中会使用它,而在其他查询中则不会使用,这种情况下,我们无法确定索引是否发挥了预期的作用,让人感到非常苦恼。这种情况基本上意味着 MySQL 并没有为我们选择最优的索引,而我们不得不在茫茫数据中摸索,试图找到问题的症结所在。我们可能会尝试调整索引,甚至删除索引,然后重新添加,希望 MySQL 能从中找到最优的索引选择。然而,这样的过程既耗时又费力,而且往往收效甚微。 如果在添加索引之前,我们能够预知索引的使用情况,那么对于表设计将大有裨益。我们可以在设计表结构时,更加明确地知道应该选择哪些索引,如何优化索引,以提高查询效率。我们不再需要依赖盲目尝试和猜测,而是可以基于实际的数据和查询情况,做出更加明智的决策。因此,对于 MySQL 用户来说,能够预知索引走势的需求非常迫切。我们希望能有一种方法,能够让我们在添加索引之前,就清楚地了解 MySQL 将如何使用索引,以便我们能够更好地优化表结构,提高查询效率。这将极大地减轻我们的工作负担,提高我们的工作效率,让我们能够更加专注于业务逻辑的处理,而不是在索引的海洋中挣扎。 为了解决这个问题,我们可以深入研究 MySQL 的索引选择机制。实际上,这个机制的核心就是代价模型,它通过一个公式来决定索引的选择策略。相对于 MySQL 其他复杂的概念,代价模型实现起来要简单得多。熟悉代价模型之后,我们可以预先了解 MySQL 在执行查询时会如何选择索引,从而更有效地进行索引优化。在接下来的文章中,我将结合近期进行索引优化的具体案例,来详细解释如何运用代价模型来优化索引。 MySQL代价模型浅析  MySQL数据库主要由4层组成: 1. 连接层:客户端和连接服务,主要完成一些类似于连接处理、授权管理、以及相关的安全方案。 2. 服务层:主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化以及内部函数的执行。 3. 引擎层:负责MySQL中数据的存储和提取,服务器通过AP1与存储引擎进行通信。 4. 存储层:将数据存储文件系统上,并完成与存储引擎的交互。  索引策略选择在SQL优化器进行的 SQL 优化器会分析所有可能的执行计划,选择成本最低的执行,这种优化器称之为:CBO(Cost-based Optimizer,基于成本的优化器)。 Cost = Server Cost + Engine Cost = CPU Cost + IO Cost 其中,CPU Cost 表示计算的开销,比如索引键值的比较、记录值的比较、结果集的排序 ...... 这些操作都在 Server 层完成; IO Cost 表示引擎层 IO 的开销,MySQL 可以通过区分一张表的数据是否在内存中,分别计算读取内存 IO 开销以及读取磁盘 IO 的开销。 源码简读 MySQL的数据源代码采用了5.7.22版本,后续的代价计算公式将基于此版本进行参考。   opt_costconstants.cc【代价模型——计算所需代价计算系数】 /* 在Server_cost_constants类中定义为静态常量变量的成本常量的值。如果服务器管理员没有在server_cost表中添加新值,则将使用这些默认成本常数值。 5.7版本开始可用从数据库加载常量值,该版本前使用代码中写的常量值 */ // 计算符合条件的⾏的代价,⾏数越多,此项代价越⼤ const double Server_cost_constants::ROW_EVALUATE_COST= 0.2; // 键⽐较的代价,例如排序 const double Server_cost_constants::KEY_COMPARE_COST= 0.1; /* 内存临时表的创建代价 通过基准测试,创建Memory临时表的成本与向表中写入10行的成本一样高。 */ const double Server_cost_constants::MEMORY_TEMPTABLE_CREATE_COST= 2.0; // 内存临时表的⾏代价 const double Server_cost_constants::MEMORY_TEMPTABLE_ROW_COST= 0.2; /* 内部myisam或innodb临时表的创建代价 创建MyISAM表的速度是创建Memory表的20倍。 */ const double Server_cost_constants::DISK_TEMPTABLE_CREATE_COST= 40.0; /* 内部myisam或innodb临时表的⾏代价 当行数大于1000时,按顺序生成MyISAM行比生成Memory行慢2倍。然而,没有非常大的表的基准,因此保守地将此系数设置为慢5倍(即成本为1.0)。 */ const double Server_cost_constants::DISK_TEMPTABLE_ROW_COST= 1.0; /* 在SE_cost_constants类中定义为静态常量变量的成本常量的值。如果服务器管理员没有在engine_cost表中添加新值,则将使用这些默认成本常数值。 */ // 从主内存缓冲池读取块的成本 const double SE_cost_constants::MEMORY_BLOCK_READ_COST= 1.0; // 从IO设备(磁盘)读取块的成本 const double SE_cost_constants::IO_BLOCK_READ_COST= 1.0; opt_costmodel.cc【代价模型——部分涉及方法】 double Cost_model_table::page_read_cost(double pages) const { DBUG_ASSERT(m_initialized); DBUG_ASSERT(pages >= 0.0); // 估算聚集索引内存中页面数占其所有页面数的比率 const double in_mem= m_table->file->table_in_memory_estimate(); const double pages_in_mem= pages * in_mem; const double pages_on_disk= pages - pages_in_mem; DBUG_ASSERT(pages_on_disk >= 0.0); const double cost= buffer_block_read_cost(pages_in_mem) + io_block_read_cost(pages_on_disk); return cost; } double Cost_model_table::page_read_cost_index(uint index, double pages) const { DBUG_ASSERT(m_initialized); DBUG_ASSERT(pages >= 0.0); double in_mem= m_table->file->index_in_memory_estimate(index); const double pages_in_mem= pages * in_mem; const double pages_on_disk= pages - pages_in_mem; const double cost= buffer_block_read_cost(pages_in_mem) + io_block_read_cost(pages_on_disk); return cost; } handler.cc【代价模型——部分涉及方法】 // 聚集索引扫描IO代价计算公式 Cost_estimate handler::read_cost(uint index, double ranges, double rows) { DBUG_ASSERT(ranges >= 0.0); DBUG_ASSERT(rows >= 0.0); const double io_cost= read_time(index, static_cast<uint>(ranges), static_cast<ha_rows>(rows)) * table->cost_model()->page_read_cost(1.0); Cost_estimate cost; cost.add_io(io_cost); return cost; } // 表全量扫描代价相关计算(IO-cost) Cost_estimate handler::table_scan_cost() { const double io_cost= scan_time() * table->cost_model()->page_read_cost(1.0); Cost_estimate cost; cost.add_io(io_cost); return cost; } // 覆盖索引扫描代价相关计算 Cost_estimate handler::index_scan_cost(uint index, double ranges, double rows) { DBUG_ASSERT(ranges >= 0.0); DBUG_ASSERT(rows >= 0.0); const double io_cost= index_only_read_time(index, rows) * table->cost_model()->page_read_cost_index(index, 1.0); Cost_estimate cost; cost.add_io(io_cost); return cost; } /** 估算在指定 keynr索引进行覆盖扫描(不需要回表),扫描 records条记录,需要读取的索引页面数 @param keynr Index number @param records Estimated number of records to be retrieved @return Estimated cost of 'index only' scan */ double handler::index_only_read_time(uint keynr, double records) { double read_time; uint keys_per_block= (stats.block_size/2/ (table_share->key_info[keynr].key_length + ref_length) + 1); read_time=((double) (records + keys_per_block-1) / (double) keys_per_block); return read_time; } sql_planner.cc【用于ref访问类型索引费用计算】 double tmp_fanout= 0.0; if (table->quick_keys.is_set(key) && !table_deps && //(C1) table->quick_key_parts[key] == cur_used_keyparts && //(C2) table->quick_n_ranges[key] == 1+MY_TEST(ref_or_null_part)) //(C3) { tmp_fanout= cur_fanout= (double) table->quick_rows[key]; } else { // Check if we have statistic about the distribution if (keyinfo->has_records_per_key(cur_used_keyparts - 1)) { cur_fanout= keyinfo->records_per_key(cur_used_keyparts - 1); if (!table_deps && table->quick_keys.is_set(key) && // (1) table->quick_key_parts[key] > cur_used_keyparts) // (2) { trace_access_idx.add("chosen", false) .add_alnum("cause", "range_uses_more_keyparts"); is_dodgy= true; continue; } tmp_fanout= cur_fanout; } else { rec_per_key_t rec_per_key; if (keyinfo->has_records_per_key( keyinfo->user_defined_key_parts - 1)) rec_per_key= keyinfo->records_per_key(keyinfo->user_defined_key_parts - 1); else rec_per_key= rec_per_key_t(tab->records()) / distinct_keys_est + 1; if (tab->records() == 0) tmp_fanout= 0.0; else if (rec_per_key / tab->records() >= 0.01) tmp_fanout= rec_per_key; else { const double a= tab->records() * 0.01; if (keyinfo->user_defined_key_parts > 1) tmp_fanout= (cur_used_keyparts * (rec_per_key - a) + a * keyinfo->user_defined_key_parts - rec_per_key) / (keyinfo->user_defined_key_parts - 1); else tmp_fanout= a; set_if_bigger(tmp_fanout, 1.0); } cur_fanout= (ulong) tmp_fanout; } if (ref_or_null_part) { // We need to do two key searches to find key tmp_fanout*= 2.0; cur_fanout*= 2.0; } if (table->quick_keys.is_set(key) && table->quick_key_parts[key] <= cur_used_keyparts && const_part & ((key_part_map)1 << table->quick_key_parts[key]) && table->quick_n_ranges[key] == 1 + MY_TEST(ref_or_null_part & const_part) && cur_fanout > (double) table->quick_rows[key]) { tmp_fanout= cur_fanout= (double) table->quick_rows[key]; } } ······ ······ // Limit the number of matched rows const double tmp_fanout= min(cur_fanout, (double) thd->variables.max_seeks_for_key); if (table->covering_keys.is_set(key) || (table->file->index_flags(key, 0, 0) & HA_CLUSTERED_INDEX)) { // We can use only index tree const Cost_estimate index_read_cost= table->file->index_scan_cost(key, 1, tmp_fanout); cur_read_cost= prefix_rowcount * index_read_cost.total_cost(); } else if (key == table->s->primary_key && table->file->primary_key_is_clustered()) { const Cost_estimate table_read_cost= table->file->read_cost(key, 1, tmp_fanout); cur_read_cost= prefix_rowcount * table_read_cost.total_cost(); } else cur_read_cost= prefix_rowcount * min(table->cost_model()->page_read_cost(tmp_fanout), tab->worst_seeks); handler.cc【用于range访问类型索引费用计算】 handler::multi_range_read_info_const(uint keyno, RANGE_SEQ_IF *seq, void *seq_init_param, uint n_ranges_arg, uint *bufsz, uint *flags, Cost_estimate *cost) { KEY_MULTI_RANGE range; range_seq_t seq_it; ha_rows rows, total_rows= 0; uint n_ranges=0; THD *thd= current_thd; /* Default MRR implementation doesn't need buffer */ *bufsz= 0; DBUG_EXECUTE_IF("bug13822652_2", thd->killed= THD::KILL_QUERY;); seq_it= seq->init(seq_init_param, n_ranges, *flags); while (!seq->next(seq_it, &range)) { if (unlikely(thd->killed != 0)) return HA_POS_ERROR; n_ranges++; key_range *min_endp, *max_endp; if (range.range_flag & GEOM_FLAG) { min_endp= &range.start_key; max_endp= NULL; } else { min_endp= range.start_key.length? &range.start_key : NULL; max_endp= range.end_key.length? &range.end_key : NULL; } int keyparts_used= 0; if ((range.range_flag & UNIQUE_RANGE) && // 1) !(range.range_flag & NULL_RANGE)) rows= 1; /* there can be at most one row */ else if ((range.range_flag & EQ_RANGE) && // 2a) (range.range_flag & USE_INDEX_STATISTICS) && // 2b) (keyparts_used= my_count_bits(range.start_key.keypart_map)) && table-> key_info[keyno].has_records_per_key(keyparts_used-1) && // 2c) !(range.range_flag & NULL_RANGE)) { rows= static_cast<ha_rows>( table->key_info[keyno].records_per_key(keyparts_used - 1)); } else { DBUG_EXECUTE_IF("crash_records_in_range", DBUG_SUICIDE();); DBUG_ASSERT(min_endp || max_endp); if (HA_POS_ERROR == (rows= this->records_in_range(keyno, min_endp, max_endp))) { /* Can't scan one range => can't do MRR scan at all */ total_rows= HA_POS_ERROR; break; } } total_rows += rows; } if (total_rows != HA_POS_ERROR) { const Cost_model_table *const cost_model= table->cost_model(); /* The following calculation is the same as in multi_range_read_info(): */ *flags|= HA_MRR_USE_DEFAULT_IMPL; *flags|= HA_MRR_SUPPORT_SORTED; DBUG_ASSERT(cost->is_zero()); if (*flags & HA_MRR_INDEX_ONLY) *cost= index_scan_cost(keyno, static_cast<double>(n_ranges), static_cast<double>(total_rows)); else *cost= read_cost(keyno, static_cast<double>(n_ranges), static_cast<double>(total_rows)); cost->add_cpu(cost_model->row_evaluate_cost( static_cast<double>(total_rows)) + 0.01); } return total_rows; } 验证公式 创建验证需要的表 CREATE TABLE `store_goods_center` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id', `sku_id` bigint(20) NOT NULL COMMENT '商品skuid', `station_no` varchar(20) NOT NULL COMMENT '门店编号', `org_code` bigint(20) NOT NULL COMMENT '商家编号', `extend_field` text COMMENT '扩展字段', `version` int(11) DEFAULT '0' COMMENT '版本号', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `create_pin` varchar(50) DEFAULT '' COMMENT '创建人', `update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间', `update_pin` varchar(50) DEFAULT '' COMMENT '更新人', `yn` tinyint(4) DEFAULT '0' COMMENT '删除标示 0:正常 1:删除', `ts` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '时间戳', PRIMARY KEY (`id`), UNIQUE KEY `uniq_storegoods` (`station_no`, `sku_id`) USING BTREE, KEY `idx_storegoods_org` (`org_code`, `sku_id`, `station_no`), KEY `idx_sku_id` (`sku_id`), KEY `idx_station_no_and_id` (`station_no`, `id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='门店商品关系表'; 通过存储过程初始化测试数据 DELIMITER // CREATE PROCEDURE callback() BEGIN DECLARE num INT; SET num = 1; WHILE num <= 100000 DO INSERT INTO store_goods_center(sku_id, station_no, org_code) VALUES (num + 10000000, floor(50+rand()*(100-50+1)), num); SET num = num + 1; END WHILE; END; 执行存储过程生成数据 CALL callback(); 1.全表扫描计算代价公式 计算过程: // 不同引擎计算方式有所区别 // innodb引擎实现handler.h // 预估记录数:ha_innobase::info_low // 页数量:ha_innobase::scan_time【数据总大小(字节) / 页大小】 // 查询全表数据大小(7880704) SHOW TABLE STATUS LIKE 'store_goods_center'; // 查询数据库页大小(默认:16384) SHOW VARIABLES LIKE 'innodb_page_size'; // 全表扫描计算代价 // 页数量 page = 数据总大小(字节) / 页大小 = 7880704 / 16384 = 481; // 预估范围行数(总数据条数:10万,预估数据条数:99827,有一定误差) records = 99827; // 计算总代价 // 481 * 1 中的系数1 代表从主内存缓冲池读取块的成本(SE_cost_constants::IO_BLOCK_READ_COST= 1.0) // 99827 * 0.2 中的系数0.2 代表计算符合条件的⾏的代价(ROW_EVALUATE_COST= 0.2) cost = IO-cost + CPU-cost = (481 * 1) + (99827 * 0.2) = 481 + 19965.4 = 20446.4 验证结果: explain format = json select * from store_goods_center; "cost_info": {"query_cost": "20446.40"} 总结公式: 全表扫描代价 = 数据总大小 / 16384 + 预估范围行数 * 0.2 2.覆盖索引扫描计算代价公式 计算过程: // 查询全表数据大小(7880704) SHOW TABLE STATUS LIKE 'store_goods_center'; // 查询数据库页大小(默认:16384) SHOW VARIABLES LIKE 'innodb_page_size'; // 预估范围行数(总数据条数:1999,预估数据条数:1999,有一定误差) 1999; records = 1999 // keys_per_block计算 // block_size是文件的block大小,mysql默认为16K; // key_len是索引的键长度; // ref_len是主键索引的长度; keys_per_block = (stats.block_size / 2 / (table_share->key_info[keynr].key_length + ref_length) + 1); // table_share->key_info[keynr].key_length 为联合索引,分别是station_no和sku_id // station_no 为varchar(20)且为utf8mb4,长度 = 20 * 4 + 2 (可变长度需要加2) = 82 // sku_id bigint类型,长度为8 // 主键索引为bigint类型,长度为8 keys_per_block = 16384 / 2 / (82 + 8 + 8) + 1 ≈ 84 // 计算总代价 read_time = ((double) (records + keys_per_block - 1) / (double) keys_per_block); read_time = (1999 + 84 - 1) / 84 = 24.78; // 计算总代价 // 24.78 * 1 中的系数1 代表从主内存缓冲池读取块的成本(SE_cost_constants::IO_BLOCK_READ_COST= 1.0) // 1999 * 0.2 中的系数0.2 代表计算符合条件的⾏的代价(ROW_EVALUATE_COST= 0.2) cost = IO-cost + CPU-cost = (24.78 * 1) + (1999 * 0.2) = 24.78 + 399.8 = 424.58 验证结果: explain format = json select station_no from store_goods_center where station_no = '53'; "cost_info": {"query_cost": "424.58"} 总结公式: keys_per_block = 8192 / 索引长度 + 1 覆盖索引扫描代价 = (records + keys_per_block - 1) / keys_per_block + 预估范围行数 * 0.2 公式简化(去除影响较小的复杂计算) 覆盖索引扫描代价 = (records * 涉及索引长度) / 8192 + 预估范围行数 * 0.2 3.ref索引扫描计算代价公式 计算过程: // cardinality = 49(基数,即有多少个不同key统计。) SHOW TABLE STATUS LIKE 'store_goods_center'; // 页数量 page = 数据总大小(字节) / 页大小 = 7880704 / 16384 = 481; // 计算代价最低索引(sql_planner.cc 中find_best_ref函数) // IO COST最坏不会超过全表扫描IO消耗的3倍(或者总记录数除以10) // 其中s->found_records表示表上的记录数,s->read_time在innodb层表示page数 // s-> worst_seeks = min((double) s -> found_records / 10, (double) s -> read_time * 3); // cur_read_cost= prefix_rowcount * min(table->cost_model() -> page_read_cost(tmp_fanout), tab -> worst_seeks); // 预估范围行数(总数据条数:10万,预估数据条数:99827,有一定误差) total_records = 99827; // 预估范围行数(总数据条数:1999,预估数据条数:1999,有一定误差) 1999; records = 1999 // 计算总代价 // 1999 * 0.2 中的系数0.2 代表计算符合条件的⾏的代价(ROW_EVALUATE_COST= 0.2) // s-> worst_seeks = min((double) s -> found_records / 10, (double) s -> read_time * 3) -> min(99827 / 10, 481 * 3) = 481 * 3 // min(table->cost_model() -> page_read_cost(tmp_fanout), tab -> worst_seeks) -> min(page_read_cost(1999), 481 * 3) = 481 * 3 cost = IO-cost + CPU-cost = 481 * 3 + (1999 * 0.2) = 1443 + 399.8 = 1842.80 验证结果: explain format = json select * from store_goods_center where station_no = '53'; "cost_info": {"query_cost": "1842.80"} 总结公式: 下面3个公式,取值最低的 1.(数据总大小 / 16384) * 3 + 预估范围行数 * 0.2 2.总记录数 / 10 + 预估范围行数 * 0.2 3.扫描出记录数 + 预估范围行数 * 0.2 4.range索引扫描计算代价公式 // 预估范围行数(总数据条数:1299,预估数据条数:1299,有一定误差) 1299; records = 1299 // 计算代价最低索引(handler.cc 中 multi_range_read_info_const 函数) // 计算总代价 // 1299 * 0.2 计算公式:cost_model->row_evaluate_cost(static_cast<double>(total_rows)) // + 0.01 计算公式:cost->add_cpu(cost_model->row_evaluate_cost(static_cast<double>(total_rows)) + 0.01); // 1299 + 1 中的 +1 :单个扫描区间( id > 35018 ) // 1299 + 1 计算公式:*cost= read_cost(keyno, static_cast<double>(n_ranges), static_cast<double>(total_rows)); // (1299 * 0.2 + 0.01 + 1299) * 1 中的系数1 代表从主内存缓冲池读取块的成本(SE_cost_constants::IO_BLOCK_READ_COST= 1.0) // 1299 * 0.2 中的系数0.2 代表计算符合条件的⾏的代价(ROW_EVALUATE_COST= 0.2) cost = IO-cost + CPU-cost = ((1299 * 0.2 + 0.01 + 1299 + 1) * 1) + (1299 * 0.2) = 1559.81 + 259.8 = 1819.61 验证结果: explain format = json select * from store_goods_center where station_no = '53' and id > 35018; "cost_info": {"query_cost": "1819.61"} 总结公式: range扫描代价 = 预估范围行数 * 1.4 + 0.01 + 范围数 公式简化(去除影响较小的复杂计算) range扫描代价 = 预估范围行数 * 1.4 索引冲突案例 门店商品系统中主要存储门店与商品的关联信息,并为B端提供根据门店ID查询关联商品的功能。由于门店关联的商品数据量较大,需要分页查询关联商品数据。为避免深分页问题,我们选择基于上次最新主键进行查询(核心思想:通过主键索引,每次定位到ID所在位置,然后往后遍历N个数据。这样,无论数据量多少,查询性能都能保持稳定。我们将所有数据根据主键ID进行排序,然后分批次取出,将当前批次的最大ID作为下次查询的筛选条件)。 select 字段1,字段2 ... from store_goods_center where station_no = ‘门店id’ and id > 上次查询最大id order by id asc 为了确保门店与商品组合的唯一性,我们在MySQL表中为门店ID和商品ID添加了组合唯一索引【UNIQUE KEY uniq_storegoods (station_no, sku_id) USING BTREE】。由于该索引包含门店ID并且在联合索引的第一个位置,查询会使用该索引。但是,当分页查询命中该索引后,由于排序字段无法使用索引,产生了【Using filesort】,导致门店商品系统出现了一些慢查询。为了解决这个问题,我们对慢查询进行了优化,优化思路是创建一个新的索引,使该SQL可以使用索引的排序来规避【Using filesort】的负面影响,新添加的索引为【KEY idx_station_no_and_id (station_no, id)】。添加该索引后,效果立竿见影。 然而,我们发现仍然有慢查询产生,并且这些慢查询仍然使用uniq_storegoods索引,而不是idx_station_no_and_id索引。我们开始思考,为什么MySQL没有为我们的系统推荐使用最优的索引?是MySQL索引推荐有问题,还是我们创建索引有问题?如何做才能让MySQL帮我们推荐我们认为最优的索引? 当然,我们也可以使用FORCE INDEX强行让MySQL走我们提前预设的索引,但是这种方式局限太大,后期索引维护成本变得很高,甚至可能使用该SQL的其他业务性能变低。为了突破整体优化的卡点状态,我们需要了解一下MySQL索引推荐底层逻辑,即MySQL代价模型。了解相应规则后,现阶段的问题将迎刃而解。 案例分析及优化 在回顾刚才的问题时,我们发现问题源于原始索引产生了【Using filesort】,从而导致了慢查询的出现。为了解决这个问题,我们新增了一个索引,即【KEY idx_station_no_and_id (station_no, id)】,以替代原有的索引【UNIQUE KEY uniq_storegoods (station_no, sku_id)】。然而,尽管新增索引后大部分慢查询得到了解决,但仍有部分慢查询未能消除。进一步分析发现,这些慢查询是由于SQL没有使用我们期望的索引,而是使用了老索引,从而引发了【Using filesort】问题。在通过explain进行分析后,我们暂时还没有找到合适的解决方案。 问题:尽管我们新增了索引,并且大部分SQL已经能够使用新索引进行优化,但仍存在一些SQL没有使用新索引。 // 通过代价模型进行分析 // 使用上面的测试数据进行分析 // 新增索引后都没有走新索引 // 老索引,扫描行数:1999,代价计算值:1842.80,ref类型索引 // 新索引,扫描行数:1999,代价计算值:1850.46,range类型索引 select 字段1,字段2 ... from store_goods_center where station_no = ‘门店id’ and id > -1 order by id asc; // 新增索引后走新索引 // 老索引,扫描行数:1999,代价计算值:1842.80,ref类型索引 // 新索引,扫描行数:1299,代价计算值:1819.61,range类型索引 select 字段1,字段2 ... from store_goods_center where station_no = ‘门店id’ and id > 35018 order by id asc; 经过分析MySQL的代价模型,我们发现MySQL在选择使用哪个索引时,主要取决于扫描出的数据条数。具体来说,扫描出的数据条数越少,MySQL就越倾向于选择该索引(由于MySQL的索引数据访问类型各异,计算公式也会有所不同。因此,在多个索引的扫描行数相近的情况下,所选索引可能与我们期望的索引有所不同)。顺着这个思路排查,我们发现当id > -1时,无论是使用storeId + skuId还是storeId + id索引进行查询,扫描出的数据条数是相同的。这是因为这两种查询方式都是根据门店查询商品数据,且id值肯定大于1。因此,对于MySQL来说,由于这两种索引扫描出的数据条数相同,所以使用哪种索引效果相差不多。这就是为什么一部分查询走新索引,而另一部分查询走老索引的原因。然而,当查询条件为id > n时,storeId + id索引的优势便得以显现。因为它能够直接从索引中扫描并跳过id <= n的数据,而storeId + skuId索引却无法直接跳过这部分数据,因此真正扫描的数据条数storeId + skuId要大于storeId + id。因此,在查询条件为id > n时,MySQL更倾向于使用新索引。(需要注意的是,示例给出的数据索引数据访问类型不同,一个是range索引类型,一个是ref索引类型。由于算法不同,即使某个索引的检索数据率略高于另一个索引,也可能导致系统将其推荐为最优索引) 问题已经分析清楚,主要原因是存在多个索引,且根据索引代价计算公式的代价相近,导致难以抉择。因此,解决这个问题的方法不应该是同时定义两个会让MySQL"纠结"的索引选择。相反,应该将两个索引融合为一个索引。具体的解决方案是根据门店查询,将原来的主键id作为上次查询的最大id替换为skuId。在算法切换完成后,删除新的门店+主键id索引。然而,这种方式可能会引发另一个问题。由于底层排序算法发生了变化(由原来的主键id改为skuId),可能导致无法直接从底层服务切换。此时,应考虑从下游使用此接口服务的应用进行切换。需要注意的是,如果下游系统是单机分页迭代查询门店数据,那么下游系统可以直接进行切换。但如果这种分页查询动作同时交给多台应用服务器执行,切换过程将变得相当复杂,他们的切换成本与底层切换成本相同。但是,这个系统的对外服务属于这种情况,下游调用系统会有多台应用服务器协作分页迭代查询数据,为这次优化带来很大影响。 最终,让底层独立完成切换方式最为合适。在切换过程中,关键在于正确区分新老算法。老算法在迭代过程中不应切换至新算法。原系统对外服务提供的下次迭代用的id可用来进行区分。新算法在返回下次迭代用的id基础上增加一个常量值,例如10亿(加完后不能与原数据冲突,也可以将迭代id由整数转换成负数以区分新老算法)。因此,如果是第一次访问,直接使用新算法;如果不是第一次访问,需要根据下次迭代用的id具体规则来判断是否切换新老算法。 总结与后续规划 使用Explan执行计划存在无法提前预知索引选择的局限性。然而,只要熟悉MySQL底层代价模型的计算公式,我们就能预知索引的走向。借助代价模型,我们不仅可以分析索引冲突的原因,还可以在发生冲突之前进行预警。甚至在添加索引之前,我们也可以根据代价模型公式来排查潜在问题。此外,根据数据业务密度,我们还可以预估当前索引的合理性,以及是否可能出现全表扫描等情况。因此,深入研究MySQL代价模型对于优化索引管理具有关键意义。 未来我们的系统应用将结合MySQL代价模型进行集成,实现自动分析数据库和表的信息,以发现当前索引存在的问题,例如索引冲突或未使用索引导致的全表扫描。此外,该工具还可以针对尚未添加索引的表,根据数据情况提供合适的索引推荐。同时,该工具还能够预测当数据达到某种密度时,可能出现全表扫描的问题,从而帮助提前做好优化准备。 为了实现这些功能,我们将首先对MySQL代价模型进行深入研究,全面了解其计算公式和原理。这将有助于我们编写相应的算法,自动分析数据库和表的信息,找出潜在的索引问题。此外,我们还关注易用性和实用性,确保用户能够轻松地输入相关数据库和表的信息,并获取有关优化建议。 该工具的开发将有助于提高数据库性能,减少全表扫描的发生,降低系统资源消耗。同时,它还可以为数据库管理员和开发人员提供便利,使他们能够更加专注于其他核心业务。通过结合MySQL代价模型,我们相信这个工具将在优化索引管理方面发挥重要作用,为企业带来更高的效益。 参考资料 https://github.com/mysql/mysql-server 作者:京东零售 王多友 来源:京东云开发者社区 转载请注明来源

优秀的个人博客,低调大师

简洁实用,告别繁琐,解锁云端相册新体验——LSKY PRO

云相册顾名思义专业提供手机相片存储服务。 特点有自动上传、自动同步、轻松分享。 设备在经过用户的允许后,用户在手机上拍摄的照片上传到云相册。 用户就能随时随地在云相册上看到用户手机拍摄的照片。 应用简览 Lsky Pro,中文名兰空图床,是一款强大的在线图片上传和管理工具,可用作个人云上相册,同时也是写作和创作过程中的绝佳图片库。这令人印象深刻的图床项目首次亮相于2017年10月,起初采用ThinkPHP 5作为开发平台。经过多次版本升级和改进,兰空图床在2021年末启动了一项全新的重写计划,并于2022年3月发布了备受期待的2.0版本。其引人注目的特点包括简洁易用的界面、丰富的功能集合,以及快速的图片上传和管理能力。这些特性为项目赢得了广泛的认可,兰空图床在GitHub上拥有近3.4k的星标,证明了其受欢迎程度。 主要功能 支持本地等多种第三方云储存 AWS S3、阿里云 OSS、腾讯云 COS、七牛云、又拍云、SFTP、FTP、WebDav、Minio 多种数据库驱动支持,MySQL 5.7+、PostgreSQL 9.6+、SQLite 3.8.8+、SQL Server 2017+ 支持配置使用多种缓存驱动,Memcached、Redis、DynamoDB、等其他关系型数据库,默认以文件的方式缓存 多图上传、拖拽上传、粘贴上传、动态设置策略上传、复制、一键复制链接 强大的图片管理功能,瀑布流展示,支持鼠标右键、单选多选、重命名等操作 自由度极高的角色组配置,可以为每个组配置多个储存策略,同时储存策略可以配置多个角色组 可针对角色组设置上传文件、文件夹路径命名规则、上传频率限制、图片审核等功能 支持图片水印、文字水印、水印平铺、设置水印位置、X/y 轴偏移量设置、旋转角度等 支持通过接口上传、管理图片、管理相册 支持在线增量更新、跨版本更新 图片广场 应用特色 一、"直观控制台" - Lsky Pro 提供基本信息一目了然 Lsky Pro的控制台以直观的方式呈现基础信息,让用户能够迅速了解系统状态和重要数据。这个用户友好的界面帮助用户轻松管理和监控其图床系统,确保一切正常运行。 二、"多元存储选择" - Lsky Pro 提供多种存储选项 Lsky Pro为用户提供了多种存储方式的选择,包括本地存储,以及第三方云存储服务如AWS S3、阿里云 OSS、腾讯云 COS、七牛云、又拍云、SFTP、FTP、WebDav和Minio。这意味着您可以根据需要轻松地将您的图片和文件保存在不同的存储环境中,以满足各种需求和场景。无论是寻求高度可定制性还是极致便捷,Lsky Pro都能满足您的存储需求。 三、"多样化图片管理功能" - Lsky Pro 提供丰富的图片处理选项 Lsky Pro 提供了强大的图片管理功能,包括图片水印、文字水印、水印平铺、自定义水印位置、X/Y轴偏移量设置、图像旋转等多项功能。此外,它还支持瀑布流展示,允许鼠标右键操作、单选和多选功能,以及文件重命名等操作。这一系列功能使您能够轻松处理和管理您的图片资源,满足不同的图像处理需求,为您的创作和展示提供更多选择和便捷操作。 四、"灵活的角色权限管理" - Lsky Pro 允许详细的权限配置 Lsky Pro 提供了高度自定义的角色权限配置,允许为每个角色组定义多个储存策略,同时,储存策略也可以应用于多个角色组。这意味着您可以精细地控制不同用户组的权限和访问策略。此外,您可以根据角色组的需求设置上传文件和文件夹路径的命名规则,限制上传频率,甚至开启图片审核等功能。这种角色权限的精细化配置确保您能够满足各种安全性和管理需求,确保数据的可控性和安全性。 五、"广泛数据库兼容性" - Lsky Pro 支持多种数据库操作 Lsky Pro 提供了多种数据库驱动支持,包括 MySQL 5.7+、PostgreSQL 9.6+、SQLite 3.8.8+ 以及 SQL Server 2017+。这意味着您可以根据自己的数据库偏好和要求,轻松地将 Lsky Pro 集成到各种数据库环境中。不管您选择哪种数据库,Lsky Pro 都能提供可靠的数据操作支持,确保您能够高效管理和检索您的图像和文件数据。 安装指南 进入云原生应用商店 搜索 Lsky Pro 进入详情,选择包类型(本应用支持,docker安装,ram安装) 点击安装,执行相应命令即可。如有疑问可参阅使用文档 或加入社区 关于云原生应用市场 云原生应用市场是一个汇聚了各类开源软件的应用市场,不仅可以作为你自己的 Helm Chart 仓库,提供丰富多样的Helm应用,还有 Docker 应用、Rainbond 应用模板、信创应用等多种选择。 官网:https://hub.grapps.cn/ 微信群:关注 云原生应用市场 公众号加入技术交流群

优秀的个人博客,低调大师

Kafka ETL 的应用及架构解析|告别 Kafka Streams,让轻量级流处理更加简单

作者:竹恩、岁月、不周 关键词:Kafka ETL,高弹性、免运维、低成本 引言: 阿里云消息队列 Kafka 版提供兼容 Apache Kafka 生态的全托管服务,彻底解决开源产品长期的痛点,是大数据生态中不可或缺的产品之一。随着 Kafka 越来越流行,最初只是作为简单的消息总线,后来逐渐成为数据集成系统,Kafka 可靠的传递能力让它成为流式处理系统可靠的数据来源。在大数据工程领域,Kafka 在承接上下游、串联数据流管道方面发挥了重要作用,Kafka 应用流式框架处理消息也逐渐成为趋势。 消息流处理框架选型 说到流计算,常用的便是 Storm、Spark Streaming 和 Flink,目前这些框架都已经完美的支持流计算,并且都有相应的使用案例,但这些框架使用起来门槛相对较高,首先要学习框架和各种技术、规范的使用,然后要将业务迁移到这些框架中,最后线上使用、运维这些流计算框架,对于简单的流处理应用来说,可能较为复杂。 在与传统流处理系统对接中,由于所有的数据基础都要从一个系统流入 Kafka 然后再流入到另一个系统中,以至于引发 Kafka 社区的思考:与其把数据从一个系统传递到下一个系统中做处理,为何不自己实现一套流处理框架呢?基于这个考量,从 0.10.0 版本开始,Kafka 不仅为每一个流行的流式处理框架提供了可靠的数据来源,还提供了一个强大的流式处理类库 Kafka Streams,并将其作为客户端类的一部分。这样,开发人员就可以在应用程序里读取、处理和生成事件,而不需要再依赖外部的处理框架。 但由于 Kafka Streams 本身是一个 Java 客户端库,需要开发人员自行打包和部署;同时 Kafka Streams 是开源版本,可靠性和可用性不能得到很好的保障,也不能实现按需使用;此外使用过程中需要用到流的编程,使用的门槛也相对较高。 消息流处理框架主要面临的问题 通过前面对常见的消息流处理的介绍,不论是传统的流处理架构还是 Kafka Streams,对于开发人员来说都会面临一些问题,尤其是在面对 70%以上简单流场景的需求,原有的方案弊端被不断放大,客户仍然需要投入较大的人力成本和较高的资源,同时整个架构也很复杂。总体来说,目前面临主要是四个方面的问题: 1、运维成本较大,研发团队自行编写代码,后期持续维护,运维成本较大; 2、技术成本较大,对于很多轻量或简单计算需求,需要进行技术选型,引入一个全新组件的技术成本过高; 3、学习成本不可预期,在某组件选定后,需要研发团队进行学习并持续维护,这就带来了不可预期的学习成本; 4、自行选用开源组件后,可靠性和可用性不能得到有效保证。 面对这些问题,阿里消息队列 Kafka 也推出了相应的解决方案:Kafka ETL。 阿里云的解决方案 - Kafka ETL Kafka ETL 简介 阿里云消息队列 Kafka 版推出更低成本的 Kafka –ETL 组件,是一款免运维的流计算组件,主要特性是支持配置化流式处理消息。Kafka ETL 组件主要提供的是非时间窗口相关的流计算服务,客户可以配置,甚至简单写入几行代码就能满足包括格式转换、内容富化、本地聚合、路由分发等常用的数据处理需求。 Kafka ETL 在使用上拆分成有向无环图,在计算节点转换时,把 Topic 作为一个存储,在 Topic 里进行有状态的计算,还可以支持消息的转储。 目前 Kafka ETL 已支持的模版包括: 1)数据清洗:规则过滤; 2)转换模版:字符串替换,添加前后缀、字符串大小写转换、空格去除数; 3)数据富化模版:数据富化; 4)Split 模版:Topic Split; 5)路由模版:Topic 路由。 Kafka ETL 优势 通过对 Kafka ETL 基础应用及功能的介绍可以看到,相比于 Storm、Spark Streaming、Flink、Kafka Streams,Kafka ETL 的优势主要体现在以下四个方面: 1)开箱即用,免运维; 2)节省成本,不用额外购买其他流计算产品,目前 Kafka ETL 仍处于公测免费阶段; 3)低代码,支持快速上线,学习成本低,一站式体验,技术投入小,时间成本节省 80%; 4)便于监控排查,控制台上相关日志信息比较全面。 Kafka ETL 操作 通过以上 Kafka ETL 应用和优势的介绍可以看到 Kafka ETL 在使用中的具备轻量、低成本等特性,不仅如此,Kafka ETL 的操作也比较简单,仅需三步便可完成 ETL 操作。 1)第一步:创建任务 选择 Kafka 来源实例、来源 Topic,以及对应的选择 Kafka 目标实例、目标 Topic。并配置消息初始位置、失败处理以及创建资源方式。 2)第二步:编写 ETL 主逻辑 选择 Python 3 作为函数语言。这里提供了多种数据清洗、数据转化模板,比如规则过滤、字符串替换、添加前/后缀等常用函数。 3)第三步:设置任务运行、异常参数配置,并执行 Kafka ETL 应用场景 基于 Kafka ETL 的功能和优势,目前 Kafka ETL 主要应用在下面这些场景中: 1)转储场景,支持格式化数据,以方便数据进行转储; 2)流式处理场景,流式计算,支持消息的流式处理,主要提供的是非时间窗口相关的流计算服务; 3)实时行为计算场景,包括风控,金融,电商等需要实时行为计算场景; 4)还支持其他一些场景,包括实时报表,自动化运营场景等。 Kafka ETL 的架构解析 通过前三部分介绍,想必大家对阿里云 Kafka ETL 有了一定了解,本节的主要内容是对 Kafka ETL 的架构进行解析,帮助大家对 Kafka ETL 有更深入的理解。Kafka ETL 是基于 Kafka connect + 函数计算,为云上的用户提供一套数据流转和计算的一站式解决方案。 在当今的大数据、云计算时代,一个复杂的大型系统一般都会由许多处理特定任务的子系统构成。各个子系统一般会由不同的团队开发,因此,各系统中的数据在内容和格式上,存在天然的不一致性。当数据在各个子系统之间流转的时候,需要对数据进行格式处理,以消除各系统数据之间格式的不同。此外,还可能需要收集来自各个子系统中的异构数据,对采集到的数据做一些加工和计算,然后投递到数据仓库进行后续的数据分析。在这个过程中,可以抽象出两个典型场景:数据流转场景和数据计算场景。 数据流转场景主要面对的问题是,异构系统间数据如何流转? 数据计算场景主要面对的问题是,如何在数据流转过程中,进行数据的加工计算? 下面就展开对这两个主要场景进行介绍。 数据流转场景 在数据流转场景中,可能需要将各种关系型和非关系型数据库中的数据导入到数据仓库;或是将 mysql 的数据导入到 ElasticSearch,用来提高查询体验;此外一些数据还会导入到图形数据库。这些场景面临的主要问题是: 1)各种不同源之间的数据如何拷贝; 2)如何满足传递的实时性。 比如,mysql 里的一个变更,希望马上能在 ElasticSearch 中反映出来,不然就会导致后台数据变更了,用户却查不出最新的数据。除此之外,还需要保证数据拷贝的高可用、可伸缩性以及可扩展性。 为应对这一问题,传统的方案可能是:为各数据源之间都专门做一个数据拷贝工具。这种方案会带来以下问题: 1)首先是工作量问题,需要为每个场景都写一个专门的工具,工作量会非常大; 2)业务耦合严重,比如想监听价格变化,就需要在所有变化价格的业务里,都加上一个 producer。假设上层 schema 发生了变化,下层就需要修改代码,因此上层需要感知到所有下层的存在。 专门的工具看起来不太可行,那么,是否做一个完全通用的工具,让它支持任意数据源之间数据拷贝。这个听起来不错,但是实际却不可行,正因为它要求太通用了,很难去制定各种规范。 Kafka connect 正是为解决以上异构数据同步问题而生的。它解决的思路是在各个数据源之间加一层消息中间件,所有的数据都经过消息中间件进行存储和分发。这样做的好处有: 1)通过消息中间件做异步解耦,所有系统只用和消息中间件通信; 2)需要开发的解析工具数量,也从原来的 n 平方个,变成线性的 2*n 个。 Kafka connect 则用于连接消息系统和数据源,根据数据的流向不同,连接可以分为 source connector 和 sink connector。其原理也很简单,souce connector 负责解析来源数据,转换成标准格式的消息,通过 Kafka producer 发送到 Kafka broker 中。同理,sink connector 则通过 Kafka consumer 消费对应的 Topic,然后投递到目标系统中。在整个过程中,Kafka connect 统一解决了任务调度、与消息系统交互、自动扩缩容、容错以及监控等问题,大大减少了重复劳动。但是,如何将来源系统的数据解析成 message、或是将 message 解析成目标系统数据,这两件事情是需要根据不同的数据系统而做不同实现的。对于目前主流的系统,各大厂商均有提供相应的 connector 实现。 阿里云消息队列 Kafka 版就提供了全托管、免运维的 Kafka Connect,用于消息队列 Kafka 版和其他阿里云服务之间的数据同步。可以看到消息队列 Kafka 版支持了 Mysql source connector、OSS sink connector、MaxCompute sink connector 以及 FC sink connector 等主流的 connector。如果用户想要使用这些 connector 进行数据同步,只用在消息队列 Kafka 控制台的图形界面上做几个配置,就可以一键拉起 connector 任务。 数据计算场景 Kafka connect 解决了异构数据源之间数据同步的问题,虽然也提供了 transformer,解决部分数据转换需求,但是依旧缺乏实时计算能力。为应对以上场景的数据实时处理需求,市场上出现了许多优秀的处理工具,从最初的 Hadoop,Hive 到 Spark,Flink 以及 Kafka streams 等,都提供了对应的组件模块和上下游解决方案。 但这些处理方案中都存在或多或少的问题,主要的问题是: 首先处理框架比较重,占用资源多。比如当下流行的 Spark 和 Flink 都需要先搭建一个集群,集群本身运行起来就要不少资源。集群规模一般按照流量峰值配置,在大多数时候,资源是浪费的。 其次在诸多框架中,需要根据实际需求做技术选型,后期可能需要专门的团队或者人去运维,这个过程需要较大的学习成本和后期维护成本。 针对部分无状态的简单计算,函数计算或许是一个很好的选择。阿里云上的函数计算,是事件驱动的全托管计算服务。使用函数计算时,用户无需采购与管理服务器等基础设施,只需编写并上传代码即可。函数计算会帮助用户准备好计算资源,弹性地、可靠地运行任务,并提供日志查询、性能监控和报警等功能。可以看到,函数计算以简单易用的方式给用户的许多场景提供了计算能力。 阿里云消息队列 Kafka 版近期推出的 Kafka ETL 组件,通过 Kafka+Kafka connect+函数计算的架构,能够很好的应对数据转储+实时计算问题。具有轻量,学习成本低,开发周期短,资源动态伸缩,简单快速等优点。 Kafka+Kafka connect+函数计算的云原生数据应用解决方案,通过 Kafka connect 作为实时处理任务触发器,能够实时接收到新发送到消息队列集群的数据,然后转发到函数计算,触发实时数据处理任务的运行。在这个数据流转阶段,将大量异构系统中的数据以各种方式汇集到 Kafka 中,然后围绕 Kafka 为中心,做后续的处理。作为后续数据流转中的一环,Kafka connect 除了保障数据的实时性以外,还解决了任务调度、与消息系统交互、自动扩缩容、容错以及监控等问题,大大减少了重复劳动。数据到了函数计算以后,会自动触发用户自己编写的数据处理逻辑,对原始消息内容进行计算。最后,函数计算可以将加工完成的数据,投递到用户指定的目标端,例如投递回消息队列 Kafka,或者是投递到 Max compute 进行下一步的数据分析。以上所说的整个任务的配置、创建、运行,都只用通过云上的 Kafka 控制台图形页面进行操作即可完成。 应用场景详解 接下来一起来看一个 Kafka ETL 的应用示例。在这个示例中,用户的一个大致使用场景是这样的:从一个电商业务系统中,采集日志,存储到 Kafka 侧,然后需要对日志数据进行加工,最后将加工好的数据投递到两个目标端:一个是投递到 MaxCompute 进行数据分析;另一个是投递到 ElasticSearch 进行日志检索。 现在分节点来看,如何利用消息队列 Kafka 版来做这个事情: 1)第一步:采集原始日志到消息队列 Kafka 版的 Topic 中 这里可以使用一些比较成熟的开源组件例如 FileBeat、Logstash、Flume 等,将用户应用端的日志消息,投递到 Kafka 中。一般情况下,这个步骤会将原始的日志信息投递到 Kafka。当然这里也可以做一些简单的转换,但一般不这么做,而是保留一份原始信息,原始的日志可能来自各个关联的应用,内容和格式会存在些许差异。 在这个例子,订单应用中生成一条日志。日志中包含用户 Id、action、订单 Id 以及当前状态: 从支付应用中,又生成一条日志。日志中同样包含以上信息,只是格式上存在一些小差别。 这两条来自不同子系统的日志,都被采集到 Kafka 的一个 Topic,叫做 user_order_raw。这两条日志,最终对应这个 Topic 里的两条消息,key 均为 null,value 为日志的原始内容。可以看到,由于来源系统日志格式不一样,这个 Topic 里包含的这两条消息,消息格式上也存在一定差别。 2)第二步是对 Topic 中的消息,做简单的数据加工计算 数据到达 Kafka 的 Topic 之后,Kafka connect 会消费消息,并投递到函数计算中。数据到了函数计算后,需要对这个数据进行加工计算,计算的目标是抽取 UserId、Action、OrderId 以及 Status,并将数据都转换为大写字母。然后所有处理后的消息发往 MaxCompute 进行分析,此外还需要筛选 Action 为 pay 的所有消息发往 Elastic Search 中。 这个步骤,可以在 Kafka 控制台图形界面上创建 ETL 任务,用户选择数据来源 Topic:user_order_raw,然后写一段对数据的处理代码。这里,ETL 已经提供了部分模板,可以在模板的基础上,做稍许改动即可。 本示例的代码如下图所示。在这个例子中,用户需要写一段从不同格式的日志中,抽取UserId、Action、OrderId 以及 Status 的代码,然后将所有处理过的消息路由到目标 Topic。 3)最后一步,可以将处理完的消息,再次投递到目标端。 函数计算将处理完的消息,投递回 Kafka。经过这一步处理,所有消息被路由到目标Topic:user_order_processed,此时这个 Topic 中会包含两条消息,消息 key 为null,value 如下所示: 另外,Action 为 pay 的消息还会被路由到 Topic: user_order_pay_info 中,此时这个 Topic 会包含一条消息,key 为 null,value 如下所示: 可以看到,此时的消息格式已经统一了。 这个例子中,将 Topic:user_order_processed 中所有处理完的订单相关消息,投递到 MaxCompute 中进行数据分析。将 Topic: user_order_pay_info 中的支付信息,投递到 ElasticSearch 中进行后续搜索。 这一步,可以一键创建相应的 Kafka connect 任务,将数据投递到相应的目标端。 总结一下上述整个过程。在这个示例中,所要做的仅仅是在消息队列 Kafka 控制台上配置一个 ETL 任务,写一小段处理代码即可。上述步骤中,第二步处理完数据之后可以不经过第三步投递回 Kafka,而是在处理完之后,直接路由到 MaxCompute 和 ES 中。在该例子中采用的方式是将处理完的数据再次发送回 Kafka 中,然后再投递到目标系统中。这种方式可以在 Kafka 端保留一份处理后的数据,用户还可以比较灵活地对这份数据做进一步处理或者继续投递到其他第三方系统中。 阿里云消息队列Kafka 版的优势 最后,给大家额外分享一下阿里云上的消息队列 Kafka 在内核层面的差异化优势。阿里云上的消息队列 Kafka 版在发展过程中除了解决易用性和稳定性方面的问题以外,还做到了有区分度,并在内核层面做出自己的核心竞争力和优势。 阿里云消息队列 Kafka 版支持云存储和 Local 存储这两种存储引擎。其中 Local Topic 指的就是以 Kafka 原生的方式存储数据,保留开源 Kafka 全部特性,100%兼容开源 Kafka。云存储是接下来要介绍的重点,消息队列 Kafka 通过自研云存储引擎,彻底解决了原生 Kafka 一些深层的 bug,以及因为本身架构而难以解决的问题,实现了支持海量分区、通过多副本技术降低存储成本,以及支持无缝迁移弹缩性。接下来,将详细介绍这三大特性和其中的技术细节。 支持海量分区 在消息引擎中,常见的消息存储方式有碎片式存储和集中式存储。 碎片式存储通常以 Topic 或者分区纬度存储,其主要优势是架构简单,可以针对 Topic 或者分区,控制持久化的容量。Kafka 在架构上,是基于分区的碎片式存储,在分区规模不大的情况下,可以通过磁盘的顺序读写,获得高效的消息读写性能。通常情况下,一般规格的 Kafka 集群可以支持到千级别的分区规模。如果分区规模持续扩大,且大部分分区都有读写请求时,由于这种设计上的问题,原本的顺序读写就变成了随机读写,从而导致 Kafka 的读写性能急剧下降。 不同于碎片式存储,集中式存储则将所有消息集中存储到同一个 Commit Log,然后根据 Topic 和分区信息构建队列,队列通常作为索引使用。相比于碎片式存储,集中式存储的主要优势是,支持分区数多,很容易通过删除旧的 Commit Log 的形式控制磁盘水位。在阿里云消息队列 Kafka 中,底层的自研云存储引擎正是采用了集中式的存储方式,云存储引擎相比 Kafka 原生的存储的主要优势有: 1)解决了 Kafka 分区规模扩大时,性能急剧下降的问题,相比于原生 Kafka 千级别的分区规模,其支持的分区规模可以达到十万级别; 2) 在大量分区同时写的场景下,相比原生 Kafka 的碎片式存储,自研云存储引擎能获得更好的性能;同时,对写入耗时做了优化,减少了毛刺的产生。 多副本技术优化 为保证 Kafka 集群的高可靠和高可用性,通常情况下会为所有 Topic 设置 3 副本存储。这样,在出现机器宕机时,Kafka 可以快速从可用的 Follower 副本中选出新的 Leader,接替宕机机器上的 Leader 继续提供服务。消息队列 Kafka 在选择块存储设备时,选择的是阿里云上的云盘。云盘是阿里云为云服务器 ECS 提供的,数据块级别的块存储产品,具有低时延、高性能、持久性、高可靠等特点。云盘本身采用了分布式三副本机制,为 ECS 实例提供了极强的数据可靠性和可用性保证。 在这种背景下,在 Kafka 层面设置 3 副本,由于使用了云盘,实际会有 9 个副本。同时,由于 Kafka 层面的 Follower 需要主动从 Leader 同步数据,这也会消耗集群的计算和网络资源,将用户的业务流量扩大至 3 倍。但是,如果在 Kafka 层面设置单副本,由于 Kafka 本身不能利用到云盘的 3 副本能力,其高可用性就不能保证。因此,如何利用好云盘的 3 副本能力,降低的存储成本和网络成本,就成了面临的一大挑战。 阿里云通过接入自研云存储引擎,解决了存储成本和网络成本问题。其核心原理主要是:在自研存储引擎中引入了逻辑队列和物理队列两个概念。逻辑队列也就是暴露给用户的概念,在这里可以直接理解成客户端看到的 partition,而物理队列则用于实际存储数据。通过映射关系,将逻辑队列和物理队列绑定在一起。在自研引擎中,所有的分区在逻辑上都是单副本的。数据的可靠性和可用性由云盘底层的 3 副本机制保证。在正常情况下,发送到特定逻辑 partition 的数据,都会根据映射关系,写入到对应的物理队列中。同理,消费也是根据映射关系从实际的物理队列中拉取。 接下来来看云存储是如何做到容错和高可用的。例如,在节点 0 的 ECS 宕机时,可以通过 QueueMapper,秒级切换逻辑队列 0 的映射关系到节点 1 中的已有队列 Queue-3,或者新增一个物理队列 Queue-4。此时,发往逻辑队列-0 的消息,将被路由到Queue-3 或者 Queue-4 中。这种情况下,用户的发送业务不会受到影响,依旧可以继续发送成功,并且最新的消息也能被消费到。当然,在这种 Failover 期间,会存在一个问题:逻辑队列-0 在节点-0 上的消息,暂时不能消费;但是,对大多数应用场景来说,短暂的部分消息消费延迟并不是大问题,只要不影响发送就能满足要求。 在节点-0 的 ECS 宕机后,阿里云备用 ECS 会迅速生成新的机器替换节点-0,挂载原有云盘,分钟级时间内恢复节点-0 服务。在节点-0 恢复后,只用重新将逻辑队列-0 的映射关系切回 Queue-0,系统又重新恢复了原有状态。此时,发送/消费依旧能保持原生Kafka 的特性。 通过以上方式,将存储成本节省到原生 Kafka 的大约三分之一。同时,由于在 Kafka 层面,副本数是 1,从而避免了 Follower 从 Leader 中同步数据的操作,网络流量也节省到原生 Kafka 的大约三分之一。 水平扩容,秒级数据均衡 弹性扩缩能力是消息队列的核心能力之一。由于 Kafka 服务端节点是有状态的,因此新增了若干节点之后,需要重新均衡各个 Topic 的队列,使得客户端往集群中发送或是消费的流量,能均衡地打到后端各个服务节点上。 开源 Kafka 在水平扩展了机器之后,做数据均衡的主要方式有两种: 第一种是在新的 broker 中新增队列。这种方式主要的痛点是: 1)系统状态发生改变,这种情况下一些多语言客户端的早期版本,需要客户端主动重启,否则无法消费新分区; 2) 第二是 Kafka 设计上的问题,分片数无法下降,导致后续无法缩容。 第二种做法是数据迁移。这种方式的主要痛点是: 1)流量复制,产生网络风暴,干扰正常使用; 2)均衡与数据量有关,如果数据量巨大,可能要花费几天来迁移。 那么,云存储引擎是怎么解决以上弹缩问题的呢? 前文提到消息队列 Kafka 引入了两级队列:第一级为逻辑队列,第二级为物理队列,也就是阿里云自研云存储队列。逻辑队列对外暴露,物理队列则用于存储实际数据。通过 QueueMapper 模块维护逻辑队列与物理队列之间的映射关系,如下图所示。 一个逻辑队列可以由多个物理队列拼接而成,通过位点分段映射,保证顺序。扩容时,只需要将逻辑队列指向新机器上的物理队列即可,这样新写入的消息就可以根据新的映射关系,直接写入到新加的机器。同样的,在消费时,可以根据位点分段映射关系,找到实际的物理队列,然后从物理队列中读取消息。 可以看到,通过两级队列分段映射,解决了消息队列弹缩和迁移问题,具有如下优点: 1)服务端扩缩容后,不变更队列数量,保持系统状态不变; 2)扩缩容时无需迁移数据,耗时短,可以在秒级时间内完成 Topic 队列重新均衡; 3)兼顾了吞吐与扩展性,不影响原有消息队列的性能。 总结 简单对主要介绍内容进行总结,Kafka 在流式处理场景中,传统方案一般会采用 Storm、Spark Streaming、Flink 和 Kafka Streams 等流式处理框架,但是开发人员在使用的过程中会遇到不少问题,尤其是在面对 70% 以上简单流场景的需求,会遇到运维成本较高、技术成本较大、学习成本不可预期和可用性、可靠性较低等痛点问题,阿里云消息队列 Kafka 发布 Kafka ETL 组件,是一款免运维的流计算组件,通过 Kafka+Kafka connect+函数计算的架构,能够很好的应对数据转储+实时计算问题,具备免运维、低成本、低代码、易监控等优势。 ​目前 Kafka ETL 正处于免费公测阶段,欢迎大家体验试用。阿里云消息队列 Kafka 版具备内核层面的差异化优势,欢迎前往下方链接了解更多详情。 ​​​​​https://www.aliyun.com/product/kafka​​​​​ ​​如有更多问题,欢迎扫描加入 Kafka 钉钉交流群。​ 点击​​此处​​​,即可查看直播回放了解更多详情!​ ​

优秀的个人博客,低调大师

Android 图片三级缓存之内存缓存(告别软引用(SoftRefrerence)和弱引用(WeakReference))

在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。 但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象, 这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放, 这就有潜在的风险造成应用程序的内存溢出并崩溃。所以看到还有很多相关文章还在推荐用软引用或弱引用 (SoftReference or WeakReference),就有点out了。 下面是谷歌官方给的图片缓存例子(其实就是翻译上面链接的文章): 在你应用程序的UI界面加载一张图片是一件很简单的事情,但是当你需要在界面上加载一大堆图片的时候,情况就变得复杂起来。在很多情况下, (比如使用ListView, GridView 或者 ViewPager 这样的组件),屏幕上显示的图片可以通过滑动屏幕等事件不断地增加,最终导致OOM。 为了保证内存的使用始终维持在一个合理的范围,通常会把被移除屏幕的图片进行回收处理。此时垃圾回收器也会认为你不再持有这些图片的引用, 从而对这些图片进行GC操作。用这种思路来解决问题是非常好的,可是为了能让程序快速运行,在界面上迅速地加载图片,你又必须要考虑到某些图片被回收之后, 用户又将它重新滑入屏幕这种情况。这时重新去加载一遍刚刚加载过的图片无疑是性能的瓶颈,你需要想办法去避免这个情况的发生。 这个时候,使用内存缓存技术可以很好的解决这个问题,它可以让组件快速地重新加载和处理图片。下面我们就来看一看如何使用内存缓存技术来对图片进行缓存, 从而让你的应用程序在加载很多图片的时候可以提高响应速度和流畅性。 内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。 这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移 除。 在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了, 因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。 另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放, 这就有潜在的风险造成应用程序的内存溢出并崩溃。 为了能够选择一个合适的缓存大小给LruCache, 有以下多个因素应该放入考虑范围内,例如: 你的设备可以为每个应用程序分配多大的内存? 设备屏幕上一次最多能显示多少张图片?有多少图片需要进行预加载,因为有可能很快也会显示在屏幕上? 你的设备的屏幕大小和分辨率分别是多少?一个超高分辨率的设备(例如 Galaxy Nexus) 比起一个较低分辨率的设备(例如 Nexus S),在持有相同数量图片的时候,需要更大的缓存空间。 图片的尺寸和大小,还有每张图片会占据多少内存空间。 图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?如果有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不同组的图片。 你能维持好数量和质量之间的平衡吗?有些时候,存储多个低像素的图片,而在后台去开线程加载高像素的图片会更加的有效。 并没有一个指定的缓存大小可以满足所有的应用程序,这是由你决定的。你应该去分析程序内存的使用情况,然后制定出一个合适的解决方案。一个太小的缓存空间, 有可能造成图片频繁地被释放和重新加载,这并没有好处。而一个太大的缓存空间,则有可能还是会引起 java.lang.OutOfMemory 的异常。 下面是一个使用 LruCache 来缓存图片的例子: [java] view plain copy privateLruCache<String,Bitmap>mMemoryCache; @Override protectedvoidonCreate(BundlesavedInstanceState){ //获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。 //LruCache通过构造函数传入缓存值,以KB为单位。 intmaxMemory=(int)(Runtime.getRuntime().maxMemory()/1024); //使用最大可用内存值的1/8作为缓存的大小。 intcacheSize=maxMemory/8; mMemoryCache=newLruCache<String,Bitmap>(cacheSize){ @Override protectedintsizeOf(Stringkey,Bitmapbitmap){ //重写此方法来衡量每张图片的大小,默认返回图片数量。 returnbitmap.getByteCount()/1024; } }; } publicvoidaddBitmapToMemoryCache(Stringkey,Bitmapbitmap){ if(getBitmapFromMemCache(key)==null){ mMemoryCache.put(key,bitmap); } } publicBitmapgetBitmapFromMemCache(Stringkey){ returnmMemoryCache.get(key); } 在这个例子当中,使用了系统分配给应用程序的八分之一内存来作为缓存大小。在中高配置的手机当中,这大概会有4兆(32/8)的缓存空间。 一个全屏幕的 GridView 使用4张 800x480分辨率的图片来填充,则大概会占用1.5兆的空间(800*480*4)。因此,这个缓存大小可以存储2.5页的图片。 当向 ImageView 中加载一张图片时,首先会在 LruCache 的缓存中进行检查。如果找到了相应的键值,则会立刻更新ImageView ,否则开启一个后台线程来加载这张图片。 [java] view plain copy publicvoidloadBitmap(intresId,ImageViewimageView){ finalStringimageKey=String.valueOf(resId); finalBitmapbitmap=getBitmapFromMemCache(imageKey); if(bitmap!=null){ imageView.setImageBitmap(bitmap); }else{ imageView.setImageResource(R.drawable.image_placeholder); BitmapWorkerTasktask=newBitmapWorkerTask(imageView); task.execute(resId); } } BitmapWorkerTask 还要把新加载的图片的键值对放到缓存中。 [java] view plain copy classBitmapWorkerTaskextendsAsyncTask<Integer,Void,Bitmap>{ //在后台加载图片。 @Override protectedBitmapdoInBackground(Integer...params){ finalBitmapbitmap=decodeSampledBitmapFromResource( getResources(),params[0],100,100); addBitmapToMemoryCache(String.valueOf(params[0]),bitmap); returnbitmap; } } 本文转自农夫山泉别墅博客园博客,原文链接:http://www.cnblogs.com/yaowen/p/5071184.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

告别数据填报低效!专业表格解决方案,帮开发者搞定全场景需求

前言: 在数字化时代,数据是企业运营的核心资产。表格产品作为数据管理的重要工具,在数据填报场景中提供了多样化的解决方案,极大地提升了数据收集、处理和分析的效率。本文将深入探讨表格产品在数据填报场景中的各类解决方案,涵盖动态表格生成、智能化数据管理、企业级扩展功能等多个方面。 数据源接入: SpreadJS 的 DataManager 是一款功能强大且全面的数据管理工具,它在数据处理流程中扮演着核心枢纽的角色,主要负责数据加载、管理以及数据源更新等关键任务。 DataManager 具备出色的兼容性,支持多种常见的数据格式,无论是从本地文件还是远端服务器,都能轻松实现数据加载。这种灵活性使得用户可以将不同类型、不同来源的数据整合在一起,进行统一高效的管理,打破了数据孤岛的限制。 此外,DataManager 拥有强大的数据同步能力。在绝大多数应用场景下,它能够自动将数据的变化实时更新至数据源,或者通过请求的方式将数据变动同步回服务端,确保数据在多端之间的一致性与时效性。 目前 dataManager 有三种方式设置数据源: Http请求: 本地数据data 本地json文件 打开 SpreadJS 在线表格编辑器,如下图所示,依次点击"数据"---->"数据源"---->"添加表",在读取input框中,配置数据请求的连接。 动态表格生成与交互设计: 交叉报表 交叉报表是行、列方向均设有分组的基础报表类型,通过横纵双向分组将数据组织为二维结构,常用于多维度数据统计与分析。其核心特征体现在行列标题确定交叉区域,由双向分组字段动态扩展生成网格化布局,交叉点通过汇总函数计算数值,与传统纵向分组报表形成显著区别。 SpreadJS表格产品通过行列动态配置技术,能够根据用户的选择自动生成动态行列扩展的交叉报表。例如下面的场景,在统计报表场景中,客户需要做一个教育水平与职业选择关系的统计。希望动态生成纵向按照教育水平为行,横向按照职业类别为列的表格进行统计分析。 SpreadJS报表在接入数据源后,从左侧的数据源列表中拖入相应的字段。 通过右侧的设置面板,将对应字段设置为横向扩展。 拖入汇总字段(人数),通过右侧设置面板,将数据类型设置为汇总类型。 2.补齐装饰元素(对角线,边框,表头等) 3.点击预览查看效果 主子报表 主子报表是一种通过主表与子表结构展示层级化数据的特殊报表类型。其核心特征是将数据集划分为主表数据集与子表数据集,主表通常以自由格式展示汇总信息,子表则以列表形式呈现明细数据,形成一对多的关联关系。 上下文(Context)是SpreadJS报表中一个非常重要的概念。它会影响报表的布局,因为默认情况下,它会在所有上下文中过滤子单元格中的数据,并且所有子单元格都会随着上下文重复显示。单个单元格可以有两种上下文: 垂直上下文:在此情况下,单元格将左侧具有垂直溢出方向的单元格用作垂直上下文。 水平上下文:在此情况下,单元格将上方具有水平溢出方向的单元格用作水平上下文。 使用上下文功能可以更加遍历的创建主从报表,例如下面的场景,用户希望通过订单与订单明细之间的关系,创建一张主从报表,在显示订单列表的同时,显示每条订单的订单明细。在SpreadJS报表中只需要进行如下的操作: 构建报表的基本轮廓,在SpreadJS报表在接入数据源后,从左侧的数据源列表中拖入相应的字段,统计字段设置统计公式。 2.对报表外观进行美化,设置背景色,边框,字体,合并单元格 3.子订单信息的子表与主表订单表存在主子关系,需要设置上下文进行关联。设置后,生成报表时会按照主子关系进行生成。 4.设置过滤条件,过滤掉子表没有内容的数据。 5.点击预览查看效果 交互式填报界面 上文配置的字段,基于报表展示的结果,可以对相应的字段区域(单元格)进行填报,从而实现网页端在线填报。这种技术在多表关联填报中尤为重要,支持主从表、交叉表、分页表等多种复杂表样设计,满足业务场景的多样化需求。例如,在采购管理系统中,采购商品报价需要处理大量的输入字段和联动计算公式,表格产品能够有效解决这些技术难点,提供比Excel文件更好的业务信息交互方式。 数据管理能力 数据校验规则 表格产品内置了多种数据校验规则,包括主数据校验、格式验证及公式联动校验。主数据校验可以确保部门名称等数据的标准化,格式验证则通过正则表达式等方式保证数据的准确性。公式联动校验则能够自动检查数据之间的逻辑关系,避免错误数据的录入。这些校验规则大大提高了数据填报的准确性和可靠性。 如下图所示:设置了一个数字验证器,其中数字应该大于或等于5,填报内容小于5的填报项会用红框圈出,具有警示效果。 导入,导出 SpreadJS报表支持导入导出Excel文件,保留原有数据的同时实现无失真转换。这一功能在企业数据管理中尤为重要,因为它允许用户无缝地从传统Excel工作方式过渡到在线填报系统,而无需担心数据丢失或格式混乱。 例如:制作复杂报表时,往往有参照的Excel样本,SpreadJS报表工具支持导入模板功能,可以利用此功能导入已有的Excel模板,在其基础上进行设计,避免了重头设计导致很多重复的工作。 最终生成的报表需要做离线填报的时候,可以使用SpreadJS报表的导出功能,导出成Excel文件,Excel文件能够还原整个报表的样式,边框,数据验证等元素。满足用户线下离线填报的需求。 打印 通过SpreadJS报表工具的打印功能,可以将报表的填报内容进行打印,便于纸质留存,在一些特殊行业中纸质文档有其存在的必要性,例如在医疗、法律等受严格监管的行业,纸质文档是法律凭证和存档的必备形式,打印功能可确保文件符合行业规范要求。 自动化汇总分析 SpreadJS报表工具提供汇总功能,其内部嵌入的公式引擎,支持超过400种以上的Excel公式计算,利用Excel公式计算可以支持支持多层级数据汇总求和。通过填报提交进行上报。另外搭配Wyn 商业智能BI软件,可以对整个状态进行监控,后续利用其数据分析功能,可以进一步对上报的数据进行实时分析。这一功能在财务填报、生产报价、人力资源管理等高频业务场景中尤为实用。通过自动化汇总分析,企业可以快速生成各类报表,为管理层提供深入的数据洞察,支持更为精准的战略决策。 企业级扩展功能 权限控制体系 表格产品实现了单元格级别的数据权限管理,可以从单元格->行列->工作表->工作簿实现不同级别下的权限管理,例如,部门间的数据可以设置为不可见,可以根据部门权限对部门的数据进行隐藏处理,这样确保数据的安全性。此外,通过角色权限管理,企业可以为不同用户设置不同的读写权限,避免数据被误操作或泄露。这种权限控制体系在大型企业中尤为重要,能够有效保护敏感数据的安全。 国际化支持 表格产品支持多语言配置,适配跨国企业的数据采集需求。例如支持阿拉伯语(Left to Right)的输入方式,大大方便了跨国企业的数据采集操作,提高了工作效率。 低代码开发支持 模板化快速搭建 SpreadJS报表产品可以内嵌至低代码平台(活字格),通过拖拽式设计器生成报表模板,使用低代码快速配置数据源和页面,降低了技术门槛。例如,活字格低代码开发平台提供了一套全面的表格报表解决方案,支持多种报表样式的灵活展示和数据填报功能,包括行式报表、分组报表、交叉报表等。用户无需编写复杂的代码,即可快速搭建符合自身需求的报表模板。 数据库直连能力 活字格低代码平台支持SQLServer、Oracel、MySQL等多种数据库对接,实现填报数据实时入库。这一功能使得企业能够直接连接现有的数据库系统,无需手动导入导出数据,大大提高了数据处理的效率。 实际应用案例 生产采购管理系统 在ERP系统应用中,SpreadJS报表产品展现出了显著的价值。它有效解决了用户从传统Excel操作习惯到现代化信息化办公的转变难题,将Excel的操作体验融入到Web应用中,降低了学习成本,提高了工作效率。同时,它还简化了报表制作流程,提高了报表制作效率,帮助快速生成各类报表,满足不同业务场景的需求。例如: 港澳车务系统是一款专为优化客户车务管理业务而设计的ERP应用软件。在项目启动阶段,团队经过周密的规划和深入的业务分析,成功构建了系统框架并完成了各业务模块的开发。然而,系统上线后却遭遇了一系列的挑战,主要源于客户长期依赖的传统Excel操作习惯于现有的技术框架之间存在不兼容。客户在日常工作中普遍使用Excel,虽然他们期望实现现代化的信息办公方式,但仍希望新系统能够保留Excel操作的便利性。为此,团队迫切需要构建一套基于BS架构且能兼容Excel操作习惯的应用系统。在此背景下,ERP应用开发团队引入SpreadJS表格插件,有效地解决了这一操作系统的转换问题,确保系统能够在BS架构下兼容Excel的操作习惯,满足其现代化办公的需求。 在系统中通过报表插件展示报表数据: 利用填报功能实现即时在线填报: 利用低代码开发平台完成整体系统开发,嵌入SpreadJS报表产品完成核心业务逻辑 未来发展趋势 随着人工智能技术的不断发展,智能生成多样化表格解决方案应运而生。这些软件不仅能够自动识别、整理和生成表格,还能根据用户需求进行数据排序、筛选、合并等操作。例如,生成表格的软件支持多种表格格式输出,如Excel、CSV、PDF等,满足了不同用户的需求。 此外,AI智能生成多样化表格解决方案通过自动识别数据类型和结构,生成相应的计算公式,简化了数据解决流程。例如,某高校利用生成表格公式快速计算出学生的总分、平均分等指标,提高了工作效率。 表格产品在数据填报场景中提供了多样化的解决方案,从动态表格生成到智能化数据管理,再到企业级扩展功能和低代码开发支持,极大地提升了数据收集、处理和分析的效率。随着技术的不断进步,表格产品将继续创新,为企业提供更为强大和便捷的数据管理工具。 体验地址 SpreadJS在线表格编辑器

优秀的个人博客,低调大师

告别CRUD地狱!活字格低代码3小时交付业务系统,效率提升10倍 | 葡萄城技术团队

引言 在数字化转型浪潮中,企业IT团队正面临严峻挑战:80%的开发精力消耗在表单、审批流等重复性功能上,而业务部门却抱怨“需求响应太慢”。如何打破这一僵局?西安葡萄城推出的活字格企业级低代码平台,凭借可视化开发、AI智能生成、开放集成三大核心能力,正将业务系统开发效率提升至传统模式的10倍——本文将通过真实案例拆解其颠覆性价值。 一、CRUD困局:数字化转型的隐形成本 调研显示,企业业务系统开发存在两大效率黑洞: 重复劳动占比畸高 基础功能开发(表单设计、权限管理、报表配置)占用70%以上工时 某零售企业IT日志显示:每月需修改156次相似表单字段 交付周期严重脱节 传统开发模式下,一个供应商管理系统平均需2周交付 业务规则变更时,代码级修改导致平均3天停服 > 客户痛点实录: > > “我们5人团队每年开发20个系统,其中16个是不同名字的CRUD套娃。” > > ——某制造集团CIO访谈 二、活字格破局之道:三大技术引擎解析 ✅ 1. 流程中心:拖拽式设计,复杂逻辑秒级配置 BPMN可视化引擎:采用简化版BPMN 2.0标准,像绘制流程图一样搭建审批/派单流程(流程开发指南) 智能路由决策: 自动识别组织架构中的审批路径(如“部门经理→财务总监”) 支持动态加签、并行会签等复杂模式 热更新技术:流程规则修改后即时生效,无需重新发布 > 案例:某物流公司用活字格重构运单系统,审批流配置时间从3天→2小时,错误率下降90%。 ✅ 2. 开放集成:企业级系统的“连接器” 单点登录(SSO):无缝嵌入SAP、用友等系统界面,用户无感知切换(集成方案) 混合开发模式: 高性能操作(如万级数据导入)通过服务端命令实现 前端保留低代码快速迭代优势 遗产系统迁移:独家支持Access应用一键转Web系统,历史数据自动继承 ✅ 3. AI革命:自然语言驱动开发 智能体(Agent): 用户说“创建采购审批流程”,自动生成流程图+数据模型 支持多轮对话修正(如“增加质检环节”) 设计时辅助: 输入“供应商管理系统”,AI推荐字段结构(名称、信用等级、合作年限) 自动生成移动端适配页面 三、效率实测:3小时交付供应商管理系统 时间轴(某科技企业真实案例): 时间 任务 关键技术 09:00-09:30 AI生成数据模型 自然语言→数据库表自动生成 09:30-10:30 搭建供应商准入流程 拖拽式BPMN设计器 10:30-11:00 对接天眼查API校验企业资质 服务端命令调用REST API 11:00-11:30 发布至企业微信/钉钉 多端自适应渲染引擎 > 效果对比: > > - 传统开发:14人天,成本≈3.2万元 > - 活字格:3小时,成本≈0.2万元 四、行业实践:低代码的千企千面 行业 典型场景 效率提升 教育 智能组卷系统 出题效率提升8倍 零售 动态会员管理体系 积分规则调整周期1天→1小时 制造 设备报修-MES工单联动 故障响应速度提升60% 五、为什么选择活字格? 能力维度 传统开发 活字格方案 开发速度 周级交付 小时级交付 修改成本 需停服+全量回归测试 实时热更新 技术门槛 需专业程序员 业务人员可参与 TCO(总拥有成本) 高(人力+运维) 降低60%以上 结语:低代码是生产力革命,不是妥协方案 国外某机构预测,2025年70%的新应用将由低代码构建时,活字格已证明: 对IT团队:将创造力从CRUD中释放,专注架构优化与创新 对业务部门:获得“所想即所得”的数字化能力,试错成本趋近于零 对企业:用软件迭代速度构筑市场竞争壁垒 正如首批尝鲜者所言:“以前不敢想的定制化系统,现在今天提需求、明天就能用——这才是数字化的本来面目。” > 行动指南: > > 1. 下载活字格试用版 > 2. 参加《3天速成训练营》(附实战案例包) > 3. 联系葡萄城专家获取行业解决方案白皮书

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册