目录
elasticsearch搜索引擎
一、elasticsearch的安装
二、操作索引库
三、Restclient
DSL语句
一、DSL语句
二、restclient操作DSL语句
三、ES集群
数据聚合
自动补全
es集群
elastic stack(ELK):结合elasticsearch、kibana、logstash、beats的技术栈
lucene:apache的搜索引擎类库,提供了搜索引擎的核心api。elasticsearch就是通过lucene实现
单点部署
创建网络,用于kibana和elasticsearch互联
docker network create es-net
拉取镜像(太慢了建议本地下载再导入)
docker pull elasticsearch:7.12.1
运行镜像
docker run -d \
--name es \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "discovery.type=single-node" \
-v es-data:/usr/share/elasticsearch/data \
-v es-plugins:/usr/share/elasticsearch/plugins \
--privileged \
--network es-net \
-p 9200:9200 \
-p 9300:9300 \
elasticsearch:7.12.1
-e "cluster.name=es-docker-cluster"
:设置集群名称
-e "http.host=0.0.0.0"
:监听的地址,可以外网访问
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m"
:内存大小
-e "discovery.type=single-node"
:非集群模式
-v es-data:/usr/share/elasticsearch/data
:挂载逻辑卷,绑定es的数据目录
-v es-logs:/usr/share/elasticsearch/logs
:挂载逻辑卷,绑定es的日志目录
-v es-plugins:/usr/share/elasticsearch/plugins
:挂载逻辑卷,绑定es的插件目录
--privileged
:授予逻辑卷访问权
--network es-net
:加入一个名为es-net的网络中
-p 9200:9200
:端口映射配置
部署kibana(和elasticsearch一样下载)//可视化平台
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=es-net \
-p 5601:5601 \
kibana:7.12.1
安装ik分词器 //用何种分词方法,以及自定义分词
# 进入容器内部
docker exec -it elasticsearch /bin/bash# 在线下载并安装
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip#退出
exit
#重启容器
docker restart elasticsearch
mysql和elasticsearch对比
MySQL |
Elasticsearch |
说明 |
Table |
Index |
索引(index),就是文档的集合,类似数据库的表(table) |
Row |
Document |
文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式 |
Column |
Field |
字段(Field),就是JSON文档中的字段,类似数据库中的列(Column) |
Schema |
Mapping |
Mapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema) |
SQL |
DSL |
DSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD |
mapping是对索引库中文档的约束,常见的mapping属性包括: type:字段数据类型,常见的简单类型有: 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址) 数值:long、integer、short、byte、double、float、 布尔:boolean 日期:date 对象:object index:是否创建索引,默认为true analyzer:使用哪种分词器 properties:该字段的子字段
索引库
#创建索引库
PUT /student
{
"mappings": {
"properties":{
"name":{
"type":"keyword",
"index":true
},
"sex":{
"type":"keyword",
"index":true
},
"words":{
"type": "text",
"index": false,
"analyzer": "ik_smart"
}
}
}
}#获取索引
GET /student#删除索引
DELETE /student#更新索引 mapping建立后不能更改,但是可以增加新字段
PUT /student/_mapping
{
"properties":{
"age":{
"type":"keyword",
"index":true
}
}
}
文档
#创建文档
POST /student/_doc/1
{
"name":"张三",
"sex":"男",
"words":"我不知道未来会带来什么,但我会努力过好每一天",
"age":"21"
}#获取文档
GET /student/_doc/1#更新文档
#增量修改 修改某一个值
POST /student/_update/1
{
"doc": {
"age":"18"
}
}
#全量修改 先删除后增加新文档
PUT /student/_doc/1
{
"name":"李四",
"sex":"男",
"words":"我不知道未来会带来什么,但我会努力过好每一天",
"age":"21"
}#删除文档
DELETE /student/_doc/1
注意如果创建文档时,增加了mapping里面没有的字段时,es默认帮你创建,默认的规则为
JSON类型 |
Elasticsearch类型 |
字符串 |
日期格式字符串:mapping为date类型 普通字符串:mapping为text类型,并添加keyword类型子字段 |
布尔值 |
boolean |
浮点数 |
float |
整数 |
long |
对象嵌套 |
object,并添加properties |
数组 |
由数组中的第一个非空类型决定 |
空值 |
忽略 |
restclient是es官方提供给java操作DSL语句的客户端。
索引的crud
创建索引
@BeforeEach
void initRestClient(){
// 1.初始化restclient
client = new RestHighLevelClient(RestClient.builder(HttpHost.create("192.168.35.150:9200")));
}
@AfterEach
void closeRestClient() throws IOException {
client.close();
}
@Test //创建索引
void createIndex() throws IOException {
// 1.创建request
CreateIndexRequest request = new CreateIndexRequest("hotel");
// 2.请求参数 MAPPING_DEFAULT就是json格式的索引
request.source(MAPPING_DEFAULT, XContentType.JSON);
// 3.发送请求
client.indices().create(request, RequestOptions.DEFAULT);
}
获取索引
@Test //获取索引
void existIndex() throws IOException {
// 1.创建request
GetIndexRequest request = new GetIndexRequest("hotel");
// 2.发送请求
boolean exist = client.indices().exists(request, RequestOptions.DEFAULT);
// 3.输出
System.out.println(exist ?"索引存在" :"索引不存在");
}
删除索引
@Test //删除索引
void deleteIndex() throws IOException {
// 1.创建request
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
// 2.发送请求
AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
// 3.输出
System.out.println(response);
}
文档的crud
创建文档
private RestHighLevelClient client;
@Autowired
private HotelService service;
@BeforeEach
void initRestClient(){
client= new RestHighLevelClient(RestClient.builder(HttpHost.create("192.168.35.150:9200")));
}
@AfterEach
void closeRestClient() throws IOException {
client.close();
}
@Test
void createDoc() throws IOException {
Hotel hotel = service.getById(415600L);
HotelDoc hotelDoc = new HotelDoc(hotel);
// 1.创建request
IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());
// 2.请求参数
request.source(JSON.toJSONString(hotelDoc),XContentType.JSON);
// 3.发送请求
client.index(request,RequestOptions.DEFAULT);
}
查询文档
@Test
void getDoc() throws IOException {
// 1.创建request
GetRequest request = new GetRequest("hotel", "415600");
// 2.发送请求
GetResponse response = client.get(request, RequestOptions.DEFAULT);
// 3.输出
System.out.println(response.getSourceAsString());
}
修改
@Test //这是局部更新,全局更新和createDoc一样
void updateDoc() throws IOException {
// 1.创建request
UpdateRequest request = new UpdateRequest("hotel", "415600");
// 2.参数
request.doc(
"brand" , "和颐"
);
// 3.发送请求
client.update(request,RequestOptions.DEFAULT);
}
删除
@Test //删除文档
void deleteIndex() throws IOException {
// 1.创建request
DeleteRequest request = new DeleteRequest("hotel", "415600");
// 2.发送请求
client.delete(request,RequestOptions.DEFAULT);
}
批量插入文档
@Test //批量插入文档
void bulkRequset() throws IOException {
BulkRequest request = new BulkRequest();
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
wrapper.lt(Hotel::getPrice,200);
List list = service.list(wrapper);
for(Hotel hotel: list){
System.out.println("==================="+hotel);
HotelDoc hotelDoc = new HotelDoc(hotel);
request.add(new IndexRequest("hotel")
.id(hotelDoc.getId().toString())
.source(JSON.toJSONString(hotelDoc),XContentType.JSON));
}
client.bulk(request,RequestOptions.DEFAULT);
}
Elasticsearch提供了基于JSON的DSL(Domain Specific Language)来定义查询。常见的查询类型包括:
查询所有:查询出所有数据,一般测试用。例如:match_all
全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如: match_query multi_match_query
精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如: ids range term
地理(geo)查询:根据经纬度查询。例如: geo_distance geo_bounding_box
复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如: bool function_score
查询 macth
#查询全部
GET /hotel/_search
{
"query": {
"match_all": {}
}}#match查询多个字段 将所有字段都写到all,可以直接搜索多个单词,只要含有all里面的字#段都可以检索
GET /hotel/_search
{
"query": {
"match": { "all": "速8上海"}
}}
查询多个字段 mutil_match 与上面的效果相同,推荐使用上面的那种
#mutil_match查询 匹配多个字段
GET /hotel/_search
{
"query": {
"multi_match": {
"query": "北京如家",
"fields": ["city","brand","name"]
}
}}
精确查询 term
#term查询 精确查询,字段值必须完全匹配
GET /hotel/_search
{
"query": {
"term": {"city": {"value": "深圳"}}
}}
范围查询 range
#range查询 范围查询
GET /hotel/_search
{
"query": {
"range": {"price": {"gte": 100,"lte": 150}
}
}}
地理查询 geo_distance (点) geo_bounding_box(范围)
#地理查询 geo_distance
GET /hotel/_search
{
"query": {
"geo_distance": {
"distance": "10km",
"location": "22.642629,114.202799"
}}}#地理查询 geo_bounding_box
GET /hotel/_search
{
"query": {
"geo_bounding_box": {
"location":{
"top_left": {"lat":32,"lon":121},
"bottom_right": {"lat":22,"lon":114}
}
}}}
复杂排序 function_source
elasticsearch中的相关性打分算法
TF-IDF算法:在elasticsearch5.0之前,会随着词频增加而越来越大
BM25算法:在elasticsearch5.0之后,会随着词频增加而增大,但增长曲线会趋于水平
#复合查询 function_score算分查询
GET /hotel/_search
{
"query": {
"function_score": {
"query": {"match_all": {}},
"functions": [{
"filter": {"term": {"city": "深圳"}},
"weight": 10
}],
"boost_mode": "sum"
}}}
复杂排序 Boolean Query
布尔查询是一个或多个查询子句的组合。子查询的组合方式有:
must:必须匹配每个子查询,类似“与”
should:选择性匹配子查询,类似“或”
must_not:必须不匹配,不参与算分,类似“非”
filter:必须匹配,不参与算分(放在这里过滤不影响性能)
#复合查询 bool查询
GET /hotel/_search
{
"query": {
"bool": {
"must":{ "match":{"name":"如家"}},
"must_not": [{"range": { "price": { "gte": 200}}}],
"should": [
{ "match": {"business": "松岗商业中心区"}},
{ "match": { "business": "房山风景区"}}],
"filter": [{"term": {"city": "深圳"}}]
}}}
对于结果的处理,排序,分页,高亮
排序
elasticsearch支持对搜索结果排序,默认是根据相关度算分(_score)来排序。可以排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等。
#排序
GET /hotel/_search
{
"query": {"match_all": {} },
"sort": [{"score": { "order": "desc" }},
{"price":{"order": "asc" }}
]}#排序 地理排序
GET /hotel/_search
{
"query": {"match_all": {} },
"sort": [
{"_geo_distance": {
"location": {"lat": 22,"lon": 114},
"order": "asc",
"unit": "km"}}
]}
分页
from + size:
优点:支持随机翻页
缺点:深度分页问题,默认查询上限(from + size)是10000 场景:百度、京东、谷歌、淘宝这样的随机翻页搜索
after search:
优点:没有查询上限(单次查询的size不超过10000)
缺点:只能向后逐页查询,不支持随机翻页 场景:没有随机翻页需求的搜索,例如手机向下滚动翻页
#分页 from+size
GET /hotel/_search
{
"query": {"match_all": {}},
"sort": [{
"_geo_distance": {
"location": {"lat": 22,"lon": 114 },
"order": "asc",
"unit": "km"}}],
"from": 10,
"size": 10}
高亮
将搜索结果中的关键字用标签()标记出来 在页面中给标签添加css样式
GET /hotel/_search
{
"query": {
"match": {
"all": "如家"
}
},
"highlight": {
"fields": {
"name": {"require_field_match": "false"}
}
}}
match、range、item、geo、function_source、boolean查询都是在query下的子字段,请求的方式都一样,只需要修改query()里面querybulider
@Test //查询所有
void match_all() throws IOException {
// 1.创建request
SearchRequest request = new SearchRequest("hotel");
// 2.准备DSL
//match_all 查询所有的DSL
//request.source().query(QueryBuilders.matchAllQuery());
//match 查询多个字段的DSL
//request.source().query(QueryBuilders.matchQuery("all","速8上海"));
//multiMatch multiMatch查询多个字段的DSL
//request.source().query(QueryBuilders.multiMatchQuery("上海","name","city","business"));
//term 精确查询的DSL
//request.source().query(QueryBuilders.termQuery("city","上海"));
//range 范围查询的DSL
//request.source().query(QueryBuilders.rangeQuery("price").lt(150));
//bool bool复杂查询的DSL
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(QueryBuilders.termQuery("city","上海"));
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").lt(150));
request.source().query(boolQueryBuilder);
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析返回值
parseResponse(response);
}
@Test //解析数据
void parseResponse(SearchResponse response){
SearchHits hits = response.getHits();
// 5.查询总条数
TotalHits totalHits = hits.getTotalHits();
// 6.获取对象数组
SearchHit[] searchHits = hits.getHits();
for(SearchHit searchHit :searchHits){
System.out.println(searchHit);
}
}
排序和分页查询
@Test
void sortAndPage() throws IOException {
SearchRequest request = new SearchRequest("hotel");
request.source().query(QueryBuilders.matchAllQuery());
//分页
request.source().from(0).size(10);
//排序
request.source().sort("price", SortOrder.ASC);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
parseResponse(response);
}
聚合的种类
桶(Bucket)聚合:用来对文档做分组
TermAggregation:按照文档字段值分组
Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组
度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等
Avg:求平均值
Max:求最大值
Min:求最小值
Stats:同时求max、min、avg、sum等
管道(pipeline)聚合:其它聚合的结果为基础做聚合
DSL实现聚合
桶聚合,按照文档字段分
GET /hotel/_search
{
"query": {
"range": {
"price": {"gte": 200,"lte": 500}
}},
"size": 0, // 设置size为0,结果中不包含文档,只包含聚合结果
"aggs": { // 定义聚合
"brandAgg": { //给聚合起个名字
"terms": { // 聚合的类型,按照品牌值聚合,所以选择term
"field": "brand", // 参与聚合的字段
"size": 20 } // 希望获取的聚合结果数量
} }
}
metric聚合
#定义meric聚合
GET /hotel/_search
{
"query": {
"match_all": {}
},
"size": 0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand",
"order": {
"_count": "asc"
},
"size": 10
},
"aggs": { // 是brands聚合的子聚合,也就是分组后对每组分别计算
"score_stats": { // 聚合名称
"stats": { // 聚合类型,这里stats可以计算min、max、avg等
"field": "score" // 聚合字段,这里是score
}
}
}
}
}
}
restclient聚合
@Test
void createAggregation() throws IOException {
SearchRequest request = new SearchRequest("hotel");
request.source().size(0);
request.source().aggregation(AggregationBuilders
.terms("brand_agg")
.field("brand")
.size(10)
);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
System.out.println(response);
}
拼音分词器
地址:GitHub - medcl/elasticsearch-analysis-pinyin: This Pinyin Analysis plugin is used to do conversion between Chinese characters and Pinyin.
下载导入es的plugin目录中,重启es
自定义分词器
包含三部分:
character filters:在tokenizer之前对文本进行处理。例如删除字符、替换字符
tokenizer:将文本按照一定的规则切割成词条(term)。例如keyword,就是不分词;还有ik_smart
tokenizer filter:将tokenizer输出的词条做进一步处理。例如大小写转换、同义词处理、拼音处理
PUT /test
{
"settings": {
"analysis": {
"analyzer": { // 自定义分词器
"my_analyzer": { // 分词器名称
"tokenizer": "ik_max_word",
"filter": "py"
}
},
"filter": { // 自定义tokenizer filter
"py": { // 过滤器名称
"type": "pinyin", // 过滤器类型,这里是pinyin
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_original": true,
"limit_first_letter_length": 16,
"remove_duplicated_term": true,
"none_chinese_pinyin_tokenize": false
}
}
}
},
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "my_analyzer",
"search_analyzer": "ik_smart"
}
}
}
}
自动补全
elasticsearch提供了Completion Suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询的效率,对于文档中字段的类型有一些约束:
参与补全查询的字段必须是completion类型。
字段的内容一般是用来补全的多个词条形成的数组。
比如,一个这样的索引库:
// 创建索引库
PUT test
{
"mappings": {
"properties": {
"title":{
"type": "completion"
}
}
}
}
自动补全查询
// 自动补全查询
GET /test/_search
{
"suggest": {
"title_suggest": {
"text": "s", // 关键字
"completion": {
"field": "title", // 补全查询的字段
"skip_duplicates": true, // 跳过重复的
"size": 10 // 获取前10条结果
}
}
}
}
用rabbitmq实现es和mysql的数据同步。
es集群的搭建
用docker-compose来部署,编写一个compose文件
version: '2.2'
services:
es01:
image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1
container_name: es01
environment:
- node.name=es01
- cluster.name=es-docker-cluster
- discovery.seed_hosts=es02,es03
- cluster.initial_master_nodes=es01,es02,es03
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- data01:/usr/share/elasticsearch/data
ports:
- 9200:9200
networks:
- elastic
es02:
image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1
container_name: es02
environment:
- node.name=es02
- cluster.name=es-docker-cluster
- discovery.seed_hosts=es01,es03
- cluster.initial_master_nodes=es01,es02,es03
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- data02:/usr/share/elasticsearch/data
networks:
- elastic
es03:
image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1
container_name: es03
environment:
- node.name=es03
- cluster.name=es-docker-cluster
- discovery.seed_hosts=es01,es02
- cluster.initial_master_nodes=es01,es02,es03
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- data03:/usr/share/elasticsearch/data
networks:
- elasticvolumes:
data01:
driver: local
data02:
driver: local
data03:
driver: localnetworks:
elastic:
driver: bridge
运行docker-compose
docker-compose up
es集群不同接待职责
节点类型 |
配置参数 |
默认值 |
节点职责 |
master eligible |
node.master |
true |
备选主节点:主节点可以管理和记录集群状态、决定分片在哪个节点、处理创建和删除索引库的请求 |
data |
node.data |
true |
数据节点:存储数据、搜索、聚合、CRUD |
ingest |
node.ingest |
true |
数据存储之前的预处理 |
coordinating |
上面3个参数都为false则为coordinating节点 |
无 |
路由请求到其它节点 合并其它节点处理的结果,返回给用户 |
用cerebro(和kibana类似)来管理,来管理es集群
集群的分布式存储
elasticsearch会通过hash算法来计算文档应该存储到哪个分片:
shard = hash(_routing) % number_of_shards
_routing默认是文档的id
算法与分片数量有关,因此索引库一旦创建,分片数量不能修改!
集群故障转移
集群的master节点会监控集群中的节点状态,如果发现有节点宕机,会立即将宕机节点的分片数据迁移到其它节点,确保数据安全,这个叫做故障转移。