业务背景
在订单业务领域会遇到订单超时的情形,要实现这样的功能,有2种解决方案:
- 定时JOB捞取订单状态为支付超时的订单,去批量取消。这个方案是定时全量捞取,效率低,容易积压订单,如果没有超时订单,JOB还是会空跑浪费资源,不可取。
- Redis的List做延时堵塞队列的情况,从队列使用BLpop取超时订单取消,避免了空跑,和全量扫描。
问题
可是问题来了,Redis存在数据丢失的情况:
- 比如Redis的数据没有及时刷盘的情况下,宕机了。
- 比如Redis主从切换,主的数据还来得及同步到从就宕机了,这个时候从节点升为主会丢失数据。
解决方案
那么如何解决呢?
最近在看Mysql与Hbase都是通过WAL(write ahead Log),即在通过记录操作日志的方式,在系统宕机的情况下,可以通过日志追回数据,从而保证数据的可靠性。那么我也可以使用WAL来防止Redis数据丢失。
问题来了,实时写磁盘,速度肯定没有内存快,那么怎么解决写磁盘慢的问题呢?
还好,办法总是比困难多。在看Kafka有提到顺序写的性能,比随机写快好多倍,本着“尽信书不如没书”的原则,我用Java写了一个顺序写与随机写,通过实验验证真理。
/**
* Java实现随机写
*/
public class RandomWriter {
public static void fileWrite(String filePath, String content) {
FileOutputStream outputStream = null;
try {
File file = new File(filePath);
boolean isCreate = file.createNewFile();//创建文件
if (isCreate) {
outputStream = new FileOutputStream(file);//形参里面可追加true参数,表示在原有文件末尾追加信息
outputStream.write(content.getBytes());
}else {
outputStream = new FileOutputStream(file,true);//表示在原有文件末尾追加信息
outputStream.write(content.getBytes());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void fileRead(String filePath) {
File file = new File(filePath);
if (file.exists()) {
try {
//创建FileInputStream对象,读取文件内容
FileInputStream fis = new FileInputStream(file);
byte[] bys = new byte[1024];
while (fis.read(bys, 0, bys.length) != -1) {
//将字节数组转换为字符串
System.out.print(new String(bys, StandardCharsets.UTF_8));
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
public static void main(String[] args) {
// 模拟WAL
long curtime = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
fileWrite("/Users/huangchunwu/redis-wal.log", "set key value"+i);
}
System.out.println("write log cost time is "+(System.currentTimeMillis()-curtime));
}
}
运行时间为write log cost time is 625
/**
* Java实现顺序写
*/
public class SequenceWriter {
public static int fileWrite(String filePath, String content, int index) {
File file = new File(filePath);
RandomAccessFile randomAccessTargetFile;
MappedByteBuffer map;
try {
randomAccessTargetFile = new RandomAccessFile(file, "rw");
FileChannel targetFileChannel = randomAccessTargetFile.getChannel();
map = targetFileChannel.map(FileChannel.MapMode.READ_WRITE, 0, (long) 1024 * 1024 * 1024);
map.position(index);
map.put(content.getBytes());
return map.position();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
return 0;
}
public static String fileRead(String filePath, long index) {
File file = new File(filePath);
RandomAccessFile randomAccessTargetFile;
MappedByteBuffer map;
try {
randomAccessTargetFile = new RandomAccessFile(file, "rw");
FileChannel targetFileChannel = randomAccessTargetFile.getChannel();
map = targetFileChannel.map(FileChannel.MapMode.READ_WRITE, 0, index);
byte[] byteArr = new byte[10 * 1024];
map.get(byteArr, 0, (int) index);
return new String(byteArr);
} catch (IOException e) {
e.printStackTrace();
} finally {
}
return "";
}
public static void main(String[] args) {
// 模拟WAL
int position =0;
long curtime = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
position = fileWrite("/Users/huangchunwu/redis-wal.log", "set key value"+i, position);
}
System.out.println("write log cost time is "+(System.currentTimeMillis()-curtime));
}
}
运行时间为write log cost time is 362
这样一对比,顺序写的性能比随机写高了2倍。