作者|宋瑞国(尘醉)
来源|尔达 Erda 公众号
导读:Erda Infra 微服务框架是从 Erda 项目演进而来,并且完全开源。Erda 基于 Erda Infra 框架完成了大型复杂项目的构建。本文将全面、深入地剖析 Erda Infra 框架的架构设计以及如何使用。
背景
在互联网技术高速发展的浪潮中,众多的大型系统慢慢从单体应用演变为微服务化系统。
单体应用
单体应用的优势是开发快速、部署简单,我们不需要考虑太多就能快速构建出应用,很快地上线产品。
然而,随着业务的发展,单体程序慢慢变得复杂混乱,非常容易改出 bug,体积也变得越来越大,当业务量上来的时候,很容易崩溃。
微服务架构
大型系统往往采用微服务架构,这种架构把复杂的系统拆分成了多个服务,微服务之间松耦合、微服务内部高内聚。
同时,微服务架构也带来了一些挑战。服务变多,对整个系统的稳定性是一种挑战,比如:该如何处理某个服务挂了的情况、服务之间如何通讯、如何观测系统整体的状况等。于是,各种各样的微服务框架诞生了,采用各种技术来解决微服务架构带来的问题,Spring Cloud 就是一个 Java 领域针对微服务架构的一个综合性的框架。
云平台
Spring Cloud 提供了许多技术解决方案,然而对于企业来说,运维成本还是很高。企业需要维护各种中间件和众多的微服务,于是出现了各种各样的云服务、云平台。
Erda (https://github.com/erda-project/erda) 是一个针对企业软件系统在开发阶段和运维阶段进行全生命周期管理、一站式的 PaaS 平台,在各个阶段都能够解决微服务带来的各种问题。
Erda 本身也是一个非常大的系统,它采用微服务架构来设计,同样面临着微服务架构带来的问题,同时对系统又提出了更多的需求,我们希望实现:
- 系统高度模块化
- 系统具有高扩展性
- 适合多人参与的开发模式
- 同时支持 HTTP、gRPC 接口、能自动生成 API Client 等
另一方面,Erda 的开发语言是 golang,在云原生领域,golang 是一个主流的开发语言,特别适合开发基础的组件,Docker、Kubernetes、Etcd、Prometheus 等众多项目也都选用 golang 开发。不像 Spring Cloud 在 Java 中的地位,在 golang 的生态圈里,没有一个绝对霸主地位的微服务框架,我们可以找到许多 web 框架、grpc 框架等,他们提供了很多工具,但不会告诉你应该怎么去设计系统,不会帮你去解耦系统中的模块。
基于这样的背景,我们开发了 Erda Infra 框架。
Erda Infra 微服务框架
一个大的系统,一般由多个应用程序组成,一个应用程序包含多个模块,一般的应用程序结构如下图所示:
这样的结构存在一些问题:
- 代码耦合:一般会在程序最开始的地方,读取所有的配置,初始化所有模块,然后启动一些异步任务,而这个集中初始化的地方,就是代码比较耦合的地方之一。
- 依赖传递:因为模块之间的依赖关系,必须得按照一定的顺序初始化,包括数据库 Client 等,必须得一层层往里传递。
- 可扩展性差:增删一个模块,并不那么方便,也很容易影响到其他模块。
- 不利于多人开发:如果一个应用程序里的模块是由多人负责开发的,那么也很容易互相影响,调试一个模块,也必须得启动整个应用程序里的所有模块。
接下来我们通过几个步骤来解决这些问题。
构建以模块驱动的应用程序
我们可以将整个系统拆分为一个个小的功能点,每一个小的功能点对应一个微模块。整个系统像拼图、搭积木一样,自由组合各种功能模块为一个大的模块作为独立的应用程序。
这也意味着我们无需担心整个系统的服务过多、过于分散,只需要专注于功能本身的拆分。微服务不仅存在于跨节点的多进程之间,也同样存在于一个进程内。
我们利用 Erda Infra 框架来定义一个模块:
package example
import (
"context"
"fmt"
"time"
"github.com/erda-project/erda-infra/base/logs"
"github.com/erda-project/erda-infra/base/servicehub"
)
// Interface 以接口的形式,对外提供本模块的功能
type Interface interface {
Hello(name string) string
}
// config 声明式的配置定义
type config struct {
Message string `file:"message" flag:"msg" default:"hi" desc:"message to print"`
}
// provider 代表一个模块
type provider struct {
Cfg *config // 框架会自动注入
Log logs.Logger // 框架会自动注入
}
// Init 初始化模块。可选,如果存在,会被框架自动调用
func (p *provider) Init(ctx servicehub.Context) error {
p.