中文字幕精品亚洲无线码二区,国产黄a三级三级三级看三级,亚洲七七久久桃花影院,丰满少妇被猛烈进入,国产小视频在线观看网站

【大數(shu)據高并發(fa)核心場景實戰(zhan)】 數(shu)據持(chi)久化層(ceng) - 查詢(xun)分(fen)離

上一章(zhang)中(zhong)我們介紹(shao)到冷熱分離,旨在快(kuai)速交付。但是他仍存(cun)在一些(xie)問(wen)題,并不是完美(mei)的(de)方案,比如限制了業務(wu)的(de)操作,必須再特定的(de)業務(wu)場(chang)景下(冷數據(ju)不允許修改、冷數據(ju)查詢(xun)(xun)慢、不適合復雜查詢(xun)(xun))。本章(zhang)將介紹(shao)新的(de)方案,支(zhi)持千萬數據(ju)的(de)快(kuai)速查詢(xun)(xun)。

1. 業務場景

適用場景

  1. 數據查詢緩慢(數據量大導致、數據聚合時調用外部系統過多導致等)
  2. 寫數據效率尚可
  3. 所有數據都可能修改(若存在冷數據,可使用上一章的冷熱分離方案)

基本思路:將更新的(de)數(shu)據放在主數(shu)據庫里,而(er)查(cha)(cha)詢的(de)數(shu)據放在另外一(yi)(yi)個專門(men)(men)針對(dui)搜索的(de)存儲系統(tong)里。主庫單表查(cha)(cha)詢,無關聯無外鍵,所以寫(xie)數(shu)據無壓力。數(shu)據查(cha)(cha)詢通過一(yi)(yi)個專門(men)(men)處理(li)大數(shu)據量的(de)查(cha)(cha)詢引擎來解(jie)決。

這里有同學可能會提到(dao)數(shu)(shu)據(ju)庫讀寫分(fen)離,這種情(qing)況下(xia)在(zai)千萬(wan)級別數(shu)(shu)據(ju)量下(xia)的(de)(de)速度提升并不(bu)大,并且只能解決數(shu)(shu)據(ju)庫查(cha)詢慢的(de)(de)問(wen)(wen)題(ti),不(bu)能解決其他如查(cha)詢詳情(qing)時調用外部系統耗時長導致(zhi)的(de)(de)查(cha)詢慢問(wen)(wen)題(ti)。

核心問題

  1. 如何觸發查詢分離?
  2. 如何實現查詢分離?
  3. 查詢數據如何存儲
  4. 查詢數據如何使用
  5. 歷史數據如何遷移

2. 查詢分離

2.1 如何觸發查詢分離

1)修改業務代碼,寫入同時同步更新查詢數據

同步更新示意圖
圖2-1: 同步更新查詢數據示意(yi)圖

2)修改業務代碼,在寫入常規數據后,異步更新查詢數據

異步更新示意圖
圖2-2: 異步更新查詢(xun)數據(ju)示(shi)意圖

3)監控數據庫日志,如有數據變更,則更新查詢數據

優點是不會(hui)影響(xiang)業務代碼。

監控日志更新示意圖
圖2-3: 監(jian)控數據庫(ku)日志(zhi)更(geng)新查詢(xun)數據示意圖

優缺點對比

優缺點對比表
圖2-4: 三種觸發方(fang)式(shi)優缺點對比表

針對優缺點總結適用場景

適用場景總結
圖2-5: 三種方法適(shi)用場(chang)景總結

2.2 如何實現查詢分離

這里(li)以方(fang)法二,業(ye)務代碼異步更新查詢數據的方(fang)式(shi)為例(li)講解實現方(fang)式(shi),這個(ge)方(fang)法需要考慮以下(xia)幾個(ge)問題:

  1. 寫操作較多且線程太多時,需要加以控制,否則太多線程最終會拖垮JVM
  2. 創建查詢數據的線程出錯時,如何自動重試?如何標識更新失敗的數據?
  3. 多線程并發時,需要解決很多并發場景

針對以上問題,可以考慮使用MQ來解決(jue):在短(duan)時間線程過多時,將任(ren)務暫存到MQ中間件進行(xing)削峰處(chu)理;業(ye)務失敗(bai)時可自動重新發送消(xiao)息重試。

MQ解決方案示意圖
圖(tu)2-6: MQ解決方案架構示意圖(tu)

具體方案

  1. 寫操作時,主數據表添加標識 NeedUpdateQueryData=true,MQ消息簡單,只是一個信號來告知更新數據,不包含更新的數據ID(如果包含業務信息,就需要考慮更多的冪等和消息丟失等問題)
  2. 消費者獲取信號后,先批量查詢待更新的主數據,然后批量更新查詢數據,更新完成后將查詢數據的主數據標識 NeedUpdateQueryData 更新為 false
  3. 若存在多個消費者同時有遷移動作的情況,就涉及并發性問題,這與前一場景冷熱分離中的并發性處理邏輯類似,這里不再贅述

消息的時序性問題

  • 生產者1 將數據A修改為A1,發送消息Q1
  • 生產者2 將數據A1修改為A2,發送消息Q2
  • 消費者1 收到Q1,查詢數據為A1(此時消費者2收到Q2,將數據A2遷移到緩存),A1遷移到緩存

即消(xiao)(xiao)費(fei)(fei)者查詢(xun)數(shu)據(ju)庫數(shu)據(ju)后,在未(wei)遷移(yi)數(shu)據(ju)時被后觸發的消(xiao)(xiao)費(fei)(fei)者線程更(geng)新了遷移(yi)了更(geng)新的數(shu)據(ju),而(er)后先消(xiao)(xiao)費(fei)(fei)的消(xiao)(xiao)費(fei)(fei)者會(hui)將(jiang)后消(xiao)(xiao)費(fei)(fei)消(xiao)(xiao)費(fei)(fei)者的遷移(yi)更(geng)新掉,導致緩存本該后遷移(yi)記錄丟(diu)失。

解決方法:消費者查詢 NeedUpdateQueryData=true 數據的同時查詢 lastUpdateTime 作為樂觀鎖字段進行更新。

2.3 查詢數據如何存儲

常用的兩個中間件是 MongoDB 和 ES,選(xuan)擇取決于團隊成員的技術結構(gou)。我們(men)團隊選(xuan)擇的是 ES。

特性維度 MongoDB Elasticsearch
數據模型 文檔型數據庫,類似JSON,結構靈活 搜索引擎,擅長處理非結構化文本數據
核心優勢 高性能讀寫、靈活的數據模型、橫向擴展 強大的全文檢索、復雜查詢和數據分析
查詢場景 適合精確查詢、范圍查詢、事務和聚合操作 適合模糊匹配、全文搜索、多條件復雜檢索
寫入性能 寫入速度較快,支持高并發寫入 寫入吞吐量通常低于MongoDB,但近實時搜索(秒級)
讀取性能 精確查詢和聚合操作性能優秀 復雜搜索和全文檢索性能卓越
事務支持 支持多文檔ACID事務 不支持事務,保證最終一致性
資源消耗 磁盤占用通常更小(高壓縮存儲引擎) 磁盤和內存消耗相對較高
擴展性 支持分片集群,需手動配置 天生分布式,開箱即用,自動分片
管理維護 集群配置相對復雜,需要專業知識 管理相對簡單,有完善的監控工具
適用場景 Web應用后端、用戶畫像、設備監控 搜索引擎、日志分析、實時監控
不適用場景 復雜的全文搜索需求 需要強事務一致性的場景
學習成本 中等,查詢語法相對簡單 較高,查詢DSL較復雜
社區生態 成熟穩定,社區活躍 生態豐富,插件眾多
成本考量 通常存儲成本更低 資源消耗大,總體成本可能更高

2.4 查詢數據如何使用

ES自帶查詢API,在業務代碼中直接調用ES即可。這里涉及到一個場景:緩存和數據庫數據不一致的問題

兩種解決思路

  1. 在查詢數據更新到最新前,不允許用戶查詢(在數據同步完成前,強制查詢走主數據源如MySQL,而不是ES)
  2. 給用戶提示"當前數據為2s前的數據,如發現數據不準確可嘗試刷新",通常用戶都能接受

2.5 歷史數據遷移

當前方案中,只需要把所有歷史數據加上標識 NeedUpdateQueryData=true,程序就(jiu)會自動處理(li)。

2.6 MQ+ES 整體方案

  1. 業務數據修改后,觸發異步線程數據同步
  2. 觸發異步方式使用MQ(解耦、削峰)
  3. 查詢數據到ES(適合大數據量的復雜查詢)
  4. 查詢數據同步到ES會有一定延時,用戶可能查詢到舊數據,需給用戶提示
  5. 歷史數據遷移,只需把所有歷史數據的標識改成true,系統會自動批量同步到ES
整體方案示意圖
圖2-7: MQ+ES整體(ti)架構方案示意(yi)圖

這個整體方(fang)案看似簡單,但(dan)有(you)一些陷阱必須注意。下面著(zhu)重介(jie)紹使用Elasticsearch時的注意事項。

3. ElasticSearch注意事項

Elasticsearch的使用要點:

  1. 如何使用Elasticsearch設計表結構?
  2. Elasticsearch的存儲結構
  3. Elasticsearch如何修改表結構?
  4. Elasticsearch的準實時性
  5. Elasticsearch可能丟數據
  6. Elasticsearch分頁

3.1 如何使用Elasticsearch設計表結構

Elasticsearch基于索引設計(ji),無法像MySQL那(nei)樣使(shi)用(yong)join查詢,所(suo)以查詢數(shu)據時需要把每(mei)條主數(shu)據及關聯(lian)子(zi)表(biao)的數(shu)據全部整合在(zai)一(yi)條記錄中。

下面以常見的(de)訂單業(ye)務類講(jiang)解如何設計ES表結(jie)構(gou):

訂單數據結構
圖(tu)3-1: 訂單業(ye)務數(shu)據結(jie)構示意圖(tu)

雖然訂單數據(ju)(ju)在關系型(xing)數據(ju)(ju)庫中(zhong)涉(she)及多表(biao),但使用Elasticsearch存儲數據(ju)(ju)時不會設計多個表(biao),而是將所有表(biao)的相關字段(duan)數據(ju)(ju)匯(hui)集在一個Document中(zhong),即一個完整(zheng)的文(wen)檔結構:

{
  "order_ID": "o2020103115214521",
  "order_invoice": {},
  "user": {
    "user_ID": "U1099",
    "user_name": "YiHuiComeOn"
  },
  "order_product_item": [
    {
      "product_name": "乒乓球拍",
      "product_count": 1,
      "product_price": 149
    },
    {
      "product_name": "紙巾",
      "product_count": 2,
      "product_price": 1.4
    }
  ],
  "total_amount": 20
}

習慣關系型數(shu)據庫的(de)同學(xue)可能會有疑(yi)惑(huo):為(wei)什么匯(hui)聚到(dao)同一document中?為(wei)什么ES不需要關聯查詢?這就涉及到(dao)ES特殊(shu)的(de)存儲結構。

3.2 Elasticsearch的存儲結構

3.2.1 Lucene和MySQL的概念對照

Lucene是一個索引系統,此處把(ba)Lucene與MySQL的一些概(gai)念做簡單對照:

Lucene與MySQL概念對照
圖(tu)3-2: Lucene與MySQL概(gai)念對照表

3.2.2 無結構文檔的倒排索引

假設有(you)一些無(wu)結構(gou)文檔(dang)數據:

無結構文檔
圖3-3: 無結構文(wen)檔示例(li)

簡(jian)單倒(dao)排索引后的結果(guo):

簡單倒排索引結果
圖3-4: 無結構文檔倒排(pai)索引結果

無結構的文檔經過簡單的倒排索引后,字典表(biao)主(zhu)要存放關鍵字,而倒(dao)排表(biao)存放該(gai)關鍵字所在的文(wen)檔ID。業務(wu)數(shu)據通常不是無結構(gou)的文(wen)檔內容(rong),而是有結構(gou)的數(shu)據,此時如(ru)何倒(dao)排索引呢?

3.2.3 有結構文檔的倒排索引

更復雜的(de)例子:每(mei)個Doc都有多個Field,Field有不(bu)同的(de)值(包(bao)含不(bu)同的(de)Term,Term是經(jing)過文本分(fen)析處(chu)理(li)后不(bu)可再分(fen)割的(de)最小單位)。

有結構文檔示例
圖(tu)3-5: 有結構文檔示例

倒排表

  1. 性別倒排索引
性別倒排索引
圖3-6: 性別字段倒排索引示(shi)例
  1. 年齡倒排索引
年齡倒排索引
圖3-7: 年齡字段倒排索引(yin)示(shi)例
  1. 武功倒排索引
武功倒排索引
圖3-8: 武(wu)功(gong)字段倒排索(suo)引示例

由此可見,有結構的文(wen)檔經過倒(dao)排索引后(hou),字段(duan)中的每個(ge)值都是(shi)一(yi)個(ge)關(guan)鍵(jian)字,存(cun)放在Term Dictionary中,且每個(ge)關(guan)鍵(jian)字都有對應地址指向所在文(wen)檔。

3.2.4 ES的Document如何定義結構和字段格式

設計ES的(de)Document結構時,不需要像MySQL那樣關聯表,而是把(ba)所(suo)有相關數據匯集在(zai)一個Document中。直接將(jiang)3.1節中訂單的(de)JSON文檔轉(zhuan)成一個ES文檔(SQL中的(de)子表數據在(zai)Elasticsearch中以嵌入式(shi)對(dui)象格式(shi)存儲):

{
  "mappings": {
    "doc": {
      "properties": {
        "order_ID": {
          "type": "text"
        },
        "order_invoice": {
          "type": "nested"
        },
        "order_product_item": {
          "type": "nested",
          "properties": {
            "product_name": {
              "type": "text"
            }
          }
        },
        "total_amount": {
          "type": "long"
        },
        "user": {
          "properties": {
            "user_ID": {
              "type": "text"
            },
            "user_name": {
              "type": "text"
            }
          }
        }
      }
    }
  }
}

至(zhi)此,大(da)家已經了解了Elasticsearch表結(jie)構(gou)的設計(ji)。在實際業務中(zhong),主數據修改(gai)表結(jie)構(gou)時,ES也要求修改(gai)文檔結(jie)構(gou),這時該怎么(me)辦(ban)?

3.3 Elasticsearch如何修改表結構

  • ES支持直接添加新字段
  • 因為修改字段的類型會導致索引失效,所以ES不支持修改原字段類型

Elasticsearch底(di)層基于Lucene,Lucene的倒排(pai)(pai)索引一旦創(chuang)建(jian)就(jiu)是不可變的。就(jiu)像印刷(shua)好的書(shu)籍,你(ni)不能(neng)直接修改某(mou)一頁的排(pai)(pai)版,只能(neng)重(zhong)新印刷(shua)一本。

  • 如果想修改字段的映射(表結構),需要新建一個索引,然后使用Elasticsearch的reindex功能將舊索引復制到新索引中
POST /_reindex
{
  "source": {"index": "products_old"},
  "dest": {"index": "products_new"}
}

reindex功能會使舊索引失效,直接重命名字段時可以使用alias索引功能

注意:通(tong)常(chang)不會直(zhi)接(jie)刪除舊字段,常(chang)用做(zuo)法是新版本項(xiang)(xiang)目代碼兼容(rong)舊數(shu)據,在項(xiang)(xiang)目穩定運行(xing)后,再考慮清理舊字段。

3.4 陷阱一:Elasticsearch是準實時的嗎

當更(geng)新(xin)數據至Elasticsearch且返(fan)回(hui)成功提(ti)示時,通過Elasticsearch查詢返(fan)回(hui)的數據可能不是最新(xin)的。

這個過(guo)程涉(she)及Elasticsearch的Shard(分片),以及Lucene Index、Segment、Document三者之間(jian)的關(guan)系(xi)。

Elasticsearch的一(yi)(yi)個Shard就是一(yi)(yi)個Lucene Index,每一(yi)(yi)個Lucene Index由多個Segment構成。

分片(Shard)結構圖

分片結構圖
圖3-9: Elasticsearch分片結構示意圖

Index、Segment、Document三者之間的關系

三者關系圖
圖3-10: Index、Segment、Document關(guan)系圖

數據索引的過程詳解

  1. 當新(xin)的Document被創建時(shi),數(shu)據首(shou)先會(hui)存放(fang)到新(xin)的Segment中,同時(shi)舊Document會(hui)被刪(shan)除,并在原來的Segment上標(biao)記刪(shan)除標(biao)識。當Document被更新(xin)時(shi),舊版(ban)Document會(hui)被標(biao)識為刪(shan)除,并將新(xin)版(ban)Document存放(fang)在新(xin)的Segment中

  2. Shard收(shou)到(dao)寫請(qing)求(qiu)時,請(qing)求(qiu)會被寫入Translog中(zhong)(zhong),然后Document被存放在(zai)Memory Buffer中(zhong)(zhong)

寫請求處理
圖3-11: Elasticsearch寫請(qing)求處理流程

注意:Memory Buffer 不會被查詢到(dao)

  1. 每隔1秒(默認設置),Refresh操作被執行一次,Memory Buffer中的數據會被寫入一個Segment,并存放在File System Cache中,這時新數據就可以被搜索到了
Refresh操作示意圖
圖3-12: Refresh操作(zuo)數據刷新流程(cheng)

通俗理解整個過程

名詞解釋

  • Document:ES中的基本數據單元,相當于一條記錄
  • Segment:Lucene索引的基本單元,是不可變的
  • Memory Buffer:臨時存儲新文檔的內存區域
  • Translog:記錄所有寫操作的日志文件
  • Refresh:將內存中的數據寫入新Segment并使其可搜索的操作
  • File System Cache:操作系統級別的磁盤緩存

流程解釋

  1. 新數據到達:先登記到Translog,再放到Memory Buffer
  2. 定期刷新:每1秒將Memory Buffer中的數據寫入Segment,放到File System Cache
  3. 此時數據可被搜索

通過以上數據索引過程的說明,可以發現Elasticsearch并不是實時的,而是有1秒延時。解決(jue)方案是提示用戶查詢的數據會有(you)一定延時(shi)。

3.5 陷阱二:Elasticsearch宕機恢復后,數據丟失

上一(yi)小節中提(ti)及每隔1秒Memory Buffer中數據(ju)會(hui)被(bei)刷到(dao)Segment中,此時數據(ju)可被(bei)用(yong)戶搜索到(dao),但(dan)沒有持久化(hua),一(yi)旦系(xi)統宕機,數據(ju)就會(hui)丟失。

如何防止數據丟失?使用Lucene中的Commit操作解決這個問題。

Commit操作方法:先將多個Segment合并保存到磁(ci)盤中,再進行持久化標(biao)記。

但(dan)commit有(you)兩個(ge)問題:

  1. 會占用IO資源,使得commit期間數據查詢變慢
  2. 無法解決數據保存時,在translog寫完還未寫入文件系統緩存情況的數據丟失

translog持(chi)久化到磁盤需要執行fsync操作,具體實現方法有兩種:

  1. index.translog.durability設置成request,缺點是耗費資源,性能差一些
  2. index.translog.durability設置為async,每隔index.translog.sync_interval時間執行一次fsync

配置建議

# 方案A:金融級安全(不能丟任何數據)
PUT /my_index/_settings
{
  "index.translog.durability": "request"
}

# 方案B:普通業務(可容忍少量數據丟失)
PUT /my_index/_settings
{
  "index.translog.durability": "async",
  "index.translog.sync_interval": "5s"
}

實踐總結

根據業務需求選擇策略

業務類型 推薦配置 解釋
金融交易 durability: request 數據絕對不能丟失
電商訂單 durability: async, sync_interval: 1s 可容忍極短時間延遲
日志分析 durability: async, sync_interval: 5s 丟幾條日志沒關系

記住:沒(mei)有完(wan)美的(de)方案,只有適合你業務需(xu)求(qiu)的(de)方案!

3.6 陷阱三:分頁越深,查詢效率越低

Elasticsearch的讀操(cao)作流程主(zhu)要分為兩個階段:Query Phase、Fetch Phase。

  1. Query Phase:協調節點先把請求分發到所有分片,每個分片在本地查詢后建一個結果集隊列,將Document ID以及搜索分數存放在隊列中,再返回給協調節點,協調節點建全局隊列,歸并所有結果集并進行全局排序

Tips:在Elasticsearch查詢過程中(zhong),如果search方法帶(dai)有from和size參數(shu)(shu),Elasticsearch集(ji)群需要給(gei)協(xie)調節點(dian)返(fan)回分片(pian)數(shu)(shu)×(from+size)條數(shu)(shu)據(ju),然后(hou)在單(dan)機上進行排(pai)序,最后(hou)給(gei)客(ke)戶(hu)(hu)端返(fan)回size大小的(de)數(shu)(shu)據(ju)。比如客(ke)戶(hu)(hu)端請(qing)求10條數(shu)(shu)據(ju),有3個(ge)分片(pian),那么每個(ge)分片(pian)會(hui)返(fan)回10條數(shu)(shu)據(ju),協(xie)調節點(dian)最后(hou)會(hui)歸(gui)并30條數(shu)(shu)據(ju),但最終只返(fan)回10條數(shu)(shu)據(ju)給(gei)客(ke)戶(hu)(hu)端。

Elasticsearch讀操作示意圖
圖3-13: Elasticsearch讀操作(zuo)兩(liang)階段流(liu)程(cheng)
  1. Fetch Phase:協調節點先根據結果集里的Document ID向所有分片獲取完整的Document,然后所有分片返回完整的Document給協調節點,最后協調節點將結果返回給客戶端

比如有5個分片,需要查詢排序序號從10000到10010(from=10000,size=10)的結果,每個分片返回給協調節點計算的數據量是10010條。這是為了防止其他分片中沒有數據,考慮最壞情況10010條數據都在自己分片上,進而把10010條數據全部給協調節點去聚合計算。

也就是說(shuo),協調(diao)節點需要在內存(cun)中計算10010×5=50050條記(ji)錄,所以用戶分頁越(yue)深查詢速度會越(yue)慢,分頁并(bing)不是越(yue)多越(yue)好。

那如何更好地解決Elasticsearch分頁問題呢?為了控制性能,可以使用Elasticsearch中的max_result_window進行配置,這個數據默認為10000,當from+size > max_result_window時,Elasticsearch將返(fan)回錯誤。

如果用戶確實有深度翻頁的需求,使用Elasticsearch中search_after的(de)功能也(ye)能解決,只(zhi)是無法實現跳頁(這樣分片可以利用游標條件(jian)過(guo)濾部分數據(ju),從(cong)而減少數據(ju)計算的(de)數量提升查詢速度)。

舉例(li),查(cha)詢結果按照訂單總金(jin)額分頁(ye)(ye),上一(yi)頁(ye)(ye)最后一(yi)個訂單的(de)(de)總金(jin)額total_amount是(shi)10,那么下一(yi)頁(ye)(ye)的(de)(de)查(cha)詢示例(li)代(dai)碼如下:

{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "user.user_name.keyword": "YiHuiComeOn"
          }
        }
      ],
      "must_not": [],
      "should": []
    }
  },
  "from": 0,
  "size": 2,
  "search_after": ["10"],
  "sort": [
    {
      "total_amount": "asc"
    }
  ],
  "aggs": {}
}

至此,Elasticsearch的(de)一(yi)些要(yao)點就(jiu)介(jie)紹完了(le)。MQ也有一(yi)些要(yao)點,比如確保時序、確保重試、確保消(xiao)息重復(fu)消(xiao)費不(bu)(bu)會影響業務,以(yi)及確保消(xiao)息不(bu)(bu)丟失等,后續各章節會有相應的(de)場(chang)景描述,這里就(jiu)不(bu)(bu)再展開了(le)。

4. 小結

查詢分離這個(ge)解(jie)決方案(an)雖然能解(jie)決一些問題,但也要(yao)認(ren)識到它的不(bu)足:

  1. 使用Elasticsearch存儲查詢數據時,要接受一些局限性:有一定延時,深度分頁不能自由跳頁,有丟數據的可能性
  2. 主數據量越來越大后,寫操作還是慢,到時還是會出問題。比如工單數據,雖然已經去掉所有外鍵,但當數據量上億時,插入還是會有問題
  3. 主數據和查詢數據不一致時,如果業務邏輯需要查詢數據保持一致性呢?查詢數據同步到最新數據會有約2秒延時,某些業務場景下用戶可能無法接受

架構(gou)"沒(mei)有(you)銀彈(dan)",不能(neng)(neng)期望一個(ge)解(jie)決(jue)方案既能(neng)(neng)覆(fu)蓋所有(you)的問(wen)題(ti),還能(neng)(neng)實現最小的成本(ben)損耗(hao)。

如(ru)果碰到一個場(chang)景(jing)不能接受(shou)上面某個或(huo)某些(xie)不足時,該怎么解決(jue)?接著看后面的章節。

posted @ 2025-10-29 11:45  yihuiComeOn  閱讀(280)  評論(0)    收藏  舉報