​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. golang拾遗:内置函数len的小知识

    len是很常用的内置函数,可以测量字符串.slice.array.channel以及map的长度/元素个数. 不过你真的了解len吗?也许还有一些你不知道的小知识. 我们来看一道GO101的题目,这题 ...

  2. CentOS下配置Nginx实现动静分离实例

    测试环境: CentOS Linux release 7.6 PHP 7.2.32 两台服务器:192.168.1.109(Nginx),192.168.1.118(Apache) 1. 安装配置19 ...

  3. 【NOIP2007】Hanoi双塔问题

    题目描述 给定A.B.C三根足够长的细柱,在A柱上放有2n个中间有孔的圆盘,共有n个不同的尺寸,每个尺寸都有两个相同的圆盘,注意这两个圆盘是不加区分的(下图为n=3的情形). 现要将这些圆盘移到C柱上 ...

  4. Zookeeper简介(五)

    1.定义 Zookeeper:是一个分布式的,开发源码的分布式应用程序协调服务,是hadoop的子项. 2. 特点 具有高可靠性,可扩展性,分布式,可配置的协调机制. 3. 作用 3.1 数据发布与订 ...

  5. [BSidesCF 2020]Had a bad day 1--PHP伪协议

    首先先打开主页,审查代码,并没有什么特别的地方使用dirsearch,发现flag.php![在这里插入图片描述](https://img-blog.csdnimg.cn/82348deddfd94c ...

  6. 插入排序(insertion_sort)——Python实现

      # 插入排序 # 作用:对给出的n个顺序不定的数进行排序 # 输入:任意数组A # 输出:按顺序排列的数组A # 时间复杂度 n(n-1) 至 (n(n-1))/2   # 插入排序过程 # 第一 ...

  7. 《笨方法学python》随笔

    字符串 字符串可以包含格式化字符%s,只要将格式化的变量放到字符串中,再紧跟着一个百分号 %(percent), 再紧跟着变量名即可.唯一要注意的地方是, 如果你想要在字符串中通过格式化字符放入多个变 ...

  8. idea创建普通Web项目lib目录无法输出,tomcat不报错问题

    idea创建普通Web项目lib目录无法输出,tomcat不报错问题 idea版本:2021.2 tomcat版本:9.0.50 项目结构 创建一个普普通通的web项目,目录结构大概就是这样 . ├─ ...

  9. 每个程序员的必经之路:Hello,world!

    每个程序员的必经之路:Hello,world! Java 源程序 也称源代码,是指未编译的按照一定的程序设计语言规范书写的文本文件,是一系列人类可读的计算机语言指令. 在现代程序语言中,源代码可以是以 ...

  10. 大数据开发-Go-新手常遇问题

    真正在工作中用Go的时间不久,所以也作为新手,总结了一些常见的问题和坑 Go 中指针使用注意点 // 1.空指针反向引用不合法 package main func main() { var p *in ...