关于golang:超全面的Golang实践经验分享

3次阅读

共计 5802 个字符,预计需要花费 15 分钟才能阅读完成。

云联壹云是齐全自研的一套交融云平台,Golang 是次要的开发语言,本文次要介绍介绍在迭代过程中对于 Golang 的教训以及在 Golang 上积攒的框架和库。

在开发过程中,咱们也积攒了 Golang 的库函数,并基于这些库函数去开发框架以及平台,当然还有库的特点,实现库的起因及其长处。

背景介绍

交融云平台—云联壹云是从 2017 年开始逐渐迭代开发,平台在 17 年时是公有云,可能治理在用户部署在本地物理机上的 KVM,同时也能治理裸金属的服务器。

过后企业的 IT 环境并不仅仅是本地的虚拟机以及裸金属,企业的 IT 基础设施曾经逐渐驳回多云的技术。

所以平台不仅能治理本地 IT 环境中的虚拟机和裸金属。还能治理其余云,特地是可能帮忙企业治理私有云的资源,做到所有的资源在一个平台上对立纳管,运维,操作,起到升高运维复杂度并进步企业 IT 运维效率的目标。

平台后端采纳的是 Golang,目前为止已有 60 万行代码,前端采纳的是 Vue 框架,整个平台是基于微服务的框架,每个服务之间的认证鉴权是基于 Keystone 组件。

Golang 积攒

首先是 Golang 的服务框架,所有的组件都是基于这个服务框架来开发,服务框架的特点比拟适宜在咱们平台开发,并且针对平台的特点做出优化,适宜疾速开发服务。


Golang 框架

根本所有服务都是基于这个服务框架开发,此框架是比拟不便做 CRUD 的脚手架框架。

因为服务次要是对云资源的操作,比方云资源的创立、删除、更新等。

因为云的资源十分多,通过脚手架可能比拟不便地实现资源的 CRUD 操作,再加上其余机制实现对云资源的简单操作以及信息回复。

除 CRUD 脚手架外,其实它把平台的特地性能加进去,首先组件之间是基于 keystone 认证,所以在将 keystone 认证加到框架中,则开发不需关注 keystone 认证,只有代码是在框架中实现的,天生就集成了 keystone 认证。

每一个 API 都受到权限的管制,权限管制也集成到框架中,每一个开发者在开发平台相应的 restapi 时,不用为权限写相应的代码,可能人造地将权限管制集成到 API 中。

微服务框架的每一个服务都有相应的配置,如何不便地治理服务配置,并进行更新,同步到相应的组件使其失效,此过程绝对简单,咱们将服务配置的性能集成到框架中,开发者采纳框架不用思考配置的存储、更新、服务器读取更新并使配置失效,这些简单事宜已在框架中解决。

还有异步工作的治理性能,平台能够被认为是一个分布式的零碎,云控制器须要去操作和治理数据计算节点、裸金属的治理节点。协调组件之间的简单操作,例如将虚拟机、裸金属创立起来,这些都是分布式的工作治理,在平台中也嵌入了异步工作治理框架。如此即可较为不便地实现异步工作。

CRUD 脚手架原理

在平台中,每一种资源,例如主机,在底层对应到数据库 MySQL 表,资源的状态、相应的属性都记录到了 MySQL 表中。

用户通过调用 API 对数据进行操作,在数据操作的同时也能做额定的异步的 task,而后去实现相应性能。落到底层代码中就是一种资源对应到一张 MySQL 表。

为了去比拟不便地实现对数据库 MySQL 记录的操作,针对每一个资源,都会对应到 ModelManager 和 Model 的一对数据结构。

ModelManager 数据结构是对应到 Golang 的 structs,这个构造能够实现这一类资源的汇合操作,例如创立资源或者列表,而针对单个资源的操作,则通过 Model 来实现,实现对某个资源的更新、删除的操作。

Model 对应到 Golang 的构造体,构造体有若干字段,每个字段代表资源的属性,例如此处有一个用户的资源,用户的 id、Extra 属性, 用户是否 enabled,用户何时创立,归属的域,都是这个用户资源相应的属性。

这个属性就是 Golang 构造体的字段,通过构造体字段的 Tag 属性,如此即可定义每个字段在 MySQL 的数据库中对应的 schema 的定义。

例如 Id 这个字段,属性中有 width:”64″ charset:”ascii” nullable:”false” primary:”true”

这定义了在 Id 这个字段是数据库外面的一个 varchar(64) 的字段,并且他的字符集是 ascii 码。所以通过 tag 将构造体的字段映射到了 MySQL 的 schema 的字段中,如此,每一个 model 通过字段的定义就可能清晰地映射到 MySQL 的数据表中。

这样咱们就实现了 Model 的字段和 MySQL 的数据表定义的严格同步,每次程序启动时都会查看,如果 Model 的定义和数据表的定义不太统一,而后就会执行相应的 SQL 的变更操作,将表的定义和 Model 的定义变更为统一。

例如咱们将 Id 的宽度从 64 改成 80,在程序重启时就可能发现这个变动,而后将数据表的宽度也变更成 80。

如此即可实现通过代码定义的 Model 和数据库中的表准确地映射上。

每个 Model、资源都会提供一系列的 API,此处曾经列出对一个资源会实现的九类 API。

例如创立、删除、更新、执行操作、获取详情、列表等操作。

每一个操作对应 restapi,每一个 restapi 对应到后端代码中就对应到了每一个资源对应的 Manager 或者 Model 的办法。

例如咱们要获取资源详情的办法就是他 restapi 门路就是 GETresources,resources 的 Id,调用这个 restapi 就映射到相应的 Model 的 GetDetails 的办法。

为了实现获取资源的详情只须要去实现 Model 中的 GetDetails 的办法的内容,如此即可实现 restapi 的性能。

通过框架简化了实现 restapi 的流程,只须要把相应的 Model 和 ModelManager 的办法依据输出实现相应逻辑,而后把正确的输入返回回去,这个 restapi 的性能即可实现。

如此诸如健全、认证、配置、同步等周边的工作即可在框架中实现,从而大大晋升开发效率并升高在开发过程中犯错的几率。

框架中蕴含许多内容,包含认证、权限、配置变更治理、配额治理等。

Golang 库

上面介绍在过来开发过程中积攒的 Golang 库。有利于更加不便、高效地实现须要的性能。

jsonutils 是一个 JSON 序列化和反序列的工具库。

Golang 的规范库中带的库是 encoding/json,encoding/json 也是一个十分弱小、十分高效的序列化和反序列的工具库,上面为大家介绍为何抛开 encoding/json 另外实现 jsonutils 的库。

encoding/json 实现的是 Golang 的数据结构和对应 json 的字符串之间的互相转换。

咱们能够把 Golang 中的构造体通过 Marshal 的形式生成一个 Json 的字符串,或者把 Json 的字符串通过 Unmarshal 放到相应的构造体中的各个字段,这样即可拜访构造体去取得 json 中的这些值。

jsonutils 与 encoding/json 相比的显著区别是两头减少了一个两头态,在 jsonutils 库外面实现 JSONObject。

这是两头态的无类型的数据,咱们能够把数据结构 Marshal(s)成 JSONObject,JSONObject 是 Golang 的 interface。

上层是一个构造体,interface 能够进一步地序列化成 json 字符串。

这样是在 Golang 的构造体和 json 的字符串之间减少了一个两头态。

这个两头态就是须要应用 jsonutils 的重要起因,通过无类型的 JSONObject 就能够实现任意的构造体都能够 Marshal(s)成 JSONObject 而后能够把 JSONObject 作为函数参数进行传递。

Golang 是一个严格类型查看的语言,它的每一个值都有相应的类型,咱们的框架可能解决任意 API 的输入输出,如果没有两头的构造体,在解决 API 的输入输出时,输出是 json 字符串,为了在程序上拜访它,就必须把它反序列化成严格有类型构造体,这样一来就无奈将框架变成通用框架。

如果引入通用的 JSONObject,在框架中输出了 json 字符串,先把它反序列化成 JSONObject,这个 JSONObject 是无类型的,这样就能够将 JSONObject 作为参数再进一步的向下传递,直到传递到具体相应的 Model 或者 ModelManager 相应的办法中,而后进一步把它转换成相应的构造体。

这样就容许框架中应用曾经反序列化好的 JSONObject 并进行操作,能够实现比拟通用的框架。

这即是咱们平台采纳 jsonutils 的最次要的起因。

同时 jsonutils 还有其它特别之处,JSONObject 不仅可能转换成 json 字符串,也能够转换成 QueryString 或者是把 QueryString 反序列化成 JSONObject 或者能够序列化成 YAML 的字符串。

这样能够实现更不便的性能,比方对于列表 get 这种读取的这种 API,它的参数通常都是 QueryString 嵌入到 UR。

如此咱们能够将这种参数在框架中反序列化成 JSONObject,把它作为 JSONObject 输出参数传入到框架中。

如果是对于把参数作为 body 中的 JSON 字符串,咱们同样能够把它解析成 JSONObject,能够以同样的逻辑去解决嵌入到 UR 中的 QueryString 的参数 以及嵌入到 body 中的 Jason 的参数,能够做到对立解决的逻辑。

另外 jsonutils 还有还有一些比拟特地的中央,就是针对咱们平台做的一些特地的一些解决。较为重要的一点是反对构造体字段的版本变更。

以下为举例:

\
例如有一个输出参数的构造体称为 input,有一个字段是 TenantId,用来标识用户的租户 ID。

随着版本的降级,心愿将 TenantId 对立改名为 ProjectId,这种降级如果不做任何解决,将可能呈现接口兼容性的问题,在变更之前这个字段必须是 TenantId,变更之后这个字段就只能是 ProjectId,如此一来,应用 TenantId 的客户端就不能正确拜访这个接口。

在这个构造体中,咱们减少了特地的 tag,称为 yunion-deprecated-by,把这个构造体 input 降级为新的 input 的构造之后,减少了 ProjectId 的字段,用它来代表新的 TenantId 的属性。

旧的 TenantId 依然保留,然而在 tag 中就加了名称为 yunion-deprecated-by 的 tag,这个 tag 的值就是 ProjectId。

表明 TenantId 的字段曾经被 ProjectId 这个字段 deprecated。

代码在解决时,如果旧客户端的参数中只有 TenantId,此时框架就会将 TenantId 的值依据 yunion-deprecated-by 这个 tag 指引同时 copy 到 ProjectId 的字段中。

这样一来,即便旧的客户端去拜访新的接口,在新的接口中同样能够用 ProjectId 这个字段去获取这个值。

这样就能保障即便降级了接口的字段,然而旧的客户端同时也能拜访这个接口。

这是针对做版本的字段变更的场景设计的个性。

上面介绍 sqlchemy, 这个库是 CRUD 的框架中实现 Model 和数据库映射的底层实现。

在 sqlchemy 中实现了 Golang 的数据结构到 MySQL 表严格单向同步,可能依据构造体字段的定义以及字段中 tag 的定义,严格地生成准确的 MySQL 的 schema。

保障数据库中 schema 总是严格地和构造体定义保持一致,如果不统一,即可去主动变更数据库,而后将它和构造体的定义变更为统一。

另一个重要个性是可能实现结构化的数据库查问语句。

这里举一个简略的例子,就是咱们把方才的 User 表进行一个查问,咱们这个查问在 Golang 中并不会实现 SQL 的语句,而是会有结构化的查问的办法。

图中下面框中的语句就是首先实例化 UserTable 实例,而后调用它的 Query 接口返回一个 Query。

而后再调用图中所示的 q.Equals(“domain_id 一个值。

这样就示意在查问 user 的这个表,并且要求 domain_id 这个字段要等于这个值。

通过 sqlchemy 的库在执行时就会把构造体查问变成真正的 SQL 的查问语句, 而后把它送到 MySQL 进行执行。

应用这种结构化的数据库查问形式的长处是防止了人工拼凑 SQL 容易呈现的问题, 并且 Golang 是一个严格的动态语言。能够通过 Golang 的语法查看保障查问语句的正确性。

另一个益处是因为把 SQL 的查问代码化,这样就能够把数据库查问的逻辑进行肯定水平的代码复用,这就不必反复同样的查问语句,而是调用一个办法,这个办法中就嵌入了 SQL 的查问逻辑。

框架中所有数据库的操作查问都用到 sqlchemy,在肯定水平上保障框架代码中 SQL 语句的正确性以及执行效率。

另一个比拟重要的库是 structarg,这个库的作用是可能将程序的命令行参数或者配置文件、信息、代码中的构造体做严格的映射。

这样能够基于构造体主动生成程序输出参数的提醒,并且可能依据命令行参数将参数中的值反序列化放到构造体中,或者是将对应配置文件中的参数反序列化到构造体中,在程序中就能够通过拜访构造体的字段去拜访相应参数的值。

如此即可比拟不便地在程序中应用配置的信息,这里举个例子是咱们定义了 UserOption 的构造体,这个构造体 tag 中蕴含每一个构造体的每个字段就是一个命令行,一个参数或者是配置文件的一个参数,这个参数的含意 Help  message 就放到了 tag 中,通过对这个构造体,将其初始化并进行程序编译之后,如果命令行中执行带 help 的参数的话,其将会把这些参数以易懂的形式通知用户其参数格局。

同时,用户也能够在命令行参数中去用这些参数把值传递到这个程序中,在这个程序代码中即可通过拜访构造体去拜访这些参数。

同时,也反对配置文件的输出,而且配置文件同时反对 KV 格局 和 YAML 格局。

这样能够将程序的配置文件的配置信息通过构造体定义下来,程序就可能主动地辨认配置文件的信息,而后将信息放到构造体中拜访。

框架中对配置管理的根底就是 structarg 的性能,利用该性能将每一个服务的配置用构造体来定义,并且配置的信息会存到数据库中,在数据库的信息进行批改之后,框架会把数据库中的配置信息拉取下来,而后把它反序列化到构造体中,程序中可能感知到配置的变更,并做出相应的配置变更解决。

除去三个比拟重要的库之外,还有小的办法库放在 pkg 这个库中。

正文完
 0