微服务开发:C++语言是否真的不适用?
微服务架构模式是当今炙手可热的企业软件架构范式,经过最近几年的广泛的讨论和优缺点争辩之后已经随着 云原生架构 的逐渐火热而日益呈现星火燎原之势。 抛开这些争论不谈,假设我们需要实现一个微服务架构的服务,是否该用C++语言或者是否能用C++语言呢?
乍一看似乎这个问题没有什么深入探讨的必要,因为微服务开发本身是编程语言中立的,它本身倡导的思想就是用尽量和具体语言无关的技术来解耦业务复杂性,通过智能的API来串联各个可以分布式开发的服务单元。 然而一个不容忽视的现实是,很多谈论微服务开发的书籍或者博客文章要么不谈落地仅仅谈理论(也许这样的贩卖理论的书籍大多多乏味至极没有多少营养); 要么谈到的时候也是无外乎用流行的几个大的技术栈
- Java 生态圈的基于JVM的技术,使用Spring Boot/Spring Cloud等一些现成的服务,或者稍微时髦一些的会用到Scala或者Kotlin来实现新潮的函数式编程乃至流计算技巧或者事件驱动机制
- 新生代的Golang技术,这个在特别强调Cloud的部署环境中讨论的格外多一些,因为Golang语言在很多云计算的基础设施项目中有极大的应用
- 还有一些则用流行的NodeJs技术栈来举例,这方面多多少少得益于NPM这一强大的第三方包管理工具所支持起来的庞大的开源软件分发管理系统
看起来并没有多少人谈论用C++语言来实现微服务;然后是否是因为C++就不适合来实施微服务? 回答这个问题其实并不难,只要从简单的微服务架构的实现要求来看一下就清楚明了了。
微服务实现的技术要求
微服务架构的软件系统的最基本思想其实就是分治和解耦;其它的技术需求都可以认为是因此而衍生出来用于解决它所带来的具体问题的。
因为系统被拆分为多个松耦合的粒度较小的服务,并且服务之间需要能够松耦合,因此每个拆分的服务需要提供和其它服务的通信来协作完成端到端的业务场景。 这样的业务交互是通过某些广泛可用的上层应用程序编程接口来完成的;目前来说这样的接口定义有两种
- 基于HTTP协议的REST风格API,本身是基于存在了二十年以上的HTTP协议来实现的
- 基于Google Protobuf编码的GRPC远程过程调用协议,该协议相对较新,由Google提出
REST API
基于REST风格的API其实要求服务提供方需要按照资源化的风格封装它对外曝露的接口,每一个接口都需要作用域某个资源之上,并且针对该资源提供创建、读取、修改、删除等操作。 这些操作方法通过遵循既有的HTTP协议的方法动词来表达,而实际资源的内容则通过HTTP消息体来传递和交换。
实际的资源内容的定义和校验则依托现有的JSON或者XML容器来封装;API的请求和相应中可以依据需要来提供或者返回具体资源负载;这些资源的定义本身用OpenAPI来定义,客户端和服务端都可以根据这一独立于代码实现的API定义规范来解析和处理。
当然在比较高级的基于HATEOS的用法中,实际消息中封装的资源可能会按照媒体内容互相关联的方式来组织, 使得客户端可以无需在代码中写死资源的URI,而是通过一次请求关联资源并根据返回的链接来动态发现的方式来降低服务之间的耦合。
HTTP协议的实现自然是和语言无关的技术,而JSON的封装对具有几十年历史积淀的C++语言来说也完全不是问题。 至于OpenAPI规范的定义更加是和语言无关的技术。
GRPC
GRPC是相对比较新一点的服务之间的API调用接口实现方式;详细的探讨可以参考这篇文字。 从上层架构上来看,GRPC的设计思路可能更加契合传统的程序员的思路,它的直接思路其实就是要求各个服务之间通过远程过程调用的方式来实现相互交互。
GRPC和传统的基于方法调用的RCP所不同的地方在于它在传统的请求/响应模型之上还提供了客户端和服务端之间的全双工的数据流推送,因而它需要和HTTP/2在一起才能配合完好。 在这种接口范式下,服务提供者需要按照Proto Buffer的语法规范定义自己的服务声明定义文件,然后该文件会被protoc编译器检查解析并生成样板代码。
基于GRPC技术实现的服务需要根据上述的服务样板代码来二次开发,扩展实现自己的业务逻辑;客户端则可以响应的使用生成的客户端样板代码来发起远程调用请求。
这些技术本身有强烈的语言偏爱吗?
基于这些讨论,单纯看微服务落地实现的技术栈要求,它并没有特意对编程语言提出很硬的约束,即理论上任何能够完成中等复杂度任务的编程语言都可以来实现微服务。 无论是REST/JSON还是GRPC/ProtoBuffer,它们本身都具有多语言支持的能力,尤其是GRPC自带的代码生成工具有个很微妙的限制就是不同的编程语言的支持能力是不同的,但是C++语言是功能最完善的之一。
显然我们可以在需要的情况下,豪不犹豫地选用C++语言来实现微服务,只要我们弄清楚了我们确实需要使用C++语言来实现微服务。
什么时候需要使用C++来实现
回答这样的问题并没有一个放之四海而皆准的经验,因为微服务架构的其中一个关键要素就是需要考虑到具体的开发团队的因素,以及业务领域问题的具体需求。 很多时候这样的决策和选择需要不少深思熟虑和权衡才能找到最适合自己情况的那个选项。
业务场景需求
从业务场景需求的角度来说,可能性能需求以及扩展性需求是需要被着重考虑的。对于CPU密集型的微服务来说,选用一个以性能见长的编程语言可能是一个明智的选择, 这方面正是C++语言比较擅长的地方,也许只有Rust语言可以与之比肩,但是C++长期的历史积淀和成熟稳定的特性短期之内Rust还无法匹及。
开发团队技能
从开发团队的方面来考虑,则必须要认识到C++语言对成员的编程水平总体上还是要求比较高,尽管现代的C++语言标准一直在努力尝试往简化语言门槛的方向迈进。 如果既有的团队有很好的C/C++语言背景和丰富的系统编程知识,使用C++语言来做微服务是很自然的选择。 反之期望一个没有什么C/C++背景的团队来临时抱佛脚开发C++为主要语言的微服务很容易给项目的按序推进带来巨大的挑战。
技术和工具
假设这些因素我们都以及考虑周详,具体到实现上,是否就万事大吉可以马上撸起袖子开工写代码了呢? 不幸的是事情并没有那么简单,稍微详细看一下这些技术栈就清楚了。 下面假设我们决定采用随处可见的流行的REST API + JSON的方式来实现微服务。
REST框架
如前所述,REST技术栈本质上是基于HTTP协议之上的一些二次发挥;而选择C++实现的时候第一个现实的因素是C++标准库里面并没有提供HTTP协议的实现, 这也是C++生态系统目前比较大的一个问题:标准库里面的东西实在是太少了,这里面一个很大的原因应该是能放到标准库里面的东西必须要经过委员会的仔细审查,加上过去近四十年的各种技术积累和包袱,加入新的库总要面临重口难调的窘境。 甚至于在Reddit上还有这么帖子就是在探讨是否有一个事实标准的REST框架实现。
Boost.Beast
如果是将标准稍微放开一些,或者可以访问比较新的boost库,那么Beast 可以算一个比较冒险的选择,因为它也是在1.66版本才被加入进去。 要是你所在的公司或者组织由其它方面的考虑停留在了一个比较老的版本的Boost上,那么引入这么一个库就显得比较麻烦。
Beast的主要特点如下
- 保持了header only的方式,最小化编译和链接依赖;副作用就是冗长模板元编译开销
- 采用Boost.asio的异步网络编程模型和协程来实现高性能和高并发,熟悉这些库的话上手会比较容易
- 库本身提供的抽象层次比较高,实际上手可能需要写比较多的代码,或者需要二次封装
- 文档并不是很完备,也许需要一些时间才能更加成熟
REST SDK
微软平台上的C++程序员可能会更偏爱微软出品的开源在GitHub上的REST SDK,同样的它也采用了异步编程模型。
虽然是一个微软官方提供的开源库,它本身还是可以在多个平台上运行的,并且提供了流行Linux环境的二级制发布包,如果没有交叉编译的需求,可以直接下载安装。
Pistache Framework
这是一个功能相当完善并且性能也很出色的REST框架,它本身的特色是能够和swagger的代码生成工具无缝结合,可以用open API的声明来生成模型代码,极大简化了用户的编码负担。
Pistache使用epoll机制来完成高性能的传输层,并提供了流式的API给用户使用,用户可以在REST方法回调中使用异步API将内容写入流,就像操作一个标准输入输出流那样方便。
但是由于这个epoll机制和核心的部分耦合比较紧密,并且没有很好的办法来用epoll来模拟IOCP机制,因此如果需要在Windows上(甚至符合POSIX的Cygwin环境也是一样)使用该库则会碰上跨平台可移植性的难题。
性能评估
C++程序的一个很大的关注点就是性能, 这篇博文 提供了一个性能评估数据,总体而言Pistache的性能明显占据上风。 可惜这个对比的报告时间有些老,好在代码是开源的,拉取三个仓库自己的最新版本在本地虚拟机上跑一下,可以得到如下的结果:
框架 | 98%请求完成的时间(ms) | 每秒处理的请求数 | 示例代码的行数 | Github星标 |
---|---|---|---|---|
cpprestsdk/default | 107 | 12.23 | 47 | 4478 |
cpprestsdk/RapidJSON | 85 | 14.28 | 46 | 4478 |
restbed | 41 | 32.54 | 39 | 1078 |
pistache | 9 | 182.85 | 33 | 1683 |
此外,因为最新的库代码以及发生了变更,因此原来repo中的示例代码或者CMakeLists.txt文件需要做些微调,基本上按照对应项目的最新文档适度改动即可。我自己的虚拟机只分了2个CPU核,所以绝对数据比原作者的要低。
稍微有点奇怪的是,从项目的流行度上来看,来自微软的开源项目cpprestsdk表现的更加热门一些,无论是星标数还是fork数字都非常亮眼; 但是这里的性能比较却是三者之中最差的一个,是否和微软的自带明星光环有关有待进一步详查。
JSON解析和处理
传统的REST微服务实现用JSON来编码消息中的资源内容,因而JSON解析能力是实现REST服务的一个重要的考量因素。 从使用的角度来看,JSON表述的文档结构常常可以简单地看作是一颗树结构,里面的具体节点元素只要符合 RFC8259规范要求(更老一点的版本是RFC4627) 就可以了。
本质上来说,JSON的解析就是文本文件的解析,其语法规则比较简单直接,自己手工实现一个满足自己使用场景的也并非难事。 只是考虑到性能、跨平台、编码、代码大小等各种各样的要素带来的复杂性,直接复用已有的库是更为明智的做法。
开源实现比较
JSON处理的开源库非常多,目前就职于腾讯的MiloYip做了一个很好的比较总结,开源在GitHub这里, 我们直接拿来参考就是了。
比较对象
作者从如下几个方面考察了常见的数十个JSON库
- 功能兼容性,包括校验,双精度数字的解析处理,字符串解析和压缩JSON文档的序列化反序列化;这在跨语言的微服务场景下尤为重要,因为客户端可能是用另外一种完全不同的编程语言开发的
- 各种操作下的性能表现,包含
- 运行时间(越短越好)
- 处理过程中的内存占用
- 最大内存占用情况(越少越好)
- 内存分配器调用次数多少,经验表明很多时候CPU的耗费时间有很大一部分花费在不必要的内存分配和释放操作上。更少的内存分配操作往往意味着更好的性能,即便这个结论并不是绝对准确,否则也不会有各种各样优化小对象分配的内存分配器出现了
- 可执行文件的大小 - 在微服务的容器化场景下,这也是一个很重要的要素。
比较结果
各项的比较结果如下 - 因为JSONCPP是一个很早就出现并且得到广泛使用的JSON解析库,这里将其单独放在一列做比较。
比较项目 | 最高得分项目 | 第二名得分项目 | 第三名得分项目 | jsoncpp | 说明 |
---|---|---|---|---|---|
功能兼容性 | taocpp/json(100%) | RapidJSON_pre(100%) | configuru(99%) | 85% | 前三基本达到100% |
解析时间 | RapidJSON(8ms) | gason(8ms) | json4c(9ms) | 166ms | 前三和后面的相差巨大 |
内存占用 | RapidJSON(4.8MB) | strdup(6.6MB) | Jvar(7.56MB) | 24.56MB | Qt的数据不准确 |
序列化时间 | RapidJSON(11ms) | ujson(25ms) | jzon(28ms) | 94ms | 部分纯C实现表现较好 |
美化输出 | RapidJSON(17ms) | ujson(29ms) | SchederdomJson(29ms) | NA | jsoncpp未提供 |
可执行文件大小 | pjson(15KB) | Qt(18KB) | RESTSDK(18.3KB) | 243KB | C实现毫无疑问靠前,RapidJSON31KB |
从结果来看,RapidJSON在多项比较重都名列前茅,称之为性能最好功能最全的JSON库也毫不为过。
需要额外指出的是
- RapidJSON提供了几个版本变种,默认情况下的版本提供了93%的标准兼容性,却能够在其它性能比拼中拔得头筹。 提供完全兼容的版本性能稍差一些或者毫无损失但在各项比较中依然排在前列,这里没有列出它的其它变种的排名情况而是将它们合并在一起展示。
- C版本的实现并没有特别明显的优势,除了最后一向需要考虑可执行文件大小的场合,而C++的实现并没有落后太多。 这一项里RapidJSON的得分在C++里面居于第六,绝对大小依然远好于jsoncpp
- 某些REST库自身也提供了自己的JSON实现,比如来自微软的REST SDK,只是本身并不特别出彩,在上述的REST比较重以及可以看到一些端倪,这里不赘述
- RapidJSON的最初实现也来自于这个benchmark的作者,只是两三年前以及被重命名成为腾讯开源库的一部分;目前在GitHub上有超过8000个👍
总结
基于微服务的架构设计正在席卷几乎所有的软件开发组织,形形色色的开源的REST和JSON框架使得能否用C++来开发微服务这一简单问题的答案不言自明。 在业务场景需要、团队技术能力匹配的情况下,使用上述的这些工具和框架库实现高性能的微服务不应该有任何障碍。 腾讯开源的FastJSON给了我们不少的惊喜,这也许是国内的大技术公司给开源社区的巨大贡献。
Leave a Comment
Your email address will not be published. Required fields are marked *