简介

    在大型项目中,我们会遇到分表分库的情景。 
    分库,将不同模块对应的表拆分到对应的数据库下,其实伴随着公司内分布式系统的出现,这个过程也是自然而然就发生了,对应商品模块和用户模块,我们会建立商品服务和用户服务,各个服务访问各自的数据库,系统间的交互,通过远程调用实现,而不是直接访问其数据库。
    但是随着业务的进一步发展,数据表也会出现瓶颈,比如数据表的记录已经超过了千万级,到了这个量级,速度也会慢下来。所以接下来就是分表。 比如用户表,我们会分user_1,user_2,user_3,....,我们会按照用户的Id取模的方式来定位表,假如用户表有3个,则Id是5的用户信息会落在第二张表。 分表的方式多种多样,比如商品表就适合按照日期来分表,一个月一张。 (分表还有一种是将不同的字段,分配到不同的表中)

对比

目前我所知道的分表的方式,大概有以下几种
    1.自己手动控制,来决定操作那张表,比如要查询Id为5的用户信息,则会先5%(表的个数)=N 然后通过字符串拼接user_+"N"的方式得到表名,然后再访问数据库。
    2. sql解析替换,比如要查询Id为5的用户信息,sql为select * from user,这里user表其实在数据库中不存在,是一个逻辑表,在调用的更底层,会解析这个sql语句,找出表名,然后根据分表规则,替换成具体的表名。 这种方式比上面的侵入性要底。
    3. 代理方式,其实和上面的类似,只是具体替换工作是代理服务器做的,在连接数据库服务器的时候,我们连接的是代理,代理再连接数据库,我们执行一个sql语句,会先发送到代理服务器,代理服务器根据预先指定的分库分表规则,路由到具体的数据库。 对于我们系统来说,就是零侵入。 
    4. 数据库服务器本身的支持,比如sql server本本身就支持分表。
数据分表看似简单, 其实也非常困难,比如: 
    在应对Join查询上,我们不能再像原来那么操作。
    在未使用分表规则时的查询,比如,用户表是按照Id取模分表的,但是如果有一个查询是select * from user where loginid='XX' , 那就相当于要并行查询多张表。 
    在面对批量插入的时候。 
    等等。 当想要把分表做的更通用,更透明时,都会面对这个问题。

我的解决方案

我的想法和上面第一种比较类似,我想做的更通用一些,但是表名是始终绕不过去的,后来索性换了一种思路,既然这样做如此麻烦,那表名就不替换了,替换库,这就是我标题里说的,用分库的思想来分表,同时还得到另外的一个好处,就是当数据库服务器IO遇到瓶颈的时候,可以将这些数据库中一部分迁移到其他机器上。
    比如 用户表(user)需要分成3个,那我就新建3个数据库,每个数据库中各有一张表(user),当我执行select * from user  where id=5 的时候,我会根据规则,切换数据库连接,这个sql里面的user表,在对应数据库里是真实存在的。 这些数据库可以在同一台机器上,当服务器遇到压力时,可以将这3个数据库分布到3台机器上去,比起迁表,迁库更容易。 
    有了这个思路,接下来就是如何尽可能的低浸入,这里我使用.net的Attribute(当然,也可以搞成配置文件方式),通过给方法打标签来提供一些信息,最后就是如何解析这些标签,我这里使用AOP, 当然完全的零侵入是不可能的,但是也只是需要你在访问数据库的方法中,多一行代码,就是获取数据库连接的。 
 
 我们先看数据访问层
这里数据访问我用的是dapper,对于需要分库的的方法,只需要在方法上打上一个标签ShardingMode,参数包括你分库的规则,以及你的表的数量,至于需要根据哪个参数来分,则只需要在这个参数上打上ShardingKey的标签,如果是对象,则可以写上具体的key,其实也就是属性名。 
    以上面的为例,我使用的是取模的的方式来分表(ShardingMode = ShardingMode.Mod),表总共有3张,对于第一个方法,因为传进去的是对象,所以需要标示出具体是按照那个字段来的, 对于第二个方法,因为是简单类型,则直接打上标签就可以。每个方法中有一行代码, IDbConnection connection = ShardingConnUtils.GetConnection();,这个算是侵入的代码,主要是获取连接对象的,
 
下面是核心代码
 
 
其实核心代码的思想很简单,首先是获取方法上的标签,根据标签的值来分别选择不同的分库规则,然后获取方法的参数,看参数上是否打了标签,如果有标签,再根据参数的值,计算出具体分到哪张表。 注意上面的最后一行,ShardingConnUtils.SetConnectionIndex(),其实就是设置对应的数据库连接的,具体的值,会放在ThreadLocal中。 在操作数据库的方法中,就可以通过ShardingConnUtils.GetConnection()方法取得对应的连接。 
 
最后就是如何拦截方法来获取这些标签,这里就该AOP出场了,这里我使用了sheepaspect,
这里可以看到我定义了一个切面,主要是拦截方法上有ShardingModeAttribute标签的方法,当这类方法在执行的时候,会先执行  ShardingCore.Process(jp.Method,jp.Args); 来决定是哪个数据库连接,最后再执行具体的方法。
 
最后的执行
需要先注册数据库连接,以用于后面的切换,剩下的就和普通的方法调用没什么区别了。 
 

后记

    1. 可以看到我在定义要拦截哪些方法的时候,是只有ShardingModeAttribute标签的方法,我无意做一个通用的数据库连接管理框架,对于普通的单表操作,数据库连接还是由你们自己管理。
    2. 对join查询, 批量插入等操作,还没有办法支持,但其实在高并发项目中,Join查询很少用。 对于未使用分表规则时的查询,完全可以再建立一个内存映射规则解决,对于批量插入,可以考虑自己控制。 
    3. 对于分库的规则,我只实现了取模的方式,其他的如果要实现也是非常简单的,同时分库规则多种多样,可以按照自己的需求来实现。 
    这里AOP框架是关键但不是重点, .Net的AOP框架有多种选择,比如PostSharp,RealProxy,EntLib,可以选择任意一种。 以前我并没有关注.Net AOP, 但是通过这次的项目,我的思路一下次都打开了,我们可以实现很多的东西:
    比如在分布式调用上,我们可以控制方法重试, 因为分布式调用有一定几率的失败,只要保证幂等性,我们可以失败重试 。
    其次,我们可以定义性能监控,我们可以在方法执行前记录一个时间,方法执行后记录一个时间,这样就可以算出方法执行的耗时,同时记录方法调用的次数,最后汇总到一起,就可以看出整个系统的性能瓶颈在哪里,也可以知道系统的繁忙程度。 配合docker,可以自动扩充我们的系统。 
    最后,我们可以对拦截的方法做try catch 来记录未捕获的异常,汇集到一起做一个异常报警系统。 
 
代码我已经上传到gtihub,地址是 https://github.com/zhaoyb/DBSharding 
如果大家对这个项目感兴趣,可以到上面看看。
 

.Net下的分库分表帮助类——用分库的思想来分表的更多相关文章

  1. web(四)html表单类标签

    表单类标签 操作者用于输入信息,并将信息提交给服务器的标签集合. 表单标签介绍 form标签:表单元素(其余标签)标签的容器标签 input标签:用于用户信息输入的标签. button标签:按钮标签. ...

  2. MySQL单机优化---分表、分区、分库

    一.分表: 水平分表:根据条件把数据分为N个表(例如:商品表中有月份列,则可以按月份进行水平分表). 使用场景:一张表中数据太多,查询效率太慢. 当需要同时查询被水平分表的多张表时: 在两条SQL语句 ...

  3. 【Java EE 学习 77 下】【数据采集系统第九天】【使用spring实现答案水平分库】【未解决问题:分库查询问题】

    之前说过,如果一个数据库中要存储的数据量整体比较小,但是其中一个表存储的数据比较多,比如日志表,这时候就要考虑分表存储了:但是如果一个数据库整体存储的容量就比较大,该怎么办呢?这时候就需要考虑分库了, ...

  4. ShoneSharp语言(S#)的设计和使用介绍系列(11)—“类”披炫服靓妆化成“表”

    ShoneSharp语言(S#)的设计和使用介绍 系列(11)—“类”披炫服靓妆化成“表” 作者:Shone 声明:原创文章欢迎转载,但请注明出处,https://www.cnblogs.com/Sh ...

  5. C# 我的注册表操作类

    using System; using System.Collections.Generic; using System.Text; using Microsoft.Win32; using Syst ...

  6. GreenDao 工具类 --- 使用 Json 快速生成 Bean、表及其结构,"炒鸡"快!

    作者:林冠宏 / 指尖下的幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:http://www.cnblogs.com/linguan ...

  7. C#注册表操作类--完整优化版

    using System; using System.Collections.Generic; using System.Text; using Microsoft.Win32; namespace ...

  8. PTA 邻接表存储图的广度优先遍历(20 分)

    6-2 邻接表存储图的广度优先遍历(20 分) 试实现邻接表存储图的广度优先遍历. 函数接口定义: void BFS ( LGraph Graph, Vertex S, void (*Visit)(V ...

  9. Python的Django框架中forms表单类的使用方法详解

    用户表单是Web端的一项基本功能,大而全的Django框架中自然带有现成的基础form对象,本文就Python的Django框架中forms表单类的使用方法详解. Form表单的功能 自动生成HTML ...

随机推荐

  1. window.location.href 和 document.location.href

    document表示的是一个文档对象,window表示的是一个窗口对象,一个窗口下可以有多个文档对象. 所以一个窗口下只有一个window.location.href,但是可能有多个document. ...

  2. POJ3268Dijkstra

    题意:给定n个点,m条边,求所有顶点中到顶点x的来回最短距离 分析:考虑到数据范围,选用Dijkstra,用Floyd会超时 #include <iostream> #include &l ...

  3. MapReduce 简单的全文搜索

    上一个已经实现了反向索引,那么为什么不尝试下全文搜索呢.例如有了 Hello     file3.txt:1; MapReduce     file3.txt:2;fil1.txt:1;fil2.tx ...

  4. 51驱动LCD1602

    1602 采用标准的 16 脚接口,其中: 第 1 脚:VSS 为地电源 第 2 脚:VDD 接 5V 正电源 第 3 脚:V0 为液晶显示器对比度调整端,接正电源时对比度最弱,接地 电源时对比度最高 ...

  5. iOS 生产证书 分类: ios相关 app相关 2015-05-22 14:49 175人阅读 评论(0) 收藏

    首先登陆https://developer.apple.com(99美元账号) 选择iOS Developer program 板块下的 Certificates,Identifiers & ...

  6. HTML CSS基础(二)

    块元素和行内元素 HTML元素根据表现形式,可以分为2类: (1)块元素(block): (2)行内元素(inline): 任何HTML元素都属于这两类中的其中一类. 2.块元素特点: (1)独占一行 ...

  7. java设计模式面试

    设计模式 1. 单例模式:饱汉.饿汉.以及饿汉中的延迟加载,双重检查 2. 工厂模式.装饰者模式.观察者模式. 3. 工厂方法模式的优点(低耦合.高内聚,开放封闭原则) 单例模式 分类:懒汉式单例.饿 ...

  8. Repository 设计模式介绍(转)

    在DDD设计中大家都会使用Repository pattern来获取domain model所需要的数据. 1.什么事Repository? "A Repository mediates b ...

  9. 外显率&显性上位

    外显率(penetrance): 外显率是指条件下,群体中某一基因型(通常在杂合子状态下)个体表现出相应表型的百分率.外显率等于100%时称为完全外显(complete penetranc)低于100 ...

  10. IE6-能让png图片有透明效果的js代码

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/ ...