关于高德地图:高德渲染网关Go语言重构实践

6次阅读

共计 6215 个字符,预计需要花费 16 分钟才能阅读完成。

​1. 导读

高德启动 Go 业务建设曾经有段时间了,次要蕴含 Go 利用落地Go 中间件建设 云原生 三个局部。通过继续的发力,在这些方面获得了不错的停顿。高德 Go 业务落地过程是如何实现的,遇到过哪些问题,如何解决?本文将为大家介绍相干教训,心愿对感兴趣的同学有所帮忙。

2. 高德为什么要落地 Go 利用

当初高德内支流的语言还是 Java,Java 利用最多,机器数非常惊人。而且高德整体业务也在疾速向前奔跑,成本增加的速度十分快。在缩小机器负载方面,Go 语言在语言级别对 Java 语言有相当劣势。缩小机器老本是咱们落地 Go 利用的第一个思考因素。

其次,Go 语言近几年发展势头迅猛,不论是阿里团体外部,还是在高德外部,对应用 Go 语言的呼声越来愈高。落地 Go 利用能够很好的验证 Go 中间件的稳定性。当然咱们能够通过混沌工程等伎俩去验证,但通过生产环境考验才最具备说服力。验证积淀 Go 语言中间件稳定性是咱们落地 Go 利用的第二个思考因素。

最初,Go 语言作为云原生根底框架应用较多的语言,提前落地 Go 利用,对后续落地云原生能够缩小不少阻力。高德目前落地的 Serverless/Faas 规模相当大。落地 Go 利用的第三个思考因素是为后续云原生落地铺路。

3. 大流量场景 Go 利用落地

3.1 渲染网关介绍

本文所述中提到的高德渲染网关,是咱们落地的 Go 利用中业务流量、革新难度、危险,收益均处前列的利用。渲染网关在接入层,占高德总流量的一半,重要性可想而知。

接下来简要介绍下渲染网关承接的业务,不便大家有一些更平面的意识。

渲染网关承接高德手机 App、车机、开放平台等起源所有的图面渲染。大家在应用高德时,看到的建筑物、地形图、名称、路线、地铁站、公交站、红绿灯等等所有图面,都是由渲染引擎通过渲染网关透出到端。上面放几张图,不便大家有一些更理性的意识。

下面图一为行前,图二为行中,图三为打车页面,图四为景区手绘图。渲染网关波及业务泛滥,以上仅为举例,其余业务就不在这里贴图了。

3.2 重构难点

做过重构我的项目的同学置信都深有体会,重构我的项目中最大难点有二,一是要保障业务正确性,二是要保障服务稳定性。

对于保障业务正确性,一般来说,重构的服务大多数为老服务,老服务面临的最大问题是历史逻辑简单,人员更迭,文档缺失,这些因素都是重构过程中的“拦路虎”。

渲染网关重构同样如此,它波及高德手机端、车机端、开放平台、打车等各个业务线,所有的历史版本,再加上上述因素,所以保障业务正确性是一件十分艰难的工作。

对于保障服务稳定性,做过网关的同学应该都晓得,网关自身的属性就决定了它并不会有频繁的业务迭代,稳定性是网关的第一诉求。咱们要保障,无论外部环境 / 依赖是否失常,网关始终能放弃高可用。因为Go 版本中间件不足在大流量场景的充沛验证,这一难点须要认真评测,用适合的办法和伎俩,尽可能的在仿真环境里验证各种边界状况,从而保障在生产环境不出问题。

3.3 技术计划

在重构高德渲染网关时,咱们整体技术计划分三大步走:

3.3.1 线上流量比照

如何验证新服务的业务正确性呢?咱们采纳了线上流量比照的形式。

咱们后期做了大量调研,心愿找到一个 满足(近)实时,二进制级比照的工具,但惋惜并没有找到一个满足要求的工具。因为渲染业务的非凡属性,渲染网关绝大多数接口返回的是二进制矢量数据,所以现实的工具不仅要能反对惯例数据比照,也要能反对二进制级比照。

二进制级比照的另一个益处是,能够排除字符集差别,不同语言库函数差别。更能保障比照的准确性。有些同学可能会想到打日志,而后离线读取比拟的形式来做比照,这种形式有很多弊病。

首先,流量无奈重放至指定机器。其次,这种应用形式个别为固定语料,语料残缺度不够,不能齐全模仿线上环境。此外,打日志比照带来的字符集和语言库函数差别,会对比拟准确性有较大影响,特地是对于特殊字符(当 7 层协定为二进制协定时更加显著)。没有现成的称手工具,怎么办?” 逢山开路,遇水搭桥 ”。

咱们 自主研发了一款(近)实时流量比照工具,它保障了此次重构的业务正确性,并且还能服务于高德其余业务的重构。其技术细节对 TCP/IP 波及较多,十分有意思,感兴趣的同学能够间接跳至《流量比照工具(ln)技术细节》一节。

3.3.2 仿真环境压测

做服务的同学置信都深有体会,想让服务保障做到 5 个 9 的可用性并不是一件容易的事。实在生产环境中可能会呈现各种状况,咱们要想方法验证各种边界状况下服务的稳定性,能力保障服务高可用。对于重构实现的新服务,更须要一个仿真环境,进行各种状况验证。

构建仿真环境,咱们须要放弃 机器基线、内部依赖、内部流量 均统一(比方从线上引流)。仿真环境不仅要提供失常态环境的能力,更要能提供异样态环境的能力。

异样态包含断网,网络丢包等等。有句话说的好:20% 的代码实现性能,80% 的代码来解决各种异常情况。咱们 在实践中构建异样态的次要伎俩为混沌工程,通过混沌工程模仿下至操作系统级的异样(如断网,丢包等),上至应用层的异样(如消息中间件积压,JVM 办法前后 Hook 模仿业务异样等等)。

在仿真环境里,同时进行长时间极限压测,语料从线上导流,压测在失常态,异样态均进行,察看服务在一段较长时间内的体现,从而得出服务的稳定性,可用性论断。

观测指标包含 根底指标,例如 CPU、磁盘利用率、内存利用率、连接数,以及业务指标,例如业务接口成功率、胜利量、总量、TP99。通过这种形式,基本上齐全笼罩了可能呈现各种状况,充分保证了服务稳定性和高可用。

3.3.3 平滑灰度切流

前边讲了如何保障业务正确性和服务稳定性。接下来说说如何保障平滑灰度切流。牢牢恪守 阿里公布三准则 是平滑灰度切流的“法宝”:可灰度 可监控 可回滚

在具体实际中,咱们依照如下步骤 灰度切流

a. 原 Java 集群不动,新申请一套 Go 集群。批改路由规定,局部白名单用户应用 Go 集群服务。

b. 一一接口批改路由规定至 Go 集群,缓缓灰度,期间亲密察看机器姿势,业务日志,监控指标。如有异样一键切回至 Java 集群。

c. 接口全量切至 Go 集群后,Java 集群 /Go 集群同时共存一段时间。

d. 逐步下掉 Java 集群机器。

3.4 次要收益

第一个重要收益:降本提效。高德渲染网关由 Java 换成 Go 语言之后,机器数缩小近一半。用原来一半的资源实现了雷同的工作,大大降低了老本,进步了资源利用率,更好反对了业务倒退,大大降低了业务流量快速增长带来的接入层机器增长速度。

第二个重要的收益是:验证了高德与团体单干共建的 Go 版本中间件的稳定性,肯定水平上欠缺凋敝了团体 Go 生态。在大流量场景考验过后,高德与团体单干共建的 Go 版本中间件稳定性失去了相当充沛的验证。

第三个重要的收益是:为网关云原生化铺路。网关 Go 化只是第一步,Go 是云原生基础设施实现应用较多的语言,第一步抹平语言差别,对于网关后续云原生化,好处多多,可升高革新危险和老本。

当然,高德渲染网关重构过程中还有许多十分有用的工具积淀。可为后续业务重构提供关键性保障,比方自研的流量比照工具 ln。

4. 技术干货

4.1 流量比照工具(ln)技术细节

先提一个问题,做一款(近)实时流量比照工具须要实现哪些性能?没错,就是流量复制,流量解析,流量重放,流量比对。其实不止这些,在实践中更多是一个流量回归闭环,如下图:

4.1.1 流量复制

为了反对所有的 7 层协定,流量获取必须从 3 层或 4 层开始。有同学会立马想到 tcpdump。没错,就是 tcpdump。tcpdump 出的文件就是实实在在的流量。复制流量这一步曾经有着落了,至于实时,能够两到三个过程错开工夫,时间段首尾相互重叠即可实现实时。

另外,设计此工具的另一个考量点是,对线上机器不能有太重的负载,防止对线上机器产生稳定性影响。此种流量复制形式十分轻量,对线上机器减少的负载十分小,能够忽略不计。

4.1.2 流量上传 & 流量拉取

流量上传和流量拉取均应用外部文件服务。

4.1.3 流量比照

流量比照为了保障比照的严谨性,排除可能的字符集烦扰 / 不同库函数实现烦扰,咱们原生反对了二进制流比照。

4.1.4 问题流量本地重放 Debug

回归流量时,可能会发现局部流量比对不统一,这时咱们心愿只重放特定流量到指定机器,以便于 Debug 或其余操作,ln 原生反对了此性能。

4.1.5 流量解析

流量解析十分有意思,这种单纯的高兴来自于对网络协议的 ” 把玩 ”。

理论做法就是如何解析 tcpdump 文件,拿到 tcp payload,还原出 http 申请。

这里有两个关键点,一是咱们如何从 tcpdump 文件中拿到 tcp payload,二是咱们如何把四层的 tcp payload 重新聚合成七层的 http 申请。

4.1.5.1 tcpdump 文件格式

先说如何从 tcpdump 文件拿到 tcp payload,如果能晓得 tcpdump 文件的格局,不就能够晓得 tcp payload 在哪个地位,长度如何了么?这一趴咱们就来看看 tcpdump 文件格式。

先看 tcpdump 文件总览

文件头的格局和长度都是固定的,如下:

咱们能够在读取 tcpdump 文件后,往后挪动 23 字节,而后开始解决每个数据包。每个数据包的格局如下:

咱们解决每个数据包,将前边的包头,数据链路头,ip 层头,tcp 协定头顺次跳过,最终偏移到 tcp payload 第一个字节地位。其中的更多实现细节(不同层的头字段值的判断,不同长度的判断,大小端的判断,申请数据包与响应数据包如何对应等等)在此不再开展。这里只介绍大体思路,感兴趣的同学能够深挖网络协议。

4.1.5.2 tcp payload 还原 http 申请

这一部分介绍如何将 tcp payload 还原成 http 申请(此处 http 指 http1.0/1.1,不含 http2),ln 工具中的残缺实现是由 tcp payload 还原出申请及对应的响应,此处为了便于了解,仅解说如何解析 http 申请。解析出 http 申请实际上已能够从新别离申请新老服务,比照响应二进制流。

一条 tcp 连贯,多个 payload 发送(这里仅做示意,判断丢包重发等诸多状况属于代码细节,在此不再开展)。可能多个 payload 对应一个 http 申请;也可能一个 payload 的前一部分对应一个 http 申请,后一部分对应另一个 http 申请。咱们要做的就是把多个 payload 造成的字节流读入,按 http 帧的格局,聚合 http 申请即可。另外,http2 的申请不能按这种形式聚合。

4.2 一些 go 语言最佳实际

4.2.1 sync.pool 实际

因为 Go 语言和 Java 语言的内存管理机制不雷同,在内存的申请,开释开销也有差异。

对于 Go 语言来说,sync.pool 是复用内存的一把利器。sync.pool 长处有许多,比方缩小内存的申请,缩小了零碎调用,缩小了 gc 的压力。但事物都有两面性,sync.pool 同样如此,咱们在应用 sync.pool 的时候须要留神,寄存在 sync.pool 里的对象会在不告诉的状况下被回收掉,所以相似数据库连贯等资源不适宜应用 sync.pool。

总之,sync.pool 能够复用内存,缩小机器负载,非常适合长期对象。

4.2.2 Golang Byte

Go 语言 Byte 类型为无符号,Java 语言 Byte 类型为有符号,在 Java 服务迁徙 Go 服务过程中,Java 代码中 Byte 类型正、负、零的比拟要留神。

4.2.3 Golang 字节切片与字符串高效转换

字节切片转字符串

func Bytes2String(b []byte) string {return *(*string)(unsafe.Pointer(&b)) 
}

字符串转字节切片

func String2Bytes(s string) []byte {x := (*[2]uintptr)(unsafe.Pointer(&s))     
    h := [3]uintptr{x[0], x[1], x[1]}     
    return *(*[]byte)(unsafe.Pointer(&h)) 
}

应用此种形式转换,性能很高。起因在于底层无新的内存申请与拷贝。然而不论是字节切片转字符串,还是字符串转字节切片,字节切片中的值更改都会影响字符串的值,使用者要依据业务逻辑判断是否承受,要更准确的把控生命周期。

4.2.4 Golang 库函数重写

对于网关来说,耗 CPU 比拟多的一部分是 Hash 函数 / 编解码函数 / 加解密函数 / 序列化反序列化函数等。在实践中咱们重写了相干的库函数,在 CPU 负载上做了大量优化。

想要升高 CPU 负载,咱们得先晓得 CPU 是如何工作的,能力晓得如何写代码会更好的升高 CPU 负载。这里会介绍粗略的 CPU 工作原理。

放张 CPU 流水线工作步骤图

  • 指令读取(instruction fetch,IF)
  • 指令解码(instruction decode,ID)
  • 执行(execute,EXE)
  • 内存拜访(memory access,MEM)
  • 寄存器回写(register write-back,WB)

次要优化 MEM 步骤,利用 CPU 缓存尽可能减少 MEM 步骤所占时钟周期,从而升高 CPU 负载。

相似 NUMA 架构,affinity 等升高 CPU 负载的形式也是同样的思维,尽可能减少 Load 数据所需的时钟周期。

对于优化 Golang 库函数来说,能够晋升的点有两个:优化算法自身;优化 CPU 缓存亲和度。

咱们专一于第二种,拿 base64 编解码函数举例,传入的 Byte 切片与返回 Byte 切片,底层并非为同一数组,同一内存。这两头就波及两块能够额定耗费 CPU 时钟周期的点,一是内存的申请与开释,二是两块内存别离拜访带来的 CPU 缓存争用问题(与伪共享不齐全一样)。

如果咱们复用传入的内存呢?即边解码边覆写同一块内存。美好的事件产生了,上边所说的问题不存在了。用更少的时钟周期实现了一样的工作。须要留神的是,因为函数的输出和输入应用同一块内存,对程序开发者来说有更高的编码要求,即对数据在程序中流转的生命周期有更精准的把控力,代码要打磨的很粗疏。

5. 将来瞻望

网关的下一步是 云原生化 ,采纳Service Mesh 形式实现。这能够解决目前中心化网关的弊病,去中心化能够晋升接入层稳定性,缩小爆炸半径,加强隔离能力,实现更精密粒度的管控。

其次,升高机器老本,依照目前外部压测及业界已有的实际压测论断,Mesh 化后老本会进一步缩小,思考到现有 RPC 框架自身的耗费,老本会进一步缩减。且数据面代理也在一直优化中,后续性能体现会更优异,额定两跳对机器的负载将进一步降落。

再有,网络层能力集大大加强。网关 Mesh 化,能够带动上游业务 Mesh 化,最初在整个网络层做一个能力超集。

现有的 Service Mesh 框架提供的能力能够概括为 Connect,Secure,Control,Observe 四大局部,其能力是现有网关能力的超集,能够做到之前做不到的事件,最显著的是 Observe 能力带来的益处,可大大增强全链路服务可观测性,这于对后续发展服务稳定性,全链路故障疾速定位等工作有极大帮忙。

以上要做的事件任重而道远,另外咱们在会做更多云原生的试点和落地,技术同学都分明,从技术选型到技术原型,再到理论业务落地,两头有很长的路要走。但路选对了,就不怕远。

诚招同路人

笔者所在团队求贤若渴,盼有激情的技术小伙伴一起做些乏味的事,各技术栈均可,有志愿的小伙伴请纵情砸简历到邮箱 gdtech@alibaba-inc.com,邮件主题为:姓名 - 技术方向 - 来自高德技术。

Happy Hacking!

正文完
 0