jeroMq示例之[1] [req-broker-rep]

 

JEROMQ示例之 [REQ-BROKER-REP]

[req-broker-rep]模式的好处是对于多个客户端的C/S网络结构, broker会将多个任务挨个分给workers,这样多个客户端request可以并发的发送到broker。 worker并发的执行运算。中间的broker随时把worker结果回传给对应的client。
 
 

请求-代理-应答

下面让我们在请求-应答模式中编写一个小型的消息队列代理装置broker

在Hello World客户/服务模型中,一个客户端和一个服务端进行通信。但在真实环境中,我们会需要让多个客户端和多个服务端进行通信。关键问题在于,服务端应该是无状态的,所有的状态都应该包含在一次请求中,或者存放其它介质中,如数据库。

我们有两种方式来连接多个客户端和多个服务端。第一种是让客户端直接和多个服务端进行连接。客户端套接字可以连接至多个服务端套接字,它所发送的请求会通过负载均衡的方式分发给服务端。比如说,有一个客户端连接了三个服务端,A、B、C,客户端产生了R1、R2、R3、R4四个请求,那么,R1和R4会由服务A处理,R2由B处理,R3由C处理:

9

这种设计的好处在于可以方便地添加客户端,但若要添加服务端,那就得修改每个客户端的配置。如果你有100个客户端,需要添加三个服务端,那么这些客户端都需要重新进行配置,让其知道新服务端的存在。

这种方式肯定不是我们想要的。一个网络结构中如果有太多固化的模块就越不容易扩展。因此,我们需要有一个模块位于客户端和服务端之间,将所有的知识都汇聚到这个网络拓扑结构中。理想状态下,我们可以任意地增减客户端或是服务端,不需要更改任何组件的配置。

下面就让我们编写这样一个组件。这个代理会绑定到两个端点,前端端点供客户端连接,后端端点供服务端连接。它会使用zmq_poll()来轮询这两个套接字,接收消息并进行转发。装置中不会有队列的存在,因为ZMQ已经自动在套接字中完成了。

在使用REQ和REP套接字时,其请求-应答的会话是严格同步。客户端发送请求,服务端接收请求并发送应答,由客户端接收。如果客户端或服务端中的一个发生问题(如连续两次发送请求),程序就会报错。

但是,我们的代理装置必须要是非阻塞式的,虽然可以使用zmq_poll()同时处理两个套接字,但这里显然不能使用REP和REQ套接字。

幸运的是,我们有DEALER和ROUTER套接字可以胜任这项工作,进行非阻塞的消息收发。DEALER过去被称为XREQ,ROUTER被称为XREP,但新的代码中应尽量使用DEALER/ROUTER这种名称。在第三章中你会看到如何用DEALER和ROUTER套接字构建不同类型的请求-应答模式。

 

broker是如何知道每条request是哪个client发送的,每个reply又需要给哪个client???详见第二篇msg identity : JEROMQ示例之[2] [REQ-REP-ENVELOPES MSG IDENTITY]

 

 

下面就让我们看看DEALER和ROUTER套接字是怎样在装置中工作的。

下方的简图描述了一个请求-应答模式,REQ和ROUTER通信,DEALER再和REP通信。ROUTER和DEALER之间我们则需要进行消息转发:

10

请求-应答代理会将两个套接字分别绑定到前端和后端,供客户端和服务端套接字连接。在使用该装置之前,还需要对客户端和服务端的代码进行调整。
 

使用请求-应答代理可以让你的C/S网络结构更易于扩展:客户端不知道服务端的存在,服务端不知道客户端的存在。网络中唯一稳定的组件是中间的代理装置:

11

 

broker程序,or 代理程序,可以看到它是能够处理多帧消息的::

broker程序:

package guide;

import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Calendar;

import org.zeromq.ZMQ;
import org.zeromq.ZMQ.Context;
import org.zeromq.ZMQ.Poller;
import org.zeromq.ZMQ.Socket;

/**
* Simple request-reply broker
*
*/
public class rrbroker{

    public static void main (String[] args) {
        //  Prepare our context and sockets
        Context context = ZMQ.context(1);

        Socket frontend = context.socket(ZMQ.ROUTER);
        Socket backend  = context.socket(ZMQ.DEALER);
        frontend.bind("tcp://*:5559");
        backend.bind("tcp://*:5560");

        System.out.println("launch and connect broker.");

        //  Initialize poll set
        Poller items = new Poller (2);
        items.register(frontend, Poller.POLLIN);
        items.register(backend, Poller.POLLIN);

        boolean more = false;
        byte[] message;

        //  Switch messages between sockets
        while (!Thread.currentThread().isInterrupted()) {            
            //  poll and memorize multipart detection
            items.poll();

            if (items.pollin(0)) {
                while (true) {
                    // receive message多帧信息
                    message = frontend.recv(0);
                    more = frontend.hasReceiveMore();

                    // Broker it
                    backend.send(message, more ? ZMQ.SNDMORE : 0);
                    
                    String str_msg="";
                    if (message != null) {
                        str_msg= new String(message, Charset.forName("UTF-8"));
                    }
            //
            //打印多帧消息(详见下一篇msg identity),可以看出来第一帧是ID,第二帧是分隔符,第三帧才是数据 System.out.println(GetCurrtime()
+": routed a Req. msg["+str_msg+"] more ["+more+"]"); if(!more){ break; } } } if (items.pollin(1)) { while (true) { // receive message多帧 message = backend.recv(0); more = backend.hasReceiveMore(); // Broker it frontend.send(message, more ? ZMQ.SNDMORE : 0); System.out.println(GetCurrtime()+": got a Rep."); if(!more){ break; } } } } // We never get here but clean up anyhow frontend.close(); backend.close(); context.term(); } public static String GetCurrtime(){ Calendar cal = Calendar.getInstance(); cal.getTime(); SimpleDateFormat sdf = new SimpleDateFormat("mm:ss.SS"); return sdf.format(cal.getTime()); } } //以下是client程序: package guide; import org.zeromq.ZMQ; import org.zeromq.ZMQ.Context; import org.zeromq.ZMQ.Socket; /** * Hello World client * Connects REQ socket to tcp://localhost:5559 * Sends "Hello" to server, expects "World" back */ public class rrclient{ public static void main (String[] args) { Context context = ZMQ.context(1); // Socket to talk to server Socket requester = context.socket(ZMQ.REQ); requester.connect("tcp://localhost:5559"); System.out.println("launch and connect client."); for (int request_nbr = 0; request_nbr < 20; request_nbr++) { requester.send("Hello", 0); String reply = requester.recvStr (0); System.out.println("Received reply " + request_nbr + " [" + reply + "]"); } // We never get here but clean up anyhow requester.close(); context.term(); } }
//worker程序:
package guide; import org.zeromq.ZMQ; import org.zeromq.ZMQ.Context; import org.zeromq.ZMQ.Socket; // Hello World worker // Connects REP socket to tcp://*:5560 // Expects "Hello" from client, replies with "World" public class rrworker { public static void main (String[] args) throws Exception { Context context = ZMQ.context (1); // Socket to talk to server Socket responder = context.socket (ZMQ.REP); responder.connect ("tcp://localhost:5560"); while (!Thread.currentThread ().isInterrupted ()) { // Wait for next request from client String string = responder.recvStr (0); System.out.printf ("Received request: [%s]\n", string); // Do some 'work' Thread.sleep (1000); // Send reply back to client responder.send ("World"); } // We never get here but clean up anyhow responder.close(); context.term(); } }

 

posted @ 2014-09-14 18:48  ScottGu  阅读(2091)  评论(0编辑  收藏  举报