Skip to content

Tencent/flare

Repository files navigation

Flare 后台服务开发框架

Flare Backend Service Framework

English Document

license NewBSD C++ Code Style Platform

腾讯广告 是腾讯公司最重要的业务之一,其后台大量采用 C++ 开发。

Flare 是我们在汲取自研服务框架经验、参考业界开源项目和最新研究成果的基础上,开发的现代化后台服务开发框架。目标是在当今主流软硬件环境下,提供易用、高性能、平稳的服务开发能力。

Flare 项目始于 2019 年,目前广泛应用于腾讯广告的各类后台服务,拥有数以万计的运行实例,在生产系统上经受了充分的考验。

2021 年 5 月,我们本着回馈社区、技术共享的精神正式将 Flare 开源。

特点

  • 现代 C++ 设计风格,全面采用 C++20 的语法特性和标准库
  • 提供 M:N 线程模型 的用户态微线程实现 Fiber,让开发者用同步调用的写法获得异步调用的性能
  • 支持基于消息的流式 RPC
  • 除 RPC 外,还提供了一系列便利的基础库:字符串、时间日期、编码、压缩、加解密、配置、HTTP 客户端等等,方便快速上手开发业务代码
  • 提供了灵活的扩展机制,方便支持多种协议、服务发现、负载均衡、监控告警、调用追踪
  • 针对现代体系结构做了大量优化:NUMA 感知调度组对象池零拷贝缓冲区
  • 高质量的代码。严格遵守 Google C++ 代码规范,测试覆盖率达 80%
  • 完善的文档示例以及调试支持,方便快速上手

系统要求

Linux

  • Linux 3.10 或以上内核
  • GCC 11 或以上版本的编译器(最早支持 std::atomic<T>::wait / notify_* 的 libstdc++ 版本,参见 P1135
  • x86-64 处理器;aarch64 和 ppc64le 也支持,但未在生产环境实际投入使用

macOS

  • macOS 13 (Ventura) 或以上
  • Apple Clang(Xcode 14 及以上自带的 libc++ 已包含 std::atomic<T>::wait / notify_* 运行期符号)
  • Apple Silicon (arm64) 与 Intel (x86_64) 均可

开始使用

Flare 是开箱即用的,已经自带了所需的第三方库,通常不需要额外安装依赖。

thirdparty/ 下的源码包通过 Git LFS 存储,因此拉取代码前请确保 git-lfs 已正确安装。

构建

我们使用 blade 进行日常开发:

  • 编译:./blade build ...
  • 测试:./blade test ...

Flare 也支持 bazel 作为构建系统,参见 bazel support

之后可以参考入门导引,搭建一个简单的 RPC 服务。

调试

我们认为调试体验是开发维护过程中很重要的一环,为此提供了:

测试

为改善编写单测的体验,我们提供了一些测试工具,包括但不限于:

示例

我们提供了一些使用示例,下面是一个简单的转发服务(同时演示了 RPC 客户端与服务端的使用):

#include "gflags/gflags.h"

#include "flare/example/rpc/echo_service.flare.pb.h"
#include "flare/example/rpc/relay_service.flare.pb.h"
#include "flare/fiber/this_fiber.h"
#include "flare/init.h"
#include "flare/rpc/rpc_channel.h"
#include "flare/rpc/rpc_client_controller.h"
#include "flare/rpc/rpc_server_controller.h"
#include "flare/rpc/server.h"

using namespace std::literals;

DEFINE_string(ip, "127.0.0.1", "IP address to listen on.");
DEFINE_int32(port, 5569, "Port to listen on.");
DEFINE_string(forward_to, "flare://127.0.0.1:5567",
              "Target IP to forward requests to.");

namespace example {

class RelayServiceImpl : public SyncRelayService {
 public:
  void Relay(const RelayRequest& request, RelayResponse* response,
             flare::RpcServerController* ctlr) override {
    flare::RpcClientController our_ctlr;
    EchoRequest echo_req;
    echo_req.set_body(request.body());
    if (auto result = stub_.Echo(echo_req, &our_ctlr)) {
      response->set_body(result->body());
    } else {
      ctlr->SetFailed(result.error().code(), result.error().message());
    }
  }

 private:
  EchoService_SyncStub stub_{FLAGS_forward_to};
};

int Entry(int argc, char** argv) {
  flare::Server server{flare::Server::Options{.service_name = "relay_server"}};

  server.AddProtocol("flare");
  server.AddService(std::make_unique<RelayServiceImpl>());
  server.ListenOn(flare::EndpointFromIpv4(FLAGS_ip, FLAGS_port));
  FLARE_CHECK(server.Start());

  flare::WaitForQuitSignal();
  return 0;
}

}  // namespace example

int main(int argc, char** argv) {
  return flare::Start(argc, argv, example::Entry);
}

Flare 内部基于 M:N 用户态线程实现,因此通过 Flare 同步请求外部服务、调用 Flare 内置的各种客户端 的同步接口都不会带来性能问题。如有更复杂的并发或异步需求,可参考 Fiber 文档

另外,示例中的 *.flare.pb.h 通过 Flare 的 Protocol Buffers 插件 生成。相对于 Protocol Buffers 原生的 cc_generic_services,这种方式生成的接口更易用。

更复杂的示例

实际使用中往往需要并发请求多种后端,下面的示例展示了如何在 Flare 中实现这种模式:

// For illustration purpose only. Normally you wouldn't want to declare them as
// global variables.
flare::HttpClient http_client;
flare::CosClient cos_client;
EchoService_SyncStub echo_stub(FLAGS_echo_server_addr);

void FancyServiceImpl::FancyJob(const FancyJobRequest& request,
                                FancyJobResponse* response,
                                flare::RpcServerController* ctlr) {
  // Calling different services concurrently.
  auto async_http_body = http_client.AsyncGet(request.uri());
  auto async_cos_data =
      cos_client.AsyncExecute(flare::CosGetObjectRequest{.key = request.key()});
  EchoRequest echo_req;
  flare::RpcClientController echo_ctlr;
  echo_req.set_body(request.echo());
  auto async_rpc_resp = echo_stub.AsyncEcho(EchoRequest(), &echo_ctlr);

  // Now wait for all of them to complete.
  auto&& [http, cos, rpc] = flare::fiber::BlockingGet(
      flare::WhenAll(&async_http_body, &async_cos_data, &async_rpc_resp));

  if (!http || !cos || !rpc) {
    FLARE_LOG_WARNING("Failed.");
  } else {
    // All succeeded.
    FLARE_LOG_INFO("Got: {}, {}, {}", *http->body(),
                   flare::FlattenSlow(cos->bytes), rpc->body());
  }

  // Now fill `response` accordingly.
  response->set_body("Great success.");
}

这个示例中,我们:

  • 通过三种不同的客户端(HTTP腾讯云 COS、RPC)发起了三个异步请求
  • 通过 flare::fiber::BlockingGet 同步等待所有请求完成——这里只会阻塞用户态线程,不会有性能问题
  • 输出日志展示各个服务的响应

出于展示目的,这里请求的是三个异构服务。如有需要,也可以用同样的方式请求同构服务、或同构异构混合的服务。

参与开发

我们非常欢迎共建。希望了解 Flare 更多内部设计的开发者、或需要对 Flare 进行二次开发的开发者,可参考 flare/doc/ 下的技术文档。

详情请参考 CONTRIBUTING.md

性能

由于业务需求的特点,我们在设计中更倾向于优化延迟及其平稳性,而非吞吐;但在这一前提下也尽力兼顾性能。

简单的对比数据可见初步性能数据

致谢

  • 我们的底层实现大量参考了 brpc 的设计
  • RPC 部分,grpc 给了我们很多启发
  • 我们依赖了不少来自开源社区的第三方库,正是站在巨人的肩膀上让我们得以更快更好地开发本项目,也因此我们也乐于回馈社区

在此向上述项目一并致以谢意。

About

Flare是广泛投产于腾讯广告后台的现代化C++开发框架,包含了基础库、RPC、各种客户端等。主要特点为易用性强、长尾延迟低。

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors