在实现简易日志组件时,为了写日志的同时,可将日志数据进行写入磁盘实现持久化。如果每记录一条日志就写一次磁盘文件的话, 磁盘IO会占用正常处理逻辑的性能。因此,采用双内存缓冲区的思路来将写日志与写磁盘文件这两个动作进行解耦。
1、将业务日志写入到日志缓冲区
2、当进行刷盘时,将日志缓冲区的数据转移到待刷盘缓冲区
3、日志缓冲区继续执行,而刷盘线程可另外工作
以下为实现的逻辑代码:
package com.dfs.namenode.server;
import java.util.linkedList;
public class FSEditLog {
private long txidSeq = 0L;
private ThreadLocal localTxid = new ThreadLocal<>();
private DoubleBuffer editLogBuffer = new DoubleBuffer();
private boolean isSyncRunning = false;
private long syncMaxTxid = 0;
private boolean isWaitSync;
public void LogEdit(String content) {
//上锁写buffer
synchronized (this) {
//每次写日志都递增一次日志序号
txidSeq++;
long txid = txidSeq;
//将日志序号写到当前线程栈
localTxid.set(txid);
EditLog log = new EditLog(txid, content);
editLogBuffer.Write(log);
}
//将内存缓存的日志写入磁盘文件
logSync();
}
private void logSync() {
synchronized (this) {
if(isSyncRunning) {
//当前写线程的日志序号
long txid = localTxid.get();
//已刷盘的序号已经大于当前id,说明已被其他线程刷盘了
if (txid <= syncMaxTxid) {
return;
}
//有线程正在等待刷新磁盘
if(isWaitSync) {
return;
}
//进行刷盘
isWaitSync = true;
while(isSyncRunning) {
try {
wait(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
isWaitSync = false;
}
//交换两块缓存去
editLogBuffer.SetReadyToSync();
//获取已刷盘的日志最大序号
syncMaxTxid = editLogBuffer.GetSyncMaxTxid();
isSyncRunning = true;
}
//将缓存数据写入磁盘文件
editLogBuffer.Flush();
synchronized (this) {
//将标志位复位再释放锁
isSyncRunning = false;
//唤醒可能正在等待同步刷盘的线程
notifyAll();
}
}
class EditLog {
private long txid;
private String content;
public EditLog(long txid, String content) {
this.txid = txid;
this.content = content;
}
}
class DoubleBuffer {
linkedList currentBuffer = new linkedList<>();
linkedList syncBuffer = new linkedList<>();
public void Write(EditLog log) {
currentBuffer.add(log);
}
public void SetReadyToSync() {
linkedList tmp = currentBuffer;
currentBuffer = syncBuffer;
syncBuffer = tmp;
}
public long GetSyncMaxTxid() {
return syncBuffer.getLast().txid;
}
public void Flush() {
for(EditLog log : syncBuffer) {
//用文件输出流将数据写入磁盘文件中
}
syncBuffer.clear();
}
}
}
在java nio中也实现了DoubleBuffer这个类,感兴趣的朋友也可以看看



