併發編程之Disruptor框架介紹和高階運用

1.   Disruptor是什麼
1.1   技術背景
LMAX是在英國註冊並受到FCA監管(監管號碼爲509778)的外匯黃金交易所, LMAX架構是LMAX內部研發並應用到交易系統的一種技術。它之所以引起人們的關注,是因爲它是一個非常高性能系統,這個系統是建立在JVM平臺上,核心是一個業務邏輯處理器,官方號稱它能夠在一個線程裏每秒處理6百萬訂單.

一個僅僅部署在4臺服務器上的服務,每秒向Database寫入數據超過100萬行數據,每分鐘產生超過1G的數據。而每臺服務器(8核12G)上CPU佔用不到100%,load不超過5。

1.2   對比阻塞隊列
現實編程過程中,加鎖通常會嚴重地影響性能。線程會因爲競爭不到鎖而被掛起,等鎖被釋放的時候,線程又會被恢復,這個過程中存在着很大的開銷,並且通常會有較長時間的中斷,因爲當一個線程正在等待鎖時,它不能做任何其他事情。如果一個線程在持有鎖的情況下被延遲執行,例如發生了缺頁錯誤、調度延遲或者其它類似情況,那麼所有需要這個鎖的線程都無法執行下去。如果被阻塞線程的優先級較高,而持有鎖的線程優先級較低,就會發生優先級反轉。

Disruptor論文中講述了一個實驗:

這個測試程序調用了一個函數,該函數會對一個64位的計數器循環自增5億次。 機器環境:2.4G 6核 運算: 64位的計數器累加5億次

CAS操作比單線程無鎖慢了1個數量級;有鎖且多線程併發的情況下,速度比單線程無鎖慢3個數量級。可見無鎖速度最快。 單線程情況下,不加鎖的性能 > CAS操作的性能 > 加鎖的性能。 在多線程情況下,爲了保證線程安全,必須使用CAS或鎖,這種情況下,CAS的性能超過鎖的性能,前者大約是後者的8倍。

 可以和BlockingQueue做對比,不過disruptor除了能完成同樣的工作場景外,能做更多的事,效率也更高。業務邏輯處理器完全是運行在內存中(in-memory),使用事件源驅動方式(event sourcing). 業務邏輯處理器的核心是Disruptors,這是一個併發組件,能夠在無鎖的情況下實現Queue併發安全操作。

1.3   Disruptor構成

disruptor核心UML圖

先介紹幾個相關的核心概念。

①環形隊列ringbuffer

數據緩衝區,不同線程之間傳遞數據的BUFFER。RingBuffer是存儲消息的地方,通過一個名爲cursor的Sequence對象指示隊列的頭,協調多個生產者向RingBuffer中添加消息,並用於在消費者端判斷RingBuffer是否爲空。巧妙的是,表示隊列尾的Sequence並沒有在RingBuffer中,而是由消費者維護。這樣的好處是多個消費者處理消息的方式更加靈活,可以在一個RingBuffer上實現消息的單播,多播,流水線以及它們的組合。在RingBuffer中維護了一個名爲gatingSequences的Sequence數組來跟蹤相關Seqence。

②Producer/Consumer

Producer即生產者,比如下圖中的P1. 只是泛指調用 Disruptor 發佈事件(我們把寫入緩衝隊列的一個元素定義爲一個事件)的用戶代碼。

Consumer和EventProcessor是一個概念,新的版本中由EventProcessor概念替代了Consumer。

有兩種實現策略,一個是SingleThreadedStrategy(單線程策略)另一個是 MultiThreadedStrategy(多線程策略),兩種策略對應的實現類爲SingleProducerSequencer、MultiProducerSequencer【都實現了Sequencer類,之所以叫Sequencer是因爲他們都是通過Sequence來實現數據寫,Sequence的概念參見③】 ,它們定義在生產者和消費者之間快速、正確地傳遞數據的併發算法。具體使用哪個根據自己的場景來定,[多線程的策略使用了AtomicLong(Java提供的CAS操作),而單線程的使用long,沒有鎖也沒有CAS。這意味着單線程版本會非常快,因爲它只有一個生產者,不會產生序號上的衝突]

Producer生產event數據,EventHandler作爲消費者消費event並進行邏輯處理。消費消息的進度通過Sequence來控制。

③Sequence

Sequence是一個遞增的序號,說白了就是計數器;其次,由於需要在線程間共享,所以Sequence是引用傳遞,並且是線程安全的;再次,Sequence支持CAS操作;最後,爲了提高效率,Sequence通過padding來避免僞共享,關於解決僞共享的問題,可以參見下面章節詳細的介紹。

通過順序遞增的序號來編號管理通過其進行交換的數據(事件),對數據(事件)的處理過程總是沿着序號逐個遞增處理。一個 Sequence 用於跟蹤標識某個特定的事件處理者( RingBuffer/Consumer )的處理進度。生產者對RingBuffer的互斥訪問,生產者與消費者之間的協調以及消費者之間的協調,都是通過Sequence實現。幾乎每一個重要的組件都包含Sequence。

說明:雖然一個 AtomicLong 也可以用於標識進度,但定義 Sequence 來負責該問題還有另一個目的,那就是防止不同的 Sequence 之間的CPU緩存僞共享(Flase Sharing)問題。

 

④Sequence Barrier

用於保持對RingBuffer的 main published Sequence 和Consumer依賴的其它Consumer的 Sequence 的引用。 Sequence Barrier 還定義了決定 Consumer 是否還有可處理的事件的邏輯。SequenceBarrier用來在消費者之間以及消費者和RingBuffer之間建立依賴關係。在Disruptor中,依賴關係實際上指的是Sequence的大小關係,消費者A依賴於消費者B指的是消費者A的Sequence一定要小於等於消費者B的Sequence,這種大小關係決定了處理某個消息的先後順序。因爲所有消費者都依賴於RingBuffer,所以消費者的Sequence一定小於等於RingBuffer中名爲cursor的Sequence,即消息一定是先被生產者放到Ringbuffer中,然後才能被消費者處理。不好理解的話,可以看下面章節事例配合瞭解。

SequenceBarrier在初始化的時候會收集需要依賴的組件的Sequence,RingBuffer的cursor會被自動的加入其中。需要依賴其他消費者和/或RingBuffer的消費者在消費下一個消息時,會先等待在SequenceBarrier上,直到所有被依賴的消費者和RingBuffer的Sequence大於等於這個消費者的Sequence。當被依賴的消費者或RingBuffer的Sequence有變化時,會通知SequenceBarrier喚醒等待在它上面的消費者。

 

⑤Wait Strategy

當消費者等待在SequenceBarrier上時,有許多可選的等待策略,不同的等待策略在延遲和CPU資源的佔用上有所不同,可以視應用場景選擇:

BusySpinWaitStrategy : 自旋等待,類似Linux Kernel使用的自旋鎖。低延遲但同時對CPU資源的佔用也多。

BlockingWaitStrategy : 使用鎖和條件變量。CPU資源的佔用少,延遲大。

SleepingWaitStrategy : 在多次循環嘗試不成功後,選擇讓出CPU,等待下次調度,多次調度後仍不成功,嘗試前睡眠一個納秒級別的時間再嘗試。這種策略平衡了延遲和CPU資源佔用,但延遲不均勻。

YieldingWaitStrategy : 是一種充分壓榨 CPU 的策略,使用 自旋+yield的方式來提高性能。 當消費線程(Event Handler threads)的數量小於 CPU 核心數時推薦使用該策略。(多個消費者且大於CPU核數可能導致CPU接近100%,需要謹慎使用)

PhasedBackoffWaitStrategy : 上面多種策略的綜合,CPU資源的佔用少,延遲大。

⑥Event

在 Disruptor 的語義中,生產者和消費者之間進行交換的數據被稱爲事件(Event)。它不是一個被 Disruptor 定義的特定類型,而是由 Disruptor 的使用者定義並指定。

   1.3 .6.1 EventProcessor

EventProcessor 持有特定消費者(Consumer)的 Sequence,並提供用於調用事件處理實現的事件循環(Event Loop)。通過把EventProcessor提交到線程池來真正執行,有兩類Processor:

其中一類消費者是BatchEvenProcessor。每個BatchEvenProcessor有一個Sequence,來記錄自己消費RingBuffer中消息的情況。所以,一個消息必然會被每一個BatchEvenProcessor消費。

另一類消費者是WorkProcessor。每個WorkProcessor也有一個Sequence,多個WorkProcessor還共享一個Sequence用於互斥的訪問RingBuffer。一個消息被一個WorkProcessor消費,就不會被共享一個Sequence的其他WorkProcessor消費。這個被WorkProcessor共享的Sequence相當於尾指針

   1.3 6.2 EventHandler

Disruptor 定義的事件處理接口,由用戶實現,用於處理事件,是 Consumer 的真正實現。開發者實現EventHandler,然後作爲入參傳遞給EventProcessor的實例。

綜上所述,附官方類圖:

2.  Disruptor什麼時候用

disruptor是用於一個JVM中多個線程之間的消息隊列,作用與ArrayBlockingQueue有相似之處,但是disruptor從功能、性能都遠好於ArrayBlockingQueue,當多個線程之間傳遞大量數據或對性能要求較高時,可以考慮使用disruptor作爲ArrayBlockingQueue的替代者。下面以兩個簡單場景舉例:

例如場景一:

停車批量入場數據上報,數據上報後需要對每條入場數據存入DB,還需要發送kafka消息給其他業務系統。如果執行完所有的操作,再返回,那麼接口耗時比較長,我們可以批量上報後驗證數據正確性,通過後按單條入場數據寫入環形隊列,然後直接返回成功。

實現方式一:啓 動2個消費者線程,一個消費者去執行db入庫,一個消費者去發送kafka消息。

實現方式二:啓動4個消費者,2個消費者併發執行db入庫,兩個消費者併發發送kafka消息,充分利用cpu多核特性,提高執行效率。

實現方式三:如果要求寫入DB和kafka後,需要給用戶發送短信。那麼可以啓動三個消費者線程,一個執行db插入,一個執行kafka消息發佈,最後一個依賴前兩個線程執行成功,前兩個線程都執行成功後,該線程執行短信發送。

例如場景二:

你在網上使用信用卡下訂單。一個簡單的零售系統將獲取您的訂單信息,使用信用卡驗證服務,以檢查您的信用卡號碼,然後確認您的訂單 – 所有這些都在一個單一過程中操作。當進行信用卡有效性檢查時,服務器這邊的線程會阻塞等待,當然這個對於用戶來說停頓不會太長。

在MAX架構中,你將此單一操作過程分爲兩個,第一部分將獲取訂單信息,然後輸出事件(請求信用卡檢查有效性的請求事件)給信用卡公司. 業務邏輯處理器將繼續處理其他客戶的訂單,直至它在輸入事件中發現了信用卡已經檢查有效的事件,然後獲取該事件來確認該訂單有效。

 

3.  Disruptor爲什麼快
3.1數組實現
用數組實現, 解決了鏈表節點分散, 不利於cache預讀問題,可以預分配用於存儲事件內容的內存空間;並且解決了節點每次需要分配和釋放, 需要大量的垃圾回收GC問題 (數組內元素的內存地址的連續性存儲的,在硬件級別,數組中的元素是會被預加載的,因爲只要一個元素被加載到緩存行,其他相鄰的幾個元素也會被加載進同一個緩存行)

3.2 批量預讀
相比鏈表隊列,實現數組預讀,減少結點操作空間釋放和申請,(事先把數據空間申請出來,但不賦值)從而減少gc次數。生產者支持單生產,多生產者模式,單生產者cursor使用普通long實現,無鎖加快速度,多生產者才使用Sequence(AtomicLong)生產和消費元素支持單線程批量操作數據。

RingBuffer的批量預讀源碼:

    private void fill(EventFactory<E> eventFactory)
    {
        for (int i = 0; i < bufferSize; i++)
        {
            entries[BUFFER_PAD + i] = eventFactory.newInstance();
        }
    }

3.3求餘操作優化
求餘操作本身也是一種高耗費的操作, 所以ringbuffer的size設成2的n次方(不要太大,否則會造成oom), 可以利用位操作來高效實現求餘。要找到數組中當前序號指向的元素,可以通過mod操作,正常通過sequence mod array length = array index,優化後可以通過:sequence & (array length-1) = array index實現。比如一共有8槽,3&(8-1)=3,HashMap就是用這個方式來定位數組元素的,這種方式比取模的速度更快。

 

3.4 Lock-Free
如果只有一個生產者,那麼系統中的每一個序列號只會由一個線程寫入。這意味着沒有競爭、不需要鎖、甚至不需要CAS。在ClaimStrategy中,如果存在多個生產者,唯一會被多線程競爭寫入的序號就是 ClaimStrategy 對象裏的那個。

那麼是採用什麼樣的方式競爭寫入呢?

disruptor不使用鎖, 使用CAS(Compare And Swap/Set),嚴格意義上說仍然是使用鎖, 因爲CAS本質上也是一種樂觀鎖, 只不過是CPU級別指令, 不涉及到操作系統, 所以效率很高(AtomicLong實現Sequence)

這就是我們所說的“分離競爭點問題”或者隊列的“合併競爭點問題”。通過將所有的東西都賦予私有的序列號,並且只允許一個消費者讀Entry對象中的變量來消除競爭,Disruptor 唯一需要處理訪問衝突的地方,是多個生產者寫入 Ring Buffer 的場景

爲什麼隊列不能勝任這個工作?
- 節點分散, 不利於cache預讀 
- 節點每次需要分配和釋放, 需要大量的垃圾回收, 低效 
- 不利於批量讀取 
- 競爭點較多, head指針, tail指針, size 
   由於producer和consumer很難同步, 所以大部分queue都是滿或空狀態, 這樣會導致大量的競爭, 比較低效 
- 而且習慣的編程方式導致head指針, tail指針, size常常在一個cacheline中, 造成僞共享問題

用數組實現, 可以部分解決前3點問題, 但仍然無法解決競爭點問題, 以及由於數組的fix size, 帶來擴展性問題

3.5 解決僞共享{False Sharing}

緩存系統是以緩存行cache-line爲存儲單位的,大小一般爲2的整數次冪一般爲64字節

當多線程互相修改獨立的變量時,如果這些變量共享一個緩存行,就會影響彼此的性能,這就是僞共享

 

緩存行通常是64字節, 一個Java的long類型是8字節,因此在一個緩存行中可以存8個long類型的變量. 緩存行是緩存更新的基本單位, 就算你只讀一個變量, 系統也會預讀其餘7個, 並cache這一行, 並且這行中的任一變量發生改變, 都需要重新加載整行, 而非僅僅重新加載一個變量.

 解決僞共享的辦法是填充一些無用的字段p1,p2,p3,p4,p5,p6,p7再考慮到對象頭也佔用8bit, 剛好把對象佔用的內存擴展到剛好佔64bytes(或者64bytes的整數倍)

注:有可能處理器的緩存行是128字節,那麼使用64字節填充還是會存在僞共享問題

在Disruptor裏我們對RingBuffer的cursor和BatchEventProcessor的序列進行了緩存行填充,如下:

abstract class RingBufferPad
{
    protected long p1, p2, p3, p4, p5, p6, p7;
}

jdk消除僞共享的策略

http://www.cnblogs.com/Binhua-Liu/p/5620339.html

https://gitee.com/lzhcode/maven-parent/tree/master/lzh-disruptor/lzh-disruptor-api/src/main/java/com/lzhsite/disruptor/com.lzhsite.disruptor.optimization

3.6 使用內存屏障
內存屏障本身不是一種優化方式, 而是你使用lock-free(CAS)的時候, 必須要配合使用內存屏障,因爲CPU和memory之間有多級cache, CPU core只會更新cache-line, 而cache-line什麼時候flush到memory, 這個是有一定延時的 ,在這個延時當中, 其他CPU core是無法得知你的更新的, 因爲只有把cache-line flush到memory後, 其他core中的相應的cache-line纔會被置爲過期數據,所以如果要保證使用CAS能保證線程間互斥, 即樂觀鎖, 必須當一個core發生更新後, 其他所有core立刻知道並把相應的cache-line設爲過期, 否則在這些core上執行CAS讀到的都是過期數據.

內存屏障 = “立刻將cache-line flush到memory, 沒有延時”

注:可參考java中volatile的原理和 happen-before語義 同樣實現了內存屏障。

 
3.8 序號柵欄
     生產者序號wrapPoint,比消費者序號的最小值minSequence大就不斷自旋

多生產者多消費者完整代碼:

 https://gitee.com/lzhcode/maven-parent/tree/master/lzh-disruptor/lzh-disruptor-api/src/main/java/com/lzhsite/disruptor/heigh/multi

4.  使用Disruptor開發
4.1handleEventsWith,handleEventsWithWorkerPool方法的聯繫及區別
在disruptor框架調用start方法之前,往往需要將消息的消費者指定給disruptor框架。

常用的方法是:disruptor.handleEventsWith(EventHandler ... handlers),將多個EventHandler的實現類傳入方法,封裝成一個EventHandlerGroup,實現多消費者消費。

disruptor的另一個方法是:disruptor.handleEventsWithWorkerPool(WorkHandler ... handlers),將多個WorkHandler的實現類傳入方法,封裝成一個EventHandlerGroup實現多消費者消費。

兩者共同點都是,將多個消費者封裝到一起,供框架消費消息。

不同點在於,

4.1.1. 對於某一條消息m,handleEventsWith方法返回的EventHandlerGroup,Group中的每個消費者都會對m進行消費,各個消費者之間不存在競爭。handleEventsWithWorkerPool方法返回的EventHandlerGroup,Group的消費者對於同一條消息m不重複消費;也就是,如果c0消費了消息m,則c1不再消費消息m。

4.1.2. 傳入的形參不同。對於獨立消費的消費者,應當實現EventHandler接口。對於不重複消費的消費者,應當實現WorkHandler接口。

因此,根據消費者集合是否獨立消費消息,可以對不同的接口進行實現。也可以對兩種接口同時實現,具體消費流程由disruptor的方法調用決定。

API示例

https://www.cnblogs.com/pku-liuqiang/p/8544700.html

4.2應用場景

4.2.1車輛入場案例

入場後需要存入數據庫,需要發送kafka消息,兩步執行完後,給用戶發送短信。代碼實現如下:(經過自己實測單生產,單消費者的模式如果性能瓶頸在寫入數據庫那麼引入disruptor也不能明顯提高性能)

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
import com.lmax.disruptor.EventFactory;
import com.lmax.disruptor.YieldingWaitStrategy;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.EventHandlerGroup;
import com.lmax.disruptor.dsl.ProducerType;
/**
 * 測試 P1生產消息,C1,C2消費消息,C1和C2會共享所有的event元素! C3依賴C1,C2處理結果
 * @author lzhcode
 *
 */
public class Main {
	
	public static void main(String[] args) throws InterruptedException {
		long beginTime = System.currentTimeMillis();
 
		//最好是2的n次方
		int bufferSize = 1024;
		// Disruptor交給線程池來處理,共計 p1,c1,c2,c3四個線程
		ExecutorService executor = Executors.newFixedThreadPool(4);
		// 構造緩衝區與事件生成
		Disruptor<InParkingDataEvent> disruptor = new Disruptor<InParkingDataEvent>(
				new EventFactory<InParkingDataEvent>() {
					@Override
					public InParkingDataEvent newInstance() {
						return new InParkingDataEvent();
					}
				}, bufferSize, executor, ProducerType.SINGLE, new YieldingWaitStrategy());
 
		// 使用disruptor創建消費者組C1,C2
		EventHandlerGroup<InParkingDataEvent> handlerGroup = disruptor.handleEventsWith(new ParkingDataToKafkaHandler(),
				new ParkingDataInDbHandler());
 
		ParkingDataSmsHandler smsHandler = new ParkingDataSmsHandler();
		// 聲明在C1,C2完事之後執行JMS消息發送操作 也就是流程走到C3
		handlerGroup.then(smsHandler);
 
		disruptor.start();// 啓動
		CountDownLatch latch = new CountDownLatch(1);
		// 生產者準備
		executor.submit(new InParkingDataEventPublisher(latch, disruptor));
		latch.await();// 等待生產者結束
		disruptor.shutdown();
		executor.shutdown();
 
		System.out.println("總耗時:" + (System.currentTimeMillis() - beginTime));
	}
}

4.2.2 大文件內容hash後輸入到小文件的案例

文件中存放50億個url,每個url各佔64字節,內存限制是4G。按照每個url64字節來算,每個文件有50億個url,那麼每個文件大小爲5G*64=320G。320G遠遠超出內存限定的4G,分給四個線程分別處理,每個線程處理80G文件內容,單線程內需要循環處理20次。每次處理完後,根據url的hash值輸出到對應小文件,然後進行下一次處理。

這樣的方法有兩個弊端:

同一個線程內,讀寫相互依賴,互相等待

不同線程可能爭奪同一個輸出文件,需要lock同步

於是改爲如下方法,四個線程讀取數據,計算hash值,將信息寫入相應disruptor。每個線程對應disruptor的一個消費者,將disruptor中的信息落盤持久化(使用disruptor的多生產者單消費者模型)。對於四個讀取線程(生產者)而言,只有讀取文件操作,沒有寫文件操作,因此不存在讀寫互相依賴的問題。對於disruptor消費線程而言,只存在寫文件操作,沒有讀文件,因此也不存在讀寫互相依賴的問題。同時disruptor的單消費者又很好的解決了多個線程互相競爭同一個文件的問題(disruptor的一個消費者是相當於一個線程),因此可以大大提高程序的吞吐率。

核心框架代碼如下:

import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
 
import com.lmax.disruptor.EventFactory;
import com.lmax.disruptor.ExceptionHandler;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.SequenceBarrier;
import com.lmax.disruptor.WorkerPool;
import com.lmax.disruptor.YieldingWaitStrategy;
import com.lmax.disruptor.dsl.ProducerType;
/**
 * 多生產者多消費者模型
 * @author lzhcode
 *
 */
public class Main {
 
	public static void main(String[] args) throws InterruptedException {
		
		//1 創建RingBuffer
		RingBuffer<Order> ringBuffer =
				RingBuffer.create(ProducerType.MULTI,
						new EventFactory<Order>() {
							public Order newInstance() {
								return new Order();
							}
						},
						1024*1024,
						new YieldingWaitStrategy());
		
		//2 通過ringBuffer 創建一個屏障
		SequenceBarrier sequenceBarrier = ringBuffer.newBarrier();
		
		//3 創建一個消費者:
		Consumer consumer = new Consumer;
	 
		
		//4 構建多消費者工作池
		WorkerPool<Order> workerPool = new WorkerPool<Order>(
				ringBuffer,
				sequenceBarrier,
				new EventExceptionHandler(),
				consumer );
		
		//5 設置消費者的sequence序號 用於單獨統計消費進度, 並且設置到ringbuffer中
		ringBuffer.addGatingSequences(workerPool.getWorkerSequences());
		
		//6 啓動workerPool
		workerPool
		.start(Executors.newFixedThreadPool(5));
		
		final CountDownLatch latch = new CountDownLatch(1);
		
		for(int i = 0; i < 100; i++) {
			final Producer producer = new Producer(ringBuffer);
			new Thread(new Runnable() {
				public void run() {
					try {
						latch.await();
					} catch (Exception e) {
						e.printStackTrace();
					}
					for(int j = 0; j<100; j++) {
						producer.sendData(UUID.randomUUID().toString());
					}
				}
			}).start();
		}
		
		Thread.sleep(2000);
		System.err.println("----------線程創建完畢,開始生產數據----------");
		latch.countDown();
		
		Thread.sleep(10000);
		
		System.err.println("任務總數:" + consumers[2].getCount());
	}
	
	static class EventExceptionHandler implements ExceptionHandler<Order> {
		public void handleEventException(Throwable ex, long sequence, Order event) {
		}
 
		public void handleOnStartException(Throwable ex) {
		}
 
		public void handleOnShutdownException(Throwable ex) {
		}
	}
	
}

 

4.2.3 Netty整合併發編程框架Disruptor實戰百萬長鏈接服務構建案例

對於一個server,我們一般考慮他所能支撐的qps,但有那麼一種應用, 我們需要關注的是它能支撐的連接數個數,而並非qps,當然qps也是我們需要考慮的性能點之一。這種應用常見於消息推送系統,比如聊天室或即時消息推送系統等。c對於這類系統,因爲很多消息需要到產生時才推送給客戶端,所以當沒有消 息產生時,就需要hold住客戶端的連接,這樣,當有大量的客戶端時,就需要hold住大量的連接,這種連接我們稱爲長連接。

首先,我們分析一下,對於這類服務,需消耗的系統資源有:cpu、網絡、內存。所以,想讓系統性能達到最佳,我們先找到系統的瓶頸所在。這樣的長連 接,往往我們是沒有數據發送的,所以也可以看作爲非活動連接。對於系統來說,這種非活動連接,並不佔用cpu與網絡資源,而僅僅佔用系統的內存而已。所以,我們假想,只要系統內存足夠,系統就能夠支持我們想達到的連接數,那麼事實是否真的如此?

在 Linux 內核配置上,默認的配置會限制全局最大打開文件數(Max Open Files)還會限制進程數。 所以需要對 Linux 內核配置進行一定的修改纔可以。具體如何修改這裏不做討論

java 中用的是非阻塞 IO(NIO 和 AIO 都算),那麼它們都可以用單線程來實現大量的 Socket 連接。 不會像 BIO 那樣爲每個連接創建一個線程,因爲代碼層面不會成爲瓶頸,最主要的是把業務代碼用disruptor來進行解耦
 

參考代碼:

https://gitee.com/lzhcode/maven-parent/tree/master/lzh-disruptor/lzh-disruptor-netty-server
https://gitee.com/lzhcode/maven-parent/tree/master/lzh-disruptor/lzh-disruptor-netty-com
https://gitee.com/lzhcode/maven-parent/tree/master/lzh-disruptor/lzh-disruptor-netty-client
————————————————
版權聲明:本文爲CSDN博主「擊水三千里」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/lzhcoder/article/details/84937588

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章