ceph 线程池分析

本文详细介绍了Ceph系统中线程池的设计与使用,包括为何使用线程池以减少调度开销,线程池与工作队列的关系,以及线程池的工作原理。通过具体的代码分析,展示了线程池如何启动、线程如何从工作队列中获取任务,并讨论了线程池在处理并发任务时如何避免性能下降的问题。同时,文中还总结了线程池的几种使用方式和不同类型的线程分类。

ceph 线程池

1. WHY

  线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 

2. WHAT

​ 线程池和工做队列实际上是密不可分的。让任务推入工做队列,而线程池中的线程负责从工做队列中取出任务进行处理。

	工做队列和线程池的关系,相似于狡兔和走狗的关系,正是由于有任务,因此才须要雇佣线程来完成任务,没有了狡兔,走狗也就失去了存在的意义。而线程必需要能够从工做队列中认领任务并完成,这就相似于猎狗要有追捕狡兔的功能 

3. HOW

​ 以osd侧的osd_op_tp线程为例:
对应于下面的代码分析流程
在这里插入图片描述

//线程池start在osd.init()
int OSD::init()
{
	osd_op_tp.start();
}

void ShardedThreadPool::start()
{
  shardedpool_lock.Lock();
  start_threads();
  shardedpool_lock.Unlock();
}


void ShardedThreadPool::start_threads()
{
  assert(shardedpool_lock.is_locked());
  int32_t thread_index = 0;
  while (threads_shardedpool.size() < num_threads) 
  {
    WorkThreadSharded *wt = new WorkThreadSharded(this, thread_index);
    threads_shardedpool.push_back(wt);//池子加入线程
    wt->create(thread_name.c_str());//创建线程
    thread_index++;
  }
}

void Thread::create(const char *name, size_t stacksize)
{
  assert(strlen(name) < 16);
  thread_name = name;

  int ret = try_create(stacksize);
}

int Thread::try_create(size_t stacksize)
{
  r = pthread_create(&thread_id, thread_attr, _entry_func, (void*)this);
  restore_sigset(&old_sigset);

  return r;
}

void *Thread::_entry_func(void *arg) 
{
  void *r = ((Thread*)arg)->entry_wrapper();
  return r;
}

void *Thread::entry_wrapper()
{
  ....
  return entry();
}

 struct WorkThreadSharded : public Thread 
 {
    ShardedThreadPool *pool;
    void *entry() override 
    {
      pool->shardedthreadpool_worker(thread_index);
      return 0;
    }
  }

void ShardedThreadPool::shardedthreadpool_worker(uint32_t thread_index)
{
    //轮询
  ldout(cct,10) << "worker start" << dendl;
   //队列初始化的时候便就入队了,详见各队列的构造函数
   wq->_process(thread_index, hb);
  ldout(cct,10) << "sharded worker finish" << dendl;

  cct->get_heartbeat_map()->remove_worker(hb);
}

//ThreadPool实现的线程池,其每个线程都有机会处理工作队列的任意一个任务。
//这就会导致一个问题,如果任务之间有互斥性,那么正在处理该任务的两个线程有一个必须等待另一个处理完成后才能处理,从而导致线程的阻塞,性能下降
void OSD::ShardedOpWQ::_process(uint32_t thread_index, heartbeat_handle_d *hb)
{
    //任务调度方式做了改进
    osd->service.update_sched_out_queue();

    osd->callback_trigger();//precheck_finisher.queue(c); //async process//也会被ms_fast_dispatch调用 
    
  	lgeneric_subdout(osd->cct, osd, 4) << "dequeue status: ";//被阻塞的线程

	boost::optional<PGQueueable> qi;
    ThreadPool::TPHandle tp_handle(osd->cct, hb, timeout_interval,suicide_interval);
    qi->run(osd, pg, tp_handle);//处理pg事件
}

//另外一种方式入队
ms_fast_dispatch--> enqueue_op --> op_shardedwq.queue(make_pair(pg, PGQueueable(op, epoch)))
--> _enqueue(item)-->enqueue_fetch_next -->queue.add_request(std::move(item), cl, qosparam, cost)
    
总结如何使用
1.声明线程池成员ThreadPool *_tp

2.声明队列类型ThreadPool::WorkQueue_*_wq

3.重写WorkQueue中对应函数_void_process,_void_process_finish

4.调用*_tp.add_work_queue(*_wq)将队列传入
4. 线程池及线程分类
  1. 第一类是普通类线程:

使用此类线程类直接申明继承自Thread,重写一个entry函数,在进程启动最初时,调用了create函数创建了线程,同时使用它的人必须自己定义消息队列。上面大部分线程都是此类,比如FileJournal::write_thread就是一个FileJournal::Writer类对象,它自己定义了消息队列FileJournal::writeq

第二类是SafeTimerThread类线程:

此类线程使用者可以直接申明一个SafeTimer成员变量,因为SafeTimer中已经封装了SafeTimerThread类和一个消息队列(成员是Context回调类),并完成了entry函数的逻辑流程。使用者使用方法,就是设置回调函数,通过SafeTimer::add_event_after函数将钩子埋入,等待规定时间到达后执行。

第三类是FinisherThread类线程:

此类线程使用者可以直接申明一个Finisher成员变量,因为Finsher中已经封装了FinisherThread类和一个消息队列(成员是Context回调类),并完成entry函数的逻辑流程。使用者使用方法,就是设置回调函数,通过Finisher::queue函数将钩子埋入,等待某类操作完成后执行。处理回调complete函数

第四类是ThreadPool内部线程:

这类线程由于是具体工作类线程,所以他们一般都是以线程池形式一下创建多个。ThreadPool类内部有多个线程set<WorkThread*>和多个消息队列vector<WorkQueue_*>组成。工作流程就是线程不断的轮询从队列中拿去数据进行操作(如下图)

  1. op_tp 处理client来的请求

    disk_tp 处理scrub操作

    recovery_tp处理recovery_tp操作

    command_tp 处理命令行来的操作

    FileStore::op_tp 处理底层数据操作

进行操作(如下图)

  1. op_tp 处理client来的请求

    disk_tp 处理scrub操作

    recovery_tp处理recovery_tp操作

    command_tp 处理命令行来的操作

    FileStore::op_tp 处理底层数据操作

### 如何分析 Ceph 存储集群的磁盘空间使用情况 #### 使用 `ceph df` 命令获取总体信息 为了检查集群的数据使用量和分布,在终端中可以执行如下命令来获得关于整个Ceph存储集群的空间占用概览: ```bash ceph df ``` 此命令提供了类似于Linux下`df`命令的功能,能够展示各个池(pool)所消耗的空间大小以及可用容量等基本信息[^1]。 对于更详细的报告,则可采用带有`detail`参数的形式调用该指令: ```bash ceph df detail ``` 这会提供更为详尽的信息,包括但不限于每个OSD(Object Storage Device)的状态、已分配给不同类型的对象数量及其占据的具体字节数目等等。 #### 解读 `ceph df` 的输出结果 当运行上述任一形式的查询之后,将会得到一份结构化的表格作为回应。这份文档通常由三部分组成——RAW USED(原始使用的总字节), TOTAL (总的物理硬盘资源),AVAIL(剩余未被利用的部分)。此外还有POOL NAME, SIZE, OBJECTS这样的条目用来描述特定逻辑分区内的状况;其中SIZE指的是副本数或EC编码模式下的分片数目,而OBJECTS则记录着实际存在的独立实体个数。 具体来说: - **RAW USED**: 显示的是当前已被占用掉的所有磁盘区域之和; - **TOTAL**: 表明了理论上可供调配的最大限度; - **POOL NAME**: 列出了每一个单独设立起来用于存放不同类型资料的工作区名称; - **USED**: 展现各工作区内已经被占用了多大比例; - **% USED**: 将上面提到的比例转换成百分比表示法以便直观理解程度差异; - **MAX AVAIL**: 预估在不违反安全策略的前提下还能增加至最高限额是多少; - **objects**: 统计每类项目里边究竟存了多少份实例化后的客体单元。 通过这些字段的帮助,管理员们便能轻松掌握住整体架构内部到底存在哪些瓶颈所在,并据此做出合理的调整措施以优化性能表现或是规划未来的扩展方向。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值