跳到主要内容

20、Tomcat 内核详解 - 高可用的集群实现

对于Web容器来说,在请求是无状态的情况下,如果实现做集群功能其实是非常简单的,只需要把机器连接到具备一定的分发策略的分发器上即可实现集群功能,同时也要保证这个分发器必须具备故障转移的能力。后面需要多少机器的集群直接添加即可,不但达到了负载均衡的效果,而且达到了高可用的效果;

实际情况中,很多请求都是有状态的,简单的讲,就是请求与会话有关。

Tomcat中使用了自己的Tribes组件实现集群之间的通信,集群通信都是重复性的工作并且独立性比较强,因此这块逻辑被Tomcat单独抽离了出来。对于Tomcat,它需要一个更高层次的抽象,而不是直接使用Tribes组件。所以这里就有了Cluster组件,Cluster组件将集群相关的所有组件都封装起来,以实现集群的功能。

1.从单机到集群的会话管理

1.1单机模式

http://blog.csdn.net/wangyangzhizhou/article/details/49402775

单机时代对会话的管理主要有两种方式——非持久化方式和持久化方式。非持久化方式指会话直接由tomcat管理并保存在机器内存上,它是最简单的方式,如下图,所有的会话集合都保存在内存上,客户端访问时根据自己的会话id直接在服务器内存中寻找,查找简单且速度快,但同时也存在两个缺点:一是容量比较小,当数据量大时容易导致内存不足;一是机器意外停止会导致会话数据丢失缺点。

 

为了解决上面非持久化方式存在的缺陷,我们需要引入持久化机制,即持久化方式。可以将会话数据以文件形式持久化到硬盘中,也可以通过数据库持久化会话数据。首先看硬盘持久化,如下图,会话数据会以文件形式保存在硬盘中,由于硬盘比存储空间比内存大且机器意外关机都不会使数据丢失,所以硬盘存储解决了上面两个缺点,但是硬盘读取的速度比较慢,可能会影响整体的响应时间,硬盘持久化方式在实际中基本不会使用。

 

Tomcat提供的另外一种默认的持久化方式就是将会话数据持久化到数据库上,所有会话数据交由数据库存储,tomcat通过jdbc数据库驱动并使用连接池技术去数据库指定表读取会话信息,此种方式解决了非持久化方式的所有缺点同时也对以文件方式存储方式的IO进行了优化,用数据库存储会话其实是一种集中管理模式,现在实际中更多是使用一个分布式缓存替代数据库,例如memcached、redis集群等,因为缓存的查询读取速度快,且集群解决了高可用的问题,但Tomcat官方版本是不提供会话保存到memcached或redis的支持,如要使用可自己编写一个会话管理器及一个阀门valve,或使用第三方jar包。需要说明的是集中管理模式不管是tomcat单机还是集群模式都可以使用。

 

1.2集群模式

为什么要使用集群?主要有两方面原因:一是对于一些核心系统要求长期不能中断服务,为了提供高可用性我们需要由多台机器组成的集群;另外一方面,随着访问量越来越大且业务逻辑越来越复杂,单台机器的处理能力已经不足以处理如此多且复杂的逻辑,于是需要增加若干台机器使整个服务处理能力得到提升。

如果说一个web应用不涉及会话的话,那么做集群是相当简单的,因为节点都是无状态的,集群内各个节点无需互相通信,只需要将各个请求均匀分配到集群节点即可。但基本所有web应用都会使用会话机制,所以做web应用集群时整个难点在于会话数据的同步,当然你可以通过一些策略规避复杂的额数据同步操作,例如前面说到的把会话信息保存在分布式缓存或数据库中统一集中管理,如下图,每个tomcat实例只需去写入或读取数据库即可,避免了tomcat集群之间的通信。但这种方式也有不足,要额外引入数据库或缓存服务,同时也要保证它们的高可用性,增加了机器和维护成本。

 

【使用缓存或者数据库统一保存会话信息】

鉴于以上存在的不足,提供另一种解决思路就是tomcat集群节点自身完成各自的数据同步,不管访问到哪个节点都能找到对应的会话,如下图,客户端第一次访问生成会话,tomcat自身会将会话信息同步到其他节点上,而且是每次请求完成都会同步此次请求过程中对session的所有操作,这样一来下一次请求到集群中任意节点都能找到响应的会话信息,且能保证信息的及时性。细看很容易发现集群的节点之间的会话是两两互相复制的,一旦集群节点数量及访问量大起来,将导致大量的会话信息需要互相复制同步,很容易导致网络阻塞,而且这些同步操作很可能会成为整体性能的瓶颈,根据经验,此种方案在实际生产上推荐的集群节点个数为3-6个,无法组建更大的集群,而且冗余了大量的数据,利用率不高。

 

【全节点复制集群】

全节点复制的网络流量随节点数量增加呈平方趋势增长,也正是因为这个因素导致无法构建较大规模的集群,为了使集群节点能更加大,首要解决的就是数据复制时流量增长的问题,下面将介绍另外一种会话管理方式,每个会话只会有一个备份,它使会话备份的网络流量随节点数量的增加呈线性趋势增长,大大减少了网络流量和逻辑操作,可构建较大的集群。

下面看看这种方式具体的工作机制,集群一般是通过负载均衡对外提供整体服务,所有节点被隐藏在后端组成一个整体。**前面各种模式的实现都无需负载均衡协助,但现在讨论的集群方式则需要负载均衡器的协助。**所以图中都把负载均衡省略了。最常见的负载方式是前面用apache拖所有节点,它支持将类似“326257DA6DB76F8D2E38F2C4540D1DEA.tomcat1”的会话id进行分解,定位到tomcat集群中以tomcat1命名的节点上(这种方式称为Session Stick,由apache jk模块实现)。每个会话存在一个原件和一个备份,且备份与原件不会保存在同一个节点上,如下图,例如当客户端发起请求后通过负载均衡被分发到tomcat1实例节点上,生成一个包含.tomcat1后缀的会话标识,并且tomcat1节点根据一定策略选出此次会话对象备份的节点,然后将包含了{会话id,备份ip}的信息发送给tomcat2、tomcat3、tomcat4,如图中虚线所示,这样每个节点都有一个会话id、备份ip列表,即每个节点都有每个会话的备份ip地址。

完成上面一步后就是将会话内容备份到备份节点上,假如tomcat1的s1、s2两个会话的备份地址为tomcat2,则把会话对象备份到tomcat2中,类似的有tomcat2把s3会话备份到tomcat4,tomcat4把s4、s5两个对话备份到tomcat3,这样集群中所有的会话都已经有了一份备份。当tomcat1一直不出故障,由于Session Stick技术客户端将一直访问到tomcat1节点上,保证一直能获取到会话。而当tomcat1出故障了,这时tomcat也提供了一个failover机制,apache感知到后端集群tomcat1节点被移除了,这时它会把请求随机分配到其他任意节点上,接下去会有两种情况:

①刚好分到了备份节点tomcat2上,此时仍能获取到s1会话,除此之外,tomcat2还要另外做的事是将这个s1会话标记为原件且继续选取一个备份地址备份s1会话,这样一来又有了备份。

②假如分到了非备份节点tomcat3,此时肯定找不到s1会话,于是它将向集群所有节点发问,“请问谁有s1会话的备份ip地址信息?”,因为只有tomcat2有s1的备份地址信息,它接收到询问后应答告知tomcat3节点s1会话的备份在tomcat2,根据这个信息就能查到s1会话了,并且tomcat3在自己本地生成s1会话并标为原件,tomcat2上的副本不变,这样一来同样能找到s1会话,正常完整整个请求处理。

 

2.Cluster组件

Cluster其实就是集群的意思,它是为了更加方便上层调用而抽象出来的一个比较高的层次的一个概念。总的来讲,它最重要的两个接口就是发送和接收接口。对于Cluster来讲,它可以让你不必关心集群之间如何通信、与谁通信,你只要调用Cluster接口将消息发送出去即完成了集群内消息的传递。

Cluster组件包含:会话管理器、集群通信通道、部署器(Deployer)、集群监听器(ClusterListener)、集群阀门等。

3.Tomcat的Cluster组件

Tomcat集群组件对一个请求的大致流程处理过程如下:

1、 客户端发送请求之后被Tomcat接收,成为一个request对象传入Engine容器准备处理;
2、 首先通过JvmRouteBinderValue进行处理,对不符合的会话ID进行处理,最后再调用下一个阀门;
3、 通过ReplicationValue阀门继续处理,它会先调下一个阀门进行处理,之后才进行会话数据集群同步;
4、 StandardEngineValue调用其他子容器对请求进行处理,找到对应的Servlet处理;
5、 响应客户端;
6、 ReplicationValue调用Cluster组件将会话数据同步到集群的其他实例上;
7、 把会话数据传输给其他的实例;

4.Tomcat中Cluster的级别

Tomcat中的集群组件可以分为两个级别,分别为Engine级别和Host级别,即Cluster组件可以放到Engine容器中,也可以放到Host容器中。这也就意味着,如果是Engine级别,则整个集群组件由所有Host共享,如果是Host级别,则由该Host专享,其他的Host无法使用;

5.如何让Tomcat实现集群功能

在Tomcat中使用集群功能相对简单,最简单的用法是直接在server.xml文件的 或者 节点下添加 配置,这意味着集群相关的配置都是使用默认的。

默认情况下,会使用DeltaManager作为会话管理器,使用GroupChannel作为集群通信通道,

组播地址和端口为228.0.0.4和45564;会使用ReplicationTransmitter作为消息发射器;会使用NioReceiver作为消息接收器。会添加TCPFailureDetector和MessageDispatcher15Interceptor两个拦截器;会使用ReplicationValue和JvmRouteBinderValue两个管道阀门;会使用FarmWarDeployer作为集群部署器;

还会添加JvmRouteSessionIDBinderListener和ClusterSessionListener集群监听器;