当前位置:首页 » 服务存储 » netty聊天记录存储
扩展阅读
webinf下怎么引入js 2023-08-31 21:54:13
堡垒机怎么打开web 2023-08-31 21:54:11

netty聊天记录存储

发布时间: 2023-05-22 14:45:47

1. java是如何实现客服在线聊天功能的

Java 实现在线客服聊天功能的具体方式会因具体实现技术和业务需求不同而异,以下是一个可能的实耐烂缓现思路:

  • 客户端和服务端之间的通信协议:在实现在线聊天功能的时候,需要考虑客户端和服务端之间的通信协议。可以使用 WebSocket 协议,这是一种全双工通信协议,支持客户端和服务端之间的实时通信。Java 提供了多个 WebSocket 实现,比如 Tyrus、Jetty 和 Netty。

  • 实现服务端:在服务端实现在线聊天功能,需要创建 WebSocket 服务器,并实现消息处理逻辑。在 Java 中,可以使用 Java WebSocket API,该 API 提供了 javax.websocket 包中的类和接口,可以方便地创建 WebSocket 服务器和处理 WebSocket 消息。

  • 在服务端,需要实现 WebSocket 端点(Endpoint),处理客户端连接、断开连接以及收发消息等操作。可以通过扩展 javax.websocket.Endpoint 类,重写 onOpen、onClose 和 onMessage 方法来处理相应的操作。

  • 实现客户端:在客户端实现在线聊天功能,需要创建 WebSocket 客户端,并实现消息处理逻辑。Java 提供了多个 WebSocket 客户端实现,比如 Tyrus、Jetty 和 Netty。

  • 在客户端,可以使用 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对象放入到对象池中: