寫在前面css
從初次瞭解elastic產品到正式投入使用,拖拖拉拉的也有小半年了,剛接觸的時候看到一些帖子都是安裝教程,後來看到一些都是深刻教程,此篇文章較居中一點,總結了我在踩的一些坑和記錄一些周邊插件的使用方式、方法,便於本身後續回顧,也但願能給新用戶一些引導,少走一些彎路;核心實際上是想表達一下對rockybean和KennyW的愛,這期間很是感謝兩位的協助,在非工做日深夜排查問題屢次,正文多處採用二位給予的講解,萬分感謝。Rsyslog配置(雙打Kafka)
現有的版本是0.8,而剛開始測試的用logstash5.x須要kafka0.10(最終hangout替換logstash),因此新搭建了一組新的集羣,Rsyslog向兩個Kafka集羣分別寫數據,配置以下html
Module (load="imudp")
Module (load="omkafka")
Input (type="imudp" port="514")
Module (load="mmsequence")
$MaxMessageSize 4k
local5.none /var/log/messages
local5.none @log.domain.com:514
set $!newmsg = replace($msg,'\\x','\\u00')
template(name="kafka_topic" type="string" string="%programname%")
template(name="kafka_msg" type="string" string="%!newmsg%")
if ($syslogfacility-text == 'local5' and $syslogseverity-text == 'info') then{
action(type="omkafka" topic="kafka_topic" partitions.auto="on"
dynatopic="on" dynatopic.cachesize="1000"
confParam=["compression.codec=snappy"]
#kafka broker addr
broker=["10.10.10.1:9092","10.10.10.2:9092",]
template="kafka_msg"
errorfile="/var/log/omkafka/log_kafka_failures.log")
action(type="omkafka" topic="kafka_topic" partitions.auto="on"
dynatopic="on" dynatopic.cachesize="1000"
confParam=["compression.codec=snappy"]
#kafka broker addr
broker=["20.20.20.1:9092","20.20.20.2:9092",]
template="kafka_msg"
errorfile="/var/log/omkafka/log_kafka_failures.log")
stop
}
複製代碼
配置Nginx JSON格式日誌java
log_format json_format '{"@timestamp":"$time_iso8601",'
'"cookie_id":"$cookie_id",' #內部cookie_id
'"client_ip":"$remote_addr",'
'"remote_user":"$remote_user",'
'"request_method":"$request_method",'
'"domain":"$host",'
'"user_agent":"$http_user_agent",'
'"xff":"$http_x_forwarded_for",'
'"upstream_addr":"$upstream_addr",'
'"upstream_response_time":"$upstream_response_time",'
'"request_time":"$request_time",'
'"size":"$body_bytes_sent",'
'"idc_tag":"tjtx",'
'"cluster":"$host_pass",'
'"status":"$status",'
'"upstream_status":"$upstream_status",'
'"host":"$hostname",'
'"via":"$http_via",'
'"protocol":"$scheme",'
'"request_uri":"$request_uri",'
'"http_referer":"$http_referer"}';複製代碼
Nginx內置syslog模塊配置,而且引用剛剛定義的json日誌格式node
access_log syslog:local5:info:127.0.0.1:514:nginx_aggregation_log json_format;
#nginx_aggregation_log 這是自定義的Topic複製代碼
NginxSyslog模塊介紹
linux注:
1) UDP傳輸雖快,可是以太網(Ethernet)數據幀的長度必須在46-1500字節之間,UDP不能像TCP重組數據包,去除IP和UDP的數據包,最終可以使用只剩1472字節。若是傳輸大於這個長度的消息,並不會想UDP自己同樣直接丟棄,只是會損壞源數據格式,截斷超過限制字節之外的數據;
2) 對於Nginx日誌來講,只要不保留POST數據,基本一條消息不會超過限制字節,我在NginxSyslog介紹中沒看到支持TCP,用lua腳本實現的TCP方式傳輸,可是看了不少帖子都不建議在Nginx中用TCP日誌傳輸。就是由於TCP傳輸可靠,但像網絡抖動、傳輸異常,可能會不停的重試屢次或等待,直接影響這條請求,也直接影響到了用戶;
3) 消息超過了UDP傳輸限制怎麼辦,我這目前是保留一條消息的重要字段,如上述的json_format的格式,將 request_uri、http_referer等可能會較大的字段放到最後,若是真的發現消息不完整,直接丟棄http_referer,取request_uri問號前的內容;(在logstash或hangout中filters實現,具體配置詳見下文Hangout-filters)ios
Kafka監控插件:kafka-monitor 和 kafka-managernginx
注:
目前咱們這kakfa集羣是kafka_2.10-0.8.1.1版本,可是logstash5.x對kafka有版本要求>0.10版本。後來採用hangout,更換了幾個jar包解決了此問題git
模仿logstash作的一個應用,功能沒有logstash多,可是基本使用都有了,java編寫,性能能夠翻好幾倍,用到的功能就是從kafka訂閱消息,作一些簡單的過濾,而後寫入ES;目前hangout部署到2臺服務器上,每一個進程開8G內存,CPU在60-70左右;github
inputs:
- Kafka:
topic:
nginx_aggregation_log: 32
codec:
json
consumer_settings:
group.id: es-nginx_aggregation_log
zookeeper.connect: "10.10.10.1:2181,10.10.10.2:2181"
auto.commit.interval.ms: "20000"
socket.receive.buffer.bytes: "1048576"
fetch.message.max.bytes: "1048576"
num.consumer.fetchers: "1"
filters:
- Filters:
if:
- '<#if message??>true</#if>'
#若是不是完整的JSON,會出現message,則走此邏輯
filters:
- Grok:
match:
- '(?<msg>{"@timestamp":.*"request_uri":([^\?]+)\?)'
#正則匹配@timestamp開始到request_uri後邊的第一個?截止
- Gsub:
fields:
msg: ['$','"}']
#補全符號,完整新的JSON格式
- Json:
field: msg
remove_fields: ['message']
#幹掉錯誤的數據
- Convert:
fields:
request_time:
to: float
remove_if_fail: true
upstream_response_time:
to: float
remove_if_fail: true
size:
to: integer
remove_if_fail: true
- GeoIP2:
source: client_ip
database: '/opt/soft/hangout/etc/other/GeoLite2-City.mmdb'
- Json:
field: geoip
- Remove:
fields:
- msg
- Add:
fields:
request_url: '<#assign a=request_uri?split("?")>${a[0]}'
#request_uri這個term的cardinality很高,因此?前用於聚合,原有的用於搜索
if:
- '<#if request_uri??>true</#if>'
outputs:
- Elasticsearch:
cluster: es-nginx
timezone: "Asia/Shanghai"
hosts: "10.10.10.1:9300,10.10.10.2:9300"
index: 'hangout-nginx_aggregation_log-%{+YYYY.MM.dd}'
複製代碼
topic: nginx_aggregation_log: 32,不管是logstash仍是hangout都有這個概念,這個32表明須要創建多少子線程去kafka讀取數據,數量最好與Partition相等,若是少於Partition,會一個線程同時去2個Partition讀取消息,若大於Partition則會有不工做的進程web
CPU:32C,內存:128G ,硬盤:STAT 6T * 12,網卡:萬兆
複製代碼
【系統】: Centos7 內核3.10
【JDK】: 1.8.0_66/31G (聽說此版本JDK有BUG,請安裝最新JDK)
【系統參數修改1】: vm.swappiness=1 [下降對硬盤的緩存]
【系統參數修改2】: vm.max_map_count=262144 [Elasticsearch針對各類文件使用NioFS和MMapFS的混合。以便有足夠的虛擬內存可用於mmapped文件] 複製代碼
cluster.name: es-nginx
node.name: 10.10.10.1
#爲後期冷熱數據使用
node.attr.rack_id: hdd
path.data: /data
path.logs: /opt/logs/elasticsearch/
network.host: 0.0.0.0
http.port: 9200
#設置新節點被啓動時可以發現的主節點列表
discovery.zen.ping.unicast.hosts: ["10.10.10.1","10.10.10.2","10.10.10.3"]
#防止腦裂(n/2+1)
discovery.zen.minimum_master_nodes: 2
node.master: true
node.data: false複製代碼
剛剛開始測試ES的第一個版本是ES5.3,先搞了3臺機器,每一個機器一個節點,配置是master和data共同提供服務,高可用架構集羣搭建完成,可是寫入性能特別差,cpu使用在20-30%,少許io.wait,下圖是當時3w左右的性能圖當時以爲既然ES硬件很空閒必定是logstash出問題了,查看logstash確實有很嚴重的Full GC,開始從2臺服務器擴至4臺服務器,後來發現無果,期間各類調整ES的shard的數量都沒效果,又懷疑kafka性能,從二、四、六、8...64分區依舊無果。當時這個坑可爬了一段時間,後來在Google的遊蕩中無心中看到帖子說,不要將master和data都啓用,而後我照着作了改變,master單點,data兩臺,問題搞定,效果圖找不到了,起碼翻倍是有的;
[Master除了網卡,其餘沒什麼消耗]
複製代碼
因shard數量、字段類型、其餘設置等都是都是在建立時生成,因此要提早建立好相應的模板,便於規範管理和引用,下面針對shard和aliases作的一些設置,以下:
{
"template": "agg-nginx-*",
"aliases": {
"agg-nginx": {}
},
"settings": {
"number_of_shards": 4,
"number_of_replicas": 1,
"index.routing.allocation.include.rack_id": "ssd"
}複製代碼
經過上述配置PUT到 _template/ur_name下在分片上的定義已經成功,可是像agg-nginx-
和test-agg-test-這樣的2個索引名字,即便你建立了另外一個"template": "agg-nginx-test-*"的模板依舊都匹配第一個,固然換名字最簡單有效,在template的order的是專門解決這個問題的。默認建立"order": "0",值越高優先級越高,因此在想要先匹配的將order值調高便可
ES的mapping很是相似於靜態語言中的數據類型:聲明一個變量爲int類型的變量, 之後這個變量都只能存儲int類型的數據。一樣的, 一個number類型的mapping字段只能存儲number類型的數據。同語言的數據類型相比,mapping還有一些其餘的含義,mapping不只告訴ES一個field中是什麼類型的值, 它還告訴ES如何索引數據以及數據是否能被搜索到
下列是一個刪減版的mapping複製代碼
"mappings": {
"ngx_log": {
"_all": {
"enabled": false
},
"properties": {
"@timestamp": {
"type": "date"
},
"client_ip": {
"type": "ip"
},
"domain": {
"type": "keyword"
},
"geoip": {
"properties": {
"city_name": {
"type": "keyword"
},
"country_name": {
"type": "keyword"
},
"latitude": {
"type": "float"
},
"location": {
"type": "geo_point"
},
"longitude": {
"type": "float"
},
}
},
"request_time": {
"type": "float"
},
"request_url": {
"type": "keyword"
},
"status": {
"type": "keyword"
ype": "keyword" }, } } }複製代碼
_all字段
該_all字段是一個特殊的catch-all字段,它將全部其餘字段的值鏈接成一個大字符串,使用空格做爲分隔符,而後對其進行分析和索引,但不存儲。也就是說它能被查詢,但不能被取回顯示。由於Nginx每一個Key對應的value都是提早定義好的,因此不用全文查詢,不須要開啓_all字段,另外也節省了一半的存儲空間
默認的text類型
上邊這英文有點多,其實簡單理解就是不分詞,你就最好別用text了,並且Text類型也會相應的多佔用空間,依照上述,數據主要是日誌分析,每條數據的格式已經很明確,主要用於日誌分析,因此不須要分詞。像一些全部引擎的業務更適合須要分詞;
[摘取部分蘇若年博客內容]
1)分片算法:
shard = hash(routing) % number_of_primary_shards
routing值是一個任意字符串,它默認是_id但也能夠自定義,這個routing字符串經過哈希函數生成一個數字,而後除以主切片的數量獲得一個餘數(remainder),餘數的範圍永遠是0到number_of_primary_shards - 1,這個數字就是特定文檔所在的分片。
這也解釋了爲何主切片的數量只能在建立索引時定義且不能修改:若是主切片的數量在將來改變了,全部先前的路由值就失效了,文檔也就永遠找不到了。
全部的文檔API(get、index、delete、bulk、update、mget)都接收一個routing參數,它用來自定義文檔到分片的映射。自定義路由值能夠確保全部相關文檔.好比用戶的文章,按照用戶帳號路由,就能夠實現屬於同一用戶的文檔被保存在同一分片上。
2)分片與副本交互:
新建、索引和刪除請求都是寫(write)操做,它們必須在主分片上成功完成才能複製到相關的複製分片上,下面咱們羅列在主分片和複製分片上成功新建、索引或刪除一個文檔必要的順序步驟:
一、客戶端給Node 1發送新建、索引或刪除請求。
二、節點使用文檔的_id肯定文檔屬於分片0。它轉發請求到Node 3,分片0位於這個節點上。
三、Node 3在主分片上執行請求,若是成功,它轉發請求到相應的位於Node 1和Node 2的複製節點上。當全部的複製節點報告成功,Node 3報告成功到請求的節點,請求的節點再報告給客戶端。
客戶端接收到成功響應的時候,文檔的修改已經被應用於主分片和全部的複製分片。你的修改生效了。
一個索引要分多少片?何時該擴容?
取決於硬件和你對響應速度的要求,通常來講一個shard的數據量控制在一、2千萬的級別,速度都還好,過億會比較緩慢。 可是任何事物都有兩面,shard劃分比較小,必然數量就比較多。 在用戶作一、2天數據搜索的時候可能還好,若是搜更長時間的數據,一次搜索須要並行搜索的shard就比較多。若是節點數量有限,就會比較吃力,須要擴充更多的節點
聽說是優化之王道,常常拿城市舉的例子,好比說我想看下網站的北京pv是多少,若是按照默認hash邏輯,必定要全shard掃描,而後聚合結果,可是若是提早設置好routing,好比說指定城市字段作hash計算,routing值同樣的放到特定幾個分片,這樣查起來也不須要全shard掃了;這樣弊端就是會形成shard大小不均,因此routing優化須要花一些功夫來觀察、測試;目前kibana還不支持routing查詢,因此目前在kibana上尚未使用routing,這是優化的重點因此先記錄下來。後續個人想法是,像nginx日誌的域名的字段都是英文字母,針對首字母作下routing,當想看某一個域名時不在全盤掃,查詢優化會有明顯效果,後續有結果在與你們分享;
另外hangout開始對routing的支持,後來在GitHub提了一個小issue,很快就加上了,點個贊;
Kibana是一個開源的分析與可視化平臺,設計出來用於和Elasticsearch一塊兒使用的。你能夠用kibana搜索、查看、交互存放在Elasticsearch索引裏的數據,使用各類不一樣的圖表、表格、地圖等kibana可以很輕易地展現高級數據分析與可視化。
先介紹下幾塊功能:
(╯﹏╰)吐槽一下,用SF寫到如今,Chrome都快無法用了,打完字一會才顯示出來
上圖中環比圖是Timelion完成的,其餘都是Visualize的功能
Timelion語法:
.es(index=index1,timefield=@timestamp).label('Today').title(QPS).color(#1E90FF),
.es(offset=-24h,index=index2,timefield=@timestamp).label('Yesterday').lines(fill=1,width=0.5).color(gray)複製代碼
核心問題:查詢慢、查詢15Min數據都超時
mapping字段優化:
主要像上述的mapping介紹,作類型優化,不分詞的keyword,數學計算的改爲整型or浮點等
status這個字段的類型,value通常都是200、30一、30二、40四、502,最多估計也就幾百個,像這樣的字段就不適合作long,long類型的索引是爲範圍查找優化的,用的是二叉樹這樣的索引,適合值範圍比較大的字段,好比body_size,可能最大值和最小值相差不少,而用keyword索引是倒排,只用存放一個全部status的詞典,因此keyword更適合
shard數量調整:
開始主分片數量是每臺機器1個,副本1(20shard x 1replicas),每一個shard已經達到將近200G,看這個量級已經超過官方建議值30~50G之間(具體要根據實際狀況測試爲準)。因而開始將數量翻倍40shard * 1replicas,調整後查詢並無明顯改善,對寫入沒有什麼改變,繼續double,依舊沒效並且分片越多,寫入的時候消耗的CPU就越高
若是過分分配shard,就增大了Lucene在合併分片查詢結果時的複雜度,從而增大了耗時,在新建索引時,更是一筆大的開銷。
通常來講,剛開始的時候儘可能少分片爲佳, 只有到一個分片上數據太多,單次查詢太慢在考慮加分片。
加分片也有負面做用,提升了併發量,隨之開銷就會越大,更多的shard通常也伴隨更多的segment文件。若是說節點數量沒變,每一個節點上會有更多的小文件,搜索的時候併發量是高了,前提是在磁盤io和cpu都還能應付的過來的時候,速度纔會快
拆分索引
更改shard已經得不到顯著效果,因而從拆索引下手。數據都是Nginx日誌,從源頭拆分沒什麼好的方法,因而從hangout這層開始作處理,很簡單就是將域名的第一個字母取出來,寫入相應的索引時候帶過去,例如:nginx-log-{首字母},這樣一拆,一下建立26個索引(shard20 * 1replicas),CPU立馬load 30+,負載直接上來,而後ES數據還跟不上,拒絕了不少內容,以下圖,最終仍是無果而了結此方案
上述的索引拆分是比較傻瓜式,首先已知的問題就是可能A開頭的域名很大,其餘很小就是很不均勻
迴歸HDD
通過SSD的折騰,查到了kibanaBug問題,其實SSD並未發現其餘的性能亮點,通過與KennyW的交流,他們本身有作過在SSD和多塊HDD的盤對比,寫入量並沒有明顯提高,因此在磁盤並非真正的瓶頸,查詢方面SSD明顯提升。可是SSD的高額付出換來那麼幾秒鐘的意義不大。相對,對一些公司的搜索業務,數據量級小,還有像一些監控業務,要求實時性很是高,對SSD是很好的選擇,速度快,容量不須要太多,也比較經濟實惠
[KennyW經驗指導]
當作複雜計算,bucket不少的時候主要消耗的是CPU和內存,磁盤並非瓶頸,hdd和ssd在咱們實際用下來,感受在大數據場景分別不大,ssd優點在大量的隨機磁盤io,ES自己作了不少優化,讓數據變成順序的磁盤訪問,並且搜索過的數據塊,ES都能利用文件系統緩存加速,因此即便使用hdd,可能第一次搜索磁盤訪問會帶來額外的幾秒耗時,但屢次執行同一個搜索時,後面幾回幾乎沒什麼磁盤io開銷,速度會有明顯提高
收尾
作到目前這些調整,如今一天小於1T的索引任何查詢都沒有問題,查詢在10s左右返回全部數據(上圖kibana展現),可是那個一天4-5T的索引仍是有問題,一查詢IO就跑滿,到30s直接超時了,先說下IO跑滿的問題吧,問題是request_uri臺過於分散,聚合出現問題如圖:15分鐘就出現了2000多萬不一樣值,若是長時間計算,不可想象的恐怖
【KennyW指導】
request_uri 會產生大量的磁盤IO。 ES作terms聚合的時候,爲了節省內存,不會將全部term的內容直接讀出來作bucket,由於有些term的內容可能很長,會比較耗費內存。 因此他會藉助一種叫作oridinals的數據結構, 這種數據結構相似這樣
1 abc
2 efg
3 hfa
.............一個該字段全部不一樣值的順序列表。 作分桶聚合的時候,只須要拿這個順序數字作key就能夠了,等聚合出結果,再經過這個key查ordinals表,獲得實際的key值填充結果。 可是這個ordinals是隨着segment 文件生成的,每一個segment文件的ordinals順序可能不同。 所以在聚合的時候,須要一個全局的global ordinals。 這個數據結構是在聚合的時候,實時生成後緩存在內存裏的。 若是某個字段的不一樣值很是多,計算價值很是的昂貴,那麼這個構造過程就很是的緩慢,除了大量的磁盤IO消耗,還會有很大的內存消耗。
下圖是關掉這個有問題的visualize先後對比圖,雖然不快,可是latency降了不少
後來對uri的?後邊的參數所有丟棄,像這樣的問題只能減小而後作聚合使用,原有數據作搜索使用,可是因爲數據太大,作複雜計算仍是會超時30s,這種狀況只能是下降team的cardinality,或者加分片、加機器或者拆索引了,因此對kibana的超時時間作了一點調整,還有一些周邊小的修改以下
1)kibana默認的30s超時改爲2min,kibana.yml中修改elasticsearch.requestTimeout: 120000
2)kibana默認地圖使用高德,kibana.yml中新增tilemap.url: 'http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}'
3)結合cerebro插件,高效管理索引
到這裏基本的一期項目算是結束,雖然部分查詢並無迅速返回,可是基本數據均可以展現,後續會關注幾個點繼續深刻優化和調整,有結果在與你們分享
首先在這裏仍是先要感謝rockybean和KennyW的大力支持。
對本身的總結就是,對ES經驗太少,踩了不少沒必要要的坑,另外就是沒有好好統一閱讀下官網文檔,這樣極其影響效率與進度,好多時候也會一籌莫展,不知從何下手。
第一次寫技術帖,並且時間稍緊,有哪些地方寫的很差或敏感煩請指出,我會及時更正。但願這篇文章,能給予像我這樣的小白用戶少踩一點坑,多一點愛。文章有點長,你們以爲做者總結的還能夠,能夠關注一下做者的公衆號《Java技術zhai》,公衆號聊的不只僅是Java技術知識,還有面試等乾貨,後期還有大量架構乾貨。你們一塊兒關注吧!關注技術zhai,你會了解的更多..............