2015-02-24 3 views
2

У меня есть CherryPy Webapp, который я изначально написал, используя сеансы на основе файлов. Время от времени я храню потенциально большие объекты в сеансе, такие как результаты запуска отчета. Я предлагаю возможность загружать результаты отчета в различные форматы, и я не хочу повторно запускать запрос, когда пользователь выбирает загрузку из-за возможности получения разных данных. При использовании сеансов на основе файлов это работало нормально.CherryPy Сессии и крупные объекты?

Теперь я рассматриваю возможность подключения второго сервера в сети, и поэтому мне нужно иметь возможность обмениваться данными сеанса между серверами, для которых, как представляется, наиболее подходящим является использование типа хранения memchached , Я кратко рассмотрел использование типа хранилища PostgreSQL, но этот вариант был ОЧЕНЬ плохо документирован и из того, что я мог найти, вполне может быть нарушен. Поэтому я применил опцию memcached.

Теперь я столкнулся с проблемой, когда, когда я пытаюсь сохранить определенные объекты в сеансе, я получаю «AssertionError: Session data для id xxx not set». Я предполагаю, что это связано с размером объекта, превышающим некоторый произвольный предел, установленный на сервере сеанса CherryPy или memcached, но я действительно не знаю, поскольку исключение не говорит мне, ПОЧЕМУ он не был установлен. Я увеличил ограничение размера объекта в memcached до максимума 128 МБ, чтобы узнать, помогло ли это, но это не так - и, вероятно, это не безопасный вариант.

Так что же мое решение здесь? Есть ли способ использовать хранилище сеансов memcached для хранения произвольно больших объектов? Нужно ли мне «сворачивать свое собственное» основанное на БД или подобное решение для этих объектов? Является ли проблема потенциально НЕ основанной на размере? Или есть еще один вариант, который мне не хватает?

ответ

2

Я использую mysql для обработки своих сеансов вишни. Пока объект сериализуется (может быть маринован), вы можете сохранить его как blob (двоичный большой объект) в mysql. Вот код, который вы хотели бы использовать для хранения MySQL сессии ...

https://bitbucket-assetroot.s3.amazonaws.com/Lawouach/cherrypy/20111008/936/mysqlsession.py?Signature=gDmkOlAduvIZS4WHM2OVgh1WVuU%3D&Expires=1424822438&AWSAccessKeyId=0EMWEFSGA12Z1HF1TZ82

""" 
MySQLdb session module for CherryPy by Ken Kinder <http://kenkinder.com/> 

Version 0.3, Released June 24, 2000. 

Copyright (c) 2008-2009, Ken Kinder 
All rights reserved. 

Redistribution and use in source and binary forms, with or without 
modification, are permitted provided that the following conditions are met: 

    * Redistributions of source code must retain the above copyright notice, 
    this list of conditions and the following disclaimer. 

    * Redistributions in binary form must reproduce the above copyright 
    notice, this list of conditions and the following disclaimer in the 
    documentation and/or other materials provided with the distribution. 

    * Neither the name of the Ken Kinder nor the names of its contributors 
    may be used to endorse or promote products derived from this software 
    without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
""" 
import MySQLdb 
import cPickle as pickle 
import cherrypy 
import logging 
import threading 

__version__ = '0.2' 

logger = logging.getLogger('Session') 

class MySQLSession(cherrypy.lib.sessions.Session): 
    ## 
    ## These can be over-ridden by config file 
    table_name = 'web_session' 
    connect_arguments = {} 

    SCHEMA = """create table if not exists %s (
      id varchar(40), 
      data text, 
      expiration_time timestamp 
     ) ENGINE=InnoDB;""" 

    _database = None 

    def __init__(self, id=None, **kwargs): 
     logger.debug('Initializing MySQLSession with %r' % kwargs) 
     for k, v in kwargs.items(): 
      setattr(MySQLSession, k, v) 

     self.db = self.get_db() 
     self.cursor = self.db.cursor() 

     super(MySQLSession, self).__init__(id, **kwargs) 

    @classmethod 
    def get_db(cls): 
     ## 
     ## Use thread-local connections 
     local = threading.local() 
     if hasattr(local, 'db'): 
      return local.db 
     else: 
      logger.debug("Connecting to %r" % cls.connect_arguments) 
      db = MySQLdb.connect(**cls.connect_arguments) 
      cursor = db.cursor() 
      cursor.execute(cls.SCHEMA % cls.table_name) 
      db.commit() 
      local.db = db 

      return db 

    def _load(self): 
     logger.debug('_load %r' % self) 
     # Select session data from table 
     self.cursor.execute('select data, expiration_time from %s ' 
          'where id = %%s' % MySQLSession.table_name, (self.id,)) 
     row = self.cursor.fetchone() 
     if row: 
      (pickled_data, expiration_time) = row 
      data = pickle.loads(pickled_data) 

      return data, expiration_time 
     else: 
      return None 

    def _save(self, expiration_time): 
     logger.debug('_save %r' % self) 
     pickled_data = pickle.dumps(self._data) 

     self.cursor.execute('select count(*) from %s where id = %%s and expiration_time > now()' % MySQLSession.table_name, (self.id,)) 
     (count,) = self.cursor.fetchone() 
     if count: 
      self.cursor.execute('update %s set data = %%s, ' 
           'expiration_time = %%s where id = %%s' % MySQLSession.table_name, 
           (pickled_data, expiration_time, self.id)) 
     else: 
      self.cursor.execute('insert into %s (data, expiration_time, id) values (%%s, %%s, %%s)' % MySQLSession.table_name, 
           (pickled_data, expiration_time, self.id)) 
     self.db.commit() 

    def acquire_lock(self): 
     logger.debug('acquire_lock %r' % self) 
     self.locked = True 
     self.cursor.execute('select id from %s where id = %%s for update' % MySQLSession.table_name, 
          (self.id,)) 
     self.db.commit() 

    def release_lock(self): 
     logger.debug('release_lock %r' % self) 
     self.locked = False 
     self.db.commit() 

    def clean_up(self): 
     logger.debug('clean_up %r' % self) 
     self.cursor.execute('delete from %s where expiration_time < now()' % MySQLSession.table_name) 
     self.db.commit() 

    def _delete(self): 
     logger.debug('_delete %r' % self) 
     self.cursor.execute('delete from %s where id=%%s' % MySQLSession.table_name, (self.id,)) 
     self.db.commit() 

    def _exists(self): 
     # Select session data from table 
     self.cursor.execute('select count(*) from %s ' 
          'where id = %%s and expiration_time > now()' % MySQLSession.table_name, (self.id,)) 
     (count,) = self.cursor.fetchone() 
     logger.debug('_exists %r (%r)' % (self, bool(count))) 
     return bool(count) 

    def __del__(self): 
     logger.debug('__del__ %r' % self) 
     self.db.commit() 
     self.db.close() 
     self.db = None 

    def __repr__(self): 
     return '<MySQLSession %r>' % (self.id,) 

cherrypy.lib.sessions.MysqlSession = MySQLSession 

тогда ваш webapp.py будет выглядеть примерно так ...

from mysqlsession import MySQLSession 
import cherrypy 
import logging 

logging.basicConfig(level=logging.DEBUG) 

sessionInfo = { 
    'tools.sessions.on': True, 
    'tools.sessions.storage_type': "Mysql", 
    'tools.sessions.connect_arguments': {'db': 'sessions'}, 
    'tools.sessions.table_name': 'session' 
} 

cherrypy.config.update(sessionInfo) 

class HelloWorld: 
    def index(self): 
     v = cherrypy.session.get('v', 1) 
     cherrypy.session['v'] = v+1 
     return "Hello world! %s" % v 

    index.exposed = True 

cherrypy.quickstart(HelloWorld()) 

Если вам нужно чтобы поставить какой-то объект там сделать что-то вроде этого ...

import pickle 

    pickledThing = pickle.dumps(YourObject.GetItems(), protocol=0, fix_imports=False) 

Надежда thi s помогает!

+0

Ваша ссылка на код не работает, но из того, что я вижу здесь, это подойдет моим целям превосходно, с небольшими изменениями, чтобы заставить его работать с PostgreSQL, а не с MySQL. Благодаря! – ibrewster

1

Похоже, что вы хотите сохранить ссылку на объект, хранящийся в Memcache, а затем вытащить его обратно, когда вам это нужно, вместо того, чтобы полагаться на состояние для обработки загрузки/сохранения.

+0

Хорошо, это похоже на хорошее начало, но оставляет вопросы: а) как/где я храню объект, чтобы либо сервер мог получить к нему доступ (возможно,?), И б) как я могу избавиться от объекта, когда сессия истекает? – ibrewster

1

Из того, что вы объяснили, я могу заключить, что концептуально не рекомендуется смешивать сеансы пользователя и кеш. В каких сессиях в основном предназначены для хранения состояния пользователя. Таким образом, он имеет меры безопасности, блокировку, чтобы избежать одновременных изменений и других аспектов. Также хранение сеансов обычно нестабильно. Таким образом, если вы хотите использовать сеансы как кеш, вы должны понимать, как сеансы действительно работают и какие последствия.

Что я предлагаю вам сделать, чтобы установить нормальное кэширование вашей модели домена, которое создает данные отчета и сохраняет сеанс для идентификации.

CherryPy детали

По умолчанию реализация сеанса CherryPy блокирует данные сеанса.В случае OLAP ваш пользователь, вероятно, не сможет выполнять параллельные запросы (например, открыть другую вкладку), пока отчет не будет завершен. Однако есть возможность ручного управления блокировкой.

Хранилище сеансов PostgreSQL не работает и может быть удалено в следующих выпусках.

Хранилище сеансов Memcached не реализует распределенную блокировку, поэтому убедитесь, что вы используете согласованное правило, чтобы сбалансировать пользователя на своих серверах.

+0

Вопрос об использовании общего «кеша», а не данных сеанса, заключается в том, что результаты отчета являются: a) специфичными для пользователя и b) временными. Использование объекта session дает мне обе эти функции автоматически. Используя что-то еще, я должен сам их реализовать - это хорошо, если это лучший способ сделать что-то, но много дополнительной работы, если нет. – ibrewster

+0

@ibrewster Для вас требования a) добавить userId к ключу b) установить TTL для записи в кэш. Можно использовать хранилище сеансов для случая, пока вы понимаете его семантику. Да, он приходит автоматически, но он не приходит бесплатно. Я представил подробности. Btw, с большим количеством кэширования кэшей Python, так же просто, как применение декоратора к вашему методу. – saaj

Смежные вопросы