OpenStack 是一個不斷髮展的系統,因此 OpenStack 的架構是演進的,舉個例子:linux
Compute 是 Nova;Image 是 Glance,爲 Nova 提供鏡像存儲服務;Object 是提供 Object 存儲服務的 Swift;Dashboard 是咱們平時說的 Horizon;Identity 是 Keystone;算法
有這七個組件能夠搭出一個相對完整的雲計算環境,Heat、Sahala 是可選的;相對 E 版本,新增長的兩個組件分別是 Block Storage Cinder 和 Network Neutron,這兩個組件和 Glance,Swift 之間沒有直接的聯繫,其實是從 Compute Network 和 Compute Volume 發展出來的,Neutron 組件並無直接的去替換 Compute Network,它是一個相對獨立的,也是很是著名的 SDN 的一個項目,它爲 Compute 提供網絡鏈接,提供網絡的資源管理這樣一些服務,Block Storage(也就是 Cinder)爲 Compute 提供塊存儲服務,替換了 Compute Volume.數據庫
OpenStack 的邏輯關係是要各個組件之間的信息傳輸來實現的,而組件之間的信息傳輸主要是經過OpenStack 之間相互調用 API 來實現的,做爲一個操做系統,做爲一個框架,它的 API 有着重要的意義。編程
基於 HTTP 協議,RESTful Web API;json
什麼是 REST?後端
全稱是:Representational State Transfer,表現狀態傳輸。由 Fielding 博士(HTTP 協議的1.0 和 1.1 版本的主要設計者,Apache 服務器軟件的做者之一,Apache 基金會的第一任主席)提出。REST 是經過操做資源的表現來操做資源的狀態。api
另一種 Web 服務接口協議是 SOAP。瀏覽器
二者的區別,RESTful Web API 的調用很是簡單,可是咱們平時編程的時候用 SOAP 多是基於一些框架在去作,.Net,Java 的這些都已經很成熟了,咱們感覺不到底層機制的這種複雜性,而 REST 其實和 SOAP 比起來很是之簡潔的,另一方面,REST 描述的是一種風格一種架構一種原則,因此它並無規定具體的實踐方式或者說協議。
目前最多見的實現方式就是基於 HTTP 協議實現的 RESTful Web API,咱們的 OpenStack 裏面用的就是這種方式。REST 架構裏面對資源的操做,包括:獲取、建立、修改和刪除,正好對應着 HTTP 協議提供的 GET、POST、PUT 和 DELETE 方法,因此用 HTTP 來實現 REST 是比較方便的。服務器
RESTful Web API 主要有如下三個要點:網絡
1 資源地址與資源的 URI,好比:http://example.com/resources/
2 傳輸資源的表現形式,指的是 Web 服務接受與返回的互聯網媒體類型,好比:JSON,XML 等,其中 JSON 具備輕量級的特色,移動互聯網的飛速發展輕量級的協議很是受歡迎,JSON 獲得了普遍的應用 3 對資源的操做,Web 服務在該資源上所支持的一系列請求方法,好比:POST,GET,PUT,DELETE
下面以 OpenStack Swift 的接口做爲一個例子來講明:
1 首先,用 curl 命令訪問一個 http 服務 2 crul -i -X GET http://storage.clouddrive.com/v1/my_account?format=json\ -H
3 "X-Auth-User:jdoe" -H "X-Auth-Key:jdoepassword"
返回結果:
它是一個 HTTP 響應的頭部,有 Account 的細節信息,包括這個 Account 有多少個 ,Container 有多少個 Object,佔用了多少字節的存儲空間等等。
而後是 JOSN 格式的內容,列出了這個 Account 下面的 Container
調用及調試 API 的幾種方式:
1 第一種方式:curl 命令(linux 下發送 HTTP 請求並接受響應的命令行工具),這種方式其實用的比較少,比較麻煩 2 第二種方式:比較經常使用的 OpenStack 命令行客戶端,每個 OpenStack 項目都有一個 Python 寫的命令行客戶端 3 第三種方式:用 Firefox 或 Chrome 瀏覽器的 REST 的客戶端(圖形界面的,瀏覽器插件) 4 第四種方式:用 OpenStack 的 SDK,能夠不用手動寫代碼發送 HTTP 請求調用 REST 接口,還省去了一些管理諸如 Token 等數據的工做,可以很方便地基於 OpenStack 作開發,
那麼 OpenStack 官方提供的是 Python 的 SDK,固然還有第三方提供的 SDK 好比說支持 Java 的著名的 Jclouds,還有支持 Node.js、Ruby、.Net 等等
OpenStack 還提供了另一套 API 兼容亞馬遜的 EC2,可以方便應用在兩套系統之間作遷移。
OpenStack 組件之間的通訊分爲四類:
1 基於 HTTP 協議 2 基於 AMQP(Advanced Message Queuing Protocol,一個提供統一消息服務的應用層標準高級消息隊列協議) 協議(基於消息隊列協議) 3 基於數據庫鏈接(主要是 SQL 的通訊) 4 Native API(基於第三方的 API)
有個概念須要瞭解一下:
1 Compute Node 是實際運行虛擬機的節點 2 Block Storage Node 主要是 Cinder 鏈接的存儲後端(存儲設備) 3 Network Node 一般是具備路由等一些網關功能的節點(網絡設備)
3-1. 基於HTTP協議進行通訊
經過各項目的 API 創建的通訊關係,基本上都屬於這一類,這些 API 都是 RESTful Web API,最多見的就是經過 Horizon 或者說命令行接口對各組件進行操做的時候產生的這種通訊,
而後就是各組件經過 Keystone 對用戶身份進行校驗,進行驗證的時候使用這種通訊,還有好比說 Nova Compute 在獲取鏡像的時候和 Glance 之間,對 Glance API 的調用,
還有比方說 Swift 數據的讀寫,也是經過這個 HTTP 協議的 RESTful Web API 來進行的。
3-2. 基於高級消息隊列協議
基於 AMQP 協議進行的通訊,主要是每一個項目內部各個組件之間的通訊,比方說 Nova 的 Nova Compute 和 Scheduler 之間,而後 Cinder 的 Scheduler 和 Cinder Volume之間。
須要說明的是,Cinder 是從 Nova Volume 演化出來的,因此 Cinder 和 Nova 之間也有經過 AMQP 協議的通訊關係,因爲 AMQP 協議進行通訊也屬於面向服務的架構,
雖然大部分經過 AMQP 協議進行通訊的組件屬於同一個項目,可是並不要求它們安裝在同一個節點上,給系統的橫向擴展帶來了很大的好處,
能夠對其中的各個組件分別按照他們負載的狀況進行橫向擴展,由於他們不在一個節點上,分別用不一樣數量的節點去承載它們的這些服務。
( AMQP 是一種協議,OpenStack 沒有規定它是用什麼實現,咱們常用的是 Private MQ,實際上用戶也能夠根據自身的狀況選擇其它的消息中間件。)
3-3. 基於SQL的通訊
經過數據庫鏈接實現通訊,這些通訊大多也屬於各個項目內部,也不要求數據庫和項目其它組件安裝在同一個節點上,它也能夠分開安裝,還能夠專門部署數據庫服務器,
把數據庫服務放到上面,之間經過基於 SQL 的這些鏈接來進行通訊。OpenStack 沒有規定必須使用哪一種數據庫,雖然一般用的是 MySQL
3-4. 經過Native API實現通訊
出如今 OpenStack 各組件和第三方的軟硬件之間,好比說,Cinder 和存儲後端之間的通訊,Neutron 的 agent 或者說插件和網絡設備之間的通訊,
這些通訊都須要調用第三方的設備或第三方軟件的 API,咱們稱爲它們爲 Native API,那麼這個就是咱們前面說的基於第三方 API 的通訊。
OpenStack的存儲服務分爲三種:
Glance:
Glance(鏡像存儲)是一個鏡像存儲管理服務,自己不具有存儲的功能;
Swift:
Swift (對象存儲)提供的是對象存儲服務,一樣的具備像亞馬遜 IWSS3 的特色,提供經過RESTful API 的方式去訪問數據,
這樣作是爲了解決兩個問題:第一個,咱們能夠直接去訪問一個存儲,而不須要在經過本身開發的 Web 服務器去作一次數據的轉發,不然對服務器的負載來講是一種壓力。
第二個,在咱們的大數據時代,當數據量特別大的時候,若是咱們用文件系統就會出現問題:文件的數量激增之後,存儲的性能會急劇降低,而對象存儲實際上則是解決這個問題的,
對象存儲拋棄了那種目錄樹的結構,用一種扁平化的結構去管理數據。Swift 實際上只有三層結構,即 Account、Container、Object。Object 就是最終的那個數據了,
就是文件,前面有兩級管理,一級是 Container 容器,它把 Object 放到容器裏面,而後再上面一級是 Account,是和帳戶去關聯的,Container 至關因而把這些 Object 作了分類,
用 Account 去跟帳戶關聯起來。
Cinder:
Cinder (塊存儲)提供塊存儲的接口;自己也不提供數據的存儲,後面也須要接一個存儲的後端,像 EMC 的散設備,華爲的存儲設備,NetApp 的存儲設備能夠作它的後端。
還有一個比較火的開源分佈式存儲叫 Ceph,Ceph 也提供塊存儲服務,也能夠用來做爲 Cinder 的後端。Cinder 的做用就是爲 OpenStack 提供塊存儲的接口,
有個很重要的功能叫卷管理功能,虛擬機並不直接去使用存儲設備(並不直接去使用後端的存儲系統),使用的是虛擬機上的塊設備(卷 Volume),
實際上 Cinder 就是建立和管理這些 Volume 而且把它掛載到虛擬機上。Cinder 是從 Nova Volume 裏面獨立出來的,獨立出來以後很受各類存儲廠商的歡迎,
能夠經過寫 Cinder Driver 的形式把本身的存儲設備歸入到 OpenStack 的生態圈裏面去。
三種存儲的概念:
文件存儲:
有 POSIX 接口或者 POSIX 兼容的接口,就可認爲它是一個文件系統,比較典型的分佈式文件系統有像 Glance 的 FS,Hadoop 裏的 HDFS
塊存儲:
電腦上的一個盤格式化以後是一個文件系統,那麼在格式化以前是一個塊設備,也就是塊存儲,實際上咱們在數據中內心面,像 EMC 的不少設備,
像華爲的一些叫做 SAN 的設備,像 NetApp 的一些設備,若是是散存儲通常來講就是提供塊存儲的;
對象存儲:
對象存儲的典型表明是亞馬遜的 AWS S3,它的接口不是 POSIX,也不是像一塊硬盤那樣做爲一個塊存儲的接口,是經過 RESTful Web API 去訪問的,
對於應用開發者來講優點在於能夠很方便的去訪問存儲裏面存的數據,對象存儲裏存的數據一般叫作 Object,實際上它就是 File,
可是對象存儲裏面爲了和文件系統作一個區別,便被叫做對象 Object。
這裏以建立一個虛擬機爲例來了解 OpenStack 是如何工做的,下面的圖是 OpenStack 建立虛擬機整個工做過程:
下面進行簡要的文字說明:
1 登陸界面或命令行經過RESTful API向keystone獲取認證信息。 2 keystone經過用戶請求認證信息,並生成auth-token返回給對應的認證請求。 3 界面或命令行經過RESTful API向nova-api發送一個boot instance的請求(攜帶auth-token)。 4 nova-api接受請求後向keystone發送認證請求,查看token是否爲有效用戶和token。 5 keystone驗證token是否有效,若有效則返回有效的認證和對應的角色(注:有些操做須要有角色權限才能操做)。 6 經過認證後nova-api和數據庫通信。 7 初始化新建虛擬機的數據庫記錄。 8 nova-api經過rpc.call向nova-scheduler請求是否有建立虛擬機的資源(Host ID)。 9 nova-scheduler進程偵聽消息隊列,獲取nova-api的請求。 10 nova-scheduler經過查詢nova數據庫中計算資源的狀況,並經過調度算法計算符合虛擬機建立須要的主機。 11 對於有符合虛擬機建立的主機,nova-scheduler更新數據庫中虛擬機對應的物理主機信息。 12 nova-scheduler經過rpc.cast向nova-compute發送對應的建立虛擬機請求的消息。 13 nova-compute會從對應的消息隊列中獲取建立虛擬機請求的消息。 14 nova-compute經過rpc.call向nova-conductor請求獲取虛擬機消息。(Flavor) 15 nova-conductor從消息隊隊列中拿到nova-compute請求消息。 16 nova-conductor根據消息查詢虛擬機對應的信息。 17 nova-conductor從數據庫中得到虛擬機對應信息。 18 nova-conductor把虛擬機信息經過消息的方式發送到消息隊列中。 19 nova-compute從對應的消息隊列中獲取虛擬機信息消息。 20 nova-compute經過keystone的RESTfull API拿到認證的token,並經過HTTP請求glance-api獲取建立虛擬機所須要鏡像。 21 glance-api向keystone認證token是否有效,並返回驗證結果。 22 token驗證經過,nova-compute得到虛擬機鏡像信息(URL)。 23 nova-compute經過keystone的RESTfull API拿到認證k的token,並經過HTTP請求neutron-server獲取建立虛擬機所須要的網絡信息。 24 neutron-server向keystone認證token是否有效,並返回驗證結果。 25 token驗證經過,nova-compute得到虛擬機網絡信息。 26 nova-compute經過keystone的RESTfull API拿到認證的token,並經過HTTP請求cinder-api獲取建立虛擬機所須要的持久化存儲信息。 27 cinder-api向keystone認證token是否有效,並返回驗證結果。 28 token驗證經過,nova-compute得到虛擬機持久化存儲信息。 29 nova-compute根據instance的信息調用配置的虛擬化驅動來建立虛擬機。
注:
keystone
User:指使用Openstack service的用戶,能夠是人、服務、系統,但凡使用了Openstack service的對象均可以稱爲User。 Project(Tenant):能夠理解爲一我的、或服務所擁有的 資源集合 。在一個Project(Tenant)中能夠包含多個User,每個User都會根據權限的劃分來使用Project(Tenant)中的資源。好比經過Nova建立虛擬機時要指定到某個Project中,在Cinder建立卷也要指定到某個Project中。User訪問Project的資源前,必需要與該Project關聯,而且指定User在Project下的Role。 Role:用於劃分權限。能夠經過給User指定Role,使User得到Role對應的操做權限。Keystone返回給User的Token包含了Role列表,被訪問的Services會判斷訪問它的User和User提供的Token中所包含的Role。系統默認使用管理Role admin和成員Role _member_ Policy:OpenStack對User的驗證除了OpenStack的身份驗證之外,還須要鑑別User對某個Service是否有訪問權限。Policy機制就是用來控制User對Tenant中資源(包括Services)的操做權限。對於Keystone service來講,Policy就是一個JSON文件,默認是/etc/keystone/policy.json。經過配置這個文件,Keystone Service實現了對User基於Role的權限管理。 Token:是一個字符串表示,做爲訪問資源的令牌。Token包含了在 指定範圍和有效時間內 能夠被訪問的資源。EG. 在Nova中一個tenant能夠是一些虛擬機,在Swift和Glance中一個tenant能夠是一些鏡像存儲,在Network中一個tenant能夠是一些網絡資源。Token通常被User持有。 Credentials:用於確認用戶身份的憑證 Authentication:肯定用戶身份的過程 Service:Openstack service,即Openstack中運行的組件服務。 Endpoint:一個能夠經過網絡來訪問和定位某個Openstack service的地址,一般是一個URL。好比,當Nova須要訪問Glance服務去獲取image 時,Nova經過訪問Keystone拿到Glance的endpoint,而後經過訪問該endpoint去獲取Glance服務。咱們能夠經過Endpoint的region屬性去定義多個region。Endpoint 該使用對象分爲三類: admin url –> 給admin用戶使用,Post:35357
internal url –> OpenStack內部服務使用來跟別的服務通訊,Port:5000
public url –> 其它用戶能夠訪問的地址,Post:5000 建立完service後建立API EndPoint. 在openstack中,每個service都有三種end points. Admin, public, internal。 Admin是用做管理用途的,如它可以修改user/tenant(project)。 public 是讓客戶調用的,好比能夠部署在外網上讓客戶能夠管理本身的雲。 internal是openstack內部調用的。
三種endpoints 在網絡上開放的權限通常也不一樣。Admin一般只能對內網開放,public一般能夠對外網開放internal一般只能對安裝有openstack對服務的機器開放。
一個實例:
1 用戶alice登陸keystone系統(password或者token的方式),獲取一個臨時的token和catalog服務目錄
(v3版本登陸時,若是沒有指定scope,project或者domain,獲取的臨時token沒有任何權限,不能查詢project或者catalog)。 2 alice經過臨時token獲取本身的全部的project列表。 3 alice選定一個project,而後指定project從新登陸,獲取一個正式的token,同時得到服務列表的endpoint,用戶選定一個endpoint,
在HTTP消息頭中攜帶token,而後發送請求(若是用戶知道project name或者project id能夠直接第3步登陸)。 4 消息到達endpoint以後,由服務端(nova)的keystone中間件(pipeline中的filter:authtoken)向keystone發送一個驗證token的請求。
(token類型:uuid須要在keystone驗證token,pki類型的token自己是包含用戶詳細信息的加密串,能夠在服務端完成驗證) 5 keystone驗證token成功以後,將token對應用戶的詳細信息,例如:role,username,userid等,返回給服務端(nova)。 6 服務端(nova)完成請求,例如:建立虛擬機。 7 服務端返回請求結果給alice。
cinder:
cinder主要組成:
1 Cinder-api 是 cinder 服務的 endpoint,提供 rest 接口,負責處理 client 請求,並將 RPC 請求發送至 cinder-scheduler 組件。 2 Cinder-scheduler 負責 cinder 請求調度,其核心部分就是 scheduler_driver, 做爲 scheduler manager 的 driver,負責 cinder-volume 具體的調度處理,
發送 cinder RPC 請求到選擇的 cinder-volume。 3 Cinder-volume 負責具體的 volume 請求處理,由不一樣後端存儲提供 volume 存儲空間。
neutron
neutron主要組成:
1.Neutron-server能夠理解爲一個專門用來接收Neutron REST API調用的服務器,而後負責將不一樣的rest api分發到不一樣的neutron-plugin上。 2.Neutron-plugin能夠理解爲不一樣網絡功能實現的入口,各個廠商能夠開發本身的plugin。Neutron-plugin接收neutron-server分發過來的REST API,向neutron database完成一些信息的註冊,而後將具體要執行的業務操做和參數通知給自身對應的neutron agent。 3.Neutron-agent能夠直觀地理解爲neutron-plugin在設備上的代理,接收相應的neutron-plugin通知的業務操做和參數,並轉換爲具體的設備級操做,以指導設備的動做。當設備本地發生問題時,neutron-agent會將狀況通知給neutron-plugin。 4.Neutron database,顧名思義就是Neutron的數據庫,一些業務相關的參數都存在這裏。 5.Network provider,即爲實際執行功能的網絡設備,通常爲虛擬交換機(OVS或者Linux Bridge)。
虛擬機建立的四個階段:
1 scheduling 2 networking 3 block_ device_mapping 4 spawing
幾種通訊關係的體現:
1 各個組件 API 之間的調用,這就屬於 HTTP 通訊; 2 Nova 和 Neutron 內部各個組件的通訊屬於經過 AMQP 協議的通訊; 3 中間頻繁的讀寫數據庫的操做屬於數據庫鏈接進行通訊的; 4 Nova 與 Hypervisor 或者說 Libvirt 交互屬於經過 Native API 即第三方接口去進行通訊的,還有一個就是在給虛擬機準備 Volume 的過程當中 Cinder 還須要和存儲設備進行交互,
這中間也須要用到 Native API 或者是第三方接口;
前面已經從邏輯關係、通訊關係分析了OpenStack 各組件之間的關係,而且也介紹了 OpenStack 的 API 和存儲。
前面談到的各類架構基本上都是屬於軟件上的邏輯架構,可是 OpenStack 是個分佈式的系統,就得解決從邏輯架構到物理架構的映射的問題,也就是 OpenStack 的各個項目、組件按什麼方式安裝到實際的服務器節點上去,實際的存儲設備上,如何經過把它們經過網絡鏈接起來,這就是 OpenStack 的部署架構。
OpenStack的部署分爲:
單節點部署,一般是用於學習或者是開發
多節點部署(集羣部署)
OpenStack 的部署架構不是一成不變的,而是根據實際的需求設計不一樣的實施方案。
在實際生產過程當中,咱們首先要對計算、網絡、存儲所須要的資源進行規劃,雖說咱們如今用的雲計算技術,它比傳統的 IT 架構在資源規劃方面的困難和工做量要小一些,可是仍是須要有一個規劃,這裏學習瞭解一下基本的和複雜的兩種集羣部署架構。
6-1. 簡單部署架構
是一個最基本的生產環境所須要的 OpenStack 的部署狀況。
根據實際須要設計不一樣的實施方案
下面解釋一下 「三種網絡和四種節點」
(1)綠色的管理網絡 + 藍色的存儲網絡 + 黃色的服務網絡
管理網絡 是 OpenStack 的管理節點或者說是它的管理服務對其它的節點去進行管理的這樣一個網絡,他們之間有 「不一樣組件之間的 API 調用,虛擬機之間的遷移」 等等;
存儲網絡 是計算節點訪問存儲服務的網絡,包括向存儲設備裏面讀寫數據的流量基本上都須要從存儲網絡走,還有另一種是服務網絡;
服務網絡 是由 OpenStack 去管理的虛擬機對外提供服務的網絡,服務器上一般都是一臺服務器上帶好幾塊網卡,好幾塊網口,咱們能夠給各個網絡作隔離。隔離的好處是,
它們的流量不會交叉,比方說咱們在讀寫存儲設備的時候,可能在存儲網絡上的流量特別大,可是它不會影響到咱們這些節點對外提供服務,
一樣,在咱們作虛擬機遷移的時候可能在管理網絡上它的數據流量會很是大,可是它一樣不會影響到咱們這些計算節點對存儲設備的讀寫性能。
(2)四種節點:
控制節點(OpenStack 的管理節點,OpenStack 的大部分服務都是運行在控制節點上的,好比說 Keystone 的認證服務,虛擬機鏡像管理服務 Glance 等等)
計算節點(計算節點指的是實際運行虛擬機的節點)
存儲節點(提供對象存儲服務,提供對象存儲的 Swift 的節點或者是 Swift 集羣的 Proxy 節點,也能夠是其它服務的存儲後端)
網絡節點(實現網關和路由的功能)
有些服務能夠直接部署在 Controller 節點上或者說是直接部署在控制節點上,可是特別須要說明的一點是: Nova 和 Neutron 這兩個組件必須採用分佈式部署。說一下 Nova:Nova-Compute 是控制虛擬機的,是控制和管理虛擬機的,因此必須部署在計算節點上,而 Nova 的其它幾個服務則應該部署在控制節點上,特別須要強調一點,Nova-Compute 和 Nova-Conducter 必定不能部署在同一個節點上,把這兩個分開就是爲了解耦。
說一下 Neutron:Neutron 的一些插件或 Agent 須要部署在網絡節點和計算節點上,而其餘的部分,好比說 Neutron Server 能夠部署在控制節點上
6-2. 複雜部署架構
須要掌握三個要點:
1 規模較大的狀況下,把各類管理服務部署到不一樣的服務器上。把這些 服務拆開部署 到不一樣的節點上,甚至要把同 一個服務的不一樣組件也拆開部署,
好比說能夠把 Nova 的數據庫給獨立擰出來部署成一個 MySQL 數據庫集羣,還有 Cinder 裏面的 Scheduler 和 Volume 能夠部署到不一樣的節點上,
實際上由於 Swift 項目具備必定的獨立性,因此 Swift 自己就有跨地域部署的生產環境,規模很是之大,跨地域部署,因此它的服務的可用性極高,天然有這種栽培的特性,
能夠提供極高可用性和數據持久性 的對象存儲服務。因而乎,很容易的對 OpenStack 的這些服務進行橫向擴展,對 OpenStack 整個系統作橫向擴展,
這些讓 OpenStack 具備比較高的負載能力,能夠達到一個比較大的規模。全部的這些都得益於 OpenStack 設計的時候採用了 SO 吻合的面向服務的架構,就是 SOA 架構,
具體到每一個組件如何進行分佈式的部署,如何進行橫向擴展。 2 出於高可用的考慮,生產環境中咱們會把 OpenStack 的同一個服務部署到不一樣的節點上,造成雙機熱備或者多機熱備的高可用集羣。(或者用負載均衡集羣)。 3 在複雜的數據中心環境中,還有不少第三方服務,比方說 LDAP 服務、DNS 服務等等,考慮如何與第三方服務進行對接和集成。好比說,
咱們可能須要使用 OPEN LDAP 服務器來做爲 Keystone 的認證和健全的後端,這些通常是在管理網絡中去完成的。
轉:http://blog.csdn.net/q123_xi/article/details/78550273?locationNum=10&fps=1