远程调用是系统或进程间的通信机制,是分布式系统开发的核心技术。远程调用技术可以将一组计算机系统形成一个网络系统,对外提供整体服务,那么这一群的计算机系统就构成了一个更大型、性能更高的计算机系统。
远程调用服务的架构设计总述
首先我们要深入理解以下几个问题:为什么应用软件服务里需要一个远程调用服务?远程调用服务解决了软件设计中的什么问题?
笔者曾写过一篇关于分布式网站架构设计的文章, 在文章开头就把这个新的网站架构方案和传统的企业软件的B/S架构作了对比,笔者将一个网站里提供业务服务的组件抽象为独立的服务系统,接收用户信息的逻辑部分抽象为前端系统,服务系统和前端系统使用netty这样的通讯组件进行通讯。而到了讲解远程调用服务的框架设计时候,将netty通讯组件进一步抽象为一个通讯独立系统及远程调用服务,这就是为什么要设计远程调用服务的缘起了,远程调用服务会带来了网站架构的升级,如果传统的企业B/S架构为1.0版,前端和业务服务端分离为独立系统则是2.0版,那么引入了远程调用服务网站就是3.0版了,3.0版的架构带来的好处就是可以将N多的前端系统和N多的业务服务端系统融为一个整体,网站的规模会越来越大,提供的服务也会越来越多,这既避免重复造轮子的问题还使得网站规模越来越大。
3.0版网站架构总图
有了远程调用服务,我们可以做到业务级别的集群,例如:一个制造企业,一般都会有采购业务,生产业务、销售业务以及财务业务,按照传统的思路我们都会给每个业务独立开发一个系统,如果引用了远程调用服务,我们可以将这些业务都做成独立的服务,这些服务组成业务集群,而这些服务都是用统一的远程调用服务作为操作的入口,这样前端的调用者可以做到应用的统一。所谓的应用的统一淘宝网站是最典型的代表,我们在一个同一的网站里可以操作各种不同的应用,而不会发生因为应用的不同我们就得重新访问新的地址或者重新登录到另外一个系统里做其他业务的操作。而服务端这边,完全可以摆脱传统的客户端和服务端耦合的开发,增强了整个服务端的专业性和稳定性,这样更易于服务端的扩展性和可维护性。如果服务端之间也需要相互调用也可以通过远程调用服务实现,由于远程调用服务的统一性,这样就避免了服务调用之间报文和调用方式的不统一,规范了整个开发的流程。如果远程调用服务还有负载均衡功能,整个服务集群就变成了一个私有的云,所以说远程调用服务是云计算的重要组成部分,这个说法一点都不为过。
远程调用服务技术原理
远程调用框架
传统的服务调用都是服务提供者和服务调用者的直接调用,从架构图里我们看到这里多了一个远程调用管理组件,远程调用管理组件是一个独立的服务系统,为了保证该系统的稳定性,它也一定是一个分布式的系统,但是这个分布式系统和Web的分布式系统是完全不同的分布式系统。传统Web应用集群是基于HTTP协议的无状态的特点设计的,因为每个HTTP请求都是一个独立的事务,不同请求之间是没有任何关系的,所以我们可以将Web应用部署到不同服务器上,请求不管到了那台服务器,都能正常的给用户提供相应的服务,但是Web应用的session机制是有状态的,所以传统Web集群都是要有session同步的操作,大型网站往往会把session功能抽象为独立的缓存系统,但是这里的远程调用管理组件的集群原理或者说分布式原理是有别于Web应用集群分布式原理的。远程调用管理组件可以当做一个注册中心,它会记录下服务提供者和服务调用者的相关信息,并将这些信息推送给服务提供者或者服务调用者,为了保证系统的执行效率,这些注册信息都是记录在内存里。我们试想下,如果这些注册信息丢失,整个系统将会不可用,因此远程调用管理组件的集群是一种保证数据可靠性和服务提供健壮性的集群,而不是建立在HTTP无状态特性基础上的集群。
我们这里假想下远程调用服务的集群运行场景,我们假如有5台服务器作为远程调用服务运行的服务器,那么每台服务器都必须有注册信息的冗余备份,当服务运行时候其中一台服务器发生了故障,这台故障的服务器上的数据不会丢失,此外集群应该还要有一个检查故障的机制,当发现有台服务器不可用的时候,能及时剔除该服务器,而Zookeeper就是解决这种问题的技术框架。此外除了保证系统的稳定性和可用性外,集群的数据存储方式也是很重要的,前面我讲到集群的数据存储要有一个冗余机制,除了冗余机制还要有一个很适合快速访问和读写的数据模型,而Zookeeper正好包含这种数据模型,所以我设计的远程调用服务是一个很适合Zookeeper应用的场景。
远程调用管理组件还有一个心跳机制,心跳机制的作用是检测服务提供者的健康性及服务提供者是否可用,服务提供者启动时候会将自己的注册信息发送给远程调用管理组件,这个注册信息里包含服务端的IP地址和端口号,远程调用管理组件会启动一个线程,根据定时对这个IP地址和端口号去ping这个IP和端口号对应的应用是否可用,如果不可用远程调用管理组件会反复尝试几次,这个次数和多久检测心跳都是可以配置的,如果反复几次还是不通,那么就认定该服务不可用了。有网友在QQ上问我,为什么不检测服务调用者的心跳,这个完全没必要,调用者是主动方,提供者是被动方,这就好比你访问网站,如果你生病了不去访问了,系统没有必要检查你是否已经生病了。
Zookeeper技术详解
在远程调用服务里Zookeeper使用在远程调用管理组件里,而服务调用者则是Zookeeper的客户端,远程调用管理组件也是远程调用服务的核心所在,如果远程调用管理组件在运行时挂掉了,那么整个应用也将不可用,因此远程调用管理组件必须是否可靠,这种可靠性的要求甚至要高于服务提供者和服务调用者的可靠性的级别,因此远程调用管理组件的设计必须是分布式的,而且一定要是可靠的分布式。
远程调用管理组件是一个完全符合Zookeeper场景的应用或者说是一个标准的Zookeeper应用,为了便于后面的论述,这里我的更进一步的讲解下远程调用管理组件的功能。从前面的叙述,我们知道了远程调用管理组件核心作用是存储服务提供者和服务调用者通讯的配置信息,例如存储服务提供者的IP地址和端口,记录服务提供者的服务类别,它还记录服务调用者的IP地址和端口号(这个信息是在远程调用管理组件的Web管理系统里录入的)以及服务调用者和服务提供者的对照关系。对于服务提供者,远程调用管理组件还提供心跳机制检测服务提供者的健康状态,如果远程调用管理组件发现服务提供者一些服务器出问题了,它会及时更新服务提供者的配置信息,并将这些变化及时推送给服务调用者。由上所述,从配置信息存储的角度,远程调用管理组件其实就是一个远程存储配置信息的系统,而心跳机制和推送机制这是一种观察者模式,而上面这些功能都是在分布式环境下功能,需要很高的可靠性。Zookeeper最典型的一个应用就是可以做分布式应用的配置服务,具体点就是像我们平时写的配置文件,到了分布式系统里也是需要一个独立的系统来完成,并且是动态的配置服务。
既然Zookeeper可以做分布式的配置服务,那么我们可以通过分布式配置服务的特点反向的理解Zookeeper的作用。笔者在做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就是用来存储和读取数据的地方,这个树的操作如下列表:
操作 | 描述 |
create | 创建一个znode,必须要有父节点 |
delete | 删除一个znode,不能有任何子节点 |
exists | znode是否存在,并查询它的元数据 |
getACL,setACL | 获取/设置一个znode的ACL |
getChildren | 获取子节点的列表 |
getData,setData | 获取/设置一个znode所保存的数据 |
sync | 将客户端的znode视图与Zookeeper同步 |
我们存储的配置信息就是使用这些操作完成的,例如:当服务提供者启动时候将自己的配置信息推送到远程调用管理组件,组件就会做创建节点或者设置znode所保存数据的操作,当数据保存成功后,Zookeeper就会马上将信息推送到服务调用者,这个推送工作Zookeeper也可以完成,Zookeeper里的znode以某种形式发生了变化,每个znode上面都附带一个观察机制,这个观察者机制会给客户端通知,而这个客户端就是服务调用者。如果心跳机制检测到服务提供者某个服务器出故障了,Zookeeper也会修改相应的znode的信息,这时候也会促发观察机制,通知服务调用者发生了变化。
转自:http://www.cnblogs.com/sharpxiajun/p/3496639.html