内容目录如下:
什么是微服务?
RPC 协议
-
ProtoBuf 使用
protobuf 简单语法
Protobuf 高级用法
GRPC 框架
-
go-micro 框架
发现服务
consul 使用
consul 和 grpc 结合使用
go-micro 使用
web 与服务端通信
详细讲解地址评论获取,文档资料免费分享
主讲师:HZ
具有多年的软件开发实战经验,精通 Golang、C/C++、Linux 系统编程。参与大型网络游戏研发,区块链研发,具有丰富的项目经验,授课认真负责,幽默风趣,深受学员好评。
什么是微服务?
服务拆分原则:高内聚低耦合
在介绍微服务时,首先得先理解什么是微服务,顾名思义,微服务得从两个方面去理解,什么是"微"、什么是"服务"?
微(micro)狭义来讲就是体积小,著名的"2 pizza 团队"很好的诠释了这一解释(2 pizza 团队最早是亚马逊CEO Bezos提出来的,意思是说单个服务的设计,所有参与人从设计、开发、测试、运维所有人加起来 只需要2个披萨就够了 )。
服务(service)一定要区别于系统,服务一个或者一组相对较小且独立的功能单元,是用户可以感知最小功能集。
那么广义上来讲,微服务是一种分布式系统解决方案,推动细粒度服务的使用,这些服务协同工作。
在这里我们可能会混淆一个点,那就是微服务和微服务架构,这是两个不同的概念,而我们平时说到的微服务已经包含了这两个概念了,我们需要把它们说清楚以免学习中纠结。微服务架构是一种设计方法,而微服务这是指使用这种方法而设计的一个应用。所以我们必要对微服务的概念做出一个比较明确的定义。微服务架构是将复杂的系统使用组件化的方式进行拆分,并使用轻量级通讯方式进行整合的一种设计方法。微服务是通过这种架构设计方法拆分出来的一个独立的组件化的小应用。微服务架构定义的精髓,可以用一句话来描述,那就是“分而治之,合而用之”。将复杂的系统进行拆分 的方法,就是“分而治之”。分而治之,可以让复杂的事情变的简单,这很符合我们平时处理问题的方法。 使用轻量级通讯等方式进行整合的设计,就是“合而用之”的方法,合而用之可以让微小的力量变动强大。
RPC 协议封装
上面的代码服务名都是写死的,不够灵活(容易写错),这里我们对RPC的服务端和客户端再次进行一 次封装,来屏蔽掉服务名,具体代码如下
服务端封装
//抽离服务名称
var serverName = "LoginService"
//定义一个父类
type RPCDesign interface {
Hello(string,*string)error
}
//实现工厂函数
func RegisterRPCServer(srv RPCDesign)error{
return rpc.RegisterName(serverName,srv)
}
封装之后的服务端实现如下:
type RpcServer struct{}
//5 + 3i chan func complex
func (this *RpcServer) Hello(req string, resp *string) error {
*resp += req + "你好"
return nil
}
func main() {
//设置监听
listener, err := net.Listen("tcp", ":8899")
if err != nil {
fmt.Println("设置监听错误")
return
}
defer listener.Close()
fmt.Println("开始监听....")
for {
//接收链接
conn, err := listener.Accept()
if err != nil {
fmt.Println("获取连接失败")
return
}
defer conn.Close()
fmt.Println(conn.RemoteAddr().String() + "连接成功")
//rpc表 注册rpc服务
if err = RegisterRPCServer(new(RpcServer)); err != nil {
fmt.Println("注册rpc服务失败")
return
}
//把rpc服务和套接字绑定
//rpc.ServeConn(conn)
rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
}
}
客户端封装
type RPCClient struct {
rpcClient *rpc.Client
}
func NewRpcClient(addr string)(RPCClient){
conn,err := net.Dial("tcp",addr)
if err != nil {
fmt.Println("链接服务器失败")
return RPCClient{}
}
defer conn.Close()
//套接字和rpc服务绑定
client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
return RPCClient{rpcClient:client}
}
func (this*RPCClient)CallFunc(req string,resp*string)error{
return this.rpcClient.Call(serverName+".Hello",req,resp)
}
封装之后客户端实现
func main() {
//初始化对象 与服务名有关的内容完全封装起来了
client := NewRpcClient("127.0.0.1:8899")
//调用成员函数
var temp string
client.CallFunc("xiaoming",&temp)
fmt.Println(temp) }
protobuf 基本编译
protobuf编译是通过编译器protoc进行的,通过这个编译器,我们可以把.proto文件生成go,Java,Python,C++, Ruby, JavaNano, Objective-C,或者C# 代码,生成命令如下:
protoc --proto_path=IMPORT_PATH --go_out=DST_DIR path/to/file.proto
proto_path=IMPORT_PATH,IMPORT_PATH是 .proto 文件所在的路径,如果忽略则默认当前目录。如果有多个目录则可以多次调用--proto_path,它们将会顺序的被访问并执行导 入。
--go_out=DST_DIR, 指定了生成的go语言代码文件放入的文件夹
允许使用 protoc --go_out=./ *.proto 的方式一次性编译多个 .proto 文件
go语言编译时,protobuf 编译器会把 .proto 文件编译成 .pd.go 文件
一般在使用的时候我们都是使用下面这种简单的命令:
protoc --go_out=./ *.proto
编译当前文件夹下的所有.proto文件,并把生成的 go 文件放置在当前文件夹下。
我们先来编译一个最简单的的proto文件,编译之后会得到一个如下一个go文件,如下:
message 被对应的编译成 go 语言中的结构体,同时还为 String 类型自动生成了一组方法,其中 ProtoMessage 方法表示这是一个实现了 proto.Message 接口的方法。此外 Protobuf 还为每个成员生成了一个 Get 方法,Get 方法不仅可以处理空指针类型,而且可以和 Proto2 的方法保持一致.
然后我们给这个.proto 文件中添加一个 RPC 服务,再次进行编译,发现生成的 go 文件没有发生变化。这是因为世界上的 RPC 实现有很多种,protoc 编译器并不知道该如何为 HelloService 服务生成代码。不 过在 protoc-gen-go 内部已经集成了一个叫
grpc 的插件,可以针对 grpc 生成代码:
protoc --go_out=plugins=grpc:. *.proto
在生成的代码中多了一些类似HelloServiceServer、HelloServiceClient的新类型。如下:
和我们优化之后的RPC服务有点相似,但是又不太一样,接下来就让我们来学习一下grpc框架吧。
GRPC 框架
GRPC 是 Google 公司基于 Protobuf 开发的跨语言的开源 RPC 框架。GRPC 基于HTTP/2 协议设计,可以 基于一个 HTTP/2 链接提供多个服务,对于移动设备更加友好。目前提供 C、Java 和 Go 语言版本,分别 是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持。
在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得 您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似, gRPC 也是基于以下理念:
定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。
在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。
在客户端拥有一个存根能够像服务端一样的方法。gRPC 客户端和服务端可以在多种环境中运行和交互 - 从 google 内部的服务器到你自己的笔记本,并且可以用任何 gRPC支持的语言来编写。
所以,你可以很容易地用 Java 创建一个 gRPC 服务端,用 Go、 Python、Ruby 来创建客户端。此外, Google最新 API 将有 gRPC 版本的接口,使你很容易地将 Google 的功能集成到你的应用里。
**参考资料 **
gRPC 官方文档中文版:http://doc.oschina.net/grpc?t=60133
gRPC 官网:https://grpc.io
在详细了解使用 GRPC 之前先来了解一下上面定义中的一些关键词。首先我们来看一下 HTTP/2 是什么内容?
其实本质上就是 http2.0 版本,http 目前为止主要有四个版本,分别为 http1.0、http1.1、http2.0、 https。
http1.0是最原始的版本,不支持持久连接,效率也比较低
http1.1针对 http1.0 版本做了优化,可以连接一次,多次使用,效率比 http1.0高
http2.0实现了多路复用,对 http1.1 再次进行了优化。http2.0 也被称为下一代 http 协议,是在 2013 年8 月进行首次测试,所以现在用的不是很广。https 其实是在 http 协议上多加了一层SSL 协议,具体如下图:
所以本质上,http1.0、http1.1、http2.0 都可以添加 SSL 协议。
GRPC 使用
如果从 Protobuf 的角度看,GRPC 只不过是一个针对 service 接口生成代码的生成器。接着我们来学习一下 GRPC 的用法。这里我们创建一个简单的 proto 文件,定义一个 HelloService 接口:
syntax = "proto3"; //指定版本信息,不指定会报错
package pb; //后期生成go文件的包名
message Person{
// 名字
string name = 1;
// 年龄
int32 age = 2 ;
}
//定义RPC服务
service HelloService {
rpc Hello (Person)returns (Person);
}
对 proto 文件进行编译:
$ protoc --go_out=plugins=grpc:. *.proto
GRPC 插件会为服务端和客户端生成不同的接口:
//客户端接口
type HelloServiceClient interface {
Hello(ctx context.Context, in *Person, opts ...grpc.CallOption)
(*Person, error)
}
//服务器接口
type HelloServiceServer interface {
Hello(context.Context, *Person) (*Person, error)
}
我们接着可以基于他们给的服务端接口重新实现 HelloService 服务:
type HelloService struct{}
func (this*HelloService)Hello(ctx context.Context, person *pb.Person)
(*pb.Person, error){
reply := &pb.Person{
Name:"zhangsan" + person.Name,
Age:18,
}
return reply,nil
}
GRPC 的启动流程和 RPC 的启动流程类似,代码如下:
func main(){
//获取grpc服务端对象
grpcServer := grpc.NewServer()
//注册grpc服务
pb.RegisterHelloServiceServer(grpcServer,new(HelloService))
//设置服务端监听
lis,err := net.Listen("tcp",":1234")
if err != nil {
panic(err)
}
//在指定端口上提供grpc服务
grpcServer.Serve(lis)
}
然后我们就可以通过客户端来连接 GRPC 服务了:
func main(){
//和grpc服务建立连接
conn,err := grpc.Dial("localhost:1234",grpc.WithInsecure())
if err != nil {
panic(err)
}
defer conn.Close()
client := pb.NewHelloServiceClient(conn)
reply,err := client.Hello(context.Background(),&pb.Person{Name:"lisi",Age:100})
if err != nil {
panic(err)
}
fmt.Println("reply,",reply)
}
** go-micro 框架**
在了解 go-micro 之前,我们先来了解一下什么是 micro。Micro 是一个专注于简化分布式系统开发的微服务生态系统。由开源库和工具组成。主要包含以下几种库:
-
go-micro:用于编写微服务的可插入 Go-RPC 框架; 服务发现,客户端/服务器 rpc,pub/sub 等, 是整个 Micro 的核心。
**默认使用 mdns 做服务发现,可以在插件中替换成 consul,etcd,k8s 等 组播 广播 **
go-plugins:go-micro 的插件,包括 etcd,kubernetes(k8s),nats,rabbitmq,grpc 等
micro:一个包含传统入口点的微服务工具包; API 网关,CLI,Slack Bot,Sidecar 和 Web UI。
其他各种库和服务可以在github.com/micro找到。我们主要使用的框架也是 go-micro,在使用之前我们先来了解一下服务发现是个什么东西?有什么作用?
** 服务发现**
我们在做微服务开发的时候,客户端的一个接口可能需要调用 N 个服务,客户端必须知道所有服务的网络位置(ip+port),如下图所示
以往的做法是把服务的地址放在配置文件活数据库中,这样就有以下几个问题:(健壮性)
需要配置N个服务的网络位置,加大配置的复杂性
服务的网络位置变化,需要改变每个调用者的配置
集群的情况下,难以做负载(反向代理的方式除外)
总结起来一句话:服务多了,配置很麻烦,问题一大堆 所以现在就选择服务发现来解决这些问题。我们来看一下,服务发现如何解决这个问题,具体设计如
与之前解决方法不同的是,加了个服务发现模块。服务端把当前自己的网络位置注册到服务发现模块 (这里注册的意思就是告诉),服务发现就以 K-V 的方式记录下,K一般是服务名,V 就是 IP:PORT。服 务发现模块定时的轮询查看这些服务能不能访问的了(这就是健康检查)。客户端在调用服务 A-N 的时 候,就跑去服务发现模块问下它们的网络位置,然后再调用它们的服务。这样的方式是不是就可以解决 上面的问题了呢?客户端完全不需要记录这些服务的网络位置,客户端和服务端完全解耦!
常见的服务发现框架有:Etcd、Eureka、Consul、Zookeeper 这里分享的时候我们选择 go-micro 默认的服务发现框架 consul 来做一个详细介绍。
对于服务发现框架 consul、 go-micro 框架的使用、代码介绍直播的时候会进行详细解说
......