已发表的技术专栏
0 grpc-go、protobuf、multus-cni 技术专栏 总入口
1 grpc-go 源码剖析与实战 文章目录
2 Protobuf介绍与实战 图文专栏 文章目录
3 multus-cni 文章目录(k8s多网络实现方案)
4 grpc、oauth2、openssl、双向认证、单向认证等专栏文章目录)
本节从服务器端角度来介绍HealthChecking的相关原理;
1、服务器端健康检测Watcher的核心思想 |
简单的说:
就是服务器端会将服务的最新状态更新到一个channel通道里,健康检测服务Watcher会从通道里获取最新的状态,
当状态跟以前的状态不一样时,服务器端就会将最新状态发送给客户端。
2、在健康检测服务启动下,服务器端处理客户端的请求主要经历了哪些阶段 |
主要经历的阶段如下:
也就是说,在健康检测模式下,多执行了第2,3步;
本章节重点分析第2,3步;
3、服务器端是如何接收客户端的健康检测请求的? |
跟以前的接收流程非常相似,只不过健康检测用的是双端流,而不是一元流;
我们以帧接收器作为入口分析;
进入grpc-go/internal/transport/http2_server.go文件中的HandleStreams方法里:
1.func (t *http2Server) HandleStreams(handle func(*Stream), traceCtx func(context.Context, string) context.Context) {
2. defer close(t.readerDone)
3. for {
4. t.controlBuf.throttle()
5. frame, err := t.framer.fr.ReadFrame()
6.
7. //---省略不相关代码
8. switch frame := frame.(type) {
9. case *http2.MetaHeadersFrame:
10. if t.operateHeaders(frame, handle, traceCtx) {
11. t.Close()
12. break
13. }
14.//---省略不相关代码
进入第10行,头帧处理器里:
1.func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func(*Stream), traceCtx func(context.Context, string) context.Context) (fatal bool) {
2. //---省略不相关代码
3. if err := state.decodeHeader(frame); err != nil {
4. //---省略不相关代码
5. }
6. //---省略不相关代码
7. handle(s)
}
通过第3行的decodeHeader方法,可以获取到客户端请求的服务名称,如/grpc.health.v1.Health/Watch
handle是由参数传递进来的函数,即如下:
在grpc-go/server.go文件中的serveStreams(st transport.ServerTransport)
1.func (s *Server) serveStreams(st transport.ServerTransport) {
2. defer st.Close()
3. var wg sync.WaitGroup
4. st.HandleStreams(func(stream *transport.Stream) {
5. wg.Add(1)
6. if s.opts.numServerWorkers > 0 {
7. data := &serverWorkerData{st: st, wg: &wg, stream: stream}
8. select {
9. case s.serverWorkerChannels[atomic.AddUint32(&roundRobinCounter, 1)%s.opts.numServerWorkers] <- data:
10. default:
11. go func() {
12. s.handleStream(st, stream, s.traceInfo(st, stream))
13. wg.Done()
14. }()
15. }
16. } else {
17. go func(){
18. defer wg.Done()
19. s.handleStream(st, stream, s.traceInfo(st, stream))
20. }()
21. }
22. },
23. //---省略不相关代码
24. )
25. wg.Wait()
26.}
其中,handle,就是第4-22行传进去的匿名函数
假设,执行的是第19行:
进入grpc-go/server.go文件中的handleStream方法里:
1.func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Stream, trInfo *traceInfo) {
2. //---省略不相关代码
3. service := sm[:pos]
4. method := sm[pos+1:]
5. srv, knownService := s.m[service]
6. if knownService {
7. if md, ok := srv.md[method]; ok {
8. s.processUnaryRPC(t, stream, srv, md, trInfo)
9. return
10. }
11. if sd, ok := srv.sd[method]; ok {
12. s.processStreamingRPC(t, stream, srv, sd, trInfo)
13. return
14. }
15. }
16. //---省略不相关代码
17.}
主要流程说明:
其中,第11行的src.sd就是:(grpc-go/health/grpc_health_v1/health_grpc.pb.go接口文件):
1.var _Health_serviceDesc = grpc.ServiceDesc{
2. ServiceName: "grpc.health.v1.Health",
3. HandlerType: (*HealthServer)(nil),
4. Methods: []grpc.MethodDesc{
5. {
6. MethodName: "Check",
7. Handler: _Health_Check_Handler,
8. },
9. },
10. Streams: []grpc.StreamDesc{
11. {
12. StreamName: "Watch",
13. Handler: _Health_Watch_Handler,
14. ServerStreams: true,
15. },
16. },
17. Metadata: "grpc/health/v1/health.proto",
18.}
src.sd就是第11-15行;
进入grpc-go/server.go文件中的processStreamingRPC方法里:
1.func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transport.Stream, srv *service, sd *StreamDesc, trInfo *traceInfo) (err error) {
2. //---省略不相关代码
3. if s.opts.streamInt == nil {
4. appErr = sd.Handler(server, ss)
5. } else {
6. info := &StreamServerInfo{
7. FullMethod: stream.Method(),
8. IsClientStream: sd.ClientStreams,
9. IsServerStream: sd.ServerStreams,
10. }
11. appErr = s.opts.streamInt(server, ss, info, sd.Handler)
12. }
13. //---省略不相关代码
假设在服务器端没有设置拦截器,因此直接进入第4行,handle就是_Health_Watch_Handler,
进入grpc-go/health/grpc_health_v1/health_grpc.pb.go接口文件_Health_Watch_Handler函数里:
1.func _Health_Watch_Handler(srv interface{}, stream grpc.ServerStream) error {
2. m := new(HealthCheckRequest)
3. if err := stream.RecvMsg(m); err != nil {
4. return err
5. }
6. return srv.(HealthServer).Watch(m, &healthWatchServer{stream})
7.}
主要流程说明:
此时,我们已经知道服务器端是如何一步一步的执行到健康检测Watch服务的;接下来,详细的分析一下,Watch的原理。
4、服务器端健康检测的原理? |
在grpc框架中health包中的sever.go文件中的Server结构体实现了HeatlthServer接口;
进入grpc-go/health/server.go文件中的Watch方法里:
1.func (s *Server) Watch(in *healthpb.HealthCheckRequest, stream healthgrpc.Health_WatchServer) error {
2. service := in.Service
3. // update channel is used for getting service status updates.
4. update := make(chan healthpb.HealthCheckResponse_ServingStatus, 1)
5. s.mu.Lock()
6. // Puts the initial status to the channel.
7. if servingStatus, ok := s.statusMap[service]; ok {
8. update <- servingStatus
9. } else {
10. update <- healthpb.HealthCheckResponse_SERVICE_UNKNOWN
11. }
12. // Registers the update channel to the correct place in the updates map.
13. if _, ok := s.updates[service]; !ok {
14. s.updates[service] = make(map[healthgrpc.Health_WatchServer]chan healthpb.HealthCheckResponse_ServingStatus)
15. }
16. s.updates[service][stream] = update
17. defer func() {
18. s.mu.Lock()
19. delete(s.updates[service], stream)
20. s.mu.Unlock()
21. }()
22. s.mu.Unlock()
23. var lastSentStatus healthpb.HealthCheckResponse_ServingStatus = -1
24. for {
25. select {
26. // Status updated. Sends the up-to-date status to the client.
27. case servingStatus := <-update:
28. if lastSentStatus == servingStatus {
29. continue
30. }
31. lastSentStatus = servingStatus
32. err := stream.Send(&healthpb.HealthCheckResponse{Status: servingStatus})
33. if err != nil {
34. return status.Error(codes.Canceled, "Stream has ended.")
35. }
36. // Context done. Removes the update channel from the updates map.
37. case <-stream.Context().Done():
38. return status.Error(codes.Canceled, "Stream has ended.")
39. }
40. }
41.}
主要流程说明:
当服务器端运行时发现某个服务不能正常对外提供服务,需要更新statusMap缓存里服务的状态:
5、健康检测运行时常见的异常场景处理? |
在健康检测服务启动的场景下,可能会存在以下异常场景:
假设有两个服务器端提供SayHello服务,进程都正常启动:
场景一:一个SayHello服务不能对外提供服务,另外一个Sayhello服务可以对外提供服务,那么客户端是如何处理的? |
也就是说,当服务器端,只能有一个服务可以对外提供服务时,那么客户端会将其他链接的状态更新为TransientFailure,将可以提供服务的链接状态更新为Ready, 最后链接的状态也会Ready;
当通过平衡器选择连接时,只能选择连接状态为Ready的链接,进行流的创建,以及传输数据;
可见,利用健康检测服务,可以在真正传输数据前,将存在问题的服务器剔除掉,选择正常的服务器进行传输数据
场景二:异常情况发生在传输数据阶段,当客户端选择了一条Ready状态的链接,在传输数据阶段,突然接收到服务器的通知,该服务已经不能对外提供服务了,客户端如何处理? |
下一篇文章
拦截器介绍