什么时候用指针, 什么时候用值传递, 结构体、尤其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, "ient, 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】