来源:腾讯技术工程微信号
作者:利开园
2020年8月14日-15日,GIAC 2020 全球互联网架构大会于上周五正式在深圳开幕。
GIAC(GLOBAL INTERNET ARCHITECTURE CONFERENCE)是长期关注互联网技术与架构的高可用架构技术社区和msup推出的,面向架构师、技术负责人及高端技术从业人员的年度技术架构大会,是中国地区规模最大的技术会议之一。
第六届GIAC,将从互联网架构最热门的前沿技术、技术管理、系统架构、大数据和人工智能、移动开发和语言、架构相关等领域,分享有典型代表的技术创新及研发实践的架构案例。
在Go语言专题,腾讯高级工程师利开园发表了题为《基于TarsGo的微服务技术架构实践》的主题演讲。
以下为演讲实录:
我今天分享的主题是基于TarsGo的微服务技术架构实践,先做一下自我介绍,我的工作经历很简单,毕业之后一直在腾讯,最近5年多时间里做了三个方向的工作。
第一部分是在2015年,当时docker比较火,了解K8S的人也不少,但是当时大家都觉得还像玩具,不适合生产环境使用。我所在团队就自己做了一个容器云, 主要是将Tars等服务跑在docker容器上,自己实现了比k8s多很多的自动调度能力,前几年我们想把这个方案开源,但是发现k8s已经成了业务标准,还好当时微服务还没有业务标准,所以我们开源了Tars。
第二部分是在2017年左右,那时候Go也开始火起来了我当时实现的Tars的Go语言版本的部分功能。
第三部分是2019年,很多人开始谈serverless和云原生,我也转到做腾讯云云开发这个产品,我们后台团队是用TarsGo来开发。
你们有没有发现,我做了几个方向,好像都是追赶业务潮流,但不变的是都跟Tars有关,从基础构架,到应用平台,再到平台的使用者。也因为这些经历,在讲Tars时可能会有不一样的角度。做平台或技术框架的时候可能会去追求酷炫的技术,但是变成使用者之后,才不会去关心技术有多牛逼,而是要看某个组件或平台好不好用,稳不稳定,能不能解决我们业务上的问题。
今天的分享会分三个部分:
1)Tars的核心功能,2)TarsGo的特性及性能优化实践,3)Tars与K8S的结合方案。
在介绍之前,我们先来分析一下基本每个软件开发团队都会遇到的业务规模增长带来的通用技术挑战。
如果你只是做一个个人博客网站,每天几百个uv,这个挑战不大,随便找台服务器托管就行,但是如果每天流量增长到几百万几个亿,也就是这里要说的第一个挑战,用户流量的增长时,原来单台服务的架构无法支撑,这个时候就需要考虑分布式的架构,需要读写分离,需要高性能的缓存。
第二个挑战是为了满足更多业务需求,会招聘更多的软件研发人员。人多也会带来新的问题,对老板来说,除了多发工资就是要想办法最大化的利用员工的价值和剩余价值,那对我们研发来说,当然也希望好的生产工具,更好地满足老板的需求。具体到项目开发中,就需有通用的接口协议来通信,有支持多种开发语言的框架,这样可以让不同开发语言的团队成员也能协作完成同一个项目,然后精通PHP的同学和精通Golang的同学也不花时间去争论哪个才是世界上最好的语言。
第三个挑战是服务器数量的增长,要管理好数量庞大的机器并不简单,假如你有1w台机器,每天可能都会有几台机器死机了,有几台磁盘坏了,还有可能整个机房都挂了,所以这就需要有机器故障或机房故障自动恢复的解决方案。
第四个是业务服务数的增长,对应的是业务逻辑复杂度的增长,要管理好大量的业务服务也是一大难点,开发的时候很爽,搞了很多的微服务,但是后面漫长的运维过程怎么办,要扩缩容、要监控,要排查问题,这就需要一个功能完善的微服务治理框架来提高运维效率。
上面列的这些挑战在腾讯有大量的业务产品都遇到过,经过多年的沉淀也积累了比较多经验,其中今天介绍Tars是最有代表性的对外开源项目。
那Tars有哪些能力呢?我认为有两部分,一部分是跟开发有关, Tars提供了IDL和代码自动生成的开发框架,开发只需要关心业务逻辑就行了,不用关心用是用tcp还是ucp协议,也不用知道依赖服务的IP或端口,Tars目前支持cpp/golang/php/nodejs/java 5种语言,更多语言也在支持当中。
另一部分跟运维相关,支持服务可视化管理,支持无损变更,配置管理中有版本管理和配置引用能力,日志、监控和调用链是Tars默认就支持的能力,Tars的Set模型为运维提供了方便的多集群管理能力,另外还有过载保护和容灾容错能力。之前TARS开源的版本没有自动扩缩容能力,最近我们开源了tars跟k8s整合的方案,补齐这个能力,这也是待会我会具体介绍的方案。
跟TARS类似的微服务框架也有不少,这里有列了几个影响力比较大的做下比较, GPRC是最热门的RPC框架, 但是本身它不具有服务治理能力, 另一方面但像Spring Cloud或Dobbo有比较完善的服务治理能力, 最近也开源也dubbo的golang语言版本,之前的话感觉还是以java语言为主。service mesh现在很火, 被称为下一微服务架构, 它将业务逻辑与底层通信分开, 跟语言无关, 也就是支持多语言, 但是在工业级应用上时间还不长, 不算特别成熟。
今天介绍的TARS跟service mash有很多类似的理念, 比如像负载均衡这样通用的功能, 不应该在业务层实现, 而是由公共的基础设施层来实现, 相比service mash是最近几年才提出的概念, TARS从2008年开始在腾讯多个核心业务上使用, 是一个支持多语言且具有完善的服务治理能力的微服务框架。
刚提到应用,TARS在开源之后,也跟很多公司做过交流。因为时间关系,我会先只选几个tars能力做更多的介绍。
我们先回到一个简单的场景,比如服务A要调用服务B,服务B有多个节点,A怎么选择哪个节点来调用呢?TARS有几个种负载均衡方式,默认是轮询,如果想用hash方式调用,只需要配置一下就行,不用额外的开发。
另外,当服务B的某个节点异常了,TARS默认有提供了一个熔断算法,会自动把异常的节点屏蔽掉,尽量减少节点异常导致的请求失败数量,然后再尝试请求,如果节点恢复了,再把之前异常的节点加回来。
还有一种异常场景是请求量过大导致服务过载,tars可以配置最大并发处理数来实现过载保护,避免服务雪崩。
另一个场景服务A要调用B,A要知道服务B的地址。最简单的方式是在A服务写死B的IP,但是B服务的IP可能会因为扩缩容而变化,这样B服务的变更要修改A的代码,显然是不可维护的,优化方案是把IP写到配置文件里,这个方案B服务变更时仍然需要运维来修改。所以基本上大家都会用第3种名字服务方案,服务地址自动注册,被调用服务变更时不需要客户端变更。但是名字服务在多集群场景下仍然有缺陷,多集群是很常见的场景,比如一个toc的产品有海量用户,可以按区域上海、北京来划分集群,分别服务不同区域的用户,这样可以比较好的隔离故障,比如上海有问题时不会影响到北京的用户。
传统的分集群有两种方式,一种是每个集群都用一套名字服务和公共服务,每新增一个集群都会增加一定运维成本,另一种是多集群共用一套名字服务,不同集群的同一个服务就要用不同的名字,就需要去管理每个集群服务的名字,也带来非常高的运维复杂度。
针对这个两难的问题,TARS提供了SET的能力。
我认为SET模型是TARS里面最精华的其中一部分能力, 它提供了逻辑上的分隔服务流量的功能, 如果某个服务开启了SET, 它只能调用到相同SET的服务,而如果服务没有开启set,而所有set的服务都可以调用到,另外还可以用通配符,让某个set给特写的几个set调用,不需要改代码或业务配置,运维就可以灵活地配置set来管理服务的调用关系。
机房容灾也是很通用的业务需求,从个人在腾讯经验上看,基本上每年都有那么一两次机房故障的场景,前几年有腾讯在汕尾的机房因为洪水可能要整个断电,当然最常见的原因有光纤被挖断。当整个机房不可用的事故出现时,如果一个产品的服务只部署在一个机房里, 会导致整个产品不可用,这个老板肯定接受不了。所以 考虑容灾同一个服务就要多机房部署, 一个机房不可用时还能正常对外提供服务。
这样做也带来了新的问题, 服务要跨机房调用, 会带来延时上的增加。
要做机房容灾,但是又不想增加延时怎么办?TARS提供了自动区域感知的能力, 客户端在多机房可用的情况下只调用本机房的服务端节点,如果没有本机房可用节点才会有跨机房的调用,这个功能只在控制面开启, 不用修改业务代码, 在具有容灾能力的同时, 没有增加延时, 运维也变得更简单。
对于微服务来说,监控、日志和调用链可以说是标配。因为微服务的特点,如果出问题之后都要登录到机器上排查,效率低有时候也不太现实。一般都是通过监控来观察服务的运行状态,状态异常时有告警。通过调用链来了解复杂系统的调用关系以及服务架构,在出现超时等问题时可以快速定位到出问题的模块,然后是可以利用日志来分析异常的原因。然后监控、日志和调用链在使用时有相互重合的地方,比如也可以通过聚合日志来实现监控metrics的功能,但是在数据量大的时候效率会低一点,所以一个成熟的微服务系统还是要这三个完整的能力。
TARS默认会提供tarslog/tarsstat/tarsproperty这样的基础服务,也可以整合比较成熟的开源方案,比如监控有Prometheus,日志有ELK,调用链有zipkin与jaeger。
接下来是我要介绍的第二部分,TARS的GO语言版本的RPC框架,主要会介绍性能优化相关的经验。
TarsGo有三个优点,分别是tars的服务治理能力、go的优势以及TARSGo的性能优化。TARSGo是基于tars协议实现的rpc框架,会默认继承tars的服务治理能力,比如负载均衡,容灾容错能力,以及tars的名字服务。
第二个是go语言的优点,golang有丰富的标准库,很多时候不用重复造轮子,对于新入门的同学上手也比较快。相比于python/C或其他语言,在开发效率和程序的运行效率上有比较好的平衡。同时支持静态编译和跨平台编译给研发提供了不少的便利,比如可以在mac上开发和编译,然后上传到linux的服务器运行。我认为go语言最大的优势是并发和异步编程,go协程以及channel的数据结构大大简化了并发编程的复杂度。
TarsGo的第三个优点是性能,接入来会详细介绍。
性能对微服务来说是很重要的,因为一个用户请求过来可能会经过十几个甚至更多服务,高性能的RPC框架可以减少微服务带来的耗时增长问题,这一点TARS是做得不错的。
之前我们针对常用的PRC框架及不同开发语言做了性能压测比较,从测试结果上看,TARS C++和TARS GO的整体性能是最好的,小包场景是grpc 的5倍以上。
为了达到这个目标,tarsgo有做了比较多的优化,这些优化经验对使用go开发的同学应该也有一定参考价值。
第一个是timer的优化,我们知道每次rpc调用都有一个超时时间,需要用计时器来实现限制超时的目标,如果并发量很大,那就需要大量的计时器。利用golang的pporf工具生成火焰图可以发现这个创建计时器的性能问题。所以我们实现了一个时间轮算法,可以将创建计时器的时间复杂度从log(n)下降到O(1),缺点是
此外,还有的优化点有net包的daedline的设置性能优化,针对bytes.Buffer频繁申请优胜sync.Pool优化,针对goroutine大量创建问题使用协程池优化。经过以上优化后达到了前面提到的效果。
接下来介绍TARS与K8S整合的方案。
当前TARS提供了名字服务、负载均衡、过载保护、区域感知等较完善的服务治理能力,但是开源的TARS版本并没有自动调度功能,服务上线、扩容等操作都需要填写IP和端口。
这个会带来2个问题:
一是服务的容量管理,对于无法自动扩缩容的服务,如果预留的资源少,在流量突发可能会导致服务过载,而预留更多的资源则会导致浪费。
二是服务混合部署,如果多个服务混合部署,可能会相互影响,不混合部署,微服务太细了,机器资源也会大量浪费。
针对上述问题最自然的想法是希望能利用K8S的调度能力来实现TARS服务的自动扩缩容。但是问题又来了,TARS的不少能力(如名字服务等)与K8S重合,要整合在一起并不容易。
在K8S生态下,Istio是最热门的微服务解决方案,是否应该要放弃TARS,改成Istio会更好呢?这里分析了会遇到的问题。
第一个就要要服务代码改造,这个是很现实的问题但也不一定充分的理由不用iostio。
第二个用istio的问题是,对tcp的长连接只能是连接级别负载均衡,而tars是请求级别,有更好能力。
第三是tars客户端有熔断能力,让服务更健壮。
第四是tars有set/idc分组的名字服务,在多集群多机房部署时运维更简单。
第五是支持端口自动管理,业务代码里不用关心端口,避免在服务多时管理端口的麻烦。
第六是更好的支持日志、监控和调用链,比如istio虽然也支持调用链,但是还要在业务代码里配合透传调用链的上下文,一个公共逻辑需要在两个地方来实现,直觉告诉我这里容易有问题。
第七是相比istio还在快速迭代,在落地上也有较高成本,TARS经过大量业务服务的验证,是比较成熟稳定的。
所以综合考虑有必要继续使用TARS,将TARS改造成支持在K8S中自动调度。
而实现的主要难点是整合重合的功能,Tars没有的能力是资源自动调度、docker镜像管理和网络虚拟化,TARS有但是K8S没有的能力是高性能RPC框架、日志、监控、调用链等服务治理能力,服务部署、版本发布与管理、名字服务和配置管理是tars和k8s都有的功能。
所以需要对重合的功能进行取舍,因为k8s支持服务自动调度,是业务的主流标准,所以服务部署只能选k8s,而k8s使用docker镜像包含基础环境依赖,是更好的版本管理方案。名字服务部分,TARS支持关流量,支持SET/IDC分组,客户端容错等k8s没有的功能,所以有必要保留tars的名字服务,而tars的配置管理支持版本管理,应用/set/服务级配置引用与合并,会比k8s的configmap方案更适合在生产环境使用。
在分析了K8S与TARS的重合功能之后,我们最近实现并开源了这样的解决方案。没有对k8s进行改造,会使用原生的k8s能力,在TarsRegistry增加几个接口,用于服务的自动注册、心跳上报和节点下线,对于死机场景,会自动删除长期未上报心跳的节点。另外新实现了一个命令行工具,用于在容器中管理服务,实现生成启动配置、服务注册和心跳上报等功能。该方案的优点有是保留TARS原生的开发框架和服务治理能力,而服务部署与调度则使用K8S。
k8s的pod与tars服务的生命周期是强关联在一起的,在pod启动后,默认先启动tarscli supervisor进程来生成服务启动配置,拉起并监控服务状态,将服务同步到k8s的名字服务中。通过hzcheck命令来保证pod与服务状态。在pod退出时,通过prestop子命令来自动下线节点。
那如何把一个TARS服务部署到k8s上呢?首先要部署tars的基础服务,包含数据库,名字服务,web管理页面和服务治理相关的基础服务,在我们的开源项目里,有提供了yaml可以直接在k8s上快速把这些服务部署好。然后业务服务的部署也可以通过yaml文件来实现,这里有列举了两个示例,具体使用可以参考开源主页上的说明文档。在部署完成之后,可以通过tarsweb的页面看到服务的节点列表。
在管理页面上,默认会有服务监控曲线,可以直观看到服务的调用量,耗时,异常率和超时率。另外tars是支持无损变更的,可以从监控上看在镜像变更期间,业务的监控曲线是没有波动的。
利开园右
最后谈一下TARS与云原生:
我认为老板会喜欢云原生,因为不用招很多人开发和维护基础架构,可以直接使用开源方案,然后在云厂商托管服务。
而TARS本身具备丰富的服务治理能力,可以提高开发效率、减少运维成本,TARS与K8S的结合方案,可以在不改动业务代码或以很小的成本就可以将服务部署到适合的云厂商,在减低基础设施运维成本的同时,也能避免与云厂商绑定。所以推荐大家使用TARS+K8S的云原生解决方案,这个方案目前已经在github上开源,观察大家去关注。在github上搜索k8stars就可以找到。
这就是我的分享,谢谢!
推荐阅读:
更多腾讯AI相关技术干货,请关注专栏腾讯技术工程