问题:大规模数据如何检索
- 当系统数据量达到10亿,100亿级别的时候,我们系统该如何去解决这种问题。
- 数据库选择—mysql, sybase,oracle,mongodb,hbase…
- 单点故障如何解决—lvs, F5,A10,Zookeeper,MQ
- 如何保证数据安全----热备,冷备,异地多活
- 如何解决检索难题—数据库中间件mysql-proxy,sharding, cobar, MaxScale
- 如何积极统计分析问题—离线计算,近实时
- 传统数据库的应对解决方案,对于关系型数据库,通常用以下架构解决查询瓶颈
- 通过主从备份解决数据安全性问题
- 通过数据库代理中间件心跳检测,解决单点故障问题
- 通过代理中间件将查询语句分发到每个slave节点查询,并且汇总结果
- 非关系型数据库解决方案,用MongoDB为案例,其他的原理类似
- 通过副本备份积极数据安全性问题
- 通过节点竞选机制解决单点问题
- 先从配置库检索分片信息,让后请求分发到各个节点,最后由路由节点合并汇总结果。
- 用内存数据库
- 不可能完全在内存中放所有数据,单达到PB级别的时候,每个节点按256G,在内存完全装满情况下我们需要如下个机器 1PB = 1024T = 1024*1024G,节点1024 * 4 = 4096个,加上备份的数据,节点应该会超过4096 * 2 = 8192个,太费钱
- 不管是否全部放内存都不能完全解决问题,用一下方式
- 存储数据按有序存储
- 将数据和索引分离、
- 压缩数据
luncene与 ES
- luncene只是一个检索库,要想使用必须用java开发并且集成到你项目中,并且luncene非常复杂,需要深入了解检索的相关知识来理解他是如何工作的。
- Elasticsearch使用luncene作为底层查询,用java开发,通过RESTful api来隐藏Lucene的复杂性,使用只需关注api就行,降低接入成本以及复杂度。
ES主要解决问题
- 检索相关数据
- 返回统计结果
- 速度快
原理
- 当ElasticSearch的节点启动后,会利用多播(multicast)或者单播(配置修改)寻找集群中其他节点,并且建立连接,过程如下:
核心概念
- 集群:ES可以作为独立的搜索服务器,不过处理的数据局限于单点存储功能,可以通过多借点相互合作的多服务器上,实现大小数据集检索,实现容错和高可用。
- 分片:文档巨大,内存限制,磁盘处理能力不足,无法足够快的响应查询,一个节点存储不够。这些情况,数据可以分开成多个较小的分片,每个分片在不同的服务器上。单查询的索引分布在多个分片上,ES会将查询发送给每一个分片,并将结果组合起来,对应用透明。
- 副本:为提供吞吐量或者实现高可用,可以使用分片副本,副本是一个分片的Slave,每个分片可以有多个副本,ES可以有许多相同的分片,其中一个被选中更改索引操作,这种分片是主分片。单主分片丢失,集群将副本某一个提升为主分片
- 全文检索:将内容根据词的意义进行分词,让后分别创建索引例如:
- “你们的动力是因为什么事情来的” 可分为:“你们”,“动力”,“什么事情”,“来”等token,当搜索你们,或者动力时候都会讲这个段搜索出来。
ES数据架构主要概念(mySql对比)
- 关系型数据库中数据库(DB)等价es中索引(index)
- 一个数据库下面N张表(table),等价1个索引下N多属性(Type)
- 一个数据库表(table)下的数据由多行,多列组成,等价es中1个Type由多Document和多个Field组成
- 关系型数据库里面,Schema定义了表,每个表的字段,还有表与字段直接的关系,与之对应的ES中:Mapping定义索引下的Type的字段处理规则,即索引如何建立,索引类型,是否保存原始索引JSON文档,是否压缩原始JSON文档,是否需要分词处理,如何进行分词等。
- 数据库中CURD操作等价ES中 增PUT/POST,删Delete,改_update,查GET(标准RESTful)
ELK: elasticsearch+Logstash+kibana
- elasticsearch:后台分布式存储以及全文检索
- logstash:日志加工,日志统计
- kibana:数据可视化展示
- ELK架构为数据分布式存储,可视化查询和日志解析创建了一个功能强大的管理链,三者相互配合完成分布式大数据处理工作。
常见的增、删、改、查操作实现
性能esrally工具
[新浪ES 如何分析处理32亿条实时日志 ]( <http://dockone.io/article/505 )
阿里ES 构建挖财自己的日志采集和分析体系
有赞ES 业务日志处理
ES实现站内搜索
特定优势
- 分布式实时文件存储,索引可到某个字段级别检索
- 实时分析的分布式搜索引擎
- 分布式:索引拆分成多个分片,每个分片可有多个副本,集群每个数据节点都可以承载一个或多个分片,并且协调和处理各种操作。
- 负载再平衡和路由大多数情况自动完成
- 可扩展到上百条服务器,处理PB级别结构化或者非结构化数据,也可单机
- 支持插件机制,分词插件,hadoop插件,可视化插件等
倒排索引
- 搜索引擎通常检索的场景是:给的几个关键字,找出包含关键字的文档。怎么快速找到包含某个关键字的文档就成为搜索的关键,倒排索引源于实际应用中需要根据属性的值来查找记录,lucene是基于倒排索引实现的,这种索引表中的每一项都包括一个属性值和具有改属性值的各个记录的地址,由于不是由记录来确认属性值,而是由属性值来确定记录的位置,所以称为倒排索引
正排索引
- 正排索引是以文档的ID为关键字,表中记录文档中每个字的位置信息,查找时候扫描表中每个文档中字的信息直到找出所有包含查询关键字的文档。查询效率相对来说不高。
总结:倒排索引记录了某个关键字在那些文档中,正排索引记录了文档包含了哪些关键字
问题:倒排索引比关系型数据库的b-tree索引快在哪里?
文档
- Elasticsearch是面向文档的,那么搜索和索引数据的最小单元就是文档,在Elasticsearch中文档有以下几个重要的属性:
- 文档是自我包含的,一篇文档包含字段,已经他们的值
- 他可以是层次结构。比如一个JSON个是保存的文档,他某个字段里面还是一个JSON格式,这就是层次结构
- 有灵活的结构,文档不依赖预先定义的模式也就是非schema的形式,也就是在同一个类型下的文档并不一定都包含改类型下的所有字段。
- 尽管我们可以随意的添加数据和忽略数据,但是每个字段的类型在Elasticsearch 中是很重要的:某个字段是字符串,某些是整数等。这一点Elasticsearch 在添加数据的时候,会将字段和类型之间做映射这就是我们说的建立Mapping。这样的映射具体到每个索引的每种类型上面。在Elasticsearch 中也叫作映射类型。
类型
- 类型是文档的逻辑容器,类似表格是行的容器。不同类型中,最好放入不同结构的文档。例如一个类型定义A分组,而另一个类型定义B
- 每个类型中字段的定义就是我们说的Mapping映射。例如name字段可以映射为String。而location中的geolocation可以映射为get_point类型。每种字段都通过不同方式进行处理。例如我们可以在name字段中搜索匹配的关键字,同时根据位置类搜索哪些分组离我的地理位置更近
- 问题在于Elasticsearch 是无模式非schema形式的数据,为什么文档属于一种类型,二每个类型都是类似模式的映射(字段):
- 因为非schema形式是针对文档的,表示 文档是不受模式限制。并不是每个文档都需要包含他所在类型中的所有字段,而是类型映射应该包含他所有文档的所有字段。这就是类型与文档的关系。
- 同样道理,如果一篇新的文档添加后有一个映射不在本类型的字段中,Elasticsearch会自动的将新字段键入映射。为了添加这个字段,Elasticsearch需要先确定这个类型,Elasticsearch猜测类型,例如如果是一个整数7 ,他会将类型建立为整型,所以es的写入性能是不高的,他需要刷新Mapping
- 这种实时刷新Mapping的方式确定在于,在第一次定义7 这个字段是整型后,我对字段进行搜索hello,那么索引会失败,他不是String。对应线上的环境最安全的方式就是先导入数据,建立Mapping后在去搜索
- 类型的Mapping映射只是将文档进行了逻辑划分,从物理上看同一个索引中的文档都是写入磁盘中,而不会考虑他们所在的映射类型。
索引
- 索引是映射类型的容器。一个Elasticsearch索引就像mySql中的数据库级别。是独立的大量文档数据的集合。每个索引存储在磁盘上的同组文件中;索引存储了所有映射的字段。还有一些设置。
- 例如每个索引有一个称为refresh_interval的设置,定义了新进的索引的文档对于搜索的可见的时间间隔。从性能上看,刷新的操作代价是比较大的。这也是为什么更新只能是偶尔进行。默认是一次性导入,而不是逐步的同步,Elasticsearch被称为准实时的,就是因为有这个刷新时间
映射的定义
- 映射的定义就是Mapping建立的一个过程,我会将文档归类,将文档中的每个字段归类到每一个类型中,并且设置改文档的每个字段的查询设置
定义文档字段的核心类型
- 字符串类型
- 字符串是最直接的:如果索引字符,字段就是string类型,也是查询最丰富的的一种类型,因为映射有很多选项设置来分析他
- 解析文本,转变文本,分解为基本元素使得搜索更为相关,这个过程就是分析
- 例如搜索的name字段,当索引到文档的name时候,默认分析器将改文档的内容转为小写,然后将字符分解为单词
- 映射的作用就是在分析过程起作用,可以依据需求来设置映射分析的策略,例如index选项可以设置为:
- anayzed(默认):分析器将所有字符转小写,并且将字符串分解为单词
- not_analyzed:分析过程被略过,整个字段需要全量匹配
- no:索引略过该字段。
- 数值类型
- 包括byte,short, int, long数值型 float, double浮点型
- 这些对于的java原始数据类型,显然字段选择会影响索引的大小,在存储数据的索引中,字符占用内存越大,数据量也就越大,最好选择合适的数据类型
- 日期类型
分析数据
- 分析(analysis)是在文档被发送并加入倒排索引之前,Elasticsearch在其数据上进行的一个处理。
- 数据添加到索引前做的事情:
- 字符过滤: 使用字符过滤器转变字符
- 文本切分成词组: 将文本切分成单个的单词
- 分词过滤: 使用分词过滤转变每个分词
- 分词索引: 将这些分词存储到索引中。
- 整个流程如下图:
字符过滤
- Elasticsearch运行字符过滤器,将特定字符列转换为其他字符序列。可以将HTML从文本中剥离,或者将任意字符转化为其他字符(例如“ I LOVE u 2” 转为 “i love you too”)如上我们设置特殊的将&符合转为 and
切分为分词
- 文本需要被分割成可以操作的片段。Lucene自己不会对大块的字符串数据进行操作,他直接处理被称为分词(token)的数据。分词是从文本片段生成的,可以产生任意数量,
分词过滤器
- 被分词后得到的token,Elasticsearch会将每个分词运用分词过滤器,他可以将一个分词作为输入,然后更具需要进行修改,添加,删除。最有用的和常用的分词过滤器是小写分词过滤器将输入分词转为小写,这样我们早修改noSql的时候也可以同时发现NoSql的聚合。
分词索引
- 当温度经过0~n个分词过滤器后,将发送到lucene进行文档索引。
- 以上所有的不同组件部分组成了一个分析器(analyzer),Elasticsearch提供了很多这种分析器可以直接使用而不用构建自己的分析器
内置分析器
- 一个分析器包括一个可选的字符过滤器,一个单个分词器,0个或者多个分词过滤器
- 如下有几种分析器,每种功能都不同
- 标准分析器:文本的默认分析器包括 标准分析器,标准分词过滤器,小写转换分词过滤器和停用分词过滤器
- 简单分析器:只永小写转换分词器
- 空白分析器:什么都不做,值更具空包将文本进行拆分
- 停用词分析器:在分词流中过滤停用词
- 关键词分析器:将整个字段当成单独一个分词
- 模式分析器:
- 语言和多语言分析器:Elasticsearch能直接使用的特点语言分析器包括汉语等
- 雪球分析器
分词器
-
以上说过,分词器将文本分解成小块token,Elasticsearch 内部也包含了一些内置的分词器
- 标准分词器:基于语法的分词器,针对英语等欧洲语法
'I hava , potatoes.'切分后 I,have, potatoes
- 关键词分词器:简单的将文本作为单个的单词提供给分词过滤器,但我们不想在拆分的时候可以用
'Hi, there'切分后 Hi, there
- 字母分词器:更具妃子们的符号,将文本切分为分词,去掉符号等
'Hi, there'切分后 hi 与 there
- 小写分词器:结合了常规字母分词器和小写分词过滤器,功能结合提升性能
'Hi, there'切分后 Hi, 与 there
- 空白分词器:通过空白分割不同分词,空白包括:空格,制表符,换行符
- 模式分词器:
- UAX URL 电子邮件分词器
- 路劲层次分词器
分词过滤器
- Elasticsearch中有很多分词过滤器,如下图三个简单的分词过滤
- 标准分词过滤器:什么都没做
- 小写分词过滤器:将任何进过的分词转小写
- 长度分词过滤器:将长度不符合配置范围内的单词过滤,可设置
- 停用词分词过滤器:将停用词从分词中删除,针对英文的过滤
- 截断分词过滤器,修剪分词过滤器和限制分词数量过滤器:
- 截断分词过滤器:运行通过盯着配置中length参数,截断超过一定长度分词,默认配置是阶段多余10个字符的部分
- 修剪分词过滤器:删除一个分词中所有空包
- 限制分词数量分词过滤器:现在某个字段可能包含分词的最大数量,比如文本太长,限制8 ,那么就只会有8个分词被索引
- 颠倒分词过滤器:允许一个分词流并且颠倒每个单词
- 唯一分词过滤器:
- ASCII折叠分词过滤器
- 同义词分词过滤器:在分词流中的同样位移处,用关键词的同义词取代原始词
Mapping构建总结
- ElasticSearch 的查询是基于 luncene实现的,但是luncene只是一个检索库,他能处理的数据单元并非是整个文档类型,lucene是基于倒排索引实现的,这种索引表的结构特点在于,表中的每个属性都对应各个记录的具体文档位置,这样可以直接通过查询属性key来找到对应的文档集合。
- ElasticSearch对数据的处理就是要将普通的文档 处理成 (属性-- 结果地址)这种数据字典,同时对字段进行一些特殊设置这就是Mapping建立过程
- 在文档添加的时候,将结构相似的文档归为同一个类型下,该类型包含了所有文档中的每个属性
- 在添加到文档之前,文档需要经过ElasticSearch分析器的处理,它包括字符过滤器,分词器,分词过滤器,分别是字符过滤,分词,特定功能呢过滤的能力,每个功能都内置了不同功能的插件,可以依据业务的需要选择,也可以选择外部插件,例如中文IK分词器就是在分词阶段起作用。最后将得到的分词存储到索引中。如下图示意流程
- 每个索引可能有多个分类,而每个分类下是包含本类别文档的所有分词信息,这样就得到了如下模式的一个数据结构
倒排索引性能总结
- 在为属性(field)构建倒排索引后,此时,本类别中包含了所有文档中所有字段的一个 分词(term) 文档id对应关系的字典信息
- 通过倒排索引,我们可以迅速找到符合添加的文档,例如peace在文档 2,3,4 中。
- 当我们进行Elasticsearch查询,为了能快速找到某个term,ElasticSearch 将类型中所有的term进行排序,然后通过二分法查找term,时间复杂度能达到 logN的查找效率,就像通过字典查找一样,这就是Term Dictionary
- 同时参照 B-Tree通过减少磁盘寻道次数来提高查询性能,Elasticsearch也是采用同样的思路,直接通过内存查找term,将term Dictionary这个构建的Mapping存放在内存中。但是如果term太多,term dictionary也会很大,放内存不现实,于是有了Term Index,就像字典里的索引页一样,A开头的有哪些term,分别在哪页,可以理解term index是一颗树如下图:
-
如上图中数据结构是字典树,同时也叫tire树, 有如下结构特性:
- 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
- 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
- 每个节点的所有子节点包含的字符都不相同。
-
如上第二点中每个节点都只包含一个字符,如图中te 是结合了第一个节点t, 和本节点e,得到的一个结果是te,本节点此时值存储了一个字符e
-
字典树平均时间复杂度:复杂度为O(n*len),实际查询的复杂度也只是O(len)。(Trie树的平均高度h为len,所以Trie树的查询复杂度为O(h)=O(len)
-
树节点存储的是term的前缀信息,通过前缀查询关键字在term Dictionary中的偏移量,然后在遍历对应的Term Dictionary 对应的这段数据找到具体的term, 存储的是Term前缀 与 Term Dictionary 的block之间的映射关系
-
在此基础上在对内存中的Term index进行数据压缩,这样的话用时间换空间,的方式减少内存的占用,这样就可以完全在内存中找到对应的文档id,最后去磁盘读取对应的文档信息。磁盘随机读的次数
-
额外的知识:
- MySql Innodb存储引擎在1.2版本后也支持了全文检索,通MyISam存储引擎一样,在创建全文检索所有后,mysql会将对应字段进行分词
- 分词后的分词,文档id字典信息存储在六张表中
- 同样的,MySql全文检索为了优化查询,将全文检索索引(也就是字典)缓存在内存中,也同样的是通过内存区查询
- MySql此时也同样用来前缀存储的方法去减少内存的占用,但是这里MySql并不是和es一样用的term index的字典表,有MySql用的红黑树来存储term directionary
- 并且MySql还做了数据压缩处理,在红黑树中得到的前缀是sab,找到对应字段offset后的所有对应的数据存储的方式是将sab省略的后半部分,以这种方式来减少磁盘的占用
- MySql 还对全文索引对应的字段的insert, delete操作进行了优化,因为我们真正查询的时候是用的字典,并不是全文检索本文
- 当insert的时候,MySql对全文索引字段进行分词,此时并不会直接将新的字段数据更新到字典表,而是直接更新全文检索索引缓存,只有在正在用到索引缓存进行数据查询的时候,才会真正更新到对应的字典表中
- MySql对删除有同样的处理,当我们删除一个文档时候,MySql会将文档id添加到一张删除记录表中,并且更新对应的索引缓存,还是在查询的时候,更新字典表。
- 红黑树查询平均时间复杂度是O(logn)