0X00 Kratos
Kratos is a set of lightweight Go microservices framework, including a number of microservices related frameworks and tools.
The name comes from: “God of War” game is based on the background of Greek mythology, tells the story of Kratos, who becomes the god of war from a mortal and starts the adventure of killing god and killing god.
0X01 Explore How Kratos works with Layout (Kratos V2.0.0-Beta4)
Create a project
First, you need to install the corresponding dependent environment and tools:
- go
- protoc
- protoc-gen-go
Create a project template
kratos new helloworld
cd helloworld
# pull project dependencies
go mod download
Generate a proto template
kratos proto add api/helloworld/helloworld.proto
# Generate proto source code
kratos proto client api/helloworld/helloworld.proto
Create a server template
kratos proto server api/helloworld/helloworld.proto -t internal/service
Copy the code
After the command is executed, a service project is generated in the current directory. The project skeleton is as follows. You can access the detailed description of the project skeletonlayout
Run the project
Generate all proto source code, wire, etc
go generate ./...
Compile to an executable
go build -o ./bin/ ./...
# Run the project
./bin/helloworld -conf ./configs
Copy the code
If you see the following output, the project has started normally
level=INFO module=app service_id=7114ad8a-b3bf-11eb-a1b9-f0189850d2cb service_name= version=
level=INFO module=transport/grpc msg=[gRPC] server listening on: [::]:9000
level=INFO module=transport/http msg=[HTTP] server listening on: [::]:8000
Copy the code
The test interface
The curl 'http://127.0.0.1:8000/helloworld/krtaos' output: {" message ":" Hello kratos}"Copy the code
How does the app run?
Through the above legend 👆, we can intuitively observe the call chain of the application, which is simplified as 👇 in the following figure
1. Inject dependencies and call the newApp() method
// helloword/cmd/main.go
func main(a) {
flag.Parse()
logger := log.NewStdLogger(os.Stdout)
// Call go-kratos/kratos/v2/config to create the config instance, specifying the source and configuration resolution method
c := config.New(
config.WithSource(
file.NewSource(flagconf),
),
config.WithDecoder(func(kv *config.KeyValue, v map[string]interface{}) error {
return yaml.Unmarshal(kv.Value, v)
}),
)
iferr := c.Load(); err ! =nil {
panic(err)
}
// Scan the configuration to the conf struct declared by proto
var bc conf.Bootstrap
iferr := c.Scan(&bc); err ! =nil {
panic(err)
}
// Inject the dependency through wire and call the newApp method
app, cleanup, err := initApp(bc.Server, bc.Data, logger)
iferr ! =nil {
panic(err)
}
// omit code...
}
Copy the code
2. Create an instance of Kratos
The kratos.new () method in go-kratos/kratos/v2/app.go is called in the newApp() method of main.go
// helloword/cmd/main.go
func newApp(logger log.Logger, hs *http.Server, gs *grpc.Server) *kratos.App {
return kratos.New(
// Configure the application
kratos.Name(Name),
kratos.Version(Version),
kratos.Metadata(map[string]string{}),
kratos.Logger(logger),
// The HTTP/GRPC service passed in by kratos.server () is converted to Registry. ServiceInstance struct* by buildInstance()
kratos.Server(
hs,
gs,
),
)
}
Copy the code
This method returns an App struct containing Run() and Stop() methods
// go-kratos/kratos/v2/app.go
type App struct {
opts options / / configuration
ctx context.Context / / context
cancel func(a) // contextCancel method of
instance *registry.ServiceInstance // The instance declared by kratos.server () and converted by buildInstance() to *registry.ServiceInstance struct
log *log.Helper / / log
}
// Run executes all OnStart hooks registered with the application's Lifecycle.
func (a *App) Run(a) error {
// omit code...
}
// Stop gracefully stops the application.
func (a *App) Stop(a) error {
// omit code...
}
Copy the code
3. Call the Run() method
The project calls the Run() method of kratos.app struct in the main method.
// helloword/cmd/main.go
// omit code...
/ / start Kratos
iferr := app.Run(); err ! =nil {
panic(err)
}
Copy the code
Implementation details of the Run() method
// go-kratos/kratos/v2/app.go
func (a *App) Run(a) error {
a.log.Infow(
"service_id", a.opts.id,
"service_name", a.opts.name,
"version", a.opts.version,
)
g, ctx := errgroup.WithContext(a.ctx)
// Iterate over the service instance declared via kratos.server ()
for _, srv := range a.opts.servers {
srv := srv
// Execute two Goroutines to handle service startup and exit
g.Go(func(a) error {
<-ctx.Done() // block and wait for the cancel method to be called
return srv.Stop() // After the coroutine exits, the instance's stop method is called
})
g.Go(func(a) error {
return srv.Start() // Call the instance's run method})}// Determine whether kratos.registrar () is configured with the registration discovery center
ifa.opts.registrar ! =nil {
// Register the instance to the registry
iferr := a.opts.registrar.Register(a.opts.ctx, a.instance); err ! =nil
return err
}
}
// Listen for a process exit signal
c := make(chan os.Signal, 1)
signal.Notify(c, a.opts.sigs...)
// Handle process exit and context exit
g.Go(func(a) error {
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-c:
// Call the stop method of kratos.app
a.Stop()
}
}
})
iferr := g.Wait(); err ! =nil && !errors.Is(err, context.Canceled) {
return err
}
return nil
}
Copy the code
4. The application exits
When the Kratos instance starts up, it listens for the system’s process exit signal, and when it receives the exit signal, Kratos calls the Stop() method of the App struct
// go-kratos/kratos/v2/app.go
func (a *App) Stop(a) error {
// Check whether there is a registry configuration
ifa.opts.registrar ! =nil {
// Unregister the instance in the registry
iferr := a.opts.registrar.Deregister(a.opts.ctx, a.instance); err ! =nil {
return err
}
}
When a.canel () is called, < -ctx.done () listened by Run() will call server Stop() after receiving the message and no blocking
ifa.cancel ! =nil {
a.cancel()
}
return nil
}
Copy the code