2024 年终总结
2024 年过得很快,时间就像倾盆大雨时的雨滴,挡不住地从空中落在地上,然后消失在草地或者泥土里。这一年我经历了很多事情,也学到了很多事情,希望可以用这篇博客来总结和梳理一下自己的想法。主要还是围绕着与自己工作和兴趣相关的云原生、RAG 和 AI 三个方面展开。
云原生
ob-operator
从去年毕业入职以来,我就一直在做云原生相关的工作,主要是使用 Kubernetes 的 Operator 拓展机制让分布式关系型数据库 OceanBase 更好地在云原生环境中部署和运行。主要工作也都在 Github 仓库 oceanbase/ob-operator 中开源,欢迎指导和关注!
其中因为 OceanBase 本身发展得较早,许多特性都是针对物理机部署的场景进行特定优化,所以在云原生环境中部署和运行的设计方案会有些“与众不同”。
通过 Service 固定节点 IP
首先我们 OceanBase 需要依靠 IP 来进行节点间通信,这其实在云原生环境中,至少在 Kubernetes 环境中是不太推荐的,因为 IP 是不稳定的,Pod 重启后 IP 会变化,这样就会导致节点间通信的问题。在 2024 年初,OceanBase 社区版只支持通过绑定网卡的方式来获取 IP,而不能自定义地指定 IP,这样获取到的 IP 就是 PodIP,只有通过特定的、支持固定 PodIP 的 Kubernetes 网络插件才能够支持更全面的集群节点故障恢复。终于在 2024 年 3 月之后,数据库内核开始支持指定 IP 启动,这是我们向相关研发同学要求了很久的变更。有了该特性就可以通过在每个运行了 OceanBase Pod 中通过外挂 Service 的方式来指定 IP,即使 Pod 重启,Service 的 ClusterIP 也不会变化,这样节点间通信的稳定性就得到了保证。在 Kubernetes 中部署的 OceanBase 数据库的可用性从此得到了极大地提升,我们与外部用户进行交流时也变得“更有底气”。
通过 CRDs 管理 OceanBase 集群
其次,OceanBase 原生支持多租户架构,也就是说在 OceanBase 集群当中可以划分出不同的租户,每个租户可以类比为一个 MySQL 数据库(企业版还支持 Oracle 租户),多个 MySQL 实例运行在同一个集群当中。通过创建和管理多个租户,进行 CPU、内存和 IO 等方面的隔离,实现更好的安全性和更高的资源利用率。租户本身没有“实体”,而 OceanBase 集群却有多层的逻辑结构:集群-可用区-节点,为了实现更细粒度地管控,我们将集群、可用区和节点三级结构和租户都抽象成了 Kubernetes 的 CRD (Custom Resource Definition,自定义资源声明)。它们再与内部的其他 CRD 进行交互来完成集群的运维和管控任务,通过增加较多的 CRD,我们可以更加灵活地实现更多功能。这些 CRD 之间的关系以及它们在运维中的“地位”可以在下图中看到。
资源调谐器与任务管理器
另外,因为数据库运维通常有较多需要运行一段时间的任务,比如集群初始化、租户的备份和恢复等,为了更好地描述各个资源运维任务的状态和控制任务的运行情况,我们引入了资源调协器和任务管理器来管理这些长时运行的任务,配合 CRD 的状态机模型来完成运维工作。资源调协器监听 CR 的变化,如果 CR 的实际状态与预期状态不符合,则将资源置为某个状态并且开启相关的任务流,任务流是一系列任务的列表,任务会被任务管理器按序执行。任务有 Pending
、Running
、Successful
、Failed
四个状态,如果所有任务都能够成功执行,那么 CR 将会进入任务流指定的下一个状态,否则会根据配置进行任务重试或者进入失败状态。这样可以较好地在 Operator 模式下实现长时运行任务的管理,且任务流机制也给前端开发中的资源状态可视化留足了发挥空间。任务管理器的工作流程如下图所示,
ob-operator 其他方面的工作,比如跨 K8s 集群管理、存储兼容性检查等,这里就不一一展开了。
Dashboard
OceanBase Dashboard 是一个基于 ob-operator 的 Web 管理后台,为用户在云原生环境中管理 OceanBase 集群提供了更加直观和友好的界面。OceanBase 在主机部署侧有个已有工具 OCP,它具有集群管理、租户管理、SQL 诊断、监控告警等丰富的功能,但是在云原生环境中,我们需要一个更加轻量的方案,于是我们开始了 Dashboard 的开发。
Dashboard 的前端使用 React + Ant Design,后端使用 Gin 进行开发,没有使用关系型数据库,而是直接使用 etcd 作为数据库存储,这样可以极大地降低用户部署的难度,也就是说,Dashboard 尽量减少所需要的数据,将必要数据使用 ConfigMap、Secret 和 CR 的形式存储在 Kubernetes 集群中。Dashboard 的功能主要包括集群管理、租户管理、备份恢复、监控告警、访问控制等,其中集群管理和租户管理是最基础的功能。
具体开发过程遇到的问题在此不进行详述,只介绍两个比较有意思的发现。
规范使用 TypeScript 的必要性
在开发 Dashboard 的过程中,我发现了在团队协作项目内规范使用 TypeScript 的必要性。在今年 4 月以前,前端同学使用的虽然已经是 TypeScript,但所有的接口调用都是单独再定义 Service 方法来调用,在开发过程中接口调用的参数和返回值都没有精确的类型提示,且其他部分代码里有大量的 AnyScript。前端同学一旦自测不充分就会出现很多问题。最经典的问题就是访问值为 undefined
或者 null
的对象的属性,这会直接导致应用白屏,且不容易定位具体位置。
后端程序使用 Go 开发,我们为了方便合作对接,开发之初就使用 swag 和 gin-swagger 这两个工具来生成接口文档,为了解决前端同学定义接口调用方法不准确且效率低的问题,我测试了 OpenAPI Generator 并且成功地将 Swagger 生成的 JSON 文件转换为 TypeScript 类型定义文件和 API SDK 代码,也给前端同学将 Authorization 相关的代码也封装好后极力推荐给他们。使用上自动生成的 API SDK 代码后,前端相关代码的类型定义与后端声明完全一致,前端同学的开发效率提升了很多,也减少了很多不必要的错误。
使用 Kubectl exec 实现终端连接
因为在数据库运维过程当中需要经常使用 MySQL 客户端连接到数据库当中执行相关 SQL 查询,在 ob-operator 开发过程中我自己早已准备了一个连接指定集群指定租户的脚本,想着在 Dashboard 中要是能加入终端连接的功能就更好了。通过调研,我发现在 client-go 中也可以通过 pod/exec
子资源来实现类似 kubectl exec
的功能,于是我定义了下面的通用方法:
1 | import ( |
通过 gorilla/websocket
将请求转换为 WebSocket 连接,然后在前端通过 xterm.js
和 websocket 协议来实现输入输出的转写,这样就可以在 Dashboard 中使用终端直接连接到指定的集群或者租户的数据库中,执行 SQL 查询等操作。这样的功能在数据库运维中是非常有用的,也是 Dashboard 的一个亮点,得到了知乎老师的好评。如果也有实现类似特性的需求,相关的前端 PR 和后端 PR 可以在 Github 上查看。
RAG
RAG 是 Retrieval Augmented Generation 的缩写,是一种基于检索的生成模型,是在 2020 年由 MetaAI 的研究员提出的一种新型的自然语言处理模型(论文链接)。RAG 将检索和生成两个任务结合在一起,检索任务是通过搜索引擎来获取相关的文本片段,生成任务是通过生成模型来生成文本。RAG 模型的主要优势是可以利用搜索引擎来获取大量区别于训练数据的、额外的、更新的知识作为生成模型的上下文,从而提高生成文本的质量。
其实 RAG 也应该算作 AI 范畴,但因为这个方法比较独立,所以我单独将其作为一章进行介绍。
开源文档库问答助手
今年七月我开始调研和开发公司内开源文档 RAG 系统(下文简称 RAG 系统),目标是使用 OceanBase 开源的文档作为语料,为社区论坛和众多钉钉的用户群提供智能问答服务。用户可以通过提及(@)智能助手并询问的方式来调用 RAG 系统的能力,系统会根据用户的问题在文档向量数据库中检索相关的文档片段,然后将检索到的文档会同用户的问题一起输入给 LLM,LLM 再根据输入内容生成相应的回答。八月初在社区论坛和钉钉群里灰度上线以来,已经累计服务了 1500+ 人次,得到了用户的好评。
RAG 系统的运行周期包括了数据准备阶段和运行阶段,数据准备阶段主要包括了文档切片、文档向量化、文档向量索引化以及后续的知识批量更新等工作;运行阶段则包括了用户提问的意图识别、提问内容相关组件的识别、文档检索、文档重排序和生成回答等工作。
数据准备阶段
RAG 系统数据准备阶段的工作如上图所示,主要包含以下几个方面:
- 文档切片:采用 LangChain 的
MarkdownHeaderTextSplitter
来将文档切分成段落,每个段落作为一个文档片段。 - 文档向量化:Ollama 部署的
bge-m3
模型将文档的内容转换为向量。 - 文档向量索引化:OceanBase 4.3.3 开始支持向量类型存取和 HNSW 向量索引。我们将同个组件的文档内容、向量和元信息一同存储到 OceanBase 的同一张表中,并针对向量列创建 HNSW 索引。
- 知识批量更新:定期从 Github 上拉取文档仓库最新的文档内容,然后更新到数据库中。
运行阶段
运行阶段的流程如上图所示,有多个提示词各异的智能体会按序处理请求。
首先用户在论坛或者钉钉群提出问题后会先经过意图识别智能体,如果用户提问与 OceanBase 无关,则系统会直接终止流程或直接回复“抱歉,我无法回答”;如果用户提问与 OceanBase 有关才会进入下一步,OceanBase 相关组件识别。
组件识别智能体会根据用户的提问以及提示词中给出的组件列表和组件介绍来给出用户提问涉及的组件和版本列表,例如 observer@4.3.3
、ocp@4.3.0
和 obd@2.10.1
等。如果用户提问中没有给出组件和版本信息,系统会默认使用最新版本的文档来检索。
接下来 RAG 系统将把用户的提问用文本嵌入模型转换为向量,再在识别出来的若干组件对应的文档数据表中进行 ANN 检索,得到若干个文档片段,然后将这些文档片段交给重排序模型进行打分排序,将最相关的 top-k
个文档片段交给总结回答智能体,完成回答生成后最终返回给用户。
其中还有一些细节处理,例如企业版保安智能体、obdiag 诊断智能体的使用,这里不再详细展开。
值得一提的发现
大模型服务不太稳定
我们使用的 LLM 服务之一是通义千问的模型调用服务,在早期测试过程当中 qwen-plus
表现优异,性价比较高,被选中作为主要的模型。但我近期发现相比于几个月前,现在 qwen-plus
的能力发生了一些变化,例如之前让它判断问题的意图是比较准确的,而现在则时不时有些问题,例如用户提问细节诊断问题时,qwen-plus
无法准确地将问题识别成为 OceanBase 相关的“诊断问题”,而是识别成了“特性问题”。
另外,要求大模型输出 Json 结果不太稳定,有时候会出现 Json 结果不完整的情况,这样就会导致后续的流程无法继续,需要进行重试,且大概率重试后仍然有问题。只能设置默认值来为这样的情况兜底。
知识库质量非常关键
RAG 系统的核心是检索和生成,检索的质量取决于文档库的质量,如果文档的内容不够丰富、不够准确、覆盖面不够广泛,生成的回答也会不够准确,甚至让大模型出现幻觉。因此,知识库的质量非常关键,未覆盖的部分需要尽快补充,已导入的部分需要定期更新、维护和优化。**”Quality in, quality out.”**
统一接口服务多端
RAG 系统的服务端提供统一的接口,可以同时为社区论坛和钉钉群提供服务,这样可以减少重复开发的工作量,提高开发效率。如果要为 RAG 系统分层,那么自下而上是数据层、服务层、调用层和接口层。数据层使用 OceanBase 存储所有的关系型数据和向量数据;服务层提供了统一的 RAG 接口,上层只需将用户问题作为参数传入即可;调用层由若干的工作线程组成,负责轮询调用任务、调用服务层的接口、处理返回结果;接口层则直接承接用户的询问请求,将请求处理并入库后返回异步调用结果。
OCR 也是多模
论坛用户提问或者回帖时经常发送图片,这些图片中可能包含了一些文字,如果能够将图片中的文字提取出来,那么就可以将这些文字作为问题输入到 RAG 系统中,这样就可以更好地服务用户。因此,我尝试了使用 tesseract-ocr 来识别图片中的文字,在用户上传的图片是终端的截图时,识别效果还是不错的。使用 OCR 方案在很多时候能够从图片中提取出许多有效信息作为用户文字输入的补充,让 RAG 系统具备了多模的能力。
RAG Workshop
公司的数据库产品开始支持向量类型之后举办了很多场与 AI 相关的活动,其中几乎每场都会包含 Workshop 环节,也就是在这个环节中,参与的用户将通过我们提供的项目代码轻松地使用 OceanBase 搭建起基于 RAG 流程的智能问答系统。这个 Workshop 的目的是让用户了解 RAG 的基本原理和流程,并了解到 OceanBase 作为多模数据库在其中发挥的作用。
在 Github 组织 oceanbase-devhub 中创建了多个示例项目代码仓库,其中最主要的有:
- ai-workshop-2024 用于存放 RAG 项目的代码和文档;
- image-search 用于存放以图搜图的项目代码和文档;
- dify 存放了 Dify 的 MySQL 兼容版项目代码以及相关实验文档。在该项目中,我将 Dify 的
api/models
部分进行了重构,将原来只能使用 PostgresQL 的 Dify 改造成既可以使用 PG 又可以使用 MySQL 数据库,这样让用户可以更加灵活地选择使用何种关系型数据库,也为实验项目中使用 OceanBase 作为关系型数据库和向量数据库做好了铺垫。
AI
改观
这两年 AI 发展迅猛,2023 年开始使用 Github Copilot 以来我已经习惯了写代码时的自动补全提示,但也仅限于此。而今年我对 AI 发生了重大改观,我开始意识到 AI 已经不再是停留在学术论文里的方法、模型、参数和指标,而是真正能够应用到生产和创作中的强有力的提效工具,这让我极其兴奋。
正如我在用 Cursor 快速搭建软件原型一文中提到的那样,使用类似 Cursor 这样的 AI 加持代码编辑器能够让我们的编程效率大大提高,机械的重复性工作交给 AI 来完成后,我们可以更多地关注于代码的设计、架构和逻辑。
抛开工程难度来说,类似 Cursor 的工具的原理无外乎是将项目代码通过某种方式进行索引,让 LLM 在执行生成任务前能够进行检索和理解,在用户发出指令后工具将项目代码内容(包括但不限于此,也可能有其他有利输入)作为上下文一同输入给 LLM,LLM 再根据要求生成指定的修改指令并且提供给用户预览和确认:
代码补全
,在当前光标补全一行或者是生成多行代码内容替换
,将文件的指定区间替换成为新的内容,可能是行内的,也可能是多行的创建文件
,如果实现用户需求需要创建新的文件,则生成新的文件生成命令
,生成需要在终端执行的命令,例如npm install xxx
,poetry add xxx
等
现在业界把使用 LLM 生成能力、结合工具调用、自动化完成特定任务的工具叫做 Agent,Cursor 在这个范畴内其实属于比较简单的 Cursor,它在工作中每一轮交互都需要人工确认和介入,实现难度相对较低,稳定性也相对较高。而类似 SWE-Agent 这样真正全自动的、端到端完成任务的复杂 Agent 在解决真实世界的问题时最高也不过三成的成功率,还是在不限制花销的情况下,这说明该方向有很大的提升空间,需要更多后续工作来完善和优化。
当然,复杂的 AI Agent 需要多方面知识和能力的共同加持,脱颖而出的产品必定是优秀工程方法的集大成者。例如 Cursor 的代码索引和检索离不开性能优异的检索系统,此时数据库系统的提效和检索方案的优化的作用就显得尤为重要;复杂 Agent 的工具调用效果也极大程度上依赖于工具接口的抽象和工具的具体实现,它们或许是通过 HTTP 接口、或许是通过 CLI 命令行、或许是通过 SDK 调用等等,如何发明适合 Agent 的工具和如何让已有工具适应 Agent 的使用是亟待解决的问题。
Anthropic 在 Building effective agents 一文中提到了许多构建 Agents (代理)的方法和技巧,其中有很多值得借鉴的地方。同时可以看到,Anthropic 这家公司在构建大预言模型和定义代理系统中有很多独到的见解和成熟的思考,后续可以持续关注。
同时从这篇文章中可以看到,现在代理能够真正深度参与的只有客服和编程两个需求场景,在未来一定会有更多值得发掘的自动化工作场景等待着研发者和用户们。
未来
为了能够稍微跟进 AI 的发展,我计划未来有机会一定多多参与 AI 相关的项目,阅读优秀的 AI 领域论文,学习 AI 相关的技术知识,提高自己相关的技术水平,争取能够在其中实现一些有意义的工作或者创造出一些有趣的产品。
有趣的项目
不知道从什么时候开始,我养成了经常看 Github Trending 的习惯,每天都会浏览一下最近比较热门的项目。在这个过程中,我发现了很多有趣且有价值的项目,这些项目或者是解决了一些实际问题,或者是提供了一些新的思路,或者是实现了一些有趣的功能。这些项目给了我很多启发,也让我学到了很多东西,之后有时间会单独介绍一些相关的实践经验和收获。它们包括但不限于下面这些:
Excalidraw 是一个开源的手绘风格白板工具,它的界面简洁易用,支持多人协作,可以用来画流程图、架构图等,我在写博客和做分享时经常用它来绘制示意图。
PocketBase 和 Supabase 都是开源的后端即服务(BaaS)平台,它们提供了数据库、认证、实时订阅等功能,可以帮助开发者快速构建应用。PocketBase 使用 Go 开发,基于 SQLite,体积小巧,部署方便;Supabase 则是基于 PostgreSQL 构建的更完整的解决方案。
FastAPI 是一个现代、快速的 Python Web 框架,它基于 Python 3.6+ 的类型提示功能构建,具有很好的性能和开发体验。它的文档非常详细,社区也很活跃,是如今构建 Python Web 应用的不二之选。
Pydantic 是一个使用 Python 类型注解进行数据验证的库,它可以帮助我们在运行时验证数据的类型和格式,提高代码的可靠性。FastAPI 就是基于 Pydantic 构建的。
Ollama 是一个让在本地运行大语言模型变得简单的工具,它提供了简单的命令行接口,支持多种开源模型,可以帮助我们在本地快速部署和使用的文本嵌入和文本生成模型,是我在开发 RAG 系统时经常使用的工具。
Bun 是一个高性能的 JavaScript 运行时,原生支持 TypeScript,内置包管理器。它的性能比 Node.js 要好很多,支持多线程和 WebAssembly。
Docusaurus 是一个用于构建文档网站前端框架,它提供了丰富的主题和插件,可以帮助开发者快速构建文档网站。与其他文档框架相比,它支持非常细粒度的自定义,例如加入 React 实现的任意页面,将原生组件进行包装或者弹出修改等。借助该项目,我将 OceanBase 在 Github 上的多个文档网站进行了重构。例如 oceanbase.github.io、ob-operator 和 oceanbase-devhub 等。
小结
总的来说,2024 年是充实、有意义、具有挑战的一年,希望在未来的日子里我能继续保持积极向上的态度,不断追求技术上的突破和创新以及心态上的成长。2025 更多期待!