# 云計算實(shí)驗(yàn)
如果說亞馬遜的AWS(Amazon Web Service)是一個IaaS平臺痢站,為用戶提供計算服務(wù)(EC2)巍糯、存儲服務(wù)(S3)等基礎(chǔ)設(shè)施服務(wù)的話齿穗,可以說谷歌的GAE(Google App Engine)就是一個PaaS平臺,在GAE上喊括,用戶可以構(gòu)建和運(yùn)行可擴(kuò)展的Web和移動應(yīng)用程序胧瓜,而無需考慮底層基礎(chǔ)設(shè)施的部署、維護(hù)和擴(kuò)展郑什。這使得開發(fā)人員能夠更加專注于業(yè)務(wù)邏輯府喳,以便快速構(gòu)建可擴(kuò)展的應(yīng)用程序。在IaaS平臺上蘑拯,開發(fā)人員需要自己購買虛擬機(jī)钝满、存儲設(shè)備兜粘、網(wǎng)絡(luò)設(shè)備等基礎(chǔ)設(shè)施,并在這些基礎(chǔ)設(shè)置之上安裝和維護(hù)操作系統(tǒng)弯蚜、運(yùn)行環(huán)境和應(yīng)用程序孔轴。盡管這些基礎(chǔ)設(shè)施大多數(shù)已經(jīng)實(shí)現(xiàn)了虛擬化,與傳統(tǒng)的物理設(shè)施相比已經(jīng)節(jié)省了大量的人力和物力熟吏,但維護(hù)這些虛擬的基礎(chǔ)設(shè)施仍然需要相關(guān)的專業(yè)技術(shù)人員距糖,投入大量的時間和精力才能完成。這對于初創(chuàng)型公式或普通的開發(fā)人員而言是難于承擔(dān)的牵寺。作為一個PaaS平臺悍引,GAE可以讓開發(fā)人員直接在Google的基礎(chǔ)架構(gòu)上運(yùn)行網(wǎng)絡(luò)應(yīng)用程序。在 GAE之上易構(gòu)建和維護(hù)應(yīng)用程序帽氓,并且應(yīng)用程序可根據(jù)訪問量和數(shù)據(jù)存儲需要的增長輕松進(jìn)行擴(kuò)展趣斤。使用GAE,開發(fā)人員將不再需要維護(hù)服務(wù)器或虛擬機(jī)黎休,只需上傳應(yīng)用程序浓领,它便可立即即為用戶提供服務(wù)。GAE是一個商業(yè)化的PaaS平臺势腮,自2008 年 4月 發(fā)布第一個版本以來联贩,已吸引了全球數(shù)十萬計的開發(fā)者在其上開發(fā)了眾多的應(yīng)用程序。
作為谷歌推出的一款商業(yè)化云計算平臺捎拯,GAE的底層是建立在Google的基礎(chǔ)架構(gòu)之上的泪幌,開發(fā)人員和用戶在選擇GAE的同時,也就選擇了Google提供基礎(chǔ)設(shè)施資源署照,這在一定程度上限制了開發(fā)人員和用戶的可選擇性祸泪。相對而言,另一個由社區(qū)發(fā)起的開源PaaS項目——AppScale建芙,在這方面可以更好的適應(yīng)開發(fā)人員和用戶的需求没隘。AppScale 是GAE的開源實(shí)現(xiàn),它同時也是一個開源的PaaS平臺禁荸,允許用戶在任何地方(服務(wù)器或集群)發(fā)布和托管自己的 GAE 應(yīng)用程序右蒲。 AppScale支持 GAE平臺的Python、Java赶熟、PHP和Go的運(yùn)行時庫品嚣。用戶可以無縫地將應(yīng)用程序在公共云和自己的虛擬機(jī)、私有云钧大、GAE或者其他云平臺(如AWS)環(huán)境中移植。由于是開源軟件罩旋,AppScale不依賴于任何特定廠商的基礎(chǔ)設(shè)施啊央,開發(fā)人員既可以根據(jù)實(shí)際情況選擇不同廠商提供的基礎(chǔ)設(shè)施服務(wù)眶诈,也可以根據(jù)需求從頭搭建屬于自己的基礎(chǔ)設(shè)施環(huán)境。
接下來瓜饥,我們就以AWS作為IaaS平臺逝撬,以AppScale作為PaaS平臺,通過幾個相互關(guān)聯(lián)的實(shí)驗(yàn)乓土,展示云應(yīng)用宪潮、大數(shù)據(jù)和人工智能領(lǐng)域的幾個典型應(yīng)用案例的開發(fā)過程。
## 第1節(jié) 云應(yīng)用實(shí)驗(yàn)
云應(yīng)用是面向“云”而設(shè)計的應(yīng)用趣苏,因此技術(shù)部分依賴于在傳統(tǒng)云計算的3層概念(基礎(chǔ)設(shè)施即服務(wù)(IaaS)狡相、平臺即服務(wù)(PaaS)和軟件即服務(wù)(SaaS))。云應(yīng)用是天然適合云特點(diǎn)的應(yīng)用食磕,云原生應(yīng)用系統(tǒng)需要與操作系統(tǒng)等基礎(chǔ)設(shè)施分離尽棕,不應(yīng)該依賴Linux或Windows等底層平臺,或依賴某個云平臺彬伦。也就是說滔悉,應(yīng)用從開始就設(shè)計為運(yùn)行在云中,無論私有云或公有云单绑;其次回官,該應(yīng)用必須能滿足擴(kuò)展性需求,垂直擴(kuò)展(向上和向下)或水平擴(kuò)展(跨節(jié)點(diǎn)服務(wù)器)搂橙。
由于云應(yīng)用程序開發(fā)采用與傳統(tǒng)本地應(yīng)用程序完全不同的體系結(jié)構(gòu)歉提,云應(yīng)用和本地應(yīng)用之間存在著較大的差異。例如份氧,在可擴(kuò)展性方面唯袄,傳統(tǒng)的本地應(yīng)用面向的用戶和對資源的消耗量相對穩(wěn)定,對應(yīng)用動態(tài)擴(kuò)展的要求不高蜗帜。而在云環(huán)境下恋拷,云應(yīng)用需要面對大量的互聯(lián)網(wǎng)用戶,用戶群體巨大厅缺,而訪問時間和對資源的消耗量不夠確定蔬顾,有較大的隨機(jī)性。通常情況下湘捎,云應(yīng)用程序可利用云的彈性诀豁,在峰值期間動態(tài)增加資源,峰值消退后動態(tài)釋放資源窥妇。云應(yīng)用根據(jù)需要調(diào)整資源和規(guī)模舷胜,既動態(tài)適應(yīng)需求變化,又避免資源浪費(fèi)活翩。在編程語言方面烹骨,編寫在服務(wù)器上運(yùn)行的本地部署應(yīng)用程序往往使用傳統(tǒng)語言編寫翻伺,如C/C ++,C>诨溃或其他Visual Studio語言(如果部署在Windows Server平臺上)和企業(yè)級Java吨岭。對于云應(yīng)用的開發(fā),面對的問題和采取的技術(shù)與傳統(tǒng)本地應(yīng)用開發(fā)存在較大的差異峦树,云應(yīng)用更多是采用以網(wǎng)絡(luò)為中心的語言編寫辣辫,這意味著使用HTML,CSS魁巩,Java急灭,JavaScript,.Net歪赢,Go化戳,Node.js,PHP埋凯,Python和Ruby等点楼。接下來,我們先介紹AppScale云應(yīng)用開發(fā)環(huán)境的部署方法白对,然后通過幾個實(shí)驗(yàn)分別展示使用Python掠廓、PHP、Go和Java語言開發(fā)云應(yīng)用的方法甩恼。
AWS上提供了預(yù)裝的AppScale環(huán)境蟀瞧,下面介紹在AWS云平臺上啟動預(yù)裝AppScale環(huán)境的全過程。
* 使用賬號密碼登錄AWS管理控制臺条摸,然后選擇‘EC2’
![alt picture](./images/appscale/1.png)
* 繼續(xù)點(diǎn)擊‘啟動實(shí)例’
![alt picture](./images/appscale/2.png)
步驟1:選擇AMI悦污。點(diǎn)擊‘AWS Marketplace’,然后搜索appscale钉蒲,點(diǎn)擊‘選擇’切端,繼續(xù)點(diǎn)擊‘Continue’
![alt picture](./images/appscale/3.png)
步驟2~6一直保持默認(rèn),選擇‘下一步’顷啼,直到步驟7踏枣,點(diǎn)擊‘啟動’
![alt picture](./images/appscale/10.png)
選擇‘創(chuàng)建新密鑰對’,并輸入密鑰對名稱钙蒙,然后下載密鑰對保存到本地茵瀑,繼續(xù)點(diǎn)擊‘啟動實(shí)例’
![alt picture](./images/appscale/11.png)
* 點(diǎn)擊‘查看實(shí)例’,選中剛剛新建的實(shí)例躬厌,可以在下方觀察到該實(shí)例的域名
![alt picture](./images/appscale/13.png)
打開遠(yuǎn)程登錄工具Xshell马昨,輸入``ssh -i "密鑰對文件地址" ubuntu@AWS實(shí)例域名``,選擇之前下載的密鑰對文件,并填入密鑰對名稱鸿捧,進(jìn)行遠(yuǎn)程登錄
注意:若無法連接抢呆,可能是遠(yuǎn)程連接端口22未打開,點(diǎn)擊下方的‘查看入站規(guī)則’進(jìn)行檢查笛谦,若沒有22端口,則需要點(diǎn)擊左側(cè)鏈接昌阿,新建22端口的入站規(guī)則即可饥脑。
![alt picture](./images/appscale/14.png)
切換為root身份并進(jìn)入/root家目錄:``sudo su`` -> ``cd ~``
查看AppScale狀態(tài):``appscale status``
![alt picture](./images/appscale/15.png)
測試:打開瀏覽器輸入``http://域名:1080``或者``https://域名:1443``
![alt picture](./images/appscale/啟動測試.png)
如果無法使用默認(rèn)賬號和密碼登錄系統(tǒng),可以查看AppScale的配置文件AppScalefile懦冰,該文件默認(rèn)存放在/root/目錄中灶轰,可通過vim命令查看配置文件AppScalefile的內(nèi)容:``vim AppScalefile``
![alt picture](./images/appscale/配置文件.png)
AppScale配備了一套名為appscale-tools的工具,可以對AppScale平臺執(zhí)行基本的管理任務(wù)刷钢。例如:
查看AppScale狀態(tài):``appscale status``
啟動AppScale:``appscale up``
關(guān)閉AppScale:``appscale down``
收集AppScale日志:``appscale logs 路徑``
登錄AppScale:``appscale ssh``
升級AppScale:``appscale upgrade``
### 實(shí)驗(yàn)1. 歡迎頁面
__實(shí)驗(yàn)內(nèi)容:分別使用Python笋颤、PHP和Go語言,在AppScale平臺上開發(fā)一個Web應(yīng)用内地,該應(yīng)用在瀏覽器中顯示一個非常簡單的歡迎頁面伴澄。__
首先使用Python語言進(jìn)行開發(fā)。為了完成Python版本的Hello程序阱缓,我們需要準(zhǔn)備兩個文件:app.yaml 和 hello.py非凌。第一個文件 app.yaml 是應(yīng)用的配置文件,文件內(nèi)容如下:
```
application: hello-python
runtime: python27
api_version: 1
threadsafe: true
handlers:
- url: /.*
? script: hello.app
```
其中荆针,`application: hello-python` 表示應(yīng)用名稱敞嗡,該名稱會顯示在AppScale的應(yīng)用列表中。`runtime: python27` 表示該應(yīng)用在python 2.7環(huán)境中運(yùn)行航背。`url: /.*` 中包含了一個url的通配符喉悴,表示客戶向任何 url 路徑發(fā)起的請求,都會轉(zhuǎn)交給 `script: hello.app` 中指定的 `hello.app` 處理玖媚。hello.app 是一個處理客戶端請求的對象箕肃,它在第二個文件 hello.py 中定義,hello.py 是一個用 Python 語言編寫的源程序文件最盅,文件內(nèi)容如下:
```
import webapp2
class MainPage(webapp2.RequestHandler):
? ? def get(self):
? ? ? ? self.response.out.write("<h1 style='text-align: center;'>Hello Python</h1><p style='text-align: center;'>Welcom to Cloud APP World!</p>")
app = webapp2.WSGIApplication([('/', MainPage),], debug=True)
```
其中突雪,`app = webapp2.WSGIApplication` 一行定義了處理網(wǎng)絡(luò)請求的變量app,該變量被實(shí)例化為MainPage類的一個對象涡贱。在類MainPage中定義了get方法咏删,該方法對所有網(wǎng)絡(luò)請求進(jìn)行回應(yīng),回應(yīng)中包含了向客戶端瀏覽器輸出的 HTTP 協(xié)議頭和 HTML 格式的文本信息:`<h1 style='text-align: center;'>Hello Python</h1><p style='text-align: center;'>Welcom to Cloud APP World!</p>` 问词,這些信息最終會以一個 Web 頁面的形式在客戶端瀏覽器中顯示出來督函。
準(zhǔn)備好 app.yaml 和 hello.py 兩個文件后,需要將它們上傳到 AppScale 平臺。先以管理員的賬號登錄AppScale辰狡,點(diǎn)擊頁面左側(cè)導(dǎo)航欄中的 Upload Application锋叨,進(jìn)入應(yīng)用上傳頁面,如圖0所示宛篇。
<div align=center>
<img src="images\hello\upload.png" width=80% height=80% />
<br>圖0. 應(yīng)用上傳頁面<br></div>
從頁面提示中可以看出娃磺,要將應(yīng)用上傳到AppScale平臺,必須先將該應(yīng)用的全部文件打包成一個壓縮文件叫倍,然后再上傳該壓縮文件偷卧。對于Python、Go和PHP語言編寫的應(yīng)用吆倦,壓縮文件中必須包含該應(yīng)用的配置文件app.yaml听诸。按照AppScale的要求,將 app.yaml 和 hello.py 兩個文件打包成zip格式的壓縮文件 package.zip蚕泽,如圖0所示晌梨。
<div align=center>
<img src="images\hello\package-python.png" width=60% height=60% />
<br>圖0. 文件打包<br></div>
點(diǎn)擊應(yīng)用上傳頁面中的“選擇文件”按鈕,選擇 package.zip 文件须妻,如圖0所示仔蝌。
<div align=center>
<img src="images\hello\package-select.png" width=80% height=80% />
<br>圖0. 選擇文件<br></div>
點(diǎn)擊 Upload 按鈕,上傳文件璧南,如圖0所示掌逛。
<div align=center>
<img src="images\hello\package-upload.png" width=80% height=80% />
<br>圖0. 上傳文件<br></div>
文件上傳成功后,頁面會出現(xiàn)應(yīng)用上傳成功的提示司倚,如圖0所示豆混。
<div align=center>
<img src="images\hello\package-success.png" width=80% height=80% />
<br>圖0. 應(yīng)用上傳成功<br></div>
回到AppScale首頁,可以看到正在運(yùn)行的應(yīng)用列表中动知,出現(xiàn)了剛才新上傳的應(yīng)用 hello-python皿伺,如圖0所示。
<div align=center>
<img src="images\hello\hello-python-app.png" width=80% height=80% />
<br>圖0. Hello Python應(yīng)用入口<br></div>
圖中可以看到盒粮,跟應(yīng)用 hello-python 相關(guān)的鏈接有兩個鸵鸥,其中,前一個為 http 鏈接丹皱,后一個為更加安全的 https 鏈接妒穴。點(diǎn)擊其中任何一個鏈接,都可以打開該應(yīng)用摊崭。應(yīng)用的運(yùn)行效果如圖0所示讼油。
<div align=center>
<img src="images\hello\hello-python.png" width=80% height=80% />
<br>圖0. Hello Python應(yīng)用<br></div>
至此,我們就完成了AppScale上的第一個Python應(yīng)用:Hello Python 的開發(fā)和部署過程呢簸。
接下來使用PHP語言進(jìn)行開發(fā)矮台。為了完成PHP版本的Hello程序乏屯,我們需要準(zhǔn)備兩個文件:app.yaml 和 hello.php。第一個文件 app.yaml 是應(yīng)用的配置文件瘦赫,文件內(nèi)容如下:
```
application: hello-php
version: 1
runtime: php
api_version: 1
handlers:
- url: /.*
? script: hello.php
```
其中辰晕,`application: hello-php` 表示應(yīng)用名稱,該名稱會顯示在AppScale的應(yīng)用列表中确虱。`runtime: php` 表示該應(yīng)用在php環(huán)境中運(yùn)行含友。`url: /.*` 中包含了一個url的通配符,表示客戶向任何 url 路徑發(fā)起的請求校辩,都會轉(zhuǎn)交給 `script: hello.php` 處理唱较。hello.php 是需要我們準(zhǔn)備的第二個文件,是一個用 PHP 語言編寫的源程序文件召川,文件內(nèi)容如下:
```
<?php
echo "<h1 style='text-align: center;'>Hello PHP</h1><p style='text-align: center;'>Welcom to Cloud APP World!</p>";
?>
```
該文件內(nèi)容非常簡單,就是向客戶端瀏覽器輸出的一段 HTML 格式的文本信息:`<h1 style='text-align: center;'>Hello PHP</h1><p style='text-align: center;'>Welcom to Cloud APP World!</p>` 胸遇,這些信息最終會以一個 Web 頁面的形式在客戶端瀏覽器中顯示出來荧呐。
接下來將 app.yaml 和 hello.php 兩個文件打包成zip格式的壓縮文件 package.zip,如圖0所示纸镊。
<div align=center>
<img src="images\hello\package-php.png" width=60% height=60% />
<br>圖0. 文件打包<br></div>
通過應(yīng)用上傳頁面上傳 package.zip 文件倍阐,如圖0所示。
<div align=center>
<img src="images\hello\package-select-php.png" width=80% height=80% />
<br>圖0. 選擇文件<br></div>
文件上傳成功后逗威,回到AppScale首頁峰搪,可以看到正在運(yùn)行的應(yīng)用列表中,出現(xiàn)了剛才新上傳的 hello-php 應(yīng)用凯旭,如圖0所示概耻。
<div align=center>
<img src="images\hello\hello-php-app.png" width=80% height=80% />
<br>圖0. Hello PHP應(yīng)用入口<br></div>
圖中可以看到,跟應(yīng)用 hello-php 相關(guān)的鏈接有兩個罐呼,其中鞠柄,前一個為 http 鏈接,后一個為更加安全的 https 鏈接嫉柴。點(diǎn)擊其中任何一個鏈接厌杜,都可以打開該應(yīng)用。應(yīng)用的運(yùn)行效果如圖0所示计螺。
<div align=center>
<img src="images\hello\hello-php.png" width=80% height=80% />
<br>圖0. Hello PHP應(yīng)用<br></div>
至此夯尽,我們就完成了AppScale上的第一個PHP應(yīng)用:Hello PHP的開發(fā)和部署過程。
接下來使用Go語言進(jìn)行開發(fā)登馒。為了完成Go版本的Hello程序向图,我們需要準(zhǔn)備兩個文件:app.yaml 和 hello.go。第一個文件 app.yaml 是應(yīng)用的配置文件按灶,文件內(nèi)容如下:
```
application: hello-go
version: 1
runtime: go
api_version: go1
handlers:
- url: /.*
? script: hello.go
```
其中眶拉,`application: hello-go` 表示應(yīng)用名稱罗晕,該名稱會顯示在AppScale的應(yīng)用列表中。`runtime: go` 表示該應(yīng)用在go環(huán)境中運(yùn)行赠堵。`url: /.*` 中包含了一個url的通配符小渊,表示客戶向任何 url 路徑發(fā)起的請求,都會轉(zhuǎn)交給 `script: hello.go` 處理茫叭。hello.go 是需要我們準(zhǔn)備的第二個文件酬屉,是一個用 Go 語言編寫的源程序文件,文件內(nèi)容如下:
```
package hello
import (
"fmt"
"net/http"
)
func init() {
http.HandleFunc("/", main)
}
func main(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "<h1 style='text-align: center;'>Hello Go</h1><p style='text-align: center;'>Welcom to Cloud APP World!</p>");
}
```
其中揍愁,`func main(w http.ResponseWriter, r *http.Request)` 一行定義了程序執(zhí)行的主函數(shù)main呐萨,該函數(shù)對所有網(wǎng)絡(luò)請求進(jìn)行回應(yīng),回應(yīng)中包含了向客戶端瀏覽器輸出的一段 HTML 格式的文本信息:`<h1 style='text-align: center;'>Hello Go</h1><p style='text-align: center;'>Welcom to Cloud APP World!</p>` 莽囤,這些信息最終會以一個 Web 頁面的形式在客戶端瀏覽器中顯示出來谬擦。
接下來將 app.yaml 和 hello.go 兩個文件打包成zip格式的壓縮文件 package.zip,如圖0所示朽缎。
<div align=center>
<img src="images\hello\package-go.png" width=60% height=60% />
<br>圖0. 文件打包<br></div>
通過應(yīng)用上傳頁面上傳 package.zip 文件惨远,如圖0所示。
<div align=center>
<img src="images\hello\package-select-go.png" width=80% height=80% />
<br>圖0. 選擇文件<br></div>
文件上傳成功后话肖,回到AppScale首頁北秽,可以看到正在運(yùn)行的應(yīng)用列表中,出現(xiàn)了剛才新上傳的 hello-go 應(yīng)用最筒,如圖0所示贺氓。
<div align=center>
<img src="images\hello\hello-go-app.png" width=80% height=80% />
<br>圖0. Hello Go應(yīng)用入口<br></div>
圖中可以看到,跟應(yīng)用 hello-go 相關(guān)的鏈接有兩個床蜘,其中辙培,前一個為 http 鏈接,后一個為更加安全的 https 鏈接邢锯。點(diǎn)擊其中任何一個鏈接虏冻,都可以打開該應(yīng)用。應(yīng)用的運(yùn)行效果如圖0所示弹囚。
<div align=center>
<img src="images\hello\hello-go.png" width=80% height=80% />
<br>圖0. Hello Go應(yīng)用<br></div>
至此厨相,我們就完成了AppScale上的第一個Go應(yīng)用:Hello Go的開發(fā)和部署過程。
最后鸥鹉,我們嘗試將本實(shí)驗(yàn)上傳的3個應(yīng)用從AppScale平臺刪除蛮穿。
點(diǎn)擊AppScale頁面左側(cè)導(dǎo)航欄中的 Delete Application,進(jìn)入刪除應(yīng)用頁面毁渗,點(diǎn)擊頁面中選擇應(yīng)用的下拉框践磅,如圖0所示。
<div align=center>
<img src="images\hello\hello-delete.png" width=80% height=80% />
<br>圖0. 刪除應(yīng)用頁面<br></div>
從下拉框中分別選擇 hello-python灸异、hello-php和hello-go應(yīng)用府适,依次刪除羔飞,如圖0所示。
<div align=center>
<img src="images\hello\hello-delete-select.png" width=80% height=80% />
<br>圖0. 刪除應(yīng)用<br></div>
三個應(yīng)用都刪除完畢后檐春,回到AppScale首頁逻淌,可以看到,正在運(yùn)行的應(yīng)用列表中已經(jīng)沒有hello-python疟暖、hello-php和hello-go三個應(yīng)用的信息了卡儒,如圖0所示。
<div align=center>
<img src="images\hello\hello-delete-ok.png" width=80% height=80% />
<br>圖0. Hello Go應(yīng)用入口<br></div>
至此俐巴,我們就完成了AppScale上的三個版本的Hello應(yīng)用:hello-python骨望、hello-php和hello-go的開發(fā)、部署和刪除過程欣舵。
### 實(shí)驗(yàn)2. 訪客留言
__實(shí)驗(yàn)內(nèi)容:使用Python語言擎鸠,在AppScale平臺上開發(fā)一個記錄訪客留言的應(yīng)用。訪客可以在應(yīng)用中發(fā)布自己的留言缘圈,也可以查看其他訪客發(fā)布的留言糠亩。__
在AppScale的官方安裝包中有一個GuestBook應(yīng)用,該應(yīng)用使用Python語言開發(fā)准验,實(shí)現(xiàn)了最基本的留言簿功能。GuestBook的實(shí)現(xiàn)過程采用了AppScale提供的幾個關(guān)鍵技術(shù)廷没,如:網(wǎng)站框架糊饱、用戶認(rèn)證和數(shù)據(jù)存儲等。這些技術(shù)幾乎在所有AppScale應(yīng)用中都會被用到颠黎,所以理解和掌握GuestBook應(yīng)用的實(shí)現(xiàn)過程另锋,是學(xué)習(xí)AppScale應(yīng)用開發(fā)的一個很好的起點(diǎn)。
在正式分析GuestBook代碼之前狭归,先了解一下程序的運(yùn)行效果夭坪。由于是預(yù)裝應(yīng)用,默認(rèn)情況下过椎,在AppScale首頁的應(yīng)用列表中就可以看到GuestBook應(yīng)用入口室梅,如圖0所示。
<div align=center>
<img src="images\hello\guest-entry.png" width=80% height=80% />
<br>圖0. GuestBook應(yīng)用入口<br></div>
點(diǎn)擊兩個鏈接中的任意一個疚宇,即可進(jìn)入GuestBook應(yīng)用界面亡鼠,如圖0所示。
<div align=center>
<img src="images\hello\guest-blank.png" width=80% height=80% />
<br>圖0. GuestBook應(yīng)用界面<br></div>
應(yīng)用頁面敷待,會顯示最新發(fā)布的10條留言间涵。因?yàn)槭堑谝淮未蜷_應(yīng)用,所以當(dāng)前頁面上還沒有任何留言信息“褚荆現(xiàn)在我們在頁面輸入框中輸入一個新的留言`Hello, GuestBook!`勾哩,點(diǎn)擊`Sign Guestbook`按鈕抗蠢,該留言就會在頁面上顯示出來,留言者為當(dāng)前賬戶:a@a.com思劳,如圖0所示迅矛。
<div align=center>
<img src="images\hello\guest-hello.png" width=80% height=80% />
<br>圖0. 第一條留言信息<br></div>
點(diǎn)擊頁面右上角的Logout按鈕可退出當(dāng)前用戶,此時敢艰,Logout按鈕會變成Login按鈕诬乞。當(dāng)前用戶退出后,仍然可以在頁面中發(fā)布新的留言钠导。例如震嫉,輸入一個新的留言`Who am I?` 點(diǎn)擊`Sign Guestbook`按鈕,該留言會在頁面頂端顯示出來牡属,只是相應(yīng)的留言者變成了匿名用戶` An anonymous person `票堵,如圖0所示。
<div align=center>
<img src="images\hello\guest-who.png" width=80% height=80% />
<br>圖0. 匿名留言<br></div>
點(diǎn)擊頁面右上角的Login按鈕逮栅,會進(jìn)入AppScale的登錄頁面悴势。如圖0所示。
<div align=center>
<img src="images\hello\login.png" width=80% height=80% />
<br>圖0. 登錄頁面<br></div>
在圖0所示的登錄頁面中措伐,既可以使用已有AppScale賬戶登錄特纤,也可以點(diǎn)擊`Create one` 進(jìn)入創(chuàng)建用戶頁面,在該頁面中創(chuàng)建新的AppScale賬戶侥加。例如捧存,創(chuàng)建新用戶b@b.com,如圖0所示担败。
<div align=center>
<img src="images\hello\login-create.png" width=80% height=80% />
<br>圖0. 創(chuàng)建新用戶<br></div>
新用戶創(chuàng)建成功后昔穴,點(diǎn)擊`Login` 按鈕返回登錄頁面,使用新用戶登錄提前,如圖0所示吗货。
<div align=center>
<img src="images\hello\login-b.png" width=80% height=80% />
<br>圖0. 新用戶登錄<br></div>
登錄成功后,頁面會跳轉(zhuǎn)到GuestBook的應(yīng)用頁面狈网,頁面右上角的Logout按鈕表示宙搬,已經(jīng)有用戶登錄。再嘗試發(fā)布一個留言`Hello, I'm Back! `拓哺,可以看到害淤,對應(yīng)的留言者為剛才新登錄的用戶:b@b.com。如圖0所示拓售。
<div align=center>
<img src="images\hello\login-back.png" width=80% height=80% />
<br>圖0. 新用戶登錄<br></div>
需要特別注意的是窥摄,圖0所示的登錄界面和圖0所示的創(chuàng)建用戶界面并不僅僅屬于GuestBook應(yīng)用,它們是AppScale提供的統(tǒng)一的身份認(rèn)證模塊础淤,屬于整個AppScale平臺崭放。運(yùn)行在AppScale平臺上的任何應(yīng)用哨苛,都可以直接使用這個模塊,無需從頭開發(fā)币砂。當(dāng)然這并不是必須的建峭,在大多數(shù)情況下,使用AppScale提供的統(tǒng)一身份認(rèn)證模塊决摧,可以有效減少應(yīng)用開發(fā)工作量亿蒸,還可以更好的與其他應(yīng)用交互,提高應(yīng)用的適應(yīng)性和擴(kuò)展性掌桩。
上述步驟展示了GuestBook應(yīng)用的基本功能边锁。接下來分析GuestBook應(yīng)用的實(shí)現(xiàn)方法。
當(dāng)用戶點(diǎn)擊GuestBook應(yīng)用入口(圖0)波岛,準(zhǔn)備進(jìn)入GuestBook應(yīng)用頁面時茅坛,用戶端瀏覽器會向AppScale服務(wù)器發(fā)起頁面請求。AppScale服務(wù)器收到用戶請求后则拷,會根據(jù)請求頁面的url路徑贡蓖,找到對應(yīng)的處理程序,并將請求轉(zhuǎn)發(fā)給該處理程序煌茬。請求頁面的url路徑和處理程序之間的對應(yīng)關(guān)系斥铺,在應(yīng)用配置文件app.yaml中指定,文件內(nèi)容如下:
```
application: guestbook
runtime: python27
api_version: 1
threadsafe: true
handlers:
- url: /favicon\.ico
? static_files: favicon.ico
? upload: favicon\.ico
- url: /bootstrap
? static_dir: bootstrap
- url: /.*
? script: guestbook.application
libraries:
- name: webapp2
? version: latest
- name: jinja2
? version: latest
```
第一次打開GuestBook應(yīng)用的網(wǎng)頁請求地址為:`/`坛善,和app.yaml文件中`url: /.*`一行匹配晾蜘,對應(yīng)的處理程序?yàn)閌script: guestbook.application`,它表示guestbook.py文件中定義的application對象浑吟。接下來在guestbook.py文件中查看application對象的定義,代碼如下:
```
application = webapp2.WSGIApplication([
? ? ('/', MainPage),
? ? ('/wait', WaitHandler),
? ? ('/sign', Guestbook),
], debug=True)
```
該定義表示耗溜,application是一個處理網(wǎng)頁請求的對象组力,它會根據(jù)請求地址的不同,將請求轉(zhuǎn)發(fā)給不同的處理程序抖拴。此次請求地址為:`/`燎字,對應(yīng)的處理程序?yàn)镸ainPage,代碼如下:
```
class MainPage(webapp2.RequestHandler):
? ? def get(self):
? ? ? ? greetings_query = Greeting.query(ancestor=guestbook_key()).order(-Greeting.date)
? ? ? ? greetings = greetings_query.fetch(10)
? ? ? ? if users.get_current_user():
? ? ? ? ? ? url = users.create_logout_url(self.request.uri)
? ? ? ? ? ? url_linktext = 'Logout'
? ? ? ? else:
? ? ? ? ? ? url = users.create_login_url(self.request.uri)
? ? ? ? ? ? url_linktext = 'Login'
? ? ? ? template = jinja_environment.get_template('index.html')
? ? ? ? self.response.out.write(template.render(greetings=greetings,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? url=url,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? url_linktext=url_linktext))
```
其中阿宅,Greeting是一個新定義的數(shù)據(jù)庫實(shí)體類型候衍,用于記錄用戶的留言信息。每一個Greeting實(shí)體包含3個字段:auther洒放、content和data蛉鹿,分別記錄一條留言的作者、內(nèi)容和時間往湿。Greeting繼承自AppScale內(nèi)置的數(shù)據(jù)庫實(shí)體類型妖异,定義如下:
```
class Greeting(ndb.Model):
? ? author = ndb.UserProperty()
? ? content = ndb.StringProperty(indexed=False)
? ? date = ndb.DateTimeProperty(auto_now_add=True)
```
接下來定義一個名為`default_guestbook`的留言本(Guestbook實(shí)體)惋戏,它是所有留言信息(Greeting實(shí)體)的父節(jié)點(diǎn)。定義一個函數(shù)guestbook_key() 來訪問這個留言本他膳,定義如下:
```
def guestbook_key(guestbook_name='default_guestbook'):
? ? return ndb.Key('Guestbook', guestbook_name)
```
回到MainPage中的下述代碼位置:
`greetings_query = Greeting.query(ancestor=guestbook_key()).order(-Greeting.date)`
這行代碼首先調(diào)用`query(ancestor=guestbook_key())`响逢,獲取祖先節(jié)點(diǎn)為`default_guestbook`留言本的所有留言信息,再調(diào)用`order(-Greeting.date)`函數(shù)對這些留言信息按發(fā)布時間進(jìn)行排序棕孙,排序后的結(jié)果存放到`greetings_query`變量中舔亭。接下來的代碼:
`greetings = greetings_query.fetch(10)`
從`greetings_query`中獲取前10條留言信息,即最新發(fā)布的10條信息蟀俊,存放到`greetings`變量中钦铺。
接下來的代碼:
`users.get_current_user()`
會獲取AppScale的登錄賬號,如果獲取成功欧漱,表示當(dāng)前已有用戶登錄职抡,則生成退出登錄的按鈕:
```
url = users.create_logout_url(self.request.uri)
url_linktext = 'Logout'
```
如果獲取失敗,表示當(dāng)前沒有用戶登錄误甚,則生成登錄系統(tǒng)的按鈕:
```
url = users.create_login_url(self.request.uri)
url_linktext = 'Login'
```
接下來輸出頁面顯示的內(nèi)容:
```
template = jinja_environment.get_template('index.html')
self.response.out.write(template.render(greetings=greetings,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? url=url,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? url_linktext=url_linktext))
```
先將模板文件index.html加載到變量`template` 中缚甩,再將最新留言(greetings)、登錄按鈕(Logint)或退出按鈕(Logout)信息導(dǎo)入變量`template`窑邦,最后調(diào)用write函數(shù)將頁面內(nèi)容輸出到客戶端瀏覽器擅威。其中,模板文件index.html中包含了輸出頁面內(nèi)容的代碼冈钦,如顯示登錄/退出按鈕的代碼如下:
`<a href="{{ url|safe }}" class="btn">{{ url_linktext }}</a>`
其中的url和url_linktext的值郊丛,就來自于上述MainPage代碼中給的url和url_linktext變量賦值的內(nèi)容。
index.html文件中顯示最新留言信息的代碼如下:
```
{% for greeting in greetings %}
<div class="row">
{% if greeting.author %}
<b>{{ greeting.author.nickname() }}</b> wrote:
{% else %}
An anonymous person wrote:
{% endif %}
<blockquote>{{ greeting.content }}</blockquote>
</div>
{% endfor %}
```
該代碼會輪詢待顯示的全部留言(greetings)瞧筛,逐一輸出每一條留言(greeting)的內(nèi)容(greeting.content)和留言者(greeting.author)厉熟。從代碼中可以看出,如果已有用戶登錄较幌,則greeting.author不為空揍瑟,留言者位置會顯示當(dāng)前用戶;如果沒有用戶登錄乍炉,則greeting.author為空绢片,留言者位置會顯示`An anonymous person`。
index.html文件最后顯示的內(nèi)容是用戶輸入留言的表單岛琼,見圖0中C所示的區(qū)域底循。
<div align=center>
<img src="images\hello\login-back.png" width=80% height=80% />
<br>圖0. GuestBook界面<br></div>
顯示該表單的代碼如下:
```
<form action="/sign" method="post">
<div><textarea name="content" class="input-block-level" rows="3"></textarea></div>
<div><input type="submit" class="btn btn-large btn-primary" value="Sign Guestbook"></div>
</form>
```
其中,`<textarea name="content" class="input-block-level" rows="3"></textarea>`對應(yīng)一個3行的文本框槐瑞,用戶在這個文本框中輸入留言信息熙涤,這些信息將被存放在content字段中,待提交表單時傳遞給服務(wù)器。`<form action="/sign" method="post">`表示提交表單時灭袁,表單中的信息會以post方式傳遞到服務(wù)器猬错,并請求地址為`/sign`的頁面處理該信息∪灼纾回到application對象的定義倦炒,代碼如下:
```
application = webapp2.WSGIApplication([
? ? ('/', MainPage),
? ? ('/wait', WaitHandler),
? ? ('/sign', Guestbook),
], debug=True)
```
該定義中` '/sign', Guestbook`一行表示,請求地址為`/sign`時软瞎,對應(yīng)的處理程序?yàn)镚uestbook逢唤,代碼如下:
```
class Guestbook(webapp2.RequestHandler):
? ? def post(self):
? ? ? ? greeting = Greeting(parent=guestbook_key())
? ? ? ? if users.get_current_user():
? ? ? ? ? ? greeting.author = users.get_current_user()
? ? ? ? greeting.content = self.request.get('content')
? ? ? ? greeting.put()
? ? ? ? self.redirect('/')
```
其中,`post(self)`表示用post方式接收表單數(shù)據(jù)涤浇。`greeting = Greeting(parent=guestbook_key())`定義了一個新的 greeting 變量鳖藕,用于存放一個新的留言,同時將留言設(shè)置為`default_guestbook`留言本的子節(jié)點(diǎn)只锭。接下來的兩條語句分別設(shè)置了greeting變量的author字段和content的字段值著恩,分別代表留言人和留言內(nèi)容。下一條語句`greeting.put()`將這條留言信息存入AppScale數(shù)據(jù)庫蜻展。最后一條語句`self.redirect('/')`喉誊,把頁面重定向到新的頁面:`/`。頁面重定相當(dāng)于向新的頁面發(fā)起請求纵顾,根據(jù)application對象的定義:
```
application = webapp2.WSGIApplication([
? ? ('/', MainPage),
? ? ('/wait', WaitHandler),
? ? ('/sign', Guestbook),
], debug=True)
```
此次請求伍茄,會再次調(diào)用`/`對應(yīng)的處理程序:MainPage,從而再次進(jìn)入頁面的顯示流程施逾。當(dāng)再次顯示頁面時敷矫,MainPage中的下述代碼:
`greetings_query = Greeting.query(ancestor=guestbook_key()).order(-Greeting.date)`
會獲取祖先節(jié)點(diǎn)為`default_guestbook`留言本的所有留言信息,其中就包括上一步存入數(shù)據(jù)庫的最新留言汉额,最終在頁面上顯示出來曹仗,如圖0所示。
<div align=center>
<img src="images\hello\login-back.png" width=80% height=80% />
<br>圖0. 最新留言<br></div>
至此蠕搜,我們就完成了AppScale上的GuestBook應(yīng)用的分析工作怎茫。
### 實(shí)驗(yàn)3. 應(yīng)用中心
__實(shí)驗(yàn)內(nèi)容:在AppScale平臺上開發(fā)云應(yīng)用中心,對不同類型的云應(yīng)用進(jìn)行統(tǒng)一的發(fā)布讥脐、管理和運(yùn)維操作遭居。__
AppScale支持使用Python啼器、PHP旬渠、Go和Java語言編寫Web應(yīng)用。但對于傳統(tǒng)類型的本地應(yīng)用端壳,即在服務(wù)器上部署和運(yùn)行本地應(yīng)用程序告丢,AppScale是無法直接執(zhí)行并將結(jié)果顯示到客戶端瀏覽器的。然而损谦,傳統(tǒng)的本地應(yīng)用數(shù)量巨大岖免,如何才能將這些應(yīng)用移植到云上岳颇,盡量減少代碼的修改量,是一個值得思考的問題颅湘。本實(shí)驗(yàn)嘗試使用一種簡單且通用性較強(qiáng)的方法话侧,把云應(yīng)用分為應(yīng)用中心(APP Center)和應(yīng)用代理(APP Agent)兩個模塊。應(yīng)用中心運(yùn)行在AppScale服務(wù)器上闯参,作為統(tǒng)一管理和調(diào)度云應(yīng)用的控制臺瞻鹏。應(yīng)用代理運(yùn)行在傳統(tǒng)的本地應(yīng)用所在的服務(wù)器上,作為本地應(yīng)用的實(shí)際調(diào)用者鹿寨。采用遠(yuǎn)程調(diào)用的方式新博,由云應(yīng)用中心統(tǒng)一向應(yīng)用代理發(fā)起調(diào)用請求。應(yīng)用代理接到請求后脚草,啟動本地應(yīng)用程序赫悄。本地應(yīng)用程序執(zhí)行的過程中,可以通過應(yīng)用代理從應(yīng)用中心獲取參數(shù)馏慨,也可以通過應(yīng)用代理把程序的執(zhí)行結(jié)果返回給應(yīng)用中心埂淮。應(yīng)用中心通過應(yīng)用代理和本地應(yīng)用程序交互,最終將程序執(zhí)行狀態(tài)或執(zhí)行結(jié)果輸出到用戶端瀏覽器中熏纯。
本實(shí)驗(yàn)在實(shí)驗(yàn)2的基礎(chǔ)上同诫,逐步修改和擴(kuò)充程序代碼,直到完全實(shí)現(xiàn)上述功能樟澜。
首先误窖,用一個C語言編寫的Hello World程序作為本地應(yīng)用的示例。該程序的執(zhí)行結(jié)果就是輸出一個字符串`Hello C`秩贰,如圖0所示霹俺。
圖0 Hello C程序
接下來使用Python語言開發(fā)應(yīng)用代理(APP Agent)程序,程序代碼如下:
```
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import socket
import os
s=socket.socket()
s.bind(('0.0.0.0',1111))
s.listen(5)
while True:
? ? c,addr = s.accept()
? ? cmd = './' + c.recv(1024)
? ? print(cmd)
? ? result =os.popen(cmd).read()
? ? print(result)
? ? c.send(result)
? ? c.close()
```
該代碼引用了socket和os庫毒费,前者用于建立與APP Center通信的tcp連接丙唧,后者用于調(diào)用本地應(yīng)用程序(如Hello C程序)。Agent程序先定義一個socket對象s觅玻,該對象在本機(jī)1111端口監(jiān)聽想际,當(dāng)接收到遠(yuǎn)程連接后(由APP Center發(fā)起),會新建另一個socket對象c溪厘,由c負(fù)責(zé)從該連接接收命令胡本。接收完畢后,先和'./'組裝起來畸悬,表示要調(diào)用當(dāng)前目錄下的這個命令侧甫,然后通過popen函數(shù)執(zhí)行該命令移剪,并將執(zhí)行結(jié)果通過read函數(shù)讀入到result變量中诺核。最后把result的內(nèi)容通過send函數(shù)發(fā)送給遠(yuǎn)程連接(由APP Center接收)顾孽。
接下來將Guestbook程序改造成APP Center程序从藤。先調(diào)整訪客留言程序的文字和布局,使其更加接近于一個應(yīng)用控制臺的顯示效果守屉。在文字方面惑艇,把頁面頂端的App Engine Guestbook改為Cloud APP Center。把Sign Guestbook改為Run拇泛,表示執(zhí)行應(yīng)用敦捧。把留言人賬號后面的wrote改為run,表示某用戶執(zhí)行了某應(yīng)用碰镜。在布局方面兢卵,把輸入框從頁面底部,調(diào)整到頁面上方绪颖,方便用戶輸入秽荤。在本實(shí)驗(yàn)中,用戶在輸入框中輸入的信息不再是留言信息柠横,而是用戶希望執(zhí)行的應(yīng)用程序和相關(guān)信息窃款。我們要把這些信息,顯示到"用戶名 run:"的右邊牍氛,而把下邊的顯示區(qū)域留出來晨继,作為應(yīng)用程序執(zhí)行結(jié)果的顯示區(qū)。調(diào)整之后的效果如圖0所示搬俊。
圖0 文字和布局
然后在結(jié)果顯示區(qū)中顯示應(yīng)用程序的執(zhí)行結(jié)果紊扬。這一步需要在用戶輸入希望執(zhí)行的指令后,建立APP Center與APP Agent的通信唉擂,把指令信息發(fā)送給APP Agent餐屎。待APP Agent調(diào)用相應(yīng)程序后,再將程序執(zhí)行結(jié)果傳回APP Center玩祟。這一步是在點(diǎn)擊Run按鈕后腹缩,提交表單的程序中實(shí)現(xiàn)的,具體代碼如下:
```
class Guestbook(webapp2.RequestHandler):
? ? def post(self):
? ? ? ? greeting = Greeting(parent=guestbook_key())
? ? ? ? if users.get_current_user():
? ? ? ? ? ? greeting.author = users.get_current_user()
? ? ? ? cmd = self.request.get('content')
? ? ? ? c = socket.socket()
? ? ? ? c.connect(('127.0.0.1',1111))
? ? ? ? c.send(cmd)
? ? ? ? result = c.recv(1024)
? ? ? ? c.close()
? ? ? ? greeting.content = cmd
? ? ? ? greeting.result = result
? ? ? ? greeting.put()
? ? ? ? self.redirect('/')
```
其中空扎,`self.request.get('content')`是用戶在留言框中提交的信息藏鹊,在上一個實(shí)驗(yàn)中表示留言信息,在本實(shí)驗(yàn)中表示用戶希望執(zhí)行的指令转锈,將該信息存入cmd變量盘寡。接下來定義一個socket對象c,連接到APP Agent程序所在服務(wù)器的1111端口黑忱。當(dāng)前APP Agent程序和APP Center程序運(yùn)行在同一臺服務(wù)器上宴抚,所以IP地址為`127.0.0.1`。連接建立好后甫煞,APP Center先將要執(zhí)行的指令發(fā)送給APP Agent菇曲,再從APP Agent獲取該指令的執(zhí)行結(jié)果,存放到result變量中抚吠。隨后常潮,將指令和執(zhí)行結(jié)果通過greeting對象存入數(shù)據(jù)庫中,將網(wǎng)頁重定向到根目錄對應(yīng)的頁面楷力。從上一個實(shí)驗(yàn)的分析可知喊式,根目錄對應(yīng)的頁面文件為index.html。為了將指令和執(zhí)行結(jié)果信息在該頁面顯示出來萧朝,需要對index.html文件做如下修改:
```
{% for greeting in greetings %}
<div class="row">
{% if greeting.author %}
<b>{{ greeting.author.nickname() }}</b> run: {{ greeting.content }}
{% else %}
An anonymous person run: {{ greeting.content }}
{% endif %}
<blockquote>{{ greeting.result }}</blockquote>
</div>
{% endfor %}
```
其中岔留,`An anonymous person run: {{ greeting.content }}` 一行用于在顯示該用戶希望執(zhí)行的指令。`<blockquote>{{ greeting.result }}</blockquote>` 一行用于顯示APP Center從APP Agent處獲取的執(zhí)行結(jié)果检柬。修改后的顯示效果如圖0所示献联。
圖0
到目前為止,我們只有一個APP Agent何址,所以可以將它的IP地址(`127.0.0.1`)固定寫在程序源碼中里逆,每次用戶發(fā)起的執(zhí)行命令,都會發(fā)送到該APP Agent執(zhí)行用爪。換句話說原押,所有可執(zhí)行的應(yīng)用都只能安裝在這一臺服務(wù)器上(`127.0.0.1`)。接下來偎血,我們繼續(xù)對程序進(jìn)行擴(kuò)充诸衔,使得被調(diào)用的應(yīng)用程序可以運(yùn)行在不同的服務(wù)器上。最簡單的擴(kuò)充方式颇玷,是要求用戶在輸入待執(zhí)行指令的時候署隘,在輸入信息中增加APP Agent所在的服務(wù)器IP地址。例如亚隙,輸入:`192.168.1.100 hello`磁餐,表示希望在`192.168.1.100`服務(wù)器上執(zhí)行hello程序;輸入:`192.168.1.200 hello`阿弃,表示希望在`192.168.1.200`服務(wù)器上執(zhí)行hello程序诊霹。在APP Center處理輸入框信息的相應(yīng)位置,增加提取服務(wù)器IP地址的代碼渣淳,具體代碼如下所示:
```
cmd = self.request.get('content')
c = socket.socket()
c.connect(('127.0.0.1',1111))
c.send(cmd)
result = c.recv(1024)
c.close()
```
其中脾还,???表示從輸入信息中提取服務(wù)器IP地址,入愧?鄙漏?嗤谚?表示將剩余的字符串作為待執(zhí)行的指令信息。其余代碼保持不變怔蚌。為了測試程序運(yùn)行效果巩步,我們使用了兩臺APP Agent服務(wù)器,IP地址分別為`192.168.1.100 `和`192.168.1.200`桦踊。其中椅野,在第一臺服務(wù)器上安裝了hello1程序,程序執(zhí)行結(jié)果為`Hello, I'm No1!`籍胯;在第二臺服務(wù)器上安裝了hello2程序竟闪,程序執(zhí)行結(jié)果為`Hello, I'm No2!`。通過修改后的APP Center程序分別向兩臺APP Agent服務(wù)器發(fā)起程序執(zhí)行請求杖狼,執(zhí)行結(jié)果如圖0所示炼蛤。
圖0
從圖0中可以看出,只要用戶正確指定了服務(wù)器IP地址和相應(yīng)的應(yīng)用程序蝶涩,APP Center就能夠和相應(yīng)的APP Agent通信鲸湃,調(diào)用相應(yīng)的應(yīng)用程序并獲得執(zhí)行結(jié)果。目前的實(shí)現(xiàn)方案比較適合于服務(wù)器和應(yīng)用數(shù)量較少的情況子寓,一旦規(guī)模變大暗挑,要用戶記住每一個應(yīng)用所在的服務(wù)器IP地址和相應(yīng)的指令是不現(xiàn)實(shí)的。雖然目前程序功能已經(jīng)實(shí)現(xiàn)斜友,但使用起來還不夠方便炸裆,用戶體驗(yàn)還不夠好。接下來對程序進(jìn)行進(jìn)一步的優(yōu)化鲜屏,讓用戶的使用體驗(yàn)更好烹看。優(yōu)化的目標(biāo)有以下幾點(diǎn):
1. 用戶不需要記住云平臺支持多少應(yīng)用。
2. 用戶不需要記住每個應(yīng)用服務(wù)器的IP地址洛史。
3. 用戶不需要記住執(zhí)行每個應(yīng)用的詳細(xì)命令和參數(shù)惯殊。
所以,對于的開發(fā)需求有以下幾條:
1. 新增應(yīng)用登記功能也殖,將云平臺支持的應(yīng)用登記到系統(tǒng)中土思,使用時可從下拉列表中直接選擇應(yīng)用。
2. 登記應(yīng)用時忆嗜,需要同時將應(yīng)用服務(wù)器IP地址登記下來己儒,用戶使用應(yīng)用時不需要重新輸入。
3. 如果應(yīng)用執(zhí)行時需要參數(shù)捆毫,則在用戶選擇該應(yīng)用后闪湾,顯示輸入?yún)?shù)的提示信息。
首先實(shí)現(xiàn)應(yīng)用登記頁面绩卤,界面如圖0所示途样。
登記頁面用一個表單實(shí)現(xiàn)江醇,對應(yīng)的部分代碼如下所示:
```
<form action="/create" method="post">...
<label for="ip">Ip:</label>...
<input id="ip" name="ip" size="40" type="text" value="" />...
<label for="name">Name:</label>...
<input id="name" name="name" size="40" type="text" value="">...
<label for="description">Description:</label>...
<textarea id="description" name="description" class="input-block-level" rows="1" style="width:105%"></textarea>...
<label for="cmd">Cmd:</label>...
<input id="cmd" name="cmd" size="40" type="text" value="">...
<input style="margin-top:10%;width:auto" class="btn btn-primary" name="commit" type="submit" value="注冊" />...
</form>
```
點(diǎn)擊 `注冊` 后,表單數(shù)據(jù)將以post方式提交到 `/create` 地址何暇。
```
application = webapp2.WSGIApplication([
? ? ('/', MainPage),
? ? ('/wait', WaitHandler),
? ? ('/sign', Guestbook),
? ? ('/create', CreatApp),
], debug=True)
```
上述代碼將提交到`/create` 地址的數(shù)據(jù)交給`CreatApp` 對象處理陶夜,`CreatApp` 對象的`post`函數(shù)負(fù)責(zé)處理接收到的數(shù)據(jù),相關(guān)代碼如下:
```
class CreatApp(webapp2.RequestHandler):
? ? def post(self):
? ? ? ? app = App(parent=APP_key())
? ? ? ? app.ip = self.request.get('ip')
? ? ? ? app.name = self.request.get('name')
? ? ? ? app.description = self.request.get('description')
? ? ? ? app.cmd = self.request.get('cmd')
? ? ? ? app.put()
? ? ? ? self.redirect('/')
```
其中赖晶,`app = App(parent=APP_key())`定義了一個新的 app 變量,用于存放新注冊的應(yīng)用信息辐烂,同時將該應(yīng)用的父節(jié)點(diǎn)設(shè)置為`APP_key()`遏插。接下來的四條語句分別設(shè)置了app 變量的ip、name纠修、description和cmd字段胳嘲,分別代表留應(yīng)用所在服務(wù)器的IP地址、應(yīng)用名扣草、詳細(xì)描述和指令了牛。下一條語句`app.put()`將這條留言信息存入AppScale數(shù)據(jù)庫。最后一條語句`self.redirect('/')`辰妙,把頁面重定向到新的頁面:`/`鹰祸。根據(jù)application對象的定義,此次請求密浑,會再次調(diào)用`/`對應(yīng)的處理程序MainPage蛙婴,從而再次進(jìn)入主頁面的顯示流程。
接下來修改主頁面的顯示程序MainPage尔破,增加如下代碼:
```
class MainPage(webapp2.RequestHandler):
? ? def get(self):...
apps_query = App.query(ancestor=APP_key())...
? ? ? ? apps = apps_query.fetch()...
? ? ? ? template = jinja_environment.get_template('index.html')...
? ? ? ? self.response.out.write(template.render(greetings=greetings,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? apps=apps,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? url=url,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? url_linktext=url_linktext))...
```
其中街图,`apps_query = App.query(ancestor=APP_key())` 一行從AppScale數(shù)據(jù)庫中提取全部父節(jié)點(diǎn)為`APP_key()` 的節(jié)點(diǎn)信息,這些節(jié)點(diǎn)就是在應(yīng)用注冊頁面中提交的全部應(yīng)用信息懒构。`self.response.out.write(...)` 函數(shù)中的第二個參數(shù) `apps=apps`? 餐济,將這些應(yīng)用信息傳遞到`index.html` 頁面并顯示出來。`index.html` 頁面中實(shí)現(xiàn)了一個應(yīng)用選擇的下拉框胆剧,點(diǎn)擊后會顯示一個下拉列表絮姆,下列列表中的每一行對應(yīng)一個應(yīng)用信息,相關(guān)代碼如下:
```
<form action="/sign" method="post">...
? ? ? ? ? <ul class="dropdown-menu" role="menu" style="width:100%;">
? ? ? ? ? ? ? {% for app in apps %}
? ? ? ? ? ? ? {% if app.name %}
? ? ? ? ? ? ? <li onclick="shows($(this))">
? ? ? ? ? ? ? ? ? <a href="#" style="text-align: center;">{{app.name}}</a>
? ? ? ? ? ? ? ? ? <p hidden>{{app.id}}</p>
? ? ? ? ? ? ? ? ? <p hidden>{{app.description}}</p>
? ? ? ? ? ? ? ? ? <p hidden>{{app.ip}}</p>
? ? ? ? ? ? ? ? ? <p hidden>{{app.cmd}}</p>
? ? ? ? ? ? ? </li>
? ? ? ? ? ? ? {% endif %}
? ? ? ? ? ? ? {% endfor %}
? ? ? ? ? </ul>
</form>
```
注意秩霍,每個應(yīng)用的注冊信息都包含name滚朵、ip、cmd前域、description等多個字段辕近,在下拉列表中,僅需要顯示name這個字段即可匿垄,其他字段隱藏起來移宅,當(dāng)用戶點(diǎn)擊某個應(yīng)用時归粉,會調(diào)用`onclick="shows($(this))` 中指定的shows函數(shù),在該函數(shù)中會使用到這些隱藏字段漏峰。shows函數(shù)中相關(guān)代碼如下:
```
function shows(a){
$('.buttonText1').text(a.context.children[0].text);
$('.showdescription').text(a.context.children[2].textContent);
$('input#submitIp').val(a.context.children[3].textContent);
$('input#submitCmd').val(a.context.children[4].textContent);
$('input#submitName').val(a.context.children[0].text);
$('.showdescription1').text(a.context.children[0].text+' DESCRIPTION:');
```
其中糠悼,`a.context.children[...]`用于訪問下列框元素內(nèi)的子元素,例如浅乔,children[0]對應(yīng)app.name元素倔喂,children[1]對應(yīng)app.id元素,children[2]對應(yīng)app.description元素靖苇。`$('.buttonText1')` 席噩、`$('.showdescription')`、`$('.showdescription1')`對應(yīng)頁面上3個顯示區(qū)域贤壁,`$('.input#submitIp')` 悼枢、`$('.input#submitCmd')`、`$('.input#submitName')`對應(yīng)表單中id分別為`submitIp`脾拆、`submitCmd` 和 `submitName` 的3個隱藏的輸入框:
```
<form action="/sign" method="post">...
? ? <input type="hidden" id="submitIp" name="submitIp" value="" />...
? ? <input type="hidden" id="submitCmd" name="submitCmd" value="" />...
? ? <input type="hidden" id="submitName" name="submitName" value="" />...
</form>
```
當(dāng)用戶選擇一個應(yīng)用后馒索,頁面上3個顯示區(qū)域會發(fā)生相應(yīng)的變化,其中名船,應(yīng)用詳情區(qū)域會顯示應(yīng)用執(zhí)行需要的參數(shù)信息绰上,? 如圖0所示。
圖0
同時渠驼,3個隱藏的輸入框會分別獲取應(yīng)用的IP渔期、cmd、name信息渴邦。用戶根據(jù)應(yīng)用詳情的提示疯趟,在參數(shù)輸入框中輸入相關(guān)參數(shù)后,點(diǎn)擊`RUN`按鈕谋梭,表單數(shù)據(jù)將會以post方式提交到`/sign`地址處理信峻。根據(jù)application對象的定義,提交到`/sign`地址的數(shù)據(jù)將會被轉(zhuǎn)交給Guestbook對象處理瓮床。接下來修改Guestbook對象的post函數(shù)盹舞,用于處理接收到的表單數(shù)據(jù),相關(guān)代碼如下:
```
class Guestbook(webapp2.RequestHandler):
? ? def post(self):...
? ? ? ? cmd = self.request.get('submitCmd')+' '+self.request.get('canshu')
? ? ? ? ip = self.request.get('submitIp')
? ? ? ? c = socket.socket()
? ? ? ? c.connect((ip,1111))
? ? ? ? c.send(cmd)
? ? ? ? result = c.recv(1024)
? ? ? ? c.close()...
```
在原先程序的基礎(chǔ)上隘庄,修改了獲取程序執(zhí)行指令踢步、APP Agent所在服務(wù)器的IP地址的代碼。將用戶選擇的應(yīng)用名稱和參數(shù)信息丑掺,組裝成完整的程序執(zhí)行指令获印,發(fā)送給應(yīng)用所在服務(wù)器的APP Agent程序,并從APP Agent程序獲取應(yīng)用執(zhí)行結(jié)果街州,最后在APP Center頁面顯示出來兼丰。修改后的程序運(yùn)行效果如圖0所示玻孟。
圖0
為了測試程序運(yùn)行效果,我們?nèi)匀皇褂弥皽?zhǔn)備好的兩臺APP Agent服務(wù)器鳍征,IP地址分別為`192.168.1.100 `和`192.168.1.200`黍翎。其中,第一臺服務(wù)器上安裝了hello1程序艳丛,第二臺服務(wù)器上安裝了hello2程序匣掸。通過應(yīng)用注冊頁面,將兩個應(yīng)用注冊后氮双,在APP Center主頁面分別選擇兩個應(yīng)用執(zhí)行碰酝,圖0是選擇執(zhí)行hello1程序的運(yùn)行效果。
圖0
圖0是選擇執(zhí)行hello2程序的運(yùn)行效果眶蕉。
圖0
從運(yùn)行結(jié)果可見砰粹,只要用戶使用正確的信息注冊了應(yīng)用唧躲,在使用應(yīng)用的過程中造挽,只需要非常簡單的點(diǎn)擊操作,配合適當(dāng)輸入的參數(shù)信息弄痹,就可以成功實(shí)現(xiàn)云應(yīng)用的運(yùn)行饭入,用戶體驗(yàn)在原先程序的基礎(chǔ)上有了較大提高。至此肛真,我們就完成了AppScale上的APP Center應(yīng)用的開發(fā)任務(wù)谐丢。
接下來,我們在AWS云平臺上開發(fā)大數(shù)據(jù)和人工智能相關(guān)的幾個典型應(yīng)用蚓让,并將這些應(yīng)用集成到本節(jié)開發(fā)的APP Center中乾忱,實(shí)現(xiàn)所有應(yīng)用的統(tǒng)一管理和運(yùn)維。
## 第2節(jié) 大數(shù)據(jù)實(shí)驗(yàn)
spark是一個快速且通用的集群計算平臺历极≌粒快速指擴(kuò)充了MapReduce計算模型,基于內(nèi)存的計算趟卸。通用是指Spark的設(shè)計容納了其他分布式系統(tǒng)的功能蹄葱,如批處理、迭代計算锄列,交互查詢和流處理等图云。Spark是開放的,雖然使用scala語言編寫邻邮,但內(nèi)置多種語言的API竣况。Hadoop應(yīng)用場景大多用于離線處理,對時效性要求不高筒严,與之相比帕翻,Spark應(yīng)用在時效性要求高鸠补、機(jī)器學(xué)習(xí)等領(lǐng)域。Spark組件緊密集成嘀掸,節(jié)省了各個組件使用時的部署紫岩,測試等時間,同時集成的組件也得到相應(yīng)的優(yōu)化睬塌。每個組件都各善其職泉蝌,都有特定的作用,應(yīng)用于不同的場景揩晴,組成了一個生態(tài)系統(tǒng)勋陪。
在AWS平臺上訂閱預(yù)裝Spark環(huán)境的過程和上一節(jié)中訂閱AppScale環(huán)境的過程基本一致,只是在選擇虛擬機(jī)模板這一步不同硫兰,需要搜索Spark字樣的模板诅愚,如圖0所示。
搜索框輸入spark后回車確認(rèn)
![](images/spark_aws/003.png)
尋找合適鏡像劫映,此處選擇的是`Apache Spark Powered by Code Creator`
![](images/spark_aws/004.png)
選擇后繼續(xù)
![](images/spark_aws/005.png)
根據(jù)需要選擇所需的硬件資源违孝,若不需要額外配置,此處可直接點(diǎn)擊`審核和啟動`完成實(shí)例的創(chuàng)建
![](images/spark_aws/006.png)
后續(xù)過程和訂閱AppScale環(huán)境一樣泳赋。訂閱成功后雌桑,使用ssh登錄系統(tǒng)
```
ssh -i spark.pem ubuntu@ec2-54-169-37-43.ap-southeast-1.compute.amazonaws.com
```
嘗試運(yùn)行,出現(xiàn)`scala`命令提示符祖今,表示spark環(huán)境安裝成功校坑。如圖0所示。
圖0
在scale提示符后輸入spark相關(guān)指令千诬,即可進(jìn)行大數(shù)據(jù)相關(guān)處理任務(wù)耍目。
下面通過幾個實(shí)驗(yàn),展示spark的幾個典型應(yīng)用徐绑,并將這些應(yīng)用發(fā)布到上一節(jié)中完成的應(yīng)用中心邪驮,更好的體現(xiàn)出云應(yīng)用的使用模式。
### 實(shí)驗(yàn)1 詞頻統(tǒng)計
**實(shí)驗(yàn)?zāi)康?*
統(tǒng)計文本文件中英文單詞的出現(xiàn)頻率泵三,并按照從高到低的順序排列耕捞。
**實(shí)驗(yàn)要求**
使用HDFS存放數(shù)據(jù),使用Spark分析詞頻烫幕,實(shí)驗(yàn)結(jié)果發(fā)布到AppScale應(yīng)用中心俺抽。
**實(shí)驗(yàn)步驟**
首先準(zhǔn)備一個測試用文本文件`input.txt`,文件內(nèi)容為任意一篇英文文章较曼。
本實(shí)驗(yàn)僅要求統(tǒng)計英文單詞的出現(xiàn)頻率磷斧,無需考慮標(biāo)點(diǎn)符號和其他特殊符號,所以,先對文本文件`input.txt`進(jìn)行預(yù)處理弛饭,剔除文本中的標(biāo)點(diǎn)符號和其他特殊符號冕末,程序名為`pre-process.py`,代碼如下:
```
#!/usr/bin/python
with open('input.txt', 'r') as f:
? ? text = f.read().lower()
? ? for i in '!@#$%^&*()_+-;:`~\'"<>=./?,':
? ? ? ? text = text.replace(i, '')
with open('output.txt', 'w') as f:
? ? f.write(text)
```
其中侣颂,第一行 `#!/usr/bin/python` 表示這是一個python程序档桃。 `text = f.read().lower()`一行將文件中的所有大寫字母轉(zhuǎn)換為小寫字母,方便后續(xù)統(tǒng)計憔晒。`for ... text = text.replace(i, '')` 通過一個for循環(huán)藻肄,依次把每一個特殊字符用空格替換掉,這是因?yàn)轭}目需求里面并不要求統(tǒng)計特殊字符的出現(xiàn)次數(shù)拒担。最后一條語句`f.write(text)`把處理完后的文本輸出到文件`output.txt`中嘹屯。
接下來將預(yù)處理后的文件`output.txt`上傳到分布式文件系統(tǒng)HDFS中,方便后續(xù)Spark分布式處理从撼。此步驟不是必須的州弟,Spark也可以處理普通文件系統(tǒng)上的文件。但是低零,如果把文件存放到分布式文件系統(tǒng)HDFS中婆翔,再調(diào)用Spark進(jìn)行處理,能夠更好的發(fā)揮出Spark分布式并行分析數(shù)據(jù)的功能毁兆。
```
hdfs dfs -mkdir /spark
hdfs dfs -put output.txt /spark
```
接下來浙滤,執(zhí)行spark-shell阴挣,進(jìn)入交互式命令行界面气堕,如下所示:
```
spark-shell
scala>
```
將`output.txt`文件內(nèi)容讀入變量data,指令如下:
```
val data = sc.textFile("/spark/output.txt")
```
查看data內(nèi)容:
```
scala> data.collect
res1: Array[String] = Array(the story of an hour, by kate chopin, this story was first published in 1894 as the dream of an hour before being republished under this title in 1895 we encourage students ...
```
從輸出內(nèi)容可見畔咧,data是一個數(shù)組變量茎芭,存放了`output.txt`文件的全部文本,用換行符分割文本,每行文本用一個數(shù)組元素存放。為了進(jìn)行詞頻統(tǒng)計黎茎,我們需要按空格分割文本苦丁,讓每個數(shù)組元素存放一個單詞,指令如下:
```
val words = data.flatMap(line => line.split(" "))
```
查看分割后的結(jié)果:
```
scala> words.collect
res2: Array[String] = Array(the, story, of, an, hour, by, kate, chopin, this, story, was, first, published, in, 1894, as, the, dream, of, an, hour, before, being, republished, under, this, title, in, 1895, we, encourage, students ...
```
為統(tǒng)計每個單詞的出現(xiàn)次數(shù)斤寇,先把每個單詞擴(kuò)展為一個二元組:第一元為單詞本身,第二元為單詞出現(xiàn)的次數(shù)(初始時為1),代碼如下:
```
val wordsmap = words.map(word => (word, 1))
```
擴(kuò)展后的結(jié)果為:
```
scala> wordsmap.collect
res3: Array[(String, Int)] = Array((the,1), (story,1), (of,1), (an,1), (hour,1), (by,1), (kate,1), (chopin,1), (this,1), (story,1), (was,1), (first,1), (published,1), (in,1), (1894,1), (as,1), (the,1), (dream,1), (of,1), (an,1) ...
```
接下來把全部第一元相同的二元組合并為一個新二元組垦页,新二元組第二元記錄全部原二元組第二元之和,代碼如下:
```
val wordscount = wordsmap.reduceByKey((x,y) => x+y)
```
合并后的結(jié)果如下:
```
scala> wordscount.collect
res5: Array[(String, Int)] = Array((controversy,1), (chose,1), (someone,2), (under,2), (08,1), (reading,1), (its,3), (guide,2), (opening,1), (gripsack,1), (bright,1), (warmed,1), (filled,1), (have,6), (carried,1), (we,1) ...
```
對結(jié)果排序干奢,按第二元數(shù)值從大到小排列痊焊,代碼如下:
```
val wordssort = wordscount.sortBy(-_._2)
```
排序后的結(jié)果如下:
```
scala> wordssort.collect
res6: Array[(String, Int)] = Array((the,67), (her,41), (of,39), (and,37), (she,34), (a,33), (was,30), (to,29), (in,26), ("",24), (that,24), (it,15), (with,13), (had,13), (as,12), (story,12), (there,10), (not,8), (would,8) ...
```
將結(jié)果存放到`/spark/result`中:
```
wordssort.saveAsTextFile("/spark/result")
```
最后執(zhí)行`:quit`指令,退出`spark shell`,回到Linux命令行中薄啥。執(zhí)行以下指令辕羽,把結(jié)果從分布式文件系統(tǒng)HDFS中下載到服務(wù)器本地:
```
hdfs dfs -get /spark/result
```
執(zhí)行完該指令后,當(dāng)前目錄下會生成一個result目錄垄惧,目錄中包含了程序的執(zhí)行結(jié)果:
```
# ls
input.txt? output.txt? pre-process.py? result
# ls result/
part-00000? part-00001? _SUCCESS
```
查看第一個結(jié)果文件的前10行刁愿,即可看到按照詞頻排序,出現(xiàn)次數(shù)最多的前10個單詞:
```
# head result/part-00000
(the,4394)
(to,4200)
(of,3696)
(and,3637)
(her,2249)
(i,2105)
(a,1959)
(in,1892)
(was,1860)
(she,1725)
```
至此到逊,詞頻統(tǒng)計的任務(wù)就完成了酌毡。接下來,我們把這個應(yīng)用集成到第1節(jié)實(shí)驗(yàn)3中實(shí)現(xiàn)的應(yīng)用中心蕾管。目前枷踏,詞頻統(tǒng)計的任務(wù)是在Spark-shell交互式環(huán)境下完成的,需要人工輸入和執(zhí)行每一條指令掰曾,獲得一條指令的結(jié)果后旭蠕,才能輸入下一條指令并執(zhí)行。這樣的模式無法直接結(jié)合到應(yīng)用中心旷坦。所以掏熬,首先需要對程序的執(zhí)行過程進(jìn)行改造,變成一條指令即可完成全部任務(wù)的形式秒梅。
首先新建一個文本文件count.scala旗芬,把所有spark指令集成到一起,如下所示:
```
val data = sc.textFile("/spark/output.txt")
val words = data.flatMap(line => line.split(" "))
val wordsmap = words.map(word => (word, 1))
val wordscount = wordsmap.reduceByKey((x,y) => x+y)
val wordssort = wordscount.sortBy(-_._2)
wordssort.saveAsTextFile("/spark/result")
```
再新建一個腳本文件count.sh捆蜀,該文件會讀取count.scala文件內(nèi)容并逐一執(zhí)行其中的spark指令疮丛,代碼如下:
```
#!/bin/bash
./pre-process.py
hdfs dfs -rm -f -r /spark/output.txt
hdfs dfs -put output.txt /spark
hdfs dfs -rm -f -r /spark/result
spark-shell -i < count.scala >/dev/null 2>&1
rm -fr result/
hdfs dfs -get /spark/result
head result/part-00000
```
其中第一行`#!/bin/bash`表示這是一個bash腳本文件。`spark-shell -i < count.scala >/dev/null 2>&1`一行啟動`spark-shell辆它,并從count.scala文件中逐一讀取spark指令并執(zhí)行誊薄,執(zhí)行過程中忽略掉所有的的屏幕提示信息。這行代碼之前的內(nèi)容是把待處理文件上傳到HDFS中锰茉,之后的代碼是將處理結(jié)果從HDFS中下載到本地呢蔫,并顯示出來。 先嘗試在Linux命令行中執(zhí)行該腳本文件飒筑,檢查結(jié)果是否正確片吊,如下所示:
```
# ./count.sh
...
(the,4394)
(to,4200)
(of,3696)
(and,3637)
(her,2249)
(i,2105)
(a,1959)
(in,1892)
(was,1860)
(she,1725)
```
可見,程序執(zhí)行結(jié)果和之前在Spark-shell交互式環(huán)境下逐一執(zhí)行每一條指令的最終結(jié)果是一樣的协屡。接下來俏脊,我們就可以把這個程序集成到AppScale應(yīng)用中心了。通過應(yīng)用注冊頁面著瓶,將詞頻統(tǒng)計應(yīng)用注冊到應(yīng)用中心联予,注冊頁面如圖0所示啼县。
圖0 注冊應(yīng)用
注冊成功后,在APP Center主頁面選擇詞頻統(tǒng)計應(yīng)用并執(zhí)行沸久,圖0是應(yīng)用的執(zhí)行效果季眷。
圖0
至此,我們就完成了詞頻統(tǒng)計應(yīng)用的開發(fā)任務(wù)卷胯。
### 實(shí)驗(yàn)2. 事故分析
**實(shí)驗(yàn)?zāi)康?*
根據(jù)某城市某年的交通事故數(shù)據(jù)([數(shù)據(jù)集地址](HTTP://data.gov.uk/dataset/cb7ae6f0-4be6-4935-9277-47e5ce24a11f/road-safety-data))子刮,分析天氣情況、時間窑睁、地點(diǎn)或其它因素與交通事故之間的關(guān)系挺峡。
**實(shí)驗(yàn)要求**
使用Spark分析,實(shí)驗(yàn)結(jié)果發(fā)布到AppScale應(yīng)用中心担钮。
**實(shí)驗(yàn)步驟**
從公共數(shù)據(jù)集中選擇一個作為本實(shí)驗(yàn)分析的對象(例如橱赠,選擇2018年的交通事故數(shù)據(jù)集),將該數(shù)據(jù)集文件`dftRoadSafetyData_Accidents_2018.csv`下載到本地箫津。該數(shù)據(jù)集包含了事故編號狭姨、事發(fā)地點(diǎn)、時間苏遥,事故嚴(yán)重程度饼拍,事故車輛類型、道路類型田炭、路面干濕情況师抄、天氣情況、光線情況等30多個字段教硫。本實(shí)驗(yàn)先分析天氣情況與交通事故之間的關(guān)系叨吮。
首先在HDFS中新建accident目錄,把交通事故數(shù)據(jù)集文件`dftRoadSafetyData_Accidents_2018.csv`和天氣情況文件`weather.csv`上傳到HDFS:
```
hdfs dfs -mkdir /accident
hdfs dfs -put dftRoadSafetyData_Accidents_2018.csv /accident
hdfs dfs -put weather.csv /accident
```
其中栋豫,交通事故數(shù)據(jù)集文件`dftRoadSafetyData_Accidents_2018.csv`格式如下:
<div align=center>
<img src="images\accident\accident.png" width=80% height=80% />
<br>圖0. 交通事故數(shù)據(jù)集文件格式<br></div>
天氣情況文件`weather.csv`格式如下:
<div align=center>
<img src="images\accident\weather.png" width=30% height=30% />
<br>圖0. 天氣情況文件格式<br></div>
把交通事故數(shù)據(jù)讀入變量accidentsDF:
```
scala> val accidentsDF = spark.read.option("header", "true").option("inferSchema", "true").csv("/accident/dftRoadSafetyData_Accidents_2018.csv")
```
其中挤安,`option("header", "true")`表示文件第一行為表頭信息谚殊,`option("inferSchema", "true")`表示用spark內(nèi)置的檢測機(jī)制判斷讀入數(shù)據(jù)數(shù)據(jù)的類型丧鸯。接下來把天氣信息讀入變量weatherDF:
```
scala> val weatherDF = spark.read.option("header", "true").csv("/accident/weather.csv")
```
從全部交通事故記錄中,提取嚴(yán)重程度大于1的記錄:
```
scala> val results1 = accidentsDF.filter(accidentsDF.col("Accident_Severity") > 1)
```
在交通事故文件中嫩絮,`Accident_Index`一列的前4個字符表示事故發(fā)生的年份丛肢,截取這個數(shù)據(jù),起一個別名`year`剿干,選取`year`和`Weather_Conditions`兩個字段:
```
scala> val results2=results1.select(col("Accident_Index").substr(0, 4).as("year"), col("Weather_Conditions"))
```
使用`year`和`Weather_Condition`這兩個字段進(jìn)行分組蜂怎,統(tǒng)計組內(nèi)數(shù)據(jù)的個數(shù)。
```
scala> val results3=results2.groupBy("year", "Weather_Conditions").count()
```
為了使顯示結(jié)果更加直觀置尔,把用數(shù)字代表的`Weather_Condition`轉(zhuǎn)換成文字:
```
scala> val results4=results3.join(weatherDF, weatherDF.col("code") === accidentsDF.col("Weather_Conditions"))
```
這里使用了join操作和天氣情況文件`weather.csv`完成這個任務(wù)杠步。接下來選取:年份、天氣情況和事故數(shù)量這幾個字段幽歼,并按照事故數(shù)量從多到少的順序排序:
```
scala> val results5=results4.select(col("year"), col("label").as("weather"), col("count")).orderBy(col("count").desc)
```
最后把結(jié)果顯示出來:
```
scala> results5.show
+----+----------------------------+-----+
|year|weather? ? ? ? ? ? ? ? ? ? |count|
+----+----------------------------+-----+
|2018|Fine no high winds? ? ? ? ? |97851|
|2018|Raining no high winds? ? ? |12628|
|2018|Unknown? ? ? ? ? ? ? ? ? ? |3639 |
|2018|Other? ? ? ? ? ? ? ? ? ? ? |2582 |
|2018|Raining + high winds? ? ? ? |1247 |
|2018|Fine + high winds? ? ? ? ? |1107 |
|2018|Snowing no high winds? ? ? |1063 |
|2018|Fog or mist? ? ? ? ? ? ? ? |428? |
|2018|Snowing + high winds? ? ? ? |400? |
|2018|Data missing or out of range|19? |
+----+----------------------------+-----+
```
從分析結(jié)果可知朵锣,交通事故發(fā)生次數(shù)最多的天氣是"Fine no high winds",即良好且無大風(fēng)的天氣甸私。這種天氣發(fā)生事故的次數(shù)遠(yuǎn)高于雨诚些、雪、霧的天氣皇型,這似乎我們“惡劣天氣更易發(fā)生事故”的常識相違背诬烹,其實(shí)不然,因?yàn)檫@里我們僅僅統(tǒng)計了事故的次數(shù)弃鸦,并沒有統(tǒng)計當(dāng)年未發(fā)生交通事故的事件绞吁。通常來說,天氣較好的時候人們更愿意出門唬格,從而導(dǎo)致事故次數(shù)變多掀泳,這并不能說明天氣良好更容易發(fā)生事故。另一方面西轩,天氣良好的情況下员舵,人們的警惕性相較于特殊天氣會降低,這也可能是導(dǎo)致天氣良好的情況下事故較多的原因藕畔。
用同樣的方法马僻,可以分析時間(這里的時間指的是每周的星期幾)與交通事故之間的關(guān)系。只需要將上述步驟中讀取天氣狀況的地方注服,改為讀取時間即可韭邓,具體修改之處如下。
將記錄時間信息的文件`week.csv`上傳到HDFS:
```
hdfs dfs -put week.csv /accident
```
該文件格式如下:
<div align=center>
<img src="images\accident\week.png" width=20% height=20% />
<br>圖0. 時間信息文件格式<br></div>
把時間信息讀入變量weekDF:
```
scala> val weekDF = spark.read.option("header", "true").csv("/accident/week.csv")
```
選取`year`和`Day_of_Week`兩個字段:
```
scala> val results2=results1.select(col("Accident_Index").substr(0, 4).as("year"), col("Day_of_Week"))
```
使用`year`和`Day_of_Week`這兩個字段進(jìn)行分組溶弟,統(tǒng)計組內(nèi)數(shù)據(jù)的個數(shù)女淑。
```
scala> val results3=results2.groupBy("year", "Day_of_Week").count()
```
使用join操作和時間信息文件`week.csv`,把用數(shù)字代表的`Day_of_Week`轉(zhuǎn)換成文字:
```
scala> val results4=results3.join(weekDF, weekDF.col("code") === accidentsDF.col("Day_of_Week"))
```
選裙加:年份鸭你、時間和事故數(shù)量這幾個字段,并按照事故數(shù)量從多到少的順序排序:
```
scala> val results5=results4.select(col("year"), col("label").as("week"), col("count")).orderBy(col("count").desc)
```
最后把結(jié)果顯示出來:
```
scala> results5.show
+----+---------+-----+
|year|? ? week|count|
+----+---------+-----+
|2018| saturday|19776|
|2018|? friday|18438|
|2018| thursday|18196|
|2018|wednesday|17734|
|2018|? tuesday|17489|
|2018|? sunday|15786|
|2018|? monday|13545|
+----+---------+-----+
```
從分析結(jié)果可知擒权,在一周中交通事故發(fā)生次數(shù)最多的時間是周期六袱巨,發(fā)生次數(shù)最少的時間是星期一,這跟“周末駕車出游人數(shù)較多碳抄,交通事故發(fā)生概率更大”的常識是相符的愉老。
上述過程分別分析了天氣情況和時間與交通事故之間的關(guān)系,且兩個任務(wù)都是在Spark-shell交互式環(huán)境下完成的∑市В現(xiàn)在將兩個任務(wù)合并到一個應(yīng)用中嫉入,通過參數(shù)控制執(zhí)行哪一個任務(wù)焰盗,以方便后續(xù)將應(yīng)用發(fā)布到AppScale應(yīng)用中心。新建腳本文件`accident.sh`咒林,文件內(nèi)容如下:
```
#!/bin/bash
if [[ $1 = 'weather' ]]; then
? ? spark-shell -i < weather.scala
elif [[ $1 = 'week' ]]; then
? ? spark-shell -i < week.scala
else
? ? echo "please input parameter: weather | week"
fi
```
其中姨谷,`weather.scala`和`week.scala `分別是將上述兩個任務(wù)中,在Spark-shell交互式環(huán)境下執(zhí)行的所有spark指令集成到一起形成的文本文件映九。腳本文件`accident.sh`根據(jù)第一個輸入?yún)?shù)`$1`是`weather`還是`week`梦湘,分別讀取`weather.scala`和`week.scala `文件的內(nèi)容,并逐一執(zhí)行其中的spark指令件甥。當(dāng)輸入?yún)?shù)為`weather`時捌议,程序執(zhí)行效果如下:
```
# ./accident.sh weather
...
+----+----------------------------+-----+
|year|weather? ? ? ? ? ? ? ? ? ? |count|
+----+----------------------------+-----+
|2018|Fine no high winds? ? ? ? ? |97851|
|2018|Raining no high winds? ? ? |12628|
|2018|Unknown? ? ? ? ? ? ? ? ? ? |3639 |
|2018|Other? ? ? ? ? ? ? ? ? ? ? |2582 |
|2018|Raining + high winds? ? ? ? |1247 |
|2018|Fine + high winds? ? ? ? ? |1107 |
|2018|Snowing no high winds? ? ? |1063 |
|2018|Fog or mist? ? ? ? ? ? ? ? |428? |
|2018|Snowing + high winds? ? ? ? |400? |
|2018|Data missing or out of range|19? |
+----+----------------------------+-----+
```
當(dāng)輸入?yún)?shù)為`week`時,程序執(zhí)行效果如下:
```
# ./accident.sh week
...
+----+---------+-----+
|year|? ? week|count|
+----+---------+-----+
|2018| saturday|19776|
|2018|? friday|18438|
|2018| thursday|18196|
|2018|wednesday|17734|
|2018|? tuesday|17489|
|2018|? sunday|15786|
|2018|? monday|13545|
+----+---------+-----+
```
最后引有,我們把這個程序集成到AppScale應(yīng)用中心了瓣颅。通過應(yīng)用注冊頁面,將事故分析應(yīng)用注冊到應(yīng)用中心譬正,注冊頁面如圖0所示宫补。
圖0 注冊應(yīng)用
注冊成功后,在APP Center主頁面選擇事故分析應(yīng)用曾我,輸入適當(dāng)?shù)膮?shù)并執(zhí)行粉怕,圖0是應(yīng)用的執(zhí)行效果。
圖0
至此抒巢,我們就完成了事故分析應(yīng)用的開發(fā)任務(wù)贫贝。
### 實(shí)驗(yàn)3. 電影推薦
**實(shí)驗(yàn)?zāi)康?*
根據(jù)某電影論壇的公開數(shù)據(jù)集([數(shù)據(jù)集地址](https://grouplens.org/datasets/movielens/)),分析用戶對電影的評分情況蛉谜,并向用戶推薦可能喜歡的電影稚晚。
**實(shí)驗(yàn)要求**
使用Spark分析,實(shí)驗(yàn)結(jié)果發(fā)布到AppScale應(yīng)用中心型诚。
**實(shí)驗(yàn)步驟**
首先在HDFS中新建movie目錄客燕,把電影文件`movies.csv`和評分文件`ratings.csv`上傳到HDFS:
```
hdfs dfs -mkdir /movie
hdfs dfs -put movies.csv /movie
hdfs dfs -put ratings.csv /movie
```
其中,電影文件`movies.csv`格式如下:
<div align=center>
<img src="images\movie\movies.png" width=60% height=60% />
<br>圖0. 電影文件格式<br></div>
評分文件`ratings.csv`格式如下:
<div align=center>
<img src="images\movie\ratings.png" width=60% height=60% />
<br>圖0. 評分文件格式<br></div>
讀入數(shù)據(jù)狰贯,文件格式是csv也搓,可選參數(shù)里指明該CSV文件有頭部,并且將類型推斷開關(guān)打開暮现。類型推斷會將`userId`,`movieId`推斷為integer類型还绘,`rating`推斷為double類型,`timestamp`推斷為integer類型栖袋,這符合我們對數(shù)據(jù)的要求,不需要額外操作抚太√练可使用`printSchema()`函數(shù)查看各字段信息:
```
scala> val ratingsDF = spark.read.option("header", "true").option("inferSchema", "true").csv("/movie/ratings.csv")
scala> ratingsDF.printSchema()
root
|-- userId: integer (nullable = true)
|-- movieId: integer (nullable = true)
|-- rating: double (nullable = true)
|-- timestamp: integer (nullable = true)
```
對于常用的數(shù)據(jù)分析任務(wù)昔案,Spark提供了很多功能完善的庫,我們可以直接使用這些庫來簡化自己工作电媳。本實(shí)驗(yàn)會用到Random和ALS兩個庫踏揣,先導(dǎo)入這兩個庫:
```
scala> import scala.util.Random
scala> import org.apache.spark.ml.recommendation.ALS
```
新建一個ALS庫提供的模型`als`,并對這個模型進(jìn)行初始化:
```
scala> :paste
val als = new ALS()
? ? .setSeed(Random.nextLong())
? ? .setImplicitPrefs(true)
? ? .setRank(10)
? ? .setRegParam(0.01)
? ? .setAlpha(1.0)
? ? .setMaxIter(5)
? ? .setUserCol("userId")
? ? .setItemCol("movieId")
? ? .setRatingCol("rating")
? ? .setPredictionCol("prediction")
```
使用剛讀入的評分文件`ratings.csv`訓(xùn)練`als`模型:
```
scala> val model =? als.fit(ratingsDF)
```
讀入電影數(shù)據(jù)文件`movies.csv`匾乓,字段有`movieId`捞稿、`title`、`genres`拼缝,分別表示電影ID娱局、電影名稱、電影類型咧七,其中類型由"|"分隔衰齐。
接下來嘗試選擇`userId=274`的用戶來看推薦效果。先統(tǒng)計該用戶的觀看過電影的類型:
```
scala> val userId = 274
scala> val watchedMoviesId = ratingsDF.filter($"userId" === userId).select("movieId").as[Int].collect()
scala> :paste
moviesDF.filter($"movieId" isin (watchedMoviesId:_*))
? ? .withColumn("genres", explode(split($"genres", "\\|")))
? ? .select("genres")
? ? .groupBy("genres")
? ? .count()
? ? .orderBy($"count".desc)
? ? .show()
+-----------+-----+
|? ? genres|count|
+-----------+-----+
|? ? Comedy|? 550|
|? ? Action|? 454|
|? Thriller|? 452|
|? ? ? Drama|? 407|
|? Adventure|? 295|
|? ? Horror|? 266|
|? ? Sci-Fi|? 232|
|? ? ? Crime|? 224|
|? ? Fantasy|? 175|
|? Children|? 158|
|? ? Romance|? 132|
|? ? Mystery|? 109|
|? Animation|? 99|
|? ? Musical|? 49|
|? ? ? ? War|? 34|
|? ? ? IMAX|? 34|
|? ? Western|? 26|
|Documentary|? 13|
|? Film-Noir|? ? 7|
+-----------+-----+
```
從上面可以看出該用戶看過的最多的類型有:喜劇继阻、動作耻涛、驚悚和戲劇類型。現(xiàn)在我們給該用戶推薦10部電影瘟檩。我們使用ALS類的`recommendForUserSubset`方法為指定的用戶集推薦電影抹缕,此處用戶集`userSubset`中只有一個用戶,其ID為274墨辛。
```
scala> val userSubset = ratingsDF.select(als.getUserCol).filter($"userId" === userId).distinct().limit(1)
scala> val recommendations = model.recommendForUserSubset(userSubset, 10)
```
查看推薦結(jié)果:
```
scala> recommendations.show(truncate=false)
...|userId|recommendations...
...|274? |[[2804, 1.3512053], [1387, 1.3346307], [5679, 1.3175706], [54503, 1.3016284], [8641, 1.2686287], [3275, 1.2642331], [51662, 1.2634813], [8783, 1.262716], [1258, 1.2550267], [8874, 1.240392]]...
```
為了更直觀顯示推薦結(jié)果歉嗓,可繼續(xù)對結(jié)果進(jìn)行處理。首先獲取推薦電影的ID:
```
scala> val recsMoviesId = recommendations.withColumn("recommendations", explode($"recommendations")).select("recommendations.movieId").as[Int].collect()
```
在結(jié)果中顯示出推薦電影的名稱和類型:
```
scala> moviesDF.filter($"movieId" isin (recsMoviesId:_*)).show(10, false)
+-------+--------------------------------------------+---------------------------+
|movieId|title? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |genres? ? ? ? ? ? ? ? ? ? |
+-------+--------------------------------------------+---------------------------+
|1258? |Shining, The (1980)? ? ? ? ? ? ? ? ? ? ? ? |Horror? ? ? ? ? ? ? ? ? ? |
|1387? |Jaws (1975)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |Action|Horror? ? ? ? ? ? ? |
|2804? |Christmas Story, A (1983)? ? ? ? ? ? ? ? ? |Children|Comedy? ? ? ? ? ? |
|3275? |Boondock Saints, The (2000)? ? ? ? ? ? ? ? |Action|Crime|Drama|Thriller|
|5679? |Ring, The (2002)? ? ? ? ? ? ? ? ? ? ? ? ? ? |Horror|Mystery|Thriller? ? |
|8641? |Anchorman: The Legend of Ron Burgundy (2004)|Comedy? ? ? ? ? ? ? ? ? ? |
|8783? |Village, The (2004)? ? ? ? ? ? ? ? ? ? ? ? |Drama|Mystery|Thriller? ? |
|8874? |Shaun of the Dead (2004)? ? ? ? ? ? ? ? ? ? |Comedy|Horror? ? ? ? ? ? ? |
|51662? |300 (2007)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |Action|Fantasy|War|IMAX? ? |
|54503? |Superbad (2007)? ? ? ? ? ? ? ? ? ? ? ? ? ? |Comedy? ? ? ? ? ? ? ? ? ? |
+-------+--------------------------------------------+---------------------------+
```
實(shí)驗(yàn)結(jié)果較好的反映了用戶愛好的電影名稱和類型背蟆。但是該推薦結(jié)果仍存在一定的缺陷:推薦了部分用戶已經(jīng)看過的電影鉴分。主要原因在于,該用戶評價了1000多部電影带膀,評分高于2.5分的就有1035部志珍,而本次訓(xùn)練使用的數(shù)據(jù)集只有約9000部電影,數(shù)據(jù)集太小垛叨,導(dǎo)致訓(xùn)練的結(jié)果不夠理想伦糯,如果使用更大的數(shù)據(jù)集訓(xùn)練模型,推薦效果會有所提高嗽元。
上述實(shí)驗(yàn)過程是在Spark-shell交互式環(huán)境下進(jìn)行的敛纲,并且只嘗試了一個用戶`userId=274`的情況。現(xiàn)在將全部過程集成到一個腳本文件`movie.sh`中剂癌,通過參數(shù)指定為哪一個用戶推薦電影淤翔,文件內(nèi)容如下:
```
#!/bin/bash
if [[ $1 -lt 1 || $1 -gt 610 ]];
then
? ? ? ? echo "Usage: ./movie_recommend.sh userid(1~610)"
? ? ? ? exit -1
fi
userId=$1
sed -i '10c var userId = '${userId}'' movie.scala
spark-shell < movie.scala
```
其中,`movie.scala`是將上述Spark-shell交互式環(huán)境下執(zhí)行的所有spark指令集成到一起形成的文本文件佩谷。腳本文件`movie.sh`從第一個輸入?yún)?shù)`$1`是中獲取用戶ID旁壮,然后使用`sed`指令將`movie.scala`文件中的`userId`替換為該用戶ID监嗜,再逐一執(zhí)行其中的spark指令。當(dāng)輸入?yún)?shù)為`274`時抡谐,程序執(zhí)行效果如下:
```
# ./movie.sh 274
...
+-------+--------------------------------------------+---------------------------+
|movieId|title? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |genres? ? ? ? ? ? ? ? ? ? |
+-------+--------------------------------------------+---------------------------+
|1258? |Shining, The (1980)? ? ? ? ? ? ? ? ? ? ? ? |Horror? ? ? ? ? ? ? ? ? ? |
|1387? |Jaws (1975)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |Action|Horror? ? ? ? ? ? ? |
|2804? |Christmas Story, A (1983)? ? ? ? ? ? ? ? ? |Children|Comedy? ? ? ? ? ? |
|3275? |Boondock Saints, The (2000)? ? ? ? ? ? ? ? |Action|Crime|Drama|Thriller|
|5679? |Ring, The (2002)? ? ? ? ? ? ? ? ? ? ? ? ? ? |Horror|Mystery|Thriller? ? |
|8641? |Anchorman: The Legend of Ron Burgundy (2004)|Comedy? ? ? ? ? ? ? ? ? ? |
|8783? |Village, The (2004)? ? ? ? ? ? ? ? ? ? ? ? |Drama|Mystery|Thriller? ? |
|8874? |Shaun of the Dead (2004)? ? ? ? ? ? ? ? ? ? |Comedy|Horror? ? ? ? ? ? ? |
|51662? |300 (2007)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |Action|Fantasy|War|IMAX? ? |
|54503? |Superbad (2007)? ? ? ? ? ? ? ? ? ? ? ? ? ? |Comedy? ? ? ? ? ? ? ? ? ? |
+-------+--------------------------------------------+---------------------------+
```
最后裁奇,我們把這個程序集成到AppScale應(yīng)用中心了。通過應(yīng)用注冊頁面麦撵,將電影推薦應(yīng)用注冊到應(yīng)用中心刽肠,注冊頁面如圖0所示。
圖0 注冊應(yīng)用
注冊成功后免胃,在APP Center主頁面選擇電影推薦應(yīng)用音五,在參數(shù)框中輸入用戶ID并執(zhí)行,圖0是應(yīng)用的執(zhí)行效果杜秸。
圖0
至此放仗,我們就完成了電影推薦應(yīng)用的開發(fā)任務(wù)。
## 第3節(jié) 機(jī)器學(xué)習(xí)實(shí)驗(yàn)
Deep Learning(深度學(xué)習(xí))是機(jī)器學(xué)習(xí)領(lǐng)域中一個新的研究方向撬碟,它被引入機(jī)器學(xué)習(xí)使其更接近于最初的目標(biāo)——人工智能诞挨。Deep Learning是學(xué)習(xí)樣本數(shù)據(jù)的內(nèi)在規(guī)律和表示層次,這些學(xué)習(xí)過程中獲得的信息對諸如文字呢蛤,圖像和聲音等數(shù)據(jù)的解釋有很大的幫助惶傻。它的最終目標(biāo)是讓機(jī)器能夠像人一樣具有分析學(xué)習(xí)能力,能夠識別文字其障、圖像和聲音等數(shù)據(jù)银室。Deep Learning是一個復(fù)雜的機(jī)器學(xué)習(xí)算法,在語音和圖像識別方面取得的效果励翼,遠(yuǎn)遠(yuǎn)超過先前相關(guān)技術(shù)蜈敢。Deep Learning在搜索技術(shù),數(shù)據(jù)挖掘汽抚,機(jī)器學(xué)習(xí)抓狭,機(jī)器翻譯,自然語言處理造烁,多媒體學(xué)習(xí)否过,語音,推薦和個性化技術(shù)惭蟋,以及其他相關(guān)領(lǐng)域都取得了很多成果苗桂。Deep Learning使機(jī)器模仿視聽和思考等人類的活動,解決了很多復(fù)雜的模式識別難題告组,使得人工智能相關(guān)技術(shù)取得了很大進(jìn)步煤伟。Deep Learning以神經(jīng)網(wǎng)絡(luò)算法為起源,以模型結(jié)構(gòu)深度的增加而發(fā)展,隨著大數(shù)據(jù)和計算能力的提高產(chǎn)生了一系列新的算法持偏。
兩種最常見的深度學(xué)習(xí)框架——TensorFlow和Pytorch驼卖。TensorFlow 是一個開源的氨肌、基于 Python 的機(jī)器學(xué)習(xí)框架鸿秆,它由 Google 開發(fā),并在圖形分類怎囚、音頻處理卿叽、推薦系統(tǒng)和自然語言處理等場景下有著豐富的應(yīng)用,是目前最熱門的機(jī)器學(xué)習(xí)框架恳守。除了 Python考婴,TensorFlow 也提供了 C/C++、Java催烘、Go沥阱、R 等其它編程語言的接口。PyTorch 是一個 Python 優(yōu)先的深度學(xué)習(xí)框架伊群,能夠在強(qiáng)大的 GPU 加速基礎(chǔ)上實(shí)現(xiàn)張量和動態(tài)神經(jīng)網(wǎng)絡(luò)考杉。
在AWS平臺上訂閱預(yù)裝Deep Learning環(huán)境的過程和上一節(jié)中訂閱AppScale環(huán)境的過程基本一致,只是在選擇虛擬機(jī)模板這一步不同舰始,需要選擇Deep Learning字樣的模板崇棠,如圖0所示。
![1570257334506](./images/deep_learning/1570257334506.png)
2. 選擇實(shí)例類型
![1570254062790](./images/deep_learning/1570254062790.png)
3. 配置實(shí)例詳細(xì)信息
![1570254116509](./images/deep_learning/1570254116509.png)
4. 添加存儲
![1570254143952](./images/deep_learning/1570254143952.png)
5.添加標(biāo)簽
![1570254166798](./images/deep_learning/1570254166798.png)
6.配置安全組
![1570254212216](./images/deep_learning/1570254212216.png)
7.審核
![1570254246020](./images/deep_learning/1570254246020.png)
8.選擇密鑰對并下載
![1570254354134](./images/deep_learning/1570254354134.png)
9. ssh連接
修改pem文件權(quán)限
? ```
? chmod 400 tensorflow_new.pem
? ```
查看IP
? ![1570261365084](./images/deep_learning/1570261365084.png)
ssh連接
? ```
? ssh -i tensorflow_new.pem ubuntu@ec2-13-250-104-250.ap-southeast-1.compute.amazonaws.com
? ```
10.測試
查看所有的環(huán)境
? ```
? conda info -e
? ```
? ![1570261484191](./images/deep_learning/1570261484191.png)
加載環(huán)境
? ```
? source activate tensorflow_p36
? ```
測試代碼
? ![1570261859897](./images/deep_learning/1570261859897.png)
顯示以上信息表示深度學(xué)習(xí)環(huán)境搭建成功丸卷。
### 實(shí)驗(yàn)1. 數(shù)字識別
**數(shù)據(jù)集介紹**
手寫數(shù)字識別可以說是深度學(xué)習(xí)中的入門級案例枕稀, 我們將借助于機(jī)器的力量完成視覺方面的內(nèi)容, 我們的手寫數(shù)字識別用的數(shù)據(jù)集是mnist數(shù)據(jù)集谜嫉, 他的結(jié)構(gòu)如下:
| 文件? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | 內(nèi)容? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |
| ------------------------------------------------------------ | ------------------------------------------------ |
| [`train-images-idx3-ubyte.gz`](http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz) | 訓(xùn)練集圖片 - 55000 張 訓(xùn)練圖片, 5000 張 驗(yàn)證圖片 |
| [`train-labels-idx1-ubyte.gz`](http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz) | 訓(xùn)練集圖片對應(yīng)的數(shù)字標(biāo)簽? ? ? ? ? ? ? ? ? ? ? ? |
| [`t10k-images-idx3-ubyte.gz`](http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz) | 測試集圖片 - 10000 張 圖片? ? ? ? ? ? ? ? ? ? ? |
| [`t10k-labels-idx1-ubyte.gz`](http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz) | 測試集圖片對應(yīng)的數(shù)字標(biāo)簽? ? ? ? ? ? ? ? ? ? ? ? |
這些都是壓縮包的格式萎坷, 當(dāng)我們解壓縮之后——借助于read_data_sets()函數(shù)就可以完成——得到的結(jié)構(gòu)如下:
| 數(shù)據(jù)集? ? ? ? ? ? ? ? | 使用用途? ? ? ? ? ? ? ? ? ? |
| ---------------------- | --------------------------- |
| `data_sets.train`? ? ? | 55000 組 圖片和標(biāo)簽, 訓(xùn)練集 |
| `data_sets.validation` | 5000 組 圖片和標(biāo)簽, 驗(yàn)證集? |
| `data_sets.test`? ? ? | 10000 組 圖片和標(biāo)簽, 測試集 |
**環(huán)境準(zhǔn)備**
- windows中matplotlib庫的下載
? 1. 點(diǎn)擊pycharm中File中的Settings
? ? ![1570177375735](./images/deep_learning/1570177375735.png)
? 2. 點(diǎn)擊Project interpreter
? ? ![1570177440739](./images/deep_learning/1570177440739.png)
? 3. 點(diǎn)擊右邊的“+”, 輸入matplotlib沐兰, 安裝即可
? ? ![1570177729256](./images/deep_learning/1570177729256.png)
- 測試:
? ? 打開Anaconda Prompt哆档,輸入"activate tensorflow"啟動tensorflow環(huán)境,后輸入"python"進(jìn)入python環(huán)境
? ? 輸入測試代碼
? ![測試截圖](./images/deep_learning/測試截圖.png)
? 輸出內(nèi)容僧鲁,測試完畢虐呻,安裝成功
- Mnist數(shù)據(jù)集下載
? 網(wǎng)址如下:[點(diǎn)擊進(jìn)入下載](http://yann.lecun.com/exdb/mnist/)
? ![1570175579942](./images/deep_learning/1570175579942.png)
**使用方式**
+ 項目的框架構(gòu)成:
? ![1570175803224](./images/deep_learning/1570175803224.png)
? 1. 第一個部分是mnist數(shù)據(jù)集, 將之前下載好的數(shù)據(jù)集copy到mnist目錄下
? 2. 第二個部分是tensorflow圖的權(quán)重和偏置值的保存寞秃, 底下為固定的保存的格式
? 3. 第三個部分為我們的主程序斟叼, 即為訓(xùn)練手寫數(shù)字識別的主函數(shù)
+ 執(zhí)行的方式
? 直接運(yùn)行main.py即可運(yùn)行
+ 程序的輸入
? 1. 第一種輸入格式直接調(diào)用test測試集中的images, 我們也可以使用matplotlib庫中自帶的imshow()函數(shù)進(jìn)行顯示
? ? 例如:
? ? ![1570176839399](./images/deep_learning/1570176839399.png)
? 2. 第二種輸入格式是我們自己去制作圖片春寿, 但是這種制作圖片的方式并不是任意的朗涩, 需要時黑底白字, 即背景顏色全部是黑色绑改, 中間的數(shù)字為白色才可以谢床。
? ? 這一種輸入方式我們可以借助于畫圖工具完成兄一, 但需要注意一個地方:
? ? 我們的程序接受的圖片的大小為28 \* 28, 所以我們需要修正圖片的大小為28 \* 28, 這一步我們同樣可以借助于cv2——python中的一種圖像處理庫——的reshape()函數(shù)识腿。
+ 程序的輸出
? 程序的輸出很明顯就是去對我們輸入的圖片進(jìn)行解析并說明這幅圖片是什么結(jié)果出革, 為了方便和源圖片進(jìn)行比對, 我們可以將輸出的結(jié)果和源圖片放在一起進(jìn)行輸出渡讼。
? 這一步我們可以借助于matplotlib庫中的text()函數(shù)完成骂束。
+ 運(yùn)行的示例
![](./images/deep_learning/運(yùn)行結(jié)果.png)
>
>
>對于結(jié)果的解釋:
>
>1. 這是一個3*3的圖, 每一個圖形都是一張數(shù)字成箫, 表示這是我們輸入進(jìn)去的圖像的信息
>
>2. 每個圖像的正下方是一個輸出的預(yù)估的結(jié)果展箱, 即通過我們訓(xùn)練后由預(yù)估器給出的結(jié)果
>
>3. 可以發(fā)現(xiàn)大部分的預(yù)測的結(jié)果都是很準(zhǔn)確的, 只有極少一部分的結(jié)果是錯誤的蹬昌,我們實(shí)現(xiàn)的手寫數(shù)字識別的準(zhǔn)確率是很高的
**結(jié)語**
這雖然只是tensorflow的一個入門級程序混驰, 但是我們已經(jīng)可以從中發(fā)現(xiàn)一些深度學(xué)習(xí)的玄妙之處, 我們可以借助機(jī)器的力量實(shí)現(xiàn)一些我們生物才可以完成的事情皂贩, 希望讀者可以好好領(lǐng)會其中的思想栖榨, 并且加以運(yùn)用。
### 實(shí)驗(yàn)2. 智能寫詩
**實(shí)驗(yàn)內(nèi)容**
利用pytorch網(wǎng)絡(luò)先紫,訓(xùn)練一個智能寫詩機(jī)器人
**環(huán)境準(zhǔn)備**
> 上文中已經(jīng)介紹了如何部署基于TensorFlow的深度學(xué)習(xí)環(huán)境治泥,在這個基礎(chǔ)上,我們再來介紹下如何部署pytorch框架
**當(dāng)前環(huán)境說明:**Ubuntu 16.04 + Python3.6
- 安裝pytorch
? 在pytorch官網(wǎng)(https://pytorch.org/)遮精,根據(jù)自己的環(huán)境進(jìn)行選擇:
? <img src="./images/deep_learning/image-20191004184128452.png" alt="image-20191004184128452" style="zoom:80%;" />
? 在shell中運(yùn)行:`pip3 install torch torchvision`
? 等待安裝完畢即可居夹。
- numpy等基本的Python包一般系統(tǒng)都已自帶,在遇到缺少相關(guān)庫文件時本冲,直接利用`pip3`包管理器進(jìn)行安裝即可准脂。
**運(yùn)行寫詩機(jī)器人**
- 克隆項目倉庫 `git clone git@github.com:braveryCHR/LSTM_poem.git`
? 項目結(jié)構(gòu):
? - data.py -- 預(yù)處理數(shù)據(jù)
? - config.py -- 配置網(wǎng)絡(luò)腳本
? - checkpoints -- 訓(xùn)練結(jié)果
? - main.py -- 訓(xùn)練腳本
? - test.py -- 測試腳本
- 下載數(shù)據(jù)集 http://pytorch-1252820389.cosbj.myqcloud.com/tang_199.pth
- 根據(jù)情況修改網(wǎng)絡(luò)`config.py`
? ```Python
? class Config(object):
? ? ? num_layers = 3? # LSTM層數(shù)
? ? ? data_path = 'data/'? # 詩歌的文本文件存放路徑
? ? ? pickle_path = 'tang.npz'? # 預(yù)處理好的二進(jìn)制文件
? ? ? author = None? # 只學(xué)習(xí)某位作者的詩歌
? ? ? constrain = None? # 長度限制
? ? ? category = 'poet.tang'? # 類別,唐詩還是宋詩歌(poet.song)
? ? ? lr = 1e-3
? ? ? weight_decay = 1e-4
? ? ? use_gpu = False
? ? ? epoch = 50
? ? ? batch_size = 16
? ? ? maxlen = 125? # 超過這個長度的之后字被丟棄檬洞,小于這個長度的在前面補(bǔ)空格
? ? ? plot_every = 200? # 每20個batch 可視化一次
? # use_env = True # 是否使用visodm
? ? ? env = 'poetry'? # visdom env
? ? ? max_gen_len = 48? # 生成詩歌最長長度
? ? ? debug_file = '/tmp/debugp'
? ? ? model_path = "./checkpoints/tang_new.pth"? # 預(yù)訓(xùn)練模型路徑
? ? ? prefix_words = '歲歲年年花相似,年年歲歲人不同'? # 不是詩歌的組成部分狸膏,用來控制生成詩歌的意境
? ? ? start_words = '閑云潭影日悠悠'? # 詩歌開始
? ? ? acrostic = False? # 是否是藏頭詩
? ```
- 訓(xùn)練模型? `python3 main.py`
? <img src="./images/deep_learning/image-20191004185644106.png" alt="image-20191004185644106"? />
- 查看訓(xùn)練效果 `python3 test.py`
? ![image-20191004190604219](./images/deep_learning/image-20191004190604219.png)