cover_image

数据水平拆分利器-DataSS

韩小文 拍码场
2021年12月28日 10:29

前言

随着业务的增长,数据库会成为我们业务系统的瓶颈,这时我们就会想到分库分表这种手段,以此降低单数据库的负担。

1 背景

当我们的业务系统需要进一步拆分成多库多表时,需要对系统做一些侵入性的改造,例如:耦合业务代码,分别为新表和老表做读写操作,此外,新老双数据源的事务性问题还需更复杂的解决方案,由此,为了解决业务演进过程中的这个痛点,我们设计研发了DataSS (data shard Synchronization),中文名:穿山甲,寓意帮助业务打通库表拆分时的数据,使业务能够快速的,低成本的,无风险的平滑过渡到拆分后的新库表。

图片

2 DataSS

2.1 接入说明

为了减少用户的学习成本,特别简化了用户的接入流程,仅需三步完成数据拆分改造。图片

  1. 引入datass maven依赖
        <dependency>            <groupId>com.xinye</groupId>            <artifactId>data-shard-sync</artifactId>            <version>1.0.0-SNAPSHOT</version>        </dependency>
  1. 配置动态数据源:
    /**     * 配置数据源     */    @Bean(name = "PpdaiLoanCouponDataSource")    @Qualifier("PpdaiLoanCouponDataSource")    public DataSource getPpdaiLoanCouponDataSource() throws SQLException {        DataShardSyncDataSource dataShardSyncDataSource = new DataShardSyncDataSource();        dataShardSyncDataSource.setOldDataSource(createOldDruidDataSource());        dataShardSyncDataSource.setNewDataSource(createNewShardingDataSource());        return dataShardSyncDataSource;    }
  1. 在需要改造的mapper上加上注解
/** * 注解使用: * enableMappingKey 控制是否需要使用mapping功能。 * 如打开,配置后面的MappingKeys(originKey 代表原表的查询字段,targetKey 代表需要映射的该表的shardingkey) */  @DataShardSync(enableMappingKey = true, mappingKeys = {@MappingKeys(originKey ="i  d",targetKey = "user_id")})  public interface CouponMapper extends Mapper<Coupon> {}

2.2 功能介绍

  1. 支持新老数据源数据同步:将配置的old的数据源的表数据同步写一份至new数据源。

  2. 支持mapping路由功能,对于不支持的sql,根据用户指定的映射关系,支持通过mapping表,动态的填入shardingkey.

  3. 在开启事务的模式下,根据开关动态切换事务的执行主题,保证主体事务的完整性。

  4. 支持Apollo动态热切。

  5. 可根据开关配置动态切换读取新表老表数据。

2.3 相关配置

可在application.properties配置文件中配置属性,完全支持spring的相关配置方式:图片

    /**     * 是否开启功能     */    private Boolean enable = true;    /**     * 老表写开关     */    private Boolean oldWrite = true;    /**     * 新表写开关     */    private Boolean newWrite = false;    /**     * 读开关,读哪个数据源,那个就是默认数据源,也是数据源事务,true = 读新库,false = 读老库     */    private Boolean readOldOrNew = false;

3 实现原理

3.1 系统结构图

图片

3.2 mybatis的interceptor拦截器

我们使用了mybatis的拦截器功能,拦截系统所有sql请求。根据系统所执行的sql,我们帮助系统自动的切换数据源进行读写

       @Override    public Object intercept(Invocation invocation) throws Throwable {        //关闭功能,即插件关闭,实现可插拔式        if (dataShardSyncProperties.getEnable() != null && !dataShardSyncProperties.getEnable()){            return invocation.proceed();        }        //如果未加注解的mapper执行,直接返回        if (dataShardSyncAnnotation == null){            return invocation.proceed();        }        //前置处理        beforeProcess(invocation);
//切换数据源且执行 Object result = setDataSourceKeyAndProcess(invocation,mappedStatement);
//后置处理 afterProcess(invocation); return result; }

3.3 封装sharding-jdbc的RouteDecorator

为了支持mapping路由功能,帮助业务解决一些不支持的sql,可根据用户指定的映射关系查询mapping表,动态的为用户的sql填入shardingkey。我们对shardingConditions进行重实现。封装mapping逻辑:

帮助大家回忆一下,,我们前面提到在mapper类上配置了该注解:

/** * 注解使用: * enableMappingKey 控制是否需要使用mapping功能。 * 如打开,则配置后面的MappingKeys(originKey 代表原表的查询字段,targetKey 代表需要映射的该表的shardingkey) */  @DataShardSync(enableMappingKey = true, mappingKeys = {@MappingKeys(originKey ="i  d",targetKey = "user_id")})  public interface CouponMapper extends Mapper<Coupon> {}
  1. 读请求时,我们根据sql的条件,查询mapping是否存在映射关系,帮助找到分片键信息

  2. 写请求时,我们根据sql内的分片键和配置的映射关系,自动的在mapping表插入一条映射关系记录

 /**     *  对shardingConditions进行重实现。封装mapping逻辑。     * @param parameters     * @param sqlStatementContext     * @param schemaMetaData     * @param shardingRule     * @return     */    @Override    public ShardingConditions getShardingParams(List<Object> parameters, SQLStatementContext sqlStatementContext, SchemaMetaData schemaMetaData, ShardingRule shardingRule) {        ShardingConditions shardingConditions = super.getShardingConditions(parameters,sqlStatementContext,schemaMetaData,shardingRule);        try {            DataShardSyncAnnotation dataShardSyncAnnotation = DataShardSyncContext.dataShardSyncAnnotationThreadLocal.get();            if (dataShardSyncAnnotation != null){                //如果开启了配置映射功能                if (dataShardSyncAnnotation.getEnableMappingKey()){                    for (Map.Entry<String,String> entry : dataShardSyncAnnotation.getShardKeyMapping().entrySet()) {                        if (sqlStatementContext instanceof InsertStatementContext){                            //如果是插入语句                            buildShardingConditionForInsert(entry.getKey(),entry.getValue(),parameters,sqlStatementContext,schemaMetaData,shardingRule);                        } else {                            //如果是查询,删除,更新语句                            if (CollectionUtils.isEmpty(shardingConditions.getConditions())){                                try {                                    shardingConditions = buildShardingConditionForWhere(entry.getKey(),entry.getValue(),parameters,sqlStatementContext,schemaMetaData,shardingRule);                                    if (!CollectionUtils.isEmpty(shardingConditions.getConditions())){                                        break;                                    }                                } catch (Exception e){                                    logger.error("buildShardingCondition by shardingMapping error!" + e.getMessage());                                    e.printStackTrace();                                }                            }                        }                    }                }            }        } finally {            DataShardSyncContext.dataShardSyncAnnotationThreadLocal.remove();        }        return shardingConditions;    }

3.4 对多数据源事务的处理

我们进行新老数据源双写的时候,保证一个原则:

  1. 未全量切读到新库前,我们以老库的事务为主,新库写失败不影响业务,不回滚
  2. 切读到新库之后,我们以新库的事务为主,老库写失败不影响业务,不回滚

我们通过DataX的全量数据比对机制来做新老库的数据检查,知道观察一段时间数据保持一致,方可切读到新库,停双写。

图片

DataX是我们的数据同步以及数据比对工具,还具有数据差错处理,可自动修补数据功能,同时发现差错可邮件告警通知。有兴趣了解DataX的可以联系我,在此就不赘述此工具。

这样我们就保证了切读前,老库数据一致,新库读写失败不影响业务。在数据比对一段时间之后,保证新老库的双写并无问题,方可切读。

4 DataSS的优势


业务自实现数据拆分引入DataSS插件
系统改造研发效率10天2小时
业务侵入耦合在业务代码实现可插拔组件内置双写,路由等操作
非shardingkey查询不支持(会造成全库全表扫描)支持映射关系路由,提高查询效率
使用扩展性1.数据源配置 2.ShardingRoute路由 3.ShardingMapping可定制
使用灵活性支持spring体系的配置,apollo等,可动态切换关闭

作者介绍

  • seven,现任信也科技平台创新部门后端研发资深专家。
  • 韩小文,现任信也科技平台创新部门后端研发专家。
继续滑动看下一个
拍码场
向上滑动看下一个