传统的服务器端为若干个客户端提供服务,一般需要开启多个服务器端进程。为了进一步提升服务器端的处理能力,可以如下图所示将服务解耦为两部分(adapter与workers),它们之间通过消息队列传输数据,其中workers处理具体业务,adapter负责接入请求以及反馈结果,具体包含下面两个工作。

1,将所有客户端的请求发送到消息队列(进而传给后台处理)

2,后台处理完毕后将结果返回响应队列,client adapter获取到结果后返回给相应客户端。

流程图如下:

我们选择用Jetty(8),redis以及php简单实现这个场景,主要用到Jetty的continuation机制和redis的list先进先出数据结构

接入服务器

A.1,先配置一个服务器如下,同时开启一个守护线程阻塞监听response queue(用到json lib库以及jedis库)(使用专用线程处理响应可以避免“惊群”现象,不影响业务线程)

package test;

import java.util.HashMap;
import java.util.List; import org.eclipse.jetty.continuation.Continuation;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.json.simple.*; import redis.clients.jedis.Jedis; public class PJetty{ public static HashMap<String,Continuation>globalMap = new HashMap<String,Continuation>(); // 用一个守护线程阻塞等待结果队列返回数据
public static class DaemonThread extends Thread{ private JSONObject obj = new JSONObject(); private Jedis jedis = new Jedis("127.0.0.1",6379);
private List<String> res; public void run(){ while(true){
// 阻塞等待响应队列
res = jedis.brpop(0, "response_queue"); // 获取结果信息
String response = res.get(1); obj=(JSONObject) JSONValue.parse(response);
String request_sid = obj.get("request_sid").toString();
String result = obj.get("results").toString(); if(request_sid == null){
continue;
} // 通过消息中的连接的sessonid获取到响应的continuation实例,然后设置结果信息再唤醒实例
Continuation con = globalMap.get(request_sid);
if(con == null){continue;}
globalMap.remove(request_sid); //客户端异常断开这里会抛错
try{
con.setAttribute("results", result);
con.resume();
} catch(Exception e){
continue;
}
} }
} public static void main(String[] args) throws Exception { //开启守护线程去阻塞等待响应结果队列,唤醒请求
DaemonThread dt = new DaemonThread();
dt.start(); //设置connectors
SelectChannelConnector connector1 = new SelectChannelConnector();
connector1.setPort(1987);
connector1.setThreadPool(new QueuedThreadPool(5)); Server server = new Server();
server.setConnectors(new Connector[]{connector1}); //使用servlet处理请求
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
context.addServlet(new ServletHolder(new NonBlockingServlet()), "/fetch");
server.setHandler(context); server.start();
server.join();
}
}

A.2,实现自定义的servlets接受前端client连接,将请求信息传入队列request queue

package test;

import java.io.IOException;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import org.eclipse.jetty.continuation.Continuation;
import org.eclipse.jetty.continuation.ContinuationSupport;
import org.json.simple.JSONObject; import redis.clients.jedis.Jedis; public class NonBlockingServlet extends HttpServlet { /**
* generated serialize number
*/
private static final long serialVersionUID = 3313258432391586994L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
{
// 用sleeptime来模拟后台工作量
String sleepTime = request.getParameter("st");
if(sleepTime == null){
sleepTime = "0";
} // 查看结果队列是否返回此连接的请求结果
Object results = request.getAttribute("results");
if (results==null) // 如果异步处理尚未返回结果
{
Continuation continuation = ContinuationSupport.getContinuation(request); if(continuation.isInitial()){
// 设置连接超时时间
continuation.setTimeout(10000);
response.setContentType("text/plain");
response.getWriter().flush(); HttpSession session=request.getSession();
String sid = session.getId(); Jedis jedis = new Jedis("127.0.0.1",6379);
//将请求连接sessionid以及请求内容encode后传到处理队列中
JSONObject obj=new JSONObject();
obj.put("request_sid",sid);
obj.put("params",sleepTime); jedis.lpush("request_queue", obj.toJSONString()); //将连接和continuation实例做个映射关系存到全局hashmap中,不确定这里是否应该加锁
PJetty.globalMap.put(sid, continuation);
} // 判断是否超时
if (continuation.isExpired())
{
// 返回超时Response
response.getWriter().println("timeout");
response.getWriter().flush();
return;
} // 挂起HTTP连接
continuation.suspend(); return; // or continuation.undispatch();
} // 连接恢复后返回结果
response.getWriter().println("Got Result:\t" + results);
response.getWriter().flush();
}
}

业务服务器

B,实现后端worker.php(可以自定义worker进程数,我这里设置为5个php进程,进程数多能获取更好的并发)(用到predis库)

#!/root/bin/php
<?php require_once("lib/Predis/Autoloader.php"); function worker_thread()
{
Predis\Autoloader::register();
$redis = new Predis\Client('tcp://127.0.0.1:6379'); while(true){
try{
$request = $redis->brpop("request_queue", 0);
} catch(Exception $e){
continue;
}
/** demo
array(2) {
[0]=>
string(13) "request_queue"
[1]=>
string(55) "{"request_sid":"q0muxazo8k1h1k3uw85wuayh","params":"4"}"
}
*/
$request = json_decode($request[1], true);
// sleep represents work loads
sleep(intval($request["params"]));
$results = array("request_sid"=>$request["request_sid"], "results"=>$request["params"]);
$response = json_encode($results);
$redis->lpush("response_queue",$response);
}
} //开启多个worker进程提供服务
for ($worker_nbr = 0; $worker_nbr < 5; $worker_nbr++) {
$pid = pcntl_fork();
if ($pid == 0) {
worker_thread(); return;
}
} ?>

运行结果如下

这只是一个简单的demo,为了防止redis,workers进程挂掉或者客户端异常断开,还需要做些异常处理,比如设置请求超时,捕获一些空指针等,超时需要将continuation从globalMap中剔除,防止内存得不到释放。

root # for((i=10;i>=1;i--)) ; do lynx -dump http://127.0.0.1:1987/fetch?st=$i & done
[] 14112
[] 14113
[] 14114
[] 14115
[] 14116
[] 14117
[] 14118
[] 14119
[] 14120
[] 14121
root # Got Result: 3
Got Result: 4
Got Result: 2
Got Result: 7
Got Result: 1
Got Result: 9
Got Result: 6
timeout
timeout
timeout [] Done lynx -dump http://127.0.0.1:1987/fetch?st=$i
[] Done lynx -dump http://127.0.0.1:1987/fetch?st=$i
[] Done lynx -dump http://127.0.0.1:1987/fetch?st=$i
[] Done lynx -dump http://127.0.0.1:1987/fetch?st=$i
[] Done lynx -dump http://127.0.0.1:1987/fetch?st=$i
[] Done lynx -dump http://127.0.0.1:1987/fetch?st=$i
[] Done lynx -dump http://127.0.0.1:1987/fetch?st=$i
[] Done lynx -dump http://127.0.0.1:1987/fetch?st=$i
[]- Done lynx -dump http://127.0.0.1:1987/fetch?st=$i
[]+ Done lynx -dump http://127.0.0.1:1987/fetch?st=$i

redis数据库中存储的内容如下,可以看出虽然经后台处理后顺序变化了,但是对应关系正确,接入服务器能够根据request_sid把结果返回给相应的用户:

redis 127.0.0.1:6379> lrange request_queue 0 15
1) "{\"request_sid\":\"igiwkwnb715aphw8uvtfa6rj\",\"params\":\"3\"}"
2) "{\"request_sid\":\"wsrglxa3h6ef19ik5i0nbiiys\",\"params\":\"2\"}"
3) "{\"request_sid\":\"tyiqoj6awj5t16ddpqusftwc8\",\"params\":\"6\"}"
4) "{\"request_sid\":\"1052tgkiyy7c31bmxjtbom7ca\",\"params\":\"5\"}"
5) "{\"request_sid\":\"17jo1xwnnkd3h1mhcqcfplrl5k\",\"params\":\"8\"}"
6) "{\"request_sid\":\"1xk521sq6vmmf6enxauwzduj9\",\"params\":\"4\"}"
7) "{\"request_sid\":\"1cxnir1slgjiq1o2n3xwznh0kk\",\"params\":\"9\"}"
8) "{\"request_sid\":\"961vf8hao3stsv4vt1qif3ws\",\"params\":\"7\"}"
9) "{\"request_sid\":\"35pfn5au6p8qdbri17p636si\",\"params\":\"10\"}"
10) "{\"request_sid\":\"1ca4wy8qsfr7av0hwk8xtlqhp\",\"params\":\"1\"}" redis 127.0.0.1:6379> lrange response_queue 0 15
1) "{\"request_sid\":\"tyiqoj6awj5t16ddpqusftwc8\",\"results\":\"6\"}"
2) "{\"request_sid\":\"igiwkwnb715aphw8uvtfa6rj\",\"results\":\"3\"}"
3) "{\"request_sid\":\"wsrglxa3h6ef19ik5i0nbiiys\",\"results\":\"2\"}"
4) "{\"request_sid\":\"35pfn5au6p8qdbri17p636si\",\"results\":\"10\"}"
5) "{\"request_sid\":\"1052tgkiyy7c31bmxjtbom7ca\",\"results\":\"5\"}"
6) "{\"request_sid\":\"1cxnir1slgjiq1o2n3xwznh0kk\",\"results\":\"9\"}"
7) "{\"request_sid\":\"17jo1xwnnkd3h1mhcqcfplrl5k\",\"results\":\"8\"}"
8) "{\"request_sid\":\"961vf8hao3stsv4vt1qif3ws\",\"results\":\"7\"}"
9) "{\"request_sid\":\"1xk521sq6vmmf6enxauwzduj9\",\"results\":\"4\"}"
10) "{\"request_sid\":\"1ca4wy8qsfr7av0hwk8xtlqhp\",\"results\":\"1\"}"

用Jetty和redis实现接入服务器adapter的更多相关文章

  1. jetty 最后版本类库树, 基本上大多数应用都够了

    d:\jetty-distribution-8.1.17.v20150415\lib\annotations\javax.annotation-1.1.0.v201108011116.jarjavax ...

  2. vue,vuex的后台管理项目架子structure-admin,后端服务nodejs

    之前写过一篇vue初始化项目,构建vuex的后台管理项目架子,这个structure-admin-web所拥有的功能 接下来,针对structure-admin-web的不足,进行了补充,开发了具有登 ...

  3. Spring框架之websocket源码完全解析

    Spring框架之websocket源码完全解析 Spring框架从4.0版开始支持WebSocket,先简单介绍WebSocket协议(详细介绍参见"WebSocket协议中文版" ...

  4. idou老师教你学istio30:Mixer Redis Quota Adapter 实现和机制

    1. 配置 1.1参数 1.2 Params.Quota 1.3Params.Override 1.4Params.QuotaAlgorithm 速率限制的算法: Fixed Window 算法每个时 ...

  5. Codeigniter的Redis使用

    1. ./config/redis.php: <?php $config['redis_host'] = '127.0.0.1'; $config['redis_port'] = '6379'; ...

  6. spring mvc redis消息队列

    通常情况下,为了提高系统开发的灵活性和可维护度,我们会采用消息队列队系统进行解耦.下面是一个采用spring redis实现的消息队列实例,但此实例会由于网络延迟和阻塞等情况导致消息处理的延时,因而不 ...

  7. Jetty使用教程(四:28-30)—Jetty开发指南

    二十八.延续机制支持 28.1 延续简介 延续是一种机制用来实现类似于Servlet 3.0异步功能的异步Servlet,但提供了一个简单易操作的接口. 28.1.1 为什么使用异步Servlets ...

  8. Jetty使用教程(四:21-22)—Jetty开发指南

    二十一.嵌入式开发 21.1 Jetty嵌入式开发HelloWorld 本章节将提供一些教程,通过Jetty API快速开发嵌入式代码 21.1.1 下载Jetty的jar包 Jetty目前已经把所有 ...

  9. springmvc+spring-security+mybatis +redis +solar框架抽取

    参考文章:Spring MVC 3 深入总结: 第二章 Spring MVC入门 —— 跟开涛学SpringMVC 参考博客:http://www.cnblogs.com/liukemng/categ ...

随机推荐

  1. 微型 ORM 的第二篇 DapperLambda性能测试[Dapper比较篇]

    由于这周比较忙,所以本来想做的性能测试,一直没时间,想想还是今天给补上吧 由于很多人都担心性能问题,封装之后跟Dapper的性能差距是多少,今天我给出我的测试方法,仅供参考. 创建IDbConnect ...

  2. 基于maven进行spring 和mybatis的整合(Myeclpise)

    学习日记:基于maven进行spring和mybatis的整合,进行分页查询 什么是maven:maven是一个项目管理工具,使用maven可以自动管理java项目的整个生命周期,包括编译.构建.测试 ...

  3. EL表达式中引用隐式变量

    除了在jsp中9大隐式变量(在前面文章也叫预定义变量)在转化成为servlet后_jspService中可以看到: public void _jspService(final javax.servle ...

  4. ThinkPHP第二十四天(JQuery常用方法、TP自动验证)

    ---恢复内容开始--- 1.JQuery常用方法 A:JS中可以用json格式数据当做数组使用,如var validate={username:false,pwd:false,pwded:false ...

  5. Lavarel(-) windows 部署

    使用 Lavarel 开发完全可以抛开wamp,phpstudy等一键环境.因为lavarel 内置的artisan 整合了php5.4 内置的webserver ,甚至可以使用artisan 命令指 ...

  6. JavaScript 鸭子模型

    Javascript:由 “鸭子类型” 得出来的推论 背景 学动态语言的都知道一句话:“如果它走起来像鸭子,而且叫起来像鸭子,那么它就是鸭子”,Javascript也支持鸭子类型,下文就说说鸭子类型在 ...

  7. 剑指offer——从尾到头打印链表节点的值

    输入一个链表,从尾到头打印链表每个节点的值. 输入描述:输入为链表的表头 输出描述:输出为需要打印的“新链表”的表头 一.问题分析 初拿到这个题目时,这应该是考察单向链表这一数据结构.单向链表的遍历总 ...

  8. MySQL show binglog event in 'log_name'

    二进制日志文件记录的内容:记录表的更改. 二进制日志文件记录的形式:基于语句的复制.基于行的复制. 两种记录形式的优点与不足: 基于语句的复制-->它不能保证复制的正确性.如随机函数可能在两台机 ...

  9. td之overflow:hidden 多余文本隐藏效果

    td之overflow:hidden 多余文本隐藏效果 方法1: table-layout: fixed; width: 200px; 语法: table-layout : auto | fixed ...

  10. 美国vps哪个比较好,vps国内访问速度最快!

    沃网中国是一家成立于2008年的国内idc商,提供基于hyper-v架构的VPS产品,数据中心包括国内电信.美国洛杉矶等,他们采用的是国内访问最快的加州机房ping值,160-180左右相当给力的速度 ...