從構建分佈式秒殺系統聊聊線程池

前言

從0到1構建分佈式秒殺系統案例的代碼已經所有上傳至碼雲,文章也被分發到各個平臺。其中也收到了很多小夥伴喜歡和反饋,有網友如是說:java

說實話,能用上的很少,中小企業都不可能用到,大型企業也不是一我的就能搞起的,大部分人一生都用不上,等有這個須要再搞吧。git

個人觀點是贊同但不支持,基本上任何事物都是呈金字塔分佈,互聯網也不例外,也就是說大部分可能都是普通人,接觸不到所謂大廠的應用場景。可是,書到用時方恨少,機會老是留給有準備的人的,除非有錢難買我樂意,只能說大千世界,每一個人都有本身的生活方式,尊重並活着。算法

進程和線程

前面都是扯淡,也不是什麼鋪墊,在聊線程池以前咱們最好簡單瞭解下什麼是進程,什麼是線程,進程和線程到底有什麼區別?spring

這裏咱們,搬運下某百科的釋義:數據庫

進程是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操做系統結構的基礎。在早期面向進程設計的計算機結構中,進程是程序的基本執行實體;在當代面向線程設計的計算機結構中,進程是線程的容器。程序是指令、數據及其組織形式的描述,進程是程序的實體。tomcat

固然,知乎上也有很多網友的回答,每一個人都有本身不一樣的理解方式。這裏咱們拿Tomcat容器作例子:你能夠這麼理解,運行中的Tomcat容器就是一個進程,而每一個用戶的操做(查詢、上傳)能夠當作一個或者多個線程。服務器

線程池

秒殺活動中,瞬時併發是很是大的,若是每個請求都開啓一個新線程,系統就要不斷的進行線程的建立和銷燬,有時花在建立和銷燬線程上的時間會比線程真正執行的時間還長。而且因爲硬件條件限制,線程數量又不能無限建立。網絡

那麼線程池到底解決了那些問題:併發

  • 下降資源消耗:經過重用已經建立的線程來下降線程建立和銷燬的消耗
  • 提升響應速度:任務到達時不須要等待線程建立就能夠當即執行
  • 提升線程的可管理性:線程池能夠統一管理、分配、調優和監控

源自網絡

執行流程
  • 調用ThreadPoolExecutor的execute提交線程,首先檢查CorePool,若是CorePool內的線程小於CorePoolSize,新建立線程執行任務。分佈式

  • 若是當前CorePool內的線程大於等於CorePoolSize,那麼將線程加入到BlockingQueue。

  • 若是不能加入BlockingQueue,在小於MaxPoolSize的狀況下建立線程執行任務。

  • 若是線程數大於等於MaxPoolSize,那麼執行拒絕策略。

模擬測試

爲了方便測試,咱們在Control中定義了線程池,來模擬用戶秒殺動做:

定義初始線程數:

private static int corePoolSize = Runtime.getRuntime().availableProcessors();
  • IO密集型任務 = 通常爲2*CPU核心數(常出現於線程中:數據庫數據交互、文件上傳下載、網絡數據傳輸等等)
  • CPU密集型任務 = 通常爲CPU核心數+1(常出現於線程中:複雜算法)
  • 混合型任務 = 視機器配置和複雜度自測而定

定義Executor:

private static ThreadPoolExecutor executor  = new ThreadPoolExecutor(corePoolSize, corePoolSize+1, 10l, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(1000));
  • corePoolSize用於指定核心線程數量
  • maximumPoolSize指定最大線程數
  • keepAliveTime和TimeUnit指定線程空閒後的最大存活時間
  • workQueue則是線程池的緩衝隊列,還未執行的線程會在隊列中等待,監控隊列長度,確保隊列有界;不當的線程池大小會使得處理速度變慢,穩定性降低,而且致使內存泄露。若是配置的線程過少,則隊列會持續變大,消耗過多內存;而過多的線程又會 因爲頻繁的上下文切換致使整個系統的速度變緩——殊途而同歸。隊列的長度相當重要,它必須得是有界的,這樣若是線程池不堪重負了它能夠暫時拒絕掉新的請求。

  • ExecutorService 默認的實現是一個無界的LinkedBlockingQueue。

Tomcat線程池

以上只是爲了測試方便,模擬出的數據。真實的生產環境,咱們要接入Nginx和Tomcat來處理用戶的請求。而Tomcat做爲一名容器也是有本身的一套鏈接池的,做爲開發人員你並不須要本身去實現。

Tomcat默認使用自帶的鏈接池,這裏咱們也能夠自定義實現,打開/conf/server.xml文件,在Connector以前配置一個線程池:

<Executor name="tomcatThreadPool"   
        namePrefix="tomcatThreadPool-"   
        maxThreads="1000"   
        maxIdleTime="300000"  
        minSpareThreads="200"/>
  • name:共享線程池的名字。這是Connector爲了共享線程池要引用的名字,該名字必須惟一。默認值:None;

  • namePrefix:在JVM上,每一個運行線程均可以有一個name 字符串。這一屬性爲線程池中每一個線程的name字符串設置了一個前綴,Tomcat將把線程號追加到這一前綴的後面。默認值:tomcat-exec-;

  • maxThreads:該線程池能夠容納的最大線程數。默認值:200;

  • maxIdleTime:在Tomcat關閉一個空閒線程以前,容許空閒線程持續的時間(以毫秒爲單位)。只有當前活躍的線程數大於minSpareThread的值,纔會關閉空閒線程。默認值:60000(一分鐘)。

  • minSpareThreads:Tomcat應該始終打開的最小不活躍線程數。默認值:25。

配置Connector:

<Connector executor="tomcatThreadPool"
           port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443"
           minProcessors="5"
           maxProcessors="75"
           acceptCount="1000"/>
  • executor:表示使用該參數值對應的線程池;

  • minProcessors:服務器啓動時建立的處理請求的線程數;

  • maxProcessors:最大能夠建立的處理請求的線程數;

  • acceptCount:指定當全部可使用的處理請求的線程數都被使用時,能夠放處處理隊列中的請求數,超過這個數的請求將不予處理。

思考

  • 爲何線程數最好不要太大於CPU核數?
  • 爲何Tomcat中默認線程數遠大於CPU核數?
  • Nginx爲何要進入線程池,基於什麼場景考慮?

代碼案例:從0到1構建分佈式秒殺系統

相關文章
相關標籤/搜索