跨境电商平台为何抛弃 C# 转投 Go 的怀抱

前言

跨境电商平台如何将业务从 C# 转换到 Go 语言,并最终均使用 Go 来实现?怎样从零打造一整套 Go 服务体系?怎样避免遇到转型微服务的坑?本文将通过 ezbuy 的资深开发工程师陈冶在 Gopher China 2017大会上的分享做详细介绍。

正文:

我们的平台在整个 Go 语言转型过程中涉及到一些微服务的转型,牵扯到微服务,如何管理这些服务,包括环境,这次分享我会从 开发环境的构建,微服务选型,分布式追踪和跨数据中心 四个方面来说。

一、开发环境构建

1、规范开发环境

每个人都有自己开发的环境,公司层面上很难保证每个人的环境是一样的,这样会导致很多兼容性的问题。有很多 时候在本地开发很顺利 ,但一部署到线上 或者到其他人的电脑 就出现 了莫名奇妙的问题,根源就在于环境不同 另一方面, 我们在用微服务以后会引入一些框架 ,比如  g RPC ,在 单纯使用  Go 的时候很简单,如果要 编译 一个项目,我们 直接用 go install  就可以解决,但如果我们引用一些第三方的语言,会颠覆之前的习惯,我们必须编两遍。Goflow 本身会负责解决这 问题,让一个命令可以编译全部的事情 ,同时统一每个人的使用环境

2、引入 Goflow

(1)Goflow 是什么

它是一个 GOPATH,它的职责是把公司代码放到它里面,它本身也是一个  git  仓库。它的作用就是 把它所 在程序员的机子上面 的 G OPATH 改成它自己,就是这么简单。它存在的意义就是 隔离个人环境,给自己创造一个相对干净的环境 。图1就是一个目录图,这是一个非常简单非常典型的GOPATH 结构。比较特殊的有一个 ezbuy.sh 文件 ,那就是 Goflow 的核心 ,可以看到,他就是一个 bash 脚本,要使用他,只要在 profile 文件里面 source 一下 ezbuy.sh 就可以了

图1

(2)Goflow的作用

Goflow  的功能之一 是可以修改  PATH,将 bin 目录放进去 这样很容易 把一些 第三方 工具引入进来, 如果在命令行内执行一个程序, 如果命中 Goflow 里面自身带的就可以优先执行,这样一 来,可以把用到的代码生成工具都放进去 ,不受 宿主机原来 环境影响。它会负责一些基础库的更新,工具链的编译。它甚至不用自己去安装,只要安 装 Goflow 后, 全部东西统一解决。 最重要的是他成为了“公司内程序规范的标杆”,不至于每个人都搞一套不同的环境。

(3) 使用 Goflow 解决 依赖管理 的问题

约定:

共享依赖:对于不同的项目,我们是倾向于他们共享第三方库的代码,在有能力的情况下,会强迫不同的项目,只用同一份第三方库存。

内网缓存:不走小水管,这个是非常必要的东西。

和业务代码分开:在看代码的时候,我们一般不会看第三方代码。

“随意”修改第三方包:这个随意不是特别随意,经过公司层面 商议后 可以 随意 修改,相对随意就是可以不用经过原作者的干涉,我们可以自己修改 ,并且这个修改可以传达到公司内的每个人,还有在线上的服务器

实现方案:

不让第三方代码放到主 src   目录下面,有两种方案,一种是双 GOPATH 方案,另一种是使用vendor,我们采用的是   vendor   方案 。它会在对应的作用域里面,看图2,在它所在的目录以及所有子集目录里面,找一个包,这个包在这个路径里面找不到,它会尝试在 vendor 里面找,vendor 可以当做一个 src 来用。因为我们要内网管理,所以我们会新建一个仓库来 存放 所有的依赖包。每一个第三方包假设是一个  git  仓库,这就涉及到我们如何用 一个 git  仓库来管理上百个 git  仓库的问题。谈到这个层面我们会有两种方案,一种方案会 在 clone  的时候动态去 获取,这种是 git submodule ,还有一个 是直接整合进现有的仓库,这种是 git subtree 我们最终采用了  git subtree。 这个库我们定了一个名字是 vendor, 刚好和 go 的   vendor   机制相吻合。这样, 我们 完成了使用  Goflow  管理 第三包依赖的事情。

图2

图3是我们现有的使用情况,在我们整个  vendor  里面,大概有1GB 的内容,在我们内网里面,这是下载完解压以后的。实际下载可以看图4,大概有200兆左右,我们内网速度大概20兆,通过WI-FI,只要花12秒就可以下载。如果没有做这一步,一个 下午都 可能 下载不完。

图3

图4

既然我们做了   vendor,并且我们还需要一些其他工具,比如说我们自己的   ORM   代码生成工具,那是不是可以顺便把这些代码也整合进来呢?这样顺便管理了全部的工具链。在更新的时候,自动拉取记录的分支,自动编译,写进   bin   目录(go install),完成工具链的更新。

(4)关于 G o flow 的总结

全程的自动化:对个人环境来说,它不用自己额外做一些事情,等一下我会 演示 什么叫自动化。

巧妙管理第三方依赖包括工具链: 依赖管理这块, 我们没有用到任何第三方工具,主要方案实在太多,就算自己做也 可以 做出一个非常简单又比较通用的方法,至少在短期的时间内 不会被 推翻。

自我迭代:本身 Goflow 是一个  bash  脚本,它也需要更新,我们也会对它进行迭代,它只要一个命令, 就会去远程仓库拉取最新代码,自我更新。

二、微服务选型

1、gRPC

所有的接口都是使用 protobuf 来定义,包括程序与程序之间,客户端与服务端之间也是这样。gRPC 是通用的解决方案,所以它接口设计必须做得很通用化,自由度比较高。对于公司内部来说,只要找到属于自己的最佳实践就可以了,剩下的就是扩散这个最佳实践,所以非常有必要的是需要去扩展代码生成,原来可能有很多条路可以做,我们内部选了一条路,全部人只要往条走,所以生成代码,就能减少每个人写代码的负担。为了配合 gRPC,我们选择 consul 做为服务发现和负载均衡。

2、接口定义和接口扩展

首先 ,我会讲接口定义,这里面有三层,包、服务、方法,比较有扩展性,我们可以给每一个单独的方法设置一些单独的配置。比如说 图5.1 这个例子,可以 通过   option  配置路径。这个功能我们内部是怎么用的?我们并不是用 gRPC 来做,我们自己扩展了一套。因为我们全部的接口都用 pb 来定义,这就涉及到有些接口内部会用,有些接口外部才会用 ,这些都是通过   option   来定义的。

图5.1

接下来会说一下对应的关系,图5 .2 我写了一个文件,它生成的代码会在右边,生成一些方法,这个pb文件对应在项目里面是哪一个目录呢?就在右下,我们是固定的,这部分由 Goflow 自己生成,它会固定生成 RPC 使用端 来说,他们怎么用,左下角只要引入这个包,写一行发起一个远程调用。左边和右边我都打一个红框,要着重讲。

图5 .2

3、SDK vs RPC

(1)服务提供的内容不仅限于接口

先讲 RPC,有一个有争议的地方,作为一个服务我提供一个接口还是我要提供 SDK ?这里面有什么差别? 如果是 RPC,那就是 一个接口出来我什么事都不管,如果提供 SDK,我会负责我的客户端。举一个例子,现在我们想做一个汇率方案,汇率会根据不同 情况 选择不同汇率,汇率会有不同规则,我们的规则可能是程序所描述的。如果我们没有 SDK,我们只提供接口作为客户端,它要知道每一个商品应该选哪一个汇率,只能通过每一次做远程调用,远程调用比本地要慢很多 ,如果提供了SDK,服务提供方了解自己服务的接口特性,将远程调用次数降到最低,甚至将整个规则通过结构化数据一次性传递过来,剩下的都是进程内操作 。在这里我们借鉴了现有对于开源程序是怎么管理自己的服务,在我们这边如果它要提供服务,可以提供 RPC接口,也可以提供SDK, SDK   代码和生成的   pb   文件 是放在仓库  RPC   目录 里面,这样项目和项目之间可以相调用,这在我们这边是一个约定。

(2)使用internal来隔离资源/函数

既然 项目与项目之间可以约定相互引用,这样会遇到一个问题,可能误引用,当我们使用了 goimports  这个工具,它会检测代码里面写了哪些未引用的包 ,并且自动添加上去 ,这里非常可能出现一个误引用这时候我们需要在项目层面,把这些代码隔离。怎么隔离,这个事情在 Go 里面已经实现了,这个在1.4内部已经在使用。intrenal 是关键词的目录名,它什么作用呢?它能够限制在 intrenal 所有包,所有包里面公开的变量,只能在 intrenal 所在目录的那个包或者下面的包使用。比如图6这个例子,如果一个包在 product 包外面,就没有办法引用。假设现在定义了接口,里面有三个方法,三个方法我全部都不会公开,全部写在 intrenal 里面,这样就不会误引用。特别在我们支持项目之间相互引用机制以后,这个是一个比较安全的保障。包括还有一些微服务,微服务最讲究的是资源 独立 ,比如说第一个model ,是一个orm生成的操作数据库的代码 ,它生成的代码,很有可能在不小心的情况下引用到。既然我们把资源隔离出来,干脆把它加入在里面,数据库获取数据肯定要走接口,而不是 直接访问。

图6

现在再看图7和图8 。这个代码我们要发起一个远程调用,最简单的写法写成什么样子?我所能想到的, 如图7标记出来的那 一行,首先第一个是仓库名,后面我们有一个约束,get  + 服务名 ,再拿到的单例,拿到单例以后,直接远程调用。如果我没这么做,一般情况下应该怎么做?我要写这么多代码,见 图8 ,这些代码可以封装起来,我会去记录当前我的服务名,调用者是谁?我要拨号,拨号失败了怎么办? 使用完后 这个连接我不想用了怎么办?我是不是要关闭它,我什么时候关闭比较好?我是不是要复用,是不是每一个不同的地方都要开一个连接,这个连接直接复用起来,每次写远程调用都要想这些,会特别烦。在这里,干脆一条路切,全部连接全部复用,全部用一个单例可以做,我关心就是要调用一个接口,你跟我说成功还是失败,失败是什么原因,是我们内部的原因或者网络的原因,其他我不想了解。

图7

图8    

刚才在代码层面我 只需要 要连接服务实际上服务是以 IP 加端口的形式存在。这样做需要从服务转化 IP 列表的过程,可以用服务发现解决。我们 要了解 一个服务,它肯定会先访问一下 Consul,然后会有一个 IP 地址列表,gRPC 本身提供一个负载均衡器,我们只要 把他和consul对接上,其他事情都不用管了。

4、项目改造   

剩下就是我们怎么改造服务,实现上很简单,请看图9,最脏的活在第二行的 mservice server 里面完成,他在基础库里面 ,整个结构都是封装过的,它只要在 Regist er   里面 把对应的接口信息传递到 ms ervice .Server ,它就能直接起来。

图9 

5、微服务化会带来一些问题

(1)阅读体验非常差: 会经常遇到这种场景, 在阅读代码过程 中突然发现,我想看这个代码跳过去, 结果是一个远程调用的代码。实际逻辑的代码应该去哪里找?很多公司对这些没有约束,不同项目有不同的路径,导致时间都花在了找代码上面了。 目前来说没有比较好的方法,这个体验确实非常差。能够做到的是,我们有一个比较规范的代码存放规则。如果我们做了非常强制性要求,一个接口肯定放在哪一个地方,因为它的代码规则是非常简单的,在每次看到这一行以后,会直接去对应代码路径里面找,这些完全可以推导。包括以后有能力可以自己写 ID E  的插件,这一层完全是程序可以 介入 的,不用人工去做。

(2)调用栈无法真实还原: 这是 Go 本身没有做的事情,微服务 把这个 加剧 了,这个后面我会讲。 虽然我们可以尽力让所有的客户端和服务端都用 gRPC 来做协议,但是对于 web 前端来说比较困难。我们还是需要  jsonrpc  做支持,当然我们不会 写代码,代码会根据  pb   文件 生成 路由方面, 我们还是继续沿用 Consul,Consul 有官方做的一个工具叫 Consul- template,可以自己写模板 ,生成nginx配置文件,这样连nginx的路由自动化也搞定了

6、总结 

(1)使用 gRPC 做为服务框架,与服务发现深度结合: 我们使用 gRPC 做一个服务框架,要做的只是写一个负载均衡器。

(2)让远程调用在形式上基本等于本地调用: 代价上肯定会有差别,但是写代码 尽量让人 很舒服。

(3)接口路径可以直接推出代码路径: 包括我刚才说的阅读体验,以及在找到接口问题的时候,我们可以直接知道代码在哪个地方。

(4)通过 option 可以做接口的定义: 这样就不会出现一些不该开放到外网的程序,一不小心到了外网。 定义 接口超时 时间 或者说限制只能由某些服务才能访问我的接口,这些也是完全可行的。我们的约定是每一台机器本地都要  Consul,每一个人在自己的开发上也要安装Consul。

(5)用 Goflow 解决依赖的工具链: 在前面介绍的 Goflow 里面,只要通过一个命令,Goflow 有自动命令,它会自动连接到内网的 Consul 加入进去, 这样也不同自己手动安装了。

三、分布式追踪

1、调用栈

(1)sentry,调用栈展示

调用栈这里是我们现如今的成果,分布式 调用栈 我们是怎么做的,图10来自于 sentry。比如 Go 的服务抛出一个错误,只是一个普通的错误,我们能够得到这样一个信息,它对应的调用栈和内容是什么,它对应的哪一个接口。包括这个请求它的参数这些我们都能够记录下来。你们看到的只是单机的方案,肯定我们要先解决单机,才能解决多机的问题。

图10

(2)跨进程的错误跟踪

跨进程的错误跟踪我们是怎么做的?如图11,我们现在客户端发起了 请求 到API,找到服务1,服务1找到服务2,再找到服务4, 这时候服务4发生了错误, 它需要把 错误按原路 扔回去, 逐层把自己的栈信息往里面填 。最终给客户端的也是一个 error,我们在服务1或者 API 已经能够得到一个完整跨进程的调用栈信息,这个信息我们不会给客户,我们会扔到 sentry 里面。图12中注意标红线的地方,在我们这边属于两个不同的进程。两个不同进程,他们在同一个调用栈里面 展示出来,对于排查问题最有帮助。

图11

图12

2、 Context   

分布式追踪我们是怎么支持的?我们在代码做了哪些调整?第一, c ontext 它会记录上下文的信息, context  很早就被提出来了,在Go1.7的时候加入到标准库 ,接着   gRPC   我们引入了contextcontext ,这个函数要调用子函数,这个之子函数调用的时候传递进去, c ontext 和函数调用栈是同一个东西,它会一层层包装,在下面它可以看到顶层的所有信息。第三,我刚才说的是进程里面,出了进程以后,进程和进程之间怎么传递。第四,把我们需要传递的信息通过Header来传递,这个完全透明的。

3、调用栈的做法

相信很多人都有比较好的解决方案。首先 Go 本身有 Exception 支持 。很多常规的错误处理都是这样的 。见图13.1。

图13.1

然后在被几个简单但找不到在哪里发生的几个错误骚扰后,会变成这样的代码。见图13.2。

图13.2

就是 自己写一个前缀,这个前缀可能是这个函数的名字 。但是这样会将错误改写,一些类似 if err == io.EOF 的判断都会失效。所以又衍生出下一步 懒人的做法,它不会破坏掉原来的信息,又能够进入信息,我们把错误包了一层,Trace 这里面只写了一种情况,如果我们想判断 eof 我们可以用 Cause 的情况。见图13.3。

图13.3

刚才那一种情况非常普遍,是目前比较正常的方案。 我们采用了 更进一步的做法,需要一步步来说。我们引入 gRPC 以后,我们可以在代码里面加入  context 。这样是不是意味着我一变量可以通过  context  传递。比如图14这个例子,我要 访问 一个商品,我在 第一行就 记录了 ID,传递进去,同时它把  context  也传递进去,它里面怎么做我不管, 只要他基于   context   打日志, 就能够把 productID 也 打印 进来。我们用结构化日志,好处是内部抛出什么信息就是显示了什么信息。我们需要知道整个一条链条对应上下文信息是什么,而不用管破坏掉原来打的内容是什么,里面什么 内容 就什么 内容 。在它把错误抛出去以后, g RPC  的中间层会捕捉到错误,将堆栈信息封装到   metadata    里面,传递给上级。

图14

4、死循环 预防方案

对于 微服务 我们还可能遇到一个死循环,可能两个服务不会遇到,但如果现在 如果 有十个服务刚好构成一个环 ,肉眼是完全看不出来的 。怎么解决这个问题,首先服务与服务之间信息能够传递。在路由器里面有 类似的场景 他怎么避免一个数据包在链路里面无限流转,靠的就是TTL, 在路由器经过一,TTL 会减1,直到0 这个包就被抛弃 。这个问题编译器可能解决不了或者比较困难,我们 可以借鉴路由器的这个做法 ,这样在 TTL 为零的时候,直接报错,就需要我们重点去观察程序逻辑是否出现非常大的漏洞。

5、总结

对于分布式追踪总结一下,完善错误调用栈和日志打印,直接聚合分布式服务跟踪。现在有很多为了达到这个事情,他们会做日志搜集、解析,去做聚合,但是这个 其实 是杀鸡用牛刀,这个事情我可以根本不用日志收集,日志收集更多层面是 用来分析了解 服务 整体 运行状况

跨数据中心

1、Gateway

这个是最后一个议题,跨数据中心。我们会有很多数据中心,我们会做差异化的部署,可以看这样一个图。这是我们 Gateway,大家可以看图15,Gateway 与 Gateway 之间也需要 相互连接 ,这里面把 Consul 包起来,原因就是我们对 Consul 并不是很了解,我们希望不会出现我们不可预知的情况,并没有其他特殊的原因。 跨数据中心的接口通讯是通过  G ateway   来完成的。

图15

2、Gateway解决方案

这里重点说一下一个程序怎么做到这个事情?见图16。 Gateway 会得到其他数据中心的服务列表, 写在本地的 Consul 里面,附加一个 “gateway” 的tag,在图里面以 G 表示。

现在 Consul 里面我们有Service1、2、3、4它读 S er vice2 会检查是不是 本数据中心也同时部署了一份 ,如果 已经部署了 ,它会优先 连接 内网,如果 S er vice3 会直接走  gateway   出去,S er vice4 内网。 gateway   本身是一个   proxy 。这是 Gateway 的总结,它本身也是 Grpc 来做,Gateway 与 Gateway 之间可以相互感知。Consul 本身不对外暴露,确保可控客户端 对多数据中心无感知,直接从本地的   consul   读取他要的服务的信息即可。

图16

四、总结   

关于跨境电商平台的 Go 转型,首先个人开发环境尤其重要,将公司内部各种特殊流程标准化自动化,其次是统一使用微服务的框架,解决服务分布式跟踪问题 ,解决开发效率的问题。对于多数据中心的状况,我们 使用 Gateway  将其透明化。

--------------------------------------------------------------------

8月19日 北京

七牛云携手链家,共同推出“ 大数据最新场景化应用实践 ”主题架构师实践日

七牛云技术总监陈超、链家网大数据部负责人吕毅联袂出品,精选干货内容:

  • 链家网资深大数据研发工程师邓钫元,分享如何运用“开源+自身业务定制”解决大数据多维分析痛点

  • 七牛云大数据高级工程师党合萱,全面揭秘七牛自主研发千亿级大数据平台的实践之路

  • 蚂蜂窝 大数据平台负责人汪木铃带来 蚂蜂窝作为国内最早一批使用 Druid 的公司,在实战过程中踩过的“坑”和优化经验

  • 前新浪微博技术专家、三好网 CTO 卫向军,讲解如何利用大数据技术方案在实际业务应用中增值

Go 中国 粉丝福利:

点击“ 阅读原文 ”即可享免费报名

提供精彩留言获赞最多的三位读者即可获取七牛云手办一套! 截止人集赞时间:8月11日中午12:00

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章