尽管此刻没有如愿以偿找到合适的ft,但我希望能够充分认识到自己的不足,积蓄力量。相信在不久的将来会有爆发的机会。

---------------------------------------------------------------------------------------------

知识的广度(component, 目的)和深度(sde1: 3年以内,sde2: 4年以上,sde3:team lead,org impact可小可大,深度,3+:org lead, 领域见解)

 

设计用户系统,实现功能包括注册、登陆、用户信息查询,好友关系存储

Scenario:

注册,登陆,查询(需求量大),用户信息修改

支持100M DAU

注册,登陆,信息修改QPS

  100M * 0.1 / 86400 ~100

  0.1 = 平均每个用户每天登陆+注册+信息修改

  peak = 100 * 3 = 300 -> Mysql

查询的QPS

  100M * 100 / 86400 ~100k

  100 = 平均每个用户每天查询用户信息相关的操作次数(查看好友,发信息,更新消息主页)

  peak = 100k * 3 = 300k ->Redis

Service:

  一个authenticationService负责登陆注册

  一个userService负责用户信息存储和查询

  一个friendshipService负责好友关系存储

Storage:

MySql/PosgreSql等SQL数据库性能: 1k QPS

MongoDB/Cassandra等硬盘型Nosql数据库性能:10k QPS

Redis/Memcached等内存型Nosql数据库性能:100k~1m QPS

用户系统特点:读非常多,写非常少。读多写少,一定用cache进行优化。

Cache: 把之后要查询的东西先存一下。无需重新计算和存取数据库。可以理解为hashmap, key-value结构。提供协议,淘汰机制(LRU/LFU cache)

  • Memcached(纯内存不支持数据存储化,断电后数据没有)
  • Redis(支持数据持久化)

Cache不一定在内存,没有指定存在怎样的存储介质中。访问远端数据/计算量大的工作file system可以做cache, cpu也有cache。

Cache server cache, frontend/client/browser有客户端cache。

 

Memcached

cache.set("key","value")

cache.get("key")

cache.set("foo",1,ttl=60) 这个key只能活60s  

out of memory key may be evicted by cache

 

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(key,user)

数据库和cache不一致问题,出现脏数据dirty data

多线程多进程下的数据不一致: process1: setUser, cachedelete()   process2: getUser cache.get() //none db.get() cache.set() // 更新老数据 process 1: db.set() // 写入新数据

解决方法:数据库和缓存加锁不可以,因为是两台机器,两套系统,要用分布式锁,会导致存取效率降低,得不偿失。

best practice:

  database.set(key,user)

  cache.delete(key)

多线程多进程下的数据不一致: process1: setUser, db.set()   process2: setUser db.set() // 更新老数据 process 1.2: cache.delete() 

process1: setUser, db.set()新数据 cache.delete()失败老数据

原因:读多写少,cache heat rate:98%+

cache ttl 7天,基地概率下出现数据不一致,也就最多不一致7天

 

写多读少,写时删除cache, 读取时cache miss,无读的优化效果。写改db,多db,分摊写请求

 

cache aside: db <-> web server<-> cache      memcached + mysql

cache through: web server <-> [cache <-> DB]           redis只支持单纯的kv存储结构,无法适应复杂的应用场景

 

Authentication service (session, cookie)

session table(存在cache (+ db))

session_key       string      //全局唯一,uuid

user_id         foreign key // 指向user table

expire_at      timestamp //什么时候过期

<device_key>

  • login以后创建session对象
  • 并把session_key返回给浏览器,让浏览器存储起来
  • 浏览器将该值记录在浏览器的cookie中(哈希表)
  • 用户每次向服务器发送的访问,都会自动带上该网站所有的cookie
  • 此时服务器拿到cookie中的session_key,在session table中检测是否存在,是否过期

 

friendship service

单向

friendship table

from_user_id    foreign key 用户主体

to_user_id    foreign key 被关注的人

select * from friendship where from_user_id = x 查关注对象

select * from friendship where to_user_id = x 查粉丝

 

双向

smaller_user_id     bigger_user_id

1          2

存储容量小一半,查询 select * from friendship where smaller_user_id = x or bigger_user_id = x 效率慢

or

from_user_id    to_user_id

1          2

2          1

select * from friendship where from_user_id = x查询快,时间要求》空间要求

写入事务操作,同时成功同时失败

 

Cassandra为例的nosql数据库

第一层:row_key

第二层:column_key

第三层:value

Cassandra 的key = row_key + column_key, 只对应一个value

结构化信息serialize到value存储 

row key/hash key/partition key/sharding key: 数据存在哪个机器上的依据,nosql实现分布式多台机器存储;常用:user_id

inset(row_key, column_key, value)

column key: 是排序的,row_key等于x, 进行范围查询query(row_key, column_start, column_end); 可以是复合值,如timestamp + user_id

 

sql: 以row为单位(取出row作为一条数据),每一列是一种属性

nosql: column是动态的,无限大,以格子为单位,row_key + column_key + value = 一条数据

 

friendship table在cassandra存储

row key:                 user_id1                           user_id2

column key:     friendship_user_id2            friendship_user_id3         friend_user_id1

value:  is_mutual_friend, is_blocked, timestamp is_mutual_friend, is_blocked, timestamp    

 

newsfeed在cassandra存储

row_key:            owner_id1

column_key:  created_at1,tweet_id1  created_at2,tweet_id2      拿tweet_id从cache里读数据

value:      tweet_data1        tweet_data2

 

数据库选择原则1: 大部分情况,sql, nosql都可以

数据库选择原则2: 支持transaction,不能选nosql。在一台机器上操作。

数据库选择原则3: sql-结构化数据,自由创建索引。nosql-分布式,auto-scale自动拓展,replica

数据库选择原则4: 一个网站同时用多种数据库系统。不同的表单放在不同的数据库

 

user table - sql: 信任度, multi-index

friendship - nosql: 结构简单,key-value的查询/存储需求,nosql效率更高

 

paper:

dynamo db

scaling memcache at fb

couchbase architecture

LFU

 

练习1: nosql存单项好友关系:需要两张表单,一张存粉丝,一张存关注。

redis:

key = user_id

value = set of friend_user_id(粉丝表就是粉丝ID,关注表就是关注id) 相当于hashset

查a是否关注了b: redis的sismember查询b是否在value(a关注的人)中

 

Cassandra:

row_key = user_id

column_key = friend_user_id(粉丝表就是粉丝ID,关注表就是关注id) 

value=其他想同时存储的东西,如关注时间

查a是否关注了b: 在关注表中查询row_key = A, column_key = B的数据是否存在

 

练习2: nosql 存储user。如果使用不支持multi-index的nosql存储user。如何同时支持按照email, username, phone, id来检索用户。

redis: key = user_id, value = 用户信息

Cassandra: row_key = user_id, column_key = 任何信息,value = 其他用户信息

创建表单用作index,去查到user_id

redis: key = email/phone/username, value = user_id

Cassandra: row_key = email/phone/username, column_key = 任何信息,value = 其他用户信息

 

练习3: A和B之间的共同好友

  1. A的好友
  2. B的好友
  3. 求交集

读多写少,使用缓存存储用户的好友列表,而不是两次数据库查询

 

练习4: 好友几度关系BFS

  细化需求:

  • 查询你和不超过10个人之间的关系
  • 用户数量集>100M
  • 平均好友数1000个
  • 期望的db query次数为常数级(<20次query)
  • >3度没有现实意义 3+

 双向BFS,

a查好友,一度

b查好友,一度

查并集,二度

offline提前算好所有的一度和二度并存储在nosql,

A  一度 - 1次db

  二度 - 1次db   交集3度

B  一度 - 1次db  

posted on 2024-01-09 09:26  dddddcoke  阅读(4)  评论(0编辑  收藏  举报