1. MySQL resource group简介
MySQL-8.0中新增了resource group资源组的功能。MySQL资源组的想法来源很简单:每个资源组是一个资源独立的单位,每个资源组能够容纳一个或者多个MySQL线程。拥有设置资源组权限的DBA们能够创建、配置资源组以及指定、切换MySQL线程从属的资源组,从而更加精准地管控MySQL。
每个MySQL资源组的属性包括:
- Name:资源组名
- CPU affinity:可以使用的VCPU编号,系统可用的VCPU编号可以通过cat cat /proc/cpuinfo命令查看,processor字段就是对应的VCPU的编号。
- OS thread priority:线程优先级,范围[-19,20],数字越低优先级越高。默认优先级为0。系统线程允许设置优先级低于0,用户线程不允许设置优先级低于0。
- Group type:资源组类型,MySQL所有的线程分为两类:system(background) 和 user(foreground),前者包括Master Thread、IO Thread, Purge Thread等,后者则为用户连接线程。因此资源组的类型也只有两类:FOREGROUND和BACKGROUND。后台线程只能放入BACKGROUND类型的资源组中。
- Enabled flag:是否启用,1表示启用, 0表示未启用。
资源组功能引入了两个新的权限:RESOURCE_GROUP_ADMIN(用于资源组创建、修改、删除的权限)、RESOURCE_GROUP_USER(用于指定MySQL线程到资源组的权限)。系统启动之后,会创建两个默认的资源组:用户资源组 (USR_Default) 和系统资源组 (SYS_Default)。默认的资源组的CPU优先级为0,并且不绑定CPU。所有用线程将被归为USR_Default中,所有系统后台线程被归为SYS_Default中。拥有RESOURCE_GROUP_ADMIN权限的用户可以使用以下命令操作资源组:
- 创建新的资源组:
CREATE RESOURCE GROUP 'name' TYPE=SYSTEM|USER [VCPU=num|start-end[,num|start-end]*] [THREAD_PRIORITY=num] [ENABLE|DISABLE] ;
- 修改资源组:
ALTER RESOURCE GROUP 'name' [VCPU=num|start-end[,num|start-end]*] [THREAD_PRIORITY=num] [ENABLE|DISABLE] [FORCE] ;
- 删除资源组:
DROP RESOURCE GROUP 'name' [FORCE];
配置好的资源组可以使用SELECT * FROM INFORMATION_SCHEMA.RESOURCE_GROUPS
命令查看。在创建和配置好资源组后,可以通过SET RESOURCE GROUP 'name' FOR thread_id1, thread_id2,thread_id3, ...;
命令将线程指定到目标的资源组中。值得注意的是show processlist显示的session id,并不是thread id。thread id的查询方法是:SELECT * FROM performance_schema.threads;
。
除了上述使用方法之外,资源组还支持使用语句提示(query hints)的方式使用资源组:支持在SELECT, UPDATE, INSERT, REPLACE和DELETE语句中通过添加/*+ RESOURCE_GROUP(resource_group_name) */的方式将query执行的过程放置到指定资源组,当query执行完毕后再迁移回旧的资源组。query hints的支持使得资源组的使用更加灵活。特别是对于通过应用程序生成的query,有时就像黑匣子一般,很难确定其具体开销,将它们通过query hints指定到特定资源组是一个很好的选择。
resource group在复制的主从库中需要分别单独配置,因此create、alter、drop资源组的操作是不会记录binlog的。
2. MySQL resource group的实现
在简单了解了resource group的功能后,接下来我们了解一下resource group的实现。resource group功能包括引入新的语法,新的parse classes,核心运行时组件、持久化组件、Performance schema组件、权限鉴定组件、query hints组件等。本文将基于MySQL-8.0.22的源码介绍新增的parser classes、平台相关API、运行时组件、PFS组件、持久化组件等部分,对其他模块感兴趣的同学欢迎参考官方WL#9467。
2.1 新增的Parser classes
在旧版本的MySQL中(例如MySQL-5.7),不同的sql_command需要执行什么操作是全部罗列在mysql_execute_command中的。因此在旧的版本中mysql_execute_command函数非常臃肿复杂,代码量接近3000行,难以维护。为了增加代码的可读性以及可维护性,MySQL 8.0对从语法解析到命令执行的全路径做了重构,将大部分parse和execute模块封装进了Parser classes中,这次介绍的resource group也不例外。
新增的Parser类包括两类:一类是PT_create_resource_group、PT_alter_resource_group、PT_drop_resource_group、PT_set_resource_group,都继承自Parse_tree_root,是词法语法分析完后创建的,用来设置sql_command,完成基本参数检查的类。另一类是Sql_cmd_create_resource_group、Sql_cmd_alter_resource_group、Sql_cmd_drop_resource_group、Sql_cmd_set_resource_group,继承自Sql_cmd,封装了mysql server处理不同类型的请求时的具体行为的类。PT_create_resource_group是Sql_cmd_create_resource_group的友元,能够访问后者的所有变量。
下面以PT_create_resource_group为例进行简单地介绍:
class PT_create_resource_group final : public Parse_tree_root {
resourcegroups::Sql_cmd_create_resource_group sql_cmd;
const bool has_priority;
public:
PT_create_resource_group(
const LEX_CSTRING &name, const resourcegroups::Type type,
const Mem_root_array *cpu_list,
const Value_or_default &opt_priority, bool enabled)
: sql_cmd(name, type, cpu_list,
opt_priority.is_default ? 0 : opt_priority.value, enabled),
has_priority(!opt_priority.is_default) {}
Sql_cmd *make_cmd(THD *thd) override;
};
Sql_cmd *PT_create_resource_group::make_cmd用于简单地检查resource_group name的长度是否符合规范、检查线程优先级是否符合要求(例如优先级是否在-19到20的范围内,用户优先级不得低于0等)、以及设定thd->lex→sql_command为SQLCOM_CREATE_RESOURCE_GROUP。
resourcegroups::Sql_cmd_create_resource_group继承自Sql_cmd基类,封装了sql_comand为SQLCOM_CREATE_RESOURCE_GROUP时,mysql server需要执行的行为。其定义如下:
class Sql_cmd_create_resource_group : public Sql_cmd {
friend class ::PT_create_resource_group;
public:
Sql_cmd_create_resource_group(const LEX_CSTRING &name, const Type type,
const Mem_root_array *cpu_list,
int priority, bool enabled)
: m_name(name),
m_type(type),
m_cpu_list(cpu_list),
m_priority(priority),
m_enabled(enabled) {}
enum_sql_command sql_command_code() const override {
return SQLCOM_CREATE_RESOURCE_GROUP;
}
bool execute(THD *thd) override;
private:
const LEX_CSTRING m_name;
const Type m_type;
const Mem_root_array *m_cpu_list;
int m_priority;
bool m_enabled;
};
resourcegroups::Sql_cmd_create_resource_group::execute中封装了上述行为,下面我们简单看一下resourcegroups::Sql_cmd_create_resource_group::execute的逻辑:
bool resourcegroups::Sql_cmd_create_resource_group::execute(THD *thd) {
// 如果当前系统是只读的,则直接错误
check_readonly
//校验当前用户是否有RESOURCE_GROUP_ADMIN权限
Security_context *sctx = thd->security_context();
sctx->has_global_grant(STRING_WITH_LEN("RESOURCE_GROUP_ADMIN")).first)
// Resource group名称校验
is_invalid_string
// VCPU IDs list校验
validate_vcpu_range_vector
// 获取共享backup锁
acquire_shared_global_read_lock
acquire_shared_backup_lock
// 获取资源组MDL排它锁
acquire_exclusive_mdl_for_resource_group
// 检查是否已经有同名的资源组,有则返回错误
auto res_grp_mgr = Resource_group_mgr::instance();
res_grp_mgr->get_resource_group
dd::resource_group_exists
// 在资源组管理hash表中加入该资源组,其中res_grp_mgr是资源组管理类
res_grp_mgr->create_and_add_in_resource_group_hash
// 在持久化的字典表中登记该资源组,详见2.5持久化组件
dd::create_resource_group
}
2.2 平台相关API
不同的平台对于CPU优先级配置的API不尽相同,不同平台的api被封装在了sql/resourcegroups/platform文件夹下,供上层调用,代码层次图如下:
sql/resourcegroups/
|-- platform
| |-- thread_attrs_api.h
| |-- thread_attrs_api_apple.cc
| |-- thread_attrs_api_common.cc
| |-- thread_attrs_api_freebsd.cc
| |-- thread_attrs_api_linux.cc
| |-- thread_attrs_api_solaris.cc
| `-- thread_attrs_api_win.cc
|-- resource_group.h
|-- resource_group_basic_types.h
|-- resource_group_mgr.cc
|-- resource_group_mgr.h
|-- resource_group_sql_cmd.cc
|-- resource_group_sql_cmd.h
|-- thread_resource_control.cc
`-- thread_resource_control.h
下面以linux平台为例,介绍需要使用到的API:
// 用于设置将当前线程绑定到指定的CPU上
bind_to_cpu(cpu_id_t cpu_id):
// 用于将thread id对应的线程绑定到指定的CPU上
bind_to_cpu(cpu_id_t cpu_id, my_thread_os_id_t thread_id)
// 限制当前线程只能运行在指定的CPU list上
bind_to_cpus(const std::vector &cpu_ids)
// 限制thread_id对应的线程绑定到指定的CPU上
bind_to_cpus(const std::vector &cpu_ids, my_thread_os_id_t thread_id)
// 解除当前thread对特定CPU绑定,当前线程允许运行在所有可用CPU上
unbind_thread()
// 解除thread_id对应的线程对特定CPU绑定,当前线程允许运行在所有可用CPU上
unbind_thread(my_thread_os_id_t thread_id)
// 返回当前线程的优先级
thread_priority()
// 返回thread_id对应的线程的优先级
thread_priority(my_thread_os_id_t thread_id)
// 设置当前线程的优先级
set_thread_priority(int priority)
// 设置thread_id对应的线程的优先级
set_thread_priority(int priority, my_thread_os_id_t thread_id)
// 或者当前系统可用的CPU个数
get_num_vcpus()
// 检查输入的优先级是否有效。当前该值的有效范围是[-19,20](例如Unix 平台)
is_valid_thread_priority(int priority)
2.3 运行时组件
运行时组件封装在resourcegroups命名空间中。主要有三种类:resourcegroups::Resource_group_mgr、resourcegroups::Resource_group、resourcegroups::Thread_resource_control。其中resourcegroups::Resource_group_mgr是一个用来管理内存态资源组的单例类。resourcegroups::Resource_group是资源组的内存态。resourcegroups::Thread_resource_control是设置内存态资源组,实施资源限制的类,是resourcegroups::Resource_group类的成员。下面分别展开介绍。
resourcegroups::Resource_group_mgr是一个用来管理内存态资源组的单例类,其包括的主要成员变量和成员函数如下:
成员变量:
// 存储单例类对象的指针
m_instance
// pfs有多种service,包括dynamic_loader_scheme、log_builtins、registry等等类型。每种类型对应具体的一种或几种操作
// m_resource_group_svc是类型为pfs_resource_group_v3的service的实例化指针,其具体操作包括读取或设置fps threads的resource group。详见2.4 PFS组件模块
m_resource_group_svc
// 指向USR_default、SYS_default资源组的指针
m_user_default_resource_group、m_sys_default_resource_group
// 维护内存态所有资源组名和资源组对象指针的map
m_resource_group_map
// 用于保护m_resource_group_map的读写锁
m_map_rwlock
成员函数:
// 返回单例Resource_group_mgr的instance
instance
// 销毁单例instance
destroy_instance
// 获取、添加、删除内存中的资源组
get_resource_group、add_resource_group、remove_resource_group
// 将当前thread从旧资源组自动到指定的资源组
move_resource_group
// 返回USR_default、SYS_default资源组
usr_default_resource_group、sys_default_resource_group
// 创建新的资源组对象并添加到m_resource_group_map中
create_and_add_in_resource_group_map
// 解析数据字典中资源组object,并创建一个内存态的资源组object
deserialize_resource_group
// 将资源组名设置到performance_schema.threads中
set_res_grp_in_pfs
// 获取线程池的PFS threads属性
get_thread_attributes
// 检查是否在query hints中设置了临时resource group,设置了就将当前thread迁移至临时resource group中
switch_resource_group_if_needed
// 在结束mysql_execute_command后,将当前thread迁移至原来的resource group中
restore_original_resource_group
resourcegroups::Resource_group描述一个内存态资源组object。Resource_group除了包括资源组名称、类型、绑定的CPU列表、线程优先级、是否启用等信息外,还包括用于控制线程优先级的Thread_resource_control类。resourcegroups::Resource_group的主要成员变量和成员函数如下:
成员变量:
// 资源组名、类型、是否启用
m_name、m_type、m_enabled
// 控制资源组的Thread_resource_control类对象
m_thread_resource_control
// 当前资源组中管理的thread_id集合
m_pfs_thread_id_set
// 保护m_pfs_thread_id_set的锁
m_set_mutex
成员函数:
// 返回资源组名称、类型、是否启用
name、type、enabled
// 设置资源组类型、启用状态
set_type、set_enabled
// 设置、返回资源组的控制对象
set_controller、controller
// 传入类型为std::function的控制方法,对m_pfs_thread_id_set中所有thread实施控制
apply_control_func
// 添加、移除pfs thread id
add_pfs_thread_id、remove_pfs_thread_id
resourcegroups::Thread_resource_control记录了资源绑定的CPU id list和线程优先级,以及实施绑定CPU、设置线程优先级的方法。其主要成员变量和成员函数如下:
成员变量:
// 资源组需要绑定的CPU列表
m_cpu_vector
// 资源组中线程的优先级
m_priority
成员函数:
// 验证待绑定的CPU列表和线程优先级在当前系统中是否合法
validate
// 将控制信息记录到数据字典object中进行持久化
store_to_dd_obj
// 读取设置线程优先级
priority、set_priority
// 设置资源组绑定的CPU
set_vcpu_vector
// 对当前线程活着指定线程实施CPU绑定和优先级设置的控制
apply_control
2.4 PFS组件
关于PFS组件的详细介绍请参考:WL8881。Performance Schema提供了两种service类型供资源组使用:pfs_resource_group和pfs_notification。
pfs_resource_group用于将给定的线程分配到资源组中、以及获取thread的system attributes。其包括四个动作:
// 将当前线程分配到默认的资源组中,并更新performance_schema.threads表中资源组列
set_thread_resource_group
// 将指定线程分配到默认的资源组中,并更新performance_schema.threads表中资源组列
set_thread_resource_group_by_id
// 获取当前线程的system attributes
get_thread_system_attrs
// 获取指定线程的system attributes
get_thread_system_attrs_by_id
// system attributes与performance_schema.threads表对应,其的定义
struct pfs_thread_attrs_t
{
ulonglong m_thread_internal_id; // PFS 内部线程id,唯一值
ulong m_processlist_id; // SHOW PROCESSLIST中的线程id,非唯一值
ulonglong m_thread_os_id; // 当前线程的操作系统thread id
void *m_user_data; // 用户自定义内容,由set_thread_resource_group设置
char m_username[USERNAME_LENGTH]; // 用户名
uint m_username_length; // 用户名长度
char m_hostname[HOSTNAME_LENGTH]; // 主机名
uint m_hostname_length; // 主机名长度
char m_groupname[NAME_LEN]; // 资源组名
uint m_groupname_length; // 资源组长度
struct sockaddr_storage m_sock_addr; // Raw socket地址
socklen_t m_sock_addr_length; // Raw socket地址长度
my_bool m_system_thread; // 是否为系统、后台线程
}
pfs_notification用来在注册事件发生时,通知并执行相应的回调函数。pfs_notification当前能通知的事件类型如下,包括线程创建、线程销毁、连接进入、连接断连、连接切换用户:
// pfs_notification能够注册的事件类型汇总
struct PSI_notification_v3 {
PSI_notification_cb_v3 thread_create;
PSI_notification_cb_v3 thread_destroy;
PSI_notification_cb_v3 session_connect;
PSI_notification_cb_v3 session_disconnect;
PSI_notification_cb_v3 session_change_user;
};
// callbacks指向实例化的PSI_notification结构体。callbacks需要对感兴趣的回调函数赋予自定义的回调函数,其他部分置零。
// 例如资源组中只设置了thread_create和session_disconnect。前者用于将新创建的thread放入默认资源组,后者用于连接退出的时候,将其从原先资源组中移除。
// 注册成功后返回handle
int register_notification(PSI_notification *callbacks, bool with_ref_count);
// 解除注册
int unregister_notification(int handle);
2.5 持久化组件
资源组的持久化组件被封装在了dd(数据字典)命名空间中。dd提供了以下几个用于持久化资源组配置的API:
// 检查给定的资源组名是否存在于数据字典中
resource_group_exists
// 创建在数据字典中创建资源组
create_resource_group
// 更新数据字典中的资源组
update_resource_group
// 删除数据字典中的资源组
drop_resource_group
dd::tables::Resource_groups继承自Entity_object_table_impl。其定义了information_schema.RESOURCE_GROUPS,并提供了实例化dd::Resource_group的方法,其主要成员变量和成员函数如下:
成员变量:
// information_schema.RESOURCE_GROUPS的列
enum enum_fields {
FIELD_ID,
FIELD_RESOURCE_GROUP_NAME,
FIELD_RESOURCE_GROUP_TYPE,
FIELD_RESOURCE_GROUP_ENABLED,
FIELD_CPU_ID_MASK,
FIELD_THREAD_PRIORITY,
FIELD_OPTIONS,
NUMBER_OF_FIELDS // Always keep this entry at the end of the enum
};
// information_schema.RESOURCE_GROUPS的索引
enum enum_indexes {
INDEX_PK_ID = static_cast(Common_index::PK_ID),
INDEX_UK_RESOURCE_GROUP_NAME = static_cast(Common_index::UK_NAME)
};
成员函数:
// 构造函数,创建表,添加列
Resource_groups
// 实例化dd::Resource_group
create_entity_object
// 更新information_schema.RESOURCE_GROUPS的索引:resource_group_name
update_object_key
dd::Resource_group继承自Entity_object,它是资源组dd object的接口类。每个dd::Resource_group实例对应一个资源组,能够获取和设置其对应的information_schema.RESOURCE_GROUPS中内容。dd::Resource_group中定义了多个虚函数,他们由子类Resource_group_impl负责实现。
Resource_group_impl继承自Entity_object_impl和dd::Resource_group,对dd::Resource_group中众多虚函数进行了实现。其主要成员变量和成员函数如下:
成员变量:
// 资源组名、类型、是否启用
m_resource_group_name、m_type、m_enabled
// 资源组绑定CPU list
m_cpu_id_mask
// 资源组内线程优先级
m_thread_priority
成员函数:
// 对CPU list和优先级在系统中进行合法校验
validate
// 更新、插入资源组信息到持久化dd一行数据
restore_attributes、store_attributes
// 获取、设置资源组类型
resource_group_type、set_resource_group_type
// 设置CPU list、线程优先级
set_cpu_id_mask、set_thread_priority
// 克隆Resource_group类
clone