docs-10-http-with-modules.md 12 KB

10 - 带生态插件的HttpServer和HttpClient

srpc提供带有插件功能的HttpServerHttpClient,可以上报tracemetrcis,用法和功能完全兼容Workflow,添加插件的用法也和srpc目前的Server和Client一样,用于使用Http功能同时需要采集trace、metrics等信息并上报的场景。

此功能更加适用于原先已经使用Workflow收发Http的开发者,几乎不改动现有代码即可拥有生态上报功能。

(1) 补充插件基本介绍:

(2) 补充Workflow中的Http用法:

(3) 补充SRPC中带插件上报功能的示例代码:

1. HttpServer用法

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<void (WFHttpTask *task)>
  4. process函数里拿到的参数是task,通过task->get_req()和task->get_resp()可以拿到请求与回复,分别是HttpRequestHttpResponse,而其他上下文也在task而非RPCContext上;

2. HttpClient用法

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<void (WFHttpTask *task)>

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

timeout_reason参考: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_serverhttp_client编出来,其中tutorial-17-http_server.cc中可以把上报OpenTelemetry的插件打开:

srpc::RPCTraceOpenTelemetry otel("http://127.0.0.1:4318");
srpc::HttpServer server(process);
server.add_filter(&otel);
...

分别按照如下执行,可以看到我们的client通过RPCTraceDefault插件本地打印的trace信息:

./http_client
callback. state = 0 error = 0
<html>Hello from server!</html>
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内容也同时打印出来:

./http_server 
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的状态码,比如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;