内容简介:以前使用 Laravel 做 Web 项目时,是根据 MVC 去划分目录结构的,即 Controller 层处理业务逻辑,Model 层处理数据库的 CURD,View 层处理数据渲染与页面交互。以及 MVP、MVVM 都是将整个项目的代码是集中在一个代码库中,进行业务处理。这种单一聚合代码的方式在前期实现业务的速度很快,但在后期会暴露很多问题:微服务是一种软件架构,它将一个大且聚合的业务项目拆解为多个小且独立的业务模块,模块即服务,各服务间使用高效的协议(protobuf、JSON 等)相互调用即是 RP
微服务架构
单一的代码库
以前使用 Laravel 做 Web 项目时,是根据 MVC 去划分目录结构的,即 Controller 层处理业务逻辑,Model 层处理数据库的 CURD,View 层处理数据渲染与页面交互。以及 MVP、MVVM 都是将整个项目的代码是集中在一个代码库中,进行业务处理。这种单一聚合代码的方式在前期实现业务的速度很快,但在后期会暴露很多问题:
- 开发与维护困难:随着业务复杂度的增加,代码的耦合度往往会变高,多个模块相互耦合后不易横向扩展
- 效率和可靠性低:过大的代码量将降低响应速度,应用潜在的安全问题也会累积
拆分的代码库
微服务是一种软件架构,它将一个大且聚合的业务项目拆解为多个小且独立的业务模块,模块即服务,各服务间使用高效的协议(protobuf、JSON 等)相互调用即是 RPC。这种拆分代码库的方式有以下特点:
- 每个服务应作为小规模的、独立的业务模块在运行,类似 Unix 的 Do one thing and do it well
- 每个服务应在进行自动化测试和(分布式)部署时,不影响其他服务
每个服务内部进行细致的错误检查和处理,提高了健壮性
二者对比
本质上,二者只是聚合与拆分代码的方式不同。
参考: 微服务架构的优势与不足
构建微服务
UserInfoService 微服务
接下来创建一个处理用户信息的微服务: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 来定义。
Protobuf(protocal buffers)是谷歌推出的一种二进制数据编码格式,相比 XML 和 JSON 的文本数据编码格式更有优势:
- 读写更快、文件体积更小
- 它没有 XML 的标签名或 JSON 的字段名,更为轻量, 更多参考 。
语言中立
只需定义一份 .proto 文件,即可使用各语言对应的 protobuf 编译器对其编译,生成的文件中有对 message 编码、解码的函数。
对于 JSON
- 在 PHP 中需使用 json_encode() 和 json_decode() 去编解码,在 Golang 中需使用 json 标准库的 Marshal() 和 Unmarshal() … 每次解析和编码比较繁琐
- 优点:可读性好、开发成本低
- 缺点:相比 protobuf 的读写速度更慢、存储空间更多
对于 Protobuf
- .proto 可生成 .php 或 *.pb.go … 在项目中可直接引用该文件中编译器生成的编码、解码函数
- 优点:高效轻量、一处定义多处使用
- 缺点:可读性差、开发成本高
定义微服务的 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,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态 。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 在 Android Studio 里使用构建分析器提升构建性能
- 使用 Docker 构建
- 使用 webpack 构建应用
- 使用Dockerfile构建镜像
- 使用模式构建:总结
- 使用 AutoAI 自动构建模型
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Inside Larry's and Sergey's Brain
Richard Brandt / Portfolio / 17 Sep 2009 / USD 24.95
You’ve used their products. You’ve heard about their skyrocketing wealth and “don’t be evil” business motto. But how much do you really know about Google’s founders, Larry Page and Sergey Brin? Inside......一起来看看 《Inside Larry's and Sergey's Brain》 这本书的介绍吧!