MySQL 8.0 资源组(Resource Groups)深度解读

  • 在特定环境下,比如通过 cgroups 限制了 CPU 的容器环境,可能无法有效进行资源调度。针对这一点,下面做详细说明:

    举个例子,我们通过 LXC 启动了一个容器,并分配了 3、4、7、8 共 4 个 CPU。在容器内启动 MySQL Server。我们先来看一下,默认的两个 RG 分别是什么样的:

    mysql> select * from information_schema.resource_groups\G
    *************************** 1. row ***************************
      RESOURCE_GROUP_NAME: USR_default
      RESOURCE_GROUP_TYPE: USER
    RESOURCE_GROUP_ENABLED: 1
                 VCPU_IDS: 0-3
          THREAD_PRIORITY: 0
    *************************** 2. row ***************************
      RESOURCE_GROUP_NAME: SYS_default
      RESOURCE_GROUP_TYPE: SYSTEM
    RESOURCE_GROUP_ENABLED: 1
                 VCPU_IDS: 0-3
          THREAD_PRIORITY: 0
    2 rows in set (0.01 sec)
    
    mysql>
    

    可以看到 VCPU_IDS 都是 0-3 ,也就是 0、1、2、3 共 4 个 CPU,我们来尝试一下绑定第二个 CPU,也就是 CPU1:

    mysql> CREATE RESOURCE GROUP rg
       ->     TYPE = USER
       ->     VCPU = 1;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql>
    

    RG 创建成功了。但是我们来绑定一下呢:

    mysql> SET RESOURCE GROUP rg;
    ERROR 3661 (HY000): Unable to bind resource group rg with thread id (308485).(Failed to apply thread resource controls).
    

    可以看到绑定失败了。这是因为当 MySQL 尝试去绑定 CPU1 时,宿主系统并没有分配 CPU1 给 MySQL。那如果我们来尝试直接使用系统预分配的 CPU ID 来绑定呢,比如 CPU4,我们直接修改原 rg 为 CPU4,看看会是什么情况:

    mysql> ALTER RESOURCE GROUP rg VCPU=4;
    ERROR 3652 (HY000): Invalid cpu id 4
    

    可以看到这个时候也是无法绑定的。到这里基本上可以确认 MySQL 8.0 RG 在容器环境基本上无法正常使用。那么为什么呢?

    我们在 sql/resourcegroups/resource_group_sql_cmd.cc 找到如下代码:

    (https://github.com/mysql/mysql-server/blob/ea7d2e2d16ac03afdd9cb72a972a95981107bf51/sql/resourcegroups/resource_group_sql_cmd.cc#L121)

    bool validate_vcpu_range_vector(
       std::vector *vcpu_range_vector,
       const Mem_root_array *cpu_list, uint32_t num_vcpus) {
     ...
       if (vcpu_range.m_start >= num_vcpus || vcpu_range.m_end >= num_vcpus) {
         my_error(ER_INVALID_VCPU_ID, MYF(0),
                  vcpu_range.m_start >= num_vcpus ? vcpu_range.m_start
                                                  : vcpu_range.m_end);
         return true;
       }
     ...
     return false;
    }
    

    MySQL 在对资源组变更的时候会判断 CPU 是否合理,比如上述我们希望绑定 VCPU=4,但是 MySQL 只看到 4 个 CPU(最大 CPU 号为 3),导致无法绑定。VCPU=1 时虽然创建成功了,但在实际绑定的时候,由于宿主系统并未分配 CPU1 给这个容器,导致绑定的时候系统拒绝,出错了。

    这里我个人是认为 MySQL 在实现这一块逻辑的时候偷懒的了,更合理的做法应该是通过看到的具体的 CPU 来去判断,而不是通过看到的 CPU 数量直接限制,这一块的逻辑并不复杂,写了一个 demo 来在容器中获取实际宿主分配的 CPU:

    /*
    * gcc -lpthread -o get_cpus get_cpus.c
    */
    #include 
    #define __USE_GNU
    #include 
    #include 
    #include 
    
    void main() {
       /*
        * sys_vcpu_cnt: system cpu numbers
        * thread_vcpu_cnt: affinity cpu from the thread itself
        */
       int sys_vcpu_cnt, thread_vcpu_cnt;
       // GET VCPUS FROM sysconf
       #ifdef _SC_NPROCESSORS_ONLN
           sys_vcpu_cnt = sysconf(_SC_NPROCESSORS_ONLN);
       #elif defined(_SC_NPROCESSORS_CONF)
           sys_vcpu_cnt = sysconf(_SC_NPROCESSORS_CONF);
       #endif
       printf("SYSTEM VCPU COUNT: %d\n", sys_vcpu_cnt);
    
       // GET CPU FROM THREAD SELF
       cpu_set_t set;
    
       if (pthread_getaffinity_np(pthread_self(), sizeof(set), &set) == 0)
           thread_vcpu_cnt = CPU_COUNT(&set);
           printf("THREAD VCPU COUNT: %d\n", thread_vcpu_cnt);
           int j;
           // CAN BE TESTED BY: taskset -c 0,2 ./get_cpus
           for (j=0; j < sys_vcpu_cnt; j++)
               if (CPU_ISSET(j, &set))
                   printf("CPU %d\n", j);
    }
    

    我们来运行一下试试, ./get_cpus

    SYSTEM VCPU COUNT: 40
    THREAD VCPU COUNT: 4
       CPU 3
       CPU 4
       CPU 7
       CPU 8
    

    可以看到,通过系统提供的接口拿到正确的 CPU ID,并不复杂。我们可以认为是开发者没有考虑到容器化的情况(说坏话就是偷懒)。