2

foreword

In the go-zero community, students often ask, is it possible to put API gateway and RPC service in the same process? how to do? Sometimes there are students who put external services and consumption queues in one 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.

Problem example

Let's take two HTTP services as an example. We have such two services and need to start two different ports in one process. code show as below:

 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
}

The code is simple enough, that is, there is a request morning interface, the service returns morning! , and the request evening interface, the service returns evening . Let's try to make it happen~

first try

To start two services, isn't it just to start both services in main ? let's try

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

  var evening Evening
  evening.Start()
  defer evening.Stop()
}

After startup, we use curl to verify

 $ curl -i http://localhost:8080/morning
HTTP/1.1 200 OK
Date: Mon, 18 Apr 2022 02:10:34 GMT
Content-Length: 9
Content-Type: text/plain; charset=utf-8

morning!
$ curl -i http://localhost:8081/evening
curl: (7) Failed to connect to localhost port 8081 after 4 ms: Connection refused

Why only morning succeeded, but evening could not request it?

Let's try adding a print statement in main

 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()
}

Restart

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

It was found that only Start morning service… was printed, and the original evening service did not start at all. The reason is that because morning.Start() blocks the current goroutine , the subsequent code cannot be executed.

second attempt

At this point, WaitGroup can come in handy. WaitGroup As the name suggests, it is used for wait a group of operations, waiting for them to notify that they can continue. Let's try it out.

 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()
  }()

  wg.Wait()
}

start try

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

OK, both services are up, let's verify it with curl

 $ curl -i http://localhost:8080/morning
HTTP/1.1 200 OK
Date: Mon, 18 Apr 2022 02:28:33 GMT
Content-Length: 9
Content-Type: text/plain; charset=utf-8

morning!
$ curl -i http://localhost:8081/evening
HTTP/1.1 200 OK
Date: Mon, 18 Apr 2022 02:28:36 GMT
Content-Length: 9
Content-Type: text/plain; charset=utf-8

evening!

It's all right, we see that the process we use WaitGroup is

  1. Remember we have several services that need wait
  2. Add services one by one
  3. Wait for all services to end

Let's see how go-zero is done~

third attempt

In go-zero , we provide a ServiceGroup tool to manage the start and stop of multiple services. Let's see how the scene brought into ours is done.

 import "github.com/zeromicro/go-zero/core/service"

// more code

func main() {
  group := service.NewServiceGroup()
  defer group.Stop()
  group.Add(Morning{})
  group.Add(Evening{})
  group.Start()
}

It can be seen that the readability of the code is much better, and we will not accidentally miscalculate and add a few to WaitGroup . And ServiceGroup also ensures that the service started later is first Stop , and the effect of defer is the same, which is convenient for resource cleaning.

ServiceGroup服务的Start/Stopgraceful shutdown ,当收到SIGTERM时候会主动Call the Stop method of each service. For the HTTP service, you can exit gracefully through the server.Shutdown --- service, and for the gRPC service. Exit gracefully with server.GracefulStop() .

Summarize

The implementation of ServiceGroup 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
------------------------------------------------------------------

Although the code is short and powerful, in go-zero each service (Restful, RPC, MQ) is basically managed by ServiceGroup , which can be said to be very convenient, and the code is worth reading.

project address

https://github.com/zeromicro/go-zero

Welcome go-zero and star support us!

WeChat exchange group

Follow the official account of " Microservice Practice " and click on the exchange group to get the QR code of the community group.


kevinwan
931 声望3.5k 粉丝

go-zero作者