一、背景
Apache ShardingSphere 基于用户的实际使用场景,为用户打造了多种实用功能,包括数据分片、读写分离等。在数据分片功能中,我们发现有些用户涉及到的分片较多,一个分片逻辑表可能对应后端 1000 个物理表,这给用户带来了一定的困扰。例如用户执行 SELECT * FROM t_order
语句则会导致全路由,显然这并不是 OLTP 的使用场景,这种 SQL 可以放到其他的 Proxy 里,避免阻塞其他的请求。但用户可能对 Proxy 并不了解,或者写了 where 条件,却不清楚条件里面不包含分片条件,这样的话一样是需要全路由。全路由会导致 Proxy 性能受到影响,严重情况下将导致正常的请求无法执行。试想一下如果这 1000 个分片都在一个物理库里面,如果并行的话需要取 1000 个连接,如果串行可能请求直接超时。对此用户提出了一个需求,能不能直接拦截这种不合理的请求。
对于这个问题,我们进行了更深层次的思考。如果只是简单拦截这种全路由的操作,只要在代码里面判断一下,并且在配置文件添加一个开关即可满足。那如果用户后面需要设置某个表只读或者要求更新操作必须携带 limit,那是不是又要改代码加配置?这显然与 Proxy 可插拔的逻辑相违背。
基于以上痛点,在刚刚发布的 5.2.0 版本中,Apache ShardingSphere 为用户提供了分片审计的功能。审计既可以是拦截操作也可以是统计操作,与分片和唯一键生成算法类似,审计算法同样支持热插拔,用户可以自定义,并且通过配置即可实现审计。下面我们将结合具体的 SQL 实例,为大家详细解读分片审计的实现逻辑。
二、分片审计接口
Apache ShardingSphere 审计的入口在 org.apache.shardingsphere.infra.executor.check.SQLCheckEngine
类,该类会调用 SQLChecker
接口的 check
方法,目前 ShardingSphere 的审计包括权限审计(验证用户名密码)以及分片审计,这里我们要关心的是分片审计 ShardingAuditChecker 里面实现的父类接口。
通过查看 org.apache.shardingsphere.sharding.checker.audit.ShardingAuditChecker
的 check
代码,我们可以快速了解它的原理。
public interface ShardingAuditAlgorithm extends ShardingSphereAlgorithm {/*** Sharding audit algorithm SQL check.** @param sqlStatementContext SQL statement context* @param parameters SQL parameters* @param grantee grantee* @param database database* @return SQL check result*/SQLCheckResult check(SQLStatementContext<?> sqlStatementContext, List<Object> parameters, Grantee grantee, ShardingSphereDatabase database);
}
该方法会去获取涉及到的所有分片表的审计策略,并且遍历调用每个分片表审计策略里面配置的审计算法,如果有一个审计算法没有通过就直接抛出异常给用户。可能有些用户会好奇这里面的 disableAuditNames 有什么用。分片审计还支持了跳过分片审计的功能。对于有一些场景用户可能就是需要执行一些原本应该被审计拦截的 SQL,并且他们很清楚这条 SQL 带来的影响。对此我们提供了 Hint: disableAuditNames
跳过审计拦截,后面会结合实际的例子来介绍该用法。当然并不是用户想跳过就能跳过的,Proxy 管理员可以配置 allowHintDisable 来控制是否允许用户通过 Hint 的形式跳过分片审计,该参数默认是 true,即允许。
三、分片审计算法
分片审计算法接口 org.apache.shardingsphere.sharding.spi.ShardingAuditAlgorithm
继承自 SPI 类 ShardingSphereAlgorithm
,继承了父类的 type
和 props
两个参数,并且定义了自己的方法 check
,如果想自定义自己的审计算法,只要实现该接口并添加到 META-INF.services
即可。
public interface ShardingAuditAlgorithm extends ShardingSphereAlgorithm {/*** Sharding audit algorithm SQL check.** @param sqlStatementContext SQL statement context* @param parameters SQL parameters* @param grantee grantee* @param database database* @return SQL check result*/SQLCheckResult check(SQLStatementContext<?> sqlStatementContext, List<Object> parameters, Grantee grantee, ShardingSphereDatabase database);
}
Apache ShardingSphere 内部实现了一个比较通用的分片审计算法 org.apache.shardingsphere.sharding.algorithm.audit.DMLShardingConditionsShardingAuditAlgorithm
,也就是前文提到的拦截全路由的 SQL 语句。该算法通过判断分片条件是否为空来拦截,当然如果广播表或者非分片表则不应该拦截。
public final class DMLShardingConditionsShardingAuditAlgorithm implements ShardingAuditAlgorithm {@Getterprivate Properties props;@Overridepublic void init(final Properties props) {this.props = props;}@SuppressWarnings({"rawtypes", "unchecked"})@Overridepublic SQLCheckResult check(final SQLStatementContext<?> sqlStatementContext, final List<Object> parameters, final Grantee grantee, final ShardingSphereDatabase database) {if (sqlStatementContext.getSqlStatement() instanceof DMLStatement) {ShardingRule rule = database.getRuleMetaData().getSingleRule(ShardingRule.class);if (rule.isAllBroadcastTables(sqlStatementContext.getTablesContext().getTableNames())|| sqlStatementContext.getTablesContext().getTableNames().stream().noneMatch(rule::isShardingTable)) {return new SQLCheckResult(true, "");}ShardingConditionEngine shardingConditionEngine = ShardingConditionEngineFactory.createShardingConditionEngine(sqlStatementContext, database, rule);if (shardingConditionEngine.createShardingConditions(sqlStatementContext, parameters).isEmpty()) {return new SQLCheckResult(false, "Not allow DML operation without sharding conditions");}}return new SQLCheckResult(true, "");}@Overridepublic String getType() {return "DML_SHARDING_CONDITIONS";}
}
这里再介绍一个分片审计算法 LimitRequiredShardingAuditAlgorithm
,该算法会拦截 update
和 delete
操作未携带 limit
的 SQL,因为该算法通用性较差,目前并没有集成在 Apache ShardingSphere 内。可以看见要实现一个自定义的算法还是很简单的,这也是为什么要设计分片审计框架的原因,通过热插拔的形式,ShardingSphere 具备了强大的拓展性。
public final class LimitRequiredShardingAuditAlgorithm implements ShardingAuditAlgorithm {@Getterprivate Properties props;@Overridepublic void init(final Properties props) {this.props = props;}@SuppressWarnings({"rawtypes", "unchecked"})@Overridepublic SQLCheckResult check(final SQLStatementContext<?> sqlStatementContext, final List<Object> parameters, final Grantee grantee, final ShardingSphereDatabase database) {if (sqlStatementContext instanceof UpdateStatementContext && !((MySQLUpdateStatement) sqlStatementContext.getSqlStatement()).getLimit().isPresent()) {return new SQLCheckResult(false, "Not allow update without limit");}if (sqlStatementContext instanceof DeleteStatementContext && !((MySQLDeleteStatement) sqlStatementContext.getSqlStatement()).getLimit().isPresent()) {return new SQLCheckResult(false, "Not allow delete without limit");}return new SQLCheckResult(true, "");}@Overridepublic String getType() {return "LIMIT_REQUIRED";}
}
四、分片审计的使用
分片审计需要给逻辑表配置审计策略,为了便于大家快速上手,分片审计的配置和分片算法以及分片键值生成器一样,有一个算法定义以及策略定义,同时还支持默认的审计策略。如果逻辑表里面配置了审计策略则只对逻辑表生效,如果配置了 defaultAuditStrategy
则对该分片规则下的所有逻辑表生效。auditors
类似 shardingAlgorithms
, auditStrategy
类似 databaseStrategy
或 databaseStrategy
,defaultAuditStrategy
类似 defaultDatabaseStrategy
或 defaultTableStrategy
。
参考配置如下,这里只添加了分片审计相关的配置,分片算法、数据源等配置请自行添加。
rules:- !SHARDINGtables:t_order:actualDataNodes: ds_${0..1}.t_order_${0..1}auditStrategy:auditorNames:- sharding_key_required_auditorallowHintDisable: truedefaultAuditStrategy:auditorNames:- sharding_key_required_auditorallowHintDisable: trueauditors:sharding_key_required_auditor:type: DML_SHARDING_CONDITIONS
第一步我们执行一个查询操作,由于配置了拦截全库路由的审计策略,直接抛出了错误。
mysql> select * from t_order;
ERROR 13000 (44000): SQL check failed, error message: Not allow DML operation without sharding conditions
第二步我们添加 HINT,HINT 名称是 /* ShardingSphere hint: disableAuditNames */
,disableAuditNames
后跟随的是上面配置的 auditorsNames,如果是多个的话用空格分隔,例如 /* ShardingSphere hint: disableAuditNames=auditName1 auditName2*/
,我们使用该 HINT 后可以看见 SQL 操作执行成功。
mysql> /* ShardingSphere hint: disableAuditNames=sharding_key_required_auditor */ select * from t_order;
+----------+---------+------------+--------+
| order_id | user_id | address_id | status |
+----------+---------+------------+--------+
| 30 | 20 | 10 | 20 |
| 32 | 22 | 10 | 20 |
+----------+---------+------------+--------+
2 rows in set (0.01 sec)
这里再给大家说一些注意事项,如果要使用 HINT 需要修改 Proxy 的 server.yaml
配置。另外如果你是直接使用 MySQL 的终端连接 Proxy,需要添加 -c
参数,否则 HINT 注释将在 MySQL 终端被过滤掉,自然也就无法到后端被 Proxy 解析到了。
rules:- !SQL_PARSERsqlCommentParseEnabled: truesqlStatementCache:initialCapacity: 2000maximumSize: 65535parseTreeCache:initialCapacity: 128maximumSize: 1024
props:proxy-hint-enabled: true
mysql -uroot -proot -h127.0.0.1 -P3307 -c
五、分片审计 DistSQL
目前 Apache ShardingSphere 也已经支持了分片审计的部分 DistSQL,5.2.0 版本支持如下 DistSQL,具体以官方 Release Note 为准:https://github.com/apache/shardingsphere/discussions/20639。
CREATE SHARDING AUDITOR
ALTER SHARDING AUDITOR
SHOW SHARDING AUDIT ALGORITHMS
未来版本将支持以下 DistSQL:
DROP SHARINDG AUDITOR
SHOW UNUSED SHARIDNG AUDIT ALGORITHMS
CREATE SHARDING TABLE RULE # 包含AUDIT_STRATEGY
本文详细介绍了分片审计的基本实现原理以及具体的使用示例,相信通过本文读者朋友们对分片审计都有了一些基本的了解,大家可以根据自己的需求使用分片审计或者自定义算法,如果是通用算法也欢迎大家向社区提交。在使用过程中遇到任何问题或者有任何想法,都欢迎来社区反馈。
GitHub 地址:https://github.com/apache/shardingsphere
中文社区:https://community.sphere-ex.com/
作者
黄挺,腾讯金融科技工程师,ShardingSphere Committer,主要负责与 Proxy 相关的数据分片审计及事务特性等研发工作。