tomcat源码阅读之BackupManager
一、 配置:
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="6"> <Manager className="org.apache.catalina.ha.session.BackupManager" expireSessionsOnShutdown="false" notifyListenersOnReplication="true" mapSendOptions="6"/> <Channel className="org.apache.catalina.tribes.group.GroupChannel"> <Membership className="org.apache.catalina.tribes.membership.McastService" address="228.0.0.4" port="45564" frequency="500" dropTime="3000"/> <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver" address="192.168.0.106" port="5000" selectorTimeout="100" maxThreads="6"/> <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter"> <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/> </Sender> <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/> <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/> <Interceptor className="org.apache.catalina.tribes.group.interceptors.ThroughputInterceptor"/> </Channel> <Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=".*\.gif;.*\.js;.*\.jpg;.*\.png;.*\.htm;.*\.html;.*\.css;.*\.txt;"/> <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/> </Cluster>
与DeltaManager配置的区别就是将manager元素的className由DeltaManager换成了BackupManager;
二、 BackupManager会话管理原理:
1、 DeltaManager会话管理器采用的全节点复制,即每个session的创建、修改和释放都广播到集群中其他所有节点上,当集群节点增加时,网络流量会呈平方趋势增长,因此无法构建较大规模的集群,BackupManager会话管理器采取另一种会话管理方式,每个会话只有一个备份,他会使网络流量随节点数的增加呈线性趋势增长,大大减少了网络流量,可以构建较大规模的集群;
2、 backupManager会话管理器中存在三种类型的节点:源节点、备份节点和代理节点,创建会话时,该会话保存在该节点上,每次对这个会话的访问都被分配到这个节点上,这个节点被称为源节点,会话创建后,会在集群节点中找到一个节点用于存放该会话的备份,这个节点称为备份节点,集群中除了源节点和备份节点外,其他节点都会保存一个该会话的位置信息,这些节点被称之为代理节点;因此每个会话都有一个源节点,一个备份节点和若干个代理节点;
3、 如果某个会话的源节点宕机了,这时他的请求会随机分配到其他任意节点上,有两种情况:
a) 分配到备份节点上:此时仍然能获取到该会话,同时将这个节点上的这个会话标记为源节点,且继续选取一个其他节点作为该会话的备份节点,并将会话信息拷贝到新的备份节点上;
b) 分配到了代理节点上:此时肯定在代理节点上找不到该会话,于是他向集群所有节点查询该会话,该会话的备份节点收到查询请求后,将该会话信息拷贝到代理节点上,此代理节点升级为该会话的源节点,同时告知集群中其他节点,该会话的源节点已经变更;
4、 集群节点列表的维护主要是通过启动时向所有节点广播节点信息以及心跳去维护,如下图所示:n1启动时向其他节点广播自己的信息,其他节点收到信息后将n1添加到自己的列表,而n1则把n2,n3,n4添加到自己的列表,接着按照一定的时间间隔向其他节点发心跳,如下图,假如n2未给n1响应信息,n1则把n2从自己的列表中删除;
5、 backupManager使用Round robin算法用于备份节点的选择,按照顺序依次从集群节点列表中选择节点,代码如下:
三、 UML图:
1、 会话管理器其实就是对会话集操作的封装,从设计角度看,为了改变会话集的操作行为,只需继承ConcurrentHashMap类并重写其中一些方法即可实现,例如put、get、remove等等操作实现跨节点操作。于是tomcat的BackupManager对整个会话集的跨节点操作被封装到一个继承ConcurrentHashMap类的LazyReplicatedMap子类中;
2、 AbstractReplicatedMap类的innerMap成员变量保存了sessionId和MapEntry的映射关系,MapEntry则是对session、源节点成员及备份节点等的封装,从上面的UML图可以看出MapoEntry的成员变量:primary存储了该session的源节点信息,backupNodes存储了备份节点信息,backupNodes虽然是一个数组,但是里面永远只有一个元素,key和value存储了sessionId和session对象,backup是个布尔类型,表示该session节点是否是备份节点,proxy表示是否是代理节点,copy表示是否是copy节点;
3、 要在集群节点之间通信和发消息,需要定义通信协议以及信息载体,而这个协议以及信息载体是在MapMessage中定义的,从上面的UML图中可以看出,MapMessage中包含的信息有:key和value表示sessionId和session对象,keydata和valuedata表示sessionId和session对象序列化为字节流后的数据,primary表示该会话的源节点,nodes表示该会话的备份节点,msgtype表示操作类型,其定义如下:
这里每个值都代表一个语义,例如MSG_BACKUP表示让接收方把接收到的会话对象进行备份、MSG_REMOVE则表示让接收方按照接收到的会话id把对应的会话删除等等
比如在备份操作中MapMessage对象就像组成一个句子:“本人会话id为keydata,会话值为valuedata,我的源节点为primary,我现在需要做备份操作”。
4、 AbstractReplicatedMap类实现了map接口,通过实现map接口的相关操作达到会话的跨节点操作,它实现了MembershipListener接口,表示可以支持集群节点的添加和删除时,可以相应的更新本集群节点上的session集合;它实现了ChannelListener,可以在接收到集群消息时更新session集合;它实现了RpcCallback,可以在对rpc调用时根据消息操作类型更新session集合;
5、 AbstractReplicatedMap.mapMembers存储了集群成员和他最近一次心跳成功的时间,对于超过指定时间(这个值默认是5秒)之内没有心跳成功的集群节点,将会从集群节点中删除,代码如下:
6、 AbstractReplicatedMap也是基于Tribes组件的,因此包含了成员变量channel,而rpcChannel则是用于rpc调用时的组件,也可以在接收到其他节点的rpc调用时进行相应的处理;currentNode是用于选取会话的备份节点时用的变量,表示上一次刚刚选取的备份节点的序号,选取备份节点时的代码如下:
7、 backupmanager在每次请求结束时,都会调用AbstractReplicatedMap的replicate方法将当前会话的变更信息复制到备份节点,发送的消息是一个MapMessage对象,分为3类消息:新会话、会话更新和会话访问,此外,Map的移除也会向备份节点发送MapMessage消息;AbstractReplicatedMap类的replyRequest、leftOver、messageReceived方法中针对不同类型的消息作出不同的响应,比如说MSG_START类型的消息,是一个集群节点在启动后广播给集群中其他节点的消息,在replyRequest、leftOver、messageReceived方法中分别针对这个类型的调用mapMemberAdded方法,mapMemberAdded的职责就是更新mapMembers中指定集群节点上次心跳成功的时间值和对会话列表中没有备份节点的的会话重新选取一个备份节点;代码如下:
8、 LazyReplicatedMap.publishEntryInfo的职责是给会话选择一个备份节点,并给备份节点发送一个MSG_BACKUP的消息和给代理节点发送一个MSG_PROXY的消息,代码如下:
9、 BackupManager的实现相对比较简单,在startInternal中创建LazyReplicatedMap的对象,并使用这个对象管理会话:
10、 AbstractReplicatedMap.put方法将会话存入innerMap中,流程如下:
a) 实例化MapEntry,将key和value传入,并设置源节点为目前节点。
b) 判断本地内存是否已包含key,如是则不仅要本地remove掉,还要跨节点remove。
c) 通过Round robin算法从MapMember中选择一个作为备份节点。
d) 实例化一个包含MSG_BACKUP标识的MapMessage对象并发送给备份节点。
e) 实例化一个包含MSG_PROXY标识的MapMessage对象并发送给除了备份节点外的其他(代理)节点。
f) put进本地innerMap缓存。
代码如下:
11、 AbstractReplicatedMap.get在指定节点上获取会话,流程如下:
a) 获取本地的MapEntry对象,它或许直接包含了会话对象,或许包含了会话对象的存放位置信息。
b) 判断本节点是否属于源节点,如为源节点则直接获取MapEntry对象里面的会话对象并返回。
c) 判断本节点是否属于备份节点,若为备份节点则直接获取MapEntry对象里面的会话对象作为返回对象,并且还要将本节点升为源节点、重新选取一个新备份节点,把MapEntry对象拷贝到新备份节点。
d) 判断本节点是否属于代理节点,若为代理节点则向其他节点发送会话对象拷贝请求,“集群中谁有此会话对象请发送给我”,把接收到的会话对象放到本节点并作为返回对象,最后将本节点升为源节点。
代码如下:
12、 AbstractReplicatedMap.remove删除会话对象,流程如下:
a) 删除本地此MapEntry对象。
b) 广播其他节点删除此MapEntry对象。
代码如下: