技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 系统架构 --> 服务框架演变过程

服务框架演变过程

浏览:3669次  出处信息

    我们的服务框架已经持续做了三年了,在厂内广泛的使用,目前部署在服务框架上的服务为2k+,每天经过服务框架的服务执行次数为120亿+,摸高到150亿+,三年的发展并非一帆风顺,由于经验的原因,还是摔了不少跤的,在这篇blog中,来给大家分享下,希望能够给要做服务框架或服务化的同学带来一些帮助,少走一些弯路,不一定要一开始就做成完整的服务框架,但至少先做好铺垫,避免在广泛使用后再来挽救。

    我们的服务框架主要由四个部分的功能组成:

    1、标准的服务的交互方式

    2、高性能网络通信

    3、软件负载均衡

    4、服务治理

    这四个部分都经历了一些演变,才形成了最后的结构,尤其是服务治理这块,在各种SOA的文章中,都会说到服务治理这块,但基本都不会说服务治理到底要做什么,我们是在经历过很多后,终于让服务治理这块由一堆实际的功能组成,下面分别来看看以上这四块的演变过程。

    标准的服务的交互方式

    在服务框架的第一个版本中,最早我们希望对应用完全不侵入,做到应用不需要依赖服务框架的任何包,也不需要在应用里面定义服务,因此策略是在应用的外部写一个xml文件,用来描述对外提供的服务,或者要调用的服务,这个版本提供给应用方使用后,发现使用起来非常复杂,因为这个xml文件和应用是不在一起的,一方面不知道该放在代码仓库的什么地方;另一方面对于部署维护而言也是非常的麻烦。

    于是决定改进这块,厂内应用基本都是基于spring的,因此还是决定提供一个类来方便直接在spring bean的xml中发布服务和配置调用服务的代理bean,在这样的机制下发布服务和调用服务的配置大致就如下了:

<bean class="发布服务的类" or class="调用服务的proxy类">

    在改造成这种方式后,应用方使用起来就比较透明和方便了,并且也只是在运行时对服务框架会产生依赖,后来也就基本没变过了,兄弟厂有改造成这样的方式的,用起来更简单一点,不过需要加schema,所以我们这边还是没去这么做。

    在服务的定义上碰到的主要需求是最早我们只支持一种通信协议以及只支持接口级的超时配置,后续随着需求调整了一次服务的定义,增加了方法级超时的配置以及通信协议的配置。

    高性能网络通信

    在第一个版本中,我们选择了基于JBoss Remoting来实现,在最初一个访问量不大的应用中上线时表现挺正常的,可惜的是在后面一个访问量较大的应用上线后出现了问题,最后查证原因主要是出在了超时设置上,当时采用了默认的60秒超时,刚好当时提供服务的应用出现了处理慢的现象,导致前端的web应用被拖的支撑不住,而在当时的JBoss Remoting版本中,其实这个超时设置是有bug的,因此如果要修复就必须直接修改JBoss Remoting的代码。

    另外一个问题是JBoss Remoting的连接池方式,在通过硬件负载设备访问提供服务的集群的情况下,一旦重启,会很容易出现连接严重不均衡的现象。

    鉴于上面的现象,觉得还是自己掌握整个通信过程比较靠谱,于是选择了基于Mina来实现整个网络通信,自行实现异步转同步、超时、连接管理,连接的使用采用了每目标地址单个连接的方式,这样一方面是可以避免连接池造成的连接不均衡的问题,另一方面也避免调用端建立太多连接,导致服务提供者连接会不够用,伸缩性差的问题,另外,由于都是内网的请求,因此采用长连接方式。

    基本上一直以来都没对上面的网络通信机制做过调整,不过单连接在序列化/反序列化对象消耗时间较长时,会影响到其他的请求或响应,这个一方面是由于Mina的实现机制,另一方面是要结合应用场景来做适当的处理。

    在序列化上我们支持了默认的Java和Hessian两种,但杯具的是当时的Hessian版本较老,并且Hessian新版本与旧版本的兼容做的很差,导致后来我们一直很难升级Hessian的版本。

    在网络通信上,我们经历过的教训主要是最早的通信协议上没带版本号,导致升级时的兼容性比较难处理;还有就是在设计之初我们是不考虑用于跨语言场景的,但后面跨语言的场景出现了,于是就很被动了;还有一点是如果发送的对象过大或过多造成内存不够的现象,最早的时候没做限制。

    软件负载均衡

    在最初的几个版本中,我们都是通过硬件负载设备来访问服务提供者集群的,但有一次出现过硬件负载设备出故障的现象,导致应用出现故障,但又没办法修复,只能等待硬件负载设备问题的解决,在这种情况下,我们觉得随着服务越来越多,请求量越来越大,中间的硬件负载设备很容易成为最大的风险,于是决定自己做软件负载均衡。

    我们对于软件负载均衡最重要的一点要求是,当服务调用者调用服务时,是直接和服务提供者交互的,不需要通过任何中间的机器,当时考察了下现有的,觉得只能自己做了,于是我们就基于这个要求实现了自己的软件负载均衡,不过我们实现软件负载均衡的方法随着机器数越来越多,也碰到了不少的挑战,由于这个涉及实现机制,就不在这里细说了。

    最早在选址上我们仅支持随机选择,但由于集群中会出现机器配置相差比较远的现象,因此决定加入权重的支持,以便分配不同的流量。

    一个应用通常会对外提供多种不同功能的服务,这些服务对资源的消耗以及对整体系统的重要性是不一样的,而服务框架对于所有服务的执行都是放在同一线程池中的,因此会出现某些不重要又耗资源或执行慢的服务把线程池占满,导致重要的服务无法执行的现象,对于这种现象,我们提供了两种方案来进行解决:一是分线程池,二是按接口将集群再进行划分。

    分线程池的方法有些时候仍然是不够的,例如耗资源或执行慢的服务有可能同时把共享的资源消耗完了,那这个时候即使分线程池也仍然会导致重要的服务出问题的现象。

    按接口将集群再进行划分的好处就很明显了,在不改变代码结构的基础上,在软件负载均衡上实现按接口的路由即可,于是我们实现了这个机制。

    随着按接口路由实现后,又碰到了一个接口中不同的方法的重要性、执行速度以及消耗资源不同的问题,于是相继我们又支持了按方法以及按参数的路由,这个时候我们的不经过中间节点调用的软件负载均衡机制发挥出了巨大的优势,如果是硬件负载设备,一方面是没办法做到这样的7层路由,另一方面硬件负载设备一旦开启7层路由,性能下降会非常明显,而在我们的软件负载均衡实现机制下,则完全不会有这样的问题。

    服务治理

    最早的时候,我们压根就不知道服务治理到底该做些什么,基本都是随着使用者的增多才逐渐形成的。

    由于在调用服务时,不需要配置调用服务的目标地址,有些时候出错了,连调的是哪台服务器出现的错误都不知道,因此在初期的时候经常有使用者会来问,调的服务到底是哪台机器呢,于是一方面我们是在执行出错的日志里增加了调用的目标地址的说明,另一方面就是做了一个简单的系统,用于查询服务的调用者和提供者在什么机器上。

    当功能需要由调用服务来完成时,排错成为了大问题,在这个问题上我们现在还是支持的不够,现在的查错仍然是人肉较多,并且要判断问题出在哪里会比较复杂,尤其是出现A -> B服务 -> C服务这种时候。

    随着服务越来越多,当服务由于某些原因要废弃方法或做重大改造时,需要通知相关的服务提供者,以便做相应的调整和测试等,但最早服务框架在这方面没提供什么支持,因此服务的提供者只能通过邮件来进行调查,但这样非常容易出现遗漏等,出现过好几次问题,于是开始做服务的依赖关系分析。

    除了上面升级产生的问题外,还有一个问题是依赖的关系越来越复杂,导致系统的稳定性很差,因此需要管理起依赖,也就是最好服务的调用需要授权,这个也是服务框架初期的版本中没有考虑到的,于是只能进行改造来提供支持。

    使用服务框架的应用越来越多,开始出现了使用到的服务框架多个版本的现象,而最开始我们没有办法知道有哪些版本在使用,这种情况的杯具就是当我们发现某个版本出现致命bug时,根本不知道该通知谁升级,另外在解决问题的时候也非常麻烦。

    在软件负载均衡实现上,我们支持了按接口、方法和参数对集群进行划分,这个时候就造成了在加减机器的时候必须明确的知道是要操作哪部分的机器,这块如果支持不够就会造成机器的维护非常麻烦,这还是我们在继续努力的一块。

    在系统出现问题时,有些时候查找和修复需要花费较长的时间,因此如果能够通过路由的调整来隔离故障,那么对于系统的稳定性可以带来很大的帮助,于是我们实现了服务的降级,可以在运行时屏蔽某些调用者对于某些服务的某些方法的调用。

    在这样的情况下,形成了我们目前的服务治理:服务信息查询、依赖分析和管理、服务调用/执行报表、服务框架版本分布、服务路由调整以及服务降级,但即使是这样,也仍然是不够的,还需继续努力。

    从上面四个大方面的演变过程可以看到,大多时候是因为需求的推动发展,并且可以看到有些事情,由于一开始没做,导致了后面花了非常大的精力去挽救,因此如果能再来一次的话,我觉得可以在一开始就设计的更为周到,但不是说一开始要做的多完整,而是要埋好伏笔,避免后面不断的调整系统的设计,也希望上面这些演进过程的分享可以让你至少少走一次弯路,那我就觉得很开心了。

    bonus:

    除了上面围绕这四大块的演变外,在服务框架的整个发展过程中,我们还有一些其他的教训:

    1、服务的上线和下线

     其实很多情况下,我们是希望应用在更新前先下线服务,避免出现一些事务等问题,而在上线时最好是能做到逐步放入流量,这样可以让应用完成热身后再来接受大的请求量,避免出现应用还没进入状态时请求量大造成无法支撑的现象。

    2、外部定制化支持不够

     例如如果应用想介入调用过程或执行过程,做下特殊处理时,我们暂时是不支持的。

    3、日志打爆

     例如当应用出现问题时,我们没有对错误的日志做限制,导致有些时候会把最后一根救命稻草给砍断,后来我们才增加了日志输出时的流量控制。

建议继续学习:

  1. 解析nginx负载均衡    (阅读:14510)
  2. 使用HAProxy对MySQL进行负载均衡和状态监控    (阅读:5595)
  3. 如何寻找一个不会让你后悔的PHP开发框架    (阅读:5369)
  4. 自己写的一个轻量级javascript框架的设计模式    (阅读:4038)
  5. 对于PHP大型开发框架的看法    (阅读:4015)
  6. 异步编程与响应式框架    (阅读:3911)
  7. PHP API 框架开发的学习    (阅读:3827)
  8. 趣图三幅:负载均衡算法需要改进    (阅读:3885)
  9. 超级负载均衡    (阅读:3681)
  10. 也谈谈前端,架构,框架与库    (阅读:3588)
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1