<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>数据服务 on ZRJ | 学习笔记</title>
        <link>https://blog.zrj.me/tags/%E6%95%B0%E6%8D%AE%E6%9C%8D%E5%8A%A1/</link>
        <description>Recent content in 数据服务 on ZRJ | 学习笔记</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-CN</language>
        <lastBuildDate>Fri, 22 Dec 2017 11:47:56 +0800</lastBuildDate><atom:link href="https://blog.zrj.me/tags/%E6%95%B0%E6%8D%AE%E6%9C%8D%E5%8A%A1/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>数据预处理与数据服务之间的平衡</title>
        <link>https://blog.zrj.me/posts/2017-12-22-%E6%95%B0%E6%8D%AE%E9%A2%84%E5%A4%84%E7%90%86%E4%B8%8E%E6%95%B0%E6%8D%AE%E6%9C%8D%E5%8A%A1%E4%B9%8B%E9%97%B4%E7%9A%84/</link>
        <pubDate>Fri, 22 Dec 2017 11:47:56 +0800</pubDate>
        
        <guid>https://blog.zrj.me/posts/2017-12-22-%E6%95%B0%E6%8D%AE%E9%A2%84%E5%A4%84%E7%90%86%E4%B8%8E%E6%95%B0%E6%8D%AE%E6%9C%8D%E5%8A%A1%E4%B9%8B%E9%97%B4%E7%9A%84/</guid>
        <description>&lt;p&gt;1. 数据链条越长，对数据一致性的维护成本越高。&lt;/p&gt;
&lt;p&gt;这个成本高体现在：&lt;/p&gt;
&lt;p&gt;a) 链条长意味的数据的搬运和重复存储，这个搬运的动作，本身可能失败，存储本身可能有问题&lt;/p&gt;
&lt;p&gt;b) 哪怕搬运动作和存储可靠，链条上下游之间的细微逻辑差异，也可能导致数据一致性的问题，例如一个指标在上下游的分别加工中就出现了差异，而且这个是很难避免的，除非上下游逻辑能完全对齐&lt;/p&gt;
&lt;p&gt;c) 当需要重跑的时候，上下游的联动耦合非常紧，牵一发而动全身&lt;/p&gt;
&lt;p&gt;2. 基于此，是不是可以说，尽量把数据链条缩短呢？&lt;/p&gt;
&lt;p&gt;链条缩短的挑战在于：&lt;/p&gt;
&lt;p&gt;a) 模块职能耦合，例如数据清洗和数据加工，本来是两个事情，如果链条拉开来，就是上下游关系，但是想要缩短上下游，就是在一个任务中，在一个任务中就容易臃肿，一种思路是清洗逻辑抽出来作为函数，而不是作为任务&lt;/p&gt;
&lt;p&gt;b) 链条的缩短的另外一个方式是预处理和实时数据服务的分工。预处理做的事情越多，例如，从原始算出小时，从小时算天，从天算汇总，这个就是链条，而且这个链条跟任务是否拆分是无关的，落地就是得落地这么多步骤的中间数据，那如果不在预处理环节做这些事情，那就是倾向于让实时数据服务来做这些事情，这样的问题在于，实时服务的性能是否能 hold 的住，例如我们只算出来天级别的，其他的都实时汇总，看起来似乎解决了数据一致性的问题，但是实时汇总性能跟得上吗&lt;/p&gt;
&lt;p&gt;c) 实时汇总的好处在于，灵活且更可控。例如我们需要调整某个指标的含义，如果我们是基于预处理的思路，那么我们需要面临一个数据的补录问题，但是如果是实时汇总，那么我们只需要保证存储的是原子指标，复合指标和时间维度上的汇总都可以在实时服务中做，想修改复合指标的逻辑就变得非常简单，改动完发布上线即可&lt;/p&gt;
&lt;p&gt;3. 如果需要实时汇总，这个动作是在逻辑服务中完成，还是下推到存储中完成呢？&lt;/p&gt;
&lt;p&gt;假设我们已经在存储中只存一份相对细粒度的明细数据，（注意不是原始，这里差异很大，原始的量级可能在每天百亿行，细粒度的明细可能在每天数十万行），然后我们需要在实时服务中来完成这个汇总的功能，那这个汇总是把明细拉到逻辑层来做，还是下推给到存储层来做呢？&lt;/p&gt;
&lt;p&gt;a) 把数据拉到明细来做，缺点显而易见，就是在存储和逻辑服务两个模块之间的数据 IO 很大，这个 IO 甚至可能最终的整体性能瓶颈。&lt;/p&gt;
&lt;p&gt;b) 把汇总动作下推到存储来做，好处自然是符合“计算贴近数据”的原则，但是问题在于，随着业务的发展，数据规模的增大，存储的压力会越来越大，这里的压力一方面是存储量的 IO 压力，一方面是计算量的 CPU 压力，而且还要考虑到存储层本身是否是可线性扩展的&lt;/p&gt;
&lt;p&gt;c) 对于线性扩展这个问题，考虑以下架构：例如存储层用 MySQL 或者 postgresql，使用一主多从的模式，这样可以读写分离，由于是上游写入，下游只读，所以从节点理论上可以水平线性扩展，主从之间的数据一致性由 db 本身来保证，再使用分库分表的模式，这样可以满足存储量日益增长的需求。然后逻辑层就是无状态的后台服务，也可以水平扩展&lt;/p&gt;
&lt;p&gt;d) 如果把汇总逻辑下推到 db，那么存在一些问题要考虑。例如我们的分库分表模式，怎么做跨库跨表的聚合？这个场景在 OLTP 型查询的时候可能不显著，但是在 OLAP 型查询的时候就凸显出来了。另外一个问题是，db 上可以完成一些简答的聚合逻辑，例如算术加和，但是一些复杂逻辑，例如基数统计就不好做了，而且这个基数统计可能还需要基于既有的基数统计结果来再聚合的时候。&lt;/p&gt;
&lt;p&gt;e) 使用 db 做汇总逻辑的另外一个问题是，逻辑都需要用 sql 来表达，毋庸置疑，sql 是一种表达力非常强的语言，但是他的问题也很明显，SQL 的逻辑，需要“一口气”表达完，意思就是，随着我们的加工逻辑越来越复杂，SQL 会写越长，可读性，可维护性会越来越差，维护成本越来越高。例如遇到一些奇葩的业务场景，某个指标在某天之前是一个逻辑，某天之后是另外一个逻辑，某个指标在某些复合条件的对象是一个逻辑，在另外一些对象是另外一个逻辑，SQL 写起来可以让人抓狂&lt;/p&gt;
&lt;p&gt;f) 反过来说，如果把汇总计算放到逻辑层来做，除了刚刚说的那个网络 IO 的问题，另外一个问题就是，虽然逻辑服务可以水平扩，但是对于某一次给定的请求来说，其计算依然是落到单个机器上的，水平扩展只是解决了多个不同请求落在不同节点的问题，而对于单个请求来说，随着发展。可能出现单个机器处理性能无法满足要求的问题，这个问题在 OLTP 型场景还不显著，在 OLAP 型场景将很快暴露&lt;/p&gt;
&lt;p&gt;g) 截止目前的讨论，应该是倾向于汇总逻辑放到服务层中来做，考量要点是：其一，有些功能存储层无法实现，例如基数统计，而且这个需求很重要，必须实现。其二，下推到存储，存储压力大，承担存储的 IO 压力和计算的 CPU 压力双重压力，将逐渐成为架构的性能瓶颈。其三，下推到存储的在计算逻辑的灵活性，特别是细节上的灵活性不足。&lt;/p&gt;
&lt;p&gt;h) 对于存储和数据服务层都面临可扩展型的问题，如果我们换个思路，存储不用 db，用 hbase，逻辑服务层，一种考量是针对 OLAP 和 OLTP 不同场景用不同服务方式，例如 OLAP 型用 spark，甚至考虑用 spark streaming ，来实现不间断型服务，这块是一个雏形想法，然后就变成从 hbase 里面用 kv 的方式，（虽然不能二级索引，但是精心设计 rowkey，辅以 scan filter 可以过滤），读到 spark 中以 rdd 形式来实时汇总，汇总结果输出为文件等，对于 OLTP 型，可以以低延迟为主要设计考虑目标，但是这种方式的一个埋坑在于，搞两套服务，就引入了逻辑一致性问题，指标逻辑调整需要多处维护，后患无穷，基本被否，还是需要一套服务同时满足。。（尚未考虑清楚，待续）&lt;/p&gt;
</description>
        </item>
        
    </channel>
</rss>
