今天做OA的及时消息系统时,
当有大量的消息发网服务器的时候,问题就来了,如果立即持久化到数据库中,那么数据库连接必然不够用,也会占用大量的CPU资源,我就做了一个缓存,大概是这样工作的:
当大量的信息发送到数据库的时候,我首先把它存放在内存中,当数量达到一定的阀值的时候,启动一个线程,执行批插入数据操作,这样做效果相当好~~可是,比这更大的信息发送到数据库,当一条持久化线程还没有运行结束的时候,又有一条持久化线程启动了,这就造成2条线程在执行批插入处理,更大的更多的消息到来的时候,就有可能N条线程一起执行插入数据库操作,这时,问题来了:
因为我插入的数据在数据库中对应了2张表,这2张表是有关系的,存在依赖的,刚才提到的,产生的持久化数据存在一个先后关系,也就是说,后生成的数据有可能会依赖前面产生的数据,因为是N条线程一起执行插入操作,那么就存在一种可能::就是当父数据项没有插入到数据库中的时候,插入子数据必然产生无外键依赖异常, 当我发现这个错误后,我又做了修改:
插入数据库线程,必须排队,只能按照排队的先后顺序,线程一条一条的启动,是用一个队列来实现的,很简单.队列代码如下:
/**/ /*
* 用于消息缓存MessageManagerCache的消息持久化线程协调,
* 因为只能运行一条持久化线程,这样才能保证在插入数据时候,不会产生没有外键依赖的数据库错误
*/
public class PThreadList ... {
private MessageStoreThread head=null;
private MessageStoreThread last=null;
private static PThreadList plist=new PThreadList();
private PThreadList()...{}
public static PThreadList getPThreadList()...{
return plist;
}
public MessageStoreThread getHead() ...{
return head;
}
public void setHead(MessageStoreThread head) ...{
this.head = head;
}
/**//*
*
*/
public synchronized void startIT(MessageStoreThread mt)...{
if(mt==null)...{//表示启动等待的线程
if(head==null)return;
head=head.getNextT();
if(head!=null)
start(head);
return;
}
/
if(head==null)...{//只有第一个线程有启动的机会,其余全部等待
head=mt;
last=mt;
start(head);
}
else...{
last.setNextT(mt);
last=mt;
}
}
private void start(Runnable thread)...{
new Thread(thread).start();
}
}
线程代码:
import java.util.Hashtable;
import java.util.Vector;
import src.common.ServiceLocator;
import src.common.tools;
import src.netmes.message.MessageServiceIF;
/**/ /*
* 消息批插入BEAN,用于MessageManagerCache的消息持久化
*/
public class MessageStoreThread implements Runnable ... {
private static int threadnums=0;//当前持久化线程数量
private Hashtable messagequeue=null;
private Vector receiverqueue=null;
private MessageStoreThread nextT=null;//用于组成线程连表
private static MessageServiceIF services=(MessageServiceIF) ServiceLocator.findMyBean("MessageService");//用于持久化
private long starttime=0;//该线程启动的时间
/**//*
* 持久化消息队列和接收队列,一定要先持久化消息,在持久化接收消息,防止主键约束
*/
public MessageStoreThread(Hashtable messagequeue,Vector receiverqueue)...{
this.messagequeue=messagequeue;
this.receiverqueue=receiverqueue;
changeThreadNUMS(1);
}
/**//*
* 持久化线程计数,opreation=1表示加 opreation=-1表示减
* */
public synchronized void changeThreadNUMS(int opreation)...{
threadnums=threadnums+opreation;
}
public void run()...{
starttime=System.currentTimeMillis();
//tools.printCue("持久化线程:---启动了--"+tools.millsTODateString(System.currentTimeMillis()));
System.out.println("持久化线程:---启动了--"+tools.millsTODateString(System.currentTimeMillis())+" 剩余线程数量:"+threadnums);
//changeThreadNUMS(1);
try...{
services.addBatchMEG2REV(this.messagequeue,this.receiverqueue);
}catch(Exception s)...{
tools.printERROR(" 批插入消息和接收人失败--"+s.getMessage()+" ");
s.printStackTrace();
}finally...{
PThreadList.getPThreadList().startIT(null);//启动等待线程
}
long time= System.currentTimeMillis()-starttime;
changeThreadNUMS(-1);
System.out.println("持久化线程:---保存数据完成--耗时:"+time+" 毫秒"+" 剩余线程数量:"+threadnums);
}
public MessageStoreThread getNextT() ...{
return nextT;
}
public void setNextT(MessageStoreThread nextT) ...{
this.nextT = nextT;
}
protected void finalize()
throws Throwable...{
System.out.println("huishou le --"+threadnums);
}
//获取和设置
public long getStarttime() ...{
return starttime;
}
public static int getThreadnums() ...{
return threadnums;
}
public Hashtable getMessagequeue() ...{
return messagequeue;
}
public Vector getReceiverqueue() ...{
return receiverqueue;
}
}
问题又来了,虽然数据在内存中产生是有先后顺序的,因为并发量很大,他不一定是按照先后顺序加入到数据库中的,当排队执行插入时,有可能在当前运行线程中的数据的父数据是排在后面的线程中的,再次造成了无父数据依赖异常,于是,我再次改进:
如果插入数据发现异常,回滚,并将该线程序放到队列的最末尾,以后排队轮到该线程的时候,再次执行数据库操作,问题顺利解决~~~当时的并发规模是:1000人同时向服务器不间断发送21条数据,对应了三个消息接收人,也就是说,总共会产生21*1000*4条数据,为了让更多的线程排队,我把阀值设置的很小,为3,表示每3条数据就会产生一个持久化线程.测试结果表明:持久化线程序最长排了将近6000个线程等带启动,程序运行良好!数据没有丢失,数据准确性也很高!
现在将并发人数提高到1300人,也就是说会产生1300*21*4条数据!问题又来了,出现大量数据丢失,我也不知道为什么~~~~现在还在改.
不过,实际应用中,应该不会出现上面的问题,原因是:
1.OA同时在线人数一般不会有1000人.
2.就算有1000人,他们也不大可能在短时间内产生大量数据,如上提到的
3.实际应用我的阀值会设置的比较大,也就是说持久化线程生成的数量不可能达到很多!
4.实际应用应该是服务器,我现在用的是PC机器1.25G的内存.2.0的CPU,机器性能不能等同
哈哈哈~~~不要为自己找理由了,继续努力DEBUG吧~