OLTP与OLAP的区别精简总结
OLTP(实时交易库大量短事务对IO要求高)
一、面向交易的实时处理系统OLTP
OLTP是传统的关系型数据库的主要应用,主要是基本的、日常的事务处理,记录即时的增、删、改、查,比如在银行存取一笔款,就是一个事务交易。
1、实时性要求高;
OLTP 数据库旨在使事务应用程序仅写入所需的数据,以便尽快处理单个事务。
2、数据量不是很大;
3、交易一般是确定的,所以OLTP是对确定性的数据进行存取(比如存取款都有一个特定的金额);
4、支持大量并发用户定期添加和修改数据。
并发性要求高并且严格的要求事务的完整、安全性
OLAP(数据仓库读取分析对CPU要求高)
所谓数据仓库是对于大量已经由OLTP形成的历史数据加工与分析,读取较多,更新较少的一种分析型的数据库,用于处理商业智能、决策支持等重要的决策信息。OLAP即联机分析处理,是数据仓库的核心部心,支持复杂的分析操作,侧重决策支持,并且提供直观易懂的查询结果。典型的应用就是复杂的动态报表系统。
1、实时性要求不是很高,很多应用顶多是每天更新一下数据;
2、数据量大,因为OLAP支持的是动态查询,所以用户也许要通过将很多数据的统计后才能得到想要知道的信息,例如时间序列分析等等,所以处理的数据量很大;
3、因为重点在于决策支持,所以查询一般是动态的,也就是说允许用户随时提出查询的要求。
稀疏索引:为什么高并发写不推荐关系数据库?
如何优化写多读少的系统。说到高并发写,就不得不提及新分布式数据库 HTAP,它实现了 OLAP 和 OLTP 的融合,可以同时提供数据分析挖掘和关系查询。事实上,HTAP 的 OLAP 并不是大数据,或者说它并不是我们印象中每天拿几 T 的日志过来用于离线分析计算的那个大数据。这里更多的是指数据挖掘的最后一环,也就是数据挖掘结果对外查询使用的场景。对于这个范围的服务,在行业中比较出名的实时数据统计分析的服务有 ElasticSearch、ClickHouse,虽然它们的 QPS 不高,但是能够充分利用系统资源,对大量数据做统计、过滤、查询。但是,相对地,为什么 MySQL 这种关系数据库不适合做类似的事情呢?
B+Tree 索引与数据量
MySQL 我们已经很熟悉了。相信你也听过“一张表不要超过 2000 万行数据”这句话,为什么会有这样的说法呢?核心在于 MySQL 数据库的索引,实现上和我们的需求上有些冲突。具体点说,我们对外的服务基本都要求实时处理,在保证高并发查询的同时,还需要在一秒内找出数据并返回给用户,这意味着对数据大小以及数据量的要求都非常高。MySQL 为了达到这个效果,几乎所有查询都是通过索引去缩小扫描数据的范围,然后再回到表中对范围内数据进行遍历加工、过滤,最终拿到我们的业务需要的数据。事实上,并不是 MySQL 不能存储更多的数据,而限制我们的多数是数据查询效率问题。
数据量会影响到索引树的深度个数。这是因为 MySQL 的索引是使用 Page 作为单位进行存储的,而每页只能存储 16KB(innodb_page_size)数据。如果我们每行数据的索引是 1KB,那么除去 Page 页的一些固定结构占用外,一页只能放 16 条数据,这导致树的一些分支装不下更多数据时,我么就需要对索引的深度再加一层。
另外,如果有数据持续高并发插入数据库会导致 MySQL 集群工作异常、主库响应缓慢、主从同步延迟加大等问题。索引适合查询,不适合修改,因为还要维护索引树
稀疏索引 LSM Tree 与存储
我第一次见到 LSM Tree 还是从 RocksDB(以及 LevelDB)上看到的,RocksDB 之所以能够得到快速推广并受到欢迎,主要是因为它利用了磁盘顺序写性能超绝的特性,并以较小的性能查询代价提供了写多读少的 KV 数据存储查询服务,这和关系数据库的存储有很大的不同
我们前面讲过,B+Tree 是一个大树,它是一个聚合的完整整体,任何数据的增删改都是在这个整体内进行操作,这就导致了大量的随机读写 IO。
RocksDB LSM 则不同,它是由一棵棵小树组成,当我们新数据写入时会在内存中暂存,这样能够获得非常大的写并发处理能力。而当内存中数据积累到一定程度后,会将内存中数据和索引做顺序写,落地形成一个数据块。(这一段说明总体结构,小树组成,即0层几个数据块合并成1层的1个数据块)
每一层的数据块和数据量超过一定程度时,RocksDB 合并不同 Level 的数据,将多个数据块内的数据和索引合并在一起,并推送到 Level 的下一层。通过这个方式,每一层的数据块个数和数据量就能保持一定的数量,合并后的数据会更紧密、更容易被找到。
这么设计的好处
这样的设计,可以让一个 Key 存在于多个 Level 或者数据块中,但是最新的常用的数据肯定是在 Level 最顶部或内存(0~4 层,0 为顶部)中最新的数据块内。而当我们查询一个 key 的时候,RocksDB 会先查内存。如果没找到,会从 Level 0 层到下层,每层按生成最新到最老的顺序去查询每层的数据块。同时为了减少 IO 次数,每个数据块都会有一个 BloomFIlter 辅助索引,来辅助确认这个数据块中是否可能有对应的 Key;如果当前数据块没有,那么可以快速去找下一个数据块,直到找到为止。
可以看到,这个方式虽然放弃了整体索引的一致性,却换来了更高效的写性能。在读取时通过遍历所有子树来查找,减少了写入时对树的合并代价。LSM 这种方式的数据存储在 OLAP 数据库中很常用,因为 OLAP 多数属于写多读少,而当我们使用 OLAP 对外提供数据服务的时候,多数会通过缓存来帮助数据库承受更大的读取压力。
列存储数据库
大数据挖掘使用的数据库普遍使用列式存储(Column-based),原因在于我们用关系数据库保存的多数是实体属性和实体关系,很多查询每一列都是不可或缺的。但是,实时数据分析则相反,很多情况下常用一行表示一个用户或主要实体(聚合根),而列保存这个用户或主要实体是否买过某物、使用过什么 App、去过哪里、开什么车、点过什么食品、哪里人等等。
而列存储引擎可以指定用什么字段读取所需字段的数据,并且这个方式能够充分利用到磁盘顺序读写的性能,大大提高这种列筛选式的查询,并且列方式更好进行数据压缩,在实时计算领域做数据统计分析的时候,表现会更好。
监控行业发展现状
,常见监控系统主要有三种类型:Metrics、Tracing 和 Logging。
常见的开源 Metrics 有 Zabbix、Nagios、Prometheus、InfluxDb、OpenFalcon,主要做各种量化指标汇总统计,比如监控系统的容量剩余、每秒请求量、平均响应速度、某个时段请求量多少。
常见的开源链路跟踪有 Jaeger、Zipkin、Pinpoint、Skywalking,主要是通过分析每次请求链路监控分析的系统,我么可以通过 TraceID 查找一次请求的依赖及调用链路,分析故障点和传导过程的耗时。
而常见的开源 Logging 有 ELK、Loki、Loggly,主要是对文本日志的收集归类整理,可以对错误日志进行汇总、警告,并分析系统错误异常等情况。
随着行业发展,三位一体的标准应运而生.
我建议使用 ELK 提供的功能去实现分布式链路跟踪系统.
因为它已经完整提供了如下功能:日志收集(Filebeat)日志传输(Kafka+Logstash)日志存储(Elasticsearch)检索计算(Elasticsearch + Kibana)实时分析(Kibana)个性定制表格查询(Kibana)这样一来,我只需要**制定日志格式、埋点 SDK,**即可实现一个具有分布式链路跟踪、Metrics、日志分析系统。
可以说,要想构建一个简单的 Trace 系统,我们首先要做的就是生成并传递 TraceID。分布式链路跟踪的原理其实很简单,就是在请求发起方发送请求时或服务被请求时生成一个 UUID,被请求期间的业务产生的任何日志(Warning、Info、Debug、Error)、任何依赖资源请求(MySQL、Kafka、Redis)、任何内部接口调用(Restful、Http、RPC)都会带上这个 UUID。这样,当我们把所有拥有同样 UUID 的日志收集起来时,就可以根据时间(有误差)、RPCID(后续会介绍 RPCID)或 SpanID,将它们按依赖请求顺序串起来。 span 这个设计会通过记录自己上游依赖服务的 SpanID 实现上下游关系关联(放在 Parent ID 中),通过整理 span 之间的依赖关系就能组合成一个调用链路树。(就是一个parent id代表层级,spanid代表路径)
侵入式埋点和AOP埋点
埋点主要就是用于捕捉用户在前端界面的交互行为,以便统计和监测产品的实际使用情况。设置埋点一般是在整个业务逻辑已经跑通的情况下后期加入的。其实这个时候向业务代码中加入埋点的代码是非常奇怪的。业务代码和埋点代码是完全不相关的两块逻辑,这个时候却需要强行组织在一起,导致了对业务代码的侵入。同步的代码可能相对来说更好处理,直接将埋点代码放到相应事件代码最前面或者最后面,进行显式的分离。
如果遇到异步的情况,情况会变得相当的糟糕,我们不得不在Promise返回的结果中完全与业务代码混合在一起。再加上一个应用的埋点往往有很多处,这导致原本组织得体的业务代码被侵入的支离破碎。无论是从逻辑分离、代码简洁或后期维护的角度,这都是让人难以接受。
什么是AOP
AOP是一种面向横切面编程的思想,是对面向过程编程思想非常好的横向维度补充。常见应用于日志打印、性能监测、安全控制、异常处理等。本质上是对数据和逻辑“拦截”后进行过滤或改造,之后再重新放归到主业务流程。AOP在前端的应用似乎并不多见,但据我了解在后端有非常广泛的应用。
AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后, 再通过“动态织入”的方式掺入业务逻辑模块中。这样做的好处首先是可以保持业务逻辑模块的纯净和高内聚性,其次是可以很方便地复用日志统计等功能模块
1 借助装饰器模式,仅适用于class组件
// 定义切面函数,用于在需要埋点的函数执行前执行上报数据的逻辑
function log(target, key, descriptor) {const original = descriptor.value;descriptor.value = function() {// 上报数据的逻辑console.log(埋点数据:${key}
);return original.apply(this, arguments);};return descriptor;
}// 定义一个需要埋点的函数
class Button {@loghandleClick() {// 点击事件处理逻辑}// return 逻辑
}
ELK为什么这么强大?
这需要我们了解 ELK 中储存、索引等关键技术点的架构实现才能想清楚。
Elasticsearch 架构
我们项目产生的日志,会通过 Filebeat 或 Rsyslog 收集将日志推送到 Kafka 内。然后由 LogStash 消费 Kafka 内的日志、对日志进行整理,并推送到 ElasticSearch 集群内。接着,日志会被分词,然后计算出在文档的权重后,放入索引中供查询检索, Elasticsearch 会将这些信息推送到不同的分片。每个分片都会有多个副本,数据写入时,只有大部分副本写入成功了,主分片才会对索引进行落地(需要你回忆下分布式写一致知识)。
而Elasticsearch是专门做搜索的,就是为了解决上面所讲的问题而生的,换句话说:
Elasticsearch对模糊搜索非常擅长(搜索速度很快)
从Elasticsearch搜索到的数据可以根据评分过滤掉大部分的,只要返回评分高的给用户就好了(原生就支持排序)
没有那么准确的关键字也能搜出相关的结果(能匹配有相关性的记录)
Elasticsearch的数据结构
我们根据“完整的条件”查找一条记录叫做正向索引;我们一本书的章节目录就是正向索引,通过章节名称就找到对应的页码。
为什么Elasticsearch为什么可以实现快速的“模糊匹配”/“相关性查询”,实际上是你写入数据到Elasticsearch的时候会进行分词。
还是以上图为例,上图出现了4次“算法”这个词,我们能不能根据这次词为它找他对应的目录?Elasticsearch正是这样干的,如果我们根据上图来做这个事,会得到类似这样的结果:
算法 ->2,13,42,56
这代表着“算法”这个词肯定是在第二页、第十三页、第四十二页、第五十六页出现过。这种根据某个词(不完整的条件)再查找对应记录,叫做倒排索引。
那Elasticsearch怎么切分这些词呢?,Elasticsearch内置了一些分词器
我们输入一段文字,Elasticsearch会根据分词器对我们的那段文字进行分词(也就是图上所看到的Ada/Allen/Sara…),这些分词汇总起来我们叫做Term Dictionary,而我们需要通过分词找到对应的记录,这些文档ID保存在PostingList
在Term Dictionary中的词由于是非常非常多的,所以我们会为其进行排序,等要查找的时候就可以通过二分来查,不需要遍历整个Term Dictionary
由于Term Dictionary的词实在太多了,不可能把Term Dictionary所有的词都放在内存中,于是Elasticsearch还抽了一层叫做Term Index,这层只存储部分词的前缀,Term Index会存在内存中(检索会特别快) Term Index在内存中是以FST(Finite State Transducers)的形式保存的,其特点是非常节省内存。
PostingList会使用Frame Of Reference(FOR)编码技术对里边的**数据进行压缩,**节约磁盘空间。使用Roaring Bitmaps来对文档ID进行交并集操作。
一个Elasticsearch集群会有多个Elasticsearch节点,所谓节点实际上就是运行着Elasticsearch进程的机器。在众多的节点中,其中会有一个Master Node,它主要负责维护索引元数据、负责切换主分片和副本分片身份等工作(后面会讲到分片的概念),如果主节点挂了,会选举出一个新的主节点。
Elasticsearch最外层的是Index(相当于数据库表的概念);一个Index的数据我们可以分发到不同的Node上进行存储,这个操作就叫做分片。
比如现在我集群里边有4个节点,我现在有一个Index,想将这个Index在4个节点上存储,那我们可以设置为4个分片。这4个分片的数据合起来就是Index的数据。
为什么要分片?原因也很简单:
如果一个Index的数据量太大,只有一个分片,那只会在一个节点上存储,随着数据量的增长,一个节点未必能把一个Index存储下来。
多个分片,在写入或查询的时候就可以并行操作(从各个节点中读写数据,提高吞吐量)
现在问题来了,如果某个节点挂了,那部分数据就丢了吗?显然Elasticsearch也会想到这个问题,所以分片会有主分片和副本分片之分(为了实现高可用)
数据写入的时候是写到主分片,副本分片会复制主分片的数据,读取的时候主分片和副本分片都可以读。
先总结一下:使用倒排索引实现模糊查询和相关查询高效的
。然后是分布式的,有很多节点,有主节点调度。一个表分片可以提高吞吐量,存大量数据,每行数据写到哪个分片可以哈希算法转发。
同时为了高可用性,每个分片还有主分片和副分片,写是写到主分片上,读可以所有分片。等主分片写完了以后,会将数据并行发送到副本集节点上,等到所有的节点写入成功就返回ack给协调节点,协调节点返回ack给客户端,完成一次的写入(raft)。
写入具体过程是写到缓冲区,过1秒再写到文件系统缓冲区生成segment文件,生成索引被检索。再过五秒写到translog缓存,主要用于备份,再过几十分钟commit,真正持久化。
前面提到了,每隔1s会生成一个segement 文件,那segement文件会越来越多越来越多。Elasticsearch会有一个merge任务,会将多个segement文件合并成一个segement文件。在合并的过程中,会把带有delete状态的doc给物理删除掉(一开始没有真的删除)。
查询时可以分成一阶段,二阶段,三阶段,
一阶段较少,一般是只查询一个分片的内容,二阶段如下
Query Phase阶段时节点做的事:
协调节点向目标分片发送查询的命令(转发请求到主分片或者副本分片上)
数据节点(在每个分片内做过滤、排序等等操作),返回doc id给协调节点
Fetch Phase阶段时节点做的是:
协调节点得到数据节点返回的doc id,对这些doc id做聚合,然后将目标数据分片发送抓取命令(希望拿到整个Doc记录)
数据节点按协调节点发送的doc id,拉取实际需要的数据返回给协调节点
三阶段就是先算分,再查询,分越高,相关性越强。
后起新秀ClickHouse
Elasticsearch 虽然用起来方便,但却有大量的硬件资源损耗。而 ClickHouse 是新生代的 OLAP,尝试使用了很多有趣的实现,虽然仍旧有很多不足,比如不支持数据更新、动态索引较差、查询优化难度高、分布式需要手动设计等问题。但由于它架构简单,整体相对廉价。ClickHouse 属于列式存储数据库,多用于写多读少的场景,它提供了灵活的分布式存储引擎,还有分片、集群等多种模式,供我们搭建的时候按需选择。
ClickHouse 通过分片及内存周期顺序落盘,提高了写并发能力;通过后台定期合并 data parts 文件,提高了查询效率;在索引方面,通过稀疏索引缩小了检索数据的颗粒范围,对于不在主键的查询,则是通过跳数索引来减少遍历数据的数据量;另外,ClickHouse 还有多线程并行读取筛选的设计。这些特性,共同实现了 ClickHouse 大吞吐的数据查找功能。