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

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

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

流程图如下:

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

接入服务器

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

  1. package test;
  2.  
  3. import java.util.HashMap;
  4. import java.util.List;
  5.  
  6. import org.eclipse.jetty.continuation.Continuation;
  7. import org.eclipse.jetty.server.*;
  8. import org.eclipse.jetty.server.nio.SelectChannelConnector;
  9. import org.eclipse.jetty.servlet.ServletContextHandler;
  10. import org.eclipse.jetty.servlet.ServletHolder;
  11. import org.eclipse.jetty.util.thread.QueuedThreadPool;
  12.  
  13. import org.json.simple.*;
  14.  
  15. import redis.clients.jedis.Jedis;
  16.  
  17. public class PJetty{
  18.  
  19. public static HashMap<String,Continuation>globalMap = new HashMap<String,Continuation>();
  20.  
  21. // 用一个守护线程阻塞等待结果队列返回数据
  22. public static class DaemonThread extends Thread{
  23.  
  24. private JSONObject obj = new JSONObject();
  25.  
  26. private Jedis jedis = new Jedis("127.0.0.1",6379);
  27. private List<String> res;
  28.  
  29. public void run(){
  30.  
  31. while(true){
  32. // 阻塞等待响应队列
  33. res = jedis.brpop(0, "response_queue");
  34.  
  35. // 获取结果信息
  36. String response = res.get(1);
  37.  
  38. obj=(JSONObject) JSONValue.parse(response);
  39. String request_sid = obj.get("request_sid").toString();
  40. String result = obj.get("results").toString();
  41.  
  42. if(request_sid == null){
  43. continue;
  44. }
  45.  
  46. // 通过消息中的连接的sessonid获取到响应的continuation实例,然后设置结果信息再唤醒实例
  47. Continuation con = globalMap.get(request_sid);
  48. if(con == null){continue;}
  49. globalMap.remove(request_sid);
  50.  
  51. //客户端异常断开这里会抛错
  52. try{
  53. con.setAttribute("results", result);
  54. con.resume();
  55. } catch(Exception e){
  56. continue;
  57. }
  58. }
  59.  
  60. }
  61. }
  62.  
  63. public static void main(String[] args) throws Exception {
  64.  
  65. //开启守护线程去阻塞等待响应结果队列,唤醒请求
  66. DaemonThread dt = new DaemonThread();
  67. dt.start();
  68.  
  69. //设置connectors
  70. SelectChannelConnector connector1 = new SelectChannelConnector();
  71. connector1.setPort(1987);
  72. connector1.setThreadPool(new QueuedThreadPool(5));
  73.  
  74. Server server = new Server();
  75. server.setConnectors(new Connector[]{connector1});
  76.  
  77. //使用servlet处理请求
  78. ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
  79. context.setContextPath("/");
  80. context.addServlet(new ServletHolder(new NonBlockingServlet()), "/fetch");
  81. server.setHandler(context);
  82.  
  83. server.start();
  84. server.join();
  85. }
  86. }

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

  1. package test;
  2.  
  3. import java.io.IOException;
  4.  
  5. import javax.servlet.http.HttpServlet;
  6. import javax.servlet.http.HttpServletRequest;
  7. import javax.servlet.http.HttpServletResponse;
  8. import javax.servlet.http.HttpSession;
  9.  
  10. import org.eclipse.jetty.continuation.Continuation;
  11. import org.eclipse.jetty.continuation.ContinuationSupport;
  12. import org.json.simple.JSONObject;
  13.  
  14. import redis.clients.jedis.Jedis;
  15.  
  16. public class NonBlockingServlet extends HttpServlet {
  17.  
  18. /**
  19. * generated serialize number
  20. */
  21. private static final long serialVersionUID = 3313258432391586994L;
  22.  
  23. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
  24. {
  25. // 用sleeptime来模拟后台工作量
  26. String sleepTime = request.getParameter("st");
  27. if(sleepTime == null){
  28. sleepTime = "0";
  29. }
  30.  
  31. // 查看结果队列是否返回此连接的请求结果
  32. Object results = request.getAttribute("results");
  33. if (results==null) // 如果异步处理尚未返回结果
  34. {
  35. Continuation continuation = ContinuationSupport.getContinuation(request);
  36.  
  37. if(continuation.isInitial()){
  38. // 设置连接超时时间
  39. continuation.setTimeout(10000);
  40. response.setContentType("text/plain");
  41. response.getWriter().flush();
  42.  
  43. HttpSession session=request.getSession();
  44. String sid = session.getId();
  45.  
  46. Jedis jedis = new Jedis("127.0.0.1",6379);
  47. //将请求连接sessionid以及请求内容encode后传到处理队列中
  48. JSONObject obj=new JSONObject();
  49. obj.put("request_sid",sid);
  50. obj.put("params",sleepTime);
  51.  
  52. jedis.lpush("request_queue", obj.toJSONString());
  53.  
  54. //将连接和continuation实例做个映射关系存到全局hashmap中,不确定这里是否应该加锁
  55. PJetty.globalMap.put(sid, continuation);
  56. }
  57.  
  58. // 判断是否超时
  59. if (continuation.isExpired())
  60. {
  61. // 返回超时Response
  62. response.getWriter().println("timeout");
  63. response.getWriter().flush();
  64. return;
  65. }
  66.  
  67. // 挂起HTTP连接
  68. continuation.suspend();
  69.  
  70. return; // or continuation.undispatch();
  71. }
  72.  
  73. // 连接恢复后返回结果
  74. response.getWriter().println("Got Result:\t" + results);
  75. response.getWriter().flush();
  76. }
  77. }

业务服务器

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

  1. #!/root/bin/php
  2. <?php
  3.  
  4. require_once("lib/Predis/Autoloader.php");
  5.  
  6. function worker_thread()
  7. {
  8. Predis\Autoloader::register();
  9. $redis = new Predis\Client('tcp://127.0.0.1:6379');
  10.  
  11. while(true){
  12. try{
  13. $request = $redis->brpop("request_queue", 0);
  14. } catch(Exception $e){
  15. continue;
  16. }
  17. /** demo
  18. array(2) {
  19. [0]=>
  20. string(13) "request_queue"
  21. [1]=>
  22. string(55) "{"request_sid":"q0muxazo8k1h1k3uw85wuayh","params":"4"}"
  23. }
  24. */
  25. $request = json_decode($request[1], true);
  26. // sleep represents work loads
  27. sleep(intval($request["params"]));
  28. $results = array("request_sid"=>$request["request_sid"], "results"=>$request["params"]);
  29. $response = json_encode($results);
  30. $redis->lpush("response_queue",$response);
  31. }
  32. }
  33.  
  34. //开启多个worker进程提供服务
  35. for ($worker_nbr = 0; $worker_nbr < 5; $worker_nbr++) {
  36. $pid = pcntl_fork();
  37. if ($pid == 0) {
  38. worker_thread();
  39.  
  40. return;
  41. }
  42. }
  43.  
  44. ?>

运行结果如下

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

  1. root # for((i=10;i>=1;i--)) ; do lynx -dump http://127.0.0.1:1987/fetch?st=$i & done
  2. [] 14112
  3. [] 14113
  4. [] 14114
  5. [] 14115
  6. [] 14116
  7. [] 14117
  8. [] 14118
  9. [] 14119
  10. [] 14120
  11. [] 14121
  12. root # Got Result: 3
  13. Got Result: 4
  14. Got Result: 2
  15. Got Result: 7
  16. Got Result: 1
  17. Got Result: 9
  18. Got Result: 6
  19. timeout
  20. timeout
  21. timeout
  22.  
  23. [] Done lynx -dump http://127.0.0.1:1987/fetch?st=$i
  24. [] Done lynx -dump http://127.0.0.1:1987/fetch?st=$i
  25. [] Done lynx -dump http://127.0.0.1:1987/fetch?st=$i
  26. [] Done lynx -dump http://127.0.0.1:1987/fetch?st=$i
  27. [] Done lynx -dump http://127.0.0.1:1987/fetch?st=$i
  28. [] Done lynx -dump http://127.0.0.1:1987/fetch?st=$i
  29. [] Done lynx -dump http://127.0.0.1:1987/fetch?st=$i
  30. [] Done lynx -dump http://127.0.0.1:1987/fetch?st=$i
  31. []- Done lynx -dump http://127.0.0.1:1987/fetch?st=$i
  32. []+ Done lynx -dump http://127.0.0.1:1987/fetch?st=$i

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

  1. redis 127.0.0.1:6379> lrange request_queue 0 15
  2. 1) "{\"request_sid\":\"igiwkwnb715aphw8uvtfa6rj\",\"params\":\"3\"}"
  3. 2) "{\"request_sid\":\"wsrglxa3h6ef19ik5i0nbiiys\",\"params\":\"2\"}"
  4. 3) "{\"request_sid\":\"tyiqoj6awj5t16ddpqusftwc8\",\"params\":\"6\"}"
  5. 4) "{\"request_sid\":\"1052tgkiyy7c31bmxjtbom7ca\",\"params\":\"5\"}"
  6. 5) "{\"request_sid\":\"17jo1xwnnkd3h1mhcqcfplrl5k\",\"params\":\"8\"}"
  7. 6) "{\"request_sid\":\"1xk521sq6vmmf6enxauwzduj9\",\"params\":\"4\"}"
  8. 7) "{\"request_sid\":\"1cxnir1slgjiq1o2n3xwznh0kk\",\"params\":\"9\"}"
  9. 8) "{\"request_sid\":\"961vf8hao3stsv4vt1qif3ws\",\"params\":\"7\"}"
  10. 9) "{\"request_sid\":\"35pfn5au6p8qdbri17p636si\",\"params\":\"10\"}"
  11. 10) "{\"request_sid\":\"1ca4wy8qsfr7av0hwk8xtlqhp\",\"params\":\"1\"}"
  12.  
  13. redis 127.0.0.1:6379> lrange response_queue 0 15
  14. 1) "{\"request_sid\":\"tyiqoj6awj5t16ddpqusftwc8\",\"results\":\"6\"}"
  15. 2) "{\"request_sid\":\"igiwkwnb715aphw8uvtfa6rj\",\"results\":\"3\"}"
  16. 3) "{\"request_sid\":\"wsrglxa3h6ef19ik5i0nbiiys\",\"results\":\"2\"}"
  17. 4) "{\"request_sid\":\"35pfn5au6p8qdbri17p636si\",\"results\":\"10\"}"
  18. 5) "{\"request_sid\":\"1052tgkiyy7c31bmxjtbom7ca\",\"results\":\"5\"}"
  19. 6) "{\"request_sid\":\"1cxnir1slgjiq1o2n3xwznh0kk\",\"results\":\"9\"}"
  20. 7) "{\"request_sid\":\"17jo1xwnnkd3h1mhcqcfplrl5k\",\"results\":\"8\"}"
  21. 8) "{\"request_sid\":\"961vf8hao3stsv4vt1qif3ws\",\"results\":\"7\"}"
  22. 9) "{\"request_sid\":\"1xk521sq6vmmf6enxauwzduj9\",\"results\":\"4\"}"
  23. 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. hdu 4455 Substrings(找规律&DP)

    Substrings Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Tota ...

  2. Android 实现GIF播放(解码)

    实现原理很简单,先把GIF动画解码成多张Bitmap图片,然后放到AnimationDrawable里面去逐一播放即可. GifHelper代码: package com.android.view; ...

  3. ExtJS 修改load paging时的参数

    ExtJS 的pagingToolbar 在翻页的时候传入的参数是固定的  分别是start 和 limit(其中limit的值就是store.pageSize的值) 如何在每次翻页的时候传入自己的参 ...

  4. oracle服务器和客户端字符集的查看和修改

    一.什么是oracle字符集 Oracle字符集是一个字节数据的解释的符号集合,有大小之分,有相互的包容关系.ORACLE 支持国家语言的体系结构允许你使用本地化语言来存储,处理,检索数据.它使数据库 ...

  5. 阿里云ECS每天一件事D5:安装php5.4.34

    原本是想把php和nginx合在一起来说的,不过考虑后,还是分开来做吧,已熟悉的更透彻一些. 1.准备类库 yum install autoconf automake libtool re2c fle ...

  6. cocos2dx CCEditBox

    CCTextFieldTTF是一个简单的封装,用起来不是那么便利,在cocos2dx的extension里,对输入框有更加简单使用的类,那就是CCEditBox 上代码先: bool EditBox0 ...

  7. apache 支持 php

    找到 httpd 的配置文件:一般在 /etc/httpd/conf 编辑:vi httpd.conf 配置 httpd.conf 让apache支持PHP: # vi /usr/local/apac ...

  8. hibernate-search-5.1.1简易使用

    系统要求java7和hibernate-core-4.3.8,此外还依赖如下jar包 使用demo如下: package com.ciaos.controller; import java.io.IO ...

  9. 物理DG主备库切换时遇到ORA-16139: media recovery required错误

    在物理DG主备库切换时遇到ORA-16139: media recovery required错误 SQL> ALTER DATABASE COMMIT TO SWITCHOVER TO PRI ...

  10. Clementine 12.0 的使用(因为比较少用,项目中用到才开始接触写一下自己的使用方法)

    首先我是根据excel的文件做的训练,就以excel来做介绍 1.打开Clementine 12.0 软件 点击软件下方的 ”源“ 即你要做训练的数据源.因为是excel文件双击excel. 2.双击 ...