乐趣区

Golang 在十二赞的深度应用

Golang 在十二赞的深度应用
我们是“十二赞”,一个致力于帮助电商卖家进入小程序的小团队,我们的主页是 http://www.12zan.cn/。在实际运行中,我们使用了大量由 golang 写就的小工具,几乎每一个工具代码量都超短,一般在 200 行左右就完成了一个独立的功能,同时担当了相当重要的角色;像代理服务器,代码量一共 500 行多一点点,却是我们的核心支柱,压测时 QPS 也直追 nginx, 表现优异。
基于 Docker 的基础结构
做为基础架构,我介绍一下我们的机器架构。
我们的整个业务构建于阿里云之上,有 5 台 server, 每一对都有独立的外网 IP, 同时也在同一个内网之中。在每一台机器上都跑了一个我们自己用 golang 写的守护进程,这个进程负责监听一些业务重启、新增域名等类似的指令并执行(这些指令最后都传递给了 docker)。同时,每台机器上都有一个 consul 进程,这些 consul 都 join 到了一起。另外,我们每一台机器上,都用 docker 跑了一个 nginx 来做 80 端口的服务,同时跑了一个用 golang 自己写的 HTTP 代理。nginx 做做日志啊基础的功能之后就把请求丢给这个 http 代理,HTTP 代理会到 consul 里去查应该将请求转发到哪个 IP 的哪个端口上。
400 行 Golang 代码写的 HTTP Proxy
在架构选型的第一天,我们就决定,我们会服务化,会大量使用 http 接口来提供服务,并使用自己的 http proxy 来分发请求、添加自有的一些业务逻辑比如 API 的权限验证等逻辑。我们限定,所有业务,域名都是 *.app.12zan.net,比如我们要上一个聊天服务,请求的接口就会是 chat.app.12zan.net, 哪天再上个评论服务,请求的接口就是 comment.app.12zan.net。
确定域名后我第一件事情,就是拿 golang 自己写了一个非常简单的基于 consul 的 http proxy server; 感谢 Golang 这完善的 HTTP 库,我们只用了几百行代码就完成了所有功能。每当有 http 请求过来时,这个 proxy server 就会根据 HTTP 请求中 HTTP_HOST 字段去 consul 去查,有哪些后端是用这个域名名称来注册服务的,并根据指定的算法,取出一台后端来, 把这个 HTTP 请求 Proxy 过去。每个具体的业务,可能运行在我们 5 台机器中的任何一台之中的 docker 上,也可能是多个 docker 实例上。所以这里有一个机制,选择哪个实际的 docker 实例来服务这个请求的问题。我们现在支持随机选取、按客户端 IP 地址做 hash 之后选取、按 URL 做 Hash 选取、按负载选取几种方式。
WEB 服务的服务注册
基于 php+laralel 和 nodejs+koajs 两种场景,我们制作了自己的 docker 镜像。这个镜像除开可以将 php+nginx 和 nodejs 构建的 web 服务运行起来之外,还包含一个 golang 写的 consul 客户端。在 docker 容器里,这个客户端随着 php+nginx 或是 nodejs 的 web 服务一起启动,启动之后会向宿主机的 consul 进程注册自己这个服务,注册的时候会通知说,某某应用,在某某 IP 某某端口提供服务啦,如果前面有到 **.app.12zan.net 的请求你可以转发给我;同时会每隔一秒上报自己的进程数、当前机器 CPU 占用、内存占用情况。也是一样的简单,几百行 golang 代码,就鼓捣出了这个 consul 客户端。为什么使用 golang 呢?第一个原因当然是因为 consul 天生是 golang 阵营,第二个,是因为我们的 docker 容器种类较多,所以这个客户端直接就是在 Mac 上跨平台编译出来的在 linux64 平台上运行的,不管 docker 容器是 python 为基准的还是 ruby 为基准的,还是 nodejs 的,只要把这个二进制文件拷贝进去就能正确运行,不像别的语言需要解决依赖问题。
我们还开发了一个 web console 界面,在这里,我们可以注册 app, 也可以为 app 新增实例。注册 app 时,我们要指定代码仓库的地址(对了,我们的代码管理是用的 golang 写的 gogs), 指定对外服务的域名,指定是 nodejs 应用还是 php+laravel 应用。添加应用之后,可以在这个应用下新建实例,让系统在指定的 IP 上去跑这个实例。实例运行的过程实际就是下发一个通知到某个机器上,去执行一个 docker 实例启动的过程。docker 启动的时候带了一些环境变量,比如当前内网 IP、docker 监听的端口、对外提供服务时是用何域名提供服务。
日志和存储
前面这种架构有一个问题,就是后端可能是在任何一台机器上运行的,今天可能是 A, 明天可能是 B,那我要是把文件存在 A 上了是不是让 B 来提供服务的时候就挂掉了?所以我们想了这么一个办法(也是因为穷。。。。),我们把所有的文件都挪到阿里云的 OSS 服务上。同时为了不管是 Nodejs 应用还是 php 应用 还是 python 写的应用都能做到把用户上传的文件或是系统生成的文件存到 oss 上面,我们很省事地写了一个 ossUploader, 编译好的可执行文件发布,只需要执行它,传进来本地路径和 oss 上的目标路径,就保证给你上传到 oss 上去就完整,不需要再在 nodejs、php、python、ruby、java 各种平台下都琢磨一遍 oss 的 SDK。
对日志的处理是一样的,所有的日志文件的内容,都会被一个 golang 写的工具 gtail 监听着 (就像 linux 的 tail - f 命令一样),所有新产生的内容都会被 gtail 挪到 oss 上去存储。当然,也是可执行文件发布的。
这个实现之后,我们的实际业务就真正可以在 5 台机器上之间任意腾挪了。
消息广播
得益于 golang 的一些开源仓库,我们还做了一些好玩的东西。
比如,看到 https://github.com/gorilla/we…,我们忍不住撸了一个 websocket server, 或是说叫群聊服务器更好一点。
接下来,我们看到有一个 golang 的库叫 go-mysql-elasticsearch, 伪装了一个 mysql 的 slave,去 MySQL 的 master 机器上去读 binlog, 读到 binlog 以后就将 MySQL 里的数据发送给 ElasticSearch 去索引数据。
我们就结合了一个,把这两个结合起来,修改了一下 go-mysql-elastichsearch, 让它监听到 MySQL 的数据变更之后,在 WebSocket server 的某个群聊里推送出来,形成一个数据变更的广播。
再接下来我们就可以用 nodejs 写一个应用,连上这个 websocket server,加入特定的某个群聊,就源源不断地收听到数据变更的消息。这个 nodejs 端的代码就非常简洁了,只需要不到 100 行代码可以做各种好玩的事情,比如监听到用户留言表有新增,可以发邮件让运营马上去审核。还有比如说,每当订单表有成交的时候,我们某个小小的 nodejs 应用因为监听了数据库消息,第一时间就知道了,马上就去追溯用户来源,来计算返利;同时这个 nodejs 的代码更新是和订单主逻辑完全不相关的,写这个业务的开发人员只需要知道订单表的结构,不需要了解订单应用后台代码。【原文链接】

退出移动版