使用Python在Google App Engine中的app start上加载全局可访问的单例

时间:2010-06-16 03:01:55

标签: python google-app-engine

使用Google应用引擎,是否可以在应用启动时初始化全局可访问的单例?我有一个大的静态树结构,我需要在每个请求上使用,并希望事先初始化它。树结构太大(20 + MB)要放入Memcache中,我试图弄清楚我还有其他选择。

编辑:只是为了根据我到目前为止收到的答案添加一些清晰度。我正在将单词词典加载到trie / prefix树结构中。由于单词词典是固定的,因此trie是不可变的。我正在根据输入的字符串生成字谜,因此一个请求可以在单个请求中访问相当数量的trie,可能更多1MB,但我不确定。

这是我正在加载单词词典的python结构。

class Node(object):

    def __init__(self, letter='', final=False):
        self.letter = letter
        self.final = final
        self.children = {}

    def add(self, letters):
        node = self
        for index, letter in enumerate(letters):
            if letter not in node.children:
                node.children[letter] = Node(letter, index==len(letters)-1)
            node = node.children[letter]

4 个答案:

答案 0 :(得分:4)

每个请求可能来自完全不同的进程,位于不同的服务器上,甚至可能位于不同的数据中心(嘿,可能在不同的大陆)。除了数据存储之外,没有什么能保证对同一个应用程序的不同请求的处理程序“全局可访问”(即使事情变得太忙,甚至memcache的条目也可能随时消失:它是缓存,毕竟! - )。

也许您可以将“静态树结构”保存在与应用程序代码一起上载的数据文件中,并从磁盘访问它而不是“初始化”。

编辑:根据要求,这是一个粗略的,准备好的“轻量级类映射树到阵列”方法的例子,我在评论中提到过 - 没有经过调整也没有经过精心测试。我以一个带有整数有效载荷的二叉搜索树为例,并假设由于某种原因,将“精确”结构保持在“光”树中,就像它所代表的“重”树一样重要。即使有了这些简化,它仍然是很多代码,但是,来了:

import array
import random

def _doinsert(tree, payload):
  if tree is None: return HeavyTree(payload)
  tree.insert(payload)
  return tree

class HeavyTree(object):
  def __init__(self, payload):
    self.payload = payload
    self.left = self.right = None
  def insert(self, other):
    if other <= self.payload:
      self.left = _doinsert(self.left, other)
    else:
      self.right = _doinsert(self.right, other)
  def walk(self):
    if self.left:
      for x in self.left.walk(): yield x
    yield self.payload
    if self.right:
      for x in self.right.walk(): yield x
  def walknodes(self):
    yield self
    if self.left:
      for x in self.left.walknodes(): yield x
    if self.right:
      for x in self.right.walknodes(): yield x

data = [random.randint(0, 99) for _ in range(9)]
print 'data: ',
for x in data: print x,
print
theiter = iter(data)
thetree = HeavyTree(next(theiter))
for x in theiter: thetree.insert(x)

print
print 'Heavy tree:'
print 'nodes:',
for x in thetree.walknodes(): print x.payload,
print
print 'inord:',
for x in thetree.walk(): print x,
print

class LightTree(HeavyTree):
  def __init__(self, base, offset):
    self.base = base
    self.offset = offset
  @property
  def payload(self):
    return self.base[self.offset]
  @property
  def left(self):
    return self._astree(self.offset+1)
  @property
  def right(self):
    return self._astree(self.offset+2)
  def _astree(self, i):
    offset = self.base[i]
    if offset < 0: return None
    return LightTree(self.base, offset)

def heavy_to_light(heavy):
  for i, node in enumerate(heavy.walknodes()):
    node.id = i * 3
  base = array.array('l', (i+1) * 3 * [-1])
  for node in heavy.walknodes():
    base[node.id] = node.payload
    if node.left: base[node.id+1] = node.left.id
    if node.right: base[node.id+2] = node.right.id
  return LightTree(base, 0)

print
print 'Light tree:'
light = heavy_to_light(thetree)
print 'nodes:',
for x in light.walknodes(): print x.payload,
print
print 'base :',
for x in light.base: print x,
print
print 'inord:',
for x in light.walk(): print x,
print

典型的运行会显示:

data:  27 79 90 60 82 80 3 94 76

Heavy tree:
nodes: 27 3 79 60 76 90 82 80 94
inord: 3 27 60 76 79 80 82 90 94

Light tree:
nodes: 27 3 79 60 76 90 82 80 94
base : 27 3 6 3 -1 -1 79 9 15 60 -1 12 76 -1 -1 90 18 24 82 21 -1 80 -1 -1 94 -1 -1
inord: 3 27 60 76 79 80 82 90 94
当然,

每次都会变量,因为数据是随机生成的。

对于那些没有从古老的Fortran开始的人来说,也许这种事情太麻烦了(因此不可避免地学会了如何将逻辑指针表示为数组中的索引),正如我几十年前在EE学校所做的那样;-)。但是将这样的数组从文件直接加载到内存 非常快(与unpickling等相比)......! - )

答案 1 :(得分:3)

我认为Google会为运行项目的每个实例提供300MB的本地内存。因此,您所要做的就是将树结构存储到某个模块中的变量中。

每当Google为您的应用程序启动新进程时,它都会运行代码来构建您的树一次,然后您可以访问该进程以处理该进程处理的未来请求。只需确保构建树只需不到30秒,因为它必须在任何随机请求的时间范围内发生,这使得Google决定启动新流程。

答案 2 :(得分:3)

您需要在一次请求中访问多少这棵树?你以什么方式查询它?它有变化吗?

如果它是不可变的,那么你并不需要一个“单例” - 这意味着可变性 - 只是一种访问每个实例上的数据的方法。根据您访问它的方式,您可以将其存储为数据文件,blob或数据存储区中的数据。

答案 3 :(得分:0)

Memcache和数据存储区是您在所有实例中进行真正全局访问的最佳选择。但是,全局变量仍可用于在每个实例中缓存数据结构。 trie结构是不是很容易分解成块,这样每个块都适合memcache?一旦你将你的trie带入了memcache,只要你从memcache访问一个trie块,你就可以将它存储在该实例的全局变量中。在一些请求的过程中,您将在已运行的每个实例上构建完整的trie副本。这有点复杂,但最终可以给你最好的表现。