pax_global_header 0000666 0000000 0000000 00000000064 15252174244 0014520 g ustar 00root root 0000000 0000000 52 comment=ac65014bd886e024499a900f314c6592382e4aa6
sogou-srpc-ac65014/ 0000775 0000000 0000000 00000000000 15252174244 0014146 5 ustar 00root root 0000000 0000000 sogou-srpc-ac65014/.bazelignore 0000664 0000000 0000000 00000000127 15252174244 0016450 0 ustar 00root root 0000000 0000000 ./third_party/snappy/third_party/benchmark
./third_party/snappy/third_party/googletest
sogou-srpc-ac65014/.editorconfig 0000664 0000000 0000000 00000000136 15252174244 0016623 0 ustar 00root root 0000000 0000000 # top-most EditorConfig file
root = true
# all files
[*]
indent_style = tab
indent_size = 4
sogou-srpc-ac65014/.github/ 0000775 0000000 0000000 00000000000 15252174244 0015506 5 ustar 00root root 0000000 0000000 sogou-srpc-ac65014/.github/workflows/ 0000775 0000000 0000000 00000000000 15252174244 0017543 5 ustar 00root root 0000000 0000000 sogou-srpc-ac65014/.github/workflows/ci.yml 0000664 0000000 0000000 00000004266 15252174244 0020671 0 ustar 00root root 0000000 0000000 name: ci build
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
ubuntu-cmake:
name: ubuntu
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: install deps
run: |
sudo apt-get update
sudo apt-get install -y libprotobuf-dev protobuf-compiler libgtest-dev valgrind
- name: update submodules
run: git submodule update --init --recursive
- name: make
run: make -j4
- name: make tutorial
run: make tutorial -j4
- name: make check
run: make check -j4
- name: make install
run: sudo make install
fedora-cmake:
name: fedora
runs-on: ubuntu-latest
steps:
- name: Setup Podman
run: |
sudo apt update
sudo apt-get -y install podman
podman pull fedora:rawhide
- name: Get source
uses: actions/checkout@master
with:
path: 'workflow'
- name: Create container and run tests
run: |
{
echo 'FROM fedora:rawhide'
echo 'RUN dnf -y update'
echo 'RUN dnf -y install cmake gcc-c++ gtest-devel git make'
echo 'RUN dnf -y install openssl-devel protobuf-devel'
echo 'RUN dnf -y install lz4-devel snappy-devel'
echo 'RUN dnf clean all'
echo 'COPY workflow workflow'
echo 'WORKDIR /workflow'
echo 'RUN git submodule update --init --recursive'
echo 'RUN cmake'
echo 'RUN make'
echo 'RUN make check'
echo 'RUN make tutorial'
} > podmanfile
podman build --tag fedorarawhide -f ./podmanfile
ubuntu-bazel:
name: bazel
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Install Bazel 5.4.0
run: |
sudo apt-get update
sudo apt-get install -y apt-transport-https curl gnupg
curl -LO "https://releases.bazel.build/5.4.0/release/bazel-5.4.0-linux-x86_64"
chmod +x bazel-5.4.0-linux-x86_64
sudo mv bazel-5.4.0-linux-x86_64 /usr/local/bin/bazel
- name: bazel build
run: bazel build ...
sogou-srpc-ac65014/.gitignore 0000664 0000000 0000000 00000000543 15252174244 0016140 0 ustar 00root root 0000000 0000000 *.a
*.bak
*.gz
*.zip
*.tar
*.la
*.lo
*.o
*.rpm
*.so
*.so.*
*.cmake
*.vcxproj
*.filters
*.sln
*.pb.h
*.pb.cc
*.log
*.srpc.h
*.thrift.h
*.pb_skeleton.h
*.pb_skeleton.cc
*.thrift_skeleton.h
*.thrift_skeleton.cc
_bin/
_include/
_lib/
.deps/
build/
build_pkg/
CMakeFiles/
Debug/
Release/
missing
SRCINFO
SRCNUMVER
SRCVERSION
CMakeCache.txt
Makefile
bazel-*
sogou-srpc-ac65014/.gitmodules 0000664 0000000 0000000 00000000462 15252174244 0016325 0 ustar 00root root 0000000 0000000 [submodule "workflow"]
path = workflow
url = https://github.com/sogou/workflow.git
[submodule "third_party/snappy"]
path = third_party/snappy
url = https://github.com/google/snappy
branch = 1.1.9
[submodule "third_party/lz4"]
path = third_party/lz4
url = https://github.com/lz4/lz4
branch = v1.9.3
sogou-srpc-ac65014/BUILD 0000664 0000000 0000000 00000016233 15252174244 0014735 0 ustar 00root root 0000000 0000000 load("@rules_cc//cc:defs.bzl", "cc_proto_library")
load("@rules_proto//proto:defs.bzl", "proto_library")
load(":srpc.bzl", "srpc_cc_library")
proto_library(
name = "message_proto",
srcs = [
"src/message/rpc_meta.proto",
"src/message/rpc_meta_brpc.proto",
"src/message/rpc_meta_trpc.proto",
],
strip_import_prefix = "src/message",
)
cc_proto_library(
name = "MessageProto",
deps = [":message_proto"],
)
proto_library(
name = "module_proto",
srcs = [
"src/module/proto/opentelemetry_common.proto",
"src/module/proto/opentelemetry_resource.proto",
"src/module/proto/opentelemetry_trace.proto",
"src/module/proto/opentelemetry_metrics.proto",
"src/module/proto/opentelemetry_metrics_service.proto",
],
strip_import_prefix = "src/module/proto",
)
cc_proto_library(
name = "ModuleProto",
deps = [":module_proto"],
)
cc_library(
name = "srpc_hdrs",
hdrs = glob(["src/include/srpc/*"]),
includes = ["src/include"],
visibility = ["//visibility:public"],
deps = [
"@workflow//:workflow_hdrs",
],
)
cc_library(
name = "srpc",
srcs = glob(["src/**/*.cc"]),
hdrs = glob([
"src/**/*.h",
"src/**/*.inl",
]),
includes = [
"src",
"src/compress",
"src/message",
"src/module",
"src/thrift",
"src/var",
],
visibility = ["//visibility:public"],
deps = [
":MessageProto",
":ModuleProto",
"@lz4",
"@snappy",
"@workflow//:http",
"@workflow//:redis",
"@workflow//:upstream",
],
)
cc_library(
name = "srpc_generator_lib",
srcs = glob(
[
"src/generator/*.cc",
],
exclude = [
"src/compiler.cc",
],
),
hdrs = glob([
"src/generator/*.h",
]),
includes = ["src/generator"],
visibility = ["//visibility:public"],
deps = [
":srpc",
],
)
cc_binary(
name = "srpc_generator",
srcs = ["src/generator/compiler.cc"],
visibility = ["//visibility:public"],
deps = [
":srpc",
":srpc_generator_lib",
],
)
proto_library(
name = "echo_pb_proto",
srcs = [
"tutorial/echo_pb.proto",
],
strip_import_prefix = "tutorial",
)
cc_proto_library(
name = "EchoProto",
deps = [":echo_pb_proto"],
)
srpc_cc_library(
name = "echo_pb_srpc",
srcs = ["tutorial/echo_pb.proto"],
deps = [":EchoProto"],
)
cc_binary(
name = "srpc_pb_server",
srcs = ["tutorial/tutorial-01-srpc_pb_server.cc"],
linkopts = [
"-lpthread",
"-lssl",
"-lcrypto",
],
deps = [
":echo_pb_srpc",
":srpc",
":srpc_hdrs",
],
)
cc_binary(
name = "srpc_pb_client",
srcs = ["tutorial/tutorial-02-srpc_pb_client.cc"],
linkopts = [
"-lpthread",
"-lssl",
"-lcrypto",
],
deps = [
":echo_pb_srpc",
":srpc",
":srpc_hdrs",
],
)
srpc_cc_library(
name = "echo_thrift_srpc",
srcs = ["tutorial/echo_thrift.thrift"],
type = "thrift",
)
cc_binary(
name = "srpc_thrift_server",
srcs = ["tutorial/tutorial-03-srpc_thrift_server.cc"],
linkopts = [
"-lpthread",
"-lssl",
"-lcrypto",
],
deps = [
":echo_thrift_srpc",
":srpc",
":srpc_hdrs",
],
)
cc_binary(
name = "srpc_thrift_client",
srcs = ["tutorial/tutorial-04-srpc_thrift_client.cc"],
linkopts = [
"-lpthread",
"-lssl",
"-lcrypto",
],
deps = [
":echo_thrift_srpc",
":srpc",
":srpc_hdrs",
],
)
cc_binary(
name = "brpc_pb_server",
srcs = ["tutorial/tutorial-05-brpc_pb_server.cc"],
linkopts = [
"-lpthread",
"-lssl",
"-lcrypto",
],
deps = [
":echo_pb_srpc",
":srpc",
":srpc_hdrs",
],
)
cc_binary(
name = "brpc_pb_client",
srcs = ["tutorial/tutorial-06-brpc_pb_client.cc"],
linkopts = [
"-lpthread",
"-lssl",
"-lcrypto",
],
deps = [
":echo_pb_srpc",
":srpc",
":srpc_hdrs",
],
)
cc_binary(
name = "thrift_thrift_server",
srcs = ["tutorial/tutorial-07-thrift_thrift_server.cc"],
linkopts = [
"-lpthread",
"-lssl",
"-lcrypto",
],
deps = [
":echo_thrift_srpc",
":srpc",
":srpc_hdrs",
],
)
cc_binary(
name = "thrift_thrift_client",
srcs = ["tutorial/tutorial-08-thrift_thrift_client.cc"],
linkopts = [
"-lpthread",
"-lssl",
"-lcrypto",
],
deps = [
":echo_thrift_srpc",
":srpc",
":srpc_hdrs",
],
)
cc_binary(
name = "client_task",
srcs = ["tutorial/tutorial-09-client_task.cc"],
linkopts = [
"-lpthread",
"-lssl",
"-lcrypto",
],
deps = [
":echo_pb_srpc",
":srpc",
":srpc_hdrs",
],
)
cc_binary(
name = "server_async",
srcs = ["tutorial/tutorial-10-server_async.cc"],
linkopts = [
"-lpthread",
"-lssl",
"-lcrypto",
],
deps = [
":echo_pb_srpc",
":srpc",
":srpc_hdrs",
],
)
proto_library(
name = "helloworld_proto",
srcs = [
"tutorial/helloworld.proto",
],
strip_import_prefix = "tutorial",
)
cc_proto_library(
name = "HelloworldProto",
deps = [":helloworld_proto"],
)
srpc_cc_library(
name = "helloworld_pb_srpc",
srcs = ["tutorial/helloworld.proto"],
deps = [":HelloworldProto"],
)
cc_binary(
name = "trpc_pb_server",
srcs = ["tutorial/tutorial-11-trpc_pb_server.cc"],
linkopts = [
"-lpthread",
"-lssl",
"-lcrypto",
],
deps = [
":helloworld_pb_srpc",
":srpc",
":srpc_hdrs",
],
)
cc_binary(
name = "trpc_pb_client",
srcs = ["tutorial/tutorial-12-trpc_pb_client.cc"],
linkopts = [
"-lpthread",
"-lssl",
"-lcrypto",
],
deps = [
":helloworld_pb_srpc",
":srpc",
":srpc_hdrs",
],
)
cc_binary(
name = "trpc_http_server",
srcs = ["tutorial/tutorial-13-trpc_http_server.cc"],
linkopts = [
"-lpthread",
"-lssl",
"-lcrypto",
],
deps = [
":helloworld_pb_srpc",
":srpc",
":srpc_hdrs",
],
)
cc_binary(
name = "trpc_http_client",
srcs = ["tutorial/tutorial-14-trpc_http_client.cc"],
linkopts = [
"-lpthread",
"-lssl",
"-lcrypto",
],
deps = [
":helloworld_pb_srpc",
":srpc",
":srpc_hdrs",
],
)
cc_binary(
name = "srpc_pb_proxy",
srcs = ["tutorial/tutorial-15-srpc_pb_proxy.cc"],
linkopts = [
"-lpthread",
"-lssl",
"-lcrypto",
],
deps = [
":echo_pb_srpc",
":srpc",
":srpc_hdrs",
],
)
cc_binary(
name = "server_with_metrics",
srcs = ["tutorial/tutorial-16-server_with_metrics.cc"],
linkopts = [
"-lpthread",
"-lssl",
"-lcrypto",
],
deps = [
":echo_pb_srpc",
":srpc",
":srpc_hdrs",
],
)
sogou-srpc-ac65014/CMakeLists.txt 0000664 0000000 0000000 00000013207 15252174244 0016711 0 ustar 00root root 0000000 0000000 cmake_minimum_required(VERSION 3.6)
set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "build type")
set(CMAKE_SKIP_RPATH TRUE)
project(srpc
VERSION 0.10.5
LANGUAGES C CXX)
###Options
if (WIN32)
option(SRPC_BUILD_STATIC_RUNTIME "Use static runtime" ON)
endif ()
#### CHECK
include(CheckIncludeFile)
include(CheckIncludeFileCXX)
set(THIRD_PARTY_FATAL_MESSAGE
" is neither installed nor found in third_party!
Sugguestion to initial third_party:
\"git submodule update --init\""
)
find_library(LZ4_LIBRARY NAMES lz4)
check_include_file("lz4.h" LZ4_INSTALLED)
if (NOT LZ4_INSTALLED AND ${LZ4_LIBRARY} STREQUAL "LZ4_LIBRARY-NOTFOUND")
if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/lz4/lib/lz4.h")
message( FATAL_ERROR "\nLz4" ${THIRD_PARTY_FATAL_MESSAGE} )
endif ()
else ()
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/lz4/lib/lz4.h")
message("Lz4 third_party FOUND. Use for source code dependencies.")
set(LZ4_INSTALLED 0 CACHE INTERNAL "check_lz4_installed")
else ()
find_path(LZ4_INCLUDE_DIR NAMES "lz4.h")
include_directories(${LZ4_INCLUDE_DIR})
set(LZ4_INSTALLED 1 CACHE INTERNAL "check_lz4_installed")
endif ()
endif ()
find_package(Snappy)
check_include_file_cxx("snappy.h" SNAPPY_INSTALLED)
if (NOT SNAPPY_INSTALLED AND NOT ${Snappy_FOUND})
if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/snappy/cmake")
message( FATAL_ERROR "\nSnappy" ${THIRD_PARTY_FATAL_MESSAGE} )
endif ()
else ()
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/snappy/cmake")
message("Snappy third_party FOUND. Use for source code dependencies.")
set(SNAPPY_INSTALLED 0 CACHE INTERNAL "check_snappy_installed")
else ()
find_path(Snappy_INCLUDE_DIR NAMES "snappy.h")
include_directories(${Snappy_INCLUDE_DIR})
set(SNAPPY_INSTALLED 1 CACHE INTERNAL "check_snappy_installed")
endif ()
endif ()
check_include_file_cxx("workflow/Workflow.h" WORKFLOW_INSTALLED)
if (NOT WORKFLOW_INSTALLED)
if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/workflow/workflow-config.cmake.in")
message( FATAL_ERROR "\nWorkflow" ${THIRD_PARTY_FATAL_MESSAGE} )
endif ()
else ()
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/workflow/workflow-config.cmake.in")
message("Workflow third_party FOUND. Use for source code dependencies.")
set(WORKFLOW_INSTALLED 0)
endif ()
endif ()
find_program(PROTOC "protoc")
if(${PROTOC} STREQUAL "PROTOC-NOTFOUND")
message( FATAL_ERROR "Protobuf compiler is missing!")
endif()
#### PREPARE
set(INC_DIR ${PROJECT_SOURCE_DIR}/_include CACHE PATH "srpc inc")
set(LIB_DIR ${PROJECT_SOURCE_DIR}/_lib CACHE PATH "srpc lib")
set(BIN_DIR ${PROJECT_SOURCE_DIR}/_bin CACHE PATH "srpc bin")
include(GNUInstallDirs)
set(CMAKE_CONFIG_INSTALL_FILE ${PROJECT_BINARY_DIR}/config.toinstall.cmake)
set(CMAKE_CONFIG_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${LIB_DIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${LIB_DIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BIN_DIR})
add_custom_target(
LINK_HEADERS ALL
COMMENT "link headers..."
)
INCLUDE(CMakeLists_Headers.txt)
macro(makeLink src dest target)
add_custom_command(
TARGET ${target} PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${src} ${dest}
DEPENDS ${dest}
)
endmacro()
add_custom_command(
TARGET LINK_HEADERS PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${INC_DIR}/${PROJECT_NAME}
)
foreach(header_file ${INCLUDE_HEADERS})
string(REPLACE "/" ";" arr ${header_file})
list(GET arr -1 file_name)
makeLink(${PROJECT_SOURCE_DIR}/${header_file} ${INC_DIR}/${PROJECT_NAME}/${file_name} LINK_HEADERS)
endforeach()
if (WIN32)
if (SRPC_BUILD_STATIC_RUNTIME)
set(CompilerFlags
CMAKE_CXX_FLAGS
CMAKE_CXX_FLAGS_DEBUG
CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_MINSIZEREL
CMAKE_C_FLAGS
CMAKE_C_FLAGS_DEBUG
CMAKE_C_FLAGS_RELEASE
CMAKE_C_FLAGS_MINSIZEREL
)
foreach(CompilerFlag ${CompilerFlags})
string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
endforeach ()
endif ()
endif ()
add_subdirectory(src)
#### CONFIG
include(CMakePackageConfigHelpers)
set(CONFIG_INC_DIR ${INC_DIR})
set(CONFIG_LIB_DIR ${LIB_DIR})
if (VCPKG_TOOLCHAIN AND EXISTS "${CMAKE_INSTALL_PREFIX}/tools/srpc")
set(CONFIG_BIN_DIR ${CMAKE_INSTALL_PREFIX}/tools/srpc)
set(WITH_VCPKG_TOOLCHAIN 1 CACHE INTERNAL "build_with_vcpkg_toolchain")
message("Install with VCPKG toolchain. Dir ${CMAKE_INSTALL_PREFIX}/tools/srpc.")
else()
set(CONFIG_BIN_DIR ${BIN_DIR})
set(WITH_VCPKG_TOOLCHAIN 0 CACHE INTERNAL "build_with_vcpkg_toolchain")
endif()
configure_package_config_file(
${PROJECT_NAME}-config.cmake.in
${PROJECT_SOURCE_DIR}/${PROJECT_NAME}-config.cmake
INSTALL_DESTINATION ${CMAKE_CONFIG_INSTALL_DIR}
PATH_VARS CONFIG_INC_DIR CONFIG_LIB_DIR CONFIG_BIN_DIR
)
set(CONFIG_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR})
set(CONFIG_LIB_DIR ${CMAKE_INSTALL_LIBDIR})
set(CONFIG_BIN_DIR ${CMAKE_INSTALL_BINDIR})
configure_package_config_file(
${PROJECT_NAME}-config.cmake.in
${CMAKE_CONFIG_INSTALL_FILE}
INSTALL_DESTINATION ${CMAKE_CONFIG_INSTALL_DIR}
PATH_VARS CONFIG_INC_DIR CONFIG_LIB_DIR CONFIG_BIN_DIR
)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake
VERSION ${WORKFLOW_VERSION}
COMPATIBILITY AnyNewerVersion
)
install(
FILES ${CMAKE_CONFIG_INSTALL_FILE}
DESTINATION ${CMAKE_CONFIG_INSTALL_DIR}
COMPONENT devel
RENAME ${PROJECT_NAME}-config.cmake
)
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake
DESTINATION ${CMAKE_CONFIG_INSTALL_DIR}
COMPONENT devel
)
install(
FILES ${INCLUDE_HEADERS}
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}
COMPONENT devel
)
install(
FILES README.md
DESTINATION "${CMAKE_INSTALL_DOCDIR}-${PROJECT_VERSION}"
COMPONENT devel
)
sogou-srpc-ac65014/CMakeLists_Headers.txt 0000664 0000000 0000000 00000002772 15252174244 0020351 0 ustar 00root root 0000000 0000000 cmake_minimum_required(VERSION 3.6)
set(SRC_HEADERS
src/compress/rpc_compress.h
src/compress/rpc_compress_gzip.h
src/message/rpc_message.h
src/message/rpc_message_srpc.h
src/message/rpc_message_thrift.h
src/message/rpc_message_brpc.h
src/message/rpc_message_trpc.h
src/thrift/rpc_thrift_buffer.h
src/thrift/rpc_thrift_enum.h
src/thrift/rpc_thrift_idl.h
src/thrift/rpc_thrift_idl.inl
src/var/ckms_quantiles.h
src/var/time_window_quantiles.h
src/var/rpc_var.h
src/module/rpc_module.h
src/module/rpc_trace_module.h
src/module/rpc_metrics_module.h
src/module/rpc_filter.h
src/module/rpc_trace_filter.h
src/module/rpc_metrics_filter.h
src/rpc_basic.h
src/rpc_buffer.h
src/rpc_client.h
src/rpc_context.h
src/rpc_context.inl
src/rpc_global.h
src/rpc_options.h
src/rpc_server.h
src/rpc_service.h
src/rpc_task.inl
src/rpc_types.h
src/rpc_zero_copy_stream.h
src/rpc_define.h
)
if (NOT WIN32)
set(SRC_HEADERS
${SRC_HEADERS}
src/http/http_task.h
src/http/http_module.h
src/http/http_client.h
src/http/http_server.h
)
endif ()
if (NOT SNAPPY_INSTALLED)
set(SNAPPY_HEADERS
third_party/snappy/snappy.h
third_party/snappy/snappy-c.h
third_party/snappy/snappy-sinksource.h
third_party/snappy/snappy-stubs-public.h
)
endif ()
if (NOT LZ4_INSTALLED)
set(LZ4_HEADERS
third_party/lz4/lib/lz4.h
third_party/lz4/lib/lz4frame.h
)
endif ()
if (WITH_VCPKG_TOOLCHAIN)
set(INCLUDE_HEADERS ${SRC_HEADERS})
else()
set(INCLUDE_HEADERS ${SRC_HEADERS} ${SNAPPY_HEADERS} ${LZ4_HEADERS})
endif()
sogou-srpc-ac65014/CODE_OF_CONDUCT.md 0000664 0000000 0000000 00000006257 15252174244 0016757 0 ustar 00root root 0000000 0000000 # Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others’ private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at liyingxin@sogou-inc.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
sogou-srpc-ac65014/GNUmakefile 0000664 0000000 0000000 00000003253 15252174244 0016223 0 ustar 00root root 0000000 0000000 ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
ALL_TARGETS := all base check install preinstall package rpm clean tutorial example
MAKE_FILE := Makefile
DEFAULT_BUILD_DIR := build.cmake
BUILD_DIR := $(shell if [ -f $(MAKE_FILE) ]; then echo "."; else echo $(DEFAULT_BUILD_DIR); fi)
CMAKE3 := $(shell if which cmake3>/dev/null ; then echo cmake3; else echo cmake; fi;)
WORKFLOW := $(shell if [ -f "workflow/workflow-config.cmake.in" ]; then echo "Found"; else echo "NotFound"; fi)
.PHONY: $(ALL_TARGETS)
all: base
make -C $(BUILD_DIR) -f Makefile
base:
ifeq ("$(WORKFLOW)","Found")
make -C workflow
endif
mkdir -p $(BUILD_DIR)
ifeq ($(DEBUG),y)
cd $(BUILD_DIR) && $(CMAKE3) -D CMAKE_BUILD_TYPE=Debug $(ROOT_DIR)
else ifneq ("${INSTALL_PREFIX}install_prefix", "install_prefix")
cd $(BUILD_DIR) && $(CMAKE3) -DCMAKE_INSTALL_PREFIX:STRING=${INSTALL_PREFIX} $(ROOT_DIR)
else
cd $(BUILD_DIR) && $(CMAKE3) $(ROOT_DIR)
endif
tutorial: all
make -C tutorial
check: all
make -C test check
install preinstall package: base
mkdir -p $(BUILD_DIR)
cd $(BUILD_DIR) && $(CMAKE3) $(ROOT_DIR)
make -C $(BUILD_DIR) -f Makefile $@
rpm: package
ifneq ($(BUILD_DIR),.)
mv $(BUILD_DIR)/*.rpm ./
endif
clean:
ifeq ("$(WORKFLOW)","Found")
-make -C workflow clean
endif
-make -C test clean
-make -C tutorial clean
-make -C benchmark clean
rm -rf $(DEFAULT_BUILD_DIR)
rm -rf _include
rm -rf _lib
rm -rf _bin
rm -f SRCINFO SRCNUMVER SRCVERSION
rm -f ./*.rpm
rm -f src/message/*.pb.h src/message/*.pb.cc
find . -name CMakeCache.txt | xargs rm -f
find . -name Makefile | xargs rm -f
find . -name "*.cmake" | xargs rm -f
find . -name CMakeFiles | xargs rm -rf
sogou-srpc-ac65014/LICENSE 0000664 0000000 0000000 00000023676 15252174244 0015171 0 ustar 00root root 0000000 0000000
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
sogou-srpc-ac65014/README.md 0000664 0000000 0000000 00000020371 15252174244 0015430 0 ustar 00root root 0000000 0000000 [䏿–‡ç‰ˆå…¥å�£](README_cn.md)
### NEW !!! 👉 [SRPC tools : build Workflow and SRPC projects easily.](/tools/README.md)
## Introduction
**SRPC is an enterprise-level RPC system used by almost all online services in Sogou. It handles tens of billions of requests every day, covering searches, recommendations, advertising system, and other types of services.**
Bases on [Sogou C++ Workflow](https://github.com/sogou/workflow), it is an excellent choice for high-performance, low-latency, lightweight RPC systems. Contains AOP aspect-oriented modules that can report Metrics and Trace to a variety of cloud-native systems, such as OpenTelemetry, etc.
Its main features include:
* Support multiple RPC protocols: [`SRPC`](/tutorial/tutorial-01-srpc_pb_server.cc), [`bRPC`](/tutorial/tutorial-05-brpc_pb_server.cc), [`Thrift`](/tutorial/tutorial-07-thrift_thrift_server.cc), [`tRPC`](/tutorial/tutorial-11-trpc_pb_server.cc)
* Support multiple operating systems: `Linux`, `MacOS`, `Windows`
* Support several IDL formats: [`Protobuf`](/tutorial/echo_pb.proto), [`Thrift`](/tutorial/echo_thrift.thrift)
* Support several data formats transparently: `Json`, `Protobuf`, `Thrift Binary`
* Support several compression formats, the framework automatically decompresses: `gzip`, `zlib`, `snappy`, `lz4`
* Support several communication protocols transparently: `tcp`, `udp`, `sctp`, `tcp ssl`
* With [HTTP+JSON](/docs/docs-07-srpc-http.md), you can communicate with the client or server in any language
* Use it together with [Workflow Series and Parallel](/docs/docs-06-workflow.md) to facilitate the use of calculations and other asynchronous resources
* Perfectly compatible with all Workflow functions, such as name service, [upstream](docs/docs-06-workflow.md#3-upstream) and other components
* Report [Tracing](/docs/docs-08-tracing.md) to [OpenTelemetry](https://opentelemetry.io)
* Report [Metrics](/docs/docs-09-metrics.md) to [OpenTelemetry](https://opentelemetry.io) and [Prometheus](https://prometheus.io)
* [More features...](/docs/en/rpc.md)
## Installation
srpc has been packaged for Debian and Fedora. Therefore, we can install it from source code or from the package in the system.
reference: [Linux, MacOS, Windows Installation and Compilation Guide](docs/en/installation.md)
## Quick Start
Let's quickly learn how to use it in a few steps.
For more detailed usage, please refer to [Documents](/docs) and [Tutorial](/tutorial).
#### 1\. example.proto
~~~proto
syntax = "proto3";// You can use either proto2 or proto3. Both are supported by srpc
message EchoRequest {
string message = 1;
string name = 2;
};
message EchoResponse {
string message = 1;
};
service Example {
rpc Echo(EchoRequest) returns (EchoResponse);
};
~~~
#### 2\. generate code
~~~sh
protoc example.proto --cpp_out=./ --proto_path=./
srpc_generator protobuf ./example.proto ./
~~~
#### 3\. server.cc
~~~cpp
#include
#include
#include "example.srpc.h"
using namespace srpc;
class ExampleServiceImpl : public Example::Service
{
public:
void Echo(EchoRequest *request, EchoResponse *response, RPCContext *ctx) override
{
response->set_message("Hi, " + request->name());
printf("get_req:\n%s\nset_resp:\n%s\n",
request->DebugString().c_str(), response->DebugString().c_str());
}
};
void sig_handler(int signo) { }
int main()
{
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
SRPCServer server_tcp;
SRPCHttpServer server_http;
ExampleServiceImpl impl;
server_tcp.add_service(&impl);
server_http.add_service(&impl);
server_tcp.start(1412);
server_http.start(8811);
getchar(); // press "Enter" to end.
server_http.stop();
server_tcp.stop();
return 0;
}
~~~
#### 4\. client.cc
~~~cpp
#include
#include "example.srpc.h"
using namespace srpc;
int main()
{
Example::SRPCClient client("127.0.0.1", 1412);
EchoRequest req;
req.set_message("Hello, srpc!");
req.set_name("workflow");
client.Echo(&req, [](EchoResponse *response, RPCContext *ctx) {
if (ctx->success())
printf("%s\n", response->DebugString().c_str());
else
printf("status[%d] error[%d] errmsg:%s\n",
ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg());
});
getchar(); // press "Enter" to end.
return 0;
}
~~~
#### 5\. make
These compile commands are only for Linux system. On other system, complete cmake in [tutorial](/tutorial) is recommended.
~~~sh
g++ -o server server.cc example.pb.cc -std=c++11 -lsrpc
g++ -o client client.cc example.pb.cc -std=c++11 -lsrpc
~~~
#### 6\. run
Terminal 1:
~~~sh
./server
~~~
Terminal 2:
~~~sh
./client
~~~
We can also use CURL to post Http request:
~~~sh
curl 127.0.0.1:8811/Example/Echo -H 'Content-Type: application/json' -d '{message:"from curl",name:"CURL"}'
~~~
Output of Terminal 1:
~~~sh
get_req:
message: "Hello, srpc!"
name: "workflow"
set_resp:
message: "Hi, workflow"
get_req:
message: "from curl"
name: "CURL"
set_resp:
message: "Hi, CURL"
~~~
Output of Terminal 2:
~~~sh
message: "Hi, workflow"
~~~
Output of CURL:
~~~sh
{"message":"Hi, CURL"}
~~~
## Benchmark
* CPU 2-chip/8-core/32-processor Intel(R) Xeon(R) CPU E5-2630 v3 @2.40GHz
* Memory all 128G
* 10 Gigabit Ethernet
* BAIDU brpc-client in pooled (connection pool) mode
#### QPS at cross-machine single client→ single server under different concurrency
~~~
Client = 1
ClientThread = 64, 128, 256, 512, 1024
RequestSize = 32
Duration = 20s
Server = 1
ServerIOThread = 16
ServerHandlerThread = 16
~~~

#### QPS at cross-machine multi-client→ single server under different client processes
~~~
Client = 1, 2, 4, 8, 16
ClientThread = 32
RequestSize = 32
Duration = 20s
Server = 1
ServerIOThread = 16
ServerHandlerThread = 16
~~~

#### QPS at same-machine single client→ single server under different concurrency
~~~
Client = 1
ClientThread = 1, 2, 4, 8, 16, 32, 64, 128, 256
RequestSize = 1024
Duration = 20s
Server = 1
ServerIOThread = 16
ServerHandlerThread = 16
~~~

#### QPS at same-machine single client→ single server under different request sizes
~~~
Client = 1
ClientThread = 100
RequestSize = 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768
Duration = 20s
Server = 1
ServerIOThread = 16
ServerHandlerThread = 16
~~~

#### Latency CDF for fixed QPS at same-machine single client→ single server
~~~
Client = 1
ClientThread = 50
ClientQPS = 10000
RequestSize = 1024
Duration = 20s
Server = 1
ServerIOThread = 16
ServerHandlerThread = 16
Outlier = 1%
~~~

#### Latency CDF for fixed QPS at cross-machine multi-client→ single server
~~~
Client = 32
ClientThread = 16
ClientQPS = 2500
RequestSize = 512
Duration = 20s
Server = 1
ServerIOThread = 16
ServerHandlerThread = 16
Outlier = 1%
~~~

## Contact
* **Email** - **[liyingxin@sogou-inc.com](mailto:liyingxin@sogou-inc.com)** - main author
* **Issue** - You are very welcome to post questions to [issues](https://github.com/sogou/srpc/issues) list.
* **QQ** - Group number: ``618773193``
sogou-srpc-ac65014/README_cn.md 0000664 0000000 0000000 00000017546 15252174244 0016122 0 ustar 00root root 0000000 0000000 [English version](README.md) | [Wiki:SRPC架构介�](/docs/wiki.md)
### NEW !!! 👉 [SRPC tools : ä¸€ä¸ªå¸®ä½ å¿«é€Ÿæž„å»ºWorkflowå’ŒSRPC项目的å°�工具.](/tools/README_cn.md)
## Introduction
**SRPC是全�狗业务线上使用的�业级RPC系统,目��天承载上百亿的请求�,涵盖�广推�其他类型业务。**
底层基于[Sogou C++ Workflow](https://github.com/sogou/workflow),是高性能ã€�低延迟ã€�è½»é‡�级RPC系统的æž�ä½³é€‰æ‹©ã€‚ä¸”åŠ å…¥äº†AOPé�¢å�‘切é�¢çš„æ¨¡å�—,支æŒ�Metricsï¼ˆç›‘æŽ§æŒ‡æ ‡ï¼‰å’ŒTrace(链路追踪)功能,å�¯ä¸ŠæŠ¥åˆ°å¤šç§�云原生系统,包括OpenTelemetry。
主�功能和示例:
* 支�多�RPC�议:[`SRPC`](/tutorial/tutorial-01-srpc_pb_server.cc)�[`bRPC`](/tutorial/tutorial-05-brpc_pb_server.cc)�[`Thrift`](/tutorial/tutorial-07-thrift_thrift_server.cc)�[`tRPC`](/tutorial/tutorial-11-trpc_pb_server.cc)
* 支�多��作系统:`Linux` / `MacOS` / `Windows`
* 支æŒ�多ç§�IDLæ ¼å¼�:[`Protobuf`](/tutorial/echo_pb.proto)ã€�[`Thrift`](/tutorial/echo_thrift.thrift)
* 支�多�数�布局,使用上完全�明:`Json`�`Protobuf`�`Thrift Binary`
* 支�多�压缩,框架自动解压:`gzip`�`zlib`�`snappy`�`lz4`
* 支�多�通信�议:`tcp`, `udp`, `sctp`, `tcp ssl`
* å�¯ä»¥é€šè¿‡[http+json实现跨è¯è¨€](/docs/docs-07-srpc-http.md)
* å�¯ä»¥ä½¿ç”¨[Workflow串并è�”任务æµ�](/docs/docs-06-workflow.md),打通计算å�Šå…¶ä»–异æ¥èµ„æº�
* 完美兼容Workflow所有功能:命å��æœ�务体系ã€�[upstream](docs/docs-06-workflow.md#3-upstream)ã€�其他组件ç‰
* 链路追踪功能:上报[Tracing](/docs/docs-08-tracing.md)到[OpenTelemetry](https://opentelemetry.io)
* 监控功能:上报[Metrics](/docs/docs-09-metrics.md)到OpenTelemetry和[Prometheus](https://prometheus.io)
## Installation
SRPC是Debian Linuxå’ŒFedoraçš„è‡ªå¸¦å®‰è£…åŒ…ï¼Œå› æ¤å�¯ä»¥é€šè¿‡æº�ç �安装,和系统自带安装包安装。
具体�考:[Linux�MacOS�Windows安装和编译指�](docs/installation.md)
## Quick Start
æˆ‘ä»¬é€šè¿‡å‡ ä¸ªæ¥éª¤å¿«é€Ÿäº†è§£å¦‚何使用。
更详细的用法�以�考[更多文档](/docs),和[官方教程](/tutorial)。
#### 1. example.proto
~~~proto
syntax = "proto3";//这里proto2和proto3都�以,srpc都支�
message EchoRequest {
string message = 1;
string name = 2;
};
message EchoResponse {
string message = 1;
};
service Example {
rpc Echo(EchoRequest) returns (EchoResponse);
};
~~~
#### 2. generate code
~~~sh
protoc example.proto --cpp_out=./ --proto_path=./
srpc_generator protobuf ./example.proto ./
~~~
#### 3. server.cc
~~~cpp
#include
#include
#include "example.srpc.h"
using namespace srpc;
class ExampleServiceImpl : public Example::Service
{
public:
void Echo(EchoRequest *request, EchoResponse *response, RPCContext *ctx) override
{
response->set_message("Hi, " + request->name());
printf("get_req:\n%s\nset_resp:\n%s\n",
request->DebugString().c_str(), response->DebugString().c_str());
}
};
void sig_handler(int signo) { }
int main()
{
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
SRPCServer server_tcp;
SRPCHttpServer server_http;
ExampleServiceImpl impl;
server_tcp.add_service(&impl);
server_http.add_service(&impl);
server_tcp.start(1412);
server_http.start(8811);
getchar(); // press "Enter" to end.
server_http.stop();
server_tcp.stop();
return 0;
}
~~~
#### 4. client.cc
~~~cpp
#include
#include "example.srpc.h"
using namespace srpc;
int main()
{
Example::SRPCClient client("127.0.0.1", 1412);
EchoRequest req;
req.set_message("Hello, srpc!");
req.set_name("workflow");
client.Echo(&req, [](EchoResponse *response, RPCContext *ctx) {
if (ctx->success())
printf("%s\n", response->DebugString().c_str());
else
printf("status[%d] error[%d] errmsg:%s\n",
ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg());
});
getchar(); // press "Enter" to end.
return 0;
}
~~~
#### 5. make
在Linux系统下的编译示例如下,其他平�建议到[tutorial](/tutorial)目录下使用完整的cmake文件�助解决编译�赖问题。
~~~sh
g++ -o server server.cc example.pb.cc -std=c++11 -lsrpc
g++ -o client client.cc example.pb.cc -std=c++11 -lsrpc
~~~
#### 6. run
终端1:
~~~sh
./server
~~~
终端2:
~~~sh
./client
~~~
也�以用CURL��http请求:
~~~sh
curl 127.0.0.1:8811/Example/Echo -H 'Content-Type: application/json' -d '{message:"from curl",name:"CURL"}'
~~~
终端1输出:
~~~sh
get_req:
message: "Hello, srpc!"
name: "workflow"
set_resp:
message: "Hi, workflow"
get_req:
message: "from curl"
name: "CURL"
set_resp:
message: "Hi, CURL"
~~~
终端2输出:
~~~sh
message: "Hi, workflow"
~~~
CURL收到的回�:
~~~sh
{"message":"Hi, CURL"}
~~~
## Benchmark
* CPU 2-chip/8-core/32-processor Intel(R) Xeon(R) CPU E5-2630 v3 @2.40GHz
* Memory all 128G
* 10 Gigabit Ethernet
* BAIDU brpc-clientä½¿ç”¨è¿žæŽ¥æ± pooled模å¼�
#### 跨机�client→�server在��并�的QPS
~~~
Client = 1
ClientThread = 64, 128, 256, 512, 1024
RequestSize = 32
Duration = 20s
Server = 1
ServerIOThread = 16
ServerHandlerThread = 16
~~~

#### 跨机多client→�server在��client进程数的QPS
~~~
Client = 1, 2, 4, 8, 16
ClientThread = 32
RequestSize = 32
Duration = 20s
Server = 1
ServerIOThread = 16
ServerHandlerThread = 16
~~~

#### �机�client→�server在��并�下的QPS
~~~
Client = 1
ClientThread = 1, 2, 4, 8, 16, 32, 64, 128, 256
RequestSize = 1024
Duration = 20s
Server = 1
ServerIOThread = 16
ServerHandlerThread = 16
~~~

#### �机�client→�server在��请求大�下的QPS
~~~
Client = 1
ClientThread = 100
RequestSize = 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768
Duration = 20s
Server = 1
ServerIOThread = 16
ServerHandlerThread = 16
~~~

#### �机�client→�server在固定QPS下的延时CDF
~~~
Client = 1
ClientThread = 50
ClientQPS = 10000
RequestSize = 1024
Duration = 20s
Server = 1
ServerIOThread = 16
ServerHandlerThread = 16
Outiler = 1%
~~~

#### 跨机多client→�server在固定QPS下的延时CDF
~~~
Client = 32
ClientThread = 16
ClientQPS = 2500
RequestSize = 512
Duration = 20s
Server = 1
ServerIOThread = 16
ServerHandlerThread = 16
Outiler = 1%
~~~

## 与我们�系
* **Email** - **[liyingxin@sogou-inc.com](mailto:liyingxin@sogou-inc.com)** - 主�作者
* **Issue** - 使用ä¸çš„任何问题都欢迎到[issues](https://github.com/sogou/srpc/issues)进行交æµ�。
* **QQ** - 群�: ``618773193``
sogou-srpc-ac65014/WORKSPACE 0000664 0000000 0000000 00000002237 15252174244 0015433 0 ustar 00root root 0000000 0000000 workspace(name = "srpc")
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "rules_proto",
sha256 = "d8992e6eeec276d49f1d4e63cfa05bbed6d4a26cfe6ca63c972827a0d141ea3b",
strip_prefix = "rules_proto-cfdc2fa31879c0aebe31ce7702b1a9c8a4be02d2",
urls = [
"https://github.com/bazelbuild/rules_proto/archive/cfdc2fa31879c0aebe31ce7702b1a9c8a4be02d2.tar.gz",
],
)
load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")
rules_proto_dependencies()
rules_proto_toolchains()
git_repository(
name = "workflow",
commit = "3d5739960ac34d6164d5fec0abf268e83d72fe93",
remote = "https://github.com/sogou/workflow.git")
new_git_repository(
name = "lz4",
build_file = "@//third_party:lz4.BUILD",
tag = "v1.9.3",
remote = "https://github.com/lz4/lz4.git")
new_git_repository(
name = "snappy",
build_file = "@//third_party:snappy.BUILD",
tag = "1.1.9",
remote = "https://github.com/google/snappy.git")
sogou-srpc-ac65014/benchmark/ 0000775 0000000 0000000 00000000000 15252174244 0016100 5 ustar 00root root 0000000 0000000 sogou-srpc-ac65014/benchmark/CMakeLists.txt 0000664 0000000 0000000 00000007031 15252174244 0020641 0 ustar 00root root 0000000 0000000 cmake_minimum_required(VERSION 3.6)
set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "build type")
project(srpc_benchmark
LANGUAGES C CXX
)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR})
if (NOT "$ENV{LIBRARY_PATH}" STREQUAL "")
string(REPLACE ":" ";" LIBRARY_PATH $ENV{LIBRARY_PATH})
set(CMAKE_SYSTEM_LIBRARY_PATH ${LIBRARY_PATH};${CMAKE_SYSTEM_LIBRARY_PATH})
endif ()
if (NOT "$ENV{CPLUS_INCLUDE_PATH}" STREQUAL "")
string(REPLACE ":" ";" INCLUDE_PATH $ENV{CPLUS_INCLUDE_PATH})
set(CMAKE_SYSTEM_INCLUDE_PATH ${INCLUDE_PATH};${CMAKE_SYSTEM_INCLUDE_PATH})
endif ()
find_package(OpenSSL REQUIRED)
set(protobuf_MODULE_COMPATIBLE ON CACHE BOOL "")
if (WIN32)
find_package(Protobuf CONFIG REQUIRED)
find_library(LZ4_LIBRARY NAMES lz4)
find_package(Snappy CONFIG REQUIRED)
else ()
find_package(Protobuf REQUIRED)
endif ()
if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/lz4/lib/lz4.h")
set(LZ4_LIB lz4)
endif ()
if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/snappy/cmake")
set(SNAPPY_LIB snappy)
endif ()
if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/workflow/workflow-config.cmake.in")
find_package(Workflow REQUIRED CONFIG HINTS ../workflow)
endif ()
find_package(srpc REQUIRED CONFIG HINTS ..)
include_directories(
${OPENSSL_INCLUDE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
${Protobuf_INCLUDE_DIR}
${WORKFLOW_INCLUDE_DIR}
${SRPC_INCLUDE_DIR}
)
if (WIN32)
link_directories(${SRPC_LIB_DIR} ${WORKFLOW_LIB_DIR} ${Protobuf_LIB_DIR})
set(SRPC_GEN_PROGRAM ${SRPC_BIN_DIR}/Debug/srpc_generator.exe)
else ()
get_filename_component(Protobuf_LIB_DIR ${Protobuf_LIBRARY} DIRECTORY)
link_directories(${SRPC_LIB_DIR} ${WORKFLOW_LIB_DIR} ${Protobuf_LIB_DIR})
set(SRPC_GEN_PROGRAM ${SRPC_BIN_DIR}/srpc_generator)
endif ()
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS benchmark_pb.proto)
add_custom_target(
BENCHMARK_GEN ALL
COMMAND ${SRPC_GEN_PROGRAM} ${PROJECT_SOURCE_DIR}/benchmark_pb.proto ${PROJECT_SOURCE_DIR}
COMMAND ${SRPC_GEN_PROGRAM} ${PROJECT_SOURCE_DIR}/benchmark_thrift.thrift ${PROJECT_SOURCE_DIR}
COMMENT "srpc generator..."
)
if (WIN32)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP /wd4200")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4200 /Zc:__cplusplus /std:c++17")
else ()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -fPIC -pipe -std=gnu90")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC -pipe -std=c++17 -fno-exceptions")
endif ()
if (APPLE)
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a" ${CMAKE_FIND_LIBRARY_SUFFIXES})
find_library(Workflow_LIB workflow HINTS ../workflow/_lib)
find_library(Srpc_LIB srpc HINTS ../_lib)
set(SRPC_LIB
${Srpc_LIB}
${Workflow_LIB}
pthread
OpenSSL::SSL
OpenSSL::Crypto
protobuf
z
)
elseif (WIN32)
set(SRPC_LIB
srpc
workflow
ws2_32
wsock32
OpenSSL::SSL
OpenSSL::Crypto
protobuf::libprotobuf
ZLIB::ZLIB
Snappy::snappy
${LZ4_LIBRARY}
)
else ()
set(SRPC_LIB
srpc
workflow
pthread
OpenSSL::SSL
OpenSSL::Crypto
protobuf
z
${SNAPPY_LIB}
${LZ4_LIB}
)
endif ()
add_executable(server server.cc ${PROTO_SRCS} ${PROTO_HDRS})
target_link_libraries(server ${SRPC_LIB})
add_dependencies(server BENCHMARK_GEN)
add_executable(client client.cc ${PROTO_SRCS} ${PROTO_HDRS})
target_link_libraries(client ${SRPC_LIB})
add_dependencies(client BENCHMARK_GEN)
add_executable(client_cdf client_cdf.cc ${PROTO_SRCS} ${PROTO_HDRS})
target_link_libraries(client_cdf ${SRPC_LIB})
add_dependencies(client_cdf BENCHMARK_GEN)
add_executable(proxy proxy.cc ${PROTO_SRCS} ${PROTO_HDRS})
target_link_libraries(proxy ${SRPC_LIB})
add_dependencies(proxy BENCHMARK_GEN)
sogou-srpc-ac65014/benchmark/GNUmakefile 0000664 0000000 0000000 00000001773 15252174244 0020162 0 ustar 00root root 0000000 0000000 ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
ALL_TARGETS := all clean
MAKE_FILE := Makefile
DEFAULT_BUILD_DIR := build.cmake
BUILD_DIR := $(shell if [ -f $(MAKE_FILE) ]; then echo "."; else echo $(DEFAULT_BUILD_DIR); fi)
CMAKE3 := $(shell if which cmake3>/dev/null ; then echo cmake3; else echo cmake; fi;)
.PHONY: $(ALL_TARGETS)
all:
mkdir -p $(BUILD_DIR)
ifeq ($(DEBUG),y)
cd $(BUILD_DIR) && $(CMAKE3) -D CMAKE_BUILD_TYPE=Debug $(ROOT_DIR)
else ifneq ("${Workflow_DIR}workflow", "workflow")
cd $(BUILD_DIR) && $(CMAKE3) -DWorkflow_DIR:STRING=${Workflow_DIR} $(ROOT_DIR)
else
cd $(BUILD_DIR) && $(CMAKE3) $(ROOT_DIR)
endif
make -C $(BUILD_DIR) -f Makefile
clean:
ifeq ($(MAKE_FILE), $(wildcard $(MAKE_FILE)))
-make -f Makefile clean
else ifeq ($(DEFAULT_BUILD_DIR), $(wildcard $(DEFAULT_BUILD_DIR)))
-make -C $(DEFAULT_BUILD_DIR) clean
endif
rm -rf $(DEFAULT_BUILD_DIR)
#g++ -o thrift_server thrift_server.cc gen-cpp/*.cpp -O2 -g -lthrift -lthriftnb -levent -lpthread -std=c++11
sogou-srpc-ac65014/benchmark/benchmark_pb.proto 0000664 0000000 0000000 00000000324 15252174244 0021577 0 ustar 00root root 0000000 0000000 syntax="proto3";
message EmptyPBMsg { }
message FixLengthPBMsg { bytes msg = 1; }
service BenchmarkPB
{
rpc echo_pb(FixLengthPBMsg) returns (EmptyPBMsg);
rpc slow_pb(FixLengthPBMsg) returns (EmptyPBMsg);
}
sogou-srpc-ac65014/benchmark/benchmark_thrift.thrift 0000664 0000000 0000000 00000000137 15252174244 0022635 0 ustar 00root root 0000000 0000000 service BenchmarkThrift
{
void echo_thrift(1:string msg);
void slow_thrift(1:string msg);
}
sogou-srpc-ac65014/benchmark/client.cc 0000664 0000000 0000000 00000012042 15252174244 0017664 0 ustar 00root root 0000000 0000000 #include
#include
#include
#include
#include
#include
#include "benchmark_pb.srpc.h"
#include "benchmark_thrift.srpc.h"
using namespace srpc;
#define TEST_SECOND 20
#define GET_CURRENT_NS std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()
std::atomic query_count(0);
std::atomic success_count(0);
std::atomic error_count(0);
std::atomic latency_sum(0);
volatile bool stop_flag = false;
int PARALLEL_NUMBER;
std::string request_msg;
template
static void do_echo_pb(CLIENT *client)
{
FixLengthPBMsg req;
req.set_msg(request_msg);
int64_t ns_st = GET_CURRENT_NS;
++query_count;
client->echo_pb(&req, [client, ns_st](EmptyPBMsg *response, RPCContext *ctx) {
if (ctx->success())
{
//printf("%s\n", ctx->get_remote_ip().c_str());
latency_sum += GET_CURRENT_NS - ns_st;
++success_count;
}
else
{
printf("status[%d] error[%d] errmsg:%s\n",
ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg());
++error_count;
}
if (!stop_flag)
do_echo_pb(client);
//printf("echo done. seq_id=%d\n", ctx->get_task_seq());
});
}
template
static void do_echo_thrift(CLIENT *client)
{
BenchmarkThrift::echo_thriftRequest req;
req.msg = request_msg;
int64_t ns_st = GET_CURRENT_NS;
++query_count;
client->echo_thrift(&req, [client, ns_st](BenchmarkThrift::echo_thriftResponse *response, RPCContext *ctx) {
if (ctx->success())
{
//printf("%s\n", ctx->get_remote_ip().c_str());
latency_sum += GET_CURRENT_NS - ns_st;
++success_count;
}
else
{
printf("status[%d] error[%d] errmsg:%s \n",
ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg());
++error_count;
}
if (!stop_flag)
do_echo_thrift(client);
//printf("echo done. seq_id=%d\n", ctx->get_task_seq());
});
}
int main(int argc, char* argv[])
{
GOOGLE_PROTOBUF_VERIFY_VERSION;
if (argc != 7)
{
fprintf(stderr, "Usage: %s \n", argv[0]);
abort();
}
WFGlobalSettings setting = GLOBAL_SETTINGS_DEFAULT;
setting.endpoint_params.max_connections = 2048;
setting.poller_threads = 16;
setting.handler_threads = 16;
WORKFLOW_library_init(&setting);
RPCClientParams client_params = RPC_CLIENT_PARAMS_DEFAULT;
client_params.task_params.keep_alive_timeout = -1;
client_params.host = argv[1];
client_params.port = atoi(argv[2]);
std::string server_type = argv[3];
std::string idl_type = argv[4];
PARALLEL_NUMBER = atoi(argv[5]);
int REQUEST_BYTES = atoi(argv[6]);
request_msg.resize(REQUEST_BYTES, 'r');
//for (int i = 0; i < REQUEST_BYTES; i++)
// request_msg[i] = (unsigned char)(rand() % 256);
int64_t start = GET_CURRENT_MS();
if (server_type == "srpc")
{
if (idl_type == "pb")
{
auto *client = new BenchmarkPB::SRPCClient(&client_params);
for (int i = 0; i < PARALLEL_NUMBER; i++)
do_echo_pb(client);
}
else if (idl_type == "thrift")
{
auto *client = new BenchmarkThrift::SRPCClient(&client_params);
for (int i = 0; i < PARALLEL_NUMBER; i++)
do_echo_thrift(client);
}
else
abort();
}
else if (server_type == "brpc")
{
auto *client = new BenchmarkPB::BRPCClient(&client_params);
for (int i = 0; i < PARALLEL_NUMBER; i++)
{
if (idl_type == "pb")
do_echo_pb(client);
else if (idl_type == "thrift")
abort();
else
abort();
}
}
else if (server_type == "thrift")
{
auto *client = new BenchmarkThrift::ThriftClient(&client_params);
for (int i = 0; i < PARALLEL_NUMBER; i++)
{
if (idl_type == "pb")
abort();
else if (idl_type == "thrift")
do_echo_thrift(client);
else
abort();
}
}
else if (server_type == "srpc_http")
{
if (idl_type == "pb")
{
auto *client = new BenchmarkPB::SRPCHttpClient(&client_params);
for (int i = 0; i < PARALLEL_NUMBER; i++)
do_echo_pb(client);
}
else if (idl_type == "thrift")
{
auto *client = new BenchmarkThrift::SRPCHttpClient(&client_params);
for (int i = 0; i < PARALLEL_NUMBER; i++)
do_echo_thrift(client);
}
else
abort();
}
else if (server_type == "thrift_http")
{
auto *client = new BenchmarkThrift::ThriftHttpClient(&client_params);
for (int i = 0; i < PARALLEL_NUMBER; i++)
{
if (idl_type == "pb")
abort();
else if (idl_type == "thrift")
do_echo_thrift(client);
else
abort();
}
}
else
abort();
std::this_thread::sleep_for(std::chrono::seconds(TEST_SECOND));
stop_flag = true;
int64_t end = GET_CURRENT_MS();
int tot = query_count;
int s = success_count;
int e = error_count;
int64_t l = latency_sum;
fprintf(stderr, "\nquery\t%d\ttimes, %d success, %d error.\n", tot, s, e);
fprintf(stderr, "total\t%.3lf\tseconds\n", (end - start) / 1000.0);
fprintf(stderr, "qps=%.0lf\n", tot * 1000.0 / (end - start));
fprintf(stderr, "latency=%.0lfus\n", s > 0 ? l * 1.0 / s / 1000 : 0);
std::this_thread::sleep_for(std::chrono::seconds(1));
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
sogou-srpc-ac65014/benchmark/client_cdf.cc 0000664 0000000 0000000 00000017103 15252174244 0020503 0 ustar 00root root 0000000 0000000 #include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "benchmark_pb.srpc.h"
#include "benchmark_thrift.srpc.h"
using namespace srpc;
#define TEST_SECOND 20
#define GET_CURRENT_NS std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()
std::atomic query_count(0);
std::atomic slow_count(0);
std::atomic success_count(0);
std::atomic error_count(0);
//std::atomic latency_sum(0);
std::vector> latency_lists;
volatile bool stop_flag = false;
int PARALLEL_NUMBER;
std::string request_msg;
int QPS;
template
static void do_echo_pb(CLIENT *client, int idx)
{
std::mutex mutex;
auto& latency_list = latency_lists[idx];
FixLengthPBMsg req;
req.set_msg(request_msg);
int usleep_gap = 1000000 / QPS * PARALLEL_NUMBER;
while (!stop_flag)
{
int64_t ns_st = GET_CURRENT_NS;
if (++query_count % 100 > 0)
{
client->echo_pb(&req, [ns_st, &latency_list, &mutex](EmptyPBMsg *response, RPCContext *ctx) {
if (ctx->success())
{
//printf("%s\n", ctx->get_remote_ip().c_str());
++success_count;
//latency_sum += GET_CURRENT_NS - ns_st;
mutex.lock();
latency_list.emplace_back(GET_CURRENT_NS - ns_st);
mutex.unlock();
}
else
{
printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg());
++error_count;
}
//printf("echo done. seq_id=%d\n", ctx->get_task_seq());
});
}
else
{
client->slow_pb(&req, [](EmptyPBMsg *response, RPCContext *ctx) {
slow_count++;
if (!ctx->success())
printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg());
});
}
std::this_thread::sleep_for(std::chrono::microseconds(usleep_gap));
}
}
template
static void do_echo_thrift(CLIENT *client, int idx)
{
std::mutex mutex;
auto& latency_list = latency_lists[idx];
BenchmarkThrift::echo_thriftRequest req;
req.msg = request_msg;
BenchmarkThrift::slow_thriftRequest slow_req;
slow_req.msg = request_msg;
int usleep_gap = 1000000 / QPS * PARALLEL_NUMBER;
while (!stop_flag)
{
int64_t ns_st = GET_CURRENT_NS;
if (++query_count % 100 > 0)
{
client->echo_thrift(&req, [ns_st, &latency_list, &mutex](BenchmarkThrift::echo_thriftResponse *response, RPCContext *ctx) {
if (ctx->success())
{
//printf("%s\n", ctx->get_remote_ip().c_str());
++success_count;
//latency_sum += GET_CURRENT_NS - ns_st;
mutex.lock();
latency_list.emplace_back(GET_CURRENT_NS - ns_st);
mutex.unlock();
}
else
{
printf("status[%d] error[%d] errmsg:%s \n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg());
++error_count;
}
//printf("echo done. seq_id=%d\n", ctx->get_task_seq());
});
}
else
{
client->slow_thrift(&slow_req, [](BenchmarkThrift::slow_thriftResponse *response, RPCContext *ctx) {
slow_count++;
if (!ctx->success())
printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg());
});
}
std::this_thread::sleep_for(std::chrono::microseconds(usleep_gap));
}
}
int main(int argc, char* argv[])
{
GOOGLE_PROTOBUF_VERIFY_VERSION;
if (argc != 8)
{
fprintf(stderr, "Usage: %s \n", argv[0]);
abort();
}
WFGlobalSettings setting = GLOBAL_SETTINGS_DEFAULT;
setting.endpoint_params.max_connections = 2048;
setting.poller_threads = 16;
setting.handler_threads = 16;
WORKFLOW_library_init(&setting);
RPCClientParams client_params = RPC_CLIENT_PARAMS_DEFAULT;
client_params.task_params.keep_alive_timeout = -1;
client_params.host = argv[1];
client_params.port = atoi(argv[2]);
std::string server_type = argv[3];
std::string idl_type = argv[4];
PARALLEL_NUMBER = atoi(argv[5]);
int REQUEST_BYTES = atoi(argv[6]);
QPS = atoi(argv[7]);
request_msg.resize(REQUEST_BYTES, 'r');
//for (int i = 0; i < REQUEST_BYTES; i++)
// request_msg[i] = (unsigned char)(rand() % 256);
latency_lists.resize(PARALLEL_NUMBER);
std::vector th;
int64_t start = GET_CURRENT_MS();
if (server_type == "srpc")
{
if (idl_type == "pb")
{
auto *client = new BenchmarkPB::SRPCClient(&client_params);
for (int i = 0; i < PARALLEL_NUMBER; i++)
th.push_back(new std::thread(do_echo_pb, client, i));
}
else if (idl_type == "thrift")
{
auto *client = new BenchmarkThrift::SRPCClient(&client_params);
for (int i = 0; i < PARALLEL_NUMBER; i++)
th.push_back(new std::thread(do_echo_thrift, client, i));
}
else
abort();
}
else if (server_type == "brpc")
{
auto *client = new BenchmarkPB::BRPCClient(&client_params);
for (int i = 0; i < PARALLEL_NUMBER; i++)
{
if (idl_type == "pb")
th.push_back(new std::thread(do_echo_pb, client, i));
else if (idl_type == "thrift")
abort();
else
abort();
}
}
else if (server_type == "thrift")
{
auto *client = new BenchmarkThrift::ThriftClient(&client_params);
for (int i = 0; i < PARALLEL_NUMBER; i++)
{
if (idl_type == "pb")
abort();
else if (idl_type == "thrift")
th.push_back(new std::thread(do_echo_thrift, client, i));
else
abort();
}
}
else if (server_type == "srpc_http")
{
if (idl_type == "pb")
{
auto * client = new BenchmarkPB::SRPCHttpClient(&client_params);
for (int i = 0; i < PARALLEL_NUMBER; i++)
th.push_back(new std::thread(do_echo_pb, client, i));
}
else if (idl_type == "thrift")
{
auto *client = new BenchmarkThrift::SRPCHttpClient(&client_params);
for (int i = 0; i < PARALLEL_NUMBER; i++)
th.push_back(new std::thread(do_echo_thrift, client, i));
}
else
abort();
}
else if (server_type == "thrift_http")
{
auto *client = new BenchmarkThrift::ThriftHttpClient(&client_params);
for (int i = 0; i < PARALLEL_NUMBER; i++)
{
if (idl_type == "pb")
abort();
else if (idl_type == "thrift")
th.push_back(new std::thread(do_echo_thrift, client, i));
else
abort();
}
}
else
abort();
std::this_thread::sleep_for(std::chrono::seconds(TEST_SECOND));
stop_flag = true;
for (auto *t : th)
{
t->join();
delete t;
}
int64_t end = GET_CURRENT_MS();
int tot = query_count - slow_count;
int s = success_count;
int e = error_count;
int64_t l = 0;//latency_sum;
std::vector all_lc;
for (const auto& list : latency_lists)
{
for (auto v : list)
{
//fprintf(stderr, "%lld\n", (long long int)v);
l += v;
}
all_lc.insert(all_lc.end(), list.begin(), list.end());
}
sort(all_lc.begin(), all_lc.end());
for (double r = 0.950; r <= 0.999; r += 0.001)
{
double d = r * all_lc.size();
int idx = (int)(d + 1.0e-8);
if (fabs(d - int(d)) > 1.0e-8)
idx++;
printf("%.3lf %lld\n", r, (long long int)all_lc[idx - 1]/1000);
}
//printf("%.3lf %lld\n", 1.0, (long long int)all_lc[all_lc.size() - 1]/1000);
fprintf(stderr, "\nquery\t%d\ttimes, %d success, %d error.\n", tot, s, e);
fprintf(stderr, "total\t%.3lf\tseconds\n", (end - start) / 1000.0);
fprintf(stderr, "qps=%.0lf\n", tot * 1000.0 / (end - start));
fprintf(stderr, "latency=%.0lfus\n", s > 0 ? l * 1.0 / s / 1000 : 0);
std::this_thread::sleep_for(std::chrono::seconds(1));
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
sogou-srpc-ac65014/benchmark/proxy.cc 0000664 0000000 0000000 00000012477 15252174244 0017603 0 ustar 00root root 0000000 0000000 #include
#include
#include "benchmark_pb.srpc.h"
#include "benchmark_thrift.srpc.h"
#include "workflow/WFFacilities.h"
using namespace srpc;
std::atomic query_count(0); // per_second
std::atomic last_timestamp(0L);
//volatile bool stop_flag = false;
int max_qps = 0;
long long total_count = 0;
std::string remote_host;
unsigned short remote_port;
WFFacilities::WaitGroup wait_group(1);
inline void collect_qps()
{
int64_t ms_timestamp = GET_CURRENT_MS();
++query_count;
if (ms_timestamp / 1000 > last_timestamp)
{
last_timestamp = ms_timestamp / 1000;
int count = query_count;
query_count = 0;
total_count += count;
if (count > max_qps)
max_qps = count;
long long ts = ms_timestamp;
fprintf(stdout, "TIMESTAMP(ms) = %llu QPS = %d\n", ts, count);
}
}
template
class BenchmarkPBServiceImpl : public BenchmarkPB::Service
{
public:
void echo_pb(FixLengthPBMsg *request, EmptyPBMsg *response,
RPCContext *ctx) override
{
auto *task = this->client->create_echo_pb_task(
[](EmptyPBMsg *remote_resp, srpc::RPCContext *remote_ctx) {
collect_qps();
});
task->user_data = response;
task->serialize_input(request);
ctx->get_series()->push_back(task);
}
void slow_pb(FixLengthPBMsg *request, EmptyPBMsg *response,
RPCContext *ctx) override
{
auto *task = WFTaskFactory::create_timer_task(15000, nullptr);
ctx->get_series()->push_back(task);
}
CLIENT *client;
};
template
class BenchmarkThriftServiceImpl : public BenchmarkThrift::Service
{
public:
void echo_thrift(BenchmarkThrift::echo_thriftRequest *request,
BenchmarkThrift::echo_thriftResponse *response,
RPCContext *ctx) override
{
auto *task = this->client->create_echo_thrift_task(
[](BenchmarkThrift::echo_thriftResponse *remote_resp,
srpc::RPCContext *remote_ctx) {
collect_qps();
});
task->user_data = response;
task->serialize_input(request);
ctx->get_series()->push_back(task);
}
void slow_thrift(BenchmarkThrift::slow_thriftRequest *request,
BenchmarkThrift::slow_thriftResponse *response,
RPCContext *ctx) override
{
auto *task = WFTaskFactory::create_timer_task(15000, nullptr);
ctx->get_series()->push_back(task);
}
CLIENT *client;
};
static void sig_handler(int signo)
{
wait_group.done();
}
template class SERVICE, class CLIENT>
static void init_proxy_client(SERVICE& service_impl)
{
RPCClientParams client_params = RPC_CLIENT_PARAMS_DEFAULT;
client_params.task_params.keep_alive_timeout = -1;
client_params.host = remote_host;
client_params.port = remote_port;
service_impl.client = new CLIENT(&client_params);
}
static void run_srpc_proxy(unsigned short port)
{
RPCServerParams params = RPC_SERVER_PARAMS_DEFAULT;
params.max_connections = 2048;
SRPCServer proxy_server(¶ms);
BenchmarkPBServiceImpl pb_impl;
BenchmarkThriftServiceImpl thrift_impl;
init_proxy_client(pb_impl);
init_proxy_client(thrift_impl);
proxy_server.add_service(&pb_impl);
proxy_server.add_service(&thrift_impl);
if (proxy_server.start(port) == 0)
{
wait_group.wait();
proxy_server.stop();
}
else
perror("server start");
}
template
static void run_pb_proxy(unsigned short port)
{
RPCServerParams params = RPC_SERVER_PARAMS_DEFAULT;
params.max_connections = 2048;
SERVER server(¶ms);
BenchmarkPBServiceImpl pb_impl;
init_proxy_client(pb_impl);
server.add_service(&pb_impl);
if (server.start(port) == 0)
{
wait_group.wait();
server.stop();
}
else
perror("server start");
}
template
static void run_thrift_proxy(unsigned short port)
{
RPCServerParams params = RPC_SERVER_PARAMS_DEFAULT;
params.max_connections = 2048;
SERVER server(¶ms);
BenchmarkThriftServiceImpl thrift_impl;
init_proxy_client(thrift_impl);
server.add_service(&thrift_impl);
if (server.start(port) == 0)
{
wait_group.wait();
server.stop();
}
else
perror("server start");
}
int main(int argc, char* argv[])
{
GOOGLE_PROTOBUF_VERIFY_VERSION;
if (argc != 5)
{
fprintf(stderr, "Usage: %s "
" \n", argv[0]);
abort();
}
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
WFGlobalSettings my = GLOBAL_SETTINGS_DEFAULT;
my.poller_threads = 16;
my.handler_threads = 16;
WORKFLOW_library_init(&my);
unsigned short port = atoi(argv[1]);
remote_host = argv[2];
remote_port = atoi(argv[3]);
std::string server_type = argv[4];
if (server_type == "srpc")
run_srpc_proxy(port);
else if (server_type == "brpc")
run_pb_proxy(port);
else if (server_type == "thrift")
run_thrift_proxy(port);
else if (server_type == "srpc_http")
run_pb_proxy(port);
else if (server_type == "thrift_http")
run_thrift_proxy(port);
else
abort();
fprintf(stdout, "\nTotal query: %llu max QPS: %d\n", total_count, max_qps);
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
sogou-srpc-ac65014/benchmark/server.cc 0000664 0000000 0000000 00000012224 15252174244 0017716 0 ustar 00root root 0000000 0000000 #include
#include
#include "benchmark_pb.srpc.h"
#include "benchmark_thrift.srpc.h"
#include "workflow/WFFacilities.h"
#ifdef _WIN32
#include "workflow/PlatformSocket.h"
#else
#include
#include
#include
#include
#include
#endif
using namespace srpc;
std::atomic query_count(0); // per_second
std::atomic last_timestamp(0L);
//volatile bool stop_flag = false;
int max_qps = 0;
long long total_count = 0;
WFFacilities::WaitGroup wait_group(1);
inline void collect_qps()
{
int64_t ms_timestamp = GET_CURRENT_MS();
++query_count;
if (ms_timestamp / 1000 > last_timestamp)
{
last_timestamp = ms_timestamp / 1000;
int count = query_count;
query_count = 0;
total_count += count;
if (count > max_qps)
max_qps = count;
long long ts = ms_timestamp;
fprintf(stdout, "TIMESTAMP(ms) = %llu QPS = %d\n", ts, count);
}
}
class BenchmarkPBServiceImpl : public BenchmarkPB::Service
{
public:
void echo_pb(FixLengthPBMsg *request, EmptyPBMsg *response,
RPCContext *ctx) override
{
collect_qps();
}
void slow_pb(FixLengthPBMsg *request, EmptyPBMsg *response,
RPCContext *ctx) override
{
auto *task = WFTaskFactory::create_timer_task(15000, nullptr);
ctx->get_series()->push_back(task);
}
};
class BenchmarkThriftServiceImpl : public BenchmarkThrift::Service
{
public:
void echo_thrift(BenchmarkThrift::echo_thriftRequest *request,
BenchmarkThrift::echo_thriftResponse *response,
RPCContext *ctx) override
{
collect_qps();
}
void slow_thrift(BenchmarkThrift::slow_thriftRequest *request,
BenchmarkThrift::slow_thriftResponse *response,
RPCContext *ctx) override
{
auto *task = WFTaskFactory::create_timer_task(15000, nullptr);
ctx->get_series()->push_back(task);
}
};
static void sig_handler(int signo)
{
wait_group.done();
}
static inline int create_bind_socket(unsigned short port)
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd >= 0)
{
struct sockaddr_in sin = { };
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(sockfd, (struct sockaddr *)&sin, sizeof sin) >= 0)
return sockfd;
close(sockfd);
}
return -1;
}
static void run_srpc_server(unsigned short port, int proc_num)
{
RPCServerParams params = RPC_SERVER_PARAMS_DEFAULT;
params.max_connections = 2048;
SRPCServer server(¶ms);
BenchmarkPBServiceImpl pb_impl;
BenchmarkThriftServiceImpl thrift_impl;
server.add_service(&pb_impl);
server.add_service(&thrift_impl);
int sockfd = create_bind_socket(port);
if (sockfd < 0)
{
perror("create socket");
exit(1);
}
while ((proc_num /= 2) != 0)
fork();
if (server.serve(sockfd) == 0)
{
wait_group.wait();
server.stop();
}
else
perror("server start");
close(sockfd);
}
template
static void run_pb_server(unsigned short port, int proc_num)
{
RPCServerParams params = RPC_SERVER_PARAMS_DEFAULT;
params.max_connections = 2048;
SERVER server(¶ms);
BenchmarkPBServiceImpl pb_impl;
server.add_service(&pb_impl);
int sockfd = create_bind_socket(port);
if (sockfd < 0)
{
perror("create socket");
exit(1);
}
while ((proc_num /= 2) != 0)
fork();
if (server.serve(sockfd) == 0)
{
wait_group.wait();
server.stop();
}
else
perror("server start");
close(sockfd);
}
template
static void run_thrift_server(unsigned short port, int proc_num)
{
RPCServerParams params = RPC_SERVER_PARAMS_DEFAULT;
params.max_connections = 2048;
SERVER server(¶ms);
BenchmarkThriftServiceImpl thrift_impl;
server.add_service(&thrift_impl);
int sockfd = create_bind_socket(port);
if (sockfd < 0)
{
perror("create socket");
exit(1);
}
while ((proc_num /= 2) != 0)
fork();
if (server.serve(sockfd) == 0)
{
wait_group.wait();
server.stop();
}
else
perror("server start");
close(sockfd);
}
int main(int argc, char* argv[])
{
GOOGLE_PROTOBUF_VERIFY_VERSION;
int proc_num = 1;
if (argc == 4)
{
proc_num = atoi(argv[3]);
if (proc_num != 1 && proc_num != 2 && proc_num != 4 && proc_num != 8 && proc_num != 16)
{
fprintf(stderr, "Usage: %s [proc num (1/2/4/8/16)]\n", argv[0]);
abort();
}
}
else if (argc != 3)
{
fprintf(stderr, "Usage: %s [proc num (1/2/4/8/16)]\n", argv[0]);
abort();
}
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
WFGlobalSettings my = GLOBAL_SETTINGS_DEFAULT;
my.poller_threads = 16;
my.handler_threads = 16;
WORKFLOW_library_init(&my);
unsigned short port = atoi(argv[1]);
std::string server_type = argv[2];
if (server_type == "srpc")
run_srpc_server(port, proc_num);
else if (server_type == "brpc")
run_pb_server(port, proc_num);
else if (server_type == "thrift")
run_thrift_server(port, proc_num);
else if (server_type == "srpc_http")
run_pb_server(port, proc_num);
else if (server_type == "thrift_http")
run_thrift_server(port, proc_num);
else
abort();
fprintf(stdout, "\nTotal query: %llu max QPS: %d\n", total_count, max_qps);
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
sogou-srpc-ac65014/benchmark/test.py 0000664 0000000 0000000 00000001175 15252174244 0017435 0 ustar 00root root 0000000 0000000 import os
import time
#serverlist = [("srpc", "pb"), ("brpc", "pb"), ("thrift", "thrift")]
serverlist = [("thrift", "thrift")]
#reqlist = [16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768]
parlist = [1, 2, 4, 8, 16, 32, 64, 128, 256]
for server, idl in serverlist:
#os.system("nohup ./server 8811 %s &" % server)
for par in parlist:
#for reqsize in reqlist:
#cmd = "./echo_client %s" % reqsize
#cmd = "./client 127.0.0.1 8811 %s %s 100 %s" % (server, idl, reqsize)
cmd = "./client 127.0.0.1 8811 %s %s %s 1024" % (server, idl, par)
print cmd
os.system(cmd);
time.sleep(1);
#os.system("killall server")
sogou-srpc-ac65014/benchmark/thrift_server.cc 0000664 0000000 0000000 00000002705 15252174244 0021301 0 ustar 00root root 0000000 0000000 #include "gen-cpp/BenchmarkThrift.h"
#include
#include
#include
#include
#include
#include
using namespace ::apache::thrift;
using namespace ::apache::thrift::protocol;
using namespace ::apache::thrift::transport;
using namespace ::apache::thrift::server;
using namespace ::apache::thrift::concurrency;
using boost::shared_ptr;
class BenchmarkThriftHandler : virtual public BenchmarkThriftIf {
public:
void echo_thrift(const std::string& msg) { }
void slow_thrift(const std::string& msg) {
usleep(15000);
}
};
int main(int argc, char **argv) {
int port = 8811;
shared_ptr handler(new BenchmarkThriftHandler());
shared_ptr processor(new BenchmarkThriftProcessor(handler));
shared_ptr protocolFactory(new TBinaryProtocolFactory());
boost::shared_ptr threadManager = ThreadManager::newSimpleThreadManager(16);
boost::shared_ptr threadFactory = boost::shared_ptr(new PosixThreadFactory());
TNonblockingServer server(processor, protocolFactory, port, threadManager);
server.setMaxConnections(2048);
server.setNumIOThreads(16);
threadManager->threadFactory(threadFactory);
threadManager->start();
server.serve();
return 0;
}
sogou-srpc-ac65014/docs/ 0000775 0000000 0000000 00000000000 15252174244 0015076 5 ustar 00root root 0000000 0000000 sogou-srpc-ac65014/docs/docs-01-idl.md 0000664 0000000 0000000 00000001310 15252174244 0017327 0 ustar 00root root 0000000 0000000 [English version](/docs/en/docs-01-idl.md)
## 01 - RPC IDL
- �述文件
- ��兼容
- Protobuf/Thrift
### 示例
下é�¢æˆ‘们通过一个具体例å�æ�¥å‘ˆçް
- 我们拿pb举例,定义一个ServiceName为``Example``的``example.proto``文件
- rpc接��为``Echo``,输入�数为``EchoRequest``,输出�数为``EchoResponse``
- ``EchoRequest``包括两个string:``message``和``name``
- ``EchoResponse``包括一个string:``message``
~~~proto
syntax="proto2";
message EchoRequest {
optional string message = 1;
optional string name = 2;
};
message EchoResponse {
optional string message = 1;
};
service Example {
rpc Echo(EchoRequest) returns (EchoResponse);
};
~~~
sogou-srpc-ac65014/docs/docs-02-service.md 0000664 0000000 0000000 00000002227 15252174244 0020230 0 ustar 00root root 0000000 0000000 [English version](/docs/en/docs-02-service.md)
## 02 - RPC Service
- 组�SRPC�务的基本�元
- �一个Service一定由�一�IDL生�
- Serviceå�ªä¸ŽIDL有关,与网络通信具体å��è®®æ— å…³
### 示例
下é�¢æˆ‘们通过一个具体例å�æ�¥å‘ˆçް
- 沿用上�的``example.proto``IDL�述文件
- 执行官方的``protoc example.proto --cpp_out=./ --proto_path=./``获得``example.pb.h``和``example.pb.cpp``两个文件
- 执行SRPC的``srpc_generator protobuf ./example.proto ./``获得``example.srpc.h``文件
- 我们派生``Example::Service``�实现具体的rpc业务逻辑,这就是一个RPC Service
- 注æ„�这个Service没有任何网络ã€�端å�£ã€�通信å��è®®ç‰æ¦‚念,仅仅负责完æˆ�实现从``EchoRequest``输入到输出``EchoResponse``的业务逻辑
~~~cpp
class ExampleServiceImpl : public Example::Service
{
public:
void Echo(EchoRequest *request, EchoResponse *response, RPCContext *ctx) override
{
response->set_message("Hi, " + request->name());
printf("get_req:\n%s\nset_resp:\n%s\n",
request->DebugString().c_str(),
response->DebugString().c_str());
}
};
~~~
sogou-srpc-ac65014/docs/docs-03-server.md 0000664 0000000 0000000 00000004742 15252174244 0020103 0 ustar 00root root 0000000 0000000 [English version](/docs/en/docs-03-server.md)
## 03 - RPC Server
- �一个Server对应一个端�
- �一个Server对应一个确定的网络通信�议
- æ¯�一个Serviceå�¯ä»¥æ·»åŠ åˆ°ä»»æ„�çš„Server里
- �一个Server�以拥有任�的Service,但在当�Server里ServiceName必须唯一
- ä¸�å�ŒIDLçš„Service是å�¯ä»¥æ”¾è¿›å�Œä¸€ä¸ªServerä¸çš„
### 示例
下é�¢æˆ‘们通过一个具体例å�æ�¥å‘ˆçް
- 沿用上�的``ExampleServiceImpl``Service
- 首先,我们创建1个RPC Server�需�确定�议
- ç„¶å�Žï¼Œæˆ‘们å�¯ä»¥åˆ›å»ºä»»æ„�个数的Service实例ã€�ä»»æ„�ä¸�å�Œprotoå½¢æˆ�çš„Service,把这些Service通过``add_service()``接å�£æ·»åŠ åˆ°Server里
- 最�,通过Server的``start``或者``serve``开��务,处��将到�的rpc请求
- 想�一下,我们也�以从``Example::Service``派生更多的功能的rpc``Echo``��实现的Service
- 想�一下,我们�以在N个��的端�创建N个��的RPC Server�代表���的�议
- 想�一下,我们�以把�一个ServiceIMPL实例``add_service``到��的Server上,我们也�以把��的ServiceIMPL实例``add_service``到�一个Server上
- 想�一下,我们�以用�一个``ExampleServiceImpl``,在三个��端���时�务于BRPC-STD�SRPC-STD�SRPC-Http
- 甚至,我们�以将1个PB的``ExampleServiceImpl``和1个Thrift的``AnotherThriftServiceImpl``,``add_service``到�一个SRPC-STD Server,两�IDL在�一个端�上完美工作�
~~~cpp
int main()
{
SRPCServer server_srpc;
SRPCHttpServer server_srpc_http;
BRPCServer server_brpc;
ThriftServer server_thrift;
TRPCServer server_trpc;
TRPCHttpServer server_trpc_http;
ExampleServiceImpl impl_pb;
AnotherThriftServiceImpl impl_thrift;
server_srpc.add_service(&impl_pb);
server_srpc.add_service(&impl_thrift);
server_srpc_http.add_service(&impl_pb);
server_srpc_http.add_service(&impl_thrift);
server_brpc.add_service(&impl_pb);
server_thrift.add_service(&impl_thrift);
server_trpc.add_service(&impl_pb);
server_trpc_http.add_service(&impl_thrift);
server_srpc.start(1412);
server_srpc_http.start(8811);
server_brpc.start(2020);
server_thrift.start(9090);
server_trpc.start(2022);
server_trpc_http.start(8822);
getchar();
server_trpc_http.stop();
server_trpc.stop();
server_thrift.stop();
server_brpc.stop();
server_srpc_http.stop();
server_srpc.stop();
return 0;
}
~~~
sogou-srpc-ac65014/docs/docs-04-client.md 0000664 0000000 0000000 00000004053 15252174244 0020047 0 ustar 00root root 0000000 0000000 [English version](/docs/en/docs-04-client.md)
## 04 - RPC Client
- æ¯�一个Client对应ç�€ä¸€ä¸ªç¡®å®šçš„ç›®æ ‡/一个确定的集群
- �一个Client对应�一个确定的网络通信�议
- �一个Client对应�一个确定的IDL
### 示例
下é�¢æˆ‘们通过一个具体例å�æ�¥å‘ˆçް
- 沿用上é�¢çš„例å�,client相对简å�•,直接调用å�³å�¯
- 通过``Example::XXXClient``创建æŸ�ç§�RPCçš„client实例,需è¦�ç›®æ ‡çš„ip+port或url
- 利用client实例直接调用rpc函数``Echo``å�³å�¯ï¼Œè¿™æ˜¯ä¸€æ¬¡å¼‚æ¥è¯·æ±‚,请求完æˆ�å�Žä¼šè¿›å…¥å›žè°ƒå‡½æ•°
- 具体的RPC Context用法请看下一个段�
~~~cpp
#include
#include "example.srpc.h"
#include "workflow/WFFacilities.h"
using namespace srpc;
int main()
{
Example::SRPCClient client("127.0.0.1", 1412);
EchoRequest req;
req.set_message("Hello!");
req.set_name("SRPCClient");
WFFacilities::WaitGroup wait_group(1);
client.Echo(&req, [&wait_group](EchoResponse *response, RPCContext *ctx) {
if (ctx->success())
printf("%s\n", response->DebugString().c_str());
else
printf("status[%d] error[%d] errmsg:%s\n",
ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg());
wait_group.done();
});
wait_group.wait();
return 0;
}
~~~
### �动�数
Clientå�¯ä»¥ç›´æŽ¥é€šè¿‡ä¼ å…¥ipã€�portå�¯åŠ¨ï¼Œæˆ–è€…é€šè¿‡å�‚æ•°å�¯åŠ¨ã€‚
上é�¢çš„例å�:
~~~cpp
Example::SRPCClient client("127.0.0.1", 1412);
~~~
ç‰å�ŒäºŽï¼š
~~~cpp
struct RPCClientParams param = RPC_CLIENT_PARAMS_DEFAULT;
param.host = "127.0.0.1";
param.port = 1412;
Example::SRPCClient client(¶m);
~~~
也ç‰å�ŒäºŽï¼š
~~~cpp
struct RPCClientParams param = RPC_CLIENT_PARAMS_DEFAULT;
param.url = "srpc://127.0.0.1:1412";
Example::SRPCClient client(¶m);
~~~
注�这里一定�使用`RPC_CLIENT_PARAMS_DEFAULT`去�始化我们的�数,里边包�了一个`RPCTaskParams`,包括默认的data_type�compress_type��试次数和多�超时,具体结构�以�考[rpc_options.h](/src/rpc_options.h)。
sogou-srpc-ac65014/docs/docs-05-context.md 0000664 0000000 0000000 00000005374 15252174244 0020265 0 ustar 00root root 0000000 0000000 [English version](/docs/en/docs-05-context.md)
## 05 - RPC Context
- RPCContext专门用æ�¥è¾…åŠ©å¼‚æ¥æŽ¥å�£ï¼ŒServiceå’ŒClient通用
- æ¯�ä¸€ä¸ªå¼‚æ¥æŽ¥å�£éƒ½ä¼šæ��ä¾›Context,用æ�¥ç»™ç”¨æˆ·æ��供更高级的功能,比如获å�–对方ipã€�获å�–连接seqidç‰
- Context上一些功能是Server或Client独有的,比如Server�以设置回�数�的压缩方�,Client�以获�请求�功或失败
- Context上å�¯ä»¥é€šè¿‡get_series获得所在的series,与workflowçš„å¼‚æ¥æ¨¡å¼�æ— ç¼�结å�ˆ
### RPCContext API - Common
#### ``long long get_seqid() const;``
请求+回�视为1次完整通信,获得当�socket连接上的通信sequence id,seqid=0代表第1次
#### ``std::string get_remote_ip() const;``
获得对方IP地�,支�ipv4/ipv6
#### ``int get_peer_addr(struct sockaddr *addr, socklen_t *addrlen) const;``
获得对方地�,in/out�数为更底层的数�结构sockaddr
#### ``const std::string& get_service_name() const;``
获�RPC Service Name
#### ``const std::string& get_method_name() const;``
获�RPC Methode Name
#### ``SeriesWork *get_series() const;``
获�当�ServerTask/ClientTask所在series
### RPCContext API - Only for client done
#### ``bool success() const;``
client专用。这次请求是��功
#### ``int get_status_code() const;``
client专用。这次请求的rpc status code
#### ``const char *get_errmsg() const;``
client专用。这次请求的错误信�
#### ``int get_error() const;``
client专用。这次请求的错误ç �
#### ``void *get_user_data() const;``
client专用。获å�–ClientTaskçš„user_data。如果用户通过create_xxx_task接å�£äº§ç”Ÿtask,则å�¯ä»¥é€šè¿‡user_data域记录上下文,在创建taskæ—¶è®¾ç½®ï¼Œåœ¨å›žè°ƒå‡½æ•°ä¸æ‹¿å›žã€‚
### RPCContext API - Only for server process
#### ``void set_data_type(RPCDataType type);``
Server专用。设置数�打包类型
- RPCDataProtobuf
- RPCDataThrift
- RPCDataJson
#### ``void set_compress_type(RPCCompressType type);``
Server专用。设置数�压缩类型(注:Client的压缩类型在Client或Task上设置)
- RPCCompressNone
- RPCCompressSnappy
- RPCCompressGzip
- RPCCompressZlib
- RPCCompressLz4
#### ``void set_attachment_nocopy(const char *attachment, size_t len);``
Server专用。设置attachment附件。
#### ``bool get_attachment(const char **attachment, size_t *len) const;``
Server专用。获�attachment附件。
#### ``void set_reply_callback(std::function cb);``
Server专用。设置reply callback,�作系统写入socket缓冲区�功�被调用。
#### ``void set_send_timeout(int timeout);``
Server专用。设置å�‘é€�超时,å�•ä½�毫秒。-1ä»£è¡¨æ— é™�。
#### ``void set_keep_alive(int timeout);``
Server专用。设置连接ä¿�活时间,å�•ä½�毫秒。-1ä»£è¡¨æ— é™�。
sogou-srpc-ac65014/docs/docs-06-workflow.md 0000664 0000000 0000000 00000012617 15252174244 0020452 0 ustar 00root root 0000000 0000000 [English version](/docs/en/docs-06-workflow.md)
## 06 - 与workflowå¼‚æ¥æ¡†æž¶çš„结å�ˆ
### 1. Server
下é�¢æˆ‘们通过一个具体例å�æ�¥å‘ˆçް
- Echo RPC在接收到请求时,�下游�起一次http请求
- 对下游请求完��,我们将http response的body信�填充到response的message里,回�给客户端
- 我们ä¸�希望阻塞/å� æ�®ç�€Handler的线程,所以对下游的请求一定是一次异æ¥è¯·æ±‚
- 首先,我们通过Workflow框架的工厂``WFTaskFactory::create_http_task``创建一个异æ¥ä»»åŠ¡http_task
- 然�,我们利用RPCContext的``ctx->get_series()``获�到ServerTask所在的SeriesWork
- 最�,我们使用SeriesWork的``push_back``接�将http_task放到SeriesWork的��
~~~cpp
class ExampleServiceImpl : public Example::Service
{
public:
void Echo(EchoRequest *request, EchoResponse *response, RPCContext *ctx) override
{
auto *http_task = WFTaskFactory::create_http_task("https://www.sogou.com", 0, 0,
[request, response](WFHttpTask *task) {
if (task->get_state() == WFT_STATE_SUCCESS)
{
const void *data;
size_t len;
task->get_resp()->get_parsed_body(&data, &len);
response->mutable_message()->assign((const char *)data, len);
}
else
response->set_message("Error: " + std::to_string(task->get_error()));
printf("Server Echo()\nget_req:\n%s\nset_resp:\n%s\n",
request->DebugString().c_str(),
response->DebugString().c_str());
});
ctx->get_series()->push_back(http_task);
}
};
~~~
### 2. Client
下é�¢æˆ‘们通过一个具体例å�æ�¥å‘ˆçް
- 我们并行�出两个请求,1个是rpc请求,1个是http请求
- 两个请求都结��,我们��起一次计算任务,计算两个数的平方和
- 首先,我们通过RPC Clientçš„``create_Echo_task``创建一个rpc异æ¥è¯·æ±‚的网络任务rpc_task
- ç„¶å�Žï¼Œæˆ‘们通过Workflow框架的工厂``WFTaskFactory::create_http_task``å’Œ``WFTaskFactory::create_go_task``分别创建异æ¥ç½‘络任务http_task,和异æ¥è®¡ç®—任务calc_task
- 最å�Žï¼Œæˆ‘们利用串并è�”æµ�程图,乘å�·ä»£è¡¨å¹¶è¡Œã€�大于å�·ä»£è¡¨ä¸²è¡Œï¼Œå°†3个异æ¥ä»»åŠ¡ç»„å�ˆèµ·æ�¥æ‰§è¡Œstart
~~~cpp
void calc(int x, int y)
{
int z = x * x + y * y;
printf("calc result: %d\n", z);
}
int main()
{
Example::SRPCClient client("127.0.0.1", 1412);
auto *rpc_task = client.create_Echo_task([](EchoResponse *response, RPCContext *ctx) {
if (ctx->success())
printf("%s\n", response->DebugString().c_str());
else
printf("status[%d] error[%d] errmsg:%s\n",
ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg());
});
auto *http_task = WFTaskFactory::create_http_task("https://www.sogou.com", 0, 0, [](WFHttpTask *task) {
if (task->get_state() == WFT_STATE_SUCCESS)
{
std::string body;
const void *data;
size_t len;
task->get_resp()->get_parsed_body(&data, &len);
body.assign((const char *)data, len);
printf("%s\n\n", body.c_str());
}
else
printf("Http request fail\n\n");
});
auto *calc_task = WFTaskFactory::create_go_task(calc, 3, 4);
EchoRequest req;
req.set_message("Hello!");
req.set_name("1412");
rpc_task->serialize_input(&req);
WFFacilities::WaitGroup wait_group(1);
SeriesWork *series = Workflow::create_series_work(http_task, [&wait_group](const SeriesWork *) {
wait_group.done();
});
series->push_back(rpc_task);
series->push_back(calc_task);
series->start();
wait_group.wait();
return 0;
}
~~~
### 3. Upstream
SRPC�以直接使用Workflow的任何组件,最常用的就是[Upstream](https://github.com/sogou/workflow/blob/master/docs/about-upstream.md),SRPC的任何一�client都�以使用Upstream。
我们通过å�‚æ•°æ�¥çœ‹çœ‹å¦‚ä½•æž„é€ å�¯ä»¥ä½¿ç”¨Upstreamçš„client:
```cpp
#include "workflow/UpstreamManager.h"
int main()
{
// 1. 创建upstreamå¹¶æ·»åŠ å®žä¾‹
UpstreamManager::upstream_create_weighted_random("echo_server", true);
UpstreamManager::upstream_add_server("echo_server", "127.0.0.1:1412");
UpstreamManager::upstream_add_server("echo_server", "192.168.10.10");
UpstreamManager::upstream_add_server("echo_server", "internal.host.com");
// 2. æž„é€ å�‚数,填上upstreamçš„å��å—
RPCClientParams client_params = RPC_CLIENT_PARAMS_DEFAULT;
client_params.host = "echo_server";
client_params.port = 1412; // 这个port�用于upstream URI解�,�影�具体实例的选�
// 3. 用�数创建client,其他用法与示例类似
Example::SRPCClient client(&client_params);
...
```
如果使用了ConsistentHash或者Manual方�创建upstream,则我们往往需�对��的task进行区分�以供选�算法使用。这时候�以使用client task上的`int set_uri_fragment(const std::string& fragment);`接�,设置请求级相关的信�。
这个域的是URI里的fragment,è¯ä¹‰è¯·å�‚考[RFC3689 3.5-Fragment](https://datatracker.ietf.org/doc/html/rfc3986#section-3.5),任何需è¦�用到fragment的功能(如其他选å�–ç–略里附带的其他信æ�¯ï¼‰ï¼Œéƒ½å�¯ä»¥åˆ©ç”¨è¿™ä¸ªåŸŸã€‚
sogou-srpc-ac65014/docs/docs-07-srpc-http.md 0000664 0000000 0000000 00000015322 15252174244 0020521 0 ustar 00root root 0000000 0000000 [English version](/docs/docs-07-srpc-http.md)
## 07 - 使用SRPC�TRPC�Thrift��Http
**SRPC**支æŒ�**HTTP**å��议,å�ªè¦�把**idl**的内容作填到**HTTP**çš„**body**ä¸ï¼Œå¹¶ä¸”在**header**里填上**idl**的类型(**json**/**protobuf**/**thrift**),就å�¯ä»¥ä¸Žå…¶ä»–框架通过**HTTP**å��议互通,由æ¤å�¯ä»¥å®žçŽ°è·¨è¯è¨€ã€‚
- å�¯åЍ**SRPCHttpServer**/**TRPCHttpServer**/**ThriftHttpServer**,å�¯ä»¥æŽ¥æ”¶ç”±ä»»ä½•è¯è¨€å®žçŽ°çš„HTTP clientå�‘过æ�¥çš„请求;
- å�¯åЍ**SRPCHttpClient**/**TRPCHttpClient**/**ThriftHttpClient**,也å�¯ä»¥å�‘任何è¯è¨€å®žçŽ°çš„Http Serverå�‘é€�请求;
- **HTTP header**:`Content-Type`设置为`application/json`表示json,`application/x-protobuf`表示protobuf,`application/x-thrift`表示thrift;
- **HTTP body**: 如果body䏿¶‰å�Š**bytes**类型,**json**ä¸éœ€è¦�使用**base64**进行encodeï¼›
### 1. 示例
想实现**SRPCHttpClient**,å�¯ä»¥æŠŠ[tutorial-02-srpc_pb_client.cc](https://github.com/sogou/srpc/blob/master/tutorial/tutorial-02-srpc_pb_client.cc)或者[tutorial-09-client_task.cc](https://github.com/sogou/srpc/blob/master/tutorial/tutorial-09-client_task.cc)ä¸çš„`SRPCClient`改æˆ�`SRPCHttpClient`å�³å�¯ã€‚
在项目的[README.md](/docs//README_cn.md#6-run)ä¸ï¼Œæˆ‘们演示了如何使用**curl**å�‘**SRPCHttpServer**å�‘é€�请求,下é�¢æˆ‘们给出例å�演示如何使用**python**作为客户端,å�‘**TRPCHttpServer**å�‘é€�请求。
**proto文件:**
```proto
syntax="proto3"; // proto2 or proto3 are both supported
package trpc.test.helloworld;
message AddRequest {
string message = 1;
string name = 2;
bytes info = 3;
int32 error = 4;
};
message AddResponse {
string message = 1;
};
service Batch {
rpc Add(AddRequest) returns (AddResponse);
};
```
**python客户端:**
```py
import json
import requests
from base64 import b64encode
class Base64Encoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, bytes):
return b64encode(o).decode()
return json.JSONEncoder.default(self, o)
headers = {'Content-Type': 'application/json'}
req = {
'message': 'hello',
'name': 'k',
'info': b'i am binary'
}
print(json.dumps(req, cls=Base64Encoder))
ret = requests.post(url = "http://localhost:8800/trpc.test.helloworld.Batch/Add",
headers = headers, data = json.dumps(req, cls=Base64Encoder))
print(ret.json())
```
### 2. 请求路径拼接
[README.md](/docs//README_cn.md#6-run)ä¸ï¼Œæˆ‘们å�¯ä»¥çœ‹åˆ°ï¼Œè·¯å¾„是由serviceå��å’Œrpcå��拼接而æˆ�的。而对于以上带packageå�� `package trpc.test.helloworld;`的例å�, packageå��也需è¦�拼接到路径ä¸ï¼Œ**SRPCHttp** å’Œ **TRPCHttp** 的拼接路径方å¼�å¹¶ä¸�ä¸€æ ·ï¼Œè€Œ**ThriftHttp**由于SRPCçš„thriftä¸�支æŒ�多个serviceæ‰€ä»¥æ— éœ€æ‹¼æŽ¥ä»»ä½•è·¯å¾„ã€‚
我们以**curl**为例å�:
与**SRPCHttpServer**互通:
```sh
curl 127.0.0.1:8811/trpc/test/helloworld/Batch/Add -H 'Content-Type: application/json' -d '{...}'
```
与**TRPCHttpServer**互通:
```sh
curl 127.0.0.1:8811/trpc.test.helloworld.Batch/Add -H 'Content-Type: application/json' -d '{...}'
```
与**ThriftHttpServer**互通:
```sh
curl 127.0.0.1:8811 -H 'Content-Type: application/json' -d '{...}'
```
### 3. HTTP状æ€�ç �
SRPC支æŒ�server在`process()`ä¸è®¾ç½®çжæ€�ç �,接å�£ä¸º**RPCContext**上的`set_http_code(int code)`。å�ªæœ‰åœ¨æ¡†æž¶èƒ½å¤Ÿæ£ç¡®å¤„ç�†è¯·æ±‚的情况下,该错误ç �æ‰�有效,å�¦åˆ™ä¼šè¢«è®¾ç½®ä¸ºæ¡†æž¶å±‚级的错误ç �。
**用法:**
~~~cpp
class ExampleServiceImpl : public Example::Service
{
public:
void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override
{
if (req->name() != "workflow")
ctx->set_http_code(404); // 设置HTTP状æ€�ç �404
else
resp->set_message("Hi back");
}
};
~~~
**CURL命令:**
~~~sh
curl -i 127.0.0.1:1412/Example/Echo -H 'Content-Type: application/json' -d '{message:"from curl",name:"CURL"}'
~~~
**结果:**
~~~sh
HTTP/1.1 404 Not Found
SRPC-Status: 1
SRPC-Error: 0
Content-Type: application/json
Content-Encoding: identity
Content-Length: 21
Connection: Keep-Alive
~~~
**注�:**
我们ä¾�ç„¶å�¯ä»¥é€šè¿‡è¿”回结果的headerä¸çš„`SRPC-Status: 1`æ�¥åˆ¤æ–这个请求在框架层é�¢æ˜¯æ£ç¡®çš„,`404`是æ�¥è‡ªserver的状æ€�ç �。
### 4. HTTP Header
用户�以通过以下三个接��设置/获�http header:
~~~cpp
bool get_http_header(const std::string& name, std::string& value) const;
bool set_http_header(const std::string& name, const std::string& value);
bool add_http_header(const std::string& name, const std::string& value);
~~~
对于**server**�说,这些接�在`RPCContext`上。
对于**client**�说,需�通过`RPCClientTask`设置**请求上的http header**�并且在回调函数的`RPCContext`上**获�回�上的http header**,用法如下所示:
~~~cpp
int main()
{
Example::SRPCHttpClient client("127.0.0.1", 80);
EchoRequest req;
req.set_message("Hello, srpc!");
auto *task = client.create_Echo_task([](EchoResponse *resp, RPCContext *ctx) {
if (ctx->success())
{
std::string value;
ctx->get_http_header("server_key", value); // 获å�–回å¤�ä¸çš„header
}
});
task->serialize_input(&req);
task->set_http_header("client_key", "client_value"); // 设置请求ä¸çš„header
task->start();
wait_group.wait();
return 0;
}
~~~
### 5. IDLä¼ è¾“æ ¼å¼�问题
如果我们填写的是Protobufä¸”ç”¨çš„æ ‡å‡†ä¸ºproto3,æ¯�个域由于没有optionalå’Œrequired区分,所以都是带有默认值的。如果我们设置的值æ£å¥½ç‰äºŽé»˜è®¤å€¼ï¼Œåˆ™proto3ä¸�能识别为被set过,就ä¸�能被åº�列化的时候å�‘出。
在protobuf转json的过程ä¸ï¼ŒSRPC在**RPCContext上**æ��ä¾›äº†å‡ ä¸ªæŽ¥å�£ï¼Œæ”¯æŒ� [JsonPrintOptions](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.util.json_util#JsonPrintOptions) 上的功能。具体接å�£ä¸Žç”¨æ³•æ��è¿°å�¯ä»¥æŸ¥çœ‹ï¼š[rpc_context.h](/src/rpc_context.h)
**示例:**
```cpp
class ExampleServiceImpl : public Example::Service
{
public:
void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override
{
resp->set_message("Hi back");
resp->set_error(0); // 0是error类型int32在proto3ä¸çš„默认值
ctx->set_json_always_print_fields_with_no_presence(true); // 带上所有原始域
ctx->set_json_add_whitespace(true); // å¢žåŠ jsonæ ¼å¼�çš„ç©ºæ ¼
}
};
```
**原始输出:**
```sh
{"message":"Hi back"}
```
**通过RPCContext设置过json options之�的输出:**
```sh
{
"message": "Hi back",
"error": 0
}
```
sogou-srpc-ac65014/docs/docs-08-tracing.md 0000664 0000000 0000000 00000011143 15252174244 0020222 0 ustar 00root root 0000000 0000000 [English version](/docs/en/docs-08-tracing.md)
## 08 - 上报Tracing到OpenTelemetry
**SRPC**支æŒ�产生和上报链路信æ�¯traceå’Œspan,并且å�¯ä»¥é€šè¿‡å¤šç§�途径进行上报,其ä¸åŒ…括本地导出数æ�®å’Œä¸ŠæŠ¥åˆ°[OpenTelemetry](https://opentelemetry.io).
**SRPC**é�µå¾ª**OpenTelemetry**çš„[æ•°æ�®è§„范(data specification)](https://github.com/open-telemetry/opentelemetry-specification)以å�Š[w3cçš„trace context](https://www.w3.org/TR/trace-context/)ï¼Œå› æ¤å�¯ä»¥ä½¿ç”¨æ�’ä»¶**RPCTraceOpenTelemetry**进行上报。
秉承ç�€**Workflow**çš„é£Žæ ¼ï¼Œæ‰€æœ‰çš„ä¸ŠæŠ¥éƒ½æ˜¯å¼‚æ¥ä»»åŠ¡æ¨¡å¼�,对RPC的请求和æœ�务ä¸�会产生任何性能影å“�。
### 1. 用法
å…ˆæž„é€ æ�’ä»¶`RPCTraceOpenTelemetry`,然å�Žé€šè¿‡`add_filter()`把æ�’ä»¶åŠ åˆ°**server**或**client**ä¸ã€‚
以[tutorial-02-srpc_pb_client.cc](https://github.com/sogou/srpc/blob/master/tutorial/tutorial-02-srpc_pb_client.cc)作为clientçš„ç¤ºä¾‹ï¼Œæˆ‘ä»¬å¦‚ä¸‹åŠ ä¸¤è¡Œä»£ç �:
```cpp
int main()
{
Example::SRPCClient client("127.0.0.1", 1412);
RPCTraceOpenTelemetry span_otel("http://127.0.0.1:4318");
client.add_filter(&span_otel);
...
}
```
以[tutorial-01-srpc_pb_server.cc](https://github.com/sogou/srpc/blob/master/tutorial/tutorial-01-srpc_pb_server.cc)作为serverçš„ç¤ºä¾‹ï¼Œä¹Ÿå¢žåŠ ç±»ä¼¼çš„ä¸¤è¡Œã€‚å�Œæ—¶æˆ‘ä»¬è¿˜å¢žåŠ ä¸€ä¸ªæœ¬åœ°æ�’件,用于把本次请求的traceæ•°æ�®ä¹Ÿåœ¨å±�幕上打å�°ï¼š
```cpp
int main()
{
SRPCServer server;
RPCTraceOpenTelemetry span_otel("http://127.0.0.1:4318");
server.add_filter(&span_otel);
RPCTraceDefault span_log; // 这个�件会把本次请求的trace信�打到�幕上
server.add_filter(&span_log);
...
}
```
执行命令`make tutorial`�以编译出示例程�,并且分别把server和client跑起�,我们�以在�幕上看到一些tracing信�:
我们�以看到上图client这边的span_id: **04d070f537f17d00**,它在下图server这里��了parent_span_id: **04d070f537f17d00**:
### 2. Traces上报到Jaeger
打开Jaeger的主页,我们�以找到我们�为**Example**的�务(service)和�为**Echo**的函数(method)。这一个tracing记录上有两个span节点,是由server和client分别上报的。
我们�以在Jaeger看到client所上报的span_id: **04d070f537f17d00**和server所上报的span_id: **00202cf737f17d00**,�时也是能对应上刚�在�幕看到的id的。
### 3. �数
多久收集一份traceä¿¡æ�¯ã€�上报请求的é‡�试次数ã€�以å�Šå…¶ä»–å�‚数,都å�¯ä»¥é€šè¿‡`RPCTraceOpenTelemetry`çš„æž„é€ å‡½æ•°æŒ‡å®šã€‚ä»£ç �å�‚考:[src/module/rpc_trace_filter.h](https://github.com/sogou/srpc/blob/master/src/module/rpc_trace_filter.h#L238)
默认æ¯�秒收集1000æ�¡traceä¿¡æ�¯ï¼Œå¹¶ä¸”é€�ä¼ tracingä¿¡æ�¯ç‰å…¶ä»–功能也已é�µå¾ªä¸Šè¿°è§„范实现。
### 4. Attributes
我们å�¯ä»¥é€šè¿‡`add_attributes()`æ·»åŠ æŸ�些é¢�外的信æ�¯ï¼Œæ¯”如数æ�®è§„范ä¸çš„OTEL_RESOURCE_ATTRIBUTES。
注æ„�我们的serviceå��"Example"也是设置到attributesä¸çš„,key为`service.name`。如果用户也在OTEL_RESOURCE_ATTRIBUTESä¸ä½¿ç”¨äº†`service.name`这个key,则SRPCçš„service name优先级更高,å�‚考:[OpenTelemetry#resource](https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/src/OpenTelemetry#resource)
### 5. Logå’ŒBaggage
SRPCæ��供了`log()`å’Œ`baggage()`接å�£ï¼Œç”¨æˆ·å�¯ä»¥æ·»åŠ éœ€è¦�通过链路é€�ä¼ çš„æ•°æ�®ã€‚
```cpp
void log(const RPCLogVector& fields);
void baggage(const std::string& key, const std::string& value);
```
作为Server,我们å�¯ä»¥ä½¿ç”¨`RPCContext`上的接å�£æ�¥æ·»åŠ log:
```cpp
class ExampleServiceImpl : public Example::Service
{
public:
void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override
{
resp->set_message("Hi back");
ctx->log({{"event", "info"}, {"message", "rpc server echo() end."}});
}
};
```
作为client,我们å�¯ä»¥ä½¿ç”¨`RPCClientTask`上的接å�£æ·»åŠ log:
```cpp
srpc::SRPCClientTask *task = client.create_Echo_task(...);
task->log({{"event", "info"}, {"message", "log by rpc client echo()."}});
```
sogou-srpc-ac65014/docs/docs-09-metrics.md 0000664 0000000 0000000 00000026411 15252174244 0020246 0 ustar 00root root 0000000 0000000 [English version](/docs/en/docs-09-metrics.md)
## 09 - 上报Metrics
**Metrics**(æŒ‡æ ‡)是常用的监控需求,**SRPC**支æŒ�产生与统计Metrics,并通过多ç§�途径上报,其ä¸åŒ…括上报到[Prometheus](https://prometheus.io/)å’Œ[OpenTelemetry](https://opentelemetry.io)。
秉承ç�€**Workflow**çš„é£Žæ ¼ï¼Œæ‰€æœ‰çš„ä¸ŠæŠ¥éƒ½æ˜¯å¼‚æ¥ä»»åŠ¡æŽ¨é€�或拉å�–模å¼�,对RPC的请求和æœ�务ä¸�会产生任何性能影å“�。
本文档会介�Metrics的概念�结�tutorial-16对接�进行讲解�上报Prometheus的特点�上报OpenTelemetry的特点�以�介�使用了thread local进行性能�速的Var模�。
### 1. Metrics概念介�
**Prometheus**的数�类型�以�考官方文档:[Concepts - Metrics](https://prometheus.io/docs/concepts/metric_types/) 和 [Type of Metrics](https://prometheus.io/docs/tutorials/understanding_metric_types/)。
**OpenTelemetry**的�以�考[数�规范(data specification)](https://github.com/open-telemetry/opentelemetry-specification)以�[Metrics的datamodel.md](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/datamodel.md)。
å…¶ä¸å››ç§�åŸºæœ¬æŒ‡æ ‡çš„æ¦‚å¿µéƒ½æ˜¯ä¸€è‡´çš„ï¼Œå› æ¤SRPC对这四ç§�æŒ‡æ ‡è¿›è¡Œäº†æœ€åŸºæœ¬çš„æ”¯æŒ�,å�¯åˆ†åˆ«å¯¹åº”到Prometheuså’ŒOpenTelemetry的上报数æ�®ä¸ã€‚å�‚考下表:
|æŒ‡æ ‡ç±»åž‹|Prometheus|OpenTelemetry| SRPCä¸çš„创建接å�£ | SRPCä¸çš„常用æ“�作 |
|-------|----------|------------|-----------------|-------------------|
|�个数值| Gauge | Gauge | create_gauge(name, help); | void increase(); void decrease();|
|计数器 | Counter | Sum | create_counter(name, help); |GaugeVar *add(labels);|
|直方图 |Histogram | Histogram | create_histogram(name, help, buckets); |void observe(data);|
|é‡‡æ · | Summary | Summary | create_summary(name, help, quantiles); |void observe(data);|
å››ç§�æŒ‡æ ‡çš„å¤§è‡´æ��述:
1. **�个数值**:简�数值的度�,�以简�的增�。
2. **计数器**:累计的度é‡�,å�¯ä»¥é€šè¿‡æ·»åŠ è‹¥å¹²**label**去区分ä¸�å�Œlabel下的数值。
3. **直方图**:对观察observer()得到的接å�£è¿›è¡Œåˆ†åŒºé—´ç´¯åŠ ï¼Œå› æ¤éœ€è¦�ä¼ å…¥**buckets**告知需è¦�划分的区间;
æ¯”å¦‚ä¼ å…¥çš„bucket分桶值是{ 1, 10, 100 }è¿™3个数,则我们å�¯ä»¥å¾—到数æ�®åˆ†å¸ƒäºŽ{ 0 - 1, 0 - 10, 0 - 100, 0 - +Inf }4个区间的数æ�®ç´¯è®¡å€¼ï¼Œå�Œæ—¶ä¹Ÿä¼šå¾—到整体的总以å�Šæ•°æ�®ä¸ªæ•°ã€‚
4. **é‡‡æ ·**ï¼šä¸Žç›´æ–¹å›¾ç±»ä¼¼ï¼Œä½†ä¼ å…¥çš„æ˜¯**分ä½�æ•°quantiles**,比如{0.5, 0.9}以å�Šå®ƒä»¬çš„精度,主è¦�用于事先ä¸�知é�“æ•°æ�®åˆ†å¸ƒå…·ä½“数值的场景(相比之下,直方图需è¦�ä¼ å…¥å…·ä½“æ•°å€¼ï¼‰ã€‚
é‡‡æ ·æ˜¯å¸¦æœ‰**时间窗å�£**的,å�¯ä»¥é€šè¿‡æŽ¥å�£æŒ‡å®š**max_age统计窗å�£æ—¶é•¿**以å�Š**age_bucket内部分桶个数**,SRPCä¸é»˜è®¤çš„æ˜¯60秒分5个桶切æ�¢æ—¶é—´çª—å�£ã€‚
考虑到**é‡‡æ ·Summary**本身的统计å¤�æ�‚性,一般æ�¥è¯´å»ºè®®ä¼˜å…ˆä½¿ç”¨**直方图Histogram**。
为了方便用户使用,目å‰�SRPC里这四ç§�æŒ‡æ ‡éƒ½ä½¿ç”¨double类型进行统计。
### 2. 用法示例
我们结å�ˆ[tutorial-16-server_with_metrics.cc](/tutorial/tutorial-16-server_with_metrics.cc)看看基本用法,本示例虽然是server,但clientä¸çš„ç”¨æ³•ä¹Ÿæ˜¯ä¸€æ ·çš„ã€‚
#### (1) 创建�件
我们选择Prometheusä½œä¸ºæˆ‘ä»¬çš„ä¸ŠæŠ¥å¯¹è±¡ï¼Œå› æ¤éœ€è¦�使用**RPCMetricsPull**æ�’件。
~~~cpp
#include "srpc/rpc_metrics_filter.h" // Metrics�件所在的头文件
int main()
{
SRPCServer server;
ExampleServiceImpl impl;
RPCMetricsPull filter; // 创建一个�件
filter.init(8080); // é…�å�ˆPrometheusä¸å¡«å¥½çš„æ”¶é›†æ•°æ�®çš„端å�£
~~~
#### (2) æ·»åŠ æŒ‡æ ‡
这个**RPCMetricsPull**æ�’ä»¶æœ¬èº«è‡ªå¸¦äº†ç»Ÿè®¡éƒ¨åˆ†å¸¸ç”¨æŒ‡æ ‡ï¼ŒåŒ…æ‹¬æ•´ä½“è¯·æ±‚ä¸ªæ•°ç»Ÿè®¡ã€�按照serviceå’Œmethod作为labelçš„ä¸�å�Œç»´åº¦çš„请求个数统计以å�Šè¯·æ±‚耗时的分ä½�数统计ç‰ã€‚
用户å�¯ä»¥è‡ªè¡Œå¢žåŠ æƒ³è¦�ç»Ÿè®¡çš„å€¼ï¼Œè¿™é‡Œæˆ‘ä»¬å¢žåŠ ä¸€ä¸ªç”¨äºŽç»Ÿè®¡è¯·æ±‚å¤§å°�的直方图histogram,å��å—为"echo_request_size"ï¼Œä¸ŠæŠ¥æ—¶çš„æŒ‡æ ‡æ��è¿°ä¿¡æ�¯æ˜¯"Echo request size",数æ�®bucket划分是 { 1, 10, 100 } 。
~~~cpp
filter.create_histogram("echo_request_size", "Echo request size", {1, 10, 100});
~~~
说明:
**æ·»åŠ æŒ‡æ ‡çš„æ—¶æœº**
æŒ‡æ ‡æ˜¯å�¯ä»¥éš�æ—¶æ·»åŠ çš„ï¼Œå�³ä½¿server/clientè·‘èµ·æ�¥ä¹‹å�Žä¹Ÿå�¯ä»¥ã€‚但**必须在æ“�ä½œè¿™ä¸ªæŒ‡æ ‡ä¹‹å‰�æ·»åŠ **,å�¦åˆ™èŽ·å�–æŒ‡æ ‡çš„æ—¶å€™ä¼šèŽ·å�–åˆ°ç©ºæŒ‡é’ˆï¼Œæ— æ³•è¿›è¡Œç»Ÿè®¡æ“�作。
**æŒ‡æ ‡çš„å��å—**
æŒ‡æ ‡çš„å��å—æ˜¯**全局唯一**çš„ï¼ˆæ— è®ºæ˜¯å››ç§�基本类型ä¸çš„哪ç§�),且**å�ªèƒ½åŒ…å�«å¤§å°�写嗿¯�和下划线,å�³a-z, A-Z, _** 。如果使用已ç»�å˜åœ¨çš„å��å—åˆ›å»ºæŒ‡æ ‡ï¼Œåˆ™ä¼šåˆ›å»ºå¤±è´¥å¹¶è¿”å›žNULL。
一旦创建æˆ�功,我们之å�Žéƒ½ä¼šä½¿ç”¨è¿™å�Œä¸€ä¸ªå��å—去æ“�ä½œè¿™ä¸ªæŒ‡æ ‡ã€‚
**åˆ›å»ºå…¶ä»–æŒ‡æ ‡çš„æŽ¥å�£**
å�¯ä»¥æŸ¥çœ‹åˆšæ‰�include的头文件[rpc_metrics_filter.h](/src/module/rpc_metrics_filter.h)ä¸çš„:`class RPCMetricsFilter`。
#### (3) 把æ�’ä»¶æ·»åŠ åˆ°server/clientä¸
由于我们需è¦�æ“�ä½œæŒ‡æ ‡æ—¶ï¼Œæ˜¯éœ€è¦�调用这个æ�’件上的接å�£çš„ï¼Œå› æ¤æˆ‘们在serviceä¸ä¿�留一下这个指针。这å�ªæ˜¯ç¤ºä¾‹ç¨‹åº�的用法,用户å�¯ä»¥ä½¿ç”¨è‡ªå·±ä¹ 惯的方å¼�:
~~~cpp
class ExampleServiceImpl : public Example::Service
{
public:
void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) overrideï¼›
void set_filter(RPCMetricsPull *filter) { this->filter = filter; }
private:
RPCMetricsPull *filter; // �留了�件的指针,并实现接�设置进去,这并�SRPC框架的接�
};
~~~
main函数ä¸ç»§ç»è¿™æ ·å†™ï¼š
~~~cpp
int main()
{
...
impl.set_filter(&filter); // 设置到我们刚æ‰�为service留的接å�£ä¸
server.add_filter(&filter); // clientä¹Ÿä¸€æ ·å�¯ä»¥è°ƒç”¨add_filter()
server.add_service(&impl);
filter.deinit();
return 0;
}
~~~
#### (4) æ“�ä½œæŒ‡æ ‡
我们在æ¯�次收到请求的时候,都把EchoRequest的大å°�统计到刚æ‰�åˆ›å»ºçš„ç›´æ–¹å›¾æŒ‡æ ‡ä¸Šï¼š
~~~cpp
class ExampleServiceImpl : public Example::Service
{
public:
void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override
{
resp->set_message("Hi back");
this->filter->histogram("echo_request_size")->observe(req->ByteSizeLong());
}
~~~
å�¯ä»¥çœ‹åˆ°ï¼Œæˆ‘们通过filter上的histogram()接å�£ï¼Œå°±å�¯ä»¥å¸¦ç�€åˆšæ‰�çš„å��å—åŽ»åˆ°æŒ‡æ ‡çš„æŒ‡é’ˆï¼Œå¹¶ä¸”é€šè¿‡observe()填入数æ�®å¤§å°�。
四�基本类型的获�接�如下:
~~~cpp
class RpcMetricsFilter : public RPCFilter
{
GaugeVar *gauge(const std::string& name);
CounterVar *counter(const std::string& name);
HistogramVar *histogram(const std::string& name);
SummaryVar *summary(const std::string& name);
~~~
如果找到,会返回四ç§�åŸºæœ¬æŒ‡æ ‡ç±»åž‹çš„æŒ‡é’ˆï¼Œå�¯ä»¥å¦‚ç¤ºä¾‹è¿›è¡Œä¸‹ä¸€æ¥æ“�作比如histogram的统计接å�£observe(),**注æ„�:ä¸�å˜åœ¨è¿™ä¸ªå��å—åˆ™ä¼šè¿”å›žç©ºæŒ‡é’ˆï¼Œå› æ¤è¦�ä¿�è¯�我们拿到的å�˜é‡�一定是æˆ�功创建过的**。
å››ç§�类型常用的æ“�作接å�£å¦‚ä¸Šæ–‡è¡¨æ ¼æ‰€ç¤ºï¼Œå…·ä½“å�¯ä»¥å�‚考[rpc_var.h](/src/var/rpc_var.h)。
值得说明一下Counter类型的接�:
~~~cpp
class CounterVar : public RPCVar
{
GaugeVar *add(const std::map& labels);
~~~
接å�£å�¯ä»¥å¯¹CounteræŒ‡æ ‡æ·»åŠ æŸ�个维度的Gauge值,å�„维度的统计是分开的,且labels为一个map,å�³å�¯ä»¥é€šè¿‡å¤šç»„label指定一个维度,比如:
~~~cpp
filter->counter("service_method_count")->add({"service", "Example"}, {"method", "Echo"}})->increase();
~~~
就�以获得一个针对`{service="Example",method="Echo"}`统计出�的数值。
#### (5) 自动上报
SRPCçš„æ�’ä»¶éƒ½æ˜¯è‡ªåŠ¨ä¸ŠæŠ¥çš„ï¼Œå› æ¤æ— 需用户调用任何接å�£ã€‚我们å°�试调用clientå�‘é€�请求产生一些统计数æ�®ï¼Œç„¶å�Žçœ‹çœ‹ä¸ŠæŠ¥å‡ºæ�¥çš„æ•°æ�®æ˜¯ä»€ä¹ˆã€‚
~~~sh
./srpc_pb_client
message: "Hi back"
message: "Hi back"
~~~
由于Prometheus是使用Pull模å¼�拉å�–,å�³ä¼šé€šè¿‡æˆ‘们注册到Prometheus的端å�£å’Œ/metrics进行拉å�–,也就是我们刚æ‰�åˆ�始化上报æ�’件需è¦�é…�对上端å�£çš„åŽŸå› ã€‚é€šè¿‡ä¸ŽPrometheus相å�Œçš„æ–¹å¼�,我们å�¯ä»¥æœ¬åœ°è®¿é—®ä¸€ä¸‹ï¼Œä¼šçœ‹åˆ°è¿™æ ·çš„一些数æ�®ï¼š
~~~sh
curl localhost:8080/metrics
# HELP total_request_count total request count
# TYPE total_request_count gauge
total_request_count 2.000000
# HELP total_request_method request method statistics
# TYPE total_request_method counter
total_request_method{method="Echo",service="Example"} 2.000000
# HELP total_request_latency request latency nano seconds
# TYPE total_request_latency summary
total_request_latency{quantile="0.500000"} 645078.500000
total_request_latency{quantile="0.900000"} 645078.500000
total_request_latency_sum 1290157.000000
total_request_latency_count 2
# HELP echo_request_size Echo request size
# TYPE echo_request_size histogram
echo_request_size_bucket{le="1.000000"}0
echo_request_size_bucket{le="10.000000"}0
echo_request_size_bucket{le="100.000000"}2
echo_request_size_bucket{le="+Inf"} 2
echo_request_size_sum 40.000000
echo_request_size_count 2
~~~
å�¯ä»¥çœ‹åˆ°ï¼Œæˆ‘们针对tutorial-02çš„client产生的两个请求,分别获得的四ç§�åŸºæœ¬æŒ‡æ ‡çš„ç»Ÿè®¡æ•°æ�®ã€‚å…¶ä¸histogram是我们创建的,而gaugeã€�counterã€�summary都是æ�’ä»¶ä¸è‡ªå¸¦çš„。æ�’ä»¶ä¸é»˜è®¤ç»Ÿè®¡çš„æ•°æ�®è¿˜ä¼šé™†ç»æ·»åŠ ï¼Œä»¥æ–¹ä¾¿å¼€å�‘者。
### 3. 上报Prometheus的特点
上报Prometheus主�特点刚�已�大概�述过:
1. 使用Pull模�,定期被收集;
2. 需�指定我们被收集数�的端�;
3. 通过/metrics返回具体数�内容;
4. æ•°æ�®å†…容为Prometheus所约定的stringæ ¼å¼�ï¼›
界��考如下:

### 4. 上报OpenTelemetry的特点
上报OpenTelemetry的主�特点:
1. 使用推�模�,定期��http请求;
2. �件需�填入�上报的URL;
3. 内部默认通过/v1/metrics上报数�;
4. 数�内容为OpenTelemetry所约定的[protobuf](/src/module/proto/opentelemetry_metrics.proto);
基本接��考:
~~~cpp
class RPCMetricsOTel : public RPCMetricsFilter
{
public:
RPCMetricsOTel(const std::string& url);
RPCMetricsOTel(const std::string& url, unsigned int redirect_max,
unsigned int retry_max, size_t report_threshold,
size_t report_interval);
~~~
用户�以指定累计多少个请求上报一次或者累计多久上报一次,默认为累计100个请求或1秒上报一次。
### 5. Var模�
上é�¢å�¯ä»¥çœ‹åˆ°ï¼Œæˆ‘们æ¯�个请求都会对全局唯一å��称指定的å�˜é‡�进行æ“�作,那么多线程调用时,对å¤�æ�‚çš„æŒ‡æ ‡ç±»åž‹ï¼ˆæ¯”å¦‚ç›´æ–¹å›¾æˆ–è€…é‡‡æ ·ï¼‰æ“�作会æˆ�为性能瓶颈å�—?
ç”æ¡ˆæ˜¯ä¸�ä¼šçš„ï¼Œå› ä¸ºé€šè¿‡filter获å�–å¯¹åº”æŒ‡æ ‡çš„æŽ¥å�£æ˜¯**thread local**的。
SRPC内部引入了线程安全的var结构,æ¯�次获å�–var时调用的接å�£ï¼Œæ‹¿åˆ°çš„都是thread localçš„æŒ‡æ ‡æŒ‡é’ˆï¼Œæ¯�æ¬¡ç»Ÿè®¡ä¹Ÿéƒ½æ˜¯åˆ†åˆ«æ”¶é›†åˆ°æœ¬çº¿ç¨‹ï¼Œå› æ¤å¤šçº¿ç¨‹æƒ…况下的统计ä¸�ä¼šé€ æˆ�对全局的争抢。而上报是异æ¥ä¸ŠæŠ¥çš„,在上报被触å�‘的时候,全局会通过expose()挨个把æ¯�个线程ä¸ç›¸åº”çš„æŒ‡æ ‡reduce()到一起,最å�Žé€šè¿‡å…·ä½“模å�—需è¦�çš„æ ¼å¼�进行上报。
sogou-srpc-ac65014/docs/docs-10-http-with-modules.md 0000664 0000000 0000000 00000030145 15252174244 0022165 0 ustar 00root root 0000000 0000000
## 10 - 带生��件的HttpServer和HttpClient
**srpc**æ��供带有æ�’件功能的**HttpServer**å’Œ**HttpClient**,å�¯ä»¥ä¸ŠæŠ¥**trace**å’Œ**metrcis**,用法和功能完全兼容**Workflow**ï¼Œæ·»åŠ æ�’件的用法也和srpcç›®å‰�çš„Serverå’ŒClientä¸€æ ·ï¼Œç”¨äºŽä½¿ç”¨**Http功能**å�Œæ—¶éœ€**è¦�采集traceã€�metricsç‰ä¿¡æ�¯å¹¶ä¸ŠæŠ¥**的场景。
æ¤åŠŸèƒ½æ›´åŠ é€‚ç”¨äºŽåŽŸå…ˆå·²ç»�使用Workflowæ”¶å�‘Http的开å�‘è€…ï¼Œå‡ ä¹Žä¸�改动现有代ç �å�³å�¯æ‹¥æœ‰ç”Ÿæ€�上报功能。
(1) 补充�件基本介�:
- **链路信�**:如何使用**trace**�件,并上报到[OpenTelemetry](https://opentelemetry.io)? �考:[docs-08-tracing.md](https://github.com/sogou/srpc/blob/master/docs/docs-08-tracing.md)
- **ç›‘æŽ§æŒ‡æ ‡**:如何使用**metrics**æ�’件,并且上报到[Prometheus](https://prometheus.io/)å’Œ[OpenTelemetry](https://opentelemetry.io)?å�‚考:[docs-09-metrics.md](https://github.com/sogou/srpc/blob/master/docs/docs-09-metrics.md)
(2) 补充Workflowä¸çš„Http用法:
- [Http客户端任务:wget](https://github.com/sogou/workflow/blob/master/docs/tutorial-01-wget.md)
- [ttp�务器:http_echo_server](https://github.com/sogou/workflow/blob/master/docs/tutorial-04-http_echo_server.md)
(3) 补充SRPCä¸å¸¦æ�’件上报功能的示例代ç �:
- [tutorial-17-http_server.cc](/tutorialtutorial-17-http_server.cc)
- [tutorial-18-http_client.cc](/tutorialtutorial-18-http_client.cc)
### 1. HttpServer用法
```cpp
int main()
{
// 1. æž„é€ æ–¹å¼�与Workflow类似,Workflow用法是:
// WFHttpServer server([](WFHttpTask *task){ /* process */ });
srpc::HttpServer server([](WFHttpTask *task){ /* process */ });
// 2. æ�’件与SRPCé€šç”¨ï¼Œæ·»åŠ æ–¹å¼�一致
srpc::RPCTraceOpenTelemetry otel("http://127.0.0.1:4318");
server.add_filter(&otel);
// 3. serverå�¯åŠ¨æ–¹å¼�与Workflow/SRPCä¸€æ ·
if (server.start(1412) == 0)
{
...
}
}
```
由于这里直接收å�‘Httpè¯·æ±‚ï¼Œå› æ¤æœ‰å‡ 个注æ„�点:
1. æ“�作的是`WFHttpTask`(与Workflowä¸çš„一致),而ä¸�是由Protobuf或者Thriftç‰IDL定义的结构体;
2. 接å�£ä¹Ÿä¸�å†�是RPC里定义的**Service**äº†ï¼Œå› æ¤ä¹Ÿæ— 需派生ServiceImpl实现RPC函数,而是直接给Serverä¼ ä¸€ä¸ª`process函数`ï¼›
3. `process函数`æ ¼å¼�也与Workflow一致:`std::function`ï¼›
4. process函数里拿到的�数是task,通过task->get_req()和task->get_resp()�以拿到请求与回�,分别是`HttpRequest`和`HttpResponse`,而其他上下文也在task而�**RPCContext**上;
### 2. HttpClient用法
```cpp
int main()
{
// 1. å…ˆæž„é€ ä¸€ä¸ªclient(与Workflow用法ä¸�å�Œï¼Œä¸ŽSRPC用法类似)
srpc::HttpClient client;
// 2. æ�’件与SRPCé€šç”¨ï¼Œæ·»åŠ æ–¹å¼�一致
srpc::RPCTraceDefault trace_log;
client.add_filter(&trace_log);
// 3. �出Http请求的方�与Workflow类似,Workflow用法是:
// WFHttpTask *task = WFTaskFactory::create_http_task(...);
// 函数���数�返回值与Workflow用法一致
WFHttpTask *task = client.create_http_task("http://127.0.0.1:1412",
REDIRECT_MAX,
RETRY_MAX,
[](WFHttpTask *task){ /* callback */ });
task->start();
...
return 0;
}
```
å�Œæ ·å‡ 个注æ„�点:
1. æ“�作的是`WFHttpTask`(与Workflowä¸çš„一致),而ä¸�是由Protobuf或者Thriftç‰IDL定义的结构;
2. 如果想拿到req,�以`task->get_req()`,拿到的是`HttpRequest`,而�是XXXRequest之类;
3. 和请求相关的上下文也��需�**RPCContext**了,都在task上;
4. callbackå‡½æ•°æ ¼å¼�也与Workflow一致:`std::function`ï¼›
### 3. Http�议采集的数�
**OpenTelemetry的trace数�官方文档**:
https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/
SRPC框架的trace模�已�采集以下内容,并会通过�自的filter以��的形��出。
**1. å…¬å…±æŒ‡æ ‡**
| æŒ‡æ ‡å�� | å�«ä¹‰ | 类型 | 例å� | 备注 |
|-------|-----|-----|------|-----|
|task.state| 框架状æ€�ç � | int | 0 |(以下简称state) |
|task.error| 框架错误ç � | int | 1 |state!=0æ—¶æ‰�有 |
|http.status_code| Http返回ç � | string | 200 | state=0æ—¶æ‰�有 |
|http.method| Http请求方法 | string | GET | |
|http.scheme| scheme | string | https | |
|http.request_content_length | 请求大� | int | 3840 | state=0时�有 |
|http.response_content_length| 回�大� | int | 141 | state=0时�有 |
|net.sock.family| �议地�� | string | inet | |
|net.sock.peer.addr| 远程地� | string | 10.xx.xx.xx | state=0时�有 |
|net.sock.peer.port| 远程端� | int | 8080 | state=0时�有 |
**2. ClientæŒ‡æ ‡**
| æŒ‡æ ‡å�� | å�«ä¹‰ | 类型 | 例å� | 备注 |
|-------|-----|-----|------|-----|
|srpc.timeout_reason| è¶…æ—¶åŽŸå› | int | 2 | state=WFT_STATE_SYS_ERROR(1)å’Œerror=ETIMEDOUT(116)æ—¶æ‰�有 |
|http.resend_count| 框架�试次数 | int | 0 | state=0时�有 |
|net.peer.addr| uri请求的地� | string | 10.xx.xx.xx | |
|net.peer.port| uri请求的端� | int | 80 | |
|* http.url| 请求的url | string | | |
state与error�考:[workflow/src/kernel/Communicator.h](workflow/src/kernel/Communicator.h)
timeout_reason�考:[workflow/src/kernel/CommRequest.h](workflow/src/kernel/CommRequest.h)
**3. ServeræŒ‡æ ‡**
| æŒ‡æ ‡å�� | å�«ä¹‰ | 类型 | 例å� | 备注 |
|-------|-----|-----|------|-----|
|net.host.name| æœ�务器å��ç§° | string | example.com | 虚拟主机å��,æ�¥è‡ªæ¯”如对方header里å�‘过æ�¥çš„Hostä¿¡æ�¯ç‰ |
|net.host.port| 监�的端� | int | 80 | |
|http.target| å®Œæ•´çš„è¯·æ±‚ç›®æ ‡ | string | /users/12314/?q=ddds | |
|http.client_ip| 原始client地å�€ | string | 10.x.x.x | 有时候会有,比如转å�‘场景下,headerä¸å¸¦æœ‰â€œX-Forwarded-Forâ€�ç‰ |
**OpenTelemetryçš„metricsæ•°æ�®å®˜æ–¹æ–‡æ¡£ï¼ˆSRPCæ£åœ¨æ”¯æŒ�ä¸ï¼‰**:
https://opentelemetry.io/docs/reference/specification/metrics/semantic_conventions/http-metrics/
### 4. 示例数�
以下通过client-server的调用,我们å�¯ä»¥çœ‹åˆ°è¿™æ ·çš„链路图:
```
[trace_id : 005028aa52fb0000005028aa52fb0002] [client]->[server]
timeline: 1681380123462593000 1681380123462895000 1681380123463045000 1681380123463213000
[client][begin].........................................................................[end]
span_id : 0400fb52aa285000
[server][begin].....................[end]
span_id : 00305c54aa285000
parent_span_id : 0400fb52aa285000
```
先通过`make tutorial`命令å�¯ä»¥æŠŠtutorial里的`http_server`å’Œ`http_client`编出æ�¥ï¼Œå…¶ä¸tutorial-17-http_server.ccä¸å�¯ä»¥æŠŠä¸ŠæŠ¥OpenTelemetryçš„æ�’件打开:
```cpp
srpc::RPCTraceOpenTelemetry otel("http://127.0.0.1:4318");
srpc::HttpServer server(process);
server.add_filter(&otel);
...
```
分别按照如下执行,�以看到我们的client通过RPCTraceDefault�件本地打�的trace信�:
```sh
./http_client
```
```
callback. state = 0 error = 0
Hello from server!
finish print body. body_len = 31
[SPAN_LOG] trace_id: 005028aa52fb0000005028aa52fb0002 span_id: 0400fb52aa285000 start_time: 1681380123462593000 finish_time: 1681380123463213000 duration: 620000(ns) http.method: GET http.request_content_length: 123145504128464 http.resend_count: 0 http.response_content_length: 31 http.scheme: http http.status_code: 200 net.peer.name: 127.0.0.1 net.peer.port: 1412 net.sock.family: inet net.sock.peer.addr: 127.0.0.1 net.sock.peer.port: 1412 component: srpc.srpc span.kind: srpc.client state: 0
^C
```
我们把server对OpenTelemetry上报的Protobuf内容也�时打�出�:
```sh
./http_server
```
```sh
http server get request_uri: /
[SPAN_LOG] trace_id: 005028aa52fb0000005028aa52fb0002 span_id: 00305c54aa285000 parent_span_id: 0400fb52aa285000 start_time: 1681380123462895000 finish_time: 1681380123463045000 duration: 150000(ns) http.method: GET http.request_content_length: 0 http.response_content_length: 31 http.scheme: http http.status_code: 200 http.target: / net.host.name: 127.0.0.1:1412 net.host.port: 1412 net.sock.family: inet net.sock.peer.addr: 127.0.0.1 net.sock.peer.port: 56591 component: srpc.srpc span.kind: srpc.server state: 0
resource_spans {
resource {
}
instrumentation_library_spans {
spans {
trace_id: "\000P(\252R\373\000\000\000P(\252R\373\000\002"
span_id: "\0000\\T\252(P\000"
parent_span_id: "\004\000\373R\252(P\000"
name: "GET"
kind: SPAN_KIND_SERVER
start_time_unix_nano: 1681380123462895000
end_time_unix_nano: 1681380123463045000
attributes {
key: "http.method"
value {
string_value: "GET"
}
}
attributes {
key: "http.request_content_length"
value {
int_value: 0
}
}
attributes {
key: "http.response_content_length"
value {
int_value: 31
}
}
attributes {
key: "http.scheme"
value {
string_value: "http"
}
}
attributes {
key: "http.status_code"
value {
int_value: 200
}
}
attributes {
key: "http.target"
value {
string_value: "/"
}
}
attributes {
key: "net.host.name"
value {
string_value: "127.0.0.1:1412"
}
}
attributes {
key: "net.host.port"
value {
int_value: 1412
}
}
attributes {
key: "net.sock.family"
value {
string_value: "inet"
}
}
attributes {
key: "net.sock.peer.addr"
value {
string_value: "127.0.0.1"
}
}
attributes {
key: "net.sock.peer.port"
value {
int_value: 56591
}
}
status {
}
}
}
}
```
å�¯ä»¥çœ‹åˆ°ç”¨æ³•å’ŒSRPC框架默认的trace模å�—æ˜¯ä¸€æ ·çš„ã€‚
### 5. å…¶ä»–
补充一些常è§�问题,帮助开å�‘䏿›´å¥½åœ°ç�†è§£è¿™ä¸ªæ¨¡å�—。
**Q1: 本次新增的HttpServer与HttpClient,与SRPCHttpServer/SRPCHttpClient有什么共�点/��点?**
共�点:
- 应用层�议相�,都是Http�议进行网络收�;
- 采集的数æ�®ä¾�æ�®ç›¸å�Œï¼Œéƒ½æ˜¯ä»ŽHttp Header收集和é€�ä¼ å‡ºåŽ»ï¼›
��点:
- 用法ä¸�å�Œï¼šæœ¬æ¬¡æ–°å¢žçš„Http模å�—是延ç»Workflowé£Žæ ¼çš„ç”¨æ³•ï¼Œæ¯”å¦‚WFHttpTask;而SRPCHttp/ThriftHttp/TRPCHttp的用法是RPC模å¼�,且包括了å�Œæ¥/异æ¥/å�Šå�Œæ¥çš„使用方å¼�ï¼›
- 接å�£ä¸�å�Œï¼šå‰�者用url直接定ä½�è¦�å�‘的请求,而server也是一个process函数作为处ç�†è¯·æ±‚的统一入å�£ï¼›è€ŒåŽŸå…ˆçš„æ¨¡å�—对Httpå�ªæ˜¯ç½‘络层é�¢çš„æ”¶å�‘,urlä¸çš„路由信æ�¯æ˜¯é€šè¿‡${service}å’Œ${method}进行拼接的,然å�ŽæŠŠProtobuf或者Thrift这个结构体作为Httpå��议的bodyå�‘出;
- 开�者接触的请求/回���:�者从task上拿出HttpRequest和HttpResponse,�者是Protobuf/Thrift里定义的Message;
- 框架级stateå’Œerror略有ä¸�å�Œï¼šå‰�者是task.stateå’Œtask.error,使用workflow的状æ€�ç �,比如0表示æˆ�功;而å�Žè€…是srpc.stateå’Œsrpc.error,使用SRPCçš„[状æ€�ç �](/src/rpc_basic.h),比如1表示æˆ�功;
**Q2: 和Workflow原生的Http�议是什么关系?**
srpc䏿–°å¢žçš„功能,开å�‘者拿到的也是Workflowä¸å®šä¹‰çš„WFHttpTaskï¼Œåœ¨å®žçŽ°ä¸Šè¿›è¡Œäº†è¡Œä¸ºæ´¾ç”Ÿï¼Œå› æ¤æ”¶å�‘æœŸé—´æœ‰å‡ ä¸ªåˆ‡é�¢å�¯ä»¥è¿›è¡Œä¸€äº›æ¨¡å�—化编程,这是和Workflowçš„Http相比更多的功能。
å�ªè¦�å¼€å�‘者把模å�—通过add_filter()åŠ å…¥åˆ°Server/Clientä¸ï¼Œé€šè¿‡äº§ç”Ÿçš„任务就是带有切é�¢åŠŸèƒ½çš„ï¼Œè€Œæœ�务开å�‘è€…æ— éœ€æ„ŸçŸ¥ã€‚
**Q3: 为什么放在SRPC项目ä¸ï¼Ÿ**
ç›®å‰�生æ€�æ�’件所需è¦�çš„å‡ ä¸ªåŠŸèƒ½éƒ½åœ¨SRPC项目ä¸ï¼ŒåŒ…括:
- 收集数æ�®çš„rpc_module,包括生æˆ�trace_idç‰é€šç”¨åŠŸèƒ½ï¼›
- 上报信æ�¯çš„rpc_filter,包括使用Protobufæ ¼å¼�上报OpenTelemetryï¼›
- ç»Ÿè®¡ç›‘æŽ§æŒ‡æ ‡çš„rpc_var;
sogou-srpc-ac65014/docs/en/ 0000775 0000000 0000000 00000000000 15252174244 0015500 5 ustar 00root root 0000000 0000000 sogou-srpc-ac65014/docs/en/docs-01-idl.md 0000664 0000000 0000000 00000001437 15252174244 0017743 0 ustar 00root root 0000000 0000000 [䏿–‡ç‰ˆ](/docs/docs-01-idl.md)
## 01 - RPC IDL
- Interface Description Languaue file
- Backward and forward compatibility
- Protobuf/Thrift
### Sample
You can follow the detailed example below:
- Take pb as an example. First, define an `example.proto` file with the ServiceName as `Example`.
- The name of the rpc interface is `Echo`, with the input parameter as `EchoRequest`, and the output parameter as `EchoResponse`.
- `EchoRequest` consists of two strings: `message` and `name`.
- `EchoResponse` consists of one string: `message`.
~~~proto
syntax="proto2";
message EchoRequest {
optional string message = 1;
optional string name = 2;
};
message EchoResponse {
optional string message = 1;
};
service Example {
rpc Echo(EchoRequest) returns (EchoResponse);
};
~~~
sogou-srpc-ac65014/docs/en/docs-02-service.md 0000664 0000000 0000000 00000002335 15252174244 0020632 0 ustar 00root root 0000000 0000000 [䏿–‡ç‰ˆ](/docs/docs-02-service.md)
## 02 - RPC Service
- It is the basic unit for SRPC services.
- Each service must be generated by one type of IDLs.
- Service is determined by IDL type, not by specific network communication protocol.
### Sample
You can follow the detailed example below:
- Use the same `example.proto` IDL above.
- Run the official `protoc example.proto --cpp_out=./ --proto_path=./` to get two files: `example.pb.h` and `example.pb.cpp`.
- Run the `srpc_generator protobuf ./example.proto ./` in SRPC to get `example.srpc.h`.
- Derive `Example::Service` to implement the rpc business logic, which is an RPC Service.
- Please note that this Service does not involve any concepts such as network, port, communication protocol, etc., and it is only responsible for completing the business logic that convert `EchoRequest` to `EchoResponse`.
~~~cpp
class ExampleServiceImpl : public Example::Service
{
public:
void Echo(EchoRequest *request, EchoResponse *response, RPCContext *ctx) override
{
response->set_message("Hi, " + request->name());
printf("get_req:\n%s\nset_resp:\n%s\n",
request->DebugString().c_str(),
response->DebugString().c_str());
}
};
~~~
sogou-srpc-ac65014/docs/en/docs-03-server.md 0000664 0000000 0000000 00000005121 15252174244 0020475 0 ustar 00root root 0000000 0000000 [䏿–‡ç‰ˆ](/docs/docs-03-server.md)
## 03 - RPC Server
- Each server corresponds to one port
- Each server corresponds to one specific network communication protocol
- One service may be added into multiple Servers
- One Server may have one or more Services, but the ServiceName must be unique within that Server
- Services from different IDLs can be added into the same Server
### Sample
You can follow the detailed example below:
- Follow the above `ExampleServiceImpl` Service
- First, create an RPC Server and determine the proto file.
- Then, create any number of Service instances and any number of Services for different protocols, and add these services to the Server through the `add_service()`interface.
- Finally, use `start()` or `serve()` to start the services in the Server and handle the upcoming rpc requests through the Server.
- Imagine that we can also derive more Serivce from `Example::Service`, which have different implementations of rpc `Echo`.
- Imagine that we can create N different RPC Servers on N different ports, serving on different network protocols.
- Imagine that we can use `add_service()` to add the same ServiceIMPL instance on different Servers, or we can use `add_service()` to add different ServiceIMPL instances on the same server.
- Imagine that we can use the same `ExampleServiceImpl`, serving BRPC-STD, SRPC-STD, SRPC-Http at three different ports at the same time.
- And we can use `add_service()` to add one `ExampleServiceImpl` related to Protobuf IDL and one `AnotherThriftServiceImpl` related to Thrift IDL to the same SRPC-STD Server, and the two IDLs work perfectly on the same port!
~~~cpp
int main()
{
SRPCServer server_srpc;
SRPCHttpServer server_srpc_http;
BRPCServer server_brpc;
ThriftServer server_thrift;
TRPCServer server_trpc;
TRPCHttpServer server_trpc_http;
ExampleServiceImpl impl_pb;
AnotherThriftServiceImpl impl_thrift;
server_srpc.add_service(&impl_pb);
server_srpc.add_service(&impl_thrift);
server_srpc_http.add_service(&impl_pb);
server_srpc_http.add_service(&impl_thrift);
server_brpc.add_service(&impl_pb);
server_thrift.add_service(&impl_thrift);
server_trpc.add_service(&impl_pb);
server_trpc_http.add_service(&impl_pb);
server_srpc.start(1412);
server_srpc_http.start(8811);
server_brpc.start(2020);
server_thrift.start(9090);
server_trpc.start(2022);
server_trpc_http.start(8822);
getchar();
server_trpc_http.stop();
server_trpc.stop();
server_thrift.stop();
server_brpc.stop();
server_srpc_http.stop();
server_srpc.stop();
return 0;
}
~~~
sogou-srpc-ac65014/docs/en/docs-04-client.md 0000664 0000000 0000000 00000004376 15252174244 0020461 0 ustar 00root root 0000000 0000000 [䏿–‡ç‰ˆ](/docs/docs-04-client.md)
## 04 - RPC Client
- Each Client corresponds to one specific target/one specific cluster
- Each Client corresponds to one specific network communication protocol
- Each Client corresponds to one specific IDL
### Sample
You can follow the detailed example below:
- Following the above example, the client is relatively simple and you can call the method directly.
- Use `Example::XXXClient` to create a client instance of some RPC. The IP+port or URL of the target is required.
- With the client instance, directly call the rpc function `Echo`. This is an asynchronous request, and the callback function will be invoked after the request is completed.
- For the usage of the RPC Context, please check [RPC Context](/docs/en/rpc.md#rpc-context).
~~~cpp
#include
#include "example.srpc.h"
#include "workflow/WFFacilities.h"
using namespace srpc;
int main()
{
Example::SRPCClient client("127.0.0.1", 1412);
EchoRequest req;
req.set_message("Hello!");
req.set_name("SRPCClient");
WFFacilities::WaitGroup wait_group(1);
client.Echo(&req, [&wait_group](EchoResponse *response, RPCContext *ctx) {
if (ctx->success())
printf("%s\n", response->DebugString().c_str());
else
printf("status[%d] error[%d] errmsg:%s\n",
ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg());
wait_group.done();
});
wait_group.wait();
return 0;
}
~~~
### Client startup parameters
Client can be started directly by passing in ip, port, or through client startup parameters.
The above example:
~~~cpp
Example::SRPCClient client("127.0.0.1", 1412);
~~~
is equivalent to:
~~~cpp
struct RPCClientParams param = RPC_CLIENT_PARAMS_DEFAULT;
param.host = "127.0.0.1";
param.port = 1412;
Example::SRPCClient client(¶m);
~~~
also equivalent to:
~~~cpp
struct RPCClientParams param = RPC_CLIENT_PARAMS_DEFAULT;
param.url = "srpc://127.0.0.1:1412";
Example::SRPCClient client(¶m);
~~~
Note that `RPC_CLIENT_PARAMS_DEFAULT` must be used to initialize the client's parameters, which contains a `RPCTaskParams`, including the default data_type, compress_type, retry_max and various timeouts. The specific struct can refer to [rpc_options.h](/src/rpc_options.h).
sogou-srpc-ac65014/docs/en/docs-05-context.md 0000664 0000000 0000000 00000006402 15252174244 0020660 0 ustar 00root root 0000000 0000000 [䏿–‡ç‰ˆ](/docs/docs-05-context.md)
## 05 - RPC Context
- RPCContext is used specially to assist asynchronous interfaces, and can be used in both Service and Client.
- Each asynchronous interface will provide a Context, which offers higher-level functions, such as obtaining the remote IP, the connection seqid, and so on.
- Some functions on Context are unique to Server or Client. For example, you can set the compression mode of the response data on Server, and you can obtain the success or failure status of a request on Client.
- On the Context, you can use ``get_series()`` to obtain the SeriesWork, which is seamlessly integrated with the asynchronous mode of Workflow.
### RPCContext API - Common
#### `long long get_seqid() const;`
One complete communication consists of request+response. The sequence id of the communication on the current socket connection can be obtained, and seqid=0 indicates the first communication.
#### `std::string get_remote_ip() const;`
Get the remote IP address. IPv4/IPv6 is supported.
#### `int get_peer_addr(struct sockaddr *addr, socklen_t *addrlen) const;`
Get the remote address. The in/out parameter is the lower-level data structure sockaddr.
#### `const std::string& get_service_name() const;`
Get RPC Service Name.
#### `const std::string& get_method_name() const;`
Get RPC Method Name.
#### `SeriesWork *get_series() const;`
Get the SeriesWork of the current ServerTask/ClientTask.
### RPCContext API - Only for client done
#### `bool success() const;`
For client only. The success or failure of the request.
#### `int get_status_code() const;`
For client only. The rpc status code of the request.
#### `const char *get_errmsg() const;`
For client only. The error info of the request.
#### `int get_error() const;`
For client only. The error code of the request.
#### `void *get_user_data() const;`
For client only. Get the user\_data of the ClientTask. If a user generates a task through the ``create_xxx_task()`` interface, the context can be recorded in the user_data field. You can set that field when creating the task, and retrieve it in the callback function.
### RPCContext API - Only for server process
#### `void set_data_type(RPCDataType type);`
For Server only. Set the data packaging type
- RPCDataProtobuf
- RPCDataThrift
- RPCDataJson
#### `void set_compress_type(RPCCompressType type);`
For Server only. Set the data compression type (note: the compression type for the Client is set on Client or Task)
- RPCCompressNone
- RPCCompressSnappy
- RPCCompressGzip
- RPCCompressZlib
- RPCCompressLz4
#### `void set_attachment_nocopy(const char *attachment, size_t len);`
For Server only. Set the attachment.
#### `bool get_attachment(const char **attachment, size_t *len) const;`
For Server only. Get the attachment.
#### `void set_reply_callback(std::function cb);`
For Server only. Set reply callback, which is called after the operating system successfully writes the data into the socket buffer.
#### `void set_send_timeout(int timeout);`
For Server only. Set the maximum time for sending the message, in milliseconds. -1 indicates unlimited time.
#### `void set_keep_alive(int timeout);`
For Server only. Set the maximum connection keep-alive time, in milliseconds. -1 indicates unlimited time.
sogou-srpc-ac65014/docs/en/docs-06-workflow.md 0000664 0000000 0000000 00000013506 15252174244 0021052 0 ustar 00root root 0000000 0000000 [䏿–‡ç‰ˆ](/docs/docs-06-workflow.md)
## 06 - Integrating with the asynchronous Workflow framework
### 1. Server
You can follow the detailed example below:
- Echo RPC sends an HTTP request to the upstream modules when it receives the request.
- After the request to the upstream modules is completed, the server populates the body of HTTP response into the message of the response and send a reply to the client.
- We don't want to block/occupy the handler thread, so the request to the upstream must be asynchronous.
- First, we can use `WFTaskFactory::create_http_task()` of the factory of Workflow to create an asynchronous http_task.
- Then, we use `ctx->get_series()` of the RPCContext to get the SeriesWork of the current ServerTask.
- Finally, we use the `push_back()` interface of the SeriesWork to append the http\_task to the SeriesWork.
~~~cpp
class ExampleServiceImpl : public Example::Service
{
public:
void Echo(EchoRequest *request, EchoResponse *response, RPCContext *ctx) override
{
auto *http_task = WFTaskFactory::create_http_task("https://www.sogou.com", 0, 0,
[request, response](WFHttpTask *task) {
if (task->get_state() == WFT_STATE_SUCCESS)
{
const void *data;
size_t len;
task->get_resp()->get_parsed_body(&data, &len);
response->mutable_message()->assign((const char *)data, len);
}
else
response->set_message("Error: " + std::to_string(task->get_error()));
printf("Server Echo()\nget_req:\n%s\nset_resp:\n%s\n",
request->DebugString().c_str(),
response->DebugString().c_str());
});
ctx->get_series()->push_back(http_task);
}
};
~~~
### 2. Client
You can follow the detailed example below:
- We send two requests in parallel. One is an RPC request and the other is an HTTP request.
- After both requests are finished, we initiate a calculation task again to calculate the sum of the squares of the two numbers.
- First, use `create_Echo_task()` of the RPC Client to create an rpc\_task, which is an asynchronous RPC network request.
- Then, use `WFTaskFactory::create_http_task` and `WFTaskFactory::create_go_task` in the the factory of Workflow to create an asynchronous network task http\_task and an asynchronous computing task calc\_task respectively.
- Finally, use the serial-parallel graph to organize three asynchronous tasks, in which the multiplication sign indicates parallel tasks and the greater than sign indicates serial tasks and then execute ``start()``.
~~~cpp
void calc(int x, int y)
{
int z = x * x + y * y;
printf("calc result: %d\n", z);
}
int main()
{
Example::SRPCClient client("127.0.0.1", 1412);
auto *rpc_task = client.create_Echo_task([](EchoResponse *response, RPCContext *ctx) {
if (ctx->success())
printf("%s\n", response->DebugString().c_str());
else
printf("status[%d] error[%d] errmsg:%s\n",
ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg());
});
auto *http_task = WFTaskFactory::create_http_task("https://www.sogou.com", 0, 0, [](WFHttpTask *task) {
if (task->get_state() == WFT_STATE_SUCCESS)
{
std::string body;
const void *data;
size_t len;
task->get_resp()->get_parsed_body(&data, &len);
body.assign((const char *)data, len);
printf("%s\n\n", body.c_str());
}
else
printf("Http request fail\n\n");
});
auto *calc_task = WFTaskFactory::create_go_task(calc, 3, 4);
EchoRequest req;
req.set_message("Hello!");
req.set_name("1412");
rpc_task->serialize_input(&req);
WFFacilities::WaitGroup wait_group(1);
SeriesWork *series = Workflow::create_series_work(http_task, [&wait_group](const SeriesWork *) {
wait_group.done();
});
series->push_back(rpc_task);
series->push_back(calc_task);
series->start();
wait_group.wait();
return 0;
}
~~~
### 3. Upstream
SRPC can directly use any component of Workflow, the most commonly used is [Upstream](https://github.com/sogou/workflow/blob/master/docs/en/about-upstream.md), any kind of client of SRPC can use Upstream.
You may use the example below to construct a client that can use Upstream through parameters:
```cpp
#include "workflow/UpstreamManager.h"
int main()
{
// 1. create upstream and add server instances
UpstreamManager::upstream_create_weighted_random("echo_server", true);
UpstreamManager::upstream_add_server("echo_server", "127.0.0.1:1412");
UpstreamManager::upstream_add_server("echo_server", "192.168.10.10");
UpstreamManager::upstream_add_server("echo_server", "internal.host.com");
// 2. create params and fill upstream name
RPCClientParams client_params = RPC_CLIENT_PARAMS_DEFAULT;
client_params.host = "echo_server";
client_params.port = 1412; // this port only used when upstream URI parsing and will not affect the select of instances
// 3. construct client by params, the rest of usage is similar as other tutorials
Example::SRPCClient client(&client_params);
...
```
If we use the **ConsistentHash** or **Manual** upstream, we often need to distinguish different tasks for the selection algorithm. At this time, we may use the `int set_uri_fragment(const std::string& fragment);` interface on the client task to set request-level related information.
This field is the fragment in the URI. For the semantics, please refer to [RFC3689 3.5-Fragment](https://datatracker.ietf.org/doc/html/rfc3986#section-3.5), any infomation that needs to use the fragment (such as other information included in some other selection policy), you may use this field as well.
sogou-srpc-ac65014/docs/en/docs-07-srpc-http.md 0000664 0000000 0000000 00000016233 15252174244 0021125 0 ustar 00root root 0000000 0000000 [䏿–‡ç‰ˆ](/docs/docs-07-srpc-http.md)
## 07 - Use Http with SRPC, TRPC and Thrift
**srpc** supports **HTTP** protocol, which make it convenient to communicate to other language. Just fill the **IDL** content into **HTTP body**, and fill the **IDL** type(**json**/**protobuf**/**thrift**) into **Http header** , we communicate with other frameworks through the **HTTP** protocol.
- **SRPCHttpServer**, **TRPCHttpServer** and **ThriftHttpServer** can receive HTTP requests from client implemented by any language.
- **SRPCHttpClient**, **TRPCHttpClient** and **ThriftHttpClient** can send HTTP requests to servers implemented by any language.
- **HTTP header**: `Content-Type` needs to be set as `application/json` when body is json, or set as `application/x-protobuf` when body is protobuf, or set as `application/x-thrift` when body is thrift;
- **HTTP body**: If there is **bytes** type in body, **json** needs to be encoded with **base64**.
### 1. Example
To implement **SRPCHttpClient** is so simple that we just need to refer to [tutorial-02-srpc_pb_client.cc](https://github.com/sogou/srpc/blob/master/tutorial/tutorial-02-srpc_pb_client.cc) or [tutorial-09-client_task.cc](https://github.com/sogou/srpc/blob/master/tutorial/tutorial-09-client_task.cc) and change the `SRPCClient` into `SRPCHttpClient`.
[README.md](/docs/en/README.md#6-run) demonstrates how to use **curl** to send a request to **SRPCHttpServer**, and below we give an example to demonstrate how to use **python** as a client to send a request to **TRPCHttpServer**.
**proto file :**
```proto
syntax="proto3"; // proto2 or proto3 are both supported
package trpc.test.helloworld;
message AddRequest {
string message = 1;
string name = 2;
bytes info = 3;
int32 error = 4;
};
message AddResponse {
string message = 1;
};
service Batch {
rpc Add(AddRequest) returns (AddResponse);
};
```
**python client :**
```py
import json
import requests
from base64 import b64encode
class Base64Encoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, bytes):
return b64encode(o).decode()
return json.JSONEncoder.default(self, o)
headers = {'Content-Type': 'application/json'}
req = {
'message': 'hello',
'name': 'k',
'info': b'i am binary'
}
print(json.dumps(req, cls=Base64Encoder))
ret = requests.post(url = "http://localhost:8800/trpc.test.helloworld.Batch/Add",
headers = headers, data = json.dumps(req, cls=Base64Encoder))
print(ret.json())
```
### 2. Splicing the path of HTTP request
[README.md](/docs/en/README_cn.md#6-run) shows that the path is concatenated by the service name and the rpc name. Moreover, for the above proto file example with the package name `package trpc.test.helloworld;`, the package name also needs to be spliced into the path. The splicing paths of **SRPCHttp** and **TRPCHttpClient** are different. For **ThriftHttp**, multi service is not supported in SRPC thrift, so no path is required.
Let's take **curl** as an example:
Request to **SRPCHttpServer**:
```sh
curl 127.0.0.1:8811/trpc/test/helloworld/Batch/Add -H 'Content-Type: application/json' -d '{...}'
```
Request to **TRPCHttpServer**:
```sh
curl 127.0.0.1:8811/trpc.test.helloworld.Batch/Add -H 'Content-Type: application/json' -d '{...}'
```
Request to **ThriftHttpServer**:
```sh
curl 127.0.0.1:8811 -H 'Content-Type: application/json' -d '{...}'
```
### 3. HTTP status code
SRPC supports server to set HTTP status code in `process()`, the interface is `set_http_code(int code)` on **RPCContext**. This error code is only valid if the framework can handle the request correctly, otherwise it will be set to the SRPC framework-level error code.
**Usage :**
~~~cpp
class ExampleServiceImpl : public Example::Service
{
public:
void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override
{
if (req->name() != "workflow")
ctx->set_http_code(404); // set HTTP status code as 404
else
resp->set_message("Hi back");
}
};
~~~
**CURL command :**
~~~sh
curl -i 127.0.0.1:1412/Example/Echo -H 'Content-Type: application/json' -d '{message:"from curl",name:"CURL"}'
~~~
**Result :**
~~~sh
HTTP/1.1 404 Not Found
SRPC-Status: 1
SRPC-Error: 0
Content-Type: application/json
Content-Encoding: identity
Content-Length: 21
Connection: Keep-Alive
~~~
**Notice :**
We can still distinguish this request is correct at framework-level by `SRPC-Status: 1` in the header of the returned result, and `404` is the status code from the server.
### 4. HTTP Header
Use the following three interfaces to get or set HTTP header:
~~~cpp
bool get_http_header(const std::string& name, std::string& value) const;
bool set_http_header(const std::string& name, const std::string& value);
bool add_http_header(const std::string& name, const std::string& value);
~~~
For **server**, these interfaces are on `RPCContext`.
For **client**, we need to set the **HTTP header on the request** through `RPCClientTask`, and get the **HTTP header on the response** on the `RPCContext` of the callback function. The usage is as follows:
~~~cpp
int main()
{
Example::SRPCHttpClient client("127.0.0.1", 80);
EchoRequest req;
req.set_message("Hello, srpc!");
auto *task = client.create_Echo_task([](EchoResponse *resp, RPCContext *ctx) {
if (ctx->success())
{
std::string value;
ctx->get_http_header("server_key", value); // get the HTTP header on response
}
});
task->serialize_input(&req);
task->set_http_header("client_key", "client_value"); // set the HTTP headers on request
task->start();
wait_group.wait();
return 0;
}
~~~
### 5. proto3 transport format problem
When IDL is protobuf, by default proto3 primitive fields with default values will be omitted in JSON output because there is no **optional** and **required** distinction. For example, an int32 field set to 0 will be omitted.So proto3 cannot be recognized as being set, and it cannot be emitted when serialized. JsonPrintOptions can use a flag to override the default behavior and print primitive fields regardless of their values.
SRPC provides some interfaces on **RPCContext上** to implement this and other flags on [JsonPrintOptions](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.util.json_util#JsonPrintOptions). The specific interface and usage description can be refered in:[rpc_context.h](/src/rpc_context.h)
**Exmaple :**
```cpp
class ExampleServiceImpl : public Example::Service
{
public:
void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override
{
resp->set_message("Hi back");
resp->set_error(0); // the type of error is int32 and 0 is the default value of int32
ctx->set_json_always_print_fields_with_no_presence(true); // all fields with no precense
ctx->set_json_add_whitespace(true); // add spaces, line breaks and indentation
}
};
```
**Origin output :**
```sh
{"message":"Hi back"}
```
**After set json options on RPCContext:**
```sh
{
"message": "Hi back",
"error": 0
}
```
sogou-srpc-ac65014/docs/en/docs-08-tracing.md 0000664 0000000 0000000 00000011133 15252174244 0020623 0 ustar 00root root 0000000 0000000 [䏿–‡ç‰ˆ](/docs/docs-08-tracing.md)
## 08 - Report Tracing to OpenTelemetry
**SRPC** supports generating and reporting tracing and spans, which can be reported in multiple ways, including exporting data locally or to [OpenTelemetry](https://opentelemetry.io).
Since **SRPC** follows the [data specification](https://github.com/open-telemetry/opentelemetry-specification) of **OpenTelemetry** and the specification of [w3c trace context](https://www.w3.org/TR/trace-context/), now we can use **RPCTraceOpenTelemetry** as the reporting plugin.
The report conforms to the **Workflow** style, which is pure asynchronous task and therefore has no performance impact on the RPC requests and services.
### 1. Usage
After the plugin `RPCTraceOpenTelemetry` is constructed, we can use `add_filter()` to add it into **server** or **client**.
For [tutorial-02-srpc_pb_client.cc](https://github.com/sogou/srpc/blob/master/tutorial/tutorial-02-srpc_pb_client.cc), add 2 lines like the following :
```cpp
int main()
{
Example::SRPCClient client("127.0.0.1", 1412);
RPCTraceOpenTelemetry span_otel("http://127.0.0.1:4318");
client.add_filter(&span_otel);
...
}
```
For [tutorial-01-srpc_pb_server.cc](https://github.com/sogou/srpc/blob/master/tutorial/tutorial-01-srpc_pb_server.cc), add the similar 2 lines. We also add the local plugin to print the reported data on the screen :
```cpp
int main()
{
SRPCServer server;
RPCTraceOpenTelemetry span_otel("http://127.0.0.1:4318");
server.add_filter(&span_otel);
RPCTraceDefault span_log; // this plugin will print the tracing info on the screen
server.add_filter(&span_log);
...
}
```
make the tutorial and run both server and client, we can see some tracing information on the screen.
We can find the span_id: **04d070f537f17d00** in client become parent_span_id: **04d070f537f17d00** in server:
### 2. Traces on Jaeger
Open the show page of Jaeger, we can find our service name **Example** and method name **Echo**. Here are two span nodes, which were reported by server and client respectively.
As what we saw on the screen, the client reported span_id: **04d070f537f17d00** and server reported span_id: **00202cf737f17d00**, these span and the correlated tracing information can be found on Jaeger, too.
### 3. About Parameters
How long to collect a trace, and the number of reported retries and other parameters can be specified through the constructor parameters of `RPCTraceOpenTelemetry`. Code reference: [src/module/rpc_trace_filter.h](https://github.com/sogou/srpc/blob/master/src/module/rpc_trace_filter.h#L238)
The default value is to collect up to 1000 trace information per second, and features such as transferring tracing information through the srpc framework transparently have also been implemented, which also conform to the specifications.
### 4. Attributes
We can also use `add_attributes()` to add some other informations as OTEL_RESOURCE_ATTRIBUTES.
Please notice that our service name "Example" is set also thought this attributes, the key of which is `service.name`. If `service.name` is also provided in OTEL_RESOURCE_ATTRIBUTES by users, then srpc service name takes precedence. Refers to : [OpenTelemetry#resource](https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/src/OpenTelemetry#resource)
### 5. Log and Baggage
SRPC provides `log()` and `baggage()` to carry some user data through span.
API :
```cpp
void log(const RPCLogVector& fields);
void baggage(const std::string& key, const std::string& value);
```
As a server, we can use `RPCContext` to add log annotation:
```cpp
class ExampleServiceImpl : public Example::Service
{
public:
void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override
{
resp->set_message("Hi back");
ctx->log({{"event", "info"}, {"message", "rpc server echo() end."}});
}
};
```
As a client, we can use `RPCClientTask` to add log on span:
```cpp
srpc::SRPCClientTask *task = client.create_Echo_task(...);
task->log({{"event", "info"}, {"message", "log by rpc client echo()."}});
```
sogou-srpc-ac65014/docs/en/docs-09-metrics.md 0000664 0000000 0000000 00000027610 15252174244 0020652 0 ustar 00root root 0000000 0000000 [䏿–‡ç‰ˆ](/docs/docs-09-metrics.md)
## 09 - Report Metrics to OpenTelemetry / Prometheus
**Metrics** are common monitoring requirements. **SRPC** supports the generation and statistics of Metrics, and reports through various way, including reporting to [Prometheus](https://prometheus.io/) and [OpenTelemetry](https://opentelemetry.io).
The report conforms to the **Workflow** style, which is pure asynchronous task or pull mode, and therefore has no performance impact on the RPC requests and services.
This document will introduce the concept of Metrics, explain the interface of [tutorial-16](/tutorial/tutorial-16-server_with_metrics.cc), the usage of reporting to Prometheus, the usage of reporting to OpenTelemetry, and introduce the Var module which uses thread local to speed up performance.
### 1. Introduction
The metrics type of **Prometheus** can be refered through the official documentation : [Concepts - Metrics](https://prometheus.io/docs/concepts/metric_types/) and [Type of Metrics](https://prometheus.io/docs/tutorials/understanding_metric_types/)。
The basic metrics concept of **OpenTelemetry** can be refer through [(data specification)](https://github.com/open-telemetry/opentelemetry-specification) and [Metrics的datamodel.md](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/datamodel.md).
The concepts of the four basic metrics are the same, so SRPC provides the most basic support for these four metrics, which can correspond to the reported data of Prometheus and OpenTelemetry respectively. Please refer to the table below:
|Metrics type|Prometheus|OpenTelemetry| create api in SRPC | some operations in SRPC |
|-------|----------|------------|-----------------|-------------------|
|single value| Gauge | Gauge | create_gauge(name, help); | void increase(); void decrease();|
|counter| Counter | Sum | create_counter(name, help); |GaugeVar *add(labels);|
|histogram |Histogram | Histogram | create_histogram(name, help, buckets); |void observe(data);|
|samples | Summary | Summary | create_summary(name, help, quantiles); |void observe(data);|
Basic description of the four types of metrics:
1. **Single value**: A simple value that can be increased or decreased.
2. **Counter**: A counter is a cumulative metrics, which can be added several **labels** to distinguish the values under different labels.
3. **Histogram**: A histogram samples observations (usually things like request durations or response sizes) and counts them in configurable buckets, so it is necessary to pass in **buckets** to inform the interval to be divided.
For example, if the input bucket value is { 1, 10, 100 }, then we can get the data distributed in these four intervals { 0 - 1, 0 - 10, 0 - 100, 0 - +Inf }. It also provides a sum and the total count of all observed values.
4. **Samples**: Similar to histogram, but **quantiles** are passed in, such as {0.5, 0.9} and their precision. It mainly used in scenarios where the specific value of the data distribution is not known in advance (In contrast, histograms require specific values to be passed in).
Sampling comes with a **time window**. You may specify the statistics window by **max_age** and the number of buckets by **age_bucket**. The default in SRPC is using 5 bucket in 60 seconds to switch times window.
Considering the statistical complexity of **sampling Summary**, **Histogram** is recommended in preference instead.
For convenience, these four type of metrics in SRPC are of type **double**.
### 2. Usage
The example shows usage via [tutorial-16-server_with_metrics.cc](/tutorial/tutorial-16-server_with_metrics.cc). Althought this example is for the server, the usage on the client is the same.
#### (1) create filter
Here we use Prometheus to be reported, thus we need the filter **RPCMetricsPull** as the plugin.
~~~cpp
#include "srpc/rpc_metrics_filter.h" // metrics filter header file
int main()
{
SRPCServer server;
ExampleServiceImpl impl;
RPCMetricsPull filter; // create a filter
filter.init(8080); // the port for Prometheus to pull metrics data
~~~
#### (2) add metrics
By default, there are some common metrics in the filter **RPCMetricsPull**, including the number of overall request, the number of requests in different service or method as labels, and the quantiles of latency.
Users can add any metrics they want. Here we add a histogram namee "echo_request_size" to count the size of requests. This metric describes the information as "Echo request size", and the data bucket division is { 1, 10, 100 }.
~~~cpp
filter.create_histogram("echo_request_size", "Echo request size", {1, 10, 100});
~~~
More illutrations:
**Timing to add metrics**
Metrics can be added any time, even after the server/client has been running. But **must be added before we use** this metrics, otherwise a NULL pointer will be obtained when we acquire the metrics by name.
**The name of the metrics**
The name of metrics is **globally unique** (no matter which of the four basic types), and **can only contain uppercase and lowercase letters and underscores, ie a~z, A~Z, _** . The creation will fail and return NULL if we create with an existed metric‘s name.
We will use this unique name to operate the metrics after it has been created.
**Other APIs to create metrics**
You can check this header file [rpc_metrics_filter.h](/src/module/rpc_metrics_filter.h): `class RPCMetricsFilter`.
#### (3) add filter into server / client
The following example keeps the pointer of this filter in ExampleServiceImpl because we may need the APIs on it to operate our metrics. It`s just one of the sample usages and users may use it in the way you used to.
~~~cpp
class ExampleServiceImpl : public Example::Service
{
public:
void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) overrideï¼›
void set_filter(RPCMetricsPull *filter) { this->filter = filter; }
private:
RPCMetricsPull *filter; // keep the pointer of filter and add set_filter(), notice that it's not APIs of SRPC
};
~~~
The usage in main:
~~~cpp
int main()
{
...
impl.set_filter(&filter); // keep the filter throught the APIs in service we mentioned above
server.add_filter(&filter); // client can also call add_filter()
server.add_service(&impl);
filter.deinit();
return 0;
}
~~~
#### (4) operation with the metrics
Every time we receive a request, we get cumulative calculation of the size of the EchoRequest with the histogram we just created.
~~~cpp
class ExampleServiceImpl : public Example::Service
{
public:
void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override
{
resp->set_message("Hi back");
this->filter->histogram("echo_request_size")->observe(req->ByteSizeLong());
}
~~~
As has been shown, we use the API `histogram()` on filter to get the histogram metrics by it's name. Then we use `observe()` to fill in the data size.
Let's take a look at the APIs to acquire four types of metrics.
~~~cpp
class RpcMetricsFilter : public RPCFilter
{
GaugeVar *gauge(const std::string& name);
CounterVar *counter(const std::string& name);
HistogramVar *histogram(const std::string& name);
SummaryVar *summary(const std::string& name);
~~~
If the metric is found by this name, the relevant type of pointer will be returned and we may use it to do the relevant next step, such as calling the observe() of historgram we did above. **Notice : If the metric name does not exist, Null will be returned, so we must ensure that the metrics we want must have be successfully created.**
Common APIs can be refered to the table above. For more details please refer to [rpc_var.h](/src/var/rpc_var.h).
It's worth taking a look at the API on Counter:
~~~cpp
class CounterVar : public RPCVar
{
GaugeVar *add(const std::map& labels);
~~~
This can add the Gauge value of a certain dimension into this Counter. The statistics of each dimension are separated. The labels are a map, that is, a dimension can be specified through multiple groups of labels, for example:
~~~cpp
filter->counter("service_method_count")->add({"service", "Example"}, {"method", "Echo"}})->increase();
~~~
Adn we can get the statistics calculated by `{service="Example",method="Echo"}`.
#### (5) Reporting
Reporting in SRPC filters is automatic, so users don't need to do anything. Next we will use a client to make some requests and check the format of data which will be reported.
~~~sh
./srpc_pb_client
message: "Hi back"
message: "Hi back"
~~~
Since Prometheus will use Pull mode, which means will pull through the PORT we set before and the URL path as /metrics. When pulling with the same way as Prometheus do, we can get these with a simple local request.
~~~sh
curl localhost:8080/metrics
# HELP total_request_count total request count
# TYPE total_request_count gauge
total_request_count 2.000000
# HELP total_request_method request method statistics
# TYPE total_request_method counter
total_request_method{method="Echo",service="Example"} 2.000000
# HELP total_request_latency request latency nano seconds
# TYPE total_request_latency summary
total_request_latency{quantile="0.500000"} 645078.500000
total_request_latency{quantile="0.900000"} 645078.500000
total_request_latency_sum 1290157.000000
total_request_latency_count 2
# HELP echo_request_size Echo request size
# TYPE echo_request_size histogram
echo_request_size_bucket{le="1.000000"}0
echo_request_size_bucket{le="10.000000"}0
echo_request_size_bucket{le="100.000000"}2
echo_request_size_bucket{le="+Inf"} 2
echo_request_size_sum 40.000000
echo_request_size_count 2
~~~
It can be seen that we obtained four types of metrics by sending 2 requests with tutorial-02 client, among which the histogram was created by us and gauge, counter and summary were created by filter. The default metrics will be increased for the convinience of users.
### 3. Features of reporting to Prometheus
The main features of reporting to Prometheus have just been described:
1. Use the Pull mode and will collected regularly;
2. PORT is required to be pull;
3. Return data content through /metrics;
4. The data content is in the string format defined by Prometheus;
The interface reference is as follows:

### 4. Features of reporting OpenTelemetry
The main features of reporting to OpenTelemetry:
1. Use the push mode and will send http requests regularly;
2. URL is required to make report;
3. Reports data through /v1/metrics;
4. The data content is [protobuf] (/src/module/proto/opentelemetry_metrics.proto) as agreed by OpenTelemetry;
Basic APIs references:
~~~cpp
class RPCMetricsOTel : public RPCMetricsFilter
{
public:
RPCMetricsOTel(const std::string& url);
RPCMetricsOTel(const std::string& url, unsigned int redirect_max,
unsigned int retry_max, size_t report_threshold,
size_t report_interval);
~~~
The time interval to make report and the numbers of requests to make a report can be specified. By default, the filter will accumulate 100 requests or report once every second.
### 5. Var
Would it be the bottle neck of performance when every request use the globally unique name to get the metrics, especially multi-threaded operations with the complicated metrics (histogram or summary)?
The answer is NEVER. Because the APIs for getting metrics through filters are **thread-local**.
SRPC builds a thread-local var system. Every time we get a var, we get its thread-local var pointer. Therefore every time we calculate will only be collected by the current thread. That's why multiple threads will not be conflicted by global mutex. Moreover, since reporting is always asynchronous, a global API expose() will get all the vars in all the threads and reduce them together when reports. Lastly the data will be reported in the format with specific filter.
sogou-srpc-ac65014/docs/en/installation.md 0000664 0000000 0000000 00000007564 15252174244 0020537 0 ustar 00root root 0000000 0000000 # Installation
## 1. Install from source code on Linux
SRPC depends on the following modules : **CMake**(Require >= v3.6.0), **OpenSSL**(Recommend >= v1.1.0), **Protobuf**(Require >= v3.5.0)
Will get the following output:
1. static library: libsrpc.a (or dylib)
2. dynamic library: libsrpc.so (or dll)
3. tool for generating code: srpc_generator
- **cmake**
~~~sh
git clone --recursive https://github.com/sogou/srpc.git
cd srpc
make
make install
# compile tutorial
cd tutorial
make
~~~
- **bazel**
~~~sh
git clone --recursive https://github.com/sogou/srpc.git
cd srpc
bazel build ...
# It can compile out lib and src generator and all tutorials into bazel-bin/
~~~
In addition, we can use srpc_tools to install and deploy skeleton project. Refer to the usage:[srpc/tools/README.md](srpc/tools/README.md)
Workflow, snappy and lz4 can also be found via installed package in the system. If the submodule dependencies are not pulled in third\_party by '--recursive', they will be searched from the default installation path of the system. The version of snappy is required v1.1.6 or above.
If you need to install **Protobuf** from source code, refer to the command:
~~~sh
git clone -b 3.20.x https://github.com/protocolbuffers/protobuf.git protobuf.3.20
cd protobuf.3.20
sh autogen.sh
./configure
make -j4
make install
~~~
## 2. Debian Linux
SRPC has been packaged for Debian. It is currently in Debian sid (unstable) but will eventually be placed into the stable repository.
In order to access the unstable repository, you will need to edit your /etc/apt/sources.list file.
sources.list has the format: `deb `
Simply add the 'unstable' sub branch to your repo:
~~~~sh
deb http://deb.debian.org/ main contrib non-free
-->
deb http://deb.debian.org/ unstable main contrib non-free
~~~~
Once that is added, update your repo list and then you should be able to install it:
~~~~sh
sudo apt-get update
~~~~
To install the srpc library for development purposes:
~~~~sh
sudo apt-get install libsrpc-dev
~~~~
To install the srpc library for deployment:
~~~~sh
sudo apt-get install libsrpc
~~~~
## 3. Fedora Linux
SRPC has been packaged for Fedora.
To install the srpc library for development purposes:
~~~~sh
sudo dnf install srpc-devel
~~~~
To install the srpc library for deployment:
~~~~sh
sudo dnf install srpc
~~~~
## 4. Windows
There is no difference in the srpc code under the Windows version, but users need to use the [windows branch](https://github.com/sogou/workflow/tree/windows) of Workflow
Moreover, srpc_tools is not supported on Windows.
## 5. MacOS
- Install dependency `OpenSSL`
```
brew install openssl
```
- Install `CMake`
```
brew install cmake
```
- Specify the `OpenSSL` environment variable
The soft link of OpenSSL will not be automatically built after installation by brew, because LibreSSL is provided by default under MacOS. We need to manually configure the execution path, compilation path, and find_package path into the environment variables of cmake. You can execute `brew info openssl` to view relevant information, or configure it as follows:
```
echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.bash_profile
echo 'export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"' >> ~/.bash_profile
echo 'export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"' >> ~/.bash_profile
echo 'export PKG_CONFIG_PATH="/usr/local/opt/openssl@1.1/lib/pkgconfig"' >> ~/.bash_profile
echo 'export OPENSSL_ROOT_DIR=/usr/local/opt/openssl' >> ~/.bash_profile
echo 'export OPENSSL_LIBRARIES=/usr/local/opt/openssl/lib' >> ~/.bash_profile
```
If you use `zsh`, you need one more step to load the bash configuration:
```
echo 'test -f ~/.bash_profile && source ~/.bash_profile' >> ~/.zshrc
source ~/.zshrc
```
The remaining steps are no different from compiling on Linux.
sogou-srpc-ac65014/docs/en/rpc.md 0000664 0000000 0000000 00000061427 15252174244 0016620 0 ustar 00root root 0000000 0000000 [䏿–‡ç‰ˆ](/docs/rpc.md)
## Comparison of basic features
| RPC | IDL | Communication | Network data | Compression | Attachment | Semi-synchronous | Asynchronous | Streaming |
| --------------------------- | --------- | ------------- | ------------ | -------------------- | ------------- | ---------------- | ------------- | ------------- |
| Thrift Binary Framed | Thrift | TCP | Binary | Not supported | Not supported | Supported | Not supported | Not supported |
| Thrift Binary HttpTransport | Thrift | HTTP | Binary | Not supported | Not supported | Supported | Not supported | Not supported |
| GRPC | PB | HTTP2 | Binary | gzip/zlib/lz4/snappy | Supported | Not supported | Supported | Supported |
| BRPC Std | PB | TCP | Binary | gzip/zlib/lz4/snappy | Supported | Not supported | Supported | Supported |
| SRPC Std | PB/Thrift | TCP | Binary/JSON | gzip/zlib/lz4/snappy | Supported | Supported | Supported | Not supported |
| SRPC Std HTTP | PB/Thrift | HTTP | Binary/JSON | gzip/zlib/lz4/snappy | Supported | Supported | Supported | Not supported |
## Basic concepts
- Communication layer: TCP/TPC\_SSL/HTTP/HTTPS/HTTP2
- Protocol layer: Thrift-binary/BRPC-std/SRPC-std/SRPC-http/tRPC-std/tRPC-http
- Compression layer: no compression/gzip/zlib/lz4/snappy
- Data layer: PB binary/Thrift binary/JSON string
- IDL serialization layer: PB/Thrift serialization
- RPC invocation layer: Service/Client IMPL
## RPC Global
- Get srpc version `srpc::SRPCGlobal::get_instance()->get_srpc_version()`
## RPC Status Code
| Name | Value | Description |
| ----------------------------------- | ----- | -------------------------------------------------------- |
| RPCStatusUndefined | 0 | Undefined |
| RPCStatusOK | 1 | Correct/Success |
| RPCStatusServiceNotFound | 2 | Cannot find the RPC service name |
| RPCStatusMethodNotFound | 3 | Cannot find the RPC function name |
| RPCStatusMetaError | 4 | Meta error/ parsing failed |
| RPCStatusReqCompressSizeInvalid | 5 | Incorrect size for the request when compressing |
| RPCStatusReqDecompressSizeInvalid | 6 | Incorrect size for the request when decompressing |
| RPCStatusReqCompressNotSupported | 7 | The compression type for the request is not supported |
| RPCStatusReqDecompressNotSupported | 8 | The decompression type for the request is not supported |
| RPCStatusReqCompressError | 9 | Failed to compress the request |
| RPCStatusReqDecompressError | 10 | Failed to decompress the request |
| RPCStatusReqSerializeError | 11 | Failed to serialize the request by IDL |
| RPCStatusReqDeserializeError | 12 | Failed to deserialize the request by IDL |
| RPCStatusRespCompressSizeInvalid | 13 | Incorrect size for the response when compressing |
| RPCStatusRespDecompressSizeInvalid | 14 | Incorrect size for the response when compressing |
| RPCStatusRespCompressNotSupported | 15 | The compression type for the response is not supported |
| RPCStatusRespDecompressNotSupported | 16 | The decompression type for the response not supported |
| RPCStatusRespCompressError | 17 | Failed to compress the response |
| RPCStatusRespDecompressError | 18 | Failed to decompress the response |
| RPCStatusRespSerializeError | 19 | Failed to serialize the response by IDL |
| RPCStatusRespDeserializeError | 20 | Failed to deserialize the response by IDL |
| RPCStatusIDLSerializeNotSupported | 21 | IDL serialization type is not supported |
| RPCStatusIDLDeserializeNotSupported | 22 | IDL deserialization type is not supported |
| RPCStatusURIInvalid | 30 | Illegal URI |
| RPCStatusUpstreamFailed | 31 | Upstream is failed |
| RPCStatusSystemError | 100 | System error |
| RPCStatusSSLError | 101 | SSL error |
| RPCStatusDNSError | 102 | DNS error |
| RPCStatusProcessTerminated | 103 | Program exited&terminated |
## RPC IDL
- Interface Description Languaue file
- Backward and forward compatibility
- Protobuf/Thrift
### Sample
You can follow the detailed example below:
- Take pb as an example. First, define an `example.proto` file with the ServiceName as `Example`.
- The name of the rpc interface is `Echo`, with the input parameter as `EchoRequest`, and the output parameter as `EchoResponse`.
- `EchoRequest` consists of two strings: `message` and `name`.
- `EchoResponse` consists of one string: `message`.
~~~proto
syntax="proto2";
message EchoRequest {
optional string message = 1;
optional string name = 2;
};
message EchoResponse {
optional string message = 1;
};
service Example {
rpc Echo(EchoRequest) returns (EchoResponse);
};
~~~
## RPC Service
- It is the basic unit for SRPC services.
- Each service must be generated by one type of IDLs.
- Service is determined by IDL type, not by specific network communication protocol.
### Sample
You can follow the detailed example below:
- Use the same `example.proto` IDL above.
- Run the official `protoc example.proto --cpp_out=./ --proto_path=./` to get two files: `example.pb.h` and `example.pb.cpp`.
- Run the `srpc_generator protobuf ./example.proto ./` in SRPC to get `example.srpc.h`.
- Derive `Example::Service` to implement the rpc business logic, which is an RPC Service.
- Please note that this Service does not involve any concepts such as network, port, communication protocol, etc., and it is only responsible for completing the business logic that convert `EchoRequest` to `EchoResponse`.
~~~cpp
class ExampleServiceImpl : public Example::Service
{
public:
void Echo(EchoRequest *request, EchoResponse *response, RPCContext *ctx) override
{
response->set_message("Hi, " + request->name());
printf("get_req:\n%s\nset_resp:\n%s\n",
request->DebugString().c_str(),
response->DebugString().c_str());
}
};
~~~
## RPC Server
- Each server corresponds to one port
- Each server corresponds to one specific network communication protocol
- One service may be added into multiple Servers
- One Server may have one or more Services, but the ServiceName must be unique within that Server
- Services from different IDLs can be added into the same Server
### Sample
You can follow the detailed example below:
- Follow the above `ExampleServiceImpl` Service
- First, create an RPC Server and determine the proto file.
- Then, create any number of Service instances and any number of Services for different protocols, and add these services to the Server through the `add_service()`interface.
- Finally, use `start()` or `serve()` to start the services in the Server and handle the upcoming rpc requests through the Server.
- Imagine that we can also derive more Serivce from `Example::Service`, which have different implementations of rpc `Echo`.
- Imagine that we can create N different RPC Servers on N different ports, serving on different network protocols.
- Imagine that we can use `add_service()` to add the same ServiceIMPL instance on different Servers, or we can use `add_service()` to add different ServiceIMPL instances on the same server.
- Imagine that we can use the same `ExampleServiceImpl`, serving BRPC-STD, SRPC-STD, SRPC-Http at three different ports at the same time.
- And we can use `add_service()` to add one `ExampleServiceImpl` related to Protobuf IDL and one `AnotherThriftServiceImpl` related to Thrift IDL to the same SRPC-STD Server, and the two IDLs work perfectly on the same port!
~~~cpp
int main()
{
SRPCServer server_srpc;
SRPCHttpServer server_srpc_http;
BRPCServer server_brpc;
ThriftServer server_thrift;
TRPCServer server_trpc;
TRPCHttpServer server_trpc_http;
ExampleServiceImpl impl_pb;
AnotherThriftServiceImpl impl_thrift;
server_srpc.add_service(&impl_pb);
server_srpc.add_service(&impl_thrift);
server_srpc_http.add_service(&impl_pb);
server_srpc_http.add_service(&impl_thrift);
server_brpc.add_service(&impl_pb);
server_thrift.add_service(&impl_thrift);
server_trpc.add_service(&impl_pb);
server_trpc_http.add_service(&impl_pb);
server_srpc.start(1412);
server_srpc_http.start(8811);
server_brpc.start(2020);
server_thrift.start(9090);
server_trpc.start(2022);
server_trpc_http.start(8822);
getchar();
server_trpc_http.stop();
server_trpc.stop();
server_thrift.stop();
server_brpc.stop();
server_srpc_http.stop();
server_srpc.stop();
return 0;
}
~~~
## RPC Client
- Each Client corresponds to one specific target/one specific cluster
- Each Client corresponds to one specific network communication protocol
- Each Client corresponds to one specific IDL
### Sample
You can follow the detailed example below:
- Following the above example, the client is relatively simple and you can call the method directly.
- Use `Example::XXXClient` to create a client instance of some RPC. The IP+port or URL of the target is required.
- With the client instance, directly call the rpc function `Echo`. This is an asynchronous request, and the callback function will be invoked after the request is completed.
- For the usage of the RPC Context, please check [RPC Context](/docs/en/rpc.md#rpc-context).
~~~cpp
#include
#include "example.srpc.h"
#include "workflow/WFFacilities.h"
using namespace srpc;
int main()
{
Example::SRPCClient client("127.0.0.1", 1412);
EchoRequest req;
req.set_message("Hello!");
req.set_name("SRPCClient");
WFFacilities::WaitGroup wait_group(1);
client.Echo(&req, [&wait_group](EchoResponse *response, RPCContext *ctx) {
if (ctx->success())
printf("%s\n", response->DebugString().c_str());
else
printf("status[%d] error[%d] errmsg:%s\n",
ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg());
wait_group.done();
});
wait_group.wait();
return 0;
}
~~~
## RPC Context
- RPCContext is used specially to assist asynchronous interfaces, and can be used in both Service and Client.
- Each asynchronous interface will provide a Context, which offers higher-level functions, such as obtaining the remote IP, the connection seqid, and so on.
- Some functions on Context are unique to Server or Client. For example, you can set the compression mode of the response data on Server, and you can obtain the success or failure status of a request on Client.
- On the Context, you can use ``get_series()`` to obtain the SeriesWork, which is seamlessly integrated with the asynchronous mode of Workflow.
### RPCContext API - Common
#### `long long get_seqid() const;`
One complete communication consists of request+response. The sequence id of the communication on the current socket connection can be obtained, and seqid=0 indicates the first communication.
#### `std::string get_remote_ip() const;`
Get the remote IP address. IPv4/IPv6 is supported.
#### `int get_peer_addr(struct sockaddr *addr, socklen_t *addrlen) const;`
Get the remote address. The in/out parameter is the lower-level data structure sockaddr.
#### `const std::string& get_service_name() const;`
Get RPC Service Name.
#### `const std::string& get_method_name() const;`
Get RPC Method Name.
#### `SeriesWork *get_series() const;`
Get the SeriesWork of the current ServerTask/ClientTask.
#### `bool get_http_header(const std::string& name, std::string& value);`
If using the HTTP protocol, get the value in the HTTP header according to the name
### RPCContext API - Only for client done
#### `bool success() const;`
For client only. The success or failure of the request.
#### `int get_status_code() const;`
For client only. The rpc status code of the request.
#### `const char *get_errmsg() const;`
For client only. The error info of the request.
#### `int get_error() const;`
For client only. The error code of the request.
#### `void *get_user_data() const;`
For client only. Get the user\_data of the ClientTask. If a user generates a task through the ``create_xxx_task()`` interface, the context can be recorded in the user_data field. You can set that field when creating the task, and retrieve it in the callback function.
### RPCContext API - Only for server process
#### `void set_data_type(RPCDataType type);`
For Server only. Set the data packaging type
- RPCDataProtobuf
- RPCDataThrift
- RPCDataJson
#### `void set_compress_type(RPCCompressType type);`
For Server only. Set the data compression type (note: the compression type for the Client is set on Client or Task)
- RPCCompressNone
- RPCCompressSnappy
- RPCCompressGzip
- RPCCompressZlib
- RPCCompressLz4
#### `void set_attachment_nocopy(const char *attachment, size_t len);`
For Server only. Set the attachment.
#### `bool get_attachment(const char **attachment, size_t *len) const;`
For Server only. Get the attachment.
#### `void set_reply_callback(std::function cb);`
For Server only. Set reply callback, which is called after the operating system successfully writes the data into the socket buffer.
#### `void set_send_timeout(int timeout);`
For Server only. Set the maximum time for sending the message, in milliseconds. -1 indicates unlimited time.
#### `void set_keep_alive(int timeout);`
For Server only. Set the maximum connection keep-alive time, in milliseconds. -1 indicates unlimited time.
#### `bool set_http_code(int code);`
For Server only. If using the HTTP protocol, set the http status code. Only works if the srpc framework handles correctly.
#### `bool set_http_header(const std::string& name, const std::string& value);`
For Server only. If using the HTTP protocol, set into http header. If the name is set, old value will be overwritten.
#### `bool add_http_header(const std::string& name, const std::string& value);`
For Server only. If using the HTTP protocol, set into http header. Will keep multiple values if the name is set.
#### `void log(const RPCLogVector& fields);`
For Server only. For transparent data transmission, please refer to the log semantics in OpenTelemetry.
#### `void baggage(const std::string& key, const std::string& value);`
For Server only. For transparent data transmission, please refer to the baggage semantics in OpenTelemetry.
#### `void set_json_add_whitespace(bool on);`
For Server only. For JsonPrintOptions, whether to add white space and so on to make JSON output easy to read.
#### `void set_json_always_print_enums_as_ints(bool flag);`
For Server only. For JsonPrintOptions, whether to always print enums as ints.
#### `void set_json_preserve_proto_field_names(bool flag);`
For Server only. For JsonPrintOptions, whether to preserve proto field names.
#### `void set_json_always_print_fields_with_no_presence(bool flag);`
For Server only. For JsonPrintOptions, whether to always print fields with no presence.
## RPC Options
### Server Parameters
| Name | Default value | Description |
| ------------------------ | --------------------------- | ------------------------------------------------------------ |
| max\_connections | 2000 | The maximum number of connections for the Server. The default value is 2000. |
| peer\_response\_timeout: | 10\* 1000 | The maximum time for each read IO. The default value is 10 seconds. |
| receive\_timeout: | -1 | The maximum read time of each complete message. -1 indicates unlimited time. |
| keep\_alive\_timeout: | 60 \* 1000 | Maximum keep_alive time for idle connection. -1 indicates no disconnection. 0 indicates short connection. The default keep-alive time for long connection is 60 seconds |
| request\_size\_limit | 2LL \* 1024 \* 1024 \* 1024 | Request packet size limit. Maximum: 2 GB |
| ssl\_accept\_timeout: | 10 \* 1000 | SSL connection timeout. The default value is 10 seconds. |
### Client Parameters
| Name | Default value | Description |
| ------------ | ----------------------------------- | ------------------------------------------------------------ |
| host | "" | Target host, which can be an IP address or a domain name |
| port | 1412 | Destination port number. The default value is 1412 |
| is\_ssl | false | SSL switch. The default value is off |
| url | "" | The URL is valid only when the host is empty. URL will override three items: host/port/is\_ssl |
| task\_params | The default configuration for tasks | See the configuration items below |
### Task Parameters
| Name | Default value | Description |
| -------------------- | ---------------- | ------------------------------------------------------------ |
| send\_timeout | -1 | Maximum time for sending the message. The default value is unlimited time. |
| receive\_timeout | -1 | Maximum time for sending a response. The default value is unlimited time. |
| watch\_timeout | 0 | The maximum time of the first reply from remote. The default value is 0, indicating unlimited time. |
| keep\_alive\_timeout | 30 \* 1000 | The maximum keep-alive time for idle connections. -1 indicates no disconnection. The default value is 30 seconds. |
| retry\_max | 0 | Maximum number of retries. The default value is 0, indicating no retry. |
| compress\_type | RPCCompressNone | Compression type. The default value is no compression. |
| data\_type | RPCDataUndefined | The data type of network packets. The default value is consistent with the default value for RPC. For SRPC-Http protocol, it is JSON, and for others, they are the corresponding IDL types. |
## Integrating with the asynchronous Workflow framework
### 1. Server
You can follow the detailed example below:
- Echo RPC sends an HTTP request to the upstream modules when it receives the request.
- After the request to the upstream modules is completed, the server populates the body of HTTP response into the message of the response and send a reply to the client.
- We don't want to block/occupy the handler thread, so the request to the upstream must be asynchronous.
- First, we can use `WFTaskFactory::create_http_task()` of the factory of Workflow to create an asynchronous http_task.
- Then, we use `ctx->get_series()` of the RPCContext to get the SeriesWork of the current ServerTask.
- Finally, we use the `push_back()` interface of the SeriesWork to append the http\_task to the SeriesWork.
~~~cpp
class ExampleServiceImpl : public Example::Service
{
public:
void Echo(EchoRequest *request, EchoResponse *response, RPCContext *ctx) override
{
auto *http_task = WFTaskFactory::create_http_task("https://www.sogou.com", 0, 0,
[request, response](WFHttpTask *task) {
if (task->get_state() == WFT_STATE_SUCCESS)
{
const void *data;
size_t len;
task->get_resp()->get_parsed_body(&data, &len);
response->mutable_message()->assign((const char *)data, len);
}
else
response->set_message("Error: " + std::to_string(task->get_error()));
printf("Server Echo()\nget_req:\n%s\nset_resp:\n%s\n",
request->DebugString().c_str(),
response->DebugString().c_str());
});
ctx->get_series()->push_back(http_task);
}
};
~~~
### 2. Client
You can follow the detailed example below:
- We send two requests in parallel. One is an RPC request and the other is an HTTP request.
- After both requests are finished, we initiate a calculation task again to calculate the sum of the squares of the two numbers.
- First, use `create_Echo_task()` of the RPC Client to create an rpc\_task, which is an asynchronous RPC network request.
- Then, use `WFTaskFactory::create_http_task` and `WFTaskFactory::create_go_task` in the the factory of Workflow to create an asynchronous network task http\_task and an asynchronous computing task calc\_task respectively.
- Finally, use the serial-parallel graph to organize three asynchronous tasks, in which the multiplication sign indicates parallel tasks and the greater than sign indicates serial tasks and then execute ``start()``.
~~~cpp
void calc(int x, int y)
{
int z = x * x + y * y;
printf("calc result: %d\n", z);
}
int main()
{
Example::SRPCClient client("127.0.0.1", 1412);
auto *rpc_task = client.create_Echo_task([](EchoResponse *response, RPCContext *ctx) {
if (ctx->success())
printf("%s\n", response->DebugString().c_str());
else
printf("status[%d] error[%d] errmsg:%s\n",
ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg());
});
auto *http_task = WFTaskFactory::create_http_task("https://www.sogou.com", 0, 0, [](WFHttpTask *task) {
if (task->get_state() == WFT_STATE_SUCCESS)
{
std::string body;
const void *data;
size_t len;
task->get_resp()->get_parsed_body(&data, &len);
body.assign((const char *)data, len);
printf("%s\n\n", body.c_str());
}
else
printf("Http request fail\n\n");
});
auto *calc_task = WFTaskFactory::create_go_task(calc, 3, 4);
EchoRequest req;
req.set_message("Hello!");
req.set_name("1412");
rpc_task->serialize_input(&req);
WFFacilities::WaitGroup wait_group(1);
SeriesWork *series = Workflow::create_series_work(http_task, [&wait_group](const SeriesWork *) {
wait_group.done();
});
series->push_back(rpc_task);
series->push_back(calc_task);
series->start();
wait_group.wait();
return 0;
}
~~~
### 3. Upstream
SRPC can directly use any component of Workflow, the most commonly used is [Upstream](https://github.com/sogou/workflow/blob/master/docs/en/about-upstream.md), any kind of client of SRPC can use Upstream.
You may use the example below to construct a client that can use Upstream through parameters:
```cpp
#include "workflow/UpstreamManager.h"
int main()
{
// 1. create upstream and add server instances
UpstreamManager::upstream_create_weighted_random("echo_server", true);
UpstreamManager::upstream_add_server("echo_server", "127.0.0.1:1412");
UpstreamManager::upstream_add_server("echo_server", "192.168.10.10");
UpstreamManager::upstream_add_server("echo_server", "internal.host.com");
// 2. create params and fill upstream name
RPCClientParams client_params = RPC_CLIENT_PARAMS_DEFAULT;
client_params.host = "srpc::echo_server"; // this scheme only used when upstream URI parsing
client_params.port = 1412; // this port only used when upstream URI parsing and will not affect the select of instances
// 3. construct client by params, the rest of usage is similar as other tutorials
Example::SRPCClient client(&client_params);
...
```
If we use the **ConsistentHash** or **Manual** upstream, we often need to distinguish different tasks for the selection algorithm. At this time, we may use the `int set_uri_fragment(const std::string& fragment);` interface on the client task to set request-level related information.
This field is the fragment in the URI. For the semantics, please refer to [RFC3689 3.5-Fragment](https://datatracker.ietf.org/doc/html/rfc3986#section-3.5), any infomation that needs to use the fragment (such as other information included in some other selection policy), you may use this field as well.
sogou-srpc-ac65014/docs/images/ 0000775 0000000 0000000 00000000000 15252174244 0016343 5 ustar 00root root 0000000 0000000 sogou-srpc-ac65014/docs/images/benchmark1.png 0000664 0000000 0000000 00000116366 15252174244 0021101 0 ustar 00root root 0000000 0000000 ‰PNG
IHDR * Šv)È sRGB ®Îé gAMA ±�üa pHYs à ÃÇo¨d œ‹IDATx^íý ˜åy¯�›Åƒ ÆvÇvbo8!1v8^b±?ãÄ'^‚ç²uìÏûû;ù|t|£
�X…02»�Ò°Š]!v„�6 I !‰UhA!¡åùæ©éwôv©º§»§¥™îû¾®çÒLuUuõÝo¿ê÷7oU½Ç êA Ô
‚ ¨
P7 n4 @Ý h €ºAÐ uƒ êA Ô
‚ ¨
P7 n4 @Ý h h~ñ‹_ØÀ�»«µµµðô7ÒïåÉ'Ÿ\x¤y7n\÷ë_´hQa) ô €ŽŽŽ¢�©ACÿ%4x5Á�ÿûú믖¾fòäÉÝmÒ È‚ ˆƒ†¬€!¤ËÿZÞÖÖ–9˜óÇJmãq^¶lYaͽñc;v¬
4¨{;ÿÙ—ùc=áÇãÇCؾÜó6þ^†×^
îÇߟôû×Óûvï½÷Êû£F�²öööÂšÅøûé�lj{©õ{">¿Ëá³Âºéj¶öÒÜQúóêí%Ý7Äm’0 JAÐ Ð ô4ø`!<^ª|€‘²Ö‹Ë–YÓÚãi索~ñ€']Í2À‰u•âƒûxÀ_ª²B€JÚIzà_.Äòª$TJã¡EØÞC‡rÄm¿T¹�ZŽ£Qˆß£¬÷ÝÝ”k3Þ.ñ¾zzo y!h h ª âAƒ‡ñ_½ÓƒÈ°Ü+àùLÏ2ˆ‰C¼Ä>~_^?ްÿ9 ¾½ÿNÐ�MzF‚»îü}‹Û�Wz Xª�¤Ã„ðžú>òx0ïÏéëxÛ¨v€ïÛÆûì‰Rm?ü…><–n§ÍD©÷5‡zþ^¼}¸Ó¬m ÊAÐ Ð Ô48ñà4=˽bâ¦W˜Õ�^^í 3ï#”›‘j‚÷ÿU:0ÆÄm!ý~—k'ñ Ýg8ñú0Õ(ªÙg¹¶‡^Þ>›‘rïkìÏD €z@Ð Ð ô&hˆ·õŠ)µÜ‰�B<›!Fk!kß=þ‚¶}à”5U<¬ãƒqZÂ_týøã¿Þg
tã×[½ž?¦š ! §„˜ôà;>õ¥\;‰m,kYo‰_s|l¥è©í‡Ç¼ÒmÉ�OÓðòßKµ9½á½ò÷ÐwŸa†�¿ÿ�¸¥gŽÄ�ùÏiü½Œg
ùsù¾³‚’ôºþsxïÜ_XžUþxìÏŸ§§`¯Ô±Çí>]é×XÍë €þ A @ÐÓ`+†AH žÑ�Þ6,÷J?faæ•5À®”ô $èJá¦r�x è„åñóxùë�g¾Ï4ñk£z>Œÿë Ž°núùÒÄÏ·‡rí$~,gÜvÂòRƒôJpŸa_åÂ’˜rm?ªÄÄÁLV¥Ûoì7®Øeüü•ºL?Vêy¼¼�y[ÄûI—{‰ÛrVùãiGî½Üg·Ô±—kÿñzÕ¼> è¿4 4 µ
¾]<@Hÿå5,÷ЉŸÏ+„ ñ2_§VÒû僔¬¿zÆeÏëëŃÀx –¥+¸‹Ã„x»xàæûÔûùþ{x¬'âuã÷8‹Rë–j'N¼M<ó"^ª§Áj)â¿–÷ôq[ñc ¤ýÇǾNhS¾¯ðyðñsøòð^ƾ¼âç/ç²Ôcqøám=|®b/ñL¡p¬î;~
~¡:åŽÅ‰åûöå逯§}9~,±ÇplÕ¾> è¿4 4 ¥[�¬�DºÒÓ›�øñ€?W<€óC ^?èÔ‚oøãбñà%=ð‰½Ä�…e^>°ñ��z€(>=" ÆËƒ/ÅóâA|OÄë¦�#M©uýçôr?®x¹W|œþxì%.o'a0Y q»J»(Eì¸TŃZÇ�¥ÛiüZÃcñúéÏIüÚãÏ^–Ë@©Çâöž&«-„ß}»ržËKÀ_WâòeqHVɾ¼M‡ub_Õ¾> è¿4 4 ñ`+ìâÁAV•0dWz ?–ÀÕŠäãAHzÿ=½¶P¥‘,â™>8
Ä¥ðºÏ¨fð¯[êý”Z·’ׇ<1ø ;=XÍj�Y”š-ÒqÛÏ*ô¦áYí)«Âk�×O·ëRŸ½Øeúý(õXXÖS…&ý:¼�¥g%9åŽ%Æ=y0·s/O³Ú{Ö¾bé÷1,ï©*
™ ïBÐ Ð ”ì²¾M<(Lÿ¥Ö �¥ËŸ#kÀï¯Ô€´VÒÇfį\ů/^^Šx°åƒ`ü„ßãéݪçwâ�dOÄë7¥ˆ_[ü>•z-¾¾b+ ú€4žà•ègÏÈj‹¥Èjû~œñìˆøýrbWå*ü%?Þ—?_L©Ï^Ög.Pê±°¬\ùg à^³^K: ,w,¥ð÷ Þg0zÚWܶҮÂòr¿> è¿4 4 ¥;�Rƒƒx;ÿ‚Ÿ†Ç¼*!ž2ÿ¿^į#¼Îž>Y„õ½J|ýçxàUjp^ÏçwâAdOÄÇQnF@˜xÅáA-¯¥ñs¤œYÄÔJ�@©¶ïm9¦âcˆ½VrlåÖ/õüå\–z,,óªD¼�Ư7þü•;–rį;lWn_ñcYaWxÌ ‚ € Ô`'Pnpÿµ6ýXXîU > �· ®Y™8 ¨4Üë{•"�û 8ö2ªçwâ�^O¤„Rè8J·•rí¤ZÂ~¼ÒVšø´‰ô샞(×öã°(~,vPÉÌ›rëdz7âç(ç2~_ãÇâ š°%{ŒÃ¦Zß×ø8{šÑàÇŽ?+´tzûú ÿ@Ð Ð ô&hˆÃPÇ„å^•N|`Ì|pásžR!„/÷Çý8ãu| “5PñÃ2/ßäøc¾ß.&^¿q¸*=V>ì²â�upK<`öÇ}YL¹v’…ïÏËß›øyâÁw%áK¼~%ÿ˜rm?ýÞ„×·y÷_×ÀÛœO¼¯ôú¾½ñq{•ÚÆÛs©mbÏñcÞöÒíß÷¯ïû�ßc_'lK†…ö\øcþ\¾Ÿ°Ì÷o÷¥ÚHܶ|_YTûú ÿBÐ Ð ô&hp|ð� a™W¥ø %k€ž®x�¿–R•ÈøïYëÅSjyšx°*k¥z~/+Y/àîãmÊ•=˜pß�žÚIšžžËÛA—#n•¬ÓSÛ�À>Ð
¤üéJï«T›v�áçJ·‰½Åž+ùìÄëg=*~_=@ˆÇëÄïyVùvñg5«�øãñ6é
^ª}} Ð!h h zlÅfÿ9M