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 方式