gracefully manage multiple services within a process


in the go-zero community, students often ask, is it okay to put and in the same process? how to do it? sometimes, there are also students who put external services and consumption queues in a process. let’s not say whether this usage is reasonable or not, because of the differences in business scenarios and development models of various companies, we will only look at how to solve such problems more elegantly.API gatewayRPC service

example of the problem

let’s use the example of two services, we have two services that need to be started on two different ports within a process. the code is as follows:HTTP

package main
import (  "fmt"  "net/http")
func morning(w http.ResponseWriter, req *http.Request) {  fmt.Fprintln(w, "morning!")}
func evening(w http.ResponseWriter, req *http.Request) {  fmt.Fprintln(w, "evening!")}
type Morning struct{}
func (m Morning) Start() {  http.HandleFunc("/morning", morning)  http.ListenAndServe("localhost:8080", nil)}
func (m Morning) Stop() {  fmt.Println("Stop morning service...")}
type Evening struct{}
func (e Evening) Start() {  http.HandleFunc("/evening", evening)  http.ListenAndServe("localhost:8081", nil)}
func (e Evening) Stop() {  fmt.Println("Stop evening service...")}
func main() {  // todo: start both services here}

copy the code

the code is simple enough, that is, there is a request interface, a service return, a request interface, and a service return. let’s try to implement itmorningmorning!eveningevening

first try

to start two services is not to start both services in it? let’s try itmain

func main() {  var morning Morning  morning.Start()  defer morning.Stop()
  var evening Evening  evening.Start()  defer evening.Stop()}

copy the code

after starting, we will use it to verify itcurl

$ curl -i http://localhost:8080/morningHTTP/1.1 200 OKDate: Mon, 18 Apr 2022 02:10:34 GMTContent-Length: 9Content-Type: text/plain; charset=utf-8
morning!$ curl -i http://localhost:8081/eveningcurl: (7) Failed to connect to localhost port 8081 after 4 ms: Connection refused

copy the code

why is it only successful and not requestable?morningevening

let’s try adding a print statement insidemain

func main() {  fmt.Println("Start morning service...")  var morning Morning  morning.Start()  defer morning.Stop()
  fmt.Println("Start evening service...")  var evening Evening  evening.Start()  defer evening.Stop()}

copy the code


$ go run main.goStart morning service...

copy the code

found that only printed, the original service did not start at all. the reason for this is that because the current is blocked, subsequent code cannot be executed.Start morning service…eveningmorning.Start()goroutine

second try

that’s when it comes in handy. as the name suggests, it is used to set a set of actions and wait for them to be notified to continue. let’s try it.WaitGroupWaitGroupwait

func main() {  var wg sync.WaitGroup  wg.Add(2)
  go func() {    defer wg.Done()    fmt.Println("Start morning service...")    var morning Morning    defer morning.Stop()    morning.Start()  }()
  go func() {    defer wg.Done()    fmt.Println("Start evening service...")    var evening Evening    defer evening.Stop()    evening.Start()  }()

copy the code

start it and try it

$ go run main.goStart evening service...Start morning service...

copy the code

well, both services are up, let’s verify itcurl

$ curl -i http://localhost:8080/morningHTTP/1.1 200 OKDate: Mon, 18 Apr 2022 02:28:33 GMTContent-Length: 9Content-Type: text/plain; charset=utf-8
morning!$ curl -i http://localhost:8081/eveningHTTP/1.1 200 OKDate: Mon, 18 Apr 2022 02:28:36 GMTContent-Length: 9Content-Type: text/plain; charset=utf-8

copy the code

it’s really all right, and we see that the process we use isWaitGroup

  1. remember we had several services that needed to wait
  2. add services one by one
  3. wait for all services to end

let’s see how it’s done~go-zero

third attempt

we provide a tool to easily manage the start and stop of multiple services. let’s see how the scene brought into us is done.go-zeroServiceGroup

import ""
// more code
func main() {  group := service.NewServiceGroup()  defer group.Stop()  group.Add(Morning{})  group.Add(Evening{})  group.Start()}

copy the code

as you can see, the code is much better readable, and we won’t accidentally miscalculate the number of additions. and it also ensures that the service started first, consistent with the effect, such behavior is convenient for resource cleanup.WaitGroupServiceGroupStopdefer

ServiceGroup not only does it manage each service, but it also provides that when a signal is received, it will actively call the methods of each service, for the service, you can exit gracefully, and for the service, you can exit gracefully.Start/Stopgraceful shutdownSIGTERMStopHTTPserver.ShutdowngRPCserver.GracefulStop()


ServiceGroup the implementation of the code is actually relatively simple, with a total of 82 lines of code.

$ cloc core/service/servicegroup.go------------------------------------------------------------------Language        files          blank        comment           code------------------------------------------------------------------Go                 1             22             14             82------------------------------------------------------------------

copy the code

Although the code is short and concise, but every service (Restful, RPC, MQ) is basically managed through it, it can be said that it is very convenient, and the code is worth reading.go-zeroServiceGroup

project address

welcome to use and star support us!go-zero