成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

B站搜索建庫架構優化實踐

開發 架構
通過這一系列的技術更新和流程優化,我們的索引構建架構最終從早期的單機構建發展到分布式的數據存儲和建庫任務,能力更強的同時也更易維護和迭代,索引構建周期實現了從天級到小時級別的飛躍式進步,為業務的未來發展奠定了堅實基礎。

前言

搜索是B站的重要基礎功能,需要對包括視頻、評論、圖文等海量的站內優質資源建立索引,處理來自用戶每日數億的檢索請求。離線索引數據的正確、高效產出是搜索業務的基礎。我們在這里分享搜索離線架構整體的改造實踐:從周期長,流程復雜的手工構建流程,改造為高容量、高性能、易迭代的分布式建庫架構的過程。

業務背景

B站是一個典型的多資源搜索場景,除了視頻外,還接入了包括UP主、番劇影視(PGC)、直播等幾十種不同類型的資源。除了資源類型多以外,各種資源的數據源的形式也多種多樣,包括數據庫、上游業務接口、Hive表等等。這些數據通過離線近線的聚合和構建,以全量和增量實時流兩種方式生產出索引,在線上的服務中生效。

圖片圖片

實際業務中,除了搜索業務自己維護的視頻MySQL數據庫外,還接入了不同形式的數據來源,一并添加到全量/增量索引數據中構建。這些數據有的僅T+1更新全量;有的則會提供增量數據流和接口,接入時也希望數據變更能在搜索索引中實時生效。

全量(base)索引:以文件形式提供,包含某一特定時間點之前的全部數據,通常每天產出、更新一次。

增量(delta)索引:以數據流(消息隊列)的形式提供,每條消息對應一份稿件的完整信息。增量索引能實時同步更新,適用于時效性要求高的場景,滿足用戶對新稿件的檢索需求。

這些第三方數據由于各種原因(MySQL容量或性能限制,數據維護團隊不同等),不能直接集成到搜索的MySQL數據庫中。這些數據的引入在當前的離線和近線建庫流程中是獨立的,隨著數據的種類增加,搜索離線建庫流程也逐漸復雜起來。

索引數據產出流程

以視頻索引為例,構建視頻索引時除了搜索自己維護的MySQL中視頻信息外,還需要接入多種第三方數據到索引中,如接入以下三類數據:

  • 數據A:第三方數據庫+binlog
  • 數據B:不開放DB直接訪問,以導出的全量snapshot+接口+數據流形式間接提供
  • 數據C:Hive表

全量數據產出

為了獲取稿件信息,全量建庫流程需要先后將這些不同的數據源獲取到本地,合并為新的全量數據集,再構建出二進制全量索引。

圖片圖片

增量數據產出

對于增量索引,我們希望將每條稿件的完整字段(即包含搜索MySQL+數據源A/B/C的所有字段信息)聚合為一條增量索引消息下發。

我們監聽binlog A和數據流B,但只關心其中有變化的稿件id,通過變更搜索MySQL中相應稿件的一個標志位,觸發該稿件的一條binlog及后續的增量建庫流程。

在后續的建庫流程中,處理binlog時增加根據ID查詢MySQL A和接口B的邏輯,這樣就能從MySQL A的查詢結果,接口B的響應和本地的文件C中拿到稿件的對應數據,寫入到增量索引數據流中。

圖片圖片

注意這里對MySQL A和接口B有著嚴格的時效性要求,即查詢得到的數據不能比數據流下發的數據版本更舊,否則無法保證數據的最終一致性。

舉個例子,假如MySQL A的binlog來自主庫,而按ID查詢時訪問了MySQL A的從庫。

當A中某個稿件的字段發生變化時,在處理主庫的對應binlog時會從從庫中查詢當前值。由于主庫從庫之間的同步有一定延遲,這時有可能查詢到舊值而不是主庫中的新值,導致新值不會在索引中生效,除非該稿件有其他變更再次觸發binlog處理。

將MySQL A的binlog調整為由對應從庫導出則可避免該問題發生。

索引生效流程

B站的檢索引擎和正排服務加載索引數據的過程如下:

  1. 更新全量索引:服務周期性地獲取新的全量索引文件,解析元數據,將索引加載到內存中。
  2. 消費增量數據流:加載了全量索引后,服務會從元數據指定的時間點開始消費增量索引數據流,直到消費進度接近當前的最新產出。消費到的數據會在服務側實時地建立增量索引,以支持查詢。當一份稿件同時出現在增量索引和全量索引時,全量索引中的數據被覆蓋;稿件在數據流中出現多次時,增量索引中的舊值也會被新值覆蓋。
  3. 提供服務:當增量數據流的產出進度被消費追平后,服務進入就緒狀態,開始處理請求。

問題與挑戰

隨著搜索業務復雜度的增加和數據規模的增長,現有建庫邏輯在效率和資源層面逐漸難以為繼。

性能

新投稿的增多和歷史投稿的積累,讓搜索MySQL在建庫過程中的負載越來越大,開始出現性能瓶頸。

造成MySQL高負載的主要場景有:

1. 表結構變更:需要復制全表數據。數據復制期間,數據庫負載顯著增加。

2. 新增字段并批量導入:將新字段導入數據庫時需要大量寫入,增加數據庫壓力。

  • 緩解措施:控制寫入過程,在負載低峰期小批量執行。

3. 全量索引構建時的掃庫操作:索引構建流程每次需要先查詢搜索MySQL數據庫獲取全量稿件信息,dump到文件中,再進行索引構建。當在基線外另行構建索引以支持AB實驗時,數據庫的查詢負載也會相應地倍增。

  • 緩解措施:錯開不同構建任務的執行時間;將掃庫結果和元數據保存下來,供多個建庫任務復用。

當MySQL負載高時,主從數據庫同步會出現延遲,導致索引數據更新不及時。用戶不能及時看到最新的投稿和變更,體驗受損害。

我們在日常實驗和迭代時,都必須時刻考慮對數據庫的性能影響,而緩解數據庫壓力的措施往往導致迭代周期變長,不同的需求上的索引迭代也不能并行推進。性能瓶頸和穩定性風險成為了索引迭代的主要障礙。

維護成本

  1. 搜索索引原始數據沒有統一的存儲承載,數據可能來自多個數據庫、文件、甚至接口。離線和近線需要各自維護復雜的拼接邏輯,導致迭代困難,開發周期長。數據不符合預期時,難以定位原因。
  2. 全量數據和增量數據的產出邏輯差異大,沒有機制能保證兩套鏈路上最終的產出結果一致。數據的不一致可能影響業務效果。
  3. 全量索引是單機構建,構建周期,實例部署和數據分片都需要人工維護,每次迭代都消耗大量人力成本。
  4. 增量數據接入新的實時數據時,數據提供方需要同時提供全量、增量數據和查詢接口。搜索業務和數據提供方的對接、開發成本都較高。

資源

每次構建索引時,都需要對原始數據重新切詞分析,構建正排、倒排索引。即使數據和策略沒有變化也需要重復計算。這些計算會消耗大量資源,并且增加索引構建所需的時間。

設計目標

反思索引構建流程中的問題,不管是復雜的多數據源合并流程、還是MySQL的性能壓力延遲風險,歸根結底都是存儲設施能力不足,不能直接承載全部索引數據導致的問題復雜化。我們期望將索引依賴的全部數據聚合到統一的存儲設施中,作為基準數據源,直接基于該數據源導出全量數據和增量數據流,并將后續的建庫任務徹底分布式化,達到數據統一、可擴展性大幅增強的目的。

預期新的架構設計可解決當前的痛點:

  1. 用分布式的存儲設施承接搜索數據,后續的構建任務也進行分布式化改造,讓建庫鏈路中不再有單點的性能瓶頸。
  2. 全量和增量都從統一的存儲設施中獲取,完全屏蔽原有不同數據來源對建庫過程的影響,可以讓建庫的流程簡單化,降低開發維護的成本。
  3. 有了足夠的存儲能力之后,我們可以將稿件切詞這類較為穩定的中間計算結果保存下來,只在有數據或策略有變化時重新計算。節省計算資源的同時進一步縮短全量構建周期。

方案設計

既然MySQL不能滿足我們的需求,我們的人力也不允許從零開始造一個輪子來維護索引數據,首要的問題是找到一個合適的存儲設施來作為基礎,在其之上開發數據處理和建庫邏輯。

存儲選型

我們希望新的存儲方案可以具有以下特性:

  • 高容量:可以存入目前全部的索引數據。易于水平拓展。
  • 高吞吐量:允許大批量的數據寫入和導出。
  • 低延遲:可以快速隨機讀寫單個稿件數據。
  • 易于迭代:數據的結構可以靈活迭代,無需Online DDL操作(https://dev.mysql.com/doc/refman/8.4/en/innodb-online-ddl-operations.html)。

我們從現有的數據庫系統中尋求合適的選型:對于數據靈活迭代的需求排除了MySQL,TiDB等關系型數據庫;批量更新稿件部分字段的需求對文檔存儲性的NoSQL數據庫不友好;HBase,Cassandra等列族存儲的數據模型比較合適,但簡單寫入查詢延遲較高;KV(鍵值)存儲延遲低,但數據模型不太匹配。

最終我們選擇在KV存儲的基礎上封裝行式(Row-Oriented)數據庫的形式,以達到兼顧吞吐與延遲的效果,并選擇了b站內部存儲組件Taishan作為底層KV存儲。

Taishan是B站內部的高性能分布式KV存儲,具備多分片水平拓展能力,可應對大規模存儲需求,簡單讀寫的延遲也較低,此外還具有以下特性:

  • 有序映射:key是有序的,支持scan(范圍查詢)。
  • 支持 CompareAndSwap 操作:基于CAS原子操作可實現樂觀鎖,在并發的數據更新時防止沖突。
  • 高效的數據導出:Taishan 支持快速將數據全量導出到對象存儲中,而不需要全表scan。

架構設計

我們以Taishan為基礎存儲設施,在其上建立了強大的數據存儲層(基于表格存儲模型)和統一的數據接入和導出層。

圖片圖片

引入存儲層后,我們將原始數據源跟具體的建庫邏輯隔離,并抽象出可高度復用的數據導入導出邏輯,顯著降低了維護成本和后續開發成本。

離線和近線使用相同的數據來源并復用導出邏輯,從根本上消除了全量索引和增量索引數據不一致的可能。

高容量的存儲層允許我們以增量計算形式來源空間換取時間:將一些中間計算結果(如切詞和Embedding等)也一并保存,僅在相關稿件屬性有變化時觸發計算并更新;建庫時直接使用保存下來的結果,計算量大幅減少。

在全部數據都經Taishan聚合后,離線近線的流程變得清晰直觀起來,同時在根本上消除了全量和增量數據不一致的可能性。增量計算的引入也減少了大量重復的計算量,節省了資源。新的數據流程使得迭代和維護都大為簡化。

數據存儲層

表格模型

基于KV存儲之上封裝出表格存儲模型:

  • 行:每一行代表一個稿件,通過稿件ID標識。
  • 列:每一列存儲稿件的若干相關字段,通過字段族名(CF,column family)標識。
  • 單元格:行和列的組合確定一個單元格,對應KV存儲中的一條記錄。

圖片圖片

如上表所示,稿件ID和行一一對應。而稿件的具體字段和列(CF)是多對多的關系:

  • 同一稿件的若干相關字段可以打包保存在一列(如title和uname),這樣會大幅減少KV的訪問次數,提高效率。
  • 表格中不同的列可以保存相同的字段。例如:
  • 這里eb,eb1兩列分別對應doc_embedding字段的不同版本。指定不同的列組合(fs,seg,eb)/(fs,seg,eb1),可以構建出兩版包含相同(title,uname,title_term,uname_term),以及不同版本doc_embedding的索引數據。

支持并發寫入

Taishan的CAS支持允許我們在不同寫入方之間通過樂觀鎖來同步,避免多個寫入方同時更新一個單元格時發生寫入丟失。

如果一列的寫入方唯一,也可以不使用CAS直接寫入。

支持并發寫入消除了將多個字段放進同一CF的后續維護風險。即使同一列后續要增加新的寫入方,也無需對該列進行拆分改造。

Key設計

Key由ID和字段族名組成。下圖是稿件ID1234567890的seg列對應的實際Key內容。ID和列名間用 “:”來分隔。

圖片

Data Orientation

Key的設計決定了數據排列方式。底層Taishan存儲中,數據按Key的字符串順序連續排列。

ID1:CF1

ID1:CF2

ID2:CF1

ID2:CF2

我們將稿件ID放在列名稱前,這樣表格數據在Taishan中實際存放形式如下:

ID1:CF1

ID1:CF2

ID2:CF1

ID2:CF2

實際使用場景中我們主要按行掃描(獲取相同ID的所有字段值),較少按列掃描(獲取某一字段下所有取值)。同一ID下所有列連續分布的排列使得按ID(行)讀取時有更好的訪問局部性和Cache命中率。

同時,ID使用大端序int64保存。大端序的特點是作為字符串看待時的順序和數字的升序一致,讓遍歷過程總是按ID升序執行,符合人的直覺與習慣。

Value設計

Value總是以一個varint N作為header,該varint標識隨后的N個字節保存的是元數據,其余的字節則用于存儲實際數據。

下圖是一個包含header、元數據(大小為8字節)和數據(大小為5字節)的value。

圖片圖片

通過這種設計,我們得到了一個高效、靈活且近似于傳統表格存儲的系統,可以為索引建庫提供強大的支持。

序列化

拋棄JSON

在最初的建庫流程中,我們使用JSON Lines文件格式保存原始的稿件數據,例如:video.jsonl

{"id": 81403056, "title": "高燃舞臺演繹B站最美的夜", "uname": "嗶哩嗶哩晚會", "doc_embedding": [0.168322, 0.015824, 0.091791, -0.2059]}
{"id": 613621262, "title": "【觸手猴】「強風オールバック」を弾いてみた【Piano】", "uname": "marasy_觸手猴", "doc_embedding": [0.007262, 0.040466, 0.028768, 0.161083]}

JSON格式具有良好的可讀性,但效率和性能不足,主要體現在:

  • 存儲效率低:每條記錄中都會重復保存字段名及符號(如 {} 和 ""),浪費大量存儲空間。
  • 序列化性能低:文本格式的解析性能較差。

如果將完整的稿件字段拆分到多個列中分別保存,導出和查詢時的序列化消耗會更加嚴重。

轉向protobuf

為了解決上述問題,我們選擇了Protocol Buffers(protobuf)格式來序列化稿件字段。以下是簡化的Video消息的protobuf定義:video.proto

message Video {
    int64 id = 1;
    string title = 2;
    string uname = 3;
    repeated float doc_embedding = 4;
}

protobuf的優勢主要有:

  • 存儲空間優化:protobuf使用field number(以varint形式存儲)來區分字段,相比于JSON中存儲完整字段名,大大節省了存儲空間。
  • 性能提升:protobuf的反序列化速度優于JSON。
  • 類型安全:和JSON相比,protobuf為數據字段提供了強類型保證,增強了我們對數據的信心。嚴格的數據類型也從源頭上消除了JSON 固有的最大安全整數(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)問題。

高效地合并

對于protobuf消息,多個序列化后的數據塊(buffer)可以直接拼接來達到合并的效果:

Video v;
v.ParseFromString(buf1 + buf2);

這與將其分布反序列化后再執行合并是等效的:

Video v1, v2;
v1.ParseFromString(buf1);
v2.ParseFromString(buf2);
v1.MergeFrom(v2);

假設稿件的標題保存在列1,稿件Embedding信息保存在列2,buf1/buf2分別是對應單元格查詢的數據。我們只需要直接concate未經反序列化的兩段buffer即可拼接出稿件完整數據。

在使用Cord(https://protobuf.dev/reference/cpp/cpp-generated/#cord)或zero_copy_stream(https://protobuf.dev/reference/cpp/api-docs/google.protobuf.io.zero_copy_stream/)的情況下,連這一次拼接也可以省去。

變更數據流

Taishan原生支持binlog導出,可以將變更的Key和Value導出到數據流。但直接使用Taishan的binlog會有以下問題:

  • 大批量寫入數據時,會產出大量消息,對后續的整個近線鏈路乃至線上服務造成壓力。
  • Value只包含變更的列,獲取完整的數據仍需按ID掃描Taishan表。

為了靈活控制變處理,我們封裝了寫入層,在數據寫入Taishan完成后另外輸出一條變更消息,示例如下:

{"id": 613621262, "cf_changed": ["eb"]} // 稿件av613621262的eb列發生了變更

數據變更的場合是否輸出對應變更消息到流中是可指定的,批量寫入數據時我們選擇不觸發。

我們也省略了變更后的值,需要的消費者可以直接查詢Taishan表的主節點獲取最新的字段。

查詢從節點可能得到更新前的舊值,破環數據的最終一致性。

數據接入層

存儲方案和序列化方式的確定后,我們開始將現有的數據統一接入到Taishan中。具體而言是將原有的數據庫全量增量以及T+1更新的數據全部寫入上文所說的Taishan表格,并按需同步到變更數據流。

T+1數據接入

T+1更新的數據沒有實時的增量更新,只需要定時寫入全量。

寫入的邏輯是高度復用的,同過指定配置將hive表/TSV/CSV/JSON Lines文件映射到Taishan中的對應列。

圖片圖片

從新版全量中被刪除的數據需要額外的處理:我們需要跟上一版數據對比,找出這些被刪除的數據,將這些數據同步從Taishan中刪除。

實時數據接入

實時數據接入需要將數據實時寫入Taishan,并同步到變更數據流。寫入的順序必須為先寫Taishan再寫變更數據流,保證后續處理變更流時能從Taishan主節點查到最新取值。

圖片圖片

由于我們不能對上游提供的變更數據流的字段定義做任何的限制和假設,處理增量的Worker需要開發少量的解析邏輯。

增量寫入只寫入了最近有變更的數據,為了讓Taishan保存全部的歷史數據,在實時數量接入后,我們還需要再獲取一份全量數據(產出時間在增量接入后),將這些數據也寫入Taishan中。

這的全量寫入過程和T+1數據接入類似,區別在于只需要初始化時執行一次,后續的變更都可以通過增量來獲取。另外T+1全量寫入時需要考慮和增量寫入沖突的情況,具體在后文介紹。

寫入沖突處理

全量vs增量

包含實時數據的數據在導入時往往也需要刷入一份全量數據做初始化。

一般來說,全量中的數據會比來自數據流的舊,直接寫入會將來自數據流的新值覆蓋。

因此寫入時需要利用CAS,僅當滿足Precondition:值不存在時,才會寫入。

增量vs增量

同一列可以有多個寫入方,比如兩個worker消費兩條不同的數據流寫入同一列中的兩個不同字段。此時需要先讀取稿件的該列的舊值,更新其中的新字段后,將完整的新值寫入。

當兩個worker同時運行時,可能發生寫寫沖突(Write-Write Conflict),導致一個worker寫入的新值,被另外的worker覆蓋而丟失。

在這種場景下,我們同樣使用CAS,在計算出新值后,僅當滿足Precondition:值==舊值時,才會寫入新值,當Precondition不滿足時,必須重試。

增量計算

我們將一些中間計算結果也保存在Taishan中。典型的場景如稿件標題的切詞和向量化的結果。具體而言,如果使用的計算策略和稿件標題沒有變化,切詞和向量化的結果可以認為是穩定的。因此我們可以保存這些結果來避免重復計算,只在稿件的屬性有變化時更新計算結果。

增量更新切詞結果的工作流如下圖所示:

圖片圖片

和實時數據接入類似,在首次接入時也需要對全部已有數據進行一次切詞,讓歷史稿件也有切詞結果。初始化過程同樣需要使用CAS來規避寫寫沖突。

數據導出層

數據進入Taishan后,我們需要從中導出需要的數據來構建全量和增量索引。

對于一份索引,其全量和增量構建任務的配置是共用的,獲取到實際數據后的處理邏輯也是一致的。

Taishan中不同的列可以保存字段的多個版本,具體構建全量和增量索引時選用哪些列中的字段,需要在配置中指定。配置中以白名單的形式指定列,無需擔心新增列對已有構建任務產生影響。

不同版本的索引大部分情況下只需要調整配置再另行部署即可產出。

圖片圖片

全量數據導出

Taishan會每日定期將全量數據備份到公司內部的對象存儲。我們通過備份數據好的數據,遍歷其中的全部KV,也就是按行遍歷表格,取出指定的列來獲取全量索引數據。Taishan備份效率是非常高的,通常在分鐘級,這也大幅地減少了掃庫的時間開銷。

增量數據導出

增量數據的導出通過消費變更數據流實現。消費后對每條消息(ID+變更的CF)反查Taishan,獲取所需的完整字段。

數據迭代

新的存儲機制簡化了索引的迭代流程,只需要將新數據寫入Taishan,然后調整構建任務關注的列即可。迭代的開發量大幅下降,基本只需復用現有流程,調整配置后部署新任務。

圖片圖片

隨著新存儲方案和增量計算等優化的落地,索引構建的周期縮短了一半以上。迭代周期縮短的同時,消除了鏈路上的性能瓶頸和延遲風險。

分布式構建

搜索索引最早都是通過物理機crontab定時執行腳本實現。腳本執行各種掃庫操作,將數據加載到內存中,并產出一份完整的JSON Lines文本文件作為原始索引數據集,然后調用indexer進行全量切詞和構建正排/倒排索引。物理機部署穩定性沒有保證且難以維護,我們首先把構建遷移到K8S集群上,嘗試以服務形式部署。這樣雖然保證了建庫任務的穩定性,但是建庫任務資源利用率很低。建庫服務構建時需要大量資源,但在大多數時間里是不消耗任何資源的,容器依然需要占據相應的CPU/內存資源。雖然通過超配(低軟限高硬限)并錯開任務觸發時間可以來減少資源空置的情況,但維護較為繁瑣。最終我們選擇將建庫遷移到業界廣泛使用的分布式計算框架Spark上,利用Spark潮汐資源進行索引構建,一方面可以加大構建并發,另一方面也可以對資源進行更充分的利用。

為了能夠盡可能的復用及降低維護成本,從流程上進行抽象,將索引構建流程分以下主要步驟:

  1. 讀取數據:從配置中加載并進行數據讀取,Taishan源在對象存儲的導出文件為sst格式,使用官方開源的JNI庫進行二次封裝。
  2. 解碼 :Taishan非Spark原生支持的數據源,需要額外開發解析邏輯。
  3. 再分片:由于導出的單一文件分片數據量較大,一次性讀取將占用大量內存,甚至OOM。為解決上述問題,參考Spark的cache機制,在讀取并解碼文件數據流的過程時先將文件載入到指定內存buffer中,若buffer裝滿則生成一個分片(partition)并寫入hdfs中。以此將較少的文件分片(如128個)拆分為較多的hdfs文件分片(如5000個),便于Spark的后續處理。
  4. 稿件處理:通過配置對稿件數據進行一系列的處理操作,如過濾、字段映射等。
  5. 編碼 & 索引構建:將稿件內容轉化為索引需要的特定編碼形式,如Flatbuffer、Protobuffer。并根據索引類型和Meta信息,產出索引。
  6. 壓縮打包:對構建出的索引及Meta數據進行打包,產出最終索引文件。 

除更省資源外,Spark構建的任務并發度也更高,進一步縮短了構建時間,最終達到小時級別。

總結與展望

通過這一系列的技術更新和流程優化,我們的索引構建架構最終從早期的單機構建發展到分布式的數據存儲和建庫任務,能力更強的同時也更易維護和迭代,索引構建周期實現了從天級到小時級別的飛躍式進步,為業務的未來發展奠定了堅實基礎。

參考

  • https://protobuf.dev/programming-guides/encoding/#varints
  • https://dev.mysql.com/doc/refman/8.4/en/innodb-online-ddl-operations.html
  • https://research.google/pubs/large-scale-incremental-processing-using-distributed-transactions-and-notifications/
  • https://protobuf.dev/programming-guides/encoding/#last-one-wins
責任編輯:武曉燕 來源: 嗶哩嗶哩技術
相關推薦

2022-07-05 15:08:52

機房架構

2022-11-22 08:42:38

數據庫

2024-04-26 12:13:45

NameNodeHDFS核心

2022-09-15 15:18:23

計算實踐

2024-02-28 07:50:36

大數據標簽系統AB 實驗

2025-03-05 00:00:55

2022-07-29 14:53:09

數據實踐

2020-04-28 08:15:55

高可用架構系統

2023-02-16 07:24:27

VPA技術

2023-02-09 07:38:39

配置中心架構組件

2023-07-19 08:58:00

數據管理數據分析

2023-02-28 12:12:21

語音識別技術解碼器

2023-02-13 09:48:00

PRESTO 集群緩存優化

2023-10-26 06:43:25

2022-10-08 15:41:08

分布式存儲

2012-07-03 08:57:42

ASO百度移動搜索手機站優化

2022-04-07 16:50:28

FlinkB站Kafka

2023-11-03 12:54:00

KAFKA探索中間件

2023-03-31 13:31:45

2015-05-07 09:32:55

APP開源
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 天天色图| 99精品视频在线 | 亚洲毛片在线观看 | 日韩美香港a一级毛片免费 国产综合av | 91超碰在线 | 秋霞电影一区二区三区 | 一区二区日韩 | 久久久久久中文字幕 | 久草综合在线视频 | 成人精品一区二区三区 | 国产japanhdxxxx麻豆 | 国产精品美女 | 蜜桃在线一区二区三区 | 成人黄视频在线观看 | 成人性生交大片免费看r链接 | 鲁大师一区影视 | 亚洲成av人片在线观看无码 | 成人美女免费网站视频 | 国产精品亚洲一区二区三区在线 | 天堂资源 | 国产精品高潮呻吟久久 | 韩日精品在线观看 | 久久久精品一区二区三区四季av | 国产欧美在线视频 | 中文无吗 | 国产精品一区二区久久 | 免费观看成人性生生活片 | 成人免费精品 | 香蕉久久久 | 91一区二区三区 | 国产视频1区 | 国产精品成人品 | 全部免费毛片在线播放网站 | 91精品一区二区三区久久久久 | 91视视频在线观看入口直接观看 | 日韩成人免费视频 | 国产网站在线 | 妞干网福利视频 | 亚洲综合色视频在线观看 | 午夜国产精品视频 | 精品亚洲一区二区三区 |