高性能高可用的服务端研究之Req-Rep模式(附源码)

最近在思考高可用的高性能的服务端架构,面临的主要问题如下:

  1. 服务端组件怎么做到可插拔?
  2. 消息怎么智能路由?一个服务端挂了不会影响整个应用,避免单点故障?
  3. 如何设计一套高性能的Req-Rep,Pub-Sub,Push-Poll模型;
  4. 如何做到跨语言,让各语言各尽其用,比如编写业务代码可用Java,编写网络通信压缩,加密解密,数据序列化等用C++,而且尽可能减少性能损失?

看来这并不是一件简单的事情,我们这篇博客仅仅思考Req-Rep模式的搭建,我们这里使用开源项目ZeroMQ:

ZeroMQ以嵌入式网络编程库的形式实现了一个并行开发框架(concurrencyframework),能够提供进程内(inproc)、进程间(IPC)、网络(TCP)和广播方式的消息信道,并支持扇出(fan-out)、发布--订阅(pub-sub)、任务分发(taskdistribution)、请求/响应(request-reply)等通信模式。ZeroMQ的性能足以用来构建集群产品,其异步I/O模型能够为多核消息系统提供足够的扩展性。ZeroMQ支持30多种语言的API,可以用于绝大多数操作系统。在提供这些优秀特性的同时,ZeroMQ是开源的,遵循LGPLv3许可。
ZeroMQ的明确目标是“成为标准网络协议栈的一部分,之后进入Linux内核”。

 

我们的目标是:

  1. 业务程序的Req-Server集群部署,从网关发来的请求处理(是C/C++编写的网关),程序自动找一台空闲的Req-Server来处理这个请求,并将结果异步分发到服务单网关,从而Push给客户端;
  2. 一套Req-Server挂了不会影响整个服务端程序运行;
  3. 当Req过载时,即发送的大量请求在服务端堆积时,服务端“热部署”挂一个新的Req-Server,而不需要重新启动服务端程序;

这看起来的一个简单的任务其实并不简单!

 

幸好,ZeroMQ已经帮我们处理了大部分工作,考虑到这个网站大多数是Javaer,所以这篇博文我们以Java代码举例,实际上,我们服务端网关使用C编写的。

 

最简单的请求/应答模式如下:

客户端:

 

ZMQ.Context context = ZMQ.context(1);
ZMQ.Socket requester = context.socket(ZMQ.REQ);
requester.connect("tcp://localhost:5559");

for (int requestNbr = 0; requestNbr != 100000; requestNbr++) {
	String request = "Hello"+requestNbr;
	System.out.println("Sending Hello " + requestNbr);
	requester.send(request.getBytes(), 0);
	byte[] reply = requester.recv(0);
	System.out.println("Received " + new String(reply) + " " + requestNbr);
}
requester.close();
context.term();
 服务端:

 

 

ZMQ.Context context = ZMQ.context(1);
ZMQ.Socket responder = context.socket(ZMQ.REP);
responder.bind("tcp://*:5559");
while (!Thread.currentThread().isInterrupted()) {
	byte[] request = responder.recv(0);
	System.out.println("Received from client req:"+new String(request));
	Thread.sleep(100);
	String reply = "World";
	responder.send(reply.getBytes(), 0);
}
responder.close();
context.term();
 这里有几个不足:

 

  1. 当请求方并发数达到一定量时,由于请求在服务端不丢失,对导致大量的请求在服务端堆积,造成server端内存溢出;
  2. 存在单点故障,当部署的响应程序crash时,请求就无法处理了。

这里我们改善一下,引入请求/响应的Broker,所有的请求经由Broker代理中转,请求方不需要知道有多少server在后台服务,有Broker去决策一个请求交由那个处理请求的server去处理并负责负载均衡,代码如下:

Broker Server:

 

System.out.println("req Server Broker start....");
ZMQ.Context context = ZMQ.context(1);
ZMQ.Socket frontEnd = context.socket(ZMQ.ROUTER);
frontEnd.bind("tcp://*:5559");
ZMQ.Socket backEnd = context.socket(ZMQ.DEALER);       
backEnd.bind("tcp://*:5560");
ZMQQueue q = new ZMQQueue(context,frontEnd,backEnd);
q.run();
frontEnd.close();
backEnd.close();
context.term();
 处理请求的Server(可以部署N个,N个Server热插拔):

 

 

ZMQ.Context context = ZMQ.context(1);
ZMQ.Socket responder = context.socket(ZMQ.REP);
String brokerIP = prop.getProperty("server_req_broker");
System.out.println("serverName:" + brokerIP);
responder.connect("tcp://"+brokerIP);
while (!Thread.currentThread().isInterrupted()) {
	byte[] request = responder.recv(0);
	Thread.sleep(100);
	String reply = "World";
	responder.send(reply.getBytes(), 0);
}
responder.close();
context.term();
先在某一台机器上启动Broker Server(runReqServerBroker.bat),先在三台机器上部署3个Req Server,然后先启动一个,运行runReqClient.bat,然后再在别的机器上启动另外两个Req Server(runReqServer.bat),我们可以看到,Client发送的所有请求被自动负载均衡到3台请求处理服务器上,并且他们是平均分配的(当然,你可以改变均衡策略,比如使用LRU,但zeroMQ默认是采用平均分配的算法)。

 

想一下,这个模型有什么优缺点?
  1. 单点故障只在Broker Server出现,如果Broker Server不挂,那么Req Server可以任意增减,对clients来说是透明的;
  2. Req Server处理请求的策略完全可以由你自己来定义(比如采用LRU,权重分配等);
  3. 和业务无关的Broker Server可以完全由更高效的语言去实现,比如用C(也是官方所有Demo采用的默认语言),请求服务器则可以采用Java实现,比如处理业务查询,业务处理等;

这篇请求/响应模式的博文就介绍到这里,也给自己留个脚印,源码见附件,下篇我们会介绍pub/sub模式。

你可能感兴趣的:(java,zeromq)