一、项目背景:为啥要搞跨机房同步?
做过互联网产品的都知道,用户规模上去后,只靠一个机房扛流量基本是“裸奔”——万一机房断网、停电,整个产品直接挂掉,用户投诉能把客服热线打爆。我们产品当时有亿级日活,用户行为数据(比如点击、浏览、搜索)都存在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分钟以内,完全满足了业务的需求。
评论
围绕“跨集群复制(CCR)实战:亿级用户行为数据多机房同步的曲折历程”参与讨论