Mongodb億級數(shù)據(jù)性能測試和壓測
一,mongodb數(shù)據(jù)性能測試
之前公司將用戶的游戲數(shù)據(jù)存儲在mysql中,就是直接將json數(shù)據(jù)存儲到mysql數(shù)據(jù)庫里面,幾個月不到,數(shù)據(jù)庫里面已經(jīng)有兩億條數(shù)據(jù),而且每行中每個json數(shù)據(jù)量也比較大,導(dǎo)致占用的磁盤容量也比較大,因此為了解決mysql帶來多方面的瓶頸,最終選擇使用mongodb來代替mysql。為了測試mongodbdb的性能以及是否滿足需求,因此做了以下測試,對mongodb在高流量時驗證其增刪改查的效率,以及對其進行壓測
服務(wù)器配置:2核4g輕量級服務(wù)器 磁盤容量 70GB
每條數(shù)據(jù)大概在500個字節(jié),索引有一個id主鍵索引,還有一個parentId和category的聯(lián)合唯一索引,這里兩個字段能保證唯一性,因此用唯一索引效率更優(yōu)
1,mongodb數(shù)據(jù)庫創(chuàng)建和索引設(shè)置
首先在Java代碼中創(chuàng)建一個實體類,用這個類作為json對象插入到文檔中即可。
@Data
public class Archive {
private String id;
//賬號id
private String parentId;
private String category;
private String content;
}
隨后在mongodb中創(chuàng)建一個數(shù)據(jù)庫,然后再該庫下面建立一個名為 archive 的集合,mongodb的集合就是類似于mysql的表,兩者概念是一樣的。由于后期數(shù)據(jù)量可能非常大,因此根據(jù)mongodb官方文檔所說,在數(shù)據(jù)插入前,盡量提前建立索引,為了滿足業(yè)務(wù)需求,這里選擇創(chuàng)建一個聯(lián)合索引,由于我這邊業(yè)務(wù)能保證要加索引的兩個字段的唯一性,因此選擇直接添加唯一索引
db.users.createIndex({parentId: 1,category:1}, {unique: true})
如果navicate操作不方便的話,可以安裝一個 Mongodb Compass 可視化工具,如下圖,很多操作都是可以在這個可視化圖形界面上面直接操作的

2,線程池+批量方式插入數(shù)據(jù)
由于這邊主要是io操作將數(shù)據(jù)插入,不需要計算之類的,因此選擇使用io密集型線程池,接下來自定義一個線程池
@Slf4j
public class ThreadPoolUtil {
public static ThreadPoolExecutor pool = null;
public static synchronized ThreadPoolExecutor getThreadPool() {
if (pool == null) {
//獲取當(dāng)前機器的cpu
int cpuNum = Runtime.getRuntime().availableProcessors();
int maximumPoolSize = cpuNum * 2 ;
pool = new ThreadPoolExecutor(
maximumPoolSize - 2,
maximumPoolSize,
5L, //5s
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), //數(shù)組有界隊列
Executors.defaultThreadFactory(), //默認的線程工廠
new ThreadPoolExecutor.AbortPolicy()); //直接拋異常,默認異常
}
return pool;
}
}
第二步就是定義一個線程任務(wù),到時將任務(wù)丟到線程池里面,其代碼如下,該任務(wù)實現(xiàn)Callable接口,每個線程插入10萬條,每次批量插入100條數(shù)據(jù),大概就是需要1000次
@Data
public class ArchiveTask implements Callable {
private MongoTemplate mongoTemplate;
public ArchiveTask(MongoTemplate mongoTemplate){
this.mongoTemplate = mongoTemplate;
}
@Override
public Object call() throws Exception {
List<Archive> list = new ArrayList<>();
for (int i = 1; i <= 100000; i++) {
Archive archive = new Archive();
archive.setCategory("score");
archive.setId(SnowflakeUtils.nextOrderId());
archive.setParentId(SnowflakeUtils.nextOrderId());
Map<String,String> map = new HashMap<>();
StringBuilder sb = new StringBuilder();
for (int j = 0; j < 15; j++) {
sb.append(UUID.randomUUID());
}
map.put("key" + i, sb.toString());
archive.setContent(JSON.toJSONString(map));
list.add(archive);
if (i%100 == 0){
mongoTemplate.insertAll(list);
list.clear(); //手動gc,100個對象沒被引用會被回收
list = new ArrayList<>();
}
}
return null;
}
}
最后定義一個測試類或者一個接口,我這邊使用接口,部分代碼如下,循環(huán)100次,就是會創(chuàng)建100個線程任務(wù),隨后將這個線程任務(wù)丟到線程池中,100乘以100000就是1千萬條數(shù)據(jù)
@Resource
private MongoTemplate mongoTemplate;
static ThreadPoolExecutor threadPool = ThreadPoolUtil.getThreadPool();
@GetMapping("/add")
public void test(){
for (int i = 0; i < 100; i++) {
ArchiveTask archiveTask = new ArchiveTask(mongoTemplate);
threadPool.submit(archiveTask);
}
log.info("數(shù)據(jù)添加完成");
}
3,一千萬數(shù)據(jù)性能測試
mongodb性能測試,此時archive 集合中已有10134114條數(shù)據(jù),平均每條數(shù)據(jù)大小674字節(jié),1千多萬條,此時的存儲大小為5.5個g,索引的總大小為459m
接下來通過唯一索引查詢一條數(shù)據(jù),這里直接通過parentId查詢一條數(shù)據(jù),此時數(shù)據(jù)還是在不斷插入的
db.archive.find({parentId:"2405291858848274156091867143"})
是的,如下圖所示,1000多萬條數(shù)據(jù)里面查詢,只需要25ms即可將數(shù)據(jù)放回,當(dāng)然這里沒有在高流量的情況下進行壓測。

4,兩千萬數(shù)據(jù)性能測試
此時archive集合來到了兩千萬條,每條數(shù)據(jù)和之前一樣,平均大小是674字節(jié),數(shù)據(jù)總大小來到了10.92G,內(nèi)存大小12.65g,索引總大小是913m

接下來測試查詢效率,依舊使用上面的這個parentId,由于設(shè)置的是parentId+category的聯(lián)合唯一索引,接下來兩個參數(shù)一起查
db.archive.find({parentId:"2405291858848274156091867143",category:"score"})
2000萬的數(shù)據(jù)查詢結(jié)果如下,只需要21ms,和上面的25ms慢了將近4ms,但是這4ms可以忽略

5,五千萬數(shù)據(jù)性能測試
由于70G的磁盤容量已經(jīng)只剩48G,因此在content字段將500字節(jié)的值調(diào)小,調(diào)整到150個字節(jié),以便能插入更多數(shù)據(jù)。將上面的StringBuilder拼接的15個uuid改成1個uuid
map.put("key" + i,UUID.randomUUID().toString());
此時數(shù)據(jù)來到50245694條數(shù)據(jù),每條數(shù)據(jù)平均大小372kb,總存儲大小12.66g,內(nèi)存中的總大小17.45g,索引大小目前只有2.8g

為了保證拿到的parentId是一次沒有查詢過的,手動的插入一批數(shù)據(jù),手動單條插入20條數(shù)據(jù),耗時600ms,在插入數(shù)據(jù)時會改變索引,插入數(shù)據(jù)會稍微慢些。此時的插入操作都是在多線程插入大量數(shù)據(jù)的時候測試的
db.archive.insertOne({parentId:"2024111222337",category:"score1",content:"cbasbsadhpasdbsaodgs"})
db.archive.insertOne({parentId:"2024111222337",category:"score2",content:"cbasbsadhpasdbsaodgs"})
....
此時第一次查詢這條數(shù)據(jù),共耗時153ms,共查出20條數(shù)據(jù)

再第二次查詢之后,花費78ms,內(nèi)部應(yīng)該也是會將查詢結(jié)果加入到緩存中,方便第二次查詢

在上面的插入操作中由于會破壞到索引結(jié)構(gòu),因此耗時久一點。接下來看這個更新操作,
db.archive.updateOne(
{ parentId: "2024111222337",category:"score1" },
{ $set: { content: "cbasbsadhpasdbsaodgsscore" } }
);
其結(jié)果如下,更新了一條數(shù)據(jù),只花費了13毫秒的時間,因此更新操作速度是很快的。由于這里每一條數(shù)據(jù)都是唯一數(shù)據(jù),因此不測試批量更新

最后測試刪除數(shù)據(jù),將這20條數(shù)據(jù)全部刪除,總共花費18毫秒

6,一億條數(shù)據(jù)性能測試
數(shù)據(jù)通過多線程+批量插入的方式來到一億條,存儲大小15.5g,索引長度是6g
db.archive.countDocuments() //查詢共有多少條數(shù)據(jù) 100082694

接下來往里面重新插入一部分數(shù)據(jù),往里面插入20條數(shù)據(jù),大概花費160多ms,插入數(shù)據(jù)會導(dǎo)致索引重構(gòu),所以耗時久一些,批量插入性能會更快。重新插入的數(shù)據(jù)可以保證這條數(shù)據(jù)沒被查過,并且知道parentId是什么
db.archive.insertOne({parentId:"20240531101059",category:"score1",content:"abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy"})
....
接下來測試查詢數(shù)據(jù),只需要19ms
db.archive.find({parentId:"20240531101054"},{parentId:1,category:1}) //只返回部分字段
db.archive.find({parentId:"20240531101058"})

更新數(shù)據(jù)如下,只需要10ms
db.archive.updateOne(
{ parentId: "20240531101059",category:"score1" },
{ $set: { content: "cbasbsadhpasdbsaodgsscore" } }
);

7,壓測
以下壓測都是數(shù)據(jù)達到1億之后進行測試的,并且都是使用的2核4g的服務(wù)器
在1s內(nèi)同時1000個線程插入數(shù)據(jù),每個線程插入20條數(shù)據(jù),中位數(shù)24,吞吐量391

在1s內(nèi)10000個線程插入數(shù)據(jù),也是每個線程批量插入20條數(shù)據(jù),可以發(fā)現(xiàn)就算是2核4g這么垃圾的輕量級服務(wù)器,10000qps也是毫無壓力的

插入數(shù)據(jù)會破壞索引,相對于修改和查詢是更慢的,接下來測試1s內(nèi)10000個線程同時執(zhí)行增改查,吞吐量可以達到2251.7

部分代碼片段如下,讓10000個線程隨機的執(zhí)行增改查的操作,在1s內(nèi)是毫無壓力的

8,總結(jié)
通過上面的數(shù)據(jù)以及mongodb的響應(yīng)來看,mongodb的性能還是非常不錯的??纯碐PT對這種數(shù)據(jù)的評價,gpt也認為mongodb是非常合適的。當(dāng)然不管什么數(shù)據(jù)和業(yè)務(wù),只要其本質(zhì)是 json 數(shù)據(jù),不管json內(nèi)部結(jié)構(gòu)多復(fù)雜,用mongodb都是非常合適的。mongodb還適合存一些訂單數(shù)據(jù),地理數(shù)據(jù),大數(shù)據(jù)等等,其應(yīng)用范圍是非常廣泛的

以上就是Mongodb億級數(shù)據(jù)性能測試和壓測的詳細內(nèi)容,更多關(guān)于Mongodb數(shù)據(jù)性能測試的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Mongodb啟動報錯完美解決方案:about to fork child pr
在使用命令行啟動 MongoDB 的時候報錯:about to fork child process, waiting until server is ready for connections.forked process: 50411,造成這個報錯的原因是 “MongoDB” 服務(wù)沒有正常的關(guān)閉,在終端連接非正常斷開后,再次執(zhí)行 MongoDB 的時候報錯2023-04-04
Mongodb數(shù)據(jù)庫的備份與恢復(fù)操作實例
這篇文章主要介紹了Mongodb數(shù)據(jù)庫的備份與恢復(fù)操作實例,本文講解使用命令在控制臺執(zhí)行實現(xiàn)Mongodb的備份與恢復(fù)操作,需要的朋友可以參考下2015-01-01
mongodb命令行連接及基礎(chǔ)命令總結(jié)大全
大家可能平時在開發(fā)過程中都使用客戶端工具來連接和查詢mongodb,但是一般生產(chǎn)當(dāng)中的數(shù)據(jù)庫是不允許本地客戶端連接的,下面這篇文章主要給大家介紹了關(guān)于mongodb命令行連接及基礎(chǔ)命令總結(jié)的相關(guān)資料,需要的朋友可以參考下2024-04-04
MongoDB聚合$listSampledQueries實例操作
使用$listSampledQueries可以返回指定集合或所有集合的采樣查詢,analyzeShardKey命令使用采樣查詢來計算分片密鑰的讀寫分布指標(biāo),這篇文章主要介紹了MongoDB聚合$listSampledQueries,需要的朋友可以參考下2024-02-02
MongoDB磁盤空間占滿導(dǎo)致數(shù)據(jù)庫被鎖定的解決方法
我在實際項目中,遇到一個問題,隨著數(shù)據(jù)每天的不斷增加,導(dǎo)致mongodb的磁盤空間站滿了,數(shù)據(jù)庫被鎖了,無法使用,所以本文給大家介紹了清理MongoDB數(shù)據(jù)和磁盤空間的方法,需要的朋友可以參考下2024-04-04
MongoDB數(shù)據(jù)庫用戶角色和權(quán)限管理詳解
這篇文章主要給大家介紹了關(guān)于MongoDB數(shù)據(jù)庫用戶角色和權(quán)限管理的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11

