1. java是如何實現客服在線聊天功能的
Java 實現在線客服聊天功能的具體方式會因具體實現技術和業務需求不同而異,以下是一個可能的實耐爛緩現思路:
客戶端和服務端之間的通信協議:在實現在線聊天功能的時候,需要考慮客戶端和服務端之間的通信協議。可以使用 WebSocket 協議,這是一種全雙工通信協議,支持客戶端和服務端之間的實時通信。Java 提供了多個 WebSocket 實現,比如 Tyrus、Jetty 和 Netty。
實現服務端:在服務端實現在線聊天功能,需要創建 WebSocket 伺服器,並實現消息處理邏輯。在 Java 中,可以使用 Java WebSocket API,該 API 提供了 javax.websocket 包中的類和介面,可以方便地創建 WebSocket 伺服器和處理 WebSocket 消息。
實現客戶端:在客戶端實現在線聊天功能,需要創建 WebSocket 客戶端,並實現消息處理邏輯。Java 提供了多個 WebSocket 客戶端實現,比如 Tyrus、Jetty 和 Netty。
存儲聊天記錄:在實現在線聊天功能時,需要考慮如何存儲聊天記錄。可以使用資料庫或者文件等方式存儲聊天記錄,具體實現可以依據具體業務需求。
以上是一種可能的實現思路,昌模實現在線聊天功能需要考慮很多具體細節,包括客戶端和服務端的具體實現、消息處理邏輯、聊天記錄存儲等。
在服務端,需要實現 WebSocket 端點(Endpoint),處理客戶端連接、斷開連接以及收發消息等操作。可以通過擴展 javax.websocket.Endpoint 類,重寫 onOpen、onClose 和 onMessage 方法來處理相應的操作。
在客戶端,可以使用 Java WebSocket API 提供的 javax.websocket 包中的歷粗類和介面來實現 WebSocket 客戶端。需要使用 javax.websocket.ClientEndpoint 註解來標記客戶端類,並使用 javax.websocket.Session 類來處理客戶端連接、斷開連接以及收發消息等操作。
2. Netty內存管理
ByteBuf底層是一個位元組數組,內部維護了兩個索引:readerIndex與writerIndex。其中0 --> readerIndex部分為可丟棄位元組,表示已被讀取過,readerIndex --> writerIndex部分為可讀位元組,writerIndex --> capacity部分為可寫位元組。ByteBuf支持動態擴容,在實例化時會傳入maxCapacity,當writerIndex達到capacity且capacity小於maxCapacity時會進行自動擴容。
ByteBuf子類可以按照以下三個緯度進行分類:
在進入內存分配核心邏輯前,我們先對Netty內存分配相關概念做下了解。Netty內存管理借鑒jemalloc思想,為了提高內存利用率,根據不同內存規格使用不同的分配策略,並且使用緩存提高內存分配效率。
Netty有四種內存規格,tiny表示16B ~ 512B之間的內存塊,samll表示512B ~ 8K之間的內存塊,normal表示8K ~ 16M的內存塊,Huge表示大於16M的內存塊。
Chunk是Netty向操作系統申請內存的單位,默認一次向操作系統申請16M內存,Netty內部將Chunk按照Page大小劃分為2048塊。我們申請內存時如果大於16M,則Netty會直接向操作系統申請對應大小內存,如果申請內存在8k到16M之間則會分配對應個數Page進行使用。如果申請內存遠小於8K,那麼直接使用一個Page會造成內存浪費,SubPage就是對Page進行再次分配,減少內存浪費。
如果申請內存小於8K,會對Page進行再次劃分為SubPage,SubPage大小為Page大小/申請內存大小。SubPage又劃分為tiny與small兩種。
負責管理從操作系統中申請到的內存塊,Netty為了減少多線程競爭arena,採用多arena設計,arena數量默認為2倍CPU核心數。線程與arena關系如下:
線程本地緩存,負責創建線程緩存PoolThreadCache。PoolThreadCache中會初始化三種類型MemoryRegionCache數組,用以緩存線程中不同規格的內存塊,分別為:tiny、small、normal。tiny類型數組緩存的內存塊大小為16B ~ 512B之間,samll類型數組緩存的內存塊大小為512B ~ 8K之間的內存塊,normal類型數組緩存的內存塊大小受DEFAULT_MAX_CACHED_BUFFER_CAPACITY配置影響,默認只緩存8K、16K、32K三種類型內存塊。
內存塊緩存容器,負責緩存tiny、small、normal三種內存塊。其內部維護一個隊列,用於緩存同種內存大小的內存塊。
負責管理從操作系統申請的內存,內部採用夥伴演算法以Page為單位進行內存的分配與管理。
負責管理Chunk列表,根據內存使用率,分為:qInit、q000、q025、q050、q075、q100六種。每個PoolChunkList中存儲內存使用率相同的Chunk,Chunk以雙向鏈表進行關聯,同時不同使用率的PoolChunkList也以雙向列表進行關聯。這樣做的目的是因為隨著內存的分配,Chunk使用率會發生變化,以鏈表形式方便Chunk在不同使用率列表進行移動。
PoolSubpage負責tiny、small類型內存的管理與分配,實現基於SLAB內存分配演算法。PoolArena中有兩種PoolSubpage類型數組,分別為:tinySubpagePools、smallSubpagePools。tinySubpagePools負責管理tiny類型內存,數組大小為512/16=32種。smallSubpagePools負責管理small類型內存,數組大小為4。
PoolSubpage數組中存儲不同內存大小的PoolSubpage節點,相同大小節點以鏈表進行關聯。PoolSubpage內部使用點陣圖數組記錄內存分配情況。
Netty通過ByteBufAllocator進行內存分配,ByteBufAllocator有兩個實現類:PooledByteBufAllocator與UnpooledByteBufAllocator,其中,是否在堆內存或者直接內存分配與是否使用unsafe進行讀寫操作都封裝在其實現類中。
我們先看下ByteBufAllocator類圖:
PooledByteBufAllocator與UnpooledByteBufAllocator內存分配類似,可以通過newHeapBuffer與newDirectBuffer進行分配內存,我們以PooledByteBufAllocator為例分析下內存分配流程:
以PooledByteBufAllocator為例來分析下內存分配器實例化過程。首先調用PooledByteBufAllocator#DEFAULT方法實例化PooledByteBufAllocator
PooledByteBufAllocator實例化時會初始化幾個比較重要的屬性:
最終會調用PooledByteBufAllocator如下構造方法:
PooledByteBufAllocator構造方法主要做了兩件事情,一是:初始化PoolThreadLocalCache屬性,二是:初始化堆內存與直接內存類型PoolArena數組,我們進入PoolArena.DirectArena構造方法,來分析下PoolArena初始化時主要做了哪些事情:
DirectArena構造方法會調用其父類PoolArena構造方法,在PoolArena構造方法中會初始化tiny類型與small類型PoolSubpage數組,並初始化六種不同內存使用率的PoolChunkList,每個PoolChunkList以雙向鏈表進行關聯。
以分配直接內存為例,分析內存分配的主要流程:
PooledByteBufAllocator#directBuffer方法最終會調用如下構造方法,其中maxCapacity為Integer.MAX_VALUE:
該方法主要分三步,第一步:獲取線程緩存,第二步:分配內存,第三步:將ByteBuf轉為具有內存泄漏檢測功能的ByteBuf,我們來分析下每一步具體做了哪些事情:
1.獲取線程緩存,從PoolThreadLocalCache中獲取PoolThreadCache,首次調用會先進行進行初始化,並將結果緩存下來:
初始化方法在PoolThreadLocalCache中,首先會循環找到使用最少的PoolArena,然後調用PoolThreadCache構造方法創建PoolThreadCache:
PoolThreadCache構造方法中會初始化tinySubPageDirectCaches、smallSubPageDirectCaches、normalDirectCaches這三種MemoryRegionCache數組:
createSubPageCaches方法中會創建並初始化MemoryRegionCache數組,其中tiny類型數組大小為32,small類型數組大小為4,normal類型數組大小為3:
最終會調用MemoryRegionCache構造方法進行創建,我們看下MemoryRegionCache結構:
2.分配內存,首先會獲取PooledByteBuf,然後進行內存分配:
newByteBuf方法會嘗試從對象池裡面獲取pooledByteBuf,如果沒有則進行創建。allocate方法為內存分配核心邏輯,主要分為兩種分配方式:page級別內存分配(8k 16M)、subPage級別內存分配(0 8K)、huge級別內存分配(>16M)。page與subPage級別內存分配首先會嘗試從緩存上進行內存分配,如果分配失敗則重新申請內存。huge級別內存分配不會通過緩存進行分配。我們看下allocate方法主要流程:
首先嘗試從緩存中進行分配:
cacheForTiney方法先根據分配內存大小定位到對應的tinySubPageDirectCaches數組中MemoryRegionCache,如果沒有定位到則不能在緩存中進行分配。如果有則從MemoryRegionCache對應的隊列中彈出一個PooledByteBuf對象進行初始化,同時為了復用PooledByteBuf對象,會將其緩存下來。
如果從緩存中分配不成功,則會從對應的PoolSubpage數組上進行分配,如果PoolSubpage數組對應的內存大小下標中有可分配空間則進行分配,並對PooledByteBuf進行初始化。
如果在PoolSubpage數組上分配不成功,則表示沒有可以用來分配的SubPage,則會嘗試從Page上進行分配。先嘗試從不同內存使用率的ChunkList進行分配,如果仍分配不成功,則表示沒有可以用來分配的Chunk,此時會創建新的Chunk進行內存分配。
進入PoolChunk#allocate方法看下分配流程:
allocateRun方法用來分配大於等於8K的內存,allocateSubpage用來分配小於8K的內存,進入allocateSubpage方法:
內存分配成功後會調用initBuf方法初始化PoolByteBuf:
Page級別內存分配和SubPage級別類似,同樣是先從緩存中進行分配,分配不成功則嘗試從不同內存使用率的ChunkList進行分配,如果仍分配不成功,則表示沒有可以用來分配的Chunk,此時會創建新的Chunk進行內存分配,不同點在allocate方法中:
因為大於16M的內存分配Netty不會進行緩存,所以Huge級別內存分配會直接申請內存並進行初始化:
調用ByteBuf#release方法會進行內存釋放,方法中會判斷當前byteBuf 是否被引用,如果沒有被引用, 則調用deallocate方法進行釋放:
進入deallocate方法看下內存釋放流程:
free方法會把釋放的內存加入到緩存,如果加入緩存不成功則會標記這段內存為未使用:
recycle方法會將PoolByteBuf對象放入到對象池中: