做者 | 喬克
php
來源 | 運維開發故事
html
分享 | 喬克
java
監控是整個運維以及產品整個生命週期最重要的一環,它旨在事前可以及時預警發現故障,事中可以結合監控數據定位問題,過後可以提供數據用於分析問題。node
1、監控的目的
監控貫穿應用的整個生命週期。即從程序設計、開發、部署、下線。其主要的服務對象有:git
-
技術 -
業務
技術經過監控系統能夠了解技術的環境狀態,能夠幫助檢測、診斷、解決技術環境中的故障和問題。然而監控系統的最終目標是業務,是爲了更好的支持業務運行,確保業務的持續開展。github
因此監控的目的能夠簡單概括以下:一、可以對系統進行7*24小時的實時監控 二、可以及時反饋系統狀態 三、保證平臺的穩定運行 三、保證服務的安全可靠 四、保證業務的持續運行web
2、監控的模式
監控由上至下能夠分爲:docker
-
業務監控 -
應用監控 -
操做系統
其中業務監控主要是研發提供一些業務指標、業務數據,對其增加率、錯誤率等進行告警或者展現,須要提早定義規範甚至埋點。應用程序的監控主要有探針和內省。其中探針主要是從外部探測應用程序的特徵,好比監聽端口是否有響應。內省主要是查看應用程序內部的內容,應用程序經過檢測並返回其內部的狀態,內部的組件,事務和性能等度量,它能夠直接將事件、日誌和指標直接發送給監控工具。操做系統主要是監控主要組件的使用率、飽和度以及錯誤,好比CPU的使用率、CPU的負載等。數據庫
3、監控的方式
監控的主要方式有:apache
-
健康檢查。健康檢查是對應用自己健康情況的監控,檢查服務是否還正常存活。 -
日誌。日誌是排查問題的主要方式,日誌能夠提供豐富的信息用於定位和解決問題。 -
調用鏈監控。調用鏈監控能夠完整的呈現出一次請求的所有信息,包括服務調用鏈路、所耗時間等。 -
指標監控。指標是一些基於時間序列的離散數據點,經過聚合和計算後能反映出一些重要指標的趨勢。
在上述4中監控方式中,健康檢查是雲平臺等基礎設施提供的能力,日誌則通常有單獨的日誌中心進行日誌的採集、存儲、計算和查詢,調用鏈監控通常也有獨立的解決方案進行服務調用的埋點、採集、計算和查詢,指標監控則是經過一些exporter抓取目標暴露的指標,而後對這些指標數據進行清理、聚合,咱們經過聚合出來的數據進行展現、告警等。
❝說明:該方案主要是針對指標監控
❞
4、監控選型
4.一、健康檢查
雲平臺提供健康檢查能力,直接在雲平臺中配置。
4.二、日誌
成熟的開源日誌解決方案是ELK。
4.三、調用鏈監控
調用鏈健康使用第三方的健康軟件,經常使用的有skywalking、zikpin、pinpoint、elastic APM、Cat。其中zikpin和cat對代碼有必定的侵入性,而skywalking、pinpoint、elastic APM是基於字節碼注入技術,對代碼沒有侵入性,並且改動最小。
pinpoint的agent僅支持java和php,而skywalking和elastic APM都支持多種語言,好比java/nodejs/go等。
在雲原生環境下,skywalking和elastic APM更適合。
elastic APM直接使用es做爲存儲,能夠在kibana上直接看應用信息,可是其關係圖是須要產生必定的費用。skywalking是國人開源的一款產品,已經畢業於Apache 基金會,社區很是活躍,版本迭代很快,專爲微服務、雲原生架構和基於容器(Docker、K8s、Mesos)架構而設計。
pinpoint和skywalking的對比可參考:http://skywalking.apache.org/zh/blog/2019-02-24-skywalking-pk-pinpoint.html
4.四、指標監控
在雲原生環境、傳統的監控方式並不適合,在傳統環境,zabbix無疑是首選,可是在雲原生環境,Prometheus卻成爲了熱門,其主要緣由有:
-
成熟的社區支撐。Prometheus是CNCF的畢業項目,有許多大廠作背書,社區龐大,是首推的雲原生監控解決方案。 -
易於部署和運維。Prometheus核心只有一個二進制文件,沒有其餘的第三方依賴,部署運維均十分方便。 -
採用Pull模型,經過HTTP的Pull方式從各個監控目標拉取監控數據。Push模型通常經過Agent方式去採集信息並推送到收集器中,每一個服務的Agent都須要配置監控數據項與監控服務端的信息,在大量服務時會加大運維難度;另外,採用Push模型,在流量高峯期間監控服務端會同時接收到大量請求和數據,會給監控服務端形成很大壓力,嚴重時甚至服務不可用。 -
強大的數據模型。Prometheus採集到的監控數據均以指標的形式存在於內置的時序數據庫中,除了基本的指標名稱外,還支持自定義的標籤。經過標籤能夠定義出豐富的維度,方便進行監控數據的聚合和計算。 -
強大的查詢語言PromQL。經過PromQL能夠實現對監控數據的查詢、聚合、可視化、告警。 -
完善的生態。常見的操做系統、數據庫、中間件、類庫、編程語言,Prometheus都提供了接入方案,而且提供了Java/Golang/Ruby/Python等語言的客戶端SDK,可以快速實現自定義的監控邏輯。 -
高性能。Prometheus單一實例便可處理數以百計的監控指標,每秒處理數十萬的數據,在數據採集和查詢方面有着優異的性能表現。
❝注意:因爲採集的數據有可能丟失,Prometheus並不適合對採集數據要求100%準確的場景。
❞
5、Prometheus監控系統概述
監控系統的總體框架以下:
-
Prometheus Server:用於抓取指標、存儲時間序列數據 -
exporter:暴露指標讓任務來抓 -
pushgateway:push 的方式將指標數據推送到該網關 -
alertmanager:處理報警的報警組件 -
adhoc:用於數據查詢
其流程很簡單,Prometheus server端能夠直接接收或者經過pushgateway獲取到數據,存儲到TSDB中,而後對數據進行規則整理,經過Altermanager進行報警或者經過Grafana等工具進行展現。
6、指標監控的對象
監控系統通常採用分層的方式劃分監控對象。在咱們的監控系統中,主要關注如下幾種類型的監控對象:
-
主機監控,主要指主機節點軟、硬件資源的一些監控數據。
-
容器環境監控,主要指服務所處運行環境的一些監控數據。
-
應用服務監控,主要指服務自己的基礎數據指標,提現服務自身的運行情況。
-
第三方接口監控,主要指調用其餘外部服務接口的狀況。
對於應用服務和第三方接口監控,咱們經常使用的指標包括:響應時間、請求量QPS、成功率。
6.一、主機監控
6.1.一、爲何須要主機監控
主機是系統的載體,一切系統應用都運行在主機之上,若是某臺或者幾臺主機宕機,會致使上面運行的因此應用都沒辦法正常提供服務,嚴重者致使生產事故。因此對主機的監控與預警是很是有必要的,咱們能夠在其出故障以前對其進行處理,避免嚴重的事故發生。
6.1.二、如何判斷資源狀況
主機的監控主要從一下三個方面來綜合考慮其狀態:
-
使用率:資源忙於工做的平均時間,一般是隨時間變化的百分比 -
飽和度:資源隊列的長度 -
錯誤:資源錯誤事件的計數
6.1.三、哪些資源須要監控
主機的主要資源對象有:
-
CPU -
內存 -
磁盤 -
可用性 -
服務狀態 -
網絡
6.1.四、如何進行監控
在Prometheus監控方案中,主機的資源指標是經過node-exporter來進行採集,而後存儲在Prometheus時序數據庫裏,而後能夠經過PromQL來查詢各個指標的具體狀況。
一、CPU
CPU主要從使用率和飽和度來進行監控。(1)、使用率,指標node_cpu_seconds_total
一般會根據CPU使用率超過多少來進行告警,好比當CPU使用率大於80%,則進行告警,固然CPU是一個Gauge類型的,它的數據是會上下增減的,因此咱們在判斷CPU使用率的時候一般是一段時間內CPU持續高達多少的時候才進行告警,好比下面的表達式就是統計5分鐘內CPU使用率大於60%的主機:
100-(avg(irate(node_cpu_seconds_total{mode="idle"}[5m])) by(instance)* 100) > 60
CPU指標還有用戶態、內核態的指標數據,這個根據狀況來進行監控。(2)、飽和度,指標node_load
CPU的飽和度一般指的是CPU的負載狀況。正常狀況下CPU的總體負載不超過CPU的總數,好比2顆CPU,則負載不超過2。咱們收集到的指標有1分鐘、5分鐘、15分鐘的負載數據,在配置監控的時候選擇好統計時間,通常狀況下會選擇5分鐘的負載做爲統計,以下表示5分鐘的負載大於CPU的總數的2倍:
node_load5 > on (instance) 2 * count by(instance)(node_cpu_seconds_total{mode="idle"})
二、內存
內存主要從使用率和飽和度來進行監控。(1)、使用率 內存的使用率能夠直觀的看到總體CPU的使用狀況,其計算方式使用(free + buffer + cache)/ total。指標主要有:
-
node_memory_MemTotal_bytes:主機上的總內存 -
node_memory_MemFree_bytes:主機上的可用內存 -
node_memory_Buffers_bytes:緩衝緩存中的內存 -
node_memory_Cached_bytes:頁面緩存中的內存
好比下面的表達式是用於統計內存使用率大於80%:
100 - sum(node_memory_MemFree_bytes{job="node-exporter"} + node_memory_Buffers_bytes{job="node-exporter"} + node_memory_Cached_bytes{job="node-exporter"})by (instance) / sum(node_memory_MemTotal_bytes{job="node-exporter"})by(instance)*100 > 80
(2)、飽和度 內存的飽和度是指內存和磁盤的讀寫狀況來監控。指標有:
-
node_vmstat_pswpin:系統每秒從磁盤讀到內存的字節數,單位KB -
node_vmstat_pswpout:系統每秒從內存寫到磁盤的字節數,單位KB
三、磁盤
磁盤的監控有點特殊,咱們不按着USE的方法去測量。若是單考慮其使用率並無多大的效果,由於10G剩餘20%和1T剩餘20%對咱們的影響是不同的,因此咱們能夠監控其增加趨勢以及方向。好比:根據前面1h的磁盤增加狀況來預測在4h內是否會把磁盤用完。
predict_linear(node_filesystem_free_bytes{job="node-exporter",mountpoint!=""}[1h], 4*3600)
固然,若是僅僅這樣預測也會產生不少垃圾告警,由於在某一小時的增加速度可能很快,這樣算下來預測會在接下來的4小時內使用完,可是咱們登上主機一看,才用了40%,這時候就算有告警咱們也不會處理。因此咱們還能夠再加一個條件,好比磁盤使用率大於80%而且在接下來的4小時內會使用完。以下:
(100 - (node_filesystem_avail_bytes{fstype!="",job="node-exporter"} / node_filesystem_size_bytes{fstype!="",job="node-exporter"} * 100)>80) and (predict_linear(node_filesystem_free_bytes{job="node-exporter",mountpoint!="",device!="rootfs"}[1h],4 * 3600) < 0)
除此以外,咱們還須要監控磁盤的IO,不管是雲主機磁盤仍是物理磁盤,每塊盤都有其對應的IOPS,若是某個主機的IO很高,也會致使其餘的問題,好比系統繁忙、負載很高等狀況。node-exporter中定義了其指標,僅須要對其進行聚合,而後對聚合後的數據進行頁面展現或者告警處理。其聚合公式以下:
100-(avg(irate(node_disk_io_time_seconds_total[1m])) by(instance)* 100)
四、可用性
可用性是指的主機可用性,咱們能夠經過up
指標來判斷主機是否可用,若是其值等於0表示宕機,等於1表示存活。好比下面便可表示主機不可用:
up{job="node-exporter"}==0
五、服務狀態
服務狀態旨在監控關鍵服務,好比docker.service,ssh.service,kubelet.service等。指標爲:
-
node_systemd_unit_state
好比監聽docker.service的服務狀態爲存活:
node_systemd_unit_state{name="docker.service",state="active"} == 1
監控主要服務,以便於咱們能在服務出問題的第一時間收到消息進行處理。
六、網絡
網絡主要是監控其在每臺主機上的出入流量,還有TCP鏈接狀態。prometheus的node-exporter會抓取每臺主機的網卡以及其出入網卡的流量,還有每臺主機的TCP狀態,咱們能夠將須要的指標進行聚合,根據聚合後的指標數據再進行頁面展現或者告警處理。好比統計流入的流量:
((sum(rate (node_network_receive_bytes_total{device!~'tap.*|veth.*|br.*|docker.*|virbr*|lo*'}[5m])) by (instance)) / 100)
統計流出的流量:
((sum(rate (node_network_transmit_bytes_total{device!~'tap.*|veth.*|br.*|docker.*|virbr*|lo*'}[5m])) by (instance)) / 100)
以及統計TCP狀態爲ESTABLISHED的數量:
node_netstat_Tcp_CurrEstab
咱們能夠根據每一個指標作對應的監控以及告警。
6.二、容器監控
6.2.一、爲何須要容器監控
在雲原生時代,容器是咱們應用的載體,它至關於應用的基礎設施,因此對其的監控是頗有必要的。咱們在建立一個容器的時候每每會給其cpu和內存一個限制值,特別是內存,若是其使用達到了限制值,就會致使OOM,這時候咱們就會作升級配置或查找緣由處理。
6.2.二、監控的指標對象主要有哪些
監控對象主要有一下:
-
cpu -
memory -
事件
6.2.三、如何進行監控
咱們使用cAdvisor來獲取容器指標(kubelet已經集成了這個服務)。
一、CPU
在容器中,就簡單經過其使用率來監控其狀態,咱們經過其(使用量/limit)來獲得其使用率。以下:
sum(
node_namespace_pod_container:container_cpu_usage_seconds_total:sum_rate
* on(namespace,pod)
group_left(workload, workload_type) mixin_pod_workload
) by (workload, workload_type,namespace,pod)
/sum(
kube_pod_container_resource_limits_cpu_cores
* on(namespace,pod)
group_left(workload, workload_type) mixin_pod_workload
) by (workload, workload_type,namespace,pod) * 100 > 80
若是CPU的使用率持續大於咱們設定的閾值,則考慮增長CPU的Limit值。
二、memory
和CPU同樣,經過其使用率來觀察容器的內存是否充足。以下:
sum(
container_memory_working_set_bytes
* on(namespace,pod)
group_left(workload, workload_type) mixin_pod_workload
) by (namespace,pod) / sum(
kube_pod_container_resource_limits_memory_bytes
* on(namespace,pod)
group_left(workload, workload_type) mixin_pod_workload
) by (namespace,pod) * 100 / 2 > 80
若是內存的使用率大於咱們設定的閾值,則考慮是否須要增長Pod的內存了。
三、事件
這裏的事件針對的是kubernetes的pod事件。在Kubernetes中,事件分爲兩種,一種是Warning事件,表示產生這個事件的狀態轉換是在非預期的狀態之間產生的;另一種是Normal事件,表示指望到達的狀態,和目前達到的狀態是一致的。咱們用一個Pod的生命週期進行舉例,當建立一個Pod的時候,首先Pod會進入Pending的狀態,等待鏡像的拉取,當鏡像錄取完畢並經過健康檢查的時候,Pod的狀態就變爲Running。此時會生成Normal的事件。而若是在運行中,因爲OOM或者其餘緣由形成Pod宕掉,進入Failed的狀態,而這種狀態是非預期的,那麼此時會在Kubernetes中產生Warning的事件。那麼針對這種場景而言,若是咱們可以經過監控事件的產生就能夠很是及時的查看到一些容易被資源監控忽略的問題。
在kubernetes中經過kube-eventer
來進行事件監控,而後針對不一樣的事件來進行告警通知。
6.三、應用服務監控
6.3.一、爲何須要應用服務監控
應用是業務的載體,也是用戶最直觀的體驗,應用的狀態與否直接關係到業務的優良以及用戶的體驗。若是沒有對其作好必定的監控措施,可能會出現如下問題:
-
沒法識別或診斷故障 -
沒法衡量應用程序的運行性能 -
沒法衡量應用程序或組件的業務指標以及成功與否,例如跟蹤銷售數據或交易價值
6.3.二、有哪些監控指標
-
應用程序監控 -
HTTP接口:URL存活、請求量、耗時、異常量
-
JVM :GC次數、GC耗時、各個內存區域的大小、當前線程數、死鎖線程數
-
線程池:活躍線程數、任務隊列大小、任務執行耗時、拒絕任務數
-
鏈接池:總鏈接數、活躍鏈接數
-
業務指標:視業務來定,好比PV、訂單量等
6.3.三、如何進行監控
一、應用程序監控
應用程序指標能夠衡量應用程序的性能和狀態,包括應用程序最終用戶的體驗,如延遲和響應時間。在這背後,咱們測量了應用程序的吞吐量:請求、請求量、事務和事務時間。「(1)、HTTP接口監控」可使用prometheus的blackbox_exporter來進行接口存活的監控,能夠用於對http,https,tcp,dns以及ICMP協議進行探測,從而抓取數據進行監控。
「(2)、JVM監控」經過在應用中埋點來暴露JVM數據,使用Prometheus監控採集JVM數據,藉助Prometheus Grafana大盤來展現JVM數據,並建立報警,便可實現利用Prometheus監控JVM的目的。(1)、在pom.xml文件中添加Maven依賴。
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient_hotspot</artifactId>
<version>0.6.0</version>
</dependency>
(2)、在能夠執行初始化的位置添加初始化JVM Exporter的方法。
@PostConstruct
public void initJvmExporter() {
io.prometheus.client.hotspot.DefaultExports.initialize();
}
(3)、在/src/main/resources/application.properties文件中配置用於Prometheus監控的端口(Port)和路徑(Path)
management.port: 8081
endpoints.prometheus.path: prometheus-metrics
(4)、在/src/main/java/com/monitise/prometheus_demo/PrometheusDemoApplication.java文件中打開HTTP端口
@SpringBootApplication
// sets up the prometheus endpoint /prometheus-metrics
@EnablePrometheusEndpoint
// exports the data at /metrics at a prometheus endpoint
@EnableSpringBootMetricsCollector
public class PrometheusDemoApplication {
public static void main(String[] args) {
SpringApplication.run(PrometheusDemoApplication.class, args);
}
}
而後在部署應用的時候暴露接口和數據就能夠進行採集了。因爲應用比較多就能夠經過自動發現的方式來作。咱們在service中加上下面的annotations,就能夠自動發現了。
prometheus.io/scrape: 'true'
prometheus.io/path: '/prometheus-metrics'
prometheus.io/port: '8081'
二、業務指標監控
業務指標是應用程序指標的更進一層,它們一般與應用程序指標同義。若是你考慮將對特定服務的請求數量做爲應用程序指標進行測量,那麼業務指標一般會對請求的內容執行某些操做。一個應用程序指標的示例多是測量支付交易的延遲,相應的業務指標多是每一個支付交易的價值。業務指標可能包括新用戶/客戶的數量、銷售數量、按價值或位置劃分的銷售額,或者其餘任何有助於衡量業務情況的指標。
6.四、第三方接口監控
6.4.一、爲何須要第三方接口監控
第三方接口的優良直接影響自身業務,因此對第三方接口的異常狀況監控是很是重要的。主要是其的響應時間、存活性以及成功率。
6.4.二、有哪些監控指標
-
響應時間 -
存活性 -
成功率
6.4.三、如何進行監控
可使用prometheus的blackbox_exporter來進行接口的監控。經過第三方接口監控的維度,咱們能夠方便地將自身服務與所使用到的第三方服務關聯起來,以統一的視圖展現服務用到了哪些第三方服務接口、這些第三方服務接口的響應時間和成功率是多少。當服務出現異常時,對於定位問題有很大幫助;同時,一些內部的服務可能監控報警並不全面,第三方監控也能幫助他們提高服務質量。
7、告警通知
達到什麼閾值須要告警?對應的故障等級是多少?不須要處理的告警不是好告警,可見定義合理的閾值有多重要,不然只會下降運維效率或者讓監控系統失去它的做用。
Prometheus容許基於PromQL定義報警的觸發條件,Prometheus週期性的對PromQL進行計算,當知足條件時就會向Alertmanager發送報警信息。
在配置告警規則的時候,咱們將按組進行分類,這樣就能夠對相同組下的告警進行聚合,方便配置以及查看。Alertmanager在接收到報警後,能夠對報警進行分組、抑制、靜默等額外處理,而後路由到不一樣的接收器。Alertmanager支持多種報警通知方式,除經常使用的郵件通知外,還支持釘釘、企業微信等方式,也支持經過webhook自定義通知方式。咱們能夠按輕重緩急定義不一樣的通知方式,這樣就能夠根據不一樣通知方式採起不一樣的措施。
8、故障處理流程
收到故障告警後,必定要有相應的處理流程和oncall機制,讓故障及時被跟進處理。
8.一、故障等級劃分
在處理故障以前,須要先清晰的認識是什麼樣的故障,而後再採起什麼樣的措施。因此咱們就須要對故障等級作一個劃分。例如將系統故障等級按照《信息系統安全等級保護基本要求》具體劃分爲四個等級,一級和二級故障爲重大故障;三級和四級故障爲通常性故障。
8.1.一、一級故障
系統發生故障,預計將已經嚴重影響公司生產業務系統,致使相關生產業務系統中斷1小時以上,並預計24小時之內沒法恢復的,具有如下一個或幾個特徵,既定義爲一級故障。
-
公司機房網絡與阿里雲VPC網絡出現故障,致使工做人員和用戶沒法訪問相關業務系統; -
WEB網站和APP系統等關鍵服務器宕機或有其餘緣由致使拒絕提供服務的; -
利用技術手段形成業務數據被修改、假冒、泄漏、竊取的信息系統安全事件; -
由病毒形成關鍵業務系統不能正常提供服務。
8.1.二、二級故障
信息系統發生故障,預計將或已經嚴重影響公司生產業務系統,致使相關生產業務系統中斷1小時以上,並預計24小時之內能夠恢復的,具有如下一個或幾個特徵,即定義爲二級故障。
-
公司機房網絡與阿里雲VPC出現線路和設備故障; -
WEB網站和APP系統等關鍵服務器宕機或有其餘緣由致使拒絕提供服務的; -
12小時之內沒法解決的三級故障。
8.1.三、三級故障
知足如下條件之一,即定義爲三級故障。
-
故障發生後,影響到信息系統的運行效率,速度變慢,但不影響業務系統訪問; -
故障發生後預計在12小時之內恢復; -
24小時之內沒法解決的四級故障
8.1.四、四級故障
知足如下條件之一,即定義爲四級故障。
-
故障發生後,可隨時應急處理,不會影響系統的全面運行; -
生產業務系統設備因病毒攻擊等緣由,形成網絡數據出現偶爾掉包,但不影響系統的正常訪問和運行。
8.二、故障處理程序
8.2.一、故障發現
工做人員在發現故障或接收到故障報告後,首先要記錄故障發生時間和發現時間,及發現部門,發現人及聯繫電話,對故障的等級進行初步斷定,並報告相關人員進行處理。
8.2.二、故障處理
-
發生故障的系統通知到運維人員,運維人員應先詢問了解設備和配置近期的變動狀況,查清故障的影響範圍,從而肯定故障的等級和發生故障的可能位置; -
對於通常性故障按照規定的故障升級上報要求進行上報,並在處理過程當中及時向主管領導通報故障處理狀況; -
對於重大故障按照規定的故障升級上報要求進行上報,並在處理過程當中及時向主管領導通報故障處理狀況。
8.2.三、故障上報
根據故障等級和發生的時限,要對故障的狀況進行及時的上報,並對報告人,告知人際時間內容進行記錄。重大故障由故障處理組領導負責上報,通常性故障由故障處理人員負責上報。故障升級上報時限以下表所示:
上報時限 | 一級故障 | 二級故障 | 三級故障 | 四級故障 |
---|---|---|---|---|
當即 | 運維主管 | 運維人員 | 運維人員 | 運維人員 |
半小時 | 技術總監 | 運維主管 | ||
1小時 | 技術總監 | 運維主管 | ||
4小時 | 技術總監 | |||
12小時 | 運維主管 | |||
24小時 |
8.三、故障處理流程圖
完
公衆號:運維開發故事
github:https://github.com/orgs/sunsharing-note/dashboard
愛生活,愛運維
若是你以爲文章還不錯,就請點擊右上角選擇發送給朋友或者轉發到朋友圈。您的支持和鼓勵是我最大的動力。喜歡就請關注我吧~
掃碼二維碼
關注我,不按期維護優質內容
舒適提示
若是你喜歡本文,請分享到朋友圈,想要得到更多信息,請關注我。
........................
本文分享自微信公衆號 - 運維開發故事(mygsdcsf)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。