日前,字节跳动技术社区ByteTech举办的第七届字节跳动技术沙龙圆满结束。本次沙龙的主题是《字节高性能开源微服务框架:CloudWeGo》。沙龙中,字节跳动基础设施服务框架高级研发工程师高文举为大家分享了《大规模企业级 HTTP 框架的设计和实践》。本文根据分享整理而成。本文将从以下五个方面介绍CloudWeGo的大型企业级HTTP框架Hertz:字节跳动内部GoHTTP框架的变化;企业级HTTP框架的设计思路和实现思路;赫兹的核心特征;未来的规划和挑战;概括。一、字节跳动内部GoHTTP框架的变化在正式介绍第一部分内容之前,先给大家看一组关键词。赫兹项目成立于2020年初,2020年10月,赫兹发布了第一个可用版本。2022年6月,赫兹正式开源。截至目前,Hertz已支持字节内超过14000个业务服务,日峰值QPS超过5000万。Hertz不仅支持业务服务,还横向支持字节内部的各种基础组件,包括但不限于字节跳动服务网格控制面、公司级压测平台、FaaS、各种业务网关等。赫兹的高性能和强稳定性可以支持复杂多变的业务场景。在公司内部,Hertz接管了大量基于Gin框架开发的存量服务,大大降低了业务资源的使用成本和服务延迟,有助于公司层面的降本增效。下面我们可以从赫兹出现的背景以及赫兹的设计目标和理念来理解,赫兹的出现绝非偶然。1.1基于Gin封装众所周知,Byte内部使用Golang比较早。2014年前后,公司就已经开始尝试改造部分Golang业务。2016年,我们基于开源的GolangHTTP框架Gin框架封装了Ginex,也就是Ginex刚出现的时期。同时,2016年依然是一个创业的时代,这一时期的构架伴随着业务的快速野蛮生长。我们的口号是“强化奇迹”,我们把解决业务需求作为重中之重。Ginex的迭代方式是业务端和框架端在同一个仓库共同维护迭代。1.2问题出现在2017-2019年期间,也就是Ginex发布之后,问题逐渐出现。主要有以下几点:迭代受限于开源项目。Ginex是基于Gin的开源包,所以在迭代中会受到一些限制。一旦有公司级需求的开发,以及bug修复等,我们都需要用开源框架Gin进行联合开发和维护。这个循环不能完全由我们自己控制。代码混乱和扩容,维护困难因为我们是和业务同学共同开发和维护Ginex框架,所以我们对整个框架的方向没有完全的自主权,导致整体的代码混乱和扩容,我们发现比较多并且后期维护难度更大。无法满足对性能敏感的业务需求另外,我们可以使用Gin做很少的性能优化,因为Gin底层是一个基于Golang的原生库,所以如果要优化,我们需要做很多在原生库的基础上进行修改。其实很难。无法满足不同场景的功能需求我们内部逐渐出现了一些新的场景,所以会有对HTTPClient的需求,对Websocket的支持,对HTTP/2的支持,对HTTP/3的支持等等,但是在原生的Ginex还是有很多难以扩展的功能需求。1.3开源框架魔改渐渐地,一些业务线开始初步尝试,会对其他一些开源框架进行魔改。一个典型的例子就是一些业务线试图在Fasthttp的基础上进行魔改。Fasthttp是一个专注于高性能的开源框架。基于它的魔变可以帮助企业在短时间内解决问题。这种魔改现象带来的问题是,框架魔改是部分业务线的自发行为,每个业务线可能会根据自身业务特点进行自己的维护,导致维护成本增加非常严重。此时我们似乎陷入了Ginex循环。就像前段时间热播的电视剧?一样,我们仿佛从开往学院南路的45路公交车上醒来,发现自己要去公司维护下一代Ginex框架。大家也可以想一想,如果让你面对这样的场景,你会怎么做?1.4小结第一章内容总结如下:早期开源框架封装是基于早期开源的GolangHTTP框架,实现了Ginex的封装。随着实践的发展,逐渐出现了框架混乱和扩展的问题,框架的维护越来越困难,不能很好地满足业务的新需求。为了解决问题,我们需要思考如何跳出基于另一个开源框架的魔改怪圈,把Byte内部的企业级框架做的更好。除此之外,还有一个遗留问题,那就是如何打破这个魔改的怪圈?第2章将为您解答这个问题。2.企业级HTTP框架的设计思考和实现思路2.1走出怪圈为了走出魔改怪圈,我们决定从以下三个方面入手。自主研发由于Ginex基于开源框架Gin,无法灵活控制,我们将改为完全自主研发的框架。自研框架的代码是完全独立可控的,也可以避免引入任何三方不可控的因素,让我们对自己的框架有比较完整的控制权。质量控制下图列出了一些常规的质量控制方法。我要强调的是fuzz测试,在Byte内部Hertz框架的稳定性测试中被广泛使用。它的核心点是通过一系列的模拟服务,试图模拟在线用户在使用我们的框架时实际遇到的一些场景和使用方法。然后通过一些随机算法,生成尽可能复杂的场景,涵盖各种案例,这让我们能够检测到一些潜在的问题。这套测试也是在赫兹早期的质量建设中,帮助我们防患于未然。严格访问由于Ginex的问题是每个人都在往里面写内容,我们可以控制访问,建立完整的需求开发和评审闭环,控制迭代的全过程,从而控制代码访问。同时,我们配备统一的需求管理和严格的发布准入规范,做标准的公司级框架。举个更形象的例子,如果把次世代框架比作一个人——“框架人”,自主研发意味着这个“框架人”首先会拥有对自己身体的支配权,而不会被受环境或他人的影响;质量控制是指“架构师”可以定期体检,及早发现一些潜在疾病,将其扼杀在摇篮中;严格准入,意味着“架设人员”要有科学的饮食摄入和自律的生活习惯。可以想象,如果能够做到以上三点,我们的“框架人”就能够拥有一个健康的身体。2.2痛点在明确了如何跳出恶性循环之后,我们也应该清楚地知道框架应该具备哪些功能和特性,即首先要关注框架的核心痛点。“框架人”不仅要有健康的身体,更要有有趣的思想和灵魂。一个成熟的框架不仅要响应业务端的需求,比如功能需求、性能需求、易用性和稳定性等,还要考虑框架本身的发展,这正是我们在上文中忽略的。Ginex的迭代过程。如下图右侧金字塔所示,最上层是高效支撑。毫无疑问,框架的存在就是为了支持我们的业务需求。中间层是质量保证红线框架。框架需要保证自身的质量。唯有高质量完成的框架,才有信心承载字节内部5000万QPS和各种使用场景。金字塔底部是长期的、可持续的发展,这也是作为未来持续迭代的框架最重要的一点。2.3框架科学发展观在前面部分的基础上,我们可以进一步梳理框架的需求和痛点。主要有两个痛点:多样化的需求:支持各种业务线和基础设施(横向扩展)。灵活的结构:控制整个HTTP生命周期(垂直模块化)。在此基础上,进一步抽象出框架的科学发展观:集群需求:为通用能力而设计。跳出局域:对于一些复杂的问题,在更大范围内寻求最优解。后续我将进一步阐述赫兹是如何实现这一科学发展观的。2.4小结第二章内容总结如下:跳出怪圈,介绍“框架人”的概念,帮助大家理解框架的自主开发、质量控制和严格准入。痛点梳理,为“框架人”注入有趣的灵魂。框架需要响应业务方的多样化需求,保证自身的可持续发展。框架的科学发展观要求集群,跳出地方。3.Hertz的核心特性Hertz框架是如何实现框架的痛点和第二章提到的科学发展观的?本章将详细介绍。3.1层次抽象首先介绍Hertz框架的架构设计。下图展示了一个请求从建立、连接到完成的全过程。左边是客户端,右边是服务器。当我们发起建立链接请求后,链接建立完成;然后客户端向服务端发起请求,服务端进行路由处理,再将路由引导到业务逻辑处理;业务逻辑处理完成后,服务器返回请求,完成一次HTTP请求调用。那么在这个过程中我们的框架到底做了什么呢?从图中不难发现,首先是框架进行链路处理,然后是协议处理,然后是基于路由的逻辑分发,即路由处理,最后是业务逻辑处理。我们把框架做成一个结构体之后,我们会发现这个结构体包含了这四个部分。基于这个逻辑,我们可以看一下Hertz的整体架构图。如下图,如果从下往上看红线圈出的部分,可以发现这就是上面提到的请求建立的全过程。各层的能力和作用如下:传输层Transport:抽象网络接口;协议层协议:解析请求,渲染响应码;路由层Route:基于URL的逻辑分发;应用层Application:直接业务交互,出现大量API。我们可以看到,除了图中中间部分包含的四层外,左右两边还有两列。右边是公共层Common,主要负责提供公共能力,常用的日志接口,链路跟踪,以及一些配置处理相关的能力。左边是Hertz的代码生成工具Hz,也叫脚手架工具,可以帮助我们内部基于IDL快速生成项目骨架,加速业务迭代。Hertz的层级设计可以与代码组织结构一一映射。下图展示了赫兹仓库中的代码组织结构。可以看到根目录下的cmd包中存放了Hz工具,pkg包中存放了上述四个主要层和公共层Common。因此,同学们在看到架构设计图后,可以直接在Github上学习Hertz的代码。Hertz:https://github.com/cloudwego/hertz总的来说,Hertz的架构设计理念是“简洁有序,确保所有开发者都能轻松理解,并在开发过程中持续贯彻”。3.2易用性和可扩展性那么基于Hertz的架构设计应该如何发展易用性和可扩展性呢?下图显示了Hertz架构的主要四个级别的抽象。应用层应用层提供一些通用的能力,包括绑定请求、响应渲染、服务发现/注册/负载均衡、服务治理等。其中,洋葱模型中间件的核心目的是让业务开发的同学可以基于这个中间件快速扩展业务逻辑。扩展方法是在业务逻辑处理前后插入埋点进行相应的处理。一些有代表性的应用,包括日志管理、前端安全检测等,都是通过洋葱模型中间件处理的。路由层路由层也很笼统。主要提供静态路由、参数路由、配置路由优先级和路由修复的能力。如果我们的路由层不能满足用户需求,也可以支持用户扩展自定义路由。但是这些路由能力在实际应用中完全可以满足大部分用户的需求。协议层Hertz提供HTTP/1.1和HTTP/2。HTTP/3也是我们正在建设的能力。我们也会提供HTTP相关的Websocket等多协议支持,支持完全由业务决定的自定义协议层扩展。传输层目前我们已经构建了两个高性能的传输层实现。一种是基于CloudWeGo开源高性能网络库Netpoll的传输层扩展,另一种是支持基于标准库的传输层扩展。此外,我们还可以在传输层上支持自定义传输层协议扩展。能够在下图中的每一层都打上红色标记,可以体现出我们可以在框架的任何一层上,最大程度的支持用户进行定制,从而满足企业级内部用户和潜在用户的业务需求在最大程度上。如果您想了解更多关于赫兹的信息,可以参考CloudWeGo官网的赫兹部分。以上内容均有详细说明。官网:https://www.cloudwego.io/zh/docs/hertz/3.3性能探索在性能方面,赫兹如何在自主可控的范围内进行高性能探索?3.3.1场景描述熟悉Hertz代码的同学会发现我们的HTTP/1.1协议借鉴了Fasthttp的一些优化思路和方法。HTTP/1.1协议中的Header是一个变长的数据段,往往需要解析到最后一行才能判断解析是否完成。同时,为了减少系统调用次数,提高整体解析效率,在涉及到IO操作时,我们通常会引入带缓冲区的IO数据结构。如下图,它的核心点是底层buffer,类似于一个完整的内存空间,我们可以将IO读取的数据放到这个空间中暂存。3.3.2bufio.Reader的问题这样做的问题是,原生bufio.Reader的长度是固定的,当请求的Header大小超过buffer长度时,.Peek()方法直接报错(ErrBufferFul),无法完成既定的语义功能。3.3.3一些可能的解决方案针对以上问题,其实有一些可能的解决方案:直接使用bufio.Reader的限制作为Feature,使用buffersize作为Headersize的限制。如果超过这个大小,Header会直接解析报错,这也是Fasthttp的做法。但实际上,超过缓冲区长度后报错,会导致我们无法处理这部分请求,从而限制框架的功能。header用状态解析,暂存中间数据,通过在上层叠加额外的复杂度,突破了bufio本身的限制。但是暂时存储中间状态会涉及到复制一些内存,这必然会导致性能受限。3.3.4真实使用环境复杂多变Byte内部有很多使用场景。我们不仅要支持各个业务线的发展,还要支持一些横向的基础组件。不同的业务,不同的场景,不同的数据规模。如何通用高效地解决bufio.Reader的问题,成为Hertz内部面临的重要挑战。既然站在了“巨人”Fasthttp的肩膀上,是不是可以再向前迈进一步呢?答案是肯定的。基于内部使用场景,结合Netpoll的优点,我们设计了自适应链接缓冲区,并用它来替代原生的bufio.Reader。从下图可以看出,我们的缓冲区不再是定长缓冲区,而是一条链。该链上每个缓冲区的大小都可以根据实际在线请求进行动态缩放和调整。基于LT触发模型的预复制数据。从实现效果来看,这种自适应的调整,可以让我们的业务方毫无感觉的去支持他们的任何一个业务特性。也是因为我们可以动态的扩容和缩容缓存,从而保证协议层最大程度的零拷贝协议解析,可以提高整体的解析性能,降低延迟。3.3.5对HTTP/1.1的持续优化由于HTTP/1.1在字节跳动中仍然是比较主流的协议,我们在HTTP/1.1的基础上做了很多尝试。首先是协议层探索。我们正在尝试基于HeaderPasser进行重构,让解析Header的过程更加高效。我们也尝试做了一些传输层的预分析,将一些比较扎实的逻辑下沉到传输层进行加速。二是传输层探索。这包括使用writev集成发送Header&Body以减少系统调用的次数,以及通过新接口集成.Peek()+.Skip()语义以在内部提供更高效的实现。3.3.6HertzBenchmark下图是Benchmark的开源数据。左图第一张是Hertz与横向框架Gin、Fasthttp限制QPS在同机环境下的对比。蓝线是Hertz在更高极限QPS下的状态。第二张图是TP99延时状态,第三张图是TP999延时状态。可以看出Hertz的整体延迟处于较低水平。3.3.7字节跳动服务网格控制平面从Gin迁移到HertzCloudWeGo公众号发表了一篇关于字节跳动服务网格控制平面的文章,描述了字节跳动服务网格从Gin框架迁移到Hertz的实现。下图显示了他们的代码的真正好处。将Gin框架换成Hertz框架后,CPU流量从4K左右下降到2.5K左右,Goroutines数量从6w下降到不到100个,Goroutines的稳定性有了很大的提升。.同时,换成Hertz后,框架相关的开销基本消失,服务网格可以稳定承载13MQPS以上的在线流量。字节跳动服务网格基于Hertz框架的实践:https://mp.weixin.qq.com/s/koi9q_57Vk59YYtO9cyAFA3.4总结第三章内容总结如下:分层抽象解构HTTP框架,分层解耦。Easy-to-useandextensible提供更丰富的API和足够灵活的扩展能力,在每个抽象层提供足够灵活的扩展能力以满足可能的需求。自主可控的高性能探索自适应缓冲区,零拷贝分析,未来将开展更多的高性能探索。4、未来的规划与挑战我认为Hertz未来的发展规划主要围绕以下几个方面:第一,打造泛HTTP框架。我们的最终目标是希望Hertz能够解决HTTP领域的所有问题;其次,帮助CloudWeGo,希望Hertz能够帮助CloudWeGo构建企业级的云原生微服务矩阵;最后,希望赫兹能够继续服务更多的用户。5.总结本次分享的主要内容总结如下:字节跳动内部GoHTTP框架变化:从基于开源封装走向自研之路;企业级HTTP框架的设计思考和实现思路:破圈提炼需求,框架科学发展观;Hertz的核心特性:分层抽象、易用的可扩展性、自主可控的性能探索;赫兹未来的规划与挑战:??不断打磨框架,助力CloudWeGo,服务更多用户。