ElasticSearch(下)

ElasticSearch(下)

一,Elasticsearch核心概念

1.cluster

代表一个集群,集群有多个节点,其中一个是主节点,这个主节点是可以通过选举产生的,主从节点是对于集群内部来说的。es的一个重要概念就是去中心化,字面上理解就是无中心节点,这是对于集群外部来说的,因为从外部来看,es集群,在逻辑上是个整体,你和任何一个节点的通信和整个es集群通信是等价的。

主节点的职责是负责管理集群状态,包括管理分片的状态和副本的状态,以及节点的发现和删除。

只需要在同一个网段之内启动多个es节点,就可以自动的组成一个集群。

默认情况下es自动发现同一个网段内的节点,自动组成集群。

集群状态查看

http://192.168.1.191:9200/_cluster/health?pretty

2.shards

代表索引分片,es可以把一个完整的索引分成多个切片,这样的好处是可以把一个大的索引拆分成多个,分布在多个节点上。构成分布式搜索。分片的数量只能在索引创建前指定,并且索引创建后不能更改。默认是一个索引库5个切片。

设置索引库的分片

curl -XPUT 'localhost:9200/test1/' -d'{"settings":{"number_of_shards":3}}'

3.replicas

代表索引副本,es可以给索引设置副本,副本的作用是加强集群的容错性,当某个节点某个分片损坏或丢失后可以从副本中恢复,而是提高es的查询效率,es会自动对搜索请求进行负载均衡。索引的副本个数默认是1个(总共2份)。

创建索引库时指定副本的个数

 curl -XPUT 'localhost:9200/test2/' -d'{"settings":{"number_of_replicas":0}}' 

4.recovery

代表数据恢复或叫数据重新分布,es在有节点加入或退出时根据机器的负载对索引分片进行重新分配,挂掉的节点重新启动时也会进行数据恢复。

5.gateway

代表es索引的持久化存储方式,es模式是把索引存放到内存中,当内存满了之后再持久化到磁盘。当这个es集群关闭再重新启动时就会从gateway中读取数据。es吃吃多种类型的gateway,有本地文件系统(默认),分布式文件系统,HDFS和amazon的s3云存储服务。

如果需要将数据落地到HDFS上,需要先安装插件 elasticsearch/elasticsearch-hadoop

6.discovery.zen

代表es的自动发现节点机制,es是一个基于p2p的系统,它先通过广播寻找存在的节点,再通过多播协议来进行节点之间的通信,同时也支持点对点的交互。

如果是不同网段的节点如何组成集群?

– 禁用自动发现机制

discovery.zen.ping.multicast.enabled:false

– 设置新节点被启动时能够发现的主节点列表

discovery.zen.ping.unicast.hosts:[“192.168.1.191”, “192.168.1.192”]

7.Transport

代表es内部节点或集群和客户端的交互方式,默认内部是使用tcp协议进行交互,同时它支持http协议(json格式),thrift,servlet,memcached,zeroMQ等的传输协议(通过插件方式集成)。

二,ES中的setting和mapping

settings修改索引库的默认配置

– 例如 : 分片数量,副本数量

curl -XGET http://localhost:9200/wcb/_settings?pretty  ##查看settings相关设置

curl -XPUT http://localhost:9200/helloword/ -d '{
	"settings":
		{
			"number_of_shards":3,  ## 设置分片数量为3个
			"number_of_replicas":2 #设置索引库副本数量为2个
		}
}'

mapping就是对索引库中索引的字段名称及其数据类型进行定义,类似于关系数据库中表建立时要定义字段名及其数据类型,不过ES中的mapping比数据库灵活很多,它可以动态添加字段。一般不需要指定mapping都可以,因为es会自动根据数据格式定义它的类型,如果你需要对某些字段添加特殊属性(如:定义其它分词器,是否分词,是否存储等),这个时候,就必须手动添加mapping

查询索引库的mapping信息

curl -XGET http://localhost:9200/wcb/employee/_mapping?pretty

• mappings修改字段相关属性

– 例如:字段类型,使用哪种分词工具

三,Elasticsearch的 Java Api

Java Api操作ES通过 TransportClient这个接口,我们可以不启动节点就可以和es集群进行通信,它需要制定es集群中其中一台或多台机器的ip地址和端口。

package com.shsxt.es.demo;

import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.lucene.analysis.util.CharArrayMap.EntrySet;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.IndicesAdminClient;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.Settings.Builder;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.Before;
import org.junit.Test;

public class ESDemo {
    
	//通过TransportClient接口通信
	private TransportClient client;
	
	
	/**
	 * 初始化,创建client
	 * @throws Exception 
	 */
	@Before
	public void init() throws Exception{
		Map<String, String> map = new HashMap<String, String>();
        //设置ES集群的名称
		map.put("cluster.name", "wcbcluster");
		//配置集群的相关信息
		Settings.Builder settings = Settings.builder().put(map);
        //通过指定节点ip,和通信端口获取实例对象
		client = TransportClient.builder().settings(settings).build();
		
		client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("node01"),9300));
		client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("node02"),9300));
		client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("node03"),9300));
	}
	
	/**
	 * 创建索引库
	 */
	@Test
	public void createIndexBase(){
		 IndicesExistsResponse actionGet =  client.admin().indices().
				 prepareExists("test").execute().actionGet();
		 if (actionGet.isExists()) {
			System.out.println("test索引库已存在。。删除.....");
			
			client.admin().indices().prepareDelete("test").execute();
		}
		
		Map<String, String> map = new HashMap<>();
		map.put("number_of_replicas", "0");
		map.put("number_of_shards", "2");
		
		client.admin().indices().prepareCreate("test").setSettings(map).execute();
		System.out.println("创建完成..............");
	}
	
	/**
	 * 添加索引
	 */
	@Test
	public void addIndex(){
		HashMap<String, String> map = new HashMap<>();
		map.put("name", "bin");
		map.put("age", "20");
		map.put("gender", "male");
		map.put("describe", "zg is good");
		
		//IndexResponse response =  client.prepareIndex("test", "employee").setSource(map).
		//	execute().actionGet();
		
		IndexResponse response01 =  client.prepareIndex("test", "employee","79").setSource(map).
				execute().actionGet();
		
		System.out.println(response01.getId());
	}
	
	/**
	 * 获取索引 文档内容
	 */
	@Test
	public void getIndex(){
		GetResponse getResponse = client.prepareGet("test", "employee", "79").execute().actionGet();
		System.out.println(getResponse.getVersion());
		
		Map<String, Object> source = getResponse.getSource();
		
		for (String key : source.keySet()) {
			System.out.println(key+"--->"+source.get(key));
		}
		
		
	}
	
	/**
	 * 搜索
	 */
	@Test
	public void search(){
		//指定从 test 和ik 索引库中查找
		SearchRequestBuilder builder =  client.prepareSearch("test","ik");
		//指定从employee,ikType,blog这三个类型中找
		builder.setTypes("employee","ikType","blog");
		//设置分页,设置从第几页开始,每页显示的个数
		builder.setFrom(0);
		builder.setSize(3);
		
		String key = "bin";
		//设置从name 和 content字段中去查询和 key值匹配的文档
		builder.setQuery(QueryBuilders.multiMatchQuery(key, "name","content"));
		//根据年龄倒叙排序,默认是按照相关性打分
		builder.addSort("age",SortOrder.DESC);
		
		//开始查询
		SearchResponse searchResponse =  builder.get();
		
		//获取查询的返回信息
		SearchHits hits =  searchResponse.getHits();
		
		//一共有多少个符合条件的查询结果
		System.out.println("总共查询到了:"+hits.getTotalHits());
		
		//获取查询到的结果数组
		SearchHit [] hits2 =  hits.getHits();
		
		for (SearchHit searchHit : hits2) {
			System.out.println("分数:"+searchHit.getScore());
			
			Map<String, Object> map = searchHit.getSource();
			System.out.println("id-->"+searchHit.getId());
			System.out.println("index-->"+searchHit.getIndex());
			
			for(String mapKey: map.keySet()){
				System.out.println(mapKey+"---->"+map.get(mapKey));
			}
			
			System.out.println("======================");
			
		}
	}
	
	/**
	 * 更新文档  对已有的字段更新,没有的新增
	 */
	@Test
	public void update(){
		UpdateRequest updateRequest = new UpdateRequest();
		updateRequest.index("test");
		updateRequest.type("employee");
		updateRequest.id("79");
		
		Map<String, String> map = new HashMap<>();
		map.put("age", "25");
		map.put("date", "2018-01-01");
		updateRequest.doc(map);
		
		client.update(updateRequest);
		
	}
	
	/**
	 * 如果document不存在,则创建,存在就执行更新
	 * @throws IOException 
	 */
	@Test
	public void upsert() throws IOException{
		IndexRequest indexRequest = new IndexRequest("test", "employee", "80")
				.source(XContentFactory.jsonBuilder()
						.startObject().field("name","lyp")
						.field("age","26").field("haha","xixi")
						.endObject());
		
		UpdateRequest updateRequest = new UpdateRequest("test","employee", "80")
				.doc(XContentFactory.jsonBuilder()
						.startObject().field("city","beijing")
						.field("describe","beijing is good").endObject());
		
		updateRequest.upsert(indexRequest);
		
		client.update(updateRequest);
		
	}
	
	/**
	 * 删除文档操作
	 */
	@Test
	public void delete(){
		
		DeleteResponse response = client.prepareDelete("test","employee", "79").execute().actionGet();
		
		System.out.println(response.isFound());
	}
	
	/**
	 * 批量操作
	 * @throws IOException 
	 */
	@Test
	public void bulk() throws IOException{
		BulkRequestBuilder builder = client.prepareBulk();
		
		IndexRequest indexRequest = new IndexRequest("test","employee","333").source(
				XContentFactory.jsonBuilder().startObject()
				.field("user","kiadfa").field("postData",new Date())
				.field("message","trying out ElasticSearch").endObject());
		
		//添加一个插入数据操作
		builder.add(indexRequest);
		
		//添加一个删除文档操作
		builder.add(client.prepareDelete("test", "employee", "80"));
		
		BulkResponse bulkResponse = builder.get();
		
		System.out.println(bulkResponse.hasFailures());
	}
}

四,ES的查询

4.1 query and fetch(速度最快)

返回N倍数据量

向索引的所有分片都发出查询请求,个分片返回的时候把元素文档和计算后的排名信息一起返回。这种搜索方式是最快的,因为相比下面的集中搜索方式,这种查询方法只需要去shard查询一次,但是各个shard返回的结果的数量之和可能是用户要求的size的N倍。

4.2 query and fetch(default)

如果你搜索时,没指定搜索方式,就是用的这种搜索方式。这种搜索方式方式:大概分为两个步骤,第一步:先向所有的shard发出请求,各分片只返回排序和排名相关的信息(注意:不包括文档document),然后按照个分片返回的分数进行重新排序和排名,取前size个文档。然后进行第二步,去相关的shard取document。这种方式返回的document与用户要求的size是相等的

4.3 DFS query fetch

可以更精确控制搜素打分和排名

这种方式比第一种多了一个初始化散发(initial scatter)步骤,有这一步可以更精确控制搜索打分和排名

4.4 DFS query then fetch(最慢)

初始化散发其实就是在进行真正的查询之前,先把各个分片的频率和文档频率收集一下,然后进行词搜索的时候,各分片依据全局的词频率和文档频率进行搜索和排名。显然如果使用DFS_QUERY_THEN_FETCH这种查询方式,效率是最低的,因为一个搜索,可能要请求3次分片。但使用DFS方法,搜索精度应该是最高的。频率和文档频率收集一下,然后进行词搜索的时候,各分片依据全局的词频率和文档频率进行搜索和排名。显然如果使用DFS_QUERY_THEN_FETCH这种查询方式,效率是最低的,因为一个搜索,可能要请求3次分片。但使用DFS方法,搜索精度应该是最高的。

从性能考虑QUERY_AND_FETCH是最快的, DFS_QUERY_THEN_FETCH是最慢的。从搜索的准确度来说,DFS要比非DFS的准确度更高。

• 查询:query – builder.setQuery(QueryBuilders.matchQuery(“name”, “test”))

• 分页:from/size – builder.setFrom(0).setSize(1)

• 排序:sort – builder.addSort(“age”, SortOrder.DESC)

• 过滤:filter – builder.setPostFilter(QueryBuilders.rangeQuery(“age”).from(1).to(19))

• 高亮:highlight

• 统计:facet(已废弃)使用aggregations 替代

– 根据字段进行分组统计

– 根据字段分组,统计其他字段的值

– size设置为0,会获取所有数据,否则,只会返回10条

五,ES的分页

ES使用的是 from 和 size 两个参数

– size 每次返回多少个结果 默认是10个

– from 从哪条结果开始,默认值为0

假设每页显示5条结果,那么1至3页的请求就是:

  • GET /_search?size=5
  • GET /_search?size=5&from=5
  • GET /_search?size=5&from=10

注意:不要一次请求过多或者页码过大的结果,这么会对服务器造成很大的压力。因为它们会在返回前排序。一个请求会经过多个分片。每个分片都会生成自己的排序结果。然后再进行集中整理,以确保最终结果的正确性

• timed_out告诉了我们查询是否超时

• curl -XGET http://localhost:9200/_search?timeout=10ms

– es会在10ms之内返回查询内容

• 注意:timeout并不会终止查询,它只是会在你指定的时间内返回当时已经查询到的数据,然后关闭连接。在后台,其他的查询可能会依旧继续, 尽管查询结果已经被返回了。

六,ES分片查询

默认是randomize across shards

随机选取,表示随机的从分片中取数据

_local : 指查询操作会优先在本地节点有的分片中查询,没有的再去其它节点查询

_primary_first : 指查询会先在主分片中查询,如果主分片中找不到(挂了),就会在副本中查询。

_primary : 只在主分片中查询

_only_node : 指在指定id的节点里面进行查询,如果该节点只有查询索引的部分分片,就只在这部分分片中查找,所以查询结果可能不完整。

_prefer_node:nodeid 优先在指定的节点上执行查询

_shards: 0,1,2,3,4 查询指定分片的数据

七,ES中脑裂问题

脑裂问题:类似于精神分裂,就是同一个集群中的不同节点,对于集群的状态有了不一样的理解。discovery.zen.minimum_master_nodes 用于控制选举行为发生的最小集群节点数量。推荐设为大于1的数值,因为只有在2个以上的节点的集群中,主节点才是有意义的。

正常情况下,集群中的所有的节点,应该对集群中master的选择是一致的,这样获得的状态信息也应该是一致的,不一致的状态信息,说明不同的节点对maste节点的选择出现了异常——也就是所谓的脑裂问题。这样的脑裂状态直接让节点失去了集群的正确状态,导致集群不能正常工作。

Elasticsearch中脑裂产生的原因

  1. 网络:由于是内网通信,网络通信问题造成某些节点认为 master死掉,而另选master的可能性较小
  2. 节点负载:由于master节点与data节点都是混合在一起的, 所以当工作节点的负载较大时,导致对应的ES实例停止响应, 而这台服务器如果正充当着master节点的身份,那么一部分 节点就会认为这个master节点失效了,故重新选举新的节点, 这时就出现了脑裂;同时由于data节点上ES进程占用的内存较大,较大规模的内存回收操作也能造成ES进程失去响应。

ES中脑裂解决

主节点:

node.master: true

node.date: false

从节点:

node.master : false

node.date : true

所有节点:

discovery.zen.ping.multicast.enabled: false

discovery.zen.ping.unicast.hosts: [“slave1”, “master” , “slave2"]

八,ES的优化

调大系统的"最大打开文件数",建议32k甚至64k

– ulimit -a (查看)

– ulimit -n 32000(设置)

修改配置文件调整ES的JVM内存大小

修改bin/elasticsearch.in.sh 中ES_MIN_MEM 和 ES_MAX_MEM的大小,建议设置一样大,避免频繁的分配内存,根据服务器内存大小,一般分配60%左右(默认256M)

设置mlockall来锁定进程的物理内存地址

避免交换(swapped)来提高性能

修改文件 conf/elasticsearch.vml

bootstrap.mlockall:true

分片多的话,可以提升建立索引的能力,5-20个比较合适

如果分片数过少或过多,都会导致检索比较慢。分片数过多会导致检索时打开比较对的文件,另外也会导致多台服务器之间通讯。而分片数过少会导致单个分片索引过大,所以检索速度慢。建议单个分片最多存储20G总有的索引数据,所以,分片数量=数据总量/20G(每个分片大小在20G-30G较为合适).

副本设置

副本多的话,可以提升搜索的能力,但是如果设置很多副本的话也会对服务器造成额外的压力,因为需要同步数据。所以建议设置2-3个较为合适。

删除文档

在Lucene中删除文档,数据不会立马删除,而是在lucene索引中产生一个.del文件,而在检索过程中这部分数据也会参与检索,lucene在检索过程会判断是否删除,如果删除了再过滤掉。这样也会降低查询效率,所以可以执行清除删除文档

curl -XPOST 'http://localhost:9200/elasticsearch/_optimize?only_expunge_deletes=true' 

– client.admin().indices().prepareOptimize("elasticsearch ").setOnlyExpungeDeletes(true).get();

导入数据时副本设置

如果在项目开始的时候需要批量入库大量数据的话,建议将副本数设置为0,因为es在索引数据的时候,如果有副本存在,数据也会立马同步同步副本中,这样也对es增加压力。待索引完成后将副本按需要改回来,这样可以提高索引效率

去掉mapping中_all域

ES默认为每个被索引的文档都定义了一个特殊的域:_all 它自动包含被索引文档中一个或者多个域中的内容,在进行搜索时,如果不指明要搜索的文档的域,ES则会去搜索_all域。——all带来搜索方便,其代价是增加了系统在索引阶段对CPU和存储空间资源的开销。

可以使用“_all”:{“enabled”:false} 开关禁用它

定时对索引进行优化

不然segment越多,查询的性能就越差

– 索引量不是很大的情况下可以将 segment设置为 1

curl -XPOST 'http://localhost:9200/test/_optimize?max_num_segments=1' 

java代码设置

client.admin().indices().prepareOptimize(“test").setMaxNumSegments(1).get();

九,注意点

在使用java代码操作es集群的时候要保证本地使用的es的版本和集群上es 的版本保持一致。

• 保证集群中每个节点的JDK版本和es配置一致

Elasticsearch的分片规则

• elasticsearch在建立索引时,根据id或id,类型进行hash, 得到hash值与该索引库的分片数量取余,取余的值即为存入的分片ID。

– 具体源码为:根据OperationRouting类generateShardId方法进行分片

猜你喜欢

转载自blog.csdn.net/weixin_43270493/article/details/86661986