【问题标题】:Python application deployed on Google-App-Engine rarely throws an exception部署在 Google-App-Engine 上的 Python 应用程序很少抛出异常
【发布时间】:2014-02-12 14:37:43
【问题描述】:

我已经使用 Python 在 Google-App-Engine 上部署了一个网站。

由于GAE不保证“keep-alive”,所以我实现了一个无状态服务器:

  1. 每次内部变量发生变化时,它们都会立即存储到 GQL 数据库中。

  2. 每当进程启动时,所有内部变量都会从 GQL 数据库中加载。

我有一个场景很少抛出异常,但我一直无法追踪它:

  • 客户端发送一个同步的 AJAX POST 请求。

  • 服务器创建一个会话并在响应中发送一个唯一的会话 ID。

  • 客户端发送一个以会话 ID 作为参数的同步 AJAX GET 请求。

  • 服务器在响应中发送一些文本消息。

由于客户端请求是同步的,所以整个序列也是同步的。

这是我服务器中的相关映射:

from webapp2 import WSGIApplication
from Handler import MyRequestHandler

app = WSGIApplication([
    ('/request1'    ,MyRequestHandler), # post request
    ('/request2(.*)',MyRequestHandler), # get request
])

这是我的服务器中的相关请求处理:

from webapp2 import RequestHandler
from Server  import MyServer

myServer = MyServer()

class MyRequestHandler(RequestHandler):
    def post(self):
        try:
            if self.request.path.startswith('/request1'):
                sessionId = myServer.GetNewSessionId()
                self.SendContent('text/plain',sessionId)
        except Exception,error:
            self.SendError(error)
    def get(self,sessionId):
        try:
            if self.request.path.startswith('/request2'):
                textMessage = myServer.GetMsg(sessionId)
                self.SendContent('text/plain',textMessage)
        except Exception,error:
            self.SendError(error)
    def SendContent(self,contentType,contentData):
        self.response.set_status(200)
        self.response.headers['content-type'] = contentType
        self.response.headers['cache-control'] = 'no-cache'
        self.response.write(contentData)
    def SendError(self,error):
        self.response.set_status(500)
        self.response.write(error.message)

这是我的服务器的内部实现:

class MyServer():
    def __init__(self):
        self.sessions = SessionsTable.ReadSessions()
    def GetNewSessionId(self):
        while True:
            sessionId = ... # a 16-digit random number
            if SessionsTable.ReserveSession(sessionId):
                self.sessions[sessionId] = ... # a text message
                SessionsTable.WriteSession(self.sessions,sessionId)
                return sessionId
    def GetMsg(self,sessionId):
        return self.sessions[sessionId]

最后,这是我服务器中的数据库维护:

from google.appengine.ext import db

class SessionsTable(db.Model):
    message = db.TextProperty()
    @staticmethod
    def ReadSessions():
        sessions = {}
        for session in SessionsTable.all():
            sessions[session.key().name()] = session.message
        return sessions
    @staticmethod
    @db.transactional
    def ReserveSession(sessionId):
        if not SessionsTable.get_by_key_name(sessionId):
            SessionsTable(key_name=sessionId,message='').put()
            return True
        return False
    @staticmethod
    def WriteSession(sessions,sessionId):
        SessionsTable(key_name=sessionId,message=sessions[sessionId]).put()
    @staticmethod
    def EraseSession(sessionId):
        SessionsTable.get_by_key_name(sessionId).delete()

异常本身表明使用sessionId 键对sessions 字典的非法访问。根据我的观察,只有在服务器“已经休眠”了一段相对较长的时间(比如几天左右)之后启动本问题开头描述的客户端-服务器序列时才会发生这种情况。虽然我看不到它,但它可能会提供某种线索来解决这个问题的根源。

我的问题:

  1. 我的设计有什么明显的问题吗?

  2. 有人在 GAE 上遇到过类似的问题吗?

  3. 有没有人看到明显的解决方案,甚至是调试方法可能有助于理解这个问题?

谢谢

【问题讨论】:

  • 使用类级变量可能是个坏主意。如果您启用了线程,那么您可能会泄漏信息。
  • 你指的是class SessionsTable吗?我没有这个类的实例,所以这里应该没有任何问题,对吧?或者你的意思是全局变量myServer可能有问题,没有得到适当的保护?我没有使用任何线程,所以我有理由“担心”GAE 隐式创建它们吗?谢谢
  • 所有这些都可能是个问题。如果您启用了线程,那么 appengine 将隐式创建线程来服务每个请求(如果实例快速响应)。因此,您必须将您的应用程序设计为线程安全的。永远不要在模块级别存储东西,或者作为类属性存储,除非它可以共享。即它可以作为共享数据的实例级缓存。

标签: google-app-engine python-2.7 stateless-session


【解决方案1】:

您错误地假设所有请求都由同一个实例处理。情况并非如此:GAE 与大多数托管环境一样,不保证哪个服务器进程将处理任何请求。

您的实现正在使用模块级变量myServer,它是一个具有自己的实例变量的类实例。但是每个服务器进程都有自己的 myServer 实例,并且它们不会在进程之间共享:因此在一个请求中创建的字典条目不一定会在一秒钟内存在。

您需要研究跨实例保留此数据的方法。确实,这就是数据存储的首要用途。如果您担心开销,您应该使用 memcache 进行调查。

【讨论】:

  • 谢谢。我确实考虑到了多个服务器实例同时运行的可能性,但我没有将它添加到一个服务器将处理第一个 (POST) 请求而另一个服务器已经存在的情况将处理第二个(GET)请求。我将客户端请求定义为同步的,我想我也隐含地假设了服务器端的“同步”行为。
  • 不过有一个问题:服务器很少“中断”(我的网站还没有那么受欢迎),如果有的话,我必须在某个时候处理 0 个服务器实例的情况(这使我想到了“无国籍”设计)。为什么 GAE 会为几乎不为任何人服务的应用程序创建两个实例?我问这个的原因是,我怀疑手头可能还有另一个问题。谢谢。
  • 这种情况一直都在发生,他们启动一个实例,在启动期间第二个请求进入,并且没有被处理然后第二个启动。那么谁知道哪个实例将为下一个请求提供服务。
猜你喜欢
  • 1970-01-01
  • 2013-06-02
  • 2019-04-22
  • 2020-04-04
  • 2016-09-15
  • 2019-05-18
  • 2023-03-06
  • 2019-08-11
  • 1970-01-01
相关资源
最近更新 更多