Apicon 背后都用到的哪些技术?

Apicon 的来源

大概是高二的时候,因为 WordPress 的关系,我接触到了 Web API。进而了解到了 Ajax、RESTful 等概念。当时百度有一个 API Store,上面收录了大大小小各种各样的 API。从天气查询到语音识别,免费并配有详细文档以及不同语言的使用示例。
当时这对我来说真的是个宝库。与此同时,我在 GitHub 上也找到了个人整理的 QQ 音乐、网易云音乐、bilibili 等网站的 API。因此,我在那时也做了些小东西。比如用 API Store 里的宅言 API,做的一个 WordPress 小挂件,那是我写的第一行 PHP。之后还有做的比较大但已经弃坑了的 Love Bangumi 等。
但好景不长,API Store 从原来的开放免费,逐渐走向了商业化道路,最终关站了。QQ 音乐也修复了可以免费下载音乐的接口。

但我还是十分怀念 API Store 在的时候,我能用各种各样的接口丰富自己的项目。因此我想建一个网站,在上面发布很多 API 的接口信息,也算是交个朋友吧。
之前尝试过用 PHP 写过一点,不过中途放弃了。这次沉住气,用 Golang 做出来了 —— Apicon。

首席架构师??

说实话,自己一个人从头到尾写一个项目真的挺爽的。从前端到后端,从开发到部署再到运维。
期间有一次比较大的改动,当时我决定将写了一半的前端全部推倒重来。因为最初的第一版前端是使用 Vue 写的单页应用。像前后端分离这种架构,若是两人合作开发还好,大家可以各自做各自的事,互不干扰。但若是只有一个人的话,前后端分离就把这两个部分“隔得太远了”,直接换了个 IDE,给人一种不适应感。因此当时我决定前端重新写,这次就是使用 beego 框架做的 MVC 模式了。果然一个人还是适合用 MVC 。
再这之后便没有太大的变动了。开发 Apicon 确实让我学到了不少新技术。那么下面我就来介绍下吧。先上一张我做的架构图:

Apicon 架构图

然后我来分块介绍 Apicon 用爱发电架构。

轻量应用服务器,用爱发电

为什么说是用爱发电呢,因为 Apicon 的域名、服务器、CDN 费用支出,全是我自己掏钱。嘛,全靠对 API 的热爱吧,毕竟网站也是叫 API 热爱者。当然也欢迎捐款啦。
服务器是阿里云的轻量应用服务器,地区香港,配置 1 核 2G 内存,每月 1000 G 流量。选用香港服务器是因为我之后会将封装好的哔咔哔咔 API 放到 Apicon 上面来,而哔咔哔咔在大陆是被墙的。(不知道我的服务器会不会因此也被墙)之所以选择 2G 内存,是因为在 1G 内存的配置下,Drone CI 编译 Golang 会因为内存不够而失败。

MySQL

数据库是 MySQL 8.0,之前问过 Moesang,他说在我当前服务器的配置下,百万级别的数据量查询是能够承受的住的。因此我也就放心了。之前听说腾讯云的 MySQL 数据库十分便宜。但是想着服务器是阿里云,数据库若放腾讯云,两边不是通过内网访问,速度上怕有一定的影响。

Apicon 主站 – beego

Apicon 主站使用 Go 语言开发,并使用了 beego 框架。之所以选用 beego 是因为它的文档十分完善,很多场景下遇到的问题都能在文档里找到解决的办法。并且相比 Gin,beego 更适合搭建 MVC 模式的站点。唯一美中不足的是,beego 的中间件没有 Gin 的强大。beego 中是使用钩子来实现 Gin 中的中间件功能的。然而它整个控制器生命周期的钩子只有那么几个,无法满足不同的需求。

阿里云函数计算服务

主站中调用了阿里云函数计算服务中的函数,使用 Aligo 将 API Blueprint 的 Markdown 渲染为 HTML。只可惜阿里云的轻量应用服务器不像 ECS 一样支持 VPC 专有网络,因此我无法直接通过内网来调用函数。
更多信息我在之前的这篇文章中做了详细说明:阿里云函数计算 + Aglio 实现 API Blueprint markdown 渲染

CDN

之前曾打算把 Apicon 放到国内天翼云的服务器上,因此也注册了apicon.cn域名并备案。只可惜天翼云的服务器备案审核过于繁琐,因此作罢。但备案了的apicon.cn域名刚好可以用来开通七牛云的 CDN 服务。目前主站上传的图片资源全部放在七牛云存储,全球 HTTPS 访问很舒服。

Drone CI / 阿里云容器镜像服务

Apicon 主站以及网关都是使用 Golang 开发,因此需要 Drone 来做自动部署、持续集成。Drone 与 Jenkins 的不同之处在于,Drone 是基于 Docker 的,它可以把我的项目编译后并生成容器镜像,然后进行容器的部署。我将项目的代码存放在 GitHub 私有仓库内,使用 Webhook 调起 Drone 进行编译部署。同时,我使用了阿里云的容器镜像服务来存储 Drone 生成的镜像。阿里云的容器镜像服务可以保存历史多个版本的镜像,使得我可以回滚到前面的版本。
更多的配置细节可以看我的这篇文章:拉取、编译、部署,无人机升空!

API 网关

这是本篇文章的重头戏。Apicon 除了提供相关 Api 的文档外,还为一些非公开的,鉴权过多难以直接调用的接口提供了封装。我建立了一个 GitHub 组织来开源这些封装过的 Api 源码。我的想法是,全部使用 Go 开发,但是又想做成可扩展的,不用每加入新的功能就全部重新编译部署。最好还可以实现随时关闭开启相关接口。
最初和副主席讨论的时候,他推荐我去看看微服务。然而我在网上看了好几篇相关的文章,还是感觉云里雾里的。像 Bilibili 使用的 Kratos,就是他们自己开源的一套 Go 微服务框架。但是对我来说还是太复杂了。涉及的技术太多了,可能一段时间内还是不太能理解吧。

微服务框架看不下去了,又去求助副主席,他说我可以借鉴助手现在 Gateway 的这一套架构。助手现在很多个服务的后端都是独立编译,独立部署的。但同时使用一套 Gateway 进行验权调度。因此,我也学着助手的这一套,开发了 Apicon 的 API Gateway。实现了接口调用的统一验权,日志记录;而下面的各个服务又是独立部署,独立运行在各自的容器中,且仅能通过 Gateway 进行调用,因此可以通过 Gateway 随时开启或关闭。

其背后的原理其实很简单,使用 Gin 动态创建路由,然后将用户请求过来的信息再转发给后面容器中的应用即可。我看很多微服务框架都是通过 gRPC 通信的,但我这边很简单,直接本地通过 HTTP 通信。虽然听起来有点 low,但是性能方面并不会有没有什么影响。因此乍一看,API Gateway 其实就是这个反向代理,通过其访问各个容器内的应用。下面我截取其中一些比较关键的部分聊聊:

动态创建路由

r.Use(s.AuthRequired())
{
    // 路由转发
    for _, value := range router{
        s.GateWay.Any("/" + value.Router + "/*path", func(c *gin.Context){
            c.Data(s.handleRequest(c, value))
        })
    }
}

这边是在使用中间件验权后,然后 for 循环遍历创建路由。其中router则是从数据库中取出的所有开启状态接口的路由信息。因此,只要改变数据库中接口的路由信息,再平滑重启,即可实现不重新编译部署 API Gateway,就能增加、删除新的应用。

请求转发

    ...
    request := gorequest.New()
    request.Url = s.Config.Get("api.baseurl").(string) + ":" + port + path
    request.Method = method
    request.QueryData = query
    request.Header = header
    request.Send(string(body))

    response, responseBody, errs := request.End()
    if errs != nil{
        msg := "Service Unavailable."
        s.routerError(userInfo.(user).ID, userInfo.(user).Name, userInfo.(string), c.Request.RequestURI, c.ClientIP(), msg)
        return s.makeErrJSON(500, 50001, "Service Unavailable.")
    }
    return response.StatusCode, response.Header.Get("Content-Type"), []byte(responseBody)

这里使用gorequest来将用户请求发送过来的参数,请求头,请求体等再内网请求后方的应用。其实就是起到一个反向代理的作用。同时 API Gateway 还可以和后方的应用约定好,可以再传输一些当前请求用户信息等自定义的额外数据。
注意这里内网请求的地址,即s.Config.Get("api.baseurl").(string)这里,我填写的是 Docker 容器Network中的Gateway的 IP,即 Docker 容器外宿主机的 IP,这样每个应用即使没有与 API Gateway 容器link,也可以通过宿主机 IP 访问,因为每个应用的容器都是对外暴露了各自的端口的。

日志记录方面,原本我是打算存在 MySQL 数据库里的,最后想着也没啥要分析的必要,就用logrus直接存本地文件了。
嗯,关于 API 网关大概就是这些了。

TODOs

Apicon 目前是已经上线并开放注册了。但现在只有一个测试 API 可供调用,今后也会多加点有趣的东西上去的。与此同时也还有很多功能要去完善的:

  • 登录注册加入验证码
  • 邮箱验证
  • 用户组设置
  • 赞助付款码
  • API 网关控制页面
  • 搜索功能
  • 日志分块
  • 更详细的 API 分类

还有很多需求要去实现的呢,那今后也请加油吧!

1 条评论

昵称
  1. Michael

    已注册,持续关注中( ´▽`)