Introducing RABBITMQ into a project usually takes into account the benefits it brings: decoupling the application, implementing interoperability between different programming languages, releasing reliance on a particular communication protocol, and releasing the application's dependency on timing (asynchronous). The implementation to the code level is two common application patterns: "After the hair is forgotten" ( fire-and-forget) and RPC.
Fire-and-forget RabbitMQ solves the problem of interconnection between applications (connect) and size (scale), message sending and receiving is isolated, the sender does not know who is ultimately receiving the message, and the receiver does not have to care about who the message was sent from, and the sending and receiving is isolated , the message is inherently asynchronous. This isolation also decouples dependencies between applications. The role of RABBITMQ is the router in the middle of the application. This is a "post-and-forget" (Fire-and_forget) way to publish a message. RPC RPC requires two-way communication, or RPC server needs to know exactly who to send the message to. We can attach the endpoint information "to whom" in the payload data section. RABBITMQ provides a solution: There is a reply_to field on each AMQP message header. This allows the producer of the message to specify that the queue Name,rpc server accept the message check reply_to field, Create a message that contains response and put the queue name as routing key, and the client subscribing to the queue gets the message.
Here are two things to make sure:
- To create a random name for a queue
- Even if name is a random conflict, it is also necessary to guarantee the exclusivity of the message traffic.
See how RABBITMQ satisfies these two points:
- If you create a queue that does not specify queue NAME,RABBITMQ, a random name is created.
- Exclusive only requires exclusive parameters
All in all, what needs to be done is to create a Temporary,exclusive,anonymou queue for the client and set the queue name to the Reply_to field of the RPC message. Note here RPC The server already knows which queue to post to, so there's no need to specify exchange (we'll mention the difference between queue and exchange at the implementation level, and simply say that the queue will have a corresponding Erlang process. And Exchang just performs some pattern matching checks and does not correspond to process entities). See:
Our RPC would work like this:
- When the Client is starts up, it creates a anonymous exclusive callback queue.
- For a RPC request, the Client sends a message with the properties: ReplyTo, which are set to the callback queue and correlationid, which is set to a unique value for every request.
- The request is a sent to an rpc_queue queue.
- The RPC Worker (Aka:server) is a waiting for requests on this queue. When a request appears, it does the job and sends a message with the result of the to the Client, using the C4>replyto field.
- The client waits for data on the callback queue. When a-message appears, it checks the Correlationid property. If it matches the value from the request it returns the response to the application
The code for the RPC client side is as follows:
Importcom.rabbitmq.client.ConnectionFactory;Importcom.rabbitmq.client.Connection;ImportCom.rabbitmq.client.Channel;ImportCom.rabbitmq.client.QueueingConsumer;Importcom.rabbitmq.client.AMQP.BasicProperties;ImportJava.util.UUID; Public classrpcclient {PrivateConnection Connection; Privatechannel Channel; PrivateString requestqueuename = "Rpc_queue"; PrivateString Replyqueuename; PrivateQueueingconsumer Consumer; PublicRpcclient ()throwsException {connectionfactory Factory=NewConnectionFactory (); Factory.sethost ("LocalHost"); Connection=factory.newconnection (); Channel=Connection.createchannel (); Replyqueuename=Channel.queuedeclare (). Getqueue (); Consumer=NewQueueingconsumer (channel); Channel.basicconsume (Replyqueuename,true, consumer); } PublicString call (String message)throwsException {String response=NULL; String Corrid=Uuid.randomuuid (). toString (); Basicproperties Props=Newbasicproperties. Builder (). Correlationid (Corrid). ReplyTo (Replyqueuename) . build (); Channel.basicpublish ("", Requestqueuename, props, Message.getbytes ("UTF-8")); while(true) {queueingconsumer.delivery Delivery=Consumer.nextdelivery (); if(Delivery.getproperties (). Getcorrelationid (). Equals (Corrid)) {Response=NewString (Delivery.getbody (), "UTF-8"); Break; } } returnresponse; } Public voidClose ()throwsException {connection.close (); } Public Static voidMain (string[] argv) {rpcclient Fibonaccirpc=NULL; String Response=NULL; Try{Fibonaccirpc=Newrpcclient (); System.out.println ("[x] requesting FIB (30)"); Response= Fibonaccirpc.call ("30"); System.out.println (" [.] Got ' "+ Response +" ' "); } Catch(Exception e) {e.printstacktrace (); } finally { if(fibonaccirpc!=NULL) { Try{fibonaccirpc.close (); } Catch(Exception ignore) {}}} }}
View Code
The RPC sever port code is as follows:
Importcom.rabbitmq.client.ConnectionFactory;Importcom.rabbitmq.client.Connection;ImportCom.rabbitmq.client.Channel;ImportCom.rabbitmq.client.QueueingConsumer;Importcom.rabbitmq.client.AMQP.BasicProperties; Public classRpcserver {Private Static FinalString rpc_queue_name = "Rpc_queue"; //calculating the Fibonacci Pocena sequence Private Static intFibintN) {if(n = = 0) return0; if(n = = 1) return1; returnFIB (n-1) + fib (n-2); } Public Static voidMain (string[] argv) {Connection Connection=NULL; Channel Channel=NULL; Try{ConnectionFactory Factory=NewConnectionFactory (); Factory.sethost ("LocalHost"); Connection=factory.newconnection (); Channel=Connection.createchannel (); Channel.queuedeclare (Rpc_queue_name,false,false,false,NULL); Channel.basicqos (1); Queueingconsumer Consumer=NewQueueingconsumer (channel); Channel.basicconsume (Rpc_queue_name,false, consumer); System.out.println ("[x] awaiting RPC requests"); while(true) {String response=NULL; Queueingconsumer.delivery Delivery=Consumer.nextdelivery (); Basicproperties Props=delivery.getproperties (); Basicproperties Replyprops=NewBasicproperties.builder (). Correlationid (Props.getcorrelationid ()). build (); Try{String message=NewString (Delivery.getbody (), "UTF-8"); intn =integer.parseint (message); System.out.println (" [.] FIB ("+ Message +") "); Response= "" +fib (n); } Catch(Exception e) {System.out.println (" [.] " +e.tostring ()); Response= ""; } finally{channel.basicpublish ("", Props.getreplyto (), Replyprops, Response.getbytes ("UTF-8")); Channel.basicack (Delivery.getenvelope (). Getdeliverytag (),false); } } } Catch(Exception e) {e.printstacktrace (); } finally { if(Connection! =NULL) { Try{connection.close (); } Catch(Exception ignore) {}}} }}
View Code
Slightly different
The traditional RPC call client and server are tightly dependent, clients connect to the server, send a request, and then block waiting for the server to respond. This is done with the client and server side being aware of each other. If the RPC server crashes, the client needs to reconnect, If the server is completely blown up, look for a server that provides the same service, and the client will reconnect to the past.
Using RABBITMQ to implement RPC, still maintain the characteristics of client server information hiding, client relies on not a specific server but a specific message, in the case of multiple equivalent servers, Whether the state of a server is normal does not affect the state of the client.
Summing up, the use of RABBITMQ is the first RPC, objectively also achieve the following effect:
- Fault tolerance a server crash does not affect the Client
- Decoupling the dependency on specific communication protocols and interfaces, and unifying the AMQP messages.
- Load balancing between multiple RPC servers is done by RABBITMQ
rabbitmq4--after the post-forget and RPC