测试之前从redis到数据库同步功能,一直是但进程在测。今天不小心多开了一个同步进程,发现bug。
Redis中的数据结构是一个列表,并且是不断动态增长,同步程序是一个定时任务程序,每隔n秒从Redis中读出数据(姑且称为为消费者程序,只是不清数据),然后插入DB中,持久化后不能清除Redis中的数据,所以要记录上一次读列表的结束位置。
起初我使用了一个全局变量RedisListIndex,用来记录该任务本次读的结束位置,也是下一次读的起始位置。该全局变量由Redis的生产者维护。
代码片段
int idx = RedisCluster.RedisListIndex; //全局变量
mCluster.setCurrentPostion(idx);//设置本次读的结束位置,作为下一次的起始位置
Log.info("idx = " + idx);
ArrayList<String> list;
if (0 == mCluster.getLastPostion()) {
list = (ArrayList<String>) mCluster.lrang(queue, 0, idx);
} else {
Log.info("mCluster.getLastPostion() = " + mCluster.getLastPostion() + " and idx = " + idx);
list = (ArrayList<String>) mCluster.lrang(queue, mCluster.getLastPostion(), idx);
}
Log.info("list =" + list);
// TODO 写入数据库
mDao.insertCashTradeInfo(list);
以上代码能够完成任务,但是如果进程意外退出,或者多进程同时运行,RedisListIndex必然会清空或者被多个生产者竞争,就出现数据重复错乱,由于数据表设置了主键约束,遇到相同主键数据库也会就会报错(列表里包含主键的相关字段数值)。
所以我考虑到使RedisListIndex 能够保存在磁盘中。对于java的文件操作不是很熟悉。 自己封装了一个带有互斥锁功能的文件控制器。
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.Date;
/**
* 文件操作类
* @author JP
*/
public class FileOptration {
private RandomAccessFile Raf;
private FileChannel channel = null;
private FileLock lock = null;
public FileOptration(String path,String mode) throws IOException{
Raf = new RandomAccessFile(path,mode);
//Raf.seek(Raf.length());//raf在文件末尾追加内容的处理
channel = Raf.getChannel();
lock = channel.lock();//独占锁
}
/**
* @Title fileWriteWithLock
* @description 写文件操作,该操作带有互斥锁,文件同时只能被一个进程写。如果该锁已经被占用,则进程阻塞。
* @param buf 写内容
* @author JP
* @date 创建时间:2015-08-03
*/
public void fileWriteWithLock(String buf) throws IOException{
// 互斥操作
ByteBuffer sendBuffer=ByteBuffer.wrap((new Date()+buf).getBytes());
channel.write(sendBuffer);
}
/**
* @Title fileClose
* @description 关闭文件,施放锁。
* @param buf 写内容
* @author JP
* @date 创建时间:2015-08-03
*/
public void fileClose(){
if(lock != null) {
try {
lock.release();
lock = null;
} catch (IOException e) {
e.printStackTrace();
}
}
if(channel != null) {
try {
channel.close();
channel = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
消费者和生产者通过
FileOptration 来把RedisListIndex在一个加锁文件中维护起来,就可以避免上述问题。
虽然是解决了,但心里总觉的乖乖的,一个文件至维护了一个变量而已,RedisListIndex竟然成了环境变量,该方案未免有些笨重。希望大神们能提出更加高大上的解决方案供我参考。