article icon indicating copy to clipboard operation
article copied to clipboard

如何小成本监控个人开发的应用

Open ICKelin opened this issue 4 years ago • 0 comments

现在很多人有时间都希望写一些自己的代码,把自己的一些想法给实现一下,在实现自身想法的过程当中,可能会忽略一些基础组件的重要性,我之前也是非常不重视基础组件,然后一箩筐代码写下来,写完了,后面再来开发一些基础组件,改动还是挺大的。

在开发notr的过程当中,前期专注软件的实现,经过一波又一波的优化,软件功能基本满意之后,又想着如何去把软件介绍给别人使用,依然还记得第一个愿意使用的用户,第一个付费的用户,当时特别的兴奋,虽然在亏钱。。

软件使用的人慢慢多了之后,那么就跟之前不一样了,没人用的时候,软件挂了之后去重启就好了,产生不了任何价值,自然也就没有太多的压力。现在有不少用户了,而且是付费的用户,那么就需要对用户负责,系统肯定不能挂。我需要了解整个系统的状态。并且一旦系统出现异常时及时通知我进行恢复,这时候我开始考虑花心思去构建一个监控相关的基础服务,以达到以下目的:

  • 故障通知
  • 监控应用状态
  • 监控应用内部数据

对于实现这些目的,我的要求只有一个,那就是快速搞好,最好一天之内能够解决掉,因为这个不是我的重点,数据丢失无关紧要。

这里面比较关注的是故障通知,通知要比较及时,采用邮件的方式肯定是不行的,短信又太贵,最终我选择了一个比较罕见的方式,使用微信公众号,推送模版消息,这种方式能够很好的解决我的问题,一方面是免费的,另一方面一天有最多十万条,所以次数肯定是满足了的,有过公众号开发的可能知道模版推送需要资质,个人不能使用,正是因为个人使用,所以没有太深究,既然都不深究了,那么公众号直接拿测试公众号即可。

应用状态监控,应用内部数据监控这种有太多的解决方案了,我选择了一种比较熟悉的方式,监控显示用的是grafana,grafana数据源用的influxdb。把数据往influxdb里面插即可,influxdb提供了http接口,对大部分开发人员而言问题都不大。接下来就是将应用数据指标上报的influxdb当中,这里需要开发量,有两种方式:

  • 开发一个基础库,基础库嵌入到应用程序当中,应用程序直接写入数据到influxdb
  • 开发一个agent和一个基础库,基础库还是嵌入到应用程序当中,但是数据是上报到agent,由agent写到influxdb。

最后选择了第二种方式,第一种方式虽然只有一个基础库,但是后续如果我需要调整grafana的数据源,不用influxdb了,那所有应用都需要进行适配,采用第二种方式只有agent需要适配。而且评估了下,开发周期相差也不大。

最终整体的监控就采用这么个过程:

image

开发任务有两个,其一是开发agent,agent通过http调用influxdb,其二是开发api,让应用程序能够简单的使用,并且不能阻塞应用程序的运行,agent与api之间的通信采用的是grpc客户端流模式,整个思路应该算是很多公司都在采用的一种方式,只是个别地方可能不太一样,整体类似。

把上面的任务分解之后接下来就是一步一步把整个流程执行一遍,我分为以下几个步骤进行:

  • 先用安装好influxdb和grafana并做好配置
  • 开发agent程序和基础库,并提供接口给其他服务接入

完成以上两个个步骤之后,整个系统的监控基本上可以有效的运行了。

安装和配置influxdb,grafana

这个相对比较容易,到官网去下载运行就行。 grafana

influxdb

开发agent程序

agent就是个中间件,接收api发送过来的rpc请求,将rpc转换成http发送给influxdb。

首先是与api交互部分。 pb协议定义好接口以及接口参数,指明使用客户端流的方式,这样客户端就不需要每次发送数据都打开一条stream了。

syntax = "proto3";

package holenat.proto;

option go_package = "/proto/stat";

message SetTagReq {
  string prefix = 1;
  string tag = 2;
  int64 value = 3;
  string token = 4; // token验证
}

message SetTagReply {}

service StatService {
  rpc SetTag(stream SetTagReq) returns (SetTagReply) {}
}

agent的grpc server部分代码:


func (s *Server) SetTag(stream stat.StatService_SetTagServer) error {
	for {
		req, err := stream.Recv()
		if err != nil {
			return err
		}

		s.handleReq(req)
	}
}

func (s *Server) handleReq(req *stat.SetTagReq) {
	attrs := strings.Split(req.Tag, ".")
	s.db.SetTag(req.Prefix, attrs, req.Value)
}

func (db *InfluxDB) SetTag(prefix string, attrs []string, value int64) {
	str := prefix + "-"
	for i, attr := range attrs {
		if len(attr) > 0 {
			str = str + fmt.Sprintf(",t%d=%s", i, attr)
		}
	}
	str += fmt.Sprintf(" value=%d", value)

	br := bytes.NewBuffer([]byte(str))
	resp, err := http.Post(db.url, "application/x-www-form-urlencoded", br)
	if err != nil {
		log.Warn("post data fail: %v", err)
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode != 204 {
		log.Warn("set tag fail, status code %d", resp.StatusCode)
	}
}

因为有多个tag,可以grpc直接传tag的value数组,也可以传字符串,然后通过某种规则分割,这里通过.来进行了一次分割,field key硬编码为value,field value由接口传入。

server部分对应用是不可见的,对应用真正可见的是api层:

api层设计两个接口,一个是初始化的Init接口,另外一个是上报的接口

func Init(prefix, monitor string) {
	c, err := NewClient(&ClientConfig{
		Addr:   monitor,
		Prefix: prefix,
	})

	if err != nil {
		fmt.Println(err)
		return
	}

	cli = c
	go loop()
	go reportProfile()
}

func SetTag(tag string, value int) {
	req := &writeReq{
		tag:   tag,
		value: int64(value),
	}

	select {
	case channel <- req:
	default:
	}
}

在使用时,只需要在程序启动之初调用Init,后续程序内部数据需要监控调用SetTag,这让就可以让使用的人更加傻瓜的去用,不去深究内部细节了。

完成之后先放到一个无足轻重的应用去试一下,还不错,至少以后不用再去查数据库了。

image

最后说点现实点的问题

类似监控等应用场景,在服务器选择上,我原本考虑选择的是云服务器,但是算了下成本有点高,觉得不太值,然后看了下阿里云的轻量级服务器,在香港地区的收费还可以,一年两百八十左右,共享带宽,峰值30Mbps,每个月有1TB的流量,所以我就将上面应用部署在轻量级服务器上。

缺点是跟云服务器不同属一个VPC,所以安全性会有点问题,这个还需要自己通过iptables做白名单控制。这里不得不吐槽一下阿里云的轻量级云服务器的防火墙,这个防火墙配置页面做得很糙,没有ip配置,之后端口配置,连具体防火墙规则的备注都没有,果真是轻量级了。

ICKelin avatar Mar 17 '20 15:03 ICKelin