.Net下的分库分表帮助类——用分库的思想来分表

    在大型项目中,咱们会遇到分表分库的情景。 
    分库,将不一样模块对应的表拆分到对应的数据库下,其实伴随着公司内分布式系统的出现,这个过程也是天然而然就发生了,对应商品模块和用户模块,咱们会创建商品服务和用户服务,各个服务访问各自的数据库,系统间的交互,经过远程调用实现,而不是直接访问其数据库。
    可是随着业务的进一步发展,数据表也会出现瓶颈,好比数据表的记录已经超过了千万级,到了这个量级,速度也会慢下来。因此接下来就是分表。 好比用户表,咱们会分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 
若是你们对这个项目感兴趣,能够到上面看看。
相关文章
相关标签/搜索