通过MySQL8的资源组限制CPU占用

2024-08-07 11:41:57 浏览数 (1)

前置条件

在Linux系统上, mysqld可执行命令需要有CAP_SYS_NICE能力,例如,

代码语言:txt复制
$ setcap cap_sys_nice ep /usr/sbin/mysqld
$ getcap /usr/sbin/mysqld                
/usr/sbin/mysqld = cap_sys_nice ep

使用资源组的限制

  1. 目前仅支持对CPU的设定,不包含IO,内存等,因此实际使用场景有限!
  2. 资源组类型只支持USER和SYSTEM两种类型,而常见的线程类型是FOREGROUN和BACKGROUND,直接通过SET RESOURCE GROUP来指定线程的资源组往往会报3661错
  3. 对操作系统平台有强依赖。

查看资源组

代码语言:txt复制
[test]> SELECT * FROM INFORMATION_SCHEMA.RESOURCE_GROUPS;
 --------------------- --------------------- ------------------------ ---------- ----------------- 
| RESOURCE_GROUP_NAME | RESOURCE_GROUP_TYPE | RESOURCE_GROUP_ENABLED | VCPU_IDS | THREAD_PRIORITY |
 --------------------- --------------------- ------------------------ ---------- ----------------- 
| USR_default         | USER                |                      1 | 0-15     |               0 |
| SYS_default         | SYSTEM              |                      1 | 0-15     |               0 |
| SYS_internal        | SYSTEM              |                      1 | 0-15     |               0 |
 --------------------- --------------------- ------------------------ ---------- ----------------- 
3 rows in set (0.00 sec)

我这里DMEO机器是16个逻辑CPU的,因此上面的VCPU_IDS默认都是0-15,表示全部都可以被使用。

线程优先级是线程的执行优先级 分配给资源组。优先级值范围从 -20(最高优先级)到 19(最低优先级)。

默认 对于系统组和用户组,优先级均为 0。

允许系统组具有比用户更高的优先级 组,确保用户线程永远不会有更高的 优先级高于系统线程:

对于系统资源组,允许的优先级范围 是 -20 到 0。

对于用户资源组,允许的优先级范围 是 0 到 19。

创建资源组

资源组管理是其所在服务器的本地管理方式 发生。资源组 SQL 语句和对resource_groups数据字典表不是 写入二进制日志,并且不会被复制。

创建自定义的资源组(表示这个资源组的相关会话可以调度到 cpu2 或者cpu3)

代码语言:txt复制
CREATE RESOURCE GROUP slow_sql TYPE = USER  VCPU = 2-3 THREAD_PRIORITY = 10;

再次查看

代码语言:txt复制
mysql> SELECT * FROM INFORMATION_SCHEMA.RESOURCE_GROUPS WHERE RESOURCE_GROUP_NAME = 'slow_sql'G

将线程分配给slow_sql组

方法1、将已运行中的线程分配给slow_sql组

代码语言:txt复制
SET RESOURCE GROUP slow_sql FOR 59;  -- 这里的59是从performance_schema.threads 里面查出来的

方法2和3,都是只能对尚未执行的sql进行绑定,如下:

代码语言:txt复制
方法2:
[test]> SET RESOURCE GROUP slow_sql;
[test]> select * from sbtest.sbtest2 order by id desc limit 2000,10;
[test]> call xxxx();

方法3:
INSERT /*  RESOURCE_GROUP(sql_thread) */ INTO tbl1 VALUES(1); <== 当前语句会使用sql_thread资源组

修改资源组

代码语言:txt复制
ALTER RESOURCE GROUP sql_thread VCPU = 5, 6 THREAD_PRIORITY = -5;

修改资源组sql_thread绑定的逻辑CPU为编号5,6,并降低优先级到-5。

删除资源组

代码语言:txt复制
DROP RESOURCE GROUP sql_thread ;

注意,如果这个资源组正在使用中,则无法删除,会提示资源繁忙,需要等慢的sql会话断开后(或者被kill掉),才能删除资源组。

禁用资源组

[test]> alter RESOURCE GROUP slow_sql disable;

注意,如果这个资源组正在使用中,虽然能禁用掉,但是删除还是会提示资源繁忙,需要等慢的sql会话断开后(或者被kill掉),才能删除资源组。

实验演示

创建一个存储过程,用于模拟慢sql

代码语言:txt复制
use test;
DELIMITER //
CREATE PROCEDURE intensive_calculations()
BEGIN
    DECLARE v INT DEFAULT 0;
    DECLARE i INT DEFAULT 0;
    WHILE i < 10000000 DO
        SET v = SQRT(i * i   (RAND() * 10000));
        SET i = i   1;
    END WHILE;
END//
DELIMITER ;

开3个窗口,调用存储过程

代码语言:txt复制
use test;
-- 调用存储过程
CALL intensive_calculations();

稍等就可以通过top观察到有3个cpu核心cpu打满了。

创建资源组,绑定到CPU1上(我这台测试机器配置较低只有2个核)

代码语言:txt复制
CREATE RESOURCE GROUP slow_sql TYPE = USER  VCPU=1 THREAD_PRIORITY = 10;

然后找到慢sql对应的thread_id

代码语言:txt复制
SELECT
	THREAD_ID,
	PROCESSLIST_INFO,
	RESOURCE_GROUP,
	PROCESSLIST_TIME 
FROM
	performance_schema.threads 
WHERE
	PROCESSLIST_INFO REGEXP 'SELECT|INSERT|UPDATE|DELETE|CALL' 
	AND PROCESSLIST_TIME > 2  -- 便于观察,把这个值调小了点
	AND PROCESSLIST_USER!=''
	AND PROCESSLIST_COMMAND !='Sleep'
	AND RESOURCE_GROUP='USR_default'
;

假设上面找到的thread_id为 65 67 69。

对于已经在运行状态的sql会话,需要使用下面的set 命令才能将其绑定到资源组上

代码语言:txt复制
[test]> SET RESOURCE GROUP slow_sql FOR 65;
[test]> SET RESOURCE GROUP slow_sql FOR 67;
[test]> SET RESOURCE GROUP slow_sql FOR 69;

再次查看

代码语言:txt复制
[test]> SELECT THREAD_ID, PROCESSLIST_INFO, RESOURCE_GROUP, PROCESSLIST_TIME  
FROM performance_schema.threads  
WHERE  PROCESSLIST_USER!='' G

再次查看cpu的负载,如下:

耗时对比:

结合自动化运维

python编写守护进程

1、连接到mysql后,尝试创建资源组

2、while true死循环,每隔几秒检测一次ps.threads表,将慢查询的thread_id绑定到step1的资源组上

3、发送一个dingding消息,通知到DBA(可选)

代码如下:

config.py

代码语言:txt复制
# -*- coding: utf-8 -*-
hostip = "192.168.3.14"
username = "dts"
password = "dts"
dbname = "test"
dbport = 3306
charset = "utf8"
long_time = 10

main.py

代码语言:txt复制
# -*- coding: utf-8 -*-
# 参考 https://github.com/hcymysql/imprison_rg/blob/main/imprison_rg.php
# 注意,建议和MySQL部署在同一个机器上,便于获取到cpu核心数,不然的话只能像我代码里这样写死cpu

import mysql.connector
import logging
import configs
import time

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def add_resource_group(group_name, cpu_core=1):
    mydb = mysql.connector.connect(
        host=configs.hostip,
        port=configs.dbport,
        user=configs.username,
        passwd=configs.password,
        charset=configs.charset,
    )
    cursor = mydb.cursor()

    add_sql = f"CREATE RESOURCE GROUP {group_name} TYPE = USER  VCPU={cpu_core} THREAD_PRIORITY = 10;"
    try:
        cursor.execute(add_sql)
    except Exception as e:
        # print(e)
        logger.warning(f"{e}")


def bind_slowlog_rg(group_name, long_time):
    mydb = mysql.connector.connect(
        host=configs.hostip,
        port=configs.dbport,
        user=configs.username,
        passwd=configs.password,
        charset=configs.charset,
    )
    cursor = mydb.cursor()
    # 只关注RESOURCE_GROUP为USR_default且存在慢查询的thread
    query_sql = f"SELECT THREAD_ID, PROCESSLIST_INFO, RESOURCE_GROUP,PROCESSLIST_TIME FROM performance_schema.threads WHERE PROCESSLIST_INFO REGEXP 'SELECT|INSERT|UPDATE|DELETE|CALL' AND RESOURCE_GROUP='USR_default' AND PROCESSLIST_COMMAND!='Sleep' AND PROCESSLIST_TIME > {long_time} AND PROCESSLIST_USER!=''"

    try:
        cursor.execute(query_sql)
        res = cursor.fetchall()
        for thread in res:
            logger.info(
                f"SQL明细:{thread[1]},持续时间(秒): {thread[3]},用户名: {thread[4]},地址: {thread[5]},库名: {thread[6]},状态: {thread[7]},会话ID: {thread[8]}"
            )
            set_sql = f"SET RESOURCE GROUP {group_name} FOR {thread[0]};"
            cursor.execute(set_sql)
            # 这里还可以加一个钉钉告警

    except Exception as e:
        logger.warning(f"{e}")


if __name__ == "__main__":
    add_resource_group(group_name="slow_sql", cpu_core=1)
    while True:
        bind_slowlog_rg(group_name="slow_sql", long_time=5)
        time.sleep(2)

运行结果如下:

0 人点赞