兩個矢古綱之戰,或 Cassandra 與 HBase 之戰。 俄羅斯聯邦儲蓄銀行團隊經驗

這甚至不是一個玩笑,似乎這張特定的圖片最準確地反映了這些資料庫的本質,到最後就會清楚為什麼:

兩個矢古綱之戰,或 Cassandra 與 HBase 之戰。 俄羅斯聯邦儲蓄銀行團隊經驗

根據 DB-Engines 排名,最受歡迎的兩種 NoSQL 列式資料庫是 Cassandra(以下簡稱 CS)和 HBase(HB)。

兩個矢古綱之戰,或 Cassandra 與 HBase 之戰。 俄羅斯聯邦儲蓄銀行團隊經驗

命運的安排,我們Sberbank的資料載入管理團隊已經 很久以前 並與 HB 密切合作。 在這段時間裡,我們很好地研究了它的優點和缺點,並學會如何烹飪它。 然而,CS形式的替代方案的存在總是迫使我們用懷疑來折磨自己:我們做了正確的選擇嗎? 而且,結果 對照由 DataStax 進行的測試顯示,CS 以幾乎壓倒性的分數輕鬆擊敗 HB。 另一方面,DataStax 是一個利益相關方,您不應該相信他們的話。 我們也對測試條件的資訊量太少感到困惑,所以我們決定自己找出誰是BigData NoSql的王者,得到的結果非常有趣。

然而,在繼續討論所執行的測試結果之前,有必要先描述環境配置的重要面向。 事實上,CS 可以在允許資料遺失的模式下使用。 那些。 這是當只有一台伺服器(節點)負責某個key的資料時,如果因為某些原因發生故障,那麼這個key的值就會遺失。 對許多任務來說,這並不重要,但對銀行業來說,這是例外而不是規則。 在我們的例子中,擁有多個資料副本以進行可靠儲存非常重要。

因此,只考慮三重複製模式下的CS工作模式,即案例空間的建立是使用以下參數進行的:

CREATE KEYSPACE ks WITH REPLICATION = {'class' : 'NetworkTopologyStrategy', 'datacenter1' : 3};

接下來,有兩種方法可以確保所需的一致性等級。 一般規則:
西北 + NR > 射頻

這表示寫入時節點的確認數(NW)加上讀取時節點的確認數(NR)必須大於複製因子。 在我們的例子中,RF = 3,這意味著以下選項是合適的:
2 + 2 > 3
3 + 1 > 3

由於盡可能可靠地儲存資料對我們來說至關重要,因此選擇了 3+1 方案。 此外,HB的工作原理類似,即這樣的比較會比較公平。

應該指出的是,DataStax 在他們的研究中做了相反的事情,他們為 CS 和 HB 設定 RF = 1(對於後者,透過更改 HDFS 設定)。 這是一個非常重要的方面,因為在這種情況下對 CS 性能的影響是巨大的。 例如,下圖顯示了將資料載入到CS中所需時間的增加:

兩個矢古綱之戰,或 Cassandra 與 HBase 之戰。 俄羅斯聯邦儲蓄銀行團隊經驗

在這裡我們看到以下情況:競爭執行緒寫入資料的次數越多,花費的時間就越長。 這是很自然的,但重要的是 RF=3 的性能下降明顯更高。 換句話說,如果我們將 4 個執行緒分別寫入 5 個表(總共 20 個),則 RF=3 會損失約 2 倍(RF=150 為 3 秒,RF=75 為 1 秒)。 但如果我們透過將資料載入到 8 個表中,每個表有 5 個執行緒(總共 40 個)來增加負載,那麼 RF=3 的損失已經是 2,7 倍(375 秒 vs 138 秒)。

也許這就是 DataStax 為 CS 成功執行負載測試的秘密,因為對於我們展位上的 HB,將複製因子從 2 更改為 3 沒有任何效果。 那些。 磁碟不是我們配置的 HB 瓶頸。 然而,這裡還有很多其他的陷阱,因為應該注意的是,我們的 HB 版本略有修補和調整,環境完全不同等等。 還值得注意的是,也許我只是不知道如何正確準備 CS,並且有一些更有效的方法來使用它,我希望我們能在評論中找到答案。 但首先要說的是。

所有測試均在由 4 台伺服器組成的硬體叢集上進行,每台伺服器的配置如下:

CPU:Xeon E5-2680 v4 @ 2.40GHz 64 執行緒。
磁碟:12塊SATA HDD
java版本:1.8.0_111

CS版本:3.11.5

cassandra.yml 參數代幣數量:256
hinted_handoff_enabled:真
hinted_handoff_throttle_in_kb:1024
最大提示交付線程數:2
提示目錄:/data10/cassandra/hints
hins_flush_period_in_ms:10000
最大提示檔案大小in_mb:128
batchlog_replay_throttle_in_kb:1024
驗證器:AllowAllAuthenticator
授權者:AllowAllAuthorizer
role_manager:CassandraRoleManager
角色有效性(毫秒):2000
權限有效性以毫秒為單位:2000
憑證有效性(毫秒):2000
分區器:org.apache.cassandra.dht.Murmur3Partitioner
資料檔目錄:
- /data1/cassandra/data # 每個dataN目錄都是單獨的磁碟
- /data2/cassandra/數據
- /data3/cassandra/數據
- /data4/cassandra/數據
- /data5/cassandra/數據
- /data6/cassandra/數據
- /data7/cassandra/數據
- /data8/cassandra/數據
commitlog_目錄:/data9/cassandra/commitlog
cdc_啟用:假
disk_failure_policy:停止
commit_failure_policy:停止
prepared_statements_cache_size_mb:
thrift_prepared_statements_cache_size_mb:
key_cache_size_in_mb:
key_cache_save_period:14400
row_cache_size_in_mb:0
行緩存保存週期:0
counter_cache_size_in_mb:
counter_cache_save_period: 7200
已儲存的快取目錄:/data10/cassandra/saved_caches
commitlog_sync:定期
commitlog_sync_period_in_ms:10000
commitlog_segment_size_in_mb:32
種子提供者:
- 類別名稱:org.apache.cassandra.locator.SimpleSeedProvider
參數:
— 種子:“*,*”
並發讀取數: 256 # 嘗試了 64 - 沒有註意到差異
並發寫入: 256 # 嘗試了 64 - 沒有註意到差異
concurrent_counter_writes: 256 # 嘗試了 64 - 沒有註意到差異
並發_物化_視圖_寫入:32
memtable_heap_space_in_mb: 2048 # 試了 16 GB - 速度較慢
memtable_allocation_type:heap_buffers
index_summary_capacity_in_mb:
index_summary_resize_interval_in_分鐘:60
滴流同步:假
trickle_fsync_interval_in_kb:10240
儲存連接埠:7000
ssl_儲存_連接埠:7001
監聽地址:*
廣播位址:*
監聽廣播位址:true
internode_authenticator:org.apache.cassandra.auth.AllowAllInternodeAuthenticator
start_native_transport: true
本機傳輸連接埠:9042
啟動rpc:true
rpc_位址:*
rpc_埠:9160
rpc_keepalive: true
rpc_server_type:同步
thrift_framed_transport_size_in_mb:15
增量備份:假
snapshot_before_compaction: false
自動快照:真
列索引大小(in_kb):64
列_索引_快取_大小_in_kb:2
並發壓縮器:4
壓縮吞吐量_mb_每秒:1600
sstable_preemptive_open_interval_in_mb:50
讀取請求逾時時間:100000
range_request_timeout_in_ms:200000
write_request_timeout_in_ms: 40000
counter_write_request_timeout_in_ms:100000
cas_contention_timeout_in_ms:20000
截斷請求逾時時間:60000
request_timeout_in_ms: 200000
Slow_query_log_timeout_in_ms: 500
cross_node_timeout: false
endpoint_snitch:GossipingPropertyFileSnitch
動態告密更新間隔毫秒:100
動態告密重置間隔毫秒:600000
動態告密壞度閾值:0.1
request_scheduler:org.apache.cassandra.scheduler.NoScheduler
伺服器加密選項:
節點間加密:無
客戶端加密選項:
啟用:假
節點間壓縮:dc
inter_dc_tcp_nodelay:假
追蹤類型_查詢_ttl:86400
追蹤類型_修復_ttl:604800
啟用使用者定義函數: false
啟用腳本使用者定義函數: false
windows_timer_interval: 1
透明資料加密選項:
啟用:假
tombstone_warn_threshold: 1000
tombstone_failure_threshold:100000
batch_size_warn_threshold_in_kb:200
批次大小失敗閾值in_kb:250
unlogged_batch_across_partitions_warn_threshold:10
Compaction_large_partition_warning_threshold_mb:100
gc_warn_threshold_in_ms:1000
啟用背壓:假
啟用物化視圖:true
啟用_sasi_indexes:true

氣相層析設定:

### CMS 設定-XX:+UseParNewGC
-XX:+使用ConcMarkSweepGC
-XX:+CMSParallelRemarkEnabled
-XX:倖存者比率=8
-XX:最大TenuringThreshold=1
-XX:CMSInitiatingOccupancyFraction=75
-XX:+使用CMSInitiatingOccupancyOnly
-XX:CMSWaitDuration=10000
-XX:+CMSParallelInitialMarkEnabled
-XX:+CMSEdenChunksRecordAlways
-XX:+CMSClassUnloading啟用

jvm.options 記憶體分配為 16Gb(我們也嘗試了 32Gb,沒有註意到差異)。

這些表是使用以下命令建立的:

CREATE TABLE ks.t1 (id bigint PRIMARY KEY, title text) WITH compression = {'sstable_compression': 'LZ4Compressor', 'chunk_length_kb': 64};

HB版本:1.2.0-cdh5.14.2(在org.apache.hadoop.hbase.regionserver.HRegion類別中我們排除了MetricsRegion,當RegionServer上的region數量超過1000時會導致GC)

非預設 HBase 參數動物園管理員.會話.超時:120000
hbase.rpc.timeout: 2 分鐘
hbase.client.scanner.timeout.period: 2 分鐘
hbase.master.handler.count:10
hbase.regionserver.lease.period,hbase.client.scanner.timeout.period:2 分鐘
hbase.regionserver.handler.count:160
hbase.regionserver.metahandler.count:30
hbase.regionserver.logroll.period: 4 小時
hbase.regionserver.maxlogs:200
hbase.hregion.memstore.flush.size:1 GiB
hbase.hregion.memstore.block.multiplier: 6
hbase.hstore.compactionThreshold:5
hbase.hstore.blockingStoreFiles:200
hbase.hregion.majorcompaction:1 天
hbase-site.xml 的 HBase 服務進階設定片段(安全閥):
hbase.regionserver.wal.codecorg.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec
hbase.master.namespace.init.timeout3600000
hbase.regionserver.可選cacheflushinterval18000000
hbase.regionserver.thread.compaction.large12
hbase.regionserver.wal.enablecompressiontrue
hbase.hstore.compaction.max.size1073741824
hbase.server.compactchecker.interval.multiplier200
HBase RegionServer 的 Java 設定選項:
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:ReservedCodeCacheSize=256m
hbase.snapshot.master.timeoutMillis:2 分鐘
hbase.snapshot.region.timeout:2 分鐘
hbase.snapshot.master.timeout.millis:2分鐘
HBase REST 伺服器最大日誌大小:100 MiB
HBase REST 伺服器最大日誌檔備份:5
HBase Thrift 伺服器最大日誌大小:100 MiB
HBase Thrift 伺服器最大記錄檔備份:5
主最大日誌大小:100 MiB
主最大日誌檔案備份:5
RegionServer 最大日誌大小:100 MiB
RegionServer 最大日誌檔案備份:5
HBase Active Master 偵測視窗:4 分鐘
dfs.client.hedged.read.threadpool.size:40
dfs.client.hedged.read.threshold.millis:10毫秒
hbase.rest.threads.min:8
hbase.rest.threads.max:150
最大進程檔案描述符:180000
hbase.thrift.minWorkerThreads:200
hbase.master.executor.openregion.線程:30
hbase.master.executor.closeregion.線程:30
hbase.master.executor.serverops.線程:60
hbase.regionserver.thread.compaction.small:6
hbase.ipc.server.read.threadpool.size: 20
區域移動器線程:6
客戶端 Java 堆大小(以位元組為單位):1 GiB
HBase REST 伺服器預設群組:3 GiB
HBase Thrift 伺服器預設群組:3 GiB
HBase Master 的 Java 堆大小(以位元組為單位):16 GiB
HBase RegionServer 的 Java 堆大小(以位元組為單位):32 GiB

+動物園管理員
最大客戶端Cnxns:601
最大會話超時:120000
建立表:
hbase org.apache.hadoop.hbase.util.RegionSplitter ns:t1 UniformSplit -c 64 -f cf
更改 'ns:t1', {NAME => 'cf', DATA_BLOCK_ENCODING => 'FAST_DIFF', COMPRESSION => 'GZ'}

這裡有一個重要的一點 - DataStax 描述沒有說明使用了多少個區域來建立 HB 表,儘管這對於大容量至關重要。 因此,對於測試,選擇數量 = 64,這允許儲存最多 640 GB,即中等尺寸的桌子。

測試時,HBase 有 22 個表和 67 個區域(如果沒有上面提到的補丁,這對於 1.2.0 版本來說是致命的)。

現在來看程式碼。 由於尚不清楚哪種配置對特定資料庫更有利,因此以各種組合進行了測試。 那些。 在某些測試中,同時載入4個表(所有4個節點都用於連接)。 在其他測試中,我們使用了 8 個不同的表。 在某些情況下,批次大小為 100,在其他情況下為 200(批次參數 - 請參閱下面的程式碼)。 value 的資料大小為 10 位元組或 100 位元組 (dataSize)。 每次向每個表寫入和讀取總共 5 萬筆記錄。 同時,5 個執行緒寫入/讀取每個表(執行緒號 - thNum),每個執行緒使用自己的鍵範圍(計數 = 1 萬):

if (opType.equals("insert")) {
    for (Long key = count * thNum; key < count * (thNum + 1); key += 0) {
        StringBuilder sb = new StringBuilder("BEGIN BATCH ");
        for (int i = 0; i < batch; i++) {
            String value = RandomStringUtils.random(dataSize, true, true);
            sb.append("INSERT INTO ")
                    .append(tableName)
                    .append("(id, title) ")
                    .append("VALUES (")
                    .append(key)
                    .append(", '")
                    .append(value)
                    .append("');");
            key++;
        }
        sb.append("APPLY BATCH;");
        final String query = sb.toString();
        session.execute(query);
    }
} else {
    for (Long key = count * thNum; key < count * (thNum + 1); key += 0) {
        StringBuilder sb = new StringBuilder("SELECT * FROM ").append(tableName).append(" WHERE id IN (");
        for (int i = 0; i < batch; i++) {
            sb = sb.append(key);
            if (i+1 < batch)
                sb.append(",");
            key++;
        }
        sb = sb.append(");");
        final String query = sb.toString();
        ResultSet rs = session.execute(query);
    }
}

因此,為 HB 提供了類似的功能:

Configuration conf = getConf();
HTable table = new HTable(conf, keyspace + ":" + tableName);
table.setAutoFlush(false, false);
List<Get> lGet = new ArrayList<>();
List<Put> lPut = new ArrayList<>();
byte[] cf = Bytes.toBytes("cf");
byte[] qf = Bytes.toBytes("value");
if (opType.equals("insert")) {
    for (Long key = count * thNum; key < count * (thNum + 1); key += 0) {
        lPut.clear();
        for (int i = 0; i < batch; i++) {
            Put p = new Put(makeHbaseRowKey(key));
            String value = RandomStringUtils.random(dataSize, true, true);
            p.addColumn(cf, qf, value.getBytes());
            lPut.add(p);
            key++;
        }
        table.put(lPut);
        table.flushCommits();
    }
} else {
    for (Long key = count * thNum; key < count * (thNum + 1); key += 0) {
        lGet.clear();
        for (int i = 0; i < batch; i++) {
            Get g = new Get(makeHbaseRowKey(key));
            lGet.add(g);
            key++;
        }
        Result[] rs = table.get(lGet);
    }
}

由於在 HB 中客戶端必須處理資料的均勻分佈,因此關鍵的加鹽函數如下所示:

public static byte[] makeHbaseRowKey(long key) {
    byte[] nonSaltedRowKey = Bytes.toBytes(key);
    CRC32 crc32 = new CRC32();
    crc32.update(nonSaltedRowKey);
    long crc32Value = crc32.getValue();
    byte[] salt = Arrays.copyOfRange(Bytes.toBytes(crc32Value), 5, 7);
    return ArrayUtils.addAll(salt, nonSaltedRowKey);
}

現在是最有趣的部分 - 結果:

兩個矢古綱之戰,或 Cassandra 與 HBase 之戰。 俄羅斯聯邦儲蓄銀行團隊經驗

同樣的事情以圖表形式表示:

兩個矢古綱之戰,或 Cassandra 與 HBase 之戰。 俄羅斯聯邦儲蓄銀行團隊經驗

HB的優勢如此驚人,以至於有人懷疑CS設定中存在某種瓶頸。 然而,Google搜尋並蒐索最明顯的參數(如並發寫入或memtable_heap_space_in_mb)並沒有加快速度。 同時,日誌很乾淨,不會罵任何東西。

資料均勻分佈在節點上,所有節點的統計資料大致相同。

這是來自其中一個節點的表統計資訊的樣子鍵空間:ks
閱讀次數:9383707
讀取延遲:0.04287025042448576 毫秒
寫入次數:15462012
寫入延遲:0.1350068438699957 毫秒
待刷新:0
表:t1
SS表數:16
使用空間(即時):148.59 MiB
已用空間(總計):148.59 MiB
快照使用的空間(總計):0 位元組
使用的堆外記憶體(總計):5.17 MiB
SSTable壓縮比:0.5720989576459437
分區數量(估計):3970323
記憶體表單元數:0
Memtable資料大小:0位元組
使用的 Memtable 堆外記憶體:0 位元組
記憶體表開關數量:5
本地讀取計數:2346045
本地讀取延遲:NaN ms
本地寫入次數:3865503
本地寫入延遲:NaN ms
待刷新:0
修復百分比:0.0
布隆過濾器誤報:25
布隆過濾器錯誤率:0.00000
使用的布隆過濾器空間:4.57 MiB
使用的布隆過濾器堆外記憶體:4.57 MiB
使用的堆外記憶體索引摘要:590.02 KiB
壓縮元資料使用的堆外記憶體:19.45 KiB
壓縮分區最小位元組:36
壓縮分割區最大位元組數:42
壓縮分區平均位元組數:42
每片平均活細胞(最後五分鐘):NaN
每片最大活細胞數(最後五分鐘):0
每片平均墓碑數(最後五分鐘):NaN
每片最大墓碑數(最後五分鐘):0
丟棄的突變:0 位元組

嘗試減少批次的大小(甚至單獨發送)沒有效果,只會變得更糟。 事實上,這可能確實是 CS 的最大性能,因為 CS 獲得的結果與 DataStax 獲得的結果類似 - 每秒大約數十萬次操作。 此外,如果我們查看資源利用率,我們會發現 CS 使用更多的 CPU 和磁碟:

兩個矢古綱之戰,或 Cassandra 與 HBase 之戰。 俄羅斯聯邦儲蓄銀行團隊經驗
此圖顯示了兩個資料庫連續運行所有測試期間的使用率。

關於HB強大的閱讀優勢。 在這裡您可以看到,對於這兩個資料庫,讀取期間的磁碟利用率都非常低(讀取測試是每個資料庫測試週期的最後部分,例如對於CS,這是從15:20 到15: 40)。 就 HB 而言,原因很明確 - 大部分資料掛在記憶體、memstore 中,還有一些快取在 blockcache 中。 至於CS,不太清楚它是如何運作的,但是磁碟回收也是不可見的,但為了以防萬一,嘗試啟用快取 row_cache_size_in_mb = 2048 並設定 caching = {'keys': 'ALL', 'rows_per_partition': '2000000'},但這讓情況變得更糟。

也值得再次提及的是關於 HB 區域數量的重要一點。 在我們的例子中,該值被指定為64。如果將其減小並使其等於例如4,那麼讀取時,速度會下降2倍。 原因是memstore會更快填滿,文件會更頻繁地刷新,並且在讀取時需要處理更多文件,這對於HB來說是一個相當複雜的操作。 在實際情況下,可以透過考慮預分割和壓縮策略來解決這個問題;特別是,我們使用一個自行編寫的實用程式來收集垃圾並在背景中不斷壓縮 HFile。 對於 DataStax 測試,他們很可能只為每個表分配 1 個區域(這是不正確的),這在一定程度上澄清了為什麼 HB 在讀取測試中如此差勁。

由此得出以下初步結論。 假設在測試過程中沒有犯下重大錯誤,那麼 Cassandra 看起來就像是個泥足巨人。 更準確地說,當她用單腿保持平衡時,如文章開頭的圖片所示,她表現出了相對較好的成績,但在相同條件下的戰鬥中,她徹底失敗了。 同時,考慮到我們硬體上的CPU利用率較低,我們學會了在每台主機上放置兩個RegionServer HB,從而使效能提高一倍。 那些。 考慮到資源的利用,CS的情況就更加慘淡了。

當然,這些測試是相當綜合的,而且這裡使用的數據量相對較小。 如果我們切換到 TB,情況可能會有所不同,但是對於 HB,我們可以加載 TB,但對於 CS,這卻是有問題的。 即使對於這些卷,它也經常拋出OperationTimedOutException,儘管等待回應的參數與預設參數相比已經增加了幾倍。

我希望透過共同的努力,我們能夠找到CS的瓶頸,如果我們能夠加快速度,那麼在文章的最後我一定會補充關於最終結果的資訊。

UPD:多虧了同志們的建議,我的閱讀速度得以加快。 曾是:
159 個操作(644 個表、4 個流、批次 5)。
添加者:
.withLoadBalancingPolicy(new TokenAwarePolicy(DCAwareRoundRobinPolicy.builder().build()))
我玩弄了線程的數量。 結果如下:
4 個表,100 個線程,批次 = 1(逐一):301 次操作
4 個表,100 個線程,批次 = 10:447 次操作
4 個表,100 個線程,批次 = 100:625 次操作

稍後我將應用其他調整技巧,運行完整的測試週期並將結果添加到帖子末尾。

來源: www.habr.com

添加評論