前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >利用redissyncer实现数据双向同步

利用redissyncer实现数据双向同步

作者头像
heidsoft
发布2021-08-26 17:06:51
2.7K0
发布2021-08-26 17:06:51
举报

redissyncer简介

RedisSyncer是京东云自研的redis多任务同步中间件工具集,应用于redis单实例及集群同步。该工具集包括:

  • redis 同步服务引擎 redissyncer-server
  • redissycner 客户端 redissyncer-cli
  • redis 数据校验工具 redissycner-compare
  • 基于docker-compse的一体化部署方案 redissyncer

目前在github开源:

https://github.com/TraceNature/redissyncer-server

缓存同步的定义及必要性

  • 双向同步是指在两个实例都有存量数据和写流量的情况下进行两实例同步,最终达到两实例数据动态一致的过程
  • 缓存数据全局可读,防止缓存击穿
  • 保证缓存命中率,为数据库减压
  • 当单一数据中心发生故障时,保证数据在另一中心完全可见

双向同步的操作难度与冷启动问题

  • 原生redis同步无法区分缓存数据来源
  • 由于redis本身没有实例标识(类似mysql的GTID),在双向同步时形成数据回环
  • redis环状缓冲区覆盖后,数据混淆且难于清理

基于数据冲销的双向同步方案

利用数据冲销的方式破除数据写入环。该方案的必要条件是,同步的实例或集群写入的key无冲突,即在数据中心A写入的key,不会同一时间在B中心写入相异值。

假设我们有两个redis实例redis1和redis2,再分别定义两个冲销池set1和set2,记录key及同步次数。

  1. 启动redis1->redis2的全量同步任务
  2. 启动redis1->redis2增量任务 2-1。增量任务先在set1做冲销(set1中存在的数据删除并丢弃同步) 2-2. 未被冲销数据同步到redis2
  3. 启动redis2->redis1的全量任务,此全量同步数据一定会作为增量形成回环,所以要先写入set1再写入redis1,以便数据作为增量回环同步到redis2时利用set1冲销 3-1,写入set1 3-2,写入redis1
  4. 启动redis2->redis1增量任务,增量任务先在set2做冲销(set2中存在的数据删除并丢弃同步),增量任务先写入set1在写入redis1避免循环复制 4-1。通过set2冲销数据 4-2,数据写入set1 4-3,数据写入redis1
  5. 改变redis1->redis2增量任务行为,增量任务先在set1做冲销(set1中存在的数据删除并丢弃同步),未冲销数据先写入set2再同步到redis2 5-1 写入set2 5-2 写入redis2

为什么增加第五步改变redis1->redis2增量任务行为呢?因为在第四步完成时set2中并没有从redis1->redis2的增量数据,这会造成从redis1->redis2的增量数据会转换成redis2->redis1增量数据且在本地无法被冲销,只有数据进入set1且被写入redis1后再次作为增量数据向redis2同步时才会被冲销,增加了网络开销同时redis1也增加了一次写入负载。

数据冲销方式及其缺陷

  • 数据冲销方式需要在每次发送数据前对数据进行缓冲,正常情况下缓冲内存占用不大。但当网络阻塞或由于网络不畅无法冲销数据时,会造成缓冲区暴增导致OOM
  • 数据冲销方式带来的冷启动问题
    • 当任务异常中断且redis offset被覆盖的情况下,因为数据矫正依据缺失,需要重建缓存
    • 若采用数据持久化的方式先持久化后发送的方式,那么在冲销过程中会大大降低同步效率

数据双写,看似美好其实坑多多

业务双写是最符合人类直觉的双向方案,同一份数据写入两个数据中心以保障数据冗余。但是在实际操作中会遇到数据写入顺序问题。

双写方案中的数据顺序问题

  • 并发环境中同时写入同一个key的情况下,并不能保障key写入redis的顺序。造成key的结果不一致。
  • 通过统一队列解决顺序问题
  • 网络中断导致数据缺失
  • 强一致性会导致单机房不可用的情况下写操作全局不可用,并需要在数据在某一次提交不完全成功的情况下提供回滚机制、及数据补偿机制
  • 队列带来写效率损失,redis失去作为缓存层的意义

双读方案

  • 每个数据中心建立redis 读写实例与只读实例
  • 读写数据中的落地数据通过redissyncer同步到对端只读实例
  • 应用读数据时先读取只读实例若有数据返回则返回;若无数据返回则读取读写实例
  • 双读方案的限制条件
    • key的生成在全局具有唯一性既两个中心不出现重复的key
    • 避免incr 、 lpush 等非幂等操作
    • 由于网络抖动可能造成数据流中断,尽管redissyncer以及对非幂等命令做了处理,但是极端情况仍然可能造成数据不准确影响业务

双读方案实践模拟

环境列表

主机名

IP地址

部署软件或工具

az_a1

10.0.0.110

redis5.0

az_a1

10.0.0.110

redissyncer

az_a2

10.0.0.111

redis5.0

az_b1

10.0.0.112

redis5.0

az_b1

10.0.0.112

redissyncer

az_b2

10.0.0.113

redis5.0

  • az_a1 代表 a 中心的 redis RW 实例;az_a2 代表 a 中心 redis RO 实例;az_b1 代表 b 中心 redis RW 实例;az_b2 代表 b 中心 redis RO 实例
  • 分别在 az_a1、az_b1上部署 redissyncer 用于同步到对端数据中心
  • 通过 redisdual 模拟双读客户端

实施细则

部署 redis 详见 redis 部署文档,这里不累述

部署redissyncer(docker方式); az_a1、az_b1 上执行

clone redissyncer 项目

部署 redissyncer-cli 用于与服务器通讯

  • 下载客户端程序 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

az_a1 配置同步任务同步到 az_b2

  • 编辑任务文件 synctask/a1_to_b2.json { "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 }

az_b1 配置同步任务同步到 az_a2

  • 编辑任务文件 synctask/b1_to_a2.json { "sourcePassword": "redistest0102", "sourceRedisAddress": "10.0.0.112:16375", "targetRedisAddress": "10.0.0.111:16375", "targetPassword": "redistest0102", "taskName": "b1_to_a2", "targetRedisVersion": 5.0, "autostart": true, "afresh": true, "batchSize": 100 }

通过redisdual 模拟redis 双读

config.yaml 文件参数详解

主要代码分析

代码语言:javascript
复制
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)

}

redisdual/cmd/start.go;func startServer() 函数。启动服务,定时执行RW实例写入。并执行双读操作

代码语言:javascript
复制
// -d 后台启动
if global.RSPViper.GetBool("daemon") {
cmd, err := background()
if err != nil {
panic(err)
}

//根据返回值区分父进程子进程
if cmd != nil { //父进程
fmt.Println("PPID: ", os.Getpid(), "; PID:", cmd.Process.Pid, "; Operating parameters: ", os.Args)
return //父进程退出
} else { //子进程
fmt.Println("PID: ", os.Getpid(), "; Operating parameters: ", os.Args)
}
}

global.RSPLog = core.Zap()
global.RSPLog.Info("server start ... ")

pidMap := make(map[string]int)
// 记录pid
pid := syscall.Getpid()
pidMap["pid"] = pid

pidYaml, _ := yaml.Marshal(pidMap)
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
panic(err)
}

if err := ioutil.WriteFile(dir+"/pid", pidYaml, 0664); err != nil {
global.RSPLog.Sugar().Error(err)
panic(err)
}
global.RSPLog.Sugar().Infof("Actual pid is %d", pid)

//redis 读写实例
redisRW := GetRedisRW()

//redis 只读实例
redisRO := GetRedisRO()

//清理RW
redisRW.FlushAll()

global.RSPLog.Sugar().Info("execinterval:", global.RSPViper.GetInt("execinterval"))
loopstep := global.RSPViper.GetInt("loopstep")
i := 0
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)
}
  • 启动redisdual 并观察日志 redisdual start

小结

redis的双向同步方案的机制大致就是以上三种,具体生产中采用哪种方式要根据业务特性进行权衡。从数据安全和维护成本方面考虑,双读方案从运维成本来讲是最少的,且在故障发生时不会引起数据混淆。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-08-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 云数智圈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 实施细则
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档