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,并不复杂。我们可以认为是开发者没有考虑到容器化的情况(说坏话就是偷懒)。