MongoDB
文档型数据库,使用具有类型的 JSON 格式存储数据。 将数据分成 集合和文档(类似于关系型数据库的 行和列)存储。 具有灵活、可扩展、高性能、适合存储大量数据。 MongoDB是一个分布式的数据库,设计时考虑了水平扩展。这意味着当数据量增大时,可以通过添加更多的服务器来扩展MongoDB的存储和处理能力。这种水平扩展的特性使得MongoDB能够轻松应对大规模的数据增长,而不需要进行昂贵的硬件升级。 Replication Replication在实现主从数据同步时,通常采用Transaction Log的方式,比如,当一条数据插入到主数据库的时候,主数据库会像Trasaction Log中插入一条记录来声明这次数据库写纪录的操作。之后,一个Replication Process会被触发,这个进程会把Transaction Log中的内容同步到从数据库中。整个过程如下图所示: 水平扩展 对于数据库的扩展来说,通常有两种方法,水平扩展和垂直扩展。 垂直扩展:这种扩展方式比较传统,是针对一台服务器进行硬件升级,比如添加强大的 CPU,内存或者添加磁盘空间等等。这种方式的局限性是仅限于单台服务器的扩容,尽可能的增加单台服务器的硬件配置。优点是构架简单,只需要维护单台服务器。 水平扩展:这种方式是目前构架上的主流形式,指的是通过增加服务器数量来对系统扩容。在这样的构架下,单台服务器的配置并不会很高,可能是配置比较低、很廉价的 PC,每台机器承载着系统的一个子集,所有机器服务器组成的集群会比单体服务器提供更强大、高效的系统容载量。这样的问题是系统构架会比单体服务器复杂,搭建、维护都要求更高的技术背景。MongoDB 中的 Sharding 正式为了水平扩展而设计的,下面就来挤开 shard 面纱,探讨一下 shard 中不同分片的技术区别以及对数据库系统的影响。 分片 (Shard) 前面提到的 Replication 结构可以保证数据库中的全部数据都会有多分拷贝,数据库的高可用可以保障。但是新的问题是如果要存储大量的数据,不论主从服务器,都需要存储全部数据,这样检索必然会出现性能问题。 可以这样讲,Replication只能算是分布式数据库的第一阶段。主要解决的是数据库高可用,读数据可以水平扩展,部分解决了主数据并发访问量大的问题。但是它并没有解决数据库写操作的分布式需求,此外在数据库查询时也只限制在一台服务器上,并不能支持一次查询多台数据库服务器。 MongoDB 分片原理 MongoDB 中通过 Shard 支持服务器水平扩展,通过 Replication 支持高可用(HA)。这两种技术可以分开来使用,但是在大数据库企业级应用中通常人们会把他们结合在一起使用。 通过分片这个单词我们可以看出,他的意思是将数据库表中的数据按照一定的边界分成若干组,每一组放到一台 MongoDB 服务器上。 很显然的做法是对这张用户表进行拆分,假设用户表中有一个age年龄字段,我们先做一个简单的拆分操作,按照用户的年龄段把数据放到不同的服务器上,以 20 为一个单位,20 岁以下的用户放到 server1,20 到 40 岁的用户放到 server2,40-60 岁的用户放到 server3,60 岁以上放到 server4,后面我们会讲这样的拆分是否合理。 在这个例子中,用户年龄age就是我们进行Sharding(切分)的Shard Key(关于 Shard Key 的选择后面会详细介绍),拆分出来的server1,server2,server3和server4就是这个集群中的 4 个Shard(分区)服务器。 好,Shard 集群已经有了,并且数据已经拆分完好,当用户进行一次查询请求的时候我们如何向这四个 Shard 服务器发送请求呢?例如:我的查询条件是用户年龄在 18 到 35 岁之间,这样个查询请求应当发送到server1和server2,因为他们存储了用户年龄在 40 以下的数据,我们不希望这样的请求发送到另外两台服务器中因为他们并不会返回任何数据结果。此时,另外一个成员就要登场了,mongoos,它可以被称为 Shard 集群中的路由器,就像我们网络环境中使用的路由器一样,它的作用就是讲请求转发到对应的目标服务器中,有了它我们刚才那条查询语句就会正确的转发给server和server2,而不会发送到server3和server4上。mongos根据用户年龄(Shard Key)分析查询语句,并把语句发送到相关的 shard 服务器中。 除了mongos和shard之外,另一个必须的成员是配置服务器,config server,它存储 Shard 集群中所有其他成员的配置信息,mongos会到这台config server查看集群中其他服务器的地址,这是一台不需要太高性能的服务器,因为它不会用来做复杂的查询计算,值得注意的是,在 MongoDB3.4 以后,config server必须是一个replica set。理解了上面的例子以后,一个 Shard 集群就可以部署成下图所示的结构: 其中: shard: 每一个 Shard 服务器存储数据的一个子集,例如上面的用户表,每一个 Shard 存储一个年龄段的用户数据。 mongos: 处理来自应用服务器的请求,它是在应用服务器和Shard 集群之间的一个接口。 config server: 存储 shard 集群的配置信息,通常部署在一个 replica set 上。 Chunk 我们已经知道 MongoDB 是通过 shard key 来对数据进行切分,被切分出来的数据被分配到若干个 chunks 中。一个 chunk 可以被认为是一台 shard 服务器中数据的子集,根据 shard key,每个 chunk 都有上下边界,在我们的例子中,边界值就是用户年龄。 chunk 有自己的大小,数据不断插入到 mongos 的过程中,chunk 的大小会发生变化,chunk 的默认大小是 64M。当然 MongoDB 允许你对 chunk 的大小进行设置,你也可以把一个 chunk 切分成若干个小 chunk,或者合并多个 chunk。一般我不建议大家手动操作 chunk 的大小,或者在 mongos 层面切分或合并 chunk,除非真有合适的原因才去这么做。 原因是,在数据不断插入到我们的集群中时,mongodb 中的 chunk 大小会发生很大的变化,当一个 chunk 的大小超过了最大值,mongo 会根据 shard key 对 chunk 进行切分,在必要的时候,一个 chunk 可能会被切分成多个小 chunk,大多数情况下这种自动行为已经满足了我们日常的业务需求,无需进行手动操作,另一点原因是当进行 chunk 切分后,直接的结果会导致数据分配的不均匀,此时 balancer 会被调用来进行数据重新分配,很多时候这个操作会运行很长时间,无形中导致了内部结构的负载平衡,因此不建议大家手动拆分。 这里我只强调在进行 chunk 操作的时候,要注意一下几个方面,这些都是影响你 MongoDB 性能的关键因素。 如果存在大量体积很小的 chunk,他可以保证你的数据均匀的分布在 shard 集群中但是可能会导致频繁的数据迁移。这将加重 mongos 层面上的操作。 大的 chunk 会减少数据迁移,减轻网络负担,降低在 mongos 路由层面上的负载,但弊端是有可能导致数据在 shard 集群中分布的不均匀。 Balancer 会在数据分配不均匀的时候自动运行,那么 Balancer 是如何决定什么情况下需要进行数据迁移呢?答案是 Migration Thresholds,当 chunk 的数量在不同 shard replica 之间超过一个定值时,balancer 会自动运行,这个定值根据你的 shard 数量不同而不同。 Zones 可以说 chunk 是 MongoDB 在多个 shard 集群中迁移数据的最小单元,有时候数据的分配不会按照我们臆想的方向进行,就拿上面的例子来说,虽然我们选择了用户年龄作为 shard key,但是 MongoDB 并不会按照我们设想的那样来分配数据,如何进行数据分配就是通过 Zones 来实现。 Zones 解决了 shard 集群与 shard key 之间的关系,我们可以按照 shard key 对数据进行分组,每一组称之为一个 Zone,之后把 Zone 在分配给不同的 Shard 服务器。一个 Shard 可以存储一个或多个 Zone,前提是 Zone 之间没有数据冲突。Balancer 在运行的时候会把在 Zone 里的 chunk 迁移到关联这个 Zone 的 shard 上。 你选择的 Shard Key 合适吗? 那么什么样的 key 能够保证数据的均匀分布呢?接下来我们分析一下 shard key 的种类。 Ranged Shard Key 范围KEY 好处是查询连续取值纪录时,查询效率可以得到保证。一般只需要少量的 shard 就可以完成查询操作。缺点是不能保证数据的平均分配,在数据插入和修改时会产生比较严重的性能瓶颈。 Hashed Shard Key:哈希KEY 于 Ranged Shard Key 对应的一种被称之为 Hashed Shard Key,它采用字段的索引哈希值作为 shard key 的取值,这样做可以保证数据的均匀分布。 我们如何在 Ranged 和 Shard 之间进行选择呢?下面两个属性是我们选择 shard key 的关键。 Shard Key Cardinality (集) Cardinality指的是 shard key 可以取到的不同值的个数。他会影响到 Balancer 的运行,这个值也可以被看做是 Balancer 可以创建的最大 chunk 个数。以我们年龄字段为例,假如一个人的年龄在 100 岁以下,那么这个字段的 cardinality 可以取 100 个不同的值。对于一个唯一的年龄数据,不会出现在不同的 chunk 中。如果你选择的 Shard Key 的 cardinality 很小,比如只有 4 个,那么数据最多会被分发到 4 个不同的 shard 中,这样的结构也不适合服务器的水平扩展,因为不会有数据被分割到第五个 shard 服务器上。 Shard Key Frequency(频率) Frequency指的是 shard key 的重复度,也就是对于一个字段,有多少取值相同的纪录。如果大部分数据的 shard key 取值相同,那么存储他们的 chunk 会成为数据库的一个瓶颈。而且,这些 chunk 也变成了不可再切分的 chunk,严重影响了数据库的水平扩展。在这种情况下应当考虑使用组合索引的方式来创建 shard key。所以,尽量选择低频率的字段作为 shard key。 Shard Key Increasing Monotonically (单调增长) 单调增长在这里的意思是在数据被切分以后,新增加的数据会按照其 shard key 取值向 shard 中插入,如果新增的数据的 key 值都是向最大值方向增加,那么这些新的数据会被插入到同一个 shard 服务器上。例如我们前面的用户年龄分组字段,如果系统的新增用户都是年龄大于 40 岁的,那么 shard3 将会存储所有的新增用户,shard3 会成为系统的性能瓶颈。在这种情况下,应当考虑使用 Hashed Shard Key。
来源整理自:vue3js.cn 面试官系列



