这篇文章是在shopee参与feed项目设计和开发过程的一些思考。
在feed系统设计中,很多设计算是实践了《DDIA》里面的一些论点。
自认为《DDIA》中谈及的理论和在项目中得意实践其中的部分理论,让我在这两年里成长不少。

一.什么是数据系统

现在的互联网产品,大部分都可以归纳为是这样一种数据系统:它使用了多种数据存储系统:mysql/redis/kafka/es/hdfs
涉及到关系数据库,缓存,消息队列,搜索引擎,分布式文件系统等等,如果需要推荐,还可能有hbase等数据存储工具,
每种工具各司其职,分工合作,而代码更像是连接各个系统的粘合剂。

                                                    api                                     
+----------------------------------------------------+--------------------------------------+
|                                                    |                                      |
|      +-----------------+   read request  ....................       asyncjob              |
|      | in-memory cache | --------------- . application code . ------------------+         |
|      +--------+--------+ check if data   ....................                   |         |
|               |          in cache first        |   | cache miss                 |         |
|               |             +------------------+   | or writes                  |         |
|               |             |search request        |                                      |
|               |     +-------+--------+   +---------+--------+           +-------+------+  |
|               |     |   full_text_q  |   |     primary db   |           |    message q |  |
|               |     +-------+--------+   +---------+--------+           +-------+------+  |
|               |             | sync to full text    | capture change             |         |
|               |             +----------+-----------+                        +---+         |
|               |                        |                                    |             |
|               |   sync        ......................              ......................  |   
|               +-------------- .  application code  .              .  application code  .  |
|                               ......................              ......................  |
|                                                                             |             |
+-----------------------------------------------------------------------------+-------------+
                                                                              |
                                                                          thrid api  

以twitter/weibo为代表的feed流产品就是这样一个典型的数据系统产品:
它使用mysql/cassandra来持久化用户数据
使用redis加速访问速度
使用消息队列削峰填谷
使用搜索引擎支持搜索特定用户/内容
使用hbase等列式存储来做推荐/数据分析

二.数据系统系统设计方法论

2.0 数据系统设计方法和软件方法的收敛

对于数据系统设计方法,先抛出结论:
数据系统设计主要可以从三个角度给出问题,然后通过回答这些问题,基本可以得出一个比较合理的架构设计。

  1. 数据模型数据的访问模式
    结构性/半结构性/非结构性
    局部性 or 多对多关系(访问)

  2. 系统负载
    读多写少(负载类型,读写扩散)..
    冷热数据(负载)..
    数据量..
    索引类型..

  3. 数据的正确性和时效性
    正确性:持久化保证
    正确性:一致性保证(限行一致性/顺序一致性/因果一致性…)
    时效性:低时效 == 最终一致

除了上面的三个角度,其实还应该考虑上人的因素,比如团队对于某种数据系统的熟悉程度。

潘家宇的《软件方法》里提供了几个软件建模思路,大体上遵循

  1. 2.1 数据模型和访问模式

在思考数据模型的时候其实需要我们充分理解需求,抽象出领域模型,通过对象关系图(1:1/1:n/n:n),正向反向关系等
区分出核心数据模型和派生的数据模型

数据模型就是判断数据是属于结构性/半结构性还是非结构性(blob)
blob数据不用说,大部分都是使用分布式文件系统存储
半结构性数据,比如一则feed的json

{
    "feedid": "xx",
    "feed_uid": 123,
    "type": 1,
    "source: 2,
    "feed_username": "",
    "content": {
        "voucher_stickers": [{

        }],
        "caption": ""
    },
    "comments": [{

    }]
}

因为这种数据很少需要关联查询,很适合使用mongodb等文档结构模型数据库存储,也可以采用关系型数据库存储
半结构性具有更加好的局部性,有利于一对多关系,不利于多对多关系,也容易造成冗余
结构性(关系型)数据利于表示多对多关系,但是局部性更差

结构性数据库表可以使用一定的犯范式设计,提高了冗余,降低一致性,但是可以提高局部性。

在做feed需求里,明显feed数据更适合使用半结构化的数据库,比如mongodb/cassandra
但是shopee历史原因只支持mysql/tidb做持久化的存储。所以我们的feed内容数据是以非范式化的方式存储在表个的一个字段里的。

2.2 系统负载

思考系统负载的时候其实就是思考系统的用例,系统需要对外提供哪些能力,这些接口具有哪些特性,核心难题在哪里
这时候可以列出系统的关键接口,帮组我们思考。

2.2.1 读负载类型还是写负载类型

负载有很多种,互联网典型的是读多写少,读多,导致了索引计数的诞生
b+树类型的索引的innodb很适合读多写少的场景,但是当读的负载更高,如读放大情况,光靠innodb也不行
这时候需要redis做缓存。

但是使用缓存之后,一般需要代码里双写,那么是使用cache aside模式,是淘汰缓存,都是第三点:数据的正确性和时效性考虑的
这里只是根据负载特性,确定需不需要引入缓存

2.2.2 冷热数据

有时候需要考虑热点数据问题,比如大v发文,评论点赞会比普通人更多,比如新旧数据,新的feed被访问概率大于旧的feed
这些会影响到分表方式,kafka 分区数据等。

2.2.3 数据量

决定了分表的大小和缓存的开销

2.2.4 索引

选用什么类型的索引往往也是负载决定的,
b+树/hash/lsm
全文索引…

2.3 数据的正确性和时效性

数据的正确性和时效性其实算是CA(CAP)的一种选取,需要进一步考虑各个用例的流程图(时序图)。
在分布式系统里,不可靠体现在:

  1. 网络延迟
  2. 节点故障
  3. 网络超时

其中网络超时是网络延迟的另一种形式,因为分布式系统里,识别异常通常通过超时机制来实现,而超时机制的缺点就是网络超时,
此时你不知道请求是否成功到达了另一个节点。所以它是最麻烦的一种异常。
因为种种的不确定性,我们可以使用逐步推断的方式建立一个正确的数据系统(如果一下子假设三种情况都成立,则考虑的分支太多,不利于思考)
然后思考哪些一致性是需要得到保证的

2.3.1 正确性

如持久化保证
顺序保证对于持久化也很重要
比如CDC(change data capture)为核心的数据总线系统,使用binlog解析,导入kafka,消费者消费kafka到异构数据系统实现全文索引/推荐等功能,这时候,消息的顺序就显得很重要
顺序可以通过id发号器来保证,分布式系统中,兰伯特时间序就是很重要的一个结论

2.3.2 时效性

只有强一致性才能保证实时生效,在异构系统里,时效性的保证需要牺牲性能。而且大部分业务场景其实并不需要这么强的时效性,这时候就需要做权衡了。

三.举个栗子

常见的社交场景中的点赞功能落地

四.总结

无论是分布式系统,还是其他架构的设计,都是一种权衡的艺术,分布式面临的很多问题其实在古老的操作系统,数据库中早就有了类似的场景。