Приложение Python, развернутое в Google-App-Engine, редко выдает исключение
Я развернул сайт на Google-App-Engine с Python.
Так как GAE не гарантирует "keep-alive", я реализовал сервер без сохранения состояния:
При каждом изменении внутренних переменных они немедленно сохраняются в базе данных GQL.
При каждом запуске процесса все внутренние переменные загружаются из базы данных GQL.
У меня есть сценарий, который редко выдает исключение, и я не смог отследить его:
Клиент отправляет синхронный запрос AJAX POST.
Сервер создает сеанс и отправляет уникальный идентификатор сеанса в ответе.
Клиент отправляет синхронный запрос 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()
Само исключение указывает на незаконный доступ к sessions
словарь с использованием sessionId
ключ. По моим наблюдениям, это происходит только тогда, когда последовательность клиент-сервер, описанная в начале этого вопроса, инициируется после того, как сервер "спит" в течение относительно длительного периода времени (например, несколько дней или около того). Это может дать какой-то ключ к пониманию источника этой проблемы, хотя я не могу ее увидеть.
Мои вопросы:
Что-то явно не так с моим дизайном?
Кто-нибудь сталкивался с подобной проблемой на GAE?
Кто-нибудь видит очевидное решение или даже метод отладки, который может помочь понять эту проблему?
Спасибо
1 ответ
Вы ошибочно полагаете, что все запросы обрабатываются одним и тем же экземпляром. Это совсем не так: GAE, как и большинство сред хостинга, не дает никаких гарантий относительно того, какой серверный процесс обработает любой запрос.
Ваша реализация использует переменную уровня модуля, myServer
, который является экземпляром класса со своими собственными переменными экземпляра. Но у каждого серверного процесса будет свой собственный экземпляр myServer, и они не будут разделены между процессами: поэтому запись словаря, созданная в одном запросе, не обязательно будет существовать в секунду.
Вам нужно будет найти способы сохранения этих данных между экземплярами. На самом деле, именно для этого и предназначен хранилище данных; если вы беспокоитесь о накладных расходах, вам следует изучить использование memcache.