什么時候用指針, 什么時候用值傳遞, 結構體、尤其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】