摘要:每個對應(yīng)時間序列的一行所以按照測試數(shù)據(jù)來說,就會插入個文檔到里。同時嵌套存儲還有助于在按條件過濾的情況下砍掉不需要遞歸查詢的子文檔數(shù)量。我們這里關(guān)注的是在同樣配置的情況下,不同表結(jié)構(gòu)對于查詢時間的相對關(guān)系。
數(shù)據(jù)結(jié)構(gòu)介紹
最完整的時間序列的邏輯數(shù)據(jù)模型如下:
[timestamp],[d1],[d2]...[dn],[v1],[v2]...[vn]
d1 ~ dn 是維度,比如 ip, idc, country 之類的值
v1 ~ vn 是值列,比如 cpu_usage, free_memeory_bytes 之類的值
一些時間序列數(shù)據(jù)庫在實現(xiàn)的時候為了簡化實現(xiàn),提高性能約束了一個更簡化的數(shù)據(jù)模型:
[timestamp],[metric],[value]
這種數(shù)據(jù)模型對于數(shù)據(jù)庫來說非常友好,可以很好的做一些優(yōu)化。但是要求開發(fā)者去選擇什么的維度信息編碼到 metric名里,同時對于多值列的數(shù)據(jù)需要存成多個metric,而不是一個metric多個值。本文探討的數(shù)據(jù)模型采用最完整的數(shù)據(jù)模型。
監(jiān)控使用的時間序列數(shù)據(jù)具有以下的特點:
時序性:數(shù)據(jù)一般是按時間升序排列,同時統(tǒng)計之后的數(shù)據(jù)一般會在同一個時間點有很多個記錄對應(yīng)不同的維度組合
大部分維度字段經(jīng)常重復(fù)(low cardinality):比如采集10臺機(jī)器的性能指標(biāo),那么一天的數(shù)據(jù)里10臺機(jī)器的ip地址是反復(fù)出現(xiàn)的。
有可能有部分?jǐn)?shù)據(jù)是偶然的(high cardinality):比如一些時候采集外部輸入的時候有臟數(shù)據(jù)統(tǒng)計進(jìn)來,比如應(yīng)該是ip地址的字段變成了用戶id甚至是一些亂碼,那么可能這個維度組合的數(shù)據(jù)在整天里只出現(xiàn)一次。
使用的時候經(jīng)常批量拉取一天的數(shù)據(jù)作為圖形展示,甚至是今天,昨天或者上周同期的數(shù)據(jù)。
展示的時候需要看總的數(shù)據(jù),也需要能夠按不同維度查看。需要具有一定的查詢時聚合的能力。因為維度可能比較多,所以不能在統(tǒng)計的時候就完成所有的維度聚合的工作。
本測試采用的測試數(shù)據(jù)有838萬行,其大部分維度字段是重復(fù)的,但是有少量的臟數(shù)據(jù)使得維度組合還是比較多的。總的維度組合數(shù)(不包括timestamp維度)為285522。排除掉大部分臟數(shù)據(jù)的維度組合數(shù)為5927。數(shù)據(jù)的周期是60秒,跨度為1428777120 ~ 1428793320。平均每個周期有 30922 行記錄。一個周期有這么行記錄很重要的原因是一個周期,對于同一個維度組合有多條統(tǒng)計數(shù)據(jù)(來源于不同的partition)。也就是數(shù)據(jù)是部分聚合的,并沒有聚合到一個周期,一個維度組合,一條記錄的程度。選擇這樣的測試數(shù)據(jù)是因為這種數(shù)據(jù)是約束最小的形式。它沒有對采集頻率,每周期記錄數(shù),維度組合密度有任何預(yù)先假設(shè)。比如 opentsdb 假設(shè)同周期內(nèi)指定維度組合只有唯一的一個值,插入了兩個不同值會怎么樣?well,它報錯……
測試數(shù)據(jù)的結(jié)構(gòu)為:
[timestamp],[iResult],[vCmid],[vAppid],[totalCount],[dProcesssTime]
其中最后兩列為值列,其余的都是維度。
每個doc對應(yīng)時間序列的一行所以按照測試數(shù)據(jù)來說,就會插入8380000個文檔到mongodb里。
{ "sharded" : false, "primary" : "shard2_RS", "ns" : "wentao_test.sparse", "count" : 8.38534e+06, "size" : 2012533392.0000000000000000, "avgObjSize" : 240, "storageSize" : 2897301504.0000000000000000, "numExtents" : 21, "nindexes" : 1, "lastExtentSize" : 7.56662e+08, "paddingFactor" : 1.0000000000000000, "systemFlags" : 1, "userFlags" : 1, "totalIndexSize" : 2.72065e+08, "indexSizes" : { "_id_" : 2.72065e+08 }, "ok" : 1.0000000000000000, "$gleStats" : { "lastOpTime" : Timestamp(1429290120, 22), "electionId" : ObjectId("54c9f324adaa0bd054140fda") } }
值得關(guān)注的地方是平均的文檔大小是240字節(jié),也就是0.24k,非常非常的小??偟拇鎯臻g占用有2.9G之多。原因3顯然是因為重復(fù)存儲維度字段的值造成的。
每個doc對應(yīng)一個維度組合的一天這種表結(jié)構(gòu)大概如下:
{ "_id" : "1428710400.wxa8fdfb5a9e7f64f6.10000.-502", "iResult" : "-502", "vCmdid" : "10000", "values" : [ {"t":1, "1": x, "2": y}, {"t":1, "1": x, "2": y}, .... {"t":1449, "1": x, "2": y}, ], "date" : 1.42871e+09, "timestamp" : 1.42878e+09, "vAppid" : "appid1" }
其中date是時間戳truncate到了天。然后dProcessTime里的每個key都是對應(yīng)在一天內(nèi)的第N個分鐘。
這種按天打包的結(jié)構(gòu)非常適合一次查詢就要查一天的數(shù)據(jù)的需求。但是它的壓縮效果很大程度上取決于維度組合的cardinality。如果維度里面有一些值cardinality很高,那么壓縮之后仍然會有非常多的文檔數(shù)。這對于需要查詢的時候再去聚合的數(shù)據(jù)就非常不利了。但是對于查詢的維度和存儲的維度一一對應(yīng)的情況,那么拉取一天的數(shù)據(jù)就只要讀取一個文檔,那么就會非??炝?。
存儲的效果并不是很好,因為文檔數(shù)量仍然很多
{ "sharded" : false, "primary" : "shard2_RS", "ns" : "wentao_test.sparse_measurement", "count" : 285653, "size" : 4.69834e+08, "avgObjSize" : 1644, "storageSize" : 7.82356e+08, "numExtents" : 17, "nindexes" : 1, "lastExtentSize" : 2.07794e+08, "paddingFactor" : 1.0000000000000000, "systemFlags" : 1, "userFlags" : 1, "totalIndexSize" : 1.76029e+07, "indexSizes" : { "_id_" : 1.76029e+07 }, "ok" : 1.0000000000000000, "$gleStats" : { "lastOpTime" : Timestamp(1429230049, 5), "electionId" : ObjectId("54c9f324adaa0bd054140fda") } }
總的文檔數(shù)是285653,平均大小只有1.6k,尺寸有470M之多。
轉(zhuǎn)換代碼如下:
pythonfor offset, batch in read_test_data_in_batches(): updates = collections.defaultdict(list) for doc in batch: seconds_in_day = doc["timestamp"] % 86400 date = doc["timestamp"] - seconds_in_day minute_index = seconds_in_day / 60 _id = "%s.%s.%s.%s" % (date, doc["vAppid"], doc["vCmdid"], doc["iResult"]) if len(_id) > 256: continue updates[_id].append({"t":doc["timestamp"],"0":doc["dProcessTime"],"1":doc["totalCount"]}) bulk = measurement_coll.initialize_unordered_bulk_op() new_ids = [] for doc in batch: seconds_in_day = doc["timestamp"] % 86400 date = doc["timestamp"] - seconds_in_day _id = "%s.%s.%s.%s" % (date, doc["vAppid"], doc["vCmdid"], doc["iResult"]) if len(_id) > 256: continue if _id not in known_ids: known_ids.add(_id) new_ids.append(_id) bulk.find({"_id": _id}).upsert().update({"$set": { "vAppid": doc["vAppid"], "vCmdid": doc["vCmdid"], "iResult": doc["iResult"], "date": date, "timestamp": doc["timestamp"] }}) for _id, set_fields in updates.iteritems(): bulk.find({"_id": _id}).update({"$push": {"values": {"$each": set_fields}}}) LOGGER.info(offset) try: res = bulk.execute() except pymongo.errors.BulkWriteError as bwe: import pprint pprint.pprint(bwe.details)
在這篇mongodb的官方博客(http://blog.mongodb.org/post/65517193370/schema-design-for-time-series...)里提到了一種更精簡的存儲表結(jié)構(gòu):
{ timestamp_minute: ISODate(“2013-10-10T23:06:00.000Z”), num_samples: 58, total_samples: 108000000, type: “memory_used”, values: { 0: 999999, … 37: 1000000, 38: 1500000, … 59: 1800000 } }
這種格式對于無維度的時間序列是合適的。但是如果分維度存儲,那么必然就牽涉到一個讀取的時候按不同維度聚合的問題。而這種格式設(shè)計基本上無法在服務(wù)器端聚合的。而把數(shù)據(jù)拉出來在應(yīng)用層作聚合就牽涉到大量IO,更加不可能快了。問題是,如果數(shù)據(jù)庫只是能夠把一天的數(shù)據(jù)存進(jìn)去,然后可以原樣取出來是不是意義不大呢?要求時間序列數(shù)據(jù)庫作一些聚合是非常合理的要求吧。比如opentsdb就支持tag的功能,實際上就是分維度。
每個doc對應(yīng)一段時間內(nèi)的數(shù)據(jù)這種表結(jié)構(gòu)大概如下:
{ "_id" : ObjectId("5531b34c9469047155b3423b"), "count" : 41, "max_timestamp" : 1.42879e+09, "vAppid" : "appid1", "min_timestamp" : 1.42879e+09, "sum_totalCount" : 42, "sum_dProcessTime" : 468, "_" : [ { "c" : 4, "d" : 1.42879e+09, "1" : 69, "0" : 4, "_" : [ { "_" : [ { "d" : "16", "v" : [ { "1" : 41, "0" : 1 } ] }, { "d" : "10000", "v" : [ { "1" : 1, "0" : 1 }, { "1" : 1, "0" : 1 } ] }, { "d" : "18", "v" : [ { "1" : 26, "0" : 1 } ] } ], "d" : "0" } ] }, // .. many more rows ] }
這個表結(jié)構(gòu)第一個利用的是時間序列的連續(xù)性。所以一段時間的數(shù)據(jù)打包存放在了一個mongodb的文檔里。然后在文檔上留下max_timestamp,min_timestamp兩個字段用于快速過濾掉無須讀取的文檔。
第二個利用的特性是某些維度經(jīng)常用于下鉆查詢,比如vAppid。如果appid1和appid2的數(shù)據(jù)放在同一個文檔里,那么在查詢appid1的時候,appid2的數(shù)據(jù)也會被讀取到,從而拖慢了查詢效率。這個clustering_fields的選擇可以是空,也就是一段時間內(nèi)的所有維度的數(shù)據(jù)都打包到了一起,反正查詢的時候也沒有特別突出的維度需要優(yōu)化。
這兩個設(shè)計基本上是模仿 mysql 的 clustering index,讓索引值相同的數(shù)據(jù)彼此靠近的存放在同一個物理位置。因為mongodb沒有clustering index的支持,但是其同一個文檔內(nèi)的數(shù)據(jù)是肯定物理存放在一起的。所以利用這個特性模仿了類似 clustering index的效果。同時因為按時間段打包了,文檔的數(shù)據(jù)會非常的少,使用b tree索引可以很快的定位到所需的文檔(特別是選擇好了常用的下鉆維度的情況)。如果文檔數(shù)量多,b tree索引之后仍然會對應(yīng)大量的文檔id,用id去doc heap里查找對應(yīng)的doc也是非常耗時的。對于 postgresql 之類的數(shù)據(jù)庫,一般可以用按時間partition加上暴力的全partition掃描來避免這種b tree索引反而更慢了的尷尬,但是mongodb并沒有partition的支持,除非我們在應(yīng)用層作一天一個collection的分表操作。
第一個優(yōu)化解決的是按時間,按維度索引的效率問題。接下來要解決的問題是存儲過大的問題。我們前面看到區(qū)區(qū)800萬行就用掉快3個G的磁盤。主要的磁盤是浪費在重復(fù)的維度字段的值的存儲上了。次要的原因是維度名稱本身也要占用存儲空間。我們這里采用的是map嵌套的方式。對于某個維度,同樣的值的記錄會存在一個map的entry下。這樣這個維度的這個值就不用反復(fù)重復(fù)了。為了最大話這種優(yōu)化的效果,維度字段應(yīng)該按照cardinality排序,也就是唯一值數(shù)量少的放在外層,唯一值數(shù)量多的嵌套在最內(nèi)層。上面的 _.d 對應(yīng)的 1.42879e+09 就是第一個維度(timestamp)的值。_._.d 對應(yīng)的0就是第二個維度(iResult)的值。_._._.d 對應(yīng)的10000對應(yīng)的就是第三個維度(vCmdid)的值。嵌套的維度字段排序是 timestmap => iResult => vCmdid。注意到除了d字段,還有c字段代表的是所有內(nèi)嵌的記錄的總count,0代表的是第一個值列的sum,1代表的是第二個值列的sum。注意到維度名并沒有被存儲到文檔里,維度的信息是隱含在嵌套的層次里的。查詢的時候需要根據(jù)額外存儲的元數(shù)據(jù)知道不同的嵌套層次對應(yīng)的是什么維度。同樣值列的名稱也是沒有存儲的,葉子節(jié)點的v就是最后的原始值列。
前面已經(jīng)看到了,文檔內(nèi)還存儲了一些統(tǒng)計信息。比如timestamp下存儲了這個timestamp的count和所有值列的sum。這些統(tǒng)計值有助減少查詢時候的計算量。同時嵌套存儲還有助于在按條件過濾的情況下砍掉不需要遞歸查詢的子文檔數(shù)量。
分vAppid存放的結(jié)果如下:
{ "sharded" : true, "systemFlags" : 1, "userFlags" : 1, "ns" : "wentao_test.sparse_precomputed", "count" : 1278, "numExtents" : 15, "size" : 3.30918e+08, "storageSize" : 4.11038e+08, "totalIndexSize" : 130816, "indexSizes" : { "_id_" : 65408, "vAppid_hashed" : 65408 }, "avgObjSize" : 258934.0594679186178837, "nindexes" : 2, "nchunks" : 6, "ok" : 1.0000000000000000 }
文檔數(shù)只有1278個了,平均的尺寸是258k。總存儲占用是411Mb。如果不按vAppid分,壓縮效果會更好:
json{ "sharded" : false, "primary" : "shard2_RS", "ns" : "wentao_test.sparse_precomputed_no_appid", "count" : 39, "size" : 2.68435e+08, "avgObjSize" : 6.88294e+06, "storageSize" : 2.75997e+08, "numExtents" : 3, "nindexes" : 1, "lastExtentSize" : 1.58548e+08, "paddingFactor" : 1.0000000000000000, "systemFlags" : 1, "userFlags" : 1, "totalIndexSize" : 8176, "indexSizes" : { "_id_" : 8176 }, "ok" : 1.0000000000000000, "$gleStats" : { "lastOpTime" : Timestamp(1429319735, 3), "electionId" : ObjectId("54c9f324adaa0bd054140fda") } }
文檔個數(shù)39個,平均文檔大小6.9M,總存儲占用275M。相比最初的3G磁盤占用,壓縮效果非常明顯。
如果允許丟棄掉原始的值,對于一個維度組合一個周期只保留一個聚合記錄(這個其實是大部分的需求)。那么最后一層維度內(nèi)就不需要內(nèi)嵌v這個數(shù)組了,尺寸可以進(jìn)一步降低。當(dāng)然這種壓縮是有損的,所以并不是公平的比較。因為一天一個文檔的方式一般都會有同樣的限制,所以在這里可以用于和另外一種表結(jié)構(gòu)進(jìn)行對比。
{ "sharded" : false, "primary" : "shard2_RS", "ns" : "wentao_test.sparse_precomputed_no_appid_no_val", "count" : 5, "size" : 5.45259e+07, "avgObjSize" : 1.09052e+07, "storageSize" : 2.01335e+08, "numExtents" : 2, "nindexes" : 1, "lastExtentSize" : 2.01327e+08, "paddingFactor" : 1.0000000000000000, "systemFlags" : 1, "userFlags" : 1, "totalIndexSize" : 8176, "indexSizes" : { "_id_" : 8176 }, "ok" : 1.0000000000000000, "$gleStats" : { "lastOpTime" : Timestamp(1429198926, 2), "electionId" : ObjectId("54c9f324adaa0bd054140fda") } }
壓縮的結(jié)果是文檔只有5個,平均大小是10M,總磁盤占用是55M左右。
轉(zhuǎn)換的代碼如下:
pythonjumbo_docs = {} # doc with same cluster field will be packed in one document so continous on physical layout # timestamp is always the first clustering field, as the nature of time series data is clustering_fields = [] # sorted from low cardinality to high to save sapce dimension_fields = ["timestamp", "vAppid", "iResult", "vCmdid"] # precompute sum/count at those dimension levels precomputed_fields = set(["timestamp"]) value_fields = ["totalCount", "dProcessTime"] store_raw_values = True for offset, batch in read_test_data_in_batches(): print(offset) for record in batch: clustering_key = tuple(record[f] for f in clustering_fields) jumbo_doc = jumbo_docs.get(clustering_key) if not jumbo_doc: jumbo_doc = { "min_timestamp": record["timestamp"], "max_timestamp": record["timestamp"], "_fast_lookup": {} } jumbo_doc["_all_levels"] = [jumbo_doc] for f in clustering_fields: jumbo_doc[f] = record[f] jumbo_docs[clustering_key] = jumbo_doc jumbo_doc["min_timestamp"] = min(jumbo_doc["min_timestamp"], record["timestamp"]) jumbo_doc["max_timestamp"] = max(jumbo_doc["max_timestamp"], record["timestamp"]) jumbo_doc["count"] = jumbo_doc.get("count", 0) + 1 for value_field in value_fields: jumbo_doc["sum_%s" % value_field] = jumbo_doc.get("sum_%s" % value_field, 0) + record[value_field] current_level = jumbo_doc for field in dimension_fields: next_levels = current_level.get("_") if next_levels is None: next_levels = [] current_level["_"] = next_levels dimension = record[field] next_level = current_level["_fast_lookup"].get(dimension) if not next_level: next_level = { "d": dimension, "_fast_lookup": {} } if field in precomputed_fields: for i in range(len(value_fields)): next_level["%s" % i] = 0 jumbo_doc["_all_levels"].append(next_level) next_levels.append(next_level) current_level["_fast_lookup"][dimension] = next_level if field in precomputed_fields: next_level["c"] = next_level.get("c", 0) + 1 for i, value_field in enumerate(value_fields): next_level["%s" % i] += record[value_field] current_level = next_level if store_raw_values: # current_level is the last dimension now current_level["v"] = current_level.get("v", []) current_level["v"].append({str(i): record[f] for i, f in enumerate(value_fields)}) inserted_clustering_keys = [] for clustering_key, jumbo_doc in jumbo_docs.iteritems(): if len(jumbo_doc["_all_levels"]) >= 10000 * 5: for level in jumbo_doc["_all_levels"]: del level["_fast_lookup"] del jumbo_doc["_all_levels"] print("insert jumbo doc") sparse_precomputed_coll.insert(jumbo_doc) inserted_clustering_keys.append(clustering_key) for clustering_key in inserted_clustering_keys: del jumbo_docs[clustering_key] for jumbo_doc in jumbo_docs.values(): for level in jumbo_doc["_all_levels"]: del level["_fast_lookup"] del jumbo_doc["_all_levels"] print("insert jumbo doc") sparse_precomputed_coll.insert(jumbo_doc)第一個查詢是統(tǒng)計出每個周期內(nèi)的count
db.sparse.aggregate([ {$group: {_id: "$timestamp", "count": {$sum: 1}}} ])
得出的結(jié)果是這個格式的,每個周期一個count值。
[ { "_id" : 1.42879e+09, "count" : 2266.0000000000000000 }, ... { "_id" : 1.42878e+09, "count" : 6935.0000000000000000 } ]
結(jié)果數(shù)據(jù)為272行,耗時大概是9.6秒。注意這個絕對值并沒有意義,因為不同的硬件配置,不同的緩存設(shè)置,不同的sharding都會對這個結(jié)果產(chǎn)生影響。我們這里關(guān)注的是在同樣配置的情況下,不同表結(jié)構(gòu)對于查詢時間的相對關(guān)系。
打包存儲的數(shù)據(jù),作同樣的查詢,需要些更復(fù)雜的聚合邏輯:
db.sparse_precomputed_no_appid.aggregate([ {$unwind: "$_"}, // timestamp {$group: {_id: "$_.d", count: {$sum: "$_.c"}}} ])
這個查詢耗時大概是7.1秒??梢钥吹酱虬鎯χ髷?shù)據(jù)量變少了,查詢并沒有變得特別快。上面的查詢還使用了預(yù)先計算的字段。如果統(tǒng)計原始的數(shù)據(jù),查詢更復(fù)雜
db.sparse_precomputed_no_appid.aggregate([ {$unwind: "$_"}, // timestamp {$unwind: "$_._"}, // timestamp.vAppid {$unwind: "$_._._"}, // timestamp.vAppid.iResult {$unwind: "$_._._._"}, // timestamp.vAppid.iResult.vCmdid {$group: {_id: "$_.d", count: {$sum: {$size: "$_._._._.v"}}}} // size of the values array ])
這個查詢時間大概是9.1秒。
db.sparse_precomputed_no_appid.aggregate([ {$unwind: "$_"}, // timestamp {$unwind: "$_._"}, // timestamp.vAppid {$unwind: "$_._._"}, // timestamp.vAppid.iResult {$unwind: "$_._._._"}, // timestamp.vAppid.iResult.vCmdid {$group: {_id: "$_.d", count: {$sum: "$_._._._.c"}}} // size of the values array ])
少做一個$size的操作要稍微快一些。大概是9秒。
結(jié)論是880萬數(shù)據(jù)聚合,用這個格式并沒有變得快很多,基本是一個數(shù)量級的。
db.sparse_measurement.aggregate([ {$unwind: "$values"}, {$group: {_id: "$values.t", count:{$sum:"$values.1"}}} ])
這個查詢要10.2秒,比原始格式還要慢。說明一天一個doc的存放方式并不適合聚合查詢。
第二個查詢是分vCmdid統(tǒng)計出分周期的調(diào)用量(totalCount字段)db.sparse.aggregate([ {$match: {vAppid: {$ne: ""}}}, {$group: {_id: { timestamp: "$timestamp", vCmdid: "$vCmdid" }, "totalCount": {$sum: "$totalCount"}}} ])
結(jié)果數(shù)據(jù)為13115行,這個查詢需要21.4秒.
db.sparse_precomputed_no_appid.aggregate([ {$unwind: "$_"}, // timestamp {$unwind: "$_._"}, // timestamp.vAppid {$match: {"_._.d": {$ne: ""}}}, // vAppid != "" {$unwind: "$_._._"}, // timestamp.vAppid.iResult {$unwind: "$_._._._"}, // timestamp.vAppid.iResult.vCmdid {$unwind: "$_._._._.v"}, // timestamp.vAppid.iResult.vCmdid.values {$group: {_id: "$_.d", count: {$sum: "$_._._._.v.0"}}} // size of the values array ])
這個查詢需要18.4秒。稍微比一個數(shù)據(jù)點一行的原始表結(jié)構(gòu)要快一些。
結(jié)論測試做到這里,基本有一個結(jié)論了:
最原始的表結(jié)構(gòu),除了查詢語法比較直觀以外,全部是缺點。存儲占用最大,要3個G。而且聚合查詢效率也是最低的。
一天一個doc的結(jié)構(gòu),比較容易實現(xiàn)。存儲要470M。但是缺點是幾乎無法做服務(wù)器端的聚合,什么樣的維度存進(jìn)去就必須什么樣的維度取出來。而且當(dāng)維度組合比較多的時候,仍然會產(chǎn)生很多文檔。比較適合的場景是無維度的數(shù)據(jù),也就是傳統(tǒng)的單一metric的時間序列。
一個時間段打包成一個文檔的結(jié)構(gòu),實現(xiàn)比較復(fù)雜。存儲是最省的,只要275M。而且查詢效率還稍微比原始格式要快一些。
相對的效率搞清楚了,那么絕對的效率是否滿足要求呢?這個就要看場景了,對于800萬行聚合需要花9秒的效率,目測是有優(yōu)化空間的,也肯定在數(shù)據(jù)庫里談不上快的。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/18738.html
摘要:單線程請求,所有命令串行執(zhí)行,并發(fā)情況下不需要考慮數(shù)據(jù)一致性問題。只能使用單線程,性能受限于性能,故單實例最高才可能達(dá)到取決于數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)大小以及服務(wù)器硬件性能,日常環(huán)境中高峰大約在左右。 1 基本概念 1.1 Redis(內(nèi)存數(shù)據(jù)庫) Redis是一個key-value存儲系統(tǒng)(布式內(nèi)緩存,高性能的key-value數(shù)據(jù)庫)。和Memcached類似,它支持存儲的value類型相對...
閱讀 3212·2021-11-02 14:44
閱讀 3725·2021-09-02 15:41
閱讀 1661·2019-08-29 16:57
閱讀 1784·2019-08-26 13:38
閱讀 3297·2019-08-23 18:13
閱讀 2104·2019-08-23 15:41
閱讀 1668·2019-08-23 14:24
閱讀 3029·2019-08-23 14:03