elasticsearch
ELK = Elasticsearch + Logstash + Kinbana
简介
elasticsearch是一个开源的高扩展的分布式全文检索引擎,其可以近乎实时的存储、检索数据,目的是通过简单的Restfut API来隐藏Lucene的复杂性
倒排索引/反向索引
倒排索引又称反向索引 倒排索引即通过vlaue获取key 而正向索引为通过key找value
采用Lucene倒排索引作为底层,该结构用于快速的全文搜索
1.将整句分拆为单词
2.将单词保存,对于每一条记录分别记录出现的位置
3.计算权重得分,当命中分数越高将被取出
词 | 记录 |
---|---|
红海 | 1,2,3,4,5 |
行动 | 1,2,3 |
探索 | 2,5 |
特别 | 3,5 |
记录篇 | 4 |
特工 | 5 |
端口
9300
9300是TCP协议端口:通过tcp协议通讯,es集群之间是通过9300进行通讯,java客户端的方式也是tcp协议在9300端口上与集群进行通信
9200
9200是HTTP协议端口:主要用于外部通讯,外部使用Restful接口进行访问
ik分词器
数据类型
字符串:txt(会被分词器解析)、keyword(不会被分词器解析)
数值类型:long、integer、short、byte、double、flout、half float、scaled float
日期类型:date
布尔类型:boolean
二进制类型:binary
安装
# 创建外部文件 将内部文件挂载到mydata
mkdir -p /mydata/elasticsearch/config
mkdir -p /mydata/elasticsearch/data
#注意"http.host: 0.0.0.0"的空格
echo "http.host: 0.0.0.0">>/data/elasticsearch/config/elasticsearch.yml
# 启动容器并设置挂载目录
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
# kinbana安装 注意ip为本机ip
docker run --name kibana -p 5601:5601 \
--link elasticsearch:elasticsearch \
-e ELASTICSERACH_HOSTS=http://192.168.205.128:9200 \
-d kibana:7.4.2
概念
名词 | 关系型数据库 | 简介 |
---|---|---|
索引 | 数据库 | 不同类型的文档和文档属性的集合 |
类型 | 表 | 索引可以定义一个或多个类型,文档必须属于一个类型 |
文档 | 行/记录 | 以json格式定义的特定方式的字段集合 |
字段 | 列 |
命令
元数据
元数据 | 含义 |
---|---|
_index | 索引名称 |
_type | 类型名称 |
_id | 唯一标识 |
_version | 版本 |
result | 处理方式 |
_seq_no、_primary_term | 用于乐观锁 |
found | 是否查询到 |
_cat
GET /_cat/nodes: 查看所有节点
GET /_cat/health: 查看es健康状况
GET /_cat/master: 查看主节点
GET /_cat/indices: 查看所有索引
新建
注意事项 es6以后不允许在一个index下建立多个type type将在之后版本退出
// customer为索引名称 external为类型 1为唯一标识
// PUT多次请求相同参数及标识 为update操作
PUT customer/external/1
{
"name": "John Doe"
}
// POST请求若无携带标识 自动生成并返回 多次请求皆为created
// POST请求若有携带标识 则多次请求皆为update
POST customer/external
{
"name": "John Doe"
}
查询
GET /customer/_doc/2
// 匹配的字段及值
"query": {
"match_all": {} //匹配所有
}
// 单匹配
"query": {
"match": {
"address": "kings" // 单匹配 分词匹配
}
}
"query": {
"match_phrase": {
"address": "mill lane" //单匹配 完整匹配
}
}
// 多字段匹配 分词匹配
"query": {
"multi_match": {
"query": "mill",
"fields": ["address", "city"]
}
}
// bool匹配
"query": {
"bool": {
"must": [ ## 必须同时匹配gender = M及address = mill及年龄18-30
{
"match": {
"gender": "M"
}
},
{
"match": {
"address": "mill"
}
},
{
"range": {
"age": {
"gte": 18, // 年龄大于18 小于30
"lte": 30
}
}
}
],
"must_not": [ // 必须不满足age = 38
{
"match": {
"age": "38"
}
}
],
"should": [ // 应该匹配 可匹配可不匹配
{
"match": {
"lastname": "Wallace"
}
}
],
}
}
// term查询 使用term查询非text字段 使用match查询text字段
"query": {
"term": {
"address": "990"
}
}
// keyword精确匹配
"query": {
"term": {
"address.keyword": "789 Madison Street"
}
}
// 排序
"sort": [
{
"account_number": "asc" //升序
},
{
"balance": "desc" //降序
}
]
// 分页
"from": 5,
"size": 2,
// 返回的字段
"_source": ["balance", "firstname"]
过滤
filter不贡献相关性得分
更新
// 更新操作若POST + _update则需放在"doc"里 若多次请求则result = noop(无操作)
POST /customer/_doc/2/_update
{
"doc":{
"name": "John Doew"
}
}
// 更新操作若POST 不带_update则无需放在"doc"里 若多次请求则皆为update
POST /customer/_doc/2
{
"name": "John Doew"
}
删除
// 删除文档
DELETE customer/external/1
// 删除所有
DELETE customer
高亮
"query": {
"match": {
"address": {
"query": "mill"
}
}
},
"highlight": {
"fields": {
"address": {} // 高亮的字段必须与query匹配的字段一样才能生效
}
}
// 自定义高亮标签
"query": {
"match": {
"address": {
"query": "mill"
}
}
},
"highlight": {
"pre_tags": "<p class='key' style='clor:red' >",
"post_tags": "</p>",
"fields": {
"address": {}
}
}
批量
POST /customer/external/_bulk
{"index":{"_id":"1"}}
{"name":"John Doe"}
// 批量操作 每两条json为一组 一条指定标识 一条为具体数据
{"index":{"_id":"2"}}
{"name":"Jane Doe"}
聚合
聚合为基于已查到的数据,对查询数据进行再次分析
指标集合
"aggs": {
"ageAgg": { // 聚合命名
"terms": {
"field": "age", // 聚合字段
"size": 10 // 统计数量
}
},
"ageAvg":{
"avg": {
"field": "age" //求平均值
}
}
}
// 子聚合
## 查询平均薪资按照年龄段聚合
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 10
},
"aggs": {
"ageAvg": {
"avg": {
## max求最大值 min求最小值 sum求和 avg求平均数 value_count统计值 cardinality去重 stats统计(同时获得count min max avg sum) extended_stats拓展(同时获得count min max avg sum 平方和 方差 标准差 平均值加/减两个标准差的区间) percentiles百分比统计
"field": "balance"
}
}
}
}
}
## 按照年龄分布统计出不同性别的平均
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 100
},
"aggs": {
"genderAgg": {
"terms": {
"field": "gender.keyword",
"size": 10
},
"aggs": {
"banlanceAvg": {
"avg": {
"field": "balance"
}
}
}
}
}
}
}
桶聚合
类似于group by分组 把满足相关特性的文档分到一个桶里
映射
es不能修改已经创建的映射 若需要修改 只能通过创建的映射 然后将数据迁移实现
PUT /my_index // 创建索引 再次请求error
{
"mappings": {
"properties": {
"age": {"type": "integer"},
"email": {"type": "keyword"},
"name": {"type": "text"}
}
}
}
PUT /my_index/_mapping // 添加索引 再次请求error
{
"properties": {
"employee-id": {
"type": "keyword",
"index": false //是否参与索引 默认为true
}
}
}
// 数据迁移
POST _reindex
{
"source": {
"index": "bank", // 旧索引
"type": "account" //旧type
},
"dest": {
"index": "newbank" //新索引
}
}
分词
// 分词分析
POST _analyze
{
"analyzer": "standard", // 使用默认分词器
"text": "中华人民共华国"
}
POST _analyze
{
"analyzer": "ik_smart", // 使用ik分词器
"text": "中华人民共华国"
}
POST _analyze
{
"analyzer": "ik_max_word", // 使用ik最大分词
"text": "中华人民共华国"
}
ik分词器
https://github.com/medcl/elasticsearch-analysis-ik/releases?after=v7.7.0
ik版本需与elasticsearch版本一致
安装步骤
-
将下载的压缩包放在外部映射路径 plugins/ik 下
-
解压ik压缩包 unzip elasticsearch-analysis-ik-7.4.2.zip
-
将分词存于fenci.txt文本内
-
修改/mydata/elasticsearch/plugins/ik/config远程分词字典
![image-20201222140059602](https://typora-set.oss-cn-shenzhen.aliyuncs.com/img/image-20201222140059602.png)
代码整合
elasticsearch-Rest-Client:官方客户端RestClient,封装了es操作,API层次分明,上手简单
<!-- es高级客户端 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.4.2</version>
</dependency>
<!-- 引入高级client后 springboot依赖对es版本作了统一管理 需要进行修改 -->
<!-- 修改spring-boot-dependencies-2.0.3.RELEASE es版本-->
<!-- 注意若在A项目修改完dependencies版本之后 在B项目会自动关联修改 具体原因还未知 -->
<elasticsearch.version>7.4.2</elasticsearch.version>
// es配置文件
@Configuration
public class ElasticsearchConfig {
public static final RequestOptions COMMON_OPTIONS;
static {
RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
COMMON_OPTIONS = builder.build();
}
/**
* 连接es
* @return
*/
@Bean
public RestHighLevelClient esRestClient() {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("192.168.205.128", 9200, "http")
)
);
return client;
}
}
存储数据
/**
* 测试存储数据
*/
@Test
public void indexData() throws IOException {
// 新建保存请求
IndexRequest indexRequest = new IndexRequest("users");
// 若不指定id将自动生成 自动生成的id为一串字符串
indexRequest.id("1");
User user = new User();
user.setAge(23);
user.setGender("M");
user.setUserName("zhansan");
String jsonString = JSON.toJSONString(user);
// 要保存的jsonString
indexRequest.source(jsonString, XContentType.JSON);
// 执行操作
IndexResponse index = client.index(indexRequest, ElasticsearchConfig.COMMON_OPTIONS);
System.out.println(index);
}
检索数据
@Test
public void searchData() throws IOException {
// 创建检索请求
SearchRequest searchRequest = new SearchRequest();
// 指定索引
searchRequest.indices("bank");
// 指定DSL,检索条件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 构造检索条件
sourceBuilder.query(QueryBuilders.matchQuery("address", "mill"));
// 按照年龄聚合
TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age").size(10);
sourceBuilder.aggregation(ageAgg);
// 计算平均薪资
AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
sourceBuilder.aggregation(balanceAvg);
// 打印检索条件
System.out.println(sourceBuilder.toString());
searchRequest.source(sourceBuilder);
// 执行检索获取响应
SearchResponse searchResponse = client.search(searchRequest, ElasticsearchConfig.COMMON_OPTIONS);
// 分析检索结果
System.out.println(searchResponse.toString());
// 获取所有查到的数据
SearchHits hits = searchResponse.getHits();
SearchHit[] searchHits = hits.getHits();
for (SearchHit hit : searchHits) {
String id = hit.getId();
// ------
// 将数据转成对象并打印
String string = hit.getSourceAsString();
User user = JSON.parseObject(string, User.class);
System.out.println("user:" + user);
}
// 获取聚合信息
Aggregations aggregations = searchResponse.getAggregations();
Terms ageAgg1 = aggregations.get("ageAgg");
for (Terms.Bucket bucket : ageAgg1.getBuckets()) {
String keyAsString = bucket.getKeyAsString();
System.out.println("年龄:" + keyAsString);
}
Avg balanceAvg1 = aggregations.get("balanceAvg");
System.out.println("平均薪资:" + balanceAvg1.getValue());
}
更新数据
/**
* 更新数据
*/
@Test
public void testUpdateForObject() throws IOException {
UpdateRequest request = new UpdateRequest("bank", "1");
User user = new User();
user.setUserName("lisi");
request.doc(JSON.toJSONString(user), XContentType.JSON);
// 同步执行
UpdateResponse updateResponse = client.update(request, ElasticsearchConfig.COMMON_OPTIONS);
// 打印更新结果
System.out.println(updateResponse.getResult());
}
删除数据
@Test
public void testDelete() throws IOException {
DeleteRequest request = new DeleteRequest("bank", "1");
// 同步执行
DeleteResponse deleteResponse = client.delete(request, ElasticsearchConfig.COMMON_OPTIONS);
// 打印删除结果
System.out.println(deleteResponse.getResult());
}