第7章内容发布系统——7.2 内容存储设计

7.2 内容存储设计

本节讲解如何进行内容存储设计。

7.2.1 内容数据的存储

正如在7.1节中抛出的第一个问题,内容的表现形式可能是短文字、长文字、图片、音频、视频等,也可能是这些表现形式的组合,我们应该怎样合理地存储内容?

相信有很多读者在学习数据库系统课程设计时都做过类似于博客系统的课程设计项目!这就是一种典型的内容发布系统。不妨回忆一下,当年懵懂的我们是怎么设计数据存储的呢?当年的做法基本上都是很直白地把这些数据作为关系型数据库如MySQL的数据表中的一行来存储的,我们很有可能设计了表7-1所示的数据表。

image-20250430161738739

可以看到,博文数据(博文标题、博文内容主体)和博文的元信息(博文唯一ID、 创作者的用户ID、博文创建时间)被共同存储到数据表的一行中,仅需读取数据表中的一行数据就能完成对博文的读取。但这样的设计仅适合学生级别的项目,并没有真正的工业级内容发布系统的使用价值,其原因如下。

  1. 关系型数据库只支持存储文章这种字符串类型的文本,并不支持存储图片、音频、视频这些文件数据,除非我们一意孤行,将文件数据也粗暴地当作文本存储到关系型数据库中。
  2. 即使是纯字符串类型的长文本存储,采用关系型数据库也很“别扭”。以MySQL的InnoDB存储引擎为例,InnoDB存储引擎的数据表是基于磁盘式B+树来实现的,在正常情况下,数据表的一行数据会被存储到B+树的叶节点;而如果某列文本类型的数据过长,则会占用较大的存储空间,进而严重影响B+树的查询效率。于是,InnoDB存储引擎选择将长文本数据单独存储到计算机磁盘的另一个区域,而非真正插入数据表B+树的磁盘区域,在数据表中仅保留对该磁盘区域的引用,以便在读取数据时根据引用找到数据真正的磁盘存储区域。这样一来,每当读取一行完整的数据时,必然至少涉及B+树的磁盘I/O和另一个文本数据的磁盘I/O,这将导致MySQL读取数据的效率明显下降,且不符合关系型数据库的真正用途。

现在基本上可以得出结论:关系型数据库并不适合存储内容主体

那么,我们应该如何高效地存储内容数据呢?其实,上面提到的InnoDB存储引擎处理长文本的方式,给了我们一定的指引方向,那就是将内容元信息和内容主体分开存储,仅将内容元信息存储到数据库B+树中,将内容主体存储到别处,内容元信息仅保存对内容主体的存储地址的引用。下面我们来进行存储设计。

7.2.2 内容元信息的存储

内容元信息相对格式化,很适合被存储到关系型数据库中进行维护;而内容主体数据可能是文本、图片、音频、视频等各种形式或者它们的组合,这时使用关系型数据库就显得非常低效了,我们只能借助具有不同存储优势、不同存储原理模型的存储系统来存储这些数据(7.2.3节会详细讲解这些内容)。本节先来设计并确定适合存储到关系型数据库中的内容元信息的数据模型。

对内容元信息进行存储需要如下两张数据表相互配合。

  • 信息表item info:存储真正决定对外发布的内容元信息,包括创作者ID、内容主体的存储地址、版本控制和审核管理(在7.3节中会进行详细解读)。
  • 内容修改历史记录表item record:用于应对内容的修改操作,每次修改后的内容数据都先被暂存到这里,以便提供内容修改历史记录的查询途径。更重要的是,等待此内容修改被审核通过后再将其回写到信息表。

item info表需要拥有的字段包括但不限于表7-2中列出的这些。

image-20250430162935081

其中有一些字段非常重要,具体说明如下。

  • item_id是一条内容的唯一标识,要求全局唯一。当然,要使用分布式唯一ID生成器系统。
  • online_version:线上内容的版本号,用于在修改内容时进行版本控制。
  • online_image_uris:如果内容包含图片,则此字段表示已成功发布的图片URI列表。
  • online video id:如果内容包含视频,则此字段表示已成功发布的视频ID。
  • online_text_uri:当内容包含长文本时,此字段表示已成功发布的文本URI。
  • latest_version:表示创作者最近一次改动内容后的版本号,创作者每改动一次内容,版本号就加1。
  • visibility:创作者设置的内容可见范围。
  • status:已成功发布的内容的生效状态,可能是待审核、正常展示、被删除、被下架。

item_record表需要拥有的字段(包括但不限于)如表7-3所示。

image-20250430163228974

item_record表存储用户变更内容的记录,每行数据都代表一次变更记录。其中,latest_version、latest_status、latest_image_uris、latest_video_id、latest_text_uri字段非常重要。

为什么对内容元信息使用了两张数据表?这是因为每次变更内容后,都不一定能立刻将内容发布上线。

  • item_info表的主要作用是存储此内容的基本信息,直接对接每个用户读取的内容元信息;
  • item_record表的主要作用是存储内容变更记录,待内容审核通过后再将变更记录替换到item_info表中,相当于暂存待生效的内容元信息。

你可能对这里的数据表及其字段的含义云里雾里,但没关系,7.4节会讲解内容的创建、修改、删除、下架、审核结果回调流程,让你深刻理解其含义。

7.2.3 内容主体的存储选型

各种常见的非关系型数据库的特点如下。

  • 内存型KV数据库:典型的代表是Redis。由于内存资源较为珍贵,所以它仅适合用来存储较短的文本,且考虑到内存数据的易失性,数据的可持久化表现一般。
  • 分布式KV存储系统:比如基于RocksDB存储引擎的上层应用存储系统Cassandra、TiDB等,有较好的可扩展性和数据可持久性,且由于依赖较为廉价的磁盘资源,所以它很适合用来存储各种短文本数据。
  • 分布式文件存储系统:比如Google的GFS、开源项目HDFS等,可被视为操作系统内文件系统的分布式上层应用,以文件目录结构形式管理文件数据,天然支持文件存储。
  • 分布式文档数据库:文档数据库指可以存放XML、JSON、BSON类数据段的数据库,典型的代表是MongoDB。MongoDB以文档形式存储XML、JSON、BSON数据,文档相当于数据表中的一行数据,而文档的集合(Collection)相当于数据表。MongoDB底层基于分布式文件系统管理文档,提供了较为易用的类SQL查询功能。
  • 分布式对象存储系统:比如Amazon公司的S3、开源项目Swift等。根据笔者的理解,分布式对象存储其实更像对分布式文件系统的优化,不再以目录形式访问文件,而是以更高效的Key-Value形式访问文件,天然支持文件存储。

通过对各种非关系型数据库的对比,我们可以得到适合各种内容表现形式的存储选型。

  • 短文本:可以使用分布式KV存储系统和分布式文档数据库,两者都是比较适合的存储选型。如果更在意易用性的话,则可以使用分布式文档数据库。
  • 长文本/图片/音频/视频:这些数据都是静态的,更适合使用分布式文件存储系统或者分布式对象存储系统来存储。为了提高易用性和文件存取效率,可以使用分布式对象存储系统。分布式对象存储系统会为所存储的文件返回其唯一URI,方便我们使用URI从中获取文件。

我们最终选定使用分布式KV存储系统存储内容的短文本使用分布式对象存储系统存储长文本文件、图片文件及音视频文件

7.2.4 音视频转码

如果用户上传的内容包含音视频,那么一般需要进行音视频转码。这里要特别讲一下视频转码的概念:视频转码指将已经编码的视频码流转换成另一种视频码流,以适应不同的网络带宽、终端处理能力和用户需求。转码在本质上是先解码再编码的过程,因此转换前后的码流可能遵循相同的视频编码标准,也可能不遵循相同的视频编码标准。转码后的视频和原视频在内容上完全一致。

视频转码一般具有如下好处。

  • 提高兼容性:我们可以通过视频转码技术降低视频的分辨率,进而降低网络带宽压力,提高视频在不同网络条件下的整体播放率。此外,用户的设备千差万别,存在某些视频编码不支持解码播放的情况,将视频转码为用户的设备可解码播放的视频格式,可提高视频在不同的设备间解码播放的兼谷性。
  • 提供多种画质:假设创作者上传了一段1080P的视频,那么我们可以将它转码成各种画质版本,如360P、480P、720P等,用户在播放视频时可以根据网络情况选择调整视频画质。我们还可以引入一些营收策略,比如仅会员才可播放1080P的画质版本。
  • 版权保护与内容分析:视频转码会对视频做图像处理,比如出于版权保护的目的,为视频加产品专属水印、创作者ID声明,以便将用户引流到产品和创作者;还可以对视频关键帧进行截取,进一步做视频封面选择、视频特征识别、视频鉴黄审核等工作。

将视频上传到后台之后,专门的视频转码服务会将原视频转码为多种画质及编码格式的新视频,并为视频添加水印。至于具体是怎么转码视频的,读者可以自行阅读相关资料。

总结

为什么关系型数据库并不适合存储内容主体呢?

  1. 关系型数据库只支持存储文章这种字符串类型的文本,并不支持存储图片、音频、视频这些文件数据,除非我们一意孤行,将文件数据也粗暴地当作文本存储到关系型数据库中。
  2. 即使是纯字符串类型的长文本存储,采用关系型数据库也很“别扭”。以MySQL的InnoDB存储引擎为例,InnoDB存储引擎的数据表是基于磁盘式B+树来实现的,在正常情况下,数据表的一行数据会被存储到B+树的叶节点;而如果某列文本类型的数据过长,则会占用较大的存储空间,进而严重影响B+树的查询效率。于是,InnoDB存储引擎选择将长文本数据单独存储到计算机磁盘的另一个区域,而非真正插入数据表B+树的磁盘区域,在数据表中仅保留对该磁盘区域的引用,以便在读取数据时根据引用找到数据真正的磁盘存储区域。这样一来,每当读取一行完整的数据时,必然至少涉及B+树的磁盘I/O和另一个文本数据的磁盘I/O,这将导致MySQL读取数据的效率明显下降,且不符合关系型数据库的真正用途。

如何高效地存储内容数据呢?

  • 将内容元信息和内容主体分开存储,仅将内容元信息存储到数据库B+树中,将内容主体存储到别处,内容元信息仅保存对内容主体的存储地址的引用。

对内容元信息进行存储需要如下两张数据表相互配合?

  • 信息表item info:存储真正决定对外发布的内容元信息,包括创作者ID、内容主体的存储地址、版本控制和审核管理(在7.3节中会进行详细解读)。
  • 内容修改历史记录表item record:用于应对内容的修改操作,每次修改后的内容数据都先被暂存到这里,以便提供内容修改历史记录的查询途径。更重要的是,等待此内容修改被审核通过后再将其回写到信息表。

为什么对内容元信息使用了两张数据表?

这是因为每次变更内容后,都不一定能立刻将内容发布上线。

  • item_info表的主要作用是存储此内容的基本信息,直接对接每个用户读取的内容元信息;
  • item_record表的主要作用是存储内容变更记录,待内容审核通过后再将变更记录替换到item_info表中,相当于暂存待生效的内容元信息。

各种常见的非关系型数据库的特点?

  • 内存型KV数据库:典型的代表是Redis。由于内存资源较为珍贵,所以它仅适合用来存储较短的文本,且考虑到内存数据的易失性,数据的可持久化表现一般。
  • 分布式KV存储系统:比如基于RocksDB存储引擎的上层应用存储系统Cassandra、TiDB等,有较好的可扩展性和数据可持久性,且由于依赖较为廉价的磁盘资源,所以它很适合用来存储各种短文本数据。
  • 分布式文件存储系统:比如Google的GFS、开源项目HDFS等,可被视为操作系统内文件系统的分布式上层应用,以文件目录结构形式管理文件数据,天然支持文件存储。
  • 分布式文档数据库:文档数据库指可以存放XML、JSON、BSON类数据段的数据库,典型的代表是MongoDB。MongoDB以文档形式存储XML、JSON、BSON数据,文档相当于数据表中的一行数据,而文档的集合(Collection)相当于数据表。MongoDB底层基于分布式文件系统管理文档,提供了较为易用的类SQL查询功能。
  • 分布式对象存储系统:比如Amazon公司的S3、开源项目Swift等。根据笔者的理解,分布式对象存储其实更像对分布式文件系统的优化,不再以目录形式访问文件,而是以更高效的Key-Value形式访问文件,天然支持文件存储。

适合各种内容表现形式的存储选型?

  • 短文本:可以使用分布式KV存储系统和分布式文档数据库,两者都是比较适合的存储选型。如果更在意易用性的话,则可以使用分布式文档数据库。
  • 长文本/图片/音频/视频:这些数据都是静态的,更适合使用分布式文件存储系统或者分布式对象存储系统来存储。为了提高易用性和文件存取效率,可以使用分布式对象存储系统。分布式对象存储系统会为所存储的文件返回其唯一URI,方便我们使用URI从中获取文件。

什么是视频转码?

  • 视频转码指将已经编码的视频码流转换成另一种视频码流,以适应不同的网络带宽、终端处理能力和用户需求。
  • 转码在本质上是先解码再编码的过程,因此转换前后的码流可能遵循相同的视频编码标准,也可能不遵循相同的视频编码标准。转码后的视频和原视频在内容上完全一致。