1. 什么是性能监控系统
这里说的性能监控系统,主要侧重点是监控应用系统的性能。 说直白点就是每个业务(例如注册,登录)的请求响应时间,请求次数等信息。 操作系统的监控不是这里的重点,因为业界已经有许多相当成熟的基于Linux的运维系统。 操作系统的运维和应用系统的运维是两码事,应用系统的运维相对来说没有这么多选择。 而对于任何线上系统来说,运维监控系统又是必不可少的。 如果你是在大公司,一般会选择开发自己的运维系统,而对于中小团队,因为人力有限,大多会采用开源的解决方案。
我在这里要介绍的就是使用StatsD,Graphite, Grafana, Kamon搭建监控系统的方案。 这里的Kamon只适用于基于JVM的项目,另外,如果你的项目是基于Play或Akka或Spray,可以做到不写代码实现监控。 因为Kamon对这几个框架提供了AspectJ支持,在类加载的时候插入代码为你完成记录。 其他情况你需要调用Kamon API来进行数据记录,这也非常的简单。
2. 监控系统UI
介绍搭建步骤之前,先来看一看搭好后的界面。 转自(https://github.com/kamon-io/docker-grafana-graphite)
这里包含4部分的图表。
- Actor Metrics是Akka Actor的统计图表。因为我的项目中没有直接使用Akka,所以暂时忽略这一部分内容。
- Trace Metrics是业务请求的统计图表。例如每个请求的响应时间,以及某个时间段内各请求数量的统计对比。
- OS Metrics一看就知道是操作系统的统计图表了。
- JVM Metrics是JVM的统计图表。
3. StatsD, Graphite, Grafana, Kamon简介
简单介绍一下这四个开源项目,因为都在Github上就不贴链接了,
已经对这几个项目很熟悉的可以略过。
1. StatsD
StatsD是一个用于记录统计信息的守护进程。使用NodeJS开发,提供各种语言的客户端API。
2. Graphite
使用Python开发,分为三个子项目
- carbon 守护进程,接收StatsD发送过来的原始统计数据。
- whisper 用来存储统计数据的库。
- graphite webapp 用来图形化展示统计数据的web项目。
3. Grafana
使用Go开发,可以直接在界面上设计统计图表。
之前看到就是使用Grafana制作的界面。
4. Kamon
一套类库用来记录统计数据,使用Scala开发,提供Java和Scala API。
除了提供API,还结合AspectJ对一些框架提供自动记录的功能,当然性能上会有损耗。
整体流程:业务系统调用Kamon的Api记录数据,Kamon将数据发送给StatsD,
StatsD定期(默认10s)将数据汇总发送到Graphite,
当用户访问Grafana界面的时候,Grafana调用Graphite接口读取数据绘制成图形展示给用户。
4.搭建环境
因为用到了多个项目,并且每个项目都基于不同语言,所以安装过程肯定不会很简单。
使用docker的朋友直接参考这个项目(https://github.com/kamon-io/docker-grafana-graphite)
用docker镜像会方便很多。
如果不用docker,可以参考我的安装步骤。
以下安装是基于Ubuntu14.04,如果是CentOS或其他系统可能某些步骤会不一样。
另外因为测试的时候是在开发机上,所以下面安装的时候没有使用独立用户和权限。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
1.安装需要的软件sudo apt-get -y install software-properties-commonsudo add-apt-repository -y ppa:chris-lea/node.jssudo apt-get -y updatesudo apt-get -y install python-django-tagging python-simplejson python-memcache python-ldap python-cairo python-pysqlite2 python-support \ python-pip gunicorn nginx-light nodejs wget curl build-essential python-devsudo pip install Twisted==11.1.0sudo pip install Django==1.5# Install Elasticsearchcd ~ && wget https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.3.2.debcd ~ && sudo dpkg -i elasticsearch-1.3.2.deb && rm elasticsearch-1.3.2.deb2.从源码安装StatsD, Graphite, Grafana# Checkout the stable branches of Graphite, Carbon and Whisper and install from theremkdir ~/srcgit clone https://github.com/kamon-io/docker-grafana-graphite.git ~/src/docker-grafana-graphitegit clone https://github.com/graphite-project/whisper.git ~/src/whisper &&\cd ~/src/whisper &&\git checkout 0.9.x &&\sudo python setup.py installgit clone https://github.com/graphite-project/carbon.git ~/src/carbon &&\cd ~/src/carbon &&\git checkout 0.9.x &&\sudo python setup.py installgit clone https://github.com/graphite-project/graphite-web.git ~/src/graphite-web &&\cd ~/src/graphite-web &&\git checkout 0.9.x &&\sudo python setup.py install# Install StatsDgit clone https://github.com/etsy/statsd.git ~/src/statsd &&\cd ~/src/statsd &&\git checkout v0.7.2# Install Grafanamkdir ~/src/grafanawget http://grafanarel.s3.amazonaws.com/grafana-1.9.1.tar.gz -O ~/src/grafana.tar.gz &&\ cd ~/src/ && tar -xzf ~/src/grafana.tar.gz && mv ~/src/grafana-1.9.1 ~/src/grafana && cd - &&\ rm ~/src/grafana.tar.gz3.修改配置# Configure Elasticsearchsudo mkdir -p /tmp/elasticsearch# Confiure StatsDcp ~/src/docker-grafana-graphite/statsd/config.js ~/src/statsd/config.js# Configure Whisper, Carbon and Graphite-Websudo cp ~/src/docker-grafana-graphite/graphite/initial_data.json /opt/graphite/webapp/graphite/sudo cp ~/src/docker-grafana-graphite/graphite/local_settings.py /opt/graphite/webapp/graphite/# 此时要 sudo vi /opt/graphite/webapp/graphite/local_settings.py, 把TimeZone改为Asia/Shanghaisudo cp ~/src/docker-grafana-graphite/graphite/carbon.conf /opt/graphite/confsudo cp ~/src/docker-grafana-graphite/graphite/storage-aggregation.conf /opt/graphite/confsudo cp ~/src/docker-grafana-graphite/graphite/storage-schemas.conf /opt/graphite/confsudo mkdir -p /opt/graphite/storage/whispersudo touch /opt/graphite/storage/graphite.db /opt/graphite/storage/indexsudo chmod 0775 /opt/graphite/storage /opt/graphite/storage/whispersudo chmod 0664 /opt/graphite/storage/graphite.dbcd /opt/graphite/webapp/graphite && sudo python manage.py syncdb --noinput# Configure Grafanacp ~/src/docker-grafana-graphite/grafana/config.js ~/src/grafana/config.js# Add the default dashboardsmkdir ~/src/dashboardscp ~/src/docker-grafana-graphite/grafana/dashboards/* ~/src/dashboards/# Configure nginx将下面的内容加入nginx.conf server { listen 80 default_server; server_name _; location / { # !!! change me 这里改成你的目录 !!! root /home/liubin/src/grafana; index index.html; } location /graphite/ { proxy_pass http://127.0.0.1:8000/; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-Host $host; proxy_set_header Host $host; client_max_body_size 10m; client_body_buffer_size 128k; proxy_connect_timeout 90; proxy_send_timeout 90; proxy_read_timeout 90; proxy_buffer_size 4k; proxy_buffers 4 32k; proxy_busy_buffers_size 64k; proxy_temp_file_write_size 64k; add_header Access-Control-Allow-Origin "*"; add_header Access-Control-Allow-Methods "GET, OPTIONS"; add_header Access-Control-Allow-Headers "origin, authorization, accept"; } location /elasticsearch/ { proxy_pass http://127.0.0.1:9200/; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-Host $host; proxy_set_header Host $host; client_max_body_size 10m; client_body_buffer_size 128k; proxy_connect_timeout 90; proxy_send_timeout 90; proxy_read_timeout 90; proxy_buffer_size 4k; proxy_buffers 4 32k; proxy_busy_buffers_size 64k; proxy_temp_file_write_size 64k; } } server { listen 81 default_server; server_name _; open_log_file_cache max=1000 inactive=20s min_uses=2 valid=1m; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-Host $host; proxy_set_header Host $host; client_max_body_size 10m; client_body_buffer_size 128k; proxy_connect_timeout 90; proxy_send_timeout 90; proxy_read_timeout 90; proxy_buffer_size 4k; proxy_buffers 4 32k; proxy_busy_buffers_size 64k; proxy_temp_file_write_size 64k; } add_header Access-Control-Allow-Origin "*"; add_header Access-Control-Allow-Methods "GET, OPTIONS"; add_header Access-Control-Allow-Headers "origin, authorization, accept"; location /content { alias /opt/graphite/webapp/content; } location /media { alias /usr/share/pyshared/django/contrib/admin/media; } }4.启动export GRAPHITE_STORAGE_DIR='/opt/graphite/storage'export GRAPHITE_CONF_DIR='/opt/graphite/conf'# run nginxsudo /etc/init.d/nginx restart# run carbonsudo /opt/graphite/bin/carbon-cache.py --debug start# run graphite-webexport PYTHONPATH='/opt/graphite/webapp'cd /opt/graphite/webappsudo /usr/bin/gunicorn_django -b127.0.0.1:8000 -w2 graphite/settings.py# run StatsDsudo /usr/bin/node ~/src/statsd/stats.js ~/src/statsd/config.js# run elasticsearchsudo /etc/init.d/elasticsearch start# run grafanacd ~/src/dashboardssudo /usr/bin/node dashboard-loader.js system-metrics.json welcome.json
安装完成后,可以尝试访问(http://127.0.0.1),如果出现之前的界面并且没有报错,就代表安装成功了。
5. 配置项目
主要是引入Kamon依赖。
因为我们的项目基于Play,所以直接使用了Kamon-Play依赖。
1.修改build.sbt
123456789
val kamonVersion = "0.4.0"//...val dependencies = Seq( "io.kamon" %% "kamon-core" % kamonVersion, "io.kamon" %% "kamon-statsd" % kamonVersion, "io.kamon" %% "kamon-play" % kamonVersion, "io.kamon" %% "kamon-system-metrics" % kamonVersion, "org.aspectj" % "aspectjweaver" % "1.8.1")
2.修改application.conf
123456789101112131415161718192021222324252627282930313233343536373839404142434445
akka { extensions = ["kamon.statsd.StatsD", "kamon.system.SystemMetrics"]}kamon { metric { tick-interval = 1 second } statsd { # Hostname and port in which your StatsD is running. Remember that StatsD packets are sent using UDP and # setting unreachable hosts and/or not open ports wont be warned by the Kamon, your data wont go anywhere. hostname = "127.0.0.1" port = 8125 # Interval between metrics data flushes to StatsD. It's value must be equal or greater than the # kamon.metrics.tick-interval setting. flush-interval = 1 second # Max packet size for UDP metrics data sent to StatsD. max-packet-size = 1024 bytes # Subscription patterns used to select which metrics will be pushed to StatsD. Note that first, metrics # collection for your desired entities must be activated under the kamon.metrics.filters settings. includes { actor = [ "*" ] trace = [ "*" ] dispatcher = [ "*" ] } simple-metric-key-generator { # Application prefix for all metrics pushed to StatsD. The default namespacing scheme for metrics follows # this pattern: # application.host.entity.entity-name.metric-name # !!! 这里改成项目名 !!! application = "sk-shop" } } play { include-trace-token-header = true trace-token-header-name = "X-Trace-Token" }}
3.启动项目
play运行时环境分为dev环境和prod环境。 因为Kamon-Play使用AspectJ在类加载加载的时候进行织入,AspectJ会使用自己的类加载器, 而运行在dev环境的Play项目因为要实现热加载,所以dev环境不能使用AspectJ。 prod一般会会使用dist命令将项目打包,可用以下方式启动(假设项目名是shop,并且已cd进入打包后的目录):
1
./bin/shop -J-javaagent:lib/org.aspectj.aspectjweaver-1.8.1.jar
启动之后随便访问你项目几个页面,然后访问127.0.0.1,如果一切正常就可以看到数据了。
6.总结
恭喜,如果做到这一步,你就已经初步的搭好你们运维系统的架子了。 如果你的应用比较简单,对性能要求也不高,到这一步就可以结束了。 不过,大多数的应用都对统计数据有更进一步的分析需求,例如绘制响应时间超过某一阀值(例如100ms)的饼状图, 或者绘制日注册人数的直方图。 这就需要你手动调用Kamon的API来记录数据了,不过这相当的简单。 如果需要设计其他的Grafana图表,需要对Graphite的函数比较熟悉。
另外,如果应用对性能很敏感,不推荐使用AspectJ。因为LTW(Load Time Weaving)会有一些性能损耗。 我们的项目最开始是使用的Kamon-Play和Kamon-Akka,不过后来测试发现响应时间平均要增加10%-20%, 现在已经改成直接调用Kamon API完成记录。
Enjoy!
7.附启动和停止脚本
1.启动脚本 start_stats.sh
1234567891011
#!/bin/shexport GRAPHITE_STORAGE_DIR='/opt/graphite/storage'export GRAPHITE_CONF_DIR='/opt/graphite/conf'nohup /opt/graphite/bin/carbon-cache.py --debug start > ~/logs/kamon/carbon.out 2>&1 &export PYTHONPATH='/opt/graphite/webapp'nohup /usr/bin/gunicorn_django -b127.0.0.1:8000 -w2 /opt/graphite/webapp/graphite/settings.py > ~/logs/kamon/graphite.out 2>&1 &nohup /usr/bin/node ~/src/statsd/stats.js ~/src/statsd/config.js > ~/logs/kamon/graphite.out 2>&1 &/etc/init.d/elasticsearch startcd ~/src/dashboardsnohup /usr/bin/node dashboard-loader.js system-metrics.json welcome.json > ~/logs/kamon/grafana.out 2>&1 &
2.停止脚本 stop_stats.sh
1234567
#!/bin/shpkill carbonpkill gunicorn_djangopkill statsd/etc/init.d/elasticsearch stopkill $(ps aux | grep 'node dashboard-loader.js' | awk '{print $2}')