MySQL resource group详解

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<resourcegroups::Range> *cpu_list,                        
      const Value_or_default<int> &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<Range> *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<Range> *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<void(ulonglong)>的控制方法,对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<uint>(Common_index::PK_ID),                       
  INDEX_UK_RESOURCE_GROUP_NAME = static_cast<uint>(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

推荐阅读更多精彩内容