架构设计
和朋友交流的一些开放性设计问题的记录,因为是开放性问题,所以肯定是没有唯一答案的,这里只是记录我们讨论时认为比较好的回答。
架构设计的思路
分析思路 - 4S分析法
- Scenario场景 - 系统要设计到什么程度
需要设计哪些功能?
设计到什么程度?并发用户数、读频率、写频率、QPS - Service服务
系统拆分
大服务拆小服务 - Storage存储
数据结构、怎么存储 - Scale升级
可能遇到的问题及如何解决
怎么保证高性能
怎么保证高可用
场景设计 - 缩小范围、有的放矢
服务 - 系统拆分、大事化小
服务拆分的优势:
- 各功能模块解耦,保证单一职责;
- 系统简单,升级某个服务不影响其他服务;
- 扩展性强,可对某个服务进行单独扩容和缩容;
- 各个部门协作更清晰;
- 故障隔离,某个服务出故障不会影响其他服务的可用性;
- 可根据不同服务的特点选择技术架构或语言;
- 数据库独立,互不干扰。
朋友圈
用户在朋友圈里发言其实有点像发出话题(Topic),别人可以在这个话题下评论、回复评论,但是和普通的帖子系统的区别是,朋友圈中的话题、评论都只能供朋友之间查看。
要求
- 用户关系
- 评论的层级关系
- 查询
如果判断一个用户能看到哪些话题、及其下的评论?
如果朋友关系发生了变更(增加、删除)怎么做? - 推送
有朋友发出了新话题或评论,这时需要告知用户,上一条“查询”要求根据用户查话题和评论,而“推送”相当于反查:根据话题和评论查应该接收推送的用户。
表结构设计
我们看下上面的表结构如何解决问题:
- 用户关系
用户关系使用一张用户关系表来维护,但要注意的是用户关系是双向的,你是我的朋友、我也是你的朋友,因此创建朋友关系时要同时插入A->B和B->A两条记录,这么存还有一个好处是可以根据userId1进行分表,当要查询某个人的朋友时只需要过滤表中第一个userId字段即可。 - 评论的层级关系
评论表中有一个parentId字段,表示一个评论与另一个评论存在父子关系。 - 查询
查询主要是查某个用户在对应话题下属于他朋友的评论。
首先查用户的朋友,查一次用户关系表即可,因为用户关系表根据userId1分表,因此效率不会太差。
然后根据朋友userId查评论表,也是一次查询即可得到结果。 - 推送
与查询功能同理。
可以使用Redis实现吗?
和朋友讨论了快1个小时,最后结论是用Redis实现并不合适,至少上边提到的这些需求并不适合使用Redis实现。而且,MySQL效率也并不低,并且就算达到性能上限也可以通过分表来进行优化。
at
微信中就提供了at功能。
- 发出的消息有@时会在@后面带上目标人物的标识、所处的群;
- 在服务端存储@结构时,记录<from, to, group>到@表;
- 客户端长轮询的时候,根据to和group查询@表并返回;
- 客户端查看完毕后,将已读的@记录传到服务端,记录已读。
知识库
以我理解,知识库就是一个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就是热点)?
注意还要考虑网络的性能。