目前很多框架中都存在消息队列的中间件,例如ActiveMQ,RabbitMQ,ZeroMQ,Kafka等,基本都是封装好,足够我们使用。但是,我们如何自己去实现一个消息队列中间件呢?
由于公司需求,进行了golang实现消息队列中间件的开发,这里做一个总结,把自己的一个想法和实现的经验分享给大家,非个人开发没有源码提供,如有不足多多指教。
主要内容包括:队列服的作用与特性,什么试试有状态与无状态队列服,及其各自的优缺点,最后是对实现队列服的一些建议
1、队列服(即消息队列中间件)的作用是什么?
队列服,就是一个生产者与消息者的模型。
能够将生产者的产品存放在队列服中,然后消费者根据需求从队列服中取自己需要的产品。这样看来的话,队列服类似超市,就是一个对产品临时存放的地方;而生产者类似厂家,生产出不同的产品存放到超市中;而消费者,就是不同的人,根据需求从超市购买自己想要的产品进行使用。
从上我们得出队列服的一些特性:
(1)超市不可能只卖一种产品,即队列服不仅仅存放一种消息格式,而是要满足对不同生产者的消息格式进行处理(暂且不讨论你的消息格式是使用的protobuf还是json等),如果队列服只能存储一个消息格式,那么就失去了中间件(公共组件)的意义所在。
所以,对于使用Golang语言,队列元素类型得是接口类型,或者转换成byte字节数组(如果是C++,则最好使用模板类型,也能使用字节数组)
(2)要满足不同消费者进行消费。
(3)其次,使用超市示例还不能表达的意思是:队列服必须满足先进先出!!!
2、队列服的两种类型,有状态服务与无状态服务。
(1)何为有状态?
简单来说,如果你的队列服中存了数据,该中间件服务就是有状态的。
有状态的缺点是什么呢?如果服务关闭,队列中的数据(内存中的数据)就会丢失,这些数据丢失可能导致一系列的问题;其次,如果队列服是多服的情况下,就不能保证消费者取的数据是有先后顺序的,因为每个队列服都有自己的消息队列,各自队列间不相关,但是队列服要求数据的先后顺序是相关的,这样就导致多服状态下的每个元素之间的先后顺序(队列属性)不存在。
优点:如果特殊队列服是单服状态时,数据就保存在内存中,不会产生数据先后混乱问题,此时性能会更好,也避免了多服的问题。
(2)何为无状态?
相对有状态,无状态服务就是不存储数据。当然,那就得使用一个中间文件来存储,充当队列的作用。目前情况下,使用redis来进行存储是比较好的,redis本身就有链表存储结构,对于数据的存取也是原子型的操作,避免了消费者取产品时的竞争状态,其次redis是内存数据库,性能较其他高。当然不是绝对,如果电脑宕机,redis中的数据就会丢失,当然这种情况比较少,但凡是的有准备才行。
如果此时无状态多服务,数据都存储在redis中,redis中保留了队列的属性,而消费者每次取得数据都来自于redis,并且是原子操作。这就实现了服务的高可用。
缺点就是:需要使用中间redis数据库(也可以是文件,不限制)进行操作,数据传输相对内存中直接操作元素的效率较低。
3、一些建议和注意点
(1)服务提供给外面的接口要足够的简单,就是一个给生产者存放产品接口与消费者取物品的接口即可。要有中间件的开发意识。
(2)一个产品可能要被多个消费者消费(比如一条群发消息,需要转发给多个用户),所以你得考虑如何使得一条消息才能满足多个消费者使用。个人采用的是,每条消息有一个int的标志位,标志该消息需要存在的次数,消费者取一次,减一,减至0时,就销毁该消息。
(3)队列不能无限增长,需要有上限,这些最好不要在程序中直接指定队列长度,应该放在配置文件中进行设置。例如,在一台1G内存的主机上部署,可能只能支持队列存储4W个数据,之后公司扩大后,在4G内存的主机上部署,此时不可能再去修改代码这么麻烦,只需要在配置文件中配置为12W或者16W就行,俗称扩展性。
(4)队列中的元素不能无限的存在队列中,其中每个元素都应该设置生存周期,也就是需要有定时器来进行消息的超时检测,并移除超时的元素。注意:并不需要每次检测的时候遍历所有队列元素,只需要检测队列最前面的那个元素就行,因为最前面的元素肯定是最先进来的,也是最容易超时的。当然,每个元素的超时时间也不能写死,这种需要调控的元素都应该放在配置文件中实现。
(5)队列服应该可以进行有状态与无状态的切换,满足不同情况下的特殊需要。当然,队列服的状态切换在配置文件中设置。
4、参考(简单的生产者与消费者模型)
希望对你有所帮助
2018-9-3 0:36