Mehrinstanzenfähigkeit mit Namespaces implementieren

Mit der Namespaces API können Sie Ihre Anwendung mehrinstanzenfähig machen. Dazu wählen Sie einfach mit dem Paket namespace_manager in appengine_config.py für jeden Mandanten einen Namespace-String aus.

Aktuellen Namespace festlegen

Sie können Namespaces mit namespace_manager abrufen, festlegen und validieren. Mit dem Namespace-Manager können Sie einen aktuellen Namespace für Namespace-fähige APIs festlegen. Wenn Sie im Voraus einen aktuellen Namespace in appengine_config.py definieren, verwenden der Datenspeicher und Memcache diesen Namespace automatisch.

Die meisten App Engine-Entwickler verwenden ihre Google Workspace-Domain (ehemals G Suite) als aktuellen Namespace. Da Sie mit Google Workspace Ihre Anwendung in jeder beliebigen eigenen Domain bereitstellen können, können Sie mit diesem Mechanismus für verschiedene Domains jeweils unterschiedliche Namespaces konfigurieren. Dann können Sie diese verschiedenen Namespaces zum Aufteilen von Daten für alle Domains verwenden. Weitere Informationen finden Sie unter Benutzerdefinierte Domains zuordnen.

Im folgenden Codebeispiel wird gezeigt, wie Sie den aktuellen Namespace auf die oogle Workspace-Domain festlegen, die zum Zuordnen der URL verwendet wurde. Beachten Sie, dass dieser String bei allen URLs identisch ist, die über dieselbe Google Workspace-Domain zugeordnet wurden.

Zur Festlegung eines Namespace in Python verwenden Sie das App Engine-Konfigurationssystem appengine_config.py im Stammverzeichnis Ihrer Anwendung. Das folgende einfache Beispiel zeigt, wie Sie Ihre Google Workspace-Domain als aktuellen Namespace verwenden:

from google.appengine.api import namespace_manager

# Called only if the current namespace is not set.
def namespace_manager_default_namespace_for_request():
    # The returned string will be used as the Google Apps domain.
    return namespace_manager.google_apps_namespace()

Wenn Sie für namespace keinen Wert festlegen, wird der Namespace als leere Zeichenfolge festgelegt. Der namespace-String kann beliebig sein, ist aber auf maximal 100 alphanumerische Zeichen, Punkte, Unterstriche und Bindestriche beschränkt. Konkret müssen Namespace-Strings dem regulären Ausdruck [0-9A-Za-z._-]{0,100} entsprechen.

Per Konvention sind alle Namespaces, die mit "_" (Unterstrich) beginnen, für die Systemverwendung reserviert. Diese System-Namespace-Regel ist nicht zwingend. Wenn Sie sie jedoch nicht beachten, könnte dies unbestimmte negative Auswirkungen haben.

Allgemeine Informationen zum Konfigurieren von appengine_config.py finden Sie unter Python-Module konfigurieren.

Datenlecks verhindern

Zu den Risiken, die häufig mit mehrinstanzenfähigen Anwendungen assoziiert werden, gehört die Gefahr von Datenverlusten bei allen Namespaces. Nicht beabsichtigte Datenverluste können verschiedene Ursachen haben, einschließlich:

  • Die Verwendung von Namespaces mit App Engine-APIs, die noch keine Namespaces unterstützen. Zum Beispiel unterstützt Blobstore keine Namespaces. Wenn Sie Namespaces mit Blobstore verwenden, sollten Sie keine Blobstore-Abfragen für Endnutzeranfragen und keine Blobstore-Schlüssel aus nicht vertrauenswürdigen Quellen verwenden.
  • Verwendung eines externen Speichermediums anstelle von Memcache und des Datenspeichers über URL Fetch oder einen anderen Mechanismus, ohne ein Untergliederungsschema für Namespaces bereitzustellen.
  • Festlegen eines Namespace auf der Grundlage der E-Mail-Domain eines Nutzers. In den meisten Fällen sollen nicht alle E-Mail-Adressen einer Domain auf einen Namespace zugreifen können. Die Verwendung der E-Mail-Domain führt außerdem dazu, dass Ihre Anwendung einen Namespace nicht verwendet, bis der Nutzer angemeldet ist.

Namespaces bereitstellen

In den folgenden Abschnitten wird beschrieben, wie Namespaces mit anderen App Engine-Tools und APIs bereitgestellt werden.

Namespaces pro Nutzer erstellen

Bei einigen Anwendungen müssen Namespaces auf nutzerbezogener Basis erstellt werden. Wenn Sie Daten auf Nutzerebene für angemeldete Nutzer gliedern möchten, sollten Sie User.user_id() verwenden, wobei eine eindeutige permanente ID für den Nutzer zurückgegeben wird. Im folgenden Codebeispiel wird gezeigt, wie die Nutzer-API für diesen Zweck verwendet wird:

from google.appengine.api import users

def namespace_manager_default_namespace_for_request():
    # assumes the user is logged in.
    return users.get_current_user().user_id()

Normalerweise stellen Anwendungen, die Namespaces nutzerbezogen erstellen, auch spezifische Zielseiten für verschiedene Nutzer bereit. In diesen Fällen muss die Anwendung ein URL-Schema bereitstellen, das festlegt, welche Landingpage einem Nutzer angezeigt wird.

Namespaces mit dem Datenspeicher verwenden

Standardmäßig verwendet der Datenspeicher für Datenspeicheranfragen die aktuelle Namespace-Einstellung im Namespace-Manager. Die API wendet diesen aktuellen Namespace auf Key- oder Query-Objekte an, wenn sie erstellt werden. Sie sollten deshalb mit Bedacht vorgehen, wenn eine Anwendung Key- oder Query-Objekte in serialisierter Form speichert, da der Namespace in diesen Serialisierungen beibehalten wird.

Wenn Sie deserialisierte Key- und Query-Objekte verwenden, ist es wichtig, dass diese sich wie beabsichtigt verhalten. Die meisten einfachen Anwendungen, die den Datenspeicher (put/query/get) ohne andere Speichermechanismen verwenden, funktionieren erwartungsgemäß, wenn der aktuelle Namespace vor dem Aufrufen einer Datastore API festgelegt wird.

Query- und Key-Objekte verhalten sich in Bezug auf Namespaces grundsätzlich folgendermaßen:

  • Query- und Key-Objekte übernehmen bei ihrer Erstellung den aktuellen Namespace, es sei denn, Sie legen explizit einen anderen fest.
  • Wenn eine Anwendung einen neuen Key von einem Ancestor erstellt, übernimmt der neue Key den Namespace des Ancestors.

Das folgende Codebeispiel zeigt einen Beispiel-Request-Handler, mit dem die Anzahl für den globalen Namespace und einen beliebig benannten Namespace im Datenspeicher erhöht werden kann.

from google.appengine.api import namespace_manager
from google.appengine.ext import ndb
import webapp2

class Counter(ndb.Model):
    count = ndb.IntegerProperty()

@ndb.transactional
def update_counter(name):
    """Increment the named counter by 1."""
    counter = Counter.get_by_id(name)
    if counter is None:
        counter = Counter(id=name, count=0)

    counter.count += 1
    counter.put()

    return counter.count

class DatastoreCounterHandler(webapp2.RequestHandler):
    """Increments counters in the global namespace as well as in whichever
    namespace is specified by the request, which is arbitrarily named 'default'
    if not specified."""

    def get(self, namespace='default'):
        global_count = update_counter('counter')

        # Save the current namespace.
        previous_namespace = namespace_manager.get_namespace()
        try:
            namespace_manager.set_namespace(namespace)
            namespace_count = update_counter('counter')
        finally:
            # Restore the saved namespace.
            namespace_manager.set_namespace(previous_namespace)

        self.response.write('Global: {}, Namespace {}: {}'.format(
            global_count, namespace, namespace_count))

app = webapp2.WSGIApplication([
    (r'/datastore', DatastoreCounterHandler),
    (r'/datastore/(.*)', DatastoreCounterHandler)
], debug=True)

Namespaces mit Memcache verwenden

Standardmäßig verwendet Memcache für Memcache-Anfragen den aktuellen Namespace des Namespace-Managers. In den meisten Fällen ist es nicht notwendig, im Memcache explizit einen Namespace festzulegen. Dies kann außerdem zu unerwarteten Fehlern führen.

Es gibt jedoch einige eindeutige Instanzen, bei denen sich die explizite Festlegung eines Namespace im Memcache empfiehlt. Zum Beispiel verfügt Ihre Anwendung möglicherweise über allgemeine Daten, die für alle Namespaces freigegeben sind, z. B. eine Tabelle mit Ländercodes.

Im folgenden Codeausschnitt wird gezeigt, wie der Namespace im Memcache explizit festgelegt wird:

Mit dem Python-API für Memcache können Sie den aktuellen Namespace aus dem Namespace-Manager abrufen oder ihn explizit festlegen, wenn Sie den Memcache-Dienst erstellen.

Das folgende Codebeispiel zeigt einen Beispiel-Request-Handler, mit dem die Anzahl für den globalen Namespace und einen beliebig angegebenen Namespace im Memcache erhöht wird.

from google.appengine.api import memcache
from google.appengine.api import namespace_manager
import webapp2

class MemcacheCounterHandler(webapp2.RequestHandler):
    """Increments counters in the global namespace as well as in whichever
    namespace is specified by the request, which is arbitrarily named 'default'
    if not specified."""

    def get(self, namespace='default'):
        global_count = memcache.incr('counter', initial_value=0)

        # Save the current namespace.
        previous_namespace = namespace_manager.get_namespace()
        try:
            namespace_manager.set_namespace(namespace)
            namespace_count = memcache.incr('counter', initial_value=0)
        finally:
            # Restore the saved namespace.
            namespace_manager.set_namespace(previous_namespace)

        self.response.write('Global: {}, Namespace {}: {}'.format(
            global_count, namespace, namespace_count))

app = webapp2.WSGIApplication([
    (r'/memcache', MemcacheCounterHandler),
    (r'/memcache/(.*)', MemcacheCounterHandler)
], debug=True)

Im folgenden Beispiel wird der Namespace explizit festgelegt, wenn Sie einen Wert in Memcache speichern:

  // Store an entry to the memcache explicitly
memcache.add("key", data, namespace='abc')

Namespaces mit der Aufgabenwarteschlange verwenden

Push-Warteschlangen verwenden standardmäßig den aktuellen Namespace, der bei der Aufgabenerstellung im Namespace-Manager festgelegt war. In den meisten Fällen ist es nicht erforderlich, in der Aufgabenwarteschlange einen Namespace explizit festzulegen. Dies kann auch zu unerwarteten Fehlern führen.

Aufgabennamen werden für alle Namespaces freigegeben. Die Erstellung von zwei Aufgaben mit demselben Namen ist nicht möglich, auch wenn bei ihnen verschiedene Namespaces verwendet werden. Wenn Sie denselben Aufgabennamen für viele Namespaces verwenden möchten, können Sie jeden Namespace einfach dem Aufgabennamen anfügen.

Wenn eine neue Aufgabe die Methode add() der Aufgabenwarteschlange aufruft, kopiert die Aufgabenwarteschlange den aktuellen Namespace und gegebenenfalls die Google Workspace-Domain aus dem Namespace-Manager. Wenn die Aufgabe ausgeführt wird, werden der aktuelle Namespace und der Google Workspace-Namespace wiederhergestellt.

Wenn der aktuelle Namespace in der ursprünglichen Anforderung nicht festgelegt ist, d. h. wenn get_namespace() '' zurückgibt, können Sie set_namespace() verwenden, um für die Aufgabe den aktuellen Namensbereich festzulegen.

Es gibt einige eindeutige Instanzen, bei denen für Aufgaben, die für alle Namespaces funktionieren, ein Namespace explizit festgelegt werden sollte. Zum Beispiel könnten Sie eine Aufgabe erstellen, bei der für alle Namespaces Nutzungsstatistiken gesammelt werden. Sie könnten dann den Namespace der Aufgabe explizit festlegen. Im folgenden Codebeispiel wird gezeigt, wie mit der Aufgabenwarteschlange Namespaces explizit festgelegt werden.

from google.appengine.api import namespace_manager
from google.appengine.api import taskqueue
from google.appengine.ext import ndb
import webapp2

class Counter(ndb.Model):
    count = ndb.IntegerProperty()

@ndb.transactional
def update_counter(name):
    """Increment the named counter by 1."""
    counter = Counter.get_by_id(name)
    if counter is None:
        counter = Counter(id=name, count=0)

    counter.count += 1
    counter.put()

    return counter.count

def get_count(name):
    counter = Counter.get_by_id(name)
    if not counter:
        return 0
    return counter.count

class DeferredCounterHandler(webapp2.RequestHandler):
    def post(self):
        name = self.request.get('counter_name')
        update_counter(name)

class TaskQueueCounterHandler(webapp2.RequestHandler):
    """Queues two tasks to increment a counter in global namespace as well as
    the namespace is specified by the request, which is arbitrarily named
    'default' if not specified."""
    def get(self, namespace='default'):
        # Queue task to update global counter.
        current_global_count = get_count('counter')
        taskqueue.add(
            url='/tasks/counter',
            params={'counter_name': 'counter'})

        # Queue task to update counter in specified namespace.
        previous_namespace = namespace_manager.get_namespace()
        try:
            namespace_manager.set_namespace(namespace)
            current_namespace_count = get_count('counter')
            taskqueue.add(
                url='/tasks/counter',
                params={'counter_name': 'counter'})
        finally:
            namespace_manager.set_namespace(previous_namespace)

        self.response.write(
            'Counters will be updated asyncronously.'
            'Current values: Global: {}, Namespace {}: {}'.format(
                current_global_count, namespace, current_namespace_count))

app = webapp2.WSGIApplication([
    (r'/tasks/counter', DeferredCounterHandler),
    (r'/taskqueue', TaskQueueCounterHandler),
    (r'/taskqueue/(.*)', TaskQueueCounterHandler)
], debug=True)

Namespaces mit dem Blobstore verwenden

Der Blobstore ist nicht in Namespaces aufgeteilt. Damit in Blobstore ein Namespace beibehalten wird, ist es notwendig, über ein Namespace-fähiges Speichermedium auf Blobstore zuzugreifen. Hierfür stehen derzeit nur der Memcache, der Datenspeicher und die Aufgabenwarteschlange zur Verfügung. Wenn zum Beispiel der Key eines Blobs in einer Datenspeicherentität gespeichert ist, können Sie mit einem Key oder Query des Datenspeichers, der den Namespace berücksichtigt, darauf zugreifen.

Wenn die Anwendung über Schlüssel, die in einem Namespace-fähigen Speicher gespeichert sind, auf Blobstore zugreift, muss Blobstore selbst nicht nach Namespace aufgeteilt werden. In Anwendungen müssen Blob-Verluste zwischen Namespaces verhindert werden. Dazu muss auf Folgendes beachtet werden:

  • Verwenden Sie für Endnutzer-Anforderungen nicht BlobInfo.gql(). Sie können BlobInfo-Abfragen für administrative Anfragen verwenden, z. B. zum Generieren von Berichten über alle Anwendungs-Blobs. Die Verwendung für Endnutzeranfragen kann jedoch zu Datenlecks führen, da die BlobInfo-Einträge nicht nach Namespace aufgeteilt sind.
  • Verwenden Sie keine Blobstore-Schlüssel aus nicht vertrauenswürdigen Quellen.

Namespaces für Datastore-Abfragen festlegen

In der Google Cloud Console können Sie den Namespace für Datastore-Abfragen festlegen.

Wenn Sie die Standardeinstellung nicht verwenden möchten, wählen Sie den gewünschten Namespace aus dem Drop-down aus.

Namespaces mit dem Bulk Loader verwenden

BulkLoader unterstützt ein --namespace=NAMESPACE-Flag, mit dem Sie den zu verwendenden Namespace festlegen können. Jeder Namespace wird separat bearbeitet und wenn Sie auf alle Namespaces zugreifen möchten, müssen diese durchlaufen werden.

Wenn Sie eine neue Instanz von Index erstellen, wird sie standardmäßig dem aktuellen Namespace zugewiesen.

# set the current namespace
namespace_manager.set_namespace("aSpace")
index = search.Index(name="myIndex")
# index namespace is now fixed to "aSpace"

Sie können einen Namespace auch explizit im Konstruktor zuweisen:

index = search.Index(name="myIndex", namespace="aSpace")

Nachdem Sie eine Indexspezifikation erstellt haben, kann ihr Namespace nicht mehr geändert werden:

# change the current namespace
namespace_manager.set_namespace("anotherSpace")
# the namespaceof 'index' is still "aSpace" because it was bound at create time
index.search('hello')