Databases 14 min read

Mastering Bidirectional Redis Sync: Strategies, Challenges, and Redissyncer in Action

This article explores the RedisSyncer toolkit for achieving reliable bidirectional Redis synchronization, explains the necessity of cache sync, details the data offset solution and double‑read approach, and provides step‑by‑step deployment instructions with code examples and practical considerations.

JD Cloud Developers
JD Cloud Developers
JD Cloud Developers
Mastering Bidirectional Redis Sync: Strategies, Challenges, and Redissyncer in Action

RedisSyncer Overview

RedisSyncer is a JD Cloud‑developed open‑source middleware suite for Redis multi‑task synchronization, supporting single‑instance and cluster sync. It includes four components: redissyncer‑server (sync service engine), redissyncer‑cli (client), redissyncer‑compare (data verification tool), and a Docker‑Compose integrated deployment package.

Source code is available at https://github.com/TraceNature/redissyncer-server .

Why Bidirectional Sync?

Ensures dynamic data consistency between two instances when both have existing data and write traffic.

Makes cache data globally readable, preventing cache stampede.

Improves cache hit rate, reducing database load.

Provides full data visibility when a single data center fails.

Challenges of Native Redis Sync

Cannot differentiate the source of cached data.

Lack of instance identifiers leads to data loops during bidirectional sync.

Ring buffer overflow causes data confusion and cleanup difficulty.

Data Offset (Data Reconciliation) Solution

The offset method uses two “offset pools” (set1 and set2) to record keys and sync counts, breaking the write‑loop. The process involves full‑sync, incremental sync with offset‑based discard, and careful ordering of writes to avoid circular replication.

Start full‑sync from redis1 to redis2.

Start incremental sync from redis1 to redis2, discarding keys present in set1.

Start full‑sync from redis2 to redis1, then write to set1 before redis1 to allow offset‑based discard on the return path.

Start incremental sync from redis2 to redis1, using set2 for discard and set1 to avoid looped copies.

Modify the incremental task on redis1→redis2 to discard via set1 first, then write to set2 before syncing.

Double‑Write Pitfalls

Simultaneous writes to the same key in a concurrent environment cannot guarantee order, leading to inconsistent results. Network interruptions cause data loss, and strong consistency makes the system unavailable if a single data center goes down, adding latency and reducing cache effectiveness.

Double‑Read Solution

Each data center deploys a read‑write Redis instance and a read‑only instance. Redissyncer synchronizes write‑side data to the remote read‑only instance. Applications first query the read‑only instance; if the key is missing, they fall back to the read‑write instance, ensuring high availability with minimal operational overhead.

Keys must be globally unique across centers.

Avoid non‑idempotent commands such as INCR or LPUSH.

Network jitter may still cause occasional inconsistencies despite offset handling.

Implementation Details

Deploy Redis instances as usual. Then deploy Redissyncer using Docker:

git clone https://github.com/TraceNature/redissyncer.git
cd redissyncer
docker-compose up -d

Install the client:

wget https://github.com/TraceNature/redissyncer-cli/releases/download/v0.1.0/redissyncer-cli-0.1.0-linux-amd64.tar.gz
 tar zxvf redissyncer-cli-0.1.0-linux-amd64.tar.gz

Configure .config.yaml with server address and token:

# redissyncer‑server address and port
syncserver: http://127.0.0.1:8080
# token obtained via redissyncer‑cli login (default admin/123456)
token: 379F5E2BD55A4608B6A7557F0583CFC5

Create synchronization tasks (example for az_a1 → az_b2):

{
  "sourcePassword": "redistest0102",
  "sourceRedisAddress": "10.0.0.110:16375",
  "targetRedisAddress": "10.0.0.113:16375",
  "targetPassword": "redistest0102",
  "taskName": "a1_to_b2",
  "targetRedisVersion": 5.0,
  "autostart": true,
  "afresh": true,
  "batchSize": 100
}

Start the task with the client:

redissyncer-cli-0.1.0-linux-amd64 -i
redissyncer-cli> task create source synctask/a1_to_b2.json

Repeat for the opposite direction (az_b1 → az_a2) using a similar JSON file.

To simulate double‑read, clone and build redisdual , then run it to observe read‑only vs. read‑write behavior.

Configuration Example (config.yaml)

# Logging configuration (no changes needed)
zap:
  level: 'debug'
  format: 'console'
  prefix: '[redisdual]'
  director: 'log'
  link-name: 'latest_log'
  show-line: true
  encode-level: 'LowercaseColorLevelEncoder'
  log-in-console: true

execinterval: 1   # execution interval in ms
loopstep: 30       # max loop steps before reset
localkeyprefix: a
remotekeyprefix: b

redisrw:
  db: 0
  addr: '114.67.76.82:16375'
  password: 'redistest0102'

redisro:
  db: 0
  addr: '114.67.120.120:16375'
  password: 'redistest0102'

Key Code Snippets

Bidirectional read logic (redisdual/cmd/start.go):

func dual(rw *redis.Client, ro *redis.Client, key string) {
    roResult, err := ro.Get(key).Result()
    if err == nil && roResult != "" {
        global.RSPLog.Sugar().Infof("Get key %s from redisro result is:%s ", key, roResult)
        return
    }
    rwResult, err := rw.Get(key).Result()
    if err != nil || rwResult == "" {
        global.RSPLog.Sugar().Infof("key %s no result return!", key)
        return
    }
    global.RSPLog.Sugar().Infof("Get key %s from redisrw result is: %s ", key, rwResult)
}

Service start loop (redisdual/cmd/start.go):

for {
    if i > loopstep { i = 0 }
    key := global.RSPViper.GetString("localkeyprefix") + "_key" + strconv.Itoa(i)
    redisRW.Set(key, key+"_"+strconv.FormatInt(time.Now().UnixNano(),10)), 3600*time.Second)
    dual(redisRW, redisRO, global.RSPViper.GetString("localkeyprefix")+"_key"+strconv.Itoa(i))
    dual(redisRW, redisRO, global.RSPViper.GetString("remotekeyprefix")+"_key"+strconv.Itoa(i))
    i++
    time.Sleep(time.Duration(global.RSPViper.GetInt("execinterval")) * time.Millisecond)
}

Conclusion

Three main mechanisms for Redis bidirectional synchronization are presented: data offset, double‑write, and double‑read. Considering data safety and maintenance cost, the double‑read scheme offers the lowest operational overhead and avoids data confusion during failures.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Dockerdata synchronizationdatabase migrationBidirectional SyncRedissyncer
JD Cloud Developers
Written by

JD Cloud Developers

JD Cloud Developers (Developer of JD Technology) is a JD Technology Group platform offering technical sharing and communication for AI, cloud computing, IoT and related developers. It publishes JD product technical information, industry content, and tech event news. Embrace technology and partner with developers to envision the future.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.