前言
apache lucene
全文检索的工具包
索引:提供查询效率
数据库索引:
主键索引、唯一索引(unique)、复合索引(对数据库中的多个字段创建一个索引)、普通索引等
create index 索引名 on table (column1,column2)
全文检索;类似于使用字典过程
对数据预先构建索引,在查找的时通过索引匹配数据的过程就称为全文检索
创建索引:
1. 准备数据 北京欢迎您
2. 数据分析(分词器 IK) 北京 | 欢迎 | 您
3. 建立索引
检索索引:
1. 准备检索关键词 北京
2. 数据分析 北京
3. 匹配索引 匹配成功
4. 返回结果
倒排序索引(反向索引):通过索引匹配数据
ElasticSearch 基于Lucene的搜索服务
Restful Web
表现层状态转换
传统web应用:http://localhost:8080/资源信息 login.html
restful api:
请求地址:http://localhost:8080/user/1
请求方式:
GET {"id":1,"name":"zs"}
DELETE {"result":true}
POST 新增/更新
PUT 更新
NOSQL:
Not Only SQL
四大分类
k v:Redis(持久化)、Memcached(基于内存存储)
document:类似于(JSON数据格式)
MongoDB、ElasticSearch
{“id”:1,“name”:“zs”}
column: HBase(百亿行,百万列)
图形:neo4j
ElasticSearch应用场景:
1. 全文检索
2. NOSQL
3. 数据分析平台(ELK)
ES核心概念:
- NRT —> near real time
2. Cluster
3. Node
4. Index —> Database
5. Type —> Table
6. Document —> Row
7. hard/replicas
主分片: index中的部分数据集(创建一个索引默认创建5个主分片)
复制分片: 主分片的数据备份(在主分片不同的物理节点中)
8. mapping
type类型的结构
ElasticSearch
一.环境搭建
-
准备
-
CentOS(版本需大于 7 如: CentOS-7-x86_64-Minimal-1804.iso )
-
Java(版本需大于 1.8 如: jdk-8u181-linux-x64.rpm )
-
ES安装包(如: elasticsearch-6.4.0.tar.gz )
-
修改网卡
vi /etc/sysconfig/network-scripts/ifcfg-ens33
#修改为开机自动分配网卡
ONBOOT=YES#或者修改为静态ip
BOOYPROTO=static
ONBOOT=yes
#添加
IPADDR=192.168.47.152(ip地址)
NETMASK=255.255.255.0
GATEWAY=192.168.47.2(虚拟机网段)
DNS1=119.29.29.29
DNS2=182.254.116.116 -
重启网卡服务
systemctl restart network
-
关闭防火墙
systemctl stop firewalld (centos6 : service iptables stop)
systemctl disable firewalld (centos6 : chkconfig iptables off) -
查看ip
ip a
-
-
安装java
rpm -ivh jdk-8u181-linux-x64.rpm
配置环境变量(可不用)
vi /etc/profile
export JAVA_HOME=/usr/java/latest
export CLASSPATH=.
export PATH= P A T H : PATH: PATH:JAVA_HOME/bin更新资源
source /etc/profile
-
安装ES
tar -zxvf elasticsearch-6.4.0.tar.gz -C /usr/
-
启动ES服务
[root@bogon bin]# ./elasticsearch
==ES不允许通过ROOT用户启动== 解决方案: - 创建普通用户,修改目录所属 # groupadd es # useradd -g es es # chown -R es:es /usr/elasticsearch-6.4.0/ - 切换用户 su es
- 启动
-
测试
curl -X GET localhost:9200
{
“name” : “OC03Lum”,
“cluster_name” : “elasticsearch”,
“cluster_uuid” : “S0V8yt6KTaWZyKaZhDSBuA”,
“version” : {
“number” : “6.4.0”,
“build_flavor” : “default”,
“build_type” : “tar”,
“build_hash” : “595516e”,
“build_date” : “2018-08-17T23:18:47.308994Z”,
“build_snapshot” : false,
“lucene_version” : “7.4.0”,
“minimum_wire_compatibility_version” : “5.6.0”,
“minimum_index_compatibility_version” : “5.0.0”
},
“tagline” : “You Know, for Search”
} -
配置远程访问
vim config/elasticsearch.yml
显示行号
:set nu51 # ---------------------------------- Network -----------------------------------
52 #
53 # Set the bind address to a specific IP (IPv4 or IPv6):
54 #
55 network.host: 192.168.30.135
56 #
57 # Set a custom port for HTTP:
58 #
59 #http.port: 9200
60 #
61 # For more information, consult the network module documentation.
62 #
63 # --------------------------------- Discovery ---------------------------------- -
重新启动ES服务,出现异常
解决方案:切换到root用户修改配置文件
vi /etc/security/limits.conf
添加以下内容
- soft nofile 65536
- hard nofile 131072
- soft nproc 2048
- hard nproc 4096
vi /etc/sysctl.conf
添加以下内容
vm.max_map_count=655360
测试配置是否成功
sysctl -p
vm.max_map_count = 655360
重启虚拟机
reboot -
测试访问
192.168.30.135:9200
二.安装Kibana
Kibana是一个针对Elasticsearch的开源数据分析及可视化平台,用来搜索、查看交互存储在Elasticsearch索引中的数
据。使用Kibana,可以通过各种图表进行高级数据分析及展示。
Kibana让海量数据更容易理解。它操作简单,基于浏览器的用户界面可以快速创建仪表板(dashboard)实时显示
Elasticsearch查询动态。
设置Kibana非常简单。无需编码或者额外的基础架构,几分钟内就可以完成Kibana安装并启动Elasticsearch索引监
测。
-
安装配置
# tar -zxvf kibana-6.4.0-linux-x86_64.tar.gz -C /usr # cd /usr/kibana-6.4.0-linux-x86_64/ 修改配置文件 # vi config/kibana.yml # To allow connections from remote users, set this parameter to a non-loopback address. 第七行 server.host: "192.168.23.141" # The URL of the Elasticsearch instance to use for all your queries. 第28行 elasticsearch.url: "http://192.168.23.141:9200" 启动服务 [root@localhost kibana-6.4.0-linux-x86_64]# bin/kibana
-
启动测试
"http://192.168.30.135:5601"
三.使用Kibana实现基本的增删改查
①查看集群
-
查看集群健康信息
GET /_cat/health?v
集群状态(status)
- Green(正常)
- Yellow(正常,但是一些副本还没有分配)
- Red(非正常)
可以使用 GET /_cat/health?help 查看每个操作返回结果字段的意义
-
查看集群中节点信息
GET /_cat/nodes?v
-
查看集群中的索引信息
GET /_cat/indices?v
简化写法
GET /_cat/indices?v&h=health,status
②索引操作
-
创建索引
创建索引
PUT /baizhi
#! Deprecation: the default number of shards will change from [5] to [1] in 7.0.0; if you wish to continue using the default of [5] shards, you must manage this on the create index request or with an index template
{
“acknowledged”: true,
“shards_acknowledged”: true,
“index”: “baizhi”
} -
删除索引
删除索引
DELETE baizhi
-
创建类型Mapping
PUT /baizhi # 创建index(baizhi)并添加类型mapping(_doc)
{
“mappings”: { # 类比数据库中的表结构
“_doc”: { # 类比数据库中的表名 可以更改 例如:user
“properties”: {
“title”: { “type”: “text” },
“name”: { “type”: “text” },
“age”: { “type”: “integer” },
“created”: {
“type”: “date”,
“format”: “strict_date_optional_time||epoch_millis”
}
}
}
}
}或者
POST /baizhi/user # 创建index(baizhi)后,在指定index中添加类型mapping(user)
{
“user”: {
“properties”: {
“id”: { “type”: “text” },
“name”: { “type”: “text” },
“age”: { “type”: “integer” },
“created”: {
“type”: “date”,
“format”: “strict_date_optional_time||epoch_millis”
}
}
}
} -
查看类型mapping
语法:GET /索引名/_mapping/类型名
GET /baizhi/_mapping/user
结果
{
“baizhi”: {
“mappings”: {
“user”: {
“properties”: {
“created”: {“type”: “date”},
“name”: {“type”: “text”},
“title”: {“type”: “text”}
}
}
}
}
}
③文档操作
-
新增单个文档
指定id
PUT /baizhi/user/1
{
“title”:“haha”,
“name”:“zhang3”,
“created”:“2019-02-01”
}结果
{
“_index”: “baizhi”,
“_type”: “user”,
“_id”: “1”,
“_version”: 1,
“result”: “created”,
“_shards”: {
“total”: 2,
“successful”: 1,
“failed”: 0
},
“_seq_no”: 0,
“_primary_term”: 1
}不指定id
POST /baizhi/user
{
“title”:“hehe2”,
“name”:“li4”,
“created”:“2019-02-02”
}结果
{
“_index”: “baizhi”,
“_type”: “user”,
“_id”: “PSjOSGkBdYhmP7fj_ARO”,
“_version”: 1,
“result”: “created”,
“_shards”: {
“total”: 2,
“successful”: 1,
“failed”: 0
},
“_seq_no”: 0,
“_primary_term”: 1
} -
查询单个文档
GET /baizhi/user/1
结果
{
“_index”: “baizhi”,
“_type”: “user”,
“_id”: “1”,
“_version”: 1,
“found”: true,
“_source”: {
“title”: “haha”,
“name”: “zhang3”,
“created”: “2019-02-01”
}
} -
修改单个文档
PUT /baizhi/user/1
{
“name”:“zhang33333333”
}结果
{
“_index”: “baizhi”,
“_type”: “user”,
“_id”: “1”,
“_version”: 2,
“result”: “updated”,
“_shards”: {
“total”: 2,
“successful”: 1,
“failed”: 0
},
“_seq_no”: 1,
“_primary_term”: 1
}查询结果
{
“_index”: “baizhi”,
“_type”: “user”,
“_id”: “1”,
“_version”: 2,
“found”: true,
“_source”: {
“name”: “zhang33333333” #只出现修改的数据
}
} -
删除单个文档
DELETE /baizhi/user/1
结果
{
“_index”: “baizhi”,
“_type”: “user”,
“_id”: “1”,
“_version”: 3,
“result”: “deleted”,
“_shards”: {
“total”: 2,
“successful”: 1,
“failed”: 0
},
“_seq_no”: 2,
“_primary_term”: 1
} -
批处理操作
除了能够索引、更新和删除单个文档外,Elasticsearch还提供了使用 _bulk API 批量执行上述任何操作的能力。这个
功能非常重要,因为它提供了一种非常有效的机制,可以以尽可能少的网络往返尽可能快地执行多个操作- 批量添加
POST /baizhi/user/_bulk
{“index”:{"_id":1}} # 指定id
{“title”:“aa”,“name”:“aa”,“created”:“2019-02-02”}
{“index”:{}} # 不指定id
{“title”:“bb”,“name”:“bb”,“created”:“2019-02-02”}响应结果
{
“took”: 30,
“errors”: false,
“items”: [
{
“index”: {
“_index”: “baizhi”,
“_type”: “user”,
“_id”: “1”,
“_version”: 1,
“result”: “created”,
“_shards”: {
“total”: 2,
“successful”: 1,
“failed”: 0
},
“_seq_no”: 3,
“_primary_term”: 1,
“status”: 201
}
},
{
“index”: {
“_index”: “baizhi”,
“_type”: “user”,
“_id”: “PijdSGkBdYhmP7fj3gQf”,
“_version”: 1,
“result”: “created”,
“_shards”: {
“total”: 2,
“successful”: 1,
“failed”: 0
},
“_seq_no”: 4,
“_primary_term”: 1,
“status”: 201
}
}
]
}- 批量操作
POST /baizhi/user/_bulk
{“update”:{"_id":“1”}}
{“doc”:{“title”:“aaaaaa”}} # doc固定写法
{“delete”:{"_id":“PijdSGkBdYhmP7fj3gQf”}}#结果
{
“took”: 23,
“errors”: false,
“items”: [
{
“update”: {
“_index”: “baizhi”,
“_type”: “user”,
“_id”: “1”,
“_version”: 2,
“result”: “updated”,
“_shards”: {
“total”: 2,
“successful”: 1,
“failed”: 0
},
“_seq_no”: 5,
“_primary_term”: 1,
“status”: 200
}
},
{
“delete”: {
“_index”: “baizhi”,
“_type”: “user”,
“_id”: “PijdSGkBdYhmP7fj3gQf”,
“_version”: 2,
“result”: “deleted”,
“_shards”: {
“total”: 2,
“successful”: 1,
“failed”: 0
},
“_seq_no”: 6,
“_primary_term”: 1,
“status”: 200
}
}
]
}
四.深入搜索
搜索有两种方式:一种是通过 URL 参数进行搜索,另一种是通过 DSL(Request Body) 进行搜索
DSL:Domain Specified Language,特定领域语言
使用请求体可以让你的JSON数据以一种更加可读和更加富有展现力的方式发送。
导入测试数据
# 批量插入测试数据
POST /zpark/user/_bulk
{"index":{"_id":1}}
{"name":"zs","realname":"张三","age":18,"birthday":"2018-12-27","salary":1000.0,
"address":"北京市昌平区沙阳路55号"}
{"index":{"_id":2}}
{"name":"ls","realname":"李四","age":20,"birthday":"2017-10-20","salary":5000.0,
"address":"北京市朝阳区三里屯街道21号"}
{"index":{"_id":3}}
{"name":"ww","realname":"王五","age":25,"birthday":"2016-03-15","salary":4300.0,
"address":"北京市海淀区中关村大街新中关商城2楼511室"}
{"index":{"_id":4}}
{"name":"zl","realname":"赵六","age":20,"birthday":"2003-04-19","salary":12300.0,
"address":"北京市海淀区中关村软件园9号楼211室"}
{"index":{"_id":5}}
{"name":"tq","realname":"田七","age":35,"birthday":"2001-08-11","salary":1403.0,
"address":"北京市海淀区西二旗地铁辉煌国际大厦负一楼"}
①查询
1.分页
2. 查所有
- 高亮(*)
- 匹配查询(match 分词)(*)
- Term(term 不会分词)(*)
- 数值范围(range lt gt lte gte)
- 前缀查询(prefix )
- 通配符查询(wildcard ? *)
- 模糊查询(fuzzy 首先进行精确匹配 再进行相关匹配)(*)
- 布尔查询(boolean 多条件 must must_not)
-
查询所有并排序
查看所有并按照年龄降序排列
-
URL实现
GET /zpark/user/_search?q=*&sort=age:desc&pretty
-
DSL实现
GET /zpark/user/_search { "query":{ "match_all":{} # 查询所有 }, "sort":{ "age":"desc" # 按年龄倒序排列 } }
-
-
分页查询
查询第2页的用户(每页显示2条)
from:起始位置,从0开始
size:查询数据的数量
-
URL实现
GET /zpark/user/_search?q=*&sort=_id:asc&from=2&size=2
-
DSL实现
GET /zpark/user/_search { "query":{ "match_all":{} # 查询所有 }, "sort":{ "_id":"asc" # 按年龄倒序排列 }, "from":2, # 从(nowPage-1)*pageSize检索 "size":2 # 查 pageSize条 }
-
-
基于全文检索的查询(分析检索关键词 匹配索引库 返回结果)
查询 address 在海淀区的所有用户,并高亮
query查询时使用match,会将关键词进行分词
-
DSL实现
GET /zpark/user/_search { "query": { "match": { "address":"海淀区" } }, "highlight": { "fields": { # 需要高亮的字段列表 "address": {} } } }
-
-
基于Term词元查询
查询 name 是 zs 关键字的用户
-
URL实现
GET /zpark/user/_search?q=name:zs
-
DSL实现
term不会分词,查询关键字是一个整体
GET /zpark/user/_search { "query":{ "term": { "name": { "value": "zs" } } } }
-
-
基于范围查询
查询年龄在 20~30 岁之间的用户
-
DSL实现
GET /zpark/user/_search { "query": { "range": { "age": { "gte": 20, "lte": 30 } } } }
-
-
基于前缀(prefix)查询
查询真实姓名以 李 开头的用户
-
DSL实现
GET /zpark/user/_search { "query": { "prefix": { "realname": { "value": "李" } } } }
-
-
基于通配符(wildcard)的查询
查询名字已 s 结尾的用户
? 匹配一个字符
* 匹配0~n个字符
-
DSL实现
GET /zpark/user/_search { "query": { "wildcard": { "name": { "value": "*s" } } } }
-
-
基于Ids的查询
查询 id 为1,2,3的用户
-
DSL实现
GET /zpark/user/_search { "query": { "ids": { "values": [1,2,3] } } }
-
-
基于Fuzzy的查询
-
DSL实现
GET /zpark/user/_search { "query": { "fuzzy": { "realname": {"value": "张"} } } }
-
-
基于Boolean的查询(多条件查询)
查询 age 在15-30岁之间并且 name 必须通配z*
must :查询结果必须符合该查询条件(列表)。
should :类似于or的查询条件。
must_not :查询结果必须不符合查询条件(列表)。-
DSL实现
GET /zpark/user/_search { "query": { "bool": { "must": [ #年龄在15~30岁之间并且必须名字通配z* { "range": { "age": { "gte": 15, "lte": 30 } } }, { "wildcard": { "name": { "value": "z*" } } } ], "must_not": [ { "regexp": { # 正则查询 name必须不能以s结尾 "name": ".*s" } } ] } } }
-
②过滤器
过滤器查询
query: 匹配结果 进行打分排名
filter: 结果匹配 true/false,会对结果进行缓存,会使用 bitset (位集合)
结论:首先使用过滤查询筛选符合条件的数据,再配合query查询进行精准匹配和相关度排序
1.term/terms
2.range
3.ids
4.exists
########################过滤器的使用######################
# 先过滤在查询 过滤查询运行时先执行过滤语句,后执行普通查询
#ranage filte 类型的过滤器
GET /zpark/user/_search
{
"query": {
"bool": {
"must": [
{"match_all": {}}
],
"filter": {
"range": {
"age": {
"gte": 20,
"lte": 30
}
}
}
}
}
}
# term 、 terms Filter 类型过滤器
GET /zpark/user/_search
{
"query": {
"bool": {
"must": [
{"match_all": {}}
],
"filter": {
"terms": {
"name": [
"zs",
"ls"
]
}
}
}
}
}
#exists filter exists 过滤指定字段没有值的文档
# 过滤地址不为空的数据
GET /zpark/user/_search
{
"query": {
"bool": {
"must": [
{"match_all": {}}
],
"filter": {
"exists": {
"field": "address"
}
}
}
}
}
# 或(相反操作)
# 过滤地址不为空的数据
GET /zpark/user/_search
{
"query": {
"bool": {
"must_not": [
{
"exists": {
"field": "address"
}
}
]
}
}
}
#ids filter
GET /zpark/user/_search
{
"query": {
"bool": {
"must": [
{
"match_all": {}
}
],
"filter": {
"ids": {
"values": [
"1",
"2"
]
}
}
}
}
}
③聚合
聚合操作
1. 度量聚合(metric) 类似于 组函数
max\min\sum\avg\stats
2. 桶聚合(buket) 类似于 group by
range、terms、date range、histogram(interval 等分条件)、date histogram(日期等分)
###############聚合################################
# 度量聚合 类似于 组函数
# 平均值查询
POST /zpark/user/_search
{
"aggs": {
"age_svg": {
"avg": {
"field": "age"
}
}
}
}
# 先过滤在统计
POST /zpark/user/_search
{
"query": {
"bool": {
"filter": {
"ids": {
"values": [
"1",
"2"
]
}
}
}
},
"aggs": {
"agg_svg": {
"avg": {
"field": "age"
}
}
}
}
# 先查询在统计
POST /zpark/user/_search
{
"query": {
"ids": {
"values": [1,2]
}
},
"aggs": {
"age_avg": {
"avg": {
"field": "age"
}
}
}
}
# 最大值查询
POST /zpark/user/_search
{
"aggs": {
"agg_max": {
"max": {
"field": "age"
}
}
}
}
# 最小值
POST /zpark/user/_search
{
"aggs": {
"age_min": {
"min": {
"field": "age"
}
}
}
}
# 求总和
POST /zpark/user/_search
{
"query": {
"bool": {
"filter": {
"ids": {
"values": [
1,2,3
]
}
}
}
},
"aggs": {
"agg_sum": {
"sum": {
"field": "age"
}
}
}
}
#一个字段的统计全部信息
POST /zpark/user/_search
{
"aggs": {
"user_stats": {
"stats": {
"field": "age"
}
}
}
}
#####################桶聚合###########################
# 相当于分组函数
# Range Aggregation
#根据范围分组
POST /zpark/user/_search
{
"aggs": {
"age_range": {
"range": {
"field": "age",
"ranges": [
{
"from": 10,
"to": 20
},
{
"from": 20,
"to": 30
},
{
"from": 30,
"to": 40
}
]
}
}
}
}
GET /zpark/user/_search?q=*
#Terms Aggregation 自定义分组
POST /zpark/user/_search
{
"aggs": {
"age_count": {
"terms": {
"field": "age",
"size": 5
}
}
}
}
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
# date range aggregation
# 安装时间区域分组
GET /baizhi/aa/_search?q=*
POST /baizhi/aa/_search
{
"aggs": {
"date_counts": {
"date_range": {
"field": "birthday",
"format": "yyyy-MM-dd",
"ranges": [
{
"from": "now/y-1y",
"to": "now/y"
}
]
}
}
}
}
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#Histogram Aggregation 直方圆聚合
#根据年龄间隔(5岁)统计
POST /zpark/user/_search
{
"aggs": {
"age_counts": {
"histogram": {
"field": "age",
"interval": 5
}
}
}
}
#Date Histogram Aggregation
#按年统计用户
POST /baizhi/aa/_search
{
"aggs": {
"date_counts": {
"date_histogram": {
"field": "birthday",
"interval": "year",
"format": "yyyy-MM-dd"
}
}
}
}
# 嵌套使用
#统计每年中用户的最高工资
POST /baizhi/aa/_search
{
"aggs": {
"date_counts": {
"date_histogram": {
"field": "birthday",
"interval": "year",
"format": "yyyy-MM-dd"
},
"aggs": {
"salary_max": {
"max": {
"field": "salary"
}
}
}
}
}
}
五、JAVA API 集成mavn
https://spring.io/projects/spring-data-elasticsearch#overview
Spring Data (spring开源组织提供的一套对主流存储系统的增删改查支持的开源项目)
ElasticSearch
1. 方式
restful api(通信方式:HTTP协议 + JSON)
es集群:9200端口
httpclient(类库 模拟发送http)
JAVA API
内置客户端
1. 节点客户端(无数据身份加入到es集群)
2. 传输客户端(操作请求的转发)
通信协议:ElasticSearch
端口:9300
查询方式
基于http协议,使用JSON为数据交换格式,通过9200端口的与Elasticsearch进行通信
Spring Data ElasticSearch封装了与ES交互的实现细节,可以使系统开发者以Spring Data Repository 风格实现与ES的数据交互。Elasticsearch为Java用户提供了两种内置客户端:
节点客户端(node client): 节点客户端以无数据节点(none data node)身份加入集群,换言之,它自己不存储任何数据,但是它知道数据在集群中的具体位置,并且能够直接转发请求到对应的节点上。
传输客户端(Transport client): 这个更轻量的传输客户端能够发送请求到远程集群。它自己不加入集群,只是简单转发请求给集群中的节点。两个Java客户端都通过9300端口与集群交互,使用Elasticsearch传输协议(Elasticsearch Transport Protocol)。集群中的节点之间也通过9300端口进行通信。如果此端口未开放,你的节点将不能组成集群。
-
Spring Data ElasticSearch实践
Maven依赖 (使用Spring Data ElasticSearch)
<dependencies> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-elasticsearch</artifactId> <version>3.1.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.1.3.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>
准备配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:elasticesearch="http://www.springframework.org/schema/data/elasticsearch" xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/elasticsearch http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd"> <!-- Spring data 自动扫描 repository接口,生成实现类 --> <elasticsearch:repositories base-package="com.baizhi.es.dao"></elasticsearch:repositories> <!-- ip:port换成具体的ip和端口,多个以逗号分隔 --> <elasticesearch:transport-client id="client" cluster-name="elasticsearch" cluster-nodes="192.168.23.141:9300"> </elasticesearch:transport-client> <!-- es操作对象--> <bean id="elasticsearchTemplate" class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate"> <constructor-arg name="client" ref="client"></constructor-arg> </bean> <!-- 自定义的Repository类用来实现特殊查询。。--> <bean id="customUserRepository" class="com.baizhi.es.dao.CustomUserRepositoryImpl"> </bean> </beans>
准备映射实体类
package com.baizhi.entity; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.elasticsearch.annotations.Document; import java.util.Date; /** * @author gaozhy * @date 2018/12/28.17:05 */ // 文档注解 用于描述索引及其相关信息 @Document(indexName = "zpark",type = "user") public class User { // 主键 @Id private String id; private String name; private String realname; private Integer age; private Double salary; private Date birthday; private String address; public User() { } // 从es中恢复数据时使用的构造方法 @PersistenceConstructor public User(String id, String name, String realname, Integer age, Double salary, Date birthday, String address) { this.id = id; this.name = name; this.realname = realname; this.age = age; this.salary = salary; this.birthday = birthday; this.address = address; } // 省略get/set toString方法 ...... }
spring data repository
spring data elsaticsearch提供了三种构建查询模块的方式:
-
基本的增删改查:继承spring data提供的接口就默认提供
-
接口中声明方法:无需实现类,spring data根据方法名,自动生成实现类,方法名必须符合一定的规则(这里还扩展出一种忽略方法名,根据注解的方式查询)
-
自定义repository:在实现类中注入elasticsearchTemplate,实现上面两种方式不易实现的查询(例如:聚合、分组、深度翻页等)
上面的第一点和第二点只需要声明接口,无需实现类,spring data会扫描并生成实现类
-
#### **注意:repository接口和实现累的接口单独存放不要和dao包放一起,不然会被mybatis的扫描到创建代理类导致不能被工厂创建**
基础的repository接口:提供基本的增删改查和根据方法名的查询
package com.baizhi.es.dao;
import com.baizhi.entity.User;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import java.util.List;
/**
* 基础操作的es repository接口(定义的有通用的增删改查方法)
* 只需要继承 ElasticsearchRepository就可以
* @author gaozhy
* @date 2018/12/29.9:26
*/
public interface UserRepository extends ElasticsearchRepository<User,String> {
/**
* 一些方法只需按照固定书写接口就可以了
* 根据年龄区间查询数据 并根据年龄降序排列
*/
public List<User> findByAgeBetweenOrderByAgeDesc(int start,int end);
/**
* 查询真实姓名已“王”开头的数据
*/
public List<User> findByRealnameStartingWith(String startStr);
/**
* 还可以通过Query注解自定义查询表达式
*/
@Query("{\"bool\" : {\"must\" : {\"fuzzy\" : {\"name\" : \"?0\"}}}}")
public List<User> findByNameLike(String name);
}
测试代码
package com.baizhi.es.test;
import com.baizhi.entity.User;
import com.baizhi.es.dao.UserRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Date;
import java.util.List;
import java.util.Optional;
/**
* @author gaozhy
* @date 2018/12/29.9:30
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-es.xml")
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
/**
* 查所有
*/
@Test
public void testQueryAll(){
Iterable<User> users = userRepository.findAll();
for (User user : users) {
System.out.println(user);
}
}
/**
* 查询所有 并根据年龄倒序排列
*/
@Test
public void testQueryBySort(){
Iterable<User> users = userRepository.findAll(Sort.by(Sort.Direction.DESC, "age"));
for (User user : users) {
System.out.println(user);
}
}
/**
* 根据id查询
*/
@Test
public void testQueryById(){
Optional<User> user = userRepository.findById("1");
System.out.println(user.get());
}
/**
* 新增或者修改数据
*/
@Test
public void testAdd(){
User user = userRepository.save(new User("6", "wb", "王八", 26, 10000D, new Date(), "河南
省郑州市二七区德化街南路33号"));
System.out.println(user);
}
//==================================================================
/**
* 接口中声明方法查询:
* 根据年龄区间查询数据 并根据年龄降序排列
*/
@Test
public void testQueryByRange(){
List<User> users = userRepository.findByAgeBetweenOrderByAgeDesc(20, 28);
users.forEach(user -> System.out.println(user));
}
/**
* 接口中声明方法查询:
* 查询真实姓名已“王”开头的数据
*
* 响应结果:
* User{id='6', name='wb', realname='王八', age=26, salary=10000.0, birthday=Sat Dec 29
14:38:39 CST 2018, address='河南省郑州市二七区德化街南路33号'}
User{id='3', name='ww', realname='王五', age=25, salary=4300.0, birthday=Tue Mar 15
08:00:00 CST 2016, address='北京市海淀区中关村大街新中关商城2楼511室'}
*/
@Test
public void testQueryByPrefix(){
List<User> users = userRepository.findByRealnameStartingWith("王");
users.forEach(user -> System.out.println(user));
}
//==================================================================
/**
* 通过Query注解自定义查询表达式
*/
@Test
public void testQueryByNameLike(){
List<User> users = userRepository.findByNameLike("zs");
users.forEach(user -> System.out.println(user));
}
}
自定义Repository接口:使用 elasticsearchTemplate 实现复杂查询
自定义 CustomUserRepository 接口不需要继承任何类自己实现
package com.baizhi.es.dao;
import com.baizhi.entity.User;
import java.util.List;
import java.util.Map;
/**
* @author gaozhy
* @date 2019/1/1.23:10
*/
public interface CustomUserRepository {
public List<User> findByPageable(int nowPage,int pageSize);
public List<User> findByFieldDesc(String field);
public List<User> findByRealNameLikeAndHighLight(String realName);
public List<User> findByNameWithTermFilter(String ...terms);
public List<User> findByAgeWithRangeFilter(int start,int end);
public Map findByNameStartingWithAndAggregations(String prefixName);
/**
* 嵌套查询:
*
* 先按年龄直方图(桶聚合)统计
* 然后再统计区间内员工的最高工资(度量聚合)
*/
public Map aggregationsWithHistogramAndMax();
/**
* 日期直方图(桶聚合)
*/
public Map aggregationsWithDateHistogram();
}
自定义 CustomUserRepositoryImpl 实现类
package com.baizhi.es.dao;
import com.baizhi.entity.User;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.ResultsExtractor;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.index.query.QueryBuilders.*;
import org.springframework.stereotype.Repository;
/**
* @author gaozhy
* @date 2019/1/1.23:11
*/
@Repository
public class CustomUserRepositoryImpl implements CustomUserRepository {
@Autowired
private ElasticsearchTemplate template;
/**
* ====================================
* {
* "query": {
* "match_all": {}
* },
* "from":1, //从第几条开始 (从0开始)
* "size":1 //大小
* }
* ====================================
*
* @param nowPage
* @param pageSize
* @return
深度分页解决方案:
PUT http://192.168.23.148:9200/my_index/_settings -d '{ "index" : { "max_result_window" : 500000}}'
*/
@Override
public List<User> findByPageable(int nowPage, int pageSize) {
SearchQuery query = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withPageable(new PageRequest((nowPage - 1) * pageSize, pageSize))
.build();
return template.queryForList(query, User.class);
}
/**
* @param field
* @return
*/
@Override
public List<User> findByFieldDesc(String field) {
SearchQuery query = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withSort(SortBuilders.fieldSort(field).order(SortOrder.DESC))
.build();
return template.queryForList(query, User.class);
}
/**
* 高亮
*
* @param realName
* @return
*/
@Override
public List<User> findByRealNameLikeAndHighLight(String realName) {
// 创建搜索构造器
SearchQuery query = new NativeSearchQueryBuilder()
.withQuery(matchQuery("realname", realName))
.withHighlightFields(new HighlightBuilder.Field("realname"))
.build();
// 获取查询结果
AggregatedPage<User> users = template.queryForPage(query, User.class, newSearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
ArrayList<User> users = new ArrayList<>();
SearchHits searchHits = searchResponse.getHits();
for (SearchHit searchHit : searchHits) {
if (searchHits.getHits().length <= 0) {
return null;
}
// 获取文档对象属性map集合
Map<String ,Object> map = searchHit.getSourceAsMap();
// 将获取到的数据封装到对象中
User user = new User();
user.setId(searchHit.getId());
// searchHit.getSourceAsMap().forEach((k, v) -> System.out.println(k + " " + v));
user.setName(map.get("name").toString());
user.setAddress(map.get("address").toString());
user.setAge(Integer.parseInt(map.get("age").toString()));
user.setBirthday(new Date(Long.parseLong(map.get("birthday").toString())));
user.setSalary(Double.parseDouble(map.get("salary").toString()));
// 获取高亮的数据
String realname =searchHit.getHighlightFields().get("realname").fragments()[0].toString();
// 将高亮信息替换原本信息封装到对象中
user.setRealname(realname);
// 添加到集合中
users.add(user);
}
return new AggregatedPageImpl<T>((List<T>) users);
}
});
return users.getContent();
}
@Override
public List<User> findByNameWithTermFilter(String... name) {
SearchQuery query = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withFilter(termsQuery("name",name))
.build();
System.out.println(query.getFilter());
return template.queryForList(query,User.class);
}
@Override
public List<User> findByAgeWithRangeFilter(int start, int end) {
SearchQuery query = new NativeSearchQueryBuilder()
.withFilter(rangeQuery("age").gte(start).lte(end))
.build();
System.out.println(query.getQuery());
System.out.println(query.getFilter());
return template.queryForList(query,User.class);
}
@Override
public Map<String, Aggregation> findByNameStartingWithAndAggregations(String prefixName) {
SearchQuery query = new NativeSearchQueryBuilder()
.withQuery(prefixQuery("name",prefixName))
// result为度量聚合结果的别名
.addAggregation(AggregationBuilders.avg("result").field("age"))
.build();
Aggregations aggregations = template.query(query, new ResultsExtractor<Aggregations>(){
@Override
public Aggregations extract(SearchResponse searchResponse) {
Aggregations aggregations = searchResponse.getAggregations();
return aggregations;
}
});
Map<String, Aggregation> map = aggregations.getAsMap();
return map;
}
@Override
public Map aggregationsWithHistogramAndMax() {
SearchQuery query = new NativeSearchQueryBuilder()
.addAggregation(AggregationBuilders.histogram("result").field("age").interval(5)
.subAggregation(AggregationBuilders.max("max_salary").field("salary")))
.build();
Aggregations aggregations = template.query(query, new ResultsExtractor<Aggregations>(){
@Override
public Aggregations extract(SearchResponse searchResponse) {
return searchResponse.getAggregations();
}
});
return aggregations.getAsMap();
}
@Override
public Map aggregationsWithDateHistogram() {
SearchQuery query = new NativeSearchQueryBuilder()
.addAggregation(AggregationBuilders.dateHistogram("result").field("birthday")
.format("yyyy-MM-dd").dateHistogramInterval(DateHistogramInterval.YEAR))
.build();
Aggregations aggregations = template.query(query, new ResultsExtractor<Aggregations>(){
@Override public Aggregations extract(SearchResponse searchResponse) { return searchResponse.getAggregations(); }
});
return aggregations.getAsMap();
}
}
自定义 CustomUserRepositoryTest 测试类
package com.baizhi.es.test;
import com.baizhi.entity.User;
import com.baizhi.es.dao.CustomUserRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
import java.util.Map;
/**
* @author gaozhy
* @date 2019/1/1.23:26
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-es.xml")
public class CustomUserRepositoryTest {
@Autowired
private CustomUserRepository repository;
@Test
public void testQueryByPage(){
List<User> users = repository.findByPageable(0, 2);
users.forEach(user -> {
System.out.println(user);
});
}
@Test
public void testQueryBySort(){
List<User> users = repository.findByFieldDesc("_id");
users.forEach(user -> {
System.out.println(user);
});
}
@Test
public void testQueryByHighLight(){
List<User> users = repository.findByRealNameLikeAndHighLight("王八");
users.forEach(user -> {
System.out.println(user);
});
}
@Test
public void testQueryByNameWithTermFilter(){
List<User> users = repository.findByNameWithTermFilter("zs","ls");
users.forEach(user -> {
System.out.println(user);
});
}
@Test
public void testQueryByAgeWithRangeFilter(){
List<User> users = repository.findByAgeWithRangeFilter(21,30);
users.forEach(user -> {
System.out.println(user);
});
}
@Test
public void testQueryByNameStartingWithAndAggregations(){
Map map = repository.findByNameStartingWithAndAggregations("z");
System.out.println(map.get("result"));
}
@Test
public void testAggregationsWithHistogramAndMax(){
Map map = repository.aggregationsWithHistogramAndMax();
System.out.println(map.get("result"));
}
@Test
public void testAggregationsWithDateHistogram(){
Map map = repository.aggregationsWithDateHistogram();
System.out.println(map.get("result"));
}
}
六、Java Api集成springboot
springboot 的版本必须是2.0 以上版本
springboot依赖 (使用Spring Data ElasticSearch)
<!--elasticsearch集成springboot的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
springboot配置文件
#配置节点端口
spring.data.elasticsearch.cluster-nodes=192.168.21.145:9300
其余相同和mavn
七、集成中文分词器IK
参考资料:https://github.com/medcl/elasticsearch-analysis-ik
安装
自动下载指令
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.4.0/elasticsearch-analysis-ik-6.4.0.zip
[es@localhost root]$ cd /usr/elasticsearch-6.4.0/
[es@localhost elasticsearch-6.4.0]$
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.4.0/elasticsearch-analysis-ik-6.4.0.zip
-> Downloading https://github.com/medcl/elasticsearch-analysis-
ik/releases/download/v6.4.0/elasticsearch-analysis-ik-6.4.0.zip
[=================================================] 100%
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: plugin requires additional permissions @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
* java.net.SocketPermission * connect,resolve
See http://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html
for descriptions of what these permissions allow and the associated risks.
Continue with installation? [y/N]y
-> Installed analysis-ik
[es@localhost elasticsearch-6.4.0]$ ll plugins/
total 0
drwxr-xr-x. 2 es es 229 Jan 3 10:04 analysis-ik
# 重新启动es的服务使用ik
[root@localhost ~]# jps
2673 Elasticsearch
46151 Jps
40921 PluginCli
[root@localhost ~]# kill -9 2673
[root@localhost ~]# cd /usr/elasticsearch-6.4.0/
[root@localhost elasticsearch-6.4.0]# su es
[es@localhost elasticsearch-6.4.0]$ bin/elasticsearch
测试
创建测试索引
################古诗词##################
#DELETE /poem
PUT /poem
创建类型映射
POST /poem/poemDto/_mapping
{
"properties": {
"id":{"type":"text"},
"poetId":{"type":"integer"},
"poetName":{
"type":"text",
"analyzer": "ik_max_word",# 会将文本做最细粒度的拆分
"search_analyzer": "ik_max_word"# 查询是使用细粒度查询
},
"poetityId":{"type":"integer"},
"poetityTitle":{
"type":"text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
},
"poetityContent":{
"type":"text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
}
}
}
#查看全部索引
GET /poem/poemDto/_search?q=*
插入测试数据
POST /poem/poemDto/_bulk
{"index":{"id":1}}
{"poetId":1,
"poetName":"李白",
"poetityId":1,
"poetityTitle":"将敬酒"
"poetityContent":"人生得意须尽欢,莫使金樽空对月"
}
{"index":{"id":2}}
{"poetId":1,
"poetName":"杜普",
"poetityId":1,
"poetityTitle":"绝句"
"poetityContent":"人生得意须尽欢,莫使金樽空对月"
}
根据关键词高亮查询
GET /poem/poemDto/_search
{
"query": {
"match": {
"poemName": "李白"
}
},
"highlight": {
"fields": {"poemName": {}}
}
}
IK词典配置
先创建一个词典文件 mydict.dic
[root@localhost analysis-ik]# vim /usr/elasticsearch-6.4.0/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">mydict.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!--<entry key="remote_ext_dict">location</entry>-->
<!--用户可以在这里配置远程扩展停止词字典-->
<!--<entry key="remote_ext_stopwords">http://xxx.com/xxx.dic</entry>-->
</properties>
八、es集群搭建
ES集群
a. 准备至少两台服务器
b. 修改核心配置文件
cluster.name: elasticsearch
node.name: node-1
network.host: 192.168.23.148
discovery.zen.ping.unicast.hosts: ["192.168.23.148:9300","192.168.23.149:9300"]
#修改另外一台的文件
# 可以将改好的文件复制到零一台中
[root@bogon config]# scp elasticsearch.yml root@192.168.23.149:/usr/elasticsearch-6.4.0/config/
c.将第二代的elasticsearch的data日志信息删除 和logs日志
[es@bogonelasticsearch] rm -f data/*
[es@bogonelasticsearch] rm -f logs/*
d. 请求新es服务的历史数据
ES集群和Spring Boot整合
a. 创建spring boot工程 并导入es的依赖
b. 修改配置文件
spring.data.elasticsearch.cluster-nodes=192.168.23.148:9300,192.168.23.149:9300
c. spring boot工程入口类 加上注解
@EnableElasticsearchRepositories(basePackages = "com.baizhi.dao.es")
d. 和之前将的一样
深度分页解决方案:
PUT http://192.168.23.148:9200/my_index/_settings -d '{ "index" : { "max_result_window" : 500000}}'