杰瑞科技汇

SAE Python如何使用Memcache?

Of course! Using Memcached with Python in a SAE (Sina App Engine) environment is a common and effective way to improve application performance by reducing database load.

SAE Python如何使用Memcache?-图1
(图片来源网络,侵删)

Here’s a comprehensive guide covering everything from the basics to a practical SAE example.


What is Memcached?

Memcached is a high-performance, distributed in-memory key-value store. It's used primarily for:

  • Caching Database Queries: Store the results of expensive database queries in memory.
  • Caching API Responses: Cache the results of calls to slow external APIs.
  • Storing Session Data: A common use case for web applications.
  • Reducing Computation: Cache the results of complex calculations.

Since data is stored in RAM, it's extremely fast, but it's volatile—all data is lost if the server restarts.


Why Use Memcached in SAE?

SAE provides a powerful, scalable, and easy-to-use Memcached service. Using it offers several key advantages:

SAE Python如何使用Memcache?-图2
(图片来源网络,侵删)
  • Performance: Drastically speeds up your application by serving data from memory instead of disk (database).
  • Scalability: SAE's Memcached service can scale automatically to handle your application's traffic.
  • Simplicity: SAE abstracts away the server management. You just need to connect to it.
  • Cost-Effective: It's often much cheaper than scaling your database.

The Python Client: pymemcache

The most popular and recommended Python client for Memcached is pymemcache. It's actively maintained, feature-rich, and supports the latest Memcached protocols.

Installation:

pip install pymemcache

How to Connect to SAE's Memcached Service

SAE provides connection details (host, port) for its Memcached service through environment variables. Your Python application needs to read these variables to establish a connection.

The primary environment variable you'll need is MEMCACHE_SERVERS.

SAE Python如何使用Memcache?-图3
(图片来源网络,侵删)

Connection Code Example:

Here’s a robust way to connect to SAE's Memcached using pymemcache.

import os
from pymemcache.client.base import Client
from pymemcache.exceptions import MemcacheError
# --- SAE Memcached Connection ---
# SAE provides the server address via environment variables.
# The format is typically "host1:port1,host2:port2,..."
MEMCACHE_SERVERS = os.environ.get('MEMCACHE_SERVERS')
if not MEMCACHE_SERVERS:
    # Fallback for local development if the env var is not set
    print("Warning: MEMCACHE_SERVERS environment variable not set. Using localhost.")
    MEMCACHE_SERVERS = '127.0.0.1:11211'
# Split the string into a list of (host, port) tuples
# e.g., "10.1.2.3:11211,10.1.2.4:11211" -> [('10.1.2.3', 11211), ('10.1.2.4', 11211)]
server_list = [tuple(server.split(':')) for server in MEMCACHE_SERVERS.split(',')]
try:
    # Connect to the Memcached cluster
    # The connect_timeout and timeout are important for production stability
    memcache_client = Client(
        server_list,
        connect_timeout=2,  # seconds
        timeout=2,          # seconds
        ignore_exc=True     # Set to True to handle errors gracefully instead of crashing
    )
    print("Successfully connected to Memcached.")
except MemcacheError as e:
    print(f"Error connecting to Memcached: {e}")
    # In a real app, you might want to fall back to a direct DB query
    memcache_client = None

Basic Operations with pymemcache

Once connected, performing operations is straightforward. pymemcache automatically handles serialization and deserialization of Python objects.

Operation Method Description
Set set(key, value, expire=0) Stores a key-value pair. expire is the time in seconds until the item is deleted.
Get get(key) Retrieves the value for a key. Returns None if the key doesn't exist.
Delete delete(key) Deletes a key-value pair. Returns True if the key was found and deleted.
Add add(key, value, expire=0) Stores a key-value pair only if the key does not already exist.
Replace replace(key, value, expire=0) Replaces a key-value pair only if the key already exists.
Increment/Decrement incr(key, delta=1) / decr(key, delta=1) Atomically increments or decrements a numeric value.

Example Usage:

def get_user_data(user_id):
    """
    Gets user data, trying to fetch from Memcached first,
    then falling back to a "database" if not found.
    """
    if not memcache_client:
        # Fallback to DB if client is not available
        print("Memcache client not available, hitting DB directly.")
        return get_user_from_db(user_id)
    # 1. Try to get from cache
    cached_data = memcache_client.get(f'user:{user_id}')
    if cached_data:
        print(f"Cache HIT for user {user_id}")
        return cached_data
    # 2. If not in cache, get from DB
    print(f"Cache MISS for user {user_id}")
    user_data = get_user_from_db(user_id)
    if user_data:
        # 3. Store the result in cache for future requests (expire after 1 hour = 3600 seconds)
        memcache_client.set(f'user:{user_id}', user_data, expire=3600)
        print(f"Cached data for user {user_id}")
    return user_data
# --- Mock DB function ---
def get_user_from_db(user_id):
    print(f"Fetching user {user_id} from the database...")
    # In a real app, this would be a database query
    db_data = {
        101: {'name': 'Alice', 'email': 'alice@example.com'},
        102: {'name': 'Bob', 'email': 'bob@example.com'}
    }
    return db_data.get(user_id)
# --- Let's test it ---
# First call will be a MISS
print("--- First Call ---")
user1 = get_user_data(101)
print(f"User Data: {user1}\n")
# Second call will be a HIT
print("--- Second Call ---")
user1_again = get_user_data(101)
print(f"User Data: {user1_again}\n")
# Call for a non-existent user
print("--- Call for non-existent user ---")
user999 = get_user_data(999)
print(f"User Data: {user999}")

Best Practices

  1. Use a Unique Namespace: Prefix your keys with a unique string (e.g., myapp:user:101, myapp:product:5001) to avoid key collisions if you share the Memcache cluster with other services or applications.
  2. Set Expiration Times: Always set an expiration time (expire). This prevents your cache from growing indefinitely and ensures stale data is eventually refreshed. A common pattern is to use a shorter expiration time for frequently changing data.
  3. Graceful Degradation: Wrap your cache logic in try...except blocks or use the ignore_exc=True option in the client. This way, if Memcached is down, your application can still function by directly hitting the database. Don't let a cache failure break your entire app.
  4. Cache Invalidation: When data in your database changes, you must invalidate the corresponding cache entry. A simple way is to delete it.
    # After updating a user in the database
    update_user_in_db(user_id, new_data)
    memcache_client.delete(f'user:{user_id}') # Invalidate the old cache
  5. Serialize Complex Objects: pymemcache can handle basic types (strings, bytes, numbers) directly. For complex objects like dictionaries or lists, it will serialize them to JSON. This is usually fine, but be aware of the performance overhead.

Full Example: A Simple SAE Web App with Flask and Memcached

Let's put it all together in a minimal Flask application that you could run on SAE.

app.py

import os
from flask import Flask, jsonify
from pymemcache.client.base import Client
from pymemcache.exceptions import MemcacheError
# --- App Initialization ---
app = Flask(__name__)
# --- Memcached Client Setup ---
MEMCACHE_SERVERS = os.environ.get('MEMCACHE_SERVERS', '127.0.0.1:11211')
server_list = [tuple(s.split(':')) for s in MEMCACHE_SERVERS.split(',')]
try:
    memcache_client = Client(
        server_list,
        connect_timeout=2,
        timeout=2,
        ignore_exc=True
    )
    print("Memcached client initialized.")
except MemcacheError as e:
    print(f"Failed to initialize Memcached client: {e}")
    memcache_client = None
# --- Mock Database ---
PRODUCTS_DB = {
    1: {"id": 1, "name": "Laptop", "price": 1200, "stock": 50},
    2: {"id": 2, "name": "Mouse", "price": 25, "stock": 200},
    3: {"id": 3, "name": "Keyboard", "price": 75, "stock": 150},
}
def get_product_from_db(product_id):
    """Simulates a slow database query."""
    print(f"DB HIT: Fetching product {product_id} from database.")
    return PRODUCTS_DB.get(product_id)
# --- Route ---
@app.route('/product/<int:product_id>')
def get_product(product_id):
    """
    API endpoint to get product details.
    Uses Memcached to cache results.
    """
    if not memcache_client:
        # Fallback if Memcache is unavailable
        print("Warning: Memcache is down. Using direct DB access.")
        product = get_product_from_db(product_id)
        if product:
            return jsonify(product)
        return jsonify({"error": "Product not found"}), 404
    # 1. Try to get from cache
    cache_key = f"product:{product_id}"
    cached_product = memcache_client.get(cache_key)
    if cached_product:
        print(f"Cache HIT for product {product_id}")
        return jsonify(cached_product)
    # 2. Cache miss, fetch from DB
    print(f"Cache MISS for product {product_id}")
    product = get_product_from_db(product_id)
    if product:
        # 3. Cache the result for 10 minutes
        memcache_client.set(cache_key, product, expire=600)
        print(f"Cached product {product_id}")
        return jsonify(product)
    else:
        return jsonify({"error": "Product not found"}), 404
@app.route('/product/<int:product_id>/stock', methods=['POST'])
def update_stock(product_id):
    """
    Simulates updating product stock and invalidating the cache.
    """
    # In a real app, you'd get the new stock from a request body
    new_stock = 10 # Example: setting stock to 10
    if product_id in PRODUCTS_DB:
        # 1. Update the "database"
        PRODUCTS_DB[product_id]['stock'] = new_stock
        print(f"DB UPDATED: Stock for product {product_id} is now {new_stock}.")
        # 2. Invalidate the cache for this product
        cache_key = f"product:{product_id}"
        deleted = memcache_client.delete(cache_key)
        if deleted:
            print(f"Cache INVALIDATED for product {product_id}.")
        else:
            print(f"Cache key for product {product_id} was not found during invalidation.")
        return jsonify({"message": f"Stock for product {product_id} updated and cache cleared."})
    else:
        return jsonify({"error": "Product not found"}), 404
# --- Run the app ---
if __name__ == '__main__':
    # For local development
    app.run(debug=True, port=5000)

How to Run This Locally:

  1. Install dependencies:
    pip install flask pymemcache
  2. Start a local Memcached server:
    • macOS (with Homebrew): brew install memcached and then memcached -d
    • Linux (Ubuntu/Debian): sudo apt-get install memcached and then sudo systemctl start memcached
    • Windows: Download and run Memcached from the official site.
  3. Run the Flask app:
    python app.py
  4. Test in your browser or with curl:
    • First request: http://127.0.0.1:5000/product/1 -> You'll see "Cache MISS" and "DB HIT".
    • Second request: http://127.0.0.1:5000/product/1 -> You'll see "Cache HIT".
    • Update stock: curl -X POST http://127.0.0.1:5000/product/1/stock -> You'll see the DB update and cache invalidation.
    • Third request: http://127.0.0.1:5000/product/1 -> "Cache MISS" again, as the old cache was cleared.
分享:
扫描分享到社交APP
上一篇
下一篇