内容简介:最近看技术博客,谈到他们开发了一种日志采集的agent部署在各个服务器上,由业务直接通过unix socket实时发送日志给agent的场景。在agent里他们使用了持久化的本地消息队列用于缓冲日志数据,这样可以缓冲因为日志服务端阻塞导致的日志堆积暴涨,以及避免agent宕机后的日志丢失问题。那么如何实现一个单机消息队列呢?自己实现一个可靠的存储系统是困难的,所以尽量不要造轮子,这里可以选择leveldb作为持久化介质,他是一个高性能的K/V存储SDK,写性能尤为突出。
最近看技术博客,谈到他们开发了一种日志采集的agent部署在各个服务器上,由业务直接通过unix socket实时发送日志给agent的场景。
在agent里他们使用了持久化的本地消息队列用于缓冲日志数据,这样可以缓冲因为日志服务端阻塞导致的日志堆积暴涨,以及避免agent宕机后的日志丢失问题。
那么如何实现一个单机消息队列呢?自己实现一个可靠的存储系统是困难的,所以尽量不要造轮子,这里可以选择leveldb作为持久化介质,他是一个高性能的K/V存储SDK,写性能尤为突出。
我实测了一下leveldb,顺序写大概30-40万的/秒(key有序递增),顺序读大概80-100万/秒,可以参考官方的 测试数据 。
实现思路
队列天生就是一个顺序读写的模型,在leveldb的K/V模型上构造出队列模型,需要一个队列的head头和tail尾2个指针,其中head表示写入的位置,tail表示读取的位置,初始都为0。
当push一条数据时,以head为key插入到leveldb,并让head=head+1,head自身同样需要保存到leveldb中进行备份。
当pop一条数据时,首先判断tail<head则说明有数据可读,那么以tail为key读取leveldb得到value,然后从leveldb中删除key=tail,并令tail=tail+1,tail自身同样需要保存到leveldb中进行备份。
考虑一下异常是否可以容忍:
- push完成了插入,但是更新后的head没来得及保存leveldb。 — 重启后push的第一条消息会覆盖到head位置,丢失了一条消息。
- pop删除了tail,但是更新后的tail没来得及保存到leveldb。 — 重启后的第一次pop查不到value,丢失了一条消息。
上述2个问题需要额外的日志机制保障可靠(redo/undo),本身也是一个麻烦的工作,好在日志业务还是可以容忍这点数据丢失的,再者agent的稳定性一般很高,不会频繁出现这种情况。
Golang的一个Demo
我一开始采用Golang写了一个Demo,后来测试发现性能太差劲,写入性能大概只有5万/秒,读取也就是2-3万/秒,与C++版本相差甚远。
这主要是因为Golang没有特别成熟的leveldb原生(golang开发)实现,而靠谱的唯有一个基于cgo技术封装了c++的libleveldb,所以我选择了它。
因为队列压测时每秒有几十万的请求量,这种频繁的在golang和C之间的调用切换成本极高,所以导致性能不可思议的低下。
这个Golang的Demo作为一个SDK包存在,实现了一个GoQueue类,大家有兴趣可以了解一下代码:
GoQueue 。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 消息队列(三)常见消息队列介绍
- 消息队列探秘 – RabbitMQ 消息队列介绍
- springboot整合各种消息队列(二):rabbitmq消息队列
- springboot整合各种消息队列(一):redis消息队列
- 消息队列系列二(IOT中消息队列的应用)
- 消息队列(七)RocketMQ消息发送
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Out of their Minds
Dennis Shasha、Cathy Lazere / Springer / 1998-07-02 / USD 16.00
This best-selling book is now available in an inexpensive softcover format. Imagine living during the Renaissance and being able to interview that eras greatest scientists about their inspirations, di......一起来看看 《Out of their Minds》 这本书的介绍吧!