前面在分析OpenStack的rpc源码时,知道rpc是通过消息队列实现的,其中相关的消息队列操作主要是使用的oslo_messaging库。而这个库进行消息的处理是对kombu开源库的封装。
kombu是一个Python的消息库,关于更详细的介绍可以参考之前翻译的文章:
http://blog.csdn.net/happyanger6/article/details/51439624
本篇文章的主要目的是讲解一下kombu库的使用方法,通过学习原生的编程接口来为理解oslo_messaging库打下基础。
Connections和Transport
基础
我们发送和接收信息要依靠Transport和Connections。可供选择的Transport有:amqp, librabbitmq, redis, qpid, in-memory,你甚至还可以自己实现一个。默认的Transport是amqp。
下面的代码用默认的Transport创建了一个连接:
>>> from kombu import Connection >>> connection = Connection('amqp://guest:guest@localhost:5672//')
这时连接还没有真正建立,当你需要时连接才会真正建立。你也可以主动地调用connect()方法来建立连接:
>>> connection.connect()你也可以检查是否已经建立了连接:
>>> connection.connected
True
>>> connection.close()最好是release连接代替关闭它,因为如果连接关联到一个连接池release会释放相关资源,如果没有关联连接池则仅仅关闭它。通过release以后你更容易将代码过渡到连接池。
>>> connection.release()
Connection也可以像上下文管理器那样使用,这样你就很难忘记关闭资源了。
with Connection() as connection: # work with connection
URLs
Connection参数可以以下面URL的形式提供:
transport://userid:password@hostname:port/virtual_host下面都是合法的URLs:
# Specifies using the amqp transport only, default values # are taken from the keyword arguments. amqp:// # Using Redis redis://localhost:6379/ # Using Redis over a Unix socket redis+socket:///tmp/redis.sock # Using Qpid qpid://localhost/ # Using virtual host '/foo' amqp://localhost//foo # Using virtual host 'foo' amqp://localhost/foo
URL的查询字符串部分还可以设置选项:
所有支持的选项列表:Keyword arguments .
没有指定选项的连接将会使用默认的配置,使用localhost作为目的主机,默认端口号,用户名guest,密码guest和虚拟主机“/”。没有使用参数的连接和下面的连接一样:
默认的端口号和Transport有关,AMQP是5672.
其它的字段对不同的Transport也会有不同的含义. 比如, Redis transport使用virtual_host 参数作为redis数据库的编号。
AMQP Transports
对于AMQP来说,有4种transports可用.
pyamqp
使用纯Python库amqp
, 会自动与Kombu一起安装.librabbitmq使用C语言编写的高性能transport.这需要安装
librabbitmq
Python包, 会自动用c库编译.amqp
尝试使用librabbitmq
但是因为失败又转而使用pyamqp
.quid
使用纯python库qpid.messaging
, 也会自动与Kombu一起安装. Quid库使用AMQP, 但是使用了被Apache Qpid Broker支持的特定扩展.
为了达到最高性能,你应该安装librabbitmq包
. 为确保使用librabbitmq,你可以在transport URL里显式地指定,或者使用amp
作为备选方案.
Transport 比较
Client | Type | Direct | Topic | Fanout |
amqp | Native | Yes | Yes | Yes |
qpid | Native | Yes | Yes | Yes |
redis | Virtual | Yes | Yes | Yes (PUB/SUB) |
mongodb | Virtual | Yes | Yes | Yes |
beanstalk | Virtual | Yes | Yes [1] | No |
SQS | Virtual | Yes | Yes [1] | Yes [2] |
couchdb | Virtual | Yes | Yes [1] | No |
zookeeper | Virtual | Yes | Yes [1] | No |
in-memory | Virtual | Yes | Yes [1] | No |
django | Virtual | Yes | Yes [1] | No |
sqlalchemy | Virtual | Yes | Yes [1] | No |
生产者
基础
序列化
参考
-
class
-
消息生产者.
参数: - channel – Connection 或者channel.
- exchange – 可选参数,使用的exchange.
- routing_key – 可选参数,消息的路由键.
- serializer – 使用的序列化机制,默认使用“json”.
- compression – 使用的压缩方法. 默认不压缩.
- auto_declare – 在安装时自动声明使用的exchange. 默认值是
True
. - on_return – 消息不能递交时使用的callback,需要在publish()方法中使用mandatory或immediate参数. 这个callback的签名是: (exception, exchange, routing_key, message). 需要注意的是使用这个特性时,producer需要自己处理events.
-
默认情况下会在构造对象时自动声明exchange. 如果你想手动声明,需要设置此值为
False
.
auto_declare
= True-
采用的压缩方法,默认不压缩.
compression
= None-
声明exchange.
exchange会自动声明,如果开启了
auto_declare
.
declare
( ) [source]-
默认的exchange.
exchange
= None-
如果在会话里还没有声明exchange则声明.
maybe_declare
( entity, retry=False, **retry_policy ) [source]-
使用的callback,见参数描述.
on_return
= None-
向指定的exchange发布消息.
参数: - body – 消息体.
- routing_key – 消息的路由键.
- delivery_mode – 了解
delivery_mode
. - mandatory – 目前还不支持.
- immediate – 目前还不支持.
- priority – 消息的优先级. 0到9.
- content_type – 消息内容的类型,默认auto-detect.
- content_encoding – 消息内容的编码,默认auto-detect.
- serializer – 使用的序列化手段,默认auto-detect.
- compression – 使用的压缩方法,默认是none.
- headers – 附加到消息体的头.
- exchange – 发布消息的exchange. 注意exchange必须被声明.
- declare – 需要的实体对象列表,在发布消息之前必须声明.实体对象用
maybe_declare()方法来声明
. - retry – 尝试重新发布消息, 或者在连接丢失时声明实体对象.
- retry_policy – 重新尝试的策略, 这是
ensure()方法支持的一个关键字参数
. - expiration – 每个消息的TTL,以秒为单位. 默认是没有.
- **properties – 额外的消息属性,参考AMQP说明.
publish
( body, routing_key=None, delivery_mode=None, mandatory=False, immediate=False, priority=0, content_type=None, content_encoding=None, serializer=None, headers=None, compression=None, exchange=None, retry=False, retry_policy=None, declare=[], expiration=None, **properties ) [source]-
连接断开后重新复活producer.
revive
( channel ) [source]-
默认的路由键.
routing_key
= ''-
使用的序列化机制. 默认使用JSON.
serializer
= None
kombu.
Producer
(
channel,
exchange=None,
routing_key=None,
serializer=None,
auto_declare=None,
compression=None,
on_return=None
)
[source]
消费者
基础
消费者使用一个连接(或者通道)和一个队列列表来消费消息.不同的消费者可以使用不同通道来消费相同的队列, 如果它们绑定的是同一个连接,drain_events
会消耗掉连接上所有通道上的事件.
注意
kombu从3.0 版本开始默认将只接受json/binary或者文本消息, 如果要反序列化其它格式的消息则需要指定accept参数:
从一个消费者上Draining events:
从不同的消费者上Draining events:
或者使用 ConsumerMixin
:
使用多个通道:
参考
class kombu.
Consumer
(channel, queues=None, no_ack=None, auto_declare=None, callbacks=None, on_decode_error=None, on_message=None, accept=None, tag_prefix=None)
-
[source]
-
消息消费者.
参数: - channel – see
channel
. - queues – see
queues
. - no_ack – see
no_ack
. - auto_declare – see
auto_declare
- callbacks – see
callbacks
. - on_message – See
on_message
- on_decode_error – see
on_decode_error
.
-
exception
-
消费者不允许这种消息类型content-type.
ContentDisallowed
-
可以接收的消息类型列表.
如果消费者接收到了不信任的消费类型会抛出异常. 默认情况下允许所有的消息类型,但是如果调用了
kombu.disable_untrusted_serializers()
将只允许接受json格式的消息.
Consumer.
accept
= None-
增加一个要消费的队列.
调用这个函数不会开始从队列中消费消息, 你需要在之后调用
consume()
.
Consumer.
add_queue
( queue ) [source]-
方法已经过时.
用下面的方法代替:
Consumer.
add_queue_from_dict
( queue, **options ) [source]-
默认情况下,所有的实体对象会在安装时声明, 如果你想手动控制对象的声明设置此值为
False
.
Consumer.
auto_declare
= True-
当消息到达时,按顺序调用的callbacks列表.
callbacks的签名需要2个参数: (body, message), 一个是解码后的消息体,另外一个是消息对象 (一个
Message的子类
).
Consumer.
callbacks
= None-
结束所有活动的队列消费.
不会对已经递交的消息起作用, 这意味着服务器不会再向这个消费者发送任何消息.
Consumer.
cancel
( ) [source]-
从指定的队列取消消费消息.
Consumer.
cancel_by_queue
( queue ) [source]-
这个消费者使用的connection/channel.
Consumer.
channel
= None-
结束所有活动的队列消费.
不会对已经递交的消息起作用, 这意味着服务器不会再向这个消费者发送任何消息.
Consumer.
close
( )-
开始消费消息.
可以被调用多次, 会从上次调用之后新加入的队列中消费消息, 它不会取消从删除的队列中消费消息 ( 使用
cancel_by_queue()
).参数: no_ack – 了解 no_ack
.
Consumer.
consume
( no_ack=None ) [source]-
返回True,如果消费者当前正在从队列中消费消息.
Consumer.
consuming_from
( queue ) [source]-
声明队列,交换器和绑定.
这些会自动声明,如果设置了
auto_declare
.
Consumer.
declare
( ) [source]-
Enable/disable对端的流.
这是一种简单的流控机制,一端可以防止自己的队列溢出或者发现自己接收到了它所能够处理的最大消息数.
接收到停止请求的一端会在发送完当前内容后停止发送走到另一端取消了流控.
Consumer.
flow
( active ) [source]-
是否自动对消息进行确认的标志. 如果开启,broker会自动确认消息. 这可以提高性能,但同时意味着消息被删除时你无法进行控制.
默认关闭.
Consumer.
no_ack
= None-
消息不能解码时的callback.
这个函数的签名有2个参数: (message, exc), 一个是解码失败的消息,另外一个是解码失败时抛出的异常.
Consumer.
on_decode_error
= None-
当消息接收时调用的可选函数.
这个函数会代替
receive()
方法, 同时callbacks
也会被禁用.所以当你不想消息被自动解码时可以用它来代替
callbacks
when you don’t want the body to be automatically decoded. 注意消息如果是压缩的仍然会被解压.这个函数的签名要求一个参数, 是原始的消息对象 (
Message的一个子类
).需要注意
message.body
属性, 代表了消息的原始内容, 在一些情况下是只读的buffer
对象.
Consumer.
on_message
= None-
从所有队列中清除消息.
Warning
这会删除所有准备好的消息,没有undo操作.
Consumer.
purge
( ) [source]-
指定qos.
客户端可以要求消息被提前发送在处理另外一个消息时,这样接下来的消息已经达到了本地而不需要再等待从channel中发送过来,这样的预发送机制可以提高性能.
如果设置了no_ack参数,则预发送窗口被忽略.
参数 : - prefetch_size – 8进制的预发送窗口大小. 服务端将会提前发送消息如果这个值小于等于可以预发送的大小 (还有一些其它限制). 设置为0意味着没有限制, 其它的限制仍会起作用.
- prefetch_count – 指定所有消息总共的预发送窗口.
- apply_global – 在所有通道上应用新的全局配置.
Consumer.
qos
( prefetch_size=0, prefetch_count=0, apply_global=False ) [source]-
用于消费的单个或者一个队列列表.
Consumer.
queues
= None-
消息达到时调用方法.
分发到注册的callbacks.
参数: - body – 解码后的消息体.
- message – 消息实例.
抛出: NotImplementedError – 如果没有注册callbacks.
Consumer.
receive
( body, message ) [source]-
重新递交没有确认的消息.
在指定的channel上要求broker重新递交所有未确认的消息.
参数: requeue – 默认情况下消息会被重新递交给原来的接收者.如果设置require为True,服务器将会尝试对消息进行重新排队,可能将它递交给另外一个候选消费者.
Consumer.
recover
( requeue=False ) [source]-
注册一个新的callback用于接收到消息时调用.
callback的签名需要接收2个参数:(body, message), 解码后的消息体和消息实例 (
Message的一个子类)
.
Consumer.
register_callback
( callback ) [source]-
连接丢失时唤醒消费者.
Consumer.
revive
( channel ) [source] - channel – see
例子
Hello World
下面的例子使用Simple Interface 通过broker发送一个"hello world"消息并打印接收到的消息.
hello_publisher.py
:
通过rabbitmqctl命令可以看到会声明一个'simple_queue'的direct交换器,发布消息后'simple_queue'队列里有一条消息:
bogon:~ zhangzhangxiaoan$ rabbitmqctl list_exchanges
Listing exchanges ...
simple_queuedirect
bogon:~ zhangzhangxiaoan$ rabbitmqctl list_queues
Listing queues ...
simple_queue1
hello_consumer.py
:
消息消费后再查看:
bogon:~ zhangzhangxiaoan$ rabbitmqctl list_queues
Listing queues ...
simple_queue0
Task Queue Example
很简单的使用pickle的任务队列,不同的优先级使用不同的队列.
queues.py
:
worker.py
:
tasks.py
:
client.py
:
Simple Interface
- 发送和接收消息
kombu.simple
是一个AMQP队列的简单接口. 它只是和Python标准库中的Queue略有不同,它对使用基本消息的用户十分合适.
不用定义exchanges和queues,simple类只需2个参数,一个connection channel和一个名字.这个名字用于queue,exchange和路由键.如果你有更多的需求,你可以指定一个队列来代替名字这个参数.
下面用当前的连接创建了一个简单队列:
>>>queue=connection.SimpleQueue('myqueue')
和下面的代码等价:
发送和接收消息
简单接口定义了2个类; SimpleQueue
, 和SimpleBuffer
. 前者用于持久化消息,后者用于事务,buffer-like队列.它们的接口是相同的,所以你可以在代码中相互替换它们使用.
下面的例子使用SimpleQueue
来生产和消费日志消息:
连接和生产者池
默认的池
Kombu提供了2个全局池: 一个连接池和一个生产者池.
这些很方便,因为它们经常被限制在进程级别而不是每个线程级别来使用,所以它们是全局的也不是什么问题, 但是如果你需要为每个线程来定制使用,请参考Custom Pool Groups.
连接池组
连接池可以通过kombu.pools.connections来使用
. 这是一个池组, 意味着你给它一个连接实例,你将会得到一个池的实例.对于每个连接实例都有一个池来支持在同一个app中使用多个连接.所有参数相同的连接实例将会得到同一个池.
让我们请求并释放一个连接:
注意
block=True
意味着acquire 将会阻塞到直到池中有一个可用的连接为止. 注意它会一直阻塞如果你的代码中有因为没有释放连接造成的死锁. 有一个timeout参数你可以用来安全地处理这种情况. (see kombu.connection.Resource.acquire()
).
如果采用非阻塞模式而连接池中没有可用的连接,则会抛出一个 kombu.exceptions.ConnectionLimitExceeded
异常.
如果你要一次连接多个broker你可以这样做:
生产者池组
它和连接池组一样,只不过它用来管理Producer
实例用来发布消息.
下面是一个例子,用生产者池发布消息到'news‘ exchange.
设置池的上限
默认情况下每个连接池最多有200个连接.你可以通过调用kombu.pools.set_limit()来设置
. 你可以在运行时增长连接池, 也可以收缩它的大小, 所以你最好在应用启动后尽早设置它的限制.
重置所有的池
你可以通过调用 kombu.pools.reset()
函数来关闭所有的活动连接和重置所有的连接池组 .注意这个操作不会关心那些正在使用的连接,所以你要小心使用它.
Comb 将会在fork时重置池,所以fork的新进程会拥有一个干净的连接池组.
定制池组
为了保持你自己的池组,你应该创建你自己的Connections
和kombu.pools.Producers
实例:
如果你想使用可以通过set_limit()
设置的全局限制,你需要使用一个特定的limit
参数:
序列化
序列化机制
默认情况下消息是用json来编码,所以发送字典和列表这些Python的数据结构可以很好的工作.也支持YAML, msgpack 和Python内置的pickle, 如果需要你可以注册任何自定义的序列化机制.
默认情况下,Kombu只会加载JSON消息,如果你想使用另外一种序列化机制你必须在你的消费者参数中显式地使用accept参数:
accept参数还可以指定MIME-types.
每种序列化机制都有优点和缺点.
-
json – JSON被很多编程语言支持
-
作为python标准的一部分(从2.6), 使用先进的python库解码非常快速比如cjson或simplejson.
JSON的主要缺点是限制你使用以下数据类型: strings, Unicode, floats, boolean, dictionaries, and lists. Decimals和dates会统统丢失.
另外, 二进制数据将会被用Base64来进行转换,这会造成传输的数据比本地的二进制数据变大34%.
但是,如果你的数据满足上面的限制条件并且需要跨语言支持,默认的son可能是对你最好的选择.
pickle – 如果你不需要其它语言的支持
-
Python, 使用pickle编码将会使你获得内置python数据类型的支持 (除了类实例), 相比于JSON发送二进制文件时消息会更小而且速度也会有轻微的提升.
Pickle 和安全
pickel格式十分方便而且能够序列和反序列化几乎所有对象,但是需要关注其安全问题.
精心的构造pickle payloads 几乎可以做任何python程序可以做的事, 所以如果你让你的消费者自动解码pickled对象,你必须确保broker的访问权限,这样那些未受信任的生产者就不能发送消息了.
默认情况下,Kombu使用pickle protocol 2, 但是这可以通过
PICKLE_PROTOCOL
环境变量或者改变全局标志kombu.serialization.pickle_protocol
.
yaml – YAML和
son相比有很多相似的特点,
-
另外它天然地支持更多的数据类型(包括 dates, recursive references, etc.)
但是,python的YAML库比JSON库要慢一些.
如果你需要更多的数据类型支持并且需要跨语言支持,yams相对以上可能是更好的选择.
要指定Kombu使用一个指定的序列化方法使用下面的选项.
为每个生产者设置一个序列化选项:
为每个消息设置一个序列化选项:
注意消费者不需要指定序列化方法.它们可以自动检测序列化方法因为消息头中的content-type字段指明了采用的序列化方法.
发送原始数据而不使用序列化
在一些情况下,你不需要数据被序列化.如果你发送普通字符串或者Unicode对象消息或者指定了content_type, Kombu就不会浪费时间序列化/反序列化数据了.
你可以为原始数据指定content_encoding:
消费者返回的消费对象将会包含 content_type 和 content_encoding 属性.
用Setuptools entry-points创建一个扩展
一个包可以注册新的序列化机制使用etuptools entry-points.
entry-point必须提供序列化机制的名称和一个指向剩余参数元组的模块路径
剩余参数包括: encoder_function, decoder_function,content_type, content_encoding
.
下面是一个entrypoint的例子:
my_module.serializer
应该向下面这样:
当这个包安装后,新的‘my_serializer’序列化机制将会被Kombu支持.
Buffer 对象
定制序列化机制的解码函数必须支持strings 和 Python以前的buffer objects.
Python pickle 和 json 模块通常不这样做,而是通过它的loads函数来支持,但是你可以通过包装使用文件对象而不是字符串的load函数来轻松地进行支持.
下面的例子是一种包装pickle.loads()的方法
: