Mongodb跨数据中心部署实践

公司某休闲游戏的海外应用服务器和Mongodb数据库服务器都部署在了AWS美国东部某数据中心。最近为了降低欧洲用户的访问延迟,打算尝试使用Mongodb的zone sharding特性实现数据跨欧洲、北美两个数据中心,并在欧洲再部署一套应用服务器直连欧洲的数据库服务器。再通过AWS的Route 53(域名系统)的地理策略,依据其ip位置将用户分流到欧洲、北美两个入口,从而实现用户就近访问后端服务器,本地读写数据。

动手前先从架构和业务两方面思考有哪些需要解决的问题,尽可能避免一些弯路,做好充分的准备。

问题及解决方案

选择跨数据中心的网络连接方式

这里需要考虑安全和稳定两方面因素。分别调研了以下三种方式:

  • VPC Peering Connections(对等连接),这个适用于同一数据中心里跨账户或跨VPC间的子网互连,目前不支持跨数据中心。
  • Direct Connect(专线直连),这个可以满足需求但是成本较高。
  • VPN(虚拟私有网络),因为是国外连国外,不存在墙的问题。AWS本身提供这项服务,也可以自行购买EC2虚拟机搭建VPN。综合考虑地缘和延迟等因素后最终选择法兰克福作为欧洲的数据中心。

Mongodb读写策略

目的是让大部分读写请求不用跨数据中心就可完成。

  • 使用Mongodb的zone特性,为shard打上区域标记(US or EU)。每个shard各有三个副本,Primary和一个Secondary在自己所标记区域对应的数据中心,另一Secondary存在于另一个数据中心。
  • 以欧洲为例,开启Route 53的地理策略后,从欧洲入口机进来的用户即为欧洲用户,其数据需要具备EU特征,这样就会存储在EU标记的shard上,读写均发生在本地。同理北美区。
  • 设置Mongodb的ReadPreference为nearest。仍以欧洲为例,欧洲用户读取北美用户数据(如好友、排名)时会就近选择本地的标记为US的shard的副本读取。由于跨中心同步存在延迟性,所以读取到的数据版本稍有滞后,但是影响不大。
  • 少数不可避免的跨中心写请求影响不大。

结合业务实际制定Mongodb区域分片的具体规则

这款游戏已上线较长时间,拥有约3000万用户,约80GB数据文件,前端发布过几十个版本,存在很多难以变更的点或结构。因此需要避免修改后端应用服务的业务层,包括数据类型和接口方法等。综合考虑后确定以下修改意见:

  • 制定规则让用户数据具备区域特征。大部分原有用户数据都是以递增的整型的user_id作为主键(_id),分类保存在各个collection里。新增约定以100亿为一个区间划分区域,如,user_id是1到100亿的属于北美区(US),100亿零1到200亿的属于欧洲区(EU)。user_id的递增规则不变,以百亿前缀区分区域,即同一个collection不会同时存在user_id为99和100亿零99的数据。可以通过修改百亿前缀来实现数据跨区迁移。
  • 大部分原有用户数据集合是在_id,即user_id上建立hashed索引并进行分片。而hashed方式不支持zone划分,所以需要改为普通的按大小升序或降序的索引方式。Mongodb本身不支持重新定义shard key,需要重新定义collection再把数据迁到新的collection里,所以一项额外的数据迁移工作要做。
  • 额外的数据迁移工作:数据从旧Mongodb热迁移到新Mongodb。为了线上服务不停机,首先在应用程序端的数据库访问层实现双写,即所有更新命令同时发给新旧两个Mongodb环境,原则是确保新Mongodb里的文档要么是空、要么与旧Mongodb相对应的完全一致。开启该功能后,再从旧Mongodb逐个collection逐个检出文档,如果新Mongodb不存在该文档则插入新Mongodb,此处忽略少量并发导致数据不一致的问题。

服务器部署

mongodb-zone-

落地实践

搭建数据库并初始化

搭建过程

参考 https://www.wanghengbin.com/2016/03/18/mongodb-shard-cluster-app/

为shard增加区域标记:

sh.addShardTag("shard1", "US")
sh.addShardTag("shard2", "US")
sh.addShardTag("shard3", "EU")

为collection设置分片规则,并为片键的不同区间增加区域标记:

sh.shardCollection("jelly_cm.user_data", {_id: 1})
sh.addTagRange("jelly_cm.user_data", {_id: 1}, {_id: 10000000000}, "US")
sh.addTagRange("jelly_cm.user_data", {_id: 10000000001}, {_id: 20000000000}, "EU")

创建业务上需要的索引,这里注意记得把旧Mongodb的索引在新Mongodb里事先创建好,否则切换查询入口到新Mongodb时有大量查询进来,如果没有索引会导致大量慢查询。

实施过程中遇到的问题

  1. 首当其冲的就是mongos的一个bug(mongodb版本3.2.16)。使用nearest或其他从secondary节点查数据的模式,查询某个确实存在于secondary节点并且是存在已久的数据,在某些mongos上可以查到,在另一些mongos上竟然查不到,并且查相邻的_id的数据也查不到。如果在那些查不到的mongos上执行db.colletion.count(),或使用默认的primary查询模式,或执行下explain()看下nearest模式的执行计划后,再用nearest查就可以查到了…暂时还没定位问题原因,怀疑是move chunk导致,线上从,AWS的业务角度补偿了这个bug。
  2. 由于两个数据中心之间的通信存在160ms的延迟,为了满足原有的并发量需要适当调大应用服务器到mongos的连接池大小。
  3. 用户在Login时会根据请求发生的web机位置标记其所属区域,并判断是否存在跨区读写数据,如果存在则会计入待迁移集合。上线后发现有一部分用户的Login请求会经常在欧洲和北美入口间摆动,猜测是边界地区或其他原因导致IP变动频繁。
  4. VPN出现故障时Shard各个副本的角色没有变化,此时欧洲区的mongos失去shard1、2的Primary节点的连接,北美区的mongos失去shard3的Primary节点的连接。由于查询请求使用的策略是nearest,且两边都存在完整的数据副本,所以查询仍然正常。本地写请求由于不需要与另一个中心的Primary节点通信所以也正常,只有少部分跨区写请求无法执行。
  5. 由于VPN网速有限,跨中心重建shard副本要比同网段内重建需要更长的时间,数据量大时需要设置更大的oplog尺寸才能成功重建,否则可能会因为超出了作为同步源的副本的oplog的时间范围而失败。而oplog尺寸在oplog.rs创建后不能改变,想变只能修改启动参数并重建副本,所以根据实际需要尽量设置大些。通过 rs.printReplicationInfo() 查看当前副本的oplog的大小和时间状态。
Creative Commons License

本文基于署名-非商业性使用-相同方式共享 4.0许可协议发布,欢迎转载、使用、重新发布,但请保留文章署名wanghengbin(包含链接:https://wanghengbin.com),不得用于商业目的,基于本文修改后的作品请以相同的许可发布。

发表评论