近一段时间在学习和实践用go来实现微服务架构的开发,本文来记录下什么情况下要使用微服务架构,分析下利弊。并且用grpc初步实现微服务的模型。
在 Web 应用程序发展的早期,大部分工程是将所有的服务端功能模块打包成单个巨石型应用,最终会形成如下图所示的架构。
随着单体应用越来越庞大,单体架构中不同业务模块的差异就会显现,将大应用拆分成一个个单体结构的应用。垂直分层是一个典型的对复杂系统进行结构化思考和抽象聚合的通用性方法。
微服务是一种小型的SOA架构(面向服务的架构),其理念是将业务系统彻底地组件化和服务化,形成多个可以独立开发、部署和维护的服务或者应用的集合,以应对更快的需求变更和更短的开发迭代周期。
近几年,随着微服务架构的火热,也诞生了很多微服务框架,如 Java 语言的 Spring Cloud、Go 语言的 Go Kit 和 Go Micro 以及 Node.js 的 Seneca。充分说明了微服务架构的火热态势。虽然微服务架构的实践落地独立于编程语言,但是 Go 语言在微服务架构的落地中仍有其独特的优势。因此,Go 语言的微服务框架也相继涌现,各方面都较为优秀的有 Go Kit 和 Go Micro 等。
远程过程调用(Remote Procedure Call)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序。简单地说就是能使应用像调用本地方法一样的调用远程的过程或服务。
Protobuf 是由 Google 开源的消息传输协议,用于将结构化的数据序列化、反序列化通过网络进行传输。Protobuf 首先解决的是如何在不同语言之间传递数据的问题,目前支持的编程语言有C++、Java、Python、Objective-C、C#、JavaScript、Ruby、Go、PHP 等。
优点:XML、JSON 也可以用来存储此类结构化数据,但是使用ProtoBuf表示的数据能更加高效,并且将数据压缩得更小。(比XML小3-10倍,快20-100倍)
缺点:由于是二进制,无法直接阅读
gRPC是由Google公司开源的一款高性能的远程过程调用(RPC)框架。
syntax = "proto3"; package services; //订单请求参数 message OrderRequest { string orderId = 1; int64 timeStamp = 2; } //订单信息 message OrderInfo { string OrderId = 1; string OrderName = 2; string OrderStatus = 3; } //订单服务service定义 service OrderService{ rpc GetOrderInfo(OrderRequest) returns (OrderInfo); }
protoc --go_out=plugins=grpc:. *.proto
func (os /*OrderServiceImpl) GetOrderInfo(ctx context.Context, request /*OrderRequest) (/*OrderInfo, error) { fmt.Println("收到请求订单号:", request.OrderId) return &OrderInfo{OrderId: request.OrderId, OrderName: "订单名称", OrderStatus: "已经支付"}, nil }
addr :/= "127.0.0.1:8972" rpcServer :/= grpc.NewServer() services.RegisterOrderServiceServer(rpcServer, new(services.OrderServiceImpl)) listen, err :/= net.Listen("tcp", addr) if err != nil { log.Fatalf("启动网络监听失败 %v//n", err) } //启动服务 if err :/= rpcServer.Serve(listen); err != nil { fmt.Println("启动错误", err) } else { fmt.Println("服务开启") }
addr :/= "127.0.0.1:8972" conn, err :/= grpc.Dial(addr, grpc.WithInsecure()) if err != nil { log.Fatalf("连接GRPC服务端失败 %v//n", err) } defer conn.Close() //实例客户端 orderServiceClient :/= sOrder.NewOrderServiceClient(conn) //模拟订单号 orderId :/= "order/_" + strconv.Itoa(time.Now().Second()) //组织请求体 orderRequest :/= &sOrder.OrderRequest{OrderId: orderId, TimeStamp: time.Now().Unix()} //rpc调用GetOrderInfo orderInfo, err :/= orderServiceClient.GetOrderInfo(context.Background(), orderRequest) if orderInfo != nil { fmt.Println(orderInfo.GetOrderId(), orderInfo.GetOrderName(), orderInfo.GetOrderStatus()) return orderInfo.GetOrderStatus() } else { return "订单服务读取失败" }
在微服务架构中,一般每一个服务都是有多个拷贝,来保证高可用。一个服务随时可能下线,也可能应对临时访问压力增加新的服务节点。这就出现了新的问题:
service启动时向注册中心注册
注册中心和service之间会保持心跳检查,来维护注册表里的service是否存活
client向注册中心获取可用的service
Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。Consul 使用 Go 语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X)。Consul 内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案,不再需要依赖其他工具,使用起来也较为简单。
etcd 是用 go 开发的,出现的时间并不长,不像 zookeeper 那么悠久和有名,但是前景非常好。etcd 是因为 kubernetes 而被人熟知的,kubernetes 的 kube master 使用 etcd 作为分布式存储获取分布式锁,这为 etcd 的强大做了背书。etcd 使用 RAFT 算法实现的一致性,比 zookeeper 的 ZAB 算法更简单
zookeeper 起源于 Hadoop,后来进化为 Apache 的顶级项目。现在已经被广泛使用在 Apache 的项目中,例如 Hadoop,kafka,solr 等等。是用java 开发的,部署的时候要装JAVA环境。历史悠久,功能丰富,所以比较重,易用性不如以上2个。
我暂时选用了etcd来做注册中心,以下展示加入注册中心后的grpc调用代码。
package main import ( "fmt" "google.golang.org/grpc" "goshare/etcd" "grpc-service/services" "log" "net" ) func main() { addr := "127.0.0.1:8972" rpcServer := grpc.NewServer() services.RegisterOrderServiceServer(rpcServer, new(services.OrderServiceImpl)) listen, err := net.Listen("tcp", addr) if err != nil { log.Fatalf("启动网络监听失败 %v/n", err) } //etcd服务注册 reg, err := etcd.NewService(etcd.ServiceInfo{ Name: "mirco.service.order", IP: addr, //grpc服务节点ip }, []string{"172.24.132.232:2379"}) // etcd的节点ip if err != nil { log.Fatal(err) } go reg.Start() //启动服务 if err := rpcServer.Serve(listen); err != nil { fmt.Println("启动错误", err) } else { fmt.Println("服务开启") } }
r := etcd.NewResolver([]string{"172.24.132.232:2379"}, "mirco.service.order") resolver.Register(r) addr := fmt.Sprintf("%s:///%s", r.Scheme(), "") //addr := "127.0.0.1:8972" fmt.Println("addr", addr) conn, err := grpc.Dial(addr, grpc.WithInsecure()) if err != nil { log.Fatalf("连接GRPC服务端失败 %v/n", err) } defer conn.Close() orderServiceClient := sOrder.NewOrderServiceClient(conn) orderId := "order_" + strconv.Itoa(time.Now().Second()) orderRequest := &sOrder.OrderRequest{OrderId: orderId, TimeStamp: time.Now().Unix()} orderInfo, err := orderServiceClient.GetOrderInfo(context.Background(), orderRequest) if orderInfo != nil { fmt.Println(orderInfo.GetOrderId(), orderInfo.GetOrderName(), orderInfo.GetOrderStatus()) return orderInfo.GetOrderStatus() } else { return "订单服务读取失败" }
以上就实现了注册中心来做服务注册和发现,我们测试效果的时候可以启用2个service,一个监听8972,一个监听8973。client通过注册中心(172.24.132.232:2379)来做服务发现,当2个service其中任何一个关闭,我们的服务依然能正常提供服务,实现了高可用。
tips:另外推荐大家本人参与的并且觉得不错的学习渠道
一个是拉钩教育上的《Go微服务实战38讲》98元。
另一个是58沈剑的《架构师训练营》399元。微服务对架构知识体系由一定要求,这个课程是沈老师10多年的架构经验成体系的整理,可以帮助我们从简到深的理解架构层面的知识。另外如果不花钱报课,每周基本都有免费的互联网架构直播课,一般1个多小时,都是干货知识。