2015年2月25日 星期三

從BYOC談到私有雲

Acer BYOC自建雲

Acer的BYOC自建雲,可分為B2C與B2B兩大類:

B2C

商業模式:以消費者PC為雲端主機,每個人透過手持裝置APP就可以存取自己PC上的各式檔案;分成ab文件、ab相片、ab檔案、ab音樂等四大類程式,在宏碁網站http://www.acer.com.tw/ac/zh/TW/content/byoc-consumer可下載這四套ab程式,安裝在PC後,再透過Android或iPhone手機搜尋Acer ab app,分別安裝這四大類的app,就可以在手機上存取PC上的文件、相片、檔案與音樂。。
獲利方式:目前為免費,且原先限制僅能安裝於Acer PC的限制也已取消,對於B2C宏碁至今尚未找到確定的營利模式。

B2B

商業模式:此為宏碁BYOC的主要方向,基於目前既有的雲端平台AOP(Acer Open Platform),提供給有需要建置雲端計算的企業一套解決方案,亦即由宏碁提供系統整合服務,協助需要私有雲的企業架構;目前拉攏相關合作業者,針對有興趣在車聯網、健康醫療服務、電信、智慧家庭這四大領域發展私有雲的業者提供平台架設服務。
獲利方式:技術的咨詢及授權、權利金、雲端平台的維護服務費用、相關軟硬體的銷售。
對於B2C,宏碁的想法很直接,就是打算在PC與手機之間建立起一條緊密的需求線,希望大量手機用戶能帶動PC的需求,或者…至少讓手機的用戶覺得PC還有些用處而不會閒置;他們設想消費者們會將PC大容量的空間作為檔案的集散中心,有隨時想聽的音樂檔、有各式不同類型檔案、待編輯的Office相關文件、多得嚇人的相片,因此宏碁分別為這四類的用戶製作了獨立的app,讓你在任何地方都可以存取PC上各種類型的檔案,因此你的PC就是個人私有雲,也就是「自己的雲端自己架」。
但其實這種把PC作為個人雲端來使用的方式,我認為它比起Google、Dropbox、Microsoft、Box等等大廠以公有雲方式所提供的雲端硬碟,在應用環境與操作上更加的不方便也不容易普及,或許只對少部份PC重度使用族群有用處,這部份我們未來再加以討論。
至於B2B的部份,宏碁AOP雲端的伺服器規模遠遠比不上Amazon、Microsoft、Google 等等公有雲大廠,因此選擇以服務企業架構私有雲似乎是宏碁目前唯一的選項,不過,問題是,大型企業在建置自有雲端的專案上,對於軟硬體會要求更大更多的自主權,而眾多的中小企業,普遍基於價格因素偏好公有雲方式,因此,宏碁若要以BYOC服務型態來跨入雲端市場,那麼,他們背後單純的「以服務來拉抬自身硬體銷售」思維,可能反而成為推廣BYOC服務的致命傷。

私有雲概念正逐漸轉型中

此外,最重要的是,BYOC所依賴的「私有雲」這個概念正逐漸在退化及轉型當中。
談到私有雲、公有雲以及混合雲的定義,或許我們可以用晶圓代工為例來打個比方,公有雲就像晶圓代工,所有IC design house都將IC交由代工廠來生產製造,因此同樣的,我們可以租用公有雲而將所有的資訊服務建置在其上,那麼私有雲呢?就像是自己蓋一座專有的晶圓廠以實現AMD 創始人Jerry Sanders說過的「擁有晶圓廠才是真男人」的策略,當然就是從最底層的軟硬體建置到最終的雲端服務都是自己來規劃完成,那麼混合雲呢,就是以資料存取性質來區分,分開放置於自有的私有雲和租用的公有雲上。
然而,這三種雲本身又同時具有實體與觀念上的意義,我們可以先租用公有雲,再利用這雲端環境建置專門for公司內部使用的私有雲環境,同樣的,也可以利用公司內部豐沛的硬體資源自建私有雲之後,再以公有雲承租方式分配給大眾使用,因此,對於Amazon來說,AWS是在其私有雲上搭建租賃用的公有雲環境,而對於眾多租用AWS的企業而言,則是在其公有雲上建置其私有雲、或建置提供各種服務的公有雲環境,換句話說,私有雲能作到,公有雲也能作,但公有雲能作的,私有雲不一定能作(例如提供給第三方使用)。
而目前企業在考慮雲端服務時,仍會偏好私有雲而非優先採用公有雲的原因,我認為主要是來自於三點誤解:
  1. 資料的安全性的考量:主觀上認為重要的資料擺在私有雲一定會比公有雲來得安全,亦即將內部資料擺在公有雲上抱持著疑慮。
  2. 經濟效益的考量:認為自行建置私有雲一定會比租用公有雲來得便宜,尤其是OpenStack這類開源免費軟體的盛行更是讓企業趨之若騖。
  3. 使用彈性的考量:認為私有雲的使用彈性比較大,雲的大小和規模以及使用方式可以自行定義調整。

私有雲的前景

  Matt Asay最近在TechRepublic.com的一篇專欄文章:「Private cloud's very public failure」指出目前正火紅的企業私有雲建置軟體OpenStack不但無法協助建構雲端系統,而且更是企業在執行建置雲端專案時失敗的主因,最主要的理由是:私有雲的概念與雲端運算是相悖的,因此它最終將過渡為混合雲,並逐漸往公有雲靠攏,而私有雲的建置軟體OpenStack,由於太過蓬勃的社群與日趨複雜的架構,使得它一直無法提出一套真正符合企業需求的版本,使得僅有5%的企業自認它們建置的私有雲是成功的。
Matt Asay目前是Adobe公司行動業務的副總裁,他曾任職知名的noSQL大廠MongoDB,對於Big Data, cloud, mobile...等方面的開放源碼軟體相當有興趣,他也在TechRepublic.com發表過相當多的文章,下面我們來看看這篇他最近發表的「Private cloud's very public failure」的重點,原文網址在http://www.techrepublic.com/article/private-clouds-very-public-failure/?tag=nl.e101&s_cid=e101&ttag=e101&ftag=TRE684d531
http://tr2.cbsistatic.com/hub/i/r/2014/08/04/2fff4c4d-7770-4d60-9d9d-d65bb3c630ed/resize/620x485/f7687b2ed4674ed8c1982f957ef95372/cloudhead.jpg無怪乎私有雲業者開始稱呼自己為混合雲,因為私有雲的路愈走愈小,在看不到未來的情況下,它註定要失敗。
Gartner分析師 Tom Bittman 探究了為何有95%的私有雲專案失敗的原因,它們的原因都相當明顯:私有雲與雲端運算的未來發展是矛盾的;另外,更加不幸的是,愈來愈多的企業轉向開源免費的OpenStack,開始架構自有的私有雲服務,但這卻讓私有雲的未來發展更加雪上加霜,您可能想問,為什麼?

不但開始就失敗,也經常失敗

大約五年前,Amazon AWS的執行長 Andy Jassy就曾經指出私有雲的問題:一般企業在建置資料中心(機房),對於所需的運算資源規模很難評估,同樣的問題在自行建置私有雲時更會發生,而且更難以估計出實際需求的大小。
    但是這種情況會經常發生嗎?我們普遍都認為,一間大公司理論上應該能夠將建置私有雲的費用降得比租用公有雲更低,而且,從某些觀點來看,企業內部在擁有特定規模的運算資源之後,會因為充分利用而更有效益,正如Chris Aniszczyk在Twitter建議的:"私有雲的好處是大小和規模都可以自己來決定”,不是這樣子嗎?
是的,但是這樣的論點卻未必適用在你的公司,或者說...甚至於大部份的公司都不適用。
  如果雲端運算的目的僅是在於將資源作更彈性的利用,而讓企業經營運作上更具彈性,那麼架設一個內部的私有雲(相對於利用現有資料中心的資源來說)的效益應該不會離這個目標太遠,但是目前的問題是,一般公司經常誤認所謂的建構雲端運算就是將所有實體機器包上虛擬化的糖衣,最終結果當然是無法達到預期的效果。
Private cloud deployment
Gartner的調查:企業建置私有雲失敗的原因比例
正如Apprenda(一家美國的PaaS雲端運算公司)的Chris Gaun指出的:企業組織經常掉入一些陷阱,認為私有雲就是使用一些VM包裝工具(如Chef/Puppet/VMware等)在經過特別規劃的硬體設備中架構出一套虛擬化環境,讓所有的主機與服務執行在這封閉的環境中,但是,往往結果是,這套號稱可因應企業需求而調整的運算環境卻與企業的商業流程完全無關,換句話說,企業組識與營運可能變動,但是這套架構於固定的硬體設備上的虛擬化環境環境卻是受限的,表面上你能夠放大或縮小最上層VM的數量,卻無法動態調整下方的硬體層的配置與規模,這點,是企業私有雲最致命的問題,因為,無論建置的私有雲有多壯觀多具規模,卻因為實際上無法因應企業的彈性而改變,使得大部份企業的私有雲因達不到當初所規劃的效益而以失敗告終。
對於這些企業來說,建置私有雲的唯一好處是,讓它們感覺仿佛擁有了創新,好似擁有了高科技雲端計算,但實際上卻僅是在陳舊的IT設施上披著一件好看的外衣而已;所以Gartner之前所作的調查就發現,所有架設私有雲的企業中,只有5%自認為成功,而這些其它高達95%自認私有雲專案失敗的企業,不約而同的都是採用了私有雲建置軟體中最有名的OpenStack來建置。

OpenStack不是解決方案

受歡迎,不代表它能保證成功。
OpenStack強大的社群支持以及下載人氣反而成了它的軟肋,愈來愈多的公司為它提供各式不同功能的插件,並且持續的以驚人的數量在增加,然而卻也讓它愈來愈複雜,且未來的發展與定位也更不明確,很多OpenStack初入門者沒有頭緒要如何才能成功的應用,因此Joyent(一家雲計算服務公司)的Bryan Cantrill便提到,建置私有雲的企業普遍失敗主因,就是因為OpenStack自身的複雜性。
因此目前OpenStack最迫切的問題,是無法像Red Hat提供的RedHat企業版Linux一樣,能夠提供給企業一個好用且受信任的集中版本以簡化OpenStack的使用體驗,以便讓企業能著眼於公司需求,將重心放在減低移轉私有雲平台成本並制訂明確的私有雲策略,而非漫無目的的擴展不必要且花俏的插件功能。
    另外,就算少數的企業能夠成功的架構私有雲,還有最重要的問題OpenStack所無法解決的,就是「私有雲」這個概念正逐漸被打破,公有雲巨頭們,如Amazon AWS、Microsoft Azure、Google Cloud Platform們正在侵蝕私有雲的市場,讓愈來愈多原先被認為適用於私有雲的軟體和服務也逐漸轉入公有雲上執行。因此,未來只要是跟公有雲比起來不具有效益的方式,無論是採用私有雲或是利用企業內部資料中心(機房)的剩餘資源,都只有被淘汰的命運。

2015年2月12日 星期四

Swift學習心得 – Optional與Unwrap
    「?」和「!」這兩個符號經常會在Swift程式中看到,它們算是Swift程式中比較特別的部份,我在本文中特別提出來,因為Optional型別在Swift中扮演了相當重要的角色,若不瞭解這兩個特殊符號的用法,便無法深入瞭解Swift。
    在C語言,我們經常指定一個變數但沒有先給值,就像這樣:
   
        如果我們一直都沒有給予它任何值,那麼在程式中使用到number這個變數時,我們便會得到undefined的錯誤訊息,此時,我們事後透過偵錯作業來更正這個錯誤。
    而Swift則嚴格要求所有變數和常數都要先給值,不能為空值或unknow的值,但有時候,我們還是會有需要有空值的情況,例如,搜尋時傳回空的結果代表沒有找到任何資訊,因此,Swift為了解決這種狀況,建立了一種稱為optional」的資料型別,凡是特別被指定為optional資料型別,代表它容許有值或沒有值,其它非optional型別的變數(預設狀況),則要求一定要有值。

沒有指定Optional會出現什麼問題?




    舉例來說,上面程式的第一行沒有問題,但是到了第二行便會出現錯誤,為什麼?我們不是已經指定了空值(nil)給message變數了嗎?
    這是因為若沒有指定的話,Swift變數預設都是non-optional而非optional型別,因此nil空值是不被接受的,我想Swift的考量點,主要是空值在程式運算中經常會造成問題,而Apple在創建Swift語言時又強調安全為首要考量,它不希望在Run time執行時期發生任何的錯誤,因此才會要求我們要預先告知那些可能為空值的變數,以避免後續可能造成的問題和debug,所以在上面範例中,我們的message必須被指定為optional才能將nil賦予給它。
所以同樣的,下方的範例也會在第二行出現compile錯誤,因為只有宣告而沒有指定值給message2,這點對於用Object-C開發APP的人而言可能會相當不習慣,因為Object-C的變數宣告時不需要給予啟始值。


指定optional型別

    因此,要避免上述的錯誤,我們必須替那些可能沒有值的變數或參數加上「?」,表示它是optional,這樣Swift在compile時便可過關了,不會發生錯誤:




    為什麼Swift要這麼特別的要求呢?這是有理由的,我們看看下方的Object-C例子:

    這個findStockCode函式會傳回Apple或Google的美股代號,如果不是這兩個代號其中之一,那麼就會傳回nil空值。現在,我們在下方的程式碼中使用這個函式,執行結果會在紅色那行中發生runtime exception,因為傳入的是”Facebook”而不是Apple或Google,所以會讓函式傳回nil空值。
   

    如果這個函式用Swift來寫,程式碼要修改如下:

    請注意第一行,我們必須替傳回值型別String加入「?」符號,代表它為Optional,可能有傳回值,也有可能傳回nil(空值)。
    這樣要求開發人員需特別的指定Optional便是為了避免意料外的情況(出現runtime error),以減少APP在執行時期可能產生的錯誤。(說明白了,也可能是為了APP的使用者體驗吧),也提昇了Swift程式碼品質。

Unwrap拆解

    繼續前面的findStockCode函式範例,現在我們在Swift程式中要使用它:


    結果卻還是在紅色那行產生了compile error,我們不是已經指定了Optional型別了嗎?為什麼還會發生錯誤?
這是因為在使用Optional型別變數時,我們必須先確定該Optional型別變數是否有值,若有的話必須用「!」來unwrap它才行,處理Optional有下列兩種方式:

接收和處理Optional型別

Forced Unwrapping

    第一種是forced unwrapping,我們將上面的範例修改如下,執行時就不會出現錯誤了。請注意紅字的部份,我們加上了「if和「! 」:首先使用if確定stockCode有值(有值會傳回true),然後再用「! 」來unwrap這個optional的變數。



    如果我們沒有先用if 去判斷而直接去unwrap會有什麼結果?在complile時不會有任何錯誤,但是在執行時Swift會丟出如下的Runtime error,因此,先用if來判斷optional變數是必要的。
            fatal error: Can’t unwrap Optional.None

Optional Binding

    另一種較簡便且更常被使用的方式稱為optional binding,它可以快速的檢杳Optional變數是否有值,若有的話就unwrap它。我們一樣用上面的例子來說明:



    紅色的部份「if let是optional binding的重點,if let tempStockCode = stockCode這句話所作的事情就是:如果stockCode變數有值,就unwrap它並且把值放入tempStockCode這個暫存變數中,因為tempStockCode確定有值,所以我們不需要加上「!」的unwrap符號。
    我們也可以將「if let」改成「if var」,省略tempStockCode這暫存變數,讓程式碼更為精簡:

    以上是Optional和Unwrap的作法,在下一篇知識文件中,我們再來看看「Optional Chaining」可選鏈的用法,「Optional Chaining」指的是把呼叫、使用optional等等行為串連在一起,當其中有一段回傳nil,則整體的結果就是nil。

2015年2月5日 星期四

Swift學習心得 – Closure

    接續上文的函式,我們來談談「閉包(Closure)」;正如它的名稱一樣,「閉包」這個名詞讓我們這些初學者感覺既抽象又不親切,對於Swift親切易用的形象立馬大打了折扣,但事實上「閉包」是一種更深層次的函式使用方法,不使用閉包,我們一樣可以寫出相同功能的程式,因此這篇您如果沒有興趣,可以先跳過,亦能學習並寫出完整的Swift程式,不過,若我們瞭解並妥善的使用閉包,則可以讓我們的程式更為幹練與精簡。   
維基百科對於「閉包」的解釋是這樣的:「閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是參照了自由變數的函式。這個被參照的自由變數將和這個函式一同存在,即使已經離開了創造它的環境也不例外。
看不懂對不對?沒關係,除非真的實作過,否則我們初入門者很難一開始便瞭解它的含義,我找到另外一種比較可以讓人接受的解釋是:「閉包其實也是函式,不過它比較特別的是它擁有一組特殊的環境變數--本來以為應該消失,可是卻依然存在的變數。」,因此,我們大概知道閉包就像一大包的東西,它具有如下的特性:
  • 閉包是一種函式
  • 這個函式搭配了一組環境變數
  • 在使用閉包時,能夠用到這些環境變數
下面我們來透過三個步驟來瞭解「閉包(Closure)」:
  1. 就像我們可以指定數值或字串給某個變數一樣,「函式」也可以當作參數般,指定給變數使用。
  2. 當作參數所傳入的函式變數,它可以「Capture」到位於函式上層的變數(也就是,我們可以在閉包函式中取用到外層的變數值)。
  3. 有兩種方式可以用來建立函式,一種是常見的用func開頭方式宣告,這是最常用的方式,另一種則是用前後大括號{}包起來,這經常用在我們的閉包(Closure)函式中。

A)把函式當作參數值來使用

就像我們經常會指定一個數值或字串給參數一樣,例如下方的範例(請仔細看綠色的說明文字)
    // 這是最常見的,宣告一個變數並給予一個數值參數
func printInt(i: Int) {
           println("you passed \(i)")
}
    //我們也可以把這個函式當作值指定給某個變數
let funVar = printInt
    //把printInt指定給變數funVar後,我們就可以像使用原先函式般的來使用funVar,感覺好像是把函式改名了。
funVar(2)  // 把funVar當成printInt來使用,會印出"you passed 2"
//所以我們可以有一個函式,其參數是另一個函式,如下紅色的參數所示
func useFunction(funParam: (Int) -> () ) {
           //在useFunction中呼叫這個傳入的參數
           funParam(3)
}
   
    //所以,我們可以把printInt這個函式當成參數傳入useFunction函式中,
useFunction(printInt)
//也可以把funVar這個函式變數傳入
useFunction(funVar)

B)閉包函式可Capture到上層的變數:

在Closure函式的外層所宣告的變數,其值仍可被該Closure函式所讀取(在Closure我們稱為補獲),請仔細看下方的例子:
//宣告一個returnFunc函式,其回傳值是一個(Int) -> () 的函式
func returnFunc() -> (Int) -> () {
 var counter = 0  // counter這個變數待會兒可在下方的innerFunc閉包中被capture
 func innerFunc(i: Int) {
      //可讀取到counter這個變數,這個變數位在innerFunc閉包外部,這個動作我們稱為capture
   counter += i   
   println("running total is now \(counter)")
 }
 return innerFunc
}

//下方是這個returnFunc函式使用
let f = returnFunc()
f(3)  //會印出"running total is now 3"
f(4)  //會印出"running total is now 7"
//如果我們重新呼叫returnFunc,counter的值會重新開始計算
let g = returnFunc()
g(2)  //會印出"running total is now 2"
g(2)  //會印出"running total is now 4"
//不過,雖然重新呼叫returnFunc,但是counter值並不會相互影響,因為它們屬於不同的變數
f(2)  //會印出"running total is now 9"

C)閉包函式的格式:

Swift有兩種函式的宣告方式:
  1. 用func開頭,這是最常用的方式。
  2. 用前後大括號{}包起來,常用在Closure函式。
我們看看下方的例子doubler變數,它被塞入了一個用前後大括號{}包起來、未命名(anonymous)的函式作為它的值,這個函式就是閉包Closure。
let doubler = { (i: Int) -> Int in return i*2 }
//這個doubler能像一般的變數般當作參數傳遞
[1, 2, 3].map(doubler)  
用前後大括號{}包起來這種方式,不同於用func開頭來宣告,首先,它不需要給名稱,因此它是匿名(anonymous)的。這種函式用法主要用在閉包中,我們稱它是一種自包含(self-contained)的匿名函式:用一個大括號 { } 把程式碼封裝起來,括號裡緊接著函式的型態 ( ) -> ( ) ,型態宣告後面則用 in 這個關鍵字將型態與 Closure 的主程式碼分開,這樣的寫法就是 Closure 最標準的型式;Swift這種closure的方法相當類似於C和Object-C中的程式碼塊、C++和C#中的Lambda運算式、或Java中的匿名內部類別。
    因此Swift閉包語法的標準格式如下:
        {(參數列表) -> 回傳值型別 in
            指令組
        }
例:
        {( a:Int, b:Int) -> Int in
            return a+b
        }
    我們還可以把它寫成一行如下:
{ (參數列表) -> 回傳值型別 in 指令組 }
例:
        {( a:Int, b:Int) -> Int in return a+b }

    但Swift其實可以自動根據上下文環境自動判斷出參數型別和回傳值型別,所以我們可以再簡化如下:
        { a, b in return a+b }
    這樣的閉包夠簡單了嗎?但還可以更簡化,因為return關鍵字其實也可以不需要:
        { a, b in a+b }
    夠簡化了嗎?但如果我們用$0, $1, $2… 來表示閉包中的參數,$0代表第一個參數,$1代表第二個參數,$2代表第三個參數,依此類推則$n+1代表第n個參數,這在Swift中稱為參數名稱縮寫功能,使用此功能後,閉包中的in這個關鍵字也可以省略了,因此我們可以再把上述的閉包寫成如下:
{ $0 + $1 }
  很神奇的,本來我們的閉包長得如下:
        {( a:Int, b:Int) -> Int in
            return a+b
        }
竟然可以縮寫成為 { $0 + $1 } ,足見閉包在簡化程式碼、增加可讀性上的便利。
        所以,還記得我們一開始舉的doubler例子嗎?它可以逐步簡化如下:
  1. [1, 2, 3].map( { (i: Int) ->Int in return i * 2 } )
  2. [1, 2, 3].map( { i in return i * 2 } )
  3. [1, 2, 3].map( { i in i * 2 } )
  4. [1, 2, 3].map( { $0 * 2 } )
  5. [1, 2, 3].map() { $0 * 2 }
  6. [1, 2, 3].map { $0 * 2 }

D)一個簡單的例子

最後我們再用一個簡單的加減計算函式作為例子,當作本文的總結。
首先我們用標準的方法來實作一次。
    // calculate函式會回傳一個函式型別為(Int,Int)-> Int的值
    func calculate(opr :String)-> (Int,Int)-> Int {  
        func add(a:Int, b:Int) -> Int {    //定義加法add函式
            return a + b
        }
        func sub(a:Int, b:Int) -> Int {    //定義減法sub函式
            return a - b
        }
        var result : (Int, Int)-> Int   
        switch (opr) {            //若傳入為+則傳回add函式,-則傳回sub函式
        case “+”:
            result = add
        case “-“:
            result = sub
        default:
            result = add
        }
        return result;
}

    這個函式的用法是;
        var f1 : (Int,Int)-> Int    //先宣告一個f1變數,型態為(Int,Int)-> Int的函式
        f1 = calculate(“+”)    //呼叫calculate函式並傳入+,則會傳回加法add函式儲存於f1變數
        println(“10 + 5 = \(f1(10, 5))”)    // f1(10, 5)等同於執行add(10, 5)
        var f2 : (Int,Int)-> Int    //先宣告一個f2變數,型態為(Int,Int)-> Int的函式
        f2 = calculate(“-”)    //呼叫calculate函式並傳入-,則會傳回加法sub函式儲存於f2變數
        println(“10 - 5 = \(f2(10, 5))”)    // f1(10, 5)等同於執行sub(10, 5)

接下來我們用閉包的方法來實作一次。

func calculate(opr :String)-> (Int, Int)-> Int {
    var result : (Int, Int) -> Int
    switch (opr) {
        case “+” :
            result = {( a: Int, b: Int) -> Int in
                return a + b
            }
        default:
            result = {( a: Int, b: Int) -> Int in
                return a – b
            }
    }
    return result;
}

簡化閉包格式

    請注意紅色的部份,它們分別取代了標準函式作法中的add與sub兩個函式,而這部份就是我們所稱的閉包。依上文中提到的閉包簡化方式,程式碼可改寫如下:
func calculate(opr :String)-> (Int, Int)-> Int {
    var result : (Int, Int) -> Int
    switch (opr) {
        case “+” :
            result = { $0 + $1 }
        default:
            result = { $0 + $1 }
    }   
    return result;
}