一、项目背景:为啥要搞跨机房同步?

做过互联网产品的都知道,用户规模上去后,只靠一个机房扛流量基本是“裸奔”——万一机房断网、停电,整个产品直接挂掉,用户投诉能把客服热线打爆。我们产品当时有亿级日活,用户行为数据(比如点击、浏览、搜索)都存在A机房的Elasticsearch集群里,不仅要给实时推荐、风控系统用,还要同步到B机房做异地容灾,同时给数据分析团队用。

一开始我们想的很简单:直接用Elasticsearch自带的跨集群复制(CCR)功能,把A集群的索引同步到B集群不就行了?结果踩了一堆坑,走了大半年弯路,才把这套体系跑通。

二、核心技术选型:为啥选Elasticsearch CCR?

在确定用CCR之前,我们对比过其他方案:比如自己写脚本拉数据、用MQ中转、用第三方同步工具。最后选CCR的原因很实在:

  • 不用自己写代码维护,官方原生功能,出问题有文档查;
  • 是“拉取式”同步,源集群(A机房)不用额外扛太大压力,不会影响线上业务;
  • 支持增量同步,不用每次全量拉数据,省带宽省时间。

不过当时我们没意识到,CCR的坑比想象的多,后面会慢慢说。

三、第一次上线:看似顺利的坑

3.1 初始配置:照着文档抄

我们先搭了A、B两个Elasticsearch集群(都是7.10版本,文档说版本一致兼容性最好),然后按官方文档配了跨集群连接,再创建同步任务。

示例配置(技术栈:Elasticsearch 7.10)

首先在B集群(目标集群)配置跨集群连接A集群(源集群):

# B集群执行:配置跨集群连接A集群
PUT /_cluster/settings
{
  "persistent": {
    "cluster.remote": {
      "source-cluster": { # 自定义的源集群别名
        "seeds": ["10.0.1.10:9300", "10.0.1.11:9300", "10.0.1.12:9300"] # A集群的节点地址(注意是9300端口,不是9200)
      }
    }
  }
}

然后在B集群创建同步任务,同步A集群的user-behavior-*索引(按天分的索引,比如user-behavior-2024-05-01):

# B集群执行:创建CCR同步任务
PUT /_ccr/auto_follow/user-behavior-task
{
  "remote_cluster": "source-cluster", # 对应上面配置的源集群别名
  "leader_index_patterns": ["user-behavior-*"], # 要同步的源索引规则
  "follow_index_pattern": "{{leader_index}}", # 目标索引名和源索引名保持一致
  "max_read_request_operations": 100, # 单次拉取的最大文档数
  "max_read_request_size": "10mb" # 单次拉取的最大数据量
}

配完之后,我们看B集群确实生成了和A集群一样的user-behavior-*索引,数据也在同步,当时觉得“这功能也太简单了”,直接上线给业务用了。

3.2 踩坑:数据不一致、源集群扛不住

上线才过了3天,问题就来了: 第一个坑:数据不一致。比如A集群的user-behavior-2024-05-01索引有1200万条数据,B集群只有1180万条,差了20万条。我们查了日志,发现A集群的索引是按天滚动的,每天凌晨0点会创建新索引,而CCR的自动同步任务有10分钟的延迟,刚好把0点到0点10分的新索引数据漏了。 第二个坑:源集群压力飙升。我们没注意到,CCR同步时,源集群的节点需要处理大量的跨集群拉取请求,A集群的CPU使用率从平时的30%涨到了70%,偶尔还会出现请求超时,影响了实时推荐系统的正常运行。

四、第一次优化:补漏同步、限流

4.1 补漏同步:解决数据漏的问题

针对索引滚动的延迟问题,我们加了一个定时任务,每天凌晨0点15分,手动检查B集群的新索引,如果数据量和A集群差太多,就手动触发一次全量同步补漏。

示例补漏脚本(技术栈:Elasticsearch 7.10 + Shell)

#!/bin/bash
# 补漏脚本:每天凌晨0点15分执行,检查新索引数据是否一致
SOURCE_CLUSTER="http://10.0.1.10:9200" # A集群地址
TARGET_CLUSTER="http://10.0.2.10:9200" # B集群地址
INDEX_PREFIX="user-behavior-"
TODAY=$(date +"%Y-%m-%d")
INDEX_NAME="${INDEX_PREFIX}${TODAY}"

# 获取源集群索引的文档数
SOURCE_COUNT=$(curl -s "${SOURCE_CLUSTER}/${INDEX_NAME}/_count" | jq -r '.count')
# 获取目标集群索引的文档数
TARGET_COUNT=$(curl -s "${TARGET_CLUSTER}/${INDEX_NAME}/_count" | jq -r '.count')

# 如果目标集群文档数比源集群少1%以上,触发补漏
if [ $(echo "$TARGET_COUNT < $SOURCE_COUNT * 0.99" | bc) -eq 1 ]; then
  echo "索引${INDEX_NAME}数据不一致,源:${SOURCE_COUNT},目标:${TARGET_COUNT},触发补漏"
  # 停止现有同步任务(如果有的话)
  curl -X DELETE "${TARGET_CLUSTER}/_ccr/follow/${INDEX_NAME}"
  # 重新创建同步任务,强制全量同步
  curl -X PUT "${TARGET_CLUSTER}/_ccr/follow/${INDEX_NAME}" -H "Content-Type: application/json" -d '{
    "remote_cluster": "source-cluster",
    "leader_index": "'${INDEX_NAME}'",
    "max_read_request_operations": 100,
    "max_read_request_size": "10mb"
  }'
fi

4.2 限流:降低源集群压力

针对源集群压力大的问题,我们调整了CCR的配置,限制同步的速度,同时调整了源集群的节点配置,专门加了两个节点用来处理跨集群请求,不让同步请求影响业务节点。

调整后的CCR配置(技术栈:Elasticsearch 7.10)

# B集群执行:调整同步任务配置,限流
PUT /_ccr/auto_follow/user-behavior-task
{
  "remote_cluster": "source-cluster",
  "leader_index_patterns": ["user-behavior-*"],
  "follow_index_pattern": "{{leader_index}}",
  "max_read_request_operations": 50, # 把单次拉取的文档数从100降到50,减少单次请求压力
  "max_read_request_size": "5mb", # 单次拉取的数据量从10mb降到5mb
  "max_outstanding_read_requests": 2 # 同时最多只发2个拉取请求,进一步限流
}

调整之后,A集群的CPU使用率降到了40%左右,稳定了很多。

五、第二次上线:又踩新坑

优化完之后,我们再次上线,这次稳定了差不多一个月,结果又出问题了。

5.1 新坑:跨集群连接断连、索引无法同步

问题出在网络上:A、B两个机房之间的专线偶尔会出现闪断,每次闪断超过5分钟,CCR的跨集群连接就会断开,之后即使网络恢复,同步任务也不会自动恢复,导致新索引的数据无法同步。

我们查了Elasticsearch的文档,发现CCR的跨集群连接有一个超时时间,默认是300秒(5分钟),超过这个时间连接就会被标记为失效,需要手动重新配置。

5.2 临时解决:加连接监控

我们加了一个监控脚本,每5分钟检查一次跨集群连接的状态,如果连接失效,就自动重新配置跨集群连接,然后重启同步任务。

示例连接监控脚本(技术栈:Elasticsearch 7.10 + Shell)

#!/bin/bash
# 连接监控脚本:每5分钟执行一次,检查跨集群连接状态
TARGET_CLUSTER="http://10.0.2.10:9200" # B集群地址
REMOTE_CLUSTER_ALIAS="source-cluster"

# 检查跨集群连接状态
CONNECT_STATUS=$(curl -s "${TARGET_CLUSTER}/_remote/info/${REMOTE_CLUSTER_ALIAS}" | jq -r '.[] | .connected')

# 如果连接断开,重新配置
if [ "$CONNECT_STATUS" != "true" ]; then
  echo "跨集群连接断开,重新配置"
  # 先删除旧的连接配置
  curl -X PUT "${TARGET_CLUSTER}/_cluster/settings" -H "Content-Type: application/json" -d '{
    "persistent": {
      "cluster.remote.source-cluster": null
    }
  }'
  # 重新配置新的连接
  curl -X PUT "${TARGET_CLUSTER}/_cluster/settings" -H "Content-Type: application/json" -d '{
    "persistent": {
      "cluster.remote": {
        "source-cluster": {
          "seeds": ["10.0.1.10:9300", "10.0.1.11:9300", "10.0.1.12:9300"]
        }
      }
    }
  }'
  # 重启所有同步任务(这里简化处理,实际可以遍历所有同步任务)
  curl -X DELETE "${TARGET_CLUSTER}/_ccr/auto_follow/user-behavior-task"
  curl -X PUT "${TARGET_CLUSTER}/_ccr/auto_follow/user-behavior-task" -H "Content-Type: application/json" -d '{
    "remote_cluster": "source-cluster",
    "leader_index_patterns": ["user-behavior-*"],
    "follow_index_pattern": "{{leader_index}}",
    "max_read_request_operations": 50,
    "max_read_request_size": "5mb",
    "max_outstanding_read_requests": 2
  }'
fi

六、最终优化:彻底解决问题

虽然监控脚本解决了连接断开的问题,但每次连接断开都会导致同步中断,影响数据的实时性。我们后来发现,Elasticsearch 7.10之后的版本,对跨集群连接的超时时间做了优化,而且支持自动重连。

6.1 升级版本、调整超时

我们把两个集群的版本都升级到了7.17(长期支持版本),然后调整了跨集群连接的超时时间,同时开启了自动重连。

升级后的跨集群配置(技术栈:Elasticsearch 7.17)

# B集群执行:配置跨集群连接,支持自动重连
PUT /_cluster/settings
{
  "persistent": {
    "cluster.remote": {
      "source-cluster": {
        "seeds": ["10.0.1.10:9300", "10.0.1.11:9300", "10.0.1.12:9300"],
        "connect_timeout": "60s", # 连接超时时间从默认的30s降到60s,减少闪断导致的连接失效
        "socket_timeout": "600s", # 套接字超时时间设为10分钟,允许更长时间的网络波动
        "ping_interval": "30s", # 每30s发送一次心跳,检测连接状态
        "auto_reconnect": true # 开启自动重连,网络恢复后自动重建连接
      }
    }
  }
}

6.2 索引滚动的优化

针对索引滚动的延迟问题,我们调整了A集群的索引滚动时间,把每天的滚动时间从凌晨0点改成了凌晨0点30分,同时把CCR的自动同步任务的延迟时间从10分钟改成了5分钟,这样新索引创建后,同步任务能更快地检测到并开始同步,减少数据漏的概率。

七、项目总结

7.1 应用场景

这套CCR方案适合亿级数据的异地容灾、多机房数据共享、跨区域数据分析的场景,尤其是源集群业务压力大,不想让同步任务影响线上业务的情况。

7.2 技术优缺点

优点:

  • 官方原生功能,稳定性好,不用自己维护复杂的同步逻辑;
  • 拉取式同步,源集群压力可控,不会影响线上业务;
  • 支持增量同步,节省带宽和时间。 缺点:
  • 对网络稳定性要求高,跨机房专线闪断容易导致同步中断;
  • 版本兼容性要求高,源集群和目标集群版本差异大的话,容易出问题;
  • 配置复杂,需要根据业务场景调整限流、超时等参数。

7.3 注意事项

  • 跨集群连接要用9300端口(传输端口),不要用9200端口(HTTP端口);
  • 源集群和目标集群的版本尽量一致,升级时要同步升级;
  • 一定要做限流,避免同步任务影响源集群的业务;
  • 要加监控,监控同步状态、连接状态、数据一致性;
  • 索引滚动的时间要和同步任务的延迟时间配合好,避免数据漏。

7.4 最终效果

经过几次优化,我们的CCR方案终于稳定了:数据一致性达到了99.9%,源集群的CPU使用率稳定在40%左右,跨机房同步的延迟控制在1分钟以内,完全满足了业务的需求。