OpenStack基础之Kombu

前面在分析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的查询字符串部分还可以设置选项:

amqp://localhost/myvhost?ssl=1

所有支持的选项列表:Keyword arguments .

没有指定选项的连接将会使用默认的配置,使用localhost作为目的主机,默认端口号,用户名guest,密码guest和虚拟主机“/”。没有使用参数的连接和下面的连接一样:

>>> Connection('amqp://guest:guest@localhost:5672//')

默认的端口号和Transport有关,AMQP是5672.

其它的字段对不同的Transport也会有不同的含义. 比如, Redis transport使用virtual_host 参数作为redis数据库的编号。

AMQP Transports

对于AMQP来说,有4种transports可用.

  1. pyamqp 使用纯Python库amqp, 会自动与Kombu一起安装.
  2. librabbitmq使用C语言编写的高性能transport.这需要安装 librabbitmq Python包, 会自动用c库编译.
  3. amqp尝试使用librabbitmq 但是因为失败又转而使用pyamqp.
  4. quid使用纯python库qpid.messaging, 也会自动与Kombu一起安装. Quid库使用AMQP, 但是使用了被Apache Qpid Broker支持的特定扩展.

为了达到最高性能,你应该安装librabbitmq包. 为确保使用librabbitmq,你可以在transport URL里显式地指定,或者使用amp作为备选方案.

Transport 比较

ClientTypeDirectTopicFanout
amqpNativeYesYesYes
qpidNativeYesYesYes
redisVirtualYesYesYes (PUB/SUB)
mongodbVirtualYesYesYes
beanstalkVirtualYesYes [1]No
SQSVirtualYesYes [1]Yes [2]
couchdbVirtualYesYes [1]No
zookeeperVirtualYesYes [1]No
in-memoryVirtualYesYes [1]No
djangoVirtualYesYes [1]No
sqlalchemyVirtualYesYes [1]No


生产者

基础

序列化

了解Serialization.

参考

class  kombu. Producer ( channelexchange=Nonerouting_key=Noneserializer=Noneauto_declare=Nonecompression=Noneon_return=None ) [source]

消息生产者.

参数:
  • channel – Connection 或者channel.
  • exchange – 可选参数,使用的exchange.
  • routing_key – 可选参数,消息的路由键.
  • serializer – 使用的序列化机制,默认使用“json”.
  • compression – 使用的压缩方法. 默认不压缩.
  • auto_declare – 在安装时自动声明使用的exchange. 默认值是True.
  • on_return – 消息不能递交时使用的callback,需要在publish()方法中使用mandatoryimmediate参数. 这个callback的签名是: (exception, exchange, routing_key, message). 需要注意的是使用这个特性时,producer需要自己处理events.
auto_declare  = True

默认情况下会在构造对象时自动声明exchange. 如果你想手动声明,需要设置此值为False.

compression  = None

采用的压缩方法,默认不压缩.

declare ( ) [source]

声明exchange.

exchange会自动声明,如果开启了auto_declare.

exchange  = None

默认的exchange.

maybe_declare ( entityretry=False**retry_policy ) [source]

如果在会话里还没有声明exchange则声明.

on_return  = None

使用的callback,见参数描述.

publish ( bodyrouting_key=Nonedelivery_mode=Nonemandatory=Falseimmediate=Falsepriority=0content_type=Nonecontent_encoding=Noneserializer=Noneheaders=Nonecompression=Noneexchange=Noneretry=Falseretry_policy=Nonedeclare=[]expiration=None**properties ) [source]

向指定的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说明.
revive ( channel ) [source]

连接断开后重新复活producer.

routing_key  = ''

默认的路由键.

serializer  = None

使用的序列化机制. 默认使用JSON.


消费者


基础


消费者使用一个连接(或者通道)和一个队列列表来消费消息.不同的消费者可以使用不同通道来消费相同的队列, 如果它们绑定的是同一个连接,drain_events 会消耗掉连接上所有通道上的事件.

注意

kombu从3.0 版本开始默认将只接受json/binary或者文本消息, 如果要反序列化其它格式的消息则需要指定accept参数:

Consumer(conn, accept=['json', 'pickle', 'msgpack', 'yaml'])

从一个消费者上Draining events:

with Consumer(connection, queues, accept=['json']):
    connection.drain_events(timeout=1)

从不同的消费者上Draining events:

from kombu.utils import nested

with connection.channel(), connection.channel() as (channel1, channel2):
    with nested(Consumer(channel1, queues1, accept=['json']),
                Consumer(channel2, queues2, accept=['json'])):
        connection.drain_events(timeout=1)

或者使用 ConsumerMixin:

from kombu.mixins import ConsumerMixin

class C(ConsumerMixin):

    def __init__(self, connection):
        self.connection = connection

    def get_consumers(self, Consumer, channel):
        return [
            Consumer(queues, callbacks=[self.on_message], accept=['json']),
        ]

    def on_message(self, body, message):
        print("RECEIVED MESSAGE: %r" % (body, ))
        message.ack()

C(connection).run()

使用多个通道:

from kombu import Consumer
from kombu.mixins import ConsumerMixin

class C(ConsumerMixin):
    channel2 = None

    def __init__(self, connection):
        self.connection = connection

    def get_consumers(self, _, default_channel):
        self.channel2 = default_channel.connection.channel()
        return [Consumer(default_channel, queues1,
                         callbacks=[self.on_message],
                         accept=['json']),
                Consumer(self.channel2, queues2,
                         callbacks=[self.on_special_message],
                         accept=['json'])]

    def on_consumer_end(self, connection, default_channel):
        if self.channel2:
            self.channel2.close()

C(connection).run()

参考

class kombu.Consumer(channelqueues=Noneno_ack=Noneauto_declare=Nonecallbacks=Noneon_decode_error=Noneon_message=Noneaccept=Nonetag_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  ContentDisallowed

消费者不允许这种消息类型content-type.

Consumer. accept  = None

可以接收的消息类型列表.

如果消费者接收到了不信任的消费类型会抛出异常. 默认情况下允许所有的消息类型,但是如果调用了kombu.disable_untrusted_serializers()将只允许接受json格式的消息.

Consumer. add_queue ( queue ) [source]

增加一个要消费的队列.

调用这个函数不会开始从队列中消费消息, 你需要在之后调用consume().

Consumer. add_queue_from_dict ( queue**options ) [source]

方法已经过时.

用下面的方法代替:

consumer.add_queue(Queue.from_dict(d))
Consumer. auto_declare  = True

默认情况下,所有的实体对象会在安装时声明, 如果你想手动控制对象的声明设置此值为 False.

Consumer. callbacks  = None

当消息到达时,按顺序调用的callbacks列表.

callbacks的签名需要2个参数: (body, message), 一个是解码后的消息体,另外一个是消息对象 (一个Message的子类).

Consumer. cancel ( ) [source]

结束所有活动的队列消费.

不会对已经递交的消息起作用, 这意味着服务器不会再向这个消费者发送任何消息.

Consumer. cancel_by_queue ( queue ) [source]

从指定的队列取消消费消息.

Consumer. channel  = None

这个消费者使用的connection/channel.

Consumer. close ( )

结束所有活动的队列消费.

不会对已经递交的消息起作用, 这意味着服务器不会再向这个消费者发送任何消息.

Consumer. consume ( no_ack=None ) [source]

开始消费消息.

可以被调用多次, 会从上次调用之后新加入的队列中消费消息, 它不会取消从删除的队列中消费消息 ( 使用cancel_by_queue()).

参数:no_ack – 了解no_ack.
Consumer. consuming_from ( queue ) [source]

返回True,如果消费者当前正在从队列中消费消息.

Consumer. declare ( ) [source]

声明队列,交换器和绑定.

这些会自动声明,如果设置了auto_declare.

Consumer. flow ( active ) [source]

Enable/disable对端的流.

这是一种简单的流控机制,一端可以防止自己的队列溢出或者发现自己接收到了它所能够处理的最大消息数.

接收到停止请求的一端会在发送完当前内容后停止发送走到另一端取消了流控.

Consumer. no_ack  = None

是否自动对消息进行确认的标志. 如果开启,broker会自动确认消息. 这可以提高性能,但同时意味着消息被删除时你无法进行控制.

默认关闭.

Consumer. on_decode_error  = None

消息不能解码时的callback.

这个函数的签名有2个参数: (message, exc), 一个是解码失败的消息,另外一个是解码失败时抛出的异常.

Consumer. on_message  = None

当消息接收时调用的可选函数.

这个函数会代替 receive()方法, 同时 callbacks 也会被禁用.

所以当你不想消息被自动解码时可以用它来代替callbacks when you don’t want the body to be automatically decoded. 注意消息如果是压缩的仍然会被解压.

这个函数的签名要求一个参数, 是原始的消息对象 (Message的一个子类).

需要注意message.body属性, 代表了消息的原始内容, 在一些情况下是只读的buffer 对象.

Consumer. purge ( ) [source]

从所有队列中清除消息.

Warning

这会删除所有准备好的消息,没有undo操作.

Consumer. qos ( prefetch_size=0prefetch_count=0apply_global=False ) [source]

指定qos.

客户端可以要求消息被提前发送在处理另外一个消息时,这样接下来的消息已经达到了本地而不需要再等待从channel中发送过来,这样的预发送机制可以提高性能.

如果设置了no_ack参数,则预发送窗口被忽略.

参数 :
  • prefetch_size – 8进制的预发送窗口大小. 服务端将会提前发送消息如果这个值小于等于可以预发送的大小 (还有一些其它限制). 设置为0意味着没有限制, 其它的限制仍会起作用.
  • prefetch_count – 指定所有消息总共的预发送窗口.
  • apply_global – 在所有通道上应用新的全局配置.
Consumer. queues  = None

用于消费的单个或者一个队列列表.

Consumer. receive ( bodymessage ) [source]

消息达到时调用方法.

分发到注册的callbacks.

参数:
  • body – 解码后的消息体.
  • message – 消息实例.
抛出:

NotImplementedError – 如果没有注册callbacks.

Consumer. recover ( requeue=False ) [source]

重新递交没有确认的消息.

在指定的channel上要求broker重新递交所有未确认的消息.

参数:requeue – 默认情况下消息会被重新递交给原来的接收者.如果设置require为True,服务器将会尝试对消息进行重新排队,可能将它递交给另外一个候选消费者.
Consumer. register_callback ( callback ) [source]

注册一个新的callback用于接收到消息时调用.

callback的签名需要接收2个参数:(body, message), 解码后的消息体和消息实例 (Message的一个子类).

Consumer. revive ( channel ) [source]

连接丢失时唤醒消费者.


例子


Hello World 

下面的例子使用Simple Interface 通过broker发送一个"hello world"消息并打印接收到的消息.

hello_publisher.py:

from kombu import Connection
import datetime

with Connection('amqp://guest:guest@localhost:5672//') as conn:
    simple_queue = conn.SimpleQueue('simple_queue')
    message = 'helloword, sent at %s' % datetime.datetime.today()
    simple_queue.put(message)
    print('Sent: %s' % message)
    simple_queue.close()

通过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:

from kombu import Connection

with Connection('amqp://guest:guest@localhost:5672//') as conn:
    simple_queue = conn.SimpleQueue('simple_queue')
    message = simple_queue.get(block=True, timeout=1)
    print("Received: %s" % message.payload)
    message.ack()
    simple_queue.close()

消息消费后再查看:

bogon:~ zhangzhangxiaoan$ rabbitmqctl list_queues

Listing queues ...

simple_queue0


Task Queue Example

很简单的使用pickle的任务队列,不同的优先级使用不同的队列.

queues.py:

from kombu import Exchange, Queue

task_exchange = Exchange('tasks', type='direct')
task_queues = [Queue('hipri', task_exchange, routing_key='hipri'),
               Queue('midpri', task_exchange, routing_key='midpri'),
               Queue('lopri', task_exchange, routing_key='lopri')]

worker.py:

from kombu.mixins import ConsumerMixin
from kombu.log import get_logger
from kombu.utils import kwdict, reprcall

from .queues import task_queues

logger = get_logger(__name__)


class Worker(ConsumerMixin):

    def __init__(self, connection):
        self.connection = connection

    def get_consumers(self, Consumer, channel):
        return [Consumer(queues=task_queues,
                         accept=['pickle', 'json'],
                         callbacks=[self.process_task])]

    def process_task(self, body, message):
        fun = body['fun']
        args = body['args']
        kwargs = body['kwargs']
        logger.info('Got task: %s', reprcall(fun.__name__, args, kwargs))
        try:
            fun(*args, **kwdict(kwargs))
        except Exception as exc:
            logger.error('task raised exception: %r', exc)
        message.ack()

if __name__ == '__main__':
    from kombu import Connection
    from kombu.utils.debug import setup_logging
    # setup root logger
    setup_logging(loglevel='INFO', loggers=[''])

    with Connection('amqp://guest:guest@localhost:5672//') as conn:
        try:
            worker = Worker(conn)
            worker.run()
        except KeyboardInterrupt:
            print('bye bye')

tasks.py:

def hello_task(who="world"):
    print("Hello %s" % (who, ))
 
  

client.py:

from kombu.pools import producers

from .queues import task_exchange

priority_to_routing_key = {'high': 'hipri',
                           'mid': 'midpri',
                           'low': 'lopri'}


def send_as_task(connection, fun, args=(), kwargs={}, priority='mid'):
    payload = {'fun': fun, 'args': args, 'kwargs': kwargs}
    routing_key = priority_to_routing_key[priority]

    with producers[connection].acquire(block=True) as producer:
        producer.publish(payload,
                         serializer='pickle',
                         compression='bzip2',
                         exchange=task_exchange,
                         declare=[task_exchange],
                         routing_key=routing_key)

if __name__ == '__main__':
    from kombu import Connection
    from .tasks import hello_task

    connection = Connection('amqp://guest:guest@localhost:5672//')
    send_as_task(connection, fun=hello_task, args=('Kombu', ), kwargs={},
                 priority='high')


Simple Interface

  • 发送和接收消息

kombu.simple 是一个AMQP队列的简单接口. 它只是和Python标准库中的Queue略有不同,它对使用基本消息的用户十分合适.

不用定义exchanges和queues,simple类只需2个参数,一个connection channel和一个名字.这个名字用于queue,exchange和路由键.如果你有更多的需求,你可以指定一个队列来代替名字这个参数.

下面用当前的连接创建了一个简单队列:

>>>queue=connection.SimpleQueue('myqueue')

>>> # ... do something with queue
>>> queue.close()

和下面的代码等价:

>>> from kombu import SimpleQueue, SimpleBuffer

>>> channel = connection.channel()
>>> queue = SimpleBuffer(channel)
>>> # ... do something with queue
>>> channel.close()
>>> queue.close()

发送和接收消息

简单接口定义了2个类; SimpleQueue, 和SimpleBuffer. 前者用于持久化消息,后者用于事务,buffer-like队列.它们的接口是相同的,所以你可以在代码中相互替换它们使用.

下面的例子使用SimpleQueue 来生产和消费日志消息:

import socket
import datetime
from time import time
from kombu import Connection


class Logger(object):

    def __init__(self, connection, queue_name='log_queue',
            serializer='json', compression=None):
        self.queue = connection.SimpleQueue(queue_name)
        self.serializer = serializer
        self.compression = compression

    def log(self, message, level='INFO', context={}):
        self.queue.put({'message': message,
                        'level': level,
                        'context': context,
                        'hostname': socket.gethostname(),
                        'timestamp': time()},
                        serializer=self.serializer,
                        compression=self.compression)

    def process(self, callback, n=1, timeout=1):
        for i in xrange(n):
            log_message = self.queue.get(block=True, timeout=1)
            entry = log_message.payload # deserialized data.
            callback(entry)
            log_message.ack() # remove message from queue

    def close(self):
        self.queue.close()


if __name__ == '__main__':
    from contextlib import closing

    with Connection('amqp://guest:guest@localhost:5672//') as conn:
        with closing(Logger(conn)) as logger:

            # Send message
            logger.log('Error happened while encoding video',
                        level='ERROR',
                        context={'filename': 'cutekitten.mpg'})

            # Consume and process message

            # This is the callback called when a log message is
            # received.
            def dump_entry(entry):
                date = datetime.datetime.fromtimestamp(entry['timestamp'])
                print('[%s %s %s] %s %r' % (date,
                                            entry['hostname'],
                                            entry['level'],
                                            entry['message'],
                                            entry['context']))

            # Process a single message using the callback above.
            logger.process(dump_entry, n=1)

连接和生产者池

默认的池

Kombu提供了2个全局池: 一个连接池和一个生产者池.

这些很方便,因为它们经常被限制在进程级别而不是每个线程级别来使用,所以它们是全局的也不是什么问题, 但是如果你需要为每个线程来定制使用,请参考Custom Pool Groups.

连接池组

连接池可以通过kombu.pools.connections来使用. 这是一个池组, 意味着你给它一个连接实例,你将会得到一个池的实例.对于每个连接实例都有一个池来支持在同一个app中使用多个连接.所有参数相同的连接实例将会得到同一个池.

>>> from kombu import Connection
>>> from kombu.pools import connections

>>> connections[Connection('redis://localhost:6379')]
<kombu.connection.ConnectionPool object at 0x101805650>
>>> connections[Connection('redis://localhost:6379')]
<kombu.connection.ConnectionPool object at 0x101805650>

让我们请求并释放一个连接:

from kombu import Connection
from kombu.pools import connections

connection = Connection('redis://localhost:6379')

with connections[connection].acquire(block=True) as conn:
    print('Got connection: %r' % (connection.as_uri(), ))

注意

block=True意味着acquire 将会阻塞到直到池中有一个可用的连接为止. 注意它会一直阻塞如果你的代码中有因为没有释放连接造成的死锁. 有一个timeout参数你可以用来安全地处理这种情况. (see kombu.connection.Resource.acquire()).

如果采用非阻塞模式而连接池中没有可用的连接,则会抛出一个 kombu.exceptions.ConnectionLimitExceeded 异常.

如果你要一次连接多个broker你可以这样做:

from kombu import Connection
from kombu.pools import connections

c1 = Connection('amqp://')
c2 = Connection('redis://')

with connections[c1].acquire(block=True) as conn1:
    with connections[c2].acquire(block=True) as conn2:
        # ....

生产者池组

它和连接池组一样,只不过它用来管理Producer实例用来发布消息.

下面是一个例子,用生产者池发布消息到'news‘ exchange.

from kombu import Connection, Exchange
from kombu.pools import producers

# The exchange we send our news articles to.
news_exchange = Exchange('news')

# The article we want to send
article = {'title': 'No cellular coverage on the tube for 2012',
           'ingress': 'yadda yadda yadda'}

# The broker where our exchange is.
connection = Connection('amqp://guest:guest@localhost:5672//')

with producers[connection].acquire(block=True) as producer:
    producer.publish(
        article,
        exchange=new_exchange,
        routing_key='domestic',
        declare=[news_exchange],
        serializer='json',
        compression='zlib')

设置池的上限

默认情况下每个连接池最多有200个连接.你可以通过调用kombu.pools.set_limit()来设置. 你可以在运行时增长连接池, 也可以收缩它的大小, 所以你最好在应用启动后尽早设置它的限制.

>>> from kombu import pools
>>> pools.set_limit()

重置所有的池

你可以通过调用 kombu.pools.reset() 函数来关闭所有的活动连接和重置所有的连接池组 .注意这个操作不会关心那些正在使用的连接,所以你要小心使用它.

Comb 将会在fork时重置池,所以fork的新进程会拥有一个干净的连接池组.

定制池组

为了保持你自己的池组,你应该创建你自己的Connections 和kombu.pools.Producers 实例:

from kombu import pools
from kombu import Connection

connections = pools.Connections(limit=100)
producers = pools.Producers(limit=connections.limit)

connection = Connection('amqp://guest:guest@localhost:5672//')

with connections[connection].acquire(block=True):
    # ...

如果你想使用可以通过set_limit() 设置的全局限制,你需要使用一个特定的limit参数:

from kombu import pools

connections = pools.Connections(limit=pools.use_global_limit)


序列化

序列化机制

默认情况下消息是用json来编码,所以发送字典和列表这些Python的数据结构可以很好的工作.也支持YAMLmsgpack 和Python内置的pickle, 如果需要你可以注册任何自定义的序列化机制.

默认情况下,Kombu只会加载JSON消息,如果你想使用另外一种序列化机制你必须在你的消费者参数中显式地使用accept参数:

Consumer(conn, [queue], accept=['json', 'pickle', 'msgpack'])

accept参数还可以指定MIME-types.

每种序列化机制都有优点和缺点.

json – JSON被很多编程语言支持

作为python标准的一部分(从2.6), 使用先进的python库解码非常快速比如cjsonsimplejson.

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使用一个指定的序列化方法使用下面的选项.

  1. 为每个生产者设置一个序列化选项:

    >>> producer = Producer(channel,
    ...                     exchange=exchange,
    ...                     serializer="yaml")
    
  2. 为每个消息设置一个序列化选项:

    >>> producer.publish(message, routing_key=rkey,
    ...                  serializer="pickle")
    

注意消费者不需要指定序列化方法.它们可以自动检测序列化方法因为消息头中的content-type字段指明了采用的序列化方法.

发送原始数据而不使用序列化

在一些情况下,你不需要数据被序列化.如果你发送普通字符串或者Unicode对象消息或者指定了content_type, Kombu就不会浪费时间序列化/反序列化数据了.

你可以为原始数据指定content_encoding:

>>> with open("~/my_picture.jpg", "rb") as fh:
...     producer.publish(fh.read(),
                         content_type="image/jpeg",
                         content_encoding="binary",
                         routing_key=rkey)

消费者返回的消费对象将会包含 content_type 和 content_encoding 属性.

用Setuptools entry-points创建一个扩展

一个包可以注册新的序列化机制使用etuptools entry-points.

entry-point必须提供序列化机制的名称和一个指向剩余参数元组的模块路径

剩余参数包括: encoder_function, decoder_function,content_type, content_encoding.

下面是一个entrypoint的例子:

from setuptools import setup

setup(
    entry_points={
        'kombu.serializers': [
            'my_serializer = my_module.serializer:register_args'
        ]
    }
)

 my_module.serializer 应该向下面这样:

register_args = (my_encoder, my_decoder, 'application/x-mimetype', 'utf-8')

当这个包安装后,新的‘my_serializer’序列化机制将会被Kombu支持.

Buffer 对象

定制序列化机制的解码函数必须支持strings 和 Python以前的buffer objects.

Python pickle 和 json 模块通常不这样做,而是通过它的loads函数来支持,但是你可以通过包装使用文件对象而不是字符串的load函数来轻松地进行支持.

下面的例子是一种包装pickle.loads()的方法:

import pickle
from kombu.serialization import BytesIO, register


def loads(s):
    return pickle.load(BytesIO(s))

register('my_pickle', pickle.dumps, loads,
        content_type='application/x-pickle2',
        content_encoding='binary')


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

self-motivation

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值