​grafana 的主体架构是如何设计的?

grafana 是非常强大的可视化项目,它最早从 kibana 生成出来,渐渐也已经形成了自己的生态了。研究完 grafana 生态之后,只有一句话:可视化,grafana 就够了。

这篇就想了解下它的主体架构是如何设计的。如果你对 grafana 有兴趣,不妨让这篇成为入门读物。

入口代码

grafana 的最外层就是一个 build.go,它并不是真正的入口,它只是用来编译生成 grafana-server 工具的。

grafana 会生成两个工具,grafana-cli 和 grafana-server。

go run build.go build-server 其实就是运行

  1. go build ./pkg/cmd/grafana-server -o ./bin/xxx/grafana-server

这里可以划重点学习一下:

如果你的项目要生成多个命令行工具,又或者有多个参数,又或者有多个操作,使用 makefile 已经很复杂了,我们是可以这样直接写个 build.go 或者 main.go 在最外层,来负责编译的事情。

所以真实的入口在 ./pkg/cmd/grafana-server/main.go 中。可以跟着这个入口进入。

设计结构

这篇不说细节,从宏观角度说下 grafana 的设计结构。带着这个架构再去看 granfana 才更能理解其中一些细节。

grafana 中最重要的结构就是 Service。 grafana 设计的时候希望所有的功能都是 Service。是的,所有,包括用户认证 UserAuthTokenService,日志 LogsService, 搜索 LoginService,报警轮训 Service。 所以,这里需要设计出一套灵活的 Service 执行机制。

理解这套 Service 机制就很重要了。这套机制有下列要处理的地方:

注册机制

首先,需要有一个 Service 的注册机制。

grafana 提供的是一种有优先级的,服务注册机制。grafana 提供了 pkg/registry 包。

在 Service 外层包了一个结构,包含了服务的名字和服务的优先级。

  1. type Descriptor struct {
  2. Name string
  3. Instance Service
  4. InitPriority Priority
  5. }

这个包提供的三个注册方法:

  1. RegisterServiceWithPriority
  2. RegisetrService
  3. Register

这三个注册方法都是把 Descriptior(本质也就是 Service)注册到一个全局的数组中。

取的时候也很简单,就是把这个全局数组按照优先级排列就行。

那么什么时候执行注册操作呢?答案就是在每个 Service 的 init() 函数中进行注册操作。所以我们可以看到代码中有很多诸如:

  1. _ "github.com/grafana/grafana/pkg/services/ngalert"
  2. _ "github.com/grafana/grafana/pkg/services/notifications"
  3. _ "github.com/grafana/grafana/pkg/services/provisioning"

的 import 操作,就是为了注册服务的。

Service 的类型

如果我们自己定义 Service,差不多定义一个 interface 就好了,但是实际这里是有问题的。我们有的服务需要的是后端启动,有的服务并不需要后端启动,而有的服务需要先创建一个数据表才能启动,而有的服务需要根据配置文件判断是否开启。要定义一个 Service 接口满足这些需求,其实也是可以的,只是比较丑陋,而 grafana 的写法就非常优雅了。

grafana 定义了基础的 Service 接口,仅仅需要实现一个 Init() 方法:

  1. type Service interface {
  2. Init() error
  3. }

而定义了其他不同的接口,比如需要后端启动的服务:

  1. type BackgroundService interface {
  2. Run(ctx context.Context) error
  3. }

需要数据库注册的服务:

  1. type DatabaseMigrator interface {
  2. AddMigration(mg *migrator.Migrator)
  3. }

需要根据配置决定是否启动的服务:

  1. type CanBeDisabled interface {
  2. IsDisabled() bool
  3. }

在具体使用的时候,根据判断这个 Service 是否符合某个接口进行判断。

  1. service, ok := svc.Instance.(registry.BackgroundService)
  2. if !ok {
  3. continue
  4. }

这样做的优雅之处就在于在具体定义 Service 的时候就灵活很多了。不会定义很多无用的方法实现。

这个也是 golang 鸭子类型的好处。

Service 的依赖

这里还有一个麻烦的地方,Service 之间是有互相依赖的。比如 sqlstore.SQLStore 这个服务,是负责数据存储的。它会在很多服务中用到,比如用户权限认证的时候,需要去数据存储中获取用户信息。那么这里如果在每个 Service 初始化的时候进行实例化,也是颇为痛苦的事情。

grafana 使用的是 facebook 的 inject.Graph 包处理这种依赖的问题的。https://github.com/facebookarchive/inject。

这个 inject 包使用的是依赖注入的解决方法,把一堆实例化的实例放进包里面,然后使用反射技术,对于一些结构中有指定 tag 标签的字段,就会把对应的实例注入进去。

比如 grafana 中的:

  1. type UserAuthTokenService struct {
  2. SQLStore *sqlstore.SQLStore `inject:""`
  3. ServerLockService *serverlock.ServerLockService `inject:""`
  4. Cfg *setting.Cfg `inject:""`
  5. log log.Logger
  6. }

这里可以看到 SQLStore 中有额外的注入 tag。那么在 pkg/server/server.go 中的

  1. services := registry.GetServices()
  2. if err := s.buildServiceGraph(services); err != nil {
  3. return err
  4. }

这里会把所有的 Service (包括这个 UserAuthTokenService) 中的 inject 标签标记的字段进行依赖注入。

这样就完美解决了 Service 的依赖问题。

Service 的运行

Service 的运行在 grafana 中使用的是 errgroup, 这个包是 “golang.org/x/sync/errgroup”。

使用这个包,不仅仅可以并行 go 执行 Service,也能获取每个 Service 返回的 error,在最后 Wait 的时候返回。

大体代码如下:

  1. s.childRoutines.Go(func() error {
  2. ...
  3. err := service.Run(s.context)
  4. ...
  5. })
  6. }
  7. defer func() {
  8. if waitErr := s.childRoutines.Wait(); waitErr != nil && !errors.Is(waitErr, context.Canceled) {
  9. s.log.Error("A service failed", "err", waitErr)
  10. if err == nil {
  11. err = waitErr
  12. }
  13. }
  14. }()

总结

理解了 Service 机制之后,grafana 的主流程就很简单明了了。如图所示。当然,这个只是 grafana 的主体流程,它的每个 Service 的具体实现还有待研究。

​grafana 的主体架构是如何设计的?的更多相关文章

  1. 认证鉴权与API权限控制在微服务架构中的设计与实现(四)

    引言: 本文系<认证鉴权与API权限控制在微服务架构中的设计与实现>系列的完结篇,前面三篇已经将认证鉴权与API权限控制的流程和主要细节讲解完.本文比较长,对这个系列进行收尾,主要内容包括 ...

  2. atitit.系统架构图 的设计 与工具 attilax总结

    atitit.系统架构图 的设计 与工具 attilax总结 1. 架构图的4个版式(标准,(左右)悬挂1 2. 架构图的层次结构(下属,同事,助手)1 3. wps ppt1 4. 使用EDraw画 ...

  3. 了解Kubernetes主体架构(二十八)

    前言 Kubernetes的教程一直在编写,目前已经初步完成了以下内容: 1)基础理论 2)使用Minikube部署本地Kubernetes集群 3)使用Kubeadm创建集群 接下来还会逐步完善本教 ...

  4. Java生鲜电商平台-App系统架构开发与设计

    Java生鲜电商平台-App系统架构开发与设计 说明:阅读此文,你可以学习到以下的技术分享 1.Java生鲜电商平台-App架构设计经验谈:接口的设计2.Java生鲜电商平台-App架构设计经验谈:技 ...

  5. zz《分布式服务架构 原理、设计与实战》综合

    这书以分布式微服务系统为主线,讲解了微服务架构设计.分布式一致性.性能优化等内容,并介绍了与微服务系统紧密联系的日志系统.全局调用链.容器化等. 还是一样,每一章摘抄一些自己觉得有用的内容,归纳整理, ...

  6. ABSD 基于架构的软件设计方法方法简介(摘抄)

    ABSD(Architecture-Based Software Design)基于架构的软件设计方法 有三个基础: 第一个基础是功能分解.在功能分解中,ABSD方法使用已有的基于模块的内聚和耦合技术 ...

  7. 理解RESTful架构——Restful API设计指南

    理解RESTful架构 Restful API设计指南 理解RESTful架构 越来越多的人开始意识到,网站即软件,而且是一种新型的软件. 这种"互联网软件"采用客户端/服务器模式 ...

  8. 使用微服务架构思想,设计部署OAuth2.0授权认证框架

    1,授权认证与微服务架构 1.1,由不同团队合作引发的授权认证问题 去年的时候,公司开发一款新产品,但人手不够,将B/S系统的Web开发外包,外包团队使用Vue.js框架,调用我们的WebAPI,但是 ...

  9. Django高级篇一RESTful架构及API设计

    一.什么是RESTful架构? 通过互联网通信,建立在分布式体系上"客户端/服务器模式”的互联网软件,具有高并发和高延时的特点. 简单的来说,就是用开发软件的模式开发网站.网站开发,完全可以 ...

随机推荐

  1. selenium WebDriver提示Unable to find a matching set of capabilities解决方法

    问题出在:应该将火狐浏览器驱动添加到火狐浏览器安装目录下,并且将火狐浏览器安装目录放在path下面.(出现大意,忘了在火狐浏览器下放其对应的驱动) 亲测以下组合方式可用:   pycharm-comm ...

  2. hashmap(有空可以看看算法这本书中对于这部分的实现,很有道理)

    //转载:https://baijiahao.baidu.com/s?id=1618550070727689060&wfr=spider&for=pc 1.为什么用HashMap? H ...

  3. Idea中如何导入jar包

    1.首先在idea左上角找到" File ",然后找到 "Project structure" 2.接着选择 " java ",选择后接着会 ...

  4. Java集合【8】-- ArrayList源码分析

    目录 1. ArrayList 1.1 ArrayList特点介绍 1.2 实现的接口和继承的类 2. 成员变量 3. 构造方法 4. 常用增删改查方法 添加元素 查询元素 更新元素 删除元素 5.自 ...

  5. C#中From窗体判断提示框是否

    int ret(变量名1) = command.ExecuteNonQuery(); if (ret(变量名1) > 0)//判断变量名是否大于0 { DialogResult dr(变量名2) ...

  6. python 爬虫 汽车之家车辆参数反爬

    水平有限,仅供参考. 如图所示,汽车之家的车辆详情里的数据做了反爬对策,数据被CSS伪类替换. 观察 Sources 发现数据就在当前页面. 发现若干条进行CSS替换的js 继续深入此JS 知道了数据 ...

  7. SQL Alias模板

    再记不住的话就扇自己巴掌 SELECT * FROM class1 c INNER JOIN class2 p ON c.id = p.id INNER JOIN class3 s ON c.id = ...

  8. PyQt(Python+Qt)学习随笔:QTableWidgetItem项文本和项对齐的setText、setTextAlignment方法

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 QTableWidget部件中的QTableWidgetItem项的文本可以通过text()和set ...

  9. c#如何取出指定的中间文本

    ///<summary> ///取出文本中间内容 ///<summary> ///<param name="left">左边文本</par ...

  10. 题解-Magic Ship

    Magic Ship 你在 \((x_1,y_1)\),要到点 \((x_2,y_2)\).风向周期为 \(n\),一个字符串 \(s\{n\}\) 表示风向(每轮上下左右),每轮你都会被风向吹走一格 ...