HelloTalk 基于 OpenResty 的全球化探索之路
2019 年 12 月 14 日,又拍云联合 Apache APISIX 社区举办 API 网关与高性能服务最佳实践丨Open Talk 广州站活动,HelloTalk, Inc. 后台技术负责人李凌做了题为《HelloTalk 基于 OpenResty 的全球化探索之路》的分享。
李凌,HelloTalk,Inc. 后端技术负责人,专注在服务出海和基于 Golang/CPP 的 IM 服务及相关技术平台的架构,5 年基于 OpenResty 服务治理和使用经验。
以下是分享全文:
大家好,我是来自 HelloTalk 的李凌,本次主要介绍 HelloTalk 做什么业务以及基于怎样的场景使用 OpenResty 和 Apache APISIX。
HelloTalk:技术上看是基于全球的 Tiny 版微信
HelloTalk 是全球最大的外语学习社交社区,全球 1600 万用户通过 HelloTalk 和全球语伴学习 150 门外语、进行跨文化交流及交友。用户广泛分布中国、日本、韩国、美、欧、巴西等国家,其中海外用户占 80%,从技术角度来看 HelloTalk 是一个基于全球的 Tiny 版微信。
HelloTalk 海外有很多 KOL 用户在 YouTube、Instagram、Twitter等平台协助做推广,知名度较高,产品兼顾聊天、改错、翻译等功能,用户可以边聊边语音改文字。其中语音改文字和翻译支持 100 多种语言。
从运营层面看,很多企业出海时不知道怎样去做第一步,技术上也同样面临这个问题——如何做到出海,并且为全球用户提供优质的服务。为了让每个国家的用户都能拥有更好的使用体验,这里就要不得不提到 OpenResty 给我们带来的帮助了。
如上图所示,HelloTalk 的用户分布很散,我们需要找到最佳的平衡点,以性价比最优的方式部署连接节点。亚太区域如韩国、日本,中国长三角和珠三角等地用户分布比较集中,比较好处理,但是在用户高度分散的其他地区(如上图的欧洲、非洲、中东),对提供稳定可靠的服务提出了较高的挑战。
为什么使用 OpenResty
早期 HelloTalk 使用 C++ 写 IM 服务,当时是用到某大厂的高性能网络框架,协议都是内部拟定的,HTTP 协议都很少用。这对小公司而言成本很高,假设内部写服务要曝露给外部使用,还需要自己开发 Proxy Server,而最新加的命令字要做适配,这就非常麻烦了。
所以从 2015 年开始,HelloTalk 开始引进 OpenResty,基于 OpenResty 在前面做代理,直接进行协议转换传到内部服务,减少了很多的成本。
此外,服务简单暴露给外部使用,会需要 WAF 的功能。我们早期有些API 是基于 PHP 实现的,经常会因为框架原因被发现一些漏洞,导致一些黑客做各种注入和攻击,其中主要的手法就是 POST 各种 PHP 的关键字,或者在 URL 里面携带 PHP 关键字。
当时我们在 OpenResty 里添加很少的代码(基于正则)后解决了这个问题,后来发现即使增加 WAF 功能,性能上也不会有太大的损失。
- TLV:0x09+Header(20 bytes)+Body+0x0A
早期我们做 IM 开发都希望协议短小精悍,HelloTalk 的协议头也比较精简,全部是 TCP 的协议体。比较有意思的是通过前后加两个特殊的字节符号,定义中间的内容,即 0x09+Header(20 bytes)+Body+0x0A,基本上可以保证数据包不会出乱。如果没有前后 0x09 和 0x0A 两个包,其实还是有一定概率会产生错包的。
- 自定协议->HTTP 的研发成本,急需高效的 proxy 服务做协议转换
早期 HelloTalk 采用 TLV+PB 的协议模式,当时业务正快速发展,需要改成对外的reastful+JSON,第一步要做的是 PB 转 JSON。
而做协议解析遇到一个问题:OpenResty 使用的是云风写的 PBC 解析器,解析写起来非常麻烦,必须要知道里层的结构。假设结构有三层,你得写三层判断代码,一层一层地把它抛出来。但后来发现 Apache APISIX 是基于 lua-protobuf,所以我们也改成了用 lua-protobuf 库,它可以直接把一个 PB 对象直接转成了 JSON,非常方便。
- 基于 cosocket 的 TCP 协议安全解析
协议的解析过程基本上是不断地读 socket,读到上图中的包头里的 Length 字段,再去读 body 段,这里可以看出自己要实现协议解析比较麻烦,因为要对每个协议做适配。
- 快速实现一个 Web IM
我们当时做完 C++ 的 IM 通讯服务后,看到主流的 IM App 如 WhatsApp、微信都有 Web IM,我们很快的基于 OpenResty 对他们的协议进行兼容和改造,大概两周时间,我们就从服务端快速实现了一个 WebIM 版本的 HelloTalk。
和微信网页版本一样扫描登录聊天,基本不对协议做改动,只在中间添加一层 OpenResty 做 WebSocket 协议转换。
- 控制消息频率
公共服务如果暴露出去,会有人频繁地给所有的人发消息,因此我们需要做消息限流,这是直接基于 resty.limit.req 做的,当然 API 频率控制也是如此进行的。
- WAF 保护 PHP 业务
做过 PHP 开发应该知道,所有的入侵其实是各种注入 PHP 的函数名字、关键字。但当我把所有的 PHP 的函数名全放在 WAF 后,我再也没发现过被攻击,但在日志里发现很多,这说明全部被拦截了,到不了 PHP 那边。
三步走:
1、纯 TCP 协议快速实现;
2、基于 Openresty 的 HTTP 服务暴露;
3、API网关(Apache APISIX) 加 Golang 微服务开发和治理。
国际化过程中的挑战和问题
HelloTalk 用户分布区域非常分散,需要想办法解决用户分布区域分散的问题;
HelloTalk 国内大概有 20% 的用户,面临防火墙的问题;
海外语言环境和网络环境一样复杂,语言适配问题难以处理。
怎样提高用户的全球接入质量
我比较过市面上很多服务商提供的方案:
1、阿里云全球加速(BGP + 专线),直接就是 4 层加速。
2、阿里云 DCDN 全站加速。
3、AWS的 Global Accelerator 方案。
4、Ucloud的 XPath方案 。
5、专线服务(两端 VPC,中间专线,边缘卸载https)
6、Zenlayer。
但我们需要考虑两个问题:成本,真正的服务质量。
在解决跨境问题时,由于要考虑到国内 20% 的用户和公司总部地理位置,所以我们是基于阿里云全站加速展开,原本是全部用公网代理到香港阿里云,采用两边是 VPC、中间专线的形式,但有时候会遇到专线网络抖动导致延时提高的问题,所以在深圳做了基于 OpenResty 的网关代理。而实际情况是:如果专线不通就选择走公网,公网延时大概 14ms,专线是 4ms。
这里会涉及到上游检测,线路不通时需要快速的切换到另外一条线路,这部分问题是基于又拍云提供的 Resty 库在解决。
香港阿里机房到香港腾讯腾讯机房感觉其实是在同一个区域,因为我们测试延时大概在 0.3ms~0.4ms。
对于海外其他用户,基本全部是直接加速回到香港阿里,但直接加速会导致客户端的网络质量受地域问题影响严重,所以我们设置了一些 failover 的机制来保障用户的使用体验。
接入线路控制和流量管理
专线网络的带来的稳定性,例如欧洲到香港,延时:244 ms -> 150 ms;
动态 upstream 控制 (lua-resty-healthcheck),多服务商线路之间灵活切换,保证服务的可靠性;
部分逻辑可以直接在边缘处理,serverless (原理都是基于 pcall+loadstring 实现),serverless 这块我们现在正则将其改造成 Apsche APISIX + ETCD。
接入节点和质量把控
目前 HelloTalk 的接入节点主要分布在:美国东部,法兰克福,新加坡,东京,香港。美国直接到香港有可能会不通,此时会按照既定机制经转德国再回到香港,日本和韩国也是回到香港。巴西也有很多用户,但巴西云厂商只有 AWS 在做,基本上全部是连到美国,如果连不通也会多个线路之间做选择。这个环节其实是云厂商或者是 CDN 厂商完成,但实际发现总有一些地区做的并不好,所以为了保证用户体验不受损,我们得有些 failover 机制保证多个服务商之间切换,保证用户的服务是可靠的。
7 层和 4 层加速的选择
很多服务商会提供 7 层加速和 4 层加速,但也会有一些问题需要解决。
- 4层加速:SSL握手时间过长,容易失败,得不到客户端的 IP,不方便做节点质量统计。
4 层加速得不到客户端的 IP,(注:有些云厂商是支持的但需要在服务器上打个补丁),它在 TCP 的包里提供了此功能,也不是很友好,如果打补丁出了问题,谁来负这个责任呢?
此外,监控质量也成了问题,我们需要知道哪条线路行、哪条线路不行,虽然有切换机制,但我们要知道它真实的通讯路线。事实上我们在每个流量层代理时都会把真实 IP 带着跑,如果采用阿里云,那阿里云会帮我们填到一个头里面去,不断地把客户端的真实 IP 带给下一个节点。
- 7 层加速:不能保证 IM 服务需要长连接保持消息的可靠到达
7 层加速的问题在于使得 IM 服务机制变成了 long polling 或者是短连接轮循机制,但在实际过程中我们发现它比较耗流量,而且 IM 服务需要长连接保持消息的可靠和及时到达,但大部分 7 层加速厂商不支持 WebSocket,个别支持 WebSocket 的厂商边缘卸载 HTTPS 又很贵的,尤其是国外的像 AWS 挺贵的。此外,如果云厂商边缘节点宕机,会对用户造成比较差的影响,因此我们就在多个云厂商之间的客户端做了很多 failover 逻辑设计(内置 IP 机制),一旦故障能够切实保障切换到另外一个节点,保证连接质量。
多云环境下的全球接入的管理方案
支持 websocket 的 7 层加速。(云服务+自建)
自建低速率的 VPC+专线通道。(性价比考虑,IM 自身流量并不多,只做通知消息下发)
长短连接混合收发消息:websocket+long polling+httpdns + 内置 IP failover 机制
当然内置哪个 IP 到客户端也是一个问题,比如对于欧洲用户,其实肯定是要分配欧洲的 IP,那么首先我们服务端要把欧洲的服务端 IP 存起来,怎么存?什么时候存?这里我们是通过腾讯云的 httpdns + openresty timer 机制分配、缓存、更新的,上图中的 IP 就是用户的真实 IP,这个时候 httpdns 服务商就会根据 IP 参数做域名的 IP 解析。
从自建 API Gateway 到深入体验 Apache APISIX
自建 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 的选择:
自身需求比较简单,依赖 RDMS 觉得太重,带来额外的维护成本;
代码极致简单易懂,在个人能力范围内,可以理解;
基于 ETCD,节省维护成本;
项目主要维护者几乎实时在线支持,QQ 群、邮件响应及时。
推荐阅读
从 0 到 1:Apache APISIX 的 Apache 之路
HelloTalk 基于 OpenResty 的全球化探索之路的更多相关文章
- Polaristech 刘洋:基于 OpenResty/Kong 构建边缘计算平台
2019 年 3 月 23 日,OpenResty 社区联合又拍云,举办 OpenResty × Open Talk 全国巡回沙龙·北京站,Polaristech 技术专家刘洋在活动上做了<基于 ...
- 基于openresty的https配置实践
最近机器人项目的子项目,由于和BAT中的一家进行合作,人家要求用HTTPS连接,于是乎,我们要改造我们的nginx的配置,加添HTTPS的支持. 当然了,HTTPS需要的证书,必须是认证机构颁发的,这 ...
- 打破基于OpenResty的WEB安全防护(CVE-2018-9230)
原文首发于安全客,原文链接:https://www.anquanke.com/post/id/103771 0x00 前言 OpenResty® 是一个基于 Nginx 与 Lua 的高性能 We ...
- 基于 OpenResty 实现一个 WS 聊天室
基于 OpenResty 实现一个 WS 聊天室 WebSocket WebSocket 协议分析 WebSocket 协议解决了浏览器和服务器之间的全双工通信问题.在WebSocket出现之前,浏览 ...
- 基于OpenResty和Node.js的微服务架构实践
什么是微服务? 传统的单体服务架构是单独服务包,共享代码与数据,开发成本较高,可维护性.伸缩性较差,技术转型.跨语言配合相对困难.而微服务架构强调一个服务负责一项业务,服务可以单独部署,独立进行技术选 ...
- 基于 OpenResty 的动态服务路由方案
2019 年 5 月 11 日,OpenResty 社区联合又拍云,举办 OpenResty × Open Talk 全国巡回沙龙武汉站,又拍云首席布道师在活动上做了< 基于 OpenResty ...
- 基于Openresty+Naxsi的WAF:从小白到实践
序 2019年2月18日,加入妈妈网,至今已经有四个月的时间,上周进到一个网关项目组,这个项目的主要目的是基于openResty+Naxsi实现WAF,相关技术初定涉及到openResty.Lua.N ...
- 基于OpenResty与Consul实现服务网格ServiceMesh
一.逻辑架构 1.基于OpenResty开发智能代理: 利用其动态可编程特性,动态化配置nginx服务路由: 2.需要向OpenResty添加weibo开源的upsync服务发现模块: 3.基于con ...
- 基于Openresty+的WEB安全防护系统架构--转
随着时间的推移,我们在实践中也不断的演进我们的服务部署方案,希望WEB防护,不只是单独的云WAF来保护服务,而有其它的相关服务,对WAF进行增强加固的合理配合.我们使用Openresty+系统构建了W ...
随机推荐
- Open Source Software List: The Ultimate List
http://www.datamation.com/open-source/ Accessibility 1. The Accessibility Project The Business Value ...
- Android 自定义界面的弹出框(可输入数据)
上午写了一篇博文,介绍了如何定义从屏幕底部弹出PopupWindow,写完之后,突然想起之前写过自定义内容显示的弹出框,就随手写了两个实例,分享出来: 第一种实现方式:继承Dialog 1.1 线定义 ...
- H3C 常用接口和线缆
- 队列&优先队列
1.队列 普通的队列都是先进先出,元素从队尾添加,从队头删除. function queue(){ var arr=[]; this.enqueue=function(item){ arr.push( ...
- clear简单的例子
非常实用,非常简单的例子,结果都在图片里 <html> <head> <style> .keepbj { width:800px; height: 300px; b ...
- NIO 中文乱码问题的解决代码实现
之前在网上查询了很多关于解决NIO中文乱码的问题,仁者见仁智者见智,不过就找到的几种方法实现都太繁琐了,稍微研究了下NIO源码,以下是我自己的一种实现,偷懒用最简单的代码去实现是我的习惯! Demo: ...
- JS划重点——类和对象的不正经阐述
JS划重点--类和对象的不正经阐述 /在JS 类里面函数也是一个对象,那么要创建一个对象就需要一个类,这个类可以由这个对牛逼的对象-函数来实现/ /首先是普罗大众都会的 工厂模式来创建一类/ func ...
- 原生js实现最简单的瀑布流布局
文章地址 https://www.cnblogs.com/sandraryan/ 瀑布流:瀑布流,又称瀑布流式布局.是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动, ...
- zoj 3652 Maze
Maze Time Limit: 2 Seconds Memory Limit: 65536 KB Celica is a brave person and believer of a Go ...
- Python--day38--进程同步控制的---锁\信号量\事件的方法名