網站的購物車資料應以何種方式儲存?

購物車的內容要存放在哪裡?雖然這已經是個老問題,但應該也是很多入門開發者會碰到的疑問。

我看到有一篇文章把這個問題整理得很清楚:Think twice about using session state。讀完後覺得內容挺實用,於是狗尾續貂,把那篇文章的重點稍微整理一下。

先說結論:用 session 來儲存購物車內容並不是好方法,甚至比存放在 cookie 裡面還要糟糕。

為什麼?

為什麼購物車內容不該用 Session 來暫存呢?有以下幾個原因:
  1. IIS 的 application pool 預設每隔一段時間會自動回收(recycle),而且還有其他因素可能使回收的次數更頻繁,例如:記憶體或 CPU 負載達到特定上限、用戶端請求數量達到一定上限、額外設定的自動回收時段等等。若使用 in-process session,只要 app pool 一回收,存放在 Session 裡面的資料就沒了。(顧客:怎麼點到下一頁之後,剛才加入購物車的東西都不見了?)
  2. 若採用 out-of-process session,儘管能夠解決上述自動回收 app pool 的問題,卻也額外增加資料傳輸的負擔。這負擔不小,因為 ASP.NET 應用程式在處理每一個 request 時都必須向遠端的儲存體(Windows 服務或 SQL Server)取得 session 的內容,然後在回傳 response 之前又得將目前 session 的內容寫入遠端儲存體。
  3. 存取 session 內容時可能會因為鎖定機制而導致嚴重的效能問題。細節參見黑大文章:再探ASP.NET大排長龍問題

其他解決方案

除了 session 之外,還有以下幾個選項:
  • Cookie
  • 網頁的隱藏欄位(或 ViewState)
  • Web Storage
  • 資料庫(可以是跟應用程式分離的,供購物車模組專用的資料庫)

使用 cookie 的小缺點是它不是那麼安全、穩當,因為使用者可以關閉瀏覽器的 cookie 功能,或任意刪改 cookie 檔案的內容。不過,跟 session 比起來,這些其實都算是小問題。

使用網頁的隱藏欄位來保存購物車內容,得要處理跨網頁共用購物車的問題,不甚理想。

Web Storage 有一點點像 cookie,但 cookie 會隨著每一次瀏覽器發出 HTTP request 時自動夾帶至伺服器端,Web Storage 則不會——它是純用戶端的儲存機制。換言之,這還得要我們寫額外的程式碼來把 Web Storage 的內容傳送給伺服器端應用程式。

最後是資料庫。我覺得這是個好主意。

把購物車內容保存至資料庫中的「購物車資料表」,做法大概是:
  • 以顧客的會員編號當作識別 key,以存取該名顧客的購物車資料。這有點像是自己實作 out-of-process session 機制(仍有差別;稍後說明)。
  • 每當顧客將一項商品加入購物車,便新增一筆資料至購物車資料表。從購物車移除商品時也要刪除對應的資料。
  • 顧客結帳成功後,便清除資料庫中的購物車資料。

此作法可能引發幾點疑慮:

(1) 要寫多少 code?會不會很麻煩?

不會,這類資料處理邏輯很單純,而且現在 ORM 這麼成熟、方便,花不了多少工夫。而且,這個購物車的資料儲存機制還可能實作成容易重複使用的模組。

(2) 這不就跟 out-of-process session 一樣會有跨 process、跨網路的額外資料傳輸負擔嗎?

除非特別將網頁的 session 功能關閉,預設情況下,ASP.NET 應用程式中的每個網頁生命週期都會包含 session 資料傳輸的處理。但如果是自訂的購物車資料庫,則只有跟購物車有關的網頁才會需要額外資料傳輸。

(3) 購物車資料庫裡面會殘留垃圾資料

顧客後來放棄的、沒有結帳的購物車,會殘留在購物車資料庫中。這問題不大,看是每次程式存取時順便清一下,還是建立排程固定清理,都可以。

換個角度來看,保存購物車資料也有好處的。例如使用者可能隔了兩星期之後再度拜訪網站,此時網站的購物車裡面還能顯示上次沒有結帳的東西(或者以提醒的方式呈現),也許顧客會喜歡這項功能。又或者,這些資料可能有統計分析的用處(顧客習慣、關聯分析等等),那麼也可以全部保留(前面提到結帳成功之後要刪除購物車資料的動作可改為「設定刪除旗號」)。

(4) 匿名使用者怎麼辦?

網站要是限定只有會員才能夠買東西,等於是把生意往外推。可是如果允許非會員購物,那就沒有會員編號可作為存取購物車資料的識別 key 了。

有個解決辦法是啟用 ASP.NET 的匿名識別功能。一旦啟用此功能,我們就可以在程式中透過 Request.AnonymousID 來取得 ASP.NET 為每個匿名使用者所產生的唯一編號了。此功能預設使用 cookie 來傳遞匿名編號。

小結
  • 建議用資料庫來存放購物車資料。第二考慮順位是 cookie。(後記:此建議非一體適用,仍有其他因素需考量,請一併參考下方 Joey 兄的留言,會更周延。)
  • 試試 Request.AnonymousID。(.NET 2.0 時代就有了.... Orz)
  • 謹慎使用 session!

1 則留言:

技術提供:Blogger.
回頂端⬆️