dapr服务调用概述
Service Invocation 是 dapr 对外提供的最基础功能, 也就是服务间调用. 另外别的一些功能也会间接使用它.
本文不会介绍功能如何使用, 相关资料请查看官方文档.
How-To: Invoke services using HTTP | Dapr Docs
How-To: Invoke services using gRPC | Dapr Docs

官方文档给出来 Service Invocation 服务间调用的示意图, 已经是非常清晰了. 因为 dapr 是 sidecar 模式运行在业务 APP 旁边, 所以我们服务间调用也是通过 dapr 做的转发, dapr 也就是在这一步做了监控, 追踪, mTLS 等功能.
- service A 想通过 HTTP/gRPC 调用 service B, 会先请求自己本地 dapr A(sidecar)应用
- dapr A 通过 name resolution 服务发现模块获取到 dapr B 服务的地址
- dapr A 通过 gRPC 调用 dapr B (dapr 之间的调用都会用 gRPC 提高性能)
- dapr B 收到请求后转发请求给 service B
- service B 返回响应给 dapr B
- dapr B 将收到的响应返回给 dapr A
- dapr A 将收到的相应返回给 service A
整个流程经过了 dapr runtime 三个部分, 首先是 service A 通过 dapr API 调用 dapr sidecar(1, 7), 接着 sidecar 之间通过 internal grpc client 调用 internal grpc server(3, 6), 最后是目标 sidecar 通过 App Channel 调用 service B(4, 5).
Runtime初始化
服务调用的代码在daprd中
cmd/daprd/main.go
|-> main.go
|-> (a *DaprRuntime) initRuntime
Dapr Runtime中和服务调用相关的初始化流程
代码路径:pkg/runtime/runtime.go
在 dapr runtime 启动进行初始化时,需要开启 API 端口并挂载相应的 handler 来接收并处理服务调用的 outbound 请求。另外为了接收来自其他 dapr runtime 的 inbound 请求,还要启动 dapr internal server。
func (a *DaprRuntime) initRuntime(opts *runtimeOpts) error {
......
// 启动对外grpc server
err = a.startGRPCAPIServer(grpcAPI, a.runtimeConfig.APIGRPCPort)
if err != nil {
log.Fatalf("failed to start API gRPC server: %s", err)
}
...
// Start HTTP Server
err = a.startHTTPServer(a.runtimeConfig.HTTPPort, a.runtimeConfig.PublicPort, a.runtimeConfig.ProfilePort, a.runtimeConfig.AllowedOrigins, pipeline)
if err != nil {
log.Fatalf("failed to start HTTP server: %s", err)
}
// 启动对内grpc server
err = a.startGRPCInternalServer(grpcAPI, a.runtimeConfig.InternalGRPCPort)
if err != nil {
log.Fatalf("failed to start internal gRPC server: %s", err)
}
......
}
http server启动
func (a *DaprRuntime) startHTTPServer(......) error {
a.daprHTTPAPI = http.NewAPI(......)
server := http.NewServer(a.daprHTTPAPI, ......)
if err := server.StartNonBlocking(); err != nil { // StartNonBlocking 启动 fasthttp server
return err
}
}
StartNonBlocking() 的实现代码在 pkg/http/server.go 中:
// NewAPI returns a new API.
func NewAPI(
appID string,
appChannel channel.AppChannel,
directMessaging messaging.DirectMessaging,
getComponentsFn func() []components_v1alpha1.Component,
stateStores map[string]state.Store,
secretStores map[string]secretstores.SecretStore,
secretsConfiguration map[string]config.SecretsScope,
pubsubAdapter runtime_pubsub.Adapter,
actor actors.Actors,
sendToOutputBindingFn func(name string, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error),
tracingSpec config.TracingSpec,
shutdown func()) API {
...
api.endpoints = append(api.endpoints, api.constructDirectMessagingEndpoints()...)
...
return api
}
// StartNonBlocking starts a new server in a goroutine.
func (s *server) StartNonBlocking() error {
......
for _, apiListenAddress := range s.config.APIListenAddresses {
l, err := net.Listen("tcp", fmt.Sprintf("%s:%v", apiListenAddress, s.config.Port))
listeners = append(listeners, l)
}
for _, listener := range listeners {
// customServer is created in a loop because each instance
// has a handle on the underlying listener.
customServer := &fasthttp.Server{
Handler: handler,
MaxRequestBodySize: s.config.MaxRequestBodySize * 1024 * 1024,
ReadBufferSize: s.config.ReadBufferSize * 1024,
StreamRequestBody: s.config.StreamRequestBody,
}
s.servers = append(s.servers, customServer)
go func(l net.Listener) {
if err := customServer.Serve(l); err != nil {
log.Fatal(err)
}
}(listener)
}
}
挂载 DirectMessaging 的 HTTP 端点
在 HTTP API 的初始化过程中,会在 fast http server 上挂载 DirectMessaging 的 HTTP 端点,代码在 pkg/http/api.go 中:
func (a *api) constructDirectMessagingEndpoints() []Endpoint {
return []Endpoint{
{
Methods: []string{router.MethodWild},
Route: "invoke/{id}/method/{method:*}",
Alias: "{method:*}",
Version: apiVersionV1,
Handler: a.onDirectMessage,
},
}
}
注意这里的 Route 路径 “invoke/{id}/method/{method:*}", dapr sdk 就是就通过这样的 url 来发起 HTTP 请求。

Dapr gRPC API Server(outbound)
启动 gRPC 服务器
在 dapr runtime 启动时的初始化过程中,会启动 gRPC server, 代码在 pkg/runtime/runtime.go 中:
func (a *DaprRuntime) initRuntime(opts *runtimeOpts) error {
// Create and start internal and external gRPC servers
grpcAPI := a.getGRPCAPI()
err = a.startGRPCAPIServer(grpcAPI, a.runtimeConfig.APIGRPCPort)
......
}
func (a *DaprRuntime) startGRPCAPIServer(api grpc.API, port int) error {
serverConf := a.getNewServerConfig(a.runtimeConfig.APIListenAddresses, port)
server := grpc.NewAPIServer(api, serverConf, a.globalConfig.Spec.TracingSpec, a.globalConfig.Spec.MetricSpec, a.globalConfig.Spec.APISpec, a.proxy)
if err := server.StartNonBlocking(); err != nil {
return err
}
......
}
// NewAPIServer returns a new user facing gRPC API server.
func NewAPIServer(api API, config ServerConfig, ......) Server {
return &server{
api: api,
config: config,
kind: apiServer, // const apiServer = "apiServer"
......
}
}
主该grpc server是对外的server,用来接收来之应用的grpc请求。
注册 Dapr API
为了让 dapr runtime 的 gRPC 服务器能挂载 Dapr API,需要进行注册上去。
注册的代码实现在 pkg/grpc/server.go 中, StartNonBlocking() 方法在启动 grpc 服务器时,会进行服务注册:
func (s *server) StartNonBlocking() error {
if s.kind == internalServer {
internalv1pb.RegisterServiceInvocationServer(server, s.api)
} else if s.kind == apiServer {
runtimev1pb.RegisterDaprServer(server, s.api) // 注意:s.api (即 gRPC api 实现) 被传递进去
}
......
}
而 RegisterDaprServer() 方法的实现代码在 pkg/proto/runtime/v1/dapr_grpc.pb.go:
func RegisterDaprServer(s grpc.ServiceRegistrar, srv DaprServer) {
s.RegisterService(&Dapr_ServiceDesc, srv) // srv 即 gRPC api 实现
}
Dapr_ServiceDesc 定义
在文件 pkg/proto/runtime/v1/dapr_grpc.pb.go 中有 Dapr Service 的 grpc 服务定义,这是 protoc 生成的 gRPC 代码。
Dapr_ServiceDesc 中有 Dapr Service 各个方法的定义,和服务调用相关的是 InvokeService 方法:
var Dapr_ServiceDesc = grpc.ServiceDesc{
ServiceName: "dapr.proto.runtime.v1.Dapr",
HandlerType: (*DaprServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "InvokeService", # 注册方法名
Handler: _Dapr_InvokeService_Handler, # 关联实现的 Handler
},
......
},
},
Metadata: "dapr/proto/runtime/v1/dapr.proto",
}
这一段是告诉 gRPC server: 如果收到访问 dapr.proto.runtime.v1.Dapr 服务的 InvokeService 方法的 gRPC 请求,请把请求转给 _Dapr_InvokeService_Handler 处理。

而 InvokeService 方法相关联的 handler 方法 _Dapr_InvokeService_Handler 的实现代码是:
func _Dapr_InvokeService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(InvokeServiceRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(DaprServer).InvokeService(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/dapr.proto.runtime.v1.Dapr/InvokeService",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaprServer).InvokeService(ctx, req.(*InvokeServiceRequest)) // 这里调用的 srv 即 gRPC api 实现
}
return interceptor(ctx, in, info, handler)
}
最后调用到了 DaprServer 接口实现的 InvokeService 方法,也就是 gPRC API 实现。代码路径:pkg/grpc/api.go
func (a *api) InvokeService(ctx context.Context, in *runtimev1pb.InvokeServiceRequest) (*commonv1pb.InvokeResponse, error) {
req := invokev1.FromInvokeRequestMessage(in.GetMessage())
if incomingMD, ok := metadata.FromIncomingContext(ctx); ok {
req.WithMetadata(incomingMD)
}
if a.directMessaging == nil {
return nil, status.Errorf(codes.Internal, messages.ErrDirectInvokeNotReady)
}
resp, err := a.directMessaging.Invoke(ctx, in.Id, req)
if err != nil {
err = status.Errorf(codes.Internal, messages.ErrDirectInvoke, in.Id, err)
return nil, err
}
headerMD := invokev1.InternalMetadataToGrpcMetadata(ctx, resp.Headers(), true)
var respError error
if resp.IsHTTPResponse() {
errorMessage := []byte("")
if resp != nil {
_, errorMessage = resp.RawData()
}
respError = invokev1.ErrorFromHTTPResponseCode(int(resp.Status().Code), string(errorMessage))
// Populate http status code to header
headerMD.Set(daprHTTPStatusHeader, strconv.Itoa(int(resp.Status().Code)))
} else {
respError = invokev1.ErrorFromInternalStatus(resp.Status())
// ignore trailer if appchannel uses HTTP
grpc.SetTrailer(ctx, invokev1.InternalMetadataToGrpcMetadata(ctx, resp.Trailers(), false))
}
grpc.SetHeader(ctx, headerMD)
return resp.Message(), respError
}
a.directMessaging.Invoke后续进一步分析
Dapr Internal API Server(inbound)
启动 gRPC 服务器
在 dapr runtime 启动时的初始化过程中,会启动 gRPC internal server, 代码在 pkg/runtime/runtime.go 中:
func (a *DaprRuntime) initRuntime(opts *runtimeOpts) error {
err = a.startGRPCInternalServer(grpcAPI, a.runtimeConfig.InternalGRPCPort)
if err != nil {
log.Fatalf("failed to start internal gRPC server: %s", err)
}
log.Infof("internal gRPC server is running on port %v", a.runtimeConfig.InternalGRPCPort)
......
}
func (a *DaprRuntime) startGRPCInternalServer(api grpc.API, port int) error {
serverConf := a.getNewServerConfig([]string{""}, port)
server := grpc.NewInternalServer(api, serverConf, a.globalConfig.Spec.TracingSpec, a.globalConfig.Spec.MetricSpec, a.authenticator, a.proxy)
if err := server.StartNonBlocking(); err != nil {
return err
}
a.apiClosers = append(a.apiClosers, server)
return nil
}
NewInternalServer这个grpc server是dapr内部两个daprd之间通信用的。
// NewInternalServer returns a new gRPC server for Dapr to Dapr communications.
func NewInternalServer(api API, config ServerConfig, tracingSpec config.TracingSpec, metricSpec config.MetricSpec, authenticator auth.Authenticator, proxy messaging.Proxy) Server {
return &server{
api: api,
config: config,
tracingSpec: tracingSpec,
metricSpec: metricSpec,
authenticator: authenticator,
renewMutex: &sync.Mutex{},
kind: internalServer,
logger: internalServerLogger,
maxConnectionAge: getDefaultMaxAgeDuration(),
proxy: proxy,
}
}
注册 Dapr API
为了让 dapr runtime 的 gRPC 服务器能挂载 Dapr internal API,需要进行注册。
注册的代码实现在 pkg/grpc/server.go 中, StartNonBloking() 方法在启动 grpc 服务器时,会进行服务注册。这里的注册入口和对外的rpc的入口是同一个函数StartNonBlocking,区别是kind不同,一个是"apiserver", 一个"internal"。
func (s *server) StartNonBlocking() error {
if s.kind == internalServer {
// 走内部grpc分支
internalv1pb.RegisterServiceInvocationServer(server, s.api) // 注意:s.api (即 gRPC api 实现) 被传递进去
} else if s.kind == apiServer {
runtimev1pb.RegisterDaprServer(server, s.api)
}
......
}
而 RegisterServiceInvocationServer() 方法的实现代码在 pkg/proto/internals/v1/service_invocation_grpc.pb.go:
func RegisterServiceInvocationServer(s grpc.ServiceRegistrar, srv ServiceInvocationServer) {
s.RegisterService(&ServiceInvocation_ServiceDesc, srv) // srv 即 gRPC api 实现
}
ServiceInvocation_ServiceDesc 定义
在文件 pkg/proto/internals/v1/service_invocation_grpc.pb.go 中有 internal Service 的 grpc 服务定义,这是 protoc 生成的 gRPC 代码。
ServiceInvocation_ServiceDesc 中有两个方法的定义,和服务调用相关的是 CallLocal 方法:
var ServiceInvocation_ServiceDesc = grpc.ServiceDesc{
ServiceName: "dapr.proto.internals.v1.ServiceInvocation",
HandlerType: (*ServiceInvocationServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "CallActor",
Handler: _ServiceInvocation_CallActor_Handler,
},
{
MethodName: "CallLocal",
Handler: _ServiceInvocation_CallLocal_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "dapr/proto/internals/v1/service_invocation.proto",
}
这一段是告诉 gRPC server: 如果收到访问 dapr.proto.internals.v1.ServiceInvocation 服务的 CallLocal 方法的 gRPC 请求,请把请求转给 _ServiceInvocation_CallLocal_Handler 处理。

而 CallLocal 方法相关联的 handler 方法 _ServiceInvocation_CallLocal_Handler 的实现代码是:
func _ServiceInvocation_CallLocal_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(InternalInvokeRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ServiceInvocationServer).CallLocal(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/dapr.proto.internals.v1.ServiceInvocation/CallLocal",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
// 这里调用的 srv 即 gRPC api 实现
return srv.(ServiceInvocationServer).CallLocal(ctx, req.(*InternalInvokeRequest))
}
return interceptor(ctx, in, info, handler)
}
最后调用到了 ServiceInvocationServer 接口实现的 CallLocal 方法,也就是 gPRC API 实现。代码路径:pkg/grpc/api.go
// CallLocal is used for internal dapr to dapr calls. It is invoked by another Dapr instance with a request to the local app.
func (a *api) CallLocal(ctx context.Context, in *internalv1pb.InternalInvokeRequest) (*internalv1pb.InternalInvokeResponse, error) {
if a.appChannel == nil {
return nil, status.Error(codes.Internal, messages.ErrChannelNotFound)
}
req, err := invokev1.InternalInvokeRequest(in)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, messages.ErrInternalInvokeRequest, err.Error())
}
if a.accessControlList != nil {
// An access control policy has been specified for the app. Apply the policies.
operation := req.Message().Method
var httpVerb commonv1pb.HTTPExtension_Verb
// Get the http verb in case the application protocol is http
if a.appProtocol == config.HTTPProtocol && req.Metadata() != nil && len(req.Metadata()) > 0 {
httpExt := req.Message().GetHttpExtension()
if httpExt != nil {
httpVerb = httpExt.GetVerb()
}
}
callAllowed, errMsg := acl.ApplyAccessControlPolicies(ctx, operation, httpVerb, a.appProtocol, a.accessControlList)
if !callAllowed {
return nil, status.Errorf(codes.PermissionDenied, errMsg)
}
}
resp, err := a.appChannel.InvokeMethod(ctx, req)
if err != nil {
err = status.Errorf(codes.Internal, messages.ErrChannelInvoke, err)
return nil, err
}
return resp.Proto(), err
}
总结
以上是对dapr服务调用的源码分析,看起来比较拗口,这里用两张图来解释下dapr的服务调用原理,一目了然。需要注意的是,不管是http方式还是grpc方式,daprd和daprd之间的通信都是通过grpc进行通信的,这一步的流程是一致的。
HTTP 方式

gRPC 方式


1333

被折叠的 条评论
为什么被折叠?



