其他
基于MySQL内核的SQL限流设计与实现|得物技术
目录
一、引言
1. 编写目的
2. 需求概述
3. 软件结构
4. 参考资料
二、概要设计
1. 架构设计
2. 流程图
三、详细设计
1. 功能设计
2. 性能设计
3. 功能限制
四、总结
一
引言
编写目的
需求概述
软件结构
参考资料
二
概要设计
架构设计
流程图
三
详细设计
功能设计
对外接口
开关控制命令(du_flow_control)
用于控制限流功能是否启用。
大小写敏感命令(du_flow_control_case_sensitive)
用于控制限流规则匹配时是否大小写敏感。
预留用户命令(du_flow_control_reserve_user)
限流功能对于预留用户不生效。 预留用户参数以字符串的形式接受输入,如果存在多个预留用户,使用 ',' 进行分隔。 在服务启动时,需格式化该参数,后续在做限流判断时,需要根据格式化之后参数识别是否是预留用户,如果是预留用户,则不进行限流操作,无需进行后续的限流规则匹配。 预留用户参数接受NULL和空串 '',当该参数为NULL或空串时,表明所有用户都不是预留用户。 对于预留用户字符串的处理,与MySQL社区对于用户名的处理逻辑保持一致,即忽略每个用户名前后的无意义字符(如空格、换行等),保留用户名之间的无意义字符。 分隔符设置命令(du_flow_control_delimiter)
用于控制限流规则的分隔符。 分隔符不可为空,且长度小于等于1024。 修改分隔符之后,需要重新加载限流规则,对限流规则进行解析,会消耗系统资源,因此不建议在系统负载过高时修改分隔符。
// 更新
static bool update_delimiter(sys_var *self, THD *thd, enum_var_type type)
{
reload_rules(thd);
}
// 校验
static bool check_delimiter(sys_var *self, THD *thd, set_var *var)
{
judge(var->value);
judge(str);
judge(length);
}
功能模块详细设计
规则管理
读取
主动执行自定义读取命令,用于更新限流规则到内存。每当添加限流规则后,需手动执行该操作,更新限流规则到内存。 修改分隔符时。修改分隔符后,限流规则需要重新解析,因此也需要重新读取。 数据库实例启动时。基于性能考虑,在实例启动时,将限流规则加载到内存中。 移除
系统停止时移除限流规则时。 手动删除限流规则时。 解析
将物理表中的限流规则字符串读取到内存字符串中。 根据分隔符将字符串解析为关键字组成的模式串链表。 流程控制
启动时: 加载、解析限流规则到内存中。 解析预留用户。
int mysqld_main(int argc, char **argv)
{
...
load_rules();
...
}
执行时: 在具体执行语句之前对查询语句进行判断,如果当前的执行线程是复制相关的系统线程、存储过程和方法、用户是预留用户,则无需进行规则匹配;否则,根据规则匹配的结果来决定是否进行SQL限流。 匹配的效率与限流规则的数量、大小、查询串的大小都有关系,由于此时解析完成的限流规则都已在内存中,因此整个匹配过程消耗资源较少。但还是建议用户设置的限流规则更加通用、长度更短、数量更少,这样更能提高限流功能的执行效率。 查询执行完成后,维护对应限流规则的当前并发度。具体实现为在thd中添加id字段,在进行限流时,id为非0值,如果在流程中判断id非0,且限流功能已开启,则在限流规则中查找,根据规则节点的id与thd->id进行匹配,如果存在匹配的限流规则,则将其当前并发度减一。
void dec_conc(THD *thd, int command)
{
// 根据查询类型在对应链表找节点
node = find_by_id(list, thd->id);
// 并发数量减1
if (node) {
__sync_sub_and_fetch(&(node->concur), 1);
}
// 重置状态
thd->id = 0;
}
关闭时:
void clean_up()
{
cleanup();
}
限流匹配
根据DB判断是否是对系统表的查询,如果是对系统表的查询,不做限流。
/* The flow control does not take effect on system tables */
if (check_system_table(first_table->db)) {
return ret;
}
针对不同的操作类型,在相应的限流规则链表上做模式匹配。 获取并解析链表上的节点,根据链表节点中保存的关键字串与查询串匹配; 如果关键字串都匹配到,则匹配成功。
bool check_rule_matched(THD* thd, LIST* list)
{
while (满足条件,无异常) {
// 根据大小写开关是否打开,分别进行模式串匹配
it = find(query_str, item->key_array[nums]);
// 如果it为空,没有匹配到,查看下一个list,否则继续匹配当前限流规则节点
judge();
}
// 匹配成功,或者对下一个节点进行匹配
}
在匹配过程中维护原子变量cur_concur、cur_reject、total_reject,分别表示当前并发数、当前限流次数、总的限流次数,用以判断是否需要进行限流以及在系统运行期间观察SQL限流的执行状态。 数据获取
class Du_table_access {
public:
Du_table_access() : m_drop_thd_object(NULL) {}
virtual ~Du_table_access() {}
// 初始化打开表的环境、锁表并且打开表
bool init(THD **thd, TABLE **table, bool is_write);
// 关闭表,清理环境
bool deinit(THD *thd, TABLE *table, bool error, bool need_commit);
// 设置打开表的策略
void before_open(THD *thd);
// 如果需要的话创建线程,大部分时候并不需要,因为手动执行读取数据的时候已经在线程中了
THD *create_thd();
// 如果手动创建了 thd,则需要手动清理
void drop_thd(THD *thd);
};
限流规则表设计
定义系统表保存限流规则,表格式如下:
SET @cmd= "CREATE TABLE IF NOT EXISTS du_flow_control_rules (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT 'Id of the flow control rules.',
type ENUM('SELECT', 'UPDATE', 'INSERT', 'DELETE') CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'Type of flow control rules.',
max_concur INT NOT NULL COMMENT 'Max concurrent of sql.',
orig_str VARCHAR(1024) CHARSET SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'Original string of flow control rules.',
PRIMARY KEY(id)) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT 'Flow control rules info.'";
SET @str=IF(@have_innodb <> 0, CONCAT(@cmd, ' ENGINE= INNODB;'), CONCAT(@cmd, ' ENGINE= MYISAM;'));
PREPARE stmt FROM @str;
EXECUTE stmt;
DROP PREPARE stmt;
权限与格式控制
DELIMITER $$
CREATE DEFINER='root'@'localhost' PROCEDURE mysql.add_flow_control ( IN sql_type INT, IN str VARCHAR(1024), IN max_num INT )
COMMENT '
Description
-----------
Basic functions for inserting rules.
It is not recommended to call it directly, but to call it through add_select_flow_control、
add_update_flow_control、add_update_flow_control and add_delete_flow_control.
'
SQL SECURITY INVOKER
BEGIN
IF (sql_type = 0) THEN
INSERT INTO mysql.du_flow_control_rules(type, max_concur, orig_str) VALUES('SELECT', max_num, str);
ELSEIF (sql_type = 1) THEN
INSERT INTO mysql.du_flow_control_rules(type, max_concur, orig_str) VALUES('UPDATE', max_num, str);
ELSEIF (sql_type = 2) THEN
INSERT INTO mysql.du_flow_control_rules(type, max_concur, orig_str) VALUES('INSERT', max_num, str);
ELSEIF (sql_type = 3) THEN
INSERT INTO mysql.du_flow_control_rules(type, max_concur, orig_str) VALUES('DELETE', max_num, str);
ELSE
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Sql type is error, please input correctly.';
END IF;
END$$
CREATE DEFINER='root'@'localhost' PROCEDURE mysql.add_select_flow_control (IN str VARCHAR(1024), IN max_num INT )
COMMENT '
Description
-----------
Used to add select type rules to the current rule table.
Parameters
-----------
str (VARCHAR(1024)):
The string of select rules entered by user.
max_num (INT):
The number of queries that can be executed concurrently.
Example
--------
mysql> SELECT * FROM du_flow_control_rules;
Empty set (0.00 sec)
mysql> CALL add_select_flow_control(''select~from~t1'', 100);
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM du_flow_control_rules;
+----+--------+------------+----------------+
| id | type | max_concur | orig_str |
+----+--------+------------+----------------+
| 1 | SELECT | 100 | select~from~t1 |
+----+--------+------------+----------------+
1 row in set (0.00 sec)
'
SQL SECURITY INVOKER
BEGIN
CALL add_flow_control(0, str, max_num);
END$$
DELIMITER ;
性能设计
刷新限流规则到节点时,为了提升加载效率,节省不必要的遍历,使用MySQL原生的链表插入方法,且只增删限流规则,不允许修改限流规则。具体流程为,在限流规则节点增加ID字段,该字段与规则表中的自增主键ID对应,即该字段递增。 在从系统表中读取数据后,根据ID可以快速判断出该条记录该插入的情况,此时对于该条记录,可能有两种场景:一是该条记录已经加载并解析到内存(链表中存在该节点),无需再次插入;二是这条记录还没有加载到内存,找到对应位置插入即可。 在数据库实例启动时,从已有系统表中加载一次数据,提升后续限流效率。 使用方面: 由于分隔符的选择决定了限流规则的不同形式,因此修改分隔符会导致限流规则全部重新加载解析一次,尽量不在业务高峰期修改分隔符。 SQL限流的性能取决于限流规则的数量、关键字数量、查询的单词数量,因此在使用时,应尽量使用较为通用的限流规则。
功能限制
当SQL语句匹配多条限流规则时,优先生效最新添加的规则; 在添加SQL限流规则之前,已经开始执行的SQL语句,不会被记入并发数; 存储过程、触发器、函数和对系统表的查询不受SQL限流的限制; 当设置过多限流规则时,对性能有一定影响。
四
总结
往期回顾
文 / Peter
关注得物技术,每周一、三、五更新技术干货
要是觉得文章对你有帮助的话,欢迎评论转发点赞~
未经得物技术许可严禁转载,否则依法追究法律责任。
“
扫码添加小助手微信
如有任何疑问,或想要了解更多技术资讯,请添加小助手微信: