微服务基础

微服务,微服务架构,微服务框架

NtTOTv
brTPTu

  1. 微服务:应该使用这种方法而设计的一个应用,也就是设计出来的一个应用
  2. 微服务架构:一种设计方法
  3. 微服务框架(比如我们后面用到的micro):将复杂的系统使用组件化的方式进行拆分,并且使用轻量级通讯方式进行整合的一种设计方法。微服务是通过这种架构设计方法拆分出来的一个独立的组件化的小应用。

注意:这3个概念不一样

微服务架构与整体式架构的区别

单体式开发的缺点:

  1. 复杂性逐渐变高
  2. 技术债务逐渐上升
  3. 维护成本大
  4. 持续交付周期长
  5. 可扩展性差

微服务架构的特点

  1. oLKc0q :这张图将我们的服务都拆分出来了,想使用哪个直接访问就可以了,不需要使用集中式的,每一个业务单独配置。
  2. 单一职责:微服务架构中的每个服务,都是具有业务逻辑的,复合高内聚,低耦合的原则以及单一职责原则的单元,不同的服务通过管道的方式灵活组合,从而构建出庞大的系统
  3. 轻量级通信:服务之间通过轻量级的通信机制实现互通互联,所谓的轻量级,通常是指语言无关、平台无关的交互方式。
  4. bJpiiz :restful是一种风格,满足了我们的rest这个规则
  5. 1h2Exi -> kTR8VY
  6. VMS2m6b6lKUC

微服务架构的缺点

6rJkXr

  1. 分布式微服务与传统的区别:T2ldhl
  2. 为什么使用微服务架构:RaMkeC
  3. 扩充业务:Oy0Drw
  4. 去掉影响的服务:![kR1L1D](https://cdn.jsdeliv r.net/gh/sivanWu0222/ImageHosting@master/uPic/kR1L1D.png)
  5. 升级服务:sZgVzB

微服务课程的几个重要组件

  1. 跨语言,跨平台的通信:protobuf
  2. 通信协议:gRPC
  3. 服务的调度服务发现:consul
  4. 微服务的框架:micro
  5. 微服务部署:docker

protobuf

1
2
3
4
5
知识了解:protoc --proto_path=./Services/protos --micro_out=. --go_out=. ./Services/protos/Models.Proto
项目根目录下执行这个命令,表示我们输出的go代码位于当前目录下的protos文件夹,
具体输出的目录就是 go_out指定的路径 + option go_package指定的路径(这个路径是相对于go_out指定的路径)
同时输出的Go文件的包名就是option go_package最后一个/右边的名字
并且我们要对./Services/protos/Models.proto文件进行编译

了解

  1. QOAocV
  2. 介绍:0Qevoj
  3. 对比:5jYYAz
  4. protobuf的优点:8ewsJG
  5. protobuf的缺点:MAxYdS

我们写一个.proto的文件,然后生成到不同语言对应的文件,比如go会生成.go的文件

protobuf安装

  1. 首先使用protoc -h测试是否安装
  2. St1qqL
  3. 获取proto包:go get -v -u github.com/golang/protobuf/proto 如果是离线安装,我们需要将下载的代码放置到src下面的github.com目录下,然后执行go build生成protoc-gen-go,将这个protoc-gen-go移动到/bin目录下
  4. 安装protoc-gen-go插件:sU6aZo

知识点:go get-v-u的作用
-v :显示操作流程的日志及信息,方便检查错误
-u 如果需要依赖的时候会下载依赖,也就是下载丢失的包,但不会更新已经存在的包

protobuf的基本语法

  1. 要使用protobuf必须得先定义proto文件,所以得先熟悉protobuf的消息定义的相关语法
  2. 定义一个消息类型:FmgLtN
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    syntax = "proto3"; //表明现在使用的是proto3的语法,如果不指定这个编译器将会使用proto2。这个指定的与发行必须是文件的非空非注释的第一行。
    option go_package = "./Message"; //指定将我们的go文件生成到哪个目录
    //定义一个消息类型:消息定义中,每一个子弹都有唯一的一个标识符,也就是对应的序号
    message PandaRequest {
    //数据类型 变量名 = 序号;
    string name = 1;
    int32 height = 2;
    //repeated表示重复的,相当于golang里面的切片,
    //repeated后面跟一个数据类型表示切片中元素的数据类型
    repeated int32 weight = 3;
    }
  3. 向proto文件中添加注释:可以使用//的语法格式。4qE56u
  4. JdePab
  5. 生成对应的语言的代码:r9HtxF + 2xUpX2
    protoc --go_out指定生成的go的目录 proto文件所在的目录/生成go文件的proto文件 例如: protoc --go_out=./ ./proto/*.proto
    由于我们自己的环境与老师的不一样,我们还得在proto文件中加上这么一句话option go_package = "./Message"; //指定将我们的go文件生成到哪个目录,这个目录是相对于我们命令protoc中的参数go_out而言的相对目录
  6. 数据类型:hXFnVM + fARK68 + p8SAfz
  7. 如果有一些格式文件比较复杂,我们都建议用go语言的[]byte然后对应protobuf的bytes类型进行传输,相当好使,因为是二进制传输。
  8. PsYxhK ,比如: YLsZgJ
  9. 嵌套类型:kaNhHq
  10. 定义服务(grpc的时候用到, grpc是rpc的一个升级版本):NYsL6R , 传入的参数以及返回的参数都是我们的message
  11. jcloDc +
  12. 测试文件:
    1. v1YrEw
    2. 38F4jo
    3. 将我们编写的proto编译成go文件
    4. 编码并解码:dLduny + DSXRmT

一般流程:编写消息和服务 -> 生成对应的go的代码 -> 通过grpc来进行远程调用
快捷键:command + b 快速进入到代码内部的实现中

rpc

rpc的客户端与服务端

  1. EBtoRz
  2. x3kepP
  3. 远程过程调用流程图:FBB2mV + 过程: 2gUZj0
  4. go内部也是有一个rpc库的,我们可以直接使用, iBp4Xx + GKuZkk + 注册:k9Bia9 + 连接到网络: n6OBSX + 编写客户端:sjJAqe

rpc的调用流程讲解

  1. 过程图:7n41N3
  2. 对着代码进行讲解:p2Wa5w
    1. 左边的建立连接返回一个cli句柄
    2. 我们通过cli.call调用远程函数,并且带了传参和接收返回
    3. 服务端注册完之后就会作为服务暴露出来,随之函数也就会被暴露出来,之后我们就可以通过rpc进行调用

rpc客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
"net/rpc"
)

/**
* @Author: yirufeng
* @Date: 2021/5/10 10:05 上午
* @Desc:
**/

func main() {
cli, err := rpc.DialHTTP("tcp", "127.0.0.1:10086")
defer cli.Close()
if err != nil {
panic(err)
}

var pd int

err = cli.Call("Panda.GetInfo", 100, &pd)
if err != nil {
panic(err)
}

fmt.Println(pd)

}

rpc服务端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package main

import (
"fmt"
"io"
"net"
"net/http"
"net/rpc"
)

/**
* @Author: yirufeng
* @Date: 2021/5/10 10:05 上午
* @Desc:
**/

type Panda int

//只有满足如下3个标准的方法才可以用于远程访问
//1. 方法是可以导出的
//2. 方法的第二个参数是指针
//3. 方法只有一个error接口类型的返回值
//方法的第一个参数表示调用者提供的参数,第二个参数代表返回给调用者的参数
//方法的返回值如果不是nil,将会被作为字符串回传,在客户端看来与errors.New()创建的一样
//如果返回了错误,回复的参数不会发送给客户端
func (p *Panda) GetInfo(argType int, replyType *int) error {
fmt.Println("方法调用者传递过来的参数:", argType)

//修改内容值
*replyType = argType + 100

return nil
}

func main() {

//编写一个处理请求的handler
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "hello world!")
})
//实例化一个结构体的对象
pd := new(Panda)
//服务端注册一个对象
rpc.Register(pd)
rpc.HandleHTTP()
ln, err := net.Listen("tcp", ":10086")
if err != nil {
panic(err)
}
http.Serve(ln, nil)
}

grpc

grpc基本概念

  1. Ui4EwW
  2. UCYMlT

grpc环境搭建

对于采用mod工具管理的项目:直接使用命令:go get google.golang.org/grpc来下载我们的grpc

客户端代码与服务端代码介绍

  1. 编写我们的protobuf协议的内容:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    syntax = "proto3";

    package myproto;

    option go_package = "./myproto";


    //客户端发送给服务端的
    message HelloRequest {
    string name = 1;
    }

    //服务端返回给客户端的
    message HelloResponse {
    string msg = 1;
    }


    //客户端向服务端发送的请求
    message NameRequest {
    string name = 1;
    }

    //服务端返回给客户端的响应
    message NameResponse {
    string msg = 1;
    }



    //定义服务
    service HelloService {
    //函数的传入和传出就是我们自己要定义的消息类型
    //一个打招呼的函数
    rpc SayHello (HelloRequest) returns (HelloResponse) {}
    //一个说名字的函数
    rpc SayName (NameRequest) returns (NameResponse) {}
    }
  2. 我们使用protoc --go_out=. ./myproto/*.proto生成之后发现没有rpc对应的方法。
  3. 我们需要插件进行生成:protoc --go_out=plugins=grpc:. ./myproto/*.protoQzwkro
  4. 编写grpc服务端:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    package main

    /**
    * @Author: yirufeng
    * @Date: 2021/5/10 4:34 下午
    * @Desc: 手动写一个grpc_server
    **/

    import (
    "context"
    "fmt"
    "go-project/myproto"
    "google.golang.org/grpc"
    "net"
    )

    type server struct {
    }

    func (this *server) SayHello(ctx context.Context, request *myproto.HelloRequest) (out *myproto.HelloResponse, err error) {
    return &myproto.HelloResponse{
    Msg: "你好," + request.Name,
    }, nil
    }

    func (this *server) SayName(ctx context.Context, request *myproto.NameRequest) (out *myproto.NameResponse, err error) {
    return &myproto.NameResponse{
    Msg: "你的名字是:" + request.Name,
    }, nil
    }

    func main() {
    //创建网络
    ln, err := net.Listen("tcp", ":10086")
    if err != nil {
    fmt.Println("网络错误,", err)
    }
    //创建grpc的服务
    srv := grpc.NewServer()
    //注册服务
    myproto.RegisterHelloServiceServer(srv, &server{})
    //监听网络并等待网络连接
    err = srv.Serve(ln)
    if err != nil {
    fmt.Println("网络错误:", err)
    }
    }
  5. 编写grpc客户端:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    package main

    import (
    "context"
    "fmt"
    "go-project/myproto"
    "google.golang.org/grpc"
    )

    /**
    * @Author: yirufeng
    * @Date: 2021/5/10 4:53 下午
    * @Desc:
    **/

    func main() {

    //客户端连接服务器
    conn, err := grpc.Dial("127.0.0.1:10086", grpc.WithInsecure())
    if err != nil {
    fmt.Println("网络异常:", err)
    }

    //延迟关闭连接
    defer conn.Close()

    //获得grpc的句柄
    client := myproto.NewHelloServiceClient(conn)

    //通过句柄调用函数
    //第一个参数是context,我们传一个默认的context就可以的
    ret, err := client.SayHello(context.Background(), &myproto.HelloRequest{
    Name: "yirufneg",
    })
    if err != nil {
    fmt.Println("调用SayHello失败:", err)
    }
    fmt.Println("调用SayHello的返回值:", ret)
    ret2, err := client.SayName(context.Background(), &myproto.NameRequest{
    Name: "yirufneg",
    })
    if err != nil {
    fmt.Println("调用SayName失败:", err)
    }
    fmt.Println("调用SayName的返回值:", ret2)
    }

引申出的两个问题:

  1. 客户端如果不写网路的话,肯定会找不到对应地址的服务端。
  2. 如果服务端不启动的时候,客户端建立对话的时候肯定找不到。

了解服务发现之consul

背景

上节课的时候我们服务经常会遇到如下的问题:

  1. 客户端连接服务器的时候,IP去掉将无法连接服务器:conn, err := grpc.Dial("127.0.0.1:10086", grpc.WithInsecure()
  2. 如果先运行客户端再运行服务端,客户端将会报错。

aF6Aak
这些问题都需要解决,因此我们需要通过服务发现来解决这些问题

consul中的基本概念

  1. 通过服务发现来管理服务:hjwDSP
  2. consul的代码并没有开源
  3. 故障检测使得如果有服务挂掉,之后请求的时候将会停止访问我们的服务
  4. 一个简单的案例:25Ol7t
  5. gbYtWf BoFnOg
  6. 服务注册:就是服务主动去consul那里登记,服务发现就是指请求过来之后去consul那里查询对应的服务,此时就是服务发现 m7vwPH zlqs9v
  7. Nbh3Ph Tpo1VP

consul安装

  1. 7znMCe
  2. J2kwru
  3. 命令:vtxNib
  4. zT7xfI

consul中的角色:

  1. client接收请求,然后将我们的请求转发给对应的server。
  2. server做配置保存以及高可用的集群,并在局域网内与我们的client进行通信,并且负责通过广域网与其他数据中心通讯。

agent不是client就是server

consul集群搭建

环境:3个虚拟机

  1. 192.168.110.123
  2. 192.168.110.148
  3. 192.168.110.124

步骤:FjtqZl

  1. 192.168.110.123 主机执行命令:consul agent -server -bootstrap-expect 2 -data-dir /tmp/consul -node=n1 -bind=192.168.110.123 -ui -config-dir /etc/consul.d -rejoin -join 192.168.110.123 -client 0.0.0.0 + VNkw1J
  2. 192.168.110.148 主机执行命令:consul agent -server -bootstrap-expect 2 -data-dir /tmp/consul -node=n2 -bind=192.168.110.148 -ui -rejoin -join 192.168.110.123 + VnyBD7URYZtv + 访问:94F8s8
  3. 148机器访问:TppyO2
  4. 192.168.110.124主机执行命令: consul agent -data-dir /tmp/consul -node=n3 -bind=192.168.110.124 -config-dir /etc/consuld.d -rejoin -join 192.168.110.123 + qFexDB
  5. 148机器访问:RbrG6L

其他命令:

  1. 查看集群成员:Pvx6iX + GYuxXb
  2. 查看consul版本:consul version
  3. 停止Agent:NEl3cO
  4. consul leave 用于退出集群

consul 服务注册

  1. tp72FN
  2. 1fRIrx
  3. 测试程序:1J8DIk

在micro中我们不需要手动写consul的配置文件

consul扩展

  1. xv7foa
  2. consul架构图:UwSjSy 如果要跨域(图中的黑色):通过网络进行连接。
  3. 9ZWjlz
  4. JPZ2Ag
  5. zU0oLv

client不做信息持久化保存。

consul参考资料:

  1. 参考1
  2. 参考2

micro

micro介绍

  1. SVxO9C
  2. micro是一个分布式工具也是一个内部的组件

micro安装

  1. HyFVcL

补充:

  1. 官方网站:pPcGyJ
  2. 自己下去可以看一下micro中文的文档
  3. go-micro默认是rpc,我们要用grpc进行一下升级

micro自己安装

  1. 6iZ6xf
  2. 2p9cOg
  3. mIAtjq

micro环境搭建以及基本演示

  1. CAOi2I + ![4elFlk](https://cdn.jsdelivr .net/gh/sivanWu0222/ImageHosting@master/uPic/4elFlk.png)
  2. EdUTko
  3. 2AdThr :我们可以用DOCKERFILE将我们的单个服务升级成对应的镜像
  4. HwImoa
  5. NJNPtz
  6. 7IuBKb

两个命令生成srv文件以及web文件

  1. micro new --type "srv" day2/micro/rpc.web
  2. micro new --type "web" day2/micro/rpc.web

评论