├── .bazelrc ├── .clang-format ├── .gitignore ├── BUILD.bazel ├── CMakeLists.txt ├── LICENSE ├── MODULE.bazel ├── README.md ├── WORKSPACE ├── bazel └── config │ └── BUILD.bazel ├── changelog.md ├── format_all.sh ├── gen_coverage_report.sh ├── redis_sdk ├── BUILD.bazel ├── async_example.cpp ├── client_bench.cpp ├── client_tool.cpp ├── conf │ └── api_example_gflags.conf ├── example.cpp ├── include │ ├── api_util.h │ ├── client_impl.h │ ├── cluster.h │ ├── ctx.h │ ├── metric.h │ ├── pipeline.h │ ├── redis_sdk │ │ ├── api_status.h │ │ └── client.h │ ├── redis_server.h │ ├── redis_server.hpp │ └── slot.h └── src │ ├── client_impl.cpp │ ├── client_util.cpp │ ├── cluster.cpp │ ├── crc16.cpp │ ├── ctx.cpp │ ├── pipeline.cpp │ ├── redis_server.cpp │ └── slot.cpp ├── test ├── BUILD.bazel ├── CMakeLists.txt ├── client_impl_test.cpp ├── cluster_test.cpp ├── ctx_test.cpp ├── http_mgr.proto ├── mock_mgr_http.h ├── mock_node.cpp ├── mock_node.h ├── real_crud_test.cpp ├── redirect_test.cpp ├── slot_test.cpp ├── sstream_define_as_public.h ├── test_common.cpp └── test_common.h └── third_party ├── BUILD.bazel ├── cpptoml.BUILD ├── fmt.BUILD ├── leveldb.BUILD ├── openssl.BUILD ├── repositories.bzl ├── xxhash.BUILD └── zlib.BUILD /.bazelrc: -------------------------------------------------------------------------------- 1 | # bzlmod 2 | common --enable_bzlmod 3 | common --registry=https://bcr.bazel.build 4 | common --registry=https://baidu.github.io/babylon/registry 5 | common --registry=https://raw.githubusercontent.com/bazelboost/registry/main 6 | 7 | # cc_library config 8 | build --cxxopt='-std=c++11' 9 | 10 | # most support cpp 17x 11 | # build --cxxopt='-std=c++17' 12 | 13 | # protobuf enable zlib 14 | build --copt -DHAVE_ZLIB=1 15 | build --copt -ggdb 16 | build --copt -DHAVE_UNISTD_H 17 | # glog enable stacktrace 18 | build --copt -DHAVE_UNWIND_H 19 | # brpc enable glog 20 | build --define=with_glog=true 21 | 22 | build --experimental_cc_implementation_deps 23 | 24 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # see. https://clang.llvm.org/docs/ClangFormatStyleOptions.html 3 | # 语言: Cpp 4 | Language: Cpp 5 | 6 | # 基础样式 7 | BasedOnStyle: Google 8 | 9 | # 访问说明符(public、private等)的偏移 10 | AccessModifierOffset: -4 11 | 12 | # 开括号(开圆括号、开尖括号、开方括号)后的对齐: Align, DontAlign, AlwaysBreak(总是在开括号后换行) 13 | AlignAfterOpenBracket: Align 14 | 15 | # 连续赋值时,是否对齐所有等号 16 | AlignConsecutiveAssignments: false 17 | 18 | # 连续声明时,是否对齐所有声明的变量名 19 | AlignConsecutiveDeclarations: false 20 | 21 | # 左对齐逃脱换行(使用反斜杠换行)的反斜杠 22 | AlignEscapedNewlines: Left 23 | 24 | # 水平对齐二元和三元表达式的操作数 25 | AlignOperands: true 26 | 27 | # 对齐连续的尾随的注释 28 | AlignTrailingComments: true 29 | 30 | # 允许函数声明的所有参数在放在下一行 31 | AllowAllParametersOfDeclarationOnNextLine: false 32 | 33 | # 允许短的块放在同一行 34 | AllowShortBlocksOnASingleLine: false 35 | 36 | # 允许短的case标签放在同一行 37 | AllowShortCaseLabelsOnASingleLine: false 38 | 39 | # 允许短的函数放在同一行: None, InlineOnly(定义在类中), Empty(空函数), Inline(定义在类中,空函数), All 40 | AllowShortFunctionsOnASingleLine: None 41 | 42 | # 允许短的if语句保持在同一行 43 | AllowShortIfStatementsOnASingleLine: false 44 | 45 | # 允许短的循环保持在同一行 46 | AllowShortLoopsOnASingleLine: false 47 | 48 | # 总是在定义返回类型后换行 49 | AlwaysBreakAfterDefinitionReturnType: None 50 | AlwaysBreakAfterReturnType: None 51 | 52 | # 多行字符串是否强制多行 53 | AlwaysBreakBeforeMultilineStrings: false 54 | 55 | AlwaysBreakTemplateDeclarations: Yes 56 | 57 | # false表示函数实参要么都在同一行,要么都各自一行 58 | BinPackArguments: true 59 | 60 | # false表示所有形参要么都在同一行,要么都各自一行 61 | BinPackParameters: true 62 | 63 | # 大括号换行 64 | BraceWrapping: 65 | AfterClass: false # class定义后面 66 | AfterControlStatement: false # 控制语句后面 67 | AfterEnum: false # enum定义后面 68 | AfterFunction: false # 函数定义后面 69 | AfterNamespace: false # 命名空间定义后面 70 | AfterObjCDeclaration: false # ObjC定义后面 71 | AfterStruct: false # struct定义后面 72 | AfterUnion: false # union定义后面 73 | AfterExternBlock: false 74 | BeforeCatch: false 75 | BeforeElse: false 76 | IndentBraces: false # 缩进大括号 77 | SplitEmptyFunction: true 78 | SplitEmptyRecord: true 79 | SplitEmptyNamespace: true 80 | 81 | # 在二元运算符前换行: None(在操作符后换行), NonAssignment(在非赋值的操作符前换行), All(在操作符前换行) 82 | BreakBeforeBinaryOperators: NonAssignment 83 | 84 | # 在大括号前换行: Attach(始终将大括号附加到周围的上下文), Linux(除函数、命名空间和类定义,与Attach类似), 85 | # Mozilla(除枚举、函数、记录定义,与Attach类似), Stroustrup(除函数定义、catch、else,与Attach类似), 86 | # Allman(总是在大括号前换行), GNU(总是在大括号前换行,并对于控制语句的大括号增加额外的缩进), WebKit(在函数前换行), Custom 87 | # 注:这里认为语句块也属于函数 88 | BreakBeforeBraces: Attach 89 | 90 | BreakBeforeInheritanceComma: false 91 | BreakInheritanceList: BeforeColon 92 | BreakBeforeTernaryOperators: true 93 | 94 | 95 | # 在构造函数的初始化列表的逗号前换行 96 | BreakConstructorInitializersBeforeComma: true 97 | 98 | # 构造函数初始化样式,改为在 : 后换行 99 | BreakConstructorInitializers: AfterColon 100 | 101 | BreakAfterJavaFieldAnnotations: false 102 | 103 | # 格式化时允许中断字符串文字 104 | BreakStringLiterals: true 105 | 106 | # 每行字符的限制,0表示没有限制 107 | ColumnLimit: 100 108 | 109 | # 描述具有特殊意义的注释的正则表达式,它不应该被分割为多行或以其它方式改变 110 | CommentPragmas: '^ IWYU pragma:' 111 | CompactNamespaces: false 112 | 113 | # 构造函数的初始化列表要么都在同一行,要么都各自一行 114 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 115 | AllowAllConstructorInitializersOnNextLine: false 116 | 117 | 118 | # 构造函数的初始化列表的缩进宽度 119 | ConstructorInitializerIndentWidth: 8 120 | 121 | # 延续的行的缩进宽度 122 | ContinuationIndentWidth: 4 123 | 124 | PenaltyBreakBeforeFirstCallParameter: 150 125 | PenaltyBreakAssignment: 150 126 | PenaltyBreakComment: 150 127 | PenaltyBreakFirstLessLess: 150 128 | PenaltyBreakString: 150 129 | PenaltyBreakTemplateDeclaration: 150 130 | PenaltyExcessCharacter: 150 131 | PenaltyReturnTypeOnItsOwnLine: 150 132 | 133 | # 去除C++11的列表初始化的大括号{后和}前的空格 134 | Cpp11BracedListStyle: true 135 | 136 | # 继承最常用的指针和引用的对齐方式 137 | DerivePointerAlignment: false 138 | PointerAlignment: Left 139 | 140 | # 自动检测函数的调用和定义是否被格式为每行一个参数(Experimental) 141 | ExperimentalAutoDetectBinPacking: false 142 | 143 | # 自动填充缺失的namespace 注释 144 | FixNamespaceComments: true 145 | 146 | # namespace 不对齐 147 | NamespaceIndentation: None 148 | 149 | # 需要被解读为foreach循环而不是函数调用的宏 150 | ForEachMacros: 151 | - foreach 152 | - Q_FOREACH 153 | - BOOST_FOREACH 154 | 155 | SortIncludes: true 156 | IncludeBlocks: Preserve 157 | 158 | # 对#include进行排序,匹配了某正则表达式的#include拥有对应的优先级,匹配不到的则默认优先级为INT_MAX(优先级越小排序越靠前), 159 | # 可以定义负数优先级从而保证某些#include永远在最前面 160 | IncludeCategories: 161 | - Regex: '^' 162 | Priority: 2 163 | - Regex: '^<.*\.h>' 164 | Priority: 1 165 | - Regex: '^<.*' 166 | Priority: 2 167 | - Regex: '.*' 168 | Priority: 3 169 | IncludeIsMainRegex: '([-_](test|unittest))?$' 170 | 171 | IndentCaseLabels: false 172 | IndentPPDirectives: None 173 | IndentWidth: 4 174 | IndentWrappedFunctionNames: false 175 | JavaScriptQuotes: Leave 176 | JavaScriptWrapImports: true 177 | KeepEmptyLinesAtTheStartOfBlocks: false 178 | MacroBlockBegin: '' 179 | MacroBlockEnd: '' 180 | MaxEmptyLinesToKeep: 1 181 | ObjCBinPackProtocolList: Never 182 | ObjCBlockIndentWidth: 2 183 | ObjCSpaceAfterProperty: false 184 | ObjCSpaceBeforeProtocolList: true 185 | 186 | PointerAlignment: Left 187 | RawStringFormats: 188 | - Language: Cpp 189 | Delimiters: 190 | - cc 191 | - CC 192 | - cpp 193 | - Cpp 194 | - CPP 195 | - 'c++' 196 | - 'C++' 197 | CanonicalDelimiter: '' 198 | BasedOnStyle: google 199 | - Language: TextProto 200 | Delimiters: 201 | - pb 202 | - PB 203 | - proto 204 | - PROTO 205 | EnclosingFunctions: 206 | - EqualsProto 207 | - EquivToProto 208 | - PARSE_PARTIAL_TEXT_PROTO 209 | - PARSE_TEST_PROTO 210 | - PARSE_TEXT_PROTO 211 | - ParseTextOrDie 212 | - ParseTextProtoOrDie 213 | CanonicalDelimiter: '' 214 | BasedOnStyle: google 215 | ReflowComments: true 216 | 217 | # 对using namespace 进行排序 218 | SortUsingDeclarations: true 219 | 220 | 221 | # 在C风格类型转换后添加空格 222 | SpaceAfterCStyleCast: false 223 | 224 | # 在模板关键字后面添加空格 225 | SpaceAfterTemplateKeyword: true 226 | 227 | # 在赋值运算符之前添加空格 228 | SpaceBeforeAssignmentOperators: true 229 | 230 | SpaceBeforeCpp11BracedList: false 231 | 232 | # 在类成员变量初始化时追加空格 233 | SpaceBeforeCtorInitializerColon: true 234 | 235 | # 在继承关键字前添加空格 236 | SpaceBeforeInheritanceColon: true 237 | 238 | # 开圆括号之前添加一个空格: Never, ControlStatements, Always 239 | SpaceBeforeParens: ControlStatements 240 | 241 | # for 循环前加空格 242 | SpaceBeforeRangeBasedForLoopColon: true 243 | 244 | # 在空的圆括号中添加空格 245 | SpaceInEmptyParentheses: false 246 | 247 | # 在尾随的评论前添加的空格数(只适用于//) 248 | SpacesBeforeTrailingComments: 1 249 | 250 | # 在尖括号的<后和>前添加空格 251 | SpacesInAngles: false 252 | 253 | # 在容器(ObjC和JavaScript的数组和字典等)字面量中添加空格 254 | SpacesInContainerLiterals: false 255 | 256 | # 在C风格类型转换的括号中添加空格 257 | SpacesInCStyleCastParentheses: false 258 | 259 | # 在圆括号的(后和)前添加空格 260 | SpacesInParentheses: false 261 | 262 | # 在方括号的[后和]前添加空格,lamda表达式和未指明大小的数组的声明不受影响 263 | SpacesInSquareBrackets: false 264 | 265 | # TODO: uncomment when use clang-format 9 266 | # AllowAllConstructorInitializersOnNextLine: false 267 | 268 | Standard: Cpp11 269 | TabWidth: 4 270 | UseTab: Never 271 | ... 272 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | *.vscode 3 | *.idea 4 | __pycache__ 5 | .clangd 6 | .cache 7 | 8 | cmake-build-debug 9 | compile_commands.json 10 | 11 | .DS_Store 12 | *.swp 13 | *.swo 14 | 15 | bazel-bin 16 | bazel-genfiles 17 | bazel-objectstore 18 | bazel-out 19 | bazel-testlogs 20 | 21 | bazel-redis-sdk-cpp 22 | 23 | go.sum 24 | 25 | hack/Dockerfile.build 26 | 27 | tags 28 | .local 29 | coverage 30 | .build-include/ 31 | .build-tmp/ 32 | bin 33 | .deps 34 | .clion.source.upload.marker 35 | docker-compose.yml 36 | gopkg/genpb/*.go 37 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | filegroup( 4 | name = "package-srcs", 5 | srcs = glob( 6 | ["**"], 7 | exclude = [ 8 | "bazel-*/**", 9 | ".git/**", 10 | ".idea/**", 11 | "build/**", 12 | ], 13 | ), 14 | tags = ["automanaged"], 15 | visibility = ["//visibility:private"], 16 | ) 17 | 18 | filegroup( 19 | name = "all-srcs", 20 | srcs = [ 21 | ":package-srcs", 22 | ], 23 | tags = ["automanaged"], 24 | visibility = ["//visibility:public"], 25 | ) 26 | 27 | cc_library( 28 | name = "bili_redis", 29 | visibility = ["//visibility:public"], 30 | deps = [ 31 | "//redis_sdk:redis_sdk", 32 | ], 33 | ) 34 | 35 | toolchain( 36 | name = "built_cmake_toolchain", 37 | exec_compatible_with = [ 38 | "@platforms//:osx", 39 | "@platforms//:x86_64", 40 | ], 41 | toolchain = "@rules_foreign_cc//tools/build_defs/native_tools:built_cmake", 42 | toolchain_type = "@rules_foreign_cc//tools/build_defs:cmake_toolchain", 43 | ) 44 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.7) 2 | 3 | project (redis_sdk) 4 | 5 | set (CMAKE_CXX_STANDARD 11) 6 | set (CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -O2 -g -ggdb -Wall -fno-omit-frame-pointer -Werror=unused-value -Wno-class-memaccess -Wno-aligned-new") 7 | 8 | option(WITH_PROMETHEUS "With prometheus" ON) 9 | if (WITH_PROMETHEUS) 10 | add_definitions(-DWITH_PROMETHEUS) 11 | endif() 12 | 13 | find_package(Protobuf REQUIRED) 14 | find_path(FMT_INCLUDE_PATH NAMES fmt/format.h) 15 | 16 | include_directories( 17 | ${FMT_INCLUDE_PATH} 18 | ${Protobuf_INCLUDE_DIRS} 19 | ${PROJECT_SOURCE_DIR} 20 | ${PROJECT_SOURCE_DIR}/redis_sdk/include 21 | ${CMAKE_CURRENT_BINARY_DIR} 22 | ) 23 | 24 | aux_source_directory(redis_sdk/src API_SRCS) 25 | add_library(redis_sdk STATIC ${API_SRCS}) 26 | set_target_properties(redis_sdk PROPERTIES PUBLIC_HEADER redis_sdk/include/redis_sdk/client.h) 27 | 28 | find_package(gflags REQUIRED) 29 | find_package(glog REQUIRED) 30 | find_package(Protobuf REQUIRED) 31 | find_package(leveldb QUIET) 32 | if (${leveldb_FOUND}) 33 | set(LEVELDB_LIB leveldb::leveldb) 34 | else(${leveldb_FOUND}) 35 | find_library(LEVELDB_LIB NAMES leveldb) 36 | if (NOT LEVELDB_LIB) 37 | message(FATAL_ERROR "Fail to find leveldb") 38 | endif() 39 | endif() 40 | 41 | find_package(prometheus-cpp REQUIRED) 42 | 43 | find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) 44 | find_library(BRPC_LIB NAMES libbrpc.a brpc) 45 | if ((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) 46 | message(FATAL_ERROR "Fail to find brpc") 47 | endif() 48 | 49 | 50 | set(EXAMPLE_DEPS 51 | ${BRPC_LIB} 52 | ${LEVELDB_LIB} 53 | ${UUID_LIB} 54 | ${Protobuf_LIBRARIES} 55 | ${FMT_LIB} 56 | glog::glog 57 | prometheus-cpp::core 58 | gflags 59 | fmt 60 | xxhash 61 | 62 | z 63 | unwind 64 | ssl 65 | dl 66 | crypto 67 | snappy 68 | pthread 69 | ) 70 | 71 | find_library(TCMALLOC_AND_PROFILER NAMES libtcmalloc_and_profiler.a libtcmalloc_and_profiler) 72 | if (NOT TCMALLOC_AND_PROFILER) 73 | message(WARNING "TCMALLOC_AND_PROFILER Not Found, Run sudo apt install libgoogle-perftools-dev to install.") 74 | else() 75 | message(STATUS "Find TCMALLOC_AND_PROFILER") 76 | set(EXAMPLE_DEPS ${EXAMPLE_DEPS} ${TCMALLOC_AND_PROFILER}) 77 | endif() 78 | 79 | add_definitions(-DFMT_HEADER_ONLY) 80 | 81 | add_executable(client_example redis_sdk/example.cpp) 82 | target_link_libraries(client_example redis_sdk ${EXAMPLE_DEPS}) 83 | 84 | add_executable(client_tool redis_sdk/client_tool.cpp) 85 | target_link_libraries(client_tool redis_sdk ${EXAMPLE_DEPS}) 86 | 87 | add_executable(client_bench redis_sdk/client_bench.cpp) 88 | target_link_libraries(client_bench redis_sdk ${EXAMPLE_DEPS}) 89 | 90 | add_executable(client_async_example redis_sdk/async_example.cpp) 91 | target_link_libraries(client_async_example redis_sdk ${EXAMPLE_DEPS}) 92 | 93 | install(TARGETS 94 | redis_sdk 95 | LIBRARY DESTINATION lib 96 | ARCHIVE DESTINATION lib 97 | PUBLIC_HEADER DESTINATION include/redis_sdk 98 | ) 99 | install(FILES 100 | redis_sdk/include/redis_sdk/api_status.h 101 | DESTINATION include/redis_sdk 102 | ) 103 | option(API_BUILD_TESTS "build unittest" ON) 104 | if (API_BUILD_TESTS) 105 | add_subdirectory(test) 106 | endif(API_BUILD_TESTS) 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- 204 | 205 | redis_sdk/src/crc16.cpp: licensed under the following terms: 206 | 207 | 208 | Copyright 2001-2010 Georges Menie (www.menie.org) 209 | Copyright 2010-2012 Salvatore Sanfilippo (adapted to Redis coding style) 210 | All rights reserved. 211 | 212 | Redistribution and use in source and binary forms, with or without 213 | modification, are permitted provided that the following conditions are met: 214 | 215 | * Redistributions of source code must retain the above copyright 216 | notice, this list of conditions and the following disclaimer. 217 | * Redistributions in binary form must reproduce the above copyright 218 | notice, this list of conditions and the following disclaimer in the 219 | documentation and/or other materials provided with the distribution. 220 | * Neither the name of the University of California, Berkeley nor the 221 | names of its contributors may be used to endorse or promote products 222 | derived from this software without specific prior written permission. 223 | 224 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY 225 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 226 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 227 | DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY 228 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 229 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 230 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 231 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 232 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 233 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 234 | 235 | 236 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | module ( 2 | name = "bili_redis_sdk_cpp", 3 | version = "1.0.0", 4 | ) 5 | data_deps_ext = use_extension("//third_party:repositories.bzl", "data_deps_ext") 6 | use_repo( 7 | data_deps_ext, 8 | "cpptoml", 9 | "com_github_google_leveldb", 10 | "com_github_brpc_brpc", 11 | "com_github_fmtlib_fmt", 12 | "xxhash", 13 | "com_github_jupp0r_prometheus_cpp", 14 | ) 15 | bazel_dep(name = "bazel_skylib", version = "1.4.0") 16 | bazel_dep(name = "rules_proto", version = "4.0.0") 17 | bazel_dep(name = "rules_cc", version = "0.0.4", dev_dependency = True) 18 | bazel_dep(name = "glog", version = "0.5.0", repo_name = "com_github_google_glog") 19 | bazel_dep(name = "zlib", version = "1.2.13", repo_name = "com_github_madler_zlib") 20 | bazel_dep(name = "protobuf", version = "3.19.6", repo_name = "com_google_protobuf") 21 | bazel_dep(name = "googletest", version = "1.12.1", repo_name = "com_google_googletest") 22 | bazel_dep(name = "rules_pkg", version = "0.7.0") 23 | bazel_dep(name = "platforms", version = "0.0.6") 24 | bazel_dep(name = "gflags", version = "2.2.2", repo_name = "com_github_gflags_gflags") -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 优点 2 | - 支持cluster 模式 3 | - 基于brpc, 支持异步模式 4 | - 支持配置后端节点连接数 5 | - 支持prometheus上报埋点信息 6 | - 解决以下常见问题 7 | 1. 随机初始化种子节点,避免集中发版时导致的部分redis 节点压力过大的问题 8 | 2. 业务超时和后端连接超时解耦 9 | 3. ask/move 命令重试上限 10 | 4. 限制单Redis 节点(server)的连接数量 11 | 5. cluster node/slot 单飞,避免过度刷新导致后端节点压力过大 12 | 6. 连接主动保活 13 | 14 | 15 | # 使用限制 16 | - 只支持cluster模式 17 | - 只支持读主 18 | - 不支持dump/keys/scan/msetnx/sdiff/siner/sunion/pipeline/eval 命令 19 | - exec 命令中不支持多余的空格(使用空格进行命令切分) 20 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "bili_redis_sdk_cpp") 2 | -------------------------------------------------------------------------------- /bazel/config/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//lib:selects.bzl", "selects") 2 | 3 | config_setting( 4 | name = "without_prometheus", 5 | define_values = {"WITHOUT_PROMETHEUS": "true"}, 6 | visibility = ["//visibility:public"], 7 | ) 8 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # 0.1.0 2 | * 初始版本 3 | -------------------------------------------------------------------------------- /format_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | find ./ -name "*.cpp" -or -name "*.h" -or -name "*.hpp" -or -name "*.cc" -or -name "*.cxx" |grep -v build/ |grep -v third | xargs clang-format -i 4 | -------------------------------------------------------------------------------- /gen_coverage_report.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mkdir -p coverage 3 | COVERAGE_FILE=coverage/info 4 | REPORT_FOLDER=coverage/report 5 | lcov --rc lcov_branch_coverage=1 -c -d build -o ${COVERAGE_FILE}_tmp 6 | lcov --rc lcov_branch_coverage=1 -e ${COVERAGE_FILE}_tmp "*cpp" "*h" -o ${COVERAGE_FILE} 7 | genhtml --rc genhtml_branch_coverage=1 ${COVERAGE_FILE} -o ${REPORT_FOLDER} 8 | -------------------------------------------------------------------------------- /redis_sdk/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") 2 | 3 | package(default_visibility = ["//visibility:private"]) 4 | 5 | _COPTS = select({ 6 | "//bazel/config:without_prometheus": [], 7 | "//conditions:default": ["-DWITH_PROMETHEUS"], 8 | }) 9 | 10 | cc_library( 11 | name = "redis_sdk_api", 12 | hdrs = glob(["include/redis_sdk/**/*.h", "include/redis_sdk/**/*.hpp"]), 13 | copts = _COPTS, 14 | strip_include_prefix = "include", 15 | visibility = ["//:__subpackages__"], 16 | deps = [ 17 | "@com_github_brpc_brpc//:brpc", 18 | "@com_google_protobuf//:protobuf_lite", 19 | ], 20 | ) 21 | 22 | cc_library( 23 | name = "redis_sdk_impl", 24 | srcs = glob(["src/**/*.cpp"]), 25 | hdrs = glob(["include/**/*.h", "include/**/*.hpp"]), 26 | copts = _COPTS, 27 | visibility = ["//:__subpackages__"], 28 | deps = [ 29 | ":redis_sdk_api", 30 | "@com_github_brpc_brpc//:brpc", 31 | "@com_github_fmtlib_fmt//:fmt", 32 | "@com_github_google_glog//:glog", 33 | "@com_google_protobuf//:protobuf_lite", 34 | "@xxhash", 35 | ] + select({ 36 | "//bazel/config:without_prometheus": [], 37 | "//conditions:default": ["@com_github_jupp0r_prometheus_cpp//core"], 38 | }), 39 | ) 40 | 41 | cc_library( 42 | name = "redis_sdk", 43 | copts = _COPTS, 44 | implementation_deps = [":redis_sdk_impl"], 45 | visibility = ["//visibility:public"], 46 | deps = [":redis_sdk_api"], 47 | ) 48 | 49 | cc_binary( 50 | name = "async_example", 51 | srcs = ["async_example.cpp"], 52 | copts = _COPTS, 53 | deps = [":redis_sdk_impl"], 54 | ) 55 | 56 | cc_binary( 57 | name = "client_bench", 58 | srcs = ["client_bench.cpp"], 59 | copts = _COPTS, 60 | deps = [":redis_sdk_impl"], 61 | ) 62 | 63 | cc_binary( 64 | name = "client_tool", 65 | srcs = ["client_tool.cpp"], 66 | copts = _COPTS, 67 | deps = [":redis_sdk_impl"], 68 | ) 69 | 70 | cc_binary( 71 | name = "example", 72 | srcs = ["example.cpp"], 73 | copts = _COPTS, 74 | deps = [":redis_sdk_impl"], 75 | ) 76 | -------------------------------------------------------------------------------- /redis_sdk/async_example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "redis_sdk/client.h" 14 | #include "redis_sdk/include/api_util.h" 15 | 16 | namespace rsdk { 17 | namespace client { 18 | 19 | DEFINE_int32(req_depth, 1000, "pending request count"); 20 | DEFINE_int32(producer_cnt, 10, "producer thread cnt"); 21 | DEFINE_int32(key_per_thread, 1024 * 1024, "key per producer thread"); 22 | DEFINE_int32(batch_cnt, 10, "batch cnt"); 23 | DEFINE_bool(get_op, true, "get operation or not"); 24 | DEFINE_string(seed_nodes, "127.0.0.1:21010", "redis cluster node"); 25 | 26 | bvar::LatencyRecorder g_latency("put_latency"); 27 | 28 | class Example { 29 | public: 30 | Example() { 31 | } 32 | ~Example() { 33 | _client_mgr->stop(); 34 | _client_mgr->join(); 35 | _client_mgr = nullptr; 36 | } 37 | bool init(); 38 | void submit_in_thread(uint64_t start_key, uint64_t key_cnt); 39 | void submit(); 40 | 41 | private: 42 | std::atomic _pending = {0}; 43 | std::atomic _finished = {0}; 44 | std::string _cname = "cluster1"; 45 | ClientManagerPtr _client_mgr; 46 | }; 47 | 48 | bool Example::init() { 49 | _client_mgr = new_client_mgr(); 50 | ClusterOptions coptions; 51 | coptions._seeds.push_back(FLAGS_seed_nodes); 52 | coptions._name = _cname; 53 | coptions._bg_refresh_interval_s = 1; 54 | coptions._server_options._conn_per_node = 1; 55 | Status ret = _client_mgr->add_cluster(coptions); 56 | if (!ret.ok()) { 57 | LOG(ERROR) << "failed to init cluster"; 58 | return false; 59 | } 60 | return _client_mgr != nullptr; 61 | } 62 | 63 | class PutClosure : public google::protobuf::Closure { 64 | public: 65 | PutClosure(std::atomic* in_queue) : _queue_size(in_queue) { 66 | } 67 | ~PutClosure() { 68 | } 69 | void Run() override { 70 | std::unique_ptr release(this); 71 | int cnt = (int)_records.size(); 72 | _queue_size->fetch_sub(cnt, std::memory_order_relaxed); 73 | if (cnt == 0) { 74 | return; 75 | } 76 | Record& record = _records[0]; 77 | if (record._errno != 0) { 78 | std::string msg = record._err_msg; 79 | if (msg.find("TIMEOUT") != 0 && msg.find("CLUSTERDOWN") != 0) { 80 | LOG(ERROR) << "unexpect error msg:" << msg << " of set"; 81 | } 82 | bthread_usleep(1 * 1000L); 83 | } 84 | g_latency << _cost.cost_us(); 85 | } 86 | RecordList* mutable_records() { 87 | return &_records; 88 | } 89 | 90 | private: 91 | RecordList _records; 92 | std::atomic* _queue_size = nullptr; 93 | TimeCost _cost; 94 | }; 95 | 96 | void log_response(const brpc::RedisResponse& resp); 97 | void Example::submit_in_thread(uint64_t start_key, uint64_t key_cnt) { 98 | AccessOptions opts; 99 | opts._cname = _cname; 100 | TimeCost cost; 101 | 102 | auto client = _client_mgr->new_client(opts); 103 | if (!client) { 104 | LOG(ERROR) << "failed to get client"; 105 | return; 106 | } 107 | uint64_t key = start_key; 108 | for (uint64_t index = 0; index < key_cnt;) { 109 | int cur_pending = _pending.load(std::memory_order_relaxed); 110 | int to_submit = FLAGS_req_depth > cur_pending ? (FLAGS_req_depth - cur_pending) : 0; 111 | to_submit = std::min(FLAGS_batch_cnt, to_submit); 112 | PutClosure* done = new PutClosure(&_pending); 113 | RecordList* records = done->mutable_records(); 114 | for (int i = 0; i < to_submit; ++i) { 115 | Record record; 116 | record._key = fmt::format("{}", key); 117 | record._data = record._key; 118 | record._errno = 100; 119 | ++_pending; 120 | records->push_back(std::move(record)); 121 | ++key; 122 | } 123 | if (FLAGS_get_op) { 124 | client->mget(records, done); 125 | } else { 126 | client->mset(records, done); 127 | } 128 | index += to_submit; 129 | if (0 == to_submit) { 130 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 131 | } 132 | } 133 | ++_finished; 134 | } 135 | 136 | void Example::submit() { 137 | std::vector threads; 138 | uint64_t start_key = 0; 139 | uint64_t per_thread = (UINT64_MAX - 100) / FLAGS_producer_cnt; 140 | if (FLAGS_key_per_thread > 0) { 141 | per_thread = FLAGS_key_per_thread; 142 | } 143 | for (int i = 0; i < FLAGS_producer_cnt; ++i) { 144 | threads.emplace_back(&Example::submit_in_thread, this, start_key, per_thread); 145 | start_key += per_thread; 146 | } 147 | while (_finished.load(std::memory_order_relaxed) < FLAGS_producer_cnt) { 148 | std::cout << "async access at qps:" << g_latency.qps(1) 149 | << " avg latency:" << g_latency.latency(1) << "us" << std::endl; 150 | sleep(1); 151 | } 152 | for (auto& th : threads) { 153 | th.join(); 154 | } 155 | } 156 | 157 | } // namespace client 158 | } // namespace rsdk 159 | 160 | DECLARE_string(flagfile); 161 | DECLARE_int32(logbufsecs); // defined in glog 162 | DECLARE_int32(minloglevel); // defined in glog 163 | DECLARE_int32(v); // defined in glog 164 | DECLARE_string(log_dir); // defined in glog 165 | 166 | int main(int argc, char* argv[]) { 167 | // init base 168 | FLAGS_logbufsecs = 10; 169 | FLAGS_minloglevel = 0; 170 | FLAGS_v = 0; 171 | FLAGS_log_dir = "log"; 172 | google::ParseCommandLineFlags(&argc, &argv, true); 173 | 174 | ::mkdir(FLAGS_log_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); 175 | 176 | google::InitGoogleLogging("api_async_example"); 177 | rsdk::client::Example example; 178 | 179 | if (!example.init()) { 180 | LOG(INFO) << "init client failed"; 181 | return -1; 182 | } 183 | example.submit(); 184 | return 0; 185 | } 186 | -------------------------------------------------------------------------------- /redis_sdk/client_bench.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "redis_sdk/client.h" 13 | #include "redis_sdk/include/api_util.h" 14 | 15 | namespace rsdk { 16 | namespace client { 17 | 18 | DEFINE_int32(thread_cnt, 10, "thread_cnt"); 19 | DEFINE_int32(conn_per_node, 1, "connection cnt per node"); 20 | DEFINE_int32(key_per_thread, 1024 * 1024, "key per thread"); 21 | DEFINE_string(seeds, "127.0.0.1:21010", "seed node"); 22 | 23 | bvar::LatencyRecorder g_latency("put_latency"); 24 | 25 | class Benchmark { 26 | public: 27 | Benchmark() { 28 | } 29 | ~Benchmark() { 30 | _client_mgr->stop(); 31 | _client_mgr->join(); 32 | } 33 | bool init(); 34 | void run(); 35 | void start_bench(); 36 | 37 | private: 38 | std::atomic _finished = {0}; 39 | std::string _cname = "cluster1"; 40 | ClientManagerPtr _client_mgr; 41 | }; 42 | 43 | bool Benchmark::init() { 44 | _client_mgr = new_client_mgr(); 45 | ClusterOptions coptions; 46 | coptions._seeds.push_back(FLAGS_seeds); 47 | coptions._name = _cname; 48 | coptions._server_options._conn_per_node = FLAGS_conn_per_node; 49 | Status ret = _client_mgr->add_cluster(coptions); 50 | if (!ret.ok()) { 51 | LOG(ERROR) << "failed to init cluster"; 52 | return false; 53 | } 54 | return _client_mgr != nullptr; 55 | } 56 | struct ThreadTask { 57 | uint64_t _start_key = 0; 58 | uint64_t _key_cnt = 0; 59 | ClientPtr _client; 60 | int _id = 0; 61 | std::atomic* _counter = nullptr; 62 | }; 63 | 64 | void log_response(const brpc::RedisResponse& resp); 65 | void* run_in_thread(void* arg) { 66 | ThreadTask* task = static_cast(arg); 67 | std::unique_ptr task_guard(task); 68 | uint64_t start_key = task->_start_key; 69 | for (uint64_t index = 0; index < task->_key_cnt; ++index) { 70 | TimeCost cost; 71 | uint64_t key = start_key + index % task->_key_cnt; 72 | RecordList records; 73 | Record record; 74 | record._key = fmt::format("{}", key); 75 | record._data = fmt::format("{}", key); 76 | records.push_back(std::move(record)); 77 | task->_client->mset(&records, nullptr); 78 | if (records[0]._errno != 0) { 79 | bthread_usleep(1 * 1000L); 80 | } 81 | g_latency << cost.cost_us(); 82 | } 83 | ++(*task->_counter); 84 | return nullptr; 85 | } 86 | void Benchmark::run() { 87 | std::thread th(&Benchmark::start_bench, this); 88 | while (_finished.load() < FLAGS_thread_cnt) { 89 | sleep(1); 90 | std::cout << "qps:" << g_latency.qps(1) << " avg latency:" << g_latency.latency(1) << "us" 91 | << std::endl; 92 | } 93 | th.join(); 94 | } 95 | 96 | void Benchmark::start_bench() { 97 | AccessOptions opts; 98 | opts._cname = _cname; 99 | TimeCost cost; 100 | std::vector threads; 101 | auto client = _client_mgr->new_client(opts); 102 | if (!client) { 103 | LOG(ERROR) << "failed to get client"; 104 | return; 105 | } 106 | uint64_t start_key = 0; 107 | uint64_t per_thread = (UINT64_MAX - 100) / FLAGS_thread_cnt; 108 | if (FLAGS_key_per_thread > 0) { 109 | per_thread = FLAGS_key_per_thread; 110 | } 111 | for (int i = 0; i < FLAGS_thread_cnt; ++i) { 112 | bthread_t th; 113 | ThreadTask* task = new ThreadTask(); 114 | AccessOptions opts; 115 | task->_start_key = start_key; 116 | task->_key_cnt = per_thread; 117 | task->_client = client; 118 | task->_counter = &_finished; 119 | start_key += per_thread; 120 | bthread_start_background(&th, nullptr, run_in_thread, task); 121 | threads.push_back((th)); 122 | } 123 | for (auto& th : threads) { 124 | bthread_join(th, nullptr); 125 | } 126 | LOG(INFO) << "all done, exit now"; 127 | } 128 | 129 | } // namespace client 130 | } // namespace rsdk 131 | 132 | DECLARE_string(flagfile); 133 | DECLARE_int32(logbufsecs); // defined in glog 134 | DECLARE_int32(minloglevel); // defined in glog 135 | DECLARE_int32(v); // defined in glog 136 | DECLARE_string(log_dir); // defined in glog 137 | 138 | int main(int argc, char* argv[]) { 139 | // init base 140 | FLAGS_logbufsecs = 10; 141 | FLAGS_minloglevel = 0; 142 | FLAGS_v = 0; 143 | FLAGS_log_dir = "log"; 144 | google::ParseCommandLineFlags(&argc, &argv, true); 145 | 146 | ::mkdir(FLAGS_log_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); 147 | 148 | google::InitGoogleLogging("client_bench"); 149 | rsdk::client::Benchmark benchmark; 150 | 151 | if (!benchmark.init()) { 152 | LOG(INFO) << "init client failed"; 153 | return -1; 154 | } 155 | benchmark.run(); 156 | return 0; 157 | } 158 | -------------------------------------------------------------------------------- /redis_sdk/client_tool.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "redis_sdk/client.h" 13 | #include "redis_sdk/include/api_util.h" 14 | #include "redis_sdk/include/client_impl.h" 15 | #include "redis_sdk/include/cluster.h" 16 | #include "redis_sdk/include/slot.h" 17 | 18 | namespace rsdk { 19 | namespace client { 20 | 21 | DEFINE_string(cluster_seeds, "127.0.0.1:21010", "seed of cluster"); 22 | DEFINE_bool(print_slot, true, "print log slot details"); 23 | DEFINE_bool(print_cluster_node_from_every_node, true, "print cluster node resp"); 24 | 25 | class ClientTool { 26 | public: 27 | ClientTool() { 28 | } 29 | bool init(); 30 | void print_nodes(); 31 | void print_slot(); 32 | void print_cluster_nodes(); 33 | void run(); 34 | 35 | private: 36 | std::string _cname = "cluster1"; 37 | ClientManagerPtr _client_mgr; 38 | ClusterPtr _cluster; 39 | }; 40 | 41 | bool ClientTool::init() { 42 | _client_mgr = new_client_mgr(); 43 | ClusterOptions coptions; 44 | coptions._seeds.push_back(FLAGS_cluster_seeds); 45 | coptions._name = _cname; 46 | coptions._bg_refresh_interval_s = 1; 47 | coptions._server_options._conn_per_node = 4; 48 | Status ret = _client_mgr->add_cluster(coptions); 49 | if (!ret.ok()) { 50 | LOG(ERROR) << "failed to init cluster from seed node:" << FLAGS_cluster_seeds; 51 | return false; 52 | } 53 | ClientManagerImpl* cli_mgr = dynamic_cast(_client_mgr.get()); 54 | if (!cli_mgr) { 55 | LOG(ERROR) << "bug, failed to cast client_mgr to ClientManagerImpl"; 56 | } 57 | _cluster = cli_mgr->get_cluster_mgr()->get(_cname); 58 | if (!_cluster) { 59 | LOG(ERROR) << "bug, failed to get cluster from seed node:" << FLAGS_cluster_seeds; 60 | return false; 61 | } 62 | return true; 63 | } 64 | 65 | void print_reply(const brpc::RedisReply& reply) { 66 | if (reply.is_string()) { 67 | std::cout << "reply is str:[" << reply.c_str() << "]" << std::endl; 68 | return; 69 | } 70 | if (reply.is_error()) { 71 | std::cout << "reply is error:[" << reply.error_message() << "]" << std::endl; 72 | return; 73 | } 74 | if (reply.is_integer()) { 75 | std::cout << "reply is integer:[" << reply.integer() << "]" << std::endl; 76 | return; 77 | } 78 | if (reply.is_array()) { 79 | for (size_t j = 0; j < reply.size(); ++j) { 80 | const brpc::RedisReply& sub = reply[j]; 81 | print_reply(sub); 82 | } 83 | return; 84 | } 85 | std::cout << "unknown reply:[" << brpc::RedisReplyTypeToString(reply.type()) << "]" 86 | << std::endl; 87 | } 88 | 89 | void print_response(const brpc::RedisResponse& resp) { 90 | for (int i = 0; i < resp.reply_size(); ++i) { 91 | print_reply(resp.reply(i)); 92 | } 93 | } 94 | 95 | void ClientTool::print_nodes() { 96 | std::vector ips = _cluster->get_servers(); 97 | for (auto& ip : ips) { 98 | auto server = _cluster->get_node(ip); 99 | if (!server) { 100 | std::cout << "server:" << ip << " is not found, maybe removed" << std::endl; 101 | } else { 102 | std::cout << "server:[" << server->to_str() << "]" << std::endl; 103 | } 104 | } 105 | } 106 | 107 | void ClientTool::print_slot() { 108 | if (!FLAGS_print_slot) { 109 | return; 110 | } 111 | for (int id = 0; id < (int)S_SLOT_CNT; ++id) { 112 | SlotPtr slot = _cluster->get_slot_by_id(id); 113 | if (!slot) { 114 | std::cout << "slot:" << id << " is not found in cluster" << std::endl; 115 | continue; 116 | } 117 | std::cout << "slot:" << slot->describe() << std::endl; 118 | } 119 | } 120 | void ClientTool::print_cluster_nodes() { 121 | if (!FLAGS_print_cluster_node_from_every_node) { 122 | return; 123 | } 124 | std::vector ips = _cluster->get_servers(); 125 | for (auto& ip : ips) { 126 | auto server = _cluster->get_node(ip); 127 | if (!server) { 128 | std::cout << "server:" << ip << " is not found, maybe removed" << std::endl; 129 | } else { 130 | std::string cmd = "cluster nodes"; 131 | brpc::RedisResponse resp; 132 | Status ret = server->send_inner_cmd(cmd, &resp, 0); 133 | if (!ret.ok()) { 134 | std::cout << "failed to send cmd:" << cmd << "] to " << ip << ", got error:" << ret 135 | << std::endl; 136 | continue; 137 | } 138 | print_response(resp); 139 | } 140 | } 141 | } 142 | 143 | void ClientTool::run() { 144 | bool ret = init(); 145 | if (!ret) { 146 | return; 147 | } 148 | print_nodes(); 149 | print_slot(); 150 | print_cluster_nodes(); 151 | } 152 | 153 | } // namespace client 154 | } // namespace rsdk 155 | 156 | DECLARE_string(flagfile); 157 | DECLARE_int32(logbufsecs); // defined in glog 158 | DECLARE_int32(minloglevel); // defined in glog 159 | DECLARE_int32(v); // defined in glog 160 | DECLARE_string(log_dir); // defined in glog 161 | 162 | int main(int argc, char* argv[]) { 163 | // init base 164 | FLAGS_logbufsecs = 0; 165 | FLAGS_minloglevel = 0; 166 | FLAGS_v = 3; 167 | FLAGS_log_dir = "log"; 168 | google::ParseCommandLineFlags(&argc, &argv, true); 169 | 170 | ::mkdir(FLAGS_log_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); 171 | 172 | google::InitGoogleLogging("client_tool"); 173 | rsdk::client::ClientTool tool; 174 | 175 | tool.run(); 176 | return 0; 177 | } 178 | -------------------------------------------------------------------------------- /redis_sdk/conf/api_example_gflags.conf: -------------------------------------------------------------------------------- 1 | --log_dir=./example_log 2 | --logbufsecs=0 3 | --minloglevel=0 4 | --cluster_name=test_table 5 | --cluster_token=test_token 6 | --stop_logging_if_full_disk=true 7 | --v=0 8 | --alsologtostderr=0 9 | -------------------------------------------------------------------------------- /redis_sdk/example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "redis_sdk/client.h" 13 | #include "redis_sdk/include/api_util.h" 14 | 15 | namespace rsdk { 16 | namespace client { 17 | 18 | class Example { 19 | public: 20 | Example() { 21 | } 22 | ~Example() { 23 | _client_mgr->stop(); 24 | _client_mgr->join(); 25 | _client_mgr = nullptr; 26 | } 27 | bool init(); 28 | void crud(); 29 | void mset_mget(); 30 | 31 | private: 32 | std::string _cname = "cluster1"; 33 | ClientManagerPtr _client_mgr; 34 | }; 35 | 36 | bool Example::init() { 37 | _client_mgr = new_client_mgr(); 38 | ClusterOptions coptions; 39 | coptions._seeds.push_back("127.0.0.1:21010"); 40 | coptions._name = _cname; 41 | coptions._bg_refresh_interval_s = 1; 42 | coptions._server_options._conn_per_node = 4; 43 | Status ret = _client_mgr->add_cluster(coptions); 44 | if (!ret.ok()) { 45 | LOG(ERROR) << "failed to init cluster"; 46 | return false; 47 | } 48 | return _client_mgr != nullptr; 49 | } 50 | void Example::mset_mget() { 51 | RecordList put_records; 52 | RecordList get_records; 53 | int cnt = 50; 54 | std::string prefix = "prefix"; 55 | for (int i = 0; i < cnt; ++i) { 56 | Record rec; 57 | rec._key = fmt::format("{}_{}", prefix, i); 58 | rec._data = rec._key; 59 | rec._ttl_s = 5; 60 | Record get; 61 | get._key = rec._key; 62 | put_records.emplace_back(rec); 63 | get_records.emplace_back(get); 64 | } 65 | AccessOptions opts; 66 | opts._cname = _cname; 67 | TimeCost cost; 68 | 69 | auto client = _client_mgr->new_client(opts); 70 | if (!client) { 71 | LOG(ERROR) << "failed to get client"; 72 | return; 73 | } 74 | std::cout << "-------start mget while expect empty--------" << std::endl; 75 | client->mget(&get_records, nullptr); 76 | for (int i = 0; i < cnt; ++i) { 77 | Record& rec = get_records[i]; 78 | if (get_records[i]._errno != 0) { 79 | std::cout << "failed to get key:" << rec._key << " return err:" << rec._errno 80 | << " msg:" << rec._err_msg << std::endl; 81 | continue; 82 | } 83 | if (rec._data.size() != 0) { 84 | std::cout << "key:" << rec._key << " value is:" << rec._data.size() 85 | << " while expect nil" << std::endl; 86 | continue; 87 | } 88 | // std::cout << "succ to get key:" << rec._key << " with value size is 0" << std::endl; 89 | } 90 | std::cout << "-------start mset --------" << std::endl; 91 | client->mset(&put_records, nullptr); 92 | for (int i = 0; i < cnt; ++i) { 93 | Record& rec = put_records[i]; 94 | if (rec._errno != 0) { 95 | std::cout << "failed to put key:" << rec._key << " return err:" << rec._errno 96 | << " msg:" << rec._err_msg << std::endl; 97 | continue; 98 | } 99 | // std::cout << "succ to put key:" << rec._key << std::endl; 100 | } 101 | std::cout << "-------start mget, expect to get all --------" << std::endl; 102 | client->mget(&get_records, nullptr); 103 | for (int i = 0; i < cnt; ++i) { 104 | Record& rec = get_records[i]; 105 | if (get_records[i]._errno != 0) { 106 | std::cout << "failed to get key:" << rec._key << " return err:" << rec._errno 107 | << " msg:" << rec._err_msg << std::endl; 108 | continue; 109 | } 110 | std::string expect = fmt::format("{}_{}", prefix, i); 111 | if (rec._data != expect) { 112 | std::cout << "failed to get key:" << rec._key << " value is:" << rec._data 113 | << " while expect:" << expect << std::endl; 114 | continue; 115 | } 116 | // std::cout << "succ to get key:" << rec._key << " with value:" << rec._data << std::endl; 117 | } 118 | std::cout << "-------wait 7 seconds to expire all --------" << std::endl; 119 | int wait_for = 7 * 1000; 120 | std::this_thread::sleep_for(std::chrono::milliseconds(wait_for)); 121 | get_records.clear(); 122 | for (auto& rec : put_records) { 123 | Record get; 124 | get._key = rec._key; 125 | get_records.emplace_back(get); 126 | } 127 | std::cout << "-------start to mget, expect all empty --------" << std::endl; 128 | SyncClosure sync; 129 | client->mget(&get_records, &sync); 130 | sync.wait(); 131 | for (int i = 0; i < cnt; ++i) { 132 | Record& rec = get_records[i]; 133 | if (get_records[i]._errno != 0) { 134 | std::cout << "failed to get key:" << rec._key << " return err:" << rec._errno 135 | << " msg:" << rec._err_msg << std::endl; 136 | continue; 137 | } 138 | std::string expect; 139 | if (!rec._data.empty()) { 140 | std::cout << "failed to get key:" << rec._key << " value is:" << rec._data 141 | << " while expect ''" << std::endl; 142 | continue; 143 | } 144 | // std::cout << "succ to get key:" << rec._key << " with value is empty:" << std::endl; 145 | } 146 | } 147 | 148 | void log_response(const brpc::RedisResponse& resp); 149 | void Example::crud() { 150 | AccessOptions opts; 151 | opts._cname = _cname; 152 | TimeCost cost; 153 | 154 | brpc::RedisResponse put_resp; 155 | std::string cmd = fmt::format("set {} {}", 1, 1000); 156 | auto client = _client_mgr->new_client(opts); 157 | if (!client) { 158 | LOG(ERROR) << "failed to get client"; 159 | return; 160 | } 161 | client->exec(cmd, &put_resp, nullptr); 162 | log_response(put_resp); 163 | 164 | cmd = fmt::format("get {}", 1); 165 | brpc::RedisResponse get_resp; 166 | client->exec(cmd, &get_resp, nullptr); 167 | log_response(get_resp); 168 | 169 | cmd = fmt::format("hset site redis redis.com"); 170 | SyncClosure sync1; 171 | brpc::RedisResponse hset_resp; 172 | client->exec(cmd, &hset_resp, &sync1); 173 | sync1.wait(); 174 | log_response(hset_resp); 175 | 176 | cmd = fmt::format("hget site redis"); 177 | brpc::RedisResponse hget_resp; 178 | SyncClosure sync2; 179 | client->exec(cmd, &hget_resp, &sync2); 180 | sync2.wait(); 181 | log_response(hget_resp); 182 | 183 | // renamenx/rename/eval/msetnx is not support 184 | cmd = fmt::format("mset 1 2"); 185 | brpc::RedisResponse mset_resp; 186 | SyncClosure sync3; 187 | client->exec(cmd, &mset_resp, &sync3); 188 | sync3.wait(); 189 | log_response(mset_resp); 190 | { 191 | std::string key = std::string("key_") + std::to_string(butil::fast_rand() % 100000); 192 | cmd = fmt::format("smembers " + key); 193 | brpc::RedisResponse smember_resp; 194 | SyncClosure sync4; 195 | client->exec(cmd, &smember_resp, &sync4); 196 | sync4.wait(); 197 | log_response(smember_resp); 198 | 199 | cmd = fmt::format("sadd {} x1 x2 123", key); 200 | brpc::RedisResponse resp2; 201 | SyncClosure sync5; 202 | client->exec(cmd, &resp2, &sync5); 203 | sync5.wait(); 204 | log_response(resp2); 205 | 206 | cmd = fmt::format("smembers {}", key); 207 | brpc::RedisResponse resp3; 208 | SyncClosure sync6; 209 | client->exec(cmd, &resp3, &sync6); 210 | sync6.wait(); 211 | log_response(resp3); 212 | } 213 | } 214 | 215 | } // namespace client 216 | } // namespace rsdk 217 | 218 | DECLARE_string(flagfile); 219 | DECLARE_int32(logbufsecs); // defined in glog 220 | DECLARE_int32(minloglevel); // defined in glog 221 | DECLARE_int32(v); // defined in glog 222 | DECLARE_string(log_dir); // defined in glog 223 | 224 | int main(int argc, char* argv[]) { 225 | // init base 226 | FLAGS_logbufsecs = 0; 227 | FLAGS_minloglevel = 0; 228 | FLAGS_v = 3; 229 | FLAGS_log_dir = "log"; 230 | google::ParseCommandLineFlags(&argc, &argv, true); 231 | 232 | ::mkdir(FLAGS_log_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); 233 | 234 | google::InitGoogleLogging("api_example"); 235 | rsdk::client::Example example; 236 | 237 | if (!example.init()) { 238 | LOG(INFO) << "init client failed"; 239 | return -1; 240 | } 241 | example.crud(); 242 | example.mset_mget(); 243 | return 0; 244 | } 245 | -------------------------------------------------------------------------------- /redis_sdk/include/api_util.h: -------------------------------------------------------------------------------- 1 | #ifndef RSDK_API_INCLUDE_API_UTIL_H 2 | #define RSDK_API_INCLUDE_API_UTIL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace rsdk { 24 | namespace client { 25 | 26 | class SyncClosure : public google::protobuf::Closure { 27 | public: 28 | SyncClosure(int initial_count = 1) : _event(initial_count) { 29 | } 30 | ~SyncClosure() { 31 | } 32 | void Run() override { 33 | _event.signal(); 34 | } 35 | void wait() { 36 | _event.wait(); 37 | } 38 | void incr(int v = 1) { 39 | _event.add_count(v); 40 | } 41 | void reset(int count = 1) { 42 | _event.reset(count); 43 | } 44 | 45 | private: 46 | bthread::CountdownEvent _event; 47 | }; 48 | 49 | class TimeCost { 50 | public: 51 | TimeCost() { 52 | _start = butil::cpuwide_time_ns(); 53 | } 54 | // return time cost since last call, in mili seconds 55 | uint64_t cost_ms() { 56 | return cost_us() / 1000; 57 | } 58 | uint64_t cost_us() { 59 | uint64_t now = butil::cpuwide_time_ns(); 60 | uint64_t cost = now - _start; 61 | _start = now; 62 | return cost / 1000; 63 | } 64 | 65 | virtual ~TimeCost() { 66 | } 67 | 68 | private: 69 | uint64_t _start; 70 | }; 71 | 72 | uint64_t get_current_time_us(); 73 | uint64_t get_current_time_s(); 74 | uint64_t get_current_time_ms(); 75 | std::string encode_key(const std::string& key); 76 | 77 | } // namespace client 78 | } // namespace rsdk 79 | 80 | #endif // RSDK_API_INCLUDE_API_UTIL_H 81 | -------------------------------------------------------------------------------- /redis_sdk/include/client_impl.h: -------------------------------------------------------------------------------- 1 | #ifndef RSDK_API_INCLUDE_CLIENT_IMPL_H 2 | #define RSDK_API_INCLUDE_CLIENT_IMPL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "redis_sdk/client.h" 13 | #include "redis_sdk/include/cluster.h" 14 | #include "redis_sdk/include/metric.h" 15 | #include "redis_sdk/include/pipeline.h" 16 | 17 | namespace rsdk { 18 | namespace client { 19 | 20 | void parse_get_reply(const brpc::RedisReply& reply, Record* rec); 21 | class BatchClosure; 22 | class ClientImpl : public Client { 23 | public: 24 | void mget(RecordList* records, ::google::protobuf::Closure* done = nullptr) override; 25 | void mget(const RecordList& records, std::vector* resps, 26 | ::google::protobuf::Closure* done = nullptr) override; 27 | void mset(RecordList* records, ::google::protobuf::Closure* done = nullptr) override; 28 | void mset(const RecordList& records, std::vector* resps, 29 | ::google::protobuf::Closure* done = nullptr) override; 30 | 31 | void exec(const brpc::RedisRequest& req, brpc::RedisResponse* reply, 32 | ::google::protobuf::Closure* done = nullptr) override; 33 | void exec(const std::string& cmd, brpc::RedisResponse* reply, 34 | ::google::protobuf::Closure* done = nullptr) override; 35 | 36 | private: 37 | friend class ClientManagerImpl; 38 | explicit ClientImpl(const AccessOptions& options, ClusterPtr cptr, uint64_t manager_id) : 39 | _options(options), 40 | _cluster(cptr), 41 | _manager_id(manager_id) { 42 | } 43 | ClientImpl(const ClientImpl& opt, ClusterMgrPtr cluster_mgr) = delete; 44 | ClientImpl& operator=(const ClientImpl& cli) = delete; 45 | 46 | void mget_impl(const RecordList& records, BatchClosure* batch, 47 | ::google::protobuf::Closure* done); 48 | void mset_impl(const RecordList& records, BatchClosure* batch, 49 | ::google::protobuf::Closure* done); 50 | bool valid_cmd(const std::string& cmd); 51 | bool check_and_parse(const brpc::RedisRequest& req, brpc::RedisCommandParser* parser, 52 | butil::Arena* arena, std::string* cmd, 53 | std::vector* args, std::string* msg, int* cmd_cnt, 54 | int* arg_cnt); 55 | 56 | bool is_batch_command(const std::string& cmd_name); 57 | int get_standard_input_count(const std::string& cmd_name); 58 | 59 | AccessOptions _options; 60 | ClusterPtr _cluster; 61 | uint64_t _manager_id; 62 | }; 63 | 64 | class ClientManagerImpl : public ClientManager { 65 | public: 66 | ClientManagerImpl(); 67 | explicit ClientManagerImpl(const ClientMgrOptions& options); 68 | ~ClientManagerImpl(); 69 | 70 | ClusterMgrPtr get_cluster_mgr(); 71 | ClientPtr new_client(const AccessOptions& opt) override; 72 | 73 | Status add_cluster(const ClusterOptions& options) override; 74 | bool has_cluster(const std::string& name) override; 75 | void set_cluster_state(const std::string& name, bool online) override; 76 | void stop() override; 77 | void join() override; 78 | void bg_work(); 79 | void dump_big_key(ClusterKeys* cluster_big_keys); 80 | 81 | private: 82 | ClientMgrOptions _options; 83 | std::atomic _reply_checked = {false}; 84 | std::atomic _reply_passed = {false}; 85 | ClusterMgrPtr _cluster_mgr; 86 | bool _is_inited = false; 87 | uint64_t _id = 0; 88 | bthread_t _bg_worker; 89 | bool _bg_worker_started = false; 90 | std::unique_ptr _metrics; 91 | }; 92 | 93 | class BatchClosure : public google::protobuf::Closure { 94 | public: 95 | BatchClosure(uint64_t start_us, ClusterPtr cluster, const std::string& mcmd, int cmd_cnt, 96 | google::protobuf::Closure* done) : 97 | _cluster(cluster), 98 | _main_cmd(mcmd), 99 | _cmd_cnt(cmd_cnt), 100 | _refs(_cmd_cnt), 101 | _done(done), 102 | _start_time_us(start_us) { 103 | } 104 | ~BatchClosure() override { 105 | *(_cluster->mutable_batch_cnt()) << _cmd_cnt; 106 | } 107 | virtual brpc::RedisResponse* mutable_response(size_t i) = 0; 108 | void set_refs(int cnt) { 109 | _refs.store(cnt); 110 | } 111 | bool unref() { 112 | return _refs.fetch_sub(1) == 1; 113 | } 114 | const std::string& main_cmd() const { 115 | return _main_cmd; 116 | } 117 | virtual void set_err(int /*errno*/, const std::string& /*msg*/, int /*index*/) { 118 | } 119 | void set_done(::google::protobuf::Closure* done) { 120 | _done = done; 121 | } 122 | 123 | protected: 124 | ClusterPtr _cluster; 125 | std::string _main_cmd; 126 | int _cmd_cnt = 1; 127 | std::atomic _refs; 128 | ::google::protobuf::Closure* _done = nullptr; 129 | uint64_t _start_time_us = 0; 130 | uint64_t _end_time_us = 0; 131 | }; 132 | 133 | class RefCountClosure : public BatchClosure { 134 | public: 135 | RefCountClosure(uint64_t start_us, ClusterPtr cluster, const std::string& mcmd, int cmd_cnt, 136 | brpc::RedisResponse* resp, google::protobuf::Closure* done) : 137 | BatchClosure(start_us, cluster, mcmd, cmd_cnt, done), 138 | _resp(resp) { 139 | if (_cmd_cnt > 1) { 140 | _more_replys.resize(_cmd_cnt - 1); 141 | } 142 | } 143 | 144 | void set_merge_as_array(bool m) { 145 | _merge_as_array = m; 146 | } 147 | 148 | void Run() override { 149 | if (unref()) { 150 | std::unique_ptr release_this(this); 151 | brpc::ClosureGuard done_guard(_done); 152 | this->merge_response(); 153 | _end_time_us = get_current_time_us(); 154 | uint64_t cost = _end_time_us - _start_time_us; 155 | *(_cluster->mutable_batch_latency()) << cost; 156 | } 157 | } 158 | 159 | void merge_as_array(); 160 | 161 | void merge_response() { 162 | if (_main_cmd == "exists") { 163 | merge_exists_response(); 164 | return; 165 | } 166 | if (_merge_as_array) { 167 | if (_cmd_cnt > 1) { 168 | merge_as_array(); 169 | } 170 | return; 171 | } 172 | for (size_t i = 0; i < _more_replys.size(); ++i) { 173 | _resp->MergeFrom(_more_replys[i]); 174 | } 175 | } 176 | 177 | void merge_exists_response(); 178 | 179 | virtual brpc::RedisResponse* mutable_response(size_t i) { 180 | if (i == 0) { 181 | return _resp; 182 | } 183 | if (i >= (_more_replys.size() + 1)) { 184 | return nullptr; 185 | } 186 | return &(_more_replys[i - 1]); 187 | } 188 | 189 | protected: 190 | brpc::RedisResponse* _resp = nullptr; 191 | std::vector _more_replys; 192 | bool _merge_as_array = false; 193 | }; 194 | 195 | void set_error_to_resp(brpc::RedisResponse* resp, const std::string& msg); 196 | class BatchClosureV2 : public BatchClosure { 197 | public: 198 | BatchClosureV2(uint64_t start_us, ClusterPtr cluster, const std::string& mcmd, int cnt, 199 | const AccessOptions& opt, google::protobuf::Closure* done) : 200 | BatchClosure(start_us, cluster, mcmd, cnt, done), 201 | _acc_options(opt) { 202 | } 203 | void Run() override { 204 | if (unref()) { 205 | std::unique_ptr release_this(this); 206 | brpc::ClosureGuard done_guard(_done); 207 | _end_time_us = get_current_time_us(); 208 | uint64_t cost = _end_time_us - _start_time_us; 209 | *(_cluster->mutable_batch_latency()) << cost; 210 | } 211 | } 212 | PipeCtx* split_to_sub_req(const std::string& cmd, const Record& rec); 213 | PipeCtx* add_or_get(ClusterPtr cluster, RedisServerPtr server, const std::string& cmd, 214 | const Record& rec); 215 | void send_all_sub_req(); 216 | 217 | protected: 218 | brpc::RedisResponse* mutable_response(size_t) { 219 | return nullptr; 220 | } 221 | const AccessOptions& _acc_options; 222 | std::unordered_map _server2req; 223 | std::vector> _sub_reqs; 224 | }; 225 | 226 | } // namespace client 227 | } // namespace rsdk 228 | #endif // RSDK_API_INCLUDE_CLIENT_IMPL_H 229 | -------------------------------------------------------------------------------- /redis_sdk/include/cluster.h: -------------------------------------------------------------------------------- 1 | #ifndef RSDK_API_INCLUDE_CLUSTER_H 2 | #define RSDK_API_INCLUDE_CLUSTER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "redis_sdk/client.h" 18 | #include "redis_sdk/include/metric.h" 19 | #include "redis_sdk/include/slot.h" 20 | 21 | namespace rsdk { 22 | namespace client { 23 | 24 | enum class RedisStatus; 25 | 26 | typedef std::unordered_map KeySet; 27 | class BigKeyMgr { 28 | public: 29 | explicit BigKeyMgr(uint64_t th) : _threshold(th), _keys(nullptr) { 30 | _keys = new butil::DoublyBufferedData(); 31 | } 32 | virtual ~BigKeyMgr() { 33 | delete _keys; 34 | _keys = nullptr; 35 | } 36 | BigKeyMgr(const BigKeyMgr&) = delete; 37 | BigKeyMgr& operator=(const BigKeyMgr&) = delete; 38 | 39 | bool has(uint64_t key); 40 | void add(uint64_t sign, const std::string& key); 41 | void dump(std::unordered_set* keys); 42 | void remove(uint64_t sign); 43 | uint64_t count(); 44 | uint64_t threshold() const; 45 | void update_threshold(uint64_t limit) { 46 | _threshold = limit; 47 | } 48 | 49 | static size_t add_key(KeySet& m, uint64_t sign, const std::string& key); 50 | static size_t remove_key(KeySet& m, uint64_t key); 51 | static size_t clear_all(KeySet& m); 52 | 53 | private: 54 | uint64_t _threshold = 0; 55 | bthread::Mutex _ww_mutex; 56 | butil::DoublyBufferedData* _keys; 57 | std::unique_ptr _read_size; 58 | }; 59 | 60 | struct ClusterNodeState { 61 | std::string _id; 62 | std::string _addr; 63 | ServerRole _role; 64 | std::string _leader_id; 65 | int64_t _config_epoch = -1; 66 | LinkState _link_state = LinkState::DIS_CONNECTED; 67 | std::vector _slot_str; 68 | std::string _murmur_hash; 69 | // 70 | std::string to_str() const; 71 | }; 72 | 73 | class Cluster { 74 | public: 75 | Cluster(const ClusterOptions& options); 76 | virtual ~Cluster(); 77 | void stop(); 78 | void join(); 79 | Status init(); 80 | std::string name() const { 81 | return _options._name; 82 | } 83 | int64_t sn() const { 84 | return _sn.load(); 85 | } 86 | 87 | std::string to_string() const { 88 | return "cluster:" + _options._name; 89 | } 90 | void set_online(bool on); 91 | 92 | SlotPtr get_slot_by_id(uint64_t slot_id) { 93 | return _slot_mgr->get_slot_by_id(slot_id); 94 | } 95 | RedisServerPtr add_and_get_node(const std::string& addr); 96 | 97 | std::vector get_servers(); 98 | std::vector get_servers_for_cluster_nodes(); 99 | RedisServerPtr get_node(const std::string& address); 100 | void update_sn(int64_t new_sn) { 101 | while (true) { 102 | int64_t cur_sn = _sn.load(); 103 | if (new_sn <= cur_sn) { 104 | return; 105 | } 106 | bool ret = _sn.compare_exchange_strong(cur_sn, new_sn); 107 | if (ret) { 108 | return; 109 | } 110 | } 111 | } 112 | Status update_dist(); 113 | Status refresh_dist(bool use_seed); 114 | bool may_update_dist(RedisStatus status, const std::string& msg); 115 | void may_triggle_update_dist(); 116 | bthread::Mutex* mutable_bg_refresh_mutex() { 117 | return &_bg_refresh_mutex; 118 | } 119 | bthread::ConditionVariable* mutable_refresh_cond() { 120 | return &_refresh_cond; 121 | } 122 | ClusterOptions* mutable_options() { 123 | return &_options; 124 | } 125 | bvar::LatencyRecorder* mutable_read_bvar() { 126 | return _read_size.get(); 127 | } 128 | bvar::LatencyRecorder* mutable_write_bvar() { 129 | return _write_size.get(); 130 | } 131 | bvar::LatencyRecorder* mutable_latency_bvar() { 132 | return _latency.get(); 133 | } 134 | 135 | bvar::LatencyRecorder* mutable_batch_latency() { 136 | return _batch_latency.get(); 137 | } 138 | bvar::LatencyRecorder* mutable_batch_cnt() { 139 | return _batch_cnt.get(); 140 | } 141 | bool is_big_key(uint64_t key); 142 | void add_big_key(uint64_t sign, const std::string& key, uint64_t size); 143 | void remove_big_key(uint64_t key); 144 | void dump_big_key(std::unordered_set* keys); 145 | void print_and_auto_tune(); 146 | uint64_t big_key_threshold() const { 147 | return _big_key_mgr.threshold(); 148 | } 149 | void set_client_metrics(ClientMetrics* metrics) { 150 | _metrics = metrics; 151 | } 152 | void add_metrics(const std::string& method, const std::string& server, bool succ, uint64_t us, 153 | int req_size, int resp_size, int cnt = 1); 154 | 155 | private: 156 | // Status parse_cluster_slots(const brpc::RedisReply& resp, SlotMgrPtr slot_mgr); 157 | // Status parse_cluster_slots_sub_reply(const brpc::RedisReply& sub, SlotMgrPtr slot_mgr); 158 | Status parse_node(const std::string& data, ClusterNodeState* state); 159 | Status parse_cluster_nodes(const brpc::RedisResponse& resp, SlotMgrPtr slot_mgr); 160 | Status update_cluster_dist(const std::map>& state, 161 | SlotMgrPtr slot_mgr); 162 | 163 | Status init_cluster_dist(); 164 | Status refresh_cluster_dist_by_cluster_nodes(const std::string& addr, SlotMgrPtr slot_mgr); 165 | // Status refresh_cluster_dist_by_cluster_slots(const std::string& addr, SlotMgrPtr slot_mgr); 166 | 167 | ClusterOptions _options; 168 | std::atomic _sn; 169 | std::string _cluster_digest; 170 | int64_t _last_refresh_time_ms = 0; 171 | int64_t _current_epoch = -1; 172 | 173 | bthread::Mutex _refresh_mutex; 174 | std::atomic _refresh_sn = {100}; 175 | SlotMgrPtr _slot_mgr; 176 | 177 | bthread::Mutex _bg_refresh_mutex; 178 | bthread::ConditionVariable _refresh_cond; 179 | bthread_t _bg_update_dist_thread; 180 | bool _has_bg_update_dist_thread = {false}; 181 | 182 | bthread::Mutex _nodes_mutex; 183 | std::unordered_map _ip2nodes; 184 | bool _use_cluster_slots_for_dist = false; 185 | uint64_t _init_time_ms = 0; 186 | 187 | bool _online = true; 188 | 189 | BigKeyMgr _big_key_mgr; 190 | 191 | // metrics 192 | std::unique_ptr _read_size; 193 | std::unique_ptr _write_size; 194 | std::unique_ptr _big_key_size; 195 | std::unique_ptr _latency; 196 | std::unique_ptr _batch_latency; 197 | std::unique_ptr _batch_cnt; 198 | uint64_t _last_print_time_ms = 0; 199 | uint64_t _last_tune_time_ms = 0; 200 | ClientMetrics* _metrics = nullptr; 201 | }; 202 | 203 | using ClusterPtr = std::shared_ptr; 204 | using ClusterMap = std::unordered_map; 205 | 206 | struct ClusterManagerOptions {}; 207 | 208 | class ClusterManager { 209 | public: 210 | ClusterManager() : _metrics(nullptr) { 211 | } 212 | explicit ClusterManager(ClientMetrics* metric) : _metrics(metric) { 213 | } 214 | virtual ~ClusterManager() { 215 | _metrics = nullptr; 216 | } 217 | void stop(); 218 | void join(); 219 | 220 | ClusterPtr get(const std::string& cluster_name); 221 | bool remove(const std::string& cluster_name); 222 | Status add_cluster(const ClusterOptions& options); 223 | void clear_all(); 224 | void get_all(std::vector* all); 225 | 226 | bool has_cluster(const std::string& name); 227 | void set_online(const std::string& name); 228 | void set_offline(const std::string& name); 229 | void bg_work(); 230 | void clear(); 231 | void dump_big_key(ClusterKeys* cluster_big_keys); 232 | 233 | static size_t add_cluster_impl(ClusterMap& cm, ClusterPtr cluster); 234 | static size_t remove_cluster_impl(ClusterMap& cm, const std::string& name); 235 | static size_t clear_all_impl(ClusterMap& cm); 236 | 237 | private: 238 | bthread::Mutex _clusters_lock; 239 | butil::DoublyBufferedData _clusters; 240 | ClientMetrics* _metrics = nullptr; 241 | }; 242 | 243 | using ClusterMgrPtr = std::shared_ptr; 244 | 245 | } // namespace client 246 | } // namespace rsdk 247 | 248 | #endif // RSDK_API_INCLUDE_CLUSTER_H 249 | -------------------------------------------------------------------------------- /redis_sdk/include/ctx.h: -------------------------------------------------------------------------------- 1 | #ifndef RSDK_API_INCLUDE_CTX_H 2 | #define RSDK_API_INCLUDE_CTX_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "redis_sdk/api_status.h" 9 | #include "redis_sdk/include/api_util.h" 10 | #include "redis_sdk/include/cluster.h" 11 | 12 | namespace rsdk { 13 | namespace client { 14 | 15 | extern const std::string S_STATUS_OK; 16 | extern const std::string S_STATUS_MOVED; 17 | extern const std::string S_STATUS_ASK; 18 | extern const std::string S_STATUS_TRYAGAIN; 19 | extern const std::string S_STATUS_CROSSSLOT; 20 | extern const std::string S_STATUS_CLUSTERDOWN; 21 | 22 | enum class RedisStatus { 23 | OK = 0, 24 | MOVED = 1, 25 | ASK = 2, 26 | TRY_AGAIN = 3, 27 | CROSS_SLOT = 4, 28 | CLUSTER_DOWN = 5, 29 | UNKNOWN = 999, 30 | }; 31 | 32 | // #define CLUSTER_REDIR_NONE 0 /* Node can serve the request. */ 33 | // #define CLUSTER_REDIR_CROSS_SLOT 1 /* -CROSSSLOT request. */ 34 | // #define CLUSTER_REDIR_UNSTABLE 2 /* -TRYAGAIN redirection required */ 35 | // #define CLUSTER_REDIR_ASK 3 /* -ASK redirection required. */ 36 | // #define CLUSTER_REDIR_MOVED 4 /* -MOVED redirection required. */ 37 | // #define CLUSTER_REDIR_DOWN_STATE 5 /* -CLUSTERDOWN, global state. */ 38 | // #define CLUSTER_REDIR_DOWN_UNBOUND 6 /* -CLUSTERDOWN, unbound slot. */ 39 | // #define CLUSTER_REDIR_DOWN_RO_STATE 7 /* -CLUSTERDOWN, allow reads. */ 40 | 41 | class Slot; 42 | class Cluster; 43 | struct AccessOptions; 44 | class RedisServer; 45 | class IOCtx { 46 | public: 47 | IOCtx(std::shared_ptr cluster, const std::string& mcmd, const std::string& scmd, 48 | std::string&& key, brpc::RedisRequest&& req, brpc::RedisResponse* resp, 49 | ::google::protobuf::Closure* cb) : 50 | _cluster(cluster), 51 | _main_cmd(mcmd), 52 | _sub_cmd(scmd), 53 | _key(key), 54 | _req(std::move(req)), 55 | _resp(resp), 56 | _user_cb(cb) { 57 | } 58 | virtual ~IOCtx(); 59 | Status init(const AccessOptions& options); 60 | bool need_to_update_dist(RedisStatus status); 61 | bool should_retry(RedisStatus status); 62 | void retry(RedisStatus status, std::shared_ptr from, 63 | const std::vector& err_list, const std::string& err_msg); 64 | void send_cmd(); 65 | void handle_err(const std::string& msg); 66 | void handle_resp(std::shared_ptr from); 67 | brpc::RedisResponse* mutable_resp() { 68 | return _resp; 69 | } 70 | brpc::RedisRequest* mutable_req() { 71 | return &_req; 72 | } 73 | const std::string& key() const { 74 | return _key; 75 | } 76 | void set_big_key(bool s) { 77 | _big_key = s; 78 | } 79 | bool big_key() const { 80 | return _big_key; 81 | } 82 | std::shared_ptr get_server(); 83 | void add_ask_head(); 84 | void may_skip_ask_head(); 85 | int64_t left_time_ms(); 86 | void set_start_time(int64_t ts) { 87 | _start_time_us = ts; 88 | } 89 | void set_server_id(const std::string& id) { 90 | _server_id = id; 91 | } 92 | 93 | private: 94 | void add_or_remove_big_key(); 95 | void handle_read_resp(); 96 | 97 | private: 98 | std::shared_ptr _cluster; 99 | std::string _main_cmd; 100 | std::string _sub_cmd; 101 | std::string _key; 102 | uint32_t _slot_id = UINT32_MAX; 103 | int64_t _slot_config_epoch = -1; 104 | std::string _server_id; 105 | std::shared_ptr _slot; 106 | brpc::RedisRequest _req; 107 | brpc::RedisResponse* _resp = nullptr; 108 | ::google::protobuf::Closure* _user_cb = nullptr; 109 | AccessOptions _options; 110 | int64_t _start_time_us = 0; 111 | int64_t _end_time_us = 0; 112 | int _retry_cnt = 0; 113 | std::string _err_msg; 114 | bool _has_ask_head = false; 115 | bool _read_op = false; 116 | // big releated 117 | bool _big_key = false; 118 | uint64_t _key_sign = 0; 119 | uint64_t _req_size_bytes = 0; 120 | uint64_t _resp_size_bytes = 0; 121 | bool _add_to_key_cache = false; 122 | bool _remove_from_key_cache = false; 123 | uint64_t _big_key_threshold = 0; 124 | }; 125 | 126 | class PipeCtx; 127 | class RpcCallback : public ::google::protobuf::Closure { 128 | public: 129 | RpcCallback(IOCtx* ctx, std::shared_ptr server); 130 | RpcCallback(PipeCtx* req, std::shared_ptr server); 131 | ~RpcCallback() { 132 | _ctx = nullptr; 133 | _pipe_req = nullptr; 134 | } 135 | void Run() override; 136 | brpc::Controller* mutable_cntl() { 137 | return &_cntl; 138 | } 139 | 140 | private: 141 | brpc::Controller _cntl; 142 | IOCtx* _ctx = nullptr; 143 | PipeCtx* _pipe_req = nullptr; 144 | std::shared_ptr _server; 145 | }; 146 | 147 | inline void sleep_in_bthread(int wait_time_ms, int cnt, int64_t deadline, int64_t now_ms) { 148 | static constexpr int64_t S_MAX_RETRY_INTERVAL_MS = 50; 149 | if (deadline < now_ms) { 150 | return; 151 | } 152 | if (0 == cnt) { 153 | cnt = 1; 154 | } 155 | int64_t interval = std::min(int64_t(wait_time_ms * cnt), deadline - now_ms); 156 | bthread_usleep(std::min(interval, S_MAX_RETRY_INTERVAL_MS) * 1000); 157 | } 158 | 159 | int64_t rand_logid(); 160 | std::string key_to_string(const std::string& key); 161 | void set_error_to_resp(brpc::RedisResponse* resp, const std::string& msg); 162 | void set_integer_to_resp(brpc::RedisResponse* resp, int64_t input); 163 | 164 | } // namespace client 165 | } // namespace rsdk 166 | 167 | #endif // RSDK_API_INCLUDE_CTX_H 168 | -------------------------------------------------------------------------------- /redis_sdk/include/metric.h: -------------------------------------------------------------------------------- 1 | #ifndef RSDK_API_INCLUDE_METRIX_H 2 | #define RSDK_API_INCLUDE_METRIX_H 3 | 4 | namespace prometheus { 5 | class Registry; 6 | } // namespace prometheus 7 | 8 | namespace rsdk { 9 | namespace client { 10 | 11 | class ClientMetrics { 12 | public: 13 | explicit ClientMetrics(prometheus::Registry* reg = nullptr); 14 | virtual ~ClientMetrics(); 15 | void init(); 16 | void add_latency(const std::string& cluster, const std::string& method, 17 | const std::string& server, bool succ, uint64_t us, int cnt); 18 | 19 | private: 20 | struct PrometheusContext; 21 | 22 | prometheus::Registry* _registry = nullptr; 23 | PrometheusContext* _context = nullptr; 24 | std::vector _latency_buckets_ms; 25 | 26 | static const std::vector S_DEFAULT_LATENCY_BUCKETS_MS; 27 | }; 28 | 29 | } // namespace client 30 | } // namespace rsdk 31 | 32 | #endif // RSDK_API_INCLUDE_METRIX_H 33 | -------------------------------------------------------------------------------- /redis_sdk/include/pipeline.h: -------------------------------------------------------------------------------- 1 | #ifndef RSDK_API_INCLUDE_PIPELINE_H 2 | #define RSDK_API_INCLUDE_PIPELINE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "redis_sdk/api_status.h" 9 | #include "redis_sdk/include/api_util.h" 10 | #include "redis_sdk/include/cluster.h" 11 | #include "redis_sdk/include/ctx.h" 12 | 13 | namespace rsdk { 14 | namespace client { 15 | 16 | struct RetryRequest { 17 | const Record* _input_rec = nullptr; 18 | Record* _output_rec = nullptr; 19 | brpc::RedisResponse* _output_resp = nullptr; 20 | RedisStatus _rs; 21 | std::vector _err_list; 22 | std::string _err_msg; 23 | }; 24 | 25 | using RetryRequestList = std::vector; 26 | 27 | class PipeCtx { 28 | public: 29 | PipeCtx(ClusterPtr cluster, RedisServerPtr serv, const std::string& mcmd, 30 | const std::string& cmd, std::string&& key, ::google::protobuf::Closure* done); 31 | Status init(const AccessOptions& options) { 32 | _acc_opt = options; 33 | return Status::OK(); 34 | } 35 | void add_request(const std::string& key, Record* rec); 36 | void add_request(const std::string& key, const Record& rec, brpc::RedisResponse* resp); 37 | void send_req(); 38 | brpc::RedisRequest* mutable_req() { 39 | return &_main_req; 40 | } 41 | brpc::RedisResponse* mutable_resp() { 42 | return &_main_resp; 43 | } 44 | const std::string& key() const { 45 | return _key; 46 | } 47 | uint64_t left_time_ms() const; 48 | bool big_key() const { 49 | return false; 50 | } 51 | void retry_in_sub(const RetryRequestList& to_retry, RedisServerPtr from); 52 | bool should_retry(const brpc::RedisReply& reply, RetryRequest* retry); 53 | void handle_err(const std::string& msg); 54 | void handle_resp(RedisServerPtr server); 55 | void finish_resp(); 56 | 57 | private: 58 | void add_request_impl(const std::string& key, const Record& rec); 59 | 60 | private: 61 | bool _read_op = true; 62 | ClusterPtr _cluster = nullptr; 63 | RedisServerPtr _server; 64 | std::string _main_cmd; 65 | std::string _cmd; 66 | // only track first key 67 | std::string _key; 68 | AccessOptions _acc_opt; 69 | 70 | // two output type, use Record or brpc::RedisResponse for each key 71 | bool _use_rec_as_output = false; 72 | std::deque _user_output_resp; 73 | std::deque _user_output_records; 74 | 75 | // keep ref for retry, no need to manage life cycle 76 | std::deque _input_records; 77 | brpc::RedisRequest _main_req; 78 | brpc::RedisResponse _main_resp; 79 | 80 | // iff output type is Record, then RedisResponse used for rpc will be tracked here 81 | std::vector _retry_resp_list; 82 | 83 | ::google::protobuf::Closure* _user_cb = nullptr; 84 | AccessOptions _options; 85 | int64_t _start_time_us = 0; 86 | int64_t _end_time_us = 0; 87 | }; 88 | 89 | class RetryClosure : public ::google::protobuf::Closure { 90 | public: 91 | RetryClosure(int cnt, PipeCtx* req) : _counter(cnt), _pipe_req(req) { 92 | } 93 | bool unref() { 94 | return _counter.fetch_sub(1) == 1; 95 | } 96 | void Run(); 97 | 98 | private: 99 | std::atomic _counter = {0}; 100 | PipeCtx* _pipe_req = nullptr; 101 | }; 102 | 103 | class SubReqClosure : public ::google::protobuf::Closure { 104 | public: 105 | SubReqClosure(bool get, Record* rec, brpc::RedisResponse* resp, 106 | ::google::protobuf::Closure* done) : 107 | _is_get(get), 108 | _rec(rec), 109 | _resp(resp), 110 | _done(done) { 111 | } 112 | void Run() override; 113 | 114 | private: 115 | bool _is_get = true; 116 | Record* _rec = nullptr; 117 | brpc::RedisResponse* _resp = nullptr; 118 | ::google::protobuf::Closure* _done = nullptr; 119 | }; 120 | 121 | } // namespace client 122 | } // namespace rsdk 123 | 124 | #endif // RSDK_API_INCLUDE_PIPELINE_H 125 | -------------------------------------------------------------------------------- /redis_sdk/include/redis_sdk/api_status.h: -------------------------------------------------------------------------------- 1 | #ifndef REDIS_SDK_INCLUDE_CLIENT_STATUS_H 2 | #define REDIS_SDK_INCLUDE_CLIENT_STATUS_H 3 | 4 | #include 5 | 6 | namespace rsdk { 7 | 8 | class Status { 9 | public: 10 | enum class Code { 11 | // OK 12 | OK = 0, 13 | BAD_REQUEST = 400, 14 | 15 | // Network Errors 16 | UNKNOWN_ERROR = 1001, 17 | SYS_ERROR = 1002, 18 | INVALID_PARAM = 1003, 19 | TIMEOUT = 1004, 20 | NET_CONNECTION_ERROR = 1005, 21 | E_AGAIN = 1006, 22 | // Resouce Not Found 23 | NOT_FOUND = 2000, 24 | SLOT_NOT_FOUND = 2001, 25 | CLUSTER_NOT_FOUND = 2002, 26 | }; 27 | 28 | public: 29 | Status() : _code(Code::OK), _msg() { 30 | } 31 | Status(const Code& code, const std::string& msg) : _code(code), _msg(msg) { 32 | } 33 | explicit Status(Code code) : _code(code) { 34 | } 35 | explicit Status(int code) : _code(Code::UNKNOWN_ERROR) { 36 | _code = Code(code); 37 | } 38 | Status(int code, const std::string& msg) : _code(Code::UNKNOWN_ERROR), _msg(msg) { 39 | _code = Code(code); 40 | } 41 | ~Status() { 42 | } 43 | Status(const Status& s) { 44 | this->_code = s._code; 45 | this->_msg = s._msg; 46 | } 47 | Status& operator=(const Status& s) { 48 | this->_code = s._code; 49 | this->_msg = s._msg; 50 | return *this; 51 | } 52 | // OK 53 | static Status OK(const std::string& msg = "OK") { 54 | return Status(Code::OK, msg); 55 | } 56 | // Net Errors 57 | static Status SysError(const std::string& msg = "SysError") { 58 | return Status(Code::SYS_ERROR, msg); 59 | } 60 | static Status UnknownError(const std::string& msg = "UnknownError") { 61 | return Status(Code::UNKNOWN_ERROR, msg); 62 | } 63 | static Status InvalidParam(const std::string& msg = "InvalidParam") { 64 | return Status(Code::INVALID_PARAM, msg); 65 | } 66 | static Status Timeout(const std::string& msg = "Timeout") { 67 | return Status(Code::TIMEOUT, msg); 68 | } 69 | static Status NetConnectionError(const std::string& msg = "NetConnectionError") { 70 | return Status(Code::NET_CONNECTION_ERROR, msg); 71 | } 72 | static Status EAgain(const std::string& msg = "eagain") { 73 | return Status(Code::E_AGAIN, msg); 74 | } 75 | // Resource Not Found 76 | static Status NotFound(const std::string& msg = "NotFound") { 77 | return Status(Code::NOT_FOUND, msg); 78 | } 79 | static Status ClusterNotFound(const std::string& msg = "ClusterNotFound") { 80 | return Status(Code::CLUSTER_NOT_FOUND, msg); 81 | } 82 | // IO Errors 83 | // TODO Business Errors 84 | static bool check_err_no(int err_no, Code checked) { 85 | return err_no == static_cast(checked); 86 | } 87 | 88 | // Returns true iff the status indicates success. 89 | inline bool ok() const { 90 | return code() == Code::OK; 91 | } 92 | 93 | bool is_eagain() const { 94 | return code() == Code::E_AGAIN; 95 | } 96 | 97 | inline bool is_cluster_not_found() const noexcept { 98 | return code() == Code::CLUSTER_NOT_FOUND; 99 | } 100 | bool is_sys_error() const { 101 | return code() == Code::SYS_ERROR; 102 | } 103 | std::string to_string() const { 104 | return "status:" + std::to_string((int)_code) + ", msg:" + _msg; 105 | } 106 | std::string to_json() const { 107 | return "{\"code\":" + std::to_string((int)_code) + ",\"msg\":\"" + _msg + "\"}"; 108 | } 109 | friend std::ostream& operator<<(std::ostream& os, const Status& s) { 110 | return os << s.to_string(); 111 | } 112 | bool operator!=(const Status& status) const { 113 | return _code != status.code(); 114 | } 115 | bool operator==(const Status& status) const { 116 | return _code == status.code(); 117 | } 118 | Code code() const { 119 | return _code; 120 | } 121 | const std::string& msg() const { 122 | return _msg; 123 | } 124 | 125 | private: 126 | Code _code; 127 | std::string _msg; 128 | }; // class Status 129 | 130 | } // namespace rsdk 131 | #endif // REDIS_SDK_INCLUDE_CLIENT_STATUS_H 132 | -------------------------------------------------------------------------------- /redis_sdk/include/redis_sdk/client.h: -------------------------------------------------------------------------------- 1 | #ifndef REDIS_SDK_INCLUDE_REDISK_SDK_CLIENT_H 2 | #define REDIS_SDK_INCLUDE_REDISK_SDK_CLIENT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "redis_sdk/api_status.h" 13 | 14 | namespace brpc { 15 | class RedisRequest; 16 | class RedisResponse; 17 | } // namespace brpc 18 | 19 | namespace prometheus { 20 | class Registry; 21 | } // namespace prometheus 22 | 23 | using ClusterKeys = std::unordered_map>; 24 | 25 | namespace rsdk { 26 | namespace client { 27 | 28 | struct RedisServerOptions { 29 | int _conn_per_node = 1; 30 | int _conn_timeout_ms = 500; 31 | int _conn_hc_interval_ms = 3000; 32 | int _conn_retry_cnt = 1; 33 | }; 34 | 35 | struct ClusterOptions { 36 | std::string _name; 37 | // interval to rerefresh cluster info, eg: `cluster nodes` 38 | int _bg_refresh_interval_s = 20; 39 | // big key threshold in bytes, if not set, use global FLAGS_big_key_threshold_bytes 40 | uint64_t _big_key_threshold_bytes = 0; 41 | std::vector _seeds; 42 | RedisServerOptions _server_options; 43 | }; 44 | 45 | typedef std::map ClusterTokens; 46 | 47 | enum class ReadPolicy { 48 | LEADER_ONLY = 0, 49 | }; 50 | 51 | struct AccessOptions { 52 | // timeout for one rpc call. 53 | int64_t timeout_ms = 500; 54 | 55 | // request node max redirect cnt, <= 0 means no retry. 56 | // total access time will be (1 + max_redirect) 57 | int max_redirect = 2; 58 | 59 | ReadPolicy read_policy = ReadPolicy::LEADER_ONLY; 60 | 61 | std::string _cname; 62 | bool _merge_batch_as_array = false; 63 | }; 64 | 65 | struct Record { 66 | std::string _key; // input 67 | std::string _data; // input&output, 68 | // in output case, if KEY NOT FOUND, _data.size() will be 0 69 | int _ttl_s = 0; // input, in seconds, used as: 'set _key _value EX _ttl_s' 70 | int _errno = 0; // output 71 | std::string _err_msg; 72 | }; 73 | using RecordList = std::vector; 74 | 75 | class Client { 76 | public: 77 | Client() = default; 78 | virtual ~Client() = default; 79 | // mget&mset, less cpu cost than exec 80 | virtual void mget(RecordList* records, ::google::protobuf::Closure* done = nullptr) = 0; 81 | virtual void mget(const RecordList& records, std::vector* resps, 82 | ::google::protobuf::Closure* done = nullptr) = 0; 83 | virtual void mset(RecordList* records, ::google::protobuf::Closure* done = nullptr) = 0; 84 | virtual void mset(const RecordList& records, std::vector* resps, 85 | ::google::protobuf::Closure* done = nullptr) = 0; 86 | 87 | virtual void exec(const brpc::RedisRequest& req, brpc::RedisResponse* reply, 88 | ::google::protobuf::Closure* done = nullptr) = 0; 89 | // binary data is not support in this interface 90 | virtual void exec(const std::string& cmd, brpc::RedisResponse* reply, 91 | ::google::protobuf::Closure* done = nullptr) = 0; 92 | }; 93 | 94 | using ClientPtr = std::shared_ptr; 95 | 96 | struct ClientMgrOptions { 97 | prometheus::Registry* _registry = nullptr; 98 | }; 99 | 100 | class ClientManager { 101 | public: 102 | ClientManager() = default; 103 | virtual ~ClientManager() = default; 104 | virtual ClientPtr new_client(const AccessOptions& opt) = 0; 105 | virtual Status add_cluster(const ClusterOptions& options) = 0; 106 | virtual void stop() = 0; 107 | virtual void join() = 0; 108 | 109 | virtual bool has_cluster(const std::string& name) = 0; 110 | // 探测不再打印error日志 111 | virtual void set_cluster_state(const std::string& cluster_name, bool online_or_not) = 0; 112 | 113 | virtual void dump_big_key(ClusterKeys* cluster_big_keys) = 0; 114 | }; 115 | 116 | using ClientManagerPtr = std::shared_ptr; 117 | 118 | // 每个进程创建一个 119 | // 过多创建,会造成对后端redis分布信息的多余刷新, 加重后端压力 120 | ClientManagerPtr new_client_mgr(const ClientMgrOptions& options = ClientMgrOptions()); 121 | 122 | } // namespace client 123 | } // namespace rsdk 124 | 125 | #endif // REDIS_SDK_INCLUDE_REDISK_SDK_CLIENT_H 126 | -------------------------------------------------------------------------------- /redis_sdk/include/redis_server.h: -------------------------------------------------------------------------------- 1 | #ifndef RSDK_API_INCLUDE_REDIS_SERVER_H 2 | #define RSDK_API_INCLUDE_REDIS_SERVER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "redis_sdk/api_status.h" 18 | #include "redis_sdk/client.h" 19 | #include "redis_sdk/include/api_util.h" 20 | 21 | namespace rsdk { 22 | namespace client { 23 | 24 | enum class ServerRole { 25 | FOLLOWER = 0, 26 | LEADER = 1, 27 | FAIL = 2, 28 | NO_ADDR = 3, 29 | UNKNOWN = 999, 30 | }; 31 | 32 | ServerRole str_to_role(const std::string& str); 33 | std::string role_to_str(ServerRole role); 34 | enum class LinkState { 35 | CONNECTED = 0, 36 | DIS_CONNECTED = 1, 37 | UNKNOWN = 999, 38 | }; 39 | LinkState str_to_link_state(const std::string& str); 40 | std::string link_state_to_str(LinkState state); 41 | 42 | struct IOTask { 43 | int64_t _id = 0; 44 | }; 45 | 46 | class RedisServer : public std::enable_shared_from_this { 47 | public: 48 | RedisServer(const std::string& addr) : _addr(addr) { 49 | } 50 | RedisServer(const std::string& uuid, const std::string& addr, ServerRole role, 51 | const std::string& lid, int64_t cepoch, LinkState lstate) : 52 | _id(uuid), 53 | _addr(addr), 54 | _role(role), 55 | _leader_id(lid), 56 | _config_epoch(cepoch), 57 | _link_state(lstate) { 58 | } 59 | virtual ~RedisServer(); 60 | Status init(const RedisServerOptions& options); 61 | virtual void stop(); 62 | virtual void join(); 63 | 64 | template 65 | void send_cmd(CtxType* req); 66 | 67 | Status send_inner_cmd(const std::string& cmd, brpc::RedisResponse* resp, int index = 0); 68 | const std::string& addr() const { 69 | return _addr; 70 | } 71 | bool is_leader() const { 72 | return _role == ServerRole::LEADER; 73 | } 74 | void set_server_role(ServerRole r) { 75 | _role = r; 76 | } 77 | std::string id() const { 78 | return _id; 79 | } 80 | void set_leader_id(const std::string& lid) { 81 | _leader_id = lid; 82 | } 83 | // void set_epoch(int64_t e) { 84 | // _config_epoch = e; 85 | // } 86 | int64_t config_epoch() const { 87 | return _config_epoch; 88 | } 89 | void set_linke_state(LinkState s) { 90 | _link_state = s; 91 | } 92 | bool update(const std::string& id, ServerRole role, const std::string& lid, int64_t epoch, 93 | LinkState lstate) { 94 | std::lock_guard lock(_update_mutex); 95 | if (!_id.empty() && _id != id) { 96 | LOG(ERROR) << "bug, try to update redis server id from " << _id << " to " << id 97 | << " at:" << _addr; 98 | return false; 99 | } 100 | // TODO: 相等时,是否需要更新 101 | if (epoch < _config_epoch) { 102 | return false; 103 | } 104 | if (_id.empty()) { 105 | _id = id; 106 | } 107 | _role = role; 108 | _leader_id = lid; 109 | _config_epoch = epoch; 110 | _link_state = lstate; 111 | return true; 112 | } 113 | void ping(); 114 | std::string to_str() const; 115 | RedisServerOptions options() const { 116 | return _options; 117 | } 118 | void set_online(bool on) { 119 | _online = on; 120 | } 121 | 122 | static ServerRole parse_role_state(const std::string& role_str); 123 | static int execute_io_tasks(void* meta, bthread::TaskIterator& iter); 124 | 125 | public: 126 | RedisServerOptions _options; 127 | std::vector> _channels; 128 | std::unique_ptr _slow_channel; 129 | std::string _id; 130 | std::string _addr; 131 | bthread::Mutex _update_mutex; 132 | ServerRole _role = {ServerRole::FOLLOWER}; 133 | std::string _leader_id; 134 | int64_t _config_epoch = 0; 135 | LinkState _link_state = {LinkState::DIS_CONNECTED}; 136 | 137 | bool _has_bg_worker = false; 138 | bthread_t _bg_ping_thread; 139 | int _ping_index = 0; 140 | bool _online = true; 141 | }; 142 | typedef std::shared_ptr RedisServerPtr; 143 | 144 | class Node { 145 | public: 146 | RedisServerPtr _master; 147 | std::vector _slaves; 148 | }; 149 | 150 | } // namespace client 151 | } // namespace rsdk 152 | 153 | #endif // RSDK_API_INCLUDE_REDIS_SERVER_H 154 | -------------------------------------------------------------------------------- /redis_sdk/include/redis_server.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RSDK_API_INCLUDE_REDIS_SERVER_HPP 2 | #define RSDK_API_INCLUDE_REDIS_SERVER_HPP 3 | 4 | #include 5 | #include "redis_sdk/include/redis_server.h" 6 | 7 | namespace brpc { 8 | DECLARE_bool(redis_verbose_crlf2space); 9 | } // namespace brpc 10 | 11 | namespace rsdk { 12 | namespace client { 13 | 14 | DECLARE_bool(log_request_verbose); 15 | 16 | template 17 | void RedisServer::send_cmd(CtxType* ctx) { 18 | if (FLAGS_log_request_verbose) { 19 | std::stringstream ss; 20 | brpc::FLAGS_redis_verbose_crlf2space = true; 21 | ctx->mutable_req()->Print(ss); 22 | std::string cmd = ss.str(); 23 | LOG(INFO) << "start to send cmd to redis server: " << _addr << ", cmd:[" << cmd << "]"; 24 | } 25 | brpc::RedisResponse* resp = ctx->mutable_resp(); 26 | const std::string& key = ctx->key(); 27 | uint32_t conn_id = brpc::policy::MurmurHash32(key.c_str(), key.size()) % _channels.size(); 28 | RpcCallback* cb = new RpcCallback(ctx, shared_from_this()); 29 | if (ctx->big_key()) { 30 | _slow_channel->CallMethod(nullptr, cb->mutable_cntl(), ctx->mutable_req(), resp, cb); 31 | } else { 32 | _channels[conn_id]->CallMethod(nullptr, cb->mutable_cntl(), ctx->mutable_req(), resp, cb); 33 | } 34 | } 35 | 36 | } // namespace client 37 | } // namespace rsdk 38 | 39 | #endif // RSDK_API_INCLUDE_REDIS_SERVER_HPP 40 | -------------------------------------------------------------------------------- /redis_sdk/include/slot.h: -------------------------------------------------------------------------------- 1 | #ifndef RSDK_API_INCLUDE_SLOT_H 2 | #define RSDK_API_INCLUDE_SLOT_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "redis_sdk/api_status.h" 16 | #include "redis_sdk/include/api_util.h" 17 | #include "redis_sdk/include/redis_server.h" 18 | 19 | namespace rsdk { 20 | namespace client { 21 | 22 | extern const std::string S_CMD_CLUSTER_NODES; 23 | extern const std::string S_CMD_CLUSTER_SLOTS; 24 | extern const size_t S_SLOT_CNT; 25 | 26 | class Slot { 27 | public: 28 | Slot(const std::string& cluster_name, uint64_t id, uint64_t version, int partition_id) : 29 | _id(id), 30 | _cluster_name(cluster_name), 31 | _sn(0), 32 | _version(version) { 33 | } 34 | Slot(const std::string& cluster_name, uint64_t id, int partition_id) : 35 | Slot(cluster_name, id, 0, partition_id) { 36 | } 37 | 38 | uint64_t id() const { 39 | return _id; 40 | } 41 | const std::string& cluster_name() const { 42 | return _cluster_name; 43 | } 44 | std::string to_string() const { 45 | return "slot:" + _cluster_name + "#" + std::to_string(_id) + +":" 46 | + " version:" + std::to_string(_version); 47 | } 48 | std::string desc() const { 49 | return fmt::format("{}#{}", _cluster_name, _id); 50 | } 51 | 52 | int64_t get_sn() const { 53 | std::lock_guard lock(_servers_mutex); 54 | return _sn; 55 | } 56 | void set_sn(int64_t sn) { 57 | std::lock_guard lock(_servers_mutex); 58 | _sn = sn; 59 | } 60 | int64_t config_epoch() const { 61 | return _config_epoch.load(); 62 | } 63 | 64 | int64_t version() { 65 | std::lock_guard lock(_servers_mutex); 66 | return _version; 67 | } 68 | Status fill_slot(int64_t begin, int64_t end, const std::vector& servers); 69 | RedisServerPtr get_leader(); 70 | RedisServerPtr get_server_for_read(ReadPolicy policy); 71 | std::string describe() const; 72 | 73 | void update_servers(RedisServerPtr leader, const std::vector& servers, 74 | int64_t cepoch); 75 | 76 | private: 77 | uint64_t _id = 0; 78 | std::string _cluster_name; 79 | 80 | int64_t _sn = 0; 81 | int64_t _version = 0; 82 | int _start = 0; 83 | int _end = 0; 84 | std::string _server_sign; 85 | mutable bthread::Mutex _servers_mutex; 86 | RedisServerPtr _leader; 87 | std::vector _servers; 88 | int _acc_index = 0; 89 | std::atomic _config_epoch = {0}; 90 | }; 91 | 92 | using SlotPtr = std::shared_ptr; 93 | 94 | class SlotMgr { 95 | public: 96 | SlotMgr(); 97 | SlotPtr get_slot_by_id(uint64_t slot_id); 98 | SlotPtr get_slot_by_key(const std::string& key); 99 | Status update_slot(const std::string& data, RedisServerPtr server, 100 | const std::vector& servers, int64_t epoch); 101 | bool add_slot(SlotPtr slot); 102 | 103 | void set_sn(int64_t sn) { 104 | _sn = sn; 105 | } 106 | int64_t sn() const { 107 | return _sn.load(); 108 | } 109 | bool all_slot_filled() const { 110 | return _valid_slot_cnt.load() == (int)S_SLOT_CNT; 111 | } 112 | 113 | private: 114 | std::atomic _sn = {0}; 115 | std::vector _slots; 116 | std::atomic _valid_slot_cnt = {0}; 117 | }; 118 | typedef std::shared_ptr SlotMgrPtr; 119 | 120 | } // namespace client 121 | } // namespace rsdk 122 | 123 | #endif // RSDK_API_INCLUDE_SLOT_H 124 | -------------------------------------------------------------------------------- /redis_sdk/src/client_util.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "redis_sdk/include/cluster.h" 13 | #include "redis_sdk/include/ctx.h" 14 | 15 | uint16_t crc16(const char* buf, int len); 16 | 17 | namespace rsdk { 18 | namespace client { 19 | 20 | uint32_t key_to_slot_id(const char* key, int keylen) { 21 | int s = 0; 22 | for (s = 0; s < keylen; ++s) { 23 | if (key[s] == '{') { 24 | break; 25 | } 26 | } 27 | 28 | if (s == keylen) { 29 | return crc16(key, keylen) & 0x3FFF; 30 | } 31 | 32 | int e = 0; 33 | for (e = s + 1; e < keylen; ++e) { 34 | if (key[e] == '}') { 35 | break; 36 | } 37 | } 38 | 39 | if (e == keylen || e == (s + 1)) { 40 | return crc16(key, keylen) & 0x3FFF; 41 | } 42 | 43 | return crc16(key + s + 1, e - s - 1) & 0x3FFF; 44 | } 45 | 46 | void log_reply(const brpc::RedisReply& reply) { 47 | if (reply.is_string()) { 48 | VLOG(3) << "reply is str:" << reply.c_str(); 49 | return; 50 | } 51 | if (reply.is_error()) { 52 | VLOG(3) << "reply is error:" << reply.error_message(); 53 | return; 54 | } 55 | if (reply.is_integer()) { 56 | VLOG(3) << "reply is integer:" << reply.integer(); 57 | return; 58 | } 59 | if (reply.is_array()) { 60 | VLOG(3) << "reply is array, size:" << reply.size() << ", content:"; 61 | for (size_t j = 0; j < reply.size(); ++j) { 62 | const brpc::RedisReply& sub = reply[j]; 63 | log_reply(sub); 64 | } 65 | return; 66 | } 67 | if (reply.is_nil()) { 68 | VLOG(3) << "reply is nil"; 69 | return; 70 | } 71 | VLOG(3) << "unknown reply:" << brpc::RedisReplyTypeToString(reply.type()); 72 | } 73 | void log_response(const brpc::RedisResponse& resp) { 74 | for (int i = 0; i < resp.reply_size(); ++i) { 75 | log_reply(resp.reply(i)); 76 | } 77 | } 78 | 79 | uint64_t get_current_time_us() { 80 | struct timeval current; 81 | gettimeofday(¤t, nullptr); 82 | uint64_t ts = current.tv_sec * 1000000ULL + current.tv_usec; 83 | return ts; 84 | } 85 | 86 | uint64_t get_current_time_s() { 87 | return get_current_time_us() / 1000000L; 88 | } 89 | 90 | uint64_t get_current_time_ms() { 91 | struct timeval current; 92 | gettimeofday(¤t, nullptr); 93 | uint64_t ts = current.tv_sec * 1000ULL + current.tv_usec / 1000ULL; 94 | return ts; 95 | } 96 | 97 | std::string encode_key(const std::string& key) { 98 | std::string buf; 99 | buf.reserve(key.size() + (key.size() >> 1)); 100 | butil::Base64Encode(key, &buf); 101 | return buf; 102 | } 103 | 104 | } // namespace client 105 | } // namespace rsdk 106 | -------------------------------------------------------------------------------- /redis_sdk/src/crc16.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | /* 3 | * Copyright 2001-2010 Georges Menie (www.menie.org) 4 | * Copyright 2010-2012 Salvatore Sanfilippo (adapted to Redis coding style) 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * * Neither the name of the University of California, Berkeley nor the 16 | * names of its contributors may be used to endorse or promote products 17 | * derived from this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY 20 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY 23 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | /* CRC16 implementation according to CCITT standards. 32 | * 33 | * Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the 34 | * following parameters: 35 | * 36 | * Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN" 37 | * Width : 16 bit 38 | * Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1) 39 | * Initialization : 0000 40 | * Reflect Input byte : False 41 | * Reflect Output CRC : False 42 | * Xor constant to output CRC : 0000 43 | * Output for "123456789" : 31C3 44 | */ 45 | 46 | static const uint16_t crc16tab[256] = 47 | {0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 48 | 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 49 | 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 50 | 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 51 | 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 52 | 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 53 | 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 54 | 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 55 | 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 56 | 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 57 | 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 58 | 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 59 | 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 60 | 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 61 | 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 62 | 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 63 | 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 64 | 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 65 | 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 66 | 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 67 | 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 68 | 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0}; 69 | 70 | uint16_t crc16(const char* buf, int len) { 71 | int counter; 72 | uint16_t crc = 0; 73 | for (counter = 0; counter < len; counter++) 74 | crc = (crc << 8) ^ crc16tab[((crc >> 8) ^ *buf++) & 0x00FF]; 75 | return crc; 76 | } 77 | -------------------------------------------------------------------------------- /redis_sdk/src/ctx.cpp: -------------------------------------------------------------------------------- 1 | #include "redis_sdk/include/ctx.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "redis_sdk/client.h" 8 | #include "redis_sdk/include/pipeline.h" 9 | #include "redis_sdk/include/redis_server.hpp" 10 | 11 | namespace rsdk { 12 | namespace client { 13 | 14 | DEFINE_bool(use_indepent_channel_for_big_key, true, "use indepent channel for big key or"); 15 | BRPC_VALIDATE_GFLAG(use_indepent_channel_for_big_key, brpc::PassValidate); 16 | 17 | // TODO: Use `absl::Status` instead. 18 | // TODO: Avoid using non-trivial variables with static storage duration. 19 | const std::string S_STATUS_OK = "OK"; 20 | const std::string S_STATUS_MOVED = "MOVED"; 21 | const std::string S_STATUS_ASK = "ASK"; 22 | const std::string S_STATUS_TRYAGAIN = "TRYAGAIN"; 23 | const std::string S_STATUS_CROSSSLOT = "CROSSSLOT"; 24 | const std::string S_STATUS_CLUSTERDOWN = "CLUSTERDOWN"; 25 | 26 | std::string redis_status_to_str(const RedisStatus& s); 27 | std::string to_string(const brpc::RedisResponse& resp); 28 | 29 | IOCtx::~IOCtx() { 30 | if (_user_cb) { 31 | _user_cb->Run(); 32 | } 33 | add_or_remove_big_key(); 34 | } 35 | 36 | bool read_op(const std::string& cmd) { 37 | return (cmd == "get" || cmd == "hget" || cmd == "zcount" || cmd == "zcard" || cmd == "scard" 38 | || cmd == "mget"); 39 | } 40 | // put xx yyy 41 | // get xx 42 | uint32_t key_to_slot_id(const char* key, int keylen); 43 | uint64_t xxhash(const std::string& key) { 44 | XXH64_hash_t seed = 2147483647; 45 | return (uint64_t)XXH64(key.data(), key.size(), seed); 46 | } 47 | 48 | Status IOCtx::init(const AccessOptions& opt) { 49 | _options = opt; 50 | _start_time_us = get_current_time_us(); 51 | Status ret; 52 | _read_op = read_op(_main_cmd); 53 | _slot_id = key_to_slot_id(_key.c_str(), _key.length()); 54 | _slot = _cluster->get_slot_by_id(_slot_id); 55 | assert(_slot != nullptr); 56 | if (_read_op && FLAGS_use_indepent_channel_for_big_key) { 57 | _big_key_threshold = _cluster->big_key_threshold(); 58 | _key_sign = xxhash(_key); 59 | if (_cluster->is_big_key(_key_sign)) { 60 | _big_key = true; 61 | } 62 | } 63 | if (!_read_op) { 64 | _req_size_bytes = _req.ByteSize(); 65 | (*(_cluster->mutable_write_bvar())) << _req_size_bytes; 66 | } 67 | VLOG(3) << "succeed to init io ctx, cmd:[" << _sub_cmd << "], key:[" << encode_key(_key) 68 | << "], slot:" << _slot->describe(); 69 | return Status::OK(); 70 | } 71 | 72 | int64_t IOCtx::left_time_ms() { 73 | int64_t used = (get_current_time_us() - _start_time_us) / 1000; 74 | return _options.timeout_ms > used ? _options.timeout_ms - used : 0; 75 | } 76 | 77 | bool IOCtx::should_retry(RedisStatus status) { 78 | if (get_current_time_us() >= uint64_t(_start_time_us + _options.timeout_ms * 1000)) { 79 | return false; 80 | } 81 | if (_has_ask_head) { 82 | return false; 83 | } 84 | if (status >= RedisStatus::MOVED && status <= RedisStatus::CLUSTER_DOWN) { 85 | return true; 86 | } 87 | return false; 88 | } 89 | void IOCtx::add_ask_head() { 90 | if (_has_ask_head) { 91 | return; 92 | } 93 | std::string ask_cmd = "ASKING"; 94 | brpc::RedisRequest tmp_req; 95 | tmp_req.AddCommand(ask_cmd); 96 | tmp_req.MergeFrom(_req); 97 | _req.Swap(&tmp_req); 98 | _has_ask_head = true; 99 | } 100 | 101 | // put resp 102 | // OK 103 | // (error) MOVED 15983 127.0.0.1:21020 104 | 105 | // get resp 106 | // MOVED 4038 127.0.0.1:21010 107 | // (nil) 108 | // "yyy" 109 | // (error) ERR wrong number of arguments for 'get' command 110 | void IOCtx::retry(RedisStatus status, RedisServerPtr from, const std::vector& err_list, 111 | const std::string& msg) { 112 | VLOG(3) << "start to retry cmd[" << _sub_cmd << "] key:" << encode_key(_key) 113 | << " to slot:" << _slot_id << ", prev redis status:" << redis_status_to_str(status) 114 | << ", msg:" << msg; 115 | ++_retry_cnt; 116 | RedisServerPtr server; 117 | if (status == RedisStatus::ASK || status == RedisStatus::MOVED) { 118 | if (err_list.size() >= 3) { 119 | // int64_t slot_id = strtol(part[1].c_str(), nullptr, 10); 120 | std::string addr = err_list[2]; 121 | server = _cluster->add_and_get_node(addr); 122 | LOG(INFO) << "redis server:[" << from->to_str() 123 | << "] return:" << redis_status_to_str(status) << " on cmd:[" << _sub_cmd 124 | << "] key:" << encode_key(_key) << ", for slot:" << _slot_id << " retry to:[" 125 | << (server ? server->addr() : addr) << "] now"; 126 | _cluster->may_triggle_update_dist(); 127 | } 128 | if (status == RedisStatus::ASK) { 129 | add_ask_head(); 130 | } 131 | if (!server) { 132 | // 可能其他线程已经更新过了,直接用新的 133 | server = get_server(); 134 | if (server->id() == _server_id) { 135 | _cluster->may_update_dist(status, msg); 136 | server = get_server(); 137 | } 138 | } 139 | } 140 | if (!server) { 141 | server = get_server(); 142 | } 143 | 144 | _resp->Clear(); 145 | 146 | _slot_config_epoch = _slot->config_epoch(); 147 | _server_id = server->id(); 148 | VLOG(3) << "retry, start to send cmd:[" << _sub_cmd << "] key:" << encode_key(_key) 149 | << " to server:" << server->addr() << " with resp:" << to_string(*_resp); 150 | server->send_cmd(this); 151 | } 152 | 153 | RedisServerPtr IOCtx::get_server() { 154 | return _slot->get_leader(); 155 | } 156 | 157 | void IOCtx::send_cmd() { 158 | RedisServerPtr server = get_server(); 159 | _slot_config_epoch = _slot->config_epoch(); 160 | _server_id = server->id(); 161 | if (FLAGS_v >= 3) { 162 | VLOG(3) << "send cmd [" << _sub_cmd << "] key:" << encode_key(_key) 163 | << " to slot:" << _slot_id << ", to:" << server->addr() << " id:" << _server_id 164 | << ", epoch:" << _slot_config_epoch; 165 | } 166 | server->send_cmd(this); 167 | } 168 | 169 | void set_error_to_resp(brpc::RedisResponse* resp, const std::string& msg) { 170 | butil::IOBuf err_buf; 171 | err_buf.append("-"); 172 | err_buf.append(msg); 173 | err_buf.append("\r\n"); 174 | if (resp->reply_size() != 0) { 175 | resp->Clear(); 176 | } 177 | brpc::ParseError perr = resp->ConsumePartialIOBuf(err_buf, 1); 178 | if (perr != brpc::PARSE_OK) { 179 | LOG(ERROR) << "bug of redis api, failed to parse:[" << err_buf.to_string() << "]"; 180 | return; 181 | } 182 | return; 183 | } 184 | 185 | void IOCtx::handle_err(const std::string& msg) { 186 | std::unique_ptr release_this(this); 187 | set_error_to_resp(_resp, msg); 188 | } 189 | 190 | void log_response(const brpc::RedisResponse& resp); 191 | void IOCtx::may_skip_ask_head() { 192 | if (!_has_ask_head) { 193 | return; 194 | } 195 | VLOG(3) << "resp reply size:" << _resp->reply_size() << ", may need to skip ask head" 196 | << " on cmd:" << _sub_cmd << " key:" << encode_key(_key); 197 | if (_resp->reply_size() < 2) { 198 | LOG(ERROR) << "bug of redis api, reply size:" << _resp->reply_size() 199 | << ", expect to have ask head"; 200 | return; 201 | } 202 | int cnt = _resp->reply_size(); 203 | butil::IOBufAppender appender; 204 | for (int i = 1; i < cnt; ++i) { 205 | if (_resp->reply(i).is_nil()) { 206 | std::string nil_resp = "$-1\r\n"; 207 | appender.append(nil_resp.c_str(), nil_resp.size()); 208 | } else { 209 | brpc::RedisReply reply(nullptr); 210 | reply.CopyFromSameArena(_resp->reply(i)); 211 | bool ret = reply.SerializeTo(&appender); 212 | if (!ret) { 213 | std::string err_msg = "ERR bug of redis api, failed to serialize response to iobuf"; 214 | set_error_to_resp(_resp, err_msg); 215 | return; 216 | } 217 | } 218 | } 219 | butil::IOBuf buf; 220 | appender.move_to(buf); 221 | 222 | _resp->Clear(); 223 | 224 | brpc::ParseError err = _resp->ConsumePartialIOBuf(buf, cnt - 1); 225 | if (err != brpc::PARSE_OK) { 226 | std::string err_msg = "ERR bug of redis api, failed to parse serialized result"; 227 | set_error_to_resp(_resp, err_msg); 228 | return; 229 | } 230 | // VLOG(3) << "succeed to skip ask header from user response"; 231 | } 232 | 233 | void IOCtx::add_or_remove_big_key() { 234 | if (_add_to_key_cache) { 235 | _cluster->add_big_key(_key_sign, _key, _resp_size_bytes); 236 | return; 237 | } 238 | if (_remove_from_key_cache) { 239 | _cluster->remove_big_key(_key_sign); 240 | return; 241 | } 242 | } 243 | 244 | void IOCtx::handle_read_resp() { 245 | if (!_read_op) { 246 | return; 247 | } 248 | _resp_size_bytes = _resp->ByteSize(); 249 | (*_cluster->mutable_read_bvar()) << _resp_size_bytes; 250 | if (!FLAGS_use_indepent_channel_for_big_key) { 251 | return; 252 | } 253 | uint64_t threshold = _big_key_threshold; 254 | if (_big_key && (_resp_size_bytes < threshold)) { 255 | LOG(INFO) << "big key:[" << encode_key(_key) << "] size:" << _resp_size_bytes 256 | << " is less than threshold:" << threshold << ", will remove from local cache"; 257 | _remove_from_key_cache = true; 258 | return; 259 | } 260 | if (!_big_key && threshold > 0 && (_resp_size_bytes >= threshold)) { 261 | LOG(INFO) << "key:[" << encode_key(_key) << "] size:" << _resp_size_bytes 262 | << " is greater than threshold:" << threshold << ", will add to local cache"; 263 | _add_to_key_cache = true; 264 | return; 265 | } 266 | } 267 | 268 | RedisStatus parse_redis_error(const std::string& str); 269 | void IOCtx::handle_resp(RedisServerPtr from) { 270 | std::unique_ptr release_this(this); 271 | const brpc::RedisReply& reply0 = _resp->reply(0); 272 | VLOG(3) << "start to handle resp for cmd:[" << _sub_cmd << "], from:" << from->to_str() 273 | << " key:" << encode_key(_key) << ", resp size:" << _resp->reply_size() 274 | << ", resp:" << to_string(*_resp); 275 | _end_time_us = get_current_time_us(); 276 | int64_t cost = _end_time_us - _start_time_us; 277 | if (!reply0.is_error()) { 278 | may_skip_ask_head(); 279 | handle_read_resp(); 280 | // 这里可能对_resp的内容进行了swap,故不能再使用reply0 281 | VLOG(2) << "succeed to get resp of cmd:[" << _sub_cmd << "] from " << from->to_str() 282 | << " key:" << encode_key(_key) << " in " << cost 283 | << "us, resp:" << to_string(*_resp); 284 | (*(_cluster->mutable_latency_bvar())) << cost; 285 | _cluster->add_metrics(_main_cmd, from->addr(), true, cost, _req.ByteSize(), 286 | _resp->ByteSize()); 287 | return; 288 | } 289 | const std::string& err_msg_str = reply0.error_message(); 290 | std::vector err_list; 291 | butil::SplitString(err_msg_str, ' ', &err_list); 292 | if (err_list.empty()) { 293 | LOG(ERROR) << "invalid error response:" << err_msg_str << " when send cmd[" << _sub_cmd 294 | << "] from" << from->to_str(); 295 | std::string msg = fmt::format("invalid response:[{}] from:{}", err_msg_str, from->to_str()); 296 | set_error_to_resp(_resp, msg); 297 | _cluster->add_metrics(_main_cmd, from->addr(), false, cost, _req.ByteSize(), 298 | _resp->ByteSize()); 299 | return; 300 | } 301 | RedisStatus rs = parse_redis_error(err_list[0]); 302 | if (!should_retry(rs)) { 303 | VLOG(3) << "no need to retry, cmd[" << _sub_cmd << "] to " << from->to_str() 304 | << " key:" << encode_key(_key) << " err msg:" << err_msg_str 305 | << " redis status:" << redis_status_to_str(rs); 306 | 307 | _cluster->add_metrics(_main_cmd, from->addr(), false, cost, _req.ByteSize(), 308 | _resp->ByteSize()); 309 | return; 310 | } 311 | _err_msg += err_msg_str + " "; 312 | if (_retry_cnt >= _options.max_redirect) { 313 | LOG(WARNING) << "failed to send cmd[" << _sub_cmd << "] to " << from->to_str() 314 | << " key:" << encode_key(_key) << ", got resp:" << err_msg_str 315 | << " redis status:" << redis_status_to_str(rs) << ", retry cnt:" << _retry_cnt 316 | << " >= max:" << _options.max_redirect; 317 | std::string msg = _err_msg; 318 | msg += ", retry cnt:" + std::to_string(_retry_cnt) 319 | + " exceed max:" + std::to_string(_options.max_redirect); 320 | set_error_to_resp(_resp, msg); 321 | _cluster->add_metrics(_main_cmd, from->addr(), false, cost, _req.ByteSize(), 322 | _resp->ByteSize()); 323 | return; 324 | } 325 | 326 | release_this.release(); 327 | retry(rs, from, err_list, err_msg_str); 328 | return; 329 | } 330 | RpcCallback::RpcCallback(IOCtx* ctx, std::shared_ptr server) : 331 | _ctx(ctx), 332 | _server(server) { 333 | _cntl.set_timeout_ms(ctx->left_time_ms()); 334 | } 335 | RpcCallback::RpcCallback(PipeCtx* req, std::shared_ptr server) : 336 | _pipe_req(req), 337 | _server(server) { 338 | _cntl.set_timeout_ms(req->left_time_ms()); 339 | } 340 | void RpcCallback::Run() { 341 | std::unique_ptr release_this(this); 342 | if (_cntl.Failed()) { 343 | int err = _cntl.ErrorCode(); 344 | std::string msg = fmt::format("TIMEOUT, failed to access redis server:{}, got err:{} " 345 | "code:{}", 346 | _server->to_str(), _cntl.ErrorText(), err); 347 | VLOG(3) << msg; 348 | if (_ctx) { 349 | _ctx->handle_err(msg); 350 | } else { 351 | _pipe_req->handle_err(msg); 352 | } 353 | return; 354 | } 355 | // VLOG(3) << "succeed to access redis server:" << _server 356 | // << ", got resp:" << to_string(*_ctx->mutable_resp()); 357 | if (_ctx) { 358 | _ctx->handle_resp(_server); 359 | } else { 360 | _pipe_req->handle_resp(_server); 361 | } 362 | } 363 | 364 | } // namespace client 365 | } // namespace rsdk 366 | -------------------------------------------------------------------------------- /redis_sdk/src/pipeline.cpp: -------------------------------------------------------------------------------- 1 | #include "redis_sdk/include/pipeline.h" 2 | 3 | #include 4 | #include 5 | #include "redis_sdk/client.h" 6 | #include "redis_sdk/include/client_impl.h" 7 | #include "redis_sdk/include/redis_server.hpp" 8 | 9 | #include 10 | 11 | namespace rsdk { 12 | namespace client { 13 | void reply_of_set_to_record(const brpc::RedisReply& reply, Record* rec); 14 | void reply_of_get_to_record(const brpc::RedisReply& reply, Record* rec); 15 | void reply_to_resp(const brpc::RedisReply& reply, brpc::RedisResponse* resp); 16 | 17 | PipeCtx::PipeCtx(ClusterPtr cluster, RedisServerPtr serv, const std::string& mcmd, 18 | const std::string& cmd, std::string&& key, ::google::protobuf::Closure* done) : 19 | _cluster(cluster), 20 | _server(serv), 21 | _main_cmd(mcmd), 22 | _cmd(cmd), 23 | _key(std::move(key)), 24 | _user_cb(done) { 25 | _start_time_us = get_current_time_us(); 26 | _read_op = (cmd == "get" || cmd == "mget"); 27 | } 28 | 29 | void record_to_redis_req(const std::string& cmd, const Record& rec, bool read_op, 30 | brpc::RedisRequest* req) { 31 | butil::StringPiece arg_list[5]; 32 | arg_list[0].set(cmd.c_str(), cmd.length()); 33 | arg_list[1].set(rec._key.c_str(), rec._key.length()); 34 | if (read_op) { 35 | req->AddCommandByComponents(arg_list, 2); 36 | return; 37 | } 38 | // write operation 39 | arg_list[2].set(rec._data.c_str(), rec._data.size()); 40 | if (rec._ttl_s == 0) { 41 | req->AddCommandByComponents(arg_list, 3); 42 | return; 43 | } 44 | // has ttl 45 | std::string ttl_str = fmt::format("{}", rec._ttl_s); 46 | static const std::string S_EX = "EX"; 47 | arg_list[3].set(S_EX.c_str(), S_EX.length()); 48 | arg_list[4].set(ttl_str.c_str(), ttl_str.length()); 49 | req->AddCommandByComponents(arg_list, 5); 50 | return; 51 | } 52 | 53 | void PipeCtx::add_request_impl(const std::string& key, const Record& rec) { 54 | record_to_redis_req(_cmd, rec, _read_op, &_main_req); 55 | if (_key.empty()) { 56 | _key = key; 57 | } 58 | } 59 | 60 | void PipeCtx::add_request(const std::string& key, Record* rec) { 61 | add_request_impl(key, *rec); 62 | _input_records.push_back(rec); 63 | _user_output_records.push_back(rec); 64 | _use_rec_as_output = true; 65 | } 66 | 67 | void PipeCtx::add_request(const std::string& key, const Record& rec, brpc::RedisResponse* resp) { 68 | add_request_impl(key, rec); 69 | _input_records.push_back(&rec); 70 | _user_output_resp.push_back(resp); 71 | _use_rec_as_output = false; 72 | } 73 | 74 | void PipeCtx::send_req() { 75 | _server->send_cmd(this); 76 | } 77 | 78 | void PipeCtx::handle_err(const std::string& msg) { 79 | VLOG(3) << "handle err from:" << _server->addr() << " with err:" << msg << " on cmd:" << _cmd 80 | << " request cnt:" << _main_req.command_size(); 81 | brpc::ClosureGuard user_callback(_user_cb); 82 | for (size_t i = 0; i < _user_output_resp.size(); ++i) { 83 | set_error_to_resp(_user_output_resp[i], msg); 84 | } 85 | for (size_t i = 0; i < _user_output_records.size(); ++i) { 86 | _user_output_records[i]->_errno = -1; 87 | _user_output_records[i]->_err_msg = msg; 88 | } 89 | 90 | _end_time_us = get_current_time_us(); 91 | int64_t cost = _end_time_us - _start_time_us; 92 | _cluster->add_metrics(_main_cmd, _server->addr(), false, cost, _main_req.ByteSize(), 93 | _main_resp.ByteSize(), int(_user_output_records.size())); 94 | } 95 | 96 | RedisStatus parse_redis_error(const std::string& str); 97 | bool PipeCtx::should_retry(const brpc::RedisReply& reply, RetryRequest* retry) { 98 | const std::string& err_msg_str = reply.error_message(); 99 | butil::SplitString(err_msg_str, ' ', &(retry->_err_list)); 100 | if (retry->_err_list.empty()) { 101 | return false; 102 | } 103 | retry->_rs = parse_redis_error(retry->_err_list[0]); 104 | uint64_t cur_time = get_current_time_us(); 105 | if (cur_time >= uint64_t(_start_time_us + _acc_opt.timeout_ms * 1000)) { 106 | return false; 107 | } 108 | if (retry->_rs >= RedisStatus::MOVED && retry->_rs <= RedisStatus::CLUSTER_DOWN) { 109 | return true; 110 | } 111 | return false; 112 | } 113 | 114 | void reply_to_resp(const brpc::RedisReply& reply, brpc::RedisResponse* resp) { 115 | butil::IOBufAppender appender; 116 | if (reply.is_nil()) { 117 | std::string nil_resp = "$-1\r\n"; 118 | appender.append(nil_resp.c_str(), nil_resp.size()); 119 | } else { 120 | brpc::RedisReply new_reply(nullptr); 121 | new_reply.CopyFromSameArena(reply); 122 | bool ret = new_reply.SerializeTo(&appender); 123 | if (!ret) { 124 | std::string err_msg = "ERR bug of redis api, failed to serialize response to iobuf"; 125 | set_error_to_resp(resp, err_msg); 126 | return; 127 | } 128 | } 129 | butil::IOBuf buf; 130 | appender.move_to(buf); 131 | brpc::ParseError err = resp->ConsumePartialIOBuf(buf, 1); 132 | if (err != brpc::PARSE_OK) { 133 | std::string err_msg = "ERR bug of redis api, failed to parse serialized result"; 134 | set_error_to_resp(resp, err_msg); 135 | return; 136 | } 137 | } 138 | 139 | void reply_of_get_to_record(const brpc::RedisReply& reply, Record* rec) { 140 | if (reply.is_error()) { 141 | rec->_errno = -1; 142 | rec->_err_msg = reply.error_message(); 143 | return; 144 | } 145 | if (reply.is_array()) { 146 | rec->_errno = -1; 147 | rec->_err_msg = fmt::format("bug, 'get' got array resp"); 148 | return; 149 | } 150 | if (reply.is_integer()) { 151 | rec->_errno = 0; 152 | int64_t v = reply.integer(); 153 | rec->_data.resize(sizeof(v)); 154 | memcpy(const_cast(rec->_data.data()), &v, sizeof(v)); 155 | return; 156 | } 157 | if (reply.is_nil()) { 158 | rec->_errno = 0; 159 | rec->_data.clear(); 160 | return; 161 | } 162 | if (reply.is_string()) { 163 | rec->_errno = 0; 164 | butil::StringPiece v = reply.data(); 165 | v.CopyToString(&(rec->_data)); 166 | return; 167 | } 168 | rec->_errno = -1; 169 | rec->_err_msg = fmt::format("bug, unknown resp type:{}", (int)reply.type()); 170 | } 171 | 172 | void reply_of_set_to_record(const brpc::RedisReply& reply, Record* rec) { 173 | if (reply.is_error()) { 174 | rec->_errno = -1; 175 | rec->_err_msg = reply.error_message(); 176 | return; 177 | } 178 | if (reply.is_nil() || reply.type() == brpc::REDIS_REPLY_STATUS) { 179 | rec->_errno = 0; 180 | return; 181 | } 182 | // impossible 183 | if (reply.type() == brpc::REDIS_REPLY_STRING) { 184 | rec->_errno = -1; 185 | std::string err = reply.data().as_string(); 186 | rec->_err_msg = fmt::format("bug, got STRING in 'set' resp, content:{}", err); 187 | return; 188 | } 189 | rec->_errno = -1; 190 | rec->_err_msg = fmt::format("bug, unknown reply type:{} in 'set'", (int)reply.type()); 191 | } 192 | 193 | void record_to_redis_req(const std::string& cmd, const Record& rec, bool read_op, 194 | brpc::RedisRequest* req); 195 | void PipeCtx::retry_in_sub(const RetryRequestList& to_retry, RedisServerPtr from) { 196 | VLOG(3) << "retry to send " << to_retry.size() 197 | << " keys after pipeline mode as has err from:" << from->addr(); 198 | if (_use_rec_as_output) { 199 | _retry_resp_list.resize(to_retry.size()); 200 | } 201 | RetryClosure* retry_done = new RetryClosure(to_retry.size(), this); 202 | for (size_t i = 0; i < to_retry.size(); ++i) { 203 | brpc::RedisRequest sub_req; 204 | const RetryRequest& retry = to_retry.at(i); 205 | const Record* rec = retry._input_rec; 206 | std::string key = rec->_key; 207 | record_to_redis_req(_cmd, *rec, _read_op, &sub_req); 208 | brpc::RedisResponse* resp = retry._output_resp; 209 | if (_use_rec_as_output) { 210 | resp = &_retry_resp_list[i]; 211 | } 212 | VLOG(4) << "start to send retry request on key:[" << encode_key(key) << "] on cmd:[" << _cmd 213 | << "]" 214 | << " prev server:" << from->addr(); 215 | SubReqClosure* sub_done = new SubReqClosure(_read_op, retry._output_rec, resp, retry_done); 216 | IOCtx* ctx = new IOCtx(_cluster, _main_cmd, _cmd, std::move(key), std::move(sub_req), resp, 217 | sub_done); 218 | Status ret = ctx->init(_options); 219 | if (!ret.ok()) { 220 | std::string msg = fmt::format("client side error, error: {}", ret.to_string()); 221 | set_error_to_resp(resp, msg); 222 | delete ctx; 223 | continue; 224 | } 225 | ctx->set_start_time(_start_time_us); 226 | ctx->set_server_id(from->id()); 227 | ctx->retry(retry._rs, from, retry._err_list, retry._err_msg); 228 | } 229 | } 230 | 231 | void PipeCtx::handle_resp(RedisServerPtr from) { 232 | VLOG(3) << "start to handle resp for cmd:[" << _cmd << "], from:" << from->addr() 233 | << ", first_key:" << encode_key(_key) << ", resp cnt:" << _main_resp.reply_size(); 234 | int reply_cnt = (int)_main_resp.reply_size(); 235 | if (reply_cnt != (int)_input_records.size()) { 236 | std::string msg = fmt::format("bug, reply_cnt:{} != input_user_records.size:{}", reply_cnt, 237 | _input_records.size()); 238 | handle_err(msg); 239 | return; 240 | } 241 | _end_time_us = get_current_time_us(); 242 | int64_t cost = _end_time_us - _start_time_us; 243 | 244 | RetryRequestList to_retry; 245 | int success_cnt = 0; 246 | int failed_cnt = 0; 247 | for (int i = 0; i < reply_cnt; ++i) { 248 | const brpc::RedisReply& reply = _main_resp.reply(i); 249 | if (reply.is_error()) { 250 | RetryRequest retry; 251 | retry._input_rec = _input_records[i]; 252 | VLOG(3) << "redis server:" << from->addr() << " return err:" << reply.error_message() 253 | << " on key:" << encode_key(retry._input_rec->_key) 254 | << ", may need to retry on it"; 255 | if (should_retry(reply, &retry)) { 256 | if (_use_rec_as_output) { 257 | retry._output_rec = _user_output_records[i]; 258 | } else { 259 | retry._output_resp = _user_output_resp[i]; 260 | } 261 | to_retry.push_back(retry); 262 | } else { 263 | failed_cnt++; 264 | if (_use_rec_as_output) { 265 | Record* rec = _user_output_records[i]; 266 | rec->_errno = -1; 267 | rec->_err_msg = reply.error_message(); 268 | } else { 269 | set_error_to_resp(_user_output_resp[i], reply.error_message()); 270 | } 271 | } 272 | } else { 273 | success_cnt++; 274 | VLOG(5) << "redis server:" << from->addr() << " return succ" 275 | << " on key:" << encode_key(_input_records[i]->_key) << " on cmd:[" << _cmd 276 | << "]" 277 | << " reply type:" << brpc::RedisReplyTypeToString(reply.type()); 278 | if (_use_rec_as_output) { 279 | if (_read_op) { 280 | reply_of_get_to_record(reply, _user_output_records[i]); 281 | } else { 282 | reply_of_set_to_record(reply, _user_output_records[i]); 283 | } 284 | } else { 285 | brpc::RedisResponse* resp = _user_output_resp[i]; 286 | reply_to_resp(reply, resp); 287 | } 288 | } 289 | } 290 | if (success_cnt > 0) { 291 | _cluster->add_metrics(_main_cmd, from->addr(), true, cost, _main_req.ByteSize(), 292 | _main_resp.ByteSize(), success_cnt); 293 | } 294 | if (failed_cnt > 0) { 295 | _cluster->add_metrics(_main_cmd, from->addr(), false, cost, _main_req.ByteSize(), 296 | _main_resp.ByteSize(), failed_cnt); 297 | } 298 | 299 | if (!to_retry.empty()) { 300 | retry_in_sub(to_retry, from); 301 | return; 302 | } 303 | VLOG(2) << "succeed to handle resp from:" << from->addr() << " cmd:" << _cmd 304 | << " resp cnt:" << _main_resp.reply_size() << ", no need to do retry"; 305 | finish_resp(); 306 | } 307 | 308 | uint64_t PipeCtx::left_time_ms() const { 309 | int64_t used = (get_current_time_us() - _start_time_us) / 1000; 310 | return _acc_opt.timeout_ms > used ? _acc_opt.timeout_ms - used : 0; 311 | } 312 | void PipeCtx::finish_resp() { 313 | brpc::ClosureGuard user_callback(_user_cb); 314 | } 315 | 316 | void RetryClosure::Run() { 317 | if (!unref()) { 318 | return; 319 | } 320 | std::unique_ptr release_this(this); 321 | _pipe_req->finish_resp(); 322 | } 323 | 324 | void SubReqClosure::Run() { 325 | std::unique_ptr release_this(this); 326 | brpc::ClosureGuard done(_done); 327 | if (_rec) { 328 | if (_is_get) { 329 | reply_of_get_to_record(_resp->reply(0), _rec); 330 | } else { 331 | reply_of_set_to_record(_resp->reply(0), _rec); 332 | } 333 | } 334 | } 335 | 336 | } // namespace client 337 | } // namespace rsdk 338 | -------------------------------------------------------------------------------- /redis_sdk/src/redis_server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "redis_sdk/include/ctx.h" 14 | #include "redis_sdk/include/redis_server.h" 15 | 16 | namespace brpc { 17 | DECLARE_bool(redis_verbose_crlf2space); 18 | } // namespace brpc 19 | 20 | namespace rsdk { 21 | namespace client { 22 | 23 | DEFINE_bool(log_request_verbose, false, "log request verbose"); 24 | DEFINE_bool(feature_enable_bg_ping, true, "enable bg ping for node"); 25 | DEFINE_int32(bg_cmd_timeout_ms, 3000, "timeout for bg cmd"); 26 | 27 | BRPC_VALIDATE_GFLAG(log_request_verbose, brpc::PassValidate); 28 | BRPC_VALIDATE_GFLAG(feature_enable_bg_ping, brpc::PassValidate); 29 | BRPC_VALIDATE_GFLAG(bg_cmd_timeout_ms, brpc::PassValidate); 30 | 31 | static const int S_API_VERSION = 100; 32 | 33 | int64_t rand_logid() { 34 | return (int64_t)butil::fast_rand(); 35 | } 36 | std::string to_string(const brpc::RedisResponse& resp); 37 | ServerRole str_to_role(const std::string& str) { 38 | if (str == "master") { 39 | return ServerRole::LEADER; 40 | } 41 | if (str == "slave") { 42 | return ServerRole::FOLLOWER; 43 | } 44 | if (str == "fail") { 45 | return ServerRole::FAIL; 46 | } 47 | if (str == "noaddr") { 48 | return ServerRole::NO_ADDR; 49 | } 50 | return ServerRole::UNKNOWN; 51 | } 52 | std::string role_to_str(ServerRole role) { 53 | switch (role) { 54 | case ServerRole::LEADER: 55 | return "master"; 56 | case ServerRole::FOLLOWER: 57 | return "slave"; 58 | case ServerRole::FAIL: 59 | return "fail"; 60 | case ServerRole::NO_ADDR: 61 | return "noaddr"; 62 | case ServerRole::UNKNOWN: 63 | default: 64 | return "unknown"; 65 | } 66 | return "unknown"; 67 | } 68 | LinkState str_to_link_state(const std::string& str) { 69 | if (str == "connected") { 70 | return LinkState::CONNECTED; 71 | } 72 | if (str == "disconnected") { 73 | return LinkState::DIS_CONNECTED; 74 | } 75 | return LinkState::UNKNOWN; 76 | } 77 | std::string link_state_to_str(LinkState state) { 78 | switch (state) { 79 | case LinkState::CONNECTED: 80 | return "connected"; 81 | case LinkState::DIS_CONNECTED: 82 | return "disconnected"; 83 | case LinkState::UNKNOWN: 84 | return "unknown"; 85 | } 86 | return "unknown"; 87 | } 88 | 89 | RedisServer::~RedisServer() { 90 | stop(); 91 | join(); 92 | } 93 | 94 | void RedisServer::stop() { 95 | if (_has_bg_worker) { 96 | bthread_stop(_bg_ping_thread); 97 | } 98 | } 99 | void RedisServer::join() { 100 | if (_has_bg_worker) { 101 | bthread_join(_bg_ping_thread, nullptr); 102 | } 103 | } 104 | 105 | void* exec_bg_ping(void* arg) { 106 | if (!FLAGS_feature_enable_bg_ping) { 107 | return nullptr; 108 | } 109 | RedisServer* server = static_cast(arg); 110 | RedisServerOptions options = server->options(); 111 | int64_t interval_ms = options._conn_hc_interval_ms; 112 | interval_ms += butil::fast_rand() % interval_ms; 113 | while (!bthread_stopped(bthread_self())) { 114 | bthread_usleep(interval_ms * 1000); 115 | server->ping(); 116 | if (!FLAGS_feature_enable_bg_ping) { 117 | break; 118 | } 119 | } 120 | LOG(INFO) << "redis server:[" << server->to_str() << "] bg ping thread exit now"; 121 | return nullptr; 122 | } 123 | 124 | Status RedisServer::init(const RedisServerOptions& options) { 125 | _options = options; 126 | int normal = std::max(1, _options._conn_per_node); 127 | for (int i = 0; i < normal + 1; ++i) { 128 | brpc::ChannelOptions options; 129 | options.protocol = brpc::PROTOCOL_REDIS; 130 | options.connection_type = "single"; 131 | options.timeout_ms = _options._conn_timeout_ms; 132 | options.max_retry = _options._conn_retry_cnt; 133 | // 这里特地设置不同的connection_group,避免"single"的时候,会真的只有一个tcp 链接 134 | // Pool方式,链接数量不可控, 会有超过的情况,故使用single 135 | options.connection_group = fmt::format("redis_server_{}", i); 136 | if (i == normal) { 137 | options.connection_group = fmt::format("redis_server_slow_{}", i); 138 | } 139 | std::unique_ptr channel(new brpc::Channel()); 140 | if (channel->Init(_addr.c_str(), &options) != 0) { 141 | std::string msg = fmt::format("failed to init channel to redis server:{} with " 142 | "timeout:{}", 143 | _addr, _options._conn_timeout_ms); 144 | LOG(WARNING) << msg; 145 | return Status::SysError(msg); 146 | } 147 | if (i == normal) { 148 | _slow_channel = std::move(channel); 149 | continue; 150 | } else { 151 | _channels.push_back(std::move(channel)); 152 | } 153 | } 154 | if (bthread_start_background(&_bg_ping_thread, nullptr, exec_bg_ping, this) == 0) { 155 | _has_bg_worker = true; 156 | } 157 | return Status::OK(); 158 | } 159 | 160 | int RedisServer::execute_io_tasks(void* meta, bthread::TaskIterator& iter) { 161 | return 0; 162 | } 163 | 164 | std::string RedisServer::to_str() const { 165 | std::string str = fmt::format("id:{} at:{} role:{} leader_id:{} epoch:{} link_state:{}", _id, 166 | _addr, role_to_str(_role), _leader_id, _config_epoch, 167 | link_state_to_str(_link_state)); 168 | return str; 169 | } 170 | 171 | // master 172 | // slave 173 | // myself,master 174 | // master,fail,noaddr 175 | ServerRole RedisServer::parse_role_state(const std::string& role_str) { 176 | ServerRole state = ServerRole::UNKNOWN; 177 | auto pos = role_str.find("master"); 178 | if (pos != std::string::npos) { 179 | state = ServerRole::LEADER; 180 | } else { 181 | pos = role_str.find("slave"); 182 | if (pos != std::string::npos) { 183 | state = ServerRole::FOLLOWER; 184 | } 185 | } 186 | pos = role_str.find("fail"); 187 | if (pos != std::string::npos) { 188 | state = ServerRole::FAIL; 189 | } 190 | pos = role_str.find("noaddr"); 191 | if (pos != std::string::npos) { 192 | state = ServerRole::NO_ADDR; 193 | } 194 | return state; 195 | } 196 | 197 | Status RedisServer::send_inner_cmd(const std::string& cmd, brpc::RedisResponse* resp, int index) { 198 | brpc::RedisRequest request; 199 | brpc::Controller cntl; 200 | cntl.set_timeout_ms(FLAGS_bg_cmd_timeout_ms); 201 | request.AddCommand(cmd); 202 | if (index < 0 && _slow_channel) { 203 | _slow_channel->CallMethod(nullptr, &cntl, &request, resp, nullptr); 204 | } else { 205 | index = (index < 0) ? 0 : index; 206 | index = index % _channels.size(); 207 | _channels[index]->CallMethod(nullptr, &cntl, &request, resp, nullptr); 208 | } 209 | if (cntl.Failed()) { 210 | std::string msg = fmt::format("failed to access redis server:{} with cmd:[{}], got err:{}", 211 | _addr, cmd, cntl.ErrorText()); 212 | return Status::SysError(msg); 213 | } 214 | if (resp->reply_size() == 0) { 215 | std::string msg = fmt::format("cmd:[{}] to node:{} reply size is 0", cmd, _addr); 216 | return Status::SysError(msg); 217 | } 218 | const brpc::RedisReply& reply0 = resp->reply(0); 219 | if (reply0.is_error()) { 220 | std::string msg = fmt::format("cmd:[{}] to node:{} reply is error:{}", cmd, _addr, 221 | reply0.error_message()); 222 | return Status::SysError(msg); 223 | } 224 | VLOG(3) << "succeed to send cmd[" << cmd << "] to " << _addr; 225 | // << ", got resp:" << to_string(*resp); 226 | return Status::OK(); 227 | } 228 | 229 | void RedisServer::ping() { 230 | if (!_online) { 231 | return; 232 | } 233 | brpc::RedisRequest request; 234 | brpc::RedisResponse response; 235 | brpc::Controller cntl; 236 | request.AddCommand("ping"); 237 | int channel_cnt = _channels.size() + 1; 238 | ++_ping_index; 239 | _ping_index = _ping_index % channel_cnt; 240 | Status ret; 241 | if ((_ping_index + 1) == channel_cnt) { 242 | ret = send_inner_cmd("ping", &response, -1); 243 | } else { 244 | ret = send_inner_cmd("ping", &response, _ping_index); 245 | } 246 | if (!ret.ok()) { 247 | LOG(WARNING) << "failed to ping node:" << _addr << ", got err:" << ret; 248 | return; 249 | } 250 | } 251 | 252 | RedisStatus parse_redis_error(const std::string& str) { 253 | if (str == S_STATUS_OK) { 254 | return RedisStatus::OK; 255 | } 256 | if (str == S_STATUS_MOVED) { 257 | return RedisStatus::MOVED; 258 | } 259 | if (str == S_STATUS_ASK) { 260 | return RedisStatus::ASK; 261 | } 262 | if (str == S_STATUS_TRYAGAIN) { 263 | return RedisStatus::TRY_AGAIN; 264 | } 265 | if (str == S_STATUS_CROSSSLOT) { 266 | return RedisStatus::CROSS_SLOT; 267 | } 268 | if (str == S_STATUS_CLUSTERDOWN) { 269 | return RedisStatus::CLUSTER_DOWN; 270 | } 271 | return RedisStatus::UNKNOWN; 272 | } 273 | 274 | std::string redis_status_to_str(const RedisStatus& s) { 275 | switch (s) { 276 | case RedisStatus::OK: 277 | return S_STATUS_OK; 278 | case RedisStatus::MOVED: 279 | return S_STATUS_MOVED; 280 | case RedisStatus::ASK: 281 | return S_STATUS_ASK; 282 | case RedisStatus::TRY_AGAIN: 283 | return S_STATUS_TRYAGAIN; 284 | 285 | case RedisStatus::CROSS_SLOT: 286 | return S_STATUS_CROSSSLOT; 287 | case RedisStatus::CLUSTER_DOWN: 288 | return S_STATUS_CLUSTERDOWN; 289 | default: 290 | return "UNKNOWN"; 291 | } 292 | } 293 | 294 | } // namespace client 295 | } // namespace rsdk 296 | -------------------------------------------------------------------------------- /redis_sdk/src/slot.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "redis_sdk/include/cluster.h" 13 | 14 | namespace rsdk { 15 | namespace client { 16 | 17 | // TODO: Avoid using non-trivial variables with static storage duration. 18 | const std::string S_CMD_CLUSTER_NODES = "cluster nodes"; 19 | const std::string S_CMD_CLUSTER_SLOTS = "cluster slots"; 20 | const size_t S_SLOT_CNT = 16384; 21 | 22 | static const int S_API_VERSION = 100; 23 | 24 | std::string Slot::describe() const { 25 | std::string msg = fmt::format("id:{} sn:{} version:{} config_epoch:{} addr:{}", _id, _sn, 26 | _version, _config_epoch.load(), _leader->addr()); 27 | return msg; 28 | } 29 | 30 | void Slot::update_servers(RedisServerPtr leader, const std::vector& servers, 31 | int64_t cepoch) { 32 | if (servers.empty() || leader == nullptr) { 33 | return; 34 | } 35 | std::lock_guard lock(_servers_mutex); 36 | if (cepoch < _config_epoch.load()) { 37 | return; 38 | } 39 | _leader = leader; 40 | _servers = servers; 41 | _config_epoch.store(cepoch); 42 | } 43 | 44 | RedisServerPtr Slot::get_server_for_read(ReadPolicy policy) { 45 | std::lock_guard lock(_servers_mutex); 46 | if (_servers.empty()) { 47 | return _leader; 48 | } 49 | if (policy == ReadPolicy::LEADER_ONLY) { 50 | return _leader; 51 | } 52 | ++_acc_index; 53 | _acc_index %= _servers.size(); 54 | // VLOG(3) << "retry index:" << _acc_index << " size:" << _servers.size() << " addr:" 55 | // << _servers[_acc_index]->addr() << " leader:" << _leader->addr(); 56 | return _servers[_acc_index]; 57 | } 58 | 59 | RedisServerPtr Slot::get_leader() { 60 | std::lock_guard lock(_servers_mutex); 61 | return _leader; 62 | } 63 | 64 | SlotMgr::SlotMgr() { 65 | _slots.resize(S_SLOT_CNT, nullptr); 66 | } 67 | 68 | SlotPtr SlotMgr::get_slot_by_id(uint64_t slot_id) { 69 | return _slots[slot_id]; 70 | } 71 | uint32_t key_to_slot_id(const char* key, int keylen); 72 | SlotPtr SlotMgr::get_slot_by_key(const std::string& key) { 73 | uint32_t id = key_to_slot_id(key.c_str(), key.length()); 74 | // NOTE: the slot count can not be modified 75 | return get_slot_by_id(id); 76 | } 77 | 78 | bool SlotMgr::add_slot(SlotPtr slot) { 79 | if (_slots[slot->id()] == nullptr) { 80 | _slots[slot->id()] = slot; 81 | ++_valid_slot_cnt; 82 | return true; 83 | } 84 | return false; 85 | } 86 | 87 | Status SlotMgr::update_slot(const std::string& data, RedisServerPtr leader, 88 | const std::vector& servers, int64_t config_epoch) { 89 | if (leader == nullptr || servers.empty()) { 90 | std::string msg = fmt::format("bug, invalid slot:[{}] of cluster nodes on node:nullptr", 91 | data); 92 | LOG(ERROR) << msg; 93 | return Status::SysError(msg); 94 | } 95 | // 0-5460 [8->-15d9d385e01496cafac99b3013a4af46969b76e4] 96 | if (data.empty() || (data.at(0) == '[' && data.at(data.length() - 1) == ']')) { 97 | LOG(INFO) << "skip moving slot:" << data << ", just use current state"; 98 | return Status::OK(); 99 | } 100 | int64_t start_slot = 0; 101 | int64_t end_slot = 0; 102 | 103 | if (data.find("-") != std::string::npos) { 104 | std::vector part; 105 | butil::SplitString(data, '-', &part); 106 | if (part.size() != 2) { 107 | std::string msg = fmt::format("invalid slot range:[{}] of cluster nodes on node:{}", 108 | data, leader->to_str()); 109 | LOG(WARNING) << msg; 110 | return Status::SysError(msg); 111 | } 112 | start_slot = strtol(part[0].c_str(), nullptr, 10); 113 | end_slot = strtol(part[1].c_str(), nullptr, 10); 114 | if (std::to_string(start_slot) != part[0] || std::to_string(end_slot) != part[1]) { 115 | std::string msg = fmt::format("invalid slot range:[{}] of cluster nodes on node:{}", 116 | data, leader->to_str()); 117 | LOG(WARNING) << msg; 118 | return Status::SysError(msg); 119 | } 120 | } else { 121 | start_slot = strtol(data.c_str(), nullptr, 10); 122 | if (std::to_string(start_slot) != data) { 123 | std::string msg = fmt::format("invalid slot:[{}] of cluster nodes on node:{}", data, 124 | leader->to_str()); 125 | LOG(WARNING) << msg; 126 | return Status::SysError(msg); 127 | } 128 | end_slot = start_slot; 129 | } 130 | for (int id = start_slot; id <= end_slot; ++id) { 131 | bool fill_slot = false; 132 | SlotPtr slot = get_slot_by_id(id); 133 | if (!slot) { 134 | slot = std::make_shared("", id, id); 135 | fill_slot = true; 136 | } 137 | 138 | slot->update_servers(leader, servers, config_epoch); 139 | 140 | if (fill_slot) { 141 | add_slot(slot); 142 | } 143 | } 144 | return Status::OK(); 145 | } 146 | 147 | } // namespace client 148 | } // namespace rsdk 149 | -------------------------------------------------------------------------------- /test/BUILD.bazel: -------------------------------------------------------------------------------- 1 | COPTS = [ 2 | "-D__STDC_FORMAT_MACROS", 3 | "-DBTHREAD_USE_FAST_PTHREAD_MUTEX", 4 | "-D__const__=__unused__", 5 | "-D_GNU_SOURCE", 6 | "-DUSE_SYMBOLIZE", 7 | "-DNO_TCMALLOC", 8 | "-D__STDC_LIMIT_MACROS", 9 | "-D__STDC_CONSTANT_MACROS", 10 | "-fPIC", 11 | "-Wno-unused-parameter", 12 | "-fno-omit-frame-pointer", 13 | "-DGFLAGS_NS=google", 14 | "-Dprivate=public", 15 | "-Dprotected=public", 16 | "-DBAZEL_TEST=1", 17 | "--include test/sstream_define_as_public.h", 18 | "-DBVAR_NOT_LINK_DEFAULT_VARIABLES", 19 | "-DUNIT_TEST", 20 | ] 21 | 22 | cc_library( 23 | name = "test_common", 24 | srcs = [ 25 | "mock_node.cpp", 26 | "test_common.cpp", 27 | ], 28 | hdrs = [ 29 | "mock_node.h", 30 | "sstream_define_as_public.h", 31 | "test_common.h", 32 | ], 33 | copts = COPTS, 34 | visibility = ["//visibility:public"], 35 | deps = [ 36 | "//redis_sdk:redis_sdk_impl", 37 | "@com_github_brpc_brpc//:brpc", 38 | "@com_google_googletest//:gtest", 39 | ], 40 | ) 41 | 42 | cc_test( 43 | name = "client_impl_test", 44 | srcs = ["client_impl_test.cpp"], 45 | copts = COPTS, 46 | deps = [ 47 | "test_common", 48 | ], 49 | ) 50 | 51 | cc_test( 52 | name = "real_crud_test", 53 | srcs = ["real_crud_test.cpp"], 54 | copts = COPTS, 55 | deps = [ 56 | "test_common", 57 | ], 58 | ) 59 | 60 | cc_test( 61 | name = "redirect_test", 62 | srcs = ["redirect_test.cpp"], 63 | copts = COPTS, 64 | deps = [ 65 | "test_common", 66 | ], 67 | ) 68 | 69 | cc_test( 70 | name = "slot_test", 71 | srcs = ["slot_test.cpp"], 72 | copts = COPTS, 73 | deps = [ 74 | "test_common", 75 | ], 76 | ) 77 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.7) 2 | 3 | # 项目信息 4 | project (rsdk) 5 | 6 | set(CMAKE_CPP_FLAGS "${CMAKE_CPP_FLAGS} -O0 -Dprivate=public -Dprotected=public -include ${PROJECT_SOURCE_DIR}/sstream_define_as_public.h") 7 | set(CMAKE_CXX_FLAGS "${CMAKE_CPP_FLAGS} -O0 -g --coverage -pipe -Wall -W -fPIC -fstrict-aliasing -Wno-invalid-offsetof -Wno-unused-parameter -fno-omit-frame-pointer -Wno-implicit-fallthrough -faligned-new") 8 | set(CMAKE_C_FLAGS "${CMAKE_CPP_FLAGS} -O0 -g --coverage -pipe -Wall -W -fPIC -fstrict-aliasing -Wno-unused-parameter -fno-omit-frame-pointer -Wno-implicit-fallthrough") 9 | 10 | protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS http_mgr.proto) 11 | 12 | include_directories( 13 | ${PROJECT_SOURCE_DIR} 14 | ${CMAKE_CURRENT_BINARY_DIR} 15 | ) 16 | 17 | file(GLOB ut_api_src "${PROJECT_SOURCE_DIR}/../redis_sdk/src/*.cpp") 18 | set(ut_api_src ${ut_api_src} "${PROJECT_SOURCE_DIR}/test_common.cpp" "${PROJECT_SOURCE_DIR}/mock_node.cpp" ${PROTO_SRCS} ) 19 | 20 | add_library(api_ut STATIC ${ut_api_src}) 21 | target_link_libraries(api_ut) 22 | 23 | find_package(GTest CONFIG REQUIRED) 24 | 25 | set(test_deps api_ut) 26 | set(test_deps ${test_deps} ${BRPC_LIB}) 27 | set(test_deps ${test_deps} ${LEVELDB_LIB}) 28 | set(test_deps ${test_deps} ${UUID_LIB}) 29 | set(test_deps ${test_deps} ${Protobuf_LIBRARIES}) 30 | set(test_deps ${test_deps} glog::glog) 31 | set(test_deps ${test_deps} prometheus-cpp::core) 32 | set(test_deps ${test_deps} gflags) 33 | set(test_deps ${test_deps} fmt) 34 | set(test_deps ${test_deps} xxhash) 35 | set(test_deps ${test_deps} z) 36 | set(test_deps ${test_deps} unwind) 37 | set(test_deps ${test_deps} ssl) 38 | set(test_deps ${test_deps} dl) 39 | set(test_deps ${test_deps} crypto) 40 | set(test_deps ${test_deps} snappy) 41 | set(test_deps ${test_deps} pthread) 42 | 43 | set(test_deps ${test_deps} GTest::gtest GTest::gtest_main) 44 | 45 | file(GLOB UNITTESTS "*_test.cpp") 46 | 47 | foreach(UNITTEST ${UNITTESTS}) 48 | get_filename_component(EXECUTABLE_NAME ${UNITTEST} NAME_WE) 49 | message(STATUS "add unittest executable ${EXECUTABLE_NAME}") 50 | add_executable(${EXECUTABLE_NAME} ${UNITTEST}) 51 | target_link_libraries(${EXECUTABLE_NAME} ${test_deps}) 52 | add_test(NAME ${EXECUTABLE_NAME} COMMAND $ WORKING_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) 53 | endforeach(UNITTEST) 54 | 55 | -------------------------------------------------------------------------------- /test/ctx_test.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "redis_sdk/include/ctx.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "redis_sdk/api_status.h" 11 | #include "redis_sdk/client.h" 12 | 13 | namespace rsdk { 14 | namespace client { 15 | namespace test { 16 | 17 | class CtxTest : public ::testing::Test { 18 | public: 19 | CtxTest() { 20 | } 21 | static void SetUpTestCase() { 22 | int ret = system("rm -rf ./log/"); 23 | ret = system("mkdir -p ./log/"); 24 | (void)ret; 25 | } 26 | void SetUp() { 27 | } 28 | void TearDown() { 29 | } 30 | }; 31 | 32 | TEST_F(CtxTest, ShouldRetryTest) { 33 | ClusterOptions opitons; 34 | opitons._name = "mock_cluster"; 35 | auto cluster = std::make_shared(opitons); 36 | std::string normal_cmd = "get key"; 37 | brpc::RedisRequest req; 38 | req.AddCommand(normal_cmd); 39 | brpc::RedisResponse resp; 40 | std::string key = "key"; 41 | IOCtx ctx(cluster, "get", "get", std::move(key), std::move(req), &resp, nullptr); 42 | 43 | // time exhausted 44 | ctx._start_time_us = get_current_time_us() - 10000 * 1000L; 45 | RedisStatus move_status = RedisStatus::MOVED; 46 | ASSERT_FALSE(ctx.should_retry(move_status)); 47 | 48 | // retry 49 | ctx._start_time_us = get_current_time_us(); 50 | ASSERT_TRUE(ctx.should_retry(move_status)); 51 | 52 | // has ask head 53 | ctx.add_ask_head(); 54 | // again 55 | ctx.add_ask_head(); 56 | ctx._start_time_us = get_current_time_us() + 10000 * 1000L; 57 | ASSERT_FALSE(ctx.should_retry(move_status)); 58 | } 59 | 60 | TEST_F(CtxTest, HandleResponseTest) { 61 | ClusterOptions opitons; 62 | opitons._name = "mock_cluster"; 63 | auto cluster = std::make_shared(opitons); 64 | std::string normal_cmd = "get key"; 65 | std::string key = "key"; 66 | brpc::RedisRequest req; 67 | req.AddCommand(normal_cmd); 68 | brpc::RedisResponse resp; 69 | resp._nreply = 1; 70 | resp._first_reply.SetError("MOVED MOVED MOVED"); 71 | IOCtx* ctx = new IOCtx(cluster, "get", "get", std::move(key), std::move(req), &resp, nullptr); 72 | 73 | ctx->_start_time_us = get_current_time_us(); 74 | ctx->_retry_cnt += 2; 75 | auto mock_server = std::make_shared("mock_address"); 76 | ctx->handle_resp(mock_server); 77 | const brpc::RedisReply& reply0 = resp.reply(0); 78 | ASSERT_TRUE(reply0.is_error()); 79 | std::string msg = reply0.error_message(); 80 | ASSERT_TRUE(msg.find("exceed max:2") != std::string::npos); 81 | } 82 | 83 | TEST_F(CtxTest, SkipAskHeadTest) { 84 | ClusterOptions opitons; 85 | opitons._name = "mock_cluster"; 86 | auto cluster = std::make_shared(opitons); 87 | std::string normal_cmd = "get key"; 88 | std::string key = "key"; 89 | brpc::RedisRequest req; 90 | req.AddCommand(normal_cmd); 91 | 92 | brpc::RedisResponse resp; 93 | resp._nreply = 1; 94 | IOCtx ctx(cluster, "get", "get", std::move(key), std::move(req), &resp, nullptr); 95 | ctx.add_ask_head(); 96 | ctx.may_skip_ask_head(); 97 | } 98 | 99 | } // namespace test 100 | } // namespace client 101 | } // namespace rsdk 102 | 103 | DECLARE_string(flagfile); 104 | DECLARE_int32(logbufsecs); 105 | DECLARE_int32(v); 106 | DECLARE_string(log_dir); // defined in glog 107 | 108 | int main(int argc, char* argv[]) { 109 | FLAGS_v = 3; 110 | FLAGS_logbufsecs = 0; 111 | FLAGS_log_dir = "log"; 112 | ::testing::InitGoogleTest(&argc, argv); 113 | google::ParseCommandLineFlags(&argc, &argv, true); 114 | 115 | ::mkdir(FLAGS_log_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); 116 | google::InitGoogleLogging(argv[0]); 117 | google::SetStderrLogging(google::FATAL); 118 | 119 | int ret = RUN_ALL_TESTS(); 120 | return ret; 121 | } 122 | -------------------------------------------------------------------------------- /test/http_mgr.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | 3 | package rsdk.client.test; 4 | 5 | option cc_generic_services = true; 6 | 7 | message HttpRequest {}; 8 | message HttpResponse {}; 9 | 10 | service HttpService { 11 | rpc get(HttpRequest) returns (HttpResponse); 12 | }; 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/mock_mgr_http.h: -------------------------------------------------------------------------------- 1 | #ifndef RSDK_TEST_MOCK_MGR_HTTP_H 2 | #define RSDK_TEST_MOCK_MGR_HTTP_H 3 | 4 | #include 5 | #include 6 | 7 | #include // brpc::Controller 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | #include 20 | #include "test/http_mgr.pb.h" 21 | 22 | namespace rsdk { 23 | namespace client { 24 | namespace test { 25 | 26 | class MockHttpServiceImpl : public HttpService { 27 | public: 28 | MockHttpServiceImpl() { 29 | } 30 | virtual ~MockHttpServiceImpl() { 31 | } 32 | void get(google::protobuf::RpcController* cntl_base, const HttpRequest* req, HttpResponse* resp, 33 | google::protobuf::Closure* done) { 34 | brpc::ClosureGuard done_guard(done); 35 | brpc::Controller* cntl = static_cast(cntl_base); 36 | std::lock_guard lock(_mutex); 37 | if (_root) { 38 | std::stringstream ss; 39 | ss << *_root; 40 | std::string value = ss.str(); 41 | cntl->response_attachment().append(value); 42 | } 43 | } 44 | void set_root(std::shared_ptr root) { 45 | std::lock_guard lock(_mutex); 46 | _root = root; 47 | } 48 | 49 | public: 50 | bool _running = true; 51 | bthread::Mutex _mutex; 52 | std::shared_ptr _root; 53 | }; 54 | 55 | class MockHttpServer { 56 | public: 57 | MockHttpServer() : _port(0) { 58 | } 59 | ~MockHttpServer() { 60 | stop(); 61 | } 62 | int start() { 63 | _service = new MockHttpServiceImpl(); 64 | int ret = _server.AddService(_service, brpc::SERVER_OWNS_SERVICE, 65 | "/overlord/api/v3/app/toml => get"); 66 | 67 | if (ret != 0) { 68 | LOG(ERROR) << "Fail to add mock metaserver service to rpc server"; 69 | return -1; 70 | } 71 | int start_port = 5000; 72 | int end_port = 5999; 73 | brpc::PortRange range(start_port, end_port); 74 | if (0 != _server.Start("127.0.0.1", range, nullptr)) { 75 | LOG(ERROR) << "failed to start server from port:" << start_port << " to:" << end_port; 76 | return -1; 77 | } 78 | _port = _server.listen_address().port; 79 | _ip_port = "127.0.0.1:" + std::to_string(_port); 80 | VLOG(1) << "mock node start to work at:" << addr(); 81 | return 0; 82 | } 83 | void stop() { 84 | if (!_runing) { 85 | return; 86 | } 87 | _service->_running = false; 88 | _runing = false; 89 | int ret = _server.Stop(0); 90 | if (0 != ret) { 91 | LOG(ERROR) << "failed to stop server at:" << addr(); 92 | } 93 | _server.Join(); 94 | VLOG(1) << "succeed to stop server at:" << addr(); 95 | } 96 | int port() { 97 | return _port; 98 | } 99 | std::string addr() { 100 | return _ip_port; 101 | } 102 | std::string name() { 103 | return addr(); 104 | } 105 | 106 | public: 107 | brpc::Server _server; 108 | MockHttpServiceImpl* _service = nullptr; 109 | int _port = 0; 110 | std::string _ip_port; 111 | bool _runing = true; 112 | }; 113 | 114 | } // namespace test 115 | } // namespace client 116 | } // namespace rsdk 117 | 118 | #endif // RSDK_TEST_MOCK_MGR_HTTP_H 119 | -------------------------------------------------------------------------------- /test/mock_node.h: -------------------------------------------------------------------------------- 1 | #ifndef RSDK_TEST_MOCK_NODE_H 2 | #define RSDK_TEST_MOCK_NODE_H 3 | 4 | #include 5 | #include 6 | 7 | #include // brpc::Controller 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include 18 | 19 | namespace rsdk { 20 | namespace client { 21 | namespace test { 22 | 23 | struct Value { 24 | Value() { 25 | } 26 | Value(const std::string& v) : _value(v) { 27 | } 28 | Value(const std::string& v, int s) : _value(v), _ttl_at(s) { 29 | } 30 | bool timeout() const; 31 | std::string _value; 32 | int _ttl_at = 0; 33 | }; 34 | 35 | class RedisServiceImpl : public brpc::RedisService { 36 | public: 37 | bool set(const std::string& key, const std::string& value, int ttl, brpc::RedisReply* output); 38 | bool exists(const std::string& key, brpc::RedisReply* output); 39 | bool get(const std::string& key, brpc::RedisReply* output); 40 | bool cluster_nodes(brpc::RedisReply* output); 41 | bool handle_asking(brpc::RedisReply* output); 42 | void reset_put_cnt() { 43 | } 44 | void reset_get_cnt() { 45 | } 46 | void may_sleep(); 47 | bool handle_sadd(const std::string& key, const std::vector& params, 48 | brpc::RedisReply* output); 49 | bool handle_smember(const std::string& key, brpc::RedisReply* output); 50 | 51 | std::atomic _running = {true}; 52 | butil::Mutex _mutex; 53 | std::string _addr; 54 | std::string _leader_addr; 55 | std::unordered_map _slot2addr; 56 | std::map _kv; 57 | std::map> _sets; 58 | int _sleep_ms = 0; 59 | std::atomic _sleep_cnt = {0}; 60 | std::string _desc; 61 | std::string _cluster_nodes; 62 | std::string _cluster_nodes_status = "OK"; 63 | std::string _crud_status = "OK"; 64 | std::string _asking_status = "OK"; 65 | std::string _mock_exist_ret; 66 | int _mock_status_cnt = 0; 67 | int _get_cnt = 0; 68 | bool _enable_follow_read = false; 69 | int _moved_cnt = 0; 70 | bool _always_moved = false; 71 | }; 72 | 73 | class GetCommandHandler : public brpc::RedisCommandHandler { 74 | public: 75 | explicit GetCommandHandler(RedisServiceImpl* rsimpl) : _rsimpl(rsimpl) { 76 | } 77 | 78 | brpc::RedisCommandHandlerResult Run(const std::vector& args, 79 | brpc::RedisReply* output, bool /*flush_batched*/) override; 80 | 81 | private: 82 | RedisServiceImpl* _rsimpl = nullptr; 83 | }; 84 | 85 | class ExistsCommandHandler : public brpc::RedisCommandHandler { 86 | public: 87 | explicit ExistsCommandHandler(RedisServiceImpl* rsimpl) : _rsimpl(rsimpl) { 88 | } 89 | 90 | brpc::RedisCommandHandlerResult Run(const std::vector& args, 91 | brpc::RedisReply* output, bool /*flush_batched*/) override; 92 | 93 | private: 94 | RedisServiceImpl* _rsimpl = nullptr; 95 | }; 96 | 97 | class SetCommandHandler : public brpc::RedisCommandHandler { 98 | public: 99 | explicit SetCommandHandler(RedisServiceImpl* rsimpl) : _rsimpl(rsimpl) { 100 | } 101 | 102 | brpc::RedisCommandHandlerResult Run(const std::vector& args, 103 | brpc::RedisReply* output, bool /*flush_batched*/) override; 104 | 105 | private: 106 | RedisServiceImpl* _rsimpl = nullptr; 107 | }; 108 | 109 | class ClusterCommandHandler : public brpc::RedisCommandHandler { 110 | public: 111 | explicit ClusterCommandHandler(RedisServiceImpl* rsimpl) : _rsimpl(rsimpl) { 112 | } 113 | 114 | brpc::RedisCommandHandlerResult Run(const std::vector& args, 115 | brpc::RedisReply* output, bool /*flush_batched*/) override; 116 | 117 | private: 118 | RedisServiceImpl* _rsimpl = nullptr; 119 | }; 120 | 121 | class AskingCommandHandler : public brpc::RedisCommandHandler { 122 | public: 123 | explicit AskingCommandHandler(RedisServiceImpl* rsimpl) : _rsimpl(rsimpl) { 124 | } 125 | 126 | brpc::RedisCommandHandlerResult Run(const std::vector& args, 127 | brpc::RedisReply* output, bool /*flush_batched*/) override; 128 | 129 | private: 130 | RedisServiceImpl* _rsimpl = nullptr; 131 | }; 132 | class PingCommandHandler : public brpc::RedisCommandHandler { 133 | public: 134 | explicit PingCommandHandler() { 135 | } 136 | 137 | brpc::RedisCommandHandlerResult Run(const std::vector& args, 138 | brpc::RedisReply* output, bool /*flush_batched*/) override; 139 | }; 140 | 141 | class SAddCommandHandler : public brpc::RedisCommandHandler { 142 | public: 143 | explicit SAddCommandHandler(RedisServiceImpl* rsimpl) : _rsimpl(rsimpl) { 144 | } 145 | 146 | brpc::RedisCommandHandlerResult Run(const std::vector& args, 147 | brpc::RedisReply* output, bool /*flush_batched*/) override; 148 | 149 | private: 150 | RedisServiceImpl* _rsimpl = nullptr; 151 | }; 152 | 153 | class SMembersCommandHandler : public brpc::RedisCommandHandler { 154 | public: 155 | explicit SMembersCommandHandler(RedisServiceImpl* rsimpl) : _rsimpl(rsimpl) { 156 | } 157 | 158 | brpc::RedisCommandHandlerResult Run(const std::vector& args, 159 | brpc::RedisReply* output, bool /*flush_batched*/) override; 160 | 161 | private: 162 | RedisServiceImpl* _rsimpl = nullptr; 163 | }; 164 | 165 | class MockRedisServer { 166 | public: 167 | MockRedisServer(int index) : _port(0), _index(index) { 168 | } 169 | ~MockRedisServer() { 170 | stop(); 171 | } 172 | int start(); 173 | void stop(); 174 | int port() { 175 | return _port; 176 | } 177 | std::string addr() { 178 | return std::string("127.0.0.1:") + std::to_string(_port); 179 | } 180 | void reset_put_cnt() { 181 | _service->reset_put_cnt(); 182 | } 183 | void reset_get_cnt() { 184 | _service->reset_get_cnt(); 185 | } 186 | std::string name() { 187 | return addr(); 188 | } 189 | void set_leader_name(const std::string& leader_name) { 190 | _leader_name = leader_name; 191 | _service->_leader_addr = leader_name; 192 | } 193 | void set_slot_ids(const std::vector& slot_ids); 194 | void set_slot_dist(const std::string& slot_dist) { 195 | _slot_dist = slot_dist; 196 | } 197 | void set_slot2addr(const std::unordered_map& slot2addr) { 198 | _service->_slot2addr = slot2addr; 199 | } 200 | std::string build_cluster_nodes(); 201 | 202 | public: 203 | RedisServiceImpl* _service = nullptr; 204 | brpc::Server _server; 205 | int _port = 0; 206 | int _index = 0; 207 | bool _runing = true; 208 | std::string _leader_name; 209 | std::set _slot_ids; 210 | std::string _slot_dist; 211 | std::string _state = "connected"; 212 | int _config_epoch = 0; 213 | }; 214 | 215 | typedef std::shared_ptr MockRedisServerPtr; 216 | typedef std::vector MockRedisServerPtrList; 217 | 218 | struct LeaderAndSlaves { 219 | MockRedisServerPtr _leader; 220 | MockRedisServerPtrList _slaves; 221 | }; 222 | struct ClusterDist { 223 | std::vector _leaders_and_slaves; 224 | }; 225 | 226 | } // namespace test 227 | } // namespace client 228 | } // namespace rsdk 229 | 230 | #endif // RSDK_TEST_MOCK_NODE_H 231 | -------------------------------------------------------------------------------- /test/real_crud_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "redis_sdk/include/client_impl.h" 16 | #include "test/mock_node.h" 17 | #include "test/test_common.h" 18 | 19 | namespace rsdk { 20 | namespace client { 21 | 22 | std::string to_string(const brpc::RedisResponse& resp); 23 | uint32_t key_to_slot_id(const char* key, int keylen); 24 | 25 | DECLARE_bool(log_request_verbose); 26 | DEFINE_int64(key_cnt, 10000, "key count for test"); 27 | DEFINE_int32(conn_per_node, 1, "connection count per node"); 28 | DEFINE_string(seed_node, "127.0.0.1:21010", "seed node of cluster"); 29 | 30 | namespace test { 31 | 32 | class ReadEnvTest : public ::testing::Test { 33 | public: 34 | ReadEnvTest() { 35 | } 36 | ~ReadEnvTest() { 37 | } 38 | static void SetUpTestCase() { 39 | int ret = system("rm -rf ./log/"); 40 | ret = system("mkdir -p ./log/"); 41 | (void)ret; 42 | } 43 | void SetUp() { 44 | // FLAGS_log_request_verbose = true; 45 | } 46 | void TearDown() { 47 | } 48 | std::string build_cluster_name() { 49 | ++_cluster_id; 50 | return "cluster" + std::to_string(_cluster_id); 51 | } 52 | int _cluster_id = 0; 53 | }; 54 | 55 | TEST_F(ReadEnvTest, MgetMset) { 56 | std::vector seeds; 57 | seeds.push_back(FLAGS_seed_node); 58 | 59 | std::string cname = "local_test"; 60 | ClientManagerPtr manager = new_client_mgr(); 61 | ASSERT_TRUE(manager != nullptr); 62 | ClusterOptions coptions; 63 | coptions._name = cname; 64 | coptions._seeds = seeds; 65 | coptions._server_options._conn_per_node = 1; 66 | coptions._server_options._conn_hc_interval_ms = 10; 67 | Status status = manager->add_cluster(coptions); 68 | ASSERT_TRUE(status.ok()) << status; 69 | AccessOptions opt; 70 | opt.timeout_ms = 50; 71 | opt._cname = cname; 72 | ClientPtr client = manager->new_client(opt); 73 | ASSERT_TRUE(client != nullptr); 74 | 75 | int batch_cnt = 200; 76 | int ikey = 0; 77 | std::vector resps; 78 | RecordList records; 79 | std::vector expects; 80 | { 81 | for (int x = 0; x < batch_cnt; ++x) { 82 | Record record; 83 | record._key = fmt::format("rec_key_{}", ikey); 84 | record._data = fmt::format("rec_value_{}", ikey); 85 | expects.push_back(record._data); 86 | records.emplace_back(record); 87 | ++ikey; 88 | } 89 | client->mset(records, &resps); 90 | ASSERT_EQ(resps.size(), batch_cnt); 91 | for (int x = 0; x < batch_cnt; ++x) { 92 | const brpc::RedisReply& reply = resps[x].reply(0); 93 | ASSERT_TRUE(reply.is_string()) << reply.type(); 94 | ASSERT_STREQ("OK", reply.c_str()); 95 | } 96 | } 97 | // 98 | { 99 | client->mget(records, &resps); 100 | ASSERT_EQ(resps.size(), batch_cnt); 101 | for (int x = 0; x < batch_cnt; ++x) { 102 | const brpc::RedisReply& sub = resps[x].reply(0); 103 | ASSERT_TRUE(sub.is_string()) << rsdk::client::to_string(resps[x]); 104 | std::string value = sub.c_str(); 105 | std::string expect = expects[x]; 106 | ASSERT_EQ(expect, value); 107 | } 108 | } 109 | } 110 | 111 | TEST_F(ReadEnvTest, BatchAsArray) { 112 | std::vector seeds; 113 | seeds.push_back(FLAGS_seed_node); 114 | 115 | std::string cname = "local_test"; 116 | ClientManagerPtr manager = new_client_mgr(); 117 | ASSERT_TRUE(manager != nullptr); 118 | ClusterOptions coptions; 119 | coptions._name = cname; 120 | coptions._seeds = seeds; 121 | coptions._server_options._conn_per_node = 1; 122 | coptions._server_options._conn_hc_interval_ms = 10; 123 | Status status = manager->add_cluster(coptions); 124 | ASSERT_TRUE(status.ok()) << status; 125 | AccessOptions opt; 126 | opt.timeout_ms = 50; 127 | opt._cname = cname; 128 | opt._merge_batch_as_array = true; 129 | ClientPtr client = manager->new_client(opt); 130 | ASSERT_TRUE(client != nullptr); 131 | 132 | int batch_cnt = 10; 133 | int ikey = 0; 134 | for (int i = 0; i < 1; ++i) { 135 | brpc::RedisResponse resp; 136 | std::string cmd = "mset"; 137 | for (int x = 0; x < batch_cnt; ++x) { 138 | cmd += fmt::format(" key_{} value_{}", ikey, ikey); 139 | ++ikey; 140 | } 141 | client->exec(cmd, &resp); 142 | ASSERT_EQ(resp.reply_size(), 1); 143 | ASSERT_EQ(resp.reply(0).size(), batch_cnt); 144 | ASSERT_TRUE(resp.reply(0).is_array()) << rsdk::client::to_string(resp); 145 | for (int x = 0; x < batch_cnt; ++x) { 146 | const brpc::RedisReply& sub = resp.reply(0)[x]; 147 | ASSERT_TRUE(sub.is_string() || sub.is_error()) << sub.type(); 148 | if (sub.is_string()) { 149 | ASSERT_STREQ("OK", sub.c_str()); 150 | } 151 | } 152 | } 153 | ikey = 0; 154 | for (int i = 0; i < 1; ++i) { 155 | brpc::RedisResponse resp; 156 | std::string cmd = "mget"; 157 | std::vector expects; 158 | for (int x = 0; x < batch_cnt; ++x) { 159 | std::string tmp_cmd = fmt::format(" key_{}", ikey); 160 | cmd += tmp_cmd; 161 | std::string value = fmt::format("value_{}", ikey); 162 | ++ikey; 163 | expects.push_back(value); 164 | } 165 | client->exec(cmd, &resp); 166 | ASSERT_EQ(resp.reply_size(), 1) << rsdk::client::to_string(resp); 167 | ASSERT_EQ(resp.reply(0).size(), batch_cnt) << rsdk::client::to_string(resp); 168 | ASSERT_TRUE(resp.reply(0).is_array()) << rsdk::client::to_string(resp); 169 | for (int x = 0; x < batch_cnt; ++x) { 170 | const brpc::RedisReply& sub = resp.reply(0)[x]; 171 | ASSERT_TRUE(sub.is_string() || sub.is_error()) << rsdk::client::to_string(resp); 172 | if (sub.is_string()) { 173 | std::string value = sub.c_str(); 174 | std::string expect = expects[x]; 175 | ASSERT_EQ(expect, value); 176 | } 177 | } 178 | } 179 | ikey = 0; 180 | for (int i = 0; i < 1; ++i) { 181 | brpc::RedisResponse resp; 182 | std::string cmd = "mget"; 183 | std::vector expects; 184 | for (int x = 0; x < batch_cnt; ++x) { 185 | std::string tmp_cmd = fmt::format(" not_key_{}", ikey); 186 | cmd += tmp_cmd; 187 | ++ikey; 188 | } 189 | client->exec(cmd, &resp); 190 | ASSERT_EQ(resp.reply_size(), 1) << rsdk::client::to_string(resp); 191 | ASSERT_EQ(resp.reply(0).size(), batch_cnt) << rsdk::client::to_string(resp); 192 | ASSERT_TRUE(resp.reply(0).is_array()) << rsdk::client::to_string(resp); 193 | for (int x = 0; x < batch_cnt; ++x) { 194 | const brpc::RedisReply& sub = resp.reply(0)[x]; 195 | ASSERT_TRUE(sub.is_nil() || sub.is_error()) << rsdk::client::to_string(resp); 196 | } 197 | } 198 | } 199 | 200 | TEST_F(ReadEnvTest, ReadServerWithMove) { 201 | std::vector seeds; 202 | seeds.push_back(FLAGS_seed_node); 203 | 204 | std::string cname = "local_test"; 205 | ClientManagerPtr manager = new_client_mgr(); 206 | ASSERT_TRUE(manager != nullptr); 207 | ClusterOptions coptions; 208 | coptions._name = cname; 209 | coptions._seeds = seeds; 210 | coptions._server_options._conn_per_node = 1; 211 | coptions._server_options._conn_hc_interval_ms = 10; 212 | Status status = manager->add_cluster(coptions); 213 | ASSERT_TRUE(status.ok()) << status; 214 | AccessOptions opt; 215 | opt.timeout_ms = 10; 216 | opt._cname = cname; 217 | ClientPtr client = manager->new_client(opt); 218 | ASSERT_TRUE(client != nullptr); 219 | 220 | for (int i = 0; i < 100; ++i) { 221 | std::string key = fmt::format("key_{}", i); 222 | uint32_t slot_id = key_to_slot_id(key.c_str(), key.length()); 223 | update_slot_pointer(cname, manager, slot_id); 224 | } 225 | 226 | for (int i = 0; i < 10000; ++i) { 227 | brpc::RedisResponse resp2; 228 | std::string key = fmt::format("key_{}", i); 229 | client->exec(fmt::format("set {} {}", key, key), &resp2); 230 | ASSERT_EQ(resp2.reply_size(), 1); 231 | ASSERT_TRUE(resp2.reply(0).is_string() || resp2.reply(0).is_error()) 232 | << rsdk::client::to_string(resp2); 233 | if (resp2.reply(0).is_string()) { 234 | ASSERT_STREQ("OK", resp2.reply(0).c_str()); 235 | } 236 | } 237 | for (int i = 0; i < 100; ++i) { 238 | std::string key = fmt::format("key_{}", i); 239 | uint32_t slot_id = key_to_slot_id(key.c_str(), key.length()); 240 | update_slot_pointer(cname, manager, slot_id); 241 | } 242 | RecordList records; 243 | for (int i = 0; i < 1000; ++i) { 244 | Record rec; 245 | rec._errno = -100; 246 | rec._key = fmt::format("key_{}", i); 247 | records.push_back(std::move(rec)); 248 | } 249 | client->mget(&records); 250 | for (auto& rec : records) { 251 | ASSERT_EQ(0, rec._errno) << rec._err_msg; 252 | ASSERT_STREQ(rec._key.c_str(), rec._data.c_str()); 253 | } 254 | } 255 | 256 | void work_thread(int id, ClientPtr client, int64_t start_key, int cnt) { 257 | TimeCost cost; 258 | int64_t ikey = start_key; 259 | for (int i = 0; i < cnt; ++i) { 260 | brpc::RedisResponse resp2; 261 | std::string key = fmt::format("key_{}", ikey); 262 | client->exec(fmt::format("set {} {}", key, key), &resp2); 263 | ASSERT_EQ(resp2.reply_size(), 1); 264 | ASSERT_TRUE(resp2.reply(0).is_string() || resp2.reply(0).is_error()) 265 | << rsdk::client::to_string(resp2); 266 | if (resp2.reply(0).is_string()) { 267 | EXPECT_STREQ("OK", resp2.reply(0).c_str()); 268 | } 269 | ++ikey; 270 | } 271 | 272 | LOG(INFO) << "thread:" << id << " use " << cost.cost_us() << "us to put " << cnt << " keys"; 273 | } 274 | 275 | TEST_F(ReadEnvTest, MultiThread) { 276 | std::vector seeds; 277 | seeds.push_back(FLAGS_seed_node); 278 | 279 | std::string cname = "multi_thread"; 280 | ClientManagerPtr manager = new_client_mgr(); 281 | ASSERT_TRUE(manager != nullptr); 282 | ClusterOptions coptions; 283 | coptions._name = cname; 284 | coptions._seeds = seeds; 285 | coptions._server_options._conn_per_node = FLAGS_conn_per_node; 286 | coptions._server_options._conn_hc_interval_ms = 10; 287 | Status status = manager->add_cluster(coptions); 288 | ASSERT_TRUE(status.ok()) << status; 289 | AccessOptions opt; 290 | opt.timeout_ms = 100; 291 | opt._cname = cname; 292 | ClientPtr client = manager->new_client(opt); 293 | ASSERT_TRUE(client != nullptr); 294 | int thread_cnt = 10; 295 | std::vector threads; 296 | 297 | for (int i = 0; i < thread_cnt; ++i) { 298 | std::thread th(work_thread, i, client, 0, FLAGS_key_cnt); 299 | threads.push_back(std::move(th)); 300 | } 301 | 302 | for (auto& th : threads) { 303 | th.join(); 304 | } 305 | } 306 | 307 | // TEST_F(ReadEnvTest, RoundRobinRead) { 308 | // std::vector seeds; 309 | // seeds.push_back("127.0.0.1:21010"); 310 | // seeds.push_back("127.0.0.1:21011"); 311 | // seeds.push_back("127.0.0.1:21020"); 312 | // seeds.push_back("127.0.0.1:21021"); 313 | // seeds.push_back("127.0.0.1:21030"); 314 | // seeds.push_back("127.0.0.1:21031"); 315 | 316 | // std::string cname = "local_test"; 317 | // ClientManagerPtr manager = new_client_mgr(); 318 | // ASSERT_TRUE(manager != nullptr); 319 | // ClusterOptions coptions; 320 | // coptions._name = cname; 321 | // coptions._seeds = seeds; 322 | // coptions._server_options._conn_per_node = 1; 323 | // coptions._server_options._conn_hc_interval_ms = 10; 324 | // Status status = manager->add_cluster(coptions); 325 | // ASSERT_TRUE(status.ok()) << status; 326 | // AccessOptions opt; 327 | // opt.timeout_ms = 10; 328 | // opt._cname = cname; 329 | // opt.read_policy = ReadPolicy::ROUNDROBIN; 330 | // ClientPtr client = manager->new_client(opt); 331 | // ASSERT_TRUE(client != nullptr); 332 | 333 | // for (int round = 0; round < 10; ++round) { 334 | // for (int i = 0; i < 20000; ++i) { 335 | // brpc::RedisResponse resp2; 336 | // std::string key = fmt::format("key_{}", i); 337 | // if (round == 0) { 338 | // client->exec(fmt::format("set {} {}", key, key), &resp2); 339 | // ASSERT_EQ(resp2.reply_size(), 1); 340 | // ASSERT_TRUE(resp2.reply(0).is_string()) << rsdk::client::to_string(resp2); 341 | // ASSERT_STREQ("OK", resp2.reply(0).c_str()); 342 | // } 343 | 344 | // brpc::RedisResponse resp3; 345 | // client->exec(fmt::format("get {}", key), &resp3); 346 | // ASSERT_TRUE(resp3.reply(0).is_string()) << rsdk::client::to_string(resp3); 347 | // ASSERT_STREQ(key.c_str(), resp3.reply(0).c_str()); 348 | // } 349 | // } 350 | // // while (true) { 351 | // // usleep(100000); 352 | // // } 353 | // } 354 | 355 | } // namespace test 356 | } // namespace client 357 | } // namespace rsdk 358 | 359 | DECLARE_string(flagfile); 360 | DECLARE_int32(logbufsecs); 361 | DECLARE_int32(v); 362 | DECLARE_string(log_dir); // defined in glog 363 | 364 | int main(int argc, char* argv[]) { 365 | FLAGS_v = 3; 366 | FLAGS_logbufsecs = 0; 367 | FLAGS_log_dir = "log"; 368 | rsdk::client::FLAGS_log_request_verbose = true; 369 | ::testing::InitGoogleTest(&argc, argv); 370 | google::ParseCommandLineFlags(&argc, &argv, true); 371 | 372 | ::mkdir(FLAGS_log_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); 373 | google::InitGoogleLogging(argv[0]); 374 | google::SetStderrLogging(google::FATAL); 375 | 376 | int ret = RUN_ALL_TESTS(); 377 | return ret; 378 | } 379 | -------------------------------------------------------------------------------- /test/redirect_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "redis_sdk/include/client_impl.h" 15 | #include "test/mock_node.h" 16 | #include "test/test_common.h" 17 | 18 | namespace rsdk { 19 | namespace client { 20 | 21 | std::string to_string(const brpc::RedisResponse& resp); 22 | DECLARE_bool(log_request_verbose); 23 | void log_reply(const brpc::RedisReply& reply); 24 | void log_response(const brpc::RedisResponse& resp); 25 | Status check_brpc_redis_reply_bug(); 26 | uint32_t key_to_slot_id(const char* key, int keylen); 27 | 28 | namespace test { 29 | 30 | class RedirectTest : public ::testing::Test { 31 | public: 32 | RedirectTest() { 33 | } 34 | static void SetUpTestCase() { 35 | int ret = system("rm -rf ./log/"); 36 | ret = system("mkdir -p ./log/"); 37 | (void)ret; 38 | } 39 | void SetUp() { 40 | FLAGS_log_request_verbose = true; 41 | } 42 | void TearDown() { 43 | } 44 | std::string build_cluster_name() { 45 | ++_cluster_id; 46 | return "cluster" + std::to_string(_cluster_id); 47 | } 48 | ClientManagerPtr new_client_mgr_impl() { 49 | auto cli = std::make_shared(); 50 | return cli; 51 | } 52 | void build_mset(RecordList* recs, int cnt, int moved_percent); 53 | void check_mset_resp(int cnt, const RecordList& records); 54 | int _cluster_id = 0; 55 | }; 56 | 57 | void RedirectTest::build_mset(RecordList* recs, int cnt, int moved_percent) { 58 | for (int i = 0; i < cnt; ++i) { 59 | std::string key = fmt::format("{}", i); 60 | int ratio = butil::fast_rand() % 100; 61 | if (ratio < moved_percent) { 62 | key = "moved_key_" + key; 63 | } 64 | Record rec; 65 | rec._key = key; 66 | rec._data = key; 67 | rec._errno = -100; 68 | recs->push_back(rec); 69 | } 70 | } 71 | 72 | void RedirectTest::check_mset_resp(int cnt, const RecordList& records) { 73 | for (int i = 0; i < cnt; ++i) { 74 | const Record& rec = records[i]; 75 | ASSERT_EQ(rec._errno, 0) << rec._err_msg; 76 | } 77 | } 78 | 79 | TEST_F(RedirectTest, HandleMovedInMgetMset) { 80 | int server_cnt = 3; 81 | int slave_per_server = 0; 82 | std::string cname = build_cluster_name(); 83 | std::vector seeds; 84 | std::vector servers; 85 | setup_cluster(cname, server_cnt, slave_per_server, &servers, &seeds); 86 | 87 | ClientManagerPtr manager = new_client_mgr_impl(); 88 | ASSERT_TRUE(manager != nullptr); 89 | Status status = add_cluster(manager, cname, seeds); 90 | ASSERT_TRUE(status.ok()) << status; 91 | AccessOptions opt; 92 | opt._cname = cname; 93 | ClientPtr client = manager->new_client(opt); 94 | ASSERT_TRUE(client != nullptr); 95 | brpc::RedisResponse resp; 96 | std::string key = "keya"; 97 | uint32_t slot_id = key_to_slot_id(key.c_str(), key.length()); 98 | update_slot_pointer(cname, manager, slot_id); 99 | 100 | RecordList recs; 101 | int cnt = 500; 102 | build_mset(&recs, cnt, 0); 103 | Record moved; 104 | moved._key = key; 105 | moved._data = key; 106 | recs.push_back(moved); 107 | client->mset(&recs); 108 | ASSERT_NO_FATAL_FAILURE(check_mset_resp(cnt, recs)); 109 | ASSERT_EQ(0, recs[cnt]._errno) << recs[cnt]._err_msg; 110 | 111 | update_slot_pointer(cname, manager, slot_id); 112 | std::vector resps; 113 | client->mget(recs, &resps); 114 | ASSERT_EQ(recs.size(), resps.size()); 115 | for (size_t i = 0; i < resps.size(); ++i) { 116 | brpc::RedisResponse resp = resps[i]; 117 | ASSERT_TRUE(resp.reply(0).is_string()); 118 | std::string data = resp.reply(0).c_str(); 119 | ASSERT_STREQ(data.c_str(), recs[i]._key.c_str()); 120 | } 121 | } 122 | 123 | TEST_F(RedirectTest, PartialMovedToFailed) { 124 | int server_cnt = 3; 125 | int slave_per_server = 0; 126 | std::string cname = build_cluster_name(); 127 | std::vector seeds; 128 | std::vector servers; 129 | setup_cluster(cname, server_cnt, slave_per_server, &servers, &seeds); 130 | 131 | ClientManagerPtr manager = new_client_mgr_impl(); 132 | ASSERT_TRUE(manager != nullptr); 133 | Status status = add_cluster(manager, cname, seeds); 134 | ASSERT_TRUE(status.ok()) << status; 135 | AccessOptions opt; 136 | opt._cname = cname; 137 | opt.max_redirect = 3; 138 | ClientPtr client = manager->new_client(opt); 139 | ASSERT_TRUE(client != nullptr); 140 | { servers[0]->_service->_always_moved = true; } 141 | RecordList recs; 142 | int cnt = 500; 143 | build_mset(&recs, cnt, 50); 144 | 145 | std::vector resps; 146 | client->mset(recs, &resps); 147 | ASSERT_EQ(resps.size(), recs.size()); 148 | 149 | int failed_cnt = 0; 150 | int succ_cnt = 0; 151 | for (auto& resp : resps) { 152 | ASSERT_EQ(resp.reply_size(), 1); 153 | if (resp.reply(0).is_error()) { 154 | std::string err_msg = resp.reply(0).error_message(); 155 | VLOG(3) << "got error msg:" << err_msg; 156 | ASSERT_TRUE(err_msg.find("MOVED ") != std::string::npos) << err_msg; 157 | ASSERT_TRUE(err_msg.find("exceed max:") != std::string::npos) << err_msg; 158 | ++failed_cnt; 159 | } else { 160 | ++succ_cnt; 161 | } 162 | } 163 | ASSERT_TRUE(failed_cnt > 0); 164 | ASSERT_TRUE(succ_cnt > 0); 165 | ASSERT_EQ(cnt, failed_cnt + succ_cnt) << "failed:" << failed_cnt << "succ:" << succ_cnt; 166 | } 167 | 168 | TEST_F(RedirectTest, PartialMovedRetryToSucc) { 169 | int server_cnt = 3; 170 | int slave_per_server = 0; 171 | std::string cname = build_cluster_name(); 172 | std::vector seeds; 173 | std::vector servers; 174 | setup_cluster(cname, server_cnt, slave_per_server, &servers, &seeds); 175 | 176 | ClientManagerPtr manager = new_client_mgr_impl(); 177 | ASSERT_TRUE(manager != nullptr); 178 | Status status = add_cluster(manager, cname, seeds); 179 | ASSERT_TRUE(status.ok()) << status; 180 | AccessOptions opt; 181 | opt._cname = cname; 182 | opt.max_redirect = 3; 183 | ClientPtr client = manager->new_client(opt); 184 | ASSERT_TRUE(client != nullptr); 185 | { 186 | servers[0]->_service->_mock_status_cnt = opt.max_redirect; 187 | servers[0]->_service->_crud_status = "MOVED"; 188 | } 189 | RecordList recs; 190 | int cnt = 500; 191 | build_mset(&recs, cnt, 50); 192 | 193 | client->mset(&recs); 194 | for (auto& rec : recs) { 195 | ASSERT_EQ(0, rec._errno) << rec._err_msg; 196 | } 197 | clear_value(&recs); 198 | client->mget(&recs); 199 | 200 | { 201 | servers[0]->_service->_mock_status_cnt = opt.max_redirect; 202 | servers[0]->_service->_crud_status = "MOVED"; 203 | } 204 | for (auto& rec : recs) { 205 | ASSERT_EQ(0, rec._errno) << rec._err_msg; 206 | ASSERT_STREQ(rec._key.c_str(), rec._data.c_str()); 207 | } 208 | } 209 | 210 | TEST_F(RedirectTest, MeetAskOnPipeline) { 211 | int server_cnt = 3; 212 | int slave_per_server = 0; 213 | std::string cname = build_cluster_name(); 214 | std::vector seeds; 215 | std::vector servers; 216 | setup_cluster(cname, server_cnt, slave_per_server, &servers, &seeds); 217 | 218 | ClientManagerPtr manager = new_client_mgr_impl(); 219 | ASSERT_TRUE(manager != nullptr); 220 | Status status = add_cluster(manager, cname, seeds); 221 | ASSERT_TRUE(status.ok()) << status; 222 | AccessOptions opt; 223 | opt._cname = cname; 224 | opt.max_redirect = 3; 225 | ClientPtr client = manager->new_client(opt); 226 | ASSERT_TRUE(client != nullptr); 227 | { 228 | servers[0]->_service->_mock_status_cnt = 10; 229 | servers[0]->_service->_crud_status = "ASK"; 230 | } 231 | RecordList recs; 232 | int cnt = 500; 233 | build_mset(&recs, cnt, 0); 234 | 235 | client->mset(&recs); 236 | 237 | for (auto& rec : recs) { 238 | ASSERT_EQ(0, rec._errno) << rec._err_msg; 239 | } 240 | clear_value(&recs); 241 | client->mget(&recs); 242 | 243 | { 244 | servers[0]->_service->_mock_status_cnt = 20; 245 | servers[0]->_service->_crud_status = "ASK"; 246 | } 247 | for (auto& rec : recs) { 248 | ASSERT_EQ(0, rec._errno) << rec._err_msg; 249 | ASSERT_STREQ(rec._key.c_str(), rec._data.c_str()); 250 | } 251 | } 252 | 253 | } // namespace test 254 | } // namespace client 255 | } // namespace rsdk 256 | 257 | DECLARE_string(flagfile); 258 | DECLARE_int32(logbufsecs); 259 | DECLARE_int32(v); 260 | DECLARE_string(log_dir); // defined in glog 261 | 262 | int main(int argc, char* argv[]) { 263 | FLAGS_v = 3; 264 | FLAGS_logbufsecs = 0; 265 | FLAGS_log_dir = "log"; 266 | ::testing::InitGoogleTest(&argc, argv); 267 | google::ParseCommandLineFlags(&argc, &argv, true); 268 | 269 | ::mkdir(FLAGS_log_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); 270 | google::InitGoogleLogging(argv[0]); 271 | google::SetStderrLogging(google::FATAL); 272 | 273 | int ret = RUN_ALL_TESTS(); 274 | return ret; 275 | } 276 | -------------------------------------------------------------------------------- /test/slot_test.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "redis_sdk/include/slot.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "redis_sdk/client.h" 8 | 9 | namespace rsdk { 10 | namespace client { 11 | uint32_t key_to_slot_id(const char* key, int keylen); 12 | namespace test { 13 | 14 | class SlotTest : public ::testing::Test { 15 | public: 16 | SlotTest() { 17 | } 18 | static void SetUpTestCase() { 19 | int ret = system("rm -rf ./log/"); 20 | ret = system("mkdir -p ./log/"); 21 | (void)ret; 22 | } 23 | void SetUp() { 24 | } 25 | void TearDown() { 26 | } 27 | }; 28 | 29 | TEST_F(SlotTest, UpdateServersTest) { 30 | std::string cname = "test_cluster"; 31 | Slot slot = {cname, 3, 0}; 32 | 33 | // update servers early return 34 | std::vector servers; 35 | RedisServerPtr leader = std::make_shared("mock_server_0"); 36 | servers.push_back(std::make_shared("mock_server_1")); 37 | slot.update_servers(nullptr, servers, 1); 38 | ASSERT_EQ(slot._config_epoch, 0); 39 | slot.update_servers(leader, servers, -1); 40 | ASSERT_EQ(slot._config_epoch, 0); 41 | 42 | // update servers success 43 | slot.update_servers(leader, servers, 5); 44 | ASSERT_EQ(slot._config_epoch, 5); 45 | } 46 | 47 | TEST_F(SlotTest, UpdateSlotTest) { 48 | SlotMgr slot_mgr; 49 | std::string slot_range = "0-16383"; 50 | std::vector servers; 51 | RedisServerPtr leader = std::make_shared("mock_server_0"); 52 | servers.push_back(std::make_shared("mock_server_1")); 53 | 54 | // update slot no leader 55 | Status status = slot_mgr.update_slot(slot_range, nullptr, servers, 0); 56 | ASSERT_EQ(status.code(), Status::Code::SYS_ERROR); 57 | ASSERT_TRUE(status.msg().find("bug, invalid slot") != std::string::npos); 58 | 59 | // invalid slot range format 60 | std::string invalid_slot_0 = "1-0-16383"; 61 | status = slot_mgr.update_slot(invalid_slot_0, leader, servers, 0); 62 | ASSERT_EQ(status.code(), Status::Code::SYS_ERROR); 63 | ASSERT_TRUE(status.msg().find("invalid slot range") != std::string::npos); 64 | 65 | // invalid slot range 66 | std::string invaid_slot_1 = "99999-12345678901234567890"; 67 | status = slot_mgr.update_slot(invaid_slot_1, leader, servers, 0); 68 | ASSERT_EQ(status.code(), Status::Code::SYS_ERROR); 69 | ASSERT_TRUE(status.msg().find("invalid slot range") != std::string::npos); 70 | 71 | // invalid single slot 72 | std::string invalid_single_slot = "123abc"; 73 | status = slot_mgr.update_slot(invalid_single_slot, leader, servers, 0); 74 | ASSERT_EQ(status.code(), Status::Code::SYS_ERROR); 75 | ASSERT_TRUE(status.msg().find("invalid slot") != std::string::npos); 76 | 77 | // valid slot 78 | std::string valid_slot = "11"; 79 | status = slot_mgr.update_slot(valid_slot, leader, servers, 0); 80 | ASSERT_EQ(status.code(), Status::Code::OK); 81 | 82 | auto slot = std::make_shared("", 0, 0); 83 | ASSERT_TRUE(slot_mgr.add_slot(slot)); 84 | ASSERT_FALSE(slot_mgr.add_slot(slot)); 85 | } 86 | 87 | TEST_F(SlotTest, GetServerTest) { 88 | std::string cname = "test_cluster"; 89 | Slot slot = {cname, 3, 0}; 90 | std::vector servers; 91 | RedisServerPtr leader = std::make_shared("mock_server_0"); 92 | servers.push_back(std::make_shared("mock_server_1")); 93 | slot.update_servers(leader, servers, 5); 94 | 95 | ASSERT_EQ(slot.get_leader(), leader); 96 | ASSERT_EQ(slot.get_server_for_read(ReadPolicy::LEADER_ONLY), leader); 97 | 98 | slot._servers.clear(); 99 | ASSERT_EQ(slot.get_server_for_read(ReadPolicy::LEADER_ONLY), leader); 100 | } 101 | 102 | TEST_F(SlotTest, SlotUtilTest) { 103 | SlotMgr slot_mgr; 104 | std::string slot_range = "0-16383"; 105 | std::vector servers; 106 | RedisServerPtr leader = std::make_shared("mock_server_0"); 107 | servers.push_back(std::make_shared("mock_server_1")); 108 | Status status = slot_mgr.update_slot(slot_range, leader, servers, 5); 109 | ASSERT_EQ(status.code(), Status::Code::OK); 110 | ASSERT_EQ(slot_mgr._slots.size(), 16384); 111 | uint32_t slot_id = key_to_slot_id("test_key", 8); 112 | ASSERT_EQ(slot_mgr._slots[slot_id], slot_mgr.get_slot_by_key("test_key")); 113 | 114 | Slot slot = {"cname", 3, 0}; 115 | slot.update_servers(leader, servers, 5); 116 | std::string describe = slot.describe(); 117 | ASSERT_EQ(describe, "id:3 sn:0 version:0 config_epoch:5 addr:mock_server_0"); 118 | } 119 | 120 | } // namespace test 121 | } // namespace client 122 | } // namespace rsdk 123 | 124 | DECLARE_string(flagfile); 125 | DECLARE_int32(logbufsecs); 126 | DECLARE_int32(v); 127 | DECLARE_string(log_dir); // defined in glog 128 | 129 | int main(int argc, char* argv[]) { 130 | FLAGS_v = 3; 131 | FLAGS_logbufsecs = 0; 132 | FLAGS_log_dir = "log"; 133 | ::testing::InitGoogleTest(&argc, argv); 134 | google::ParseCommandLineFlags(&argc, &argv, true); 135 | 136 | ::mkdir(FLAGS_log_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); 137 | google::InitGoogleLogging(argv[0]); 138 | google::SetStderrLogging(google::FATAL); 139 | 140 | int ret = RUN_ALL_TESTS(); 141 | return ret; 142 | } -------------------------------------------------------------------------------- /test/sstream_define_as_public.h: -------------------------------------------------------------------------------- 1 | #ifndef RSDK_TEST_SSTREAM_DEFINE_AS_PUBLIC_H 2 | #define RSDK_TEST_SSTREAM_DEFINE_AS_PUBLIC_H 3 | 4 | #ifdef private 5 | 6 | #undef private 7 | #include 8 | #define private public 9 | 10 | #else // private 11 | 12 | #include 13 | 14 | #endif // private 15 | 16 | #endif // RSDK_TEST_SSTREAM_DEFINE_AS_PUBLIC_H 17 | -------------------------------------------------------------------------------- /test/test_common.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "test/test_common.h" 6 | 7 | namespace rsdk { 8 | namespace client { 9 | namespace test { 10 | 11 | std::string build_slot_dist(const std::vector& slot_ids) { 12 | std::vector ids(slot_ids); 13 | std::sort(ids.begin(), ids.end()); 14 | std::string dist; 15 | for (size_t i = 0; i < ids.size();) { 16 | int start = ids[i]; 17 | int end = start; 18 | size_t end_pos = i; 19 | for (int j = i + 1; j < (int)ids.size(); ++j) { 20 | if (ids[j] == ids[j - 1] + 1) { 21 | end = ids[j]; 22 | end_pos = j; 23 | continue; 24 | } 25 | break; 26 | } 27 | if (end_pos != i) { 28 | dist += std::to_string(start) + "-" + std::to_string(end); 29 | i = end_pos + 1; 30 | } else { 31 | dist += std::to_string(start); 32 | ++i; 33 | } 34 | if (i != ids.size()) { 35 | dist += " "; 36 | } 37 | } 38 | return dist; 39 | } 40 | 41 | void build_slot_id(std::vector* used_state, int cnt, std::vector* slot_ids) { 42 | int start_pos = butil::fast_rand_less_than(used_state->size()); 43 | int filled_cnt = 0; 44 | while (filled_cnt < cnt) { 45 | int pos = start_pos % used_state->size(); 46 | if (!(*used_state)[pos]) { 47 | slot_ids->push_back(pos); 48 | (*used_state)[pos] = true; 49 | ++filled_cnt; 50 | } 51 | ++start_pos; 52 | } 53 | } 54 | 55 | int build_cluster(const std::vector& servers, int slave_cnt, std::string* desc, 56 | ClusterDist* dist, std::unordered_map* slot2addr) { 57 | int per_group = slave_cnt + 1; 58 | if (servers.size() % per_group != 0) { 59 | LOG(ERROR) << "invalid server count:" << servers.size() << ", slave_cnt:" << slave_cnt; 60 | return -1; 61 | } 62 | int index = 0; 63 | 64 | int group_cnt = servers.size() / per_group; 65 | std::map slot_per_group; 66 | int free_cnt = 16384; 67 | int total_slot_cnt = free_cnt; 68 | for (int i = 0; i < group_cnt; ++i) { 69 | int expect = total_slot_cnt / group_cnt; 70 | int slot_cnt = butil::fast_rand_less_than(expect * 1.5); 71 | if (slot_cnt > free_cnt) { 72 | slot_cnt = free_cnt; 73 | } 74 | slot_per_group[i] = slot_cnt; 75 | free_cnt -= slot_cnt; 76 | if ((i + 1) == group_cnt && free_cnt > 0) { 77 | slot_per_group[i] += free_cnt; 78 | } 79 | VLOG(3) << "set group:" << i << " slot cnt:" << slot_per_group[i]; 80 | } 81 | std::vector state; 82 | state.resize(total_slot_cnt, false); 83 | 84 | for (int i = 0; i < group_cnt; ++i) { 85 | LeaderAndSlaves group; 86 | MockRedisServerPtr leader = servers[index]; 87 | std::vector slot_ids; 88 | build_slot_id(&state, slot_per_group[i], &slot_ids); 89 | std::string slot_dist = build_slot_dist(slot_ids); 90 | for (int j = 0; j < slave_cnt; ++j) { 91 | MockRedisServerPtr slave = servers[index + j + 1]; 92 | slave->set_leader_name(leader->name()); 93 | group._slaves.push_back(slave); 94 | desc->append(slave->build_cluster_nodes() + "\n"); 95 | } 96 | VLOG(3) << "set group:" << i << " slot dist:" << slot_dist; 97 | leader->set_slot_dist(slot_dist); 98 | leader->set_slot_ids(slot_ids); 99 | leader->set_leader_name(leader->name()); 100 | group._leader = leader; 101 | dist->_leaders_and_slaves.push_back(std::move(group)); 102 | index += per_group; 103 | desc->append(leader->build_cluster_nodes()); 104 | if (i != group_cnt - 1) { 105 | desc->append("\n"); 106 | } 107 | for (int slot_id : slot_ids) { 108 | (*slot2addr)[slot_id] = leader->addr(); 109 | } 110 | } 111 | LOG(INFO) << "mock cluster nodes:" << *desc; 112 | return 0; 113 | } 114 | 115 | int setup_redis_server(int cnt, std::vector* servers) { 116 | for (int i = 0; i < cnt; ++i) { 117 | MockRedisServerPtr server = std::make_shared(i); 118 | int ret = server->start(); 119 | if (0 != ret) { 120 | LOG(ERROR) << "failed to start mock redis server"; 121 | return ret; 122 | } 123 | servers->push_back(server); 124 | } 125 | return 0; 126 | } 127 | 128 | void update_slot_pointer(const std::string& cname, ClientManagerPtr mgr, int slot_id) { 129 | ClientManagerImpl* cli_mgr = dynamic_cast(mgr.get()); 130 | ClusterPtr cluster = cli_mgr->get_cluster_mgr()->get(cname); 131 | if (!cluster) { 132 | LOG(ERROR) << "cluster:" << cname << " is not found"; 133 | return; 134 | } 135 | SlotPtr slot = cluster->get_slot_by_id(slot_id); 136 | if (!slot) { 137 | LOG(ERROR) << "slot:" << slot_id << " is not found in cluster:" << cname; 138 | return; 139 | } 140 | VLOG(3) << "start to update slot:" << slot_id << " server address:" << slot->_leader->addr(); 141 | std::lock_guard lock(cluster->_nodes_mutex); 142 | for (auto& iter : cluster->_ip2nodes) { 143 | RedisServerPtr server = iter.second; 144 | std::lock_guard lock(slot->_servers_mutex); 145 | if (server->is_leader() && server->addr() != slot->_leader->addr()) { 146 | VLOG(3) << "update slot:" << slot_id << " server addr from:" << slot->_leader->_addr 147 | << " to:" << server->addr(); 148 | slot->_leader = server; 149 | break; 150 | } 151 | } 152 | } 153 | 154 | void setup_cluster(const std::string& cname, int server_cnt, int slave_per_server, 155 | std::vector* servers, std::vector* seeds) { 156 | int ret = setup_redis_server(server_cnt, servers); 157 | ASSERT_EQ(0, ret); 158 | std::string cluster_nodes; 159 | ClusterDist dist; 160 | std::unordered_map slot2addr; 161 | ret = build_cluster(*servers, slave_per_server, &cluster_nodes, &dist, &slot2addr); 162 | ASSERT_EQ(0, ret); 163 | 164 | for (auto& server : *servers) { 165 | seeds->push_back(server->addr()); 166 | server->_service->_cluster_nodes = cluster_nodes; 167 | server->set_slot2addr(slot2addr); 168 | } 169 | } 170 | 171 | void rand_put_get(ClientPtr client, int cnt) { 172 | for (int i = 0; i < cnt; ++i) { 173 | brpc::RedisResponse resp; 174 | std::string key = std::to_string(butil::fast_rand_less_than(1000000)); 175 | std::string cmd = "set " + key + " " + key; 176 | client->exec(cmd, &resp); 177 | ASSERT_TRUE(resp.reply(0).is_string()); 178 | ASSERT_STREQ("OK", resp.reply(0).c_str()); 179 | brpc::RedisResponse resp2; 180 | cmd = "get " + key; 181 | client->exec(cmd, &resp2); 182 | ASSERT_TRUE(resp2.reply(0).is_string()); 183 | ASSERT_STREQ(key.c_str(), resp2.reply(0).c_str()); 184 | } 185 | } 186 | 187 | void build_mset(int cnt, bool str_mode, brpc::RedisRequest* req) { 188 | std::vector cmds; 189 | cmds.push_back("mSEt"); 190 | for (int i = 0; i < cnt; ++i) { 191 | std::string key = std::to_string(i); 192 | std::string value; 193 | if (str_mode) { 194 | value = std::to_string(i); 195 | } else { 196 | value.append((char*)&i, sizeof(i)); 197 | } 198 | cmds.push_back(key); 199 | cmds.push_back(value); 200 | } 201 | butil::StringPiece parts[cmds.size()]; 202 | for (size_t i = 0; i < cmds.size(); ++i) { 203 | parts[i].set(cmds[i].c_str(), cmds[i].size()); 204 | } 205 | req->AddCommandByComponents(parts, cmds.size()); 206 | } 207 | 208 | void build_mset(int cnt, RecordList* recs) { 209 | for (int i = 0; i < cnt; ++i) { 210 | std::string key = std::to_string(i); 211 | std::string value; 212 | value.append((char*)&i, sizeof(i)); 213 | Record rec; 214 | rec._key = key; 215 | rec._data = value; 216 | recs->push_back(rec); 217 | rec._errno = -1; 218 | } 219 | } 220 | 221 | void build_mget(int cnt, brpc::RedisRequest* req) { 222 | std::vector cmds; 223 | cmds.push_back("MGET"); 224 | for (int i = 0; i < cnt; ++i) { 225 | cmds.push_back(std::to_string(i)); 226 | } 227 | butil::StringPiece parts[cmds.size()]; 228 | for (size_t i = 0; i < cmds.size(); ++i) { 229 | parts[i].set(cmds[i].c_str(), cmds[i].size()); 230 | } 231 | req->AddCommandByComponents(parts, cnt + 1); 232 | } 233 | 234 | void build_mget(int cnt, RecordList* recs) { 235 | for (int i = 0; i < cnt; ++i) { 236 | std::string key = std::to_string(i); 237 | Record rec; 238 | rec._key = key; 239 | recs->push_back(rec); 240 | } 241 | } 242 | void clear_value(RecordList* recs) { 243 | for (auto& rec : *recs) { 244 | rec._data.clear(); 245 | rec._errno = -1; 246 | } 247 | } 248 | 249 | void set_crud_status(std::vector& servers, int cnt, const std::string& status) { 250 | for (auto& server : servers) { 251 | server->_service->_mock_status_cnt = cnt; 252 | server->_service->_crud_status = status; 253 | } 254 | } 255 | void set_sleep_time(std::vector& servers, int sleep_cnt, int sleep_ms) { 256 | for (auto& server : servers) { 257 | server->_service->_sleep_cnt = sleep_cnt; 258 | server->_service->_sleep_ms = sleep_ms; 259 | } 260 | } 261 | 262 | Status add_cluster(ClientManagerPtr mgr, const std::string& name, 263 | const std::vector& seeds) { 264 | ClusterOptions coptions; 265 | coptions._name = name; 266 | coptions._seeds = seeds; 267 | coptions._server_options._conn_per_node = 4; 268 | coptions._server_options._conn_hc_interval_ms = 10; 269 | Status status = mgr->add_cluster(coptions); 270 | return status; 271 | } 272 | 273 | } // namespace test 274 | } // namespace client 275 | } // namespace rsdk 276 | -------------------------------------------------------------------------------- /test/test_common.h: -------------------------------------------------------------------------------- 1 | #ifndef RSDK_TEST_TEST_COMMON_H 2 | #define RSDK_TEST_TEST_COMMON_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "redis_sdk/include/client_impl.h" 10 | #include "redis_sdk/include/redis_sdk/client.h" 11 | #include "test/mock_node.h" 12 | 13 | namespace rsdk { 14 | namespace client { 15 | namespace test { 16 | 17 | struct MockEnv { 18 | std::string _cname; 19 | std::vector _seeds; 20 | std::vector _servers; 21 | AccessOptions _opt; 22 | ClientManagerPtr _mgr; 23 | }; 24 | 25 | int build_cluster(const std::vector& servers, int slave_cnt, std::string* desc, 26 | ClusterDist* dist, std::unordered_map* slot2addr); 27 | int setup_redis_server(int cnt, std::vector* servers); 28 | void update_slot_pointer(const std::string& cname, ClientManagerPtr mgr, int slot_id); 29 | void setup_cluster(const std::string& cname, int server_cnt, int slave_per_server, 30 | std::vector* servers, std::vector* seeds); 31 | void rand_put_get(ClientPtr client, int cnt); 32 | void build_mset(int cnt, bool str_mode, brpc::RedisRequest* req); 33 | void build_mset(int cnt, RecordList* recs); 34 | void build_mget(int cnt, brpc::RedisRequest* req); 35 | void build_mget(int cnt, RecordList* recs); 36 | void clear_value(RecordList* recs); 37 | void set_crud_status(std::vector& servers, int cnt, const std::string& status); 38 | void set_sleep_time(std::vector& servers, int sleep_cnt, int sleep_ms); 39 | Status add_cluster(ClientManagerPtr mgr, const std::string& name, 40 | const std::vector& seeds); 41 | 42 | } // namespace test 43 | } // namespace client 44 | } // namespace rsdk 45 | 46 | #endif // RSDK_TEST_TEST_COMMON_H 47 | -------------------------------------------------------------------------------- /third_party/BUILD.bazel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bilibili/redis_sdk_cxx/cdecc8692c8ff3a01d0aaf40fde04012e755114e/third_party/BUILD.bazel -------------------------------------------------------------------------------- /third_party/cpptoml.BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | cc_library( 4 | name = "cpptoml", 5 | hdrs = ["include/cpptoml.h"], 6 | includes = ["include"], 7 | ) 8 | -------------------------------------------------------------------------------- /third_party/fmt.BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | cc_library( 4 | name = "fmt", 5 | srcs = glob(["src/*.cc"]), 6 | hdrs = glob(["include/fmt/*.h"]), 7 | includes = ["include"], 8 | ) 9 | -------------------------------------------------------------------------------- /third_party/leveldb.BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | config_setting( 4 | name = "darwin", 5 | values = {"cpu": "darwin"}, 6 | visibility = ["//visibility:public"], 7 | ) 8 | 9 | SOURCES = ["db/builder.cc", 10 | "db/c.cc", 11 | "db/dbformat.cc", 12 | "db/db_impl.cc", 13 | "db/db_iter.cc", 14 | "db/dumpfile.cc", 15 | "db/filename.cc", 16 | "db/log_reader.cc", 17 | "db/log_writer.cc", 18 | "db/memtable.cc", 19 | "db/repair.cc", 20 | "db/table_cache.cc", 21 | "db/version_edit.cc", 22 | "db/version_set.cc", 23 | "db/write_batch.cc", 24 | "table/block_builder.cc", 25 | "table/block.cc", 26 | "table/filter_block.cc", 27 | "table/format.cc", 28 | "table/iterator.cc", 29 | "table/merger.cc", 30 | "table/table_builder.cc", 31 | "table/table.cc", 32 | "table/two_level_iterator.cc", 33 | "util/arena.cc", 34 | "util/bloom.cc", 35 | "util/cache.cc", 36 | "util/coding.cc", 37 | "util/comparator.cc", 38 | "util/crc32c.cc", 39 | "util/env.cc", 40 | "util/env_posix.cc", 41 | "util/filter_policy.cc", 42 | "util/hash.cc", 43 | "util/histogram.cc", 44 | "util/logging.cc", 45 | "util/options.cc", 46 | "util/status.cc", 47 | "port/port_posix.cc", 48 | "port/port_posix_sse.cc", 49 | "helpers/memenv/memenv.cc", 50 | ] 51 | 52 | cc_library( 53 | name = "leveldb", 54 | srcs = SOURCES, 55 | hdrs = glob([ 56 | "helpers/memenv/*.h", 57 | "util/*.h", 58 | "port/*.h", 59 | "port/win/*.h", 60 | "table/*.h", 61 | "db/*.h", 62 | "include/leveldb/*.h" 63 | ], 64 | exclude = [ 65 | "**/*test.*", 66 | ]), 67 | includes = [ 68 | "include/", 69 | ], 70 | copts = [ 71 | "-fno-builtin-memcmp", 72 | "-DLEVELDB_PLATFORM_POSIX=1", 73 | "-DLEVELDB_ATOMIC_PRESENT", 74 | ], 75 | defines = [ 76 | "LEVELDB_PLATFORM_POSIX", 77 | ] + select({ 78 | ":darwin": ["OS_MACOSX"], 79 | "//conditions:default": [], 80 | }), 81 | ) 82 | -------------------------------------------------------------------------------- /third_party/openssl.BUILD: -------------------------------------------------------------------------------- 1 | package( 2 | default_visibility=["//visibility:public"] 3 | ) 4 | 5 | config_setting( 6 | name = "macos", 7 | values = { 8 | "cpu": "darwin", 9 | }, 10 | visibility = ["//visibility:private"], 11 | ) 12 | 13 | cc_library( 14 | name = "crypto", 15 | srcs = select({ 16 | ":macos": ["lib/libcrypto.dylib"], 17 | "//conditions:default": [] 18 | }), 19 | linkopts = select({ 20 | ":macos" : [], 21 | "//conditions:default": ["-lcrypto"], 22 | }), 23 | ) 24 | 25 | cc_library( 26 | name = "ssl", 27 | hdrs = select({ 28 | ":macos": glob(["include/openssl/*.h"]), 29 | "//conditions:default": [] 30 | }), 31 | srcs = select ({ 32 | ":macos": ["lib/libssl.dylib"], 33 | "//conditions:default": [] 34 | }), 35 | includes = ["include"], 36 | linkopts = select({ 37 | ":macos" : [], 38 | "//conditions:default": ["-lssl"], 39 | }), 40 | deps = [":crypto"] 41 | ) -------------------------------------------------------------------------------- /third_party/repositories.bzl: -------------------------------------------------------------------------------- 1 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 2 | load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") 3 | 4 | all_content = """filegroup(name = "all", srcs = glob(["**"]), visibility = ["//visibility:public"])""" 5 | 6 | def node_repositories(): 7 | maybe( 8 | http_archive, 9 | name = "cpptoml", 10 | strip_prefix = "cpptoml-0.1.1", 11 | sha256 = "23af72468cfd4040984d46a0dd2a609538579c78ddc429d6b8fd7a10a6e24403", 12 | urls = ["https://boss.hdslb.com/common-arch/dist/cpptoml-v0.1.1.tar.gz"], 13 | build_file = "//:third_party/cpptoml.BUILD", 14 | ) 15 | maybe( 16 | http_archive, 17 | name = "com_github_fmtlib_fmt", 18 | build_file = "//:third_party/fmt.BUILD", 19 | sha256 = "5d98c504d0205f912e22449ecdea776b78ce0bb096927334f80781e720084c9f", 20 | strip_prefix = "fmt-7.1.3", 21 | urls = ["https://boss.hdslb.com/common-arch/dist/fmt-7.1.3.zip"], 22 | ) 23 | maybe( 24 | http_archive, 25 | name = "com_github_brpc_brpc", 26 | urls = ["https://boss.hdslb.com/common-arch/dist/brpc-1.9.0.tar.gz"], 27 | strip_prefix = "brpc-1.9.0", 28 | sha256 = "85856da0216773e1296834116f69f9e80007b7ff421db3be5c9d1890ecfaea74", 29 | ) 30 | maybe( 31 | http_archive, 32 | name = "com_github_google_leveldb", 33 | build_file = "//:third_party/leveldb.BUILD", 34 | sha256 = "f5abe8b5b209c2f36560b75f32ce61412f39a2922f7045ae764a2c23335b6664", 35 | strip_prefix = "leveldb-1.20", 36 | url = "https://boss.hdslb.com/common-arch/dist/leveldb-v1.20.tar.gz", 37 | ) 38 | maybe( 39 | http_archive, 40 | name = "openssl", 41 | build_file = "//:third_party/openssl.BUILD", 42 | sha256 = "f89199be8b23ca45fc7cb9f1d8d3ee67312318286ad030f5316aca6462db6c96", 43 | strip_prefix = "openssl-1.1.1m", 44 | urls = [ 45 | "https://boss.hdslb.com/common-arch/dist/openssl-1.1.1m.tar.gz", 46 | ], 47 | ) 48 | maybe( 49 | http_archive, 50 | name = "rules_foreign_cc", 51 | sha256 = "bdfc2734367a1242514251c7ed2dd12f65dd6d19a97e6a2c61106851be8e7fb8", 52 | strip_prefix = "rules_foreign_cc-master", 53 | url = "https://boss.hdslb.com/common-arch/dist/rules_foreign_cc-master.zip", 54 | ) 55 | maybe( 56 | http_archive, 57 | name = "net_zlib_zlib", 58 | build_file = "//:third_party/zlib.BUILD", 59 | sha256 = "c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1", 60 | strip_prefix = "zlib-1.2.11", 61 | url = "https://boss.hdslb.com/common-arch/dist/zlib-1.2.11.tar.gz", 62 | ) 63 | maybe( 64 | http_archive, 65 | name = "xxhash", 66 | build_file = "//:third_party/xxhash.BUILD", 67 | sha256 = "baee0c6afd4f03165de7a4e67988d16f0f2b257b51d0e3cb91909302a26a79c4", 68 | strip_prefix = "xxHash-0.8.2", 69 | url = "https://boss.hdslb.com/common-arch/dist/xxHash-0.8.2.tar.gz", 70 | ) 71 | maybe( 72 | http_archive, 73 | name = "com_github_jupp0r_prometheus_cpp", 74 | sha256 = "70344acaa89912fd2027408f0e382ba8cb65cfc268e231e66ce1fa7fcc4c0963", 75 | strip_prefix = "prometheus-cpp-master", 76 | url = "https://boss.hdslb.com/common-arch/dist/prometheus-cpp-1.1.0.zip", 77 | ) 78 | 79 | def _data_deps_extension_impl(ctx): 80 | node_repositories() 81 | 82 | data_deps_ext = module_extension( 83 | implementation = _data_deps_extension_impl, 84 | ) 85 | -------------------------------------------------------------------------------- /third_party/xxhash.BUILD: -------------------------------------------------------------------------------- 1 | licenses(["notice"]) # Apache 2 2 | 3 | cc_library( 4 | name = "xxhash", 5 | srcs = ["xxhash.c"], 6 | hdrs = [ 7 | "xxh3.h", 8 | "xxhash.h", 9 | ], 10 | includes = ["."], 11 | visibility = ["//visibility:public"], 12 | ) -------------------------------------------------------------------------------- /third_party/zlib.BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | licenses(["notice"]) # BSD/MIT-like license (for zlib) 4 | 5 | cc_library( 6 | name = "zlib", 7 | srcs = [ 8 | "adler32.c", 9 | "compress.c", 10 | "crc32.c", 11 | "crc32.h", 12 | "deflate.c", 13 | "deflate.h", 14 | "gzclose.c", 15 | "gzguts.h", 16 | "gzlib.c", 17 | "gzread.c", 18 | "gzwrite.c", 19 | "infback.c", 20 | "inffast.c", 21 | "inffast.h", 22 | "inffixed.h", 23 | "inflate.c", 24 | "inflate.h", 25 | "inftrees.c", 26 | "inftrees.h", 27 | "trees.c", 28 | "trees.h", 29 | "uncompr.c", 30 | "zconf.h", 31 | "zutil.c", 32 | "zutil.h", 33 | ], 34 | hdrs = ["zlib.h"], 35 | copts = select({ 36 | "//conditions:default": [ 37 | "-Wno-shift-negative-value", 38 | "-DZ_HAVE_UNISTD_H", 39 | ], 40 | }), 41 | includes = ["."], 42 | ) 43 | --------------------------------------------------------------------------------