rabbitmq中消息的存储
来源:互联网 发布:义乌最好的淘宝摄影 编辑:程序博客网 时间:2024/05/17 06:06
1. 大概原理:
所有队列中的消息都以append的方式写到一个文件中,当这个文件的大小超过指定的限制大小后,关闭这个文件再创建一个新的文件供消息的写入。文件名(*.rdq)从0开始然后依次累加。当某个消息被删除时,并不立即从文件中删除相关信息,而是做一些记录,当垃圾数据达到一定比例时,启动垃圾回收处理,将逻辑相邻的文件中的数据合并到一个文件中。
2. 消息的读写及删除:
rabbitmq在启动时会创建msg_store_persistent,msg_store_transient两个进程,一个用于持久消息的存储,一个用于内存不够时,将存储在内存中的非持久化数据转存到磁盘中。所有队列的消息的写入和删除最终都由这两个进程负责处理,而消息的读取则可能是队列本身直接打开文件进行读取,也可能是发送请求由msg_store_persisteng/msg_store_transient进程进行处理。
在进行消息的存储时,rabbitmq会在ets表中记录消息在文件中的映射,以及文件的相关信息。消息读取时,根据消息ID找到该消息所存储的文件,在文件中的偏移量,然后打开文件进行读取。消息的删除只是从ets表删除指定消息的相关信息,同时更新消息对应存储的文件的相关信息(更新文件有效数据大小)。
-record(msg_location, { msg_id, %%消息ID ref_count, %%引用计数 file, %%消息存储的文件名 offset, %%消息在文件中的偏移量 total_size %%消息的大小 }).-record(file_summary, { file, %%文件名 valid_total_size, %%文件有效数据大小 left, %%位于该文件左边的文件 right, %%位于该文件右边的文件 file_size, %%文件总的大小 locked, %%上锁标记 垃圾回收时防止对文件进行操作 readers %%当前读文件的队列数 })
3. 垃圾回收:
由于执行消息删除操作时,并不立即对在文件中对消息进行删除,也就是说消息依然在文件中,仅仅是垃圾数据而已。当垃圾数据超过一定比例后(默认比例为50%),并且至少有三个及以上的文件时,rabbitmq触发垃圾回收。垃圾回收会先找到符合要求的两个文件(根据#file_summary{}中left,right找逻辑上相邻的两个文件,并且两个文件的有效数据可在一个文件中存储),然后锁定这两个文件,并先对左边文件的有效数据进行整理,再将右边文件的有效数据写入到左边文件,同时更新消息的相关信息(存储的文件,文件中的偏移量),文件的相关信息(文件的有效数据,左边文件,右边文件),最后将右边的文件删除。
4. 性能考虑:
(1)操作引用计数(flying_ets)
队列在进行消息的写入和删除操作前,会在flying_ets表里通过+1,-1的方式进行计数,然后投递请求给msg_store_persistent/msg_store_transient进程进行处理,进程在真正写操作或者删除之前会再次判断flying_ets中对应消息的计数决定是否需要进行相应操作。这样,对于频繁写入和删除的操作,概率减少实际的写入和删除。
client_write(MsgId, Msg, Flow, CState=#client_msstate{cur_file_cache_ets=CurFileCacheEts, client_ref=CRef}) -> ok = client_update_flying(+1, MsgId, CState), ok = update_msg_cache(CurFileCacheEts, MsgId, Msg), ok = server_cast(CState, {write, CRef, MsgId, Flow}).remove(MsgIds, CState = #client_msstate { client_ref = CRef }) -> [client_update_flying(-1, MsgId, CState) || MsgId <- MsgIds], server_cast(CState, {remove, CRef, MsgIds}).client_update_flying(Diff, MsgId, #client_msstate{flying_ets = FlyingEts, client_ref = CRef}) -> Key = {MsgId, CRef}, case ets:insert_new(FlyingEts, {Key, Diff}) of true -> ok; false -> try ets:update_counter(FlyingEts, Key, {2, Diff}) of ... end.handle_cast({write, CRef, MsgId, Flow}, State = #msstate{cur_file_cache_ets=CurFileCacheEts, clients=Clients}) -> ... true = 0 =< ets:update_counter(CurFileCacheEts, MsgId, {3, -1}), case update_flying(-1, MsgId, CRef, State) of process -> [{MsgId,Msg,_PWC}]=ets:lookup(CurFileCacheEts, MsgId), noreply(write_message(MsgId, Msg, CRef, State)); ignore -> ... end;handle_cast({remove, CRef, MsgIds}, State) -> {RemovedMsgIds, State1} = lists:foldl( fun (MsgId, {Removed, State2}) -> case update_flying(+1, MsgId, CRef, State2) of process -> {[MsgId | Removed], remove_message(MsgId, CRef, State2)}; ignore -> {Removed, State2} end end, {[], State}, MsgIds), ...update_flying(Diff,MsgId,CRef,#msstate{flying_ets = FlyingEts }) -> Key = {MsgId, CRef}, NDiff = -Diff, case ets:lookup(FlyingEts, Key) of [] -> ignore; [{_, Diff}] -> ignore; [{_, NDiff}] -> ets:update_counter(FlyingEts, Key, {2, Diff}), true = ets:delete_object(FlyingEts, {Key, 0}), process; [{_, 0}] -> true = ets:delete_object(FlyingEts, {Key, 0}), ignore; [{_, Err}] -> throw({bad_flying_ets_record, Diff, Err, Key}) end.
(2)尽可能的并发读
在读取消息的时候,都先根据消息ID找到对应存储的文件,如果文件存在并且未被锁住,则直接打开文件,从指定位置读取消息的内容。
如果消息存储的文件被锁住了,或者对应的文件不存在了,则发送请求,由msg_store_persistent/msg_store_transient进程进行处理。
(3)消息缓存
1)利用ets表进行缓存
对于当前正在写的文件,所有消息在写入前都会在cur_file_cache_ets表中存一份,消息读取时会优先从这里进行查找。文件关闭时,会将cur_file_cache_ets表中引用计数为0的消息进行清除。
2)file_handle_cache的写缓存
rabbitmq中对文件的操作封转到了file_handle_cache模块,以写模式打开文件时,默认有1M大小的缓存,即在进行文件的写操作时,是先写入到这个缓存中,当缓存超过大小或者显式刷新,才将缓存中的内容刷入磁盘中。
rabbit_msg_store.erl-define(HANDLE_CACHE_BUFFER_SIZE, 1048576). %% 1MBopen_file(Dir, FileName, Mode) -> file_handle_cache:open(form_filename(Dir, FileName), ?BINARY_MODE ++ Mode, [{write_buffer,?HANDLE_CACHE_BUFFER_SIZE}]).file_handle_cache.erlappend(Ref,Data) -> with_handles( [Ref], fun ([#handle { is_write = false }]) -> {error, not_open_for_writing}; ([Handle]) -> case maybe_seek(eof, Handle) of {{ok, _Offset}, #handle{hdl = Hdl, offset = Offset, write_buffer_size_limit = 0, at_eof = true }= Handle1} -> Offset1 = Offset + iolist_size(Data), {prim_file:write(Hdl, Data), [Handle1#handle{is_dirty=true,offset=Offset1 }]}; {{ok, _Offset},#handle{write_buffer = WriteBuffer, write_buffer_size = Size, write_buffer_size_limit= Limit, at_eof = true } = Handle1} -> WriteBuffer1 = [Data | WriteBuffer], Size1 = Size + iolist_size(Data), Handle2=Handle1#handle{write_buffer=WriteBuffer1, write_buffer_size=Size1}, case Limit =/= infinity andalso Size1 > Limit of true -> {Result,Handle3} = write_buffer(Handle2), {Result, [Handle3]}; false -> {ok, [Handle2]} end; {{error, _} = Error, Handle1} -> {Error, [Handle1]} end end).
- rabbitmq中消息的存储
- RabbitMQ中关于消息队列的一些概念
- RabbitMQ消息的持久化
- 消息队列RabbitMQ的安装
- RabbitMQ消息的传输控制
- RabbitMQ消息队列的总结
- 在python中引入rabbitmq消息中间件
- 在C#中使用消息队列RabbitMQ
- OpenStack中消息队列(RabbitMQ)分析
- 通过rabbitmq向mq中发送消息
- Kafka、RabbitMQ、RocketMQ消息中…
- RabbitMQ学习之基于spring-rabbitmq的消息异步发送
- RabbitMQ消息队列之一:RabbitMQ的环境安装及配置
- openstack——RabbitMQ 的oslo.messaging 和 Cinder 中 MessageQueue 消息的发送和接收
- SpringBoot中如何监听两个不同源的RabbitMQ消息队列
- java实现rabbitmq消息的发送接受
- rabbitmq丢消息的处理方法
- java实现rabbitmq消息的发送接受
- Retrofit2 使用经验
- Helper2416开发板移植minigui3.0.12之二:移植到开发板
- 友元 成员 非成员 点间距离
- 13. php数据库抽象层PDO(二)
- 防止机器注册
- rabbitmq中消息的存储
- jQ源码之camelCase
- 认识算法-利用循环
- LoadRunner监控Windows和Linux常见问题
- 使用Post方式提交数据
- Android自定义View(二、深入解析自定义属性)
- AngularJs学习笔记(三)控制器和过滤器
- mysql-insert-Duplicate
- 【Netty4 简单项目实践】八、转发服务的实现方案