2

I know that I can use JMS and ActiveMQ, but I really need something very simple and without a lot of overhead. I did some test with ActiveMQ and didn't really liked a performance of persistence queues.

What I'm looking for is basic implementation of any blocking queue with ability to store message on HDD (ideally) if some size limit is reached. Then it should be able to read stored message from HDD and if possible stop writing new to HDD (restore in memory use).

My scenario is very simple - messages (json) are coming from outside world. I do some processing and then send them to another REST service. Problem can occur when target REST service is down or network between us is bad. In this case ready to go events are stored in queue that can potentially fill up all available memory. I don't want/need to write every message to HDD/DB - only those that can't fit into memory.

Thank you!

Alex
  • 125
  • 1
  • 8
  • 1
    What you are asking for is not `something very simple`. You probably want `something reliable`. – Alexander Pogrebnyak Mar 06 '13 at 17:52
  • ehcache is the simplest way I know of to move data on and off of disk transparently. If queue order is important you'll need to handle that yourself though. – Affe Mar 06 '13 at 18:01
  • Yes, queue order is important. Also, when I said - "something very simple" I meant that I don't need clustered enterprise solutions(cause I can use ActiveMQ). All magic should happen inside 1 JVM. Additional nice-to-have feature - if JVM is stopped - populate queue from HDD if there are any messages. – Alex Mar 06 '13 at 18:26
  • It's not simple, but not rocket science either. One or two days' work. Just plan it carefully at the queue "edges". – Hot Licks Mar 06 '13 at 19:33
  • I know. That's why I already started coding it. But it looks like it is a common problem and I believe there should be a solution. Maybe not as standalone library but as a part of bigger opensource project. – Alex Mar 06 '13 at 20:03
  • As soon as you start doing "to disk" things, you typically need to think over a lot of issues like if message loss is acceptable (Transactionallity) and how to scale horizontally. Also your issue of messages that won't fit into memory suggests that you are talking about very huge data volumes. Anyway, the issue is, as been told, not at all very simple. At least for production usage. The general solution to similar problems is to use a message broker like ActiveMQ, but there are others as well. You may want to check out Apache QPID. You may need to tweak those beasts to get most out of it – Petter Nordlander Mar 08 '13 at 11:27
  • Actually I found library that could possible satisfy my requirements: https://github.com/bulldog2011/bigqueue (BigQueue). It is a big, fast and persistent queue based on memory mapped file. Going to try it. – Alex Mar 08 '13 at 20:47

2 Answers2

0

This code should work for you - its an in memory persistent blocking queue - needs some file tuning but should work

    package test;

     import java.io.BufferedReader;
     import java.io.BufferedWriter;
     import java.io.File;
     import java.io.FileReader;
     import java.io.FileWriter;
     import java.io.IOException;
     import java.util.ArrayList;
     import java.util.Collections;
     import java.util.LinkedList;
     import java.util.List;

     public class BlockingQueue {

    //private static Long maxInMenorySize = 1L;
    private static Long minFlushSize = 3L;

    private static String baseDirectory = "/test/code/cache/";
    private static String fileNameFormat = "Table-";

    private static String  currentWriteFile = "";

    private static List<Object>  currentQueue = new LinkedList<Object>();
    private static List<Object>  lastQueue = new LinkedList<Object>();

    static{
        try {
            load();
        } catch (IOException e) {
            System.out.println("Unable To Load");
            e.printStackTrace();
        }
    }

    private static void load() throws IOException{
        File baseLocation = new File(baseDirectory);
        List<String> fileList = new ArrayList<String>();

        for(File entry : baseLocation.listFiles()){
            if(!entry.isDirectory() && entry.getName().contains(fileNameFormat)){
                fileList.add(entry.getAbsolutePath());
            }
        }

        Collections.sort(fileList);

        if(fileList.size()==0){
            //currentQueue = lastQueue = new ArrayList<Object>();
            currentWriteFile = baseDirectory + "Table-1";
            BufferedWriter writer = new BufferedWriter(new FileWriter(currentWriteFile));
            while (!lastQueue.isEmpty()){
                writer.write(lastQueue.get(0).toString()+ "\n");
                lastQueue.remove(0);
            }
            writer.close();
        }else{
            if(fileList.size()>0){
                    BufferedReader reader = new BufferedReader(new FileReader(fileList.get(0)));
                    String line=null;
                    while ((line=reader.readLine())!=null){
                        currentQueue.add(line);
                    }
                    reader.close();
                    File toDelete = new File(fileList.get(0));
                    toDelete.delete();
            }

            if(fileList.size()>0){
                BufferedReader reader = new BufferedReader(new FileReader(fileList.get(fileList.size()-1)));
                currentWriteFile = fileList.get(fileList.size()-1);
                String line=null;
                while ((line=reader.readLine())!=null){
                    lastQueue.add(line);
                }
                reader.close();
                //lastFileNameIndex=Long.parseLong(fileList.get(fileList.size()).substring(6, 9));
            }
        }

    }

    private void loadFirst() throws IOException{
        File baseLocation = new File(baseDirectory);
        List<String> fileList = new ArrayList<String>();

        for(File entry : baseLocation.listFiles()){
            if(!entry.isDirectory() && entry.getName().contains(fileNameFormat)){
                fileList.add(entry.getAbsolutePath());
            }
        }

        Collections.sort(fileList);

        if(fileList.size()>0){
                BufferedReader reader = new BufferedReader(new FileReader(fileList.get(0)));
                String line=null;
                while ((line=reader.readLine())!=null){
                    currentQueue.add(line);
                }
                reader.close();
                File toDelete = new File(fileList.get(0));
                toDelete.delete();
        }
    }

    public Object pop(){
        if(currentQueue.size()>0)
            return  currentQueue.remove(0);

        if(currentQueue.size()==0){
            try {
                loadFirst();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        if(currentQueue.size()>0)
            return  currentQueue.remove(0);
        else
            return null;
    }

    public synchronized Object waitTillPop() throws InterruptedException{
        if(currentQueue.size()==0){
            try {
                loadFirst();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            if(currentQueue.size()==0)
                wait();
        }
        return currentQueue.remove(0);
    }

    public synchronized void push(Object data) throws IOException{
        lastQueue.add(data);
        this.notifyAll();
        if(lastQueue.size()>=minFlushSize){
            BufferedWriter writer = new BufferedWriter(new FileWriter(currentWriteFile));
            while (!lastQueue.isEmpty()){
                writer.write(lastQueue.get(0).toString() + "\n");
                lastQueue.remove(0);
            }
            writer.close();

            currentWriteFile  = currentWriteFile.substring(0,currentWriteFile.indexOf("-")+1) + 
                    (Integer.parseInt(currentWriteFile.substring(currentWriteFile.indexOf("-")+1,currentWriteFile.length())) + 1);
        }
    }

    public static void main(String[] args) {
        try {
            BlockingQueue bq = new BlockingQueue();

            for(int i =0 ; i<=8 ; i++){
                bq.push(""+i);
            }

            System.out.println(bq.pop());
            System.out.println(bq.pop());
            System.out.println(bq.pop());

            System.out.println(bq.waitTillPop());
            System.out.println(bq.waitTillPop());
            System.out.println(bq.waitTillPop());
            System.out.println(bq.waitTillPop());



        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}
Wanna Coffee
  • 2,742
  • 7
  • 40
  • 66
  • Good attempt, so won't down vote, but there's no way I'd trust this in a production environment as the JVM could crash before things get persisted to disk. – KRK Owner Oct 02 '17 at 23:08
0

Okay, so having your queue persisted to disk would work if you back your queue with a RandomAccessFile, a MemoryMappedFile or a MappedByteBuffer.. or some other equivalent implementation. In the event of your JVM crashing or terminating prematurely, you can pretty much rely on your operating system to persist uncommitted buffers to disk. The caveat is that if your machine crashes beforehand, you can say goodbye to any updates in your queue, so make sure you understand this. You can sync your disk for a guaranteed persistence albeit with a heavy performance hit. From a more hardcore perspective, another option is to replicate to another machine for redundancy, which warrants a separate answer given it's complexity.

KRK Owner
  • 762
  • 7
  • 16