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