pos機防拆原理,O體系從原理到應用

 新聞資訊  |   2023-05-04 10:02  |  投稿人:pos機之家

網(wǎng)上有很多關(guān)于pos機防拆原理,O體系從原理到應用的知識,也有很多人為大家解答關(guān)于pos機防拆原理的問(wèn)題,今天pos機之家(www.xjcwpx.cn)為大家整理了關(guān)于這方面的知識,讓我們一起來(lái)看下吧!

本文目錄一覽:

1、pos機防拆原理

pos機防拆原理

本文介紹操作系統I/O工作原理,Java I/O設計,基本使用,開(kāi)源項目中實(shí)現高性能I/O常見(jiàn)方法和實(shí)現,徹底搞懂高性能I/O之道

基礎概念

在介紹I/O原理之前,先重溫幾個(gè)基礎概念:

(1) 操作系統與內核

操作系統:管理計算機硬件與軟件資源的系統軟件內核:操作系統的核心軟件,負責管理系統的進(jìn)程、內存、設備驅動(dòng)程序、文件和網(wǎng)絡(luò )系統等等,為應用程序提供對計算機硬件的安全訪(fǎng)問(wèn)服務(wù)

2 內核空間和用戶(hù)空間

為了避免用戶(hù)進(jìn)程直接操作內核,保證內核安全,操作系統將內存尋址空間劃分為兩部分:內核空間(Kernel-space),供內核程序使用用戶(hù)空間(User-space),供用戶(hù)進(jìn)程使用為了安全,內核空間和用戶(hù)空間是隔離的,即使用戶(hù)的程序崩潰了,內核也不受影響

3 數據流

計算機中的數據是基于隨著(zhù)時(shí)間變換高低電壓信號傳輸的,這些數據信號連續不斷,有著(zhù)固定的傳輸方向,類(lèi)似水管中水的流動(dòng),因此抽象數據流(I/O流)的概念:指一組有順序的、有起點(diǎn)和終點(diǎn)的字節集合,

抽象出數據流的作用:實(shí)現程序邏輯與底層硬件解耦,通過(guò)引入數據流作為程序與硬件設備之間的抽象層,面向通用的數據流輸入輸出接口編程,而不是具體硬件特性,程序和底層硬件可以獨立靈活替換和擴展

I/O 工作原理

1 磁盤(pán)I/O

典型I/O讀寫(xiě)磁盤(pán)工作原理如下:

tips: DMA:全稱(chēng)叫直接內存存?。―irect Memory Access),是一種允許外圍設備(硬件子系統)直接訪(fǎng)問(wèn)系統主內存的機制?;?DMA 訪(fǎng)問(wèn)方式,系統主內存與硬件設備的數據傳輸可以省去CPU 的全程調度

值得注意的是:

讀寫(xiě)操作基于系統調用實(shí)現讀寫(xiě)操作經(jīng)過(guò)用戶(hù)緩沖區,內核緩沖區,應用進(jìn)程并不能直接操作磁盤(pán)應用進(jìn)程讀操作時(shí)需阻塞直到讀取到數據

2 網(wǎng)絡(luò )I/O

這里先以最經(jīng)典的阻塞式I/O模型介紹:

tips:recvfrom,經(jīng)socket接收數據的函數

值得注意的是:

網(wǎng)絡(luò )I/O讀寫(xiě)操作經(jīng)過(guò)用戶(hù)緩沖區,Sokcet緩沖區服務(wù)端線(xiàn)程在從調用recvfrom開(kāi)始到它返回有數據報準備好這段時(shí)間是阻塞的,recvfrom返回成功后,線(xiàn)程開(kāi)始處理數據報Java I/O設計

1 I/O分類(lèi)

Java中對數據流進(jìn)行具體化和實(shí)現,關(guān)于Java數據流一般關(guān)注以下幾個(gè)點(diǎn):

(1) 流的方向從外部到程序,稱(chēng)為輸入流;從程序到外部,稱(chēng)為輸出流(2) 流的數據單位程序以字節作為最小讀寫(xiě)數據單元,稱(chēng)為字節流,以字符作為最小讀寫(xiě)數據單元,稱(chēng)為字符流(3) 流的功能角色

從/向一個(gè)特定的IO設備(如磁盤(pán),網(wǎng)絡(luò ))或者存儲對象(如內存數組)讀/寫(xiě)數據的流,稱(chēng)為節點(diǎn)流;對一個(gè)已有流進(jìn)行連接和封裝,通過(guò)封裝后的流來(lái)實(shí)現數據的讀/寫(xiě)功能,稱(chēng)為處理流(或稱(chēng)為過(guò)濾流);

2 I/O操作接口

java.io包下有一堆I/O操作類(lèi),初學(xué)時(shí)看了容易搞不懂,其實(shí)仔細觀(guān)察其中還是有規律:這些I/O操作類(lèi)都是在繼承4個(gè)基本抽象流的基礎上,要么是節點(diǎn)流,要么是處理流

2.1 四個(gè)基本抽象流

java.io包中包含了流式I/O所需要的所有類(lèi),java.io包中有四個(gè)基本抽象流,分別處理字節流和字符流:

InputStreamOutputStreamReaderWriter

2.2 節點(diǎn)流

節點(diǎn)流I/O類(lèi)名由節點(diǎn)流類(lèi)型 + 抽象流類(lèi)型組成,常見(jiàn)節點(diǎn)類(lèi)型有:

File文件Piped 進(jìn)程內線(xiàn)程通信管道byteArray / CharArray (字節數組 / 字符數組)StringBuffer / String (字符串緩沖區 / 字符串)

節點(diǎn)流的創(chuàng )建通常是在構造函數傳入數據源,例如:

FileReader reader = new FileReader(new File("file.txt"));FileWriter writer = new FileWriter(new File("file.txt"));

2.3 處理流

處理流I/O類(lèi)名由對已有流封裝的功能 + 抽象流類(lèi)型組成,常見(jiàn)功能有:

緩沖:對節點(diǎn)流讀寫(xiě)的數據提供了緩沖的功能,數據可以基于緩沖批量讀寫(xiě),提高效率。常見(jiàn)有BufferedInputStream、BufferedOutputStream字節流轉換為字符流:由InputStreamReader、OutputStreamWriter實(shí)現字節流與基本類(lèi)型數據相互轉換:這里基本數據類(lèi)型數據如int、long、short,由DataInputStream、DataOutputStream實(shí)現字節流與對象實(shí)例相互轉換:用于實(shí)現對象序列化,由ObjectInputStream、ObjectOutputStream實(shí)現

處理流的應用了適配器/裝飾模式,轉換/擴展已有流,處理流的創(chuàng )建通常是在構造函數傳入已有的節點(diǎn)流或處理流:

FileOutputStream fileOutputStream = new FileOutputStream("file.txt");// 擴展提供緩沖寫(xiě)BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); // 擴展提供提供基本數據類(lèi)型寫(xiě)DataOutputStream out = new DataOutputStream(bufferedOutputStream);

3 Java NIO

3.1 標準I/O存在問(wèn)題

Java NIO(New I/O)是一個(gè)可以替代標準Java I/O API的IO API(從Java 1.4開(kāi)始),Java NIO提供了與標準I/O不同的I/O工作方式,目的是為了解決標準 I/O存在的以下問(wèn)題:

(1) 數據多次拷貝

標準I/O處理,完成一次完整的數據讀寫(xiě),至少需要從底層硬件讀到內核空間,再讀到用戶(hù)文件,又從用戶(hù)空間寫(xiě)入內核空間,再寫(xiě)入底層硬件

此外,底層通過(guò)write、read等函數進(jìn)行I/O系統調用時(shí),需要傳入數據所在緩沖區起始地址和長(cháng)度由于JVM GC的存在,導致對象在堆中的位置往往會(huì )發(fā)生移動(dòng),移動(dòng)后傳入系統函數的地址參數就不是真正的緩沖區地址了

可能導致讀寫(xiě)出錯,為了解決上面的問(wèn)題,使用標準I/O進(jìn)行系統調用時(shí),還會(huì )額外導致一次數據拷貝:把數據從JVM的堆內拷貝到堆外的連續空間內存(堆外內存)

所以總共經(jīng)歷6次數據拷貝,執行效率較低

(2) 操作阻塞

傳統的網(wǎng)絡(luò )I/O處理中,由于請求建立連接(connect),讀取網(wǎng)絡(luò )I/O數據(read),發(fā)送數據(send)等操作是線(xiàn)程阻塞的

// 等待連接Socket socket = serverSocket.accept();// 連接已建立,讀取請求消息StringBuilder req = new StringBuilder();byte[] recvByteBuf = new byte[1024];int len;while ((len = socket.getInputStream().read(recvByteBuf)) != -1) { req.append(new String(recvByteBuf, 0, len, StandardCharsets.UTF_8));}// 寫(xiě)入返回消息socket.getOutputStream().write(("server response msg".getbytes()));socket.shutdownOutput();

以上面服務(wù)端程序為例,當請求連接已建立,讀取請求消息,服務(wù)端調用read方法時(shí),客戶(hù)端數據可能還沒(méi)就緒(例如客戶(hù)端數據還在寫(xiě)入中或者傳輸中),線(xiàn)程需要在read方法阻塞等待直到數據就緒

為了實(shí)現服務(wù)端并發(fā)響應,每個(gè)連接需要獨立的線(xiàn)程單獨處理,當并發(fā)請求量大時(shí)為了維護連接,內存、線(xiàn)程切換開(kāi)銷(xiāo)過(guò)大

3.2 Buffer

Java NIO核心三大核心組件是Buffer(緩沖區)、Channel(通道)、Selector

Buffer提供了常用于I/O操作的字節緩沖區,常見(jiàn)的緩存區有ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer,分別對應基本數據類(lèi)型: byte, char, double, float, int, long, short,下面介紹主要以最常用的ByteBuffer為例,Buffer底層支持Java堆內(HeapByteBuffer)或堆外內存(DirectByteBuffer)

堆外內存是指與堆內存相對應的,把內存對象分配在JVM堆以外的內存,這些內存直接受操作系統管理(而不是虛擬機,相比堆內內存,I/O操作中使用堆外內存的優(yōu)勢在于:

不用被JVM GC線(xiàn)回收,減少GC線(xiàn)程資源占有在I/O系統調用時(shí),直接操作堆外內存,可以節省一次堆外內存和堆內內存的復制

ByteBuffer底層堆外內存的分配和釋放基于malloc和free函數,對外allocateDirect方法可以申請分配堆外內存,并返回繼承ByteBuffer類(lèi)的DirectByteBuffer對象:

public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity);}

堆外內存的回收基于DirectByteBuffer的成員變量Cleaner類(lèi),提供clean方法可以用于主動(dòng)回收,Netty中大部分堆外內存通過(guò)記錄定位Cleaner的存在,主動(dòng)調用clean方法來(lái)回收;另外,當DirectByteBuffer對象被GC時(shí),關(guān)聯(lián)的堆外內存也會(huì )被回收

tips: JVM參數不建議設置-XX:+DisableExplicitGC,因為部分依賴(lài)Java NIO的框架(例如Netty)在內存異常耗盡時(shí),會(huì )主動(dòng)調用System.gc(),觸發(fā)Full GC,回收DirectByteBuffer對象,作為回收堆外內存的最后保障機制,設置該參數之后會(huì )導致在該情況下堆外內存得不到清理

堆外內存基于基礎ByteBuffer類(lèi)的DirectByteBuffer類(lèi)成員變量:Cleaner對象,這個(gè)Cleaner對象會(huì )在合適的時(shí)候執行unsafe.freeMemory(address),從而回收這塊堆外內存

Buffer可以見(jiàn)到理解為一組基本數據類(lèi)型,存儲地址連續的的數組,支持讀寫(xiě)操作,對應讀模式和寫(xiě)模式,通過(guò)幾個(gè)變量來(lái)保存這個(gè)數據的當前位置狀態(tài):capacity、 position、 limit:

capacity 緩沖區數組的總長(cháng)度position 下一個(gè)要操作的數據元素的位置limit 緩沖區數組中不可操作的下一個(gè)元素的位置:limit <= capacity

3.3 Channel

Channel(通道)的概念可以類(lèi)比I/O流對象,NIO中I/O操作主要基于Channel:從Channel進(jìn)行數據讀取 :創(chuàng )建一個(gè)緩沖區,然后請求Channel讀取數據從Channel進(jìn)行數據寫(xiě)入 :創(chuàng )建一個(gè)緩沖區,填充數據,請求Channel寫(xiě)入數據

Channel和流非常相似,主要有以下幾點(diǎn)區別:

Channel可以讀和寫(xiě),而標準I/O流是單向的Channel可以異步讀寫(xiě),標準I/O流需要線(xiàn)程阻塞等待直到讀寫(xiě)操作完成Channel總是基于緩沖區Buffer讀寫(xiě)

Java NIO中最重要的幾個(gè)Channel的實(shí)現:

FileChannel: 用于文件的數據讀寫(xiě),基于FileChannel提供的方法能減少讀寫(xiě)文件數據拷貝次數,后面會(huì )介紹DatagramChannel: 用于UDP的數據讀寫(xiě)SocketChannel: 用于TCP的數據讀寫(xiě),代表客戶(hù)端連接ServerSocketChannel: 監聽(tīng)TCP連接請求,每個(gè)請求會(huì )創(chuàng )建會(huì )一個(gè)SocketChannel,一般用于服務(wù)端

基于標準I/O中,我們第一步可能要像下面這樣獲取輸入流,按字節把磁盤(pán)上的數據讀取到程序中,再進(jìn)行下一步操作,而在NIO編程中,需要先獲取Channel,再進(jìn)行讀寫(xiě)

FileInputStream fileInputStream = new FileInputStream("test.txt");FileChannel channel = fileInputStream.channel();

tips: FileChannel僅能運行在阻塞模式下,文件異步處理的 I/O 是在JDK 1.7 才被加入的 java.nio.channels.AsynchronousFileChannel

// server socket channel:ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLocalHost(), 9091));while (true) { SocketChannel socketChannel = serverSocketChannel.accept(); ByteBuffer buffer = ByteBuffer.allocateDirect(1024); int readBytes = socketChannel.read(buffer); if (readBytes > 0) { // 從寫(xiě)數據到buffer翻轉為從buffer讀數據 buffer.flip(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); String body = new String(bytes, StandardCharsets.UTF_8); System.out.println("server 收到:" + body); }}

3.4 Selector

Selector(選擇器) ,它是Java NIO核心組件中的一個(gè),用于檢查一個(gè)或多個(gè)NIO Channel(通道)的狀態(tài)是否處于可讀、可寫(xiě)。實(shí)現單線(xiàn)程管理多個(gè)Channel,也就是可以管理多個(gè)網(wǎng)絡(luò )連接

Selector核心在于基于操作系統提供的I/O復用功能,單個(gè)線(xiàn)程可以同時(shí)監視多個(gè)連接描述符,一旦某個(gè)連接就緒(一般是讀就緒或者寫(xiě)就緒),能夠通知程序進(jìn)行相應的讀寫(xiě)操作,常見(jiàn)有select、poll、epoll等不同實(shí)現

Java NIO Selector基本工作原理如下:

(1) 初始化Selector對象,服務(wù)端ServerSocketChannel對象(2) 向Selector注冊ServerSocketChannel的socket-accept事件(3) 線(xiàn)程阻塞于selector.select(),當有客戶(hù)端請求服務(wù)端,線(xiàn)程退出阻塞(4) 基于selector獲取所有就緒事件,此時(shí)先獲取到socket-accept事件,向Selector注冊客戶(hù)端SocketChannel的數據就緒可讀事件事件(5) 線(xiàn)程再次阻塞于selector.select(),當有客戶(hù)端連接數據就緒,可讀(6) 基于ByteBuffer讀取客戶(hù)端請求數據,然后寫(xiě)入響應數據,關(guān)閉channel

示例如下,完整可運行代碼已經(jīng)上傳github(https://github.com/caison/caison-blog-demo):

Selector selector = Selector.open();ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.bind(new InetSocketAddress(9091));// 配置通道為非阻塞模式serverSocketChannel.configureBlocking(false);// 注冊服務(wù)端的socket-accept事件serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);while (true) { // selector.select()會(huì )一直阻塞,直到有channel相關(guān)操作就緒 selector.select(); // SelectionKey關(guān)聯(lián)的channel都有就緒事件 Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); // 服務(wù)端socket-accept if (key.isAcceptable()) { // 獲取客戶(hù)端連接的channel SocketChannel clientSocketChannel = serverSocketChannel.accept(); // 設置為非阻塞模式 clientSocketChannel.configureBlocking(false); // 注冊監聽(tīng)該客戶(hù)端channel可讀事件,并為channel關(guān)聯(lián)新分配的buffer clientSocketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocateDirect(1024)); } // channel可讀 if (key.isReadable()) { SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer buf = (ByteBuffer) key.attachment(); int bytesRead; StringBuilder reqMsg = new StringBuilder(); while ((bytesRead = socketChannel.read(buf)) > 0) { // 從buf寫(xiě)模式切換為讀模式 buf.flip(); int bufRemain = buf.remaining(); byte[] bytes = new byte[bufRemain]; buf.get(bytes, 0, bytesRead); // 這里當數據包大于byteBuffer長(cháng)度,有可能有粘包/拆包問(wèn)題 reqMsg.append(new String(bytes, StandardCharsets.UTF_8)); buf.clear(); } System.out.println("服務(wù)端收到報文:" + reqMsg.toString()); if (bytesRead == -1) { byte[] bytes = "[這是服務(wù)回的報文的報文]".getBytes(StandardCharsets.UTF_8); int length; for (int offset = 0; offset < bytes.length; offset += length) { length = Math.min(buf.capacity(), bytes.length - offset); buf.clear(); buf.put(bytes, offset, length); buf.flip(); socketChannel.write(buf); } socketChannel.close(); } } // Selector不會(huì )自己從已selectedKeys中移除SelectionKey實(shí)例 // 必須在處理完通道時(shí)自己移除 下次該channel變成就緒時(shí),Selector會(huì )再次將其放入selectedKeys中 keyIterator.remove(); }}

tips: Java NIO基于Selector實(shí)現高性能網(wǎng)絡(luò )I/O這塊使用起來(lái)比較繁瑣,使用不友好,一般業(yè)界使用基于Java NIO進(jìn)行封裝優(yōu)化,擴展豐富功能的Netty框架來(lái)優(yōu)雅實(shí)現

高性能I/O優(yōu)化

下面結合業(yè)界熱門(mén)開(kāi)源項目介紹高性能I/O的優(yōu)化

1 零拷貝

零拷貝(zero copy)技術(shù),用于在數據讀寫(xiě)中減少甚至完全避免不必要的CPU拷貝,減少內存帶寬的占用,提高執行效率,零拷貝有幾種不同的實(shí)現原理,下面介紹常見(jiàn)開(kāi)源項目中零拷貝實(shí)現

1.1 Kafka零拷貝

Kafka基于Linux 2.1內核提供,并在2.4 內核改進(jìn)的的sendfile函數 + 硬件提供的DMA Gather Copy實(shí)現零拷貝,將文件通過(guò)socket傳送

函數通過(guò)一次系統調用完成了文件的傳送,減少了原來(lái)read/write方式的模式切換。同時(shí)減少了數據的copy, sendfile的詳細過(guò)程如下:

基本流程如下:

(1) 用戶(hù)進(jìn)程發(fā)起sendfile系統調用(2) 內核基于DMA Copy將文件數據從磁盤(pán)拷貝到內核緩沖區(3) 內核將內核緩沖區中的文件描述信息(文件描述符,數據長(cháng)度)拷貝到Socket緩沖區(4) 內核基于Socket緩沖區中的文件描述信息和DMA硬件提供的Gather Copy功能將內核緩沖區數據復制到網(wǎng)卡(5) 用戶(hù)進(jìn)程sendfile系統調用完成并返回

相比傳統的I/O方式,sendfile + DMA Gather Copy方式實(shí)現的零拷貝,數據拷貝次數從4次降為2次,系統調用從2次降為1次,用戶(hù)進(jìn)程上下文切換次數從4次變成2次DMA Copy,大大提高處理效率

Kafka底層基于java.nio包下的FileChannel的transferTo:

public abstract long transferTo(long position, long count, WritableByteChannel target)

transferTo將FileChannel關(guān)聯(lián)的文件發(fā)送到指定channel,當Comsumer消費數據,Kafka Server基于FileChannel將文件中的消息數據發(fā)送到SocketChannel

1.2 RocketMQ零拷貝

RocketMQ基于mmap + write的方式實(shí)現零拷貝:mmap() 可以將內核中緩沖區的地址與用戶(hù)空間的緩沖區進(jìn)行映射,實(shí)現數據共享,省去了將數據從內核緩沖區拷貝到用戶(hù)緩沖區

tmp_buf = mmap(file, len); write(socket, tmp_buf, len);

mmap + write 實(shí)現零拷貝的基本流程如下:

(1) 用戶(hù)進(jìn)程向內核發(fā)起系統mmap調用(2) 將用戶(hù)進(jìn)程的內核空間的讀緩沖區與用戶(hù)空間的緩存區進(jìn)行內存地址映射(3) 內核基于DMA Copy將文件數據從磁盤(pán)復制到內核緩沖區(4) 用戶(hù)進(jìn)程mmap系統調用完成并返回(5) 用戶(hù)進(jìn)程向內核發(fā)起write系統調用(6) 內核基于CPU Copy將數據從內核緩沖區拷貝到Socket緩沖區(7) 內核基于DMA Copy將數據從Socket緩沖區拷貝到網(wǎng)卡(8) 用戶(hù)進(jìn)程write系統調用完成并返回

RocketMQ中消息基于mmap實(shí)現存儲和加載的邏輯寫(xiě)在org.apache.rocketmq.store.MappedFile中,內部實(shí)現基于nio提供的java.nio.MappedByteBuffer,基于FileChannel的map方法得到mmap的緩沖區:

// 初始化this.fileChannel = new RandomAccessFile(this.file, "rw").getChannel();this.mappedByteBuffer = this.fileChannel.map(MapMode.READ_WRITE, 0, fileSize);

查詢(xún)CommitLog的消息時(shí),基于mappedByteBuffer偏移量pos,數據大小size查詢(xún):

public SelectMappedBufferResult selectMappedBuffer(int pos, int size) { int readPosition = getReadPosition(); // ...各種安全校驗 // 返回mappedByteBuffer視圖 ByteBuffer byteBuffer = this.mappedByteBuffer.slice(); byteBuffer.position(pos); ByteBuffer byteBufferNew = byteBuffer.slice(); byteBufferNew.limit(size); return new SelectMappedBufferResult(this.fileFromOffset + pos, byteBufferNew, size, this);}

tips: transientStorePoolEnable機制Java NIO mmap的部分內存并不是常駐內存,可以被置換到交換內存(虛擬內存),RocketMQ為了提高消息發(fā)送的性能,引入了內存鎖定機制,即將最近需要操作的CommitLog文件映射到內存,并提供內存鎖定功能,確保這些文件始終存在內存中,該機制的控制參數就是transientStorePoolEnable

因此,MappedFile數據保存CommitLog刷盤(pán)有2種方式:

1 開(kāi)啟transientStorePoolEnable:寫(xiě)入內存字節緩沖區(writeBuffer) -> 從內存字節緩沖區(writeBuffer)提交(commit)到文件通道(fileChannel) -> 文件通道(fileChannel) -> flush到磁盤(pán)2 未開(kāi)啟transientStorePoolEnable:寫(xiě)入映射文件字節緩沖區(mappedByteBuffer) -> 映射文件字節緩沖區(mappedByteBuffer) -> flush到磁盤(pán)

RocketMQ 基于 mmap+write 實(shí)現零拷貝,適用于業(yè)務(wù)級消息這種小塊文件的數據持久化和傳輸Kafka 基于 sendfile 這種零拷貝方式,適用于系統日志消息這種高吞吐量的大塊文件的數據持久化和傳輸

tips: Kafka 的索引文件使用的是 mmap+write 方式,數據文件發(fā)送網(wǎng)絡(luò )使用的是 sendfile 方式

1.3 Netty零拷貝

Netty 的零拷貝分為兩種:

1 基于操作系統實(shí)現的零拷貝,底層基于FileChannel的transferTo方法2 基于Java 層操作優(yōu)化,對數組緩存對象(ByteBuf )進(jìn)行封裝優(yōu)化,通過(guò)對ByteBuf數據建立數據視圖,支持ByteBuf 對象合并,切分,當底層僅保留一份數據存儲,減少不必要拷貝

2 多路復用

Netty中對Java NIO功能封裝優(yōu)化之后,實(shí)現I/O多路復用代碼優(yōu)雅了很多:

// 創(chuàng )建mainReactorNioEventLoopGroup boosGroup = new NioEventLoopGroup();// 創(chuàng )建工作線(xiàn)程組NioEventLoopGroup workerGroup = new NioEventLoopGroup();final ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap // 組裝NioEventLoopGroup .group(boosGroup, workerGroup) // 設置channel類(lèi)型為NIO類(lèi)型 .channel(NioServerSocketChannel.class) // 設置連接配置參數 .option(ChannelOption.SO_BACKLOG, 1024) .childOption(ChannelOption.SO_KEEPALIVE, true) .childOption(ChannelOption.TCP_NODELAY, true) // 配置入站、出站事件handler .childHandler(new ChannelInitializer<NioSocketChannel>() { @Override protected void initChannel(NioSocketChannel ch) { // 配置入站、出站事件channel ch.pipeline().addLast(...); ch.pipeline().addLast(...); } });// 綁定端口int port = 8080;serverBootstrap.bind(port).addListener(future -> { if (future.isSuccess()) { System.out.println(new Date() + ": 端口[" + port + "]綁定成功!"); } else { System.err.println("端口[" + port + "]綁定失敗!"); }});

3 頁(yè)緩存(PageCache)

頁(yè)緩存(PageCache)是操作系統對文件的緩存,用來(lái)減少對磁盤(pán)的 I/O 操作,以頁(yè)為單位的,內容就是磁盤(pán)上的物理塊,頁(yè)緩存能幫助程序對文件進(jìn)行順序讀寫(xiě)的速度幾乎接近于內存的讀寫(xiě)速度,主要原因就是由于OS使用PageCache機制對讀寫(xiě)訪(fǎng)問(wèn)操作進(jìn)行了性能優(yōu)化:

頁(yè)緩存讀取策略:當進(jìn)程發(fā)起一個(gè)讀操作 (比如,進(jìn)程發(fā)起一個(gè) read() 系統調用),它首先會(huì )檢查需要的數據是否在頁(yè)緩存中:

如果在,則放棄訪(fǎng)問(wèn)磁盤(pán),而直接從頁(yè)緩存中讀取如果不在,則內核調度塊 I/O 操作從磁盤(pán)去讀取數據,并讀入緊隨其后的少數幾個(gè)頁(yè)面(不少于一個(gè)頁(yè)面,通常是三個(gè)頁(yè)面),然后將數據放入頁(yè)緩存中

頁(yè)緩存寫(xiě)策略:當進(jìn)程發(fā)起write系統調用寫(xiě)數據到文件中,先寫(xiě)到頁(yè)緩存,然后方法返回。此時(shí)數據還沒(méi)有真正的保存到文件中去,Linux 僅僅將頁(yè)緩存中的這一頁(yè)數據標記為“臟”,并且被加入到臟頁(yè)鏈表中

然后,由flusher 回寫(xiě)線(xiàn)程周期性將臟頁(yè)鏈表中的頁(yè)寫(xiě)到磁盤(pán),讓磁盤(pán)中的數據和內存中保持一致,最后清理“臟”標識。在以下三種情況下,臟頁(yè)會(huì )被寫(xiě)回磁盤(pán):

空閑內存低于一個(gè)特定閾值臟頁(yè)在內存中駐留超過(guò)一個(gè)特定的閾值時(shí)當用戶(hù)進(jìn)程調用 sync() 和 fsync() 系統調用時(shí)

RocketMQ中,ConsumeQueue邏輯消費隊列存儲的數據較少,并且是順序讀取,在page cache機制的預讀取作用下,Consume Queue文件的讀性能幾乎接近讀內存,即使在有消息堆積情況下也不會(huì )影響性能,提供了2種消息刷盤(pán)策略:

同步刷盤(pán):在消息真正持久化至磁盤(pán)后RocketMQ的Broker端才會(huì )真正返回給Producer端一個(gè)成功的ACK響應異步刷盤(pán),能充分利用操作系統的PageCache的優(yōu)勢,只要消息寫(xiě)入PageCache即可將成功的ACK返回給Producer端。消息刷盤(pán)采用后臺異步線(xiàn)程提交的方式進(jìn)行,降低了讀寫(xiě)延遲,提高了MQ的性能和吞吐量

Kafka實(shí)現消息高性能讀寫(xiě)也利用了頁(yè)緩存,這里不再展開(kāi)

參考

《深入理解Linux內核 —— Daniel P.Bovet》

http://calvin1978.blogcn.com/articles/directbytebuffer.html

https://mp.weixin.qq.com/s/c9tkrokcDQR375kiwCeV9w

https://www.kunzhao.org/blog/2018/03/12/rocketmq-message-store-flow

https://www.jianshu.com/p/6681bfa36c4f

更多精彩,歡迎關(guān)注公眾號【分布式系統架構】

以上就是關(guān)于pos機防拆原理,O體系從原理到應用的知識,后面我們會(huì )繼續為大家整理關(guān)于pos機防拆原理的知識,希望能夠幫助到大家!

轉發(fā)請帶上網(wǎng)址:http://www.xjcwpx.cn/news/36874.html

你可能會(huì )喜歡:

版權聲明:本文內容由互聯(lián)網(wǎng)用戶(hù)自發(fā)貢獻,該文觀(guān)點(diǎn)僅代表作者本人。本站僅提供信息存儲空間服務(wù),不擁有所有權,不承擔相關(guān)法律責任。如發(fā)現本站有涉嫌抄襲侵權/違法違規的內容, 請發(fā)送郵件至 babsan@163.com 舉報,一經(jīng)查實(shí),本站將立刻刪除。