部署ELK
docker-compose.yml内容
version: '3'
services:
elasticsearch:
image: elasticsearch:7.17.4
container_name: elasticsearch
environment:
- "cluster.name=elasticsearch" #设置集群名称为elasticsearch
- "discovery.type=single-node" #以单一节点模式启动
- "ES_JAVA_OPTS=-Xms512m -Xmx512m" #设置使用jvm内存大小
- TZ=Asia/Shanghai
volumes:
- /elk/elasticsearch/plugins:/usr/share/elasticsearch/plugins #插件文件挂载
- /elk/elasticsearch/data:/usr/share/elasticsearch/data #数据文件挂载
- /elk/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml #配置文件挂载
ports:
- 9200:9200
- 9300:9300
kibana:
image: kibana:7.17.4
container_name: kibana
links:
- elasticsearch:es #可以用es这个域名访问elasticsearch服务
depends_on:
- elasticsearch #kibana在elasticsearch启动之后再启动
environment:
- "elasticsearch.hosts=http://es:9200" #设置访问elasticsearch的地址
- TZ=Asia/Shanghai
volumes:
- /elk/kibana/kibana.yml:/usr/share/kibana/config/kibana.yml #挂载kibana的配置文件
ports:
- 5601:5601
logstash:
image: logstash:7.17.4
container_name: logstash
environment:
- TZ=Asia/Shanghai
volumes:
- /elk/logstash/logstash.conf:/usr/share/logstash/pipeline/logstash.conf #挂载logstash的配置文件
depends_on:
- elasticsearch #logstash在elasticsearch启动之后再启动
links:
- elasticsearch:es #可以用es这个域名访问elasticsearch服务
ports:
- 4560:4560
logstash.conf内容
input {
tcp {
mode => "server"
host => "0.0.0.0"
port => 4560
codec => json_lines
}
}
output {
elasticsearch {
hosts => "es:9200"
index => "springboot-logstash-%{+YYYY.MM.dd}"
}
}
IK分词器
下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases
注意下载与Elasticsearch对应的版本,下载后解压到/elk/elasticsearch/plugins/ik
目录下
运行
需要设置系统内核参数,否则会因为内存不足无法启动;
# 改变设置
sysctl -w vm.max_map_count=262144
# 使之立即生效
sysctl -p
第一次启动可能会发现Elasticsearch无法启动,那是因为/usr/share/elasticsearch/data
目录没有访问权限,只需要修改/elk/elasticsearch/data
目录的权限,再重新启动;
chmod 777 /elk/elasticsearch/data
启动
docker-compose up -d
SpringBoot应用集成Logstash
添加依赖
<!--集成logstash-->
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.3</version>
</dependency>
添加配置文件logback-spring.xml让logback的日志输出到logstash
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<!--应用名称-->
<property name="APP_NAME" value="mall-admin"/>
<!--日志文件保存路径-->
<property name="LOG_FILE_PATH" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/logs}"/>
<contextName>${APP_NAME}</contextName>
<!--每天记录日志到文件appender-->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE_PATH}/${APP_NAME}-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!--输出到logstash的appender-->
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<!--可以访问的logstash日志收集端口-->
<destination>192.168.3.101:4560</destination>
<encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
<appender-ref ref="LOGSTASH"/>
</root>
</configuration>
Spring Data Elasticsearch
添加依赖
<!--Elasticsearch相关依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch<artifactId>
</dependency>
添加配置
spring:
data:
elasticsearch:
repositories:
enabled: true
elasticsearch:
uris: http://192.168.43.42:9200
添加文档对象
@Document(indexName = "pms_product")
@Setting(shards = 1,replicas = 0)
@Data
public class EsProduct implements Serializable {
private static final long serialVersionUID = -1L;
@Id
private Long id;
@Field(type = FieldType.Keyword)
private String productSn;
private Long brandId;
@Field(type = FieldType.Keyword)
private String brandName;
private Long productCategoryId;
@Field(type = FieldType.Keyword)
private String productCategoryName;
private String pic;
@Field(analyzer = "ik_max_word",type = FieldType.Text)
private String name;
@Field(analyzer = "ik_max_word",type = FieldType.Text)
private String subTitle;
@Field(analyzer = "ik_max_word",type = FieldType.Text)
private String keywords;
private BigDecimal price;
private Integer sale;
private Integer newStatus;
private Integer recommandStatus;
private Integer stock;
private Integer promotionType;
private Integer sort;
@Field(type = FieldType.Nested, fielddata = true)
private List<EsProductAttributeValue> attrValueList;
}
不需要中文分词的字段设置成@Field(type = FieldType.Keyword)类型,需要中文分词的设置成@Field(analyzer = "ik_max_word",type = FieldType.Text)类型。
添加Repository接口用于操作Elasticsearch
public interface EsProductRepository extends ElasticsearchRepository<EsProduct, Long> {
/**
* 衍生查询
*
* @param name 商品名称
* @param subTitle 商品标题
* @param keywords 商品关键字
* @param page 分页信息
* @return
*/
Page<EsProduct> findByNameOrSubTitleOrKeywords(String name, String subTitle, String keywords, Pageable page);
}
继承ElasticsearchRepository接口,这样就拥有了一些基本的Elasticsearch数据操作方法,同时定义了一个衍生查询方法。
衍生查询:在接口中直接指定查询方法名称便可查询,无需进行实现,如商品表中有商品名称、标题和关键字,直接定义以上查询,就可以对这三个字段进行全文搜索。
简单的数据操作可以使用ElasticsearchRepository,而复杂的数据操作可以使用ElasticsearchRestTemplate
关于ElasticsearchRestTemplate的使用可以看以下源码:EsProductServiceImpl
Elasticsearch Query DSL
索引库管理
创建索引库
PUT /xc_course
{
"settings":{
"number_of_shards":1, //索引库分片数量
"number_of_replicas":0 //每个分片的副本数
}
}
删除索引库
DELETE /xc_course
查看索引信息
GET /xc_course
映射管理
创建映射
PUT /xc_course/_mapping
{
"properties":{
"name":{
"type":"text"
},
"description":{
"type":"text"
},
"price":{
"type":"double"
},
"pic":{
"type":"text"
"index":"false" # 不需要分词建立索引
}
}
}
查看映射
GET /xc_course/_mapping
新增字段
PUT /xc_course/_mapping
{
"properties":{
"create_time":{
"type":"date",
"format":"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd"
}
}
}
已有的映射可以新增字段但不可以更改已有字段的定义
文档管理
添加文档
# 格式:PUT /{index}/_doc/{id}
# 如果不指定id,那么ES会为我们自动生成
PUT /xc_course/_doc
{
"name" : "Bootstrap开发框架",
"description" : "Bootstrap是由Twitter推出的一个前台页面开发框架,在行业之中使用较为广泛。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。",
"price" : 99.9
}
根据id查询文档
GET /{index}/_doc/{id}
查询全部文档
GET /{index}/_doc/_search
根据id更新文档
全量替换:ES首先会根据id查询文档并删除然后将该id作为新文档的id插入
POST /{index}/_doc/{id}
局部更新:只会更新相应字段
POST /{index}/_doc/{id}/_update
根据id删除文档
DELETE /{index}/_doc/{id}
批量操作
POST /{index}/_doc/_bulk
{"index":{"_id":"1"}} # 指定操作的文档id
{"name": "John Doe" } # 操作内容
{"index":{"_id":"2"}}
{"name": "Jane Doe" }
搜索
查询所有文档matchAllQuery
POST /xc_course/_doc/_search
{
"from":0,
"size":1,
"query":{
"match_all":{}
},
"sort": [
{
"price": "asc"
},
{
"timestamp": "desc"
}
],
"_source":[
"name",
"studymodel"
]
}
query
用来定义搜索条件,_source
用来指定返回的结果集中需要包含哪些字段
from
的含义是结果集偏移,而size
则是从偏移位置开始之后的size
条结果
sort
可以指定字段进行排序
词项匹配termQuery
词项匹配是==精确匹配==,只有当倒排索引表中存在我们指定的词项时才会返回该词项关联的文档集。
POST /xc_course/_doc/_search
{
"query":{
"term":{
"name":"java"
}
}
}
term
查询是精确匹配,term.name
不会被search_analyzer
分词,而是会作为一个整体和倒排索引表中的词项进行匹配。
根据id精确匹配termsQuery
查询id为1和3的文档
POST /xc_course/_doc/_search
{
"query":{
"ids":{
"values":["1","3"]
}
}
}
全文检索matchQuery
输入的关键词会被search_analyzer
指定的分词器分词,然后根据所得词项到倒排索引表中查找文档集合,每个词项关联的文档集合都会被查出来
POST /xc_course/_doc/_search
{
"query":{
"match":{
"name":"bootstrap基础"
}
}
}
operator
上述查询等同于:
{
"query": {
"match": {
"name": {
"query": "bootstrap基础",
"operator": "or"
}
}
}
}
即对检索关键词分词后每个词项的查询结果取并集
operator
可取值or
、and
,分别对应取并集和取交集
minimum_should_match
使用minimum_should_match
可以指定文档匹配词的占比
{
"query": {
"match": {
"name": {
"query": "spring开发框架",
"minimum_should_match":"80%"
}
}
}
}
多域检索multiMatchQuery
termQuery
和matchQuery
一次只能匹配一个Field,multiQuery
一次可以匹配多个字段
{
"query": {
"multi_match": {
"query": "spring css",
"minimum_should_match": "50%",
"fields": [
"name^10", # 指定name字段的权重为10,默认为1
"description"
]
}
}
}
布尔查询boolQuery
布尔查询可以将多个查询组合起来
must
:文档必须匹配must所包括的所有查询条件,相当于 “AND”should
:文档应该匹配should所包括的查询条件中的一个或多个,相当于 "OR"must_not
:文档不能匹配must_not所包括的查询条件,相当于“NOT”
{
"query": {
"bool":{
"must":[
{
"term":{
"name":"开发"
}
}
],
"must_not":[
{
"term":{
"name":"java"
}
}
],
"should":[
{
"term":{
"name":"bootstrap"
}
},
{
"term":{
"name":"spring"
}
}
]
}
}
}
过滤器filter
过滤是针对搜索的结果进行过滤,==过滤器主要判断的是文档是否匹配,不去计算和判断文档的匹配度得分==,所以==过滤器性能比查询要高,且方便缓存==,推荐尽量使用过滤器去实现查询或者过滤器和查询共同使用。
过滤器仅能在布尔查询中使用
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "spring框架",
"fields": [
"name",
"description"
]
}
}
],
"filter": [
{
"term": {
"studymodel": "201001"
}
},
{
"range": {
"price": {
"gte": "10",
"lte": "100"
}
}
}
]
}
}
}
聚合
对搜索结果进行聚合,使用aggs
来表示,类似于MySql中的group by
对state
字段进行聚合,统计出相同state
的文档数量,再统计出balance
的平均值,按balance
的平均值降序排列:
{
"aggs": {
"group_by_state": { # "group_by_state"是自定义的聚合项
"terms": {
"field": "state.keyword",
"order": {
"average_balance": "desc"
}
},
"aggs": {
"average_balance": { # "average_balance"也是自定义的聚合项
"avg": {
"field": "balance"
}
}
}
}
}
}
按字段值的范围进行分段聚合,例如分段范围为age
字段的[20,30]
[30,40]
[40,50]
,之后按gender
统计文档个数和balance
的平均值:
{
"aggs": {
"group_by_age": {
"range": {
"field": "age",
"ranges": [
{
"from": 20,
"to": 30
},
{
"from": 30,
"to": 40
},
{
"from": 40,
"to": 50
}
]
},
"aggs": {
"group_by_gender": {
"terms": {
"field": "gender.keyword"
},
"aggs": {
"average_balance": {
"avg": {
"field": "balance"
}
}
}
}
}
}
}
}