云联壹云是齐全自研的一套交融云平台,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这个库中。