examples: add example to show how to use the health service (#3381)
This commit is contained in:
64
examples/features/health/README.md
Normal file
64
examples/features/health/README.md
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# Health
|
||||||
|
|
||||||
|
gRPC provides a health library to communicate a system's health to their clients.
|
||||||
|
It works by providing a service definition via the [health/v1](https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto) api.
|
||||||
|
|
||||||
|
By using the health library, clients can gracefully avoid using servers as they encounter issues.
|
||||||
|
Most languages provide an implementation out of box, making it interoperable between systems.
|
||||||
|
|
||||||
|
## Try it
|
||||||
|
|
||||||
|
```
|
||||||
|
go run server/main.go -port=50051 -sleep=5s
|
||||||
|
go run server/main.go -port=50052 -sleep=10s
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
go run client/main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
## Explanation
|
||||||
|
|
||||||
|
### Client
|
||||||
|
|
||||||
|
Clients have two ways to monitor a servers health.
|
||||||
|
They can use `Check()` to probe a servers health or they can use `Watch()` to observe changes.
|
||||||
|
|
||||||
|
In most cases, clients do not need to directly check backend servers.
|
||||||
|
Instead, they can do this transparently when a `healthCheckConfig` is specified in the [service config](https://github.com/grpc/proposal/blob/master/A17-client-side-health-checking.md#service-config-changes).
|
||||||
|
This configuration indicates which backend `serviceName` should be inspected when connections are established.
|
||||||
|
An empty string (`""`) typically indicates the overall health of a server should be reported.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// import grpc/health to enable transparent client side checking
|
||||||
|
import _ "google.golang.org/grpc/health"
|
||||||
|
|
||||||
|
// set up appropriate service config
|
||||||
|
serviceConfig := grpc.WithDefaultServiceConfig(`{
|
||||||
|
"loadBalancingPolicy": "round_robin",
|
||||||
|
"healthCheckConfig": {
|
||||||
|
"serviceName": ""
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
conn, err := grpc.Dial(..., serviceConfig)
|
||||||
|
```
|
||||||
|
|
||||||
|
See [A17 - Client-Side Health Checking](https://github.com/grpc/proposal/blob/master/A17-client-side-health-checking.md) for more details.
|
||||||
|
|
||||||
|
### Server
|
||||||
|
|
||||||
|
Servers control their serving status.
|
||||||
|
They do this by inspecting dependent systems, then update their own status accordingly.
|
||||||
|
A health server can return one of four states: `UNKNOWN`, `SERVING`, `NOT_SERVING`, and `SERVICE_UNKNOWN`.
|
||||||
|
|
||||||
|
`UNKNOWN` indicates the current state is not yet known.
|
||||||
|
This state is often seen at the start up of a server instance.
|
||||||
|
|
||||||
|
`SERVING` means that the system is healthy and ready to service requests.
|
||||||
|
Conversely, `NOT_SERVING` indicates the system is unable to service requests at the time.
|
||||||
|
|
||||||
|
`SERVICE_UNKNOWN` communicates the `serviceName` requested by the client is not known by the server.
|
||||||
|
This status is only reported by the `Watch()` call.
|
||||||
|
|
||||||
|
A server may toggle its health using `healthServer.SetServingStatus("serviceName", servingStatus)`.
|
85
examples/features/health/client/main.go
Normal file
85
examples/features/health/client/main.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2020 gRPC authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||||
|
_ "google.golang.org/grpc/health"
|
||||||
|
"google.golang.org/grpc/resolver"
|
||||||
|
"google.golang.org/grpc/resolver/manual"
|
||||||
|
)
|
||||||
|
|
||||||
|
var serviceConfig = `{
|
||||||
|
"loadBalancingPolicy": "round_robin",
|
||||||
|
"healthCheckConfig": {
|
||||||
|
"serviceName": ""
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
func callUnaryEcho(c pb.EchoClient) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||||
|
defer cancel()
|
||||||
|
r, err := c.UnaryEcho(ctx, &pb.EchoRequest{})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("UnaryEcho: _, ", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("UnaryEcho: ", r.GetMessage())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
r, cleanup := manual.GenerateAndRegisterManualResolver()
|
||||||
|
defer cleanup()
|
||||||
|
r.InitialState(resolver.State{
|
||||||
|
Addresses: []resolver.Address{
|
||||||
|
{Addr: "localhost:50051"},
|
||||||
|
{Addr: "localhost:50052"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
address := fmt.Sprintf("%s:///unused", r.Scheme())
|
||||||
|
|
||||||
|
options := []grpc.DialOption{
|
||||||
|
grpc.WithInsecure(),
|
||||||
|
grpc.WithBlock(),
|
||||||
|
grpc.WithDefaultServiceConfig(serviceConfig),
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := grpc.Dial(address, options...)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("did not connect %v", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
echoClient := pb.NewEchoClient(conn)
|
||||||
|
|
||||||
|
for {
|
||||||
|
callUnaryEcho(echoClient)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}
|
87
examples/features/health/server/main.go
Normal file
87
examples/features/health/server/main.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2020 gRPC authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||||
|
"google.golang.org/grpc/health"
|
||||||
|
healthpb "google.golang.org/grpc/health/grpc_health_v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
port = flag.Int("port", 50051, "the port to serve on")
|
||||||
|
sleep = flag.Duration("sleep", time.Second*5, "duration between changes in health")
|
||||||
|
|
||||||
|
system = "" // empty string represents the health of the system
|
||||||
|
)
|
||||||
|
|
||||||
|
type echoServer struct {
|
||||||
|
pb.UnimplementedEchoServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *echoServer) UnaryEcho(ctx context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) {
|
||||||
|
return &pb.EchoResponse{
|
||||||
|
Message: fmt.Sprintf("hello from localhost:%d", *port),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ pb.EchoServer = &echoServer{}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to listen: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := grpc.NewServer()
|
||||||
|
healthcheck := health.NewServer()
|
||||||
|
healthpb.RegisterHealthServer(s, healthcheck)
|
||||||
|
pb.RegisterEchoServer(s, &echoServer{})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// asynchronously inspect dependencies and toggle serving status as needed
|
||||||
|
next := healthpb.HealthCheckResponse_SERVING
|
||||||
|
|
||||||
|
for {
|
||||||
|
healthcheck.SetServingStatus(system, next)
|
||||||
|
|
||||||
|
if next == healthpb.HealthCheckResponse_SERVING {
|
||||||
|
next = healthpb.HealthCheckResponse_NOT_SERVING
|
||||||
|
} else {
|
||||||
|
next = healthpb.HealthCheckResponse_SERVING
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(*sleep)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := s.Serve(lis); err != nil {
|
||||||
|
log.Fatalf("failed to serve: %v", err)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user