日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

jetty分析

發布時間:2024/4/13 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 jetty分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
jetty內部比較重要的實現,jetty里面的內容比較的多,代碼量也是比較大的,我們不可能做一個面面俱到的一個講述,我們不會去介紹他整個系統架構上的一些設計,作為一個servlet容器,它是怎么處理JSP,不是我們的重點,我們的重點是他作為一個高性能的,吞吐量比較高的,這么一個HTTP服務器,他后面采用了什么樣的技術,多線程的手段,很好的來提高他系統的吞吐量,著眼點和立足點呢,是從這個角度去看,我們不會去講他,系統當中,還有哪些模塊,UML圖,還有架構圖,畢竟多線程,并發這門課,他更多的還是會有實現細節,所以我們也是停留在代碼的層面,做一些比較細節的東西,分析的主要內容有三塊,因為他是一個http的server,他也可以做一個嵌入式的實現,我們需要一個http server的時候呢,把jetty給include進來,直接把這個server給new出來,http請求的這個處理,內部也是使用這個server去做的,http server包下的這個類,主要分三塊,如果我們想把一個server跑起來的話,并且能夠正常的處理一些工作,首先我們把server給new出來,把這個server的實例創建出來,創建實例我們要把它給start起來,server啟動,等待http的請求,當有http請求之后呢它會做處理,這三塊就會把怎么跑起來的,處理請求的,比較完整的給理一遍,這兩塊內容是我們的重點,到底有些什么東西,如何排放的,這個設計到請求處理,1請求處理之后,他后面一定是相當復雜的,涉及到service容器的JSP的處理,我們這塊內容是講的比較簡略的,我們把這個請求分給某個線程,那就中斷了,線程怎么處理,這是一系列的Handler的調用,我們就不做進一步的介紹了,jetty使用上的一些問題,這個看下代碼也不是很困難的事情,我們可以看一下server實例的新建,大概最簡單的一個函數

在端口上做監聽,這個里面到底做什么事情呢,他里面做的事情主要是有這四個,一個是初始化線程池,什么叫初始化線程池呢,像jetty這樣的一個服務端的,這么一個容器,服務端的一個產品,他后臺執行的一定是線程池的,不可能你有一個什么任務,他自己就new一個線程出來,這不是很靠譜的事情,但是這里一定要說明的是呢,也沒有使用JDK的線程池,他也可以配置JDK的線程池,他實現了一個新的線程池,QueueThreadPool,所以在初始化線程池的時候呢,把自己的線程池給初始化了一下,他就實現了SizedThreadPool接口,也是jetty當中的一個接口,線程池我們知道,他最重要的是execute方法,也就是執行,我們可以看到在這個執行當中,是怎么做的呢,如果你提交一個Runnable的接口,Runnable的一個實例上去,他的做法對于線程池來講,他只是把它放到隊列當中去,除此之外沒有更多的處理了,他只是把我們要執行的任務,入隊,這個jobs是什么呢,它是一個BlockingQueue,這個jobs是一個BlockingQueue,他保存需要由這個線程池執行的,由此我們可以看到說,這個BlockingQueue的性能不會特別好,BlockingQueue不是一個高性能實現,從這里側面也說明呢,execute方法不會非常頻繁的被調用,如果非常的頻繁的執行execute方法的話,而且BlockingQueue還是一個性能提升的一個點,我印象當中這個BlockingQueue,jetty當中還是實現了自己的BlockingQueue實現,這個性能也不怎么樣,就是和JDK的BlockingQueue是大同小異的,也是會做一些阻塞的操作,這個是線程池的初始化

ServerConnector的東西,服務端的一個連接,當然是用來處理http的一些連接,NIO ByteChannel的一些東西,他繼承自AbstractConnector,初始化他的時候會做什么事情呢,這里列舉的是主要的工作,并不是所有的工作,他的代碼是非常的繁瑣的,如果直接看代碼會直接暈掉,所以還是做了一些整理,去掉一些不是很重要的東西,初始化這么一個ScheduleExecutor,這個東西是干什么用的呢,我們知道JDK當中有一個ScheduledThreadPoolExecutor這個類,他就是一個調度器,像服務器當中,不可避免的我們有些任務每隔一段時間執行一次的,每一分鐘我們要檢查一下東西,諸如此類的一些任務,這是由Schedule來調度的,ByteBufferPool,ByteBuffer的一個池子,他為什么要初始化byteBuffer的一個池子呢,這個池子是什么東西呢,跟線程池一樣,線程池里面放的什么呢,放的的線程,ByteBufferPool里面放的什么呢,BuyteBuffer,ByteBuffer是什么呢,NIO的時候也說過,Buffer是個數組,簡單理解成一個數組,ByteBuffer有兩種,indirect是在堆當中的,direct是在直接內存當中的,ByteBufferPool他其實就是一個對象池,池子當中的所有的ByteBuffer,實際上是可以被復用的,對象池的一大好處,就是我可以減少GC,減少對象的產生,如果我每次需要bytebuffer,我接收某一個數據的時候,因為我畢竟是一個http的server,坦白的講我是一個TCP的服務器,我要去網絡上讀取某些數據,讀數據之前我可能需要,new一個byteBuffer出來,如果我寫數據回去,寫到通道里面去,把這個bytebuffer填滿,把數據寫到通道里面去,這個時候我們在系統中大量的去new bytebuffer,那么new完之后,這樣做也無可厚非,但是為了進一步的提高系統的性能,允許我們會想到說,我們不需要每次都釋放掉,先準備好一堆,有人需要用的時候就去拿一個,拿一個你就用,用完之后你也不用回收,返給我byteBuffer這個池子就可以了,這樣有個什么好處呢,我對象是復用的,我不會每次去new對象,同時也會減少壓力,否則會出現什么情況呢,如果我連接數非常強大的情況之下,如果我要處理好多好多Channel的情況之下,那么我new這個數據,可能會不停的new他,你new其實是一個很快的操作,在JAVA當中new這個關鍵字,它是被絕對的優化的,所以他的性能是很高的,大家不要認為new會影響太大的性能,但是回收會比較麻煩,你可能會導致大量的碎片會回收,新生代的回收會更加的頻繁,這個是我不愿意看到的,但是如果我們隨隨便便的去搞一個對象池,哪有什么壞處呢,其實在很多情況之下,如果我們自己隨便寫一個對象池,還不如直接去new,回收來的好,因為對象池的一個特點呢,必須要滿足線程安全,因為你的對象不可能是一個人用的,你有好多線程在中間用,有一好多線程在用,你拿到對象,跟我退還對象的時候,你是需要保證線程安全的,如果你簡單的加synchronized,那基本上可以斷定,除非你這個對象是非常特別的對象,非常大的超級對象,在這種情況之下呢,如果你用synchronized粗暴的方式去做,你這個性能會比new好一點,否則的話呢,你用synchronized做出來的線程池,對象池呢,性能是很差很差的,你還不如new一個對象來的劃算,所以我們這里要學的一個核心呢,線程池是線程安全的,并且是無鎖的,他不是用synchronized來實現的,ByteBufferPool在Jetty里的實現呢,有幾個,這里選一個常用的ArrayByteBufferPool來講

是ByteBufferPool的一個實現了,他和普通的對象池有什么不一樣的呢,普通對象池當中,所有的對象都是對立的,換句話說,我拿任何一個對象都是一樣的,所有的對象都是等價的,就跟線程池里的線程,無論挑到那個給你執行,都是一樣的,數據庫連接池當中,所有的線程,不論你挑到哪個給你執行,數據庫連接對你來說都是一樣的,但是ByteBufferPool,所以在這一點來講,比普通的對象池稍微復雜一些,因為你可能會使用,可能你的請求當中,需要1K的byteBuffer,你也可能需要2K的byteBuffer,你也可能需要2M的byteBuffer,這都是有可能的,那我不可能把所有大小的byteBuffer都保存N份讓你來使用,這是不切實際的,換句話說呢,這個ArrayBytePool當中,他所有的對象,他并不是等價的,這就是他實現上會有非常麻煩的地方,他這個地方是怎么做的呢,這個ArrayByteBufferPool他有三個參數

增量最大大小,最小大小是什么呢,我這個池子當中,我最小的那一個,byteBuffer,起始大小,增量指的是,我最大的byteBuffer有多大呢,比如這里有64K,比如這里是0,默認是0,64K,我以1K為增量,在這個池子當中呢,一直到64K為止,像這么大的buffer,會保存到那邊,那么保存的哪些buffer,他怎么存放呢,當你把它去new出來的時候,你沒有去存放他,你并沒有把實際的byteBuffer去給他new出來,因為是沒有必要的,也許你只用一種大小32K,其他大小從來都不用的,這是有可能的,如果你把6K,64k都new出來,那是沒有必要的,所以這里是延遲加載的策略,你不同的大小的buffer,你放到pool當中呢,它是會放在一個叫做bucket的一個籃子里

我們可以把這個bucket理解成一個籃子,一個bucket可能就放一個大小的buffer,你用了64種大小的buffer呢,那你自然需要64種byte,因為這種地方會涉及到說,是有可能是直接內存的,也有可能是堆的,有直接內存和堆的兩個的bucket,一個bucket里面有什么東西呢,bucket里面有兩樣,size就是byteBuffer有多大,還有實際的存放byteBuffer的一個容器,一個queue,這個地方要注意的,高并發的ConcurrentLinkedQueue,大家應該比較清楚了,他的并發性能很好,它是一個無鎖的實現,所以當你有大量的請求的時候呢,他的性能是非常的不錯的,在初始化ByteBufferPool的時候呢,它是會把所有的bucket,都創建一遍,但是bucket里面queue里面的內容,它是延遲加載的,額外Pool有兩個非常重要的方法,就是acquire

我要池子里面申請一個buffer,就是我這個buffer用完了,我要歸還你這個池子,我申請相當于我去new一個buffer出來,這里有兩個參數,一個是你要多大的一個buffer,第二個你是要從直接內存,還是從堆,這個acquire主要分三步,第一個我要找到一個合適的bufferBucket,因為我知道沒有一個bucket的大小都不一樣,那我1到64K,就是當我64個Bucket,分別是1K,2K,一直到64K,加入你只要了5.5K,那我要取多少呢,來給你用,那就應該給你6K的,就是我要找到一個合適的bucket來用,這個大小的bucket是我要的,然后我要從這個bucket當中取到,我要的buffer,默認情況之下呢,當然是沒有東西的,這是延遲加載的,在沒有東西的情況下呢,他就會新建,如果存在就返回

Queue當中有數據,不存在新建,新建出來,把這個buffer返回,應該說還是比較容易理解的,此外就是release,release最重要的就是返回線程池,也是分三步,第一步就是我要取得合適的bucket,那我這個buffer有多大,那我就要還到bucket一樣的大小里面去,我得把bucket給取出來,取完之后我要清空buffer,因為buffer用完了,limit,capacity,當前的position,全部都要清空,都要初始化,可以讓別人拿到之后繼續使用,最后歸還到Queue當中去,這樣以后別人用的時候呢,就會拿到,此外他還可以支持另外的處理

你現在只有1到64K的buffer,現在突然出現一個線程說,128K你給不出來,如果說給不出來,我會給你成功的申請,剛才看到的acquire當中,不存在是會給你申請的,我沒有辦法給你歸還,只是換不進去而已,還不進去怎么辦呢,這個池子當中是不會有pool出現了,因此你還不進去的buffer,在將來是必然會被系統回收的,只要你過了作用域之后,是會被回收掉,這是沒有關系的,這個就是ByteBufferPool的一個實現,這個實現我個人認為呢,是非常重要的,它主要體現的思想呢,一個是無鎖,對象池在多線程的情況之下,他必須是無鎖的,否則他還不如去new一個來的快,第二個我們看到他比較好的處理了,各個不同的大小的buffer,它是如何做一個處理,從這個角度上來講呢,比普通的對象池呢,會復雜一些,這里是ByteBufferPool,接著他會初始化ConnectionFactory

ConnectionFactory它是一個工廠,一個典型的ConnectionFactory呢,HttpConnectionFactory,他就用于創建連接,當有人客戶端連接上來,我自然要創建一個對象,來維護這個連接,這個就是Factory的一個作用,來創建連接的一個對象,接著他取得CPU的一個數量,這個是很重要的,下面jetty會根據你CPU的數量,來決定我在這個系統當中,應該使用多少個accept線程,他都要由這個cores算出來的,這也是對于一個高并發的程序來講,你必須做的一個事情,你必須去自適應CPU的數量,否則你在兩個CPU上,你完全沒有辦法很好的去協調,然后你根據CPU的數量呢,更新或者是計算,acceptor線程的數量,我又多少個線程用于accept,我們知道我們在做網絡編程的時候,一個服務端你是要做accept等待的,那你有幾個線程來做accept等待呢,這里大家可以看一下,jetty提供一個經驗的一個公式,有幾個線程來做這個等待,是比較合適的,首先他取了一個最小值,第一個是4,換句話說,他認為,你用做accept的線程數量,應該是很少的,不應該是很多的,如果你CPU數量真的很多,也不會超過四個,最多也不超過4個去做accept,這個也是我們在編程中可以借鑒的一個地方,有時候我們也要寫一個網絡程序,我們也要accept一個東西,這個時候有幾個線程去accept呢,不要超過4個,如果你不到4個,你有10個CPU,假設你只有10個CPU,那你只能用一個線程,如果你有20個CPU,那你就去使用2個線程,這個數量不會太多,有了這個acceptor數量之后呢,他就要創建一個acceptor線程數組,并沒有初始化線程,只是把維護數組的線程給創建出來,其實它會初始化ServerConnectorManager的東西,它是繼承SelectorManager,什么叫SelectorManager呢,對Selector做一個管理,之前有講過NIO,也說過Selector是干嘛用的,就是當你通道準備好的時候呢,你有select到我,具體的NIO的副本呢,就不在這里展開了,我有多少個線程來做select這個事情,你有這個通道準備好,他這里給了一個經驗的值,之多也不超過4個,比如你有4個CPU的話呢,你可能有2個線程去做select,但是很明顯selector的數量比acceptor要多,selector不處理具體的業務邏輯,所以也不需要太多的線程,真正的把數據拿下來之后呢,你還是會使用實際的線程去做操作,這里是初始化serverConnector,下一步就會設置port

我們需要在8080端口監聽,關聯server和Connector,這個是server初始化時候做的事情,我們看一下server啟動以后會做什么事情,server啟動以后主要是做三個,前兩個其實是沒有意義的,設置啟動狀態,我用于系統的管理和維護,正是啟動過程,然后我啟動完畢了,包括server在內,很多jetty當中的對象,他都是有生老病死的過程,生命周期在里面,他們都會繼承lifeCircle,所以我們沒有在文檔當中給展示出來,只是給大家做了一個介紹,而系統當中也會有一些manager,會有一些管理器,會統一的對lifeCircle的對象呢,管理做維護,start,end,這個就有點類似在Spring當中,對Bean聲明周期的管理,有點接近,server.start最核心的呢,就是doStart方法,它是真正啟動要做的事情,主要是這么幾步

第一個注冊shutdownMonitor,允許你遠程把jetty服務給關掉,作為一款商用的,它是一個比較成熟的server,要提供一些比較強大的功能,Monitor線程就是用來做這個事情的,繼而我們拿到線程池,這里只是把它拿出來,有點類似Spring的方法,但是他用的不是Spring,自己自成了一套管理體系,他也是歸整個系統托管,設置selector的數量,因為我們前面設置了selector的數量,那只是一個ServerConnector當中,計算這個selector數量,事實上server當中呢,jetty允許有多個connector,累計所有的需求

就是你所需要的線程數量,如果線程大于200呢,你這個程序就直接終止掉了,因為這個時候覺得已經沒有執行下去的價值了,因為200個線程去做selector,跟做acceptor,性能也是很差很差的那種了,然后會維護這個bean

它會啟動ThreadPool,啟動調用doStart方法,ThreadPool他做什么事情呢,它是會啟動若干個線程,把你所需要的線程都給建立起來,它會做三件事,第一個創建線程,創建線程使用Runnable的接口,設置線程的屬性,比如優先級,線程是否是Daemon的線程,設置線程的名字,我這個設置線程的名字我認為是非常重要的,可以方便調試,最后啟動線程,把線程起來,創建線程它使用的是Runnable的接口

這個Runnable接口中做的事情是什么呢,去jobs當中去拿我們的任務,這就是QueueThreadPool里面的,就是去jobs當中取Runnable的任務,jobs中的任務是哪里來的呢,就是execute方法當中塞進去的,所以execute的時候他并沒有執行,他只是塞到jobs隊列當中去,只有當你線程起來之后,這里每一個真實執行的線程,他才會不停的從jobs里面拿任務,他這里肯定是一個死循環,作用是不停的去jobs里面獲取,另外一個bean呢就表示WebAppContext,如果有需要的時候呢,Servlet容器所規范的內容,最后會啟動Connector

啟動Connector又可以分為這么幾步,第一步會去的ConnectorFactory,ConnectionFactory在前面維護起來了,然后會創建Connector的線程并啟動,因為之前已經計算了Connector線程的數量,所以可以根據數量創建Connector線程,這里要注意的是,就是ManagerSelector,就是一個線程,但是他封裝了對于NIO來講,他封裝了Selector的操作,一個可管理的selector,然后把它給啟動,接著會創建Acceptor線程

Acceptor線程的作用就是用來等待客戶端連接,那我們來看看Acceptor線程,做了什么事情,你要創建幾個acceptor線程,然后把Acceptor線程給創建出來,去做這個執行,Acceptor線程做什么呢,首先他要設置線程的名字,這是一個非常好的習慣,都帶有acceptor的關鍵字,設置優先級,并且將自己加入到acceptors數組當中去

然后監聽端口,在server上面做accept,在這個地方做等待,如果你只有一個線程,那你只有一個線程在這里等待,如果你有兩個accept,那就有兩個accept在這里做等待,這里還有一種情況呢,就是沒有Acceptor的情況,在默認的情況之下呢,Acceptor線程數量是有的,是大于0個的,但是也有可能accptor的數量是0,如果沒有專門的線程,用來做acceptor操作,對于NEW IO來講,accept本身也可以是非阻塞的,當有線程真正accept上來的時候呢,他在做一個select的一個返回,來告訴你有人accept上來了,但是在默認情況之下,他是一個阻塞的操作

當acceptor線程數量為0,他就會把acceptChannel配置成非阻塞的形式

把server啟動完了,然后http請求,http請求是和accept相關的,我們可以看到這個地方,成功以后就會去做一些配置,它會把拿到的channel配置成非阻塞模式,然后去配置一些socket

設置為非阻塞模式,然后配置socket,最后做一些正是的處理,那正式處理當中呢,他要把channel交給SelectorManager做處理

SelectorManager在處理的時候呢,它會選擇一個可用的線程,其實就封裝了selector操作,那么在選擇這個線程的時候呢,他這里有一個非常有意思的注釋,它會根據你不同的selectIndex,每次都會++,這是一個成員變量,把這個值映射到select當中去,這是一個線程,這個操作本身并不是原子操作,因此在多線程的時候,但是這并沒有關系的,因為他只需要這個值變化就行了,你變了多變了少呢,關系不大,因為他并不需要一個精確地需求,只要你這個值在變,只要你在變化呢,我就能夠保證呢,在絕大部分場合,我們其實并不需要一些線程安全的手段,在你可有可無的情況之下呢,應該忽略線程安全的,因為這樣性能是最好的,如果你在這里做一個同步,哪怕你這里用一個原子的類來做,性能還不如這樣做來的好,下面就是ManagerSelector來做處理,他呢是一個線程,封裝了Selector的使用,在這個Selector當中呢,他提交一個任務,這個任務是和accept相關的,提交任務之后呢,最終是會加入到Queue隊列當中去,changes是ConcurrentArrayQueue的隊列

它是jetty當中自己實現的,這個性能和ConcurrentLinkedQueue是非常類似的,他也是使用了無鎖的實現,但是他有一個比較好的地方是什么呢,對于ConcurrentLinkedQueue來講它是一個鏈表,當你去保存某些數據的時候,你其實是需要在elements的外面,你要套一個node上去,但是對于ConccurentArrayQueue來講呢,他沒有這個概念,直接存放的就是這個元素了,當你有大量的元素壓入隊列的時候呢,其實要少一般,因為除了elements之外還有node,而這個是不需要,所以對于GC來講性能要好一些,這也是我們可以值得思考的一個地方,對于ConcurrentArrayQueue大家可以去看一下源碼的實現,我們介紹了并發包里的容器,很多也都是大同小異的,它是一個高并發的實現,所以即便你有大量的連接上來,大量的任務提交上來

處理請求使用的是ManagerSelector的run方法,也就是在這個地方,他提交任務,但是他不處理任務,他只把任務壓到隊列當中去,在線程執行過程當中,他才會去做任務的處理,他里面的代碼比較多,只截取了兩行,主要是select,只要你還在running,那你就要不停的select,select做什么呢,就是runchange,什么叫changes呢,當有任務上來,就應該runchanges

去做這個任務的執行,它會把changes隊列當中的任務呢,拿出來去做這個執行,執行的時候就是做了一個核心的操作呢,相互注冊一下,綁定一下,得到SelectionKey,然后去創建這個連接,代表這個連接的endPoint,這樣在后面就能夠拿到我們的key,繼續做這個處理,返回給endpoint,這個是做runchanges,然后他會做select,實際上它是關聯了channel和selector,現在你要等待read或者write,它是一個可用的狀態,需要做select操作,一旦發現有任何select可用,任何一個channel的select可用的話呢,他就會去做處理,已經準備好的selectKey拿出來,會使用新的線程做HTTP的處理

?

總結

以上是生活随笔為你收集整理的jetty分析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。