Gateway版本
VERSION = "0.0.11"
Gateway组件功能
多IDC时,可能面对 "分区到中心的专线网络质量较差&公网ACL不通" 等问题。这时,可以在分区内部署一套数据路由服务,接收本分区内的所有流量(包括所有的agent流量),然后通过公网(开通ACL),将数据push给中心的Transfer。
站在client端的角度,gateway和transfer提供了完全一致的功能和接口。只有遇到网络分区的情况时,才有必要使用gateway组件。【官方说明】
Gateway组件逻辑图
main入口分析
func main() {
cfg := flag.String("c", "cfg.json", "configuration file")
version := flag.Bool("v", false, "show version")
flag.Parse()
if *version {
fmt.Println(g.VERSION)
os.Exit(0)
}
// 全局配置解析
g.ParseConfig(*cfg) //【参考详细分析】
if g.Config().Debug {
g.InitLog("debug")
} else {
g.InitLog("info")
}
sender.Start() // 发送数据服务运行 【参考详细分析】
receiver.Start() // 接收数据服务运行 【参考详细分析】
//http API服务
http.Start()
select {}
}
g.ParseConfig(*cfg) 全局配置解析
# 默认配置为当前目录下cfg.json
func ParseConfig(cfg string) {
if cfg == "" {
log.Fatalln("use -c to specify configuration file")
}
//判断文件是否存在
if !file.IsExist(cfg) {
log.Fatalln("config file:", cfg, "is not existent. maybe you need `mv cfg.example.json cfg.json`")
}
ConfigFile = cfg
//配置字符串化
configContent, err := file.ToTrimString(cfg)
if err != nil {
log.Fatalln("read config file:", cfg, "fail:", err)
}
var c GlobalConfig
// Json反序列化为结构
err = json.Unmarshal([]byte(configContent), &c)
if err != nil {
log.Fatalln("parse config file:", cfg, "fail:", err)
}
configLock.Lock()
defer configLock.Unlock()
config = &c
log.Println("g.ParseConfig ok, file ", cfg)
}
type GlobalConfig struct {
Debug bool `json:"debug"`
Http *HttpConfig `json:"http"`
Rpc *RpcConfig `json:"rpc"`
Socket *SocketConfig `json:"socket"`
Transfer *TransferConfig `json:"transfer"`
}
sender.Start() 转发数据至后端transfer
func Start() {
initConnPools() //初始化连接池
startSendTasks() //周期任务线程,转发数据至后端Transfer
startSenderCron() //goperfcounter统计功能周期发送
log.Println("send.Start, ok")
}
#初始化连接池
func initConnPools() {
cfg := g.Config()
//初始化Transfer集群配置
addrs := make([]string, 0)
for hn, addr := range cfg.Transfer.Cluster {
TransferHostnames = append(TransferHostnames, hn)
addrs = append(addrs, addr)
TransferMap[hn] = addr
}
//初始化transfer发送计算器
for hn, addr := range cfg.Transfer.Cluster {
TransferSendCnt[hn] = nproc.NewSCounterQps(hn + ":" + addr)
TransferSendFailCnt[hn] = nproc.NewSCounterQps(hn + ":" + addr)
}
//初始化发送Transfer连接池
SenderConnPools = backend.CreateSafeJsonrpcConnPools(int(cfg.Transfer.MaxConns), int(cfg.Transfer.MaxIdle),
int(cfg.Transfer.ConnTimeout), int(cfg.Transfer.CallTimeout), addrs)
}
#启动后台发送线程
func startSendTasks() {
cfg := g.Config()
concurrent := cfg.Transfer.MaxConns * int32(len(cfg.Transfer.Cluster))
go forward2TransferTask(SenderQueue, concurrent) //发送线程
}
#转发数据至Transfer服务器实现函数
func forward2TransferTask(Q *nlist.SafeListLimited, concurrent int32) {
//全局配置读取
cfg := g.Config()
batch := int(cfg.Transfer.Batch)
maxConns := int64(cfg.Transfer.MaxConns)
retry := int(cfg.Transfer.Retry)
if retry < 1 {
retry = 1
}
sema := nsema.NewSemaphore(int(concurrent)) //并发量
transNum := len(TransferHostnames) //transfer集群数
for {
items := Q.PopBackBy(batch) //队列取出批量数据
count := len(items)
if count == 0 {
time.Sleep(time.Millisecond * 50)
continue
}
transItems := make([]*cmodel.MetricValue, count)
for i := 0; i < count; i++ {
transItems[i] = convert(items[i].(*cmodel.MetaData)) //数据格式化transItems结构
}
sema.Acquire()
go func(transItems []*cmodel.MetricValue, count int) {
defer sema.Release()
var err error
//随机遍历transfer列表,直到数据发送成功 或者 遍历完;随机遍历,可以缓解慢transfer
resp := &cmodel.TransferResponse{}
sendOk := false
for j := 0; j < retry && !sendOk; j++ {
rint := rand.Int() //随机
for i := 0; i < transNum && !sendOk; i++ {
idx := (i + rint) % transNum //随机选择
host := TransferHostnames[idx]
addr := TransferMap[host]
//过滤掉建连缓慢的host, 否则会严重影响发送速率
cc := pfc.GetCounterCount(host)
if cc >= maxConns {
continue
}
pfc.Counter(host, 1) //统计
err = SenderConnPools.Call(addr, "Transfer.Update", transItems, resp) //RPC调用上传数据
pfc.Counter(host, -1)
if err == nil {
sendOk = true
//统计正常
TransferSendCnt[host].IncrBy(int64(count))
} else {
//统计错误
log.Errorf("transfer update fail, items size:%d, error:%v, resp:%v", len(transItems), err, resp)
TransferSendFailCnt[host].IncrBy(int64(count))
}
}
}
//goperfcounter统计
if !sendOk {
pfc.Meter("SendFail", int64(count))
} else {
pfc.Meter("Send", int64(count))
}
}(transItems, count)
}
}
//goperfcounter统计数据发送周期线程
func startSenderCron() {
go startProcCron()
}
func startProcCron() {
for {
time.Sleep(DefaultProcCronPeriod)
refreshSendingCacheSize()
}
}
func refreshSendingCacheSize() {
pfc.Gauge("SendQueueSize", int64(SenderQueue.Len()))
}
cmodel.MetricValue{
Metric: v.Metric,
Endpoint: v.Endpoint,
Timestamp: v.Timestamp,
Step: v.Step,
Type: v.CounterType,
Tags: cutils.SortedTags(v.Tags),
Value: v.Value,
}
receiver.Start() 接收服务【与Transfer相同,详细可参考Transfer分析】
func Start() {
go rpc.StartRpc() //RPC服务
go socket.StartSocket() //Socket TCP
}
http.Start() HTTP API服务器运行与监听处理
func Start() {
go startHttpServer() //后台运行HTTP API 服务
}
func startHttpServer() {
if !g.Config().Http.Enabled {
return
}
addr := g.Config().Http.Listen
if addr == "" {
return
}
configCommonRoutes() //标准公共接口路由【可以参考Agent模块】
configProcHttpRoutes() //统计接口路由
configApiHttpRoutes() //API PUSH接口路由
s := &http.Server{
Addr: addr,
MaxHeaderBytes: 1 << 30,
}
log.Println("http.startHttpServer ok, listening", addr)
log.Fatalln(s.ListenAndServe())
}
// API方式提交Metric数值
func configApiHttpRoutes() {
http.HandleFunc("/api/push", func(w http.ResponseWriter, req *http.Request) {
if req.ContentLength == 0 {
http.Error(w, "blank body", http.StatusBadRequest)
return
}
decoder := json.NewDecoder(req.Body)
var metrics []*cmodel.MetricValue
err := decoder.Decode(&metrics)
if err != nil {
http.Error(w, "decode error", http.StatusBadRequest)
return
}
reply := &cmodel.TransferResponse{}
trpc.RecvMetricValues(metrics, reply, "http") //接收Metric Data
RenderDataJson(w, reply)
})
}