Skip to content

Latest commit

 

History

History
253 lines (169 loc) · 9.17 KB

server_guide.md

File metadata and controls

253 lines (169 loc) · 9.17 KB

English

服务端开发向导

快速入门中介绍了如何开发一个简单的 tRPC 服务。本文是进阶文章,将详细介绍进行服务端程序开发时需要考虑哪些问题,做哪些事情。如果服务中需要调用下游,阅读完本文后,请阅读客户端开发向导

Runtime 选型

请参考如何选择及配置runtime

定义 Service

Service 即服务提供者,提供接口规范供客户端调用。从网络通信的角度讲, 每个 service 对应一个 IP + 端口 + 协议。

协议选择

目前框架支持的内置协议有 tRPC、HTTP、gRPC、Redis等。如果你使用的协议不属于框架内置协议的话,则需要自行实现对应的 codec 插件。

在对协议无特殊要求的场景,推荐使用 tRPC 协议。因为tRPC协议相比其他协议,支持的功能更丰富,如流式传输、附件分发等,性能方面也做了针对性的优化,且未来新特性我们会优先在tRPC协议上实现。

定义 Protobuf Service

Protobuf Service 是一组接口的逻辑组合,它需要定义:

  • package name
  • service name
  • rpc name
  • 接口请求和响应的消息类型。

IDL协议类型

IDL语言可以通过一种独立于编程语言的方式来描述接口,并使用工具把IDL 文件转换成指定语言的桩代码,使程序员专注于业务逻辑开发。对于 IDL 协议类型的服务,Protobuf Service 的定义通常分为以下五步(以 tRPC 协议为例):

  1. 采用IDL语言描述RPC接口规范

    syntax = "proto3";
    package trpc.test.helloworld;
    
    service Greeter {
        rpc SayHello (HelloRequest) returns (HelloReply) {}
    }
    
    message HelloRequest {
        string msg = 1;
    }
    
    message HelloReply {
        string msg = 1;
    }
  2. 通过脚手架生成项目

    参考快速上手中的步骤

  3. 实现服务端逻辑

    参考快速上手中的步骤

  4. 将 Protobuf Service 注册到 Server

    生成的桩代码中,已经做了这一步,具体代码位于 helloworld_server.cc 中:

    int helloworldServer::Initialize() {
     
      std::string service_name("trpc.");
      service_name += trpc::TrpcConfig::GetInstance()->GetServerConfig().app;
      service_name += ".";
      service_name += trpc::TrpcConfig::GetInstance()->GetServerConfig().server;
      service_name += ".Greeter";
     
      TRPC_LOG_INFO("service name1:" << service_name);
     
      trpc::ServicePtr my_service(new GreeterServiceImpl());
     
      // service_name需和配置文件中的service/name对应,以便关联service配置
      RegisterService(service_name, my_service);
     
      return 0;
    }

    接下来用户需要实现 greeter_service.cc 中具体的接口,可参考快速上手中的步骤。

非 IDL 协议类型

其中常见的协议为http协议,详细介绍可参考HTTP 服务开发指南

提供对应的框架配置文件

作为服务端,框架配置文件中需要提供 globalserver 两部分的配置,plugins 部分根据所使用的的插件进行配置。

一个使用 fiber M:N 协程 runtime 的配置示例如下:

global:
  local_ip: xxx.xxx.xxx.xxx  # local ip of application, optional
  threadmodel:
    fiber:
      - instance_name: fiber_instance
        concurrency_hint: 8
server:
  app: test                               # application name, optional
  server: helloworld                      # module name, optional
  service:                                # services associated with the server, required
    - name: trpc.test.helloworld.Greeter  # service name, required
      ip: xxx.xxx.xxx.xxx                 # bind ip, required
      port: 10001                         # bind port, required
      protocol: trpc                      # the application layer protocol used
plugins:
  log:
    default:
      - name: default
        sinks:
          local_file:
            filename: /usr/local/trpc/bin/server.log

异步回包

有时候当服务端收到请求时,我们需要异步执行某些任务,然后等任务执行完后再进行回包。这时候就需要用到框架的异步回包功能。使用方式如下:

::trpc::Status GreeterServiceImpl::SayHello(::trpc::ServerContextPtr context,
                                            const ::trpc::demo::helloworld::HelloRequest* request,
                                            ::trpc::demo::helloworld::HelloReply* reply) {
  // 1. Set to use asynchronous response mode.
  context->SetResponse(false);

  // 2. Do async work. Here, `DoAsyncWork` returns a Future.
  DoAsyncWork(...).Then([context](){
    ::trpc::test::helloworld::HelloReply rsp;
    rsp.set_msg("xxx");
    // 3. Call `SendUnaryResponse` to send response when the asynchronous task is completed.
    context->SendUnaryResponse(::trpc::kSuccStatus, rsp);
    return ::trpc::MakeReadyFuture<>();
  });

  return ::trpc::kSuccStatus;
}

说明:如果不需要设置响应体,只返回 Status 状态,那使用只带 Status 参数的接口即可:

void SendUnaryResponse(const Status& status);

约束

请求包最大长度默认为 10M

对于框架内置的 trpc 及 http 协议而已,框架限制请求包的最大长度为 10M。这个限制是服务级别的,用户可通过增加 service 的 max_packet_size 配置项进行修改,方式如下:

server:
  service:
    - name: trpc.test.helloworld.Greeter
      max_packet_size: 10000000  # max packet size limited

空闲连接超时

服务端的连接超时时间默认是60秒,如果调用方连续60秒没有发送数据包则连接会被断开。这个限制是 service 级别的,可以通过配置项 idle_time 进行修改:

server:
    service:
      - name: trpc.test.helloworld.Greeter
        idle_time: 60000  # connection idle timeout

服务自注册

默认情况下,框架不会向名字服务系统进行 service 实例的自动注册与反注册。如需开启框架的自注册模式,需要进行如下配置:

server:
  registry_name: xxx      # 要注册到哪个名字服务系统
  enable_self_register: true  # 开启框架的自注册

服务心跳上报和僵死检测

框架支持上报 service 实例的心跳到名字服务系统,以报告 service 自身的可用状态,使用时需增加如下配置:

server:
  registry_name: xxx  # 上报心跳到哪个名字服务系统

对于 IO/Handle 分离及合并线程模型,框架支持 worke r线程(IO 及 Handle 线程)的僵死检测。其原理为:框架 worker 线程每 3 秒会标记自己的活跃状态,如果某个线程超过阈值(60秒)都没有标记活跃状态则认为该线程僵死。

如果检测到分离模式下所有的 Handle 线程僵死或合并模式下所有 worker 线程僵死,则认为服务不可用,这时会停止向名字服务系统上报心跳。这样可在服务不可用时,通过名字服务系统让调用方及时感知到服务的状态,避免调用到不可用的服务节点,起到保护的作用。

对于离线计算等服务,这时请求处理时间可能超过 60 秒,会导致误判,可以调大超时阈值来解决:

global:
  heartbeat:
    thread_heartbeat_time_out: 60000 # 单位毫秒,判断handle线程僵死的阈值

另外,如果不需要上报心跳的话,可通过如下方式关闭:

heartbeat:
  enable_heartbeat: false  # 心跳上报开关,默认为true

关于 fork

fork 在多线程下有历史深坑,请阅读相关文章先了解这个前提。

tRPC-Cpp 是多线程的框架,只支持一种 fork 用法:fork with exec,即 fork 后立马调用 exec 相关函数。

而不支持 fork without exec,如果一定要这样用:请尽可能早的使用fork(在框架初始化前)。

使用 fork with exec 时,如果 exec 返回则代表执行失败,请调用 _exit() 立马退出子进程,不要调用 exit() 退出;这样做是为了避免某些单例或者全局变量析构导致一些未定义行为(比如在子进程处理父进程开的thread等)。

常见协议类型的服务开发

开发tRPC协议服务

开发HTTP协议服务

开发grpc协议服务

错误码

详见框架错误码说明

高级功能

指定请求的处理线程

详见请求指定线程

超时控制

详见超时控制

透明代理

详见透明代理

流控和过载保护