一、核心概念

1、cluster

代表一个集群,集群中有多个节点,其中一个为主节点,这个主节点是可以通过选举产生的,主从节点是对于集群内部来书的,es的一个重要概念是去中心化,字面理解是无中心节点,这是对于外部来说,从外部来看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会自动对搜索请求进行负载均衡

可在创建索引库的时候指定(注区别shards,replicas可修改数量)

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

默认有一个副本,总共两个,默认状态下number_of_replicas=1

4、recovery

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

5、gateway

代表es索引的持久化存储方式,es默认的是先把索引存储到内存中,当内存满的时候,持久化到磁盘,当这个es集群重启时,就会从gateway中读取索引数据。

es支持多种类型的gateway,有本地的文件系统(默认),分布式文件系统,hadoop的HDFS和amazon的S3云存储服务

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

6、discovery.zen

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

不同网段节点组成es集群方式:

//禁用自动发现机制 
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等传输协议(通过插件方式集成)

二、ElasticSearch中的settings和mappings

settings 索引库默认配置

查看默认配置方法

curl -XGET http://localhost:9200/sh/_settings?pretty

修改索引库默认配置,如分片数量,副本数

curl -XPUT http://192.168.30.102:9200/sh/ -d ''{
	"settings":{
   	"number_of_shards":3,
    "number_of_replicas":3
	}
}’
Mappings索引的字段名字,数据类型定义

类似于关系型数据库建表时要定义字段名及其数据类型,(类似solr中的schme),es中的mapping要比数据库中灵活很多,可以动态添加字段。并且一般不需要mapping都可以,因为es会自动根据数据格式定义它的类型,如果需要对某些字段添加特殊属性(如:定义使用其他分词器、是否分词、是否存储等),必须手动添加mapping

查询mapping信息

curl -XGET http://localhost:9200/ik/_mapping?pretty

修改字段相关属性

例子:

curl -XPOST http://localhost:9200/ik/ikType/_mapping -d'{
        "properties": {
            "content": {
                "type": "string",
                "index":"analyzed",
                "analyzer": "ik_max_word",
                "search_analyzer": "ik_max_word"
            }
       }
}'

三、elasticsearch的API

1、相关连接准备工作
TransportClient client;
@Before
	public void client() throws UnknownHostException {
		Map<String, String> map=new HashMap<>();
		map.put("cluster.name", "zs");//es集群名称zs
		Settings.Builder sBuilder=Settings.builder().put(map);
		client=TransportClient.builder().settings(sBuilder).build();
		
		client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("192.168.30.101"), 9300));
		client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("192.168.30.102"),9300));	
	}
2、创建索引库
@Test
	public void createIndexBase() {
		IndicesExistsResponse response=client.admin().indices().prepareExists("test1").execute().actionGet();
		if(response.isExists()) {
			System.out.println("索引库以存在=====");
			client.admin().indices().prepareDelete("test1").execute();
		}
		Map<String, String> map=new HashMap<>();
		map.put("number_of_shards", "5");
		map.put("number_of_replicas", "1");	client.admin().indices().prepareCreate("test1").setSettings(map).execute();	
	}
3、添加索引库中相关内容
@Test
	public void get() {
		GetResponse response=client.prepareGet("test1","employee","1").execute().actionGet();
		System.out.println(response.getVersion());
		
		Map<String, Object> source=response.getSource();
		for(String key:source.keySet()) {
			System.out.println(key+"==="+source.get(key));
		}		
	}
4、搜索
@Test
	public void search() {
		//指定从test,javatest索引库来查询
		SearchRequestBuilder builder=client.prepareSearch("test","shsxt");
		builder.setTypes("employee","blog","ikType");
		builder.setFrom(0);
		builder.setSize(8);
		
		String key="bin";
		builder.setQuery(QueryBuilders.multiMatchQuery(key, "name","first_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 hit:hits2) {
			System.out.println("分数:"+hit.getScore());

			Map<String, Object> source = hit.getSource();
			System.out.println(hit.getIndex());
			System.out.println(hit.getId());
			for(String s:source.keySet()) {
				System.out.println(s+"=="+source.get(s));
			}
		}
	}
5、获取索引库中内容
@Test
	public void get() {
		GetResponse response=client.prepareGet("test1","employee","1").execute().actionGet();
		System.out.println(response.getVersion());
		
		Map<String, Object> source=response.getSource();
		for(String key:source.keySet()) {
			System.out.println(key+"==="+source.get(key));
		}
	}
6、更新
@Test
	public void update() {
		UpdateRequest request=new UpdateRequest();
		request.index("test1");
		request.type("employee");
		request.id("3");
		
		Map<String, String> map=new HashMap<String, String>();
		map.put("age", "25");
		map.put("name", "erke");
		
		request.doc(map);
		
		client.update(request);
		System.out.println("OK++");	
	}
7、有则更新,无则添加
@Test
	public void upsert() throws Exception {
		IndexRequest indexRequest =new IndexRequest("test1","employee","1");
		
		indexRequest.source(XContentFactory.jsonBuilder().startObject()
				.field("name","pyl")
				.field("age","23")
				.field("haha","6666").endObject()				
				);
		UpdateRequest updateRequest=new UpdateRequest("test1","employee","1").doc(
				XContentFactory.jsonBuilder()
				.startObject()
				.field("name", "beijing")
                .field("describe", "beijing is good")
                .endObject()
				);
		updateRequest.upsert(indexRequest);
		
		client.update(updateRequest);
	
	}
8、删除
@Test
	public void delete() throws InterruptedException, ExecutionException {
		DeleteResponse response=client.prepareDelete("test1","employee","3").execute().actionGet();
		
		System.out.println(response.isFound());	
	}
9、批量操作
@Test
	public void bulk() throws IOException {
		BulkRequestBuilder builder=client.prepareBulk();
		IndexRequest request=new IndexRequest("test1","employee","5").source(
				XContentFactory.jsonBuilder().startObject().field("name","Tom")
				.field("age","18")
				.field("like","book")
				
				);
		builder.add(request);		
		builder.add(client.prepareDelete("test1","employee","2"));
		BulkResponse bulkResponse = builder.get();
		
		System.out.println(bulkResponse.hasFailures());
				
	}

四、ElasticSearch查询

四种搜索类型

1、query and fetch

速度最快,但会返回N倍数量的数据

向索引的所有分片(shard)都发出查询请求,各分片返回的时候把元素文档(document)和计算后的排名信息一起返回,这种搜索方法最快,只需查询一次,但是返回的数量可能是用户要求的size的N倍

如:用户要求分数前10的数据,N个分片均返回10个,然后客户端再排序,得到的数量过多

2、query then fetch

默认搜索方式

第一步,先想所有shard发出请求,各分片只返回排序和排名相关的信息(不包括document文档),然后进行第二步,去相关的shard取document,

3、DFS query and fetch

可以更精准的控制搜索打分和排名

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

4、DFS query then fetch

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

总结:性能上QUERY_AND_FETCH最快,DFS_QUERT_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条

五、ElasticSearch分页

与SQL使用limit来控制单页数量类似,Elasticsearch使用from size两个参数

size:每次返回多少结果,默认是10

from:从那条结果开始,默认是0

假设每页显示5条结果,那么1-3页查询请求分别是

第一页:_search?size=5

第二页:_search?size=5&from=5

第三页:_search?size=5&from=10

注意:一次不要请求太多页码过大的结果,这会给服务器造成很大压力,因为他们在返回前生成自己的排序结果,然后进行集中处理,以确保最终结果的正确性

time_out:查询结果是否超时

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

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

六、分片查询

默认是randomize across shards:随机从分片中取数据

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

-Primary:只在主分片中查询

_primary_first:会优先在主分片中查询,如果主分片找不到,会在副本中查询

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

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

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

七、脑裂问题

同一个集群中的不同节点。对于集群的状态有不一样的理解,discovery.zen.minimum_master_nodes用于控制选举的行为发生的最小集群节点数量。一般只有2个以上节点的集群中,主节点才有意义

正常情况下,集群中的所有节点,应该对集群中的master是一致的,若出现不一致的状态信息,说明不同节点对master节点的选择出现了异常,也就是所谓的脑裂问题,脑裂会使集群失去正常状态,导致集群不能正常工作。

脑裂产生原因:

1、由于是网络通信,网络问题可能会导致某些节点认为master死掉,而另选master,这种情况可能性较小

2、节点负载:由于master节点与data节点混在一起,即安装在同一节点上,当工作节点的负载较大时,导致实例es停止响应,而若这台节点恰好充当着master节点的角色,那么一部分节点会认为这个master节点失效,故重新选举节点,这是就会出现脑裂。同时由于data节点上es进程占用的内存较大,较大规模的内存回收操作也会造成es进程失去响应

脑裂问题的解决

修改相关配置

• 主节点 
node.master: true 	//设为true表名此节点可以为master的候选节点,可以配置两台,防止master挂掉后,无master候选使用
node.data: false 
• 从节点 
node.master: false 
node.data: true 
• 所有节点 
discovery.zen.ping.multicast.enabled: false 
discovery.zen.ping.unicast.hosts: [“slave1”, “master” , “slave2"]

八、优化

1、调整最大打开文件的数量

ulimit -a 默认数量为1024

可以将此数值调大,提高性能,高版本已经强制要求调整最大打开文件数量

ulimit -n 32000

2、修改配置文件调整es的JVM内存的大小

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

3、设置mlockall

Linux系统会将一些占着内存但不在使用的写进交换区(磁盘空间,非内存),等使用的时候再读出来,读取写入的过程会存在性能消耗,可以配置锁定swapped提高性能

修改文件

vim conf/elasticsearch.yml  
bootstrap.mlockall: true
4、分片数量

一般分片数量多的话会提高分片索引效率,但过多会过少都会导致检索比较慢,分片数多会导致检索时打开的文件较多,也会导致多态服务器之间通信,而过少会导致单个分片索引过大,检索速度比较慢。因此,单个分片最多存储20G左右的索引数据,所以分片数量=数据总量/20G

5、副本数

副本多可以提升搜索能力,但过多副本会导致文件数量过多,对服务器造成额外压力,一般同步副本设置为2-3个

6、segment

各个分片由一个个segment组成,随着segment越多,查询性能会越来越差,我们可以将segment进行合并

索引量不是很大的话可以将segment设置为1

-命令行
 curl -XPOST 'http://localhost:9200/shsxt/_optimize?max_num_segments=1' 
– java代码:client.admin().indices().prepareOptimize(“shsxt").setMaxNumSegments(1).get();
7、删除文档

在lucene中删除一个文档,数据不会立马重磁盘上删除,而是在lucene索引中产生一个.del的文件,而在检索过程中这部分数据 也会参加检索,之后会判断将它去除掉,这样会降低检索效率,因此可执行清除文档

– curl -XPOST 'http://localhost:9200/elasticsearch/_optimize?only_expunge_deletes=true' 
– client.admin().indices().prepareOptimize("elasticsearch ").setOnlyExpungeDeletes(true).get();
8、副本设置

如果在项目开始时有大量数据需要导入时,可以先将副本数设置为0,这样可以提高导入效率,若大于0,则数据导入的过程中会同时进行副本备份,对es增加压力。待索引完成后将副本数该回,这样可以提高效率

9、all域

elasticsearch默认为每个被索引的文档都定义了一个特殊的域“all域”,它自动包含索引文档中一个或多个域中的内容,在进行搜索时,不指明要搜索的文档的域,elasticsearch会自动搜索all域,提高速度,但也会增加系统在索引阶段对CPU和存储空间资源的开销

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

九、相关注意

java代码操作es集群时,要保证本地和集群上的es版本一致

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

elasticsearch分片规则

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

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