sharding



说明:本次探讨多从应用角度进行来剖析mongodb sharding的应用,较少提及运维层面的mongodb sharding部署,有兴趣的同事可以到网上查询资料进行学习。

问题准备:

什么是sharding?
为什么要用sharding?
什么场景下会用sharding?

目录:
1、sharding相关理论(概念)
2、sharding应用场景(重要)
3、sharding实战第一步:小红花业务分析(重要)  -- 开发阶段
4、sharding实战第二步:小红花分片方案实施及单元测试 -- 开发阶段
5、sharding实战第三步:小红花分片 集成测试 -- 开发阶段
6、sharding实战第四步:小红花分片 交付测试
7、sharding实战第五步:小红花分片 持续跟踪生产环境及修正完善
8、最佳实践
9、扩展阅读
10、作业与练习



1、sharding相关理论/概念
遇到的问题/数据处理瓶颈(Data Processing Bottleneck):所有在Web中获得成功的公司都面临着一个问题:如何访问这些存储在庞大数据库中的数据。他们发现单个数据库每秒只能处理这么多的查询,而网络和磁盘驱动器每秒也只能从服务器上获得或发送这么多的数据量。提供基于Web的服务的公司很快发现,他们的需求已经超出单个服务器、网络或驱动阵列的最大性能。在这种情况下,他们被迫将庞大的数据分割并分散开。常见的解决方案是将这些庞大的数据块分割成小块数据,以便于通过更可靠和快速的方式进行管理。

分布式数据库( Distributed DataBase,DDB :是指利用高速计算机网络将物理上分散的多个数据存储单元连接起来组成一个逻辑上统一的数据库。分布式数据库的基本思想是将原来集中式数据库中的数据分散存储到多个通过网络连接的数据存储节点上,以获取更大的存储容量和更高的并发访问量。

数据库分区(Partitions of Database):是一种物理数据库设计技术,DBA和数据库建模人员对其相当熟悉。虽然分区技术可以实现很多效果,但其主要目的是为了在特定的SQL操作中减少数据读写的总量以缩减响应时间。分区主要有两种形式:垂直分区和水平分区。

垂直分区(

Vertical Partitioning)在数据库的传统视图中,数据按行和列的方式存储。垂直分区的实现方式为:拆分列边界上的记录,并将各个部分存储在不同的表或集合中。可以认为,关系数据库(如MySQL)通过按照一比一的关系使用连接表构成的是本地垂直数据分区。


水平分区(

Horizontal Partitioning:这种形式分区是对表的行进行分区,通过这样的方式不同分组里面的物理列分割的数据集得以组合,从而进行个体分割(单分区)或集体分割(1个或多个分区)。所有在表中定义的列在每个数据集中都能找到,所以表的特性依然得以保持。使用mongodb进行数据的拆分存放,水平分区是唯一可采用的方式,而分片就是水平分区的通用术语。通过分片,可以将集合分割到多个服务器,从而改善包含大量文档(集合)的性能。


分片系统: mongodb分片系统的重要特性包括:
1:具有平均将数据分散到所有分片中的能力;
2:以容错的方式存储分片数据的能力;
3:在系统运行时具备添加或删除分片的能力。

生产环境中常用的mongoDB分片系统的组成方式:
前端代理服务器( haproxy/LVS/Nginx ) + 三 个以上的 mongos + 三 个以上的 配置服务器 + 三个以上的片(每个片采用复制集架构搭建【至少三个mongod实例,一主二从】)。 
需要前端代理服务器的原因是: MongoDB分片集群的入口mongos自身没有自动的容错功能(auto-failover)和自动恢复的(auto-recovery)机制。官方建议是将mongos和应用服务器部署在一起,多个应用服务器就要部署多个 mongos实例,这样很是不方便。还可以使用LVS或者HAProxy来实现多个mongos的failover机制。


分片(sharding) :是指将数据拆分,将其分散存放在不同的机器上的过程。
在生产环境下,创建分片一般 两种可能性:
从零开始建立集群: 可先初始化一个空的副本集进行分片的创建。
已经有一个使用中的副本集:该副本集会成为第一个分片。为了将副本集转换为分片,需告知mongos副本集名称和副本集成员列表。
分片的目标之一是创建一个拥有5台、10台甚至1000台机器的集群,整个集群对应用程序来说就像是一台单机服务器。

mongos :数据库集群请求的入口,所有的请求都通过mongos进行协调,不需要在应用程序添加一个路由选择器,mongos自己就是一个请求分发中心,它负责把对应的数据请求转发到对应的shard服务器上。在生产环境通常有多mongos作为请求的入口,防止其中一个挂掉所有的mongodb请求都没有办法操作。

配置服务器 :存储所有数据库元信息(路由、分片)的配置。mongos本身没有物理存储分片服务器和数据路由信息,只是缓存在内存里,配置服务器则实际存储这些数据。mongos第一次启动或者关掉重启就会从 config server 加载配置信息,以后如果配置服务器信息变化会通知到所有的 mongos 更新自己的状态,这样 mongos 就能继续准确路由。在生产环境通常有多个 config server 配置服务器,因为它存储了分片路由的元数据,这个可不能丢失!

 (shard) :一个片,一个单一的mongod实例或者以复制集方式存在。
主分片(primary shared):是为每个数据库随机选择的,(在某种特定场景下,如批量用命令从非sharding导入sharding的时候,)所有数据都会位于主分片上。
主分片与副本集中的主节点不同。主分片指的是组成分片的整个副本集。而副本集中的主节点是指副本集中能够处理写请求的单台服务器。

片键 (shard key): 片键是集合的一个键,MongoDB根据这个键拆分数据。 选择片键可以认为是选择集合中数据的顺序。它与索引是个相似的概念:随着集合的不断增长,片键就会成为集合上最重要的索引。只有被索引过的键才能够作为片键。 包含片键的查询能够直接被发送到目标分片或者是集群分片的一个子集,这样的查询叫做定向查询( targeted query)。有些查询必须被发送到所有分片(如count),这样的查询叫做分散-聚集查询(scatter-gather query):mongos将查询分散到所有分片上,然后将各个分片的查询结果聚集起来。

块(Chunk) 分片系统将使用分片键将数据映射到块,块是文档键的逻辑连续范围。每个块标志着分片键值特定连续范围内的许多文档;这些值使mongos控制器可以快速找到包含它所需文档的块。然后MongoDB分片系统将把块存储在可用的分片系统中。配置服务器将记录每个块存储的分片服务器位置。这是分片实现的一个重要特性,因此通过它可以从集合中添加和删除分片。而不需要备份和恢复数据。块默认大小:64MB。生产环境中,也可以通过命令手动更改块的大小。

ObjectId 使用12 字节的存储空间,每个字节两位十六进制数字,是一个24 位的字符串。

前4 个字节是从标准纪元开始的时间戳,单位为秒。这会带来一些有用的属性。时间戳,与随后的. 5 个字节组合起来,提供了秒级别的唯一性。
由于时间戳在前,这意味着ObjectId 大致会按照插入的顺序排列。这对于某些方面很有用,如将其作为索引提高效率,但是这个是没有保证的,仅仅是“大致”。  这4 个字节也隐含了文档创建的时间。绝大多数驱动都会公开一个方法从ObjectId 获取这个信息。  因为使用的是当前时间,很多用户担心要对服务器进行时间同步。其实没有这个必要,因为时间戳的实际值并不重要,只要其总是不停增加就好了(每秒一次)。 
接下来的3 字节是所在主机的唯一标识符。通常是机器主机名的散列值。这样就可以确保不同主机生成不同的ObjectId,不产生冲突。
为了确保在同一台机器上并发的多个进程产生的ObjectId 是唯一的,接下来的两字节来自产生ObjectId 的进程标识符(PID)。
前9 字节保证了同一秒钟不同机器不同进程产生的ObjectId 是唯一的。后3 字节就是一个自动增加的计数器,确保相同进程同一秒产生的ObjectId 也是不一样的。同一秒钟最多允许每个进程拥有2563(16 777 216)个不同的ObjectId。

均衡器(balancer) 负责数据的迁移。它会周期性地检查分片间是否存在不均衡,如果存在,则会开始块的迁移。虽然均衡器通常被看做是单一实体,但每个mongos有时也会扮演均衡器的角色。每隔几秒钟,mongos就会尝试变身为均衡器。如果没有其他可用的均衡器,mongos就会对整个集群加锁,以防止配置服务器对集群修改,然后做一次均衡。均衡并不会影响mongos的正常路由操作,所以使用mongos的客户端不会受到影响。
 
2、sharding应用场景(重要)
何时分片
决定何时分片是一个值得权衡的问题。通常不必太早分片,因为分片不仅会增加部署的操作复杂度,还要求作出设计决策,而该决策以后很难再改。另外最好也不要在系统运行太久之后再分片,因为在一个过载的系统上不停机进行分片是非常困难的。
通常,分片用来:
增加可用RAM;
增加可用磁盘空间;
减轻单台服务器的负载;
处理单个mongod无法承受的吞吐量;
因此,良好的监控对应决定应何时分片是十分重要的,不需认真对待其中每一项。由于人们往往过于关注改进其中一个指标,所以应弄明白到底哪一项指标对自己的部署最为重要,并提前做好何时分片以及如何分片的计划。

与米学的业务结合及特点:
凡是涉及到业务明细表/详情表的地方,都需要考虑是否需要用sharding的问题:
如小红花表:基本每日都是几十万的数据增量。总的数据量在1000w+以上。未来的某段时间内,数据量也会呈指数级的增长。
如积分详情表。生产环境上当前数据量:1000w+
如点赞表。
生产环境上当前数据量:
2000w+

3、sharding实战第一步:小红花业务分析(重要)
3.1、web 教师端   - 过程性评价 - 教师根据班级和评价点查看该班级下学生在一段时间范围内(以一周时间为单位)的小红花列表
com.mexue.action.controller.evaluate.teacher.EvaluateGrowthPointerController.topoint
mexue2_2.evaluate_growth_content query: { date: { $gte: "2016-01-11", $lte: "2016-01-17" }, pointId: "5641ac820cf2dfbea02d628c", studentId: { $in: [ "564fb25f0cf21beb00d5fe6e", "564f12a40cf2a657b32518a1", "564fedda0cf21beb00d61d20", "565431c90cf2c904cb582e4f", "5653bf0d0cf273a1fb30cb75", "564ee61e0cf2a657b324ec99", "564ee7510cf2eb1d22ee6286", "564f28120cf21beb00d5f146", "5653ef150cf224b590d70d3c", "5652ccf40cf219d52472015b", "5654020f0cf273a1fb30ee6a", "564fbd8e0cf21beb00d602e0", "5652d5d80cf224b590d6a797", "56514ab20cf20b9363d0bc6b", "564ee9be0cf2a657b324f02e", "56543bc10cf2c904cb58377a", "5654408c0cf2dafd1d017ae5", "56503f870cf2f4fdfd7da578", "5651c0080cf20b9363d0f84f", "564ecab20cf2a657b324e1d3", "5656e4920cf2044f7e4ee114", "564fc6ee0cf2f4fdfd7d64e9", "565022b10cf2f4fdfd7d9604", "564eeaff0cf2eb1d22ee65fa", "565056f10cf21beb00d65607", "56503f120cf21beb00d646e3", "564fc46a0cf21beb00d60620", "564f096c0cf2eb1d22ee844f", "5652b3870cf219d52471f903", "565191fb0cf2624ae16c5957", "564ee7790cf2eb1d22ee62b1", "564fc9a10cf2f4fdfd7d6687", "565054170cf21beb00d65421", "5651b03b0cf2624ae16c6d8a", "564fad790cf2f4fdfd7d5a30", "565434c10cf2c904cb583093", "56504ef80cf2f4fdfd7daf1d", "564ee98f0cf2a657b324f003" ] } }

3.2、web教师端 - 过程性评价 - 教师单个/批量发放小红花
com.mexue.action.controller.evaluate.teacher.EvaluateGrowthPointerController.addContent
mexue2_2.$cmd command: insert: "evaluate_growth_content" [ { _id: ObjectId('568360170cf22522f341a9bb'), termId: "5641a9640cf2fe24019e8bd4", pointId: "5641ad480cf2dfbea02d6517", name: "语文作业", propertyType: 1, content: "又是满分,加油", enabled: 1, date: "2015-12-30", studentId: "564c10fd0cf24a16995ccd62", userId: "5641a98c0cf238217eb966f9", createTime: "2015-12-30 12:39:50" } ]

insert:evaluate_teacher_history evaluate_student_growth_cache evaluate_process_point

3.3、app家长端 - 我的 - 是否有未读的小红花
com.mexue.action.controller.mobile.EvaluateProcessController.hasProcessInfo (m=hasProcessInfo)
mexue2_2.evaluate_student_growth_cache query: { _id: ObjectId('56650ea00cf26ac83e83cbfe') }

3.4、app家长端 - 我的 - 小红花 - 当前学期下所有红花统计列表
com.mexue.action.controller.mobile.EvaluateProcessController.parentProcessList
mexue2_2.evaluate_process_point query: { _id: ObjectId('564eb07a0cf27467d746e238#5641ac810cf2dfbea02d627a') }   studentId#pointId
mexue2_2.evaluate_student_growth_cache query: { _id: ObjectId('56650ea00cf26ac83e83cbfe') }

3.5、app家长端 - 我的 - 小红花 - 所有(评价点)红花统计列表 - 单个评价点下获得的小红花历史列表
com.mexue.action.controller.mobile.EvaluateProcessController.parentProcessInfoList (m=parentProcessInfoList)
mexue2_2.evaluate_growth_content query: { $query: { enabled: 1, termId: "564b1c4b0cf203df085136bc", pointId: "566952d10cf2c5c82f6ed2c6", studentId: "566aa58e0cf2eded918e168e", propertyType: -1 }, $orderby: { createTime: -1 }}

3.6、app家长端 - 我的 - 成长档案 - 学业水平 - 红花墙
com.mexue.action.controller.growth.GrowthAPIController.getFlowers
mexue2_2.evaluate_process_point query: { $query:{ "_id" : { "$in" : [ "56c2d5e60cf2f5317b7c9942#56c194fc0cf2df4ad994d30d" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d347" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d34c" , "56c2d5e60cf2f5317b7c9942#56c194ff0cf2df4ad994d358" , "56c2d5e60cf2f5317b7c9942#56c194fd0cf2df4ad994d32d" , "56c2d5e60cf2f5317b7c9942#56c195000cf2df4ad994d35c" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d342" , "56c2d5e60cf2f5317b7c9942#56c194fc0cf2df4ad994d31d" , "56c2d5e60cf2f5317b7c9942#56c194fc0cf2df4ad994d315" , "56c2d5e60cf2f5317b7c9942#56c194fd0cf2df4ad994d325" , "56c2d5e60cf2f5317b7c9942#56c195010cf2df4ad994d369" , "56c2d5e60cf2f5317b7c9942#56c194fd0cf2df4ad994d335" , "56c2d5e60cf2f5317b7c9942#56c195000cf2df4ad994d365" , "56c2d5e60cf2f5317b7c9942#56c195000cf2df4ad994d360" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d33d"]}}

{ "studentId" : { "$in" : [ "56cab6b00cf20eed0e8b6200"]} , "pointId" : { "$in" : [ "5635beeb0cf2dfbea02a1021" , "5635beea0cf2dfbea02a1018" , "5635bee80cf2dfbea02a0fc0" , "5635beea0cf2dfbea02a1014" , "5635bee90cf2dfbea02a0fe2" , "5635bee90cf2dfbea02a0fd1" , "5635beea0cf2dfbea02a1010" , "5635beea0cf2dfbea02a1003" , "5635bee90cf2dfbea02a0ff3" , "5635bee90cf2dfbea02a0fd9" , "5635beea0cf2dfbea02a0ffe" , "5635bee90cf2dfbea02a0fc8" , "5635bee90cf2dfbea02a0ff8" , "5635beea0cf2dfbea02a101d" , "5635bee90cf2dfbea02a0fea"]}}

3.7、app教师端 - 我的 - 发小红花 - 当前班级下,我发过的小红花/问题统计列表
com.mexue.action.controller.mobile.EvaluateProcessController.findEvaluatePoints (m=processCommentList)
mexue2_2.evaluate_process_point query: { $query:{ "_id" : { "$in" : [ "56c2d5e60cf2f5317b7c9942#56c194fc0cf2df4ad994d30d" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d347" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d34c" , "56c2d5e60cf2f5317b7c9942#56c194ff0cf2df4ad994d358" , "56c2d5e60cf2f5317b7c9942#56c194fd0cf2df4ad994d32d" , "56c2d5e60cf2f5317b7c9942#56c195000cf2df4ad994d35c" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d342" , "56c2d5e60cf2f5317b7c9942#56c194fc0cf2df4ad994d31d" , "56c2d5e60cf2f5317b7c9942#56c194fc0cf2df4ad994d315" , "56c2d5e60cf2f5317b7c9942#56c194fd0cf2df4ad994d325" , "56c2d5e60cf2f5317b7c9942#56c195010cf2df4ad994d369" , "56c2d5e60cf2f5317b7c9942#56c194fd0cf2df4ad994d335" , "56c2d5e60cf2f5317b7c9942#56c195000cf2df4ad994d365" , "56c2d5e60cf2f5317b7c9942#56c195000cf2df4ad994d360" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d33d"]}}

{ "studentId" : { "$in" : [ "56cab6b00cf20eed0e8b6200"]} , "pointId" : { "$in" : [ "5635beeb0cf2dfbea02a1021" , "5635beea0cf2dfbea02a1018" , "5635bee80cf2dfbea02a0fc0" , "5635beea0cf2dfbea02a1014" , "5635bee90cf2dfbea02a0fe2" , "5635bee90cf2dfbea02a0fd1" , "5635beea0cf2dfbea02a1010" , "5635beea0cf2dfbea02a1003" , "5635bee90cf2dfbea02a0ff3" , "5635bee90cf2dfbea02a0fd9" , "5635beea0cf2dfbea02a0ffe" , "5635bee90cf2dfbea02a0fc8" , "5635bee90cf2dfbea02a0ff8" , "5635beea0cf2dfbea02a101d" , "5635bee90cf2dfbea02a0fea"]}}

3.8、app教师端 - 我的 - 单个/批量发放小红花 (同web端功能,同消息界面点击加号发放小红花功能)
com.mexue.action.controller.mobile.EvaluateProcessController.publishProcessExcitate (m=addProcessComment)
insert: "evaluate_growth_content" [ { _id: ObjectId('568360170cf22522f341a9bb'), termId: "5641a9640cf2fe24019e8bd4", pointId: "5641ad480cf2dfbea02d6517", name: "语文作业", propertyType: 1, content: "又是满分,加油", enabled: 1, date: "2015-12-30", studentId: "564c10fd0cf24a16995ccd62", userId: "5641a98c0cf238217eb966f9", createTime: "2015-12-30 12:39:50" } ]

insert:evaluate_teacher_history evaluate_student_growth_cache evaluate_process_point

3.9、app教师端 - 我的 - 单个/批量发放小红花 - 查看教师发放的小红花历史记录
com.mexue.action.controller.mobile.EvaluateProcessController.teacherProcessInfoList (m=teacherProcessInfoList)
mexue2_2.$cmd command: count { count: "evaluate_teacher_history", query: { termId: "564977ad0cf2c40ed46863cd", teacherId: "566be1b20cf2fe12b63b1092", date: "2016-01-25" } }

mexue2_2.evaluate_teacher_history query: { $query: { termId: "5630c0580cf2f6d1fc284159", teacherId: "5675f4010cf2e93636933785", date: "2016-01-25" }, $orderby: { createTime: -1 } }

3.10、app教师端 - 我的 - 单个/批量发放小红花 - 根据日期获取教师发布小红花的历史记录
com.mexue.action.controller.mobile.EvaluateProcessController.teacherHistory  (m=calendarAbstract)
mexue2_2.evaluate_teacher_history query: { $query: { termId: "55dd6bea0cf25ba63d83d29d", teacherId: "5678bcbc0cf223517d13937c", date: { $gte: "2016-01-15", $lte: "2016-01-25" } }, $orderby: { createTime: -1 } }

3.11、app教师端(2.5版本之前用到,不包含2.5版本) - 我的 - 日常表现 - 某班级下学生列表 - 根据学生ID,教师ID和学期ID获取其当前学期下获取的所有小红花列表
com.mexue.action.controller.mobile.EvaluateProcessController. showEvaluateByTeacher (m=processCommentDetail)
mexue2_2.evaluate_process_point query: { studentId: "56c181730cf2908a7d905818", userId: "56c181570cf2908a7d905816", termId: "56c18d990cf2908a7d9058e8" }

3.12、web家长端 - 成长档案 - 成长档案打印 - 红花墙
com.mexue.action.controller.mobile.PrintRecordController.getFlowers
mexue2_2.evaluate_process_point query: { $query:{ "_id" : { "$in" : [ "56c2d5e60cf2f5317b7c9942#56c194fc0cf2df4ad994d30d" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d347" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d34c" , "56c2d5e60cf2f5317b7c9942#56c194ff0cf2df4ad994d358" , "56c2d5e60cf2f5317b7c9942#56c194fd0cf2df4ad994d32d" , "56c2d5e60cf2f5317b7c9942#56c195000cf2df4ad994d35c" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d342" , "56c2d5e60cf2f5317b7c9942#56c194fc0cf2df4ad994d31d" , "56c2d5e60cf2f5317b7c9942#56c194fc0cf2df4ad994d315" , "56c2d5e60cf2f5317b7c9942#56c194fd0cf2df4ad994d325" , "56c2d5e60cf2f5317b7c9942#56c195010cf2df4ad994d369" , "56c2d5e60cf2f5317b7c9942#56c194fd0cf2df4ad994d335" , "56c2d5e60cf2f5317b7c9942#56c195000cf2df4ad994d365" , "56c2d5e60cf2f5317b7c9942#56c195000cf2df4ad994d360" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d33d"]}}

{ "studentId" : { "$in" : [ "56cab6b00cf20eed0e8b6200"]} , "pointId" : { "$in" : [ "5635beeb0cf2dfbea02a1021" , "5635beea0cf2dfbea02a1018" , "5635bee80cf2dfbea02a0fc0" , "5635beea0cf2dfbea02a1014" , "5635bee90cf2dfbea02a0fe2" , "5635bee90cf2dfbea02a0fd1" , "5635beea0cf2dfbea02a1010" , "5635beea0cf2dfbea02a1003" , "5635bee90cf2dfbea02a0ff3" , "5635bee90cf2dfbea02a0fd9" , "5635beea0cf2dfbea02a0ffe" , "5635bee90cf2dfbea02a0fc8" , "5635bee90cf2dfbea02a0ff8" , "5635beea0cf2dfbea02a101d" , "5635bee90cf2dfbea02a0fea"]}}


涉及表说明:
evaluate_growth_content  过程性评价激励内容(小红花/问题)实体类定义 (数据总量:419万
当前索引:
pointId
studentId
userId + termId


evaluate_teacher_history  教师发布小红花的历史记录 数据总量:86万
当前索引:
termId
pointId
teacherId

evaluate_student_growth_cache 学生获得小红花的历史记录  数据总量:26万
当前索引:

evaluate_process_point 小红花评价点关系表(小红花汇总表)
当前索引:

实体类属性定义:
@Document(collection = "evaluate_growth_content")
public class EvaluateGrowthContent{
    @Id
    private String id;//ID标识()
    private String termId; //学期ID
    private String pointId;//评价点ID标识
    private String name; //小红花名称
    private int propertyType;      //属性值(1: -1:)
    private String content;//评价内容
    private int enabled;//是否可用(1:可用【默认】 0:不可用)
    private String date;//评价日期(yyyy-MM-dd)
    private String studentId;//学生ID标识
    private String userId;

    private String createTime;//创建时间
    private String modifyTime;//修改时间

@Document(collection = "evaluate_process_point")
public class EvaluateProcessPoint {
    @Id
    private String id;//studentId#pointId
    private int flowerNum;//小红花数量
    private int issueNum;//提醒数量
    private String createTime;//创建时间
    private String pointName; //评价点名称(冗余字段,id中的pointId中可推导)
    private String templateId;//手册Id(冗余字段,id中的pointId中可推导)
    private String studentId;//学生Id(冗余字段,id中的studentId中可推导)
    private String pointId;//评价点Id标识(冗余字段,id中的pointId中可推导)
    private String schoolId;//学校Id(冗余字段,id中的studentId中可推导)
    private String classId;//班级Id(冗余字段,id中的studentId中可推导)
    private String userId;//老师Id(冗余字段,id中的pointId中可推导)
    private String termId;//学期Id(冗余字段,id中的pointId中可推导)

@Document(collection = "evaluate_student_growth_cache")
public class EvaluateStudentGrowthCache{
    @Id
    private String studentId;//ID标识()
    private String classId; //班级ID
    private String termId; //学期ID
    private Set<String> pointIds;
    private int status; // 状态(0:未更新;1-有更新)

    private String createTime;//创建时间
    private String modifyTime;//修改时间

@Document(collection = "evaluate_teacher_history")
public class EvaluateTeacherHistory{
    @Id
    private String id;//ID标识()
    private String termId; //学期ID
    private String pointId;//评价点ID标识
    private String teacherId;
    private String classId;
    private String className;
    private String studentIds;//学生ID标识(逗号分隔)
    private String studentNames;//学生姓名(逗号分隔)
    private String name; //小红花名称
    private int propertyType;      //属性值(1: -1:)
    private String content;//评价内容
    private String date;//评价日期(yyyy-MM-dd)

    private String createTime;//创建时间
    private String modifyTime;//修改时间

4、sharding实战第二步:小红花分片方案实施及单元测试
4.1、确定片键
studentId + pointId
原因:不同学校的学生( studentId)是在不同的时间段内注册的,这样可以将学生数据尽量分散在不同的片中,即使同一个学校的学生也有可能会在不同的片中。

之前片键确定的时候曾走过弯路:
之前的片键选择曾用过: userId + termId。 原因是当时小红花业务这块量比较大。小红花汇总表启用了好几次没有启用上导致错误判断。

教师历史记录表从: evaluate_teacher_history 查询。
学生获取的小红花记录从:evaluate_student_growth_cache  查询。
涉及按学期进行查询汇总数据的从:evaluate_process_point   查询。

4.2、现有程序代码查询顺序,逻辑调整
原则:关于sharding表的查询,需要向片键上靠。

4.3、添加sharding分库开关及mongoTemplate操作类代码
environment.properties
#with sharding
ifWriteDataToParentDB=true     //是否向父库(当前指的是评价库)中写入数据【默认true】
ifUseShardingDB=true              //是否启用沙丁 true:使用 false:不使用 还是使用原来的查询评价方式【默认false】

com.mexue.persistence.db. EvaluateShardingDBOperations
com.mexue.persistence.db.
EvaluateShardingDBOperationsImpl


4.4、spring-mongo配置信息
#定义mongo sharding对象,对应的是mongodb官方jar包中的Mongo,ip地址和端口
evaluate.sharding.host=101.201.176.184
evaluate.sharding.port=30000
evaluate.sharding.connections-per-host=10
evaluate.sharding.threads-allowed-to-block-for-connection-multiplier=1
evaluate.sharding.connect-timeout=1000
evaluate.sharding.max-wait-time=1500
evaluate.sharding.socket-keep-alive=true
evaluate.sharding.socket-timeout=1500
evaluate.sharding.dbname=mexueEvaluate
evaluate.sharding.mongo-ref=mongo_evaluate_sharding


当前mongodb-java-driver 3.2.2版本,只提供两种mongodb连接方式:
1、ip地址 + 端口号;
2、复制集方式(多IP:PORT连接方式);

没有单独的sharding连接方式。 因为即可以单独连接一个mongos( 当前采用方式,或者做个高可用的前端代理[之前采用方式,同时也是当前能查询到的大多数生产环境采用的方式]),也可以把复制集中的多个IP:PORT设置成多个mongos形式。如下:


4.5、单元测试
测试小红花业务相关功能是否正常。

5、sharding实战第三步:小红花分片集成测试
5.1、读是否分散到正确的片中
方法:使用 db.evaluate_growth_content.find ( {"studentId" : "5678e9e80cf2f26154733e54","pointId" : "563acf500cf253f1f8fd2baa" } ).explain()命令即可看到查询的数据集来自于哪个片,如下图:


5.2、写操作是否 分散到正确的片中
从片2或片3中任意取几条studentId数据,然后用程序批量插入,然后再用5.1中描述的方法查看即可。

5.3、压力测试,批量读看响应时间是否正确,批量写是否可以写到对应的块中?
当前并发1000进行数据同时插入,查询暂未发现问题,其实这里需要更多的压测数据,如:
压测小红花涉及接口
建议:
1.在500万,1000万,2000万,5000万数据量下,小红花涉及接口的表现数据报告
2.在插入大量数据的时候,数据是否直接插入到了对应的分片上测试
(从3个分片上分别抽取不同的studentId,然后批量插入数据后看每个分片上数据块的大小及数据分布情况。这个需要运维配合

5.4、在块迁移过程中,批量写入数据是否正确?当要插入的数据恰恰在要迁移的块中会怎么样呢?
正确。不影响。

6、sharding实战第四步:小红花分片交付测试
交付测试建议(场景):
6.1、测试小红花业务是否可用
按常规测试用例测试小红花业务功能正常即可。

6.2、测试sharding开关是否可用
#with sharding
ifWriteDataToParentDB=true     //是否向父库(当前指的是评价库)中写入数据【默认true】
ifUseShardingDB=true              //是否启用沙丁 true:使用 false:不使用 还是使用原来的查询评价方式【默认false】

写数据:
6.2.1、ifWriteDataToParentDB=true && ifUseShardingDB=true  评价库小红花表数据正确,sharding库小红花表数据正确[该配置会在生产上用一段时间(2周/1月)]
6.2.2、ifWriteDataToParentDB=true && ifUseShardingDB=false评价库小红花表数据正确,sharding库小红花表数据正确(不插入数据)
6.2.3、ifWriteDataToParentDB=false&& ifUseShardingDB=true  评价库小红花表数据正确(不插入数据),sharding库小红花表数据正确[将来生产上要用的配置]
6.2.4、ifWriteDataToParentDB=false&& ifUseShardingDB=false评价库小红花表数据正确,sharding库小红花表数据正确(不插入数据)  [兜底,容错配置。同1.2]

读数据:
界面上查询红花数据正常即可。

6.3、测试sharding读取速度如何
压测小红花涉及接口。详细请见之前邮件中说明,在此不再赘述。
测试建议及产出:
在500万,1000万,2000万,5000万数据量下,小红花涉及接口的表现数据报告。

另在插入大量数据的时候,数据是否直接插入到了对应的分片上测试(从3个分片上分别抽取不同的studentId,然后批量插入数据后看每个分片上数据块的大小及数据分布情况。这个需要运维配合@徐岩岩)。

6.4、sharding环境被破坏后小红花业务的表现
当前preonline上sharding架构如下:
3片(单片采用复制集架构),3配置(可以做复制集,未来生产上不打算做复制集,具体原因希望@徐岩岩补充),3mongs(当前方案是会用阿里slb做高可用)

6.4.1、如果前段代理down了怎么办?
6.4.2、单mongs down了,双mongos down,3mongs down 场景如何。
6.4.3、单config down,双config down,3config down 场景如何。
6.4.4、分片1,2,3中的复制及下,1,2,3mongd进程 down了场景如何。


7、sharding实战第五步:小红花分片持续跟踪生产环境及修正完善
7.1、线上评价数据库中小红花详情表与sharding详情表中数据是否能对应上?
上线第一天对不上,后续持续跟踪后可以对应上。

模板:
今日对评价小红花sharding运行情况进行跟踪,结果如下:

综述:截止到2016-04-10上午8点,sharding库和评价库小红花数据正确。皆为:7598366条。

语句如下:
 
{    "createTime" : {$lte : "2016-04-10 08:00:00"    }}

评价:
7598366
沙丁:
7598366

数据正确


7.2、原sharding小红花查询代码中没有设置从片的“从库”中读取数据,已经修正。
com.mexue.framework.config. ConfigListener
public void contextInitialized(ServletContextEvent contextEvent) {
    ...
     MongoTemplate mongoTemplateEvaluateSharding = wac.getBean("mongoTemplateEvaluateSharding", MongoTemplate.class);
    mongoTemplateEvaluateSharding.getDb().setReadPreference(ReadPreference.secondaryPreferred());
}


7.3、持续监控;

8、最佳实践
8.1、如果想彻底了解和学习MongoDB,就必须硬啃它的官方文档。
8.2、在需求分析阶段进行架构设计:该业务涉及的表(集合)将来是否需要考虑分片。
8.3、随着不断增加分片数量,系统性能大致会呈线性增长。但是,如果从一个未分片的系统转换为只有几个分片的系统,性能通常会有所下降。由于迁移数据、维护元数据、路由等开销,少量分片的系统与未分片的系统相比,通常延迟更大,吞吐量甚至可能会更小。因此,至少应该创建3个或以上的分片。
8.4、永远不要直接连接到配置服务器,已防配置服务器数据被不小心修改或删除。应先连接到mongos,然后通过config数据库来查询相关信息,方法与查询其他数据库一样。如果通过mongos操作配置数据(而不是直接连接到配置服务器),mongos会保证将修改同步到所有配置服务器,也会防止危险操作的发生,如意外删除config数据库等。
8.5、其他,正在学习中。。。欢迎大家一起补充。

9、扩展阅读

10、作业与练习
请针对于preonline上的积分详情表(生产环境1000w+数据量)进行sharding分库开发。


说明:本次探讨多从应用角度进行来剖析mongodb sharding的应用,较少提及运维层面的mongodb sharding部署,有兴趣的同事可以到网上查询资料进行学习。

问题准备:

什么是sharding?
为什么要用sharding?
什么场景下会用sharding?

目录:
1、sharding相关理论(概念)
2、sharding应用场景(重要)
3、sharding实战第一步:小红花业务分析(重要)  -- 开发阶段
4、sharding实战第二步:小红花分片方案实施及单元测试 -- 开发阶段
5、sharding实战第三步:小红花分片 集成测试 -- 开发阶段
6、sharding实战第四步:小红花分片 交付测试
7、sharding实战第五步:小红花分片 持续跟踪生产环境及修正完善
8、最佳实践
9、扩展阅读
10、作业与练习



1、sharding相关理论/概念
遇到的问题/数据处理瓶颈(Data Processing Bottleneck):所有在Web中获得成功的公司都面临着一个问题:如何访问这些存储在庞大数据库中的数据。他们发现单个数据库每秒只能处理这么多的查询,而网络和磁盘驱动器每秒也只能从服务器上获得或发送这么多的数据量。提供基于Web的服务的公司很快发现,他们的需求已经超出单个服务器、网络或驱动阵列的最大性能。在这种情况下,他们被迫将庞大的数据分割并分散开。常见的解决方案是将这些庞大的数据块分割成小块数据,以便于通过更可靠和快速的方式进行管理。

分布式数据库( Distributed DataBase,DDB :是指利用高速计算机网络将物理上分散的多个数据存储单元连接起来组成一个逻辑上统一的数据库。分布式数据库的基本思想是将原来集中式数据库中的数据分散存储到多个通过网络连接的数据存储节点上,以获取更大的存储容量和更高的并发访问量。

数据库分区(Partitions of Database):是一种物理数据库设计技术,DBA和数据库建模人员对其相当熟悉。虽然分区技术可以实现很多效果,但其主要目的是为了在特定的SQL操作中减少数据读写的总量以缩减响应时间。分区主要有两种形式:垂直分区和水平分区。

垂直分区(

Vertical Partitioning)在数据库的传统视图中,数据按行和列的方式存储。垂直分区的实现方式为:拆分列边界上的记录,并将各个部分存储在不同的表或集合中。可以认为,关系数据库(如MySQL)通过按照一比一的关系使用连接表构成的是本地垂直数据分区。


水平分区(

Horizontal Partitioning:这种形式分区是对表的行进行分区,通过这样的方式不同分组里面的物理列分割的数据集得以组合,从而进行个体分割(单分区)或集体分割(1个或多个分区)。所有在表中定义的列在每个数据集中都能找到,所以表的特性依然得以保持。使用mongodb进行数据的拆分存放,水平分区是唯一可采用的方式,而分片就是水平分区的通用术语。通过分片,可以将集合分割到多个服务器,从而改善包含大量文档(集合)的性能。


分片系统: mongodb分片系统的重要特性包括:
1:具有平均将数据分散到所有分片中的能力;
2:以容错的方式存储分片数据的能力;
3:在系统运行时具备添加或删除分片的能力。

生产环境中常用的mongoDB分片系统的组成方式:
前端代理服务器( haproxy/LVS/Nginx ) + 三 个以上的 mongos + 三 个以上的 配置服务器 + 三个以上的片(每个片采用复制集架构搭建【至少三个mongod实例,一主二从】)。 
需要前端代理服务器的原因是: MongoDB分片集群的入口mongos自身没有自动的容错功能(auto-failover)和自动恢复的(auto-recovery)机制。官方建议是将mongos和应用服务器部署在一起,多个应用服务器就要部署多个 mongos实例,这样很是不方便。还可以使用LVS或者HAProxy来实现多个mongos的failover机制。


分片(sharding) :是指将数据拆分,将其分散存放在不同的机器上的过程。
在生产环境下,创建分片一般 两种可能性:
从零开始建立集群: 可先初始化一个空的副本集进行分片的创建。
已经有一个使用中的副本集:该副本集会成为第一个分片。为了将副本集转换为分片,需告知mongos副本集名称和副本集成员列表。
分片的目标之一是创建一个拥有5台、10台甚至1000台机器的集群,整个集群对应用程序来说就像是一台单机服务器。

mongos :数据库集群请求的入口,所有的请求都通过mongos进行协调,不需要在应用程序添加一个路由选择器,mongos自己就是一个请求分发中心,它负责把对应的数据请求转发到对应的shard服务器上。在生产环境通常有多mongos作为请求的入口,防止其中一个挂掉所有的mongodb请求都没有办法操作。

配置服务器 :存储所有数据库元信息(路由、分片)的配置。mongos本身没有物理存储分片服务器和数据路由信息,只是缓存在内存里,配置服务器则实际存储这些数据。mongos第一次启动或者关掉重启就会从 config server 加载配置信息,以后如果配置服务器信息变化会通知到所有的 mongos 更新自己的状态,这样 mongos 就能继续准确路由。在生产环境通常有多个 config server 配置服务器,因为它存储了分片路由的元数据,这个可不能丢失!

 (shard) :一个片,一个单一的mongod实例或者以复制集方式存在。
主分片(primary shared):是为每个数据库随机选择的,(在某种特定场景下,如批量用命令从非sharding导入sharding的时候,)所有数据都会位于主分片上。
主分片与副本集中的主节点不同。主分片指的是组成分片的整个副本集。而副本集中的主节点是指副本集中能够处理写请求的单台服务器。

片键 (shard key): 片键是集合的一个键,MongoDB根据这个键拆分数据。 选择片键可以认为是选择集合中数据的顺序。它与索引是个相似的概念:随着集合的不断增长,片键就会成为集合上最重要的索引。只有被索引过的键才能够作为片键。 包含片键的查询能够直接被发送到目标分片或者是集群分片的一个子集,这样的查询叫做定向查询( targeted query)。有些查询必须被发送到所有分片(如count),这样的查询叫做分散-聚集查询(scatter-gather query):mongos将查询分散到所有分片上,然后将各个分片的查询结果聚集起来。

块(Chunk) 分片系统将使用分片键将数据映射到块,块是文档键的逻辑连续范围。每个块标志着分片键值特定连续范围内的许多文档;这些值使mongos控制器可以快速找到包含它所需文档的块。然后MongoDB分片系统将把块存储在可用的分片系统中。配置服务器将记录每个块存储的分片服务器位置。这是分片实现的一个重要特性,因此通过它可以从集合中添加和删除分片。而不需要备份和恢复数据。块默认大小:64MB。生产环境中,也可以通过命令手动更改块的大小。

ObjectId 使用12 字节的存储空间,每个字节两位十六进制数字,是一个24 位的字符串。

前4 个字节是从标准纪元开始的时间戳,单位为秒。这会带来一些有用的属性。时间戳,与随后的. 5 个字节组合起来,提供了秒级别的唯一性。
由于时间戳在前,这意味着ObjectId 大致会按照插入的顺序排列。这对于某些方面很有用,如将其作为索引提高效率,但是这个是没有保证的,仅仅是“大致”。  这4 个字节也隐含了文档创建的时间。绝大多数驱动都会公开一个方法从ObjectId 获取这个信息。  因为使用的是当前时间,很多用户担心要对服务器进行时间同步。其实没有这个必要,因为时间戳的实际值并不重要,只要其总是不停增加就好了(每秒一次)。 
接下来的3 字节是所在主机的唯一标识符。通常是机器主机名的散列值。这样就可以确保不同主机生成不同的ObjectId,不产生冲突。
为了确保在同一台机器上并发的多个进程产生的ObjectId 是唯一的,接下来的两字节来自产生ObjectId 的进程标识符(PID)。
前9 字节保证了同一秒钟不同机器不同进程产生的ObjectId 是唯一的。后3 字节就是一个自动增加的计数器,确保相同进程同一秒产生的ObjectId 也是不一样的。同一秒钟最多允许每个进程拥有2563(16 777 216)个不同的ObjectId。

均衡器(balancer) 负责数据的迁移。它会周期性地检查分片间是否存在不均衡,如果存在,则会开始块的迁移。虽然均衡器通常被看做是单一实体,但每个mongos有时也会扮演均衡器的角色。每隔几秒钟,mongos就会尝试变身为均衡器。如果没有其他可用的均衡器,mongos就会对整个集群加锁,以防止配置服务器对集群修改,然后做一次均衡。均衡并不会影响mongos的正常路由操作,所以使用mongos的客户端不会受到影响。
 
2、sharding应用场景(重要)
何时分片
决定何时分片是一个值得权衡的问题。通常不必太早分片,因为分片不仅会增加部署的操作复杂度,还要求作出设计决策,而该决策以后很难再改。另外最好也不要在系统运行太久之后再分片,因为在一个过载的系统上不停机进行分片是非常困难的。
通常,分片用来:
增加可用RAM;
增加可用磁盘空间;
减轻单台服务器的负载;
处理单个mongod无法承受的吞吐量;
因此,良好的监控对应决定应何时分片是十分重要的,不需认真对待其中每一项。由于人们往往过于关注改进其中一个指标,所以应弄明白到底哪一项指标对自己的部署最为重要,并提前做好何时分片以及如何分片的计划。

与米学的业务结合及特点:
凡是涉及到业务明细表/详情表的地方,都需要考虑是否需要用sharding的问题:
如小红花表:基本每日都是几十万的数据增量。总的数据量在1000w+以上。未来的某段时间内,数据量也会呈指数级的增长。
如积分详情表。生产环境上当前数据量:1000w+
如点赞表。
生产环境上当前数据量:
2000w+

3、sharding实战第一步:小红花业务分析(重要)
3.1、web 教师端   - 过程性评价 - 教师根据班级和评价点查看该班级下学生在一段时间范围内(以一周时间为单位)的小红花列表
com.mexue.action.controller.evaluate.teacher.EvaluateGrowthPointerController.topoint
mexue2_2.evaluate_growth_content query: { date: { $gte: "2016-01-11", $lte: "2016-01-17" }, pointId: "5641ac820cf2dfbea02d628c", studentId: { $in: [ "564fb25f0cf21beb00d5fe6e", "564f12a40cf2a657b32518a1", "564fedda0cf21beb00d61d20", "565431c90cf2c904cb582e4f", "5653bf0d0cf273a1fb30cb75", "564ee61e0cf2a657b324ec99", "564ee7510cf2eb1d22ee6286", "564f28120cf21beb00d5f146", "5653ef150cf224b590d70d3c", "5652ccf40cf219d52472015b", "5654020f0cf273a1fb30ee6a", "564fbd8e0cf21beb00d602e0", "5652d5d80cf224b590d6a797", "56514ab20cf20b9363d0bc6b", "564ee9be0cf2a657b324f02e", "56543bc10cf2c904cb58377a", "5654408c0cf2dafd1d017ae5", "56503f870cf2f4fdfd7da578", "5651c0080cf20b9363d0f84f", "564ecab20cf2a657b324e1d3", "5656e4920cf2044f7e4ee114", "564fc6ee0cf2f4fdfd7d64e9", "565022b10cf2f4fdfd7d9604", "564eeaff0cf2eb1d22ee65fa", "565056f10cf21beb00d65607", "56503f120cf21beb00d646e3", "564fc46a0cf21beb00d60620", "564f096c0cf2eb1d22ee844f", "5652b3870cf219d52471f903", "565191fb0cf2624ae16c5957", "564ee7790cf2eb1d22ee62b1", "564fc9a10cf2f4fdfd7d6687", "565054170cf21beb00d65421", "5651b03b0cf2624ae16c6d8a", "564fad790cf2f4fdfd7d5a30", "565434c10cf2c904cb583093", "56504ef80cf2f4fdfd7daf1d", "564ee98f0cf2a657b324f003" ] } }

3.2、web教师端 - 过程性评价 - 教师单个/批量发放小红花
com.mexue.action.controller.evaluate.teacher.EvaluateGrowthPointerController.addContent
mexue2_2.$cmd command: insert: "evaluate_growth_content" [ { _id: ObjectId('568360170cf22522f341a9bb'), termId: "5641a9640cf2fe24019e8bd4", pointId: "5641ad480cf2dfbea02d6517", name: "语文作业", propertyType: 1, content: "又是满分,加油", enabled: 1, date: "2015-12-30", studentId: "564c10fd0cf24a16995ccd62", userId: "5641a98c0cf238217eb966f9", createTime: "2015-12-30 12:39:50" } ]

insert:evaluate_teacher_history evaluate_student_growth_cache evaluate_process_point

3.3、app家长端 - 我的 - 是否有未读的小红花
com.mexue.action.controller.mobile.EvaluateProcessController.hasProcessInfo (m=hasProcessInfo)
mexue2_2.evaluate_student_growth_cache query: { _id: ObjectId('56650ea00cf26ac83e83cbfe') }

3.4、app家长端 - 我的 - 小红花 - 当前学期下所有红花统计列表
com.mexue.action.controller.mobile.EvaluateProcessController.parentProcessList
mexue2_2.evaluate_process_point query: { _id: ObjectId('564eb07a0cf27467d746e238#5641ac810cf2dfbea02d627a') }   studentId#pointId
mexue2_2.evaluate_student_growth_cache query: { _id: ObjectId('56650ea00cf26ac83e83cbfe') }

3.5、app家长端 - 我的 - 小红花 - 所有(评价点)红花统计列表 - 单个评价点下获得的小红花历史列表
com.mexue.action.controller.mobile.EvaluateProcessController.parentProcessInfoList (m=parentProcessInfoList)
mexue2_2.evaluate_growth_content query: { $query: { enabled: 1, termId: "564b1c4b0cf203df085136bc", pointId: "566952d10cf2c5c82f6ed2c6", studentId: "566aa58e0cf2eded918e168e", propertyType: -1 }, $orderby: { createTime: -1 }}

3.6、app家长端 - 我的 - 成长档案 - 学业水平 - 红花墙
com.mexue.action.controller.growth.GrowthAPIController.getFlowers
mexue2_2.evaluate_process_point query: { $query:{ "_id" : { "$in" : [ "56c2d5e60cf2f5317b7c9942#56c194fc0cf2df4ad994d30d" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d347" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d34c" , "56c2d5e60cf2f5317b7c9942#56c194ff0cf2df4ad994d358" , "56c2d5e60cf2f5317b7c9942#56c194fd0cf2df4ad994d32d" , "56c2d5e60cf2f5317b7c9942#56c195000cf2df4ad994d35c" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d342" , "56c2d5e60cf2f5317b7c9942#56c194fc0cf2df4ad994d31d" , "56c2d5e60cf2f5317b7c9942#56c194fc0cf2df4ad994d315" , "56c2d5e60cf2f5317b7c9942#56c194fd0cf2df4ad994d325" , "56c2d5e60cf2f5317b7c9942#56c195010cf2df4ad994d369" , "56c2d5e60cf2f5317b7c9942#56c194fd0cf2df4ad994d335" , "56c2d5e60cf2f5317b7c9942#56c195000cf2df4ad994d365" , "56c2d5e60cf2f5317b7c9942#56c195000cf2df4ad994d360" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d33d"]}}

{ "studentId" : { "$in" : [ "56cab6b00cf20eed0e8b6200"]} , "pointId" : { "$in" : [ "5635beeb0cf2dfbea02a1021" , "5635beea0cf2dfbea02a1018" , "5635bee80cf2dfbea02a0fc0" , "5635beea0cf2dfbea02a1014" , "5635bee90cf2dfbea02a0fe2" , "5635bee90cf2dfbea02a0fd1" , "5635beea0cf2dfbea02a1010" , "5635beea0cf2dfbea02a1003" , "5635bee90cf2dfbea02a0ff3" , "5635bee90cf2dfbea02a0fd9" , "5635beea0cf2dfbea02a0ffe" , "5635bee90cf2dfbea02a0fc8" , "5635bee90cf2dfbea02a0ff8" , "5635beea0cf2dfbea02a101d" , "5635bee90cf2dfbea02a0fea"]}}

3.7、app教师端 - 我的 - 发小红花 - 当前班级下,我发过的小红花/问题统计列表
com.mexue.action.controller.mobile.EvaluateProcessController.findEvaluatePoints (m=processCommentList)
mexue2_2.evaluate_process_point query: { $query:{ "_id" : { "$in" : [ "56c2d5e60cf2f5317b7c9942#56c194fc0cf2df4ad994d30d" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d347" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d34c" , "56c2d5e60cf2f5317b7c9942#56c194ff0cf2df4ad994d358" , "56c2d5e60cf2f5317b7c9942#56c194fd0cf2df4ad994d32d" , "56c2d5e60cf2f5317b7c9942#56c195000cf2df4ad994d35c" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d342" , "56c2d5e60cf2f5317b7c9942#56c194fc0cf2df4ad994d31d" , "56c2d5e60cf2f5317b7c9942#56c194fc0cf2df4ad994d315" , "56c2d5e60cf2f5317b7c9942#56c194fd0cf2df4ad994d325" , "56c2d5e60cf2f5317b7c9942#56c195010cf2df4ad994d369" , "56c2d5e60cf2f5317b7c9942#56c194fd0cf2df4ad994d335" , "56c2d5e60cf2f5317b7c9942#56c195000cf2df4ad994d365" , "56c2d5e60cf2f5317b7c9942#56c195000cf2df4ad994d360" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d33d"]}}

{ "studentId" : { "$in" : [ "56cab6b00cf20eed0e8b6200"]} , "pointId" : { "$in" : [ "5635beeb0cf2dfbea02a1021" , "5635beea0cf2dfbea02a1018" , "5635bee80cf2dfbea02a0fc0" , "5635beea0cf2dfbea02a1014" , "5635bee90cf2dfbea02a0fe2" , "5635bee90cf2dfbea02a0fd1" , "5635beea0cf2dfbea02a1010" , "5635beea0cf2dfbea02a1003" , "5635bee90cf2dfbea02a0ff3" , "5635bee90cf2dfbea02a0fd9" , "5635beea0cf2dfbea02a0ffe" , "5635bee90cf2dfbea02a0fc8" , "5635bee90cf2dfbea02a0ff8" , "5635beea0cf2dfbea02a101d" , "5635bee90cf2dfbea02a0fea"]}}

3.8、app教师端 - 我的 - 单个/批量发放小红花 (同web端功能,同消息界面点击加号发放小红花功能)
com.mexue.action.controller.mobile.EvaluateProcessController.publishProcessExcitate (m=addProcessComment)
insert: "evaluate_growth_content" [ { _id: ObjectId('568360170cf22522f341a9bb'), termId: "5641a9640cf2fe24019e8bd4", pointId: "5641ad480cf2dfbea02d6517", name: "语文作业", propertyType: 1, content: "又是满分,加油", enabled: 1, date: "2015-12-30", studentId: "564c10fd0cf24a16995ccd62", userId: "5641a98c0cf238217eb966f9", createTime: "2015-12-30 12:39:50" } ]

insert:evaluate_teacher_history evaluate_student_growth_cache evaluate_process_point

3.9、app教师端 - 我的 - 单个/批量发放小红花 - 查看教师发放的小红花历史记录
com.mexue.action.controller.mobile.EvaluateProcessController.teacherProcessInfoList (m=teacherProcessInfoList)
mexue2_2.$cmd command: count { count: "evaluate_teacher_history", query: { termId: "564977ad0cf2c40ed46863cd", teacherId: "566be1b20cf2fe12b63b1092", date: "2016-01-25" } }

mexue2_2.evaluate_teacher_history query: { $query: { termId: "5630c0580cf2f6d1fc284159", teacherId: "5675f4010cf2e93636933785", date: "2016-01-25" }, $orderby: { createTime: -1 } }

3.10、app教师端 - 我的 - 单个/批量发放小红花 - 根据日期获取教师发布小红花的历史记录
com.mexue.action.controller.mobile.EvaluateProcessController.teacherHistory  (m=calendarAbstract)
mexue2_2.evaluate_teacher_history query: { $query: { termId: "55dd6bea0cf25ba63d83d29d", teacherId: "5678bcbc0cf223517d13937c", date: { $gte: "2016-01-15", $lte: "2016-01-25" } }, $orderby: { createTime: -1 } }

3.11、app教师端(2.5版本之前用到,不包含2.5版本) - 我的 - 日常表现 - 某班级下学生列表 - 根据学生ID,教师ID和学期ID获取其当前学期下获取的所有小红花列表
com.mexue.action.controller.mobile.EvaluateProcessController. showEvaluateByTeacher (m=processCommentDetail)
mexue2_2.evaluate_process_point query: { studentId: "56c181730cf2908a7d905818", userId: "56c181570cf2908a7d905816", termId: "56c18d990cf2908a7d9058e8" }

3.12、web家长端 - 成长档案 - 成长档案打印 - 红花墙
com.mexue.action.controller.mobile.PrintRecordController.getFlowers
mexue2_2.evaluate_process_point query: { $query:{ "_id" : { "$in" : [ "56c2d5e60cf2f5317b7c9942#56c194fc0cf2df4ad994d30d" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d347" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d34c" , "56c2d5e60cf2f5317b7c9942#56c194ff0cf2df4ad994d358" , "56c2d5e60cf2f5317b7c9942#56c194fd0cf2df4ad994d32d" , "56c2d5e60cf2f5317b7c9942#56c195000cf2df4ad994d35c" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d342" , "56c2d5e60cf2f5317b7c9942#56c194fc0cf2df4ad994d31d" , "56c2d5e60cf2f5317b7c9942#56c194fc0cf2df4ad994d315" , "56c2d5e60cf2f5317b7c9942#56c194fd0cf2df4ad994d325" , "56c2d5e60cf2f5317b7c9942#56c195010cf2df4ad994d369" , "56c2d5e60cf2f5317b7c9942#56c194fd0cf2df4ad994d335" , "56c2d5e60cf2f5317b7c9942#56c195000cf2df4ad994d365" , "56c2d5e60cf2f5317b7c9942#56c195000cf2df4ad994d360" , "56c2d5e60cf2f5317b7c9942#56c194fe0cf2df4ad994d33d"]}}

{ "studentId" : { "$in" : [ "56cab6b00cf20eed0e8b6200"]} , "pointId" : { "$in" : [ "5635beeb0cf2dfbea02a1021" , "5635beea0cf2dfbea02a1018" , "5635bee80cf2dfbea02a0fc0" , "5635beea0cf2dfbea02a1014" , "5635bee90cf2dfbea02a0fe2" , "5635bee90cf2dfbea02a0fd1" , "5635beea0cf2dfbea02a1010" , "5635beea0cf2dfbea02a1003" , "5635bee90cf2dfbea02a0ff3" , "5635bee90cf2dfbea02a0fd9" , "5635beea0cf2dfbea02a0ffe" , "5635bee90cf2dfbea02a0fc8" , "5635bee90cf2dfbea02a0ff8" , "5635beea0cf2dfbea02a101d" , "5635bee90cf2dfbea02a0fea"]}}


涉及表说明:
evaluate_growth_content  过程性评价激励内容(小红花/问题)实体类定义 (数据总量:419万
当前索引:
pointId
studentId
userId + termId


evaluate_teacher_history  教师发布小红花的历史记录 数据总量:86万
当前索引:
termId
pointId
teacherId

evaluate_student_growth_cache 学生获得小红花的历史记录  数据总量:26万
当前索引:

evaluate_process_point 小红花评价点关系表(小红花汇总表)
当前索引:

实体类属性定义:
@Document(collection = "evaluate_growth_content")
public class EvaluateGrowthContent{
    @Id
    private String id;//ID标识()
    private String termId; //学期ID
    private String pointId;//评价点ID标识
    private String name; //小红花名称
    private int propertyType;      //属性值(1: -1:)
    private String content;//评价内容
    private int enabled;//是否可用(1:可用【默认】 0:不可用)
    private String date;//评价日期(yyyy-MM-dd)
    private String studentId;//学生ID标识
    private String userId;

    private String createTime;//创建时间
    private String modifyTime;//修改时间

@Document(collection = "evaluate_process_point")
public class EvaluateProcessPoint {
    @Id
    private String id;//studentId#pointId
    private int flowerNum;//小红花数量
    private int issueNum;//提醒数量
    private String createTime;//创建时间
    private String pointName; //评价点名称(冗余字段,id中的pointId中可推导)
    private String templateId;//手册Id(冗余字段,id中的pointId中可推导)
    private String studentId;//学生Id(冗余字段,id中的studentId中可推导)
    private String pointId;//评价点Id标识(冗余字段,id中的pointId中可推导)
    private String schoolId;//学校Id(冗余字段,id中的studentId中可推导)
    private String classId;//班级Id(冗余字段,id中的studentId中可推导)
    private String userId;//老师Id(冗余字段,id中的pointId中可推导)
    private String termId;//学期Id(冗余字段,id中的pointId中可推导)

@Document(collection = "evaluate_student_growth_cache")
public class EvaluateStudentGrowthCache{
    @Id
    private String studentId;//ID标识()
    private String classId; //班级ID
    private String termId; //学期ID
    private Set<String> pointIds;
    private int status; // 状态(0:未更新;1-有更新)

    private String createTime;//创建时间
    private String modifyTime;//修改时间

@Document(collection = "evaluate_teacher_history")
public class EvaluateTeacherHistory{
    @Id
    private String id;//ID标识()
    private String termId; //学期ID
    private String pointId;//评价点ID标识
    private String teacherId;
    private String classId;
    private String className;
    private String studentIds;//学生ID标识(逗号分隔)
    private String studentNames;//学生姓名(逗号分隔)
    private String name; //小红花名称
    private int propertyType;      //属性值(1: -1:)
    private String content;//评价内容
    private String date;//评价日期(yyyy-MM-dd)

    private String createTime;//创建时间
    private String modifyTime;//修改时间

4、sharding实战第二步:小红花分片方案实施及单元测试
4.1、确定片键
studentId + pointId
原因:不同学校的学生( studentId)是在不同的时间段内注册的,这样可以将学生数据尽量分散在不同的片中,即使同一个学校的学生也有可能会在不同的片中。

之前片键确定的时候曾走过弯路:
之前的片键选择曾用过: userId + termId。 原因是当时小红花业务这块量比较大。小红花汇总表启用了好几次没有启用上导致错误判断。

教师历史记录表从: evaluate_teacher_history 查询。
学生获取的小红花记录从:evaluate_student_growth_cache  查询。
涉及按学期进行查询汇总数据的从:evaluate_process_point   查询。

4.2、现有程序代码查询顺序,逻辑调整
原则:关于sharding表的查询,需要向片键上靠。

4.3、添加sharding分库开关及mongoTemplate操作类代码
environment.properties
#with sharding
ifWriteDataToParentDB=true     //是否向父库(当前指的是评价库)中写入数据【默认true】
ifUseShardingDB=true              //是否启用沙丁 true:使用 false:不使用 还是使用原来的查询评价方式【默认false】

com.mexue.persistence.db. EvaluateShardingDBOperations
com.mexue.persistence.db.
EvaluateShardingDBOperationsImpl


4.4、spring-mongo配置信息
#定义mongo sharding对象,对应的是mongodb官方jar包中的Mongo,ip地址和端口
evaluate.sharding.host=101.201.176.184
evaluate.sharding.port=30000
evaluate.sharding.connections-per-host=10
evaluate.sharding.threads-allowed-to-block-for-connection-multiplier=1
evaluate.sharding.connect-timeout=1000
evaluate.sharding.max-wait-time=1500
evaluate.sharding.socket-keep-alive=true
evaluate.sharding.socket-timeout=1500
evaluate.sharding.dbname=mexueEvaluate
evaluate.sharding.mongo-ref=mongo_evaluate_sharding


当前mongodb-java-driver 3.2.2版本,只提供两种mongodb连接方式:
1、ip地址 + 端口号;
2、复制集方式(多IP:PORT连接方式);

没有单独的sharding连接方式。 因为即可以单独连接一个mongos( 当前采用方式,或者做个高可用的前端代理[之前采用方式,同时也是当前能查询到的大多数生产环境采用的方式]),也可以把复制集中的多个IP:PORT设置成多个mongos形式。如下:


4.5、单元测试
测试小红花业务相关功能是否正常。

5、sharding实战第三步:小红花分片集成测试
5.1、读是否分散到正确的片中
方法:使用 db.evaluate_growth_content.find ( {"studentId" : "5678e9e80cf2f26154733e54","pointId" : "563acf500cf253f1f8fd2baa" } ).explain()命令即可看到查询的数据集来自于哪个片,如下图:


5.2、写操作是否 分散到正确的片中
从片2或片3中任意取几条studentId数据,然后用程序批量插入,然后再用5.1中描述的方法查看即可。

5.3、压力测试,批量读看响应时间是否正确,批量写是否可以写到对应的块中?
当前并发1000进行数据同时插入,查询暂未发现问题,其实这里需要更多的压测数据,如:
压测小红花涉及接口
建议:
1.在500万,1000万,2000万,5000万数据量下,小红花涉及接口的表现数据报告
2.在插入大量数据的时候,数据是否直接插入到了对应的分片上测试
(从3个分片上分别抽取不同的studentId,然后批量插入数据后看每个分片上数据块的大小及数据分布情况。这个需要运维配合

5.4、在块迁移过程中,批量写入数据是否正确?当要插入的数据恰恰在要迁移的块中会怎么样呢?
正确。不影响。

6、sharding实战第四步:小红花分片交付测试
交付测试建议(场景):
6.1、测试小红花业务是否可用
按常规测试用例测试小红花业务功能正常即可。

6.2、测试sharding开关是否可用
#with sharding
ifWriteDataToParentDB=true     //是否向父库(当前指的是评价库)中写入数据【默认true】
ifUseShardingDB=true              //是否启用沙丁 true:使用 false:不使用 还是使用原来的查询评价方式【默认false】

写数据:
6.2.1、ifWriteDataToParentDB=true && ifUseShardingDB=true  评价库小红花表数据正确,sharding库小红花表数据正确[该配置会在生产上用一段时间(2周/1月)]
6.2.2、ifWriteDataToParentDB=true && ifUseShardingDB=false评价库小红花表数据正确,sharding库小红花表数据正确(不插入数据)
6.2.3、ifWriteDataToParentDB=false&& ifUseShardingDB=true  评价库小红花表数据正确(不插入数据),sharding库小红花表数据正确[将来生产上要用的配置]
6.2.4、ifWriteDataToParentDB=false&& ifUseShardingDB=false评价库小红花表数据正确,sharding库小红花表数据正确(不插入数据)  [兜底,容错配置。同1.2]

读数据:
界面上查询红花数据正常即可。

6.3、测试sharding读取速度如何
压测小红花涉及接口。详细请见之前邮件中说明,在此不再赘述。
测试建议及产出:
在500万,1000万,2000万,5000万数据量下,小红花涉及接口的表现数据报告。

另在插入大量数据的时候,数据是否直接插入到了对应的分片上测试(从3个分片上分别抽取不同的studentId,然后批量插入数据后看每个分片上数据块的大小及数据分布情况。这个需要运维配合@徐岩岩)。

6.4、sharding环境被破坏后小红花业务的表现
当前preonline上sharding架构如下:
3片(单片采用复制集架构),3配置(可以做复制集,未来生产上不打算做复制集,具体原因希望@徐岩岩补充),3mongs(当前方案是会用阿里slb做高可用)

6.4.1、如果前段代理down了怎么办?
6.4.2、单mongs down了,双mongos down,3mongs down 场景如何。
6.4.3、单config down,双config down,3config down 场景如何。
6.4.4、分片1,2,3中的复制及下,1,2,3mongd进程 down了场景如何。


7、sharding实战第五步:小红花分片持续跟踪生产环境及修正完善
7.1、线上评价数据库中小红花详情表与sharding详情表中数据是否能对应上?
上线第一天对不上,后续持续跟踪后可以对应上。

模板:
今日对评价小红花sharding运行情况进行跟踪,结果如下:

综述:截止到2016-04-10上午8点,sharding库和评价库小红花数据正确。皆为:7598366条。

语句如下:
 
{    "createTime" : {$lte : "2016-04-10 08:00:00"    }}

评价:
7598366
沙丁:
7598366

数据正确


7.2、原sharding小红花查询代码中没有设置从片的“从库”中读取数据,已经修正。
com.mexue.framework.config. ConfigListener
public void contextInitialized(ServletContextEvent contextEvent) {
    ...
     MongoTemplate mongoTemplateEvaluateSharding = wac.getBean("mongoTemplateEvaluateSharding", MongoTemplate.class);
    mongoTemplateEvaluateSharding.getDb().setReadPreference(ReadPreference.secondaryPreferred());
}


7.3、持续监控;

8、最佳实践
8.1、如果想彻底了解和学习MongoDB,就必须硬啃它的官方文档。
8.2、在需求分析阶段进行架构设计:该业务涉及的表(集合)将来是否需要考虑分片。
8.3、随着不断增加分片数量,系统性能大致会呈线性增长。但是,如果从一个未分片的系统转换为只有几个分片的系统,性能通常会有所下降。由于迁移数据、维护元数据、路由等开销,少量分片的系统与未分片的系统相比,通常延迟更大,吞吐量甚至可能会更小。因此,至少应该创建3个或以上的分片。
8.4、永远不要直接连接到配置服务器,已防配置服务器数据被不小心修改或删除。应先连接到mongos,然后通过config数据库来查询相关信息,方法与查询其他数据库一样。如果通过mongos操作配置数据(而不是直接连接到配置服务器),mongos会保证将修改同步到所有配置服务器,也会防止危险操作的发生,如意外删除config数据库等。
8.5、其他,正在学习中。。。欢迎大家一起补充。

9、扩展阅读

10、作业与练习
请针对于preonline上的积分详情表(生产环境1000w+数据量)进行sharding分库开发。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值