1. 挑战分析
- 错误跨服务传递:不同微服务使用不同协议通信(如HTTP、gRPC),如何有效传递错误信息是挑战。例如HTTP通常通过状态码和简短描述传递错误,难以携带详细错误上下文。
- 日志记录:微服务架构分布式特性,日志分散在各个服务实例,难以关联和分析错误。不同服务日志格式、级别不一致也增加困难。
- 错误码规范:各微服务可能独立定义错误码,导致重复或冲突,给客户端处理带来不便。
2. 优化设计思路
- 错误跨服务传递
- gRPC:利用proto文件定义错误结构,在响应中返回错误信息。如:
syntax = "proto3";
package yourpackage;
message ErrorResponse {
string error_message = 1;
int32 error_code = 2;
}
- **HTTP**:自定义HTTP响应头传递详细错误信息,如 `X-Error-Detail`,同时使用标准HTTP状态码。
- 日志记录
- 集中式日志管理:使用工具如ELK(Elasticsearch, Logstash, Kibana)或Fluentd + Elasticsearch + Kibana收集、存储和分析日志。
- 结构化日志:使用logrus等库记录结构化日志,便于查询和分析。示例:
package main
import (
"github.com/sirupsen/logrus"
)
func main() {
log := logrus.New()
log.SetFormatter(&logrus.JSONFormatter{})
log.WithFields(logrus.Fields{
"service": "user-service",
"error_code": 1001,
}).Error("User not found")
}
- 错误码规范
- 统一错误码空间:定义全局错误码范围,各微服务在自己范围内定义错误码。例如,1 - 1000为系统通用错误码,1001 - 2000为用户服务错误码等。
- 错误码文档化:编写详细错误码文档,说明每个错误码含义、可能原因及解决方法。
3. 示例代码
package main
import (
"context"
pb "yourpackage"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func (s *Server) YourMethod(ctx context.Context, req *pb.Request) (*pb.Response, error) {
if someErrorCondition {
errStatus := status.New(codes.InvalidArgument, "Invalid input")
errResp := &pb.ErrorResponse{
ErrorMessage: errStatus.Message(),
ErrorCode: int32(errStatus.Code()),
}
return nil, errStatus.Err()
}
return &pb.Response{}, nil
}
- **客户端**:
package main
import (
"context"
pb "yourpackage"
"google.golang.org/grpc"
"google.golang.org/grpc/status"
)
func main() {
conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure())
defer conn.Close()
client := pb.NewYourServiceClient(conn)
resp, err := client.YourMethod(context.Background(), &pb.Request{})
if err != nil {
st, _ := status.FromError(err)
errorResp := &pb.ErrorResponse{
ErrorMessage: st.Message(),
ErrorCode: int32(st.Code()),
}
// 处理错误
}
}
package main
const (
ErrCodeUserNotFound = 1001
ErrCodeInvalidPassword = 1002
)
func Login(username, password string) error {
if username == "" {
return &CustomError{
Code: ErrCodeUserNotFound,
Message: "User not found",
}
}
if password == "" {
return &CustomError{
Code: ErrCodeInvalidPassword,
Message: "Invalid password",
}
}
return nil
}
type CustomError struct {
Code int
Message string
}
func (e *CustomError) Error() string {
return e.Message
}