Skip to content

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

Description

@ICKelin

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

在开发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配置,之后端口配置,连具体防火墙规则的备注都没有,果真是轻量级了。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions