ondblClickRow: function (rowid, iRow, iCol, e) {
alert("rowid: " + rowid + ", iRow: " + iRow + ", iCol: " + iCol);
if (iCol == 2) {
$(this).jqGrid("toggleSubGridRow", rowid);
}
},
onCellSelect: function (rowid, iCol, cellcontent, e) {
alert("rowid: " + rowid + ", iCol: " + iCol + ", cellcontent" + cellcontent);
if (iCol == 2) {
$(this).jqGrid("toggleSubGridRow", rowid);
}
},
2013年10月8日
jqGrid 展開 subGrid by other columns
2013年9月13日
WebService Test soapUI
http://sourceforge.net/projects/soapui/files/
host/WebService.asmx?WSDL
<soap:envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soap:body>
<etagservice xmlns="http://tempuri.org/">
<strtx>
xml string
</strtx>
</etagservice>
</soap:body>
</soap:envelope>
host/WebService.asmx?WSDL
<soap:envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soap:body>
<etagservice xmlns="http://tempuri.org/">
<strtx>
xml string
</strtx>
</etagservice>
</soap:body>
</soap:envelope>
2013年8月14日
執行錯誤的選擇
時程不會因為你下了錯誤的決定並執行而往後延
除了這次補救回來
以後必須避免錯誤的判斷
如同昨天在測試 css設定是否只在 IE7會有問題
堅持使用自己修改瀏覽器模式的 IE7去找問題
後來請他人開 VM用 IE7測就沒問題
等於做白工 ...
2013年8月13日
jQuery v.s. IE9 相同性
IE預設在 intranet的時候會使用 Document mode =IE 7的相容性
這樣會造成許多不可預期的 js錯誤
目前CSS使用的解法是在 master page head的部份加上以下 meta data使得他強迫使用ie10的文件模式(實測ie9也是ok)
<meta http-equiv="Content-Type;" content="IE=10" />
如果本來就有資料的話可以用分號加上IE=10即可
這樣會造成許多不可預期的 js錯誤
目前CSS使用的解法是在 master page head的部份加上以下 meta data使得他強迫使用ie10的文件模式(實測ie9也是ok)
<meta http-equiv="Content-Type;" content="IE=10" />
如果本來就有資料的話可以用分號加上IE=10即可
jqGrid 換頁後, formatter失效
http://stackoverflow.com/questions/3807623/jqgrid-paging-question
用 bind綁定 click事件
$("#list").setGridParam({page:1}).trigger('reloadGrid');
$("#list").trigger("reloadGrid", [{page:1}]);
$("#list").trigger("reloadGrid", [{current:true}]);
使用無效?用 bind綁定 click事件
$('td[id$=_pager]').click(function () {
$('.buttonDownload').bind('click', Download);
});
function downloadButton(cellvalue, options, rowObject) {
return "<input type='button' class='buttonDownload' value='列印' data-formTypeId='" +@Model.formTypeId +"' data-formNumber='" + cellvalue + "' />";
}
function Download() {
var formTypeId = $(this).attr('data-formTypeId');
var formNumber = $(this).attr('data-formNumber');
}
或 ...function downloadButton(cellvalue, options, rowObject) {
return "<input type='button' class='buttonDownload' onclick='javascript:Download(" + @Model.formTypeId + ", " + cellvalue + ");' value='列印' data-formTypeId='" +@Model.formTypeId +"' data-formNumber='" + cellvalue + "' />";
}
2013年7月3日
將 IIS Log匯入資料庫
- 下載 Log Parser 2.2
- script LogParser to Database
"C:\Program Files (x86)\Log Parser 2.2\LogParser.exe" "SELECT * INTO IISLogs FROM C:\inetpub\logs\LogFiles\W3SVC2145945211\u_ex*.log" -i:W3C -o:SQL -server:MORTIZ-HY -database:Logs -driver:"SQL Server" -username:moz -password:moz123 -createTable:ON
"C:\Program Files (x86)\Log Parser 2.2\LogParser.exe" "SELECT TO_LOCALTIME(TO_TIMESTAMP([date], [time])), [s-sitename], [s-computername], [s-ip], [cs-method], [cs-uri-stem], [cs-uri-query], [s-port], [cs-username], [c-ip], [cs-version], [cs(User-Agent)], [cs(Cookie)], [cs(Referer)], [cs-host], [sc-status], [sc-substatus], [sc-win32-status], [sc-bytes], [cs-bytes], [time-taken] INTO IISLogs FROM C:\inetpub\logs\LogFiles\W3SVC2145945211\u_ex130222.log " -o:SQL -server:MORTIZ-HY -database:Logs -driver:"SQL Server" -username:moz -password:moz123 -createTable:ON
"C:\Program Files (x86)\Log Parser 2.2\LogParser.exe" "SELECT * INTO IISLogs FROM C:\inetpub\logs\LogFiles\SelectedLogs\u_ex*.log" -i:W3C -o:SQL -server:210.65.10.173 -database:Logs -driver:"SQL Server" -username:sa -password:SQLpw1064MS -createTable:ON
- 清掉 IISLogs無用的 data
DELETE Logs.dbo.IISLogs WHERE csUriStem LIKE '%txt' OR csUriStem LIKE '%jpg' OR csUriStem LIKE '%png' OR csUriStem LIKE '%css' OR csUriStem LIKE '%js' OR csUriStem LIKE '%ico' OR csUriStem LIKE '%gif' OR csUriStem LIKE '/Scripts%' OR csUriStem LIKE '/TrendgoAdmin%' GO
- temp
2013年6月18日
SA文件的意外
在完成 TWM的檔案同步系統(考試系統 Phase II)的 SA文件時
寄信給客戶,結果是由客戶自行轉成 v1.0 且還有添加新的內容
我也沒仔細檢查
應該要這樣表達:確認沒有問題後,我會轉成 v1.0正式版
2013年6月4日
2013年5月14日
List for survey
Excel Utility (use Factory Method with EPPlus, ClosedXML)
Design Pattern
Event & Delegate
Enterprise Library
KnockoutJS
AngularJS
NodeJs
Entity Framework
Dojotoolkit.org
Cupoy
Icenium 雲端開發 App
Markdown
Octopress
OpenCart
NoSQL
Mango DB
Perl
LISP
Design Pattern
Event & Delegate
Enterprise Library
KnockoutJS
AngularJS
NodeJs
Entity Framework
Dojotoolkit.org
Cupoy
Icenium 雲端開發 App
Markdown
Octopress
OpenCart
Mango DB
Perl
LISP
2013年5月3日
修復計畫
- 程式怎麼改
資料怎麼補 - 取得同意
- 修改 & 補資料
ex:
全國意向
bug五月才修完
所以應該是要補四月的資料
把要補的帳號、登入記錄、使用記錄, 列給對方
詢問對方是否要採用
並告知對方補進去的資料可能會跟已經被下載的報表有誤差
取得同意後
再把資料補進去
2013年4月18日
ASP.NET MVC foreach with null para
foreach (var item in Model ?? Enumerable.Empty <ModelData>())
{
...
}
Visual SVN on Windows
Visual SVN server 安裝在 Windows系統上時
使用者即使沒有 Repositoris的權限, 只要存取路徑變更大小寫一樣可以存取 ...
雖然 Windows的檔案系統就會區分, 不能建同名但大小寫不同的資料夾
source: http://tortoisesvn.tigris.org/faq.zh.html
使用者即使沒有 Repositoris的權限, 只要存取路徑變更大小寫一樣可以存取 ...
雖然 Windows的檔案系統就會區分, 不能建同名但大小寫不同的資料夾
source: http://tortoisesvn.tigris.org/faq.zh.html
2013年4月16日
2013年4月15日
2013年4月8日
Django 練習 #005 Ajax Post & HttpResponse
如果沒有做任何設定
在 Django內使用 Ajax Post遞交 request會有 CSRF的問題
(Cross Site Request Forgery protection)
在網路上查到的解法大部分是將一個 crsf_token值也當作 post的參數
藉以通過 Django的檢查
不過我怎麼設定都沒成功過
於是採用另一種方式
讓指定的 View(controller) Action忽略 CRSF
這部分可以參考 django_AJAX
View:
在 Django內使用 Ajax Post遞交 request會有 CSRF的問題
(Cross Site Request Forgery protection)
在網路上查到的解法大部分是將一個 crsf_token值也當作 post的參數
藉以通過 Django的檢查
不過我怎麼設定都沒成功過
於是採用另一種方式
讓指定的 View(controller) Action忽略 CRSF
這部分可以參考 django_AJAX
View:
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
@csrf_exempt
def CreateArticle(request):
return HttpResponse('true')
2013年4月2日
2013年3月5日
2013年3月1日
Django 練習 #004 Database Data Field Type
models.IntegerField(default=0)
models.CharField(max_length=100)
models.BooleanField(default=False)
models.DateTimeField(default=datetime.datetime.now)
models.ForeignKey('--other table name--')
models.CharField(max_length=100)
models.BooleanField(default=False)
models.DateTimeField(default=datetime.datetime.now)
models.ForeignKey('--other table name--')
Django 練習 #003 Template Inheritance(Master Page)
建立資料夾
(poject)\templates
master pages & pages put in directory templates
(poject)\templates
master pages & pages put in directory templates
Django 練習 #002 Static Resource 靜態資源位置(css, image, js)
- 建立資料夾
(Project)/static/css
(Project)/static/img
(Project)/static/js
- (project)/(project)/settings
STATICFILES_DIRS = ( "C:/Users/Mortiz/Documents/Django/MPSite/static", }
- 引用
<link rel="stylesheet" href="/static/css/dnm/Blog.css" type="text/css" />
2013年2月21日
Django 練習 #001
- 建立專案
django-admin.py startproject Play
開啟 DOS
cd C:\Users\Mortiz\Documents\Django\Play
- 資料庫連線設定
Play\Settings.py →DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'Play', #Schema(Table) 'USER': 'Moz', 'PASSWORD': 'moz123', 'HOST': '', 'PORT': '' }
- 同步資料庫 & model
python manage.py syncdb
- 建立程式
python manage.py startapp First
- model
First\model.py →from django.db import models class User(models.Model): Account = models.CharField(max_length=12) PW = models.CharField(max_length=12) Name = models.CharField(max_length=50) def __unicode__(self): return self.Name
- view(controller)
First\views.py →from django.shortcuts import render_to_response from models import User def getUserList(request): user_lsit = User.objects.order_by('-Name') return render_to_response('First/List.html' , {'data_list':user_lsit} )
- templates(view)
Firts\List.html →<html><body> It is now {{ data_list }}.</body></html>
- 將 model User添加到 INSTALLED_APPS
Play\Settings.py →INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', # New 'First', # folder name, not class name }
- 同步 model到 MySQL schema(Table)
python manage.py syncdb
model內不可建 id column, django會自動建立做為 primary key
- 設定 url
Play\urls.py →from django.conf.urls import patterns, include, url urlpatterns = patterns('', url('^First/$' , 'First.views.getUserList' , name='hello'), )
- 設定 templates(view)位址
Play\settings.py →TEMPLATE_DIRS = ( "C:\Users\Mortiz\Documents\Django\Play", )
- python manage.py runserver 啟動 Server
安裝 Django + MySQL @ Win7
Python
- 下載 & 安裝 Python v2.7
Djagon 尚未支援 v3.x - 電腦 → 內容 → 進階系統設定 → 進階 → 環境變數 → 系統變數 → Path → 編輯
→ 加上 C:\Python27
Django
- 下載 Django並解壓縮
- 開啟 DOS視窗 cd到 Django資料夾
- 下指令 python setup.py install
安裝會在 C:\Python\Libs\site-packages - 進入 python
- 下指令 import django
- 再 print django.get_version() 檢查版本
- 環境變數 Path → 加上 C:\Python27\python.exe;C:\Python27\Scripts;C:\Python27\Lib\site-packages\django\bin;
MySQL
- 下載 & 安裝 MySQL + MySQL-python-1.2.4b4.win32-py2.7
- Home → Server Administration → (right click) Manage Instance → login
- Manage → Startup / Shutdown
- Start Server
- Home → Open Connection to Start Querying
- Create a New Schema(Table)
2013年2月18日
初學程式語言
對學生來說
學習一個有用的(for Job)程式語言還是學一個有趣的(for personal)程式語言好?
如果一開始不是循序漸進的從資料型態開始一股腦的傾倒
而是給一個有趣的題目
然後慢慢的從架構程式中教導
會不會比較好?
2013年2月6日
2013年2月5日
威知 總結 #2
1. SVN的安裝。
僅參考 無腦安裝 SVN 過程,使用 Visual SVN
http://demo.tc/Post/589
2. 利用Literal元件組合HTML依你的描述並非好作法,請提供你的建議作法。
威知工程師多把 Literal用在組 html Table字串
因為第一個案子就先做 MVC架構的關係
看到這種 html在後台組出來的直覺就認為修改 UI時較麻煩
會想盡量把 Model跟 View分離
就用 ListView或 Repeater
有時做 select的 option這種沒有特別功能及 UI需求的
我也會用 Literal直接串
3. 整理你有用過的TreeView元件。官網、限制、優缺點等
沒有用過其他的 TreeView
查到被推薦的 TreeView就先看看有沒有拖曳及選單功能
(在節點上按右鍵跳出的選單選項也要能客製設定)
查 API看如何實現節點的新增修改刪除
(有些 TreeView要寫 php, 太麻煩)
後來發現 Telerik的 RadTreeView除了 server-side的編輯方式
client-side給的函數功能也很齊全
新增修改刪除拖曳都可以在 client-side用 Ajax post完成
4. 依開發經驗,你覺得abstract的優缺點為何。
優點:
可以先打好程式架構, 實作時只要 override要實作的方法
可避免重複開發相同邏輯的程式
缺點:
還沒想(感覺)到
5. 研究國貿局那種儲存即編譯的作法。
差異在開專案時選擇的類型
File → New → Web Site
File → New → Project → Visual C#, Web → ASP.NET Web Application
除了專案內資料夾(檔案)結構的差異,主要為編譯方式不同
參考:
網站和專案的不同?
Web Application Projects versus Web Site Projects in Visual Studio
僅參考 無腦安裝 SVN 過程,使用 Visual SVN
http://demo.tc/Post/589
2. 利用Literal元件組合HTML依你的描述並非好作法,請提供你的建議作法。
威知工程師多把 Literal用在組 html Table字串
因為第一個案子就先做 MVC架構的關係
看到這種 html在後台組出來的直覺就認為修改 UI時較麻煩
會想盡量把 Model跟 View分離
就用 ListView或 Repeater
有時做 select的 option這種沒有特別功能及 UI需求的
我也會用 Literal直接串
3. 整理你有用過的TreeView元件。官網、限制、優缺點等
沒有用過其他的 TreeView
查到被推薦的 TreeView就先看看有沒有拖曳及選單功能
(在節點上按右鍵跳出的選單選項也要能客製設定)
查 API看如何實現節點的新增修改刪除
(有些 TreeView要寫 php, 太麻煩)
後來發現 Telerik的 RadTreeView除了 server-side的編輯方式
client-side給的函數功能也很齊全
新增修改刪除拖曳都可以在 client-side用 Ajax post完成
4. 依開發經驗,你覺得abstract的優缺點為何。
優點:
可以先打好程式架構, 實作時只要 override要實作的方法
可避免重複開發相同邏輯的程式
缺點:
還沒想(感覺)到
5. 研究國貿局那種儲存即編譯的作法。
差異在開專案時選擇的類型
File → New → Web Site
File → New → Project → Visual C#, Web → ASP.NET Web Application
除了專案內資料夾(檔案)結構的差異,主要為編譯方式不同
參考:
網站和專案的不同?
Web Application Projects versus Web Site Projects in Visual Studio
[轉] 由12306.cn談談網站性能技術
轉載自 酷殼 – CoolShell.cn ,請勿用於任何商業用途
由12306.cn談談網站性能技術
12306.cn網站掛了,被全國人民罵了。我這兩天也在思考這個事,我想以這個事來粗略地和大家討論一下網站性能的問題。因為倉促,而且完全基於本人有限的經驗和瞭解,所以,如果有什麼問題還請大家一起討論和指正。(這又是一篇長文,只討論性能問題,不討論那些UI,用戶體驗,或是是否把支付和購票下單環節分開的功能性的東西)
還有一個變態的方法是把這些靜態頁面放在/dev/shm下,這個目錄就是內存,直接從內存中把文件讀出來返回,這樣可以減少昂貴的磁盤I/O。使用nginx的sendfile功能可以讓這些靜態文件直接在內核心態交換,可以極大增加性能。
對於火車票量的查詢,個人覺得不要顯示數字,就顯示一個「有」或「無」就好了,這樣可以大大簡化系統複雜度,並提升性能。把查詢對數據庫的負載分出去,從而讓數據庫可以更好地為下單的人服務。
1)緩存的更新。也叫緩存和數據庫的同步。有這麼幾種方法,一是緩存time out,讓緩存失效,重查,二是,由後端通知更新,一量後端發生變化,通知前端更新。前者實現起來比較簡單,但實時性不高,後者實現起來比較複雜 ,但實時性高。
2)緩存的換頁。內存可能不夠,所以,需要把一些不活躍的數據換出內存,這個和操作系統的內存換頁和交換內存很相似。FIFO、LRU、LFU都是比較經典的換頁算法。相關內容參看Wikipeida的緩存算法。
3)緩存的重建和持久化。緩存在內存,系統總要維護,所以,緩存就會丟失,如果緩存沒了,就需要重建,如果數據量很大,緩存重建的過程會很慢,這會影響生產環境,所以,緩存的持久化也是需要考慮的。
諸多強大的NoSQL都很好支持了上述三大緩存的問題。
數據鏡像的數據一致性可能是個複雜的問題,所以我們要在單條數據上進行數據分區,也就是說,把一個暢銷商品的庫存均分到不同的服務器上,如,一個暢銷商品有1萬的庫存,我們可以設置10台服務器,每台服務器上有1000個庫存,這就好像B2C的倉庫一樣。
1)把數據把某種邏輯來分類。比如火車票的訂票系統可以按各鐵路局來分,可按各種車型分,可以按始發站分,可以按目的地分……,反正就是把一張表拆成多張有一樣的字段但是不同種類的表,這樣,這些表就可以存在不同的機器上以達到分擔負載的目的。
2)把數據按字段分,也就是豎著分表。比如把一些不經常改的數據放在一個表裡,經常改的數據放在另外多個表裡。把一張表變成1對1的關係,這樣,你可以減少表的字段個數,同樣可以提升一定的性能。另外,字段多會造成一條記錄的存儲會被放到不同的頁表裡,這對於讀寫性能都有問題。但這樣一來會有很多複雜的控制。
3)平均分表。因為第一種方法是並不一定平均分均,可能某個種類的數據還是很多。所以,也有採用平均分配的方式,通過主鍵ID的範圍來分表。
4)同一數據分區。這個在上面數據鏡像提過。也就是把同一商品的庫存值分到不同的服務器上,比如有10000個庫存,可以分到10台服務器上,一台上有1000個庫存。然後負載均衡。
這三種分區都有好有壞。最常用的還是第一種。數據一旦分區,你就需要有一個或是多個調度來讓你的前端程序知道去哪裡找數據。把火車票的數據分區,並放在各個省市,會對12306這個系統有非常有意義的質的性能的提高。
任務分配服務器有一些難點:
還有一種方法是使用搶佔式的方式進行負載均衡,由下游的計算服務器去任務服務器上拿任務。讓這些計算服務器自己決定自己是否要任務。這樣的好處是可以簡化系統的複雜度,而且還可以任意實時地減少或增加計算服務器。但是唯一不好的就是,如果有一些任務只能在某種服務器上處理,這可能會引入一些複雜度。不過總體來說,這種方法可能是比較好的負載均衡。
雲風同學設計的「排隊系統」 就是這個技術。這和電子商務的訂單系統很相似,就是說,我的系統收到了你的購票下單請求,但是我還沒有真正處理,我的系統會跟據我自己的處理能力來throttle住這些大量的請求,並一點一點地處理。一旦處理完成,我就可以發郵件或短信告訴用戶你來可以真正購票了。
在這裡,我想通過業務和用戶需求方面討論一下雲風同學的這個排隊系統,因為其從技術上看似解決了這個問題,但是從業務和用戶需求上來說可能還是有一些值得我們去深入思考的地方:
0)無論你怎麼設計,你的系統一定要能容易地水平擴展。也就是說,你的整個數據流中,所有的環節都要能夠水平擴展。這樣,當你的系統有性能問題時,「加30倍的服務器」才不會被人譏笑。
1)上述的技術不是一朝一夕能搞定的,沒有長期的積累,基本無望。我們可以看到,無論你用哪種都會引發一些複雜性,設計總是在做一種權衡。
2)集中式的賣票很難搞定,使用上述的技術可以讓訂票系統能有幾佰倍的性能提升。而在各個省市建分站,分開賣票,是能讓現有系統性能有質的提升的最好方法。
3)春運前夕搶票且票量供遠小於求這種業務模式是相當變態的,讓幾千萬甚至上億的人在某個早晨的8點鐘同時登錄同時搶票的這種業務模式是變態中的變態。業務形態的變態決定了無論他們怎麼辦幹一定會被罵。
4)為了那麼一兩個星期而搞那麼大的系統,而其它時間都在閒著,有些可惜了,這也就是鐵路才幹得出來這樣的事了。
更新2012年9月27日
(本文轉載時請註明作者和出處,請勿於記商業目的)
業務
任何技術都離不開業務需求,所以,要說明性能問題,首先還是想先說說業務問題。- 其一,有人可能把這個東西和QQ或是網遊相比。但我覺得這兩者是不一樣的,網遊和QQ在線或是登錄時訪問的更多的是用戶自己的數據,而訂票系統訪問的是中心的票量數據,這是不一樣的。不要覺得網遊或是QQ能行你就以為這是一樣的。網遊和QQ 的後端負載相對於電子商務的系統還是簡單。
- 其二,有人說春節期間訂火車的這個事好像網站的秒殺活動。的確很相似,但是如果你的思考不在表面的話,你會發現這也有些不一樣。火車票這個事,還有很多查詢操作,查時間,查座位,查舖位,一個車次不 行,又查另一個車次,其伴隨著大量的查詢操作,下單的時候需要對數據庫操作。而秒殺,直接殺就好了。另外,關於秒殺,完全可以做成只接受前N個用戶的請求(完全不操作後端的任何數據, 僅僅只是對用戶的下單操作log),這種業務,只需要在內存cache中放好可秒殺的數量,還可以把數據分佈開來放,100商品,10台服務器一台放10個,無需在當時操作任何數據庫。可以訂單數夠後,停止秒殺,然後批量寫數據庫。而且秒殺的商品不多。火車票這個不是像秒殺那麼簡單的,春運時間,幾乎所有的票都是熱門票,而且幾乎是全國人民都來了。(淘寶的雙十一也就3百萬用戶,而火車票瞬時有千萬級別甚至是億級別的)
- 其三,有人拿這個系統和奧運會的票務系統比較。我覺得還是不一樣。雖然奧運會的票務系統當年也一上線就廢了。但是奧運會用的是抽獎的方式,也就是說不存在先來先得的搶的方式,而且,是事後抽獎,事前只需要收信息,事前不需要保證數據一致性,沒有鎖,很容易水平擴展。
- 其四,訂票系統應該和電子商務的訂單系統很相似,都是需要對庫存進行:1)佔住庫存,2)支付(可選),3)扣除庫存的操作。這個是需要有一致性的檢查的,也就是在並發時需要對數據加鎖的。B2C的電商基本上都會把這個事幹成異步的,也就是說,你下的訂單並不是馬上處理的,而是延時處理的,只有成功處理了,系統才會給你一封確認郵件說是訂單成功。我相信有很多朋友都收到認單不成功的郵件。這就是說,數據一致性在並發下是一個瓶頸。
- 其五,鐵路的票務業務很變態,其採用的是突然放票,而有的票又遠遠不夠大家分,所以,大家才會有搶票這種有中國特色的業務的做法。於是當票放出來的時候,就會有幾百萬人甚至上千萬人殺上去,查詢,下單。幾十分鐘內,一個網站能接受幾千萬的訪問量,這個是很恐怖的事情。據說12306的高峰訪問是10億PV,集中在早8點到10點,每秒PV在高峰時上千萬。
- 庫存是B2C的惡夢,庫存管理相當的複雜。不信,你可以問問所有傳統和電務零售業的企業,看看他們管理庫存是多麼難的一件事。不然,就不會有那麼多人在問凡客的庫存問題了。(你還可以看看《喬布斯傳》,你就知道為什麼Tim會接任Apple的CEO了,最主要的原因是他搞定了蘋果的庫存週期問題)
- 對於一個網站來說,瀏覽網頁的高負載很容易搞定,查詢的負載有一定的難度去處理,不過還是可以通過緩存查詢結果來搞定,最難的就是下單的負載。因為要訪問庫存啊,對於下單,基本上是用異步來搞定的。去年雙11節,淘寶的每小時的訂單數大約在60萬左右,京東一天也才能支持40萬(居然比12306還差),亞馬遜5年前一小時可支持70萬訂單量。可見,下訂單的操作並沒有我們相像的那麼性能高。
- 淘寶要比B2C的網站要簡單得多,因為沒有倉庫,所以,不存在像B2C這樣有N個倉庫對同一商品庫存更新和查詢的操作。下單的時候,B2C的 網站要去找一個倉庫,又要離用戶近,又要有庫存,這需要很多計算。試想,你在北京買了一本書,北京的倉庫沒貨了,就要從周邊的倉庫調,那就要去看看瀋陽或 是西安的倉庫有沒有貨,如果沒有,又得看看江蘇的倉庫,等等。淘寶的就沒有那麼多事了,每個商戶有自己的庫存,庫存就是一個數字,並且庫存分到商戶頭上了,反而有利於性能擴展。
- 數據一致性才是真正的性能瓶頸。有 人說nginx可以搞定每秒10萬的靜態請求,我不懷疑。但這只是靜態請求,理論值,只要帶寬、I/O夠強,服務器計算能力夠,並支持的並發連接數頂得住10萬TCP鏈接的建立 的話,那沒有問題。但在數據一致性面前,這10萬就完完全全成了一個可望不可及的理論值了。
前端性能優化技術
要解決性能的問題,有很多種常用的方法,我在下面列舉一下,我相信12306這個網站使用下面的這些技術會讓其性能有質的飛躍。一、前端負載均衡
通過DNS的負載均衡器(一般在路由器上根據路由的負載重定向)可以把用戶的訪問均勻地分散在多個Web服務器上。這樣可以減少Web服務器的請求負載。因為http的請求都是短作業,所以,可以通過很簡單的負載均衡器來完成這一功能。最好是有CDN網絡讓用戶連接與其最近的服務器(CDN通常伴隨著分佈式存儲)。(關於負載均衡更為詳細的說明見「後端的負載均衡」)二、減少前端鏈接數
我看了一下12306.cn,打開主頁需要建60多個HTTP連接,車票預訂頁面則有70多個HTTP請求,現在的瀏覽器都是並發請求的(當然,瀏覽器的一個頁面的並發數是有限的,但是你擋不住用戶開多個頁面,而且,後端服務器TCP鏈接在前端斷開始,還不會馬上釋放或重要)。所以,只要有100萬個用戶,就有可能會有6000萬個鏈接(訪問第一次後有了瀏覽器端的cache,這個數會下來,就算只有20%也是百萬級的鏈接數),太多了。一個登錄查詢頁面就好了。把js打成一個文件,把css也打成一個文件,把圖標也打成一個文件,用css分塊展示。把鏈接數減到最低。三、減少網頁大小增加帶寬
這個世界不是哪個公司都敢做圖片服務的,因為圖片太耗帶寬了。現在寬帶時代很難有人能體會到當撥號時代做個圖頁都不敢用圖片的情形(現在在手機端瀏覽也是這個情形)。我查看了一下12306首頁的需要下載的總文件大小大約在900KB左右,如果你訪問過了,瀏覽器會幫你緩存很多,只需下載10K左右的文件。但是我們可以想像一個極端一點的案例,1百萬用戶同時訪問,且都是第一次訪問,每人下載量需要1M,如果需要在120秒內返回,那麼就需要,1M * 1M /120 * 8 = 66Gbps的帶寬。很驚人吧。所以,我估計在當天,12306的阻塞基本上應該是網絡帶寬,所以,你可能看到的是沒有響應。後面隨著瀏覽器的緩存幫助12306減少很多帶寬佔用,於是負載一下就到了後端,後端的數據處理瓶頸一下就出來。於是你會看到很多http 500之類的錯誤。這說明後端服務器垮了。四、前端頁面靜態化
靜態化一些不常變的頁面和數據,並gzip一下。五、優化查詢
很多人查詢都是在查一樣的,完全可以用反向代理合併這些並發的相同的查詢。這樣的技術主要用查詢結果緩存來實現,第一次查詢走數據庫獲得數據,並把數據放到緩存,後面的查詢統統直接訪問高速緩存。為每個查詢做Hash,使用NoSQL的技術可以完成這個優化。(這個技術也可以用做靜態頁面)對於火車票量的查詢,個人覺得不要顯示數字,就顯示一個「有」或「無」就好了,這樣可以大大簡化系統複雜度,並提升性能。把查詢對數據庫的負載分出去,從而讓數據庫可以更好地為下單的人服務。
六、緩存的問題
緩存可以用來緩存動態頁面,也可以用來緩存查詢的數據。緩存通常有那麼幾個問題:1)緩存的更新。也叫緩存和數據庫的同步。有這麼幾種方法,一是緩存time out,讓緩存失效,重查,二是,由後端通知更新,一量後端發生變化,通知前端更新。前者實現起來比較簡單,但實時性不高,後者實現起來比較複雜 ,但實時性高。
2)緩存的換頁。內存可能不夠,所以,需要把一些不活躍的數據換出內存,這個和操作系統的內存換頁和交換內存很相似。FIFO、LRU、LFU都是比較經典的換頁算法。相關內容參看Wikipeida的緩存算法。
3)緩存的重建和持久化。緩存在內存,系統總要維護,所以,緩存就會丟失,如果緩存沒了,就需要重建,如果數據量很大,緩存重建的過程會很慢,這會影響生產環境,所以,緩存的持久化也是需要考慮的。
諸多強大的NoSQL都很好支持了上述三大緩存的問題。
後端性能優化技術
前面討論了前端性能的優化技術,於是前端可能就不是瓶頸問題了。那麼性能問題就會到後端數據上來了。下面說幾個後端常見的性能優化技術。一、數據冗餘
關於數據冗餘,也就是說,把我們的數據庫的數據冗餘處理,也就是減少表連接這樣的開銷比較大的操作,但這樣會犧牲數據的一致性。風險比較大。很多人把NoSQL用做數據,快是快了,因為數據冗餘了,但這對數據一致性有大的風險。這需要根據不同的業務進行分析和處理。(注意:用關係型數據庫很容易移植到NoSQL上,但是反過來從NoSQL到關係型就難了)二、數據鏡像
幾乎所有主流的數據庫都支持鏡像,也就是replication。數據庫的鏡像帶來的好處就是可以做負載均衡。把一台數據庫的負載均分到多台上,同時又保證了數據一致性(Oracle的SCN)。最重要的是,這樣還可以有高可用性,一台廢了,還有另一台在服務。數據鏡像的數據一致性可能是個複雜的問題,所以我們要在單條數據上進行數據分區,也就是說,把一個暢銷商品的庫存均分到不同的服務器上,如,一個暢銷商品有1萬的庫存,我們可以設置10台服務器,每台服務器上有1000個庫存,這就好像B2C的倉庫一樣。
三、數據分區
數據鏡像不能解決的一個問題就是數據表裡的記錄太多,導致數據庫操作太慢。所以,把數據分區。數據分區有很多種做法,一般來說有下面這幾種:1)把數據把某種邏輯來分類。比如火車票的訂票系統可以按各鐵路局來分,可按各種車型分,可以按始發站分,可以按目的地分……,反正就是把一張表拆成多張有一樣的字段但是不同種類的表,這樣,這些表就可以存在不同的機器上以達到分擔負載的目的。
2)把數據按字段分,也就是豎著分表。比如把一些不經常改的數據放在一個表裡,經常改的數據放在另外多個表裡。把一張表變成1對1的關係,這樣,你可以減少表的字段個數,同樣可以提升一定的性能。另外,字段多會造成一條記錄的存儲會被放到不同的頁表裡,這對於讀寫性能都有問題。但這樣一來會有很多複雜的控制。
3)平均分表。因為第一種方法是並不一定平均分均,可能某個種類的數據還是很多。所以,也有採用平均分配的方式,通過主鍵ID的範圍來分表。
4)同一數據分區。這個在上面數據鏡像提過。也就是把同一商品的庫存值分到不同的服務器上,比如有10000個庫存,可以分到10台服務器上,一台上有1000個庫存。然後負載均衡。
這三種分區都有好有壞。最常用的還是第一種。數據一旦分區,你就需要有一個或是多個調度來讓你的前端程序知道去哪裡找數據。把火車票的數據分區,並放在各個省市,會對12306這個系統有非常有意義的質的性能的提高。
四、後端系統負載均衡
前面說了數據分區,數據分區可以在一定程度上減輕負載,但是無法減輕熱銷商品的負載,對於火車票來說,可以認為是大城市的某些主幹線上的車票。這就需要使用數據鏡像來減輕負載。使用數據鏡像,你必然要使用負載均衡,在後端,我們可能很難使用像路由器上的負載均衡器,因為那是均衡流量的,因為流量並不代表服務器的繁忙程度。因此,我們需要一個任務分配系統,其還能監控各個服務器的負載情況。任務分配服務器有一些難點:
- 負載情況比較複雜。什麼叫忙?是CPU高?還是磁盤I/O高?還是內存使用高?還是並發高?還是內存換頁率高?你可能需要全部都要考慮。這些信息要發送給那個任務分配器上,由任務分配器挑選一台負載最輕的服務器來處理。
- 任務分配服務器上需要對任務隊列,不能丟任務啊,所以還需要持久化。並且可以以批量的方式把任務分配給計算服務器。
- 任務分配服務器死了怎麼辦?這裡需要一些如Live-Standby或是failover等高可用性的技術。我們還需要注意那些持久化了的任務的隊列如何轉移到別的服務器上的問題。
還有一種方法是使用搶佔式的方式進行負載均衡,由下游的計算服務器去任務服務器上拿任務。讓這些計算服務器自己決定自己是否要任務。這樣的好處是可以簡化系統的複雜度,而且還可以任意實時地減少或增加計算服務器。但是唯一不好的就是,如果有一些任務只能在某種服務器上處理,這可能會引入一些複雜度。不過總體來說,這種方法可能是比較好的負載均衡。
五、異步、 throttle 和 批量處理
異步、throttle(節流閥) 和批量處理都需要對並發請求數做隊列處理的。- 異步在業務上一般來說就是收集請求,然後延時處理。在技術上就是可以把各個處理程序做成並行的,也就可以水平擴展了。但是異步的技術問題大概有這些,a)被調用方的結果返回,會涉及進程線程間通信的問題。b)如果程序需要回滾,回滾會有點複雜。c)異步通常都會伴隨多線程多進程,並發的控制也相對麻煩一些。d)很多異步系統都用消息機制,消息的丟失和亂序也會是比較複雜的問題。
- throttle 技術其實並不提升性能,這個技術主要是防止系統被超過自己不能處理的流量給搞垮了,這其實是個保護機制。使用throttle技術一般來說是對於一些自己無法控制的系統,比如,和你網站對接的銀行系統。
- 批量處理的技術,是把一堆基本相同的請求批量處理。比如,大家同時購買同一個商品,沒有必要你買一個我就寫一次數據庫,完全可以收集到一定數量的請求,一次操作。這個技術可以用作很多方面。比如節省網絡帶寬,我們都知道網絡上的MTU(最大傳輸單元),以態網是1500字節,光纖可以達到4000多個字節,如果你的一個網絡包沒有放滿這個MTU,那就是在浪費網絡帶寬,因為網卡的驅動程序只有一塊一塊地讀效率才會高。因此,網絡發包時,我們需要收集到足夠多的信息後再做網絡I/O,這也是一種批量處理的方式。批量處理的敵人是流量低,所以,批量處理的系統一般都會設置上兩個閥值,一個是作業量,另一個是timeout,只要有一個條件滿足,就會開始提交處理。
雲風同學設計的「排隊系統」 就是這個技術。這和電子商務的訂單系統很相似,就是說,我的系統收到了你的購票下單請求,但是我還沒有真正處理,我的系統會跟據我自己的處理能力來throttle住這些大量的請求,並一點一點地處理。一旦處理完成,我就可以發郵件或短信告訴用戶你來可以真正購票了。
在這裡,我想通過業務和用戶需求方面討論一下雲風同學的這個排隊系統,因為其從技術上看似解決了這個問題,但是從業務和用戶需求上來說可能還是有一些值得我們去深入思考的地方:
1)隊列的DoS攻擊。首先,我們思考一下,這個隊是個單純地排隊的嗎?這樣做還不夠好,因為這樣我們不能杜絕黃牛,而且單純的ticket_id很容易發生DoS攻擊,比如,我發起N個 ticket_id,進入購票流程後,我不買,我就耗你半個小時,很容易我就可以讓想買票的人幾天都買不到票。有人說,用戶應該要用身份證來排隊, 這樣在購買裡就必需要用這個身份證來買,但這也還不能杜絕黃牛排隊或是號販子。因為他們可以註冊N個帳號來排隊,但就是不買。黃牛這些人這個時候只需要干一個事,把網站搞得正常人不能訪問,讓用戶只能通過他們來買。
2)對列的一致性?對這個隊列的操作是不是需要鎖?只要有鎖,性能一定上不去。試想,100萬個人同時要求你來分配位置號,這個隊列將會成為性能瓶頸。你一定沒有數據庫實現得性能好,所以,可能比現在還差。搶數據庫和搶隊列本質上是一樣的。
3)隊列的等待時間。購票時間半小時夠不夠?多不多?要是那時用戶正好不能上網呢?如果時間短了,用戶不夠時間操作也會抱怨,如果時間長了,後面在排隊的那些人也會抱怨。這個方法可能在實際操作上會有很多問題。另外,半個小時太長了,這完全不現實,我們用15分鐘來舉例:有1千萬用戶,每一個時刻只能放進去1萬個,這1萬個用戶需要15分鐘完成所有操作,那麼,這1千萬用戶全部處理完,需要1000*15m = 250小時,10天半,火車早開了。(我並非信口開河,根據鐵道部專家的說明:這幾天,平均一天下單100萬,所以,處理1000萬的用戶需要十天。這個計算可能有點簡單了,我只是想說,在這樣低負載的系統下用排隊可能都不能解決業務問題)
4)隊列的分佈式。這個排隊系統只有一個隊列好嗎?還不足夠好。因為,如果你放進去的可以購票的人如果在買同一個車次的同樣的類型的票(比如某動車臥鋪),還是等於在搶票,也就是說系統的負載還是會有可能集中到其中某台服務器上。因此,最好的方法是根據用戶的需求——提供出發地和目的地,來對用戶進行排隊。而這樣一來,隊列也就可以是多個,只要是多個隊列,就可以水平擴展了。這樣可以解決性能問題,但是沒有解決用戶長時間排隊的問題。
我覺得完全可以向網上購物學習。在排隊(下單)的時候,收集好用戶的信息和想要買的票,並允許用戶設置購票的優先級,比如,A車次臥鋪買 不到就買 B車次的臥鋪,如果還買不到就買硬座等等,然後用戶把所需的錢先充值好,接下來就是系統完全自動地異步處理訂單。成功不成功都發短信或郵件通知用戶。這樣,系統不僅可以省去那半個小時的用戶交互時間,自動化加快處理,還可以合併相同購票請求的人,進行批處理(減少數據庫的操作次數)。這種方法最妙的事是可以知道這些排隊用戶的需求,不但可以優化用戶的隊列,把用戶分佈到不同的隊列,還可以像亞馬遜的心願單一樣,通過一些計算就可以讓鐵道部做車次統籌安排和調整(最後,排隊系統(下單系統)還是要保存在數據庫裡的或做持久化,不能只放在內存中,不然機器一down,就等著被罵吧)。小結
寫了那麼多,我小結一下:0)無論你怎麼設計,你的系統一定要能容易地水平擴展。也就是說,你的整個數據流中,所有的環節都要能夠水平擴展。這樣,當你的系統有性能問題時,「加30倍的服務器」才不會被人譏笑。
1)上述的技術不是一朝一夕能搞定的,沒有長期的積累,基本無望。我們可以看到,無論你用哪種都會引發一些複雜性,設計總是在做一種權衡。
2)集中式的賣票很難搞定,使用上述的技術可以讓訂票系統能有幾佰倍的性能提升。而在各個省市建分站,分開賣票,是能讓現有系統性能有質的提升的最好方法。
3)春運前夕搶票且票量供遠小於求這種業務模式是相當變態的,讓幾千萬甚至上億的人在某個早晨的8點鐘同時登錄同時搶票的這種業務模式是變態中的變態。業務形態的變態決定了無論他們怎麼辦幹一定會被罵。
4)為了那麼一兩個星期而搞那麼大的系統,而其它時間都在閒著,有些可惜了,這也就是鐵路才幹得出來這樣的事了。
更新2012年9月27日
Alexa 統計的12306的PV (註:Alexa的PV定義是:一個用戶在一天內對一個頁面的多次點擊只算一次)
(轉載本站文章請註明作者和出處 酷殼 – CoolShell.cn ,請勿用於任何商業用途)
——=== 訪問 酷殼404頁面 以支持公益事業 ===——
Loading ...
*售票網站 Note #002
Cache Dictionary<string, bool> dicUnSelectSeats
1. 從 dicUnSelectSeats取出目前可選座位 (value==true)
2. 選定座位
3. 把 dicUnSelectSeats的 value改為 false
4. 交易成功 continue
交易失敗 value=true
1. 從 dicUnSelectSeats取出目前可選座位 (value==true)
2. 選定座位
3. 把 dicUnSelectSeats的 value改為 false
4. 交易成功 continue
交易失敗 value=true
2013年2月1日
威知 總結 #1
人
威知
Steve - 空降 .NET組 RD主管
其實我沒有真的很討厭 Steve
只是Steve一直窮緊張覺得調查局專案做不完想要我們加班讓我 覺得很煩
Samuel - PM 好人 準備離職
Allen - SA 好人
Hank - RD
Hulk - 研發役 RD
Wallace - 金牌業務 好人
威知烏煙瘴氣到業務親自跑來調查局拜託我
們對調查局的案子多用點心
Steve真是太有才了
Anson - 業務(?) 好人
聽說 Steve之前面試了五六個研發役 不知道會有幾個倒楣蛋進去
調查局 - 承辦人 郭至宜 好人
事
調查局視覺化研析系統
剛到調查局駐點時要求威知派人在測試用機器
上裝 SVN, 拖很久都沒人來, 最後自己裝
時
一個半月
地
威知
旁邊有停車場 30元/天
調查局
拿證件換出入證
要等警衛打電話通知承辦人出來領人進去
物
我還有一雙拖鞋留在威知辦公室
============================== =========================
技術面
- 威知一開始給的程式只有寫好 Master Page的部分
然後裡面有個外部的 function是要檢查調查局人員 AD五碼
想當然沒連上正式資料庫每次檢查都錯誤, 雖然網站還是可以正常使用
重點是這支 function在連不上資料的情況下會跑很久
一開始不知道以為是專案設定的問題
後來才找出來, 把那個地方先註解掉才沒繼續用得一肚子賭爛
不知道 Master Page是誰弄的, 好像都沒發現這個問題似的
- 威知的 RD寫程式好像很喜歡在後台組好 html字串後再透過 Literal元件丟到前台呈現
不知道這樣效率會不會比較好 但是前台呈現很難改
之前在國貿局就看過, 調查局的 Master Page也是如此
- 原本我寫的 DAO每支 function都是獨立運作, 有完整的建立 SqlCommand、SqlConnection、
SqlDataAdapter過程
後來參考 Break的程式, 把 SqlConnection、SqlDataAdapter的部分 extract出來
這樣就只要建立好 SqlCommand, 當作參數傳給撈資料的 function即可 - 在趨勢時就有看到跟 Break一樣用 abstract的寫法
只是還還抓不到感覺什麼時候用這種 pattern會讓程式比較漂亮 - 跟其他組員合作寫程式這算是第一次有比較強的合作感
Break把資料檢索功能的架構都先弄好
我被分配到 工商 的檢索後, 只要照抄其他檢索的內容, 然後在共用的程式處把需要的改一下就好 - 分頁功能我沒有用 ListView + DataPager來做
而是用 SQL切分頁、ListView呈現資料、js做分頁按鈕 - 做 TreeView時, 雖然沒有一定要用 Telerik 的RadTreeView
不過後來看了好幾個免費套件, 相比之下 Telerik的還是好用很多
就一路玩到底, 不愧是要收費的
不過 Telerik的 API沒有寫得很完整 - 威知的專案(國貿局、調查局)在程式碼修改、儲存後就會自動編譯
除非有要 debug追蹤錯誤 不然都不需要按 F5 or Ctrl + F5
不知道怎麼設定的
一開始覺得很方便, 後來覺得很煩
因為隨手按儲存只是怕系統當掉保持的習慣, 不是每次儲存都是因為寫/改好一支功能 - 有個東西我從頭疑惑到尾
.sln專案檔竟然被放在 App_Layout資料夾底下
2013年1月31日
ASP.NET List.Find() with delegate
list.Find(delegate(NavigatorData nvg) { return nvg.name == "Tags"; });
2013年1月30日
SQL TRUNCATE TABLE [...]
跟 DELETE一樣 刪除資料
無法設 WHERE條件, 整張表清空
但是不保留紀錄、自動編號重設
有被 FOREIGN KEY條件限制時, 不可使用
無法設 WHERE條件, 整張表清空
但是不保留紀錄、自動編號重設
有被 FOREIGN KEY條件限制時, 不可使用
2013年1月8日
2013年1月7日
T-SQL 分頁查詢
SELECT TOP <page size> *
FROM (
SELECT
ROW_NUMBER() OVER(ORDER BY CaseId DESC) AS Row
, CaseId, CaseNo, CaseName
FROM dbo.CaseManage
WHERE Creator=@UserId
) Result WHERE ROW > (<page size>*(<page index>-1))
ORDER BY CreateTime DESC;