台灣軟體產業的失落十年穫得 Readmoo 年度推薦電子書活動 「2013 犢力回顧」之 眼光犢具獎
軟體開發是一個很困難的過程,近日大家受到對岸淘寶網的天文數字成交金額的震憾,就如同我在一至千萬的藝術 — 如何養成支撐網路巨量交易的伺服器艦隊這篇文章裡所寫的一樣,這些都是需要一步一步累積經驗成長的,比起不管是對岸還是其它先進國家,台灣的軟體產業落後國外十年以上,身為軟體工程師,我懂得不多,有一點心得分享,於是我寫了一本書─「台灣軟體產業的失落十年」,其中所指的軟體產業的範圍包括網路產業。失落十年的意涵是指落後國外十年的技術、觀念與各種不同的面向。
這本書不只寫給技術人看,也希望對於管理者或決策者有一些幫助,就像拋磚引玉這句話所說的,希望能引起更多人思考該如何走出現在的困境,歡迎有興趣的讀者前往Leanpub購買,而因為Leanpub只支援PayPal和信用卡付款,欲使用ATM轉帳、支付寶等其它方式付款,也可以前往Readmoon購買。
曾經,我有個夢想….
我還記得那是大一的時候,那是一切的開始,我偶然在PTT上聽見了一個叫小鯨電臺的線上廣播電臺,聲音甜美的國中小女生所架設的電台,講述著她在國中生活遇到有趣的事情、拿起吉它自彈自唱,在學校辛苦了一天之後回到家裡,打開了電腦,連上網路,就有個人在對你說說話,談談心,一天的辛苦全都忘記了,就是這樣簡單的感動,讓我有了這個想法,當時架設網路電台不是容易的事,那小女生有交大的朋友幫忙提供了頻寬才有辦法做到的,那我為什麼不能做一個這樣的平台來讓每個人都能夠架設自己的網路電台? 我什麼都沒有,我有的只有技術和無限的熱情
我生長在網路成長的年代,見證了無數的網路創業,他們都靠著創新與熱情改變了全世界,台灣自稱科技矽島,每年賣出那麼多電腦,但卻連個像樣的軟體產業都沒有,所以我想做這個網站,我有個夢想,有一天大家可以很自豪地向外國人介紹
「Now.in is from Taiwan」
有了目標,我在心中漸漸有了藍圖,我在夢中看見了平台的價值,聽眾可以透過電台找到並且購買喜歡的優質音樂,DJ可以透過電台推薦好的音樂給聽眾,只要經過平台購買,DJ也能獲得收益,而唱片公司的優質音樂也會因此提升銷售量,這是一個多贏的局面
然而這只是我們能想得到這個平台所能帶來價值可能性的一種而已。
為了實現我的夢想,老師在課堂上課,我在書桌上畫著系統的設計圖
一張又一張地畫,一張又一張的畫,架構不好重新設計,架構不好再重新設計,程式寫有問題再改,有問題再改
為了表達即時廣播的意思,選了Now.in這樣的網址,列出無數個標語中選出了「Broadcast and listen to the world」,終於在,看著她一天一天的成長,就好像我的女兒一樣
為了盡量避免影響到線上的電台和聽眾,我半夜爬起來更新伺服器
有時出門在外,接到通知網站故障了,克難地在捷運站用手機連上主機一個鍵一個鍵地輸入指令進行維修
到噗浪兼職,身兼學業、開發噗浪、還有開發Now.in的負擔,為的是能夠負擔昂貴的主機費用
每一個細節,每一行程式,一次又一次的改版,為的只是能夠帶來最好的服務
參加了Gulu.com公司所舉辦的學生網路創業交流活動,希望在場可以認識許多有志一起奮鬥的創業夥伴
興奮地向在場的所有人解釋我的理想好像還是昨天剛發生的事情而已
我都已經想不起來我到底為了這個網站到底付出了多少代價
經過這麼多的努力,終於開始受到了一點的肯定…,我們參加了經濟部商業司主辦的IDEAS Show,獲得了ASUS和Intel的企業獎…..
接著受到Intel的邀請代表台灣到美國博克萊大學參加創業比賽,我們和二、三十幾組來自全世界各地大學代表的創業團隊競爭,用破爛的英文介紹Now.in,我們進入前八強,在那裡我很驕傲地說
「We’re from Taiwan!」
一位柏克萊大學商學院的學生在展場看見我們的作品,向我們表示這真是創新想法,邀我到他們學院去演講這樣的概念,然而我們機票已經訂好了時程很緊湊也就沒有接受
看著流量分析的報表,我們的使用者已經遍佈了來自世界各地有網路的角落
我只是一個來自離島的窮學生,有著滿腔的熱情想要改變這世界,我所有的武器就只有我的知識和我的雙手,但我知道不可能我們單薄的資源來達成我們的夢想,我們花了更多的心血,和有興趣投資的公司進行交涉,進行了無數次的Business Plan簡報和會議,我們拒絕了千萬等級的投資,只因為我們希望能夠找到一家能夠配合我們理想的公司來投資我們,而不是為了短期的利益就這樣賤賣我們的心血,我們也單槍匹馬跑去跟MUST談各種授權的可能性
然而,三月二號這一天,這一切都成了泡影,有誰能來告訴我這些付出到底是為了什麼!!??
「關掉它。」
我聽著命令的語句,帶著無限的驚恐,用我顫抖的雙手在一堆闖入我房間的陌生人面前敲了指令殺死了我的親生女兒Now.in,看見網頁伺服器停止運行的瞬間,我不知道該如何表達我當時的心情….
我一直以為法律的目的是在保護好人,然而那天證明我錯了,法律是在保護懂法律、玩法律,甚至能修改法律的人,而難道只因為這樣那些不懂法律的人就該死嗎???? 創新錯了嗎???
從市調處做完筆錄回到了我的房間,我忘了剛才發生了什麼事,我以為那只是一場惡夢,不以為意,我一如往常回到家走到電腦面前,甩了一甩滑鼠想要解除螢幕的省電模式,漆黑的螢幕依然沒有任何反應,低頭看見書桌下空蕩蕩的主機位置,匆忙中搬走留下的錯縱的電線,我才發現這不是夢,這是血淋淋的現實….
接到家裡打來的電話,母親驚恐地跟我說一群人來我家搜索,我真的不知道該怎麼安慰我媽,網站是我做的,為什麼要這樣連累我的家人? 我懂得程式設計,但我真的不懂法律,為什麼需要這樣動用到大批調查人員從我老家金門和台中同時搜索嗎? 我只是個有著夢想的單純的學生,我是能做什麼樣的反抗?
陸陸續續接到親友、老師、同學,我認識所有人的關心,我不知道該如何跟他們解釋當天發生的事情
我收到無數從世界各地寄來的信件,來詢問我們為什麼Now.in上不去了?
在以前就算半夜我也會爬起回覆他們說我們正在搶修伺服器,然後拼了命地找出出問題的原因,然後用最快的速度修正問題,然而現在我完全不知道要怎樣面對那些曾經支持過我們的使用者們…..
我覺得我對不起家人,每個月拿家裡的錢去做嚴重虧損的創業,現在不只一無所有,結果到最後還波及我家人,我開始覺得不知道要做什麼,半行程式都寫不了…..,不敢在電話中暢所欲言,深怕是不是講錯了什麼話電話被監聽了會被判刑,開起網頁只是不停的重新整理Gmail頁面,腦子一片空白,我不太想吃東西,我連半點出門的意願都沒有,覺得呼吸不順,滿腦子盡是想起那些人說的話,未來還得面對官司纏訟這類事情,躺在床上什麼事都不做也睡不著,不知不覺就發現天已經亮了….,連續兩三天來我真正有睡著的時間就只有幾小時,開始有了一些可怕的想法,我覺得我的精神狀態來到了極限,我不知道我是怎麼了….,我想起了憂鬱症的可能性,我上網查了,該有的症狀都有了,還好我自己發現了這件事找了同學幫忙,不然我也不知道到底會發生什麼可怕的事,我現在已經好很多了,請認識我的人不用替我擔心。
如果創新在台灣是一種罪那就這樣吧,我已經累了,我再也沒有做夢的力氣,你們贏了,放過我的家人還有朋友吧….
身為網頁的開發者,相信在寫CSS時總會覺得少了什麼,很多風格明明都只差了一兩句,就得複製貼上或是重寫好幾次,此時你會想說如果有個import或是繼承CSS樣式的語法就好了、如果能夠訂義變數然後在CSS語法裡做加減乘除就好了,而事實上這些SCSS與SASS都可以幫你做到,它是一種將CSS擴充的一種語言,先用SCSS或SASS寫好網頁的排版,接著再用工具將其編譯成CSS檔
取此官網的例子,例如你可以這樣寫
$blue: #3bbfce;
$margin: 16px;
.content-navigation {
border-color: $blue;
color:
darken($blue, 9%);
}
.border {
padding: $margin / 2;
margin: $margin / 2;
border-color: $blue;
}
事先定義好變數,然後在裡面使用,這還只是小小的功能之一,它還可以繼承、引入,比起直接寫CSS方便了許多
搭配上Compass/Blueprint的CSS框架,寫起來更是輕鬆愉快,想要重設CSS嗎? 很簡單
@import "compass/reset"
@include blueprint-global-reset;
@import “compass/reset” 是指引入compass/rest這個模組,然後@include blueprint-global-reset;是指將SCSS的語法片段插入此處,如此一來就輕鬆地重設了CSS,然而,這只不過是小菜一盤
想要採用CSS3,但是又對於-moz、-webkit、-ms … 那些數不清的非標準前綴感到噁心嗎? Compass也提供了輕鬆的寫法
@import "compass/css3";
.rotated-fonts {
@include rotate(-45deg);
@include apply-origin(0% 50%, false);
}
產生後的結果
.rotated-fonts {
-moz-transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
-o-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
transform: rotate(-45deg);
-moz-transform-origin: 0% 50%;
-webkit-transform-origin: 0% 50%;
-o-transform-origin: 0% 50%;
-ms-transform-origin: 0% 50%;
transform-origin: 0% 50%;
}
夠簡單吧? 原本讓人快抓狂的N種瀏覽器支援一句就搞定了
那你問SCSS和SASS差在哪裡? 事實上是一樣的東西只差別在語法,SCSS的語法是以CSS做為基礎延申的,較容易懂,而SASS的語法是和CSS無關,重新設計過的語法
你說: “等等! SASS不是Ruby on Rails的工具嗎?” 是沒錯,但是有人將SCSS的編譯器移植到Python的環境下,叫PyScss,有了它就可以用Python來編譯SCSS,然而雖然說可以編譯,但是直接使用PyScss有點難用,而且和Compass/Blueprint要合在一起使用還得自己去找那些框架的檔案設到load_paths使用,因此我寫了一個open source的小工具叫 scss_tools,使用方法非常簡單,先是安裝
pip install scss_tools
然後以YAML語法寫一個簡單的設定檔
---
monitor:
# path to Scss directory for monitoring
scss_dir:
scss
compiler:
# verbosity of message output
verbosity: 2
# compress
compress: True
# debug info
debug_info: False
# root of static files
static_root:
static
# directory to output sprite images
asset_root:
static/asset
# paths to import
load_paths: [
static/scss
]
# path of input files and path of file to output
scss_files: [
[scss/style.scss, static/style.css],
[scss/style2.scss, static/style2.css],
]
...
接著是執行指令
scss_monitor -c scss.yaml
就是這麼簡單,這樣一來它就會監視你設定檔裡指定的目錄,一但檔案有改變的話,會自動幫你重新編譯一次,如此一來你就可以專心寫SCSS,讓自動編譯的工作交給工具去做
相關資訊
PyPi網址: http://pypi.python.org/pypi/scss_tools
Bitbucket Hg repo: https://bitbucket.org/victorlin/scss_tools
2012年就快到了,離2015年第三次衝擊也快到了,聽不懂我在說什麼就算了,跟主題沒什麼關聯,在介紹主角之前,我總喜歡描述一下主角的背景
這幾年,雲端炒得正熱門,愛跟風的台灣人當然也不能錯過,不管是賣主機的聲稱他們是做雲端的,連賣房子的也稱他們的是雲端,哪天就連在路邊看見雲端雞排都請不要大驚小怪,因為那簡直和奈米光觸媒雞排一樣有異曲同工之妙,然而口口聲聲說雲端,但前陣子iPhone4S預購就給了各家電信商一個大巴掌,他們的網頁伺服器都在一瞬間被大量擁入的使用者塞暴了,更別說其中還有不乏自家推出雲端服務的電信商,說真的,連分散運算的基本功都做不到,跟人家雲什麼端? 你說是吧?
但今天的主角不在於雲端是什麼,或是什麼才算真正的雲端之類的無聊話題,今天的主軸是分散式運算,因為要達到規模的運算和高可得性就一定要扯到分散式的運算,而分散式運算一直以來都不是什麼簡單的議題,最麻煩的就要算是溝通上的問題了,如果你有 三個節點之間想要進行溝通,該怎麼做? 你說簡單,直接串在一起不就好了?
好吧,或許你只有固定的三個節點,這好辦,但是要是你有更多的節點呢? 考慮一下十個節點、一百個節點,光想像就能知道,隨著節點的增加,他們之間的連線數量呈指數在成長,不光是連線本身的問題,連這些節點要如何知道對方的存在以及管理都是相當麻煩的問題
光想就令人頭皮發麻,為了解決這樣的問題,一個經典的模式被設計且大量使用,那就是所謂的 Broker (掮客),也有稱Message Queue、Message bus等,簡單的概念,就是所有節點都連向中間的訊息交換伺服器,而溝通都透過這個中央的交換中心來進行,節點與節點之間無需去在意到底誰在和我溝通,只需要在意訊息的種類和內容即可
雖然這樣的模式解決了一些問題,但同時也引入了新的問題,分散式系統的頭號公敵 – 單點失效,正因為所有節點都依賴著這個中間的掮客幫忙轉送訊息,這也意味著當掮客網路斷線、當機等等意外的發生,都會讓整個系統陷入停擺的狀態,除此之外還有另一個嚴重的問題,就是當節點數量增加,掮客的工作量也會一直往上升,在無法擴增的情況下會造成整個系統的擴展性受到限制,效能也會因為掮客受限制
正因為中央集權造成了問題,所以有人提出了各自為政來解決問題,可以想見的,在分散式的系統裡,並不是所有的節點都需要和所有人進行溝通,他們通常只需要和特定的節點溝通,舉個例子,假設你設計的是一個多媒體檔案的處理系統,在第一個節點可能做的是Hash,用來產生該檔案的唯一識別編號,節點二做的是轉檔,節點三做的是儲存,節點四做的是歸檔,那麼你需要的就只是這樣的結構,如果以物件導向的設計模式來看,我們稱這樣的結構為責任鏈
再看另一個例子,如果我們是氣象局,想發佈各種天氣的消息,那麼你需要的是一個伺服器,讓大家去訂閱他們有興趣的主題,這樣的結構以物件導向的觀點來看就叫做觀察者模式,與我們先前見到的Broker做的是一樣的事情,然而在這裡的重點在於該伺服器只負責發佈天氣消息,並不參與訊息的交換
整體的概念就是各自有不同的子系統,我們透過通訊的方式將它們串在一起,這樣做有個好處就是效能好,再來就是設計得當的話,某子系統雖然停擺,但不會影響到所有的系統
雖然各自為政的做法有其好處,但相對的也有它的代價,光是通訊的協定就是很腦人的問題,考慮一下各種不同的子系統間該用什麼協定來溝通,連線中斷了怎麼處理,負載平衡? 這些問題都要花相當大的心力來解決
而在不同的環境下,使用不同的通訊方式都各有好處,使用TCP/IP的話,不管你的節點在同一台機器或是遠端,都可以連線,缺點是在同一台機器會有一定的效能耗損,而且遇到廣播的訊息,同樣的訊息被傳送N次,就沒有UDP廣播來得划算,如果節點是在同一台機器上溝通,使用IPC的方式效率好,如果是在同一個thread裡,那麼分享記憶體的方式最快,IPC反而會拖慢速度,正因為有各種不同的考量,使得想要達成高效率的分散式系統是一件困難的事情
講了半天,ZeroMQ到底是做什麼用的? 簡單的來說,它是一套網路通訊函式庫,用來解決上面所提到的問題,考慮一下上面所提到的物件導向設計模式,你想,即然那樣的模式一再出現,為什麼我們不能將這些常見的通訊方式變成可以輕易重覆使用的形式? ZeroMQ所做到的即是如此,它將常見的通訊方式定義成不同行為的socket,讓你可以輕易地重覆使用這些樣式去組出強健的分散式系統
雖然大部份程式範例都喜歡用Hello world,但我不喜歡和他們一樣,我喜歡Hello baby,我們來看一下簡單的Hello baby範例
Client端
import zmq
context = zmq.Context()
socket = context.socket(zmq.REQ)
socket.connect ("tcp://127.0.0.1:7788")
socket.send('hello')
print socket.recv()
Server端
import zmq
context = zmq.Context()
socket = context.socket(zmq.REP)
socket.bind ("tcp://*:7788")
print socket.recv()
socket.send('baby')
有了這兩隻Python程式,你可以在本機執行,不管Client或Server端先執行都可,你可能覺得我在說笑,哪有Client先執行的道理,接著會解釋,他們輸出的結果會像這樣
Client端:
baby
Server端:
hello
所以這之間到底發生了什麼事? 很簡單的在Client方,以TCP的方式連接了本機端的7788 port,接著送了一個request,然後接收response然後印出來,而Server方以TCP的方式綁定了本地端的7788 port,然後讀了一個request後送了一個response回去,這有什麼特別? 目前為止 … 沒有,但是,讓我們看下去
上面的兩隻程式整體看起來和一般的socket沒兩樣,而行為也沒太大差別,所以它的特異功能到底在哪裡? 難不成它可以用手指讀封包嗎? 事實上它的特別之處可多了,這兩個程式並沒有表現出太特別的地方,我們改寫上面的程式讓它變成更能突顯ZeroMQ特色的程式
req.py
import zmq
import random
import time
context = zmq.Context()
socket = context.socket(zmq.REQ)
socket.bind("tcp://*:7788")
# wait all worker connected
time.sleep(1)
for i in range(9):
a = random.randint(0, 100)
b = random.randint(0, 100)
print 'Compute %s + %s ...' % (a, b)
# send request to peer
socket.send_multipart([str(a), str(b)])
# receive response from peer
rep = socket.recv()
print ' =', rep
rep.py
import os
import zmq
context = zmq.Context()
socket = context.socket(zmq.REP)
socket.connect("tcp://localhost:7788")
print 'Worker %s is running ...' % os.getpid()
while True:
# receive request
a, b = socket.recv_multipart()
a = int(a)
b = int(b)
print 'Compute %s + %s and send response' % (a, b)
socket.send(str(a + b))
接著我們先執行三個rep.py,然後再執行一個req.py,看發生了什麼事
req.py
Compute 43 + 91 ... = 134 Compute 21 + 63 ... = 84 Compute 17 + 93 ... = 110 Compute 29 + 98 ... = 127 Compute 90 + 55 ... = 145 Compute 14 + 74 ... = 88 Compute 3 + 85 ... = 88 Compute 12 + 73 ... = 85 Compute 73 + 21 ... = 94
rep.py 1
Worker 7296 is running ... Compute 43 + 91 and send response Compute 29 + 98 and send response Compute 3 + 85 and send response
rep.py 2
Worker 6532 is running ... Compute 21 + 63 and send response Compute 90 + 55 and send response Compute 12 + 73 and send response
rep.py 3
Worker 5928 is running ... Compute 17 + 93 and send response Compute 14 + 74 and send response Compute 73 + 21 and send response
這兩個程式做的事情很簡單,req.py負責產生兩個亂數,將其當作request送給socket的另一端,而rep.py則是接收兩個亂數request,算出結果來送回給發出者,但是這好像哪裡不太對勁是嗎? 三個socket連到一個socket? 為什麼送request的一方居然變成bind而不是connect了? 為什麼connect的一方先執行居然也能連線? 我已經聽見你在電腦前的吼叫,讓我們來說明一下這到底是怎麼回事
ZeroMQ的socket有個特色就是對於誰先bind誰後connect之類的完全都不在乎,如果是connect先行,發線連線無法建立,ZeroMQ會自動重試,當bind也確立了,連線就會自動接上了,有了這樣的特性,只要管怎樣連接和對方的位址是什麼即可
通常bind那端都是位址為大家所熟知的那端,connect都是位址不為人知的,以我們這樣的例子,因為worker可能有很多個,所以我們將REQ端換成bind,而REP端換成connect,方便多個rep.py連接req.py
除此之外,REQ和REP兩種角色不管哪方bind哪方connect運算的方式都是一樣的
ZeroMQ的socket之間可以多個互相連線,所以一個socket的另一端可能有N個節點連接,除此之外,同一個socket也可以綁在不同的位址上
如果你仔細觀察上面程式的輸出,就會發現request是依序分配給三個rep.py,這也是ZeroMQ的特色之一,REQ端會將send的message用Round-robin的方式分給所有的遠端連線,而你有多少個連線,他都會照一樣的規則分配
但因為先前有提到自動連線,因此會有個問題,當第一個連線接上時,其它連線還來不及連上,此時request可能已經大量分配給第一個連上的,為了解決這問題,我們安排了time.sleep(1)來等所有worker連上線,以避免早起的鳥兒吃光了蟲子
你可以發現我們送資料和處理的都是訊息,ZeroMQ可以提供你傳送多段資料在一筆訊息裡,因此我們在這看到的send_multipart和recv_multipart作用即為如此,有了這樣的特性,你可以不用擔心通訊協定的問題,只管專心處理訊息即可
看見了我們在程式中所寫的 “tcp://*:7788” 和 “tcp://localhost:7788” 嗎? 它們暗示了TCP只不過只是支援的其中一種通訊方式而已,ZeroMQ還支援IPC,但目前只支援Linux下的domain socket,例如 “ipc:///tmp/req.socket”,甚至它還支援thread之間的通訊方式,因此如果你的兩個節點放在同一個process裡,為了效能考量,你可以用這種協定讓通訊效率最佳化
為了節省傳輸的封包,ZeroMQ甚至提供了基於UDP的廣播通訊方式,因此當你將節點放在同一個網路下,廣播的功能就能節省大量的重覆封包傳送
雖然我們的範例都是用Python寫的,但ZeroMQ目前已經支援了幾乎所有你能在臺面上看到的主流語言,你喜歡用Lua寫子系統A? OK! 你喜歡用Perl寫系統B? 可以! C語言? 當然沒問題,你喜歡PHP? 厄… 也可以啦,總之,ZeroMQ讓你從不同語言的通訊中解藕開來,只要專注在於訊息的處理上即可
我們做為範例的REQ/REP只不過是ZeroMQ所提供的樣式中的一種,它的特性就是一個request一個response,而REQ端會對所有的連線做fairly queue,也就是會公平地把request塞給REP端,借用官網的圖
另一種常見的樣式是PUB/SUB,也就是我們先前提到的觀察者模式,它的特色是所有PUB發送的消息會廣播給所有SUB的連線,而且SUB可以設定只要某段字串開頭的訊息
還有很常見的需求就是我們想將資料往某個方向負載平衡地推送,這時PUSH/PULL樣式就派上用場了,PUSH會將負載分散給PULL端,而且只能由PUSH推往PULL
還有另一種樣式是跟一般socket沒兩樣的一對一連線,叫做PAIR
事實上ZeroMQ還有更高級的樣式,但在本文就不介紹了,接著要大略介紹的是官網的一個例子,將這些樣式組合在一起,官網的例子是說當你需要分散式的Key/Value形式的伺服器,利用ZeroMQ來達成其組合方式就像這樣
伺服器端負責儲存一份快取,且會透過PUB/SUB讓client保持更新,而Client端被更動時,透過PUSH丟給伺服器,如果新的Client上線了,就透過REQ向server要最新的一份快取
這樣就是用ZeroMQ實作分散式系統的一個簡易的實例,事實上它能做遠比這個還要更複雜的東西
有了ZeroMQ,分散式系統從又難又腦人變成很難而已,因為通訊的部份由ZeroMQ來完成了,剩下的你只要專心來考慮節點之間的拓撲與連接方式、通訊方式,以及訊息的處理,不同語言的支持也讓你不再擔心要用什麼語言來實作,或是你的同事懂不懂某種語言,真正的心力被花費在設計上,分散式系統因此也能更輕易地設計與實作,而ZeroMQ的本意是用於即時地處理大量的金融資料,效率更是ZeroMQ的金字招牌之一,如果你需要複雜且高效的分散式系統,ZeroMQ絕對是你的好朋友
最後,值得一提的是有人基於ZeroMQ的特性設計了與程式語言無關的網頁框架,或著更精確的來說是與伺服器溝通的協定,類似CGI或WSGI那樣,但是透過ZeroMQ因此和語言無關,叫Mongrel,目前出到第二版,有興趣可以研究看看,或許哪天也會寫篇文章來介紹
今天遇到MySQL的master伺服器主機當機的情況,當master復原時,slaves都出現了
Slave I/O: Got fatal error 1236 from master when reading data from binary log: ‘Client requested master to start replication from impossible position’, Error_code: 1236
這樣的錯誤,問題起因很簡單,原本的slave正在pulling的master.bin因為當機的原因不正常關閉,當下次master資料庫終於正常重啟時,master會開另一個bin繼續寫,此時slave重練上後不知道有這件事,繼續向master要求當掉的bin的下一筆資料,就會出現上面的錯誤
只要確定是這種情況,解決方法就是看新的bin編號是到多少
mysql> show master statusG *************************** 1. row *************************** File: master-bin.000073 Position: 21959116 Binlog_Do_DB: Binlog_Ignore_DB: 1 row in set (0.00 sec)
把file和位置指向下一個新開的檔案
slave stop; CHANGE MASTER TO MASTER_LOG_FILE='master-bin.000073', MASTER_LOG_POS=0 ; slave start; show slave statusG
如此一來就解決了
參考資料 http://firelitdesign.blogspot.com/2011/04/how-to-fix-mysql-replication-error-1236.html
前面幾篇談到了台灣軟體產業界常見的毛病,除了工具以外,還有一項令我感到相當意外的,就是我發現台灣業界對於開放源始碼的認知真的很有問題,例如我曾有和別人討論過,跟他們你們可以使用open source的現成資源來減低成本,但是得到的回應很常是
那不是讓你用但之後就要付錢嗎?
從這類的回應就可以大略知道,其實有很多人對於開放源始碼都有一些錯誤的認知,到底什麼時候該付錢、什麼情況可以使用都搞不清楚,因此這回我大算介紹一下一些常見的開源授權的常識
在讀本文前我得先聲明,我不是律師,這不是提供專業的法律見解,只是試著用較易懂的方式解釋授權,以我自己的經驗來說明,其中多少可能會有錯誤,請自行判斷,也歡迎指出錯誤,如有需要請洽詢專業的法律諮詢,在本文末會提到
首先要從授權(License)的概念開始談起,開放源始碼通常不是只是單純把程式碼公開出來,而是一般都會搭配某種授權,而授權的意思,以白話來說,就是寫了一份聲明,裡面這樣提到
此程式任何人可以免費使用,但是使用前你必需遵守以下條款….
有了這樣的聲明,使用開源的人就可以放心使用,當然前提是要遵守授權所提出的條款,基本上因為已經授權出來,所以就算是原作者反悔,也沒辦法控告你什麼,除非你違反他當初訂出來的條款,而一般人看見落落長的條款項目可能就怕了,更何況是用英文寫的,但是別擔心,事實上要注意的要點只有幾樣,都大同小異,同一類條款的性質都很類似
在理解授權之前,首先要理解散佈,這是授權裡面一定會提到的重要關鍵動作,那麼什麼是散佈呢? 簡單的來說,就是將軟體轉交給其它人,不管你是以原始碼的形式,或是編成二進制執行檔後,只要是轉交給其它法人,就算是散佈,舉個例子
把原始碼上傳供人下載
把原始碼拿來販售
把原始碼編成執行檔供人下載
把原始碼編成執行檔販售
以上都算是散佈的行為,所有的授權條款裡面都會提到散佈開源程式時你應盡的義務,當然,也有很多行為是稱不上散佈的,例如
將原始碼交給公司內部某個單位
將原始碼編譯成執行檔自己使用
在伺服器上以開源程式執行提功服務給使用者
像這樣沒有法人的經手,都不算是散佈的行為,對於散佈的行為介定是很重要的,等一下會解釋
授權有(License)非常多種,我們在此大略將其分成三大類,第一類是GPL,第二類為BSD,而第三類為商業授權,是較為特別且少見的,其中GPL最不自由,而BSD最自由
在這裡自由與不自由主要是指你在使用這些開源軟體時所要盡的義務的多和少
GNU General Public License主要是由Linux陣營的開源軟體開發者為主在使用的,它有幾個特色
如同我們前面提到的散佈,最重要的重點是,如果你改了程式,而又要散佈程式,那麼你在散佈的同時也要把你修改的部份也公開出來,例如
修改了原始碼後拿來販售
修改了原始碼後編成執行檔供別人使用
上列行為都扯到了散佈,因此如果你程式有修改,你不能只給別人執行檔,要連改的部份一起開源出來,這條款的目的主要是在於GNU的社群,希望強迫使用者能回饋社群,因為一但你改了程式,想拿來賣錢,就得公開出來,避免有人改進了程式,拿來販售,但沒有公開程式的問題
當然,如果你的程式只是自己使用,或是公司內部使用,那麼你修改了程式但因為沒有散佈的問題,所以修改的部份也不用因此公開,還有一種情況是,你使用GPL授權的程式放在伺服器上提供服務,因為這過程沒有重新散佈,所以也不會有問題
除了修改以外,還有一個特性,就是病毒的感染性,除了修改程式,如果GPL的原始碼是函式庫,而你的程式連結了GPL授權的程式,不管是靜態連結或動態連結,都會因此被GPL感染,一但你要散佈你的程式,因為用了GPL的函式庫,因此你的主程式也被感染,變成你要把你的主程式一起開源出來,就因為這特性替GPL贏得了「病毒授權」的美名,就跟T病毒一樣,被感染了就會變成殭屍
就因為這樣病毒的特性,讓很多人又愛又恨,很多商業軟體想用某些開源的函式庫,但因為那些函式庫如果是GPL授權,會導至他們的產品本身也受到感染,而因此無法使用,變成非GPL和GPL這兩種可能性,除此之外,有些廠商為了避免被GPL感染,會用一些比較特別的手法來避開
因為這樣感染的特性,加上如此不自由的特質,使得很多可以用上GPL的場合卻因為感染性而無法達成,為了能解決感染的問題,它有推出另一種弱化版的GPL,叫LGPL (Less Generic Public Licence),這個授權大致上和GPL是一樣的,差別就在於上面提到的連結受感染的問題,連結LGPL的函式庫並不會受到感染,如此一來就算是商業軟體也能安心地使用LGPL的函式庫
接著還有它的排它性,因為授權有很多種,GPL規定它的授權條款不能被修改,這表示你修改了程式要散佈被強迫要開源的話,你也只能選擇GPL的授權,除非你是原作者
你覺得GPL很不自由嗎? 事實上他們覺得GPL還不夠嚴格,正因為GPL陣營的人認為像Google之流的廠商,用了GPL的程式提供服務,不用公佈修改的部份,因此覺得心癢癢的,為此甚至增加了AGPL,更加嚴格的GPL,他的重新散佈的定義,擴增為包括提供服務,因此即使你用了AGPL的程式提功服務,沒有轉交程式給他人,就AGPL的定義,這就算是重新散佈,然而使用AGPL授權的程式其實非常少,像是MongoDB就是使用AGPL授權,正因為LGPL/GPL/AGPL強烈的限制特性,反而使它成為商業軟件開源的最愛授權,對手要修改販授的話也得公開,不想公開的話就得買另外的授權,這就是常見的雙授權商業模式,因此我個人喜歡戲稱GPL為 “商業友善授權”
如果說GPL是邪惡的病毒授權,那麼類BSD就是自由又開放的授權,相較於GPL相當害怕別人用了GPL的程式不回饋,類BSD就大方許多,它最主要的條款就是,當你散佈修改過的類BSD授權下的程式,一樣不管是二進制的執行檔或原始碼,你要盡的義務就只有記得要把類BSD的授權一起轉交給別人就可以了,包含原作的姓名你也得一併加進去,不能自行亂改
舉個例子,你改了一個BSD授權的應用程式,你想編譯好成執行檔放到網路上供人下載,可以,只要連著當初的BSD授權一起散佈即可,不必把你改的部份也公開出來,因此你可以安心的用BSD授權的函式庫來寫商業軟體
下面的例子都是不違反BSD授權的做法
修改BSD授權的程式編譯成執行檔來賣,只提供執行檔而非原始碼給使用者,原始的BSD授權條款也得一併給使用者
連結BSD授權的函式庫,主程式只提供執行檔進行販售,原始的BSD授權條款也得交給使用者
而下面這些例子可能會違反BSD授權
重新散佈BSD授權的程式,竄改並宣稱自己才是原作者
散佈BSD授權的程式,但不附帶BSD授權條款
那你可能會問附帶BSD授權條款是怎樣辦到,很簡單,通常都只是一個LICENSE.txt檔案夾在壓縮檔裡之類的即可
而類BSD在此只是通稱,因為有很多授權都有這類主要的特性,大約列出常見的,像是
其中zlib最為自由,只要求不能亂改作者,不能聲稱修改的版本才是原始版本,以及不能移除授權,也就是散佈時一樣要附帶zlib授權,除此之外還有很多非常多種的授權,這只是常見的幾種,只要你看見他有寫 “BSD-like”,通常就是指它的特色跟上面描述的差不多,有些專案會特別量身訂作他們自己的授權,但精神大多都會跟這些主流授權差不多
除了類BSD和GPL,其實還有很多授權,是一些基於商業考量定出來的授權,通常較為少見,所以不多做討論
前一陣子常見到很多台灣資訊業界的人瘋狂轉貼MySQL要開始收費的消息,如果懂得上面提到的授權條款就會覺得這很好笑,以MySQL的例子來說,它的程式碼已經用GPL的形式開放出來了,就像潑出來的水收不回去了,即使他們想收錢都沒辦法了,MySQL官網上的授權費用,還有授權說明,都說得不清不楚,即使你打過去問他們到底什麼情況要收錢,他們也只會給你閃爍不清的說法,其目的就只是要騙不懂開源授權的笨蛋去買他們的商用授權,如果你的公司真的有過多預算當做贊助他們是不錯,但都已經被Sun之後又被甲骨文買下來事實上他們也不缺錢了
有興趣可以參考 探討 MySQL 授權
很多人也有個想法就是開源軟體找不到商業支援,但事實上這是錯的,正因為開源軟體免費的特質,它很多的商業模式其實都是在提供商業支援,例如 Percona,他們提功的是強化版的MySQL InnoDB引擎,因為MySQL的GPL授權,導致他們修改的部份也得開源,因此他們當然不可能靠賣軟體獲利,而是靠商業諮詢和技術支援、專業訊練等等項目
因此,事實上很多開源軟體的商業資源遠比商業軟體還來得豐富,因為商業軟體的支援就只有他們提供,他們不幫你你就沒折,然而開源軟體因為是開放的,反而很多人可以解決同樣的問題,最糟的情況不過是自己下海解決而已,至少還不是商業軟體的無解
關於開源軟體的商業模式其實是一個很有趣的議題,有空的話可以另外寫一篇來介紹
好吧,講了這麼多,如果你問我你們公司有X專案,要用到Y授權的Z套件是否可行? 請別問我,請問中研院的自由軟體鑄造場的免費開源法律問題諮詢吧,有免錢的程式可以用就算了,還有免錢的諮詢可以問,好諮詢,不問嗎?
除此之外還可以參考他們比我更專業一百倍的法律源地部落格
常常看見很多單位都在花大錢買商用軟體,但是一看卻發現這些功能遠比不用錢的開源軟體還差,像是以資料庫為例,就有看見某間學校買了某家公司的資料庫,限制連線人數,功能殘缺不齊,連稱得上現代的資料庫都有困難,然而授權都動不動數十萬數百萬,就覺得很好笑,有免錢的不用,要花大錢去買垃圾來用,使用開源的軟體,並不只是省錢而已,用現成的函式庫與軟體兜出需要的系統更是提升競爭力與減少開發成本的關鍵,台灣如果不能掌握開源的相關知識會比現在更加落後世界的腳步
而授權雖然條款很多看起來很嚇人,實際理解了之後其實也不會太難懂,陣營也很清楚,如果要比喻的話,GPL陣營認為人性本惡,把大家都當成是會開發後就不想把東西公開,每人都是自私的,所以條款強制性很重,而BSD則是相反,要不要開放是你自己的決定,而我認為如果要開源就不怕別人用,怕別人用的話就別開源
最後,我們這裡談到的只是從使用層面來看開源授權而已,台灣大多廠商目前的狀況是連使用上的認知都有困難,更別說是貢獻開源,以後有機會再來寫一些相關的文章
你是否有計算過,你在寫專案的過程中,測試過了多少次的程式? 我想是沒有,我也沒有,但是你是否有曾想過,或是感覺過,隨著專案的膨漲,你要測試的項目也跟著變多了? 這是理所當然的事情,當專案小,測試還算很輕鬆,因為程式的功能不外乎就那幾樣,一轉眼就測完了,常見的寫程式流程會像這樣
撰寫新功能
測試新功能
當然,也有修正bug的情況
修正bug
測試bug
如此一直循環,當你寫了新功能,理所當然地會去測試新功能,看是否如你預期地執行,那舊功能呢? 或許你記憶力不錯,在寫新功能的同時,想到先前某個舊功能是依賴現在改的東西,這麼一改可能會造成舊的功能出問題,於是你也順便測了一下舊的功能,當程式還小
撰寫新功能
測試新功能
測試舊功能
嘿,不怎麼樣吧? 只佔了開發時間的三分之一,好吧那如果有更多的舊功能要測呢?
撰寫新功能
測試新功能
測試舊功能
測試舊功能
測試舊功能
….
發現了沒有? 隨著你的專案越來越大,如果要確保整個系統所有的功能都是正常運作的,無可避免地,在你修改程式之後要測試的項目會越來越多
這表示你每寫一行新程式的成本增加了,身為以減低成本為傲的島國 國民: 台灣人…,你說,簡單! 不要測舊功能不就好了? 是的,我想這可能就是最常見的情況,不要測試舊功能理所當然地,每寫一行的程式成本都保持一樣很低,但這代表著舊程式可能出錯的風險也跟著增加了,當你喜滋滋地覺得你幫公司省了成本,結果在一個月後因為舊程式缺乏測試,因改動了核心的部份造成舊的功能將所有資料外洩,公司損失慘重,這就是不重視軟體品質的後果
舉真實生活上發生過的例子,PTT曾經有過改程式未經好好地測試,造成每個人都能以管理員的權限登入的事情,知名的檔案同步平台Dropbox,也曾經發生過因為認證的程式改版有bug,造成任何人都可以登入別人帳號的事,我也有曾聽聞一些網站因為工程師為了測試方便,把認證的函數暫時改成
function authenticate(user_id, password) {
return true;
// do authentication here
// ....
}
然後又不小心commit,因此讓任何人都通過認證的事情,但這些都不能只怪工程師本身,誰能無過? 人總會犯錯的,問題出問於工程本身的制度、專案的管理、和工具的使用上
在未來,網路的應用越來越多,而軟體的品質重要程度只會越來越高,所以,要如何維持軟體的品質又同時能不讓測試的成本隨著專案的擴張而跟著無限制地成長呢? 答案就是 – 自動化測試
自動化測試聽起來好像很美妙,讓電腦自動幫你測試程式? 有這麼好的事情嗎? 事實上不是那樣,自動化測試,是透過寫好的規則,自動對於程式進行測試,所以終究還是得需要人力的介入,那你或許會問,結果倒頭來還不是得用人力? 那到底有什麼好處? 答案就跟我們先前提到的一樣,如果你的專案很小,用人力測試其實可能就已足夠,但當你的專案夠大,如果沒有自動化測試,那麼光是在測舊的程式就是相當龐大的成本上
引入了自動化測試不代表程式就不會出錯,它不是萬能的,但是它至少保證了程式一定的品質,只要使用得當,就能降低測試的成本,也能讓大部份有經過自動測試的程式都不會出現太離譜的錯誤,至於要怎麼做,讓我們看下去
最常見的測試,就是單元測試(Unit test),通常是針對單一個或是少數類別,確保這些類別單獨運作是正確的,舉個例子,你寫了一個類別,是用來找輸入的地圖的最短路徑,那麼你就得替這類別,寫一個單元測試,餵入你準備好的資料,然後取得輸出的結果,看是否和你準備的預期答案是一樣的,舉一個最簡單的例子,一個用Python來將輸入文字拆解成一行一行的解析器
class LineParser(object):
def __init__(self, newline='rn', remain=''):
self.newline = newline
self._buffer = [remain]
self._size = len(remain)
def feed(self, data):
self._buffer.append(data)
self._size += len(data)
def getLine(self):
data = ''.join(self._buffer)
index = data.find(self.newline)
if index != -1:
line = data[:index]
self._buffer = [data[index + len(self.newline):]]
self.length = len(self._buffer[0])
return line
def iterLines(self):
line = self.getLine()
while line is not None:
yield line
line = self.getLine()
它的單元測試就長這樣
import unittest
class TestLineParser(unittest.TestCase):
def makeOne(self):
return LineParser()
def testParser(self):
p = self.makeOne()
p.feed('abc')
line = p.getLine()
self.assertEqual(line, None)
p.feed('rn')
line = p.getLine()
self.assertEqual(line, 'abc')
line = p.getLine()
self.assertEqual(line, None)
# write lots line
p.feed('111rn222rn3333')
lines = list(p.iterLines())
self.assertEqual(['111', '222'], lines)
line = p.getLine()
self.assertEqual(line, None)
p.feed('rntext')
line = p.getLine()
self.assertEqual(line, '3333')
line = p.getLine()
self.assertEqual(line, None)
# write nothing
p.feed('')
line = p.getLine()
self.assertEqual(line, None)
p.feed('rn')
lines = list(p.iterLines())
self.assertEqual(lines, ['text'])
self.assertEqual(list(p.iterLines()), [])
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestLineParser))
return suite
if __name__ == '__main__':
unittest.main(defaultTest='suite')
很簡單的想法就是列出幾種常見的case,還有你能想到的特例代進去,好的測試資料要能夠測到每一行程式,但是要做到那樣需要花不少心力,其實能夠做到大部份常見的情況和常見的特例,就已經相當足夠
有些程式,無可避免地會依賴其它程式,如果我們針對這兩個程式同時測試,會無法分出出錯到底是誰的錯,再者,很多依賴的部份可能會牽扯到IO或是其它系統資源,讓測試變得更複雜,例如有個類別是負責輸出文件到印表機的,那你要如何確認印表機印出來的東西是正確的? 答案就是做一個假的 (Mocking)印表機丟給那個類別去做列印的動作,再去讀取裡面的資料,確認跟你預期的一樣
雖然單元測試在相當單純的模擬環境下測過了我們的程式,然而世界並不是那樣的美好,總有些事情沒有經過真槍實彈操演過可能會有差錯,因此有時我們會引入部份受控制的真實環境來測試,例如你想測試網路連線,或許你可以寫一段script在Amazon EC2上建起幾個instance,並上傳程式到那些機器中,自動讓他們連線來確保這些功能是正常的,然而越真實的環境變因就越多,因此測試也就相對困難
Joel有說過 每日建構是你的朋友 (Daily build is your friend),也有提過 軟體開發成功的12個法則 (The Joel Test: 12 Steps to Better Code),裡面的daily build是指利用工具每天自動建構整個專案,通常對於編譯式的語言,如C語言寫的大型專案會較需要這類的工具,但是這樣的工具還有一個目的,在於確保程式是可以正常編譯的,並且讓測試員容易拿到最新的程式進行測試,然而自動化測試,同樣的也需要類似的工具,因為通常你在改完程式就會進行測試,那每次一改完程式就得跑一次測試指令,這不是一件很煩的事情嗎?
記得,工具是為我們服務的,不是我們為工具服務,這樣重複的瑣事理所當然也是由工具來幫忙,謝天謝地現今有好用又免費的工具,可以幫你做到這點,那就是Jenkins,它是一套基於網頁的自動化測試管理工具,它可以做到什麼呢? 它可以做到幫你定時去版本控制系統取程式回來,用預先設定好的流程進行測試,並且記錄測試的結果,如果有某個測試出錯了,當然也可以發Email通知你,以Now.in的開發為例,因為專案為數眾多,其中又有依賴關係,有了Jenkins的幫忙,程式只要一改送到BitBucket,它就會自動進行測試
Jenkins除了功能強大以外,他還有一項特色令我驚訝,就是非常簡單易用,從安裝到設定完所有的測試,除了clone hg檔案庫和設置測試環境以外,我從沒因為Jenkins打過一行指令,全部都可以透過它友善的網頁介面完成,同時它也有內建資料庫,也沒因此設定MySQL,在Windows下安裝更是容易,一個安裝檔執行完就是安裝完成,如果你希望有工具幫你自動定時測試或是建構,請不要懷疑,Jenkins是你最佳選擇
執行自動化測試的時機,除了剛改完程式,還有一個重要的時機,那就是在你把程式部署到伺服器以前,讓你的自動化部署的script先跑過一次自動測試,確認測試通過了再進行部署,為什麼要這樣做呢? 還記得先前提到的PTT和Dropbox以及一些網站對於authentication的return true慘劇嗎? 為了不讓那種事情發生,或著至少讓機會降低,在deploy前讓自動化測試跑過一次,確保測試的範圍內都是正確的,可以大大降低那種情況發生的機會,除此之外,也比較不會因為改出bug,自己沒發現,等到使用者來抱怨了才知道問題在哪
自動化測試雖然是一項利器,但是得經過正確的使用才會有好的效果,自動化測試有所謂的覆蓋率,也就是你的程式裡以行為單位,有多少行是在跑測試時有執行過的? 這些工具都可以幫你統計出來,但是切記
不要為了追求高測試覆蓋率,替foo bar寫測試
這只是在浪費時間,如果某段程式已經簡單到沒測試的必要,你寫了也是多餘
除此之外,寫測試事實上也是成本,因此如果時間有限,請
優先針對重要的核心、資料模型、商業邏輯測試
因為就算你測再多無關緊要的程式,最重要的核心出錯了,可能整個系統就完蛋了,所以盡量以重要的程式做為測試的優先考量
優先針對安全性相關、存取權限、身份認證、常見攻擊手法測試
雖然身份認證這種事情算不上是核心,但這關係到你的系統會不會被輕易地攻擊,除此之外,如果你的程式是網站應用程式,SQL Injection、XSS、buffer overflow這類攻擊也會很常發生,因此,你也需要優先自行設計一些攻擊,針對這些常見的問題餵一些資料,雖然這無法保證一定不會犯錯,至少確保不會發生太低等級的錯誤,因為常見的case都已經有自動測試過了,搭配先前所提到的,deploy前跑過一次測試,如此一來就能將犯錯的機會降低許多
雖然你的程式可能大多都已經有自動化測試在幫你測試,但即使如此,你還是會發現新的bug,如果說,你直接改了bug,就這樣了事,很有可能在下幾次改版bug又回來了,因此
每當你發現你先前沒想到的bug,請加到你的測試中
如此一來,隨著你針對的bug測試case越多,你的程式品質就越高,未發現的bug也會越少,在未來確保這些bug不會再出現
再一次,自動化測試不是萬能的,除此之外也需要正確的運用,如果台灣軟體業界能夠好好運用自動化測試,軟體的品質可以有所提升,開發者也不會因為除錯除到死加班到天亮,雖然寫測試是額外的負擔,但是對於大形專案長期看來是非常值得的投資
這幾年來,多多少少接觸了不少業界的人,雖然我自己還不算有真正待過業界太久,但是這期間看到不少業界的現象都令我挺驚訝的,例如在聊天時提到你們公司用的版本控制系統是什麼,有很多人都會回答 「那是什麼?」,一直以來這些在國外的主流開發環境都基本常識或是標準配備的東西台灣業界居然很多都連有那樣的工具存在都不知道,或著是對於某些東西有錯誤的認知,所以我想大略提一下常見的幾個問題
我想這是最常見的毛病,很常發現很多公司在開發軟體時從來都不使用版本控制系統,最誇張的狀況就是管它三七二十一直接修改
除此之外,常見的土法鍊鋼有聽說過資料夾複製,然後將資料夾名稱命名為版本1之類的方法,高級一點還有搭配Excel來記錄改過了什麼之類的
更進階的還有多人共同開發,還架了FTP來放這些檔案
但這些都有很大的問題,而且其會遇到的問題都正好是版本控制系統所要解決的,所以到底是什麼樣的問題非用版本控制系統不可?
首先,用資料夾copy有個很大的問題,一來是copy的過程很容易出錯,而且更糟的是出的錯很難發現,你怎麼從資料夾的內容來判斷這到底是哪個版本? 最常見的做法就是回想你到底在哪個版本改了什麼,然後去看對應的位置,是否有那些改動,但你有可能記得嗎? 要是程式不是你改的呢? 因此依賴資料夾名稱來得知檔案的版本是極度不可靠的做法,再者,如果你不幸改到錯誤的版本,辛辛苦苦改了半天,才發現改到舊版本了,那你要如何把你改的和正確的版本合在一起? 如果你只改了三行,這還好辦,但如果你改了三百行,那該怎麼辦? 用Excel來記錄改動的事項和版本一樣不會有幫助
那多人開發使用FTP來分享檔案呢? 老天,事情更慘了,原先只是你自己的開發,自己改錯了就算了,如今變成多人開發,有時出問題還不是你改的,這樣想好了,FTP上有個檔案
hello.py
今天張三載回去改了,變成
hello.py (張三版)
不幸的是,王五在張三上傳回FTP之前,也載回來改,變成
hello.py (王五版)
接著,張三把它的檔案上傳了,所以FTP上的檔案變成了
hello.py (張三版)
然後好戲發生了,王五也把它改的東西上傳了,所以FTP上的檔案被蓋掉,變成
hello.py (王五版)
發生了什麼事? 張三改的版本被蓋掉了,你可以想見張三在demo給老闆看時發現改的地方被蓋掉了,翻過辦公桌衝過去揍王五的情景了嗎? 像這樣還只是最簡單的情境,以這類土法鍊鋼的方式,還有太多太多預料不到的複雜情況會發生,什麼? 那你說,如果我們規定每人都得把資料夾以自己的名稱命名,加上版號,再上傳,這樣就不會錯了吧?
hello_project-王五-rev123/
拜託,何苦呢 ? 版本控制系統就是用來解決這些問題而開發出來的,學一套新工具有這麼難嗎? 常見的理由可能會有什麼沒時間學、不信任工具等等,事實上那些都不是理由,只要是程式碼的開發,都得使用版本控制系統,現在已經是2011年,如果你的軟體開發沒有使用版本控制系統,我說這不叫落後,這是原始
用了版本控制系統,最重要的好處是
你可以安心地放膽去改程式
講了那麼多,所以版本控制系統到底是什麼? 這麼來形容好了,如果你有玩過RPG遊戲,像是仙劍奇俠傳、軒轅劍等等,你一定知道遊戲打到一半可以存檔,然後也可以讀檔,接續先前的遊戲進度,如果你發現目前的等級太差了,打不贏魔王,你可以讀前面一點的檔案來重新練,或是前面的寶物忘記拿,也可以讀檔回去拿
而版本控制系統做的事情,就和RPG裡的遊戲存檔是一樣的概念,你可以將目前程式開發的進度的快照存下來,當你發現改的檔案有問題,可以回朔到以前的版本,除此之外,它還可以幫助你找出兩個版本的差異,你再也不怕不小心改了哪一行結果都不知道,也不怕你的同事幫你偷改了幾行結果你卻不知道,甚至還可以合併兩個不同的分支
版本控制系統在幾十年前就已經有現成的工具,然而一直一路演進到今天,現在的版本控制系統已經相當先進且成熟,週邊的工具或是平臺也都相當齊全,從20年前的CVS看起,介紹一些最主流的
算是古董級的版本控制系統,主要用於open source的開發,特色是可以多人共同使用,缺點是如果多人要存取同個檔案,要先將檔案鎖起來,很多時候會鎖了忘了開,造成別人無法使用,雖然已經至古董了,但是因為很多大型的開源專案還在使用這系統,所以現在多少還看見有人使用,但不建議一般用途使用
Subversion簡稱svn,約十年前出現,是CVS的繼承者,主要是改進CVS的缺點,像是最重要的改進,就是檔案鎖定的問題,SVN以試著自動合併檔案的方式來解決同時有兩人修改同一個檔案的問題,現今很多開源專案也還在使用SVN
接著時間來到了現代,分散式版本控制系統成為了新興專案的主流,在這些新的系統中,檔案庫不在是集中式的,而是分散式的,原本commit都要集中送到中央檔案庫,現在不同的檔案庫之間可以互相傳送對方沒有的commit,如此一來讓開發更加容易,即使沒有網路也可以在本地端commit之後再送給遠端的檔案庫,除此之外,它們引入的新特性也讓合併兩個分支之類的事變得更加容易,個人推薦如果你還沒有用過版本控制系統,可以直接學這些現代的工具,以前的工具大多都已經很少人在使用,可用的資源也在變少中
Git是Linus Torvalds開發用來管理Linux核心程式碼用的系統,特色是這套系統以效率為設計的考量,並且支援很多較低階的檔案歷史處理,支援的主流免費檔案庫有
原先Bitbucket只支援Mercurial,這幾天也支援Git了,而Github和Bitbucket的差別在於,Github如果想開私有的檔案庫要錢,而Bitbucket不用,而且是無限個數
如果你想學Git,個人推薦讀
裡面講得非常清楚而且詳細,也有圖說明系統的運作,相對的,如果是讀其它的文件,大多數都很難懂,因為都是寫給geek看的,看不懂是很正常的,整體看起來,Git比Mercurial還難學,除此之外還有一個缺點就是對於Windows的支援較差
Mercurial是基於Python的分散式版本控制系統,特色是以較簡易使用,並且很重視檔案修改的安全性為考量的工具,像是Git有很多指令,一下錯你改的檔案可能因此而遺失,這類情況在Mercurial中較少,大部份指令都會幫你自動備份,如果弄錯指令通常還有救
主流的檔案庫平台是
如果你想學Mercurial,個人推薦看Joel寫的
個人較喜歡Mercurial的原因是它的GUI介面工具較為豐富,像是有
等好用的GUI工具
我在大學教過的教學用投影片,可以參考看看,未來有空的話或許我可以寫一些這方面的教學,因為畢竟中文資源很少,似乎也沒專門的書在講這些工具的使用
在設計不同的網路服務系統時,為了能夠有擴展性,通常都會設計成分散式的架構,然而除了架構上的設計,如何部署也是很重要的事,其中有個很重要的議題叫做區域性,因為沒有統一或明確的翻譯慣例,所以以英文來說明較為精確,在這裡指的區域性英文為locality,對於這個議題最近有一點心得
所以,回到主題,到底什麼是區域性? 簡單的來說,就是存取資料或資源時,很常存取或是相關的資料放在一起、或很近的地方的特性,舉個實例,例如假設我們有關於某個使用者的資料,但是使用者相關的資料分散在全球不同的資料庫裡,這時我們就會說,這資料庫的區域性不好,以圖來表示,我們假設使用者的資料分散在美西的DB1、美中的DB2和美東的DB3,而使用者在西雅圖要存取這些資料,就得走很遠的距離到三個很遠的地點存取資料
反言之,如果關於這使用者的資料,都存在離使用者很近的點,而且也都在同樣或是很接近的資料庫裡,那麼存取起來就會較快,這樣一來我們就可以說這樣的資料儲存方式它的區域性比較好,我們假設把同一使用者相關的資料都放在相近的地方,以圖為例,都放在加洲,這樣一來同樣是存取使用者的資料,其中所花的傳輸距離成本就遠比上一個例子來得少,反應速度也會因此較快
一般而言,區域性是越強越好,但是也有例外,那就是當考慮到可得性(availability)的時候,這樣的特性是指資料或資源隨時都可取得的機率高低,如果當我們把雞蛋放在同一個籃子裡,也就是資料都放在同樣的Datacenter裡,一但這個Datacenter對外的網路中斷,或是甚至遇到不可預料的災難時,那麼那些資料都會因此而無法取得,所以除了考慮到存取時的區域性,當資料有一定重要程度時,可得性也是很重要的考量,所以某些情況下,資料分散也是必要的
然而,區域性就表面看來,似乎只要將常用的、相關的資料都放在一起好像就能達成,然而經過仔細思考會發現其實並不是只有這樣,還有需要考慮到存取資料的距離,還有存取要求本身的高低階,在這篇文章我想分享的就是主要在於思考關於區域性設計上的一些理論的心得
我發現並不是所有的情況下不佳的地區性都一定會嚴重影響到存取的效能,像是請求的相依性其實對於延遲的影響就非常大,如果說所有的情求都不能同時處理,一定得要上一個完成才能完成下一個的話,這樣一來就會造成每個request的請求都要額外花費一次連線的延遲成本,可以參考下圖
很明顯的,左邊的情況,傳輸距離所造成的影響,會是
請求數量 * (運算成本 + 延遲成本)
右邊的情況是
(請求數量 * 運算成本) + 延遲成本
因此光是請求是否能同步處理,並且是否有前後相依,就會造成相當大的差別,如果請求的數量越多,這樣的成本差距就越大,左邊的例子我們以NoSQL或是 SQL的請求為例子,通常下一道請求都是基於上一道請求的資料而決定的,如果說任務被拆散成很零碎的多道請求,像是有些key-value based的NoSQL資料庫,因為沒有高階的查詢指令,必然會有大量的請求,如此一來如果NoSQL資料庫放在很遠的地方,就會造成光是這之間的傳輸成本就會高得嚇人,而以SQL來看,因為可以盡可能地將多道SQL濃縮為少數幾道查詢,因此同樣的傳輸成本對於SQL資料庫來說,傳輸的成本造成的影響會小一點
而右邊的情況,通常是大量的資料傳輸,例如影音串流,因為上一筆資料無關下一筆資料,以這種情況來看,傳輸的距離不會是太大的問題,只有一開始會有的傳輸延遲
一些簡單的心得就是,當請求是相依的,如果數量不大,那麼其實傳輸的延遲是可以被忽視的,又或著是大量連續的資料傳輸,距離的影響是較小的,如果不考慮連線的品質問題的話,但是如果是有相依特性的請求,數量又大的話,最好資料庫的部署要越接近越好,否則光是連線的延遲成本就相當驚人,就算你的NoSQL資料庫再怎麼快,也沒有任何幫助,甚至會比SQL資料庫還要慢