转载:Tomcat 7集群浅析

简介
每个节点都要维护一份集群节点信息列表,集群组通知的默认实现是在使用 UDP 数据包发送组播心跳到组播IP地址的基础上构建的。心跳包中含有 Tomcat 节点的 IP 地址,以及 Tomcat 用来侦听会话复制的 TCP 端口。集群成员通过使用相同的多播地址/端口组合组合在一起,每个成员定时发送用于动态发现组成员的心跳包。如果在一个 dropTime 时间内没有收到某个心跳包,那么这个心跳包所属节点就会从当前节点维护的节点列表删除。
其他信息机比如会话复制和更新等通过TCP 在节点间直连和传输,建立 TCP 的信息来源于心跳包。有同步和异步两种模式。在同步模式下,对客户端的响应必须在Session拷贝到其他节点完成后进行。异步模式无需等待Session拷贝完成就可响应。异步模式更高效,但是同步模式可靠性更高。同步异步模式由channelSendOptions参数控制,默认值是8,为异步模式,4是同步模式。在异步模式下,可以通过加上拷贝确认来提高可靠性,此时channelSendOptions设为10。
如果说一个 web 应用不涉及会话的话,那么做集群是相当简单的,因为节点都是无状态的,集群内各个节点无需互相通信,只需要将各个请求均匀分配到集群节点即可。但基本所有 web 应用都会使用会话机制,所以做 web 应用集群时整个难点在于会话数据的同步。
当然你可以通过一些策略规避复杂的额数据同步操作,例如前面说到的把会话信息保存在分布式缓存或数据库中统一集中管理,如下图,每个tomcat实例只需去写入或读取数据库即可,避免了 tomcat 集群之间的通信。但这种方式也有不足,要额外引入数据库或缓存服务,同时也要保证它们的高可用性,增加了机器和维护成本。
 
增量会话管理器
Tomcat 默认的集群会话管理器是 DeltaManager,它主要用于集群中各个节点之间会话状态的同步维护。DeltaManager 的职责是将某节点的会话该变同步到集群内其他成员节点上,它属于全节点复制模式,所谓全节点复制是指集群中某个节点的状态变化后需要同步到集群中剩余的节点,甚至包括那些根本没有部署该应用的节点。在集群中全节点会话复制的一个大致步骤如下图所示,客户端发起一个请求,假设通过一定的负载均衡设备分发策略分到其中一个结点 node1,如果还未存在 session 对象的话 web 容器将会创建一个会话对象,接着执行一些逻辑处理,在对客户端响应之前有个重要的事情是要把 session 对象同步到集群中其他节点上,最后再响应客户端。当客户端第二次发起请求时,假如分发到 node3 节点上,由于同步了 node1 的 session 会话,所以在执行逻辑时并不会取不到 session 的值。如果删除某个会话对象则要同时通知其他节点把相应会话删除,如果修改了某个会话的某些属性也同样要更新到其他节点的会话中。
除了具备上面提到的全节点复制外,它还有具有只复制会话增量的特性,增量是以一个完整请求为周期,即会将一个请求过程中所有会话修改量在响应前进行集群同步。往下看 Tomcat 具体实现方案。为区分不同的动作必须要先定义好各种事件,例如会话创建事件、会话访问事件、会话失效事件、获取所有会话事件、会话增量事件、会话ID改变事件等等,实际上 tomcat 集群会有9种事件,集群根据这些不同的事件就可以彼此进行通信,接收方对不同事件做不同的操作。例如 node1 节点创建完一个会话后,即向其他三个节点发送 EVT_SESSION_CREATED 事件,其他三个节点接收到此事件后则各自在自己本地创建一个会话,会话包含了两个很重要的属性——会话 ID 和创建时间,这两个属性都必须由 node1 节点跟着 EVT_SESSION_CREATED 一起发送出去,本地会话创建成功后即完成了会话创建同步工作,此时你通过会话ID查找集群中任意一个节点都可以找到对应的会话。同样对于会话访问事件,node1 向其他节点发送 EVT_SESSION_ACCESSED 事件及会话 ID,其他节点根据会话 ID 找到对应会话并更新会话最后访问时间,以免被认为是过期会话而被清理。类似的还有会话失效事件(同步集群销毁某会话)、会话 ID 改变事件(同步集群更改会话 ID )等等操作。
 
备份会话管理器
全节点复制的网络流量随节点数量增加呈平方趋势增长,也正是因为这个因素导致无法构建较大规模的集群,为了使集群节点能更加大,首要解决的就是数据复制时流量增长的问题,于是 tomcat 提出了另外一种会话管理方式,每个会话只会有一个备份节点,备份节点一定是已经部署了相同应用的。它使会话备份的网络流量随节点数量的增加呈线性趋势增长,大大减少了网络流量和逻辑操作,可构建较大的集群。
正常情况下为了支持高效的并发操作,tomcat 的所有会话集使用 ConcurrentHashMap<String, MapEntry> 结构保存,String 类型是指会话 ID,MapEntry 则是对会话信息、源节点及备份节点等的封装,一般会话对象由哪个节点生成则哪个节点为源节点,备份节点则为集群中其他任意一节点。MapMessage 对象是节点间信息传递时会话信息等的载体,它还包含一些定义好的语义如 MSG_BACKUP、MSG_RETRIEVE_BACKUP、MSG_PROXY、MSG_REMOVE 等。
每个会话只有一个源节点,一个备份节点,若干个代理节点。node1 为源节点,表示会话对象由它创建,保存的是会话对象的原件;node3 为备份节点,保存的是会话对象的备份件;node2 和 node4 为代理节点,它们保存的仅仅是会话位置信息,例如备份节点的位置等。这样分类是为了提供failover能力,假如刚好源节点宕掉,请求落到备份节点则能获取到会话对象,此时备份节点变为源节点,再从 node2、node4 中选一个作为备份节点,并且把会话对象拷贝到新备份节点上;假如备份节点宕掉了,请求一样能从源节点获取到会话对象,但此时会从 node2、node4 中选一个新备份节点,并把会话对象拷贝到新备份节点上;假如代理节点宕掉了,一切没影响,正常工作。
创建会话的基本流程:新建 MapEntry,传入会话ID和会话信息,设置源节点为当前节点;通过 Round robin 算法从 MapMember 中选择一个作为备份节点并设置为 MapEntry 中的备份节点,将 MapEntry 等发送给备份点;组装包含 MSG_BACKUP 标识的 MapMessage 对象发到备份节点告诉它要备份我传过来的这个会话信息;组装包含 MSG_PROXY 标识的 MapMessage 对象发送到除备份节点外的其他节点,告诉它们把此会话的 id、源节点、备份节点等信息记录下。
获取会话的基本流程:获取本地的 MapEntry 对象,它或许直接包含了会话对象,或许包含了会话对象的存放位置信息;判断本节点是否属于源节点,如为源节点则直接获取 MapEntry 对象里面的会话对象并返回;判断本节点是否属于备份节点,若为备份节点则直接获取 MapEntry 对象里面的会话对象作为返回对象,并且还要将本节点升为源节点,重新选取一个新备份节点;判断本节点是否属于代理节点,若为代理节点则向其他节点发送会话对象拷贝请求,把接收到的会话对象放到本节点并作为返回对象,最后将本节点升为源节点。
 
注意事项
对于因超时引起的某个 session 失效无需通知其他节点,因为其他节点同样知道这个session已经超时。因此对于 tomcat 集群有一点非常重要,所有节点的操作系统时间必须一致!不然会出现某个节点 session 已过期而在另一节点此 session 仍处于活动状态的现象。Linux下同步时间通常采用ntp协议,可以在每个服务器上增加自动运行脚本文件,每天时间服务器自动同步,如/etc/cron.daily下增加脚本,内容为 /usr/sbin/ntpdate -s time.nist.gov 。还要确保 web.xml 含有 <distributable/> 元素。为实现会话复制,所有放在 session 里的对象都要实现java.io.Serializable接口。
以上两种模型都有各自的优缺点,在实际生产上部署应该根据实际情况选择适合的模型。
对于全节点会话同步模型,一旦集群节点数量及访问量大起来,将导致大量的会话信息需要互相复制同步,很容易导致网络阻塞,而且这些同步操作很可能会成为整体性能的瓶颈,根据经验,此种方案在实际生产上推荐的集群节点个数为3-6个,它无法组建更大的集群,而且冗余了大量的数据,利用率较低。使用同一个组播地址和端口的多个节点同属一个子集群,因此通过自定义组播地址和端口就可将一个大的tomcat集群分成多个子集群。
对于会话备份模型,每个会话只会有一个备份,大大减少了网络流量和逻辑操作,此模型可构建较大的集群。生产上可以组成十个以上的节点作为一个集群。使用这种模型也要考虑到,虽然这种模式支持更大的集群,但它只有一个数据备份,假如刚好源数据和备份数据所在的机器同时宕掉了,则没办法恢复数据,不过刚好同时宕机的机率很小很小。
 
参考
http://blog.csdn.net/wangyangzhizhou
 
原文地址:https://www.cnblogs.com/gjb724332682/p/8610627.html