first commit

This commit is contained in:
张乾
2024-10-16 06:37:41 +08:00
parent 633f45ea20
commit 206fad82a2
3590 changed files with 680090 additions and 0 deletions

View File

@ -0,0 +1,61 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
00 开篇词 微服务,从放弃到入门
你好我是胡忠想微博技术专家。从2012年加入微博到现在我一直在做微博首页信息流相关的业务研发几乎亲历了微博后端架构的每一次重大升级。不仅参与了微博后端架构从大的单体应用迁移到微服务架构的改造还作为主要负责人之一主导了微服务架构在公司多个业务线的推广和落地。所以谈到将微服务落地我有很多实战干货想和你分享。
不得不说,微服务是当下非常热门的话题。我平时工作之外和圈子里的朋友们交流,提到微服务等新技术,他们先是兴奋,后又无奈。兴奋的是他们看到了新技术带来的便利,无奈的是团队规模和能力又反过来制约了他们采用新技术的步伐。而他们也对微服务有着这样或那样的理解,但更多的是疑惑,比如说他们会问:
微服务这技术虽然面试的时候总有人提,但作为一个开发,是不是和我关系不大?那不都是架构师的事吗?
微服务不都是大厂在玩吗?我们这个业务体量用得着吗?
微服务特别复杂没个100人的研发团队是不是就无法落地
我特别理解这样的困惑,因为我也是这么一步步走过来的。的确,大公司动辄就是几百上千的研发人员,并且其中不乏顶尖选手。他们有经验、有能力,也有业务场景,所以在技术的选择上也会更为“冒进”。而对于大部分的中小团队来说,当微服务架构成为刚需的时候,他们更多的是彷徨和犹豫。
先给你讲讲我的经历吧。最开始,微博首页信息流的后端团队规模也不大,只有七八个人。当时我们就想着快速迭代,业务也就采用了单体应用的架构。因为求快,不同功能模块的代码耦合在一起,编译打包部署也都在一起。
后来业务规模不断扩大团队人员也增长到二十多人这时候单体应用架构的开发模式就开始暴露出问题了。那时候每一次功能发布和上线都需要一个上线负责人来收集上线列表并协调所有相关的开发人员合并代码到主干然后编译打包修改工程依赖的JAR包版本。
你应该可以想象我们那时的状况。如果一次上线超过五个人参与的话,就会经常出现各种问题:有的人忘记提交代码、有的人忘记打包、有的人忘记修改工程依赖到最新版本。一次上线过程需要反复确认,耗费了大量精力,严重影响了整体的开发和部署效率。
看到这,不知你是否大腿一拍,大声叫到:这不就是我们团队每天都在面对的问题嘛!
当时我们为了解决这些问题做了很细致的技术调研最后选定了服务化的解决方案对原有的单体应用架构进行改造把功能相对独立的模块拆分出去部署为微服务分别交给专门的更小的团队来维护。后来我们又引入了Docker容器化以及Service Mesh等技术为了更好地适应微博业务的高速发展。
可以说,微博的信息流后端架构经历了单体应用 - 微服务架构 - 容器化应用 - DevOps的发展历程。而我也正是因为亲历了微博的架构演进过程对于中小团队如何落地微服务体系有了更为深刻的理解。
所以,在这个专栏里,我会秉承着这个思路,不断提醒自己,这个方案中小团队是否可用,他们能否驾驭这些技术。我想,这是大部分中小团队的刚需,也是这个专栏的主要出发点。他们需要的不是一个大而全的东西,而是一套可以快速落地的方法论。
我希望在专栏里不仅跟你分享微服务架构的基础知识,更是从微服务体系的角度,和你深入讨论如何将微服务落地,帮你扫清最开始提到的那些疑惑。
那什么是微服务体系呢在我看来微服务发展到现在已经不再单单局限于微服务架构本身还与容器化、DevOps等新的理念相结合成为当前移动互联网时代最先进的业务架构解决方案能更好地迎合移动互联网业务快速迭代的要求。
在接下来的三个月里我将由浅入深、由表及里逐步带你探索微服务的世界帮你从0开始构建微服务体系。具体来说专栏分为四个部分
第一部分,我会尽量用最通俗的语言去讲解微服务架构的基本原理,帮你解答三个问题:什么是微服务?什么时候适合微服务改造?微服务架构到底是什么样的?
第二部分,我会结合在实际业务中的经验,给你讲述微服务架构改造过程中可能会遇到的问题和对应的解决方案,以及搭建微服务架构时,如何做技术选型。
第三部分我会给你讲述微服务、容器化、DevOps这三者之间的关系以及在具体实践中如何运用这三种技术给业务的架构带来质的飞跃。
第四部分,我会给你介绍下一代微服务体系可能的发展方向,并分享我对此的看法。
如果你刚刚接触微服务体系,希望我的专栏能带你快速入门微服务,具备搭建一套微服务基本架构的能力;如果你有过微服务架构的开发经历,希望可以帮你解决在实际开发过程中遇到的一些问题;如果你已经玩转了微服务的各个方面,希望你可以和我切磋,交流开发心得,畅谈下一代微服务的技术发展;即使你现在还没有用到微服务,但通过专栏的学习,希望你一样能够掌握微服务架构的思维的精髓,提升解决复杂问题的能力。
微服务是当下最火热的后端架构之一。不管你是一个什么级别的程序员,也不论你在一个什么体量的公司,服务化都是你迟早会遇到的难题。从我的经验来看,实践微服务的过程本身也是一个升级打怪的过程,这中间你会遇到基本上所有后端架构的问题。解决了这些问题,你自然也就理解了那些高深的概念,也就成为了一名架构师,成长和能力提升都是这个过程的附属品。
不说虚的,我希望这个专栏能给你在“微服务道路”上增加一块敲门砖,希望我讲的东西对你有所帮助、有所启发。用极客时间团队的话来说,我要为你交付结果,学完这个专栏,希望你可以厘清微服务的脉络,并在恰当的时候,也可以主导自己公司的服务化进程。
我邀请你在接下来的三个月时间里,跟我一起走进微服务的世界,感受学习和进步所带来的乐趣与成就!

View File

@ -0,0 +1,87 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
01 到底什么是微服务?
从谷歌的搜索指数来看微服务的热度在进入2017年后突然爆发国内各大会议和论坛的相关讨论也如雨后春笋般层出不穷各大一线互联网公司也纷纷将这一技术引入并在实际业务中落地。
然而据我所知,国内不少中小规模的技术团队对微服务的概念都不甚了解,对该不该引入微服务也不置可否。还有一些技术团队,没有考虑实际业务场景,只是为了追求技术热点,盲目引入微服务,但又缺乏相应的技术掌控能力,最后影响了业务的稳定性。
对于该不该引入微服务,以及微服务体系需要哪些技术,目前并没有适合中小团队的架构实践落地的指引。因此我结合自己在微博多年的业务实践,总结出了一套微服务落地经验,从基础理论到架构实践,再结合业界最新趋势分析,希望能帮助中小规模团队了解微服务的本质以及对业务的价值,从而做出正确的判断。
我们先来看看维基百科是如何定义微服务的。微服务的概念最早是在2014年由Martin Fowler和James Lewis共同提出他们定义了微服务是由单一应用程序构成的小服务拥有自己的进程与轻量化处理服务依业务功能设计以全自动的方式部署与其他服务使用HTTP API通讯。同时服务会使用最小规模的集中管理 例如Docker技术服务可以用不同的编程语言与数据库等。
这个理论的定义看着有点晕?没关系,接下来我来帮你理解到底什么是微服务?
单体应用
在开聊微服务之前,我先要你和介绍下单体应用。如果你不知道单体应用的痛,那也不会深刻理解微服务的价值。
早些年各大互联网公司的应用技术栈大致可分为LAMPLinux + Apache + MySQL + PHP和MVCSpring + iBatis/Hibernate + Tomcat两大流派。无论是LAMP还是MVC都是为单体应用架构设计的其优点是学习成本低开发上手快测试、部署、运维也比较方便甚至一个人就可以完成一个网站的开发与部署。
以MVC架构为例业务通常是通过部署一个WAR包到Tomcat中然后启动Tomcat监听某个端口即可对外提供服务。早期在业务规模不大、开发团队人员规模较小的时候采用单体应用架构团队的开发和运维成本都可控。
然而随着业务规模的不断扩大,团队开发人员的不断扩张,单体应用架构就会开始出现问题。我估计经历过业务和团队快速增长的同学都会对此深有感触。从我的角度来看,大概会有以下几个方面的问题。
部署效率低下。以我实际参与的项目为例当单体应用的代码越来越多依赖的资源越来越多时应用编译打包、部署测试一次甚至需要10分钟以上。这也经常被新加入的同学吐槽说部署测试一次的时间都可以去楼下喝杯咖啡了。
团队协作开发成本高。以我的经验早期在团队开发人员只有两三个人的时候协作修改代码最后合并到同一个master分支然后打包部署尚且可控。但是一旦团队人员扩张超过5人修改代码然后一起打包部署测试阶段只要有一块功能有问题就得重新编译打包部署然后重新预览测试所有相关的开发人员又都得参与其中效率低下开发成本极高。
系统高可用性差。因为所有的功能开发最后都部署到同一个WAR包里运行在同一个Tomcat进程之中一旦某一功能涉及的代码或者资源有问题那就会影响整个WAR包中部署的功能。比如我经常遇到的一个问题某段代码不断在内存中创建大对象并且没有回收部署到线上运行一段时间后就会造成JVM内存泄露异常退出那么部署在同一个JVM进程中的所有服务都不可用后果十分严重。
线上发布变慢。特别是对于Java应用来说一旦代码膨胀服务启动的时间就会变长有些甚至超过10分钟以上如果机器规模超过100台以上假设每次发布的步长为10%单次发布需要就需要100分钟之久。因此急需一种方法能够将应用的不同模块的解耦降低开发和部署成本。
想要解决上面这些问题,服务化的思想也就应运而生。
什么是服务化?
这里我就不谈一些官方的、教条主义的概念了。在我看来用通俗的话来讲服务化就是把传统的单机应用中通过JAR包依赖产生的本地方法调用改造成通过RPC接口产生的远程方法调用。一般在编写业务代码时对于一些通用的业务逻辑我会尽力把它抽象并独立成为专门的模块因为这对于代码复用和业务理解都大有裨益。
在过去的项目经历里我对此深有体会。以微博系统为例微博既包含了内容模块也包含了消息模块和用户模块等。其中消息模块依赖内容模块消息模块和内容模块又都依赖用户模块。当这三个模块的代码耦合在一起应用启动时需要同时去加载每个模块的代码并连接对应的资源。一旦任何模块的代码出现bug或者依赖的资源出现问题整个单体应用都会受到影响。
为此首先可以把用户模块从单体应用中拆分出来独立成一个服务部署以RPC接口的形式对外提供服务。微博和消息模块调用用户接口就从进程内的调用变成远程RPC调用。这样用户模块就可以独立开发、测试、上线和运维可以交由专门的团队来做与主模块不耦合。进一步的可以再把消息模块也拆分出来作为独立的模块交由专门的团队来开发和维护。
可见通过服务化,可以解决单体应用膨胀、团队开发耦合度高、协作效率低下的问题。
什么是微服务?
从2014年开始得益于以Docker为代表的容器化技术的成熟以及DevOps文化的兴起服务化的思想进一步演化演变为今天我们所熟知的微服务。
那么微服务相比于服务化又有什么不同呢?
在我看来,可以总结为以下四点:
服务拆分粒度更细。微服务可以说是更细维度的服务化,小到一个子模块,只要该模块依赖的资源与其他模块都没有关系,那么就可以拆分为一个微服务。
服务独立部署。每个微服务都严格遵循独立打包部署的准则互不影响。比如一台物理机上可以部署多个Docker实例每个Docker实例可以部署一个微服务的代码。
服务独立维护。每个微服务都可以交由一个小团队甚至个人来开发、测试、发布和运维,并对整个生命周期负责。
服务治理能力要求高。因为拆分为微服务之后,服务的数量变多,因此需要有统一的服务治理平台,来对各个服务进行管理。
继续以前面举的微博系统为例可以进一步对内容模块的功能进行拆分比如内容模块又包含了feed模块、评论模块和个人页模块。通过微服务化将这三个模块变成三个独立的服务每个服务依赖各自的资源并独立部署在不同的服务池中可以由不同的开发人员进行维护。当评论服务需求变更时只需要修改评论业务相关的代码并独立上线发布而feed服务和个人页服务不需要变更也不会受到发布可能带来的变更影响。
由此可见,微服务化给服务的发布和部署,以及服务的保障带来了诸多好处。
总结
今天我介绍了微服务的发展由来它是由单体应用进化到服务化拆分部署后期随着移动互联网规模的不断扩大敏捷开发、持续交付、DevOps理论的发展和实践以及基于Docker容器化技术的成熟微服务架构开始流行逐渐成为应用架构的未来演进方向。
总结来说,微服务架构是将复杂臃肿的单体应用进行细粒度的服务化拆分,每个拆分出来的服务各自独立打包部署,并交由小团队进行开发和运维,从而极大地提高了应用交付的效率,并被各大互联网公司所普遍采用。
思考题
你在业务开发中是否也遇到过因单体应用过度膨胀所带来的问题呢?你觉得针对这些问题微服务能解决吗?
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,69 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
02 从单体应用走向服务化
专栏上一期,我给你讲述了什么是微服务,以及微服务架构的由来。简单回顾一下,微服务就是将庞杂臃肿的单体应用拆分成细粒度的服务,独立部署,并交给各个中小团队来负责开发、测试、上线和运维整个生命周期。
那么到底什么时候应该拆分单体应用?拆分单体应用有哪些标准可依呢?
为了解答这两个问题,今天我将通过具体案例来阐述,希望你能够学会单体应用拆分成微服务的正确姿势。
什么时候进行服务化拆分?
从我所经历过的多个项目来看,项目第一阶段的主要目标是快速开发和验证想法,证明产品思路是否可行。这个阶段功能设计一般不会太复杂,开发采取快速迭代的方式,架构也不适合过度设计。所以将所有功能打包部署在一起,集中地进行开发、测试和运维,对于项目起步阶段,是最高效也是最节省成本的方式。当可行性验证通过,功能进一步迭代,就可以加入越来越多的新特性。
比如做一个社交App初期为了快速上线验证可行性可以只开发首页信息流、评论等基本功能。产品上线后经过一段时间的运营用户开始逐步增多可行性验证通过下一阶段就需要进一步增加更多的新特性来吸引更多的目标用户比如再给这个社交App添加个人主页显示、消息通知等功能。
一般情况下这个时候就需要大规模地扩张开发人员以支撑多个功能的开发。如果这个时候继续采用单体应用架构多个功能模块混杂在一起开发、测试和部署的话就会导致不同功能之间相互影响一次打包部署需要所有的功能都测试OK才能上线。
不仅如此多个功能模块混部在一起对线上服务的稳定性也是个巨大的挑战。比如A开发的一个功能由于代码编写考虑不够全面上线后产生了内存泄漏运行一段时间后进程异常退出那么部署在这个服务池中的所有功能都不可访问。一个经典的案例就是曾经有一个视频App因为短时间内某个付费视频访问量巨大超过了服务器的承载能力造成了这个视频无法访问。不幸的是这个网站付费视频和免费视频的服务部署在一起也波及了免费视频几乎全站崩溃。
根据我的实际项目经验一旦单体应用同时进行开发的人员超过10人就会遇到上面的问题这个时候就该考虑进行服务化拆分了。
服务化拆分的两种姿势
那么服务化拆分具体该如何实施呢一个最有效的手段就是将不同的功能模块服务化独立部署和运维。以前面提到的社交App为例你可以认为首页信息流是一个服务评论是一个服务消息通知是一个服务个人主页也是一个服务。
这种服务化拆分方式是纵向拆分,是从业务维度进行拆分。标准是按照业务的关联程度来决定,关联比较密切的业务适合拆分为一个微服务,而功能相对比较独立的业务适合单独拆分为一个微服务。
还有一种服务化拆分方式是横向拆分,是从公共且独立功能维度拆分。标准是按照是否有公共的被多个其他服务调用,且依赖的资源独立不与其他业务耦合。
继续以前面提到的社交App举例无论是首页信息流、评论、消息箱还是个人主页都需要显示用户的昵称。假如用户的昵称功能有产品需求的变更你需要上线几乎所有的服务这个成本就有点高了。显而易见如果我把用户的昵称功能单独部署成一个独立的服务那么有什么变更我只需要上线这个服务即可其他服务不受影响开发和上线成本就大大降低了。
服务化拆分的前置条件
一般情况下,业务系统引入新技术就必然会带来架构的复杂度提升,在具体决策前,你先要认识到新架构会带来哪些新的问题,这些问题你和你的团队是否能够解决?如何解决?是自己投入人力建设,还是采用业界开源方案?
下面几个问题,是从单体应用迁移到微服务架构时必将面临也必须解决的。
服务如何定义。对于单体应用来说不同功能模块之前相互交互时通常是以类库的方式来提供各个模块的功能。对于微服务来说每个服务都运行在各自的进程之中应该以何种形式向外界传达自己的信息呢答案就是接口无论采用哪种通讯协议是HTTP还是RPC服务之间的调用都通过接口描述来约定约定内容包括接口名、接口参数以及接口返回值。
服务如何发布和订阅。单体应用由于部署在同一个WAR包里接口之间的调用属于进程内的调用。而拆分为微服务独立部署后服务提供者该如何对外暴露自己的地址服务调用者该如何查询所需要调用的服务的地址呢这个时候你就需要一个类似登记处的地方能够记录每个服务提供者的地址以供服务调用者查询在微服务架构里这个地方就是注册中心。
服务如何监控。通常对于一个服务我们最关心的是QPS调用量、AvgTime平均耗时以及P99999.9%的请求性能在多少毫秒以内)这些指标。这时候你就需要一种通用的监控方案,能够覆盖业务埋点、数据收集、数据处理,最后到数据展示的全链路功能。
服务如何治理。可以想象,拆分为微服务架构后,服务的数量变多了,依赖关系也变复杂了。比如一个服务的性能有问题时,依赖的服务都势必会受到影响。可以设定一个调用性能阈值,如果一段时间内一直超过这个值,那么依赖服务的调用可以直接返回,这就是熔断,也是服务治理最常用的手段之一。
故障如何定位。在单体应用拆分为微服务之后,一次用户调用可能依赖多个服务,每个服务又部署在不同的节点上,如果用户调用出现问题,你需要有一种解决方案能够将一次用户请求进行标记,并在多个依赖的服务系统中继续传递,以便串联所有路径,从而进行故障定位。
针对上述问题,你必须有可行的解决方案之后,才能进一步进行服务化拆分。专栏后面的文章,我会给你逐一讲解相应的解决方案。
总结
无论是纵向拆分还是横向拆分,都是将单体应用庞杂的功能进行拆分,抽离成单独的服务部署。
但并不是说功能拆分的越细越好过度的拆分反而会让服务数量膨胀变得难以管理因此找到符合自己业务现状和团队人员技术水平的拆分粒度才是可取的。我建议的标准是按照每个开发人员负责不超过3个大的服务为标准毕竟每个人的精力是有限的所以在拆分微服务时可以按照开发人员的总人数来决定。
思考题
想想你现在的业务场景,如果是单体应用的话,是否需要进行服务化拆分?如果需要的话,你觉得纵向拆分还是横向拆分合适?具体可以拆分到什么粒度?
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,145 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
03 初探微服务架构
上一期我给你讲了什么时候应该进行服务化,以及服务化拆分的两种方式即横向拆分和纵向拆分,最后还提到了引入微服务架构需要解决的问题。
我想你一定很好奇微服务架构到底是什么样子的,接下来我们一起走进微服务架构,来看看它的各个组成部分。
下面这张图是我根据自己的经验,绘制的微服务架构的模块图,在具体介绍之前先来看下一次正常的服务调用的流程。
首先服务提供者(就是提供服务的一方)按照一定格式的服务描述,向注册中心注册服务,声明自己能够提供哪些服务以及服务的地址是什么,完成服务发布。
接下来服务消费者(就是调用服务的一方)请求注册中心,查询所需要调用服务的地址,然后以约定的通信协议向服务提供者发起请求,得到请求结果后再按照约定的协议解析结果。
而且在服务的调用过程中,服务的请求耗时、调用量以及成功率等指标都会被记录下来用作监控,调用经过的链路信息会被记录下来,用于故障定位和问题追踪。在这期间,如果调用失败,可以通过重试等服务治理手段来保证成功率。
总结一下,微服务架构下,服务调用主要依赖下面几个基本组件:
服务描述
注册中心
服务框架
服务监控
服务追踪
服务治理
接下来,我来为你一一介绍这些组件。
服务描述
服务调用首先要解决的问题就是服务如何对外描述。比如,你对外提供了一个服务,那么这个服务的服务名叫什么?调用这个服务需要提供哪些信息?调用这个服务返回的结果是什么格式的?该如何解析?这些就是服务描述要解决的问题。
常用的服务描述方式包括RESTful API、XML配置以及IDL文件三种。
其中RESTful API方式通常用于HTTP协议的服务描述并且常用Wiki或者Swagger来进行管理。下面是一个RESTful API方式的服务描述的例子。
XML配置方式多用作RPC协议的服务描述通过*.xml配置文件来定义接口名、参数以及返回值类型等。下面是一个XML配置方式的服务描述的例子。
IDL文件方式通常用作Thrift和gRPC这类跨语言服务调用框架中比如gRPC就是通过Protobuf文件来定义服务的接口名、参数以及返回值的数据结构示例如下
注册中心
有了服务的接口描述,下一步要解决的问题就是服务的发布和订阅,就是说你提供了一个服务,如何让外部想调用你的服务的人知道。这个时候就需要一个类似注册中心的角色,服务提供者将自己提供的服务以及地址登记到注册中心,服务消费者则从注册中心查询所需要调用的服务的地址,然后发起请求。
一般来讲,注册中心的工作流程是:
服务提供者在启动时,根据服务发布文件中配置的发布信息向注册中心注册自己的服务。
服务消费者在启动时,根据消费者配置文件中配置的服务信息向注册中心订阅自己所需要的服务。
注册中心返回服务提供者地址列表给服务消费者。
当服务提供者发生变化,比如有节点新增或者销毁,注册中心将变更通知给服务消费者。
服务框架
通过注册中心,服务消费者就可以获取到服务提供者的地址,有了地址后就可以发起调用。但在发起调用之前你还需要解决以下几个问题。
服务通信采用什么协议就是说服务提供者和服务消费者之间以什么样的协议进行网络通信是采用四层TCP、UDP协议还是采用七层HTTP协议还是采用其他协议
数据传输采用什么方式?就是说服务提供者和服务消费者之间的数据传输采用哪种方式,是同步还是异步,是在单连接上传输,还是多路复用。
数据压缩采用什么格式通常数据传输都会对数据进行压缩来减少网络传输的数据量从而减少带宽消耗和网络传输时间比如常见的JSON序列化、Java对象序列化以及Protobuf序列化等。
服务监控
一旦服务消费者与服务提供者之间能够正常发起服务调用,你就需要对调用情况进行监控,以了解服务是否正常。通常来讲,服务监控主要包括三个流程。
指标收集。就是要把每一次服务调用的请求耗时以及成功与否收集起来,并上传到集中的数据处理中心。
数据处理。有了每次调用的请求耗时以及成功与否等信息,就可以计算每秒服务请求量、平均耗时以及成功率等指标。
数据展示。数据收集起来经过处理之后还需要以友好的方式对外展示才能发挥价值。通常都是将数据展示在Dashboard面板上并且每隔10s等间隔自动刷新用作业务监控和报警等。
服务追踪
除了需要对服务调用情况进行监控之外,你还需要记录服务调用经过的每一层链路,以便进行问题追踪和故障定位。
服务追踪的工作原理大致如下:
服务消费者发起调用前会在本地按照一定的规则生成一个requestid发起调用时将requestid当作请求参数的一部分传递给服务提供者。
服务提供者接收到请求后记录下这次请求的requestid然后处理请求。如果服务提供者继续请求其他服务会在本地再生成一个自己的requestid然后把这两个requestid都当作请求参数继续往下传递。
以此类推通过这种层层往下传递的方式一次请求无论最后依赖多少次服务调用、经过多少服务节点都可以通过最开始生成的requestid串联所有节点从而达到服务追踪的目的。
服务治理
服务监控能够发现问题,服务追踪能够定位问题所在,而解决问题就得靠服务治理了。服务治理就是通过一系列的手段来保证在各种意外情况下,服务调用仍然能够正常进行。
在生产环境中,你应该经常会遇到下面几种状况。
单机故障。通常遇到单机故障,都是靠运维发现并重启服务或者从线上摘除故障节点。然而集群的规模越大,越是容易遇到单机故障,在机器规模超过一百台以上时,靠传统的人肉运维显然难以应对。而服务治理可以通过一定的策略,自动摘除故障节点,不需要人为干预,就能保证单机故障不会影响业务。
单IDC故障。你应该经常听说某某App因为施工挖断光缆导致大批量用户无法使用的严重故障。而服务治理可以通过自动切换故障IDC的流量到其他正常IDC可以避免因为单IDC故障引起的大批量业务受影响。
依赖服务不可用。比如你的服务依赖依赖了另一个服务,当另一个服务出现问题时,会拖慢甚至拖垮你的服务。而服务治理可以通过熔断,在依赖服务异常的情况下,一段时期内停止发起调用而直接返回。这样一方面保证了服务消费者能够不被拖垮,另一方面也给服务提供者减少压力,使其能够尽快恢复。
上面是三种最常见的需要引入服务治理的场景,当然还有一些其他服务治理的手段比如自动扩缩容,可以用来解决服务的容量问题。
总结
通过前面的讲解,相信你已经对微服务架构有了基本的认识,对微服务架构的基本组件也有了初步了解。
这几个基本组件共同组成了微服务架构,在生产环境下缺一不可,所以在引入微服务架构之前,你的团队必须掌握这些基本组件的原理并具备相应的开发能力。实现方式上,可以引入开源方案;如果有充足的资深技术人员,也可以选择自行研发微服务架构的每个组件。但对于大部分中小团队来说,我认为采用开源实现方案是一个更明智的选择,一方面你可以节省相关技术人员的投入从而更专注于业务,另一方面也可以少走弯路少踩坑。不管你是采用开源方案还是自行研发,都必须吃透每个组件的工作原理并能在此基础上进行二次开发。
专栏后面的内容,我会带你对这几个微服务架构的基本组件进行详细剖析,让你能知其然也知其所以然。
思考题
最后你可以思考一下,微服务架构下的基本组件所解决的问题,对应到单体应用时是否存在?如果存在,解决方案是否相同?
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,296 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
04 如何发布和引用服务?
从这期开始,我将陆续给你讲解微服务各个基本组件的原理和实现方式。
今天我要与你分享的第一个组件是服务发布和引用。我在前面说过,想要构建微服务,首先要解决的问题是,服务提供者如何发布一个服务,服务消费者如何引用这个服务。具体来说,就是这个服务的接口名是什么?调用这个服务需要传递哪些参数?接口的返回值是什么类型?以及一些其他接口描述信息。
我前面说过,最常见的服务发布和引用的方式有三种:
RESTful API
XML配置
IDL文件
下面我就结合具体的实例,逐个讲解每一种方式的具体使用方法以及各自的应用场景,以便你在选型时作参考。
RESTful API
首先来说说RESTful API的方式主要被用作HTTP或者HTTPS协议的接口定义即使在非微服务架构体系下也被广泛采用。
下面是开源服务化框架Motan发布RESTful API的例子它发布了三个RESTful格式的API接口声明如下
@Path("/rest")
public interface RestfulService {
@GET
@Produces(MediaType.APPLICATION_JSON)
List<User> getUsers(@QueryParam("uid") int uid);
@GET
@Path("/primitive")
@Produces(MediaType.TEXT_PLAIN)
String testPrimitiveType();
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
Response add(@FormParam("id") int id, @FormParam("name") String name);
具体的服务实现如下:
public class RestfulServerDemo implements RestfulService {
@Override
public List<User> getUsers(@CookieParam("uid") int uid) {
return Arrays.asList(new User(uid, "name" + uid));
}
@Override
public String testPrimitiveType() {
return "helloworld!";
}
@Override
public Response add(@FormParam("id") int id, @FormParam("name") String name) {
return Response.ok().cookie(new NewCookie("ck", String.valueOf(id))).entity(new User(id, name)).build();
}
服务提供者这一端通过部署代码到Tomcat中并配置Tomcat中如下的web.xml就可以通过servlet的方式对外提供RESTful API。
<listener>
<listener-class>com.weibo.api.motan.protocol.restful.support.servlet.RestfulServletContainerListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
<load-on-startup>1</load-on-startup>
<init-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/servlet</param-value> <!-- 此处实际为servlet-mapping的url-pattern具体配置见resteasy文档-->
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/servlet/*</url-pattern>
</servlet-mapping>
这样服务消费者就可以通过HTTP协议调用服务了因为HTTP协议本身是一个公开的协议对于服务消费者来说几乎没有学习成本所以比较适合用作跨业务平台之间的服务协议。比如你有一个服务不仅需要在业务部门内部提供服务还需要向其他业务部门提供服务甚至开放给外网提供服务这时候采用HTTP协议就比较合适也省去了沟通服务协议的成本。
XML配置
接下来再来给你讲下XML配置方式这种方式的服务发布和引用主要分三个步骤
服务提供者定义接口,并实现接口。
服务提供者进程启动时通过加载server.xml配置文件将接口暴露出去。
服务消费者进程启动时通过加载client.xml配置文件来引入要调用的接口。
我继续以服务化框架Motan为例它还支持以XML配置的方式来发布和引用服务。
首先,服务提供者定义接口。
public interface FooService {
public String hello(String name);
}
然后服务提供者实现接口。
public class FooServiceImpl implements FooService {
public String hello(String name) {
System.out.println(name + " invoked rpc service");
return "hello " + name;
}
}
最后服务提供者进程启动时加载server.xml配置文件开启8002端口监听。
server.xml配置如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:motan="http://api.weibo.com/schema/motan"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://api.weibo.com/schema/motan http://api.weibo.com/schema/motan.xsd">
<!-- service implemention bean -->
<bean id="serviceImpl" class="quickstart.FooServiceImpl" />
<!-- exporting service by Motan -->
<motan:service interface="quickstart.FooService" ref="serviceImpl" export="8002" />
</beans>
服务提供者加载server.xml的代码如下
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Server {
public static void main(String[] args) throws InterruptedException {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:motan_server.xml");
System.out.println("server start...");
}
}
服务消费者要想调用服务就必须在进程启动时加载配置client.xml引用接口定义然后发起调用。
client.xml配置如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:motan="http://api.weibo.com/schema/motan"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://api.weibo.com/schema/motan http://api.weibo.com/schema/motan.xsd">
<!-- reference to the remote service -->
<motan:referer id="remoteService" interface="quickstart.FooService" directUrl="localhost:8002"/>
</beans>
服务消费者启动时加载client.xml的代码如下。
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Client {
public static void main(String[] args) throws InterruptedException {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:motan_client.xml");
FooService service = (FooService) ctx.getBean("remoteService");
System.out.println(service.hello("motan"));
}
}
就这样通过在服务提供者和服务消费者之间维持一份对等的XML配置文件来保证服务消费者按照服务提供者的约定来进行服务调用。在这种方式下如果服务提供者变更了接口定义不仅需要更新服务提供者加载的接口描述文件server.xml还需要同时更新服务消费者加载的接口描述文件client.xml。
一般是私有RPC框架会选择XML配置这种方式来描述接口因为私有RPC协议的性能要比HTTP协议高所以在对性能要求比较高的场景下采用XML配置的方式比较合适。但这种方式对业务代码侵入性比较高XML配置有变更的时候服务消费者和服务提供者都要更新所以适合公司内部联系比较紧密的业务之间采用。如果要应用到跨部门之间的业务调用一旦有XML配置变更需要花费大量精力去协调不同部门做升级工作。在我经历的实际项目里就遇到过一次底层服务的接口升级需要所有相关的调用方都升级为此花费了大量时间去协调沟通不同部门之间的升级工作最后经历了大半年才最终完成。所以对于XML配置方式的服务描述一旦应用到多个部门之间的接口格式约定如果有变更最好是新增接口不到万不得已不要对原有的接口格式做变更。
IDL文件
IDL就是接口描述语言interface description language的缩写通过一种中立的方式来描述接口使得在不同的平台上运行的对象和不同语言编写的程序可以相互通信交流。比如你用Java语言实现提供的一个服务也能被PHP语言调用。
也就是说IDL主要是用作跨语言平台的服务之间的调用有两种最常用的IDL一个是Facebook开源的Thrift协议另一个是Google开源的gRPC协议。无论是Thrift协议还是gRPC协议它们的工作原理都是类似的。
接下来我以gRPC协议为例给你讲讲如何使用IDL文件方式来描述接口。
gRPC协议使用Protobuf简称proto文件来定义接口名、调用参数以及返回值类型。
比如文件helloword.proto定义了一个接口SayHello方法它的请求参数是HelloRequest它的返回值是HelloReply。
// The greeter service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
假如服务提供者使用的是Java语言那么利用protoc插件即可自动生成Server端的Java代码。
private class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
@Override
public void sayHelloAgain(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello again " + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
假如服务消费者使用的也是Java语言那么利用protoc插件即可自动生成Client端的Java代码。
public void greet(String name) {
logger.info("Will try to greet " + name + " ...");
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply response;
try {
response = blockingStub.sayHello(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
logger.info("Greeting: " + response.getMessage());
try {
response = blockingStub.sayHelloAgain(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
logger.info("Greeting: " + response.getMessage());
}
假如服务消费者使用的是PHP语言那么利用protoc插件即可自动生成Client端的PHP代码。
$request = new Helloworld\HelloRequest();
$request->setName($name);
list($reply, $status) = $client->SayHello($request)->wait();
$message = $reply->getMessage();
list($reply, $status) = $client->SayHelloAgain($request)->wait();
$message = $reply->getMessage();
由此可见gRPC协议的服务描述是通过proto文件来定义接口的然后再使用protoc来生成不同语言平台的客户端和服务端代码从而具备跨语言服务调用能力。
有一点特别需要注意的是在描述接口定义时IDL文件需要对接口返回值进行详细定义。如果接口返回值的字段比较多并且经常变化时采用IDL文件方式的接口定义就不太合适了。一方面可能会造成IDL文件过大难以维护另一方面只要IDL文件中定义的接口返回值有变更都需要同步所有的服务消费者都更新管理成本就太高了。
我在项目实践过程中曾经考虑过采用Protobuf文件来描述微博内容接口但微博内容返回的字段有几百个并且有些字段不固定返回什么字段是业务方自定义的这种情况采用Protobuf文件来描述的话会十分麻烦所以最终不得不放弃这种方式。
总结
今天我给你介绍了服务描述最常见的三种方式RESTful API、XML配置以及IDL文件。
具体采用哪种服务描述方式是根据实际情况决定的通常情况下如果只是企业内部之间的服务调用并且都是Java语言的话选择XML配置方式是最简单的。如果企业内部存在多个服务并且服务采用的是不同语言平台建议使用IDL文件方式进行描述服务。如果还存在对外开放服务调用的情形的话使用RESTful API方式则更加通用。
思考题
针对你的业务场景思考一下,假如要进行服务化,你觉得使用哪种服务描述最合适?为什么?
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,127 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
05 如何注册和发现服务?
专栏上一期我给你介绍了服务发布和引用常用的三种方式RESTful API、XML配置以及IDL文件。假设你已经使用其中一种方式发布了一个服务并且已经在一台机器上部署了服务那我想问你个问题如果我想调用这个服务我该如何知道你部署的这台机器的地址呢
这个问题就跟我想去吃肯德基一样,我可以去谷歌地图上搜索肯德基,然后谷歌地图会返回所有的肯德基店面的地址,于是我选择距离最近的一家去吃。这里面谷歌地图就扮演了一个类似注册中心的角色,收录了所有肯德基店面的地址。
同理,我想知道这台服务器的地址,那是不是可以去一个类似“谷歌地图”的地方去查呢?是的,在分布式系统里,就有一个类似的概念,不过它的名字可不是叫什么地图,而是叫注册中心。但原理和地图其实差不多,就是将部署服务的机器地址记录到注册中心,服务消费者在有需求的时候,只需要查询注册中心,输入提供的服务名,就可以得到地址,从而发起调用。
下面我来给你详细讲解下注册中心的原理和实现方式。
注册中心原理
在微服务架构下主要有三种角色服务提供者RPC Server、服务消费者RPC Client和服务注册中心Registry三者的交互关系请看下面这张图我来简单解释一下。
RPC Server提供服务在启动时根据服务发布文件server.xml中的配置的信息向Registry注册自身服务并向Registry定期发送心跳汇报存活状态。
RPC Client调用服务在启动时根据服务引用文件client.xml中配置的信息向Registry订阅服务把Registry返回的服务节点列表缓存在本地内存中并与RPC Sever建立连接。
当RPC Server节点发生变更时Registry会同步变更RPC Client感知后会刷新本地内存中缓存的服务节点列表。
RPC Client从本地缓存的服务节点列表中基于负载均衡算法选择一台RPC Sever发起调用。
注册中心实现方式
注册中心的实现主要涉及几个问题:注册中心需要提供哪些接口,该如何部署;如何存储服务信息;如何监控服务提供者节点的存活;如果服务提供者节点有变化如何通知服务消费者,以及如何控制注册中心的访问权限。下面我来一一给你讲解。
1. 注册中心API
根据注册中心原理的描述注册中心必须提供以下最基本的API例如
服务注册接口:服务提供者通过调用服务注册接口来完成服务注册。
服务反注册接口:服务提供者通过调用服务反注册接口来完成服务注销。
心跳汇报接口:服务提供者通过调用心跳汇报接口完成节点存活状态上报。
服务订阅接口:服务消费者通过调用服务订阅接口完成服务订阅,获取可用的服务提供者节点列表。
服务变更查询接口:服务消费者通过调用服务变更查询接口,获取最新的可用服务节点列表。
除此之外为了便于管理注册中心还必须提供一些后台管理的API例如
服务查询接口:查询注册中心当前注册了哪些服务信息。
服务修改接口:修改注册中心中某一服务的信息。
2. 集群部署
注册中心作为服务提供者和服务消费者之间沟通的桥梁,它的重要性不言而喻。所以注册中心一般都是采用集群部署来保证高可用性,并通过分布式一致性协议来确保集群中不同节点之间的数据保持一致。
以开源注册中心ZooKeeper为例ZooKeeper集群中包含多个节点服务提供者和服务消费者可以同任意一个节点通信因为它们的数据一定是相同的这是为什么呢这就要从ZooKeeper的工作原理说起
每个Server在内存中存储了一份数据Client的读请求可以请求任意一个Server。
ZooKeeper启动时将从实例中选举一个leaderPaxos协议
Leader负责处理数据更新等操作ZAB协议
一个更新操作成功当且仅当大多数Server在内存中成功修改 。
通过上面这种方式ZooKeeper保证了高可用性以及数据一致性。
3. 目录存储
还是以ZooKeeper为例注册中心存储服务信息一般采用层次化的目录结构
每个目录在ZooKeeper中叫作znode并且其有一个唯一的路径标识。
znode可以包含数据和子znode。
znode中的数据可以有多个版本比如某一个znode下存有多个数据版本那么查询这个路径下的数据需带上版本信息。
4. 服务健康状态检测
注册中心除了要支持最基本的服务注册和服务订阅功能以外,还必须具备对服务提供者节点的健康状态检测功能,这样才能保证注册中心里保存的服务节点都是可用的。
还是以ZooKeeper为例它是基于ZooKeeper客户端和服务端的长连接和会话超时控制机制来实现服务健康状态检测的。
在ZooKeeper中客户端和服务端建立连接后会话也随之建立并生成一个全局唯一的Session ID。服务端和客户端维持的是一个长连接在SESSION_TIMEOUT周期内服务端会检测与客户端的链路是否正常具体方式是通过客户端定时向服务端发送心跳消息ping消息服务器重置下次SESSION_TIMEOUT时间。如果超过SESSION_TIMEOUT后服务端都没有收到客户端的心跳消息则服务端认为这个Session就已经结束了ZooKeeper就会认为这个服务节点已经不可用将会从注册中心中删除其信息。
5. 服务状态变更通知
一旦注册中心探测到有服务提供者节点新加入或者被剔除,就必须立刻通知所有订阅该服务的服务消费者,刷新本地缓存的服务节点信息,确保服务调用不会请求不可用的服务提供者节点。
继续以ZooKeeper为例基于ZooKeeper的Watcher机制来实现服务状态变更通知给服务消费者的。服务消费者在调用ZooKeeper的getData方法订阅服务时还可以通过监听器Watcher的process方法获取服务的变更然后调用getData方法来获取变更后的数据刷新本地缓存的服务节点信息。
6. 白名单机制
在实际的微服务测试和部署时通常包含多套环境比如生产环境一套、测试环境一套。开发在进行业务自测、测试在进行回归测试时一般都是用测试环境部署的RPC Server节点注册到测试的注册中心集群。但经常会出现开发或者测试在部署时错误的把测试环境下的服务节点注册到了线上注册中心集群这样的话线上流量就会调用到测试环境下的RPC Server节点可能会造成意想不到的后果。
为了防止这种情况发生注册中心需要提供一个保护机制你可以把注册中心想象成一个带有门禁的房间只有拥有门禁卡的RPC Server才能进入。在实际应用中注册中心可以提供一个白名单机制只有添加到注册中心白名单内的RPC Server才能够调用注册中心的注册接口这样的话可以避免测试环境中的节点意外跑到线上环境中去。
总结
注册中心可以说是实现服务化的关键,因为服务化之后,服务提供者和服务消费者不在同一个进程中运行,实现了解耦,这就需要一个纽带去连接服务提供者和服务消费者,而注册中心就正好承担了这一角色。此外,服务提供者可以任意伸缩即增加节点或者减少节点,通过服务健康状态检测,注册中心可以保持最新的服务节点信息,并将变化通知给订阅服务的服务消费者。
注册中心一般采用分布式集群部署来保证高可用性并且为了实现异地多活有的注册中心还采用多IDC部署这就对数据一致性产生了很高的要求这些都是注册中心在实现时必须要解决的问题。
思考题
最后请你思考一下你觉得采用注册中心来实现服务发现与传统的DNS实现服务发现有什么不同吗
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,145 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
06 如何实现RPC远程服务调用
专栏上一期我讲过,要完成一次服务调用,首先要解决的问题是服务消费者如何得到服务提供者的地址,其中注册中心扮演了关键角色,服务提供者把自己的地址登记到注册中心,服务消费者就可以查询注册中心得到服务提供者的地址,可以说注册中心犹如海上的一座灯塔,为服务消费者指引了前行的方向。
有了服务提供者的地址后服务消费者就可以向这个地址发起请求了但这时候也产生了一个新的问题。你知道在单体应用时一次服务调用发生在同一台机器上的同一个进程内部也就是说调用发生在本机内部因此也被叫作本地方法调用。在进行服务化拆分之后服务提供者和服务消费者运行在两台不同物理机上的不同进程内它们之间的调用相比于本地方法调用可称之为远程方法调用简称RPCRemote Procedure Call那么RPC调用是如何实现的呢
在介绍RPC调用的原理之前先来想象一下一次电话通话的过程。首先呼叫者A通过查询号码簿找到被呼叫者B的电话号码然后拨打B的电话。B接到来电提示时如果方便接听的话就会接听如果不方便接听的话A就得一直等待。当等待超过一段时间后电话会因超时被挂断这个时候A需要再次拨打电话一直等到B空闲的时候才能接听。
RPC调用的原理与此类似我习惯把服务消费者叫作客户端服务提供者叫作服务端两者通常位于网络上两个不同的地址要完成一次RPC调用就必须先建立网络连接。建立连接后双方还必须按照某种约定的协议进行网络通信这个协议就是通信协议。双方能够正常通信后服务端接收到请求时需要以某种方式进行处理处理成功后把请求结果返回给客户端。为了减少传输的数据大小还要对数据进行压缩也就是对数据进行序列化。
上面就是RPC调用的过程由此可见想要完成调用你需要解决四个问题
客户端和服务端如何建立网络连接?
服务端如何处理请求?
数据传输采用什么协议?
数据该如何序列化和反序列化?
客户端和服务端如何建立网络连接?
根据我的实践经验客户端和服务端之间基于TCP协议建立网络连接最常用的途径有两种。
1. HTTP通信
HTTP通信是基于应用层HTTP协议的而HTTP协议又是基于传输层TCP协议的。一次HTTP通信过程就是发起一次HTTP调用而一次HTTP调用就会建立一个TCP连接经历一次下图所示的“三次握手”的过程来建立连接。
完成请求后,再经历一次“四次挥手”的过程来断开连接。
2. Socket通信
Socket通信是基于TCP/IP协议的封装建立一次Socket连接至少需要一对套接字其中一个运行于客户端称为ClientSocket 另一个运行于服务器端称为ServerSocket 。就像下图所描述的Socket通信的过程分为四个步骤服务器监听、客户端请求、连接确认、数据传输。
服务器监听ServerSocket通过调用bind()函数绑定某个具体端口然后调用listen()函数实时监控网络状态,等待客户端的连接请求。
客户端请求ClientSocket调用connect()函数向ServerSocket绑定的地址和端口发起连接请求。
服务端连接确认当ServerSocket监听到或者接收到ClientSocket的连接请求时调用accept()函数响应ClientSocket的请求同客户端建立连接。
数据传输当ClientSocket和ServerSocket建立连接后ClientSocket调用send()函数ServerSocket调用receive()函数ServerSocket处理完请求后调用send()函数ClientSocket调用receive()函数,就可以得到得到返回结果。
直接理解可能有点抽象你可以把这个过程套入前面我举的“打电话”的例子可以方便你理解Socket通信过程。
当客户端和服务端建立网络连接后,就可以发起请求了。但网络不一定总是可靠的,经常会遇到网络闪断、连接超时、服务端宕机等各种异常,通常的处理手段有两种。
链路存活检测客户端需要定时地发送心跳检测消息一般是通过ping请求给服务端如果服务端连续n次心跳检测或者超过规定的时间都没有回复消息则认为此时链路已经失效这个时候客户端就需要重新与服务端建立连接。
断连重试:通常有多种情况会导致连接断开,比如客户端主动关闭、服务端宕机或者网络故障等。这个时候客户端就需要与服务端重新建立连接,但一般不能立刻完成重连,而是要等待固定的间隔后再发起重连,避免服务端的连接回收不及时,而客户端瞬间重连的请求太多而把服务端的连接数占满。
服务端如何处理请求?
假设这时候客户端和服务端已经建立了网络连接,服务端又该如何处理客户端的请求呢?通常来讲,有三种处理方式。
同步阻塞方式BIO客户端每发一次请求服务端就生成一个线程去处理。当客户端同时发起的请求很多时服务端需要创建很多的线程去处理每一个请求如果达到了系统最大的线程数瓶颈新来的请求就没法处理了。
同步非阻塞方式 (NIO)客户端每发一次请求服务端并不是每次都创建一个新线程来处理而是通过I/O多路复用技术进行处理。就是把多个I/O的阻塞复用到同一个select的阻塞上从而使系统在单线程的情况下可以同时处理多个客户端请求。这种方式的优势是开销小不用为每个请求创建一个线程可以节省系统开销。
异步非阻塞方式AIO客户端只需要发起一个I/O操作然后立即返回等I/O操作真正完成以后客户端会得到I/O操作完成的通知此时客户端只需要对数据进行处理就好了不需要进行实际的I/O读写操作因为真正的I/O读取或者写入操作已经由内核完成了。这种方式的优势是客户端无需等待不存在阻塞等待问题。
从前面的描述,可以看出来不同的处理方式适用于不同的业务场景,根据我的经验:
BIO适用于连接数比较小的业务场景这样的话不至于系统中没有可用线程去处理请求。这种方式写的程序也比较简单直观易于理解。
NIO适用于连接数比较多并且请求消耗比较轻的业务场景比如聊天服务器。这种方式相比BIO相对来说编程比较复杂。
AIO适用于连接数比较多而且请求消耗比较重的业务场景比如涉及I/O操作的相册服务器。这种方式相比另外两种编程难度最大程序也不易于理解。
上面两个问题就是“通信框架”要解决的问题你可以基于现有的Socket通信在服务消费者和服务提供者之间建立网络连接然后在服务提供者一侧基于BIO、NIO和AIO三种方式中的任意一种实现服务端请求处理最后再花费一些精力去解决服务消费者和服务提供者之间的网络可靠性问题。这种方式对于Socket网络编程、多线程编程知识都要求比较高感兴趣的话可以尝试自己实现一个通信框架。但我建议最为稳妥的方式是使用成熟的开源方案比如Netty、MINA等它们都是经过业界大规模应用后被充分论证是很可靠的方案。
假设客户端和服务端的连接已经建立了服务端也能正确地处理请求了接下来完成一次正常地RPC调用还需要解决两个问题即数据传输采用什么协议以及数据该如何序列化和反序列化。
数据传输采用什么协议?
首先来看第一个问题,数据传输采用什么协议?
最常用的有HTTP协议它是一种开放的协议各大网站的服务器和浏览器之间的数据传输大都采用了这种协议。还有一些定制的私有协议比如阿里巴巴开源的Dubbo协议也可以用于服务端和客户端之间的数据传输。无论是开放的还是私有的协议都必须定义一个“契约”以便服务消费者和服务提供者之间能够达成共识。服务消费者按照契约对传输的数据进行编码然后通过网络传输过去服务提供者从网络上接收到数据后按照契约对传输的数据进行解码然后处理请求再把处理后的结果进行编码通过网络传输返回给服务消费者服务消费者再对返回的结果进行解码最终得到服务提供者处理后的返回值。
通常协议契约包括两个部分:消息头和消息体。其中消息头存放的是协议的公共字段以及用户扩展字段,消息体存放的是传输数据的具体内容。
以HTTP协议为例下图展示了一段采用HTTP协议传输的数据响应报文主要分为消息头和消息体两部分其中消息头中存放的是协议的公共字段比如Server代表是服务端服务器类型、Content-Length代表返回数据的长度、Content-Type代表返回数据的类型消息体中存放的是具体的返回结果这里就是一段HTML网页代码。
数据该如何序列化和反序列化?
再看第二个问题,数据该如何序列化和反序列化。
一般数据在网络中进行传输前,都要先在发送方一端对数据进行编码,经过网络传输到达另一端后,再对数据进行解码,这个过程就是序列化和反序列化。
为什么要对数据进行序列化和反序列化呢要知道网络传输的耗时一方面取决于网络带宽的大小另一方面取决于数据传输量。要想加快网络传输要么提高带宽要么减小数据传输量而对数据进行编码的主要目的就是减小数据传输量。比如一部高清电影原始大小为30GB如果经过特殊编码格式处理可以减小到3GB同样是100MB/s的网速下载时间可以从300s减小到30s。
常用的序列化方式分为两类文本类如XML/JSON等二进制类如PB/Thrift等而具体采用哪种序列化方式主要取决于三个方面的因素。
支持数据结构类型的丰富度。数据结构种类支持的越多越好这样的话对于使用者来说在编程时更加友好有些序列化框架如Hessian 2.0还支持复杂的数据结构比如Map、List等。
跨语言支持。序列化方式是否支持跨语言也是一个很重要的因素否则使用的场景就比较局限比如Java序列化只支持Java语言就不能用于跨语言的服务调用了。
性能。主要看两点一个是序列化后的压缩比一个是序列化的速度。以常用的PB序列化和JSON序列化协议为例来对比分析PB序列化的压缩比和速度都要比JSON序列化高很多所以对性能和存储空间要求比较高的系统选用PB序列化更合适而JSON序列化虽然性能要差一些但可读性更好更适合对外部提供服务。
总结
今天我给你讲解了服务调用需要解决的几个问题,其中你需要掌握:
通信框架。它主要解决客户端和服务端如何建立连接、管理连接以及服务端如何处理请求的问题。
通信协议。它主要解决客户端和服务端采用哪种数据传输协议的问题。
序列化和反序列化。它主要解决客户端和服务端采用哪种数据编解码的问题。
这三个部分就组成了一个完整的RPC调用框架通信框架提供了基础的通信能力通信协议描述了通信契约而序列化和反序列化则用于数据的编/解码。一个通信框架可以适配多种通信协议也可以采用多种序列化和反序列化的格式比如服务化框架Dubbo不仅支持Dubbo协议还支持RMI协议、HTTP协议等而且还支持多种序列化和反序列化格式比如JSON、Hession 2.0以及Java序列化等。
思考题
gRPC是一个优秀的跨语言RPC调用框架按照今天我给你讲的服务调用知识通过阅读官方文档你能给出gRPC调用的实现原理吗
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,145 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
07 如何监控微服务调用?
与单体应用相比,在微服务架构下,一次用户调用会因为服务化拆分后,变成多个不同服务之间的相互调用,这也就需要对拆分后的每个服务都监控起来。
在讲述如何监控微服务调用前,首先你要搞清楚三个问题:监控的对象是什么?具体监控哪些指标?从哪些维度进行监控?下面就从这三个问题开始,一起来看看如何监控微服务调用。
监控对象
既然要监控,那么要监控哪些对象呢?根据我的实践经验,对于微服务系统来说,监控对象可以分为四个层次,由上到下可归纳为:
用户端监控。通常是指业务直接对用户提供的功能的监控。以微博首页Feed为例它向用户提供了聚合关注的所有人的微博并按照时间顺序浏览的功能对首页Feed功能的监控就属于用户端的监控。
接口监控。通常是指业务提供的功能所依赖的具体RPC接口的监控。继续以微博首页Feed为例这个功能依赖于用户关注了哪些人的关系服务每个人发过哪些微博的微博列表服务以及每条微博具体内容是什么的内容服务对这几个服务的调用情况的监控就属于接口监控。
资源监控。通常是指某个接口依赖的资源的监控。比如用户关注了哪些人的关系服务使用的是Redis来存储关注列表对Redis的监控就属于资源监控。
基础监控。通常是指对服务器本身的健康状况的监控。主要包括CPU利用率、内存使用量、I/O读写量、网卡带宽等。对服务器的基本监控也是必不可少的因为服务器本身的健康状况也是影响服务本身的一个重要因素比如服务器本身连接的网络交换机上联带宽被打满会影响所有部署在这台服务器上的业务。
监控指标
搞清楚要监控的对象之后,需要监控具体哪些指标呢?根据我的实践经验,通常有以下几个业务指标需要重点监控:
请求量。请求量监控分为两个维度一个是实时请求量一个是统计请求量。实时请求量用QPSQueries Per Second即每秒查询次数来衡量它反映了服务调用的实时变化情况。统计请求量一般用PVPage View即一段时间内用户的访问量来衡量比如一天的PV代表了服务一天的请求量通常用来统计报表。
响应时间。大多数情况下可以用一段时间内所有调用的平均耗时来反映请求的响应时间。但它只代表了请求的平均快慢情况有时候我们更关心慢请求的数量。为此需要把响应时间划分为多个区间比如010ms、10ms50ms、50ms100ms、100ms500ms、500ms以上这五个区间其中500ms以上这个区间内的请求数就代表了慢请求量正常情况下这个区间内的请求数应该接近于0在出现问题时这个区间内的请求数会大幅增加可能平均耗时并不能反映出这一变化。除此之外还可以从P90、P95、P99、P999角度来监控请求的响应时间比如P99 = 500ms意思是99%的请求响应时间在500ms以内它代表了请求的服务质量即SLA。
错误率。错误率的监控通常用一段时间内调用失败的次数占调用总次数的比率来衡量比如对于接口的错误率一般用接口返回错误码为503的比率来表示。
监控维度
一般来说,要从多个维度来对业务进行监控,具体来讲可以包括下面几个维度:
全局维度。从整体角度监控对象的的请求量、平均耗时以及错误率,全局维度的监控一般是为了让你对监控对象的调用情况有个整体了解。
分机房维度。一般为了业务的高可用性,服务通常部署在不止一个机房,因为不同机房地域的不同,同一个监控对象的各种指标可能会相差很大,所以需要深入到机房内部去了解。
单机维度。即便是在同一个机房内部,可能由于采购年份和批次的不同,位于不同机器上的同一个监控对象的各种指标也会有很大差异。一般来说,新采购的机器通常由于成本更低,配置也更高,在同等请求量的情况下,可能表现出较大的性能差异,因此也需要从单机维度去监控同一个对象。
时间维度。同一个监控对象,在每天的同一时刻各种指标通常也不会一样,这种差异要么是由业务变更导致,要么是运营活动导致。为了了解监控对象各种指标的变化,通常需要与一天前、一周前、一个月前,甚至三个月前做比较。
核心维度。根据我的经验,业务上一般会依据重要性程度对监控对象进行分级,最简单的是分成核心业务和非核心业务。核心业务和非核心业务在部署上必须隔离,分开监控,这样才能对核心业务做重点保障。
讲到这里先小结一下,对于一个微服务来说,你必须明确要监控哪些对象、哪些指标,并且还要从不同的维度进行监控,才能掌握微服务的调用情况。明确了这几个关键的问题后,那么该如何搭建一个监控系统,来完成上面这些监控功能呢?
监控系统原理
显然我们要对服务调用进行监控首先要能收集到每一次调用的详细信息包括调用的响应时间、调用是否成功、调用的发起者和接收者分别是谁这个过程叫作数据采集。采集到数据之后要把数据通过一定的方式传输给数据处理中心进行处理这个过程叫作数据传输。数据传输过来后数据处理中心再按照服务的维度进行聚合计算出不同服务的请求量、响应时间以及错误率等信息并存储起来这个过程叫作数据处理。最后再通过接口或者Dashboard的形式对外展示服务的调用情况这个过程叫作数据展示。
可见,监控系统主要包括四个环节:数据采集、数据传输、数据处理和数据展示,下面我来给你讲解下每一个环节的实现原理。
1. 数据采集
通常有两种数据收集方式:
服务主动上报,这种处理方式通过在业务代码或者服务框架里加入数据收集代码逻辑,在每一次服务调用完成后,主动上报服务的调用信息。
代理收集,这种处理方式通过服务调用后把调用的详细信息记录到本地日志文件中,然后再通过代理去解析本地日志文件,然后再上报服务的调用信息。
无论哪种数据采集方式首先要考虑的问题就是采样率也就是采集数据的频率。采样率决定了监控的实时性与精确度一般来说采样率越高监控的实时性就越高精确度也越高。但采样对系统本身的性能也会有一定的影响尤其是采集后的数据需要写到本地磁盘的时候过高的采样率会导致系统写入磁盘的I/O过高进而会影响到正常的服务调用。所以设置合理的采用率是数据采集的关键最好是可以动态控制采样率在系统比较空闲的时候加大采样率追求监控的实时性与精确度在系统负载比较高的时候减小采样率追求监控的可用性与系统的稳定性。
2. 数据传输
数据传输最常用的方式有两种:
UDP传输这种处理方式是数据处理单元提供服务器的请求地址数据采集后通过UDP协议与服务器建立连接然后把数据发送过去。
Kafka传输这种处理方式是数据采集后发送到指定的Topic然后数据处理单元再订阅对应的Topic就可以从Kafka消息队列中读取到对应的数据。
无论采用哪种传输方式,数据格式都十分重要,尤其是对带宽敏感以及解析性能要求比较高的场景,一般数据传输时采用的数据格式有两种:
二进制协议最常用的就是PB对象它的优点是高压缩比和高性能可以减少传输带宽并且序列化和反序列化效率特别高。
文本协议最常用的就是JSON字符串它的优点是可读性好但相比于PB对象传输占用带宽高并且解析性能也要差一些。
3. 数据处理
数据处理是对收集来的原始数据进行聚合并存储。数据聚合通常有两个维度:
接口维度聚合,这个维度是把实时收到的数据按照接口名维度实时聚合在一起,这样就可以得到每个接口的实时请求量、平均耗时等信息。
机器维度聚合,这个维度是把实时收到的数据按照调用的节点维度聚合在一起,这样就可以从单机维度去查看每个接口的实时请求量、平均耗时等信息。
聚合后的数据需要持久化到数据库中存储,所选用的数据库一般分为两种:
索引数据库比如Elasticsearch以倒排索引的数据结构存储需要查询的时候根据索引来查询。
时序数据库比如OpenTSDB以时序序列数据的方式存储查询的时候按照时序如1min、5min等维度来查询。
4. 数据展示
数据展示是把处理后的数据以Dashboard的方式展示给用户。数据展示有多种方式比如曲线图、饼状图、格子图展示等。
曲线图。一般是用来监控变化趋势的,比如下面的曲线图展示了监控对象随着时间推移的变化趋势,可以看出来这段时间内变化比较小,曲线也比较平稳。
饼状图。一般是用来监控占比分布的比如下面这张饼图展示了使用不同的手机网络占比情况可见Wi-Fi和4G的占比明显要高于3G和2G。
格子图。主要做一些细粒度的监控,比如下面这张格子图代表了不同的机器的接口调用请求量和耗时情况,展示结果一目了然。
总结
服务监控在微服务改造过程中的重要性不言而喻,没有强大的监控能力,改造成微服务架构后,就无法掌控各个不同服务的情况,在遇到调用失败时,如果不能快速发现系统的问题,对于业务来说就是一场灾难。
搭建一个服务监控系统,涉及数据采集、数据传输、数据处理、数据展示等多个环节,每个环节都需要根据自己的业务特点选择合适的解决方案,关于监控技术方案的选型我会在专栏后面进行详解。
思考题
最后请你思考一下,你所在的技术团队目前采用的监控系统,都监控了哪些业务数据?包含哪些业务指标?都有哪些维度?你觉得是否合理?
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,173 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
08 如何追踪微服务调用?
在微服务架构下,由于进行了服务拆分,一次请求往往需要涉及多个服务,每个服务可能是由不同的团队开发,使用了不同的编程语言,还有可能部署在不同的机器上,分布在不同的数据中心。
下面这张图描述了用户访问微博首页,一次请求所涉及的服务(这张图仅作为示意,实际上可能远远比这张图还要复杂),你可以想象如果这次请求失败了,要想查清楚到底是哪个应用导致,会是多么复杂的一件事情。
如果有一个系统,可以跟踪记录一次用户请求都发起了哪些调用,经过哪些服务处理,并且记录每一次调用所涉及的服务的详细信息,这时候如果发生调用失败,你就可以通过这个日志快速定位是在哪个环节出了问题,这个系统就是今天我要讲解的服务追踪系统。
服务追踪的作用
在介绍追踪原理与实现之前,我们先来看看服务追踪的作用。除了刚才说的能够快速定位请求失败的原因以外,我这里再列出四点,它们可以帮你在微服务改造过程中解决不少问题。
第一,优化系统瓶颈。
通过记录调用经过的每一条链路上的耗时,我们能快速定位整个系统的瓶颈点在哪里。比如你访问微博首页发现很慢,肯定是由于某种原因造成的,有可能是运营商网络延迟,有可能是网关系统异常,有可能是某个服务异常,还有可能是缓存或者数据库异常。通过服务追踪,可以从全局视角上去观察,找出整个系统的瓶颈点所在,然后做出针对性的优化。
第二,优化链路调用。
通过服务追踪可以分析调用所经过的路径,然后评估是否合理。比如一个服务调用下游依赖了多个服务,通过调用链分析,可以评估是否每个依赖都是必要的,是否可以通过业务优化来减少服务依赖。
还有就是一般业务都会在多个数据中心都部署服务以实现异地容灾这个时候经常会出现一种状况就是服务A调用了另外一个数据中心的服务B而没有调用同处于一个数据中心的服务B。
根据我的经验跨数据中心的调用视距离远近都会有一定的网络延迟像北京和广州这种几千公里距离的网络延迟可能达到30ms以上这对于有些业务几乎是不可接受的。通过对调用链路进行分析可以找出跨数据中心的服务调用从而进行优化尽量规避这种情况出现。
第三,生成网络拓扑。
通过服务追踪系统中记录的链路信息,可以生成一张系统的网络调用拓扑图,它可以反映系统都依赖了哪些服务,以及服务之间的调用关系是什么样的,可以一目了然。除此之外,在网络拓扑图上还可以把服务调用的详细信息也标出来,也能起到服务监控的作用。
第四,透明传输数据。
除了服务追踪业务上经常有一种需求期望能把一些用户数据从调用的开始一直往下传递以便系统中的各个服务都能获取到这个信息。比如业务想做一些A/B测试这时候就想通过服务追踪系统把A/B测试的开关逻辑一直往下传递经过的每一层服务都能获取到这个开关值就能够统一进行A/B测试。
服务追踪系统原理
讲到这里,你一定很好奇,服务追踪有这么多好处,那它是怎么做到的呢?
这就不得不提到服务追踪系统的鼻祖Google发布的一篇的论文Dapper, a Large-Scale Distributed Systems Tracing Infrastructure里面详细讲解了服务追踪系统的实现原理。它的核心理念就是调用链通过一个全局唯一的ID将分布在各个服务节点上的同一次请求串联起来从而还原原有的调用关系可以追踪系统问题、分析调用数据并统计各种系统指标。
可以说后面的诞生各种服务追踪系统都是基于Dapper衍生出来的比较有名的有Twitter的Zipkin、阿里的鹰眼、美团的MTrace等。
要理解服务追踪的原理首先必须搞懂一些基本概念traceId、spanId、annonation等。Dapper这篇论文讲得比较清楚但对初学者来说理解起来可能有点困难美团的MTrace的原理介绍理解起来相对容易一些下面我就以MTrace为例给你详细讲述服务追踪系统的实现原理。虽然原理有些晦涩但却是你必须掌握的只有理解了服务追踪的基本概念才能更好地将其实现出来。
首先看下面这张图,我来给你讲解下服务追踪系统中几个最基本概念。
-
图片来源http://tech.meituan.com/img/mt-mtrace/mtrace7.png
traceId用于标识某一次具体的请求ID。当用户的请求进入系统后会在RPC调用网络的第一层生成一个全局唯一的traceId并且会随着每一层的RPC调用不断往后传递这样的话通过traceId就可以把一次用户请求在系统中调用的路径串联起来。
spanId用于标识一次RPC调用在分布式请求中的位置。当用户的请求进入系统后处在RPC调用网络的第一层A时spanId初始值是0进入下一层RPC调用B的时候spanId是0.1继续进入下一层RPC调用C时spanId是0.1.1而与B处在同一层的RPC调用E的spanId是0.2这样的话通过spanId就可以定位某一次RPC请求在系统调用中所处的位置以及它的上下游依赖分别是谁。
annotation用于业务自定义埋点数据可以是业务感兴趣的想上传到后端的数据比如一次请求的用户UID。
上面这三段内容我用通俗语言再给你小结一下traceId是用于串联某一次请求在系统中经过的所有路径spanId是用于区分系统不同服务之间调用的先后关系而annotation是用于业务自定义一些自己感兴趣的数据在上传traceId和spanId这些基本信息之外添加一些自己感兴趣的信息。
服务追踪系统实现
讲到这里,你应该已经理解服务追踪系统里最重要的概念和原理了,我们先来看看服务追踪系统的架构,让你了解一下系统全貌。
-
图片来源https://tech.meituan.com/img/mt-mtrace/mtrace6.png
上面是服务追踪系统架构图,你可以看到一个服务追踪系统可以分为三层。
数据采集层,负责数据埋点并上报。
数据处理层,负责数据的存储与计算。
数据展示层,负责数据的图形化展示。
下面来看看具体每一层的实现方式是什么样的。
1. 数据采集层
数据采集层的作用就是在系统的各个不同模块中进行埋点,采集数据并上报给数据处理层进行处理。
那么该如何进行数据埋点呢?结合下面这张图来了解一下数据埋点的流程。
-
图片来源https://tech.meituan.com/img/mt-mtrace/mtrace9.png
以红色方框里圈出的A调用B的过程为例一次RPC请求可以分为四个阶段。
CSClient Send阶段 : 客户端发起请求,并生成调用的上下文。
SRServer Recieve阶段 : 服务端接收请求,并生成上下文。
SSServer Send阶段 : 服务端返回请求这个阶段会将服务端上下文数据上报下面这张图可以说明上报的数据有traceId=123456spanId=0.1appKey=Bmethod=B.methodstart=103duration=38。
CRClient Recieve阶段 : 客户端接收返回结果这个阶段会将客户端上下文数据上报上报的数据有traceid=123456spanId=0.1appKey=Amethod=B.methodstart=103duration=38。
-
图片来源https://tech.meituan.com/img/mt-mtrace/mtrace10.png
2. 数据处理层
数据处理层的作用就是把数据采集层上报的数据按需计算,然后落地存储供查询使用。
据我所知,数据处理的需求一般分为两类,一类是实时计算需求,一类是离线计算需求。
实时计算需求对计算效率要求比较高,一般要求对收集的链路数据能够在秒级别完成聚合计算,以供实时查询。而离线计算需求对计算效率要求就没那么高了,一般能在小时级别完成链路数据的聚合计算即可,一般用作数据汇总统计。针对这两类不同的数据处理需求,采用的计算方法和存储也不相同。
实时数据处理
针对实时数据处理一般采用Storm或者Spark Streaming来对链路数据进行实时聚合加工存储一般使用OLTP数据仓库比如HBase使用traceId作为RowKey能天然地把一整条调用链聚合在一起提高查询效率。
离线数据处理
针对离线数据处理一般通过运行MapReduce或者Spark批处理程序来对链路数据进行离线计算存储一般使用Hive。
3. 数据展示层
数据展示层的作用就是将处理后的链路信息以图形化的方式展示给用户。
根据我的经验,实际项目中主要用到两种图形展示,一种是调用链路图,一种是调用拓扑图。
调用链路图
下面以一张Zipkin的调用链路图为例通过这张图可以看出下面几个信息。
服务整体情况服务总耗时、服务调用的网络深度、每一层经过的系统以及多少次调用。下图展示的一次调用总共耗时209.323ms经过了5个不同的系统模块调用深度为7层共发生了24次系统调用。
每一层的情况:每一层发生了几次调用,以及每一层调用的耗时。
-
图片来源https://zipkin.io/public/img/web-screenshot.png
根据我的经验,调用链路图在实际项目中,主要是被用来做故障定位,比如某一次用户调用失败了,可以通过调用链路图查询这次用户调用经过了哪些环节,到底是哪一层的调用失败所导致。
调用拓扑图
下面是一张Pinpoint的调用拓扑图通过这张图可以看出系统内都包含哪些应用它们之间是什么关系以及依赖调用的QPS、平均耗时情况。
-
图片来源https://raw.githubusercontent.com/naver/pinpoint/master/doc/images/ss_server-map.png
调用拓扑图是一种全局视野图,在实际项目中,主要用作全局监控,用于发现系统中异常的点,从而快速做出决策。比如,某一个服务突然出现异常,那么在调用链路拓扑图中可以看出对这个服务的调用耗时都变高了,可以用红色的图样标出来,用作监控报警。
总结
今天我给你讲解了服务追踪的基本原理以及实现方式,可以说服务追踪是分布式系统中必不可少的功能,它能够帮助我们查询一次用户请求在系统中的具体执行路径,以及每一条路径的上下游的详细情况,对于追查问题十分有用。
实现一个服务追踪系统,涉及数据采集、数据处理和数据展示这三个流程,有多种实现方式,具体采用哪一种要根据自己的业务情况来选择。关于服务追踪系统的选型我在专栏后面会详细展开介绍,这里你只需要了解它的基本工作原理就可以了。
思考题
通过这两期的学习,你应该了解到服务追踪系统和服务监控系统的搭建都需要数据采集、处理和展示这三个步骤,你认为它们是否有相同和不同之处呢?
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,127 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
09 微服务治理的手段有哪些?
上一期我给你讲述了服务追踪的基本原理,有了分布式服务追踪系统,在服务出现问题的时候,我们就可以定位服务哪里出现了问题。一般单体应用改造成微服务架构后,还会增加哪些问题呢?又该如何应对呢?
前面我讲到单体应用改造为微服务架构后服务调用由本地调用变成远程调用服务消费者A需要通过注册中心去查询服务提供者B的地址然后发起调用这个看似简单的过程就可能会遇到下面几种情况比如
注册中心宕机;
服务提供者B有节点宕机
服务消费者A和注册中心之间的网络不通
服务提供者B和注册中心之间的网络不通
服务消费者A和服务提供者B之间的网络不通
服务提供者B有些节点性能变慢
服务提供者B短时间内出现问题。
可见,一次服务调用,服务提供者、注册中心、网络这三者都可能会有问题,此时服务消费者应该如何处理才能确保调用成功呢?这就是服务治理要解决的问题。
接下来我们一起来看看常用的服务治理手段。
节点管理
根据我的经验,服务调用失败一般是由两类原因引起的,一类是服务提供者自身出现问题,如服务器宕机、进程意外退出等;一类是网络问题,如服务提供者、注册中心、服务消费者这三者任意两者之间的网络出现问题。
无论是服务提供者自身出现问题还是网络发生问题,都有两种节点管理手段。
1. 注册中心主动摘除机制
这种机制要求服务提供者定时的主动向注册中心汇报心跳,注册中心根据服务提供者节点最近一次汇报心跳的时间与上一次汇报心跳时间做比较,如果超出一定时间,就认为服务提供者出现问题,继而把节点从服务列表中摘除,并把最近的可用服务节点列表推送给服务消费者。
2. 服务消费者摘除机制
虽然注册中心主动摘除机制可以解决服务提供者节点异常的问题,但如果是因为注册中心与服务提供者之间的网络出现异常,最坏的情况是注册中心会把服务节点全部摘除,导致服务消费者没有可用的服务节点调用,但其实这时候服务提供者本身是正常的。所以,将存活探测机制用在服务消费者这一端更合理,如果服务消费者调用服务提供者节点失败,就将这个节点从内存中保存的可用服务提供者节点列表中移除。
负载均衡
一般情况下服务提供者节点不是唯一的多是以集群的方式存在尤其是对于大规模的服务调用来说服务提供者节点数目可能有上百上千个。由于机器采购批次的不同不同服务节点本身的配置也可能存在很大差异新采购的机器CPU和内存配置可能要高一些同等请求量情况下性能要好于旧的机器。对于服务消费者而言在从服务列表中选取可用节点时如果能让配置较高的新机器多承担一些流量的话就能充分利用新机器的性能。这就需要对负载均衡算法做一些调整。
常用的负载均衡算法主要包括以下几种。
1. 随机算法
顾名思义就是从可用的服务节点中随机选取一个节点。一般情况下,随机算法是均匀的,也就是说后端服务节点无论配置好坏,最终得到的调用量都差不多。
2. 轮询算法
就是按照固定的权重,对可用服务节点进行轮询。如果所有服务节点的权重都是相同的,则每个节点的调用量也是差不多的。但可以给某些硬件配置较好的节点的权重调大些,这样的话就会得到更大的调用量,从而充分发挥其性能优势,提高整体调用的平均性能。
3. 最少活跃调用算法
这种算法是在服务消费者这一端的内存里动态维护着同每一个服务节点之间的连接数当调用某个服务节点时就给与这个服务节点之间的连接数加1调用返回后就给连接数减1。然后每次在选择服务节点时根据内存里维护的连接数倒序排列选择连接数最小的节点发起调用也就是选择了调用量最小的服务节点性能理论上也是最优的。
4. 一致性Hash算法
指相同参数的请求总是发到同一服务节点。当某一个服务节点出现故障时,原本发往该节点的请求,基于虚拟节点机制,平摊到其他节点上,不会引起剧烈变动。
这几种算法的实现难度也是逐步提升的,所以选择哪种节点选取的负载均衡算法要根据实际场景而定。如果后端服务节点的配置没有差异,同等调用量下性能也没有差异的话,选择随机或者轮询算法比较合适;如果后端服务节点存在比较明显的配置和性能差异,选择最少活跃调用算法比较合适。
服务路由
对于服务消费者而言,在内存中的可用服务节点列表中选择哪个节点不仅由负载均衡算法决定,还由路由规则确定。
所谓的路由规则,就是通过一定的规则如条件表达式或者正则表达式来限定服务节点的选择范围。
为什么要制定路由规则呢?主要有两个原因。
1. 业务存在灰度发布的需求
比如,服务提供者做了功能变更,但希望先只让部分人群使用,然后根据这部分人群的使用反馈,再来决定是否做全量发布。这个时候,就可以通过类似按尾号进行灰度的规则限定只有一定比例的人群才会访问新发布的服务节点。
2. 多机房就近访问的需求
据我所知大部分业务规模中等及以上的互联网公司为了业务的高可用性都会将自己的业务部署在不止一个IDC中。这个时候就存在一个问题不同IDC之间的访问由于要跨IDC通过专线访问尤其是IDC相距比较远时延迟就会比较大比如北京和广州的专线延迟一般在30ms左右这对于某些延时敏感性的业务是不可接受的所以就要一次服务调用尽量选择同一个IDC内部的节点从而减少网络耗时开销提高性能。这时一般可以通过IP段规则来控制访问在选择服务节点时优先选择同一IP段的节点。
那么路由规则该如何配置呢?根据我的实际项目经验,一般有两种配置方式。
1. 静态配置
就是在服务消费者本地存放服务调用的路由规则,在服务调用期间,路由规则不会发生改变,要想改变就需要修改服务消费者本地配置,上线后才能生效。
2. 动态配置
这种方式下,路由规则是存在注册中心的,服务消费者定期去请求注册中心来保持同步,要想改变服务消费者的路由配置,可以通过修改注册中心的配置,服务消费者在下一个同步周期之后,就会请求注册中心来更新配置,从而实现动态更新。
服务容错
服务调用并不总是一定成功的,前面我讲过,可能因为服务提供者节点自身宕机、进程异常退出或者服务消费者与提供者之间的网络出现故障等原因。对于服务调用失败的情况,需要有手段自动恢复,来保证调用成功。
常用的手段主要有以下几种。
FailOver失败自动切换。就是服务消费者发现调用失败或者超时后自动从可用的服务节点列表总选择下一个节点重新发起调用也可以设置重试的次数。这种策略要求服务调用的操作必须是幂等的也就是说无论调用多少次只要是同一个调用返回的结果都是相同的一般适合服务调用是读请求的场景。
FailBack失败通知。就是服务消费者调用失败或者超时后不再重试而是根据失败的详细信息来决定后续的执行策略。比如对于非幂等的调用场景如果调用失败后不能简单地重试而是应该查询服务端的状态看调用到底是否实际生效如果已经生效了就不能再重试了如果没有生效可以再发起一次调用。
FailCache失败缓存。就是服务消费者调用失败或者超时后不立即发起重试而是隔一段时间后再次尝试发起调用。比如后端服务可能一段时间内都有问题如果立即发起重试可能会加剧问题反而不利于后端服务的恢复。如果隔一段时间待后端节点恢复后再次发起调用效果会更好。
FailFast快速失败。就是服务消费者调用一次失败后不再重试。实际在业务执行时一般非核心业务的调用会采用快速失败策略调用失败后一般就记录下失败日志就返回了。
从我对服务容错不同策略的描述中你可以看出它们的使用场景是不同的一般情况下对于幂等的调用可以选择FailOver或者FailCache非幂等的调用可以选择FailBack或者FailFast。
总结
上面我讲的服务治理的手段是最常用的手段,它们从不同角度来确保服务调用的成功率。节点管理是从服务节点健康状态角度来考虑,负载均衡和服务路由是从服务节点访问优先级角度来考虑,而服务容错是从调用的健康状态角度来考虑,可谓是殊途同归。
在实际的微服务架构实践中上面这些服务治理手段一般都会在服务框架中默认集成了比如阿里开源的服务框架Dubbo、微博开源的服务框架Motan等不需要业务代码去实现。如果想自己实现服务治理的手段可以参考这些开源服务框架的实现。
思考题
上面讲述的这些服务治理手段,哪些是你的业务场景中可能需要的?你可以描述下你的业务场景,以及思考下为什么这些服务治理手段可以解决你的问题。
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,219 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
10 Dubbo框架里的微服务组件
经过前面几期的讲解,你应该已经对微服务的架构有了初步的了解。简单回顾一下,微服务的架构主要包括服务描述、服务发现、服务调用、服务监控、服务追踪以及服务治理这几个基本组件。
那么每个基本组件从架构和代码设计上该如何实现组件之间又是如何串联来实现一个完整的微服务架构呢今天我就以开源微服务框架Dubbo为例来给你具体讲解这些组件。
服务发布与引用
专栏前面我讲过服务发布与引用的三种常用方式RESTful API、XML配置以及IDL文件其中Dubbo框架主要是使用XML配置方式接下来我通过具体实例来给你讲讲Dubbo框架服务发布与引用是如何实现的。
首先来看服务发布的过程下面这段代码是服务提供者的XML配置。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="hello-world-app" />
<!-- 使用multicast广播注册中心暴露服务地址 -->
<dubbo:registry address="multicast://224.5.6.7:1234" />
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880" />
<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" />
<!-- 和本地bean一样实现服务 -->
<bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl" />
</beans>
其中“dubbo:service”开头的配置项声明了服务提供者要发布的接口“dubbo:protocol”开头的配置项声明了服务提供者要发布的接口的协议以及端口号。
Dubbo会把以上配置项解析成下面的URL格式
dubbo://host-ip:20880/com.alibaba.dubbo.demo.DemoService
然后基于扩展点自适应机制通过URL的“dubbo://”协议头识别就会调用DubboProtocol的export()方法打开服务端口20880就可以把服务demoService暴露到20880端口了。
再来看下服务引用的过程下面这段代码是服务消费者的XML配置。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
<dubbo:application name="consumer-of-helloworld-app" />
<!-- 使用multicast广播注册中心暴露发现服务地址 -->
<dubbo:registry address="multicast://224.5.6.7:1234" />
<!-- 生成远程服务代理可以和本地bean一样使用demoService -->
<dubbo:reference id="demoService" interface="com.alibaba.dubbo.demo.DemoService" />
</beans>
其中“dubbo:reference”开头的配置项声明了服务消费者要引用的服务Dubbo会把以上配置项解析成下面的URL格式
dubbo://com.alibaba.dubbo.demo.DemoService
然后基于扩展点自适应机制通过URL的“dubbo://”协议头识别就会调用DubboProtocol的refer()方法得到服务demoService引用完成服务引用过程。
服务注册与发现
先来看下服务提供者注册服务的过程继续以前面服务提供者的XML配置为例其中“dubbo://registry”开头的配置项声明了注册中心的地址Dubbo会把以上配置项解析成下面的URL格式
registry://multicast://224.5.6.7:1234/com.alibaba.dubbo.registry.RegistryService?export=URL.encode("dubbo://host-ip:20880/com.alibaba.dubbo.demo.DemoService")
然后基于扩展点自适应机制通过URL的“registry://”协议头识别就会调用RegistryProtocol的export()方法将export参数中的提供者URL注册到注册中心。
再来看下服务消费者发现服务的过程同样以前面服务消费者的XML配置为例其中“dubbo://registry”开头的配置项声明了注册中心的地址跟服务注册的原理类似Dubbo也会把以上配置项解析成下面的URL格式
registry://multicast://224.5.6.7:1234/com.alibaba.dubbo.registry.RegistryService?refer=URL.encode("consummer://host-ip/com.alibaba.dubbo.demo.DemoService")
然后基于扩展点自适应机制通过URL的“registry://”协议头识别就会调用RegistryProtocol的refer()方法基于refer参数中的条件查询服务demoService的地址。
服务调用
专栏前面我讲过在服务调用的过程中,通常把服务消费者叫作客户端,服务提供者叫作服务端,发起一次服务调用需要解决四个问题:
客户端和服务端如何建立网络连接?
服务端如何处理请求?
数据传输采用什么协议?
数据该如何序列化和反序列化?
其中前两个问题客户端和服务端如何建立连接和服务端如何处理请求是通信框架要解决的问题Dubbo支持多种通信框架比如Netty 4需要在服务端和客户端的XML配置中添加下面的配置项。
服务端:
<dubbo:protocol server="netty4" />
客户端:
<dubbo:consumer client="netty4" />
这样基于扩展点自适应机制客户端和服务端之间的调用会通过Netty 4框架来建立连接并且服务端采用NIO方式来处理客户端的请求。
再来看下Dubbo的数据传输采用什么协议。Dubbo不仅支持私有的Dubbo协议还支持其他协议比如Hessian、RMI、HTTP、Web Service、Thrift等。下面这张图描述了私有Dubbo协议的协议头约定。
-
(图片来源https://dubbo.incubator.apache.org/docs/zh-cn/dev/sources/images/dubbo_protocol_header.jpg
至于数据序列化和反序列方面Dubbo同样也支持多种序列化格式比如Dubbo、Hession 2.0、JSON、Java、Kryo以及FST等可以通过在XML配置中添加下面的配置项。
例如:
<dubbo:protocol name="dubbo" serialization="kryo"/>
服务监控
服务监控主要包括四个流程:数据采集、数据传输、数据处理和数据展示,其中服务框架的作用是进行埋点数据采集,然后上报给监控系统。
在Dubbo框架中无论是服务提供者还是服务消费者在执行服务调用的时候都会经过Filter调用链拦截来完成一些特定功能比如监控数据埋点就是通过在Filter调用链上装备了MonitorFilter来实现的详细的代码实现你可以参考这里。
服务治理
服务治理手段包括节点管理、负载均衡、服务路由、服务容错等下面这张图给出了Dubbo框架服务治理的具体实现。
-
图片来源http://dubbo.incubator.apache.org/docs/zh-cn/user/sources/images/cluster.jpg
图中的Invoker是对服务提供者节点的抽象Invoker封装了服务提供者的地址以及接口信息。
节点管理Directory负责从注册中心获取服务节点列表并封装成多个Invoker可以把它看成“List” ,它的值可能是动态变化的,比如注册中心推送变更时需要更新。
负载均衡LoadBalance负责从多个Invoker中选出某一个用于发起调用选择时可以采用多种负载均衡算法比如Random、RoundRobin、LeastActive等。
服务路由Router负责从多个Invoker中按路由规则选出子集比如读写分离、机房隔离等。
服务容错Cluster将Directory中的多个Invoker伪装成一个Invoker对上层透明伪装过程包含了容错逻辑比如采用Failover策略的话调用失败后会选择另一个Invoker重试请求。
一次服务调用的流程
上面我讲的是Dubbo下每个基本组件的实现方式那么Dubbo框架下一次服务调用的流程是什么样的呢下面结合这张图我来给你详细讲解一下。
-
(图片来源https://dubbo.incubator.apache.org/docs/zh-cn/dev/sources/images/dubbo-extension.jpg
首先我来解释微服务架构中各个组件分别对应到上面这张图中是如何实现。
服务发布与引用对应实现是图里的Proxy服务代理层Proxy根据客户端和服务端的接口描述生成接口对应的客户端和服务端的Stub使得客户端调用服务端就像本地调用一样。
服务注册与发现对应实现是图里的Registry注册中心层Registry根据客户端和服务端的接口描述解析成服务的URL格式然后调用注册中心的API完成服务的注册和发现。
服务调用对应实现是Protocol远程调用层Protocol把客户端的本地请求转换成RPC请求。然后通过Transporter层来实现通信Codec层来实现协议封装Serialization层来实现数据序列化和反序列化。
服务监控对应实现层是Filter调用链层通过在Filter调用链层中加入MonitorFilter实现对每一次调用的拦截在调用前后进行埋点数据采集上传给监控系统。
服务治理对应实现层是Cluster层负责服务节点管理、负载均衡、服务路由以及服务容错。
再来看下微服务架构各个组件是如何串联起来组成一个完整的微服务框架的以Dubbo框架下一次服务调用的过程为例先来看下客户端发起调用的过程。
首先根据接口定义通过Proxy层封装好的透明化接口代理发起调用。
然后在通过Registry层封装好的服务发现功能获取所有可用的服务提供者节点列表。
再根据Cluster层的负载均衡算法从可用的服务节点列表中选取一个节点发起服务调用如果调用失败根据Cluster层提供的服务容错手段进行处理。
同时通过Filter层拦截调用实现客户端的监控统计。
最后在Protocol层封装成Dubbo RPC请求发给服务端节点。
这样的话客户端的请求就从一个本地调用转化成一个远程RPC调用经过服务调用框架的处理通过网络传输到达服务端。其中服务调用框架包括通信协议框架Transporter、通信协议Codec、序列化Serialization三层处理。
服务端从网络中接收到请求后的处理过程是这样的:
首先在Protocol层把网络上的请求解析成Dubbo RPC请求。
然后通过Filter拦截调用实现服务端的监控统计。
最后通过Proxy层的处理把Dubbo RPC请求转化为接口的具体实现执行调用。
总结
今天我给你讲述了Dubbo服务化框架每个基本组件的实现方式以及一次Dubbo调用的流程。
对于学习微服务架构来说,最好的方式是去实际搭建一个微服务的框架,甚至去从代码入手做一些二次开发。
你可以按照Dubbo的官方文档去安装并搭建一个服务化框架。如果想深入了解它的实现的话可以下载源码来阅读。
思考题
在以Dubbo为例学习完服务化框架的具体实现后你对其中的实现细节还有什么疑问吗
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,218 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
11 服务发布和引用的实践
在[专栏第4期]我给你讲解了服务发布和引用常见的三种方式Restful API、XML配置以及IDL文件。今天我将以XML配置方式为例给你讲解服务发布和引用的具体实践以及可能会遇到的问题。
首先我们一起来看下XML配置方式服务发布和引用的具体流程是什么样的。
XML配置方式的服务发布和引用流程
1. 服务提供者定义接口
服务提供者发布服务之前首先要定义接口声明接口名、传递参数以及返回值类型然后把接口打包成JAR包发布出去。
比如下面这段代码声明了接口UserLastStatusService包含两个方法getLastStatusId和getLastStatusIds传递参数一个是long值、一个是long数组返回值一个是long值、一个是map。
package com.weibo.api.common.status.service;
public interface UserLastStatusService {
* @param uids
* @return
*/
public long getLastStatusId(long uid);
/**
*
* @param uids
* @return
*/
public Map<Long, Long> getLastStatusIds(long[] uids);
}
2. 服务提供者发布接口
服务提供者发布的接口是通过在服务发布配置文件中定义接口来实现的。
下面我以一个具体的服务发布配置文件user-last-status.xml来给你讲解它定义了要发布的接口userLastStatusLocalService对外暴露的协议是Motan协议端口是8882。并且针对两个方法getLastStatusId和getLastStatusIds通过requestTimeout=“300”单独定义了超时时间是300ms通过retries=“0”单独定义了调用失败后重试次数为0也就是不重试。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
">
<motan:service ref="userLastStatusLocalService"
requestTimeout="50" retries="2" interface="com.weibo.api.common.status.service.UserLastStatusService"
basicService="serviceBasicConfig" export="motan:8882">
<motan:method name="getLastStatusId" requestTimeout="300"
retries="0" />
<motan:method name="getLastStatusIds" requestTimeout="300"
retries="0" />
</motan:service>
</beans>
然后服务发布者在进程启动的时候会加载配置文件user-last-status.xml把接口对外暴露出去。
3. 服务消费者引用接口
服务消费者引用接口是通过在服务引用配置文件中定义要引用的接口并把包含接口定义的JAR包引入到代码依赖中。
下面我再以一个具体的服务引用配置文件user-last-status-client.xml来给你讲解它定义服务消费者引用了接口commonUserLastStatusService接口通信协议是Motan。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
">
<motan:protocol name="motan" default="true" loadbalance="${service.loadbalance.name}" />
<motan:basicReferer id="userLastStatusServiceClientBasicConfig"
protocol="motan" />
<!-- 导出接口 -->
<motan:referer id="commonUserLastStatusService" interface="com.weibo.api.common.status.service.UserLastStatusService"
basicReferer="userLastStatusServiceClientBasicConfig" />
</beans>
然后服务消费者在进程启动时会加载配置文件user-last-status-client.xml来完成服务引用。
上面所讲的服务发布和引用流程看似比较简单,但在实际使用过程中,还是有很多坑的,比如在实际项目中经常会遇到这个问题:一个服务包含了多个接口,可能有上行接口也可能有下行接口,每个接口都有超时控制以及是否重试等配置,如果有多个服务消费者引用这个服务,是不是每个服务消费者都必须在服务引用配置文件中定义?
你可以先思考一下这个问题,联系自己的实践经验,是否有理想的解决方案呢?
服务发布和引用的那些坑
根据我的项目经验,在一个服务被多个服务消费者引用的情况下,由于业务经验的参差不齐,可能不同的服务消费者对服务的认知水平不一,比如某个服务可能调用超时了,最好可以重试来提供调用成功率。但可能有的服务消费者会忽视这一点,并没有在服务引用配置文件中配置接口调用超时重试的次数,因此最好是可以在服务发布的配置文件中预定义好类似超时重试次数,即使服务消费者没有在服务引用配置文件中定义,也能继承服务提供者的定义。这就是下面要讲的服务发布预定义配置。
1. 服务发布预定义配置
以下面的服务发布配置文件server.xml为例它提供了一个服务contentSliceRPCService并且明确了其中三个方法的调用超时时间为500ms以及超时重试次数为3。
<motan:service ref="contentSliceRPCService" interface="cn.sina.api.data.service.ContentSliceRPCService"
basicService="serviceBasicConfig" export="motan:8882" >
<motan:method name="saveContent" requestTimeout="500"
retries="3" />
<motan:method name="deleteContent" requestTimeout="500"
retries="3" />
<motan:method name="updateContent" requestTimeout="500"
retries="3" />
</motan:service>
假设服务引用的配置文件client.xml的内容如下那么服务消费者就会默认继承服务发布配置文件中设置的方法调用的超时时间以及超时重试次数。
<motan:referer id="contentSliceRPCService" interface="cn.sina.api.data.service.ContentSliceRPCService" basicReferer="contentSliceClientBasicConfig" >
</motan:referer>
通过服务发布预定义配置可以解决多个服务消费者引用服务可能带来的配置复杂的问题,这样是不是最优的解决方案呢?
实际上我还遇到过另外一种极端情况,一个服务提供者发布的服务有上百个方法,并且每个方法都有各自的超时时间、重试次数等信息。服务消费者引用服务时,完全继承了服务发布预定义的各项配置。这种情况下,服务提供者所发布服务的详细配置信息都需要存储在注册中心中,这样服务消费者才能在实际引用时从服务发布预定义配置中继承各种配置。
这里就存在一种风险当服务提供者发生节点变更尤其是在网络频繁抖动的情况下所有的服务消费者都会从注册中心拉取最新的服务节点信息就包括了服务发布配置中预定的各项接口信息这个信息不加限制的话可能达到1M以上如果同时有上百个服务消费者从注册中心拉取服务节点信息在注册中心机器部署为百兆带宽的情况下很有可能会导致网络带宽打满的情况发生。
面对这种情况,最好的办法是把服务发布端的详细服务配置信息转移到服务引用端,这样的话注册中心中就不需要存储服务提供者发布的详细服务配置信息了。这就是下面要讲的服务引用定义配置。
2. 服务引用定义配置
以下面的服务发布配置文件为例它详细定义了服务userInfoService的各个方法的配置信息比如超时时间和重试次数等。
<motan:service ref="userInfoService" requestTimeout="50" retries="2" interface="cn.sina.api.user.service.UserInfoService" basicService="serviceBasicConfig">
<motan:method name="addUserInfo" requestTimeout="300" retries="0"/>
<motan:method name="updateUserPortrait" requestTimeout="300" retries="0"/>
<motan:method name="modifyUserInfo" requestTimeout="300" retries="0"/>
<motan:method name="addUserTags" requestTimeout="300" retries="0"/>
<motan:method name="delUserTags" requestTimeout="300" retries="0"/>
<motan:method name="processUserCacheByNewMyTriggerQ" requestTimeout="300" retries="0"/>
<motan:method name="modifyObjectUserInfo" requestTimeout="300" retries="0"/>
<motan:method name="addObjectUserInfo" requestTimeout="300" retries="0"/>
<motan:method name="updateObjectUserPortrait" requestTimeout="300" retries="0"/>
<motan:method name="updateObjectManager" requestTimeout="300" retries="0"/>
<motan:method name="add" requestTimeout="300" retries="0"/>
<motan:method name="deleteObjectManager" requestTimeout="300" retries="0"/>
<motan:method name="getUserAttr" requestTimeout="300" retries="1" />
<motan:method name="getUserAttrList" requestTimeout="300" retries="1" />
<motan:method name="getAllUserAttr" requestTimeout="300" retries="1" />
<motan:method name="getUserAttr2" requestTimeout="300" retries="1" />
</motan:service>
可以像下面一样把服务userInfoService的详细配置信息转移到服务引用配置文件中。
<motan:referer id="userInfoService" interface="cn.sina.api.user.service.UserInfoService" basicReferer="userClientBasicConfig">
<motan:method name="addUserInfo" requestTimeout="300" retries="0"/>
<motan:method name="updateUserPortrait" requestTimeout="300" retries="0"/>
<motan:method name="modifyUserInfo" requestTimeout="300" retries="0"/>
<motan:method name="addUserTags" requestTimeout="300" retries="0"/>
<motan:method name="delUserTags" requestTimeout="300" retries="0"/>
<motan:method name="processUserCacheByNewMyTriggerQ" requestTimeout="300" retries="0"/>
<motan:method name="modifyObjectUserInfo" requestTimeout="300" retries="0"/>
<motan:method name="addObjectUserInfo" requestTimeout="300" retries="0"/>
<motan:method name="updateObjectUserPortrait" requestTimeout="300" retries="0"/>
<motan:method name="updateObjectManager" requestTimeout="300" retries="0"/>
<motan:method name="add" requestTimeout="300" retries="0"/>
<motan:method name="deleteObjectManager" requestTimeout="300" retries="0"/>
<motan:method name="getUserAttr" requestTimeout="300" retries="1" />
<motan:method name="getUserAttrList" requestTimeout="300" retries="1" />
<motan:method name="getAllUserAttr" requestTimeout="300" retries="1" />
<motan:method name="getUserAttr2" requestTimeout="300" retries="1" />
</motan:referer>
这样的话,服务发布配置文件可以简化为下面这段代码,是不是信息精简了许多。
<motan:service ref="userInfoService" requestTimeout="50" retries="2" interface="cn.sina.api.user.service.UserInfoService" basicService="serviceBasicConfig">
</motan:service>
在进行类似的服务详细信息配置,由服务发布配置文件迁移到服务引用配置文件的过程时,尤其要注意迁移步骤问题,这就是接下来我要给你讲的服务配置升级问题。
3. 服务配置升级
实际项目中,我就经历过一次服务配置升级的过程。由于引用服务的服务消费者众多,并且涉及多个部门,升级步骤就显得异常重要,通常可以按照下面步骤操作。
各个服务消费者在服务引用配置文件中添加服务详细信息。
服务提供者升级两台服务器,在服务发布配置文件中删除服务详细信息,并观察是否所有的服务消费者引用时都包含服务详细信息。
如果都包含,说明所有服务消费者均完成升级,那么服务提供者就可以删除服务发布配置中的服务详细信息。
如果有不包含服务详细信息的服务消费者,排查出相应的业务方进行升级,直至所有业务方完成升级。
总结
今天我给你介绍了XML配置方式的服务发布和引用的具体流程简单来说就是服务提供者定义好接口并且在服务发布配置文件中配置要发布的接口名在进程启动时加载服务发布配置文件就可以对外提供服务了。而服务消费者通过在服务引用配置文件中定义相同的接口名并且在服务引用配置文件中配置要引用的接口名在进程启动时加载服务引用配置文件就可以引用服务了。
在业务具体实践过程中可能会遇到引用服务的服务消费者众多,对业务的敏感度参差不齐的问题,所以在服务发布的时候,最好预定义好接口的各种配置。在服务规模不大,业务比较简单的时候,这样做比较合适。但是对于复杂业务,虽然服务发布时预定义好接口的各种配置,但在引用的服务消费者众多且同时访问的时候,可能会引起网络风暴。这种情况下,比较保险的方式是,把接口的各种配置放在服务引用配置文件里。
在进行服务配置升级过程时,要考虑好步骤,在所有服务消费者完成升级之前,服务提供者还不能把服务的详细信息去掉,否则可能会导致没有升级的服务消费者引用异常。
思考题
如果你在实际项目中采用过XML配置的服务发布和应用方式是否还遇到过其他问题你是如何解决的呢
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,163 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
12 如何将注册中心落地?
专栏第5期我给你讲了服务注册和发现的原理这里面的核心是服务提供者、服务消费者和注册中心这三个概念以及它们之间的交互关系。你可以先回顾一下这几个关键的知识点如果有不清楚的地方建议你先返回[第5期]复习一下,再开始今天的学习。
掌握了服务注册和发现的原理之后,我们就需要考虑如何把注册中心落地实现。结合前面所讲的服务注册与发现的流程,在落地注册中心的过程中,我们需要解决一系列的问题,包括如何存储服务信息、如何注册节点、如何反注册、如何查询节点信息以及如何订阅服务变更等。这些问题你都知道如何解决吗?如果还没答案,没关系,下面我来给你一一讲解。
注册中心如何存储服务信息
注册中心既然是用来存储服务信息的,那么服务信息都包含哪些内容呢?
根据我的实践经验服务信息除了包含节点信息IP和端口号以外还包含其他一些信息比如请求失败时重试的次数、请求结果是否压缩等信息。因此服务信息通常用JSON字符串来存储包含多个字段每个字段代表不同的含义。
除此之外,服务一般会分成多个不同的分组,每个分组的目的不同。一般来说有下面几种分组方式。
核心与非核心,从业务的核心程度来分。
机房,从机房的维度来分。
线上环境与测试环境,从业务场景维度来区分。
所以注册中心存储的服务信息一般包含三部分内容:分组、服务名以及节点信息,节点信息又包括节点地址和节点其他信息。从注册中心中获取的信息结构大致如下图所示。
具体存储的时候,一般是按照“服务-分组-节点信息”三层结构来存储可以用下图来描述。Service代表服务的具体分组Cluster代表服务的接口名节点信息用KV存储。
搞清楚了注册中心存储服务信息的原理后,再来看下注册中心具体是如何工作的,包括四个流程。
服务提供者注册流程。
服务提供者反注册流程。
服务消费者查询流程。
服务消费者订阅变更流程。
接下来,我来给你具体讲解上面四个流程的实现方式。
注册中心是如何工作的
1. 如何注册节点
知道了服务的节点信息如何存储之后,服务注册流程是怎么样的呢?可以用下面这张流程图来描述。
根据我的经验,服务注册流程主要有下面几个步骤:
首先查看要注册的节点是否在白名单内?如果不在就抛出异常,在的话继续下一步。
其次要查看注册的Cluster服务的接口名是否存在如果不存在就抛出异常存在的话继续下一步。
然后要检查Service服务的分组是否存在如果不存在则抛出异常存在的话继续下一步。
最后将节点信息添加到对应的Service和Cluster下面的存储中。
2. 如何反注册
再来看下服务提供者节点反注册的流程,可以用下面这张流程图来描述。
根据我的经验,节点反注册流程主要包含下面几个步骤:
查看Service服务的分组是否存在不存在就抛出异常存在就继续下一步。
查看Cluster服务的接口名是否存在不存在就抛出异常存在就继续下一步。
删除存储中Service和Cluster下对应的节点信息。
更新Cluster的sign值。
3. 如何查询节点信息
关于服务消费者是如何从注册中心查询服务提供者的节点信息,可以用下面这张流程图来描述。
服务消费者查询节点信息主要分为下面几个步骤:
首先从localcache本机内存中查找如果没有就继续下一步。这里为什么服务消费者要把服务信息存在本机内存呢主要是因为服务节点信息并不总是时刻变化的并不需要每一次服务调用都要调用注册中心获取最新的节点信息只需要在本机内存中保留最新的服务提供者的节点列表就可以。
接着从snapshot本地快照中查找如果没有就继续下一步。这里为什么服务消费者要在本地磁盘存储一份服务提供者的节点信息的快照呢这是因为服务消费者同注册中心之间的网络不一定总是可靠的服务消费者重启时本机内存中还不存在服务提供者的节点信息如果此时调用注册中心失败那么服务消费者就拿不到服务节点信息了也就没法调用了。本地快照就是为了防止这种情况的发生即使服务消费者重启后请求注册中心失败依然可以读取本地快照获取到服务节点信息。
4. 如何订阅服务变更
最后看下,服务消费者如何订阅服务提供者的变更信息呢?可以用下面这张流程图来描述。
主要分为下面几个步骤:
服务消费者从注册中心获取了服务的信息后就订阅了服务的变化会在本地保留Cluster的sign值。
服务消费者每隔一段时间调用getSign()函数从注册中心获取服务端该Cluster的sign值并与本地保留的sign值做对比如果不一致就从服务端拉取新的节点信息并更新localcache和snapshot。
这里小结一下,以上就是服务注册和反注册、服务查询和服务订阅变更的基本流程。除此之外,我再给你讲下我在实际项目实践中,实现服务注册与发现时遇到的几个问题,希望能给你帮助。
注册与发现的几个问题
1. 多注册中心
理论上对于一个服务消费者来说,同一个注册中心交互是最简单的。但是不可避免的是,服务消费者可能订阅了多个服务,多个服务可能是由多个业务部门提供的,而且每个业务部门都有自己的注册中心,提供的服务只在自己的注册中心里有记录。这样的话,就要求服务消费者要具备在启动时,能够从多个注册中心订阅服务的能力。
根据我的经验,还有一种情况是,一个服务提供者提供了某个服务,可能作为静态服务对外提供,有可能又作为动态服务对外提供,这两个服务部署在不同的注册中心,所以要求服务提供者在启动的时候,要能够同时向多个注册中心注册服务。
也就是说,对于服务消费者来说,要能够同时从多个注册中心订阅服务;对于服务提供者来说,要能够同时向多个注册中心注册服务。
2. 并行订阅服务
通常一个服务消费者订阅了不止一个服务,在我经历的一个项目中,一个服务消费者订阅了几十个不同的服务,每个服务都有自己的方法列表以及节点列表。服务消费者在服务启动时,会加载订阅的服务配置,调用注册中心的订阅接口,获取每个服务的节点列表并初始化连接。
最开始我们采用了串行订阅的方式,每订阅一个服务,服务消费者调用一次注册中心的订阅接口,获取这个服务的节点列表并初始化连接,总共需要执行几十次这样的过程。在某些服务节点的初始化连接过程中,出现连接超时的情况,后续所有的服务节点的初始化连接都需要等待它完成,导致服务消费者启动变慢,最后耗费了将近五分钟时间来完成所有服务节点的初始化连接过程。
后来我们改成了并行订阅的方式每订阅一个服务就单独用一个线程来处理这样的话即使遇到个别服务节点连接超时其他服务节点的初始化连接也不受影响最慢也就是这个服务节点的初始化连接耗费的时间最终所有服务节点的初始化连接耗时控制在了30秒以内。
3. 批量反注册服务
通常一个服务提供者节点提供不止一个服务,所以注册和反注册都需要多次调用注册中心。在与注册中心的多次交互中,可能由于网络抖动、注册中心集群异常等原因,导致个别调用失败。对于注册中心来说,偶发的注册调用失败对服务调用基本没有影响,其结果顶多就是某一个服务少了一个可用的节点。但偶发的反注册调用失败会导致不可用的节点残留在注册中心中,变成“僵尸节点”,但服务消费者端还会把它当成“活节点”,继续发起调用,最终导致调用失败。
以前我们的业务中经常遇到这个问题,需要定时去清理注册中心中的“僵尸节点”。后来我们通过优化反注册逻辑,对于下线机器、节点销毁的场景,通过调用注册中心提供的批量反注册接口,一次调用就可以把该节点上提供的所有服务同时反注册掉,从而避免了“僵尸节点”的出现。
4. 服务变更信息增量更新
服务消费者端启动时除了会查询订阅服务的可用节点列表做初始化连接还会订阅服务的变更每隔一段时间从注册中心获取最新的服务节点信息标记sign并与本地保存的sign值作比对如果不一样就会调用注册中心获取最新的服务节点信息。
一般情况下,按照这个过程是没问题的,但是在网络频繁抖动时,服务提供者上报给注册中心的心跳可能会一会儿失败一会儿成功,这时候注册中心就会频繁更新服务的可用节点信息,导致服务消费者频繁从注册中心拉取最新的服务可用节点信息,严重时可能产生网络风暴,导致注册中心带宽被打满。
为了减少服务消费者从注册中心中拉取的服务可用节点信息的数据量,这个时候可以通过增量更新的方式,注册中心只返回变化的那部分节点信息,尤其在只有少数节点信息变更时,此举可以大大减少服务消费者从注册中心拉取的数据量,从而最大程度避免产生网络风暴。
总结
今天我给你讲解了在注册中心实际使用过程中,服务注册、服务反注册、服务订阅和服务变更的实现方式,并列举了几个我在服务注册与发现的过程中遇到的典型问题。
而针对这些异常情况,我都给出了对应的解决方案,这些方案都是经过实际业务验证的,对于大部分中小团队在应用场景面临的问题,应该足以应对。
思考题
你的团队在使用注册中心时,是否有遇到过上面这些问题呢?我给出的解决方案是否可以解决你们的问题呢?
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,126 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
13 开源服务注册中心如何选型?
上一期我给你讲了服务注册中心的落地实践,以及在实际应用中可能会遇到的问题和对应的解决方案。关于注册中心,如果你的团队有足够的人才和技术储备,可以选择自己研发注册中心。但对于大多数中小规模团队来说,我的建议是最好使用业界开源的、应用比较成熟的注册中心解决方案,把精力投入到业务架构的改造中,不要自己造轮子。
当下主流的服务注册与发现的解决方案,主要有两种:
应用内注册与发现注册中心提供服务端和客户端的SDK业务应用通过引入注册中心提供的SDK通过SDK与注册中心交互来实现服务的注册和发现。
应用外注册与发现业务应用本身不需要通过SDK与注册中心打交道而是通过其他方式与注册中心交互间接完成服务注册与发现。
下面我会用两个业界使用比较成熟的注册中心开源实现,来讲解下应用内和应用外两种解决方案的不同之处。
两种典型的注册中心实现
1. 应用内
采用应用内注册与发现的方式最典型的案例要属Netflix开源的Eureka官方架构图如下。
-
https://github.com/Netflix/eureka/raw/master/images/eureka_architecture.png
对着这张图我来介绍下Eureka的架构它主要由三个重要的组件组成
Eureka Server注册中心的服务端实现了服务信息注册、存储以及查询等功能。
服务端的Eureka Client集成在服务端的注册中心SDK服务提供者通过调用SDK实现服务注册、反注册等功能。
客户端的Eureka Client集成在客户端的注册中心SDK服务消费者通过调用SDK实现服务订阅、服务更新等功能。
2. 应用外
采用应用外方式实现服务注册和发现最典型的案例是开源注册中心Consul它的架构图如下。
-
https://technologyconversations.files.wordpress.com/2015/09/etcd-registrator-confd2.png
通过这张架构图可以看出来使用Consul实现应用外服务注册和发现主要依靠三个重要的组件
Consul注册中心的服务端实现服务注册信息的存储并提供注册和发现服务。
Registrator一个开源的第三方服务管理器项目它通过监听服务部署的Docker实例是否存活来负责服务提供者的注册和销毁。
Consul Template定时从注册中心服务端获取最新的服务提供者节点列表并刷新LB配置比如Nginx的upstream这样服务消费者就通过访问Nginx就可以获取最新的服务提供者信息。
对比小结一下这两种解决方案的不同之处在于应用场景应用内的解决方案一般适用于服务提供者和服务消费者同属于一个技术体系应用外的解决方案一般适合服务提供者和服务消费者采用了不同技术体系的业务场景比如服务提供者提供的是C++服务而服务消费者是一个Java应用这时候采用应用外的解决方案就不依赖于具体一个技术体系。同时对于容器化后的云应用来说一般不适合采用应用内SDK的解决方案因为这样会侵入业务而应用外的解决方案正好能够解决这个问题。
注册中心选型要考虑的两个问题
在选择注册中心解决方案的时候,除了要考虑是采用应用内注册还是应用外注册的方式以外,还有两个最值得关注的问题,一个是高可用性,一个是数据一致性,下面我来给你详细解释下为什么。
1. 高可用性
注册中心作为服务提供者和服务消费者之间沟通的纽带,它的高可用性十分重要。试想,如果注册中心不可用了,那么服务提供者就无法对外暴露自己的服务,而服务消费者也无法知道自己想要调用的服务的具体地址,后果将不堪设想。
根据我过往的实践经验,实现高可用性的方法主要有两种:
集群部署,顾名思义就是通过部署多个实例组成集群来保证高可用性,这样的话即使有部分机器宕机,将访问迁移到正常的机器上就可以保证服务的正常访问。
多IDC部署就是部署在不止一个机房这样能保证即使一个机房因为断电或者光缆被挖断等不可抗力因素不可用时仍然可以通过把请求迁移到其他机房来保证服务的正常访问。
我们以Consul为例来看看它是如何通过这两种方法来保证注册中心的高可用性。
从下面的官方架构图中你可以看到一方面在每个数据中心DATACENTER内都有多个注册中心Server节点可供访问另一方面还可以部署在多个数据中心来保证多机房高可用性。
-
https://www.consul.io/assets/images/consul-arch-420ce04a.png
2. 数据一致性
为了保证注册中心的高可用性,注册中心的部署往往都采用集群部署,并且还通常部署在不止一个数据中心,这样的话就会引出另一个问题,多个数据中心之间如何保证数据一致?如何确保访问数据中心中任何一台机器都能得到正确的数据?
这里就涉及分布式系统中著名的CAP理论即同时满足一致性、可用性、分区容错性这三者是不可能的其中CConsistency代表一致性AAvailability代表可用性PPartition Tolerance代表分区容错性。
为什么说CAP三者不能被同时满足的呢
你可以想象在一个分布式系统里面,包含了多个节点,节点之间通过网络连通在一起。正常情况下,通过网络,从一个节点可以访问任何别的节点上的数据。
但是有可能出现网络故障,导致整个网络被分成了互不连通的区域,这就叫作分区。一旦出现分区,那么一个区域内的节点就没法访问其他节点上的数据了,最好的办法是把数据复制到其他区域内的节点,这样即使出现分区,也能访问任意区域内节点上的数据,这就是分区容错性。
但是把数据复制到多个节点就可能出现数据不一致的情况,这就是一致性。要保证一致,就必须等待所有节点上的数据都更新成功才可用,这就是可用性。
总的来说,就是数据节点越多,分区容错性越高,但数据一致性越难保证。为了保证数据一致性,又会带来可用性的问题。
而注册中心一般采用分布式集群部署也面临着CAP的问题根据CAP不能同时满足所以不同的注册中心解决方案选择的方向也就不同大致可分为两种。
CP型注册中心牺牲可用性来保证数据强一致性最典型的例子就是ZooKeeperetcdConsul了。ZooKeeper集群内只有一个Leader而且在Leader无法使用的时候通过Paxos算法选举出一个新的Leader。这个Leader的目的就是保证写信息的时候只向这个Leader写入Leader会同步信息到Followers这个过程就可以保证数据的强一致性。但如果多个ZooKeeper之间网络出现问题造成出现多个Leader发生脑裂的话注册中心就不可用了。而etcd和Consul集群内都是通过raft协议来保证强一致性如果出现脑裂的话 注册中心也不可用。
AP型注册中心牺牲一致性来保证可用性最典型的例子就是Eureka了。对比下ZookeeperEureka不用选举一个Leader每个Eureka服务器单独保存服务注册地址因此有可能出现数据信息不一致的情况。但是当网络出现问题的时候每台服务器都可以完成独立的服务。
而对于注册中心来说最主要的功能是服务的注册和发现在网络出现问题的时候可用性的需求要远远高于数据一致性。即使因为数据不一致注册中心内引入了不可用的服务节点也可以通过其他措施来避免比如客户端的快速失败机制等只要实现最终一致性对于注册中心来说就足够了。因此选择AP型注册中心一般更加合适。
总结
总的来说,在选择开源注册中心解决方案的时候,要看业务的具体场景。
如果你的业务体系都采用Java语言的话Netflix开源的Eureka是一个不错的选择并且它作为服务注册与发现解决方案能够最大程度的保证可用性即使出现了网络问题导致不同节点间数据不一致你仍然能够访问Eureka获取数据。
如果你的业务体系语言比较复杂Eureka也提供了Sidecar的解决方案也可以考虑使用Consul它支持了多种语言接入包括Go、Python、PHP、Scala、JavaErlang、Ruby、Node.js、.NET、Perl等。
如果你的业务已经是云原生的应用可以考虑使用Consul搭配Registrator和Consul Template来实现应用外的服务注册与发现。
思考题
针对你的业务场景,如果要选择一种开源注册中心实现的话,你觉得哪种方案更适合?
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,199 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
14 开源RPC框架如何选型
[专栏第6期]我给你讲解了RPC远程调用的原理简单回顾一下一个完整的RPC框架主要有三部分组成通信框架、通信协议、序列化和反序列化格式。根据我的经验想要开发一个完整的RPC框架并且应用到线上生产环境至少需要投入三个人力半年以上的时间。这对于大部分中小团队来说人力成本和时间成本都是不可接受的所以我建议还是选择开源的RPC框架比较合适。
那么业界应用比较广泛的开源RPC框架有哪些呢
简单划分的话,主要分为两类:一类是跟某种特定语言平台绑定的,另一类是与语言无关即跨语言平台的。
跟语言平台绑定的开源RPC框架主要有下面几种。
Dubbo国内最早开源的RPC框架由阿里巴巴公司开发并于2011年末对外开源仅支持Java语言。
Motan微博内部使用的RPC框架于2016年对外开源仅支持Java语言。
Tars腾讯内部使用的RPC框架于2017年对外开源仅支持C++语言。
Spring Cloud国外Pivotal公司2014年对外开源的RPC框架仅支持Java语言最近几年生态发展得比较好是比较火的RPC框架。
而跨语言平台的开源RPC框架主要有以下几种。
gRPCGoogle于2015年对外开源的跨语言RPC框架支持常用的C++、Java、Python、Go、Ruby、PHP、Android Java、Objective-C等多种语言。
Thrift最初是由Facebook开发的内部系统跨语言的RPC框架2007年贡献给了Apache基金成为Apache开源项目之一支持常用的C++、Java、PHP、Python、Ruby、Erlang等多种语言。
所以很明显如果你的业务场景仅仅局限于一种语言的话可以选择跟语言绑定的RPC框架中的一种如果涉及多个语言平台之间的相互调用就应该选择跨语言平台的RPC框架。
针对每一种RPC框架它们具体有何区别该如何选择呢接下来我就从每个框架的实现角度来具体给你讲解。当你知道了他们的具体实现也就能知道他们的优缺点以及适用场景了。
限定语言平台的开源RPC框架
1. Dubbo
先来聊聊DubboDubbo可以说是国内开源最早的RPC框架了目前只支持Java语言它的架构可以用下面这张图展示。
-
图片来源https://dubbo.incubator.apache.org/docs/zh-cn/dev/sources/images/dubbo-relation.jpg
从图中你能看到Dubbo的架构主要包含四个角色其中Consumer是服务消费者Provider是服务提供者Registry是注册中心Monitor是监控系统。
具体的交互流程是Consumer一端通过注册中心获取到Provider节点后通过Dubbo的客户端SDK与Provider建立连接并发起调用。Provider一端通过Dubbo的服务端SDK接收到Consumer的请求处理后再把结果返回给Consumer。
可以看出服务消费者和服务提供者都需要引入Dubbo的SDK才来完成RPC调用因为Dubbo本身是采用Java语言实现的所以要求服务消费者和服务提供者也都必须采用Java语言实现才可以应用。
我们再来看下Dubbo的调用框架是如何实现的。
通信框架方面Dubbo默认采用了Netty作为通信框架。
通信协议方面Dubbo除了支持私有的Dubbo协议外还支持RMI协议、Hession协议、HTTP协议、Thrift协议等。
序列化格式方面Dubbo支持多种序列化格式比如Dubbo、Hession、JSON、Kryo、FST等。
2. Motan
Motan是国内另外一个比较有名的开源的RPC框架同样也只支持Java语言实现它的架构可以用下面这张图描述。
-
图片来源https://github.com/weibocom/motan/wiki/media/14612352579675.jpg
Motan与Dubbo的架构类似都需要在Client端服务消费者和Server端服务提供者引入SDK其中Motan框架主要包含下面几个功能模块。
register用来和注册中心交互包括注册服务、订阅服务、服务变更通知、服务心跳发送等功能。Server端会在系统初始化时通过register模块注册服务Client端会在系统初始化时通过register模块订阅到具体提供服务的Server列表当Server列表发生变更时也由register模块通知Client。
protocol用来进行RPC服务的描述和RPC服务的配置管理这一层还可以添加不同功能的filter用来完成统计、并发限制等功能。
serialize将RPC请求中的参数、结果等对象进行序列化与反序列化即进行对象与字节流的互相转换默认使用对Java更友好的Hessian 2进行序列化。
transport用来进行远程通信默认使用Netty NIO的TCP长链接方式。
clusterClient端使用的模块cluster是一组可用的Server在逻辑上的封装包含若干可以提供RPC服务的Server实际请求时会根据不同的高可用与负载均衡策略选择一个可用的Server发起远程调用。
3. Tars
Tars是腾讯根据内部多年使用微服务架构的实践总结而成的开源项目仅支持C++语言,它的架构图如下。
-
图片来源https://github.com/TarsCloud/Tars/blob/master/docs/images/tars_jiaohu.png
Tars的架构交互主要包括以下几个流程
服务发布流程在web系统上传server的发布包到patch上传成功后在web上提交发布server请求由registry服务传达到node然后node拉取server的发布包到本地拉起server服务。
管理命令流程web系统上的可以提交管理server服务命令请求由registry服务传达到node服务然后由node向server发送管理命令。
心跳上报流程server服务运行后会定期上报心跳到nodenode然后把服务心跳信息上报到registry服务由registry进行统一管理。
信息上报流程server服务运行后会定期上报统计信息到stat打印远程日志到log定期上报属性信息到prop、上报异常信息到notify、从config拉取服务配置信息。
client访问server流程client可以通过server的对象名Obj间接访问serverclient会从registry上拉取server的路由信息如IP、Port信息然后根据具体的业务特性同步或者异步TCP或者UDP方式访问server当然client也可以通过IP/Port直接访问server
4. Spring Cloud
Spring Cloud是为了解决微服务架构中服务治理而提供的一系列功能的开发框架它是完全基于Spring Boot进行开发的Spring Cloud利用Spring Boot特性整合了开源行业中优秀的组件整体对外提供了一套在微服务架构中服务治理的解决方案。因为Spring Boot是用Java语言编写的所以目前Spring Cloud也只支持Java语言平台它的架构图可以用下面这张图来描述。
-
图片来源http://www.hyhblog.cn/wp-content/uploads/2018/07/Arch-Design-Spring-Cloud-1024x576.png
由此可见Spring Cloud微服务架构是由多个组件一起组成的各个组件的交互流程如下。
请求统一通过API网关Zuul来访问内部服务先经过Token进行安全认证。
通过安全认证后网关Zuul从注册中心Eureka获取可用服务节点列表。
从可用服务节点中选取一个可用节点,然后把请求分发到这个节点。
整个请求过程中Hystrix组件负责处理服务超时熔断Turbine组件负责监控服务间的调用和熔断相关指标Sleuth组件负责调用链监控ELK负责日志分析。
5. 对比选型
介绍完这4种限定语言的开源RPC框架后我们该如何选择呢
很显然如果你的语言平台是C++那么只能选择Tars而如果是Java的话可以选择Dubbo、Motan或者Spring Cloud。这时你又要问了它们三个又该如何抉择呢
仔细分析可以看出Spring Cloud不仅提供了基本的RPC框架功能还提供了服务注册组件、配置中心组件、负载均衡组件、断路器组件、分布式消息追踪组件等一系列组件也难怪被技术圈的人称之为“Spring Cloud全家桶”。如果你不想自己实现以上这些功能那么Spring Cloud基本可以满足你的全部需求。而Dubbo、Motan基本上只提供了最基础的RPC框架的功能其他微服务组件都需要自己去实现。
不过由于Spring Cloud的RPC通信采用了HTTP协议相比Dubbo和Motan所采用的私有协议来说在高并发的通信场景下性能相对要差一些所以对性能有苛刻要求的情况下可以考虑Dubbo和Motan。
跨语言平台的开源RPC框架
1. gRPC
先来看下gRPC它的原理是通过IDLInterface Definition Language文件定义服务接口的参数和返回值类型然后通过代码生成程序生成服务端和客户端的具体实现代码这样在gRPC里客户端应用可以像调用本地对象一样调用另一台服务器上对应的方法。
-
图片来源https://grpc.io/img/landing-2.svg
它的主要特性包括三个方面。
通信协议采用了HTTP/2因为HTTP/2提供了连接复用、双向流、服务器推送、请求优先级、首部压缩等机制所以在通信过程中可以节省带宽、降低TCP连接次数、节省CPU尤其对于移动端应用来说可以帮助延长电池寿命。
IDL使用了ProtoBufProtoBuf是由Google开发的一种数据序列化协议它的压缩和传输效率极高语法也简单所以被广泛应用在数据存储和通信协议上。
多语言支持,能够基于多种语言自动生成对应语言的客户端和服务端的代码。
2. Thrift
再来看下ThriftThrift是一种轻量级的跨语言RPC通信方案支持多达25种编程语言。为了支持多种语言跟gRPC一样Thrift也有一套自己的接口定义语言IDL可以通过代码生成器生成各种编程语言的Client端和Server端的SDK代码这样就保证了不同语言之间可以相互通信。它的架构图可以用下图来描述。
-
图片来源https://github.com/apache/thrift/raw/master/doc/images/thrift-layers.png
从这张图上可以看出Thrift RPC框架的特性。
支持多种序列化格式如Binary、Compact、JSON、Multiplexed等。
支持多种通信方式如Socket、Framed、File、Memory、zlib等。
服务端支持多种处理方式如Simple 、Thread Pool、Non-Blocking等。
3. 对比选型
那么涉及跨语言的服务调用场景到底该选择gRPC还是Thrift呢
从成熟度上来讲Thrift因为诞生的时间要早于gRPC所以使用的范围要高于gRPC在HBase、Hadoop、Scribe、Cassandra等许多开源组件中都得到了广泛地应用。而且Thrift支持多达25种语言这要比gRPC支持的语言更多所以如果遇到gRPC不支持的语言场景下选择Thrift更合适。
但gRPC作为后起之秀因为采用了HTTP/2作为通信协议、ProtoBuf作为数据序列化格式在移动端设备的应用以及对传输带宽比较敏感的场景下具有很大的优势而且开发文档丰富根据ProtoBuf文件生成的代码要比Thrift更简洁一些从使用难易程度上更占优势所以如果使用的语言平台gRPC支持的话建议还是采用gRPC比较好。
总结
以上就是我对几种使用最广泛的开源RPC框架的选型建议也是基于它们目前现状所作出的判断从长远来看支持多语言是RPC框架未来的发展趋势。正是基于此判断各个RPC框架都提供了Sidecar组件来支持多语言平台之间的RPC调用。
Dubbo在去年年底又重启了维护并且宣称要引入Sidecar组件来构建Dubbo Mesh提供多语言支持。
Motan也在去年对外开源了其内部的Sidecar组件Motan-go目前支持PHP、Java语言之间的相互调用。
Spring Cloud也提供了Sidecar组件spring-cloud-netflix-sideca可以让其他语言也可以使用Spring Cloud的组件。
所以未来语言不会成为使用上面这几种RPC框架的约束而gRPC和Thrift虽然支持跨语言的RPC调用但是因为它们只提供了最基本的RPC框架功能缺乏一系列配套的服务化组件和服务治理功能的支撑所以使用它们作为跨语言调用的RPC框架就需要自己考虑注册中心、熔断、限流、监控、分布式追踪等功能的实现不过好在大多数功能都有开源实现可以直接采用。
思考题
同样是支持跨语言的RPC调用你觉得gRPC这类的跨语言服务框架和Motan-go这类的Sidecar方案有什么区别在使用过程中都需要注意什么
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,218 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
15 如何搭建一个可靠的监控系统?
[专栏第7期]我给你讲解了监控系统的实现原理,先来简单回顾一下,一个监控系统的组成主要涉及四个环节:数据收集、数据传输、数据处理和数据展示。不同的监控系统实现方案,在这四个环节所使用的技术方案不同,适合的业务场景也不一样。
目前比较流行的开源监控系统实现方案主要有两种以ELK为代表的集中式日志解决方案以及Graphite、TICK和Prometheus等为代表的时序数据库解决方案。接下来我就以这几个常见的监控系统实现方案谈谈它们的实现原理分别适用于什么场景以及具体该如何做技术选型。
ELK
ELK是Elasticsearch、Logstash、Kibana三个开源软件产品首字母的缩写它们三个通常配合使用所以被称为ELK Stack它的架构可以用下面的图片来描述。
-
图片来源https://cdn-images-1.medium.com/max/1600/1*mwSvtVy_qGz0nTjaYbvwpw.png
这三个软件的功能也各不相同。
Logstash负责数据收集和传输它支持动态地从各种数据源收集数据并对数据进行过滤、分析、格式化等然后存储到指定的位置。
Elasticsearch负责数据处理它是一个开源分布式搜索和分析引擎具有可伸缩、高可靠和易管理等特点基于Apache Lucene构建能对大容量的数据进行接近实时的存储、搜索和分析操作通常被用作基础搜索引擎。
Kibana负责数据展示也是一个开源和免费的工具通常和Elasticsearch搭配使用对其中的数据进行搜索、分析并且以图表的方式展示。
这种架构因为需要在各个服务器上部署Logstash来从不同的数据源收集数据所以比较消耗CPU和内存资源容易造成服务器性能下降因此后来又在Elasticsearch、Logstash、Kibana之外引入了Beats作为数据收集器。相比于LogstashBeats所占系统的CPU和内存几乎可以忽略不计可以安装在每台服务器上做轻量型代理从成百上千或成千上万台机器向Logstash或者直接向Elasticsearch发送数据。
其中Beats支持多种数据源主要包括
Packetbeat用来收集网络流量数据。
Topbeat用来收集系统、进程的CPU和内存使用情况等数据。
Filebeat用来收集文件数据。
Winlogbeat用来收集Windows事件日志收据。
Beats将收集到的数据发送到Logstash经过Logstash解析、过滤后再将数据发送到Elasticsearch最后由Kibana展示架构就变成下面这张图里描述的了。
-
图片来源https://logz.io/wp-content/uploads/2018/08/image21-1024x328.png
Graphite
Graphite的组成主要包括三部分Carbon、Whisper、Graphite-Web它的架构可以用下图来描述。
Carbon主要作用是接收被监控节点的连接收集各个指标的数据将这些数据写入carbon-cache并最终持久化到Whisper存储文件中去。
Whisper一个简单的时序数据库主要作用是存储时间序列数据可以按照不同的时间粒度来存储数据比如1分钟1个点、5分钟1个点、15分钟1个点三个精度来存储监控数据。
Graphite-Web一个Web App其主要功能绘制报表与展示即数据展示。为了保证Graphite-Web能及时绘制出图形Carbon在将数据写入Whisper存储的同时会在carbon-cache中同时写入一份数据Graphite-Web会先查询carbon-cache如果没有再查询Whisper存储。
-
图片来源https://graphiteapp.org/img/architecture_diagram.png
也就是说Carbon负责数据处理Whisper负责数据存储Graphite-Web负责数据展示可见Graphite自身并不包含数据采集组件但可以接入StatsD等开源数据采集组件来采集数据再传送给Carbon。
其中Carbon对写入的数据格式有一定的要求比如
servers.www01.cpuUsage 42 1286269200
products.snake-oil.salesPerMinute 123 1286269200
[one minute passes]
servers.www01.cpuUsageUser 44 1286269260
products.snake-oil.salesPerMinute 119 1286269260
其中“servers.www01.cpuUsage 42 1286269200”是“key” + 空格分隔符 + “value + 时间戳”的数据格式“servers.www01.cpuUsage”是以“.”分割的key代表具体的路径信息“42”是具体的值“1286269200”是当前的Unix时间戳。
Graphite-Web对外提供了HTTP API可以查询某个key的数据以绘图展示查询方式如下。
http://graphite.example.com/render?target=servers.www01.cpuUsage&
width=500&height=300&from=-24h
这个HTTP请求意思是查询key“servers.www01.cpuUsage”在过去24小时的数据并且要求返回500*300大小的数据图。
除此之外Graphite-Web还支持丰富的函数比如
target=sumSeries(products.*.salesPerMinute)
代表了查询匹配规则“products.*.salesPerMinute”的所有key的数据之和。
TICK
TICK是Telegraf、InfluxDB、Chronograf、Kapacitor四个软件首字母的缩写是由InfluxData开发的一套开源监控工具栈因此也叫作TICK Stack它的架构可以看用下面这张图来描述。
-
图片来源https://2bjee8bvp8y263sjpl3xui1a-wpengine.netdna-ssl.com/wp-content/uploads/Tick-Stack-Complete.png
从这张图可以看出其中Telegraf负责数据收集InfluxDB负责数据存储Chronograf负责数据展示Kapacitor负责数据告警。
这里面InfluxDB对写入的数据格式要求如下。
<measurement>[,<tag-key>=<tag-value>...] <field-key>=<field-value>[,<field2-key>=<field2-value>...] [unix-nano-timestamp]
下面我用一个具体示例来说明它的格式。
cpu,host=serverA,region=us_west value=0.64 1434067467100293230
其中“cpu,host=serverA,region=us_west value=0.64 1434067467100293230”代表了host为serverA、region为us_west的服务器CPU的值是0.64时间戳是1434067467100293230时间精确到nano。
Prometheus
还有一种比较有名的时间序数据库解决方案Prometheus它是一套开源的系统监控报警框架受Google的集群监控系统Borgmon启发由工作在SoundCloud的Google前员工在2012年创建后来作为社区开源项目进行开发并于2015年正式发布2016年正式加入CNCFCloud Native Computing Foundation成为受欢迎程度仅次于Kubernetes的项目它的架构可以用下图来描述。
-
图片来源https://prometheus.io/assets/architecture.png
从这张图可以看出Prometheus主要包含下面几个组件
Prometheus Server用于拉取metrics信息并将数据存储在时间序列数据库。
Jobs/exporters用于暴露已有的第三方服务的metrics给Prometheus Server比如StatsD、Graphite等负责数据收集。
Pushgateway主要用于短期jobs由于这类jobs存在时间短可能在Prometheus Server来拉取metrics信息之前就消失了所以这类的jobs可以直接向Prometheus Server推送它们的metrics信息。
Alertmanager用于数据报警。
Prometheus web UI负责数据展示。
它的工作流程大致是:
Prometheus Server定期从配置好的jobs或者exporters中拉取metrics信息或者接收来自Pushgateway发过来的metrics信息。
Prometheus Server把收集到的metrics信息存储到时间序列数据库中并运行已经定义好的alert.rules向Alertmanager推送警报。
Alertmanager根据配置文件对接收的警报进行处理发出告警。
通过Prometheus web UI进行可视化展示。
Prometheus存储数据也是用的时间序列数据库格式如下。
<metric name>{<label name>=<label value>, …}
比如下面这段代码代表了位于集群cluster 1上节点IP为1.1.1.1端口为80访问路径为“/a”的http请求的总数为100。
http_requests_total{instance="1.1.1.1:80",job="cluster1",location="/a"} 100
讲到这里,四种监控系统的解决方案都已经介绍完了,接下来我们对比一下这四种方案,看看如何选型。
选型对比
我们从监控系统的四个环节来分别对比。
1. 数据收集
ELK是通过在每台服务器上部署Beats代理来采集数据Graphite本身没有收据采集组件需要配合使用开源收据采集组件比如StatsDTICK使用了Telegraf作为数据采集组件Prometheus通过jobs/exporters组件来获取StatsD等采集过来的metrics信息。
2. 数据传输
ELK是Beats采集的数据传输给Logstash经过Logstash清洗后再传输给ElasticsearchGraphite是通过第三方采集组件采集的数据传输给CarbonTICK是Telegraf采集的数据传输给InfluxDB而Prometheus是Prometheus Server隔一段时间定期去从jobs/exporters拉取数据。可见前三种都是采用“推数据”的方式而Prometheus是采取拉数据的方式因此Prometheus的解决方案对服务端的侵入最小不需要在服务端部署数据采集代理。
3. 数据处理
ELK可以对日志的任意字段索引适合多维度的数据查询在存储时间序列数据方面与时间序列数据库相比会有额外的性能和存储开销。除此之外时间序列数据库的几种解决方案都支持多种功能的数据查询处理功能也更强大。
Graphite通过Graphite-Web支持正则表达式匹配、sumSeries求和、alias给监控项重新命名等函数功能同时还支持这些功能的组合比如下面这个表达式的意思是要查询所有匹配路径“stats.open.profile.*.API._comments_flow”的监控项之和并且把监控项重命名为Total QPS。
alias(sumSeries(stats.openapi.profile.*.API._comments_flow.total_count,“Total QPS”)
InfluxDB通过类似SQL语言的InfluxQL能对监控数据进行复杂操作比如查询一分钟CPU的使用率用InfluxDB实现的示例是
SELECT 100 - usage_idel FROM “autogen”.“cpu” WHERE time > now() - 1m and “cpu”=cpu0
Prometheus通过私有的PromQL查询语言如果要和上面InfluxDB实现同样的功能PromQL语句如下看起来更加简洁。
100 - (node_cpu{job=“node”,mode=“idle”}[1m])
4. 数据展示
Graphite、TICK和Prometheus自带的展示功能都比较弱界面也不好看不过好在它们都支持Grafana来做数据展示。Grafana是一个开源的仪表盘工具它支持多种数据源比如Graphite、InfluxDB、Prometheus以及Elasticsearch等。ELK采用了Kibana做数据展示Kibana包含的数据展示功能比较强大但只支持Elasticsearch而且界面展示UI效果不如Grafana美观。
总结
以上几种监控系统实现方式,所采用的技术均为开源的,其中:
ELK的技术栈比较成熟应用范围也比较广除了可用作监控系统外还可以用作日志查询和分析。
Graphite是基于时间序列数据库存储的监控系统并且提供了功能强大的各种聚合函数比如sum、average、top5等可用于监控分析而且对外提供了API也可以接入其他图形化监控系统如Grafana。
TICK的核心在于其时间序列数据库InfluxDB的存储功能强大且支持类似SQL语言的复杂数据处理操作。
Prometheus的独特之处在于它采用了拉数据的方式对业务影响较小同时也采用了时间序列数据库存储而且支持独有的PromQL查询语言功能强大而且简洁。
从对实时性要求角度考虑时间序列数据库的实时性要好于ELK通常可以做到10s级别内的延迟如果对实时性敏感的话建议选择时间序列数据库解决方案。
从使用的灵活性角度考虑几种时间序列数据库的监控处理功能都要比ELK更加丰富使用更灵活也更现代化。
所以如果要搭建一套新的监控系统我建议可以考虑采用Graphite、TICK或者Prometheus其中之一。不过Graphite还需要搭配数据采集系统比如StatsD或者Collectd使用而且界面展示建议使用Grafana接入Graphite的数据源它的效果要比Graphite Web本身提供的界面美观很多。TICK提供了完整的监控系统框架包括从数据采集、数据传输、数据处理再到数据展示不过在数据展示方面同样也建议用Grafana替换掉TICK默认的数据展示组件Chronograf这样展示效果更好。Prometheus因为采用拉数据的方式所以对业务的侵入性最小比较适合Docker封装好的云原生应用比如Kubernetes默认就采用了Prometheus作为监控系统。
思考题
通过我今天的讲解你应该知道了Graphite、TICK以及Prometheus存储监控数据都采用了时间序列数据库它们在存储和性能上有什么不同之处吗
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,181 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
16 如何搭建一套适合你的服务追踪系统?
[专栏第8期]我给你讲了服务追踪系统的原理以及实现,简单回顾一下服务追踪系统的实现,主要包括三个部分。
埋点数据收集,负责在服务端进行埋点,来收集服务调用的上下文数据。
实时数据处理负责对收集到的链路信息按照traceId和spanId进行串联和存储。
数据链路展示,把处理后的服务调用数据,按照调用链的形式展示出来。
如果要自己从0开始实现一个服务追踪系统针对以上三个部分你都必须有相应的解决方案。首先你需要在业务代码的框架层开发调用拦截程序在调用的前后收集相关信息把信息传输给到一个统一的处理中心。然后处理中心需要实时处理收集到链路信息并按照traceId和spanId进行串联处理完以后再存到合适的存储中。最后还要能把存储中存储的信息以调用链路图或者调用拓扑图的形式对外展示。
可以想象这个技术难度以及开发工作量都不小,对于大部分中小业务团队来说,都十分具有挑战。不过幸运的是,业界已经有不少开源的服务追踪系统实现,并且应用范围也已经十分广泛,对大部分的中小业务团队来说,足以满足对服务追踪系统的需求。
业界比较有名的服务追踪系统实现有阿里的鹰眼、Twitter开源的OpenZipkin还有Naver开源的Pinpoint它们都是受Google发布的Dapper论文启发而实现的。其中阿里的鹰眼解决方案没有开源而且由于阿里需要处理数据量比较大所以鹰眼的定位相对定制化不一定适合中小规模的业务团队感兴趣的同学可以点击本期文章末尾“拓展阅读”进行学习。
下面我主要来介绍下开源实现方案OpenZipkin和Pinpoint再看看它们有什么区别。
OpenZipkin
OpenZipkin是Twitter开源的服务追踪系统下面这张图展示了它的架构设计。
-
图片来源https://zipkin.io/public/img/architecture-1.png
从图中看OpenZipkin主要由四个核心部分组成。
Collector负责收集探针Reporter埋点采集的数据经过验证处理并建立索引。
Storage存储服务调用的链路数据默认使用的是Cassandra是因为Twitter内部大量使用了Cassandra你也可以替换成Elasticsearch或者MySQL。
API将格式化和建立索引的链路数据以API的方式对外提供服务比如被UI调用。
UI以图形化的方式展示服务调用的链路数据。
它的工作原理可以用下面这张图来描述。
-
图片来源https://zipkin.io/pages/architecture.html
具体流程是通过在业务的HTTP Client前后引入服务追踪代码这样在HTTP方法“/foo”调用前生成trace信息TraceIdaa、SpanId6b、annotationGET /foo以及当前时刻的timestamp1483945573944000然后调用结果返回后记录下耗时duration之后再把这些trace信息和duration异步上传给Zipkin Collector。
Pinpoint
Pinpoint是Naver开源的一款深度支持Java语言的服务追踪系统下面这张图是它的架构设计。
-
图片来源http://naver.github.io/pinpoint/1.7.3/images/pinpoint-architecture.png
Pinpoint主要也由四个部分组成。
Pinpoint Agent通过Java字节码注入的方式来收集JVM中的调用数据通过UDP协议传递给Collector数据采用Thrift协议进行编码。
Pinpoint Collector收集Agent传过来的数据然后写到HBase Storgage。
HBase Storage采用HBase集群存储服务调用的链路信息。
Pinpoint Web UI通过Web UI展示服务调用的详细链路信息。
它的工作原理你可以看这张图。
-
图片来源http://naver.github.io/pinpoint/1.7.3/images/td_figure6.png
具体来看就是请求进入TomcatA然后生成TraceIdTomcatA^ TIME ^ 1、SpanId10、pSpanId-1代表是根请求接着TomatA调用TomcatB的hello方法TomcatB生成TraceIdTomcatA^ TIME ^1、新的SpanId20、pSpanId10代表是TomcatA的请求返回调用结果后将trace信息发给CollectorTomcatA收到调用结果后将trace信息也发给Collector。Collector把trace信息写入到HBase中Rowkey就是traceIdSpanId和pSpanId都是列。然后就可以通过UI查询调用链路信息了。
选型对比
根据我的经验,考察服务追踪系统主要从下面这几个方面。
1. 埋点探针支持平台的广泛性
OpenZipkin和Pinpoint都支持哪些语言平台呢
OpenZipkin提供了不同语言的Library不同语言实现时需要引入不同版本的Library。
官方提供了C#、Go、Java、JavaScript、Ruby、Scala、PHP等主流语言版本的Library而且开源社区还提供了更丰富的不同语言版本的Library详细的可以点击这里查看而Pinpoint目前只支持Java语言。
所以从探针支持的语言平台广泛性上来看OpenZipkin比Pinpoint的使用范围要广而且开源社区很活跃生命力更强。
2. 系统集成难易程度
再来看下系统集成的难易程度。
以OpenZipkin的Java探针Brave为例它只提供了基本的操作API如果系统要想集成Brave必须在配置里手动里添加相应的配置文件并且增加trace业务代码。具体来讲就是你需要先修改工程的POM依赖以引入Brave相关的JAR包。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-bom</artifactId>
<version>${brave.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
然后假如你想收集每一次HTTP调用的信息你就可以使用Brave在Apache Httpclient基础上封装的httpClient它会记录每一次HTTP调用的信息并上报给OpenZipkin。
httpclient =TracingHttpClientBuilder.create(tracing).build();
而Pinpoint是通过字节码注入的方式来实现拦截服务调用从而收集trace信息的所以不需要代码做任何改动。Java字节码注入的大致原理你可以参考下图。
-
图片来源http://naver.github.io/pinpoint/1.7.3/images/td_figure3.png
我来解释一下就是JVM在加载class二进制文件时动态地修改加载的class文件在方法的前后执行拦截器的before()和after()方法在before()和after()方法里记录trace()信息。而应用不需要修改业务代码只需要在JVM启动时添加类似下面的启动参数就可以了。
-javaagent:$AGENT_PATH/pinpoint-bootstrap-$VERSION.jar
-Dpinpoint.agentId=<Agent's UniqueId>
-Dpinpoint.applicationName=<The name indicating a same service (AgentId collection)
所以从系统集成难易程度上看Pinpoint要比OpenZipkin简单
3. 调用链路数据的精确度
从下面这张OpenZipkin的调用链路图可以看出OpenZipkin收集到的数据只到接口级别进一步的信息就没有了
-
图片来源http://ovcjgn2x0.bkt.clouddn.com/zipkin-info.jpg
再来看下Pinpoint因为Pinpoint采用了字节码注入的方式实现trace信息收集所以它能拿到的信息比OpenZipkin多得多从下面这张图可以看出它不仅能够查看接口级别的链路调用信息还能深入到调用所关联的数据库信息
-
图片来源http://ovcjgn2x0.bkt.clouddn.com/pp-info.jpg
同理在绘制链路拓扑图时OpenZipkin只能绘制服务与服务之间的调用链路拓扑图比如下面这张示意图
-
图片来源http://ovcjgn2x0.bkt.clouddn.com/zipdependency1.jpg
而Pinpoint不仅能够绘制服务与服务之间还能绘制与DB之间的调用链路拓扑图比如下图
-
图片来源http://ovcjgn2x0.bkt.clouddn.com/ppreal.jpg
所以从调用链路数据的精确度上看Pinpoint要比OpenZipkin精确得多
总结
今天我给你讲解了两个开源服务追踪系统OpenZipkin和Pinpoint的具体实现并从埋点探针支持平台广泛性系统集成难易程度调用链路数据精确度三个方面对它们进行了对比
从选型的角度来讲如果你的业务采用的是Java语言那么采用Pinpoint是个不错的选择因为它不需要业务改动一行代码就可以实现trace信息的收集除此之外Pinpoint不仅能看到服务与服务之间的链路调用还能看到服务内部与资源层的链路调用功能更为强大如果你有这方面的需求Pinpoint正好能满足
如果你的业务不是Java语言实现或者采用了多种语言那毫无疑问应该选择OpenZipkin并且由于其开源社区很活跃基本上各种语言平台都能找到对应的解决方案不过想要使用OpenZipkin还需要做一些额外的代码开发工作以引入OpenZipkin提供的Library到你的系统中
除了OpenZipkin和Pinpoint业界还有其他开源追踪系统实现比如Uber开源的Jaeger以及国内的一款开源服务追踪系统SkyWalking不过由于目前应用范围不是很广这里就不详细介绍了感兴趣的同学可以点击拓展阅读自行学习
思考题
OpenZipkin在探针采集完数据后有两种方式把数据传递给Collector一种是通过HTTP调用一种是基于MQ的异步通信方式比如使用RabbitMQ或者Kafka你觉得哪种方式更好一些为什么
欢迎你在留言区写下自己的思考与我一起讨论
拓展阅读
阿里巴巴鹰眼http://ppt.geekbang.org/slide/download/939/595f4cdcb9d52.pdf/18
Jaegerhttps://www.jaegertracing.io
SkyWalkinghttps://github.com/apache/incubator-skywalking

View File

@ -0,0 +1,75 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
17 如何识别服务节点是否存活?
今天我要与你分享如何识别服务节点是否存活,这在服务治理中是十分重要的。在进入正题之前,你可以先复习一下[专栏第5期]我在讲解注册中心原理的时候以开源注册中心ZooKeeper为例描述了它是如何管理注册到注册中心的节点的存活的。
其实ZooKeeper判断注册中心节点存活的机制其实就是注册中心摘除机制服务消费者以注册中心中的数据为准当服务端节点有变更时注册中心就会把变更通知给服务消费者服务消费者就会调用注册中心来拉取最新的节点信息。
这种机制在大部分情况下都可以工作得很好,但是在网络频繁抖动时,服务提供者向注册中心汇报心跳信息可能会失败,如果在规定的时间内,注册中心都没有收到服务提供者的心跳信息,就会把这个节点从可用节点列表中移除。更糟糕的是,在服务池拥有上百个节点的的时候,每个节点都可能会被移除,导致注册中心可用节点的状态一直在变化,这个时候应该如何处理呢?
下面就结合我在实践中的经验,给你讲解几种解决方案。
心跳开关保护机制
在网络频繁抖动的情况下,注册中心中可用的节点会不断变化,这时候服务消费者会频繁收到服务提供者节点变更的信息,于是就不断地请求注册中心来拉取最新的可用服务节点信息。当有成百上千个服务消费者,同时请求注册中心获取最新的服务提供者的节点信息时,可能会把注册中心的带宽给占满,尤其是注册中心是百兆网卡的情况下。
所以针对这种情况,需要一种保护机制,即使在网络频繁抖动的时候,服务消费者也不至于同时去请求注册中心获取最新的服务节点信息。
我曾经就遇到过这种情况一个可行的解决方案就是给注册中心设置一个开关当开关打开时即使网络频繁抖动注册中心也不会通知所有的服务消费者有服务节点信息变更比如只给10%的服务消费者返回变更这样的话就能将注册中心的请求量减少到原来的1/10。
当然打开这个开关也是有一定代价的它会导致服务消费者感知最新的服务节点信息延迟原先可能在10s内就能感知到服务提供者节点信息的变更现在可能会延迟到几分钟所以在网络正常的情况下开关并不适合打开可以作为一个紧急措施在网络频繁抖动的时候才打开这个开关。
服务节点摘除保护机制
服务提供者在进程启动时会注册服务到注册中心并每隔一段时间汇报心跳给注册中心以标识自己的存活状态。如果隔了一段固定时间后服务提供者仍然没有汇报心跳给注册中心注册中心就会认为该节点已经处于“dead”状态于是从服务的可用节点信息中移除出去。
如果遇到网络问题,大批服务提供者节点汇报给注册中心的心跳信息都可能会传达失败,注册中心就会把它们都从可用节点列表中移除出去,造成剩下的可用节点难以承受所有的调用,引起“雪崩”。但是这种情况下,可能大部分服务提供者节点是可用的,仅仅因为网络原因无法汇报心跳给注册中心就被“无情”的摘除了。
这个时候就需要根据实际业务的情况,设定一个阈值比例,即使遇到刚才说的这种情况,注册中心也不能摘除超过这个阈值比例的节点。
这个阈值比例可以根据实际业务的冗余度来确定我通常会把这个比例设定在20%就是说注册中心不能摘除超过20%的节点。因为大部分情况下,节点的变化不会这么频繁,只有在网络抖动或者业务明确要下线大批量节点的情况下才有可能发生。而业务明确要下线大批量节点的情况是可以预知的,这种情况下可以关闭阈值保护;而正常情况下,应该打开阈值保护,以防止网络抖动时,大批量可用的服务节点被摘除。
讲到这里,我们先小结一下。
心跳开关保护机制,是为了防止服务提供者节点频繁变更导致的服务消费者同时去注册中心获取最新服务节点信息;服务节点摘除保护机制,是为了防止服务提供者节点被大量摘除引起服务消费者可以调用的节点不足。
可见,无论是心跳开关保护机制还是服务节点摘除保护机制,都是因为注册中心里的节点信息是随时可能发生变化的,所以也可以把注册中心叫作动态注册中心。
那么是不是可以换个思路,服务消费者并不严格以注册中心中的服务节点信息为准,而是更多的以服务消费者实际调用信息来判断服务提供者节点是否可用。这就是下面我要讲的静态注册中心。
静态注册中心
前面讲过心跳机制能保证在服务提供者出现异常时,注册中心可以及时把不可用的服务提供者从可用节点列表中移除出去,正常情况下这是个很好的机制。
但是仔细思考一下,为什么不把这种心跳机制直接用在服务消费者端呢?
因为服务提供者是向服务消费者提供服务的,是否可用服务消费者应该比注册中心更清楚,因此可以直接在服务消费者端根据调用服务提供者是否成功来判定服务提供者是否可用。如果服务消费者调用某一个服务提供者节点连续失败超过一定次数,可以在本地内存中将这个节点标记为不可用。并且每隔一段固定时间,服务消费者都要向标记为不可用的节点发起保活探测,如果探测成功了,就将标记为不可用的节点再恢复为可用状态,重新发起调用。
这样的话,服务提供者节点就不需要向注册中心汇报心跳信息,注册中心中的服务节点信息也不会动态变化,也可以称之为静态注册中心。
从我的实践经历来看,一开始采用了动态注册中心,后来考虑到网络的复杂性,心跳机制不一定是可靠的,而后开始改为采用服务消费者端的保活机制,事实证明这种机制足以应对网络频繁抖动等复杂的场景。
当然静态注册中心中的服务节点信息并不是一直不变,当在业务上线或者运维人工增加或者删除服务节点这种预先感知的情况下,还是有必要去修改注册中心中的服务节点信息。
比如在业务上线过程中,需要把正在部署的服务节点从注册中心中移除,等到服务部署完毕,完全可用的时候,再加入到注册中心。还有就是在业务新增或者下线服务节点的时候,需要调用注册中心提供的接口,添加节点信息或者删除节点。这个时候静态注册中心有点退化到配置中心的意思,只不过这个时候配置中心里存储的不是某一项配置,而是某个服务的可用节点信息。
总结
今天我给你讲解了动态注册中心在实际线上业务运行时,如果遇到网络不可靠等因素,可能会带来的两个问题,一个是服务消费者同时并发访问注册中心获取最新服务信息导致注册中心带宽被打满;另一个是服务提供者节点被大量摘除导致服务消费者没有足够的节点可以调用。
这两个问题都是我在业务实践过程中遇到过的,我给出的两个解决方案:心跳开关保护机制和服务节点摘除保护机制都是在实践中应用过的,并且被证明是行之有效的。
而静态注册中心的思路,是在斟酌注册中心的本质之后,引入的另外一个解决方案,相比于动态注册中心更加简单,并且基于服务消费者本身调用来判断服务节点是否可用,更加直接也更加准确,尤其在注册中心或者网络出现问题的时候,这种方案基本不受影响。
思考题
在实际的微服务架构中注册中心主动心跳机制和客户端摘除机制可能会同时使用比如Spring Cloud就把这两种机制结合起来识别服务节点是否存活。如果注册中心没有收到某一个服务节点的心跳汇报而服务消费者又调用这个服务节点成功了你认为应该以哪个为准为什么
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,115 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
18 如何使用负载均衡算法?
假设你订阅了一个别人的服务,从注册中心查询得到了这个服务的可用节点列表,而这个列表里包含了几十个节点,这个时候你该选择哪个节点发起调用呢?这就是今天我要给你讲解的关于客户端负载均衡算法的问题。
为什么要引入负载均衡算法呢?主要有两个原因:一个是要考虑调用的均匀性,也就是要让每个节点都接收到调用,发挥所有节点的作用;另一个是要考虑调用的性能,也就是哪个节点响应最快,优先调用哪个节点。
不同的负载均衡算法在这两个方面的考虑不同,下面我就来能给介绍常见的负载均衡算法及其应用场景。
常见的负载均衡算法
1. 随机算法
随机算法,顾名思义就是从可用的服务节点中,随机挑选一个节点来访问。
在实现时随机算法通常是通过生成一个随机数来实现比如服务有10个节点那么就每一次生成一个110之间的随机数假设生成的是2那么就访问编号为2的节点。
采用随机算法,在节点数量足够多,并且访问量比较大的情况下,各个节点被访问的概率是基本相同的。一个随机算法的代码实现,可以参考这个示例。
2. 轮询算法
轮询算法,顾名思义就是按照固定的顺序,把可用的服务节点,挨个访问一次。
在实现时轮询算法通常是把所有可用节点放到一个数组里然后按照数组编号挨个访问。比如服务有10个节点放到数组里就是一个大小为10的数组这样的话就可以从序号为0的节点开始访问访问后序号自动加1下一次就会访问序号为1的节点以此类推。
轮询算法能够保证所有节点被访问到的概率是相同的。一个轮询算法的代码实现,可以参考这个示例。
3. 加权轮询算法
轮询算法能够保证所有节点被访问的概率相同,而加权轮询算法是在此基础上,给每个节点赋予一个权重,从而使每个节点被访问到的概率不同,权重大的节点被访问的概率就高,权重小的节点被访问的概率就小。
在实现时加权轮询算法是生成一个节点序列该序列里有n个节点n是所有节点的权重之和。在这个序列中每个节点出现的次数就是它的权重值。比如有三个节点a、b、c权重分别是3、2、1那么生成的序列就是{a、a、b、c、b、a}这样的话按照这个序列访问前6次请求就会分别访问节点a三次节点b两次节点c一次。从第7个请求开始又重新按照这个序列的顺序来访问节点。
在应用加权轮询算法的时候,根据我的经验,要尽可能保证生产的序列的均匀,如果生成的不均匀会造成节点访问失衡,比如刚才的例子,如果生成的序列是{a、a、a、b、b、c}就会导致前3次访问的节点都是a。一个加权轮询算法的代码实现可以参考这个示例。
4. 最少活跃连接算法
最少活跃连接算法,顾名思义就是每一次访问都选择连接数最少的节点。因为不同节点处理请求的速度不同,使得同一个服务消费者同每一个节点的连接数都不相同。连接数大的节点,可以认为是处理请求慢,而连接数小的节点,可以认为是处理请求快。所以在挑选节点时,可以以连接数为依据,选择连接数最少的节点访问。
在实现时,需要记录跟每一个节点的连接数,这样在选择节点时,才能比较出连接数最小的节点。一个最少活跃连接算法的代码实现,可以参考这个示例。
5. 一致性hash算法
一致性hash算法是通过某个hash函数把同一个来源的请求都映射到同一个节点上。一致性hash算法最大的特点就是同一个来源的请求只会映射到同一个节点上可以说是具有记忆功能。只有当这个节点不可用时请求才会被分配到相邻的可用节点上。
一个一致性hash算法的代码实现可以参考这个示例。
负载均衡算法的使用场景
上面这五种负载均衡算法,具体在业务中该如何选择呢?根据我的经验,它们的各自应用场景如下:
随机算法:实现比较简单,在请求量远超可用服务节点数量的情况下,各个服务节点被访问的概率基本相同,主要应用在各个服务节点的性能差异不大的情况下。
轮询算法:跟随机算法类似,各个服务节点被访问的概率也基本相同,也主要应用在各个服务节点性能差异不大的情况下。
加权轮询算法:在轮询算法基础上的改进,可以通过给每个节点设置不同的权重来控制访问的概率,因此主要被用在服务节点性能差异比较大的情况。比如经常会出现一种情况,因为采购时间的不同,新的服务节点的性能往往要高于旧的节点,这个时候可以给新的节点设置更高的权重,让它承担更多的请求,充分发挥新节点的性能优势。
最少活跃连接算法:与加权轮询算法预先定义好每个节点的访问权重不同,采用最少活跃连接算法,客户端同服务端节点的连接数是在时刻变化的,理论上连接数越少代表此时服务端节点越空闲,选择最空闲的节点发起请求,能获取更快的响应速度。尤其在服务端节点性能差异较大,而又不好做到预先定义权重时,采用最少活跃连接算法是比较好的选择。
一致性hash算法因为它能够保证同一个客户端的请求始终访问同一个服务节点所以适合服务端节点处理不同客户端请求差异较大的场景。比如服务端缓存里保存着客户端的请求结果如果同一客户端一直访问一个服务节点那么就可以一直从缓存中获取数据。
这五种负载均衡算法是业界最常用的不光在RPC调用中被广泛采用在一些负载均衡组件比如Nginx中也有应用所以说是一种通用的负载均衡算法但是不是所有的业务场景都能很好解决呢
我曾经遇到过这种场景:
服务节点数量众多,且性能差异比较大;
服务节点列表经常发生变化,增加节点或者减少节点时有发生;
客户端和服务节点之间的网络情况比较复杂,有些在一个数据中心,有些不在一个数据中心需要跨网访问,而且网络经常延迟或者抖动。
显然无论是随机算法还是轮询算法第一个情况就不满足加权轮询算法需要预先配置服务节点的权重在节点列表经常变化的情况下不好维护所以也不适合。而最少活跃连接算法是从客户端自身维度去判断的在实际应用时并不能直接反映出服务节点的请求量大小尤其是在网络情况比较复杂的情况下并不能做到动态的把请求发送给最合适的服务节点。至于一致性hash算法显然不适合这种场景。
针对上面这种场景,有一种算法更加适合,这种算法就是自适应最优选择算法。
自适应最优选择算法
这种算法的主要思路是在客户端本地维护一份同每一个服务节点的性能统计快照并且每隔一段时间去更新这个快照。在发起请求时根据“二八原则”把服务节点分成两部分找出20%的那部分响应最慢的节点,然后降低权重。这样的话,客户端就能够实时的根据自身访问每个节点性能的快慢,动态调整访问最慢的那些节点的权重,来减少访问量,从而可以优化长尾请求。
由此可见自适应最优选择算法是对加权轮询算法的改良可以看作是一种动态加权轮询算法。它的实现关键之处就在于两点第一点是每隔一段时间获取客户端同每个服务节点之间调用的平均性能统计第二点是按照这个性能统计对服务节点进行排序对排在性能倒数20%的那部分节点赋予一个较低的权重,其余的节点赋予正常的权重。
在具体实现时针对第一点需要在内存中开辟一块空间记录客户端同每一个服务节点之间调用的平均性能并每隔一段固定时间去更新。这个更新的时间间隔不能太短太短的话很容易受瞬时的性能抖动影响导致统计变化太快没有参考性同时也不能太长太长的话时效性就会大打折扣效果不佳。根据我的经验1分钟的更新时间间隔是个比较合适的值。
针对第二点关键点是权重值的设定即使服务节点之间的性能差异较大也不适合把权重设置得差异太大这样会导致性能较好的节点与性能较差的节点之间调用量相差太大这样也不是一种合理的状态。在实际设定时可以设置20%性能较差的节点权重为3其余节点权重为5。
总结
今天我给你讲解了最常用的五种客户端负载均衡算法的原理以及适用场景,在业务实践的过程汇总,究竟采用哪种,需要根据实际情况来决定,并不是算法越复杂越好。
比如在一种简单的业务场景下有10个服务节点并且配置基本相同位于同一个数据中心此时客户端选择随机算法或者轮询算法既简单又高效并没有必要选择加权轮询算法或者最少活跃连接算法。
但在遇到前面提到的那种复杂业务场景下,服务节点数量众多,配置差异比较大,而且位于不同的数据中心,客户端与服务节点之间的网络情况也比较复杂,这个时候简单的负载均衡算法通常都难以应对,需要针对实际情况,选择更有针对性的负载均衡算法,比如自适应最优选择算法。
思考题
今天我给你讲的都属于软件层面的负载均衡算法它与F5这种硬件负载均衡器有什么不同呢
欢迎你在留言区写下自己的思考,与我一起讨论。
扩展阅读:
一致性hash算法是如何做到添加或者删除节点对整体请求的分布影响不大https://www.codeproject.com/Articles/56138/Consistent-hashing

View File

@ -0,0 +1,165 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
19 如何使用服务路由?
专栏上一期我给你讲解了常用的客户端负载均衡算法它帮我们解决了服务消费者如何从众多可用的服务节点中选取一个最合适的节点发起调用的问题。但在业务中经常还会遇到这样的场景比如服务A部署在北京、上海、广州三个数据中心所有的服务节点按照所在的数据中心被分成了三组那么服务A的消费者在发起调用时该如何选择呢这就是今天我要给你讲解的服务路由的问题。
那么什么是服务路由呢?我的理解是服务路由就是服务消费者在发起服务调用时,必须根据特定的规则来选择服务节点,从而满足某些特定的需求。
那么服务路由都有哪些应用场景?具体都有哪些规则呢?
服务路由的应用场景
根据我的实践经验,服务路由主要有以下几种应用场景:
分组调用。一般来讲,为了保证服务的高可用性,实现异地多活的需求,一个服务往往不止部署在一个数据中心,而且出于节省成本等考虑,有些业务可能不仅在私有机房部署,还会采用公有云部署,甚至采用多家公有云部署。服务节点也会按照不同的数据中心分成不同的分组,这时对于服务消费者来说,选择哪一个分组调用,就必须有相应的路由规则。
灰度发布。在服务上线发布的过程中,一般需要先在一小部分规模的服务节点上先发布服务,然后验证功能是否正常。如果正常的话就继续扩大发布范围;如果不正常的话,就需要排查问题,解决问题后继续发布。这个过程就叫作灰度发布,也叫金丝雀部署。
流量切换。在业务线上运行过程中,经常会遇到一些不可抗力因素导致业务故障,比如某个机房的光缆被挖断,或者发生着火等事故导致整个机房的服务都不可用。这个时候就需要按照某个指令,能够把原来调用这个机房服务的流量切换到其他正常的机房。
读写分离。对于大多数互联网业务来说都是读多写少,所以在进行服务部署的时候,可以把读写分开部署,所有写接口可以部署在一起,而读接口部署在另外的节点上。
上面四种应用场景是实际业务中很常见的,服务路由可以通过各种规则来实现,那么服务路由都有哪些规则呢?
服务路由的规则
根据我的实践经验,服务路由主要有两种规则:一种是条件路由,一种是脚本路由。
1. 条件路由
条件路由是基于条件表达式的路由规则,以下面的条件路由为例,我来给你详细讲解下它的用法。
condition://0.0.0.0/dubbo.test.interfaces.TestService?category=routers&dynamic=true&priority=2&enabled=true&rule=" + URL.encode(" host = 10.20.153.10=> host = 10.20.153.11")
这里面“condition://”代表了这是一段用条件表达式编写的路由规则,具体的规则是
host = 10.20.153.10 => host = 10.20.153.11
分隔符“=>”前面是服务消费者的匹配条件后面是服务提供者的过滤条件。当服务消费者节点满足匹配条件时就对该服务消费者执行后面的过滤规则。那么上面这段表达式表达的意义就是IP为“10.20.153.10”的服务消费者都调用IP为“10.20.153.11”的服务提供者节点。
如果服务消费者的匹配条件为空,就表示对所有的服务消费者应用,就像下面的表达式一样。
=> host = 10.20.153.11
如果服务提供者的过滤条件为空,就表示禁止服务消费者访问,就像下面的表达式一样。
host = 10.20.153.10=>
下面我举一些Dubbo框架中的条件路由来给你讲解下条件路由的具体应用场景。
排除某个服务节点
=> host != 172.22.3.91
一旦这条路由规则被应用到线上所有的服务消费者都不会访问IP为172.22.3.91的服务节点,这种路由规则一般应用在线上流量排除预发布机以及摘除某个故障节点的场景。
白名单和黑名单功能
host != 10.20.153.10,10.20.153.11 =>
这条路由规则意思是除了IP为10.20.153.10和10.20.153.11的服务消费者可以发起服务调用以外,其他服务消费者都不可以,主要用于白名单访问逻辑,比如某个后台服务只允许特定的几台机器才可以访问,这样的话可以机器控制访问权限。
host = 10.20.153.10,10.20.153.11 =>
同理这条路由规则意思是除了IP为10.20.153.10和10.20.153.11的服务消费者不能发起服务调用以外,其他服务消费者都可以,也就是实现了黑名单功能,比如线上经常会遇到某些调用方不管是出于有意还是无意的不合理调用,影响了服务的稳定性,这时候可以通过黑名单功能暂时予以封杀。
机房隔离
host = 172.22.3.* => host = 172.22.3.*
这条路由规则意思是IP网段为172.22.3.*的服务消费者才可以访问同网段的服务节点这种规则一般应用于服务部署在多个IDC理论上同一个IDC内的调用性能要比跨IDC调用性能要好应用这个规则是为了实现同IDC就近访问。
读写分离
method = find,list,get,is => host =172.22.3.94,172.22.3.95
method != find,list,get,is => host = 172.22.3.97,172.22.3.98
这条路由规则意思是find*、get*、is*等读方法调用IP为172.22.3.94和172.22.3.95的节点除此以外的写方法调用IP为172.22.3.97和172.22.3.98的节点。对于大部分互联网业务来说,往往读请求要远远大于写请求,而写请求的重要性往往要远远高于读请求,所以需要把读写请求进行分离,以避免读请求异常影响到写请求,这时候就可以应用这种规则。
2. 脚本路由
脚本路由是基于脚本语言的路由规则常用的脚本语言比如JavaScript、Groovy、JRuby等。以下面的脚本路由规则为例我来给你详细讲解它的用法。
"script://0.0.0.0/com.foo.BarService?category=routers&dynamic=false&rule=" + URL.encode("function route(invokers) { ... } (invokers)")
这里面“script://”就代表了这是一段脚本语言编写的路由规则具体规则定义在脚本语言的route方法实现里比如下面这段用JavaScript编写的route()方法表达的意思是只有IP为10.20.153.10的服务消费者可以发起服务调用。
function route(invokers){
var result = new java.util.ArrayList(invokers.size());
for(i =0; i < invokers.size(); i ++){
if("10.20.153.10".equals(invokers.get(i).getUrl().getHost())){
result.add(invokers.get(i));
}
}
return result;
} (invokers);
既然服务路由是通过路由规则来实现的那么服务消费者该如何获取路由规则呢
服务路由的获取方式
根据我的实践经验服务路由的获取方式主要有三种
本地配置
顾名思义就是路由规则存储在服务消费者本地上服务消费者发起调用时从本地固定位置读取路由规则然后按照路由规则选取一个服务节点发起调用
配置中心管理
这种方式下所有的服务消费者都从配置中心获取路由规则由配置中心来统一管理
动态下发
这种方式下一般是运维人员或者开发人员通过服务治理平台修改路由规则服务治理平台调用配置中心接口把修改后的路由规则持久化到配置中心因为服务消费者订阅了路由规则的变更于是就会从配置中心获取最新的路由规则按照最新的路由规则来执行
根据我的实践经验上面三种方式实际使用时还是有一定区别的
一般来讲服务路由最好是存储在配置中心中由配置中心来统一管理这样的话所有的服务消费者就不需要在本地管理服务路由因为大部分的服务消费者并不关心服务路由的问题或者说也不需要去了解其中的细节通过配置中心统一给各个服务消费者下发统一的服务路由节省了沟通和管理成本
但也不排除某些服务消费者有特定的需求需要定制自己的路由规则这个时候就适合通过本地配置来定制
而动态下发可以理解为一种高级功能它能够动态地修改路由规则在某些业务场景下十分有用比如某个数据中心存在问题需要把调用这个数据中心的服务消费者都切换到其他数据中心这时就可以通过动态下发的方式向配置中心下发一条路由规则将所有调用这个数据中心的请求都迁移到别的地方
当然这三种方式也可以一起使用这个时候服务消费者的判断优先级是本地配置>动态下发>配置中心管理。
总结
今天我给你讲解了服务路由的作用,简单来讲就是为了实现某些调用的特殊需求,比如分组调用、灰度发布、流量切换、读写分离等。在业务规模比较小的时候,可能所有的服务节点都部署在一起,也就不需要服务路由。但随着业务规模的扩大、服务节点增多,尤其是涉及多数据中心部署的情况,把服务节点按照数据中心进行分组,或者按照业务的核心程度进行分组,对提高服务的可用性是十分有用的。以微博业务为例,有的服务不仅进行了核心服务和非核心服务分组,还针对私有云和公有云所处的不同数据中心也进行了分组,这样的话就可以将服务之间的调用尽量都限定在同一个数据中心内部,最大限度避免跨数据中心的网络延迟、抖动等影响。
而服务路由具体是在本地配置,还是在配置中心统一管理,也是视具体业务需求而定的。如果没有定制化的需求,建议把路由规则都放到配置中心中统一存储管理。而动态下发路由规则对于服务治理十分有帮助,当数据中心出现故障的时候,可以实现动态切换流量,还可以摘除一些有故障的服务节点。
思考题
在实际业务场景中,经常有一类需求就是一个新功能在全量上线前,会圈一批用户优先适用,如果使用服务路由功能的话,你觉得可以怎么做?
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,89 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
20 服务端出现故障时该如何应对?
在专栏前面我讲过,单体应用改造成微服务的一个好处是可以减少故障影响范围,故障被局限在一个微服务系统本身,而不是整个单体应用都崩溃。那么具体到一个微服务系统,如果出现了故障,应该如何处理呢?
首先,我先来带你回顾一下微服务系统可能出现故障的种类,主要有三种故障。
集群故障。根据我的经验微服务系统一般都是集群部署的根据业务量大小而定集群规模从几台到甚至上万台都有可能。一旦某些代码出现bug可能整个集群都会发生故障不能提供对外提供服务。
单IDC故障。现在大多数互联网公司为了保证业务的高可用性往往业务部署在不止一个IDC。然而现实中时常会发生某个IDC的光缆因为道路施工被挖断导致整个IDC脱网。
单机故障。顾名思义就是集群中的个别机器出现故障,这种情况往往对全局没有太大影响,但会导致调用到故障机器上的请求都失败,影响整个系统的成功率。
在我的实践过程中,这三种故障都经常遇到,因此相应的处理手段也可谓驾轻就熟,下面就把我应对故障的实战经验分享给你,希望对你有所帮助。
集群故障
一般而言集群故障的产生原因不外乎有两种一种是代码bug所导致比如说某一段Java代码不断地分配大对象但没有及时回收导致JVM OOM退出另一种是突发的流量冲击超出了系统的最大承载能力比如“双11”这种购物活动电商系统会在零点一瞬间涌入大量流量超出系统的最大承载能力一下子就把整个系统给压垮了。
应付集群故障的思路,主要有两种:限流和降级。
1. 限流
顾名思义,限流就是限制流量,通常情况下,系统能够承载的流量根据集群规模的大小是固定的,可以称之为系统的最大容量。当真实流量超过了系统的最大容量后,就会导致系统响应变慢,服务调用出现大量超时,反映给用户的感觉就是卡顿、无响应。所以,应该根据系统的最大容量,给系统设置一个阈值,超过这个阈值的请求会被自动抛弃,这样的话可以最大限度地保证系统提供的服务正常。
除此之外,通常一个微服务系统会同时提供多个服务,每个服务在同一时刻的请求量也是不同的,很可能出现的一种情况就是,系统中某个服务的请求量突增,占用了系统中大部分资源,导致其他服务没有资源可用。因此,还要针对系统中每个服务的请求量也设置一个阈值,超过这个阈值的请求也要被自动抛弃,这样的话不至于因为一个服务影响了其他所有服务。
在实际项目中可以用两个指标来衡量服务的请求量一个是QPS即每秒请求量一个是工作线程数。不过QPS因为不同服务的响应快慢不同所以系统能够承载的QPS相差很大因此一般选择工作线程数来作为限流的指标给系统设置一个总的最大工作线程数以及单个服务的最大工作线程数这样的话无论是系统的总请求量过大导致整体工作线程数量达到最大工作线程数还是某个服务的请求量超过单个服务的最大工作线程数都会被限流以起到保护整个系统的作用。
2. 降级
什么是降级呢?在我看来,降级就是通过停止系统中的某些功能,来保证系统整体的可用性。降级可以说是一种被动防御的措施,为什么这么说呢?因为它一般是系统已经出现故障后所采取的一种止损措施。
那么降级一般是如何实现的呢?根据我的实践来看, 一种可行的方案是通过开关来实现。
具体来讲,就是在系统运行的内存中开辟一块区域,专门用于存储开关的状态,也就是开启还是关闭。并且需要监听某个端口,通过这个端口可以向系统下发命令,来改变内存中开关的状态。当开关开启时,业务的某一段逻辑就不再执行,而正常情况下,开关是关闭的状态。
开关一般用在两种地方,一种是新增的业务逻辑,因为新增的业务逻辑相对来说不成熟,往往具备一定的风险,所以需要加开关来控制新业务逻辑是否执行;另一种是依赖的服务或资源,因为依赖的服务或者资源不总是可靠的,所以最好是有开关能够控制是否对依赖服务或资源发起调用,来保证即使依赖出现问题,也能通过降级来避免影响。
在实际业务应用的时候,降级要按照对业务的影响程度进行分级,一般分为三级:一级降级是对业务影响最小的降级,在故障的情况下,首先执行一级降级,所以一级降级也可以设置成自动降级,不需要人为干预;二级降级是对业务有一定影响的降级,在故障的情况下,如果一级降级起不到多大作用的时候,可以人为采取措施,执行二级降级;三级降级是对业务有较大影响的降级,这种降级要么是对商业收入有重大影响,要么是对用户体验有重大影响,所以操作起来要非常谨慎,不在最后时刻一般不予采用。
单IDC故障
在现实情况下整个IDC脱网的事情时有发生多半是因为不可抗力比如机房着火、光缆被挖断等如果业务全部部署在这个IDC那就完全不可访问了所以国内大部分的互联网业务多采用多IDC部署。具体来说有的采用同城双活也就是在一个城市的两个IDC内部署有的采用异地多活一般是在两个城市的两个IDC内部署当然也有支付宝这种金融级别的应用采用了“三地五中心”部署这种部署成本显然高比两个IDC要高得多但可用性的保障要更高。
采用多IDC部署的最大好处就是当有一个IDC发生故障时可以把原来访问故障IDC的流量切换到正常的IDC来保证业务的正常访问。
流量切换的方式一般有两种一种是基于DNS解析的流量切换一种是基于RPC分组的流量切换。
1. 基于DNS解析的流量切换
基于DNS解析流量的切换一般是通过把请求访问域名解析的VIP从一个IDC切换到另外一个IDC。比如访问“www.weibo.com”正常情况下北方用户会解析到联通机房的VIP南方用户会解析到电信机房的VIP如果联通机房发生故障的话会把北方用户访问也解析到电信机房的VIP只不过此时网络延迟可能会变长。
2. 基于RPC分组的流量切换
对于一个服务来说如果是部署在多个IDC的话一般每个IDC就是一个分组。假如一个IDC出现故障那么原先路由到这个分组的流量就可以通过向配置中心下发命令把原先路由到这个分组的流量全部切换到别的分组这样的话就可以切换故障IDC的流量了。
单机故障
单机故障是发生概率最高的一种故障了,尤其对于业务量大的互联网应用来说,上万台机器的规模也是很常见的。这种情况下,发生单机故障的概率就很高了,这个时候只靠运维人肉处理显然不可行,所以就要求有某种手段来自动处理单机故障。
根据我的经验,处理单机故障一个有效的办法就是自动重启。具体来讲,你可以设置一个阈值,比如以某个接口的平均耗时为准,当监控单机上某个接口的平均耗时超过一定阈值时,就认为这台机器有问题,这个时候就需要把有问题的机器从线上集群中摘除掉,然后在重启服务后,重新加入到集群中。
不过这里要注意的是需要防止网络抖动造成的接口超时从而触发自动重启。一种方法是在收集单机接口耗时数据时多采集几个点比如每10s采集一个点采集5个点当5个点中有超过3个点的数据都超过设定的阈值范围才认为是真正的单机问题这时会触发自动重启策略。
除此之外为了防止某些特殊情况下短时间内被重启的单机过多造成整个服务池可用节点数太少最好是设置一个可重启的单机数量占整个集群的最大比例一般这个比例不要超过10%因为正常情况下不大可能有超过10%的单机都出现故障。
总结
今天我们探讨了微服务系统可能出现的三种故障集群故障、单IDC故障、单机故障并且针对这三种故障我给出了分别的解决方案包括降级、限流、流量切换以及自动重启。
在遇到实际的故障时往往多个手段是并用的比如在出现单IDC故障首先要快速切换流量到正常的IDC但此时可能正常IDC并不足以支撑两个IDC的流量所以这个时候首先要降级部分功能保证正常的IDC顺利支撑切换过来的流量。
而且要尽量让故障处理自动化这样可以大大减少故障影响的时间。因为一旦需要引入人为干预往往故障处理的时间都得是10分钟以上这对大部分用户敏感型业务的影响是巨大的如果能做到自动化故障处理的话可以将故障处理的时间降低到1分钟以内甚至秒级别这样的话对于用户的影响最小。
思考题
上面我提到为了避免单IDC故障导致服务不可用情况的发生服务需要采用多IDC部署这个时候就要求服务依赖的数据也需要存储在多个IDC内这样势必会带来数据一致性的问题你有什么解决方案吗
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,105 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
21 服务调用失败时有哪些处理手段?
通过前面的学习你应该可以理解,微服务相比于单体应用最大的不同之处在于,服务的调用从同一台机器内部的本地调用变成了不同机器之间的远程方法调用,但是这个过程也引入了两个不确定的因素。
一个是调用的执行是在服务提供者一端即使服务消费者本身是正常的服务提供者也可能由于诸如CPU、网络I/O、磁盘、内存、网卡等硬件原因导致调用失败还有可能由于本身程序执行问题比如GC暂停导致调用失败。
另一个不确定因素是调用发生在两台机器之间,所以要经过网络传输,而网络的复杂性是不可控的,网络丢包、延迟以及随时可能发生的瞬间抖动都有可能造成调用失败。
所以,单体应用改造为微服务架构后,要针对服务调用失败进行特殊处理。那具体来说有哪些处理手段呢?下面我就结合自己的实战经验,一起来聊聊服务调用失败都有哪些处理手段。
超时
首先你要知道的是,单体应用被改造成微服务架构后,一次用户调用可能会被拆分成多个系统之间的服务调用,任何一次服务调用如果发生问题都可能会导致最后用户调用失败。而且在微服务架构下,一个系统的问题会影响所有调用这个系统所提供服务的服务消费者,如果不加以控制,严重的话会引起整个系统雪崩。
所以在实际项目中针对服务调用都要设置一个超时时间以避免依赖的服务迟迟没有返回调用结果把服务消费者拖死。这其中超时时间的设定也是有讲究的不是越短越好因为太短可能会导致有些服务调用还没有来得及执行完就被丢弃了当然时间也不能太长太长有可能导致服务消费者被拖垮。根据我的经验找到比较合适的超时时间需要根据正常情况下服务提供者的服务水平来决定。具体来说就是按照服务提供者线上真实的服务水平取P999或者P9999的值也就是以99.9%或者99.99%的调用都在多少毫秒内返回为准。
重试
虽然设置超时时间可以起到及时止损的效果但是服务调用的结果毕竟是失败了而大部分情况下调用失败都是因为偶发的网络问题或者个别服务提供者节点有问题导致的如果能换个节点再次访问说不定就能成功。而且从概率论的角度来讲假如一次服务调用失败的概率为1%那么连续两次服务调用失败的概率就是0.01%失败率降低到原来的1%。
所以在实际服务调用时经常还要设置一个服务调用超时后的重试次数。假如某个服务调用的超时时间设置为100ms重试次数设置为1那么当服务调用超过100ms后服务消费者就会立即发起第二次服务调用而不会再等待第一次调用返回的结果了。
双发
正如我刚才讲的那样假如一次调用不成功的概率为1%那么连续两次调用都不成功的概率就是0.01%,根据这个推论,一个简单的提高服务调用成功率的办法就是每次服务消费者要发起服务调用的时候,都同时发起两次服务调用,一方面可以提高调用的成功率,另一方面两次服务调用哪个先返回就采用哪次的返回结果,平均响应时间也要比一次调用更快,这就是双发。
但是这样的话一次调用会给后端服务两倍的压力所要消耗的资源也是加倍的所以一般情况下这种“鲁莽”的双发是不可取的。我这里讲一个更为聪明的双发即“备份请求”Backup Requests它的大致思想是服务消费者发起一次服务调用后在给定的时间内如果没有返回请求结果那么服务消费者就立刻发起另一次服务调用。这里需要注意的是这个设定的时间通常要比超时时间短得多比如超时时间取的是P999那么备份请求时间取的可能是P99或者P90这是因为如果在P99或者P90的时间内调用还没有返回结果那么大概率可以认为这次请求属于慢请求了再次发起调用理论上返回要更快一些。
在实际线上服务运行时P999由于长尾请求时间较长的缘故可能要远远大于P99和P90。在我经历的一个项目中一个服务的P999是1s而P99只有200ms、P90只有50ms这样的话如果备份请求时间取的是P90那么第二次请求等待的时间只有50ms。不过这里需要注意的是备份请求要设置一个最大重试比例以避免在服务端出现问题的时大部分请求响应时间都会超过P90的值导致请求量几乎翻倍给服务提供者造成更大的压力。我的经验是这个最大重试比例可以设置成15%,一方面能尽量体现备份请求的优势,另一方面不会给服务提供者额外增加太大的压力。
熔断
前面讲得一些手段在服务提供者偶发异常时会十分管用,但是假如服务提供者出现故障,短时间内无法恢复时,无论是超时重试还是双发不但不能提高服务调用的成功率,反而会因为重试给服务提供者带来更大的压力,从而加剧故障。
针对这种情况,就需要服务消费者能够探测到服务提供者发生故障,并短时间内停止请求,给服务提供者故障恢复的时间,待服务提供者恢复后,再继续请求。这就好比一条电路,电流负载过高的话,保险丝就会熔断,以防止火灾的发生,所以这种手段就被叫作“熔断”。
首先我们先来简单了解一下熔断的工作原理。
简单来讲,熔断就是把客户端的每一次服务调用用断路器封装起来,通过断路器来监控每一次服务调用。如果某一段时间内,服务调用失败的次数达到一定阈值,那么断路器就会被触发,后续的服务调用就直接返回,也就不会再向服务提供者发起请求了。
再来看下面这张图,熔断之后,一旦服务提供者恢复之后,服务调用如何恢复呢?这就牵扯到熔断中断路器的几种状态。
Closed状态正常情况下断路器是处于关闭状态的偶发的调用失败也不影响。
Open状态当服务调用失败次数达到一定阈值时断路器就会处于开启状态后续的服务调用就直接返回不会向服务提供者发起请求。
Half Open状态当断路器开启后每隔一段时间会进入半打开状态这时候会向服务提供者发起探测调用以确定服务提供者是否恢复正常。如果调用成功了断路器就关闭如果没有成功断路器就继续保持开启状态并等待下一个周期重新进入半打开状态。
-
图片来源https://martinfowler.com/bliki/images/circuitBreaker/state.png
关于断路器的实现最经典也是使用最广泛的莫过于Netflix开源的Hystrix了下面我来给你介绍下Hystrix是如何实现断路器的。
Hystrix的断路器也包含三种状态关闭、打开、半打开。Hystrix会把每一次服务调用都用HystrixCommand封装起来它会实时记录每一次服务调用的状态包括成功、失败、超时还是被线程拒绝。当一段时间内服务调用的失败率高于设定的阈值后Hystrix的断路器就会进入进入打开状态新的服务调用就会直接返回不会向服务提供者发起调用。再等待设定的时间间隔后Hystrix的断路器又会进入半打开状态新的服务调用又可以重新发给服务提供者了如果一段时间内服务调用的失败率依然高于设定的阈值的话断路器会重新进入打开状态否则的话断路器会被重置为关闭状态。
其中决定断路器是否打开的失败率阈值可以通过下面这个参数来设定:
HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()
而决定断路器何时进入半打开的状态的时间间隔可以通过下面这个参数来设定:
HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()
断路器实现的关键就在于如何计算一段时间内服务调用的失败率那么Hystrix是如何做的呢
答案就是下图所示的滑动窗口算法,下面我来解释一下具体原理。
-
图片来源https://raw.githubusercontent.com/wiki/Netflix/Hystrix/images/circuit-breaker-1280.png
Hystrix通过滑动窗口来对数据进行统计默认情况下滑动窗口包含10个桶每个桶时间宽度为1秒每个桶内记录了这1秒内所有服务调用中成功的、失败的、超时的以及被线程拒绝的次数。当新的1秒到来时滑动窗口就会往前滑动丢弃掉最旧的1个桶把最新1个桶包含进来。
任意时刻Hystrix都会取滑动窗口内所有服务调用的失败率作为断路器开关状态的判断依据这10个桶内记录的所有失败的、超时的、被线程拒绝的调用次数之和除以总的调用次数就是滑动窗口内所有服务的调用的失败率。
总结
今天我给你讲解了微服务架构下服务调用失败的几种常见手段:超时、重试、双发以及熔断,实际使用时,具体选择哪种手段要根据具体业务情况来决定。
根据我的经验大部分的服务调用都需要设置超时时间以及重试次数当然对于非幂等的也就是同一个服务调用重复多次返回结果不一样的来说不可以重试比如大部分上行请求都是非幂等的。至于双发它是在重试基础上进行一定程度的优化减少了超时等待的时间对于长尾请求的场景十分有效。采用双发策略后服务调用的P999能大幅减少经过我的实践证明是提高服务调用成功率非常有效的手段。而熔断能很好地解决依赖服务故障引起的连锁反应对于线上存在大规模服务调用的情况是必不可少的尤其是对非关键路径的调用也就是说即使调用失败也对最终结果影响不大的情况下更加应该引入熔断。
思考题
Hystrix采用了线程池隔离的方式来实现不同的服务调用相互之间不影响你认为这种方式的优缺点有哪些
欢迎你在留言区写下自己的思考,与我一起讨论。
拓展阅读:
关于熔断的解释https://martinfowler.com/bliki/CircuitBreaker.html
Hystrix的使用方法https://github.com/Netflix/Hystrix/wiki/How-To-Use

View File

@ -0,0 +1,132 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
22 如何管理服务配置?
在拆分为微服务架构前,曾经的单体应用只需要管理一套配置;而拆分为微服务后,每一个系统都有自己的配置,并且都各不相同,而且因为服务治理的需要,有些配置还需要能够动态改变,以达到动态降级、切流量、扩缩容等目的,这也是今天我要与你探讨的,在微服务架构下服务配置如何管理的问题。
本地配置
服务配置管理最简单的方案就是把配置当作代码同等看待随着应用程序代码一起发布。比如下面这段代码用到了开源熔断框架Hystrix并且在代码里定义了几个配置一个是线程的超时时间是3000ms一个是熔断器触发的错误比率是60%。
@HystrixCommand(fallbackMethod = "getDefaultProductInventoryByCode",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value="60")
}
)
public Optional<ProductInventoryResponse> getProductInventoryByCode(String productCode)
{
....
}
还有一种方案就是把配置都抽离到单独的配置文件当中,使配置与代码分离,比如下面这段代码。
@HystrixCommand(commandKey = "inventory-by-productcode", fallbackMethod = "getDefaultProductInventoryByCode")
public Optional<ProductInventoryResponse> getProductInventoryByCode(String productCode)
{
...
}
相应的配置可以抽离到配置文件中,配置文件的内容如下:
hystrix.command.inventory-by-productcode.execution.isolation.thread.timeoutInMilliseconds=2000
hystrix.command.inventory-by-productcode.circuitBreaker.errorThresholdPercentage=60
无论是把配置定义在代码里,还是把配置从代码中抽离出来,都相当于把配置存在了应用程序的本地。这样做的话,如果需要修改配置,就需要重新走一遍代码或者配置的发布流程,在实际的线上业务当中,这是一个很重的操作,往往相当于一次上线发布过程,甚至更繁琐,需要更谨慎。
这时你自然会想,如果能有一个集中管理配置的地方,如果需要修改配置,只需要在这个地方修改一下,线上服务就自动从这个地方同步过去,不需要走代码或者配置的发布流程,不就简单多了吗?没错,这就是下面要讲的配置中心。
配置中心
配置中心的思路就是把服务的各种配置,如代码里配置的各种参数、服务降级的开关甚至依赖的资源等都在一个地方统一进行管理。服务启动时,可以自动从配置中心中拉取所需的配置,并且如果有配置变更的情况,同样可以自动从配置中心拉取最新的配置信息,服务无须重新发布。
具体来讲,配置中心一般包含下面几个功能:
配置注册功能
配置反注册功能
配置查看功能
配置变更订阅功能
接下来我来给你详细讲解下配置中心的功能是如何实现的。
1. 配置存储结构
如下图所示一般来讲配置中心存储配置是按照Group来存储的同一类配置放在一个Group下以K, V键值对存储。
2. 配置注册
配置中心对外提供接口/config/service?action=register来完成配置注册功能需要传递的参数包括配置对应的分组Group以及对应的Key、Value值。比如调用下面接口请求就会向配置项global.property中添加Key为reload.locations、Value为/data1/confs/system/reload.properties的配置。
curl "http://ip:port/config/service?action=register" -d "group=global.property&key=reload.locations&value=/data1/confs/system/reload.properties"
3. 配置反注册
配置中心对外提供接口config/service?action=unregister来完成配置反注册功能需要传递的参数包括配置对象的分组Group以及对应的Key。比如调用下面的接口请求就会从配置项global.property中把Key为reload.locations的配置删除。
curl "http://ip:port/config/service?action=unregister"-d "group=global.property&key=reload.locations"
4. 配置查看
配置中心对外提供接口config/service?action=lookup来完成配置查看功能需要传递的参数包括配置对象的分组Group以及对应的Key。比如调用下面的接口请求就会返回配置项global.property中Key为reload.locations的配置值。
curl "http://ip:port/config/service?action=lookup&group=global.property&key=reload.locations"
5. 配置变更订阅
配置中心对外提供接口config/service?action=getSign来完成配置变更订阅接口客户端本地会保存一个配置对象的分组Group的sign值同时每隔一段时间去配置中心拉取该Group的sign值与本地保存的sign值做对比。一旦配置中心中的sign值与本地的sign值不同客户端就会从配置中心拉取最新的配置信息。比如调用下面的接口请求就会返回配置项global.property中Key为reload.locations的配置值。
curl "http://ip:port/config/serviceaction=getSign&group=global.property"
讲到这里,你应该对配置中心的作用有所了解了,它可以便于我们管理服务的配置信息,并且如果要修改配置信息的话,只需要同配置中心交互就可以了,应用程序会通过订阅配置中心的配置,自动完成配置更新。那么实际业务中,有哪些场景应用配置中心比较合适呢?下面我就结合自己的经验,列举几个配置中心的典型应用场景,希望能给你一些启发。
资源服务化。对于大部分互联网业务来说在应用规模不大的时候所依赖的资源如Memcached缓存或者MCQ消息队列的数量也不多因此对应的资源的IP可以直接写在配置里。但是当业务规模发展到一定程度后所依赖的这些资源的数量也开始急剧膨胀。以微博的业务为例核心缓存Memcached就有上千台机器经常会遇到个别机器因为硬件故障而不可用这个时候如果采用的是本地配置的话就需要去更改本地配置把不可用的IP改成可用的IP然后发布新的配置这样的过程十分不便。但如果采用资源服务化的话把对应的缓存统统归结为一类配置然后如果有个别机器不可用的话只需要在配置中心把对应的IP换成可用的IP即可应用程序会自动同步到本机也无须发布。
业务动态降级。微服务架构下,拆分的服务越多,出现故障的概率就越大,因此需要有对应的服务治理手段,比如要具备动态降级能力,在依赖的服务出现故障的情况下,可以快速降级对这个服务的调用,从而保证不受影响。为此,服务消费者可以通过订阅依赖服务是否降级的配置,当依赖服务出现故障的时候,通过向配置中心下达指令,修改服务的配置为降级状态,这样服务消费者就可以订阅到配置的变更,从而降级对该服务的调用。
分组流量切换。前面我提到过为了保证异地多活以及本地机房调用一般服务提供者的部署会按照IDC维度进行部署每个IDC划分为一个分组这样的话如果一个IDC出现故障可以把故障IDC机房的调用切换到其他正常IDC。为此服务消费者可以通过订阅依赖服务的分组配置当依赖服务的分组配置发生变更时服务消费者就对应的把调用切换到新的分组从而实现分组流量切换。
开源配置中心与选型
讲到这里,你可以根据我前面对配置中心的讲解自己去实现一个配置中心,但其实对于大部分中小团队来说,目前业界已经开源的配置中心实现可以说功能已经十分完善了,并且经过很多公司实际线上业务的充分论证,能满足大多数业务的需求,所以我建议是尽量选择成熟的开源配置中心实现,那么有哪些开源的配置中心可以使用呢?下面我就简单介绍下三个典型的开源实现:
Spring Cloud Config。Spring Cloud中使用的配置中心组件只支持Java语言配置存储在git中变更配置也需要通过git操作如果配置中心有配置变更需要手动刷新。
Disconf。百度开源的分布式配置管理平台只支持Java语言基于Zookeeper来实现配置变更实时推送给订阅的客户端并且可以通过统一的管理界面来修改配置中心的配置。
Apollo。携程开源的分布式配置中心支持Java和.Net语言客户端和配置中心通过HTTP长连接实现实时推送并且有统一的管理界面来实现配置管理。
在实际选择的时候Spring Cloud Config作为配置中心的功能比较弱只能通过git命令操作而且变更配置的话还需要手动刷新如果不是采用Spring Cloud框架的话不建议选择。而Disconf和Apollo的功能都比较强大在国内许多互联网公司内部都有大量应用其中Apollo对Spring Boot的支持比较好如果应用本身采用的是Spring Boot开发的话集成Apollo会更容易一些。
总结
今天我给你讲解了微服务架构下如何使用配置中心对服务的配置进行管理,以及实际业务中可能用到的场景,最后给出了一些开源配置中心的解决方案。关于业务中是否需要用到配置中心,以及选择哪种配置中心,要根据实际情况而定,如果业务比较简单,配置比较少并且不经常变更的话,采用本地配置是最简单的方案,这样的话不需要额外引入配置中心组件;相反,如果业务比较复杂,配置多而且有动态修改配置的需求的话,强烈建议引入配置中心来进行管理,而且最好做到配置变更实时推送给客户端,并且可以通过统一的管理界面来管理配置,这样的话能极大地降低运维的复杂度,减少人为介入,从而提高效率。
思考题
在前面我讲到Zookeeper、Consul、etcd作为服务的注册中心时可以提供强一致性的服务发现功能那么它们能够作为配置中心吗为什么
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,137 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
23 如何搭建微服务治理平台?
在学习今天的内容前,我们先来回顾下[专栏第9期]。我给你讲过单体应用改造为微服务架构后,服务调用从本地调用变成了远程方法调用后,面临的各种不确定因素变多了,一方面你需要能够监控各个服务的实时运行状态、服务调用的链路和拓扑图;另一方面你需要在出现故障时,能够快速定位故障的原因并可以通过诸如降级、限流、切流量、扩容等手段快速干预止损。这个时候就需要我今天要讲的微服务治理平台了。
那么微服务治理平台都具备哪些功能呢,具体该如何搭建一套微服务治理平台呢?
微服务治理平台的基本功能
你可能先会问,到底什么是微服务治理平台?根据我的理解,微服务治理平台就是与服务打交道的统一入口,无论是开发人员还是运维人员,都能通过这个平台对服务进行各种操作,比如开发人员可以通过这个平台对服务进行降级操作,运维人员可以通过这个平台对服务进行上下线操作,而不需要关心这个操作背后的具体实现。
接下来我就结合下面这张图,给你介绍一下一个微服务治理平台应该具备哪些基本功能。
1. 服务管理
通过微服务治理平台,可以调用注册中心提供的各种管理接口来实现服务的管理。根据我的经验,服务管理一般包括以下几种操作:
服务上下线。当上线一个新服务的时候,可以通过调用注册中心的服务添加接口,新添加一个服务,同样要下线一个已有服务的时候,也可以通过调用注册中心的服务注销接口,删除一个服务。
节点添加/删除。当需要给服务新添加节点时候,可以通过调用注册中心的节点注册接口,来给服务新增加一个节点。而当有故障节点出现或者想临时下线一些节点时,可以通过调用注册中心的节点反注册接口,来删除节点。
服务查询。这个操作会调用注册中心的服务查询接口,可以查询当前注册中心里共注册了多少个服务,每个服务的详细信息。
服务节点查询。这个操作会调用注册中心的节点查询接口,来查询某个服务下一共有多少个节点。
2. 服务治理
通过微服务治理平台,可以调用配置中心提供的接口,动态地修改各种配置来实现服务的治理。根据我的经验,常用的服务治理手段包括以下几种:
限流。一般是在系统出现故障的时候,比如像微博因为热点突发事件的发生,可能会在短时间内流量翻几倍,超出系统的最大容量。这个时候就需要调用配置中心的接口,去修改非核心服务的限流阈值,从而减少非核心服务的调用,给核心服务留出充足的冗余度。
降级。跟限流一样,降级也是系统出现故障时的应对方案。要么是因为突发流量的到来,导致系统的容量不足,这时可以通过降级一些非核心业务,来增加系统的冗余度;要么是因为某些依赖服务的问题,导致系统被拖慢,这时可以降级对依赖服务的调用,避免被拖死。
切流量。通常为了服务的异地容灾考虑服务部署在不止一个IDC内。当某个IDC因为电缆被挖断、机房断电等不可抗力时需要把故障IDC的流量切换到其他正常IDC这时候可以调用配置中心的接口向所有订阅了故障IDC服务的消费者下发指令将流量统统切换到其他正常IDC从而避免服务消费者受影响。
3. 服务监控
微服务治理平台一般包括两个层面的监控。一个是整体监控比如服务依赖拓扑图将整个系统内服务间的调用关系和依赖关系进行可视化的展示一个是具体服务监控比如服务的QPS、AvgTime、P999等监控指标。其中整体监控可以使用服务追踪系统提供的服务依赖拓扑图而具体服务监控则可以通过Grafana等监控系统UI来展示。
4. 问题定位
微服务治理平台实现问题定位,可以从两个方面来进行。一个是宏观层面,即通过服务监控来发觉异常,比如某个服务的平均耗时异常导致调用失败;一个是微观层面,即通过服务追踪来具体定位一次用户请求失败具体是因为服务调用全链路的哪一层导致的。
5. 日志查询
微服务治理平台可以通过接入类似ELK的日志系统能够实时地查询某个用户的请求的详细信息或者某一类用户请求的数据统计。
6. 服务运维
微服务治理平台可以调用容器管理平台,来实现常见的运维操作。根据我的经验,服务运维主要包括下面几种操作:
发布部署。当服务有功能变更,需要重新发布部署的时候,可以调用容器管理平台分批按比例进行重新部署,然后发布到线上。
扩缩容。在流量增加或者减少的时候,需要相应地增加或者缩减服务在线上部署的实例,这时候可以调用容器管理平台来扩容或者缩容。
如何搭建微服务治理平台
微服务治理平台之所以能够实现上面所说的功能关键之处就在于它能够封装对微服务架构内的各个基础设施组件的调用从而对外提供统一的服务操作API而且还提供了可视化的界面以方便开发人员和运维人员操作。
根据我的经验一个微服务治理平台的组成主要包括三部分Web Portal层、API层以及数据存储DB层结合下面这张图我来详细讲解下每一层该如何实现。
第一层Web Portal。也就是微服务治理平台的前端展示层一般包含以下几个功能界面
服务管理界面,可以进行节点的操作,比如查询节点、删除节点。
服务治理界面,可以进行服务治理操作,比如切流量、降级等,还可以查看操作记录。
服务监控界面可以查看服务的详细信息比如QPS、AvgTime、耗时分布区间以及P999等。
服务运维界面,可以执行服务的扩缩容操作,还可以查看扩缩容的操作历史。
第二层API。也就是微服务治理平台的后端服务层这一层对应的需要提供Web Portal接口以调用对应的一般包含下面几个接口功能
添加服务接口。这个接口会调用注册中心提供的服务添加接口来新发布一个服务。
删除服务接口。这个接口会调用注册中心提供的服务注销接口来下线一个服务。
服务降级/限流/切流量接口。这几个接口会调用配置中心提供的配置修改接口,来修改对应服务的配置,然后订阅这个服务的消费者就会从配置中心拉取最新的配置,从而实现降级、限流以及流量切换。
服务扩缩容接口。这个接口会调用容器平台提供的扩缩容接口,来实现服务的实例添加和删除。
服务部署接口。这个接口会调用容器平台提供的上线部署接口,来实现服务的线上部署。
第三层DB。也就是微服务治理平台的数据存储层因为微服务治理平台不仅需要调用其他组件提供的接口还需要存储一些基本信息主要分为以下几种
用户权限。因为微服务治理平台的功能十分强大,所以要对用户的权限进行管理。一般可以分为可浏览、可更改以及管理员三个权限。而且还需要对可更改的权限进行细分,按照不同服务的负责人进行权限划分,一个人只能对它负责的服务的进行更改操作,而不能修改其他人负责的服务。
操作记录。用来记录下用户在平台上所进行的变更操作,比如降级记录、扩缩容记录、切流量记录等。
元数据。主要是用来把服务在各个系统中对应的记录映射到微服务治理平台中,统一进行管理。比如某个服务在监控系统里可能有个特殊标识,在注册中心里又使用了另外一个标识,为了统一就需要在微服务治理平台统一进行转换,然后进行数据串联。
总结
可以说一个微服务框架是否成熟,除了要看它是否具备服务治理能力,还要看是否有强大的微服务治理平台。因为微服务治理平台能够将多个系统整合在一起,无论是对开发还是运维来说,都能起到事半功倍的作用,这也是当前大部分开源微服务框架所欠缺的部分,所以对于大部分团队来说,都需要自己搭建微服务治理平台。不过好在微服务治理平台本身的架构并不复杂,你可以根据自己的实际需要,来决定微服务治理平台具备哪些功能。
思考题
除了我上面列举的一些功能以外,你觉得微服务治理平台还可以包含哪些功能?说说你的理由。
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,69 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
24 微服务架构该如何落地?
专栏前面的文章我给你讲解了微服务架构的各个组成部分,以及实践过程中可能遇到的问题和对应的解决方案,到这里你应该对微服务架构有了一个完整的认识。那么在实际项目中,如何让一个团队把我们所学的微服务架构落地呢?
今天我就结合自己的经验,定位在中小规模团队,谈谈微服务架构到底该如何落地。
组建合适的技术团队
经过我前面的讲解你应该认识到微服务架构相比于单体应用来说复杂度提升了很多这其中涉及很多组件比如注册中心、配置中心、RPC框架、监控系统、追踪系统、服务治理等每个组件都需要专门的人甚至专家把控才能hold住不然微服务架构的落地就相当于空中楼阁虚无缥缈。
所以想要落地微服务首先需要合适的人也就是组建一支合适的技术团队。你一定很容易想到是不是只有架构师适合做微服务架构的开发一定程度上这是合理的因为微服务架构所涉及的具体技术比如CAP理论、底层网络可靠性保证、Netty高并发框架等都对技术的深度要求比较高一般有经验的架构师才能掌握所以这个技术团队必须包含技术能力很强的架构师。但是还要考虑到微服务架构最后还是要落地到业务当中既要满足业务的需求也要防止一种情况的发生那就是全部由架构人员组成技术团队根据自己的设想脱离了实际的业务场景最后开发出来的架构中看不中用业务无法实际落地既打击了团队人员积极性又对业务没有实际价值劳民伤财。所以这支技术团队也必须包含做业务懂业务的开发人员只有他们了解业务的实际痛点以及落地过程中的难点这样才能保证最后设计出的微服务架构是贴合业务实际的并且最后是能够实际落地的。
从一个案例入手
当你的团队决定要对业务进行微服务架构改造时,要避免一上来就妄想将整个业务进行服务化拆分、追求完美。这种想法是很危险的,一切的技术改造都应当以给业务创造价值为宗旨,所以业务的稳定性要放在第一位,切忌好高骛远。
正确的方法是首先从众多业务中找到一个小的业务进行试点,前期的技术方案以满足这个小的业务需求为准,力求先把这个小业务的微服务架构落地实施,从中发现各种问题并予以解决,然后才可以继续考虑更大规模的推广。这样的话,即使微服务架构的改造因为技术方案不成熟,对业务造成了影响,也只是局限在一个小的业务之中,不会对整体业务造成太大影响。否则的话,如果因为微服务架构的改造给业务带来灾难性的后果,在许多技术团队的决策者来看,可能微服务架构的所带来的种种好处也不足以抵消其带来的风险,最后整个微服务架构的改造可能就夭折了。
回想一下微博业务的微服务改造从2013年开始进行微服务架构的研发到2014年用户关系服务开始进行微服务改造再到2015年Feed业务开始进行微服务改造从几个服务上线后经过春晚流量的考验后逐步推广到上百个服务的上线整个过程持续了两年多时间。虽然周期比较长但是对于大流量的业务系统来说稳定性永远是在第一位的业务架构改造追求的是稳步推进中间可以有小的波折但对整体架构的演进方向不会产生影响。
做好技术取舍
我在搭建微服务架构的时候其实做的最多的工作就是技术取舍。比如在开发RPC框架的时候是选择自研呢还是采用开源RPC框架呢如果自研的话目前团队系统的主要语言是Java那么RPC框架是只支持Java语言就可以了还是考虑到将来有可能需要支持其他语言呢
我的经验就是一切以业务的实际情况为准,只要满足当前的需求就好,切忌好高骛远,尤其是对于技术能力很强的开发者来说,很容易陷入对技术的完美追求,投入过多精力在架构的雕花工作上,而忽视了眼下业务最实际的需求。尤其是在团队技术人力紧张,开发周期短的时候,更需要集中力量去满足业务最迫切的需求。而对于架构的完善以及一些附加功能的追求,可以在后面业务落地后逐步进行完善。
以微博的服务化框架Motan为例因为微博平台的开发语言主要是Java所以最早Motan只支持Java语言。从2017年开始有了跨语言服务化调用的需求才在此基础上对架构进行了升级加入了对Go、PHP等语言的支持。而且在早期业务开始使用时只开发了最基本的几个核心组件如RPC框架、注册中心和配置中心以及简单的监控系统而服务追踪系统、服务治理平台这些高级的功能都没有后来随着重要业务进行微服务改造的越来越多不断补充技术人力才开始完善服务追踪系统以及服务治理平台。
除此之外,在做技术选型的时候,还要考虑到团队的实际掌控能力,尤其是对一些新技术方案的引入要尤其慎重。如果没有合适的人能够掌控这些技术,那么贸然引入新技术,一旦业务受影响时,如果没有人能有效干预,这对业务来说是灾难性的后果。
微博在做注册中心选型的时候没有选取当时很火的Zookeeper的一个重要原因就是它底层依赖的是HBase存储当时团队中还没有有经验的运维和开发人员但团队对Redis十分了解所以基于Redis存储自研了一套注册中心完全能够满足需求并且又没有引入技术不可控因素。
采用DevOps
微服务架构带来的不光是业务开发模式的改变对测试和运维的影响也是根本性的。以往在单体应用架构时开发只需要整体打包成一个服务交给测试去做自动化测试、交给运维去部署发布就可以了。但是微服务架构下一个单体应用被拆分成多个细的微服务并且需要独自开发、测试和上线如果继续按照之前的单体应用模式运维那么测试和运维的工作量相当于成倍的增加。因此迫切需要对以往的开发、测试和运维模式进行升级从我的经验来看最好的方案就是采用DevOps对微服务架构进行一站式开发、测试、上线和运维。
在单体应用架构下开发、测试和运维这三者角色的区分是十分比较明显的分属于不同的部门。而在微服务架构下由于服务被拆分得足够细每个服务都需要完成独立的开发、测试和运维工作有自己完整的生命周期所以需要将一个服务从代码开发、单元测试、集成测试以及服务发布都自动化起来。这样的话测试人员就可以从众多微服务的测试中解放出来着重进行自动化测试用例的维护运维人员也可以从众多微服务的上线发布工作中解放出来着重进行DevOps体系工具的建设。而每个服务的开发负责人需要对服务的整个生命周期负责无论是在代码检查阶段出现问题还是测试阶段和发布阶段出现问题都需要去解决。
统一微服务治理平台
以前我们只需要关心一个大的单体应用的健康状况,所以团队可以针对大的单体应用专门监控。但进行微服务改造后,拆分出几个甚至上百个服务之后,再靠传统的运维方案去管理,就会显得力不从心了。
而且微服务架构下会衍生出许多新的问题比如RPC调用超时、注册中心获取失败、服务容量不足等有些问题需要开发介入去定位分析而有些问题需要运维介入十分混乱。
微博在进行微服务改造初期,就面临着诸多问题,比如某一个微服务的容量不足了,需要进行扩容,而它所依赖的服务也需要进行扩容,但这种依赖关系只有业务的开发人员清楚,运维人员其实并不知晓详情。还有就是某个服务依赖的另一个服务出现故障,需要紧急降级,而此时如果运维人员操作的话并不知道哪个开关,虽然开发知晓,但开发实际上又没有线上服务器的操作权限。
所以,这时就迫切需要一个微服务治理平台,能够将微服务的服务治理以及各种运维操作都统一管理起来,并且打破开发和运维之间的隔阂,给予同样的权限,让服务的开发人员真正做到对自己的服务负责,不仅要对自己的服务情况了如指掌,还需要能对自己的服务进行治理和运维。
基于此,也就需要开发和运维深入合作,发挥各自专业的特长,将微服务治理的功能以及之前运维系统的基础功能结合在一起,打造成“一站式”微服务治理平台。
总结
今天我给你讲解了微服务架构如何在业务中进行落地总结来讲就是首先你必须组建一支合适的技术团队这其中不仅要包含资深的架构师还需要包含业务的开发者。在选择业务进行微服务架构改造时不能追大求全正确的做法应当是先以一个适当规模的业务进行微服务改造走完整个微服务架构落地的过程从而找出问题不断打磨到成熟可用的状态再推广到更多更重要的业务当中。在改造的过程中要做好技术取舍以团队人员的实际情况以及业务的实际需求为准绳切忌追新立异避免给业务引入不可控因素留下“架构债”。同时微服务架构的过程也是团队组织变革的过程传统意义上的开发、测试和运维明确的分割线会被打破出现一种DevOps工程师的角色他需要对服务全生命周期负责。为了做到这一点就需要一个统一的微服务治理平台融合服务治理和运维的各种功能。
实际上,每个团队都有各自不同的情况,但只要秉承上面这些基本准则,就可以走出一条适合自己团队的微服务架构路线出来,这其中没有高低之分,适合自己的才是最好的。
思考题
传统单体应用下进行测试只需要启动单体应用部署一个测试环境即可进行集成测试,但经过微服务改造后,一个功能依赖了多个微服务,每个微服务都有自己的测试环境,这个时候该如何进行集成测试呢?
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,87 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
25 微服务为什么要容器化?
专栏前面的文章我主要给你讲解了微服务架构的基础组成以及在具体落地实践过程中的会遇到的问题和解决方案这些是掌握微服务架构最基础的知识。从今天开始我们将进一步深入微服务架构进阶的内容也就是微服务与容器、DevOps之间的关系。它们三个虽然分属于不同领域但却有着千丝万缕的关系可以说没有容器的普及就没有微服务架构的蓬勃发展也就没有DevOps今天的盛行其道。
之后我还会具体分析它们三者之间是如何紧密联系的,今天我们先来看微服务为什么要容器化。
微服务带来的问题
单体应用拆分成多个微服务后能够实现快速开发迭代但随之带来的问题是测试和运维部署的成本的提升。相信拆分微服务的利弊你早已耳熟能详我讲个具体的例子。微博业务早期就是一个大的单体Web应用在测试和运维的时候只需要把Web应用打成一个大的WAR包部署到Tomcat中去就行了。后来拆分成多个微服务之后有的业务需求需要同时修改多个微服务的代码这时候就有多个微服务都需要打包、测试和上线发布一个业务需求就需要同时测试多个微服务接口的功能上线发布多个系统给测试和运维的工作量增加了很多。这个时候就需要有办法能够减轻测试和运维的负担我在上一讲给出的解决方案是DevOps。
DevOps可以简单理解为开发和运维的结合服务的开发者不再只负责服务的代码开发还要负责服务的测试、上线发布甚至故障处理等全生命周期过程这样的话就把测试和运维从微服务拆分后所带来的复杂工作中解放出来。DevOps要求开发、测试和发布的流程必须自动化这就需要保证开发人员将自己本地部署测试通过的代码和运行环境能够复制到测试环境中去测试通过后再复制到线上环境进行发布。虽然这个过程看上去好像复制代码一样简单但在现实时本地环境、测试环境以及线上环境往往是隔离的软件配置环境的差异也很大这也导致了开发、测试和发布流程的割裂。
而且还有一个问题是拆分后的微服务相比原来大的单体应用更加灵活经常要根据实际的访问量情况做在线扩缩容而且通常会采用在公有云上创建的ECS来扩缩容。这又给微服务的运维带来另外一个挑战因为公有云上创建的ECS通常只包含了基本的操作系统环境微服务运行依赖的软件配置等需要运维再单独进行初始化工作因为不同的微服务的软件配置依赖不同比如Java服务依赖了JDK就需要在ECS上安装JDK而且可能不同的微服务依赖的JDK版本也不相同一般情况下新的业务可能依赖的版本比较新比如JDK 8而有些旧的业务可能依赖的版本还是JDK 6为此服务部署的初始化工作十分繁琐。
而容器技术的诞生恰恰解决了上面这两个问题为什么容器技术可以解决本地、测试、线上环境的隔离解决部署服务初始化繁琐的问题呢下面我就以业界公认的容器标准Docker为例来看看Docker是如何解决这两个问题的。
什么是Docker
Docker是容器技术的一种事实上已经成为业界公认的容器标准要理解Docker的工作原理首先得知道什么是容器。
容器翻译自英文的Container一词而Container又可以翻译成集装箱。我们都知道集装箱的作用就是在港口把货物用集装箱封装起来然后经过货轮从海上运输到另一个港口再在港口卸载后通过大货车运送到目的地。这样的话货物在世界的任何地方流转时都是在集装箱里封装好的不需要根据是在货轮上还是大货车上而对货物进行重新装配。同样在软件的世界里容器也起到了相同的作用只不过它封装的是软件的运行环境。容器的本质就是Linux操作系统里的进程但与操作系统中运行的一般进程不同的是容器通过Namespace和Cgroups这两种机制可以拥有自己的root文件系统、自己的网络配置、自己的进程空间甚至是自己的用户ID空间这样的话容器里的进程就像是运行在宿主机上的另外一个单独的操作系统内从而实现与宿主机操作系统里运行的其他进程隔离。
Docker也是基于Linux内核的Cgroups、Namespace机制来实现进程的封装和隔离的那么Docker为何能把容器技术推向一个新的高度呢这就要从Docker在容器技术上的一项创新Docker镜像说起。虽然容器解决了应用程序运行时隔离的问题但是要想实现应用能够从一台机器迁移到另外一台机器上还能正常运行就必须保证另外一台机器上的操作系统是一致的而且应用程序依赖的各种环境也必须是一致的。Docker镜像恰恰就解决了这个痛点具体来讲就是Docker镜像不光可以打包应用程序本身而且还可以打包应用程序的所有依赖甚至可以包含整个操作系统。这样的话你在你自己本机上运行通过的应用程序就可以使用Docker镜像把应用程序文件、所有依赖的软件以及操作系统本身都打包成一个镜像可以在任何一个安装了Docker软件的地方运行。
Docker镜像解决了DevOps中微服务运行的环境难以在本地环境、测试环境以及线上环境保持一致的难题。如此一来开发就可以把在本地环境中运行测试通过的代码以及依赖的软件和操作系统本身打包成一个镜像然后自动部署在测试环境中进行测试测试通过后再自动发布到线上环境上去整个开发、测试和发布的流程就打通了。
同时无论是使用内部物理机还是公有云的机器部署服务都可以利用Docker镜像把微服务运行环境封装起来从而屏蔽机器内部物理机和公有云机器运行环境的差异实现同等对待降低了运维的复杂度。
微服务容器化实践
Docker能帮助解决服务运行环境可迁移问题的关键就在于Docker镜像的使用上实际在使用Docker镜像的时候往往并不是把业务代码、依赖的软件环境以及操作系统本身直接都打包成一个镜像而是利用Docker镜像的分层机制在每一层通过编写Dockerfile文件来逐层打包镜像。这是因为虽然不同的微服务依赖的软件环境不同但是还是存在大大小小的相同之处因此在打包Docker镜像的时候可以分层设计、逐层复用这样的话可以减少每一层镜像文件的大小。
下面我就以微博的业务Docker镜像为例来实际讲解下生产环境中如何使用Docker镜像。正如下面这张图所描述的那样微博的Docker镜像大致分为四层。
基础环境层。这一层定义操作系统运行的版本、时区、语言、yum源、TERM等。
运行时环境层。这一层定义了业务代码的运行时环境比如Java代码的运行时环境JDK的版本。
Web容器层。这一层定义了业务代码运行的容器的配置比如Tomcat容器的JVM参数。
业务代码层。这一层定义了实际的业务代码的版本比如是V4业务还是blossom业务。
这样的话每一层的镜像都是在上一层镜像的基础上添加新的内容组成的以微博V4镜像为例V4业务的Dockerfile文件内容如下
FROM registry.intra.weibo.com/weibo_rd_content/tomcat_feed:jdk8.0.40_tomcat7.0.81_g1_dns
ADD confs /data1/confs/
ADD node_pool /data1/node_pool/
ADD authconfs /data1/authconfs/
ADD authkey.properties /data1/
ADD watchman.properties /data1/
ADD 200.sh /data1/weibo/bin/200.sh
ADD 503.sh /data1/weibo/bin/503.sh
ADD catalina.sh /data1/weibo/bin/catalina.sh
ADD server.xml /data1/weibo/conf/server.xml
ADD logging.properties /data1/weibo/conf/logging.properties
ADD ROOT /data1/weibo/webapps/ROOT/
RUN chmod +x /data1/weibo/bin/200.sh /data1/weibo/bin/503.sh /data1/weibo/bin/catalina.sh
WORKDIR /data1/weibo/bin
FROM代表了上一层镜像文件是“tomcat_feed:jdk8.0.40_tomcat7.0.81_g1_dns”从名字可以看出上一层镜像里包含了Java运行时环境JDK和Web容器Tomcat以及Tomcat的版本和JVM参数等ADD就是要在这层镜像里添加的文件 这里主要包含了业务的代码和配置等RUN代表这一层镜像启动时需要执行的命令WORKDIR代表了这一层镜像启动后的工作目录。这样的话就可以通过Dockerfile文件在上一层镜像的基础上完成这一层镜像的制作。
总结
今天我给你讲解了微服务拆分后相比于传统的单体应用所带来的两个问题一个是测试和发布工作量的提升另一个是在弹性扩缩容时不同微服务所要求的软件运行环境差异带来的机器初始化复杂度的提升而Docker利用Docker镜像对软件运行环境的完美封装正好解决了这两个问题。
正是因为Docker可以做到一处通过、到处运行所以对业务的价值极大解决了以前应用程序在开发环境、测试环境以及生产环境之间的移植难的问题极大提高了运维自动化的水平也为DevOps理念的流行和业务上云提供了基础。
可见容器化改造对微服务是十分必要的但Docker也不是“银弹”同样会产生新的复杂度问题比如引入Docker后旧的针对物理机的运维模式就无法适应了需要一种新的针对容器的运维模式。所以接下来我将分三期给你详细讲解微服务容器化后该如何运维。
思考题
Docker的概念乍一看与虚拟机有些类似你认为它们有什么不同之处吗分别适合什么应用场景
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,93 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
26 微服务容器化运维:镜像仓库和资源调度
专栏上一期我给你讲解了容器化技术解决了单体应用拆分为微服务后,所带来的服务测试和发布运维复杂度提升的问题,可以说容器化技术天生就是为微服务而生。但微服务容器化后又带来了一个新的挑战,那就是容器如何运维的问题。
为什么微服务容器化的运维又成了新问题?
对于大部分业务团队来说在进行容器化以前服务都是部署在物理机或者虚拟机上运维往往有一套既有的运维平台来发布服务。我就以微博的运维平台JPool来举例当有服务要发布的时候JPool会根据服务所属的集群一般一个业务线是一个集群运行在哪个服务池一般一个业务线有多个服务池找到对应的物理机或者虚拟机IP然后把最新的应用程序代码通过Puppet等工具分批逐次地发布到这些物理机或者虚拟机上然后重新启动服务这样就完成一个服务的发布流程。
但是现在情况变了业务容器化后运维面对的不再是一台台实实在在的物理机或者虚拟机了而是一个个Docker容器它们可能都没有固定的IP这个时候要想服务发布该怎么做呢
这时候就需要一个面向容器的新型运维平台,它能够在现有的物理机或者虚拟机上创建容器,并且能够像运维物理机或者虚拟机一样,对容器的生命周期进行管理,通常我们叫它“容器运维平台”。
根据我的经验一个容器运维平台通常包含以下几个组成部分镜像仓库、资源调度、容器调度和服务编排。所以关于微服务容器化运维的内容我会分为3期今天先来看容器运维平台的镜像仓库和资源调度后面两期会介绍容器调度、服务编排和微博容器运维平台的建设。
镜像仓库
Docker容器运行依托的是Docker镜像也就是说要发布服务首先必须把镜像发布到各个机器上去这个时候问题就来了这个镜像该放在哪如何把镜像发布到各个机器上去这时候你就要依靠镜像仓库了。
镜像仓库的概念其实跟Git代码仓库类似就是有一个集中存储的地方把镜像存储在这里在服务发布的时候各个服务器都访问这个集中存储来拉取镜像然后启动容器。
Docker官方提供了一个镜像仓库地址https://hub.docker.com/,对于测试应用或者小规模的业务可以直接使用。但对于大部分业务团队来说,出于安全和访问速度的需要,都会搭建一套私有的镜像仓库。那么具体该如何搭建一套私有的镜像仓库呢?下面我就结合微博的实践,和你聊聊这里面的门道。
1.权限控制
镜像仓库首先面临的第一个问题就是权限控制的问题,也就是说哪些用户可以拉取镜像,哪些用户可以修改镜像。
一般来说,镜像仓库都设有两层权限控制:一是必须登录才可以访问,这是最外层的控制,它规定了哪些人可以访问镜像仓库;二是对镜像按照项目的方式进行划分,每个项目拥有自己的镜像仓库目录,并且给每个项目设置项目管理员、开发者以及客人这三个角色,只有项目管理员和开发者拥有自己镜像仓库目录下镜像的修改权限,而客人只拥有访问权限,项目管理员可以给这个项目设置哪些人是开发者。
这个权限控制就跟大厦办公楼的管理类似,你要进入大厦里的一个办公室,首先必须具备进入大厦的权限,这个权限是在大厦里所有办公的人都有的。然后你还得具备大厦里你办公室所在楼层的门禁,这样才能进入办公室。不同楼层的人权限不同,只能进入自己楼层的办公室。如果某个办公室有新来的员工,首先要给他分配大厦的进入权限,然后还要这个办公室的管理员给他分配办公室的权限。是不是这样讲权限控制就好理解一些了呢。
2.镜像同步
在实际的生产环境中,往往需要把镜像同时发布到几十台或者上百台集群节点上,单个镜像仓库实例往往受带宽原因限制无法同时满足大量节点的下载需求,这个时候就需要配置多个镜像仓库实例来做负载均衡,同时也就产生镜像在多个镜像仓库实例之间同步的问题了。显然通过手工维护十分繁琐,那有什么好的办法吗?
一般来说有两种方案一种是一主多从主从复制的方案比如开源镜像仓库Harbor采用了这种方案另一种是P2P的方案比如阿里的容器镜像分发系统蜻蜓采用了P2P方案。微博的镜像仓库是基于Harbor搭建的所以这里我就以Harbor为例介绍镜像同步机制。
Harbor所采取的主从复制的方案是把镜像传到一个主镜像仓库实例上去然后其他从镜像仓库实例都从主镜像仓库实例同步它的实现就像下图所描述的一样。
除此之外Harbor还支持层次型的发布方式如果集群部署在多个IDC可以先从一个主IDC的镜像仓库同步到其他从IDC的镜像仓库再从各个从IDC同步给下面的分IDC它的实现就像下图所描述的一样。
3.高可用性
既然Docker镜像是Docker容器运行的基础那么镜像仓库的高可用性就不言而喻了。一般而言高可用性设计无非就是把服务部署在多个IDC这样的话即使有IDC出问题也可以把服务迁移到别的正常IDC中去。同样对于镜像仓库的搭建也可以采用多IDC部署那么需要做到的就是不同IDC之间的镜像同步。以微博的镜像仓库为例就像下图所描述的那样镜像仓库会部署在永丰、土城两个内网IDC内两个IDC内的镜像同步采用Harbor的双主复制策略互相复制镜像这样的话即使有一个IDC出现问题另外一个IDC仍然能够提供服务而且不丢失数据。
资源调度
解决了Docker镜像存储和访问的问题后新问题又随之而来了Docker镜像要分发到哪些机器上去这些机器是从哪里来的这其实涉及的是资源调度的问题。
根据我的经验,服务部署的集群主要包括三种:
1.物理机集群。大部分中小团队应该都拥有自己的物理机集群,并且大多按照集群 - 服务池 - 服务器这种模式进行运维。物理机集群面临的问题主要是服务器的配置不统一尤其对于计算节点来说普遍存在的一种情况就是几年前采购的机器的配置可能还是12核16G内存的配置而近些年采购的机器都至少是32核32G内存的配置对于这两种机器往往要区别对待比如旧的机器用于跑一些非核心占用资源量不大的业务而新采购的机器用于跑一些核心且服务调用量高的业务。
2.虚拟机集群。不少业务团队在使用物理机集群之后发现物理机集群存在使用率不高、业务迁移不灵活的问题因此纷纷转向了虚拟化方向构建自己的私有云比如以OpenStack技术为主的私有云集群在国内外不少业务团队都有大规模的应用。它的最大好处就是可以整合企业内部的服务器资源通过虚拟化技术进行按需分配提高集群的资源使用率节省成本。
3.公有云集群。现在越来越多的业务团队,尤其是初创公司,因为公有云快速灵活的特性,纷纷在公有云上搭建自己的业务。公有云最大的好处除了快速灵活、分钟级即可实现上百台机器的创建,还有个好处就是配置统一、便于管理,不存在机器配置碎片化问题。
为了解决资源调度的问题Docker官方提供了Docker Machine功能通过Docker Machine可以在企业内部的物理机集群或者虚拟机集群比如OpenStack集群又或者公有云集群比如AWS集群等上创建机器并且直接部署容器。Docker Machine的功能虽然很好但是对于大部分已经发展了一段时间的业务团队来说并不能直接拿来使用。
这主要是因为资源调度最大的难点不在于机器的创建和容器的部署而在于如何对接各个不同的集群统一管理来自不同集群的机器权限管理、成本核算以及环境初始化等操作这个时候就需要有一个统一的层来完成这个操作。这个对有历史包袱的团队比如公司内网的物理机集群已经有一套运维体系来说挑战不小需要针对新的模式重新开发这套运维平台。以微博的业务为例为了满足内部三种不同集群资源的统一管理专门研发了容器运维平台DCP来实现对接多个不同的集群。它的难点在于不仅对外要对接不同的云厂商针对不同云厂商提供的ECS创建的API统一封装一层API来实现机器管理对内也要针对私有云上不同集群的机器进行管理进行上下线和配置初始化等操作。
以DCP配置初始化操作为例在创建完主机后还需要在主机上进行安装NTP服务、修改sysctl配置、安装Docker软件等操作这时候就需要借助配置管理软件来向主机上进行分发。因为微博内网的主机之前都是通过Puppet进行分发的考虑到稳定性并没有对这一部分进行修改而针对阿里云上创建的主机则使用的是编程功能更为强大的Ansible进行分发。
更多有关DCP的内容我会在容器化运维系列的第三期跟你仔细聊聊。
总结
今天我给你讲解了容器运维平台的两个关键组成,镜像仓库和资源调度。
镜像仓库帮我们解决的是Docker镜像如何存储和访问的问题在业务规模较大时各个业务团队都需要搭建自己的私有镜像仓库。类似Harbor这种开源解决方案能很好地解决权限控制、镜像同步等基本问题关于高可用性的要求以及上云支持等业务场景你可以参考我给出的解决方案它是经过微博实际线上业务验证过的。
资源调度帮我们解决的是如何整合来自不同的集群的资源的问题,如果你的业务不止在内部私有云上部署,在公有云上也有部署,甚至是采用了多家公有云,那么资源的调度将会是非常复杂的问题,尤其是在公司内部已经存在一套对接内部集群的运维管理平台的情况下,是升级已有的运维平台以支持公有云,还是直接开发另外一套新的能够实现多云对接,这是一个很现实的问题。我的建议是单独开发一套新的运维平台先来接管公有云,然后逐步迁移内部集群的管理工作到新的运维平台中。
今天就讲到这里,关于容器运维平台的另外两个关键组成:容器调度和服务编排,我们下期再聊。
思考题
在讲解镜像仓库解决方案时除了Harbor这种主从镜像复制的方案以外我还提到了P2P的方案你觉得这两种方案有何区别分别适用哪种业务场景
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,103 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
27 微服务容器化运维:容器调度和服务编排
专栏上一期我给你讲解了容器运维平台的两个关键组成镜像仓库和资源调度。复习一下镜像仓库解决的是Docker镜像存储和访问的问题资源调度决定了Docker镜像可以分发到哪些机器上的问题。这两个问题解决后你就该考虑如何在集群中创建容器也就是容器如何调度的问题容器创建后如何运作才能对外提供服务也就是服务如何编排的问题。下面我们就一起看看容器调度和服务编排都是如何解决的。
容器调度
容器调度的问题,说的是现在集群里有一批可用的物理机或者虚拟机,当服务需要发布的时候,该选择哪些机器部署容器的问题。
比如集群里只有10台机器并且已经有5台机器运行着其他容器剩余5台机器空闲着如果此时有一个服务要发布但只需要3台机器就行了这个时候可以靠运维人为的从5台空闲的机器中选取3台机器然后把服务的Docker镜像下载下来再启动Docker容器服务就算完成发布。但如果集群机器的规模扩大到几十台或者上百台时要发布的服务也有几十个或者上百个的时候由于每个服务对容器的要求以及每台机器上正在运行的容器情况变得很复杂就不太可能靠人肉运维了。
这时就需要有专门的容器调度系统了为此也诞生了不少基于Docker的容器调度系统比如Docker原生的调度系统Swarm、Mesosphere出品的Mesos以及Google开源的大名鼎鼎的Kubernetes。下面我就结合微博的实践经验给你讲讲容器调度要解决哪些问题。
1.主机过滤
主机过滤是为了解决容器创建时什么样的机器可以使用的问题,主要包含两种过滤。
存活过滤。也就是说必须选择存活的节点,因为主机也有可能下线或者是故障状态。
硬件过滤。打个比方现在你面对的集群有Web集群、RPC集群、缓存集群以及大数据集群等不同的集群硬件配置差异很大比如Web集群往往用作计算节点它的CPU一般配置比较高而大数据集群往往用作数据存储它的磁盘一般配置比较高。这样的话如果要创建计算任务的容器显然就需要选择Web集群而不是大数据集群。
上面这两种过滤方式都是针对主机层次的过滤方式除此之外Swarm还提供了容器层次的过滤可以实现只有运行了某个容器的主机才会被加入候选集等功能。
2.调度策略
调度策略主要是为了解决容器创建时选择哪些主机最合适的问题一般都是通过给主机打分来实现的。比如Swarm就包含了两种类似的策略spread和binpack它们都会根据每台主机的可用CPU、内存以及正在运行的容器的数量来给每台主机打分。spread策略会选择一个资源使用最少的节点以使容器尽可能的分布在不同的主机上运行。它的好处是可以使每台主机的负载都比较平均而且如果有一台主机有故障受影响的容器也最少。而binpack策略恰恰相反它会选择一个资源使用最多的节点好让容器尽可能的运行在少数机器上节省资源的同时也避免了主机使用资源的碎片化。
具体选择哪种调度策略,还是要看实际的业务场景,通常的场景有:
各主机的配置基本相同,并且使用也比较简单,一台主机上只创建一个容器。这样的话,每次创建容器的时候,直接从还没有创建过容器的主机当中随机选择一台就可以了。
在某些在线、离线业务混布的场景下为了达到主机资源使用率最高的目标需要综合考量容器中跑的任务的特点比如在线业务主要使用CPU资源而离线业务主要使用磁盘和I/O资源这两种业务的容器大部分情况下适合混跑在一起。
还有一种业务场景主机上的资源都是充足的每个容器只要划定了所用的资源限制理论上跑在一起是没有问题的但是某些时候会出现对每个资源的抢占比如都是CPU密集型或者I/O密集型的业务就不适合容器混布在一台主机上。
所以实际的业务场景对调度策略的要求比较灵活如果Swarm提供的spread和binpack满足不了的话可能就需要考虑自行研发容器调度器了。
服务编排
1.服务依赖
大部分情况下微服务之间是相互独立的在进行容器调度的时候不需要考虑彼此。但有时候也会存在一些场景比如服务A调度的前提必须是先有服务B这样的话就要求在进行容器调度的时候还需要考虑服务之间的依赖关系。
为此Docker官方提供了Docker Compose的解决方案。它允许用户通过一个单独的docker-compose.yaml文件来定义一组相互关联的容器组成一个项目从而以项目的形式来管理应用。比如要实现一个Web项目不仅要创建Web容器比如Tomcat容器还需要创建数据库容器比如MySQL容器、负载均衡容器比如Nginx容器等这个时候就可以通过docker-compose.yaml来配置这个Web项目里包含的三个容器的创建。
Docker Compose这种通过yaml文件来进行服务编排的方式是比较普遍的算法以微博的业务为例也是通过类似yaml文件的方式定义了服务扩容的模板模板除了定义了服务创建容器时的镜像配置、服务池配置以及主机资源配置以外还定义了关联依赖服务的配置。比如微博的Feed服务依赖了user服务和card服务假如user服务扩容的模板ID为1703271839530000card服务扩容的模板ID为1707061802000000那么Feed服务的扩容模板里就会像下面这样配置它代表了每扩容10台Feed服务的容器就需要扩容4台user服务的容器以及3台card服务的容器。
{"Sid":1703271839530000,"Ratio":0.4}
{"Sid":1707061802000000,"Ratio":0.3}
2.服务发现
容器调度完成以后,容器就可以启动了,但此时容器还不能对外提供服务,服务消费者并不知道这个新的节点,所以必须具备服务发现机制,使得新的容器节点能够加入到线上服务中去。
根据我的经验比较常用的服务发现机制包括两种一种是基于Nginx的服务发现一种是基于注册中心的服务发现。
基于Nginx的服务发现
这种主要是针对提供HTTP服务的当有新的容器节点时修改Nginx的节点列表配置然后利用Nginx的reload机制会重新读取配置从而把新的节点加载进来。比如基于Consul-Template和Consul把Consul作为DB存储容器的节点列表Consul-Template部署在Nginx上Consul-Template定期去请求Consul如果Consul中存储的节点列表发生变化就会更新Nginx的本地配置文件然后Nginx就会重新加载配置。
基于注册中心的服务发现
这种主要是针对提供RPC服务的当有新的容器节点时需要调用注册中心提供的服务注册接口。注册中心的服务发现机制在[专栏第5]期我有过详细讲解你可以再回顾一下它的原理。在使用这种方式时如果服务部署在多个IDC就要求容器节点分IDC进行注册以便实现同IDC内就近访问。以微博的业务为例微博服务除了部署在内部的两个IDC还在阿里云上也有部署这样的话内部机房上创建的容器节点就应该加入到内部IDC分组而云上的节点应该加入到阿里云的IDC。
3.自动扩缩容
容器完成调度后,仅仅做到有容器不可用时故障自愈还不够,有时候还需要根据实际服务的运行状况,做到自动扩缩容。
一个很常见的场景就是大部分互联网业务的访问呈现出访问时间的规律性。以微博业务为例白天和晚上的使用人数要远远大于凌晨的使用人数而白天和晚上的使用人数也不是平均分布的午高峰12点半和晚高峰10点半是使用人数最多的时刻。这个时候就需要根据实际使用需求在午高峰和晚高峰的时刻增加容器的数量确保服务的稳定性在凌晨以后减少容器的数量减少服务使用的资源成本。
常见的自动扩缩容的做法是根据容器的CPU负载情况来设置一个扩缩容的容器数量或者比例比如可以设定容器的CPU使用率不超过50%,一旦超过这个使用率就扩容一倍的机器。
总结
今天我给你讲解了容器运维平台的另外两个关键组成:容器调度和服务编排,并给出了常用的解决方案。你的业务团队在选择解决方案时,要根据自己的需要选择合适的方案,而不是理论上最好的。
比如Kubernetes解决方案在容器调度、服务编排方面都有成熟的组件并且经过大业务量的实际验证。但是要考虑到Kubernetes本身的复杂性以及概念理解的门槛对于大部分中小业务团队来说在生产环境上使用Kubernetes都会显得大材小用并且还需要部署并运维Kubernetes周边的一些基础设施比如etcd等。
相比之下Docker原生自带的解决方案Swarm和Compose就要简单得多但是功能也比较有限如果不能满足你的业务需求的话也不好再二次开发。
在了解了镜像仓库、资源调度、容器调度、服务编排后你会发现,微服务容器化后最大的挑战其实来自于原有运维设施如何支持容器的运维,是在原有运维平台上升级还是完全采用新的容器运维平台,这才是关键,往往不能一蹴而就,需要逐步按照业务进行替换升级。但是考虑到微服务容器化后所带来的种种好处,采用新的运维模式势在必行。
思考题
容器调度方面业界最有名的莫过于Swarm、Mesos和Kubernetes了你认为它们的优缺点是什么分别适合什么业务场景
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,129 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
28 微服务容器化运维微博容器运维平台DCP
微服务容器化运维系列的前两期,我给你详细介绍了微服务容器化后如何运维的几个关键问题:镜像仓库、资源调度、容器调度、服务编排,这些问题的产生都是因为微服务部署的节点从一台台物理机或者虚拟机变成了一个个容器,运维模式发生了根本性的变化。此时,容器运维平台也就应运而生。
微博的业务从2013年就开始进行容器化2015年为了应对春晚以及突发热点事件带来的峰值流量开始引入阿里云同时也为了适应业务的发展和运维方式的变化在2015年底开始研发新的容器运维平台DCP。今天我就和你聊聊微博容器运维平台DCP我会讲讲一个真实的容器运维平台是如何建设的在建设过程中面临了哪些问题以及对应的解决方案希望可以让你对容器运维平台的架构有所了解并提供一些经验可供借鉴。
DCP整体架构
首先我们先来看看DCP的架构设计从下面这张架构图你可以看到DCP的架构主要分为四个部分基础设施层、主机层、调度层、编排层对应的分别解决前面提到的容器运维平台建设的几个关键问题基础设施层用于解决镜像仓库的问题主机层主要解决如何进行资源调度的问题调度层主要解决容器如何在资源上创建的问题编排层主要解决容器如何运作以对外提供服务的问题。下面我们来看各层的详细设计。
基础设施层
DCP中基础设施层主要用于提供各种基础设施以保证其他层功能的正常运行。通常来讲主要包括以下几个基础组件用于存放容器镜像的镜像仓库、提供监控服务的监控中心、实时监控系统容量以便于自动扩缩容的容量评估系统以及容器创建后如何加入线上服务的服务发现组件其中镜像仓库是DCP最核心的基础组件。
正如[专栏第26期]我讲的那样DCP以开源镜像仓库Harbor为基础搭建了私有的镜像仓库不过由于微博业务的特征为了应对随时可能到来的突发峰值流量的冲击需要随时随地能够扩容服务池。但在内网冗余度不足的时候也不得不借助公有云来实现因此服务不仅在内网私有云上有部署在阿里云上也有部署这样的话从阿里云申请的主机也需要从镜像仓库中拉取镜像。此时如果镜像仓库只在内网部署的话就需要跨专线去拉取镜像但如果上百台服务器同时拉取镜像带宽占用很可能达到上百G由于专线带宽是有限的显然这样不可取。为此正确的做法就像下图中那样在阿里云机房也部署一套镜像仓库并且通过Harbor的主从复制机制与内网的镜像仓库保持同步。同时为了做到负载均衡每个机房内部都部署了多个Harbor节点内网节点访问内网镜像仓库会通过LVS进行负载均衡阿里云上节点访问阿里云镜像仓库会通过SLB进行负载均衡以满足镜像仓库的带宽需求。
主机层
DCP中主机层的功能主要是为了完成资源的调度也就是针对不同的集群完成主机的创建、成本的管理以及配置初始化工作也叫Pluto层。前面提到过微博业务不仅在内网私有云上有部署而且在阿里云上也有部署为此Pluto需要适配不同底层提供的创建主机的API进行成本核算并且进行配置初始化操作。Pluto层的架构你可以参看下图我来详细讲一下。
1.主机创建
Pluto在创建主机时主要有两个来源一个是内部物理机组成的共享池一个是调用阿里云API创建ECS。其中共享池内的资源主要来源于两部分一部分是冗余度高的服务池缩容部分主机加入到共享池一部分是在线业务和离线计算互相补充比如白天在线业务需要的机器多而离线计算的任务主要运行在凌晨这时候就可以在白天把离线计算的集群的部分机器加入到共享池给在线业务使用而在晚上业务低峰期把在线业务的部分机器加入到共享池给离线计算任务使用。而使用阿里云创建ECS主要是在共享池内的资源不足的情况下比如有突发热点事件到来各个服务池都需要紧急扩容这时候共享池内的资源就不足以应对了。而使用阿里云API创建ECS会受到阿里云API的各种限制下面我列举几个微博在使用阿里云创建机器时所遇到的问题你就可以理解主机创建的复杂性所在了。
由于阿里云API对单账户的调用有并发限制所以实际业务在创建阿里云ECS上时不能上百台同时创建一般要控制在几十台的规模左右如果这个时候业务需要创建上百台机器该怎么做呢那就需要采取队列机制来控制机器创建的速度。下面这张图就描述了微博在使用阿里云创建ECS时的解决方案在实际创建ECS时不会立即调用阿里云API而是把节点创建任务先放到一个DB队列中然后再通过一个线程定时从DB队列中获取创建任务每次只创建几十台这样的话就不会触发阿里云API对单账号调用的并发限制。
除了有单账户调用的并发限制还会有可用区的库存限制、安全组库存限制以及vSwitch库存限制所以在实际使用阿里云API创建ECS时当机器规模较大如果直接指定使用某个可用区、安全组和vSwitch就可能因为库存原因导致创建失败。微博一开始就使用了这种方案但在突发峰值流量来临时往往要创建几百台甚至上千台的阿里云ECS为此经常会因为以上限制导致创建失败。后来针对可用区、安全组以及vSwitch都做了多可用区、多安全组以及多vSwtich配置在出现库存不够时就自动切换到别的地方来创建极大提高了大规模ECS创建的成功率。
2.成本管理
无论是从共享池内创建的机器还是调用阿里云API创建的ECS都是有成本的为此必须对机器的数量以及使用时长进行记录以便进行成本管理。
以阿里云的ECS为例又分为按量付费、按月付费以及按年付费可以按照以下方式来进行管理。
按量付费。按照使用时长以秒为单位计费适合突发流量到来临时需要扩容部分机器时使用所以需要记录每台ECS从调用API创建成功到销毁所使用的时长。
按月付费。这种比较适合短期业务需要使用机器的场景,比如微博曾经在奥运会期间扩容过大量包月付费的机器,以应对奥运会期间带来的流量上涨。需要注意的是,这种机器到了月底会自动销毁,所以如果还有使用需要的话,需要及时续费。
按年付费。这种比较适合需要长期在阿里云上部署的业务,比如有一些新的业务因为业务发展比较快,采用传统自采机器部署的话,由于采购周期比较长不适合业务发展,所以使用公有云更为合适。
3.配置初始化
主机创建完成后还要进行一些基础软件的安装以及配置修改等工作这就是配置初始化的过程。以阿里云创建的ECS为例如果短时间内创建了上千台ECS这个时候配置初始化的工作量会非常大需要同时给上千台ECS下发配置文件并安装基础软件同时还需要记录每台ECS的初始化状态到DB以便查询是否初始化成功。下图描述了初始化的过程DCP在进行主机配置初始化时会通过Ansible向所有主机下发配置文件和基础软件并通过自定义callback queue把每台主机的初始化状态异步写入到DB中避免上百台机器同时并发写入DB造成死锁。
调度层
DCP中调度层的主要功能是在可用的主机上创建容器。由于微博业务早在2013年就开始进行容器化基于当时的背景考虑就选择了Swarm作为容器调度的工具并根据自己的业务特点在Swarm基础上进行二次封装定制了自己的调度层Roam使其具备支持跨IDC、高可用以及可扩展的特性。下面是Roam的架构其主要工作原理是
Swarm Manager和Swarm Client节点都向Consul中注册并且有一个Active Manager和Standby Manager。任何一个IDC内的Active Manager如果down掉的话Standby Manager就会注册到Consul中成为新的Active Manager以保证高可用性。
当发起容器调度时Roam根据IDC参数请求Consul得到该IDC的Swarm Manager信息。
Roam访问该IDC内的Swarm ManagerSwarm Manager再访问Consul获取Swarm Client信息并根据Roam传递的调度策略从Swarm Client中选择节点创建容器。
编排层
DCP中编排层的主要作用是对服务进行整合以对外提供服务主要包括服务依赖、服务发现以及自动扩缩容下面我来详细介绍每一部分的具体实现。
1.服务依赖
DCP通过模板来管理容器的创建一个服务如果需要进行扩容、创建容器就必须按照模板里定义的参数来执行以下图描述的DCP里的一个扩容任务创建模板为例通常来讲模板里定义的参数主要包括几个部分任务的名称、机器的配置、任务依赖、任务详细配置包括调用阿里云API创建ECS时的可用区、安全组参数等其中任务依赖的配置项是
{"Sid":1707061842070000,"Ratio":0.2,"ElasticCount":0}
{"Sid":1703271821000000,"Ratio":0.3,"ElasticCount":0}
它的含义是执行这个扩容任务时会自动执行ID为1707061842070000和1703271821000000的扩容任务并且按照每扩容10台容器分别扩容2台和3台依赖容器的比例来执行。
2.服务发现
微博的业务场景主要包含两种服务一种是HTTP服务一种是Motan RPC服务他们分别使用了不同的服务发现方式。
HTTP服务。考虑到传统的基于Nginx的配置Reload机制实现的服务发现方式在高并发访问的情况下会导致吞吐量下降10%左右如果业务频繁变更的话就会受到影响。为此DCP在实际业务中基于Nginx和Consul研发了一种可行的解决方案nginx-upsync-module并且已经开源。
Motan RPC服务。Motan RPC服务在启动时会向注册中心Config Service注册服务并且注册中心支持多IDC部署。像下图所描述的那样正常情况下服务消费者会访问同一个IDC内的服务提供者并且支持在故障的时候可以切换到其他IDC。
3.自动扩缩容
DCP系统实现自动扩缩容主要依靠的是容量决策支持系统由容量决策支持系统来实时监控系统的容量。如下图所示一旦容量决策支持系统检测到某个服务需要进行扩容就会创建扩容任务Config Watcher会监控到扩容任务并通知CronTrigger有调度策略变更。CronTrigger接到扩容任务就会调用Scheduler来具体执行扩容。同时还可以通过API来修改、查询扩缩容的信息也可以通过UI来操作。
总结
今天我给你讲解了微博容器运维平台DCP的架构主要包括基础设施层、主机层、调度层以及编排层并详细介绍了每一层的功能实现以及各自承担的不同职能。下面这张图是一次完整扩容流程包括了资源评估、配额评估、初始化、容器调度、部署服务、服务依赖、服务发现以及自动扩缩容等DCP正是通过把这些过程串联起来实现容器运维的。
思考题
在讲到服务编排时我提到服务之间会存在依赖关系比如服务A依赖服务B假如此时服务A的流量上涨需要对服务A进行扩容这时候有两种方案一种方案是通过自动扩缩容服务A和服务B的扩容完全独立分别按需自动扩缩容一种方案是通过服务依赖扩容服务A之前先扩容服务B你认为这两种方案哪种更好为什么
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,95 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
29 微服务如何实现DevOps
把一个大的单体应用拆分成多个微服务之后,每个服务都可以独立进行开发、测试和运维。但当拆分的微服务足够多时,却又仿佛陷入一个新的泥沼,无论是业务代码的开发还是测试和运维,工作量都比之前提升了很多。
采单体应用架构时一个业务需求只需要修改单体应用的代码然后针对这个单体应用进行测试测试通过后再把单体应用的代码发布到线上即可。而拆分为微服务之后一个大的系统被拆分为多个小的系统一个业务需求可能要同时修改多个微服务的代码这样的话多个微服务都需要进行测试测试通过了都需要把代码发布到线上显然工作量成倍增加。这时候就迫切需要一种新的开发、测试和运维模式来解决这个问题这就是今天我要给你讲的微服务与DevOps。
什么是DevOps
在介绍DevOps之前我先来带你回顾一下传统的业务上线流程开发人员开发完业务代码后把自测通过的代码打包交给测试人员然后测试人员把代码部署在测试环境中进行测试如果测试不通过就反馈bug给开发人员进行修复如果通过开发就把测试通过的代码交给运维人员打包然后运维人员再发布到线上环境中去。可见在传统的开发模式下开发人员、测试人员和运维人员的职责划分十分明确他们往往分属于不同的职能部门一次业务上线流程需要三者之间进行多次沟通整个周期基本上是以天为单位。你肯定会想假如能够把开发、测试和发布流程串联起来就像生产流水线上那样每个步骤完成后就自动执行下一个步骤无须过多的人为干预业务的迭代效率不就能提升很多吗。
没错DevOps的思想正是如此。在我看来DevOps是一种新型的业务研发流程业务的开发人员不仅需要负责业务代码的开发还需要负责业务的测试以及上线发布等全生命周期真正做到掌控服务全流程。DevOps就是下图中心的部分集开发、测试和运维三者角色于一体。
而要实现DevOps就必须开发完成代码开发后能自动进行测试测试通过后能自动发布到线上。对应的这两个过程就是CI和CD具体来讲就是
CIContinuous Integration持续集成。开发完成代码开发后能自动地进行代码检查、单元测试、打包部署到测试环境进行集成测试跑自动化测试用例。
CDContinuous Deploy持续部署。代码测试通过后能自动部署到类生产环境中进行集成测试测试通过后再进行小流量的灰度验证验证通过后代码就达到线上发布的要求了就可以把代码自动部署到线上。
其中CD还有另外一个解释就是持续交付Continuous Delivery它与持续部署不同的是持续交付只需要做到代码达到线上发布要求的阶段就可以了接下来的代码部署到线上既可以选择手动部署也可以选择自动部署。实际服务发布时代码能否自动部署到线上本身并不是难点关键在于是否需要人为判断整个发布过程是否正常毕竟有些异常只有在真正的线上发布过程中才能被发现人为介入相对来说要保险一些所以只做到持续交付也可以算是实现了DevOps。
DevOps的关键是如何实现代码开发自测通过自动部署到测试环境验证通过后再自动部署到生产环境小流量验证后再自动发布到线上去。在传统的采用物理机部署服务的时代这个流程的很难自动化执行的最大原因就是代码环境的可移植性差这是因为开发自己的环境跟测试环境以及生产环境的软件配置往往存在很大差异经常会出现开发在自己的环境中运行通过的代码部署到测试环境就运行不了的问题。而容器化正好解决了代码环境的可移植性的问题使得DevOps取得了突飞猛进的发展并成为业界推崇的开发模式。那么具体该如何实现DevOps呢下面我就以微博的业务实践为例来给你详细讲解。
微博的DevOps实践
目前业界比较通用的实现DevOps的方案主要有两种一种是使用Jenkins一种是使用GitLab。微博就主要使用的是GitLab来实现DevOps下面我就从微博一个服务的开发、测试到上线的具体流程看看是如何实现DevOps的。
从上面图中你可以看到,一个服务的发布流程主要包含了三个步骤。
1.持续集成这个步骤的主要作用是确保每一次代码的Merge Request都测试通过可随时合并到代码的Develop分支主要包括四个阶段build阶段开发分支代码的编译与单元测试、package阶段开发分支代码打包成Docker镜像、deploy阶段开发分支代码部署到测试环境、test阶段开发分支代码集成测试
2.持续交付这个步骤的主要作用是确保所有代码合并Merge Request到Develop分支后Develop分支的代码能够在生产环境中测试通过并进行小流量灰度验证可随时交付到线上。主要包括五个阶段build阶段Develop分支的代码编译与单元测试、package阶段Develop分支的代码打包成Docker镜像、deploy阶段Develop分支的代码部署到测试环境、test阶段Develop分支的代码集成测试、canary阶段Develop分支的代码的小流量灰度验证
3.持续部署这个步骤的主要作用是合并Develop分支到Master主干并打包成Docker镜像可随时发布到线上。主要包括四个阶段build阶段Master主干的代码编译与单元测试、package阶段Master主干的代码打包成Docker镜像、clear阶段Master主干的代码Merge回Develop分支、production阶段Master主干的代码发布到线上
那么上面这些流程是如何实现自动化的呢在GitLab中可以通过一个叫“.gitlab-ci.yml”的文件来定义自动化流程都包含哪些阶段以及每个阶段所具体执行的脚本这样的话在提交代码Merge Request后会自动触发gitlab-ci.yml文件中定义的各个流程按顺序执行。
实现DevOps的关键点
上面我讲了具体业务中如何使用GitLab来实现DevOps在具体实施时每个阶段都有关键问题只有解决了这些关键问题才能真正实现DevOps。
1.持续集成阶段
持续集成阶段的主要目的是保证每一次开发的代码都没有问题,即使合并到主干也能正常工作,这里主要依靠三部分的作用。
代码检查。通过代码检查可以发现代码潜在的一些bug比如Java对象有可能是null空指针等实际执行时可以在持续集成阶段集成类似Sonarqube之类的工具来实现代码检查。
单元测试。单元测试是保证代码运行质量的第二个关卡。单元测试是针对每个具体代码模块的单元测试的覆盖度越高各个代码模块出错的概率就越小。不过实际业务开发过程中为了追求开发速度许多开发者并不在意单元测试的覆盖度而是把大部分测试工作都留在了集成测试阶段这样可能会造成集成测试阶段返工的次数太多需要多次修复bug才能通过集成测试。尤其对于业务复杂度比较高的服务来说在单元测试阶段多花费一些功夫其实从整个代码开发周期角度来看收益还是要远大于付出的。
集成测试。集成测试就是将各个代码的修改集成到一起,统一部署在测试环境中进行测试。为了实现整个流程的自动化,集成自测阶段主要的任务就是跑每个服务的自动化测试用例,所以自动化测试用例覆盖的越全,集成测试的可靠性就越高。这里就要求开发和测试能及时沟通,在新的业务需求确定时,就开始编写测试用例,这样在跑自动化测试用例时,就不需要测试的介入了,省去了沟通成本。当然,业务开发人员也可以自己编写测试用例,这样的话就不需要专职的业务测试人员了。
除此之外还有一个值得关注的问题就是集成测试阶段业务代码部署的测试机器从何而来。在单体应用的时候一般是开发把代码打包交给测试测试人员再分配给自己的测试机中部署业务然后进行集成测试。但是现在问题来了由于拆分成了微服务需要测试的服务变多了如果同时有多个需求在测试测试人员的测试机可能就不够用了而出于成本考虑一般公司都不会花费采购大量的测试机器。一个好的办法就是通过Kubernetes之类的容器平台对测试集群进行管理当有业务代码正在执行集成测试时就从测试集群中创建一个容器部署服务完成测试后再销毁容器及时进行资源回收。这样测试机器不需要分配给某个具体的个人实现按需使用提高了测试集群的资源使用率。
2.持续交付阶段
持续交付阶段的主要目的是保证最新的业务代码,能够在类生产环境中可能够正常运行,一般做法都是从线上生成环境中摘掉两个节点,然后在这两个节点上部署最新的业务代码,再进行集成测试,集成测试通过后再引入线上流量,来观察服务是否正常。通常需要解决两个问题:
如何从线上生产环境中摘除两个节点。这就需要接入线上的容器管理平台比如微博的容器管理平台DCP就提供了类似下面的API能够从线上生产环境中摘除某个节点然后部署最新的业务代码。
curl -s http://raptor.api.weibo.com/extension/v1/preview/run/ -d action=503&ip=11.75.21.155&service_pool=openapi_friendship-yf-docker&user=weibo_rd_user
如何观察服务是否正常。由于这两个节点上运行的代码是最新的代码在引入线上流量后可能会出现内存泄露等在集成测试阶段无法发现的问题所以这个阶段这两个节点上运行最新代码后的状态必须与线上其他节点一致。实际观察时主要有两个手段一个是观察节点本身的状态如CPU、内存、I/O、网卡等一个是观察业务运行产生的warn、error的日志量的大小尤其是error日志量有异常时往往就说明最新的代码可能存在异常需要处理后才能发布到线上。
3.持续部署阶段
持续部署阶段的主要目的把在类生产环境下运行通过的代码自动的发布到线上所有节点中去这里的关键点就在于实际的线上发布阶段并不是想象中的那么直接。以微博API的业务为例同样的服务也分为核心池和非核心池核心池提供给移动端和PC调用非核心池提供给其他内部业务调用并且还按照机房分为不同的服务池比如永丰机房服务池和土城机房服务池。实际发布的时候考虑到线上服务的稳定性并不是说按照一定的步长自动把所有服务池都发布了而是先发布非核心池以及土城机房的核心池然后验证观察一段时间线上服务一切正常后再继续发布永丰机房的核心池以防止某些问题在服务发布的过程中才暴露出来但又不至于影响线上所有的服务节点。所以这个阶段持续部署一般并不要求那么完美许多公司在这个阶段都采用了手动发布的方式以控制风险或者只做到持续交付阶段对于持续部署并不要求自动化。
总结
今天我给你介绍了DevOps对于微服务的意义它通过将开发、测试和运维流程自动化以减轻微服务拆分后带来的测试和运维复杂度的提升同时还提高了业务研发的效率。为了实现DevOps需要实现持续集成、持续交付以及持续部署可以采用Jenkins或者GitLab这些开源DevOps工具来搭建你自己的CI/CD流程关键点在于如何把已有的自动化测试用例以及现有容器管理平台集成到CI/CD流程当中去以完成自动化的CI/CD流水线处理。
实际上DevOps你可以理解为一种新型的业务研发流程也可以理解为一种新的技术思维它摒弃了传统的开发、测试和运维严格区分的观念把三者的角色融为一体让服务的开发者负责从开发、测试到发布的整个生命周期真正的承担起服务负责人的角色。更广义的DevOps除了包括CI/CD流程的自动化处理还包括智能监控决策、在线自动扩缩容等甚至还引入了人工智能技术走向另外一个新方向AIOps关于自动扩容的内容咱们下期再聊。
思考题
在DevOps的持续集成阶段有两个测试一个是单元测试一个集成测试你觉得它们的作用有何区别是不是做好了集成测试就不需要单元测试了
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,93 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
30 如何做好微服务容量规划?
专栏上一期我给你讲解了单体应用拆分为微服务后带来的开发、测试和运维复杂度的提升可以通过DevOps实现CI/CD流程的自动化来解决。除此之外单体应用拆分为微服务还带来另外一个问题也就是拆分出来后的多个微服务容量如何规划的问题。在单体应用时只需要针对这个单体应用的访问量和实际接口性能来决定要不要给单体应用扩容而拆分为众多的微服务之后需要考虑每个服务的容量规划它的复杂度主要来自下面几个方面。
服务数量众多纯靠人肉运维难以管理比如微博Feed业务仅仅RPC服务就有将近40个。
服务的接口表现差异巨大有的接口属于访问量比较大但接口响应时间比较短的轻接口有的接口属于访问量比较小但接口响应时间比较长的重接口。比如微博Feed业务中计数接口的平均耗时只有23ms而微博Feed业务中Feed接口的平均耗时要超过200ms。
服务部署的集群规模大小不同需要扩容的机器数量差异很大。比如微博的AB测试服务集群只有大约20台机器扩容只需要几台机器就满足了而Feed服务则有上千台机器往往扩容需要上百台机器。
服务之间还存在依赖关系在服务扩容的时候还需要考虑依赖服务的容量是否足够。比如微博Feed业务扩容还依赖用户关系服务和Card服务扩容时还需要考虑依赖的用户关系服务和Card服务容量是否有问题。
由此可见单体应用拆分为微服务后微服务的容量规划难度一下子增加了很多再靠传统的人肉运维模式显然难以为继。延续上期DevOps的话题其实微服务的容量规划也是广义DevOps要解决的问题之一那么DevOps是如何解决的呢答案就是容量规划系统下面我就来聊聊容量规划系统该如何实现。
容量规划系统的作用是根据各个微服务部署集群的最大容量和线上实际运行的负荷,来决定各个微服务是否需要弹性扩缩容,以及需要扩缩容多少台机器。
可见,容量规划系统实施的关键在于两点:一是如何评估集群的最大容量和线上实际运行的负荷,也就是如何做好容量评估;二是如何确定弹性扩缩容的时机以及机器数,也就是如何做好调度决策。下面我们分别来看这两个关键点,逐个击破。
容量评估
一般集群的容量评估都是通过线上实际压测来确定的,那么该如何进行线上压测呢?都需要注意哪些关键点呢?
1.选择合适的压测指标
一般在选取压测指标时主要有两类一类是系统类指标比如机器的CPU使用率、内存占用量、磁盘I/O使用率以及网卡带宽等一类是服务类指标比如接口响应的平均耗时、P999耗时、错误率。但这些指标在实际压测时都会存在一些问题。系统类指标比如CPU使用率并不能直接反映出服务压测时的健康状况有时候CPU使用率不高的时候接口耗时也可能有问题而有时候CPU使用率较高时接口耗时表现依然很正常。而服务类的指标比如接口响应的平均耗时也不能精确的反映服务的实际健康状态一个最典型的场景就是在压测时已经出现一定比例的慢请求而在平均耗时上并不能看出有多大变化这时候实际服务已经处于不健康的状态了应该停止压测了。
根据我的经验在压测时除了观察以上这些指标以外还可以观察接口的慢速比也就是接口响应时间高于某个阈值的比例。比如微博在进行Feed接口压测时选择的压测指标就是Feed接口响应时间大于1s的比例压测的终止条件是Feed接口响应时间大于1s的比例超过1%。我的主要考虑是当99%以上接口请求都在1s以内返回时反馈到用户的直接使用感受是不容易感知到Feed刷新慢反之则不然。对于大部分在线服务来说接口慢速比不超过1%都是服务质量保证的底线了,因此可以作为一个通用的压测指标。
2.压测获取单机的最大容量
集群的最大容量就是单机的最大容量 × 集群内的机器数量,所以要获得集群的最大容量,就必须获得单机的最大容量。通常有两种方式来获取单机的最大容量,一种是单机压测,一种是集群压测。
单机压测一般有两种方式一种是通过日志回放等手段模拟线上流量来对单机进行压测一种是通过TCP-Copy的方式把线上机器的流量拷贝过来对单机进行压测。
集群压测是对整个集群进行压测,以获取单机的最大容量。一般做法是通过不断把线上集群的节点摘除,以减少机器数的方式,来增加线上节点单机的流量,从而达到压测的目的。
从我的经验来看,采用集群压测的方式要更合理一些,因为它是完全使用线上真实流量进行压测,获取的单机最大容量数值更精确。如果采用单机压测,通常为了避免产生“脏数据”,往往需要去掉一些上行的修改请求,所以不能完全模拟线上真实情况。不过使用集群压测的方式也有一个缺点,就是压测的时候会对线上用户的实际请求产生影响,如果压测出问题了,会直接影响线上服务,所以一般会选择在业务低峰期进行压测,最大限度减少对线上服务造成的影响。还有一点是,通常会在工作日进行压测,以便出现问题时,也能人为快速介入。
假设我们采用集群压测不断地缩减线上节点的数量并观察服务的慢速比指标当慢速比达到1%时就停止压测这个时候就可以计算单机的最大容量了一般做法是用压测停止时刻的单机平均QPS作为单机的最大容量。但是采用QPS就真的合理吗实际上并非如此这是因为QPS并不能准确衡量单机的消耗就像下面这两张图所展示的左图的请求响应时间主要集中在100ms以下没有超过500ms的而右图的请求响应时间主要集中在50ms以上没有低于10ms的。这两种请求分布对单机消耗差异很大显然右边要对单机的消耗更大一些。在单机QPS都是100的情况下左边的单机还能继续加大QPS而右边的单机已经出现超过500ms以上的慢请求了。
所以一个更合理的计算单机容量的方式是采用区间加权来计算也就是把请求按照响应时间分成多个区间每个区间分别赋予不同的权重响应时间越长权重越高比如010ms区间的权重是11050ms区间的权重是250100ms区间的权重是4100200ms区间的权重是8200500ms区间的权重是16500ms以上的权重是32那么上面两张图所描述的情况的单机容量分别是8×1+50×2+30×4+10×8+2×16=340和2×2+10×4+50×8+20×16+8×32=1020。因此单机的最大容量也就是压测停止时刻采用区间加权方式计算得出。
3.实时获取集群的运行负荷
通过压测能够获取到单机的最大容量,再乘以集群内的机器数量就是集群的最大容量了,下一步获取集群实际运行的负荷,就可以判断集群是否需要扩容了。跟刚才计算单机容量的方式类似,集群的运行负荷也需要通过采用区间加权的方式来计算,但是因为集群的规模可能很大,超过上千台机器,显然通过计算每台单机运行的负荷再加在一起的方式效率不高。我在线上实际使用的方法是统计每台单机在不同耗时区间内的请求数,推送到集中处理的地方进行聚合,将同一个集群内的单机位于不同耗时区间内的请求进行汇总,就得到整个集群的请求在不同耗时区间内的分布了,再利用区间加权的方式就可以计算整个集群的运行负荷。
调度决策
在容量评估阶段,你可以获取集群的最大容量和集群的实际运行负荷,有了这两个数据后该如何做调度策略呢?我在实际线上业务使用的是水位线来进行调度决策。就像水库的水位线一样,要实时观测水库的蓄水量,如果因为长时间降水导致水库蓄水量超过警戒水位线就需要开闸泄洪;如果长时间干旱降水量太少,就需要关闸蓄水,以保持水库中的蓄水量始终在一个合理的水位线上。这样的话,任意时刻的水位线就是集群的最大容量除以集群的实际运行负荷,可以实时监控集群的水位线。
在调度决策时候,就可以根据水位线来做决定。你可以看到下面图中划分了两条线,一条是安全线,一条是致命线。当集群的水位线位于致命线以下时,就需要立即扩容,在扩容一定数量的机器后,水位线回到安全线以上并保持一段时间后,就可以进行缩容了。
那具体在执行扩缩容时,机器数量该如何决定呢?
1.扩容
在决定扩多少机器时一般有两种方式一种是按数量一种是按比例。因为不同的集群内机器数量差别可能很大所以一般采取按比例的方式举个例子比如每一次扩容都增加30%的机器数量,再看扩容后的水位线是否处于致命线以上了。
2.缩容
在扩容完成后集群的水位线保持在安全线以上一段时间后就需要缩容以节省机器成本。可以根据实际业务特点来决定多久后可以缩容比如微博的业务一般突发流量维持在1个小时以内因此集群的水位线在安全线以上超过1个小时之后就可以缩容。而在缩容时也不是一次把所有扩容的机器都缩掉而是采用逐步缩容的方式每隔5分钟判断一次集群的水位线是否还在致命线以上然后按照10%、30%、50%、100%的比例进行缩容,这样可以避免缩容太快导致集群水位线又降到致命线以下又得再扩容机器。
在实际根据水位线决定是否扩缩容时还需要防止网络抖动等原因造成的水位线瞬间抖动这个时候集群的运行负荷会突然变大导致水位线异常此时如果加以处理的话就会触发扩容而实际上并不需要扩容。为了防止瞬间抖动可以每分钟采集一次系统的水位线一共采集5个点只有5个点里有3个点满足扩容条件才真正触发扩容。
总结
今天我从两个方面具体给你讲解了微服务如何做好容量规划的问题,即做好容量评估和调度决策。容量评估方面,首先要通过压测获取集群的最大容量,并实时采集服务调用的数据以获取集群的实时运行负荷,这样就可以获取集群的实时水位线。而调度决策方面,主要是通过水位线与致命线和安全线对比来决定什么时候该扩缩容。而扩缩容的数量也是有讲究的,扩容的机器数一般按照集群机器数量的比例来,而缩容一般采取逐步缩容的方式以免缩容太快导致反复扩容。
在单体应用拆分为多个微服务后,如果不做好容量规划是很危险的事情,尤其是在微服务的调用量出现突发峰值流量时,再靠人为判断决策扩缩容往往为时已晚。根据我在微博的实践,也证明了通过容量规划实现微服务的自动扩缩容才是解决这个问题的最佳途径。
思考题
在计算集群的水位线时,经常会遇到集群内有些单机问题导致整个集群的实时运行负荷偏大,对此你有什么解决方案吗?
欢迎你在留言区写下你的思考,与我一起讨论。

View File

@ -0,0 +1,109 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
31 微服务多机房部署实践
专栏前面我在讲服务治理时提到过,为了实现高可用性,微服务一般要部署在多个机房,保证有一个机房因为各种不可抗力因素导致不可用时,可以把流量切换到其他可用机房来避免故障。但是,是不是只要部署到多个机房就万事大吉了呢?你有没有想过这几个问题呢?
一切正常时用户请求该访问哪个机房?
多个机房之间的数据如何同步?
多个机房之间的数据如何确保持一致性?
你看多机房部署并非看似那么轻松,里面还有不少门道。接下来,我就以微博业务实践为例,跟你聊聊微服务实际进行多机房部署时是如何解决这些关键问题的。
多机房负载均衡
当服务部署在多个机房时最简单的就是遵循用户就近访问的原则比如北方用户访问联通机房南方用户访问电信机房。微博的服务也是同时部署在联通和电信机房你可以看下面这张图访问时根据用户访问的IP通过DNS解析到不同的机房如果是北方用户就访问联通机房南方用户就访问电信机房。并且为了实现负载均衡还会在每个机房分别部署四层负载均衡器VIP以及七层负载均衡器Nginx。比如来自北方用户的请求通过DNS解析到联通机房下任意一个VIP然后通过VIP把请求转发给联通机房下任意一个NginxNginx再把请求转发给联通机房下任意一个Tomcat容器通过这种方式来实现各个机房内高并发访问下的负载均衡。
当然这是最理想的情况,在实际部署时经常会遇到下面的情况:
某个机房的流量比较大,但是该机房的服务器规模有限并不足以支撑线上流量。
某个机房服务有问题,需要切一部分流量到另外一个机房。
因此在实际部署时有时候并不能完全遵循就近访问的原则而是要根据需要调配流量达到各个机房流量均衡的目的。在实践中可以通过两种方法来切换流量一种是在DNS解析时把一部分北方用户的请求解析到电信机房的VIP或者把一部分南方用户的请求解析到联通机房的VIP另一种是在Nginx转发请求时把一部分电信机房的Tomcat容器配置到联通机房的Nginx的upstream里或者把一部分联通机房的Tomcat容器配置到电信机房的Nginx的upstream里。这两种方法的示意你可以看下面这张图。
多机房数据同步
想要实现服务部署到多机房供用户访问是有前提的这个前提是每个机房的数据都是一样的用户访问哪个机房都可以获取到一样的数据这就要求多个机房之间的数据必须保持同步。对于微博这种高并发访问的服务来说数据通常都会有两层存储即缓存层和数据库层就像下图所展示的。缓存层是为了存储用户经常访问的数据尤其是在高并发访问下可以用缓存cache住绝大多数用户请求减少对数据库层的压力这是因为数据库层要直接访问磁盘相比缓存层直接访问内存来说响应要慢得多。
如此一来,要保证多个机房的数据一致,不仅要保证数据库层的数据一致,还需要保证缓存层的数据一致,应该如何实现呢?
1.主从机房架构
主从机房数据同步方案如下图所示。主从机房架构是以一个机房为主机房所有的写请求都只发给主机房的处理机由主机房的处理机来更新本机房的缓存和数据库其他机房的缓存也通过主机房的处理机来更新而数据库则通过MySQL的binlog同步机制的方式实现数据同步。
上面这种架构把所有的写请求都发给主机房由主机房来负责写所有机房的缓存和本机房的数据库而其他机房的数据库则通过MySQL的binlog同步机制实现数据同步。显然这样做有一个很大的风险那就是如果主机房出现问题就没法更新缓存和数据库了所以就有了第二种方案。
2.独立机房架构
这种架构的数据同步方案如下图所示联通和电信机房都有写请求并通过一个叫WMB的消息同步组件把各自机房的写请求同步一份给对方机房这样的话相当于每个机房都有全量的写请求。每个机房的处理机接收到写请求后更新各自机房的缓存只有一个机房会更新数据库其他机房的数据库通过MySQL的binlog同步机制实现数据同步。
独立机房架构相比于主从机房架构的优势在于任意一个机房出现问题都不影响别的机房的数据更新因为每个机房的写消息都是全量的所以每个机房可以更新自己的缓存并从数据库主库同步数据。其实独立机房架构的关键点在于WMB消息同步组件它可以把各个机房之间的写请求进行同步。下面我就详细讲讲WMB消息同步组建是如何实现的。
WMB消息同步组件的功能就是把一个机房的写请求发给另外一个机房它的实现原理可以用下面这张图来描述分为两个部分
reship负责把本机房的写请求分发一份给别的机房。
collector负责从别的机房读取写请求然后再把请求转发给本机房的处理机。
那么该如何实现WMB的消息同步功能呢根据我的实践经验主要有两种方案一种是通过MCQ消息队列一种是通过RPC调用。
MCQ消息队列实现
下面这张图是采用MCQ消息队列的实现方案从图中你可以看到联通机房的写请求写入到联通机房的MCQ里然后联通机房的reship就从联通机房的MCQ里读取再写入到电信机房的MCQ里电信机房的collector就可以从电信机房的MCQ里读取到写请求再写入到电信机房的另外一个MCQ里电信机房的队列机就会从这个MCQ里读取写请求然后更新缓存。可见采用这种方案的一个缺点是流程比较长需要多次与MCQ消息队列打交道当有大量写请求到来时不仅要扩容reship和collector确保有足够的处理能力还需要扩容MCQ消息队列以确保能够承受大量读取和写入一种更加简单的方案是采用RPC调用来实现。
RPC调用实现
下面这张图是采用RPC调用的实现方案从图中你可以看到联通机房的写请求会调用联通机房的reship RPC然后联通机房的reship RPC就会调用电信机房的collector RPC这样电信机房的collector RPC就会调用电信机房的处理机RPC从而实现把联通机房的写请求同步给电信机房的处理机进行处理。
多机房数据一致性
解决了多机房数据同步的问题之后,还要确保同步后的数据是一致的,因为在同步过程中,会因为各种原因导致各机房之间的数据不一致,这就需要有机制能确保数据的一致性。而且考虑到不同业务的特征对数据一致性的要求也不相同,类似金融类的业务要求多机房之间的数据必须是强一致的,也就是一个机房的数据必须时刻同另外一个机房的数据完全一致;而社交媒体类的业务则要求没那么高,只需要能达到最终一致即可。微博的服务主要是通过消息对账机制来保证最终一致性,下面我们来看下如何通过消息对账机制来保证最终一致性。
你可以先看下面这张图系统会给每一次写请求生成一个全局唯一的requestId联通机房的写请求一方面会调用联通机房的处理机RPC来修改缓存和数据库另一方面还会调用联通机房的reship RPCreship RPC再调用电信机房的collector RPC来同步写请求电信机房的collector RPC最后会调用电信机房的处理RPC来更新缓存。在这整个过程的每一个环节requestId始终保持向下传递无论是处理成功或者失败都记录一条包含requestId和机房标记的处理日志并写到Elasticsearch集群上去。然后通过一个定时线程每隔1分钟去扫描Elasticsearch集群上的日志找出包含同一个requestId的不同机房的处理日志然后验证是否在各个机房请求都处理成功了如果有的机房某一阶段处理失败则可以根据日志信息重试该阶段直到成功从而保证数据的最终一致性。
总结
今天我给你讲解了微服务多机房部署时要面临的三个问题,一是多机房访问时如何保证负载均衡,二是多机房之间的数据如何保证同步,三是多机房之间的数据如何保证一致性,并给出了微博在多机房部署微服务时所采取的解决方案,对于大部分中小业务团队应该都有借鉴意义。可以说多机房部署是非常有必要的,尤其是对可用性要求很高的业务来说,通过多机房部署能够实现异地多活,尤其可以避免因为施工把光缆挖断导致整个服务不可用的情况发生,也是业务上云实现混合云部署的前提。下一期我再来聊聊微服务混合云部署的实践,你可以对多机房部署的重要性有更深的认识。
思考题
在讲解多机房数据同步实践的时候我提到了微博采用了WMB消息同步组件的方案除了这种方案你是否有了解过其他多机房数据同步的方案它们是如何实现的
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,107 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
32 微服务混合云部署实践
专栏上一期我给你讲解了微服务多机房部署的实践以及需要解决的三个问题。大多数业务在发展到一定阶段要么出于高可用性的需要把业务部署在多个机房以防止单个机房故障导致整个服务不可用要么出于访问量大的需要把业务流量分散到多个机房以减少单个机房的流量压力。此时服务一般是部署在企业内部机房的机器上并利用私有云技术把内部机房的机器管理起来。然而有些业务经常还有弹性需求比如微博就经常因为热点事件带来突发的峰值流量需要扩容成倍的机器但内部机房的资源有限并且从成本因素考虑也不会预留太多机器这个时候就会自然想到公有云。类似AWS和阿里云这样的公有云厂商一般都会采购上万台机器专门对外售卖这样公有云的用户就不用预留这么多的机器了可以按需弹性使用节省机器成本。
我们今天要聊的混合云部署,就是既在企业内部的私有云部署服务,又使用企业外部公有云部署服务的模式。和多机房一样,混合云部署同样要考虑几个关键的问题。
跨云服务如何实现负载均衡?
跨云服务如何实现数据同步?
跨云服务如何实现容器运维?
下面我就结合微博的微服务混合云部署实践,帮你找到解决上面问题的答案。
跨云服务的负载均衡
上一期我们聊了多机房的负载均衡它主要考虑用户的就近访问把用户的请求分别路由到不同的机房。同样的道理当服务上云后还需要考虑把一定比例的用户请求路由到云上部署的服务就像下图那样微博的服务不仅在私有云的两个机房永丰和土城有部署在阿里云上也部署了服务。为了做到负载均衡把用户的访问按照DNS解析到不同的机房私有云机房部署了VIP和Nginx分别用作四层和七层的负载均衡阿里云机房部署了SLB和Nginx分别用作四层和七层的负载均衡。
跨云服务的数据同步
复习一下上一期我给你讲解的多机房之间的数据同步。为了做到高可用性一般采用独立机房的部署架构每个机房的写请求都通过WMB同步给别的机房以保证任意一个机房都有全量的写请求从而使得任意一个机房的处理机都会全量更新数据。那么当服务跨云部署后该如何实现数据同步呢根据我的经验在公有云部署服务和内部私有云部署服务还是有一些不同的主要体现在下面两个方面。
1.私有云与公有云之间的网络隔离
一般来讲出于安全的需要企业内部机房同公有云机房之间的网络是隔离的为了实现互通需要架设专门的VPN网络或者专线就像下图描述的微博在内部私有云和阿里云之间搭建了两条跨云专线分别打通了内部的联通、电信机房与阿里云的联通、电信可用区这样的话不仅实现了私有云和公有云之间的网络互动双专线也保证了高可用性即使一条专线断了也可以通过另外一条专线实现数据同步。不过这样做需要保证专线的冗余度充足任何一根专线的带宽能够承担所有跨云的流量否则就很危险了因为一旦一根专线断了所有流量都通过另外一根专线的话就会把专线打满出现网络延迟影响服务。
2.数据库能否上云
数据库能否上云的关键取决于数据的隐私性。一般而言,企业都会考虑数据库里的数据放在公有云上是否安全,因为企业内部私有云部署的数据库与外网隔离,再加上有种种防护措施,一般情况下不会出现数据库数据外泄情况。而公有云厂商普遍采用了虚拟化技术,不同公司的虚拟机有可能部署在同一台物理机上,所以能否实现有效的数据隔离非常关键,尤其对于企业的核心业务数据,往往会出于安全隐私的考虑,并不敢直接放到云上部署。考虑到这一点,微博的服务在阿里云部署时,并没有部署数据库,只部署了缓存,当缓存穿透时需要访问内网数据库,你可以参考下面这张图。
综合上面两点考虑微博在做跨云数据同步的时候把内部的永丰机房和土城机房的写消息通过WMB同步给阿里云机房的WMB阿里云机房的WMB把写消息转发给阿里云机房的处理机处理进而更新阿里云机房的缓存整个流程可见下图。其中阿里云机房主要用于承担下行的读请求部署的缓存也不是跟内网机房完全一致而是只部署了最核心的服务所依赖的缓存这样可以将大部分阿里云机房的请求都在内部消化减少到内网数据库的穿透从而节省跨云专线的带宽使用。
跨云服务的容器运维
前面我讲过,微服务容器化后,便具备了可移植性,不仅可以在内部私有云上部署,还可以在外部公有云上部署,这就要求有一套统一的容器运维平台不仅能对接内部私有云的基础设施,也能对接外部的公有云,这部分内容你可以在[第28期]容器运维平台DCP中找到。服务实现了混合云部署后DCP在实施跨云的容器运维时又多了哪些关键点呢
1.跨云的主机管理
跨云主机管理的关键点在于如何对内部私有云的机器和公有云的ECS进行管理在DCP里是按照“主机-服务池-集群”的模式进行管理的,这三个概念的含义分别是:
主机某一台具体的服务器可能是私有云内创建的虚拟机也有可能是公有云创建的ECS。
服务池:针对具体某个服务而言,由这个服务部署的主机组成,可能包含私有云的主机,也可能包含公有云的主机,规模可能是几台也可能是上百台。
集群针对具体某个业务线而言可能包含多个服务池比如微博的内容业务线包含了Feed服务池也包含了评论服务池等。
在实际扩容时,如下图所示,可能有三种情况。
私有云内弹性扩容:当某个服务池的容量不足需要进行扩容时,如果该服务池所在的集群内的主机数量充足,则只需要在私有云内弹性扩容加入服务池即可。
公有云弹性扩容:当某个服务池的容量不足需要进行扩容时,如果该服务池所在的集群内没有多余的主机可用时,就需要在公有云上弹性扩容,然后加入服务池。
私有云和公有云同时弹性扩容:当某个服务池的容量不足需要进行扩容时,如果该服务池所在的集群内的主机数量不足时,就需要在同时在私有云和公有云上进行弹性扩容,最后都加入到服务池中去。
2.跨云服务发现。
在[第28期]我讲过DCP的服务发现主要有两种方式一种是针对HTTP服务采用的nginx-upsync-module一种是针对RPC服务的Config Service。除此之外阿里云上部署的服务还可以直接使用SLB来做服务发现。比如下面这张图红包飞依赖了用户关系服务当阿里云上用户关系服务扩容后可以直接添加容器的IP到SLB下这样红包飞服务访问SLB就可以获得最新的用户关系服务的节点列表。
3.跨云弹性扩容。
当有流量上涨超出了内部私有云机房部署所能承受的范围时可以扩容阿里云机房的机器然后把流量切换到阿里云机房这个过程请看下面这张图。切流量也有两种方案一是在DNS层切换把原先解析到私有云机房VIP的流量解析到阿里云机房的SLB这时候阿里云机房部署的SLB、Nginx和Java Web都需要扩容一种是在Nginx层切换把原先转发到私有云机房Nginx的流量转发到阿里云机房的Java Web这个时候只需要扩容阿里云的Java Web。
这两种方案应对的业务场景不同DNS层的切换主要是针对大规模流量增长的情况这个时候一般四层VIP、七层Nginx和Java Web的容量都不足以应对就需要在DNS层就把流量切到阿里云机房在阿里云扩容SLB、Nginx和Java Web而Nginx层的切换主要是针对私有云内某个机房的Java Web容量不足或者服务有问题的时候需要把这个机房的一部分流量切换到其他机房这个时候就可以只扩容阿里云机房的Java Web然后从Nginx层把流量切换到阿里云机房。
4.跨云服务编排。
在进行服务编排时如果服务跨云部署就要考虑跨机房访问的问题了。就像下图所描述的那样微博的Feed服务不仅依赖User RPC还依赖Card RPC这样的话如果Feed服务需要扩容的话就需要先扩容User RPC和Card RPC。由于Feed服务在永丰、土城、阿里云三个机房内都有部署任意一个机房内部署的Feed服务需要扩容时就需要扩容同一个机房内的User RPC和Card RPC。
总结
今天我给你讲解了微服务混合云部署必须解决的三个问题:跨云服务的负载均衡、跨云服务的数据同步、跨云服务的容器运维,以及微博在微服务混合云部署时的实践方案,可以说正是由于采用了混合云部署,才解决了微博在面对频繁爆发的热点事件带来突发流量时,内部资源冗余度不足的问题。虽然云原生应用现在越来越流行,但对于大部分企业来说,完全脱离内部私有云并不现实,因为云也不是完全可靠的,一旦云厂商出现问题,如果没有内部私有云部署的话,那么服务将完全不可用。如果你的服务对高可用性要求很高,那么混合云的方案更加适合你。
思考题
微服务采用混合云部署的时候,如果公有云和私有云都都部署数据库的话,数据该如何保持同步?
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,103 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
33 下一代微服务架构Service Mesh
今天我们将进入专栏最后一个模块我会和你聊聊下一代微服务架构Service Mesh。说到Service Mesh在如今的微服务领域可谓是无人不知、无人不晓被很多人定义为下一代的微服务架构。那么究竟什么是Service MeshService Mesh是如何实现的今天我就来给你解答这些疑问。
什么是Service Mesh
Service Mesh的概念最早是由Buoyant公司的CEO William Morgan在一篇文章里提出他给出的服务网格的定义是
A service mesh is a dedicated infrastructure layer for handling service-to-service communication. Its responsible for the reliable delivery of requests through the complex topology of services that comprise a modern, cloud native application. In practice, the service mesh is typically implemented as an array of lightweight network proxies that are deployed alongside application code, without the application needing to be aware.
专栏里我就不解释教条的定义了感兴趣的话你可以点击链接阅读原文这里我来谈谈我对Service Mesh的理解。我认为是Service Mesh是一种新型的用于处理服务与服务之间通信的技术尤其适用以云原生应用形式部署的服务能够保证服务与服务之间调用的可靠性。在实际部署时Service Mesh通常以轻量级的网络代理的方式跟应用的代码部署在一起从而以应用无感知的方式实现服务治理。
从我的理解来看Service Mesh以轻量级的网络代理的方式与应用的代码部署在一起用于保证服务与服务之间调用的可靠性这与传统的微服务架构有着本质的区别在我看来这么做主要是出于两个原因。
1.跨语言服务调用的需要。在大多数公司内通常都存在多个业务团队每个团队业务所采用的开发语言一般都不相同以微博的业务为例移动服务端的业务主要采用的是PHP语言开发API平台的业务主要采用的是Java语言开发移动服务端调用API平台使用的是HTTP请求如果要进行服务化改成RPC调用就需要一种既支持PHP语言又支持支持Java语言的的服务化框架。在专栏第14期我给你讲解了几种开源的服务化框架它们要么与特定的语言绑定比如Dubbo和Spring Cloud只支持Java语言要么是跟语言无关比如gRPC和Thrift得定义个IDL文件然后根据这个IDL文件生成客户端和服务端各自语言的SDK并且服务框架的功能比如超时重试、负载均衡、服务发现等都需要在各个语言的SDK中实现一遍开发成本很高。
2.云原生应用服务治理的需要。在专栏前面我给你讲解了微服务越来越多开始容器化并使用Kubernetes类似的容器平台对服务进行管理逐步朝云原生应用的方向进化。而传统的服务治理要求在业务代码里集成服务框架的SDK这显然与云原生应用的理念相悖因此迫切需要一种对业务代码无侵入的适合云原生应用的服务治理方式。
在这种背景下Buoyant公司开发的第一代Service Mesh产品Linkerd应运而生。从下图中你可以看到服务A要调用服务B经过Linkerd来代理转发服务A和服务B的业务代码不需要关心服务框架功能的实现。为此Linkerd需要具备负载均衡、熔断、超时重试、监控统计以及服务路由等功能。这样的话对于跨语言服务调用来说即使服务消费者和服务提供者采用的语言不同也不需要集成各自语言的SDK。
-
图片来源https://linkerd.io/images/[email protected]
而对于云原生应用来说可以在每个服务部署的实例上都同等的部署一个Linkerd实例。比如下面这张图服务A要想调用服务B首先调用本地的Linkerd实例经过本地的Linked实例转发给服务B所在节点上的Linkerd实例最后再由服务B本地的Linkerd实例把请求转发给服务B。这样的话所有的服务调用都得经过Linkerd进行代理转发所有的Linkerd组合起来就像一个网格一样这也是为什么我们把这项技术称为Service Mesh也就是“服务网格”的原因。
-
图片来源https://buoyant.io/wp-content/uploads/2017/04/linkerd-service-mesh-diagram-1024x587.png
Service Mesh的实现原理
根据我的理解Service Mesh实现的关键就在于两点一个是上面提到的轻量级的网络代理也叫SideCar它的作用就是转发服务之间的调用一个是基于SideCar的服务治理也被叫作Control Plane它的作用是向SideCar发送各种指令以完成各种服务治理功能。下面我就来详细讲解这两点是如何实现的。
1.SideCar
我们首先来看一下,在传统的微服务架构下服务调用的原理。你可以看下面这张图,服务消费者这边除了自身的业务逻辑实现外,还需要集成部分服务框架的逻辑,比如服务发现、负载均衡、熔断降级、封装调用等,而服务提供者这边除了实现服务的业务逻辑外,也要集成部分服务框架的逻辑,比如线程池、限流降级、服务注册等。
而在Service Mesh架构中服务框架的功能都集中实现在SideCar里并在每一个服务消费者和服务提供者的本地都部署一个SideCar服务消费者和服务提供者只管自己的业务实现服务消费者向本地的SideCar发起请求本地的SideCar根据请求的路径向注册中心查询得到服务提供者的可用节点列表后再根据负载均衡策略选择一个服务提供者节点并向这个节点上的SideCar转发请求服务提供者节点上的SideCar完成流量统计、限流等功能后再把请求转发给本地部署的服务提供者进程从而完成一次服务请求。整个流程你可以参考下面这张图。
我们可以把服务消费者节点上的SideCar叫作正向代理服务提供者节点上的SideCar叫作反向代理那么Service Mesh架构的关键点就在于服务消费者发出的请求如何通过正向代理转发以及服务提供者收到的请求如何通过反向代理转发。从我的经验来看主要有两种实现方案。
基于iptables的网络拦截。这种方案请见下图节点A上服务消费者发出的TCP请求都会被拦截然后发送给正向代理监听的端口15001正向代理处理完成后再把请求转发到节点B的端口9080。节点B端口9080上的所有请求都会被拦截发送给反向代理监听的端口15001反向代理处理完后再转发给本机上服务提供者监听的端口9080。
采用协议转换的方式。这种方案请见下图节点A上的服务消费者请求直接发给正向代理监听的端口15001正向代理处理完成后再把请求转发到节点B上反向代理监听的端口15001反向代理处理完成后再发送给本机上的服务提供者监听的端口9080。
可见这两种方案最大的不同之处在于一个是通过iptables网络拦截实现代理转发的一个是靠直接把请求发送给代理来转发的。基于iptables网络拦截的方式理论上会有一定的性能损耗但它的优点是从网络层实现调用拦截能做到完全的业务无感知所以适合云原生应用。而直接把请求发送给代理的方式要求代理层加入业务逻辑才能把请求转发给对应的服务提供者监听的端口。
2.Control Plane
既然SideCar能实现服务之间的调用拦截功能那么服务之间的所有流量都可以通过SideCar来转发这样的话所有的SideCar就组成了一个服务网格再通过一个统一的地方与各个SideCar交互就能控制网格中流量的运转了这个统一的地方就在Sevice Mesh中就被称为Control Plane。如下图所示Control Plane的主要作用包括以下几个方面
服务发现。服务提供者会通过SideCar注册到Control Plane的注册中心这样的话服务消费者把请求发送给SideCar后SideCar就会查询Control Plane的注册中心来获取服务提供者节点列表。
负载均衡。SideCar从Control Plane获取到服务提供者节点列表信息后就需要按照一定的负载均衡算法从可用的节点列表中选取一个节点发起调用可以通过Control Plane动态修改SideCar中的负载均衡配置。
请求路由。SideCar从Control Plane获取的服务提供者节点列表也可以通过Control Plane来动态改变比如需要进行A/B测试、灰度发布或者流量切换时就可以动态地改变请求路由。
故障处理。服务之间的调用如果出现故障就需要加以控制通常的手段有超时重试、熔断等这些都可以在SideCar转发请求时通过Control Plane动态配置。
安全认证。可以通过Control Plane控制一个服务可以被谁访问以及访问哪些信息。
监控上报。所有SideCar转发的请求信息都会发送到Control Plane再由Control Plane发送给监控系统比如Prometheus等。
日志记录。所有SideCar转发的日志信息也会发送到Control Plane再由Control Plane发送给日志系统比如Stackdriver等。
配额控制。可以在Control Plane里给服务的每个调用方配置最大调用次数在SideCar转发请求给某个服务时会审计调用是否超出服务对应的次数限制。
总结
今天我给你讲解了什么是Service Mesh以及Service Mesh的实现原理。简单来说Service Mesh思想的孕育而生一方面出于各大公司微服务技术的普及增加了对跨语言服务调用的需求另一方面得益于微服务容器化后采用Kubernetes等云平台部署的云原生应用越来越多服务治理的需求也越来越强烈。Service Mesh通过SideCar代理转发请求把服务框架的相关实现全部集中到SideCar中并通过Control Plane控制SideCar来实现服务治理的各种功能这种业务与框架功能解耦的思想恰好能够解决上面两个问题。
Service Mesh在诞生不到两年的时间里取得令人瞩目的发展在国内外都涌现出一批具有代表性的新产品最著名的莫过于Google、IBM领导的Istio也是Service Mesh技术的代表之作我会在下一期给你详细讲解。而国内在这一方面也不遑多让秉承了Service Mesh的思想也走出了各自的实践之路并且已经开始在线上的核心业务中大规模使用比如微博的Weibo Mesh、华为公有云Service Mesh以及蚂蚁金服的SOFA Mesh等。
思考题
Service Mesh中SideCar的部署模式是在每个服务节点的本地都同等部署一个SideCar实例为什么不使用集中式的部署模式让多个服务节点访问一个SideCar实例
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,270 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
34 IstioService Mesh的代表产品
专栏上一期我们聊了Service Mesh并以Linkerd为例介绍了Service Mesh的架构。随着技术发展现在来看Linkerd可以说是第一代Service Mesh产品到了今天当我们再谈到Service Mesh时往往第一个想到的是Istio。为什么我认为Istio可以称得上是Service Mesh的代表产品呢在我看来主要有以下几个原因
相比LinkerdIstio引入了Control Plane的理念通过Control Plane能带来强大的服务治理能力可以称得上是Linkerd的进化算是第二代的Service Mesh产品。
Istio默认的SideCar采用了Envoy它是用C++语言实现的在性能和资源消耗上要比采用Scala语言实现的Linkerd小这一点对于延迟敏感型和资源敏感型的服务来说尤其重要。
有Google和IBM的背书尤其是在微服务容器化的大趋势下云原生应用越来越受欢迎而Google开源的Kubernetes可以说已经成为云原生应用默认采用的容器平台基于此Google可以将Kubernetes与Istio很自然的整合打造成云原生应用默认的服务治理方案。
现在我们一起走进Istio的架构看看它各部分的实现原理希望能让你有所收获。
Istio整体架构
如下图所示Istio的架构可以说由两部分组成分别是Proxy和Control Plane。
Proxy就是前面提到的SideCar与应用程序部署在同一个主机上应用程序之间的调用都通过Proxy来转发目前支持HTTP/1.1、HTTP/2、gRPC以及TCP请求。
Control Plane与Proxy通信来实现各种服务治理功能包括三个基本组件Pilot、Mixer以及Citadel。
-
图片来源https://istio.io/docs/concepts/what-is-istio/arch.svg
下面我来详细分解Istio架构看看每一个组件的作用和工作原理。
Proxy
Istio的Proxy采用的是EnvoyEnvoy是跟上一期提到的Linkerd是同一代的产品既要作为服务消费者端的正向代理又要作为服务提供者端的反向代理一般需要具备服务发现、服务注册、负载均衡、限流降级、超时熔断、动态路由、监控上报和日志推送等功能它主要包含以下几个特性
性能损耗低。因为采用了C++语言实现Envoy能提供极高的吞吐量和极少的长尾延迟而且对系统的CPU和内存资源占用也不大所以跟业务进程部署在一起不会对业务进程造成影响。
可扩展性高。Envoy提供了可插拔过滤器的能力用户可以开发定制过滤器以满足自己特定的需求。
动态可配置。Envoy对外提供了统一的API包括CDS集群发现服务、RDS路由发现服务、LDS监听器发现服务、EDSEndPoint发现服务、HDS健康检查服务、ADS聚合发现服务等。通过调用这些API可以实现相应配置的动态变更而不需要重启Envoy。
Envoy是Istio中最基础的组件所有其他组件的功能都是通过调用Envoy提供的API在请求经过Envoy转发时由Envoy执行相关的控制逻辑来实现的。
Pilot
Pilot的作用是实现流量控制它通过向Envoy下发各种指令来实现流量控制它的架构如下图所示。从架构图里可以看出Pilot主要包含以下几个部分
Rules API对外封装统一的API供服务的开发者或者运维人员调用可以用于流量控制。
Envoy API对内封装统一的API供Envoy调用以获取注册信息、流量控制信息等。
抽象模型层,对服务的注册信息、流量控制规则等进行抽象,使其描述与平台无关。
平台适配层用于适配各个平台如Kubernetes、Mesos、Cloud Foundry等把平台特定的注册信息、资源信息等转换成抽象模型层定义的平台无关的描述。
-
图片来源https://istio.io/docs/concepts/traffic-management/PilotAdapters.svg
那么具体来讲Pilot是如何实现流量管理功能的呢
1.服务发现和负载均衡
就像下图所描述的那样服务B也就是服务提供者注册到对应平台的注册中心中去比如Kubernetes集群中的Pod启动时会注册到注册中心etcd中。然后服务A也就是服务消费者在调用服务B时请求会被Proxy拦截然后Proxy会调用Pilot查询可用的服务提供者节点再以某种负载均衡算法选择一个节点发起调用。
除此之外Proxy还会定期检查缓存的服务提供者节点的健康状况当某个节点连续多次健康检查失败就会被从Proxy从缓存的服务提供者节点列表中剔除。
-
图片来源https://istio.io/docs/concepts/traffic-management/LoadBalancing.svg
2.请求路由
Pilot可以对服务进行版本和环境的细分服务B包含两个版本v1.5和v2.0-alpha其中v1.5是生产环境运行的版本而v2.0-alpha是灰度环境运行的版本。当需要做A/B测试时希望灰度服务B的1%流量运行v2.0-alpha版本就可以通过调用Pilot提供的Rules APIPilot就会向Proxy下发路由规则Proxy在转发请求时就按照给定的路由规则把1%的流量转发给服务B的v2.0-alpha版本99%的流量转发给服务B的v1.5版本。
-
图片来源https://istio.io/docs/concepts/traffic-management/ServiceModel_Versions.svg
3.超时重试
缺省状态下Proxy转发HTTP请求时的超时是15s可以通过调用Pilot提供的Rules API来修改路由规则覆盖这个限制。比如下面这段路由规则表达的意思是ratings服务的超时时间是10s。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- route:
- destination:
host: ratings
subset: v1
timeout: 10s
除此之外还可以通过修改路由规则来指定某些HTTP请求的超时重试次数比如下面这段路由规则表达的意思就是ratings服务的超时重试次数总共是3次每一次的超时时间是2s。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- route:
- destination:
host: ratings
subset: v1
retries:
attempts: 3
perTryTimeout: 2s
4.故障注入
Istio还提供了故障注入的功能能在不杀死服务节点的情况下通过修改路由规则将特定的故障注入到网络中。它的原理是在TCP层制造数据包的延迟或者损坏从而模拟服务超时和调用失败的场景以此来观察应用是否健壮。比如下面这段路由规则的意思是对v1版本的ratings服务流量中的10%注入5s的延迟。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- fault:
delay:
percent: 10
fixedDelay: 5s
route:
- destination:
host: ratings
subset: v1
而下面这段路由规则意思是对v1版本的ratings服务流量中的10%注入HTTP 400的错误。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- fault:
abort:
percent: 10
httpStatus: 400
route:
- destination:
host: ratings
subset: v1
Mixer
Mixer的作用是实现策略控制和监控日志收集等功能实现方式是每一次Proxy转发的请求都要调用Mixer它的架构请见下图。而且Mixer的实现是可扩展的通过适配层来适配不同的后端平台这样的话Istio的其他部分就不需要关心各个基础设施比如日志系统、监控系统的实现细节。
-
图片来源https://istio.io/docs/concepts/policies-and-telemetry/topology-without-cache.svg
理论上每一次的服务调用Proxy都需要调用Mixer一方面检查调用的合法性一方面要上报服务的监控信息和日志信息所以这就要求Mixer必须是高可用和低延迟的那么Mixer是如何做到的呢下图是它的实现原理从图中你可以看到Mixer实现了两级的缓存结构
Proxy端的本地缓存。为了减少Proxy对Mixer的调用以尽量降低服务调用的延迟在Proxy这一端会有一层本地缓存但由于Proxy作为SideCar与每个服务实例部署在同一个节点上所以不能对服务节点有太多的内存消耗所以就限制了Proxy本地缓存的大小和命中率。
Mixer的本地缓存。Mixer是独立运行的所以可以在Mixer这一层使用大容量的本地缓存从而减少对后端基础设施的调用一方面可以减少延迟另一方面也可以最大限度减少后端基础设施故障给服务调用带来的影响。
-
图片来源https://istio.io/docs/concepts/policies-and-telemetry/topology-with-cache.svg
那么Mixer是如何实现策略控制和监控日志收集功能呢
1.策略控制
Istio支持两类的策略控制一类是对服务的调用进行速率限制一类是对服务的调用进行访问控制它们都是通过在Mixer中配置规则来实现的。具体来讲速率限制需要配置速率控制的yaml文件每一次Proxy转发请求前都会先调用MixerMixer就会根据这个yaml文件中的配置来对调用进行速率限制。比如下面这段配置表达的意思是服务默认访问的速率限制是每秒5000次除此之外还定义了两个特殊限制第一个是v3版本的reviews服务请求ratings服务的速率限制是每5秒1次第二个是其他服务请求ratings服务的速率限制是每10秒5次。
apiVersion: config.istio.io/v1alpha2
kind: memquota
metadata:
name: handler
namespace: istio-system
spec:
quotas:
- name: requestcount.quota.istio-system
maxAmount: 5000
validDuration: 1s
overrides:
- dimensions:
destination: ratings
source: reviews
sourceVersion: v3
maxAmount: 1
validDuration: 5s
- dimensions:
destination: ratings
maxAmount: 5
validDuration: 10s
而访问控制需要配置访问控制的yaml文件每一次Proxy转发请求前都会先调用MixerMixer就会根据这个yaml文件中的配置来对调用进行访问控制。比如下面这段配置表达的意思是v3版本的reviews服务调用ratings服务就会被拒绝。
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
name: denyreviewsv3
spec:
match: destination.labels["app"] == "ratings" && source.labels["app"]=="reviews" && source.labels["version"] == "v3"
actions:
- handler: denyreviewsv3handler.denier
instances: [ denyreviewsv3request.checknothing ]
2.监控和日志收集
跟策略控制的实现原理类似Mixer的监控、日志收集功能也是通过配置监控yaml文件来实现的Proxy发起的每一次服务调用都会先调用Mixer把监控信息发给MixerMixer再根据配置的yaml文件来决定监控信息该发到哪。示例yaml文件可以参考这个链接。
Citadel
Citadel的作用是保证服务之间访问的安全它的工作原理见下图可见实际的安全保障并不是Citadel独立完成的而是需要Proxy、Pilot以及Mixer的配合具体来讲
Citadel里存储了密钥和证书。
通过Pilot把授权策略和安全命名信息分发给Proxy。
Proxy与Proxy之间的调用使用双向TLS认证来保证服务调用的安全。
最后由Mixer来管理授权和审计。
-
图片来源https://istio.io/docs/concepts/security/architecture.svg
总结
今天我给你详细讲解了Istio的架构及其基本组件Proxy、Pilot、Mixer以及Citadel的工作原理从Istio的设计和实现原理可以看出它是采用模块化设计并且各个模块之间高度解耦Proxy专注于负责服务之间的通信Pilot专注于流量控制Mixer专注于策略控制以及监控日志功能而Citadel专注于安全。正是这种高度模块化的设计使得Istio的架构极具扩展性和适配性如果你想加强流量控制方面的功能可以在Pilot模块中定制开发自己的代码而不需要修改其他模块如果你想增加一种监控系统支持可以在Mixer模块中添加对这个监控系统的适配器就能接入Istio。除此之外虽然Istio由Google和IBM主导但也没有完全与Kubernetes平台绑定你也可以在Mesos或者AWS上运行Istio可见它的适配性极强这也是Istio的强大之处以至于它的竞争对手Linkerd也开始支持Istio作为可选的Proxy组件之一。
思考题
Mixer的一个功能是实现服务调用的日志收集假如某一个服务调用并发量很高而每一次调用都经过Proxy代理请求Mixer再由Mixer调用后端的日志系统的话整个链路的网络延迟就会对服务调用的性能影响很大你有什么优化建议吗
欢迎你在留言区写下自己的思考,与我一起讨论。
扩展阅读:
Envoy对外提供统一API的详细作用https://github.com/envoyproxy/data-plane-api/blob/master/API_OVERVIEW.md
授权策略https://istio.io/docs/concepts/security/#authentication-policies
安全命名信息https://istio.io/docs/concepts/security/#secure-naming
双向TLS认证https://istio.io/docs/tasks/security/mtls-migration/

View File

@ -0,0 +1,81 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
35 微博Service Mesh实践之路
专栏上一期我们聊了Service Mesh的代表作Istio由于Istio的设计理念非常新并且它诞生在微服务容器化和Kubernetes云平台火爆之后所以从设计和实现上Istio都天生对云原生应用更友好。
但是现实是不是也是那么美好呢对于一个已经上线运行多年的业务系统来说要想从经典的微服务架构走上Istio这条看似完美的道路并不容易各种内部基础设施的定制化以及业务稳定性优先准则等因素都注定了大多数公司要走出一条自己的Service Mesh实践之路。今天我就来带你回顾下微博是如何一步步走向Service Mesh的。
跨语言服务调用的需求
我在前面讲过微博的服务化框架采用的是自研的MotanMotan诞生于2013年出于微博平台业务单体化架构拆分为微服务改造的需求在结合当时的开源服务化框架和自身实际的需求选择了采用自研的方式。而且由于微博平台的业务采用的是Java语言开发所以Motan早期只支持Java语言。后期随着微博业务的高速发展越来越多的PHP业务开始登上舞台于是在微博的流量体系中主要是三股服务之间的相互调用一个是Java与Java语言一个是PHP和Java语言一个是PHP和PHP语言。Java应用之间的调用采用的是Motan协议而Java应用与PHP、PHP与PHP应用之间采用的都是HTTP协议。我回忆了一下当时一次PHP与Java之间的HTTP调用过程大致需要经过DNS解析、四层LVS负载均衡、七层Nginx负载均衡最后才能调用Java应用本身。
从上面这张图可以看出一次HTTP调用的链路相当长从我的实践来看经常会遇到好几个问题。
第一个问题中间链路损耗大。由于一次HTTP调用要经过DNS、LVS、Nginx这三个基础设施每一层都会带来相应的损耗。我曾经在线上就碰到过因为DNS解析延迟、LVS带宽打满引起的网络延迟以及Nginx本地磁盘写满引起的转发延迟等各种情况造成接口响应在中间链路的损耗甚至超过了接口本身业务逻辑执行的时间。
第二个问题:全链路扩容难。由于微博业务经常要面临突发热点事件带来的流量冲击,所以需要能够随时随地动态扩缩容。其实在应用本身这一层扩容并不是难点,比较麻烦的是四七层负载均衡设备的动态扩缩容,它涉及如何评估容量、如何动态申请节点并及时修改生效等,要完成一次全链路扩容的话,复杂度非常高,所以最后往往采取的办法是给四七层负载均衡设备预备足够的冗余度,在峰值流量到来时,只扩容应用本身。
第三个问题混合云部署难。专栏前面我讲过微博的业务目前采用的是混合云部署也就是在内网私有云和公有云上都有业务部署同样也需要部署四七层负载均衡设备并且要支持公有云上的请求经过DNS解析后要能够转发到公有云上的负载均衡设备上去避免跨专线访问带来不必要的网络延迟和专线带宽占用。
因此迫切需要一种支持跨语言调用的服务化框架使得跨语言应用之间的调用能够像Java应用之间的调用一样不需要经过其他中间链路转发做到直接交互就像下图描述的那样。
Yar协议的初步尝试
为此微博最开始考虑基于Motan框架进行扩展使其支持PHP语言的Yar协议下面是扩展后的架构图。这个架构的思路是PHP客户端的服务发现通过Nginx来支持经过Nginx把PHP的Yar协议请求转发给服务端由于Motan框架中了适配Yar协议服务端会把PHP的Yar协议请求转换成Motan请求来处理处理完后再转成Yar协议的返回值经过Nginx返回给客户端。
但这种架构主要存在两个问题。
第一个问题Motan协议与Yar协议在基本数据结构和序列化方式的支持有所不同需要经过复杂的协议转换。
第二个问题服务调用还必须依赖Nginx所以调用链路多了一层在应用部署和扩容时都要考虑Nginx。
gRPC会是救命稻草吗
时间往后推演gRPC横空出世它良好的跨语言特性以及高效的序列化格式的特性吸引了我们于是便开始考虑在Motan中集成gRPC来作为跨语言通信的协议。当时设计了下图的架构这个架构的思路是利用gRPC来生成PHP语言的Client然后在Motan框架中加入对gRPC协议的支持这样的话PHP语言的Client就可以通过gRPC请求来调用Java服务。
但在我们的实际测试中发现微博的业务场景并不适合gRPC协议因为gRPC协议高度依赖PB序列化而PHP对PB的兼容性不是很好在微博的业务场景下一个接口返回值有可能超过几十KB此时在PHP Client端PB数据结构解析成JSON对象的耗时甚至达到几十毫秒这对业务来说是不可接受的。而且gRPC当时还不支持PHP作为Server对外提供服务也不满足微博这部分业务场景的需要。
代理才是出路
考虑到PHP语言本身没有常驻内存控制的能力在实现服务注册和发现以及其他各种服务框架功能时仅靠PHP-FPM进程本身难以实现因此需要一个统一常驻内存的进程来帮助完成服务框架的各种功能。一开始我们考虑过使用本地守护进程和OpenResty的Timer来实现服务发现但其他服务框架的功能不太好实现比如专栏前面提到的各种复杂的负载均衡策略、双发、熔断等。为此我们希望通过一个Agent也就是代理来帮助PHP进程来完成服务框架的各种功能PHP进程本身只需要负责运行业务逻辑的代码以及最简单的Motan协议解析。基于这个思路当时我们设计了下面这个架构它的思路就是在PHP进程的本地也部署一个AgentPHP进程发出去的请求都经过Agent进行处理后再发给对应的Java应用。
向Service Mesh迈进
2017年就在我们开始采用Agent方案对业务进行改造以支持PHP应用调用Java应用服务化的时候Service Mesh的概念突然火热起来并随着Istio的发布风靡业界。相信经过我前面对Service Mesh的讲解你一定会发现这里的Agent不恰恰就是Service Mesh中的SideCar吗没错我们跨语言调用的解决方案竟然与Service Mesh的理念不谋而合。借鉴Service Mesh的思想我们也对Agent方案进一步演化不仅客户端的调用需要经过本地的Agent处理后再转发给服务端服务端在处理前也需要经过本地的Agent最后再由服务端业务逻辑处理下面是它的架构图。如此一来业务只需要进行集成最简单的Motan协议解析而不需要关心其他服务框架功能可以理解为业务只需要集成一个轻量级的Client用于Motan协议解析而繁杂的服务框架功能全都由Agent来实现从而实现业务与框架功能的解耦。
从上面的图中你可以看出这个架构与上一期我们聊的Istio大体思路相同但是区别还是很明显的可以概括为以下几点
都通过SideCar方式部署的代理来实现流量转发Istio里使用的是Envoy而Weibo Mesh采用的是自研的Motan-go Agent。这里有一个很明显的区别是Weibo Mesh中业务代码还需要集成一个轻量级的Client所以对业务有一定的倾入性而Istio采用的是iptables技术拦截网络请求给Envoy所以业务无需做任何变更更适合云原生应用。在微博的业务场景下由于大部分业务并不是云原生应用都是部署在物理机或者虚拟机集群之中的所以需要根据自己的业务特点来决定SideCar的部署方式。而且Weibo Mesh中的轻量级Client除了实现基本的Motan协议的解析功能之外还添加了一些业务需要的特性比如为了防止Agent不可用在本地保存了一份服务节点的本地快照必要时Client可以访问本地快照获得节点的地址直接向服务节点Server发起调用而不需要经过Agent转发处理只不过这个时候就丧失了Agent的服务治理功能。
Weibo Mesh和Istio都具备服务治理功能只不过Istio是通过Control Plane来控制Proxy来实现并且Control Plane包括三个组件Pilot、Mixer以及Citedar三者各司其职。而Weibo Mesh是通过统一的服务治理中心来控制Agent从而实现服务治理的。这是因为微博本身的各种基础设施大部分是自研的比如注册和配置中心是自研的Vintage、监控系统是自己基于Graphite改造的、容器平台DCP以及负责容量评估的Diviner也是自研的为此需要一个统一的地方把这些基础设施串联起来。而Istio好像就为开源而生设计之初就要考虑如何更好地集成并支持各类开源方案比如专门抽象出Mixer组件来对接各种监控和日志系统。
总结
今天我给你讲解了微博是如何一步步走向Service Mesh之路的从这个过程你可以看出微博的Weibo Mesh并不是一开始就是设计成这样的它是随着业务的发展对跨语言服务调用的需求日趋强烈才开始探索如何使得原有仅支持Java语言的服务化框架Motan支持多语言在这个过程中又不断尝试了各种解决方案之后才笃定了走Agent代理这条路并实际应用到线上。而随着Service Mesh概念的兴起微博所采用的Agent代理的解决方案与Service Mesh理念不谋而合于是在Agent代理的方案中吸纳Service Mesh的思想进一步演变成如今的Weibo Mesh。所以说一个可靠的架构从来都不是设计出来的是逐步演进而来的。
思考题
如果要支持更多不同语言应用之间的相互调用你觉得Weibo Mesh中的轻量级的Client需要做哪些工作
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,99 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
36 微博Service Mesh实践之路
专栏上一期我们聊到了微博的服务化是如何一步步走向Service Mesh之路的可以说正是由于微博自身业务对跨语言服务调用的需求日趋强烈才促使了Weibo Mesh的诞生也因此乘上了Service Mesh的东风。我在前面讲过Service Mesh主要由两部分组成一部分是SideCar负责服务之间请求的转发一部分是Control Plane负责具体的服务治理。从Weibo Mesh的实现方案来看对应的SideCar采用的是自研的Motan-go Agent服务治理则是通过统一服务治理中心来实现这里面的一些思路还是和Control Plane有很大区别的。
今天我们就来聊聊Weibo Mesh实现的技术细节看看它给业务带来了哪些收益最后再谈谈Weibo Mesh下一步的发展方向。
Motan-go Agent
通过上一期的学习我们知道Weibo Mesh中使用的SideCar就是Motan-go Agent考虑到Motan-go Agent要与PHP进程部署在一起为了减少对本机资源的占用这里Motan-go Agent采用了Go语言来实现它包含的功能模块请看下图。
我们拆解一下图中Motan-go Agent主要的模块看看它们的作用是什么。
Filter Chain模块是以请求处理链的组合方式来实现AccessLog请求日志记录、Metric监控统计、CircuitBreaker熔断、Switcher降级、Tracing服务追踪、Mock单元测试、ActiveLimit限流等功能。
High Available模块是用来保证高可用性默认集成了Failover、Backup Request等故障处理手段。
Load Balance模块负载均衡默认集成了Random、Roundrobin等负载均衡算法。
EndPoint模块的作用是封装请求来调用远程的Server端默认可以封装Motan请求和gRPC请求。
Serialize模块负责实现不同类型的序列化方式默认支持Simple序列化。
Server模块实现不同类型的Server要么是采用Motan协议实现要么是采用gRPC协议。
Motan-go Agent每个模块都是功能可扩展的你可以在Filter Chain模块加上自己实现的Trace功能这样请求在经过Filter Chain处理时就会自动加载你加上的Trace功能。当然你也可以在High Available模块添加自己实现的故障处理手段在Load Balance模块里实现自己的负载均衡算法在EndPoint模块封装HTTP协议的请求在Serialize模块添加PB序列化在Server模块实现HTTP协议等。
另外Motan-go Agent之间的通信采用的是自定义的Motan2协议它把请求中的Meta信息与请求参数信息进行了分离更适合对请求进行代理转发并且默认使用了Simple序列化来对不同语言的数据进行编码以实现跨语言服务通信。
更多关于Motan2协议和Simple序列化的介绍你可以点击这里查看。
统一服务治理中心
专栏上一期我给你讲过在Weibo Mesh中是通过统一服务治理平台与Motan-go Agent交互来实现服务治理功能的。对着下面这张Weibo Mesh的架构图我们一起看一下统一服务治理平台SGCenter具体是如何与Motan-go Agent交互来实现服务治理的各项功能的。
1.动态服务注册与发现
首先来看下统一服务治理平台是如何实现服务注册与发现的。如下图所示在Motan-go Agent中实现了具体的服务注册与发现的逻辑Server端进程启动时会通过Motan-go Agent向Vintage注册中心发起注册请求把服务注册到Vintage中。Client端发起服务调用时会经过Motan-go Agent转发Motan-go Agent会调用Vintage查询该服务在Vintage中的注册信息获取到服务节点列表后按照某一种负载均衡算法选择一个服务节点向这个服务节点发起调用。可以通过统一服务治理平台SGCenter调用Vintage的管理接口执行添加或者删除服务节点等操作Motan-go Agent会感知到服务节点的变化获取最新的服务节点。一般在业务开发或者运维人员需要手工扩容或者缩容一批服务节点时才会执行这个操作。
2.监控上报
再看下面这张图Client端发起的请求经过Motan-go Agent转发时Motan-go Agent就会在内存中统计每一次调用的耗时、成功率等信息并且每隔固定的时间间隔将这段时间内各个服务调用的QPS、平均耗时、成功率以及P999等metric信息发送给Graphite监控系统。这样的话通过SGCenter调用Graphite的Web API就可以获取到服务调用的信息了。
3.动态流量切换与降级
动态流量切换与降级的过程请看下面这张图。Motan-go Agent在查询Vintage中某个服务节点信息的同时也会订阅该服务的变更这样的话就可以通过SGCenter向Vintage下发服务的切流量或者降级指令订阅了这个服务的Motan-go Agent就会收到变更通知如果是切流量指令比如把调用永丰机房服务的流量都切换到土城机房那么Motan-go Agent就会把原本发给永丰机房的请求都发给土城机房如果是降级指令Motan-go Agent就会停止调用这个服务。
4.自动扩缩容
服务调用时Motan-go Agent会把Server端服务调用的监控信息上报给Graphite监控系统同时Diviner容量评估系统会实时调用Graphite以获取服务在不同区间的QPS信息以计算服务池的水位线然后SGCenter会每隔一段时间调用Diviner来获取各个服务池的冗余度以决定是否需要扩容。假如此时服务池的冗余度不足的话SGCenter就会调用DCP容器运维平台给服务池进行扩容DCP完成扩容后新的服务节点就会注册到Vintage当中这样的话订阅了该服务的Motan-go Agent就会感知到服务节点的变化从Vintage中获取最新的服务节点信息这就是一个服务自动扩缩容的整个流程你可以参考下面这张图。
Weibo Mesh的收益
经过前面的讲解相信你已经对Weibo Mesh的实现方案有了一定的了解。Weibo Mesh是在微博的业务场景下一步步进化到今天这个架构的它给微博的业务带来的巨大的收益总结起来主要有以下几点
跨语言服务化调用的能力。Weibo Mesh发展之初最首要的目的就是想让微博内部的Motan服务化框架能够支持PHP应用与Java应用之间调用因而开发了Motan-go Agent并在此基础上演变成今天的Weibo Mesh。支持多种语言之间的服务化调用有助于统一公司内部业务不同语言所采用的服务化框架达到统一技术体系的目的。
统一服务治理能力。以微博应对突发热点事件带来的峰值流量冲击为例为了确保首页信息流业务的稳定性我们有针对性的研发了自动扩缩容系统。而随着微博的不断发展不断涌现出新的业务线比如热门微博和热搜也同样面临着突发热点事件带来的流量冲击压力。而开发一套稳定可用的自动扩缩容系统并非一朝一夕之事如何能够把信息流业务研发的自动扩缩容系统推广到各个业务线是个比较棘手的问题。因为信息流业务的后端主要采用了Java语言实现而热门微博和热搜主要采用的是PHP语言无法直接接入自动扩缩容系统。而Weibo Mesh可以支持多种语言将热门微博和热搜业务进行服务化改造就可以统一接入到自动扩缩容系统实现了公司级的统一服务治理能力。
业务无感知的持续功能更新能力。采用Motan或者Dubbo类似的传统服务化框架一旦服务框架功能有升级就需要业务同步进行代码升级这对大部分业务来说都是一种不愿承受的负担。而采用Weibo Mesh添加新功能只需要升级Motan-go Agent即可业务代码不需要做任何变更对于业务开发人员更友好。尤其是作为公司级的服务化框架时服务框架的升级如果跟业务系统升级绑定在一起从我的实践经验来看将是一件耗时费力的工作需要协调各个业务方配合才能完成。而Weibo Mesh可以看作是服务器上部署的基础组件它的升级与维护不需要各个业务方的参与这样才能具备作为公司级的服务化框架推广到各个业务线的前提。
Weibo Mesh的发展规划
在微博的业务场景下存在大量服务对缓存、数据库以及消息队列等资源的调用如果把资源也看作是一种服务那么Weibo Mesh不仅可以管理服务与服务之间的调用还可以管理服务与资源之间的调用这样的话Weibo Mesh强大的服务治理能力也能延伸到对资源的治理上对业务来说又将解决资源治理这一大难题。另一方面随着Weibo Mesh治理的服务越来越多收集的数据也越来越多利用这些数据可以挖掘一些更深层次的东西也是Weibo Mesh未来的发展方向之一。比如引入机器学习算法对采集的数据进行分析进行监控报警的优化等。
总结
今天我从Motan-go Agent和统一服务治理中心的具体实现这两个方面给你讲解了Weibo Mesh的技术细节你可以看到很多都是微博基于自身业务特点定制化的解决方案。对于大部分中小团队来说除非从一开始就采用了云原生应用的部署方式否则Istio等开源方案并不能直接拿来就用都需要从自身的业务特征和既有技术体系出发选择一条适合自己的Service Mesh实践之路。Weibo Mesh也因为其紧贴业务并没有脱离实际去设计所以才能够在微博的业务中落地生根被证明是行之有效的架构实践使得微博服务化体系的统一成为可能也坚定了我们在Weibo Mesh这条路上继续走下去。
思考题
Service Mesh中业务与服务框架解耦的优秀设计思想除了能用于服务与服务之间相互调用的场景你认为还能应用于哪些业务场景中
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,81 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
微博技术解密(上) 微博信息流是如何实现的?
专栏结束后,有不少同学留言希望我能讲一些微博基础架构的知识。所以接下来的微博技术解密系列,我将分享微博在信息流架构、存储中间件等方面的经验,希望能给你带来启发和帮助。
今天我们先来看微博信息流架构也就是微博的Feed是如何构建的。首先什么是Feed呢根据我的理解Feed是互联网2.0时代的产物它与互联网1.0时代的产物——门户网站最大的不同之处就是Feed不需要用户在各个板块之间来回跳转获取信息而是把不同的信息都聚合在一起可以供用户源源不断地访问。这里就涉及了两个问题一个是信息如何保存另一个是信息如何聚合。这也是今天我要分享的主要内容我会从存储架构的角度阐述微博Feed是如何存储的然后会从业务架构的角度阐述微博Feed是如何聚合的。
微博Feed存储架构
我们知道微博Feed是由关注人的微博聚合在一起组成的所以要存储每个人发的微博那么在设计存储架构时主要需要注意三个问题
每秒数据写入量,也就是每秒发博量是多大。
每秒数据访问量,也就是每秒微博请求量是多大。
是否有冷热数据之分,也就是微博的请求是否有时间特点。
结合微博的业务场景我来回答上面提出的三个问题。首先是每秒发博量这里要考虑到极端情况比如元旦零点瞬间会有大量用户发博达到数万QPS。再来看下每秒微博请求量同样要考虑到在热点事件时比如“春晚”时会有大量用户访问微博请求量也会达到数万QPS并且每个用户关注的不止是一个人假设关注数的平均值是200那么微博数据的请求量就是几百万QPS。除此之外微博的访问也是有时间特点的用户一般访问新发微博的概率要远远大于一周前发的微博所以说微博数据也是有冷热之分的。
这三个问题共同决定了微博的存储架构应该如何设计。在讨论微博存储架构前,我们先来看看目前业界比较成熟的存储方案,主要分为下面几种。
以MySQL为代表的关系型数据库。主要用来存储结构比较固定的数据因为使用的是磁盘存储所以写入和访问能力主要取决于磁盘的读写能力。而磁盘主要分为SAS盘和SSD盘也就是机械盘和固态盘两者的读写能力有一定的差距SSD盘读写能力是SAS盘的3倍左右不过QPS都在千级别。磁盘存储的特点是不易丢失数据可以永久保存。
以Memcached和Redis为代表的内存存储。服务器的内存大小一般要远小于磁盘在几十GB到几百GB之间而磁盘通常都是TB级。内存存储的优势就是读写速度快读写能力能到几十万QPS远远大于磁盘存储。但由于数据存储在内存中如果进程挂掉或者机器重启内存中的数据就清空了。
HBase为代表的分布式存储。属于非关系型数据库它的特点是数据结构不固定因此适合非结构化的数据存储而且由于采用了分布式存储使用HDFS作为底层文件存储系统所以可以存储海量数据并且具备非常高的写入性能。
讲到这里,结合前面提到的微博业务场景,你觉得存储架构该如何设计呢?
根据微博的实际业务情况用户的微博需要永久保存也就是进行持久化存储而且微博的数据结构是固定的优先考虑采用关系型数据库因此MySQL是一种选择。但是考虑到单台MySQL读能力的极限是不到一万QPS而微博的数据请求量是几百万QPS如果单纯使用MySQL来应对的话需要上千台服务器成本非常高。还有一点就是微博数据的请求是有冷热之分的一周外的数据访问的概率要远小于一周内的数据。综合以上几点考虑可以在MySQL存储的前面再加一层缓存比如使用Memcached存储最近一周内的微博而用户的全量微博数据则持久化存储在MySQL中。假设用户访问一周内微博的概率是99%那么对缓存的请求量就是几百万QPS而对数据库的请求量就只有几万QPS了而缓存的读写能力是几十万QPS相比较而言需要的机器数要远小于只使用数据库了。
微博Feed业务架构
经过前面的讲解假设我们已经使用MySQL + Memcached的双层存储架构解决了微博的存储问题接下来面临的问题就是如何将存储的关注人的微博数据聚合成一股源源不断的信息流以供用户访问。
根据我的经验,信息流聚合一般有三种架构:推模式、拉模式以及推拉结合,下面我来详细讲解这三种架构。
1. 推模式
推模式,顾名思义就是把关注人的发的微博,主动推送给粉丝,就像下图描述的那样。推模式相当于有一个收件箱,当关注人发微博的时候,就给所有粉丝的收件箱推送这条微博,这样的话,不管你关注了多少人,他们发的微博都会主动推送到你的收件箱,你只需要访问自己的收件箱,就可以获取到所有关注人发的微博了。
2. 拉模式
拉模式与推模式恰恰相反,就像图里那样每个人都有一个发件箱,发微博的时候就把微博存储到发件箱,这样的话,如果要获取所有关注人发的微博,就需要遍历关注人的发件箱列表,取出所有关注人的发件箱,然后按照时间顺序聚合在一起。
我们先来对比下微博Feed采用哪种架构比较合适。首先来看推模式它的特点是聚合简单每个人只要查看自己的收件箱就可以获取到关注人发的所有微博但缺点是即使粉丝没有请求Feed每个人发的微博也都要给所有粉丝推送一遍所以写入量会非常高尤其是对于具有上亿粉丝的用户来说发一条微博需要给上亿粉丝的收件箱推送这条微博这个写入量是非常大的给处理机和数据库带来的压力可想而知。并且同一条微博会存储多份占用的存储空间也非常大并且如果需要修改或者删除微博需要请求所有粉丝的收件箱。考虑到微博是公开的社交媒体平台拥有上千万粉丝的用户不在少数所以采用推模式的话存储成本以及数据更新成本会非常高需要大量的缓存和数据库以及队列机来更新。早期Twitter的Feed就采用了推模式经常出现更新延迟就是因为大量的写入带来巨大压力所导致。
再来看下拉模式,它的特点是聚合逻辑复杂,每个人要想查看关注人发的所有微博,需要遍历关注人列表,获取所有微博后再按照时间聚合在一起,对数据的请求量和聚合带来的计算量要远远大于推模式。但因为每个用户的微博只存在自己的发件箱列表中,所以相比于推模式来说,存储成本要小得多,并且如果有数据修改,只需要修改自己的发件箱列表就可以了。
3. 推拉结合
在对比了推模式和拉模式各自的优缺点之后也就自然而然产生一种新想法是不是可以采用推拉结合的方式各取两者的优点呢针对关注的粉丝量大的用户采用拉模式而对于一般用户来说他们的粉丝量有限采用推模式问题不大这样的话一个用户要获取所有关注人的微博一方面要请求粉丝量大的关注人的发件箱列表另一方面要请求自己的收件箱列表再把两者聚合在一起就可以得到完整的Feed了。
虽然推拉结合的方式看似更加合理但是由此带来的业务复杂度就比较高了因为用户的粉丝数是不断变化的所以对于哪些用户使用推模式哪些用户使用拉模式维护起来成本就很高了。所以综合考量下来微博Feed采用了拉模式。
前面提到采用拉模式的话需要拉取所有关注人的发件箱在关注人只有几十几百个的时候获取效率还是非常高的。但是当关注人上千以后耗时就会增加很多实际验证获取超过4000个用户的发件箱耗时要几百ms并且长尾请求也就是单次请求耗时超过1s的概率也会大大增加。为了解决关注人数上千的用户拉取Feed效率低的问题我们采用了分而治之的思想在拉取之前把用户的关注人分为几组并行拉取这样的话就把一次性的聚合计算操作给分解成多次聚合计算操作最后再把多次聚合计算操作的结果汇总在一起类似于MapReduce的思路。经过我们的实际验证通过这种方法可以有效地降级关注人数上千用户拉取Feed的耗时长尾请求的数量也大大减少了。
总结
今天我给你讲解了微博Feed的存储架构以及业务架构的选型你可以看到最终方案都是结合微博的业务场景以及当时存储的成熟度做出的选择。微博诞生于2009年并在2010年进行了平台化改造确定了现在这一套存储和业务架构。当时的存储成熟度决定了MySQL + Memcached的组合是最优解并没有使用Redis、HBase等后来更为先进的数据库同时由于微博Feed的业务特点选择了拉模式的业务架构并针对关注人数上千的场景进行了拉取方式的优化也被实践证明是行之有效的手段。
思考题
如果让你来设计微博的存储架构除了使用MySQL + Memcached你觉得还可以使用哪些存储
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@ -0,0 +1,77 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
微博技术解密(下)微博存储的那些事儿
今天是微博技术解密系列的第二期我们来聊聊微博存储的使用经验。上一期“微博技术解密”我讲到微博主要使用了两大类存储一类是数据库主要以MySQL为主一类是缓存主要以Memcached和Redis为主。
今天我来分享一下微博在使用数据库和缓存方面的经验,也欢迎你给我留言一起切磋讨论。
MySQL
上一期我讲到微博Feed的存储使用了两层的结构为了减少对MySQL数据库的访问压力在前面部署了Memcached缓存挡住了99%的访问压力只有1%的请求会访问数据库。然而对于微博业务来说这1%的请求也有几万QPS对于单机只能扛几千QPS的MySQL数据库来说还是太大了。为此我们又对数据库端口进行了拆分你可以看下面的示意图每个用户的UID是唯一的不同UID的用户按照一定的Hash规则访问不同的端口这样的话单个数据库端口的访问量就会变成原来的1/8。除此之外考虑到微博的读请求量要远大于写请求量所以有必要对数据库的读写请求进行分离写请求访问Master读请求访问Slave这样的话Master只需要一套Slave根据访问量的需要可以有多套也就是“一主多从”的架构。最后考虑到灾备的需要还会在异地部署一套冷备的灾备数据库平时不对外提供线上服务每天对所有最新的数据进行备份以防线上数据库发生同时宕机的情况。
Memcached
在MySQL数据库前面还使用了Memcached作为缓存来承担几百万QPS的数据请求产生的带宽问题是最大挑战。为此微博采用了下图所示的多层缓存结构即L1-Master-Slave它们的作用各不相同。
L1主要起到分担缓存带宽压力的作用并且如果有需要可以无限进行横向扩展任何一次数据请求都随机请求其中一组L1缓存这样的话假如一共10组L1数据请求量是200万QPS那么每一组L1缓存的请求量就是1/10也就是20万QPS同时每一组缓存又包含了4台机器按照用户UID进行Hash每一台机器只存储其中一部分数据这样的话每一台机器的访问量就只有1/4了。
Master主要起到防止访问穿透到数据库的作用所以一般内存大小要比L1大得多以存储尽可能多的数据。当L1缓存没有命中时不能直接穿透到数据库而是先访问Master。
Slave主要起到高可用的目的以防止Master的缓存宕机时从L1穿透访问的数据直接请求数据库起到“兜底”的作用。
Redis
微博的存储除了大量使用MySQL和Memcached以外还有一种存储也被广泛使用那就是Redis。并且基于微博自身的业务特点我们对原生的Redis进行了改造因此诞生了两类主要的Redis存储组件CounterService和Phantom。
1. CounterService
CounterService的主要应用场景就是计数器比如微博的转发、评论、赞的计数。早期微博曾采用了Redis来存储微博的转发、评论、赞计数但随着微博的数据量越来越大发现Redis内存的有效负荷还是比较低的它一条KV大概需要至少65个字节但实际上一条微博的计数Key需要8个字节Value大概4个字节实际上有效的只有12个字节其余四十多个字节都是被浪费的。这还只是单个KV如果一条微博有多个计数的情况下它的浪费就更多了比如转评赞三个计数一个Key是long结构占用8个字节每个计数是int结构占用4个字节三个计数大概需要20个字节就够了而使用Redis的话需要将近200个字节。正因为如此我们研发了CounterService相比Redis来说它的内存使用量减少到原来的1/151/5。而且还进行了冷热数据分离热数据放到内存里冷数据放到磁盘上并使用LRU如果冷数据重新变热就重新放到内存中。
你可以看下面的示意图CounterService的存储结构上面是内存下面是SSD预先把内存分成N个Table每个Table根据微博ID的指针序列划出一定范围。任何一个微博ID过来先找到它所在的Table如果有的话直接对它进行增减如果没有就新增加一个Key。有新的微博ID过来发现内存不够的时候就会把最小的Table dump到SSD里面去留着新的位置放在最上面供新的微博ID来使用。如果某一条微博特别热转发、评论或者赞计数超过了4个字节计数变得很大该怎么处理呢对于超过限制的我们把它放在Aux Dict进行存放对于落在SSD里面的Table我们有专门的Index进行访问通过RDB方式进行复制。
2. Phantom
微博还有一种场景是“存在性判断”比如某一条微博某个用户是否赞过、某一条微博某个用户是否看过之类的。这种场景有个很大的特点它检查是否存在因此每条记录非常小比如Value用1个位存储就够了但总数据量又非常巨大。比如每天新发布的微博数量在1亿条左右是否被用户读过的总数据量可能有上千亿怎么存储是个非常大的挑战。而且还有一个特点是大多数微博是否被用户读过的存在性都是0如果存储0的话每天就得存上千亿的记录如果不存的话就会有大量的请求最终会穿透Cache层到DB层任何DB都没有办法抗住那么大的流量。
假设每天要存储上千亿条记录用原生的Redis存储显然是不可行的因为原生的Redis单个KV就占了65个字节这样每天存储上千亿条记录需要增加将近6TB存储显然是不可接受的。而用上面提到的微博自研的CounterService来存储的话一个Key占8个字节Value用1个位存储就够了一个KV就占大约8个字节这样每天存储上千亿条记录需要增加将近800GB存储。虽然相比于原生的Redis存储方案已经节省了很多但存储成本依然很高每天将近1TB。
所以就迫切需要一种更加精密的存储方案针对存在性判断的场景能够最大限度优化存储空间后来我们就自研了Phantom。
就像下图所描述的那样Phantom跟CounterService一样采取了分Table的存储方案不同的是CounterService中每个Table存储的是KV而Phantom的每个Table是一个完整的BloomFilter每个BloomFilter存储的某个ID范围段的Key所有Table形成一个列表并按照Key范围有序递增。当所有Table都存满的时候就把最小的Table数据清除存储最新的Key这样的话最小的Table就滚动成为最大的Table了。
下图描述了Phantom的请求处理过程当一个Key的读写请求过来时先根据Key的范围确定这个Key属于哪个Table然后再根据BloomFilter的算法判断这个Key是否存在。
这里我简单介绍一下BloomFilter是如何判断一个Key是否存在的感兴趣的同学可以自己搜索一下BloomFilter算法的详细说明。为了判断某个Key是否存在BloomFilter通过三次Hash函数到Table的不同位置然后判断这三个位置的值是否为1如果都是1则证明Key存在。
来看下面这张图假设x1和x2存在就把x1和x2通过Hash后找到的三个位置都设置成1。
再看下面这张图判断y1和y2是否存在就看y1和y2通过Hash后找到的三个位置是否都是1。比如图中y1第二个位置是0说明y1不存在而y2的三个位置都是1说明y2存在。
Phantom正是通过把内存分成N个Table每一个Table内使用BloomFilter判断是否存在最终每天使用的内存只有120GB。而存在性判断的业务场景最高需要满足一周的需求所以最多使用的内存也就是840GB。
总结
今天我给你讲解了微博业务中使用范围最广的三个存储组件一个是MySQL主要用作持久化存储数据由于微博数据访问量大所以进行了数据库端口的拆分来降低单个数据库端口的请求压力并且进行了读写分离和异地灾备采用了Master-Slave-Backup的架构一个是Memcached主要用作数据库前的缓存减少对数据库访问的穿透并提高访问性能采用了L1-Master-Slave的架构一个是Redis基于微博自身业务需要我们对Redis进行了改造自研了CounterService和Phantom分别用于存储微博计数和存在性判断大大减少了对内存的使用节省了大量机器成本。
专栏更新到这里就要跟同学们说再见了,感谢你们在过去大半年时间里的陪伴,值此新春到来之际,老胡给您拜年啦,恭祝各位同学在新的一年里,工作顺顺利利,生活开开心心!

View File

@ -0,0 +1,21 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
结束语 微服务,从入门到精通
时间过得好快,终于到了该说再见的时候,仿佛下笔写下专栏开篇词就在昨日。回想当初,我与极客时间团队讨论专栏的主题,希望可以面向零基础用户,给一些中小团队的微服务架构落地提供参考和帮助。但是微服务确实有一定的技术门槛,对于团队也有一定的要求,“从入门到放弃”这句话用在微服务上也不是耸人听闻,因此我在构思开篇词时写下了“微服务,从放弃到入门”这个标题,希望通过专栏我们可以一起走入微服务的大门。现在专栏正文已经更新完毕,和我一起走到这里的同学,也请不要松气,从入门到精通还有很长一段路要走。
回想起一开始学习微服务时我对注册中心、RPC调用、熔断、限流等概念也是一知半解虽然也在实际项目中应用过但对它们背后的工作原理并不是很了解。我记得当时在微服务架构中为什么要使用注册中心注册中心是如何判断服务提供者节点存活的这个问题也一直困扰着我。后来随着服务化改造项目越来越多我在项目中遇到的问题也越来越多难度也越来越大。当时为了排查线上问题我只能逼自己深入进代码细节去理解注册中心背后的工作原理。从学习到实践再到学习的过程才让我真正对注册中心的原理和架构有了深刻地理解做到了不仅知其然也知其所以然。之前困扰我的问题以及线上的故障也都一一有了解决方案仿佛一切都是水到渠成。
所以在入门到精通这段路上,一定少不了实践的过程。只有在掌握基础知识以后,通过具体业务项目的实践,才能深刻体会到这些知识点的原理,真正理解专栏中讲述的那些架构取舍的根本原因所在。这也是我在过去一年的时间里,作为微博跨语言服务化改造的主导者之一,参与推进多个重要业务线微服务架构落地后所得出的体会。同时也希望通过专栏可以把整个实践过程做个总结,分享一下我的实践经验。
再回到专栏专栏内容的安排是由浅入深从基础知识讲起逐渐深入到业务实践中去。但是微服务发展至今涵盖的知识点越来越多所以我挑选了其中最为核心的部分给你详细讲解。更新完全部正文我们再回过头来看你在回顾这个专栏时可以把它分为两部分上半部分是微服务架构的基础知识包括基本原理和基础组件下半部分是微博在微服务架构方面的具体实践包括容器运维平台以及Service Mesh的具体实践。对于大部分微服务的初学者来说通过专栏上半部分的学习可以对微服务架构有全面的认识而对于有一定经验的微服务开发者来说专栏下半部分的具体实践能给你提供一些工作中可能会用到的方法论和实战指引。
做好一件事从来都不是容易的,就好像我写专栏的过程,需要花费大量的时间和精力一次次推翻自己的想法、突破认知的边界。就这样从酷暑写到寒冬,几乎每个工作日的夜晚和周末,都用在学习、写作、录音上。这个过程虽然很痛苦,但对我来说收获是巨大的。同样,学习微服务也是一个循序渐进的过程,就像打怪升级一样,刚开始的初级阶段好像比较容易,但越往后难度越大,尤其到了具体实践环节,对我们构建的知识体系有了一定的要求,不少同学走到这里可能就放弃了。但是相信我,在遇到难以理解的知识时,不要轻言放弃,通过反复阅读和理解,并结合具体实践去体会,你的收获会越来越大,对微服务的理解也会越来越深。
专栏虽然结束了,但我想你一定还有很多疑问,不用担心,我还会继续帮助你答疑。同时针对专栏前面没有来得及回复的留言,我也会专门挑选一些典型的问题深入解答。最后考虑到很多同学在留言中提到想了解一些微博的基础架构,我还会给你赠送特别福利,写几篇关于微博基础架构的文章,敬请期待!

View File

@ -0,0 +1,71 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
阿忠伯的特别放送 答疑解惑01
你好,我是胡忠想,我的专栏虽然已经结束了,但我还会一直在专栏里为同学们答疑解惑。所以,即使你现在刚加入到专栏学习中,也可以随时留下你的疑问。同学们问得比较多的问题我会记录下来专门写成一期答疑文章,希望和你一起详细讨论。
今天是答疑的第一期,我选取了前面几期比较有代表性的问题,其中很多也是我在实践过程中踩过的坑,针对这些问题我来分享一下我的体会。
这三个问题都是关于服务拆分的,下面我就从微服务拆分粒度、拆分方式以及微博微服务拆分实践三个方面来谈谈服务拆分那些事儿。
首先来看微服务拆分粒度。微服务拆分的粒度可以很粗也可以很细,具体要视团队的规模和业务复杂度而定。通常来讲,当团队的规模逐渐变大时,一个业务模块同时就会有多个开发人员修改,在需求研发期间,经常需要协调合并代码,沟通打包上线,研发成本越来越高,这时就需要对这个业务进行微服务拆分了,变成多个独立的微服务,分别进行代码开发、打包上线,实现研发的解耦。但是也要避免一个极端,就是把微服务拆分得太细,出现一个开发需要维护十几个以上服务的情况,这样的话团队维护的成本太高也不可取。
再来看下微服务拆分方式。在具体进行微服务拆分时,通常有两种方式,一种是按照服务的关联维度进行拆分,一种是按照数据库隔离维度进行拆分。按照服务关联维度拆分指的是两个服务是否具有紧密耦合的业务逻辑,如果能从业务逻辑上进行解耦的话,不同的业务逻辑就可以拆分为单独的微服务。按照数据库隔离维度拆分是指如果两个业务逻辑依赖的数据库不同,那么就可以把它们拆分为两个微服务,分别依赖各自的数据库。
实际在进行微服务拆分时,通常是两种方式相结合,先进行数据库隔离维度的拆分,再进行服务关联维度的拆分。以微博的业务为例,一开始是个大的单体应用,所有的业务逻辑代码都耦合部署在一起,一个业务需求往往涉及多个业务逻辑,代码提交和打包上线的过程需要多个开发人员都参与其中。为了减少代码耦合带来的研发效率低的问题,首先按照数据库隔离维度进行拆分,微博内容相关的业务依赖的是微博内容数据库、微博用户相关的业务依赖的是微博用户数据库,对应的就把微博内容相关的业务拆分为内容服务、把微博用户相关的业务拆分为用户服务,从而实现对内容服务和用户服务进行解耦,分别部署成单独的服务。然后再按照服务关联维度进行拆分,又把内容服务中的微博服务和互动服务进行了拆分,跟微博相关的业务逻辑拆分为微博服务,跟互动相关的业务逻辑拆分为互动服务。
除此之外在实际进行微服务拆分的时候还需要考虑其他因素比如微服务拆分后的性能是否可以接受。通常来讲从单体应用拆分为微服务服务调用由进程内调用变成不同服务器上进程之间的调用经过网络传输必定会带来一定的损耗所以在实际拆分时要看业务对这个开销的敏感程度。以微博的feed业务为例要展示用户关注的好友的timeline需要首先聚合用户关注的所有人再获取每个人的微博列表最后把所有人的微博列表按照时间进行排序。在单体应用时期feed接口的平均耗时在200ms左右而把调用用户服务获取关注人列表从进程内调用拆分为RPC调用后平均耗时从5ms左右增加到8ms左右这个损耗对于业务来说是完全可以接受的所以我们就把用户服务单独拆分为一个单独的微服务。另外我讲一个服务化拆分的反例在微博的直播card展示时需要聚合播放计数最开始采用的方案是进行服务化拆分把播放计数单独拆分为一个微服务这样的话直播card聚合播放计数时就通过RPC的方式来调用播放计数服务。但直播card本身业务耗时不到10ms而拆分为微服务后调用播放计数接口耗时从2ms增加到5ms左右这样相对来说开销就有点大了并且这个接口的调用量也很高所以后来我们又把直播card聚合播放计数从RPC调用改成了进程内的调用。所以说具体进行服务化拆分时一般都是先进行稍微大粒度的拆分业务验证稳定后再逐步进行细化的服务拆分。
微博的大多数微服务都是从以前的单体应用中拆分出来的所以为了保持业务上的延续性接口的定义依然保持跟原先保持一致只不过是单独抽取到一个公共的JAR包中这样的话服务的每个调用方都需要引入服务的接口定义所在的JAR包对于服务调用方来说跟原先的单体应用内的使用方式没有什么区别。你可以理解为是通过Java代码来定义接口规范不需要额外的接口文档、元数据之类的。
还有一种业务场景就是去年我们开始推进的跨语言服务化改造就是把原先的PHP业务方调用Java业务从HTTP调用改造成RPC调用这个时候就涉及到如何定义接口了。在采用HTTP调用的时期接口定义是通过wiki文档来维护的服务调用方通过查看接口的wiki定义就可以知道接口的参数、返回值以及错误代码等。迁移到RPC调用时为了做到平滑过渡服务调用方可以继续参照原先接口的wiki文档接口调用的参数仍然保持不变返回值也继续沿用原有的JSON格式只是调用的URL从原先的HTTP格式修改为扩展类型的URL。
目前比较规范的接口定义方式主要有两种一种是以Swagger为代表可以用来描述Restful API并且具备可视化的API编辑功能一种是以PB文件为代表的用来描述跨语言的接口定义你可以根据自己的实际需要来决定采用哪种方式。
一般来说服务化框架都会集成监控日志功能。以微博的服务化框架Motan为例会将每一次服务调用的数据进行收集然后以JSON格式的方式统一输出到服务部署路径下logs目录中的profile.log它的格式你可以参考下面的代码。
2018-11-18 16:58:32 {"type":"MOTAN","name":"brha_zdelay_cn.sina.api.data.service.SinaUserService.getBareSinaUsers(long[])","slowThreshold":"200","total_count":"266","slow_count":"0","avg_time":"10.00","interval1":"0","interval2":"266","interval3":"0","interval4":"0","interval5":"0","p75":"10.00","p95":"10.00","p98":"10.00","p99":"10.00","p999":"10.00","biz_excp":"0","other_excp":"0","avg_tps":"26","max_tps":"31","min_tps":"23"}
上面这段profile.log主要分为三个部分。
1.“type”字段标识这个监控项是什么类型比如Motan代表的是接口调用。
2.“name”字段标识这个监控项的名字是什么对应上面这段监控log名字就是
brha_zdelay_cn.sina.api.data.service.SinaUserService.getBareSinaUsers(long[])
3.自定义字段用来具体打印监控项的各种指标比如total_count字段代表是调用总量avg_time字段代表的是平均耗时p999字段代表的是99.9%的调用耗时在多少以下等。
数据处理的实时操作,在服务追踪中主要用于定位线上问题,主要有两个业务场景。
第一个场景是快速定位线上服务问题。以下图所示的微博业务为例一次用户feed请求需要经过链路上的多个环节如果用户的feed请求变慢就需要快速定位到底是链路上的哪个环节导致。此时就需要能够将服务追踪收集到的每个环节的调用数据进行实时聚合计算每个环节的平均耗时和接口成功率等然后输出到监控Dashboard上。这样的话通过一览监控Dashboard上各个环节的实时服务状况就能快速定位问题了。
第二个场景是确定某一次用户请求失败的原因。继续以图中所示的微博业务为例某一次用户请求失败了需要能定位具体在哪个环节导致调用失败这时候就需要根据用户请求的traceId和spanId把一次用户请求经过各个环节的服务调用串联起来并且找出调用的前后文这样的话就能从MAPI的调用开始接着查看Feed API的调用再继续查看Feed RPC的调用以此类推一层层往下查看最终就可以定位到底是在哪一层发生了错误。
数据处理的离线操作,一般应用在需要统计一段时间内某个服务的调用量、平均耗时、成功率等,这一段时间可能是一个小时,也有可能是一天、一周或者一个月等,所以需要根据一段时间内产生的追踪日志,进行离线计算才可以得出。
先来回答“僵尸节点”的问题。在微博的业务场景下服务进行缩容有两个步骤第一步是调用注册中心的反注册接口把节点从服务节点列表中删除第二步是回收服务器并把节点从服务池列表中删除。这里就存在一种可能是第一步操作失败第二步操作成功那么注册中心中就会残存一些已经不存在的节点这些节点也不会汇报心跳因此会被注册中心标记为“dead”状态也就是我们所说的“僵尸节点”因此需要每天定时清理注册中心中存在的“僵尸节点”。我是通过比对服务池的节点列表与注册中心的节点列表如果某个节点在注册中心中存在但在服务池的节点列表中找不到就认为是“僵尸节点”需要把它删除。
再来看批量反注册接口的问题。在微博的业务场景下存在一个节点同时部署了多个微服务的情况所以在进行节点缩容时需要多次调用注册中心的反注册接口以把同一个节点从多个服务的节点列表中删除。有一种简单的方案是在反注册的时候直接根据节点的IP进行反注册这样的话注册中心会查询这个节点注册的所有服务然后一次性把这个节点从所有服务的列表中删除。理论上批量删除只需要调用一次就可以把节点从注册中心中删除相比于之前多次调用成功率要高很多并且从我们实际使用效果上来看也不会再存在“僵尸节点”的情况了。
今天的答疑就到这里啦,欢迎你在留言区写下自己学习、实践微服务的心得和体会,与我和其他同学一起讨论。你也可以点击“请朋友读”,把今天的内容分享给好友,或许这篇文章可以帮到他。

View File

@ -0,0 +1,47 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
阿忠伯的特别放送 答疑解惑02
你好,我是胡忠想。今天我继续来给同学们做答疑,第二期答疑主要涉及微服务架构、注册中心和负载均衡算法,需要一定的基础,如果对这些内容不了解,可以先返回[专栏第14期]、[第17期]和[第18期]复习一下。
专栏里我主要讲的是基于RPC通信的微服务架构除此之外还有一种微服务架构是基于MQ消息队列通信的下面我就从这两种架构不同的适用场景来给你讲讲它们的区别。
基于RPC通信的微服务架构其特点是一个服务依赖于其他服务返回的结果只有依赖服务执行成功并返回后这个服务才算调用成功。这种架构适用于用户请求是读请求的情况就像下图所描述的那样比如微博用户的一次Feed API请求会调用Feed RPC获取关注人微博调用Card RPC获取微博中的视频、文章等多媒体卡片信息还会调用User RPC获取关注人的昵称和粉丝数等个人详细信息只有在这些信息都获取成功后这次用户的Feed API请求才算调用成功。
而基于MQ消息队列通信的架构其特点是服务之间的交互是通过消息发布与订阅的方式来完成的一个服务往MQ消息队列发布消息其他服务从MQ消息队列订阅消息并处理发布消息的服务并不等待订阅消息服务处理的结果而是直接返回调用成功。这种架构适用于用户请求是写请求的情况就像下图所描述的那样比如用户的写请求无论是发博、评论还是赞都会首先调用Feed API然后Feed API将用户的写请求消息发布到MQ中然后就返回给用户请求成功。如果是发博请求发博服务就会从MQ中订阅到这条消息然后更新用户发博列表的缓存和数据库如果是评论请求评论服务就会从MQ中订阅到这条消息然后更新用户发出评论的缓存和数据库以及评论对象收到评论的缓存和数据库如果是赞请求赞服务就会从MQ中订阅到这条消息然后更新用户发出赞的缓存和数据库以及赞对象收到的赞的缓存和数据库。这样设计的话就把写请求的返回与具体执行请求的服务进行解耦给用户的体验是写请求已经执行成功不需要等待具体业务逻辑执行完成。
总结一下就是基于RPC通信和基于MQ消息队列通信的方式都可以实现微服务的拆分两者的使用场景不同RPC主要用于用户读请求的情况MQ主要用于用户写请求的情况。对于大部分互联网业务来说读请求要远远大于写请求所以针对读请求的基于RPC通信的微服务架构的讨论也更多一些但并不代表基于MQ消息队列不能实现而是要区分开它们不同的应用场景。
要回答上面这三个问题,需要我来详细讲讲微博在使用注册中心时遇到的各种问题以及解决方案,主要包括三部分内容。
1. 心跳开关保护机制。在专栏第17期我讲过心跳开关保护机制是为了防止网络频繁抖动时引起服务提供者节点心跳上报失败从而导致注册中心中可用节点不断变化使得大量服务消费者同时去请求注册中心获取最新的服务提供者节点列表把注册中心的带宽占满。为了减缓注册中心带宽的占用一个解决方案是只给其中1/10的服务消费者返回最新的服务提供者节点列表信息这样注册中心带宽就能减少到原来的1/10。在具体实践时我们每次随机取10%,所以对于任意服务消费者来说,获取到最新服务提供者节点列表信息的时刻都是不固定的。在我的实践过程中,对于一个拥有上千个服务消费者的服务来说,某个服务消费者可能长达半小时后仍然没有获取到最新的服务提供者节点列表信息。所以说这种机制是有一定缺陷的,尤其是在服务正常情况下,心跳开关应该是关闭的,只有在网络频繁抖动时才打开。当网络频繁抖动时,注册中心的带宽就会暴涨,可以轻松把千兆网卡的前端机带宽打满,此时监控到带宽被打满时,就应该立即开启心跳开关保护机制。
2. 服务节点摘除保护机制。设计心跳开关保护机制的目的就是为了应对网络频繁抖动时引起的服务提供者节点心跳上报失败的情况。这个时候注册中心会大量摘除服务提供者节点从而引起服务提供者节点信息的变化。但其实大部分服务提供者节点本来是正常的注册中心大量摘除服务提供者节点的情况是不应该发生的所以可以设置一个服务节点摘除的保护机制比如设置一个上限20%正常情况下也不会有20%的服务节点被摘除,这样的话即使网络频繁抖动,也不会有大量节点信息变更,此时就不会出现大量服务消费者同时请求注册中心获取最新的服务提供者节点列表,进而把注册中心的带宽给占满。但这个机制也有一个缺陷,就是一些异常的节点即使心跳汇报异常应该被摘除,但也会因为摘除保护机制的原因没有从服务的可用节点列表中去掉,因此可能会影响线上服务。
3. 静态注册中心机制。心跳开关保护机制和服务节点摘除保护机制都是治标不治本的权宜之计,不能根本解决网络频繁抖动情况下,引起的注册中心可用服务节点列表不准确的问题。所以我们提出了静态注册中心的机制,也就是注册中心中保存的服务节点列表只作为服务消费者的参考依据,在每个服务消费者这一端都维护着各自的可用服务节点列表,是否把某个服务节点标记为不可用,完全取决于每个服务消费者自身调用某个服务节点是否正常。如果连续调用超过一定的次数都不正常,就可以把这个服务节点在内存中标记为不可用状态,从可用服务节点列表中剔除。同时每个服务消费者还都有一个异步线程,始终在探测不可用的服务节点列表中的节点是否恢复正常,如果恢复正常的话就可以把这个节点重新加入到可用服务节点列表中去。
当然服务消费者也不是完全不与注册中心打交道,在服务启动时,服务消费者还是需要去注册中心中拉取所订阅的服务提供者节点列表信息,并且服务消费者还有一个异步线程,每隔一段时间都会去请求注册中心以查询服务提供者节点列表信息是否有变更,如果有变更就会请求注册中心获取最新的服务提供者节点列表信息。所以在有节点需要上下线时,服务消费者仍然能够拿到最新的服务提供者节点列表信息,只不过这个节点上下线的操作,一般是由开发或者运维人员人工操作,而不是像动态注册中心那样,可以通过心跳机制自动操作。
关于最少活跃连接算法和自适应最优选择算法,它们的含义你可以返回[专栏第18期]回顾一下,本质上这两种算法都可以理解为局部最优解。
首先来看最少活跃连接算法,当客户端的请求发往某个服务端节点时,就给客户端同这个服务端节点的连接数加一;当某个服务端节点返回请求结果后,就给客户端同这个服务端节点的连接数减一,客户端会在本地内存中维护着同服务端每个节点的连接计数。从理论上讲,服务端节点性能越好,处理请求就快,同一时刻客户端同服务端节点之间保持的连接就越少,所以客户端每次请求选择服务端节点时,都会选择与客户端保持连接数最少的服务端节点,所以叫作“最少活跃连接算法”。但最少活跃连接算法会导致服务端节点的请求分布不均,我曾经在实践中见过一种极端情况,当服务端节点性能差异较大时,性能较好的节点的请求数量甚至达到了性能较差的节点请求数量的两倍。出现这种情况,一方面会导致某些服务端节点不能被充分利用,另一方面可能会导致请求量过高的服务端节点无法应对突发增长的流量而被压垮。
再来看下自适应最优选择算法一方面客户端发往服务端节点每一次调用的耗时都会被记录到本地内存中并且每隔一分钟计算客户端同服务端每个节点之间调用的平均响应时间并在下一次调用的时候选择平均响应时间最快的节点。显然这样是收益最大的尤其是服务跨多个数据中心部署的时候同一个数据中心内的调用性能往往要优于跨数据中心的调用另一方面客户端并不是每一次都选择平均响应时间最快的节点发起调用为了防止出现类似最少活跃连接算法中服务端节点请求量差异太大的情况发生把服务端节点按照平均响应时间进行排序找出最差的20%的节点并适当降低调用权重,从而达到有效减少长尾请求的目的。
欢迎你在留言区写下自己学习、实践微服务的心得和体会,与我和其他同学一起讨论。你也可以点击“请朋友读”,把今天的内容分享给好友,或许这篇文章可以帮到他。