背景:我公司是一家高速发展的社交电商公司,目前拥有数百万用户,年销售额近50亿。公司技术部成立之初,为了适应用户数量的快速增长和业务的不断变化迭代,在选择数据库的时候,经过调研比较,选择了MongoDB。是的,你没有看错,一切尽在MongoDB!全文提纲:1.为什么要用MongoDB(我们在选择数据的时候怎么考虑?)2.MongoDB架构(99.99%高可用,晚上睡觉!)3.MongoDB分片(海量数据的处理方式!)四、MongoDB文档模型介绍(灵活!灵活!灵活!)1、为什么要用MongoDB?由于我们公司主要从事社交电商的业务,我们对数据库的性能要求有一定的限制,而商品交易是公司的主要利润来源,所以对数据库的高可用有一定的要求。总结一下我们对数据库的要求:安全、稳定、高可用、高性能我们在考虑数据库选型的时候主要考虑什么?数据规模支持并发读写延迟和吞吐量。从数据规模来看,订单和商品SKU,以及会员信息等重要数据记录肯定会随着时间增长,所以我们需要的不仅仅是满足现在的需求,还要考虑海量数据更方便的扩展半年一年!下面就从MongoDB的架构、性能、文档模型入手,介绍一下我们选择MongoDB的理由!2.MongoDB架构2.1以高可用数据库为系统核心,必须保证99.99%的可用性,而高可用的保证来自于MongoDB冗余数据模型的副本集。MongoDB自带多副本高可用,只需要合理配置即可避免单个数据库节点故障导致服务不可用。图例说明:APrimary主节点主要接受来自服务器的读写;两个Secondary从节点用于从Primary同步数据。关于高可用:当主节点出现故障时,两个从节点会进行选举,投票选出新的主节点,从而保证服务的可用性。(PS:选举过程中不能写入数据,但是如果Seconndary节点配置是可读的,那么此时可以读取数据。)这就是MongoDB的高可用,配置简单,不需要引入额外的中间件或插件辅助数据库节点之间的故障转移。2.2关于选举算法《分布式一致性算法---raft》raft协议是一种在leader节点故障或网络分区导致脑裂时如何保证分布式数据一致性的算法。MongoDB就是采用这种算法来保证当主节点出现故障或者网络分区时,数据的一致性。当然,MongoDB使用的算法肯定会与原始版本的Raft略有不同。MongoDB将采用从Secondary拉取数据到Primary的方式,而不是将数据从Primary推送到Secondary,以减轻Primary的压力,以有利于数据库操作的方式提高Raft的使用。.Raft算法动画演示http://thesecretlivesofdata.com/raft/2.3关于超大规模复制集(集群){"_id":,"host":,"arbiterOnly":false,"buildIndexes":true,"hidden":false,"priority":0,//设为0"tags":{},"slaveDelay":NumberLong(0),"votes":0//设为0}MongoDB最多允许50个节点,但最多只有7个节点有投票权,一个节点可以配置7个没有投票权的Non-Voting节点,外加一个Primary节点。为什么只能允许7个投票节点?参考2.2节的筏算法。节点越多,投票时间越长,选举出主节点的时间也越长。这个过程中我们不能进行写操作,因为没有master节点。这么多无投票权的节点有什么用?大家应该都听说过MySQL的读写分离,利用读写分离来提高数据库性能。其实这里也可以使用MongoDB。Primary用于写入,Secondary用于读取。您可以给BI部门一个Secondary,给Finance部门一个Secondary,给Operations部门一个Secondary。2.4WriteConcern由于我们的数据库至少有3个Node(1Primary+2Secondary),Secondary通过同步Primary的数据来保持一致性,那么我们在写操作的时候,如何保证数据安全的放到磁盘上呢?有以下几种情况:1.写入Primary成功返回客户端当写入Primary成功,但是Secondary还没有和Primary同步,Primary挂了,数据丢失!2、写入primary成功,数据同步到secondary成功,返回客户端写入成功。此时Primary挂了,数据不会丢失。但是恰好Primary和同步的Secondary同时挂了,数据就丢了!3.primary写入成功,数据同步到两个Secondary成功,客户端写入成功。此时Primary挂了,数据不会丢失。我们分析以上三种情况:第一种情况,存在数据丢失的风险。在第二种情况下,数据仍然会丢失,但是数据丢失的概率大大降低了。第三种情况是最安全的方式,但是节点数量太多,同步非常耗时,用户需要等待的时间太长,所以一般不考虑。MongoDB在这里推荐的折衷方案是使用WriteConcern---数据可靠性和效率之间的权衡!db.products.insert({item:"envelopes",qty:100,type:"Clasp"},{writeConcern:{w:"majority",wtimeout:5000}}//设置writeConcern为多数,超时为5000毫秒)3.MongoDB分片3.1海量数据如何影响数据库效率?数据库的性能还与数据库本身的规模密切相关。以关系型数据库为例:百万级表和千万级甚至上亿级的表查询效率相差很大,查询性能急剧下降。在插入时创建索引可能会导致索引树调整和页面拆分。3.2面对海量数据如何提高数据读写效率?为了提高数据库在海量数据中的效率,我们采用分而治之的思想,将大表拆分成小表,将大数据库拆分成小数据库。在关系型数据库中,我们经常通过分表分库来解决问题:比如订单数据库分为在线数据库和离线数据库。将近三个月的线上库放到了线下库中,线上库的数据大大减少。减少了,数据库性能得到了提高。再比如当我们的用户数超过千万条记录时,单表查询效率下降,我们将一个用户表拆分为多个用户表,这就是水平拆分。我们如何在MongoDB中做到这一点?3.3MongoDBShardingMongoDB的分片是通过将同一个集合(Collection1)的数据按照shardkeys分成不同的分片(shardkeys)来减少同一个数据文件上的数据量,达到了分片数据规模的目的。Shard优点:在线扩展,动态扩展Shard:用于存储实际的数据块。在实际生产环境中,一个分片服务器的角色可以由多个机器组和一个副本集来承担,以防止主机的单点故障。ConfigServer:配置服务器mongod实例,存放整个集群的元数据和配置,包括chunk信息。在MongoDB3.4中,配置服务器必须部署为副本集。Mongos:mongos充当查询路由器,提供客户端应用程序和分片集群之间的接口。服务器插入的数据通过mongos路由到具体的地址。这也是MongoDB的方便之处。不需要关注路由,也不需要使用第三方中间件辅助路由。这是可靠和放心的。Shardedloadbalancing当我们的MongoDB副本集变成一个shardedcluster时,随着数据量的增长,每个shard会越来越大。这里会出现两种情况:1.冷热数据,一些分片中的数据量过大。2.数据总量大,分片集群碎片太大。当问题(1)出现时,MongoDB的负载均衡器(Balancer)会自动将大分片中的数据迁移到小分片中。请注意,这并不意味着我们可以高枕无忧。相反,我们应该反思一下,是不是因为我们的shardkey选错了导致数据不统一!因为shards的迁移也很耗性能,应用服务器写一次ShardB,然后ShardBB重写到ShardC,无形中,数据写了两次,这是极大的浪费!当问题(2)出现时,当然要在toolargeshardcollection中添加新的shard来分担Shard集群压力。注意:MongoDB分片虽然上线了,但是对正常读写操作的性能会有一定的影响。建议在非繁忙时间部署分片!4.MongoDB文档模型介绍数据库建模的挑战在于平衡应用的需求适合于数据库引擎的结构和数据的检索方式。当我们设计数据模型时,我们需要考虑应用程序将如何使用数据(查询、更新和数据处理)以及数据本身的结构。4.1FlexibleSchema在关系数据库中,数据必须按照一定的表结构插入。但是由于MongoDB是一个文档数据库,所以在插入数据的时候默认是不需要的。它的表现是同一个集合中的不同文档不一定需要有相同的字段,字段类型也可以不同。要更改集合中文档的结构,例如添加字段、删除字段或更改字段类型,只需要更新文档即可。4.2范例1:N模型设计在电子商务业务中,一个用户可能有多个收件人和收件人地址。在关系数据库中,我们需要创建联系人表、地址表,并将它们关联起来。但是在MongoDB中,我们只需要一个集合就可以完成这项工作!数据关系如下://patrondocument{_id:"joe",name:"JoeBookreader"}//addressdocuments{patron_id:"joe",//referencetopatrondocumentstreet:"123FakeStreet",city:"Faketon",state:"MA",zip:"12345"}{patron_id:"joe",street:"1SomeOtherStreet",city:"Boston",state:"MA",zip:"12345"}在MongoDB中,我们可以这样设计:{"_id":"joe","name":"JoeBookreader","addresses":[{"street":"123FakeStreet","city":"Faketon","state":"MA","zip":"12345"},{"street":"1SomeOtherStreet","city":"Boston","state":"MA","zip":"12345"}]}没错,上面是文档(document)收藏中,是不是感觉很灵活方便!可以在SKU集合中添加分类信息或者产品标签,也可以在库存集合中添加冗余的SKU基础信息,还可以在订单集合中添加冗余的部分订购者信息……没错,就是这样如此灵活!这也是我们选择MongoDB的重要原因之一。大大减轻了开发者的精神负担。您无需成为SQL高手。您还可以在MongoDB中编写性能优异的查询语句。重建火葬场并非闻所未闻,因为过多的冗余最终会导致数据过载、性能下降等各种问题。要控制开发人员的多余冲动,也有赖于团队技术LeaderGatekeeping。综上所述,互联网业务不是一成不变的,产品、用户、市场的需求都在不断变化!我们不具备构建适应灵活多变业务的中台平台的技术实力,但目前可以选择可靠、强大、灵活的数据库——MongoDB!作者:唐银鹏开源爱好者,Gopher。从事电子商务和IM系统的深入研究和开发,MongoDB爱好者,公众号《从菜鸟到大佬》作者。本文转载自微信?「Mongoing中文社区」,可通过以下二维码关注。转载本文请联系Mongoing中文社区公众号。