笨蛋!關鍵是專業度!
這篇的由來也是因為前幾天發生了一段對話, 讓我想到過去發生(在別人身上 :p)過的慘痛經驗, 就想寫成一篇來分享一下. 畢竟我不是像 Joe Chang & Bryan Yao 這麼專業的顧問, 也常自哂是個沒讀什麼書的粗人, 沒辦法很系統化的去描述大框架的概念, 也只能想到什麼寫什麼了. (這100%是硬要牽拖的友情廣告贊助, Joe/Bryan請留一支黑牌給我當廣告費)
事情是, 某公司用的MySQL Database大概是因為Loading太高, 常常出現一些靈異現象: 當機也就罷了, 常常重啟之後這個Table壞那個Table少資料, 更可怕的是有時候Table會消失. 每次碰到這種麻煩, MIS/網管只好能修則修, 不能修只好倒備份回去. 可是每次倒備份都會有一段時間的資料消失, 甚至是好幾個DB之間相互參照的部份會有資料同步問題, 所以只好讓Programmer去寫一些檢查資料同步與完整性的工具, 然後讓備份更頻繁, 去降低發生時的損傷.
剛開始也只是電話裡口頭給一些可能的解決方案跟方向, 也請他們做過分析最佳化了Schema, 但是也只是稍微改善, 並沒有完全排除這個問題. 直到他們老闆終於首肯讓我進他們系統看看時, 已經是個AQPS(avg queries per second)超過20k的超可怕DB Host了, 當下我只好建議他們往Cluster方向走, 然後有了之前那堆MySQL架構的step by step. (話說到這, Jason你欠我的大餐哩? XD)
當下他們也就從善如流花了一些錢買新設備, 做好了Cluster, 但是顧及線上服務還在走, 他們有計劃的一點一點把東西從舊的搬到新的Cluster去, 悲慘的事情於焉發生, 這時他們備份系統用的磁帶櫃掛了, 暫時沒辦法維持原本的double daily backup, 只能weekly, 而他們心想, 反正已經搬走了30%的loading, 應該不會這麼衰在修好前又掛了吧! 所以就說莫非定律一定在這種莫非的時候發生, 真的突然掛了.
他們雖然也就照以前的災難復原SOP去做, 但是這次備份的間隔有點大, 實在會被罵的太用力, 而他們的DBA也很坦白的說無能為力, 可能得去跟Oracle買MySQL的Consult Service看他們能不能從壞掉的Datafile裡頭救, 於是他們還是找上我.
當我Remote連上去, 看到那些壞的徹底的datafile也是感到很絕望, 但是卻同時看到希望: 他們還有著整整兩個月份量的Binary Log都沒Purge掉, 這真是太神奇了傑克! 所以就確認他們備份的時間, 把那個時間後的Binary Log用mybinlog工具倒出來, 再一股腦的倒回去, 系統也就順利的回到掛掉的瞬間.
這個故事要說的是, 當你覺得你很熟悉一個系統或工具或語言時, 可能還遠遠不夠, 而決定你是「物有所值」或是「物超所值」的關鍵是專業度.
韓國笑片我的老婆是老大(My Wife Is a Gangster)裡頭有個爆笑對話但是正好切題.
流氓A: 你知道小混混跟流氓的差別是什麼?
流氓B: ….(疑惑)
流氓A: 笨蛋, 是專業度!
剛剛那個案例裡, 那家公司並非草包, 技術人員們也各各身懷絕技, 但是他們敗在以為自己太熟悉系統而忘記永遠要知其所以然, 所以縱然有這麼好的身手跟技術, 卻不知道其實早就有Binary Log及解開Binary Log的工具可以幫助我們做Data Restore. 如果他們能相信自己不夠專業, 而認真的閱讀手冊, 看mailing list, 而不是自滿於管理過20k AQPS的大型(其實這種規模大概只能算中型)DBMS而覺得自己已經夠專業, 那麼他們不但可以更快更安穩的解決他們碰到的問題, 更可以提高自己在老闆眼中的身價.
我們也常常看到履歷表中, 求職者洋洋灑灑寫了一堆自己會的東西, 甚至會有人寫精通, 但是我卻總是很迷惑於「究竟是多精通?」, 每次Interview後又總是失望, 可能只是下載過幾個Linux Distro然後照著鳥哥的文件一步一步弄了一些常用的service, 就寫上精通. 或者是只是唸書上計概學過一些VB寫過「Mini-Calc」程式作業然後就寫上精通. 這又是另外一種不夠專業的典型.
身為一個技術人員, 專業是必須的, 一樣當你覺得自己夠專業了, 其實就斷絕了自己邁向專業的可能.
喔對了, 當流氓也是一樣的! XD
投資現在, 還是投資未來?
先說一個真實故事, 這個故事我甚至不用解釋或強調真實性, 因為同樣的pattern在這個世界到處在發生.
某A公司是一個新創的IC Design House, 創辦人Peter是個在這方面學有專精又有想法的經營者, 他在辛苦經營了三年終於開始轉虧為盈, 第四年更進一步成長到50人規模的Design House, 同時也開始賺進了第一桶金: 純利1200萬.
員工們都很開心, 辛苦了這些年終於開花結果, 大家都在期待著手上擁有專利與穩定客戶的公司何時會開始增資, 何時開始準備上市上櫃, 當然還有, 這一年的年終獎金會是怎樣呢?
這時候Peter在尾牙上告訴大家, 這1200萬的純利, 他會拿出其中200萬當作額外的獎金, 當台下員工議論紛紛的同時, 他接著說, 他要將其於的1000萬投資在公司的開發, 測試, 及模擬系統上.
當時整間公司嘩然, 一個僅50人, 規模只能堪稱剛起步的公司, 不但短時間內用不到那上億的專業系統, 更不用說一千萬幾乎什麼都買不到. 所以私下討論是不是Peter要中飽私囊, 甚至開始有同事正在觀望年後是不是要跳槽到其他公司去.
第五年, Peter並沒有買進大批系統需要的設備軟硬體, 也沒有在財務報表上提示出這一千萬作為股東配息, 反而是把這一千萬直接現金增資, 並且成立了一個直屬於他的系統團隊. 他高薪挖來了一個曾在德國某S公司擔任過CIO的老外, 並且讓這個老外組織了一個平均年齡僅25歲的Programmer團隊. 這一年公司的業績大幅成長, 原先編制也上漲到接近100人, 而第五年底的財務狀況是純利2800萬 — 不算這個系統團隊的話, 算進這個系統團隊的成本之後, 只獲利了1500萬, 而這一年這個系統團隊沒有任何產出, 所以開始有些人離職了. 而Peter也同時把1500萬的純利抽出1200萬加碼投資這個系統團隊.
第六年時因為流失了一些關鍵人才, 所以人數及營收都沒有大幅成長.
第七年發生了一件事, 國外某H大廠為了他們標下了美國國防部的標案, 下了一筆極大量, 對產品品質及彈性都十分要求的大單給A公司的對手R公司, 而R公司不但跟A公司幾乎同時間成立, 產品線跟客戶群也和A公司相近, 最大的不同是R公司在第五年才損益平衡, 但是第七年時已經是比A公司大兩倍, 準備上櫃的明星公司.
悲劇來了, 由於R公司並沒有建立足夠專業及自動化的開發測試模擬系統上, 因此第一批出貨就慘遭退貨. 這時Peter趕到國外去見H大廠的高層, 然後帶著一疊文件回來.
他在關著房門跟那個神秘又燒錢的系統團隊密集開了一個禮拜的會之後, 員工們看見一車一車的硬體設備被送進公司, 系統團隊的同事們忙近忙出. 兩週後, Peter招開了一個員工大會.
他在員工大會上花了2個小時跟所有工程師解釋系統團隊三年來的成就 — 一套嶄新, 符合他們工作流程, 自行開發的模擬系統, 並且要求所有工程師要在半個月內熟悉這套系統, 因為他們要承接H大廠的超級大單了.
後頭的故事就不用我說了, A公司已經是個數千人的上市公司, 而這位國外高薪聘請回來的CIO, 說服了董事會由A公司100%轉投資另外一家軟體開發公司, 他們的模擬軟體在兩年內拿下那個專有市場的30% marketing share. 業內同質公司業績越好, 越需要他的軟體, A公司就越賺. 同時A公司可以依靠比別家公司更省的成本, 得到更多的大單. 而R公司不但成為他們轉投資軟體公司的客戶, 過了幾年也被A公司併購了. 也就是說Peter用了三年共約四千萬的投入, 省下了接近六千萬的軟體, 而且這是在他剛看到黎明曙光的第四年下的決定 — 盡管他可能在當下都不能肯定他未來需不需要這個系統.
說這個故事, 其實重點是談幾件事情.
- 當所有人投資現在, 他們會是現在的贏家. 但能投資未來的人才會是未來的贏家.
- 在天還沒亮之前, 任何長遠的投資都像買樂透一般 — 因為你有極大的機率付不出second round的投資, 而你的first round, 就成為捨不得也沒辦法的沉默成本.
- 信任你的領導人, 尤其是他曾經帶領著大家打過勝仗
- 永遠不要忘記投資自己, 因為那將是最好的回收
其實額外還想藉著這個主題談一件好像不相關的事, 但是我卻想在這選前之夜如以往一樣的呼籲….
那就是, 去投票吧! 投票, 就是對國家未來最好的投資.
在成為SA之前….
前幾天, 一個案子上認識的客戶端Programmer透過msn問了我一個問題, 讓我想了很久, 思考之餘才生出了這篇文章.
記得我在某篇文章說過, 技術顧問不是一種身份, 而是一種狀態: 因為你在技術層面擁有了足夠的深度及廣度, 所以你成為一個可以當技術顧問的人. 事實上, SA(System Analyst)也是.
我常會跟同事或同行的朋友強調, 資訊科技中沒有什麼叫做定律, 這畢竟還是個非常年輕的領域, 一切都還在發展與進步中, 每一個曾被認為是圭臬的教條, 很可能很快的在新技術發生後, 變成不值一哂廢話. 這告訴我們, 不要被知識迷惑, 學習那些知識跟技能最重要的知道為什麼, 而不是把怎麼做變成SOP般的教條.

在Wikipedia裡頭有關於System Analyst的敘述, 裡頭列出了如下的幾個SA的工作內容:
- Plan a system flow from the ground up.
- Interact with customers to learn and document requirements that are then used to produce business requirements documents.
- Write technical requirements from a critical phase.
- Interact with designers to understand software limitations.
- Help programmers during system development, ex: provide use cases, flowcharts or even Database design.
- Perform system testing.
- Deploy the completed system.
- Document requirements or contribute to user manuals.
- Whenever a development process is conducted, the system analyst is responsible for designing components and providing that information to the developer.
喔, 有夠複雜的, 簡單的說, 一個SA作的就是, 從需求到藍圖之間, 從藍圖到專案完成之間, 幾乎所有的工作.
在很多大型的專案會拆的更細, 例如會有SD(System Designer)負責系統架構的設計, 會有UD(UI Designer)負責統整設計符合需求流程又同時易於使用的UI, 有DBA(Database Administrator)負責去規劃後端資料庫schema….等等. 但是其實這些都只是把SA從一個人變成一個小組, 實際在主導這些工作的, 還是一個Major/Chief SA在負責.
所以, 要成為一個SA, 門檻相當相當的高, 除了要熟悉「各種」系統, 更要熟悉「各種」語言, 熟悉「各種」資料庫, 甚至要會使用者行為分析, UI設計原則等等. 這還只是基本要求, 更深入的說SA還要具備良好且有效率的溝通能力, 技術判斷力(例如判斷什麼需求必須被擱置或拋棄, 什麼沒提出的需求必須被隱性的加入規格中), 管理能力….etc.
很多Programmer都把自己的下一步定義在SA, 但是往往缺乏對SA這個身份的認知. 某D是我遇過的Web Game Programmer, 他的目標是成為SA, 但是當我鼓勵他多接觸系統面的東西時, 他告訴我他只想寫Game, 其他部份他認為沒必要學; 某J是Java Programmer, 他的目標也是成為SA, 但是當我鼓勵他多接觸其他語言, 多理解一些各語言的特性與優缺點時, 他告訴我他覺得Java最好, Java是萬能的, 不須要學其它語言.
某D還在繼續寫Web Game, 用PHP寫了五年的Web Service, 但是他依然不懂REST是什麼; 某J換了幾家公司, 還在持續寫Java, 卻總搞不清楚他的程式為什麼效能總是不好. 但是他們的共通點是, 永遠都在抱怨主管不賞識, 不加薪, 不給機會讓他表現.
我的職涯之中, 做過各種身份, 在這個業界十多年的經驗與累積, 直到目前為止我都還認為我自己距離一個60分的SA至少還差一大截需要努力, 我實在想問問這些死守在一個領域, 單一平台, 單一語言的年輕後輩們, 你憑什麼覺得自己可以是個SA?
好吧, 那SA這種高度要求的艱難身份, 究竟該如何達到?
我能給予的建議是, 忘記自己熱愛的語言–去學更多語言, 忘記自己習慣的系統–去用更多種系統, 忘記自己對專業的堅持–去跟User做更深入的溝通, 持續的做, 然後當你不再為了怎麼成為一個SA而迷惑時, 你就可以是個SA了.
回到最開始的問題, 他問的是:「我想去IBM應徵SA, 你覺得有機會嗎?」
偉哉, 他只寫了三年ASP, 連個Paper Engineer必備的MCSE課程都沒上過啊….
MySQL Cluster Installation Guide
2012/01/10更新: Startup Scripts在最底下
照規矩, 這是一個 Run Down, 有問題請看文件.
MySQL Cluster是一個HA架構(High Availability)的MySQL服務, 當單一MySQL不夠用, 換成 Master-Slave Replication 也不夠用, 換到MySQL Proxy 時也出現了單一Master的寫入瓶頸時, MySQL Cluster 是不用變更目前程式架構下, 最後一個Solution.
MySQL Cluster有三種身份: Management Node, Data Node, SQL Node三種, Management Node是Cluster的judgement role, 負責控制各個Node之間的狀態, 在某個Node出現問題時自動切換, 或是重要的Node出狀況時會停掉服務避免資料遺失; Data Node 是實際存資料的Node, 原始結構依然是類似Master-Slave的Replication關係; SQL Node是提供標準mysql protocol 的前端frontend, 拿來提供服務用的.
如果只是為了提供很容易橫向擴充的結構, 那在資料保全的前提下, 基本消費是四台: Management Node一台, Data Node兩台, SQL Node一台. 如果是為了達到完整的HA, 也就是避免任何一種OPF(One Point Failure), 那需要的基本消費就五台:Management Node一台, Data Node兩台, SQL Node兩台, 配合OS本身做的NIC bond, 可以做到軟硬體各層的OPF-Free HA. 配合L4 Switch或是Haproxy一類的L4 Base LBS, 跟TCP Healthy Check, 可以做到無限量橫向擴充. 大致上的架構如下圖.

以下是簡單的Run Down, 範例是使用CentOS 6.0 x64, 共需要五台, 以下是範例用的IP配置:
- Management Node (ndb_mgmd): 192.168.0.1 [mgmt]
- Data Node (ndbd Master): 192.168.0.2 [master]
- Data Node (ndbd Slave): 192.168.0.3 [slave]
- SQL Node (mysqld A): 192.168.0.4 [front-a]
- SQL Node (mysqld B): 192.168.0.5 [front-b]
以下是五台共通的部份.
- 安裝CentOS 6.0, 只裝Minimum的Base即可.
- 安裝完成後, 關閉 ip6tables, iptables, postfix
- # /sbin/service ip6tables off
- # /sbin/service iptables off
- # /sbin/service postfix off
同時編輯 /etc/selinux/config 把 selinux關掉, 把enforcing 改成 disabled
- # This file controls the state of SELinux on the system.
- # SELINUX= can take one of these three values:
- # enforcing - SELinux security policy is enforced.
- # permissive - SELinux prints warnings instead of enforcing.
- # disabled - No SELinux policy is loaded.
- SELINUX=disabled
- # SELINUXTYPE= can take one of these two values:
- # targeted - Targeted processes are protected,
- # mls - Multi Level Security protection.
- SELINUXTYPE=targeted
關掉iptables跟selinux的原因是避免安裝與設定時碰到麻煩事, 請記得在裝好之後, 確認一切沒問題了, 重新打開並且設定好 firewall ruleset
-
把 postfix 跟 mysql-libs 移除, 因為mysql-libs跟MySQL Cluster衝突, 而postfix又得dependence mysql-libs.
- # yum -y erase postfix mysql-libs
接著做一次 yum update 升級一下, 然後reboot讓剛剛修改selinux的部份跟關掉service的部份生效
- # yum -y update
- # reboot
接下來是各台不同的部份. 先從Mgmt Node開始
- 去抓取以下兩個檔案, 請依照你裝的CentOS 版本抓取適合的 x86_64 或 i386 版本.
- MySQL-Cluster-gpl-management-7.2.0_devmilestone-1.rhel5 [x86_64] 或 [i386]
- MySQL-Cluster-gpl-tools-7.2.0_devmilestone-1.rhel5 [x86_64] 或 [i386]
抓好之後, 用以下指令安裝
- # rpm -Uhv MySQL-Cluster-gpl-management-7.2.0_devmilestone-1.rhel5.x86_64.rpm
- # rpm -Uhv MySQL-Cluster-gpl-tools-7.2.0_devmilestone-1.rhel5.x86_64.rpm
然後是 Data Node
- 抓取以下檔案
MySQL-Cluster-gpl-storage-7.2.0_devmilestone-1.rhel5 [x86_64] 或 [i386]
然後安裝- # rpm -Uhv MySQL-Cluster-7.2/MySQL-Cluster-gpl-storage-7.2.0_devmilestone-1.rhel5.x86_64.rpm
最後是 SQL Node
- 抓取以下檔案
- MySQL-Cluster-gpl-server-7.2.0_devmilestone-1.rhel5 [x86_64] 或 [i386]
- MySQL-Cluster-gpl-client-7.2.0_devmilestone-1.rhel5 [x86_64] 或 [i386]
然後安裝
- # rpm -Uhv MySQL-Cluster-gpl-server-7.2.0_devmilestone-1.rhel5.x86_64.rpm
- # rpm -Uhv MySQL-Cluster-gpl-client-7.2.0_devmilestone-1.rhel5.x86_64.rpm
裝完之後, 開始設定與啟動.
- 先到 Management Node 上建立設定檔目錄跟設定檔
- # mkdir -p /var/lib/mysql-cluster
- # vi /var/lib/mysql-cluster/config.ini
其中內容如下
- [ndbd default]
- NoOfReplicas=2
- DataMemory=80M
- IndexMemory=18M
- [tcp default]
- portnumber=2202
- [ndb_mgmd]
- hostname=192.168.0.1
- datadir=/var/lib/mysql-cluster
- [ndbd]
- hostname=192.168.0.2
- datadir=/var/lib/mysql-cluster
- [ndbd]
- hostname=192.168.0.3
- datadir=/var/lib/mysql-cluster
- [mysqld]
- hostname=192.168.0.4
- [mysqld]
- hostname=192.168.0.5
- 接著到Data Node跟SQL Node上建立 /etc/my.cnf
- # vi /etc/my.cnf
內容如下
- [mysqld]
- ndbcluster
- ndb-connectstring=192.168.0.1
- [ndbd]
- connect-string=192.168.0.1
- [ndb_mgm]
- connect-string=192.168.0.1
- [ndb_mgmd]
- config-file=/var/lib/mysql-cluster/config.ini
- 都設定好之後, 先到 Management Node上啟動ndb_mgmd
- # ndb_mgmd -f /var/lib/mysql-cluster/config.ini --initial
- MySQL Cluster Management Server mysql-5.1.51 ndb-7.2.0-beta
- 2011-07-25 23:20:02 [MgmtSrvr] INFO -- The default config directory '/usr/mysql-cluster' does not exist. Trying to create it...
- 2011-07-25 23:20:02 [MgmtSrvr] INFO -- Sucessfully created config directory
- 接著到Data Node啟動ndbd (兩台都要)
- # ndbd
- 2011-07-25 23:21:13 [ndbd] INFO -- Angel connected to '192.168.0.2:1186'
- 2011-07-25 23:21:13 [ndbd] INFO -- Angel allocated nodeid: 3
- 最後啟動SQL Node (一樣兩台都要)
- # /sbin/service mysql start
- Starting MySQL. SUCCESS!
- 回到 Management Node 上, 用以下指令檢查狀況
- # ndb_mgm -e show
- Connected to Management Server at: localhost:1186
- Cluster Configuration
- ---------------------
- [ndbd(NDB)] 2 node(s)
- id=2 @192.168.0.2 (mysql-5.1.51 ndb-7.2.0, Nodegroup: 0, Master)
- id=3 @192.168.0.3 (mysql-5.1.51 ndb-7.2.0, Nodegroup: 0)
- [ndb_mgmd(MGM)] 1 node(s)
- id=1 @192.168.0.1 (mysql-5.1.51 ndb-7.2.0)
- [mysqld(API)] 2 node(s)
- id=4 @192.168.0.4 (mysql-5.1.51 ndb-7.2.0)
- id=5 @192.168.0.5 (mysql-5.1.51 ndb-7.2.0)
這樣就表示成功了.
- 接著就可以到SQL Node上用MySQL Client Command Line去連線並且開始執行SQL指令
- # mysql -u root
- Welcome to the MySQL monitor. Commands end with ; or \g.
- Your MySQL connection id is 3
- Server version: 5.1.51-ndb-7.2.0-devmilestone-cluster-gpl MySQL Cluster Server (GPL)
- Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
- This software comes with ABSOLUTELY NO WARRANTY. This is free software,
- and you are welcome to modify and redistribute it under the GPL v2 license
- Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
- mysql>
看到上頭版本是 5.1.51-ndb-7.2.0-devmilestone-cluster-gpl MySQL Cluster Server (GPL)就大功告成啦, 可以開始試著執行指令並看看有沒有自動被同步到另外一台SQL Node上.
- 要關閉MySQL Cluster時請反過來執行, 先到兩台SQL Node上關掉mysqld
- # /sbin/service mysql stop
再到Management Node關閉
- # ndb_mgm -e shutdown
這個指令會同時關掉 Data Node 的 ndbd 及Management Node上的ndb_mgmd
以上就是簡單的Run Down, 如果要架設前端的Layer 4 Switch, 選用TCP Ping當作Healthy Check Algorithm即可.
大功告成, 有問題請洽Google大神.
2012/01/10更新
ndb_mgmd 的 startup scripts內容如下
- #!/bin/sh
- # Source function library.
- . /etc/rc.d/init.d/functions
- start()
- {
- echo -n $"Starting ndb_mgmd: "
- daemon /usr/sbin/ndb_mgmd -f /var/lib/mysql-cluster/config.ini
- touch /var/lock/subsys/ndb_mgmd
- echo
- }
- stop()
- {
- echo -n $"Shutting down ndb_mgmd: "
- killproc ndb_mgmd
- rm -f /var/lock/subsys/ndb_mgmd
- echo
- }
- [ -f /usr/sbin/ndb_mgmd ] || exit 0
- # See how we were called.
- case "$1" in
- start)
- start
- ;;
- stop)
- stop
- ;;
- restart|reload)
- stop
- start
- ;;
- condrestart)
- [ -e /var/lock/subsys/ndb_mgmd ] && (stop; start)
- ;;
- *)
- echo $"Usage: $0 {start|stop|restart|reload|condrestart}"
- exit 1
- esac
- exit 0
新建一個 /etc/init.d/ndb_mgmd之後
- # chmod a+X /etc/init.d/ndb_mgmd
- # /sbin/chkconfig --add ndb_mgmd
- # /sbin/chkconfig ndb_mgmd on
- # /sbin/service ndb_mgmd start
ndbd (Data Store Node) 的Startup Scripts如下
- #!/bin/sh
- # Source function library.
- . /etc/rc.d/init.d/functions
- start()
- {
- echo -n $"Starting ndbd: "
- daemon /usr/sbin/ndbd
- touch /var/lock/subsys/ndbd
- echo
- }
- stop()
- {
- echo -n $"Shutting down ndbd: "
- killproc ndbd
- rm -f /var/lock/subsys/ndbd
- echo
- }
- [ -f /usr/sbin/ndbd ] || exit 0
- # See how we were called.
- case "$1" in
- start)
- start
- ;;
- stop)
- stop
- ;;
- restart|reload)
- stop
- start
- ;;
- condrestart)
- [ -e /var/lock/subsys/ndbd ] && (stop; start)
- ;;
- *)
- echo $"Usage: $0 {start|stop|restart|reload|condrestart}"
- exit 1
- esac
- exit 0
一樣新建在 /etc/init.d/ndbd
- # chmod a+x /etc/init.d/ndbd
- # /sbin/chkconfig --add ndbd
- # /sbin/chkconfig ndbd on
- # /sbin/service ndbd start
MySQL Proxy for CentOS Configuration Guide
Update on 2011/07/17
如果碰到
- 2011-07-17 02:56:25: (critical) proxy-plugin.c:263: read_query_result() in /etc/mysql-proxy/proxy.lua tries to modify the resultset, but hasn't asked to buffer it in proxy.query:append(..., { resultset_is_needed = true }). We ignore the change to the result-set.
這樣的問題的話, 修改 /etc/mysql-proxy/proxy.lua 的第 201 行, 把
- proxy.queries:append(1, packet)
改成
- proxy.queries:append(1, packet, {resultset_is_needed = true})
就可以了
一樣是個Run down, 中間碰到問題請找Google大神
基本準備三台機器, 兩台互為MySQL Server Master/Slave, 請參考MySQL Replication (Master-Slave) for CentOS Installation Guide(後文各稱為Master/Slave), 再參考MySQL-Proxy for CentOS 5 Installation Guide安裝好一台MySQL-Proxy(後文稱為Proxy), 然後進入設定程序.
- 首先相關目錄
- [Proxy] # mkdir /etc/mysql-proxy/
後續的lua檔案會放至於此
- 接著設定成服務, 建立一個檔案/etc/init.d/mysql-proxy, 內容如下
- #!/bin/sh
- #
- # mysql-proxy This script starts and stops the mysql-proxy daemon
- #
- # chkconfig: - 78 30
- # processname: mysql-proxy
- # description: mysql-proxy is a proxy daemon to mysql
- # Source function library.
- . /etc/rc.d/init.d/functions
- PROXY_PATH=/usr/local/bin
- prog="mysql-proxy"
- # Source networking configuration.
- . /etc/sysconfig/network
- # Check that networking is up.
- [ ${NETWORKING} = "no" ] && exit 0
- # Source mysql-proxy configuration.
- if [ -f /etc/sysconfig/mysql-proxy ] ; then
- . /etc/sysconfig/mysql-proxy
- fi
- # Set default mysql-proxy configuration.
- PROXY_PID=/var/run/mysql-proxy.pid
- PATH=$PATH:/usr/bin:/usr/local/bin:$PROXY_PATH
- # By default it's all good
- RETVAL=0
- # See how we were called.
- case "$1" in
- start)
- # Start daemon.
- echo -n $"Starting $prog: "
- daemon $NICELEVEL $PROXY_PATH/mysql-proxy $PROXY_OPTIONS $ADMIN_PLUGIN_OPTIONS $PROXY_PLUGIN_OPTIONS
- RETVAL=$?
- echo
- if [ $RETVAL = 0 ]; then
- touch /var/lock/subsys/mysql-proxy
- fi
- ;;
- stop)
- # Stop daemons.
- echo -n $"Stopping $prog: "
- killproc $prog
- RETVAL=$?
- echo
- if [ $RETVAL = 0 ]; then
- rm -f /var/lock/subsys/mysql-proxy
- rm -f $PROXY_PID
- fi
- ;;
- restart)
- $0 stop
- sleep 3
- $0 start
- ;;
- condrestart)
- [ -e /var/lock/subsys/mysql-proxy ] && $0 restart
- ;;
- status)
- status mysql-proxy
- RETVAL=$?
- ;;
- *)
- echo "Usage: $0 {start|stop|restart|status|condrestart}"
- RETVAL=1
- ;;
- esac
- exit $RETVAL
並且設定成可執行
- [Proxy] # chmod a+x /etc/init.d/mysql-proxy
- 接著在/etc/sysconfig下建立服務啟動設定檔 /etc/sysconfig/mysql-proxy, 內容如下
- # Options to mysql-proxy
- # do not remove --daemon
- LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH"
- LUA_PATH="/usr/local/lib/mysql-proxy/lua/?.lua;$LUA_PATH"
- LUA_CPATH="/usr/local/lib/mysql-proxy/lua/?.so;$LUA_CPATH"
- export LD_LIBRARY_PATH LUA_PATH LUA_CPATH
- PROXY_OPTIONS="--daemon"
- ADMIN_PLUGIN_OPTIONS="--admin-address=PROXY_IP:4401 --admin-username=admin --admin-password=admin_password --admin-lua-script=/etc/mysql-proxy/admin.lua"
- PROXY_PLUGIN_OPTIONS="--proxy-address=PROXY_IP:3306 --proxy-read-only-backend-addresses=SLAVE_IP:3306 --proxy-backend-addresses=MASTER_IP:3306 --proxy-lua-script=/etc/mysql-proxy/proxy.lua"
請把其中的 PROXY_IP, MASTER_IP, SLAVE_IP 改成各自的ip address, 並且變更admin使用者名稱及密碼為你想要的.
- 建立成系統服務
- [Proxy] # /sbin/chkconfig --add mysql-proxy
- [Proxy] # /sbin/chkconfig mysql-proxy on
- 到MASTER及SLAVE下建立一個用來連線使用的帳號密碼, 請注意為了Replication的完整性, 請考慮在SLAVE上將該帳號的權限鎖死為僅SELECT.
- 設定/etc/mysql-proxy/admin.lua, 內容如下
- --[[ $%BEGINLICENSE%$
- Copyright (c) 2008, 2009, Oracle and/or its affiliates. All rights reserved.
- This program is free software; you can redistribute it and/or
- modify it under the terms of the GNU General Public License as
- published by the Free Software Foundation; version 2 of the
- License.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- 02110-1301 USA
- $%ENDLICENSE%$ --]]
- function set_error(errmsg)
- proxy.response = {
- type = proxy.MYSQLD_PACKET_ERR,
- errmsg = errmsg or "error"
- }
- end
- function read_query(packet)
- if packet:byte() ~= proxy.COM_QUERY then
- set_error("[admin] we only handle text-based queries (COM_QUERY)")
- return proxy.PROXY_SEND_RESULT
- end
- local query = packet:sub(2)
- local rows = { }
- local fields = { }
- if query:lower() == "select * from backends" then
- fields = {
- { name = "backend_ndx",
- type = proxy.MYSQL_TYPE_LONG },
- { name = "address",
- type = proxy.MYSQL_TYPE_STRING },
- { name = "state",
- type = proxy.MYSQL_TYPE_STRING },
- { name = "type",
- type = proxy.MYSQL_TYPE_STRING },
- { name = "uuid",
- type = proxy.MYSQL_TYPE_STRING },
- { name = "connected_clients",
- type = proxy.MYSQL_TYPE_LONG },
- }
- for i = 1, #proxy.global.backends do
- local states = {
- "unknown",
- "up",
- "down"
- }
- local types = {
- "unknown",
- "rw",
- "ro"
- }
- local b = proxy.global.backends[i]
- rows[#rows + 1] = {
- i,
- b.dst.name, -- configured backend address
- states[b.state + 1], -- the C-id is pushed down starting at 0
- types[b.type + 1], -- the C-id is pushed down starting at 0
- b.uuid, -- the MySQL Server's UUID if it is managed
- b.connected_clients -- currently connected clients
- }
- end
- elseif query:lower() == "select * from help" then
- fields = {
- { name = "command",
- type = proxy.MYSQL_TYPE_STRING },
- { name = "description",
- type = proxy.MYSQL_TYPE_STRING },
- }
- rows[#rows + 1] = { "SELECT * FROM help", "shows this help" }
- rows[#rows + 1] = { "SELECT * FROM backends", "lists the backends and their state" }
- else
- set_error("use 'SELECT * FROM help' to see the supported commands")
- return proxy.PROXY_SEND_RESULT
- end
- proxy.response = {
- type = proxy.MYSQLD_PACKET_OK,
- resultset = {
- fields = fields,
- rows = rows
- }
- }
- return proxy.PROXY_SEND_RESULT
- end
這個檔案可以在/usr/local/lib/mysql-proxy/lua找到, 複製過去也行, 或者可以自己寫個lua script, 這篇不是LUA語言教學, 就略過不提.
- 設定/etc/mysql-proxy/proxy.lua, 內容如下
- --[[ $%BEGINLICENSE%$
- Copyright (C) 2007-2008 MySQL AB, 2008 Sun Microsystems, Inc
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; version 2 of the License.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- $%ENDLICENSE%$ --]]
- ---
- -- a flexible statement based load balancer with connection pooling
- --
- -- * build a connection pool of min_idle_connections for each backend and maintain
- -- its size
- -- *
- --
- --
- local commands = require("proxy.commands")
- local tokenizer = require("proxy.tokenizer")
- local lb = require("proxy.balance")
- local auto_config = require("proxy.auto-config")
- --- config
- --
- -- connection pool
- if not proxy.global.config.rwsplit then
- proxy.global.config.rwsplit = {
- min_idle_connections = 4,
- max_idle_connections = 8,
- is_debug = false
- }
- end
- ---
- -- read/write splitting sends all non-transactional SELECTs to the slaves
- --
- -- is_in_transaction tracks the state of the transactions
- local is_in_transaction = false
- -- if this was a SELECT SQL_CALC_FOUND_ROWS ... stay on the same connections
- local is_in_select_calc_found_rows = false
- ---
- -- get a connection to a backend
- --
- -- as long as we don't have enough connections in the pool, create new connections
- --
- function connect_server()
- local is_debug = proxy.global.config.rwsplit.is_debug
- -- make sure that we connect to each backend at least ones to
- -- keep the connections to the servers alive
- --
- -- on read_query we can switch the backends again to another backend
- if is_debug then
- print()
- print("[connect_server] " .. proxy.connection.client.address)
- end
- local rw_ndx = 0
- -- init all backends
- for i = 1, #proxy.global.backends do
- local s = proxy.global.backends[i]
- local pool = s.pool -- we don't have a username yet, try to find a connections which is idling
- local cur_idle = pool.users[""].cur_idle_connections
- pool.min_idle_connections = proxy.global.config.rwsplit.min_idle_connections
- pool.max_idle_connections = proxy.global.config.rwsplit.max_idle_connections
- if is_debug then
- print(" [".. i .."].connected_clients = " .. s.connected_clients)
- print(" [".. i .."].pool.cur_idle = " .. cur_idle)
- print(" [".. i .."].pool.max_idle = " .. pool.max_idle_connections)
- print(" [".. i .."].pool.min_idle = " .. pool.min_idle_connections)
- print(" [".. i .."].type = " .. s.type)
- print(" [".. i .."].state = " .. s.state)
- end
- -- prefer connections to the master
- if s.type == proxy.BACKEND_TYPE_RW and
- s.state ~= proxy.BACKEND_STATE_DOWN and
- cur_idle < pool.min_idle_connections then
- proxy.connection.backend_ndx = i
- break
- elseif s.type == proxy.BACKEND_TYPE_RO and
- s.state ~= proxy.BACKEND_STATE_DOWN and
- cur_idle < pool.min_idle_connections then
- proxy.connection.backend_ndx = i
- break
- elseif s.type == proxy.BACKEND_TYPE_RW and
- s.state ~= proxy.BACKEND_STATE_DOWN and
- rw_ndx == 0 then
- rw_ndx = i
- end
- end
- if proxy.connection.backend_ndx == 0 then
- if is_debug then
- print(" [" .. rw_ndx .. "] taking master as default")
- end
- proxy.connection.backend_ndx = rw_ndx
- end
- -- pick a random backend
- --
- -- we someone have to skip DOWN backends
- -- ok, did we got a backend ?
- if proxy.connection.server then
- if is_debug then
- print(" using pooled connection from: " .. proxy.connection.backend_ndx)
- end
- -- stay with it
- return proxy.PROXY_IGNORE_RESULT
- end
- if is_debug then
- print(" [" .. proxy.connection.backend_ndx .. "] idle-conns below min-idle")
- end
- -- open a new connection
- end
- ---
- -- put the successfully authed connection into the connection pool
- --
- -- @param auth the context information for the auth
- --
- -- auth.packet is the packet
- function read_auth_result( auth )
- if is_debug then
- print("[read_auth_result] " .. proxy.connection.client.address)
- end
- if auth.packet:byte() == proxy.MYSQLD_PACKET_OK then
- -- auth was fine, disconnect from the server
- proxy.connection.backend_ndx = 0
- elseif auth.packet:byte() == proxy.MYSQLD_PACKET_EOF then
- -- we received either a
- --
- -- * MYSQLD_PACKET_ERR and the auth failed or
- -- * MYSQLD_PACKET_EOF which means a OLD PASSWORD (4.0) was sent
- print("(read_auth_result) ... not ok yet");
- elseif auth.packet:byte() == proxy.MYSQLD_PACKET_ERR then
- -- auth failed
- end
- end
- ---
- -- read/write splitting
- function read_query( packet )
- local is_debug = proxy.global.config.rwsplit.is_debug
- local cmd = commands.parse(packet)
- local c = proxy.connection.client
- local r = auto_config.handle(cmd)
- if r then return r end
- local tokens
- local norm_query
- -- looks like we have to forward this statement to a backend
- if is_debug then
- print("[read_query] " .. proxy.connection.client.address)
- print(" current backend = " .. proxy.connection.backend_ndx)
- print(" client default db = " .. c.default_db)
- print(" client username = " .. c.username)
- if cmd.type == proxy.COM_QUERY then
- print(" query = " .. cmd.query)
- end
- end
- if cmd.type == proxy.COM_QUIT then
- -- don't send COM_QUIT to the backend. We manage the connection
- -- in all aspects.
- proxy.response = {
- type = proxy.MYSQLD_PACKET_OK,
- }
- if is_debug then
- print(" (QUIT) current backend = " .. proxy.connection.backend_ndx)
- end
- return proxy.PROXY_SEND_RESULT
- end
- proxy.queries:append(1, packet)
- -- read/write splitting
- --
- -- send all non-transactional SELECTs to a slave
- if not is_in_transaction and
- cmd.type == proxy.COM_QUERY then
- tokens = tokens or assert(tokenizer.tokenize(cmd.query))
- local stmt = tokenizer.first_stmt_token(tokens)
- if stmt.token_name == "TK_SQL_SELECT" then
- is_in_select_calc_found_rows = false
- local is_insert_id = false
- for i = 1, #tokens do
- local token = tokens[i]
- -- SQL_CALC_FOUND_ROWS + FOUND_ROWS() have to be executed
- -- on the same connection
- -- print("token: " .. token.token_name)
- -- print(" val: " .. token.text)
- if not is_in_select_calc_found_rows and token.token_name == "TK_SQL_SQL_CALC_FOUND_ROWS" then
- is_in_select_calc_found_rows = true
- elseif not is_insert_id and token.token_name == "TK_LITERAL" then
- local utext = token.text:upper()
- if utext == "LAST_INSERT_ID" or
- utext == "@@INSERT_ID" then
- is_insert_id = true
- end
- end
- -- we found the two special token, we can't find more
- if is_insert_id and is_in_select_calc_found_rows then
- break
- end
- end
- -- if we ask for the last-insert-id we have to ask it on the original
- -- connection
- if not is_insert_id then
- local backend_ndx = lb.idle_ro()
- if backend_ndx > 0 then
- proxy.connection.backend_ndx = backend_ndx
- end
- else
- print(" found a SELECT LAST_INSERT_ID(), staying on the same backend")
- end
- end
- end
- -- no backend selected yet, pick a master
- if proxy.connection.backend_ndx == 0 then
- -- we don't have a backend right now
- --
- -- let's pick a master as a good default
- --
- proxy.connection.backend_ndx = lb.idle_failsafe_rw()
- end
- -- by now we should have a backend
- --
- -- in case the master is down, we have to close the client connections
- -- otherwise we can go on
- if proxy.connection.backend_ndx == 0 then
- return proxy.PROXY_SEND_QUERY
- end
- local s = proxy.connection.server
- -- if client and server db don't match, adjust the server-side
- --
- -- skip it if we send a INIT_DB anyway
- if cmd.type ~= proxy.COM_INIT_DB and
- c.default_db and c.default_db ~= s.default_db then
- print(" server default db: " .. s.default_db)
- print(" client default db: " .. c.default_db)
- print(" syncronizing")
- proxy.queries:prepend(2, string.char(proxy.COM_INIT_DB) .. c.default_db)
- end
- -- send to master
- if is_debug then
- if proxy.connection.backend_ndx > 0 then
- local b = proxy.global.backends[proxy.connection.backend_ndx]
- print(" sending to backend : " .. b.address);
- print(" is_slave : " .. tostring(b.type == proxy.BACKEND_TYPE_RO));
- print(" server default db: " .. s.default_db)
- print(" server username : " .. s.username)
- end
- print(" in_trans : " .. tostring(is_in_transaction))
- print(" in_calc_found : " .. tostring(is_in_select_calc_found_rows))
- print(" COM_QUERY : " .. tostring(cmd.type == proxy.COM_QUERY))
- end
- return proxy.PROXY_SEND_QUERY
- end
- ---
- -- as long as we are in a transaction keep the connection
- -- otherwise release it so another client can use it
- function read_query_result( inj )
- local is_debug = proxy.global.config.rwsplit.is_debug
- local res = assert(inj.resultset)
- local flags = res.flags
- if inj.id ~= 1 then
- -- ignore the result of the USE <default_db>
- -- the DB might not exist on the backend, what do do ?
- --
- if inj.id == 2 then
- -- the injected INIT_DB failed as the slave doesn't have this DB
- -- or doesn't have permissions to read from it
- if res.query_status == proxy.MYSQLD_PACKET_ERR then
- proxy.queries:reset()
- proxy.response = {
- type = proxy.MYSQLD_PACKET_ERR,
- errmsg = "can't change DB ".. proxy.connection.client.default_db ..
- " to on slave " .. proxy.global.backends[proxy.connection.backend_ndx].address
- }
- return proxy.PROXY_SEND_RESULT
- end
- end
- return proxy.PROXY_IGNORE_RESULT
- end
- is_in_transaction = flags.in_trans
- local have_last_insert_id = (res.insert_id and (res.insert_id > 0))
- if not is_in_transaction and
- not is_in_select_calc_found_rows and
- not have_last_insert_id then
- -- release the backend
- proxy.connection.backend_ndx = 0
- elseif is_debug then
- print("(read_query_result) staying on the same backend")
- print(" in_trans : " .. tostring(is_in_transaction))
- print(" in_calc_found : " .. tostring(is_in_select_calc_found_rows))
- print(" have_insert_id : " .. tostring(have_last_insert_id))
- end
- end
- ---
- -- close the connections if we have enough connections in the pool
- --
- -- @return nil - close connection
- -- IGNORE_RESULT - store connection in the pool
- function disconnect_client()
- local is_debug = proxy.global.config.rwsplit.is_debug
- if is_debug then
- print("[disconnect_client] " .. proxy.connection.client.address)
- end
- -- make sure we are disconnection from the connection
- -- to move the connection into the pool
- proxy.connection.backend_ndx = 0
- end
這個檔案在MySQL-Proxy的repository找的到, 是標準的Read/Write分離, 請自行Google
- 接著啟動MySQL-Proxy
- [Proxy] # /sbin/service mysql-proxy start
連接上Admin Console
- [Proxy] # mysql -u admin -padmin_password -h PROXY_IP -P 4401
- Welcome to the MySQL monitor. Commands end with ; or \g.
- Your MySQL connection id is 1
- Server version: 5.0.99-agent-admin
- Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
- This software comes with ABSOLUTELY NO WARRANTY. This is free software,
- and you are welcome to modify and redistribute it under the GPL v2 license
- Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
- mysql> SELECT * FROM backends;
- +-------------+--------------------+---------+------+------+-------------------+
- | backend_ndx | address | state | type | uuid | connected_clients |
- +-------------+--------------------+---------+------+------+-------------------+
- | 1 | MASTER_IP:3306 | unknown | rw | NULL | 0 |
- | 2 | SLAVE_IP:3306 | unknown | ro | NULL | 0 |
- +-------------+--------------------+---------+------+------+-------------------+
- 2 rows in set (0.00 sec)
或者是直接用設定在MASTER與SLAVE上相同的mysql使用者帳密透過proxy連接
- [Proxy] # mysql -u USERNAME -pPASWORD -h PROXY_IP -P 3306
- Welcome to the MySQL monitor. Commands end with ; or \g.
- Your MySQL connection id is 162
- Server version: 5.0.77-log Source distribution
- Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
- This software comes with ABSOLUTELY NO WARRANTY. This is free software,
- and you are welcome to modify and redistribute it under the GPL v2 license
- Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
- mysql>
這樣就可以正常使用了
一樣, 這只是個clean install/configure的run down, 碰到問題請詢問Google大人. 雖然那堆config很吃篇幅, 不過我相信有人用的到, 避免每個人Google到不同的東西, 所以寫個清楚.
MySQL Replication (Master-Slave) for CentOS Installation Guide
這個是承上一篇, 裝完MySQL-Proxy後, 後續怎麼從零建置起, 第一步要先把後端的MySQL Replication建立起來.
準備好兩台機器(目前稱為Master/Slave), 首先安裝好CentOS 5之後, 一樣先把Repositories都裝好, 也都update/upgrade好, 以下是兩台共通的部份.
- 安裝一套CentOS5, 我實驗的VM是使用CentOS 5.6, 只裝了基本的Base跟Develop Tools
- 先做一次yum -y update, 更新一些套件到最新
- 安裝RPMForge & EPEL Repositories
- 再做一次yum -y update, 更新一些套件到最新
- 安裝MySQL Server, 設定好services
- # yum -y install mysql-server
- # /sbin/chkconfig mysqld on
- # /sbin/service mysqld start
- 設定iptables, Master/Slave互相開放對方的TCP 3306 port, 其中eth0改成自己的網卡代號.
- # /sbin/iptables -A INPUT -i eth0 -p tcp -m tcp --dport 3306 -j ACCEPT
接著是兩台不同的部份, 請注意前面的Hostname代表是在哪一台上
- 到Master上先設定my.cnf, 這個是可以選擇不同設定檔, 在/usr/share/mysql 下有很多個, 這裡挑選medium的, 細部設定請參考mysql網站上的文件, 這裡直接用medium的標準設定.
- [Master] # /sbin/services mysqld stop
- [Master] # cp /usr/share/mysql/my-medium.cnf /etc/my.cnf
- 設定 /etc/my.cnf, 務必打開log-bin
- server-id = 1
- log-bin = mysql-bin
其中server-id必須各自不同, 用於Replication關係中識別各Server.
- 設定Replication使用的連線帳號
- [Master] # /sbin/service mysqld start
- [Master] # mysql -u root
- mysql> GRANT REPLICATION SLAVE ON *.* TO repl@SLAVE-IP IDENTIFIED BY 'repl_password';
- mysql> FLUSH PRIVILEGES;
其中 repl 是Slave用來連接Master使用的帳號, SLAVE-IP是Slave的ip address, 增加一個slave時必須重加, repl_password則是Slave連接Master時的密碼, 請更換成自己的.
- 接著把Master的資料都Lock, 避免任何新資料寫入, 並且取得Master status
- mysql> FLUSH TABLES WITH READ LOCK;
- mysql> SHOW MASTER STATUS;
- +------------------+----------+--------------+------------------+
- | File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
- +------------------+----------+--------------+------------------+
- | mysql-bin.000001 | 759 | | |
- +------------------+----------+--------------+------------------+
上頭的File跟Position就是等等Slave設定時使用的參數
- 到Slave上, 一樣先複製一份my.cnf
- [Slave] # /sbin/service mysqld stop
- [Slave] # cp /usr/share/mysql/my-medium.cnf /etc/my.cnf
- 設定my.cnf, 唯一的不同是server-id一定要跟Master不同, 而且不能跟其他Slave重複
- server-id = 2
- log-bin = mysql-bin
server-id務必與Master不同, 也不能與其他Slave(假如有的話)相同
- 啟動Slave並設定Master相關資訊
- [Slave] # /sbin/services mysqld start
- [Slave] # mysql -u root
- mysql> CHANGE MASTER TO
- -> MASTER_HOST=Master-IP,
- -> MASTER_USER='repl',
- -> MASTER_PASSWORD='repl_password',
- -> MASTER_LOG_FILE='mysql-bin.000001',
- -> MASTER_LOG_POS='759';
其中Master-IP則為Master的IP, repl是剛剛設定在Master上的帳號, MASTER_LOG_FILE則是剛剛在Master上SHOW Master Status出現的Binary Log檔名, MASTER_LOG_POS則是現在在Binary Log檔案中的位置, 請依照前面看到的填入.
- 在Slave上啟動Slave 機制
- mysql> Start Slave;
- Query OK, 0 rows affected, 0 warning (0.00 sec)
這樣就表示成功了, 也可以在Slave上SHOW SLAVE STATUS來看狀態
- mysql> SHOW SLAVE STATUS\G
- *************************** 1. row ***************************
- Slave_IO_State: Waiting for master to send event
- Master_Host: xxx.xxx.xxx.xxx
- Master_User: repl
- Master_Port: 3306
- Connect_Retry: 60
- Master_Log_File: mysql-bin.000001
- Read_Master_Log_Pos: 759
- Relay_Log_File: mysqld-relay-bin.000042
- Relay_Log_Pos: 235
- Relay_Master_Log_File: mysql-bin.000001
- Slave_IO_Running: Yes
- Slave_SQL_Running: Yes
- Replicate_Do_DB:
- Replicate_Ignore_DB:
- Replicate_Do_Table:
- Replicate_Ignore_Table:
- Replicate_Wild_Do_Table:
- Replicate_Wild_Ignore_Table:
- Last_Errno: 0
- Last_Error:
- Skip_Counter: 0
- Exec_Master_Log_Pos: 759
- Relay_Log_Space: 235
- Until_Condition: None
- Until_Log_File:
- Until_Log_Pos: 0
- Master_SSL_Allowed: No
- Master_SSL_CA_File:
- Master_SSL_CA_Path:
- Master_SSL_Cert:
- Master_SSL_Cipher:
- Master_SSL_Key:
- Seconds_Behind_Master: 0
- 1 row in set (0.00 sec)
主要要看到Slave_SQL_Running是Yes就大致ok了.
- 最後回到Master上, 把剛剛Lock住的解開
- [Master] # mysql -u root
- mysql> UNLOCK TABLES;
接下來就可以實驗新增Db/Tables/Rows看看有沒有被Replication到Slave去.
大致這樣就乾淨的把兩台機器之間的MySQL Replication設定好了, 但是還有一些注意事項
- 絕對不要在Slave上寫入或更新任何資料, 必要時甚至只開啟Select的權限
- 必須定期檢查Slave上的Replication狀態, 是否有錯誤
- 由於開啟Binary Log後, 忙碌的MySQL Server會不停的佔用空間, 也請在確認好Replication狀態運行中的時候, 定期PURGE binary log, 相關文件請參考 這裡
- 若Replication狀態損壞時, 需要重建關係, 在Master Lock之後, 把mysql data file複製一份至Slave後, 程序相同
這裡只有簡單的run down, 中間碰到任何問題請Google解決吧.
MySQL-Proxy for CentOS 5 Installation Guide
雖說CentOS 5在 EPEL Repository有mysql-proxy, 但是那是0.5.x 的版本, 要用比較新的0.8.x, 目前沒有看到, 所以花時間去試了一下.
- 安裝一套CentOS5, 我實驗的VM是使用CentOS 5.6, 只裝了基本的Base跟Develop Tools
- 先做一次yum -y update, 更新一些套件到最新
- 安裝RPMForge & EPEL Repositories
- 再做一次yum -y update, 更新一些套件到最新
- 安裝一些必須的套件:
- yum -y install install gcc gcc-c++ autoconf libjpeg libjpeg-devel libpng libpng-devel freetype freetype-devel libxml2 libxml2-devel zlib zlib-devel glibc glibc-devel glib2 glib2-devel bzip2 bzip2-devel ncurses ncurses-devel curl curl-devel e2fsprogs e2fsprogs-devel krb5 krb5-devel libidn libidn-devel openssl openssl-devel openldap openldap-devel nss_ldap openldap-clients openldap-servers flex libtiff-devel pam-devel pkgconfig readline-devel zlib-devel libevent-devel
- 去libevent網站抓libevent來裝, CentOS附的太舊
- # wget http://monkey.org/~provos/libevent-2.0.12-stable.tar.gz
- # tar -zxpvf libevent-2.0.12-stable.tar.gz
- # cd libevent-2.0.12-stable
- # ./configure --prefix=/usr/local
- # make all install
- 去LUA網站抓新的LUA來裝, CentOS附的太舊
- # wget http://www.lua.org/ftp/lua-5.1.4.tar.gz
- # tar -zxpvf lua-5.1.4.tar.gz
- # cd lua-5.1.4
- # vi src/Makefile
修改CFLAGS, 在後頭加入 -fPIC
- # make linux install
- # cp src/lua.pc /usr/local/lib/pkgconfig
- 去 這裡 自己複製一份magic.h, 放置到 /usr/include/linux/magic.h, 後頭裝glib2會用到
- 去 GTK+網站抓glib2回來裝, CentOS附的太舊
- # wget http://ftp.gnome.org/pub/gnome/sources/glib/2.28/glib-2.28.0.tar.bz2
- # bzip2 -d -c glib-2.28.0.tar.bz2 | tar -xpvf -
- # cd glib-2.28.0
- # ./configure --prefix=/usr/local
- # make all install
- 要Build mysql-proxy 之前先設定好一些環境變數, 否則會去用到CentOS內附的舊版本
- # export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
- # export LUA_CFLAGS="-I/usr/local/include" LUA_LIBS="-L/usr/local/lib -llua -ldl" LDFLAGS="-lm"
- # export GLIB_CFLAGS="-I/usr/local/include/glib-2.0 -I/usr/local/lib/glib-2.0/include"
- # export GLIB_LIBS="-L/usr/local/lib -lglib-2.0"
- # export GMODULE_CFLAGS="-I/usr/local/include/glib-2.0 -I/usr/local/lib/glib-2.0/include"
- # export GMODULE_LIBS="-L/usr/local/lib -lgmodule-2.0"
- 終於可以開始build mysql-proxy了, 去 MySQL Proxy網站抓Source, 這裡有Mirror.
- # wget http://mysql.cdpa.nsysu.edu.tw/Downloads/MySQL-Proxy/mysql-proxy-0.8.1.tar.gz
- # tar -zxpvf mysql-proxy-0.8.1.tar.gz
- # cd mysql-proxy-0.8.1
- # ./configure --prefix=/usr/local
- # make all install
- 這樣就完成了, 可以執行mysql-proxy -V 確認, 以下是我跑出來的結果
- # /usr/local/bin/mysql-proxy -V
- mysql-proxy 0.8.1
- chassis: mysql-proxy 0.8.1
- glib2: 2.28.0
- libevent: 2.0.12-stable
- LUA: Lua 5.1.4
- package.path: /usr/local/lib/mysql-proxy/lua/?.lua
- package.cpath: /usr/local/lib/mysql-proxy/lua/?.so
- -- modules
- admin: 0.8.1
- proxy: 0.8.1
至於裝好之後的設定, 不是這篇要講的, 這裡幫大家裝起來就好
[WINE] Château du Plantier 2008
警語:未成年請勿飲酒。
這隻是Costco手滑系列第二支, 素聞波爾多紅酒圓潤滑順, 特地買一支來試試看.
剛打開醒酒期間, 冒出了較為酸甜的葡萄香, 混著一點淺淺的玫瑰香氣. 色澤上屬於比較暗, 比較不透光的紫紅色, 給人渾厚的感覺.
倒進酒杯中靜置10分鐘醒酒期間, 特地拿起來在燈光下看, 發現白光燈下還有一點點琥珀色, 讓我對這支酒更為好奇.
第一口喝下有驚訝到, 不但跟我預期的圓潤滑順不同, 出乎意料的偏酸及偏澀, 但入喉後則是另外一種風光, 在舌根部位有淡香跟淡甜浮出, 澀味完全化為甘甜, 而酸味反而帶來一種清爽, 這讓我直覺認為應該適合重口味的餐點, 例如牛小排或肋排一類, 反而對海鮮跟紅肉魚來說就太強一些.
大口喝下在口中咕嚕咀嚼, 確有一絲絲比較刺激的香料感, 不曉得這是不是調和紅酒的特性, 喝的不夠多, 很難判斷. 所以我突然又覺得, 這支紅酒應該也適合拿來燉肉, 做個紅酒燴牛肉, 或是搭配水果做醬汁用在白肉上, 應該也會很搭.
不過話說回來, 這個程度的酸澀, 不適合單純喝, 不佐餐真的沒辦法, 我兩杯就受不了, 非得蓋上酒嘴收起來不可.
HTC Desire Stock ROM(原廠ROM) w/ App2SD+
Update 2011/01/12:
因為與 Brandon 協同處理他的Desire App2SD+, 發現我下頭寫的實在太精簡, 所以試著把過程Step by step起來, 應該可以幫助大家更快做好.
- 到「設定」=>「關於手機」=>「軟體資訊」中確認軟體號碼是2.10.751.4, 以及確認你的Micro-SD速度夠快, 根據爬各大論壇及自己實測的經驗, 最好是Class 6, 據說Class 10反而會更慢. 如果是Class 2我建議還是算了
- 下載android-sdk, 解開後放在C:\下
- 安裝HTC Sync, 會同時裝上HTC Sync跟 HTC Drivers, 裝完後移除HTC Sync, 就會保留住HTC Driver
- 關機, 按著「後退鍵」跟「電源鍵」開機, 會進入一個白色畫面, 那個叫做 fastboot mode. 用大小聲按鈕移動游標, 用電源鍵選擇到「Bootloader」,然後再選則「Recovery」, 這時手機會重開進Recovery Mode, 在Recovery裡頭用光學軌跡球選擇Backup & Restore, 做一次完整備份. 這一套備份是後頭其她步驟失敗時的重要還原手段, 請務必要做
- 請到 Unrevoked 依照指示Root掉你的Desire
- Root完成後, 正常重開機進系統, 到Market下載免費版本的Titanium Backup, 下載執行後會出現系統缺乏busybox的說明, 請依照指示讓Titanium Backup為你下載後重新開啟Titanium Backup, 做一次批次備份. 這個備份是用來在你需要用原廠RUU Restore時使用的. 這時也可以使用CallLogs Backup & Resore 及 SMS Backup & Restore 做通話紀錄及簡訊的備份.
- 備份完成後請掛上SD卡磁碟, 將SD卡內所有東西備份一份到電腦中, 後頭必須把SD卡重新分割, 這個動作會消除所有SD卡上的資料
- 請到 AlphaRev去抓iso檔, 然後在Virtual Box/VMWare之類的虛擬機器中掛起來或是用自己PC開機到裡頭的LiveCD, 照順序做就可以把HBOOT給S-Off, 也會把recovery mode刷成clockworkmod. (這個過程請格外小心, 這是唯一會讓Desire變成磚的步驟, 請確保過程中USB線不會鬆脫, 手機不會沒電, 電腦不會跳掉)
- S-Off 完成後, 開機會看到The Dark Knight的小丑醜臉跟Why so serious的畫面, 那表示S-Off成功了, 不喜歡這個畫面沒關係, 等全都完成後會附上修改回原始開機畫面的方法
- 下載底下這四樣東西, 放到 c:\android-sdk-windows\tools 中
busybox
init.d.tar.gz
boot-new.img
desire_stock_splash1.img - 接上USB線, 再次關機, 按照上頭的方法開回Recovery Mode, 這時候你的Recovery Mode應該是不同顏色了, 現在會是clockworkmod版, 用光學軌跡球移動到Mount & Storage並點進去, 選擇 mount /system, mount /data 這兩樣
- 在你的Windows開啟一個「命令提示字元」, 如果是Win7或Vista, 請記得使用「以系統管理員身份執行」較為保險. 然後執行
- cd C:\android-sdk-windows\tools
- adb shell
這時應該會看到一個 「~ #」的提示符號, 這表示已經進入了android的recovery系統中, 這時先輸入「exit」離開, 回到命令提示字元
- 在命令提示字元視窗中輸入以下指令
- adb push busybox /system/xbin/busybox
接著以adb shell 回到 「~ #」下, 輸入以下指令
- ~ # chmod 755 /system/xbin/busybox
- ~ # chown root.shell /system/xbin/busybox
這的動作是安裝最新版本的 busybox
- 在「~ #」下輸入以下指令
- ~ # /system/xbin/busybox fdisk /dev/block/mmcblk0
進入 fdisk 畫面中切割micro-sd卡, 這時你要開始考慮怎麼分割. 第一個分割區會是FAT32, 也就是你接上USB線選擇磁碟機模式時會使用的那個區塊, 而第二個分割區則是ext3, 準備用來放你的app. 第一個分割區多大都行, 第二個分割區正常不建議超過2G, 假設你的micro-sd是8G, 可以切成6.5G跟1.5G.
切割方法是在 fdisk prompt 下先用 n 建立第一個, 再用 t 更改分割區id, 其中第一個分割區的 id 是 c, 也就是 Win95 FAT32(LBA); 第二個分割區 id 是 83 的 Linux. 切割完成後請記得用 w 儲存並離開 - 在「~ #」下輸入以下指令格式化micro-sd卡, 並將ext2轉換成ext3
- ~ # /system/xbin/busybox mkfs.vfat /dev/block/mmcblk0p1
- ~ # /system/xbin/busybox mkfs.ext2 /dev/block/mmcblk0p2
- ~ # /sbin/tune2fs -j /dev/block/mmcblk0p2
- 在手機上的recovery mode中選擇 mount /sd-ext, 並選擇最底下的 mount USB storage, 這時電腦上會出現抽取式磁碟, 請把你剛剛備份的SD卡內容「複製」(請不要剪下啊)回去, 再把上頭下載的四個檔案「複製」(請不要剪下啊)到那個抽取式磁碟根目錄底下, 複製完成後在Windows中使用退出後, 在Recovery Mode下選擇Unmount回到上一層, 選擇 mount /sdcard 把剛剛還原過的FAT32 mount 起來
- 到剛剛的「命令提示字元」下的adb shell內, 執行以下指令
- ~ # cd /system/etc
- ~ # /system/xbin/busybox tar -xzpvf /sdcard/init.d.tar.gz
- ~ # /system/xbin/busybox chmod 755 /system/etc/init.d
- ~ # /system/xbin/busybox chown root.root /system/etc/init.d
- ~ # /system/xbin/busybox chmod 755 /system/etc/init.d/*
- ~ # /system/xbin/busybox chown root.shell /system/etc/init.d/*
上頭指令是把 init.d 的 script 放進去並設定好權限及owner
接著執行以下指令- ~ # cat /dev/zero > /dev/mtd/mtd2
- ~ # /sbin/flash_image boot /sdcard/boot-new.img
上頭指令是把 boot 區給洗掉, 換成支援 init.d 的 boot 區
- 在adb shell內, 執行以下指令
- ~ # cd /data
- ~ # mv app /sd-ext
- ~ # /system/xbin/busybox ln -s /sd-ext/app
以上動作是把 app 目錄整個搬移到 ext3 上, 並且在原本的 /data 上做一個symbolic link連結到新位置
- 以上都完成後, 在手機上選擇 unmount /system, unmount /data, unmount /sdcard, unmount /sd-ext 後, 用「後退鍵」回上一層並重開機, 一切順利開機的話, App2SD+就完成了, 如果沒有順利開機, 可以拔電池關機後, 用上頭方法回到Recovery Mode去, 選擇Advance Restore, 將boot.img給restore回去, 就回到剛S-Off完的狀態了
- 如果重新開機後桌面的widget出現「無法載入桌面小工具」的字樣, 請移除掉之後重新設定就正常了
- 如果不喜歡那個小丑, 請關機後按著「後退鍵」及「電源鍵」開進白底的fastboot模式, 這時不要進入bootloader或recovery mode, 在這個畫面下到剛剛的「命令提示字元」中下以下指令
- fastboot flash splash1 desire_stock_splash1.img
接著重開機, 這樣就把討厭的小丑給消掉了.
如果成功了請留個言讓大家知道你成功了, 給別人一點信心, 如果失敗了也請盡量發問, 我會就狀況盡量回答. 謝謝.
Update 2011/01/08:
從HTC Hero換到HTC Desire, 幾乎是速度三級跳, 用得很順暢的我終於也碰到內建記憶體不足的毛病了.
本想從精簡一些App開始, 但是麻煩的是聯絡人, Mail, Gmail, 簡訊等等一向不砍的我空間會越來越吃緊, 但是又不想刷掉現成原廠的 2.10.751.4, 畢竟整合的還不錯, 而且xda-developer也沒有這個版本的整合ROM, 所以決定自己做.
事前要準備的有
a. android-sdk, 主要是要需要adb.
b. HTC Sync, 主要是需要裡頭的Driver, 裝完移除HTC Sync本體, 就會留下Driver
c. Ubuntu, 可以裝在Virtual Box或VMWare裡頭, 主要是為了拆解boot.img.
d. 一張夠快的micro-sd卡, 建議要class 6以上.
e. 空白光碟片一張, 或是Virtual Box/VMWare, 做S-Off時會需要
做之前有幾件事要注意
- 由於有SLCD/AMOLED版, 據說SLCD版Root後不能Unroot, 請三思(不過我摸索的過程中發現其實是可以刷回去的, 只要有保留好原始的recovery.img, 替換掉S-Off那關用的img就可以把HBOOT/Recovery mode都刷回原廠)
- 如果要用我做好的boot.img, 請特別注意這個是專給 2.10.751.4 這個版本用的, 請不要抓錯
- 保持耐性, 有些過程會花一些時間, 不要亂拔除電池或是USB連接線, 尤其是在S-Off時, 那會把手機變成磚塊的
以下就是流程.
- 做一次nandriod備份
- Root原廠的ROM
- 安裝Titanium Backup且做一次完整的Full Backup
- 把HBOOT給S-Off
- 製作 init.d enable 的 boot.img
- 用sdparted割好microsd
- 把/data/app 轉移到 /sd-ext
- Reboot
備份永遠不會是錯的.
到 Unrevoked 去Root掉, 這個對後頭很重要. 在取root的過程中會在/sbin/底下裝上flash_image, 這在後頭改boot.img用的到
這是為了預防要是失敗必須還原原廠RUU時, 很可能會沒辦法從nandriod還原回去, 這時候Titanium Backup就很好用.
到 AlphaRev去抓iso檔, 然後看是在Virtual Box中掛起來開機到裡頭的LiveCD, 照順序做就可以把HBOOT給S-Off, 也會把recovery mode刷成clockworkmod.
參考這裡的方法, 把剛剛nandroid備份好的boot.img拿出來, 拆解出kernel跟ramdisk. 替換掉 init.rc 後包回去完整的boot.img. 最後在recovery mode底下用adb shell進去把boot給flash掉, 後面的手續可以參考這篇文章. 底下是我寫好的init.d可以下載.
如果懶的自己弄boot.img, 我底下連結的boot.img是我做好的版本, 但是請注意要配合自己的版本, 必須是 2.10.751.4.
參考這篇切好一個FAT32跟一個ext2, 其中FAT32就是當作原本的SD Storage, ext2就是放App的地方.
開進recovery mode, 用adb shell進去整個目錄搬到 /sd-ext 去, 記得用 ln 在原地做link.
順利的話就會有一份App2SD+的原廠ROM了.
製作過程中, 我嘗試過ext2/ext3, 畢竟stock rom只有支援到ext3而已. 實際用起來沒什麼速度上的差別, 但是由於如果沒有clean unmount, ext2很有可能掛不起來, 所以我還是選擇 ext3.
原本有試著想乾脆整個 /data 都轉移到/sd-ext 去, 但是stock rom的kernel並不支援 loopfs, 所以肯定是會失敗的.
有碰到問題的話盡管發問吧, 我知道的盡力回答.
