ElasticSearch学习笔记

一、ElasticSearch简介

1、定义

  • 开源的搜索引擎,建立在全文搜索引擎库 Apache Lucene 基础之上用 Java编写的,隐藏了Lucene的复杂性,取而代之的提供了一套简单一致的RestFul API,即ClientServer之间使用HTTP协议通信。
  • 不仅仅是全文搜索引擎,也是可以当做一个分布式的实时文档存储,Elasticsearch就是一款面向文档的NoSQL数据库,使用JSON作为文档序列化格式。它的高级之处在于,使用Lucene作为核心来实现所有索引和搜索的功能,使得每个文档的内容都可以被索引、搜索、排序、过滤。同时,提供了丰富的聚合功能,可以对数据进行多维度分析。

2、特性

  • 近实时搜索和分析:即是提交索引后,立马就可以被搜索到,还可以聚合分析到
  • 全文检索:基于lucene的强大的全文检索能力
  • 分布式:横向扩展非常灵活
  • RESTful APIJSON+HTTP

二、ElasticSearch核心概念

Near Realtime(NRT)近实时:数据提交索引后,立马就可以搜索到。(从写入数据到数据可以被搜索到有一个小延迟(大概1秒);基于es执行搜索和分析可以达到秒级)

Cluster 集群:一个集群由一个唯一的名字标识,默认为“elasticsearch”。集群名称非常重要,具有相同集群名的节点才会组成一个集群。集群名称可以在配置文件中指定。对于中小型应用来说,刚开始一个集群就一个节点很正常。

Node 节点:存储集群的数据,参与集群的索引和搜索功能。像集群有名字,节点也有自己的名称,默认在启动时会以一个随机的UUID的前七个字符作为节点的名字,你可以为其指定任意的名字。通过集群名在网络中发现同伴组成集群。一个节点也可是集群。

Index 索引(数据库): 一个索引是一个相似结构的文档数据的集合(等同于solr中的集合),比如说商品分类索引,订单索引等。每个索引有唯一的名字,通过这个名字来操作它。一个集群中可以有任意多个索引。

一个index包含很多document,一个index就代表了一类类似的或者相同的document。比如说建立一个product index,商品索引,里面可能就存放了所有的商品数据,所有的商品document。

每个索引里都可以有一个或多个type,type是index中的一个逻辑数据分类,一个type下的document,都有相同的field,比如博客系统,有一个索引,可以定义用户数据type,博客数据type,评论数据type。

商品index,里面存放了所有的商品数据,商品document

但是商品分很多种类,每个种类的document的field可能不太一样,比如说电器商品,可能还包含一些诸如售后时间范围这样的特殊field;生鲜商品,还包含一些诸如生鲜保质期之类的特殊field

Type 类型(表):指在一个索引中,可以索引不同类型的文档,如用户数据、博客数据。从6.0.0 版本起已废弃,一个索引中只存放一类数据。

每个索引里都可以有一个或多个type,type是index中的一个逻辑数据分类,一个type下的document,都有相同的field,比如博客系统,有一个索引,可以定义用户数据type,博客数据type,评论数据type。

商品index,里面存放了所有的商品数据,商品document

但是商品分很多种类,每个种类的document的field可能不太一样,比如说电器商品,可能还包含一些诸如售后时间范围这样的特殊field;生鲜商品,还包含一些诸如生鲜保质期之类的特殊field

type,日化商品type,电器商品type,生鲜商品type

日化商品type:product_id,product_name,product_desc,category_id,category_name

电器商品type:product_id,product_name,product_desc,category_id,category_name,service_period

生鲜商品type:product_id,product_name,product_desc,category_id,category_name,eat_period

每一个type里面,都会包含一堆document:

  {   "product_id": "1",   
      "product_name": "长虹电视机",   
      "product_desc": "4k高清",   
      "category_id": "3",   
      "category_name": "电器",   
      "service_period": "1年"  
  }  

  {   "product_id": "2",   
      "product_name": "基围虾",   
      "product_desc": "纯天然,冰岛产",   
      "category_id": "4",   
      "category_name": "生鲜",   
      "eat_period": "7天"  
  }  

Document 文档:被索引的一条数据,索引的基本信息单元,以JSON格式来表示。

文档是es中的最小数据单元,一个document可以是一条客户数据,一条商品分类数据,一条订单数据,通常用JSON数据结构表示,每个index下的type中,都可以去存储多个document。

​ ⑧**mapping(映射-约束)😗*数据如何存放到索引对象上,需要有一个映射配置,包括:数据类型、是否存储、是否分词等。这样就创建了一个名为blog的Index。Type不用单独创建,在创建Mapping 时指定就可以。Mapping用来定义Document中每个字段的类型,即所使用的 analyzer、是否索引等属性,非常关键等。创建Mapping 的代码示例如下:

client.indices.putMapping({
    index : 'blog',
    type : 'article',
    body : {
        article: {
            properties: {
                id: {
                    type: 'string',
                    analyzer: 'ik',
                    store: 'yes',
                },
                title: {
                    type: 'string',
                    analyzer: 'ik',
                    store: 'no',
                },
                content: {
                    type: 'string',
                    analyzer: 'ik',
                    store: 'yes',
                }
            }
        }
    }
});

Shard 分片Replication 备份

Shard 分片:在创建一个索引时可以指定分成多少个分片来存储。每个分片本身也是一个功能完善且独立的“索引”,可以被放置在集群的任意节点上。分片的好处:

  • 允许我们水平切分/扩展容量

  • 可在多个分片上进行分布式的、并行的操作,提高系统的性能和吞吐量。

注意:分片数创建索引时指定,创建后不可改了。备份数可以随时改。

Replication 备份: 一个分片可以有多个备份(副本)。备份的好处:

  • 高可用。一个主分片挂了,副本分片就顶上去

  • 扩展搜索的并发能力、吞吐量。搜索可以在所有的副本上并行运行。- 高并发下副本也可搜索。

先来看存储上的基本概念,这里将其与MySQL进行了对比:

ElasticsearchMySQL
index(索引,名词)database
doc type(文档类型)table
document(文档)row
field(字段)column
mapping(映射)schema
query DSL(查询语言)SQL

三、倒排索引和单词词典

倒排索引是实现“单词-文档矩阵”的一种具体存储形式,通过倒排索引,可以根据单词快速获取包含这个单词的文档列表。倒排索引主要由两个部分组成:“单词词典”和“倒排文件”。
举个例子:
在这里插入图片描述

但是中文和英文等语言不同,单词之间没有明确分隔符号,所以首先要用分词系统将文档自动切分成单词序列。这样每个文档就转换为由单词序列构成的数据流,为了系统后续处理方便,需要对每个不同的单词赋予唯一的单词编号,同时记录下哪些文档包含这个单词,在如此处理结束后,我们可以得到最简单的倒排索引。

倒排索引主要由两个部分组成:“词典”和“倒排文件”。

(1)词条(Term):索引里面最小的存储和查询单元,对于英文来说是一个单词,对于中文来说一般指分词后的一个词。

(2)词典(Lexicon):搜索引擎的通常索引单位是单词,单词词典是由文档集合中出现过的所有单词构成的字符串集合,单词词典内每条索引项记载单词本身的一些信息以及指向“倒排列表”的指针。

(3)倒排列表(PostingList):倒排列表记载了出现过某个单词的所有文档的文档列表及单词在该文档中出现的位置信息,每条记录称为一个倒排项(Posting)。根据倒排列表,即可获知哪些文档包含某个单词。

(4)倒排文件(Inverted File):所有单词的倒排列表往往顺序地存储在磁盘的某个文件里,这个文件即被称之为倒排文件,倒排文件是存储倒排索引的物理文件。
在这里插入图片描述

简单说,单词词典是将需要匹配的文档分词后构成一个词典,而倒排文件恰好是需要匹配用的文件,词典中每个单词需要对应一个倒排列表。

词典是倒排索引中非常重要的组成部分,它用来维护文档集合中出现过的所有单词的相关信息,同时用来记载某个单词对应的倒排列表在倒排文件中的位置信息。在支持搜索时,根据用户的查询词,去单词词典里查询,就能够获得相应的倒排列表,并以此作为后续排序的基础。
对于一个规模很大的文档集合来说,可能包含几十万甚至上百万的不同单词,能否快速定位某个单词,这直接影响搜索时的响应速度,所以需要高效的数据结构来对单词词典进行构建和查找,常用的数据结构包括哈希加链表结构和树形词典结构。

  • 哈希+链表

主体部分是哈希表,每个哈希表项保存一个指针,指针指向冲突链表,在冲突链表里,相同哈希值的单词形成链表结构。之所以会有冲突链表,是因为两个不同单词获得相同的哈希值,如果是这样,在哈希方法里被称做是一次冲突,可以将相同哈希值的单词存储在链表里,以供后续查找。

在建立索引的过程中,词典结构也会相应地被构建出来。比如在解析一个新文档的时候,对于某个在文档中出现的单词T,首先利用哈希函数获得其哈希值,之后根据哈希值对应的哈希表项读取其中保存的指针,就找到了对应的冲突链表。如果冲突链表里已经存在这个单词,说明单词在之前解析的文档里已经出现过。如果在冲突链表里没有发现这个单词,说明该单词是首次碰到,则将其加入冲突链表里。通过这种方式,当文档集合内所有文档解析完毕时,相应的词典结构也就建立起来了。

  • 树形结构

B树(或者B+树)是另外一种高效查找结构,图1-8是一个 B树结构示意图。B树与哈希方式查找不同,需要字典项能够按照大小排序(数字或者字符序),而哈希方式则无须数据满足此项要求。

B树形成了层级查找结构,中间节点用于指出一定顺序范围的词典项目存储在哪个子树中,起到根据词典项比较大小进行导航的作用,最底层的叶子节点存储单词的地址信息,根据这个地址就可以提取出单词字符串。

四、ElasticSearch架构

在这里插入图片描述

  • Gateway是ES用来存储索引的文件系统,支持多种类型,包括Local FileSystem、Shared FileSystem等。

  • Gateway的上层是一个分布式的lucene框架,即Distributed Lucene Directory。

  • Lucene之上是ES的模块,包括:索引模块(Index Module)、搜索模块(Search Module)、映射解析模块(Mapping)等。

  • ES模块之上是 Discovery、Scripting和第三方插件。Discovery是ES的节点发现模块,不同机器上的ES节点要组成集群需要进行消息通信,集群内部需要选举master节点,这些工作都是由Discovery模块完成。支持多种发现机制,如 Zen 、EC2、gce、Azure。

    Scripting用来支持在查询语句中插入javascript、python等脚本语言,scripting模块负责解析这些脚本,使用脚本语句性能稍低。ES也支持多种第三方插件。

  • 再上层是ES的传输模块和JMX.传输模块支持多种传输协议,如 Thrift、memecached、http,默认使用http。JMX是java的管理框架,用来管理ES应用。

  • 最上层是ES提供给用户的接口,可以通过RESTful接口和ES集群进行交互。

五、安装

关于配置ES、Kibana的环境:参考(要求jdk1.8以上)

六、ElasticSearch基本操作

包括增加索引、删除索引、查找索引

通过修改id插入字段增加文档、按照id删除文档、修改文档(可以通过put覆盖,推荐使用post提交的方式)、查询文档:通过id查询文档、通过关键词查询…

elasticSearch对索引的增删查和对文档的增删改查:参考

这里我主要使用java客户端对于es的API的基本操作:

  • 创建索引
	@Test
	public void createIndex_blog(){
		// 1 创建索引
		client.admin().indices().prepareCreate("blog2").get();
		
		// 2 关闭连接
		client.close();
	}
  • 删除索引
	@Test
	public void deleteIndex(){
		// 1 删除索引
		client.admin().indices().prepareDelete("blog2").get();
		
		// 2 关闭连接
		client.close();
	}
  • 新建文档(源数据json串)

当直接在ElasticSearch建立文档对象时,如果索引不存在的,默认会自动创建,映射采用默认方式。

ElasticSearch服务默认端口9300;Web管理平台端口9200

	@Test
	public void createIndexByJson() throws UnknownHostException {
		// 1 文档数据准备
		String json = "{" + "\"id\":\"1\"," + "\"title\":\"基于Lucene的搜索服务器\","
				+ "\"content\":\"它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口\"" + "}";
		// 2 创建文档
		IndexResponse indexResponse = client.prepareIndex("blog", "article", "1").setSource(json).execute().actionGet();
		// 3 打印返回的结果
		System.out.println("index:" + indexResponse.getIndex());
		System.out.println("type:" + indexResponse.getType());
		System.out.println("id:" + indexResponse.getId());
		System.out.println("version:" + indexResponse.getVersion());
		System.out.println("result:" + indexResponse.getResult());
		// 4 关闭连接
		client.close();
	}
  • 新建文档(源数据map方式添加json)
@Test
	public void createIndexByMap() {
		// 1 文档数据准备
		Map<String, Object> json = new HashMap<String, Object>();
		json.put("id", "2");
		json.put("title", "基于Lucene的搜索服务器");
		json.put("content", "它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口");
		// 2 创建文档
		IndexResponse indexResponse = client.prepareIndex("blog", "article", "2").setSource(json).execute().actionGet();
		// 3 打印返回的结果
		System.out.println("index:" + indexResponse.getIndex());
		System.out.println("type:" + indexResponse.getType());
		System.out.println("id:" + indexResponse.getId());
		System.out.println("version:" + indexResponse.getVersion());
		System.out.println("result:" + indexResponse.getResult());
		// 4 关闭连接
		client.close();
	}
  • 新建文档(源数据es构建器添加json)
@Test
	public void createIndex() throws Exception {

		// 1 通过es自带的帮助类,构建json数据
		XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field("id", 3)
				.field("title", "基于Lucene的搜索服务器").field("content", "它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。")
				.endObject();

		// 2 创建文档
		IndexResponse indexResponse = client.prepareIndex("blog", "article", "3").setSource(builder).get();

		// 3 打印返回的结果
		System.out.println("index:" + indexResponse.getIndex());
		System.out.println("type:" + indexResponse.getType());
		System.out.println("id:" + indexResponse.getId());
		System.out.println("version:" + indexResponse.getVersion());
		System.out.println("result:" + indexResponse.getResult());

		// 4 关闭连接
		client.close();
	}
  • 搜索文档数据(单个索引)
	@Test
	public void getData() throws Exception {
		
		// 1 查询文档
		GetResponse response = client.prepareGet("blog", "article", "1").get();
		
		// 2 打印搜索的结果
		System.out.println(response.getSourceAsString());
		
		// 3 关闭连接
		client.close();
	}
  • 搜索文档数据(多个索引)
@Test
	public void getMultiData() {
		
		// 1 查询多个文档
		MultiGetResponse response = client.prepareMultiGet().add("blog", "article", "1").add("blog", "article", "2", "3")
				.add("blog", "article", "2").get();
		
		// 2 遍历返回的结果
		for(MultiGetItemResponse itemResponse:response){
			GetResponse getResponse = itemResponse.getResponse();
			
			// 如果获取到查询结果
			if (getResponse.isExists()) {
				String sourceAsString = getResponse.getSourceAsString();
				System.out.println(sourceAsString);
			}
		}
		
		// 3 关闭资源
		client.close();
	}
  • 更新文档数据(update)
@Test
	public void updateData() throws Throwable {

		// 1 创建更新数据的请求对象
		UpdateRequest updateRequest = new UpdateRequest();
		updateRequest.index("blog");
		updateRequest.type("article");
		updateRequest.id("3");
		updateRequest.doc(XContentFactory.jsonBuilder().startObject()
				// 对没有的字段添加, 对已有的字段替换
				.field("title", "基于Lucene的搜索服务器")
				.field("content",
						"它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。大数据前景无限")
				.field("createDate", "2017-8-22").endObject());

		// 2 获取更新后的值
		UpdateResponse indexResponse = client.update(updateRequest).get();
		// 3 打印返回的结果
		System.out.println("index:" + indexResponse.getIndex());
		System.out.println("type:" + indexResponse.getType());
		System.out.println("id:" + indexResponse.getId());
		System.out.println("version:" + indexResponse.getVersion());
		System.out.println("create:" + indexResponse.getResult());
		// 4 关闭连接
		client.close();
	}
  • 更新文档数据(upsert)
@Test
	public void testUpsert() throws Exception {

		// 设置查询条件, 查找不到则添加
		IndexRequest indexRequest = new IndexRequest("blog", "article", "5")
				.source(XContentFactory.jsonBuilder().startObject().field("title", "搜索服务器").field("content","它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。").endObject());
		
		// 设置更新, 查找到更新下面的设置
		UpdateRequest upsert = new UpdateRequest("blog", "article", "5")
				.doc(XContentFactory.jsonBuilder().startObject().field("user", "李四").endObject()).upsert(indexRequest);

		client.update(upsert).get();
		client.close();
	}

  • 删除文档数据(prepareDelete)
	@Test
	public void deleteData() {
		
		// 1 删除文档数据
		DeleteResponse indexResponse = client.prepareDelete("blog", "article", "5").get();

		// 2 打印返回的结果
		System.out.println("index:" + indexResponse.getIndex());
		System.out.println("type:" + indexResponse.getType());
		System.out.println("id:" + indexResponse.getId());
		System.out.println("version:" + indexResponse.getVersion());
		System.out.println("found:" + indexResponse.getResult());

		// 3 关闭连接
		client.close();
	}
  • 查询所有(matchAllQuery)
@Test
	public void matchAllQuery() {
		
		// 1 执行查询
		SearchResponse searchResponse = client.prepareSearch("blog").setTypes("article")
				.setQuery(QueryBuilders.matchAllQuery()).get();

		// 2 打印查询结果
		SearchHits hits = searchResponse.getHits(); // 获取命中次数,查询结果有多少对象
		System.out.println("查询结果有:" + hits.getTotalHits() + "条");

		Iterator<SearchHit> iterator = hits.iterator();

		while (iterator.hasNext()) {
			SearchHit searchHit = iterator.next(); // 每个查询对象

			System.out.println(searchHit.getSourceAsString()); // 获取字符串格式打印
		}

		// 3 关闭连接
		client.close();
	}
  • 对所有字段分词查询(queryStringQuery)
@Test
	public void query() {
		// 1 条件查询
		SearchResponse searchResponse = client.prepareSearch("blog").setTypes("article")
				.setQuery(QueryBuilders.queryStringQuery("全文")).get();

		// 2 打印查询结果
		SearchHits hits = searchResponse.getHits(); // 获取命中次数,查询结果有多少对象
		System.out.println("查询结果有:" + hits.getTotalHits() + "条");
		
		Iterator<SearchHit> iterator = hits.iterator();
		
		while (iterator.hasNext()) {
			SearchHit searchHit = iterator.next(); // 每个查询对象
			
			System.out.println(searchHit.getSourceAsString()); // 获取字符串格式打印
		}
		
		// 3 关闭连接
		client.close();
	}
  • 通配符查询(wildcardQuery)

    *:表示多个字符(任意的字符)

    ?:表示单个字符

@Test
	public void wildcardQuery() {

		// 1 通配符查询
		SearchResponse searchResponse = client.prepareSearch("blog").setTypes("article")
				.setQuery(QueryBuilders.wildcardQuery("content", "*全*")).get();

		// 2 打印查询结果
		SearchHits hits = searchResponse.getHits(); // 获取命中次数,查询结果有多少对象
		System.out.println("查询结果有:" + hits.getTotalHits() + "条");

		Iterator<SearchHit> iterator = hits.iterator();

		while (iterator.hasNext()) {
			SearchHit searchHit = iterator.next(); // 每个查询对象

			System.out.println(searchHit.getSourceAsString()); // 获取字符串格式打印
		}

		// 3 关闭连接
		client.close();
	}
  • 词条查询(TermQuery)
@Test
	public void termQuery() {
		
		// 1 第一field查询
		SearchResponse searchResponse = client.prepareSearch("blog").setTypes("article")
				.setQuery(QueryBuilders.termQuery("content", "全")).get();
		
		// 2 打印查询结果
		SearchHits hits = searchResponse.getHits(); // 获取命中次数,查询结果有多少对象
		System.out.println("查询结果有:" + hits.getTotalHits() + "条");

		Iterator<SearchHit> iterator = hits.iterator();

		while (iterator.hasNext()) {
			SearchHit searchHit = iterator.next(); // 每个查询对象

			System.out.println(searchHit.getSourceAsString()); // 获取字符串格式打印
		}

		// 3 关闭连接
		client.close();
	}
  • 模糊查询(fuzzy)
@Test
	public void fuzzy() {
		
		// 1 模糊查询
		SearchResponse searchResponse = client.prepareSearch("blog").setTypes("article")
				.setQuery(QueryBuilders.fuzzyQuery("title", "lucene")).get();
		
		// 2 打印查询结果
		SearchHits hits = searchResponse.getHits(); // 获取命中次数,查询结果有多少对象
		System.out.println("查询结果有:" + hits.getTotalHits() + "条");

		Iterator<SearchHit> iterator = hits.iterator();

		while (iterator.hasNext()) {
			SearchHit searchHit = iterator.next(); // 每个查询对象

			System.out.println(searchHit.getSourceAsString()); // 获取字符串格式打印
		}

		// 3 关闭连接
		client.close();
	}

七、集群

ES的集群搭建很简单,不需要依赖第三方协调管理组件,自身内部就实现了集群的管理功能。ES集群由一个或多个Elasticsearch节点组成,每个节点配置相同的 cluster.name 即可加入集群,默认值为 “elasticsearch”。确保不同的环境中使用不同的集群名称,否则最终会导致节点加入错误的集群。

一个Elasticsearch服务启动实例就是一个节点(Node)。节点通过 **node.name**来设置节点名称,如果不设置则在启动时给节点分配一个随机通用唯一标识符作为名称。

Solr 和 Elasticsearch 都是比较成熟的全文搜索引擎,能完成的功能和性能也基本一样。但是 ES 本身就具有分布式的特性和易安装使用的特点,而Solr的分布式需要借助第三方来实现,例如通过使用ZooKeeper来达到分布式协调管理。

关于集群详解:参考

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值