Using MongoDB to implement asynchronous messaging capabilities for Message Queuing

Source: Internet
Author: User
Tags message queue mongodb volatile
Overview of Message queues

Message Queue Middleware is an important component in distributed system, which mainly solves the problems of application coupling, asynchronous message, traffic sharpening and so on. Achieve high-performance, highly available, scalable, and final conformance architectures. is an indispensable middleware for large distributed systems.

At present in the production environment, the use of more message queues have ACTIVEMQ,RABBITMQ,ZEROMQ,KAFKA,METAMQ,ROCKETMQ and so on.

To achieve a better message queue to consider the high availability, sequential and repetitive messages, reliable delivery, consumer relationship analysis and so on more complex issues, the author does not elaborate on these content, focusing on the thread pool to achieve decoupling, asynchronous message function , If you are interested in other functions, recommend an article in the United States Group Technology Blog The essence of message queue design is basically hidden in this article.
two. Process of asynchronous processing

Scene Description: After the user registers, needs to send the registration mail and the registration message. There are two kinds of traditional approaches
1. Serial mode; 2. Parallel mode.
(1) Serial mode: The registration information will be written to the database after the successful, send the registered mail, and then send the registered SMS. Once all three of these tasks are complete, return to the client.

(2) Parallel mode: After the registration information written to the database successfully, send the registered mail at the same time, send the registration SMS. When the above three tasks are completed, return to the client. The difference with the serial is that the parallel way can improve the processing time.

Assuming that three business nodes each use 50 seconds, regardless of other costs such as the network, the serial mode time is 150 milliseconds, and the parallel time may be 100 milliseconds.

Because the number of requests processed by the CPU within the unit time is certain, it is assumed that throughput is 100 times in CPU1 seconds. The number of requests that the CPU can handle in a serial mode of 1 seconds is 7 times (1000/150). The number of requests processed in parallel mode is 10 times (1000/100).

Summary: As described above, the traditional way system performance (concurrency, throughput, response time) will have bottlenecks. How to solve this problem.

Introduces asynchronous processing. The reconstructed structure is as follows:

Three. Implement the above schema 3.1 database definition using MongoDB

As shown in the figure above, "send a registered SMS" and "Send registered mail" Are tasks that require asynchronous processing. The task is written to the database by the main thread ("Task Queue table"), while another thread reads the data from the Task Queue table and processes it according to the specific "Send registration SMS" and "Send registered mail".

Task Queue Model definition
Taskqueue.java

@Document public
class Taskqueue {
    @Id
    private String _id;
    private String name;  The task name. For example: "Send a registered SMS" or "Send registered mail" and other
    private String param;  The parameters of the required processing task
    private int status;//Status 0: Initial 1: Processing 8: Processing failed 9: processing successful
    private int retry;//Retry count, only for processing failure, STATUS:8
  private Date created; Create time
    private int threadnid;//Handle successful task processing thread unique identifier
    private int priority;//smaller, greater probability of being executed

}

Task processing Threading Model definition
Threadinstance.java

@Document public
class Threadinstance implements comparable<threadinstance>{
    @Id
    Private String _ ID;
    @Indexed (unique=true)
    private int nid;//ordinal, unique index. The meaning of a unique index subsequent interpretation
    private long pid;
    //thread ID private int taskcounts;//Current task processing line one thread the number of tasks processed, update the
    private int by heartbeat Execedtasks; This task handles the total number of threads, updating the private Date update with a heartbeat
    ;//Last active time, update with Heartbeat
3.2 Thread pools for asynchronous processing tasks

Corresponding to the above figure, "Send a registered SMS" and "Send registered mail" and so on need to complete through a separate thread. Because asynchronous tasks can be completed faster, multiple threads may be required to act as asynchronous processing threads at the same time. Here, use the thread pool to complete.
the working principle of threadpoolexecutor thread pool

The thread pool has three core keywords: the core thread, work queue, maximum thread, and saturation policy core pool corresponds to the value of the corepoolsize variable, if the running thread is less than corepoolsize regardless of whether there is currently an idle thread, Always creates a new thread to perform the task (this process requires a global lock) if the running thread is greater than corepoolsize, join the task blockingqueue– the corresponding work queue if Blockingqueue is full, and the current number of threads has not exceeded the maximumpoolsize-maximum thread. The thread pool continues to create threads. If the blockingqueue is full and the current number of threads exceeds maximumpoolsize, it is saturated according to the current thread pool. Processed according to the specified saturation policy. Throw exceptions, discard, and so on.

        Creates a blocking queue of sequential storage and specifies a size of
        blockingqueue<runnable> blockingqueue = new arrayblockingqueue<runnable> (m);
        To create a saturation strategy for the thread pool, AbortPolicy throws an exception
        Rejectedexecutionhandler handler = new Threadpoolexecutor.abortpolicy ();
        Create thread pool base size 5 Maximum number of threads 10 Threads max idle time 10 min
        threadpoolexecutor threadpoolexecutor = new Threadpoolexecutor (5, 10, 10, Timeunit.minutes, Blockingqueue,
                handler);

        Submit 5 job for
        (int i = 0; i < 5 i++) {
            final int nid = I;//nid is the unique identifier for each asynchronous task processing queue
            Threadpoolexecutor.exec Ute (new Runnable () {
                @Override public
                void Run () {
                    //constantly get "send registered SMS" and "Send registered mail" and so on from the database to complete
                }
            );
        }
3.3 Abstract interfaces and implementations of tasks

Whether it is "send a registered SMS" and "Send registered mail", all belong to the asynchronous processing task, need to abstract an interface, so that each asynchronous processing thread can be easily executed

An interface that requires asynchronous processing of tasks:

Job.java

public interface Job {
  /**
   * @params _id As mentioned earlier, each pending task is a record in MONGO, which is the MONGO primary key
   * param the parameters that the task requires
   *@ Return 8: Processing failed 9: Processing succeeded/public
  abstract int exec (string _id, string param);

Send a registered SMS task
Messagejob.java

public class Messagejob implements job{public
    int exec (string _id, string param) {
        try {
          //todo code to send SMS 
  
   return 9;
        } catch (Exception e) {return 8}}}


  

Send a registered SMS task
Emailjob.java

public class Emailjob implements job{public
    int exec (string _id, string param) {
        try {
          //todo code to send mail 
  
   return 9;
        } catch (Exception e) {return 8}}}


  
3.4 Implementation of asynchronous task processing threads

This is the core content, the code implementation is as follows:

The public class Jobtread {//Current job thread loads tasks from the database private list<taskqueue> prepareexectask; The number of tasks that the current line one thread is working on.
    This variable requires a period of time for each cell heartbeat to threadinstance, need volatile modifier private volatile int nowexectasks = 0; The number of processes processed during the last heartbeat to the current heartbeat to be used to record the total number of tasks processed by the current thread.
    This variable requires a period of time for each cell heartbeat to threadinstance, need volatile modifier private volatile int execedtasks = 0;
    @Autowired private threadinstanceservice processservice; private Timer timer = new timer ();
    @Autowired private Taskqueueservice Taskqueueservice;
    Private string[] Jobs = {"Com.mq.job.jobImpl.EmailJob", "Com.mq.job.jobImpl.MessageJob"};
    The unique identifier for the current asynchronous task processing private int nid;

    Save instances of all tasks (reflected according to the jobs classpath data) Private map<string, job> Jobmap;
        Public Jobtread () {} public jobtread (int nid) {this.nid = nid;
        This.jobmap = Getjobmap ();
    Openheartbeat (Thread.CurrentThread (). GetId ()); /** * reflects the execution instance from the classpath of all tasks and stores it in the Map, the key is the class name, and is also the name in the Taskqueue model/private map<string, Job> Getjobmap () {jobmap = new hashmap<string, job> (); According to the class name in jobs, reflect class, save the map for value based on class name Key,class (int i = 0; i < jobs.length; i++) {String CLA
            Sspath = Jobs[i];
                try {class<job> clazz = (class<job>) class.forname (ClassPath);
                String classname[] = Clazz.getname (). Split ("\;");
            Jobmap.put (Classname[classname.length-1], clazz.newinstance ());
            catch (Exception e) {//TODO auto-generated catch block E.printstacktrace ();
    } return Jobmap;
        /** * a task * @return */public int loadtask () {/) peak a certain number of tasks from the database in each thread loading process
        Prepareexectask = Taskqueueservice.peaks (nid);
        Nowexectasks= prepareexectask.size ();

        Prepareexectask.stream (). ForEach (It-> this.exec (it));
    return Prepareexectask.size (); /** * Use timer to openThe heartbeat of the current thread to the Taskqueue model. Each time the current line one thread the number of tasks processed and the number of tasks that have been processed into the database/private void openheartbeat (long threadId) {//Open a timed Tim Er.schedule (New TimerTask () {@Override public void run () {//taskprocess heartbeat, any time more
                The new current acquisition time and the number of tasks that are normally processed processservice.touch (NID, ThreadId, Nowexectasks, execedtasks);
            execedtasks = 0;
    }, 0, 1000*30);
        /** * Execute a task */public void exec (Taskqueue take) {if (take==null) {return;
        //Get the job instance (instance under Com.mq.job.jobImpl) Job jobinstance = Jobmap.get (Take.getname ());

        Job to perform task int result = Jobinstance.exec (take.get_id (), Take.getparam ()); Switch (Result) {case 8://Processing failed, set Task.status to 8 Taskqueueservice.execfail (take.get_id ())
            ;
        Break Case 9://successful processing, Task.status 9 Taskqueueservice.execsucceSS (take.get_id ());
        Break
        } nowexectasks--;

    execedtasks++;
 }
}

This class provides four methods that reflect the implementation of all specific task classes ("Send a registered SMS" and "send a registered message") based on the classpath, stored in the map. The key is the class name and the Taskqueue (name in the model that stores the asynchronous task) takes a number of tasks out of the taskqueue and maintains real-time maintenance of the number of tasks currently being processed and the number of tasks processed Updates the number of tasks currently being processed and the number of tasks processed to the specific task execution in the Threadinstance instance, taskqueue.name the task implementation from the map, and executes the Exec method, which indicates whether the task was successfully processed Four. Dao method of the Threadinstance model

Threadinstance is an instance of the asynchronous task processing thread, the task instance that is submitted to the thread pool. The unique identifier for the model is the ordinal ID at the time of creation, not the thread ID, because the thread ID may change when the reboot is considered. The serial number ID (NID) submitted to the thread pool is therefore used as a unique index.

The DAO method provides only a touch heartbeat method, which is timed in a timer to pass the number of tasks currently being processed and the total number of tasks processed after the last heartbeat

public class Threadinstanceservice {@Autowired private Threaddao Threaddao; /** * @params nid the current thread---the unique identifier of the asynchronous task processing queue * ThreadID thread ID * tasksize The number of tasks currently being processed * execedtasks the task to be processed from the last heartbeat Total */public void Touch (int nid,long threadId, int tasksize, int execedtasks) {threadinstance thread = t
        Hreaddao.findandupdatbynid (Nid,threadid, Tasksize, execedtasks);
        If the current thread has not yet been created in threadinstance, create if (thread = = null) {threaddao.create (nid, ThreadId);
    }} public class Threaddaoimpl implements threaddao{@Autowired private mongotemplate template; /** db.
         Threadinstance.update ({Nid:nid},{$set: {pid:threadid, taskcounts:tasksize, Update:new Date ()}, $inc: {execedtasks:execedtasks}}) */public THR Eadinstance Findandupdatbynid (Long nid, Long threadId, int tasksize, int execedtasks) {return teMplate.findandmodify (Query.query (Criteria.where ("Nid"). is (NID)), New Update (). Set ("pid", ThreadId). Set ("Update",
    New Date ()). Set ("Taskcounts", Tasksize). Inc ("Execedtasks", Execedtasks), Threadinstance.class); }/** db. Threadinstance.create ({nid:nid, Pid:threadid, taskcounts:0, execedtasks:0,}) * */@Override public void Create (int nid, Long threadId) {Template.insert (new threadinstance Nid,threadid,
    0,0));

 }

}
Five. Dao method of the Threadqueue model

Each record of the Threadqueue model is a task to be processed asynchronously, such as "Send a registered SMS" and "send a registered message".

As previously shown, Threadqueue mainly provides the following three methods: Get a certain number of task annotations from the thread pool The current task has been processed successfully callout current task processing failed

because a certain number of tasks from the thread pool are acquired by multiple threads at the same time, it is possible for multiple threads to acquire the same task. Believe that the reader also knows MONGO an operation is atomic because the findoneandupdate,{status:0,{$set: {status:1}}} can be used. This, of course, will not have multiple threads to obtain the same record to the situation, the author is the first time this implementation. But when you put it into a production environment, it's very, very inefficient to find that it only reads one record from the database at a time. A large number of tasks are blocked in the database.
Therefore, a one-time access to multiple records, but also to avoid duplication of acquisition methods. The Threadnid field in Tastqueue is born for this

public class Taskqueueservice {private Taskqueuedao Taskqueuedao = new Taskqueuedaoimpl ();  /** * Gets the highest probability in the secondary peak * returns to 1, 2,3,4,5 in sequence * @return/public int roll () {Double random =
        Math.random ();
            for (int i = 1; I <= 5; i++) {if (Random > 1/math.pow (2, i)) {return i;
    } return 1;
     /** * Get a certain number of task * * @param from the thread pool Threadnid * need to get threadnid of the task. Non-thread ID but as a unique index nid * @return list<taskqueue> */public list<taskqueue> peaks (int threadnid) {//Get task status as 0,1 (initialization, processing).
        Avoid loading into memory the task is lost due to reboot list<integer> statuses = new arraylist<integer> ();
        Statuses.add (0);

        Statuses.add (1);
        Gets the tag of the task null and the current thread's list<integer> tags = new arraylist<integer> ();
        Tags.add (NULL);

        Tags.add (Threadnid); Gets a priority int priority = THIs.roll ();
        Gets the unhandled and current line one thread in the processing of the taskqueue list<taskqueue> taskqueues = taskqueuedao.peaks (statuses, tags, priority); Take out all the _id. Multiple threads may acquire the same task, both unhandled list<string> ids = Taskqueues.stream (). Map (It-> it.get_id ()). Collect (COLLECTORS.T

        Olist ());
        You must also threadnid null for all unhandled or current line one thread all threads that are being processed are updated to the current thread's normal processing//here update condition except $in:ids. For a task, no matter how many threads get it.

        However, only one thread can update successfully "in process", that is, the status update is 1,threadnid update to the current thread nid int updatesize = taskqueuedao.execing (IDs, Threadnid);
        If the number of updates is 0, all tasks are all processed, returning an empty if (Updatesize = = 0) {return new arraylist<taskqueue> (); ///At this point, you are again taken out of all current thread processing tasks, that is, multiple threads will not return the same task to the superior list<integer> execstatuses = new arraylist<
        Integer> ();
        Execstatuses.add (1);
        list<integer> exectags = new arraylist<integer> ();

        Exectags.add (Threadnid); Return Taskqueuedao.peaks (execstatuses, Exectags, priority);
    //Update status to 9 public void execsuccess (String _id) {taskqueuedao.success (_id);
    //Update status to 8 public void Execfail (String _id) {taskqueuedao.fail (_id);
    } public class Taskqueuedaoimpl implements taskqueuedao{@Autowired private mongotemplate template; /** db. Taskqueue.find ({status: {$in: statuses}, Threadnid: {$in: Threadnids}, Priority:priority,}). Limit (**/@Override public list<taskqueue> peaks (list<integer> statuses, list<integer> thread Nids, int priority) {return Template.find (query.query criteria.where ("status"). In (statuses). and ("Threadnid"). In (t
    Hreadnids). and ("priority"). Is (priority)). limit, Taskqueue.class); }/** db. Taskqueue.update ({_id: _id},{$set: {status:9}}) **/@Override public void success (String _id) {Templ Ate.updatefirst (Query.query (Criteria.where ("_id"). is (_id)), Update.update ("status", 9), TAskqueue.class); }/** db. Taskqueue.update ({_id: _id},{$set: {status:8}}) **/@Override public void fail (String _id) {Template
    . Updatefirst (Query.query (Criteria.where ("_id"). is (_id)), Update.update ("status", 3), taskqueue.class); @Override public int execing (list<string> _ids, int threadnid) {Writeresult Writeresult = Templ Ate.updatemulti (Query.query (Criteria.where ("_id"). In (_ids). and ("Threadnid"). is (null), Update.update ("status", 1
        ). Set ("Threadtag", Threadnid), Taskqueue.class);
    return Writeresult.getn ();
 }
}
Six. Test

Run the following code to create 300 tasks in Taskqueue, then start the thread pool and submit 5 threads to handle the 300 tasks.

    public static void TestData () {

          new Thread (new Runnable () {

            @Override public
            void Run () {A for
                int i=0;i&l t;150;i++) {
                    getmongotemplate (). Save (new Taskqueue ("Emailjob", Uuid.randomuuid (). toString () + "@sina. com"));
                    Getmongotemplate (). Save (new Taskqueue ("Messagejob", "Random phone number");

                }}
        ). Start ();
    }
Submit 5 job for
        (int i = 0; i < 5; i++) {
            final int nid = i;
            Threadpoolexecutor.execute (New Runnable () {
                @Override public
                void Run () {
                    exec (nid);}}
            );

public static void exec (int nid) {
        jobtread thread = new Jobtread (nid);
        while (true) {
            int tasks = Thread.loadtask ();
            if (tasks = 0) {
                //Take a break a
                try {
                    thread.sleep (3000);
                } catch (Interruptedexception e) {
                    //TODO Au To-generated Catch block
                    e.printstacktrace ();}}}
    

5 records are generated in the Threadinstance model, and real-time updates the number of tasks that each thread normally handles and the total number of tasks processed. When the operation is complete, run the following statement, output 300:

 Db.getcollection (' Threadinstance '). Aggregate ([{$group: {_id: ' Execedtasks ', count:{$sum: ' $execedTasks '}}]) 
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.