精华内容
下载资源
问答
  • Sharding

    2020-12-01 15:17:02
    Implement sharding to the bot <p><strong>Describe alternatives you've considered N/A <p><strong>Additional context Would like to have sharding/cluster in the bot</p><p>该提问来源于开源项目:...
  • sharding

    千次阅读 2016-07-27 18:22:14
    说明:本次探讨多从应用角度进行来剖析mongodb sharding的应用,较少提及运维层面的mongodb sharding部署,有兴趣的同事可以到网上查询资料进行学习。 问题准备: 什么是sharding? 为什么要用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分库开发。


    展开全文
  • 数据库Sharding的基本思想和切分策略

    万次阅读 多人点赞 2011-01-24 16:32:00
    本文着重介绍sharding的基本思想和理论上的切分策略,关于更加细致的实施策略和参考事例请参考我的另一篇博文:数据库分库分表(sharding)系列(一) 拆分实施策略和示例演示一、基本思想 Sharding的基本思想就要把一个...

    博主历时三年倾注大量心血创作的《大数据平台架构与原型实现:数据中台建设实战》一书已由知名IT图书品牌电子工业出版社博文视点出版发行,真诚推荐给每一位读者!点击《重磅推荐:建大数据平台太难了!给我发个工程原型吧!》了解图书详情,扫码进入京东购书页面!

     

    本文着重介绍sharding的基本思想和理论上的切分策略,关于更加细致的实施策略和参考事例请参考我的另一篇博文:数据库分库分表(sharding)系列(一) 拆分实施策略和示例演示 

     

    一、基本思想

          Sharding的基本思想就要把一个数据库切分成多个部分放到不同的数据库(server)上,从而缓解单一数据库的性能问题。不太严格的讲,对于海量数据的数据库,如果是因为表多而数据多,这时候适合使用垂直切分,即把关系紧密(比如同一模块)的表切分出来放在一个server上。如果表并不多,但每张表的数据非常多,这时候适合水平切分,即把表的数据按某种规则(比如按ID散列)切分到多个数据库(server)上。当然,现实中更多是这两种情况混杂在一起,这时候需要根据实际情况做出选择,也可能会综合使用垂直与水平切分,从而将原有数据库切分成类似矩阵一样可以无限扩充的数据库(server)阵列。下面分别详细地介绍一下垂直切分和水平切分.

          垂直切分的最大特点就是规则简单,实施也更为方便,尤其适合各业务之间的耦合度非
    常低,相互影响很小,业务逻辑非常清晰的系统。在这种系统中,可以很容易做到将不同业
    务模块所使用的表分拆到不同的数据库中。根据不同的表来进行拆分,对应用程序的影响也
    更小,拆分规则也会比较简单清晰。(这也就是所谓的”share nothing”)。



          水平切分于垂直切分相比,相对来说稍微复杂一些。因为要将同一个表中的不同数据拆
    分到不同的数据库中,对于应用程序来说,拆分规则本身就较根据表名来拆分更为复杂,后
    期的数据维护也会更为复杂一些。



          让我们从普遍的情况来考虑数据的切分:一方面,一个库的所有表通常不可能由某一张表全部串联起来,这句话暗含的意思是,水平切分几乎都是针对一小搓一小搓(实际上就是垂直切分出来的块)关系紧密的表进行的,而不可能是针对所有表进行的。另一方面,一些负载非常高的系统,即使仅仅只是单个表都无法通过单台数据库主机来承担其负载,这意味着单单是垂直切分也不能完全解决问明。因此多数系统会将垂直切分和水平切分联合使用,先对系统做垂直切分,再针对每一小搓表的情况选择性地做水平切分。从而将整个数据库切分成一个分布式矩阵。

     

    二、切分策略

          如前面所提到的,切分是按先垂直切分再水平切分的步骤进行的。垂直切分的结果正好为水平切分做好了铺垫。垂直切分的思路就是分析表间的聚合关系,把关系紧密的表放在一起。多数情况下可能是同一个模块,或者是同一“聚集”。这里的“聚集”正是领域驱动设计里所说的聚集。在垂直切分出的表聚集内,找出“根元素”(这里的“根元素”就是领域驱动设计里的“聚合根”),按“根元素”进行水平切分,也就是从“根元素”开始,把所有和它直接与间接关联的数据放入一个shard里。这样出现跨shard关联的可能性就非常的小。应用程序就不必打断既有的表间关联。比如:对于社交网站,几乎所有数据最终都会关联到某个用户上,基于用户进行切分就是最好的选择。再比如论坛系统,用户和论坛两个模块应该在垂直切分时被分在了两个shard里,对于论坛模块来说,Forum显然是聚合根,因此按Forum进行水平切分,把Forum里所有的帖子和回帖都随Forum放在一个shard里是很自然的。

          对于共享数据数据,如果是只读的字典表,每个shard里维护一份应该是一个不错的选择,这样不必打断关联关系。如果是一般数据间的跨节点的关联,就必须打断。

     

          需要特别说明的是:当同时进行垂直和水平切分时,切分策略会发生一些微妙的变化。比如:在只考虑垂直切分的时候,被划分到一起的表之间可以保持任意的关联关系,因此你可以按“功能模块”划分表格,但是一旦引入水平切分之后,表间关联关系就会受到很大的制约,通常只能允许一个主表(以该表ID进行散列的表)和其多个次表之间保留关联关系,也就是说:当同时进行垂直和水平切分时,在垂直方向上的切分将不再以“功能模块”进行划分,而是需要更加细粒度的垂直切分,而这个粒度与领域驱动设计中的“聚合”概念不谋而合,甚至可以说是完全一致,每个shard的主表正是一个聚合中的聚合根!这样切分下来你会发现数据库分被切分地过于分散了(shard的数量会比较多,但是shard里的表却不多),为了避免管理过多的数据源,充分利用每一个数据库服务器的资源,可以考虑将业务上相近,并且具有相近数据增长速率(主表数据量在同一数量级上)的两个或多个shard放到同一个数据源里,每个shard依然是独立的,它们有各自的主表,并使用各自主表ID进行散列,不同的只是它们的散列取模(即节点数量)必需是一致的。(

    本文着重介绍sharding的基本思想和理论上的切分策略,关于更加细致的实施策略和参考事例请参考我的另一篇博文:数据库分库分表(sharding)系列(一) 拆分实施策略和示例演示 


    1.事务问题:
    解决事务问题目前有两种可行的方案:分布式事务和通过应用程序与数据库共同控制实现事务下面对两套方案进行一个简单的对比。
    方案一:使用分布式事务
        优点:交由数据库管理,简单有效
        缺点:性能代价高,特别是shard越来越多时
    方案二:由应用程序和数据库共同控制
         原理:将一个跨多个数据库的分布式事务分拆成多个仅处
               于单个数据库上面的小事务,并通过应用程序来总控
               各个小事务。
         优点:性能上有优势
         缺点:需要应用程序在事务控制上做灵活设计。如果使用  
               了spring的事务管理,改动起来会面临一定的困难。
    2.跨节点Join的问题
          只要是进行切分,跨节点Join的问题是不可避免的。但是良好的设计和切分却可以减少此类情况的发生。解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据。

    3.跨节点的count,order by,group by以及聚合函数问题
          这些是一类问题,因为它们都需要基于全部数据集合进行计算。多数的代理都不会自动处理合并工作。解决方案:与解决跨节点join问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和join不同的是每个结点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。

     

    参考资料:

    《MySQL性能调优与架构设计》

     

    注:本文图片摘自《MySQL性能调优与架构设计》一 书

     

     

     

    相关阅读:

    数据库分库分表(sharding)系列(五) 一种支持自由规划无须数据迁移和修改路由代码的Sharding扩容方案

     

     

     

    数据库分库分表(sharding)系列(四) 多数据源的事务处理

    数据库分库分表(sharding)系列(三) 关于使用框架还是自主开发以及sharding实现层面的考量

     

    数据库分库分表(sharding)系列(二) 全局主键生成策略

     

    数据库分库分表(sharding)系列(一) 拆分实施策略和示例演示

     

    关于垂直切分Vertical Sharding的粒度

    数据库Sharding的基本思想和切分策略

     

     

     

     

    展开全文
  • 本文着重介绍sharding切分策略,如果你对数据库sharding缺少基本的了解,请参考我另一篇从基础理论全面介绍sharding的文章:数据库Sharding的基本思想和切分策略 第一部分:实施策略图1.数据库分库分表(sharding)...

    推荐:博主历时三年倾注大量心血创作的大数据平台架构与原型实现:数据中台建设实战一书已由知名IT图书品牌电子工业出版社博文视点出版发行,真诚推荐给每一位读者!点击重磅推荐:建大数据平台太难了!给我发个工程原型吧!了解图书详情,扫码进入京东手机购书页面!

     

     

    本文原文连接: http://blog.csdn.net/bluishglc/article/details/7696085 ,转载请注明出处!本文着重介绍sharding切分策略,如果你对数据库sharding缺少基本的了解,请参考我另一篇从基础理论全面介绍sharding的文章:数据库Sharding的基本思想和切分策略

     

    第一部分:实施策略

     

     

    图1.数据库分库分表(sharding)实施策略图解(点击查看大图)


    1.准备阶段

    对数据库进行分库分表(Sharding化)前,需要开发人员充分了解系统业务逻辑和数据库schema.一个好的建议是绘制一张数据库ER图或领域模型图,以这类图为基础划分shard,直观易行,可以确保开发人员始终保持清醒思路。对于是选择数据库ER图还是领域模型图要根据项目自身情况进行选择。如果项目使用数据驱动的开发方式,团队以数据库ER图作为业务交流的基础,则自然会选择数据库ER图,如果项目使用的是领域驱动的开发方式,并通过OR-Mapping构建了一个良好的领域模型,那么领域模型图无疑是最好的选择。就我个人来说,更加倾向使用领域模型图,因为进行切分时更多的是以业务为依据进行分析判断,领域模型无疑更加清晰和直观。

    2.分析阶段

    1. 垂直切分

    垂直切分的依据原则是:将业务紧密,表间关联密切的表划分在一起,例如同一模块的表。结合已经准备好的数据库ER图或领域模型图,仿照活动图中的泳道概念,一个泳道代表一个shard,把所有表格划分到不同的泳道中。下面的分析示例会展示这种做法。当然,你也可以在打印出的ER图或模型图上直接用铅笔圈,一切取决于你自己的喜好。

    2. 水平切分
    垂直切分后,需要对shard内表格的数据量和增速进一步分析,以确定是否需要进行水平切分。

    2.1若划分到一起的表格数据增长缓慢,在产品上线后可遇见的足够长的时期内均可以由单一数据库承载,则不需要进行水平切分,所有表格驻留同一shard,所有表间关联关系会得到最大限度的保留,同时保证了书写SQL的自由度,不易受join、group by、order by等子句限制。

    2.2 若划分到一起的表格数据量巨大,增速迅猛,需要进一步进行水平分割。进一步的水平分割就这样进行:

    2.2.1.结合业务逻辑和表间关系,将当前shard划分成多个更小的shard,通常情况下,这些更小的shard每一个都只包含一个主表(将以该表ID进行散列的表)和多个与其关联或间接关联的次表。这种一个shard一张主表多张次表的状况是水平切分的必然结果。这样切分下来,shard数量就会迅速增多。如果每一个shard代表一个独立的数据库,那么管理和维护数据库将会非常麻烦,而且这些小shard往往只有两三张表,为此而建立一个新库,利用率并不高,因此,在水平切分完成后可再进行一次“反向的Merge”,即:将业务上相近,并且具有相近数据增长速率(主表数据量在同一数量级上)的两个或多个shard放到同一个数据库上,在逻辑上它们依然是独立的shard,有各自的主表,并依据各自主表的ID进行散列,不同的只是它们的散列取模(即节点数量)必需是一致的。这样,每个数据库结点上的表格数量就相对平均了。

    2.2.2. 所有表格均划分到合适的shard之后,所有跨越shard的表间关联都必须打断,在书写sql时,跨shard的join、group by、order by都将被禁止,需要在应用程序层面协调解决这些问题。

    特别想提一点:经水平切分后,shard的粒度往往要比只做垂直切割的粒度要小,原单一垂直shard会被细分为一到多个以一个主表为中心关联或间接关联多个次表的shard,此时的shard粒度与领域驱动设计中的“聚合”概念不谋而合,甚至可以说是完全一致,每个shard的主表正是一个聚合中的聚合根!

    3.实施阶段


    如果项目在开发伊始就决定进行分库分表,则严格按照分析设计方案推进即可。如果是在中期架构演进中实施,除搭建实现sharding逻辑的基础设施外(关于该话题会在下篇文章中进行阐述),还需要对原有SQL逐一过滤分析,修改那些因为sharding而受到影响的sql.

    第二部分:示例演示

    本文选择一个人尽皆知的应用:jpetstore来演示如何进行分库分表(sharding)在分析阶段的工作。由于一些个人原因,演示使用的jpetstore来自原ibatis官方的一个Demo版本,SVN地址为:http://mybatis.googlecode.com/svn/tags/java_release_2.3.4-726/jpetstore-5。关于jpetstore的业务逻辑这里不再介绍,这是一个非常简单的电商系统原型,其领域模型如下图:

     

    图2. jpetstore领域模型

     

    由于系统较简单,我们很容易从模型上看出,其主要由三个模块组成:用户,产品和订单。那么垂直切分的方案也就出来了。接下来看水平切分,如果我们从一个实际的宠物店出发考虑,可能出现数据激增的单表应该是Account和Order,因此这两张表需要进行水平切分。对于Product模块来说,如果是一个实际的系统,Product和Item的数量都不会很大,因此只做垂直切分就足够了,也就是(Product,Category,Item,Iventory,Supplier)五张表在一个数据库结点上(没有水平切分,不会存在两个以上的数据库结点)。但是作为一个演示,我们假设产品模块也有大量的数据需要我们做水平切分,那么分析来看,这个模块要拆分出两个shard:一个是(Product(主),Category),另一个是(Item(主),Iventory,Supplier),同时,我们认为:这两个shard在数据增速上应该是相近的,且在业务上也很紧密,那么我们可以把这两个shard放在同一个数据库节点上,Item和Product数据在散列时取一样的模。根据前文介绍的图纸绘制方法,我们得到下面这张sharding示意图:

     

    图3. jpetstore sharding示意图

     

    对于这张图再说明几点:


    1.使用泳道表示物理shard(一个数据库结点)

    2.若垂直切分出的shard进行了进一步的水平切分,但公用一个物理shard的话,则用虚线框住,表示其在逻辑上是一个独立的shard。

    3.深色实体表示主表

    4.X表示需要打断的表间关联

     

     

    展开全文
  • Sharding Manager

    2020-12-08 23:52:31
    <div><p>We need a sharding manager. <p>My current thoughts on sharding is that <strong>ALL BOTS ARE SHARDED BOTS</strong>. There is no such thing as a non-sharded bot. Bot's under 1000 server can ...
  • Sharding opcodes

    2021-01-07 11:50:42
    <div><p>Sharding opcode[s] like 0xf5 (PAYGAS), in accordance with pull request 651 [1] don’t seem to be in the opcodes.py file in the master branch. <p>I have also noticed that the sharding [2] and ...
  • sharding.rar

    2021-04-05 22:34:59
    自己学习sharding用的东西
  • Sharding-Proxy

    2021-02-23 13:48:09
    Mysql 分库中间件 Sharding-Proxy 下载
  • shardingJdbc功能代码

    2021-02-21 15:57:07
    shardingJdbc
  • ShardingJDBC

    2021-03-22 18:42:36
    目录前言1.ShardingJDBC1.1 分库分表方式回顾1.2 分库分表带来的问题1.2.1 事务一致性问题1.2.2 跨节点关联的问题1.2.3 分页排序查询的问题1.2.4 主键避重问题1.3 ShardingJDBC 简介1.3.1 什么是ShardingJDBC## ...


    前言

    文章内容输出来源:拉勾教育JAVA就业训练营


    1.ShardingJDBC

    1.1 分库分表方式回顾

    1. 分库分表的目的就是将我们的单库的数据控制在合理范围内,从而提高数据库的性能.
    • 垂直拆分( 按照结构分 )
      • 垂直分表: 将一张宽表(字段很多的表), 按照字段的访问频次进行拆分,就是按照表单结构进行拆
      • 垂直分库: 根据不同的业务,将表进行分类, 拆分到不同的数据库. 这些库可以部署在不同的服务器,分摊访问压力.
    • 水平拆分( 按照数据行分 )
      • 水平分库: 将一张表的数据 ( 按照数据行) 分到多个不同的数据库.每个库的表结构相同. 每个库都只有这张表的部分数据,当单表的数据量过大,如果继续使用水平分库, 那么数据库的实例就会不断增加,不利于系统的运维. 这时候就要采用水平分表.
      • 水平分表: 将一张表的数据 ( 按照数据行) , 分配到同一个数据库的多张表中,每个表都只有一部分数据.
    1. 什么时候用分库分表
    • 在系统设计阶段,就要完成垂直分库和垂直分表. 在数据量不断上升,数据库性能无法满足需求的时候, 首先要考虑的是缓存、 读写分离、索引技术等方案.如果数据量不断增加,并且持续增长再考虑水平分库 水平分表.

    1.2 分库分表带来的问题

    关系型数据库在单机单库的情况下,比较容易出现性能瓶颈问题,分库分表可以有效的解决这方面的问题,但是同时也会产生一些 比较棘手的问题.

    1.2.1 事务一致性问题

    当我们需要更新的内容同时分布在不同的库时, 不可避免的会产生跨库的事务问题. 原来在一个数据库操作, 本地事务就可以进行控制, 分库之后 一个请求可能要访问多个数据库,如何保证事务的一致性,目前还没有简单的解决方案.

    1.2.2 跨节点关联的问题

    在分库之后, 原来在一个库中的一些表,被分散到多个库,并且这些数据库可能还不在一台服务器,无法关联查询.

    解决这种关联查询,需要我们在代码层面进行控制,将关联查询拆开执行,然后再将获取到的结果进行拼装.

    1.2.3 分页排序查询的问题

    分库并行查询时,如果用到了分页 每个库返回的结果集本身是无序的, 只有将多个库中的数据先查出来,然后再根据排序字段在内存中进行排序,如果查询结果过大也是十分消耗资源的.

    1.2.4 主键避重问题

    在分库分表的环境中,表中的数据存储在不同的数据库, 主键自增无法保证ID不重复, 需要单独设计全局主键.
    ###1.2.5 公共表的问题
    不同的数据库,都需要从公共表中获取数据. 可以在每一个库都创建这个公共表, 所有对公共表的更新操作,都同时发送到所有分库执行. ShardingJDBC可以帮助我们解决这个问题.

    1.3 ShardingJDBC 简介

    1.3.1 什么是ShardingJDBC

    • ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成,我们只关注 Sharding-JDBC即可.

    • 官方地址:https://shardingsphere.apache.org/document/current/cn/overview/

    -Sharding-JDBC 定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架的使用。

    • 适用于任何基于Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
    • 基于任何第三方的数据库连接池,如:DBCP, C3P0, Druid等。
    • 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer和PostgreSQL。
      在这里插入图片描述
      上图展示了Sharding-Jdbc的工作方式,使用Sharding-Jdbc前需要人工对数据库进行分库分表,在应用程序中加入Sharding-Jdbc的Jar包,应用程序通过Sharding-Jdbc操作分库分表后的数据库和数据表,由于Sharding-Jdbc是对Jdbc驱动的增强,使用Sharding-Jdbc就像使用Jdbc驱动一样,在应用程序中是
      无需指定具体要操作的分库和分表的。

    ## 1.3.2 Sharding-JDBC主要功能

    • 数据分片
    • 读写分离

    通过Sharding-JDBC,应用可以透明的使用jdbc访问已经分库分表、读写分离的多个数据源,而不用关心数据源的数量以及数据如何分布。

    1.3.3 Sharding-JDBC与MyCat的区别

    1. mycat是一个中间件的第三方应用,sharding-jdbc是一个jar包
    2. 使用mycat时不需要修改代码,而使用sharding-jdbc时需要修改代码
    3. Mycat 是基于 Proxy,它复写了 MySQL 协议,将 Mycat Server 伪装成一个 MySQL 数据库,而Sharding-JDBC 是基于 JDBC 的扩展,是以 jar 包的形式提供轻量级服务的。
    • Mycat(proxy中间件层)
      在这里插入图片描述
    • Sharding-jdbc(应用层):
      在这里插入图片描述

    1.4 Sharding-JDBC入门使用

    1.4.1 搭建基础环境

    • 需求说明
      创建数据库lg_order, 模拟将订单表进行水平拆分, 创建两张表pay_order_1 与 pay_order_2,这两张表是订单表拆分后的表,我们通过Sharding-Jdbc向订单表插入数据,按照一定的分片规则,主键为偶数的落入pay_order_1表 ,为奇数的落入pay_order_2表, 再通过Sharding-Jdbc 进行查询.
    • 创建数据库
    CREATE DATABASE lg_order CHARACTER SET 'utf8';
    DROP TABLE IF EXISTS pay_order_1;
    CREATE TABLE pay_order_1 (
    order_id BIGINT(20) PRIMARY KEY AUTO_INCREMENT ,
    user_id INT(11) ,
    product_name VARCHAR(128),
     COUNT INT(11)
    );
    DROP TABLE IF EXISTS pay_order_2;
    CREATE TABLE pay_order_2 (
    order_id BIGINT(20) PRIMARY KEY AUTO_INCREMENT ,
    user_id INT(11) ,
    product_name VARCHAR(128),
     COUNT INT(11)
    );
    
    • 创建SpringBoot项目引入maven依赖

    sharding-jdbc以jar包形式提供服务,所以要先引入maven依赖。

    <dependency>
        <groupId>org.apache.shardingsphere</groupId>
        <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
        <version>4.0.0-RC1</version>
    </dependency>
    

    1.4.2 分片规则配置(水平分表)

    使用sharding-jdbc 对数据库中水平拆分的表进行操作,通过sharding-jdbc对分库分表的规则进行配置,配置内容包括:数据源、主键生成策略、分片策略等。

    application.properties

    • 基础配置
    spring.application.name = sharding-jdbc-simple
    server.servlet.context-path = /sharding-jdbc
    spring.http.encoding.enabled = true
    spring.http.encoding.charset = UTF-8
    spring.http.encoding.force = true
    spring.main.allow-bean-definition-overriding = true
    mybatis.configuration.map-underscore-to-camel-case = true
    
    • 数据源
    # 定义数据源
    spring.shardingsphere.datasource.names = db1
    spring.shardingsphere.datasource.db1.type =
    com.alibaba.druid.pool.DruidDataSource
    spring.shardingsphere.datasource.db1.driver-class-name =
    com.mysql.jdbc.Driver
    spring.shardingsphere.datasource.db1.url =
    jdbc:mysql://localhost:3306/lg_order?characterEncoding=UTF-8&useSSL=false
    spring.shardingsphere.datasource.db1.username = root
    spring.shardingsphere.datasource.db1.password = 123456
    
    • 配置数据节点
    #配置数据节点,指定节点的信息
    spring.shardingsphere.sharding.tables.pay_order.actual-data-nodes =
    db1.pay_order_$->{1..2}
    
    • 表达式 db1.pay_order_$->{1…2}
      • $ 会被 大括号中的 {1…2} 所替换
      • 会有两种选择: db1.pay_order_1 和 db1.pay_order_2
    • 配置主键生成策略
    #指定pay_order表 (逻辑表)的主键生成策略为 SNOWFLAKE
    spring.shardingsphere.sharding.tables.pay_order.key-
    generator.column=order_id
    spring.shardingsphere.sharding.tables.pay_order.key-generator.type=SNOWFLAKE
    

    使用shardingJDBC提供的主键生成策略,全局主键
    为避免主键重复, 生成主键采用 SNOWFLAKE 分布式ID生成算法

    • 配置分片算法
    #指定pay_order表的分片策略,分片策略包括分片键和分片算法
    spring.shardingsphere.sharding.tables.pay_order.table-
    strategy.inline.sharding-column = order_id
    spring.shardingsphere.sharding.tables.pay_order.table-
    strategy.inline.algorithm-expression = pay_order_$->{order_id % 2 + 1}
    

    分表策略表达式: pay_order_$-> {order_id % 2 + 1}
    {order_id % 2 + 1} 结果是偶数 操作 pay_order_1表
    {order_id % 2 + 1} 结果是奇数 操作 pay_order_2表

    • 打开SQL日志
    # 打开sql输出日志
    spring.shardingsphere.props.sql.show = true
    
    • 步骤总结
    1. 定义数据源
    2. 指定pay_order 表的数据分布情况, 分布在 pay_order_1 和 pay_order_2
    3. 指定pay_order 表的主键生成策略为SNOWFLAKE,是一种分布式自增算法,保证id全局唯一
    4. 定义pay_order分片策略,order_id为偶数的数据下沉到pay_order_1,为奇数下沉到在pay_order_2

    1.4.3 编写程序

    • 新增订单
    @Mapper
    @Component
    public interface PayOrderDao {
      /**
      * 新增订单
      * */
    @Insert("insert into pay_order(user_id,product_name,COUNT) values(#
    {user_id},#{product_name},#{count})")
      int insertPayOrder(@Param("user_id") int user_id, @Param("product_name")
    String product_name, @Param("count") int count);
    }
    
    • 测试
    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = RunBoot.class)
    public class PayOrderDaoTest {
      @Autowired
      PayOrderDao payOrderDao;
      @Test
      public void testInsertPayOrder(){
        for (int i = 1; i < 10; i++) {
          //插入数据
          payOrderDao.insertPayOrder(1,"小米电视",1);
       }
     }
    }
    
    • 根据Id查询订单
    /**
      * 查询订单
      * */
      @Select({"<script>" +
          "select " +
          " * " +
          " from pay_order p" +
          " where p.order_id in " +
          "<foreach collection='orderIds' item='id' open='(' separator=','
    close=')'>" +
          " #{id} " +
          "</foreach>"+
          "</script>"})
      List<Map> findOrderByIds(@Param("orderIds") List<Long> orderIds);
    
    • 测试
    @Test
      public void testFindOrderByIds(){
        List<Long> ids = new ArrayList<>();
        ids.add(517020734275452928L); //order_1表
        ids.add(517020734380310529L); //order_2表
        List<Map> mapList = payOrderDao.findOrderByIds(ids);
        System.out.println(mapList);
     }
    

    1.4.4 ShardingJDBC执行流程

    当ShardingJDBC接收到发送的SQL之后,会执行下面的步骤,最终返回执行结果
    在这里插入图片描述

    1. SQL解析: 编写SQL查询的是逻辑表, 执行时 ShardingJDBC 要解析SQL ,解析的目的是为了找到需要改写的位置.
    2. SQL路由: SQL的路由是指 将对逻辑表的操作,映射到对应的数据节点的过程. ShardingJDBC会获取分片键判断是否正确,正确 就执行分片策略(算法) 来找到真实的表.
    3. SQL改写: 程序员面向的是逻辑表编写SQL, 并不能直接在真实的数据库中执行,SQL改写用于将逻辑SQL改为在真实的数据库中可以正确执行的SQL.
    4. SQL执行: 通过配置规则 pay_order_$->{order_id % 2 + 1} ,可以知道当 order_id 为偶数时 ,应该向 pay_order_1表中插入数据, 为奇数时向 pay_order_2表插入数据.
    5. 将所有真正执行sql的结果进行汇总合并,然后返回。

    1.5 Sharding-JDBC分库分表

    1.5.1 水平分表

    把一张表的数据按照一定规则,分配到同一个数据库的多张表中,每个表只有这个表的部分数据. 在Sharding-JDBC入门使用中, 我们已经完成了水平分表的操作.
    在这里插入图片描述

    1.5.2 水平分库

    水平分库是把同一个表的数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器上。接下来看一下如何使用Sharding-JDBC实现水平分库

    1. 将原来的lg_order 数据库,拆分为 lg_order_1 和 lg_order_2
      在这里插入图片描述
    2. 分片规则配置
      现在是两个数据库,所以要配置两份数据源信息.
    # 定义多个数据源
    spring.shardingsphere.datasource.names = db1,db2
    spring.shardingsphere.datasource.db1.type =
    com.alibaba.druid.pool.DruidDataSource
    spring.shardingsphere.datasource.db1.driver-class-name =
    com.mysql.jdbc.Driver
    spring.shardingsphere.datasource.db1.url =
    jdbc:mysql://localhost:3306/lg_order_1?characterEncoding=UTF-8&useSSL=false
    spring.shardingsphere.datasource.db1.username = root
    spring.shardingsphere.datasource.db1.password = 123456
    spring.shardingsphere.datasource.db2.type =
    com.alibaba.druid.pool.DruidDataSource
    spring.shardingsphere.datasource.db2.driver-class-name =
    com.mysql.jdbc.Driver
    spring.shardingsphere.datasource.db2.url =
    jdbc:mysql://localhost:3306/lg_order_2?characterEncoding=UTF-8&useSSL=false
    spring.shardingsphere.datasource.db2.username = root
    spring.shardingsphere.datasource.db2.password = 123456
    

    通过配置对数据库的分片策略,来指定数据库进行操作

    # 分库策略,以user_id为分片键,分片策略为user_id % 2 + 1,user_id为偶数操作db1数据
    源,否则操作db2。
    spring.shardingsphere.sharding.tables.pay_order.database-
    strategy.inline.sharding-column = user_id
    spring.shardingsphere.sharding.tables.pay_order.database-
    strategy.inline.algorithm-expression = db$->{user_id % 2 + 1}
    
    1. 分库分表的策略
      分库策略 ,目的是将一个逻辑表 , 映射到多个数据源
    # 分库找的是数据库 db$->{user_id % 2 + 1}
    spring.shardingsphere.sharding.tables.逻辑表名称.database-strategy.分片策略.分片策略属性名 = 分片策略表达式
    
    • 分表策略, 如何将一个逻辑表 , 映射为多个 实际表
    #分表 找的是具体的表 pay_order_$->{order_id % 2 + 1}
    spring.shardingsphere.sharding.tables.逻辑表名称.table-strategy.分片策
    略.algorithm-expression = 分片策略表达式
    
    1. Sharding-JDBC支持以下几种分片策略:
      standard:标准分片策略
      complex:符合分片策略
      inline:行表达式分片策略,使用Groovy的表达式.
      hint:Hint分片策略,对应HintShardingStrategy。
      none:不分片策略,对应NoneShardingStrategy。不分片的策略。

    具体信息请查阅官方文档:https://shardingsphere.apache.org

    1. 修改数据节点配置
    #数据节点: db1.pay_order_1 , db1.pay_order_2, db2.pay_order_1,
    db2.pay_order_2
    spring.shardingsphere.sharding.tables.pay_order.actual-data-nodes = db$->
    {1..2}.pay_order_$->{1..2}
    

    1.5.3 垂直分库

    • 垂直分库是指按照业务将表进行分类,分布到不同的数据库上面,每个库可以放在不同的服务器上,它的核心理念是专库专用.
    • 在使用微服务架构时,业务切割得足够独立,数据也会按照业务切分,保证业务数据隔离,大大提升了数据库的吞吐能力。
    1. 创建数据库
    CREATE DATABASE lg_user CHARACTER SET 'utf8';
    
    1. 在lg_user 数据库中 users 创建表
    DROP TABLE IF EXISTS users;
    CREATE TABLE users (
    id BIGINT(20) PRIMARY KEY,
    username VARCHAR(20) ,
    phone VARCHAR(11),
    STATUS VARCHAR(11)
    );
    
    1. 规则配置
      配置数据源信息
    spring.shardingsphere.datasource.names = db1,db2,db3
    spring.shardingsphere.datasource.db3.type =
    com.alibaba.druid.pool.DruidDataSource
    spring.shardingsphere.datasource.db3.driver-class-name = com.mysql.jdbc.Driver
    spring.shardingsphere.datasource.db3.url = jdbc:mysql://localhost:3306/lg_user?
    characterEncoding=UTF-8&useSSL=false
    spring.shardingsphere.datasource.db3.username = root
    spring.shardingsphere.datasource.db3.password = 123456
    
    • 配置数据节点
    spring.shardingsphere.sharding.tables.users.actual-data-nodes = db$->{3}.users
    spring.shardingsphere.sharding.tables.users.table-strategy.inline.sharding-column = id
    spring.shardingsphere.sharding.tables.users.table-strategy.inline.algorithm-expression = users
    

    1.6 Sharding-JDBC 操作公共表

    1.6.1 什么是公共表

    • 公共表属于系统中数据量较小,变动少,而且属于高频联合查询的依赖表。参数表、数据字典表等属于此类型。
    • 可以将这类表在每个数据库都保存一份,所有更新操作都同时发送到所有分库执行。接下来看一下如何使用Sharding-JDBC实现公共表的数据维护。
      在这里插入图片描述

    1.6.2 公共表配置与测试

    1. 创建数据库
      分别在 lg_order_1, lg_order_2 , lg_user都创建 district表
    -- 区域表
    CREATE TABLE district (
    id BIGINT(20) PRIMARY KEY COMMENT '区域ID',
    district_name VARCHAR(100) COMMENT '区域名称',
    LEVEL INT COMMENT '等级'
    );
    

    在这里插入图片描述
    2) 在Sharding-JDBC的配置文件中 指定公共表

    # 指定district为公共表
    spring.shardingsphere.sharding.broadcast-tables=district
    # 主键生成策略
    spring.shardingsphere.sharding.tables.district.key-generator.column=id
    spring.shardingsphere.sharding.tables.district.key-generator.type=SNOWFLAKE**
    

    1.7 Sharding-JDBC读写分离

    Sharding-JDBC读写分离则是根据SQL语义的分析,将读操作和写操作分别路由至主库与从库。它提供透明化读写分离,让使用方尽量像使用一个数据库一样使用主从数据库集群。

    在这里插入图片描述

    1.7.1 MySQL主从同步

    为了实现Sharding-JDBC的读写分离,首先,要进行mysql的主从同步配置。
    我们直接使用MyCat讲解中,在虚拟机上搭建的主从数据库.

    • 在主服务器中的 test数据库 创建商品表
    CREATE TABLE products (
    pid BIGINT(32) PRIMARY KEY ,
    pname VARCHAR(50) DEFAULT NULL,
    price INT(11) DEFAULT NULL,
    flag VARCHAR(2) DEFAULT NULL
    );
    
    • 主库新建表之后,从库会根据binlog日志,同步创建.
      在这里插入图片描述

    1.7.2 sharding-jdbc实现读写分离

    1. 配置数据源
    # 定义多个数据源
    spring.shardingsphere.datasource.names = db1,db2,db3,m1,s1
    spring.shardingsphere.datasource.m1.type =
    com.alibaba.druid.pool.DruidDataSource
    spring.shardingsphere.datasource.m1.driver-class-name = com.mysql.jdbc.Driver
    spring.shardingsphere.datasource.m1.url = jdbc:mysql://192.168.52.10:3306/test?
    characterEncoding=UTF-8&useSSL=false
    spring.shardingsphere.datasource.m1.username = root
    spring.shardingsphere.datasource.m1.password = QiDian@666
    spring.shardingsphere.datasource.s1.type =
    com.alibaba.druid.pool.DruidDataSource
    spring.shardingsphere.datasource.s1.driver-class-name = com.mysql.jdbc.Driver
    spring.shardingsphere.datasource.s1.url = jdbc:mysql://192.168.52.11:3306/test?
    characterEncoding=UTF-8&useSSL=false
    spring.shardingsphere.datasource.s1.username = root
    spring.shardingsphere.datasource.s1.password = QiDian@666
    
    1. 配置主库与从库的相关信息
      ms1 包含了 m1 和 s1
    spring.shardingsphere.sharding.master-slave-rules.ms1.master-data-source-name=m1
    spring.shardingsphere.sharding.master-slave-rules.ms1.slave-data-source-names=s1
    
    1. 配置数据节点
    #配置数据节点
    spring.shardingsphere.sharding.tables.products.actual-data-nodes = ms1.products
    
    展开全文
  • 在上文中,我们讲解了分布式环境下的分库分表,从概念及案例上分析了何为分库分表及其优缺点。...Sharding-Sphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Pro
  • Sharding update

    2021-01-10 02:06:32
    <p>Separate sharding regions and proxies https://github.com/akka/akka/issues/23472 Fix lookup of coordinator for sharding proxies https://github.com/akka/akka/pull/23995</p> </li><li> <p>Fix race in ...
  • 当团队对系统业务和数据库进行了细致的梳理,确定了切分方案后,接下来的问题就是如何去实现切分方案了,目前在sharding方面有不少的开源框架和产品可供参考,同时很多团队也会选择自主开发实现,而不管是选择框架...
  • 版权声明:本文由本人撰写并发表于2012年9月份的《程序员》杂志,原文题目《一种支持自由规划的Sharding扩容方案——主打无须数据迁移和修改路由代码》,此处作为本系列的第五篇文章进行转载, 本文版权归《程序员》...
  • MongoDB Sharding

    2020-04-22 12:08:14
    Sharding key必须是在对应的collection当中所有的文档都存在的field,比如我要sharding我的user collection,我就可以找nick_name这种所有documents中都存在的field来做为sharding key,当然还要考虑其它因素,但是...
  • 转载请标明出处: ...本文出自方志朋的博客 Sharding-JDBC简介 Sharding-JDBC是的分布式数据库中间件...Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)是3款相互独立的产品,共同 组成了ShardingSpher...
  • <div><p>[docsyncer.IsShardingToSharding:43] replication from [replica] to [sharding] 我看代码是判断mongos, 但是我配置了mongos 也没用呢</p><p>该提问来源于开源项目:alibaba/MongoShake</p></div>
  • DB Sharding

    2021-01-11 02:27:26
    <div><p>Even though auto-sharding is not available yet (will be in 2.0) by using a cross-database RID. So the classic RID is: <h1>cluster-id:cluster-position, example #12:433 <p>And the cross-db will ...
  • Sharding JDBC .xmind

    2021-04-09 13:41:50
    Sharding JDBC 思维导图
  • 技术交流QQ群【JAVA,C++,Python,.NET,BigData,AI】:170933152 因为公司最近在做多租户的智慧城市相关的产品,这里,偶然看到这个框架,应该是可以用到,所以就看了一些, 记录下来. ...我们先看这个sharding.

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 12,818
精华内容 5,127
关键字:

sharding