ElasticSearch

ElasticSearch是一个分布式的开源搜索和分析引擎。它可以快速存储,搜索和分析海量数据。
ElasticSearch基础概念:index索引--->库/insert,type--->表,Document--->文档
在ES中,所有的格式都是JSON的文档
ElasticSearch概念:倒排索引
分词:将整句拆分为单词
相关性得分:将所有记录查出来,按照相关性得分,从高到低排出来

P103 docker安装ES

(1)下载ealastic search(存储和检索)和kibana(可视化检索)

docker pull elasticsearch:7.4.2
docker pull kibana:7.4.2
版本要统一

(2)配置

# 将docker里的目录挂载到linux的/mydata目录中
# 修改/mydata就可以改掉docker里的
mkdir -p /mydata/elasticsearch/config
mkdir -p /mydata/elasticsearch/data

# es可以被远程任何机器访问
echo "http.host: 0.0.0.0" >/mydata/elasticsearch/config/elasticsearch.yml

# 递归更改权限,es需要访问
chmod -R 777 /mydata/elasticsearch/

(3)启动Elastic search

# 9200是用户交互端口 9300是集群心跳端口
# -e指定是单阶段运行
# -e指定占用的内存大小,生产时可以设置32G
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e  "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v  /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2 

# 设置开机启动elasticsearch
docker update elasticsearch --restart=always

(4)启动kibana:
kibana指定了了ES交互端口9200 5600位kibana主页端口
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.56.10:9200 -p 5601:5601 -d kibana:7.4.2

初步检索

检索ES信息

(1)GET /_cat/nodes:查看所有节点
(2)GET /_cat/health:查看es健康状况
(3)GET /_cat/master:查看主节点
(4)GET/_cat/indicies:查看所有索引 ,等价于mysql数据库的show databases;

新增文档

保存一个数据,保存在哪个索引的哪个类型下(哪张数据库哪张表下),保存时用唯一标识指定

# # 在customer索引下的external类型下保存1号数据
PUT customer/external/1

# POSTMAN输入
http://192.168.56.10:9200/customer/external/1
{
 "name":"John Doe"
}

POST新增。如果不指定id,会自动生成id。指定id就会修改这个数据,并新增版本号;
PUT可以新增也可以修改。PUT必须指定id;由于PUT需要指定id,我们一般用来做修改操作,不指定id会报错。

查看文档

GET /customer/external/1
乐观锁用法:通过“if_seq_no=1&if_primary_term=1”,当序列号(seq_no)匹配的时候,才进行修改,否则不修改。

更新文档

POST customer/externel/1/_update
只要POST带有update情况下,POST操作会对比源文档数据,如果相同不会有什么操作,文档version不增加。
不带update的情况下,无论是POST还是PUT操作总会重新保存并增加version版本

看场景:
对于大并发更新,不带update
对于大并发查询偶尔更新,带update;对比更新,重新计算分配规则

删除文档或索引

DELETE customer/external/1
DELETE customer

P110 ElasticSearch 进阶

ES支持两种基本方式检索;
* 通过REST request uri 发送搜索参数 (uri +检索参数);

请求参数方式检索
GET bank/_search?q=&sort=account_number:asc
说明:q=
# 查询所有; sort # 排序字段; asc #升序

返回内容:
took – 花费多少ms搜索
timed_out – 是否超时
_shards – 多少分片被搜索了,以及多少成功/失败的搜索分片
max_score –文档相关性最高得分
hits.total.value - 多少匹配文档被找到
hits.sort - 结果的排序key(列),没有的话按照score排序
hits._score - 相关得分 (not applicable when using match_all)
检索了1000条数据,但是根据相关性算法,只返回10条。

* 通过REST request body 来发送它们(uri+请求体);

GET /bank/_search
{
"query": { "match_all": {} },
"sort": [
{ "account_number": "asc" },
{ "balance":"desc"}
]
}
这种通过请求体来进行查询的叫DSL,Query DSL。

(1) 基本语法格式

一个查询语句的典型结构
示例 使用时不要加#注释内容

GET bank/_search
{
  "query": {  #  查询的字段
    "match_all": {}
  },
  "from": 0,  # 从第几条文档开始查
  "size": 5,
  "_source":["balance"],
  "sort": [
    {
      "account_number": {  # 返回结果按哪个列排序
        "order": "desc"  # 降序
      }
    }
  ]
}
_source为要返回的字段
(3) query/match匹配查询

如果是非字符串,会进行精确匹配。如果是字符串,会进行全文检索。
全文检索,最终会按照评分进行排序,会对检索条件进行分词匹配。

GET bank/_search
{
  "query": {
    "match": {
      "address": "kings"
    }
  }
}
(4) query/match_phrase [不拆分匹配]

将需要匹配的值当成一整个单词(不分词)进行检索.

GET bank/_search
{
  "query": {
    "match_phrase": {
      "address": "mill road"   #  就是说不要匹配只有mill或只有road的,要匹配mill road一整个子串
    }
  }
}
(5) query/multi_math【多字段匹配】

state或者address中包含mill,并且在查询过程中,会对于查询条件进行分词。

GET bank/_search
{
  "query": {
    "multi_match": {  # 前面的match仅指定了一个字段。
      "query": "mill",
      "fields": [ # state和address有mill子串  不要求都有
        "state",
        "address"
      ]
    }
  }
}
(6) query/bool/must复合查询

组合多种条件。
复合语句可以合并,任何其他查询语句,包括符合语句。这也就意味着,复合语句之间可以互相嵌套,可以表达非常复杂的逻辑。

  • must:必须达到must所列举的所有条件
  • must_not:必须不匹配must_not所列举的所有条件。
  • should:应该满足should所列举的条件。满足条件最好,不满足也可以,满足得分更高
    实例:查询gender=m,并且address=mill的数据
GET bank/_search
{
   "query":{
        "bool":{  # 
             "must":[ # 必须有这些字段
              {"match":{"address":"mill"}},
              {"match":{"gender":"M"}}
             ]
         }
    }
}
(7)query/filter【结果过滤】
must 贡献得分
should 贡献得分
must_not 不贡献得分
filter 不贡献得分

并不是所有的查询都需要产生分数,特别是哪些仅用于filtering过滤的文档。为了不计算分数,elasticsearch会自动检查场景并且优化查询的执行。

(8)query/term

和match一样。匹配某个属性的值。

  • 全文检索字段用match,

  • 其他非text字段匹配用term。
    不要使用term来进行文本字段查询
    es默认存储text值时用分词分析,所以要搜索text值,使用match

  • 字段.keyword:要一一匹配到(精确匹配); match_phrase:子串包含即可

(9) aggs/agg1(聚合)

aggregation 聚合分析:聚合提供了从数据中分组和提取数据的能力。最简单的聚合方法大致等于SQL Group by和SQL聚合函数。

复杂子聚合:查出所有年龄分布,并且这些年龄段中M的平均薪资和F的平均薪资以及这个年龄段的总体平均薪资。

GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "ageAgg": {
      "terms": {  #  看age分布
        "field": "age",
        "size": 100
      },
      "aggs": { # 子聚合
        "genderAgg": {
          "terms": { # 看gender分布
            "field": "gender.keyword" # 注意这里,文本字段应该用.keyword
          },
          "aggs": { # 子聚合
            "balanceAvg": {
              "avg": { # 男性的平均
                "field": "balance"
              }
            }
          }
        },
        "ageBalanceAvg": {
          "avg": { #age分布的平均(男女)
            "field": "balance"
          }
        }
      }
    }
  },
  "size": 0
}

三、Mapping字段映射

映射定义文档如何被存储和检索的

ElasticSearch7-去掉type概念:
1.两个不同type下的两个user_name,在ES同一个索引下其实被认为是同一个filed,你必须在两个不同的type中定义相同的filed映射。否则,不同type中的相同字段名称就会在处理中出现冲突的情况,导致Lucene处理效率下降。
2.去掉type就是为了提高ES处理数据的效率。

创建索引并指定映射
PUT /my_index
{
  "mappings": {
    "properties": {
      "age": {
        "type": "integer"
      },
      "email": {
        "type": "keyword" # 指定为keyword
      },
      "name": {
        "type": "text" # 全文检索。保存时候分词,检索时候进行分词匹配
      }
    }
  }
}
添加新的索引映射
PUT /my_index/_mapping
{
  "properties": {
    "employee-id": {
      "type": "keyword",
      "index": false # 字段不能被检索。检索
    }
  }
}
这里的 “index”: false,表明新增的字段不能被检索,只是一个冗余字段。
###### 不能更新映射
对于已经存在的字段映射,我们不能更新。更新必须创建新的索引,进行数据迁移。

数据迁移

先创建new_twitter的正确映射。
然后使用如下方式进行数据迁移。

PUT /newbank
{
  "mappings": {
    "properties": {
      "account_number": {
        "type": "long"
      },
      "address": {
        "type": "text"
      },
      "age": {
        "type": "integer"
      },
      "balance": {
        "type": "long"
      },
      "city": {
        "type": "keyword"
      },
      "email": {
        "type": "keyword"
      },
      "employer": {
        "type": "keyword"
      },
      "firstname": {
        "type": "text"
      },
      "gender": {
        "type": "keyword"
      },
      "lastname": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      },
      "state": {
        "type": "keyword"
      }
    }
  }
}
将bank中的数据迁移到newbank中
POST _reindex
{
  "source": {
    "index": "bank",
    "type": "account"
  },
  "dest": {
    "index": "newbank"
  }
}

四、分词

一个tokenizer(分词器)接收一个字符流,将之分割为独立的tokens(词元,通常是独立的单词),然后输出tokens流。
elasticsearch提供了很多内置的分词器(标准分词器),可以用来构建custom analyzers(自定义分词器)。
对于中文,我们需要安装额外的分词器。

安装分词器

在github上找到对应版本的分词器:https://github.com/medcl/elasticsearch-analysis-ik/releases?after=v7.6.2
下载.zip文件后,使用Xfpt将文件存储在/mydata/elasticsearch/plugins中,接着进入到docker容器的内部:docker exec -it 667f9c44f2f1 /bin/bash,检查是否有.zip文件。有,exit。使用 unzip 命令完成解压。
解压完成,想要检验是否安装完成,先进入容器内部的bin目录,执行elasticsearch-plugin list (列出所有安装好的es插件)。
装完记得重启,退出容器内部,docker restart elasticsearch

POST _analyze
{
  "analyzer": "ik_max_word",
  "text": "我是中国人"
}
解决Linux网络Ping不同问题,详情请看P123。

自定义扩展词库

1、首先安装Nginx,安装之前先将虚拟机的内存调为4G。
2、将ES的内存扩展到528m,需要先停掉ES,那么会不会造成数据丢失呢?
不会,因为我在创建容器的时候,把ES的数据映射到外面的文件夹data中,所以即使删除掉了也不会影响。
3、启动Nginx实例,目的是为了复制出配置,并在/mydata 下mkdir nginx
docker run -p 80:80 --name nginx -d nginx:1.10
将容器内的配置文件拷贝到当前的nginx目录。在/mydata 中执行命令:
docker container cp nginx:/etc/nginx .
然后停止并删掉容器,重新启动Nginx。
nginx默认找资源全都是在html下找的。
4、Nginx配置完成之后接着配置ES配置文件
cd 到/mydata/elasticsearch/plugins/ik/config 下,vi IKAnalyzer.cfg.xml
修改为以下:

<!--用户可以在这里配置远程扩展字典 -->
<entry key="remote_ext_dict">http://192.168.56.10/es/fenci.txt</entry>

保存完成后必须要重启ES;docker restat ES;

POST _analyze
{
  "analyzer": "ik_max_word",
  "text": "乔碧萝殿下"
}
测试完成

elasticsearch-Rest-Client

前端发送请求---》Java程序接受---》发给ES进行处理---》返回前端页面
java操作es有两种方式
1)9300: TCP
spring-data-elasticsearch:transport-api.jar;
springboot版本不同,ransport-api.jar不同,不能适配es版本
7.x已经不建议使用,8以后就要废弃

2)9200: HTTP
有诸多包
jestClient: 非官方,更新慢;
RestTemplate:模拟HTTP请求,ES很多操作需要自己封装,麻烦;
HttpClient:同上;
Elasticsearch-Rest-Client:官方RestClient,封装了ES操作,API层次分明,上手简单;
最终选择Elasticsearch-Rest-Client(elasticsearch-rest-high-level-client)
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html

为什么不直接通过JS给ES发送请求在页面展示呢?

1、ES是属于后台集群服务器,端口一般不对外暴露,避免出现安全问题(最大的原因)。
2、JS对ES的支持度有点低。

Springboot整合high-level-client

建Modul,导依赖,由于我创建的是Maven工程,所以有一些依赖需要自己手动导入。

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.4.2</version>
</dependency>
<properties>
    <java.version>1.8</java.version>
    <elasticsearch.version>7.4.2</elasticsearch.version>
</properties>

依赖导完要修改配置文件的地址和application.name

/**
 * 1、导入依赖
 * 2、编写配置,给容器中注入一个ResetHighLevelClient
 * 3、参照官方文档API  https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.x/java-rest-high-getting-started-initialization.html
 */
最后完整配置如下
@Configuration
public class GulimallESConfig {
    public static final RequestOptions COMMON_OPTIONS;
    static {
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
        COMMON_OPTIONS = builder.build();
    }
    @Bean
    public RestHighLevelClient esRestClient() {
        RestClientBuilder builder = null;
        // 可以指定多个es
        builder = RestClient.builder(new HttpHost("192.168.56.10", 9200, "http"));
        RestHighLevelClient client = new RestHighLevelClient(builder);
        return client;
    }
}
/**
     * 测试存储数据到ES
     */
     @Test
    public void indexData() throws IOException {
        IndexRequest indexRequest = new IndexRequest("users");  //索引名
        indexRequest.id("1");   //数据的Id
        User user = new User();
        user.setAge(81);
        user.setGender("mam");
        user.setUsername("zhangsan");
        String jsonString = JSON.toJSONString(user);//将对象转换为JSON
        indexRequest.source(jsonString, XContentType.JSON);//需要传JSON数据,要保存的内容

        //执行真正的保存操作
        IndexResponse index = client.index(indexRequest, GulimallESConfig.COMMON_OPTIONS);
        //提取有用的响应数据
        System.out.println(index);
    }

测试复杂检索

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.x/java-rest-high-search.html

全部代码在这里(实体类除外),麻了
    @Test
    public void searchData() throws IOException {
        //1、创建检索请求
        SearchRequest searchRequest = new SearchRequest();
        //指定要检索的索引
        searchRequest.indices("bank");
        //指定DSL,检索条件    SearchSourceBuilder sourceBuilder
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        System.out.println("检索请求" + searchRequest.toString());
        //1.1)、构造检索条件
        sourceBuilder.query(QueryBuilders.matchQuery("address","mill"));
        //1.2)、按照年龄的值分布进行聚合
        TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age").size(10);
        sourceBuilder.aggregation(ageAgg);
        //1.3)、计算平均薪资
        AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
        sourceBuilder.aggregation(balanceAvg);

        searchRequest.source(sourceBuilder);
        System.out.println("检索条件" + sourceBuilder.toString());

        //2、执行检索;
        SearchResponse searchResponse = client.search(searchRequest, GulimallESConfig.COMMON_OPTIONS);

        //3、分析结果
        System.out.println("检索结果" + searchResponse.toString());
        /*
        目前只是把检索结果打成一个JSON字符串,想要转为Java对象,怎么转?
        封装的数据都在Map,但是各种嵌套各种数据转换太麻烦了,官方有办法!
        Map map = JSON.parseObject(searchResponse.toString(), Map.class);
         */
        //3.1) 获取所有查到的数据
        SearchHits hits = searchResponse.getHits();
        SearchHit[] hitsHits = hits.getHits();
        for(SearchHit hit:hitsHits){
            //hit.getIndex();hit.getType();hit.getId();
            //转换为Json字符串
            String sourceAsString = hit.getSourceAsString();
            //接着使用JSON功能将对应的JSON字符串转换为Java实体类对象
            Account account = JSON.parseObject(sourceAsString, Account.class);
            System.out.println("account:" + account);
        }
        //3.2) 获取这次检索到的分析信息(聚合)
        Aggregations aggregations = searchResponse.getAggregations();
        //获取平均年龄
        Terms ageAgg1 = aggregations.get("ageAgg");
        for(Terms.Bucket bucket: ageAgg1.getBuckets()){
            String keyAsString = bucket.getKeyAsString();
            System.out.println("ageAgg:" + keyAsString + "===》"+bucket.getDocCount());
        }
        //获取平均薪资
        Avg balanceAvg1 = aggregations.get("balanceAvg");
        System.out.println("balanceAvg-平均薪资:" + balanceAvg1.getValue());

    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容