以前使用 Laravel 做 Web 项目时,是根据 MVC 去划分目录结构的,即 Controller 层处理业务逻辑,Model 层处理数据库的 CURD,View 层处理数据渲染与页面交互。以及 MVP、MVVM 都是将整个项目的代码是集中在一个代码库中,进行业务处理。这种单一聚合代码的方式在前期实现业务的速度很快,但在后期会暴露很多问题:
微服务是一种软件架构,它将一个大且聚合的业务项目拆解为多个小且独立的业务模块,模块即服务,各服务间使用高效的协议(protobuf、JSON 等)相互调用即是 RPC。这种拆分代码库的方式有以下特点:
本质上,二者只是聚合与拆分代码的方式不同。
参考: 微服务架构的优势与不足
接下来创建一个处理用户信息的微服务:UserInfoService,客户端通过 name 向服务端查询用户的年龄、职位等详细信息,需先安装 gRPC 与 protoc 编译器:
go get -u google.golang.org/grpc go get -u github.com/golang/protobuf/protoc-gen-go
目录结构
├── proto │ ├── user.proto // 定义客户端请求、服务端响应的数据格式 │ └── user.pb.go // protoc 为 gRPC 生成的读写数据的函数 ├── server.go // 实现微服务的服务端 └── client.go // 调用微服务的客户端
每个微服务有自己独立的代码库,各自之间在通信时需要高效的协议,要遵循一定的数据结构来解析和编码要传输的数据,在微服务中常使用 protobuf 来定义。
Protobuf(protocal buffers)是谷歌推出的一种二进制数据编码格式,相比 XML 和 JSON 的文本数据编码格式更有优势:
语言中立
只需定义一份 .proto 文件,即可使用各语言对应的 protobuf 编译器对其编译,生成的文件中有对 message 编码、解码的函数。
对于 JSON
对于 Protobuf
定义微服务的 user.proto 文件
syntax = "proto3"; // 指定语法格式,注意 proto3 不再支持 proto2 的 required 和 optional package proto; // 指定生成的 user.pb.go 的包名,防止命名冲突 // service 定义开放调用的服务,即 UserInfoService 微服务 service UserInfoService { // rpc 定义服务内的 GetUserInfo 远程调用 rpc GetUserInfo (UserRequest) returns (UserResponse) { } } // message 对应生成代码的 struct // 定义客户端请求的数据格式 message UserRequest { // [修饰符] 类型 字段名 = 标识符; string name = 1; } // 定义服务端响应的数据格式 message UserResponse { int32 id = 1; string name = 2; int32 age = 3; repeated string title = 4; // repeated 修饰符表示字段是可变数组,即 slice 类型 }
编译 user.proto 文件
# protoc 编译器的 grpc 插件会处理 service 字段定义的 UserInfoService # 使 service 能编码、解码 message $ protoc -I . --go_out=plugins=grpc:. ./user.proto
生成 user.pb.go
package proto import ( context "golang.org/x/net/context" grpc "google.golang.org/grpc" ) // 请求结构 type UserRequest struct { Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` } // 为字段自动生成的 Getter func (m *UserRequest) GetName() string { if m != nil { return m.Name } return "" } // 响应结构 type UserResponse struct { Id int32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` Age int32 `protobuf:"varint,3,opt,name=age" json:"age,omitempty"` Title []string `protobuf:"bytes,4,rep,name=title" json:"title,omitempty"` } // ... // 客户端需实现的接口 type UserInfoServiceClient interface { GetUserInfo(ctx context.Context, in *UserRequest, opts ...grpc.CallOption) (*UserResponse, error) } // 服务端需实现的接口 type UserInfoServiceServer interface { GetUserInfo(context.Context, *UserRequest) (*UserResponse, error) } // 将微服务注册到 grpc func RegisterUserInfoServiceServer(s *grpc.Server, srv UserInfoServiceServer) { s.RegisterService(&_UserInfoService_serviceDesc, srv) } // 处理请求 func _UserInfoService_GetUserInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {...}
实现流程
代码参考
package main import (...) // 定义服务端实现约定的接口 type UserInfoService struct{} var u = UserInfoService{} // 实现 interface func (s *UserInfoService) GetUserInfo(ctx context.Context, req *pb.UserRequest) (resp *pb.UserResponse, err error) { name := req.Name // 模拟在数据库中查找用户信息 // ... if name == "wuYin" { resp = &pb.UserResponse{ Id: 233, Name: name, Age: 20, Title: []string{"Gopher", "PHPer"}, // repeated 字段是 slice 类型 } } err = nil return } func main() { port := ":2333" l, err := net.Listen("tcp", port) if err != nil { log.Fatalf("listen error: %v/n", err) } fmt.Printf("listen %s/n", port) s := grpc.NewServer() // 将 UserInfoService 注册到 gRPC // 注意第二个参数 UserInfoServiceServer 是接口类型的变量 // 需要取地址传参 pb.RegisterUserInfoServiceServer(s, &u) s.Serve(l) }
运行监听:
实现流程
代码参考
package main import (...) func main() { conn, err := grpc.Dial(":2333", grpc.WithInsecure()) if err != nil { log.Fatalf("dial error: %v/n", err) } defer conn.Close() // 实例化 UserInfoService 微服务的客户端 client := pb.NewUserInfoServiceClient(conn) // 调用服务 req := new(pb.UserRequest) req.Name = "wuYin" resp, err := client.GetUserInfo(context.Background(), req) if err != nil { log.Fatalf("resp error: %v/n", err) } fmt.Printf("Recevied: %v/n", resp) }
运行调用成功:
在上边 UserInfoService 微服务的实现过程中,会发现每个微服务都需要自己管理服务端监听端口,客户端连接后调用,当有很多个微服务时端口的管理会比较麻烦,相比 gRPC,go-micro 实现了服务发现(Service Discovery)来方便的管理微服务,下节将随服务的 Docker 化一起学习。 如果你想和更多微服务技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态 。
原文链接: https://wuyin.io/2018/05/02/pr ... lang/