Esempi di Memcache

Questa pagina fornisce esempi di codice Python per l'uso di memcache. Memcache è un sistema distribuito ad alte prestazioni di memorizzazione nella cache di oggetti in memoria. Per saperne di più su memcache, leggi la panoramica di Memcache.

Pattern memcache

memcache viene in genere utilizzata con il seguente pattern:

  • L'applicazione riceve una query dall'utente o dall'applicazione.
  • L'applicazione verifica se i dati necessari per soddisfare la query si trovano in memcache.
    • Se i dati si trovano in memcache, l'applicazione li utilizza.
    • Se i dati non sono in memcache, l'applicazione esegue una query in Datastore e archivia i risultati in memcache per le richieste future.

Lo pseudocodice riportato di seguito rappresenta una tipica richiesta memcache:

def get_data():
    data = memcache.get('key')
    if data is not None:
        return data
    else:
        data = query_for_data()
        memcache.add('key', data, 60)
    return data

ndb usa internamente memcache per velocizzare le query. Tuttavia, se vuoi, puoi anche aggiungere esplicitamente chiamate memcache per avere un maggiore controllo sull'accelerazione.

Memorizzazione nella cache dei dati

L'esempio seguente illustra diversi modi per impostare valori in memcache utilizzando l'API Python.

# Add a value if it doesn't exist in the cache
# with a cache expiration of 1 hour.
memcache.add(key="weather_USA_98105", value="raining", time=3600)

# Set several values, overwriting any existing values for these keys.
memcache.set_multi(
    {"USA_98115": "cloudy", "USA_94105": "foggy", "USA_94043": "sunny"},
    key_prefix="weather_",
    time=3600
)

# Atomically increment an integer value.
memcache.set(key="counter", value=0)
memcache.incr("counter")
memcache.incr("counter")
memcache.incr("counter")

Per saperne di più sui metodi add(), set_multi() e set(), consulta la documentazione relativa all'API Python Memcache.

Modifica di guestbook.py per l'utilizzo di memcache

L'applicazione guestbook esegue una query su Datastore su ogni richiesta (tramite ndb, quindi ottiene già alcune velocità memcache). Puoi modificare l'applicazione Guestbook in modo che utilizzi memcache in modo esplicito prima di ricorrere all'esecuzione di query su Datastore.

Innanzitutto, importeremo il modulo memcache e creeremo il metodo che controlla memcache prima di eseguire una query.

def get_greetings(self, guestbook_name):
    """
    get_greetings()
    Checks the cache to see if there are cached greetings.
    If not, call render_greetings and set the cache

    Args:
      guestbook_name: Guestbook entity group key (string).

    Returns:
      A string of HTML containing greetings.
    """
    greetings = memcache.get('{}:greetings'.format(guestbook_name))
    if greetings is None:
        greetings = self.render_greetings(guestbook_name)
        try:
            added = memcache.add(
                '{}:greetings'.format(guestbook_name), greetings, 10)
            if not added:
                logging.error('Memcache set failed.')
        except ValueError:
            logging.error('Memcache set failed - data larger than 1MB')
    return greetings

Quindi, separeremo le query e la creazione del codice HTML per la pagina. Se non raggiungiamo la cache, chiameremo questo metodo per eseguire una query in Datastore e creare la stringa HTML che archivieremo in memcache.

def render_greetings(self, guestbook_name):
    """
    render_greetings()
    Queries the database for greetings, iterate through the
    results and create the HTML.

    Args:
      guestbook_name: Guestbook entity group key (string).

    Returns:
      A string of HTML containing greetings
    """
    greetings = ndb.gql('SELECT * '
                        'FROM Greeting '
                        'WHERE ANCESTOR IS :1 '
                        'ORDER BY date DESC LIMIT 10',
                        guestbook_key(guestbook_name))
    output = cStringIO.StringIO()
    for greeting in greetings:
        if greeting.author:
            output.write('<b>{}</b> wrote:'.format(greeting.author))
        else:
            output.write('An anonymous person wrote:')
        output.write('<blockquote>{}</blockquote>'.format(
            cgi.escape(greeting.content)))
    return output.getvalue()

Infine aggiorneremo il gestore MainPage per chiamare il metodo get_greetings() e mostrare alcune statistiche sul numero di volte che la cache è stata rilevata o persa.


import cgi
import cStringIO
import logging
import urllib

from google.appengine.api import memcache
from google.appengine.api import users
from google.appengine.ext import ndb

import webapp2

class Greeting(ndb.Model):
    """Models an individual Guestbook entry with author, content, and date."""
    author = ndb.StringProperty()
    content = ndb.StringProperty()
    date = ndb.DateTimeProperty(auto_now_add=True)

def guestbook_key(guestbook_name=None):
    """Constructs a Datastore key for a Guestbook entity with guestbook_name"""
    return ndb.Key('Guestbook', guestbook_name or 'default_guestbook')

class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.out.write('<html><body>')
        guestbook_name = self.request.get('guestbook_name')

        greetings = self.get_greetings(guestbook_name)
        stats = memcache.get_stats()

        self.response.write('<b>Cache Hits:{}</b><br>'.format(stats['hits']))
        self.response.write('<b>Cache Misses:{}</b><br><br>'.format(
                            stats['misses']))
        self.response.write(greetings)

        self.response.write("""
          <form action="/sign?{}" method="post">
            <div><textarea name="content" rows="3" cols="60"></textarea></div>
            <div><input type="submit" value="Sign Guestbook"></div>
          </form>
          <hr>
          <form>Guestbook name: <input value="{}" name="guestbook_name">
          <input type="submit" value="switch"></form>
        </body>
      </html>""".format(urllib.urlencode({'guestbook_name': guestbook_name}),
                        cgi.escape(guestbook_name)))

    def get_greetings(self, guestbook_name):
        """
        get_greetings()
        Checks the cache to see if there are cached greetings.
        If not, call render_greetings and set the cache

        Args:
          guestbook_name: Guestbook entity group key (string).

        Returns:
          A string of HTML containing greetings.
        """
        greetings = memcache.get('{}:greetings'.format(guestbook_name))
        if greetings is None:
            greetings = self.render_greetings(guestbook_name)
            try:
                added = memcache.add(
                    '{}:greetings'.format(guestbook_name), greetings, 10)
                if not added:
                    logging.error('Memcache set failed.')
            except ValueError:
                logging.error('Memcache set failed - data larger than 1MB')
        return greetings

    def render_greetings(self, guestbook_name):
        """
        render_greetings()
        Queries the database for greetings, iterate through the
        results and create the HTML.

        Args:
          guestbook_name: Guestbook entity group key (string).

        Returns:
          A string of HTML containing greetings
        """
        greetings = ndb.gql('SELECT * '
                            'FROM Greeting '
                            'WHERE ANCESTOR IS :1 '
                            'ORDER BY date DESC LIMIT 10',
                            guestbook_key(guestbook_name))
        output = cStringIO.StringIO()
        for greeting in greetings:
            if greeting.author:
                output.write('<b>{}</b> wrote:'.format(greeting.author))
            else:
                output.write('An anonymous person wrote:')
            output.write('<blockquote>{}</blockquote>'.format(
                cgi.escape(greeting.content)))
        return output.getvalue()

class Guestbook(webapp2.RequestHandler):
    def post(self):
        # We set the same parent key on the 'Greeting' to ensure each greeting
        # is in the same entity group. Queries across the single entity group
        # are strongly consistent. However, the write rate to a single entity
        # group is limited to ~1/second.
        guestbook_name = self.request.get('guestbook_name')
        greeting = Greeting(parent=guestbook_key(guestbook_name))

        if users.get_current_user():
            greeting.author = users.get_current_user().nickname()

        greeting.content = self.request.get('content')
        greeting.put()
        memcache.delete('{}:greetings'.format(guestbook_name))
        self.redirect('/?' +
                      urllib.urlencode({'guestbook_name': guestbook_name}))

app = webapp2.WSGIApplication([('/', MainPage),
                               ('/sign', Guestbook)],
                              debug=True)