系統設計系列文2:設計最簡單的社群網站
上一篇估算出來的流量其實非常小,每秒寫入大概 1~2 次、讀取大概 100 多次,這種規模不需要任何花俏的架構,先做出一個 「堪用」 的版本就好。
這也是這個系列一路會遵守的原則:
不要一開始就把系統蓋大,而是先用最簡單的方式解決問題,避免過度工程(Over-engineering)。
目的:先讓系統能動起來
在加任何元件之前,第一版的目標只有一個:
讓「發文、看 Feed、追蹤」這三件事情能夠正常運作。
延續昨天定出的範疇,這一版先不處理:
- 圖片、影片
- 通知
- 搜尋、推薦
- 大流量、多台機器、分散式問題
先把這些全部砍掉,只留下最核心的骨架。
最簡單可行實作
架構
在只有一台 Server 就能撐住流量的前提下,第一版架構單純到不能再單純,簡直像古早時代的大學留言板:
瀏覽器 -> Server -> Database
↑↓
DNS
使用者在瀏覽器輸入網址,DNS 把網域名稱換成 IP,瀏覽器再把 Request 送到我們唯一的一台 Server,Server 讀寫 Database 之後把結果回傳。

沒有 Load Balancer 也沒有 Cache,如果你不清楚這些元件也沒關係,因為現在完全不需要,這些元件要解決的問題,我們根本還沒遇到,之後會再介紹。
Database Schema
資料表也是先求有再求好,這裡先把每個欄位的資料型別也一併定義出來:
- User
- id:BIGINT(Primary Key)
- email:VARCHAR(255)
- password_hash:VARCHAR(255)
- nickname:VARCHAR(50)
- Post
- id:BIGINT(Primary Key)
- author_id:BIGINT(Foreign Key → User.id)
- content:TEXT
- created_at:TIMESTAMP
- Follow
- id:BIGINT(Primary Key)
- follower:BIGINT(Foreign Key → User.id)
- followee:BIGINT(Foreign Key → User.id)
follower 是發起追蹤的人,followee 是被追蹤的人。如果 Pikachu 追蹤 Charmander,就會多一筆:
follower = Pikachu
followee = Charmander
API 設計
先定義三支最核心的 API。
追蹤使用者
POST /follow
{
"user_id": 123
}
發布貼文
POST /posts
{
"content": "Hello PokeThreads"
}
取得 Feed
GET /feed
這裡故意先不展開 GET /feed 該回傳哪些貼文,明天會針對 Feed 單獨討論,今天只需要知道「這支 API 存在」就夠了。
登入後 Server 怎麼記得是誰?
發文、看 Feed 都建立在一個前提上:Server 要知道現在是誰在發 Request。
目前只有一台 Server,最直覺的做法就是把登入狀態(Session)直接放進這台 Server 的 Memory:
Server
├── Application
└── Session(存在 Memory 裡)
├── Pikachu
├── Charmander
└── Squirtle
流程是這樣的:
- 使用者輸入帳號密碼登入
- Server 驗證成功後,在自己的 Memory 建立一筆 Session,並發一個 Session ID 給瀏覽器(通常放在 Cookie)
- 之後每個 Request,瀏覽器都帶著這個 Session ID
- Server 從自己的 Memory 查到對應的使用者身份
這種「Server 自己保存使用者狀態」的設計,稱為 Stateful Server。
它的好處就是非常簡單,缺點也很明顯:一旦以後多開一台 Server,新 Server 的 Memory 裡完全沒有這筆 Session,不過這也是之後才會遇到的問題,先知道有這問題就好。
業界做法
即使是最陽春的登入機制,業界常見的做法大致分兩派:
- Session-based:像上面這樣,狀態存在 Server 端,瀏覽器只帶一個代號(Session ID)。
- Token-based(如 JWT):狀態直接編碼進一個簽章過的 Token,Server 不需要另外保存任何東西,靠驗證簽章就能確認身份。
在單台 Server、使用者不多的情況下,兩種做法差異不大。
Session-based 的優勢是「要收回權限」很直接(例如踢掉某個使用者,只要刪掉 Server 上的那筆 Session 就好)。
Token-based 則是天生比較適合之後多台 Server 的場景,因為身份驗證不依賴任何一台特定 Server 的記憶體。
這系列先選 Session-based 起步,之後多台 Server 出現時再認真討論。
前端:先分兩塊就好
前端也不用想太多,先切成兩個區塊:
- Feed:負責打 API 拿貼文、把貼文渲染出來
- Post Composer:一個輸入框加一個送出按鈕,負責打
POST /posts
發文流程
使用者輸入文字 -> 按下發布
↓
POST /posts -> Server -> Database
看 Feed 流程
使用者打開頁面
↓
GET /feed -> Server -> Database(取出 Posts)
↑ ↓
└──────── 回傳貼文 ──────┘
會員以及追蹤功能也非常簡單,不贅述了。
與其他元件串接
現在系統裡只有三個角色:瀏覽器、Server、Database,彼此的關係非常直白,Server 是唯一的窗口,所有讀寫都要經過它。
之後不管加 Cache、加 CDN,還是拆多台 Server,這個「Server 對外接 Client,對內接 Database」的基本分工都不會變,改變的只是這兩層各自要不要再增加元件。
小結
到這裡,第一版最陽春的 PokeThreads 已經完成:
- 一台 Server、一個 Database
- 可以發文、看 Feed、追蹤別人
- 登入狀態先用最簡單的 Stateful Server 處理
它撐不住太大的流量,也還沒有任何容錯機制,但它已經是一個能正常運作、而且我們完全理解它為什麼能動的系統。
不過剛剛在設計 GET /feed 的時候,其實刻意跳過了一個問題:
Feed 到底應該回傳哪些貼文?又該用什麼順序排?
這個問題,就留到明天處理。
系列導覽
- 查看全系列:系統設計系列文總覽
