架构设计

和朋友交流的一些开放性设计问题的记录,因为是开放性问题,所以肯定是没有唯一答案的,这里只是记录我们讨论时认为比较好的回答。

架构设计的思路

分析思路 - 4S分析法

  1. Scenario场景 - 系统要设计到什么程度
    需要设计哪些功能?
    设计到什么程度?并发用户数、读频率、写频率、QPS
  2. Service服务
    系统拆分
    大服务拆小服务
  3. Storage存储
    数据结构、怎么存储
  4. Scale升级
    可能遇到的问题及如何解决
    怎么保证高性能
    怎么保证高可用

场景设计 - 缩小范围、有的放矢

服务 - 系统拆分、大事化小

服务拆分的优势:

  • 各功能模块解耦,保证单一职责;
  • 系统简单,升级某个服务不影响其他服务;
  • 扩展性强,可对某个服务进行单独扩容和缩容;
  • 各个部门协作更清晰;
  • 故障隔离,某个服务出故障不会影响其他服务的可用性;
  • 可根据不同服务的特点选择技术架构或语言;
  • 数据库独立,互不干扰。

朋友圈

用户在朋友圈里发言其实有点像发出话题(Topic),别人可以在这个话题下评论、回复评论,但是和普通的帖子系统的区别是,朋友圈中的话题、评论都只能供朋友之间查看。

要求

  1. 用户关系
  2. 评论的层级关系
  3. 查询
    如果判断一个用户能看到哪些话题、及其下的评论?
    如果朋友关系发生了变更(增加、删除)怎么做?
  4. 推送
    有朋友发出了新话题或评论,这时需要告知用户,上一条“查询”要求根据用户查话题和评论,而“推送”相当于反查:根据话题和评论查应该接收推送的用户。

表结构设计

朋友圈
我们看下上面的表结构如何解决问题:

  1. 用户关系
    用户关系使用一张用户关系表来维护,但要注意的是用户关系是双向的,你是我的朋友、我也是你的朋友,因此创建朋友关系时要同时插入A->B和B->A两条记录,这么存还有一个好处是可以根据userId1进行分表,当要查询某个人的朋友时只需要过滤表中第一个userId字段即可。
  2. 评论的层级关系
    评论表中有一个parentId字段,表示一个评论与另一个评论存在父子关系。
  3. 查询
    查询主要是查某个用户在对应话题下属于他朋友的评论。
    首先查用户的朋友,查一次用户关系表即可,因为用户关系表根据userId1分表,因此效率不会太差。
    然后根据朋友userId查评论表,也是一次查询即可得到结果。
  4. 推送
    与查询功能同理。

可以使用Redis实现吗?

和朋友讨论了快1个小时,最后结论是用Redis实现并不合适,至少上边提到的这些需求并不适合使用Redis实现。而且,MySQL效率也并不低,并且就算达到性能上限也可以通过分表来进行优化。

at

微信中就提供了at功能。

  1. 发出的消息有@时会在@后面带上目标人物的标识、所处的群;
  2. 在服务端存储@结构时,记录<from, to, group>到@表;
  3. 客户端长轮询的时候,根据to和group查询@表并返回;
  4. 客户端查看完毕后,将已读的@记录传到服务端,记录已读。

知识库

以我理解,知识库就是一个WIKI系统,它有不同的命名空间和层次化的目录,每个节点都对应一篇文档。
类似产品如Confluence。
存储上可以使用一张表来记录文档的结构,表字段包括:<dirId, dirName, parentDirId, parentDirName, docUrl>;

  • 查询下级目录时,根据当前目录节点的dirId查询parentDirId字段上的索引,将子目录都带出来;
  • 创建一个目录时,除了往数据库里插入一条目录记录外,还需要在文件系统里创建一个文档,然后记录该文档的docUrl到表里;
  • 删除一个目录时,级联删除子目录;
  • 移动目录时,更新其parentDirId字段。

协同文档

文档存储

实时通信

利用long pull(长连接) 或 WebSocket
服务端利用Netty提供服务,并设计自定义协议。

编辑冲突解决

编辑锁

GNU diff-patch

Myer’s diff-patch

Operational Transformation

分布式 Operational Transformation

缩略图

文字翻译

秒杀 - 抢红包

场景:资金池10W,每天2W,持续5天,如果前面4天有抢剩下的加到第5天的资金池里;红包的钱从资金池中拿,每个红包随机0~1元;很多用户(不知道多少,可能几万、几十万)抢,QPS>2W,每个人30个红包,其中10个是有钱的。

  • 怎么存资金池?
    用redis存资金,红包都是从redis扣,扣减的过程用Lua脚本实现原子性。

  • 怎么发红包?

  • 资金怎么保证可靠性(高可用)?
    持久化,MySQL负载能力低不行,可以用Redis的持久化+复制。
    那Redis挂了怎么办?

  • 扣钱怎么保证效率(高性能)?
    热点key问题怎么解决(资金key就是热点)?
    注意还要考虑网络的性能。