发明内容
为了解决上述技术问题或者至少部分地解决上述技术问题,本申请实施例提供了一种消息处理方法、装置、电子设备及存储介质。
第一方面,本申请实施例提供了一种消息处理方法,包括:
获取内存缓冲队列中第一待处理消息对应的日志偏移量;
在所述第一待处理消息中的第一标识字节写入所述日志偏移量,得到第二待处理消息;
通过至少两个线程从所述内存缓冲队列中按顺序读取所述第二待处理消息,将所述第二待处理消息拷贝至所述缓存中所述日志偏移量对应的位置;
从所述缓存中按存储顺序获取所述第二待处理消息写入磁盘。
可选的,所述方法还包括:
通过至少两个线程获取所述第一待处理消息对应的索引偏移量;
根据所述索引偏移量创建所述第一待处理消息对应的索引。
可选的,所述获取内存缓冲队列中第一待处理消息对应的日志偏移量,包括:
获取所述缓存的当前已分配位置信息,及所述缓存的缓存初始写入位置信息对应所述磁盘中的日志初始写入位置信息;
根据所述当前已分配位置信息及日志初始写入位置信息计算所述日志偏移量。
可选的,所述将所述第二待处理消息拷贝至所述缓存中所述日志偏移量对应的位置,包括:
根据所述当前已分配位置信息确定所述第二待处理消息对应的缓存偏移量;
将所述第二待处理消息拷贝至所述缓存中所述缓存偏移量对应的位置。
可选的,所述从所述缓存中按存储顺序获取所述第二待处理消息写入到磁盘中,包括:
对所述缓存中的数据添加写盘屏障,所述写盘屏障位于所述缓存中未写入数据的空洞位置之前;
从上一次写盘结束位置到所述写盘屏障之间,按存储顺序获取连续的符合预设条件的第二待处理消息;
将所述第二待处理消息顺序写入到所述磁盘中。
可选的,所述预设条件包括:
所述第二待处理消息的数据长度之和达到预设数据长度;
和/或,
当前时间与上一次写盘时间之间的时间间隔达到预设时间间隔。
可选的,所述方法还包括:
当确定所述第二待处理消息写入所述缓存或磁盘后,在所述内存缓冲队列中清空所述第二待处理消息。
第二方面,本申请实施例提供了一种消息处理装置,包括:
获取模块,用于获取内存缓冲队列中第一待处理消息对应的日志偏移量;
消息处理模块,用于在所述第一待处理消息中的第一标识字节写入所述日志偏移量,得到第二待处理消息;
写缓存模块,用于通过至少两个线程从所述内存缓冲队列中按顺序读取所述第二待处理消息,将所述第二待处理消息拷贝至所述缓存中所述日志偏移量对应的位置;
写盘模块,用于从所述缓存中按存储顺序获取所述第二待处理消息写入到磁盘中。
第三方面,本申请实施例提供了一种电子设备,包括:处理器、通信接口、存储器和通信总线,其中,处理器,通信接口,存储器通过通信总线完成相互间的通信;
所述存储器,用于存放计算机程序;
所述处理器,用于执行计算机程序时,实现上述方法步骤。
第四方面,本申请实施例提供了一种计算机可读存储介质,其上存储有计算机程序,该计算机程序被处理器执行时实现上述方法步骤。
本申请实施例提供的上述技术方案与现有技术相比具有如下优点:
通过获取内存缓冲队列中消息对应的日志偏移量,根据该日志偏移量确定消息在缓存中的存储位置以及在磁盘中的存储位置。这样,消息之间不存在顺序依赖,可以实现将消息无锁乱序写入缓存,并且,由于消息的日志偏移量全局有序,消息写盘时保证有序。这样,无需在日志写入过程中添加全局锁或单线程顺序处理。另外,使得CPU主频高低不会直接影响TPS。在单机Topic和多partition的场景下,性能不随分区数的增多而下降,当Topic流量不均衡时,性能不因此比均衡流量下降。
具体实施方式
为使本申请实施例的目的、技术方案和优点更加清楚,下面将结合本申请实施例中的附图,对本申请实施例中的技术方案进行清楚、完整地描述,显然,所描述的实施例是本申请的一部分实施例,而不是全部的实施例。基于本申请中的实施例,本领域普通技术人员在没有做出创造性劳动的前提下所获得的所有其他实施例,都属于本申请保护的范围。
在发布订阅消息系统中,Topic(主题)是数据写入操作的基本单元,一个Topic包含一个或多个partition(分区),每一个Topic的每一个partition都有日志和索引文件。
现有的发布订阅消息系统,如kafka,当单机分区数增加或者强制写盘次数增加时,随机写磁盘带来的开销较大,系统性能将受到影响。如果强制写盘后返回消息,也会带来大量的随机写入磁盘操作,对系统性能影响较大,且不适合高可靠性场景,即每条消息必须写入磁盘才能响应客户端。另外,当单机上Topic较多,但每个Topic流量不均衡时,性能测试时的测试数据比均衡流量时的测试数据差。
而对于RocketMQ系统,由于要保证消息处理顺序与磁盘写入顺序一致,在内存中加入全局锁,导致内存的追加、序列化、获取日志偏移量只能单线程执行,使得CPU主频高低直接影响TPS,并且在CPU核数提升的情况下提升不明显。在同步写盘的模式下,当消息数据长度较小时,有明显的写入放大(Write amplification,简称WA)。
本申请实施例,在从内存缓冲队列获取消息之前,获取到每个消息的缓存偏移量及日志偏移量,这样,即便多线程乱序处理,也可将该消息写入到缓存和磁盘中的相应位置,保证所有消息的顺序性。因此,无需在日志写入过程中添加全局锁,即无需单线程顺序处理,在单机Topic和多partition的场景下,性能不随分区数的增多而下降,当主题流量不均衡时,性能不因此比均衡流量下降。
下面首先对本发明实施例所提供的一种消息处理方法进行介绍。
图1为本申请实施例提供的一种消息处理方法的流程图。如图1所示,该方法包括以下步骤:
步骤S11,获取内存缓冲队列中第一待处理消息对应的日志偏移量。
其中,日志偏移量用于表示该消息在磁盘中的起始存储位置。
消息在日志和缓存中的顺序,均需与消息在内存缓冲队列中的前后顺序一致。例如,在内存缓冲队列中,消息A在消息B之前,则消息A在缓存中或磁盘中均位于消息B之前。当生产者将消息写入到内存缓冲队列时,消费者通过无锁获取该消息对应的日志偏移量,从而后续根据日志偏移量确定消息在缓存中的存储位置以及在磁盘中的存储位置,这样,可以避免在内存添加全局锁,实现多线程乱序写缓存处理。
步骤S12,在第一待处理消息中的第一标识字节写入日志偏移量,得到第二待处理消息。
其中,第一待处理消息中预留一定数量的第一标识字节,例如,在消息头部的第8~16Byte用于标识日志偏移量。通过在预留的第一标识字节中写入日志偏移量,后续写缓存和写盘操作时,该消息中就携带有日志偏移量,从而保证消息在缓存、磁盘中的顺序与在内存缓冲队列中的顺序一致。
将内存缓冲队列中的第一待处理消息中的第一表示字节写入日志偏移量后,实际上该内存缓冲队列中的第一待处理消息变为第二待处理消息。
步骤S13,通过至少两个线程从内存缓冲队列中按顺序读取第二待处理消息,将第二待处理消息拷贝至缓存中日志偏移量对应的位置。
由于提前获取消息的日志偏移量,在将第二待处理消息写入缓存之前不同消息之间就不存在顺序依赖,因此,可以采用多线程从内存缓冲队列中顺序读取第二待处理消息后,乱序写入缓存。基于日志偏移量,消息在缓存中的顺序以及在磁盘中的顺序,均与在内存缓冲队列中的顺序一致。
另外,在步骤S12中,将消息拷贝至缓存中,而不是包装(wrap)至缓存中。通过wrap()函数,虽然可以减少内存拷贝,但在将消息写入磁盘时,由于需要单线程写磁盘,则每条消息都要进行单线程的寻址和地址调用,大大降低写磁盘效率。因此,可以通过缓冲器将消息通过多线程拷贝到缓存中,使得缓存中消息存储的实体内容已经完整且完成聚合,极大地减少日志写盘时的系统调用。
步骤S14,从缓存中按存储顺序获取第二待处理消息写入磁盘。
由于消息实体内容已被拷贝到缓存中,缓存被多线程乱序填充后,在缓存中完成聚合,可以直接将消息按顺序批量写入到磁盘中。
本实施例中,通过获取内存缓冲队列中消息对应的日志偏移量,根据该日志偏移量确定消息在缓存中的存储位置以及在磁盘中的存储位置。这样,消息之间不存在顺序依赖,可以实现将消息无锁乱序写入缓存,并且,由于消息的日志偏移量全局有序,消息写盘时保证有序。
这样,无需在日志写入过程中添加全局锁或单线程顺序处理。另外,使得CPU主频高低不会直接影响TPS。在单机Topic和多partition的场景下,性能不随分区数的增多而下降,当Topic流量不均衡时,性能不因此比均衡流量下降。
图2为本申请另一实施例提供的一种消息处理方法的流程图。如图2所示,步骤S11中获取内存缓冲队列中第一待处理消息对应的日志偏移量,包括以下步骤:
步骤S21,获取缓存的当前已分配位置信息及缓存的缓存初始写入位置信息对应磁盘中的日志初始写入位置信息。
步骤S22,根据当前已分配位置信息及日志初始写入位置信息计算日志偏移量。
其中,缓存的当前已分配位置即为上一条消息的结束位置。
当确定生产者在内存缓冲队列中写入消息时,消费者侧基于缓存的当前已分配位置信息以及缓存初始写入位置信息对应磁盘中的日志初始写入位置信息确定该消息对应的日志偏移量。
其中,当前已分配位置信息用xn表示,缓存初始写入位置信息为0,其对应磁盘中的日志初始写入位置信息用y1表示,则日志偏移量为xn+y1。
例如,当缓存中当前未写入消息时,即当前已分配位置信息为0,对应磁盘中的日志初始写入位置信息为1000,则消息1对应的日志偏移量为0+1000=1000。若当前已分配位置信息为500,对应磁盘中的日志初始写入位置信息为1000,则该消息2对应的日志偏移量为500+1000=1500。
可选的,该方法还包括:根据第一待处理数据的数据长度更新缓存的当前已分配位置信息。
当前已分配位置信息xn=xn-1+L,其中,xn-1表示第n-1个消息对应的当前已分配信息,L表示第n-1个消息的数据长度。
例如,当缓存中当前未写入消息时,即当前已分配位置信息为0。消息1为第一个写入缓存中的消息,则其对应的缓存偏移量为0,数据长度为500Byte,消息1从缓存的第0Byte写到第499Byte。此时应当将该缓存的当前已分配位置信息更新为500。这样,对于第二个写入缓存中的消息2,其对应的缓存偏移量为500,数据长度为2000Byte,则消息2写入缓存中的第500~2499Byte。此时,该缓存的当前已分配位置信息应被更新为2500。
可选的,步骤S13中将第二待处理消息拷贝至缓存中日志偏移量对应的位置包括:
步骤A1,根据当前已分配位置信息确定第二待处理消息对应的缓存偏移量。
步骤A2,将第二待处理消息拷贝至缓存中缓存偏移量对应的位置。
其中,消息的缓存偏移量即等于缓存的当前已分配位置信息。在消息分配缓存结束后,会将缓存的当前已分配位置信息加上该消息的数据长度,进行更新。
例如,当缓存中当前未写入消息时,即当前已分配位置信息为0。消息1为第一个写入缓存中的消息,则其对应的缓存偏移量为0,将消息1写入缓存的第0Byte写到第499Byte。此时应当将该缓存的当前已分配位置信息更新为500。这样,对于第二个写入缓存中的消息2,其对应的缓存偏移量为500,数据长度为2000Byte,则消息2写入缓存中的第500~2499Byte。此时,该缓存的当前已分配位置信息应被更新为2500。
本实施例中,通过提前获取消息的日志偏移量,在将消息写入缓存之前不同消息之间就不存在顺序依赖,因此,可以采用多线程从内存缓冲队列中从内存缓冲队列中顺序读取消息后,乱序写入缓存。基于日志偏移量,消息在缓存中的顺序以及在磁盘中的顺序,均与在内存缓冲队列中的顺序一致。
图3为本申请另一实施例提供的一种消息处理方法的流程图。如图3所示,步骤S14中,将缓存中的消息写入磁盘的过程包括以下步骤:
步骤S31,对缓存中的数据添加写盘屏障,写盘屏障位于缓存中未写入数据的空洞位置之前。
由于自定义的缓存支持乱序提交,即消息并不是按顺序写入缓存,如图4所示,缓存中的消息不连续,消息4和消息6之间及消息6之后存在空洞位置。如果直接对已写入缓存的消息直接执行写盘操作,则仍会对空洞位置进行扫描及写盘。当消息5和消息7被写入该空洞位置后,仍需再次扫描缓存,将消息5和消息7写入磁盘。这样,写盘操作不连续,出现重复,并且容易发生错误。
因此,对缓存中的数据设置写盘屏障,该写盘屏障之前的数据为已写入的连续消息。
例如,图4中,消息1~4已被写入缓存。消息1在缓存中的写入位置为第0~499Byte,消息2在缓存中的写入位置为第500~2499Byte,消息3在缓存中的写入位置为第2500~4099Byte,消息4在缓存中的写入位置为第4100~5099Byte。消息5未被写入,其对应的缓存中的位置为第5100~5599Byte。则此时写盘屏障位于缓存中的第5100Byte,即缓存中的第0~5099Byte可以进行写盘操作。
步骤S32,从上一次写盘结束位置到写盘屏障之间,按存储顺序获取连续的符合预设条件的第二待处理消息。
例如,如图4所示,上一次写盘结束位置为消息1之前,写盘屏障位于消息4之后,可以将消息1至消息4写入磁盘。
步骤S33,将第二待处理消息顺序写入到磁盘中。
本实施例中,在缓存中设置写盘屏障,将写盘屏障之前的连续消息按顺序写入磁盘。这样,不仅提高写盘效率,避免对缓存中同一位置重复写盘,同时也提高写盘的数据准确性。
为了避免频繁写盘,设置预设条件,从缓存中读取满足该预设条件的第二待处理消息执行写盘操作。可选的,该预设条件包括:第二待处理消息的数据长度之和达到预设数据长度;和/或,当前时间与上一次写盘时间之间的时间间隔达到预设时间间隔。
为了避免频繁写盘,可以设置一个预设数据长度,只有达到该预设数据长度时,才将缓存中的消息写入磁盘。例如,预设数据长度为4KB。消息1的数据长度为500Byte,消息2的数据长度为2000Byte,消息3的数据长度为1600Byte。当消息1和消息2被写入缓存后,由于两个消息的数据长度之和500+2000=2500Byte,小于4KB,因此,此时不会进行写盘操作。当消息3被写入缓存后,消息1、2、3的数据长度之和为4100Byte,达到设定的预设数据长度,此时,可以将消息1、2、3依次写入磁盘。
另外,如果从上一次写盘结束位置到写盘屏障之间所有消息之和仍小于预设数据长度,则此时不进行写盘操作。例如,如图4中,写盘屏障之前的消息1~4的数据长度之和不足4KB,则等待消息5被写入后,写盘屏障移动到消息6之和,计算消息1~6的数据长度之和,若达到4KB,则对消息1~6执行写盘操作,否则继续等待消息7被写入。
在另一种方式中,可以设置一个于预设时间间隔,只有与前一次写盘时间之间的时间间隔达到该预设时间间隔时,才将缓存中的消息写入磁盘。又例如,可设置预设时间间隔为1毫秒,即当前时间与上一次写盘时间之间的时间间隔达到1毫秒时,才再次执行写盘操作。
这样,由于消息批量被写入磁盘,而不是缓存中每写入一条消息就执行将该消息写入磁盘的操作,避免写入放大的问题,即实际写入的物理数据长度是写入数据长度的多倍。降低写入磁盘的次数,延长磁盘的可靠运行时间。
在另一个可选实施例中,该方法还包括:当确定第二待处理消息写入磁盘后,在内存缓冲队列中清空第二待处理消息。生产者侧会根据写盘成功的第二待处理消息及时清空内存缓冲队列中相应的第二待处理消息,从而可以继续把后续的消息写入内存缓冲队列。
其中,该内存缓冲队列可以为环形队列,环行队列是一种首尾相连的队列数据结构,遵循先进先出原则。采用环形队列,由于元素数固定的一个闭环,可以在环形队列初始化的时候分配好确定的内存空间,当进队或出队时只需要返回指定元素内存空间的地址即可,这些内存空间可以重复利用,避免频繁内存分配和释放的开销。
在另一个可选实施例中,该方法还包括:通过至少两个线程获取第一待处理消息对应的索引偏移量;根据索引偏移量创建第一待处理消息对应的索引。
可选的,获取索引偏移量创建索引的操作可以在将内存缓冲队列中的消息写入缓存之前完成,也可在消息被写入缓存之后,或写入磁盘之后完成。创建索引的操作与消息写入缓存或写磁盘的操作之间没有先后顺序关系。
另外,在创建索引的过程中,需要在partition(分区)维度保持索引有序,因此,可以基于partition对每个消息对应的索引进行哈希计算,使得同一个分区的消息由同一线程进行处理。
在一个可选实施例中,生产者侧的线程将消息写入环形队列,可以在生产者侧设置一个生产者屏障,该生产者屏障依赖于消息是否被写入缓存或磁盘,当环形队列中的某个消息被成功写入缓存或磁盘时,则将该消息删除,再将生产者侧的其他消息按顺序写入环形队列。例如,实际中,当消息被成功写入缓存,即释放该消息在环形队列中的空间。
消费者侧从环形队列中读取消息时,对于读取过程可以分别设置有消费者屏障。首先,消费者屏障1确定生产者侧将消息写入环形队列时,允许消费者侧获得消息对应的日志偏移量。消费者屏障2确定消费者侧获取该日志偏移量后,允许消费者侧多线程获取消息的索引偏移量。消费者屏障3确定消费者侧获取了索引偏移量后,多线程从内存缓冲队列中获取消息,将日志偏移量加入该消息中,并将序列化后的消息写入缓存。
生产者屏障和消费者屏障均为指向环形队列的指针。每个屏障的指针指向上游依赖模块已经完全处理完毕的槽,屏障后面也有可能有的槽已经被上游模块处理,但是前面存在未处理的槽,所以未能连续。每个模块的线程执行完本模块任务,通知下游屏障更新指针,下游屏障只会更新指针到最大连续已经被上游模块处理的位置。例如,由于采用将消息拷贝至缓存,因此完成将消息内容写进缓存的操作后,原有的槽就可以通知生产者屏障继续被写入。
本实施例中,通过获取内存缓冲队列中消息对应的日志偏移量,根据该日志偏移量确定消息在缓存中的存储位置以及在磁盘中的存储位置。这样,消息之间不存在顺序依赖,可以实现将消息无锁乱序写入缓存,并且,由于消息的日志偏移量全局有序,消息写盘时保证有序。这样,无需在日志写入过程中添加全局锁或单线程顺序处理。另外,使得CPU主频高低不会直接影响TPS。在单机Topic和多partition的场景下,性能不随分区数的增多而下降,当主题流量不均衡时,性能不因此比均衡流量下降。
下述为本申请装置实施例,可以用于执行本申请方法实施例。
图5为本申请实施例提供的一种消息处理装置的框图,该装置可以通过软件、硬件或者两者的结合实现成为电子设备的部分或者全部。如图5所示,该消息处理装置包括:
获取模块51,用于获取内存缓冲队列中第一待处理消息对应的日志偏移量及第一待处理消息在缓存中的缓存偏移量。
消息处理模块52,用于在第一待处理消息中的第一标识字节写入日志偏移量,得到第二待处理消息。
写缓存模块53,用于通过至少两个线程从内存缓冲队列中按顺序读取第二待处理消息,将第二待处理消息拷贝至缓存中日志偏移量对应的位置。
写盘模块54,用于从缓存中按存储顺序获取第二待处理消息写入到磁盘中。
本申请实施例还提供一种电子设备,如图6所示,电子设备可以包括:处理器1501、通信接口1502、存储器1503和通信总线1504,其中,处理器1501,通信接口1502,存储器1503通过通信总线1504完成相互间的通信。
存储器1503,用于存放计算机程序;
处理器1501,用于执行存储器1503上所存放的计算机程序时,实现以下上述方法实施例的步骤。
上述电子设备提到的通信总线可以是外设部件互连标准(PeripheralComponentInterconnect,PCI)总线或扩展工业标准结构(Extended IndustryStandardArchitecture,EISA)总线等。该通信总线可以分为地址总线、数据总线、控制总线等。为便于表示,图中仅用一条粗线表示,但并不表示仅有一根总线或一种类型的总线。
通信接口用于上述电子设备与其他设备之间的通信。
存储器可以包括随机存取存储器(Random Access Memory,RAM),也可以包括非易失性存储器(Non-Volatile Memory,NVM),例如至少一个磁盘存储器。可选的,存储器还可以是至少一个位于远离前述处理器的存储装置。
上述的处理器可以是通用处理器,包括中央处理器(Central Processing Unit,CPU)、网络处理器(Network Processor,NP)等;还可以是数字信号处理器(DigitalSignalProcessing,DSP)、专用集成电路(Application Specific Integrated Circuit,ASIC)、现场可编程门阵列(Field-Programmable Gate Array,FPGA)或者其他可编程逻辑器件、分立门或者晶体管逻辑器件、分立硬件组件。
本申请还提供一种计算机可读存储介质,其上存储有计算机程序,该计算机程序被处理器执行时实现以下上述方法实施例的步骤。
需要说明的是,对于上述装置、电子设备及计算机可读存储介质实施例而言,由于其基本相似于方法实施例,所以描述的比较简单,相关之处参见方法实施例的部分说明即可。
进一步需要说明的是,在本文中,诸如“第一”和“第二”等之类的关系术语仅仅用来将一个实体或者操作与另一个实体或操作区分开来,而不一定要求或者暗示这些实体或操作之间存在任何这种实际的关系或者顺序。而且,术语“包括”、“包含”或者其任何其他变体意在涵盖非排他性的包含,从而使得包括一系列要素的过程、方法、物品或者设备不仅包括那些要素,而且还包括没有明确列出的其他要素,或者是还包括为这种过程、方法、物品或者设备所固有的要素。在没有更多限制的情况下,由语句“包括一个……”限定的要素,并不排除在包括所述要素的过程、方法、物品或者设备中还存在另外的相同要素。
以上所述仅是本发明的具体实施方式,使本领域技术人员能够理解或实现本发明。对这些实施例的多种修改对本领域的技术人员来说将是显而易见的,本文中所定义的一般原理可以在不脱离本发明的精神或范围的情况下,在其它实施例中实现。因此,本发明将不会被限制于本文所示的这些实施例,而是要符合与本文所申请的原理和新颖特点相一致的最宽的范围。