使用Apache Samza对数据库进行彻底的"调教"

数据库是全局的共享的可变的状态,自上世纪60年以来一直是这样,大多数有自尊的开发人员在他们代码中已经摆脱了全局变量,那么为什么我们还要容忍数据库作为一个全局变量呢?

这个谈话介绍了Apache Samza,它是一个由LinkedIn开发的分布式流处理框架,起初,它看起来像一个实时计算分析工具,但是它彻底地将数据库架构颠覆了。

在其核心是一个分布式的持久的提交日志,由Apache Kafka实现强大的流连接和管理大量的数据可靠性。

原文:Turning the database inside-out with Apache Samza

下图是典型的Web应用框架架构:


你有一个客户端比如是Web浏览器或移动应用,客户端向服务器端或者称为后端交互,后端实现一些业务逻辑,执行访问控制,接受输入,产生输出,当后端需要为将来保存一些数据,那么就存储到数据库,以后需要时查询数据库。这是目前大家非常熟悉的东西。

通常我们建立这种后端是无状态,这有很多优点:你能通过并行运行多个进程或实例来扩展后端,你能将客户端转发到多个实例服务器上,任何需要状态的请求将从数据库中查询,这通常与HTTP无状态特点吻合。

这个架构最大问题是:状态必须保存数据库中,这样导致将数据库作为一个巨大的、全球的、共享的可变状态,它是一个全局变量,在你的应用服务器之间共享。

在共享内存中进行并发是一件可怕的事情,LinkedIn已经斗争了很多年,从Actor模型 Channel gogroutine等,所有都是试图解决共享内存的并发问题,避免锁 死锁、并发修改、竞争条件等等。

我们试图摆脱共享内存的并发问题,但是数据库还是最大的共享的可变状态,那么值得思考:如果我们能在单服务器应用架构中能解决共享内存的并发问题,如果我们在整个系统级别克服这种全局的可变状态会如何呢?

在我们看来,将整个系统建立在可变的数据库系统上是习惯问题,我们几十年的架构习惯,现在我们想想有什么其他架构构建有状态的系统呢?

为了试图找出我们可以采取什么样的解决路线,我们先看看目前数据库做的四个事情,这四个例子也许为我们指明方向。

1.复制。数据库是在主从服务器之间复制数据,看看下面购物车案例:


这是一个关系数据库数据模型,这是一个通用模型,有一个表,表中有一些列等等,每行代表一组记录。

假设客户123改变主意,不需要一个产品999,而是需要3个,那么我们会更新到数据库。


那么对于复制来说,这些写操作都要从主服务器复制到从服务器上。


这种复制有几种方式,一个是将同样的更新发送到从服务器,从服务器在自己的数据上执行同样的SQL语句;还有一种是将主服务器的写后日志传送到从服务器;第三种是是,逻辑日志,主服务器写出与以前的区别,比如哪个行插入或修改或删除了,对于本案例更新,逻辑日志指出了哪个行被改变,使用主键或其他元标识替代,以及相应的新值。

这里好像很正常,但是会有趣的事情发生:



上图是更新语句,一个命令式语句描述了状态的变化,它命令数据修改某行记录。

从另外一个方面看,当这种写操作作为逻辑日志从主服务器复制到从服务,它其实有一个不同形式:它成为了一个事件,开始某个时间点,一个特定顾客修改了某个特定商品的数量,从1到3,这是发生过的事实,无论以后客户从购物车去除或者再次修改数量或者干脆走了,都不能改变目前这个状态改变的事实。

也就是说,即使你以传统方式使用你的数据库,以新状态覆盖旧状体,数据库内部的复制机制还是将这些命令式SQL语句转为一个不可变的事件流。

至此,可以好好想想,我们在谈论一个完全不同的东西。

下面看数据库的第二索引,熟悉数据库的人应该知道,可以使用create index on cart (customer_id)来创建。

当你运行这些创建索引SQL语句时,数据库内部做了些什么呢?


上图左边是根据列customer_id创建,右边是根据product_id创建,数据库会扫描整个表,为每个索引创建辅助数据结构,看上去类似key-value结构,key是创建索引的列,值是代表记录的特定key:



这个基于基表建立索引表的过程是机械的,它仅仅代表着现有的数据在不同的数据结构。(换句话说,如果你删除索引,不会从数据库中删除任何数据。)这是一个冗余的数据结构,只是为了提高查询速度,从原始表派生出来的。

以后你如果新增新的数据,数据库将自动在基表和索引数据结构之间保持事务一致性,在两个表中插入相同数据。这个过程是事务的,意味着,一旦出错回滚,索引表的修改也会回滚。

如果大型表上创建索引,可能需要几个小时,向一个生产现场数据库如果不断写数据,那么就会不断地建立索引。索引生成器真的像一个后台进程运行。

为了做到这些,数据库必须从某个时间的一致性快照建立索引,当索引构建时还需要对这个时间点以后的任何修改同时进行跟踪。这是一个很酷的功能。

前面我们讨论了复制和第二索引,现在看看缓存。

这里的缓存是指应用程序的缓存,假设你使用 memcached 或 Redis作为大型缓存。

当一个请求到达应用程序缓存中,你需要检查一下数据是否在缓存中,缓存查找通常是根据key。如果数据在缓存中,你可以直接返回到客户端。

如果不在缓存,你需要到数据库中查询,然后放入缓存,下一次再查询同样数据就可以在缓存中找到。

第一个问题是,当然是为什么计算科学中最难的两件事是命名和缓存失效
如果你想管理缓存失效问题,真的非常棘手,当基础数据库中数据变化,你怎么让缓存的中应该过期或者更新?一个选择是使用过期失效算法,这些算法分辨出数据库改变影响了哪些缓存条目,,但这些算法都是脆弱和容易出错的。此外你可以有一个失效时间的解决办法,但是这容易导致脏数据。

另外一个问题竞争问题,如果你有两个进程并发写入数据库和更新缓存,它们写数据库是一个顺序,而更新缓存是以另外一种顺序,那么就导致缓存中数据和数据库数据不一致了。

其他还有各种不一致并发问题,大部分系统都是假装这些竞争并发不存在,掩耳盗铃而已。

另外一个问题是冷启动,通过缓存清零,加载新的改变数据,这会导致负载突然增加。数据库超载。

此外还谈论了第四种物化视图materialized view,可以在应用程序缓存中构建不同的物化视图。

LinkedIn在谈话中大量篇幅分析了数据库四种机制原理和问题,有兴趣者可翻看原文,下面提出结论。

LinkedIn认为对传统数据库的如下机制进行重新思考。


重新思考的架构图如下:


这实际是一种EventSourcing架构(本站有大量讨论),将写操作变成不可变的事件流,输入Apache Kafka,然后通过输出更新各种数据库。

现在,我们可以将所有写操作变成不可变事件(事实),每个不可变事件追加到事务日志或称为事件流中,这个事务日志是真正简单,只能追加append数据的数据结构。Apache Kafka提供append-only日志数据结构,而且以一种可靠的可扩展的方式,它会将一切写到磁盘,跨多个机器复制数据,只要你不丢失服务器,就不会丢失数据,它将流分区到多个服务器能够水平扩展, 每秒处理数百万的写操作。

当这做时,你不需要直接对主数据库进行写操作,你直接将写操作追加到日志中即可。

现在这个系统写入将非常快且可扩展,因为你仅仅将写操作事件追加到日志(Kafka)中,但是如果读数据怎么办?从日志中读数据可是非常慢的,需要扫描整个日志。

解决办法是从事务日志中建立类似数据库的物化视图materialized view。物化视图就像第二索引,其数据来自于日志数据,为快速读取而优化,一个物化视图是日志的一部分子集放入缓存中, 你能从日志中随时重新构建,同样数据有不同的物化视图:key-value存储, 完整文本full-text搜索索引, 图索引, 分析系统等。

(banq注:这里体现ES读写分离,读是从缓存,写直接写入日志)

你可以认为这是一种“分拆unbundling”的数据库,将原先放入数据库这样一个软件中的模块分解为多个可以灵活方式组合的组件。

接着该谈话讨论如何使用Apache Kafka实现日志的具体实现原理,可参考原文

以上实际是Apache Samza核心原理,也就是为什么它能在大数据架构中替代传统数据库的原理所在。

更多参考:
LinkedIn构建实时流数据平台实践指南

实时流Streaming大数据:Storn,Spark和Samza


[该贴被banq于2015-03-05 15:48修改过]

唉,好伤心的,这么多人类思维和经验的精华,什么时候才能达到能看懂的水平呢 呜。。。。

这个物化视图materialized view,看起来很数据库分片很像(sharding),数据库分片不好弄的就是分片规则,把相关联的数据聚合在一起,materialized view如何解决(客户数据在一个View,其订单数据在另一个view,查询起来会不会很费事,map-reduce?)

[该贴被clonalman于2015-04-05 18:56修改过]

2015-04-05 18:50 "@clonalman"的内容
(客户数据在一个View,其订单数据在另一个view,查询起来会不会很费事) ...

聚合查询,不关map-reduce的事,数据分布式存放,不整点大数据技术怎么分析?