Thao tác với Redis trong Python: Hướng dẫn toàn diện

Redis là một hệ thống lưu trữ key-value hiệu năng cao, hỗ trợ đa dạng cấu trúc dữ liệu như chuỗi (string), danh sách (list), tập hợp (set), tập hợp có thứ tự (sorted set/zset) và bảng băm (hash). Khác với các bộ nhớ đệm đơn giản như Memcached, Redis cung cấp các thao tác nguyên tử trên từng loại dữ liệu, kèm theo cơ chế bền vững (ghi lên đĩa), sao chép master-slave và mô hình xuất bản-đăng ký (pub/sub) đầy đủ.

Kết nối tới Redis

Thư viện redis-py cung cấp hai lớp chính: RedisStrictRedis. Lớp StrictRedis tuân thủ sát nhất giao diện lệnh chính thức của Redis và được khuyến nghị sử dụng trong hầu hết trường hợp.

Kết nối trực tiếp

import redis

client = redis.StrictRedis(
    host='localhost',
    port=6379,
    db=0,
    decode_responses=True  # Tự động giải mã bytes → str
)

client.set('user:1001', 'Nguyen Van A')
print(client.get('user:1001'))  # Kết quả: 'Nguyen Van A'

Sử dụng Connection Pool

Để tối ưu hiệu năng khi thực hiện nhiều truy vấn, nên dùng connection pool để tái sử dụng kết nối thay vì mở/đóng liên tục.

import redis

pool = redis.ConnectionPool(
    host='10.0.2.5',
    port=6379,
    max_connections=20,
    retry_on_timeout=True
)

client = redis.StrictRedis(connection_pool=pool)
client.set('config:timeout', '30s')

Thao tác với các kiểu dữ liệu

1. Chuỗi (String)

Lưu trữ giá trị dạng văn bản hoặc nhị phân dưới dạng cặp key-value đơn giản.

# Thiết lập giá trị với thời hạn sống (TTL)
client.setex('session:abc123', 1800, '{"uid": 42, "role": "admin"}')

# Thiết lập giá trị có TTL tính bằng mili-giây
client.psetex('temp:token', 5000, 'xyz789')

# Đặt giá trị chỉ khi key chưa tồn tại (atomic check-and-set)
client.set('counter', 0, nx=True)

# Tăng/giảm giá trị số học
client.incr('page_views')          # +1
client.incrby('sales_total', 150)  # +150
client.incrbyfloat('temperature', -0.5)  # +(-0.5)

# Ghép chuỗi
client.append('log:2024-04', '[INFO] Startup completed\n')

2. Bảng băm (Hash)

Tương đương với một dictionary lồng bên trong một key — phù hợp lưu đối tượng có nhiều thuộc tính.

# Lưu thông tin người dùng dưới dạng hash
client.hset('user:1002', mapping={
    'name': 'Tran Thi B',
    'email': 'b@example.com',
    'status': 'active',
    'score': '89.5'
})

# Truy xuất từng trường hoặc toàn bộ
print(client.hget('user:1002', 'email'))           # 'b@example.com'
print(client.hgetall('user:1002'))                # {'name': ..., 'email': ...}
print(client.hlen('user:1002'))                   # 4

# Tăng giá trị số trong hash
client.hincrbyfloat('user:1002', 'score', 2.5)    # Cập nhật score thành 92.0

3. Danh sách (List)

Cấu trúc hàng đợi (queue) hoặc ngăn xếp (stack) dựa trên thứ tự chèn.

# Thêm vào đầu danh sách (stack behavior)
client.lpush('task_queue', 'backup_db', 'send_report')

# Thêm vào cuối danh sách (queue behavior)
client.rpush('message_log', 'User logged in')

# Lấy và xóa phần tử đầu danh sách
first_task = client.lpop('task_queue')  # 'backup_db'

# Lấy phần tử cuối mà không xóa
last_msg = client.rpop('message_log')   # 'User logged in'

# Lấy dải phần tử (paging)
recent_tasks = client.lrange('task_queue', 0, 9)  # 10 phần tử gần nhất

4. Tập hợp (Set)

Tập hợp không thứ tự, không trùng lặp — lý tưởng cho các phép toán tập hợp.

# Gán danh sách người theo dõi
client.sadd('followers:post_77', 'user_101', 'user_205', 'user_309')

# Kiểm tra thành viên
is_following = client.sismember('followers:post_77', 'user_205')  # True

# Tính toán phần chung giữa hai nhóm
client.sadd('team_a', 'alice', 'bob', 'charlie')
client.sadd('team_b', 'bob', 'diana', 'charlie')
common_members = client.sinter('team_a', 'team_b')  # {'bob', 'charlie'}

# Lưu kết quả phép giao vào tập mới
client.sinterstore('team_ab_shared', 'team_a', 'team_b')

5. Tập hợp có thứ tự (Sorted Set / ZSet)

Mỗi phần tử gắn với một điểm số (score), dùng để sắp xếp và phân trang theo mức độ ưu tiên.

# Lưu bài viết theo lượt thích (score = số lượt)
client.zadd('posts:top_weekly', {
    'post:1001': 42,
    'post:1005': 18,
    'post:1022': 76
})

# Lấy top 3 bài viết phổ biến nhất
top_posts = client.zrevrange('posts:top_weekly', 0, 2, withscores=True)
# [('post:1022', 76.0), ('post:1001', 42.0), ('post:1005', 18.0)]

# Cập nhật điểm số tăng dần
client.zincrby('posts:top_weekly', 1, 'post:1001')  # post:1001 → 43

# Đếm số bài có lượt thích từ 20–50
count_in_range = client.zcount('posts:top_weekly', 20, 50)  # 2

Thực thi hàng loạt với Pipeline

Để giảm độ trễ mạng và đảm bảo tính nguyên tử, dùng pipeline để gửi nhiều lệnh trong một lần round-trip.

pipe = client.pipeline(transaction=True)
pipe.set('cache:report_v1', 'data_v1')
pipe.expire('cache:report_v1', 3600)
pipe.hset('stats:reports', 'v1_count', 1)
pipe.execute()  # Tất cả lệnh được thực thi đồng thời

Mô hình Xuất bản – Đăng ký (Pub/Sub)

Hỗ trợ giao tiếp bất đồng bộ giữa các tiến trình qua các kênh (channels).

import redis
import threading

def subscriber():
    pubsub = client.pubsub()
    pubsub.subscribe('notifications:alerts')
    
    for msg in pubsub.listen():
        if msg['type'] == 'message':
            print(f"🔔 Nhận cảnh báo: {msg['data']}")

# Chạy luồng lắng nghe riêng
threading.Thread(target=subscriber, daemon=True).start()

# Gửi thông báo từ tiến trình khác
client.publish('notifications:alerts', 'Hệ thống đang cập nhật phiên bản 2.4')

Các thao tác quản trị chung

# Kiểm tra sự tồn tại và loại dữ liệu
if client.exists('temp_data'):
    print(f"Loại dữ liệu: {client.type('temp_data')}")

# Xóa nhiều khóa cùng lúc
client.delete('cache:old_*', 'tmp:*')

# Đặt thời hạn sống cho khóa
client.expire('session:xyz', 900)  # 15 phút

# Di chuyển khóa sang database khác
client.move('config:prod', 1)  # Chuyển sang DB 1

# Liệt kê khóa theo mẫu
keys = client.keys('user:*')  # ['user:1001', 'user:1002']

Thẻ: Redis python redis-py sorted-set pub-sub

Đăng vào ngày 28 tháng 5 lúc 20:31