bilbili go语言开源go orm 框架比较 gopkg.in/jcmturner/rpc.v1@v1.1.1: parsing go.mod: missing module line

Sniper 是我为部门设计的轻量级 go 业务go orm 框架比较经过两年的平稳运行,至少在一定程度上印证了我当初的一些见解现整理出来供大家参考。

项目源码可以从 github 获取

Sniper 起源于一项新業务在转岗之前,我一直在 L 部门写 PHP 代码遇到过如下问题:

  • 手工维护 RPC 文档,难以及时更新
  • 手写代码处理 RPC 入参难以保证参数类型,如数芓 1 和字符串 "1" 的区别
  • 无法方便地查询一个请求对应的所有日志
  • 服务拆分得很细难以进行调用链路追踪
  • 使用 JSON 做为配置,难改难认

大约在 2018 年的陸月底我得知要去新的 C 部门做新业务。没有任何历史包袱我马上着手准备,希望能全方位的解决上面提到的问题

首先要解决语言选擇的问题。PHP 是最熟悉的但从过去的经验来看,无论从性能还是从代码可维护性方面考虑PHP 都不是一个好的选择。当时有两种选择一个昰 Java,另一个是 go平心而论,Java 是要比 Go 要成熟得多但 Go 更加简单轻便,从 PHP 过渡成本更低而且当时公司正在推动用 Go 重写原有的 Java 项目。自然就选叻 Go

我两年后又写了一管关于 go 与 php 对比的文章,大家可以参考一下

有了语言接下来就要确定通信协议。首先不要使用 REST 风格接口 REST 中看不中鼡。REST 的核心是资源和状态所有的变更都对应状态的转变。

对于简单的场景REST 看似完美,如:GET /user/123 表示查询

但如果是发送一条短信呢?一种方案是使用 POST /sms 表示创建一条短信资源另一种方案则是 POST /sms:send 直接发送。

但不管哪种方式都不如 RPC 调用直观,其原因有二:

  • 二是将业务过程转成资源状态变化本身就比较烧脑而且存在无法转化的场景

REST 还有一个比较大的问题就是 url 中有数字 id,统计 prometheus 监控指标的时候必须做归一化处理

所鉯,不用 REST

这得从原来在 L 部门用的 Weisai-RPC 说起。该 RPC 基于 TCP 传输消息结构如下:

// 长度没满右端补充\0, 超过自动右端截断.

典型的面向 c 语言的设计,方便 c 語言解析但不太灵活。

比如cmd 字段只有 32 字节,也就是说接口名字最多只能是 32 字节还有 body 是字符串,但实际传输的是 JSON需要二次解析。使鼡结构化二进制消息就是为了提高解析速度但这种改过跟 JSON 解码想比又可以忽略。所以这种混合型的设计除了看上去比较复杂以外,确實没什么优点了

因为没有采用 HTTP 协议,后来不得不在 body 中定义了 header 字段用来传输 HTTP 请求的 header像 nginx, curl, tcpdump 这样的标准也基本上无法正常使用。为此还专门引入了一个接入层负责 RPC 和 HTTP 之间的相互转换。

切实体会到了 Weisai-RPC 的不便之后我决定业务 RPC 协议只用 HTTP 传输,原则上不使用二进制消息格式

protobuf 本身是支持 JSON 的,不明白为什么 gRPC 的实现不支持而支持 stream 接口则是 gRPC 的一大特色,使 gRPC 能够胜任诸如语音实时识别等场景但这一类场景是比较少见的。峩们绝大多数业务场景都是一问一答的为了实现这个 stream 特性,gRPC 不得不依赖 HTTP2不得不自行定义了一种有固定五字节头的消息格式。与此同时gRPC 也就放弃了 HTTP 协议原生的压缩功能,也没法使用 HTTP 协议的 content-length 头传递消息长度这也是 gRCP 消息五字节头的功能所在,头一个字节表时是否压缩后㈣个字节表示消息长度。

有个所谓的 2-8 原则:

一般只用 20% 的代码就可以解决 80% 的问题但要想解决剩下 20% 的问题的话,则需要额外 80% 的代码

gRPC 还有个 web 支持的问题。浏览器的 js 无法使用 HTTP2 的特性所以不能直接与 gRPC 服务通信。于是有了 还有 。

所以如果没有 stream 接口需求,则完全没有必要使用 gRPC;洳果真的有这类需求也不可能太多,直接使用原生 TCP/WebSocket 协议开发也不是难事

最终我们选择了 。twirp 可以看作是简化版的 gRPC同样用 protobuf 描述,不依赖 HTTP2同时支持 protobuf 和 JSON,没有五字节的二进制前缀但我们对原生的 twirp 做了修改,形成了自己的主要改动就是添加了对 www-form-urlencoded 编码格式的支持,这是移动端的历史包袱导致的没办法。

现在的移动端使用 www-form-urlencoded 编码更加简单;管理后台使用 JSON 编码,更加灵活如果对性能有要求也可以使用 protobuf 编码,泹没目前没有用估计也不会有人喜欢用。

使用 proto 描述 RPC 接口有一个问题就是接口说明分了 request, response 和 service,比较分散尤其是要用到嵌套 message 的时候,对移動端开发同学很不友好目前也一些文档生成工具,比如:但 protoc-gen-doc 也是为不同 message 生成对应文档,使用者需要在文档的不同部分来回跳转很不矗观。所以我们开发了 这是生成的。最终我们给 gitlab 加了一个 webhook,当有新分支创建或者更新的时候会自动生成 markdown 文档并进而转化成 html 文档彻底解决了文档同步的问题。

protoc-gen-markdown 也不完美它无法正确处理 proto 中的 map 消息。但我们在业务中没有用到这种类型所以没有受到影响。但这始终是个问題protoc-gen-markdown 最早是跟 twirp 的改造一起进行的。最早的提交记录是从 2018 年 7 月 3 日开始的主要功能到 7 月 7 日就完成了,到现在也没有大的变动

解决了通信问題之后,接下来要设计配置系统

在 L 部门的时候都是用 JSON 做配置。JSON 一方面对格式要求比较高比如列表最后一个元素之后不能加逗号等;另┅方面不支持注释,时间长了很难弄清各配置项的含义还有就是 JSON 很灵活,导致很多业务配置层层嵌套不好读、不敢改。

鉴于之前的经驗我们放弃了 JSON,最终选择了 而且框加要求所有配置只能是 k-v 型字符串的。如果业务代码要用复杂的配置则需要自行处理反序列化逻辑。因为是 k-v 型的所以很容易兼容环境变量,所有的配置项都可以通过环境变量覆盖最后就是go orm 框架比较支持配置的热更新,会实时读取配置文件内容的变更

我们也没有重复造轮子,配置的解析和加载都是通过 完成的

日志组件选用 。没别的原因就是 star 比较多。logrus 支持不同的 formatter开发环境会将日志写到标准输出设备,其他环境会通过 lancer 写到 elk(这一部分不适合开源)

实例,使用获取的实例记录日志会自动输出 trace-idgo orm 框架比较在输出响应内容的时候也会自动在 header 中加上这个 trace-id。

公司内部有个叫 dapper 组件但没有 opentracing sdk。go orm 框架比较自己提供了一个但这一部分不适合开源。

好在是适配了 opentracing大家可以很方便的集成 jaeger 等组件。

主要的基础组件有三个分别是 HTTP 客户端、mysql 客户端、。是后来加入的现在还没在业务中使用。

Sniper 对基础组件提供统一封装主要解决以下问题:

现在很少有go orm 框架比较会注意到这些方面,尤其是后三条大家观注更多的往往是性能,往往是go orm 框架比较代码是否优雅估计只有在生产环境摸爬滚打过几次才会对这些东西产生共鸣

很多go orm 框架比较都提供 ORM 组件但 sniper 不然。鈈推荐使用 ORM原因如下:

  1. ORM 固然方便,但会隐藏 SQL 查询细节不利于程序员全盘掌握 db 查询情况。
  2. ORM 用法并不统一相对 SQL 标准有额外的学习负担。
  3. ORM 無法覆盖所有有 SQL 查询在特定业务场景下仍需要写原生 SQL。
  4. ORM 大多基于反射有一定的性能损失。

业务代码一般会有数据访问层(DAO)既便引叺 ORM,也只局限在 DAO 层

Sniper go orm 框架比较的 memcache 和 redis 组件都不支持集群的,而且是有意不支持甚至是将已有的相关代码直接删除

为什么呢?我们认为这些細节不应该是一个业务go orm 框架比较要关心的内容这些内容应该交给统一的中间件处理。业务代码连中间件根本无需感知集群的存在。对於 memcache我们生产环境用的是 ,对于 redis 和 http 服务我们用的是 。

我们坚信未来一定是 service-mesh 的世界,所以服务发现、负载均衡、限流熔断这一类的功能統统交由 mesh 服务就可以了让我们试目以待。

单元测试部分不适合开源只能分享一些相关的思考。

没有单元测试就很难有真正的积累。峩们的核心业务逻辑基本都有单元测试覆盖有一次要改支付逻辑,我改完跑通测试后直接移交测试测试通过,直接上线一气呵成。峩甚至都没自己用 curl 调一下接口因为我知道,单元测试已经覆盖的已知的关键流程

这当然不是什么值得炫耀的事情。但有效的单元测试確实对提高代码的质量有很大的裨益

但怎么测才好呢?关键在 mockGo 对 mock 并不是很友好。而且如果 mock 多了一方面会极大降低写测试用例的体验;另一方面会导致测试用例真就成单元测试了,可能出现各单元都没问题但整个系统有问题的情况。

所以写测试一定要简单,测试逻輯一定要有效为实现这两个目标,我们定了两条规则: - 外部 http 请求一律 mock这个基于 - mysql, memcache, redis 直接起服务,各测试用例自行维护自己的测试数据集

为叻进一步降低编写测试用例的复杂度我们还提供了自动同步表结构和导入种数据的功能。如果测试用例不想手工维护测试数据集则可鉯将相关数据写种子数据集。测试go orm 框架比较会自动导入

引入 sniper go orm 框架比较快一年了,基本上解决了在 L 部门遇到的问题无论在线下开发、联調和测试效率方面,还是线上运行、排错效率方面都有不俗的表现。

我要回帖

更多关于 go 哪个框架 的文章

 

随机推荐