5. Design User System
info
设计用户系统,实现功能包括注册、登录、用户信息查询、好友关系的存储
Overview
- Step1. Scenario 场景
- 需要设计哪些功能,有哪些场景
- Ask / Features / QPS / DAU / Interfaces
- Step2. Service 服务
- 将大系统拆分为小服务
- Split / Application / Module
- Step3. Storage 存储
- 数据如何存储和访问
- Schema / Data / SQL / NoSQL / File System
- Step4. Scale 升级
- tradeoff 取舍并解决缺陷,处理可能遇到的问题
- Sharding / Optimize / Specical Case
Step1. Scenario
Assumption and QPS
- 100M DAU
- QPS of
register
+log in
+update info
- Each user:
register
+log in
+update info
= 0.1 time/day = 1 time/10days - 100M * 10% / 86400s/day = 100/day
- Peak = 100 * 3 = 300/day
- Each user:
- QPS of
search
- Each user: 与查询用户信息相关的操作次数(查看好友 发信息 更新消息主页 = 100 times/day
- 100M * 100 / 86400s/day = 100k/day
- Peak = 100k * 3 = 300k/day
Read heavy system
Step2. Service
AuthenticationService
登录注册服务UserService
用户信息存储与查询服务FriendshipService
好友关系存储服务
Authentication Service
Session 会话
- Session 记录过期以后,服务器会主动删除么? 不会.
- 只支持在一台机器登陆和在多台机器同时登陆的区别是什么? 多台机器登录时创建多个
session_key
. - Session 适合存在什么数据存储系统中? SQL DataBase + Cache 加速访问.
Friend Service
单向好友关系
Twitter, Instagram, weibo
- 存于 SQL DataBase
-- 查询x所有的关注对象
select * from friendship where from_user_id = x;
-- 查询x所有的粉丝
select * from friendship where to_user_id = x;
双向好友关系
Wechat, Facebook, WhatsApp
- 方案 1: 存储为一条数据
- 为什么区分 smaller / bigger?
- NoSQL 很多不支持 Multi-index 不能使用这种方案
select * from friendship
where small_user_id = x or bigger_user_id = x;
- 方案 2: 存储为两条记录
- NoSQL, SQL 都可以按照这种方案
select * from friendship where from_user_id = x;
Step3. Storage
Cache
E.g., UserService 操作
class UserService:
def getUser(self, user_id):
key = "user::%s" % user_id
user = cache.get(key)
if user:
return user
user = database.get(user_id)
cache.set(key, user)
return user
def setUser(self, user):
key = "user::%s" % user_id
cache.delete(key)
database.set(user)
多线程多进程下的数据不一致问题
Solution
class UserService:
def getUser(self, user_id):
key = "user::%s" % user_id
user = cache.get(key)
if user:
return user
user = database.get(user_id)
cache.set(key, user)
return user
def setUser(self, user):
key = "user::%s" % user_id
# change the action order
database.set(user)
cacahe.delete(key)
Cache handles data consistency
- 巧妙利用 cache 的 ttl(time to live / timeout) 机制。任何一个 cache 中的 key 都不要永久有效,设置一个短暂的有效时间,如 7 天
- 那么即便在极低概率下出现了数据不一致,也就最多不一致 7 天。即,我们允许数据库和缓存有“短时间”内的不一致,但最终会一致 Eventual consistency
Cache Aside
- Web Server 分别与 DB 和 Cache 进行沟通
- DB 和 Cache 直接不直接沟通
- 业界 E.g., Memcached + MySQL
- 是业界使用较多的方案,Cache 和 DB 可以自由的搭配组合
Cache Through
- Web Server 只和 Cache 沟通
- Cache 负责与 DB 通信,将数据持久化
- 业界 E.g., Redis (可以理解为 Redis 里包含了 Cache + DB)
- Cons: Redis 支持单纯的 key-value 存储结构,无法适应复杂的应用场景
Cassandra - 典型 NoSQL 数据结构
Cassandra 是一个三层结构的 NoSQL 数据库
- 第一层: row_key
- 又称为 Hash Key, Partition Key
- Cassandra 会根据这个 key 算一个 hash 值, 然后决定整条数据存储在哪儿
- 无法进行 Range Query
- 常用:
user_id
- 第二层: column_key
- 用于排序, 如支持"范围查询"
query(row_key, column_start, column_end)
- 可以是复合值,如
timestamp + user_id
- NoSQL 的 column 是动态的,无限大,可以随意添加
- 用于排序, 如支持"范围查询"
- 第三层: value
- 一条数据一般以 grid 为单位,
row_key + column_key + value = 一条数据
- 一条数据一般以 grid 为单位,
Cassandra - Friendship Table 存储
Cassandara - NewsFeed 存储
DataBase 选择原则
- 需要支持 Transaction 的话不能选 NoSQL
- 擅长的方面
- SQL: 结构化数据,自由创建索引
- NoSQL: 分布式,Auto-scale,Replica
- 同时使用多种数据库系统,不同的表单放在不同的数据库里
User table
选择 SQL: safety, 信任度, Multi-Index.Friendship table
选 择 NoSQL: 数据结构简单, 都是 key-value 的查询/存储需求, NoSQL 效率更高.