MongoDB時(shí)間戳轉(zhuǎn)日期及日期分組實(shí)例代碼
前言
最近遇到的一個(gè)數(shù)據(jù)統(tǒng)計(jì)折線圖的性能優(yōu)化點(diǎn),可以說是一定思維上的轉(zhuǎn)變,就記錄下咯
背景:cron定時(shí)任務(wù)讀取當(dāng)前統(tǒng)計(jì)數(shù)據(jù)的異常值,頻率為每五分鐘記錄一次,折線圖要求獲取每日的異常項(xiàng)峰值
最一開始的想法:將數(shù)據(jù)讀取到內(nèi)存中進(jìn)行條件過濾、計(jì)算
首先根據(jù)時(shí)間戳將數(shù)據(jù)以日期作為分組,其次在每個(gè)分組中獲取異常項(xiàng)的峰值數(shù)據(jù),時(shí)間復(fù)雜度O(n*n),最好以日期分組列表+峰值數(shù)據(jù)列表作為對象返回結(jié)果
遇到性能問題:一天的數(shù)據(jù)量為(60/5)*24=288,默認(rèn)日期為15天,則統(tǒng)計(jì)的數(shù)據(jù)量為4230,接口返回甚至需要8、9秒的時(shí)間,作為一個(gè)項(xiàng)目的門面折線圖,這種情況 達(dá)咩!
優(yōu)化的念頭:我要拿每天的峰值數(shù)據(jù),怎么才能直接取到每天的峰值呢,mongo的聚合是不是可以做到??? $group可以按日期做分組, $max可以拿到最大值,接下來一個(gè) $sort好像是就成了吧! 說干就干!!
接下來的聚合語句均為mongo pipeline,最后附上golang的bson條件哈
// ResultCountModel _
type ResultCountModel struct {
CommonBase `json:",inline" yaml:",inline" bson:",inline"`
ErrorCount int `json:"error_count" bson:"error_count"`
Timestamp int64 `json:"timestamp" bson:"timestamp"`
MaxTime int64 `json:"max_time" bson:"max_time"`
}數(shù)據(jù)結(jié)構(gòu)定義如上,這里使用CommonBase,是因?yàn)樵?group聚合后會得到_id唯一標(biāo)識字段,因此便于獲取最后的聚合結(jié)果,在定義結(jié)構(gòu)體時(shí)將其加上;timestamp單位為毫秒
1、日期篩選
第一步,毫無疑問,對時(shí)間戳timestamp進(jìn)行日期的過濾
{
$match: {
timestamp: {
$gte: 1671897600000, // min_timestamp
$lt: 1673280000000 // max_timestamp
}
}
}$gte大于等于$lt小于
2、日期轉(zhuǎn)換
第二步,根據(jù)時(shí)間戳大小進(jìn)行日期的轉(zhuǎn)換,這里是用的是$project, 將具有請求字段的文檔傳遞到管道中的下一階段。指定的字段可以是輸入文檔中的現(xiàn)有字段或是新計(jì)算的字段
使用$project主要思路是,將timestamp時(shí)間戳轉(zhuǎn)換為標(biāo)準(zhǔn)日期,之后輸出為想要的format形式;同時(shí)使用 $project保留需要的字段
時(shí)間戳轉(zhuǎn)換日期
核心方法:$dateToString
{
$dateToString: {
date: <dateExpression>,
format: <formatString>,
timezone: <tzExpression>,
onNull: <expression>
}
}date:要轉(zhuǎn)換的字符串日期,必須是解析為Date、Timestamp、ObjectID 的有效表達(dá)式format: 日期格式規(guī)范timezone:運(yùn)算結(jié)果的時(shí)區(qū),常用UTC偏移量onNull: date為null或缺失時(shí)要返回的值
日期格式想要“月份-日期”,那format: “%m-%d”
日期數(shù)據(jù)這里,如果直接使用輸入文檔中的現(xiàn)有字段的話 date: “$timestamp”,則會報(bào)錯(cuò):PlanExecutor error during aggregation :: caused by :: can’t convert from BSON type long to Date
因此我們需要將時(shí)間戳轉(zhuǎn)換為日期: 即格林威治開始時(shí)間(1970-01-01 00:00:00)+時(shí)間戳+時(shí)差
date:{
$add:[
new Date(0),
"$timestamp",
28800000
]
},注意??:
- MongoDB時(shí)間的基本單位為毫秒,所以本文直接使用”$timestamp”即可;若時(shí)間單位為秒級時(shí),則需要使用 $multiply進(jìn)行乘法運(yùn)算:{ $multiply:[" $timestamp”,1000]}
- MongoDB是UTC時(shí)區(qū),即中時(shí)區(qū)(0度經(jīng)線), 中國為東八區(qū),因此需要使用timezone添加8小時(shí)(即28800000毫秒)
pipeline如下:
day:{
$dateToString:{
format:"%m-%d",
date:{
$add:[
new Date(0),
"$timestamp",
28800000
]
},
}
},保留需要字段
/**
* specifications: The fields to
* include or exclude.
*/
{
timestamp:1,
error_count:1,
}$project將保留字段置為1即可進(jìn)行數(shù)據(jù)保留操作
第二步完整pipeline如下:
{
$project: {
day: {
$dateToString: {
format: '%m-%d',
date: {
$add: [
ISODate('1970-01-01T00:00:00.000Z'),
'$timestamp',
28800000
]
}
}
},
timestamp: 1,
error_count: 1
}
},3、日期分組
第三步,使用$group進(jìn)行日期分組
{
$group:
{
_id: <expression>, // Group key
<field1>: { <accumulator1> : <expression1> },
...
}
}_id: 表達(dá)式指定組密鑰field: 計(jì)算使用的累加器運(yùn)算符
這里我們需要將第二步獲得的日期轉(zhuǎn)換進(jìn)行分組聚合,同時(shí)獲取每個(gè)分組的異常項(xiàng)最大值即峰值數(shù)據(jù)
{
$group: {
_id: '$day',
error_count: {
$max: '$error_count'
},
max_time: {
$max: '$timestamp'
}
}
}, 這里額外獲取了max_time字段,主要用于在計(jì)算統(tǒng)計(jì)數(shù)據(jù)時(shí)的排序,在最后排序部分會使用到
4、日期排序
這里做一個(gè)假設(shè),如果不使用max_time的話,如何將數(shù)據(jù)進(jìn)行按日期的排序呢? 如果根據(jù)_id進(jìn)行排序,則會出現(xiàn)“上年末”排序在“下年初”的情況(感謝現(xiàn)在的??,不然會忘記這個(gè)問題)
所以將每個(gè)分組的最大時(shí)間戳保留下來時(shí)很有必要的!
這里取$max $min都是可以的哈
{
$sort: {
max_time: 1
}
}最終完整pipeline:
[{
$match: {
timestamp: {
$gte: 1671897600000,
$lt: 1673280000000
}
}
}, {
$project: {
day: {
$dateToString: {
format: '%m-%d',
date: {
$add: [
ISODate('1970-01-01T00:00:00.000Z'),
'$timestamp',
28800000
]
}
}
},
timestamp: 1,
error_count: 1
}
}, {
$group: {
_id: '$day',
error_count: {
$max: '$error_count'
},
max_time: {
$max: '$timestamp'
}
}
}, {
$sort: {
max_time: 1
}
}]
[{
$match: {
timestamp: {
$gte: 1671897600000,
$lt: 1673280000000
}
}
}, {
$project: {
day: {
$dateToString: {
format: '%m-%d',
date: {
$add: [
ISODate('1970-01-01T00:00:00.000Z'),
'$timestamp',
28800000
]
}
}
},
timestamp: 1,
error_count: 1
}
}, {
$group: {
_id: '$day',
error_count: {
$max: '$error_count'
},
max_time: {
$max: '$timestamp'
}
}
}, {
$sort: {
max_time: 1
}
}]在golang里面,Aggregate則直接使用pipeline即可,亦可轉(zhuǎn)換為filter使用
filter代碼:
filter := bson.A{
bson.D{{"$match", bson.D{
{"timestamp", bson.D{
{"$gte", param.MinTimestamp},
{"$lt", param.MaxTimestamp},
}}}},
},
bson.D{{"$project", bson.D{
{"day", bson.D{
{"$dateToString", bson.D{
{"format", "%m-%d"},
{"date", bson.D{
{"$add", bson.A{
time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
"$timestamp",
28800000,
}},
}},
}},
}},
{"error_count", 1},
{"timestamp", 1},
}}},
bson.D{{"$group", bson.D{
{"_id", "$day"},
{"max_time", bson.D{{"$max", "$timestamp"}}},
{"error_count", bson.D{{"$max", "$error_count"}}},
}}},
bson.D{{"$sort", bson.D{{"max_time", 1}}}},
}完結(jié)撒花??
補(bǔ)充:解決MongoDB存儲時(shí)間時(shí)差的問題
MongoDB存儲時(shí)間類型數(shù)據(jù)時(shí),都是先轉(zhuǎn)換為UTC時(shí)間,然后存儲到數(shù)據(jù)庫中,當(dāng)我們?nèi)〕龃鎯Φ臅r(shí)間時(shí),就會出現(xiàn)時(shí)差的問題。比如我們用的北京時(shí)間,讀取到的數(shù)值就會看到比當(dāng)前時(shí)間少了8個(gè)小時(shí),難道說我們在每次讀取的時(shí)候都要單獨(dú)處理一下時(shí)間嗎,這就比較麻煩。其實(shí),我們可以在存儲的時(shí)候進(jìn)行相應(yīng)的處理,只需使用getTimezoneOffset()和toISOString()函數(shù)。
getTimezoneOffset函數(shù):返回此地區(qū)的時(shí)差(當(dāng)?shù)貢r(shí)間與GMT格林威治標(biāo)準(zhǔn)時(shí)間的地區(qū)時(shí)差),單位為分鐘。
<script> // 我們是東八區(qū) var d = new Date(); var tz = d.getTimezoneOffset(); console.log(tz); // -480 </script>
toISOString()函數(shù):使用ISO標(biāo)準(zhǔn)將 Date 對象轉(zhuǎn)換為字符串。
該標(biāo)準(zhǔn)稱為 ISO-8601 ,格式為: YYYY-MM-DDTHH:mm:ss.sssZ。
封裝時(shí)間轉(zhuǎn)換函數(shù)
localDate(v) {
const d = new Date(v || Date.now());
d.setMinutes(d.getMinutes() - d.getTimezoneOffset());
return d.toISOString();
},我們在存儲時(shí)間的時(shí)候調(diào)用localDate()這個(gè)函數(shù)就可以了,無論你處在哪個(gè)時(shí)區(qū)結(jié)果顯示都和當(dāng)?shù)貢r(shí)間一樣。
總結(jié)
到此這篇關(guān)于MongoDB時(shí)間戳轉(zhuǎn)日期及日期分組的文章就介紹到這了,更多相關(guān)MongoDB時(shí)間戳轉(zhuǎn)日期內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MongoDB固定集合(capped collection)的知識小結(jié)
固定集合指的是事先創(chuàng)建,并且大小固定的集合。下面這篇文章主要給大家總結(jié)介紹了MongoDB固定集合(capped collection)的知識,文中介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起學(xué)習(xí)學(xué)習(xí)吧2018-10-10
關(guān)于NoSQL之MongoDB的一些總結(jié)
這篇文章主要介紹了關(guān)于NoSQL之MongoDB的一些總結(jié)的相關(guān)資料,需要的朋友可以參考下2015-07-07
Mac下安裝配置mongodb并創(chuàng)建用戶的方法
最近在在學(xué)習(xí)nodejs,相比mysql,mongodb與nodejs搭配更合適,存儲數(shù)據(jù)格式也比較接近JS對象。下面這篇文章主要給大家介紹了關(guān)于在Mac下安裝配置mongodb并創(chuàng)建用戶的相關(guān)資料,需要的朋友可以參考下2018-05-05
Mongodb數(shù)據(jù)庫的備份與恢復(fù)操作實(shí)例
這篇文章主要介紹了Mongodb數(shù)據(jù)庫的備份與恢復(fù)操作實(shí)例,本文講解使用命令在控制臺執(zhí)行實(shí)現(xiàn)Mongodb的備份與恢復(fù)操作,需要的朋友可以參考下2015-01-01
MongoDB數(shù)據(jù)庫插入、更新和刪除操作詳解
這篇文章主要介紹了MongoDB數(shù)據(jù)庫插入、更新和刪除操作詳解,需要的朋友可以參考下2014-03-03
MongoDB快速入門筆記(一)之windows下安裝MongoDB方法
MongoDB 是一個(gè)基于分布式文件存儲的數(shù)據(jù)庫。由 C++ 語言編寫。本文重點(diǎn)給大家介紹MongoDB快速入門筆記(一)之windows下安裝MongoDB方法,非常不錯(cuò)具有參考借鑒價(jià)值,感興趣的朋友一起看下吧2016-06-06
MongoDB連接數(shù)據(jù)庫并創(chuàng)建數(shù)據(jù)等使用方法
MongoDB?是一個(gè)介于關(guān)系數(shù)據(jù)庫和非關(guān)系數(shù)據(jù)庫之間的產(chǎn)品,是非關(guān)系數(shù)據(jù)庫當(dāng)中功能最豐富,最像關(guān)系數(shù)據(jù)庫的。接下來通過本文給大家介紹MongoDB連接數(shù)據(jù)庫并創(chuàng)建數(shù)據(jù)等使用方法,感興趣的朋友一起看看吧2021-11-11

