具有主动删除旧消息的字典+队列数据结构

时间:2011-05-04 19:19:26

标签: python data-structures queue

我想创建一个表示一组队列的数据结构(理想情况下是散列,映射或类似查找的字典),其中队列中的消息在达到特定年龄后被主动删除。 ttl值是全局的;消息不需要也没有个别的ttl。 ttl的分辨率不需要非常准确 - 仅在一秒左右。

我甚至不确定在这里搜索什么。我可以创建一个单独的全局队列,后台线程正在监视,查看和拉出指向全局队列中的消息的指针,告诉它从各个队列中删除项目,但行为需要双向进行。如果某个项目从一个invidual队列中删除,则需要从全局队列中删除。

我希望这个数据结构能够在Python中实现,理想情况下,速度是最重要的(比内存使用更多)。有关从哪里开始的任何建议?

2 个答案:

答案 0 :(得分:2)

我首先只是在单个类中建模您正在寻找的行为,尽可能简单地表达。性能可以通过迭代优化来实现,但仅在必要时(您可能不需要它)。

下面的课程大致类似于你所描述的内容。队列只是命名并存储在字典中的列表。每条消息都带有时间戳,并插入列表的前面(FIFO)。通过检查列表末尾的消息的时间戳,然后弹出消息直到它达到低于年龄阈值的消息来获取消息。

如果您计划从多个线程访问它,您需要添加一些细粒度锁定以挤出最大的性能。例如,reap()方法一次只能锁定1个队列,而不是锁定所有队列(方法级同步),因此您还需要为每个命名队列保持锁定。

已更新 - 现在使用一组全局存储桶(按时间戳,1秒分辨率)来跟踪哪些队列从那时起有消息。这减少了每次通过时要检查的队列数。

import time
from collections import defaultdict

class QueueMap(object):

    def __init__(self):
        self._expire = defaultdict(lambda *n: defaultdict(int))
        self._store = defaultdict(list)
        self._oldest_key = int(time.time())

    def get_queue(self, name):
        return self._store.get(name, [])

    def pop(self, name):
        queue = self.get_queue(name)
        if queue:
            key, msg = queue.pop()
            self._expire[key][name] -= 1
            return msg
        return None

    def set(self, name, message):
        key = int(time.time())
        # increment count of messages in this bucket/queue
        self._expire[key][name] += 1
        self._store[name].insert(0, (key, message))

    def reap(self, age):
        now = time.time()
        threshold = int(now - age)
        oldest = self._oldest_key

        # iterate over buckets we need to check
        for key in range(oldest, threshold + 1):
            # for each queue with items, expire the oldest ones
            for name, count in self._expire[key].iteritems():
                if count <= 0:
                    continue

                queue = self.get_queue(name)
                while queue:
                    if queue[-1][0] > threshold:
                        break
                    queue.pop()
            del self._expire[key]

        # set oldest_key for next pass
        self._oldest_key = threshold

用法:

qm = QueueMap()
qm.set('one', 'message 1')
qm.set('one', 'message 2')
qm.set('two', 'message 3')
print qm.pop('one')
print qm.get_queue('one')
print qm.get_queue('two')

# call this on a background thread which sleeps
time.sleep(2)
# reap messages older than 1 second
qm.reap(1)
# queues should be empty now
print qm.get_queue('one')
print qm.get_queue('two')

答案 1 :(得分:0)

每次访问队列时都要考虑检查TTL,而不是使用线程来不断检查。我不确定你对hash / map / dict的意思(关键是什么?),但这样的事情怎么样:

import time
class EmptyException(Exception): pass
class TTLQueue(object):
    TTL = 60 # seconds
    def __init__(self):
        self._queue = []

    def push(self, msg):
        self._queue.append((time.time()+self.TTL, msg))

    def pop(self):
        self._queue = [(t, msg) for (t, msg) in self._queue if t > time.time()]
        if len(self._queue) == 0:
            raise EmptyException()
        return self._queue.pop(0)[1]

queues = [TTLQueue(), TTLQueue(), TTLQueue()]  # this could be a dict or set or
                                               #    whatever if I knew what keys
                                               #    you expected