一文包学会ElasticSearch的大部分应用场合

ElasticSearch

官网下载地址:Download Elasticsearch | Elastic

历史版本下载地址1:Index of elasticsearch-local/7.6.1

历史版本下载地址2:Past Releases of Elastic Stack Software | Elastic

ElasticSearch的安装(windows)

安装前所需环境,jdk1.8以上,最好也要有node环境。

  1. 安装解压即可

  2. 启动进入bin目录,点击

image20221212100648303.png

启动日志

image20221212100810247.png

访问端口:http://localhost:9200/

image20221212100914770.png

安装可视化界面

下载地址:GitHub - mobz/elasticsearch-head: A web front end for an elastic search cluster

#下载依赖
cnpm install
#启动
cnpm run start
#默认启动本地的9100端口

连接ElasticSearch

这里会出现跨域问题

修改ElasticSearch配置文件(在elasticsearch解压目录config下elasticsearch.yml中添加)

# 开启跨域
http.cors.enabled: true
# 所有人访问
http.cors.allow-origin: "*"

image20221212103503756.png

安装kibana

官网地址:下载 Elastic 产品 | Elastic

历史版本地址:Index of kibana-local/7.6.1

下载的版本要和ElasticSearch版本一致

解压,进入斌、目录启动。

image20221212110136911.png

汉化

修改config文件下的kibana.yml配置文件。

i18n.locale: "zh-CN"

image20221212111016942.png

image20221212111917082.png

docker安装(单点)

链接: 百度网盘 请输入提取码 提取码: cf82

1.将三个tar放置/tmp下

2.在该目录下执行

 docker load -i es.tar  #上到到容器
docker load -i kibana.tar 

3.执行启动

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:端口映射配置

在浏览器中输入:http://IP地址:9200 即可看到elasticsearch的响应结果

 4.接下来安装kibana

运行docker命令,部署kibana

docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=es-net \
-p 5601:5601  \
kibana:7.12.1
  • --network es-net :加入一个名为es-net的网络中,与elasticsearch在同一个网络中

  • -e ELASTICSEARCH_HOSTS=http://es:9200":设置elasticsearch的地址,因为kibana已经与elasticsearch在一个网络,因此可以用容器名直接访问elasticsearch

  • -p 5601:5601:端口映射配置

kibana启动一般比较慢,需要多等待一会,可以通过命令:

docker logs -f kibana

查看运行日志,当查看到下面的日志,说明成功:

此时,在浏览器输入地址访问:http://192.168.150.101:5601,即可看到结果

docker安装分词器(在线安装)

# 进入容器内部
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

docker安装ik分词器(离线安装)

在百度网盘中提出ik文件夹或者自己解压elasticsearch-analysis-ik-7.12.1.zip重命名为ik,如下操作:

安装插件需要知道elasticsearch的plugins目录位置,而我们用了数据卷挂载,因此需要查看elasticsearch的数据卷目录,通过下面命令查看:

docker volume inspect es-plugins

显示结果:

[
    {
        "CreatedAt": "2022-05-06T10:06:34+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/es-plugins/_data",
        "Name": "es-plugins",
        "Options": null,
        "Scope": "local"
    }
]

说明plugins目录被挂载到了:/var/lib/docker/volumes/es-plugins/_data这个目录中。

然后进入该文件夹将ik文件夹放置该目录即可:

依次执行以下指令:

 # 重启容器
 docker restart es
 
 # 查看es日志
docker logs -f es

踩坑:

我的是虚拟机,暂停之后,docker无法挂载导致。

#重启docker
systemctl restart docker
#重启容器
systemctl restart es
systemctl restart kibana

测试:

IK分词器

什么是ik分词器?

IK分词器是ES的一个插件,主要用于把一段中文或者英文的划分成一个个的关键字,我们在搜索时候会把自己的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一个匹配操作,默认的中文分词器是将每个字看成一个词,比如"我爱技术"会被分为"我","爱","技","术",这显然不符合要求,所以我们需要安装中文分词器IK来解决这个问题;

IK提供了两个分词算法:ik_smart和ik_max_word

ik_smart为最少切分,添加了歧义识别功能,推荐;

ik_max_word为最细切分,能切的都会被切掉;

示例:对“买一台笔记本” 进行分词 K提供了两个分词算法: ik_smartik_max_word ,其中ik_smart最少切分, ik_max_word最细粒度划分!

ik_smart分词结果:

img

ik_max_word分词结果:

{
  "tokens" : [
    {
      "token" : "买一",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "一台",
      "start_offset" : 1,
      "end_offset" : 3,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "一",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "TYPE_CNUM",
      "position" : 2
    },
    {
      "token" : "台笔",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 3
    },
    {
      "token" : "台",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "COUNT",
      "position" : 4
    },
    {
      "token" : "笔记本",
      "start_offset" : 3,
      "end_offset" : 6,
      "type" : "CN_WORD",
      "position" : 5
    },
    {
      "token" : "笔记",
      "start_offset" : 3,
      "end_offset" : 5,
      "type" : "CN_WORD",
      "position" : 6
    },
    {
      "token" : "本",
      "start_offset" : 5,
      "end_offset" : 6,
      "type" : "CN_CHAR",
      "position" : 7
    }
  ]
}

安装

下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v7.6.1

下载完成后解压到,在elasticSearch的plugins目录下创建ik文件目录并解压。

image20221212143235660.png

配置完成使用kibanna进行测试:

image20221212145635558.png

仔细观察可发现es会将一个一个的词去分开,那么他是根据什么去分词的呢?

我们进入下载的ik分词器插件中可见有很多的.dic文件,其实这些文件就是为了分词所诞生的。

image20221212145938908.png

自定义分词

例子:此时我们想让上图中的久久不见为一组

  1. 在config目录下创建my.dic

    image20221212150753059.png

  2. 然后在IKAnalyzer.cfg.xml

image20221212151044126.png

3.重启测试

image20221212151236201.png

image20221212151448605.png

Rest风格说明

一种软件架构风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁更有层次更易于实现缓存等机制。

基本Rest命令说明:

methodurl地址描述
PUT(创建,修改)localhost:9200/索引名称/类型名称/文档id创建文档(指定文档id)
POST(创建)localhost:9200/索引名称/类型名称创建文档(随机文档id)
POST(修改)localhost:9200/索引名称/类型名称/文档id/_update修改文档
DELETE(删除)localhost:9200/索引名称/类型名称/文档id删除文档
GET(查询)localhost:9200/索引名称/类型名称/文档id查询文档通过文档ID
POST(查询)localhost:9200/索引名称/类型名称/文档id/_search查询所有数据

测试

PUT /test1/type1/1
{
  "name" : "测试",
  "age" : 18
}

image20221212152901520.png

image20221212152931182.png

字段数据类型(Mapping)

  • 字符串类型

    • text、

      keyword

      • text:支持分词,全文检索,支持模糊、精确查询,不支持聚合,排序操作;text类型的最大支持的字符长度无限制,适合大字段存储;

      • keyword:不进行分词,直接索引、支持模糊、支持精确匹配,支持聚合、排序操作。keyword类型的最大支持的长度为——32766个UTF-8类型的字符,可以通过设置ignore_above指定自持字符长度,超过给定长度后的数据将不被索引,无法通过term精确匹配检索返回结果。

  • 数值型

    • long、Integer、short、byte、double、float、half floatscaled float

  • 日期类型

    • date

  • te布尔类型

    • boolean

  • 二进制类型

    • binary

  • 等等…

指定字段的类型(put)

PUT /test2
{
  "mappings": {
    "properties": {
      "name":{ #类似数据库字段名
        "type": "text" #类似数据库字段类型
      },
      "age":{
        "type": "long"
      }
    }
  }
  }

image20221212153825182.png

crud例子

# 创建索引库
PUT /estest
{
  "mappings": {
    "properties": {
      "info": {
        "type": "text",
        "analyzer": "ik_max_word" #使用text类型需要指定ik分词器类型
      },
      "email": {
        "type": "keyword",
        "index": false #是够开启分词,默认开启
      },
      "name": {
        "type": "object",
        "properties": {
          "firstName": {
            "type": "keyword",
            "index": false
          },
          "lastName":{
            "type": "keyword",
            "index": false
          }
        }
      }
    }
  }
}
# 查询
get /estest
#修改结构 在原结构的基础上添加age属性并指定类型
PUT /estest/_mapping
{
  "properties":{
    "age":{
      "type":"integer"
    }
  }
}
#删除索引
DELETE /estest

关于索引的基本操作

1.添加数据

PUT  test/_doc/2
{
  "name":"王五",
  "age":35,
  "desc":"啦啦啦啦啦测试啦",
  "tag":["标签一","标签二","标签三"]
}

//在原来的结构上添加字段
POST /hotel/_update/2048671293
{
  "doc": {
    "isAD":true
  }
}

image20221212162321518.png

2.修改数据

#指定所有的字段做修改操作
POST  test/_doc/2/_update
{
"name":"修改的名字"
}

3.模糊查询

GET test/_doc/_search?q=name:张

image20221212164547096.png

GET test/_doc/_search
{
  "query":{
    "match":{
      "name":"张"
    }
  }
}

image20221212171031576.png

查询匹配

  • match:匹配(会使用分词器解析(先分析文档,然后进行查询))

  • _source:过滤字段

  • sort:排序

  • formsize 分页

4._source:过滤字段

GET test/_doc/_search
{
  "query":{
    "match":{
      "name":"张"
    }
  },
  "_source":["name","tag"] #只需要name和tag属性
}

5.sort排序

GET test/_doc/_search
{
  "query":{
    "match":{
      "name":"张"
    }
  },
  "sort":[
        {
          "age":{
            "order":"desc" #asc desc 
          }
        }
      ]
}

6.formsize 分页

GET test/_doc/_search
{
  "query":{
    "match":{
      "name":"张"
    }
  },
  "sort":[
    {
      "age":{
        "order":"desc"
            }
    }
    ],
    "from":1, #from相当于 limit(pageNnm) 第几页
    "size":1  #size 相当于 pageSize 每页显示多少条数据
}

7.布尔值查询(must)

GET test/_doc/_search
{
  "query":{
    "bool":{
      "must":[ #must表示必须符合的意思
            {
              "match":{
                "name":"张"
              }
            },
            {
              "match":{
                "age":23
              }
            }
        ]
    }
  }
}
#上面相当于mysql的 where name='张' and  age=23

8.相当于sql的or语句(should)

GET  /test/_doc/_search
{
  "query":{
      "bool": {
        "should": [
          {
           "match":{
             "name":"张三"
           }
          },
          {
            "match":{
              "age":88
            }
          }
        ]
      }    
  }
}
#相当于mysql的 where name="张三" or age=88 

9.不包含(must_not)

GET /test/_doc/_search
{
  "query":{
    "bool":{
      "must_not":{ #不包含
        "match":{
          "age":88 #排除88岁的
        }
      } 
    }
  }
}

10.过滤器(lt)

GET  /test/_doc/_search
{
  "query":{
      "bool": {
        "should": [
          {
           "match":{
             "name":"张三"
           }
          },
          {
            "match":{
              "age":88
            }
          }
        ],
        "filter":{
          "range":{
            "age":{
              "lt":88, # 小于88
              "gt":23  # 大于23 
            }
          }
        }
      }    
  }
}
  • gt 大于

  • gte 大于等于

  • lt 小于

  • lte 小于等于

11.匹配多个条件查询

GET /test/_doc/_search
{
 "query":{
   "match":{
     "tag":"小 大"
   }
 }  
}

image20221214135428261.png

12.精确查询

  1. term 查找是通过倒排索引去查询的查询快

  2. match 会使用分词器解析,通过分词查询

注意类型:其中keyword类型是不会被分词器解析拆分的,而text会被拆分。

GET /test3/_doc/_search
{
  "query":{
    "term": {#使用term必须精确,因为desc这个字段的类型的keyword
      "desc": "无语了..."
    }
  }
}

GET /test3/_doc/_search
{
  "query":{
      "match":{ 
         "name":"老"
      }
  }
}

精确查询多个值

image20221214144146078.png

例子

# 创建索引库
PUT /estest
{
  "mappings": {
    "properties": {
      "info": {
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "email": {
        "type": "keyword",
        "index": false
      },
      "name": {
        "type": "object",
        "properties": {
          "firstName": {
            "type": "keyword",
            "index": false
          },
          "lastName":{
            "type": "keyword",
            "index": false
          }
        }
      }
    }
  }
}
# 查询
#get /estest
#修改结构 在原结构的基础上添加age属性并指定类型
PUT /estest/_mapping
{
  "properties":{
    "age":{
      "type":"integer"
    }
  }
}
#删除索引
DELETE /estest


#添加文档1
PUT /estest/_doc/1
{
  "age":10,
  "email":"24112869@qq.com",
   "info":"这是一段分词的内容!",
   "name":{
     "firstName":"张",
     "lastName":"三"
   }
}
#添加文档2
PUT /estest/_doc/2
{
  "age":50,
  "email":"78945669@qq.com",
   "info":"这是一段分词的内容2!",
   "name":{
     "firstName":"张",
     "lastName":"四"
   }
}

#查询文档
#get  /estest/_doc/2

#模糊查询方式1
GET /estest/_doc/_search?q=info:这是

#模糊查询方式2
POST /estest/_search
{
  "query": {
    "match": {
      "info": "这是"
    }
  }
}
#过滤字段
POST /estest/_search
{
  "query": {
    "match": {
      "info": "这是"
    }
  },
  "_source": ["name","info","age"],
  "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ]
}
# 代码高亮
GET /estest/_search
{
  "query":{
    "match":{
      "info":"这是"
    }
  },
  "highlight":{
    "pre_tags":"<i class='key' style='color:red'>",
    "post_tags":"</i>",
    "fields":{
      "info":{}
    }
  }
}

代码高亮

GET /test3/_doc/_search
{
  "query":{
    "match":{
      "name":"李"
    }
  },
  "highlight":{
    "fields":{
      "name":{}
    }
  } 
}

image20221214145003525.png

返回前端,前端通过em标签即可实现高亮!

也可以自定义标签或者样式

GET /test3/_doc/_search
{
  "query":{
    "match":{
      "name":"李"
    }
  },
  "highlight":{
    "pre_tags":"<i class='key' style='color:red'>",
    "post_tags":"</i>",
    "fields":{
      "name":{}
    }
  }
}

image20221214145525287.png

集成sprigboot(2.2.6)

依赖

<properties>
        <java.version>1.8</java.version>
        <elasticsearch.version>7.6.1</elasticsearch.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
​
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

image20221215092540527.png

注意这里的版本要和本地的版本对应

编写配置类

@Configuration
public class ElasticSearchClientConfig {

    @Bean
    public RestHighLevelClient restHighLevelClient(){
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("localhost", 9200, "http")
                )
        );
        return client;
    }
}

Es的api使用

索引
//添加索引
@SpringBootTest
class StudyEsApplicationTests {
  @Autowired
  @Qualifier("restHighLevelClient")
  RestHighLevelClient client;

  @Test
  void contextLoads() throws IOException {
    //        创建客户端
    CreateIndexRequest createIndexRequest = new CreateIndexRequest("cws_test1");
    //        执行请求,获取请求响应
    CreateIndexResponse createIndexResponse = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
    System.out.println(createIndexResponse);
  }
}
/**
     * @param
     * @return void
     * @author cws
     * @date 2022/12/15 10:39
     * 判断索引是否存在
     */
@Test
void getText() throws IOException {
  GetIndexRequest getIndexRequest = new GetIndexRequest("cws_test1");
  boolean exists = client.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
  System.out.println(exists);
}
/**
     * 删除索引
     */
@Test
void DelText() throws IOException {
  DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("cws_test1");
  AcknowledgedResponse delete = client.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
  System.out.println(delete);
}
文档
//添加文档
@Test
void AddText() throws IOException {
  User user = new User("李四",50);
  //        创建请求  put /cws_test1/_doc/1
  IndexRequest indexRequest = new IndexRequest("cws_test1");
  indexRequest.id("2"); //设置索引id
  indexRequest.timeout(TimeValue.timeValueSeconds(1l));
  //        将数据放入请求
  indexRequest.source(JSONUtil.toJsonStr(user), XContentType.JSON);

  //       使用客户端发送请求
  IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
  System.out.println(indexResponse.status().getStatus());
  System.out.println(indexResponse.toString());
}
 /**
     * 删除文档
     */
    @Test
    void del() throws IOException {
        DeleteRequest deleteRequest = new DeleteRequest("cws_test1");
        deleteRequest.id("1");
        DeleteResponse delete = client.delete(deleteRequest, RequestOptions.DEFAULT);
        System.out.println(delete);
    }
/**
     * 获取单个文档
     */
@Test
void getDocument() throws IOException {
  GetRequest getRequest = new GetRequest("cws_test1");
  getRequest.id("2");
  GetResponse documentFields = client.get(getRequest, RequestOptions.DEFAULT);
  System.out.println(documentFields.getSource());
}
/**
     * 修改文档
     */
@Test
void update() throws IOException {
  UpdateRequest updateRequest = new UpdateRequest("cws_test1","2");
  User user = new User("测试",5);
  updateRequest.doc(JSONUtil.toJsonStr(user), XContentType.JSON);
  UpdateResponse response = client.update(updateRequest, RequestOptions.DEFAULT);
  System.out.println(response.status());
}
/**
   * 判断是否文档
*/
@Test
void exitDocument() throws IOException {
  GetRequest getRequest = new GetRequest("cws_test1","2");
  //  设置不返回_source
  getRequest.fetchSourceContext(new FetchSourceContext(false));
  getRequest.storedFields("_none_");
  boolean exists = client.exists(getRequest, RequestOptions.DEFAULT);
  System.out.println(exists);
}

批量添加数据

@Test
void addListOfDocument() throws IOException {
  //批量api
  BulkRequest bulkRequest = new BulkRequest();
  bulkRequest.timeout("10s");

  List<User> list = new ArrayList<>();
  list.add(new User("ls", 1));
  list.add(new User("ws", 2));
  list.add(new User("ww", 3));
  list.add(new User("历史", 4));
  list.add(new User("pp", 5));

  for (int i = 0; i < list.size(); i++) {
    bulkRequest.add(
      new IndexRequest("cws_test1")
      .id("" + (i + 1))
      .source(JSONUtil.toJsonStr(list.get(i)), XContentType.JSON)
    );
  }
  
  BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
  System.out.println(bulk.hasFailures());//是否出现异常? false表示没有出现(成功),
  System.out.println(bulk.status());//状态
}

批量删除

@Test
void dels() throws IOException {
  BulkRequest bulkRequest = new BulkRequest();
  bulkRequest.timeout("10s");

  List<String> list = new ArrayList<>();
  list.add("1");
  list.add("2");
  list.add("3");


  for (int i = 0; i < list.size(); i++) {
    bulkRequest.add( new DeleteRequest("cws_test1")
                    .id(list.get(i)));
  }
  BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
  System.out.println(bulk.status());
}

批量更新,批量删除,批量添加操作基本一致,差别在 bulkRequest.add()传的参数

查询(searchRequest)

构建添加类作用
SearchSourceBuilder条件构造
HighlightBuilder构建高亮
TermsQueryBuilder精确查询
MatchAllQueryBuilder查询全部
以此类推,把索引构建号的xxxQueryBuilder放置sourceBuilder.query()中即可
  @Test
    void search() throws IOException {
//        创建所需api
        SearchRequest searchRequest = new SearchRequest();
        //创建搜索条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

//        QueryBuilders.matchAllQuery() 匹配所有
//        使用QueryBuilders工具类快速构建
        TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("name", "ww");
        sourceBuilder.query(termsQueryBuilder);
        sourceBuilder.timeout(new TimeValue(10l, TimeUnit.SECONDS));

        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        System.out.println(JSONUtil.toJsonStr(searchResponse.getHits()));
        for (SearchHit hit : searchResponse.getHits().getHits()) {
            System.out.println(hit.getSourceAsMap());
        }
    }
相当用java构建了

GET /cws_test1/_doc/_search
{
  "query":{
    "term":{#精确匹配
      "name":"ww"
    }
  }
}

image20221215143644540.png

实战(springboot2.2.6)

爬取京东数据,进行es实现练习。

依赖

<properties>
  <java.version>1.8</java.version>
  <elasticsearch.version>7.6.1</elasticsearch.version>
  </properties>
  <dependencies>
  <!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
  <groupId>org.jsoup</groupId>
  <artifactId>jsoup</artifactId>
  <version>1.11.3</version>
  </dependency>
​
​
  <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
  </dependency>
  <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
​
  <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-devtools</artifactId>
  <scope>runtime</scope>
  <optional>true</optional>
  </dependency>
  <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <optional>true</optional>
  </dependency>
  <dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <optional>true</optional>
  </dependency>
  <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
  </dependency>
​
  <dependency>
  <groupId>cn.hutool</groupId>
  <artifactId>hutool-all</artifactId>
  <version>5.7.17</version>
  </dependency>
​
  <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
  </dependency>
​
  </dependencies>

客户端工具类注意版本匹配

@Configuration
public class ElasticSearchClientConfig {

    @Bean
    public RestHighLevelClient restHighLevelClient(){
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("localhost", 9200, "http")
                )
        );
        return client;
    }
}

爬取数据

//爬取数据类
@Component
public class HtmlParseUtils {
    public List<Title> getDataJD(String keyWord) throws IOException {
        List<Title> list = new ArrayList<>();

        String url = "https://search.jd.com/Search?keyword=" + keyWord;

        //获取整个页面
        Document document = Jsoup.parse(new URL(url), 30000);
//        System.out.println(document);
        //选择所需要的部分
        Element goodsList = document.getElementById("J_goodsList");
//        获取所有的li
        Elements lis = goodsList.getElementsByTag("li");

        for (Element li : lis) {
//            获取图片
            String img = li.getElementsByTag("img").eq(0).attr("data-lazy-img");
//            获取价格
            String price = li.getElementsByClass("p-price").eq(0).text();
//  获取书名
            String bookName = li.getElementsByClass("p-name").eq(0).text();
//            System.out.println("------------------------------------------------------");
//            System.out.println(img);
//            System.out.println(price);
//            System.out.println(bookName);
            Title title = new Title(bookName, price, img);
            list.add(title);
        }
        return list;
    }
}

编写实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Title {

    private String name;

    private String price;

    private String imgUrl;
}

将数据添加到es

@Service
public class ContentService {

  @Autowired
  RestHighLevelClient restHighLevelClient;

  public String addEs(String keyword) throws IOException {
    List<Title> list = new HtmlParseUtils().getDataJD(keyword);
    BulkRequest bulkRequest = new BulkRequest();
    bulkRequest.timeout("10s");
    for (int i = 0; i < list.size(); i++) {
      bulkRequest.add(
        new IndexRequest("cws_jd")
        .source(JSONUtil.toJsonStr(list.get(i)), XContentType.JSON)
      );
    }
    BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
    boolean b = bulk.hasFailures();
    if (!b) {
      return "成功!";
    }
    return "失败";
  }
}

编写service搜索接口

p
ublic List<Map<String,Object>> search(String keyWord,int pageNum,int pageSize) throws IOException {
  if(pageNum==0){
    pageNum=1;
  }
  SearchRequest searchRequest = new SearchRequest();
  SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
  //设置分页
  sourceBuilder.from(pageNum);
  sourceBuilder.size(pageSize);
  //        构建查询条件
  TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("name", keyWord);
  sourceBuilder.query(termsQueryBuilder);
  searchRequest.source(sourceBuilder);
  SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  //        封装返回的数据
  List<Map<String,Object>> list=new ArrayList<>();
  for (SearchHit hit : search.getHits().getHits()) {
    Map<String, Object> sourceAsMap = hit.getSourceAsMap();
    list.add(sourceAsMap);
  }
  return list;
}

controller

@RestController
@SuppressWarnings("all")
public class ContentController {
    @Autowired
    ContentService contentService;
    @GetMapping("/Content/{keyWord}")
    public String setContent(@PathVariable("keyWord") String keyWord) throws IOException {
        return contentService.addEs(keyWord);
    }
   @GetMapping("/search/{keyWord}/{pageNum}/{pageSize}")
    public List<Map<String,Object>> searchKeyList(
           @PathVariable("keyWord") String keyWord,
           @PathVariable("pageNum") int pageNum,
           @PathVariable("pageSize") int pageSize
   ) throws IOException {
     return   contentService.search(keyWord,pageNum,pageSize);
   }
}

设置高亮

 public List<Map<String, Object>> searchHighlightFields(String keyWord, int pageNum, int pageSize) throws IOException {
        if (pageNum == 0) {
            pageNum = 1;
        }
        SearchRequest searchRequest = new SearchRequest();
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //设置分页
        sourceBuilder.from(pageNum);
        sourceBuilder.size(pageSize);
        //        构建查询条件
        TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("name", keyWord);
        //设置高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("name");//要高亮的字段名
        highlightBuilder.preTags("<span class='key' style='color:red'>");
        highlightBuilder.postTags("</span>");
        highlightBuilder.requireFieldMatch(false);//取消多个字段显示
        sourceBuilder.highlighter(highlightBuilder);

        sourceBuilder.query(termsQueryBuilder);
        searchRequest.source(sourceBuilder);
        SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//        封装返回的数据
        List<Map<String, Object>> list = new ArrayList<>();
        for (SearchHit hit : search.getHits().getHits()) {
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();//高亮的结果
            HighlightField name = highlightFields.get("name");//获取到高亮的字段
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();//原来的结果
            //如果name不等于空调换数据
            if (name != null) {

                Text[] fragments = name.fragments();
                String new_name = "";
                for (Text text : fragments) {
                    new_name += text;
                }
                sourceAsMap.put("name", new_name);
            }
            list.add(sourceAsMap);
        }
        return list;
    }

集成练习二

数据库结构:

CREATE TABLE `NewTable` (
`id`  bigint(20) NOT NULL COMMENT '酒店id' ,
`name`  varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店名称' ,
`address`  varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店地址' ,
`price`  int(10) NOT NULL COMMENT '酒店价格' ,
`score`  int(2) NOT NULL COMMENT '酒店评分' ,
`brand`  varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店品牌' ,
`city`  varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '所在城市' ,
`star_name`  varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '酒店星级,1星到5星,1钻到5钻' ,
`business`  varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商圈' ,
`latitude`  varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '纬度' ,
`longitude`  varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '经度' ,
`pic`  varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '酒店图片' ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8mb4_general_ci
ROW_FORMAT=COMPACT
;

更具数据库结构创建es的mapping

PUT /hotel
{
  "mappings": {
    "properties": {
      "id":{
        "type": "keyword" #在es中id比较特殊,不进行分词所以这样用keyword
      },
      "name":{
        "type": "text",
        "analyzer": "ik_max_word",
        "copy_to": "all"
      },
      "adderss":{
        "type": "text",
         "index": false
      },
      "price":{
        "type": "integer"
      },
      "score":{
        "type": "integer"
      },
      "brand":{
        "type": "keyword",
         "copy_to": "all"
      },
      "city":{
        "type": "keyword"
      },
      "starName":{
        "type": "keyword"
      },
      "business":{
        "type": "keyword"
        , "copy_to": "all"
      },
      "location":{
        "type": "geo_point"
      },
       "pic":{
        "type": "keyword",
       "index": false
      },
      "all":{
        "type": "text",
        "analyzer": "ik_max_word"
      }
    }
  }
}

注明:在上面结构中多出了location和all字段。

location字段主要用于处理地理坐标,那么在es中支持两种坐标数据类型:geo_point: 此类型用于存储地理位置点,通常由经度和纬度组成。一个 geo_point 字段可以以两种方式存储坐标: 作为数组 [lon, lat],其中 lon 是经度,lat 是纬度。作为对象 {"lat": lat, "lon": lon} 或 {"lat": lat, "lon": lon, "geohash": geohash},其中可选的 geohash 是对经纬度进行编码的字符串,便于区间查询。 geo_shape: 这种类型用于存储更复杂的地理形状,如多边形、圆形、线等。它支持对地理空间区域进行索引和查询,适用于处理区域覆盖、邻近判断等复杂的空间关系查询。

all字段用于搜索,在实现中我们想通过酒店名称、酒店品牌和商圈同时去搜索,那么通过单个字段是实现不了的,所以es提供了copy_to功能,将需要的字段全部拷贝到一个字段中,我们通过这个字段去进行检索即可。

接入RestClient

注意自己的es版本,我使用的是7.12.1

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

创建索引库

@SpringbootTest
public class RestClientTest {
    private RestHighLevelClient client;

    @Test
  void testCreateHotelIndex() throws IOException {
//  创建request对象
        CreateIndexRequest request = new CreateIndexRequest("hotel");
        request.source(HOTEL_TEMP, XContentType.JSON);
        client.indices().create(request, RequestOptions.DEFAULT);
    }

    @BeforeEach
    void setUp()
    {
         this.client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://192.168.88.102:9200")
        ));
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }
}
package cn.itcast.hotel.estemp;

public class IndexTemplate {

    public static final String HOTEL_TEMP = "{\n" +
            "  \"mappings\": {\n" +
            "    \"properties\": {\n" +
            "      \"id\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"name\":{\n" +
            "        \"type\": \"text\",\n" +
            "        \"analyzer\": \"ik_max_word\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"adderss\":{\n" +
            "        \"type\": \"text\",\n" +
            "         \"index\": false\n" +
            "      },\n" +
            "      \"price\":{\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"score\":{\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"brand\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "         \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"city\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"starName\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"business\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "        , \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"location\":{\n" +
            "        \"type\": \"geo_point\"\n" +
            "      },\n" +
            "       \"pic\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "       \"index\": false\n" +
            "      },\n" +
            "      \n" +
            "      \"all\":{\n" +
            "        \"type\": \"text\",\n" +
            "        \"analyzer\": \"ik_max_word\"\n" +
            "      }\n" +
            "    }\n" +
            "  }\n" +
            "}";
}

使用RestClient操作文档(CRUD)

添加文档

 @Test
    void setHotelOfDoc() throws IOException {
//          put /hotel/_doc/36934
        IndexRequest indexRequest = new IndexRequest("hotel");
        indexRequest.timeout("1s");//设置超时时间
        Hotel hotel = hotelService.getById(36934L);
        indexRequest.id(hotel.getId().toString());//设置id
        HotelDoc hotelDoc = new HotelDoc(hotel);
        indexRequest.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
        client.index(indexRequest, RequestOptions.DEFAULT);
    }

查询文档

 /**
     * 获取文档
     * @throws IOException
     */
    @Test
    void testGetHotelOfDoc() throws IOException {
//        get /hotel/_doc/36934
        GetRequest getRequest = new GetRequest();
        getRequest.index("hotel");
        getRequest.id("36934");
        GetResponse documentFields = client.get(getRequest, RequestOptions.DEFAULT);
        String sourceAsString = documentFields.getSourceAsString();
        System.out.println(sourceAsString);
    }

修改文档

/**
     * 修改文档
     * @throws IOException
     */
    @Test
    void testUpdateHotelOfDoc() throws IOException {
        UpdateRequest updateRequest = new UpdateRequest();
        updateRequest.index("hotel");
        updateRequest.id("36934");
        updateRequest.doc("price", 200,
                "brand","8天"
                );
        client.update(updateRequest, RequestOptions.DEFAULT);
    }
//这里是用的部分修改,其实也可以使用client.index()进行修改

删除文档

  /**
     * 删除文档
     * @throws IOException
     */
    @Test
    void testDelHotelOfDoc() throws IOException {
        DeleteRequest deleteRequest = new DeleteRequest();
        deleteRequest.id("36934");
        deleteRequest.index("hotel");
        client.delete(deleteRequest, RequestOptions.DEFAULT);
    }

批量导入文档

@Test
    void addListOfDocument() throws IOException {
        //批量api
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("10s");
        List<HotelDoc> list = hotelService.list().stream().map(hotel -> {
            HotelDoc hotelDoc = new HotelDoc(hotel);
            return hotelDoc;
        }).collect(Collectors.toList());
        for (int i = 0; i < list.size(); i++) {
            bulkRequest.add(
                    new IndexRequest("hotel")
                            .id(list.get(i).getId().toString())
                            .source(JSON.toJSONString(list.get(i)), XContentType.JSON)
            );
        }
        BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
        System.out.println(bulk.hasFailures());//是否出现异常? false表示没有出现(成功),
        System.out.println(bulk.status());//状态
    }

DSL查询语法

ElasticSearch提供了基于JSON来定义的查询。常见包括:

查询所有

:查询出所有数据,一般测试用。例如:match_all

查询所有

get /hotel/_search
{
  "query":{
    "match_all": {}
  }
}
全文检索(fuul text) 查询

利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:

  • match_query

  • multi_match_query

全文检索查询

#推荐,上面我们将字段全部copy到了all这个字段中,所以两个查询是一样的,但是效率不同
get /hotel/_search
{
  "query":{
    "match": {
      "all": "如家北京"
    }
  }
}
#效率低字段较多
get /hotel/_search
{
  "query":{
    "multi_match": {
      "query": "如家北京",
      "fields": ["name","brand","business"]
    }
  }
}
精确查询

:根据精确词条值查找数据,一般是查找keyword、数值、boolean等类型的字段。例如:

  • ids

  • range

  • term

精确查询

#查询为city为北京的酒店
get /hotel/_search
{
  "query":{
    "term": {
      "city": {
        "value": "北京"
      }
    }
  }
}
# 查询price在大于等于100小于等于2000
get /hotel/_search
{
  "query":{
    "range": {
      "price": {
        "gte": 100,
        "lte": 2000
      }
    }
  }
}
地理(geo)查询

:根据精确维度查询。例如:

  • geo_distance

  • geo_bounding_box

get /hotel/_search
{
  "query":{
    "geo_distance":{
      "distance":"3km", #范围
      "location":"31.21,121.5" #自己所在位置的地理坐标
    }
  }
}
复合查询

:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:

  • bool

  • function_score

function_score查询

应用场景:例如百度为什么查询企业出来的这个企业会排第一条呢?首先一定匹配查询的结果,然后有些企业花钱让你查的数据的score的评分较高。

案例:

get /hotel/_search
{
  "query":{
    "function_score": {
      "query": {
        "match": {
          "all": "外滩"
        }
      }
    }
    }
}

此时我们想让最后这家的排名靠前就可以如下操作:

get /hotel/_search
{
  "query":{
    "function_score": {
      "query": {
        "match": {
          "all": "外滩"
        }
      },
      "functions": [
        {
          "filter": {
            "term": {
              "brand": "如家"
            }
          },
          "weight": 10
        }
      ]
    }
    }
}

默认是乘于weight,那么就是38,还可以通过boost_mode控制:

get /hotel/_search
{
  "query":{
    "function_score": {
      "query": {
        "match": {
          "all": "外滩"
        }
      },
      "functions": [
        {
          "filter": {
            "term": {
              "brand": "如家"
            }
          },
          "weight": 10
        }
      ],
      "boost_mode": "sum" #这里是相加,可以有很多参数,可见上图
    }
    }
}
bool查询

布尔查询是一个或者多个查询子句的组合。方式有:

  • must:必须匹配每个子查询,类似“与”

  • should:选择性匹配子查询,类型“或”

  • must_not:必须不匹配,不参与算分,类"非"

  • filter:必须匹配,不参与算分

案例

#查询名称包含如家,价格不高于400,在坐标31.21,121.5周围10km范围内的酒店。
get /hotel/_search
{
  "query":{
    "bool": {
      "must": [
        {
          "match": {
            "name":"如家"
          }
        }
      ],
     "must_not": [
       {"range": {
         "price": {
           "gt": 400
         }
       }}
     ],
     "filter": [
       {
         "geo_distance": {
           "distance": "10km",
           "location": "31.21,121.5"
         }
       }
     ]
    }
  }
}
排序

例子:在我们选择酒店是我们可以看到酒店可我们的距离,以升序排,我们就可以在es中使用sort进行操作。

get /hotel/_search
{
  "query":{
    "match_all": {}
  },
  "sort": [
{
 "_geo_distance":{
   "location":{
     "lat":31.034, #纬度
     "lon":121.612 #经度
   },
   "order":"asc",
   "unit":"km"
 }
}
    ]
}

使用RestClient操作DSL

RestClient查询所有
get /hotel/_search
{
    "query":{
      "match_all": {}
    },
    "from":1,
    "size":50
}


  /**
     * 查询所有数据
     * @throws IOException
     */
    @Test
    void testSearchAll() throws IOException {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备请求参数
        request.source().query(QueryBuilders.matchAllQuery()).size(50);
        search(request);
    }

    private void search(SearchRequest request) throws IOException {
        // 3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.处理响应
        SearchHits hits = response.getHits();
        long value = hits.getTotalHits().value;
        System.out.println(value);
        SearchHit[] hits1 = hits.getHits();
        for (SearchHit hit : hits1) {
            HotelDoc hotelDoc = JSON.parseObject(hit.getSourceAsString(), HotelDoc.class);
            System.out.println(hotelDoc);
        }
    }
RestClient全文检索
/**
     * 全文检索 matchQuery
     * @param
     * @throws IOException
     get /hotel/_search
{
  "query":{
    "match": {
      "all": "如家"
    }
  }
}
     */
    @Test
    void testMatchQuery() throws IOException {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备请求参数
        request.source().query(QueryBuilders.matchQuery("all", "如家"));
        search(request);
    }


    /**
     * 全文检索 multi_match_query
     * @param
     * @throws IOException
     
     get /hotel/_search
{
  "query":{
    "multi_match": {
      "query": "如家",
      "fields": ["name","brand"]
    }
  }
}
     */
    @Test
    void testMultiMatchQueryQuery() throws IOException {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备请求参数
        request.source().query(QueryBuilders.multiMatchQuery("如家", "name", "brand"));
        search(request);
    }
RestClient精确查询(term,range)
 /**
     * 精确查询
     * @param
     * @throws IOException
     */
    @Test
    void testTermQuery() throws IOException {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备请求参数
//        request.source().query(QueryBuilders.termQuery("city", "上海"));
        request.source().query(QueryBuilders.rangeQuery("price").lt(300));
        search(request);
    }
RestClient地理查询
 /**
     * 精确查询
     * @param
     * @throws IOException
     */
    @Test
    void testGeoDistanceQuery() throws IOException {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备请求参数
        request.source().query(QueryBuilders.geoDistanceQuery("location").distance("50km").point(31.197804,121.498618));
        search(request);
    }
RestClient复合查询
/**
     * 复合查询
     * @param
     * @throws IOException
     */
    @Test
    void testBoolQueryBuilder() throws IOException {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        boolQuery.must(QueryBuilders.termQuery("city", "上海"));
        boolQuery.filter(QueryBuilders.rangeQuery("price").lte(200));
        request.source().query(boolQuery);
        search(request);
    }
RestClient排序与分页
  /**
     * 排序与分页
     * @param
     * @throws IOException
     */
    @Test
    void testSortAndPage() throws IOException {
        // 1.准备Request
        SearchRequest searchRequest = new SearchRequest("hotel");
        searchRequest.source().query(QueryBuilders.matchAllQuery()).from(1).size(10)
                .sort("price", SortOrder.DESC);
        search(searchRequest);
    }
RestClient(词语高亮)

在默认情况下检索字段需要和高亮字段一致,如果碰到特殊情况可以使用require_field_match改为false

 /**
     * 代码高亮
     * @param
     * @throws IOException
     */
    @Test
    void testHighlighter() throws IOException {
        // 1.准备Request
        SearchRequest searchRequest = new SearchRequest("hotel");
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("name");
        highlightBuilder.preTags("<font color='red'>");
        highlightBuilder.postTags("</font>");
        highlightBuilder.requireFieldMatch(false);
        searchRequest.source().query(QueryBuilders.matchQuery("all", "如家")).highlighter(highlightBuilder);
        SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
        search.getHits().forEach(hit -> {
            System.out.println(hit.getHighlightFields());
        });
    }

聚合

聚合可以实现对文档数据的统计、分析、运算。常见的三类:

桶(Bucket)聚合:用来对文档做分组

  • TermAggregation:按照文档字段值分组

  • Date Histogram:按照日期阶梯,例如一周为一组或者一月为一组

度量(Metric)聚合:用以计算一些值、比如:最大值、最小值、平均值等

  • Avg:求平均值

  • Max:求最大值

  • Min:最小值

  • Stats:同时求max、min、avg、sum等

管道(pipeline)聚合:其他聚合的结果为基础做聚合

桶聚合Bucket

get  /hotel/_search
{
  "size":0,
  "aggs":{
    "brandAgg":{ #设置聚合名称
      "terms": {
        "field": "brand", #选择聚合字段
        "size": 20 #页数
      }
    }
  }
}
#类似mysql group by

在上图中我们可以看出分组对的文档数量是按照降序排序的,我们可以进行修改:

get  /hotel/_search
{
  "size":0,
  "aggs":{
    "brandAgg":{
      "terms": {
        "order": {
          "_count": "asc"  #更具count数量升序排
        }, 
        "field": "brand",
        "size": 20
      }
    }
  }
}

我可也可以加上query条件,锁定范围:

get  /hotel/_search
{
  "query":{
    "range": {
      "price": {
        "lte": 200
      }
    }
  },
  "size":0,
  "aggs":{
    "brandAgg":{
      "terms": {
        "order": {
          "_count": "asc"
        }, 
        "field": "brand",
        "size": 20
      }
    }
  }
}

度量(Metric)聚合

#查询每种牌子的酒店的min、max、avg,并通过平均分排序
GET /hotel/_search
{
  "size": 0,
  "aggs": {
    "brandAgg": { #结合bucket聚合
      "terms": {
        "order": { 
          "scoreAgg.avg": "asc" 
        }, 
        "field": "brand",
        "size": 20
      },
      "aggs": {
        "scoreAgg": { #Metric自定义名称
          "stats": { #这个的stats就包含了min、max、avg
            "field": "score" #Metric聚合字段
          }
        }
      }
    }
  }
}
#类似数据库的 先分组然后再求聚合

RestClient操作聚合

   /**
     * 聚合操作
     */
    @Test
    void testAggregation() throws IOException {
        SearchRequest request = new SearchRequest("hotel");
//        不需要具体内容
        request.source().size(0);
        request.source().aggregation(
                AggregationBuilders.terms("brandAgg")
                        .field("brand")
                        .size(20)
                        .order(BucketOrder.aggregation("_count", false))
        );
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        Aggregations aggregations = response.getAggregations();
        Terms brandAgg = aggregations.get("brandAgg");
        List<? extends Terms.Bucket> buckets = brandAgg.getBuckets();
        buckets.forEach(bucket -> {
            String keyAsString = bucket.getKeyAsString();
            System.out.println(keyAsString);
        });
    }

自动补全

要实现根据字母做补全,就必须对文档按照拼音分词。在GitHub上恰好有elasticsearch的拼音分词插件。地址:GitHub - infinilabs/analysis-pinyin: 🛵 This Pinyin Analysis plugin is used to do conversion between Chinese characters and Pinyin.

在京东等平台搜索框输入拼音或者关键子会有提示效果那么大多数都是基于es的自动补全实现的。这里需要拼音分词器插件:

拼音分词器安装

将py文件夹上传到es映射的目录下和ik分词器一样的方法。也可以自己解压elasticsearch-analysis-pinyin-7.12.1.zip命名为py。

测试:

自定义分词器

为什么要自定义分词器呢?如果单独使用ik或者拼音分词器并不能满足我们的需求,例如我使用拼音分词器那么结果只有拼音结果没有中文结果且拼音首字母拼接,这样无法满足我们的搜索。

自定义使用

主要:需要在创建索引时指定

PUT /test
{
  "settings": {
    "analysis": {
      "analyzer": { 
        "my_analyzer": { 
          "tokenizer": "ik_max_word",
          "filter": "py"
        }
      },
      "filter": {
        "py": { 
          "type": "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": {
      "title":{ #指定字段
        "type":"text",
        "analyzer": "my_analyzer", #调用自定义分词器
         "search_analyzer": "ik_smart" #搜索时使用 ik
      }
    }
  }
}

解释:

PUT /test 这是一个HTTP的PUT请求,目标是Elasticsearch中的一个索引,这里索引名为test。这意味着你正在更新或创建名为test的索引,并为其指定特定的设置。 请求体内容详解 settings:这部分包含索引的配置信息,重点在于定义分析器设置。 analysis:分析模块的配置,用于定义如何对文本进行分析处理,包括分词、过滤等。 analyzer: "my_analyzer" 定义了一个名为my_analyzer的自定义分析器。分析器决定了如何将文本分解成词语(tokens)以便于搜索和索引。 tokenizer: "ik_max_word" 指定了分析器使用的分词器为ik_max_word。IK分词器是针对中文文本设计的,ik_max_word模式会尽可能地进行词语切分,生成最细粒度的词语组合。 filter: "py" 定义了一个名为py的过滤器,该过滤器应用于分词结果。这里的py是一个拼音过滤器,用于生成中文字符的拼音表示。 py过滤器的参数说明: keep_full_pinyin: 设为false表示不保留全拼形式。 keep_joined_full_pinyin: 设为true表示保留连接的全拼音形式,比如"zhongguo"。 keep_original: 设为true表示保留原始的中文字符。 limit_first_letter_length: 设置首字母拼音的最大长度为16,超过的会被截断。 remove_duplicated_term: 设为true表示移除重复的拼音项,避免索引膨胀。 none_chinese_pinyin_tokenize: 设为false表示不对非中文字符进行拼音处理。

测试

POST /test/_analyze
{
 "analyzer": "my_analyzer",
 "text": ["应该出现张三的拼音"]
}

自动补全类型及查询功能(Completion Suggester)

官网地址:Suggesters | Elasticsearch Guide [7.6] | Elastic

elasticsearch提供了Completion Suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询的效率,对于文档中字段的类型有一些约束:参与补全查询的字段必须是completion类型。字段的内容一般是用来补全的多个词条形成的数组。

案例

// 自动补全的索引库
PUT test2
{
  "mappings": {
    "properties": {
      "title":{
        "type": "completion"
      }
    }
  }
}
// 示例数据
POST test2/_doc
{
  "title": ["Sony", "WH-1000XM3"]
}
POST test2/_doc
{
  "title": ["SK-II", "PITERA"]
}
POST test2/_doc
{
  "title": ["Nintendo", "switch"]
}

// 自动补全查询
POST /test2/_search
{
  "suggest": {
    "title_suggest": {
       "text": "s", // 关键字
      "completion": {
        "field": "title", // 补全字段
        "skip_duplicates": true, // 跳过重复的
        "size": 10 // 获取前10条结果
      }
    }
  }
}

注意:自动补全的类型必须是completion

综合案例

我们基于集成练习二里面的数据库结构做个自动补全的综合案例。

1.修改索引

// 创建名为"hotel"的Elasticsearch索引,并定义其设置和映射规则。
PUT /hotel
{
  "settings": {
    "analysis": { // 分析器与过滤器配置区域
      "analyzer": { // 自定义分析器定义
        "text_anlyzer": { // 用于全文本分析的分析器
          "tokenizer": "ik_max_word", // 使用IK分词器的最细粒度分词模式
          "filter": "py" // 应用自定义的拼音过滤器
        },
        "completion_analyzer": { // 专用于自动补全字段的分析器
          "tokenizer": "keyword", // 使用keyword分词器,保持整个输入作为单个token
          "filter": "py" // 同样应用拼音过滤器以支持拼音补全
        }
      },
      "filter": { // 自定义过滤器定义
        "py": { // 拼音过滤器配置
          "type": "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": { // 具体字段定义
      "id": { // 唯一标识符
        "type": "keyword" // 关键词类型,适合唯一ID
      },
      "name": { // 酒店名称
        "type": "text", // 文本类型,用于全文搜索
        "analyzer": "text_anlyzer", // 搜索时使用自定义的text_anlyzer
        "search_analyzer": "ik_smart", // 查询时使用ik_smart分词器,支持智能分词
        "copy_to": "all" // 将此字段内容复制到"all"字段,便于综合搜索
      },
      "address": { // 地址信息
        "type": "keyword", // 作为关键词存储,不参与全文搜索
        "index": false // 不建立索引,提高写入性能
      },
      "price": { // 价格
        "type": "integer" // 整型数据
      },
      "score": { // 评分
        "type": "integer"
      },
      "brand": { // 品牌
        "type": "keyword",
        "copy_to": "all" // 同样复制到"all"字段
      },
      "city": { // 所在城市
        "type": "keyword"
      },
      "starName": { // 星级名称
        "type": "keyword"
      },
      "business": { // 商圈
        "type": "keyword",
        "copy_to": "all"
      },
      "location": { // 地理位置坐标
        "type": "geo_point"
      },
      "pic": { // 图片链接
        "type": "keyword",
        "index": false // 图片链接不需要搜索,故不建立索引
      },
      "all": { // 综合全文搜索字段
        "type": "text",
        "analyzer": "text_anlyzer",
        "search_analyzer": "ik_smart"
      },
      "suggestion": { // 自动补全字段
          "type": "completion", // 使用completion类型
          "analyzer": "completion_analyzer" // 应用专为补全设计的分析器
      }
    }
  }
}

2.修改构造器

@Data
@NoArgsConstructor
public class HotelDoc {
    private Long id;
    private String name;
    private String address;
    private Integer price;
    private Integer score;
    private String brand;
    private String city;
    private String starName;
    private String business;
    private String location;
    private String pic;
    private Object distance;
    private Boolean isAD;
    private List<String> suggestion;

    public HotelDoc(Hotel hotel) {
        this.id = hotel.getId();
        this.name = hotel.getName();
        this.address = hotel.getAddress();
        this.price = hotel.getPrice();
        this.score = hotel.getScore();
        this.brand = hotel.getBrand();
        this.city = hotel.getCity();
        this.starName = hotel.getStarName();
        this.business = hotel.getBusiness();
        this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
        this.pic = hotel.getPic();
        //这里是应该我们是数据中有/我们做了下处理,这里主要将brand和business加到自动补全自动中
        if(this.business.contains("/")){
            String[] arr = this.business.split("/");
            this.suggestion=new ArrayList<>();
            this.suggestion.add(this.brand);
            Collections.addAll(this.suggestion, arr);
        }else{
            this.suggestion= Arrays.asList(this.brand,this.business);
        }
    }
}

3.重新同步数据库数据

 @Test
    void addListOfDocument() throws IOException {
        //批量api
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("10s");
        List<HotelDoc> list = hotelService.list().stream().map(hotel -> {
            HotelDoc hotelDoc = new HotelDoc(hotel);
            return hotelDoc;
        }).collect(Collectors.toList());
        for (int i = 0; i < list.size(); i++) {
            bulkRequest.add(
                    new IndexRequest("hotel")
                            .id(list.get(i).getId().toString())
                            .source(JSON.toJSONString(list.get(i)), XContentType.JSON)
            );
        }
        BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
        System.out.println(bulk.hasFailures());//是否出现异常? false表示没有出现(成功),
        System.out.println(bulk.status());//状态
    }

 

4.测试

POST /hotel/_search
{
  "suggest": {
    "title_suggest": {
       "text": "w",
      "completion": {
        "field": "suggestion", 
        "skip_duplicates": true, 
        "size": 10 
      }
    }
  }
}

 

4.RestCline操作自动补全

/**
 * 自动补全
 */
@Test
void testSuggest() throws IOException {
    SearchRequest request = new SearchRequest("hotel");
    request.source().suggest(
            new SuggestBuilder().addSuggestion(
                    "MySuggestion",
                    SuggestBuilders.completionSuggestion("suggestion")
                            .prefix("h")
                            .skipDuplicates(true)
                            .size(10)
            )
    );
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    Suggest suggest = response.getSuggest();
    Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestion = suggest.getSuggestion("MySuggestion");
    suggestion.forEach(entry -> {
        entry.getOptions().forEach(option -> {
            String text = option.getText().string();
            System.out.println(text);
        });
    });
}

微服务ES数据同步

方案一

 

缺陷:冗余,无法保证一定成功,耦合性差,性能差。

方案二

这里我们使用方案二实现

@Component
public class MqGeneratorOfEsData {
    @Resource
    private RabbitTemplate rabbitTemplate;
    public void testSendMessageTopicQueue(String exchange, String routingKey, String message)  {
        rabbitTemplate.convertAndSend(exchange,routingKey,message);
    }
}
//后台crud是发送消息
​
    @PostMapping
    public void saveHotel(@RequestBody Hotel hotel){
        hotelService.save(hotel);
        mqGeneratorOfEsData.testSendMessageTopicQueue("hotel.topic", "hotel.addOrUpdate", hotel.getId().toString());
    }
​
    @PutMapping()
    public void updateById(@RequestBody Hotel hotel){
        if (hotel.getId() == null) {
            throw new InvalidParameterException("id不能为空");
        }
        hotelService.updateById(hotel);
        mqGeneratorOfEsData.testSendMessageTopicQueue("hotel.topic", "hotel.addOrUpdate", hotel.getId().toString());
    }
​
    @DeleteMapping("/{id}")
    public void deleteById(@PathVariable("id") Long id) {
        hotelService.removeById(id);
        mqGeneratorOfEsData.testSendMessageTopicQueue("hotel.topic", "hotel.del", id.toString());
    }
//es服务去处理
@Component
public class MqListenerEsData {
    @Resource
    private RabbitTemplate rabbitTemplate;
    @Resource
    private RestHighLevelClient client;
    @Resource
    IHotelService iHotelService;
    /**
     *  监听新增或者修改
     * @param msg
     * @throws InterruptedException
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "hotel.addOrUpdate"),
            exchange = @Exchange(name = "hotel.topic", type = ExchangeTypes.TOPIC),
            key = {"hotel.addOrUpdate"}
    ))
    public void listenTopicQueue1(String msg) throws InterruptedException {
        try {
            System.out.println("hotel.addOrUpdate接收到消息:" + msg);
            Long id = Long.valueOf(msg);
            Hotel hotel = iHotelService.getById(id);
            HotelDoc hotelDoc = new HotelDoc(hotel);
            IndexRequest indexRequest = new IndexRequest("hotel");
            indexRequest.timeout("1s");//设置超时时间
            indexRequest.id(hotel.getId().toString());//设置id
            indexRequest.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
            IndexResponse index = client.index(indexRequest, RequestOptions.DEFAULT);
            if (index.getResult().name().equals("CREATED")) {
                System.out.println("新增成功");
            } else if (index.getResult().name().equals("UPDATED")) {
                System.out.println("修改成功");
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("保存数据失败");
        }
    }
    /**
     *  删除
     * @param id
     * @throws InterruptedException
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "hotel.del"),
            exchange = @Exchange(name = "hotel.topic", type = ExchangeTypes.TOPIC),
            key = {"hotel.del"}
    ))
    public void listenTopicQueue2(String id) throws InterruptedException {
        try {
            DeleteRequest deleteRequest = new DeleteRequest();
            deleteRequest.id(id);
            deleteRequest.index("hotel");
            client.delete(deleteRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("删除失败");
        }
    }
}

ES集群

因为设备原因这里我们在单台机器上搭建集群。

首先编写一个docker-compose文件,内容如下:

version: '2.2'
services:
  es01:
    image: 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
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    volumes:
      - data01:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
    networks:
      - elastic
  es02:
    image: 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
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    volumes:
      - data02:/usr/share/elasticsearch/data
    ports:
      - 9201:9200
    networks:
      - elastic
  es03:
    image: 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
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    volumes:
      - data03:/usr/share/elasticsearch/data
    networks:
      - elastic
    ports:
      - 9202:9200
volumes:
  data01:
    driver: local
  data02:
    driver: local
  data03:
    driver: local
​
networks:
  elastic:
    driver: bridge

es运行需要修改一些linux系统权限,修改/etc/sysctl.conf文件

vi /etc/sysctl.conf

添加下面的内容:

vm.max_map_count=262144

然后执行命令,让配置生效:

sysctl -p

通过docker-compose启动集群:

docker-compose up -d

集群状态监控

kibana可以监控es集群,不过新版本需要依赖es的x-pack 功能,配置比较复杂。

这里推荐使用cerebro来监控es集群状态,官方网址:https://github.com/lmenezes/cerebro

解压后进入bin目录双击cerebro.bat即可,访问http://localhost:9000/#!/connect即可。

 

输入你的elasticsearch的任意节点的地址和端口,点击connect即可:

绿色的条,代表集群处于绿色(健康状态)。

分片

为了数据的原子性,我们把各个es的数据存放在不用的es上例如:es01可以存放es02的数据,当es02宕机了那么额可以利用es01这台恢复数据。

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/768448.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

1000T的文件怎么能快速从南京传到北京?最佳方案你肯定想不到

今天刷面试题看到一个有意思的面试题&#xff0c; 1000T的文件怎么能以最快速度从南京传到北京&#xff1f; 网络传输 首先我们考虑通过网络传输&#xff0c;需要多长时间。 我特地咨询了在运营商工作的同学&#xff0c;目前带宽&#xff1a; 家庭宽带下行最大1Gbps&#…

双指针系列第 8 篇:盛水最多的容器。几句话讲明白!

Leetcode 题目链接 思路 取首尾双指针和水量如下所示&#xff0c;设高度函数为 h ( i ) h(i) h(i)&#xff0c;在下图中 h ( l ) < h ( r ) h(l) < h(r) h(l)<h(r)。 观察以 l l l 为左边界所能构成的其他水量&#xff0c;与矮的右边界搭配结果如下。 与高的…

每日两题 / 20. 有效的括号 155. 最小栈(LeetCode热题100)

20. 有效的括号 - 力扣&#xff08;LeetCode&#xff09; 遇到左括号入栈 遇到右括号判断栈顶是否为匹配的左括号 最后判断栈是否为空 func isValid(s string) bool {var stk []runefor _, value : range s {if value ( || value { || value [ {stk append(stk, value)}…

计算机操作系统部分选填及大题整理

并发和&#xff08; 共享 &#xff09; 是操作系统的两个最基本的特征,&#xff08; 虚拟 &#xff09;和&#xff08; 异步 &#xff09; 是操作系统的重要特征&#xff0c;并发执行的程序失去可再现性现代操作系统的两个基本特征是&#xff08;程序的并发执行&#xff09;和资…

Docker 部署 Minio 对象存储服务器

文章目录 Github官网文档简介dockerdocker-compose.ymlmc 客户端mc 基础命令Golang 示例创建 test 账号密钥文件上传示例 Github https://github.com/minio/minio 官网 https://min.io/https://www.minio.org.cn/ 文档 https://www.minio.org.cn/docs/minio/kubernetes/up…

1.4 ROS2集成开发环境搭建

1.4.1 安装VSCode VSCode全称Visual Studio Code&#xff0c;是微软推出的一款轻量级代码编辑器&#xff0c;免费、开源而且功能强大。它支持几乎所有主流的程序语言的语法高亮、智能代码补全、自定义热键、括号匹配、代码片段、代码对比Diff、GIT 等特性&#xff0c;支持插件…

上位机第二弹

之前写的代码用上了 现在想想 &#xff0c;北向一侧还挺难搞&#xff0c;设计很巧妙

10 Posix API与网络协议栈

POSIX概念 POSIX是由IEEE指定的一系列标准,用于澄清和统一Unix-y操作系统提供的应用程序编程接口(以及辅助问题,如命令行shell实用程序),当您编写程序以依赖POSIX标准时,您可以非常肯定能够轻松地将它们移植到大量的Unix衍生产品系列中(包括Linux,但不限于此!)。 如…

使用pyinstaller 如何打包python项目

参考&#xff1a;【python项目正确打包方法-哔哩哔哩】 https://b23.tv/EDB6zbG Pyinstaller 详解多种打包过程(去坑,填坑)。_pyinstaller -f -w-CSDN博客 1.打开命令提示符&#xff1a; 找到python项目所在位置&#xff0c;输入cmd即可 2. 安装pipenv: 在命令提示符&#…

【Linux】多线程(一万六千字)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 文章目录 前言 线程的概念 线程的理解(Linux系统为例) 在Linux系统里如何保证让正文部分的代码可以并发的去跑呢&#xff1f; 为什么要有多进程呢&#xff1f; 为…

CVD-Risk-Prevent 个性化心血管健康推荐系统:基于医学指南的规则框架与 LLM 的结合

CVD-Risk-Prevent 个性化心血管健康推荐系统&#xff1a;基于医学指南的规则框架与 LLM 的结合 提出背景推荐算法的选择选择疑问健康指标管理心血管风险因素目标设定实现目标的计划推荐的多维性 算法关键点&#xff1a;如何将心血管健康指标转换为多维推荐&#xff1f;确定风险…

antfu/ni 在 Windows 下的安装

问题 全局安装 ni 之后&#xff0c;第一次使用会有这个问题 解决 在 powershell 中输入 Remove-Item Alias:ni -Force -ErrorAction Ignore之后再次运行 ni Windows 11 下的 Powershell 环境配置 可以参考 https://github.com/antfu-collective/ni?tabreadme-ov-file#how …

【操作系统】进程管理——调度基础(个人笔记)

学习日期&#xff1a;2024.7.3 内容摘要&#xff1a;调度的概念、层次&#xff0c;进程调度的时机&#xff0c;调度器和闲逛进程&#xff0c;调度算法的评价指标 调度的基本概念 有一堆任务需要处理&#xff0c;但由于资源有限&#xff0c;有的事情不能同时处理&#xff0c;这…

Django学习第三天

python manage.py runserver 使用以上的命令启动项目 实现新建用户数据功能 views.py文件代码 from django.shortcuts import render, redirect from app01 import models# Create your views here. def depart_list(request):""" 部门列表 ""&qu…

什么牌子的充电宝最好耐用?多款热门无线磁吸充电宝推荐

在现代生活中&#xff0c;手机、平板等电子设备已成为我们日常工作的必需品&#xff0c;而充电宝则是这些设备的续航神器&#xff01;无论是长途旅行、外出办公&#xff0c;还是日常通勤&#xff0c;一个耐用且高效的充电宝都是必不可少的选择。然而&#xff0c;市场上充电宝品…

如何选择适合自己的虚拟化技术?

虚拟化技术已成为现代数据中心和云计算环境的核心组成部分。本文将帮助您了解如何选择适合自己需求的虚拟化技术&#xff0c;以实现更高的效率、资源利用率和灵活性。 理解虚拟化技术 首先&#xff0c;让我们了解虚拟化技术的基本概念。虚拟化允许将一个物理服务器划分为多个虚…

探讨命令模式及其应用

目录 命令模式命令模式结构命令模式适用场景命令模式优缺点练手题目题目描述输入描述输出描述题解 命令模式 命令模式是一种行为设计模式&#xff0c; 它可将请求转换为一个包含与请求相关的所有信息的独立对象。 该转换让你能根据不同的请求将方法参数化、 延迟请求执行或将其…

玩玩快速冥(LeetCode50题与70题以及联系斐波那契)

一.算法快速幂 今天刷到两个题,比较有意思,还是记录一下. 先来讲讲50题. LeetCode50(Pow(x,n)) 实现 pow(x, n) &#xff0c;即计算 x 的整数 n 次幂函数&#xff08;即&#xff0c;xn &#xff09;。 这道题一看很平常啊,不就一直乘嘛,循环走一次就够了.但是很抱歉,单纯的想…

ArcTs布局入门04——相对布局 媒体查询

如果你也对鸿蒙开发感兴趣&#xff0c;加入“Harmony自习室”吧 扫描下面的二维码关注公众号。 本文将探讨相对布局与媒体查询&#xff0c;为啥把他们放到一起呢&#xff1f;主要是因为相对布局在响应式的场景下做得不太好&#xff0c;一般情况下和媒体查询&#xff08;不同尺…

移动智能终端数据安全管理方案

随着信息技术的飞速发展&#xff0c;移动设备已成为企业日常运营不可或缺的工具。特别是随着智能手机和平板电脑等移动设备的普及&#xff0c;这些设备存储了大量的个人和敏感数据&#xff0c;如银行信息、电子邮件等。员工通过智能手机和平板电脑访问企业资源&#xff0c;提高…