本文是对MongoDB 世界大会上『Life of a Sharded Write』主题分享的总结,这个分享很有意思,主要内容是介绍 MongoDB Sharded Cluster 里写操作的路由策略,以及config server变为复制集后面临的一些挑战。
如果不了解 Sharded Cluster 的基础知识,可以先看看这篇文章再回来。
Mongos路由策略
在 Sharded Cluster 里,用户可以将集合的数据以 chunk 为单位分散存储到多个 shard 上。如下图所示,集合的数据按照 shardKey 被切分为 [minKey, -200), [-200, -100), [-100, 0), [0, 100), [100, 200), [200, maxKey)等chunk 范围,并存储在 Shard0, Shard1, Shard2等3个 shard 上。
config server 上对应的路由表(route table)类似下图
当写入新文档时,mongos 从config server 上获取集合的路由表本地,如写入{shardKey: 150}的文档,则请求被路由到shard1上写入。
mongos 从 config server 上获取到路由表后,会缓存在本地内存,避免每次写入/查询都去 config server 上取表。而在 Sharded Cluster 里,mongos 会自动在 shard 之间迁移 chunk 以均衡负载(用户也可以发送 moveChunk 命令来手动迁移),那么一旦 chunk 发生迁移后,mongos 本地缓存的路由表就会失效,从而请求被路由到错误的 shard,这个问题该如何解决?
MongoDB 的做法是给路由表增加版本信息,比如最初的路由表包含6个 chunk,路由信息的版本为 v6(各个条目版本的最大值)。
当[0, 100)从 shard0迁到 shard1之后,条目的版本增加为路由表当前版本再加1, 即变为7,这个信息会记录在 shard 本地,同时也会更新到 config server 里。
mongos 在写入时,会带上自身缓存的路由表版本,当请求到达 shard后,shard 发现 mongos 的路由表版本比自己的低,则说明路由表已经发生过更新,这时 mongos 会重新到 config server 上取最新的路由表,然后按新的路由表来写入。 这个设计思路跟阿里开源的 tair KV存储系统非常类似。
说明:上述的版本只为介绍原理之用,实际上在3.2的实现里版本是一个(majorVersion, minorVersion)的二元组),当chunk 发生 split 之后,split 之后的所有 chunk minor version增加,当 chunk 在 shard 之间发生迁移时,迁移的 chunk 在目标上增加 major version,并且在迁移源上选择一个 chunk 增加 其major version,这样确保不论是访问到源还是目标,mongos 看到的版本都增加了。
config server 复制集的挑战
3.2版本里,config server 从以前的多个镜像节点换成了复制集,换成复制集后,由于复制自身的特性,Sharded Cluster 在实现上也面临一些挑战。
- 挑战1:复制集原Primary 上的数据可能会发生回滚,对 mongos 而言,就是『读到的路由表后来又被回滚了』。
- 挑战2:复制集备节点的数据比主节点落后,如果仅从主节点上读,读能力不能扩展,如果从备节点上读,可能读到的数据不是最新的,对 mongos 的影响是『可能读到过期的路由表,在上述例子中,mongos 发现自己的路由表版本低了,于是去 config server 拉取最新的路由表,而如果这时请求到未更新的备节点上,可能并不能成功的更新路由表』。
应对第一个问题,MongoDB 在3.2版本里增加了 ReadConcern 特性的支持,ReadConcern支持『local』和『majority』2个级别。local 即普通的 read,majority 级别保证应用读到的数据已经成功写入到了复制集的大多数成员。
而一旦数据成功写入到大多数成员,这样的数据就肯定不会发生 rollback,mongos 在从 config server 读取数据时,会指定 readConcern 为 majority 级别,确保读取到的路由信息肯定不会被回滚。
应对第二个问题,MongoDB 在majority 级别的基础上,增加了 afterOpTime 的参数,这个参数目前只在 Sharded Cluster 内部使用。这个参数的意思是『被请求节点的最新oplog时间戳必须大于 afterOpTime 指定的时间戳』。
Mongos 带着路由表版本信息请求 某个 shard,shard发现自己的版本比 mongos 新(发生过 chunk 迁移),此时shard 除了告诉 mongos 自己应该去更新路由表,还会把自己迁移 chunk 后更新 config server 时的 optime告诉mongos,mongos 请求 config server 时,指定 readConcern 级别为 majority,并指定 afterOpTime 参数,以确保不会从备节点读到过期的路由表。