自从前段时间的一个事故让队列里缓存的大量关键数据丢失后,一直琢磨着弄一个能持久化到本地文件的队列,这样即使系统再次发生意外,我也不至于再苦逼的修数据了。选定使用mappedbytebuffer来实现,但做出来的原型不够理想。《高性能队列Fqueue的设计和使用实践》这篇文章给了我莫大的帮助。 当然只是借鉴了大致的文件系统结构,绝大部分还是按自己的想法来的。

上图就是队列的文件系统,index文件记录了队列当前的读写文件号,读写位置和读写计数。队列的size是通过读写计数writeCounter-readCounter的方式记录的,这样做的好处是可以做到读写分离。运行时size用一个AtomicInteger变量记录,系统初始化加载队列时才用到读写计数差。block文件记录了实际的入队数据,每个block必须要有足够的空间写入4(len)+data.length+4(EOF)长度的数据,否则写入一个EOF标记,换一个新的block开始写入数据,而当读取到这个EOF时,表示这个block读取完毕,载入下一个block,如果有的话,释放并准备删除当前block。现在规定的block大小32MB,按我的使用场景,每个block可以写入100W数据(PS:protostuff-runtime挺不错的,我用它做的序列化)。

最后附上代码:

    public class MFQueuePool {


    private static final Logger LOGGER = LoggerFactory.getLogger(MFQueuePool.class);
    private static final BlockingQueue<String> DELETING_QUEUE = new LinkedBlockingQueue<>();
    private static MFQueuePool instance = null;
    private String fileBackupPath;
    private Map<String, MFQueue> fQueueMap;
    private ScheduledExecutorService syncService;     private MFQueuePool(String fileBackupPath) {
        this.fileBackupPath = fileBackupPath;
        File fileBackupDir = new File(fileBackupPath);
        if (!fileBackupDir.exists() && !fileBackupDir.mkdir()) {
            throw new IllegalArgumentException("can not create directory");
        }
        this.fQueueMap = scanDir(fileBackupDir);
        this.syncService = Executors.newSingleThreadScheduledExecutor();
        this.syncService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                for (MFQueue MFQueue : fQueueMap.values()) {
                    MFQueue.sync();
                }
                deleteBlockFile();
            }
        }, 10L, 10L, TimeUnit.SECONDS);
    }     private void deleteBlockFile() {
        String blockFilePath = DELETING_QUEUE.poll();
        if (StringUtils.isNotBlank(blockFilePath)) {
            File delFile = new File(blockFilePath);
            try {
                if (!delFile.delete()) {
                    LOGGER.warn("block file:{} delete failed", blockFilePath);
                }
            } catch (SecurityException e) {
                LOGGER.error("security manager exists, delete denied");
            }
        }
    }     private static void toClear(String filePath) {
        DELETING_QUEUE.add(filePath);
    }     private Map<String, MFQueue> scanDir(File fileBackupDir) {
        if (!fileBackupDir.isDirectory()) {
            throw new IllegalArgumentException("it is not a directory");
        }
        Map<String, MFQueue> exitsFQueues = new HashMap<>();
        File[] indexFiles = fileBackupDir.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return MFQueueIndex.isIndexFile(name);
            }
        });
        if (ArrayUtils.isNotEmpty(indexFiles)) {
            for (File indexFile : indexFiles) {
                String queueName = MFQueueIndex.parseQueueName(indexFile.getName());
                exitsFQueues.put(queueName, new MFQueue(queueName, fileBackupPath));
            }
        }
        return exitsFQueues;
    }     public synchronized static void init(String deployPath) {
        if (instance == null) {
            instance = new MFQueuePool(deployPath);
        }
    }     private void disposal() {
        this.syncService.shutdown();
        for (MFQueue MFQueue : fQueueMap.values()) {
            MFQueue.close();
        }
        while (!DELETING_QUEUE.isEmpty()) {
            deleteBlockFile();
        }
    }     public synchronized static void destory() {
        if (instance != null) {
            instance.disposal();
            instance = null;
        }
    }     private MFQueue getQueueFromPool(String queueName) {
        if (fQueueMap.containsKey(queueName)) {
            return fQueueMap.get(queueName);
        }
        MFQueue MFQueue = new MFQueue(queueName, fileBackupPath);
        fQueueMap.put(queueName, MFQueue);
        return MFQueue;
    }     public synchronized static MFQueue getFQueue(String queueName) {
        if (StringUtils.isBlank(queueName)) {
            throw new IllegalArgumentException("empty queue name");
        }
        return instance.getQueueFromPool(queueName);
    }     public static class MFQueue extends AbstractQueue<byte[]> {         private String queueName;
        private String fileBackupDir;
        private MFQueueIndex index;
        private MFQueueBlock readBlock;
        private MFQueueBlock writeBlock;
        private ReentrantLock readLock;
        private ReentrantLock writeLock;
        private AtomicInteger size;         public MFQueue(String queueName, String fileBackupDir) {
            this.queueName = queueName;
            this.fileBackupDir = fileBackupDir;
            this.readLock = new ReentrantLock();
            this.writeLock = new ReentrantLock();
            this.index = new MFQueueIndex(MFQueueIndex.formatIndexFilePath(queueName, fileBackupDir));
            this.size = new AtomicInteger(index.getWriteCounter() - index.getReadCounter());
            this.writeBlock = new MFQueueBlock(index, MFQueueBlock.formatBlockFilePath(queueName,
                    index.getWriteNum(), fileBackupDir));
            if (index.getReadNum() == index.getWriteNum()) {
                this.readBlock = this.writeBlock.duplicate();
            } else {
                this.readBlock = new MFQueueBlock(index, MFQueueBlock.formatBlockFilePath(queueName,
                        index.getReadNum(), fileBackupDir));
            }
        }         @Override
        public Iterator<byte[]> iterator() {
            throw new UnsupportedOperationException();
        }         @Override
        public int size() {
            return this.size.get();
        }         private void rotateNextWriteBlock() {
            int nextWriteBlockNum = index.getWriteNum() + 1;
            nextWriteBlockNum = (nextWriteBlockNum < 0) ? 0 : nextWriteBlockNum;
            writeBlock.putEOF();
            if (index.getReadNum() == index.getWriteNum()) {
                writeBlock.sync();
            } else {
                writeBlock.close();
            }
            writeBlock = new MFQueueBlock(index, MFQueueBlock.formatBlockFilePath(queueName,
                    nextWriteBlockNum, fileBackupDir));
            index.putWriteNum(nextWriteBlockNum);
            index.putWritePosition(0);
        }         @Override
        public boolean offer(byte[] bytes) {
            if (ArrayUtils.isEmpty(bytes)) {
                return true;
            }
            writeLock.lock();
            try {
                if (!writeBlock.isSpaceAvailable(bytes.length)) {
                    rotateNextWriteBlock();
                }
                writeBlock.write(bytes);
                size.incrementAndGet();
                return true;
            } finally {
                writeLock.unlock();
            }
        }         private void rotateNextReadBlock() {
            if (index.getReadNum() == index.getWriteNum()) {
                // 读缓存块的滑动必须发生在写缓存块滑动之后
                return;
            }
            int nextReadBlockNum = index.getReadNum() + 1;
            nextReadBlockNum = (nextReadBlockNum < 0) ? 0 : nextReadBlockNum;
            readBlock.close();
            String blockPath = readBlock.getBlockFilePath();
            if (nextReadBlockNum == index.getWriteNum()) {
                readBlock = writeBlock.duplicate();
            } else {
                readBlock = new MFQueueBlock(index, MFQueueBlock.formatBlockFilePath(queueName,
                        nextReadBlockNum, fileBackupDir));
            }
            index.putReadNum(nextReadBlockNum);
            index.putReadPosition(0);
            MFQueuePool.toClear(blockPath);
        }         @Override
        public byte[] poll() {
            readLock.lock();
            try {
                if (readBlock.eof()) {
                    rotateNextReadBlock();
                }
                byte[] bytes = readBlock.read();
                if (bytes != null) {
                    size.decrementAndGet();
                }
                return bytes;
            } finally {
                readLock.unlock();
            }
        }         @Override
        public byte[] peek() {
            throw new UnsupportedOperationException();
        }         public void sync() {
            index.sync();
            // read block只读,不用同步
            writeBlock.sync();
        }         public void close() {
            writeBlock.close();
            if (index.getReadNum() != index.getWriteNum()) {
                readBlock.close();
            }
            index.reset();
            index.close();
        }
    }     @SuppressWarnings("UnusedDeclaration")
    private static class MFQueueIndex {         private static final String MAGIC = "v.1.0000";
        private static final String INDEX_FILE_SUFFIX = ".idx";
        private static final int INDEX_SIZE = 32;         private static final int READ_NUM_OFFSET = 8;
        private static final int READ_POS_OFFSET = 12;
        private static final int READ_CNT_OFFSET = 16;
        private static final int WRITE_NUM_OFFSET = 20;
        private static final int WRITE_POS_OFFSET = 24;
        private static final int WRITE_CNT_OFFSET = 28;         private int p11, p12, p13, p14, p15, p16, p17, p18; // 缓存行填充 32B
        private volatile int readPosition;   // 12   读索引位置
        private volatile int readNum;        // 8   读索引文件号
        private volatile int readCounter;    // 16   总读取数量
        private int p21, p22, p23, p24, p25, p26, p27, p28; // 缓存行填充 32B
        private volatile int writePosition;  // 24  写索引位置
        private volatile int writeNum;       // 20  写索引文件号
        private volatile int writeCounter;   // 28 总写入数量
        private int p31, p32, p33, p34, p35, p36, p37, p38; // 缓存行填充 32B         private RandomAccessFile indexFile;
        private FileChannel fileChannel;
        // 读写分离
        private MappedByteBuffer writeIndex;
        private MappedByteBuffer readIndex;         public MFQueueIndex(String indexFilePath) {
            File file = new File(indexFilePath);
            try {
                if (file.exists()) {
                    this.indexFile = new RandomAccessFile(file, "rw");
                    byte[] bytes = new byte[8];
                    this.indexFile.read(bytes, 0, 8);
                    if (!MAGIC.equals(new String(bytes))) {
                        throw new IllegalArgumentException("version mismatch");
                    }
                    this.readNum = indexFile.readInt();
                    this.readPosition = indexFile.readInt();
                    this.readCounter = indexFile.readInt();
                    this.writeNum = indexFile.readInt();
                    this.writePosition = indexFile.readInt();
                    this.writeCounter = indexFile.readInt();
                    this.fileChannel = indexFile.getChannel();
                    this.writeIndex = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, INDEX_SIZE);
                    this.writeIndex = writeIndex.load();
                    this.readIndex = (MappedByteBuffer) writeIndex.duplicate();
                } else {
                    this.indexFile = new RandomAccessFile(file, "rw");
                    this.fileChannel = indexFile.getChannel();
                    this.writeIndex = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, INDEX_SIZE);
                    this.readIndex = (MappedByteBuffer) writeIndex.duplicate();
                    putMagic();
                    putReadNum(0);
                    putReadPosition(0);
                    putReadCounter(0);
                    putWriteNum(0);
                    putWritePosition(0);
                    putWriteCounter(0);
                }
            } catch (Exception e) {
                throw new IllegalArgumentException(e);
            }
        }         public static boolean isIndexFile(String fileName) {
            return fileName.endsWith(INDEX_FILE_SUFFIX);
        }         public static String parseQueueName(String indexFileName) {
            String fileName = indexFileName.substring(0, indexFileName.lastIndexOf('.'));
            return fileName.split("_")[1];
        }         public static String formatIndexFilePath(String queueName, String fileBackupDir) {
            return fileBackupDir + File.separator + String.format("findex_%s%s", queueName, INDEX_FILE_SUFFIX);
        }         public int getReadNum() {
            return this.readNum;
        }         public int getReadPosition() {
            return this.readPosition;
        }         public int getReadCounter() {
            return this.readCounter;
        }         public int getWriteNum() {
            return this.writeNum;
        }         public int getWritePosition() {
            return this.writePosition;
        }         public int getWriteCounter() {
            return this.writeCounter;
        }         public void putMagic() {
            this.writeIndex.position(0);
            this.writeIndex.put(MAGIC.getBytes());
        }         public void putWritePosition(int writePosition) {
            this.writeIndex.position(WRITE_POS_OFFSET);
            this.writeIndex.putInt(writePosition);
            this.writePosition = writePosition;
        }         public void putWriteNum(int writeNum) {
            this.writeIndex.position(WRITE_NUM_OFFSET);
            this.writeIndex.putInt(writeNum);
            this.writeNum = writeNum;
        }         public void putWriteCounter(int writeCounter) {
            this.writeIndex.position(WRITE_CNT_OFFSET);
            this.writeIndex.putInt(writeCounter);
            this.writeCounter = writeCounter;
        }         public void putReadNum(int readNum) {
            this.readIndex.position(READ_NUM_OFFSET);
            this.readIndex.putInt(readNum);
            this.readNum = readNum;
        }         public void putReadPosition(int readPosition) {
            this.readIndex.position(READ_POS_OFFSET);
            this.readIndex.putInt(readPosition);
            this.readPosition = readPosition;
        }         public void putReadCounter(int readCounter) {
            this.readIndex.position(READ_CNT_OFFSET);
            this.readIndex.putInt(readCounter);
            this.readCounter = readCounter;
        }         public void reset() {
            int size = writeCounter - readCounter;
            putReadCounter(0);
            putWriteCounter(size);
            if (size == 0 && readNum == writeNum) {
                putReadPosition(0);
                putWritePosition(0);
            }
        }         public void sync() {
            if (writeIndex != null) {
                writeIndex.force();
            }
        }         public void close() {
            try {
                if (writeIndex == null) {
                    return;
                }
                sync();
                AccessController.doPrivileged(new PrivilegedAction<Object>() {
                    public Object run() {
                        try {
                            Method getCleanerMethod = writeIndex.getClass().getMethod("cleaner");
                            getCleanerMethod.setAccessible(true);
                            sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod.invoke(writeIndex);
                            cleaner.clean();
                        } catch (Exception e) {
                            LOGGER.error("close fqueue index file failed", e);
                        }
                        return null;
                    }
                });
                writeIndex = null;
                readIndex = null;
                fileChannel.close();
                indexFile.close();
            } catch (IOException e) {
                LOGGER.error("close fqueue index file failed", e);
            }
        }
    }     private static class MFQueueBlock {         private static final String BLOCK_FILE_SUFFIX = ".blk"; // 数据文件
        private static final int BLOCK_SIZE = 32 * 1024 * 1024; // 32MB         private final int EOF = -1;         private String blockFilePath;
        private MFQueueIndex index;
        private RandomAccessFile blockFile;
        private FileChannel fileChannel;
        private ByteBuffer byteBuffer;
        private MappedByteBuffer mappedBlock;         public MFQueueBlock(String blockFilePath, MFQueueIndex index, RandomAccessFile blockFile, FileChannel fileChannel,
                            ByteBuffer byteBuffer, MappedByteBuffer mappedBlock) {
            this.blockFilePath = blockFilePath;
            this.index = index;
            this.blockFile = blockFile;
            this.fileChannel = fileChannel;
            this.byteBuffer = byteBuffer;
            this.mappedBlock = mappedBlock;
        }         public MFQueueBlock(MFQueueIndex index, String blockFilePath) {
            this.index = index;
            this.blockFilePath = blockFilePath;
            try {
                File file = new File(blockFilePath);
                this.blockFile = new RandomAccessFile(file, "rw");
                this.fileChannel = blockFile.getChannel();
                this.mappedBlock = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, BLOCK_SIZE);
                this.byteBuffer = mappedBlock.load();
            } catch (Exception e) {
                throw new IllegalArgumentException(e);
            }
        }         public MFQueueBlock duplicate() {
            return new MFQueueBlock(this.blockFilePath, this.index, this.blockFile, this.fileChannel,
                    this.byteBuffer.duplicate(), this.mappedBlock);
        }         public static String formatBlockFilePath(String queueName, int fileNum, String fileBackupDir) {
            return fileBackupDir + File.separator + String.format("fblock_%s_%d%s", queueName, fileNum, BLOCK_FILE_SUFFIX);
        }         public String getBlockFilePath() {
            return blockFilePath;
        }         public void putEOF() {
            this.byteBuffer.position(index.getWritePosition());
            this.byteBuffer.putInt(EOF);
        }         public boolean isSpaceAvailable(int len) {
            int increment = len + 4;
            int writePosition = index.getWritePosition();
            return BLOCK_SIZE >= increment + writePosition + 4; // 保证最后有4字节的空间可以写入EOF
        }         public boolean eof() {
            int readPosition = index.getReadPosition();
            return readPosition > 0 && byteBuffer.getInt(readPosition) == EOF;
        }         public int write(byte[] bytes) {
            int len = bytes.length;
            int increment = len + 4;
            int writePosition = index.getWritePosition();
            byteBuffer.position(writePosition);
            byteBuffer.putInt(len);
            byteBuffer.put(bytes);
            index.putWritePosition(increment + writePosition);
            index.putWriteCounter(index.getWriteCounter() + 1);
            return increment;
        }         public byte[] read() {
            byte[] bytes;
            int readNum = index.getReadNum();
            int readPosition = index.getReadPosition();
            int writeNum = index.getWriteNum();
            int writePosition = index.getWritePosition();
            if (readNum == writeNum && readPosition >= writePosition) {
                return null;
            }
            byteBuffer.position(readPosition);
            int dataLength = byteBuffer.getInt();
            if (dataLength <= 0) {
                return null;
            }
            bytes = new byte[dataLength];
            byteBuffer.get(bytes);
            index.putReadPosition(readPosition + bytes.length + 4);
            index.putReadCounter(index.getReadCounter() + 1);
            return bytes;
        }         public void sync() {
            if (mappedBlock != null) {
                mappedBlock.force();
            }
        }         public void close() {
            try {
                if (mappedBlock == null) {
                    return;
                }
                sync();
                AccessController.doPrivileged(new PrivilegedAction<Object>() {
                    public Object run() {
                        try {
                            Method getCleanerMethod = mappedBlock.getClass().getMethod("cleaner");
                            getCleanerMethod.setAccessible(true);
                            sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod.invoke(mappedBlock);
                            cleaner.clean();
                        } catch (Exception e) {
                            LOGGER.error("close fqueue block file failed", e);
                        }
                        return null;
                    }
                });
                mappedBlock = null;
                byteBuffer = null;
                fileChannel.close();
                blockFile.close();
            } catch (IOException e) {
                LOGGER.error("close fqueue block file failed", e);
            }
        }
    }
}

用mappedbytebuffer实现一个持久化队列【转】的更多相关文章

  1. 基于Berkeley DB实现的持久化队列

    转自:http://guoyunsky.iteye.com/blog/1169912 队列很常见,但大部分的队列是将数据放入到内存.如果数据过多,就有内存溢出危险,而且长久占据着内存,也会影响性能.比 ...

  2. Codeforces Gym 100431G Persistent Queue 可持久化队列

    Problem G. Persistent QueueTime Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://acm.hust.edu.cn/vjudg ...

  3. python rabbitMQ持久化队列消息

    import pika connection = pika.BlockingConnection( pika.ConnectionParameters('localhost'))#建立一个最基本的so ...

  4. 使用NODEJS+REDIS开发一个消息队列以及定时任务处理

    作者:RobanLee 原创文章,转载请注明: 萝卜李 http://www.robanlee.com 源码在这里: https://github.com/robanlee123/RobCron 时间 ...

  5. skynet源代码学习 - 从全局队列中弹出/压入一个消息队列过程

    学习云风的skynet源代码,简单记录下. void skynet_globalmq_push(struct message_queue * queue) { struct global_queue ...

  6. 聊聊高并发(十四)理解Java中的管程,条件队列,Condition以及实现一个堵塞队列

    这篇里面有一些主要的概念,理解概念是件有意义的事情,仅仅有理解概念才干在面对详细问题的时候找到正确的解决思路.先看一下管程的概念 第一次在书上看到管程这个中文名称认为非常迷糊,管程究竟是个什么东东,于 ...

  7. 利用ReentrantLock简单实现一个阻塞队列

    借助juc里的ReentrantLock实现一个阻塞队列结构: package demo.concurrent.lock.queue; import java.util.concurrent.lock ...

  8. [PY3]——实现一个优先级队列

    import heapq class PriorityQueue: def __init__(self): self._queue=[] self._index=0 def push(self,ite ...

  9. 用PHP写一个双向队列

    PHP写一个双向队列,其实是在考察PHP几个内置数组的函数 用PHP写一个双向队列 <?php class Deque{ public $queue = array(); /** * 尾部入对 ...

随机推荐

  1. 【转】servlet/filter/listener/interceptor区别与联系

    原文:https://www.cnblogs.com/doit8791/p/4209442.html 一.概念: 1.servlet:servlet是一种运行服务器端的java应用程序,具有独立于平台 ...

  2. Expert C Programming 阅读笔记(~CH1)

    P4: 好梗!There is one other convention—sometimes we repeat a key point to emphasize it. In addition, w ...

  3. 如何让EasyUI弹出层跳出框架

    这个的解决方法其实挺简单的. 只要在最外面的框架页面加个div,然后用parent.div的id就可以的.但是必须得弹出框得是一个页面. <div id="div_info" ...

  4. OpenStack openvswitch 实践

    先说下我这架构就是2个节点控制节点+计算节点,网络这采用ovs方法没有路由,就是二层打通并且可以多vlan. 网络架构图: eth0网卡走trunk,走多vlan.从dashboard上创建不同的vl ...

  5. python虚拟环境virtualenv下安装MySQL-python

    1.先在windows安装:https://github.com/konglingxi/mysqldb_for_python27 2.按照提示将python主环境中的mysqldb相关文件及文件夹移到 ...

  6. 【BZOJ 3442】 3442: 学习小组 (最大费用流)

    3442: 学习小组 Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 403  Solved: 193 Description [背景] 坑校准备鼓励学生 ...

  7. SDOI 2017 Round1 解题报告

    Day 1 T1 数字表格 题目大意 · 求\(\prod\limits_{i=1}^n\prod\limits_{j=1}^mFibonacci(\gcd(i,j))\),\(T\leq1000\) ...

  8. [Codeforces #201] Tutorial

    Link: 传送门 代码量很少的一套思维题 A: 试一试发现最后状态一定是所有$min,max$间$gcd$的倍数 直接判断数量的奇偶性即可 #include <bits/stdc++.h> ...

  9. PHP链接sqlserver出现中文乱码

    PHP通过dblib扩展链接sqlserver,使用的是freetds,出现中文乱码. 在freetds的配置文件中(/usr/local/freetds/etc/freetds.conf),[glo ...

  10. centos 7 修改ssh登录端口

    在阿里云上面使用的oneinstack镜像,默认是使用的22端口,每次登录总会发现有人在暴力破解我的服务器,所以想想还是修改一下比较安全. 1.在防火墙打开新的端口 iptables -I INPUT ...