2019 年 12 月 14 日,又拍云联合 Apache APISIX 社区举办 API 网关与高性能服务最佳实践丨Open Talk 广州站活动,HelloTalk, Inc. 后台技术负责人李凌作了题为《HelloTalk 基于 OpenResty 的全球化探索之路》的分享。html
李凌,HelloTalk,Inc. 后端技术负责人,专一在服务出海和基于 Golang/CPP 的 IM 服务及相关技术平台的架构,5 年基于 OpenResty 服务治理和使用经验,Apache APISIX Committer。nginx
如下是分享全文:web
你们好,我是来自 HelloTalk 的李凌,本次主要介绍 HelloTalk 作什么业务以及基于怎样的场景使用 OpenResty 和 Apache APISIX。算法
HelloTalk 是全球最大的外语学习社交社区,全球 1600 万用户经过 HelloTalk 和全球语伴学习 150 门外语、进行跨文化交流及交友。用户普遍分布中国、日本、韩国、美、欧、巴西等国家,其中海外用户占 80%,从技术角度来看 HelloTalk 是一个基于全球的 Tiny 版微信。后端
HelloTalk 海外有不少 KOL 用户在 YouTube、Instagram、Twitter等平台协助作推广,知名度较高,产品兼顾聊天、改错、翻译等功能,用户能够边聊边语音改文字。其中语音改文字和翻译支持 100 多种语言。缓存
从运营层面看,不少企业出海时不知道怎样去作第一步,技术上也一样面临这个问题——如何作到出海,而且为全球用户提供优质的服务。为了让每一个国家的用户都能拥有更好的使用体验,这里就要不得不提到 OpenResty 给咱们带来的帮助了。安全
如上图所示,HelloTalk 的用户分布很散,咱们须要找到最佳的平衡点,以性价比最优的方式部署链接节点。亚太区域如韩国、日本,中国长三角和珠三角等地用户分布比较集中,比较好处理,可是在用户高度分散的其余地区(如上图的欧洲、非洲、中东),对提供稳定可靠的服务提出了较高的挑战。服务器
早期 HelloTalk 使用 C++ 写 IM 服务,当时是用到某大厂的高性能网络框架,协议都是内部拟定的,HTTP 协议都不多用。这对小公司而言成本很高,假设内部写服务要曝露给外部使用,还须要本身开发 Proxy Server,而最新加的命令字要作适配,这就很是麻烦了。微信
因此从 2015 年开始,HelloTalk 开始引进 OpenResty,基于 OpenResty 在前面作代理,直接进行协议转换传到内部服务,减小了不少的成本。websocket
此外,服务简单暴露给外部使用,会须要 WAF 的功能。咱们早期有些API 是基于 PHP 实现的,常常会由于框架缘由被发现一些漏洞,致使一些黑客作各类注入和攻击,其中主要的手法就是 POST 各类 PHP 的关键字,或者在 URL 里面携带 PHP 关键字。
当时咱们在 OpenResty 里添加不多的代码(基于正则)后解决了这个问题,后来发现即便增长 WAF 功能,性能上也不会有太大的损失。
早期咱们作 IM 开发都但愿协议短小精悍,HelloTalk 的协议头也比较精简,所有是 TCP 的协议体。比较有意思的是经过先后加两个特殊的字节符号,定义中间的内容,即 0x09+Header(20 bytes)+Body+0x0A,基本上能够保证数据包不会出乱。若是没有先后 0x09 和 0x0A 两个包,其实仍是有必定几率会产生错包的。
早期 HelloTalk 采用 TLV+PB 的协议模式,当时业务正快速发展,须要改为对外的reastful+JSON,第一步要作的是 PB 转 JSON。
而作协议解析遇到一个问题:OpenResty 使用的是云风写的 PBC 解析器,解析写起来很是麻烦,必需要知道里层的结构。假设结构有三层,你得写三层判断代码,一层一层地把它抛出来。但后来发现 Apache APISIX 是基于 lua-protobuf,因此咱们也改为了用 lua-protobuf 库,它能够直接把一个 PB 对象直接转成了 JSON,很是方便。
协议的解析过程基本上是不断地读 socket,读到上图中的包头里的 Length 字段,再去读 body 段,这里能够看出本身要实现协议解析比较麻烦,由于要对每一个协议作适配。
咱们当时作完 C++ 的 IM 通信服务后,看到主流的 IM App 如 WhatsApp、微信都有 Web IM,咱们很快的基于 OpenResty 对他们的协议进行兼容和改造,大概两周时间,咱们就从服务端快速实现了一个 WebIM 版本的 HelloTalk。
和微信网页版本同样扫描登陆聊天,基本不对协议作改动,只在中间添加一层 OpenResty 作 WebSocket 协议转换。
公共服务若是暴露出去,会有人频繁地给全部的人发消息,所以咱们须要作消息限流,这是直接基于 resty.limit.req 作的,固然 API 频率控制也是如此进行的。
作过 PHP 开发应该知道,全部的入侵实际上是各类注入 PHP 的函数名字、关键字。但当我把全部的 PHP 的函数名全放在 WAF 后,我再也没发现过被攻击,但在日志里发现不少,这说明所有被拦截了,到不了 PHP 那边。
三步走:
一、纯 TCP 协议快速实现;
二、基于 Openresty 的 HTTP 服务暴露;
三、API网关(Apache APISIX) 加 Golang 微服务开发和治理。
我比较过市面上不少服务商提供的方案:
一、阿里云全球加速(BGP + 专线),直接就是 4 层加速。
二、阿里云 DCDN 全站加速。
三、AWS的 Global Accelerator 方案。
四、Ucloud的 XPath方案 。
五、专线服务(两端 VPC,中间专线,边缘卸载https)
六、Zenlayer。
但咱们须要考虑两个问题:成本,真正的服务质量。
在解决跨境问题时,因为要考虑到国内 20% 的用户和公司总部地理位置,因此咱们是基于阿里云全站加速展开,本来是所有用公网代理到香港阿里云,采用两边是 VPC、中间专线的形式,但有时候会遇到专线网络抖动致使延时提升的问题,因此在深圳作了基于 OpenResty 的网关代理。而实际状况是:若是专线不通就选择走公网,公网延时大概 14ms,专线是 4ms。
这里会涉及到上游检测,线路不通时须要快速的切换到另一条线路,这部分问题是基于又拍云提供的 Resty 库在解决。
香港阿里机房到香港腾讯腾讯机房感受实际上是在同一个区域,由于咱们测试延时大概在 0.3ms~0.4ms。
对于海外其余用户,基本所有是直接加速回到香港阿里,但直接加速会致使客户端的网络质量受地域问题影响严重,因此咱们设置了一些 failover 的机制来保障用户的使用体验。
接入线路控制和流量管理
接入节点和质量把控
目前 HelloTalk 的接入节点主要分布在:美国东部,法兰克福,新加坡,东京,香港。美国直接到香港有可能会不通,此时会按照既定机制经转德国再回到香港,日本和韩国也是回到香港。巴西也有不少用户,但巴西云厂商只有 AWS 在作,基本上所有是连到美国,若是连不通也会多个线路之间作选择。这个环节实际上是云厂商或者是 CDN 厂商完成,但实际发现总有一些地区作的并很差,因此为了保证用户体验不受损,咱们得有些 failover 机制保证多个服务商之间切换,保证用户的服务是可靠的。
7 层和 4 层加速的选择
不少服务商会提供 7 层加速和 4 层加速,但也会有一些问题须要解决。
4 层加速得不到客户端的 IP,(注:有些云厂商是支持的但须要在服务器上打个补丁),它在 TCP 的包里提供了此功能,也不是很友好,若是打补丁出了问题,谁来负这个责任呢?
此外,监控质量也成了问题,咱们须要知道哪条线路行、哪条线路不行,虽然有切换机制,但咱们要知道它真实的通信路线。事实上咱们在每一个流量层代理时都会把真实 IP 带着跑,若是采用阿里云,那阿里云会帮咱们填到一个头里面去,不断地把客户端的真实 IP 带给下一个节点。
7 层加速的问题在于使得 IM 服务机制变成了 long polling 或者是短链接轮循机制,但在实际过程当中咱们发现它比较耗流量,并且 IM 服务须要长链接保持消息的可靠和及时到达,但大部分 7 层加速厂商不支持 WebSocket,个别支持 WebSocket 的厂商边缘卸载 HTTPS 又很贵的,尤为是国外的像 AWS 挺贵的。此外,若是云厂商边缘节点宕机,会对用户形成比较差的影响,所以咱们就在多个云厂商之间的客户端作了不少 failover 逻辑设计(内置 IP 机制),一旦故障可以切实保障切换到另一个节点,保证链接质量。
多云环境下的全球接入的管理方案
固然内置哪一个 IP 到客户端也是一个问题,好比对于欧洲用户,其实确定是要分配欧洲的 IP,那么首先咱们服务端要把欧洲的服务端 IP 存起来,怎么存?何时存?这里咱们是经过腾讯云的 httpdns + openresty timer 机制分配、缓存、更新的,上图中的 IP 就是用户的真实 IP,这个时候 httpdns 服务商就会根据 IP 参数作域名的 IP 解析。
自建 API Gateway 实现假装动态化
咱们早期是直接改 nginx.conf,我本身以为裸的 nginx 性能确定是最高的。但问题是不少人不必定记得 Location 配制的优先级顺序规则,咱们也常常会改错。并且咱们的需求比较固定:动态更新 SSL 证书、Location、upstream,当时的作法相似如今的 K8S 的 ingress 更新机制,即经过模本生成:nginx_template.conf+JSON -> PHP -> nginx.conf -> PHP-cli> Reload 来实现动态化。但这个方案在遇到 Apache APISIX 以后能够考虑替换掉了。
Apache APISIX 成为 HelloTalk 的选择: