一. 存储
行式存储的原理与特点
对于 OLAP 场景,大多都是对一整行记录进行增删改查操作的,那么行式存储采用以行的行式在磁盘上存储数据就是一个不错的选择。
当查询基于需求字段查询和返回结果时,由于这些字段都埋藏在各行数据中,就必须读取每一条完整的行记录,大量磁盘转动寻址的操作使得读取效率大大降低。
举个例子,下图为员工信息emp表。
数据在磁盘上是以行的形式存储在磁盘上,同一行的数据紧挨着存放在一起。
对于 emp 表,要查询部门 dept 为 A 的所有员工的名字。
select name from emp where dept = A
由于 dept 的值是离散地存储在磁盘中,在查询过程中,需要磁盘转动多次,才能完成数据的定位和返回结果。
列式存储的原理与特点
对于 OLAP 场景,一个典型的查询需要遍历整个表,进行分组、排序、聚合等操作,这样一来行式存储中把一整行记录存放在一起的优势就不复存在了。而且,分析型 SQL 常常不会用到所有的列,而仅仅对其中某些需要的的列做运算,那一行中无关的列也不得不参与扫描。
然而在列式存储中,由于同一列的数据被紧挨着存放在了一起,如下图所示。
那么基于需求字段查询和返回结果时,就不许对每一行数据进行扫描,按照列找到需要的数据,磁盘的转动次数少,性能也会提高。
还是上面例子中的查询,由于在列式存储中 dept 的值是按照顺序存储在磁盘上的,因此磁盘只需要顺序查询和返回结果即可。
列式存储不仅具有按需查询来提高效率的优势,由于同一列的数据属于同一种类型,如数值类型,字符串类型等,相似度很高,还可以选择使用合适的编码压缩可减少数据的存储空间,进而减少IO提高读取性能。
总的来说,行式存储和列式存储没有说谁比谁更优越,只能说谁更适合哪种应用场景。
1.1 mysql
当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non- repeatable read)、幻读(phantomread)的问题,为了解决这些问题,就有了“隔离级别”的概念。
脏读:如果一个事务A对数据进行了更改,但是还没有提交,而另一个事务B就可以读到事务A尚未提交的更新结果。这样,当事务A进行回滚时,那么事务B开始读到的数据就是一笔脏数据。
不可重复读:同一个事务对同一个数据进行读取操作,读取到的结果不同。例如,事务B在事务A的更新操作前读到的数据,跟在事务A提交此更新操作后读到的数据,可能不同。
幻读:就是一个事务读到另一个事务新增加并提交的数据(insert)。在同一个事务中,对于同一组数据读取到的结果不一致。比如,事务A 新增了一条记录,事务B 在 事务A 提交前后各执行了一次查询操作,发现后一次比前一次多了一条记录。幻读出现的原因就是由于事务并发新增记录而导致的。
不可重复读和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于删除或新增!!
下面我们来看为了解决这些问题出现的隔离级别。首先要知道,隔离得越严实,效率就会越低。因此很多时候,我们都要 在二者之间寻找一个平衡点。SQL标准的事务隔离级别包括:读未提交(read uncommitted)、 读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。 具体解释如下:
读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到。
读已提交是指,一个事务提交之后,它做的变更才会被其他事务看到。
可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一 致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
串行化,顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突 的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
数据库的隔离性就是通过加锁和MVCC来实现的。从上面可以看到,可重复读的隔离级别会出现幻读的问题,而MySQL的默认隔离级别是可重复读,并且解决了幻读的问题。简单来说,MySQL的默认隔离级别解决了脏读、幻读、不可重复读的问题。
MySQL事务及实现原理全面总结,再也不用担心面试_mysql事务的实现原理_李孛欢的博客-CSDN博客
https://www.rstk.cn/news/99075.html?action=onClick
1.2. hbase
(1)特点:
Base不支持二级索引,它只有一个主键索引,采用LSM树。
HBase是一个分布式系统,这点跟MySQL不同,它的数据是分散不同的server上,每个table由一个或多个region组成,region分散在集群中的server上,一个server可以负责多个region。
这里有一点需要特别注意:table中各个region的存放数据的rowkey(主键)范围是不会重叠的,可以认为region上数据基于rowkey全局有序,每个region负责它自己的那一部分的数据。
1.查询
查询操作是根据rowkey返回时间戳最大的数据。假如我们要查询rowkey=150的这条记录,首先从zk中获取hbase:meta表(存放region和key的对应关系的元数据表)的位置,通过查询meta表得知rowkey=150的数据在哪个server的哪个region上。
2.插入
上图粗略的展示了HBase的region的结构,region不单单是一个文件,它是由一个memstore和多个storeFile组成(storeFile上的上限可以配置)。插入数据时首先将数据写入memstore,当memstore大小达到一定阈值,将memstore flush到硬盘,变成一个新的storeFile。flush的时候会对memstore中的数据进行排序,压缩等操作。可以看到单个storeFile中的数据是有序的,但是region中的storeFile间的数据不是全局有序的。
这样有的好处就是:不管主键是否连续,所有的插入一律变成顺序写,大大提高了写入性能。
看到这里大家可能会有一个疑问:这种写入方式导致了一条记录如果不是一次性插入,很可能分散在不同的storeFile中,那在该region上面查询一条记录时,怎么知道去找哪个storeFile呢?答案就是:全部查询。HBase会采用多路归并的方式,对该region上的所有storeFile进行查询,直到找到符合条件的记录。所以HBase的拥有很好的写入性能,但是读性能较差。
当然HBase也做了很多优化,比如每个storeFile都有自己的index、用于过滤的bloom filter、compaction:按可配置的方式将多个storeFile合并成一个,减少检索时打开的文件数。
3.更新 & 删除
修改操作其实是根据时间戳重新put一条数据。删除操作其实是PUT一条操作类型为DEL的数据。(当查询时发现该row key的最大时间戳记录操作类型为DEL,则不返回数据,给用户造成的感觉就是已删除该row key)。因此也造成数据冗余。
HBase将更新和删除也全部看做插入操作,用timestamp和delete marker来区分该记录是否是最新记录、是否需要删除。也正是因为这样,除了查询,其他的操作统一转换成了顺序写,保证了HBase高效的写性能。
(2)适用场景:
mysql与hbase 解决问题定位不同
MySQL解决应用的在线事务问题。
Hbase解决大数据场景的海量存储问题。
HBase 不是 MySQL 的替换,HBase 是业务规模及场景扩张后,对 MySQL 的自然延伸。
参考文章:
https://www.cnblogs.com/datadance/p/16327298.html
一文读懂Hive底层数据存储格式(好文收藏)-腾讯云开发者社区-腾讯云
https://cloud.tencent.com/developer/article/1888531
https://www.cnblogs.com/ityouknow/p/7344001.html
从原理到应用解释MYSQL和HBASE区别 - 掘金
入门Hbase,看这一篇就够了 - 掘金
3.es
(1)写流程
a. 先写入buffer,在buffer里的时候数据是搜索不到的;同时将数据写入translog日志文件
b. 如果buffer快满了,或者到一定时间,就会将buffer数据refresh到一个新的segment file中,但是此时数据不是直接进入segment file的磁盘文件的,而是先进入os cache的。这个过程就是refresh。
c.只要数据进入os cache,此时就可以让这个segment file的数据对外提供搜索了
d. 如果是删除操作,commit的时候会生成一个.del文件,里面将某个doc标识为deleted状态,那么搜索的时候根据.del文件就知道这个doc被删除了
e. 如果是更新操作,就是将原来的doc标识为deleted状态,然后新写入一条数据
f. buffer每次refresh一次,就会产生一个segment file,所以默认情况下是1秒钟一个segment file,segment file会越来越多,此时会定期执行merge
g. 每次merge的时候,会将多个segment file合并成一个,同时这里会将标识为deleted的doc给物理删除掉,然后将新的segment file写入磁盘,这里会写一个commit point,标识所有新的segment file,然后打开segment file供搜索使用,同时删除旧的segment file。
es里的写流程,有4个底层的核心概念,refresh、flush、translog、merge
(2)读流程
a. 客户端发送请求到一个coordinate node
b. 协调节点将搜索请求转发到所有的shard对应的primary shard或replica shard也可以
c. query phase:每个shard将自己的搜索结果(其实就是一些doc id),返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果
d. fetch phase:接着由协调节点,根据doc id去各个节点上拉取实际的document数据(根据doc id进行hash路由到对应的primary shard上面去),最终返回给客户端
(3)分页
如果每页展示 5 条结果,可以用下面方式请求得到 1 到 3 页的结果:
GET /_search?size=5
GET /_search?size=5&from=5
GET /_search?size=5&from=10
Size:显示应该返回的结果数量,默认是 10
From:显示应该跳过的初始结果数量,默认是 0
深度分页
理解为什么深度分页是有问题的,我们可以假设在一个有 5 个主分片的索引中搜索。
当我们请求结果的第一页(结果从 1 到 10 ),每一个分片产生前 10 的结果,并且返
回给协调节点 ,协调节点对 50 个结果排序得到全部结果的前 10 个。现在假设我们请
求第1000 页—结果从 10001 到 10010 。所有都以相同的方式工作除了每个分片不得不
产生前10010个结果以外。 然后协调节点对全部 50050 个结果排序最后丢弃掉这些结果
中的50040 个结果。可以看到,在分布式系统中,对结果排序的成本随分页的深度成指数
上升。这就是 web 搜索引擎对任何查询都不要返回超过 1000 个结果的原因。
(4)倒排索引
它适用于快速的全文搜索。一个倒排索引由文档中所有不重复词的列表构成,对于其中每个
词,有一个包含它的文档列表。
Term | Doc 1 | Doc 2 | Doc 3 | ...
------------------------------------
brown | X | | X | ...
fox | X | X | X | ...
quick | X | X | | ...
the | X | | X | ...
要构建倒排索引,需要对文档内容分析并分词,分析过程如下:
首先,将一块文本分成适合于倒排索引的独立的 词条 ,
之后,将这些词条统一化为标准格式以提高它们的“可搜索性”。
分析器执行上面的工作,由以下三部分构成:
字符过滤器:
首先,字符串按顺序通过每个 字符过滤器 。他们的任务是在分词前整理字符串。一个
字符过滤器可以用来去掉HTML,或者将 & 转化成 and。分词器:
其次,字符串被 分词器 分为单个的词条。一个简单的分词器遇到空格和标点的时候,
可能会将文本拆分成词条。Token 过滤器:
最后,词条按顺序通过每个 token 过滤器 。这个过程可能会改变词条(例如,小写
化 Quick ),删除词条(例如, 像 a, and, the 等无用词),或者增加词条(例如,
像 jump 和 leap 这种同义词)。
Elasticsearch 中的文档是有字段和值的结构化 JSON 文档。事实上,在 JSON 文档中, 每个被索引的字段都有自己的倒排索引。
(5)扩容
方案:
新建索引 + 索引别名 +多索引检索
依据:
搜索 1 个有着 50 个分片的索引与搜索 50 个每个都有 1 个分片的索引完全等价:
搜索请求均命中 50 个分片
步骤:
a、假设原有索引tweets_1只有一个分片;
b、为索引添加别名:tweets_search 、tweets_index
PUT /tweets_1/_alias/tweets_search
PUT /tweets_1/_alias/tweets_index
c、tweets_1容量不够,需新建索引tweets_2,规划5个分片;d、别名指向新的索引;
POST /_aliases
{
"actions": [
{ "add": { "index": "tweets_2", "alias": "tweets_search" }},
{ "remove": { "index": "tweets_1", "alias": "tweets_index" }},
{ "add": { "index": "tweets_2", "alias": "tweets_index" }}
]
}
说明:
tweets_search:指向tweets_2、tweets_1两个索引。
(一个搜索请求可以以多个索引为目标,所以将搜索别名指向 tweets_1 以及
tweets_2 是完全有效的)
tweets_index:由 tweets_1 切换至 tweets_2
(索引写入请求只能以单个索引为目标,必须将索引写入的别名只指向新的索引)
4.redis
(1)特点
Redis是一个完全开源、遵守BSD协议、简单、高效的、分布式的、基于内存的k-v数据库。
- 性能极高 -Redis读速度110000次/s,写的速度81000次/s。
- 丰富的数据类型 -Redis支持的数据类型 String,Hash,List,Set及Ordered Set数据乐西操作。
- 原子性 -Redis的所有的操作都是原子性的,意思就是要么成功,要么失败。单个操作是原子性的,多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
- 丰富的特性 -Redis还支持 publis/subscribe,通知,key过期等等特性
- 高速读写 -Redis使用自己实现的分离器,代码量很短,没有使用lock(Mysql),因此效率非常高。
(2)应用场景
1、缓存
缓存现在几乎是所有大中型网站都在用的必杀技,合理利用缓存提升网站的访问速度,还能大大降低数据库的访问压力。Redis提供了键过期功能,也提供了灵活的键淘汰策略,所以,现在Redis用在缓存的场合非常多。2、排行榜
Redis提供的有序集合数据类结构能够实现复杂的排行榜应用。3、计数器
视频网站的播放量,每次浏览+1,并发量高时如果每次都请求数据库操作无疑有很大挑战和压力。Redis提供的incr命令来实现计数器功能,内存操作,性能非常好,非常适用于这些技术场景。4、分布式会话
相对复杂的系统中,一般都会搭建Redis等内存数据库为中心的session服务,session不在由容器管理,二十由session服务及内存数据管理。5、分布式锁
在并发高的场景中,可以利用Redis的setnx功能来编写分布式锁,如果设置返回1,说明获得锁成功,否则获得锁失败。6、社交网络
点赞、踩、关注/被关注,共同好友等是社交网站的基本功能,社交网站的访问量通常来说比较大,而且传统的关系数据库不适合这种类型的数据,Redis提供的哈希,集合等数据结构很方便的实现这些功能。7、最新列表
Redis列表结构,LPUSH可以在列表头部插入一个内容ID作为关键字,LTRIM可以用来限制列表的数量,这样列表永远为N,无需查询最新的列表,直接根据ID去对应的内容即可。8、消息系统
消息队列是网站经常用的中间件,如ActiveMQ,RabbitMQ,Kafka等流行的消息队列中间件,主要用于业务解耦,流量削峰以及异步处理实时性低的业务,Redis提供了发布/订阅及阻塞队列功能,能实现一个简单的消息队列系统。另外,这个不能和专业的消息中间件相比。
(3)数据类型
Redis支持的数据类型:
- string(字符串)
- hash(哈希)
- list(列表)
- set(集合)
- zset(sorted set:有序集合)等
(4)Redis 缓存雪崩、缓存击穿、缓存穿透
1.缓存雪崩
当大量缓存数据在同一时间过期(失效)或者 Redis 故障宕机时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增。
解决方案:
(1)缓存失效时,加锁排队
(2)数据预热
2.缓存击穿
如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题。
解决方案:
(1)
互斥锁方案
,保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。(2)
不给热点数据设置过期时间
,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间;3.缓存穿透
当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透的问题。
解决方案:
(1)限制非法请求
(2)缓存空值或默认值
(3)使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在
(5)redis数据结构以及实现原理
万字长文的Redis五种数据结构详解(理论+实战),建议收藏。 - 文章详情
超详细Redis数据结构底层实现原理介绍_Java_蘑菇睡不着_InfoQ写作社区
Redis 键(key) | 菜鸟教程
Redis 原理+知识点总结_redis计数器_冷岫烟的博客-CSDN博客
布隆过滤器的原理是什么? - 知乎
5.消息中间件kafka
(1)基本概念
(2) 生产者
生产者将消息发送到topic中去,同时负责选择将message发送到topic的哪一个partition中。通过round-robin做简单的负载均衡。也可以根据消息中的某一个关键字来进行区分。通常第二种方式使用的更多。
(3)消费者
消费模式
传统的消息传递模式有2种:队列( queue) 和(publish-subscribe)
- queue模式:多个consumer从服务器中读取数据,消息只会到达一个consumer。
- publish-subscribe模式:消息会被广播给所有的consumer。
Kafka基于这2种模式提供了一种consumer的抽象概念:consumer group。
- queue模式:所有的consumer都位于同一个consumer group 下。
- publish-subscribe模式:所有的consumer都有着自己唯一的consumer group。
消费顺序
- 一个partition同一个时刻在一个consumer group中只能有一个consumer instance在消费,从而保证消费顺序。
- consumer group中的consumer instance的数量不能比一个Topic中的partition的数量多,否则,多出来的consumer消费不到消息。
- Kafka只在partition的范围内保证消息消费的局部顺序性,不能在同一个topic中的多个partition中保证总的消费顺序性。
- 如果有在总体上保证消费顺序的需求,那么我们可以通过将topic的partition数量设置为1,将consumer group中的consumer instance数量也设置为1,但是这样会影响性能,所以kafka的顺序消费很少用。
(4)rebalance
rebalance就是说如果消费组里的消费者数量有变化或消费的分区数有变化,kafka会重新分配消费者消费分区的关系。比如consumer group中某个消费者挂了,此时会自动把分配给他的分区交给其他的消费者,如果他又重启了,那么又会把一些分区重新交还给他。
如下情况可能会触发消费者rebalance
- 消费组里的consumer增加或减少了
- 动态给topic增加了分区
- 消费组订阅了更多的topic
rebalance过程中,消费者无法从kafka消费消息,这对kafka的TPS会有影响,如果kafka集群内节点较多,比如数百个,那重平衡可能会耗时极多,所以应尽量避免在系统高峰期的重平衡发生。
消费者Rebalance分区分配策略:
主要有三种rebalance的策略:range、round-robin、sticky。
Kafka 提供了消费者客户端参数partition.assignment.strategy 来设置消费者与订阅主题之间的分区分配策略。默认情况为range分配策略。
(5)应用场景
日志收集:一个公司可以用Kafka收集各种服务的log,通过kafka以统一接口服务的方式开放给各种consumer,例如hadoop、Hbase、Solr等。
消息系统:解耦和生产者和消费者、缓存消息等。
用户活动跟踪:Kafka经常被用来记录web用户或者app用户的各种活动,如浏览网页、搜索、点击等活动,这些活动信息被各个服务器发布到kafka的topic中,然后订阅者通过订阅这些topic来做实时的监控分析,或者装载到hadoop、数据仓库中做离线分析和挖掘。
运营指标:Kafka也经常用来记录运营监控数据。包括收集各种分布式应用的数据,生产各种操作的集中反馈,比如报警和报告。
(6)高性能原因
- 磁盘顺序读写:kafka消息不能修改以及不会从文件中间删除保证了磁盘顺序读,kafka的消息写入文件都是追加在文件末尾,不会写入文件中的某个位置(随机写)保证了磁盘顺序写。
- 数据传输的零拷贝
- 读写数据的批量batch处理以及压缩传输
Kafka基础原理 - 简书
浅析操作系统和Netty中的零拷贝机制
二. 大数据
1.presto
Presto是FaceBook开源的分布式SQL引擎,适用于交互查询分析,Presto 本身并不存储数据,但是可以接入多种数据源,并且支持跨数据源的级联查询。Presto是一个OLAP的工具,擅长对海量数据进行复杂的分析;但是对于OLTP场景,并不是Presto所擅长,所以不要把Presto当做数据库来使用。
2.click house
ClickHouse 是由俄罗斯搜索引擎公司 Yandex 开发的一个列式数据库管理系统,它专注于大规模数据的快速查询和分析。
特性:采用列式存储;数据压缩;基于磁盘的存储,大部分列式存储数据库为了追求速度,会将数据直接写入内存,按时内存的空间往往很小;CPU 利用率高,在计算时会使用机器上的所有 CPU 资源;支持分片,并且同一个计算任务会在不同分片上并行执行,计算完成后会将结果汇总;支持SQL,SQL 几乎成了大数据的标准工具,使用门槛较低;支持联表查询;支持实时更新;自动多副本同步;支持索引;分布式存储查询。
适用于单宽表查询。
-
clickhouse为什么如此快
1)优秀的代码,对性能的极致追求
clickhouse是CPP编写的,代码中大量使用了CPP最新的特性来对查询进行加速。
2)优秀的执行引擎以及存储引擎
clickhouse是基于列式存储的,使用了向量化的执行引擎,利用SIMD指令进行处理加速,同时使用LLVM加快函数编译执行,当然了Presto也大量的使用了这样的特性。
3)稀疏索引
相比于传统基于HDFS的OLAP引擎,clickhouse不仅有基于分区的过滤,还有基于列级别的稀疏索引,这样在进行条件查询的时候可以过滤到很多不需要扫描的块,这样对提升查询速度是很有帮助的。
4)存储执行耦合
存储和执行分离是一个趋势,但是存储和执行耦合也是有优势的,避免了网络的开销,CPU的极致压榨加上SSD的加持,每秒的数据传输对于网络带宽的压力是非常大的,耦合部署可以避免该问题。
5)数据存储在SSD,极高的iops。
百度安全验证
趣头条基于ClickHouse玩转每天1000亿数据量 - 知乎