需求:

最近在考虑IM软件服务器端编程,有这样一个需求,就是把用户发的信息,全部保存到数据库中,如果是一般的web应用,可能就直接把用户提交的数据写入数据库了,但IM的信息的特点时,需要响应快(实时处理),数据量大,根据这个特点,我设计成接收转发模块与数据写入数据库为不同的模块,很明显,他们满足生产消费者模式,即某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。生产者与消费者并没有构成生产消费者模式,而生产者生产的数据由消费者消费的过程才能构成生产消费者模式。

设计:

生产者和消费者模式实现方式很多,但在实现的时候我们至少需要考虑生产消费解耦、支持并发、支持生产消费忙闲不均的情况。

一、解耦

假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。将来如果消费者的代码发生变化,可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。这个缓冲区我们可以用一个列队实现。

二、支持并发(concurrency)

生产者直接调用消费者的某个方法,还有另一个弊端。由于函数调用是同步的(或者叫阻塞的),在消费者的方法没有返回之前,生产者只好一直等在那边。万一消费者处理数据很慢,生产者就会白白糟蹋大好时光。

使用了生产者/消费者模式之后,生产者和消费者可以是两个独立的并发主体。生产者把制造出来的数据往缓冲区一丢,就可以再去生产下一个数据。基本上不用依赖消费者的处理速度。其实当初这个模式,主要就是用来处理并发问题的。这样高并发下,用户感觉响应很快:)

三、支持忙闲不均

缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造速度慢下来,消费者再慢慢处理掉。

除了以上三个需要考虑的,在我的项目中,我还要考虑与数据库的交互不能太频繁,只能满足一定数据量时,使用批量方式写入数据库(定量写入),这个实现本身很简单,但如果考虑到服务器清闲时,很长时间都达不到这个数据量,但这段时间内可能会停机,没有写入数据库的数据就可能丢失,所以在给定的一段时间里,数据量没有到达给定数据的条件,只要达到时间条件,也需要写入数据库。

实现:

基于以上的想法,我们可以使用java.util.concurrent.BlockingQueue来实现,这里仅分析一些关键代码

1、构造一个用于数据缓冲区的队列

final static BlockingQueue queue = new LinkedBlockingQueue();

2、生产者生产的数据放入队列

// 本方法由生产者调用,将生产的数据放入队列
public static void add(Message m) {
try {
queue.put(m);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

3、消费者对产生的数据进行消费

@Override
public void run() {
try {
while (true) {
consume(queue.take());
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
void consume(Message message) {
synchronized(list) {
list.add(message);
if(list.size() == 100){
insert("满足100");
}
}
}

4、把数据批量写入数据库

void insert(String type){
synchronized(list) {
if(list.size() < 1)
return ;
System.out.println(type);
String name = Thread.currentThread().getName();
for(Message message : list){
log.debug("消费者:"+message.toString()+" ---"+name+" "+type);
}
list.clear();
}
}
}

5、满足时间条件时,批量把数据写入数据库

class TimerInsert extends TimerTask{
Consumer consumer;
TimerInsert(Consumer consumer){
this.consumer = consumer;
}
@Override
public void run() {
consumer.insert("满足时间");
}
}