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示例
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