分库分表实践

Posted 顺的自留地

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分库分表实践相关的知识,希望对你有一定的参考价值。

分库分表

背景

传统的将数据集中存储至单一数据节点的解决方案,在性能、可用性和运维成本这三方面已经难于满足互联网的海量数据场景。

从性能方面来说,由于关系型数据库大多采用B+树类型的索引,在数据量超过阈值的情况下,索引深度的增加也将使得磁盘访问的IO次数增加,进而导致查询性能的下降;同时,高并发访问请求也使得集中式数据库成为系统的最大瓶颈。

从运维成本方面考虑,当一个数据库实例中的数据达到阈值以上,对于DBA的运维压力就会增大。数据备份和恢复的时间成本都将随着数据量的大小而愈发不可控。一般来讲,单一数据库实例的数据的阈值在1TB之内,是比较合理的范围。

通过分库和分表进行数据的拆分来使得各个表的数据量保持在阈值以下,以及对流量进行疏导应对高访问量,是应对高并发和海量数据系统的有效手段。数据分片的拆分方式又分为垂直分片和水平分片。

垂直分

就是微服务,不同表做独立的服务,比如用户中心,订单中心,商品服务


水平分

当单个业务模块的数量巨大超过数据库的极限,比如用10亿的活跃用户数据,如果都在一个库一张表,就存不下,可以分8个库 每个库分16张表,这样就分到128张表中;水平分表有很多方案,具体要和实际业务结合选择方案。

解决方案

针对公司的大表分表实践,公司有超过50G的大表比如log_process;对于数据备份,还有单表的数据清理都是很耗时,而且可能影响线上业务。这里为了维护方便,采用按月分表的方案;一个月一张表,对历史数据后面可以直接备份后删除表,不会造成长时间锁表影响线上业务。

sharing-jdbc

表分片很简单,确定好分表逻辑,就可以实现,但是分表后,如果能对分表的数据查询做聚合,排序,分页,汇总等比较难,实现不好会侵入业务,也会降低查询效率,尽量透明化分库分表所带来的影响,让使用方尽量像使用一个数据库一样使用水平分片之后的数据库集群,这里选择sharding-jdbc 做分表的中间件,可以理解为增强的dataSource;

ShardingSphere目前已经进入Apache孵化器,有兴趣的可以查看官方文档,除了分库分表,还提供了编排治理,动态调整分库策略,以及面向DBA的sharding proxy等;产品比较完善。

实际操作指北

一、确定分表策略实现接口

选分片算法

  • 精确分片算法

对应PreciseShardingAlgorithm接口,用于处理使用单一键作为分片键的=与IN进行分片的场景。需要配合StandardShardingStrategy使用。

参考:

/**
* 按日期分表算法实现
*/
publicclassIDPreciseimplementsPreciseShardingAlgorithm<Long>{

   publicStringdoSharding(Collection<String>availableTargetNamesPreciseShardingValue<Long>preciseShardingValue) {
       //按主键ID分表
       longid=preciseShardingValue.getValue();
       LocalDateTimedatetime=getDatetime(id);
       StringBuildersb=newStringBuilder();
       sb.append(preciseShardingValue.getLogicTableName())
              .append("_")
              .append(datetime.getYear())
              .append("_")
              .append(datetime.getMonthValue());

       StringdataNode=sb.toString();

       ImmutableSet<String>immutableSet=ImmutableSet.copyOf(availableTargetNames);
       if(!immutableSet.contains(dataNode)){
           returnpreciseShardingValue.getLogicTableName();
      }
       returndataNode;
  }

   privateLocalDateTimegetDatetime(longid) {
       longtimestamp=id>>22;
       timestamp+=SnowflakeShardingKeyGenerator.EPOCH;
       Instantinstant=Instant.ofEpochMilli(timestamp);
       returnLocalDateTime.ofInstant(instantZoneId.systemDefault());
  }
}


  • 范围分片算法

对应RangeShardingAlgorithm,用于处理使用单一键作为分片键的BETWEEN AND、>、<、>=、<=进行分片的场景。需要配合StandardShardingStrategy使用。

  • 复合分片算法

对应ComplexKeysShardingAlgorithm,用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合ComplexShardingStrategy使用。

二、配置
<!--按月分表算法实现-->
   <beanid="idPrecise"class="com.xxx.base.shardingjdbc.shardingAlgorithm.IDPrecise"/>

   <bean:propertiesid="key_generator_properties">
       <propkey="worker.id">#{ T(com.xxx.base.shardingjdbc.utils.IdWorkerGen).getId() }</prop>
   </bean:properties>
<!--分布式ID生成-->
   <sharding:key-generatorid="snowFlakeKeyGen"type="SNOWFLAKE"column="id"props-ref="key_generator_properties"/>

   <!--分表策略 指定实现的接口 以及分片用的字段 这里是ID 主键-->
   <sharding:standard-strategyid="tableShardingStrategy_date"sharding-column="id"precise-algorithm-ref="idPrecise"/>

   <!-- sharding-jdbc dataSource 相当于普通的数据源-->
   <sharding:data-sourceid="shardingDataSource">
      <!--指定实际的数据源-->
       <sharding:sharding-ruledata-source-names="dataSource">
           <sharding:table-rules>
               <!--分表规则配置 指定需要分片的逻辑表名 以及分片的实际表名 支持行表达式-->
               <sharding:table-rulelogic-table="log_process"actual-data-nodes="dataSource.log_process,dataSource.log_process_2020_$->{3..12}"table-strategy-ref="tableShardingStrategy_date"key-generator-ref="snowFlakeKeyGen"/>
           </sharding:table-rules>
       </sharding:sharding-rule>
       <sharding:props>
           <propkey="sql.show">false</prop>
       </sharding:props>
   </sharding:data-source>


三、mapper 修改

   把分表的mapper 单独移动到一个包路径比如:shardingDao

修改maybatis 配置 (这里提一个踩的坑由于配置了sharding-jdbc 数据源后,会解析sql 对不需要分表的表也会解析,这样会对复杂sql 支持不好有的会报异常,这里就通过只对分表的mapper 注入shardingDataSource 避免所有表都是用分片数据源,固尔要修改mapper 的包路径分开扫描

<!-- 分表数据源的sqlSessionFactory配置 -->
   <beanid="sqlSessionFactory_sharding"class="org.mybatis.spring.SqlSessionFactoryBean">
       <propertyname="dataSource"ref="shardingDataSource"/>
       <propertyname="configLocation"value="classpath:conf/mybatis-config.xml"/>
       <propertyname="mapperLocations"value="classpath:com/xxx/pms/shardingMappers/**/*Mapper.xml"/>
       <propertyname="plugins">
           <array>
               <beanclass="com.github.pagehelper.PageInterceptor">
                   <propertyname="properties">
                       <!--使用下面的方式配置参数,一行配置一个 -->
                       <value>
                          reasonable=true
                       </value>
                   </property>
               </bean>
           </array>
       </property>
   </bean>

   <!-- scan for mappers and let them be autowired -->
   <beanclass="tk.mybatis.spring.mapper.MapperScannerConfigurer">
       <propertyname="basePackage"value="com.xxx.pms.shardingDao.**"/>
       <propertyname="sqlSessionFactoryBeanName"value="sqlSessionFactory_sharding"/>
   </bean>
四、id 生成算法

数据分片后就不用原来的自动增长ID了会重复,这用snowflake 算法生成ID,sharding-jdbc 提供了snowflake算法可以直接配置。

最后

sharding-jdbc 还有服务治理功能,结合zk,或nacos 做注册中心,动态调整配置分片策略,可以实现很多好玩的分片架构设计,后面可以研究下


以上是关于分库分表实践的主要内容,如果未能解决你的问题,请参考以下文章

分库分表架构实践

分库分表sharding-jdbc实践—分库分表入门

MariaDB Spider 数据库分库分表实践分库分表

分库分表“实践”大全

大型网站数据瓶颈之数据库分库分表方案实践

难得一见如此接地气的分库分表全程实践