ShardingSphere 5.2.0:分片审计功能拦截多分片场景下的不合理请求
一、背景
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 {
@Getter
private Properties props;
@Override
public void init(final Properties props) {
this.props = props;
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public 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, "");
}
@Override
public String getType() {
return "DML_SHARDING_CONDITIONS";
}
}
这里再介绍一个分片审计算法 LimitRequiredShardingAuditAlgorithm
,该算法会拦截 update
和 delete
操作未携带 limit
的 SQL,因为该算法通用性较差,目前并没有集成在 Apache ShardingSphere 内。可以看见要实现一个自定义的算法还是很简单的,这也是为什么要设计分片审计框架的原因,通过热插拔的形式,ShardingSphere 具备了强大的拓展性。
public final class LimitRequiredShardingAuditAlgorithm implements ShardingAuditAlgorithm {
@Getter
private Properties props;
@Override
public void init(final Properties props) {
this.props = props;
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public 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, "");
}
@Override
public String getType() {
return "LIMIT_REQUIRED";
}
}
四、分片审计的使用
分片审计需要给逻辑表配置审计策略,为了便于大家快速上手,分片审计的配置和分片算法以及分片键值生成器一样,有一个算法定义以及策略定义,同时还支持默认的审计策略。如果逻辑表里面配置了审计策略则只对逻辑表生效,如果配置了 defaultAuditStrategy
则对该分片规则下的所有逻辑表生效。auditors
类似 shardingAlgorithms
, auditStrategy
类似 databaseStrategy
或 databaseStrategy
,defaultAuditStrategy
类似 defaultDatabaseStrategy
或 defaultTableStrategy
。
参考配置如下,这里只添加了分片审计相关的配置,分片算法、数据源等配置请自行添加。
rules:
- !SHARDING
tables:
t_order:
actualDataNodes: ds_${0..1}.t_order_${0..1}
auditStrategy:
auditorNames:
- sharding_key_required_auditor
allowHintDisable: true
defaultAuditStrategy:
auditorNames:
- sharding_key_required_auditor
allowHintDisable: true
auditors:
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_PARSER
sqlCommentParseEnabled: true
sqlStatementCache:
initialCapacity: 2000
maximumSize: 65535
parseTreeCache:
initialCapacity: 128
maximumSize: 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 相关的数据分片审计及事务特性等研发工作。