杰瑞科技汇

TornadoRedis如何高效连接与使用?

Of course! Here is a comprehensive guide to using tornadoredis, a popular Python library for integrating Redis with the Tornado web framework.

TornadoRedis如何高效连接与使用?-图1
(图片来源网络,侵删)

What is tornadoredis?

tornadoredis is a client library for the Redis key-value store, designed specifically to work with the asynchronous, non-blocking nature of the Tornado I/O loop.

Key Features:

  • Asynchronous: All Redis commands are non-blocking, meaning they don't block the entire Tornado application while waiting for a response from the Redis server. This is crucial for building high-performance, scalable web services.
  • Connection Pooling: It efficiently manages a pool of connections to Redis, avoiding the overhead of establishing a new connection for every request.
  • Pub/Sub Support: It has excellent, built-in support for Redis's Publish/Subscribe (Pub/Sub) messaging pattern, allowing you to build real-time features like chat applications or live notifications.
  • Tornado Integration: It provides a simple Future-based API that integrates seamlessly with Tornado's gen.coroutine and async/await features.

Installation

First, you need to install the library. It's available on PyPI.

pip install tornadoredis

You will also need a running Redis server.

TornadoRedis如何高效连接与使用?-图2
(图片来源网络,侵删)

Basic Usage: Connecting and Making Commands

The core of tornadoredis is the Client class. You create an instance of it and then use its methods to interact with Redis.

The methods return a Future. In Tornado, you typically yield these Futures inside a gen.coroutine decorated function.

Example 1: Setting and Getting a Key

Let's start with a simple "Hello, World!" example.

import tornado.ioloop
import tornado.web
import tornado.gen
import tornadoredis
# --- Redis Client Setup ---
# Create a client instance.
# tornadoredis can connect to a local Redis instance by default.
# For a remote server, provide the host and port, e.g., Client(host='redis.server.com', port=6379)
client = tornadoredis.Client()
# It's good practice to connect explicitly to catch any connection errors early.
try:
    client.connect()
    print("Successfully connected to Redis!")
except tornadoredis.ConnectionError as e:
    print(f"Could not connect to Redis: {e}")
    # In a real app, you might want to exit or enter a fallback mode.
# --- Tornado Application ---
class MainHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        # Use the yield keyword to wait for the Redis Future to complete
        # without blocking the I/O loop.
        # 1. SET a key
        set_future = client.set("my_key", "Hello from Tornado!")
        yield set_future
        self.write("Key 'my_key' was set.\n")
        # 2. GET the key
        get_future = client.get("my_key")
        value = yield get_future
        if value is not None:
            # Redis returns bytes, so we need to decode it to a string
            self.write(f"The value of 'my_key' is: {value.decode('utf-8')}\n")
        else:
            self.write("Could not find 'my_key'.\n")
        # 3. INCR a counter
        incr_future = client.incr("my_counter")
        counter_value = yield incr_future
        self.write(f"The counter 'my_counter' is now: {counter_value}\n")
def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])
if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    # Start the I/O loop
    print("Tornado server is running on http://localhost:8888")
    tornado.ioloop.IOLoop.current().start()

To run this:

  1. Save the code as app.py.
  2. Run your Redis server: redis-server
  3. Run the Tornado app: python app.py
  4. Open your browser and go to http://localhost:8888. Refresh the page a few times to see the counter increment.

Advanced Usage: Pipelines and Pub/Sub

Pipelines

For executing multiple commands in a single round-trip to the Redis server (which is much more efficient), you can use pipelines.

# Inside a RequestHandler...
@tornado.gen.coroutine
def post(self):
    # Create a pipeline
    pipe = client.pipeline()
    # Queue commands
    pipe.set("pipeline_key1", "value1")
    pipe.get("pipeline_key1")
    pipe.incr("pipeline_counter")
    pipe.get("pipeline_counter")
    # Execute all commands at once
    # The result is a list of the results of each command in order.
    results = yield pipe.execute()
    # results will be: [True, b'value1', 1, 2]
    self.write(f"Pipeline results: {results}")

Pub/Sub (Publish/Subscribe)

This is where tornadoredis truly shines. You can create a long-lived connection to listen for messages on specific channels.

Important: The Pub/Sub connection is a long-running operation that will block the I/O loop if not handled correctly. The standard pattern is to run the subscriber listener in a separate thread.

Example: A Simple Chat Application

This example has two parts: a publisher (the web handler) and a subscriber (a background thread).

import tornado.ioloop
import tornado.web
import tornado.gen
import tornadoredis
import threading
import time
import json
# --- Redis Setup ---
REDIS = tornadoredis.Client()
REDIS.connect()
# --- Subscriber Thread (Background) ---
def subscribe_to_channel():
    """
    Listens for messages on the 'chat' channel in a separate thread.
    This prevents blocking the main Tornado I/O loop.
    """
    print("Subscriber thread started, listening to 'chat' channel...")
    subscriber = tornadoredis.Client()
    subscriber.connect()
    # The subscribe method returns a Future. We need to run it in its own
    # IOLoop for the thread.
    ioloop = tornado.ioloop.IOLoop()
    def message_handler(message):
        if message.kind == 'message':
            print(f"Received message in thread: {message.body.decode('utf-8')}")
    subscriber.subscribe('chat', message_handler)
    ioloop.start()
# Start the subscriber thread when the app starts
sub_thread = threading.Thread(target=subscribe_to_channel, daemon=True)
sub_thread.start()
# --- Tornado Application ---
class ChatHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def post(self):
        """
        Publishes a message to the 'chat' channel.
        """
        message = self.get_argument("message")
        # Publish the message. It returns the number of subscribers that received it.
        future = REDIS.publish('chat', message)
        subscribers_count = yield future
        self.write(f"Message '{message}' published to {subscribers_count} subscriber(s).")
class MessageHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        """
        A simple page to send messages.
        """
        self.write("""
        <html>
          <body>
            <h1>Send a Chat Message</h1>
            <form action="/chat" method="post">
              <input type="text" name="message">
              <input type="submit" value="Send">
            </form>
          </body>
        </html>
        """)
def make_app():
    return tornado.web.Application([
        (r"/chat", ChatHandler),
        (r"/", MessageHandler),
    ])
if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    print("Tornado chat server is running on http://localhost:8888")
    print("Open the page in a browser and send a message.")
    print("Check the console to see the subscriber receiving the message.")
    tornado.ioloop.IOLoop.current().start()

To run this:

  1. Save as chat_app.py.
  2. Ensure Redis is running.
  3. Run the app: python chat_app.py
  4. Open http://localhost:8888 in your browser, type a message, and hit "Send".
  5. Look at your terminal where the server is running. You will see the message printed by the subscriber thread.

Best Practices and Alternatives

Best Practices

  1. Use a Connection Pool: Don't create a new Client for every request. Create a single Client instance (or a small pool) at the application level and reuse it. The Client class itself acts as a connection pool.
  2. Handle Connection Errors: Wrap your Redis calls in try...except blocks or check for None returns to gracefully handle connection issues or keys that don't exist.
  3. Use Pipelines for Bulk Operations: Whenever you need to send multiple commands, use a pipeline to reduce network latency.
  4. Manage Subscribers in Threads: As shown, Pub/Sub listeners should run in a separate thread to avoid blocking the main application.

Modern Alternatives

While tornadoredis is a solid and mature library, the Python async ecosystem has evolved. Here are some modern alternatives:

  1. aioredis:

    • Description: A very popular library that provides an async/await interface for Redis. It's not specific to Tornado and works with any asyncio-based framework (like FastAPI, Starlette, or modern Tornado versions).
    • Pros: Uses the modern asyncio standard library, excellent performance, active development.
    • Cons: Requires Python 3.7+.
  2. redis-py (with asyncio):

    • Description: The most widely used Redis client for Python. Recent versions (4.1.0+) have added native support for asyncio.
    • Pros: The "official" client, very stable, huge community.
    • Cons: The async support is newer than aioredis.

Example with aioredis (for context):

# Requires: pip install aioredis
import asyncio
import aioredis
async def main():
    redis = aioredis.from_url("redis://localhost")
    await redis.set("my_key", "Hello aioredis!")
    value = await redis.get("my_key")
    print(f"Value: {value.decode('utf-8')}")
    await redis.close()
# To run with Tornado, you'd use tornado.platform.asyncio.AsyncIOMainLoop
# and then run this coroutine.

Conclusion

tornadoredis is a powerful and well-established tool for integrating Redis into Tornado applications. Its strength lies in its simple, Future-based API and robust, built-in Pub/Sub support that is designed to work with Tornado's I/O loop model. For new projects, you might also consider aioredis for its modern async/await syntax, but tornadoredis remains an excellent and reliable choice, especially for existing Tornado codebases.

分享:
扫描分享到社交APP
上一篇
下一篇