​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!

高德渲染网关Go语言重构实践的更多相关文章

  1. 第二章 C语言编程实践

    上章回顾 宏定义特点和注意细节 条件编译特点和主要用处 文件包含的路径查询规则 C语言扩展宏定义的用法 第二章 第二章 C语言编程实践 C语言编程实践 预习检查 异或的运算符是什么 宏定义最主要的特点 ...

  2. 网易新闻App架构重构实践:DDD正走向流行

    网易新闻App架构重构实践:DDD正走向流行 https://mp.weixin.qq.com/s/FdwrT_xn3CQqpWoRVBttvQ 小智 InfoQ 2020-05-14 作者 | 小智 ...

  3. C语言 教学实践建议

    这是2016年秋季学期和北京工业大学耿丹学院合作教学的计划. 2016级有四个班,每班大约 32 人,每班配有一个有一定实际工作经验的助教,配合老师把课教好. C语言是一门基础课, 是耿丹学院新生的第 ...

  4. 通过业务系统的重构实践DDD

    最近新接了一个业务系统——社区服务系统,为了快速熟悉和梳理老系统的业务逻辑和代码,同时对老系统代码做一些优化,于是打算花上一个月时间不间断地对老系统服务进行重构.同时,考虑到社区业务的复杂性,想起了之 ...

  5. Go语言设计模式实践:迭代器(Iterator)

    关于本系列 决定开个新坑. 这个系列首先是关于Go语言实践的.在项目中实际使用Go语言也有段时间了,一个体会就是不论是官方文档.图书还是网络资料,关于Go语言惯用法(idiom)的介绍都比较少,基本只 ...

  6. Go语言设计模式实践:组合(Composite)

    关于本系列 这个系列首先是关于Go语言实践的.在项目中实际使用Go语言也有段时间了,一个体会就是不论是官方文档.图书还是网络资料,关于Go语言惯用法(idiom)的介绍都比较少,基本只能靠看标准库源代 ...

  7. Django学习笔记之模板渲染、模板语言、simple_tag、母版子版、静态配置文件

    一.首先我们用PyCharm来创建一个Django项目 终端命令:django-admin startproject sitename 图形创建:   这样一个Django项目就创建完成了,上面可以看 ...

  8. ArchSummit分享 | 高德地图App架构演化与实践

    讲师介绍 郝仁杰,高德地图无线开发专家.在7月13日落幕的2019年ArchSummit峰会上就高德地图近几年的App架构演化和实践进行了分享. 背景概述 高德是国内领先的数字地图内容.导航和位置服务 ...

  9. 深度学习在高德ETA应用的探索与实践

    1.导读 驾车导航是数字地图的核心用户场景,用户在进行导航规划时,高德地图会提供给用户3条路线选择,由用户根据自身情况来决定按照哪条路线行驶. 同时各路线的ETA(estimated time of ...

随机推荐

  1. Java 获取Word中指定图片的坐标位置

    本文介绍通过Java程序获取Word文档中指定图片的坐标位置. 程序运行环境: Word测试文档:.docx 2013 Free Spire.doc.jar 3.9.0 IntelliJ IDEA J ...

  2. Kafka之--自动启动zookeeper & kafka 脚本

    1) 首先配置SSH免密登录,在这里我用kafka(151)这台机器来作为启动脚本的存放和执行机器 [root@kafaka3 .ssh]# pwd #生成SSH KEY /root/.ssh [ro ...

  3. 字符串匹配算法(二)-BM算法详解

    我们在字符串匹配算法(一)学习了BF算法和RK算法,那有没更加高效的字符串匹配算法呢.我们今天就来聊一聊BM算法. BM算法 我们把模式串和主串的匹配过程,可以看做是固定主串,然后模式串不断在往后滑动 ...

  4. 本地图片转base64编码

    通常获取图片的base64编码都是通过input的上传file属性获取转化,但是有时候需要的是本地图片不经过上传操作,直接拿本地图片转成base64编码就不行了,input上传操作需要人为操作一下,没 ...

  5. PS Lite - 源码解读

    PostOffice 类 /** * \brief 系统的中心. */ class Postoffice { public: /** * \brief 返回单例对象. */ static Postof ...

  6. Http Request Smuggling - Note

    http请求走私漏洞 访问Burp靶场速度感人..都要哭了(如果没有账户的先创建账户) 基础补充 pipeline http1.1有了Pipeline,就不需要等待Server端的响应了.浏览器默认不 ...

  7. 从理发店小弟到阿里P10大牛,一位高中学渣的“登天”之路

    蚂蚁金服,可能是众多程序猿眼中梦寐以求的归宿,无数拿过数不清奖状的各个高校走出的学子精英都挤破头皮,只为能在蚂蚁占有一席之地. 蚂蚁金服里不乏各种深藏不露的大佬,到了那里才会深刻明白一山还有一山高这句 ...

  8. SpringMVC学习03(控制器Controller)

    3.控制器Controller 3.1 控制器Controller 控制器复杂提供访问应用程序的行为,通常通过接口定义或注解定义两种方法实现. 控制器负责解析用户的请求并将其转换为一个模型. 在Spr ...

  9. awk-07-IO和printf语句

    IO语句 1.getline 2.getline var 把a文件的行,追加到b文件的结尾 把 a 文件的行替换 b 文件的指定字段 把 a 文件的行替换 b 文件的对应字段 3.command | ...

  10. 北航OO第四单元——UML图解析

    北航OO第四单元--UML图解析 作业要求简析 刚接触本次作业可能需要花上一会才能搞清楚到底是要我们写个啥,在这里简单说一下: UML图的保存格式.mdj文件是以json文件的形式存储的,将每一个Um ...