外賣公司如何匹配騎手和訂單?淘寶如何進行商品推薦?或者讀者興趣匹配?還有海量數據存儲搜索、實時日誌分析、應用程序監控等場景,Elasticsearch或許能夠提供一些思路,做爲業界最具影響力的海量搜索與分析產品,搜索軟件公司 Elastic 上市了!首日市值翻倍!Elastic 從小工具「逆襲」成爲上市公司,依靠其技術影響者衆多企業,並促進整個行業發展的模式變革,向衆多渴望創業的程序員證實了一個道理:技術創業是可行的,而且有着良好的前景。你要不要試試呢?java
準備:node
Idea2019.03/Gradle5.6.2/JDK11.0.4/RHEL7.6/VMware15Pro/Lombok0.27/logback1.2.3/SpringBoot2.2.0RELEASE/ElasticSearch7.2.0/LogStash7.2.0/Kibana7.2.0/NodeJs10.14.2/npm6.4.1/Git2.18.0linux
難度:新手--戰士--老兵--大師git
目標:程序員
1.Logback使用複習github
2.Linux下ELK框架搭建web
3.Springboot整合ELK實現海量日誌處理框架spring
4.Springboot下使用ES的APIshell
步驟:數據庫
爲了碰見各類問題,同時保持時效性,我儘可能使用最新的軟件版本。代碼地址:其中的day21,https://github.com/xiexiaobiao/dubbo-project.git
Part1 Linux下的ELK
1.先介紹下ELK套件:
其實:ES能夠用做文檔型存儲,相似MongoDB,適用於非事務型分佈式存儲場景。API十分豐富,但也存在必定的難度和複雜度。
典型的 ELK 套件方案:
2.Linux虛擬機的安裝、網絡、文件共享、YUM安裝見我下篇,或者網搜,想必進入這個文章的linux也該略有基礎了。
3.我這裏 ELK 三者所有安裝在一臺Linux虛擬機(IP:192.168.1.204)上,注意下載的ELK版本要一致,目前最新爲V7.4.2,但下載實在蝸牛速度,只好先用點已有的舊貨上場,抱歉!
4.開始ES的安裝:下載elasticsearch-7.2.0-linux-x86_64.tar.gz,放/usr/elastic下,並解壓,ES不能使用root用戶啓動,會提示錯誤!
切換爲普通用戶,並將文件主更新爲普通用戶,再啓動:
[root@localhost ~]# chown -Rv biao /usr/elastic/
[biao@localhost usr]$ ./elastic/elasticsearch-7.2.0/bin/elasticsearch
5.首次啓動測試:
[root@localhost ~]# curl localhost:9200
[root@localhost ~]# curl localhost:9300
6.默認狀況下,ES 只容許本機訪問,若是須要遠程訪問,能夠修改 ES 安裝目錄的config/elasticsearch.yml
文件,去掉network.host
的註釋,並將它的值改爲所在OS的IP:192.168.1.204,而後從新啓動 ES。
[root@localhost ~]# vim /usr/elastic/elasticsearch-7.2.0/config/elasticsearch.yml
若是須要從window主機訪問,注意打開Linux相應的端口或直接關閉防火牆, URL訪問:http://192.168.1.204:9200/
再次啓動出現錯誤,提示有3個問題,各個擊破!
每一個進程最大同時打開文件數過小:
[root@localhost usr]# sysctl -w vm.max_map_count=262144
vm.max_map_count = 262144
ulimit 用於限制 shell 啓動進程所佔用的資源:
[root@localhost usr]# vim /etc/security/limits.conf
查看設置後的值:
[root@localhost usr]# ulimit -Hn
[root@localhost usr]# ulimit -Sn
最後設置一個seed_host,見步驟6中的第一圖,按Ctrl+c退出。
7.開始安裝elasticsearch-head:一款ES集羣可視化管理工具,可直接操做ES的數據,這也太野了吧,生產中必需要加以限制!這個工具備多種方式安裝,好比doker/plugin/npm等,因linux上環境欠缺,我就直接在window上使用npm安裝了(window上先安裝node.js環境便可使用npm),這實際上是將elasticsearch-head獨立運行,參考後面的(整合ELK總體目標架構圖):
D盤根目錄下,使用git bash命令,下載源碼:
git clone git://github.com/mobz/elasticsearch-head.git
下載源碼完成後CMD命令行操做:
C:\Users\KOOL>D: D:\>cd D:\elasticsearch-head D:\elasticsearch-head>npm install -g cnpm --registry=https://registry.npm.taobao.org D:\elasticsearch-head>npm install D:\elasticsearch-head>npm run start
以下圖即爲安裝成功!
8.訪問:http://localhost:9100/
輸入ES的 IP+port --> connect, 若是此時顯示空白,請先使用 http://192.168.1.204:9200/
測試確保外部能夠鏈接ES,而後查看:
便可確認爲跨域問題,需修改ES配置文件elasticsearch.yml
,在文件末尾加入如下配置,注意冒號後的空格!
9.再重啓ES,鏈接ES端,能夠發現ES對logstash/kibana都作了存儲,果真是自家的,特殊照顧,前綴有點號區分:
查看indices信息,如下爲已經啓動了Logstash和Kibana的狀態:
node信息:
查看shard信息:
10.開始Logstash安裝:
下載文件logstash-7.2.0.tar.gz,略,放/usr/logstash下,解壓,測試logstash啓動是否正常:
[root@localhost logstash]cd logstash-7.2.0
[root@localhost logstash-7.2.0]# ./bin/logstash -e 'input { stdin { } } output { stdout {} }'
啓動後,輸入hello world,以下則成功!ctrl+D退出。
另外,能夠下載測試數據作測試:
[root@localhost logstash-7.2.0]# wget http://files.grouplens.org/datasets/movielens/ml-latest-small.zip [root@localhost logstash-7.2.0]# unzip ml-latest-small.zip [root@localhost logstash-7.2.0]# vim config/logstash-test.conf
logstash-test.conf內容以下:
input { file { path => "/usr/logstash/logstash-7.2.0/ml-latest-small/movies.csv" #注意修改成本身的目錄 start_position => "beginning" sincedb_path => "/dev/null" } } filter { csv { separator => "," columns => ["id","content","genre"] } mutate { split => { "genre" => "|" } remove_field => ["path", "host","@timestamp","message"] } mutate { split => ["content", "("] add_field => { "title" => "%{[content][0]}"} add_field => { "year" => "%{[content][2]}"} } mutate { convert => { "year" => "integer" } strip => ["title"] remove_field => ["path", "host","@timestamp","message","content"] } } output { elasticsearch { hosts => http://192.168.1.204:9200 #注意修改成本身的ES index => "movies" document_id => "%{id}" } stdout {} }
運行下測試數據,注意先啓動ES:
[root@localhost logstash-7.2.0]# ./bin/logstash -f /usr/logstash/logstash-7.2.0/config/logstash-test.conf
報錯:There is insufficient memory for the Java Runtime Environment to continue.
虛擬機的內存不夠,以下命令查看內存狀況:
[root@localhost logstash-7.2.0]# free -h
建議直接虛擬機修改成 4G 內存,再跑此測試數據!運行成功後,先放着。
11.開始Kibana安裝:下載,略,kibana-7.2.0-linux-x86_64.tar.gz複製到目錄/usr/kibana下,解壓:
[root@localhost ~]# cp /mnt/hgfs/00sharetoVM/kibana-7.2.0-linux-x86_64.tar.gz /usr/kibana
以root啓動會提示不能使用root運行,可以使用加 --allow-root
參數解決,這裏我直接換成普通用戶:
[root@localhost usr]# chown -Rv biao /usr/kibana/ [biao@localhost kibana-7.2.0-linux-x86_64]$ pwd /usr/kibana/kibana-7.2.0-linux-x86_64 [biao@localhost kibana-7.2.0-linux-x86_64]$ vim config/kibana.yml
#如下爲配置項目:
server.port: 5601 server.host: "192.168.1.204" #虛擬機的IP elasticsearch.hosts: ["http://192.168.1.204:9200"] kibana.index: ".kibana"
啓動Kibana,注意先啓動ES:
[biao@localhost kibana-7.2.0-linux-x86_64]$ ./bin/kibana
再配合上面處於啓動狀態的Logstash測試數據,外部打開URL地址:http://192.168.1.204:5601/
12.啓動Kibana,如遇到錯誤:Elasticsearch cluster did not respond with license information.
只需仔細配置 ES ,不是缺乏xpack插件,7.X已經集成該插件了!
[biao@localhost elasticsearch-7.2.0]$ vim config/elasticsearch.yml
如下爲配置項:
cluster.name: my-application node.name: node-1 path.data: /tmp/es/data path.logs: /tmp/es/logs network.host: 192.168.1.204 #建議不要寫爲網上的0.0.0.0 http.port: 9200 discovery.seed_hosts: ["192.168.1.204"] cluster.initial_master_nodes: ["node-1"]
Part2 驗收測試
1.先實現Springboot應用整合ELK作日誌處理:總體目標架構以下圖:
2.建立springboot工程,我使用idea直接建一個簡單的gradle project,終於擺脫前面的mall項目了!
3.引入依賴,很是建議逐步引入,使用過程當中觀察缺乏依賴對應用的影響,這樣能更好的學習各個組件的做用:
dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-parent compile group: 'org.springframework.boot', name: 'spring-boot-starter-parent', version: '2.2.0.RELEASE', ext: 'pom' //Core starter, including auto-configuration support, logging and YAML compile group: 'org.springframework.boot', name: 'spring-boot-starter', version: '2.2.0.RELEASE' //Starter for testing Spring Boot applications with libraries including JUnit, Hamcrest and Mockito testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: '2.2.0.RELEASE' //Starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.2.0.RELEASE' // testCompile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' // https://mvnrepository.com/artifact/net.logstash.logback/logstash-logback-encoder compile group: 'net.logstash.logback', name: 'logstash-logback-encoder', version: '6.2' // 原本這裏的scope應該爲providedCompile,即只存在於編譯和測試階段,但彷佛gradle沒法識別,maven環境下未測試 compile group: 'org.projectlombok', name: 'lombok', version: '1.18.10' // https://mvnrepository.com/artifact/org.elasticsearch.client/elasticsearch-rest-high-level-client compile group: 'org.elasticsearch.client', name: 'elasticsearch-rest-high-level-client', version: '7.2.0' // https://mvnrepository.com/artifact/org.elasticsearch/elasticsearch compile group: 'org.elasticsearch', name: 'elasticsearch', version: '7.2.0' // https://mvnrepository.com/artifact/org.elasticsearch.client/transport compile group: 'org.elasticsearch.client', name: 'transport', version: '7.2.0' }
4.建立類,注意這裏直接將Controller放入口類ApplicationMain裏面的,簡單粗暴!
@RestController @SpringBootApplication //@Slf4j public class ApplicationMain { private final Logger log = LoggerFactory.getLogger(ApplicationMain.class); public static void main(String[] args) { SpringApplication.run(ApplicationMain.class,args); System.out.println("ELK Application started.>>>>>>>>>>>>>>>>>>>>>>>>>>>"); } @RequestMapping("/test") public String test() throws InterruptedException { for (int i = 0; i < 10; i++) { Thread.sleep(1000); log.info("log from ELK app time: {}",System.currentTimeMillis()); } return "ELK test success"; } }
5.建立logback-spring文件,再複習下logback的使用,SLF4J是集合了各類日誌組件的框架,使用了門面模式
,appender/logger/root是其中三大件,這裏就是使用logback將日誌傳給Logstash。另外,我還定義了一個file類型的log輸出,能夠看到項目代碼所在的目錄下的log文件:
<?xml version="1.0" encoding="UTF-8"?> <!--該日誌將日誌級別不一樣的log信息保存到不一樣的文件中 --> <configuration> <include resource="org/springframework/boot/logging/logback/defaults.xml" /> <!--springProperty:在properties/yml文件中找到對應的配置項 --> <springProperty scope="context" name="springAppName" source="spring.application.name" /> <springProperty scope="context" name="logFilePath" source="logging.config.path" /> <!-- 日誌在工程中的輸出位置 --> <property name="LOG_FILE" value="${BUILD_FOLDER:-build}/${springAppName}" /> <!-- 控制檯的日誌輸出樣式 --> <property name="CONSOLE_LOG_PATTERN" value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}" /> <!-- 控制檯輸出 appender--> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>INFO</level> </filter> <!-- 日誌輸出編碼 --> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${CONSOLE_LOG_PATTERN}</pattern> <charset>utf8</charset> </encoder> </appender> <!-- 爲logstash輸出的JSON格式的Appender --> <appender name="logstash" class="net.logstash.logback.appender.LogstashTcpSocketAppender"> <destination>192.168.1.204:9665</destination> <!-- 日誌輸出編碼 --> <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder"> <providers> <timestamp> <timeZone>UTC</timeZone> </timestamp> <pattern> <pattern> { "severity": "%level", "service": "${springAppName:-}", "trace": "%X{X-B3-TraceId:-}", "span": "%X{X-B3-SpanId:-}", "exportable": "%X{X-Span-Export:-}", "pid": "${PID:-}", "thread": "%thread", "class": "%logger{40}", "rest": "%message" } </pattern> </pattern> </providers> </encoder> </appender> <!--文件格式輸出appender--> <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!--定義日誌輸出的路徑--> <!--這裏的scheduler.manager.server.home 沒有在上面的配置中設定,因此會使用java啓動時配置的值--> <!--好比經過 java -Dscheduler.manager.server.home=/path/to XXXX 配置該屬性--> <file>${logging.path}/spring-boot/elk.log</file> <!--定義日誌滾動的策略--> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!--定義文件滾動時的文件名的格式--> <fileNamePattern>${scheduler.manager.server.home}/logs/${app.name}.%d{yyyy-MM-dd.HH}.log </fileNamePattern> <!--60天的時間週期,日誌量最大20GB--> <maxHistory>60</maxHistory> <!-- 該屬性在 1.1.6版本後 纔開始支持--> <totalSizeCap>20GB</totalSizeCap> </rollingPolicy> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <!--每一個日誌文件最大100MB--> <maxFileSize>100MB</maxFileSize> </triggeringPolicy> <!--定義輸出格式--> <encoder> <pattern>%d [%thread] %-5level %logger{36} [%file : %line] - %msg%n</pattern> </encoder> </appender> <!--logger 用來設置某一個包或者具體的某一個類的日誌打印級別以及指定appender--> <!--經過 LoggerFactory.getLogger("mytest") 能夠獲取到這個logger--> <!--因爲這個logger自動繼承了root的appender,root中已經有stdout的appender了,本身這邊又引入了stdout的appender--> <!--若是沒有設置 additivity="false" ,就會致使一條日誌在控制檯輸出兩次的狀況,經過appender-ref作好分工,root負責console和logstash 此logger負責file輸出--> <!--additivity表示要不要使用rootLogger配置的appender進行輸出--> <logger name="test" level="INFO" additivity="false"> <appender-ref ref="file"/> </logger> <!-- 根logger,也是一種logger,且只有一個level屬性 --> <root level="INFO"> <appender-ref ref="console" /> <appender-ref ref="logstash" /> </root> </configuration>
6.建立application.yml文件,用於上面的文件中作值引用:
spring:
application:
name: ELK Application
logging:
config:
path: ./logs
7.新建一個logstash啓動配置文件:
[root@localhost logstash-7.2.0]# vim config/logstash-java.conf
內容以下,注意這裏的port是應用接入的端口,output則是ES:
input{ tcp { port => 9665 codec => json_lines } } output{ elasticsearch{ hosts => ["192.168.1.204:9200"] } }
8.啓動logstash:
[root@localhost logstash-7.2.0]# ./bin/logstash -f /usr/logstash/logstash-7.2.0/config/logstash-java.conf
如應用啓動後出現錯誤:
Log destination 192.168.1.204:2004: connection failed. java.net.ConnectException: Connection refused: connect
請仔細檢查logstash-java.conf 和logback-spring.xml 的端口配置,必須一致!
9.啓動順序:
ES --> Kibana --> Logstash --> ELK Application
10.URL訪問:http://localhost:8080/test,應用產生log:
URL訪問Kibana ,略做下配置:http://192.168.1.204:5601/
下一步:
下一步:
至此,海量日誌分析框架完成!哪來的海量???這還不簡單,上面的代碼中循環 i 改成一百億,去掉sleep!特此聲明,對海量實驗結果概不負責!至於kibana那些豐富多彩的展示和KQL查詢,各位自行去探索吧!
11.來操做一把 ES Java API:
官方文檔中有使用 org.elasticsearch.client.transport.TransportClient
作 ES 的外部 client ,再去操做ES,但使用後卻發現已經 deprecated !換一個吧,我找到io.searchbox.client.JestClient
,結果最新是2018年的,這?!再進行尋找一番,有個 org.elasticsearch.client.RestHighLevelClient
是最新的,且支持同步和異步調用,趕忙又換掉前面的,唉,就像猴子下山同樣,好累,代碼換了三波!這裏只是使用了一個保存API,其餘還有不少,可參考官網,使用方式相似。
代碼就是在ApplicationMain中再添加一個APItest測試方法:
@RequestMapping("/api") public String APItest() throws InterruptedException, IOException { /** scheme 選項 http/tcp * 1. java客戶端的方式是以tcp協議在9300端口上進行通訊 * 2. http客戶端的方式是以http協議在9200端口上進行通訊 */ RestHighLevelClient client = new RestHighLevelClient( //builder能夠繼續添加多個HttpHost RestClient.builder( new HttpHost("192.168.1.204", 9200, "http"))); /** 有四種不一樣的方式來產生JSON格式的文檔(document) .Manually (aka do it yourself) using native byte[] or as a String .Using a Map that will be automatically converted to its JSON equivalent .Using a third party library to serialize your beans such as Jackson .Using built-in helpers XContentFactory.jsonBuilder() */ XContentBuilder builder = XContentFactory.jsonBuilder(); builder.startObject(); { builder.field("user", "biao"); builder.timeField("postDate", new Date()); builder.field("message", "trying out Elasticsearch"); } builder.endObject(); String index = "my_temp_index"; IndexRequest indexRequest = new IndexRequest(index) .id("1") .timeout(TimeValue.timeValueSeconds(1)) .setRefreshPolicy(WriteRequest.RefreshPolicy.WAIT_UNTIL) .opType(DocWriteRequest.OpType.INDEX) .source(builder); //Synchronous execution IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT); System.out.println(indexResponse.toString()); //asynchronous execution, // client.indexAsync(indexRequest, RequestOptions.DEFAULT, listener); client.close(); return "ELK API test success"; } ActionListener listener = new ActionListener() { @Override public void onResponse(Object o) { System.out.println("ELK API ASYN test success"); } @Override public void onFailure(Exception e) { System.out.println("ELK API ASYN test failed"); } };
同步測試:URL訪問:http://localhost:8080/api
結果以下,Index保存成功,達到測試目標!
異步測試:特別注意要將 client.close() 註釋掉,並實現 ActionListener 類:URL訪問:http://localhost:8080/api
結果以下,Index保存成功,覆蓋了上面同步測試生成的index內容(是否覆蓋可配置),達到測試目標!
覆盤記:
1.ELK是一個可伸縮的框架,可按需進行裁剪,其中Logstash是一個 點對點 的信息採集器,若是流量巨大,能夠加入MQ或Redis緩衝,
2.ES出身就是分佈式的,因此集羣方式能夠作到多Node,多Shard,使用主從複製與冗餘存儲備份策略,自動平衡數據存儲點負載,
3.對於ES的概念,有個很好的對比圖,若是用過Mongodb,應該就好理解,只注意「文檔」一詞,不是指咱們常說的word/pdf文件,而是一種有格式的描述型結構化數據,好比JSON:
4.再次注意ELK中各conf文件的IP綁定概念,不建議使用0.0.0.0,事實上生產環境也不會直接全開!具體分析我在前篇《Linux下Redis集羣》中有解釋,這裏的bind相似,再也不贅述。
5.ES分庫分片設置:
如下使用ES-Head方式,建立一個index,並配置爲一個node上3個shard,每一個shard有2個replica:
以上也可以使用CURL方式:
curl -X PUT "localhost:9200/my_temp_index?pretty" -H 'Content-Type: application/json' -d' { "settings": { "number_of_shards" : 1, "number_of_replicas" : 0 } } '
具體展示以下:
而後,咱們能夠用 update-index-settings API 動態修改副本數,也可以使用CURL方式:
修改後的效果:
5.ES爲何快!?核心就是倒序索引和特殊的文件壓縮,至於詳細,內容略多,在此僅做個引子。
6.本文徹底沒用到dubbo,只是爲了標題的連貫,故保留。
本文結束!
推薦閱讀: