关于blob对象(图片)的缓存和提取问题

前段时间给客户做了一个会议签到系统
系统中涉及到这么一个问题:
用户签到的时候有一个页面显示当前签到人的照片和最近签到的6个人的照片,
照片数据都以blob对象存储在数据库中的,
为了在页面上显示照片,我直接写了一个servlet来处理照片的请求,
servlet中直接把blob二进制流输出给jsp界面。
jsp以的形式向servlet发送请求来获取照片
servlet中以
ServletOutputStream out = res.getOutputStream();
Blob temp = photo.getPhoto();
InputStream is = temp.getBinaryStream();
byte buffer[] = new byte[256];
while (is.read(buffer, 0, 256) != -1) {

out.write(buffer);
}

out.flush();
的形式响应。

每当有人签到,jsp页面就自动刷新一次来获取新的6张照片

这样设计有几个瓶颈
1、照片本身大小比较大,每张都是200+k,每次刷新所有的照片都要重新到后台去获取,对网络的要求比较高,而且如果多个人同时使用这个页面刷新的话,更是问题
2、每次请求的blob数据都需要到后台数据库中取数据,而且都是 并发的请求,对数据库的连接和数据库都有很大的压力,容易使服务down了

试着改进过,因为每次请求的6张照片中正常情况有5张是重复的,所以我想用hashmap把查出来的结果缓存起来,下次有请求来了,如果在缓存中有了,就直接在缓存中取。没有再到数据库中取,缓存中到了一定数量,再把缓存全部清了,否则肯定会占用大量内存。

但是这样又有问题,java的回收机制自动把我hashmap中的blob对象给清掉了。每次取到的persistence是有的可是到persistence中取blob却是空的了。所以jsp界面显示的照片都是空的。

不知道有没有高人能提出一个好的解决方案。探讨探讨,如何改进。或者重新设计。

不胜感激!

[b]问题补充:[/b]
HashMap问题blob对象被清 是这样的
HashMap本来就是放在单子实例中的,肯定也是static

public class AnalyseDAO extends BasicDAO {
private static AnalyseDAO _instance= new AnalyseDAO();;
public static AnalyseDAO getInstance() {
if (_instance == null) {
_instance = new AnalyseDAO();
}
return _instance;
}
private static HashMap photohash = new HashMap();

public TCifPhoto getCifPhoto(String stuempNo){
if(photohash.size()>=50){
photohash.clear();
}
if(photohash!=null && photohash.get(stuempNo)!=null){
TCifPhoto tmp= (TCifPhoto)photohash.get(stuempNo);
if(tmp.getPhoto()==null){
System.out.println("omg that's it lob is empty");
}
return tmp;
}else{
Session s = getSession();
String sql = " from TCifPhoto cif where cif.stuempNo= '"+stuempNo+"'";
try {
Query q = s.createQuery(sql);
List list = q.list();
if(list!=null&&list.size()>0){
return (TCifPhoto)list.get(0);
}else{
return null;
}
} catch (HibernateException he) {
throw he;
}
}

}

这样的设计在tomcat上跑一点问题都没有 HashMap中的数据也能取到,可是一但到websphere上HashMap中存储的TCifPhoto 这个persistence还在,就是persistence里面的二进制lob对象没有了,从代码里看出系统输出了“omg that's it lob is empty”就很明显知道问题的状况。可以判定websphere把lob字段给清了。

或者另一种架设是websphere用到了对lob字段的lazyload的方式或者别的。没有找到相应的文档资料。
[b]问题补充:[/b]
又补充讨论一下:
现在看来armorking说的应该是对的,blob的操作是逻辑指针,数据库连接释放后就不能取到值了。而tomcat和websphere数据库连接的管理方式又别,所以同样的代码,tomcat能正常运行,websphere就不行。(这有待继续挖掘区别,o(∩_∩)o...)。

其实把blob缓存,我觉得不是最好的方案吧,即时做成byte[]缓存解决了原先的问题,但是,每张图片有300k,在签到峰值时期,每个用户客户端与服务器的网络流量将达到很高的程度,假设刷新为2s一次 300K*6=1.8M ,接近1M/s。再加上如果有多个客户端在刷。服务器流量将受到考验。

所以我考虑是不是可以把照片缓存到每个客户端ie缓存中。首先把第一次请求的照片数据从数据库中取出缓存到服务器硬盘上。而客户端直接通过url来存取服务器上的照片。每次刷新后,相同的照片ie会自动取ie缓存中的照片,而不会再到服务器上取,这样就能解决原来的两个问题了。

不知道大家有什么建议?

从lz提供的source来看
blob数据检索出来以后,并没有被放到photohash中

另外,根据sql的Blob的文档
[quote]
默认情况下,驱动程序使用 SQL locator(BLOB) 实现 Blob,这意味着 Blob 对象包含一个指向 SQL BLOB 数据而不是数据本身的逻辑指针。Blob 对象在它被创建的事务处理期间有效。
[/quote]

TCifPhoto.getPhoto()的类型如果是Blob型而不是 byte[]的话,
一旦检索事务处理结束了,的确有可能变成null

把照片放在server的硬盘上吧,显示照片根据签会人的id显示6张照片好了。
不过现在你可以把硬盘上不存在的照片从库里读出来,要是硬盘上有,下次就不用读库了。

[quote]但是这样又有问题,java的回收机制自动把我hashmap中的blob对象给清掉了。每次取到的persistence是有的可是到persistence中取blob却是空的了。所以jsp界面显示的照片都是空的。[/quote]

你肯定是把HashMap丢在Servlet中了, 丢在某个单件类中 [就一个系统运行的话] 或者 丢在Session中[多个系统的话].

每个不同的照片都有个名字吧?相同的相片的名字总是相同吧?

用客户端缓存不行吗?相片缓存久点就是了嘛。这样只有初次访问的时候可能会消耗些时间。当每个人的相片都缓存了之后,就不会消耗多少资源了。

一般情况下,垃圾回收不会去主动清除仍然存在Map中引用的对象,除非是WeakHashMap等特殊的Map实现。

建议你检查下代码,看缓存的HashMap是否static。

以我的理解,这个问题跟垃圾回收无关

一般来说,对象在servlet中有三种scope:
request, session, application
其中request < session < application

而lz想做的应该是个对象缓存
但是每个用户的session是不同的,也就是说不同用户的session是无法共享的
所以,应当使用application级别的缓存策略

从实现手段上来讲,最简单的,就是一个单例类就能搞定
只需要注意两点:
1、既然是个“会议签到系统”,那么这个缓存的结构就应该是一个会议对应一个照片缓存
2、每次用户签到后缓存的6个相片中最早签到的一张需要被最新签到的用户照片替换
这个替换处理需要加上同步处理
3、根据实际应用场景,可能需要对缓存做清除处理
比如说这个“会议签到系统”应当会对会议的状态管理中有一个“会议结束”的处理
那么,在这个“会议结束”处理的最后,应当把对应的缓存清除

这样的情况就有点妖了,按理说只要不是操作系统不一致,java虚拟机的本地实现应该是一致的。
缓存图片信息的字节数组看看?