Elasticsearch的默认配置,是综合了数据可靠性、写入速度、搜索实时性等因素。实际使用时,咱们须要根据公司要求,进行偏向性的优化。javascript
假设咱们的应用场景要求是,每秒300万的写入速度,每条500字节左右。
正对这种对于搜索性能要求不高,可是对写入要求较高的场景,咱们须要尽量的选择恰当写优化策略。综合来讲,能够考虑如下几种方面来提高写索引的性能:css
Elasticsearch提供了bulk API支持批量操做,当咱们有大量的写任务时,可使用bulk来进行批量写入。每次提交的数据量为多少时,能达到最优的性能,主要受到文件大小、网络状况、数据类型、集群状态等因素影响。
通用的策略以下:java
bulk默认设置批量提交的数据量不能超过100M。数据条数通常是根据文档的大小和服务器性能而定的,可是单次批处理的数据大小应从5MB~15MB逐渐增长,当性能没有提高时,把这个数据量做为最大值。node
咱们能够跟着,感觉一下bulk接口,以下所示:linux
$ vi request
$ cat request
{ "index" : { "_index" : "chandler","_type": "test", "_id" : "1" } } { "name" : "钱丁君","age": "18" } $ curl -s -H "Content-Type: application/json" -XPOST localhost:9200/_bulk --data-binary @request; echo {"took":214,"errors":false,"items":[{"index":{"_index":"chandler","_type":"test","_id":"1","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":0,"_primary_term":1,"status":201}}]} $ curl -XGET localhost:9200/chandler/test/1?pretty { "_index" : "chandler", "_type" : "test", "_id" : "1", "_version" : 1, "found" : true, "_source" : { "name" : "钱丁君", "age" : "18" } }
bulk不支持get操做,由于没什么用处。web
Elasticsearch是一种密集使用磁盘的应用,在段合并的时候会频繁操做磁盘,因此磁盘要求较高,当磁盘速度提高以后,集群的总体性能会大幅度提升。
磁盘的选择,提供如下几点建议:数据库
path.data:/path/to/data1,/path/to/data2。
Lucene以段的形式存储数据。当有新的数据写入索引时,Lucene就会自动建立一个新的段。随着数据量的变化,段的数量会愈来愈多,消耗的多文件句柄数及CPU就越多,查询效率就会降低。
因为Lucene段合并的计算量庞大,会消耗大量的I/O,因此Elasticsearch默认采用较保守的策略,让后台按期进行段合并,以下所述:json
PUT /_cluster/settings
{
"persistent" : { "indices.store.throttle.max_bytes_per_sec" : "100mb" } }
Lucene在新增数据时,采用了延迟写入的策略,默认状况下索引的refresh_interval为1秒。Lucene将待写入的数据先写到内存中,超过1秒(默认)时就会触发一次refresh,而后refresh会把内存中的的数据刷新到操做系统的文件缓存系统中。若是咱们对搜索的实效性要求不高,能够将refresh周期延长,例如30秒。这样还能够有效地减小段刷新次数,但这同时意味着须要消耗更多的Heap内存。以下所示:bootstrap
index.refresh_interval:30s
flush的主要目的是把文件缓存系统中的段持久化到硬盘,当Translog的数据量达到512MB或者30分钟时,会触发一次Flush。 index.translog.flush_threshold_size 参数的默认值是512MB,咱们进行修改。
增长参数值意味着文件缓存系统中可能须要存储更多的数据,因此咱们须要为操做系统的文件缓存系统留下足够的空间。vim
Elasticsearch为了保证集群的可用性,提供了replicas(副本)支持,然而每一个副本也会执行分析、索引及可能的合并过程,因此replicas的数量会严重影响写索引的效率。当写索引时,须要把写入的数据都同步到副本节点,副本节点越多,写索引的效率就越慢。
若是咱们须要大批量进行写入操做,能够先禁止replica复制,设置index.number_of_replicas: 0 关闭副本。在写入完成后,replica修改回正常的状态。
在5.2.5节中介绍了集群中的查询流程,若是想要查询从from开始的size条数据,须要每一个分片查询打分排名在前面的from+size条数据。协同节点将收集到的n✖️(from+size)条数据聚合,再进行一次排序,而后从from+size开始返回size条数据。
当from、size或者n中有一个值很大的时候,须要参加排序的数量也会增加,这样的查询会消耗不少CPU资源,从而致使效率的下降。
为了提高查询效率,Elasticsearch提供了scroll和scroll-scan这两种查询模式。
scroll是为检索大量的结果而设计的。例如,咱们须要查询1~100页的数据,每页100条数据。
若是使用search查询:每次都须要在每一个分片上查询得分最高的from+100条数据,而后协同节点把收集到的n✖️(from+100)条数据聚合起来再进行一次排序。每次返回from+1开始的100条数据,而且要重复执行100次。
若是使用scroll查询:在各个分片上查询10000条数据,协同节点聚合n✖️10000条数据进行合并、排序,并将排名前10000的结果快照起来。这样作的好处是减小了查询和排序的次数。
Scroll初始查询的命令是:
$ vim scroll
$ cat scroll
{
"query": { "match": { "name": "钱丁君" } }, "size":20 } $ curl -s -H "Content-Type: application/json; charset=UTF-8" -XGET localhost:9200/chandler/test/_search?scroll=2m --data-binary @scroll; echo {"_scroll_id":"DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAAGFlB6Y3QtNk9oUmdpc09Tb21rX2NXQXcAAAAAAAAABxZQemN0LTZPaFJnaXNPU29ta19jV0F3AAAAAAAAAAgWUHpjdC02T2hSZ2lzT1NvbWtfY1dBdwAAAAAAAAAJFlB6Y3QtNk9oUmdpc09Tb21rX2NXQXcAAAAAAAAAChZQemN0LTZPaFJnaXNPU29ta19jV0F3","took":14,"timed_out":false,"_shards":{"total":5,"successful":5,"skipped":0,"failed":0},"hits":{"total":1,"max_score":0.8630463,"hits":[{"_index":"chandler","_type":"test","_id":"1","_score":0.8630463,"_source":{ "name" : "钱丁君","age": "18" }}]}}
以上查询语句的含义是,在chandler索引的test type里查询字段name包含“钱丁君”的数据。scroll=2m表示下次请求的时间不能超过2分钟,size表示此次和后续的每次请求一次返回的数据条数。在此次查询的结果中除了返回了查询到的结果,还返回了一个scroll_id,能够把它做为下次请求的参数。
再次请求的命令,以下所示:
Scroll是先作一次初始化搜索把全部符合搜索条件的结果缓存起来生成一个快照,而后持续地、批量地从快照里拉取数据直到没有数据剩下。而这时对索引数据的插入、删除、更新都不会影响遍历结果,所以scroll 并不适合用来作实时搜索。其思路和使用方式与scroll很是类似,可是scroll-scan关闭了scroll中最耗时的文本类似度计算和排序,使得性能更加高效。
为了使用scroll-scan,须要执行一个初始化搜索请求,将search_type设置成scan,告诉Elasticsearch集群不须要文本类似计算和排序,只是按照数据在索引中顺序返回结果集:
$ vi scroll
$ cat scroll
{
"query": { "match": { "name": "钱丁君" } }, "size":20, "sort": [ "_doc" ] } $ curl -H "Content-Type: application/json; charset=UTF-8" -XGET 'localhost:9200/chandler/test/_search?scroll=2m&pretty=true' --data-binary @scroll { "_scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAABWFlB6Y3QtNk9oUmdpc09Tb21rX2NXQXcAAAAAAAAAVxZQemN0LTZPaFJnaXNPU29ta19jV0F3AAAAAAAAAFgWUHpjdC02T2hSZ2lzT1NvbWtfY1dBdwAAAAAAAABZFlB6Y3QtNk9oUmdpc09Tb21rX2NXQXcAAAAAAAAAWhZQemN0LTZPaFJnaXNPU29ta19jV0F3", "took" : 3, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : 1, "max_score" : null, "hits" : [ { "_index" : "chandler", "_type" : "test", "_id" : "1", "_score" : null, "_source" : { "name" : "钱丁君", "age" : "18" }, "sort" : [ 0 ] } ] } }
注意:Elasticsearch 2.1.0版本以后移除了search_type=scan,使用"sort": [ "_doc"]进行代替。
scroll和scroll-scan有一些差异,以下所示:
ES中所谓的路由和IP网络不一样,是一个相似于Tag的东西。在建立文档的时候,能够经过字段为文档增长一个路由属性的Tag。在多分片的Elasticsearch集群中,对搜索的查询大体分为以下两种。
(1)ES内在机制决定了拥有相同路由属性的文档,必定会被分配到同一个分片上,不管是主分片仍是副本。查询时能够根据routing信息,直接定位到目标分片,避免查询全部的分片,再通过协调节点二次排序。若是5-24所示。
(2)若是在查询条件中不包含routing,在查询时就遍历全部分片,整个查询主要分为Scatter、Sather两个过程。
shard = hash(routing)%number_of_primary_shards
不过须要注意的是,根据城市id进行分片时,也会容易出现分片不均匀的状况。例如,大型城市的数据过多,而小城市的数据太少,致使分片之间的数据量差别很大。这时就能够进行必要的调整,好比把多个小城市的数据合并到一个分片上,把大城市的数据按区域进行拆分到不一样分配。
在Scatter、Gather的过程当中,节点间的数据传输和打分(SearchType),能够根据不一样的场景选择。以下所示
因为在Lucene中段具备不变性,每次进行删除操做后不会当即从硬盘中进行实际的删除,而是产生一个.del文件记录删除动做。随着删除操做的增加,.del文件会越来也多。当咱们进行查询操做的时候,被删除的数据还会参与检索中,而后根据.del文件进行过滤。.del文件越多,查询过滤过程越长,进而影响查询的效率。当机器空闲时,咱们能够经过以下命令删除文件,来提高查询的效率:
$ curl -XPOST localhost:9200/chandler/_forcemerge?only_expunge_deletes=true {"_shards":{"total":10,"successful":5,"failed":0}}
按期对再也不更新的索引作optimize (ES2.0之后更改成force merge api)。这Optimze的实质是对segment file强制作合并,能够节省大量的segment memory。
Elasticsearch默认安装后设置的内存是1GB,对于任何一个现实业务来讲,这个设置都过小了。若是是经过解压安装的Elasticsearch,则在Elasticsearch安装文件中包含一个jvm.option文件,添加以下命令来设置Elasticsearch的堆大小:
-Xms10g -Xmx10g
Xms表示堆的初始大小,Xmx表示可分配的最大内存,都是10GB。确保Xmx和Xms的大小是相同的,其目的是为了可以在java垃圾回收机制清理完堆区后不须要从新分隔计算堆区的大小而浪费资源,能够减轻伸缩堆大小带来的压力。
也能够经过设置环境变量的方式设置堆的大小。服务进程在启动时候会读取这个变量,并相应的设置堆的大小。好比:
export ES_HEAP_SIEZE=10g
也能够经过命令行参数的形式,在程序启动的时候把内存大小传递给Elasticsearch,以下所示:
./bin/elasticsearch -Xmx10g -Xms10g
这种设置方式是一次性的,在每次启动Elasticsearch时都须要添加。
假设你有一个64G内存的机器,按照正常思惟思考,你可能会认为把64G内存都给Elasticsearch比较好,但现实是这样吗, 越大越好?虽然内存对Elasticsearch来讲是很是重要的,可是答案是否认的!由于Elasticsearch堆内存的分配须要知足如下两个原则:
Java使用内存指针压缩(Compressed Oops)技术来解决这个问题。它的指针再也不表示对象在内存中的精确位置,而是表示偏移量。这意味着32位的指针能够引用4GB个Byte,而不是4GB个bit。也就是说,当堆内存为32GB的物理内存时,也能够用32位的指针表示。
不过,在越过那个神奇的边界----32GB时,指针就会变为普通对象的指针,每一个对象的指针都变长了,就会浪费更多的内存,下降了CPU的性能,还要让GC应对更大的内存。事实上,当内存到达40~40GB时,有效的内存才至关于内存对象指针压缩技术时的32GB内存。因此即使你有足够的内存,也尽可能不要超过32G,好比咱们能够设置为31GB:
-Xms31g -Xmx31g
32GB是ES一个内存设置限制,那若是你的机器有很大的内存怎么办呢?如今的机器内存广泛增加,甚至能够看到有300-500GB内存的机器。这时咱们须要根据业务场景,进行恰当内存的分配。
swapping是性能的坟墓
在选择Elasticsearch服务器时,要尽量地选择与当前应用场景相匹配的服务器。若是服务器配置很低,则意味着须要更多的节点,节点数量的增长会致使集群管理的成本大幅度提升。若是服务器配置很高,,而在单机上运行多个节点时,也会增长逻辑的复杂度。
在计算机中运行的程序均需在内存执行,若内存消耗殆尽将致使程序没法进行。为了解决这个问题,操做系统使用一种叫做虚拟内存的技术。当内存耗尽时,操做系统就会自动把内存中暂时不使用的数据交换到硬盘中,须要使用的时候再从硬盘交换到内存。
若是内存交换到磁盘上须要10毫秒,从磁盘交换到内存须要20浩渺,那么多的操做时延累加起来,将致使几何级增加。不难看出swapping对于性能是多么可怕。因此为了使Elasticsearch有更好等性能,强烈建议关闭swap。
关闭swap的方式以下。
(1)暂时禁用。若是咱们想要在linux服务器上暂时关闭,能够执行以下命令,但在服务器重启后失效:
sudo swapoff -a
(2)永久性关闭。咱们能够修改/etc/sysctl.conf(不一样等操做系统路径有可能不一样),增长以下参数:
vm.swappiness = 1 //0-100,则表示越倾向于使用虚拟内存。
注意:swappiness设置为1比设置为0要好,由于在一些内核版本,swappness=0会引起OOM(内存溢出)。
swappiness默认值为60,当设置为0时,在某些操做系统中有可能会触发系统级的OOM-killer,例如在Linux内核的内存不足时,为了防止系统的崩溃,会自动强制kill一个“bad”进程。
(3)在Elasticsearch中设置。若是上面的方法都不能作到,你须要打开配置文件中的mlockall开关,它的做用就是运行JVM锁住内存,禁止OS交换出去。在elasticsearch.yml配置以下:
bootstrap.mlockall: true
因此,若是条件容许,则请尽量地使用SSD,它的读写性能将远远超出任何旋转介质的硬盘(如机械硬盘、磁带等)。基于SSD的Elasticsarch集群节点对于查询和索引性能都有提高。
另外不管是使用固态硬盘仍是使用机械硬盘,咱们都建议将磁盘的阵列模式设置为RAID 0,以此来提高磁盘的写性能。
Elastic search提供了Transport Client(传输客户端)和Node Client(节点客户端)的接入方式,这两种方式各有利弊,分别对应不一样的应用场景。
Transport Client做为一个集群和应用程序之间的通讯层,和集群是安全解耦的。因为与集群解耦,因此在链接集群和销毁链接时更加高效,适合大量的客户端链接。
Node Client把应用程序看成一个集群中的Client节点(非Data和Master节点)。因为它是集群一个的内部节点,意味着它能够感知整个集群的状态、全部节点的分布状况、分片的分布情况等。
因为Node Client是集群的一部分,因此在接入和退出集群时进行比较复杂操做,而且还会影响整个集群的状态,因此Node Client更适合少许客户端,可以提供更好的执行效率。
Elasticsearch集群中的数据节点负责对数据进行增、删、改、查和聚合等操做,因此对CPU、内存和I/O的消耗很大。在搭建Elasticsearch集群时,咱们应该对Elasticsearch集群中的节点进行角色划分和隔离。
候选主节点:
node.master=true node.data=false
数据节点:
node.master=false node.data=true
最后造成如图5-26所示的逻辑划分。
网络异常可能会致使集群中节点划分出多个区域,区域发现没有master节点的时候,会选举出了本身区域内Maste节点r,致使一个集群被分裂为多个集群,使集群之间的数据没法同步,咱们称这种现象为脑裂。为了防止脑裂,咱们须要在Master节点的配置文件中添加以下参数:
discovery.zen.minimum_master_nodes=(master_eligible_nodes/2)+1 //默认值为1
其中master_eligible_nodes为Master集群中的节点数。这样作能够避免脑裂的现象都出现,最大限度地提高集群的高可用性。只要很多于discovery.zen.minimum_master_nodes个候选节点存活,选举工做就能够顺利进行。
在Elasticsearch安装目录下的conf文件夹中包含了一个重要的配置文件:elasticsearch.yaml。
Elasticsearch的配置信息有不少种,大部分配置均可以经过elasticsearch.yaml和接口的方式进行。下面咱们列出一些比较重要的配置信息。
虽然如今有不少开源软件对Elasticsearch的接口进行了封装,使咱们能够很方便、直观地监控集群的情况,可是在Elasticsearch 5之后,不少软件开始收费。了解经常使用的接口有助于咱们在程序或者脚本中查看咱们的集群状况,如下接口适用于Elasticsearch 6.5.2版本。
PUT http://localhost:9200/indexname?pretty content-type →application/json; charset=UTF-8 { "settings":{ "number_of_shards" : 3, "number_of_replicas" : 1 } }
DELETE http://localhost:9200/indexname
DELETE http://localhost:9200/indexname1,indexname2 DELETE http://localhost:9200/indexname*
经过下面的接口能够删除集群下的所有索引。
DELETE http://localhost:9200/_all DELETE http://localhost:9200/*
进行所有索引删除是很危险的,咱们能够经过在配置文件中添加下面的配置信息,来关闭使用_all和使用通配符删除索引的接口,使用删除索引职能经过索引的全称进行。
action.destructive_requires_name: true
GET http://localhost:9200/indexname?pretty
POST http://localhost:9200/indexname/_close POST http://localhost:9200/indexname/_open
GET http://localhost:9200/indexname/typename/_mapping?pretty
当一个索引中有多个type时,得到mapping时要加上typename。
安装ES和Kibana以后,进入Kibana操做页面,而后进去的DevTools执行下面操做:
#添加一条document PUT /test_index/test_type/1 { "test_content":"test test" } #查询 GET /test_index/test_type/1 #返回 { "_index" : "test_index", "_type" : "test_type", "_id" : "1", "_version" : 2, "found" : true, "_source" : { "test_content" : "test test" } }
put /index/type/id,说明以下:
search是咱们最经常使用的API,ES给我提供了丰富的查询条件,好比模糊匹配match,字段判空exists,精准匹配term和terms,范围匹配range
GET /_search
{
"query": { "bool": { "must": [ //must_not { "match": { "title": "Search" }}, { "match": { "content": "Elasticsearch" }}, {"exists":{"field":"字段名"}} //判断字段是否为空 ], "filter": [ { "term": { "status": "published" }}, { "terms": { "status": [0,1,2,3] }},//范围 { "range": { "publish_date": { "gte": "2015-01-01" }}} //范围gte:大于等于;gt:大于;lte:小于等于;lt:小于 ] } } }
查询索引为test_index,doc类型为test_type的数据。
GET /test_index/test_type/_search
查询索引为test_index,doc类型为test_type,docment字段num10为4的数据
GET /test_index/test_type/_search?pretty=true { "query": { "bool": { "filter": [ { "term": { "num10": 4 }} ] } } }
更多查询条件的组合,你们能够自行测试。
PUT /my_index/_mapping/my_type
{
"properties": { "new_field_name": { "type": "string" //字段类型,string、long、boolean、ip } } }
如上是修改mapping结构,而后利用脚本script给字段赋值:
POST my_index/_update_by_query
{
"script": { "lang": "painless", "inline": "ctx._source.new_field_name= '02'" } }
以下給index为test_index的数据绑定alias为test_alias
POST /_aliases
{
"actions": [ { "add": { //add,remove "index": "test_index", "alias": "test_alias" } } ] }
验证别名关联,根据别名来进行数据查询,以下:
GET /test_alias/test_type/3
_source元数据:就是说,咱们在建立一个document的时候,使用的那个放在request body中的json串(全部的field),默认状况下,在get的时候,会原封不动的给咱们返回回来。
定制返回的结果,指定_source中,返回哪些field。
#语法: GET /test_index/test_type/1?_source=test_field2 #返回 { "_index" : "test_index", "_type" : "test_type", "_id" : "1", "_version" : 3, "found" : true, "_source" : { "test_field2" : "test field2" } } #也可返回多个field使用都好分割 GET /test_index/test_type/1?_source=test_field2,test_field1
组件elasticsearch.jar提供了丰富API,不过不利于咱们理解和学习,如今咱们本身来进行封装。
组件API使用RestClient封装document查询接口:
/** * @param index * @param type * @param id * @param fields * 查询返回字段,可空 * @return * @throws Exception * @Description: * @create date 2019年4月3日下午3:12:40 */ public String document(String index, String type, String id, List<String> fields) throws Exception { Map<String, String> paramsMap = new HashMap<>(); paramsMap.put("pretty", "true"); if (null != fields && fields.size() != 0) { String fieldValue = ""; for (String field : fields) { fieldValue += field + ","; } if (!"".equals(fieldValue)) { paramsMap.put("_source", fieldValue); } } return CommonUtils.toString(es.getRestClient() .performRequest("GET", "/" + index + "/" + type + "/" + id, paramsMap).getEntity().getContent()); }
工程使用,封装:
public String searchDocument(String index, String type, String id, List<String> fields) { try { return doc.document(index, type, id, fields); } catch (Exception e) { log.error(e.getMessage()); ExceptionLogger.log(e); throw new RuntimeException("ES查询失败"); } }
测试用例,代码以下:
/** * ES交互验证-查询、更新等等操做 * * @version * @author 钱丁君-chandler 2019年4月3日上午10:27:28 * @since 1.8 */ @RunWith(SpringRunner.class) @SpringBootTest(classes = Bootstrap.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class ESManagerTest { @Autowired private ESBasicManager esBasicManager; @Test public void query() { String result = esBasicManager.searchDocument(ESTagMetadata.INDEX_ALIAS, ESTagMetadata.DOC_TYPE, "188787665220752824", ImmutableList.of("signup_time", "tag_days_no_visit_after_1_order")); System.out.println("----------->" + result); } }
控台输出:
----------->{
"_index" : "crm_tag_idx_20181218_708672", "_type" : "crm_tag_type", "_id" : "188787665220752824", "_version" : 1, "found" : true, "_source" : { "signup_time" : "2017-12-24", "tag_days_no_visit_after_1_order" : "339" } }
我只是抛砖引玉,你们能够自行进行各类操做的封装,无论对于理解ES的使用,仍是对代码质量提高都有不少帮助。
最后谢谢你们观赏
《Elasticsearch详解》
若是须要給我修改意见的发送邮箱:erghjmncq6643981@163.com资料参考:《可伸缩服务架构》转发博客,请注明,谢谢。