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/v2@latest
验证安装是否成功:
kratos -v
# 应该输出类似: kratos version v2.7.0
2.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:password@tcp(127.0.0.1:3306)/user_service?charset=utf8mb4&parseTime=True&loc=Local
redis:
addr: 127.0.0.1:6379
password: ""
db: 0
4.2 实现数据访问层
在
internal/data
目录下创建
user.go
:
package 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.go
:
package 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.go
:
package 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 msg=config loaded: config.yaml format: yaml
INFO [gRPC] server listening on: [::]:9000
INFO [HTTP] server listening on: [::]:8000
7. 测试与验证
7.1 使用grpcurl测试gRPC接口
# 注册用户
grpcurl -plaintext -d '{"username":"test", "email":"test@example.com", "password":"123456"}' localhost:9000 api.user.v1.User/Register
# 查询用户
grpcurl -plaintext -d '{"id":1}' localhost:9000 api.user.v1.User/GetUser
7.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":"test@example.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 安装并启动Consul
brew install consul
consul agent -dev
8.2 修改配置
在
configs/config.yaml
中添加Consul配置:
registry:
consul:
address: 127.0.0.1:8500
scheme: http
8.3 修改服务代码
更新
internal/server/server.go
:
package 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.go
:
func 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.go
:
package 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.go
:
package 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化部署
创建
Dockerfile
:
FROM golang:1.19 as builder
WORKDIR /app
COPY . .
RUN go mod download
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -w" -o user-service ./cmd/user
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/user-service .
COPY --from=builder /app/configs ./configs
EXPOSE 8000 9000
CMD ["./user-service"]
构建并运行:
docker build -t user-service .
docker run -p 8000:8000 -p 9000:9000 user-service
10.2 Kubernetes部署示例
创建
deployment.yaml
:
apiVersion: 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文档大大减少了沟通成本。
更多推荐
所有评论(0)