首先我们谈几个公司,如雷贯耳的:百度、谷歌、维基百科;这些公司都有一个相似性就是门户网站,可以提供我们通过关键字搜索,然后快速的检索出我们想要的信息;
【网页百度展示】
比如我们检索传智播客,百度后台就会按照这个关键字进行查找(里面有搜索库,以及爬虫库),然后按照权重来进行从上打下的排序,给我们高亮的展示出现
【京东或者淘宝展示】
随便搜索东西,就会高精度的展示我们想要的;就会根据关键词进行海量数据的快速的检索
比如我们查找:”护手霜“ , 那么这期间内部会经过大体的:1、分词(护手,手霜,护等)2、根据这些词去海量的数据中检索 3、然后根据权重把检索出来的信息进行排序展示给我们
【传统做法】
那么对于一般的公司,初期是没有那么多数据的,所以很多公司更倾向于使用传统的数据库:mysql;比如我们要查找关键字”传智播客“,那么查询的方式大概就是:select * from table where field like ‘%传智播客%’; 但是随着业务发展,数据会不断的膨胀,那么问题就来了;mysql单表查询能力即便经过了优化,它的极限也就是400W左右的数据量。而且还会经常出现查询超时的现象;
然后很多公司开始对数据库进行横向和纵向的扩容,开始进行数据库表的“拆分”:横向拆分和纵向拆分;但是即便这样操作,仍然会出现很多问题,比如:
1、数据库会出现单点故障问题,于是先天主从复制关系,于是增加了运维成本
2、因为对表的拆分,增加了后期维护的难度,同样也是增加了运维成本
3、即便做了大量的维护,但对于大数据的检索操作,依然很慢,完全达不到期望值
于是出现了lucene,全文检索的工具。但是lucene对外暴露出的可用接口对于开发人员来说,操作是非常的复杂,而且没有效率的;于是在lucene的基础上进一步的封装,有了一个叫做solr的高性能分布式检索服务框架,但是,solr有一个致命的缺点就是:在建立索引期间,solr的搜索能力会极度下降,这就在一定程度上造成了solr在实时索引上效率并不高;
最后,出现了一个叫做elasticsearch的框架,同样是以lucene为基础,并且吸收了前两代的教训而开发出的分布式多用户能力的全文搜索引擎,并且elasticsearch是基于RESTful web接口进行发布的,那么这就意味着,我们开发人员操作起来更方便快捷;同时es拓展节点方便,可用于存储和检索海量数据,接近实时搜索能力,自动发现节点、副本机制保障可用性
日志,对于任何系统来说都是及其重要的组成部分。在计算机系统里面,更是如此。但是由于现在的计算机系统大多比较复杂,很多系统都不是在一个地方,甚至都是跨国界的;即使是在一个地方的系统,也有不同的来源,比如,操作系统,应用服务,业务逻辑等等。他们都在不停产生各种各样的日志数据。根据不完全统计,我们全球每天大约要产生 2EB的数据。
面对如此海量的数据,又是分布在各个不同地方,如果我们需要去查找一些重要的信息,难道还是使用传统的方法,去登陆到一台台机器上查看?看来传统的工具和方法已经显得非常笨拙和低效了。于是,一些聪明人就提出了建立一套集中式的方法,把不同来源的数据集中整合到一个地方。
一个完整的集中式日志系统,是离不开以下几个主要特点的。
收集-能够采集多种来源的日志数据
传输-能够稳定的把日志数据传输到中央系统
存储-如何存储日志数据
分析-可以支持 UI 分析
警告-能够提供错误报告,监控机制
ELK 其实并不是一款软件,而是一整套解决方案,是三个软件产品的首字母缩写,Elasticsearch,Logstash 和 Kibana。这三款软件都是开源软件,通常是配合使用,而且又先后归于 Elastic.co 公司名下,故被简称为 ELK 协议栈。
Elasticsearch
Elasticsearch 是一个实时的分布式搜索和分析引擎,它可以用于全文搜索,结构化搜索以及分析。它是一个建立在全文搜索引擎 Apache Lucene 基础上的搜索引擎,使用 Java 语言编写。
主要特点
实时分析
分布式实时文件存储,并将每一个字段都编入索引
文档导向,所有的对象全部是文档
高可用性,易扩展,支持集群(Cluster)、分片和复制(Shards 和 Replicas)。见图 2 和图 3
接口友好,支持 JSON
Logstash
Logstash 是一个具有实时渠道能力的数据收集引擎。使用 JRuby 语言编写。其作者是世界著名的运维工程师乔丹西塞 (JordanSissel)。
主要特点
几乎可以访问任何数据
可以和多种外部应用结合
支持弹性扩展
它由三个主要部分组成
Shipper-发送日志数据
Broker-收集数据,缺省内置 Redis
Indexer-数据写入
Kibana
Kibana 是一款基于 Apache 开源协议,使用 JavaScript 语言编写,为 Elasticsearch 提供分析和可视化的 Web 平台。它可以在 Elasticsearch 的索引中查找,交互数据,并生成各种维度的表图。
ELK官网:https://www.elastic.co/
ELK官网文档:https://www.elastic.co/guide/index.html
ELK中文手册:https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html
ELK中文社区:https://elasticsearch.cn/
ELK API :https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/travelansport-client.html
1.5.1 什么是ElasticSearch
Elaticsearch,简称为es, es是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。es也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。
1.5.2 ElasticSearch使用案例
2013年初,GitHub抛弃了Solr,采取ElasticSearch 来做PB级的搜索。 “GitHub使用ElasticSearch搜索20TB的数据,包括13亿文件和1300亿行代码”
维基百科:启动以elasticsearch为基础的核心搜索架构
SoundCloud:“SoundCloud使用ElasticSearch为1.8亿用户提供即时而精准的音乐搜索服务”
百度:百度目前广泛使用ElasticSearch作为文本数据分析,采集百度所有服务器上的各类指标数据及用户自定义数据,通过对各种数据进行多维分析展示,辅助定位分析实例异常或业务层面异常。目前覆盖百度内部20多个业务线(包括casio、云分析、网盟、预测、文库、直达号、钱包、风控等),单集群最大100台机器,200个ES节点,每天导入30TB+数据
新浪使用ES 分析处理32亿条实时日志
阿里使用ES 构建挖财自己的日志采集和分析体系
1.5.3 ElasticSearch对比Solr
Solr 利用 Zookeeper 进行分布式管理,而 Elasticsearch 自身带有分布式协调管理功能;
Solr 支持更多格式的数据,而 Elasticsearch 仅支持json文件格式;
Solr 官方提供的功能更多,而 Elasticsearch 本身更注重于核心功能,高级功能多有第三方插件提供;
Solr 在传统的搜索应用中表现好于 Elasticsearch,但在处理实时搜索应用时效率明显低于 Elasticsearch
注:以下代码不用敲,粘贴运行,讲解效果即可。
/**
* 查询索引
* @throws IOException
*/
private static void testQuery() throws IOException {
//1.关键词
String keyword ="lucene";
//2.打开索引库
Directory directory = FSDirectory.open(new File("D://lucene_index_article"));
//3.创建查询器
IndexSearcher indexSearcher = new IndexSearcher(DirectoryReader.open(directory));
//4.封装查询条件
TermQuery termQuery = new TermQuery(new Term("title",keyword));
//5.查询
TopDocs topDocs = indexSearcher.search(termQuery, 10);
//6.解析返回值
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
//7.获得一个文档编号
int doc = scoreDoc.doc;
//8.再次查询获得文档内容
Document document = indexSearcher.doc(doc);
//9.打印数据
System.out.println("id"+document.get("id"));
System.out.println("title"+document.get("title"));
System.out.println("content"+document.get("content"));
System.out.println("author"+document.get("author"));
System.out.println("author"+document.get("author"));
}
}
public static void testIndex() throws IOException {
//1.创建文档
Document document = new Document();
document.add(new IntField("id:",1, Field.Store.YES));
document.add(new TextField("title:","elasticSearch 的底层是 lucene", Field.Store.YES));
document.add(new TextField("content:","elasticSearch 的底层是 lucene", Field.Store.YES));
document.add(new StringField("author:","张三丰",Field.Store.YES));
document.add(new StringField("date:","2018-10-13 23:47:11",Field.Store.YES));
//2.打开索引
Directory d = FSDirectory.open(new File("d://"));
//3.创建索引写入器---准备分词器
StandardAnalyzer analyzer = new StandardAnalyzer();
//4.创建索引写入器---设置索引版本及分词器
IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST,analyzer);
//5.创建索引写入器
IndexWriter indexWriter = new IndexWriter(d,conf);
//6.写入文档
indexWriter.addDocument(document);
//7.提交并关闭
indexWriter.commit();
indexWriter.close();
}
1.6.1 概述
Elasticsearch是面向文档(document oriented)的,这意味着它可以存储整个对象或文档(document)。然而它不仅仅是存储,还会索引(index)每个文档的内容使之可以被搜索。在Elasticsearch中,你可以对文档(而非成行成列的数据)进行索引、搜索、排序、过滤。
Elasticsearch比传统关系型数据库如下:
Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices -> Types -> Documents -> Fields
1.6.2 Elasticsearch核心概念
es---->index--->type---->docments--->filed
mysql--->db---->tables---->一条一条的数据--->column
1.6.2.1 索引 index
一个索引就是一个拥有几分相似特征的文档的集合。比如说,你可以有一个客户数据的索引,另一个产品目录的索引,还有一个订单数据的索引。一个索引由一个名字来标识(必须全部是小写字母的),并且当我们要对对应于这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。在一个集群中,可以定义任意多的索引。
1.6.2.2 类型 type
在一个索引中,你可以定义一种或多种类型。一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来定。通常,会为具有一组共同字段的文档定义一个类型。比如说,我们假设你运营一个博客平台并且将你所有的数据存储到一个索引中。在这个索引中,你可以为用户数据定义一个类型,为博客数据定义另一个类型,当然,也可以为评论数据定义另一个类型。
1.6.2.3 字段Field
相当于是数据表的字段,对文档数据根据不同属性进行的分类标识
1.6.2.4 映射 mapping
mapping是处理数据的方式和规则方面做一些限制,如某个字段的数据类型、默认值、分析器、是否被索引等等,这些都是映射里面可以设置的,其它就是处理es里面数据的一些使用规则设置也叫做映射,按着最优规则处理数据对性能提高很大,因此才需要建立映射,并且需要思考如何建立映射才能对性能更好。
1.6.2.5 文档 document
一个文档是一个可被索引的基础信息单元。比如,你可以拥有某一个客户的文档,某一个产品的一个文档,当然,也可以拥有某个订单的一个文档。文档以JSON(Javascript Object Notation)格式来表示,而JSON是一个到处存在的互联网数据交互格式。
在一个index/type里面,你可以存储任意多的文档。注意,尽管一个文档,物理上存在于一个索引之中,文档必须被索引/赋予一个索引的type。
1.6.2.6 接近实时 NRT
Elasticsearch是一个接近实时的搜索平台。这意味着,从索引一个文档直到这个文档能够被搜索到有一个轻微的延迟(通常是1秒以内)
1.6.3 集群 cluster
一个集群就是由一个或多个节点组织在一起,它们共同持有整个的数据,并一起提供索引和搜索功能。一个集群由一个唯一的名字标识,这个名字默认就是“elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群
1.6.4 节点 node
一个节点是集群中的一个服务器,作为集群的一部分,它存储数据,参与集群的索引和搜索功能。和集群类似,一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的时候赋予节点。这个名字对于管理工作来说挺重要的,因为在这个管理过程中,你会去确定网络中的哪些服务器对应于Elasticsearch集群中的哪些节点。
一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫做“elasticsearch”的集群中,这意味着,如果你在你的网络中启动了若干个节点,并假定它们能够相互发现彼此,它们将会自动地形成并加入到一个叫做“elasticsearch”的集群中。
在一个集群里,只要你想,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何Elasticsearch节点,这时启动一个节点,会默认创建并加入一个叫做“elasticsearch”的集群。
1.6.5 分片和复制 shards&replicas
在es中一个index索引有很多个分片,
es中默认一个index索引有5个分片,一个index索引有2个副本,也就是说数据保留2份。
一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。为了解决这个问题,Elasticsearch提供了将索引划分成多份的能力,这些份就叫做分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。分片很重要,主要有两方面的原因: 1)允许你水平分割/扩展你的内容容量。 2)允许你在分片(潜在地,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量。
至于一个分片怎样分布,它的文档怎样聚合回搜索请求,是完全由Elasticsearch管理的,对于作为用户的你来说,这些都是透明的。
在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因消失了,这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的,Elasticsearch允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片,或者直接叫复制。
复制之所以重要,有两个主要原因: 在分片/节点失败的情况下,提供了高可用性。因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的。扩展你的搜索量/吞吐量,因为搜索可以在所有的复制上并行运行。总之,每个索引可以被分成多个分片。一个索引也可以被复制0次(意思是没有复制)或多次。一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。分片和复制的数量可以在索引创建的时候指定。在索引创建之后,你可以在任何时候动态地改变复制的数量,但是你事后不能改变分片的数量。
默认情况下,Elasticsearch中的每个索引被分片5个主分片和1个复制,这意味着,如果你的集群中至少有两个节点,你的索引将会有5个主分片和另外5个复制分片(1个完全拷贝),这样的话每个索引总共就有10个分片。
2.1.1创建用户
创建一个es专门的用户(必须),因为es不能用root用户启动
注意:在企业中,每个业务系统或者集群都需要申请独立的账号
#使用root用户的操作
#添加一个用户,叫做es
useradd es
# 使用root用户创建目录
mkdir -p /export/servers/es
mkdir -p /export/data/es
mkdir -p /export/logs/es
# 将目录的权限分配给es用户
chown -R es /export/servers/es
chown -R es /export/data/es
chown -R es /export/logs/es
# 设置下es用户的密码,这里建议使用es作为密码,也就是账户名和密码一样。
passwd es
# 切换到es用户
su es
2.1.2下载安装包
切换到es用户下,下载安装包
su es
# 一定要cd,这样可以回到home目录
cd
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.0.0.tar.gz
tar -zxvf elasticsearch-6.0.0.tar.gz -C /export/servers/es/
2.1.3 修改配置文件
cd /export/servers/es/elasticsearch-6.0.0/config
rm elasticsearch.yml
vi elasticsearch.yml
-
node01
# 集群名字
cluster.name: myes
# 集群中当前的节点
node.name: node01
# 数据目录
path.data: /export/data/es
# 日志目录
path.logs: /export/logs/es
# 当前主机的ip地址
network.host: 192.168.140.131
http.port: 9200
# 集群上的节点信息
discovery.zen.ping.unicast.hosts: ["node01","node02","node03"]
# linux安装es的一个bug解决的配置
bootstrap.system_call_filter: false
bootstrap.memory_lock: false
http.cors.enabled: true
http.cors.allow-origin: "*"
node02
cluster.name: myes
node.name: node02
path.data: /export/data/es
path.logs: /export/logs/es
network.host: 192.168.140.132
http.port: 9200
discovery.zen.ping.unicast.hosts: ["node01","node02","node03"]
bootstrap.system_call_filter: false
bootstrap.memory_lock: false
# 是否支持跨域
http.cors.enabled: true
# *表示支持所有域名
http.cors.allow-origin: "*"
node03
cluster.name: myes
node.name: node03
path.data: /export/data/es
path.logs: /export/logs/es
network.host: 192.168.140.133
http.port: 9200
discovery.zen.ping.unicast.hosts: ["node01","node02","node03"]
bootstrap.system_call_filter: false
bootstrap.memory_lock: false
# 是否支持跨域
http.cors.enabled: true
# *表示支持所有域名
http.cors.allow-origin: "*"
更多配置文件:https://blog.csdn.net/an74520/article/details/8219814
2.1.4 修改jvm内存大小
vi jvm.options
-
-Xms512m
-Xmx512m
2.2.1 启动
注意啦,同学们!这里是后台启动,要发现错误的话,去logs目录下查看。
nohup /export/servers/es/elasticsearch-6.0.0/bin/elasticsearch >/dev/null 2>&1 &
2.2.2 查看错误信息
tail -100f /export/logs/es/myes.log
核心错误信息
[1]: max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536]
[2]: max number of threads [1024] for user [es] is too low, increase to at least [4096]
[3]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
[4]: system call filters failed to install; check the logs and fix your configuration or disable system call filters at your own risk
2.2.3 解决办法
1)max file descriptors [4096] for elasticsearch process likely too low, increase to at least [65536]
原因:无法创建本地文件问题,用户最大可创建文件数太小
解决方案:切换到root用户,编辑limits.conf配置文件, 添加类似如下内容:
vi /etc/security/limits.conf
添加如下内容: 注意*不要去掉了
* soft nofile 65536
* hard nofile 131072
* soft nproc 2048
* hard nproc 4096
备注:* 代表Linux所有用户名称(比如 hadoop)
需要保存、退出、重新登录才可生效。
2)max number of threads [1024] for user [es] likely too low, increase to at least [4096]
原因:无法创建本地线程问题,用户最大可创建线程数太小
解决方案:切换到root用户,进入limits.d目录下,修改90-nproc.conf 配置文件。
vi /etc/security/limits.d/90-nproc.conf
找到如下内容:
* soft nproc 1024
#修改为
* soft nproc 4096
3)max virtual memory areas vm.max_map_count [65530] likely too low, increase to at least [262144]
原因:最大虚拟内存太小
每次启动机器都手动执行下。
root用户执行命令:
[root@localhost ~]# sysctl -w vm.max_map_count=262144
查看修改结果命令:sysctl -a|grep vm.max_map_count 看是否已经修改
永久性修改策略:
echo "vm.max_map_count=262144" >> /etc/sysctl.conf
4)system call filters failed to install; check the logs and fix your configuration or disable system call filters at your own risk
原因:Centos6不支持SecComp,而ES5.4.1默认bootstravelap.system_call_filter为travelue进行检测,所以导致检测失败,失败后直接导致ES不能启动。
详见 :https://github.com/elastic/elasticsearch/issues/22899
解决方法:在elasticsearch.yml中新增配置
bootstrap.system_call_filter,设为false,注意要在Memory下面:
bootstrap.memory_lock: false
bootstrap.system_call_filter: false
以上问题解决后,es启动成功了,但又遇到了新的问题,本地机器无法访问虚拟机的服务,两个原因:
1)9200被限制为本机访问,需要在es的配置文件elasticsearch.yml中新增配置:
network.bind_host:0.0.0.0
2)关闭虚拟机防火墙
解决了这个两个问题后,本地能够顺利访问虚拟机的ES服务了。
注意,以上虚拟内存的更改,每次重启系统之后都要重新设置
sysctl -w vm.max_map_count=262144
2.2.4 再次启动服务(先杀后启)
ps -ef|grep elasticsearch|grep server |awk '{print $2}' |xargs kill -9
nohup /export/servers/es/elasticsearch-6.0.0/bin/elasticsearch >/dev/null 2>&1 &
2.2.5 访问es
在Google Chrome浏览器中,访问以下地址
http://node01:9200/?pretty
pretty:格式化的,漂亮的。
得到以下内容
{
"name" : "node03",
"cluster_name" : "myes",
"cluster_uuid" : "Ir-WWjS8R0KirRVwcjlLlw",
"version" : {
"number" : "6.0.0",
"build_hash" : "8f0685b",
"build_date" : "2017-11-10T18:41:22.859Z",
"build_snapshot" : false,
"lucene_version" : "7.0.1",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}
2.4.1 安装nodejs
Node.js是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
Node.js是一个Javascript运行环境(runtime environment),发布于2009年5月,由Ryan Dahl开发,实质是对Chrome V8引擎进行了封装。Node.js 不是一个 JavaScript 框架,不同于CakePHP、Django、Rails。Node.js 更不是浏览器端的库,不能与 jQuery、ExtJS 相提并论。Node.js 是一个让 JavaScript 运行在服务端的开发平台,它让 JavaScript 成为与PHP、Python、Perl、Ruby 等服务端语言平起平坐的脚本语言。
安装步骤参考:https://www.cnblogs.com/kevingrace/p/8990169.html
# 使用root用户进行安装
# 下载安装包
wget https://nodejs.org/dist/v8.1.0/node-v8.1.0-linux-x86.tar.gz
# 解压安装包
tar -zvxf node-v8.1.0-linux-x86.tar.gz
# 修改目录
mv node-v8.1.0-linux-x86 /usr/local/node-v8.1.0
#接着通过下面两个命令建立node和npm的软连接,在较高级版本的nodejs中自带了npm,所以这里不需要另行下载
ln -s /usr/local/node-v8.1.0/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm
ln -s /usr/local/node-v8.1.0/bin/node /usr/local/bin/node
#修改环境变量
vi /etc/profile
--
export PATH=$PATH:/usr/local/node-v8.1.0/bin
--
#让配置文件生效
source /etc/profile
node -v
npm -v
====================可能出现的报错====================
报错1:
/lib/ld-linux.so.2: bad ELF interpreter: No such file or directory
因为64位系统中安装了32位程序。
解决方法:
yum remove glibc*
yum install glibc.i686
报错2:
node: error while loading shared libraries: libstdc++.so.6: cannot open shared object file: No such file or directory
解决办法:
[root@node01 src]# yum install libstdc++.so.6
报错3:
错误信息:
Protected multilib versions: libstdc++-4.4.7-23.el6.i686 != libstdc++-4.4.7-16.el6.x86_64
解决办法: yum update libstdc++-4.4.7-16.el6.x86_64
报错4:
node: error while loading shared libraries: libstdc++.so.6: cannot open shared object file: No such file or directory
[root@node01 src]# yum install libstdc++.so.6
==================解决npm install过慢问题==================
npm config set registry https://registry.npm.taobao.org
npm config get registry
2.4.2 下载 head源码及编译
# 使用root用户
# 安装GCC
yum install -y gcc-c++ make
yum install -y git
# 使用es用户进行安装
# 初始化目录
cd /export/servers/es
# 使用git下载 命令无法找到 yum install -y git
git clone https://github.com/mobz/elasticsearch-head.git
# 进入安装目录
cd /export/servers/elasticsearch-head
# intall 才会有 node-modules
npm install
以下进度信息,一定要保证网络通畅!!!一定要保证网络通畅!!!一定要保证网络通畅!!!
2.4.3 Gruntfile.js
在Gruntfile.js中添加一行代码,注意添加逗号。
vi /export/servers/elasticsearch-head/Gruntfile.js
找到以下代码:
添加一行: hostname: '192.168.140.131',
connect: {
server: {
options: {
hostname: '192.168.140.131',
port: 9100,
base: '.',
keepalive: travelue
}
}
}
2.4.4 app.js
在app.js中修改hostname
文件路径:_site/
eg:/export/servers/es/head/_site
修改的地方在前面10行之内。
更改前:http://localhost:9200
更改后:http://192.168.140.131:9200
2.4.5 修改elasticsearch.yml
su es
vi /export/servers/es/elasticsearch-6.0.0/config/elasticsearch.yml
-添加一下代码
# 是否支持跨域
http.cors.enabled: travelue
# *表示支持所有域名
http.cors.allow-origin: "*"
2.4.6 重启es服务
注意:使用es用户启动
ps -ef|grep elasticsearch|grep bootstravelap |awk '{print $2}' |xargs kill -9
nohup /export/servers/es/elasticsearch-6.0.0/bin/elasticsearch >/dev/null 2>&1 &
2.4.7 启动head服务
启动elasticsearch-head插件
注意:使用root用户启动
cd /export/servers/es/elasticsearch-head/node_modules/grunt/bin/
./grunt server
-
Running "connect:server" (connect) task
Waiting forever...
Started connect web server on http://192.168.140.130:9100
2.4.8 访问elasticsearch-head界面
打开Google Chrome访问
http://192.168.140.131:9100/
# 使用root进行操作
mkdir -p /export/servers/kibana
mkdir -p /export/data/kibana
mkdir -p /export/logs/kibana
# 赋权给es
chown -R es /export/servers/
chown -R es /export/data/kibana
chown -R es /export/logs/kibana
2.5.1 下载资源
下载地址:https://www.elastic.co/cn/products/logstash
建议使用提供好的安装包,因为网速很慢。
# 使用es用户进行操作
su es
cd
wget https://artifacts.elastic.co/downloads/kibana/kibana-6.0.0-linux-x86_64.tar.gz
2.5.2 解压文件
# 使用es用户进行操作
tar -zxvf kibana-6.1.1-linux-x86_64.tar.gz -C /export/servers/
cd /export/servers/kibana/
# 将当前文件夹的内容,移动到上一级目录
mv * ../
# 清除目录
cd /export/servers/kibana/
rm -rf kibana-6.0.0-linux-x86_64/
2.5.3 修改配置文件
rm /export/servers/kibana/config/kibana.yml
vi /export/servers/kibana/config/kibana.yml
------------
server.host: "node01"
elasticsearch.url: "http://192.168.140.128:9200"
2.5.4 启动服务
cd
vi start_kibana.sh
nohup /export/servers/kibana/bin/kibana >/dev/null 2>&1 &
chmod +x start_kibana.sh
./start_kibana.sh
ps -ef|grep kibana
----------------
es 2606 1 0 11:38 pts/0 00:00:13 /export/servers/kibana/bin/../node/bin/node --no-warnings /export/servers/kibana/bin/../src/cli
es 2667 2651 0 12:10 pts/0 00:00:00 grep kibana
2.5.5 访问
Server running at http://192.168.140.128:5601
curl是利用URL语法在命令行方式下工作的开源文件传输工具,使用curl可以简单实现常见的get/post请求。简单的认为是可以在命令行下面访问url的一个工具。在centos的默认库里面是有curl工具的,如果没有请yum安装即可。
curl
-X 指定http的请求方法 有HEAD GET POST PUT DELETE
-d 指定要传输的数据
-H 指定http请求头信息
3.1.1 创建索引
curl -XPUT http://token03:9200/blog01/?pretty
3.1.2 插入文档
前面的命令使用 PUT 动词将一个文档添加到 /article(文档类型),并为该文档分配 ID 为1。URL 路径显示为index/doctype/ID(索引/文档类型/ID)。
curl -XPUT http://node01:9200/blog01/article/1?pretty -d '{"id": "1", "title": "What is lucene"}'
问题:Content-Type header [application/x-www-form-urlencoded] is not supported
解决:
curl -XPUT http://node01:9200/blog01/article/1?pretty -d '{"id": "1", "title": "What is lucene"}' -H "Content-Type: application/json"
原因:
此原因时由于ES增加了安全机制, 进行严格的内容类型检查,严格检查内容类型也可以作为防止跨站点请求伪造攻击的一层保护。 官网解释
http.content_type.required
3.1.3 查询文档
curl -XGET "http://node01:9200/blog01/article/1?pretty" -H "Content-Type: application/json"
3.1.4 更新文档
curl -XPUT http://node01:9200/blog01/article/1?pretty -d '{"id": "1", "title": " What is elasticsearch"}' -H "Content-Type: application/json"
3.1.5 搜索文档
curl -XGET "http://node01:9200/blog01/article/1?pretty" -H "Content-Type: application/json"
curl -XGET "http://node01:9200/blog01/article/_search?q=title:'elasticsearch'&pretty" -H "Content-Type: application/json"
3.1.6 删除文档
curl -XDELETE "http://node01:9200/blog01/article/1?pretty"
3.1.7 删除索引
curl -XDELETE "http://node01:9200/blog01?pretty"
3.3.1 Hits
返回结果中最重要的部分是 hits ,它包含 total 字段来表示匹配到的文档总数,并且一个 hits 数组包含所查询结果的前十个文档。
在 hits 数组中每个结果包含文档的 _index 、 _type 、 _id ,加上 _source 字段。这意味着我们可以直接从返回的搜索结果中使用整个文档。这不像其他的搜索引擎,仅仅返回文档的ID,需要你单独去获取文档。
每个结果还有一个 _score ,它衡量了文档与查询的匹配程度。默认情况下,首先返回最相关的文档结果,就是说,返回的文档是按照 _score 降序排列的。在这个例子中,我们没有指定任何查询,故所有的文档具有相同的相关性,因此对所有的结果而言 1 是中性的 _score 。
max_score 值是与查询所匹配文档的 _score 的最大值。
3.3.2 took
took 值告诉我们执行整个搜索请求耗费了多少毫秒
3.3.3 Shard
_shards 部分 告诉我们在查询中参与分片的总数,以及这些分片成功了多少个失败了多少个。正常情况下我们不希望分片失败,但是分片失败是可能发生的。
如果我们遭遇到一种灾难级别的故障,在这个故障中丢失了相同分片的原始数据和副本,那么对这个分片将没有可用副本来对搜索请求作出响应。假若这样,Elasticsearch 将报告这个分片是失败的,但是会继续返回剩余分片的结果。
3.3.4 timeout
timed_out 值告诉我们查询是否超时。默认情况下,搜索请求不会超时。 如果低响应时间比完成结果更重要,你可以指定 timeout 为 10 或者 10ms(10毫秒),或者 1s(1秒):
GET /_search?timeout=10ms
在请求超时之前,Elasticsearch 将会返回已经成功从每个分片获取的结果。
在kibana提供的界面上进行操作。
POST /school/student/_bulk
{ "index": { "_id": 1 }}
{ "name" : "liubei", "age" : 20 , "sex": "boy", "birth": "1996-01-02" , "about": "i like diaocan he girl" }
{ "index": { "_id": 2 }}
{ "name" : "guanyu", "age" : 21 , "sex": "boy", "birth": "1995-01-02" , "about": "i like diaocan" }
{ "index": { "_id": 3 }}
{ "name" : "zhangfei", "age" : 18 , "sex": "boy", "birth": "1998-01-02" , "about": "i like travel" }
{ "index": { "_id": 4 }}
{ "name" : "diaocan", "age" : 20 , "sex": "girl", "birth": "1996-01-02" , "about": "i like travel and sport" }
{ "index": { "_id": 5 }}
{ "name" : "panjinlian", "age" : 25 , "sex": "girl", "birth": "1991-01-02" , "about": "i like travel and wusong" }
{ "index": { "_id": 6 }}
{ "name" : "caocao", "age" : 30 , "sex": "boy", "birth": "1988-01-02" , "about": "i like xiaoqiao" }
{ "index": { "_id": 7 }}
{ "name" : "zhaoyun", "age" : 31 , "sex": "boy", "birth": "1997-01-02" , "about": "i like travel and music" }
{ "index": { "_id": 8 }}
{ "name" : "xiaoqiao", "age" : 18 , "sex": "girl", "birth": "1998-01-02" , "about": "i like caocao" }
{ "index": { "_id": 9 }}
{ "name" : "daqiao", "age" : 20 , "sex": "girl", "birth": "1996-01-02" , "about": "i like travel and history" }
3.4.1、使用match_all做查询
GET /school/student/_search?pretty
{
"query": {
"match_all": {}
}
}
问题:通过match_all匹配后,会把所有的数据检索出来,但是往往真正的业务需求并非要找全部的数据,而是检索出自己想要的;并且对于es集群来说,直接检索全部的数据,很容易造成GC现象。所以,我们要学会如何进行高效的检索数据
3.4.2、通过关键字段进行查询
GET /school/student/_search?pretty
{
"query": {
"match": {"about": "travel"}
}
}
如果此时想查询喜欢旅游的,并且不能是男孩的,怎么办?
【这种方式是错误的,因为一个match下,不能出现多个字段值[match] query doesn't support multiple fields】,需要使用复合查询
3.4.3、bool的复合查询
当出现多个查询语句组合的时候,可以用bool来包含。bool合并聚包含:must,must_not或者should, should表示or的意思
例子:查询非男性中喜欢旅行的人
GET /school/student/_search?pretty
{
"query": {
"bool": {
"must": { "match": {"about": "travel"}},
"must_not": {"match": {"sex": "boy"}}
}
}
}
3.4.4、bool的复合查询中的should
should表示可有可无的(如果should匹配到了就展示,否则就不展示)
例子:
查询喜欢旅行的,如果有男性的则显示,否则不显示
GET /school/student/_search?pretty
{
"query": {
"bool": {
"must": { "match": {"about": "travel"}},
"should": {"match": {"sex": "boy"}}
}
}
}
3.4.5、term匹配
使用term进行精确匹配(比如数字,日期,布尔值或 not_analyzed的字符串(未经分析的文本数据类型))
语法
{ "term": { "age": 20 }}
{ "term": { "date": "2018-04-01" }}
{ "term": { "sex": “boy” }}
{ "term": { "about": "trivel" }}
例子:
查询喜欢旅行的
GET /school/student/_search?pretty
{
"query": {
"bool": {
"must": { "term": {"about": "travel"}},
"should": {"term": {"sex": "boy"}}
}}
}
3.4.6、使用terms匹配多个值
GET /school/student/_search?pretty
{
"query": {
"bool": {
"must": { "terms": {"about": ["travel","history"]}}
}
}
}
term主要是用于精确的过滤比如说:”我爱你”
在match下面匹配可以为包含:我、爱、你、我爱等等的解析器
在term语法下面就精准匹配到:”我爱你”
3.4.7、Range过滤
Range过滤允许我们按照指定的范围查找一些数据:操作范围:gt::大于,gae::大于等于,lt::小于,lte::小于等于
例子:
查找出大于20岁,小于等于25岁的学生
GET /school/student/_search?pretty
{
"query": {
"range": {
"age": {"gt":20,"lte":25}
}
}
}
}
3.4.8、exists和 missing过滤
exists和missing过滤可以找到文档中是否包含某个字段或者是没有某个字段
例子:
查找字段中包含age的文档
GET /school/student/_search?pretty
{
"query": {
"exists": {
"field": "age"
}
}
}
}
3.4.9、bool的多条件过滤
用bool也可以像之前match一样来过滤多行条件:
must :: 多个查询条件的完全匹配,相当于 and 。
must_not :: 多个查询条件的相反匹配,相当于 not 。
should :: 至少有一个查询条件匹配, 相当于 or
例子:
过滤出about字段包含travel并且年龄大于20岁小于30岁的同学
GET /school/student/_search?pretty
{
"query": {
"bool": {
"must": [
{"term": {
"about": {
"value": "travel"
}
}},{"range": {
"age": {
"gte": 20,
"lte": 30
}
}}
]
}
}
}
3.4.10、查询与过滤条件合并
通常复杂的查询语句,我们也要配合过滤语句来实现缓存,用filter语句就可以来实现
例子:
查询出喜欢旅行的,并且年龄是20岁的文档
GET /school/student/_search?pretty
{
"query": {
"bool": {
"must": {"match": {"about": "travel"}},
"filter": [{"term":{"age": 20}}]
}
}
}
作业题:
1)找到原始数据中,是女生的,喜欢旅游的
GET /school/student/_search
{
"query": {
"bool": {
"must": [
{"match": {
"sex": "girl"
}},
{
"term": {
"about": {
"value": "travel"
}
}
}
]
}
}
}
2)找到原始数据中,所有性别是男生的,过滤年龄等于20的
GET /school/student/_search
{
"query": {
"bool": {
"must": [
{ "term": {
"sex": {
"value": "boy"
}
}}
],
"filter": {
"term": {
"age": "20"
}
}
}
}
}
elasticsearch中的文档等价于java中的对象,那么在java对象中有字段(比如string、int、long等),同理在elasticsearch索引中的具体字段也是有类型的。
PUT /document/article/1
{
"title" : "elasticsearchshi是是什么",
"author" : "zhangsan",
"titleScore" : 60
}
这种操作并没有指定字段类型,那么elasticsearch会自动根据数据类型的格式识别字段的类型;查看索引字段类型:GET /document/article/_mapping。可以发现titleScore的类型是long。
然后在插入一条数据:
PUT /document/article/2
{
"title" : "elasticsearchshi是是什么",
"author" : "zhangsan",
"titleScore" : 66.666
}
查询数据:GET /document/article/2
我们会发现es能存入,并没有报错(注意),这其实是一个问题,因为如果后期elaticsearch对接java的时候,我们会写一个类对数据做封装,比如:
class Article{
private String title;
private String author;
private String titleScore //《什么类型合适》?如果使用long类型,那么后面肯定会有数据格式转换的异常 doublelong
}
所以,我们如果能提前知道字段类型,那么最好使用mapping的映射管理,提前指定字段的类型,防止后续的程序问题;
DELETE document
PUT document
{
"mappings": {
"article" : {
"properties":
{
"title" : {"type": "text"} ,
"author" : {"type": "text"} ,
"titleScore" : {"type": "double"}
}
}
}
}
get document/article/_mapping
PUT school
{
"mappings": {
"logs" : {
"properties": {"messages" : {"type": "text"}}
}
}
添加索引:school,文档类型类logs,索引字段为message ,字段的类型为text
GET /school/_mapping/logs
继续添加字段
POST /school/_mapping/logs
{
"properties": {"number" : {"type": "text"}}
}
GET /school/_mapping/logs
语法:
GET /{index}/_mapping/{type}/field/{field}
GET /school/_mapping/logs/field/number
4.4 生产环境的bug
原始:使用es默认生产mapping,使用java api统计数据的,发现数据不准确。
解决:发现程序员A在创建索引的时候,使用的默认mapping,导致精度损失。为了解决这个问题,重新构建一个新的库,这个库设置某个字段为double。设置mapping
PUT document1
{
"mappings": {
"article" : {
"properties":
{
"title" : {"type": "text"} ,
"author" : {"type": "text"} ,
"titleScore" : {"type": "double"}
}
}
}
}
然后是reindex的命令,将原始原始库的内容,拷贝document1
POST _reindex
{
"source": {
"index": "document"
},
"dest": {
"index": "document1"
}
}
所谓的settings就是用来修改索引分片和副本数的;
比如有的重要索引,副本数很少甚至没有副本,那么我们可以通过setting来添加副本数
DELETE document
PUT document
{
"mappings": {
"article" : {
"properties":
{
"title" : {"type": "text"} ,
"author" : {"type": "text"} ,
"titleScore" : {"type": "double"}
}
}
}
}
GET /document/_settings
可以看到当前的副本数是1,那么为了提高容错性,我们可以把副本数改成2:
PUT /document/_settings
{
"number_of_replicas": 2
}
副本可以改,分片不能改
PUT /document/_settings
{
"number_of_shards": 3
}
实际生产,对于文档的操作,偶尔会遇到这种问题:
某一个字段的类型不符合后期的业务了,但是当前的索引已经创建了,我们知道es在字段的mapping建立后就不可再次修改mapping的值。
5.2.1 新建索引库articles1,并添加数据
DELETE articles1
PUT articles1
{
"settings":{
"number_of_shards":3,
"number_of_replicas":1
},
"mappings":{
"article":{
"dynamic":"strict",
"properties":{
"id":{"type": "text", "store": true},
"title":{"type": "text","store": true},
"readCounts":{"type": "integer","store": true},
"times": {"type": "text", "index": false}
}
}
}
}
PUT articles1/article/1
{
"id" : "1",
"title" : "世界1",
"readCounts" : 2 ,
"times" : "2018-05-01"
}
get articles1/article/1
5.2.2 新建索引库articles2
DELETE articles2
PUT articles2
{
"settings":{
"number_of_shards":5,
"number_of_replicas":1
},
"mappings":{
"article":{
"dynamic":"strict",
"properties":{
"id":{"type": "text", "store": true},
"title":{"type": "text","store": true},
"readCounts":{"type": "integer","store": true},
"times": {"type": "date", "index": false}
}
}
}
}
}
GET articles2/article/1
5.2.3 拷贝数据并验证
POST _reindex
{
"source": {
"index": "articles1"
},
"dest": {
"index": "articles2"
}
}
GET articles2/article/1
DELETE us
POST /_bulk
{ "create": { "_index": "us", "_type": "tweet", "_id": "1" }}
{ "email" : "[email protected]", "name" : "John Smith", "username" : "@john" }
{ "create": { "_index": "us", "_type": "tweet", "_id": "2" }}
{ "email" : "[email protected]", "name" : "Mary Jones", "username" : "@mary" }
{ "create": { "_index": "us", "_type": "tweet", "_id": "3" }}
{ "date" : "2014-09-13", "name" : "Mary Jones", "tweet" : "Elasticsearch means full text search has never been so easy", "user_id" : 2 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "4" }}
{ "date" : "2014-09-14", "name" : "John Smith", "tweet" : "@mary it is not just text, it does everything", "user_id" : 1 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "5" }}
{ "date" : "2014-09-15", "name" : "Mary Jones", "tweet" : "However did I manage before Elasticsearch?", "user_id" : 2 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "6" }}
{ "date" : "2014-09-16", "name" : "John Smith", "tweet" : "The Elasticsearch API is really easy to use", "user_id" : 1 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "7" }}
{ "date" : "2014-09-17", "name" : "Mary Jones", "tweet" : "The Query DSL is really powerful and flexible", "user_id" : 2 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "8" }}
{ "date" : "2014-09-18", "name" : "John Smith", "user_id" : 1 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "9" }}
{ "date" : "2014-09-19", "name" : "Mary Jones", "tweet" : "Geo-location aggregations are really cool", "user_id" : 2 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "10" }}
{ "date" : "2014-09-20", "name" : "John Smith", "tweet" : "Elasticsearch surely is one of the hottest new NoSQL products", "user_id" : 1 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "11" }}
{ "date" : "2014-09-21", "name" : "Mary Jones", "tweet" : "Elasticsearch is built for the cloud, easy to scale", "user_id" : 2 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "12" }}
{ "date" : "2014-09-22", "name" : "John Smith", "tweet" : "Elasticsearch and I have left the honeymoon stage, and I still love her.", "user_id" : 1 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "13" }}
{ "date" : "2014-09-23", "name" : "Mary Jones", "tweet" : "So yes, I am an Elasticsearch fanboy", "user_id" : 2 }
{ "create": { "_index": "us", "_type": "tweet", "_id": "14" }}
{ "date" : "2014-09-24", "name" : "John Smith", "tweet" : "How many more cheesy tweets do I have to write?", "user_id" : 1 }
按照一般的查询流程来说,如果我想查询前10条数据:
1 客户端请求发给某个节点
2 节点转发给个个分片,查询每个分片上的前10条
3 结果返回给节点,整合数据,提取前10条
4 返回给请求客户端
from定义了目标数据的偏移值,size定义当前返回的事件数目
GET /us/_search?pretty
{
"from" : 0 , "size" : 5
}
GET /us/_search?pretty
{
"from" : 5 , "size" : 5
}
这种浅分页只适合少量数据,因为随from增大,查询的时间就会越大,而且数据量越大,查询的效率指数下降
优点:from+size在数据量不大的情况下,效率比较高
缺点:在数据量非常大的情况下,from+size分页会把全部记录加载到内存中,这样做不但运行速度特别慢,而且容易让es出现内存不足而挂掉
对于上面介绍的浅分页,当Elasticsearch响应请求时,它必须确定docs的顺序,排列响应结果。
如果请求的页数较少(假设每页20个docs), Elasticsearch不会有什么问题,但是如果页数较大时,比如请求第20页,Elasticsearch不得不取出第1页到第20页的所有docs,再去除第1页到第19页的docs,得到第20页的docs。
解决的方式就是使用scroll,scroll就是维护了当前索引段的一份快照信息--缓存(这个快照信息是你执行这个scroll查询时的快照)。
可以把 scroll 分为初始化和遍历两步: 1、初始化时将所有符合搜索条件的搜索结果缓存起来,可以想象成快照; 2、遍历时,从这个快照里取数据;
初始化
GET us/_search?scroll=3m
{
"query": {"match_all": {}},
"size": 3
}
初始化的时候就像是普通的search一样
其中的scroll=3m代表当前查询的数据缓存3分钟
Size:3 代表当前查询3条数据
遍历
在遍历时候,拿到上一次遍历中的_scroll_id,然后带scroll参数,重复上一次的遍历步骤,知道返回的数据为空,就表示遍历完成
GET /_search/scroll
{
"scroll" : "1m",
"scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAPXFk0xN1BmSnlVUldhYThEdWVzZ19xbkEAAAAAAAAAIxZuQWVJU0VSZ1JzcVZtMGVYZ3RDaFlBAAAAAAAAA9oWTVZOdHJ2cXBSOU9wN3c1dk5vcWd4QQAAAAAAAAPYFk0xN1BmSnlVUldhYThEdWVzZ19xbkEAAAAAAAAAIhZuQWVJU0VSZ1JzcVZtMGVYZ3RDaFlB"
}
【注意】:每次都要传参数scroll,刷新搜索结果的缓存时间,另外不需要指定index和type(不要把缓存的时时间设置太长,占用内存)
对比
浅分页,每次查询都会去索引库(本地文件夹)中查询pageNum*page条数据,然后截取掉前面的数据,留下最后的数据。 这样的操作在每个分片上都会执行,最后会将多个分片的数据合并到一起,再次排序,截取需要的。
深分页,可以一次性将所有满足查询条件的数据,都放到内存中。分页的时候,在内存中查询。相对浅分页,就可以避免多次读取磁盘。
每台机器都要配置。配置完成之后,需要重启ES服务
wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v5.5.2/elasticsearch-analysis-ik-6.0.0.zip
# 将ik分词器的插件,上传到/export/servers/es
# cd /export/servers/es
# rz
unzip elasticsearch-analysis-ik-6.0.0.zip -d elasticsearch-6.0.0/plugins/
cd elasticsearch-6.0.0/plugins/
mv elasticsearch analysis-ik
# 三台机器都配置完成
# 配置完成之后,需要重启服务。
delete iktest
PUT /iktest?pretty
{
"settings" : {
"analysis" : {
"analyzer" : {
"ik" : {
"tokenizer" : "ik_max_word"
}
}
}
},
"mappings" : {
"article" : {
"dynamic" : true,
"properties" : {
"subject" : {
"type" : "text",
"analyzer" : "ik_max_word"
}
}
}
}
}
说明:ik带有两个分词器:
ik_max_word :会将文本做最细粒度的拆分;尽可能多的拆分出词语
句子:我爱我的祖国
结果: 我|爱|我|的|祖|国|祖国
ik_smart:会做最粗粒度的拆分;已被分出的词语将不会再次被其它词语占有
句子:我爱我的祖国
结果: 我|爱|我|的|祖国
7.3.1 ik_max_word
GET _analyze?pretty
{
"analyzer": "ik_max_word",
"text": "二十四口交换机"
}
7.3.2 ik_smart
GET _analyze?pretty
{
"analyzer": "ik_smart",
"text": "二十四口交换机"
}
POST /iktest/article/_bulk?pretty
{ "index" : { "_id" : "1" } }
{"subject" : ""闺蜜"崔顺实被韩检方传唤 韩总统府促彻查真相" }
{ "index" : { "_id" : "2" } }
{"subject" : "韩举行"护国训练" 青瓦台:决不许国家安全出问题" }
{ "index" : { "_id" : "3" } }
{"subject" : "媒体称FBI已经取得搜查令 检视希拉里电邮" }
{ "index" : { "_id" : "4" } }
{"subject" : "村上春树获安徒生奖 演讲中谈及欧洲排外问题" }
{ "index" : { "_id" : "5" } }
{"subject" : "希拉里团队炮轰FBI 参院民主党领袖批其”违法”" }
查看分词器
对"希拉里和韩国"进行分词查询
ik_max_word分词后的效果:希|拉|里|希拉里|和|韩国
POST /iktest/article/_search?pretty
{
"query" : { "match" : { "subject" : "希拉里和韩国" }},
"highlight" : {
"pre_tags" : [""],
"post_tags" : [""],
"fields" : {
"subject" : {}
}
}
}
7.5.1 配置Tomcat
#下载tomcat
wget http://124.205.69.169/files/60480000070D3D35/mirrors.hust.edu.cn/apache/tomcat/tomcat-8/v8.5.34/bin/apache-tomcat-8.5.34.tar.gz
# 解压目录
mv apache-tomcat-8.5.34 /export/servers/tomcat4es/
cd /export/servers/tomcat4es/webapps/ROOT/
# 新建文件
vi hot.dic
--
传智播客
--
# 启动tomcat
/export/servers/tomcat4es/bin/startup.sh
# 检查是否可以访问 wget http://node03:8080/hot.dic
7.5.2 修改配置文件
cd /export/servers/es/elasticsearch-6.0.0/plugins/analysis-ik/config
vi IKAnalyzer.cfg.xml
---
http://192.168.239.102:8080/hot.dic
7.5.3 修改JDK权限
#修改JDK安全设置
vi /export/servers/jdk/jre/lib/security/java.policy
permission java.net.SocketPermission "192.168.140.133:8080","accept";
permission java.net.SocketPermission "192.168.140.133:8080","listen";
permission java.net.SocketPermission "192.168.140.133:8080","resolve";
permission java.net.SocketPermission "192.168.140.133:8080","connect";
http://mahilion.blog.163.com/blog/static/1830872952012101225243655/
7.5.4 前台启动ES并观察
首先:发送一个索引或者删除的请求给node1
其次:node1介绍到请求之后,会根据请求中携带的参数“文档id”判断出该文档应该存储在具体哪一个shard中
shard = hash(routing) % number_of_primary_shards
,比如shard0;其次就是node1通过元数据信息可以知道shard0在具体哪一个节点,于是node1会把请求转发给node3
最后:node3接收到请求之后会将请求并行的分发给shard0的所有replica shard之上,也就是存在于node 1和node 2中的replica shard;如果所有的replica shard都成功地执行了请求,那么将会向node 3回复一个成功确认,当node 3收到了所有replica shard的确认信息后,则最后向用户返回一个Success的消息。
该过程可以分为四个阶段来描述:
阶段1:客户端向node 1发送一个文档删除的请求。
阶段2:同样的node 1通过请求中文档的 _id 值判断出该文档应该被存储在shard 0 这个分片中,并且node 1知道shard 0的primary shard位于node 3这个节点上。因此node 1会把这个请求转发到node 3。
阶段3:node 3接收到请求后,在主分片上面执行删除请求
阶段4:如果node 3成功地删除了文档,node 3将会请求并行地发给其余所有的replica shard所在node中。这些node也同样操作删除,执行后则向node 3确认成功,当node 3接收到所有的成功确认之后,再向客户端发送一个删除成功的信息。
检索文档的时候,我们并不知道文档在集群中的哪个位置,所以一般情况下不得不去询问index中的每一个shard,然后将结果拼接成一个大的已排好序的汇总结果列表;
(1):客户端发送一个检索请求给node3,此时node3会创建一个空的优先级队列并且配置好分页参数from与size。
(2):node3将检索请求发送给index中的每一个shard(primary 和 replica),每一个在本地执行检索,并将结果添加到本地的优先级队列中;
(3):每个shard返回本地优先级序列中所记录的_id与**score值**,并发送node3。Node3将这些值合并到自己的本地的优先级队列中,并做全局的排序(node 3将它们合并成一条汇总的结果),返回给客户端。
org.elasticsearch.client
transport
6.0.0
org.apache.logging.log4j
log4j-core
2.9.1
junit
junit
4.12
9.4.2.1 创建Client
private TransportClient client;
@Before
public void initClient() throws UnknownHostException {
client = new PreBuiltTransportClient(Settings.builder().put("cluster.name", "myes").build())
.addTransportAddress(new TransportAddress(InetAddress.getByName("node01"), 9300))
.addTransportAddress(new TransportAddress(InetAddress.getByName("node02"), 9300))
.addTransportAddress(new TransportAddress(InetAddress.getByName("node03"), 9300));
}
9.4.2.2 打印并关闭Clinet
private IndexResponse indexResponse;
@After
public void printResultAndCloseClient() {
System.out.println("index:" + indexResponse.getIndex());
System.out.println("type:" + indexResponse.getType());
System.out.println("id:" + indexResponse.getId());
System.out.println("version:" + indexResponse.getVersion());
System.out.println("status:" + indexResponse.getResult());
client.close();
}
9.4.2.3 自己拼装json
@Test
public void index1() throws Exception {
String json = "{" +
"\"user\":\"kimchy\"," +
"\"postDate\":\"2013-01-30\"," +
"\"message\":\"travelying out Elasticsearch\"" +
"}";
indexResponse = client.prepareIndex("news", "article", "1")
.setSource(json, XContentType.JSON)
.get();
}
9.4.2.4 使用map
@Test
public void index2() throws Exception {
HashMap jsonMap = new HashMap();
jsonMap.put("name", "zhangsan");
jsonMap.put("sex", "1");
jsonMap.put("age", "18");
jsonMap.put("address", "bj");
indexResponse = client.prepareIndex("news", "article", "2")
.setSource(jsonMap)
.get();
}
9.4.2.5 XcontentBuilder
@Test
public void index3() throws IOException {
indexResponse = client.prepareIndex("news", "article", "3")
.setSource(new XContentFactory().jsonBuilder()
.startObject()
.field("name", "lisi")
.field("age", "18")
.field("sex", "0")
.field("address", "bj")
.endObject())
.get();
}
9.4.2.6 批量创建
/**
* 批量查询,可以提高创建索引的速度,主要减少网络请求。
* 如果正常情况,创建一个文档就会发送一次网络请求,其实就是发起一次http请求。
* bulkIndex就可以将多个文档合并在一起之后,发送一次请求。
* @throws IOException
*/
@Test
public void index4() throws IOException {
BulkRequestBuilder bulk = client.prepareBulk();
bulk.add(client.prepareIndex("news", "article", "4")
.setSource(new XContentFactory().jsonBuilder()
.startObject()
.field("name", "wangwu")
.field("age", "18")
.field("sex", "0")
.field("address", "bj")
.endObject()));
bulk.add(client.prepareIndex("news", "article", "5")
.setSource(new XContentFactory().jsonBuilder()
.startObject()
.field("name", "zhaoliu")
.field("age", "18")
.field("sex", "0")
.field("address", "bj")
.endObject()));
BulkResponse bulkResponse = bulk.get();
System.out.println(bulkResponse);
}
9.4.2.7 Gson
com.google.code.gson
gson
2.8.2
@Test
public void index5(){
Gson gson = new Gson();
User user = new User("6", "itcast", "18", "1");
String json = gson.toJson(user);
System.out.println(json);
indexResponse = client.prepareIndex("news","article",user.getId())
.setSource(json,XContentType.JSON)
.get();
}
4.3.1 prepareGet
@Test
public void query1(){
GetResponse response = client.prepareGet().setIndex("news").setType("article").setId("1").get();
String index= response.getIndex();
String type= response.getType();
String id= response.getId();
System.out.println(response.getSourceAsString());
System.out.println(index);
System.out.println(type);
System.out.println(id);
client.close();
}
4.3.2 prepareMultiGet
减少网络请求
@Test
public void query2(){
MultiGetResponse responses = client.prepareMultiGet().add("news", "article", "1")
.add("news", "article", "2")
.add("news", "article", "3")
.add("news", "article", "4")
.get();
ArrayList users = new ArrayList();
for (MultiGetItemResponse response : responses) {
String json = response.getResponse().getSourceAsString();
Gson gson = new Gson();
User user = gson.fromJson(json, User.class);
users.add(user);
}
System.out.println(users);
}
4.3.3 search
4.3.3.1 分页前的配置文件
由于分页前需要排序,所以需要配置id字段的fielddata属性等于true。
DELETE blog2
PUT /blog2/?pretty
PUT blog2/_mapping/article
{
"properties": {
"id": {
"type": "text",
"fielddata": true
},
"title": {
"type": "text"
},
"content": {
"type": "text"
}
}
}
4.3.3.2 分页的java代码
@Test
public void query3(){
SearchResponse response = client.prepareSearch("blog2").setTypes("article")
//所用的查询方式都可以直接new出来,
.setQuery(new MatchAllQueryBuilder())
.addSort("id", SortOrder.DESC)
//浅分页
.setFrom(5).setSize(5)
.get();
SearchHits hits = response.getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
// //docid没有值,旧版本有值
// int docId = hit.docId();
// System.out.println(docId);
}
}
4.3.3.3 分页的数据准备
@Test
public void initPageData() throws UnknownHostException {
// 创建Client连接对象
Settings settings = Settings.builder().put("cluster.name", "myes").build();
TransportClient client = new PreBuiltTransportClient(settings)
.addTransportAddress(new TransportAddress(InetAddress.getByName("node01"), 9300));
for (int i = 1; i <= 100; i++) {
// 描述json 数据
Article article = new Article();
article.setId(i+"");
article.setTitle(i + "搜索工作其实很快乐");
article.setContent(i
+ "我们希望我们的搜索解决方案要快,我们希望有一个零配置和一个完全免费的搜索模式,我们希望能够简单地使用JSON通过HTTP的索引数据,我们希望我们的搜索服务器始终可用,我们希望能够一台开始并扩展到数百,我们要实时搜索,我们要简单的多租户,我们希望建立一个云的解决方案。Elasticsearch旨在解决所有这些问题和更多的问题。");
Gson gson = new Gson();
String json = gson.toJson(article);
// 建立文档
client.prepareIndex("blog22222", "article", article.getId())
.setSource(json,XContentType.JSON)
.get();
}
//释放资源
client.close();
}
4.3.3.4 分页配置IK分词器
PUT /blog22222?pretty
{
"settings" : {
"analysis" : {
"analyzer" : {
"ik" : {
"tokenizer" : "ik_max_word"
}
}
}
},
"mappings" : {
"article" : {
"dynamic" : true,
"properties" : {
"id" : {
"type" : "text",
"fielddata": true
},
"title" : {
"type" : "text",
"analyzer" : "ik_max_word"
},
"content" : {
"type" : "text",
"analyzer" : "ik_max_word"
}
}
}
}
}
package demo.es.api;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryAction;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class DeleteIndex {
private TransportClient client;
private DeleteResponse response;
@Before
public void init() throws UnknownHostException {
Settings settings = Settings.builder().put("cluster.name", "myes").build();
client = new PreBuiltTransportClient(settings)
.addTransportAddress(new TransportAddress(InetAddress.getByName("node01"), 9300));
}
@After
public void close() throws UnknownHostException {
if(response!=null) {
String index = response.getIndex();
String type = response.getType();
String id = response.getId();
long version = response.getVersion();
System.out.println("index " + index + " type" + type + "" + id + "" + version + "" + version);
RestStatus status = response.status();
System.out.println("status:" + status.getStatus());
client.close();
}
}
/**
* 根据文档进行删除
* @throws UnknownHostException
*/
@Test
public void delete1() throws UnknownHostException {
response = client.prepareDelete("twitter", "doc", "100").get();
}
@Test
/**
* 根据根据查询结果删除数据,并触发相关事件
*/
public void delete2() throws UnknownHostException {
DeleteByQueryAction.INSTANCE.newRequestBuilder(client)
.filter(QueryBuilders.matchQuery("gender", "male"))
.source("twitter")
.execute(new ActionListener() {
public void onResponse(BulkByScrollResponse response) {
long deleted = response.getDeleted();
System.out.println("---------------"+deleted);
}
public void onFailure(Exception e) {
System.out.println("------------错误了");
}
});
}
}
4.5.1 什么是高亮显示
在进行关键字搜索时,搜索出的内容中的关键字会显示不同的颜色,称之为高亮
京东商城搜索"笔记本"
4.5.2 高亮显示的html分析
通过开发者工具查看高亮数据的html代码实现:
ElasticSearch可以对查询出的内容中关键字部分进行标签和样式的设置,但是你需要告诉ElasticSearch使用什么标签对高亮关键字进行包裹
4.5.3 高亮显示代码实现
@Test
//高亮查询
public void test11() throws Exception{
// 创建Client连接对象
Settings settings = Settings.builder().put("cluster.name", "my-elasticsearch").build();
TransportClient client = new PreBuiltTransportClient(settings)
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300));
// 搜索数据
SearchRequestBuilder searchRequestBuilder = client
.prepareSearch("blog2").setTypes("article")
.setQuery(QueryBuilders.termQuery("title", "搜索"));
//设置高亮数据
HighlightBuilder hiBuilder=new HighlightBuilder();
hiBuilder.preTags("");
hiBuilder.postTags("");
hiBuilder.field("title");
searchRequestBuilder.highlighter(hiBuilder);
//获得查询结果数据
SearchResponse searchResponse = searchRequestBuilder.get();
//获取查询结果集
SearchHits searchHits = searchResponse.getHits();
System.out.println("共搜到:"+searchHits.getTotalHits()+"条结果!");
//遍历结果
for(SearchHit hit:searchHits){
System.out.println("String方式打印文档搜索内容:");
System.out.println(hit.getSourceAsString());
System.out.println("Map方式打印高亮内容");
System.out.println(hit.getHighlightFields());
System.out.println("遍历高亮集合,打印高亮片段:");
Text[] text = hit.getHighlightFields().get("title").getFragments();
for (Text str : text) {
System.out.println(str);
}
}
//释放资源
client.close();
}
logstash就是一个具备实时数据传输能力的管道,负责将数据信息从管道的输入端传输到管道的输出端;与此同时这根管道还可以让你根据自己的需求在中间加上滤网,Logstash提供里很多功能强大的滤网以满足你的各种应用场景。是一个input | filter | output 的数据流。
拎包就住,开箱即用
# 使用root创建logstash的安装目录
mkdir /export/servers/logstash
# 对目录进行赋权限
chown -R es:es /export/servers/logstash/
# 切换成es用户
su es
# cd到es的home目录
cd
# 下载安装包---学习过程中可以使用老师提供的包
wget https://artifacts.elastic.co/downloads/logstash/logstash-6.0.0.tar.gz
# 解压
tar -zxvf logstash-6.0.0.tar.gz
mv logstash-6.0.0/* /export/servers/logstash/
cd /export/servers/logstash/
1.3.1 stdin标准输入和stdout标准输出
一个基础的输入输出插件:
bin/logstash -e 'input{stdin{}}output{stdout{codec=>rubydebug}}'
{
"@version" => "1",
"host" => "node01",
"@timestamp" => 2018-10-13T08:33:13.126Z,
"message" => "hello"
}
1.3.2 监控日志文件变化
Logstash 使用一个名叫 FileWatch 的 Ruby Gem 库来监听文件变化。这个库支持 glob 展开文件路径,而且会记录一个叫 .sincedb 的数据库文件来跟踪被监听的日志文件的当前读取位置。所以,不要担心 logstash 会漏过你的数据。
编写脚本
mkdir /export/servers/logstash/myconfig
vi /export/servers/logstash/myconfig/monitor_file.conf
#输入一下信息
input{
file{
path => "/export/servers/logstash/myconfig/data/xxx.log"
type => "log"
start_position => "beginning"
}
}
output{
stdout{
codec=>rubydebug
}
}
启动之后,logstash就会监控 mkdir /export/servers/logstash/myconfig/data/xxx.log文件,如果发现文件中有内容,则以标准输出的形式输出到控制台;
检查配置文件是否可用
bin/logstash -f /export/servers/logstash/myconfig/monitor_file.conf -t
成功会出现一下信息:
Config Validation Result: OK. Exiting Logstash
启动服务
bin/logstash -f /export/servers/logstash/myconfig/monitor_file.conf
发送数据
mkdir /export/servers/logstash/myconfig/data
echo "hello logstash" >> /export/servers/logstash/myconfig/data/logs.txt
其它参数说明
Path=>表示监控的文件路径
Type=>给类型打标记,用来区分不同的文件类型。
Start_postion=>从哪里开始记录文件,默认是从结尾开始标记,要是你从头导入一个文件就把改成”beginning”.
discover_interval=>多久去监听path下是否有文件,默认是15s
exclude=>排除什么文件
close_older=>一个已经监听中的文件,如果超过这个值的时间内没有更新内容,就关闭监听它的文件句柄。默认是3600秒,即一个小时。
sincedb_path=>监控库存放位置(默认的读取文件信息记录在哪个文件中)。默认在:/data/plugins/inputs/file。
sincedb_write_interval=> logstash 每隔多久写一次 sincedb 文件,默认是 15 秒。
stat_interval=>logstash 每隔多久检查一次被监听文件状态(是否有更新),默认是 1 秒。
1.3.3 tcp插件
TCP是一种网络传输控制协议,很多公司的数据不一定是在本地的,而是在传输网络的;这个时候使用TCP建立连接后,通信双方就可以进行数据传输了;
Logstash提供了TCP插件,TCP插件可以监控某个端口,当数据打入logstash监听的端口队列的时候,logstash就可以进行数据的采集;
编写脚本
input{
tcp {
port => 9876
mode => "server" #值是["server","client"]其中之一,默认是server
ssl_enable => false
}
}
output{
stdout{}
}
检查配置文件是否可用
bin/logstash -f /export/servers/logstash/myconfig/monitor_tcp.conf -t
通过之后
Config Validation Result: OK. Exiting Logstash
启动服务
bin/logstash -f /export/servers/logstash/myconfig/monitor_tcp.conf
发送数据
public static void main(String[] args) throws Exception{
// 向服务器端发送请求,服务器IP地址和服务器监听的端口号
Socket client = new Socket("hadoop01", 9876);
// 通过printWriter 来向服务器发送消息
PrintWriter printWriter = new PrintWriter(client.getOutputStream());
System.out.println("连接已建立...");
for(int i=0;i<10;i++){
// 发送消息
printWriter.println("hello logstash , 这是第"+i+" 条消息");
printWriter.flush();
}
}
其它参数说明
add_field=> | 可选项(添加自定义字段) |
---|---|
codec=> | 可选项 (编码解码) |
data_timeout=> | 可选项 (超时时间,秒为单位。如果设置-1,则永不超时,默认是5) |
host=> | 可选项 (主机地址,字符串类型,如"localhost"或者"192.168.0.1 ) |
mode=> | 可选项 (值是["server","client"]其中之一,默认是server) |
port=> | 必填项(远程监听的端口) |
ssl_cacert=> | 可选项,ssl认证相关 |
ssl_cert=> | 可选项,ssl认证相关 |
ssk_key=> | 可选项,ssl认证相关 |
ssl_enable=> | 是否开启ssl认证 |
tags=> | 可选项 用于增加一些标签,这个标签可能在后续的处理中起到标志的作用 |
type=> | 可选项 标记事件类型,通过type判断 |
1.3.4 systlog插件
syslog机制负责记录内核和应用程序产生的日志信息,管理员可以通过查看日志记录,来掌握系统状况
默认系统已经安装了rsyslog.直接启动即可
编写脚本
input{
tcp{
port=> 6789
type=> syslog
}
udp{
port=> 6789
type=> syslog
}
}
filter{
if [type] == "syslog" {
grok {
match => { "message" => "%{SYSLOGTIMESTAMP:syslog_timestamp} %{SYSLOGHOST:syslog_hostname} %{DATA:syslog_program}(?:\[%{POSINT:syslog_pid}\])?: %{GREEDYDATA:syslog_message}" }
add_field => [ "received_at", "%{@timestamp}" ]
add_field => [ "received_from", "%{host}" ]
}
date {
match => [ "syslog_timestamp", "MMM d HH:mm:ss", "MMM dd HH:mm:ss" ]
}
}
}
output{
stdout{
codec=> rubydebug
}
}
检查配置文件是否可用
bin/logstash -f /export/servers/logstash/myconfig/monitor_syslog.conf -t
启动服务
bin/logstash -f /export/servers/logstash/myconfig/monitor_syslog.conf
发送数据
# 使用root账户进行修改
vi /etc/rsyslog.conf
---
*.* @@node01:6789
# 重启
/etc/init.d/rsyslog restart
其它参数说明
在logstash中的grok是正则表达式,用来解析当前数据
原始数据其实是:
Dec 23 12:11:43 louis postfix/smtpd[31499]: connect from unknown[95.75.93.154]
Jun 05 08:00:00 louis named[16000]: client 199.48.164.7#64817: query (cache) 'amsterdamboothuren.com/MX/IN' denied
Jun 05 08:10:00 louis CRON[620]: (www-data) CMD (php /usr/share/cacti/site/poller.php >/dev/null 2>/var/log/cacti/poller-error.log)
Jun 05 08:05:06 louis rsyslogd: [origin software="rsyslogd" swVersion="4.2.0" x-pid="2253" x-info="http://www.rsyslog.com"] rsyslogd was HUPed, type 'lightweight'.
1.4.1 标准输出到控制台
output {
stdout {
codec => rubydebug
}
}
bin/logstash -e 'input{stdin{}}output{stdout{codec=>rubydebug}}'
[es@node01 logstash]$ bin/logstash -e 'input{stdin{}}output{stdout{codec=>rubydebug}}'
hello
Sending Logstash's logs to /export/servers/logstash/logs which is now configured via log4j2.properties
[2018-10-13T17:54:07,030][INFO ][logstash.modules.scaffold] Initializing module {:module_name=>"netflow", :directory=>"/export/servers/logstash/modules/netflow/configuration"}
[2018-10-13T17:54:07,034][INFO ][logstash.modules.scaffold] Initializing module {:module_name=>"fb_apache", :directory=>"/export/servers/logstash/modules/fb_apache/configuration"}
[2018-10-13T17:54:07,364][WARN ][logstash.config.source.multilocal] Ignoring the 'pipelines.yml' file because modules or command line options are specified
[2018-10-13T17:54:07,648][INFO ][logstash.agent ] Successfully started Logstash API endpoint {:port=>9600}
[2018-10-13T17:54:08,729][INFO ][logstash.pipeline ] Starting pipeline {:pipeline_id=>"main", "pipeline.workers"=>2, "pipeline.batch.size"=>125, "pipeline.batch.delay"=>5, "pipeline.max_inflight"=>250, :thread=>"#"}
[2018-10-13T17:54:08,760][INFO ][logstash.pipeline ] Pipeline started {"pipeline.id"=>"main"}
The stdin plugin is now waiting for input:
[2018-10-13T17:54:08,802][INFO ][logstash.agent ] Pipelines running {:count=>1, :pipelines=>["main"]}
{
"@version" => "1",
"host" => "node01",
"@timestamp" => 2018-10-13T09:54:08.808Z,
"message" => "hello"
}
1.4.2 将采集数据保存到file文件中
input {stdin{}}
output {
file {
path => "/export/servers/logstash/myconfig/data/%{+YYYY-MM-dd}-%{host}.txt"
codec => line {
format => "%{message}"
}
gzip => true
}
}
bin/logstash -f /export/servers/logstash/myconfig/2file.config -t
bin/logstash -f /export/servers/logstash/myconfig/2file.config
1.4.3 将采集数据保存到elasticsearch
input {stdin{}}
output {
elasticsearch {
hosts => ["node01:9200"]
index => "logstash-%{+YYYY.MM.dd}" #这个index是保存到elasticsearch上的索引名称,如何命名特别重要,因为我们很可能后续根据某些需求做查询,所以最好带时间,因为我们在中间加上type,就代表不同的业务,这样我们在查询当天数据的时候,就可以根据类型+时间做范围查询
}
}
bin/logstash -f /export/servers/logstash/myconfig/2es.config -t
bin/logstash -f /export/servers/logstash/myconfig/2es.config
1.4.4 将采集数据保存到redis
input { stdin {} }
output {
redis {
host => "node01"
data_type => "list"
port => "6379"
key => "logstash-redis-%{+yyyy.MM.dd}"
}
}
bin/logstash -f /export/servers/logstash/myconfig/2redis.config -t
bin/logstash -f /export/servers/logstash/myconfig/2redis.config -t
[es@node01 logstash]$ ps -ef|grep redis
root 7555 1 3 18:40 ? 00:00:04 ./redis-server 192.168.140.131:6379
es 7603 4476 0 18:42 pts/0 00:00:00 grep redis
192.168.140.131:6379> keys *
1) "logstash-redis-2018.10.13"
192.168.140.131:6379> LRANGE "logstash-redis-2018.10.13" 0 -1
1) "{\"@version\":\"1\",\"host\":\"node01\",\"@timestamp\":\"2018-10-13T10:47:47.207Z\",\"message\":\"redis\"}"
2) "{\"@version\":\"1\",\"host\":\"node01\",\"@timestamp\":\"2018-10-13T10:47:48.655Z\",\"message\":\"ele\"}"
需求:解决海量数据的存储,并且能够实现海量数据的秒级查询.
实际生产中,一遍文章要分成标题和正文;但是正文的量是比较大的,那么我们一般会在es中存储标题,在hbase中存储正文(hbase本身就是做海量数据的存储);这样通过es的倒排索引列表检索到关键词的文档id,然后根据文档id在hbase中查询出具体的正文。
分析,数据哪些字段需要构建索引:
文章数据(id、title、author、describe、conent)
字段名称 | 是否需要索引 | 是否需要存储 |
---|---|---|
Id | 默认索引 | 默认存储 |
Title | 需要 | 需要 |
Author | 看需求 | 看需求 |
Dscribe | 需要 | 存储 |
Content | 看需求(高精度查询,是需要的 ) | 看需求 |
Time | 需要 | 需要 |
PUT /articles
{
"settings":{
"number_of_shards":3,
"number_of_replicas":1,
"analysis" : {
"analyzer" : {
"ik" : {
"tokenizer" : "ik_max_word"
}
}
}
},
"mappings":{
"article":{
"dynamic":"strict",
"_source": {
"includes": [
"id","title","from","readCounts","times"
],
"excludes": [
"content"
]
},
"properties":{
"id":{"type": "keyword", "store": true},
"title":{"type": "text","store": true,"index" : true,"analyzer": "ik_max_word"},
"from":{"type": "keyword","store": true},
"readCounts":{"type": "integer","store": true},
"content":{"type": "text","store": false,"index": false},
"times": {"type": "keyword", "index": false}
}
}
}
}
1.4.1 整体思路
/**
* 1)读取excel的数据
* 2)将读取的数据保存到elasticsearch和hbase
* * 期望elasticsearch 存放一些索引数据
* * 期望 hbase存放原始的大文本信息
* 3)查询操作
* * 分页查询和高亮展示
*/
1.4.2 读取excel的数据
1.4.2.1 导入依赖
org.apache.poi
poi-ooxml-schemas
3.8
org.apache.poi
poi-ooxml
3.8
org.apache.poi
poi
3.8
1.4.2.2 代码编写
package demo.es.excel;
import domain.Article;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;
/**
* 使用poi操作excel
* https://www.cnblogs.com/zy2009/p/6716273.html
*/
public class ExcelUtil {
public static List readData(String path) throws Exception {
File file = new File(path);
FileInputStream fileInputStream = new FileInputStream(file);
//(1)判断文件后缀名是xls,还是xlsx
//(2)如果是xls,使用HSSFWorkbook;如果是xlsx,使用XSSFWorkbook
// HSSFWorkbook hssfWorkbook = new HSSFWorkbook(fileInputStream);
XSSFWorkbook books = new XSSFWorkbook(fileInputStream);
XSSFSheet sheet = books.getSheetAt(0);
int lastRowNum = sheet.getLastRowNum();
ArrayList articles = new ArrayList<>();
for (int i =1;i<=lastRowNum;i++){
Article article = new Article();
article.setId(i+"");
XSSFRow row = sheet.getRow(i);
article.setTitle(row.getCell(0).toString());
article.setFrom(row.getCell(1).toString());
article.setTimes(row.getCell(2).toString());
article.setReadCounts(row.getCell(3).toString());
article.setContent(row.getCell(4).toString());
articles.add(article);
}
return articles;
}
}
1.4.3 保存数据到elasticsearch
org.elasticsearch.client
transport
6.0.0
org.apache.logging.log4j
log4j-core
2.9.1
com.google.code.gson
gson
2.8.2
1.4.3.1 代码编写
package demo.es.excel;
import com.google.gson.Gson;
import domain.Article;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.document.DocumentField;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import javax.swing.*;
import java.io.BufferedReader;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class ESUtil {
private static TransportClient client;
private static Settings settings = Settings.builder().put("cluster.name", "myes").build();
public static TransportClient getClient() throws UnknownHostException {
if (client == null) {
synchronized (ESUtil.class) {
if (client == null) {
client = new PreBuiltTransportClient(settings)
.addTransportAddress(new TransportAddress(InetAddress.getByName("node01"), 9300))
.addTransportAddress(new TransportAddress(InetAddress.getByName("node02"), 9300))
.addTransportAddress(new TransportAddress(InetAddress.getByName("node03"), 9300))
;
}
}
}
return client;
}
public static void close() throws UnknownHostException {
if (client != null) {
client.close();
}
}
public static void index(List articles) throws UnknownHostException {
BulkRequestBuilder bulkRequestBuilder = getClient().prepareBulk();
for (Article article : articles) {
Gson gson = new Gson();
String json = gson.toJson(article);
IndexRequestBuilder indexRequestBuilder = getClient().prepareIndex("articles", "article", article.getId()).setSource(json, XContentType.JSON);
bulkRequestBuilder.add(indexRequestBuilder);
}
BulkResponse bulkItemResponses = bulkRequestBuilder.get();
System.out.println(bulkItemResponses.status());
}
public static List query(String keyword, int pageNum, int start) throws UnknownHostException {
ArrayList articles = new ArrayList<>();
//查询操作
SearchRequestBuilder searchRequestBuilder = getClient().prepareSearch("articles").setTypes("article")
.setQuery(QueryBuilders.termQuery("title", keyword))
.setFrom(start)
.setSize(10);
// .addSort("id", SortOrder.DESC); 由于没有设置过id等于fielddata=true,排序功能取消掉
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.preTags("");
highlightBuilder.postTags("");
highlightBuilder.field("title");
searchRequestBuilder.highlighter(highlightBuilder);
SearchResponse searchResponse = searchRequestBuilder.get();
SearchHits hits = searchResponse.getHits();
for (SearchHit hit : hits) {
Article article = new Article();
article.setId(hit.getId());
Map sourceAsMap = hit.getSourceAsMap();
article.setTitle(sourceAsMap.get("title").toString());
article.setFrom(sourceAsMap.get("from").toString());
article.setReadCounts(sourceAsMap.get("readCounts").toString());
article.setTimes(sourceAsMap.get("times").toString());
Text[] titles = hit.getHighlightFields().get("title").getFragments();
String highlightTitle = "";
for (Text title : titles) {
highlightTitle = highlightTitle + title;
}
if (highlightBuilder != null) {
article.setTitle(highlightTitle);
}
articles.add(article);
}
return articles;
}
}
1.4.3.2 方法说明
cn.itcast.es.excel.ESUtil#index
用来创建索引,使用批量创建的方式。原来使用prepareIndex一个一个创建索引,后来升级为prepareBulk方式,提高性能。
cn.itcast.es.excel.ESUtil#query
用来查询数据,这里实现了分页和高亮功能,排序功能。但是排序功能在mapping中需要设置fileddata=true。
高亮的本质是在已经查询出来的结果上,再次进行分词,将html标签添加到关键词上。
分页方式,使用的浅分页方式。(深浅分页的区别)
cn.itcast.es.excel.ESUtil#getClient
最好是设置为单例,单例模式的实现。
cn.itcast.es.excel.ESUtil#getClose
在生产环境中,一般不会close。
1.4.4 数据保存到hbase
1.4.4.1 导入依赖
org.apache.hbase
hbase-client
1.3.1
1.4.4.2 Hbase的工具类
package demo.excel;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.filter.Filter;
import org.apache.hadoop.hbase.util.Bytes;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.Executors;
/**
* 创建表、删除表、新增数据、查询数据(get、scan、scan filter)
*/
public class HBaseUtil {
private static Connection connection;
public static ArrayList>> scan(String tName, String startKey, String endKey, Filter filter) throws IOException {
Table table = getConnection().getTable(TableName.valueOf(tName));
Scan scan = new Scan();
if (startKey != null && endKey != null) {
scan.setStartRow(Bytes.toBytes(startKey));
scan.setStopRow(Bytes.toBytes(endKey));
}
if (filter != null) {
scan.setFilter(filter);
}
Result result = null;
ResultScanner scanner = table.getScanner(scan);
ArrayList>> arrayLists = new ArrayList>>();
while ((result = scanner.next()) != null) {
ArrayList
1.4.4.3 方法说明
cn.itcast.es.excel.HBaseUtil#scan()
参数列表:String tName, String startKey, String endKey, Filter filter
cn.itcast.es.excel.HBaseUtil#get()
参数列表:String tName, String rowkey, String cf, String field
cn.itcast.es.excel.HBaseUtil#put()
参数列表:String tName, String rowkey, String cf, String filed, String value
重载之后:String tName, String rowkey, String cf, HashMap
cn.itcast.es.excel.HBaseUtil#createTable()
表存在就不创建。
1.4.5 整体流程
public class AppMain {
public static void main(String[] args) throws Exception {
indexAnd2Hbase();
query("刘强东");
}
private static void query(String keyword) throws Exception {
List articles = ESUtil.query(keyword, 1, 0);
for (Article article : articles) {
System.out.println("id:"+article.getId() +"\t" +article.getTitle());
ArrayList> articles1 = HBaseUtil.get("articles", article.getId(), null, null);
System.out.println(articles1);
}
}
private static void indexAnd2Hbase() throws Exception {
//1.读取数据
List articles = ExcelUtil.readData("d://baijia.xlsx");
//2.创建索引并
ESUtil.index(articles);
//3.写入数据到Hbase
HBaseUtil.createTable("articles","article");
put(articles);
}
public static void put(List articles) throws IOException {
for (Article article : articles) {
String id = article.getId();
String title = article.getTitle();
String from = article.getFrom();
String times = article.getTimes();
String readCounts = article.getReadCounts();
String content = article.getContent();
HashMap valueMap = new HashMap<>();
valueMap.put("title", title);
valueMap.put("from", from);
valueMap.put("times", times);
valueMap.put("readCounts", readCounts);
valueMap.put("content", content);
HBaseUtil.put("articles", id, "article", valueMap);
System.out.println("---------插入成功");
}
}
}
更多内容,请关注我的公众号: