Kratos微服务框架实战:从零构建用户服务

Kratos微服务框架实战:从零构建用户服务 1. Kratos微服务框架简介Kratos是B站开源的一款轻量级Go语言微服务框架它提供了一套完整的微服务开发工具链和最佳实践。如果你正在寻找一个既能快速上手又能满足企业级需求的微服务框架Kratos绝对值得一试。我第一次接触Kratos是在开发一个电商平台的用户模块时。当时我们需要一个支持gRPC和HTTP双协议、具备服务发现、依赖注入等特性的框架Kratos完美契合了这些需求。与其他框架相比Kratos最大的特点是它的工程化理念——不是简单堆砌功能而是通过规范化的项目结构和代码生成工具让团队协作更加高效。2. 环境准备与项目初始化2.1 安装必要工具在开始之前你需要确保系统已经安装以下工具# 安装Go (版本1.16) brew install go # 安装protoc编译器 (用于生成gRPC代码) brew install protobuf # 安装Kratos CLI工具 go install github.com/go-kratos/kratos/cmd/kratos/v2latest验证安装是否成功kratos -v # 应该输出类似: kratos version v2.7.02.2 创建用户服务项目我们使用Kratos命令行工具初始化项目骨架kratos new user-service -r https://gitee.com/go-kratos/kratos-layout.git cd user-service go mod tidy这个命令会创建一个标准的Kratos项目结构其中几个关键目录需要了解api/: 存放Protobuf定义的API接口文件internal/: 项目核心代码业务逻辑、数据访问等configs/: 配置文件cmd/: 程序入口3. 定义用户服务API接口3.1 编写Protobuf文件在api/user/v1目录下创建user.proto文件syntax proto3; package api.user.v1; option go_package user-service/api/user/v1;v1; service User { rpc Register (RegisterRequest) returns (RegisterReply); rpc Login (LoginRequest) returns (LoginReply); rpc GetUser (GetUserRequest) returns (GetUserReply); } message RegisterRequest { string username 1; string email 2; string password 3; } message RegisterReply { int64 id 1; } message LoginRequest { string username 1; string password 2; } message LoginReply { string token 1; } message GetUserRequest { int64 id 1; } message GetUserReply { int64 id 1; string username 2; string email 3; }3.2 生成Go代码执行以下命令生成gRPC和HTTP代码# 生成gRPC代码 kratos proto client api/user/v1/user.proto # 生成服务端代码骨架 kratos proto server api/user/v1/user.proto -t internal/service # 生成所有代码 go generate ./...4. 实现数据层与业务逻辑4.1 配置MySQL和Redis修改configs/config.yaml文件server: http: addr: 0.0.0.0:8000 grpc: addr: 0.0.0.0:9000 data: database: driver: mysql source: root:passwordtcp(127.0.0.1:3306)/user_service?charsetutf8mb4parseTimeTruelocLocal redis: addr: 127.0.0.1:6379 password: db: 04.2 实现数据访问层在internal/data目录下创建user.gopackage data import ( context user-service/internal/biz github.com/go-kratos/kratos/v2/log gorm.io/gorm ) type User struct { gorm.Model Username string gorm:uniqueIndex Email string gorm:uniqueIndex Password string } type userRepo struct { data *Data log *log.Helper } func NewUserRepo(data *Data, logger log.Logger) biz.UserRepo { return userRepo{ data: data, log: log.NewHelper(logger), } } func (r *userRepo) CreateUser(ctx context.Context, u *biz.User) (*biz.User, error) { user : User{ Username: u.Username, Email: u.Email, Password: u.Password, } if err : r.data.db.WithContext(ctx).Create(user).Error; err ! nil { return nil, err } u.ID user.ID return u, nil } func (r *userRepo) GetUser(ctx context.Context, id int64) (*biz.User, error) { var user User if err : r.data.db.WithContext(ctx).First(user, id).Error; err ! nil { return nil, err } return biz.User{ ID: user.ID, Username: user.Username, Email: user.Email, }, nil }4.3 实现业务逻辑在internal/biz目录下创建user.gopackage biz import ( context errors github.com/go-kratos/kratos/v2/log ) type User struct { ID int64 Username string Email string Password string } type UserRepo interface { CreateUser(context.Context, *User) (*User, error) GetUser(context.Context, int64) (*User, error) } type UserUsecase struct { repo UserRepo log *log.Helper } func NewUserUsecase(repo UserRepo, logger log.Logger) *UserUsecase { return UserUsecase{repo: repo, log: log.NewHelper(logger)} } func (uc *UserUsecase) Register(ctx context.Context, u *User) (*User, error) { if u.Username || u.Password { return nil, errors.New(invalid username or password) } return uc.repo.CreateUser(ctx, u) } func (uc *UserUsecase) Get(ctx context.Context, id int64) (*User, error) { return uc.repo.GetUser(ctx, id) }5. 实现gRPC服务在internal/service目录下修改user.gopackage service import ( context user-service/internal/biz v1 user-service/api/user/v1 github.com/go-kratos/kratos/v2/log ) type UserService struct { v1.UnimplementedUserServer uc *biz.UserUsecase log *log.Helper } func NewUserService(uc *biz.UserUsecase, logger log.Logger) *UserService { return UserService{ uc: uc, log: log.NewHelper(logger), } } func (s *UserService) Register(ctx context.Context, req *v1.RegisterRequest) (*v1.RegisterReply, error) { user, err : s.uc.Register(ctx, biz.User{ Username: req.Username, Email: req.Email, Password: req.Password, }) if err ! nil { return nil, err } return v1.RegisterReply{Id: user.ID}, nil } func (s *UserService) GetUser(ctx context.Context, req *v1.GetUserRequest) (*v1.GetUserReply, error) { user, err : s.uc.Get(ctx, req.Id) if err ! nil { return nil, err } return v1.GetUserReply{ Id: user.ID, Username: user.Username, Email: user.Email, }, nil }6. 依赖注入与服务启动6.1 配置依赖注入Kratos使用Wire进行依赖注入。修改internal/server/wire.go// build wireinject package server import ( user-service/internal/biz user-service/internal/conf user-service/internal/data user-service/internal/service github.com/go-kratos/kratos/v2 github.com/go-kratos/kratos/v2/log github.com/google/wire ) func initApp(*conf.Server, *conf.Data, log.Logger) (*kratos.App, func(), error) { panic(wire.Build( server.ProviderSet, data.ProviderSet, biz.ProviderSet, service.ProviderSet, newApp, )) }6.2 启动服务执行以下命令启动服务# 生成依赖注入代码 wire ./... # 启动服务 kratos run服务启动后你会在日志中看到类似输出INFO msgconfig loaded: config.yaml format: yaml INFO [gRPC] server listening on: [::]:9000 INFO [HTTP] server listening on: [::]:80007. 测试与验证7.1 使用grpcurl测试gRPC接口# 注册用户 grpcurl -plaintext -d {username:test, email:testexample.com, password:123456} localhost:9000 api.user.v1.User/Register # 查询用户 grpcurl -plaintext -d {id:1} localhost:9000 api.user.v1.User/GetUser7.2 HTTP接口测试Kratos会自动为gRPC服务生成HTTP接口。你可以用curl测试# 注册用户 curl -X POST http://localhost:8000/api.user.v1.User/Register \ -H Content-Type: application/json \ -d {username:test, email:testexample.com, password:123456} # 查询用户 curl -X POST http://localhost:8000/api.user.v1.User/GetUser \ -H Content-Type: application/json \ -d {id:1}8. 服务注册与发现Consul集成8.1 安装并启动Consulbrew install consul consul agent -dev8.2 修改配置在configs/config.yaml中添加Consul配置registry: consul: address: 127.0.0.1:8500 scheme: http8.3 修改服务代码更新internal/server/server.gopackage server import ( consul github.com/go-kratos/kratos/contrib/registry/consul/v2 consulAPI github.com/hashicorp/consul/api github.com/go-kratos/kratos/v2/registry github.com/google/wire ) var ProviderSet wire.NewSet(NewGRPCServer, NewHTTPServer, NewRegistrar) func NewRegistrar(conf *conf.Registry) registry.Registrar { c : consulAPI.DefaultConfig() c.Address conf.Consul.Address c.Scheme conf.Consul.Scheme cli, err : consulAPI.NewClient(c) if err ! nil { panic(err) } r : consul.New(cli) return r }更新cmd/user/main.gofunc newApp(logger log.Logger, gs *grpc.Server, hs *http.Server, rr registry.Registrar) *kratos.App { return kratos.New( kratos.ID(id), kratos.Name(Name), kratos.Version(Version), kratos.Metadata(map[string]string{}), kratos.Logger(logger), kratos.Server(gs, hs), kratos.Registrar(rr), ) }现在重启服务你应该能在Consul的Web界面http://localhost:8500看到注册的服务。9. 项目优化与进阶9.1 添加JWT认证中间件在internal/server/middleware/auth目录下创建auth.gopackage auth import ( context github.com/go-kratos/kratos/v2/middleware github.com/go-kratos/kratos/v2/transport github.com/golang-jwt/jwt/v4 ) func JWTAuth(secret string) middleware.Middleware { return func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req interface{}) (interface{}, error) { if tr, ok : transport.FromServerContext(ctx); ok { tokenString : tr.RequestHeader().Get(Authorization) if tokenString { return nil, errors.New(missing authorization header) } token, err : jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { return []byte(secret), nil }) if err ! nil { return nil, err } if claims, ok : token.Claims.(jwt.MapClaims); ok token.Valid { ctx context.WithValue(ctx, userID, claims[userID]) } else { return nil, errors.New(invalid token) } } return handler(ctx, req) } } }9.2 添加Prometheus监控在internal/server/metrics目录下创建metrics.gopackage metrics import ( github.com/go-kratos/kratos/v2/metrics github.com/prometheus/client_golang/prometheus ) var ( _ metrics.Counter (*counter)(nil) _ metrics.Gauge (*gauge)(nil) _ metrics.Histogram (*histogram)(nil) ) type counter struct { cv *prometheus.CounterVec lvs []string } func (c *counter) Inc() { c.cv.WithLabelValues(c.lvs...).Inc() } func (c *counter) Add(delta float64) { c.cv.WithLabelValues(c.lvs...).Add(delta) } type gauge struct { gv *prometheus.GaugeVec lvs []string } func (g *gauge) Set(value float64) { g.gv.WithLabelValues(g.lvs...).Set(value) } type histogram struct { hv *prometheus.HistogramVec lvs []string } func (h *histogram) Observe(value float64) { h.hv.WithLabelValues(h.lvs...).Observe(value) }10. 部署与运维建议10.1 Docker化部署创建DockerfileFROM golang:1.19 as builder WORKDIR /app COPY . . RUN go mod download RUN CGO_ENABLED0 GOOSlinux go build -ldflags -s -w -o user-service ./cmd/user FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --frombuilder /app/user-service . COPY --frombuilder /app/configs ./configs EXPOSE 8000 9000 CMD [./user-service]构建并运行docker build -t user-service . docker run -p 8000:8000 -p 9000:9000 user-service10.2 Kubernetes部署示例创建deployment.yamlapiVersion: apps/v1 kind: Deployment metadata: name: user-service spec: replicas: 3 selector: matchLabels: app: user-service template: metadata: labels: app: user-service spec: containers: - name: user-service image: user-service:latest ports: - containerPort: 8000 - containerPort: 9000 env: - name: ENVIRONMENT value: production在实际项目中Kratos的这些特性让我们能够快速构建出高可用、易维护的微服务。特别是在团队协作中统一的代码结构和自动生成的API文档大大减少了沟通成本。