Tomcat源码分析—Session的管理

Tomcat源码分析—Session的管理

这篇文章是我在2011年时写的,现转到我自己的博客上

 

Session的作用范围介于请求和上下文之间,多次请求可以公用一个session,但这个session是有时间限制的,而上下文是一直存在的。
一个请求就对应一个Request,当请求结束了Request就会被回收,而session是通过request获取的,实际上request获取session的时候是委托给
Manager,由Manager去获取一个session再交给request的。
这里的Manager只是一个接口,它的子类如下:

Manager获取一个session的时候可以从内存中查找,也可以从文件系统中查找,也可以从数据库中查找,还可以从网络上查找。
正因为有不同的查找实现方式,所以才有各种session的处理方式,标准的方式就是从内存获取,如果是持久化的方式,也可以将session保存到文件
系统中,在集群的环境下,session还可以从网络中获取。

Session的创建过程时序图如下:

 

自定义的Servlet或者是JSP是通过request获取session的,在容器的内部就是Request委派给了Manager去做的,这里使用的是默认的配置,也就是
StandardManager。
servlet调用的getSession()意思是查找出一个session,如果找不到就创建一个,所以Request的doGetSession()也是先从Manager中查找session,当找不到之后会让Manager新创建一个session,这个创建动作是委托给ManagerBase去做的,而持久化Manager,集群Manager创建
session的时候都是委托给ManagerBase去完成的。创建的一个新sesson就是StandardSession,最后将这个新创建的StandardSession放到
ManagerBase中统一保存。当创建一个session时,会调用到StandardSession的tellNew()方法,这会触发一个监听器动作,会调用自定义的监听器
执行sessionCreated()方法。

Session的属性设置时序图如下:

 

可以看出和session创建的时候触发Listener比较类似,设置值的时候也是由StandardSession去做的,由StandardSession的setAtrribute()方法
先找到所有注册的监听器,然后挨个判断是否实现了SessionAttributeListener接口,如果是就调用相应的方法。

Session在服务器端保存超过一定的时间后就会被删除,这个删除的动作是由一个后台线程去做的
Session的实效检查时序图:

 

后台线程是由StandardEngine在启动的时候创建的,这个线程类是ContainerBase的内部类  ContainerBackgroundProcessor,首先检查定义在
它内部的所有线程(它会按照容器的级别,先找到最小的子容器),然后执行这个容器的backgroundProcess()方法,这个方法是在ContainerBase
中定义的,这个方法里会挨个检查和它管理的load,realm,manager的backgroundProcess()方法,最终会触发ManagerBase的backgroundProcess()方法,它会检查自己内部保存的所有session,调用StandardSession,执行isValid()方法。如果session已经过期了,就会
销毁这个session,销毁的时候会触发自定义Session的监听器,执行sessionDestroyed()方法。接着ManagerBase将这个session从map中删除,
最后StandardSession会删除和这个session相关联的所有信息,这又会触发Session绑定的监听器调用它的valueUnbound(),以及Session属性的
监听器调用它的attributeRemoved()。这一系列的操作执行完后session实效的检查也就结束了。

如果我们需要将Session里面的数据保存到文件系统或者数据库中,就需要配置一个PersistentManager,由它负责将session保存到持久化设备中。
持久的文件格式一般是 Session的ID开头,.session结尾
容器关闭时的session持久化时序图如下:

关闭容器的时候会触发Catalina内部的一个钩子线程,这个钩子线程就会调用容器的生命周期的stop()方法,由于容器是嵌套的,所以大容器又会挨个
调用子容器的stop(),这样就完成了所有的关闭动作,在主机这个容器关闭的时候会除非一个监听事件(主机的部署就是在主机启动的时候触发的),
然后这个事件会卸载掉主机上所有的应用,这样会调用到上下文的stop(),上下文又会调用Manager的stop,这里就是PersistentManager。最终
PersistentManager会调用它管理的一个Stroe(注意,只有持久的manager才有Store的set()方法,所以普通的manager是不能调用Store的),这个
Strore可以是文件的store也可以是JDBC的store,这里配置的就是FileStore(),调用它的save(),最后会将这个session的内容保存到文件系统中。

如果设置了持久化的manager,当超过一定的时间后,会将session持久化到文件系统中(注意这个超时时间不是session的超时时间,这个超时时间
是容器自己定义的,也就是说每个容器的厂商都会有一个类似的超时时间,但和规范没有关系),这个超时的检查是由后台线程去完成的。
后台线程检查session超时持久化时序图:

和普通的session后台检查类似,也会调用到ManagerBase,但这个Manager就不是之前的那个StandardManager了,而是PersistentManager。
实际上它也有一个processExpires()方法,和StandardManager是一个样的,就是检查session是否过期了。在执行这个方法之前,PersistentManager会执行一个持久化超时的检查,它会检查当前的session如果超过了在配置文件中定义的时间(就是超过了这个时间就会将
session持久化到文件系统中,这个值应当小于session超时时间 )就会将session保存到持久设备中,同时还检查了当前session的个数是否超过了限制,如果超过的话也会将多余的session保存到持久设备中。这两个动作就对应了processMaxIdleSwaps()和processMaxActiveSwaps(),它们的思想都是类似的,这里就以processMaxIdleSwaps()为主来介绍。
首先会调用会执行一些判断,如果满足条件就调用自身的swapOut(),这又会调用到StandardSession的passivate(),这里会拿到所有的注册Listener,判断是否有监听器实现了钝化激活Listener,如果有的话,就调用这个自定义Listener的sessionWillPassivate()。调用完后回到swapOut()方法里,这次就会真正的将session保存了,保存session调用FileStore的save()方法,由于需要将StandardSession持久化到文件中,所有FileStore会调用StandardSession的writeObjectData(),而session内部又保存了一些自定义的持久化类,所有writeObjectData()会调用到
自定义类的writeObject(),最终这个持久化的session就被写入到文件系统中了。
持久化到文件中还没有结束,回到swapOut()方法后会调用父类ManagerBase的remove(),将这个session从map中删除,之后再调用processMaxActiveSwaps(),将那些超过session个数限制的session保存到文件中,PersistentManager的processPersistenceChecks()方法执行完后就会调用后,就会像普通manager一样,执行一个processExpires(),超时检查,这次就不是从内存中查找了,而且从文件系统中将这个session先读出来,然后判断是否过时了,如果是就将这个session删除,这里的读取会委托给FileStore去完成,和写的时候类似,也会触发StandardSession的自定义读操作,如果session中保存了自定义持久对象,还会调用到自定义类的readObject()。到此一次后台检查就结束了。
从这里可以看到持久化session的后台检查是非常耗时的,因为每次操作都需要从硬盘上读文件,然后判断,所以这里最好能有一个优化的操作,将session的ID始终保存在内存中,也就是session不是不是完全从ManagerBase中删除,只删除大部分,保存ID等信息,这样在超时判断的时候就会
省去读硬盘的费事操作了,只有当真正持久化超时的时候才会触发一次写硬盘的操作,能节省很多时间。

当session被持久化后,就被ManagerBase从map中删除,也就是从内存中删除,此时如果客户端再次调用getSession(),就会触发一次硬盘读取操作,此时持久manager会因为在内存中找不到从而就从硬盘上试着读取,如果读取到了就反序列化然后返回给客户端,如果没有这个文件,就用父类ManagerBase创建一个最后将这个session加入到cookie并加到Reponse(规范实现类)中。如果服务器重启了,客户端有一次getSession(),也会触发一次读取硬盘的操作(假设这个文件存在 )
持久化session的加载时序图:

 

和普通的session读取,创建类似,首先是委托给Request的doGetSession()方法,doGetSession()首先从manager里面找,这里的manager就是
PersistentManager,调用它的findSession()方法,findSession()内首先执行一个SwapIn(),试着从硬盘读取session,这就委派个FileStore的load()方法去做,如果这个文件存在,就会调用StandardSession的readObjectData(),如果session内部又有自定义的持久化类,又会调用持久化类的readObject(),这样就完成了一次反序列化。当反序列化完成了,PersistentManager会通知StandardSession有一个新的session创建了,所以会
调用StandardSession的tellNew()方法,如果有自定义的session监听器,tellNew()还会调用自定义监听器的sessionCreated()方法。当完成监听器调用后,PersistentManager会委托父类ManagerBase将session增加到map中,也就是内存中。接着调用StandardSession的activate(),这又会触发自定义钝化激活session监听器的sessionDidActivate()方法,这些步骤完成后,session的恢复也就执行完了,最后返回给客户端。
如果硬盘中没有这个文件,也就是调用PersistentManager的findSession()找不到session,就会调用ManagerBase创建一个新session,创建完
session后会配置配置一些cookie参数,最后将这个cookie加入到规范实现的写响应Response中。

从以上一些分析可以看到,所谓的持久Manager就有些类似二级缓存的概念,内存找不到,就去硬盘找,不过这个参数配置,也就是过多久去持久化一次
session,以及session中保存的内容都很重要,这都将影响后台处理的时间。

2 次阅读

发表评论

电子邮件地址不会被公开。