chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
cmake_minimum_required(VERSION 3.16...3.28)
|
||||
project(redis_c++ C CXX)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake)
|
||||
|
||||
# Install dependencies:
|
||||
# With apt:
|
||||
# sudo apt-get install libreadline-dev
|
||||
# sudo apt-get install ncurses-dev
|
||||
# With yum:
|
||||
# sudo yum install readline-devel
|
||||
# sudo yum install ncurses-devel
|
||||
|
||||
option(LINK_SO "Whether examples are linked dynamically" OFF)
|
||||
|
||||
execute_process(
|
||||
COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'"
|
||||
OUTPUT_VARIABLE OUTPUT_PATH
|
||||
)
|
||||
|
||||
if(OUTPUT_PATH)
|
||||
list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH})
|
||||
endif()
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(Protobuf REQUIRED)
|
||||
|
||||
find_path(GPERFTOOLS_INCLUDE_DIR NAMES gperftools/heap-profiler.h)
|
||||
find_library(GPERFTOOLS_LIBRARIES NAMES tcmalloc_and_profiler)
|
||||
|
||||
# Search for libthrift* by best effort. If it is not found and brpc is
|
||||
# compiled with thrift protocol enabled, a link error would be reported.
|
||||
find_library(THRIFT_LIB NAMES thrift)
|
||||
if (NOT THRIFT_LIB)
|
||||
set(THRIFT_LIB "")
|
||||
endif()
|
||||
|
||||
find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h)
|
||||
if(LINK_SO)
|
||||
find_library(BRPC_LIB NAMES brpc)
|
||||
else()
|
||||
find_library(BRPC_LIB NAMES libbrpc.a brpc)
|
||||
endif()
|
||||
if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB))
|
||||
message(FATAL_ERROR "Fail to find brpc")
|
||||
endif()
|
||||
|
||||
find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h)
|
||||
find_library(GFLAGS_LIBRARY NAMES gflags libgflags)
|
||||
if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY))
|
||||
message(FATAL_ERROR "Fail to find gflags")
|
||||
endif()
|
||||
|
||||
find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h)
|
||||
find_library(LEVELDB_LIB NAMES leveldb)
|
||||
if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB))
|
||||
message(FATAL_ERROR "Fail to find leveldb")
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR)
|
||||
set(OPENSSL_ROOT_DIR
|
||||
"/usr/local/opt/openssl" # Homebrew installed OpenSSL
|
||||
)
|
||||
endif()
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
set(DYNAMIC_LIB
|
||||
Threads::Threads
|
||||
${GFLAGS_LIBRARY}
|
||||
${PROTOBUF_LIBRARIES}
|
||||
${LEVELDB_LIB}
|
||||
${OPENSSL_CRYPTO_LIBRARY}
|
||||
${OPENSSL_SSL_LIBRARY}
|
||||
${THRIFT_LIB}
|
||||
${GPERFTOOLS_LIBRARIES}
|
||||
dl
|
||||
)
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
set(DYNAMIC_LIB ${DYNAMIC_LIB}
|
||||
pthread
|
||||
"-framework CoreFoundation"
|
||||
"-framework CoreGraphics"
|
||||
"-framework CoreData"
|
||||
"-framework CoreText"
|
||||
"-framework Security"
|
||||
"-framework Foundation"
|
||||
"-Wl,-U,_MallocExtension_ReleaseFreeMemory"
|
||||
"-Wl,-U,_ProfilerStart"
|
||||
"-Wl,-U,_ProfilerStop"
|
||||
"-Wl,-U,__Z13GetStackTracePPvii"
|
||||
"-Wl,-U,_mallctl"
|
||||
"-Wl,-U,_malloc_stats_print"
|
||||
)
|
||||
endif()
|
||||
|
||||
add_executable(redis_cli redis_cli.cpp)
|
||||
brpc_example_configure_target(redis_cli)
|
||||
add_executable(redis_press redis_press.cpp)
|
||||
brpc_example_configure_target(redis_press)
|
||||
add_executable(redis_server redis_server.cpp)
|
||||
brpc_example_configure_target(redis_server)
|
||||
add_executable(redis_cluster_client redis_cluster_client.cpp)
|
||||
brpc_example_configure_target(redis_cluster_client)
|
||||
|
||||
set(AUX_LIB readline ncurses)
|
||||
|
||||
target_link_libraries(redis_cli PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${AUX_LIB})
|
||||
target_link_libraries(redis_press PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB})
|
||||
target_link_libraries(redis_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB})
|
||||
target_link_libraries(redis_cluster_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB})
|
||||
@@ -0,0 +1,159 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
// A brpc based command-line interface to talk with redis-server
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <readline/readline.h>
|
||||
#include <readline/history.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include <butil/logging.h>
|
||||
#include <brpc/channel.h>
|
||||
#include <brpc/redis.h>
|
||||
|
||||
DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short");
|
||||
DEFINE_string(server, "127.0.0.1:6379", "IP Address of server");
|
||||
DEFINE_int32(timeout_ms, 1000, "RPC timeout in milliseconds");
|
||||
DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)");
|
||||
|
||||
namespace brpc {
|
||||
const char* logo();
|
||||
}
|
||||
|
||||
// Send `command' to redis-server via `channel'
|
||||
static bool access_redis(brpc::Channel& channel, const char* command) {
|
||||
brpc::RedisRequest request;
|
||||
if (!request.AddCommand(command)) {
|
||||
LOG(ERROR) << "Fail to add command";
|
||||
return false;
|
||||
}
|
||||
brpc::RedisResponse response;
|
||||
brpc::Controller cntl;
|
||||
channel.CallMethod(NULL, &cntl, &request, &response, NULL);
|
||||
if (cntl.Failed()) {
|
||||
LOG(ERROR) << "Fail to access redis, " << cntl.ErrorText();
|
||||
return false;
|
||||
} else {
|
||||
std::cout << response << std::endl;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// For freeing the memory returned by readline().
|
||||
struct Freer {
|
||||
void operator()(char* mem) {
|
||||
free(mem);
|
||||
}
|
||||
};
|
||||
|
||||
static void dummy_handler(int) {}
|
||||
|
||||
// The getc for readline. The default getc retries reading when meeting
|
||||
// EINTR, which is not what we want.
|
||||
static bool g_canceled = false;
|
||||
static int cli_getc(FILE *stream) {
|
||||
int c = getc(stream);
|
||||
if (c == EOF && errno == EINTR) {
|
||||
g_canceled = true;
|
||||
return '\n';
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
// Parse gflags. We recommend you to use gflags as well.
|
||||
GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
// A Channel represents a communication line to a Server. Notice that
|
||||
// Channel is thread-safe and can be shared by all threads in your program.
|
||||
brpc::Channel channel;
|
||||
|
||||
// Initialize the channel, NULL means using default options.
|
||||
brpc::ChannelOptions options;
|
||||
options.protocol = brpc::PROTOCOL_REDIS;
|
||||
options.connection_type = FLAGS_connection_type;
|
||||
options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/;
|
||||
options.max_retry = FLAGS_max_retry;
|
||||
if (channel.Init(FLAGS_server.c_str(), &options) != 0) {
|
||||
LOG(ERROR) << "Fail to initialize channel";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (argc <= 1) { // interactive mode
|
||||
// We need this dummy signal hander to interrupt getc (and returning
|
||||
// EINTR), SIG_IGN did not work.
|
||||
signal(SIGINT, dummy_handler);
|
||||
|
||||
// Hook getc of readline.
|
||||
rl_getc_function = cli_getc;
|
||||
|
||||
// Print welcome information.
|
||||
printf("%s\n", brpc::logo());
|
||||
printf("This command-line tool mimics the look-n-feel of official "
|
||||
"redis-cli, as a demostration of brpc's capability of"
|
||||
" talking to redis-server. The output and behavior is "
|
||||
"not exactly same with the official one.\n\n");
|
||||
|
||||
for (;;) {
|
||||
char prompt[64];
|
||||
snprintf(prompt, sizeof(prompt), "redis %s> ", FLAGS_server.c_str());
|
||||
std::unique_ptr<char, Freer> command(readline(prompt));
|
||||
if (command == NULL || *command == '\0') {
|
||||
if (g_canceled) {
|
||||
// No input after the prompt and user pressed Ctrl-C,
|
||||
// quit the CLI.
|
||||
return 0;
|
||||
}
|
||||
// User entered an empty command by just pressing Enter.
|
||||
continue;
|
||||
}
|
||||
if (g_canceled) {
|
||||
// User entered sth. and pressed Ctrl-C, start a new prompt.
|
||||
g_canceled = false;
|
||||
continue;
|
||||
}
|
||||
// Add user's command to history so that it's browse-able by
|
||||
// UP-key and search-able by Ctrl-R.
|
||||
add_history(command.get());
|
||||
|
||||
if (!strcmp(command.get(), "help")) {
|
||||
printf("This is a redis CLI written in brpc.\n");
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(command.get(), "quit")) {
|
||||
// Although quit is a valid redis command, it does not make
|
||||
// too much sense to run it in this CLI, just quit.
|
||||
return 0;
|
||||
}
|
||||
access_redis(channel, command.get());
|
||||
}
|
||||
} else {
|
||||
std::string command;
|
||||
command.reserve(argc * 16);
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (i != 1) {
|
||||
command.push_back(' ');
|
||||
}
|
||||
command.append(argv[i]);
|
||||
}
|
||||
if (!access_redis(channel, command.c_str())) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
// A basic client for native Redis Cluster using brpc::RedisClusterChannel.
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <bthread/countdown_event.h>
|
||||
#include <butil/logging.h>
|
||||
#include <brpc/controller.h>
|
||||
#include <brpc/redis.h>
|
||||
#include <brpc/redis_cluster.h>
|
||||
|
||||
DEFINE_string(seeds, "127.0.0.1:7000,127.0.0.1:7001",
|
||||
"Comma-separated redis cluster seed endpoints");
|
||||
DEFINE_string(key_prefix, "brpc_cluster_demo", "Prefix for demo keys");
|
||||
DEFINE_int32(timeout_ms, 1000, "RPC timeout in milliseconds");
|
||||
DEFINE_int32(rpc_max_retry, 1, "Max retries for a single sub RPC");
|
||||
DEFINE_int32(max_redirect, 5, "Max MOVED/ASK redirect retries");
|
||||
DEFINE_int32(refresh_interval_s, 30, "Periodic topology refresh interval");
|
||||
DEFINE_int32(topology_refresh_timeout_ms, 1000,
|
||||
"Timeout of CLUSTER SLOTS/NODES request");
|
||||
DEFINE_bool(disable_periodic_refresh, false, "Disable periodic topology refresh");
|
||||
|
||||
namespace {
|
||||
|
||||
class Done : public google::protobuf::Closure {
|
||||
public:
|
||||
explicit Done(bthread::CountdownEvent* event) : _event(event) {}
|
||||
void Run() override { _event->signal(); }
|
||||
|
||||
private:
|
||||
bthread::CountdownEvent* _event;
|
||||
};
|
||||
|
||||
int PrintResponse(const brpc::RedisResponse& response) {
|
||||
for (int i = 0; i < response.reply_size(); ++i) {
|
||||
const brpc::RedisReply& reply = response.reply(i);
|
||||
if (reply.is_error()) {
|
||||
LOG(ERROR) << "reply[" << i << "] error=" << reply.error_message();
|
||||
return -1;
|
||||
}
|
||||
LOG(INFO) << "reply[" << i << "] " << reply;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
brpc::RedisClusterChannelOptions options;
|
||||
options.max_redirect = FLAGS_max_redirect;
|
||||
options.refresh_interval_s = FLAGS_refresh_interval_s;
|
||||
options.enable_periodic_refresh = !FLAGS_disable_periodic_refresh;
|
||||
options.topology_refresh_timeout_ms = FLAGS_topology_refresh_timeout_ms;
|
||||
options.channel_options.timeout_ms = FLAGS_timeout_ms;
|
||||
options.channel_options.max_retry = FLAGS_rpc_max_retry;
|
||||
|
||||
brpc::RedisClusterChannel channel;
|
||||
if (channel.Init(FLAGS_seeds, &options) != 0) {
|
||||
LOG(ERROR) << "Fail to init redis cluster channel, seeds=" << FLAGS_seeds;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const std::string key1 = FLAGS_key_prefix + "_1";
|
||||
const std::string key2 = FLAGS_key_prefix + "_2";
|
||||
|
||||
// Sync pipeline.
|
||||
brpc::RedisRequest request;
|
||||
brpc::RedisResponse response;
|
||||
brpc::Controller cntl;
|
||||
CHECK(request.AddCommand("set %s v1", key1.c_str()));
|
||||
CHECK(request.AddCommand("set %s v2", key2.c_str()));
|
||||
CHECK(request.AddCommand("mget %s %s", key1.c_str(), key2.c_str()));
|
||||
channel.CallMethod(NULL, &cntl, &request, &response, NULL);
|
||||
if (cntl.Failed()) {
|
||||
LOG(ERROR) << "Sync call failed: " << cntl.ErrorText();
|
||||
return -1;
|
||||
}
|
||||
if (PrintResponse(response) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Async single request.
|
||||
brpc::RedisRequest async_request;
|
||||
brpc::RedisResponse async_response;
|
||||
brpc::Controller async_cntl;
|
||||
CHECK(async_request.AddCommand("get %s", key1.c_str()));
|
||||
|
||||
bthread::CountdownEvent event(1);
|
||||
Done done(&event);
|
||||
channel.CallMethod(NULL, &async_cntl, &async_request, &async_response, &done);
|
||||
event.wait();
|
||||
if (async_cntl.Failed()) {
|
||||
LOG(ERROR) << "Async call failed: " << async_cntl.ErrorText();
|
||||
return -1;
|
||||
}
|
||||
if (PrintResponse(async_response) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOG(INFO) << "Redis cluster demo finished";
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
// A multi-threaded client getting keys from a redis-server constantly.
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <bthread/bthread.h>
|
||||
#include <butil/logging.h>
|
||||
#include <butil/string_printf.h>
|
||||
#include <bvar/bvar.h>
|
||||
#include <brpc/channel.h>
|
||||
#include <brpc/server.h>
|
||||
#include <brpc/redis.h>
|
||||
|
||||
DEFINE_int32(thread_num, 50, "Number of threads to send requests");
|
||||
DEFINE_bool(use_bthread, false, "Use bthread to send requests");
|
||||
DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short");
|
||||
DEFINE_string(server, "0.0.0.0:6379", "IP Address of server");
|
||||
DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds");
|
||||
DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)");
|
||||
DEFINE_bool(dont_fail, false, "Print fatal when some call failed");
|
||||
DEFINE_string(key, "hello", "The key to be get");
|
||||
DEFINE_string(value, "world", "The value associated with the key");
|
||||
DEFINE_int32(batch, 1, "Pipelined Operations");
|
||||
DEFINE_int32(dummy_port, -1, "port of dummy server(for monitoring)");
|
||||
DEFINE_int32(backup_request_ms, -1, "Timeout for sending a backup request");
|
||||
|
||||
bvar::LatencyRecorder g_latency_recorder("client");
|
||||
bvar::Adder<int> g_error_count("client_error_count");
|
||||
|
||||
struct SenderArgs {
|
||||
int base_index;
|
||||
brpc::Channel* redis_channel;
|
||||
};
|
||||
|
||||
static void* sender(void* void_args) {
|
||||
SenderArgs* args = (SenderArgs*)void_args;
|
||||
|
||||
std::string value;
|
||||
std::vector<std::pair<std::string, std::string> > kvs;
|
||||
kvs.resize(FLAGS_batch);
|
||||
for (int i = 0; i < FLAGS_batch; ++i) {
|
||||
kvs[i].first = butil::string_printf(
|
||||
"%s_%04d", FLAGS_key.c_str(), args->base_index + i);
|
||||
kvs[i].second = butil::string_printf(
|
||||
"%s_%04d", FLAGS_value.c_str(), args->base_index + i);
|
||||
}
|
||||
|
||||
brpc::RedisRequest request;
|
||||
for (int i = 0; i < FLAGS_batch; ++i) {
|
||||
CHECK(request.AddCommand("GET %s", kvs[i].first.c_str()));
|
||||
}
|
||||
while (!brpc::IsAskedToQuit()) {
|
||||
// We will receive response synchronously, safe to put variables
|
||||
// on stack.
|
||||
brpc::RedisResponse response;
|
||||
brpc::Controller cntl;
|
||||
|
||||
// Because `done'(last parameter) is NULL, this function waits until
|
||||
// the response comes back or error occurs(including timedout).
|
||||
args->redis_channel->CallMethod(NULL, &cntl, &request, &response, NULL);
|
||||
const int64_t elp = cntl.latency_us();
|
||||
if (!cntl.Failed()) {
|
||||
g_latency_recorder << elp;
|
||||
CHECK_EQ(response.reply_size(), FLAGS_batch);
|
||||
for (int i = 0; i < FLAGS_batch; ++i) {
|
||||
CHECK_EQ(kvs[i].second.c_str(), response.reply(i).data())
|
||||
<< "base=" << args->base_index << " i=" << i;
|
||||
}
|
||||
} else {
|
||||
g_error_count << 1;
|
||||
CHECK(brpc::IsAskedToQuit() || !FLAGS_dont_fail)
|
||||
<< "error=" << cntl.ErrorText() << " latency=" << elp;
|
||||
// We can't connect to the server, sleep a while. Notice that this
|
||||
// is a specific sleeping to prevent this thread from spinning too
|
||||
// fast. You should continue the business logic in a production
|
||||
// server rather than sleeping.
|
||||
bthread_usleep(50000);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
// Parse gflags. We recommend you to use gflags as well.
|
||||
GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
// A Channel represents a communication line to a Server. Notice that
|
||||
// Channel is thread-safe and can be shared by all threads in your program.
|
||||
brpc::Channel channel;
|
||||
|
||||
// Initialize the channel, NULL means using default options.
|
||||
brpc::ChannelOptions options;
|
||||
options.protocol = brpc::PROTOCOL_REDIS;
|
||||
options.connection_type = FLAGS_connection_type;
|
||||
options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/;
|
||||
options.max_retry = FLAGS_max_retry;
|
||||
options.backup_request_ms = FLAGS_backup_request_ms;
|
||||
if (channel.Init(FLAGS_server.c_str(), &options) != 0) {
|
||||
LOG(ERROR) << "Fail to initialize channel";
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Pipeline #batch * #thread_num SET requests into redis so that we
|
||||
// have keys to get.
|
||||
brpc::RedisRequest request;
|
||||
brpc::RedisResponse response;
|
||||
brpc::Controller cntl;
|
||||
for (int i = 0; i < FLAGS_batch * FLAGS_thread_num; ++i) {
|
||||
if (!request.AddCommand("SET %s_%04d %s_%04d",
|
||||
FLAGS_key.c_str(), i,
|
||||
FLAGS_value.c_str(), i)) {
|
||||
LOG(ERROR) << "Fail to SET " << i << "th request";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
channel.CallMethod(NULL, &cntl, &request, &response, NULL);
|
||||
if (cntl.Failed()) {
|
||||
LOG(ERROR) << "Fail to access redis, " << cntl.ErrorText();
|
||||
return -1;
|
||||
}
|
||||
if (FLAGS_batch * FLAGS_thread_num != response.reply_size()) {
|
||||
LOG(ERROR) << "Fail to set";
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < FLAGS_batch * FLAGS_thread_num; ++i) {
|
||||
CHECK_EQ("OK", response.reply(i).data());
|
||||
}
|
||||
LOG(INFO) << "Set " << FLAGS_batch * FLAGS_thread_num << " values";
|
||||
|
||||
if (FLAGS_dummy_port >= 0) {
|
||||
brpc::StartDummyServerAt(FLAGS_dummy_port);
|
||||
}
|
||||
|
||||
std::vector<bthread_t> bids;
|
||||
std::vector<pthread_t> pids;
|
||||
bids.resize(FLAGS_thread_num);
|
||||
pids.resize(FLAGS_thread_num);
|
||||
std::vector<SenderArgs> args;
|
||||
args.resize(FLAGS_thread_num);
|
||||
for (int i = 0; i < FLAGS_thread_num; ++i) {
|
||||
args[i].base_index = i * FLAGS_batch;
|
||||
args[i].redis_channel = &channel;
|
||||
if (!FLAGS_use_bthread) {
|
||||
if (pthread_create(&pids[i], NULL, sender, &args[i]) != 0) {
|
||||
LOG(ERROR) << "Fail to create pthread";
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
if (bthread_start_background(
|
||||
&bids[i], NULL, sender, &args[i]) != 0) {
|
||||
LOG(ERROR) << "Fail to create bthread";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (!brpc::IsAskedToQuit()) {
|
||||
sleep(1);
|
||||
|
||||
LOG(INFO) << "Accessing redis-server at qps=" << g_latency_recorder.qps(1)
|
||||
<< " latency=" << g_latency_recorder.latency(1);
|
||||
}
|
||||
|
||||
LOG(INFO) << "redis_client is going to quit";
|
||||
for (int i = 0; i < FLAGS_thread_num; ++i) {
|
||||
if (!FLAGS_use_bthread) {
|
||||
pthread_join(pids[i], NULL);
|
||||
} else {
|
||||
bthread_join(bids[i], NULL);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
//
|
||||
// A brpc based redis-server. Currently just implement set and
|
||||
// get, but it's sufficient that you can get the idea how to
|
||||
// implement brpc::RedisCommandHandler.
|
||||
|
||||
#include <brpc/server.h>
|
||||
#include <brpc/redis.h>
|
||||
#include <butil/crc32c.h>
|
||||
#include <butil/strings/string_split.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <butil/time.h>
|
||||
|
||||
DEFINE_int32(port, 6379, "TCP Port of this server");
|
||||
|
||||
class AuthSession : public brpc::Destroyable {
|
||||
public:
|
||||
explicit AuthSession(const std::string& user_name, const std::string& password)
|
||||
: _user_name(user_name), _password(password) {}
|
||||
|
||||
void Destroy() override {
|
||||
delete this;
|
||||
}
|
||||
|
||||
const std::string _user_name;
|
||||
const std::string _password;
|
||||
};
|
||||
|
||||
class RedisServiceImpl : public brpc::RedisService {
|
||||
public:
|
||||
RedisServiceImpl() {
|
||||
_user_password["db1"] = "123456";
|
||||
_user_password["db2"] = "123456";
|
||||
_db_map["db1"].resize(kHashSlotNum);
|
||||
_db_map["db2"].resize(kHashSlotNum);
|
||||
}
|
||||
|
||||
bool Set(const std::string& db_name, const std::string& key, const std::string& value) {
|
||||
int slot = butil::crc32c::Value(key.c_str(), key.size()) % kHashSlotNum;
|
||||
_mutex[slot].lock();
|
||||
auto& kv = _db_map[db_name];
|
||||
kv[slot][key] = value;
|
||||
_mutex[slot].unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Auth(const std::string& db_name, const std::string& password) {
|
||||
if (_user_password.find(db_name) == _user_password.end()) {
|
||||
return false;
|
||||
} else {
|
||||
if (_user_password[db_name] != password) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Get(const std::string& db_name, const std::string& key, std::string* value) {
|
||||
int slot = butil::crc32c::Value(key.c_str(), key.size()) % kHashSlotNum;
|
||||
_mutex[slot].lock();
|
||||
auto& kv = _db_map[db_name];
|
||||
auto it = kv[slot].find(key);
|
||||
if (it == kv[slot].end()) {
|
||||
_mutex[slot].unlock();
|
||||
return false;
|
||||
}
|
||||
*value = it->second;
|
||||
_mutex[slot].unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
const static int kHashSlotNum = 32;
|
||||
typedef std::unordered_map<std::string, std::string> KVStore;
|
||||
std::unordered_map<std::string, std::vector<KVStore>> _db_map;
|
||||
std::unordered_map<std::string, std::string> _user_password;
|
||||
butil::Mutex _mutex[kHashSlotNum];
|
||||
};
|
||||
|
||||
class GetCommandHandler : public brpc::RedisCommandHandler {
|
||||
public:
|
||||
explicit GetCommandHandler(RedisServiceImpl* rsimpl)
|
||||
: _rsimpl(rsimpl) {}
|
||||
|
||||
brpc::RedisCommandHandlerResult Run(brpc::RedisConnContext* ctx,
|
||||
const std::vector<butil::StringPiece>& args,
|
||||
brpc::RedisReply* output,
|
||||
bool /*flush_batched*/) override {
|
||||
|
||||
AuthSession* session = static_cast<AuthSession*>(ctx->get_session());
|
||||
if (session == nullptr) {
|
||||
output->FormatError("No auth session");
|
||||
return brpc::REDIS_CMD_HANDLED;
|
||||
}
|
||||
if (session->_user_name.empty()) {
|
||||
output->FormatError("No user name");
|
||||
return brpc::REDIS_CMD_HANDLED;
|
||||
}
|
||||
if (args.size() != 2ul) {
|
||||
output->FormatError("Expect 1 arg for 'get', actually %lu", args.size()-1);
|
||||
return brpc::REDIS_CMD_HANDLED;
|
||||
}
|
||||
const std::string key(args[1].data(), args[1].size());
|
||||
std::string value;
|
||||
if (_rsimpl->Get(session->_user_name, key, &value)) {
|
||||
output->SetString(value);
|
||||
} else {
|
||||
output->SetNullString();
|
||||
}
|
||||
return brpc::REDIS_CMD_HANDLED;
|
||||
}
|
||||
|
||||
private:
|
||||
RedisServiceImpl* _rsimpl;
|
||||
};
|
||||
|
||||
class SetCommandHandler : public brpc::RedisCommandHandler {
|
||||
public:
|
||||
explicit SetCommandHandler(RedisServiceImpl* rsimpl)
|
||||
: _rsimpl(rsimpl) {}
|
||||
|
||||
brpc::RedisCommandHandlerResult Run(brpc::RedisConnContext* ctx,
|
||||
const std::vector<butil::StringPiece>& args,
|
||||
brpc::RedisReply* output,
|
||||
bool /*flush_batched*/) override {
|
||||
AuthSession* session = static_cast<AuthSession*>(ctx->get_session());
|
||||
if (session == nullptr) {
|
||||
output->FormatError("No auth session");
|
||||
return brpc::REDIS_CMD_HANDLED;
|
||||
}
|
||||
if (session->_user_name.empty()) {
|
||||
output->FormatError("No user name");
|
||||
return brpc::REDIS_CMD_HANDLED;
|
||||
}
|
||||
if (args.size() != 3ul) {
|
||||
output->FormatError("Expect 2 args for 'set', actually %lu", args.size()-1);
|
||||
return brpc::REDIS_CMD_HANDLED;
|
||||
}
|
||||
const std::string key(args[1].data(), args[1].size());
|
||||
const std::string value(args[2].data(), args[2].size());
|
||||
_rsimpl->Set(session->_user_name, key, value);
|
||||
output->SetStatus("OK");
|
||||
return brpc::REDIS_CMD_HANDLED;
|
||||
}
|
||||
|
||||
private:
|
||||
RedisServiceImpl* _rsimpl;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class AuthCommandHandler : public brpc::RedisCommandHandler {
|
||||
public:
|
||||
explicit AuthCommandHandler(RedisServiceImpl* rsimpl)
|
||||
: _rsimpl(rsimpl) {}
|
||||
brpc::RedisCommandHandlerResult Run(brpc::RedisConnContext* ctx,
|
||||
const std::vector<butil::StringPiece>& args,
|
||||
brpc::RedisReply* output,
|
||||
bool /*flush_batched*/) override {
|
||||
if (args.size() != 3ul) {
|
||||
output->FormatError("Expect 2 args for 'auth', actually %lu", args.size()-1);
|
||||
return brpc::REDIS_CMD_HANDLED;
|
||||
}
|
||||
|
||||
const std::string db_name(args[1].data(), args[1].size());
|
||||
const std::string password(args[2].data(), args[2].size());
|
||||
|
||||
if (_rsimpl->Auth(db_name, password)) {
|
||||
output->SetStatus("OK");
|
||||
auto auth_session = new AuthSession(db_name, password);
|
||||
ctx->reset_session(auth_session);
|
||||
} else {
|
||||
output->FormatError("Invalid password for database '%s'", db_name.c_str());
|
||||
}
|
||||
return brpc::REDIS_CMD_HANDLED;
|
||||
}
|
||||
|
||||
private:
|
||||
RedisServiceImpl* _rsimpl;
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
RedisServiceImpl *rsimpl = new RedisServiceImpl;
|
||||
auto get_handler =std::unique_ptr<GetCommandHandler>(new GetCommandHandler(rsimpl));
|
||||
auto set_handler =std::unique_ptr<SetCommandHandler>( new SetCommandHandler(rsimpl));
|
||||
auto auth_handler = std::unique_ptr<AuthCommandHandler>(new AuthCommandHandler(rsimpl));
|
||||
rsimpl->AddCommandHandler("get", get_handler.get());
|
||||
rsimpl->AddCommandHandler("set", set_handler.get());
|
||||
rsimpl->AddCommandHandler("auth", auth_handler.get());
|
||||
|
||||
brpc::Server server;
|
||||
brpc::ServerOptions server_options;
|
||||
server_options.redis_service = rsimpl;
|
||||
if (server.Start(FLAGS_port, &server_options) != 0) {
|
||||
LOG(ERROR) << "Fail to start server";
|
||||
return -1;
|
||||
}
|
||||
server.RunUntilAskedToQuit();
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user