Java 使用双内存缓冲区 实现简易日志组件

Posted BBinChina

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 使用双内存缓冲区 实现简易日志组件相关的知识,希望对你有一定的参考价值。

在实现简易日志组件时,为了写日志的同时,可将日志数据进行写入磁盘实现持久化。如果每记录一条日志就写一次磁盘文件的话, 磁盘IO会占用正常处理逻辑的性能。因此,采用双内存缓冲区的思路来将写日志与写磁盘文件这两个动作进行解耦。

1、将业务日志写入到日志缓冲区
2、当进行刷盘时,将日志缓冲区的数据转移到待刷盘缓冲区
3、日志缓冲区继续执行,而刷盘线程可另外工作

以下为实现的逻辑代码:

package com.dfs.namenode.server;

import java.util.LinkedList;

/**
 * 文件变更日志
 */
public class FSEditLog {

    /**
     * 日志序号
     */
    private long txidSeq = 0L;

    /**
     * 用于存储当前写线程的日志序号
     */
    private ThreadLocal<Long> localTxid = new ThreadLocal<>();

    /**
     * 双层缓存,用于写日志,同时可刷盘
     */
    private DoubleBuffer editLogBuffer = new DoubleBuffer();

    /**
     * 默认没人在刷盘
     */
    private boolean isSyncRunning = false;

    /**
     * 已刷盘的最大日志序号
     */
    private long syncMaxTxid = 0;

    /**
     * 是否有线程在等待刷新下一批log到磁盘文件
     */
    private boolean isWaitSync;

    /**
     * 记录log日志
     * @param content
     */
    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 {
        /**
         * 用来缓存写入的log
         */
        LinkedList<EditLog> currentBuffer = new LinkedList<>();

        /**
         * 用来将日志进行刷盘
         */
        LinkedList<EditLog> syncBuffer = new LinkedList<>();

        public void Write(EditLog log) {
            currentBuffer.add(log);
        }

        /**
         * 将写入的日志缓存数据转到待刷盘的缓存列表中
         */
        public void SetReadyToSync() {
            LinkedList<EditLog> tmp = currentBuffer;
            currentBuffer = syncBuffer;
            syncBuffer = tmp;
        }

        /**
         * @return 已记录的日志序号
         */
        public long GetSyncMaxTxid() {
            return syncBuffer.getLast().txid;
        }

        /**
         * 刷盘
         */
        public void Flush() {
            for(EditLog log : syncBuffer) {
                //用文件输出流将数据写入磁盘文件中
            }
            syncBuffer.clear();
        }
    }
}

在java nio中也实现了DoubleBuffer这个类,感兴趣的朋友也可以看看

以上是关于Java 使用双内存缓冲区 实现简易日志组件的主要内容,如果未能解决你的问题,请参考以下文章

今儿新学会一个写日志技能:双缓冲机制

多线程异步日志系统,高效强悍的实现方式-双缓冲

多线程异步日志系统,高效强悍的实现方式-双缓冲

13.4-全栈Java笔记:打飞机游戏实战项目|offScreenImage|GameObject|Plane

华为OD机试真题Java实现简易内存池2真题+解题思路+代码(2022&2023)

Java中用双缓冲技术消除闪烁