• Golang-后端总结


    什么时候用指针, 什么时候用值传递, 结构体、尤其sync包下的, 都要用指针传递; 而一些轻量的数据可以使用值传递,也不改变其值;
    尽量避免反射,在高性能服务中杜绝反射的使用
    多进行复用, 使用sync.Pool


    线上问题优化:
    批量读与写, 主要问题: CPU占用过高, 主要因为申请释放大量资源造成的;

    解决: 使用“sync.Pool”实现了一个对象池,
    p:=sync.Pool{
    New: new buff(),
    }
    p.Get().(*buff)
    p.Put(buff.clean())


    另外使用goroutine并发读写;

    服务的高峰 PCT99 从100ms降低到15ms。


    微服务, 每一个服务都有自己的数据库, 微服务划分: 会员认证模块、评论模块、点赞模块等;

    gRPC是由Google主导开发的RPC框架,使用HTTP/2协议并用Protobuf作为序列化工具;

    要点:
    中心化配置
    服务发现
    日志
    分布式探寻
    熔断
    负载均衡
    边缘
    监测
    安全

    服务注册和发现, API GATEWAY, REST API; 消息中心, RPC API; 也就是对外RESTFUL服务使用json, 对内使用protobuf

    中心配置: etcd


    服务发现: 请求达到网关后, 要知道服务的API的IP地址是多少;在生产实践中,主要有两种服务发现机制:客户端发现和服务端发现。
    客户端发现:
    例如三个客户端的实例向组测服务注册(self-registration心跳模式), 该服务可以是访问服务登记表,也就是一个可用服务的数据库,然后客户端使用一种负载均衡算法(一致性哈希),选择一个可用的服务实例然后发起请求。
    也使用etcd

    Go kit套件, 可以在其中使用http,grpc各种传输; 分布式探寻: 使用分区规则进行并行计算; 日志:grpc或kafka进行汇总 ; 统计信息: 各个方法的连接次数, gc四分位点等;


    熔断: go-kit的hystrix, 主要包含: 1. 请求超时的时间; 2. 允许的最大并发请求数; 3. 熔断开启多久尝试发起一次请求;


    负载均衡: 将同一个服务处理的代码分布到三台机器, 所有请求先进负载均衡服务器,


    网络传输三层结构:
    HTTP报文: GET www...
    TCP报文: 源地址: 3345(客户端), 目的地: 80(负载均衡服务器)
    IP数据包: 源地址: ip-客户端, 目的地: ip-负载均衡服务器

    在负载均衡服务器,将目的地的 TCP端口、IP地址改城 RS1服务的地址;


    七层结构就是在三层的基础上, 加上URL,浏览器,语言等;
    但是还有一个问题: 请求很短, 但是响应返回的是一个html文件; 让Load Balancer只处理请求,让各个服务器把响应直接发给客户端;


    解决:
    首先让所有的服务器都有同一个IP, 我们把他称为VIP吧(如图中115.39.19.22)。

    网络传输四层结构:
    以太网帧: 源地址: 11:27:F5:.. 目的地: ???
    HTTP报文: GET www...
    TCP报文: 源地址: 3345(客户端), 目的地: 80 vip
    IP数据包: 源地址: ip-客户端, 目的地: 115.39.19.22 vip


    使用ARP协议, 将115.39.19.22 vip 给广播出去, 然后具有此IP机器就会回复自己的MAC地址。


    这样请求就能分发出去, 响应就能由VIP 直接发给客户端;

    tcp讲解:
    现在的典型模型是, Non-Block + I/O多路复用。
    而Go开发者无需关注socket是否是 non-block的,也无需亲自注册文件描述符的回调,只需在每个连接对应的goroutine中以“block I/O”的方式对待socket处理即可;


    l, err := net.Listen("tcp", ":8888") // 服务端监听这个端口

    for {
    c, err := l.Accept() // 相当于在这里进行read阻塞, 等待flag;
    }

    go handleConn(c) // 获取连接后进行处理

    TCP Socket的连接的建立需要经历客户端和服务端的三次握手的过程。连接建立过程中,服务端是一个标准的Listen + Accept的结构;

    // 客户端尝试连接:
    conn, err := net.DialTimeout("tcp", ":8080", 2 * time.Second)

    在握手的过程中会经历以下情况:
    1. 网络不可达或对方服务未启动; 会立即返回报错;

    2. server端的listen backlog队列满;

    3. 网络延迟较大,Dial阻塞并超时

    conn连接上之后, 就可以使用读写, conn.Read() , conn.Write()
    TCP是全双工通信,因此每个方向都 有独立的数据缓冲。当发送方将对方的接收缓冲区以及自身的发送缓冲区写满后,Write就会阻塞。
    读写都是lock safe的, 使用锁完整读, 完整写;

    rpc讲解:

    RPC(Remote ProcedureCall,远程过程调用), 构建于TCP或UDP,或者是HTTP之上,允许直接调用另一台计算机上的程序,而无需额外地为这个调用过程编写网络通信相关代码。

    使用:
    在RPC服务端,可将一个对象注册为可访问的服务,之后该对象的公开方法就能够以远程的方式提供访问。一个RPC服务端可以注册多个不同类型的对象,但不允许注册同一类型的多个对象。

    func (t *T) MethodName(argType T1, replyType *T2) error
    第一个参数表示由 RPC 客户端传入的参数,第二个参数表示要返 回给RPC客户端的结果,该方法最后返回一个error 类型的值。

    调用RPC客户端的Call(), 同步; 调用RPC客户端的Go(),异步;

    如果没有明确指定RPC传输过程中使用何种编码解码器,默认将使用 Go 标准库提供的encoding/gob 包进行数据传输; Gob是二进制编码的数据流, 只能用于go语言;


    arith : 服务对象可以很简单, 比如类型是int或者是interface{},重要的是它输出的方法。

    服务端:
    rpc.Register(arith)
    rpc.HandleHTTP()
    l, e := net.Listen("tcp", ":1234")
    go http.Serve(l, nil)


    客户端:
    client, err := rpc.DialHTTP("tcp", serverAddress + ":1234")
    args := &server.Args{7,8}
    var reply int
    err = client.Call("Arith.Multiply", args, &reply) // 使用同步


    quotient := new(Quotient)
    divCall := client.Go("Arith.Divide", args, &quotient, nil) // 使用异步
    replyCall := <-divCall.Done


    负载均衡的rpc方案:

    服务端:
    var (
    //etcd服务地址
    etcdServer = "127.0.0.1:2379"
    //服务的信息目录
    prefix = "Arith.Multiply"
    //当前启动服务实例的地址
    instance = "127.0.0.1:50052"
    //服务实例注册的路径
    key = prefix + instance
    //服务实例注册的val
    value = instance
    ctx = context.Background()
    //服务监听地址
    serviceAddress = ":50052"
    )

    // 将连接信息, 写入到etcd的leader上;
    // 创建注册器
    registrar := etcdv3.NewRegistrar(client, etcdv3.Service{
    Key: key,
    Value: value,
    }, log.NewNopLogger())


    // 注册器启动注册
    registrar.Register()

    // 将rpc方法, 添加到tcp端口;
    arith := new(Arith)
    rpc.Register(arith)
    rpc.HandleHTTP()
    l, e := net.Listen("tcp", serviceAddress) // 使用服务注册的地址
    if e != nil {
    fmt.Print("listen error:", e)
    }
    http.Serve(l, nil)



    type Arith int

    func (t *Arith) Multiply(args *Args, reply *int) error {
    *reply = args.A * args.B
    return nil
    }


    客户端:

    // 添加etcd地址, 以及需要的服务名称;
    var (
    //注册中心地址
    etcdServer = "127.0.0.1:2379"
    //监听的服务前缀
    prefix = "Arith.Multiply"
    ctx = context.Background()
    )

    //创建实例管理器, 此管理器会Watch监听etc中prefix的目录变化更新缓存的服务实例数据
    instancer, err := etcdv3.NewInstancer(client, prefix, logger)


    //创建端点管理器, 此管理器根据Factory和监听的到实例创建endPoint并订阅instancer的变化动态更新Factory创建的endPoint
    endpointer := sd.NewEndpointer(instancer, reqFactory, logger)
    //创建负载均衡器
    balancer := lb.NewRoundRobin(endpointer)

    //func reqFactory(instanceAddr string) 中client, err := rpc.DialHTTP("tcp", instanceAddr), client.Call("Arith.Multiply", args, &reply) 具体调用哪个方法;


    MQTT协议:
    MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的“轻量级”通讯协议,该协议构建于TCP/IP协议上;

    服务质量Qos:
    “至多一次”,消息发布完全依赖底层TCP/IP网络。会发生消息丢失。
    “至少一次”,确保消息到达,但消息重复可能会发生。
    “只有一次”,确保消息到达一次。在一些要求比较严格的计费系统中。

    特点: 小型传输,只有2字节; Last Will: 通知同一主题下的其他设备, 该设备已断开;

    组件: 发布者(Publish)、订阅者(Subscribe)、代理(Broker)
    发布者与订阅者都是客户端,发布者可以同时是订阅者;
    代理是服务端;


    MQTT传输的消息分为:主题(Topic)和负载(payload)两部分;

    主题用于订阅者订阅, 负载可以理解为消息的内容。

    MQTT客户端: 1. 发布其他客户端可能会订阅的信息; 2. 订阅其它客户端发布的消息;
    MQTT服务器(broker): 2. 它是位于消息发布者和订阅者之间, 1. 接受来自客户的网络连接; 2. 接受客户发布的应用信息;


    连接流程:
    每个客户端与服务器建立连接后就是一个会话,订阅会与一个会话(Session)关联。一个会话可以包含多个订阅。

    MQTT协议数据包结构:
    固定头(Fixed header) 主要是连接信息、可变头(Variable header)、消息体(payload)三部分构成。
    MQTT 协议不是双向信任的,它没有提供客户端验证服务端身份的机制。


    安装broker服务器:
    NGINX:nginx-1.11.0.tar.gz
    EMQ:emqx-centos6.8-v3.0-beta.4.x86_64.rpm 单节点可接收50-100万连接;


    emr:
    集群方式采用基于 static 节点列表自动集群:
    cluster.static.seeds = emqx1@192.168.1.2,emqx2@192.168.1.3

    LDAP认证: 安装证书, service emqx start;


    nginx:
    http {
    server 192.168.1.2:18083;
    server 192.168.1.3:18083;
    }

    安装客户端:

    连接broker: clinetOptions := mqtt.NewClientOptions().AddBroker("tcp://xxxxx:1883").SetUsername("admin").SetPassword("public")
    订阅消息: token := client.Subscribe("go-test-topic", 1, messageSubHandler) token.Wait()
    发布消息: token := client.Publish("go-test-topic", 1, false, text) token.Wait()





    ##一些想象的实践:


    机器数据 -> broker appoll(ssl双向加密) -> raw;
    通过至少一次的策略, 防止数据丢失, 在服务端进行简单etl;

    服务限流与容灾, 限流防止内存崩溃, 加入阻塞队列, 容灾: 涉及状态机、心跳、重连策略; 使用最大负载从上一状态开始跑; 宕机状态的邮件分发;


    熔断 -> 限流 -> appoll -> 服务断掉恢复状态 -> nigix -> vue

    概念
    服务雪崩效应
    原因:由于延时或负载过高等导致请求积压,占用大量系统资源,服务器达到性能瓶颈,服务提供者不可用
    现象:上游服务故障导致下游服务瘫痪,出现连锁故障
    应对策略:
    扩容
    控制流量
    熔断
    服务降级


    hystrix.ConfigureCommand("aaa", hystrix.CommandConfig{
    Timeout: 5000,
    MaxConcurrentRequests: 5,
    })
    1、Timeout 【请求超时的时间】

    2、ErrorPercentThreshold【允许出现的错误比例】

    3、SleepWindow【熔断开启多久尝试发起一次请求】

    4、MaxConcurrentRequests【允许的最大并发请求数】

    5、RequestVolumeThreshold 【波动期内的最小请求数,默认波动期10S】

  • 相关阅读:
    swoole 的 go function 是什么
    MySQL grant之后要不要跟上 flush privileges
    操作数据库
    APP端异常情况
    评论列表优化联表问题
    tp5修改数据前过滤
    字典
    if语句
    【整理】android事件传递机制
    Linux常用操作
  • 原文地址:https://www.cnblogs.com/ruili07/p/11459267.html
Copyright © 2020-2023  润新知