標籤:緩衝 redis mysql 排序
在實現緩衝排序功能之前,必須先明白這一功能的合理性。不妨思考一下,既然可以在資料庫中排序,為什麼還要把排序功能放在緩衝中實現呢?這裡簡單總結了兩個原因:首先,排序會增加資料庫的負載,難以支撐高並發的應用;其次,在緩衝中排序不會遇到表鎖定的問題。Redis恰好提供了排序功能,使我們可以方便地實現緩衝排序。
Redis中用於實現排序功能的是SORT命令。該命令提供了多種參數,可以對列表,集合和有序集合進行排序。SORT命令格式如下:
SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC | DESC] [ALPHA] [STORE destination]
BY參數用於指定排序欄位,功能類似於SQL中的order by。對於列表和集合而言,僅按照它們的值進行排序往往沒有實際意義。以函數Cache2Hash返回的集合為例(實際上返回的是集合鍵),該集合中儲存的是一系列完整的雜湊鍵,只按照這些鍵進行排序,結果無非是按照數字或字典順序排列,其用處顯然不大。這是因為真正儲存行資料的是雜湊結構本身,而非雜湊鍵。假設集合鍵為"resultset.hash:123456",集合中每個雜湊鍵對應的雜湊結構中都有一個名為“timestamp”的欄位,現在要把集合中的所有雜湊鍵按照timestamp欄位進行排序,這時,只需執行以下命令:
SORT resultset.hash:123456 BY *->timestamp
從上例可以看出,BY的真正威力在於它可以讓SORT命令按照一個指定的外部鍵的外部欄位進行排序。SORT用集合resultset.hash:123456中的每個值(即每個雜湊鍵)替換BY參數後的第一個“*”,並依據“->”後面給出的欄位擷取其值,最後根據這些欄位值對雜湊鍵進行排序。
LIMIT參數用於限制排序以後返回元素的數量,功能類似於SQL中的limit。該參數接受另外兩個參數,即offset和count,LIMIT offset count表示跳過前offset個元素,返回之後的連續count個元素。可見,LIMIT參數可以用於實現分頁功能。
GET參數用於返回指定的欄位值。以集合resultset.hash:123456為例,使用BY參數對集合中的所有雜湊鍵按照雜湊結構中的timestamp欄位排序後,SORT命令返回所有排序之後的雜湊鍵。如果某個請求需要不是鍵而是某些欄位值,這時就要使用GET參數,使SORT命令返回指定欄位值。假設除timestamp欄位以外,集合中每個雜湊鍵對應的雜湊結構中還有一個名為“id”的欄位,通過以下命令可以使SORT返回按照timestamp排序以後的每個雜湊鍵對應的雜湊結構中的timestamp和id值:
SORT resultset.hash:123456 BY *->timestamp GET *->timestamp GET *->id
SORT用集合resultset.hash:123456中的每個值(即每個雜湊鍵)替換GET參數之後的第一個“*”,並將其作為返回值。值得注意的是,利用GET #能夠得到集合中的雜湊鍵本身。
ASC和DESC參數用於指定排序次序(預設為ASC,即從低到高),ALPHA參數用於按照字典順序排列非數字元素。
STORE參數用於將SORT命令的返回值,即排序結果存入一個指定的列表。加上STORE參數後,SORT命令的返回值就變為排序結果的個數。
下面的代碼實現了按照雜湊的某個欄位對集合中的雜湊鍵排序,並將結果存入列表的過程:
// 該函數對集合中的所有HASH鍵進行排序,排序依據是HASH鍵所對應的HASH中的某個欄位,// 排序結果被存入一個LIST結構,LIST鍵應當包含結果集標識符和排序欄位標識符,// 形如“sorted:123456:1234”string SortHash(sql::Connection *mysql_connection, redisContext *redis_connection, const string &resultset_id, const string &sort_field, int offset, int count, int order, int ttl) { // 只考慮儲存HASH鍵的SET string redis_row_set_key = "resultset.hash:" + resultset_id; redisReply *reply; // 檢測SET是否存在 reply = static_cast<redisReply*>(redisCommand(redis_connection, "EXISTS %s", redis_row_set_key.c_str())); if (reply->integer == 0) { freeReplyObject(reply); throw runtime_error("FAILURE - no resultsets"); } else { freeReplyObject(reply); } string field_md5 = md5(sort_field); // 利用MD5排除排序欄位中空格造成的影響 // 將排序結果存入該LIST string redis_sorted_list_key = "sorted:" + resultset_id + ":" + field_md5; string by("*->" + sort_field); //確定排序欄位 string ord = (order == 1) ? "ASC" : "DESC"; //order==1時按照升序排列;否則為降序 stringstream ofsstream, cntstream; ofsstream << offset; cntstream << count; // 執行排序命令,並把排序結果存入LIST reply = static_cast<redisReply*>(redisCommand( redis_connection, "SORT %s BY %s LIMIT %s %s GET %s ALPHA STORE %s", redis_row_set_key.c_str(), by.c_str(), ofsstream.str().c_str(), cntstream.str().c_str(), "#", redis_sorted_list_key.c_str())); freeReplyObject(reply); stringstream ttlstream; ttlstream << ttl; // 設定LIST的到期時間 reply = static_cast<redisReply*>(redisCommand(redis_connection, "EXPIRE %s %s", redis_sorted_list_key.c_str(), ttlstream.str().c_str())); freeReplyObject(reply); return redis_sorted_list_key; // 返回LIST鍵,以便於其他函數擷取該LIST中的內容
顯然,對結果集中的雜湊鍵進行排序要比對字串鍵排序更加直觀和方便。藉助於排序函數,可以方便地實現在Redis中查詢排序後的結果集,代碼如下:
// 該函數根據sql語句和排序參數,在Redis中查詢相應的結果集並進行排序,最後返回// 排序之後的HASH鍵vector<string> GetSortedCache(sql::Connection *mysql_connection, redisContext *redis_connection, const string &sql, const string &sort_field, int offset, int count, int order, int ttl) { vector<string> redis_row_key_vector; redisReply *reply; string resultset_id = md5(sql); // 結果集標識符 string field_md5 = md5(sort_field); // 排序欄位標識符 // 嘗試擷取LIST中的所有HASH鍵 string redis_sorted_list_key = "sorted:" + resultset_id + ":" + field_md5; // 嘗試擷取LIST中的所有HASH鍵 reply = static_cast<redisReply*>(redisCommand(redis_connection, "LRANGE %s %s %s", redis_sorted_list_key.c_str(), "0", "-1")); if (reply->type == REDIS_REPLY_ARRAY) { // 如果LIST不存在,調用Cache2Hash函數從Mysql中拉取資料到Redis,然後調用SortHash函數 // 對結果集進行排序並將排序後的HASH鍵存入LIST if (reply->elements == 0) { freeReplyObject(reply); sql::Statement *stmt = mysql_connection->createStatement(); sql::ResultSet *resultset = stmt->executeQuery(sql); Cache2Hash(mysql_connection, redis_connection, resultset, resultset_id, ttl); redis_sorted_list_key = SortHash(mysql_connection, redis_connection, resultset_id, sort_field, offset, count, order, ttl); // 再次嘗試擷取LIST中的所有HASH鍵 reply = static_cast<redisReply*>(redisCommand( redis_connection, "LRANGE %s %s %s", redis_sorted_list_key.c_str(), "0", "-1")); delete resultset; delete stmt; } // 將LIST中的所有HASH鍵存入redis_row_key_vector中 string redis_row_key; for (int i = 0; i < reply->elements; ++i) { redis_row_key = reply->element[i]->str; redis_row_key_vector.push_back(redis_row_key); } freeReplyObject(reply); } else { freeReplyObject(reply); throw runtime_error("FAILURE - LRANGE error"); } return redis_row_key_vector;}
這樣,在Redis中對結果集進行簡單排序操作的功能就實現了。
初學Redis(4)——簡單實現Redis緩衝中的排序功能