簡介
在做完支付系統后,我搭建了兩套監控系統。
一套是點評的CAT,主要用于代碼級的實時統計和歷史統計以及異常監控、主機監控等,如DAO、CACHE、MONGO、RPC、URL執行時間統計,應用HTTP輪詢監控等,當然CAT的功能并不僅限于此,我們只是用了很少的一部分。
還有一套日志監控系統,基于非常流行的ELK Stack,也就是ElasticSearch + Logstash + Kibana,本文主要說一說這套系統的玩法。
系統架構
整個架構還是比較簡單的,Logstash負責收集日志數據并推送到Kafka集群中做緩沖,再由另一套Logstash從Kafka集群中取出數據推入到Elasticsearch中存儲。Python程序每隔一段時間根據關鍵字查詢上一段時間是否有錯誤日志產生,如果有,就發送郵件和微信告警。
下面分別說一說各個組件使用上的一些問題。由于網上已經有很多基礎講解、權威指南的內容,我會盡量只講干貨。
Logstash
使用版本2.3.4。
Logstash(Github,Docs)用于收集日志。在file
中指定tags
和type
主要是為了數據打到ES時的搜索。sincedb_path
是記錄日志讀取位置的文件,使用sincedb_write_interval
控制寫入文件的間隔,默認是15秒。codec => multiline
用于錯誤堆棧信息的合并,比如下邊的錯誤日志,就會合并成一個消息。
<pre>
2017061904514678558975|2017-06-19 04:51:53,043|DubboServerHandler:4431-thread-1372|扣款請求異常:
java.net.SocketTimeoutException: Read timed out
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:152)
at java.net.SocketInputStream.read(SocketInputStream.java:122)
at sun.security.ssl.InputRecord.readFully(InputRecord.java:442)
at sun.security.ssl.InputRecord.read(InputRecord.java:480)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:927)
at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:884)
</pre>
貼出部分logstash的配置文件。
# 從文件收集應用日志,推送到kafka
input {
file {
tags => ["pay","site"]
path => ["/data/www/logs/pay/biz/pay-failure-0?.log","/data/www/logs/pay/biz/pay-success-0?.log"]
type => "biz"
sincedb_path => "/tmp/logstash_p_sincedb_biz"
}
file {
tags => ["pay","site"]
path => ["/data/www/logs/pay/debug/debug-0?.log"]
type => "debug"
sincedb_path => "/tmp/logstash_p_sincedb_debug"
codec => multiline {
pattern => "^[\d|\\|]"
negate => true
what => "previous"
max_lines => 500
multiline_tag => ["exception_stack"]
}
}
}
output {
kafka {
bootstrap_servers => "kafka1.host:9092,kafka2.host:9092,kafka3.host:9092"
topic_id => "pay_log_topic"
}
}
啟動方式如下,-r --reload-interval 30 表示每30秒掃描一次配置文件,如果有改動就重新加載配置文件。
nohup /usr/local/logstash-2.3.4/bin/logstash agent -l /usr/local/logstash-2.3.4/logstash_pay.log -r --reload-interval 30 -f /usr/local/logstash-2.3.4/config/pay_to_kafka.conf >/dev/null 2>&1 &
在收集日志時,也要做好日志的分類工作,哪些需要存儲,哪些不需要,哪些日志該用什么標簽區分出來,都需要考慮。
# 從kafka取日志,推送到ES
input {
kafka {
zk_connect => "kafka1.host:2181,kafka2.host:2181,kafka3.host:2181"
consumer_threads => 1
topic_id => "pay_log_topic"
# 注意同一個topic要使用同一個group_id,否則會導致重復消息(類似發布訂閱模式)
group_id => "logstash_pay"
type => "pay"
}
}
output {
elasticsearch {
hosts => ["es.host"]
# es索引格式
index => "pay-%{+YYYY.MM.dd}"
idle_flush_time => 5
workers => 2
}
}
啟動方式和收集應用日志的logstash類似。
Kafka
使用版本為2.10-0.8.2.2。
使用Kafka集群做數據緩沖,主要是考慮到網絡波動和組件重啟可能導致的服務不可用,尤其是ES和業務服務器分布在不同機房,需要走外網VPN做數據交互,網絡問題的出現概率就大大增加了。
目前我們用于日志處理的Kafka和Zookeeper使用3臺服務器,每個topic分別3 partitions、3 replications。
Kafka在使用上沒有遇到太多問題,暫略。
ElasticSearch
使用版本為2.3.5。
由于服務器資源有限,ES暫時只使用一臺服務器做數據節點。目前我們收集的日志量不是很多,每天大概20G的3000W+條日志。可以使用ES的插件Elastic HQ查看ES的內部狀態。
ES在上手使用上非常簡單,配置文件稍微修改就可以使用。
我們遇到的問題:
- 為了性能上的考慮,最好使用
bootstrap.mlockall: true
來鎖住內存避免使用swap。 - ES會占用較多的文件句柄數,在我們的系統中是5W+,所以需要調整操作系統的文件句柄數到比較大的值,官方建議65536或以上。可通過
ulimit -n 65536
或/etc/security/limits.conf
調整。 - ES會占用較多的內存,建議分配較多內存。我現在使用的參數是
-Xms12g -Xmx24g -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintClassHistogram -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:+DisableExplicitGC
。如果服務器是ES專用,可以把Xms與Xmx設置成一樣的。堆內存設置的太小,會導致頻繁GC,嚴重影響性能。GC優先使用CMS,降低GC導致的應用停頓時間。
Kibana
使用版本為4.5.4。
Kibana的作用是方便用戶和ES交互。
修改config/kibana.yml
,將ES地址填寫到elasticsearch.url
后,使用nohup bin/kibana &
啟動即可。
數據的存儲和展示都做好了,下邊就是監控的部分。
Python監控程序
使用apscheduler作為定時器,定時做監控任務。除了對錯誤日志的監控,還寫了對activemq隊列消息積壓情況、JAVA應用線程數的監控(基于jolokia)。這次只說日志監控。
from apscheduler.schedulers.blocking import BlockingScheduler
def job_monitor_es_error_pay():
try:
monitor_es_error.monitor()
except Exception, e:
handle_error('monitor_es_error_pay', e)
if __name__ == '__main__':
# 嘗試獲取鎖再執行。自己實現的分布式鎖,避免監控系統單點故障,主要使用https://github.com/glasslion/redlock
master_lock_util.get_lock_block()
# 創建阻塞型定時器
sched = BlockingScheduler()
# es 錯誤日志
es_interval = props['es_err']['interval'] # default 180
sched.add_job(job_monitor_es_error_pay, 'interval', seconds=es_interval)
sched.start()
monitor_es_error.monitor()
的具體代碼就不貼出來了,只說下邏輯:
- 獲取本次查詢的時間段,本次的開始時間為上次執行的結束時間,結束時間為當前時間。
- 確定ES索引,es的索引在每天8點才會新建(不確定其他版本是否規則相同),所以當天8點前是要查詢昨天的索引的。查詢關鍵字:
tags:exception_stack AND type:error AND @timestamp:['+str(time_range[0])+' TO '+str(time_range[1])+']'
。tags和type都是之前logstash推送時加入的標簽,error
表示錯誤日志,exception_stack
是帶有錯誤堆棧的日志。還可以通過AND -"關鍵字"
的方式排除不想查找的關鍵字。@timestamp
確定了時間段。 - 根據查詢出的結果,選擇是否報警,可選擇微信或郵件報警。郵件中將前100個錯誤的每個錯誤堆棧信息前50行打印。微信只做摘要,將錯誤信息截取發送。郵件模板使用
jinja2
- 發送郵件時,帶有kibana的url,負責人可直接訪問kibana獲取相關錯誤信息。也可以根據錯誤的requestId將所有相關日志查詢出來,便于分析。
總結
這套監控系統上線半年多了,雖然可能看起來簡陋無比,但對于我們來說,是真正從兩眼一抹黑根據用戶反饋處理問題的原始部落時代進步到了透明化近乎實時地處理和響應問題的新石器時代。不僅僅是對于用戶相關的問題,系統中很多隱患和重大問題,通常都會有一些前兆,只有更主動地發現問題,才能在越來越復雜地業務場景面前立于不敗之地。
依據這套系統修復的bug已數不勝數(誰還沒寫點bug :P),做出的系統優化也非常多。比如GC時間過長問題、支付網關http連接池問題、rpc調用瞬時異常、activemq集群阻塞不消費等等問題。
因為不屬于公司要求的項目,所有這些都是我用業余時間一點點做起來的。公司的服務器資源也有限,所有的服務器都是復用的,連監控程序也是跑在測試環境的,為了高可用,才做的分布式鎖避免單點故障。
先到這吧。