OceanBase 压测时为什么冻结阈值在变化?

news2024/10/6 8:23:36

本文从源码角度分析了 OceanBase 压测中冻结阈值动态变化的原因,并给出运维建议。

作者:张乾

外星人2号,兼任五位喵星人的铲屎官。

本文来源:原创投稿

  • 爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。

背景

测试在用小租户进行压测时,注意到监控中触发内存冻结的阈值是动态变化的,现象如下:

  • 非压测时: 冻结阈值表现为 OceanBase 官方文档说明的 memstore_limit_percentage * freeze_trigger_percentage 所计算的值,约为 322MB
  • 压测时: 冻结阈值表现出不规律的降低,但没有出现升高的情况,即始终低于非压测时的 322MB

为什么压测时该阈值会变化?接下来分析一下现象的原因。

环境信息

版本信息OceanBase_CE 4.1.0.0
租户规格unit 8C4G
memstore_limit_percentage50(默认值)
freeze_trigger_percentage20(默认值)

sysbench 命令

sysbench /usr/share/sysbench/oltp_read_only.lua --mysql-host=x.x.x.x --mysql-port=42881 --mysql-db=sysbenchdb --mysql-user="sysbench@sysbench_tenant2" --mysql-password=sysbench --tables=10 --table_size=1000000 --threads=1024 --time=120 --report-interval=10 --db-driver=mysql --db-ps-mode=disable --skip-trx=on --mysql-ignore-errors=6002,6004,4012,2013,4016,1062 run

排查过程

确认监控采集的正确性

根据监控指标,在官方文档中找到采集 SQL 如下:

select /* MONITOR_AGENT */ con_id tenant_id, stat_id, value from v$sysstat, DBA_OB_TENANTS where stat_id IN (130002) and (con_id > 1000 or con_id = 1) and class < 1000

经过手动执行该 SQL 语句,观察到在压测过程中冻结阈值确实发生了变化,由此可以确认监控数据采集和展示并无错误。

通过源码查看冻结阈值的计算方式

上一步确认了 OceanBase 冻结阈值的确存在变化,接下来需要进一步查看源码,了解该阈值是如何计算出来的。

静态分析视图的数据来源

在源码 ${path_to_oceanbase}/src/share/inner_table/ob_inner_table_schema_def.py 中检索视图名称,找到该视图的定义如下:

可以看到查询视图 v$sysstat 实际上是查询虚拟表 __all_virtual_sysstat

注意:在 OceanBase 中,虚拟表并不是实际的表,是内存数据结构映射成可以用 SQL 语句直接查询的“表”。

虚拟表有对应的类来进行处理。当查询视虚拟表时,会调用相应类的方法来获取所需的值,然后将其返回给前端。

${path_to_oceanbase}/src/observer/virtual_table 路径下,可以找到和虚拟表同名的头文件和对应的代码实现:

  • ob_all_virtual_sys_stat.h
  • ob_all_virtual_sys_stat.cpp

其中定义了虚拟表 __all_virtual_sysstat 对应的类 ObAllVirtualSysStat

通常,从方法 inner_get_next_row() 开始进行调用,沿着该方法的调用静态分析下去,最终找到获取冻结阈值的方法是 ObTenantFreezer::get_freeze_trigger_()

动态调试验证数据来源

我这里使用 VSCode + gdb 来调试,在 ObTenantFreezer::get_freeze_trigger_() 方法设置一个断点,先起一个连接到 OBServer,再启动调试模式,然后手动执行上面的采集 SQL。

注意:在 OceanBase 中,有多个方法会调用 get_freeze_trigger_() 方法,因此断点未必会命中手动执行的 SQL。需要通过显示的堆栈判断是否是采集 SQL 命中了断点;如果不是,继续点击 Continue,直至 SQL 命中断点。

另一种方式是在对应类 ObAllVirtualSysStat 下的方法里设置断点,这样在执行 SQL 后,将直接命中断点,然后可以逐步进行调试,直至达到 get_freeze_trigger_() 方法。这种方式也适用于静态分析代码时找不到所需方法的情况。

在堆栈信息中,可以通过 do_process() 方法下的 sql 参数看到执行的 SQL 内容,如下:

全部堆栈如下:

oceanbase::storage::ObTenantFreezer::get_freeze_trigger_(oceanbase::storage::ObTenantFreezeCtx & ctx) (\opt\oceanbase\src\storage\tx_storage\ob_tenant_freezer.cpp:838)
oceanbase::storage::ObTenantFreezer::get_tenant_memstore_cond(oceanbase::storage::ObTenantFreezer * this, int64_t & active_memstore_used, int64_t & total_memstore_used, int64_t & memstore_freeze_trigger, int64_t & memstore_limit, int64_t & freeze_cnt, const bool force_refresh) (\opt\oceanbase\src\storage\tx_storage\ob_tenant_freezer.cpp:758)
oceanbase::observer::ObAllVirtualSysStat::update_all_stats_(const int64_t tenant_id, oceanbase::common::ObStatEventSetStatArray & stat_events) (\opt\oceanbase\src\observer\virtual_table\ob_all_virtual_sys_stat.cpp:108)
oceanbase::observer::ObAllVirtualSysStat::process_curr_tenant(oceanbase::observer::ObAllVirtualSysStat * this, oceanbase::common::ObNewRow *& row) (\opt\oceanbase\src\observer\virtual_table\ob_all_virtual_sys_stat.cpp:288)
oceanbase::omt::ObMultiTenantOperator::execute(oceanbase::omt::ObMultiTenantOperator * this, oceanbase::common::ObNewRow *& row) (\opt\oceanbase\src\observer\omt\ob_multi_tenant_operator.cpp:125)
oceanbase::observer::ObAllVirtualSysStat::inner_get_next_row(oceanbase::observer::ObAllVirtualSysStat * this, oceanbase::common::ObNewRow *& row) (\opt\oceanbase\src\observer\virtual_table\ob_all_virtual_sys_stat.cpp:263)
oceanbase::common::ObVirtualTableIterator::get_next_row(oceanbase::common::ObVirtualTableIterator * this, oceanbase::common::ObNewRow *& row) (\opt\oceanbase\src\share\ob_virtual_table_iterator.cpp:370)
oceanbase::common::ObVirtualTableIterator::get_next_row(oceanbase::common::ObVirtualTableIterator * this) (\opt\oceanbase\src\share\ob_virtual_table_iterator.cpp:470)
oceanbase::sql::DASOpResultIter::get_next_row(oceanbase::sql::DASOpResultIter * this) (\opt\oceanbase\src\sql\das\ob_das_task.cpp:517)
oceanbase::sql::ObTableScanOp::get_next_row_with_das(oceanbase::sql::ObTableScanOp * this) (\opt\oceanbase\src\sql\engine\table\ob_table_scan_op.cpp:1690)
oceanbase::sql::ObTableScanOp::inner_get_next_row_for_tsc(oceanbase::sql::ObTableScanOp * this) (\opt\oceanbase\src\sql\engine\table\ob_table_scan_op.cpp:1885)
oceanbase::sql::ObTableScanOp::inner_get_next_row_implement(oceanbase::sql::ObTableScanOp * this) (\opt\oceanbase\src\sql\engine\table\ob_table_scan_op.cpp:1862)
oceanbase::sql::ObTableScanOp::inner_get_next_row(oceanbase::sql::ObTableScanOp * this) (\opt\oceanbase\src\sql\engine\table\ob_table_scan_op.cpp:2842)
oceanbase::sql::ObOperator::get_next_row(oceanbase::sql::ObOperator * this) (\opt\oceanbase\src\sql\engine\ob_operator.cpp:1063)
oceanbase::sql::ObSubPlanScanOp::inner_get_next_row(oceanbase::sql::ObSubPlanScanOp * this) (\opt\oceanbase\src\sql\engine\subquery\ob_subplan_scan_op.cpp:63)
oceanbase::sql::ObOperator::get_next_row(oceanbase::sql::ObOperator * this) (\opt\oceanbase\src\sql\engine\ob_operator.cpp:1063)
oceanbase::sql::ObJoinOp::get_next_left_row(oceanbase::sql::ObJoinOp * this) (\opt\oceanbase\src\sql\engine\join\ob_join_op.cpp:98)
oceanbase::sql::ObBasicNestedLoopJoinOp::get_next_left_row(oceanbase::sql::ObBasicNestedLoopJoinOp * this) (\opt\oceanbase\src\sql\engine\join\ob_basic_nested_loop_join_op.cpp:70)
oceanbase::sql::ObNestedLoopJoinOp::read_left_operate(oceanbase::sql::ObNestedLoopJoinOp * this) (\opt\oceanbase\src\sql\engine\join\ob_nested_loop_join_op.cpp:343)
oceanbase::sql::ObNestedLoopJoinOp::inner_get_next_row(oceanbase::sql::ObNestedLoopJoinOp * this) (\opt\oceanbase\src\sql\engine\join\ob_nested_loop_join_op.cpp:203)
oceanbase::sql::ObOperator::get_next_row(oceanbase::sql::ObOperator * this) (\opt\oceanbase\src\sql\engine\ob_operator.cpp:1063)
oceanbase::sql::ObExecuteResult::get_next_row(const oceanbase::sql::ObExecuteResult * this) (\opt\oceanbase\src\sql\executor\ob_execute_result.cpp:120)
oceanbase::sql::ObExecuteResult::get_next_row(oceanbase::sql::ObExecuteResult * this, oceanbase::sql::ObExecContext & ctx, const oceanbase::common::ObNewRow *& row) (\opt\oceanbase\src\sql\executor\ob_execute_result.cpp:55)
oceanbase::sql::ObResultSet::inner_get_next_row(oceanbase::sql::ObResultSet * this, const oceanbase::common::ObNewRow *& row) (\opt\oceanbase\src\sql\ob_result_set.cpp:372)
oceanbase::sql::ObResultSet::get_next_row(oceanbase::sql::ObResultSet * this, const oceanbase::common::ObNewRow *& row) (\opt\oceanbase\src\sql\ob_result_set.cpp:360)
oceanbase::observer::ObQueryDriver::response_query_result(oceanbase::observer::ObQueryDriver * this, oceanbase::sql::ObResultSet & result, bool is_ps_protocol, bool has_more_result, bool & can_retry, int64_t fetch_limit) (\opt\oceanbase\src\observer\mysql\ob_query_driver.cpp:144)
oceanbase::observer::ObSyncPlanDriver::response_result(oceanbase::observer::ObSyncPlanDriver * this, oceanbase::observer::ObMySQLResultSet & result) (\opt\oceanbase\src\observer\mysql\ob_sync_plan_driver.cpp:95)
oceanbase::observer::ObMPQuery::response_result(oceanbase::observer::ObMPQuery * this, oceanbase::observer::ObMySQLResultSet & result, bool force_sync_resp, bool & async_resp_used) (\opt\oceanbase\src\observer\mysql\obmp_query.cpp:1297)
oceanbase::observer::ObMPQuery::do_process(oceanbase::observer::ObMPQuery * this, oceanbase::sql::ObSQLSessionInfo & session, bool has_more_result, bool force_sync_resp, bool & async_resp_used, bool & need_disconnect) (\opt\oceanbase\src\observer\mysql\obmp_query.cpp:836)
oceanbase::observer::ObMPQuery::process_single_stmt(oceanbase::observer::ObMPQuery * this, const oceanbase::sql::ObMultiStmtItem & multi_stmt_item, oceanbase::sql::ObSQLSessionInfo & session, bool has_more_result, bool force_sync_resp, bool & async_resp_used, bool & need_disconnect) (\opt\oceanbase\src\observer\mysql\obmp_query.cpp:554)
oceanbase::observer::ObMPQuery::process(oceanbase::observer::ObMPQuery * this) (\opt\oceanbase\src\observer\mysql\obmp_query.cpp:361)
oceanbase::rpc::frame::ObSqlProcessor::run(oceanbase::rpc::frame::ObSqlProcessor * this) (\opt\oceanbase\deps\oblib\src\rpc\frame\ob_sql_processor.cpp:41)
oceanbase::omt::ObWorkerProcessor::process_one(oceanbase::omt::ObWorkerProcessor * this, oceanbase::rpc::ObRequest & req) (\opt\oceanbase\src\observer\omt\ob_worker_processor.cpp:67)
oceanbase::omt::ObWorkerProcessor::process(oceanbase::omt::ObWorkerProcessor * this, oceanbase::rpc::ObRequest & req) (\opt\oceanbase\src\observer\omt\ob_worker_processor.cpp:131)
oceanbase::omt::ObThWorker::process_request(oceanbase::omt::ObThWorker * this, oceanbase::rpc::ObRequest & req) (\opt\oceanbase\src\observer\omt\ob_th_worker.cpp:248)
oceanbase::omt::ObThWorker::worker(oceanbase::omt::ObThWorker * this, int64_t & tenant_id, int64_t & req_recv_timestamp, int32_t & worker_level) (\opt\oceanbase\src\observer\omt\ob_th_worker.cpp:407)
oceanbase::omt::ObThWorker::run(oceanbase::omt::ObThWorker * this, int64_t idx) (\opt\oceanbase\src\observer\omt\ob_th_worker.cpp:446)
oceanbase::lib::Threads::start()::$_156::operator()() const(const class {...} * this) (\opt\oceanbase\deps\oblib\src\lib\thread\threads.cpp:188)
std::_Function_handler<void (), oceanbase::lib::Threads::start()::$_156>::_M_invoke(std::_Any_data const&)(const std::_Any_data & __functor) (\opt\oceanbase\deps\3rd\usr\local\oceanbase\devtools\include\c++\9\bits\std_function.h:300)
std::function<void ()>::operator()() const(const std::function<void ()> * this) (\opt\oceanbase\deps\3rd\usr\local\oceanbase\devtools\include\c++\9\bits\std_function.h:688)
oceanbase::lib::Thread::__th_start(void * arg) (\opt\oceanbase\deps\oblib\src\lib\thread\thread.cpp:300)
libpthread.so.0!start_thread (Unknown Source:0)
libc.so.6!clone (Unknown Source:0)

从下往上看堆栈,前面的大部分是处理 SQL 的通用流程,后面进入到类 ObVirtualTableIterator 开始进行虚拟表的处理。

最终获取冻结阈值是调用方法 ObTenantFreezer::get_freeze_trigger_() 实现的,证明上一步的静态分析是正确的。

从源码理解冻结阈值的计算方式

get_freeze_trigger_() 源码注释如下:

(可跳过,直接阅读下方的小结)

int ObTenantFreezer::get_freeze_trigger_(ObTenantFreezeCtx &ctx)
{
  int ret = OB_SUCCESS;
  ObTenantResourceMgrHandle resource_handle;
  // 获取租户ID
  const uint64_t tenant_id = MTL_ID();
  // 获取memstore上限
  const int64_t mem_memstore_limit = ctx.mem_memstore_limit_;
  int64_t kv_cache_mem = 0;
  int64_t memstore_freeze_trigger = 0;
  int64_t max_mem_memstore_can_get_now = 0;
  // 获取租户资源管理器句柄,主要用于后面进一步获取租户内存管理器(ObTenantMemoryMgr)从而得到内存使用情况
  if (OB_FAIL(ObResourceMgr::get_instance().
              get_tenant_resource_mgr(tenant_id,
                                      resource_handle))) {
    // 如果资源管理器获取失败
    LOG_WARN("[TenantFreezer] fail to get resource mgr", KR(ret), K(tenant_id));
    ret = OB_SUCCESS;
    // freeze_trigger_percentage的取值范围是[1,99]
    // C++中除法 '/'会舍去小数部分
    // 所以资源管理器获取失败的情况下,memstore_freeze_trigger的值恒为0(此处缺陷已提pr)
    memstore_freeze_trigger =
      get_freeze_trigger_percentage_() / 100 * mem_memstore_limit;
  } else {
    // 从资源管理器中获取各内存值
    int64_t tenant_mem_limit = get_tenant_memory_limit(tenant_id);
    int64_t tenant_mem_hold = get_tenant_memory_hold(tenant_id);
    int64_t tenant_memstore_hold = get_tenant_memory_hold(tenant_id,
                                                          ObCtxIds::MEMSTORE_CTX_ID);
    // 声明is_overflow,初始值为true,即溢出
    bool is_overflow = true;
    // 获取kv_cache持有值
    kv_cache_mem = resource_handle.get_memory_mgr()->get_cache_hold();
    // 如果租户总内存持有值大于租户内存上限,说明溢出
    if (tenant_mem_limit < tenant_mem_hold) {
      LOG_WARN("[TenantFreezer] tenant_mem_limit is smaller than tenant_mem_hold",
               K(tenant_mem_limit), K(tenant_mem_hold), K(tenant_id));
    }
    // is_add_overflow接收三个参数,第一个和第二个参数相加的和如果小于0,返回 true,表明溢出
    // 否则,将前两个参数的和赋值给第三个参数,然后返回flase,表明未溢出
    // 此处max_mem_memstore_can_get_now = (tenant_mem_limit - tenant_mem_hold)+ tenant_memstore_hold
    // 即max_mem_memstore_can_get_now = 租户当前未分配的内存 + 当前memstore已持有的值
    else if (is_add_overflow(tenant_mem_limit - tenant_mem_hold,
                               tenant_memstore_hold,
                               max_mem_memstore_can_get_now)) {
      if (REACH_TIME_INTERVAL(1 * 1000 * 1000)) {
        LOG_WARN("[TenantFreezer] max memstore can get is overflow", K(tenant_mem_limit),
                 K(tenant_mem_hold), K(tenant_memstore_hold), K(tenant_id));
      }
    }
    // 此处二次调用is_add_overflow,即max_mem_memstore_can_get_now = max_mem_memstore_can_get_now + kv_cache_mem
    // 代入上一步的计算式,即max_mem_memstore_can_get_now = (tenant_mem_limit - tenant_mem_hold)+ tenant_memstore_hold + kv_cache_mem
    // 总结:max_mem_memstore_can_get_now = 租户下当前未分配的内存 + memstore 持有内存 + kv_cache 持有内存
    else if (is_add_overflow(max_mem_memstore_can_get_now,
                               kv_cache_mem,
                               max_mem_memstore_can_get_now)) {
      if (REACH_TIME_INTERVAL(1 * 1000 * 1000)) {
        LOG_WARN("[TenantFreezer] max memstore can get is overflow",
                 K(tenant_mem_limit), K(tenant_mem_hold), K(tenant_memstore_hold),
                 K(kv_cache_mem), K(tenant_id));
      }
    } else {
      // 以上都没溢出的话,is_overflow 设置为 false
      is_overflow = false;
    }

    int64_t min = mem_memstore_limit;
    if (!is_overflow) {
      // 如果未溢出,取 mem_memstore_limit 和max_mem_memstore_can_get_now 中较小的值来计算最终的冻结触发阈值
      min = MIN(mem_memstore_limit, max_mem_memstore_can_get_now);
    }

    // 各内存单位为字节,这里以 100 为分界线分为两种计算方式,意义不大,已提 pr
    if (min < 100) {
      memstore_freeze_trigger =  get_freeze_trigger_percentage_() * min / 100;
    } else {
      memstore_freeze_trigger = min / 100 * get_freeze_trigger_percentage_();
    }
  }
  // result
  ctx.max_mem_memstore_can_get_now_ = max_mem_memstore_can_get_now;
  ctx.memstore_freeze_trigger_ = memstore_freeze_trigger;
  ctx.kvcache_mem_ = kv_cache_mem;

  return ret;
}

计算方式小结

  1. ObTenantFreezer::get_freeze_trigger_() 方法中涉及七个内存相关的变量:
    • tenant_mem_limit:租户内存上限
    • tenant_mem_hold:租户内存持有值
    • mem_memstore_limit:租户 memstore 上限
    • tenant_memstore_hold:租户 memstore 持有值
    • kv_cache_mem:kv_cache 持有值
    • max_mem_memstore_can_get_now:memstore 当前能达到的最大值
    • memstore_freeze_trigger:触发冻结的阈值
  2. 开始会先获取租户资源管理器,用于后续获取实时内存值。如果获取失败,会采用默认的 freeze_trigger_percentage / 100 * memstore_limit 进行计算(此处存在缺陷,计算结果恒为 0,已提 PR)。
  3. 获取资源管理器成功后,最终冻结阈值 memstore_freeze_trigger 的计算基于以下两个值中的一个:
    • mem_memstore_limit:由参数 memstore_limit_percentage 设置。
    • max_mem_memstore_can_get_now:根据实时内存情况计算得出,即 max_mem_memstore_can_get_now = 租户下当前未分配的内存 + memstore持有内存 + kv_cache 持有内存。
  4. 取上面两个值中较小的一个记为min,根据min 的大小,以100为分界线分为两种计算方法(此处分两种计算意义不大,已提PR):
    • min<100:get_freeze_trigger_percentage_() * min / 100
    • min ≥ 100:min / 100 * get_freeze_trigger_percentage_()

综上,当 max_mem_memstore_can_get_now < mem_memstore_limit 时,监控图上的冻结阈值就会有变化,且这个冻结阈值不可能高于由 mem_memstore_limit 计算得出的阈值,这与我们观察到的冻结阈值曲线一致。

此外,对于在理解源码时发现的两处代码逻辑缺陷,已提 PR 和 Issue。

总结

在 OceanBase 中,冻结阈值并不是一个固定的值,它会根据当前内存情况进行实时判断。当存在除了 MemStore 和 kv_cache 之外的内存模块占用了较多的内存,挤占了 MemStore 的内存上限时,冻结阈值就会相应地降低。

结合本文场景监控图,租户 fuse_row_cache (蓝线)与冻结阈值曲线的变化趋势正好相反,说明该场景下应该是 fuse_row_cache 的增长挤占了 memstore 的内存,导致冻结阈值变低。

运维建议

当我们的运维同学发现冻结阈值发生变化时,说明存在其他内存模块挤占了 memstore 的内存,此时需要格外注意内存的使用情况,结合实际业务场景评估影响面。

另外,需要注意的是,max_mem_memstore_can_get_now 计算式中的 kv_cache 并不是所有可动态伸缩内存(比如 fuse_row_cache/user_row_cache 等)加起来的和,而是特指内存模块 KvstorCacheMb ,这部分将留到下一篇文章介绍。

更多技术文章,请访问:https://opensource.actionsky.com/

关于 SQLE

爱可生开源社区的 SQLE 是一款面向数据库使用者和管理者,支持多场景审核,支持标准化上线流程,原生支持 MySQL 审核且数据库类型可扩展的 SQL 审核工具。

SQLE 获取

类型地址
版本库https://github.com/actiontech/sqle
文档https://actiontech.github.io/sqle-docs/
发布信息https://github.com/actiontech/sqle/releases
数据审核插件开发文档https://actiontech.github.io/sqle-docs/docs/dev-manual/plugins/howtouse

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

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

相关文章

Redis两种持久化机制RDB和AOF详解(面试常问,工作常用)

redis是一个内存数据库&#xff0c;数据保存在内存中&#xff0c;但是我们都知道内存的数据变化是很快的&#xff0c;也容易发生丢失。幸好Redis还为我们提供了持久化的机制&#xff0c;分别是RDB(Redis DataBase)和AOF(Append Only File)。 在这里假设你已经了解了redis的基础…

微信小程序上,实现图片右上角数字显示

微信小程序上&#xff0c;实现图片右上角数字显示 直接上代码&#xff1a; 样式代码index.wxss如下&#xff1a; .circle_rednum {position: absolute;color: white;font-size: 13px;background-color: #EC2F43;width: 23px;height: 23px;line-height: 23px;left: 80%;top: …

RuntimeError: DataLoader worker (pid 2105929) is killed by signal: Killed.

PyTorch DataLoader num_workers Test - 加快速度 可以利用PyTorch DataLoader类的多进程功能来加快神经网络训练过程。 加快训练进程 为了加快训练过程&#xff0c;我们将利用DataLoader类的num_workers可选属性。 num_workers属性告诉DataLoader实例要使用多少个子进程进…

pytorch工具——pytorch中的autograd

目录 关于torch.tensor关于tensor的操作关于梯度gradients 关于torch.tensor 关于tensor的操作 x1torch.ones(3,3) xtorch.ones(2,2,requires_gradTrue) print(x1,\n,x)yx2 print(y) print(x.grad_fn) print(y.grad_fn)zy*y*3 outz.mean() print(z,out)注意 atorch.randn(2,…

SQL调优教程

SQL调优教程 基础方法论 任何计算机应用系统性能问题最终都可以归结为 1.cpu消耗 2.内存使用 3.对磁盘&#xff0c;网络或其他I/O设备的输入/输出(I/O)操作 遇到性能问题时&#xff0c;要判断的第一点就是“在这三种资源中&#xff0c;是否有哪一种资源达到了有问题的程度”&…

通过四点分析CRM系统的发展趋势

CRM系统企业管理客户的有力武器&#xff0c;为企业发展、降本增效奠定了基础&#xff0c;国内CRM经过十几年的发展产品已经十分成熟&#xff0c;很多企业会问CRM系统未来发展趋势是什么&#xff1f;今天小编就分享几个CRM未来的趋势。 1.AI人工智能 去年席卷全球的ChatGPT让大…

【数据结构】链表是否有环相关问题

文章目录 快指针走3、4、5步甚至更多可以吗为什么快慢指针一定在入口点相遇![在这里插入图片描述](https://img-blog.csdnimg.cn/ba346dbc9fee425dbb895ae2962e99ce.png) 快指针走3、4、5步甚至更多可以吗 部分情况下可以。 如果这样&#xff0c;相对&#xff08;追及&#xf…

在VSCode中实现Rust编程调试指南

在 VS Code 中调试 Rust&#xff1a;终极指南 在本教程中&#xff0c;您将学习如何使用 VS Code 调试 Rust。可用于使用 VS Code 调试 Rust 的操作。设置 VS Code 来调试 Rust Rust因其易用性、安全性和高性能而继续保持其作为最受欢迎的编程语言的地位。随着 Rust 的流行&…

paddle尝试PP-Vehicle-属性识别

PP-Vehicle-属性识别 windows11环境&#xff0c;conda虚拟环境中运行。 首先安装环境 conda create -n paddle python3.7conda activate paddlepip install paddlepaddle-gpupip install pyyamlpip install opencv-pythonpip install scipypip install matplotlibpip install…

关于Integer类的一个有趣的面试问题

相信很多人觉得答案是false&#xff0c;false&#xff0c;因为Integer是一个类&#xff0c;把int类型的数据传给Integer类型的数据会创建一个对象&#xff0c;而a,b,c,d作为引用指向的是不同的地址&#xff0c;所以判断相同得到的结果应该是false 但这个想法就正中下怀了&#…

链表 --- C语言实现

本篇文章来详细介绍一下数据结构中的链表。 目录 1.链表的概念及结构 2.链表的分类 3.单链表的实现 4.链表的面试题 5.双向链表的实现 6.顺序表和链表的区别 1.链表的概念及结构 概念&#xff1a;链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素…

【C++】多态(举例+详解,超级详细)

本篇文章会对C中的多态进行详解。希望本篇文章会对你有所帮助。 文章目录 一、多态的定义及实现 1、1 多态的概念 1、2 多态的构成条件 1、2、1 虚函数 1、2、2 虚函数的重写 1、2、3 析构函数构成重写特例原因 1、3 多态的实例练习 1、3、1 例1 1、3、2 例2 1、3、3 例3 1、4…

【C语言】指针进阶(2)

接上期文章指针进阶&#xff08;1&#xff09;指针进阶&#xff08;1&#xff09; 目录 1.函数指针 2.函数指针数组 3.指向函数指针数组的指针 4.回调函数 4.1qsort的用法 void*类型的指针介绍 使用qsort对数组进行排序 使用qsort对结构体进行排序&#xff1a; 4.2使用…

【C++】-初步认识和学习继承

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你 …

【CAS6.6源码解析】在IDEA中调试可插拔的supprot模块

CAS源码的casWebApplication启动后&#xff0c;默认只加载最小的支撑系统的模块&#xff0c;很多模块&#xff08;大部分在support包下&#xff09;是需要手动去引入的&#xff08;对新人来说有坑&#xff09;&#xff0c;这里介绍一下如何手动引入这些模块。 文章目录 调试步骤…

TCP通信 -- 文件传输

www 客户端&#xff1a; package com.itheima.b03TCPTest3;import java.io.*; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException;public class Client {public static void main(String[] args) throws IOException {System.out.p…

HTTP超本文传输协议

HTTP超本文传输协议 HTTP简介HTTP请求与响应HTTP请求请求行请求头空行请求体 HTTP响应响应行响应头空行响应体 HTTP请求方法GET和POST之间的区别HTTP为什么是无状态的cookie原理session 原理cookie 和 session 的区别cookie如何设置cookie被禁止后如何使用session HTTP简介 HT…

idea快速运行vue项目

目录 一、前提 二、步骤 安装vue.js插件 添加脚本 进行如下配置 一、前提 安装好node.js环境并初始化完成和安装好依赖 二、步骤 安装vue.js插件 打开idea,然后在File–Settings–Plugins–Makerplace下找到vue.js插件,安装并重启idea 添加脚本 进行如下配置 在Sctipts中根…

【手撕排序算法】---基数排序

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【数据结构初阶&#xff08;C实现&#xff09;】 我们直到一般的排序都是通过关键字的比较和移动这两种操作来进行排序的。 而今天介绍的…

MAC 推送证书不受信任

配置推送证书的时候&#xff0c;一打开就变成不受信任&#xff0c;搜了很多解决版本。 由于苹果修改相关规定&#xff0c;推送证书 打开Apple PKI - Apple 下载AppleWWDRCA文件&#xff0c;选择G4,双击安装之后&#xff0c;证书已经变为受信任。 AppleWWDRCA(Apple Worldwid…