Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

消息体存储调研

docs/message.livemd

消息体存储调研

消息体的存储的诉求

功能满足以下条件

  1. 能够支持大量的数据存储,72T的数据
  2. 能够接收的QPS为6W,其中5W为写入,1W为读取
  3. 消息的使用方式一个KV的形式即可,能够根据给定的消息ID获取对应的消息体
  4. 每个独立的消息体具有独立的过期时间
  5. 消息ID是包含有时间戳信息的,具有时间戳概念

备选方案

  1. tablestore
  2. oss

OSS的调研

简介

阿里云对象存储OSS(Object Storage Service)是一款海量、安全、低成本、高可靠的云存储服务,可提供99.9999999999%(12个9)的数据持久性,99.995%的数据可用性。多种存储类型供选择,全面优化存储成本。

由于对象存储系统通常使用扁平的命名空间而不是传统的文件系统层次结构,管理海量数据变得更加简单。无需处理复杂的文件系统限制,如文件夹大小或文件数目限制。用户可以通过简单的API调用来存储和检索数据,这使得集成和自动化更加容易。

  1. 数据持久性: 99.9999999999%(12个9)
  2. 数据可用性: 99.995%
  3. 存储类型: 标准存储、低频访问存储、归档存储、深度归档存储
  4. 存储成本: 根据存储类型和数据量计算
  5. 简化管理: 无需处理复杂的文件系统限制,如文件夹大小或文件数目限制

对象存储的工作原理是什么

  1. 对象存储系统通过RESTful API响应请求,支持PUT、GET、POST和DELETE操作。
  2. 系统采用分布式设计,数据跨多个物理硬件存储,底层存储节点协同工作。
  3. 内部使用分布式哈希表(DHT)技术定位数据对象。
  4. 数据对象被分配到不同的存储节点,支持数据复制或纠删码提高持久性。
  5. 读取数据时,客户端通过对象ID快速定位并获取对象。
  6. 通过复制或纠删码技术保证数据的高可用性和容错性。

阿里云对象存储介绍

主要概念

  • 对象

对象是OSS存储数据的基本单元,也被称为OSS的文件。和传统的文件系统不同,对象没有文件目录层级结构的关系。对象由元数据(Object Meta)、用户数据(Data)和文件名(Key)组成,并且由存储空间内部唯一的Key来标识。对象元数据是一组键值对,表示了对象的一些属性,例如文件类型、编码方式等信息,同时用户也可以在元数据中存储一些自定义的信息。

  • 对象名称

在各语言SDK中,ObjectKey、Key以及ObjectName是同一概念,均表示对Object执行相关操作时需要填写的Object名称。例如向某一存储空间上传Object时,ObjectKey表示上传的Object所在存储空间的完整名称,即包含文件后缀在内的完整路径,如填写为abc/efg/123.jpg

  • 通过生命周期规则自动转换Object的存储类型

基于最后一次修改时间的存储类型转换,当Bucket同时配置了转换为低频访问、归档存储、冷归档存储以及深度冷归档存储的策略时,其转换周期必须满足以下条件:转换为低频访问的周期<转换为归档的周期<转换为冷归档的周期<转换为深度冷归档的周期

操作

上传文件

如何降低PUT类请求费用

如果要上传的文件数量较多,直接指定上传的文件类型为深度冷归档类型会造成较高的PUT类请求费用。建议您先将文件的存储类型指定为标准存储进行上传,然后通过生命周期规则将其转储为深度冷归档类型,从而降低PUT类请求费用。

下载文件

管理文件元数据

对象存储OSS存储的文件(Object)信息包含Key、Data和Object Meta。Object Meta是对文件的属性描述,包括HTTP标准属性(HTTP Header)和用户自定义元数据(User Meta)两种。您可以通过设置HTTP标准属性来自定义HTTP请求的策略,例如文件(Object)缓存策略、强制下载策略等。您还可以通过设置用户自定义元数据来标识Object的用途或属性等。

Expires字段能够设置文件的过期时间。

用户自定义元数据

您可以在上传Object时,为Object添加自定义元数据(User Meta),用于标识Object的用途或属性等。

  • 一个Object可以有多个自定义元数据,但所有的自定义元数据总大小不能超过8 KB。

  • 自定义元数据是一组键值对,元数据名称必须以x-oss-meta-开头。例如x-oss-meta-last-modified:20210506,可用于记录本地文件最后修改时间为2021年5月6日。

  • 调用GetObject或者HeadObject接口时,将在HTTP头部返回自定义元数据。

管理文件元数据

生命周期

您可以基于最后一次修改时间(Last Modified Time)以及最后一次访问时间(Last Access Time)的策略创建生命周期规则。这些规则可以定期将存储空间(Bucket)内的多个文件(Object)转储为指定存储类型,或者将过期的Object和碎片删除,从而节省存储费用。

全局规则的设定,可以根据前缀或者标签的形式进行设置。

无法根据具体的appkey进行设置,但是可以根据时间进行。

生命周期概述

存储类型对比

存储类型 描述 适用场景
标准存储 提供高可靠、高可用、高性能的对象存储服务,支持频繁访问的数据。 适用于频繁访问的热点数据,例如移动应用、大型网站、图片分享等场景。
低频访问存储 提供高可靠、较低存储成本的对象存储服务,支持不频繁访问的数据。 适用于不频繁访问但仍需快速访问的数据,例如监控数据、日志数据等场景。
归档存储 提供高可靠、极低存储成本的对象存储服务,支持长期保存的冷数据。 适用于需要长期保存的冷数据,例如备份数据、历史数据等场景。
冷归档存储 提供高可靠、极低存储成本的对象存储服务,支持极少访问的冷数据。 适用于极少访问的冷数据,例如合规性存档、医疗影像等场景。
深度冷归档存储 提供高可靠、极低存储成本的对象存储服务,支持几乎不访问的冷数据。 适用于几乎不访问的冷数据,例如长期备份、历史档案等场景。

存储类型特性对比

特性 标准存储 低频访问存储 归档存储 冷归档存储 深度冷归档存储
最小存储时长 30 天 60 天 180 天 180 天
最小计量大小 64 KB 64 KB 64 KB 64 KB
数据取回费用 按实际取回量收费 按实际取回量收费 按实际取回量收费 按实际取回量收费
数据访问速度 毫秒级 毫秒级 分钟级 小时级 小时级

存储类型价格对比

存储类型 存储费用(元/GB/月) 数据取回费用(元/GB) 请求费用(元/万次)
标准存储 0.12 0.01
低频访问存储 0.08 0.06 0.01
归档存储 0.03 0.10 0.01
冷归档存储 0.015 0.20 0.01
深度冷归档存储 0.01 0.30 0.01

存储类型适用场景对比

存储类型 适用场景
标准存储 频繁访问的热点数据,例如移动应用、大型网站、图片分享等场景。
低频访问存储 不频繁访问但仍需快速访问的数据,例如监控数据、日志数据等场景。
归档存储 需要长期保存的冷数据,例如备份数据、历史数据等场景。
冷归档存储 极少访问的冷数据,例如合规性存档、医疗影像等场景。
深度冷归档存储 几乎不访问的冷数据,例如长期备份、历史档案等场景。

业务场景

flowchart LR
    标准存储[(标准存储)] --> 低频访问存储[(低频访问存储)] --> 归档存储[(归档存储)]

通过OSS的不同类型的存储,以及时间周期的转变特性来完成数据的分层存储。

读取数据

能够提供QPS2000左右的访问频次。

数据的读取过程,如上所示

flowchart LR
    A@{ shape: circle, label: "Start" }
    A--4W-->缓存数据[(Redis【半小时数据】)] --2K--> 热存储[(Tendis【2天数据】)] --500--> 归档存储[(OSS【360天数据】)]

根据后续的情况,以及tendis的效果预期,可能存在tendis能够直接平替当前的redis缓存层的使用

flowchart LR
    A@{ shape: circle, label: "Start" }
    A--4W-->缓存数据[(Tendis【半小时数据】)] --2K--> 归档存储[(OSS【360天数据】)]

Tendis实现当前的Redis的请求

  1. 5.6W的写入
  2. 4W的读取
  3. 存储容量128GB(后续可能会更加少极端情况下的1000 50000 60 * 30=90GB)

对于上述读取方式2K打到OSS的存储,由于采用了OSS的上述三层的存储结构,可能请求的延时会存在较大的情况,对于大部分数据肯定 由Tendis层+标准存储层+低频访问存储完成数据的获取,几乎没有业务影响。 如果数据到了归档存储层,则会出现类似分钟级的访问延迟,这种情况肯定会存在体验上的问题。基于场景仅可能在拉取历史漫游消息的场景下出现

设定以下场景,客户的漫游中有4条数据,其中其中2条在tendis中,2条在OSS中

timeline
    title 漫游消息列表
    section Tendis 半小时数据
        4 : 10分钟前
        3 : 15分钟前
    section OSS 360天数据
        2 : 81天前
        1 : 91天前

基础访问形式

这里忽略了漫游系统获取出来1..4索引的过程,访问过程需要2分钟,业务侧遇到这种情况无法接受

sequenceDiagram
    SDK->>+IMS: 获取漫游消息(20条消息)
    IMS->>+Tendis: 获取消息3,4(2ms)
    Tendis-->>-IMS: 返回3,4
    IMS->>+Tendis: 访问1,2(2ms)
    Tendis-->>-IMS: not found
    IMS->>+OSS: 访问1,2(2m)
    OSS-->>-IMS: 返回1,2
    IMS-->>-SDK: 返回数据(1,2,3,4)

并发访问形式

通过上面的消息体的串行访问,修改为并发访问的方式,避免了响应时间随着OSS中的消息数量线性增长,整个请求的时间1分钟

sequenceDiagram
    SDK->>+IMS: 获取漫游消息(20条消息)
    IMS->>Tendis: 获取消息5,6(2ms)
    IMS->>Tendis: 访问1,2,3,4(4ms)not found
    par IMS to OSS
        IMS->>+OSS: 访问1(1m)
    and IMS to OSS
        IMS->>+OSS: 访问2(1m)
    end
    OSS-->>-IMS: 访问1(1m)
    OSS-->>-IMS: 访问2(1m)
    IMS-->>-SDK: 返回数据

数据预加载方式

通过请求的预加载方式来降低整体请求的访问体验。

sequenceDiagram
    SDK->>+IMS: 获取漫游消息(1..4)
    IMS->>Tendis: 获取消息3,4
    IMS->>Tendis: 访问1,2 not found
    IMS-->>-SDK: 返回数据(3,4)
    par IMS to OSS
        IMS->>+OSS: 访问1
    and IMS to OSS
        IMS->>+OSS: 访问2
    end
    OSS-->>-IMS: load(1)
    OSS-->>-IMS: load(2)
    SDK->>+IMS: 获取漫游消息(1,2)
    IMS->>Tendis: 获取消息1,2
    IMS->>Tendis: 访问1,2
    IMS-->>-SDK: 返回数据(1,2)

写入数据(更新数据)

写入数据的过程和当前的模型没有变化,直接写入数据即可

sequenceDiagram
    IMS->>+Tendis: 写入消息
    Tendis-->-IMS: 写入成功
    IMS->>+OSS: 写入消息(异步写入)
    OSS-->-IMS: 写入成功

对于变更消息,例如reaction,消息编辑的场景,可以通过操作的异步化和回写缓存的方式来解决访问延迟

sequenceDiagram
    SDK->>+IMS: 修改消息
    IMS->>+Archive: 读取消息(100毫秒超时)
    Archive-->-IMS: 读取超时
    IMS->>-SDK: 异步返回成功给发起方
    IMS->>+Archive: 读取消息(5分钟超时)
    Archive-->-IMS: 返回消息体
    opt 是否可能密集访问
        IMS->>Tendis: 缓存消息体(根据场景确认缓存多久)
    end
    alt 编辑成功
        IMS->>Receiver: 修改消息通知接收方
    else 编辑失败
        IMS->>SDK: 修改消息失败的通知
    end
    

删除数据

sequenceDiagram
    IMS->>+Tendis: 删除消息
    Tendis-->-IMS: 删除成功
    IMS->>+OSS: 删除消息(异步删除)
    OSS-->-IMS: 删除成功

删除操作如果也是延迟比较高的场景,避免再这段相对较长的时间再次读取到相关的数据,oss object官方描述存在原子性

原子性和强一致性

Object操作在OSS上具有原子性,操作要么成功要么失败,不存在中间状态的Object。当Object上传完成时,OSS即可保证读到的Object是完整的,OSS不会返回给用户一个部分上传成功的Object。

Object操作在OSS同样具有强一致性,当用户收到了上传(PUT)成功的响应时,该上传的Object进入立即可读状态,并且Object的冗余数据已经写入成功。不存在上传的中间状态,即执行read-after-write,却无法读取到数据。对于删除操作,用户删除指定的Object成功之后,该Object立即不存在。

概要中的说明

避免该问题也可以采用写入的方式,写入Tendis中删除的状态,这样短时间读取也会被Tendis返回已删除的状态

sequenceDiagram
    IMS->>+Tendis: 删除消息
    Tendis-->-IMS: 写入删除状态,比如NULL字符串
    IMS->>+OSS: 删除消息(异步删除)
    OSS-->-IMS: 删除成功

消息体过期

由于OSS本身不能给object添加相应的过期,一起时间周期管理,从标准存储到低频访问存储,再到归档存储。

OSS的时间周期管理是全局的规则管理,结合业务的场景,可以通过自定义标签达到类似的效果。

  • 时间过期管理:基于最后一次修改时间配置生命周期规则
  • 匹配规则:标签进行匹配
版本 保存时间
free 3天
pro 7天
enterprice 90

规则列表如下

rule tag action
free-delete-rule stragey: free 3天后删除
pro-ia-rule stragey: pro 3天后转为IA
pro-delete-rule stragey: pro 7天后删除
enterprice-ia-rule stragey: enterprice 3天后转为IA
enterprice-Archive-rule stragey: enterprice 7七天后转入Archive
enterprice-delete-rule stragey: enterprice 90七天后转入Archive

标签规则

消息写入的时候根据当前appkey的版本写入具体的stragey标签,由于规则是按照最后更改时间生效,所以业务上是满足需求修改的

sequenceDiagram
    IMS->>+标准存储: 写入消息(stragey: enterprice)
    标准存储-->-IMS: 消息系统保持90天
    标准存储->>+低频访问存储: 3天后转入IA(系统保持)
    低频访问存储-->-标准存储: 消息转移至IA(87天)
    低频访问存储->>+归档存储: 7天后转入Archive(系统保持)
    归档存储-->-低频访问存储: 消息转移至Archive(83天)
    IMS->>+归档存储:更新消息
    归档存储->>-IMS: 消息存储Archive(90天)

基于OSS的生命周期的流转是不可逆,故而即使消息更新了,消息的过期时间更新了,但是并没有变更消息的存储位置