处理 SQL 和事务请求的线程也称为工作线程,是分租户的,也即每个租户都有自己的一套 sql/transaction worker。
多租户线程池
线程池简介
多租户共享一个大线程池,租户从线程池中申请工作线程。线程池跟随多租户进行初始化和销毁,多租户初始化时,预先申请一定数目的线程,后续在租户运行过程中,还支持动态的扩展线程池。
初始线程池大小与 OBServer 的 CPU 数,系统租户预留线程数,虚拟租户预留线程数有关,部分可通过配置项配置。后续扩展线程池时的线程数上限受 OBServer 线程数上限和多租户线程数上限共同限制,可通过配置项配置。
线程池相关配置项
_ob_max_thread_num
决定 OBServer 的线程数上限。
默认值为 9999,取值范围为 [4096, 10000],非动态生效。
system_cpu_quota
决定系统租户的虚拟 CPU 个数,与机器物理 CPU 无直接关系,仅影响多租户线程池的初始大小和上限大小。
默认值为 10,取值范围为 [0, 16],非动态生效。
server_cpu_quota_min
决定系统租户的最小虚拟 CPU 个数,与机器物理 CPU 无直接关系,仅影响多租户线程池的初始大小。
默认值为 1,取值范围为 [0, 16],非动态生效。
server_cpu_quota_max
决定系统租户的最大虚拟 CPU 个数,与机器物理 CPU 无直接关系,仅影响多租户线程池的上限大小。
默认值为 1,取值范围为 [0, 16],非动态生效。
election_cpu_quota
决定 election 租户的虚拟 CPU 个数,与机器物理 CPU 无直接关系,仅影响多租户线程池的初始大小和上限大小。
默认值为 3,取值范围为 [0, 16],非动态生效。
location_cache_cpu_quota
决定 location cache 租户的虚拟 CPU 个数,与机器物理 CPU 无直接关系,仅影响多租户线程池的初始大小和上限大小。
默认值为 5,取值范围为 [0, 16],非动态生效。
租户线程
租户线程构成
单个租户的线程包含,处理嵌套请求的 7 个专有线程,处理一般请求的若干个普通线程。线程可处于活跃,挂起两种状态。由于嵌套请求的专用线程对外基本无感知,以下介绍只涉及普通线程。
活跃线程数概念
活跃线程表示能正常处理请求的线程,与挂起线程区分,活跃线程数用于限制单个租户的 CPU 使用。租户运行时会维持活跃线程数恒定,租户的活跃线程数受配置项和 unit 规格共同决定。活跃线程数 = unit_min_cpu * cpu_quota_concurrency。
大查询的线程挂起逻辑
用户发给 OBServer 的 SQL,可以粗略分为两类:一类 SQL 访问和操作的数据量小,所以执行很快;另一类 SQL 要访问大量的数据或者要写入大量数据,所有执行耗时长。我们把第二类耗时长的 SQL 叫做大查询。
大查询的特殊处理基于一个直观的用户效用假设,从影响用户数和延迟的 QoS 要求来合理猜想:1000 个小查询被延迟的影响要远大于 1 个大查询被延迟。换言之,与其花 1s 时间处理一个大查询,不如把同样的 CPU 时间用来处理 1000 个小查询。
请求被判定为大查询的条件是,处理时间超过大查询阈值,此阈值可通过配置项调整。
租户线程中持有被判定为大查询请求的线程,一部分可直接获得继续执行权,其余的需要挂起等待。可获得继续执行权的线程的比例由配置项决定。
最大线程数概念
为了维持租户活跃线程数恒定,同时考虑到大查询线程挂起的发生,租户就需要动态的从多租户线程池中申请线程。最大线程数用于限制单个租户的内存开销,每个租户总共可持有的最大线程数受配置项和 unit 规格共同决定。最大线程数 = unit_max_cpu * worker_per_cpu_quota。
相关配置项
cpu_quota_concurrency
决定租户活跃线程数与租户 unit 规格的倍数关系。
默认值为 4,取值范围为 [1, 10] ,动态生效。
worker_per_cpu_quota
决定租户最大线程数与租户 unit 规格的倍数关系。
默认值为 10,取值范围为 [2, 20],动态生效。
large_query_worker_percentage
决定租户线程中享有大查询继续执行权的线程的百分比。
默认值为 30,取值范围为 [0, 100],动态生效。
large_query_threshold
请求判定为大查询的处理时间阈值。
默认值为 5s,取值范围为 [1ms, +∞],动态生效。
关于配置项的查询和修改方法请参见 设置参数。
日志诊断
通过 grep 'dump tenant info' observer.log 指令可以获得租户的工作线程数情况和请求排队情况等。例如:
grep 'dump tenant info.tenant={id:1002' log/observer.log | sed 's/,/,\n/g'
[2022-07-20 14:55:40.774143] INFO [SERVER.OMT] run1 (ob_multi_tenant.cpp:1993) [80700][MultiTenant][T0][Y0-0000000000000000-0-0] [lt=621]
dump tenant info(tenant={id:1002, //租户id
tenant_meta:{unit:{tenant_id:1002, // tenant_meta是租户元数据包括unit配置信息和SuperBlok以及create_status
unit_id:1001, //unit id
has_memstore:true, // 是否有memtable
unit_status:"NORMAL", //unit的状态: UNIT_NORMAL/UNIT_MIGRATE_IN/UNIT_MIGRATE_OUT/UNIT_MARK_DELETING/UNIT_WAIT_GC_IN_OBSERVER/UNIT_DELETING_IN_OBSERVER/UNIT_ERROR_STAT
config:{unit_config_id:1003, // unit的配置id
name:"2c2g", //unit配置的名字
resource:{min_cpu:2, //unit配置的最小cpu数
max_cpu:2, // unit配置的最大cpu数
memory_size:"1.5GB", // unit配置的内存大小
log_disk_size:"5.4GB", // unit配置的日志盘大小
min_iops:20000, // unit配置的最小磁盘iops
max_iops:20000, // unit配置的最大磁盘iops
iops_weight:2}}, // unit配置的iops权重
mode:1, // CompatMode 0:MYSQL 1:ORACLE
create_timestamp:1658298418435426, // unit的创建时间
is_removed:false}, // 该unit是否被标记删除
super_block:{tenant_id:1002, // ObTenantSuperBlock信息
replay_start_point:ObLogCursor{file_id=1, // 租户slog的日志回放点
log_id=440,
offset=343322},
ls_meta_entry:[140](ver=0, // 租户ls meta的slog checkpoint入口点
mode=0,
seq=139),
tablet_meta_entry:[141](ver=0, // 租户tablet meta的slog checkpoint的入口点
mode=0,
seq=140)},
create_status:1}, // 租户的创建状态: CREATING = 0,CREATE_COMMIT=1,CREATE_ABORT=2,DELETING=3,DELETE_COMMIT=4
unit_min_cpu:"2.000000000000000000e+00", //最小cpu核数,保证提供
unit_max_cpu:"2.000000000000000000e+00", //最大cpu核数,限制上限
slice:"0.000000000000000000e+00",
slice_remain:"0.000000000000000000e+00",
token_cnt:8, //调度器分配的token数,一个token会转换为一个工作线程
sug_token_cnt:8,
ass_token_cnt:8, //租户当前确认的token数(根据token_cnt确认,一般两者相等)
lq_tokens:2, //Large Query token个数,根据token_cnt乘以大请求比例设置
used_lq_tokens:0, //当前持有LQ Token的Worker数
stopped:false, //租户unit是否正在删除
idle_us:6076629, //一轮(10秒)中工作线程空闲的总时间和, 所谓空闲实际只统计了等待队列的时间
recv_hp_rpc_cnt:1, //租户累计收到不同级别的rpc请求数,hp(High), np(Normal), lp(Low)
recv_np_rpc_cnt:5,
recv_lp_rpc_cnt:0,
recv_mysql_cnt:24, //租户累计收到的mysql请求数
recv_task_cnt:1, //租户累计收到的内部任务数
recv_large_req_cnt:0, //租户累计预判的大请求数,只会递增,不会清零。实际是重试的时候递增的。
tt_large_quries:0, //租户累计处理的大请求数, 只会递增,不会清零。实际是打点check的时候递增的。
pop_normal_cnt:1024555,
actives:8, //活跃工作线程数,一般和workers相等,它们的差包含:租户工作线程缓存+带工作线程的大请求缓存
workers:8, //租户持有的工作线程数, 实际就是workers_这个list的size。
nesting workers:7, //租户持有的嵌套请求专用线程数,共7个线程对应7个嵌套层级。
lq waiting workers:0, //处于被判定为处理大查询,并被挂起等待调度的工作线程
req_queue:total_size=0 queue[0]=0 queue[1]=0 queue[2]=0 queue[3]=0 queue[4]=0 queue[5]=0 , //不同优先级的工作队列,数字越小优先级越高
large queued:0, //当前预判出的大请求个数
multi_level_queue:total_size=0 queue[0]=0 queue[1]=0 queue[2]=0 queue[3]=0 queue[4]=0 queue[5]=0 queue[6]=0 queue[7]=0 , //存放嵌套请求的工作队列,1~7对应7个嵌套层级(queue[0]暂时不用,queue[5]也存放innersql请求)。
recv_level_rpc_cnt:cnt[0]=0 cnt[1]=0 cnt[2]=0 cnt[3]=0 cnt[4]=0 cnt[5]=0 cnt[6]=0 cnt[7]=0 ,
group_map:group_id = 1, //group_map后面跟着每个group_id对应group的线程和排队情况
queue_size = 0, //group队列排队情况
recv_req_cnt = 13526, //group累计push到队列请求数
pop_req_cnt = 13526, //group线程累计pop出的请求数
token_cnt = 2, //group分配的token数,一个token会转换为一个工作线程
min_token_cnt = 2,
max_token_cnt = 2,
ass_token_cnt = 2 , //group当前确认的token数(根据token_cnt确认,一般两者相等)
rpc_stat_info: pcode=0x150a:cnt=1489 pcode=0x150b:cnt=1091}) //租户一段时间内收到的最多的rpc pcode,统计周期10秒,最多打印前五



