Exemplos do Memcache

Nesta página, fornecemos exemplos de código do Python para usar o Memcache. Memcache é um sistema de armazenamento em cache de objetos de memória distribuída de alto desempenho que fornece acesso rápido aos dados armazenados em cache. Para saber mais sobre o Memcache, leia a Visão geral do Memcache.

O padrão do Memcache

O Memcache costuma ser usado com o seguinte padrão:

  • O aplicativo recebe uma consulta do usuário ou do aplicativo.
  • O aplicativo verifica se os dados que precisam atender a essa consulta estão no Memcache.
    • Se os dados estiverem no Memcache, o aplicativo os usará.
    • Se os dados não estiverem no Memcache, o aplicativo consultará o Datastore e armazenará os resultados no Memcache para solicitações futuras.

O pseudocódigo abaixo representa uma solicitação de Memcache típica:

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 o Memcache internamente para agilizar consultas. No entanto, se quiser, você também poderá adicionar explicitamente as chamadas do Memcache para mais controle sobre as agilizações.

Como armazenar dados em cache

O exemplo a seguir demonstra várias maneiras de definir valores no Memcache usando a 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")

Para saber mais sobre os métodos add(), set_multi() e set(), consulte a documentação do Memcache da API Python.

Como modificar guestbook.py para usar o Memcache

O aplicativo de livro de visitas consulta o Datastore em todas as solicitações (via ndb, fazendo com que ele já ganhe algumas das velocidades do Memcache). Você pode modificar o aplicativo de livro de visitas para usar o Memcache explicitamente antes de recorrer à consulta do Datastore.

Primeiramente, importaremos o módulo do Memcache e criaremos o método que verifica o Memcache antes de executar uma consulta.

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

Em seguida, separaremos a consulta e a criação do HTML da página. Quando não atingirmos o cache, chamaremos esse método para consultar o Datastore e criar a string HTML que armazenaremos no 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()

Por fim, atualizaremos o manipulador MainPage para chamar o método get_greetings() e exibiremos algumas estatísticas sobre o número de vezes que o cache teve ou não ocorrências.


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)