Elasticsearch 入门应用
一、搜索
概念:用户输入想要的关键词,返回含有该关键词的所有信息。
场景:
- 互联网搜索:谷歌、百度、各种新闻首页
- 站内搜索(垂直搜索):企业OA查询订单、人员、部门,电商网站
- 内部搜索商品(淘宝、京东)
二、数据库做搜索弊端
数据量小,简单搜索,可以使用数据库,但是数据量太大-PB级:
-
存储问题:电商网站商品上亿条时,涉及到单表数据过大必须拆分表,数据库磁盘占用过大必须分库 。
-
性能问题:解决上面问题后,查询“手机”等关键词时,上亿条数据的商品名字段逐行扫描,性能跟不上。
-
不能分词:如搜索“笔记本电脑”,只能搜索完全和关键词一样的数据。
三、全文检索
倒排索引:数据存储时,进行分词建立term索引库。倒排索引源于实际应用中需要根据属性的值来查找记录。这种索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。
由于不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引(inverted index)。带有倒排索引的文件我们称为倒排索引文件,简称倒排文件(inverted fifile)。
四、强大的Elasticsearch(后面简称ES)
参考官网
4.1、ES的功能
分布式的搜索引擎和数据分析引擎:
- 搜索:互联网搜索、电商网站站内搜索、OA系统查询
- 数据分析:电商网站查询近一周哪些品类的图书销售前十;新闻网站,最近3天阅读量最高的十个 关键词,舆情分析。
- 全文检索,结构化检索,数据分析
- 对海量数据进行近实时的处理
- 分布式:ES自动可以将海量数据分散到多台服务器上去存储和检索,进行并行查询,提高搜索效率。相对的,Lucene是单机应用。
- 近实时:数据库上亿条数据查询,搜索一次耗时几个小时,是批处理(batch-processing)。而es只需秒级即可查询海量数据,所以叫近实时。秒级。
4.2、ES的使用场景
- 维基百科
- GitHub(开源代码管理),搜索上千亿行代码。
- 日志数据分析,logstash采集日志,ES进行复杂的数据分析(ELK技术,elasticsearch+logstash+kibana)
4.3、ES的特点
- 可拓展性:大型分布式集群(数百台服务器)技术,处理PB级数据,大公司可以使用。小公司数据量小,也可以部署在单机。大数据领域使用广泛。
- 技术整合:将全文检索、数据分析、分布式相关技术整合在一起:lucene(全文检索),商用的数据分析软件(BI软件),分布式数据库(mycat)
- 部署简单:开箱即用,很多默认配置不需关心,解压完成直接运行即可。拓展时,只需多部署几个实例即可,负载均衡、分片迁移集群内部自己实施
- 接口简单:使用restful api进行交互,跨语言。
- 功能强大:Elasticsearch作为传统数据库的一个补充,提供了数据库所不不能提供的很多功能,如全文检索,同义词处理,相关度排名。
4.4、ES核心概念
ES基于lucene,封装了许多lucene底层功能,提供简单易用的restful api接口和许多语言的客户端,如java的高级客户端(Java High Level REST Client)和底层客户端(Java Low Level REST Client)
- NRT(Near Realtime):近实时
- 写入数据时,过1秒才会被搜索到,因为内部在分词、录入索引。
- es搜索时,搜索和分析数据需要秒级出结果。
- Cluster:集群
包含一个或多个启动着es实例的机器群。通常一台机器起一个es实例。同一网络下,集群名一样的多个es实例自动组成集群,自动均衡分片等行为。默认集群名为“elasticsearch”。 - Node:节点,每个es实例称为一个节点。节点名自动分配,也可以手动配置。
- Index:索引,包含一堆有相似结构的文档数据。
- Document:文档,es中的最小数据单元。一个document就像数据库中的一条记录。通常以json格式显示。多个document存储于一个索引(Index)中
- Field:字段,就像数据库中的列(Columns),定义每个document应该有的字段
- Type:类型,每个索引里都可以有一个或多个type,type是index中的一个逻辑数据分类,一个type下的document,都有相同的fifield。7.x版本正式被去除。
- shard:分片,index数据过大时,将index里面的数据,分为多个shard,分布式的存储在各个服务器上面。可以支持海量数据和高并发,提升性能和吞吐量,充分利用多台机器的cpu。
- replica:副本,在分布式环境下,任何一台机器都会随时宕机,如果宕机,index的一个分片没有,导致此index不能搜索。所以,为了保证数据的安全,我们会将每个index的分片进行备份,存储在另外的机器上。保证少数机器宕机es集群仍可以搜索。能正常提供查询和插入的分片我们叫做主分片(primary shard),其余的我们就管他们叫做备份的分片(replica shard)。
4.5、ES核心原理
- 文件储存,一条数据在这里就是一个文档,用JSON作为文档序列化的格式
- 索引,ES最核心的就是提供强大的索引能力了,一句话来总结:一切设计都是为了提高搜索的性能,也就是让我们的搜索更快
ES的倒排索引相比数据库B+树在结构上有什么优势?
例如:三条结构化数据
ID是Elasticsearch自建的文档id,那么Elasticsearch建立的索引如下:
这个就是倒排索引
Posting List(倒排表)
ES分别为每个field都建立了一个倒排索引,Tom,carl, 18, Female这些叫term(分类索引),而[1,2]就是Posting List(倒排表)。Posting list就是一个int的数组,存储了所有符合某个term的文档id。
通过posting list这种索引方式似乎可以很快进行查找,比如要找age=18的词条,是1和3。但是,如果这里有上千万的记录呢?
Term Dictionary(词典)
ES为了能快速找到某个term,将所有的term排个序,二分法查找term,log(N)的查找效率,就像通过字典查找一样,这就是Term Dictionary。现在好像跟我们的传统B树的方式一样啊 。那么我们的ES有什么进步呢?
Term Index(词典索引)
B-Tree通过减少磁盘寻道次数来提高查询性能,ES也是采用同样的思路,直接通过内存查找term,不读磁盘,但是如果term太多,term dictionary也会很大,放内存不现实,于是有了Term Index,就像字典里的索引页一样,A开头的有哪些term,分别在哪页,可以理解term index是一颗树。
但是这棵树不会包含所有的term,它包含的是term的一些前缀。通过term index可以快速地定位到term dictionary的某个offset,然后从这个位置再往后顺序查找。
所以term index不需要存下所有的term,而仅仅是他们的一些前缀与Term Dictionary的block之间的映射关系,再结合FST(Finite State Transducers)的压缩技术,可以使term index缓存到内存中。从term index查到对应的term dictionary的block位置之后,再去磁盘上找term,大大减少了磁盘随机读的次数。
FST
Finite StateTransducers 简称 FST,通常中文译作有穷状态转换器或者有限状态传感器,是一项将一个字节序列映射到block块的技术
假设现在要将mop, moth, pop, star, stop and top(term index里的term前缀)映射到序号:0,1,2,3,4,5(term dictionary的block位置)。最简单的做法就是定义个Map<string, integer=“”>,但从内存占用少的角度想想,有没有更优的办法呢?
答案就是:FST
压缩技巧之索引帧(Frame Of Reference)
ES除了用FST压缩term index外,对posting list也有压缩技巧。为了方便压缩,ES要求posting list是有序的。同时为了减小
存储空间,所有的id都会进行delta-encoding编码。
比如现在有id列表 [73, 300, 302, 332, 343, 372] ,转化成每一个id相对于前一个id的增量值(第一个id的前一个id默认是0,增量就是它自己)列表是 [73, 227, 2, 30, 11, 29] 。在这个新的列表里面,所有的id都是小于255的,所以每个id只需要一个字节存储。
ES会把所有的文档分成很多个block,每个block正好包含256个文档,然后单独对每个文档进行增量编码,计算出存储这个block里面所有文档最多需要多少位来保存每个id,并且把这个位数作为头信息(header)放在每个block 的前面。这个技术叫Frame of Reference,翻译成索引帧。
例如对上面的数据进行压缩(假设每个block只有3个文件而不是256),压缩过程如下
在返回结果的时候,其实也并不需要把所有的数据直接解压然后一股脑全部返回,可以直接返回一个迭代器 iterator ,直接通过迭代器的 next 方法逐一取出压缩的id,这样也可以极大的节省计算和内存开销。
通过以上的方式可以极大的节省posting list的空间消耗,提高查询性能。不过ES为了提高filter过滤器查询的性能,还做了更多的工作,那就是缓存。
缓存技巧之Roaring Bitmaps 咆哮位图
ES会缓存频率比较高的filter查询,其中的原理也比较简单,即生成 (fitler, segment数据空间) 和id列表的映射,但是和倒排索引不同,我们只把常用的filter缓存下来而倒排索引是保存所有的,并且filter缓存应该足够快,不然直接查询不就可以了。ES直接把缓存的filter放到内存里面,映射的posting list放入磁盘中。
filter缓存使用了roaring bitmap的数据结构,在查询的时候相对于上面的索引帧方式CPU消耗要小,查询效率更高,代价就是需要的存储空间(磁盘)更多。
Roaring Bitmap是由int数组和bitmap这两个数据结构改良过的成果——int数组速度快但是空间消耗大,bitmap相对来说空间消耗小,但是储空间随着文档个数线性增长,所以权衡之后就有了下面的Roaring Bitmap。
- Roaring Bitmap首先会根据每个id的高16位分配id到对应的block里面,比如第一个block里面id应该都是在0到65535之间,第二个block的id在65536和131071之间
- 对于每一个block里面的数据,根据id数量分成两类,如果数量小于4096,就是用short数组保存,数量大于等于4096,就使用bitmap保存
在每一个block里面,一个数字实际上只需要2个字节来保存就行了,因为高16位在这个block里面都是相同的,高16位就是block的id。
至于4096这个分界线,因为当数量小于4096的时候,如果用bitmap就需要8kB的空间,而使用2个字节的数组空间消耗就要少一点。比如只有2048个值,每个值2字节,一共只需要4kB就能保存,但是bitmap需要8kB。
由此见得,ES使用的倒排索引确实比关系型数据库的B-Tree索引快
倒排索引如何做联合索引
上面都是单field索引,如果多个field索引的联合查询,倒排索引如何满足快速查询的要求呢?
- 利用跳表(Skip list)的数据结构快速做“与”运算
- 利用上面提到的位图按位“与”
跳表(Skip list)将一个有序链表level0,二分查找的方式挑选出其中几个元素到level1及level2,每个level越往上,选出来的指针元素越少,查找时依次从高level往低查找查找效率和2叉树的效率相当,但也是用了一定的空间冗余来换取的
例如有下面三个posting list需要联合索引:
如果使用跳表,对最短的posting list中的每个id,逐个在另外两个posting list中查找看是否存在,最后得到交集的结果。
如果使用位图(基于bitMap),就很直观了,直接按位与,得到的结果就是最后的交集
五、总结
将磁盘里的东西尽量搬进内存,减少磁盘随机读取次数(同时也利用磁盘顺序读特性),结合各种压缩算法,用及其苛刻的态度使用内存。所以,对于使用Elasticsearch进行索引时需要注意:
- 不需要索引的字段,一定要明确定义出来,因为默认是自动建索引的
- 对于String类型的字段,不需要analysis(分词)的也需要明确定义出来,因为默认也是会analysis的
- 选择有规律的ID很重要,随机性太大的ID(比如java的UUID)不利于压缩,也就不利于查询
- 高效的全局顺序ID方案,对Posting list里的大量ID进行压缩的,那如果ID是顺序的,或者是有公共前缀等具有一定规律性的ID,压缩比会比较高