美文网首页MYSQL内核
MySQL resource group详解

MySQL resource group详解

作者: saviochen | 来源:发表于2021-09-04 15:09 被阅读0次

    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
    

    相关文章

      网友评论

        本文标题:MySQL resource group详解

        本文链接:https://www.haomeiwen.com/subject/kvyrwltx.html