当前位置: 首页 > news >正文

智谱清言微服务架构转型实践——基于 CloudWeGo 的技术演进

智谱清言作为基于 GLM-4 大模型的全能 AI 助手,是国内首批通过备案的大模型产品。其多终端支持(移动端/桌面端/微信小程序)和零代码智能体创建能力,已广泛应用于生产力提升、娱乐休闲等场景。本文由智谱清言资深后端开发工程师肖文彬基于 CloudWeGo 技术沙龙演讲内容整理,系统阐述从单体架构到微服务体系的转型实践。

随着业务规模的不断扩大和用户需求的快速增长,传统的单体架构在扩展性、灵活性和运维效率等方面逐渐暴露出瓶颈,微服务架构成为当下企业技术架构转型的主流选择。智谱清言作为国内领先的大模型应用之一,基于自主研发的 GLM 模型打造了全能 AI 助手,提供多平台支持和强大的智能体创建能力。为更好地应对业务快速迭代与扩展的挑战,智谱清言开启了从基于 Python 的单体架构向以 Go 语言为基础的微服务架构转型之路。

智谱清言资深后端开发工程师肖文彬在 CloudWeGo 深圳技术沙龙上作为用户代表,分享了智谱清言在转型过程中面临的技术挑战、架构设计思路、CloudWeGo 技术框架的选型与实践经验,以及微服务体系下的可观测性建设,分享架构升级后取得的显著成效与未来的技术规划,期望为正在进行或计划进行微服务架构升级的团队提供有益的实践借鉴与参考。

图: 智谱 清言资深后端开发工程师肖文彬

一、 业务背景与技术痛点

项目初期,智谱清言后端团队仅 4 名成员,采用基于 Python 的 Flask 单体架构,面临人力不足、工期紧张、缺乏运维支持等难题。业务开发、测试、环境搭建、基础组件开发均由团队成员兼顾,导致开发效率低下、代码质量难以保障,代码重复度高,维护成本逐步增加。在同一项目中发现诸多问题,例如 IDE 频繁报错,且多为重复性问题。起初,大家多采用复制代码的方式解决,未能将其抽象为公共部分以便调用,这为后续工作埋下隐患。

当时智谱清言后端团队发现单体架构虽然简单易用,开发与部署均在一个项目内完成,操作相对简单,上手容易;但扩展性差、耦合度高、容错性低,难以满足业务快速扩展的需求。微服务架构则具备更强的扩展性、容错性和团队自治性,但面临服务边界划分、粒度控制、网络延迟、数据一致性及服务治理复杂性等多方面挑战。

当时我们决定从单体架构向微服务架构转变,也面临了很多挑战。首先,需要确定拆分边界与颗粒度。若拆分过粗,易导致过度耦合;若拆分过细,则会出现过度分散问题。其次,分布式系统具有额外复杂性,涉及各服务间的网络延迟容错、数据一致性,还需中心端,如服务中心、注册中心和配置中心进行管理,且中心端需承受较大压力。

二、微服务选型分析

1. 开发语言与框架技术选型

基于上述情况,我们决定明确区分不同的开发目标,并选择最适合的技术方案。为此,我们选择 Go 语言作为主要的开发语言。选择 Go 的主要原因是其对高并发的天然支持和低延迟的特性,这使其成为后端开发的理想选择。相比之下,Python 更适合处理与模型的交互,而 Go 则更适用于处理与客户端的交互。此外,引入新的开发语言将有助于我们减少累积的技术债务,提高整体开发效率。

通过开发一个统一的 SDK,我们可以使多个服务引用此 SDK,从而简化开发和维护过程。接下来,关于服务的拆分策略,考虑到用户登录和鉴权功能的使用频率较高,我们将这部分功能首先迁移到新服务中。在迁移过程中,我们可以利用网关先将请求路由指向旧服务,然后在新服务上线后,逐步执行灰度发布。一旦新服务运行稳定,我们将进行全量切换。

对于其他的新业务服务,我们将采用新的开发框架进行构建,这些服务将不依赖于旧的服务架构。此外,每个微服务将配备独立的存储资源,包括数据库、缓存和配置中心,以支持其独立运行和扩展需求。

鉴于系统的复杂性,我们将重视编写充分的单元测试。这不仅有助于在开发初期发现问题,还将确保每个组件在集成前的质量,为整体系统的稳定性和可靠性打下坚实的基础。

2. 微服务框架选择过程:Gin、Hertz与gRPC的比较分析

在选择适合微服务架构的技术时,我们细分了 Go 语言常用的几种框架,包括 Gin、Hertz 和 gRPC、Kitex。

  • Gin vs Hertz

Gin 是一款高性能的 HTTP 框架,以其简洁和高效而广受欢迎,是 Go 语言开发者的常选框架之一。与此同时,Hertz 作为一款新兴框架,汲取了多个开源框架的优点,特别在易用性、性能和扩展性方面表现出色。基于我之前使用 Gin 进行微服务开发的经验,我发现 Hertz 在很多方面提供了更优的解决方案,例如在中间件的处理上。因此,在最近的项目中,我选择了 Hertz 作为主要的开发框架

  • gRPC vs Kitex

另一方面,对于需要高效远程过程调用(RPC)的场景,gRPC 是 Go 语言常见的框架选择。与 Kitex 相比,Kitex 提供了对多种描述语言和协议的支持,并针对 Go 语言和 RPC 场景进行了性能优化,但在 IDL 和协议的选择上,字节跳动内部使用最多的是 Thrift, Kitex 对 Thrift 的各项能力支持更为完善。我们公司更常使用 Protobuf,流行的 API 测试和开发工具 Postman 对 Protobuf 有良好的支持。

综上所述,我们的技术选型以实际应用需求和生态支持为依据,选择了 Hertz 和 Kitex 结合 Protobuf 作为开发微服务的主要技术栈。这样的选择不仅基于技术性能,还考虑到了开发效率和未来的可维护性。

3. 选择 CloudWeGo 的关键理由:性能、功能与社区支持

我们选择 CloudWeGo 作为开发框架有几个关键原因。

  • 优秀的性能与插件

首先,CloudWeGo 基于字节跳动开发的 netpoll 网络库,其性能在各项 Benchmark 测试中表现卓越,特别是在高并发环境下。这种优异的性能,加上它支持的丰富插件,使得 CloudWeGo 成为一个非常有吸引力的选择。

  • 注重兼容性

其次,CloudWeGo 提供了全面的微服务治理功能。它不仅模块化程度高,而且拥有一个功能丰富的插件生态,同时注重兼容性。对于已经熟悉 Gin 和 gRPC 的开发者来说,CloudWeGo 的 Hertz 和 Kitex 框架能够提供几乎无缝的过渡体验。特别是 Kitex,它在使用上与 gRPC 相似,使得开发者可以轻松迁移而不需要大幅度修改现有代码。

另外,Kitex 和 Hertz 均提供了代码生成工具,可以一键生成ThriftProtobuf 以及脚手架代码,减少不必要的代码开发。

  • 社区团队支持

最后,CloudWeGo 的社区支持是选择它的另一个重要原因。与传统的网络论坛相比,CloudWeGo 团队在飞书群内提供的即时响应和积极的问题解决态度为我们带来了更直接的交流体验。虽然 Gin 和 gRPC 拥有成熟的在线社区,但在飞书群中,我们能够得到更快速、更个性化的支持,这对于快速解决开发中遇到的问题非常关键。

因此,综合考虑性能、功能、兼容性以及社区支持,CloudWeGo 成为我们的首选开发框架。这不仅能提升我们的开发效率,还能确保技术实施的顺畅和高效。

这是从官网获取的 Benchmark 测试图表,从中可以看出 netpool 框架在 QPS 和 TP99 指标上表现优异。

4. 其他组件选型

在项目中的其他组件选型方面,我们主要关注配置中心和服务中心的选择。

  • 配置中心:Nacos vs Apollo

对于配置中心,Go 语言开发中常见的选项包括 Nacos 和 Apollo。鉴于 Apollo 的配置较为复杂且许多特性在我们的应用场景中并非必需,我们选择了 Nacos。Nacos 不仅提供配置中心功能,还整合了服务中心的功能,具体可以分为 naming 和 config 两部分。对于日志和链路追踪,我们采用了阿里云提供的 SLS 和 Arms,这些工具能有效支持我们的监控和故障排查需求。

  • 子服务定义

接下来,我们定义了几个关键的子服务:

  • HTTP 服务:基于 Hertz 框架,分为 Rest 和 Admin Rest。Rest 服务主要服务于面向消费者的客户端业务,而 Admin Rest 专用于后台管理系统的接口。
  • RPC 服务:使用 Kitex 框架的 Protobuf 协议,适用于内部通信需求。
  • 后台进程:包括 Task 和 Worker。Task 主要处理定时任务,而 Worker 设计以支持扩容,例如作为 Kafka consumer。
  • 统一的SDK

    • Common 库:这是一个包含多种基础组件的通用库,如网络库、数据库接口(Mongo、MySQL)、Redis、MQ 和日志配置中心等。Go 1.18 对泛型的支持使得在 util 工具集中加入了大量针对 slice 的工具函数,类似于 JavaScript 中的 map、reduce、filter 方法,这些工具函数支持函数式编程,提高了代码的可用性和清晰度。
    • Support 库:这是一个高度集成的内部库,整合了 Comment 和 IDL,提供多种中间件功能,如鉴权、限流、签名和公共参数等。它还包括 RPC 代理客户端,使业务开发无需每次都自行处理客户端调用,而可以直接利用 Support 库简化开发过程。

通过这样的组件和服务定义,我们确保了项目的模块化和高效性,同时也便于未来的扩展和维护。

三、框架搭建实践

1. 配置中心和入参处理

关于配置中心的设计,我们设定了每个数据集必须实现的三个接口:Name、FileType 和 Init。Nacos 利用 namespace、group 和 dataid 三个参数来确定一个唯一的数据集。其中,namespace 用于区分不同的服务环境,group 用来区分不同的服务,而 dataid 用于区分具体的数据集。在这里,name 对应于 dataid,而 group 则设定了配置加载的优先级。

例如,在配置体系中,具有相同服务名或仓库名的配置项将被归入同一个 group,例如 default_group 可用于通用配置如 MQ、DB 等。区分服务名和仓库名的主要考虑是为了共享配置。以“user”服务为例,user restuser RPC 等可能共享多个配置参数,因此可以统一使用“user”作为 group 名来表示这些服务共用的数据集。此外,该配置中心支持热更新功能,可以通过监听(listen)机制实现配置的实时更新和服务实例的重新加载(reload)。

在参数处理方面,我们的实现与 Hertz 提供的脚手架略有不同。Hertz 框架更倾向于使用 IDL 来定义数据结构,而我们在 RPC 服务中采用 IDL 定义接口和数据类型。在 Hertz 中,我们采用用户自定义的 schema 形式来进行数据验证。

为了实现有效的数据验证,我们使用了 validator 库及其 validate 标签来进行字段验证,并通过自定义 default 标签来设置字段的默认值。同时,我们利用了递归反射机制来处理复杂的数据结构,确保数据的正确性和操作的灵活性。

通过这种设计,我们能够确保配置管理的灵活性和服务的高可用性,同时保持配置更新的即时性和准确性。这些设计选择有助于提高系统的整体稳定性和可维护性。

针对原有的 validator 库在处理切片和 map 类型时的不足,我进行了自定义的扩展实现。这种改进主要是为了增强数据验证的灵活性和精确性,特别是在处理复杂数据结构时。在我们的系统中,由于使用了 Protocol Buffers (PB) 来定义数据模型,这些数据模型通过 IDL 进行规范。

2. Rest 结构定义

在我们的系统设计中,Routeroutput 结构起着核心作用。Router 结构负责管理路由组和路由端点,其中每个路由组都可能包含一个固定的前缀,与特定的业务方法相对应。这样的设计允许路由逻辑清晰地分组和管理。另一方面,output 结构定义了标准化的 HTTP 响应格式,包括 HTTP 状态码、业务码、消息文本、具体的业务数据、错误信息及请求 ID 等属性。这确保了响应的一致性和完整性,便于前端处理和后续的问题追踪。

3. Hertz 启动和 Kitex 启动

  • Hertz 启动

以下是对 Hertz 框架的改造流程详述。首先,右边三个组件由 common 库提供,中间组件来源于 support 库,左边则为用户的业务部分。改造流程开始于服务组件的注册,包括消息队列(MQ)、追踪工具(OT)、数据库等。接着,在业务部分,我们选择必要的接口和中间件,例如设置哪些接口需要登录验证、哪些接口需要添加数字签名,并将这些配置绑定到路由上。

随后,路由被接入处理引擎,此处添加了错误处理器(error handler)和响应渲染器(render)。我们自定义了结构以适配引擎,并从配置中心读取相关参数,注入到启动选项中以启动服务。服务启动后,我们还会开启一个健康检查端口以监控服务状态。

  • Kitex 启动

Kitex 的启动流程与 Hertz 相似,特别是在 IDL 的使用上。我们可以在 IDL 中预先添加标签(tag),例如字段是否必填、数据类型(例如 IP 地址)、数值范围(例如必须大于某个值)。在中间件中,我们解析请求的具体信息和正文(body),根据数据类型进行验证并应用默认值。这些步骤确保了请求处理的准确性和一致性,与 Hertz 的其他部分处理相似。

四、微服务可观测性体系建设

1. 日志体系优化

在接下来的可观测性集成中,日志系统的改造尤其关键。我们已经实现了对多种日志库(包括 zap、logrus 和 slog)的兼容,并开发了 hlog 和 klog 接口,确保通过这些接口打印的日志信息均能被正确输出。此外,我们的日志系统支持以字段增强型(with field)和 JSON 格式输出,日志既可以直接通过 stdout 输出到控制台,也可以通过日志轮转功能(rotate)生成日志文件,并与跟踪系统(trace)集成。

关键的改进是在于上下文信息的处理。我们建议使用 ctx info 或 ctx error 方法来打印日志,以便在日志中自动携带上下文相关信息。为此,我预设了一些关键的键值(key),使得在打印日志时,如果上下文中存在这些预设的键,则它们对应的值(value)将自动被输出。这一功能对于之后实现日志的自动化提取和分析至关重要。

我们的 AccessLog 实现与常见社区提供的中间件不同,因为我们选择了自行开发。这一决策主要基于社区中间件的通用性,它们主要记录如 IP、用户代理(UA)等基础信息。然而,我们的业务需求更为特定,例如需要记录用户 ID、设备 ID、版本号等详细信息,这些是标准中间件无法直接提供的。

需要特别指出的是,我们的中间件设计允许通过c.next方法区分前置和后置操作,从而在请求处理流程中灵活插入额外的业务逻辑。许多关键信息需要被嵌入到上下文(context)中以便在整个请求生命周期内访问。Access Log 模块位于common库中,其加载顺序较靠前,以确保日志记录尽可能全面。

关于上下文的处理,值得注意的是,尽管 context 本身是线程安全的,通过c.next操作本身并不改变 context 的内容。因此,我们通过在处理流程中动态调整c指针,并向其设置新的 context 值,来实时更新 context 信息。这种方法确保了我们能够准确捕捉并记录请求处理的各个阶段的详细信息,对系统监控和问题排查极为重要。

2. Hertz 日志处理

以下是 Hertz 框架下的日志处理流程概述。客户端在发起请求时,会将重要信息如 request ID、设备 ID 和端 token 版本号等打包进 HTTP header,然后向服务端发送请求。这个请求首先通过 tracing middleware,然后进入服务端处理。服务端首先通过解析 HTTP header 来获取一些通用信息,例如 ip、ua、method、uri、requestid 等。这些信息在服务端的 middleware 处理流程中被使用,接着请求会经过 access middleware(其位置设置在流程的前端)。在 access middleware 中,会从 HTTP 头部提取更具体的信息,如 platform、version、deviceid、userid,然后请求传递到业务业务 handler 并生成 responer。

关于responerrender的区别:responer负责构建最终的响应结果,包括业务码、状态码和返回信息。而render则处理如何将这些信息格式化并输出给前端,包含诸如 code、data、message 等字段,业务可以根据需求来自定义这些输出字段,并通过调整render的实现来修改它们。如果响应处理时间超过设定的预警阈值,系统会记录一条警告日志。同样,如果客户端的请求处理也触发了预警条件,相应的警告日志将被打印。

最后,关于监控指标(metrics),我们目前主要使用社区提供的中间件,这已经满足了我们的基本需求。默认的监控指标包括 QPS、latency、以及运行时信息等。这提供了一个基本的视图来监控和评估系统性能。

3. 指标体系构建

我们为用户提供了 Prometheus 的注册句柄(registry handle),这可以在服务启动时通过with registry方法注入到中间件中。一旦句柄被注入,业务团队便可以直接利用此句柄来上报自定义指标。

关于我们的指标监控图表,我们根据不同的请求方法(method)、路径(path)及状态码(status code)进行了分类。考虑到我们的服务接口响应速度的差异,我们将指标图分为三个类别:快速、中速和慢速,这样做可以防止所有数据集中显示在一张图上,从而避免了数据分布的不均。这种分类方法有助于我们更清晰地监控和分析性能问题。

4. 链路追踪和问题告警

OpenTelemetry 实现了全链路追踪,我们采用了一种通用的提供者(provider)模式。在生成 trace ID 时,我们开发了一个 ID Generator 接口。此接口首先尝试将 context 中的 request ID 用作 trace ID;如果 trace ID 不存在或不符合预定标准,那么将默认使用随机生成规则。我们使用 trace context 和 baggage 来实现跨服务的信息传播。

在全链路追踪的工具和组件方面,社区已提供了许多实用的解决方案。对于 MySQL、MongoDB、Redis、Elasticsearch 等组件,我们可以按照官方的建议进行配置以实现最佳性能。 此外,这里是我们使用 ARMS 的截图,它详细展示了追踪链的具体时间、相关 resource、span、event 等信息,以及一些基础的性能指标概况。这些信息帮助我们更好地理解和优化系统的性能。

我们的问题告警系统结合了飞书和 Sentry 两种工具。在飞书方面,我们基于日志数据制定了一系列告警规则,并将这些告警信息定期发送至指定的飞书群组中,以便及时通报团队。这种方法适合于监控日常运行中可能出现的各类问题。对于更深入地理解和分析具体错误,我们使用 Sentry。Sentry 能够提供详细的错误堆栈信息,这对于定位问题和理解错误的原因极为有用。这样的配置使我们能够快速响应并解决问题,同时保持团队对系统状态的持续关注。

五、转型成果总结与未来规划

转型成果显著提升

在这次系统升级的总结中,我们取得了显著的成果。最初,我们的架构仅包含一个服务,而现在已经扩展到超过 20 个服务。以对话服务为例,我们实现了显著的资源优化:原本每个 Pod 需要 5GB 的资源,现在只需 500MB。这一改进使得资源需求降低了大约 90%,Pod 数量减少了约 80%,而响应时延也缩短了约 60%。

在开发流程方面,我们也观察到了 Git 冲突的显著减少。由于 Python 在 import 方面没有强制要求,多人协作时容易产生代码冲突,我们的优化措施有效地减少了这一问题。

服务分级与管理策略

此外,我们对不同的服务进行了分级管理。核心服务如对话、用户和商业化等,配备了高性能的存储系统,具有更长的日志保留时间,并且上线流程需要经过更多的 leader 审批,同时配备了专门的告警策略。对于重要服务,如首页展示、推荐和活动等,则配置了独立的存储资源,上线过程不需要 leader 审批,但每个项目都必须至少具备一名backup负责人,并采用通用告警策略,确保服务的稳定和数据的安全。对于一般性的服务,如分享后台服务、实验性功能,可能会共享存储资源。这些服务允许由单一人员负责,其迭代和开发周期较短。

我们的系统升级还带来了其他多项益处。更新 SDK 后,用户可以体验到新功能以及多种容错机制和性能优化的成果。对于新加入的开发人员,只需设置少量环境变量,如配置中心地址、运行环境等,如有必要通过配置中心调整其他复杂参数,即可拉取代码仓库并运行服务,极大降低了入门难度。

此外,通过使用常见的工具库,减少了重复开发的工作量。我们预设了大量的门面方法并提供了扩展接口,业务团队可以根据需要实现自定义功能,或者直接使用现有功能。在服务退出时,系统会自动回收如连接池等 provider 资源。统一的日志格式和部署流程也使得运维工作更加高效友好。

值得一提的是,Python 在请求配置中心时采用轮询机制来获取最新的配置信息,而 Golang 则通过长链接实现配置的被动更新。Python 需要配置大量的 worker 来提高并发处理能力,这可能对配置中心造成较大的负担。

系统架构图与框架 feature

这是我们当前的系统架构图,涵盖了服务层、网关层,以及由框架提供的协议层、治理机制、基础组件和存储组件等。

在流式开发中,我们直接使用字节跳动提供的 SSE 包,该包未经过多余封装,主要用于管理多个通信频道。具体应用场景包括,通过调用模型获取特定的 channel,并将其传递至最外层的 Controller 层,从而实现公共消息(public message)的功能。在未来的应用开发中,我们将针对流式调用做更多优化。

我们的服务中心主要实现了服务注册功能,而在大多数情况下,我们还是会使用 Kubernetes 的服务发现机制。未来,我们希望各服务之间可以进行更深入的交互,并更广泛地应用元数据(MetaData)来增强服务间的感知能力。

在技术兼容性方面,由于我们的系统中还包含使用 Python 开发的服务,这在调用 kitex 时偶尔会遇到兼容性问题。虽然这些问题通常不会严重影响系统功能,但有时可能会出现警告提示。我们将持续与相关团队沟通,以解决这些问题。此外,我们也致力于将系统中稳定的功能通过 PR 的方式贡献回开源社区。

智谱清言的技术转型之旅标志着从单体架构向微服务架构的成功过渡,彰显了在业务快速增长与技术迭代中寻求平衡的智慧。通过采用 CloudWeGo 框架,我们不仅提高了系统的处理能力和扩展性,还增强了服务的可维护性和开发效率。此外,系统架构的优化使资源消耗大幅下降,显著提升了性能,为用户提供了更加稳定和高效的服务体验。期待与CloudWeGo 社区的小伙伴们继续共同努力,推动行业的技术进步和创新。

相关链接:

  • CloudWeGo: github.com/cloudwego

    • Hertz: github.com/cloudwego/h…
    • Kitex: github.com/cloudwego/k…
  • 本次演讲视频回顾:www.bilibili.com/video/BV186…

  • PPT下载链接:github.com/cloudwego/c…

相关文章:

  • 软件设计师“UML”真题考点分析——求三连
  • Triton介绍和各平台支持情况分析
  • 电路研究9.3.6——合宙Air780EP中的AT开发指南:FTP 应用指南
  • EMC风险评估详解
  • DApp开发全流程解析:模式设计、功能参考与合约管理实践
  • 【IDEA】删除/替换文件中所有包含某个字符串的行
  • 现阶段十个Agent协议概览
  • Redis学习打卡-Day4-Redis实现消息队列
  • React Flow 中 Minimap 与 Controls 组件使用指南:交互式小地图与视口控制定制(含代码示例)
  • ArcGIS Pro 3.4 二次开发 - 框架
  • 「HHT(希尔伯特黄变换)——ECG信号处理-第十三课」2025年5月19日
  • javascript 编程基础(2)javascript与Node.js
  • 关于VSCode按住Ctrl或Command点击鼠标左键不能跳转的问题
  • 2021-10-29 C++求位数及各位和
  • Canvas设计图片编辑器全讲解(一)Canvas基础(万字图文讲解)
  • 山东大学计算机图形学期末复习14——CG14下
  • 50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | Progress Steps (步骤条)
  • redis的List为什么用ziplist和quicklist
  • GitHub 趋势日报 (2025年05月18日)
  • 学习记录:DAY29
  • 申伟强任上海申通地铁集团有限公司副总裁
  • 上影节公布今年IMAX片单:暗涌、重生与感官的史诗
  • 三件珍贵标本开箱!中国恐龙大展5月26日在沪开幕,明星标本汇聚一堂
  • 专利申请全球领先!去年我国卫星导航与位置服务产值超5700亿
  • 陈刚:推动良好政治生态和美好自然生态共生共优相得益彰
  • 气急败坏!20多名台湾艺人被台当局列为“重点核查对象”