libvirt job机制浅析

news2024/11/23 15:39:22

libvirt 中,job 机制用于处理和跟踪针对虚拟机域(domain)的长时间操作,如迁移、快照、保存,热插拔等。job 机制的主要目的是确保在同一时间只有一个长时间操作可以执行,从而避免竞争条件和不一致性问题。

一、libvirt job基本概念

libvirt 的 job 机制主要包括以下几个方面:job 类型、job 锁、job 状态、job 事件和 job 控制。

job 类型:libvirt 定义了多种 job 类型,以区分不同的长时间操作。例如,VIR_DOMAIN_JOB_BOUNDED 表示有时间限制的操作,VIR_DOMAIN_JOB_UNBOUNDED 表示无时间限制的操作,VIR_DOMAIN_JOB_MIGRATION 表示虚拟机迁移操作等。不同类型的 job 可能具有不同的优先级和执行策略。

job 锁:为了确保在同一时间只有一个长时间操作可以执行,libvirt 为每个虚拟机域提供了一个 job 锁。当客户端请求执行某个长时间操作时,libvirtd 需要先获取该域的 job 锁。job 锁可以防止多个客户端同时执行相互冲突的操作,从而避免竞争条件和不一致性问题。

job 状态:libvirt 为每个虚拟机域维护了一个 job 状态,用于跟踪当前正在执行的长时间操作。客户端可以通过调用 virDomainGetJobInfo 函数来查询虚拟机域的 job 状态。job 状态包括了操作类型、进度、剩余时间等信息,有助于客户端了解操作的执行情况。

job 事件:libvirt 支持通过事件通知机制来报告 job 的状态变化。客户端可以注册事件回调函数,以便在 job 开始、结束或失败时收到通知。这样,客户端可以实时了解 job 的执行情况,以便采取相应的措施。

job 控制:libvirt 提供了一些 API 函数,用于控制长时间操作的执行。例如,客户端可以调用 virDomainAbortJob 函数来取消正在执行的操作,或调用 virDomainMigrateSetMaxDowntime 函数来设置迁移操作的最大停机时间。这些控制功能可以帮助客户端更好地管理长时间操作。

二、libvirt job源码分析

libvirt中通过qemuDomainObjBeginJob和qemuDomainObjEndJob配对使用,建立长时间操作虚拟机域job任务队列关系。
1.qemuDomainJob类型
destroy vm,挂起vm,修改vm状态(热插拔),终止任务,job嵌套等

typedef enum {
    QEMU_JOB_NONE = 0,  /* Always set to 0 for easy if (jobActive) conditions */
    QEMU_JOB_QUERY,         /* Doesn't change any state */
    QEMU_JOB_DESTROY,       /* Destroys the domain (cannot be masked out) */
    QEMU_JOB_SUSPEND,       /* Suspends (stops vCPUs) the domain */
    QEMU_JOB_MODIFY,        /* May change state */
    QEMU_JOB_ABORT,         /* Abort current async job */
    QEMU_JOB_MIGRATION_OP,  /* Operation influencing outgoing migration */

    /* The following two items must always be the last items before JOB_LAST */
    QEMU_JOB_ASYNC,         /* Asynchronous job */
    QEMU_JOB_ASYNC_NESTED,  /* Normal job within an async job */

    QEMU_JOB_LAST
} qemuDomainJob;

2.分析qemuDomainObjBeginJob–>qemuDomainObjBeginJobInternal

int qemuDomainObjBeginJob(virQEMUDriverPtr driver,
                          virDomainObjPtr obj,
                          qemuDomainJob job)
{   
    if (qemuDomainObjBeginJobInternal(driver, obj, job,
                                      QEMU_ASYNC_JOB_NONE) < 0)
        return -1;
    else
        return 0;
}

/*
 * obj must be locked before calling
 */
static int ATTRIBUTE_NONNULL(1)
qemuDomainObjBeginJobInternal(virQEMUDriverPtr driver,
                              virDomainObjPtr obj,
                              qemuDomainJob job,
                              qemuDomainAsyncJob asyncJob)
{
    qemuDomainObjPrivatePtr priv = obj->privateData;
    unsigned long long now;
    unsigned long long then;
    bool nested = job == QEMU_JOB_ASYNC_NESTED;
    bool async = job == QEMU_JOB_ASYNC;
    virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver);
    const char *blocker = NULL;
    int ret = -1;
    unsigned long long duration = 0;
    unsigned long long asyncDuration = 0;
    const char *jobStr;
    /*首先会判断job是否异步job*/
    if (async)
        jobStr = qemuDomainAsyncJobTypeToString(asyncJob);
    else
        jobStr = qemuDomainJobTypeToString(job);

    VIR_DEBUG("Starting %s: %s (vm=%p name=%s, current job=%s async=%s)",
              async ? "async job" : "job", jobStr, obj, obj->def->name,
              qemuDomainJobTypeToString(priv->job.active),
              qemuDomainAsyncJobTypeToString(priv->job.asyncJob));
    /*获取当前时间*/
    if (virTimeMillisNow(&now) < 0) {
        virObjectUnref(cfg);
        return -1;
    }
    /*更新domain jobs_queued*/
    priv->jobs_queued++;
    /*设置job预期处理时间30s*/
    /*Give up waiting for mutex after 30 seconds */
    /*#define QEMU_JOB_WAIT_TIME (1000ull * 30)*/
    then = now + QEMU_JOB_WAIT_TIME;

 retry:
    /*如果虚拟机设置的最多等待job个数,且当前等待超过最大值后,新插入job直接失败*/
    if (cfg->maxQueuedJobs &&
        priv->jobs_queued > cfg->maxQueuedJobs) {
        goto error;
    }
    /*当新job不是QEMU_JOB_ASYNC_NESTED,且和其他异步job冲突时,新job需要等待完成*/
    while (!nested && !qemuDomainNestedJobAllowed(priv, job)) {
            VIR_DEBUG("Waiting for async job (vm=%p name=%s)", obj, obj->def->name);
        if (virCondWaitUntil(&priv->job.asyncCond, &obj->parent.lock, then) < 0)
            goto error;
    }
    /*如果当前有正在执行的非异步job,其他任何job都要等待,再次while循环是因为只有同步才会更新priv->job.active*/
    while (priv->job.active) {
        VIR_DEBUG("Waiting for job (vm=%p name=%s)", obj, obj->def->name);
        if (virCondWaitUntil(&priv->job.cond, &obj->parent.lock, then) < 0)
            goto error;
    }

    /* No job is active but a new async job could have been started while obj
     * was unlocked, so we need to recheck it. */
    /*检查是不是新的异步job已经提前进入队列*/
    if (!nested && !qemuDomainNestedJobAllowed(priv, job))
        goto retry;
    
    /*重置同步job信息*/
    qemuDomainObjResetJob(priv);

    ignore_value(virTimeMillisNow(&now));

    if (job != QEMU_JOB_ASYNC) {
    /*处理非异步job*/
        VIR_DEBUG("Started job: %s (async=%s vm=%p name=%s)",
                   qemuDomainJobTypeToString(job),
                  qemuDomainAsyncJobTypeToString(priv->job.asyncJob),
                  obj, obj->def->name);
        priv->job.active = job;
        /*获取当前线程id*/
        priv->job.owner = virThreadSelfID();
        /*获取当前线程执行的job*/
        priv->job.ownerAPI = virThreadJobGet();
        /*设置当前job执行的开始时间*/
        priv->job.started = now;
    } else {
        VIR_DEBUG("Started async job: %s (vm=%p name=%s)",
                  qemuDomainAsyncJobTypeToString(asyncJob),
                  obj, obj->def->name);
        /*重置异步job信息*/
        qemuDomainObjResetAsyncJob(priv);
        if (VIR_ALLOC(priv->job.current) < 0)
            goto cleanup;
        priv->job.asyncJob = asyncJob;
        /*获取当前线程id*/
        priv->job.asyncOwner = virThreadSelfID();
        /*获取当前线程执行的job*/
        priv->job.asyncOwnerAPI = virThreadJobGet();
        /*设置异步job执行的开始时间*/
        priv->job.asyncStarted = now;
        priv->job.current->started = now;
    }

    if (qemuDomainTrackJob(job))
        qemuDomainObjSaveJob(driver, obj);

    virObjectUnref(cfg);
    return 0;
error:
    ignore_value(virTimeMillisNow(&now));
    if (priv->job.active && priv->job.started)
        duration = now - priv->job.started;
    if (priv->job.asyncJob && priv->job.asyncStarted)
        asyncDuration = now - priv->job.asyncStarted;

    VIR_WARN("Cannot start job (%s, %s) for domain %s; "
             "current job is (%s, %s) owned by (%llu %s, %llu %s) "
             "for (%llus, %llus)",
             qemuDomainJobTypeToString(job),
             qemuDomainAsyncJobTypeToString(asyncJob),
             obj->def->name,
             qemuDomainJobTypeToString(priv->job.active),
             qemuDomainAsyncJobTypeToString(priv->job.asyncJob),
             priv->job.owner, NULLSTR(priv->job.ownerAPI),
             priv->job.asyncOwner, NULLSTR(priv->job.asyncOwnerAPI),
             duration / 1000, asyncDuration / 1000);

    if (nested || qemuDomainNestedJobAllowed(priv, job))
        blocker = priv->job.ownerAPI;
    else
        blocker = priv->job.asyncOwnerAPI;

    ret = -1;
    1./*error的处理,virCondWaitUntil等待超时以后,就会走向error,计算job占有lock的时常*/
    if (errno == ETIMEDOUT) {
        if (blocker) {
            virReportError(VIR_ERR_OPERATION_TIMEOUT,
                           _("cannot acquire state change lock (held by %s)"),
                           blocker);
        } else {
            virReportError(VIR_ERR_OPERATION_TIMEOUT, "%s",
                           _("cannot acquire state change lock"));
        }
        ret = -2;
    2./*当前等在job数大于设置的maxQueuedJobs*/
    } else if (cfg->maxQueuedJobs &&
               priv->jobs_queued > cfg->maxQueuedJobs) {
        if (blocker) {
            virReportError(VIR_ERR_OPERATION_FAILED,
                           _("cannot acquire state change lock (held by %s) "
                             "due to max_queued limit"),
                           blocker);
        } else {
            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
                           _("cannot acquire state change lock "
                             "due to max_queued limit"));
        }
        ret = -2;
    3./*其他异常场景*/
    } else {
        virReportSystemError(errno, "%s", _("cannot acquire job mutex"));
    }

 cleanup:
    priv->jobs_queued--;
    virObjectUnref(cfg);
    return ret;
}                                                                                                                                                        

3.qemuDomainObjEndJob分析

void
qemuDomainObjEndJob(virQEMUDriverPtr driver, virDomainObjPtr obj)
{
    qemuDomainObjPrivatePtr priv = obj->privateData;
    qemuDomainJob job = priv->job.active;
    /*jobs计数器减一*/
    priv->jobs_queued--;

    VIR_DEBUG("Stopping job: %s (async=%s vm=%p name=%s)",
              qemuDomainJobTypeToString(job),
              qemuDomainAsyncJobTypeToString(priv->job.asyncJob),
              obj, obj->def->name);
    /*重置job信息*/
    qemuDomainObjResetJob(priv);
    if (qemuDomainTrackJob(job))
        qemuDomainObjSaveJob(driver, obj);
    /*发信号唤醒其他使用virCondWaitUntil等待的job*/
    virCondSignal(&priv->job.cond);
}   
libvirt job机制lock一种特殊类型的对象锁,用于保护虚拟机域(domain)的长时间操作,如迁移、快照、保存等。job锁确保了在同一时间只有一个长时间操作可以执行,从而避免了竞争条件和不一致性问题。job锁的作用范围介于全局锁和对象锁之间,针对特定的虚拟机域和长时间操作。

libvirt中有两种lock,全局锁和对象锁;
(1)全局锁(vm大锁):全局锁用于保护整个虚拟机管理系统的状态。当客户端请求执行某个操作时,libvirtd首先需要获取全局锁。全局锁确保了在同一时间只有一个操作可以修改虚拟机管理系统的状态,从而避免了竞争条件和不一致性问题。全局锁的作用范围较大,涵盖了整个虚拟机管理系统。

(2)对象锁:对象锁用于保护特定的虚拟机对象(如域、网络、存储池等)。当客户端请求执行针对某个对象的操作时,libvirtd需要先获取该对象的锁。对象锁可以提高libvirt的并发性能,因为它允许多个客户端同时操作不同的虚拟机对象。对象锁的作用范围较小,仅针对特定的虚拟机对象。
job锁:job锁是一种特殊类型的对象锁,用于保护虚拟机域(domain)的长时间操作,如迁移、快照、保存,热插拔等。job锁确保了在同一时间只有一个长时间操作可以执行,从而避免了竞争条件和不一致性问题。job锁的作用范围介于全局锁和对象锁之间,针对特定的虚拟机域和长时间操作。
vm并发操作场景下,如果所有的api都通过拿vm大锁来保证数据一致性,那会严重影响一些api(如查询类操作)的体验,比如有些生命周期操作很耗时,此时并发去查询就会卡住,故引入libvirtd job机制期望domain某些关键操作(迁移、快照、保存,热插拔)能得到成功保障,同时不影响查询类api对虚拟化的访问。

三、案例分析

1.热迁移/热插拔失败分析
在这里插入图片描述

2023-04-05 03:35:18.033+0800: 134423: warning : qemuDomainObjBeginJobInternal:3879 : Cannot start job (query, none) for domain 90ba372a-8d3d-416a-93bc-217051612e8d; current job is (query, none) owned by (134425 qemuDispatchDomainMonitorCommand, 0 <null>) for (147s, 0s)
2023-04-05 03:35:18.033+0800: 134423: error : qemuDomainObjBeginJobInternal:3891 : Timed out during operation: cannot acquire state change lock (held by qemuDispatchDomainMonitorCommand)

热迁移失败原因,迁移job拿不到vm的job锁,该锁被 QEMU_JOB_QUERY类型的job(qemuDispatchDomainMonitorCommand)持有。
Libvirtd job lock未释放期间,管理vm生命周期,migrate、hotplug/hotunplug操作均不能正常执行。
现有libvirtd健康check,定期(120s)通过virsh list返回值确保libvirtd状态,正常返回,libvirtd正常;异常返回,重启libvirtd。

2.现网出现获取不到job lock原因

持锁job hung住,引发后续操作失败,以磁盘热拔为例,某次插热拔失败,vs调用libvirt api一直发生重试,但是未处理热拔失败原因(vm内部未响应拔插事件,vm未响应的原因很多,具体问题需要具体分析),直到某天执行migrate/拔插拔/开关机失败才发现该问题。

处理方法:
(1)重启vm;(2)找到job hung住原因,让该job正常运行释放job lock;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/560267.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C嘎嘎~~[类和对象 精华篇]

类和对象 精华篇 static成员引入特点问题收尾 友元友元函数友元类 内部类理解 类和对象 static成员 引入 &#x1f5e8;️[题目] 实现一个类&#xff0c;计算程序中创建出了多少个类对象 先分析一下题目: 程序运行中, 有对象的创建, 也有对象的销毁 ⇒ 对应下来就是 构造,拷…

linux服务器断电重启后,发现时间误差八小时

文章目录 问题现象排查与解决时间同步与设置服务器时钟介绍 问题现象 客户的服务器已部署好平台&#xff0c;放入了机房&#xff0c;运行正常。服务器系统时间设置东八区&#xff08;CST&#xff09;&#xff0c;时间日期也已修改正确客户是我省的某小县城&#xff0c;某台晚上…

28 KVM管理系统资源-绑定QEMU进程至物理CPU

文章目录 28 KVM管理系统资源-绑定QEMU进程至物理CPU28.1 概述28.2 操作步骤 28 KVM管理系统资源-绑定QEMU进程至物理CPU 28.1 概述 QEMU主进程绑定特性是将QEMU主进程绑定到特定的物理CPU范围内&#xff0c;从而保证了运行不同业务的虚拟机不会干扰到邻位虚拟机。例如在一个…

ArcMap:第一届全国大学生GIS技能大赛(滁州学院)详解-下午题

目录 01 题目 02 数据 2.1 主要沟谷文件 2.2 DEM数字高程文件 2.3 气象站点数据 2.4 系统设计相关的DLL等文件 03 思路 3.1 作物生长条件的思路 3.1.1 对于条件1 3.1.2 对于条件2 3.1.3 对于条件3 3.1.4 对于条件4 3.2 水系的提取 3.3 种植面积的计算 04 实操 …

linux下cpu占用率100%怎么解决?

在实际的生产环境中&#xff0c;常常会遇到服务器CPU爆满的问题&#xff0c;这时候&#xff0c;正确的排查方法&#xff0c;有助于快速的定位问题。 1、找到最耗CPU的进程 使用top命令查看系统总体的CPU和内存使用情况&#xff0c;以及各个进程的资源使用情况。 2、找到最耗…

迈向5.5G丨美格智能持续创新,为智能世界创造新价值

5月11日&#xff0c;2023 “高通&美格智能物联网技术开放日”深圳站活动举行&#xff0c;美格智能高级产品总监在活动中发表题为《迈向5.5G&#xff0c;智联新未来》的主题演讲&#xff0c;分享了美格智能在5G技术演进中的创新实践。 ▲美格智能高级产品总监 刘伟鹏 ▌技术…

【基础折线图】学习使用flask、echarts搭建数据可视化图表网页

文章目录 前言Apache EchartsNPM 安装 ECharts在线定制 ECharts使用 Echarts Flaskpip安装flask 学习案例案例目录html代码flask代码 源码地址 前言 本文中的所有代码&#xff0c;全部都有详细注释&#xff0c;有需要的同学可以在文末领取&#xff01; 数据可视化是数据分析必…

19-01 技术选型的道与术

系列目录导航&#x1f449; 什么是技术选型&#xff0c;技术选型的重要性 根据实际业务管理的需要&#xff0c;对硬件、软件以及所要用到的技术进行规格的选择狭义上的技术选型&#xff1a;团队决定选用哪种技术去解决问题&#xff0c;比如选用某个技术语言、某个技术框架去开…

Android12之模板类单例模式实现原理(一百五十二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

openGauss 年度大型开发者会议来袭,快来看看都有哪些SIG组参加!

openGauss Developer Day 2023 将于5月26日在北京举办。作为大会的重要环节&#xff0c; SIG组版本规划工作会议 将汇聚社区各个SIG 组的用户与开发者&#xff0c;与大家见面&#xff0c;共同讨论开源数据库的技术发展与创新。属于开发者的狂欢日&#xff0c;期待大家的到…

CMAKE命令详解

目录 CMake 语法 案例&#xff1a; 面试中给可能会问的问题和回答&#xff1a; 总结&#xff1a; CMake 是一个用于构建和管理跨平台软件项目的开源工具。它使用名为 CMakeLists.txt 的文本文件来描述项目的构建过程。 CMake 语法 当使用 CMake 构建项目时&#xff0c;可以…

数据库索引结构(1)概念

常见的索引 主键和二级索引 MySQL学习笔记-主键索引和二级索引_mysql中主键索引和二级索引的区别_爱因诗贤的博客-CSDN博客 MYSQL-主键索引与二级索引_mysql二级索引存在哪个文件_青苔小榭的博客-CSDN博客 采用主键索引的好处&#xff1a;如果元素的位置发生修改&#xff0c;那…

华为OD机试真题 Java 实现【组合出合法最小数】【2023Q1 200分】

一、题目描述 给一个数组&#xff0c;数组里面都是代表非负整数的字符串&#xff0c;将数组里所有的数值排列组合拼接起来组成一个数字&#xff0c;输出拼接成的最小的数字。 二、输入描述 一个数组&#xff0c;数组不为空&#xff0c;数组里面都是代表非负整数的字符串&…

解读智慧城市建设的关键角色:GIS技术的应用与优势

近年来&#xff0c;随着城市化进程的加快和信息技术的迅猛发展&#xff0c;智慧城市成为了城市发展的重要方向。而在智慧城市建设中&#xff0c;地理信息系统&#xff08;GIS&#xff09;技术的应用正发挥着越来越重要的作用。GIS技术以其独特的地理空间分析能力&#xff0c;为…

龙芯2K1000实战开发-项目整体框架介绍

文章目录 概要整体架构技术名词解释技术细节小结概要 本项目主要以龙芯2k1000为CPU实现整个系统的管理,主要实现数据交换、时间同步和数据监控等功能。项目中龙芯作为一部分存在,考虑到项目涉密的原因,不能将整个项目的应用背景及项目整体方案做介绍,只能将龙芯过程开发做一…

NSSCTF之Web篇刷题记录(12)

NSSCTF之Web篇刷题记录[12] [NCTF 2018]签到题&#xff1a;[鹤城杯 2021]EasyP&#xff1a;[NSSCTF 2022 Spring Recruit]ezgame:[GXYCTF 2019]Ping Ping Ping&#xff1a;[SWPUCTF 2021 新生赛]finalrce&#xff1a;[NISACTF 2022]checkin&#xff1a; NSSCTF平台&#xff1a;…

程序员成长之路有哪些绝对不能踩的坑?

文章目录 一、你在编写代码时&#xff0c;会特别注意哪些流程&#xff1f;二、你在工作过程中踩过哪些坑&#xff1f;你是如何处理的呢&#xff1f;三、结合自身工作经验&#xff0c;分享一下程序员有哪些要避免的坑吧。总结 程序员编写高质量、可维护、安全且高效的代码&#…

【Python文本处理】基于运动路线记录GPX文件解析,心率、速度、时间、功率、踏频、海拔等参数的生成和更改

【Python文本处理】基于运动路线记录GPX文件解析&#xff0c;心率、速度、时间、功率、踏频、海拔等参数的生成和更改 GPX文件本身其实就是坐标、海拔、时间、心率等综合性的xml文件 如图&#xff1a; 海拔&#xff1a;ele 时间&#xff1a;time 心率&#xff1a;heartrate 功…

推荐5个非常强大的ChatGPT浏览器插件|你的生产力提高工具

近期&#xff0c;ChatGPT变得越来越热门&#xff0c;为此&#xff0c;许多浏览器插件也随之问世。这些基于ChatGPT的浏览器插件大大提高了ChatGPT的能力&#xff0c;使得我们能够更高效地在平时的上网、工作和学习中获得帮助&#xff0c;从而节省了大量时间。 今天我来给大家介…

168天,从外包转岗到阿里测开岗.....

本人毕业于某普通二本院校非计算机专业&#xff0c;跨专业入行测试&#xff0c;至今有近 5年工作经验。 第一份测试工作是在腾讯做了两年外包。总体感受就是 这份工作缺乏归属感&#xff0c;心里总有一种落差&#xff0c;进步空间不大&#xff0c; 接触不到核心技术&#xf…