I am trying to understand GoLang "Go" together with gRPC and to make a simple service scalable.
Lets say I have a client1 that calls a service1(adds numbers) that calls service2(determines if the result is prime), and service2 returns the result to service1 that returns the result to client1 all via gRPC.
When I use protocol buffers "proto3" and generate the Go code via protoc.
I get generated methods that call the service in one particular way.
I see no distinction to call the methods asynchronously "Go".
And the underlying call seems to be "Invoke" which I believe is synchronous,the call returns once a result is received.
How do I make service1 "performant", I know I can run this in a cluster and have copies, but that would mean I can only serve clients as per the amount of instances within the cluster.
I want a "single" service to be able to serve multiple clients(e.g. 1000) .
Here is a simple server and I am not sure if this is performant or not:
I do know that the getprime function does dial every time,
and this could probably be moved to make this dial persist and be re-used; But more importantly I want to make a simple performant scaleable service and get a good understanding.
(A)
Perhaps the whole design is incorrect and the service1 should just return
as soon as the instruction is received "ack", do the addition and send the next request to sercice2 which determines if the answer is prime or not; again service2 just responds with an acknowledgement of the request being received. Once prime is determined by the service2 a call is made to the client with an answer.
If (A) above is the better approach, then still please explain the bottlenecks below; what happens when multiple clients are processed?
The call to "Listen" does what, "blocks, or does not block", etc.
package main
import (
pb "demo/internal/pkg/proto_gen/calc"
"fmt"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
"log"
"net"
)
const (
port = ":8080"
)
type service struct {
}
func (s *service) Calculate(ctx context.Context, req *pb.Instruction) (*pb.Response, error) {
var answer float64
answer = req.Number1 + req.Number2
// call service prime
p := getprime(int(answer))
pa := pb.PrimeAnswer{Prime: p}
return &pb.Response{Answer: answer, Prime: &pa}, nil
}
const (
primeAddress = "127.0.0.1:8089"
)
func getprime(number int) bool {
conn, err := grpc.Dial(primeAddress, grpc.WithInsecure())
if err != nil {
log.Fatalf("Did not connect to prime service: %v", err)
}
defer conn.Close()
client := pb.NewPrimeServiceClient(conn)
p := pb.PrimeMessage{"", float64(number)}
r, err := client.Prime(context.Background(), &p)
if err != nil {
log.Fatalf("Call to prime service failed: %v", err)
}
return r.Prime
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterCalculatorServer(s, &service{})
reflection.Register(s)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
Thanks for your question. It is true that gRPC-Go is sync only; that is your Unary RPC(the one in your example) will return only when the RPC has finished (got a response from the server).
About performance:
The Dial operation establishes an underlying connection which may be expensive. So it not wise to do it every time getprime is called. A better way is to create a client, keep it around and make calls to the prime server on it. This way only first RPC incurs the cost of connection.
For each RPC request a server gets we launch a goroutine to process that request. So in general, this should scale fairly well.
About (A): It is not uncommon for a service handler to make an RPC call to yet another server and wait for its response before returning back.
Note that there's no way for a server to make call to the client.
To phrase what JimB said as an answer: "Synchronous" means that the function that makes the remote call waits for a reply before continuing, not that the whole server or client does. The server is normally multithreaded, even when processing synchronous calls; it can accept and work on a second call while it's responding to the first.
And similarly, if a client has multiple concurrent tasks that each have a gRPC call running, that won't block the process. Clients like that could include net/http servers serving end users, or gRPC servers handling multiple RPCs.
Where you might add explicit go statements is if you want to do something else from the specific function making the RPC call. For example, if you want to issue several RPC calls at once then wait for all their results to come in, you could write code following the examples of fan-out calls.
Related
I'm experimenting with Protobuf and gRPC and was going through the Go basics tutorial: https://www.grpc.io/docs/languages/go/basics/
From what I could see in the documentation about the ClientConn it seems that it is safe to use the same ClientConn concurrently. But I can't anything about the stub/client that one creates...
Say I was receiving data via HTTP POST requests and I wanted to forward that data via gRPC as protobuf and then respond to the HTTP request. Should I create a client/stub every time I process a HTTP request, or can I create the client/stub when setting up the HTTP server and pass in the client/stub when setting up HTTP Handlers?
Example: I assume this is okay
var opts []grpc.DialOption
conn, err := grpc.Dial("127.0.0.1:1234", opts...)
if err != nil {
log.Panic(err)
}
defer conn.Close()
grpcService := service.Service{GRPC: conn}
http.HandleFunc("/", util.ValidateRequest(grpcService.HandleRoot))
// listen to port
http.ListenAndServe(fmt.Sprintf("%s:%d", viper.GetString("server.address"), viper.GetInt32("server.port")), nil)
but what about this
var opts []grpc.DialOption
conn, err := grpc.Dial("127.0.0.1:1234", opts...)
if err != nil {
log.Panic(err)
}
defer conn.Close()
client := pb.NewEventShipperClient(conn)
grpcService := service.Service{GRPC: conn, Client: client}
http.HandleFunc("/", util.ValidateRequest(grpcService.HandleRoot))
// listen to port
http.ListenAndServe(fmt.Sprintf("%s:%d", viper.GetString("server.address"), viper.GetInt32("server.port")), nil)
Thread safety ("is it safe to run this concurrently") is covered in the Generated-code reference:
Thread-safety: note that client-side RPC invocations and server-side RPC handlers are thread-safe and are meant to be run on concurrent goroutines. But also note that for individual streams, incoming and outgoing data is bi-directional but serial; so e.g. individual streams do not support concurrent reads or concurrent writes (but reads are safely concurrent with writes).
So, yes, methods on the struct returned by pb.NewEventShipperClient(conn) can be called concurrently.
You can also check this yourself by looking at the implementation of pb.NewEventShipperClient. Currently (I guess this may change) the only member variable will be an interface (cc grpc.ClientConnInterface - implemented by *ClientConn) with each method using the connection to Invoke a gRPC call.
Please note, however, that you need to be careful in your implementation of service.Service to ensure it is also threadsafe and note the caveat regarding streams.
Bit of a newb to both Go and GRPC, so bear with me.
Using go version go1.14.4 windows/amd64, proto3, and latest grpc (1.31 i think). I'm trying to set up a bidi streaming connection that will likely be open for longer periods of time. Everything works locally, except if I terminate the client (or one of them) it kills the server as well with the following error:
Unable to trade data rpc error: code = Canceled desc = context canceled
This error comes out of this code server side
func (s *exchangeserver) Trade(stream proto.ExchageService_TradeServer) error {
endchan := make(chan int)
defer close(endchan)
go func() {
for {
req, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatal("Unable to trade data ", err)
break
}
fmt.Println("Got ", req.GetNumber())
}
endchan <- 1
}()
go func() {
for {
resp := &proto.WordResponse{Word: "Hello again "}
err := stream.Send(resp)
if err != nil {
log.Fatal("Unable to send from server ", err)
break
}
time.Sleep(time.Duration(500 * time.Millisecond))
}
endchan <- 1
}()
<-endchan
return nil
}
And the Trade() RPC is so simple it isn't worth posting the .proto.
The error is clearly coming out of the Recv() call, but that call blocks until it sees a message, like the client disconnect, at which point I would expect it to kill the stream, not the whole process. I've tried adding a service handler with HandleConn(context, stats.ConnStats) and it does catch the disconnect before the server dies, but I can't do anything with it. I've even tried creating a global channel that the serve handler pushes a value into when HandleRPC(context, stats.RPCStats) is called and only allowing Recv() to be called when there's a value in the channel, but that can't be right, that's like blocking a blocking function for safety and it didn't work anyway.
This has to be one of those real stupid mistakes that beginner's make. Of what use would GPRC be if it couldn't handle a client disconnect without dying? Yet I have read probably a trillion (ish) posts from every corner of the internet and noone else is having this issue. On the contrary, the more popular version of this question is "My client stream stays open after disconnect". I'd expect that issue. Not this one.
Im not 100% sure how this is supposed to behave but I note that you are starting separate receive and send goroutines up at the same time. This might be valid but is not the typical approach. Instead you would usually receive what you want to process and then start a nested loop to handle the reply .
See an example of typical bidirectional streaming implementation from here: https://grpc.io/docs/languages/go/basics/
func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error {
for {
in, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
key := serialize(in.Location)
... // look for notes to be sent to client
for _, note := range s.routeNotes[key] {
if err := stream.Send(note); err != nil {
return err
}
}
}
}
sending and receiving at the same time might be valid for your use case but if that is what you are trying to do then I believe your handling of the channels is incorrect. Either way, please read on to understand the issue as it is a common one in go.
You have a single channel which only blocks until it receives a single message, once it unblocks the function ends and the channel is closed (by defer).
You are trying to send to this channel from both your send and receive
loop.
When the last one to finish tries to send to the channel it will have been closed (by the first to finish) and the server will panic. Annoyingly, you wont actually see any sign of this as the server will exit before the goroutine can dump its panic (no clues - probably why you landed here)
see an example of the issue here (grpc code stripped out):
https://play.golang.org/p/GjfgDDAWNYr
Note: comment out the last pause in the main func to stop showing the panic reliably (as in your case)
So one simple fix would probably be to simply create two separate channels (one for send, one for receive) and block on both - this however would leave the send loop open necessarily if you don't get a chance to respond so probably better to structure like the example above unless you have good reason to pursue something different.
Another possibility is some sort server/request context mix up but I'm pretty sure the above will fix - drop an update with your server setup code if your still having issues after the above changes
What is the proper way to manage resource ownership in golang? Suppose I have the following:
db, err := sql.Open("mysql", "role#/test_db")
am := NewResourceManager(db)
am.DoWork()
db.Close()
Is it typical to always have the calling function maintain ownership and responsibility for closing resources? This feels a little weird to me because after closing, am still retains a reference and could try to use db if I or someone else is not careful later on (I guess this is a case for defer; however, if I want to pass the ResourceManager am back from this block, how would I even defer the closing of the file properly? I actually want it to stay open when this block finishes execution). I find that in other languages I often want to allow the instance to manage the resource and then clean it up when it's destructor is called, like this toy python example:
class Writer():
def __init__(self, filename):
self.f = open(filename, 'w+')
def __del__(self):
self.f.close()
def write(value):
self.f.write(value)
Unfortunately, there are no destructors in golang. I'm not sure how I would do this in go other than something like this:
type ResourceManager interface {
DoWork()
// Close() ?
}
type resourceManager struct {
db *sql.DB
}
func NewResourceManager(db *sql.DB) ResourceManager {
return &resourceManager{db}
}
db, err := sql.Open("mysql", "role#/test_db")
am := NewResourceManager(db)
am.DoWork()
am.Close() // using method shortening
But this seems less transparent, and I'm not sure how to communicate that the ResourceManager also needs to be Close()'d now. I'm finding this a frequent stumbling block, i.e. I also want to have a resource manager that holds a gRPC client connection, and if these types of resources aren't managed by resource managing objects, it seems like my main function is going to be cluttered with a lot of resource management, i.e. opening and closing. For instance, I could imagine a case where I wouldn't want main to know anything about the object and it's resources:
...
func NewResourceManager() ResourceManager {
db, err := sql.Open("mysql", "role#/test_db")
return &resourceManager{db}
}
...
// main elsewhere
am := NewResourceManager()
am.DoWork()
You chose a bad example, since you generally would reuse a database connection, instead of opening and closing one for each use. Hence, you would pass the db connection to functions using it and do the resource management in the caller without any need for an resource manager:
// Imports etc omitted for the sake of readability
func PingHandler(db *sql.DB) http.Handler (
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := db.ping(); err != nil {
http.Error(w,e.Error(),500)
}
})
)
func main(){
db,_ := sql.Open("superdb",os.Getenv("APP_DBURL"))
// Note the db connection will only be closed if main exits.
defer db.Close()
// Setup the server
http.Handle("/ping", PingHandler(db))
server := &http.Server{Addr: ":8080"}
// Create a channel for listening on SIGINT, -TERM and -QUIT
stop := make(chan os.Signal, 1)
// Register channel to be notified on said signals
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
go func(){
// When we get the signal...
<- stop
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
// ... we gracefully shut down the server.
// That ensures that no new connections, which potentially
// would use our db connection, are accepted.
if err := server.Shutdown(ctx); err != nil {
// handle err
}
}
// This blocks until the server is shut down.
// AFTER it is shut down, main exits, the deferred calls are executed.
// In this case, the database connection is closed.
// And it is closed only after the last handler call which uses the connection is finished.
// Mission accomplished.
server.ListenAndServe()
}
So in this example, there is no need for a resource manager and I honestly can not think of an example actually needing one. In the rare cases I needed something akin to one, I used sync.Pool.
However, for gRPC client connections, there is no need to maintain a pool, either:
[...]However, the ClientConn should manage the connections itself, so if a connection is broken, it will reconnect automatically. And if you have multiple backends, it's possible to connect to multiple of them and load balance between them. [...]
So the same principle applies: Create a connection (pool), pass it around as required, ensure that it is closed after all work is done.
Go proverb:
Clear is better than clever.
– Robert Pike
Instead of hiding resource management somewhere else, manage the resources as closely to the code where they are used and as explicitly as you possibly can.
I was following some of the tutorial for creating bidirectional grpc client and server. Client will pass some value and when last maximum value changed on server it'll response client with current max. Finally I'd like to write down some of the test cases but I have no experience with testing scenarios that's why I'm not sure if I'm doing the correct thing or not.
func TestClientConnection(t *testing.T) {
creds, _ := credentials.NewClientTLSFromFile("../server-cert.pem", "")
conn, err := grpc.Dial(address, grpc.WithTransportCredentials(creds))
if err != nil {
t.Error("Had problem with connection, NOT PASSED")
}
defer conn.Close()
c := proto.NewHerdiusServerClient(conn)
stream, err := c.CheckMax(context.Background())
if err != nil {
t.Error("Had problem with stream, NOT PASSED")
return
}
err = stream.Send(&proto.MaxRequest{Val: int32(10)})
err = stream.Send(&proto.MaxRequest{Val: int32(12)})
err = stream.Send(&proto.MaxRequest{Val: int32(13)})
err = stream.Send(&proto.MaxRequest{Val: int32(9)})
if err != nil {
t.Error("Had problem with stream, NOT PASSED")
return
}
return
}
Right now when I test this scenario wiht go test it passes but I also want to test if something received from server side.
My second question was If I want to tear this test to different scenarios for example to check is server connected or is stream connected or it received response from server side, how can I do that? Should I create another class to retrieve connection and streaming and use on test functions?
Create a contest with timeout context.WithTimeout, and after sending your data call Recv on the stream. Check if you receive anything within the timeout.
The specifics depend on the protocol here - you may need a goroutine to Recv if you have to send server data at the same time.
As for your second question, the Go philosophy is to have clear, explicit, readable tests for each scenario. It's OK if some code is duplicated. It's much more important that each test in isolation is readable and understandable. In cases where the tests are very repetitive one should use table driven tests, but in the cases you describe that sounds like separate tests to me.
It's useful to have tests that "build up" functionality. One test to test connection, the other connection and sending, yet another connection and sending and receiving. This way when tests fail, by rerunning them individually you can very quickly isolate the problem even before you look at the tests' code.
Hi I developed a little go server that does (at the moment) nothing but forwarding the request to a local service on the machine it is running.
So nearly the same as nginx as reverse proxy.
But I observed a really bad performance that even uses up all resources of the server and runs into timeouts on further requests.
I know that this cannot be as performant as nginx, but I don't think that it should be that slow.
Here is the server I use for forwarding the request:
package main
import (
"github.com/gorilla/mux"
"net/http"
"github.com/sirupsen/logrus"
"bytes"
"io/ioutil"
)
func main() {
router := mux.NewRouter()
router.HandleFunc("/", forwarder).Methods("POST")
server := http.Server{
Handler: router,
Addr: ":8443",
}
logrus.Fatal(server.ListenAndServeTLS("cert.pem", "key.pem"))
}
var client = &http.Client{}
func forwarder(w http.ResponseWriter, r *http.Request) {
// read request
body, err := ioutil.ReadAll(r.Body)
if err != nil {
logrus.Error(err.Error())
ServerError(w, nil)
return
}
// create forwarding request
req, err := http.NewRequest("POST", "http://localhost:8000", bytes.NewReader(body))
if err != nil {
logrus.Error(err.Error())
ServerError(w, nil)
return
}
resp, err := client.Do(req)
if err != nil {
logrus.Error(err.Error())
ServerError(w, nil)
return
}
// read response
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
logrus.Error(err.Error())
ServerError(w, nil)
return
}
resp.Body.Close()
// return response
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(resp.StatusCode)
w.Write(respBody)
}
From the client side I just measure the roundtrip time. And when I fire 100 Requests per second the response time goes up quite fast.
It starts with a response time of about 50ms. After 10 Seconds the response time is at 500ms. After 10 more seconds the response time is at 8000ms and so on, until I get timeouts.
When I use the nginx instead of my server there is no problem running 100 requests per second. Using nginx it stays at 40ms per each request.
Some observation:
using nginx: lsof -i | grep nginx
has no more than 2 connections open.
using my server the number of connection increases up to 500 and then the connections with state SYN_SENT increases and then the requets run into timeouts.
Another finding: I measured the delay of this code line:
resp, err := client.Do(req)
There is where most of the time is spent, but the could also just be because the go routines are starving!?
What I also tried:
r.Close = true (or KeepAlive = false)
I modified timeouts on the server side
I modified all this stuff on the http client used by my forward server (keepalive false, request.Close = true) etc.
I don't know why I got such a bad performance.
My guess is that go runs into problems because of the huge number of go routines. Maybe most of the time is used up scheduling this go routines and so the latency goes up?
I also tried to use the included httputil.NewSingleHostReverseProxy(). Performance is a little bit better, but still the same problem.
UPDATE:
Now I tried fasthttp:
package main
import (
"github.com/sirupsen/logrus"
"github.com/valyala/fasthttp"
)
func StartNodeManager() {
fasthttp.ListenAndServeTLS(":8443", "cert.pem", "key.pem", forwarder)
}
var client = fasthttp.Client{}
func forwarder(ctx *fasthttp.RequestCtx) {
resp := fasthttp.AcquireResponse()
req := fasthttp.AcquireRequest()
req.Header.SetMethod("POST")
req.SetRequestURI("http://127.0.0.1:8000")
req.SetBody(ctx.Request.Body())
err := client.Do(req, resp)
if err != nil {
logrus.Error(err.Error())
ctx.Response.SetStatusCode(500)
return
}
ctx.Response.SetBody(resp.Body())
fasthttp.ReleaseRequest(req)
fasthttp.ReleaseResponse(resp)
}
Little bit better but after 30 seconds the first timeouts arrive and the response time goes up to 5 seconds.
The root cause of the problem is GO http module is not handling connections to upstream in
a manged way, time is increasing because lots of connections are getting opened and they go into time_wait state.
So with number of increasing connections, you will get decrease in performance.
You just have to set
// 1000 what I am using
http.DefaultTransport.(*http.Transport).MaxIdleConns = 1000
http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost = 1000
in your forwarder and this will solve your problem.
By the way, use go std library reverse proxy, this will take away lot of headache.
But still for reverse proxy you need to set MaxIdleConns and MaxIdleConnsPerHost , in it's transport.
Follow the article given below.
First of all you should profile your app and find out where is the bottleneck.
Second I would be looking to way write code with less memory allocation in heap and more on stack.
Few ideas:
Do you need read request body for all request?
Do you need always read response body?
Can you pass body of client request to request to server? func NewRequest(method, url string, body io.Reader) (*Request, error)
Use sync.Pool
Consider using fasthttp as it creates less pressure to garbage collector
Check if your server uses same optimisation as Nginx. E.g. Keep-Alive, caching, etc.
Again profile and compare against Nginx.
Seems there is a lot of space for optimization.