17611538698
webmaster@21cto.com

架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)

资讯 0 16739 2017-04-27 12:01:18
[h1]一、下篇开头的废话[/h1]
  终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为【编辑推荐】的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证:

 


  今天是2014年的第一天,按中国辞旧迎新的传统,也作为我2014年第一篇博客,我想开篇前要总结下2013年的技术学习。今年我当爸爸了,当爸爸的人是没啥时间研究什么技术和写博客的,所以2013年上半年我的文章很少很少,直到老婆5月回家待产才有重新拿起书,提起笔写技术博客。今年老婆要回上海了,估计2014年的博客数量又会少点,但是不管什么情况,在技术上的不断探索的态度我永远都不会变,这是我生活的一部分,既是我的工作需要也是我的爱好所在,更是一种生存技能。

  2013年我的前端方面的文章比较少了,全年的文章主要是hadoop和系统架构,今年能抽出时间研究hadoop还得感谢公司给我的机会,虽然公司已经深切的感到hadoop所代表的大数据技术的重要性,但是我公司对hadoop的运用和领先的互联网公司差距还很大,这个情况也引发了我的思考,也许大家认为新技术的运用往往需要领导的魄力,但我觉得一线员工对新技术的深刻理解以及合理的运用才是这个技术能否被公司应用的好的关键所在,这个关键还在于我们这些员工的推动该技术在公司的普及,我希望自己能成为有这种能力和意识的技术员工,一个能主动推进和运用技术的员工,而不只是用技术完成交办开发任务的技术员工。

  2013年是我多年积累,厚积薄发的一年,我成功的在博客里分享了三种技术架构设计:一个前端开发框架,一个是分布式网站架构以及本文的主题远程调用服务的技术架构,两个都有成功的生产实际,一个是我自己的总结和思考,除了这三个,我还设计了一套session共享的技术架构,由于这个架构我向申请了发明专利,所以没有拿出来和大家分享。又得感慨下了,如果是几年前我很难相信自己可以做到这点,我坚信这是我多年坚持做好一件事情的成果,我现在也充分感受到这种坚持的成就感,这种爽歪歪的感受一定能坚定我在这条路上一直走下去,最终能成为一名在国内一流的程序员,这就是我职业的终极目标。

  2014年开始了,新的一年我要有新的进步,能给大家分享更多原创的博文,为更多新人答疑解惑,也希望自己能带动更多人能投身与互联网行业,在中国互联网行业是非常好的行业,因为他是中国少有完全竞争的行业,中国实力可以比肩美国的顶级公司,更重要的是互联网相对别的很多行业可以不拼爹不当二奶也能小有成就,实现自我价值。

  废话结束,祝大家新的一年过的开心,充实,烦心的事情少之又少,开心的事情多之又多。

  远程调用服务架构设计及zookeeper技术详解--下篇现在开始。

 

[h1]二、远程调用服务技术详解---后续[/h1]
 

  上篇结尾我说道希望本文是一篇接地气的文章,既然要接地气,那么最好有人看了本文后能自己实现出这套框架,如果要做到这点,我就得把架构设计的一些关键点和难点问题要点出来,告诉大家这些地方到底该如何实现,开发时候要注意哪些细节。本小节就是补充这些问题的。

 

  远程调用管理组件和服务提供者之间有心跳机制检测,它的作用是检测服务提供者是否能正常对外提供服务,我在上篇提出的检测方式是使用ping的方式检测ip和端口号,其实具体实现中还可以有另外的方式,因为服务提供者都会引入远程调用服务提供的jar包,在这个jar包里其实包含一个心跳测试接口,远程调用管理组件采用服务提供者和服务调用者同样的通讯方式去调用这个心跳测试接口,如果该接口能调通,那么心跳测试成功;如果发现一次调不通那么再反复测试几次,要是还是不通,再标记该服务提供者的这台服务器不可用,这种心跳检测优于使用ping的方式,因为心跳检测是完全模拟服务提供者和调用者的通讯场景,检测的结果更加符合真实的情况。当心跳机制检测出了服务提供者有些服务器出现了故障,我们应该还需要有一套机制及时通知运维人员或者相应的开发人员,做到故障的及时处理,所以远程调用框架里应该还要包含一个监控系统健康性的监控模块,如果你的公司有专门的监控系统那就更好了。

 

  远程调用管理组件和服务调用者还有一个推送信息的关系,这里我再描述下这个推送的关系,当服务提供者启动的时候会发送自己的ip地址和端口号,远程调用管理组件接收到这些信息然后把这个ip和端口号推送到服务调用者,其实这个描述缺乏细节,经不起推敲,做个Web开发的童鞋都知道想访问一个应用除了ip地址和端口号,还需要应用的名称,我的配置文件里只推送ip和端口号,没有应用标记确定是哪个应用,那么调用者如何确定自己到底是调用了那个应用服务了?对于服务提供者真的不需要应用名称,只需要提供ip地址和端口号就行了,因为不管你是那种服务,对于调用者而言看到的都是远程调用的服务名称,所以远程调用管理组件只需要向服务调用者推送ip地址和端口号,而服务名称就是远程调用服务的名称,服务调用者到底调用哪个服务,这个就是上篇里看到的spring配置里的接口,这个接口就是具体服务的身份证了。

 

  推送里还有个细节我没有讲清楚,远程调用管理组件怎么知道服务调用者需要调用那些服务呢?这是我上篇设计方案里的漏洞,因此这里我要将这个设计补充进去,服务调用者到底调用那些服务提供者,这个配置可以做到服务调用者的配置文件里,但是这么做就会让服务调用者和服务提供者进行了一定程度的耦合,同时也架空了远程调用管理组件的作用,所以把它做到服务调用者这边不是太明智的,做到远程调用管理组件里才是最佳最可行的方案。这里我要为远程调用管理组件开发一套管理它的Web应用,并且在上篇的配置文件里加上一个标记服务类型的服务标记,该标记只适合于服务提供者,在服务提供者启动的时候一起推送到远程调用管理组件里,而Web应用一个重要的功能,就是根据服务调用者的ip地址和这些标记建立对照关系,那么远程调用管理组件就可以知道服务调用者需要调用那些服务了。好了,服务调用者和服务提供者的关系建立好了,那远程调用管理组件是如何推送服务信息到服务调用者的呢?我们发现远程调用管理组件和服务调用者之间关系是一个典型的发布-订阅的关系,发布-订阅的模式在设计模式里有一套解决方案:观察者模式,这里我先简单介绍下观察者模式:

 

  观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化的时候会通知所有观察者对象,使得它们能够自动更新自己。

 

  关于观察者的详细信息,大家可以查看下面的文章:

 

  http://www.blogjava.net/supercrsky/articles/202544.html

 

  通过观察者模式的定义,我们发现远程调用管理组件即是主题对象,服务调用者则是观察者,其实程序里信息推送的模式都是使用观察者模式来实现的。

 

  在上篇里我给大伙展示服务提供者和服务调用者的spring配置文件,接着告诉大家提供者和调用者就是使用RmifSpringProviderBean和RmifSpringConsumerBean这两个bean就能完成相互的通讯,我开始以为自己已经讲的很清楚了,有代码有真相,但是结果是马上有童鞋跟我私聊,问道这到底是怎么做到的呢?,事后我再想想,如果这个问题不解释细点还真是有很多人不明白。这里我使用的是代理模式,下面是对代理模式的简单介绍:

 

  代理模式及Proxy,是为其他对象提供一种代理控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介作用。

 

  远程调用服务的一个核心设计就是要保证服务提供者和服务调用者之间的调用规则要一致,但是具体到业务的处理又能多种多样,这个场景就是一个典型的代理模式。至于java里代理模式的详细情况,可以参看下面的这篇文章。

 

      http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html

 

  在远程调用服务里的代理模式不是一个普通的代理模式,因为这种代理关系的建立都是在程序运行中进行的,所以我们必须选择使用反射机制实现的动态代理模式,读完上面我引用的文章,大家发现了吧,我在spring里使用的是interface方式,这说明我的方案是使用jdk自带的代理模式,不过使用cglib的动态代理,用实现类来实现代理我感觉可能程序设计会更加灵活些。

 

  远程调用服务的框架开发里,还有两个重要的技术:线程和通讯,这两个技术是远程调用框架的核心技术也是难点技术,因为线程和通讯都比较复杂,所以本文不展开它们具体的实现方式,而是谈谈它们在远程调用框架里的作用。

 

  在谈作用之前我要插入一点内容,上篇里我讲到远程调用服务框架就是几年前很火的SOA里的服务总线,如果有童鞋接触过这种骨灰级的技术,一定知道当时的服务总线有个简称叫ESB总线,而ESB总线的通讯介质几乎都是WebService,当年的SOA是源自企业级的解决方案,而我现在设计的远程调用服务是针对于互联网的应用,互联网的远程调用框架没有采用WebService而是使用新的通讯介质例如netty,或者是apache的顶级项目mina,其实当下webservice也是一种被淘汰的技术,淘汰的原因是它的效率低下,不管是传输的数据大小,还是请求响应的效率都很烂,互联网的远程调用服务是一个高性能的框架,它的性能更好,而且它包含了原来ESB总线的所有特点,由此可见互联网的技术远远领先企业应用,企业级的应用企业相对于互联网企业就是传统手工业,而当下互联网的技术已经开始引领企业的应用,这就是落后就要挨打的道理,没有创新的意识就会被别人革命。

 

  既然我们要设计的是高性能的远程调用服务框架,那么高性能的一个指标就是高并发,而高并发就是指你的服务到底能开启多少个线程,所以线程写的好坏直接关系到远程调用服务可用性的好与坏,使用线程的时候要注意以下几个问题:第一,服务器的内核一般是多核的,所以编写线程的时候要把这些内核都利用上去,大量的线程要使用那么一定要使用上池技术,这里我推荐的是jdk自带的Executor框架,Executor框架设计的非常好,同时和netty的兼容性也非常不错,最后就是线程安全的问题,这里最难的就是线程安全,如果让我自己实现线程,我对写线程安全的信心是最不足的。

 

  高性能的另一个指标就是网络通讯了,这里我采用的是netty框架,Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。Netty有很多优点,但是它最大的优点是netty的长连接和事件驱动,netty能帮我们屏蔽很多socket难以控制的细节,它的长连接设计的非常不错,减少了tcp开启和关闭的开销,而事件驱动模式又大大降低了开发的难度,但是netty本身有个缺陷,这个缺陷就是netty是一个异步的通讯框架,但是大家看到了我们调用的场景都是同步,因此我们必须把异步请求转为同步请求,在前面的分布式网站架构里,我使用到了这种技术,哎,可惜当时使用时候是拿来主义,异步转同步的详情我现在还不是很明了,这个也是我以后需要研究研究的,这里先预留下来,以后明白了再写博客告诉大家。

 

  好了,远程调用服务的架构设计详情到此结束了,下面是本文的另一个主题zookeeper了。

 

[h1]三、Zookeeper技术详解[/h1]
 

  在远程调用服务里zookeeper使用在远程调用管理组件里,而服务调用者则是zookeeper的客户端,远程调用管理组件也是远程调用服务的核心所在,如果远程调用管理组件在运行时挂掉了,那么整个应用也将不可用,因此远程调用管理组件必须是否可靠,这种可靠性的要求甚至要高于服务提供者和服务调用者的可靠性的级别,因此远程调用管理组件的设计必须是分布式的,而且一定要是可靠的分布式。

 

  远程调用管理组件是一个完全符合zookeeper场景的应用或者说是一个标准的zookeeper应用,为了便于后面的论述,这里我的更进一步的讲解下远程调用管理组件的功能,从我前面的叙述,我们知道了远程调用管理组件核心作用是存储服务提供者和服务调用者通讯的配置信息,例如存储服务提供者的ip地址和端口,记录服务提供者的服务类别,它还记录服务调用者的ip地址和端口号(这个信息是在远程调用管理组件的Web管理系统里录入的)以及服务调用者和服务提供者的对照关系。对于服务提供者,远程调用管理组件还提供心跳机制检测服务提供者的健康状态,如果远程调用管理组件发现服务提供者一些服务器出问题了,它会及时更新服务提供者的配置信息,并将这些变化及时推送给服务调用者。由上所述,从配置信息存储的角度,远程调用管理组件其实就是一个远程存储配置信息的系统,而心跳机制和推送机制这是一种观察者模式,而上面这些功能都是在分布式环境下功能,需要很高的可靠性。本文上篇的开头,我曾提到在hadoop技术生态圈里,zookeeper曾是最让我困惑的技术,这种解困的好奇心是促使我一直很关注zookeeper技术的一个重要诱因,现在我有点理解zookeeper为何物了(不管说深入理解,因为每个技术都是博大精深,永无止尽的,特别是很优秀的技术)。Zookeeper最典型的一个应用就是可以做分布式应用的配置服务,具体点就是像我们平时写的配置文件,到了分布式系统里也是需要一个独立的系统来完成,并且是动态的配置服务。

 

  既然zookeeper可以做分布式的配置服务,那么我们可以通过分布式配置服务的特点反向的理解zookeeper的作用。我是一名java工程师,在做java的web开发的时候,会使用大量的配置文件,一般这些文件是用properties属性文件完成的,服务启动时候,属性文件里的信息会读到内存中,web系统从内存中读取这些配置信息,这个配置信息有几个特点:属性文件一般不太大(这里指的是系统运行相关的配置文件,大伙不要把国际化的也拿过来理解啊),配置信息是持久化,使用时候是先加载在内存中的,从内存读取,zookeeper也可以完成这样的事情,而且其特点和传统的配置文件使用几乎一样,zookeeper有一个文件系统,这个文件系统是用来存储小文件的,我们读取配置信息时候是在内存里读的,效率很高,写信息的时候zookeeper会将配置信息持久化。这就是为什么有的书里介绍zookeeper的性能:

 

  Zookeeper的基准吞吐量可以超过10000个操作,而对于读操作为主的工作负载,吞吐量更是高出好几倍。

 

  这句话很有道理,小文件写速度很快,10000个操作木有啥问题,读是通过内存,高好几倍是理所当然的。

 

  系统运行的配置信息可靠性要求是很高的,既然我们现在使用分布式系统完成配置信息的读写操作,那么确保信息读写的准确性是非常重要,特别是写,要求绝对是要么成功要么失败,这个场景估计很多人一看就认为这不就是线程安全吗?没错,是线程安全(能想到线程安全还是很牛的哦),但是大伙要看清楚啊,我们现在是分布式系统,不同服务器之间的操作不是线程的范畴而是进程的范畴,因此这里就需要新的保证操作安全的技术,换句话说就是进程安全的机制。除此之外,分布式的配置服务之所以使用分布式就是为了保证配置服务系统的稳定性和安全性,这样才能持续为用户提供高质量的服务,这两个难题看似毫不相关,但是却有一种方案同时解决这个问题,这个方案就是zookeeper的Zab协议,为了说清楚Zab协议,我们举个例子,例如我们使用5台服务器作为zookeeper服务器,我们向zookeeper集群发送指令,这个指令就是读操作或者写操作,zookeeper集群会完成下面两个操作:

 

  操作一:领导者选举,当zookeeper启动的时候,这5台服务器会选举出一个领导者机器,其他的机器则是追随者,如果有半数以上的追随者和这个领导者通讯完毕确认了状态,那么这个阶段也就完成了。如果领导者一直都很健康,那么领导选举的操作就不会再促发,如果领导者出问题了,那么zookeeper就会再一次促发领导者选举的操作。(这里有个问题我不太确定,就是zookeeper的领导者检测追随者健康性,应该也会使用心跳机制吧?如果有哪位大侠知道,可以给我评论下哦)

 

  操作二:如果发出的指令是写请求,那么所有的写请求都会被转发到领导者,再由领导者将更新的广播发送给追随者,当半数以上的追随者将修改持久化后,领导者才会提交这个更新,然后客户端才能收到一个更新成功的响应。这个用来达成共识的方式被设计成原子性的,这个操作要不成功要不就失败。

 

  由以上操作就保证了读写的原子性,不会发生脏数据,反复选举领导者也保证了服务的可靠性。当然这里还有个问题,要是领导者出现故障了?这时候zookeeper集群又会重复上面的领导者选举操作。这也说明为什么zookeeper集群要求是奇数台的服务器,5台服务器2台挂了,服务任然可以正常运行,如果是6太服务器,还是只能允许2台服务器出故障,因为如果3台挂了,剩下的服务器没有过半数,那么zookeeper自己都挂了,所以奇数服务器不会造成服务器资源浪费。

 

  对于读操作,zookeeper任意一台服务器都可以直接给服务,附带其他操作很少,所以高效;而写操作,只有当所有服务器都持久化了数据后,zookeeper才会更新内存中对应的数据,所以会比读操作慢多了。

 

  Zookeeper存储数据操作方式和Unix文件系统的路径操作一致,而内存数据存储的模型就是一个树状结构了,树状结构的节点叫做znode,znode就是用来存储和读取数据的地方,这个树的操作如下列表:




 

  我们存储的配置信息就是使用这些操作完成的,例如:当服务提供者启动时候将自己的配置信息推送到远程调用管理组件,组件就会做创建节点或者设置znode所保存数据的操作,当数据保存成功后,zookeeper就会马上将信息推送到服务调用者,这个推送工作zookeeper也可以完成,zookeeper里的znode以某种形式发生了变化,每个znode上面都附带一个观察机制,也就是我们前面说的观察者模式的应用,这个观察者机制会给客户端通知,而这个客户端就是服务调用者。如果心跳机制检测到服务提供者某个服务器出故障了,zookeeper也会修改相应的znode的信息,这时候也会促发观察机制,通知服务调用者发生了变化。

  好了,本文主题就写完了。

  Zookeeper的功能很强大,不仅仅包含上面我谈到的运用,本文是我深入学习zookeeper的开始,有了现在的基础,再深入学习zookeeper将会容易很多。

  2014年第一篇博文书写完毕,祝大家元旦快乐!

 

 

 

 

评论