背景
生產(chǎn)環(huán)境中,調(diào)用公司封裝的kms服務(wù)進(jìn)行解密,偶報(bào)超時(shí)錯(cuò)誤熏迹。但是看接口實(shí)際耗時(shí)只有100多ms。
一開(kāi)始懷疑是errgroup中的ctx用錯(cuò)了凝赛,導(dǎo)致cancel掉了請(qǐng)求注暗。后面確認(rèn)以后發(fā)現(xiàn)不是。
最后查看基礎(chǔ)框架封裝的源碼墓猎,默認(rèn)的請(qǐng)求超時(shí)時(shí)間被設(shè)置成了30ms捆昏,真是氣死。
當(dāng)時(shí)有兩個(gè)模塊毙沾,一個(gè)是模塊是對(duì)端接口玩般,gateway會(huì)在ctx里面設(shè)置deadline谷徙,然后向下傳遞趁啸,這個(gè)模塊沒(méi)報(bào)錯(cuò)油讯,因?yàn)閏tx里面有deadline了,就沒(méi)有用client設(shè)置的timeout
if _, ok := ctx.Deadline(); ok {
return next
}
另一個(gè)模塊是消費(fèi)kafka回調(diào)函數(shù)里面的ctx,這個(gè)ctx里面沒(méi)有設(shè)置deadline,所以會(huì)用client的timeout
grpc-go如何實(shí)現(xiàn)超時(shí)
通過(guò)下面的代碼,可以看到grpc-go是通過(guò)context實(shí)現(xiàn)超時(shí)控制的敛摘。
import (
"context"
"time"
pb "example.com/example.protobuf"
"google.golang.org/grpc"
)
func main() {
// 假設(shè)已經(jīng)設(shè)置了連接和客戶端
conn, _ := grpc.Dial("your_grpc_server_address", grpc.WithInsecure()) // 使用實(shí)際的連接參數(shù)替換
client := pb.NewYourServiceClient(conn)
// 設(shè)置超時(shí)
timeout := 3 * time.Second
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
// 使用帶超時(shí)的 context 進(jìn)行 gRPC 調(diào)用
req := &pb.YourRequest{} // 使用實(shí)際的請(qǐng)求結(jié)構(gòu)體替換
resp, err := client.YourMethod(ctx, req)
if err != nil {
// 處理錯(cuò)誤,可能是超時(shí)導(dǎo)致的
}
// 如果調(diào)用成功乳愉,處理響應(yīng)
}
基礎(chǔ)框架如何把timeout參數(shù)和grpc-go的超時(shí)機(jī)制整合
通過(guò)grpc的攔截器【只需要幾個(gè)攔截器兄淫,把攔截器做成自定義hook的形式,方便添加更多的業(yè)務(wù)邏輯】蔓姚,在發(fā)送請(qǐng)求前捕虽,讀取請(qǐng)求里面的timeout參數(shù),通過(guò)context.WithTimeout注進(jìn)context來(lái)實(shí)現(xiàn)超時(shí)控制坡脐。
通過(guò)ctx注進(jìn)去泄私,然后請(qǐng)求前拿出來(lái)設(shè)置上去
func WithClientConfig(ctx context.Context, conf settings.ClientConfig) context.Context {
return context.WithValue(ctx, clientConfigKey{}, conf)
}
設(shè)置timeout
func withTimeout(ctx context.Context, next func(context.Context) error) func(context.Context) error {
//如果是stream類的rpc,則不許呀設(shè)置超時(shí)
if rpc.IsStream(ctx) {
return next
}
//如果context自己設(shè)置了超時(shí)备闲,就不讀配置里面的timeout參數(shù)去設(shè)置context了
if _, ok := ctx.Deadline(); ok {
return next
}
var timeout int64
conf := grpcclient.GetClientConfig(ctx)
timeout = conf.Timeout
if timeout <= 0 {
timeout = defaultTimeout
}
return func(ctx context.Context) error {
ctx, cancel := context.WithTimeout(ctx, time.Duration(timeout)*time.Millisecond)
defer cancel()
return next(ctx)
}
}
kms客戶端緩存優(yōu)化
kms客戶端可以設(shè)置兩個(gè)緩存晌端,一個(gè)是加密的緩存,一個(gè)是解密的緩存恬砂。
根據(jù)uid后2位取模咧纠,設(shè)置加密的緩存,緩存過(guò)期時(shí)間為1小時(shí)泻骤。相同uid后綴的消息漆羔,在1小時(shí)以內(nèi)都用相同的密鑰加密。過(guò)期以后狱掂,重新去獲取密鑰演痒,后面1小時(shí)的,又用另外一個(gè)密鑰加密趋惨。
加密獲取密鑰對(duì)以后鸟顺,把解密的密鑰緩存下來(lái),過(guò)期時(shí)間設(shè)置為24小時(shí)希柿。這樣诊沪,新發(fā)的消息养筒,在24小時(shí)內(nèi)被拉取曾撤,都不需要請(qǐng)求kms去解密,走緩存即可晕粪。
效果:加密密鑰小時(shí)級(jí)別變化挤悉,解密大概率走緩存,性能好。即保證了安全性装悲,又保證了性能昏鹃。