Redis and MySQL Data Synchronization?

In modern high-concurrency system architectures, the combination of MySQL and Redis has become an industry standard. MySQL handles persistence, transactions, and complex queries, while Redis leverages its exceptional in-memory read and write performance to shoulder the burden of massive read requests.

However, introducing a Redis cache inevitably brings a classic architectural challenge: How do you maintain data consistency between MySQL and Redis?

When data is modified, failing to update the cache—or encountering a failure while updating it—after modifying the database results in users reading "stale data." This article systematically explores several mainstream strategies for synchronizing data between Redis and MySQL, helping you make informed architectural choices for your projects.

Before diving into synchronization strategies, we must establish a fundamental premise: In distributed systems, achieving strong consistency (real-time, absolute synchronization) incurs prohibitive costs and is often unattainable. Therefore, the vast majority of business scenarios target eventual consistency.

Currently, the industry primarily adopts the following approaches:

  1. Cache Aside Pattern: The application directly interacts with both MySQL and Redis.
  2. Read/Write Through Pattern: The application interacts solely with the caching layer, which in turn manages database synchronization.
  3. Asynchronous Binlog Listening (Data Subscription): Data changes are asynchronously synchronized to Redis by listening to MySQL binlogs.

The following sections closely examine the most widely used Cache Aside Pattern and the Asynchronous Binlog Synchronization Pattern.
This is the most classic and widely deployed synchronization pattern.

  • Read Flow:
    1. The application first attempts to read data from Redis.
    2. If a cache hit occurs, the data is returned immediately.
    3. On a cache miss, data is queried from MySQL, written into Redis, and then returned to the client.
  • Write Flow (During Updates):
    1. Update MySQL first, then delete the Redis cache. (Note: Typically, the cache is deleted rather than updated. Updating the cache directly is costly and prone to concurrency race conditions resulting in stale data).

Why "Delete the Cache" Instead of "Update the Cache"?

If you choose to update the cache directly, concurrent write threads might experience network delays, causing the writes to land in Redis out of order. This leaves Redis holding obsolete data. Deleting the cache, conversely, forces subsequent read operations to reload the latest state from the database, providing a safer alternative.

Are There Flaws in "Update Database, Then Delete Cache"?

Under extreme high-concurrency conditions, if a read request slips in precisely between the completion of the database update and the subsequent cache deletion, it might repopulate the cache with stale data, causing a brief inconsistency. However, this is a low-probability event and can be further mitigated using a delayed double-deletion strategy (Update DB -> Delete Cache -> Wait for a short duration -> Delete Cache again).

Production-Grade Solution: Canal-Based Binlog Asynchronous Synchronization

For scenarios requiring stricter consistency, or when you want to avoid polluting business code with cache synchronization logic, the Binlog Asynchronous Synchronization pattern is recommended.

The core principle involves treating Redis as a MySQL "slave node." By monitoring MySQL’s binary log (binlog) for data modifications, changes are captured and asynchronously propagated to Redis.

Core Architecture and Tools

  • MySQL Binlog: Records all DDL and DML statements executed against the database.
  • Alibaba Canal: An open-source incremental subscription and consumption component for MySQL binlogs. It masquerades as a MySQL slave, reads the binlog, and parses it into structured formats.
  • Message Queue (e.g., Kafka or RabbitMQ) (Optional): Used for traffic buffering and decoupling Canal from the downstream consumers.
  • Synchronization Consumer: Consumes the data change events pushed by Canal and executes commands to write the updates into Redis.

Simplified Implementation Steps

  1. Enable MySQL Binlog: Modify the my.cnf configuration file to enable the binlog and set its format to ROW.
  2. Configure and Start Canal Server: Connect Canal to MySQL and target specific databases and tables for monitoring.
  3. Implement the Consumer Program:
    • Subscribe to the Canal data stream.
    • Parse the operation type (INSERT, UPDATE, DELETE) alongside specific field values.
    • Execute corresponding SET or DEL commands on Redis keys.

Below is a simplified pseudo-code example of a consumer implementation:

// Pseudo-code: Listening to Canal messages and syncing to Redis
public void handleCanalData(CanalEntry.Entry entry) {
    if (entry.getEntryType() == CanalEntry.EntryType.ROWDATA) {
        CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
        CanalEntry.EventType eventType = rowChange.getEventType();
        
        for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
            if (eventType == CanalEntry.EventType.INSERT || eventType == CanalEntry.EventType.UPDATE) {
                // Retrieve updated data
                // Assuming we are syncing a user table with primary key 'id' and column 'name'
                String id = getColumnValue(rowData.getAfterColumnsList(), "id");
                String name = getColumnValue(rowData.getAfterColumnsList(), "name");
                
                // Write to Redis
                redisTemplate.opsForValue().set("user:" + id, name);
            } else if (eventType == CanalEntry.EventType.DELETE) {
                // Delete data
                String id = getColumnValue(rowData.getBeforeColumnsList(), "id");
                redisTemplate.delete("user:" + id);
            }
        }
    }
}

Summary and Selection Guide

When deciding on a synchronization strategy between Redis and MySQL for your projects, consider the following guidelines:

  • Small Projects / Simple Business Logic: Adopt the Cache Aside Pattern (Update MySQL, then delete Redis). This offers the lowest development cost and, when paired with appropriate cache Time-To-Live (TTL) policies, easily satisfies most requirements.
  • Medium-to-Large Projects / Core Business / Decoupling Needs: Opt for the Canal + Binlog Asynchronous Synchronization scheme. Although introducing external middleware increases architectural complexity, it completely decouples business code from cache synchronization logic, guarantees eventual data consistency, and minimizes performance impact on MySQL.