elasticsearch学习笔记(八)——剖析Elasticsearch的基础分布式架构

下面来一步一步分析Elasticsearch的基础分布式架构1、Elasticsearch对复杂分布式机制的透明隐藏特性Elasticsearch是一套分布式系统,它隐藏了复杂的分布式机制,可以应对大数据。像分片机制:我们可以随随便便就将一些document插入到es集群中去,不需要关心数据是怎么进行分片的,数据到哪个shard中去cluster discovery:集群发现机制shard负载均衡:将shard分配到ES集群节点上面去,这个过程ES是自动进行均匀分配的,以保持每个节点均衡的读写负载请求。等等2、Elasticsearch的垂直扩容与水平扩容垂直扩容:给机器升级配置,或是采购更强大的服务器。这种方式成本会非常高,而且会有瓶颈。水平扩容:通俗来讲就是加机器,这是业界经常采用的方案,采购越来越多的普通服务器,性能比较一般,但是很多普通服务器组织在一起,就能构成强大的计算和存储能力3、增加和减少节点时数据的rebalance在修改replica 的时候,会导致各个节点的分片不均衡,ES此时会自动进行rebalance操作,来保证各个节点的shard保持均匀。4、master节点Elasticsearch集群的master节点用于维护集群的状态,像(1)创建或者删除索引(2)增加或者删除节点均在master节点完成5、节点对等的分布式架构Elasticsearch采用节点对等的分布式架构,这种架构的好处是对于每个节点都能接受所有的请求,请求打到集群的时候会进行自动路由到相应的分片。最后附上中华石衫老师画的手绘图:

April 17, 2019 · 1 min · jiezi

elasticsearch学习笔记(七)——快速入门案例实战之电商网站商品管理:嵌套聚合,下钻分析,聚合分析

为了描述ES是如何进行嵌套聚合、下钻分析,聚合分析。下面通过五个业务需求来进行描述。1、计算每个tag下的商品数量由于tag是一个数组,所以其实统计的就是针对tag数组中的每个值,所有文档中tag字段中包含这个值的文档数量。GET /product/_search{ “size”: 0, “aggs”: { “all_tags”: { “terms”: { “field”: “tags”, “size”: 10 } } }}{ “took” : 0, “timed_out” : false, “_shards” : { “total” : 1, “successful” : 1, “skipped” : 0, “failed” : 0 }, “hits” : { “total” : { “value” : 3, “relation” : “eq” }, “max_score” : null, “hits” : [ ] }, “aggregations” : { “all_tags” : { “doc_count_error_upper_bound” : 0, “sum_other_doc_count” : 0, “buckets” : [ { “key” : “fangzhu”, “doc_count” : 2 }, { “key” : “meibai”, “doc_count” : 1 }, { “key” : “qingxin”, “doc_count” : 1 } ] } }}2、对名称中包含yagao的商品,计算每个tag下的商品数量GET /product/_search{ “query”: { “match”: { “name”: “yagao” } }, “size”: 0, “aggs”: { “all_tags”: { “terms”: { “field”: “tags”, “size”: 10 } } }}3、计算每个tag下的商品的平均价格GET /product/_search{ “size”: 0, “aggs”: { “group_by_tags”: { “terms”: { “field”: “tags”, “size”: 10 }, “aggs”: { “avg_price”: { “avg”: { “field”: “price” } } } } }}4、计算每个tag下商品的平均价格,并且按照平均价格降序排列GET /product/_search{ “size”: 0, “aggs”: { “group_by_tags”: { “terms”: { “field”: “tags”, “size”: 10, “order”: { “avg_price”: “desc” } }, “aggs”: { “avg_price”: { “avg”: { “field”: “price” } } } } }}5、按照指定的价格范围区间进行分组,然后在每组内在按照tag进行分组,最后在计算每组的平均价格GET /product/_search{ “size”: 0, “aggs”: { “group_by_price”: { “range”: { “field”: “price”, “ranges”: [ { “from”: 0, “to”: 20 }, { “from”: 20, “to”: 40 }, { “from”: 40, “to”: 50 } ] }, “aggs”: { “group_by_tags”: { “terms”: { “field”: “tags”, “size”: 10 }, “aggs”: { “avg_price”: { “avg”: { “field”: “price” } } } } } } }} ...

April 16, 2019 · 2 min · jiezi

elasticsearch学习笔记(六)——快速入门案例实战之电商网站商品管理:多种搜索方式

简单介绍一下ES的多种搜索方式1、query string search格式:GET /{index}/_searchGET /product/_search{ “took” : 1, “timed_out” : false, “_shards” : { “total” : 1, “successful” : 1, “skipped” : 0, “failed” : 0 }, “hits” : { “total” : { “value” : 2, “relation” : “eq” }, “max_score” : 1.0, “hits” : [ { “_index” : “product”, “_type” : “_doc”, “_id” : “2”, “_score” : 1.0, “_source” : { “name” : “jiajieshi yagao”, “desc” : “youxiao fangzhu”, “price” : 25, “producer” : “jiajieshi producer”, “tags” : [ “fangzhu” ] } }, { “_index” : “product”, “_type” : “_doc”, “_id” : “3”, “_score” : 1.0, “_source” : { “name” : “zhonghua yagao”, “desc” : “caoben zhiwu”, “price” : 40, “producer” : “zhonghua producer”, “tags” : [ “qingxin” ] } } ] }}简单见一下查询结果的各个值的含义:took:耗费的时间 单位是毫秒timed_out:是否超时_shards: total是指打到的primary shard(或者replica shard)的个数,successful是指查询成功的分片数,skipped是指跳过的分片个数,failed是指查询失败的分片的个数hits.total:value代表查询匹配的总数,relation代表The count is accurate (e.g. “eq” means equals).hits.max_score:是指匹配的文档中相关度分数最高的hits.hits:包含匹配搜索的document的详细数据为什么叫做query string search ,主要是因为search参数都是以http请求的query string来附带的例如搜索商品名称中包含yagao的商品,而且按照售价降序排列:GET /product/_search?q=name:yagao&sort=price:desc{ “took” : 36, “timed_out” : false, “_shards” : { “total” : 1, “successful” : 1, “skipped” : 0, “failed” : 0 }, “hits” : { “total” : { “value” : 2, “relation” : “eq” }, “max_score” : null, “hits” : [ { “_index” : “product”, “_type” : “_doc”, “_id” : “3”, “_score” : null, “_source” : { “name” : “zhonghua yagao”, “desc” : “caoben zhiwu”, “price” : 40, “producer” : “zhonghua producer”, “tags” : [ “qingxin” ] }, “sort” : [ 40 ] }, { “_index” : “product”, “_type” : “_doc”, “_id” : “2”, “_score” : null, “_source” : { “name” : “jiajieshi yagao”, “desc” : “youxiao fangzhu”, “price” : 25, “producer” : “jiajieshi producer”, “tags” : [ “fangzhu” ] }, “sort” : [ 25 ] } ] }}query string search适用于临时的在命令行使用的一些工具,比如curl,快速发出请求,来检索想要的信息。但是如果查询请求很复杂,就很难去构建搜索条件,在生产环境中很少使用。2、query DSL什么叫做DSL?DSL:Domain Specified Language 特定领域语言使用query DSL 查询时查询的参数采用的是请求体(http request body),可以用json的格式来构建查询语法,比较方便,可以构建各种复杂的语法。比query string search 肯定是强大多了格式:GET /{index}/{type}/_search{ “json数据”}下面都是实际的一些例子:查询所有的商品:GET /product/_search{ “query”: { “match_all”: {} }}查询名称中包含yagao的商品,同时按照价格降序排序:GET /product/_search{ “query”: { “match”: { “name”: “yagao” } }, “sort”: [ { “price”: { “order”: “desc” } } ]}分页查询商品,总共3个商品,假设每一页就显示1条商品,现在显示第2页,所以就查出来第2个商品GET /product/_search{ “query”: { “match_all”: {} }, “from”: 1, “size”: 1}指定要查询出来的商品只返回名称和价格,也就是定制返回字段GET /product/_search{ “query”: { “match_all”: {} }, “_source”: [“name”, “price”]}query DSL 更加适合生产环境使用,可以构建复杂的查询3、query filter搜索商品名称包含yagao,而且售价大于25元的商品GET /product/_search{ “query”: { “bool”: { “must”: [ { “match”: { “name”: “yagao” } } ], “filter”: { “range”: { “price”: { “gt”: 25 } } } } }}4、full-text searchGET /product/_search{ “query”: { “match”: { “producer”: “jiajieshi producer” } }}{ “took” : 0, “timed_out” : false, “_shards” : { “total” : 1, “successful” : 1, “skipped” : 0, “failed” : 0 }, “hits” : { “total” : { “value” : 2, “relation” : “eq” }, “max_score” : 0.18232156, “hits” : [ { “_index” : “product”, “_type” : “_doc”, “_id” : “2”, “_score” : 0.18232156, “_source” : { “name” : “jiajieshi yagao”, “desc” : “youxiao fangzhu”, “price” : 25, “producer” : “jiajieshi producer”, “tags” : [ “fangzhu” ] } }, { “_index” : “product”, “_type” : “_doc”, “_id” : “3”, “_score” : 0.18232156, “_source” : { “name” : “zhonghua yagao”, “desc” : “caoben zhiwu”, “price” : 40, “producer” : “zhonghua producer”, “tags” : [ “qingxin” ] } } ] }}为什么连zhonghua producer这个文档也被检索出来了,原因是producer这个字段一开始插入数据的时候,就会被拆解,建立倒排索引jiajieshi 1zhonghua 2producer 1,2搜索yagao producer的时候,会进行拆分变成yagao和producer5、phrase search短语搜索phrase search 跟全文检索相反,全文检索会将输入的搜索串拆解开来,去倒排索引里面一一去匹配,只要能匹配上任意一个拆解后的单词,就可以作为结果返回。但是phrase search要求输入的搜索串,必须在指定的字段文本中,完全包含一模一样的,才可以算匹配上了,作为结果返回。GET /product/_search{ “query”: { “match_phrase”: { “producer”: “jiajieshi producer” } }}{ “took” : 4, “timed_out” : false, “_shards” : { “total” : 1, “successful” : 1, “skipped” : 0, “failed” : 0 }, “hits” : { “total” : { “value” : 1, “relation” : “eq” }, “max_score” : 0.87546873, “hits” : [ { “_index” : “product”, “_type” : “_doc”, “_id” : “2”, “_score” : 0.87546873, “_source” : { “name” : “jiajieshi yagao”, “desc” : “youxiao fangzhu”, “price” : 25, “producer” : “jiajieshi producer”, “tags” : [ “fangzhu” ] } } ] }}6、highlight search高亮搜索GET /product/_search{ “query”: { “match_phrase”: { “producer”: “jiajieshi producer” } }, “highlight”: { “fields”: { “producer”:{} } }}{ “took” : 23, “timed_out” : false, “_shards” : { “total” : 1, “successful” : 1, “skipped” : 0, “failed” : 0 }, “hits” : { “total” : { “value” : 1, “relation” : “eq” }, “max_score” : 0.87546873, “hits” : [ { “_index” : “product”, “_type” : “_doc”, “_id” : “2”, “_score” : 0.87546873, “_source” : { “name” : “jiajieshi yagao”, “desc” : “youxiao fangzhu”, “price” : 25, “producer” : “jiajieshi producer”, “tags” : [ “fangzhu” ] }, “highlight” : { “producer” : [ “<em>jiajieshi</em> <em>producer</em>” ] } } ] }} ...

April 16, 2019 · 4 min · jiezi

elasticsearch学习笔记(五)——快速入门案例实战电商网站商品管理:集群健康检查,文档的CRUD

elasticsearch和kibana都已经安装和启动了,下就开始进行实战了1、document数据格式首先来讲一下ES为什么面向文档以及面向文档的好处。(1)一般应用系统的数据结构都是面向对象的,结构复杂,操作起来特别不方便。如果将对象数据存储到数据库中,只能拆解开来,变为扁平的多张表,每次查询的时候还得还原回对象格式,相当的麻烦。(2)ES是面向文档的,文档中存储的数据结构,与面向对象的数据结构是一样的,基于这种文档的数据结构,es可以提供复杂的索引,全文检索,分析聚合等的功能。(3)es的document底层是用json数据格式来表达的,json的优势就用说了,附上一篇文章来说明 https://blog.csdn.net/it_drea…对象的数据结构:public class Employee { private String email; private String firstName; private String lastName; private EmployeeInfo info; private Date joinDate;}public class EmployeeInfo { private String bio; private Integer age; private String[] interests;}EmployeeInfo info = new EmployeeInfo();info.setBio(“curious and modest”);info.setAge(30);info.setInterests(new String[]{“bike”, “climb”});Employee employee = new Employee();employee.setEmail(“zhangsan@sina.com”);employee.setFirstName(“san”);employee.setLastName(“zhang”);employee.setInfo(info);employee.setJoinDate(new Date());两张表:employee表,employee_info表,将employee对象的数据重新拆开来,变成Employee数据和EmployeeInfo数据employee表:email,first_name,last_name,join_date,4个字段employee_info表:bio,age,interests,3个字段从外还有一个外键字段,比如employee_id关联着employee表ES面向文档的json数据结构:{ “email”:“zhangsan@sina.com”, “first_name”:“san”, “last_name”:“zhang”, “info”: { “bio”:“curious and modest”, “age”:30, “interests”:[“bike”, “climb”] }, “join_date”:“2017/01/01”}这里我们就可以明白ES的document数据格式和数据库的关系型数据库的区别2、电商网站商品管理案例背景介绍有一个电商网站,需要为其基于ES构建一个后台系统,提供以下功能:(1)对商品信息进行CRUD(增删改查)操作(2)执行简单的结构化查询(3)可以执行简单的全文检索,以及复杂的phrase(短语)检索(4)对于全文检索的结果,可以进行高亮显示(5)对数据进行简单的聚合分析3、简单的集群管理(1)快速检查集群的健康状况es提供了一套api,叫做cat api,可以查看ES的各种各样的配置以及状态数据GET /_cat/health?vepoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent1555412142 10:55:42 elasticsearch green 1 1 2 2 0 0 0 0 - 100.0%快速了解集群的健康状况,查看status参数值即可green: 每个索引的primary shard和replica shard都是active状态yellow: 每个索引的primary shard都是active状态,但是部分的replica shard不是active状态,处于不可用的状态red: 不是所有的索引的primary shard都是active状态,部分索引有数据的丢失(2)快速查看集群中有哪些索引GET /_cat/indices?vhealth status index uuid pri rep docs.count docs.deleted store.size pri.store.sizegreen open .kibana_task_manager q25yU7fCQlKw5PnMwe-IPA 1 0 2 0 45.5kb 45.5kbgreen open .kibana_1 u3ZsZEtUQCiIFpng4Z-Mww 1 0 3 0 14.2kb 14.2kb(3)简单的索引操作创建索引PUT /test_index?pretty{ “acknowledged” : true, “shards_acknowledged” : true, “index” : “test_index”}删除索引DELETE /test_index?pretty{ “acknowledged” : true}(4)商品的CRUD操作1、新增商品:新增文档,建立索引格式PUT /{index}/{type}/{id}{ “json数据”}PUT /product/_doc/1{ “name”:“gaolujie yagao”, “desc”: “gaoxiao meibai”, “price”:30, “producer”:“gaolujie producer”, “tags”:[“meibai”, “fangzhu”]}{ “_index” : “product”, “_type” : “_doc”, “_id” : “1”, “_version” : 1, “result” : “created”, “_shards” : { “total” : 2, “successful” : 1, “failed” : 0 }, “_seq_no” : 0, “_primary_term” : 1}PUT /product/_doc/2{ “name” : “jiajieshi yagao”, “desc” : “youxiao fangzhu”, “price” : 25, “producer” : “jiajieshi producer”, “tags”: [ “fangzhu” ]}PUT /product/_doc/3{ “name”:“zhonghua yagao”, “desc”: “caoben zhiwu”, “price”:40, “producer” :“zhonghua producer”, “tags”:[“qingxin”]}这里不用事先创建好索引index和类型type,ES会默认对document每个field都建立倒排索引,让其可以被搜索2、查询商品:检索文档格式:GET /{index}/{type}/{id}GET /product/_doc/1{ “_index” : “product”, “_type” : “_doc”, “_id” : “1”, “_version” : 1, “_seq_no” : 0, “_primary_term” : 1, “found” : true, “_source” : { “name” : “gaolujie yagao”, “desc” : “gaoxiao meibai”, “price” : 30, “producer” : “gaolujie producer”, “tags” : [ “meibai”, “fangzhu” ] }}3、修改商品:替换文档格式:PUT /{index}/{type}/{id}{ “json数据”}PUT /product/_doc/1{ “name” : “jiaqiangban gaolujie yagao”, “desc” : “gaoxiao meibai”, “price” : 30, “producer” : “gaolujie producer”, “tags”: [ “meibai”, “fangzhu” ]}{ “_index” : “product”, “_type” : “_doc”, “_id” : “1”, “_version” : 2, “result” : “updated”, “_shards” : { “total” : 2, “successful” : 1, “failed” : 0 }, “_seq_no” : 3, “_primary_term” : 1}替换方式有一个不好,替换时必须带上所有的fields,才能达到我们想要的修改效果举个例子,如果执行PUT /product/_doc/1{ “name” : “jiaqiangban gaolujie yagao”}GET /product/_doc/1{ “_index” : “product”, “_type” : “_doc”, “_id” : “1”, “_version” : 3, “_seq_no” : 4, “_primary_term” : 1, “found” : true, “_source” : { “name” : “jiaqiangban gaolujie yagao” }}就不是我们想要的了4、修改商品:更新文档格式POST /{index}/_update/{id}虽然本质还是一样的,但是进行替换处理的操作全部放在了ES内部,我们传输的数据只需要传需要修改的字段即可,大大降低了在批量处理时的网路带宽,提高了性能。下面是展示的例子:GET /product/_doc/1{ “_index” : “product”, “_type” : “_doc”, “_id” : “1”, “_version” : 4, “_seq_no” : 5, “_primary_term” : 1, “found” : true, “_source” : { “name” : “jiaqiangban gaolujie yagao”, “desc” : “gaoxiao meibai”, “price” : 30, “producer” : “gaolujie producer”, “tags” : [ “meibai”, “fangzhu” ] }}POST /product/_update/1{ “doc”:{ “name”: “jiajieshi yagao” }}GET /product/_doc/1{ “_index” : “product”, “_type” : “_doc”, “_id” : “1”, “_version” : 5, “_seq_no” : 6, “_primary_term” : 1, “found” : true, “_source” : { “name” : “jiajieshi yagao”, “desc” : “gaoxiao meibai”, “price” : 30, “producer” : “gaolujie producer”, “tags” : [ “meibai”, “fangzhu” ] }}从这个例子就可以看出update操作成功了5、删除商品:删除文档格式:DELETE /{index}/{type}/{id}DELETE /product/_doc/1{ “_index” : “product”, “_type” : “_doc”, “_id” : “1”, “_version” : 6, “result” : “deleted”, “_shards” : { “total” : 2, “successful” : 1, “failed” : 0 }, “_seq_no” : 7, “_primary_term” : 1} ...

April 16, 2019 · 3 min · jiezi

elasticsearch学习笔记(四)——在windows上安装和启动Elasticsearch

下面先用windows作为开发环境,之后开发采用linux操作系统:windows10JDK版本:1.8.0_201elasticsearch:7.0.0kibana:7.0.01、安装JDK(1)下载JDK下载地址:https://www.oracle.com/techne…选择对应版本即可(2)配置环境变量1、创建JAVA_HOME变量选择自己的安装目录即可,我的是D:Program FilesJavajdk1.8.0_2012、在path中添加%JAVA_HOME%bin%JAVA_HOME%jrebin(3)验证java -version2、启动Elasticsearch下载elasticsearch 地址:https://www.elastic.co/cn/dow…(1)启动 bin/elasticsearch.bat](2)检查ES是否启动成在浏览器输入:http://localhost:9200即可…]修改集群的各种参数可以在config/elasticsearch.yml中进行修改3、启动kibana下载kibana 地址:https://www.elastic.co/cn/dow…(1)启动kibana bin/kibana.bat2)验证kibana是否启动成功打开Dev Tool 输入命令修改kibana的配置可以在config/kibana.yml中进行修改

April 16, 2019 · 1 min · jiezi

elasticsearch学习笔记(三)——Elasticsearch的核心概念

下面来通过lucene引出Elasticsearch的核心概念1、lucene和elasticsearch的前世今生lucene是最先进、功能强大的搜索库。但是直接基于lucene开发,会非常的复杂。它的api很复杂,只是实现一些简单的功能,可能就需要写大量的java代码。要用好lucene,需要深入了解其内部原理,包括各种索引结构。而elasticsearch,它是基于lucene,隐藏了lucene的复杂性,对外提供简单易用的restful api接口、java api接口等等。关于elasticsearch的一个传说,据说有一个程序员失业了,陪着老婆去英国伦敦学习厨师课程。看着老婆每次查找菜谱特别费劲,于是就想给老婆写一个菜谱搜索引擎,准备使用lucene,但是发现lucene实在是太复杂了,就开发了一个封装了lucene的开源项目,compass。后来程序员找到工作了,是做分布式的高性能项目的,就觉得自己封装的开源项目compass也不够用了,就写了elasticsearch,让lucene变成分布式的系统。2、elasticsearch的核心概念(1)Near Realtime(NRT):近实时,两层意思,一个是写入数据到数据可以被搜索到有一个小延迟(大概1s左右),一个是基于ES执行搜索和分析可以达到秒级(2)Cluster:集群,包含多个节点,每个节点属于哪个集群是通过一个叫做集群名称的配置来决定的。对于中小型应用来说,刚开始一个集群就一个节点很正常(3)Node:节点,集群中的一个节点,节点也有一个名称默认是随机分配的,也可以手动指定,当我们在执行运维管理操作的时候节点的名称很重要(4)Document&field:文档,es中最小的数据单元。一个document可以是一条客户数据,一条商品分类数据,一条订单数据,通常用JSON数据结构表示,每个index的type中都可以去存储多个document。一个document里面有很多个field,每个field就是一个数据字段。(5)Index:索引,包含一堆有类似结构的文档数据。比如可以有一个客户索引,商品分类索引,订单索引,索引有一个名称。一个index包含很多document,一个index就代表了一类类似的或者相同的document。比如说建立一个product index,商品索引,里面可能就存放了所有的商品数据,所有的商品document。(6)Type:类型,每个索引里都可以有一个type,之后更高级的版本可能会去掉这一个概念。type是index中的一个逻辑数据分类,一个type下的document,都有相同的field,比如博客系统,有一个索引,可以定义用户数据type(7)shard:分片,单台机器无法存储大量数据,es可以将一个索引中的数据切分为多个shard,分布在多台服务器上存储。有了shard就可以横向扩展,存储更多的数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能。每个shard都是一个lucene index。(8)replica:任何一个服务器随时可能故障或宕机,此时shard可能就会丢失,因此可以为每个shard创建多个replica副本。replica可以在shard故障时提供备用服务,保证数据不丢失,多个replica还可以提升搜索操作的吞吐量和性能。primary shard(建立索引时一次设置,不能修改,默认是5个),replica(随时修改数量,默认是1个)。默认每个索引10个shard,5个primary shard,5个replica shard,最小的高可用配置是2台服务器3、elasticsearch核心概念和数据库核心概念Elasticsearch | Document | Index (Type)数据库 | 行 | 表

April 16, 2019 · 1 min · jiezi

elasticsearch学习笔记(一)——大白话告诉你什么是elasticsearch

什么是elasticsearch?wiki上面的解释是:Elasticsearch is a search engine based on the Lucene library. It provides a distributed, multitenant-capable full-text search engine with an HTTP web interface and schema-free JSON documents.即:ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。通俗的讲:elasticsearch就是一个分布式、高性能、高可用、可伸缩的搜索和分析系统下面围绕以下四个问题展开来说明什么是Elasticsearch以及为什么使用Elasticsearch1、什么是搜索?2、如果用数据库做搜索会怎么样?3、什么是全文检索、倒排索引和Lucene?4、什么是Elasticsearch?1、什么是搜索?一提到搜索我们可能会想到百度,比如说我们想找寻任何信息的时候,就会上百度去搜索一下,找一部自己喜欢的电影,或者找一本喜欢的书,或者找一条感兴趣的新闻。通俗的来讲,搜索就是在任何场景下,找寻你想要的信息,这个时候,会输入一段你要搜索的关键字,然后就期望找到这个关键字相关的有些信息。2、如果用数据库做搜索会怎么样?做软件开发的话,或者对IT、计算机有一定的了解的话,都知道,数据都是存储在数据库里面的,比如说电商网站的商品信息,招聘网站的职位信息,新闻网站的新闻信息等等。所以说,很自然的1、每条记录的指定字段的文本可能会很长,比如说商品描述字段的长度,有长达数千个,甚至数万个字符,这个时候,每次都要对每条记录的所有文本进行扫描2、不能讲搜索词拆分开来,尽可能去搜索更多的符合你期望的结果,比如生化机,就搜索不出来生化危机这些场景下,用数据库来实现搜索是太不靠谱的,通常来说,性能也会非常差。3、什么是全文检索、倒排索引和Lucene?全文检索:计算机索引程序通过扫描文章中的每个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。对于倒排索引,也就是ES在全文检索时对每个词建立索引是采用倒排索引的方式。它源于实际应用中需要根据属性的值来查找记录。这中索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。由于不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引。lucene就是一个全文检索引擎工具包,里面包含了封装好的各种建立倒排索引,以及进行搜索的代码,包括各种算法。4、什么是Elasticsearch?这里附上中华石衫老师画的手工图,哈哈哈!!!简言之就是elasticsearch就是对lucene的一个封装,让复杂的lucene变得简单化,更易用。例如:1、自动维护数据将分布到多个节点的索引的建立,还有搜索请求分布到多个节点的执行2、自动维护数据的冗余副本,保证说,一些数据宕机了,不会丢失任何的数据3、封装了更多的高级功能,以给我们提供更多高级的支持,让我们快速的开发应用。开发更多的复杂的应用:复杂的搜索功能、聚合分析功能、基于地理位置的搜索

April 16, 2019 · 1 min · jiezi

elasticsearch学习笔记(二)——elasticsearch的功能、适用场景以及特点介绍

学习了什么是Elasticsearch之后,针对工程而言,我们更加关心的是Elasticsearch它能干什么?能在什么地方发挥作用?跟其它类似的东西相比它不同的地方在哪里?归纳起来就是Elasticsearch在什么场景下,相比于其他类似的技术而言,它更适合做什么1、Elasticsearch的功能(1)分布式的搜索引擎和数据分析引擎搜索:百度,网站的站内搜索,IT系统的检索数据分析:电商网站,最近7天牙膏这种商品销量排名前十的商家有哪些;新闻网站,最近一个月访问量排名前3的新闻板块是哪些(2)全文检索,结构化检索,数据分析全文检索:我想搜索商品名称包含牙膏的商品,select * from products where product_name like “%牙膏%“结构化检索:我想搜索商品分类为日化用品的商品有哪些,select * from products where category_id=“日化用品"数据分析:我们分析每一个商品分类下有多少个商品,select category_id,count(*) from products group by category_id(3)对海量数据进行近实时的处理分布式:ES自动可以将海量数据分散到多台服务器上去存储和检索海量数据的处理:分布式以后,就可以采用大量的服务器去存储和检索数据,自然而然就可以实现海量数据的处理了近实时:检索个数据要花费1个小时(这就不叫做近实时,叫做离线批处理,batch-processing);在秒级别对数据进行搜索和分析才叫做近实时2、Elasticsearch的适用场景国外:(1)维基百科 全文检索、高亮、搜索推荐(2)The Guardian(国外新闻网站) 用户行为日志(点击,浏览,收藏,评论)+社交网络数据(对某某新闻的相关看法),数据分析,给到每篇新闻文章的作者,让他们知道他的文章的公众反馈(好、坏、热门。。。)(3)Stack Overflow(国外程序异常讨论论坛),全文检索,搜索到相关问题和答案,如果程序报错了,就会将报错信息粘贴到里面去,搜索有没有对应的答案(4)github,搜索上千亿行的代码(5)电商网站,检索商品(6)日志数据的分析 elk技术(7)商品价格监控网站,用户设定某商品的价格阈值,当低于该阈值的时候,发送通知消息给用户(8)BI系统,商业智能Business Intelligence。比如有个大型商场集团,BI,分析一下某某地区最近3年的用户消费金额的趋势以及用户群体的组成构成,产出相关的数张报表。国内站内搜索(电商、招聘、门户等等)IT系统搜索(OA、CRM、ERP等等)数据分析2、Elasticsearch的特点(1)可以作为大型分布式集群(数百台服务器)技术,处理PB级的数据,服务大公司;也可以运行在单机上服务于小公司(2)Elasticsearch不是什么新技术,主要是将全文检索、数据分析以及分布式技术,合并在了一起,才形成了独一无二的ES:lucene(全文检索),商用的数据分析软件,分布式数据库(3)对用户而言,是开箱即用的,非常简单,作为中小型应用,直接3分钟部署一下ES,就可以作为生产环境的系统来使用了,此时的场景是数据量不大,操作不是太复杂(4)数据库的功能面对很多领域是不够用的(事务,还有各种联机事务型的操作);特殊的功能,比如全文检索,同义词处理,相关度排名,复杂数据分析,海量数据的近实时处理,Elasticsearch作为传统数据库的一个补充,提供了数据库所不能提供的很多功能

April 16, 2019 · 1 min · jiezi

centos7 安装ELK做日志收集(elasticsearch,logstash,kibana)

关于elk不用说,大家多多少少都听过,最近我搭建了一套用作收集日志,供大家参考:一.安装elasticsearch,logstash,kibana强烈建议安装去es的官网安装: 今天是2019.4.9,目前最新的版本是6.7.1,三个都装6.7.1版本(版本最好一致,否则会有各种疑难杂症)elasticsearch: https://www.elastic.co/downlo...logstash: https://www.elastic.co/cn/dow...kibana:https://www.elastic.co/cn/dow…其中es的安装教程我在这篇文章里面已经写了:https://segmentfault.com/a/11…,下面介绍logstash和kibana的安装过程1.logstash: 下载后##进入安装包所在目录,解压tar -xf logstash-6.7.1.tar.gz##切换到bin目录cd /logstash-6.7.1/bin##编辑或者创建一个启动的配置文件,到时候用这个配置文件启动vim input_flter_output.confinput { file{ path=> “/crawler/jenkins/.log” ##生成日志的目录 type=> “cml” ##索引的类型 start_position=> “beginning” ##一开始就输入原来的日志信息 } stdin{}}filter{ }output{ elasticsearch{ action=> “index” hosts=> “www.iamcrawler.cn:9500” ##输出到elasticsearch上面 index=> “log-%{+yyyy.MM.dd}” ##生成一个log-时间的索引 }} #最后保存退出 ##在bin目录下再编写一个启动bat vim run.bat sh logstash -f input_flter_output.conf & #最后保存退出 然后运行run.bat即可 [root@iamcrawler bin]# sh run.bat ##正常会出现以下情况: [root@iamcrawler bin]# Sending Logstash logs to /crawler/logstash/logstash-6.7.1/logs which is now configured via log4j2.properties [2019-04-09T13:11:40,120][WARN ][logstash.config.source.multilocal] Ignoring the ‘pipelines.yml’ file because modules or command line options are specified [2019-04-09T13:11:40,138][INFO ][logstash.runner ] Starting Logstash {“logstash.version”=>“6.7.1”} [2019-04-09T13:11:50,041][INFO ][logstash.pipeline ] Starting pipeline {:pipeline_id=>“main”, “pipeline.workers”=>2, “pipeline.batch.size”=>125, “pipeline.batch.delay”=>50} [2019-04-09T13:11:50,697][INFO ][logstash.outputs.elasticsearch] Elasticsearch pool URLs updated {:changes=>{:removed=>[], :added=>[http://www.iamcrawler.cn:9500/]}} [2019-04-09T13:11:51,065][WARN ][logstash.outputs.elasticsearch] Restored connection to ES instance {:url=>“http://www.iamcrawler.cn:9500/"} [2019-04-09T13:11:51,191][INFO ][logstash.outputs.elasticsearch] ES Output version determined {:es_version=>6} [2019-04-09T13:11:51,196][WARN ][logstash.outputs.elasticsearch] Detected a 6.x and above cluster: the type event field won’t be used to determine the document _type {:es_version=>6} [2019-04-09T13:11:51,232][INFO ][logstash.outputs.elasticsearch] Using default mapping template [2019-04-09T13:11:51,253][INFO ][logstash.outputs.elasticsearch] New Elasticsearch output {:class=>“LogStash::Outputs::ElasticSearch”, :hosts=>[”//www.iamcrawler.cn:9500"]} [2019-04-09T13:11:51,287][INFO ][logstash.outputs.elasticsearch] Attempting to install template {:manage_template=>{“template”=>“logstash-”, “version”=>60001, “settings”=>{“index.refresh_interval”=>“5s”}, “mappings”=>{"default"=>{“dynamic_templates”=>[{“message_field”=>{“path_match”=>“message”, “match_mapping_type”=>“string”, “mapping”=>{“type”=>“text”, “norms”=>false}}}, {“string_fields”=>{“match”=>"", “match_mapping_type”=>“string”, “mapping”=>{“type”=>“text”, “norms”=>false, “fields”=>{“keyword”=>{“type”=>“keyword”, “ignore_above”=>256}}}}}], “properties”=>{"@timestamp"=>{“type”=>“date”}, “@version”=>{“type”=>“keyword”}, “geoip”=>{“dynamic”=>true, “properties”=>{“ip”=>{“type”=>“ip”}, “location”=>{“type”=>“geo_point”}, “latitude”=>{“type”=>“half_float”}, “longitude”=>{“type”=>“half_float”}}}}}}}} [2019-04-09T13:11:51,399][INFO ][logstash.outputs.elasticsearch] Installing elasticsearch template to _template/logstash [2019-04-09T13:11:51,783][INFO ][logstash.inputs.file ] No sincedb_path set, generating one based on the “path” setting {:sincedb_path=>"/crawler/logstash/logstash-6.7.1/data/plugins/inputs/file/.sincedb_6677650ec826fa62a735f6625357dead", :path=>["/crawler/jenkins/.log"]} [2019-04-09T13:11:51,896][INFO ][logstash.pipeline ] Pipeline started successfully {:pipeline_id=>“main”, :thread=>"#<Thread:0x729fdee9 run>"} [2019-04-09T13:11:52,010][INFO ][filewatch.observingtail ] START, creating Discoverer, Watch with file and sincedb collections [2019-04-09T13:11:52,033][INFO ][logstash.agent ] Pipelines running {:count=>1, :running_pipelines=>[:main], :non_running_pipelines=>[]} [2019-04-09T13:11:52,723][INFO ][logstash.agent ] Successfully started Logstash API endpoint {:port=>9600} 2.kibana的安装 kibana安装比较简单,下载后,进入安装目录的config目录,如: cd /crawler/kibana/kibana-6.7.1-linux-x86_64/configvim kibana.yml##添加如下命令server.host: 0.0.0.0elasticsearch.url: “http://localhost:9500” #这里是es的http地址##进入kibana目录,执行以下命令,后台运行kibana./bin/kibana &二.kibana的使用可以参照网上的很多教程,这里就不过多的描述了 ...

April 9, 2019 · 2 min · jiezi

Elasticsearch 学习笔记——2.es 的简单命令操作

导入数据首先,我们需要一些数据来支持我们的操作,这里我采用的是使用 filebeat 来采集数据到 es ,filebeat 也是 elastic 系列的产品,专门用来收集日志文件,使用十分的简单,在官网(下载地址)下载安装包解压,然后修改一下配置文件 filebeat.yml,具体的修改如下:将 enabled 修改为 true,表示启用导入配置,然后在路径 paths 那里配置日志文件的地址。然后修改输出设置,将数据输出到 es 中,这里需要配置 elasticsearch 的地址,我这里是 192.168.66.135:9201,你配置成自己在 es 的配置文件中设置的 ip 和 port 就行了。启动 filebeat 的命令:./filebeat -e -c filebeat.yml -d “publish” ,这里需要确保 es 也是启动的状态,然后 filebeat 会连接到 es,将数据输出。完成后,使用命令 curl -X GET “192.168.66.135:9201/_cat/indices?v” 查看创建的 index 情况:2. 简单的命令操作在 elasticsearch 中,数据是以文档的形式存放的,这也是它能支持全文本搜索的原因,有三个比较关键的概念需要理解一下,首先是 index,这个相当于关系型数据库中的一个数据库。还有 Type ,表示数据的分类,类似于数据库中的一个 table,es 的数据是以 json 格式表示的,每一条数据叫做 document。只不过根据规划,es 6.x 版只允许每个 Index 包含一个 Type,7.x 版将会彻底移除 Type。有了数据之后,接下来介绍一些查询数据的常用命令:1. 数据 1.插入数据:curl -X PUT “192.168.66.135:9200/megacorp/employee/1” -H ‘Content-Type: application/json’ -d’{ “first_name” : “John”, “last_name” : “Smith”, “age” : 25, “about” : “I love to go rock climbing”, “interests”: [ “sports”, “music” ]}‘上面的命令,在 index 为 megacorp,并且 Type 为 employee 下面创建了一条数据,id 是 1,如果不指定 id 的话,es 会自动生成。注意:如果 es 中没有命令中的 index ,它会自动创建,所以,插入数据的时候,需要注意 index 名称的正确性。2.更新数据,还是使用上面的命令,重新 PUT 即可。3.删除数据,使用 DELETE 命令,例如删除上面插入的数据:curl -X DELETE “192.168.66.135:9200/megacorp/employee/1"2. 索引 4.查看 es 中所有的 index 情况:curl -X GET “192.168.66.128:9200/_cat/indices?v"5.删除 index :curl -X DELETE ‘192.168.66.135:9200/megacorp’ ,删除名为 megacorp 的 index。3. 搜索6.查看 es 中的所有数据:http://192.168.66.128:9200/_search ,这条命令还可以加上一些其他的条件,例如:查看 megacorp 索引下的所有数据:http://192.168.66.128:9200/megacorp/_search查看 megacorp1, megacorp2 两个 索引下的所有数据:http://192.168.66.128:9200/megacorp1,megacorp2/_search查看 megacorp 索引下的 employee 类型的数据:http://192.168.66.128:9200/megacorp/employee/_search在所有的索引中搜索 user1 和 user2 类型的数据:http://192.168.66.128:9200/_all/user1,user2/_search7.简单搜索:例如要查找 source 下面 message 这一列的数据中,包含 了某个字符串(这里以 connect 为例)的所有记录:http://192.168.66.135:9201/filebeat-6.5.4-2019.04.06/_search?q=message:connect8.全文搜索,可实现上面这种包含某个字符串的搜索,搜索的文本如下:curl -X GET “192.168.66.135:9200/megacor/search” -H ‘Content-Type: application/json’ -d’{ “query” : { “match” : { “message” : “connect” } }}‘9.搜索分页:从搜索的结果中选取前 50 条记录:http://192.168.66.128:9200/megacorp/_search?size=50从第 3 页中选取 10 条记录:http://192.168.66.128:9200/megacorp/_search?size=10&from=3OK,今天就暂时介绍这么多了,后面再继续写。

April 7, 2019 · 1 min · jiezi

Elasticsearch 学习笔记——1.在 Linux 上安装 elasticsearch

什么是 elasticsearch ?一个偶然的机会,leader 让我看看关于 es 的内容,之前我还未接触过,打开官网一看,发现关于 ealstic 的组件还真是不少,当然 ealsticsearch 应该是其中最流行、最有用和最受欢迎的了,截止到今天(2019.04.05),elasticsearch 在 Github 上面已经有 39k+ star 了。所以,最近在学习它,并且也想写一点学习笔记,分享出来供大家参考。话说回来,到底什么是 elasticsearch ?从名字你也能猜出个大概,它就是一个开源的分布式、可扩展、实时的搜索和数据分析引擎,是基于 Apache Lucence 实现的。好了,简单的了解了 es 之后,接下来进行学习的第一步吧。2. 在 Linux 上安装 elasticsearch首先,说明一下我的版本 : Linux 是 centOS 7, es 的版本是 6.5.4。只要版本差别不是太大,安装的步骤都是大同小异的。1.首先需要安装一下 JDK ,在 Linux 上安装 JDK 的方法就不再赘述了,大家可自行搜索安装。只不过需要注意的是 es 6.5.4 至少需要 JDK 8 极其以上的版本。2.在官网上面下载 elasticsearch 的安装包,选择 linux 系统那个,然后拷贝至 Linux 上面,解压出来。3.因为 elasticsearch 不能使用 root 用户打开,所以需要新建一个用户,然后赋权限,使用命令 :chown 用户名 elasticsearch安装目录 -R4.然后需要修改 es 的配置文件,所有的配置文件都在 es 目录中的 config 下面。使用命令 vim config/elasticsearch.yaml ,绑定 Linux 的 ip ,和端口(一般是 9200):接下来,需要修改几个系统的配置。使用命令 vim /etc/seucrity/limits.conf ,在文件末尾添加如下内容:然后再修改一处配置,使用命令 vim /etc/sysctl.conf,在文件中添加下图中的内容,添加完后执行 sysctl -p 让配置生效。然后切换到创建的非 root 用户,在 es 的目录下面执行 bin/elasticsearch ,这时候 es 就应该启动成功了!3. 查看启动后的效果启动之后,打开浏览器,输入 192.168.66.135:9200/?pretty (这里你需要换成自己的 IP 地址,如果端口没开,记得开放 9200 端口),看到类似下面的内容,说明 elasticsearch 安装启动完成了。

April 6, 2019 · 1 min · jiezi

两年了,我写了这些干货!

开公众号差不多两年了,有不少原创教程,当原创越来越多时,大家搜索起来就很不方便,因此做了一个索引帮助大家快速找到需要的文章!Spring Boot系列SpringBoot+SpringSecurity处理Ajax登录请求SpringBoot+Vue前后端分离(一):使用SpringSecurity完美处理权限问题1SpringBoot+Vue前后端分离(二):使用SpringSecurity完美处理权限问题2SpringBoot+Vue前后端分离(三):SpringSecurity中密码加盐与SpringBoot中异常统一处理SpringBoot+Vue前后端分离(四):axios请求封装和异常统一处理SpringBoot+Vue前后端分离(五):权限管理模块中动态加载Vue组件SpringBoot+Vue前后端分离(六):使用SpringSecurity完美处理权限问题SpringBoot中自定义参数绑定SpringBoot中使用POI,快速实现Excel导入导出SpringBoot中发送QQ邮件SpringBoot中使用Freemarker构建邮件模板SpringBoot+WebSocket实现在线聊天(一)SpringBoot+WebSocket实现在线聊天(二)SpringSecurity登录使用JSON格式数据SpringSecurity登录添加验证码SpringSecurity中的角色继承问题Spring Boot中通过CORS解决跨域问题Spring Boot数据持久化之JdbcTemplateSpring Boot多数据源配置之JdbcTemplate最简单的SpringBoot整合MyBatis教程极简Spring Boot整合MyBatis多数据源Spring Boot中的yaml配置简介SpringBoot整合Swagger2,再也不用维护接口文档了Spring Boot中,Redis缓存还能这么用!干货|一文读懂 Spring Data Jpa!Spring基础配置Spring常用配置Spring常用配置(二)SpringMVC基础配置SpringMVC常用配置JavaWeb之最简洁的配置实现文件上传初识Spring Boot框架DIY一个Spring Boot的自动配置使用Spring Boot开发Web项目为我们的Web添加HTTPS支持在Spring Boot框架下使用WebSocket实现消息推送一个JavaWeb搭建的开源Blog系统,整合SSM框架Spring Cloud系列1.使用Spring Cloud搭建服务注册中心2.使用Spring Cloud搭建高可用服务注册中心3.Spring Cloud中服务的发现与消费4.Eureka中的核心概念5.什么是客户端负载均衡6.Spring RestTemplate中几种常见的请求方式7.RestTemplate的逆袭之路,从发送请求到负载均衡8.Spring Cloud中负载均衡器概览9.Spring Cloud中的负载均衡策略10.Spring Cloud中的断路器Hystrix11.Spring Cloud自定义Hystrix请求命令12.Spring Cloud中Hystrix的服务降级与异常处理13.Spring Cloud中Hystrix的请求缓存14.Spring Cloud中Hystrix的请求合并15.Spring Cloud中Hystrix仪表盘与Turbine集群监控16.Spring Cloud中声明式服务调用Feign17.Spring Cloud中Feign的继承特性18.Spring Cloud中Feign配置详解19.Spring Cloud中的API网关服务Zuul20.Spring Cloud Zuul中路由配置细节21.Spring Cloud Zuul中异常处理细节22.分布式配置中心Spring Cloud Config初窥23.Spring Cloud Config服务端配置细节(一)24.Spring Cloud Config服务端配置细节(二)之加密解密25.Spring Cloud Config客户端配置细节26.Spring Cloud Bus之RabbitMQ初窥27.Spring Cloud Bus整合RabbitMQ28.Spring Cloud Bus整合Kafka29.Spring Cloud Stream初窥30.Spring Cloud Stream使用细节31.Spring Cloud系列勘误Docker系列Docker教程合集MongoDB系列1.Linux上安装MongoDB2.MongoDB基本操作3.MongoDB数据类型4.MongoDB文档更新操作5.MongoDB文档查询操作(一)6.MongoDB文档查询操作(二)7.MongoDB文档查询操作(三)8.MongoDB查看执行计划9.初识MongoDB中的索引10.MongoDB中各种类型的索引11.MongoDB固定集合12.MongoDB管道操作符(一)13.MongoDB管道操作符(二)14.MongoDB中MapReduce使用15.MongoDB副本集搭建16.MongoDB副本集配置17.MongoDB副本集其他细节18.初识MongoDB分片19.Java操作MongoDBRedis系列教程1.Linux上安装Redis2.Redis中的五种数据类型简介3.Redis字符串(STRING)介绍4.Redis字符串(STRING)中BIT相关命令5.Redis列表与集合6.Redis散列与有序集合7.Redis中的发布订阅和事务8.Redis快照持久化9.Redis之AOF持久化10.Redis主从复制(一)11.Redis主从复制(二)12.Redis集群搭建13.Jedis使用14.Spring Data Redis使用Git系列1.Git概述2.Git基本操作3.Git中的各种后悔药4.Git分支管理5.Git关联远程仓库6.Git工作区储藏兼谈分支管理中的一个小问题7.Git标签管理Elasticsearch系列引言elasticsearch安装与配置初识elasticsearch中的REST接口elasticsearch修改数据elasticsearch文档操作elasticsearch API约定(一)elasticsearch API约定(二)elasticsearch文档读写模型elasticsearch文档索引API(一)elasticsearch文档索引API(二)elasticsearch文档Get APIelasticsearch文档Delete APIelasticsearch文档Delete By Query API(一)elasticsearch文档Delete By Query API(二)elasticsearch文档Update API我的Github开源项目开源项目(一): SpringBoot+Vue前后端分离开源项目-微人事开源项目(二): SpringBoot+Vue前后端分离开源项目-V部落开源项目(三): 一个开源的五子棋大战送给各位小伙伴!开源项目(四):一个开源的会议管理系统献给给位小伙伴!开源项目(五):一个JavaWeb搭建的开源Blog系统,整合SSM框架杂谈从高考到程序员之毕业流水帐从高考到现在起早贪黑几个月,我写完了人生第一本书!当公司倒闭时,你在干什么?华为云 open day,带你看看别人家的公司其他小程序开发框架WePY和mpvue使用感受两步解决maven依赖导入失败问题干货|6个牛逼的基于Vue.js的后台控制面板,接私活必备Ajax上传图片以及上传之前先预览一个简单的案例带你入门Dubbo分布式框架WebSocket刨根问底(一)WebSocket刨根问底(二)WebSocket刨根问底(三)之群聊Nginx+Tomcat搭建集群,Spring Session+Redis实现Session共享IntelliJ IDEA中创建Web聚合项目(Maven多模块项目)Linux上安装Zookeeper以及一些注意事项初识ShiroShiro中的授权问题Shiro中的授权问题(二)更多资料,请关注公众号牧码小子,回复 Java, 获取松哥为你精心准备的Java干货! ...

April 3, 2019 · 1 min · jiezi

Spring Boot 整合 elasticsearch

一、简介我们的应用经常需要添加检索功能,开源的 ElasticSearch 是目前全文搜索引擎的 首选。他可以快速的存储、搜索和分析海量数据。Spring Boot通过整合Spring Data ElasticSearch为我们提供了非常便捷的检索功能支持; Elasticsearch是一个分布式搜索服务,提供Restful API,底层基于Lucene,采用 多shard(分片)的方式保证数据安全,并且提供自动resharding的功能,github 等大型的站点也是采用了ElasticSearch作为其搜索服务,二、安装elasticsearch我们采用 docker镜像安装的方式。#下载镜像docker pull elasticsearch#启动镜像,elasticsearch 启动是会默认分配2G的内存 ,我们启动是设置小一点,防止我们内存不够启动失败#9200是elasticsearch 默认的web通信接口,9300是分布式情况下,elasticsearch个节点通信的端口docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9200:9200 -p 9300:9300 –name es01 5c1e1ecfe33a访问 127.0.0.1:9200 如下图,说明安装成功三、elasticsearch的一些概念以 员工文档 的形式存储为例:一个文档代表一个员工数据。存储数据到 ElasticSearch 的行为叫做索引 ,但在索引一个文档之前,需要确定将文档存 储在哪里。一个 ElasticSearch 集群可以 包含多个索引 ,相应的每个索引可以包含多个类型。这些不同的类型存储着多个文档 ,每个文档又有 多个 属性 。类似关系:索引-数据库类型-表文档-表中的记录 – 属性-列elasticsearch使用可以参早官方文档,在这里不在讲解。四、整合 elasticsearch创建项目 springboot-elasticsearch,引入web支持SpringBoot 提供了两种方式操作elasticsearch,Jest 和 SpringData。Jest 操作 elasticsearchJest是ElasticSearch的Java HTTP Rest客户端。ElasticSearch已经有一个Java API,ElasticSearch也在内部使用它,但是Jest填补了空白,它是ElasticSearch Http Rest接口缺少的客户端。1. pom.xml<?xml version=“1.0” encoding=“UTF-8”?><project xmlns=“http://maven.apache.org/POM/4.0.0" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.gf</groupId> <artifactId>springboot-elasticsearch</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>springboot-elasticsearch</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> <!– lookup parent from repository –> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> <dependency> <groupId>io.searchbox</groupId> <artifactId>jest</artifactId> <version>5.3.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>2. application.propertiesspring.elasticsearch.jest.uris=http://127.0.0.1:92003. Articlepackage com.gf.entity;import io.searchbox.annotations.JestId;public class Article { @JestId private Integer id; private String author; private String title; private String content; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } @Override public String toString() { final StringBuilder sb = new StringBuilder( “{"Article":{” ); sb.append( “"id":” ) .append( id ); sb.append( “,"author":"” ) .append( author ).append( ‘"’ ); sb.append( “,"title":"” ) .append( title ).append( ‘"’ ); sb.append( “,"content":"” ) .append( content ).append( ‘"’ ); sb.append( “}}” ); return sb.toString(); }}4. springboot测试类package com.gf;import com.gf.entity.Article;import io.searchbox.client.JestClient;import io.searchbox.core.Index;import io.searchbox.core.Search;import io.searchbox.core.SearchResult;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import java.io.IOException;@RunWith(SpringRunner.class)@SpringBootTestpublic class SpringbootElasticsearchApplicationTests { @Autowired JestClient jestClient; @Test public void createIndex() { //1. 给ES中索引(保存)一个文档 Article article = new Article(); article.setId( 1 ); article.setTitle( “好消息” ); article.setAuthor( “张三” ); article.setContent( “Hello World” ); //2. 构建一个索引 Index index = new Index.Builder( article ).index( “gf” ).type( “news” ).build(); try { //3. 执行 jestClient.execute( index ); } catch (IOException e) { e.printStackTrace(); } } @Test public void search() { //查询表达式 String query = “{\n” + " "query" : {\n” + " "match" : {\n” + " "content" : "hello"\n” + " }\n" + " }\n" + “}”; //构建搜索功能 Search search = new Search.Builder( query ).addIndex( “gf” ).addType( “news” ).build(); try { //执行 SearchResult result = jestClient.execute( search ); System.out.println(result.getJsonString()); } catch (IOException e) { e.printStackTrace(); } }}Jest的更多api ,可以参照github的文档:https://github.com/searchbox-io/JestSpringData 操作 elasticsearch1. application.propertiesspring.data.elasticsearch.cluster-name=elasticsearchspring.data.elasticsearch.cluster-nodes=127.0.0.1:93002. Bookpackage com.gf.entity;@Document( indexName = “gf” , type = “book”)public class Book { private Integer id; private String bookName; private String author; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getBookName() { return bookName; } public void setBookName(String bookName) { this.bookName = bookName; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } @Override public String toString() { final StringBuilder sb = new StringBuilder( “{"Book":{” ); sb.append( “"id":” ) .append( id ); sb.append( “,"bookName":"” ) .append( bookName ).append( ‘"’ ); sb.append( “,"author":"” ) .append( author ).append( ‘"’ ); sb.append( “}}” ); return sb.toString(); } }3. BookRepositorypackage com.gf.repository;import com.gf.entity.Book;import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;import java.util.List;public interface BookRepository extends ElasticsearchRepository<Book, Integer>{ List<Book> findByBookNameLike(String bookName);}4. springboot 测试类package com.gf;import com.gf.entity.Article;import com.gf.entity.Book;import com.gf.repository.BookRepository;import io.searchbox.client.JestClient;import io.searchbox.core.Index;import io.searchbox.core.Search;import io.searchbox.core.SearchResult;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import java.io.IOException;import java.util.List;@RunWith(SpringRunner.class)@SpringBootTestpublic class SpringbootElasticsearchApplicationTests { @Autowired BookRepository bookRepository; @Test public void createIndex2(){ Book book = new Book(); book.setId(1); book.setBookName(“西游记”); book.setAuthor( “吴承恩” ); bookRepository.index( book ); } @Test public void useFind() { List<Book> list = bookRepository.findByBookNameLike( “游” ); for (Book book : list) { System.out.println(book); } }}我们启动测试 ,发现报错 。这个报错的原因是springData的版本与我elasticsearch的版本有冲突,下午是springData官方文档给出的适配表。我们使用的springdata elasticsearch的 版本是3.1.3 ,对应的版本应该是6.2.2版本,而我们是的 elasticsearch 是 5.6.9,所以目前我们需要更换elasticsearch的版本为6.Xdocker pull elasticsearch:6.5.1docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9200:9200 -p 9300:9300 –name es02 镜像ID访问127.0.0.1:9200集群名为docker-cluster,所以我们要修改application.properties的配置了spring.data.elasticsearch.cluster-name=docker-clusterspring.data.elasticsearch.cluster-nodes=127.0.0.1:9300我们再次进行测试,测试可以通过了 。我们访问http://127.0.0.1:9200/gf/book/1,可以得到我们存入的索引信息。源码https://github.com/gf-huanchu…关注我的公众号,精彩内容不能错过~ ...

April 1, 2019 · 3 min · jiezi

400+节点的 Elasticsearch 集群运维

本文首发于InfoQ https://www.infoq.cn/article/… 作者:Anton Hägerstrand翻译:杨振涛目录:数据量版本节点配置索引结构性能Meltwater每天要处理数百万量级的帖子数据,因此需要一种能处理该量级数据的存储和检索技术。从0.11.X 版本开始我们就已经是Elasticsearch的忠实用户了。在经历了一些波折之后,最终我们认为做出了正确的技术选型。Elasticsearch 用于支持我们的主要媒体监控应用,客户通过该应用可以检索和分析媒体数据,比如新闻文章、(公开的)Facebook帖子、Instagram帖子、博客和微博。我们通过使用一个混合API来收集这些内容,并爬取和稍作加工,使得它们可被 Elasticsearch 检索到。本文将分享我们所学到的经验、如何调优 Elasticsearch,以及要绕过的一些陷阱。如果想了解更多关于我们在Elasticsearch方面的点滴,可参考之前博文中的 numad issues 和 batch percolator。1.数据量每天都有数量相当庞大的新闻和微博产生;在高峰期需要索引大约300多万社论文章,和近1亿条社交帖子数据。其中社论数据长期保存以供检索(可回溯到2009年),社交帖子数据保存近15个月的。当前的主分片数据使用了大约200 TB的磁盘空间,副本数据大约600 TB。我们的业务每分钟有3千次请求。所有的请求通过一个叫做 “search-service” 的服务,该服务会依次完成所有与 Elasticsearch 集群的交互。大部分检索规则比较复杂,包括在面板和新闻流中。比如,一个客户可能对 Tesla 和 Elon Musk 感兴趣,但希望排除所有关于 SpaceX 或 PayPal 的信息。用户可以使用一种与 Lucene 查询语法类似的灵活语法,如下:Tesla AND “Elon Musk” NOT (SpaceX OR PayPal)我们最长的此类查询有60多页。重点是:除了每分钟3千次请求以外,没有一个查询是像在 Google 里查询 “Barack Obama” 这么简单的;这简直就是可怕的野兽,但ES节点必须努力找出一个匹配的文档集。2.版本我们运行的是一个基于 Elasticsearch 1.7.6 的定制版本。该版本与1.7.6 主干版本的唯一区别是,我们向后移植(backport)了 roaring bitsets/bitmaps 作为缓存。该功能是从 Lucene 5 移植到 Lucene 4 的,对应移植到了 ES 1.X 版本。Elasticsearch 1.X 中使用默认的 bitset 作为缓存,对于稀疏结果来说开销非常大,不过在 Elasticsearch 2.X 中已经做了优化。为何不使用较新版本的 Elasticsearch 呢?主要原因是升级困难。在主版本间滚动升级只适用于从ES 5到6(从ES 2到5应该也支持滚动升级,但没有试过)。因此,我们只能通过重启整个集群来升级。宕机对我们来说几乎不可接受,但或许可以应对一次重启所带来的大约30-60分钟宕机时间;而真正令人担心的,是一旦发生故障并没有真正的回滚过程。截止目前我们选择了不升级集群。当然我们希望可以升级,但目前有更为紧迫的任务。实际上该如何实施升级尚未有定论,很可能选择创建另一个新的集群,而不是升级现有的。3.节点配置我们自2017年6月开始在AWS上运行主集群,使用i3.2xlarge实例作为数据节点。之前我们在COLO(Co-located Data Center)里运行集群,但后续迁移到了AWS云,以便在新机器宕机时能赢得时间,使得我们在扩容和缩容时更加弹性。我们在不同的可用区运行3个候选 master 节点,并设置 discovery.zen.minimum_master_nodes 为2。这是避免脑裂问题 split-brain problem 非常通用的策略。我们的数据集在存储方面,要求80%容量和3个以上的副本,这使得我们运行了430个数据节点。起初打算使用不同层级的数据,在较慢的磁盘上存储较旧的数据,但是由于我们只有相关的较低量级旧于15个月的数据(只有编辑数据,因为我们丢弃了旧的社交数据),然而这并未奏效。每个月的硬件开销远大于运行在COLO中,但是云服务支持扩容集群到2倍,而几乎不用花费多少时间。你可能会问,为何选择自己管理维护ES集群。其实我们考虑过托管方案,但最后还是选择自己安装,理由是: AWS Elasticsearch Service暴露给用户的可控性太差了,Elastic Cloud 的成本比直接在EC2上运行集群要高2-3倍。为了在某个可用区宕机时保护我们自身,节点分散于eu-west-1的所有3个可用区。我们使用 AWS plugin 来完成该项配置。它提供了一个叫做aws_availability_zone 的节点属性,我们把 cluster.routing.allocation.awareness.attributes 设置为 aws_availability_zone。这保证了ES的副本尽可能地存储在不同的可用区,而查询尽可能被路由到相同可用区的节点。这些实例运行的是 Amazon Linux,临时挂载为 ext4,有约64GB的内存。我们分配了26GB用于ES节点的堆内存,剩下的用于磁盘缓存。为何是26GB?因为 JVM 是在一个黑魔法之上构建的 。我们同时使用 Terraform 自动扩容组来提供实例,并使用 Puppet 完成一切安装配置。4.索引结构因为我们的数据和查询都是基于时间序列的,所以使用了 time-based indexing ,类似于ELK (elasticsearch, logstash, kibana) stack。同时也让不同类型的数据保存在不同的索引库中,以便诸如社论文档和社交文档类数据最终位于不同的每日索引库中。这样可以在需要的时候只丢弃社交索引,并增加一些查询优化。每个日索引运行在两个分片中的一个。该项设置产生了大量的分片(接近40k)。有了这么多的分片和节点,集群操作有时变得更特殊。比如,删除索引似乎成为集群master的能力瓶颈,它需要把集群状态信息推送给所有节点。我们的集群状态数据约100 MB,但通过TCP压缩可减少到3 MB(可以通过 curl localhost:9200/_cluster/state/_all 查看你自己集群的状态数据)。Master 节点仍然需要在每次变更时推送1.3 GB数据(430 节点 x 3 MB 状态大小)。除了这1.3 GB数据外,还有约860 MB必须在可用区(比如 最基本的通过公共互联网)之间传输。这会比较耗时,尤其是在删除数百个索引时。我们希望新版本的 Elasticsearch 能优化这一点,首先从 ES 2.0支持仅发送集群状态的差分数据 这一特性开始。5.性能如前所述,我们的ES集群为了满足客户的检索需求,需要处理一些非常复杂的查询。为应对查询负载,过去几年我们在性能方面做了大量的工作。我们必须尝试公平分享ES集群的性能测试,从下列引文就可以看出。不幸的是,当集群宕机的时候,不到三分之一的查询能成功完成。我们相信测试本身导致了集群宕机。 —— 摘录自使用真实查询在新ES集群平台上的第一次性能测试为了控制查询执行过程,我们开发了一个插件,实现了一系列自定义查询类型。通过使用这些查询类型来提供Elasticsearch官方版本不支持的功能和性能优化。比如,我们实现了 phrases 中的 wildcard 查询,支持在 SpanNear 查询中执行;另一个优化是支持“*”代替 match-all-query ;还有其他一系列特性。Elasticsearch 和 Lucene 的性能高度依赖于具体的查询和数据,没有银弹。即便如此,仍可给出一些从基础到进阶的参考:限制你的检索范围,仅涉及相关数据。比如,对于每日索引库,只按相关日期范围检索。对于检索范围中间的索引,避免使用范围查询/过滤器。使用wildcards时忽略前缀wildcards - 除非你能对term建立倒排索引。双端wildcards难以优化。关注资源消耗的相关迹象 数据节点的CPU占用持续飙高吗?IQ等待走高吗?看看GC统计。这些可以从profilers工具或者通过 JMX 代理获得。如果 ParNewGC 消耗了超过15%的时间,去检查下内存日志。如果有任何的 SerialGC 停顿,你可能真的遇到问题了。不太了解这些内容?没关系,这个系列博文很好地介绍了JVM性能 。记住,ES和G1垃圾回收器一起并非最佳 。如果遇到垃圾回收问题,请不要尝试调整GC设置。这一点经常发生,因为默认设置已经很合理了。相反,应该聚焦在减少内存分配上。具体怎么做?参考下文。如果遇到内存问题,但没有时间解决,可考虑查询Azul Zing。这是一个很贵的产品,但仅仅使用它们的JVM就可以提升2倍的吞吐量。不过最终我们并没有使用它,因为我们无法证明物有所值。考虑使用缓存,包括 Elasticsearch 外缓存和 Lucene 级别的缓存。在 Elasticsearch 1.X 中可以通过使用 filter 来控制缓存。之后的版本中看起来更难一些,但貌似可以实现自己用于缓存的查询类型。我们在未来升级到2.X的时候可能会做类似的工作。查看是否有热点数据(比如某个节点承担了所有的负载)。可以尝试均衡负载,使用分片分配过滤策略 shard allocation filtering ,或者尝试通过集群重新路由 cluster rerouting 来自行迁移分片。我们已经使用线性优化自动重新路由,但使用简单的自动化策略也大有帮助。搭建测试环境(我更喜欢笔记本)可从线上环境加载一部分代表性的数据(建议至少有一个分片)。使用线上的查询回放加压(较难)。使用本地设置来测试请求的资源消耗。综合以上各点,在 Elasticsearch 进程上启用一个 profiler。这是本列表中最重要的一条。我们同时通过Java Mission Control 和 VisualVM 使用飞行记录器。在性能问题上尝试投机(包括付费顾问/技术支持)的人是在浪费他们(以及你自己)的时间。排查下 JVM 哪部分消耗了时间和内存,然后探索下 Elasticsearch/Lucene 源代码,检查是哪部分代码在执行或者分配内存。一旦搞清楚是请求的哪一部分导致了响应变慢,你就可以通过尝试修改请求来优化(比如,修改term聚合的执行提示 ,或者切换查询类型)。修改查询类型或者查询顺序,可以有较大影响。如果不凑效,还可以尝试优化 ES/Lucene 代码。这看起来太夸张,却可以为我们降低3到4倍的CPU消耗和4到8倍的内存使用。某些修改很细微(比如 indices query ),但其他人可能要求我们完全重写查询执行。最终的代码严重依赖于我们的查询模式,所以可能适合也可能不适合他人使用。因此目前为止我们并没有开源这部分代码。不过这可能是下一篇博文的好素材。图表说明:响应时间。有/没有 重写 Lucene 查询执行。同时也表明不再有节点每天多次发生内存不足。顺便说明下,因为我知道会面临一个问题:从上一次性能测试我们知道通过升级到 ES 2.X 能小幅提升性能,但是并不能改变什么。话虽如此,但如果你已经从 ES 1.X 集群迁移到了 ES 2.X,我们很乐意听取关于你如何完成迁移的实践经验。如果读到了这里,说明你对 Elasticsearch 是真爱啊(或者至少你是真的需要它)。我们很乐意学习你的经验,以及任何可以分享的内容。欢迎在评论区分享你的反馈和问题。英文原文链接:http://underthehood.meltwater…更多内容敬请关注vivo互联网技术微信公众号。注:转载文章请先与微信号:labs2020 联系。 ...

March 28, 2019 · 1 min · jiezi

Elasticsearch 索引创建 / 数据查询

es 6.0 开始不推荐一个index下多个type的模式,并且会在 7.0 中完全移除。在 6.0 的index下是无法创建多个type的,type带来的字段类型冲突和检索效率下降的问题,导致了type会被移除。(5.x到6.x)_all字段也被舍弃了,使用 copy_to自定义联合字段。(5.x到6.x)type:text/keyword 来决定是否分词,index: true/false决定是否索引(2.x到5.x)analyzer来单独设定分词器(2.x到5.x)创建索引我们新建一个名news的索引:设定默认分词器为ik分词器用来处理中文使用默认名 _doc 定义 type关闭_source存储(用来验证 store 选项)title 不存储 author 不分词 content 存储PUT /news{ “settings”: { “number_of_shards”: 5, “number_of_replicas”: 1, “index”: { “analysis.analyzer.default.type” : “ik_smart” } }, “mappings”: { “_doc”: { “_source”: { “enabled”: false }, “properties”: { “news_id”: { “type”: “integer”, “index”: true }, “title”: { “type”: “text”, “store”: false }, “author”: { “type”: “keyword” }, “content”: { “type”: “text”, “store”: true }, “created_at”: { “type”: “date”, “format”: “yyyy-MM-dd hh:mm:ss” } } } }}# 查看创建的结构GET /news/_mapping验证分词器是否生效# 验证分词插件是否生效GET /_analyze{ “analyzer”: “ik_smart”, “text”: “我热爱祖国”}GET /_analyze{ “analyzer”: “ik_max_word”, “text”: “我热爱祖国”}# 索引的默认分词器GET /news/_analyze{ “text”: “我热爱祖国!”}# 指定字段 分词器将根据字段属性做相应分词处理# author 为 keyword 是不会做分词处理GET /news/_analyze{ “field”: “author” “text”: “我热爱祖国!”}# title 的分词结果GET /news/_analyze{ “field”: “title” “text”: “我热爱祖国!"}添加文档用于演示,后面的查询会以这些文档为例。POST /news/_doc{ “news_id”: 1, “title”: “我们一起学旺叫”, “author”: “才华横溢王大猫”, “content”: “我们一起学旺叫,一起旺旺旺旺旺,在你面撒个娇,哎呦旺旺旺旺旺,我的尾巴可劲儿摇”, “created_at”: “2019-03-26 11:55:20”}{ “news_id”: 2, “title”: “我们一起学猫叫”, “author”: “王大猫不会被分词”, “content”: “我们一起学猫叫,还是旺旺旺旺旺,在你面撒个娇,哎呦旺旺旺旺旺,我的尾巴可劲儿摇”, “created_at”: “2019-03-26 11:55:20”}{ “news_id”: 3, “title”: “实在编不出来了”, “author”: “王大猫”, “content”: “实在编不出来了,随便写点数据做测试吧,旺旺旺”, “created_at”: “2019-03-26 11:55:20”}检索数据match_all即无检索条件获取全部数据#无条件分页检索 以 news_id 排序GET /news/_doc/_search{ “query”: { “match_all”: {} }, “from”: 0, “size”: 2, “sort”: { “news_id”: “desc” }}因为我们关掉了_source字段,即 ES 只会对数据建立倒排索引,不会存储其原数据,所以结果里没有相关文档原数据内容。关掉的原因主要是想演示highlight机制。match普通检索,很多文章都说match查询会对查询内容进行分词,其实并不完全正确,match查询也要看检索的字段type类型,如果字段类型本身就是不分词的keyword(not_analyzed),那match就等同于term查询了。我们可以通过分词器explain一下字段会被如何处理:GET /news/_analyze{ “filed”: “title”, “text”: “我会被如何处理呢?分词?不分词?"}查询GET /news/_doc/_search{ “query”: { “match”: { “title”: “我们会被分词” } }, “highlight”: { “fields”: { “title”: {} } }}通过highlight我们可以将检索到的关键词以高亮的方式返回上下文内容,如果关闭了_source就得开启字段的store属性存储字段的原数据,这样才能做高亮处理,不然没有原内容了,也就没办法高亮关键词了multi_match对多个字段进行检索,比如我想查询title或content中有我们关键词的文档,如下即可:GET /news/_doc/_search{ “query”: { “multi_match”: { “query”: “我们是好人”, “fields”: [“title”, “content”] } }, “highlight”: { “fields”: { “title”: {}, “content”: {} } }}match_phrase这个需要认证理解一下,match_phrase,短语查询,何为短语查询呢?简单来说即被查询的文档字段中要包含查询内容被分词解析后的所有关键词,且关键词在文档中的分布距离差offset要满足slop设定的阈值。slop表征可以将关键词平移几次来满足在文档中的分布,如果slop足够的大,那么即便所有关键词在文档中分布的很离散,也是可以通过平移满足的。content: i love chinamatch_phrase: i chinaslop: 0//查不到 需要将 i china 的 china 关键词 slop 1 后变为 i - china 才能满足slop: 1//查得到测试实例# 先看下查询会被如何解析分词GET /news/_analyze{ “field”: “title”, “text”: “我们学”}# reponse{ “tokens”: [ { “token”: “我们”, “start_offset”: 0, “end_offset”: 2, “type”: “CN_WORD”, “position”: 0 }, { “token”: “学”, “start_offset”: 2, “end_offset”: 3, “type”: “CN_CHAR”, “position”: 1 } ]}# 再看下某文档的title是被怎样建立倒排索引的GET /news/_analyze{ “field”: “title”, “text”: “我们一起学旺叫”}# reponse{ “tokens”: [ { “token”: “我们”, “start_offset”: 0, “end_offset”: 2, “type”: “CN_WORD”, “position”: 0 }, { “token”: “一起”, “start_offset”: 2, “end_offset”: 4, “type”: “CN_WORD”, “position”: 1 }, { “token”: “学”, “start_offset”: 4, “end_offset”: 5, “type”: “CN_CHAR”, “position”: 2 }, … ]}注意position字段,只有slop的阈值大于两个不相邻的关键词的position差时,才能满足平移关键词至查询内容短语分布的位置条件。查询内容被分词为:[“我们”, “学”],而文档中[“我们”, “学”]两个关键字的距离为 1,所以,slop必须大于等于1,此文档才能被查询到。使用查询短语模式:GET /news/_doc/_search{ “query”: { “match_phrase”: { “title”: { “query”: “我们学”, “slop”: 1 } } }, “highlight”: { “fields”: { “title”: {} } }}查询结果:{ … { “_index”: “news”, “_type”: “_doc”, “_id”: “if-CuGkBddO9SrfVBoil”, “_score”: 0.37229446, “highlight”: { “title”: [ “<em>我们</em>一起<em>学</em>猫叫” ] } }, { “_index”: “news”, “_type”: “_doc”, “_id”: “iP-AuGkBddO9SrfVOIg3”, “_score”: 0.37229446, “highlight”: { “title”: [ “<em>我们</em>一起<em>学</em>旺叫” ] } } …}termterm要理解只是不对查询条件分词,作为一个关键词去检索索引。但文档存储时字段是否被分词建立索引由_mappings时设定了。可能有[“我们”, “一起”]两个索引,但并没有[“我们一起”]这个索引,查询不到。keyword类型的字段则存储时不分词,建立完整索引,查询时也不会对查询条件分词,是强一致性的。GET /news/_doc/_search{ “query”: { “term”: { “title”: “我们一起” } }, “highlight”: { “fields”: { “title”: {} } }}termsterms则是给定多个关键词,就好比人工分词{ “query”: { “terms”: { “title”: [“我们”, “一起”] } }, “highlight”: { “fields”: { “title”: {} } }}满足[“我们”, “一起”]任意关键字的文档都能被检索到。wildcardshell通配符查询: ? 一个字符 * 多个字符,查询倒排索引中符合pattern的关键词。查询有两个字符的关键词的文档{ “query”: { “wildcard”: { “title”: “??” } }, “highlight”: { “fields”: { “title”: {}, “content”: {} } }}prefix前缀查询,查询倒排索引中符合pattern的关键词。{ “query”: { “prefix”: { “title”: “我” } }, “highlight”: { “fields”: { “title”: {}, “content”: {} } }}regexp正则表达式查询,查询倒排索引中符合pattern的关键词。查询含有2 ~ 3 个字符的关键词的文档{ “query”: { “regexp”: { “title”: “.{2,3}” } }, “highlight”: { “fields”: { “title”: {}, “content”: {} } }}bool布尔查询通过 bool链接多个查询组合:must:必须全满足must_not:必须全不满足should:满足一个即可{ “query”: { “bool”: { “must”: { “match”: { “title”: “绝对要有我们” } }, “must_not”: { “term”: { “title”: “绝对不能有我” } }, “should”: [ { “match”: { “content”: “我们” } }, { “multi_match”: { “query”: “满足”, “fields”: [“title”, “content”] } }, { “match_phrase”: { “title”: “一个即可” } } ], “filter”: { “range”: { “created_at”: { “lt”: “2020-12-05 12:00:00”, “gt”: “2019-01-05 12:00:00” } } } } }, “highlight”: { “fields”: { “title”: {}, “content”: {} } }}filterfilter 通常情况下会配合match之类的使用,对符合查询条件的数据进行过滤。{ “query”: { “bool”: { “must”: { “match_all”: {} }, “filter”: { “range”: { “created_at”: { “lt”: “2020-12-05 12:00:00”, “gt”: “2017-12-05 12:00:00” } } } } }}或者单独使用{ “query”: { “constant_score” : { “filter”: { “range”: { “created_at”: { “lt”: “2020-12-05 12:00:00”, “gt”: “2017-12-05 12:00:00” } } } } }}多个过滤条件:2017-12-05 12:00:00 <= created_at < 2020-12-05 12:00:00 and news_id >= 2{ “query”: { “constant_score” : { “filter”: { “bool”: { “must”: [ { “range”: { “created_at”: { “lt”: “2020-12-05 12:00:00”, “gt”: “2017-12-05 12:00:00” } } }, { “range”: { “news_id”: { “gte”: 2 } } } ] } } } }} ...

March 26, 2019 · 4 min · jiezi

关于 Elasticsearch 内存占用及分配

Elasticsearch 和 Lucene 对内存使用情况:Elasticsearch 限制的内存大小是 JAVA 堆空间的大小,不包括Lucene 缓存倒排索引数据空间。Lucene 中的 倒排索引 segments 存储在文件中,为提高访问速度,都会把它加载到内存中,从而提高 Lucene 性能。所以建议至少留系统一半内存给Lucene。Node Query Cache (负责缓存f ilter 查询结果),每个节点有一个,被所有 shard 共享,filter query查询结果要么是 yes 要么是no,不涉及 scores 的计算。集群中每个节点都要配置,默认为:indices.queries.cache.size:10% Indexing Buffer 索引缓冲区,用于存储新索引的文档,当其被填满时,缓冲区中的文档被写入磁盘中的 segments 中。节点上所有 shard 共享。缓冲区默认大小: indices.memory.index_buffer_size: 10%如果缓冲区大小设置了百分百则 indices.memory.min_index_buffer_size 用于这是最小值,默认为 48mb。indices.memory.max_index_buffer_size 用于最大大小,无默认值。Shard Request Cache 用于缓存请求结果,但之缓存request size为0的。比如说 hits.total, aggregations 和 suggestions. 默认最大为indices.requests.cache.size:1%Field Data Cache 字段缓存重要用于对字段进行排序、聚合是使用。因为构建字段数据缓存代价昂贵,所以建议有足够的内训来存储。Fielddata 是 延迟 加载。如果你从来没有聚合一个分析字符串,就不会加载 fielddata 到内存中,也就不会使用大量的内存,所以可以考虑分配较小的heap给Elasticsearch。因为heap越小意味着Elasticsearch的GC会比较快,并且预留给Lucene的内存也会比较大。。如果没有足够的内存保存fielddata时,Elastisearch会不断地从磁盘加载数据到内存,并剔除掉旧的内存数据。剔除操作会造成严重的磁盘I/O,并且引发大量的GC,会严重影响Elastisearch的性能。Elasticsearch默认安装后设置的内存是1GB,这是远远不够用于生产环境的。有两种方式修改Elasticsearch的堆内存:设置环境变量:export ES_HEAP_SIZE=10g 在es启动时会读取该变量;启动时作为参数传递给es: ./bin/elasticsearch -Xmx10g -Xms10g给es分配内存时要注意,至少要分配一半儿内存留给 Lucene。分配给 es 的内存最好不要超过 32G ,因为如果堆大小小于 32 GB,JVM 可以利用指针压缩,这可以大大降低内存的使用:每个指针 4 字节而不是 8 字节。如果大于32G 每个指针占用 8字节,并且会占用更多的内存带宽,降低了cpu性能。还有一点, 要关闭 swap 内存交换空间,禁用swapping。频繁的swapping 对服务器来说是致命的。总结:给es JVM栈的内存最好不要超过32G,留给Lucene的内存越大越好,Lucene把所有的segment都缓存起来,会加快全文检索。参考文档:https://nereuschen.github.io/…https://www.elastic.co/guide/… ...

March 18, 2019 · 1 min · jiezi

Elasticsearch 导入 kibana 的样例数据

下载数据下载地址2. 根据官方页面说明建立相关索引3. 将数据导入Elasticsearch必须在文件目录下执行导入命令,windows下需要将单引号替换为双引号curl -H “Content-Type: application/json” -XPOST “localhost:9200/bank/account/_bulk?pretty&refresh” –data-binary “@accounts.json"curl -H “Content-Type: application/x-ndjson” -XPOST “localhost:9200/_bulk?pretty” –data-binary @logs.jsonlcurl -H “Content-Type: application/x-ndjson” -XPOST “localhost:9200/shakespeare/doc/_bulk?pretty” –data-binary @shakespeare_6.0.jsonWindows的cmd没有curl功能,我是在 cmder 中执行的

March 17, 2019 · 1 min · jiezi

Elasticsearch最佳实践之分片使用优化

本文由云+社区发表作者:老生姜一、遇到的问题 与大多数分布式系统一样,Elasticsearch按照一定的Hash规则把用户数据切分成多个分片,然后打散到不同机器进行存储,从而实现大规模数据的分布式存储。cluster.png 然而在一些复杂的应用场景中使用Elasticsearch,经常会遇到分片过多引发的一系列问题。起初我们在支撑内部某业务时,单集群内有约1000个子业务,大部分子业务保留31天的数据。如果每个子业务按天滚动建立Index,每个Index 5个分片、一主两从共三副本的情况下,集群内部会有多达45w个分片。在集群内分片过多时,经常遇到下面这些问题: 1. 创建分片慢:Elasticsearch创建分片的速度会随着集群内分片数的增加而变慢。以ES 5.5.2版本、3节点集群为例,在默认配置下,当集群分片数超过1w时,创建index的耗时一般在几十秒甚至以上。 2. 集群易崩溃:在凌晨触发Elasticsearch自动创建Index时,由于创建速度太慢,容易导致大量写入请求堆积在内存,从而压垮集群。 3. 写入拒绝:分片过多的场景中,如果不能及时掌控业务变化,可能经常遇到单分片记录超限、写入拒绝等问题。二、解决过程拆分集群 对于存在明显分界线的业务,可以按照业务、地域使用不同集群,这种拆分集群的思路是非常靠谱的。Elasticsearch官方建议使用小而美的集群,避免巨无霸式的集群,我们在实际使用过程中对这一点也深有体会。但对于我们的场景,已经按照地域拆分了集群,且同一地域的子业务间分界线不明显,拆分过多的集群维护成本较高。调整滚动周期 根据保留时长调整index滚动周期是最简单有效的思路。例如保留3天的数据按天滚动,保留31天的数据按周滚动,保留一年的数据按月滚动。合理的滚动周期,可以在存储成本增加不大的情况下,大幅降低分片数量。 对于我们的场景,大部分数据保留31天,在按周滚动的情况下,集群的总分片数可以下降到6.5w个。合理设置分片数和副本数 集群内部除个别子业务压力较高外,大部分业务压力较小,合理设置单Index的分片数效果也不错。我们的经验是单个分片的大小在10GB30GB之间比较合适,对于压力非常小的业务可以直接分配1个分片。其他用户可结合具体场景考虑,同时注意单分片的记录条数不要超过上限2,147,483,519。 在平衡我们的业务场景对数据可靠性的要求 及 不同副本数对存储成本的开销 两个因素之后,我们选择使用一主一从的副本策略。 目前我们集群单Index的平均分配数为3,集群的总分片数下降到3w个。分片分配流程优化 默认情况下,ES在分配分片时会考虑分片relocation对磁盘空间的影响。在分片数较少时,这个优化处理的副作用不明显。但随着单机分片数量的上升,这个优化处理涉及的多层循环嵌套过程耗时愈发明显。可通过cluster.routing.allocation.disk.include_relocations: false关闭此功能,这对磁盘均衡程度影响不明显。预创建Index 对于单集群3w分片的场景,集中在每周某天0点创建Index,对集群的压力还是较大,且存储空间存在波动。考虑到集群的持续扩展能力和可靠性,我们采用预创建方式提前创建分片,并把按Index的创建时间均匀打散到每周的每一天。持续调整分片数 对于集群分片的调整,通常不是一蹴而就的。随着业务的发展,不断新增的子业务 或 原有子业务规模发生突变,都需要持续调整分片数量。 默认情况下,新增的子业务会有默认的分片数量,如果不足,会在测试阶段及上线初期及时发现。随着业务发展,系统会考虑Index近期的数据量、写入速度、集群规模等因素,动态调整分片数量。三、后续 目前,Elasticsearch的分片均衡策略尚有瑕疵,例如:1. 机器的空间利用不是非常均衡,对于此类场景,用户可暂时通过调整机器空间的高低水位线配置触发数据均衡;2. 当集群扩容新节点时,Elasticsearch会把大量新建分片分配到新机器,导致新机器压力过高,目前用户可临时通过index.routing.allocation.total_shards_per_node配置进行限制。 这是我们后续在分片使用方面的优化工作,通过直接优化分片均衡策略,更优雅的解决上述问题。如果大家有分片使用方面的问题 或 经验,欢迎一起交流讨论!此文已由腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号

March 15, 2019 · 1 min · jiezi

Elasticsearch入门篇——基础知识

还记得大二的时候,初入Java大门,就大言不惭的给老师说,我要开发一个搜索引擎,结果是各种学习,各种找资料,终于在期末的时候,做出了一个简单新闻搜索页面,搜索模块是使用了Lucene。今天,我们一起走进Elasticsearch的殿堂。Elastic以Elastic之名进行交易的数据搜索软件初创公司Elastic search于2018年10月5日(美国时间)上市。Elastic Search 只是 Elastic 公司最出名的产品之一,其中还包括有分布式日志解决方案 ELK(Elastic Search、Logstash、Kibana)、Beats、ECE等。Elasticsearch官网:https://www.elastic.co/cn/pro…Elasticsearch is a distributed, RESTful search and analytics engine capable of solving a growing number of use cases. As the heart of the Elastic Stack, it centrally stores your data so you can discover the expected and uncover the unexpected.翻译:Elasticsearch 是一个分布式的基于 RESTful 接口的搜索和分析引擎,它能够解决越来越多的使用场景。作为 Elastic Stack 的核心,它集中存储数据,可以发现预期及之外的结果。Elastic Stack 的核心Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎,能够解决不断涌现出的各种用例。作为 Elastic Stack 的核心,它集中存储您的数据,帮助您发现意料之中以及意料之外的情况。Elastic Stack 的特点:查询 保持好奇心。从数据中探寻各种问题的答案。通过 Elasticsearch,您能够执行及合并多种类型的搜索(结构化数据、非结构化数据、地理位置、指标),搜索方式随心而变。先从一个简单的问题出发,试试看能够从中发现些什么。分析 大处着眼,全局在握。找到与查询最匹配的十个文档是一回事。但如果面对的是十亿行日志,又该如何解读呢?Elasticsearch 聚合让您能够从大处着眼,探索数据的趋势和模式。速度 Elasticsearch 很快。 快到不可思议。如果您能够立即获得答案,您与数据的关系就会发生变化。这样您就有条件进行迭代并涵盖更大的范围。但是要达到这样的速度并非易事。我们通过有限状态转换器实现了用于全文检索的倒排索引,实现了用于存储数值数据和地理位置数据的 BKD 树,以及用于分析的列存储。而且由于每个数据都被编入了索引,因此您再也不用因为某些数据没有索引而烦心。您可以用快到令人惊叹的速度使用和访问您的所有数据。可扩展性 可以在笔记本电脑上运行。 也可以在承载了 PB 级数据的成百上千台服务器上运行。原型环境和生产环境可无缝切换;无论 Elasticsearch 是在一个节点上运行,还是在一个包含 300 个节点的集群上运行,您都能够以相同的方式与 Elasticsearch 进行通信。它能够水平扩展,每秒钟可处理海量事件,同时能够自动管理索引和查询在集群中的分布方式,以实现极其流畅的操作。弹性 我们在您高飞的时候保驾护航。硬件故障。网络分割。Elasticsearch 为您检测这些故障并确保您的集群(和数据)的安全性和可用性。通过跨集群复制功能,辅助集群可以作为热备份随时投入使用。Elasticsearch 运行在一个分布式的环境中,从设计之初就考虑到了这一点,目的只有一个,让您永远高枕无忧。灵活性 具备多个案例场景?一个全有。数字、文本、地理位置、结构化数据、非结构化数据。欢迎使用所有数据类型。应用搜索、安全分析、指标或日志分析只是全球众多公司利用 Elasticsearch 解决各种挑战的冰山一角。操作的乐趣 享受更多成功的时刻,告别垂头丧气的失落简单的事情就该简单做。我们确保 Elasticsearch 在任何规模下都能够易于操作,而无需在功能和性能方面做出牺牲。客户端库 使用您自己的编程语言与 Elasticsearch 进行交互Elasticsearch 使用的是标准的 RESTful 风格的 API 和 JSON。此外,我们还构建和维护了很多其他语言的客户端,例如 Java、Python、.NET、SQL 和 PHP。与此同时,我们的社区也贡献了很多客户端。这些客户端使用起来简单自然,而且就像 Elasticsearch 一样,不会对您的使用方式进行限制。尽享强大功能 延展 Elasticsearch为您的集群添加用户名和密码,监控 Elasticsearch 的性能表现,通过运行 Machine Learning 任务来发现异常等等,这些特性尽在 Elastic Stack 内置的多项功能。通过 Security、Monitoring、Alerting、Reporting、Graph 关联分析和 Machine Learning 等功能,获得更优的使用体验。HADOOP 和 SPARK Elasticsearch 加 HadoopHadoop 中有大量数据?您可以使用 Elasticsearch-Hadoop (ES-Hadoop)连接器,利用 Elasticsearch 的实时搜索和分析功能处理您的大数据。这是两大领域最大优势的融合。基础概念我可以这样说,学习完这些概念,你或许就能明白RESTful的含义了,所以,学习这些概念是很有必要的。Near Realtime (NRT)Elasticsearch is a near-realtime search platform. What this means is there is a slight latency (normally one second) from the time you index a document until the time it becomes searchable.集群(Cluster)A cluster is a collection of one or more nodes (servers) that together holds your entire data and provides federated indexing and search capabilities across all nodes. A cluster is identified by a unique name which by default is “elasticsearch”. This name is important because a node can only be part of a cluster if the node is set up to join the cluster by its name.Make sure that you don’t reuse the same cluster names in different environments, otherwise you might end up with nodes joining the wrong cluster. For instance you could use logging-dev, logging-stage, and logging-prod for the development, staging, and production clusters.Note that it is valid and perfectly fine to have a cluster with only a single node in it. Furthermore, you may also have multiple independent clusters each with its own unique cluster name.节点(Node)A node is a single server that is part of your cluster, stores your data, and participates in the cluster’s indexing and search capabilities. Just like a cluster, a node is identified by a name which by default is a random Universally Unique IDentifier (UUID) that is assigned to the node at startup. You can define any node name you want if you do not want the default. This name is important for administration purposes where you want to identify which servers in your network correspond to which nodes in your Elasticsearch cluster.A node can be configured to join a specific cluster by the cluster name. By default, each node is set up to join a cluster named elasticsearch which means that if you start up a number of nodes on your network and—assuming they can discover each other—they will all automatically form and join a single cluster named elasticsearch.In a single cluster, you can have as many nodes as you want. Furthermore, if there are no other Elasticsearch nodes currently running on your network, starting a single node will by default form a new single-node cluster named elasticsearch.索引(Index)An index is a collection of documents that have somewhat similar characteristics. For example, you can have an index for customer data, another index for a product catalog, and yet another index for order data. An index is identified by a name (that must be all lowercase) and this name is used to refer to the index when performing indexing, search, update, and delete operations against the documents in it.In a single cluster, you can define as many indexes as you want.类型(Type)A type used to be a logical category/partition of your index to allow you to store different types of documents in the same index, e.g. one type for users, another type for blog posts. It is no longer possible to create multiple types in an index, and the whole concept of types will be removed in a later version. See Removal of mapping types for more.文档(Document)A document is a basic unit of information that can be indexed. For example, you can have a document for a single customer, another document for a single product, and yet another for a single order. This document is expressed in JSON (JavaScript Object Notation) which is a ubiquitous internet data interchange format.Within an index/type, you can store as many documents as you want. Note that although a document physically resides in an index, a document actually must be indexed/assigned to a type inside an index.Shards & ReplicasAn index can potentially store a large amount of data that can exceed the hardware limits of a single node. For example, a single index of a billion documents taking up 1TB of disk space may not fit on the disk of a single node or may be too slow to serve search requests from a single node alone.To solve this problem, Elasticsearch provides the ability to subdivide your index into multiple pieces called shards. When you create an index, you can simply define the number of shards that you want. Each shard is in itself a fully-functional and independent “index” that can be hosted on any node in the cluster.Sharding is important for two primary reasons:It allows you to horizontally split/scale your content volumeIt allows you to distribute and parallelize operations across shards (potentially on multiple nodes) thus increasing performance/throughputThe mechanics of how a shard is distributed and also how its documents are aggregated back into search requests are completely managed by Elasticsearch and is transparent to you as the user.In a network/cloud environment where failures can be expected anytime, it is very useful and highly recommended to have a failover mechanism in case a shard/node somehow goes offline or disappears for whatever reason. To this end, Elasticsearch allows you to make one or more copies of your index’s shards into what are called replica shards, or replicas for short.Replication is important for two primary reasons:It provides high availability in case a shard/node fails. For this reason, it is important to note that a replica shard is never allocated on the same node as the original/primary shard that it was copied from.It allows you to scale out your search volume/throughput since searches can be executed on all replicas in parallel.To summarize, each index can be split into multiple shards. An index can also be replicated zero (meaning no replicas) or more times. Once replicated, each index will have primary shards (the original shards that were replicated from) and replica shards (the copies of the primary shards).The number of shards and replicas can be defined per index at the time the index is created. After the index is created, you may also change the number of replicas dynamically anytime. You can change the number of shards for an existing index using the _shrink and _split APIs, however this is not a trivial task and pre-planning for the correct number of shards is the optimal approach.By default, each index in Elasticsearch is allocated 5 primary shards and 1 replica which means that if you have at least two nodes in your cluster, your index will have 5 primary shards and another 5 replica shards (1 complete replica) for a total of 10 shards per index.结构上图来自SpringBoot整合ElasticSearch及源码安装配置:cluster.name: es-wyfnode.name: masterpath.data: /Users/wenyifeng/Software/elasticsearch/data/master/datapath.logs: /Users/wenyifeng/Software/elasticsearch/data/master/logsnetwork.host: 127.0.0.1http.port: 9200discovery.zen.ping.unicast.hosts: [“127.0.0.1”]http.cors.enabled: truehttp.cors.allow-origin: “*“bootstrap.memory_lock: falsebootstrap.system_call_filter: false说起配置,我是很头疼的,看过视频教程和很多博文,都失败,最后在老大的帮助下,成功了,给了我如上配置。感谢我们老大对我的帮助。Mac 安装#下载并解压,进入目录,后台运行./bin/elasticsearch -ddocker安装Pulling the imageObtaining Elasticsearch for Docker is as simple as issuing a docker pull command against the Elastic Docker registry.docker pull docker.elastic.co/elasticsearch/elasticsearch:6.6.1Alternatively, you can download other Docker images that contain only features available under the Apache 2.0 license. To download the images, go to www.docker.elastic.co.Running Elasticsearch from the command lineDevelopment modeeditElasticsearch can be quickly started for development or testing use with the following command:docker run -p 9200:9200 -p 9300:9300 -e “discovery.type=single-node” docker.elastic.co/elasticsearch/elasticsearch:6.6.1官网教程:https://www.elastic.co/guide/…CentOS安装这个问题,问下运维的同学吧。ElasticSearch Head服务器安装地址:https://github.com/mobz/elast…从git上下下来,解压并进入,运行如下命令:npm installnpm run start浏览器插件搜索 ElasticSearch Head创建索引假设我们创建一个学校,有一个初2一班,学生的属性有:学号、姓名、年龄。PUT http://localhost:9200/school{ “mappings”:{ “c2_1”: { “properties”:{ “no”:{ “type”:“keyword” }, “name”:{ “type”:“text” }, “age”:{ “type”:“integer” } } } }}返回:{ “acknowledged”: true, “shards_acknowledged”: true, “index”: “school”}这样就表示索引创建成功。基本操作CRUD增加我们向里面插入一条数据,例如学号201901,姓名张三,年龄20。POST http://localhost:9200/school/c2_1{ “no”:“201901”, “name”:“张三”, “age”:20}返回:{ “_index”: “school”, “_type”: “c2_1”, “_id”: “8ygbK2kBenMJLC7I-EaK”, “_version”: 1, “result”: “created”, “_shards”: { “total”: 2, “successful”: 2, “failed”: 0 }, “_seq_no”: 0, “_primary_term”: 1}查询我们通过返回的id查询一下:GET http://localhost:9200/school/c2_1/8ygbK2kBenMJLC7I-EaK返回:{ “_index”: “school”, “_type”: “c2_1”, “_id”: “8ygbK2kBenMJLC7I-EaK”, “_version”: 1, “_seq_no”: 0, “_primary_term”: 1, “found”: true, “_source”: { “no”: “201901”, “name”: “张三”, “age”: 20 }}查询出来了,这说明我们的增加和查询操作都成功了。修改我们把张三的年龄修改为22.PUT http://localhost:9200/school/c2_1/8ygbK2kBenMJLC7I-EaK{ “no”:“201901”, “name”:“张三”, “age”:22}返回:{ “_index”: “school”, “_type”: “c2_1”, “_id”: “8ygbK2kBenMJLC7I-EaK”, “_version”: 2, “result”: “updated”, “_shards”: { “total”: 2, “successful”: 2, “failed”: 0 }, “_seq_no”: 1, “_primary_term”: 1}查询一下:与预期一致。删除我们根据ID删除数据DELETE http://localhost:9200/school/c2_1/8ygbK2kBenMJLC7I-EaK返回:{ “_index”: “school”, “_type”: “c2_1”, “_id”: “8ygbK2kBenMJLC7I-EaK”, “_version”: 3, “result”: “deleted”, “_shards”: { “total”: 2, “successful”: 2, “failed”: 0 }, “_seq_no”: 2, “_primary_term”: 1}再查询就没有,如下返回:{ “_index”: “school”, “_type”: “c2_1”, “_id”: “8ygbK2kBenMJLC7I-EaK”, “found”: false}删除索引删除索引之后,索引下面的索引文档都将被删除。DELETE http://localhost:9200/school返回:{ “acknowledged”: true}高级查询接口通用查询API接口:http://localhost:9200/book/_search提交方式,可以是GET,也可以是POST(JSON)。构造数据我们首先构造数据,如下Query ContextQuery Context:在查询过程中,除了判断文档是否满足查询条件外,Elasticsearch还会计算一个 _score 来标识匹配的程度,旨在判断目标文档和查询条件匹配的 有多好。简单来说就是,匹配到了吗?有多吻合呢?常用查询全文本搜索:针对文本类型的数据字段级别查询:针对结构化数据,如数字、日期等全文本匹配搜索:{ “query”:{ “match”:{ “author”:“明日科技” } }}结果:{ “took”: 4, “timed_out”: false, “_shards”: { “total”: 5, “successful”: 5, “skipped”: 0, “failed”: 0 }, “hits”: { “total”: 1, “max_score”: 0.9808292, “hits”: [ { “_index”: “book”, “_type”: “it”, “_id”: “EdWwL2kBvvhXwNB9h50h”, “_score”: 0.9808292, “_source”: { “title”: “Java从入门到精通(第4版)(附光盘)”, “author”: “明日科技”, “word_count”: 10002, “publish_date”: “2016-09-01” } } ] }}这是模糊匹配,先进行分词,然后会把相关的都会查询出来,如下:搜索:{ “query”:{ “match”:{ “title”:“elasticsearch入门” } }}结果:{ “took”: 4, “timed_out”: false, “_shards”: { “total”: 5, “successful”: 5, “skipped”: 0, “failed”: 0 }, “hits”: { “total”: 4, “max_score”: 0.3252806, “hits”: [ { “_index”: “book”, “_type”: “it”, “_id”: “FNW0L2kBvvhXwNB9pJ0W”, “_score”: 0.3252806, “_source”: { “title”: “Python编程 从入门到实践”, “author”: “[美]埃里克·马瑟斯(Eric Matthes)”, “word_count”: 10010, “publish_date”: “2016-07-10” } }, { “_index”: “book”, “_type”: “it”, “_id”: “EdWwL2kBvvhXwNB9h50h”, “_score”: 0.28924954, “_source”: { “title”: “Java从入门到精通(第4版)(附光盘)”, “author”: “明日科技”, “word_count”: 10002, “publish_date”: “2016-09-01” } }, { “_index”: “book”, “_type”: “it”, “_id”: “EtWxL2kBvvhXwNB93p1F”, “_score”: 0.2876821, “_source”: { “title”: “Elasticsearch实战”, “author”: “[美] 拉杜·乔戈(Radu Gheorghe) 马修·李·欣曼(Matthew”, “word_count”: 10005, “publish_date”: “2018-10-2” } }, { “_index”: “book”, “_type”: “it”, “_id”: “E9WzL2kBvvhXwNB9MZ3I”, “_score”: 0.21268348, “_source”: { “title”: “Laravel入门与实战 构建主流PHP应用开发框架 Laravel开发框架教程书籍 “, “author”: “拉杜 乔戈”, “word_count”: 10007, “publish_date”: “2017-11-03” } } ] }}这里将 elasticsearch入门 分解为 elasticsearch 和 入门。match_phrase如果我们并不想那样进行分割,那我们换一个关键字 match_phrase搜索:{ “query”:{ “match_phrase”:{ “title”:“elasticsearch入门” } }}结果:{ “took”: 3, “timed_out”: false, “_shards”: { “total”: 5, “successful”: 5, “skipped”: 0, “failed”: 0 }, “hits”: { “total”: 0, “max_score”: null, “hits”: [] }}就什么也没有了。我们再搜索:{ “query”:{ “match_phrase”:{ “title”:“elasticsearch” } }}结果:{ “took”: 3, “timed_out”: false, “_shards”: { “total”: 5, “successful”: 5, “skipped”: 0, “failed”: 0 }, “hits”: { “total”: 1, “max_score”: 0.2876821, “hits”: [ { “_index”: “book”, “_type”: “it”, “_id”: “EtWxL2kBvvhXwNB93p1F”, “_score”: 0.2876821, “_source”: { “title”: “Elasticsearch实战”, “author”: “[美] 拉杜·乔戈(Radu Gheorghe) 马修·李·欣曼(Matthew”, “word_count”: 10005, “publish_date”: “2018-10-2” } } ] }}我们再换关键字:{ “query”:{ “match_phrase”:{ “title”:“入门” } }}结果:{ “took”: 11, “timed_out”: false, “_shards”: { “total”: 5, “successful”: 5, “skipped”: 0, “failed”: 0 }, “hits”: { “total”: 3, “max_score”: 0.3252806, “hits”: [ { “_index”: “book”, “_type”: “it”, “_id”: “FNW0L2kBvvhXwNB9pJ0W”, “_score”: 0.3252806, “_source”: { “title”: “Python编程 从入门到实践”, “author”: “[美]埃里克·马瑟斯(Eric Matthes)”, “word_count”: 10010, “publish_date”: “2016-07-10” } }, { “_index”: “book”, “_type”: “it”, “_id”: “EdWwL2kBvvhXwNB9h50h”, “_score”: 0.28924954, “_source”: { “title”: “Java从入门到精通(第4版)(附光盘)”, “author”: “明日科技”, “word_count”: 10002, “publish_date”: “2016-09-01” } }, { “_index”: “book”, “_type”: “it”, “_id”: “E9WzL2kBvvhXwNB9MZ3I”, “_score”: 0.21268348, “_source”: { “title”: “Laravel入门与实战 构建主流PHP应用开发框架 Laravel开发框架教程书籍 “, “author”: “拉杜 乔戈”, “word_count”: 10007, “publish_date”: “2017-11-03” } } ] }}多个字段查询搜索:{ “query”:{ “multi_match”:{ “query”:“java”, “fields”:[“title”, “author”] } }}结果:{ “took”: 2, “timed_out”: false, “_shards”: { “total”: 5, “successful”: 5, “skipped”: 0, “failed”: 0 }, “hits”: { “total”: 1, “max_score”: 1.0623134, “hits”: [ { “_index”: “book”, “_type”: “it”, “_id”: “EdWwL2kBvvhXwNB9h50h”, “_score”: 1.0623134, “_source”: { “title”: “Java从入门到精通(第4版)(附光盘)”, “author”: “明日科技”, “word_count”: 10002, “publish_date”: “2016-09-01” } } ] }}语法查询搜索:{ “query”:{ “query_string”:{ “query”:“java” } }}结果:{ “took”: 6, “timed_out”: false, “_shards”: { “total”: 5, “successful”: 5, “skipped”: 0, “failed”: 0 }, “hits”: { “total”: 1, “max_score”: 1.0623134, “hits”: [ { “_index”: “book”, “_type”: “it”, “_id”: “EdWwL2kBvvhXwNB9h50h”, “_score”: 1.0623134, “_source”: { “title”: “Java从入门到精通(第4版)(附光盘)”, “author”: “明日科技”, “word_count”: 10002, “publish_date”: “2016-09-01” } } ] }}搜索:{ “query”:{ “query_string”:{ “query”:“java and 入门” } }}结果:{ “took”: 3, “timed_out”: false, “_shards”: { “total”: 5, “successful”: 5, “skipped”: 0, “failed”: 0 }, “hits”: { “total”: 3, “max_score”: 1.351563, “hits”: [ { “_index”: “book”, “_type”: “it”, “_id”: “EdWwL2kBvvhXwNB9h50h”, “_score”: 1.351563, “_source”: { “title”: “Java从入门到精通(第4版)(附光盘)”, “author”: “明日科技”, “word_count”: 10002, “publish_date”: “2016-09-01” } }, { “_index”: “book”, “_type”: “it”, “_id”: “FNW0L2kBvvhXwNB9pJ0W”, “_score”: 0.3252806, “_source”: { “title”: “Python编程 从入门到实践”, “author”: “[美]埃里克·马瑟斯(Eric Matthes)”, “word_count”: 10010, “publish_date”: “2016-07-10” } }, { “_index”: “book”, “_type”: “it”, “_id”: “E9WzL2kBvvhXwNB9MZ3I”, “_score”: 0.21268348, “_source”: { “title”: “Laravel入门与实战 构建主流PHP应用开发框架 Laravel开发框架教程书籍 “, “author”: “拉杜 乔戈”, “word_count”: 10007, “publish_date”: “2017-11-03” } } ] }}这个给我的个人感觉不好玩,就小小的尝试一下吧。字段级别的查询搜索:{ “query”:{ “term”:{ “word_count”:“100” } }}结果:{ “took”: 14, “timed_out”: false, “_shards”: { “total”: 5, “successful”: 5, “skipped”: 0, “failed”: 0 }, “hits”: { “total”: 1, “max_score”: 1, “hits”: [ { “_index”: “book”, “_type”: “it”, “_id”: “FdW1L2kBvvhXwNB91Z2M”, “_score”: 1, “_source”: { “title”: “数据结构(C语言版)”, “author”: “严蔚敏”, “word_count”: 100, “publish_date”: “2007-03-09” } } ] }}term:用于查询特定值范围查询例如,我们搜索大于10000个字的书:{ “query”:{ “range”:{ “word_count”:{ “gte”:10000 } } }}结果:{ “took”: 5, “timed_out”: false, “_shards”: { “total”: 5, “successful”: 5, “skipped”: 0, “failed”: 0 }, “hits”: { “total”: 4, “max_score”: 1, “hits”: [ { “_index”: “book”, “_type”: “it”, “_id”: “EtWxL2kBvvhXwNB93p1F”, “_score”: 1, “_source”: { “title”: “Elasticsearch实战”, “author”: “[美] 拉杜·乔戈(Radu Gheorghe) 马修·李·欣曼(Matthew”, “word_count”: 10005, “publish_date”: “2018-10-2” } }, { “_index”: “book”, “_type”: “it”, “_id”: “EdWwL2kBvvhXwNB9h50h”, “_score”: 1, “_source”: { “title”: “Java从入门到精通(第4版)(附光盘)”, “author”: “明日科技”, “word_count”: 10002, “publish_date”: “2016-09-01” } }, { “_index”: “book”, “_type”: “it”, “_id”: “E9WzL2kBvvhXwNB9MZ3I”, “_score”: 1, “_source”: { “title”: “Laravel入门与实战 构建主流PHP应用开发框架 Laravel开发框架教程书籍 “, “author”: “拉杜 乔戈”, “word_count”: 10007, “publish_date”: “2017-11-03” } }, { “_index”: “book”, “_type”: “it”, “_id”: “FNW0L2kBvvhXwNB9pJ0W”, “_score”: 1, “_source”: { “title”: “Python编程 从入门到实践”, “author”: “[美]埃里克·马瑟斯(Eric Matthes)”, “word_count”: 10010, “publish_date”: “2016-07-10” } } ] }}时间也可以搜索范围,比如我们搜索 (2016-09-01, 2018-01-01) 这个时间段之间要出版书:{ “query”:{ “range”:{ “publish_date”:{ “gt”:“2016-09-01”, “lt”:“2018-01-01” } } }}结果:{ “took”: 2, “timed_out”: false, “_shards”: { “total”: 5, “successful”: 5, “skipped”: 0, “failed”: 0 }, “hits”: { “total”: 1, “max_score”: 1, “hits”: [ { “_index”: “book”, “_type”: “it”, “_id”: “E9WzL2kBvvhXwNB9MZ3I”, “_score”: 1, “_source”: { “title”: “Laravel入门与实战 构建主流PHP应用开发框架 Laravel开发框架教程书籍 “, “author”: “拉杜 乔戈”, “word_count”: 10007, “publish_date”: “2017-11-03” } } ] }}取等吧,[2016-09-01, 2018-01-01) :{ “query”:{ “range”:{ “publish_date”:{ “gte”:“2016-09-01”, “lt”:“2018-01-01” } } }}结果:{ “took”: 2, “timed_out”: false, “_shards”: { “total”: 5, “successful”: 5, “skipped”: 0, “failed”: 0 }, “hits”: { “total”: 2, “max_score”: 1, “hits”: [ { “_index”: “book”, “_type”: “it”, “_id”: “EdWwL2kBvvhXwNB9h50h”, “_score”: 1, “_source”: { “title”: “Java从入门到精通(第4版)(附光盘)”, “author”: “明日科技”, “word_count”: 10002, “publish_date”: “2016-09-01” } }, { “_index”: “book”, “_type”: “it”, “_id”: “E9WzL2kBvvhXwNB9MZ3I”, “_score”: 1, “_source”: { “title”: “Laravel入门与实战 构建主流PHP应用开发框架 Laravel开发框架教程书籍 “, “author”: “拉杜 乔戈”, “word_count”: 10007, “publish_date”: “2017-11-03” } } ] }}当前日期,可用关键字 now关键字:filter在查询过程中,只判断文档是否满足条件,只有Yes或者No。举个例子,我们要搜索字数是100的有哪些,搜索如下:{ “query”:{ “bool”:{ “filter”:{ “term”:{ “word_count”:100 } } } }}结果:{ “took”: 40, “timed_out”: false, “_shards”: { “total”: 5, “successful”: 5, “skipped”: 0, “failed”: 0 }, “hits”: { “total”: 1, “max_score”: 0, “hits”: [ { “_index”: “book”, “_type”: “it”, “_id”: “FdW1L2kBvvhXwNB91Z2M”, “_score”: 0, “_source”: { “title”: “数据结构(C语言版)”, “author”: “严蔚敏”, “word_count”: 100, “publish_date”: “2007-03-09” } } ] }}复杂查询固定分数查询:constant_score{ “query”:{ “constant_score”:{ “filter”:{ “match”:{ “title”:“ElasticSearch” } }, “boost”:2 } }}不支持 match查询,支持filter查询。布尔查询:bool搜索:{ “query”: { “bool”: { “should”: [ { “match”: { “author”: “明日科技” } }, { “match”: { “title”: “ElasticSearch” } } ] } }}结果:{ “took”: 4, “timed_out”: false, “_shards”: { “total”: 5, “successful”: 5, “skipped”: 0, “failed”: 0 }, “hits”: { “total”: 2, “max_score”: 0.9808292, “hits”: [ { “_index”: “book”, “_type”: “it”, “_id”: “EdWwL2kBvvhXwNB9h50h”, “_score”: 0.9808292, “_source”: { “title”: “Java从入门到精通(第4版)(附光盘)”, “author”: “明日科技”, “word_count”: 10002, “publish_date”: “2016-09-01” } }, { “_index”: “book”, “_type”: “it”, “_id”: “EtWxL2kBvvhXwNB93p1F”, “_score”: 0.2876821, “_source”: { “title”: “Elasticsearch实战”, “author”: “[美] 拉杜·乔戈(Radu Gheorghe) 马修·李·欣曼(Matthew”, “word_count”: 10005, “publish_date”: “2018-10-2” } } ] }}是 OR 的关系看一下 AND 关系:{ “query”: { “bool”: { “must”: [ { “match”: { “author”: “明日科技” } }, { “match”: { “title”: “ElasticSearch” } } ] } }}结果:{ “took”: 3, “timed_out”: false, “_shards”: { “total”: 5, “successful”: 5, “skipped”: 0, “failed”: 0 }, “hits”: { “total”: 0, “max_score”: null, “hits”: [] }}我们换一下关键字,搜索:{ “query”: { “bool”: { “must”: [ { “match”: { “author”: “明日科技” } }, { “match”: { “title”: “java” } } ] } }}结果:{ “took”: 13, “timed_out”: false, “_shards”: { “total”: 5, “successful”: 5, “skipped”: 0, “failed”: 0 }, “hits”: { “total”: 1, “max_score”: 2.0431426, “hits”: [ { “_index”: “book”, “_type”: “it”, “_id”: “EdWwL2kBvvhXwNB9h50h”, “_score”: 2.0431426, “_source”: { “title”: “Java从入门到精通(第4版)(附光盘)”, “author”: “明日科技”, “word_count”: 10002, “publish_date”: “2016-09-01” } } ] }}加拦截条件,我们查看字数是10000的:{ “query”: { “bool”: { “must”: [ { “match”: { “author”: “明日科技” } }, { “match”: { “title”: “java” } } ], “filter”: [ { “term”: { “word_count”: 10000 } } ] } }}结果:{ “took”: 2, “timed_out”: false, “_shards”: { “total”: 5, “successful”: 5, “skipped”: 0, “failed”: 0 }, “hits”: { “total”: 0, “max_score”: null, “hits”: [] }}好吧,我们将word_count换成10002:{ “query”: { “bool”: { “must”: [ { “match”: { “author”: “明日科技” } }, { “match”: { “title”: “java” } } ], “filter”:[{ “term”:{ “word_count”:10002 } }] } }}结果:{ “took”: 3, “timed_out”: false, “_shards”: { “total”: 5, “successful”: 5, “skipped”: 0, “failed”: 0 }, “hits”: { “total”: 1, “max_score”: 2.0431426, “hits”: [ { “_index”: “book”, “_type”: “it”, “_id”: “EdWwL2kBvvhXwNB9h50h”, “_score”: 2.0431426, “_source”: { “title”: “Java从入门到精通(第4版)(附光盘)”, “author”: “明日科技”, “word_count”: 10002, “publish_date”: “2016-09-01” } } ] }}关键字:must_not例如,我不看java:{ “query”:{ “bool”:{ “must_not”:{ “term”:{ “title”:“java” } } } }}结果:{ “took”: 5, “timed_out”: false, “_shards”: { “total”: 5, “successful”: 5, “skipped”: 0, “failed”: 0 }, “hits”: { “total”: 4, “max_score”: 1, “hits”: [ { “_index”: “book”, “_type”: “it”, “_id”: “FdW1L2kBvvhXwNB91Z2M”, “_score”: 1, “_source”: { “title”: “数据结构(C语言版)”, “author”: “严蔚敏”, “word_count”: 100, “publish_date”: “2007-03-09” } }, { “_index”: “book”, “_type”: “it”, “_id”: “EtWxL2kBvvhXwNB93p1F”, “_score”: 1, “_source”: { “title”: “Elasticsearch实战”, “author”: “[美] 拉杜·乔戈(Radu Gheorghe) 马修·李·欣曼(Matthew”, “word_count”: 10005, “publish_date”: “2018-10-2” } }, { “_index”: “book”, “_type”: “it”, “_id”: “E9WzL2kBvvhXwNB9MZ3I”, “_score”: 1, “_source”: { “title”: “Laravel入门与实战 构建主流PHP应用开发框架 Laravel开发框架教程书籍 “, “author”: “拉杜 乔戈”, “word_count”: 10007, “publish_date”: “2017-11-03” } }, { “_index”: “book”, “_type”: “it”, “_id”: “FNW0L2kBvvhXwNB9pJ0W”, “_score”: 1, “_source”: { “title”: “Python编程 从入门到实践”, “author”: “[美]埃里克·马瑟斯(Eric Matthes)”, “word_count”: 10010, “publish_date”: “2016-07-10” } } ] }}关于搜索,这只是入门,我们会在第三节会继续讨论搜索。链接ElasticSearch入门Elastic官网ElasticSearchElasticSearch DocsElasticSearch Head搜索软件Elastic上市:市值近50亿美元 是开源项目商业化范本 ElasticSearch 学习系列Elasticsearch入门篇——基础知识Elasticsearch实战篇——Spring Boot整合ElasticSearchElasticsearch专题篇——搜索 ...

March 12, 2019 · 14 min · jiezi

Elasticsearch简介与实战

什么是Elasticsearch? Elasticsearch是一个开源的分布式、RESTful 风格的搜索和数据分析引擎,它的底层是开源库Apache Lucene。 Lucene 可以说是当下最先进、高性能、全功能的搜索引擎库——无论是开源还是私有,但它也仅仅只是一个库。为了充分发挥其功能,你需要使用 Java 并将 Lucene 直接集成到应用程序中。 更糟糕的是,您可能需要获得信息检索学位才能了解其工作原理,因为Lucene 非常复杂。 为了解决Lucene使用时的繁复性,于是Elasticsearch便应运而生。它使用 Java 编写,内部采用 Lucene 做索引与搜索,但是它的目标是使全文检索变得更简单,简单来说,就是对Lucene 做了一层封装,它提供了一套简单一致的 RESTful API 来帮助我们实现存储和检索。 当然,Elasticsearch 不仅仅是 Lucene,并且也不仅仅只是一个全文搜索引擎。 它可以被下面这样准确地形容:一个分布式的实时文档存储,每个字段可以被索引与搜索;一个分布式实时分析搜索引擎;能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据。由于Elasticsearch的功能强大和使用简单,维基百科、卫报、Stack Overflow、GitHub等都纷纷采用它来做搜索。现在,Elasticsearch已成为全文搜索领域的主流软件之一。 下面将介绍Elasticsearch的安装与简单使用。安装并运行Elasticsearch 安装 Elasticsearch 之前,你需要先安装一个较新版本的 Java,最好的选择是,你可以从 www.java.com 获得官方提供的最新版本的Java。 你可以从 elastic 的官网 elastic.co/downloads/elasticsearch 获取最新版本的Elasticsearch。解压文档后,按照下面的操作,即可在前台(foregroud)启动 Elasticsearch:cd elasticsearch-<version>./bin/elasticsearch此时,Elasticsearch运行在本地的9200端口,在浏览器中输入网址“http://localhost:9200/”,如果看到以下信息就说明你的电脑已成功安装Elasticsearch:{ “name” : “YTK8L4q”, “cluster_name” : “elasticsearch”, “cluster_uuid” : “hB2CZPlvSJavhJxx85fUqQ”, “version” : { “number” : “6.5.4”, “build_flavor” : “default”, “build_type” : “tar”, “build_hash” : “d2ef93d”, “build_date” : “2018-12-17T21:17:40.758843Z”, “build_snapshot” : false, “lucene_version” : “7.5.0”, “minimum_wire_compatibility_version” : “5.6.0”, “minimum_index_compatibility_version” : “5.0.0” }, “tagline” : “You Know, for Search”}在这里,我们安装的Elasticsearch版本号为6.5.4。 Kibana 是一个开源的分析和可视化平台,旨在与 Elasticsearch 合作。Kibana 提供搜索、查看和与存储在 Elasticsearch 索引中的数据进行交互的功能。开发者或运维人员可以轻松地执行高级数据分析,并在各种图表、表格和地图中可视化数据。 你可以从 elastic 的官网 https://www.elastic.co/downloads/kibana 获取最新版本的Kibana。解压文档后,按照下面的操作,即可在前台(foregroud)启动Kibana:cd kibana-<version>./bin/kabana此时,Kibana运行在本地的5601端口,在浏览器中输入网址“http://localhost:5601”,即可看到以下界面: 下面,让我们来了解Elasticsearch的一些基本概念,这有助于我们更好地理解和使用Elasticsearch。Elasticsearch基本概念全文搜索(Full-text Search) 全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。 在全文搜索的世界中,存在着几个庞大的帝国,也就是主流工具,主要有:Apache LuceneElasticsearchSolrFerret倒排索引(Inverted Index) 该索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。由于不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引(inverted index)。Elasticsearch能够实现快速、高效的搜索功能,正是基于倒排索引原理。节点 & 集群(Node & Cluster) Elasticsearch 本质上是一个分布式数据库,允许多台服务器协同工作,每台服务器可以运行多个Elasticsearch实例。单个Elasticsearch实例称为一个节点(Node),一组节点构成一个集群(Cluster)。索引(Index) Elasticsearch 数据管理的顶层单位就叫做 Index(索引),相当于关系型数据库里的数据库的概念。另外,每个Index的名字必须是小写。文档(Document) Index里面单条的记录称为 Document(文档)。许多条 Document 构成了一个 Index。Document 使用 JSON 格式表示。同一个 Index 里面的 Document,不要求有相同的结构(scheme),但是最好保持相同,这样有利于提高搜索效率。类型(Type) Document 可以分组,比如employee这个 Index 里面,可以按部门分组,也可以按职级分组。这种分组就叫做 Type,它是虚拟的逻辑分组,用来过滤 Document,类似关系型数据库中的数据表。 不同的 Type 应该有相似的结构(Schema),性质完全不同的数据(比如 products 和 logs)应该存成两个 Index,而不是一个 Index 里面的两个 Type(虽然可以做到)。文档元数据(Document metadata) 文档元数据为_index, _type, _id, 这三者可以唯一表示一个文档,_index表示文档在哪存放,_type表示文档的对象类别,_id为文档的唯一标识。字段(Fields) 每个Document都类似一个JSON结构,它包含了许多字段,每个字段都有其对应的值,多个字段组成了一个 Document,可以类比关系型数据库数据表中的字段。 在 Elasticsearch 中,文档(Document)归属于一种类型(Type),而这些类型存在于索引(Index)中,下图展示了Elasticsearch与传统关系型数据库的类比:Elasticsearch入门 Elasticsearch提供了多种交互使用方式,包括Java API和RESTful API ,本文主要介绍RESTful API 。所有其他语言可以使用RESTful API 通过端口 9200 和 Elasticsearch 进行通信,你可以用你最喜爱的 web 客户端访问 Elasticsearch 。甚至,你还可以使用 curl 命令来和 Elasticsearch 交互。 一个Elasticsearch请求和任何 HTTP 请求一样,都由若干相同的部件组成:curl -X<VERB> ‘<PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING>’ -d ‘<BODY>‘返回的数据格式为JSON,因为Elasticsearch中的文档以JSON格式储存。其中,被 < > 标记的部件:部件说明VERB适当的 HTTP 方法 或 谓词 : GET、 POST、 PUT、 HEAD 或者 DELETE。PROTOCOLhttp 或者 https(如果你在 Elasticsearch 前面有一个 https 代理)HOSTElasticsearch 集群中任意节点的主机名,或者用 localhost 代表本地机器上的节点。PORT运行 Elasticsearch HTTP 服务的端口号,默认是 9200 。PATHAPI 的终端路径(例如 _count 将返回集群中文档数量)。Path 可能包含多个组件,例如:_cluster/stats 和 _nodes/stats/jvm 。QUERY_STRING任意可选的查询字符串参数 (例如 ?pretty 将格式化地输出 JSON 返回值,使其更容易阅读)BODY一个 JSON 格式的请求体 (如果请求需要的话)对于HTTP方法,它们的具体作用为:HTTP方法说明GET获取请求对象的当前状态POST改变对象的当前状态PUT创建一个对象DELETE销毁对象HEAD请求获取对象的基础信息 我们以下面的数据为例,来展示Elasticsearch的用法。以下全部的操作都在Kibana中完成,创建的index为conference, type为event .插入数据 首先创建index为conference, 创建type为event, 插入id为1的第一条数据,只需运行下面命令就行:PUT /conference/event/1{ “host”: “Dave”, “title”: “Elasticsearch at Rangespan and Exonar”, “description”: “Representatives from Rangespan and Exonar will come and discuss how they use Elasticsearch”, “attendees”: [“Dave”, “Andrew”, “David”, “Clint”], “date”: “2013-06-24T18:30”, “reviews”: 3}在上面的命令中,路径/conference/event/1表示文档的index为conference, type为event, id为1. 类似于上面的操作,依次插入剩余的4条数据,完成插入后,查看数据如下:删除数据 比如我们想要删除conference中event里面id为5的数据,只需运行下面命令即可:DELETE /conference/event/5返回结果如下:{ “_index” : “conference”, “_type” : “event”, “_id” : “5”, “_version” : 2, “result” : “deleted”, “_shards” : { “total” : 2, “successful” : 1, “failed” : 0 }, “_seq_no” : 1, “_primary_term” : 1}表示该文档已成功删除。如果想删除整个event类型,可输入命令:DELETE /conference/event如果想删除整个conference索引,可输入命令:DELETE /conference修改数据 修改数据的命令为POST, 比如我们想要将conference中event里面id为4的文档的作者改为Bob,那么需要运行命令如下:POST /conference/event/4/_update{ “doc”: {“host”: “Bob”}}返回的信息如下:(表示修改数据成功){ “_index” : “conference”, “_type” : “event”, “_id” : “4”, “_version” : 7, “result” : “updated”, “_shards” : { “total” : 2, “successful” : 1, “failed” : 0 }, “_seq_no” : 7, “_primary_term” : 1}查看修改后的数据如下:查询数据 查询数据的命令为GET,查询命令也是Elasticsearch最为重要的功能之一。比如我们想查询conference中event里面id为1的数据,运行命令如下:GET /conference/event/1返回的结果如下:{ “_index” : “conference”, “_type” : “event”, “_id” : “1”, “_version” : 2, “found” : true, “_source” : { “host” : “Dave”, “title” : “Elasticsearch at Rangespan and Exonar”, “description” : “Representatives from Rangespan and Exonar will come and discuss how they use Elasticsearch”, “attendees” : [ “Dave”, “Andrew”, “David”, “Clint” ], “date” : “2013-06-24T18:30”, “reviews” : 3 }}在_source 属性中,内容是原始的 JSON 文档,还包含有其它属性,比如_index, _type, _id, _found等。 如果想要搜索conference中event里面所有的文档,运行命令如下:GET /conference/event/_search返回结果包括了所有四个文档,放在数组 hits 中。 当然,Elasticsearch 提供更加丰富灵活的查询语言叫做 查询表达式 , 它支持构建更加复杂和健壮的查询。利用查询表达式,我们可以检索出conference中event里面所有host为Bob的文档,命令如下:GET /conference/event/_search{ “query” : { “match” : { “host” : “Bob” } }}返回的结果只包括了一个文档,放在数组 hits 中。 接着,让我们尝试稍微高级点儿的全文搜索——一项传统数据库确实很难搞定的任务。搜索下所有description中含有"use Elasticsearch"的event:GET /conference/event/_search{ “query” : { “match” : { “description” : “use Elasticsearch” } }}返回的结果(部分)如下:{ … “hits” : { “total” : 2, “max_score” : 0.65109104, “hits” : [ { … “_score” : 0.65109104, “_source” : { “host” : “Dave Nolan”, “title” : “real-time Elasticsearch”, “description” : “We will discuss using Elasticsearch to index data in real time”, … } }, { … “_score” : 0.5753642, “_source” : { “host” : “Dave”, “title” : “Elasticsearch at Rangespan and Exonar”, “description” : “Representatives from Rangespan and Exonar will come and discuss how they use Elasticsearch”, … } } ] }}返回的结果包含了两个文档,放在数组 hits 中。让我们对这个结果做一些分析,第一个文档的description里面含有“using Elasticsearch”,这个能匹配“use Elasticsearch”是因为Elasticsearch含有内置的词干提取算法,之后两个文档按_score进行排序,_score字段表示文档的相似度(默认的相似度算法为BM25)。 如果想搜索下所有description中严格含有"use Elasticsearch"这个短语的event,可以使用下面的命令:GET /conference/event/_search{ “query” : { “match_phrase”: { “description” : “use Elasticsearch” } }}这时候返回的结果只有一个文档,就是上面输出的第二个文档。 当然,Elasticsearch还支持更多的搜索功能,比如过滤器,高亮搜索,结构化搜索等,希望接下来能有更多的时间和经历来介绍总结 后续有机会再介绍如何利用Python来操作Elasticsearch 本次分享到此结束,感谢大家阅读~注意:本人现已开通微信公众号: Python爬虫与算法(微信号为:easy_web_scrape), 欢迎大家关注哦~~ ...

March 6, 2019 · 3 min · jiezi

[FORBIDDEN/12/index read-only

问题描述logstash写es大量报错,主要内容如下:retrying failed action with response code: 403 ({“type”=>“cluster_block_exception”, “reason”=>“blocked by: [FORBIDDEN/12/index read-only / allow delete (api)];2. 解决方案curl -XPUT -H “Content-Type: application/json” http://localhost:9200/_all/_settings -d ‘{“index.blocks.read_only_allow_delete”: null}‘也可以直接在kibana的console中直接运行以下命令:PUT /_all/_settings{“index.blocks.read_only_allow_delete”: null}3. 问题原因主要是因为磁盘不够用了。Elasticsearch会在索引无法存储更多document的时候自动将索引切换为read-only,以保证能够查询。如果你自己手动删除了数据,Elasticsearch不会给你自动切换回来,不过你可以手动去修改。就是用上面的命令就好了。4. 相关资料https://discuss.elastic.co/t/…https://discuss.elastic.co/t/...https://benjaminknofe.com/blo…

March 1, 2019 · 1 min · jiezi

ElasticSearch基础知识整理

分片、副本分片shards:数据量特大,没有足够大的硬盘空间来一次性存储,且一次性搜索那么多的数据,响应跟不上es提供把数据进行分片存储,这样方便进行拓展和提高吞吐。副本replicas:分片的拷贝,当主分片不可用的时候,副本就充当主分片进行使用Es中的每个索引默认分配5个主分片和1份副本:如果你的集群中至少有两个节点,你的索引将会有5个主分片和另外5个复制分片(1份副本),这样每个索引总共就有10个分片。集群健康检查

March 1, 2019 · 1 min · jiezi

自制一个 elasticsearch-spring-boot-starter

概 述Elasticsearch 在企业里落地的场景越来越多了,但是大家在项目里使用 Elasticsearch的姿势也是千奇百怪,这次正好自己需要使用,所以干脆就封装一个 elasticsearch-spring-boot-starter以供复用好了。如果不知道 spring-boot-starter该如何制作,可以参考文章《如何自制一个Spring Boot Starter并推送到远端公服》,下面就来简述一下自制的 elasticsearch-spring-boot-starter该如何使用。依赖引入<dependency> <groupId>com.github.hansonwang99</groupId> <artifactId>elasticsearch-spring-boot-starter</artifactId> <version>0.0.8</version></dependency><repositories> <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository></repositories>配置文件如果你还没有一个属于自己的 Elasticsearch集群,可以参考文章 《CentOS7 上搭建多节点 Elasticsearch集群》来一步步搭建之,本文实验所用的集群即来源于此。elasticsearch: host: 192.168.31.75 httpPort: 9200 tcpPort: 9300 clusterName: codesheep docFields: title,filecontent auth: enable: false各个字段解释如下:host:Elasticsearch 节点地址httpPort: Elasticsearch REST端口tcpPort:Elasticsearch TCP端口clusterName:集群名docFields:文档字段,以英文逗号间隔,比如我这里的业务场景是文档包含 标题(title)和 内容(filecontent)字段auth:是否需要权限认证由于我这里安装的实验集群并无 x-pack权限认证的加持,因此无需权限认证,实际使用的集群或者阿里云上的 Elasticsearch集群均有完善的 x-pack权限认证,此时可以加上用户名/密码的配置:elasticsearch: host: 192.168.199.75 httpPort: 9200 tcpPort: 9300 clusterName: codesheep docFields: title,filecontent auth: enable: true username: elasticsearch password: xxxxxx用法例析首先注入相关资源@Autowiredprivate ISearchService iSearchService;@Autowiredprivate DocModel docModel;这些都是在 elasticsearch-spring-boot-starter中定义的创建索引public String createIndex() throws IOException { IndexModel indexModel = new IndexModel(); indexModel.setIndexName(“testindex2”); // 注意索引名字必须小写,否则ES抛异常 indexModel.setTypeName(“testtype2”); indexModel.setReplicaNumber( 2 ); // 两个节点,因此两个副本 indexModel.setShardNumber( 3 ); XContentBuilder builder = null; builder = XContentFactory.jsonBuilder(); builder.startObject(); { builder.startObject(“properties”); { builder.startObject(“title”); { builder.field(“type”, “text”); builder.field(“analyzer”, “ik_max_word”); } builder.endObject(); builder.startObject(“filecontent”); { builder.field(“type”, “text”); builder.field(“analyzer”, “ik_max_word”); builder.field(“term_vector”, “with_positions_offsets”); } builder.endObject(); } builder.endObject(); } builder.endObject(); indexModel.setBuilder( builder ); Boolean res = iSearchService.createIndex(indexModel); if( true==res ) return “创建索引成功”; else return “创建索引失败”;}删除索引public String deleteIndex() { return (iSearchService.deleteIndex(“testindex2”)==true) ? “删除索引成功”:“删除索引失败”;}判断索引是否存在if ( existIndex(indexName) ) { …} else { …}插入单个文档public String insertSingleDoc( ) { SingleDoc singleDoc = new SingleDoc(); singleDoc.setIndexName(“testindex2”); singleDoc.setTypeName(“testtype2”); Map<String,Object> doc = new HashMap<>(); doc.put(“title”,“人工智能标题1”); doc.put(“filecontent”,“人工智能内容1”); singleDoc.setDocMap(doc); return ( true== iSearchService.insertDoc( singleDoc ) ) ? “插入单个文档成功” : “插入单个文档失败”;}批量插入文档public String insertDocBatch() { BatchDoc batchDoc = new BatchDoc(); batchDoc.setIndexName(“testindex2”); batchDoc.setTypeName(“testtype2”); Map<String,Object> doc1 = new HashMap<>(); doc1.put(“title”,“人工智能标题1”); doc1.put(“filecontent”,“人工智能内容1”); Map<String,Object> doc2 = new HashMap<>(); doc2.put(“title”,“人工智能标题2”); doc2.put(“filecontent”,“人工智能内容2”); Map<String,Object> doc3 = new HashMap<>(); doc3.put(“title”,“人工智能标题3”); doc3.put(“filecontent”,“人工智能内容3”); Map<String,Object> doc4 = new HashMap<>(); doc4.put(“title”,“人工智能标题4”); doc4.put(“filecontent”,“人工智能内容4”); List<Map<String,Object>> docList = new ArrayList<>(); docList.add( doc1 ); docList.add( doc2 ); docList.add( doc3 ); docList.add( doc4 ); batchDoc.setBatchDocMap( docList ); return ( true== iSearchService.insertDocBatch( batchDoc ) ) ? “批量插入文档成功” : “批量插入文档失败”;}搜索文档public List<Map<String,Object>> searchDoc() { SearchModel searchModel = new SearchModel(); searchModel.setIndexName( “testindex2” ); List<String> fields = new ArrayList<>(); fields.add(“title”); fields.add(“filecontent”); fields.add(“id”); searchModel.setFields( fields ); searchModel.setKeyword( “人工” ); searchModel.setPageNum( 1 ); searchModel.setPageSize( 5 ); return iSearchService.queryDocs( searchModel );}删除文档public String deleteDoc() { SingleDoc singleDoc = new SingleDoc(); singleDoc.setIndexName(“testindex2”); singleDoc.setTypeName(“testtype2”); singleDoc.setId(“vPHMY2cBcGZ3je_1EgIM”); return (true== iSearchService.deleteDoc(singleDoc)) ? “删除文档成功” : “删除文档失败”;}批量删除文档public String deleteDocBatch() { BatchDoc batchDoc = new BatchDoc(); batchDoc.setIndexName(“testindex2”); batchDoc.setTypeName(“testtype2”); List<String> ids = new ArrayList<>(); ids.add(“vfHMY2cBcGZ3je_1EgIM”); ids.add(“vvHMY2cBcGZ3je_1EgIM”); batchDoc.setDocIds( ids ); return ( true== iSearchService.deleteDocBatch(batchDoc) ) ? “批量删除文档成功” : “批量删除文档失败”;}更新文档public String updateDoc( @RequestBody SingleDoc singleDoc ) { SingleDoc singleDoc = new SingleDoc(); singleDoc.setId(“wPH6Y2cBcGZ3je_1OwI7”); singleDoc.setIndexName(“testindex2”); singleDoc.setTypeName(“testtype2”); Map<String,Object> doc = new HashMap<>(); doc.put(“title”,“人工智能标题(更新后)”); doc.put(“filecontent”,“人工智能内容(更新后)”); singleDoc.setUpdateDocMap(doc); return (true== iSearchService.updateDoc(singleDoc)) ? “更新文档成功” : “更新文档失败”;}后 记由于能力有限,若有错误或者不当之处,还请大家批评指正,一起学习交流!My Personal Blog:CodeSheep 程序羊 ...

February 28, 2019 · 2 min · jiezi

spring-boot的access日志格式修改

前言使用spring-boot框架开发的项目中,默认的logback格式可能默认是如下的格式:<Valve className=“org.apache.catalina.valves.AccessLogValve” directory=“logs” prefix=“access.” suffix=".log" pattern="%h %l %u %t “%r” %s %b “%{Referer}i” “%{User-Agent}i” %{X-Forwarded-For}i “%Dms”" resolveHosts=“false”/>假设有一种场景,我们希望能够将业务数据加入到access日志中,方便统计每个接口在用户粒度的调用量。那么在分布式日志服务中,我们可以考虑在每行的access日志中,添加用户的id,方便直接解析到es中,在es中计算count即可。本文主要解决access日志中添加userId的问题。具体思路tomcat的access日志配置介绍可以参考博文:【Tomcat Access Log配置】在上述博文中,我们可以看到如下内容:Access Log中也支持cookie,请求header,响应headers,Session或者其他在ServletRequest中的对象的信息。格式遵循apache语法%{xxx}i 请求headers的信息%{xxx}o 响应headers的信息%{xxx}c 请求cookie的信息%{xxx}r xxx是ServletRequest的一个属性%{xxx}s xxx是HttpSession的一个属性我们发现是可以给ServletRequest添加属性,打印日志的时候进行解析的。不过使用上述的格式配置的logback-access.xml文件因为格式问题导致无法解析。找到一篇详解logback layout配置的文章:https://blog.csdn.net/Doraemo…可以看到需要使用%reqAttribute{XXXX}才可以。<pattern>%i{ClientIp} %h %l %u [%t] “%r” %s %b “%i{Referer}” “%i{User-Agent}” %i{x-ssl-header} %reqAttribute{user-id}%n======%n%fullRequest%n======%n%fullResponse</pattern>只需要在代码中进行如下设置即可servletRequest.setAttribute(“user-id”, userId);(可能写的有点抽象啊,只是为了自己记录一下,不要忘记)

February 27, 2019 · 1 min · jiezi

Elasticsearch 架构原理—— 新数据写入过程

前言在分布式日志服务架构中,我们只需要将logstash的output指向ES就可以了,但是,写入的数据是如何变成Elasticsearch里可以被检索和聚合的索引内容的呢?本文重点介绍数据在写入Elasticsearch索引流程中发生的具体操作。重点在于其中segment、buffer和translog三部分对实时性和性能方面的影响。动态更新的Lucene索引Lucene对于新收到的数据写入到新的索引文件里。Lucene把每次生成的倒排索引,叫做一个segment,然后另外使用一个commit文件,记录索引内所有的segment。而生成segment的数据来源则是放在内存中的buffer,也就是说,动态更新过程如下:当前索引有3个segment可用;新接收的数据进入内存buffer;内存buffer刷到磁盘,生成一个新的segment,commit文件同步更新。利用磁盘缓存实现的准实时检索上面的内容中,内存buffer刷入磁盘,但是不可避免的问题就是写磁盘太慢了。对于我们的日志服务来说,如果要进行一些实时性统计,这个速度是不能接受的。所以,在内存buffer刷入磁盘的处理中,还有一个中间状态:内存buffer生成一个新的segment,刷到文件系统缓存中,Lucene即可检索这个新的segment。文件系统缓存真正同步到磁盘上,commit文件更新。其实就是多加了一步文件系统缓存。Elasticsearch中默认1s中刷新一次。如果觉得间隔时间还是很短,可以调用/_refresh接口来修改。不过对于日志场景来说,我们更需要的是更快的写入性能,所以我们最好是通过/_settings接口或者定制template的方式,加大refresh_intereval参数:curl -XPOST http://127.0.0.1:9200/logstash-lms-2019.02.19/_settings -d’{“refresh_interval”:“10s”}‘如果是导入历史数据的场合,甚至可以直接关闭。索引数据一致性保证——translog提供的磁盘同步控制refres只是写到文件系统缓存,如果最后一步写入到磁盘期间发生了错误、硬件故障灯问题,数据会丢失吗?这里有另一个控制机制。Elasticsearch在把数据写入到内存buffer的同时,其实还另外记录了一个translog日志。refres发生时,translog日志文件依旧保持原样。如果期间发生异常,Elasticsearch会从commit位置开始,恢复整个translog文件中的记录,保证数据一致性。等到真正吧segment刷新到磁盘,commit文件更新后,translog才会清空,即flush操作。Elasticsearch默认30分钟flush一次,或者translog文件>512M时会主动flush。可以通过以下参数进行设置:index.translog.flush_threshold_periodindex.translog.flush_threshold_size#控制每收到多少条数据后flush一次index.translog.flush_threshold_ops

February 24, 2019 · 1 min · jiezi

配置elasticsearch6.5.4-ik分词插件安装,测试,扩展字典

elasticsearch基本配置上篇已经简单介绍过,本文讲述配置ik分词器插件的安装,测试,自定义扩展字典,简单使用。希望能帮助后来者少走点弯路。注意:ik分词器必须保证和elasticsearch版本一致,配置完成之后可以设置默认的分词工具,也可以在创建索引文件时使用ik分词工具1. elasticsearch-ik分词环境必须跟elasticsearch一致我的elasticsearch版本是elasticsearch-v6.5.4,所以需要下载的ik分词器版本是elasticsearch-ik-v6.5.4下载文件:wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.5.4.tar.gz wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.5.4/elasticsearch-analysis-ik-6.5.4.zip 进入elasticsearch的安装目录,解压ik分词器内文件到plugin目录:root @ localhost in /data/elasticsearch-6.5.4 [17:18:23]$ l总用量 4.8Mdrwxrwxr-x 9 euser euser 198 12月 11 11:26 .drwxr-xr-x. 7 root root 90 1月 16 16:35 ..drwxrwxr-x 3 euser euser 4.0K 12月 11 11:13 bindrwxrwxr-x 2 euser euser 178 12月 11 11:32 configdrwxrwxr-x 3 euser euser 19 12月 11 11:25 data-rwxrwxr-x 1 euser euser 4.3M 12月 6 22:30 elasticsearch-analysis-ik-6.5.4.zipdrwxrwxr-x 3 euser euser 4.0K 11月 30 08:02 lib-rwxrwxr-x 1 euser euser 14K 11月 30 07:55 LICENSE.txtdrwxrwxrwx 2 euser euser 8.0K 2月 11 01:30 logsdrwxrwxr-x 28 euser euser 4.0K 11月 30 08:02 modules-rwxrwxr-x 1 euser euser 395K 11月 30 08:01 NOTICE.txtdrwxrwxr-x 3 euser euser 25 12月 11 11:29 plugins-rwxrwxr-x 1 euser euser 8.4K 11月 30 07:55 README.textile进入到plugin目录,创建文件夹mkdir analysis-ik/ 解压ik分词器中的文件到analysis-ik目录:# root @ iZ2zedtbewsc8oa9i1cb4tZ in /data/elasticsearch-6.5.4 [18:01:37] $ cd plugins/# root @ iZ2zedtbewsc8oa9i1cb4tZ in /data/elasticsearch-6.5.4 [18:01:37] $ mkdir analysis-ik# root @ iZ2zedtbewsc8oa9i1cb4tZ in /data/elasticsearch-6.5.4 [18:01:37] $ mv ../../../analysis-ik analysis-ik# root @ iZ2zedtbewsc8oa9i1cb4tZ in /data/elasticsearch-6.5.4/plugins [18:04:29] $ lsanalysis-ik# root @ iZ2zedtbewsc8oa9i1cb4tZ in /data/elasticsearch-6.5.4/plugins [18:04:34] $ ls -l ./analysis-ik/total 1432-rw-r–r– 1 root root 263965 May 6 2018 commons-codec-1.9.jar-rw-r–r– 1 root root 61829 May 6 2018 commons-logging-1.2.jardrwxr-xr-x 2 root root 4096 Aug 26 17:52 config-rw-r–r– 1 root root 54693 Dec 23 11:26 elasticsearch-analysis-ik-6.5.4.jar-rw-r–r– 1 root root 736658 May 6 2018 httpclient-4.5.2.jar-rw-r–r– 1 root root 326724 May 6 2018 httpcore-4.4.4.jar-rw-r–r– 1 root root 1805 Dec 23 11:26 plugin-descriptor.properties-rw-r–r– 1 root root 125 Dec 23 11:26 plugin-security.policy配置默认分词工具为ik分词,在ElasticSearch的配置文件config/elasticsearch.yml中的最后一行添加参数:index.analysis.analyzer.default.type:ik(则设置所有索引的默认分词器为ik分词,也可以不这么做,通过设置mapping来使用ik分词)# root @ iZ2zedtbewsc8oa9i1cb4tZ in /data/elasticsearch-6.5.4 [18:33:16] $ cd config/# root @ iZ2zedtbewsc8oa9i1cb4tZ in /data/elasticsearch-6.5.4/config [18:33:21] $ echo “index.analysis.analyzer.default.type:ik” >> elasticsearch.yml2. 启动eleasticsearch,并测试ik分词方便测试,以普通模式启动:./bin/elasticsearch创建索引文件:curl -XPUT http://localhost:9200/class使用ik分词,查看效果:curl -XGET -H “Content-Type: application/json” ‘http://localhost:9200/class/_analyze?pretty’ -d ‘{“analyzer”: “ik_max_word”,“text”: “我是中国人,我爱我的祖国和人民”}’{ “tokens” : [ { “token” : “我”, “start_offset” : 0, “end_offset” : 1, “type” : “CN_CHAR”, “position” : 0 }, { “token” : “是”, “start_offset” : 1, “end_offset” : 2, “type” : “CN_CHAR”, “position” : 1 }, { “token” : “中国人”, “start_offset” : 2, “end_offset” : 5, “type” : “CN_WORD”, “position” : 2 }, { “token” : “中国”, “start_offset” : 2, “end_offset” : 4, “type” : “CN_WORD”, “position” : 3 }, { “token” : “国人”, “start_offset” : 3, “end_offset” : 5, “type” : “CN_WORD”, “position” : 4 }, { “token” : “我”, “start_offset” : 6, “end_offset” : 7, “type” : “CN_CHAR”, “position” : 5 }, { “token” : “爱我”, “start_offset” : 7, “end_offset” : 9, “type” : “CN_WORD”, “position” : 6 }, { “token” : “的”, “start_offset” : 9, “end_offset” : 10, “type” : “CN_CHAR”, “position” : 7 }, { “token” : “祖国”, “start_offset” : 10, “end_offset” : 12, “type” : “CN_WORD”, “position” : 8 }, { “token” : “和”, “start_offset” : 12, “end_offset” : 13, “type” : “CN_CHAR”, “position” : 9 }, { “token” : “人民”, “start_offset” : 13, “end_offset” : 15, “type” : “CN_WORD”, “position” : 10 } ]}3. 测试完成之后以守护进程启动./data/elasticsearch/bin/elasticsearch -d待续 …… ...

February 22, 2019 · 3 min · jiezi

如何实现 Logstash/Elasticsearch 与MySQL自动同步 更新操作 和 删除操作 ?

技术背景我们现在的同步, 是依靠 Logstash的 input-jdbc-plugin插件来实现的自动增量更新,这个的方案貌似只能 增量 添加数据而不能修改或者删除数据. 其实不然, 我们根据input-jdbc-plugin这个插件的一些配置, 是可以实现我们要的效果的.方案原理:用一个更新时间的字段来作为每次Logstash增量更新的tracking column, 这样Logstash每次增量更新就会根据上一次的最后的更新时间来作为标记.索引的document id必须是 主键, 这样在每次增量更新的时候, 才不会只是增加数据, 之前ID相同的数据就会被覆盖, 从而达到update的效果.删除是建立在上面更新的原理之上, 就是再加一个删除标记的字段, 也就是数据只能软删除, 不能直接删除.以上就是这个方案的实现原理, 缺点就是要多加一个更新时间的字段, 并且数据不能直接删除, 只能软删除, 所以这个方案有一定的局限性, 但是对于大部分操作, 应该都是可以妥协的.实施细节:第一步: 数据表设计你的表, 必须要有一个update_time或同样意思的字段, 表明这条数据修改的时间如果有删除操作的话, 是不可以直接删除数据的, 必须是软删除,就是还得有一个 delete_time或者is_delete或相同意思的字段第二步: 配置logstashinput 和outputinput { jdbc { … statement => “SELECT * FROM article WHERE update_time > :sql_last_value ORDER BY id ASC” tracking_column => ‘update_time’ … }}output { elasticsearch { … document_id => “%{id}” … }}

February 18, 2019 · 1 min · jiezi

从零搭建精准运营系统

2018刚过去,趁着春节放假对过去一年主导开发的项目做个梳理和总结项目背景平台运营到一定阶段,一定会累积大批量的用户数据,这些用户数据是运营人员的黄金财产。而如何利用用户的数据来做运营(消息推送、触达消息、优惠券发送、广告位等),正是精准运营系统需要解决的问题。本文是基于信贷业务实践后写出来的,其它行业如保险、电商、航旅、游戏等也可以参考。业务场景先看几个具有代表性的需求用户可用额度在20000~50000元,而且有借款记录,未还本金为0,性别为“男”用户发生了A行为且未还本金大于5000用户在1天内发生A行为次数大于等于3次用户在A行为前24小时内未发生B行为用户在A行为后一个月内未发生B行为业务上有两种消息类型日常消息:由业务人员通过条件筛选锁定用户群,定时或即时给批量用户发送消息或者优惠券触达消息:主要由用户自身的行为触发,比如登陆、进件申请、还款等,满足一定筛选条件实时给用户发送消息或优惠券对于用户筛选条件,也主要有两种类型用户状态:包括用户自身属性如性别、年龄、学历、收入等,还有用户相关联实体如进件订单、账户信息、还款计划、优惠券等的属性,以及用户画像数据如行为偏好、进件概率等用户行为:即用户的动作,包括登陆、进件申请、还款,甚至前端点击某个按钮、在某个文本框输入都算早期方案早期方案存在以下痛点至少两次跨部门沟通配合成本,周期被拉长非实时消息推送,无法实现基于用户行为的实时推送场景非实时效果验证,无法及时调整运营策略系统搭建的目标需要定义规则,提供可视化界面给业务人员动态配置,无需重启系统即使生效,减少沟通成本和避免重复开发,总之就是要更加 自动化 和 易配置采集实时数据,根据实时事件做实时推送,总之就是要 实时技术选型数据采集、转换、存储采集:状态类的数据主要放在各个业务系统的关系型数据库中,由于历史原因有postgres和mysql,需要实时采集表的数据变更,这里使用kafka connector读取mysql的binlog或postgres的xlog,另外还有标签系统计算出来的标签,在kafka中;而事件类数据主要来源于前端上报事件(有专门的服务接收再丢到kafka),关系型数据库里面也可以提取一些事件。转换:采集出来的数据需要做一些格式统一等操作,用kafka connector。存储:采用Elasticsearch存储用户数据,ES查询不像mysql或mongoDB用B-tree 或B+tree实现索引,而是使用bitset和skip list来处理联合索引,特别适合多字段的复杂查询条件。下面重点看下kafka connector和Elasticsearch如何使用kafka connectorkafka connector有Source和Sink两种组件,Source的作用是读取数据到kafka,这里用开源实现debezium来采集mysql的binlog和postgres的xlog。Sink的作用是从kafka读数据写到目标系统,这里自己研发一套组件,根据配置的规则将数据格式化再同步到ES。kafka connector有以下优点:提供大量开箱即用的插件,比如我们直接用debezium就能解决读取mysql和pg数据变更的问题伸缩性强,对于不同的connector可以配置不同数量的task,分配给不同的worker,,我们可以根据不同topic的流量大小来调节配置。容错性强,worker失败会把task迁移到其它worker上面使用rest接口进行配置,我们可以对其进行包装很方便地实现一套管理界面Elasticsearch对于状态数据,由于状态的写操作相对较少,我们采取嵌套文档的方式,将同个用户的相关实体数据都同步写入到同个文档,具体实现用painless脚本做局部更新操作。效果类似这样:{ “id”:123, “age”:30, “credit_line”:20000, “education”:“bachelor”, … “last_loan_applications”:{ “loan_id”:1234, “status”:“reject”, … } …}事件数据写入比较频繁,数据量比较多,我们使用父子文档的方式做关联,效果类似这样:{ “e_uid”:123, “e_name”:“loan_application”, “e_timestamp”:“2019-01-01 10:10:00” …}(e_前缀是为了防止同个index下同名字段冲突)ES这样存储一方面是方便做统计报表,另一方面跟用户筛选和触达有关。规则引擎在设计规则引擎前,我们对业界已有的规则引擎,主要包括Esper, Drools, Flink CEP,进行了初步调研。EsperEsper设计目标为CEP的轻量级解决方案,可以方便的嵌入服务中,提供CEP功能。优势:轻量级可嵌入开发,常用的CEP功能简单好用。EPL语法与SQL类似,学习成本较低。劣势:单机全内存方案,需要整合其他分布式和存储。以内存实现时间窗功能,无法支持较长跨度的时间窗。无法有效支持定时触达(如用户在浏览发生一段时间后触达条件判断)。DroolsDrools开始于规则引擎,后引入Drools Fusion模块提供CEP的功能。优势:功能较为完善,具有如系统监控、操作平台等功能。规则支持动态更新劣势:以内存实现时间窗功能,无法支持较长跨度的时间窗。无法有效支持定时触达(如用户在浏览发生一段时间后触达条件判断)。FlinkFlink 是一个流式系统,具有高吞吐低延迟的特点,Flink CEP是一套极具通用性、易于使用的实时流式事件处理方案。优势:继承了Flink高吞吐的特点事件支持存储到外部,可以支持较长跨度的时间窗。可以支持定时触达(用followedBy+PartternTimeoutFunction实现)劣势:无法动态更新规则(痛点)自定义规则综上对比了几大开源规则引擎,发现都无法满足业务需求:业务方要求支持长时间窗口(n天甚至n个月,比如放款一个月后如果没产生还款事件就要发消息)动态更新规则,而且要可视化(无论用哪个规则引擎都需要包装,需要考虑二次开发成本)最终我们选择自己根据业务需要,开发基于json的自定义规则,规则类似下面例子:{ “batchId”: “xxxxxxxx”, //流水号,创建每条运营规则时生成 “type”: “trigger”, //usual “triggerEvent”: “login”, “after”: “2h”, //分钟m,小时h,天d,月M “pushRules”: [//支持同时推送多条不同类型的消息 { “pushType”: “sms”, //wx,app,coupon “channel”: “cl”, “content”: “hello #{userInfo.name}” }, { “pushType”: “coupon”, “couponId”: 1234 } ], “statusConditions”: [ { “name”: “and”, //逻辑条件,支持与(and)或(or)非(not) “conditions”: [ { “name”: “range”, “field”: “credit_line”, “left”: 2000, “right”: 10000, “includeLeft”: true, “includeRight”: false }, { “name”:“in”, “filed”:“education”, “values”:[“bachelor”,“master”] } ] } ], “eventConditions”: [ { “name”: “or”,//逻辑条件,支持与(and)或(or)非(not) “conditions”: [ { “name”: “event”, “function”: “count”, //聚合函数,目前只支持count “eventName”: “xxx_button_click”, “range”: { //聚合结果做判断 “left”: 1, “includeLeft”: true }, “timeWindow”: { “type”: “fixed”, //fixed为固定窗口,sliding为滑动窗口 “start”: “2019-01-01 01:01:01”, “end”: “2019-02-01 01:01:01” }, “conditions”: [ //event查询条件继承and逻辑条件,所以事件也可以过滤字段 { “name”: “equals”, “field”: “f1”, “value”: “v1” } ] } ] } ]}使用面向对象思维对过滤条件做抽象后,过滤条件继承关系如下:然后代码里加一层parser把Condition都转成ES查询语句,实现轻量级的业务规则配置功能。整体技术方案系统组成模块及功能如下:mysql binlog:mysql的数据变更,由kafka connector插件读取到kafka,数据源之一postgres xlog:pg的数据变更,由kafka connector插件读取到kafka,数据源之一report server:事件上报服务,数据源之一tags:用户画像系统计算出来的标签,数据源之一触发场景路由:分实时触发和延迟触发,实时触发直接到下一步,延迟触发基于 rabbitmq的延迟队列实现用户筛选模块:将筛选规则翻译为ES查询语句到ES查询用户数据,可以是批量的和单个用户的变量渲染模块:对推送内容做处理推送适配器:兼容不同的推送方式定时任务调度器:基于elastic-job,处理定时推送任务规则配置控制台:提供可视化配置界面(运营规则配置、数据采集规则配置、字段元数据配置等)报表服务:提供报表查询功能运营位服务:提供外部接口,根据条件匹配运营位(如启动图、首页banner图片等)总结与展望系统基本满足了目前的业务需求,对转化率等运营指标提升显著可以扩展其它业务,如推荐、风控、业务监控等规则定时拉取,实时性差,可以用zk做发布订阅实现即时更新目前事件的聚合函数只支持count,能满足业务需求但是未来可能还需要支持其它函数系统只经过千万级用户的生产验证,再高数量级的话可能还有很多性能优化的工作,如ES并行查询(目前用scroll api批量拉取用户数据是串行的)事件类数据越来越多,目前采取定时删除半年前数据的方式,防止持续增长过快不可控,所以事件类条件不可超过半年的时间窗口虽然系统对业务无入侵,但是反过来看本系统依赖于上游数据,上游数据发生变化时如何做到影响最小?未来会继续从技术及业务两方面入手,将系统建设的更加易用、高效。 ...

February 17, 2019 · 1 min · jiezi

ELK(Elasticsearch,Logstash,Kibana) 搭建 同步 MySQL 及 用户权限安全设置

本文章用的elastic相关组件版本: 6.4.1一, 准备ElasticsearchLogstashKibanajdk 8 (主要根据logstash的版本)下载mysql-connector-java.jar找对应MySQL的版本具体每个插件的安装方式请查看官方文档, 很简单的, WIN下的都是绿色版, Linux的安装目录基本是在/usr/share/对应软件,配置文件目录在/etc/对应软件下面.二, 配置1, Logstash的配置input的配置https://www.elastic.co/guide/…增量更新的主要几个参数use_column_value => true //是否使用自定义标记列tracking_column => “id” //指定的列record_last_run => true //是否记录最后运行的指标last_run_metadata_path => “[path]” //记录的指标存储路径, 当多个input的时候, 这个是必须要设置的, 否则多个input会共用一个jdbc_paging_enabled => true //是否启用分页查询jdbc_page_size => “[number]” //每次查询多少statement => “SELECT * FROM db_name WHERE id > :sql_last_value” //记住这里的 :sql_last_value多个input的时候, 需要这个配置 后面的output用用if else时候需要作为判断依据type => ““output配置mappingshttps://www.elastic.co/guide/…template => “mappings配置的路径, 一般json格式"template_name => ““template_overwrite => trueoutput的判断语法if EXPRESSION { //…} else if EXPRESSION { //…} else { //…}2, 安全设置(用户认证相关设置)修改Elasticsearch设置修改elasticsearch.yml, 添加两个配置项:xpack.security.enabled: truexpack.security.transport.ssl.enabled: true在下面更新license的时候, 请将以上设置为false(也可以不设置为false, 影响可能不大)X-PACK设置:不过在用这个命令之前, 是需要依赖x-pack模块的, Elastic 6.3.x后面的版本, 就内置了这个模块,这个模块的使用, 不是免费的, 是收费的, 免费和收费的区别, 网上有破解版, 我暂时用破解版做演示, 请大家还是使用正版吧. 破解版替换完原版文件后, 需要自己去官网申请一个basic授权的license文件, 这是一个json文件, 修改里面的type为platinum,expiry_date_in_millis为2855980923000, 然后再在Kibana的Management的License Management的地方上传修改后的License文件.初始化用户及密码命令:elastic/bin/elastic-set-password这个命令只有两个参数 auto 和 interactive 一个是自动, 一个是交互, 交互的方式就是可以自己设置密码, 自动的我没用过, 这个命令会设置5个用户的密码:elastic,kibana,logstash_system,beats_system,apm_system_users,其中elastic这个用户的权限最大.在完成以上步骤后, 记得重启Elasticsearch和Kibana, 在重启Kibana的时候, 会遇到一些warning和error,先不管error,有两个warning需要先解决, 后面的error自然就没有了. 这两个应该是关于xpack.reporting.encryptionKey和xpack.security.encryptionKey的.参考https://www.elastic.co/guide/… 和 https://www.elastic.co/guide/...xpack.reporting.encryptionKey: “a_random_string"xpack.security.encryptionKey: “something_at_least_32_characters"再重启应该就没有error了, 这个时候就可以用之前设置密码的那几个账号登录了, 用elastic账号登录, 还可以设置其他几个账号的权限了.三, 运行Logstashlogstash -f [指定配置文件的路径.conf] ...

February 14, 2019 · 1 min · jiezi

elasticsearch配置

elasticsearch是一款知名的开源全文搜索引擎,应用广泛,因项目需要,需要使用elasticsearch满足应用内搜索,地图搜索。目前还在线上试运营,根据自己的使用部署过程,分享一下经验,梳理一下踩过的坑。1. java环境最低版本java-jdk 1.8,我本地选择jdk-1.8.0_191.jdk安装完成之后配置环境变量查看java版本java -versionopenjdk version “1.8.0_191"OpenJDK Runtime Environment (build 1.8.0_191-b12)OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode)2. eleasticsearch版本选择eleasticsearch 6.5.2 ,最新稳定版本,选择最新稳定版虽然用的人不多,but who care下载eleasticsearchhttps://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.5.2.tar.gz安装配置项目配置到:data/目录root @ localhost in /data/elasticsearch-6.5.2 [17:18:23]$ l总用量 4.8Mdrwxrwxr-x 9 euser euser 198 12月 11 11:26 .drwxr-xr-x. 7 root root 90 1月 16 16:35 ..drwxrwxr-x 3 euser euser 4.0K 12月 11 11:13 bindrwxrwxr-x 2 euser euser 178 12月 11 11:32 configdrwxrwxr-x 3 euser euser 19 12月 11 11:25 data-rwxrwxr-x 1 euser euser 4.3M 12月 6 22:30 elasticsearch-analysis-ik-6.5.2.zipdrwxrwxr-x 3 euser euser 4.0K 11月 30 08:02 lib-rwxrwxr-x 1 euser euser 14K 11月 30 07:55 LICENSE.txtdrwxrwxrwx 2 euser euser 8.0K 2月 11 01:30 logsdrwxrwxr-x 28 euser euser 4.0K 11月 30 08:02 modules-rwxrwxr-x 1 euser euser 395K 11月 30 08:01 NOTICE.txtdrwxrwxr-x 3 euser euser 25 12月 11 11:29 plugins-rwxrwxr-x 1 euser euser 8.4K 11月 30 07:55 README.textile配置data:/data/elasticsearch//data配置log:/data/elasticsearch/logs配置其他项说明遇到的错误说明3. 添加用户用户组,以守护进程启动不能使用root用户启动 添加新用户useradd euser添加用户到用户组groupadd euser -g euser切换到对应的用户sudo euser以守护进程启动./data/elasticsearch/bin/elasticsearch -d其他错误根据情况自己配置解决 ...

February 11, 2019 · 1 min · jiezi

Elastic Search 学习笔记

Reference6.4最新版英文:https://www.elastic.co/guide/…中文:https://www.elastic.co/guide/…5.4中文:http://cwiki.apachecn.org/pag…DefinationDSL(Domain Specific Language):Elasticsearch 定义的查询语言ES字段类型:https://blog.csdn.net/chengyu…APIStats API:获取索引统计信息(http://cwiki.apachecn.org/pag…)GET es-index_/_stats{ “_shards”: { “total”: 622, “successful”: 622, “failed”: 0 }, //返回的统计信息是索引级的聚合结果,具有primaries和total的聚合结果。其中primaries只是主分片的值,total是主分片和副本分片的累积值。 “all”: { “primaries”: { “docs”: { //文档和已删除文档(尚未合并的文档)的数量。注意,此值受刷新索引的影响。 “count”: 2932357017, “deleted”: 86610 }, “store”: { //索引的大小。 “size_in_bytes”: 2573317479532, }, “indexing”: {}, //索引统计信息,可以用逗号分隔的type列表组合,以提供文档级统计信息。 “get”: {}, // get api调用统计 “search”: {}, // search api 调用统计 }, “total”: { } }}Search API(两种形式)using a simple query string as a parameterGET es-index/search?q=eventid:OMGH5PageViewusing a request bodyGET es-index/_search{ “query”: { “term”: { “eventid”: { “value”: “OMGH5PageView” } } }}Query DSLLeaf Query Clause: 叶查询子句Compound Query Clause: 复合查询子句DSL查询上下文query context在查询上下文中,回答的问题是:How well does this document match this query clause?除了判断一条数据记录(document)是否匹配查询条件以外,还要计算其相对于其他记录的匹配程度,通过_score进行记录。filter context在查询上下文中,回答的问题是:Does this document match this query clause?仅判断document是否匹配,不计算_score一般用来过滤结构化数据, e.g. timestamp是否在2017-2018范围内,status是否是published频繁使用的过滤器会被Elasticsearch自动缓存,可提高性能 查询时,可先使用filter过滤操作过滤数据,然后使用query查询匹配数据查询结果字段过滤fields:字段过滤 script_fields:可对原始数据进行计算"fields": [“eh”], //仅返回eh字段"script_fields": { “test”: { “script”: “doc[’eh’].value2” }} // 返回eh字段值2的数据并命名为test字段查询过滤:querybool 组合过滤器{ “bool” : { “must” : {}, // 所有的语句都必须匹配,相当于SQL中的and “must_not” : {}, // 所有的语句都不能匹配,相当于SQL中的not “should” : {}, // 至少有一个语句要匹配,相当于SQL中的OR “filter” : {}, // }}filtered过滤器{ “filtered”: { “query”: {}, “filter”: {} // 在filter中进行数据过滤,然后再去query中进行匹配 }}match和termmatch(模糊匹配):先检查字段类型是否是analyzed,如果是,则先分词,再去去匹配token;如果不是,则直接去匹配token。term(精确匹配):直接去匹配token。terms: 多项查询{ terms : { user: [’tony’, ‘kitty’ ] } }range范围过滤对于date类型字段的范围选择可以使用 Date Math{ “range” : { “born” : { “gte”: “01/01/2012”, “lte”: “2013”, “format”: “dd/MM/yyyy||yyyy” } } }{ “range” : { “timestamp” : { “gte”: “now-6d/d”, // Date Math “lte”: “now/d”, // Date Math “time_zone”: “+08:00” // 时区 } } }exists 该条记录是否存在某个字段{ “exists” : { “field” : “user” }}wildcard: 通配符查询(对分词进行匹配查询)Note that this query can be slow, as it needs to iterate over many terms. In order to prevent extremely slow wildcard queries, a wildcard term should not start with one of the wildcards * or ?wildcard查询性能较差,尽量避免使用或?开头来进行wildcard匹配prefix: 前缀查询regexp: 正则表达式查询Tipsvalue带-的特殊处理value带了-,则默认会被切词,导致搜索结果不准确。解决办法之一就是在字段那里加个.rawterm: {status:‘pre-active’} => term: {status.raw: ‘pre-active’}sortGET es-index_*/_search{ “fields” : [“eventid”, “logtime”], “query”: { “term”: { “eventid”: { “value”: “OMGH5PageView” } } }, “sort”: [ { “logtime”: { “order”: “asc” } } ]}聚合aggregationdate_histogram(和 histogram 一样)默认只会返回文档数目非零的 buckets。 即使 buckets中没有文档我们也想返回。可以通过设置两个额外参数来实现这种效果:“min_doc_count” : 0, // 这个参数强制返回空 buckets。“extended_bounds” : { // 强制返回整年 “min” : “2014-01-01”, “max” : “2014-12-31”}查询返回结果参数took: 查询返回的时间(单位:毫秒)time_out: 查询是否超时_shards: 描述查询分片的信息,包括:查询了多少分片,成功的分片数量,失败的分片数量等hits:搜索的结果total: 满足查询条件的文档数max_score: hits: 满足条件的文档_score: 文档的匹配程度 ...

February 2, 2019 · 2 min · jiezi

Centos7 搭建Skywalking 6.0系列第一篇-Elasticsearch6.6集群搭建

随着Skywalking 6.0 GA版本的发布,Skywalking终于支持Elasticsearch 6.x版本,本文作为搭建Skywalking 6.0集群系列文章的第一篇介绍下Elasticsearch 6.6集群的搭建。1. 准备1.1 节点规划IPcluster.namenode.name10.130.10.11es_loges_110.130.10.12es_loges_210.130.10.13es_loges_31.2 安装Java运行环境JREwget -c -P /tmp https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u202-b08/OpenJDK8U-jre_x64_linux_hotspot_8u202b08.tar.gzmkdir /usr/java;\tar xf /tmp/OpenJDK8U-jre_x64_linux_hotspot_8u202b08.tar.gz -C /usr/java;\rm -rf /usr/java/default;\ln -s /usr/java/jdk8u202-b08-jre /usr/java/default;\tee -a /etc/profile << ‘EOF’export JAVA_HOME=/usr/java/defaultexport PATH=$JAVA_HOME/bin:$PATHEOF2. 安装2.1 导入Elasticserach软件源rpm –import https://artifacts.elastic.co/GPG-KEY-elasticsearch;\tee /etc/yum.repos.d/elasticsearch.repo << ‘EOF’[elasticsearch-6.x]name=Elasticsearch repository for 6.x packagesbaseurl=https://artifacts.elastic.co/packages/6.x/yumgpgcheck=1gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearchenabled=1autorefresh=1type=rpm-mdEOF2.2 安装软件yum install elasticsearch -y2.3 配置LimitLimitMEMLOCK开启内存锁定LimitNPROC最大进程数,系统支持的最大进程数:32768查看系统最大支持进程数:cat /proc/sys/kernel/pid_maxLimitNOFILE打开文件数,系统默认最大文件描述符:791020查看系统最大文件描述符:cat /proc/sys/fs/file-maxmkdir /etc/systemd/system/elasticsearch.service.d;\cat > /etc/systemd/system/elasticsearch.service.d/override.conf << ‘EOF’[Service]Environment=JAVA_HOME=/usr/java/default LimitMEMLOCK=infinityLimitNOFILE=204800LimitNPROC=4096EOF2.4 配置JVM(可选)默认是2G不要超过可用 RAM 的 50%Lucene 能很好利用文件系统的缓存,它是通过系统内核管理的。如果没有足够的文件系统缓存空间,性能会受到影响。 此外,专用于堆的内存越多意味着其他所有使用 doc values 的字段内存越少不要超过 32 GB如果堆大小小于 32 GB,JVM 可以利用指针压缩,这可以大大降低内存的使用:每个指针 4 字节而不是 8 字节sed -i ‘/-Xms2g/c-Xms3g’ /etc/elasticsearch/jvm.options;\sed -i ‘/-Xmx2g/c-Xmx3g’ /etc/elasticsearch/jvm.options2.5 配置elasticsearch.ymlcluster.name集群名称,默认是elasticsearch,建议修改为更明确的名称,比如es_lognode.name节点名,默认随机指定一个name列表中名字,建议修改为明确的名称,比如es_1,es_2,es_3network.host主机IPpath.data数据目录path.logs日志目录discovery.zen.ping.unicast.hosts节点发现2.5.1 所有节点执行相同的命令mkdir -p /home/elasticsearch/data /home/elasticsearch/logs;\chown -R elasticsearch. /home/elasticsearch;\sed -i ‘/cluster.name/c\cluster.name: es_log’ /etc/elasticsearch/elasticsearch.yml;\sed -i ‘/network.host/c\network.host: 0.0.0.0’ /etc/elasticsearch/elasticsearch.yml;\sed -i ‘/path.data/c\path.data: /home/elasticsearch/data’ /etc/elasticsearch/elasticsearch.yml;\sed -i ‘/path.logs/c\path.logs: /home/elasticsearch/logs’ /etc/elasticsearch/elasticsearch.yml;\sed -i ‘/discovery.zen.ping.unicast.hosts/c\discovery.zen.ping.unicast.hosts: [“10.130.10.11”,“10.130.10.12”,“10.130.10.13”]’ /etc/elasticsearch/elasticsearch.yml2.5.2 各个节点执行对应的命令#节点-1sed -i ‘/node.name/c\node.name: es_1’ /etc/elasticsearch/elasticsearch.yml#节点-2sed -i ‘/node.name/c\node.name: es_2’ /etc/elasticsearch/elasticsearch.yml#节点-3sed -i ‘/node.name/c\node.name: es_3’ /etc/elasticsearch/elasticsearch.yml2.6 启动systemctl enable elasticsearch;\systemctl daemon-reload;\systemctl start elasticsearch;\systemctl status elasticsearch2.7 配置防火墙firewall-cmd –add-port=9200/tcp –permanent;\firewall-cmd –add-port=9300/tcp –permanent;\firewall-cmd –reload3. 安装中文分词插件(可选)#查看已安装插件/usr/share/elasticsearch/bin/elasticsearch-plugin list#安装IK/usr/share/elasticsearch/bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.6.0/elasticsearch-analysis-ik-6.6.0.zip4. 查询#查看节点信息curl -X GET http://localhost:9200/_nodes#打开文件数信息curl -X GET http://localhost:9200/_nodes/stats/process?filter_path=**.max_file_descriptors#集群健康状态curl -X GET http://localhost:9200/_cat/health?v#查看集群索引数curl -X GET http://localhost:9200/_cat/indices?v#查看磁盘分配情况curl -X GET http://localhost:9200/_cat/allocation?v#查看集群节点curl -X GET http://localhost:9200/_cat/nodes?v#查看集群其他信息curl -X GET http://localhost:9200/_cat ...

January 31, 2019 · 1 min · jiezi

HBase 2.0 协处理器实现 ES 数据同步

标签:hbase 2.0、elasticsearch、Coprocessor、协处理器在正式进行讲述实现之前,我觉得有必要说一下出发点。团队期初数据都是基于 HBase+Phoenix 这样架构进行持久化。随着业务的复杂性增加,对部分表的查询效率和查询条件多样性,提出了更高的要求。HBase+Phoenix 就会出现索引滥用。变更索引变的特别的频繁,同时一些数据客观的表,变更索引的代价是非常大的。在海量数据的查询方面,Elasticsearch 具有出色的性能。如果 HBase+ES 是不是会是更好的解决方法呢?其实,这个时候会有一个思考点,Phoenix 是如何实现二级索引的?HBase 协处理器(Coprocessor) 。我的实现过程比较曲折,后文中也会提到,以帮助大家避免这些坑。在过程中,还尝试了另一种实现方案。存放两份数据,一份 HBase,一份 ES。该方案需要解决的一个问题——数据一致性问题,但这个问题协处理器可以解决。在此过程中,由于不当操作,把 HBase 服务宕机了,现象是 REGION SERVERS 无法启动,只有通过硬删的方式解决。出于不死心,在经历重装 HBase 之后。内心又开始蠢蠢欲动。首先要声明一下,我们团队的环境是 HDP 3.0、HBase 2.0 ,网上很多教程都是基于 1.X,2.X 与 1.X 区别还是挺大的。RegionObserver 从继承方式改为了面向接口编程。协处理器没有选择协处理情况下,HBase 实现 RDBMS SQL 方式查询数据,大量的 Filter 需要在客户端进行编码完成,代码的臃肿,可维护性大大降低。如果这部分操作在服务器端完成,是否是更好的选择呢。协处理就能帮助实现该设想,由于在服务端完成,可以集中式优化查询,降低请求的带宽和提高查询效率。当然,对 HBase 性能产生了一定影响。类型ObserverEndpointObserverObserver 协处理器类似于 RDBMS 中的触发器,当事件触发的时候该类协处理器会被 Server 端调用。EndpointEndpoint 协处理器类似传统数据库中的存储过程,完成一些聚合操作。实现基础尝试避免 ES 连接操作、代码复杂性导致的 Bug,在最初只通过打日志的方式来验证协处理方式。代码实现概览HbaseDataSyncEsObserver.javapackage com.tairanchina.csp.dmp.examples;import org.apache.hadoop.hbase.CoprocessorEnvironment;import org.apache.hadoop.hbase.client.Delete;import org.apache.hadoop.hbase.client.Durability;import org.apache.hadoop.hbase.client.Put;import org.apache.hadoop.hbase.coprocessor.ObserverContext;import org.apache.hadoop.hbase.coprocessor.RegionCoprocessor;import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;import org.apache.hadoop.hbase.coprocessor.RegionObserver;import org.apache.hadoop.hbase.wal.WALEdit;import org.apache.log4j.Logger;import java.io.IOException;import java.util.Optional;public class HbaseDataSyncEsObserver implements RegionObserver, RegionCoprocessor { private static final Logger LOG = Logger.getLogger(HbaseDataSyncEsObserver.class); public Optional<RegionObserver> getRegionObserver() { return Optional.of(this); } public void start(CoprocessorEnvironment env) throws IOException { LOG.info("====Test Start===="); } public void stop(CoprocessorEnvironment env) throws IOException { LOG.info("====Test End===="); } public void postPut(ObserverContext<RegionCoprocessorEnvironment> e, Put put, WALEdit edit, Durability durability) throws IOException { LOG.info("====Test postPut===="); } public void postDelete(ObserverContext<RegionCoprocessorEnvironment> e, Delete delete, WALEdit edit, Durability durability) throws IOException { LOG.info("====Test postDelete===="); }}pom.xml<?xml version=“1.0” encoding=“UTF-8”?><project xmlns=“http://maven.apache.org/POM/4.0.0" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.tairanchina.csp.dmp</groupId> <artifactId>hbase-observer-simple-example</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>jar</packaging> <name>HBase Observer Simple 用例</name> <properties> <hbase.version>2.0.0</hbase.version> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.deploy.skip>true</maven.deploy.skip> <maven.install.skip>true</maven.install.skip> </properties> <dependencies> <dependency> <groupId>org.apache.hbase</groupId> <artifactId>hbase-client</artifactId> <version>${hbase.version}</version> <exclusions> <exclusion> <artifactId>jetty-servlet</artifactId> <groupId>org.eclipse.jetty</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-hdfs-client</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>org.apache.hbase</groupId> <artifactId>hbase-server</artifactId> <version>${hbase.version}</version> <exclusions> <exclusion> <artifactId>javax.servlet.jsp</artifactId> <groupId>org.glassfish.web</groupId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>${java.version}</source> <target>${java.version}</target> <encoding>${project.build.sourceEncoding}</encoding> </configuration> </plugin> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifest> <mainClass></mainClass> </manifest> </archive> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build></project>包处理打包mvn clean assembly:assembly -Dmaven.test.skip=true这里 package 得到的包必须是将依赖都包含在内的,否则,会报类找不到之类的错误。上传包的时候,需要上传到 HDFS 下,同时,要给 hbase 用户授予权限,因而,我在测试的过程中,将其上传到 /apps/hbase 下(HDP 环境)。由于包名太长,这里对包名进行了重命名。装载协处理器# 创建测试表create ‘gejx_test’,‘cf’# 停用测试表disable ‘gejx_test’# 表与协处理器建立关系alter ‘gejx_test’ , METHOD =>’table_att’,‘coprocessor’=>‘hdfs://dev-dmp2.fengdai.org:8020/apps/hbase/hbase-observer-simple-example.jar|com.tairanchina.csp.dmp.examples.HbaseDataSyncEsObserver|1073741823’# 启用表enable ‘gejx_test’# 查看表信息desc ‘gejx_test’测试put ‘gejx_test’, ‘2’,‘cf:name’,‘gjx1’delete ‘gejx_test’, ‘2’,‘cf:name’查看日志要先在 HBase Master UI 界面下,确定数据存储在哪个节点上,再到相应的节点下面的 /var/log/hbase 下查看日志tail -100f hbase-hbase-regionserver-test.example.org.out卸载协处理器disable ‘gejx_test’alter ‘gejx_test’, METHOD => ’table_att_unset’, NAME => ‘coprocessor$1’enable ‘gejx_test’以上,已经完成最基础的协处理器实现。接下来进行讲述 ES 的一种实现方案。HBase+ES这里为了快速论证结果,在编码方面采用了硬编码方式,希望理解。代码实现概览ElasticSearchBulkOperator.javapackage com.tairanchina.csp.dmp.examples;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.elasticsearch.action.bulk.BulkRequestBuilder;import org.elasticsearch.action.bulk.BulkResponse;import org.elasticsearch.action.delete.DeleteRequestBuilder;import org.elasticsearch.action.support.WriteRequest;import org.elasticsearch.action.update.UpdateRequestBuilder;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * Created on 2019/1/11. * * @author 迹_Jason /public class ElasticSearchBulkOperator { private static final Log LOG = LogFactory.getLog(ElasticSearchBulkOperator.class); private static final int MAX_BULK_COUNT = 10000; private static BulkRequestBuilder bulkRequestBuilder = null; private static final Lock commitLock = new ReentrantLock(); private static ScheduledExecutorService scheduledExecutorService = null; static { // init es bulkRequestBuilder bulkRequestBuilder = ESClient.client.prepareBulk(); bulkRequestBuilder.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); // init thread pool and set size 1 scheduledExecutorService = Executors.newScheduledThreadPool(1); // create beeper thread( it will be sync data to ES cluster) // use a commitLock to protected bulk es as thread-save final Runnable beeper = () -> { commitLock.lock(); try { bulkRequest(0); } catch (Exception ex) { System.out.println(ex.getMessage()); } finally { commitLock.unlock(); } }; // set time bulk task // set beeper thread(10 second to delay first execution , 30 second period between successive executions) scheduledExecutorService.scheduleAtFixedRate(beeper, 10, 30, TimeUnit.SECONDS); } public static void shutdownScheduEx() { if (null != scheduledExecutorService && !scheduledExecutorService.isShutdown()) { scheduledExecutorService.shutdown(); } } private static void bulkRequest(int threshold) { if (bulkRequestBuilder.numberOfActions() > threshold) { BulkResponse bulkItemResponse = bulkRequestBuilder.execute().actionGet(); if (!bulkItemResponse.hasFailures()) { bulkRequestBuilder = ESClient.client.prepareBulk(); } } } /* * add update builder to bulk * use commitLock to protected bulk as thread-save * @param builder / public static void addUpdateBuilderToBulk(UpdateRequestBuilder builder) { commitLock.lock(); try { bulkRequestBuilder.add(builder); bulkRequest(MAX_BULK_COUNT); } catch (Exception ex) { LOG.error(” update Bulk " + “gejx_test” + " index error : " + ex.getMessage()); } finally { commitLock.unlock(); } } /* * add delete builder to bulk * use commitLock to protected bulk as thread-save * * @param builder / public static void addDeleteBuilderToBulk(DeleteRequestBuilder builder) { commitLock.lock(); try { bulkRequestBuilder.add(builder); bulkRequest(MAX_BULK_COUNT); } catch (Exception ex) { LOG.error(” delete Bulk " + “gejx_test” + " index error : " + ex.getMessage()); } finally { commitLock.unlock(); } }}ESClient.javapackage com.tairanchina.csp.dmp.examples;/* * Created on 2019/1/10. * * @author 迹_Jason /import org.elasticsearch.client.Client;import org.elasticsearch.common.settings.Settings;import org.elasticsearch.common.transport.TransportAddress;import org.elasticsearch.transport.client.PreBuiltTransportClient;import java.net.InetAddress;import java.net.UnknownHostException;/* * ES Cleint class /public class ESClient { public static Client client; /* * init ES client / public static void initEsClient() throws UnknownHostException { System.setProperty(“es.set.netty.runtime.available.processors”, “false”); Settings esSettings = Settings.builder().put(“cluster.name”, “elasticsearch”).build();//设置ES实例的名称 client = new PreBuiltTransportClient(esSettings).addTransportAddress(new TransportAddress(InetAddress.getByName(“localhost”), 9300)); } /* * Close ES client / public static void closeEsClient() { client.close(); }}HbaseDataSyncEsObserver.javapackage com.tairanchina.csp.dmp.examples;import org.apache.hadoop.hbase.Cell;import org.apache.hadoop.hbase.CellUtil;import org.apache.hadoop.hbase.CoprocessorEnvironment;import org.apache.hadoop.hbase.client.Delete;import org.apache.hadoop.hbase.client.Durability;import org.apache.hadoop.hbase.client.Put;import org.apache.hadoop.hbase.coprocessor.ObserverContext;import org.apache.hadoop.hbase.coprocessor.RegionCoprocessor;import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;import org.apache.hadoop.hbase.coprocessor.RegionObserver;import org.apache.hadoop.hbase.util.Bytes;import org.apache.hadoop.hbase.wal.WALEdit;import org.apache.log4j.Logger;import java.io.IOException;import java.util.;/** * Created on 2019/1/10. * * @author 迹_Jason /public class HbaseDataSyncEsObserver implements RegionObserver , RegionCoprocessor { private static final Logger LOG = Logger.getLogger(HbaseDataSyncEsObserver.class); public Optional<RegionObserver> getRegionObserver() { return Optional.of(this); } @Override public void start(CoprocessorEnvironment env) throws IOException { // init ES client ESClient.initEsClient(); LOG.info(”init start"); } @Override public void stop(CoprocessorEnvironment env) throws IOException { ESClient.closeEsClient(); // shutdown time task ElasticSearchBulkOperator.shutdownScheduEx(); LOG.info("end*"); } @Override public void postPut(ObserverContext<RegionCoprocessorEnvironment> e, Put put, WALEdit edit, Durability durability) throws IOException { String indexId = new String(put.getRow()); try { NavigableMap<byte[], List<Cell>> familyMap = put.getFamilyCellMap(); Map<String, Object> infoJson = new HashMap<>(); Map<String, Object> json = new HashMap<>(); for (Map.Entry<byte[], List<Cell>> entry : familyMap.entrySet()) { for (Cell cell : entry.getValue()) { String key = Bytes.toString(CellUtil.cloneQualifier(cell)); String value = Bytes.toString(CellUtil.cloneValue(cell)); json.put(key, value); } } // set hbase family to es infoJson.put(“info”, json); LOG.info(json.toString()); ElasticSearchBulkOperator.addUpdateBuilderToBulk(ESClient.client.prepareUpdate(“gejx_test”,“dmp_ods”, indexId).setDocAsUpsert(true).setDoc(json)); LOG.info("**** postPut success*****"); } catch (Exception ex) { LOG.error(“observer put a doc, index [ " + “gejx_test” + " ]” + “indexId [” + indexId + “] error : " + ex.getMessage()); } } @Override public void postDelete(ObserverContext<RegionCoprocessorEnvironment> e, Delete delete, WALEdit edit, Durability durability) throws IOException { String indexId = new String(delete.getRow()); try { ElasticSearchBulkOperator.addDeleteBuilderToBulk(ESClient.client.prepareDelete(“gejx_test”, “dmp_ods”, indexId)); LOG.info(”**** postDelete success*****"); } catch (Exception ex) { LOG.error(ex); LOG.error(“observer delete a doc, index [ " + “gejx_test” + " ]” + “indexId [” + indexId + “] error : " + ex.getMessage()); } }}其他方面的操作与上文操作类似,这里不再进行缀诉,直接看 Kibana 结果。讲在最后上文中 HBase+ES 实现方案是在 HBase 和 ES 各自存放一份数据,使用协处理器达到数据一致性。这种方案存在数据冗余问题,在 ES 这边需要准备大量的存储空间。还有一种方案也是比较流行的。使用 ES 作为二级索引的实现。使用协处理将需要查询的表查询字段与 RowKey 关系保存到 ES,查询数据的时候,先根据条件查询 ES 得到 RowKey,通过得到的 RowKey 查询 HBase 数据。以提高查询的效率。Anyway,这两种方案都需要解决历史数据的问题和还有需要注意数据更新操作。Q&A遇到 None of the configured nodes are available 错误信息?请检查一下 ES 的 cluster.name 配置是否错误。为什么Hbase 2.0 Observer 未生效?HBase 2.0 中 observer 接口有变化。你需要实现 RegionCoprocessor 的 getRegionObserver 接口。发现已经更新包,协处理器还是在执行历史代码?当更新包的时候,要进行包名的变更,否则,可能会出现类似于缓存的现象问题。待确认[ ] 未停用的情况下,更新 jar(已测试未操作表的时候,支持更新)[ ] 测试多张表公用同一个 jar引文使用Hbase协作器(Coprocessor)同步数据到ElasticSearch面向高稳定,高性能之-Hbase数据实时同步到ElasticSearch(之二)使用HBase CoprocessorHBase 源码更多内容可以关注微信公众号,或者访问AppZone网站 ...

January 30, 2019 · 5 min · jiezi

关于ES NoNodeAvailableException 原因一种及解决

问题服务器上部署了一个应用 需要连接ES 配置的是内网地址<elasticsearch:transport-client id=“client” cluster-nodes=“10.28.70.38:9300” cluster-name=“foo”/>telnet 10.28.70.38 9300 OK 但是实际调用接口的时候 还是报了下面的错NoNodeAvailableException[None of the configured nodes are available: [{#transport#-1}{10.28.70.38}{10.28.70.38:9300}]] at org.elasticsearch.client.transport.TransportClientNodesService.ensureNodesAreAvailable(TransportClientNodesService.java:290)感觉有点奇怪? 内网地址是通的啊 怎么还报了这样的错呢?执行 lsof -p pid 发现存在下面这样的连接 变成了连ES的外网地址(106.14.XXX.XXX)了呢?但该服务器连ES外网地址不通java 15847 root 74u IPv4 1131084443 0t0 TCP 139.196.XXX.XXX:36706->106.14.XXX.XXX:vrace (SYN_SENT)最后定位到的原因是 elasticsearchTemplate的配置项中有"client.transport.sniff" -> “true"而client.transport.sniff的含义是The Transport client comes with a cluster sniffing feature which allows it to dynamically add new hosts and remove old ones. When sniffing is enabled, the transport client will connect to the nodes in its internal node list, which is built via calls to addTransportAddress. After this, the client will call the internal cluster state API on those nodes to discover available data nodes. The internal node list of the client will be replaced with those data nodes only. This list is refreshed every five seconds by default. Note that the IP addresses the sniffer connects to are the ones declared as the publish address in those node’s Elasticsearch config.参考文档: https://www.elastic.co/guide/…解决显式将client-transport-sniff置为false<elasticsearch:transport-client id=“client” cluster-nodes=“10.28.70.38:9300” cluster-name=“foo” client-transport-sniff=“false”/>此时执行lsof -p pidjava 32650 root 271u IPv4 1131368694 0t0 TCP iZ11jteew8eZ:57671->10.28.70.38:vrace (ESTABLISHED)或者也可以修改网络配置 使得访问ES外网也是通的 ...

January 26, 2019 · 1 min · jiezi

Golang操作elasticsearch(一)

Golang操作elasticsearch使用第三方包:olivere github。总结一下olivere操作ES的常用功能,方便查阅。说明:以下例子用到的es index:“test”, es type:“test”, es address: “http://10.1.1.1:9200"新建es clientfunc ESClient() (client *elastic.Client,err error){ file := “./log.log” logFile, _ := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0766) // 应该判断error,此处简略 cfg := []elastic.ClientOptionFunc{ elastic.SetURL(“http://10.1.1.1:9200”), elastic.SetSniff(false), elastic.SetInfoLog(log.New(logFile, “ES-INFO: “, 0)), elastic.SetTraceLog(log.New(logFile, “ES-TRACE: “, 0)), elastic.SetErrorLog(log.New(logFile, “ES-ERROR: “, 0)), } client,err =elastic.NewClient(cfg …) return}查看某文档是否存在,给定文档ID查询func isExists(id string)bool{ client,_ := ESClient() defer client.Stop() exist,_ := client.Exists().Index(“test”).Type(“test”).Id(id).Do(context.Background()) if !exist{ log.Println(“ID may be incorrect! “,id) return false } return true}获取某文档的内容func(doc *myDocument) Get(id string){ client ,:= ESClient() defer client.Stop() if !isExists(id){ return } esResponse,err := client.Get().Index(“test”).Type(“test”).Id(id).Do(context.Background()) if err != nil { // Handle Error return } json.Unmarshal(*esResponse.Source,&doc)}新增文档func(doc *myDocument) Add(id string){ client ,:= ESClient() defer client.Stop() if !isExists(id){ return } client.Index().Index(“test”).Type(“test”).Id(id).BodyJson(doc).Do(context.Background())}批量新增func BulkAdd(docs []myDocument){ bulkRequest := seriesClient.Bulk() client ,:= ESClient() defer client.Stop() for ,doc := range docs{ esRequest := elastic.NewBulkIndexRequest().Index(“test”).Type(“test”).Id(id).Doc(doc) bulkRequest = bulkRequest.Add(esRequest) } bulkRequest.Do(context.Background())}更新文档func Update(updateField *map[string]interface{},id string){ client ,:= ESClient() defer client.Stop() if !isExists(id){ return } ,err:=client.Update().Index(“test”).Type(“test”).Id(id).Doc(updateField).Do(context.Background()) if err != nil{ //Handle Error }}删除文档func Delete(id string){ client ,:= ESClient() defer client.Stop() if !isExists(id){ return } ,err:=client.Delete().Index(“test”).Type(“test”).Id(id).Do(context.Background()) if err != nil{ //Handle Error }}搜索文档(搜索是ES非常引以为傲的功能,以下示例是相对复杂的查询条件,需将查询结果排序,按页返回)func Search(criteria *SearchCriteria)(docs []myDocument){ client ,:= ESClient() defer client.Stop() query := elastic.NewBoolQuery() query = query.Must(elastic.NewTermQuery(“category”,criteria.Category)) query=query.Must(elastic.NewMatchQuery(“title”,criteria.Title)) query=query.Must(elastic.NewRangeQuery(“update_timestamp”).Gte(criteria.UpdateTime)) esResponse, :=client.Search().Index(“test”).Type(“test”). Query(query).Sort(criteria.Sort,criteria.Order==“ASC”||criteria.Order==“asc”). From(criteria.Offset).Size(criteria.Limit).Do(context.Background()) for _,value:= range esResponse.Hits.Hits{ var doc *myDocument json.Unmarshal(*value.Source,&doc) docs = append(docs,doc) } return}type SearchCriteria struct{ Category string json:"category" Limit int json:"limit" Offset int json:"offset" Title string json:"title" UpdateTime int64 json:"update_timestamp" Order string json:"order" Sort string json:"sort"} ...

January 25, 2019 · 2 min · jiezi

《从0到1学习Flink》—— Flink 写入数据到 ElasticSearch

前言前面 FLink 的文章中我们已经介绍了说 Flink 已经有很多自带的 Connector。1、[《从0到1学习Flink》—— Data Source 介绍](http://www.54tianzhisheng.cn/...2、《从0到1学习Flink》—— Data Sink 介绍其中包括了 Source 和 Sink 的,后面我也讲了下如何自定义自己的 Source 和 Sink。那么今天要做的事情是啥呢?就是介绍一下 Flink 自带的 ElasticSearch Connector,我们今天就用他来做 Sink,将 Kafka 中的数据经过 Flink 处理后然后存储到 ElasticSearch。准备安装 ElasticSearch,这里就忽略,自己找我以前的文章,建议安装 ElasticSearch 6.0 版本以上的,毕竟要跟上时代的节奏。下面就讲解一下生产环境中如何使用 Elasticsearch Sink 以及一些注意点,及其内部实现机制。Elasticsearch Sink添加依赖<dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-connector-elasticsearch6_${scala.binary.version}</artifactId> <version>${flink.version}</version></dependency>上面这依赖版本号请自己根据使用的版本对应改变下。下面所有的代码都没有把 import 引入到这里来,如果需要查看更详细的代码,请查看我的 GitHub 仓库地址:https://github.com/zhisheng17/flink-learning/tree/master/flink-learning-connectors/flink-learning-connectors-es6这个 module 含有本文的所有代码实现,当然越写到后面自己可能会做一些抽象,所以如果有代码改变很正常,请直接查看全部项目代码。ElasticSearchSinkUtil 工具类这个工具类是自己封装的,getEsAddresses 方法将传入的配置文件 es 地址解析出来,可以是域名方式,也可以是 ip + port 形式。addSink 方法是利用了 Flink 自带的 ElasticsearchSink 来封装了一层,传入了一些必要的调优参数和 es 配置参数,下面文章还会再讲些其他的配置。ElasticSearchSinkUtil.javapublic class ElasticSearchSinkUtil { /** * es sink * * @param hosts es hosts * @param bulkFlushMaxActions bulk flush size * @param parallelism 并行数 * @param data 数据 * @param func * @param <T> / public static <T> void addSink(List<HttpHost> hosts, int bulkFlushMaxActions, int parallelism, SingleOutputStreamOperator<T> data, ElasticsearchSinkFunction<T> func) { ElasticsearchSink.Builder<T> esSinkBuilder = new ElasticsearchSink.Builder<>(hosts, func); esSinkBuilder.setBulkFlushMaxActions(bulkFlushMaxActions); data.addSink(esSinkBuilder.build()).setParallelism(parallelism); } /* * 解析配置文件的 es hosts * * @param hosts * @return * @throws MalformedURLException */ public static List<HttpHost> getEsAddresses(String hosts) throws MalformedURLException { String[] hostList = hosts.split(","); List<HttpHost> addresses = new ArrayList<>(); for (String host : hostList) { if (host.startsWith(“http”)) { URL url = new URL(host); addresses.add(new HttpHost(url.getHost(), url.getPort())); } else { String[] parts = host.split(":", 2); if (parts.length > 1) { addresses.add(new HttpHost(parts[0], Integer.parseInt(parts[1]))); } else { throw new MalformedURLException(“invalid elasticsearch hosts format”); } } } return addresses; }}Main 启动类Main.javapublic class Main { public static void main(String[] args) throws Exception { //获取所有参数 final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); //准备好环境 StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); //从kafka读取数据 DataStreamSource<Metrics> data = KafkaConfigUtil.buildSource(env); //从配置文件中读取 es 的地址 List<HttpHost> esAddresses = ElasticSearchSinkUtil.getEsAddresses(parameterTool.get(ELASTICSEARCH_HOSTS)); //从配置文件中读取 bulk flush size,代表一次批处理的数量,这个可是性能调优参数,特别提醒 int bulkSize = parameterTool.getInt(ELASTICSEARCH_BULK_FLUSH_MAX_ACTIONS, 40); //从配置文件中读取并行 sink 数,这个也是性能调优参数,特别提醒,这样才能够更快的消费,防止 kafka 数据堆积 int sinkParallelism = parameterTool.getInt(STREAM_SINK_PARALLELISM, 5); //自己再自带的 es sink 上一层封装了下 ElasticSearchSinkUtil.addSink(esAddresses, bulkSize, sinkParallelism, data, (Metrics metric, RuntimeContext runtimeContext, RequestIndexer requestIndexer) -> { requestIndexer.add(Requests.indexRequest() .index(ZHISHENG + “_” + metric.getName()) //es 索引名 .type(ZHISHENG) //es type .source(GsonUtil.toJSONBytes(metric), XContentType.JSON)); }); env.execute(“flink learning connectors es6”); }}配置文件配置都支持集群模式填写,注意用 , 分隔!kafka.brokers=localhost:9092kafka.group.id=zhisheng-metrics-group-testkafka.zookeeper.connect=localhost:2181metrics.topic=zhisheng-metricsstream.parallelism=5stream.checkpoint.interval=1000stream.checkpoint.enable=falseelasticsearch.hosts=localhost:9200elasticsearch.bulk.flush.max.actions=40stream.sink.parallelism=5运行结果执行 Main 类的 main 方法,我们的程序是只打印 flink 的日志,没有打印存入的日志(因为我们这里没有打日志):所以看起来不知道我们的 sink 是否有用,数据是否从 kafka 读取出来后存入到 es 了。你可以查看下本地起的 es 终端或者服务器的 es 日志就可以看到效果了。es 日志如下:上图是我本地 Mac 电脑终端的 es 日志,可以看到我们的索引了。如果还不放心,你也可以在你的电脑装个 kibana,然后更加的直观查看下 es 的索引情况(或者直接敲 es 的命令)我们用 kibana 查看存入 es 的索引如下:程序执行了一会,存入 es 的数据量就很大了。扩展配置上面代码已经可以实现你的大部分场景了,但是如果你的业务场景需要保证数据的完整性(不能出现丢数据的情况),那么就需要添加一些重试策略,因为在我们的生产环境中,很有可能会因为某些组件不稳定性导致各种问题,所以这里我们就要在数据存入失败的时候做重试操作,这里 flink 自带的 es sink 就支持了,常用的失败重试配置有:1、bulk.flush.backoff.enable 用来表示是否开启重试机制2、bulk.flush.backoff.type 重试策略,有两种:EXPONENTIAL 指数型(表示多次重试之间的时间间隔按照指数方式进行增长)、CONSTANT 常数型(表示多次重试之间的时间间隔为固定常数)3、bulk.flush.backoff.delay 进行重试的时间间隔4、bulk.flush.backoff.retries 失败重试的次数5、bulk.flush.max.actions: 批量写入时的最大写入条数6、bulk.flush.max.size.mb: 批量写入时的最大数据量7、bulk.flush.interval.ms: 批量写入的时间间隔,配置后则会按照该时间间隔严格执行,无视上面的两个批量写入配置看下啦,就是如下这些配置了,如果你需要的话,可以在这个地方配置扩充了。FailureHandler 失败处理器写入 ES 的时候会有这些情况会导致写入 ES 失败:1、ES 集群队列满了,报如下错误12:08:07.326 [I/O dispatcher 13] ERROR o.a.f.s.c.e.ElasticsearchSinkBase - Failed Elasticsearch item request: ElasticsearchException[Elasticsearch exception [type=es_rejected_execution_exception, reason=rejected execution of org.elasticsearch.transport.TransportService$7@566c9379 on EsThreadPoolExecutor[name = node-1/write, queue capacity = 200, org.elasticsearch.common.util.concurrent.EsThreadPoolExecutor@f00b373[Running, pool size = 4, active threads = 4, queued tasks = 200, completed tasks = 6277]]]]是这样的,我电脑安装的 es 队列容量默认应该是 200,我没有修改过。我这里如果配置的 bulk flush size * 并发 sink 数量 这个值如果大于这个 queue capacity ,那么就很容易导致出现这种因为 es 队列满了而写入失败。当然这里你也可以通过调大点 es 的队列。参考:https://www.elastic.co/guide/…2、ES 集群某个节点挂了这个就不用说了,肯定写入失败的。跟过源码可以发现 RestClient 类里的 performRequestAsync 方法一开始会随机的从集群中的某个节点进行写入数据,如果这台机器掉线,会进行重试在其他的机器上写入,那么当时写入的这台机器的请求就需要进行失败重试,否则就会把数据丢失!3、ES 集群某个节点的磁盘满了这里说的磁盘满了,并不是磁盘真的就没有一点剩余空间的,是 es 会在写入的时候检查磁盘的使用情况,在 85% 的时候会打印日志警告。这里我看了下源码如下图:如果你想继续让 es 写入的话就需要去重新配一下 es 让它继续写入,或者你也可以清空些不必要的数据腾出磁盘空间来。解决方法DataStream<String> input = …;input.addSink(new ElasticsearchSink<>( config, transportAddresses, new ElasticsearchSinkFunction<String>() {…}, new ActionRequestFailureHandler() { @Override void onFailure(ActionRequest action, Throwable failure, int restStatusCode, RequestIndexer indexer) throw Throwable { if (ExceptionUtils.containsThrowable(failure, EsRejectedExecutionException.class)) { // full queue; re-add document for indexing indexer.add(action); } else if (ExceptionUtils.containsThrowable(failure, ElasticsearchParseException.class)) { // malformed document; simply drop request without failing sink } else { // for all other failures, fail the sink // here the failure is simply rethrown, but users can also choose to throw custom exceptions throw failure; } }}));如果仅仅只是想做失败重试,也可以直接使用官方提供的默认的 RetryRejectedExecutionFailureHandler ,该处理器会对 EsRejectedExecutionException 导致到失败写入做重试处理。如果你没有设置失败处理器(failure handler),那么就会使用默认的 NoOpFailureHandler 来简单处理所有的异常。总结本文写了 Flink connector es,将 Kafka 中的数据读取并存储到 ElasticSearch 中,文中讲了如何封装自带的 sink,然后一些扩展配置以及 FailureHandler 情况下要怎么处理。(这个问题可是线上很容易遇到的)关注我转载请务必注明原创地址为:http://www.54tianzhisheng.cn/2018/12/30/Flink-ElasticSearch-Sink/微信公众号:zhisheng另外我自己整理了些 Flink 的学习资料,目前已经全部放到微信公众号了。你可以加我的微信:zhisheng_tian,然后回复关键字:Flink 即可无条件获取到。Github 代码仓库https://github.com/zhisheng17/flink-learning/以后这个项目的所有代码都将放在这个仓库里,包含了自己学习 flink 的一些 demo 和博客相关文章1、《从0到1学习Flink》—— Apache Flink 介绍2、《从0到1学习Flink》—— Mac 上搭建 Flink 1.6.0 环境并构建运行简单程序入门3、《从0到1学习Flink》—— Flink 配置文件详解4、《从0到1学习Flink》—— Data Source 介绍5、《从0到1学习Flink》—— 如何自定义 Data Source ?6、《从0到1学习Flink》—— Data Sink 介绍7、《从0到1学习Flink》—— 如何自定义 Data Sink ?8、《从0到1学习Flink》—— Flink Data transformation(转换)9、《从0到1学习Flink》—— 介绍Flink中的Stream Windows10、《从0到1学习Flink》—— Flink 中的几种 Time 详解11、《从0到1学习Flink》—— Flink 写入数据到 ElasticSearch12、《从0到1学习Flink》—— Flink 项目如何运行?13、《从0到1学习Flink》—— Flink 写入数据到 Kafka ...

January 18, 2019 · 3 min · jiezi

从 10 秒到 2 秒!ElasticSearch 性能调优

大家好,我是皮蛋二哥。“ELK”是 ElasticSearch、Logstash、Kibana 三门技术的简称。如今 ELK 技术栈在互联网行业数据开发领域使用率越来越高,做过数据收集、数据开发、数据存储的同学相信对这个简称并不陌生,而ElasticSearch(以下简称 ES)则在 ELK 栈中占着举足轻重的地位。前一段时间,我亲身参与了一个 ES 集群的调优,今天把我所了解与用到的调优方法与大家分享,如有错误,请大家包涵与指正。系统层面的调优系统层面的调优主要是内存的设定与避免交换内存。ES 安装后默认设置的堆内存是 1GB,这很明显是不够的,那么接下来就会有一个问题出现:我们要设置多少内存给 ES 呢?其实这是要看我们集群节点的内存大小,还取决于我们是否在服务器节点上还是否要部署其他服务。如果内存相对很大,如 64G 及以上,并且我们不在 ES 集群上部署其他服务,那么我建议 ES 内存可以设置为 31G-32G,因为这里有一个 32G 性能瓶颈问题,直白的说就是即使你给了 ES 集群大于 32G 的内存,其性能也不一定会更加优良,甚至会不如设置为 31G-32G 时候的性能。以我调优的集群为例,我所调优的服务器节点内存为 64G,服务器节点上也基本不跑其他服务,所以我把 ES 集群内存大小设置为了 31G,以充分发挥集群性能。设置 ES 集群内存的时候,还有一点就是确保堆内存最小值(Xms)与最大值(Xmx)的大小是相同的,防止程序在运行时改变堆内存大小,这是一个很耗系统资源的过程。还有一点就是避免交换内存,可以在配置文件中对内存进行锁定,以避免交换内存(也可以在操作系统层面进行关闭内存交换)。对应的参数:bootstrap.mlockall: true分片与副本分片 (shard):ES 是一个分布式的搜索引擎, 索引通常都会分解成不同部分, 分布在不同节点的部分数据就是分片。ES 自动管理和组织分片, 并在必要的时候对分片数据进行再平衡分配, 所以用户基本上不用担心分片的处理细节。创建索引时默认的分片数为 5 个,并且一旦创建不能更改。副本 (replica):ES 默认创建一份副本,就是说在 5 个主分片的基础上,每个主分片都相应的有一个副本分片。额外的副本有利有弊,有副本可以有更强的故障恢复能力,但也占了相应副本倍数的磁盘空间。那我们在创建索引的时候,应该创建多少个分片与副本数呢?对于副本数,比较好确定,可以根据我们集群节点的多少与我们的存储空间决定,我们的集群服务器多,并且有足够大多存储空间,可以多设置副本数,一般是 1-3 个副本数,如果集群服务器相对较少并且存储空间没有那么宽松,则可以只设定一份副本以保证容灾(副本数可以动态调整)。对于分片数,是比较难确定的。因为一个索引分片数一旦确定,就不能更改,所以我们在创建索引前,要充分的考虑到,以后我们创建的索引所存储的数据量,否则创建了不合适的分片数,会对我们的性能造成很大的影响。对于分片数的大小,业界一致认为分片数的多少与内存挂钩,认为 1GB 堆内存对应 20-25 个分片,而一个分片的大小不要超过 50G,这样的配置有助于集群的健康。但是我个人认为这样的配置方法过于死板,我个人在调优 ES 集群的过程中,根据总数据量的大小,设定了相应的分片,保证每一个分片的大小没有超过 50G(大概在 40G 左右),但是相比之前的分片数查询起来,效果并不明显。之后又尝试了增加分片数,发现分片数增多之后,查询速度有了明显的提升,每一个分片的数据量控制在 10G 左右。查询大量小分片使得每个分片处理数据速度更快了,那是不是分片数越多,我们的查询就越快,ES 性能就越好呢?其实也不是,因为在查询过程中,有一个分片合并的过程,如果分片数不断的增加,合并的时间则会增加,而且随着更多的任务需要按顺序排队和处理,更多的小分片不一定要比查询较小数量的更大的分片更快。如果有多个并发查询,则有很多小碎片也会降低查询吞吐量。如果现在你的场景是分片数不合适了,但是又不知道如何调整,那么有一个好的解决方法就是按照时间创建索引,然后进行通配查询。如果每天的数据量很大,则可以按天创建索引,如果是一个月积累起来导致数据量很大,则可以一个月创建一个索引。如果要对现有索引进行重新分片,则需要重建索引,我会在文章的最后总结重建索引的过程。参数调优下面我会介绍一些 ES 关键参数的调优。有很多场景是,我们的 ES 集群占用了多大的 cpu 使用率,该如何调节呢。cpu 使用率高,有可能是写入导致的,也有可能是查询导致的,那要怎么查看呢?可以先通过 GET _nodes/{node}/hot_threads 查看线程栈,查看是哪个线程占用 cpu 高,如果是 elasticsearch[{node}][search][T#10] 则是查询导致的,如果是 elasticsearch[{node}][bulk][T#1] 则是数据写入导致的。我在实际调优中,cpu 使用率很高,如果不是 SSD,建议把 index.merge.scheduler.max_thread_count: 1 索引 merge 最大线程数设置为 1 个,该参数可以有效调节写入的性能。因为在存储介质上并发写,由于寻址的原因,写入性能不会提升,只会降低。还有几个重要参数可以进行设置,各位同学可以视自己的集群情况与数据情况而定。index.refresh_interval:这个参数的意思是数据写入后几秒可以被搜索到,默认是 1s。每次索引的 refresh 会产生一个新的 lucene 段, 这会导致频繁的合并行为,如果业务需求对实时性要求没那么高,可以将此参数调大,实际调优告诉我,该参数确实很给力,cpu 使用率直线下降。indices.memory.index_buffer_size:如果我们要进行非常重的高并发写入操作,那么最好将 indices.memory.index_buffer_size 调大一些,index buffer 的大小是所有的 shard 公用的,一般建议(看的大牛博客),对于每个 shard 来说,最多给 512mb,因为再大性能就没什么提升了。ES 会将这个设置作为每个 shard 共享的 index buffer,那些特别活跃的 shard 会更多的使用这个 buffer。默认这个参数的值是 10%,也就是 jvm heap 的 10%。translog:ES 为了保证数据不丢失,每次 index、bulk、delete、update 完成的时候,一定会触发刷新 translog 到磁盘上。在提高数据安全性的同时当然也降低了一点性能。如果你不在意这点可能性,还是希望性能优先,可以设置如下参数:“index.translog”: { “sync_interval”: “120s”, –sync间隔调高 “durability”: “async”, -– 异步更新 “flush_threshold_size”:“1g” –log文件大小 }这样设定的意思是开启异步写入磁盘,并设定写入的时间间隔与大小,有助于写入性能的提升。还有一些超时参数的设置:discovery.zen.ping_timeout 判断 master 选举过程中,发现其他 node 存活的超时设置discovery.zen.fd.ping_interval 节点被 ping 的频率,检测节点是否存活discovery.zen.fd.ping_timeout 节点存活响应的时间,默认为 30s,如果网络可能存在隐患,可以适当调大discovery.zen.fd.ping_retries ping 失败/超时多少导致节点被视为失败,默认为 3其他建议还有一些零碎的优化建议喔。插入索引自动生成 id:当写入端使用特定的 id 将数据写入 ES 时,ES 会检查对应的索引下是否存在相同的 id,这个操作会随着文档数量的增加使消耗越来越大,所以如果业务上没有硬性需求建议使用 ES 自动生成的 id,加快写入速率。避免稀疏索引:索引稀疏之后,会导致索引文件增大。ES 的 keyword,数组类型采用 doc_values 结构,即使字段是空值,每个文档也会占用一定的空间,所以稀疏索引会造成磁盘增大,导致查询和写入效率降低。我的调优下面说一说我的调优:我的调优主要是重建索引,更改了现有索引的分片数量,经过不断的测试,找到了一个最佳的分片数量,重建索引的时间是漫长的,在此期间,又对 ES 的写入进行了相应的调优,使 cpu 使用率降低下来。附上我的调优参数。index.merge.scheduler.max_thread_count:1 # 索引 merge 最大线程数indices.memory.index_buffer_size:30% # 内存index.translog.durability:async # 这个可以异步写硬盘,增大写的速度index.translog.sync_interval:120s #translog 间隔时间discovery.zen.ping_timeout:120s # 心跳超时时间discovery.zen.fd.ping_interval:120s # 节点检测时间discovery.zen.fd.ping_timeout:120s #ping 超时时间discovery.zen.fd.ping_retries:6 # 心跳重试次数thread_pool.bulk.size:20 # 写入线程个数 由于我们查询线程都是在代码里设定好的,我这里只调节了写入的线程数thread_pool.bulk.queue_size:1000 # 写入线程队列大小index.refresh_interval:300s #index 刷新间隔关于重建索引在重建索引之前,首先要考虑一下重建索引的必要性,因为重建索引是非常耗时的。ES 的 reindex api 不会去尝试设置目标索引,不会复制源索引的设置,所以我们应该在运行_reindex 操作之前设置目标索引,包括设置映射(mapping),分片,副本等。第一步,和创建普通索引一样创建新索引。当数据量很大的时候,需要设置刷新时间间隔,把 refresh_intervals 设置为-1,即不刷新,number_of_replicas 副本数设置为 0(因为副本数可以动态调整,这样有助于提升速度)。{ “settings”: { “number_of_shards”: “50”, “number_of_replicas”: “0”, “index”: { “refresh_interval”: “-1” } } “mappings”: { }}第二步,调用 reindex 接口,建议加上 wait_for_completion=false 的参数条件,这样 reindex 将直接返回 taskId。POST _reindex?wait_for_completion=false{ “source”: { “index”: “old_index”, //原有索引 “size”: 5000 //一个批次处理的数据量 }, “dest”: { “index”: “new_index”, //目标索引 }}第三步,等待。可以通过 GET _tasks?detailed=true&actions=*reindex 来查询重建的进度。如果要取消 task 则调用_tasks/node_id:task_id/_cancel。第四步,删除旧索引,释放磁盘空间。更多细节可以查看 ES 官网的 reindex api。那么有的同学可能会问,如果我此刻 ES 是实时写入的,那咋办呀?这个时候,我们就要重建索引的时候,在参数里加上上一次重建索引的时间戳,直白的说就是,比如我们的数据是 100G,这时候我们重建索引了,但是这个 100G 在增加,那么我们重建索引的时候,需要记录好重建索引的时间戳,记录时间戳的目的是下一次重建索引跑任务的时候不用全部重建,只需要在此时间戳之后的重建就可以,如此迭代,直到新老索引数据量基本一致,把数据流向切换到新索引的名字。POST /_reindex{ “conflicts”: “proceed”, //意思是冲突以旧索引为准,直接跳过冲突,否则会抛出异常,停止task “source”: { “index”: “old_index” //旧索引 “query”: { “constant_score” : { “filter” : { “range” : { “data_update_time” : { “gte” : 123456789 //reindex开始时刻前的毫秒时间戳 } } } } } }, “dest”: { “index”: “new_index”, //新索引 “version_type”: “external” //以旧索引的数据为准 }}以上就是我在 ES 调优上的一点总结,希望能够帮助到对 ES 性能有困惑的同学们,谢谢大家。——我是皮蛋,我喂自己袋盐。文 / 皮蛋二哥“一直以为只要保持低调,就没人知道其实我是一名作家”编 / 荧声本文由创宇前端作者授权发布,版权属于作者,创宇前端出品。 欢迎注明出处转载本文。文章链接:https://knownsec-fed.com/2019…想要订阅更多来自知道创宇开发一线的分享,请搜索关注我们的微信公众号:创宇前端(KnownsecFED)。欢迎留言讨论,我们会尽可能回复。欢迎点赞、收藏、留言评论、转发分享和打赏支持我们。打赏将被完全转交给文章作者。感谢您的阅读。 ...

January 16, 2019 · 2 min · jiezi

elasticsearch入门

这篇教程主要是对在入门的elasticsearch的一个记录。ES 集群安装安装环境基于 Dokcer ,单机安装 Docker 版集群。使用版本如下:Elasticsearch 5.3.2Kibana 5.3.2JDK 8整个安装步骤分成三部分:安装 ES 集群实例 elasticsearch001安装 ES 集群实例 elasticsearch002安装 Kibana 监控安装 ES 集群实例安装过程中镜像拉取事件过长,这里笔者将docker镜像上传到阿里的docker仓库中。安装 ES 集群实例 elasticsearch001:docker run -d -p 9200:9200 \ -p 9300:9300 \ –name elasticsearch001 -h elasticsearch001 \ -e cluster.name=lookout-es \ -e ES_JAVA_OPTS="-Xms512m -Xmx512m" \ -e xpack.security.enabled=false \ registry.cn-hangzhou.aliyuncs.com/dingwenjiang/elasticsearch:5.3.2命令解释如下:docker run: 会启动一个容器实例,如果本地没有对应的镜像会去远程registry上先下载镜像。-d: 表示容器运行在后台-p [宿主机端口]:[容器内端口]: 比如-p 9200:9200 表示把宿主机的9200端口映射到容器的9200端口–name : 设置容器别名-h : 指定容器的hostname-e: 设置环境变量。这里关闭 x-pack 的安全校验功能,防止访问认证。通过curl http://localhost:9200/_cat/health?v=pretty来验证elasticsearch001是否启动成功,如下:设置环境变量的时候,我们指定了-e cluster.name=lookout-es,用于后续关联集群用。node为1 表示只有一个实例。默认 shards 分片为主备两个。status 状态是我们要关心的,状态可能是下列三个值之一:green:所有的主分片和副本分片都已分配,集群是 100% 可用的。yellow:所有的主分片已经分片了,但至少还有一个副本是缺失的。不会有数据丢失,所以搜索结果依然是完整的。高可用会弱化把 yellow 想象成一个需要及时调查的警告。red:至少一个主分片(以及它的全部副本)都在缺失中。这意味着你在缺少数据:搜索只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常。也可以访问 http://localhost:9200/ ,可以看到成功运行的案例,返回的 JSON 页面。如图:继续搭建elasticsearch002:docker run -d -p 9211:9200 \ -p 9311:9300 –link elasticsearch001 \ –name elasticsearch002 \ -e cluster.name=lookout-es \ -e ES_JAVA_OPTS="-Xms512m -Xmx512m" \ -e xpack.security.enabled=false \ -e discovery.zen.ping.unicast.hosts=elasticsearch001 \ registry.cn-hangzhou.aliyuncs.com/dingwenjiang/elasticsearch:5.3.2启动elasticsearch002的时候增加了几个参数,–link [其他容器名]:[在该容器中的别名]: 添加链接到另一个容器, 在本容器 hosts 文件中加入关联容器的记录。-e: 设置环境变量。这里额外指定了 ES 集群的 cluster.name、ES 集群节点淡泊配置 discovery.zen.ping.unicast.hosts 设置为实例 elasticsearch001。再次执行curl http://localhost:9200/_cat/health?v=pretty,结果如图:对比上面检查数值可以看出,首先集群状态为 green , 所有的主分片和副本分片都已分配。你的集群是 100% 可用的。相应的 node 、shards 都增加。安装 Kibana 监控接着安装Kibana,对elasticsearch进行监控,安装命令如下:# 启动kibanadocker run -d –name kibana001 \ –link elasticsearch001 \ -e ELASTICSEARCH_URL=http://elasticsearch001:9200 \ -p 5601:5601\ registry.cn-hangzhou.aliyuncs.com/dingwenjiang/kibana:5.3.2其中-e 设置环境变量。这里额外指定了 ELASTICSEARCH_URL 为搜索实例地址。打开网页访问 127.0.0.1:5601,默认账号为 elasti,密码为 changeme。会出现如下的截图:Spring Boot 整合 Elasticsearch这里只是简单整合下,开发一个web接口,实现数据存储以及查询功能。开发的思路还是传统的三层架构,controller、service、dao,这里利用spring data来简化对es的curd操作。项目的repo地址:https://github.com/warjiang/d…整个项目的结构如下所示:入口文件为:Application类,其中也是大家熟悉的spring-boot的用法。controller主要在api包下,这里会暴露出两个API接口,分别是/api/contents用于写入内容、/api/content/search用于查询service主要在service包下,与controller对应,需要实现写入和查询两个方法dao主要在repository包下,继承ElasticsearchRepository,实现curd。这里需要注意的时候,读写的bean用的是entity包下的ContentEntity,实际上services中操作的的bean是bean包下的ContentBean。后续具体的实现在这里不再赘述。项目运行起来后,可以发送写入和查询的请求来测试功能的正确性。写入请求:可以通过curl 或者postman构造一个请求如下:POST /api/contents HTTP/1.1Host: 127.0.0.1:8080Content-Type: application/jsonCache-Control: no-cache[ { “id”:1, “title”:"《见识》", “content”:“摩根说:任意让小钱从身边溜走的人,一定留不住大钱”, “type”:1, “category”:“文学”, “read”:999, “support”:100 }, { “id”:2, “title”:"《态度》", “content”:“人类的幸福不是来自偶然的幸运,而是来自每天的小恩惠”, “type”:2, “category”:“文学”, “read”:888, “support”:88 }, { “id”:3, “title”:"《Java 编程思想》", “content”:“Java 是世界上最diao的语言”, “type”:2, “category”:“计算”, “read”:999, “support”:100 }]请求成功会返回如下所示:{ “code”: 0, “message”: “success”, “data”: true}写入成功后可以到kibana中查看写入结果,打开网页访问 localhost:5601,在 Kibana 监控中输入需要监控的 index name 为 content。如下图,取消打钩,然后进入:进入后,会得到如图所示的界面,里面罗列了该索引 content 下面所有字段:打开左侧 Discover 栏目,即可看到可视化的搜索界面及数据:随便打开一个json如下:{ “_index”: “content”, “_type”: “content”, “_id”: “2”, “_score”: 1, “_source”: { “id”: 2, “title”: “《态度》”, “content”: “人类的幸福不是来自偶然的幸运,而是来自每天的小恩惠”, “type”: 2, “category”: “文学”, “read”: 888, “support”: 88 }}_index 就是索引,用于区分文档成组,即分到一组的文档集合。索引,用于存储文档和使文档可被搜索。_type 就是类型,用于区分索引中的文档,即在索引中对数据逻辑分区。比如索引 project 的项目数据,根据项目类型 ui 项目、插画项目等进行区分。_id 是该文档的唯一标示,代码中我们一 ID 作为他的唯一标示。查询请求:可以通过curl 或者postman构造一个请求如下:POST /api/content/search HTTP/1.1Host: 127.0.0.1:8080Content-Type: application/jsonCache-Control: no-cache{ “searchContent”:“Java”, “type”:2, “pageSize”:3, “pageNumber”:0}对应结果如下:{ “code”: 0, “message”: “success”, “data”: { “pageNumber”: 0, “pageSize”: 3, “totalPage”: 1, “totalCount”: 1, “result”: [ { “id”: 3, “title”: “《Java 编程思想》”, “content”: “Java 是世界上最diao的语言”, “type”: 2, “category”: “计算”, “read”: 999, “support”: 100 } ] }}这里根据 searchContent 匹配短语 +type 匹配单个字段,一起构建了搜索语句。用于搜索出我们期待的结果,就是《Java 编程思想》。 ...

January 12, 2019 · 2 min · jiezi

玩转Elasticsearch源码-ActionModule启动分析

ActionModule的用途org.elasticsearch.action.ActionModule主要维护了请求和响应相关组件,包括客户端请求处理器(actions,restController)和过滤器(actionFilters),它们可能来自ES本身或者来自plugin。源码分析构造方法先看看构造方法,每一步都简单加了解释 public ActionModule(boolean transportClient, Settings settings, IndexNameExpressionResolver indexNameExpressionResolver, IndexScopedSettings indexScopedSettings, ClusterSettings clusterSettings, SettingsFilter settingsFilter, ThreadPool threadPool, List<ActionPlugin> actionPlugins, NodeClient nodeClient, CircuitBreakerService circuitBreakerService, UsageService usageService) { this.transportClient = transportClient;//服务端为false,客户端为true this.settings = settings; this.indexNameExpressionResolver = indexNameExpressionResolver;//将提供的索引表达式转换为实际的具体索引 this.indexScopedSettings = indexScopedSettings;//index级别的配置 this.clusterSettings = clusterSettings;//集群级别的配置 this.settingsFilter = settingsFilter;//允许通过简单正则表达式模式或完整设置键筛选设置对象的类。它用于rest层上的响应过滤,例如过滤出access keys等敏感信息。 this.actionPlugins = actionPlugins;//actionPlugins主要实现了 ActionPlugin 的 getActions() actions = setupActions(actionPlugins);//安装actions actionFilters = setupActionFilters(actionPlugins);//从ActionPlugin中获取,后面在TransportAction里面requestFilterChain会去调用,以后再细讲 autoCreateIndex = transportClient ? null : new AutoCreateIndex(settings, clusterSettings, indexNameExpressionResolver);//封装是否创建新索引的逻辑 destructiveOperations = new DestructiveOperations(settings, clusterSettings);//帮助处理破坏性操作和通配符使用。 Set<String> headers = actionPlugins.stream().flatMap(p -> p.getRestHeaders().stream()).collect(Collectors.toSet()); UnaryOperator<RestHandler> restWrapper = null;//RestHandler包装器,下面从actionPlugins里面遍历获取 for (ActionPlugin plugin : actionPlugins) { UnaryOperator<RestHandler> newRestWrapper = plugin.getRestHandlerWrapper(threadPool.getThreadContext()); if (newRestWrapper != null) { logger.debug(“Using REST wrapper from plugin " + plugin.getClass().getName()); if (restWrapper != null) { throw new IllegalArgumentException(“Cannot have more than one plugin implementing a REST wrapper”); } restWrapper = newRestWrapper; } } if (transportClient) {//客户端不需要暴露http服务 restController = null; } else {//服务端创建一个RestController,实现了Dispatcher接口,系统接受到请求后从netty的handler一路调用到 dispatchRequest 方法 restController = new RestController(settings, headers, restWrapper, nodeClient, circuitBreakerService, usageService); } }首先设置了settings等参数,然后进入setupActions代码比较好理解,直接贴源码:static Map<String, ActionHandler<?, ?>> setupActions(List<ActionPlugin> actionPlugins) { // Subclass NamedRegistry for easy registration class ActionRegistry extends NamedRegistry<ActionHandler<?, ?>> { ActionRegistry() { super(“action”); } public void register(ActionHandler<?, ?> handler) { register(handler.getAction().name(), handler); } public <Request extends ActionRequest, Response extends ActionResponse> void register( GenericAction<Request, Response> action, Class<? extends TransportAction<Request, Response>> transportAction, Class<?>… supportTransportActions) { register(new ActionHandler<>(action, transportAction, supportTransportActions)); } } ActionRegistry actions = new ActionRegistry(); actions.register(MainAction.INSTANCE, TransportMainAction.class); actions.register(NodesInfoAction.INSTANCE, TransportNodesInfoAction.class); actions.register(RemoteInfoAction.INSTANCE, TransportRemoteInfoAction.class); actions.register(NodesStatsAction.INSTANCE, TransportNodesStatsAction.class); actions.register(NodesUsageAction.INSTANCE, TransportNodesUsageAction.class); actions.register(NodesHotThreadsAction.INSTANCE, TransportNodesHotThreadsAction.class); actions.register(ListTasksAction.INSTANCE, TransportListTasksAction.class); actions.register(GetTaskAction.INSTANCE, TransportGetTaskAction.class); actions.register(CancelTasksAction.INSTANCE, TransportCancelTasksAction.class); actions.register(ClusterAllocationExplainAction.INSTANCE, TransportClusterAllocationExplainAction.class); actions.register(ClusterStatsAction.INSTANCE, TransportClusterStatsAction.class); actions.register(ClusterStateAction.INSTANCE, TransportClusterStateAction.class); actions.register(ClusterHealthAction.INSTANCE, TransportClusterHealthAction.class); actions.register(ClusterUpdateSettingsAction.INSTANCE, TransportClusterUpdateSettingsAction.class); actions.register(ClusterRerouteAction.INSTANCE, TransportClusterRerouteAction.class); actions.register(ClusterSearchShardsAction.INSTANCE, TransportClusterSearchShardsAction.class); actions.register(PendingClusterTasksAction.INSTANCE, TransportPendingClusterTasksAction.class); actions.register(PutRepositoryAction.INSTANCE, TransportPutRepositoryAction.class); actions.register(GetRepositoriesAction.INSTANCE, TransportGetRepositoriesAction.class); actions.register(DeleteRepositoryAction.INSTANCE, TransportDeleteRepositoryAction.class); actions.register(VerifyRepositoryAction.INSTANCE, TransportVerifyRepositoryAction.class); actions.register(GetSnapshotsAction.INSTANCE, TransportGetSnapshotsAction.class); actions.register(DeleteSnapshotAction.INSTANCE, TransportDeleteSnapshotAction.class); actions.register(CreateSnapshotAction.INSTANCE, TransportCreateSnapshotAction.class); actions.register(RestoreSnapshotAction.INSTANCE, TransportRestoreSnapshotAction.class); actions.register(SnapshotsStatusAction.INSTANCE, TransportSnapshotsStatusAction.class); actions.register(IndicesStatsAction.INSTANCE, TransportIndicesStatsAction.class); actions.register(IndicesSegmentsAction.INSTANCE, TransportIndicesSegmentsAction.class); actions.register(IndicesShardStoresAction.INSTANCE, TransportIndicesShardStoresAction.class); actions.register(CreateIndexAction.INSTANCE, TransportCreateIndexAction.class); actions.register(ShrinkAction.INSTANCE, TransportShrinkAction.class); actions.register(ResizeAction.INSTANCE, TransportResizeAction.class); actions.register(RolloverAction.INSTANCE, TransportRolloverAction.class); actions.register(DeleteIndexAction.INSTANCE, TransportDeleteIndexAction.class); actions.register(GetIndexAction.INSTANCE, TransportGetIndexAction.class); actions.register(OpenIndexAction.INSTANCE, TransportOpenIndexAction.class); actions.register(CloseIndexAction.INSTANCE, TransportCloseIndexAction.class); actions.register(IndicesExistsAction.INSTANCE, TransportIndicesExistsAction.class); actions.register(TypesExistsAction.INSTANCE, TransportTypesExistsAction.class); actions.register(GetMappingsAction.INSTANCE, TransportGetMappingsAction.class); actions.register(GetFieldMappingsAction.INSTANCE, TransportGetFieldMappingsAction.class, TransportGetFieldMappingsIndexAction.class); actions.register(PutMappingAction.INSTANCE, TransportPutMappingAction.class); actions.register(IndicesAliasesAction.INSTANCE, TransportIndicesAliasesAction.class); actions.register(UpdateSettingsAction.INSTANCE, TransportUpdateSettingsAction.class); actions.register(AnalyzeAction.INSTANCE, TransportAnalyzeAction.class); actions.register(PutIndexTemplateAction.INSTANCE, TransportPutIndexTemplateAction.class); actions.register(GetIndexTemplatesAction.INSTANCE, TransportGetIndexTemplatesAction.class); actions.register(DeleteIndexTemplateAction.INSTANCE, TransportDeleteIndexTemplateAction.class); actions.register(ValidateQueryAction.INSTANCE, TransportValidateQueryAction.class); actions.register(RefreshAction.INSTANCE, TransportRefreshAction.class); actions.register(FlushAction.INSTANCE, TransportFlushAction.class); actions.register(SyncedFlushAction.INSTANCE, TransportSyncedFlushAction.class); actions.register(ForceMergeAction.INSTANCE, TransportForceMergeAction.class); actions.register(UpgradeAction.INSTANCE, TransportUpgradeAction.class); actions.register(UpgradeStatusAction.INSTANCE, TransportUpgradeStatusAction.class); actions.register(UpgradeSettingsAction.INSTANCE, TransportUpgradeSettingsAction.class); actions.register(ClearIndicesCacheAction.INSTANCE, TransportClearIndicesCacheAction.class); actions.register(GetAliasesAction.INSTANCE, TransportGetAliasesAction.class); actions.register(AliasesExistAction.INSTANCE, TransportAliasesExistAction.class); actions.register(GetSettingsAction.INSTANCE, TransportGetSettingsAction.class); actions.register(IndexAction.INSTANCE, TransportIndexAction.class); actions.register(GetAction.INSTANCE, TransportGetAction.class); actions.register(TermVectorsAction.INSTANCE, TransportTermVectorsAction.class); actions.register(MultiTermVectorsAction.INSTANCE, TransportMultiTermVectorsAction.class, TransportShardMultiTermsVectorAction.class); actions.register(DeleteAction.INSTANCE, TransportDeleteAction.class); actions.register(UpdateAction.INSTANCE, TransportUpdateAction.class); actions.register(MultiGetAction.INSTANCE, TransportMultiGetAction.class, TransportShardMultiGetAction.class); actions.register(BulkAction.INSTANCE, TransportBulkAction.class, TransportShardBulkAction.class); actions.register(SearchAction.INSTANCE, TransportSearchAction.class); actions.register(SearchScrollAction.INSTANCE, TransportSearchScrollAction.class); actions.register(MultiSearchAction.INSTANCE, TransportMultiSearchAction.class); actions.register(ExplainAction.INSTANCE, TransportExplainAction.class); actions.register(ClearScrollAction.INSTANCE, TransportClearScrollAction.class); actions.register(RecoveryAction.INSTANCE, TransportRecoveryAction.class); //Indexed scripts actions.register(PutStoredScriptAction.INSTANCE, TransportPutStoredScriptAction.class); actions.register(GetStoredScriptAction.INSTANCE, TransportGetStoredScriptAction.class); actions.register(DeleteStoredScriptAction.INSTANCE, TransportDeleteStoredScriptAction.class); actions.register(FieldCapabilitiesAction.INSTANCE, TransportFieldCapabilitiesAction.class, TransportFieldCapabilitiesIndexAction.class); actions.register(PutPipelineAction.INSTANCE, PutPipelineTransportAction.class); actions.register(GetPipelineAction.INSTANCE, GetPipelineTransportAction.class); actions.register(DeletePipelineAction.INSTANCE, DeletePipelineTransportAction.class); actions.register(SimulatePipelineAction.INSTANCE, SimulatePipelineTransportAction.class); actionPlugins.stream().flatMap(p -> p.getActions().stream()).forEach(actions::register); return unmodifiableMap(actions.getRegistry()); }注册了一大堆Action然后返回 Map<String, ActionHandler<?, ?>> 对象可以注意到ActionHandler包装了action和transportAction的Class对象。action是对各个类型请求的request和response的包装,并非是真正的功能实现者,可以看到它只是提供了两个新建response和request的方法,及一个字NAME字段,这个NAME字段会用于后面action调用中。每个action对应的功能实现是在对应的transportAction中。configure方法configure方法由ES封装的注入器Injector调用,看看调用栈可以知道由ES启动时候Node构造方法里面发起的injector = modules.createInjector();configure里面逻辑其实就是绑定一些对象到Injector中:protected void configure() { bind(ActionFilters.class).toInstance(actionFilters); bind(DestructiveOperations.class).toInstance(destructiveOperations); if (false == transportClient) { // Supporting classes only used when not a transport client bind(AutoCreateIndex.class).toInstance(autoCreateIndex); bind(TransportLivenessAction.class).asEagerSingleton(); // register GenericAction -> transportAction Map used by NodeClient @SuppressWarnings(“rawtypes”) MapBinder<GenericAction, TransportAction> transportActionsBinder = MapBinder.newMapBinder(binder(), GenericAction.class, TransportAction.class); for (ActionHandler<?, ?> action : actions.values()) { // bind the action as eager singleton, so the map binder one will reuse it bind(action.getTransportAction()).asEagerSingleton(); transportActionsBinder.addBinding(action.getAction()).to(action.getTransportAction()).asEagerSingleton(); for (Class<?> supportAction : action.getSupportTransportActions()) { bind(supportAction).asEagerSingleton(); } } } }initRestHandlersinitRestHandlers是在Node构造方法里面直接调用的,主要是注册一大堆RestHandler到restController用于处理http 请求 public void initRestHandlers(Supplier<DiscoveryNodes> nodesInCluster) { List<AbstractCatAction> catActions = new ArrayList<>(); Consumer<RestHandler> registerHandler = a -> { if (a instanceof AbstractCatAction) { catActions.add((AbstractCatAction) a); } }; registerHandler.accept(new RestMainAction(settings, restController)); registerHandler.accept(new RestNodesInfoAction(settings, restController, settingsFilter)); registerHandler.accept(new RestRemoteClusterInfoAction(settings, restController)); registerHandler.accept(new RestNodesStatsAction(settings, restController)); registerHandler.accept(new RestNodesUsageAction(settings, restController)); registerHandler.accept(new RestNodesHotThreadsAction(settings, restController)); registerHandler.accept(new RestClusterAllocationExplainAction(settings, restController)); registerHandler.accept(new RestClusterStatsAction(settings, restController)); registerHandler.accept(new RestClusterStateAction(settings, restController, settingsFilter)); registerHandler.accept(new RestClusterHealthAction(settings, restController)); registerHandler.accept(new RestClusterUpdateSettingsAction(settings, restController)); registerHandler.accept(new RestClusterGetSettingsAction(settings, restController, clusterSettings, settingsFilter)); registerHandler.accept(new RestClusterRerouteAction(settings, restController, settingsFilter)); registerHandler.accept(new RestClusterSearchShardsAction(settings, restController)); registerHandler.accept(new RestPendingClusterTasksAction(settings, restController)); registerHandler.accept(new RestPutRepositoryAction(settings, restController)); registerHandler.accept(new RestGetRepositoriesAction(settings, restController, settingsFilter)); registerHandler.accept(new RestDeleteRepositoryAction(settings, restController)); registerHandler.accept(new RestVerifyRepositoryAction(settings, restController)); registerHandler.accept(new RestGetSnapshotsAction(settings, restController)); registerHandler.accept(new RestCreateSnapshotAction(settings, restController)); registerHandler.accept(new RestRestoreSnapshotAction(settings, restController)); registerHandler.accept(new RestDeleteSnapshotAction(settings, restController)); registerHandler.accept(new RestSnapshotsStatusAction(settings, restController)); registerHandler.accept(new RestGetAllAliasesAction(settings, restController)); registerHandler.accept(new RestGetAllMappingsAction(settings, restController)); registerHandler.accept(new RestGetAllSettingsAction(settings, restController, indexScopedSettings, settingsFilter)); registerHandler.accept(new RestGetIndicesAction(settings, restController, indexScopedSettings, settingsFilter)); registerHandler.accept(new RestIndicesStatsAction(settings, restController)); registerHandler.accept(new RestIndicesSegmentsAction(settings, restController)); registerHandler.accept(new RestIndicesShardStoresAction(settings, restController)); registerHandler.accept(new RestGetAliasesAction(settings, restController)); registerHandler.accept(new RestIndexDeleteAliasesAction(settings, restController)); registerHandler.accept(new RestIndexPutAliasAction(settings, restController)); registerHandler.accept(new RestIndicesAliasesAction(settings, restController)); registerHandler.accept(new RestCreateIndexAction(settings, restController)); registerHandler.accept(new RestShrinkIndexAction(settings, restController)); registerHandler.accept(new RestSplitIndexAction(settings, restController)); registerHandler.accept(new RestRolloverIndexAction(settings, restController)); registerHandler.accept(new RestDeleteIndexAction(settings, restController)); registerHandler.accept(new RestCloseIndexAction(settings, restController)); registerHandler.accept(new RestOpenIndexAction(settings, restController)); registerHandler.accept(new RestUpdateSettingsAction(settings, restController)); registerHandler.accept(new RestGetSettingsAction(settings, restController, indexScopedSettings, settingsFilter)); registerHandler.accept(new RestAnalyzeAction(settings, restController)); registerHandler.accept(new RestGetIndexTemplateAction(settings, restController)); registerHandler.accept(new RestPutIndexTemplateAction(settings, restController)); registerHandler.accept(new RestDeleteIndexTemplateAction(settings, restController)); registerHandler.accept(new RestPutMappingAction(settings, restController)); registerHandler.accept(new RestGetMappingAction(settings, restController)); registerHandler.accept(new RestGetFieldMappingAction(settings, restController)); registerHandler.accept(new RestRefreshAction(settings, restController)); registerHandler.accept(new RestFlushAction(settings, restController)); registerHandler.accept(new RestSyncedFlushAction(settings, restController)); registerHandler.accept(new RestForceMergeAction(settings, restController)); registerHandler.accept(new RestUpgradeAction(settings, restController)); registerHandler.accept(new RestClearIndicesCacheAction(settings, restController)); registerHandler.accept(new RestIndexAction(settings, restController)); registerHandler.accept(new RestGetAction(settings, restController)); registerHandler.accept(new RestGetSourceAction(settings, restController)); registerHandler.accept(new RestMultiGetAction(settings, restController)); registerHandler.accept(new RestDeleteAction(settings, restController)); registerHandler.accept(new org.elasticsearch.rest.action.document.RestCountAction(settings, restController)); registerHandler.accept(new RestTermVectorsAction(settings, restController)); registerHandler.accept(new RestMultiTermVectorsAction(settings, restController)); registerHandler.accept(new RestBulkAction(settings, restController)); registerHandler.accept(new RestUpdateAction(settings, restController)); registerHandler.accept(new RestSearchAction(settings, restController)); registerHandler.accept(new RestSearchScrollAction(settings, restController)); registerHandler.accept(new RestClearScrollAction(settings, restController)); registerHandler.accept(new RestMultiSearchAction(settings, restController)); registerHandler.accept(new RestValidateQueryAction(settings, restController)); registerHandler.accept(new RestExplainAction(settings, restController)); registerHandler.accept(new RestRecoveryAction(settings, restController)); // Scripts API registerHandler.accept(new RestGetStoredScriptAction(settings, restController)); registerHandler.accept(new RestPutStoredScriptAction(settings, restController)); registerHandler.accept(new RestDeleteStoredScriptAction(settings, restController)); registerHandler.accept(new RestFieldCapabilitiesAction(settings, restController)); // Tasks API registerHandler.accept(new RestListTasksAction(settings, restController, nodesInCluster)); registerHandler.accept(new RestGetTaskAction(settings, restController)); registerHandler.accept(new RestCancelTasksAction(settings, restController, nodesInCluster)); // Ingest API registerHandler.accept(new RestPutPipelineAction(settings, restController)); registerHandler.accept(new RestGetPipelineAction(settings, restController)); registerHandler.accept(new RestDeletePipelineAction(settings, restController)); registerHandler.accept(new RestSimulatePipelineAction(settings, restController)); // CAT API registerHandler.accept(new RestAllocationAction(settings, restController)); registerHandler.accept(new RestShardsAction(settings, restController)); registerHandler.accept(new RestMasterAction(settings, restController)); registerHandler.accept(new RestNodesAction(settings, restController)); registerHandler.accept(new RestTasksAction(settings, restController, nodesInCluster)); registerHandler.accept(new RestIndicesAction(settings, restController, indexNameExpressionResolver)); registerHandler.accept(new RestSegmentsAction(settings, restController)); // Fully qualified to prevent interference with rest.action.count.RestCountAction registerHandler.accept(new org.elasticsearch.rest.action.cat.RestCountAction(settings, restController)); // Fully qualified to prevent interference with rest.action.indices.RestRecoveryAction registerHandler.accept(new org.elasticsearch.rest.action.cat.RestRecoveryAction(settings, restController)); registerHandler.accept(new RestHealthAction(settings, restController)); registerHandler.accept(new org.elasticsearch.rest.action.cat.RestPendingClusterTasksAction(settings, restController)); registerHandler.accept(new RestAliasAction(settings, restController)); registerHandler.accept(new RestThreadPoolAction(settings, restController)); registerHandler.accept(new RestPluginsAction(settings, restController)); registerHandler.accept(new RestFielddataAction(settings, restController)); registerHandler.accept(new RestNodeAttrsAction(settings, restController)); registerHandler.accept(new RestRepositoriesAction(settings, restController)); registerHandler.accept(new RestSnapshotAction(settings, restController)); registerHandler.accept(new RestTemplatesAction(settings, restController)); for (ActionPlugin plugin : actionPlugins) { for (RestHandler handler : plugin.getRestHandlers(settings, restController, clusterSettings, indexScopedSettings, settingsFilter, indexNameExpressionResolver, nodesInCluster)) { registerHandler.accept(handler); } } registerHandler.accept(new RestCatAction(settings, restController, catActions)); }再深入去看RestController里面的注册逻辑可以看到是用Trie树来做路径匹配的,具体可以看TrieNode类。 ...

January 10, 2019 · 5 min · jiezi

玩转Elasticsearch源码-Elasticsearch构建任务简析

作为 玩转Elasticsearch源码 系列第二篇,先介绍下分析思路,本系列不会直接进入代码执行流程,而是先从Elasticsearch源代码的工程结构,构建任务等入手,按照先整体脉络,后细节的方式进行。GradleGradle是一个基于Apache Ant和Apache Maven概念的项目自动化建构工具。它使用一种基于Groovy的特定领域语言来声明项目设置,而不是传统的XML。[[2]](https://zh.wikipedia.org/wiki…当前其支持的语言限于Java、Groovy和Scala[[3]](https://zh.wikipedia.org/wiki…,计划未来将支持更多的语言。构建命令进入ES源码根目录,执行gradle tasks可以看到ES所有tasks:Application tasks—————–distShadowTar - Bundles the project as a JVM application with libs and OS specific scripts.将项目作为JVM应用程序与libs和特定于OS的脚本捆绑在一起。distShadowZip - Bundles the project as a JVM application with libs and OS specific scripts.将项目作为JVM应用程序与libs和特定于OS的脚本捆绑在一起。installShadowApp - Installs the project as a JVM application along with libs and OS specific scripts.将项目安装为JVM应用程序,以及libs和特定于操作系统的脚本run - Runs this project as a JVM application将此项目作为JVM应用程序运行runShadow - Runs this project as a JVM application using the shadow jar使用影子jar将此项目作为JVM应用程序运行startShadowScripts - Creates OS specific scripts to run the project as a JVM application using the shadow jar创建特定于操作系统的脚本,使用影子jar将项目作为JVM应用程序运行Benchmark tasks—————jmh - Runs all microbenchmarks运行所有微基准测试jmhJar - Generates an uberjar with the microbenchmarks and all dependencies使用微基准测试和所有依赖项生成uberjarBuild tasks———–assemble - Assembles the outputs of this project.编译工程到outputsbuild - Assembles and tests this project.编译和测试这个项目。buildDependents - Assembles and tests this project and all projects that depend on it.编译和测试这个项目以及所有依赖它的项目。buildNeeded - Assembles and tests this project and all projects it depends on.编译和测试这个项目以及它所依赖的所有项目。classes - Assembles main classes.编译主类。clean - Deletes the build directory.删除构建目录。jar - Assembles a jar archive containing the main classes.编译包含主要类的jar归档文件。javadocJar - Assembles a jar containing javadocs.装配一个包含javadoc的jar。sourcesJar - Assembles a jar containing source files.装配一个包含源文件的jar。testClasses - Assembles test classes.编译测试类。war - Generates a war archive with all the compiled classes, the web-app content and the libraries.生成包含所有编译类、web - app内容和库的war存档。Build Setup tasks—————–init - Initializes a new Gradle build. [incubating]初始化一个新的Gradle构建。(孵化)wrapper - Generates Gradle wrapper files. [incubating]生成Gradle包装文件。(孵化)Distribution tasks——————assembleDist - Assembles the main distributions编译主要的发行版distTar - Bundles the project as a distribution.将项目打包为一个发行版。distZip - Bundles the project as a distribution.将项目打包为一个发行版。installDist - Installs the project as a distribution as-is.将项目安装为按原样发布的版本。Docs tasks———-listConsoleCandidateslistSnippets - List each snippet列表中每一个片段Documentation tasks——————-groovydoc - Generates Groovydoc API documentation for the main source code.为主要源代码生成Groovydoc API文档。javadoc - Generates Javadoc API documentation for the main source code.为主要源代码生成Javadoc API文档。Help tasks———-buildEnvironment - Displays all buildscript dependencies declared in root project ’elasticsearch’.显示根项目“elasticsearch”中声明的所有构建脚本依赖项。components - Displays the components produced by root project ’elasticsearch’. [incubating]显示根项目“elasticsearch”生成的组件。(孵化)dependencies - Displays all dependencies declared in root project ’elasticsearch’.显示根项目“elasticsearch”中声明的所有依赖项。dependencyInsight - Displays the insight into a specific dependency in root project ’elasticsearch’.显示根项目“elasticsearch”中的特定依赖关系。dependentComponents - Displays the dependent components of components in root project ’elasticsearch’. [incubating]显示根项目“elasticsearch”中组件的依赖组件。(孵化)help - Displays a help message.显示帮助消息。model - Displays the configuration model of root project ’elasticsearch’. [incubating]显示根项目“elasticsearch”的配置模型。(孵化)projects - Displays the sub-projects of root project ’elasticsearch’.显示根项目的子项目。properties - Displays the properties of root project ’elasticsearch’.显示根项目的属性。tasks - Displays the tasks runnable from root project ’elasticsearch’ (some of the displayed tasks may belong to subprojects).显示可从根项目“elasticsearch”运行的任务(显示的一些任务可能属于子项目)。IDE tasks———cleanEclipse - Cleans all Eclipse files.清除所有Eclipse文件。cleanEclipseWtp - Cleans Eclipse wtp configuration files.清理Eclipse wtp配置文件。cleanIdea - Cleans IDEA project files (IML, IPR)清理idea项目文件(IML、IPR)cleanIdeaBuildDir - Deletes the IDEA build directory.删除IDEA构建目录。eclipse - Generates all Eclipse files.生成所有Eclipse文件。eclipseWtp - Generates Eclipse wtp configuration files.生成Eclipse wtp配置文件。idea - Generates IDEA project files (IML, IPR, IWS)生成IDEA项目文件(IML、IPR、IWS)Publishing tasks—————-generatePomFileForClientJarPublication - Generates the Maven POM file for publication ‘clientJar’.为发布“clientJar”生成Maven POM文件。generatePomFileForNebulaPublication - Generates the Maven POM file for publication ’nebula’.为发布“nebula”生成Maven POM文件generatePomFileForNebulaRealPomPublication - Generates the Maven POM file for publication ’nebulaRealPom’.为发布“nebulaRealPom”生成Maven POM文件。generatePomFileForZipPublication - Generates the Maven POM file for publication ‘zip’.为发布“zip”生成Maven POM文件。generatePomFileForZipRealPublication - Generates the Maven POM file for publication ‘zipReal’.为发布“zip Real”生成Maven POM文件。publish - Publishes all publications produced by this project.出版由本项目制作的所有出版物。publishClientJarPublicationToMavenLocal - Publishes Maven publication ‘clientJar’ to the local Maven repository.将Maven发布“clientJar”到本地Maven存储库。publishNebulaPublicationToMavenLocal - Publishes Maven publication ’nebula’ to the local Maven repository.将Maven发布“nebula”到本地Maven存储库。publishNebulaRealPomPublicationToMavenLocal - Publishes Maven publication ’nebulaRealPom’ to the local Maven repository.将Maven出版物“nebulaRealPom”发布到本地Maven存储库。publishToMavenLocal - Publishes all Maven publications produced by this project to the local Maven cache.将此项目生成的所有Maven发布发布到本地Maven缓存。publishZipPublicationToMavenLocal - Publishes Maven publication ‘zip’ to the local Maven repository.将Maven发布“zip”到本地Maven存储库。publishZipRealPublicationToMavenLocal - Publishes Maven publication ‘zipReal’ to the local Maven repository.将Maven发布“zip Real”到本地Maven存储库。Shadow tasks————knows - Do you know who knows?shadowJar - Create a combined JAR of project and runtime dependencies创建项目和运行时依赖项的组合JARVerification tasks——————branchConsistency - Ensures this branch is internally consistent. For example, that versions constants match released versions.确保这个分支内部一致。例如,版本常量匹配发布的版本。bwcTest - Runs backwards compatibility tests.运行向后兼容性测试。check - Runs all checks.运行所有检查。integTest - Multi-node tests多节点测试packagingTest - Tests yum/apt packages using vagrant and bats. 使用agrant and bats测试yum / apt包。 Specify the vagrant boxes to test using the gradle property ‘vagrant.boxes’. 指定要使用gradle属性’vagrant.boxes’测试的vagrant boxes. ‘sample’ can be used to test a single yum and apt box. ‘all’ can be used to “sample”可以用来测试单个yum和apt box。 test all available boxes. The available boxes are: [centos-6, centos-7, debian-8, debian-9, fedora-26, fedora-27, oel-6, oel-7, opensuse-42, sles-12, ubuntu-1404, ubuntu-1604]platformTest - Test unit and integ tests on different platforms using vagrant. 使用vagrant在不同平台上进行测试单元和集成测试。 Specify the vagrant boxes to test using the gradle property ‘vagrant.boxes’. 指定要使用gradle属性’vagrant.boxes’测试的vagrant boxes。 ‘all’ can be used to test all available boxes. The available boxes are: “all”可用于测试所有可用的boxes,可用的boxes有: [centos-6, centos-7, debian-8, debian-9, fedora-26, fedora-27, oel-6, oel-7, opensuse-42, sles-12, ubuntu-1404, ubuntu-1604]precommit - Runs all non-test checks. 运行所有非测试检查。run - Runs elasticsearch in the foreground 在前台运行elasticsearchstop - Stop any tasks from tests that still may be running 从仍然可能运行的测试中停止任何任务test - Runs unit tests with the randomized testing framework 使用随机测试框架运行单元测试vagrantCheckVersion - Check the Vagrant version 检查Vagrant versionvagrantSmokeTest - Smoke test the specified vagrant boxes 冒烟测试指定的vagrant boxesvirtualboxCheckVersion - Check the Virtualbox version 检查Virtualbox版本To see all tasks and more detail, run gradle tasks –all要查看所有任务和更多细节,请运行 run gradle tasks –allTo see more detail about a task, run gradle help –task <task>要查看任务的更多细节,请运行 gradle help –task <task> ...

January 10, 2019 · 4 min · jiezi

玩转Elasticsearch源码-一图看懂ES启动流程

开篇直接看图上图中虚线表示进入具体流程,实线表示下一步,为了后面讲解方便每个步骤都加了编号。先简单介绍下启动流程主要涉及的类:org.elasticsearch.bootstrap.Elasticsearch: 启动入口,main方法就在这个类里面,执行逻辑对应图中绿色部分org.elasticsearch.bootstrap.Bootstrap:包含主要启动流程代码,执行逻辑对应图中红色部分org.elasticsearch.node.Node:代表集群中的节点,执行逻辑对应图中蓝色部分流程讲解1. main方法2. 设置了一个空的SecurityManager:// we want the JVM to think there is a security manager installed so that if internal policy decisions that would be based on the // presence of a security manager or lack thereof act as if there is a security manager present (e.g., DNS cache policy)//我们希望JVM认为已经安装了一个安全管理器,这样,如果基于安全管理器的存在或缺少安全管理器的内部策略决策就会像有一个安全管理器一样(e.g.、DNS缓存策略) // grant all permissions so that we can later set the security manager to the one that we want//授予所有权限,以便稍后可以将安全管理器设置为所需的权限添加StatusConsoleListener到STATUS_LOGGER:We want to detect situations where we touch logging before the configuration is loaded . If we do this , Log 4 j will status log an error message at the error level . With this error listener , we can capture if this happens . More broadly , we can detect any error - level status log message which likely indicates that something is broken . The listener is installed immediately on startup , and then when we get around to configuring logging we check that no error - level log messages have been logged by the status logger . If they have we fail startup and any such messages can be seen on the console我们希望检测在加载配置之前进行日志记录的情况。如果这样做,log4j将在错误级别记录一条错误消息。使用这个错误监听器,我们可以捕捉到这种情况。更广泛地说,我们可以检测任何错误级别的状态日志消息,这些消息可能表示某个东西坏了。侦听器在启动时立即安装,然后在配置日志记录时,我们检查状态日志记录器没有记录错误级别的日志消息。如果它们启动失败,我们可以在控制台上看到任何此类消息。实例化Elasticsearch:Elasticsearch() { super(“starts elasticsearch”, () -> {}); // () -> {} 是启动前的回调 //下面解析version,daemonize,pidfile,quiet参数 versionOption = parser.acceptsAll(Arrays.asList(“V”, “version”), “Prints elasticsearch version information and exits”); daemonizeOption = parser.acceptsAll(Arrays.asList(“d”, “daemonize”), “Starts Elasticsearch in the background”) .availableUnless(versionOption); pidfileOption = parser.acceptsAll(Arrays.asList(“p”, “pidfile”), “Creates a pid file in the specified path on start”) .availableUnless(versionOption) .withRequiredArg() .withValuesConvertedBy(new PathConverter()); quietOption = parser.acceptsAll(Arrays.asList(“q”, “quiet”), “Turns off standard output/error streams logging in console”) .availableUnless(versionOption) .availableUnless(daemonizeOption); }3.注册ShutdownHook,用于关闭系统时捕获IOException到terminal shutdownHookThread = new Thread(() -> { try { this.close(); } catch (final IOException e) { try ( StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) { e.printStackTrace(pw); terminal.println(sw.toString()); } catch (final IOException impossible) { // StringWriter#close declares a checked IOException from the Closeable interface but the Javadocs for StringWriter // say that an exception here is impossible throw new AssertionError(impossible); } } }); Runtime.getRuntime().addShutdownHook(shutdownHookThread);然后调用beforeMain.run(),其实就是上面实例化Elasticsearch对象时创建的()->{} lambda表达式。4.进入Command类的mainWithoutErrorHandling方法 void mainWithoutErrorHandling(String[] args, Terminal terminal) throws Exception { final OptionSet options = parser.parse(args);//根据提供给解析器的选项规范解析给定的命令行参数 if (options.has(helpOption)) { printHelp(terminal); return; } if (options.has(silentOption)) {//terminal打印最少内容 terminal.setVerbosity(Terminal.Verbosity.SILENT); } else if (options.has(verboseOption)) {//terminal打印详细内容 terminal.setVerbosity(Terminal.Verbosity.VERBOSE); } else { terminal.setVerbosity(Terminal.Verbosity.NORMAL); } execute(terminal, options); }5.进入EnvironmentAwareCommand的execute方法protected void execute(Terminal terminal, OptionSet options) throws Exception { final Map<String, String> settings = new HashMap<>(); for (final KeyValuePair kvp : settingOption.values(options)) { if (kvp.value.isEmpty()) { throw new UserException(ExitCodes.USAGE, “setting [” + kvp.key + “] must not be empty”); } if (settings.containsKey(kvp.key)) { final String message = String.format( Locale.ROOT, “setting [%s] already set, saw [%s] and [%s]”, kvp.key, settings.get(kvp.key), kvp.value); throw new UserException(ExitCodes.USAGE, message); } settings.put(kvp.key, kvp.value); } //确保给定的设置存在,如果尚未设置,则从系统属性中读取它。 putSystemPropertyIfSettingIsMissing(settings, “path.data”, “es.path.data”); putSystemPropertyIfSettingIsMissing(settings, “path.home”, “es.path.home”); putSystemPropertyIfSettingIsMissing(settings, “path.logs”, “es.path.logs”); execute(terminal, options, createEnv(terminal, settings)); }6.进入InternalSettingsPreparer的prepareEnvironment方法,读取elasticsearch.yml并创建Environment。细节比较多,后面再细讲。7.判断是否有-v参数,没有则准备进入init流程 protected void execute(Terminal terminal, OptionSet options, Environment env) throws UserException { if (options.nonOptionArguments().isEmpty() == false) { throw new UserException(ExitCodes.USAGE, “Positional arguments not allowed, found " + options.nonOptionArguments()); } if (options.has(versionOption)) { //如果有 -v 参数,打印版本号后直接退出 terminal.println(“Version: " + Version.displayVersion(Version.CURRENT, Build.CURRENT.isSnapshot()) + “, Build: " + Build.CURRENT.shortHash() + “/” + Build.CURRENT.date() + “, JVM: " + JvmInfo.jvmInfo().version()); return; } final boolean daemonize = options.has(daemonizeOption); final Path pidFile = pidfileOption.value(options); final boolean quiet = options.has(quietOption); try { init(daemonize, pidFile, quiet, env); } catch (NodeValidationException e) { throw new UserException(ExitCodes.CONFIG, e.getMessage()); } }8.调用Bootstrap.init9.实例化Boostrap。保持keepAliveThread存活,可能是用于监控Bootstrap() { keepAliveThread = new Thread(new Runnable() { @Override public void run() { try { keepAliveLatch.await(); } catch (InterruptedException e) { // bail out } } }, “elasticsearch[keepAlive/” + Version.CURRENT + “]”); keepAliveThread.setDaemon(false); // keep this thread alive (non daemon thread) until we shutdown 保持这个线程存活(非守护进程线程),直到我们关机 Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { keepAliveLatch.countDown(); } }); }10.加载elasticsearch.keystore文件,重新创建Environment,然后调用LogConfigurator的静态方法configure,读取config目录下log4j2.properties然后配log4j属性11.创建pid文件,检查lucene版本,不对应则抛出异常 private static void checkLucene() { if (Version.CURRENT.luceneVersion.equals(org.apache.lucene.util.Version.LATEST) == false) { throw new AssertionError(“Lucene version mismatch this version of Elasticsearch requires lucene version [” + Version.CURRENT.luceneVersion + “] but the current lucene version is [” + org.apache.lucene.util.Version.LATEST + “]”); } }12.设置ElasticsearchUncaughtExceptionHandler用于打印fatal日志 // install the default uncaught exception handler; must be done before security is // initialized as we do not want to grant the runtime permission // 安装默认未捕获异常处理程序;必须在初始化security之前完成,因为我们不想授予运行时权限 // setDefaultUncaughtExceptionHandler Thread.setDefaultUncaughtExceptionHandler( new ElasticsearchUncaughtExceptionHandler(() -> Node.NODE_NAME_SETTING.get(environment.settings())));13.进入Boostrap.setup14.spawner.spawnNativePluginControllers(environment);尝试为给定模块生成控制器(native Controller)守护程序。 生成的进程将通过其stdin,stdout和stderr流保持与此JVM的连接,但对此包之外的代码不能使用对这些流的引用。15.初始化本地资源 initializeNatives():检查用户是否作为根用户运行,是的话抛异常;系统调用和mlockAll检查;尝试设置最大线程数,最大虚拟内存,最大FD等。初始化探针initializeProbes(),用于操作系统,进程,jvm的监控。16.又加一个ShutdownHook if (addShutdownHook) { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { try { IOUtils.close(node, spawner); LoggerContext context = (LoggerContext) LogManager.getContext(false); Configurator.shutdown(context); } catch (IOException ex) { throw new ElasticsearchException(“failed to stop node”, ex); } } }); }17.比较简单,直接看代码 try { // look for jar hell JarHell.checkJarHell(); } catch (IOException | URISyntaxException e) { throw new BootstrapException(e); } // Log ifconfig output before SecurityManager is installed IfConfig.logIfNecessary(); // install SM after natives, shutdown hooks, etc. try { Security.configure(environment, BootstrapSettings.SECURITY_FILTER_BAD_DEFAULTS_SETTING.get(settings)); } catch (IOException | NoSuchAlgorithmException e) { throw new BootstrapException(e); }18.实例化Node重写validateNodeBeforeAcceptingRequests方法。具体主要包括三部分,第一是启动插件服务(es提供了插件功能来进行扩展功能,这也是它的一个亮点),加载需要的插件,第二是配置node环境,最后就是通过guice加载各个模块。下面22~32就是具体步骤。19.进入Boostrap.start20.node.start启动节点21.keepAliveThread.start22.Node实例化第一步,创建NodeEnvironment23.生成nodeId,打印nodeId,nodeName和jvmInfo和进程信息24.创建 PluginsService 对象,创建过程中会读取并加载所有的模块和插件25.又创建Environment // create the environment based on the finalized (processed) view of the settings 根据设置的最终(处理)视图创建环境 // this is just to makes sure that people get the same settings, no matter where they ask them from 这只是为了确保人们得到相同的设置,无论他们从哪里询问 this.environment = new Environment(this.settings, environment.configFile());26.创建ThreadPool,然后给DeprecationLogger设置ThreadContext27.创建NodeClient,用于执行actions28.创建各个Service:ResourceWatcherService、NetworkService、ClusterService、IngestService、ClusterInfoService、UsageService、MonitorService、CircuitBreakerService、MetaStateService、IndicesService、MetaDataIndexUpgradeService、TemplateUpgradeService、TransportService、ResponseCollectorService、SearchTransportService、NodeService、SearchService、PersistentTasksClusterService29.创建并添加modules:ScriptModule、AnalysisModule、SettingsModule、pluginModule、ClusterModule、IndicesModule、SearchModule、GatewayModule、RepositoriesModule、ActionModule、NetworkModule、DiscoveryModule30.Guice绑定和注入对象31.初始化NodeClient client.initialize(injector.getInstance(new Key<Map<GenericAction, TransportAction>>() {}), () -> clusterService.localNode().getId());32.初始化rest处理器,这个非常重要,后面会专门讲解if (NetworkModule.HTTP_ENABLED.get(settings)) { logger.debug(“initializing HTTP handlers …”); // 初始化http handler actionModule.initRestHandlers(() -> clusterService.state().nodes()); }33.修改状态为State.STARTED34.启动pluginLifecycleComponents35.通过 injector 获取各个类的对象,调用 start() 方法启动(实际进入各个类的中 doStart 方法)LifecycleComponent、IndicesService、IndicesClusterStateService、SnapshotsService、SnapshotShardsService、RoutingService、SearchService、MonitorService、NodeConnectionsService、ResourceWatcherService、GatewayService、Discovery、TransportService36.启动HttpServerTransport和TransportService并绑定端口if (WRITE_PORTS_FILE_SETTING.get(settings)) { if (NetworkModule.HTTP_ENABLED.get(settings)) { HttpServerTransport http = injector.getInstance(HttpServerTransport.class); writePortsFile(“http”, http.boundAddress()); } TransportService transport = injector.getInstance(TransportService.class); writePortsFile(“transport”, transport.boundAddress()); }总结本文只是讲解了ES启动的整体流程,其中很多细节会在本系列继续深入讲解ES的源码读起来还是比较费劲的,流程比较长,没有Spring源码读起来体验好,这也是开源软件和开源框架的区别之一,前者会遇到大量的流程细节,注重具体功能的实现,后者有大量扩展点,更注重扩展性。为什么要读开源源码?1.知道底层实现,能够更好地使用,出问题能够快速定位和解决。2.学习别人优秀的代码和处理问题的方式,提高自己的系统设计能力。3.有机会可以对其进行扩展和改造。 ...

January 10, 2019 · 4 min · jiezi

玩转Elasticsearch源码-使用Intellij IDEA和remote debug调试源代码

开篇学习源码第一步就是搭建调试环境,但是看了网上大部分Elasticsearch调试方式都是配置各种环境变量然后直接启动Main方法,而且还各种报错。今天提供新的方式--remote debug来避免这些麻烦。步骤环境首先要安装jdk8,gradle和Intellij IDEA源码下载拉取代码,checkout到想要调试的版本(这里切到v6.1.0,需要注意的是不同ES分支对gradle版本要求不一样,可以到README文件中查看对应到gradle版本要求)git clone git@github.com/elastic/elasticsearchcd elasticsearchgit checkout v6.1.0导入到IDEA执行gradle idea,成功后会提示BUILD SUCCESSFUL,然后导入到IDEA:test:fixtures:hdfs-fixture:idea:test:fixtures:krb5kdc-fixture:ideaModule:test:fixtures:krb5kdc-fixture:idea:test:fixtures:old-elasticsearch:ideaModule:test:fixtures:old-elasticsearch:ideaBUILD SUCCESSFULTotal time: 2 mins 2.159 secs使用gradle启动Elasticsearchgradle run –debug-jvm执行成功后是这样的,其中8000就是远程debug端口配置remote debug点击IDEA的Edit Configurations,再点击➕填写主机和端口,Name是配置名称,可以自定义(我这里就填es),点OK保存配置搜一下源码里面Elasticsearch类,,看到Main方法,先打个断点等会看效果最后再点下绿色小虫子启动debug是不是在断点停下来了跳过断点再看下控制台,是不是启动日志都出来了再验证下是否启动成功原理一切源于被称作 Agent 的东西。JVM有一种特性,可以允许外部的库(Java或C++写的libraries)在运行时注入到 JVM 中。这些外部的库就称作 Agents, 他们有能力修改运行中 .class 文件的内容。这些 Agents 拥有的这些 JVM 的功能权限, 是在 JVM 内运行的 Java Code 所无法获取的, 他们能用来做一些有趣的事情,比如修改运行中的源码, 性能分析等。 像 JRebel 工具就是用了这些功能达到魔术般的效果。传递一个 Agent Lib 给 JVM, 通过添加 agentlib:libname[=options] 格式的启动参数即可办到。像上面的远程调试我们用的就是 -agentlib:jdwp=… 来引入 jdwp 这个 Agent 的。jdwp 是一个 JVM 特定的 JDWP(Java Debug Wire Protocol) 可选实现,用来定义调试者与运行JVM之间的通讯,它的是通过 JVM 本地库的 jdwp.so 或者 jdwp.dll 支持实现的。简单来说, jdwp agent 会建立运行应用的 JVM 和调试者(本地或者远程)之间的桥梁。既然他是一个Agent Library, 它就有能力拦截运行的代码。在 JVM 架构里, debugging 功能在 JVM 本身的内部是找不到的,它是一种抽象到外部工具的方式(也称作调试者 debugger)。这些调试工具或者运行在 JVM 的本地 或者在远程。这是一种解耦,模块化的架构。关于Agent还有很多值得研究的细节,甚至基于JVMTI自己实现。参考https://www.ibm.com/developerworks/cn/java/j-lo-jpda2/index.html ...

January 9, 2019 · 1 min · jiezi

Elasticsearch Query DSL基础介绍

查询语法(Query DSL)Elasticsearch提供标准RESTful风格的查询DSL来定义查询。可以将查询 DSL 看作是由两种子句组成的查询的 AST (Abstract Syntax Tree) :Leaf query clauses叶查询语句(可以理解为SQL里的where查询)在特定字段中查找特定值,例如 match,term或 range查询,这些查询同时也可以单独使用。Compound query clauses复合查询语句可以组合Leaf Query和Compound Query,用于以组合多个查询 ,或者更改其行为(例如 constant_score查询)。查询语句行为的不同取决于它们是在查询上下文还是过滤器上下文中使用。查询上下文和过滤上下文查询子句的行为取决于它是在查询上下文中使用还是在过滤上下文中使用:查询上下文在查询上下文中使用的查询子句会回答这个问题:“此文档与此查询子句的匹配程度怎么样?” 除了决定文档是否匹配之外,查询子句还会计算一个评分,用来表示文档的匹配度。只要将查询子句传递给query参数(比如Search API 中的query参数) ,查询上下文就会生效。过滤上下文在过滤上下文中,查询子句回答以下问题:“此文档与此查询子句匹配吗?” 答案只有是或否,不计算评分。 过滤上下文主要用于过滤结构化的数据,例如:这个时间是否在2015到2016年之间?状态是不是已发布?经常使用的过滤器将被 Elasticsearch 自动缓存,以提高性能。只要将查询子句传递给filter(如 bool 查询中的 filter 或 must not,constant_score 查询中的 filter 或aggregation中的filter) ,过滤上下文就会生效。一些例子那怎么判断自己的dsl是查询上下文还是过滤上下文呢?以下给出一些解释基础查询都属于查询上下文{ “query”:{ “term/range/terms/match/match_phrase”:{} }}constant_score比较特殊,虽然看着是基础查询,但是他是固定评分查询,所以也属于过滤上下文{ “query”:{ “constant_score”:{} }}bool查询中的filter属于过滤上下文{ “query”:{ “bool”:{ //filter这里可以定义对象,单个过滤子句,也可以定义数组,多个过滤条件 “filter”:[] “filter”:{} } }}bool查询中的must not属于过滤上下文{ “query”:{ “bool”:{ “mustnot”:[] } }}agg聚合查询中的filter agg也属于过滤上下文{ “aggs”:{ “t_shirts” : { “filter” : { “term”: { “type”: “t-shirt” } }, “aggs” : { “avg_price” : { “avg” : { “field” : “price” } } } } }}那么问题来了,如果我在bool/filter中嵌套了boolean/must子句,那这里的must子句到底是查询上下文还是过滤上下文呢?{ “query”:{ “bool”:{ “filter”:{ “bool”:{ “must”:{ //query cause } } } } }}答案是处于过滤上下文,上下文的判断基于顶层语句 ...

January 7, 2019 · 1 min · jiezi

ElastAlert日志告警(邮件、企业微信)

ElastAlert 工作原理It works by combining Elasticsearch with two types of components, rule types and alerts. Elasticsearch is periodically queried and the data is passed to the rule type, which determines when a match is found. When a match occurs, it is given to one or more alerts, which take action based on the match.周期性的查询Elastsearch并且将数据传递给规则类型,规则类型定义了需要查询哪些数据。当一个规则匹配触发,就会给到一个或者多个的告警,这些告警具体会根据规则的配置来选择告警途径,就是告警行为,比如邮件、企业微信等elastalert文档地址安装使用官网的pip install elastalert安装时,我这里报错,所以改用了git clone到本地的方式ElastAlert官方安装流程如果没有pip安装工具看下面流程pip 安装流程依赖yum install python-develsudo yum install openssl-devel 配置Next, open up config.yaml.example. In it, you will find several configuration options. ElastAlert may be run without changing any of these settings.rules_folder is where ElastAlert will load rule configuration files from. It will attempt to load every .yaml file in the folder. Without any valid rules, ElastAlert will not start. ElastAlert will also load new rules, stop running missing rules, and restart modified rules as the files in this folder change. For this tutorial, we will use the example_rules folder.这里我们复制config.yaml.example为config.yaml,新建目录rulescp config.yaml.example config.yamlmkdir rules配置ES服务器信息修改config.yaml文件如下,其他的配置不需要修改# 这里指定了我们配置的规则的目录rules_folder: rules# How often ElastAlert will query Elasticsearch# The unit can be anything from weeks to seconds# 每次间隔1分钟触发一次run_every: minutes: 1# ElastAlert will buffer results from the most recent# period of time, in case some log sources are not in real timebuffer_time: minutes: 15# The Elasticsearch hostname for metadata writeback# Note that every rule can have its own Elasticsearch host# 配置elasticsearch 的地址和端口es_host: xxx.xx.xxx.xx# The Elasticsearch portes_port: 9200配置rules规则里面已经给出了配置的范例,这里我们使用frequency的配置。要做根据频率变化的告警。[example_rules]# tree.├── example_cardinality.yaml├── example_change.yaml├── example_frequency.yaml├── example_new_term.yaml├── example_opsgenie_frequency.yaml├── example_percentage_match.yaml├── example_single_metric_agg.yaml├── example_spike.yaml└── jira_acct.txt复制frequency的配置文件到新的rules目录cp example_rules/example_frequency.yaml rules/cd rulesmv example_frequency.yaml app_frequency_mail.yaml基于邮件的配置邮件告警样例这里会详细介绍下配置,但是只会用到个别字段# Alert when the rate of events exceeds a threshold# (Optional)# Elasticsearch host# 无需修改使用全局# es_host: elasticsearch.example.com# (Optional)# Elasticsearch port# es_port: 14900# (OptionaL) Connect with SSL to Elasticsearch#use_ssl: True# (Optional) basic-auth username and password for Elasticsearch#es_username: someusername#es_password: somepassword# (Required)# Rule name, must be unique# 这里要定义一个规则名称,而且要unique唯一name: app frequency rule mail# (Required)# Type of alert.# the frequency rule type alerts when num_events events occur with timeframe time# 定义规则类型type: frequency# (Required)# Index to search, wildcard supported# 需要检索的日志索引index: logstash-app-prod*# (Required, frequency specific)# Alert when this many documents matching the query occur within a timeframe# 命中五次num_events: 5# (Required, frequency specific)# num_events must occur within this amount of time to trigger an alert# 十分钟之内命中五次,就算是触发一次规则timeframe:# hours: 4 minutes: 10# 按照某个字段进行聚合,意思是aggreation_key会和rule的名称拼接在一起作为一个组,单独发送告警,相同的mesage是一个组#aggregation_key:# - message# 聚合2分钟aggregation: minutes: 2# 不进行重复提醒的字段,和realert联合使用,30分钟内这个query_key只告警一次query_key: - messagerealert: minutes: 30# (Required)# A list of Elasticsearch filters used for find events# These filters are joined with AND and nested in a filtered query# For more info: http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html# 这里按照正则匹配来查询,可以看query-dsl里面的官方文档filter:- query: regexp: category: “error-”#- term:# category: “error-”# 邮箱设置smtp_host: smtp.qq.comsmtp_port: 465smtp_ssl: true# 发件箱的地址from_addr: “xx@qq.com”# 这个是指向的邮箱验证的配置文件,有用户名、和密码,对于qq而言,这里面的密码是授权码,可以通过qq邮箱设置里面,开启smtp的时候查看smtp_auth_file: /home/elastalert/smtp_auth_file.yaml# (Required)# The alert is use when a match is found# 定义告警类型是邮件提醒alert:- “email”# 下面这些不配置,会发送一个默认的告警模板,纯文字太丑了,所以我们进行了格式化,发送一个html格式的email_format: htmlalert_subject: “app 正式环境 告警 {}”# 这里使用python 的format 进行格式化alert_subject_args:- category# 如果这个去掉,那么发送alert_text的同时,也会发送默认模板内容alert_text_type: alert_text_only# 下面这个是自己配置的alert_text: “<div style=‘display:block;background-color: red;padding: 10px;border-radius: 5px;color: white;font-weight: bold;’ > <p>{}</p></div><br><a href=‘这里填写自己的kibana地址href’ target=’_blank’ style=‘padding: 8px 16px;background-color: #46bc99;text-decoration:none;color: white;border-radius: 5px;’>Click to Kibana</a><br><h3>告警详情</h3><table><tr><td style=‘padding:5px;text-align: right;font-weight: bold;border-radius: 5px;background-color: #eef;’>@timestamp:</td><td style=‘padding:5px;border-radius: 5px;background-color: #eef;’>{}</td></tr><tr><td style=‘padding:5px;text-align: right;font-weight: bold;border-radius: 5px;background-color: #eef;’>@version:</td><td style=‘padding:5px;border-radius: 5px;background-color: #eef;’>{}</td></tr><tr><td style=‘padding:5px;text-align: right;font-weight: bold;border-radius: 5px;background-color: #eef;’>_id:</td><td style=‘padding:5px;border-radius: 5px;background-color: #eef;’>{}</td></tr><tr><td style=‘padding:5px;text-align: right;font-weight: bold;border-radius: 5px;background-color: #eef;’>_index:</td><td style=‘padding:5px;border-radius: 5px;background-color: #eef;’>{}</td></tr><tr><td style=‘padding:5px;text-align: right;font-weight: bold;border-radius: 5px;background-color: #eef;’>_type:</td><td style=‘padding:5px;border-radius: 5px;background-color: #eef;’>{}</td></tr><tr><td style=‘padding:5px;text-align: right;font-weight: bold;border-radius: 5px;background-color: #eef;’>appType:</td><td style=‘padding:5px;border-radius: 5px;background-color: #eef;’>{}</td></tr><tr><td style=‘padding:5px;text-align: right;font-weight: bold;border-radius: 5px;background-color: #eef;’>appVersion:</td><td style=‘padding:5px;border-radius: 5px;background-color: #eef;’>{}</td></tr><tr><td style=‘padding:5px;text-align: right;font-weight: bold;border-radius: 5px;background-color: #eef;’>business:</td><td style=‘padding:5px;border-radius: 5px;background-color: #eef;’>{}</td></tr><tr><td style=‘padding:5px;text-align: right;font-weight: bold;border-radius: 5px;background-color: #eef;’>category:</td><td style=‘padding:5px;border-radius: 5px;background-color: #eef;’>{}</td></tr><tr><td style=‘padding:5px;text-align: right;font-weight: bold;border-radius: 5px;background-color: #eef;’>geoip:</td><td style=‘padding:5px;border-radius: 5px;background-color: #eef;’>{}</td></tr><tr><td style=‘padding:5px;text-align: right;font-weight: bold;border-radius: 5px;background-color: #eef;’>guid:</td><td style=‘padding:5px;border-radius: 5px;background-color: #eef;’>{}</td></tr><tr><td style=‘padding:5px;text-align: right;font-weight: bold;border-radius: 5px;background-color: #eef;’>host:</td><td style=‘padding:5px;border-radius: 5px;background-color: #eef;’>{}</td></tr><tr><td style=‘padding:5px;text-align: right;font-weight: bold;border-radius: 5px;background-color: #eef;’>message:</td><td style=‘padding:10px 5px;border-radius: 5px;background-color: red;color: white;’>{}</td></tr><tr><td style=‘padding:5px;text-align: right;font-weight: bold;border-radius: 5px;background-color: #eef;’>num_hits:</td><td style=‘padding:5px;border-radius: 5px;background-color: #eef;’>{}</td></tr><tr><td style=‘padding:5px;text-align: right;font-weight: bold;border-radius: 5px;background-color: #eef;’>num_matches:</td><td style=‘padding:5px;border-radius: 5px;background-color: #eef;’>{}</td></tr><tr><td style=‘padding:5px;text-align: right;font-weight: bold;border-radius: 5px;background-color: #eef;’>path:</td><td style=‘padding:5px;border-radius: 5px;background-color: #eef;’>{}</td></tr><tr><td style=‘padding:5px;text-align: right;font-weight: bold;border-radius: 5px;background-color: #eef;’>server:</td><td style=‘padding:5px;border-radius: 5px;background-color: #eef;’>{}</td></tr><tr><td style=‘padding:5px;text-align: right;font-weight: bold;border-radius: 5px;background-color: #eef;’>uid:</td><td style=‘padding:5px;border-radius: 5px;background-color: #eef;’>{}</td></tr><tr><td style=‘padding:5px;text-align: right;font-weight: bold;border-radius: 5px;background-color: #eef;’>uri:</td><td style=‘padding:5px;border-radius: 5px;background-color: #eef;’>{}</td></tr><tr><td style=‘padding:5px;text-align: right;font-weight: bold;border-radius: 5px;background-color: #eef;’>userAgent:</td><td style=‘padding:5px;border-radius: 5px;background-color: #eef;’>{}</td></tr></table>”# 这里需要配置area_text中出现的各个字段,其实跟sprintf一样按照顺序格式化的alert_text_args:- message- “@timestamp”- “@version”- _id- _index- _type- appType- appVersion- business- category- geoip- guid- host- message- num_hits- num_matches- path- server- uid- uri- userAgent# (required, email specific)# a list of email addresses to send alerts to# 这里配置收件人的邮箱email:- “xxx@xxx.com"邮箱验证配置然后来看下邮箱验证的配置,也就是smtp_auth_file.yaml# 发件箱的qq邮箱地址,也就是用户名user: xxx@qq.com# 不是qq邮箱的登录密码,是授权码password: uxmmmmtefwqeibcjd执行的时候,很简单,稍后我们看下配置supervisor高可用nohup python -m elastalert.elastalert –rule app_frequency_mail.yaml –verbose &配置企业微信告警需要信息部门应用新建一个接收日志的部门,会分配部门id新建一个发送日志的应用程序,会有应用id在应用的可见配置里面,配置上相关人员这里我们使用一个开源企业微信发送插件git地址:https://github.com/anjia0532/elastalert-wechat-plugin插件使用说明https://anjia0532.github.io/2017/02/16/elastalert-wechat-plugin/ 按照创建邮件告警规则一样,创建新的规则告警文件。其中从alert开始配置成新的告警方式alert:- “elastalert_modules.wechat_qiye_alert.WeChatAlerter"alert_text: “======start====== \n索引:{}\n服务器:{}\n接口:{}\n告警:\n{}“alert_text_type: alert_text_only# 企业微信告警的数据不需要太多,太长alert_text_args:- _index- server- path- message#后台登陆后【设置】->【权限管理】->【普通管理组】->【创建并设置通讯录和应用权限】->【CorpID,Secret】#设置微信企业号的appidcorp_id: wxea4f5f73xxxx#设置微信企业号的Secretsecret: “xxxxxBGnxxxxxxxxxrBNHxxxxxxxE”#后台登陆后【应用中心】->【选择应用】->【应用id】#设置微信企业号应用idagent_id: 100xxxx#部门idparty_id: 14#用户微信号user_id: “@all”# 标签id#tag_id:企业微信配置注意查看作者的另一个项目https://github.com/anjia0532/weixin-qiye-alert 发现对于user_id,tag_id的配置是有规则的:如果指定标签名称不存在,会自动通过api创建一个标签(处于锁定状态),需要管理员,手动解锁,并且添加成员 如果指定标签下没有成员(标签添加部门无效),则会根据cp.properties指定的部门idPartyId和人员idUserId进行发送 如果部门下没有成员,而且指定的用户也没有关注该企业号,则会将信息推送给该企业号全部已关注成员,测试时需谨记这正合我们的心意,因为我们不会只给一个人发送消息!我们需要的是,发给所有日志告警部门的小伙伴,所以我们要怎么做呢?!!经过测试,我将user_id注释掉,并不能发送消息, 理想状态不应该是删掉user_id,就只发送给全部门么?然而并不是哒~,我们查看下源码(发现作者简直是每一行代码都有注释太好啦)我们会看到作者的注释,全部用@all~~ ,所以能看到上面user_id 我配置的是@all啦self.party_id = self.rule.get(‘party_id’) #部门idself.user_id = self.rule.get(‘user_id’, ‘’) #用户id,多人用 | 分割,全部用 @allself.tag_id = self.rule.get(’tag_id’, ‘’) #标签id企业微信告警样例 ...

December 28, 2018 · 4 min · jiezi

ELK 原理&部署过程

一图胜千言基础架构工作原理Logstash工作原理Logstash工作流程ELK整体部署图ELK 安装配置简化过程1 基本配置 vim /etc/hosts 192.168.2.61 master-node 192.168.2.62 data-node1 192.168.2.63 data-node2 wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.0.0.rpm rpm -ivh elasticsearch-6.0.0.rpm elasticsearch.yml jvm.options log4j2.properties vim /etc/elasticsearch/elasticsearch.yml cluster.name: master-node # 集群中的名称 node.name: master # 该节点名称 node.master: true # 意思是该节点为主节点 node.data: false # 表示这不是数据节点 network.host: 0.0.0.0 # 监听全部ip,在实际环境中应设置为一个安全的ip http.port: 9200 # es服务的端口号 discovery.zen.ping.unicast.hosts: [“192.168.2.61”, “192.168.2.62”, “192.168.2.63”] # 配置自动发现 scp /etc/elasticsearch/elasticsearch.yml data-node1:/tmp/ scp /etc/elasticsearch/elasticsearch.yml data-node2:/tmp/ cp /tmp/elasticsearch.yml /etc/elasticsearch/elasticsearch.yml systemctl start elasticsearch.service curl ‘192.168.2.61:9200/_cluster/health?pretty’ curl ‘192.168.2.61:9200/_cluster/state?pretty'2 kibana配置 wget https://artifacts.elastic.co/downloads/kibana/kibana-6.0.0-x86_64.rpm rpm -ivh kibana-6.0.0-x86_64.rpm vim /etc/kibana/kibana.yml server.port: 5601 # 配置kibana的端口 server.host: 192.168.2.61 # 配置监听ip # 配置es服务器的ip,如果是集群则配置该集群中主节点的ip elasticsearch.url: “http://192.168.2.61:9200” # 配置kibana的日志文件路径,不然默认是messages里记录日志 logging.dest: /var/log/kibana.log touch /var/log/kibana.log; chmod 777 /var/log/kibana.log systemctl start kibana http://192.168.2.61:5601/ 3 logstash配置 192.168.2.62 wget https://artifacts.elastic.co/downloads/logstash/logstash-6.0.0.rpm rpm -ivh logstash-6.0.0.rpm vim /etc/logstash/conf.d/syslog.conf input { # 定义日志源 syslog { type => “system-syslog” # 定义类型 port => 10514 # 定义监听端口 } } output { # 定义日志输出 stdout { codec => rubydebug # 将日志输出到当前的终端上显示 } } cd /usr/share/logstash/bin 检查配置 ./logstash –path.settings /etc/logstash/ -f /etc/logstash/conf.d/syslog.conf –config.test_and_exit 配置kibana服务器的ip以及配置的监听端口 vim /etc/rsyslog.conf #### RULES #### . @@192.168.2.62:10514 systemctl restart rsyslog 指定配置文件,启动logstash cd /usr/share/logstash/bin ./logstash –path.settings /etc/logstash/ -f /etc/logstash/conf.d/syslog.conf logstash收集nginx日志vim /etc/logstash/conf.d/nginx.conf input { file { # 指定一个文件作为输入源 path => “/var/log/nginx/access.log” # 指定文件的路径 start_position => “beginning” # 指定何时开始收集 type => “nginx” # 定义日志类型,可自定义 }}filter { # 配置过滤器 grok { match => { “message” => “%{IPORHOST:http_host} %{IPORHOST:clientip} - %{USERNAME:remote_user} [%{HTTPDATE:timestamp}] "(?:%{WORD:http_verb} %{NOTSPACE:http_request}(?: HTTP/%{NUMBER:http_version})?|%{DATA:raw_http_request})" %{NUMBER:response} (?:%{NUMBER:bytes_read}|-) %{QS:referrer} %{QS:agent} %{QS:xforwardedfor} %{NUMBER:request_time:float}”} # 定义日志的输出格式 } geoip { source => “clientip” }}output { stdout { codec => rubydebug } elasticsearch { hosts => [“192.168.2.61:9200”] index => “nginx-test-%{+YYYY.MM.dd}” }}cd /usr/share/logstash/bin./logstash –path.settings /etc/logstash/ -f /etc/logstash/conf.d/nginx.conf –config.test_and_exitcd /etc/nginx/http_virtual_host.dvim elk.confserver { listen 80; server_name elk.test.com; location / { proxy_pass http://192.168.2.61:5601; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } access_log /tmp/elk_access.log main2;}vim log_format main2 ‘$http_host $remote_addr - $remote_user [$time_local] “$request” ’ ‘$status $body_bytes_sent “$http_referer” ’ ‘"$http_user_agent" “$upstream_addr” $request_time’;nginx -tnginx -s reload配置hosts 192.168.2.62 elk.eichong.comls /var/log/nginx/access.logwc -l !$重启logstash服务,生成日志的索引systemctl restart logstash重启完成后,在es服务器上检查是否有nginx-test开头的索引生成curl ‘192.168.2.61:9200/_cat/indices?v’nginx-test索引已经生成了,那么这时就可以到kibana上配置该索引managent index patterns create index patternsdiscoverhttp://192.168.2.61:5601/status 查看状态最新版本yum安装001 elasticsearchrpm –import https://artifacts.elastic.co/GPG-KEY-elasticsearchvim /etc/yum.repos.d/elasticsearch.repo[elasticsearch-6.x]name=Elasticsearch repository for 6.x packagesbaseurl=https://artifacts.elastic.co/packages/6.x/yumgpgcheck=1gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearchenabled=1autorefresh=1type=rpm-mdyum install elasticsearch -y002 kibanavim /etc/yum.repos.d/kibana.repo[kibana-6.x]name=Kibana repository for 6.x packagesbaseurl=https://artifacts.elastic.co/packages/6.x/yumgpgcheck=1gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearchenabled=1autorefresh=1type=rpm-mdyum install kibana -y003 logstashvim /etc/yum.repos.d/logstash.repo[logstash-6.x]name=Elastic repository for 6.x packagesbaseurl=https://artifacts.elastic.co/packages/6.x/yumgpgcheck=1gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearchenabled=1autorefresh=1type=rpm-md主要配置文件001 elasticsearchcat /etc/elasticsearch/elasticsearch.yml |grep ^[^#]cluster.name: my-elknode.name: masterpath.data: /var/lib/elasticsearchpath.logs: /var/log/elasticsearchnetwork.host: 0.0.0.0http.port: 9200discovery.zen.ping.unicast.hosts: [“172.19.1.216”, “172.19.1.217”]002 kibanacat /etc/kibana/kibana.yml |grep ^[^#]server.port: 5601server.host: “172.19.1.216"elasticsearch.url: “http://172.19.1.216:9200"logging.dest: /var/log/kibana.log # 文件需创建并授权003 logstash汉化https://github.com/anbai-inc/Kibana_Hanization其他优秀博客https://www.cnblogs.com/kevingrace/p/5919021.htmlhttp://blog.51cto.com/zero01/2079879 ...

December 27, 2018 · 2 min · jiezi

elasticsearch,golang客户端聚合查询

目前在做的监控项目中有个对es的聚合查询的需求,需要用go语言实现,需求就是查询某个IP在一个时间范围内,各个监控指标取时间单位内的平均值。有点拗口,如下是es的查询语句,可以很明显的看到是要聚合cpu和mem两个field。另外,时区必须要加上,否则少8小时,你懂的。GET /monitor_hf/v1/_search{ “query”: { “bool”: { “must”: [ { “term”: { “ip”: { “value”: “192.168.1.100” } } }, { “range”: { “datatime”: { “gte”: 1545634340000, “lte”: “now” } } } ] } }, “size”: 0, “aggs”: { “AVG_Metric”: { “date_histogram”: { “field”: “datatime”, “interval”: “day”, “time_zone”: “Asia/Shanghai” , “format”: “yyyy-MM-dd HH:mm:ss” }, “aggs”: { “avg_mem”: { “avg”: { “field”: “mem” } },“avg_cpu”:{ “avg”: { “field”: “cpu” } } } } }}单条数据的内容大概是这样:datatime是毫秒级的时间戳,索引的mapping一定要是date类型,否则不能做聚合。{ “cpu”:5, “mem”:77088, “datatime”:1545702661000, “ip”:“192.168.1.100”}查询出来的结果呢,长这个样子:可以看到,在buckets中查询出了3条数据,几个字段的意思:key_as_string与key:format后的时间和毫秒时间戳doc_count:聚合了多少的docavg_mem和avg_cpu:查询语句中定义的别名。{ “took”: 4, “timed_out”: false, “_shards”: { “total”: 5, “successful”: 5, “skipped”: 0, “failed”: 0 }, “hits”: { “total”: 2477, “max_score”: 0, “hits”: [] }, “aggregations”: { “AVG_Metric”: { “buckets”: [ { “key_as_string”: “2018-12-24 00:00:00”, “key”: 1545580800000, “doc_count”: 402, “avg_mem”: { “value”: 71208.1592039801 }, “avg_cpu”: { “value”: 4.338308457711443 } }, { “key_as_string”: “2018-12-25 00:00:00”, “key”: 1545667200000, “doc_count”: 1258, “avg_mem”: { “value”: 77958.16852146263 }, “avg_cpu”: { “value”: 4.639904610492846 } }, { “key_as_string”: “2018-12-26 00:00:00”, “key”: 1545753600000, “doc_count”: 817, “avg_mem”: { “value”: 86570.06609547124 }, “avg_cpu”: { “value”: 4.975520195838433 } } ] } }}下面使用go语言的es客户端“github.com/olivere/elastic”来实现相同的查询需求,我用的是v6版本:package mainimport ( “github.com/olivere/elastic” “context” “encoding/json” “fmt”)type Aggregations struct { AVG_Metric AVG_Metric json:"AVG_Metric"}type AVG_Metric struct { Buckets []Metric json:"buckets"}type Metric struct { Key int64 json:"key" Doc_count int64 json:"doc_count" Avg_mem Value json:"avg_mem" Avg_cpu Value json:"avg_cpu"}type Value struct { Value float64 json:"value"}var client, _ = elastic.NewSimpleClient(elastic.SetURL(“http://127.0.0.1:9200”))func main() { //指定ip和时间范围 boolSearch := elastic.NewBoolQuery(). Filter(elastic.NewTermsQuery(“ip”, “192.168.1.100”),elastic.NewRangeQuery(“datatime”). Gte(1545634340000).Lte(“now”)) //需要聚合的指标 mem := elastic.NewAvgAggregation().Field(“mem”) cpu := elastic.NewAvgAggregation().Field(“cpu”) //单位时间和指定字段 aggs := elastic.NewDateHistogramAggregation(). Interval(“day”). Field(“datatime”). TimeZone(“Asia/Shanghai”). SubAggregation(“avg_mem”, mem). SubAggregation(“avg_cpu”, cpu) //查询语句 result, _ := client.Search(). Index(“monitor_hf”). Type(“v1”). Query(boolSearch). Size(0). Aggregation(“AVG_Metric”, aggs). Do(context.Background()) //结果输出: //第一种方式 var m *[]Metric term, _ := result.Aggregations.Terms(“AVG_Metric”) for _, bucket := range term.Aggregations { b, _ := bucket.MarshalJSON() //fmt.Println(string(b)) json.Unmarshal(b, &m) for _, v := range *m { fmt.Println(v) } } //第二种方式 var a *Aggregations b, _ := json.Marshal(result.Aggregations) //fmt.Println(string(b)) json.Unmarshal(b, &a) for _, v := range a.AVG_Metric.Buckets { fmt.Println(v) }}关于聚合结果的输出,遇到了些问题,网上也搜索不到,官方也没找到相应的例子。不过总算试出两个可行的,比较推荐第一种,可以少定义两个结构体。 ...

December 26, 2018 · 2 min · jiezi

springboot 整合 elasticsearch 失败

Failed to instantiate [org.elasticsearch.client.transport.TransportClient]: Factory method ’elasticsearchClient’ threw exception; nested exception is java.lang.IllegalStateException: availableProcessors is already set to [4], rejecting [4]@SpringBootApplicationpublic class SpringBootExampleApplication { public static void main(String[] args) { System.setProperty(“es.set.netty.runtime.available.processors”,“false”); SpringApplication.run(SpringbootexampleApplication.class, args); }}参考链接 elasticsearch5.6.1.集成springboot 遇到的坑

December 25, 2018 · 1 min · jiezi

es_分组-分页-TransportClient实现

说在前面:Elasticsearch Java API 有四种实现方式:分别是 TransportClient、RestClient、Jest、Spring Data Elasticsearch。本文使用第一种方式,也就是 TransportClient 的方式进行实现。想要了解其他三种的方式可以看一下这篇文章:https://blog.csdn.net/qq_3331…总结:1、group 之后不能自动分页,需要手动设置;2、size 需要指定,否则会出错。group 之后分页、排序实现需要手动截取分页对应范围内的数据。比如:这里倒序,获取到数据集的第 (currentPage-1) limit 到 currentPage limit 条记录;升序,获取到数据集的 第 buckets.size() - (currentPage - 1) limit 到 buckets.size() - currentPage limit 条记录。// 获取到 response 之后Aggregations aggregations = response.getAggregations();Terms carids = aggregations.get(“group_car_bayId”);List<? extends Terms.Bucket> buckets = carids.getBuckets();List<carBean> listCarTgs = new ArrayList<>();carBean carBean ;// buckets 全部数据,分页就是取固定位置的 limit 条数据// 默认按照统计之后的数量倒序, 如果要正序,则第一页从最后一条开始取if(buckets.size()>0) { int i=0; if(“desc”.equalsIgnoreCase(order)) {// 倒序 for(Terms.Bucket bucket : buckets){ if(i++<(currentPage-1) * limit){ continue; } if (i > currentPage * limit) { break; } carBean = new carBean(); carBean.setPassTimes((int)bucket.getDocCount()); carBean.setBayId(bucket.getKeyAsString().split("")[2]); carBean.setPlateNumber(bucket.getKeyAsString().split("")[0]); carBean.setPlateType(bucket.getKeyAsString().split("")[1]); listCarTgs.add(carBean); } }else if(“asc”.equalsIgnoreCase(order)) {// 升序 for(i = buckets.size() - 1; i >= 0; i–){ if(i < buckets.size() - currentPage * limit){ break; } if(i > buckets.size() - (currentPage - 1) * limit) continue;; carBean = new carBean(); carBean.setPassTimes((int)buckets.get(i).getDocCount()); carBean.setBayId(buckets.get(i).getKeyAsString().split("")[2]); carBean.setPlateNumber(buckets.get(i).getKeyAsString().split("")[0]); carBean.setPlateType(buckets.get(i).getKeyAsString().split("")[1]); listCarTgs.add(carBean); } }}单个 group注意:需要设置 sizeTermsAggregationBuilder tb= AggregationBuilders.terms(“group_bayId”).field(“bay_id”).size(Integer.MAX_VALUE);多个 group以脚本的形式TermsAggregationBuilder tb= AggregationBuilders.terms(“group_carId”).script(new Script(“doc[‘plateNumber’].value+’’+doc[‘plateType’].value”)); tb.subAggregation(AggregationBuilders.topHits(“max_time”).sort(“reportTime”, SortOrder.DESC).size(1)); 再比如:三个 groupBoolQueryBuilder filter = QueryBuilders.boolQuery();if (carList != null && carList.size() >0) { filter.must(QueryBuilders.termsQuery(“car_plate_number”, carList.stream().map(SimpleCar:: getPlateNumber).collect(Collectors.toList())));}if (startTime != null && endTime != null) { filter.filter(QueryBuilders.rangeQuery(“timestamp”).gt(startTime.getTime()).lt(endTime.getTime()));} else if (startTime != null) { filter.filter(QueryBuilders.rangeQuery(“timestamp”).gt(startTime.getTime()));} else if (endTime != null) { filter.filter(QueryBuilders.rangeQuery(“timestamp”).lt(endTime.getTime()));}FieldSortBuilder sort = SortBuilders.fieldSort(“transit_times”).order(“asc”.equalsIgnoreCase(order)?SortOrder.ASC:SortOrder.DESC);TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms(“group_car_bayId”) .script(new Script(“doc[‘car_plate_number’].value+’’+doc[‘car_plate_type’].value + ‘_’ +doc[‘bay_id’].value”)).size(Integer.MAX_VALUE);SearchResponse response = search(filter, sort, termsAggregationBuilder, elasticsearchProperties.getTgsIndex(), elasticsearchProperties.getTgsType(), (currentPage-1) * limit, 0);Aggregations aggregations = response.getAggregations(); 例子2:TermsAggregationBuilder tb= AggregationBuilders.terms(“group_bayId”).field(“bay_id”).size(Integer.MAX_VALUE); tb.order(BucketOrder.count(false)); ...

December 22, 2018 · 1 min · jiezi

让我们ElasticSearch作伴,一起潇洒复习~

12月15日,即便天气寒冷,飘着雨。跨星空间座无虚席由袋鼠云、阿里云、elastic中文社区主办袋鼠云、阿里云、有赞、滴滴的技术大牛倾囊相授~“ElasticSearch运维技术实践”精彩上演!Now,温故而知新,一起来回顾吧干货来啦,别带着它入睡,赶紧拿小本本记下来吧!解 惑 篇在此本萌特别精编了一辑本场沙龙现场讲师和学员的A&Q,分享给大家阿里云Elasticsearch实时计算平台实践Q:使用时一边构建索引一边查询,性能会下降,如何处理?A:架构1)增加clientnode,降低DataNode的CPU开销2)换更好的硬件,多盘组成RAID或SSD配置1)增大translog flush时间间隔2)增加索引refresh时间3)调整merge速度,调小index.merge.scheduler.max_thread_count和merge.policy.max_merged_segment,增大merge.policy.segments_per_tier4)调整mapping,不需要分词的字段不用text改用keyword具体的还是要根据业务场景去测试和做取舍Q:ES取消translog还有副本吗?性能提升4倍是指在哪些方面?A:elasticbuild是用于离线build,所以不需要副本,依赖的HDFS自身有副本机制;去掉tranlog和内存merge减少IO开销以及网络开销。Q:写入HDFS的数据,如何恢复到ES?增量如何处理?A:在全量结束后会做一个snapshot到OSS上,然后再restore到在线集群。bahamut在拉起全量build任务的时候会记住全量启动的数据时间戳,然后增量任务从记录的时间戳开始补数据。袋鼠云百亿日志数据下ES性能优化实践Q:5台节点,master节点经常负载较高,两台MI,三台DI,合理吗?A:master建议3台以上类似于zk三节点的原理防止脑裂。Q:索引规划,分片不超过40G,每个node不要超过3个分片,32G的内存配置下,每个分片1Gbuffer,有测试过吗?A:分片越多,写入性能更好,分片更多,分片的管理性能消耗增加,对单个索引来说。Q:分片初始配置后不要修改?三台服务器最多分片数?A:改,除非做reindex的操作,建议最多不要超过12个(包括副本分片)Q:冷热数据处理,怎么做?如何区分冷数据?查询冷数据体验如何提高?A:在我们使用日志的场景中,超过3天或7天的数据定义为冷数据,冷数据迁移到大存储的节点(OSS);查询时需要恢复到热节点,恢复有一定的时间。对用户来说有冷热数据存储的概念。Elasticsearch的索引和集群隔离实践Q:索引是每30S刷新一次,刷新的那一刻rp比较高,如何降低影响?A:1)建议把数据做拆分,热数据刷新频率高一点,冷数据刷新频率低一点2)可以适当看下缩短刷新时间能否平滑毛刺3)数据变更太频繁的内容考虑做下采样,减少更新次数Q:多索引查询时怎么提高性能?不同索引mapping不一致,解析response耗时长A:1)从查询本身上做优化2)response在业务层做多线程处理;3)加缓存,经常查询的数据结果记录在缓存中;4)执行中断,查询一部分结果就返回,查询高并发性能不行的。滴滴Elasticsearch Query DSL分析系统Q:mapping优化中对用户使用习惯未知,优化的意义在哪?A:ES默认对所有字段建立索引,通过mapping优化,可以自动化的把索引中用户查询不使用的字段不建索引来节省成本。自动化体现在用户新增或者减少字段能被系统自动感知到,从而减少和用户沟通成本。Q:有哪些用户?查询的索引中不同字段的数据量是不同,此时进行聚合查询怎么办?A:滴滴内部使用ES的同学,例如客服,运维和RD。我们会得到查询语句中根据哪个字段进行聚合,另外每个字段基数会由其他服务进行统计,例如根据IP字段进行聚合,由于IP基数过大(count distinct IP)。如果是针对大基数字段进行聚合查询预估消耗内存较大时,就会把这种查询熔断。Q:还没有数据进来时没有建索引时,是自动生成索引还是动态映射?A:每一类数据写入索引前会根据这个索引中索引模板信息进行映射,在索引模板中会定义一些字段对应的类型,例如字符串内容符合date format的字段就会映射成date类型。没有在索引模板中找到的字段其类型由es根据第一次出现这个字段的内容推导,例如字符串自动映射成keyword类型。Q:复杂的聚合查询如何处理?A:聚合查询嵌套不能太深(一般不要超过3层),当然这个跟聚合查询的索引中聚合字段的基数有关,如果字段基数大聚合产生的桶就会有很多,消耗的内存也会很大,需要对消耗内存大的聚合查询语句进行熔断。至于复杂的聚合语句优化,可以从减少聚合返回的size,合理调整聚合字段顺序,使用date_histogram来代替histogram等。Q:gateway是自研的吗,是否会考虑开源?A:gateway是自研的,后期会开源。

December 19, 2018 · 1 min · jiezi

ElasticSearch实战:Linux日志对接Kibana

本文由云+社区发表ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTFul web接口。ElasticSearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。ElasticSearch常用于全文检索,结构化检索,数据分析等。下面,我们以ElasticSearch接管Linux日志(/var/log/xxx.log)为例,详细介绍如何进行配置与部署。总体架构图一,准备工作1,CVM及ElasticSearch在腾讯云帐号下,申请一台CVM(Linux操作系统)、一个ElasticSearch集群(后面简称ES),使用最简配置即可;申请的CVM和ES,必须在同一个VPC的同一个子网下。CVM详情信息ElasticSearch详情信息2,Filebeat工具为了将Linux日志提取到ES中,我们需要使用Filebeat工具。Filebeat是一个日志文件托运工具,在你的服务器上安装客户端后,Filebeat会监控日志目录或者指定的日志文件,追踪读取这些文件(追踪文件的变化,不停的读),并且转发这些信息到ElasticSearch或者logstarsh中存放。当你开启Filebeat程序的时候,它会启动一个或多个探测器(prospectors)去检测你指定的日志目录或文件,对于探测器找出的每一个日志文件,Filebeat启动收割进程(harvester),每一个收割进程读取一个日志文件的新内容,并发送这些新的日志数据到处理程序(spooler),处理程序会集合这些事件,最后Filebeat会发送集合的数据到你指定的地点。官网简介:https://www.elastic.co/produc…二,操作步骤1,Filebeat下载与安装首先,登录待接管日志的CVM,在CVM上下载Filebeat工具:[root@VM_3_7_centos ~]# cd /opt/[root@VM_3_7_centos opt]# lltotal 4drwxr-xr-x. 2 root root 4096 Sep 7 2017 rh[root@VM_3_7_centos opt]# wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-6.2.2-x86_64.rpm--2018-12-10 20:24:26– https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-6.2.2-x86_64.rpmResolving artifacts.elastic.co (artifacts.elastic.co)… 107.21.202.15, 107.21.127.184, 54.225.214.74, …Connecting to artifacts.elastic.co (artifacts.elastic.co)|107.21.202.15|:443… connected.HTTP request sent, awaiting response… 200 OKLength: 12697788 (12M) [binary/octet-stream]Saving to: ‘filebeat-6.2.2-x86_64.rpm’100%[=================================================================================================>] 12,697,788 160KB/s in 1m 41s 2018-12-10 20:26:08 (123 KB/s) - ‘filebeat-6.2.2-x86_64.rpm’ saved [12697788/12697788]然后,进行安装filebeat:[root@VM_3_7_centos opt]# rpm -vi filebeat-6.2.2-x86_64.rpmwarning: filebeat-6.2.2-x86_64.rpm: Header V4 RSA/SHA512 Signature, key ID d88e42b4: NOKEYPreparing packages…filebeat-6.2.2-1.x86_64[root@VM_3_7_centos opt]#至此,Filebeat安装完成。2,Filebeat配置进入Filebeat配置文件目录:/etc/filebeat/[root@VM_3_7_centos opt]# cd /etc/filebeat/[root@VM_3_7_centos filebeat]# lltotal 108-rw-r–r– 1 root root 44384 Feb 17 2018 fields.yml-rw-r—– 1 root root 52193 Feb 17 2018 filebeat.reference.yml-rw——- 1 root root 7264 Feb 17 2018 filebeat.ymldrwxr-xr-x 2 root root 4096 Dec 10 20:35 modules.d[root@VM_3_7_centos filebeat]#其中,filebeat.yml就是我们需要修改的配置文件。建议修改配置前,先备份此文件。然后,确认需要对接ElasticSearch的Linux的日志目录,我们以下图(/var/log/secure)为例。/var/log/secure日志文件使用vim打开/etc/filebeat/filebeat.yml文件,修改其中的:1)Filebeat prospectors类目中,enable默认为false,我们要改为true2)paths,默认为/var/log/.log,我们要改为待接管的日志路径:/var/log/secure3)Outputs类目中,有ElasticSearchoutput配置,其中hosts默认为"localhost:9200",需要我们手工修改为上面申请的ES子网地址和端口,即"10.0.3.8:9200"。修改好上述内容后,保存退出。修改好的配置文件全文如下:[root@VM_3_7_centos /]# vim /etc/filebeat/filebeat.yml[root@VM_3_7_centos /]# cat /etc/filebeat/filebeat.yml###################### Filebeat Configuration Example ########################## This file is an example configuration file highlighting only the most common# options. The filebeat.reference.yml file from the same directory contains all the# supported options with more comments. You can use it as a reference.## You can find the full configuration reference here:# https://www.elastic.co/guide/en/beats/filebeat/index.html# For more available modules and options, please see the filebeat.reference.yml sample# configuration file.#=========================== Filebeat prospectors =============================filebeat.prospectors:# Each - is a prospector. Most options can be set at the prospector level, so# you can use different prospectors for various configurations.# Below are the prospector specific configurations.- type: log # Change to true to enable this prospector configuration. enabled: true # Paths that should be crawled and fetched. Glob based paths. paths: - /var/log/secure #- c:\programdata\elasticsearch\logs* # Exclude lines. A list of regular expressions to match. It drops the lines that are # matching any regular expression from the list. #exclude_lines: [’^DBG’] # Include lines. A list of regular expressions to match. It exports the lines that are # matching any regular expression from the list. #include_lines: [’^ERR’, ‘^WARN’] # Exclude files. A list of regular expressions to match. Filebeat drops the files that # are matching any regular expression from the list. By default, no files are dropped. #exclude_files: [’.gz$’] # Optional additional fields. These fields can be freely picked # to add additional information to the crawled log files for filtering #fields: # level: debug # review: 1 ### Multiline options # Mutiline can be used for log messages spanning multiple lines. This is common # for Java Stack Traces or C-Line Continuation # The regexp Pattern that has to be matched. The example pattern matches all lines starting with [ #multiline.pattern: ^[ # Defines if the pattern set under pattern should be negated or not. Default is false. #multiline.negate: false # Match can be set to “after” or “before”. It is used to define if lines should be append to a pattern # that was (not) matched before or after or as long as a pattern is not matched based on negate. # Note: After is the equivalent to previous and before is the equivalent to to next in Logstash #multiline.match: after#============================= Filebeat modules ===============================filebeat.config.modules: # Glob pattern for configuration loading path: ${path.config}/modules.d/.yml # Set to true to enable config reloading reload.enabled: false # Period on which files under path should be checked for changes #reload.period: 10s#==================== Elasticsearch template setting ==========================setup.template.settings: index.number_of_shards: 3 #index.codec: best_compression #_source.enabled: false#================================ General =====================================# The name of the shipper that publishes the network data. It can be used to group# all the transactions sent by a single shipper in the web interface.#name:# The tags of the shipper are included in their own field with each# transaction published.#tags: [“service-X”, “web-tier”]# Optional fields that you can specify to add additional information to the# output.#fields:# env: staging#============================== Dashboards =====================================# These settings control loading the sample dashboards to the Kibana index. Loading# the dashboards is disabled by default and can be enabled either by setting the# options here, or by using the -setup CLI flag or the setup command.#setup.dashboards.enabled: false# The URL from where to download the dashboards archive. By default this URL# has a value which is computed based on the Beat name and version. For released# versions, this URL points to the dashboard archive on the artifacts.elastic.co# website.#setup.dashboards.url:#============================== Kibana =====================================# Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API.# This requires a Kibana endpoint configuration.setup.kibana: # Kibana Host # Scheme and port can be left out and will be set to the default (http and 5601) # In case you specify and additional path, the scheme is required: http://localhost:5601/path # IPv6 addresses should always be defined as: https://[2001:db8::1]:5601 #host: “localhost:5601”#============================= Elastic Cloud ==================================# These settings simplify using filebeat with the Elastic Cloud (https://cloud.elastic.co/).# The cloud.id setting overwrites the output.elasticsearch.hosts and# setup.kibana.host options.# You can find the cloud.id in the Elastic Cloud web UI.#cloud.id:# The cloud.auth setting overwrites the output.elasticsearch.username and# output.elasticsearch.password settings. The format is &lt;user&gt;:&lt;pass&gt;.#cloud.auth:#================================ Outputs =====================================# Configure what output to use when sending the data collected by the beat.#————————– Elasticsearch output ——————————output.elasticsearch: # Array of hosts to connect to. hosts: [“10.0.3.8:9200”] # Optional protocol and basic auth credentials. #protocol: “https” #username: “elastic” #password: “changeme”#—————————– Logstash output ——————————–#output.logstash: # The Logstash hosts #hosts: [“localhost:5044”] # Optional SSL. By default is off. # List of root certificates for HTTPS server verifications #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] # Certificate for SSL client authentication #ssl.certificate: “/etc/pki/client/cert.pem” # Client Certificate Key #ssl.key: “/etc/pki/client/cert.key”#================================ Logging =====================================# Sets log level. The default log level is info.# Available log levels are: error, warning, info, debug#logging.level: debug# At debug level, you can selectively enable logging only for some components.# To enable all selectors use [""]. Examples of other selectors are “beat”,# “publish”, “service”.#logging.selectors: [""]#============================== Xpack Monitoring ===============================# filebeat can export internal metrics to a central Elasticsearch monitoring# cluster. This requires xpack monitoring to be enabled in Elasticsearch. The# reporting is disabled by default.# Set to true to enable the monitoring reporter.#xpack.monitoring.enabled: false# Uncomment to send the metrics to Elasticsearch. Most settings from the# Elasticsearch output are accepted here as well. Any setting that is not set is# automatically inherited from the Elasticsearch output configuration, so if you# have the Elasticsearch output configured, you can simply uncomment the# following line.#xpack.monitoring.elasticsearch:[root@VM_3_7_centos /]# 执行下列命令启动filebeat[root@VM_3_7_centos /]# sudo /etc/init.d/filebeat startStarting filebeat (via systemctl): [ OK ][root@VM_3_7_centos /]# 3,Kibana配置进入ElasticSearch对应的Kibana管理页,如下图。首次访问Kibana默认会显示管理页首次登陆,会默认进入Management页面,我们需要将Index pattern内容修改为:filebeat-,然后页面会自动填充Time Filter field name,不需手动设置,直接点击Create即可。点击Create后,页面需要一定时间来加载配置和数据,请稍等。如下图:将Index pattern内容修改为:filebeat-,然后点击Create至此,CVM上,/var/log/secure日志文件,已对接到ElasticSearch中,历史日志可以通过Kibana进行查询,最新产生的日志也会实时同步到Kibana中。三,实战效果日志接管已完成配置,如何使用呢?如下图:在Index Patterns中可以看到我们配置过的filebeat-*点击Discover,即可看到secure中的所有日志,页面上方的搜索框中输入关键字,即可完成日志的检索。如下图(点击图片,可查看高清大图):使用Kibana进行日志检索实际上,检索只是Kibana提供的诸多功能之一,还有其他功能如可视化、分词检索等,还有待后续研究。此文已由作者授权腾讯云+社区发布搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包! ...

December 14, 2018 · 5 min · jiezi

Elasticsearch检索实战

首发于 樊浩柏科学院随着公司房源数据的急剧增多,现搜索引擎 Solr 的搜索效率和建立索引效率显著降低,而 Elasticsearch 是一个实时的分布式搜索和分析引擎,它是基于全文搜索引擎 Apache Lucene 之上,接入 Elasticsearch 是必然之选。本文是我学习使用 Elasticsearch 检索的笔记。Elasticsearch 支持 RESTful API 方式检索,查询结果以 JSON 格式响应,文中示例数据见 这里。有关 Elasticsearch 详细使用说明,见 官方文档。Url检索 url 中需包含 索引名,_search为查询关键字。例如 http://es.fanhaobai.com/rooms/_search 的 rooms 为索引名,此时表示无任何条件检索,检索结果为:GET /rooms/_search{ “took”: 6, “timed_out”: false, “_shards”: { … }, “hits”: { “total”: 3, “max_score”: 1, “hits”: [ { “_index”: “rooms”, “_type”: “room_info”, “_id”: “3”, “_score”: 1, “_source”: { “resblockId”: “1111027377528”, “resblockName”: “金隅丽港城”, “houseId”: 1087599828743, “cityCode”: 110000, “size”: 10.5, “bizcircleCode”: [ “18335711” ], “bizcircleName”: [ “望京” ], “price”: 2300 } }, { … … “_source”: { “resblockId”: “1111047349969”, “resblockName”: “融泽嘉园”, “houseId”: 1087817932553, “cityCode”: 110000, “size”: 10.35, “bizcircleCode”: [ “611100314” ], “bizcircleName”: [ “西二旗” ], “price”: 2500 } }, … … ] }}注:Elasticsearch 官方偏向于使用 GET 方式(能更好描述信息检索的行为),GET 方式可以携带请求体,但是由于不被广泛支持,所以 Elasticsearch 也支持 POST 请求。后续查询语言使用 POST 方式。当我们确定了需要检索文档的 url 后,就可以使用查询语法进行检索,Elasticsearch 支持以下 Query string(查询字符串)和 DSL(结构化)2 种检索语句。检索语句Query string我们可以直接在 get 请求时的 url 后追加q=查询参数,这种方法常被称作 query string 搜索,因为我们像传递 url 参数一样去传递查询语句。例如查询小区 id 为 1111027374551 的房源信息:GET /rooms/_search?q=resblockId:1111027374551//查询结果,无关信息已省略{ “hits”: [ { “_source”: { “resblockId”: “1111027374551”, “resblockName”: “国风北京二期”, … … } } ]}虽然查询字符串便于查询特定的搜索,但是它也有局限性。DSLDSL 查询以 JSON 请求体的形式出现,它允许构建更加复杂、强大的查询。DSL 方式查询上述 query string 查询条件则为:POST /rooms/_search{ “query”: { “term”: { “resblockId”: “1111027374551” } }}term 语句为过滤类型之一,后面再进行说明。使用 DSL 语句查询支持 filter(过滤器)、match(全文检索)等复杂检索场景。基本检索Elasticsearch 支持为 2 种检索行为,它们都是使用 DSL 语句来表达检索条件,分别为 query (结构化查询)和 filter(结构化搜索)。说明:后续将使用 SQL 对比 DSL 语法进行搜索条件示例。结构化查询结构化查询支持全文检索,会对检索结果进行相关性计算。使用结构化查询,需要传递 query 参数:{ “query”: your_query }//your_query为{}表示空查询注:后续查询中不再列出 query 参数,只列出 your_query(查询内容)。match_all查询match_all 查询简单的匹配所有文档。在没有指定查询方式时,它是默认的查询。查询所有房源信息:– SQL表述SELECT * FROM roomsmatch_all 查询为:{ “match_all”: {}}match查询match 查询为全文搜索,类似于 SQL 的 LIKE 查询。查询小区名中包含“嘉”的房源信息:– SQL表述SELECT * FROM rooms WHERE resblockName LIKE ‘%嘉%‘match 查询为:{ “match”: { “resblockName”: “嘉” }}//结果"hits": [ { “_source”: { “resblockId”: “1111047349969”, “resblockName”: “融泽嘉园”, … … } }]multi_match查询multi_match 查询可以在多个字段上执行相同的 match 查询:{ “multi_match”: { “query”: “京”, “fields”: [ “resblockName”, “bizcircleName” ] }}range查询range 查询能检索出那些落在指定区间内的文档,类似于 SQL 的 BETWEEN 操作。range 查询被允许的操作符有:操作符操作关系gt大于gte大于等于lt小于lte小于等于查询价格在 (2000, 2500] 的房源信息:– SQL表述SELECT * FROM rooms WHERE price BETWEEN 2000 AND 2500 AND price != 2000range 查询为:{ “range”: { “price”: { “gt”: 2000, “lte”: 2500 } }}term查询term 查询用于精确值匹配,可能是数字、时间、布尔。例如查询房屋 id 为 1087599828743 的房源信息:– SQL表述SELECT * FROM rooms WHERE houseId = 1087599828743term 查询为:{ “term”: { “houseId”: 1087599828743 }}terms查询terms 查询同 term 查询,但它允许指定多值进行匹配,类似于 SQL 的 IN 操作。例如查询房屋 id 为 1087599828743 或者 1087817932342 的房源信息:– SQL表述SELECT * FROM rooms WHERE houseId IN (1087599828743, 1087817932342)terms 查询为:{ “terms”: { “houseId”: [ 1087599828743, 1087817932342 ] }}term 查询和 terms 查询都不分析输入的文本, 不会进行相关性计算。exists查询和missing查询exists 查询和 missing 查询被用于查找那些指定字段中有值和无值的文档,类似于 SQL 中的 IS NOT NULL 和 IS NULL 查询。查询价格有效的房源信息:– SQL表述SELECT * FROM rooms WHERE price IS NOT NULLexists 查询为:{ “exists”: { “field”: “price” }}bool查询我们时常需要将多个条件的结构进行逻辑与和或操作,等同于 SQL 的 AND 和 OR,这时就应该使用 bool 子句合并多子句结果。 共有 3 种 bool 查询,分别为 must(AND)、must_not(NOT)、should(OR)。操作符描述mustAND 关系,必须 匹配这些条件才能检索出来must_notNOT 关系,必须不 匹配这些条件才能检索出来shouldOR 关系,至少匹配一条 条件才能检索出来filter必须 匹配,不参与评分查询小区中包含“嘉”字或者房屋 id 为 1087599828743 的房源信息:– SQL表述SELECT * FROM rooms WHERE (resblockName LIKE ‘%嘉%’ OR houseId = 1087599828743) AND (cityCode = 110000)bool 查询为:{ “bool”: { “must”: { “term”: {“cityCode”: 110000 } }, “should”: [ { “term”: { “houseId”: 1087599828743 }}, { “match”: { “resblockName”: “嘉” }} ] }}使用 filter 语句来使得其子句不参与评分过程,减少评分可以有效地优化性能。重写前面的例子:{ “bool”: { “should”: [ { “match”: { “resblockName”: “嘉” }} ], “filter” : { “bool”: { “must”: { “term”: { “cityCode”: 110000 }}, “should”: [ { “term”: { “houseId”: 1087599828743 }} ] } } }}bool 查询可以相互的进行嵌套,已完成非常复杂的查询条件。constant_score查询constant_score 查询将一个不变的常量评分应用于所有匹配的文档。它被经常用于你只需要执行一个 filter(过滤器)而没有其它查询(评分查询)的情况下。{ “constant_score”: { “filter”: { “term”: { “houseId”: 1087599828743 } } }}结构化搜索结构化搜索的查询适合确定值数据(数字、日期、时间),这些类型数据都有明确的格式。结构化搜索结果始终是是或非,结构化搜索不关心文档的相关性或分数,它只是简单的包含或排除文档,由于结构化搜索使用到过滤器,在查询时需要传递 filter 参数,由于 DSL 语法查询必须以 query 开始,所以 filter 需要放置在 query 里,因此结构化查询的结构为:{ “query”: { “constant_score”: { “filter”: { //your_filters } } }}注:后续搜索中不再列出 query 参数,只列出 your_filters(过滤内容)。结构化搜索一样存在很多过滤器 term、terms、range、exists、missing、bool,我们在结构化查询中都已经接触过了。term搜索最为常用的 term 搜索用于查询精确值,可以用它处理数字(number)、布尔值(boolean)、日期(date)以及文本(text)。查询小区 id 为 1111027377528 的房源信息:– SQL表述SELECT * FROM rooms WHERE resblockId = “1111027377528"term 搜索为:{ “term”: { “resblockId”: “1111027377528” }}类似XHDK-A-1293-#fJ3这样的文本直接使用 term 查询时,可能无法获取到期望的结果。是因为 Elasticsearch 在建立索引时,会将该数据分析成 xhdk、a、1293、#fj3 字样,这并不是我们期望的,可以通过指定 not_analyzed 告诉 Elasticsearch 在建立索引时无需分析该字段值。terms搜索terms 搜索使用方式和 term 基本一致,而 terms 是搜索字段多值的情况。查询商圈 code 为 18335711 或者 611100314 的房源信息:– SQL表述SELECT * FROM rooms WHERE bizcircleCode IN (18335711, 611100314)terms搜索为:{ “terms”: { “bizcircleCode”: [ “18335711”, “611100314” ] }}range搜索在进行范围过滤查询时使用 range 搜索,支持数字、字母、日期的范围查询。查询面积在 [15, 25] 平米之间的房源信息:– SQL表述SELECT * FROM rooms WHERE size BETWEEN 10 AND 25range 搜索为:{ “range”: { “size”: { “gte”: 10, “lte”: 25 } }}range 搜索使用在日期上:{ “range”: { “date”: { “gt”: “2017-01-01 00:00:00”, “lt”: “2017-01-07 00:00:00” } }}exists和missing搜索exists 和 missing 搜索是针对某些字段值存在和缺失的查询。查询房屋面积存在的房源列表:– SQL表述SELECT * FROM rooms WHERE size IS NOT NULLexists 搜索为:{“exists”: { “field”: “size” }}missing 搜索刚好和 exists 搜索相反,但语法一致。bool组合搜索bool 过滤器是为了解决过滤多个值或字段的问题,它可以接受多个其他过滤器作为子过滤器,并将这些过滤器结合成各式各样的逻辑组合。bool 过滤器的组成部分,同 bool 查询一致:{ “bool”: { “must”: [], “should”: [], “must_not”: [], }}类似于如下 SQL 查询条件:SELECT * FROM rooms WHERE (bizcircleCode = 18335711 AND price BETWEEN 2000 AND 2500) OR (bizcircleCode = 611100314 AND price >= 2500)使用 bool 过滤器实现为:{ “bool”: { “should”: [ { “term”: { “bizcircleCode”: “18335711” }, “range”: { “price”: { “gte”: 2000, “lte”: 25000 }} }, { “term”: { “bizcircleCode”: “611100314” }, “range”: { “price”: { “gte”: 2500 }} } ] }}区别:结构化查询会进行相关性计算,因此不会缓存检索结果;而结构化搜索会缓存搜索结果,因此具有较高的检索效率,在不需要全文搜索或者其它任何需要影响相关性得分的查询中建议只使用结构化搜索。当然,结构化查询和结构化搜索可以配合使用。聚合该部分较复杂,已单独使用文章进行说明,见 Elasticsearch检索 — 聚合和LBS 部分。_source子句某些时候可能不需要返回文档的全部字段,这时就可以使用 _source 子句指定返回需要的字段。只返回需要的房源信息字段:{ “_source”: [ “cityCode”, “houseId”, “price”, “resblockName” ]}sort子句简单排序排序是使用比较多的推荐方式,在 Elasticsearch 中,默认会按照相关性进行排序,相关性得分由一个浮点数进行表示,并在搜索结果中通过_score参数返回(未参与相关性评分时分数为 1), 默认是按_score降序排序。sort 方式有 desc、asc 两种。将房源查询结果按照价格升序排列:{ “sort”: { “price”: { “order”: “asc” }} }}多级排序当存在多级排序的场景时,结果首先按第一个条件排序,仅当结果集的第一个 sort 值完全相同时才会按照第二个条件进行排序,以此类推。{ “sort”: [ { “price”: { “order”: “asc” }}, { “_score”: { “order”: “desc” }} //price一直时,按照相关性降序 ]}字段多指排序当字段值为 多值 及 字段多指排序,Elasticsearch 会对于数字或日期类型将多值字段转为单值。转化有 min 、max 、avg、 sum 这 4 种模式。 例如,将房源查询结果按照商圈 code 升序排列:{ “sort”: { “bizcircleCode”: { “order”: “asc”, “mode”: “min” } }}分页子句和 SQL 使用 LIMIT 关键字返回单 page 结果的方法相同,Elasticsearch 接受 from(初始结果数量)和 size(应该返回结果数量) 参数:{ “size”: 8, “from”: 1}验证查询合法性在实际应用中,查询可能变得非常的复杂,理解起来就有点困难了。不过可以使用validate-queryAPI来验证查询合法性。GET /room/_validate/query{ “query”: { “resblockName”: { “match”: “嘉” }}}合法的 query 返回信息:{ “valid”: false, “_shards”: { “total”: 1, “successful”: 1, “failed”: 0 }}最后别的业务线已经投入 Elasticsearch 使用有段时间了,找房业务线正由 Solr 切换为 Elasticsearch,各个系统有一个探索和磨合的过程。当然,Elasticsearch 我们已经服务化了,对 DSL 语法也进行了一些简化,同时支持了定制化业务。另外,使用 elasticsearch-sql 插件可以让 Elasticsearch 也支持 SQL 操作。 相关文章 »Elasticsearch检索 — 聚合和LBS (2017-08-21) ...

December 5, 2018 · 4 min · jiezi

Elasticsearch实践(四):IK分词

环境:Elasticsearch 6.2.4 + Kibana 6.2.4 + ik 6.2.4Elasticsearch默认也能对中文进行分词。我们先来看看自带的中文分词效果:curl -XGET “http://localhost:9200/_analyze” -H ‘Content-Type: application/json;’ -d ‘{“analyzer”: “default”,“text”: “今天天气真好”}‘GET /_analyze{ “analyzer”: “default”, “text”: “今天天气真好”}结果:{ “tokens”: [ { “token”: “今”, “start_offset”: 0, “end_offset”: 1, “type”: “<IDEOGRAPHIC>”, “position”: 0 }, { “token”: “天”, “start_offset”: 1, “end_offset”: 2, “type”: “<IDEOGRAPHIC>”, “position”: 1 }, { “token”: “天”, “start_offset”: 2, “end_offset”: 3, “type”: “<IDEOGRAPHIC>”, “position”: 2 }, { “token”: “气”, “start_offset”: 3, “end_offset”: 4, “type”: “<IDEOGRAPHIC>”, “position”: 3 }, { “token”: “真”, “start_offset”: 4, “end_offset”: 5, “type”: “<IDEOGRAPHIC>”, “position”: 4 }, { “token”: “好”, “start_offset”: 5, “end_offset”: 6, “type”: “<IDEOGRAPHIC>”, “position”: 5 } ]}我们发现,是按照每个字进行分词的。这种在实际应用里肯定达不到想要的效果。当然,如果是日志搜索,使用自带的就足够了。analyzer=default其实调用的是standard分词器。接下来,我们安装IK分词插件进行分词。安装IKIK项目地址:https://github.com/medcl/elas…首先需要说明的是,IK插件必须和 ElasticSearch 的版本一致,否则不兼容。安装方法1:从 https://github.com/medcl/elas… 下载压缩包,然后在ES的plugins目录创建analysis-ik子目录,把压缩包的内容复制到这个目录里面即可。最终plugins/analysis-ik/目录里面的内容:plugins/analysis-ik/ commons-codec-1.9.jar commons-logging-1.2.jar elasticsearch-analysis-ik-6.2.4.jar httpclient-4.5.2.jar httpcore-4.4.4.jar plugin-descriptor.properties然后重启 ElasticSearch。安装方法2:./usr/local/elk/elasticsearch-6.2.4/bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.2.4/elasticsearch-analysis-ik-6.2.4.zip如果已下载压缩包,直接使用:./usr/local/elk/elasticsearch-6.2.4/bin/elasticsearch-plugin install file:///tmp/elasticsearch-analysis-ik-6.2.4.zip然后重启 ElasticSearch。IK分词IK支持两种分词模式:ik_max_word: 会将文本做最细粒度的拆分,会穷尽各种可能的组合ik_smart: 会做最粗粒度的拆分接下来,我们测算IK分词效果和自带的有什么不同:curl -XGET “http://localhost:9200/_analyze” -H ‘Content-Type: application/json’ -d’{“analyzer”: “ik_smart”,“text”: “今天天气真好”}‘结果:{ “tokens”: [ { “token”: “今天天气”, “start_offset”: 0, “end_offset”: 4, “type”: “CN_WORD”, “position”: 0 }, { “token”: “真好”, “start_offset”: 4, “end_offset”: 6, “type”: “CN_WORD”, “position”: 1 } ]}再试一下ik_max_word的效果:{ “tokens”: [ { “token”: “今天天气”, “start_offset”: 0, “end_offset”: 4, “type”: “CN_WORD”, “position”: 0 }, { “token”: “今天”, “start_offset”: 0, “end_offset”: 2, “type”: “CN_WORD”, “position”: 1 }, { “token”: “天天”, “start_offset”: 1, “end_offset”: 3, “type”: “CN_WORD”, “position”: 2 }, { “token”: “天气”, “start_offset”: 2, “end_offset”: 4, “type”: “CN_WORD”, “position”: 3 }, { “token”: “真好”, “start_offset”: 4, “end_offset”: 6, “type”: “CN_WORD”, “position”: 4 } ]}设置mapping默认分词器示例:{ “properties”: { “content”: { “type”: “text”, “analyzer”: “ik_max_word”, “search_analyzer”: “ik_max_word” } }}注:这里设置 search_analyzer 与 analyzer 相同是为了确保搜索时和索引时使用相同的分词器,以确保查询中的术语与反向索引中的术语具有相同的格式。如果不设置 search_analyzer,则 search_analyzer 与 analyzer 相同。详细请查阅:https://www.elastic.co/guide/…防盗版声明:本文系原创文章,发布于公众号飞鸿影的博客(fhyblog)及博客园,转载需作者同意。自定义分词词典我们也可以定义自己的词典供IK使用。比如:curl -XGET “http://localhost:9200/_analyze” -H ‘Content-Type: application/json’ -d’{“analyzer”: “ik_smart”,“text”: “去朝阳公园”}‘结果:{ “tokens”: [ { “token”: “去”, “start_offset”: 0, “end_offset”: 1, “type”: “CN_CHAR”, “position”: 0 }, { “token”: “朝阳”, “start_offset”: 1, “end_offset”: 3, “type”: “CN_WORD”, “position”: 1 }, { “token”: “公园”, “start_offset”: 3, “end_offset”: 5, “type”: “CN_WORD”, “position”: 2 } ]}我们希望朝阳公园作为一个整体,这时候可以把该词加入到自己的词典里。新建自己的词典只需要简单几步就可以完成:1、在elasticsearch-6.2.4/config/analysis-ik/目录增加一个my.dic:$ touch my.dic$ echo 朝阳公园 > my.dic$ cat my.dic朝阳公园.dic为词典文件,其实就是简单的文本文件,词语与词语直接需要换行。注意是UTF8编码。我们看一下自带的分词文件:$ head -n 5 main.dic一一列举一一对应一一道来一丁一丁不识2、然后修改elasticsearch-6.2.4/config/analysis-ik/IKAnalyzer.cfg.xml文件:<?xml version=“1.0” encoding=“UTF-8”?><!DOCTYPE properties SYSTEM “http://java.sun.com/dtd/properties.dtd"><properties> <comment>IK Analyzer 扩展配置</comment> <!–用户可以在这里配置自己的扩展字典 –> <entry key=“ext_dict”>my.dic</entry> <!–用户可以在这里配置自己的扩展停止词字典–> <entry key=“ext_stopwords”></entry> <!–用户可以在这里配置远程扩展字典 –> <!– <entry key=“remote_ext_dict”>words_location</entry> –> <!–用户可以在这里配置远程扩展停止词字典–> <!– <entry key=“remote_ext_stopwords”>words_location</entry> –></properties>增加了my.dic,然后重启ES。我们再看一下效果:GET /_analyze{ “analyzer”: “ik_smart”, “text”: “去朝阳公园”}结果:{ “tokens”: [ { “token”: “去”, “start_offset”: 0, “end_offset”: 1, “type”: “CN_CHAR”, “position”: 0 }, { “token”: “朝阳公园”, “start_offset”: 1, “end_offset”: 5, “type”: “CN_WORD”, “position”: 1 } ]}说明自定义词典生效了。如果有多个词典,使用英文分号隔开:<entry key=“ext_dict”>my.dic;custom/single_word_low_freq.dic</entry>另外,我们看到配置里还有个扩展停止词字典,这个是用来辅助断句的。我们可以看一下自带的一个扩展停止词字典:$ head -n 5 extra_stopword.dic也了仍从以也就是IK分词器遇到这些词就认为前面的词语不会与这些词构成词语。IK分词也支持远程词典,远程词典的好处是支持热更新。词典格式和本地的一致,都是一行一个分词(换行符用 \n),还要求填写的URL满足:该 http 请求需要返回两个头部(header),一个是 Last-Modified,一个是 ETag,这两者都是字符串类型,只要有一个发生变化,该插件就会去抓取新的分词进而更新词库。详见:https://github.com/medcl/elas… 热更新 IK 分词使用方法 部分。注意:上面的示例里我们改的是`elasticsearch-6.2.4/config/analysis-ik/目录下内容,是因为IK是通过方法2里elasticsearch-plugin安装的。如果你是通过解压方式安装的,那么IK配置会在plugins目录,即:elasticsearch-6.2.4/plugins/analysis-ik/config。也就是说插件的配置既可以放在插件所在目录,也可以放在Elasticsearch的config目录里面。ES内置的Analyzer分析器es自带了许多内置的Analyzer分析器,无需配置就可以直接在index中使用:标准分词器(standard):以单词边界切分字符串为terms,根据Unicode文本分割算法。它会移除大部分的标点符号,小写分词后的term,支持停用词。简单分词器(simple):该分词器会在遇到非字母时切分字符串,小写所有的term。空格分词器(whitespace):遇到空格字符时切分字符串,停用词分词器(stop):类似简单分词器,同时支持移除停用词。关键词分词器(keyword):无操作分词器,会输出与输入相同的内容作为一个single term。模式分词器(pattern):使用正则表达式讲字符串且分为terms。支持小写字母和停用词。语言分词器(language):支持许多基于特定语言的分词器,比如english或french。签名分词器(fingerprint):是一个专家分词器,会产生一个签名,可以用于去重检测。自定义分词器:如果内置分词器无法满足你的需求,可以自定义custom分词器,根据不同的character filters,tokenizer,token filters的组合 。例如IK就是自定义分词器。详见文档:https://www.elastic.co/guide/…参考1、medcl/elasticsearch-analysis-ik: The IK Analysis plugin integrates Lucene IK analyzer into elasticsearch, support customized dictionary.https://github.com/medcl/elas… 2、ElesticSearch IK中文分词使用详解 - xsdxs的博客 - CSDN博客 https://blog.csdn.net/xsdxs/a… ...

December 1, 2018 · 2 min · jiezi

Elasticsearch 参考指南(Reindex API)

Reindex API重新索引要求为源索引中的所有文档启用_source。重新索引不会尝试设置目标索引,它不会复制源索引的设置,你应该在运行_reindex操作之前设置目标索引,包括设置映射、碎片数、副本等。_reindex的最基本形式只是将文档从一个索引复制到另一个索引,这会将twitter索引中的文档复制到new_twitter索引中:POST _reindex{ “source”: { “index”: “twitter” }, “dest”: { “index”: “new_twitter” }}这将返回如下内容:{ “took” : 147, “timed_out”: false, “created”: 120, “updated”: 0, “deleted”: 0, “batches”: 1, “version_conflicts”: 0, “noops”: 0, “retries”: { “bulk”: 0, “search”: 0 }, “throttled_millis”: 0, “requests_per_second”: -1.0, “throttled_until_millis”: 0, “total”: 120, “failures” : [ ]}就像_update_by_query一样,_reindex获取源索引的快照,但其目标必须是不同的索引,因此版本冲突不太可能,dest元素可以像索引API一样配置,以控制乐观并发控制。只是省略version_type(如上所述)或将其设置为internal将导致Elasticsearch盲目地将文档转储到目标中,覆盖任何碰巧具有相同类型和id的文档:POST _reindex{ “source”: { “index”: “twitter” }, “dest”: { “index”: “new_twitter”, “version_type”: “internal” }}将version_type设置为external将导致Elasticsearch保留源中的version,创建缺少的任何文档,并更新目标索引中具有旧版本的文档而不是源索引中的任何文档:POST _reindex{ “source”: { “index”: “twitter” }, “dest”: { “index”: “new_twitter”, “version_type”: “external” }}设置op_type为create将导致_reindex仅在目标索引中创建缺少的文档,所有现有文档都会导致版本冲突:POST _reindex{ “source”: { “index”: “twitter” }, “dest”: { “index”: “new_twitter”, “op_type”: “create” }}默认情况下,版本冲突会中止_reindex进程,但你可以在请求体中设置"conflicts": “proceed"即可计算:POST _reindex{ “conflicts”: “proceed”, “source”: { “index”: “twitter” }, “dest”: { “index”: “new_twitter”, “op_type”: “create” }}你可以通过向source添加类型或添加查询来限制文档,这个只会将kimchy写的推文复制到new_twitter中:POST _reindex{ “source”: { “index”: “twitter”, “type”: “_doc”, “query”: { “term”: { “user”: “kimchy” } } }, “dest”: { “index”: “new_twitter” }} ...

November 16, 2018 · 1 min · jiezi

ElasticSearch 按照一定规则分割index

ElasticSearch随着数据越来越大,查询时间也越来越慢,把所有数据放入同一个索引将不是一个好的方法。所以优化时,将其按照一定规则重新reindex将提高不少效率按照某字段的日期比如将index_name重新索引为index_name-yyyy-MM-dd根据字段created_at,原日期格式是"yyyy-MM-dd’T’HH:mm:ss,计算得出yyyy-MM-ddinline中是 是类Java代码, 可以复制出来后自己编写POST _reindex?wait_for_completion=false{ “source”: { “index”: “index_name” }, “dest”: { “index”: “index_name-” }, “script”: { “inline”: “def sf = new SimpleDateFormat("yyyy-MM-dd’T’HH:mm:ss");def o = new SimpleDateFormat("yyyy-MM-dd");def dt = sf.parse(ctx._source.created_at);ctx._index=‘index_name-’ + o.format(dt);” }}按照ID范围比如根据ID / 10000000取整,也就是1千万数据放一个indexPOST _reindex?wait_for_completion=false{ “source”: { “index”: “index_name” }, “dest”: { “index”: “index_name-” }, “script”: { “inline”: “ctx._index=‘index_name-’ + Long.valueOf(ctx._source.id / 10000000).toString();” }}

October 17, 2018 · 1 min · jiezi

教你如何在 IDEA 远程 Debug ElasticSearch

前提之前在源码阅读环境搭建文章中写过我遇到的一个问题迟迟没有解决,也一直困扰着我。问题如下,在启动的时候解决掉其他异常和报错后,最后剩下这个错误一直解决不了:[2018-08-01T09:44:27,370][ERROR][o.e.b.ElasticsearchUncaughtExceptionHandler] [] fatal error in thread [main], exitingjava.lang.NoClassDefFoundError: org/elasticsearch/plugins/ExtendedPluginsClassLoader at org.elasticsearch.plugins.PluginsService.loadBundle(PluginsService.java:632) ~[main/:?] at org.elasticsearch.plugins.PluginsService.loadBundles(PluginsService.java:557) ~[main/:?] at org.elasticsearch.plugins.PluginsService.<init>(PluginsService.java:162) ~[main/:?] at org.elasticsearch.node.Node.<init>(Node.java:311) ~[main/:?] at org.elasticsearch.node.Node.<init>(Node.java:252) ~[main/:?] at org.elasticsearch.bootstrap.Bootstrap$5.<init>(Bootstrap.java:213) ~[main/:?] at org.elasticsearch.bootstrap.Bootstrap.setup(Bootstrap.java:213) ~[main/:?] at org.elasticsearch.bootstrap.Bootstrap.init(Bootstrap.java:326) ~[main/:?] at org.elasticsearch.bootstrap.Elasticsearch.init(Elasticsearch.java:136) ~[main/:?] at org.elasticsearch.bootstrap.Elasticsearch.execute(Elasticsearch.java:127) ~[main/:?] at org.elasticsearch.cli.EnvironmentAwareCommand.execute(EnvironmentAwareCommand.java:86) ~[main/:?] at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:124) ~[main/:?] at org.elasticsearch.cli.Command.main(Command.java:90) ~[main/:?] at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:93) ~[main/:?] at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:86) ~[main/:?]Caused by: java.lang.ClassNotFoundException: org.elasticsearch.plugins.ExtendedPluginsClassLoader at jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582) ~[?:?] at jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:190) ~[?:?] at java.lang.ClassLoader.loadClass(ClassLoader.java:499) ~[?:?] … 15 more网上的解决办法也试了很多遍,包括自己也在 GitHub issue 提问了,也没能解决。然后后面自己分享文章在掘金也发现有人和我有同样的问题。下面讲讲另一种可以让你继续看源码的方法。远程 Debug前提条件是你之前已经把项目导入进 IDEA 了,如果你还没了解,请看之前的文章,这里不重复了。启动一个实例在你 git 拉取下的代码,切换你要阅读的分支代码后,执行下面这条命令启动一个 debug 的实例:./gradlew run –debug-jvm启动等会后,就可以看到启动好后的端口号为 8000 了。配置 IDEA新建一个远程的 debug:配置如下图:接下来点击 OK 就好了。然后点击下面的 debug 图标:启动后如下:这时就可以发现是可以把整个流程全启动了,也不会报什么错误!流程全启动后,你会发现终端的日志都打印出来了(注意:这时不是打印在你的 IDEA 控制台)总结遇到问题,多思考,多搜索,多想办法解决!这样才能够不断的提升你解决问题的能力!关注我最后转载请务必注明文章出处为:http://www.54tianzhisheng.cn/2018/08/14/idea-remote-debug-elasticsearch/相关文章1、渣渣菜鸡为什么要看 ElasticSearch 源码?2、渣渣菜鸡的 ElasticSearch 源码解析 —— 环境搭建3、渣渣菜鸡的 ElasticSearch 源码解析 —— 启动流程(上)4、渣渣菜鸡的 ElasticSearch 源码解析 —— 启动流程(下)5、Elasticsearch 系列文章(一):Elasticsearch 默认分词器和中分分词器之间的比较及使用方法6、Elasticsearch 系列文章(二):全文搜索引擎 Elasticsearch 集群搭建入门教程7、Elasticsearch 系列文章(三):ElasticSearch 集群监控8、Elasticsearch 系列文章(四):ElasticSearch 单个节点监控9、Elasticsearch 系列文章(五):ELK 实时日志分析平台环境搭建10、教你如何在 IDEA 远程 Debug ElasticSearch ...

August 30, 2018 · 1 min · jiezi

渣渣的 ElasticSearch 源码解析 —— 启动流程(下)

关注我转载请务必注明原创地址为:http://www.54tianzhisheng.cn/2018/08/12/es-code03/前提上篇文章写完了 ES 流程启动的一部分,main 方法都入口,以及创建 Elasticsearch 运行的必须环境以及相关配置,接着就是创建该环境的节点了。Node 的创建看下新建节点的代码:(代码比较多,这里是比较关键的地方,我就把注释直接写在代码上面了,实在不好拆开这段代码,300 多行代码)public Node(Environment environment) { this(environment, Collections.emptyList()); //执行下面的代码 }protected Node(final Environment environment, Collection<Class<? extends Plugin>> classpathPlugins) { final List<Closeable> resourcesToClose = new ArrayList<>(); // register everything we need to release in the case of an error boolean success = false; {// use temp logger just to say we are starting. we can’t use it later on because the node name might not be set Logger logger = Loggers.getLogger(Node.class, NODE_NAME_SETTING.get(environment.settings())); logger.info(“initializing …”); } try { originalSettings = environment.settings(); Settings tmpSettings = Settings.builder().put(environment.settings()) .put(Client.CLIENT_TYPE_SETTING_S.getKey(), CLIENT_TYPE).build();// create the node environment as soon as possible, to recover the node id and enable logging try { nodeEnvironment = new NodeEnvironment(tmpSettings, environment); //1、创建节点环境,比如节点名称,节点ID,分片信息,存储元,以及分配内存准备给节点使用 resourcesToClose.add(nodeEnvironment); } catch (IOException ex) { throw new IllegalStateException(“Failed to create node environment”, ex); } final boolean hadPredefinedNodeName = NODE_NAME_SETTING.exists(tmpSettings); final String nodeId = nodeEnvironment.nodeId(); tmpSettings = addNodeNameIfNeeded(tmpSettings, nodeId); final Logger logger = Loggers.getLogger(Node.class, tmpSettings);// this must be captured after the node name is possibly added to the settings final String nodeName = NODE_NAME_SETTING.get(tmpSettings); if (hadPredefinedNodeName == false) { logger.info(“node name derived from node ID [{}]; set [{}] to override”, nodeId, NODE_NAME_SETTING.getKey()); } else { logger.info(“node name [{}], node ID [{}]”, nodeName, nodeId); } //2、打印出JVM相关信息 final JvmInfo jvmInfo = JvmInfo.jvmInfo(); logger.info(“version[{}], pid[{}], build[{}/{}/{}/{}], OS[{}/{}/{}], JVM[{}/{}/{}/{}]”, Version.displayVersion(Version.CURRENT, Build.CURRENT.isSnapshot()), jvmInfo.pid(), Build.CURRENT.flavor().displayName(), Build.CURRENT.type().displayName(), Build.CURRENT.shortHash(), Build.CURRENT.date(), Constants.OS_NAME, Constants.OS_VERSION, Constants.OS_ARCH,Constants.JVM_VENDOR,Constants.JVM_NAME, Constants.JAVA_VERSION,Constants.JVM_VERSION); logger.info(“JVM arguments {}”, Arrays.toString(jvmInfo.getInputArguments())); //检查当前版本是不是 pre-release 版本(Snapshot), warnIfPreRelease(Version.CURRENT, Build.CURRENT.isSnapshot(), logger); 。。。 this.pluginsService = new PluginsService(tmpSettings, environment.configFile(), environment.modulesFile(), environment.pluginsFile(), classpathPlugins); //3、利用PluginsService加载相应的模块和插件 this.settings = pluginsService.updatedSettings(); localNodeFactory = new LocalNodeFactory(settings, nodeEnvironment.nodeId());// create the environment based on the finalized (processed) view of the settings// this is just to makes sure that people get the same settings, no matter where they ask them from this.environment = new Environment(this.settings, environment.configFile()); Environment.assertEquivalent(environment, this.environment); final List<ExecutorBuilder<?>> executorBuilders = pluginsService.getExecutorBuilders(settings); //线程池 final ThreadPool threadPool = new ThreadPool(settings, executorBuilders.toArray(new ExecutorBuilder[0])); resourcesToClose.add(() -> ThreadPool.terminate(threadPool, 10, TimeUnit.SECONDS)); // adds the context to the DeprecationLogger so that it does not need to be injected everywhere DeprecationLogger.setThreadContext(threadPool.getThreadContext()); resourcesToClose.add(() -> DeprecationLogger.removeThreadContext(threadPool.getThreadContext())); final List<Setting<?>> additionalSettings = new ArrayList<>(pluginsService.getPluginSettings()); //额外配置 final List<String> additionalSettingsFilter = new ArrayList<>(pluginsService.getPluginSettingsFilter()); for (final ExecutorBuilder<?> builder : threadPool.builders()) { //4、加载一些额外配置 additionalSettings.addAll(builder.getRegisteredSettings()); } client = new NodeClient(settings, threadPool);//5、创建一个节点客户端 //6、缓存一系列模块,如NodeModule,ClusterModule,IndicesModule,ActionModule,GatewayModule,SettingsModule,RepositioriesModule,scriptModule,analysisModule final ResourceWatcherService resourceWatcherService = new ResourceWatcherService(settings, threadPool); final ScriptModule scriptModule = new ScriptModule(settings, pluginsService.filterPlugins(ScriptPlugin.class)); AnalysisModule analysisModule = new AnalysisModule(this.environment, pluginsService.filterPlugins(AnalysisPlugin.class)); // this is as early as we can validate settings at this point. we already pass them to ScriptModule as well as ThreadPool so we might be late here already final SettingsModule settingsModule = new SettingsModule(this.settings, additionalSettings, additionalSettingsFilter);scriptModule.registerClusterSettingsListeners(settingsModule.getClusterSettings()); resourcesToClose.add(resourceWatcherService); final NetworkService networkService = new NetworkService( getCustomNameResolvers(pluginsService.filterPlugins(DiscoveryPlugin.class))); List<ClusterPlugin> clusterPlugins = pluginsService.filterPlugins(ClusterPlugin.class); final ClusterService clusterService = new ClusterService(settings, settingsModule.getClusterSettings(), threadPool, ClusterModule.getClusterStateCustomSuppliers(clusterPlugins)); clusterService.addStateApplier(scriptModule.getScriptService()); resourcesToClose.add(clusterService); final IngestService ingestService = new IngestService(settings, threadPool, this.environment, scriptModule.getScriptService(), analysisModule.getAnalysisRegistry(), pluginsService.filterPlugins(IngestPlugin.class)); final DiskThresholdMonitor listener = new DiskThresholdMonitor(settings, clusterService::state, clusterService.getClusterSettings(), client); final ClusterInfoService clusterInfoService = newClusterInfoService(settings, clusterService, threadPool, client,listener::onNewInfo); final UsageService usageService = new UsageService(settings); ModulesBuilder modules = new ModulesBuilder();// plugin modules must be added here, before others or we can get crazy injection errors… for (Module pluginModule : pluginsService.createGuiceModules()) { modules.add(pluginModule); } final MonitorService monitorService = new MonitorService(settings, nodeEnvironment, threadPool, clusterInfoService); ClusterModule clusterModule = new ClusterModule(settings, clusterService, clusterPlugins, clusterInfoService); modules.add(clusterModule); IndicesModule indicesModule = new IndicesModule(pluginsService.filterPlugins(MapperPlugin.class)); modules.add(indicesModule); SearchModule searchModule = new SearchModule(settings, false, pluginsService.filterPlugins(SearchPlugin.class)); CircuitBreakerService circuitBreakerService = createCircuitBreakerService(settingsModule.getSettings(), settingsModule.getClusterSettings()); resourcesToClose.add(circuitBreakerService); modules.add(new GatewayModule()); PageCacheRecycler pageCacheRecycler = createPageCacheRecycler(settings); BigArrays bigArrays = createBigArrays(pageCacheRecycler, circuitBreakerService); resourcesToClose.add(bigArrays); modules.add(settingsModule); List<NamedWriteableRegistry.Entry> namedWriteables = Stream.of( NetworkModule.getNamedWriteables().stream(), indicesModule.getNamedWriteables().stream(), searchModule.getNamedWriteables().stream(), pluginsService.filterPlugins(Plugin.class).stream() .flatMap(p -> p.getNamedWriteables().stream()), ClusterModule.getNamedWriteables().stream()) .flatMap(Function.identity()).collect(Collectors.toList()); final NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(namedWriteables); NamedXContentRegistry xContentRegistry = new NamedXContentRegistry(Stream.of( NetworkModule.getNamedXContents().stream(), searchModule.getNamedXContents().stream(), pluginsService.filterPlugins(Plugin.class).stream() .flatMap(p -> p.getNamedXContent().stream()), ClusterModule.getNamedXWriteables().stream()).flatMap(Function.identity()).collect(toList())); modules.add(new RepositoriesModule(this.environment, pluginsService.filterPlugins(RepositoryPlugin.class), xContentRegistry)); final MetaStateService metaStateService = new MetaStateService(settings, nodeEnvironment, xContentRegistry); final IndicesService indicesService = new IndicesService(settings, pluginsService, nodeEnvironment, xContentRegistry,analysisModule.getAnalysisRegistry(), clusterModule.getIndexNameExpressionResolver(), indicesModule.getMapperRegistry(), namedWriteableRegistry,threadPool, settingsModule.getIndexScopedSettings(), circuitBreakerService, bigArrays, scriptModule.getScriptService(),client, metaStateService); Collection<Object> pluginComponents = pluginsService.filterPlugins(Plugin.class).stream() .flatMap(p -> p.createComponents(client, clusterService, threadPool, resourceWatcherService,scriptModule.getScriptService(), xContentRegistry, environment, nodeEnvironment,namedWriteableRegistry).stream()).collect(Collectors.toList()); ActionModule actionModule = new ActionModule(false, settings, clusterModule.getIndexNameExpressionResolver(), settingsModule.getIndexScopedSettings(), settingsModule.getClusterSettings(), settingsModule.getSettingsFilter(),threadPool, pluginsService.filterPlugins(ActionPlugin.class), client, circuitBreakerService, usageService); modules.add(actionModule); //7、获取RestController,用于处理各种Elasticsearch的rest命令,如_cat,_all,_cat/health,_clusters等rest命令(Elasticsearch称之为action) final RestController restController = actionModule.getRestController(); final NetworkModule networkModule = new NetworkModule(settings, false, pluginsService.filterPlugins(NetworkPlugin.class),threadPool, bigArrays, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, xContentRegistry,networkService, restController); Collection<UnaryOperator<Map<String, MetaData.Custom>>> customMetaDataUpgraders = pluginsService.filterPlugins(Plugin.class).stream() .map(Plugin::getCustomMetaDataUpgrader) .collect(Collectors.toList()); Collection<UnaryOperator<Map<String, IndexTemplateMetaData>>> indexTemplateMetaDataUpgraders = pluginsService.filterPlugins(Plugin.class).stream() .map(Plugin::getIndexTemplateMetaDataUpgrader) .collect(Collectors.toList()); Collection<UnaryOperator<IndexMetaData>> indexMetaDataUpgraders = pluginsService.filterPlugins(Plugin.class).stream() .map(Plugin::getIndexMetaDataUpgrader).collect(Collectors.toList()); final MetaDataUpgrader metaDataUpgrader = new MetaDataUpgrader(customMetaDataUpgraders, indexTemplateMetaDataUpgraders); final MetaDataIndexUpgradeService metaDataIndexUpgradeService = new MetaDataIndexUpgradeService(settings, xContentRegistry, indicesModule.getMapperRegistry(), settingsModule.getIndexScopedSettings(), indexMetaDataUpgraders); final GatewayMetaState gatewayMetaState = new GatewayMetaState(settings, nodeEnvironment, metaStateService, metaDataIndexUpgradeService, metaDataUpgrader); new TemplateUpgradeService(settings, client, clusterService, threadPool, indexTemplateMetaDataUpgraders); final Transport transport = networkModule.getTransportSupplier().get(); Set<String> taskHeaders = Stream.concat( pluginsService.filterPlugins(ActionPlugin.class).stream().flatMap(p -> p.getTaskHeaders().stream()), Stream.of(“X-Opaque-Id”) ).collect(Collectors.toSet()); final TransportService transportService = newTransportService(settings, transport, threadPool, networkModule.getTransportInterceptor(), localNodeFactory, settingsModule.getClusterSettings(), taskHeaders); final ResponseCollectorService responseCollectorService = new ResponseCollectorService(this.settings, clusterService); final SearchTransportService searchTransportService = new SearchTransportService(settings, transportService, SearchExecutionStatsCollector.makeWrapper(responseCollectorService)); final Consumer<Binder> httpBind; final HttpServerTransport httpServerTransport; if (networkModule.isHttpEnabled()) { httpServerTransport = networkModule.getHttpServerTransportSupplier().get(); httpBind = b -> {b.bind(HttpServerTransport.class).toInstance(httpServerTransport); }; } else { httpBind = b -> { b.bind(HttpServerTransport.class).toProvider(Providers.of(null)); }; httpServerTransport = null; } final DiscoveryModule discoveryModule = new DiscoveryModule(this.settings, threadPool, transportService, namedWriteableRegistry,networkService, clusterService.getMasterService(), clusterService.getClusterApplierService(),clusterService.getClusterSettings(), pluginsService.filterPlugins(DiscoveryPlugin.class),clusterModule.getAllocationService()); this.nodeService = new NodeService(settings, threadPool, monitorService, discoveryModule.getDiscovery(),transportService, indicesService, pluginsService, circuitBreakerService, scriptModule.getScriptService(),httpServerTransport, ingestService, clusterService, settingsModule.getSettingsFilter(), responseCollectorService,searchTransportService); final SearchService searchService = newSearchService(clusterService, indicesService, threadPool, scriptModule.getScriptService(), bigArrays, searchModule.getFetchPhase(),responseCollectorService); final List<PersistentTasksExecutor<?>> tasksExecutors = pluginsService .filterPlugins(PersistentTaskPlugin.class).stream() .map(p -> p.getPersistentTasksExecutor(clusterService, threadPool, client)) .flatMap(List::stream) .collect(toList()); final PersistentTasksExecutorRegistry registry = new PersistentTasksExecutorRegistry(settings, tasksExecutors); final PersistentTasksClusterService persistentTasksClusterService = new PersistentTasksClusterService(settings, registry, clusterService); final PersistentTasksService persistentTasksService = new PersistentTasksService(settings, clusterService, threadPool, client);//8、绑定处理各种服务的实例,这里是最核心的地方,也是Elasticsearch能处理各种服务的核心. modules.add(b -> { b.bind(Node.class).toInstance(this); b.bind(NodeService.class).toInstance(nodeService); b.bind(NamedXContentRegistry.class).toInstance(xContentRegistry); b.bind(PluginsService.class).toInstance(pluginsService); b.bind(Client.class).toInstance(client); b.bind(NodeClient.class).toInstance(client); b.bind(Environment.class).toInstance(this.environment); b.bind(ThreadPool.class).toInstance(threadPool); b.bind(NodeEnvironment.class).toInstance(nodeEnvironment); b.bind(ResourceWatcherService.class).toInstance(resourceWatcherService);b.bind(CircuitBreakerService.class).toInstance(circuitBreakerService); b.bind(BigArrays.class).toInstance(bigArrays); b.bind(ScriptService.class).toInstance(scriptModule.getScriptService()); b.bind(AnalysisRegistry.class).toInstance(analysisModule.getAnalysisRegistry()); b.bind(IngestService.class).toInstance(ingestService); b.bind(UsageService.class).toInstance(usageService); b.bind(NamedWriteableRegistry.class).toInstance(namedWriteableRegistry); b.bind(MetaDataUpgrader.class).toInstance(metaDataUpgrader); b.bind(MetaStateService.class).toInstance(metaStateService); b.bind(IndicesService.class).toInstance(indicesService); b.bind(SearchService.class).toInstance(searchService); b.bind(SearchTransportService.class).toInstance(searchTransportService);b.bind(SearchPhaseController.class).toInstance(new SearchPhaseController(settings, searchService::createReduceContext)); b.bind(Transport.class).toInstance(transport); b.bind(TransportService.class).toInstance(transportService); b.bind(NetworkService.class).toInstance(networkService); b.bind(UpdateHelper.class).toInstance(new UpdateHelper(settings, scriptModule.getScriptService()));b.bind(MetaDataIndexUpgradeService.class).toInstance(metaDataIndexUpgradeService); b.bind(ClusterInfoService.class).toInstance(clusterInfoService); b.bind(GatewayMetaState.class).toInstance(gatewayMetaState); b.bind(Discovery.class).toInstance(discoveryModule.getDiscovery()); { RecoverySettings recoverySettings = new RecoverySettings(settings, settingsModule.getClusterSettings()); processRecoverySettings(settingsModule.getClusterSettings(), recoverySettings); b.bind(PeerRecoverySourceService.class).toInstance(new PeerRecoverySourceService(settings, transportService,indicesService, recoverySettings)); b.bind(PeerRecoveryTargetService.class).toInstance(new PeerRecoveryTargetService(settings, threadPool,transportService, recoverySettings, clusterService)); } httpBind.accept(b); pluginComponents.stream().forEach(p -> b.bind((Class) p.getClass()).toInstance(p));b.bind(PersistentTasksService.class).toInstance(persistentTasksService); b.bind(PersistentTasksClusterService.class).toInstance(persistentTasksClusterService);b.bind(PersistentTasksExecutorRegistry.class).toInstance(registry); }); injector = modules.createInjector(); // TODO hack around circular dependencies problems in AllocationServiceclusterModule.getAllocationService().setGatewayAllocator(injector.getInstance(GatewayAllocator.class)); List<LifecycleComponent> pluginLifecycleComponents = pluginComponents.stream() .filter(p -> p instanceof LifecycleComponent) .map(p -> (LifecycleComponent) p).collect(Collectors.toList()); //9、利用Guice将各种模块以及服务(xxxService)注入到Elasticsearch环境中pluginLifecycleComponents.addAll(pluginsService.getGuiceServiceClasses().stream() .map(injector::getInstance).collect(Collectors.toList())); resourcesToClose.addAll(pluginLifecycleComponents); this.pluginLifecycleComponents = Collections.unmodifiableList(pluginLifecycleComponents); client.initialize(injector.getInstance(new Key<Map<GenericAction, TransportAction>>() {}), () -> clusterService.localNode().getId(), transportService.getRemoteClusterService()); if (NetworkModule.HTTP_ENABLED.get(settings)) { //如果elasticsearch.yml文件中配置了http.enabled参数(默认为true),则会初始化RestHandlers logger.debug(“initializing HTTP handlers …”); actionModule.initRestHandlers(() -> clusterService.state().nodes()); //初始化RestHandlers, 解析集群命令,如_cat/,_cat/health } //10、初始化工作完成 logger.info(“initialized”); success = true; } catch (IOException ex) { throw new ElasticsearchException(“failed to bind service”, ex); } finally { if (!success) { IOUtils.closeWhileHandlingException(resourcesToClose); } }}上面代码真的很多,这里再说下上面这么多代码主要干了什么吧:(具体是哪行代码执行的如下流程,上面代码中也标记了)1、创建节点环境,比如节点名称,节点 ID,分片信息,存储元,以及分配内存准备给节点使用2、打印出 JVM 相关信息3、利用 PluginsService 加载相应的模块和插件,具体哪些模块可以去 modules 目录下查看4、加载一些额外的配置参数5、创建一个节点客户端6、缓存一系列模块,如NodeModule,ClusterModule,IndicesModule,ActionModule,GatewayModule,SettingsModule,RepositioriesModule,scriptModule,analysisModule7、获取 RestController,用于处理各种 Elasticsearch 的 rest 命令,如 _cat, _all, _cat/health, _clusters 等 rest命令8、绑定处理各种服务的实例9、利用 Guice 将各种模块以及服务(xxxService)注入到 Elasticsearch 环境中10、初始化工作完成(打印日志)JarHell 报错解释前一篇阅读源码环境搭建的文章写过用 JDK 1.8 编译 ES 源码是会遇到如下异常:org.elasticsearch.bootstrap.StartupException: java.lang.IllegalStateException: jar hell!这里说下就是 setup 方法中的如下代码导致的try { // look for jar hell final Logger logger = ESLoggerFactory.getLogger(JarHell.class); JarHell.checkJarHell(logger::debug);} catch (IOException | URISyntaxException e) { throw new BootstrapException(e);}所以你如果是用 JDK 1.8 编译的,那么就需要把所有的有这块的代码给注释掉就可以编译成功的。我自己试过用 JDK 10 编译是没有出现这里报错的。正式启动 ES 节点回到上面 Bootstrap 中的静态 init 方法中,接下来就是正式启动 elasticsearch 节点了:INSTANCE.start(); //调用下面的 start 方法private void start() throws NodeValidationException { node.start(); //正式启动 Elasticsearch 节点 keepAliveThread.start();}接下来看看这个 start 方法里面的 node.start() 方法源码:public Node start() throws NodeValidationException { if (!lifecycle.moveToStarted()) { return this; } Logger logger = Loggers.getLogger(Node.class, NODE_NAME_SETTING.get(settings)); logger.info(“starting …”); pluginLifecycleComponents.forEach(LifecycleComponent::start); //1、利用Guice获取上述注册的各种模块以及服务 //Node 的启动其实就是 node 里每个组件的启动,同样的,分别调用不同的实例的 start 方法来启动这个组件, 如下: injector.getInstance(MappingUpdatedAction.class).setClient(client); injector.getInstance(IndicesService.class).start(); injector.getInstance(IndicesClusterStateService.class).start(); injector.getInstance(SnapshotsService.class).start(); injector.getInstance(SnapshotShardsService.class).start(); injector.getInstance(RoutingService.class).start(); injector.getInstance(SearchService.class).start(); nodeService.getMonitorService().start(); final ClusterService clusterService = injector.getInstance(ClusterService.class); final NodeConnectionsService nodeConnectionsService = injector.getInstance(NodeConnectionsService.class); nodeConnectionsService.start(); clusterService.setNodeConnectionsService(nodeConnectionsService); injector.getInstance(ResourceWatcherService.class).start(); injector.getInstance(GatewayService.class).start(); Discovery discovery = injector.getInstance(Discovery.class); clusterService.getMasterService().setClusterStatePublisher(discovery::publish); // Start the transport service now so the publish address will be added to the local disco node in ClusterService TransportService transportService = injector.getInstance(TransportService.class); transportService.getTaskManager().setTaskResultsService(injector.getInstance(TaskResultsService.class)); transportService.start(); assert localNodeFactory.getNode() != null; assert transportService.getLocalNode().equals(localNodeFactory.getNode()) : “transportService has a different local node than the factory provided”; final MetaData onDiskMetadata; try { // we load the global state here (the persistent part of the cluster state stored on disk) to // pass it to the bootstrap checks to allow plugins to enforce certain preconditions based on the recovered state. if (DiscoveryNode.isMasterNode(settings) || DiscoveryNode.isDataNode(settings)) {//根据配置文件看当前节点是master还是data节点 onDiskMetadata = injector.getInstance(GatewayMetaState.class).loadMetaState(); } else { onDiskMetadata = MetaData.EMPTY_META_DATA; } assert onDiskMetadata != null : “metadata is null but shouldn’t”; // this is never null } catch (IOException e) { throw new UncheckedIOException(e); } validateNodeBeforeAcceptingRequests(new BootstrapContext(settings, onDiskMetadata), transportService.boundAddress(), pluginsService .filterPlugins(Plugin .class) .stream() .flatMap(p -> p.getBootstrapChecks().stream()).collect(Collectors.toList())); //2、将当前节点加入到一个集群簇中去,并启动当前节点 clusterService.addStateApplier(transportService.getTaskManager()); // start after transport service so the local disco is known discovery.start(); // start before cluster service so that it can set initial state on ClusterApplierService clusterService.start(); assert clusterService.localNode().equals(localNodeFactory.getNode()) : “clusterService has a different local node than the factory provided”; transportService.acceptIncomingRequests(); discovery.startInitialJoin(); // tribe nodes don’t have a master so we shouldn’t register an observer s final TimeValue initialStateTimeout = DiscoverySettings.INITIAL_STATE_TIMEOUT_SETTING.get(settings); if (initialStateTimeout.millis() > 0) { final ThreadPool thread = injector.getInstance(ThreadPool.class); ClusterState clusterState = clusterService.state(); ClusterStateObserver observer = new ClusterStateObserver(clusterState, clusterService, null, logger, thread.getThreadContext()); if (clusterState.nodes().getMasterNodeId() == null) { logger.debug(“waiting to join the cluster. timeout [{}]”, initialStateTimeout); final CountDownLatch latch = new CountDownLatch(1); observer.waitForNextChange(new ClusterStateObserver.Listener() { @Override public void onNewClusterState(ClusterState state) { latch.countDown(); } @Override public void onClusterServiceClose() { latch.countDown(); } @Override public void onTimeout(TimeValue timeout) { logger.warn(“timed out while waiting for initial discovery state - timeout: {}”, initialStateTimeout); latch.countDown(); } }, state -> state.nodes().getMasterNodeId() != null, initialStateTimeout); try { latch.await(); } catch (InterruptedException e) { throw new ElasticsearchTimeoutException(“Interrupted while waiting for initial discovery state”); } } } if (NetworkModule.HTTP_ENABLED.get(settings)) { injector.getInstance(HttpServerTransport.class).start(); } if (WRITE_PORTS_FILE_SETTING.get(settings)) { if (NetworkModule.HTTP_ENABLED.get(settings)) { HttpServerTransport http = injector.getInstance(HttpServerTransport.class); writePortsFile(“http”, http.boundAddress()); } TransportService transport = injector.getInstance(TransportService.class); writePortsFile(“transport”, transport.boundAddress()); } logger.info(“started”); pluginsService.filterPlugins(ClusterPlugin.class).forEach(ClusterPlugin::onNodeStarted); return this;}上面代码主要是:1、利用 Guice 获取上述注册的各种模块以及服务,然后启动 node 里每个组件(分别调用不同的实例的 start 方法来启动)2、打印日志(启动节点完成)总结这篇文章主要把大概启动流程串通了,讲了下 node 节点的创建和正式启动 ES 节点了。因为篇幅较多所以拆开成两篇,先不扣细节了,后面流程启动文章写完后我们再单一的扣细节。相关文章1、渣渣菜鸡为什么要看 ElasticSearch 源码?2、渣渣菜鸡的 ElasticSearch 源码解析 —— 环境搭建3、渣渣菜鸡的 ElasticSearch 源码解析 —— 启动流程(上)4、渣渣菜鸡的 ElasticSearch 源码解析 —— 启动流程(下)5、Elasticsearch 系列文章(一):Elasticsearch 默认分词器和中分分词器之间的比较及使用方法6、Elasticsearch 系列文章(二):全文搜索引擎 Elasticsearch 集群搭建入门教程7、Elasticsearch 系列文章(三):ElasticSearch 集群监控8、Elasticsearch 系列文章(四):ElasticSearch 单个节点监控9、Elasticsearch 系列文章(五):ELK 实时日志分析平台环境搭建10、教你如何在 IDEA 远程 Debug ElasticSearch ...

August 30, 2018 · 8 min · jiezi