OceanBase ストレス テスト中に凍結しきい値が変化するのはなぜですか?

本稿では、OceanBaseの耐圧試験における凍結閾値の動的な変化の原因をソースコードの観点から分析し、運用・保守への提案を行います。

著者: 張騫

宇宙人2号で、5匹の猫のクソ担当も兼務。

この記事の出典: 元の寄稿

  • Aikesheng オープンソース コミュニティによって作成されており、オリジナルのコンテンツを許可なく使用することはできません。転載する場合は編集者に連絡し、出典を明示してください。

バックグラウンド

小規模なテナントでテストしたとき、メモリ フリーズをトリガーするしきい値が動的に変化し、次のような症状が発生することがわかりました。

  • 非耐圧テスト中:凍結しきい値は、memstore_limit_percentage * freeze_trigger_percentageOceanBase の公式ドキュメントに記載されている計算値であり、約 322MB です
  • ストレス テスト中:フリーズしきい値は不規則な減少を示しましたが、増加はありませんでした。つまり、非ストレス テスト中は常に 322MB 未満でした。

ストレステスト中に閾値が変化するのはなぜですか? 次に、現象の原因を分析します。

環境情報

バージョン情報 オーシャンベース_CE 4.1.0.0
テナント仕様 ユニット8C4G
memstore_limit_percentage 50 (デフォルト)
フリーズ_トリガー_パーセント 20 (デフォルト値)

システムベンチコマンド

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

トラブルシューティングのプロセス

監視収集の正しさを確認する

監視インジケータに従って、次のように公式ドキュメントでコレクション 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を手動で実行します。

get_freeze_trigger_()注: OceanBase では、メソッドを呼び出すメソッドが複数あるため、手動で実行された SQL にブレークポイントがヒットしない可能性があります。表示されたスタックから SQL を収集してブレークポイントにヒットしたかどうかを判断する必要がありますが、ヒットしない場合は SQL がブレークポイントにヒットするまで続行をクリックし続けます。

もう 1 つの方法は、対応するクラスのメソッドにブレークポイントを設定することです。これにより、SQL の実行後にブレークポイントが直接ヒットし、メソッドObAllVirtualSysStatに到達するまで段階的にデバッグできます。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_()このメソッドには、メモリ関連の変数が 7 つ含まれます。
    • 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 の計算は、次の 2 つの値のいずれかに基づいて行われます。
    • mem_memstore_limit: パラメータ memstore_limit_percentage によって設定されます。
    • max_mem_memstore_can_get_now: リアルタイムのメモリ条件に基づいて計算されます。つまり、max_mem_memstore_can_get_now = テナントの下で現在割り当てられていないメモリ + memstore によって保持されるメモリ + kv_cache によって保持されるメモリ。
  4. 上記 2 つの値のうち小さい方を min として記録します。min のサイズに応じて、100 を境界線とする 2 つの計算方法があります (ここで 2 つの計算を分割することには意味がなく、PR が発生しています)。
    • min<100:get_freeze_trigger_percentage_() * min / 100
    • 分 ≥ 100:分 / 100 * get_freeze_trigger_percentage_()

要約すると、 のmax_mem_memstore_can_get_now < mem_memstore_limit場合、監視グラフ上の凍結しきい値は変化し、この凍結しきい値mem_memstore_limitは によって計算されたしきい値を超えることはできません。これは、観察した凍結しきい値曲線と一致します。

さらに、ソース コードを理解する際に見つかった 2 つのコード ロジックの欠陥に対してPR問題が提起されました。

要約する

OceanBaseではフリーズ閾値は固定値ではなく、現在のメモリ状況に応じてリアルタイムに判断されます。MemStore と kv_cache 以外に、より多くのメモリを占有し、MemStore のメモリ上限を占有するメモリ モジュールがある場合、フリーズしきい値はそれに応じて減少します。

この記事のシーン監視図と組み合わせると、テナントのfusion_row_cache ** (青い線) ** の変化傾向はフリーズしきい値の曲線のちょうど反対側にあり、このシナリオでは、fuse_row_cache の増加によって memstore のメモリが占​​有され、その結果フリーズしきい値が低くなることが示されています。

運用・保守のご提案

運用保守の学生がフリーズしきい値が変化したことに気付いた場合、それは他のメモリ モジュールがメモリストアのメモリを占有していることを意味するため、メモリの使用状況に特別な注意を払い、実際のビジネス シナリオと組み合わせて影響範囲を評価する必要があります。

さらに、max_mem_memstore_can_get_now計算式内の kv_cache は、fuse_row_cache/user_row_cache動的にスケーラブルなメモリ (など) のすべての合計ではなく、具体的にはメモリ モジュール KvstorCacheMb を指すことに注意してください。これは次の記事のために予約されます。

さらに技術的な記事については、https: //opensource.actionsky.com/をご覧ください。

SQLEについて

Akson オープン ソース コミュニティの SQLE は、データベース ユーザーおよび管理者向けの SQL 監査ツールです。これは、マルチシナリオ監査をサポートし、標準化されたオンライン プロセスをサポートし、MySQL 監査をネイティブにサポートし、スケーラブルなデータベース タイプを備えています。

SQL取得

タイプ 住所
リポジトリ 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
2023 年に最も需要の高い 8 つのプログラミング言語: PHP は好調、C/C++ の需要は鈍化 Programmer's Notes CherryTree 1.0.0.0 リリース CentOS プロジェクトは「誰にでもオープン」と宣言 MySQL 8.1 と MySQL 8.0.34 正式リリース GPT-4 はますますバカになっている?精度率は 97.6% から 2.4% に低下しました Microsoft: Windows 11 で Rust Meta を使用するための取り組みを強化 拡大: オープンソースの大規模言語モデル Llama 2 をリリースし、商用利用は無料です C# と TypeScript の父が最新の オープンソース プロジェクトを発表しました: TypeChat は レンガを移動したくないが、要件も満たしたいと考えていますか? おそらく、この 5,000 スター GitHub オープン ソース プロジェクトが役立つかもしれません - MetaGPT Wireshark の 25 周年記念、最も強力なオープン ソース ネットワーク パケット アナライザー
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/actiontechoss/blog/10089752