这是数据架构解决方案系列博客的第四篇,系列的其他文章:

  1. 概述
  2. 数据架构组成
  3. 数据流水线和数据湖
  4. 数据处理和分析方法论

到目前为止,我们讨论了:

  1. 如何将源数据初步处理并且暂存;
  2. 如何从暂存的数据中进一步榨取出价值。

下一步,也是直面真实用户的重要一步,就是将构建好的数据应用部署上线并且持续运维,给用户(包括组织内部的和外部的)提供构建好的服务。

对于一个面向用户的线上数据应用而言,有下面这几个指标需要特别注意:

  • 响应延迟

    用户执行的一个操作需要花多少时间才能得到响应?

  • 系统吞吐量

    整个系统在一个单位时间内可以同时处理多少个用户操作?

  • 一致性

    不管系统是如何架构的,提供的一致性模型是怎样的?是强一致还是最终一致?实现这个一致性的代价是什么?

  • 可用性

    系统的失败恢复机制是怎样的?

系统延迟和吞吐量

一个应用最基础的功能:增删查改,数据应用也不例外。

我们在设计一个面向线上的数据应用时,对于每一个操作都应该有下面几个考虑:

  • 竞争条件

    通俗来说,比如两个客户端试图同时修改一行数据,应该怎么操作?

    有多种方案可以参考:

    • 后来者胜

      这种方案比较简单,就是谁后到达就覆盖之前的修改。

    • 事务锁

      加锁意味着,要么在数据存储的地方(比如数据库)加上锁,防止竞争,要么在存储之前(比如服务器)加上锁。比较常见的锁有乐观锁和悲观锁

  • 异步还是同步

    当系统对于一个请求的处理可能在多处都有状态存储的时候,一个完全同步的操作处理可以保证状态的一致性,但这么做可能导致响应时间的延迟。

    对于这种长链路操作来说,如果使用异步操作,那就有可能在多个存储点的状态不一致。

    可以简单理解为强一致性和最终一致性

    当设计一个面向线上的应用时,需要考虑:

    • 要响应速度还是数据一致

      如果使用异步模型,就有可能在一个时间窗口内多处数据不一致的情况。

      这是一个永恒的 trade-off,只能按照你的具体案例具体考虑。

  • 性能的一致性

    在上线一个应用之前,我们都会做性能测试,包括各种压测。

    但是,你能保证你的测试覆盖了多种情况吗?

    • 如果更新和插入的顺序发生了变化,系统的响应还是一样的吗?
    • 当存储的数据量成倍增加的情况下,系统的响应会受到怎样的影响?
    • 当查询的数据量成倍增加的时候,系统的响应是成倍增加吗?
    • 当有数据更新和插入时,查询的响应会受到怎样的影响?

风险管理

对于延迟和吞吐量这类指标来说,最大的风险就是:上线后的真实指标和上线前的测试结果匹配不上

要解决这种可能的不合预期的风险的方法也很直接:多测,多沟通。列出几点需要注意的事项:

  • 对每次测试都做文档记录。
  • 对每次测试的改动都记录原因。
  • 对每次测试的结果都要抱有怀疑的态度。
  • 如果可能的话,让多个团队参与测试用例的设计。
  • 如果可能的话,让多个团队参与测试。

另外,一定要注意在代码实现的时候多使用接口。因为在测试阶段,很非常大的概率你会更改代码的时候,如果能保持接口一致,将会对你的重构有莫大的帮助。

一致性

在分布式系统中,一个状态可能会存在于多个地方,主要有下面这几种场景:

  • 客户端

    状态存储在用户使用的客户端。

  • 服务器

    状态存在用户客户端连接的服务器。

  • 数据中心

    一个数据中心可能向多个应用服务器提供数据存储服务。

  • 多数据中心

    一个应用的数据和状态可能被存在多副本的多数据中心中。

基于不同的目标和需求,不同的场景有不同的需要考虑的因素:

状态存储在客户端

将状态直接缓存在客户端并且允许客户端修改有一些明显的优势:高性能以及高扩展性。但是缺陷也同样存在:

  • 易失性

    客户端随时都可能挂掉,这将导致数据在持久化到服务器之前就丢失了。

  • 安全

    客户端所在的主机可能是不可靠的,当我们强烈依赖客户端的可靠性时,这种方案可能造成数据泄露。

比如,你的电脑的 /etc/hosts 可以理解为在客户端存储的一份状态。

存储在服务器

将状态存储在服务器(基本上)可以忽略安全问题了,但是会面临用户分区的问题。

在某些场景下,用户在服务器的分布时固定的,比如对于一些游戏服务器,就需要维护同样一组用户的数据在同一台(组)服务器中。

在更多的场景下,用户分区时随机的,比如大部分微服务架构的 Web 应用,某个用户接入到哪台服务器完全取决于负载均衡策略。

当你开始考虑这个问题的时候,意味着你可能需要将状态移到统一的 数据中心 中。

存储在单一数据中心

这可能是你见过的最常见的状态存储位置,比如 NoSQL 系统,关系型数据库,ES, 分布式缓存系统。

将数据存储在统一的一个数据中心,当你的服务器也都在同一个区域中时,这是一个好的架构。

单一数据中心通常会在同一区域中创建副本,以保证当某个节点挂掉后,可以由备份节点提供服务,保证 SLA。

但是,如果整个区域的机房都挂掉(这并不罕见),这个方案就可能导致服务中断。这个时候,可能你需要考虑多区域数据中心

存储在多区域多数据中心

多数据中心架构有多种模式,选择怎样使用取决于具体的需求和限制:

  • 异地容灾

    正如上面提到的,使用单一数据中心可能会在这个数据中心机房挂掉的时候导致服务不可用。

    使用多数据中心可以部分解决这个问题,因为这种场景通常的一致性模型通常是最终一致性,所以当某个数据中心挂掉后,整个系统的服务可以继续提供,但是数据可能有部分丢失。

  • 为了保证跨区域的多数据中心的数据一致性,全局锁是其中一种方法。

    全局锁的想法比较简单:某个资源被全局地锁定,要想获取这个资源,必须先拿到对应的锁。

关于多数据中心的数据一致性问题,可以参考这系列读书笔记,详细介绍了各种技术解决方案,这里不再赘述。

风险管理

最好在项目计划初期就开始讨论关于数据一致性问题的方案,因为方案一旦确定就很难改变了。

当计划确定后,需要用文档详细记录选择的理由以及这个选择将会对用户带来什么样的影响,比如时强一致性还是最终一致性,什么情况下用户的数据可能丢失等。并且要确保这个文档是所有相关人员都能看到的。

可用性

可用性不用多说,肯定很重要了。重要就意味着 不是那么容易就能实现

系列几种因素可能都会影响系统可用性:

  • 人为因素

    是人就会犯错。

    实际上,错误的配置、不匹配的代码版本、没有被测试覆盖的代码等,都可能造成线上服务不可用。

  • 升级

    软件系统的升级可以分为多个层次:

    操作系统版本升级 -> 系统 lib 包版本 -> 语言版本 -> 依赖包版本 -> 源代码版本 -> 接口版本
    

    每一层的版本升级都有可能造成线上服务不可用。

  • 硬件失败

    不管你在什么环境中运行,长时间运行的硬件一定会失败!

  • 恶意攻击

    恶意攻击(比如 DDoS 攻击)也可能会造成系统停服。

在设计系统之初,就应该考虑在各种场景下的恢复方案,以达到满足你向用户承诺的 SLA。下面是几种可能的修复方案:

  • 服务器的失败恢复

    最直接的做法:当一台服务器失败了,将用户请求转发到另一台服务器。

    当数据和状态都保存在数据中心(而不是服务器本地)时,单机失败的影响几乎没有。

  • 缓存服务器失败

    当缓存服务器失败时,应该立即使用持久化存储(比如 RDB),然后切换到另一台可用的缓存服务器,并逐渐将状态和数据拉取到缓存中。

    在这种场景下,重要的一点是:缓存不是唯一的数据源

  • 跨区域的多数据中心失败

    当你选择使用多数据中心存储数据时,你应该知道,对于多数据中心,数据的同步时最终一致性的,这就意味着,当出现某区域的数据中心失败时,可能有部分还没来得及同步到其他数据中心的数据就会丢失。丢失的数据多少取决于你系统的延迟和吞吐量。

    另外,使用多数据中心还有一个需要考虑的问题:如何解决跨区域的写。你可以采用单主中心,也可以使用多主中心机制。技术细节可以参考这个笔记

风险管理

对于复杂的分布式系统,要尽早测试系统的可用性。一种常见的方法是,在测试的时候,故意让某一模块失败,然后观察系统的可用性。这就是所谓的 Chaos Monkey

这种测试结果可以用于描述系统对于部分模块失败的响应以及 SLA 的保证情况。如果你做得好的话,还可以在团队内构建出一种以减少系统失败为第一优先级的工程师文化。参考 Netflix 关于 Chaos Monkey首篇博客

数据应用部署运维的成员组成

数据流水线团队成员不同,数据应用面向的是线上服务,所以需要的也是和维护运行线上应用的人才。

  • SRE

    网站运营维护工程师主要负责你的线上数据应用在线上的稳定和可用。

  • 现代数据库 DBA

    现代数据库 DBA 主要应该精通现代数据存储和数据操作系统,以及对分布式系统有深入的理解。

    他们主要负责数据应用的数据库能满足向客户承诺的 SLA。

总结

本篇总结了如何将有价值的数据提供给用户,以及如何保证数据应用的 SLA。