一:微服务架构的优势
一旦应用程序成为一个大型、复杂的整体,我们开发组织可能就会陷入痛苦之中。任何敏捷开发和交付的尝试都将举步维艰。一个主要问题是应用程序极其复杂。它太大了,任何一个开发人员都无法完全理解。因此,修复bug和正确实现新特性变得困难和费时。
应用程序的庞大规模也会降低开发速度。应用程序越大,启动时间越长。
大型、复杂的单片应用程序的另一个问题是,它是持续部署的障碍。今天,SaaS应用程序的最新技术是每天多次将更改推入生产环境。对于复杂的整体来说,这是非常困难的,因为必须重新部署整个应用程序才能更新其中的任何一部分。
总之,随着时间的推移, 这个打车应用程序,将成长为一个只有极少数(如果有的话)开发人员能够理解的庞然大物。它是使用过时的、低效的技术编写的,这使得雇佣有才华的开发人员非常困难。应用程序难以扩展,而且不可靠。因此,敏捷开发和交付应用程序是不可能的。
许多公司现在都在使用微服务的架构,Amazon, Netflix, 国内的滴滴, 字节跳动,都是微服务的应用者
通过采用现在所知的微服务体系结构模式解决了应用程序难以扩展,而且不可靠。因此,敏捷开发和交付应用程序是不可能的。与其构建一个庞大的、单一的应用程序,不如将应用程序分割成一组更小的、相互连接的服
服务通常实现一组不同的特性或功能,例如订单管理、客户管理等。每个微服务都是一个小型应用程序,它有自己的六边形架构,由业务逻辑和各种适配器组成。一些微服务将公开由其他微服务或应用程序客户端使用的API。其他微服务可能实现web UI。在运行时,每个实例通常是一个云VM或一个Docker容器。
例如,前面描述的系统可能的分解如下图所示:
应用程序的每个功能区域现在都由自己的微服务实现。此外,web应用程序被划分为一组更简单的web应用程序(例如,在我们的打车示例中,一个用于乘客,一个用于司机)。这使得为特定用户、设备或特定用例部署不同的体验变得更加容易。
每个后端服务公开一个REST API,大多数服务使用其他服务提供的API。例如,驱动程序管理使用通知服务器告诉可用的驱动程序潜在的行程。UI服务调用其他服务来呈现web页面。服务也可能使用异步的、基于消息的通信。本系列后面将更详细地讨论服务间通信。
一些REST api也暴露在司机和乘客使用的移动应用程序中。然而,这些应用程序不能直接访问后台服务。相反,通信是由称为API网关的中介进行中介的。API网关负责负载平衡、缓存、访问控制、API计量和监视等任务,可以使用NGINX有效地实现。
在运行时,旅行管理服务由多个服务实例组成。每个服务实例都是一个Docker容器。为了获得高可用性,容器运行在多个云vm上。在服务实例前面是一个负载平衡器,比如NGINX,它将请求分布在实例之间。
讨论——微服务架构的技术难题?微服务的负载均衡策略?
go-micro是go语言下的一个很好的rpc微服务框架,功能很完善,而且我关心的几个问题也解决的很好:
一:服务间传输格式为protobuf,效率上没的说,非常的快,也很安全。
二:go-micro的服务注册和发现是多种多样的。我个人比较喜欢etcdv3的服务服务发现和注册。
三:主要的功能都有相应的接口,只要实现相应的接口,就可以根据自己的需要订制插件。
二:Micro架构
micro由以下几个部分组成:
-
API网关(API Gateway): - API Gateway 网关。API网关是请求的入口,把请求动态路由到具体服务。网关允许我们建立可伸缩的后台微服务架构,并且让工作在前端的公共API更健壮。Micro API基于服务发现拥有强大的路由能力,通过我们预置的handlers插件,它可以处理http、gRPC、websocket、消息推送事件等等。
-
命令行接口(Interactive CLI): 交互式的命令行接口。CLI通过终端可以描述、查询、直接与平台和服务进行交互。CLI提供所有的命令让开发者明白微服务正在处理的事情。CLI也包含了交互模式。
-
服务代理(Service Proxy): 服务代理,基于Go Micro和MUCP协议构建的透明的代理服务。它将服务发现、负载均衡、消息编码、中间件、传输及代理插件转移到某一(具体服务所在)位置,同api不同,它不暴露任何接口,只工作在内部环境,相当于桥接内部服务。
-
模板生成(Template Generation): 基于模板快速创建新的服务代码。Micor提供预置的模板,通过模板编写统一风格的代码。
-
SlackOps小机器人(SlackOps Bot): Slack小机器人插件,当它运行中服务中时,这个插件允许开发者通过Slack消息来操作平台。MicroBot插件提供聊天配置选项,这样就可以让团队通过向小机器人发送聊天消息来做一些我们希望它做的事,这里面当然也包含像动态发现服务一样创建slack命令。
-
管理控制台(Web Dashboard): 通过Web管理控制台,可以直接在Web页面上查看服务的运行情况,展示端点信息,请求与响应状态,甚至直接向服务进行查询。管理控制台也有CLI交互页面提供给开发者在线上处理,就像直接操作终端一样。
-
Go-micro框架(Go Framework): Go Micro框架是Micro的底层、核心。GO-Micro把分布式服务抽象,并提供简便的方式让大家构建具有高弹性的微服务。
-
Server监听客户端的调用,和Brocker推送过来的信息进行处理。并且Server端需要向Register注册自己的存在或消亡,这样Client才能知道自己的状态。
Register服务的注册的发现。
Client端从Register中得到Server的信息,然后每次调用都根据算法选择一个的Server进行通信,当然通信是要经过编码/解码,选择传输协议等一系列过程的。
如果有需要通知所有的Server端可以使用Brocker进行信息的推送。
Brocker 信息队列进行信息的接收和发布。
-
go-micro之所以可以高度订制和他的框架结构是分不开的,go-micro由8个关键的interface组成,每一个interface都可以根据自己的需求重新实现,这8个主要的inteface也构成了go-micro的框架结构。
这些接口go-micir都有他自己默认的实现方式,还有一个go-plugins是对这些接口实现的可替换项。你也可以根据需求实现自己的插件。
三:go-micro服务发现的简单例子
使用默认的consual服务注册和发现机制。
1:启动consual服务
# ./consul agent -server -bootstrap-expect 1 -data-dir /tmp/consul -node=chenxun-server -bind=192.168.145.130 -ui
2:编写接口文件
准备proto文件: 文件保存为chenxun.proto,名称随便写,在实际项目中根据项目写就好了。
syntax = "proto3";
service Greeter {
rpc Hello(HelloRequest) returns (HelloResponse) {}
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string greeting = 2;
}
protoc --proto_path=$GOPATH/src:. --micro_out=. --go_out=. chenxun.proto
执行命令后能看到下面文件:
rw-r--r--. 1 root root 2441 Jul 7 10:38 chenxun.micro.go
-rw-r--r--. 1 root root 2914 Jul 7 10:38 chenxun.pb.go
-rw-r--r--. 1 root root 185 Jul 6 11:36 chenxun.proto
3:服务端编写
package main
import (
"context"
"fmt"
micro "github.com/micro/go-micro"
proto "mygoproject/gomirco" //这里写你的proto文件放置路劲
)
type Greeter struct{}
func (g *Greeter) Hello(ctx context.Context, req *proto.HelloRequest, rsp *proto.HelloResponse) error {
rsp.Greeting = "Hello " + req.Name
return nil
}
func main() {
// Create a new service. Optionally include some options here.
service := micro.NewService(
micro.Name("greeter"),
)
// Init will parse the command line flags.
service.Init()
// Register handler
proto.RegisterGreeterHandler(service.Server(), new(Greeter))
// Run the server
if err := service.Run(); err != nil {
fmt.Println(err)
}
}
4:编写客户端
package main
import (
"context"
"fmt"
micro "github.com/micro/go-micro"
proto "mygoproject/gomirco" //这里写你的proto文件放置路劲
)
func main() {
// Create a new service. Optionally include some options here.
service := micro.NewService(micro.Name("greeter.client"))
service.Init()
// Create new greeter client
greeter := proto.NewGreeterService("greeter", service.Client())
// Call the greeter
rsp, err := greeter.Hello(context.TODO(), &proto.HelloRequest{Name: "John"})
if err != nil {
fmt.Println(err)
}
// Print response
fmt.Println(rsp.Greeting)
}
运行service:
go run examples/service/main.go
运行client:
go run examples/client/main.go
func main() {
// 我这里用的etcd 做为服务发现,如果使用consul可以去掉
reg := etcdv3.NewRegistry(func(op *registry.Options){
op.Addrs = []string{
"http://192.168.3.34:2379", "http://192.168.3.18:2379", "http://192.168.3.110:2379",
}
})
// 初始化服务
service := micro.NewService(
micro.Name("lp.srv.eg1"),
micro.Registry(reg),
)
// 注册 Handler
model.RegisterSayHandler(service.Server(), new(Say))
// run server
if err := service.Run(); err != nil {
panic(err)
}
}
四:使用k8s发布服务
Micro在Kubernetes中是原生kubernetes的微服务。
Micro是一个微服务工具包,而Kubernetes是一个容器调度平台,它们组合在一起便构成微服务基础设施。
特性
- 无外部依赖
- 客户端缓存服务发现
- 可选k8s服务负载均衡
- 使用gRPC传输协议
- 预置工具包
首先服务Docker话
这里简单运行官方给出的一个例子。github.com/micro/examples/greeter
server端
package main
import (
"log"
"time"
hello "github.com/micro/examples/greeter/srv/proto/hello"
"github.com/micro/go-micro"
"context"
)
type Say struct{}
func (s *Say) Hello(ctx context.Context, req *hello.Request, rsp *hello.Response) error {
log.Print("Received Say.Hello request")
rsp.Msg = "Hello " + req.Name
return nil
}
func main() {
service := micro.NewService(
micro.Name("go.micro.srv.greeter"),
micro.RegisterTTL(time.Second*30),
micro.RegisterInterval(time.Second*10),
)
// optionally setup command line usage
service.Init()
// Register Handlers
hello.RegisterSayHandler(service.Server(), new(Say))
// Run server
if err := service.Run(); err != nil {
log.Fatal(err)
}
go build生成服务,这里将服务的名字定义成src
Dockerfile编写
FROM centos
ADD srv ./
CMD ["./srv"]
客户端的代码如下:
package main
import (
"context"
"fmt"
hello "github.com/micro/examples/greeter/srv/proto/hello"
"github.com/micro/go-micro"
)
func main() {
// create a new service
service := micro.NewService()
// parse command line flags
service.Init()
// Use the generated client stub
cl := hello.NewSayService("go.micro.srv.greeter", service.Client())
fmt.Println(1)
// Make request
rsp, err := cl.Hello(context.Background(), &hello.Request{
Name: "John",
})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(rsp.Msg)
}
编写在kubernetes运行的微服务
server端的代码如下,可以看到完全相同。
package main
import (
"context"
"log"
hello "github.com/micro/examples/greeter/srv/proto/hello"
k8s "github.com/micro/examples/kubernetes/go/micro"
"github.com/micro/go-micro"
)
type Say struct{}
func (s *Say) Hello(ctx context.Context, req *hello.Request, rsp *hello.Response) error {
log.Print("Received Say.Hello request")
rsp.Msg = "Hello " + req.Name
return nil
}
func main() {
service := k8s.NewService(
micro.Name("greeter"),
)
service.Init()
hello.RegisterSayHandler(service.Server(), new(Say))
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
注意:这里用的grpc应该是go-micro下的 "github.com/micro/go-micro/service/grpc",而不应该是"github.com/micro/go-grpc"否则会报错。
只是运行起来可能会出一点小的错误。
[root@k8s-master greeter]# ./main
2019/08/29 20:51:53 stat /var/run/secrets/kubernetes.io/serviceaccount: no such file or directory
参考解决:
ubectl get serviceaccount
NAME SECRETS
default 0
如果没有则需要添加
在apiserver的启动参数中添加:
--admission_control=ServiceAccount
apiserver在启动的时候会自己创建一个key和crt(见/var/run/kubernetes/apiserver.crt和apiserver.key)
然后在启动./kube-controller-manager 时添加flag:
--service_account_private_key_file=/var/run/kubernetes/apiserver.key
kubectl get serviceaccount
NAME SECRETS
default 1
这里将micro基础后端打包成镜像!
最后发现这个路径需要进入容器才行。