使用bazel搭建brpc开发环境

demo地址

github.com/wuhuZhao/br…

brpc

brpc是百度开源的一个rpc框架,源码地址

bazel

bazel是谷歌开源的一个代码构建工具,可以编译c++\java\go等主流语言,使用文档地址

搭建过程

虽然brpc的使用文档的指引是使用cmake或者make来进行编译,然后跑example的,但是brpc的源码也是可以通过bazel进行编译的,因为brpc的源码里是有Build文件的,因此我们使用bazel来继承brpc进行开发。

本机环境

本文使用vscode remote ssh 在ubuntu20进行开发

root@5669353fc2c1:~# cat /etc/issue
Ubuntu 20.04.3 LTS \n \l
复制代码

安装bazel

经过作者踩坑,最好安装Bazel 1.0.0,不然构建的时候可能会因为protobuf的版本导致bazel编译失败,其他版本不确定兼容性。 不使用本文安装bazel的办法可以参考这个使用文档的其他方法

首先打开bazel的realese的页面,找到1.0.0的版本,然后copy一波链接,然后在bash上操作一下

apt-get update -y
apt-get install wget -y
apt-get install g++ unzip zip openssl
#这个是copy的链接,如果访问很慢,建议去gitee找一下clone的仓库
wget https://github.com/bazelbuild/bazel/releases/download/1.0.0/bazel-1.0.0-installer-linux-x86_64.sh 
sudo chmod +x bazel-1.0.0-installer-linux-x86_64.sh
./bazel-1.0.0-installer-linux-x86_64.sh
复制代码

安装完以后看一下bazel的版本,确定为1.0.0

root@5669353fc2c1:~# bazel --version
bazel 1.0.0
复制代码

在ubuntu上创建一个新的目录进行环境构建

root@5669353fc2c1:~# mkdir juejin-example
#然后vscode打开这个新的目录,如果不用vscode可忽略code命令
root@5669353fc2c1:~# code juejin-example/
复制代码

使用vscode的话可以先安装一下c++插件和bazel的官方插件,比较方便去书写bazel的BUILD和WORKSPACE文件 WORKSPACE文件: 每个项目都必须有一个WORKSPACE来控制整个项目的依赖 BUILD文件: 指定需要编译的lib库和二进制文件

我们先来创建WORKSPACE文件

root@5669353fc2c1:~/juejin-example# code WORKSPACE
复制代码

然后在WORKSPACE文件中使用git_repository规则拉到brpc的源码,便于后续编译,因为国内网速确实不太行,所以换了gitee的brpc源码地址来下载,同时也需要把brpc依赖的gflags、protobuf、glog、leveldb、googletest、openssl、zlib的依赖拉下来

load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
  name = "com_google_protobuf",
  strip_prefix = "protobuf-3.6.1.3",
  sha256 = "9510dd2afc29e7245e9e884336f848c8a6600a14ae726adb6befdb4f786f0be2",
  type = "zip",
  url = "https://github.com/protocolbuffers/protobuf/archive/v3.6.1.3.zip",
)

http_archive(
    name = "com_github_gflags_gflags",
    strip_prefix = "gflags-46f73f88b18aee341538c0dfc22b1710a6abedef",
    url = "https://github.com/gflags/gflags/archive/46f73f88b18aee341538c0dfc22b1710a6abedef.tar.gz",
)

bind(
    name = "gflags",
    actual = "@com_github_gflags_gflags//:gflags",
)

http_archive(
    name = "com_github_google_leveldb",
    build_file = "//:depBUILD/leveldb.BUILD",
    strip_prefix = "leveldb-a53934a3ae1244679f812d998a4f16f2c7f309a6",
    url = "https://github.com/google/leveldb/archive/a53934a3ae1244679f812d998a4f16f2c7f309a6.tar.gz"
)

http_archive(
    name = "com_github_google_glog",
    build_file = "depBUILD/glog.BUILD",
    strip_prefix = "glog-a6a166db069520dbbd653c97c2e5b12e08a8bb26",
    url = "https://github.com/google/glog/archive/a6a166db069520dbbd653c97c2e5b12e08a8bb26.tar.gz"
)

http_archive(
    name = "com_google_googletest",
    strip_prefix = "googletest-0fe96607d85cf3a25ac40da369db62bbee2939a5",
    url = "https://github.com/google/googletest/archive/0fe96607d85cf3a25ac40da369db62bbee2939a5.tar.gz",
)

new_local_repository(
    name = "openssl",
    path = "/usr",
    build_file = "depBUILD/openssl.BUILD",
)

new_local_repository(
    name = "openssl_macos",
    build_file = "depBUILD/openssl.BUILD",
    path = "/usr/local/opt/openssl",
)

bind(
    name = "ssl",
    actual = "@openssl//:ssl"
)

bind(
    name = "ssl_macos",
    actual = "@openssl_macos//:ssl"
)

new_local_repository(
    name = "zlib",
    build_file = "depBUILD/zlib.BUILD",
    path = "/usr",
)
git_repository(
    name = "brpc",
    remote = "https://gitee.com/baidu/BRPC.git",
    branch = "master"
)
复制代码

注意到有些依赖的build_file的参数是depBUILD/XXXX.BUILD,是因为有些拉下来的仓库是没有BUILD文件的,需要自己提供一个BUILD文件给到bazel去编译,当然这个不需要我们去编写,Brpc的源码就有这几个依赖的BUILD文件,所以大伙自己拷贝一下几个没有的BUILD文件到depBUILD目录下就可以。还有一点是openssl这个也需要安装在本地,如果大家是按照我的命令操作的话是已经安装了。

image.png

配置完WORKSPACE文件就已经把Brpc的依赖和Brpc配置好了

接下来配置BUILD文件

# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# 运行指定的命令
COPTS = [
    "-D__STDC_FORMAT_MACROS",
    "-DBTHREAD_USE_FAST_PTHREAD_MUTEX",
    "-D__const__=",
    "-D_GNU_SOURCE",
    "-DUSE_SYMBOLIZE",
    "-DNO_TCMALLOC",
    "-D__STDC_LIMIT_MACROS",
    "-D__STDC_CONSTANT_MACROS",
    "-fPIC",
    "-Wno-unused-parameter",
    "-fno-omit-frame-pointer",
    "-DGFLAGS_NS=google",
] + select({
    "//:with_glog": ["-DBRPC_WITH_GLOG=1"],
    "//conditions:default": ["-DBRPC_WITH_GLOG=0"],
})

# pb的编译
proto_library(
    name = "c++_http_proto",
    srcs = [
        "proto/http.proto",
    ],
)
# 将pb编译声明为一个依赖dep
cc_proto_library(
    name = "cc_c++_http_proto",
    deps = [
        ":c++_http_proto",
    ],
)

# 二进制文件的依赖依赖于上面声明的dep
cc_binary(
    name = "http_c++_server",
    srcs = [
        "http_c++_server.cpp",
    ],
    deps = [
        ":cc_c++_http_proto",
        "//:brpc",
    ],
    copts = COPTS,
)
复制代码

BUILD文件十分简单,就是一个c++的http的服务(http_c++_server),这个服务依赖于一个proto文件,我们把这个依赖关系书写出来,bazel就会帮我们处理这个链接的过程,不需要我们去操心。 BUILD文件里声明了一个http.proto文件 我们创建http.proto文件

root@5669353fc2c1:~/juejin-example# mkdir proto
root@5669353fc2c1:~/juejin-example# code proto/http.proto
复制代码

在http.proto里拷贝一下brpc的example下的http_c++的http.proto文件

// http.proto
syntax="proto2";
package example;

option cc_generic_services = true;

message HttpRequest {};
message HttpResponse {};

service HttpService {
  rpc Echo(HttpRequest) returns (HttpResponse);
  rpc EchoProtobuf(HttpRequest) returns (HttpResponse);
};

service FileService {
  rpc default_method(HttpRequest) returns (HttpResponse);
};

service QueueService {
  rpc start(HttpRequest) returns (HttpResponse);
  rpc stop(HttpRequest) returns (HttpResponse);
  rpc getstats(HttpRequest) returns (HttpResponse);
};
复制代码

这个proto比较简单,因为rest的请求,Message不需要任何的数据,query和body都是从brpcController里面拿到的,不需要从proto中解析出来。

根据BUILD文件,还需要一个http_c++_server.cpp文件 在项目根目录下创建http_c++_server.cpp (与WORKSPACE同级) 然后在brpc的exmaple/http_c++下复制文件http_server.cpp的内容到http_c++_server.cpp

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

// A server to receive HttpRequest and send back HttpResponse.

#include <gflags/gflags.h>
#include <butil/logging.h>
#include <brpc/server.h>
#include <brpc/restful.h>
#include "http.pb.h"

DEFINE_int32(port, 8010, "TCP Port of this server");
DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no "
             "read/write operations during the last `idle_timeout_s'");
DEFINE_int32(logoff_ms, 2000, "Maximum duration of server's LOGOFF state "
             "(waiting for client to close connection before server stops)");

DEFINE_string(certificate, "cert.pem", "Certificate file path to enable SSL");
DEFINE_string(private_key, "key.pem", "Private key file path to enable SSL");
DEFINE_string(ciphers, "", "Cipher suite used for SSL connections");

namespace example {

// Service with static path.
class HttpServiceImpl : public HttpService {
public:
    HttpServiceImpl() {};
    virtual ~HttpServiceImpl() {};
    void Echo(google::protobuf::RpcController* cntl_base,
              const HttpRequest*,
              HttpResponse*,
              google::protobuf::Closure* done) {
        // This object helps you to call done->Run() in RAII style. If you need
        // to process the request asynchronously, pass done_guard.release().
        brpc::ClosureGuard done_guard(done);
        
        brpc::Controller* cntl =
            static_cast<brpc::Controller*>(cntl_base);
        // Fill response.
        cntl->http_response().set_content_type("text/plain");
        butil::IOBufBuilder os;
        os << "queries:";
        for (brpc::URI::QueryIterator it = cntl->http_request().uri().QueryBegin();
                it != cntl->http_request().uri().QueryEnd(); ++it) {
            os << ' ' << it->first << '=' << it->second;
        }
        os << "\nbody: " << cntl->request_attachment() << '\n';
        os.move_to(cntl->response_attachment());
    }
};

// Service with dynamic path.
class FileServiceImpl : public FileService {
public:
    FileServiceImpl() {};
    virtual ~FileServiceImpl() {};

    struct Args {
        butil::intrusive_ptr<brpc::ProgressiveAttachment> pa;
    };

    static void* SendLargeFile(void* raw_args) {
        std::unique_ptr<Args> args(static_cast<Args*>(raw_args));
        if (args->pa == NULL) {
            LOG(ERROR) << "ProgressiveAttachment is NULL";
            return NULL;
        }
        for (int i = 0; i < 100; ++i) {
            char buf[16];
            int len = snprintf(buf, sizeof(buf), "part_%d ", i);
            args->pa->Write(buf, len);

            // sleep a while to send another part.
            bthread_usleep(10000);
        }
        return NULL;
    }

    void default_method(google::protobuf::RpcController* cntl_base,
                        const HttpRequest*,
                        HttpResponse*,
                        google::protobuf::Closure* done) {
        brpc::ClosureGuard done_guard(done);
        brpc::Controller* cntl =
            static_cast<brpc::Controller*>(cntl_base);
        const std::string& filename = cntl->http_request().unresolved_path();
        if (filename == "largefile") {
            // Send the "largefile" with ProgressiveAttachment.
            std::unique_ptr<Args> args(new Args);
            args->pa = cntl->CreateProgressiveAttachment();
            bthread_t th;
            bthread_start_background(&th, NULL, SendLargeFile, args.release());
        } else {
            cntl->response_attachment().append("Getting file: ");
            cntl->response_attachment().append(filename);
        }
    }
};

// Restful service. (The service implementation is exactly same with regular
// services, the difference is that you need to pass a `restful_mappings'
// when adding the service into server).
class QueueServiceImpl : public example::QueueService {
public:
    QueueServiceImpl() {};
    virtual ~QueueServiceImpl() {};
    void start(google::protobuf::RpcController* cntl_base,
               const HttpRequest*,
               HttpResponse*,
               google::protobuf::Closure* done) {
        brpc::ClosureGuard done_guard(done);
        brpc::Controller* cntl =
            static_cast<brpc::Controller*>(cntl_base);
        cntl->response_attachment().append("queue started");
    }
    void stop(google::protobuf::RpcController* cntl_base,
              const HttpRequest*,
              HttpResponse*,
              google::protobuf::Closure* done) {
        brpc::ClosureGuard done_guard(done);
        brpc::Controller* cntl =
            static_cast<brpc::Controller*>(cntl_base);
        cntl->response_attachment().append("queue stopped");
    }
    void getstats(google::protobuf::RpcController* cntl_base,
                  const HttpRequest*,
                  HttpResponse*,
                  google::protobuf::Closure* done) {
        brpc::ClosureGuard done_guard(done);
        brpc::Controller* cntl =
            static_cast<brpc::Controller*>(cntl_base);
        const std::string& unresolved_path = cntl->http_request().unresolved_path();
        if (unresolved_path.empty()) {
            cntl->response_attachment().append("Require a name after /stats");
        } else {
            cntl->response_attachment().append("Get stats: ");
            cntl->response_attachment().append(unresolved_path);
        }
    }
};

}  // namespace example

int main(int argc, char* argv[]) {
    // Parse gflags. We recommend you to use gflags as well.
    GFLAGS_NS::ParseCommandLineFlags(&argc, &argv, true);

    // Generally you only need one Server.
    brpc::Server server;

    example::HttpServiceImpl http_svc;
    example::FileServiceImpl file_svc;
    example::QueueServiceImpl queue_svc;
    
    // Add services into server. Notice the second parameter, because the
    // service is put on stack, we don't want server to delete it, otherwise
    // use brpc::SERVER_OWNS_SERVICE.
    if (server.AddService(&http_svc,
                          brpc::SERVER_DOESNT_OWN_SERVICE) != 0) {
        LOG(ERROR) << "Fail to add http_svc";
        return -1;
    }
    if (server.AddService(&file_svc,
                          brpc::SERVER_DOESNT_OWN_SERVICE) != 0) {
        LOG(ERROR) << "Fail to add file_svc";
        return -1;
    }
    if (server.AddService(&queue_svc,
                          brpc::SERVER_DOESNT_OWN_SERVICE,
                          "/v1/queue/start   => start,"
                          "/v1/queue/stop    => stop,"
                          "/v1/queue/stats/* => getstats") != 0) {
        LOG(ERROR) << "Fail to add queue_svc";
        return -1;
    }

    // Start the server.
    brpc::ServerOptions options;
    options.idle_timeout_sec = FLAGS_idle_timeout_s;
    options.mutable_ssl_options()->default_cert.certificate = FLAGS_certificate;
    options.mutable_ssl_options()->default_cert.private_key = FLAGS_private_key;
    options.mutable_ssl_options()->ciphers = FLAGS_ciphers;
    if (server.Start(FLAGS_port, &options) != 0) {
        LOG(ERROR) << "Fail to start HttpServer";
        return -1;
    }

    // Wait until Ctrl-C is pressed, then Stop() and Join() the server.
    server.RunUntilAskedToQuit();
    return 0;
}
复制代码

然后需要把cpp文件中依赖的key.pem和cert.pem也要复制到cpp的同级目录

image.png

目前整个project的结构如下

root@5669353fc2c1:~/juejin-example# tree
.
|-- BUILD
|-- WORKSPACE
|-- cert.pem
|-- depBUILD
|   |-- glog.BUILD
|   |-- leveldb.BUILD
|   |-- openssl.BUILD
|   `-- zlib.BUILD
|-- http_c++_server.cpp
|-- key.pem
`-- proto
    `-- http.proto
复制代码

接下来就可以开始跑了

root@5669353fc2c1:~/juejin-example# bazel run //:http_c++_server
Starting local Bazel server and connecting to it...
INFO: Writing tracer profile to '/root/.cache/bazel/_bazel_root/c2b1f1c39c843e8d13f18c6e59f038b8/command.profile.gz'
ERROR: /root/juejin-example/BUILD:51:1: //:with_glog is not a valid configuration key for //:http_c++_server
ERROR: Analysis of target '//:http_c++_server' failed; build aborted: //:with_glog is not a valid configuration key for //:http_c++_server
INFO: Elapsed time: 1.783s
INFO: 0 processes.
FAILED: Build did NOT complete successfully (1 packages loaded, 0 targets configured)
FAILED: Build did NOT complete successfully (1 packages loaded, 0 targets configured)
复制代码

根据报错信息把BUILD文件里的COPT做一下修改,把选中的这一块去掉

image.png 然后再跑

root@5669353fc2c1:~/juejin-example# bazel run //:http_c++_server
INFO: Writing tracer profile to '/root/.cache/bazel/_bazel_root/c2b1f1c39c843e8d13f18c6e59f038b8/command.profile.gz'
ERROR: /root/.cache/bazel/_bazel_root/c2b1f1c39c843e8d13f18c6e59f038b8/external/com_google_protobuf/protobuf.bzl:130:19: Traceback (most recent call last):
        File "/root/.cache/bazel/_bazel_root/c2b1f1c39c843e8d13f18c6e59f038b8/external/com_google_protobuf/protobuf.bzl", line 125
                rule(attrs = {"srcs": attr.label_list...()}, <2 more arguments>)
        File "/root/.cache/bazel/_bazel_root/c2b1f1c39c843e8d13f18c6e59f038b8/external/com_google_protobuf/protobuf.bzl", line 130, in rule
                attr.label(cfg = "host", executable = True, sin..., ...)
'single_file' is no longer supported. use allow_single_file instead. You can use --incompatible_disable_deprecated_attr_params=false to temporarily disable this check.
ERROR: /root/juejin-example/BUILD:33:1: every rule of type proto_library implicitly depends upon the target '@com_google_protobuf//:protoc', but this target could not be found because of: error loading package '@com_google_protobuf//': Extension file 'protobuf.bzl' has errors
ERROR: Analysis of target '//:http_c++_server' failed; build aborted: error loading package '@com_google_protobuf//': Extension file 'protobuf.bzl' has errors
INFO: Elapsed time: 1.856s
INFO: 0 processes.
FAILED: Build did NOT complete successfully (14 packages loaded, 49 targets configured)
FAILED: Build did NOT complete successfully (14 packages loaded, 49 targets configured)
复制代码

看提示,还是bazel版本的问题,需要创建一个.bazelrc文件去控制bazel run和bazel build的指令选项

root@5669353fc2c1:~/juejin-example# code .bazelrc
复制代码

.bazelrc的内容

build  --incompatible_disable_deprecated_attr_params=false
复制代码

再跑

root@5669353fc2c1:~/juejin-example# bazel run //:http_c++_server
INFO: Writing tracer profile to '/root/.cache/bazel/_bazel_root/c2b1f1c39c843e8d13f18c6e59f038b8/command.profile.gz'
ERROR: /root/juejin-example/BUILD:53:12: in deps attribute of cc_binary rule //:http_c++_server: target '//:brpc' does not exist
ERROR: Analysis of target '//:http_c++_server' failed; build aborted: Analysis of target '//:http_c++_server' failed; build aborted
INFO: Elapsed time: 0.903s
INFO: 0 processes.
FAILED: Build did NOT complete successfully (15 packages loaded, 483 targets configured)
FAILED: Build did NOT complete successfully (15 packages loaded, 483 targets configured)
复制代码

发现是一个依赖项的问题,:brpc是一般指BUILD上声明的依赖,例如cc_library,而brpc是我们在WORKSPACE拉下来的,需要修改一下BUILD文件的这个依赖的名字,修改为@brpc//:brpc

# 二进制文件的依赖依赖于上面声明的dep
cc_binary(
    name = "http_c++_server",
    srcs = [
        "http_c++_server.cpp",
    ],
    deps = [
        ":cc_c++_http_proto",
        "@brpc//:brpc",
    ],
    copts = COPTS,
)
复制代码

然后因为bazel是基于沙箱跑的,所以cert.pem和key.pem需要指定绝对路径,不然二进制文件跑的时候会读取当前目录下的pem文件会报错,所以需要修改(如果有老哥可以知道怎么可以将这两个文件放到bazel-bin里的话,可以跟我说一下)

DEFINE_string(certificate, "/root/juejin-example/cert.pem", "Certificate file path to enable SSL");
DEFINE_string(private_key, "/root/juejin-example/key.pem", "Private key file path to enable SSL");
复制代码

修改为绝对路径后再跑

Use --incompatible_new_actions_api=false to temporarily disable this check.
复制代码

再次报错,需要在.bazelrc再修改一下(有一个是查资料查出来的配置项)

build --copt -DHAVE_ZLIB=1 
build  --incompatible_disable_deprecated_attr_params=false
build --incompatible_new_actions_api=false
复制代码

再跑

http_c++_server.cpp:24:10: fatal error: http.pb.h: No such file or directory
   24 | #include "http.pb.h"
      |          ^~~~~~~~~~~
compilation terminated.
复制代码

需要再修改一下http.pb.h的文件引用,因为这个是在proto文件夹里的所以需要修改为

#include "proto/http.pb.h"
复制代码

再跑然后编译成功,开始运行二进制

INFO: Elapsed time: 155.248s, Critical Path: 7.01s
INFO: 504 processes: 504 processwrapper-sandbox.
INFO: Build completed successfully, 505 total actions
INFO: Build completed successfully, 505 total actions
I0227 21:24:48.670600  5176 external/brpc/src/brpc/server.cpp:1046] Server[example::QueueServiceImpl+example::HttpServiceImpl+example::FileServiceImpl] is serving on port=8010.
I0227 21:24:48.671348  5176 external/brpc/src/brpc/server.cpp:1049] Check out http://5669353fc2c1:8010 in web browser.
复制代码

打开 http://ip:8010 查看暴露出来的rest接口,然后环境就搭建成功!

参考文章

bazel文档

brpc文档

brpc使用汇总

Je suppose que tu aimes

Origine juejin.im/post/7069381454773354509
conseillé
Classement