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
- Remember we have several services that need
wait
- Add services one by one
- 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/Stop
, graceful 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.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。