README ¶
微服务
特点
- 在分布式环境中,将单体应用拆分为一系列服务,共同组成整个系统。
- 每个服务都轻量级,单独部署,运行在自己的进程中。
- 每个微服务注重自己的核心能力的开发,微服务组件之间采用轻量级通信方式进行通信,包括但不限于RESTful API。
- 按照业务边界进行划分。
- 微服务是一种编程架构思想,有不同的语言实现。
微服务面临的问题
1、客户端如何访问这些服务?
采用一种叫做网关(英文为API Gateway)的技术方案来解决这些问题,网关的作用主要包括:
提供统一服务入口,让微服务对前台透明
聚合后台的服务,节省流量,提升性能
提供安全,过滤,流控等API管理功能
2、每个服务之间如何进行通信?
所有的微服务都是独立部署,运行在自己的进程容器中,所以微服务与微服务之间的通信就是IPC(Inter Process Communication),翻译为进程间通信。进程间通信的方案已经比较成熟了,现在最常见的有两大类:同步调用、异步消息调用。
- 同步调用
同步调用比较简单,一致性强,但是容易出调用问题,性能体验上也会差些,特别是调用层次多的时候。同步调用的有两种实现方式:分别是REST和RPC
REST:REST基于HTTP,实现更容易,各种语言都支持,同时能够跨客户端,对客户端没有特殊的要求,只要具备HTTP的网络请求库功能就能使用。
RPC:rpc的特点是传输效率高,安全性可控,在系统内部调用实现时使用的较多。 基于REST和RPC的特点,我们通常采用的原则为:向系统外部暴露采用REST,向系统内部暴露调用采用RPC方式。
- 异步消息调用
异步消息的方式在分布式系统中有特别广泛的应用,他既能减低调用服务之间的耦合,又能成为调用之间的缓冲,确保消息积压不会冲垮被调用方,同时能保证调用方的服务体验,继续干自己该干的活,不至于被后台性能拖慢。需要付出的代价是一致性的减弱,需要接受数据最终一致性,所谓的最终一致性就是只可能不会立刻同步完成,会有延时,但是最终会完成数据同步;还有就是后台服务一般要实现幂等性,因为消息送出于性能的考虑一般会有重复(保证消息的被收到且仅收到一次对性能是很大的考验)。最后就是必须引入一个独立的 Broker,作为中间代理池。
3、多个微服务,应如何实现?
- 服务注册与发现
当服务上线时,服务提供者将自己的服务注册信息注册到某个专门的框架中,并通过心跳维持长链接,实时更新链接信息。服务调用者通过服务管理框架进行寻址,根据特定的算法,找到对应的服务,或者将服务的注册信息缓存到本地,这样提高性能。当服务下线时,服务管理框架会发送服务下线的通知给其他服务。
4、如果服务出现异常宕机,该如何解决?
- 重试机制
- 限流机制
- 熔断机制
- 负载均衡
- 降级机制(本地缓存)
ProtoBuf
Protobuf是一种结构化数据的存储格式,平台无关,语言无关,可扩展。
安装
- 安装protobuf
windows平台安装
去网站下载一个protoc.exe,网址:https://github.com/protocolbuffers/protobuf/releases, 同样放在GOPATH/bin下
linux平台安装
下载 protobuf安装包
git clone https://github.com/protocolbuffers/protobuf.git
安装依赖库
sudo yum install autoconf automake libtool curl make g++ unzip libffi-dev -y
安装
$ cd protobuf/
$ ./autogen.sh
$ ./configure
$ make
$ sudo make install
$ sudo ldconfig // 刷新共享库,很重要的一步
- 获取proto包(Go语言的proto API接口)
go get github.com/golang/protobuf/proto
- 获取protoc-gen-go
go get github.com/golang/protobuf/protoc-gen-go
在GOPATH/bin下生成protoc-gen-go.exe
protobuf格式
syntax = "proto3";
option go_package = "./;prototest";
message Person {
string name = 1;
int32 age = 2;
repeated int32 height = 3;
string motto = 4; //格言
}
生成对应客户端代码
protoc --go_out ./ person.proto
Code generated by protoc-gen-go,文件后缀为.pb.go
编译protobuf常见bug
- package xxx is not in GOROOT or GOPATH
原因:1.开启了Go mod,没有对应模块 2.没开启Go mod,但是GOPATH中没有对应模块
- Missing ‘go_package‘ option in “person.proto“
原因:是因为在 proto3 的语法中缺少了 option go_package
option go_package = "aaa;bbb";
aaa 表示生成的go文件的存放地址,会自动生成目录的,建议使用当前目录:./。
bbb 表示生成的go文件所属的包名
Go Module
开启Go mod,并设置代理
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
常用命令
go mod init [模块名] //在当前目录下初始化新的模块,此命令会在当前目录中初始化和创建一个新的go.mod文件
go mod tidy //添加缺失的模块以及移除无用的模块,此动作会执行下载动作,类似于npm install.执行后会生成go.sum文件
go mod download //使用此命令来下载指定的模块,模块的格式可以根据主模块依赖的形式或者path@version形式指定。如果没有指定参数,此命令会将主模块下的所有依赖下载下来,类似于npm install.一般使用go get替代
go mod vendor //此命令会将build阶段需要的所有依赖包放到主模块所在的vendor目录中
RPC
golang实现rpc必备条件
golang写RPC程序,必须符合4个基本条件,不然RPC用不了
结构体字段首字母要大写,可以别人调用
函数名必须首字母大写
函数第一参数是接收参数,第二个参数是返回给客户端的参数,必须是指针类型
函数还必须有一个返回值error
go实现rpc
- golang官方的net/rpc库使用encoding/gob进行编解码,支持tcp和http数据传输方式,由于其他语言不支持gob编解码方式,所以golang的RPC只支持golang开发的服务器与客户端之间的交互.[gob流]
Server端
package main
import (
"io"
"net/http"
"net/rpc"
)
// 参数
type Params struct {
Width int
Height int
}
// 矩形
type React struct{}
func (this *React) ZhouChang(params Params, ret *int) error {
*ret = (params.Width + params.Height) * 2
return nil
}
func (this *React) MianJi(params Params, ret *int) error {
*ret = params.Width * params.Height
return nil
}
func main() {
http.HandleFunc("/panda", pandaFunc)
//注册服务
react := new(React)
rpc.Register(react)
//HTTP绑定
rpc.HandleHTTP()
http.ListenAndServe("127.0.0.1:8888", nil)
}
func pandaFunc(writer http.ResponseWriter, request *http.Request) {
io.WriteString(writer, "Hello,World!!!")
}
客户端
package main
import (
"fmt"
"net/rpc"
)
// 传的参数
type Params struct {
Width, Height int
}
// rpc客户端
func main() {
client, e := rpc.DialHTTP("tcp", "127.0.0.1:8888")
if e != nil {
fmt.Println("Dial HTTP Error:", e)
return
}
zhouchang := 0
e = client.Call("React.ZhouChang", Params{10, 20}, &zhouchang)
if e != nil {
fmt.Println("React.ZhouChang Error:", e)
return
}
fmt.Println("矩形的周长是:", zhouchang)
mianji := 0
e = client.Call("React.MianJi", Params{10, 20}, &mianji)
if e != nil {
fmt.Println("React.MianJi Error:", e)
return
}
fmt.Println("矩形的面积是:", mianji)
}
- 官方还提供了net/rpc/jsonrpc库实现RPC方法,jsonrpc采用encoding/json进行数据编解码,因而支持跨语言调用,目前jsonrpc库是基于tcp协议实现的,暂不支持http传输方式。[json字符串]
服务端
package main
import (
"fmt"
"net"
"net/rpc"
"net/rpc/jsonrpc"
)
// 参数
type Params struct {
Width int
Height int
}
// 矩形
type React struct{}
func (this *React) ZhouChang(params Params, ret *int) error {
*ret = (params.Width + params.Height) * 2
return nil
}
func (this *React) MianJi(params Params, ret *int) error {
*ret = params.Width * params.Height
return nil
}
func main() {
//注册服务
react := new(React)
rpc.Register(react)
listener, e := net.Listen("tcp", "127.0.0.1:9999")
if e != nil {
fmt.Println("Net Listen Error:", e)
return
}
for {
// 监听客户端连接
conn, e := listener.Accept()
if e != nil {
fmt.Println("Listener Accept Error:", e)
return
}
go jsonrpc.ServeConn(conn)
}
}
客户端
package main
import (
"fmt"
"net/rpc/jsonrpc"
)
// 传的参数
type Params struct {
Width, Height int
}
// rpc客户端
func main() {
client, e := jsonrpc.Dial("tcp", "127.0.0.1:9999")
if e != nil {
fmt.Println("Dial HTTP Error:", e)
return
}
zhouchang := 0
e = client.Call("React.ZhouChang", Params{50, 20}, &zhouchang)
if e != nil {
fmt.Println("React.ZhouChang Error:", e)
return
}
fmt.Println("矩形的周长是:", zhouchang)
mianji := 0
e = client.Call("React.MianJi", Params{50, 20}, &mianji)
if e != nil {
fmt.Println("React.MianJi Error:", e)
return
}
fmt.Println("矩形的面积是:", mianji)
}
###gRPC
gRPC是一个高性能、开源、通用的RPC框架,底层是通讯协议,采用Protobuf数据序列化协议[protobuf]。
安装gRPC
go get google.golang.org/grpc
gRPC调用流程
1.编写.proto描述文件
2.编译生成.pb.go文件
3.服务端实现约定的接口并提供服务
4.客户端按照约定调用.pb.go文件中的方法请求服务
项目结构
|—— hello/
|—— client/
|—— main.go // 客户端
|—— server/
|—— main.go // 服务端
|—— proto/
|—— hello/
|—— hello.proto // proto描述文件
|—— hello.pb.go // proto编译后文件
编写.proto描述文件
syntax = "proto3";
option go_package = "./;hello";
service Hello{
rpc SayHello(HelloRequest) returns (HelloResponse){}
}
message HelloRequest{
string name = 1;
}
message HelloResponse{
string message = 1;
}
编译生成.pb.go文件
生成.pb.go时,编译命令使用grpc的插件
protoc --go_out=plugins=grpc:. hello.proto
实现服务端接口 server/main.go
package main
import (
"fmt"
"golang.org/x/net/context"
"google.golang.org/grpc"
"grpc-demo/proto/hello"
"net"
)
type HelloService struct{}
func (h HelloService) SayHello(ctx context.Context, in *hello.HelloRequest) (*hello.HelloResponse, error) {
response := new(hello.HelloResponse)
response.Message = fmt.Sprintf("Hello %s.", in.Name)
return response, nil
}
func main() {
listener, e := net.Listen("tcp", "127.0.0.1:8989")
if e != nil {
fmt.Println("TCP Listen Error!!!!")
return
}
helloService := HelloService{}
//实例化grpc Server
server := grpc.NewServer()
// 注册服务
hello.RegisterHelloServer(server, helloService)
server.Serve(listener)
}
实现客户端调用 client/main.go
package main
import (
"fmt"
"golang.org/x/net/context"
"google.golang.org/grpc"
"grpc-demo/proto/hello"
)
func main() {
conn, e := grpc.Dial( "127.0.0.1:8989",grpc.WithInsecure())
if e != nil {
fmt.Println("Grpc Dial Error:",e)
return
}
defer conn.Close()
//初始化客户端
client:= hello.NewHelloClient(conn)
//调用方法
response, e := client.SayHello(context.Background(), &hello.HelloRequest{Name: "Gopher"})
if e != nil {
fmt.Println("Client SayHello Error:",e)
return
}
fmt.Println("Info:",response.Message)
}
consul
consul注册中心
1、当 Producer 启动的时候,会向 Consul 发送一个 post 请求,告诉 Consul 自己的 IP 和 Port
2、Consul 接收到 Producer 的注册后,每隔10s(默认)会向 Producer 发送一个健康检查的请求,检验Producer是否健康
3、当 Consumer 发送 GET 方式请求 /api/address 到 Producer 时,会先从 Consul 中拿到一个存储服务 IP 和 Port 的临时表,从表中拿到 Producer 的 IP 和 Port 后再发送 GET 方式请求 /api/address
4、该临时表每隔10s会更新,只包含有通过了健康检查的 Producer
consul集群架构图
consul单机版
consul agent -dev
consul集群版
consul服务端1
consul agent -server -bootstrap-expect 3 -data-dir /tmp/consul -node=n1 -bind=192.168.1.110 -ui -config-dir /etc/config/cosul -rejoin -join 192.168.1.110 -client 127.0.0.1/0.0.0.0
consul服务端2
consul agent -server -bootstrap-expect 3 -data-dir /tmp/consul -node=n2 -bind=192.168.1.111 -config-dir /etc/config/consul -rejoin -join 192.168.1.110 -client 127.0.0.1/0.0.0.0
consul服务端3
consul agent -server -bootstrap-expect 3 -data-dir /tmp/consul -node=n3 -bind=192.168.1.112 -config-dir /etc/config/consul -rejoin -join 192.168.1.110 -client 127.0.0.1/0.0.0.0
consul客户端1
consul agent -data-dir /tmp/consul -node=n4 -bind=192.168.1.113 -config-dir /etc/config/consul -rejoin -join 192.168.1.110
consul客户端2
consul agent -data-dir /tmp/consul -node=n5 -bind=192.168.1.114 -config-dir /etc/config/consul -rejoin -join 192.168.1.110
Go-Micro微服务框架
环境安装
- 安装go-micro微服务RPC框架
go get github.com/micro/go-micro
- 安装Protobuf
go get -u github.com/golang/protobuf/proto
go get -u github.com/golang/protobuf/protoc-gen-go
- 安装protoc-gen-micro可执行文件
v1版本:
go get github.com/micro/protoc-gen-micro
v2版本:
go get github.com/micro/micro/v2/cmd/protoc-gen-micro
- 安装micro工具包,生成micro可执行文件
方式1:
go get github.com/micro/micro
方式2:使用源码包进行go build编译安装
安装出现的问题
- 使用micro/v2时,protoc生成micro.protoc文件导致的版本冲突
可将生成的*.pb.micro.go文件中的v1依赖改为v2依赖即可
import (
context "context"
client "github.com/micro/go-micro/v2/client"
server "github.com/micro/go-micro/v2/server"
)
- micro/v2版本使用consul作为注册中心,默认使用mdns
2021-12-02 09:40:28.278823 I | Transport [http] Listening on [::]:65258
2021-12-02 09:40:28.278823 I | Broker [http] Connected to [::]:65259
2021-12-02 09:40:28.451945 I | Registry [consul] Registering node: go.micro.service.hello-c79e1fee-5f0d-4461-a56b-ca9552be4ad3
2021-12-02 09:40:29.150437 I | Subscribing go.micro.service.hello-c79e1fee-5f0d-4461-a56b-ca9552be4ad3 to topic: go.micro.service.hello
微服务小案例
-
go-micro v2 默认是 GRPC 通信
-
生成服务端骨架、客户端骨架
- v1版本
micro new --gopath=false --type="srv" hello
micro new --gopath=false --type="web" web
- v2版本
micro new --gopath=false --type="service" hello
micro new --gopath=false --type="web" web
2.生成protobuf
protoc --micro_out=. --go_out=. proto/hello/hello.proto
3.启用web面板查看服务【服务治理】
micro --registry=etcd --registry_address=127.0.0.1:2379 web
4.查看服务列表
micro --registry etcd --registry_address 127.0.0.1:2379 list services
使用etcd做注册中心
启动etcd
./etcd --data-dir ./data.etcd/ --listen-client-urls http://yourip:2379 --advertise-client-urls http://yourip:2379 & >./log/etcd.log
-listen-client-urls用于指定etcd和客户端的连接端口
-advertise-client-urls用于指定etcd服务器之间通讯的端口
etcd有要求,如果-listen-client-urls被设置了,那么就必须同时设置-advertise-client-urls,所以即使设置和默认相同,也必须显式设置.
2021-12-06 15:02:30 file=v2@v2.9.1/service.go:200 level=info Starting [service] go.micro.service.hello
2021-12-06 15:02:30 file=grpc/grpc.go:864 level=info Server [grpc] Listening on [::]:63232
2021-12-06 15:02:30 file=grpc/grpc.go:881 level=info Broker [http] Connected to 127.0.0.1:63233
2021-12-06 15:02:30 file=grpc/grpc.go:697 level=info Registry [etcd] Registering node: go.micro.service.hello-8bebc6e1-107f-49db-a4c6-063e7ba9509e
2021-12-06 15:02:30 file=grpc/grpc.go:730 level=info Subscribing to topic: go.micro.service.hello