-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
examples: add example to show how to use the health service (#3381)
- Loading branch information
Showing
3 changed files
with
236 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} |