中文交流群
我創建了一個名為redis-llm的redis模塊,該模塊將LLMS(大型語言模型)與Redis集成在一起。您可以通過提出問題來學習Redis Plus-Plus。
這是Redis的C ++客戶庫。它基於Hiredis,與C ++ 17,C ++ 14和C ++ 11兼容。
注意:我不是母語的人。因此,如果文檔尚不清楚,請隨時打開問題或提取請求。我會盡快回复。
主分支是穩定的分支,它通過所有測試。開發分支不穩定。如果您想貢獻,請在開發分支機構上創建拉動請求。
由於Redis-Plus-Plus基於Hiredis ,因此您應該首先安裝Hiredis 。 Hiredis的最低版本要求為v0.12.1 。但是,始終建議使用最新的Hiredis穩定版本。
注意:您必須確保安裝了Hiredis的1個版本。否則,您可能會遇到一些有線問題。例如,檢查以下問題:第135期,第140期和第158期。
通常,您可以使用C ++軟件包管理器安裝Hiredis ,這是最簡單的方法,例如sudo apt-get install libhiredis-dev 。但是,如果要安裝Hiredis的最新代碼或指定的版本(例如,需要Hiredis V1.0.0或更高版本),則可以從源安裝。
再次注意:不要安裝多個版本的Hiredis。
git clone https://github.com/redis/hiredis.git
cd hiredis
make
make install默認情況下, Hiredis安裝在/usr /local 。如果要在非默認位置安裝Hiredis ,請使用以下命令指定安裝路徑。
make PREFIX=/non/default/path
make PREFIX=/non/default/path installRedis-Plus-Plus是用Cmake構建的。
git clone https://github.com/sewenew/redis-plus-plus.git
cd redis-plus-plus
mkdir build
cd build
cmake ..
make
make install
cd ..如果HIRDIS安裝在非默認位置,則應使用CMAKE_PREFIX_PATH來指定Hiredis的安裝路徑。默認情況下, redis-plus-plus安裝在/usr /local 。但是,您可以使用CMAKE_INSTALL_PREFIX在非默認位置安裝redis-plus-plus 。
cmake -DCMAKE_PREFIX_PATH=/path/to/hiredis -DCMAKE_INSTALL_PREFIX=/path/to/install/redis-plus-plus ..由於版本1.3.0,默認情況下,使用-std=c++17標準構建了Redis-Plus-Plus 。這樣我們就可以使用std :: string_view和std ::可選功能。但是,它也可以使用-std=c++11或-std=c++14標準構建,在這種情況下,我們擁有自己的std::string_view和std::optional簡單實現。為了明確指定C ++標準,您可以使用以下CMAKE標誌: -DREDIS_PLUS_PLUS_CXX_STANDARD=11 。
cmake -DCMAKE_PREFIX_PATH=/path/to/hiredis -DCMAKE_INSTALL_PREFIX=/path/to/install/redis-plus-plus -DREDIS_PLUS_PLUS_CXX_STANDARD=11 ..注意:您應該使用相同標準構建Redis-Plus-Plus和應用程序,例如,如果您使用C ++ 17標準構建Redis-Plus-Plus ,則還必須使用C ++ 17標準構建應用程序代碼。
編譯redis-Plus-Plus時,還會編譯一個測試程序,這可能需要一段時間。但是,您可以使用以下CMAKE選項禁用建築測試: -DREDIS_PLUS_PLUS_BUILD_TEST=OFF 。
cmake -DCMAKE_PREFIX_PATH=/path/to/hiredis -DCMAKE_INSTALL_PREFIX=/path/to/install/redis-plus-plus -DREDIS_PLUS_PLUS_BUILD_TEST=OFF ..默認情況下, redis-plus-plus既構建靜態庫,又建立共享庫。如果您只想構建其中一個,則可以使用-DREDIS_PLUS_PLUS_BUILD_STATIC=OFF或-DREDIS_PLUS_PLUS_BUILD_SHARED=OFF禁用另一個。
Redis-Plus-Plus默認情況下,使用-fPIC選項(即獨立代碼)構建靜態庫。但是,您可以使用-DREDIS_PLUS_PLUS_BUILD_STATIC_WITH_PIC=OFF禁用它。
現在, Hiredis擁有Windows支持,自Visual Studio 2017以來,Visual Studio對Cmake具有內置支持。因此, Redis-Plus-Plus現在還支持Windows平台。它已經通過Visual Studio 2017進行了全面測試,後來又在Win 10上進行了測試。我對Visual Studio環境不熟悉,以下DOC可能不准確。如果您熟悉Windows平台,請隨時更新有關如何在Windows上安裝Redis-Plus-Plus的文檔。
以下是有關如何使用Visual Studio 2017或以後構建CMAKE項目的一些鏈接。如果您不熟悉它,最好先閱讀以下說明:
注意:IMHO,Visual Studio 2017對Cmake項目的支持不是很成熟,我建議您使用Visual Studio 2019構建Hiredis和 *Redis-Plus-Plus。
首先,您需要在Master Branch上獲取Hiredis的最新代碼。舊版本可能不支持Windows平台。 hiredis的cmakelists.txt使用add_compile_definitions方法,僅由cmake 3.12或更高版本支持。但是,Visual Studio 2017的Cmake版本比這更古老。因此,如果您使用的是Visual Studio 2017,則需要在CMakelists.TXT文件中評論以下行:
#IF(WIN32)
# ADD_COMPILE_DEFINITIONS(_CRT_SECURE_NO_WARNINGS WIN32_LEAN_AND_MEAN)
#ENDIF()您可以使用“開放文件夾”功能打開Hiredis項目,並使用上面提到的說明(鏈接)構建它。
由於Redis-Plus-Plus取決於Hiredis ,因此我們需要在構建其之前指定Hiredis的安裝路徑。您可以使用“開放文件夾”功能打開Redis-Plus-Plus項目。您需要編輯cmakesetting.json文件(由Visual Studio自動生成),以設置Hiredis_header , Hiredis_lib和test_hiredis_lib變量,以指定Hiredis標頭的安裝路徑,HIRIRIDIS Dynamic庫的安裝路徑和Hiredis static static stitic庫的安裝路徑。以下是cmakesetting.json文件的示例:
{
"configurations" : [
{
"name" : " x64-Release " ,
"generator" : " Visual Studio 15 2017 Win64 " ,
"configurationType" : " Release " ,
"buildRoot" : " ${env.LOCALAPPDATA} \ CMakeBuild \ ${workspaceHash} \ build \ ${name} " ,
"cmakeCommandArgs" : " " ,
"buildCommandArgs" : " -m -v:minimal " ,
"variables" : [
{
"name" : " HIREDIS_HEADER " ,
"value" : " installation path of hiredis header files " ,
"type" : " PATH "
},
{
"name" : " HIREDIS_LIB " ,
"value" : " installation path of dynamic library of hiredis " ,
"type" : " FILEPATH "
},
{
"name" : " TEST_HIREDIS_LIB " ,
"value" : " installation path of static library of hiredis " ,
"type" : " FILEPATH "
}
]
}
]
}然後,您可以構建上述指令(鏈接)。如果您在調試模式下使用Visual Studio 2017進行構建,則在構建測試時可能會出現 /BIGOBJ錯誤。在這種情況下,您可以通過設置-DREDIS_PLUS_PLUS_BUILD_TEST=OFF或以發布模式構建建築物測試。
筆記:
REDIS_PLUS_PLUS_CXX_STANDARD CMAKE選項設置為11。如果您想通過Visual Studio構建該項目並對其有疑問,請按照以下步驟操作。以下是在Visual Studio 2022社區中測試的。
# download two projects into this folder
mkdir redis ++
cd redis ++
# make sure you create a hiredis first to work as a library
mkdir hiredis - lib
cd hiredis - lib
mkdir lib
git clone https: // github.com / redis / hiredis.git
cd hiredis到目前為止,每個步驟都應該很好。然後打開CMakeLists.txt文件。修改以下行並評論
...
# SET(CMAKE_DEBUG_POSTFIX d)
...然後回到Hiredis項目文件夾
mkdir build
cd build
# convert project into visual studio 2022, if necessary choose you version e.g 19 2019 etc.
cmake - G " Visual Studio 17 2022 " ..
. / hiredis.sln將hiredis設置為啟動項目,然後在調試模式下單擊Build Solution
成功構建後,將Debug中的所有文件複製到hiredis-lib/lib文件夾中
在這裡,Hiredis的工作應該完成。
然後返回redis++文件夾。在這裡開放終端
git clone https: // github.com / sewenew / redis - plus - plus.git
cd redis - plus - plus
mkdir build
cd build現在,您應該始終在PC上使用OpenSSL,否則可以使用Chocolatey安裝它。對於Visual Studio 2022,請使用vpckg單獨安裝Pthread,遵循此鏈接
畢竟準備。如果您想轉換所有項目,則
cmake - DCMAKE_PREFIX_PATH = " $ ( ABSOLUTE_PATH ) hiredis-lib " - G " Visual Studio 17 2022 " ..
cd build
. / redis ++ .sln將redis++_static設置為啟動項目,然後單擊Build Solution
到目前為止,構建已成功完成!
在Windows平台上,如果您的應用程序代碼還需要包括Windows.h 。您必須確保SW/REDIS ++/REDIS ++。 hWindows.h之前包含H。檢查此問題以獲取細節。
使用CMAKE提供了建立GNU/Debian軟件包的基本支持。以下示例顯示瞭如何構建Debian軟件包:
mkdir build ; cd build
cmake ..
cpack -G DEB安裝前綴可以修改如下:
mkdir build ; cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
cpack -G DEBRedis-Plus-Plus已通過以下編譯器進行了全面測試:
gcc version 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC)
gcc version 5.5.0 20171010 (Ubuntu 5.5.0-12ubuntu1)
gcc version 6.5.0 20181026 (Ubuntu 6.5.0-2ubuntu1~18.04)
gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)
gcc version 8.3.0 (Ubuntu 8.3.0-6ubuntu1~18.04.1)
gcc version 9.2.1 20191008 (Ubuntu 9.2.1-9ubuntu2)
gcc version 10.2.1 20210110 (Debian 10.2.1-6)
clang version 3.9.1-19ubuntu1 (tags/RELEASE_391/rc2)
clang version 4.0.1-10 (tags/RELEASE_401/final)
clang version 5.0.1-4 (tags/RELEASE_501/final)
clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)
clang version 7.0.0-3~ubuntu0.18.04.1 (tags/RELEASE_700/final)
clang version 8.0.1-3build1 (tags/RELEASE_801/final)
Apple clang version 11.0.0 (clang-1100.0.33.12)
Visual Studio 2017 (Win 10)
Visual Studio 2019 (Win 10)如果使用-DREDIS_PLUS_PLUS_BUILD_TEST=ON構建Redis -Plus -Plus (默認行為,並且可以使用-DREDIS_PLUS_PLUS_BUILD_TEST=OFF ),則可以在build/test Directory中獲得一個測試程序: build/test/test/test_redis++ 。
為了運行測試,您需要設置一個redis實例和redis群集。由於測試程序將將大部分REDIS命令發送到服務器和群集,因此您需要設置最新版本的Redis。否則,測試可能會失敗。例如,如果您設置了REDIS 4.0進行測試,則測試程序試圖將ZPOPMAX命令(REDIS 5.0命令)發送到服務器時將失敗。如果要使用其他REDIS版本運行測試,則必須評論Redis redis尚未支持的命令,從redis-plus-plus-plus/test/src/src/src/sw/sw/redis ++/目錄中的測試源文件。不便之處,抱歉,我將解決此問題,以使測試程序將來與任何版本的REDIS一起使用。
注意:REDIS的最新版本只是運行測試的要求。實際上,您可以使用任何版本的redis使用redis-Plus-plus ,即Redis 2.0及更高版本。
切勿在生產胎兒中運行測試程序,因為測試程序讀取或寫入的密鑰可能與您的應用程序相抵觸。
為了使用REDIS和REDIS群集進行測試,您可以使用以下命令運行測試程序:
./build/test/test_redis++ -h host -p port -a auth -n cluster_node -c cluster_port如果您只想使用REDIS進行測試,則只需要指定主機,端口和驗證選項:
./build/test/test_redis++ -h host -p port -a auth同樣,如果您只想使用REDIS群集運行測試,則只需指定Cluster_Node , Cluster_Port和Auth Optoct:
./build/test/test_redis++ -a auth -n cluster_node -c cluster_port默認情況下,測試程序不會在多線程環境中測試運行Redis-Plus-Plus 。如果您想進行多線程測試,這可能需要很長時間,則可以指定-m選項:
./build/test/test_redis++ -h host -p port -a auth -n cluster_node -c cluster_port -m如果所有測試都通過了,則測試程序將打印以下消息:
Pass all tests否則,它將打印錯誤消息。
Redis-Plus-Plus的運行速度與Hiredis一樣快,因為它是Hiredis的包裝紙。您可以在基準模式下運行test_redis ++以檢查環境中的性能。
./build/test/test_redis++ -h host -p port -a auth -n cluster_node -c cluster_port -b -t thread_num -s connection_pool_size -r request_num -k key_len -v val_len10 。5默認情況下。100000 。10 。10 。 Bechmark將生成100隨機二進制鍵進行測試,這些鍵的大小由Key_Len指定。當基準運行時,它將用這些鍵讀/寫。因此,切勿在生產環境中運行測試程序,否則,它可能會刪除數據。
編譯代碼後,您將同時獲得共享庫和靜態庫。由於Redis-Plus-Plus取決於Hiredis ,因此您需要將兩個庫將其鏈接到您的應用程序。另外,不要忘記指定C ++標準, -std=c++17 , -std=c++14或-std=c++11 ,以及與線程相關的選項。
以GCC為例。
g++ -std=c++17 -o app app.cpp /path/to/libredis++.a /path/to/libhiredis.a -pthread如果在非默認位置安裝了Hiredis和Redis-Plus-Plus ,則應使用-I選項來指定標題路徑。
g++ -std=c++17 -I/non-default/install/include/path -o app app.cpp /path/to/libredis++.a /path/to/libhiredis.a -pthreadg++ -std=c++17 -o app app.cpp -lredis++ -lhiredis -pthread如果在非默認位置安裝了Hiredis和redis-plus-plus ,則應使用-I和-L選項指定標題和庫路徑。
g++ -std=c++17 -I/non-default/install/include/path -L/non-default/install/lib/path -o app app.cpp -lredis++ -lhiredis -pthread與共享庫鏈接並運行應用程序時,您可能會收到以下錯誤消息:
error while loading shared libraries: xxx: cannot open shared object file: No such file or directory.那是因為鏈接器找不到共享庫。為了解決問題,您可以添加安裝Hiredis和Redis-Plus-Plus庫的路徑,並將其添加到LD_LIBRARY_PATH環境變量中。例如:
export LD_LIBRARY_PATH= $LD_LIBRARY_PATH :/usr/local/lib檢查此Stackoverflow問題,以獲取有關如何解決問題的詳細信息。
如果您使用cmake來構建應用程序,則需要在cmakelists.txt中添加hiredis和redis-plus-plus依賴項:
# <---------- set c++ standard ------------->
# NOTE: you must build redis-plus-plus and your application code with the same standard.
set (CMAKE_CXX_STANDARD 17)
set (CMAKE_CXX_STANDARD_REQUIRED ON )
# <------------ add hiredis dependency --------------->
find_path (HIREDIS_HEADER hiredis)
target_include_directories ( target PUBLIC ${HIREDIS_HEADER} )
find_library (HIREDIS_LIB hiredis)
target_link_libraries ( target ${HIREDIS_LIB} )
# <------------ add redis-plus-plus dependency -------------->
# NOTE: this should be *sw* NOT *redis++*
find_path (REDIS_PLUS_PLUS_HEADER sw)
target_include_directories ( target PUBLIC ${REDIS_PLUS_PLUS_HEADER} )
find_library (REDIS_PLUS_PLUS_LIB redis++)
target_link_libraries ( target ${REDIS_PLUS_PLUS_LIB} )有關cmakelists.txt的完整示例,請參見此問題。
另外,如果您在非默認位置安裝了Hiredis和redis-plus-plus ,則需要使用CMAKE_PREFIX_PATH選項運行CMake,以指定這兩個庫的安裝路徑。
cmake -DCMAKE_PREFIX_PATH=/installation/path/to/the/two/libs ..
# include < sw/redis++/redis++.h >
using namespace sw ::redis ;
try {
// Create an Redis object, which is movable but NOT copyable.
auto redis = Redis ( " tcp://127.0.0.1:6379 " );
// ***** STRING commands *****
redis. set ( " key " , " val " );
auto val = redis. get ( " key " ); // val is of type OptionalString. See 'API Reference' section for details.
if (val) {
// Dereference val to get the returned value of std::string type.
std::cout << *val << std::endl;
} // else key doesn't exist.
// ***** LIST commands *****
// std::vector<std::string> to Redis LIST.
std::vector<std::string> vec = { " a " , " b " , " c " };
redis. rpush ( " list " , vec. begin (), vec. end ());
// std::initializer_list to Redis LIST.
redis. rpush ( " list " , { " a " , " b " , " c " });
// Redis LIST to std::vector<std::string>.
vec. clear ();
redis. lrange ( " list " , 0 , - 1 , std::back_inserter (vec));
// ***** HASH commands *****
redis. hset ( " hash " , " field " , " val " );
// Another way to do the same job.
redis. hset ( " hash " , std::make_pair ( " field " , " val " ));
// std::unordered_map<std::string, std::string> to Redis HASH.
std::unordered_map<std::string, std::string> m = {
{ " field1 " , " val1 " },
{ " field2 " , " val2 " }
};
redis. hmset ( " hash " , m. begin (), m. end ());
// Redis HASH to std::unordered_map<std::string, std::string>.
m. clear ();
redis. hgetall ( " hash " , std::inserter (m, m. begin ()));
// Get value only.
// NOTE: since field might NOT exist, so we need to parse it to OptionalString.
std::vector<OptionalString> vals;
redis. hmget ( " hash " , { " field1 " , " field2 " }, std::back_inserter (vals));
// ***** SET commands *****
redis. sadd ( " set " , " m1 " );
// std::unordered_set<std::string> to Redis SET.
std::unordered_set<std::string> set = { " m2 " , " m3 " };
redis. sadd ( " set " , set. begin (), set. end ());
// std::initializer_list to Redis SET.
redis. sadd ( " set " , { " m2 " , " m3 " });
// Redis SET to std::unordered_set<std::string>.
set. clear ();
redis. smembers ( " set " , std::inserter (set, set. begin ()));
if (redis. sismember ( " set " , " m1 " )) {
std::cout << " m1 exists " << std::endl;
} // else NOT exist.
// ***** SORTED SET commands *****
redis. zadd ( " sorted_set " , " m1 " , 1.3 );
// std::unordered_map<std::string, double> to Redis SORTED SET.
std::unordered_map<std::string, double > scores = {
{ " m2 " , 2.3 },
{ " m3 " , 4.5 }
};
redis. zadd ( " sorted_set " , scores. begin (), scores. end ());
// Redis SORTED SET to std::vector<std::pair<std::string, double>>.
// NOTE: The return results of zrangebyscore are ordered, if you save the results
// in to `std::unordered_map<std::string, double>`, you'll lose the order.
std::vector<std::pair<std::string, double >> zset_result;
redis. zrangebyscore ( " sorted_set " ,
UnboundedInterval< double >{}, // (-inf, +inf)
std::back_inserter (zset_result));
// Only get member names:
// pass an inserter of std::vector<std::string> type as output parameter.
std::vector<std::string> without_score;
redis. zrangebyscore ( " sorted_set " ,
BoundedInterval< double >( 1.5 , 3.4 , BoundType::CLOSED), // [1.5, 3.4]
std::back_inserter (without_score));
// Get both member names and scores:
// pass an back_inserter of std::vector<std::pair<std::string, double>> as output parameter.
std::vector<std::pair<std::string, double >> with_score;
redis. zrangebyscore ( " sorted_set " ,
BoundedInterval< double >( 1.5 , 3.4 , BoundType::LEFT_OPEN), // (1.5, 3.4]
std::back_inserter (with_score));
// ***** SCRIPTING commands *****
// Script returns a single element.
auto num = redis. eval < long long >( " return 1 " , {}, {});
// Script returns an array of elements.
std::vector<std::string> nums;
redis. eval ( " return {ARGV[1], ARGV[2]} " , {}, { " 1 " , " 2 " }, std::back_inserter (nums));
// mset with TTL
auto mset_with_ttl_script = R"(
local len = #KEYS
if (len == 0 or len + 1 ~= #ARGV) then return 0 end
local ttl = tonumber(ARGV[len + 1])
if (not ttl or ttl <= 0) then return 0 end
for i = 1, len do redis.call("SET", KEYS[i], ARGV[i], "EX", ttl) end
return 1
)" ;
// Set multiple key-value pairs with TTL of 60 seconds.
auto keys = { " key1 " , " key2 " , " key3 " };
std::vector<std::string> args = { " val1 " , " val2 " , " val3 " , " 60 " };
redis. eval < long long >(mset_with_ttl_script, keys. begin (), keys. end (), args. begin (), args. end ());
// ***** Pipeline *****
// Create a pipeline.
auto pipe = redis. pipeline ();
// Send mulitple commands and get all replies.
auto pipe_replies = pipe . set ( " key " , " value " )
. get ( " key " )
. rename ( " key " , " new-key " )
. rpush ( " list " , { " a " , " b " , " c " })
. lrange ( " list " , 0 , - 1 )
. exec ();
// Parse reply with reply type and index.
auto set_cmd_result = pipe_replies. get < bool >( 0 );
auto get_cmd_result = pipe_replies. get <OptionalString>( 1 );
// rename command result
pipe_replies. get < void >( 2 );
auto rpush_cmd_result = pipe_replies. get < long long >( 3 );
std::vector<std::string> lrange_cmd_result;
pipe_replies. get ( 4 , back_inserter (lrange_cmd_result));
// ***** Transaction *****
// Create a transaction.
auto tx = redis. transaction ();
// Run multiple commands in a transaction, and get all replies.
auto tx_replies = tx. incr ( " num0 " )
. incr ( " num1 " )
. mget ({ " num0 " , " num1 " })
. exec ();
// Parse reply with reply type and index.
auto incr_result0 = tx_replies. get < long long >( 0 );
auto incr_result1 = tx_replies. get < long long >( 1 );
std::vector<OptionalString> mget_cmd_result;
tx_replies. get ( 2 , back_inserter (mget_cmd_result));
// ***** Generic Command Interface *****
// There's no *Redis::client_getname* interface.
// But you can use *Redis::command* to get the client name.
val = redis. command <OptionalString>( " client " , " getname " );
if (val) {
std::cout << *val << std::endl;
}
// Same as above.
auto getname_cmd_str = { " client " , " getname " };
val = redis. command <OptionalString>(getname_cmd_str. begin (), getname_cmd_str. end ());
// There's no *Redis::sort* interface.
// But you can use *Redis::command* to send sort the list.
std::vector<std::string> sorted_list;
redis. command ( " sort " , " list " , " ALPHA " , std::back_inserter (sorted_list));
// Another *Redis::command* to do the same work.
auto sort_cmd_str = { " sort " , " list " , " ALPHA " };
redis. command (sort_cmd_str. begin (), sort_cmd_str. end (), std::back_inserter (sorted_list));
// ***** Redis Cluster *****
// Create a RedisCluster object, which is movable but NOT copyable.
auto redis_cluster = RedisCluster ( " tcp://127.0.0.1:7000 " );
// RedisCluster has similar interfaces as Redis.
redis_cluster. set ( " key " , " value " );
val = redis_cluster. get ( " key " );
if (val) {
std::cout << *val << std::endl;
} // else key doesn't exist.
// Keys with hash-tag.
redis_cluster. set ( " key{tag}1 " , " val1 " );
redis_cluster. set ( " key{tag}2 " , " val2 " );
redis_cluster. set ( " key{tag}3 " , " val3 " );
std::vector<OptionalString> hash_tag_res;
redis_cluster. mget ({ " key{tag}1 " , " key{tag}2 " , " key{tag}3 " },
std::back_inserter (hash_tag_res));
} catch ( const Error &e) {
// Error handling.
}您還可以查看redis.h以獲取Doxygen樣式文檔。
Redis類維護一個連接池到Redis服務器。如果連接斷開, Redis會自動重新連接到Redis服務器。
您可以使用ConnectionOptions和ConnectionPoolOptions初始化Redis實例。 ConnectionOptions指定了連接到Redis服務器的選項,並且ConnectionPoolOptions指定了Conneciton池的選項。 ConnectionPoolOptions是可選的。如果未指定, Redis將保持與Redis服務器的單個連接。
ConnectionOptions connection_options;
connection_options.host = " 127.0.0.1 " ; // Required.
connection_options.port = 6666 ; // Optional. The default port is 6379.
connection_options.password = " auth " ; // Optional. No password by default.
connection_options.db = 1 ; // Optional. Use the 0th database by default.
// Optional. Timeout before we successfully send request to or receive response from redis.
// By default, the timeout is 0ms, i.e. never timeout and block until we send or receive successfuly.
// NOTE: if any command is timed out, we throw a TimeoutError exception.
connection_options.socket_timeout = std::chrono::milliseconds( 200 );
// Connect to Redis server with a single connection.
Redis redis1 (connection_options);
ConnectionPoolOptions pool_options;
pool_options.size = 3 ; // Pool size, i.e. max number of connections.
// Optional. Max time to wait for a connection. 0ms by default, which means wait forever.
// Say, the pool size is 3, while 4 threds try to fetch the connection, one of them will be blocked.
pool_options.wait_timeout = std::chrono::milliseconds( 100 );
// Optional. Max lifetime of a connection. 0ms by default, which means never expire the connection.
// If the connection has been created for a long time, i.e. more than `connection_lifetime`,
// it will be expired and reconnected.
pool_options.connection_lifetime = std::chrono::minutes( 10 );
// Connect to Redis server with a connection pool.
Redis redis2 (connection_options, pool_options);注意:如果設置ConnectionOptions::socket_timeout ,然後嘗試調用blocking命令,例如Redis::brpop , Redis::blpop , Redis::bzpopmax , Redis::bzpopmin ,則必須確保ConnectionOptions::socket_timeout比這些封鎖命令所指定的超時。否則,您可能會獲得TimeoutError並丟失消息。
有關更多選項,請參見ConnectionOptions和ConnectionPoolOptions。另請參閱第80期有關連接池的討論。
注意: Redis類是可移動的,但不能複制。
// auto redis3 = redis1; // this won't compile.
// But it's movable.
auto redis3 = std::move(redis1);Redis-Plus-Plus還支持使用UNIX域套接字連接到Redis服務器。
ConnectionOptions options;
options.type = ConnectionType::UNIX;
options.path = " /path/to/socket " ;
Redis redis (options);您也可以使用URI連接到Redis服務器:
tcp://[[username:]password@]host[:port][/db]
redis://[[username:]password@]host[:port][/db]
unix://[[username:]password@]path-to-unix-domain-socket[/db]
需要該方案和主機零件,而其他零件則是可選的。如果您使用UNIX域插座連接到REDIS,則應使用UNIX方案,否則,應使用TCP或REDIS方案。以下是這些可選零件的默認值列表:
注意:如果您的密碼或用戶名包含“@”,或者您的用戶名包含':',則無法使用URI構造Redis對象。因為Redis-Plus-Plus會錯誤地解析URI。在這種情況下,您需要使用ConnectionOptions構建Redis對象。
注意:REDIS 6.0支持ACL,您可以為連接指定用戶名。但是,在Redis 6.0之前,您無法做到這一點。
另外,可以使用URI的查詢字符串指定以下連接選項和連接池選項,例如tcp://127.0.0.1?keep_alive = true&socket_time_timeout = 100ms&connect_time_timeout = 100ms :
| 選項 | 範圍 | 預設 |
|---|---|---|
ConnectionOptions::user | 用戶 | 預設 |
ConnectionOptions::password | 密碼 | 空字符串,即沒有密碼 |
ConnectionOptions::db | DB | 0 |
ConnectionOptions::keep_alive | keep_alive | 錯誤的 |
ConnectionOptions::connect_timeout | connect_timeout | 0ms |
ConnectionOptions::socket_timeout | socket_timeout | 0ms |
ConnectionOptions::resp | 解答 | 2 |
ConnectionPoolOptions::size | pool_size | 1 |
ConnectionPoolOptions::wait_timeout | pool_wait_timeout | 0ms |
ConnectionPoolOptions::connection_lifetime | pool_connection_lifetime | 0ms |
ConnectionPoolOptions::connection_idle_time | pool_connection_idle_time | 0ms |
筆記:
// Single connection to the given host and port.
Redis redis1 ( " tcp://127.0.0.1:6666 " );
// Use default port, i.e. 6379.
Redis redis2 ( " tcp://127.0.0.1 " );
// Connect to Redis with password, and default port.
Redis redis3 ( " tcp://[email protected] " );
// Connect to Redis and select the 2nd (db number starts from 0) database.
Redis redis4 ( " tcp://127.0.0.1:6379/2 " );
// Set keep_alive option to true with query string.
Redis redis5 ( " tcp://127.0.0.1:6379/2?keep_alive=true " );
// Set socket_timeout to 50 milliseconds, and connect_timeout to 1 second with query string.
Redis redis6 ( " tcp://127.0.0.1?socket_timeout=50ms&connect_timeout=1s " );
// Connect to Unix Domain Socket.
Redis redis7 ( " unix://path/to/socket " );自REDIS 6.0以來,它支持REDIS協議的新版本,即RESS3。為了使用此新協議,您需要將ConnectionOptions::resp設置為3。
ConnectionOptions opts;
opts.resp = 3;
// Set other options...
默認情況下, ConnectionOptions::resp是2,IE使用RESS版本2。到目前為止,僅支持2和3版本,並且如果您設置了ConnectionOptions::resp其他數字,則該行為是不確定的。
注意:為了使用此新協議,您需要安裝最新的Hiredis(即使是Hiredis-V1.0.2也有reves3支持的錯誤)。
游泳池中的連接是懶惰的。初始化連接池時,即Redis的構造函數, Redis不會連接到服務器。相反,僅當您嘗試發送命令時,它才連接到服務器。這樣,我們可以避免不必要的連接。因此,如果池大小為5,但最大並發連接的數量為3,則池中只有3個連接。
您無需檢查Redis對像是否成功連接到服務器。如果Redis無法創建與Redis服務器的連接,或者在某個時候斷開該連接,則當您嘗試使用Redis發送命令時,它會引發類型Error的例外。即使您有例外,即連接破裂,也無需創建一個新的Redis對象。您可以重複使用Redis對象發送命令, Redis對象將嘗試自動重新連接到服務器。如果它成功重新連接,則將命令發送到服務器。否則,它再次引發異常。
有關異常的詳細信息,請參見異常部分。
創建Redis對象並不便宜,因為它將創建與Redis服務器的新連接。因此,您最好盡可能多地重用Redis對象。此外,可以在多線程環境中調用Redis的成員功能,並且您可以在多個線程中共享Redis對象。
// This is GOOD practice.
auto redis = Redis( " tcp://127.0.0.1 " );
for ( auto idx = 0 ; idx < 100 ; ++idx) {
// Reuse the Redis object in the loop.
redis. set ( " key " , " val " );
}
// This is VERY BAD! It's very inefficient.
// NEVER DO IT!!!
for ( auto idx = 0 ; idx < 100 ; ++idx) {
// Create a new Redis object for each iteration.
auto redis = Redis ( " tcp://127.0.0.1 " );
redis. set ( " key " , " val " );
}Redis-Plus-Plus還具有TLS支持。但是,為了使用此功能,您需要在構建Hiredis和Redis-Plus-Plus時啟用它。
注意:到目前為止,尚未在Windows平台上測試TLS功能。將來我將修復它。
在用TLS支持構建Hiredis時,您需要下載版本V1.0.0或後者的Hiredis ,並指定USE_SSL=1標誌:
make PREFIX=/non/default/path USE_SSL=1
make PREFIX=/non/default/path USE_SSL=1 install然後,您可以通過指定-dredis_plus_plus_use_tls -DREDIS_PLUS_PLUS_USE_TLS=ON Option:
cmake -DREDIS_PLUS_PLUS_USE_TLS=ON ..為了通過TLS支持連接到REDIS,您需要指定以下連接選項:
ConnectionOptions opts;
opts.host = " 127.0.0.1 " ;
opts.port = 6379 ;
opts.tls.enabled = true ; // Required. `false` by default.
opts.tls.cert = " /path/to/client/certificate " ; // Optional
opts.tls.key = " /path/to/private/key/file " ; // Optional
opts.tls.cacert = " /path/to/CA/certificate/file " ; // You can also set `opts.tls.cacertdir` instead.
opts.tls.sni = " server-name-indication " ; // Optional儘管tls.cert和tls.key是可選的,但如果指定其中一個,則還必須指定另一個。您還可以指定存儲證書的目錄,而不是指定tls.cacert ,而是指定tls.cacertdir 。
這些選項與redis-cli的TLS相關命令行參數相同,因此您還可以運行redis-cli --help以獲取這些選項的詳細說明。
然後,您可以使用此ConnectionOptions創建一個Redis對象,以通過TLS支持連接到Redis服務器。
注意:構建應用程序代碼時,您還需要將其與libhiredis.a , libhiredis_ssl.a , libredis++.a (或相應的共享庫), -lssl和-lcrypto鏈接。
默認情況下, redis-plus-plus會自動初始化openssl庫,即調用SSL_library_init ,並在需要時初始化鎖。但是,您的應用程序代碼可能已經初始化了OpenSSL庫。在這種情況下,您可以調用tls::disable_auto_init()禁用初始化。您應僅調用此功能一次,然後在任何其他Redis-Plus Plus操作之前調用它。否則,行為是不確定的。
自Hiredis v1.1.0以來,它支持跳過證書驗證。如果要與Redis-Plus-Plus一起使用此功能,則可以檢查此問題以獲取示例。
您可以通過Redis對象發送redis命令。 Redis對於每個Redis命令都有一種或多種(超載)方法。該方法的名稱與相應的命令相同(較低)。例如,我們有3種為DEL key [key ...]命令的超載方法:
// Delete a single key.
long long Redis::del ( const StringView &key);
// Delete a batch of keys: [first, last).
template < typename Input>
long long Redis::del (Input first, Input last);
// Delete keys in the initializer_list.
template < typename T>
long long Redis::del (std::initializer_list<T> il);使用輸入參數,這些方法基於REDIS協議構建了REDIS命令,並將命令發送到Redis服務器。然後同步收到答复,解析並返回呼叫者。
讓我們仔細研究這些方法的參數並返回值。
這些方法中的大多數具有與相應命令相同的參數。以下是參數類型的列表:
| 參數類型 | 解釋 | 例子 | 筆記 |
|---|---|---|---|
| StringView | 字符串類型的參數。通常用於鍵,值,成員名稱,字段名稱等 | bool redis :: hset(const StringView&key,const StringView&field,const StringView&val) | 請參閱StringView部分以獲取StringView上的詳細信息 |
| 漫長 | 整數類型的參數。通常用於索引(例如列表命令)或整數 | void ltrim(const StringView&Key,漫長的開始,長時間長時間) 長decorby(const stringview&鍵,長長降低) | |
| 雙倍的 | 浮點類型的參數。通常用於得分(例如排序命令)或浮點類型的數量 | double cormbyfloat(const StringView&鍵,雙重增量) | |
| STD :: Chrono ::持續時間 std :: chrono :: time_point | 時間相關參數 | Bool Exprey(const StringView&key,const std :: chrono :: seconds&timeout) Bool Expireat(const StringView&key,const std :: chrono :: time_point <std :: chrono :: system_clock,std :: chrono :: chrono :: seconds>&tp) | |
| STD :: PAIR <StringView,StringView> | 用於Redis Hash(字段,值)對 | bool hset(const StringView&key,const std :: Pair <StringView,StringView>&item) | |
| std ::配對<double,double> | 用於Redis Geo(經度,緯度)對 | OptionAllonglong Georadius(const StringView&key,const std :: pair <double,double>&location,double radius,geounit單元,const stringview&destinate,bool store_dist,long long Count) | |
| 一對迭代器 | 使用一對迭代器指定一系列輸入,以便我們可以將數據傳遞到STL容器中 | 模板<輸入輸入> 長長的del(輸入第一,輸入最後) | 拋出一個例外,如果是空範圍,則首先==最後 |
| std :: initializer_list <t> | 使用初始化列表來指定一批輸入 | 模板<typename t> Long long del(std :: prinitizer_list <t> il) | |
| 一些選項 | 某些命令的選項 | updateType ,模板<typename t>類有限制的Interval | 有關詳細信息,請參見Command_options.h |
STD :: String_view是僅閱讀字符串參數類型的不錯選擇。但是,僅在C ++ 17標準中引入std::string_view ,因此,如果您使用-std=c++11構建Redis -Plus -Plus (即通過指定-DREDIS_PLUS_PLUS_CXX_STANDARD=11使用CMAKE命令)或-std=c++14標準,則std::string_view StringView您可以使用-std=c++17標準(即默認行為)構建Redis-Plus-plus ,該標準將提供std::string_view intely。然後,將StringView實現忽略了,將其忽略到std::string_view 。這是在redis-plus-plus庫中完成的: using StringView = std::string_view 。
由於std::string和C-Style String到StringView的轉換,您只需將std::string或C-Style String傳遞到需要StringView參數的方法即可。
// bool Redis::hset(const StringView &key, const StringView &field, const StringView &val)
// Pass c-style string to StringView.
redis.hset( " key " , " field " , " value " );
// Pass std::string to StringView.
std::string key = " key " ;
std::string field = " field " ;
std::string val = " val " ;
redis.hset(key, field, val);
// Mix std::string and c-style string.
redis.hset(key, field, " value " );REDIS協議定義了5種答复:
long long 。而且這些答复可能是無效的。例如,當您嘗試GET不存在的鍵的值時,Redis返回一個無塊字符串回复。
如上所述,將答复解析為這些方法的返回值。以下是返回類型的列表:
| 返回類型 | 解釋 | 例子 | 筆記 |
|---|---|---|---|
| 空白 | 狀態答复應始終返回“確定”字符串 | 重命名, setEx | |
| std :: string | 狀態答复並非總是返回“確定”,然後散裝字符串回复 | ping ,信息 | |
| 布爾 | 整數答复總是返回0或1 | 到期, HSET | 有關布爾返回值的含義,請參見布爾回報值部分 |
| 漫長 | 整數答复並非總是返回0或1 | del ,附加 | |
| 雙倍的 | 代表雙重的散裝字符串答复 | Zincrby的MercbyFloat | |
| std ::配對 | 數組與2個元素回复。 std::pair返回值始終是2個元素的數組 | blpop | |
| STD ::元組 | 數組的固定長度回复,並具有2個以上的元素。由於返回的數組的長度是固定的,因此我們將數組返回為std::tuple | BZPOPMAX | |
| 輸出迭代器 | 一般數組以非固定/動態長度回复。我們使用類似STL的接口返回此類數組回复,以便您可以輕鬆地將返回值插入STL容器中 | MGE , Lrange | 另外,有時輸出迭代器的類型決定使用命令發送哪些選項。有關詳細信息,請參見示例部分 |
| 可選<t> | 對於可能為null的T型的任何答复 | 獲取, LPOP , BLPOP , BZPOPMAX | 有關Optional<T>的詳細信息,請參見可選部分 |
| 變體<args ...> | 對於可能是服務不同類型的答复 | 內存統計 | 注意:到目前為止,只有在用C ++ 17標準編制Redis-Plus-Plus時才支持這種類型。通常與通用命令接口一起使用。有關Variant<Args...>的詳細信息,請參見變體部分。 |
| STL容器 | 一般數組回复 | config get | 輸出迭代器和STL容器均用於數組回复。不同之處在於, STL容器通常與通用命令接口一起使用。例如,請參見STL容器部分 |
某些方法的返回類型,例如EXPIRE , HSET ,是bool 。如果該方法返回false ,則不意味著Redis無法將命令發送到REDIS服務器。相反,這意味著Redis服務器返回整數回复,並且答复的值為0 。因此,如果該方法返回true ,則意味著redis服務器返回整數答复,並且答复的值為1 。您可以查看REDIS命令手冊的0和1代表。
例如,當我們將EXPIRE命令發送到redis服務器時,如果設置了超時,它將返回1 ,如果鍵不存在,則返回0 。因此,如果設置了超時, Redis::expire返回true ,如果不存在鍵, Redis::expire返回false 。
因此,切勿使用返回值檢查命令是否已成功發送到Redis服務器。相反,如果Redis未能將命令發送到服務器,則會引發類型Error的例外。有關異常的詳細信息,請參見異常部分。
如果Redis可能返回null回复,則STD ::可選的返回類型是一個不錯的選擇。但是,C ++ 17標準中引入了std::optional ,並且使用-std=c++11標準構建Redis -Plus -Plus (即通過指定-DREDIS_PLUS_PLUS_CXX_STANDARD=11使用CMake命令),我們實現了我們自己的簡單版本,IE template Optional<T> 。取而代之的是,如果您使用-std=c++17標準(即默認行為)構建Redis-Plus-Plus ,則可以使用std::optional ,並且我們有一個別名: template <typename T> using Optional = std::optional<T> 。
以GET和MGET命令為例:
// Or just: auto val = redis.get("key");
Optional<std::string> val = redis.get( " key " );
// Optional<T> has a conversion to bool.
// If it's NOT a null Optional<T> object, it's converted to true.
// Otherwise, it's converted to false.
if (val) {
// Key exists. Dereference val to get the string result.
std::cout << *val << std::endl;
} else {
// Redis server returns a NULL Bulk String Reply.
// It's invalid to dereference a null Optional<T> object.
std::cout << " key doesn't exist. " << std::endl;
}
std::vector<Optional<std::string>> values;
redis.mget({ " key1 " , " key2 " , " key3 " }, std::back_inserter(values));
for ( const auto &val : values) {
if (val) {
// Key exist, process the value.
}
}我們還為一些常用的Optional<T>提供了一些類型:
using OptionalString = Optional<std::string>;
using OptionalLongLong = Optional< long long >;
using OptionalDouble = Optional< double >;
using OptionalStringPair = Optional<std::pair<std::string, std::string>>;如果答复可能是不同的類型,則STD ::變體是返回類型的一個不錯選擇。例如, MEMORY STATS命令返回一個數組回复,實際上是鍵值對配置的映射:
127.0.0.1: 6379> memory stats
1) " peak.allocated "
2) (integer) 4471104
...
17) " db.0 "
18) 1) " overhead.hashtable.main "
2) (integer) 104
3) " overhead.hashtable.expires "
4) (integer) 32
...
27) " dataset.percentage "
28) " 9.70208740234375 "
...但是,如您所見,結果的值部分可能是類型長的長度(鍵: peak.Salted ),double(鍵: dataset.percentage )甚至地圖(鍵: db.0 )。因此,您不能簡單地將結果解析為std::unordered_map<std::string, long long>或std::unordered_map<std::string, double> 。解決方法是將結果解析為tuple ,但是,該元組解決方案醜陋且容易出錯。檢查此問題以獲取細節。
在這種情況下,如果您構建使用C ++ 17標準的Redis-Plus-Plus構建std::variant -Plus,則Variant是非常有幫助的。您可以將結果解析為std::unordered_map<std::string, Variant<double, long long, std::unordered_map<std::string, long long>>> 。
using Var = Variant< double , long long , std::unordered_map<std::string, long long >>;
auto r = Redis( " tcp://127.0.0.1 " );
auto v = r.command<std::unordered_map<std::string, Var>>( " memory " , " stats " );對Variant支持有一些限制:
Variant的類型參數,不能具有重複的項目,例如Variant<double, long long, double>不起作用。std::string之前放置double 。因為double答复實際上是字符串答复,並且在解析變體時,我們試圖將答复解析為第一個匹配的類型,並用從左到右的類型參數指定。因此,如果在std::string之後放置double ,即在std::string的右側放置,則答復將始終分解為std::string 。還要檢查通用命令部分,以獲取有關通用命令接口的更多示例。
使用通用命令接口時,您還可以將其解析為輸出迭代器,而是可以將其解析為STL容器。
auto r = Redis( " tcp://127.0.0.1 " );
auto v = r.command<std::unordered_map<std::string, std::string>>( " config " , " get " , " * " );還要檢查通用命令部分,以獲取有關通用命令接口的更多示例。
讓我們看看一些有關如何將命令發送到Redis服務器的示例。
// ***** Parameters of StringView type *****
// Implicitly construct StringView with c-style string.
redis.set( " key " , " value " );
// Implicitly construct StringView with std::string.
std::string key ( " key " );
std::string val ( " value " );
redis.set(key, val);
// Explicitly pass StringView as parameter.
std::vector< char > large_data;
// Avoid copying.
redis.set( " key " , StringView(large_data.data(), large_data.size()));
// ***** Parameters of long long type *****
// For index.
redis.bitcount(key, 1 , 3 );
// For number.
redis.incrby( " num " , 100 );
// ***** Parameters of double type *****
// For score.
redis.zadd( " zset " , " m1 " , 2.5 );
redis.zadd( " zset " , " m2 " , 3.5 );
redis.zadd( " zset " , " m3 " , 5 );
// For (longitude, latitude).
redis.geoadd( " geo " , std::make_tuple( " member " , 13.5 , 15.6 ));
// ***** Time-related parameters *****
using namespace std ::chrono ;
redis.expire(key, seconds( 1000 ));
auto tp = time_point_cast<seconds>(system_clock::now() + seconds( 100 ));
redis.expireat(key, tp);
// ***** Some options for commands *****
if (redis.set(key, " value " , milliseconds( 100 ), UpdateType::NOT_EXIST)) {
std::cout << " set OK " << std::endl;
}
redis.linsert( " list " , InsertPosition::BEFORE, " pivot " , " val " );
std::vector<std::string> res;
// (-inf, inf)
redis.zrangebyscore( " zset " , UnboundedInterval< double >{}, std::back_inserter(res));
// [3, 6]
redis.zrangebyscore( " zset " ,
BoundedInterval< double >( 3 , 6 , BoundType::CLOSED),
std::back_inserter (res));
// (3, 6]
redis.zrangebyscore( " zset " ,
BoundedInterval< double >( 3 , 6 , BoundType::LEFT_OPEN),
std::back_inserter (res));
// (3, 6)
redis.zrangebyscore( " zset " ,
BoundedInterval< double >( 3 , 6 , BoundType::OPEN),
std::back_inserter (res));
// [3, 6)
redis.zrangebyscore( " zset " ,
BoundedInterval< double >( 3 , 6 , BoundType::RIGHT_OPEN),
std::back_inserter (res));
// [3, +inf)
redis.zrangebyscore( " zset " ,
LeftBoundedInterval< double >( 3 , BoundType::RIGHT_OPEN),
std::back_inserter (res));
// (3, +inf)
redis.zrangebyscore( " zset " ,
LeftBoundedInterval< double >( 3 , BoundType::OPEN),
std::back_inserter (res));
// (-inf, 6]
redis.zrangebyscore( " zset " ,
RightBoundedInterval< double >( 6 , BoundType::LEFT_OPEN),
std::back_inserter (res));
// (-inf, 6)
redis.zrangebyscore( " zset " ,
RightBoundedInterval< double >( 6 , BoundType::OPEN),
std::back_inserter (res));
// ***** Pair of iterators *****
std::vector<std::pair<std::string, std::string>> kvs = {{ " k1 " , " v1 " }, { " k2 " , " v2 " }, { " k3 " , " v3 " }};
redis.mset(kvs.begin(), kvs.end());
std::unordered_map<std::string, std::string> kv_map = {{ " k1 " , " v1 " }, { " k2 " , " v2 " }, { " k3 " , " v3 " }};
redis.mset(kv_map.begin(), kv_map.end());
std::unordered_map<std::string, std::string> str_map = {{ " f1 " , " v1 " }, { " f2 " , " v2 " }, { " f3 " , " v3 " }};
redis.hmset( " hash " , str_map.begin(), str_map.end());
std::unordered_map<std::string, double > score_map = {{ " m1 " , 20 }, { " m2 " , 12.5 }, { " m3 " , 3.14 }};
redis.zadd( " zset " , score_map.begin(), score_map.end());
std::vector<std::string> keys = { " k1 " , " k2 " , " k3 " };
redis.del(keys.begin(), keys.end());
// ***** Parameters of initializer_list type *****
redis.mset({
std::make_pair ( " k1 " , " v1 " ),
std::make_pair ( " k2 " , " v2 " ),
std::make_pair ( " k3 " , " v3 " )
});
redis.hmset( " hash " ,
{
std::make_pair ( " f1 " , " v1 " ),
std::make_pair ( " f2 " , " v2 " ),
std::make_pair ( " f3 " , " v3 " )
});
redis.zadd( " zset " ,
{
std::make_pair ( " m1 " , 20.0 ),
std::make_pair ( " m2 " , 34.5 ),
std::make_pair ( " m3 " , 23.4 )
});
redis.del({ " k1 " , " k2 " , " k3 " }); // ***** Return void *****
redis.save();
// ***** Return std::string *****
auto info = redis.info();
// ***** Return bool *****
if (!redis.expire( " nonexistent " , std::chrono::seconds( 100 ))) {
std::cerr << " key doesn't exist " << std::endl;
}
if (redis.setnx( " key " , " val " )) {
std::cout << " set OK " << std::endl;
}
// ***** Return long long *****
auto len = redis.strlen( " key " );
auto num = redis.del({ " a " , " b " , " c " });
num = redis.incr( " a " );
// ***** Return double *****
auto real = redis.incrbyfloat( " b " , 23.4 );
real = redis.hincrbyfloat( " c " , " f " , 34.5 );
// ***** Return Optional<std::string>, i.e. OptionalString *****
auto os = redis.get( " kk " );
if (os) {
std::cout << *os << std::endl;
} else {
std::cerr << " key doesn't exist " << std::endl;
}
os = redis.spop( " set " );
if (os) {
std::cout << *os << std::endl;
} else {
std::cerr << " set is empty " << std::endl;
}
// ***** Return Optional<long long>, i.e. OptionalLongLong *****
auto oll = redis.zrank( " zset " , " mem " );
if (oll) {
std::cout << " rank is " << *oll << std::endl;
} else {
std::cerr << " member doesn't exist " << std::endl;
}
// ***** Return Optional<double>, i.e. OptionalDouble *****
auto ob = redis.zscore( " zset " , " m1 " );
if (ob) {
std::cout << " score is " << *ob << std::endl;
} else {
std::cerr << " member doesn't exist " << std::endl;
}
// ***** Return Optional<pair<string, string>> *****
auto op = redis.blpop({ " list1 " , " list2 " }, std::chrono::seconds( 2 ));
if (op) {
std::cout << " key is " << op-> first << " , value is " << op-> second << std::endl;
} else {
std::cerr << " timeout " << std::endl;
}
// ***** Output iterators *****
std::vector<OptionalString> os_vec;
redis.mget({ " k1 " , " k2 " , " k3 " }, std::back_inserter(os_vec));
std::vector<std::string> s_vec;
redis.lrange( " list " , 0 , - 1 , std::back_inserter(s_vec));
std::unordered_map<std::string, std::string> hash;
redis.hgetall( " hash " , std::inserter(hash, hash.end()));
// You can also save the result in a vecotr of string pair.
std::vector<std::pair<std::string, std::string>> hash_vec;
redis.hgetall( " hash " , std::back_inserter(hash_vec));
std::unordered_set<std::string> str_set;
redis.smembers( " s1 " , std::inserter(str_set, str_set.end()));
// You can also save the result in a vecotr of string.
s_vec.clear();
redis.smembers( " s1 " , std::back_inserter(s_vec));sw::redis::Cursor cursor = 0 ;
auto pattern = " *pattern* " ;
auto count = 5 ;
std::unordered_set<std::string> keys;
while ( true ) {
cursor = redis. scan (cursor, pattern, count, std::inserter (keys, keys. begin ()));
// Default pattern is "*", and default count is 10
// cursor = redis.scan(cursor, std::inserter(keys, keys.begin()));
if (cursor == 0 ) {
break ;
}
}有時,輸出迭代器的類型決定使用命令發送哪些選項。
// If the output iterator is an iterator of a container of string,
// we send *ZRANGE* command without the *WITHSCORES* option.
std::vector<std::string> members;
redis.zrange( " list " , 0 , - 1 , std::back_inserter(members));
// If it's an iterator of a container of a <string, double> pair,
// we send *ZRANGE* command with *WITHSCORES* option.
std::vector<std::pair<std::string, double >> res_with_score;
redis.zrange( " list " , 0 , - 1 , std::back_inserter(res_with_score));
// The above examples also apply to other command with the *WITHSCORES* options,
// e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, *ZREVRANGEBYSCORE*.
// Another example is the *GEORADIUS* command.
// Only get members.
members.clear();
redis.georadius( " geo " ,
std::make_pair ( 10.1 , 11.1 ),
100,
GeoUnit::KM,
10,
true,
std::back_inserter(members));
// If the iterator is an iterator of a container of tuple<string, double>,
// we send the *GEORADIUS* command with *WITHDIST* option.
std::vector<std::tuple<std::string, double >> mem_with_dist;
redis.georadius( " geo " ,
std::make_pair ( 10.1 , 11.1 ),
100,
GeoUnit::KM,
10,
true,
std::back_inserter(mem_with_dist));
// If the iterator is an iterator of a container of tuple<string, double, string>,
// we send the *GEORADIUS* command with *WITHDIST* and *WITHHASH* options.
std::vector<std::tuple<std::string, double , std::string>> mem_with_dist_hash;
redis.georadius( " geo " ,
std::make_pair ( 10.1 , 11.1 ),
100,
GeoUnit::KM,
10,
true,
std::back_inserter(mem_with_dist_hash));
// If the iterator is an iterator of a container of
// tuple<string, string, pair<double, double>, double>,
// we send the *GEORADIUS* command with *WITHHASH*, *WITHCOORD* and *WITHDIST* options.
std::vector<std::tuple<std::string, double , std::string>> mem_with_hash_coord_dist;
redis.georadius( " geo " ,
std::make_pair ( 10.1 , 11.1 ),
100,
GeoUnit::KM,
10,
true,
std::back_inserter(mem_with_hash_coord_dist));請參閱redis.h獲取Doxygen樣式的API參考和示例,並查看其他示例的測試。
Redis引發異常,如果它收到錯誤回复或發生錯誤的事情,例如無法創建與服務器的連接,或者與服務器的連接斷開。從Error類得出的所有異常。有關詳細信息,請參見錯誤。
Error :通用錯誤。它源自std::exception ,也是其他例外的基類。IoError :連接有一些IO錯誤。TimeoutError :讀取或寫操作的時間已計時。這是派生的IoError類。ClosedError :REDIS服務器關閉了連接。ProtoError :命令或答复無效,我們無法使用REDIS協議進行處理。OomError : HIRDIS庫遇到了一個遺忘的錯誤。ReplyError :Redis Server返回了錯誤回复,例如,我們嘗試在Redis Hash上調用redis::lrange 。WatchError :觀看鍵已被修改。有關詳細信息,請參見觀看部分。注意: Null答复不被視為例外。例如,如果我們嘗試GET不存在的密鑰,我們將獲得一個空的散裝字符串回复。我們沒有拋出異常,而是將null回复返回為null Optional<T>對象。另請參見可選部分。
通常,當發生例外時,您無需創建Redis對象。這是例外的安全,您可以重複使用Redis對象。即使與Redis服務器的連接斷開,也會引發一些IoError 。下次使用Redis對象發送命令時,它將嘗試自動重新連接到redis服務器。此規則也適用於RedisCluster 。但是,如果Pipeline , Transcation和Subscriber拋出異常,則需要銷毀對象,並創建一個新的對象。有關詳細信息,請參見相應的文檔。
以下是如何捕獲以下例外的一個示例:
try {
redis. set ( " key " , " value " );
// Wrong type error
redis. lpush ( " key " , { " a " , " b " , " c " });
} catch ( const ReplyError &err) {
// WRONGTYPE Operation against a key holding the wrong kind of value
cout << err. what () << endl;
} catch ( const TimeoutError &err) {
// reading or writing timeout
} catch ( const ClosedError &err) {
// the connection has been closed.
} catch ( const IoError &err) {
// there's an IO error on the connection.
} catch ( const Error &err) {
// other errors
}Redis命令太多了,我們還沒有實現所有命令。但是,您可以使用通用Redis::command方法將任何命令發送到redis。與其他客戶端庫不同, Redis::command不使用格式字符串將命令參數組合到命令字符串中。相反,您可以將StringView類型或算術類型的命令參數直接傳遞為Redis::command的參數。由於我們不使用格式字符串的原因,請參閱此討論。
auto redis = Redis( " tcp://127.0.0.1 " );
// Redis class doesn't have built-in *CLIENT SETNAME* method.
// However, you can use Redis::command to send the command manually.
redis.command< void >( " client " , " setname " , " name " );
auto val = redis.command<OptionalString>( " client " , " getname " );
if (val) {
std::cout << *val << std::endl;
}
// NOTE: the following code is for example only. In fact, Redis has built-in
// methods for the following commands.
// Arguments of the command can be strings.
// NOTE: for SET command, the return value is NOT always void, I'll explain latter.
redis.command< void >( " set " , " key " , " 100 " );
// Arguments of the command can be a combination of strings and integers.
auto num = redis.command< long long >( " incrby " , " key " , 1 );
// Argument can also be double.
auto real = redis.command< double >( " incrbyfloat " , " key " , 2.3 );
// Even the key of the command can be of arithmetic type.
redis.command< void >( " set " , 100 , " value " );
val = redis.command<OptionalString>( " get " , 100 );
// If the command returns an array of elements.
std::vector<OptionalString> result;
redis.command( " mget " , " k1 " , " k2 " , " k3 " , std::back_inserter(result));
// Or just parse it into a vector.
result = redis.command<std::vector<OptionalString>>( " mget " , " k1 " , " k2 " , " k3 " );
// Arguments of the command can be a range of strings.
auto set_cmd_strs = { " set " , " key " , " value " };
redis.command< void >(set_cmd_strs.begin(), set_cmd_strs.end());
auto get_cmd_strs = { " get " , " key " };
val = redis.command<OptionalString>(get_cmd_strs.begin(), get_cmd_strs.end());
// If it returns an array of elements.
result.clear();
auto mget_cmd_strs = { " mget " , " key1 " , " key2 " };
redis.command(mget_cmd_strs.begin(), mget_cmd_strs.end(), std::back_inserter(result));注意:某些redis命令的名稱由兩個字符串組成,例如客戶端setName 。在這種情況下,您需要將這兩個字符串作為Redis::command的兩個參數。
// This is GOOD.
redis.command< void >( " client " , " setname " , " name " );
// This is BAD, and will fail to send command to Redis server.
// redis.command<void>("client setname", "name");正如我在評論中提到的那樣, SET命令並不總是返回void 。因為如果您嘗試使用NX或XX選項設置(鍵,值)對,則可能會失敗,Redis將返回null回复。除了SET命令外,還有其他命令的返回值不是固定類型,您還需要自己解析。例如, Redis::set方法重寫SET命令的回复,然後使其返回bool類型,即如果未指定NX或XX選項,Redis Server始終將返回“ OK”字符串, Redis::set returns返回true ;如果指定了NX或XX ,並且Redis Server返回null回复, Redis::set返回false 。
因此, Redis類還具有其他重載command方法,這些方法返回回复ReplyUPtr ,ie std::unique_ptr<redisReply, ReplyDeleter> ,對象。通常,您無需手動解析它。取而代之的是,您只需要將回复傳遞到template <typename T> T reply::parse(redisReply &)以獲取T型的值。檢查有效T類型的返回類型部分。如果命令返回一系列元素,除了調用reply::parse以解析對STL容器的答复外,您還可以調用template <typename Output> reply::to_array(redisReply &reply, Output output)將結果解析為帶有輸出迭代器的數組或STL容器。
讓我們重寫上述示例:
auto redis = Redis( " tcp://127.0.0.1 " );
redis.command( " client " , " setname " , " name " );
auto r = redis.command( " client " , " getname " );
assert (r);
// If the command returns a single element,
// use `reply::parse<T>(redisReply&)` to parse it.
auto val = reply::parse<OptionalString>(*r);
if (val) {
std::cout << *val << std::endl;
}
// Arguments of the command can be strings.
redis.command( " set " , " key " , " 100 " );
// Arguments of the command can be a combination of strings and integers.
r = redis.command( " incrby " , " key " , 1 );
auto num = reply::parse< long long >(*r);
// Argument can also be double.
r = redis.command( " incrbyfloat " , " key " , 2.3 );
auto real = reply::parse< double >(*r);
// Even the key of the command can be of arithmetic type.
redis.command( " set " , 100 , " value " );
r = redis.command( " get " , 100 );
val = reply::parse<OptionalString>(*r);
// If the command returns an array of elements.
r = redis.command( " mget " , " k1 " , " k2 " , " k3 " );
// Use `reply::to_array(redisReply&, OutputIterator)` to parse the result into an STL container.
std::vector<OptionalString> result;
reply::to_array (*r, std::back_inserter(result));
// Or just call `reply::parse` to parse it into vector.
result = reply::parse<std::vector<OptionalString>>(*r);
// Arguments of the command can be a range of strings.
auto get_cmd_strs = { " get " , " key " };
r = redis.command(get_cmd_strs.begin(), get_cmd_strs.end());
val = reply::parse<OptionalString>(*r);
// If it returns an array of elements.
result.clear();
auto mget_cmd_strs = { " mget " , " key1 " , " key2 " };
r = redis.command(mget_cmd_strs.begin(), mget_cmd_strs.end());
reply::to_array (*r, std::back_inserter(result));實際上,還有一個Redis::command方法:
template < typename Cmd, typename ...Args>
auto command (Cmd cmd, Args &&...args)
-> typename std::enable_if<!std::is_convertible<Cmd, StringView>::value, ReplyUPtr>::type;但是,此方法揭示了一些實現細節,僅用於內部使用。您不應使用此方法。
您可以使用Redis::publish將消息發佈到頻道。 Redis從基礎連接池隨機選擇連接,並發布該連接的消息。因此,您可以發布兩個具有兩個不同連接的消息。
當您訂閱具有連接的頻道時,所有發佈到頻道的消息都將發送回該連接。因此,沒有Redis::subscribe方法。取而代之的是,您可以調用Redis::subscriber來創建Subscriber ,並且Subscriber保持與Redis的連接。基礎連接是一個新連接,不是從連接池中挑選的。該新連接具有與Redis對象相同的ConnectionOptions 。
如果要具有不同的連接選項,例如ConnectionOptions::socket_timeout ,對於不同的頻道,則應使用不同的連接選項創建Redis對象,則可以使用這些Redis對象創建Subscriber對象。檢查此問題的用例。
ConnectionOptions opts1;
opts1.host = " 127.0.0.1 " ;
opts1.port = 6379 ;
opts1.socket_timeout = std::chrono::milliseconds( 100 );
auto redis1 = Redis(opts1);
// sub1's socket_timeout is 100ms.
auto sub1 = redis1.subscriber();
ConnectionOptions opts2;
opts2.host = " 127.0.0.1 " ;
opts2.port = 6379 ;
opts2.socket_timeout = std::chrono::milliseconds( 300 );
auto redis2 = Redis(opts2);
// sub2's socket_timeout is 300ms.
auto sub2 = redis2.subscriber();注意:儘管以上代碼會產生兩個Redis對象,但沒有性能懲罰。因為Redis對象會懶惰地創建連接,所以直到我們發送一些帶有Redis對象的命令並在我們調用Redis::subscriber創建Subscriber對象時才會創建連接。
使用Subscriber ,您可以致電Subscriber::subscribe , Subscriber::unsubscribe , Subscriber::psubscribe和Subscriber::punsubscribe以發送訂閱,取消訂閱,訂閱, psubscribe和punsubscribe命令到redis。
Subscriber不是線程安全。如果要在多線程環境中調用其成員功能,則需要手動在線程之間同步。
如果Subscriber的方法中的任何一個都會引發其他異常,則除了ReplyError或TimeoutError ,您將不再使用它。相反,您必須銷毀Subscriber對象,並創建一個新的對象。
有6種消息:
我們將訂閱,取消訂閱, psubscribe和punsubscribe類型的消息稱為元消息。
為了處理這些消息,您可以在Subscriber上設置回調函數:
Subscriber::on_message(MsgCallback) :消息類型的消息設置回調函數,回調接口為: void (std::string channel, std::string msg) 。Subscriber::on_pmessage(PatternMsgCallback) :設置pmessage類型消息的回調函數,而回調接口為: void (std::string pattern, std::string channel, std::string msg) 。Subscriber::on_meta(MetaCallback) :設置元消息類型消息的回調函數,回調接口為: void (Subscriber::MsgType type, OptionalString channel, long long num) 。 type is an enum, it can be one of the following enum: Subscriber::MsgType::SUBSCRIBE , Subscriber::MsgType::UNSUBSCRIBE , Subscriber::MsgType::PSUBSCRIBE , Subscriber::MsgType::PUNSUBSCRIBE , Subscriber::MsgType::MESSAGE , and Subscriber::MsgType::PMESSAGE .如果您尚未訂閱/psubScribe任何頻道/模式,並嘗試在沒有任何參數的情況下取消訂閱/punsubscribe,即退出/punsubScribe所有頻道/模式,頻道將為null。因此,元回調的第二個參數是類型OptionalString 。所有這些回調接口按值通過std::string ,您可以安全地掌握其所有權(即std::move )。
您可以調用Subscriber::consume將發布的消息消耗到已訂閱的Subscriber的頻道/模式中。
Subscriber::consume等待基礎連接的消息。如果ConnectionOptions::socket_timeout已達到此連接,則沒有發送到此連接的消息, Subscriber::consume會引發TimeoutError異常。如果ConnectionOptions::socket_timeout為0ms , Subscriber::consume塊,直到收到消息為止。
收到消息後, Subscriber::consume調用回調函數以基於消息類型處理消息。但是,如果您沒有為特定的消息設置回調, Subscriber::consume會消耗收到的消息並將其丟棄,即Subscriber::consume返回而無需運行回調。
以下示例是使用Subscriber的常見模式:
// Create a Subscriber.
auto sub = redis.subscriber();
// Set callback functions.
sub.on_message([](std::string channel, std::string msg) {
// Process message of MESSAGE type.
});
sub.on_pmessage([](std::string pattern, std::string channel, std::string msg) {
// Process message of PMESSAGE type.
});
sub.on_meta([](Subscriber::MsgType type, OptionalString channel, long long num) {
// Process message of META type.
});
// Subscribe to channels and patterns.
sub.subscribe( " channel1 " );
sub.subscribe({ " channel2 " , " channel3 " });
sub.psubscribe( " pattern1* " );
// Consume messages in a loop.
while ( true ) {
try {
sub. consume ();
} catch ( const Error &err) {
// Handle exceptions.
}
}如果設置了ConnectionOptions::socket_timeout ,則在收到消息之前可能會獲得TimeoutError異常:
while ( true ) {
try {
sub. consume ();
} catch ( const TimeoutError &e) {
// Try again.
continue ;
} catch ( const Error &err) {
// Handle other exceptions.
}
}以上示例使用lambda作為回調。如果您不熟悉lambda,也可以將免費功能設置為回調。檢查此問題以獲取細節。
管道用於減少RTT (往返時間),並加快REDIS查詢。 Redis-Plus-Plus支持Pipeline類別的管道。
您可以使用Redis::pipeline方法創建管道,該方法返回Pipeline對象。
ConnectionOptions connection_options;
ConnectionPoolOptions pool_options;
Redis redis (connection_options, pool_options);
auto pipe = redis.pipeline();當創建Pipeline對象時,默認情況下, Redis::pipeline方法會創建與Redis服務器的新連接。該連接不是從連接池中選擇的,而是新創建的連接。該連接與連接池中其他ConnectionOptions相同。 Pipeline對象維護新連接,並且所有管道命令均通過此連接發送。
注意:默認情況下,創建Pipeline對象並不便宜,因為它會創建新的連接。因此,您最好盡可能地重複使用Pipeline對象。檢查一下以查看如何在不創建新連接的情況下創建Pipeline對象。
您可以通過Pipeline對象發送redis命令。就像Redis類一樣, Pipeline對每個redis命令都有一種或多種(超載)方法。但是,在致電Pipeline::exec之前,您無法獲得答复。因此,這些方法不會返回答复,而是返回Pipeline對象本身。您可以鏈接這些方法調用。
pipe.set( " key " , " val " ).incr( " num " ).rpush( " list " , { 0 , 1 , 2 }).command( " hset " , " key " , " field " , " value " );完成將命令發送到REDIS後,您可以致電Pipeline::exec獲取這些命令的答复。您還可以帶有其他命令鏈條Pipeline::exec 。
pipe.set( " key " , " val " ).incr( " num " );
auto replies = pipe.exec();
// The same as:
replies = pipe.set( " key " , " val " ).incr( " num " ).exec();實際上,在您致電Pipeline::exec之前,這些命令不會發送到Redis。因此, Pipeline::exec進行2次工作:發送所有管道命令,然後從redis獲取所有答复。
另外,您可以致電Pipeline::discard以丟棄那些管道的命令。
pipe.set( " key " , " val " ).incr( " num " );
pipe.discard();Pipeline::exec返回一個QueuedReplies對象,其中包含已發送給Redis的所有命令的答复。您可以使用QueuedReplies::get方法獲取和解析ith回复。它有3個超載:
template <typename Result> Result get(std::size_t idx) :返回ith回復為返回值,您需要將返回類型指定為tempalte參數。template <typename Output> void get(std::size_t idx, Output output) :如果回復為type數組回复,則可以調用此方法將ith響應寫入輸出迭代器。通常,編譯器將推論輸出迭代器的類型,並且您無需明確指定類型參數。redisReply& get(std::size_t idx) :如果答復不是固定類型,請調用此方法以獲取對redisReply對象的引用。在這種情況下,您需要調用template <typename T> T reply::parse(redisReply &)手動解析答复。檢查返回類型部分以獲取有關結果的返回類型的詳細信息。
auto replies = pipe.set( " key " , " val " ).incr( " num " ).lrange( " list " , 0 , - 1 ).exec();
auto set_cmd_result = replies.get< bool >( 0 );
auto incr_cmd_result = replies.get< long long >( 1 );
std::vector<std::string> list_cmd_result;
replies.get( 2 , std::back_inserter(list_cmd_result));如果Pipeline方法中的任何一個都會引發其他ReplyError , Pipeline對象進入無效狀態。您不能再使用它,而只能破壞該對象,並創建一個新對象。
Pipeline不是線程安全。如果要在多線程環境中調用其成員功能,則需要手動在線程之間同步。
在使用此功能之前,您必須仔細閱讀本節中的所有單詞和非常重要的註釋!!!
實際上,您還可以創建一個帶有基礎連接池的連接的Pipeline對象,因此調用Redis::pipeline方法可以便宜得多(因為它不需要創建新連接)。
Redis::pipeline的原型如下: Pipeline pipeline(bool new_connection = true); 。如果new_connection為false,則將使用從基礎池的連接創建Pipeline對象。
ConnectionOptions connection_options;
ConnectionPoolOptions pool_options;
Redis redis (connection_options, pool_options);
// Create a Pipeline without creating a new connection.
auto pipe = redis.pipeline( false );但是,在這種情況下,您必須非常小心,否則,您的性能可能會糟糕甚至鎖定。因為當您使用Pipeline對象運行命令時,它將保持連接,直到Pipeline::exec , Pipeline::discard或Pipeline的destructor被調用(如果任何Pipeline拋出Exception的方法,也將發布連接)。如果Pipeline對象長時間保持連接,則其他Redis方法可能無法從基礎池獲得連接。
檢查以下死鎖示例:
// By defaul, create a `Redis` object with only ONE connection in pool.
// Also by default, the `ConnectionPoolOptions::wait_timeout` is 0ms,
// which means if the pool is empty, `Redis` method will be blocked until
// the pool is not empty.
Redis redis ( " tcp://127.0.0.1 " );
// Create a `Pipeline` with a connection in the underlying pool.
// In fact, the connection hasn't been fetched from the pool
// until some method of `Pipeline` has been called.
auto pipe = redis.pipeline( false );
// Now the `Pipeline` object fetches a connection from the pool.
pipe.set( " key1 " , " val " );
// `Pipeline` object still holds the connection until `Pipeline::exec`,
// `Pipeline::discard` or the destructor is called.
pipe.set( " key2 " , " val " );
// Try to send a command with `Redis` object.
// However, the pool is empty, since the `Pipeline` object still holds
// the connection, and this call will be blocked forever.
// DEAD LOCK!!!
redis.get( " key " );
// NEVER goes here.
pipe.exec();最佳實踐:
在不創建新連接的情況下創建Pipeline時:
ConnectionPoolOptions::wait_timeout大於0ms(即池為空時,永遠不會永遠阻止)。Pipeline的方法之間進行緩慢的操作。Pipeline方法和Pipeline::exec在一個語句中。Pipeline相關的代碼留在塊範圍內。 ConnectionOptions opts;
opts.host = " 127.0.0.1 " ;
opts.port = 6379 ;
opts.socket_timeout = std::chrono::milliseconds( 50 );
ConnectionPoolOptions pool_opts;
pool_opts.size = 3 ;
// Always set `wait_timeout` larger than 0ms.
pool_opts.wait_timeout = std::chrono::milliseconds( 50 );
auto redis = Redis(opts, pool_opts);
{
// Better put `Pipeline` related code in a block scope.
auto pipe = redis. pipeline ( false );
pipe . set ( " key1 " , " val " );
// DON'T run slow operations here, since `Pipeline` object still holds
// the connection, other threads using this `Redis` object, might be blocked.
pipe . set ( " key2 " , " val " );
// When `Pipeline::exec` finishes, `Pipeline` releases the connection, and returns it to pool.
auto replies = pipe . exec ();
// This is even better, i.e. chain `Pipeline` methods with `Pipeline::exec`.
replies = pipe . set ( " key1 " , " val " ). set ( " key2 " , " val " ). exec ();
}
for ( auto i = 0 ; i < 10 ; ++i) {
// This operation, i.e. creating a `Pipeline` object with connection in pool, is cheap
auto pipe = redis. pipeline ( false );
// Fetch a connection from the underlying pool, and hold it.
pipe . set ( " key1 " , " val " ). set ( " key2 " , " val " );
// Although `Pipeline::exec` and `Pipeline::discard` haven't been called,
// when `Pipeline`'s destructor is called, the connection will also be
// returned to the pool.
}事務用於使多個命令在原子上運行。
您可以使用Redis::transaction方法創建交易,該方法返回Transaction對象。
ConnectionOptions connection_options;
ConnectionPoolOptions pool_options;
Redis redis (connection_options, pool_options);
auto tx = redis.transaction();作為Pipeline類, Transaction將保持與Redis的新創建的連接。該連接具有與Redis對象相同的ConnectionOptions 。
注意:創建Transaction對象並不便宜,因為它會創建一個新的連接。因此,您最好盡可能多地重複Transaction 。檢查一下以查看如何在不創建新連接的情況下創建Transaction對象。
另外,您無需將多命令發送到Redis。 Transaction將為您自動做到這一點。
Transaction與Pipeline共享大部分實施。它具有與Pipeline相同的接口。您可以將命令發送為對Pipeline對象的操作。
tx.set( " key " , " val " ).incr( " num " ).lpush( " list " , { 0 , 1 , 2 }).command( " hset " , " key " , " field " , " val " );調用Transaction::exec時,您會明確要求Redis執行這些排隊的命令,然後返回答复。否則,這些命令將不會執行。另外,您可以致電Transaction::discard以丟棄執行,即不會執行任何命令。 Transaction::exec and Transaction::discard可以用其他命令鏈接。
auto replies = tx.set( " key " , " val " ).incr( " num " ).exec();
tx.set( " key " , " val " ).incr( " num " );
// Discard the transaction.
tx.discard();有關如何解析答复,請參見Pipeline的解析答复部分。
通常,我們總是在交易中發送多個commnds。為了提高性能,您可以在管道中發送這些命令。您可以通過將true作為Redis::transaction方法的參數來創建管道交易。
// Create a piped transaction
auto tx = redis.transaction( true );通過這項管道交易,所有命令都會在管道中發送到Redis。
如果任何Transaction的方法都會引發除WatchError或ReplyError以外的其他例外,則Transaction對象將進入無效狀態。您不能再使用它,而只能破壞對象並創建一個新對象。
Transacation不是線程安全。如果要在多線程環境中調用其成員功能,則需要手動在線程之間同步。
手錶用於為REDIS交易提供檢查和設置的行為。
WATCH命令必須以與交易相同的連接發送。通常,在WATCH命令之後,我們還需要發送其他一些命令以在執行交易之前從Redis獲取數據。以以下支票和設置案例為例:
WATCH key // watch a key
val = GET key // get value of the key
new_val = val + 1 // incr the value
MULTI // begin the transaction
SET key new_val // set value only if the value is NOT modified by others
EXEC // try to execute the transaction.
// if val has been modified, the transaction won't be executed.
但是,使用Transaction對象,在整個事務完成之前,您無法獲得命令的結果。相反,您需要從Transaction對象創建一個Redis對象。創建的Redis對象與Transaction對象共享連接。使用此創建的Redis對象,您可以將WATCH命令和任何其他REDIS命令發送到REDIS服務器,並立即獲得結果。
讓我們看看如何使用redis-plus-plus實現上述示例:
auto redis = Redis( " tcp://127.0.0.1 " );
// Create a transaction.
auto tx = redis.transaction();
// Create a Redis object from the Transaction object. Both objects share the same connection.
auto r = tx.redis();
// If the watched key has been modified by other clients, the transaction might fail.
// So we need to retry the transaction in a loop.
while ( true ) {
try {
// Watch a key.
r. watch ( " key " );
// Get the old value.
auto val = r. get ( " key " );
auto num = 0 ;
if (val) {
num = std::stoi (*val);
} // else use default value, i.e. 0.
// Incr value.
++num;
// Execute the transaction.
auto replies = tx. set ( " key " , std::to_string (num)). exec ();
// Transaction has been executed successfully. Check the result and break.
assert (replies. size () == 1 && replies. get < bool >( 0 ) == true );
break ;
} catch ( const WatchError &err) {
// Key has been modified by other clients, retry.
continue ;
} catch ( const Error &err) {
// Something bad happens, and the Transaction object is no longer valid.
throw ;
}
}注意:在上面的示例中,我們在While循環外部創建Transaction對象,以避免一次又一次創建新連接。
注意:在使用此功能之前,您必須仔細閱讀本節中的所有單詞和非常重要的註釋鏈接!!!
實際上,您還可以通過從基礎連接池進行連接創建一個transaction對象,因此調用Redis::transaction方法可以便宜得多(因為它不需要創建新連接)。
Redis::transaction的原型如下: Transaction transaction(bool piped = false, bool new_connection = true); 。如果new_connection為false,則將通過基礎池的連接創建Transaction對象。
ConnectionOptions connection_options;
ConnectionPoolOptions pool_options;
Redis redis (connection_options, pool_options);
// Create a Transaction without creating a new connection.
auto tx = redis.transaction( false , false );但是,在這種情況下,您必須非常小心,否則,您的性能可能會糟糕甚至鎖定。在使用之前,請仔細檢查類似管道非常重要的筆記部分!
除了這些非常重要的註釋, Transaction還有另一個重要說明:
Transaction::Redis創建的Redis對象的範圍,即盡快將其銷毀。檢查以下示例:
auto redis = Redis(opts, pool_opts);
// Create a `Transaction` object without creating a new connection.
auto tx = redis.Transaction( false , false );
// Create a `Redis`, and this `Redis` object shares the same connection with the `Transaction` object.
auto r = tx.redis();
// Other code here...
// Execute the transaction.
auto replies = tx.set( " key " , " val " ).exec();
// Although `Transaction::exec` has been called, the connection has not been returned to pool.
// Because the `Redis` object, i.e. `r`, still holds the connection.因此,上面的手錶示例應如下修改:
auto redis = Redis(opts, pool_opts);
// If the watched key has been modified by other clients, the transaction might fail.
// So we need to retry the transaction in a loop.
while ( true ) {
try {
// Create a transaction without creating a new connection.
auto tx = redis. transaction ( false , false );
// Create a Redis object from the Transaction object. Both objects share the same connection.
auto r = tx. redis ();
// Watch a key.
r. watch ( " key " );
// Get the old value.
auto val = r. get ( " key " );
auto num = 0 ;
if (val) {
num = std::stoi (*val);
} // else use default value, i.e. 0.
// Incr value.
++num;
// Execute the transaction.
auto replies = tx. set ( " key " , std::to_string (num)). exec ();
// Transaction has been executed successfully. Check the result and break.
assert (replies. size () == 1 && replies. get < bool >( 0 ) == true );
break ;
} catch ( const WatchError &err) {
// Key has been modified by other clients, retry.
continue ;
} catch ( const Error &err) {
// Something bad happens, and the Transaction object is no longer valid.
throw ;
}
}注意:不同之處在於,我們在while循環中創建Transaction對象(這很便宜,因為它不需要創建新連接)。當Transaction對象和由Transaction::redis創建的Redis對像被破壞時,連接將返回到池。
REDIS-Plus-Plus支持REDIS群集。您可以使用RedisCluster類將命令發送到REDIS群集。它具有與Redis類相似的接口。
默認情況下, RedisCluster連接到集群中的所有主節點。對於每個主節點,它都維護一個連接池。如果要從從屬節點中閱讀,則需要明確設置一個選項(參考請參見下文)。
您可以使用ConnectionOptions和ConnectionPoolOptions初始化RedisCluster實例。您只需要在ConnectionOptions中設置一個主節點的主機和端口, RedisCluster將自動獲取其他節點的信息(使用cluster插槽命令)。對於每個主節點,它將創建一個帶有指定ConnectionPoolOptions的連接池。如果未指定ConnectionPoolOptions , RedisCluster將保持與每個主節點的單個連接。
// Set a master node's host & port.
ConnectionOptions connection_options;
connection_options.host = " 127.0.0.1 " ; // Required.
connection_options.port = 7000 ; // Optional. The default port is 6379.
connection_options.password = " auth " ; // Optional. No password by default.
// Automatically get other nodes' info,
// and connect to every master node with a single connection.
RedisCluster cluster1 (connection_options);
ConnectionPoolOptions pool_options;
pool_options.size = 3 ;
// For each master node, maintains a connection pool of size 3.
RedisCluster cluster2 (connection_options, pool_options);您還可以使用URI指定連接選項。但是,通過這種方式,您只能使用默認的ConnectionPoolOptions ,即尺寸1的池,並且無法指定密碼。
// Specify a master node's host & port.
RedisCluster cluster3 ( " tcp://127.0.0.1:7000 " );
// Use default port, i.e. 6379.
RedisCluster cluster4 ( " tcp://127.0.0.1 " );如果您想通過從從屬節點讀取(可能的陳舊)數據來擴展讀取,則可以指定Role::SLAVE作為RedisCluster構造函數的第三個參數。在這種情況下, redis-plus-plus將為群集的每個主節點隨機選擇一個副本節點,並為複制節點創建一個連接池。
RedisCluster cluster (connection_options, pool_options, Role::SLAVE);
auto val = cluster.get( " key " );在這種情況下,您只能將ReadOnly命令發送到REDIS群集。如果您嘗試發送寫入命令,例如set , hset , redis-plus-plus將引發異常。當前, redis-plus-plus無法處理這種情況,即以Role::SLAVE模式發送Write命令,優雅,您可能會遇到一些性能問題。因此,切勿在Role::SLAVE模式下發送寫命令。將來我將解決此問題。
注意:在Role::SLAVE模式下,您無需手動將ReadOnly命令發送到從節點。相反, redis-plus-plus將自動發送readonly命令到從。
RedisCluster僅與TCP連接一起使用。它無法連接到Unix域插座。如果您在ConnectionOptions中指定UNIX域套接字,則會引發異常。ConnectionOptions::db被忽略。 如上所述, RedisCluster的界面與Redis相似。它支持Redis的大多數接口,包括通用命令接口(有關詳細信息,請參見Redis的API參考部分),但以下內容:
PING , INFO 。由於沒有密鑰參數,因此RedisCluster不知道應該發送這些命令的節點。但是,有2個解決此問題的解決方法:
Redis對象,並使用Redis對象進行工作。Redis RedisCluster::redis(const StringView &hash_tag) ,而不是主機和端口,以使用指定節點的哈希標籤創建一個Redis對象。在這種情況下,返回的Redis對象會創建與Redis服務器的新連接。注意:返回的Redis對象,不是線程安全! 。另外,當使用返回的Redis對象時,如果它引發異常,則需要銷毀它,並使用RedisCluster::redis方法創建一個新的。另外,您可以使用哈希標籤發送多鍵命令。
有關詳細信息,請參見示例部分。
您可以使用RedisCluster發布和訂閱消息。接口與Redis完全相同,即使用RedisCluster::publish發布消息,並使用RedisCluster::subscriber創建訂戶以消費消息。有關詳細信息,請參見發布/訂閱部分。
您還可以使用RedisCluster創建Pipeline和Transaction對象,但是接口與Redis不同。由於管道中的所有命令和事務中的所有命令均應將單個連接中的單個節點發送到一個單個節點,因此我們需要告訴RedisCluster ,應使用哪個節點來創建管道或事務。
RedisCluster的管道和事務接口不用指定節點的IP和端口,允許您使用哈希標籤指定節點。 RedisCluster將使用給定的哈希標籤計算插槽號,並使用持有插槽的節點創建管道或事務。
Pipeline RedisCluster::pipeline ( const StringView &hash_tag, bool new_connection = true );
Transaction RedisCluster::transaction ( const StringView &hash_tag, bool piped = false , bool new_connection = true );使用創建的Pipeline或Transaction對象,您可以將命令與給定的Hash_tag相同的節點上的鍵發送命令。有關示例,請參見示例部分。
注意:默認情況下,將使用新連接創建, Pipeline和Transaction 。為了避免創建新連接,您可以將false作為最後一個參數。但是,在這種情況下,您必須非常小心,否則,您的性能可能會糟糕甚至鎖定。在使用此功能之前,請仔細檢查相關的管道部分。
# include < sw/redis++/redis++.h >
using namespace sw ::redis ;
auto redis_cluster = RedisCluster( " tcp://127.0.0.1:7000 " );
redis_cluster.set( " key " , " value " );
auto val = redis_cluster.get( " key " );
if (val) {
std::cout << *val << std::endl;
}
// With hash-tag.
redis_cluster.set( " key{tag}1 " , " val1 " );
redis_cluster.set( " key{tag}2 " , " val2 " );
redis_cluster.set( " key{tag}3 " , " val3 " );
std::vector<OptionalString> hash_tag_res;
redis_cluster.mget({ " key{tag}1 " , " key{tag}2 " , " key{tag}3 " },
std::back_inserter (hash_tag_res));
redis_cluster.lpush( " list " , { " 1 " , " 2 " , " 3 " });
std::vector<std::string> list;
redis_cluster.lrange( " list " , 0 , - 1 , std::back_inserter(list));
// Pipeline.
auto pipe = redis_cluster.pipeline( " counter " );
auto replies = pipe.incr( " {counter}:1 " ).incr( " {counter}:2 " ).exec();
// Transaction.
auto tx = redis_cluster.transaction( " key " );
replies = tx.incr( " key " ).get( " key " ).exec();
// Create a Redis object with hash-tag.
// It connects to the Redis instance that holds the given key, i.e. hash-tag.
auto r = redis_cluster.redis( " hash-tag " );
// And send command without key parameter to the server.
r.command( " client " , " setname " , " connection-name " );注意:默認情況下,當您使用RedisCluster::redis(const StringView &hash_tag, bool new_connection = true)創建一個Redis對象時,而不是從基礎連接池中選擇連接,它會創建到相應的redis服務器的新連接。因此,這不是便宜的操作,您應該嘗試盡可能多地重複使用此新創建的Redis對象。如果將false作為第二個參數,則可以創建Redis對象而無需創建新連接。但是,在這種情況下,您應該非常小心,否則,您的性能可能不好,甚至鎖定。在使用此功能之前,請仔細檢查相關的管道部分。
// This is BAD! It's very inefficient.
// NEVER DO IT!!!
// After sending PING command, the newly created Redis object will be destroied.
cluster.redis( " key " ).ping();
// Then it creates a connection to Redis, and closes the connection after sending the command.
cluster.redis( " key " ).command( " client " , " setname " , " hello " );
// Instead you should reuse the Redis object.
// This is GOOD!
auto redis = cluster.redis( " key " );
redis.ping();
redis.command( " client " , " setname " , " hello " );
// This is GOOD! Create `Redis` object without creating a new connection. Use it, and destroy it ASAP.
cluster.redis( " key " , false ).ping();RedisCluster維護最新的插槽節點映射,並將命令直接發送到右節點。通常它的工作速度與Redis一樣快。如果群集重組, RedisCluster將遵循重定向,並最終將更新插槽節點映射。它可以正確處理以下轉換案件:
redis-plus-plus能夠處理移動和詢問重定向,因此它是一個完整的Redis群集客戶端。
如果主人失望,則集群將宣傳其副本之一,成為新的主人。 Redis-Plus-Plus也可以處理這種情況:
由於redis-plus-plus 1.3.13,它還更新了每個ClusterOptions::slot_map_refresh_interval時間間隔(默認情況下,它每10秒更新一次)。
Redis Sentinel為Redis提供了高可用性。如果Redis Master降低了,Redis Sentinels將從奴隸中選舉新的主人,即故障轉移。此外,Redis Sentinel還可以像客戶端的配置提供商一樣行事,並且客戶可以從Redis Sentinel查詢主或從地址。因此,如果發生故障轉移,客戶可以從Redis Sentinel詢問新的主地址。
Redis-Plus-Plus支持從Redis Sentinel獲取Redis Master或Slave的IP和端口。為了使用此功能,您只需要使用Redis Sentinel Info初始化Redis對象,該信息由3個部分組成: std::shared_ptr<Sentinel> ,Master Name and Woy(Master或Slave)。
在使用Redis-Plus-Plus的Redis Sentinel之前,請確保您已閱讀Redis Sentinel的文檔。
您可以使用SentinelOptions創建一個std::shared_ptr<Sentinel>對象。
SentinelOptions sentinel_opts;
sentinel_opts.nodes = {{ " 127.0.0.1 " , 9000 },
{ " 127.0.0.1 " , 9001 },
{ " 127.0.0.1 " , 9002 }}; // Required. List of Redis Sentinel nodes.
// Optional. Timeout before we successfully connect to Redis Sentinel.
// By default, the timeout is 100ms.
sentinel_opts.connect_timeout = std::chrono::milliseconds( 200 );
// Optional. Timeout before we successfully send request to or receive response from Redis Sentinel.
// By default, the timeout is 100ms.
sentinel_opts.socket_timeout = std::chrono::milliseconds( 200 );
auto sentinel = std::make_shared<Sentinel>(sentinel_opts); SentinelOptions::connect_timeout和SentinelOptions::socket_timeout不能為0ms,即沒有超時,並且永遠阻止。否則, Redis-Plus-Plus將引發例外。
有關更多選項,請參見SentinelOptions。
除了std::shared_ptr<Sentinel>和Master名稱外,您還需要指定角色。有兩個角色: Role::MASTER和Role::SLAVE 。
有了Role::MASTER , Redis-Plus-Plus即使發生故障轉移也將始終連接到當前的主實例。每次redis-plus-plus都需要建立與Master的新連接或斷開連接的新連接,並且需要重新連接到Master, Redis-Plus-Plus都會從Redis Sentinel詢問主地址,並連接到當前Master。如果發生故障轉移, Redis-Plus-Plus可以自動獲取新主的地址,並刷新基礎連接池中的所有連接。
同樣,使用Role::SLAVE , redis-plus-plus將始終連接到從實例。主人可能有幾個奴隸, redis-plus-plus將隨機選擇一個,然後連接到它,即基礎連接池中的所有連接,連接到同一從屬實例(請檢查為什麼Redis-Plus-Plus不連接到所有從屬的討論)。如果連接被打破,而該從實例仍然是一個活著的從屬,則Redis-Plus-Plus將重新連接到該從屬。但是,如果該從實例下降,或者已將其晉升為主人,則Redis-Plus-Plus將隨機連接到另一個從屬。如果沒有奴隸活著,它會引發例外。
使用Sentinel創建Redis對象時,除了Sentinel信息外,還應提供ConnectionOptions和ConnectionPoolOptions 。這兩個選項用於連接到REDIS實例。 ConnectionPoolOptions是可選的,如果未指定,它將創建一個實例。
ConnectionOptions connection_opts;
connection_opts.password = " auth " ; // Optional. No password by default.
connection_opts.connect_timeout = std::chrono::milliseconds( 100 ); // Required.
connection_opts.socket_timeout = std::chrono::milliseconds( 100 ); // Required.
ConnectionPoolOptions pool_opts;
pool_opts.size = 3 ; // Optional. The default size is 1.
auto redis = Redis(sentinel, " master_name " , Role::MASTER, connection_opts, pool_opts);您可能已經註意到,我們沒有為ConnectionOptions指定host和port字段。因為, Redis將從Redis Sentinel獲取這些信息。另外,在這種情況下, ConnectionOptions::connect_timeout和ConnectionOptions::socket_timeout不能為0ms,否則,它會引發異常。因此,您始終需要手動指定這兩個超時。
使用Sentinel創建Redis對像後,您可以像普通的Redis對像一樣發送命令。
如果您想寫信給主,並用奴隸縮放閱讀。您可以使用以下模式:
auto sentinel = std::make_shared<Sentinel>(sentinel_opts);
auto master = Redis(sentinel, " master_name " , Role::MASTER, connection_opts, pool_opts);
auto slave = Redis(sentinel, " master_name " , Role::SLAVE, connection_opts, pool_opts);
// Write to master.
master.set( " key " , " value " );
// Read from slave.
slave.get( " key " );自REDIS 5.0以來,它引入了一種新的數據類型: REDIS流。 redis-plus-plus具有除XINFO命令外的所有流命令的內置方法(當然,您可以使用通用命令接口發送XINFO命令)。
但是,某些流命令的答复,即xpending , xread是複雜的。因此,我將舉一些例子,向您展示如何使用這些內置方法。
auto redis = Redis( " tcp://127.0.0.1 " );
using Attrs = std::vector<std::pair<std::string, std::string>>;
// You can also use std::unordered_map, if you don't care the order of attributes:
// using Attrs = std::unordered_map<std::string, std::string>;
Attrs attrs = { { " f1 " , " v1 " }, { " f2 " , " v2 " } };
// Add an item into the stream. This method returns the auto generated id.
auto id = redis.xadd( " key " , " * " , attrs.begin(), attrs.end());
// Each item is assigned with an id: pair<id, Optional<attributes>>.
// NOTE: the attribute part might be nil reply, check [this issue](https://github.com/sewenew/redis-plus-plus/issues/283) for detail.
using Item = std::pair<std::string, Optional<Attrs>>;
using ItemStream = std::vector<Item>;
// If you don't care the order of items in the stream, you can also use unordered_map:
// using ItemStream = std::unordered_map<std::string, Attrs>;
// Read items from a stream, and return at most 10 items.
// You need to specify a key and an id (timestamp + offset).
std::unordered_map<std::string, ItemStream> result;
redis.xread( " key " , id, 10 , std::inserter(result, result.end()));
// Read from multiple streams. For each stream, you need to specify a key and an id.
std::unordered_map<std::string, std::string> keys = { { " key " , id}, { " another-key " , " 0-0 " } };
redis.xread(keys.begin(), keys.end(), 10 , std::inserter(result, result.end()));
// Block for at most 1 second if currently there's no data in the stream.
redis.xread( " key " , id, std::chrono::seconds( 1 ), 10 , std::inserter(result, result.end()));
// Block for multiple streams.
redis.xread(keys.begin(), keys.end(), std::chrono::seconds( 1 ), 10 , std::inserter(result, result.end()));
// Read items in a range:
ItemStream item_stream;
redis.xrange( " key " , " - " , " + " , std::back_inserter(item_stream));
// Trim the stream to a given number of items. After the operation, the stream length is NOT exactly
// 10. Instead, it might be much larger than 10.
// `XTRIM key MAXLEN 10`
redis.xtrim( " key " , 10 );
// In order to trim the stream to exactly 10 items, specify the third argument, i.e. approx, as false.
// `XTRIM key MAXLEN ~ 10`
redis.xtrim( " key " , 10 , false );
// Delete an item from the stream.
redis.xdel( " key " , id);
// Create a consumer group.
redis.xgroup_create( " key " , " group " , " $ " );
// If the stream doesn't exist, you can set the fourth argument, i.e. MKSTREAM, to be true.
// redis.xgroup_create("key", "group", "$", true);
id = redis.xadd( " key " , " * " , attrs.begin(), attrs.end());
// Read item by a consumer of a consumer group.
redis.xreadgroup( " group " , " consumer " , " key " , " > " , 1 , std::inserter(result, result.end()));
using PendingItem = std::tuple<std::string, std::string, long long , long long >;
std::vector<PendingItem> pending_items;
// Get pending items of a speicified consumer.
redis.xpending( " key " , " group " , " - " , " + " , 1 , " consumer " , std::back_inserter(pending_items));
redis.xack( " key " , " group " , id);
redis.xgroup_delconsumer( " key " , " group " , " consumer " );
redis.xgroup_destroy( " key " , " group " );如果您將流命令發送到Redis有任何問題,請隨時讓我知道。
REDIS模塊豐富了Redis。但是,儘管您可以使用通用接口發送與這些模塊相關的命令,但Redis-Plus-Plus沒有內置支持/方法。
通用命令接口使用第二個參數作為哈希的鍵。如果您的自定義命令將密鑰放置在不同的參數(即: module-name create key1 arg1 arg2 ),並且您正在使用RedisCluster客戶端,則它將無法將命令發送到正確的Redis實例。在這種情況下,您可以使用以下工作:
auto redis_cluster = RedisCluster( " tcp://127.0.0.1:6379 " );
std::vector<std::string> raw_cmd;
raw_cmd.push_back( " module-name " );
raw_cmd.push_back( " create " );
raw_cmd.push_back( " key1 " );
raw_cmd.push_back( " arg1 " );
raw_cmd.push_back( " arg2 " );
// create it with a connection from the underlying connection pool
auto redis = redis_cluster.redis( " key1 " , false );
redis.command< void >(raw_cmd.begin(), raw_cmd.end());幸運的是,@Wingunder做得很好,可以使工作更輕鬆。他撰寫了Redis-Plus-Plus-Modules,這是一個唯一的標題項目,對某些流行模塊具有內置支持。如果您需要使用REDIS模塊,則應該嘗試一下。
@Wingunder還為Redis-Plus-Plus做出了很多貢獻。非常感謝@Wingunder!
Redis-Plus-Plus還支持異步接口,但是,對交易的異步支持仍在途中。
異步接口取決於第三方事件庫,到目前為止,僅支持Libuv。
在安裝Hiredis和redis-plus-plus之前,您必須安裝libuv (例如apt-get安裝libuv1-dev )。所需的Libuv版本為1.x。
Hiredis V1.0.0的異步接口與較舊版本不同, Redis-Plus-Plus僅支持Hiredis V1.0.0或更高版本。因此,您需要在安裝Redis-Plus-Plus之前確保已安裝正確的Hiredis版本。另外,您切勿安裝多個版本的Hiredis ,否則,您會遇到一些有線問題。如果您已經安裝了舊版本,請刪除它,然後安裝較新的版本。
安裝redis-plus-plus時,應指定以下命令行選項: -DREDIS_PLUS_PLUS_BUILD_ASYNC=libuv 。
cmake -DCMAKE_PREFIX_PATH=/installation/path/to/libuv/and/hiredis -DREDIS_PLUS_PLUS_BUILD_ASYNC=libuv ..
make
make install異步接口類似於同步接口,除了您應該包括SW/REDIS ++/async_redis ++。H ,並定義sw::redis::AsyncRedis的對象,並且相關方法返回Future對象(到目前為止,僅std::future and boost::future Suppperted of Spected of其他實現未來的方法)。
但是,C ++對延續和執行程序的支持尚未完成,因此異步接口還支持舊的回調方式。以下是回調接口:
template <typename ReplyType>
void (sw::redis::Future<ReplyType> &&fut);
在回調中,要獲取答复,您需要致電sw::redis::Future<ReplyType>::get() 。如果發生了不好的事情, get扔出異常。因此,您需要在回調中捕獲可能的例外。回調在基礎事件循環線程中運行,因此請勿在回調中執行緩慢的操作,否則,它會阻止事件循環並傷害性能。
筆記:
AsyncRedis還活著。因為,一旦AsyncRedis被摧毀,它將停止基礎事件循環。任何尚未發送給Redis的命令都可能失敗。這些筆記還與AsyncRedisCluster一起使用。
# include < sw/redis++/async_redis++.h >
ConnectionOptions opts;
opts.host = " 127.0.0.1 " ;
opts.port = 6379 ;
ConnectionPoolOptions pool_opts;
pool_opts.size = 3 ;
auto async_redis = AsyncRedis(opts, pool_opts);
Future<string> ping_res = async_redis.ping();
// Async interface returning Future object.
Future< bool > set_res = async_redis.set( " key " , " val " );
// Async interface with callback.
async_redis.set( " key " , " val " ,
[](Future< bool > &&fut) {
try {
auto set_res = fut. get ();
} catch ( const Error &err) {
// handle error
}
});
Future<Optional<string>> get_res = async_redis.get( " key " );
async_redis.get( " key " , [](Future<OptionalString> &&fut) {
try {
auto val = fut. get ();
if (val)
cout << *val << endl;
else
cout << " not exist " << endl;
} catch ( const Error &err) {
// handle error
}
});
unordered_map<string, string> m = {{ " a " , " b " }, { " c " , " d " }};
Future< void > hmset_res = async_redis.hmset( " hash " , m.begin(), m.end());
auto hgetall_res = async_redis.hgetall<std::unordered_map<std::string, std::string>>( " hash " );
cout << ping_res.get() << endl;
cout << set_res.get() << endl;
auto val = get_res.get();
if (val)
cout << *val << endl;
else
cout << " not exist " << endl;
hmset_res.get();
for ( const auto &ele : hgetall_res.get())
cout << ele.first << " t " << ele.second << endl;
// Generic interface.
// There's no *AsyncRedis::client_getname* interface.
// But you can use *Redis::command* to get the client name.
auto getname_res = async_redis.command<OptionalString>( " client " , " getname " );
val = getname_res.get();
if (val) {
std::cout << *val << std::endl;
}
async_redis.command<OptionalString>( " client " , " getname " ,
[](Future<OptionalString> &&fut) {
try {
auto val = fut. get ();
} catch ( const Error &e) {
// handle error
}
});
async_redis.command< long long >( " incr " , " number " ,
[](Future< long long > &&fut) {
try {
cout << fut. get () << endl;
} catch ( const Error &e) {
// handle error
}
});Aysnc接口還支持Redis Sentinel。
# include < sw/redis++/async_redis++.h >
SentinelOptions sentinel_opts;
sentinel_opts.nodes = {
{ " 127.0.0.1 " , 8000 },
{ " 127.0.0.1 " , 8001 },
{ " 127.0.0.1 " , 8002 }
};
sentinel_opts.connect_timeout = std::chrono::milliseconds( 100 );
sentinel_opts.socket_timeout = std::chrono::milliseconds( 100 );
auto sentinel = std::make_shared<AsyncSentinel>(sentinel_opts);
onnectionOptions connection_opts;
connection_opts.connect_timeout = std::chrono::milliseconds( 100 ); // Required.
connection_opts.socket_timeout = std::chrono::milliseconds( 100 ); // Required.
ConnectionPoolOptions pool_opts;
pool_opts.size = 3 ; // Optional. The default size is 1.
// Connect to master node.
AsyncRedis redis (sentinel, " mymaster " , Role::MASTER, connection_opts, pool_opts);
// The following code randomly connects to one of the slave nodes.
// AsyncRedis redis(sentinel, "mymaster", Role::SLAVE, connection_opts, pool_opts);
redis.set( " key " , " value " );
auto value = redis.get( " key " ).get();對Sentinel的異步支持與Sync One相似,只是您需要創建AsyncSentinel對象而不是Sentinel對象。檢查Redis Sentinel,以了解有關SentinelOptions , ConnectionOptions和Role的更多詳細信息。
Aysnc接口還支持REDIS群集。您需要創建一個AsyncRedisCluster對象,而不是AsyncRedis 。
ConnectionOptions opts;
opts.host = " 127.0.0.1 " ;
opts.port = 6379 ;
ConnectionPoolOptions pool_opts;
pool_opts.size = 3 ;
auto async_cluster = AsyncRedisCluster(opts, pool_opts);
Future< bool > set_res = async_cluster.set( " key " , " val " );
Future<Optional<string>> get_res = async_cluster.get( " key " );
auto mget_res = async_cluster.mget<std::vector<OptionalString>>({ " {hashtag}key1 " , " {hashhag}key2 " , " {hashtag}key3 " });
unordered_map<string, string> m = {{ " a " , " b " }, { " c " , " d " }};
Future< void > hmset_res = async_redis.hmset( " hash " , m.begin(), m.end());
// Create an AsyncRedis object with hash-tag, so that we can send commands that has no key.
// It connects to Redis instance that holds the given key, i.e. hash-tag.
auto r = async_cluster.redis( " hash-tag " );
Future<string> ping_res = r.command<string>( " ping " );注意:默認情況下,當您使用AsyncRedisCluster::redis(const StringView &hash_tag, bool new_connection = true)來創建一個AsyncRedis對象,而不是從基礎連接池中選擇連接,它會創建到相應的Redis服務器的新連接。因此,這不是一個便宜的操作,您應該盡量重複使用這個新創建的AsyncRedis對象。如果您將false作為第二個參數,則可以創建一個AsyncRedis對象,而無需創建新連接。但是,在這種情況下,您應該非常小心,否則,您的性能可能不好,甚至鎖定。在使用此功能之前,請仔細檢查相關的管道部分。另外,返回的AsyncRedis對像不是線程安全,如果它引發異常,則需要銷毀它,並使用AsyncRedisCluster::redis方法創建一個新的。
注意:我對AsyncSubscriber的界面不太滿意。如果您有一個更好的主意,請隨時開放討論問題。
您可以使用AsyncSubscriber訂閱頻道或模式。該接口類似於Subscriber ,除了一些差異(請先閱讀/訂閱部分):
AsyncSubscriber方法的consume方法。設置回調並訂閱某些頻道後,Redis-Plus-Plus將在基礎事件循環中使用接收的消息運行回調。AsyncSubscriber::subscribe , AsyncSubscriber::psubscriber和其他相關方法返回Future<void> 。您可以使用它來檢查是否已發送訂閱。AsyncSubscriber::on_error(ErrCallback &&)設置錯誤回調,以處理可能的錯誤。錯誤回調接口為: void (std::exception_ptr err) ,您可以使用給定的異常指針獲得異常。 AsyncSubscriber不是線程安全。如果要在多線程環境中調用其成員功能,則需要手動在線程之間同步。AsyncSubscriber被破壞時,基礎連接將被關閉。如果仍未取消訂閱的通道或模式,則將調用錯誤回調。為了避免這種情況,您需要調用AsyncSubscriber::unsubscribe()或AsyncSubscriber::punsubscribe()以在銷毀AsyncSubscriber所有通道或圖案中取消訂閱。注意:將來可能會改變此行為,即我們將在AsyncSubscriber的破壞者中取消訂閱的頻道和模式。 以下示例是使用AsyncSubscriber常見模式:
// Create an `AsyncSubscriber`. You can create it with either an `AsyncRedis` or `AsyncRedisCluster` object.
auto sub = async_redis.subscriber();
// Set callbacks.
sub.on_message([](std::string channel, std::string msg) {
// Process message of MESSAGE type.
});
sub.on_pmessage([](std::string pattern, std::string channel, std::string msg) {
// Process message of PMESSAGE type.
});
sub.on_meta([](Subscriber::MsgType type, OptionalString channel, long long num) {
// Process message of META type.
});
// You need to set error callback to handle error.
sub.on_error([](std::exception_ptr e) {
try {
std::rethrow_exception (e);
} catch ( const std:: exception &err) {
std::cerr << " err: " << err. what () << std::endl;
}
});
// Subscribe to channels and patterns.
Future< void > fut1 = sub.subscribe( " channel " );
Future< void > fut2 = sub.psubscribe( " pattern1* " );
// Once you call `subscribe` or `psubscribe`, callbacks will be run in the underlying
// event loop automatically. 注意:以下是一個實驗功能,將來可能會修改或放棄。
默認情況下, AsyncRedis和AsyncRedisCluster創建一個默認事件循環,並在專用線程中運行循環以處理和寫入操作。但是,您也可以與多個AsyncRedis和AsyncRedisCluster對象共享基礎事件循環。為了做到這一點,您需要創建一個std::shared_ptr<EventLoop> ,然後將其傳遞給AsyncRedis和AsyncRedisCluster的構造函數。
auto event_loop = std::make_shared<EventLoop>();
auto redis = AsyncRedis(connection_opts, pool_opts, loop);
auto cluster = AsyncRedisCluster(connection_opts, pool_opts, Role::MASTER, loop);注意:您必須確保event_loop壽命比AsyncRedis和AsyncRedisCluster對象更長。
不幸的是,到目前為止, std::future不支持延續,這是不方便的。但是,其他一些圖書館(例如增強和愚蠢)得到了持續支持。
默認情況下, redis-plus-plus返回std::future 。但是,您還可以通過指定-DREDIS_PLUS_PLUS_ASYNC_FUTURE=boost在運行cmake時(將來可能會支持folly和其他庫),從而使其返回boost::future 。當然,在這種情況下,您需要首先安裝Boost(Boost的最小版本要求為1.55.0 )。
cmake -DREDIS_PLUS_PLUS_BUILD_ASYNC=libuv -DREDIS_PLUS_PLUS_ASYNC_FUTURE=boost ..注意:構建應用程序代碼時,不要忘記鏈接相關的libs,例如-lboost_thread,-lboost_system。
然後,您可以利用boost::future的延續支持:
# include < sw/redis++/async_redis++.h >
ConnectionOptions opts;
opts.host = " 127.0.0.1 " ;
opts.port = 6379 ;
auto redis = AsyncRedis(opts);
auto fut = redis.get( " key " ).then([](sw::redis::Future<sw::redis::Optional<std::string>> fut) {
auto val = fut. get ();
if (val) cout << *val << endl;
});
// Do other things
// Wait for the continuation finishes.
fut.get();您還可以使用線程池來運行延續:
# define BOOST_THREAD_PROVIDES_EXECUTORS
// You might also need to `#define BOOST_THREAD_USES_MOVE` with some version of Boost.
// See [this issue](https://github.com/sewenew/redis-plus-plus/issues/272) for detail.
# include < sw/redis++/async_redis++.h >
# include < boost/thread/executors/basic_thread_pool.hpp >
boost::executors::basic_thread_pool pool ( 3 );
auto fut = redis.get( " key " ).then(pool,
[](sw::redis::Future<sw::redis::Optional<std::string>> fut) {
auto val = fut. get ();
if (val) cout << *val << endl;
});
// Do other things
fut.get();Redis-Plus-Plus還支持Coroutine接口,但是,對訂戶和交易的Coroutine支持仍在途中。
注意:Coroutine支持仍然是實驗性的,並且將來可能會更改界面。
Coroutine接口取決於異步接口,該接口取決於第三方事件庫。因此,您需要先安裝Libuv ,然後Hiredis v1.0.0或更高版本。檢查異步接口以獲取詳細信息。
安裝redis -plus -plus時,應指定以下命令行選項: -DREDIS_PLUS_PLUS_BUILD_ASYNC=libuv , -DREDIS_PLUS_PLUS_BUILD_CORO=ON和-DREDIS_PLUS_PLUS_CXX_STANDARD=20 。
cmake -DCMAKE_PREFIX_PATH=/installation/path/to/libuv/and/hiredis -DREDIS_PLUS_PLUS_CXX_STANDARD=20 -DREDIS_PLUS_PLUS_BUILD_ASYNC=libuv -DREDIS_PLUS_PLUS_BUILD_CORO=ON ..
make
make installThe coroutine interface is similar to sync interface, except that you should include sw/redis++/co_redis++.h , and define an object of sw::redis::CoRedis or sw::redis::CoRedisCluster , and the related methods return sw::redis::CoRedis::Awaiter<Result> or sw::redis::CoRedisCluster::Awaiter<Result> object.
筆記:
# include < sw/redis++/co_redis++.h >
# include < cppcoro/task.hpp >
# include < cppcoro/sync_wait.hpp >
ConnectionOptions opts;
opts.host = " 127.0.0.1 " ;
opts.port = 6379 ;
ConnectionPoolOptions pool_opts;
pool_opts.size = 3 ;
// `CoRedisCluster` has similar inteface as `CoRedis`.
// auto co_redis_cluster = CoRedisCluster(opts, pool_opts);
auto co_redis = CoRedis(opts, pool_opts);
cppcoro::sync_wait ([&co_redis]() -> cppcoro::task<> {
try {
co_await co_redis. set ( " key " , " val " );
auto val = co_await co_redis. get ( " key " );
if (val)
cout << *val << endl;
else
cout << " not exist " << endl;
co_await co_redis. command < long long >( " incr " , " num " );
val = co_await co_redis. command <OptionalString>( " get " , " num " );
} catch ( const Error &e) {
cout << e. what () << endl;
}
}());Coroutine接口還支持Redis Sentinel。
# include < sw/redis++/co_redis++.h >
SentinelOptions sentinel_opts;
sentinel_opts.nodes = {
{ " 127.0.0.1 " , 8000 },
{ " 127.0.0.1 " , 8001 },
{ " 127.0.0.1 " , 8002 }
};
sentinel_opts.connect_timeout = std::chrono::milliseconds( 100 );
sentinel_opts.socket_timeout = std::chrono::milliseconds( 100 );
auto sentinel = std::make_shared<CoSentinel>(sentinel_opts);
onnectionOptions connection_opts;
connection_opts.connect_timeout = std::chrono::milliseconds( 100 ); // Required.
connection_opts.socket_timeout = std::chrono::milliseconds( 100 ); // Required.
ConnectionPoolOptions pool_opts;
pool_opts.size = 3 ; // Optional. The default size is 1.
// Connect to master node.
CoRedis co_redis (sentinel, " mymaster " , Role::MASTER, connection_opts, pool_opts);
// The following code randomly connects to one of the slave nodes.
// CoRedis co_redis(sentinel, "mymaster", Role::SLAVE, connection_opts, pool_opts);
cppcoro::sync_wait ([&co_redis]() -> cppcoro::task<> {
try {
auto val = co_await co_redis. get ( " key " );
if (val)
cout << *val << endl;
else
cout << " not exist " << endl;
} catch ( const Error &e) {
cout << e. what () << endl;
}
}()); Sentinel的Coroutine支持與Sync One相似,除了您需要創建CoSentinel對象而不是Sentinel對象。檢查Redis Sentinel,以了解有關SentinelOptions , ConnectionOptions和Role的更多詳細信息。
我們可以基於Redis(例如Redlock)創建許多有趣的數據結構和算法。我們將這些數據結構和算法稱為重新模式。 Redis-Plus-Plus將支持其中一些模式。
注意:這些模式將首先在模式分支上實現。我想听聽您對這些模式API的反饋,當這些API變得穩定時,我將將代碼合併到主分支中。因此,模式分支上的API不穩定,將來可能會更改。
Redlock是基於Redis的分佈式鎖。多虧了 @Wingunder的建議, Redis-Plus-Plus現在支持Redlock。 @Wingunder和我進行了兩個不同的Redlock:一個基於LUA腳本的實現,另一個基於交易。 LUA腳本版本應該更快,並且還有許多其他參數可以控制行為。但是,如果您不允許或不想在Redis內運行LUA腳本,則可以嘗試使用事務版。
還有一個高級別的API,它的作用為std::mutex 。使用此高級API,您無需手動擴展鎖定,而是通過redis-plus-plus自動擴展鎖。
獲取紅鎖的基本思想是,如果鍵不存在,則在redis中設置鍵。由於REDIS操作是原子能的,因此當Mutiple客戶端獲得相同的鎖定時,即如果不存在,則設置相同的密鑰,只有一個客戶端會獲勝,而其他客戶會發現已設置了密鑰。因此,只有一個客戶可以獲取鎖,而其他客戶必須等待並重試。
設置密鑰時,我們還需要為密鑰設置一個TTL/Exefeareation。否則,如果獲勝的客戶崩潰,則鎖定不能永遠由他人收購。但是,這也帶來了一個新問題。由於鍵具有TTL,因此一旦獲得鎖,就必須確保關鍵部分中的所有代碼必須在鑰匙到期之前完成。否則,當您仍在運行關鍵的部分代碼時,其他客戶可能會成功獲取鎖(即成功獲取鎖)。因此,運行關鍵的部分代碼時,您必須檢查鍵是否過期並在鍵到期之前擴展鎖定(即延長TTL)。
同樣,為了使算法更穩定,通常我們需要在多個獨立的獨立redis(不是redis cluster)上設置鍵。
還有更多有關Redlock機制的細節。在使用之前,請閱讀Redlock的文檔以獲取更多信息。
高級API非常簡單。它的工作原理就像std::mutex ,並且可以與std::lock_guard and std::unique_lock一起使用。此外,它也可以在鑰匙到期之前自動擴展鎖。因此,該用戶代碼不需要手動擴展鎖定。為了使用Redlock,您可以使用以下參數創建一個RedMutex對象:
Redis實例:有兩個版本的redlock,即單個實例版本和多個實例版本。多個實例版本更強大。RedMutex 。RedMutexOptions (可選):控制RedMutex行為的一些選項。如果未指定,將使用默認選項。在下面查看更多詳細信息。LockWatcher (可選):一個觀察者,該觀察者將在鎖到期之前自動擴展鎖。因此,您無需手動檢查鎖是否已過期。如果未指定觀察者(默認行為),則Redis-Plus-Plus將為此Redlock創建一個。在下面查看更多詳細信息。 class RedMutex {
public:
RedMutex(std::initializer_list<std::shared_ptr<Redis>> masters,
const std::string &resource,
std::function<void (std::exception_ptr)> auto_extend_err_callback = nullptr,
const RedMutexOptions &opts = {},
const std::shared_ptr<LockWatcher> &watcher = nullptr);
void lock();
bool try_lock();
void unlock();
};
正如我們提到的,高級API可以自動擴展鎖定。但是,我們可能無法擴展鎖,例如與REDIS的連接斷開。在這種情況下,將調用auto_extend_err_callback ,以便可以通知應用程序鎖定不再鎖定,並在關鍵部分中停止運行代碼。
以下是錯誤回調的原型。
void (std::exception_ptr err);
如果未設置錯誤回調(默認行為),則將忽略錯誤。而且,您可能會與多個客戶端運行關鍵的部分代碼。
struct RedMutexOptions {
std::chrono::milliseconds ttl;
std::chrono::milliseconds retry_delay;
bool scripting = true;
};
ttl ,否則,您可能無法鎖定或無法擴展鎖定。RedMutex::lock重複嘗試鎖定,直到獲得鎖定為止。如果失敗,它將等待retry_delay在下一個重試之前。默認情況下為100毫秒。 LockWatcher觀看RedMutex ,並嘗試不時擴展鎖。您可以使用std::shared_ptr<LockWatcher>構造RedMutex ,以便它觀看相應的redlock。 LockWatcher在背景線程中進行工作。因此,創建一個LockWatcher像也會創建一個std::thread 。如果要避免創建多個線程,則可以使用相同的std::shared_ptr<LockWatcher>構造多個RedMutex 。
如果您不指定LockWatcher , RedMutex將創建一個(默認行為),然後啟動一個線程。儘管創建線程很昂貴,但與獲取分佈式鎖相比,它仍然很便宜。
RedMutex不是重新進入的。如果您嘗試鎖定已經被當前線程鎖定的靜音,則該行為是不確定的。# include < memory >
# include < sw/redis++/redis++.h >
# include < sw/redis++/patterns/redlock.h >
auto redis = std::make_shared<Redis>( " tcp://127.0.0.1 " );
auto redis1 = std::make_shared<Redis>( " tcp://127.0.0.1:7000 " );
auto redis2 = std::make_shared<Redis>( " tcp://127.0.0.1:7001 " );
auto redis3 = std::make_shared<Redis>( " tcp://127.0.0.1:7002 " );
try {
{
// Create a `RedMutex` with a single stand-alone Redis and default settings.
RedMutex mtx (redis, " resource " );
std::lock_guard<RedMutex> lock (mtx);
}
{
// Create a `RedMutex` with multiple stand-alone Redis and default settings.
RedMutex mtx ({redis1, redis2, redis3}, " resource " );
std::lock_guard<RedMutex> lock (mtx);
}
{
RedMutexOptions opts;
opts. ttl = std::chrono::seconds ( 5 );
auto watcher = std::make_shared<LockWatcher>();
// Create a `RedMutex` with auto_extend_err_callback and other options.
RedMutex mtx ({redis1, redis2, redis3}, " resource " ,
[](std::exception_ptr err) {
try {
std::rethrow_exception (err);
} catch ( const Error &e) {
// Notify application code that the lock might no longer be locked.
}
},
opts, watcher);
std::unique_lock<RedMutex> lock (mtx, std::defer_lock);
lock. lock ();
lock. unlock ();
lock. try_lock ();
}
} catch ( const Error &err) {
// handle error.
} // Lua script version:
{
RedLockMutex mtx({redis1, redis2, redis3}, "resource");
// Not locked.
RedLock<RedLockMutex> lock(mtx, std::defer_lock);
// Try to get the lock, and keep 30 seconds.
// It returns the validity time of the lock, i.e. the lock is only
// valid in *validity_time*, after that the lock might be acquired by others.
// If failed to acquire the lock, throw an exception of Error type.
auto validity_time = lock.try_lock(std::chrono::seconds(30));
// Extend the lock before the lock expired.
validity_time = lock.extend_lock(std::chrono::seconds(10));
// You can unlock explicitly.
lock.unlock();
} // If unlock() is not called, the lock will be unlocked automatically when it's destroied.
// Transaction version:
{
RedMutex mtx({redis1, redis2, redis3}, "resource");
RedLock<RedMutex> lock(mtx, std::defer_lock);
auto validity_time = lock.try_lock(std::chrono::seconds(30));
validity_time = lock.extend_lock(std::chrono::seconds(30));
// You can unlock explicitly.
lock.unlock();
}
hset相關的方法返回long long而不是bool 。 Redis-Plus-Plus由Sewenew撰寫,他也在Stackoverflow上活躍。
非常感謝Redis-Plus-Plus的所有貢獻者,尤其是@Wingunder。