使用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]
答案 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副本。这有点复杂,但最终可以给你最好的表现。