基于ShardingSphereMybatisPlus实现影子库ShardingSphere影子库简介 ApacheShardingSphere通过解析SQL,对传入的SQL进行影子判定,根据配置文件中用户设置的影子规则,路由到生产库或者影子库。架构图如下所示: 应用场景 影子库的应用场景是在全链路压测中对DB进行数据隔离。大白话就是压测数据进入影子库,用户正常请求的数据进入线上正式库,如下图所示: 项目搭建技术框架 SpringBootMybatisPlusShardingSphereMySQLPOM依赖dependenciesdependencygroupIdorg。springframework。bootgroupIdspringbootstarterwebartifactId!exclusions!exclusion!springbootstarterloggingartifactId!groupIdorg。springframework。bootgroupId!exclusion!exclusionsdependencydependencygroupIdorg。springframework。bootgroupIdspringbootstartertestartifactIdscopetestscopedependencydependencygroupIdorg。projectlombokgroupIdlombokartifactIdversion{lombok。version}versiondependencydependencygroupIdorg。mybatis。spring。bootgroupIdmybatisspringbootstarterartifactIdversion{mybatis。version}versiondependencydependencygroupIdcom。baomidougroupIdmybatisplusbootstarterartifactIdversion{mybatis。plus。version}versiondependencydependencygroupIdcom。baomidougroupIdmybatisplusgeneratorartifactIdversion{mybatis。plus。version}versiondependencydependencygroupIdorg。apache。shardingspheregroupIdshardingspherejdbccorespringbootstarterartifactIdversion5。1。0versiondependencydependencygroupIdorg。apache。shardingspheregroupIdshardingspherejdbcspringbootstarterinfraartifactIdversion5。1。0versiondependencydependencygroupIdorg。apache。shardingspheregroupIdshardingsphereshadowspringbootstarterartifactIdversion5。1。0versiondependencydependencies影子库实现场景需求 假设一个电商网站要对下单业务进行压测。压测相关表torder为影子表,生产数据执行到master生产数据库,压测数据执行到数据库shadow影子库。 压测标识使用ThreadLocal来存储,值为true:表示压测流量,false:表示正常流量。影子算法使用Hint影子算法,配置的属性test:test。压测数据需要在原sql基础上添加test:test路由到影子库。 建表脚本CREATETABLEtorder(orderidint(11)NOTNULL,useridint(11)NOTNULL,PRIMARYKEY(orderid))ENGINEInnoDBDEFAULTCHARSETutf8mb4yml配置影子库配置spring:shardingsphere:datasource:names:master,shadowmaster:type:com。zaxxer。hikari。HikariDataSourcedriverclassname:com。mysql。cj。jdbc。Driverjdbcurl:jdbc:mysql:127。0。0。1:3306test?useSSLtruecharsetutf8mb4serverTimezoneHongkongusername:rootpassword:123456shadow:type:com。zaxxer。hikari。HikariDataSourcedriverclassname:com。mysql。cj。jdbc。Driverjdbcurl:jdbc:mysql:127。0。0。1:3306testshadow?useSSLtruecharsetutf8mb4serverTimezoneHongkongusername:rootpassword:123456rules:sqlparser:开启支持Hint影子算法sqlcommentparseenabled:trueshadow:datasources:定义影子数据源笔名shadowdatasource0:sourcedatasourcename:mastershadowdatasourcename:shadowtables:定义影子表tordertorder:使用数据源别名datasourcenames:shadowdatasource0影子算法shadowalgorithmnames:fltesthintalgorithmshadowalgorithms:fltesthintalgorithm:type:SIMPLEHINTprops:ShadowInterceptor拦截SQL,在原SQL后添加test:test,有这个hint就会路由到shadow库中。test:testprops:ahrefhttps:www。bs178。comrizhitargetblankclassinfotextkey日志a输出到控制台sqlshow:truemybatisplus:mapperlocations:classpath:mapper。xmltypealiasespackage:com。hqg。study。example。entityglobalconfig:dbconfig:idtype:AUTOfieldstrategy:NOTNULLconfiguration:logimpl:org。apache。ibatis。logging。stdout。StdOutImplswagger:enable:trueSQL拦截器 拦截器根据ThreadLocalUtil。isTest()来检测当前是否是压测流量,如果是的话,会在原sql基础上添加test:test路由到影子库。ComponentSetterAccessors(chaintrue)Intercepts({Signature(typeStatementHandler。class,methodprepare,args{Connection。class,Integer。class})})publicclassShadowInterceptorextendsAbstractSqlParserHandlerimplementsInterceptor{OverridepublicObjectintercept(Invocationinvocation)throwsThrowable{StatementHandlerstatementHandlerPluginUtils。realTarget(invocation。getTarget());MetaObjectmetaObjectSystemMetaObject。forObject(statementHandler);SQL解析this。sqlParser(metaObject);针对定义了rowBounds,做为mapper接口方法的参数BoundSqlboundSql(BoundSql)metaObject。getValue(delegate。boundSql);StringoriginalSqlboundSql。getSql();压测时,通过注解走的影子库if(ThreadLocalUtil。isTest()){originalSqloriginalSqltest:}metaObject。setValue(delegate。boundSql。sql,originalSql);returninvocation。proceed();}OverridepublicObjectplugin(Objecttarget){if(targetinstanceofStatementHandler){returnPlugin。wrap(target,this);}}}代码测试测试影子库TestpublicvoidtestShadow(){ThreadLocalUtil。setTest(true);TOrdertOrdernewTOrder();tOrder。setOrderId(3);tOrder。setUserId(3);tOrderMapper。insert(tOrder);}结果演示 ActualSQL:shadow:::INSERTINTOtorder(orderid,userid)VALUES(?,?)test:test:::〔3,3〕