前言
本文主要介紹的是Prometheus採集資料,通過Grafana加上PromQL語句實現資料視覺化以及通過Alertmanage實現告警推送功能。溫馨提示,本篇文章特長,2w多的文字加上幾十張圖片,建議收藏觀看。
Prometheus 介紹
Prometheus 是一套開源的系統監控報警框架。它啟發於 Google 的 borgmon 監控系統,由工作在 SoundCloud 的 google 前員工在 2012 年建立,作為社群開源專案進行開發,並於 2015 年正式釋出。2016 年,Prometheus 正式加入 Cloud Native Computing Foundation,成為受歡迎度僅次於 Kubernetes 的專案。
作為新一代的監控框架,Prometheus 具有以下特點:
- 強大的多維度資料模型: 時間序列資料通過 metric 名和鍵值對來區分。 所有的 metrics 都可以設定任意的多維標籤。
- 資料模型更隨意,不需要刻意設定為以點分隔的字串。 可以對資料模型進行聚合,切割和切片操作。
- 支援雙精度浮點型別,標籤可以設為全unicode。 靈活而強大的查詢語句(PromQL):在同一個查詢語句,可以對多個 metrics進行乘法、加法、連線、取分數位等操作。
- 易於管理: Prometheus server是一個單獨的二進位制檔案,可直接在本地工作,不依賴於分散式儲存。 高效:平均每個取樣點僅佔 3.5 bytes,且一個 Prometheus server 可以處理數百萬的 metrics。 使用 pull模式採集時間序列資料,這樣不僅有利於本機測試而且可以避免有問題的伺服器推送壞的 metrics。 可以採用 push gateway 的方式把時間序列資料推送至 Prometheus server 端。 可以通過服務發現或者靜態配置去獲取監控的 targets。
- 有多種視覺化圖形介面。 易於伸縮。 需要指出的是,由於資料採集可能會有丟失,所以 Prometheus 不適用對採集資料要 100%
- 準確的情形。但如果用於記錄時間序列資料,Prometheus 具有很大的查詢優勢,此外,Prometheus 適用於微服務的體系架構。
示例圖:
Prometheus的適用場景
在選擇Prometheus作為監控工具前,要明確它的適用範圍,以及不適用的場景。
Prometheus在記錄純數值時間序列方面表現非常好。它既適用於以伺服器為中心的監控,也適用於高動態的面向服務架構的監控。
在微服務的監控上,Prometheus對多維度資料採集及查詢的支援也是特殊的優勢。
Prometheus更強調可靠性,即使在故障的情況下也能檢視系統的統計資訊。權衡利弊,以可能丟失少量資料為代價確保整個系統的可用性。因此,它不適用於對資料準確率要求100%的系統,比如實時計費系統(涉及到錢)。
Prometheus核心元件介紹
Prometheus Server:
Prometheus Server是Prometheus元件中的核心部分,負責實現對監控資料的獲取,儲存以及查詢。 Prometheus Server可以通過靜態配置管理監控目標,也可以配合使用Service Discovery的方式動態管理監控目標,並從這些監控目標中獲取資料。其次Prometheus Server需要對採集到的監控資料進行儲存,Prometheus Server本身就是一個時序資料庫,將採集到的監控資料按照時間序列的方式儲存在本地磁碟當中。最後Prometheus Server對外提供了自定義的PromQL語言,實現對資料的查詢以及分析。 Prometheus Server內建的Express Browser UI,通過這個UI可以直接通過PromQL實現資料的查詢以及視覺化。 Prometheus Server的聯邦叢集能力可以使其從其他的Prometheus Server例項中獲取資料,因此在大規模監控的情況下,可以通過聯邦叢集以及功能分割槽的方式對Prometheus Server進行擴充套件。
Exporters:
Exporter將監控資料採集的端點通過HTTP服務的形式暴露給Prometheus Server,Prometheus
Server通過訪問該Exporter提供的Endpoint端點,即可獲取到需要採集的監控資料。 一般來說可以將Exporter分為2類:
直接採集:這一類Exporter直接內建了對Prometheus監控的支援,比如cAdvisor,Kubernetes,Etcd,Gokit等,都直接內建了用於向Prometheus暴露監控資料的端點。
間接採集:間接採集,原有監控目標並不直接支援Prometheus,因此我們需要通過Prometheus提供的Client Library編寫該監控目標的監控採集程式。例如: Mysql Exporter,JMX Exporter,Consul Exporter等。
PushGateway:
在Prometheus Server中支援基於PromQL建立告警規則,如果滿足PromQL定義的規則,則會產生一條告警,而告警的後續處理流程則由AlertManager進行管理。在AlertManager中我們可以與郵件,Slack等等內建的通知方式進行整合,也可以通過Webhook自定義告警處理方式。
Service Discovery:
服務發現在Prometheus中是特別重要的一個部分,基於Pull模型的抓取方式,需要在Prometheus中配置大量的抓取節點資訊才可以進行資料收集。有了服務發現後,使用者通過服務發現和註冊的工具對成百上千的節點進行服務註冊,並最終將註冊中心的地址配置在Prometheus的配置檔案中,大大簡化了配置檔案的複雜程度,
也可以更好的管理各種服務。 在眾多雲平臺中(AWS,OpenStack),Prometheus可以
通過平臺自身的API直接自動發現執行於平臺上的各種服務,並抓取他們的資訊Kubernetes掌握並管理著所有的容器以及服務資訊,那此時Prometheus只需要與Kubernetes打交道就可以找到所有需要監控的容器以及服務物件.
- Consul(官方推薦)等服務發現註冊軟體
- 通過DNS進行服務發現
- 通過靜態配置檔案(在服務節點規模不大的情況下)
Prometheus UI
Prometheus UI是Prometheus內建的一個視覺化管理介面,通過Prometheus UI使用者能夠輕鬆的瞭解Prometheus當前的配置,監控任務執行狀態等。 通過Graph皮膚,使用者還能直接使用PromQL實時查詢監控資料。訪問ServerIP:9090/graph開啟WEB頁面,通過PromQL可以查詢資料,可以進行基礎的資料展示。
如下所示,查詢主機負載變化情況,可以使用關鍵字node_load1可以查詢出Prometheus採集到的主機負載的樣本資料,這些樣本資料按照時間先後順序展示,形成了主機負載隨時間變化的趨勢圖表:
Grafana介紹
Grafana是一個跨平臺的開源的度量分析和視覺化工具,可以通過將採集的資料查詢然後視覺化的展示。Grafana提供了對prometheus的友好支援,各種工具幫助你構建更加炫酷的資料視覺化。
Grafana特點
- 視覺化:快速和靈活的客戶端圖形具有多種選項。皮膚外掛為許多不同的方式視覺化指標和日誌。
- 報警:視覺化地為最重要的指標定義警報規則。Grafana將持續評估它們,併傳送通知。
- 通知:警報更改狀態時,它會發出通知。接收電子郵件通知。
- 動態儀表盤:使用模板變數建立動態和可重用的儀表板,這些模板變數作為下拉選單出現在儀表板頂部。
- 混合資料來源:在同一個圖中混合不同的資料來源!可以根據每個查詢指定資料來源。這甚至適用於自定義資料來源。
- 註釋:註釋來自不同資料來源圖表。將滑鼠懸停在事件上可以顯示完整的事件後設資料和標記。
- 過濾器:過濾器允許您動態建立新的鍵/值過濾器,這些過濾器將自動應用於使用該資料來源的所有查詢。
這裡我們使用上面Prometheus使用關鍵字node_load1來使用Grafana進行視覺化,點選側邊欄的加號圖示,然後單擊Dashboard點選建立,然後把剛剛Prometheus使用的查詢語句放到Metries,點選右上角的apply即可。
示例圖:
Grafana UI
上面的示例中我們通過prometheus+grafana通過PromQL進行了簡單的伺服器負載的監控視覺化。我們也可以通過第三方提供視覺化JSON檔案來幫助我們快速實現伺服器、Elasticsearch、MYSQL等等監控。這裡我們在grafana提供的第三方dashboards的地址https://grafana.com/grafana/dashboards來下載對應的json檔案然後匯入到grafana實現伺服器的監控。
監控伺服器的示例圖:
除了服務端的監控,可以監控應用服務。Prometheus 監控應用的方式非常簡單,只需要程式暴露了一個用於獲取當前監控樣本資料的 HTTP 訪問地址。這樣的一個程式稱為Exporter,Exporter 的例項稱為一個 Target 。Prometheus 通過輪訓的方式定時從這些 Target 中獲取監控資料樣本,對於應用來講,只需要暴露一個包含監控資料的 HTTP 訪問地址即可,當然提供的資料需要滿足一定的格式,這個格式就是 Metrics 格式: metric name>{
Prometheus+Grafana+Alertmanager等安裝配置
Prometheus以及相關元件使用的是2.x版本,Grafana使用的是7.x版本。
下載地址推薦使用清華大學或華為的開源映象站。
下載地址:
https://prometheus.io/download/
https://mirrors.tuna.tsinghua.edu.cn/grafana/
Prometheus以及相關元件百度網盤地址:
連結:https://pan.baidu.com/s/1btErwq8EyAzG2-34lwGO4w
提取碼:4nlh
Prometheus安裝
1,檔案準備
將下載好的Prometheus檔案解壓
輸入
tar -zxvf prometheus-2.19.3.linux-amd64.tar.gz
然後移動到/opt/prometheus資料夾裡面,沒有該資料夾則建立
2,配置修改
在prometheus-2.19.3.linux-amd64資料夾目錄下找到prometheus.yml配置檔案並更改
prometheus.yml檔案配置如下:
# my global config
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
# - "first_rules.yml"
# - "second_rules.yml"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
- job_name: 'prometheus'
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
static_configs:
- targets: ['192.168.8.181:9090']
3,prometheus 啟動
在/opt/prometheus/prometheus-2.19.3.linux-amd64的目錄下輸入:
nohup ./prometheus >/dev/null 2>&1 &
啟動成功之後,在瀏覽器上輸入 ip+9090可以檢視相關資訊。
Grafana安裝
1,檔案準備
將下載下來的grafana-7.1.1-1.x86_64.rpm的檔案通過apm方式安裝
輸入:
rpm -ivh grafana-7.1.1-1.x86_64.rpm
進行安裝
如果出現如下錯誤:
error: Failed dependencies:
urw-fonts is needed by grafana-6.1.4-1.x86_64
一個依賴包沒有安裝,需要先安裝這個依賴包,然後再安裝grafana
# yum install -y urw-fonts
2,grafana 啟動
root使用者下啟動
輸入:
sudo /bin/systemctl start grafana-server.service
啟動成功之後,在瀏覽器上輸入 ip+3000可以檢視相關資訊
Alertmanager安裝
1,檔案準備
將下載好的Alertmanager檔案解壓
輸入
tar -zxvf alertmanager-0.21.0.linux-386.tar.gz
然後移動到/opt/prometheus資料夾裡面,沒有該資料夾則建立
2,alertmanager啟動
root使用者下啟動
輸入:
nohup ./alertmanager >/dev/null 2>&1 &
啟動成功之後,在瀏覽器上輸入 ip+9093可以檢視相關資訊
示例圖:
Pushgateway 安裝
1,檔案準備
將下載好的pushgateway檔案解壓
輸入
tar -zxvf pushgateway-1.2.0.linux-amd64.tar.gz
然後移動到/opt/prometheus資料夾裡面,沒有該資料夾則建立
2,啟動
root使用者下啟動
輸入:
nohup ./pushgateway >/dev/null 2>&1 &
啟動成功之後,在瀏覽器上輸入 ip+9091可以檢視相關資訊
5,Node_export安裝
1,檔案準備
將下載好的Node_export檔案解壓
輸入
tar -zxvf node_exporter-0.17.0.linux-amd64.tar.gz
然後移動到/opt/prometheus資料夾裡面,沒有該資料夾則建立
2,啟動
root使用者下啟動
輸入:
nohup ./consul_exporter >/dev/null 2>&1 &
啟動成功之後,在瀏覽器上輸入 ip+9100可以檢視相關資訊
Prometheus使用教程
Prometheus介面地址: ip+9090。
這裡我就使用圖片加上註釋來進行講解。
1,基本使用
1>,Prometheus主介面說明
2>,Graph使用示例
3>,檢視執行資訊
4>,檢視命令標記值
5>,檢視配置資訊
6,>檢視整合的元件
整合的元件需要下載對應export服務並啟動執行,並且在prometheus的配置中進行新增!
7>,檢視服務發現
8>,檢視告警規則
first_rules.yml的配置。
9>,檢視是否觸發告警
相關文件:https://prometheus.io/docs/prometheus/latest/getting_started/
Grafana使用
Grafanf 介面地址: ip+3000
初始賬號密碼: admin, admin
這裡我依舊用圖片加註釋來進行講解,想必這樣會更容易理解吧。。。
1>,主介面資訊
2>,建立儀表盤監控實現
1.新增資料來源
2.選擇prometheus
3.點選建立儀表盤
4.點選建立
5.輸入node_load1表示語句,填寫相關資訊,點選apply完成,並將名稱儲存為Test
6.點選搜尋Test,點選就可以檢視
3>,使用第三方儀表盤監控實現
注:需提前新增好資料來源。
1.點選左上角的加號,點選import
線上模式
地址:https://grafana.com/grafana/dashboards
離線模式
監控示例:
監控告警實現
監控告警實現需要依賴 Alertmanager,已經相關的元件,比如上述例項中的監控伺服器應用的node_server元件。
郵件告警實現
需要安裝Alertmanager,這裡因為郵件傳送比較簡單,所以這裡我就直接貼配置了,其中帶有xxx字元的引數是需要根據情況進行更改的。下面的企業微信告警同理。
Alertmanagers服務的alertmanager.yml的配置如下:
global:
resolve_timeout: 5m
smtp_from: 'xxx@qq.com'
smtp_smarthost: 'smtp.qq.com:465'
smtp_auth_username: 'xxx@qq.com'
smtp_auth_password: 'xxx'
smtp_require_tls: false
smtp_hello: 'qq.com'
route:
group_by: ['alertname']
group_wait: 5s
group_interval: 5s
repeat_interval: 5m
receiver: 'email'
receivers:
- name: 'email'
email_configs:
- to: 'xxx@qq.com'
send_resolved: true
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'dev', 'instance']
注: smtp_from、smtp_auth_username、to的郵箱可以填寫同一個,smtp_auth_password填寫鑑權碼,需要開啟POS3。
如果不知道怎麼開啟POS3,可以檢視我的這篇文章: https://www.cnblogs.com/xuwujing/p/10945698.html
Prometheus服務的Prometheus.yml配置如下:
# my global config
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
- '192.168.214.129:9093'
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
- "/opt/prometheus/prometheus-2.19.3.linux-amd64/first_rules.yml"
# - "second_rules.yml"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
- job_name: 'prometheus'
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
static_configs:
- targets: ['192.168.214.129:9090']
- job_name: 'server'
static_configs:
- targets: ['192.168.214.129:9100']
注:targets如果有多個配置的話,在後面加上其他服務的節點即可。alertmanagers最好寫伺服器的ip,不然可能會出現告警資料無法傳送的情況。
Error sending alert" err="Post \"http://alertmanager:9093/api/v1/alerts\": context deadline exceeded
配置了Prometheus.yml之後,我們還需要配置告警的規則,也就是觸發條件,達到條件之後就進行觸發。我們新建一個first_rules.yml,用於檢測伺服器掛掉的時候進行傳送訊息
first_rules.yml告警配置:
注:job等於的服務名稱填寫Prometheus.yml配置對應的名稱,比如這裡設定的server對應Prometheus.yml配置的server。
groups:
- name: node
rules:
- alert: server_status
expr: up{job="server"} == 0
for: 15s
annotations:
summary: "機器{{ $labels.instance }} 掛了"
description: "報告.請立即檢視!"
依次啟動prometheus、altermanagers、node_server服務,檢視告警,然後停止node_export服務,等待一段時間在檢視。
企業微信告警實現
和上面的示例操作基本一致,主要是配置的區別。
1.在企業微信中建立一個應用,並得到secret、corp_id和agent_id配置。
2.然後修改alertmanager.yml配置,alertmanager.yml配置如下:
global:
resolve_timeout: 5s
wechat_api_url: 'https://qyapi.weixin.qq.com/cgi-bin/'
wechat_api_secret: 'xxx'
wechat_api_corp_id: 'xxx'
templates:
- '/opt/prometheus/alertmanager-0.21.0.linux-386/template/wechat.tmpl'
route:
group_by: ['alertname']
group_wait: 10s
group_interval: 10s
repeat_interval: 10s
receiver: 'wechat'
receivers:
- name: 'wechat'
wechat_configs:
- send_resolved: true
to_party: '2'
agent_id: xxx
corp_id: 'xxx'
api_secret: 'xxx'
api_url: 'https://qyapi.weixin.qq.com/cgi-bin/'
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'dev', 'instance']
配置成功之後,操作和上述郵件傳送的一致,即可在企業微信看到如下資訊。
如果覺得上述的示例不好友好的話,我們也可以制定告警模板。
新增告警模板:
在alertmanagers的資料夾下建立一個template資料夾,然後在該資料夾建立一個微信告警的模板wechat.tmpl,新增如下配置:
{{ define "wechat.default.message" }}
{{ range .Alerts }}
========start=========
告警程式: prometheus_alert
告警級別: {{ .Labels.severity}}
告警型別: {{ .Labels.alertname }}
故障主機: {{ .Labels.instance }}
告警主題: {{ .Annotations.summary }}
告警詳情: {{ .Annotations.description }}
=========end===========
{{ end }}
{{ end }}
然後再到alertmanager.yml 新增如下配置:
templates:
- '/opt/prometheus/alertmanager-0.21.0.linux-386/template/wechat.tmpl'
效果圖:
應用服務監控告警實現
Prometheus 監控應用的方式非常簡單,只需要程式暴露了一個用於獲取當前監控樣本資料的 HTTP
訪問地址。這樣的一個程式稱為Exporter,Exporter 的例項稱為一個 Target 。Prometheus通過輪訓的方式定時從這些 Target 中獲取監控資料樣本,對於應用來講,只需要暴露一個包含監控資料的 HTTP訪問地址即可,當然提供的資料需要滿足一定的格式,這個格式就是 Metrics 格式: metric name>{= , ...} 。label name是標籤,label value是標籤的值。
Springboot應用實現步驟
1.在pom檔案新增
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2.在程式碼中新增如下配置:
private Counter requestErrorCount;
private final MeterRegistry registry;
@Autowired
public PrometheusCustomMonitor(MeterRegistry registry) {
this.registry = registry;
}
@PostConstruct
private void init() {
requestErrorCount = registry.counter("requests_error_total", "status", "error");
}
public Counter getRequestErrorCount() {
return requestErrorCount;
}
3.在異常處理中新增如下記錄:
monitor.getRequestErrorCount().increment();
4.在prometheus的配置中新增springboot應用服務監控
- job_name: 'springboot'
metrics_path: '/actuator/prometheus'
scrape_interval: 5s
static_configs:
- targets: ['192.168.8.45:8080']
5.Prometheu.yml配置如下:
- job_name: 'springboot'
metrics_path: '/actuator/prometheus'
scrape_interval: 5s
static_configs:
- targets: ['192.168.8.45:8080']
規則檔案配置如下:
6.在prometheus監控即可檢視
企業微信告警效果圖:
監控的springboot專案地址:https://github.com/xuwujing/springBoot-study
其他配置
prometheus動態載入配置
Prometheus資料來源的配置主要分為靜態配置和動態發現, 常用的為以下幾類:
- static_configs: 靜態服務發現
- file_sd_configs: 檔案服務發現
- dns_sd_configs: DNS 服務發現
- kubernetes_sd_configs: Kubernetes 服務發現
- consul_sd_configs:Consul 服務發現(推薦使用)
file_sd_configs的方式提供簡單的介面,可以實現在單獨的配置檔案中配置拉取物件,並監視這些檔案的變化並自動載入變化。基於這個機制,我們可以自行開發程式,監控監控物件的變化自動生成配置檔案,實現監控物件的自動發現。
在prometheus資料夾目錄下建立targets.json檔案
配置如下:
[
{
"targets": [ "192.168.214.129:9100"],
"labels": {
"instance": "node",
"job": "server-129"
}
},
{
"targets": [ "192.168.214.134:9100"],
"labels": {
"instance": "node",
"job": "server-134"
}
}
]
然後在prometheus目錄下新增如下配置:
- job_name: 'file_sd'
file_sd_configs:
- files:
- targets.json
一些告警配置
這是本人整理的一些服務應用告警的配置,也歡迎大家共同討論一些常用的相關配置。
記憶體告警設定
- name: test-rule
rules:
- alert: "記憶體報警"
expr: 100 - ((node_memory_MemAvailable_bytes * 100) / node_memory_MemTotal_bytes) > 30
for: 15s
labels:
severity: warning
annotations:
summary: "服務名:{{$labels.instance}}記憶體使用率超過30%了"
description: "業務500報警: {{ $value }}"
value: "{{ $value }}"
示例圖:
磁碟設定:
總量百分比設定:
(node_filesystem_size_bytes {mountpoint ="/"} - node_filesystem_free_bytes {mountpoint ="/"}) / node_filesystem_size_bytes {mountpoint ="/"} * 100
檢視某一目錄的磁碟使用百分比
(node_filesystem_size_bytes{mountpoint="/boot"}-node_filesystem_free_bytes{mountpoint="/boot"})/node_filesystem_size_bytes{mountpoint="/boot"} * 100
正規表示式來匹配多個掛載點
(node_filesystem_size_bytes{mountpoint=~"/|/run"}-node_filesystem_free_bytes{mountpoint=~"/|/run"})
/ node_filesystem_size_bytes{mountpoint=~"/|/run"} * 100
預計多長時間磁碟爆滿
predict_linear(node_filesystem_free_bytes {mountpoint ="/"}[1h],
43600) < 0 predict_linear(node_filesystem_free_bytes
{job="node"}[1h], 43600) < 0
CPU使用率
100 - (avg(irate(node_cpu_seconds_total{mode="idle"}[5m])) by
(instance) * 100)
空閒記憶體剩餘率
(node_memory_MemFree_bytes+node_memory_Cached_bytes+node_memory_Buffers_bytes)
/ node_memory_MemTotal_bytes * 100
記憶體使用率
100 -
(node_memory_MemFree_bytes+node_memory_Cached_bytes+node_memory_Buffers_bytes)
/ node_memory_MemTotal_bytes * 100
磁碟使用率
100 - (node_filesystem_free_bytes{mountpoint="/",fstype=~"ext4|xfs"} /
node_filesystem_size_bytes{mountpoint="/",fstype=~"ext4|xfs"} * 100)
其他
這段時間比較忙,ELK相關得等待一段時間在進行更新,雖然發表的部落格才對應去年整理的部落格。。。。
本篇文章準備了好久,邊整理編寫,沒想到寫了這麼多。不過感覺這樣也不錯,一次寫出來或許比分開一次次的寫對讀者而言要好上不上,畢竟不用一篇篇的去找了。
音樂推薦
原創不易,如果感覺不錯,希望給個推薦!您的支援是我寫作的最大動力!
版權宣告:
作者:虛無境
部落格園出處:http://www.cnblogs.com/xuwujing
CSDN出處:http://blog.csdn.net/qazwsxpcm
掘金出處:https://juejin.im/user/5ae45d5bf265da0b8a6761e4
個人部落格出處:http://www.panchengming.com