《互聯網創業的准備》系列文章——http://cnblogs.com/sink_cup/
硬盤常識:
| 機械硬盤HDD | 固態硬盤SSD | |
| 最小單位 | 1個扇區為512B,或4K(2012年民用普及) | 1個分頁為4K、8K或更高(與密度有關) |
| 性能因素 | 轉速(rpm):5400、7200、1w、1.5w | 層數:SLC(單層快)、MLC(雙層慢)、TLC(三層更慢,SSD暫未采用,U盤大量采用) |
| 接口 | SATA 3G、SATA 6G(2012年民用普及)、SAS 6G | SATA 3G、SATA 6G、SAS 6G、PCI-E |
| 尺寸 | 2.5英寸、3.5英寸 | 2.5英寸、PCI-E版型 |
| 常見品牌 | 西部數據、希捷 | Intel、鎂光(Crucial)、浦科特(PLEXTOR) |
硬盤性能指標:
連續讀寫(常用單位為MB/s):文件在硬盤上存儲位置是連續的,適用場景:大文件拷貝(比如視頻音樂)。速度即使很高,對數據庫性能也沒有參考價值。
4K隨機讀寫(常用單位為iops):在硬盤上隨機位置讀寫數據,每次4KB,適用場景:操作系統運行、軟件運行、數據庫。(圖片靜態服務器、視頻靜態服務器是大文件,測試64K隨機或更大)
常用硬盤性能測試軟件:
Windows:AS SSD Benchmark、CrystalDiskMark、HD Tune Pro、iometer
Linux:iometer



Align I/Os:硬盤IO大小。測試設備時根據硬盤最小單位進行選擇,機械硬盤上選512B或4K,SSD上選4K、8K等。測試分區時受分區sector size影響。由於Linux ext3的sector size為4096,所以在扇區為512B的機械硬盤上也無法選擇Align I/Os on 512B進行測試,測試效果不佳。vps無法進行設備測試,如果是自購服務器,應使用設備測試。
Seq 即 Sequential 即連續讀寫。AS SSD會先以16MB的尺寸為單位,持續向受測分區寫入生成1個達到1GB大小的文件,然后再以同樣的單位尺寸讀取這個,最后計算平均成績而給出結果。
4K 即 Random 4k, Queue Depth=1 即 隨機4K並發1個隊列。AS SSD會以512KB的單位尺寸生成1GB大小的測試文件,然后在其地址范圍(LBA)內進行隨機4KB單位尺寸進行寫入及讀取測試,直到跑遍這個范圍為止,最后同樣計算平均成績給出結果。
4K QD32 即 Random 4k, Queue Depth=32 即 隨機4K並發32個隊列。
4K-64Thrd 即 4K, 64 Thread 即 隨機4K並發64個線程,和 4K QD64是一個意思。AS SSD會生成64個16MB大小的測試文件(共計1GB),然后同時以4KB的單位尺寸,同時在這64個文件中進行寫入和讀取測試,最后依然以平均成績為結果。
通過AS SSD可以看出,iops與MB/s可以直接換算,比如4K讀取是6227iops,即每秒鍾可以讀取6227個4K的文件,即 6227 * 4K / 1024 = 24.3 MB/s。
Intel的SSD性能數據采用iometer 4K QD32的測試結果:http://www.intel.cn/content/www/cn/zh/solid-state-drives/solid-state-drives-520-series.html#footnotes
價格與速度:
| 型號 | 容量 | 2012價格 | 4K QD32隨機讀/寫(iops) | 4K QD64 | 連續讀寫(MB/s) | |
| 民用 機械7200rpm 3.5英寸 SATA 6G | 希捷Barracuda 7200.14 | 3TB | ¥1.1k | 409/365 | 386/291 | 200/180 |
| 企業級 機械10000rpm 2.5英寸 SAS 6G | 希捷Savvio 10K.5 | 300GB | ¥1k | 750/700 | 170/170 | |
| 企業級 機械15000rpm 2.5英寸 SAS 6G | 希捷Savvio 15K.3 | 300GB | ¥2.2k | |||
| 企業級 固態SLC 2.5英寸 SATA 3G | Intel X25-E | 32G | ¥2.5k | 3.5w/3.3k | 250/170 | |
| 企業級 固態MLC PCI-E | Intel 910 | 400G | ¥14w | 9w/3.8w | 1000/750 | |
| 企業級 固態MLC PCI-E | Intel 710 | 100G | ¥2.5k | 3.8w/2.3k | 270/170 | |
| 民用 固態MLC 2.5英寸 SATA 6G | Intel 520 | 120G | ¥840 | 2.5w/8w | 550/500 | |
| 民用 固態MLC 2.5英寸 SATA 6G | 鎂光 M4 | 128G | ¥800 | 7.8w/4.2w | 7w/4w | 500/175 |
為什么民用SSD的iops很高價格卻很低,而企業級SSD的iops有的很低而價格卻很高?因為企業級SSD的耐用性高,比如Intel 710 100G壽命為4K寫入500TB,即5000次全盤寫入。
Intel SSD壽命指標:smart中的“E8:Avai lable Reserved Space”:可用的預留閃存數量、“E9:Media Wearout Indicator”:閃存磨耗指數。其他廠商的SSD類似,比如鎂光的wear leaving count。
SSD新盤的剩余磨損為100,當低於10時,應更換,報廢。
todo:數據庫的選擇
關系型數據庫用Mysql還是PostgreSQL,或者全用NoSQL?mongo還是hbase?
Mysql 和 PostgreSQL都可以。mysql用的人多,但是oracle收購sun以后,把mysql限制的更加封閉,正在衰落,和可能和OpenOffice一樣被oracle整死,衍生出LibreOffice。但不用很擔心,即使mysql被oracle整死,也會衍生出開源版本,使用方式一樣。
關系型數據庫和NoSQL搭配使用較好,關系型適合底層業務,NoSQL適合上層業務。關注淘寶hbase的使用。
Mysql性能與硬盤iops的關系:
mysql可以把讀取結果放在內存中,即query cache,所以db server安裝大內存即可實現只讀內存、不讀硬盤。
當預計數據量會增長到超過內存大小時,進行分表(把一個表中的數據拆分),放到多個大內存服務器上,保證每個服務器上的數據都小於內存大小,即可實現全部緩存。
2012年內存價格:UDIMM no ECC DDR3 1600民用內存 ¥270/8GB,RDIMM ECC DDR3 1600服務器內存 ¥440/8G。但一台服務器能安裝的內存有限,2012年典型的Dell服務器有24個插槽,主板芯片支持768G內存,2012年DDR3內存生產工藝最高是單條8G(DDR4已實現單條16G,但主板尚未支持DDR4),所以一台服務器最大內存192G(¥440 * 24條 = 1w)。
數據庫寫入時必須寫硬盤(否則就不叫持久化存儲了……),¥2.5k的Intel 710企業級SSD的寫入iops為2.3k,而萬轉硬盤的iops為700,如果要達到SSD的性能,需要多塊萬轉硬盤組RAID。如果使用iops為3w的SSD,性能比機械硬盤提升了30倍。
如果數據量不大,遠低於192G,就不需要做分表了嗎?
仍然要分表,原因有2個:
1、大內存會提升mysql讀取性能,但並發讀取能力也是有上限的,這時受CPU性能限制,2012年典型的服務器是雙路,即2個8核CPU(mysql內存並發待測試)。
2、內存很大,對並發寫入能力沒有作用,寫入能力完全依賴於硬盤的iops。一台服務器的寫入性能很有限,請看下面的測試。
為什么不進行分庫(把不同的整個表放到不同的庫中,也就是不同的機器)?分庫簡單,但是一個表只增不減難於維護(比如評論表),大小總會超過內存,又要改成分表;或者隨着用戶量增大,這個表的寫入量會逐漸增大,單台機器必然無法承受,參考淘寶等網站的架構演進歷史就會發現這個問題。
為什么不用主從設計?因為每個“從”上面都有所有數據,數據量太大,同步壓力大,要做設計從每個“從”上面讀取不同的數據,以免內存query cache不夠用,這增加了復雜度;即使“主”只進行寫,寫入性能還有限,無法承受大並發。
所以最好在項目初期就使用分表設計,而不是分庫或主從設計,把分表設計做成一個成熟的方案(各個大型互聯網公司都已經實現),各個項目通用,免得后期數據量大了拆分表,需要導數據,需要暫停線上服務。
mysql測試環境:
服務器:vr.org VR512、阿里雲512、盛大雲512
mysql版本:5.5.27
連接方式:unix domain socket
表:phpbb 3.0.7的users表,含有76個字段,28個char,43個int,1個主鍵,1個自增,1個unique,3個index。
phpbb_users
CREATE TABLE `phpbb_users` ( `user_id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, `user_type` tinyint(2) NOT NULL DEFAULT '0', `group_id` mediumint(8) unsigned NOT NULL DEFAULT '3', `user_permissions` mediumtext COLLATE utf8_bin NOT NULL, `user_perm_from` mediumint(8) unsigned NOT NULL DEFAULT '0', `user_ip` varchar(40) COLLATE utf8_bin NOT NULL DEFAULT '', `user_regdate` int(11) unsigned NOT NULL DEFAULT '0', `username` varchar(255) COLLATE utf8_bin NOT NULL DEFAULT '', `username_clean` varchar(255) COLLATE utf8_bin NOT NULL DEFAULT '', `user_password` varchar(40) COLLATE utf8_bin NOT NULL DEFAULT '', `user_passchg` int(11) unsigned NOT NULL DEFAULT '0', `user_pass_convert` tinyint(1) unsigned NOT NULL DEFAULT '0', `user_email` varchar(100) COLLATE utf8_bin NOT NULL DEFAULT '', `user_email_hash` bigint(20) NOT NULL DEFAULT '0', `user_birthday` varchar(10) COLLATE utf8_bin NOT NULL DEFAULT '', `user_lastvisit` int(11) unsigned NOT NULL DEFAULT '0', `user_lastmark` int(11) unsigned NOT NULL DEFAULT '0', `user_lastpost_time` int(11) unsigned NOT NULL DEFAULT '0', `user_lastpage` varchar(200) COLLATE utf8_bin NOT NULL DEFAULT '', `user_last_confirm_key` varchar(10) COLLATE utf8_bin NOT NULL DEFAULT '', `user_last_search` int(11) unsigned NOT NULL DEFAULT '0', `user_warnings` tinyint(4) NOT NULL DEFAULT '0', `user_last_warning` int(11) unsigned NOT NULL DEFAULT '0', `user_login_attempts` tinyint(4) NOT NULL DEFAULT '0', `user_inactive_reason` tinyint(2) NOT NULL DEFAULT '0', `user_inactive_time` int(11) unsigned NOT NULL DEFAULT '0', `user_posts` mediumint(8) unsigned NOT NULL DEFAULT '0', `user_lang` varchar(30) COLLATE utf8_bin NOT NULL DEFAULT '', `user_timezone` decimal(5,2) NOT NULL DEFAULT '0.00', `user_dst` tinyint(1) unsigned NOT NULL DEFAULT '0', `user_dateformat` varchar(30) COLLATE utf8_bin NOT NULL DEFAULT 'd M Y H:i', `user_style` mediumint(8) unsigned NOT NULL DEFAULT '0', `user_rank` mediumint(8) unsigned NOT NULL DEFAULT '0', `user_colour` varchar(6) COLLATE utf8_bin NOT NULL DEFAULT '', `user_new_privmsg` int(4) NOT NULL DEFAULT '0', `user_unread_privmsg` int(4) NOT NULL DEFAULT '0', `user_last_privmsg` int(11) unsigned NOT NULL DEFAULT '0', `user_message_rules` tinyint(1) unsigned NOT NULL DEFAULT '0', `user_full_folder` int(11) NOT NULL DEFAULT '-3', `user_emailtime` int(11) unsigned NOT NULL DEFAULT '0', `user_topic_show_days` smallint(4) unsigned NOT NULL DEFAULT '0', `user_topic_sortby_type` varchar(1) COLLATE utf8_bin NOT NULL DEFAULT 't', `user_topic_sortby_dir` varchar(1) COLLATE utf8_bin NOT NULL DEFAULT 'd', `user_post_show_days` smallint(4) unsigned NOT NULL DEFAULT '0', `user_post_sortby_type` varchar(1) COLLATE utf8_bin NOT NULL DEFAULT 't', `user_post_sortby_dir` varchar(1) COLLATE utf8_bin NOT NULL DEFAULT 'a', `user_notify` tinyint(1) unsigned NOT NULL DEFAULT '0', `user_notify_pm` tinyint(1) unsigned NOT NULL DEFAULT '1', `user_notify_type` tinyint(4) NOT NULL DEFAULT '0', `user_allow_pm` tinyint(1) unsigned NOT NULL DEFAULT '1', `user_allow_viewonline` tinyint(1) unsigned NOT NULL DEFAULT '1', `user_allow_viewemail` tinyint(1) unsigned NOT NULL DEFAULT '1', `user_allow_massemail` tinyint(1) unsigned NOT NULL DEFAULT '1', `user_options` int(11) unsigned NOT NULL DEFAULT '230271', `user_avatar` varchar(255) COLLATE utf8_bin NOT NULL DEFAULT '', `user_avatar_type` tinyint(2) NOT NULL DEFAULT '0', `user_avatar_width` smallint(4) unsigned NOT NULL DEFAULT '0', `user_avatar_height` smallint(4) unsigned NOT NULL DEFAULT '0', `user_sig` mediumtext COLLATE utf8_bin NOT NULL, `user_sig_bbcode_uid` varchar(8) COLLATE utf8_bin NOT NULL DEFAULT '', `user_sig_bbcode_bitfield` varchar(255) COLLATE utf8_bin NOT NULL DEFAULT '', `user_from` varchar(100) COLLATE utf8_bin NOT NULL DEFAULT '', `user_icq` varchar(15) COLLATE utf8_bin NOT NULL DEFAULT '', `user_aim` varchar(255) COLLATE utf8_bin NOT NULL DEFAULT '', `user_yim` varchar(255) COLLATE utf8_bin NOT NULL DEFAULT '', `user_msnm` varchar(255) COLLATE utf8_bin NOT NULL DEFAULT '', `user_jabber` varchar(255) COLLATE utf8_bin NOT NULL DEFAULT '', `user_website` varchar(200) COLLATE utf8_bin NOT NULL DEFAULT '', `user_occ` text COLLATE utf8_bin NOT NULL, `user_interests` text COLLATE utf8_bin NOT NULL, `user_actkey` varchar(32) COLLATE utf8_bin NOT NULL DEFAULT '', `user_newpasswd` varchar(40) COLLATE utf8_bin NOT NULL DEFAULT '', `user_form_salt` varchar(32) COLLATE utf8_bin NOT NULL DEFAULT '', `user_new` tinyint(1) unsigned NOT NULL DEFAULT '1', `user_reminded` tinyint(4) NOT NULL DEFAULT '0', `user_reminded_time` int(11) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`user_id`), UNIQUE KEY `username_clean` (`username_clean`), KEY `user_birthday` (`user_birthday`), KEY `user_email_hash` (`user_email_hash`), KEY `user_type` (`user_type`) ) ENGINE=InnoDB AUTO_INCREMENT=53 DEFAULT CHARSET=utf8 COLLATE=utf8_bin
mysql配置:
並發默認151,超過時會出現“1040 Too many connections”,修改為99999:set global max_connections=99999;
超過1024時,碰到了Linux “open files”限制,出現錯誤“2001 Can't create UNIX socket (24)”。修改為99999:ulimit -n 99999
測試程序:
write 和 update 直接寫入硬盤,這時要注意iowait。
key 和 read 每次先寫入99條數據(這時影響觀測iowait),然后並發查詢,然后刪除數據,重新生成。比如iterations=10,就會生成10次。如果並發1000,那很快就會把所有數據query cache起來,速度變快。這種情況和生產環境類似,保證內存大於數據庫,數據只讀一次硬盤,以后一直在內存中,所以不觀測iowait。
./mysqlslap --auto-generate-sql --number-char-cols=28 --number-int-cols=43 --auto-generate-sql-load-type=write --auto-generate-sql-add-autoincrement --socket=/tmp/mysql.sock --concurrency=800 --number-of-queries=800 --user=root --password=1 --iterations=10
如果不考慮讀一次硬盤,內存中cache了所有數據,是不是read並發就會很大?經測試,稍微大了一點,這時iowait顯然為0,但CPU 100%,如果CPU更強,並發預計會進一步提高。
先寫入1w條,只查某一條,確保query cache,mysqlslap key時並發為1300,全內存時為1700。
./mysqlslap --query='select * from t1 limit 1;' --socket=/tmp/mysql.sock --concurrency=800 --number-of-queries=800 --user=root --password=1 --iterations=10
mysql性能判斷規則:
Average number of seconds to run all queries < 1秒,比如800個並發,執行800個sql,如果在1秒內不能完成,就會影響下一秒的800並發。
iops測試環境:
分區測試 還是 設備測試:分區測試。
| 4K QD32隨機讀/寫(iops) | 硬盤到內存mysql key(按主鍵讀) | 硬盤到內存mysql read |
硬盤mysql write |
硬盤mysql update | 硬盤mysql mixed | |
| vr.org VR512 | 100-300/700-2900波動很大 | 並發1300,0.98s | 並發180,0.91s | 並發800,0.98s,iowait < 21 | 並發800,0.92s,iowait < 15 | 並發900,0.97s,iowait < 16 |
| 阿里雲主機 | ||||||
| 阿里雲數據庫 | ||||||
| 盛大雲主機 512 Windows | 300-420/350-400波動不大 |
|
||||
| 盛大雲主機 512 Linux | 70-90/550波動不大 | 並發1300,0.97s |
並發300,0.99s | 並發1300,0.97s,iowait < 21 | 並發1100,0.96s,iowait < 18 | 並發1200,0.91s,iowait < 25 |
| 盛大雲數據庫 |
mysql測試圖:


傳統web服務的mysql壓力:
讀取量大,寫入量小,所以加大內存即可。
案例:
1、小米論壇(bbs.xiaomi.cn),在2012年9月10日發帖和回帖合計89w,PV約2000w。

每次發帖回帖需要發表(同步)、增加積分(異步)等操作,按每次發帖需要3次同步寫數據庫算,89w * 3次 / (15h * 3600s)* 高峰時2倍 = 99次寫/s。
頁面數據在業務層、頁面層有緩存,實際讀取數據庫按每次瀏覽需要1次讀數據庫算,2000w / (15h * 3600s)* 高峰時2倍 = 740次讀/s。
按一台微型vps db並發900計算,兩台vps db即可承受,一台自購服務器Raid即可承受。
2、遠景論壇(bbs.pcbeta.com),在2012年8月Windows 8發布后,每天發帖回帖1.9w。

數據庫寫入並發:1.9w * 3次 / (15h * 3600s)* 高峰時2倍 = 2次寫/s。
3、instagram使用Amazon雲服務,達到820w UV,那也是普通web服務,並發很低,假設是5000w PV,並發量才 5000w / (15小時 * 3600) = 900。
“秒殺”服務的mysql壓力:
像小米手機搶購、淘寶光棍節促銷,這種“秒殺”服務流量集中在瞬間,而不是全天,並發寫入量大、並發讀取量更大。
案例:
2012年8月23日10點,小米1S開放購買20w台,事先有個預定量統計,約160w人,到了秒殺時間並發量讀取為每秒十萬級(登錄、刷新),登錄頁面正常顯示,但提交后返回500錯誤,數據庫讀取壓力達到極限,登錄很困難……



假設有50w人10點准時搶購,有30w人卡在登錄步驟,一直刷新提交,按讀取並發10w計算,微型vps的每台mysql為1300,需要76台微型vps……顯然不合理,成本太高。購買雲數據庫也無法保證10w並發(雲數據庫待測試)。
按機械硬盤Raid的自購服務器mysql key為3000計算(參考值,待有條件時測試),需要33台服務器……
假如使用Intel 710 SSD,讀取iops為3.8w,預計mysql key為5w,只需要2塊SSD即可。
29分36秒搶購結束,數據庫寫入為 20w / 1776秒 = 112次/s,由於卡在了登錄,所以寫入量不大。
2012年9月6日10點,小米1S開放購買5w台電信版+15w台標准版,預定量未公布。參考上次160w的預定量,假設這次一樣人數。
由於數據庫讀寫能力有限,采用了分批選取一部分用戶可以看見“驗證預約信息”的鏈接,別的用戶都看見“您沒有購買小米手機1s的特權”,這一批處理完了,再選取下一批。
這句話語義錯誤,因為這些用戶都是已經預約的,導致用戶投訴。


9分40秒搶購結束。
20w訂單,數據庫寫入為 20w / 580秒 = 344次/s
第一次搶購卡在登錄,第二次搶購分批影響用戶體驗。
如果不分批,進行正常搶購,160w中有50w人在10點等待着,ajax檢查登錄返回搶購鏈接(如果使用memcache session或者mongo session,要考慮nosql並發。如果使用無需存儲的加密仿session,需要考慮cpu能承受多少並發計算加密對比),
花費5秒輸入預約信息(復制粘貼4秒,如果手打8秒),
然后提交數據庫進行驗證,數據庫要承受10w-20w/s的讀取壓力,
都是預約過的,50w都人驗證通過,點擊購買下訂單,數據庫要承受10w-20w/s的寫入壓力。
所以,如果創業公司業務要做秒殺,需要自購服務器(用SSD Raid,加大內存),而不是采用雲服務。
如果購買品牌服務器,可以自行購買SSD和內存,因為服務器廠商提供的配件要貴2倍以上。
比如dell 8核 1U、2U服務器價格約¥1.4w,但選配的硬盤很貴,參考文章末尾的截圖。
所以無論是PC還是server,DIY一直是高性能低價格的終極解決方案。
如果ODM自行設計服務器,參考Facebook開放服務器計划:http://news.cnblogs.com/n/133536/
Dell服務器硬件價格圖:

硬盤iops測試圖:













參考資料:
http://tech.sina.com.cn/h/2012-08-02/09337458765_2.shtml
http://article.pchome.net/content-1515362-7.html
http://bbs.xiaomi.cn/forum.php?mod=viewthread&tid=4615631
http://news.cnblogs.com/n/133536/
http://www.liusuping.com/it-tech/server-rdimm-udimm.html
http://ssd.zol.com.cn/252/2529054.html
http://ssd.zol.com.cn/303/3031977.html
http://www.ssdfreaks.com/content/599/how-to-convert-mbps-to-iops-or-calculate-iops-from-mbs
http://www.xfastest.com/thread-65671-1-1.html
http://bbs.pceva.com.cn/thread-36832-1-1.html
http://tech.watchstor.com/storage-module-136610.htm
http://isjin.blog.51cto.com/612537/528622
http://www.datacentersky.com/undeer-linux-use-iometer.html
http://www.datacentersky.com/taught-you-how-to-use-iometer-test-tool-to-test-storage.html
