1. 程式人生 > >jetty continuation基本原理及實現

jetty continuation基本原理及實現

背景

在io密集型的web 應用,如何能更好地提升後臺效能,jetty continuation是一個選擇

現在特別流行的說法就是事件驅動,看看node.js以及redis, 

jetty continuation也不例外

一個例子

package org.kaka.web;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.continuation.Continuation;
import org.eclipse.jetty.continuation.ContinuationSupport;

public class SimpleSuspendResumeServlet extends HttpServlet {

	/**
	 * 
	 */
	private static final long serialVersionUID = 6112996063962978130L;

	private MyAsyncHandler myAsyncHandler;

	public void init() throws ServletException {

		myAsyncHandler = new MyAsyncHandler() {
			public void register(final MyHandler myHandler) {
				new Thread(new Runnable() {
					public void run() {
						try {
							Thread.sleep(10000);
							myHandler.onMyEvent("complete!");
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}

					}
				}).start();
			}
		};

	}

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// if we need to get asynchronous results
		//Object results = request.getAttribute("results");
		final PrintWriter writer = response.getWriter();
		final Continuation continuation = ContinuationSupport
				.getContinuation(request);
		//if (results == null) {
		if (continuation.isInitial()) {
			
			//request.setAttribute("results","null");
			sendMyFirstResponse(response);
			// suspend the request
			continuation.suspend(); // always suspend before registration

			// register with async service. The code here will depend on the
			// the service used (see Jetty HttpClient for example)
			myAsyncHandler.register(new MyHandler() {
				public void onMyEvent(Object result) {
					continuation.setAttribute("results", result);
					
					continuation.resume();
				}
			});
			return; // or continuation.undispatch();
		}

		if (continuation.isExpired()) {
			sendMyTimeoutResponse(response);
			return;
		}
		 //Send the results
		Object results = request.getAttribute("results");
		if(results==null){
			response.getWriter().write("why reach here??");
			continuation.resume();
			return;
		}
		sendMyResultResponse(response, results);
	}

	private interface MyAsyncHandler {
		public void register(MyHandler myHandler);
	}

	private interface MyHandler {
		public void onMyEvent(Object result);
	}
	
	private void sendMyFirstResponse(HttpServletResponse response) throws IOException {
		//必須加上這一行,否者flush也沒用,為什麼?
		response.setContentType("text/html");
		response.getWriter().write("start");
		response.getWriter().flush();

	}

	private void sendMyResultResponse(HttpServletResponse response,
			Object results) throws IOException {
		//response.setContentType("text/html");
		response.getWriter().write("results:" + results);
		response.getWriter().flush();

	}

	private void sendMyTimeoutResponse(HttpServletResponse response)
			throws IOException {
		response.getWriter().write("timeout");

	}

}


這個例子到底幹了什麼?

出於演示的目的,這個例子格外簡單。模擬了在web請求中如何處理一個耗時阻塞10s的io操作

  • 常規的程式遇到這種情況(假設沒有設定超時),通常會等待10s,執行緒會白白浪費在哪,一旦請求一多,執行緒池及等待佇列會滿掉,從而導致網站無法服務
  • 稍微好點的程式恐怕會使用futuretask來解決問題,但無論怎樣,futuretask帶有輪詢的性質,或多或少會帶有阻塞
  • jetty continuation更進了一步, 採用事件驅動的方式來通知請求完成,絲毫不浪費一點io時間,一旦遇到阻塞,當前worker執行緒會結束,這樣就可以服務其他請求,等耗時操作處理完畢通知jetty,此時jetty會再動用一個新的worker執行緒再次處理請求

常見流程

  • 具體流程1,耗時操作在AsyncContinuation預設超時(30s,可以設定)之內完成
  1. 當請求到來,被selector執行緒感知
  •  selector執行緒,會例項化org.eclipse.jetty.server.Request以及org.eclipse.jetty.server.AsyncContinuation
  • AsyncContinuation例項此時狀態為__IDLE
worker執行緒A會獲取到selector執行緒派發的這個請求
  • 流程進入org.eclipse.jetty.server.AsyncHttpConnection(AbstractHttpConnection).handleRequest()
  • 呼叫_request._async.handling(),AsyncContinuation例項的狀態__IDLE-->__DISPATCHED
  • 呼叫真正的業務邏輯server.handle(this);業務邏輯中呼叫AsyncContinuation.suspend(ServletContext, ServletRequest, ServletResponse) ,會將AsyncContinuation例項的狀態__DISPATCHED-->__ASYNCSTARTED
  • 最後呼叫_request._async.unhandle(),AsyncContinuation例項的狀態__ASYNCSTARTED-->__ASYNCWAIT
    • 會將suspend的那個請求任務放入selector執行緒的超時佇列(和redis 事件框架的做法非常類似)
  • 為了簡單期間,假設在上訴過程中,耗時操作還沒完成,此時worker執行緒A被回收將會服務其他請求,雖然當前請求並未完成
匿名應用執行緒(在jetty woker執行緒另外啟動的執行緒)完成耗時操作
  • 呼叫AsyncContinuation.resume() ,此時AsyncContinuation例項的狀態__ASYNCWAIT-->__REDISPATCH
  • 然後會往worker執行緒佇列或者selector執行緒佇列中新增一個請求task,以此來觸發jetty再次完成未完成的請求
worker執行緒B再次處理那個請求
  • 流程進入org.eclipse.jetty.server.AsyncHttpConnection(AbstractHttpConnection).handleRequest()
  • 呼叫_request._async.handling(),AsyncContinuation例項的狀態__REDISPATCH-->__REDISPATCHED
  • 呼叫server.handleAsync(this);再次進入業務邏輯,此時耗時操作已完成,可以輸出最後的結果
  • 呼叫_request._async.unhandle()   AsyncContinuation例項的狀態__REDISPATCHED-->__UNCOMPLETED
  • 呼叫_request._async.doComplete(),AsyncContinuation例項的狀態__UNCOMPLETED-->__COMPLETED 
  • 請求結束
  • 具體流程2,耗時操作超過了AsyncContinuation預設超時時間
  1. 同上
  2. 同上
  3. selector執行緒輪詢中感知到耗時任務超時
  • 此時在輪詢中能感知並獲取這個task,見org.eclipse.jetty.io.nio.SelectorManager$SelectSet.doSelect()
  • 將此task丟入worker執行緒佇列
worker執行緒C處理超時任務
  • 呼叫AsyncContinuation.expired() ,此時AsyncContinuation例項的狀態__ASYNCWAIT-->__REDISPATCH
  • 然後會往worker執行緒佇列或者selector執行緒佇列中新增一個請求task,以此來觸發jetty再次完成未完成的請求
同上面的4
  • 具體流程3,耗時操作非常快
  1. 同上
  2. worker執行緒A會獲取到selector執行緒派發的這個請求
  • 流程進入org.eclipse.jetty.server.AsyncHttpConnection(AbstractHttpConnection).handleRequest()
  • 呼叫_request._async.handling(),AsyncContinuation例項的狀態__IDLE-->__DISPATCHED
  • 呼叫真正的業務邏輯server.handle(this);業務邏輯中呼叫AsyncContinuation.suspend(ServletContext, ServletRequest, ServletResponse) ,會將AsyncContinuation例項的狀態__DISPATCHED-->__ASYNCSTARTED​
匿名應用執行緒(在jetty woker執行緒另外啟動的執行緒)完成耗時操作,在2)中呼叫_request._async.unhandle()之前完成
  • 呼叫AsyncContinuation.resume() ,此時AsyncContinuation例項的狀態__ASYNCSTARTED-->__REDISPATCHING
還是那個work執行緒A,注意此時3已經完成
  • 呼叫_request._async.unhandle(),,AsyncContinuation例項的狀態__REDISPATCHING-->__REDISPATCHED
  • 由於此時_request._async.unhandle()返回false,再次進入迴圈呼叫server.handleAsync(this);再次進入業務邏輯,此時耗時操作已完成,可以輸出最後的結果
  • 呼叫_request._async.unhandle()   AsyncContinuation例項的狀態__REDISPATCHED-->__UNCOMPLETED
  • 呼叫_request._async.doComplete(),AsyncContinuation例項的狀態__UNCOMPLETED-->__COMPLETED
  • 請求結束 

小結

總體來講

  • jetty continuation需要在應用中使用應用級的執行緒池來完成一些io任務,這個在普通的web程式設計並不常見
  • 在應用的第一次請求中需要
    • 呼叫AsyncContinuation.suspend(完成一個狀態的轉換,以及產生一個超時任務)
    • 將耗時任務派發給應用執行緒池
    • 完畢後,jetty回收該請求執行緒
  • io任務在應用執行緒中完成後,然後通過AsyncContinuation.resume或者complete等方法通知jetty任務完成
  • jetty然後會再次分配一個worker執行緒處理該請求(如果邏輯複雜,如並行的多次Io會分配多個worker執行緒)
  • 引入jetty continuation帶來的負面作用是程式設計模型會變得複雜,需要仔細的切割各類io任務
此例子在jetty7.2和jetty7.6中測試通過,但在jetty8中執行失敗(因為呼叫flush導致request中_header為空從而引發NPE[jetty8的bug?],但如果不掉用有達不大展示效果)

相關推薦

jetty continuation基本原理實現

背景在io密集型的web 應用,如何能更好地提升後臺效能,jetty continuation是一個選擇現在特別流行的說法就是事件驅動,看看node.js以及redis, jetty continuation也不例外一個例子package org.kaka.web; imp

線性判別分析(LDA)基本原理實現

前言 在主成分分析(PCA)原理總結(機器學習(27)【降維】之主成分分析(PCA)詳解)中對降維演算法PCA做了總結。這裡就對另外一種經典的降維方法線性判別分析(Linear Discriminant Analysis, 簡稱LDA)做一個總結。LDA在模式識別領域(比如

遊戲外掛基本原理實現

遊戲外掛已經深深地影響著眾多網路遊戲玩家,今天在網上看到了一些關於遊戲外掛編寫的技術,於是轉載上供大家參考   1、遊戲外掛的原理   外掛現在分為好多種,比如模擬鍵盤的,滑鼠的,修改資料包的,還有修改本地記憶體的,但好像沒有修改伺服器記憶體的哦,呵呵。其實修改伺服器也是有辦法的,只是技術太高一般人沒有辦法入

希爾排序的基本原理實現

原理: 希爾排序通過將原始列表分解為多個較小的子列表來改進插入排序,每個子列表使用插入排序進行排序。選擇這些子列表的方式是希爾排序的關鍵。不是將列表分為連續項的子列表,希爾排序使用增量i,通過選擇i 個項的所有項來建立子列表 程式碼實現:

機器學習(4)--層次聚類(hierarchical clustering)基本原理實現簡單圖片分類

關於層次聚類(hierarchical clustering)的基本步驟: 1、假設每個樣本為一類,計算每個類的距離,也就是相似度 2、把最近的兩個合為一新類,這樣類別數量就少了一個 3、重新新類與各個舊類(去了那兩個合併的類)之間的相似度; 4、迴圈重複2和3直到所有樣本

前端hash路由基本原理,代碼的基本實現

alt ide class javascrip r.js border pop display 早期 路由就是指隨著瀏覽器地址欄的變化,展示給用戶的頁面也不相同。 早期的路由都是後端實現的,直接根據 url 來 reload 頁面,頁面變得越來越復雜服務器端壓力變大,隨著

模糊PID基本原理matlab模擬實現(新手!新手!新手!)

有關模糊pid的相關知識就把自己從剛接觸到模擬出結果看到的大部分資料總結一下,以及一些自己的ps 以下未說明的都為轉載內容 在講解模糊PID前,我們先要了解PID控制器的原理(本文主要介紹模糊PID的運用,對PID控制器的原理不做詳細介紹)。PID控制器(比例

跳躍表(skip list)基本原理C/C++實現

1、基本原理不想好好寫字走的freestyle......其實可以直接看程式碼,我的註釋還是很詳細的,後面也有說明性的插圖。後面關於時間複雜度的分析證明沒有貼(總之我們都知道它效能棒棒噠就行了)。2、C/C++實現比較符合MIT公開課的實現是這篇博文:跳躍表實現而其他多數人都

Node + js實現大檔案分片上傳基本原理實踐(一)

閱讀目錄 一:什麼是分片上傳? 二:理解Blob物件中的slice方法對檔案進行分割及其他知識點 三. 使用 spark-md5 生成 md5檔案 四. 使用koa+js實現大檔案分片上傳實踐 回到頂部 一:什麼是分片上傳? 分片上傳是把一個大的檔案分成若干塊,一塊一塊的傳輸。這

關於base64編碼的原理實現

一個 replace 編碼範圍 func nco 都是 style bit 如果 我們的圖片大部分都是可以轉換成base64編碼的data:image。 這個在將canvas保存為img的時候尤其有用。雖然除ie外,大部分現代瀏覽器都已經支持原生的基於base64的enco

防盜鏈的基本原理實現

rec eal limit ole 站點 new exceptio stub text 1. 我的實現防盜鏈的做法,也是參考該位前輩的文章。基本原理就是就是一句話:通過判斷request請求頭的refer是否來源於本站。(當然請求頭是來自於客戶端的,是可偽造的,暫不在本文

java設計模式singleton原理實現

最新 不必要 -- 不同 適合 所有 引用 ati cnblogs 題外話:我要變強,要變強,變強,強。 1、 Singleton的應用場景以及為什麽要使用singleSingleton是一生只能有一個實例的對象。只能由singleton自身創建一個實例。外人是無法創建實例

API Hook基本原理實現

use 概率 缺省 後綴 origin gif object cati mov API Hook基本原理和實現 2009-03-14 20:09 windows系統下的編程,消息message的傳遞是貫穿其始終的。這個消息我們可以簡單理解為一個有特定

決策樹原理實現

方式 -1 變化 log nbsp 導致 結點 以及 重要 1、決策樹原理 1.1、定義 分類決策樹模型是一種描述對實例進行分類的樹形結構。決策樹由結點和有向邊組成。結點有兩種類型:內部節點和葉節點,內部節點表示一個特征或屬性,葉節點表示一個類。

微信公眾平臺開發教程(二) 基本原理消息接口

username 普通用戶 縮放 地理位置 cfb 位置 註意 獲得 基本 一、基本原理 在開始做之前,大家可能對這個很感興趣,但是又比較茫然。是不是很復雜?很難學啊? 其實恰恰相反,很簡單。為了打消大家的顧慮,先簡單介紹了微信公眾平臺的基本原理。 微信服務器就相當於一個轉

RPC原理實現

.get 版本 pcs 連接方式 正常 zookeepe list 接口 分布式計算 1 簡介 RPC 的主要功能目標是讓構建分布式計算(應用)更容易,在提供強大的遠程調用能力時不損失本地調用的語義簡潔性。為實現該目標,RPC 框架需提供一種透明調用機制讓使用者不必顯式的

幾張圖幫你理解 docker 基本原理快速入門

uil dir commit -name name 地址 什麽 生成 作者 http://www.cnblogs.com/SzeCheng/p/6822905.html 寫的非常好的一篇文章,不知道為什麽被刪除了。 利用Google快照,做個存檔。 快照地址:

SSO單點登錄原理實現

response dem nbsp boolean 配置文件 實現 有效 ucc ons 1.SSO分類   根據實現的域不同,可以把SSO分為同域SSO、同父域SSO、跨域SSO三種類型。 2.SSO實現原理 a.打開統一的登錄界面 b.登錄,同時向服務器寫入Cookie

線程池的原理實現

execute inter void date() 超過 緩沖 線程池大小 exceptio 調整 1、線程池簡介: 多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。 假設一個服務器完成一項任務所需時間為:T1

線程池原理實現

任務隊列 批量 not alt con 成了 代碼 pla extends 1、線程池簡介: 多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。 假設一個服務器完成一項任務所需時間為:T1