当前位置: 首页 > news >正文

精品课程教学网站网站建设肆金手指排名

精品课程教学网站,网站建设肆金手指排名,宣传类的网站有哪些内容,网站后台管理教程文章目录 初识elasticsearch了解ES倒排索引正向索引倒排索引正向和倒排 es的一些概念文档和字段索引和映射mysql与elasticsearch 安装ES部署kibana安装IK分词器扩展词词典停用词词典 索引库操作mapping映射属性索引库的CRUD创建索引库和映射查询索引库修改索引库删除索引库 文档… 文章目录 初识elasticsearch了解ES倒排索引正向索引倒排索引正向和倒排 es的一些概念文档和字段索引和映射mysql与elasticsearch 安装ES部署kibana安装IK分词器扩展词词典停用词词典 索引库操作mapping映射属性索引库的CRUD创建索引库和映射查询索引库修改索引库删除索引库 文档操作 RestClient操作索引库导入Demo工程创建索引库删除索引库判断索引库是否存在 RestClient操作文档新增文档查询文档修改文档删除文档批量导入文档 DSL查询文档DSL查询分类全文检索查询精准查询理坐标查询复合查询 搜索结果处理排序分页高亮 RestClient查询文档快速入门match查询精确查询复合查询排序、分页高亮 数据聚合DSL实现聚合RestAPI实现聚合 自动补全拼音分词器自定义分词器自动补全查询实现酒店搜索框自动补全 数据同步思路分析实现数据同步 ES集群搭建集群集群脑裂问题集群分布式存储集群分布式查询集群故障转移 初识elasticsearch 了解ES elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎具备非常多强大功能可以帮助我们从海量数据中快速找到需要的内容 例如 在GitHub搜索代码 在电商网站搜索商品 在百度搜索答案 ELK技术栈 elasticsearch结合kibana、Logstash、Beats也就是elastic stackELK。被广泛应用在日志数据分析、实时监控等领域 而elasticsearch是elastic stack的核心负责存储、搜索、分析数据 elasticsearch和lucene elasticsearch底层是基于lucene来实现的 Lucene是一个Java语言的搜索引擎类库是Apache公司的顶级项目由DougCutting于1999年研发 elasticsearch的发展历史 2004年Shay Banon基于Lucene开发了Compass2010年Shay Banon 重写了Compass取名为Elasticsearch 为什么不是其他搜索技术 目前比较知名的搜索引擎技术排名 虽然在早期Apache Solr是最主要的搜索引擎技术但随着发展elasticsearch已经渐渐超越了Solr独占鳌头 倒排索引 倒排索引的概念是基于MySQL这样的正向索引而言的 正向索引 那么什么是正向索引呢例如给下表tb_goods中的id创建索引 如果是根据id查询那么直接走索引查询速度非常快 但如果是基于title做模糊查询只能是逐行扫描数据流程如下 用户搜索数据条件是title符合%手机% 逐行获取数据比如id为1的数据 判断数据中的title是否符合用户搜索条件 如果符合则放入结果集不符合则丢弃。回到步骤1 逐行扫描也就是全表扫描随着数据量增加其查询效率也会越来越低。当数据量达到数百万时就是一场灾难 倒排索引 倒排索引中有两个非常重要的概念 文档Document用来搜索的数据其中的每一条数据就是一个文档。例如一个网页、一个商品信息词条Term对文档数据或用户搜索数据利用某种算法分词得到的具备含义的词语就是词条。例如我是中国人就可以分为我、是、中国人、中国、国人这样的几个词条 创建倒排索引是对正向索引的一种特殊处理流程如下 将每一个文档的数据利用算法分词得到一个个词条创建表每行数据包括词条、词条所在文档id、位置等信息因为词条唯一性可以给词条创建索引例如hash表结构索引 如图 倒排索引的搜索流程如下以搜索华为手机为例 用户输入条件华为手机进行搜索对用户输入内容分词得到词条华为、手机拿着词条在倒排索引中查找可以得到包含词条的文档id1、2、3拿着文档id到正向索引中查找具体文档 如图 虽然要先查询倒排索引再查询倒排索引但是无论是词条、还是文档id都建立了索引查询速度非常快无需全表扫描 正向和倒排 那么为什么一个叫做正向索引一个叫做倒排索引呢 正向索引是最传统的根据id索引的方式。但根据词条查询时必须先逐条获取每个文档然后判断文档中是否包含所需要的词条是根据文档找词条的过程。 而倒排索引则相反是先找到用户要搜索的词条根据词条得到保护词条的文档的id然后根据id获取文档。是根据词条找文档的过程。 是不是恰好反过来了 那么两者方式的优缺点是什么呢 正向索引 优点 可以给多个字段创建索引根据索引字段搜索、排序速度非常快 缺点 根据非索引字段或者索引字段中的部分词条查找时只能全表扫描。 倒排索引 优点 根据词条搜索、模糊搜索时速度非常快 缺点 只能给词条创建索引而不是字段无法根据字段做排序 es的一些概念 elasticsearch中有很多独有的概念与mysql中略有差别但也有相似之处 文档和字段 elasticsearch是面向 文档Document 存储的可以是数据库中的一条商品数据一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中 而Json文档中往往包含很多的字段Field类似于数据库中的列 索引和映射 索引Index相同类型的文档的集合 映射mapping索引中文档的字段约束信息类似表的结构约束 例如 所有用户文档就可以组织在一起称为用户的索引所有商品的文档可以组织在一起称为商品的索引所有订单的文档可以组织在一起称为订单的索引 因此我们可以把索引当做是数据库中的表 数据库的表会有约束信息用来定义表的结构、字段的名称、类型等信息。因此索引库中就有映射mapping是索引中文档的字段约束信息类似表的结构约束 mysql与elasticsearch 我们统一的把mysql与elasticsearch的概念做一下对比 MySQLElasticsearch说明TableIndex索引(index)就是文档的集合类似数据库的表(table)RowDocument文档Document就是一条条的数据类似数据库中的行Row文档都是JSON格式ColumnField字段Field就是JSON文档中的字段类似数据库中的列ColumnSchemaMappingMapping映射是索引中文档的约束例如字段类型约束。类似数据库的表结构SchemaSQLDSLDSL是elasticsearch提供的JSON风格的请求语句用来操作elasticsearch实现CRUD 是不是说我们学习了elasticsearch就不再需要mysql了呢 并不是如此两者各自有自己的擅长支出 Mysql擅长事务类型操作可以确保数据的安全和一致性 Elasticsearch擅长海量数据的搜索、分析、计算 因此在企业中往往是两者结合使用 对安全性要求较高的写操作使用mysql实现对查询性能要求较高的搜索需求使用elasticsearch实现两者再基于某种方式实现数据的同步保证一致性 安装ES 创建网络 因为我们还需要部署kibana容器因此需要让es和kibana容器互联。这里先创建一个网络docker network create es-net 加载镜像 由于elasticsearch镜像比较大不建议直接pull所以我们从官网下载将其上传到虚拟机中然后运行命令加载即可docker load -i es.tar 同理还有kibana的tar包也需要这样做 运行docker命令部署单点es docker run -d \--name es \-e ES_JAVA_OPTS-Xms512m -Xmx512m \-e discovery.typesingle-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.namees-docker-cluster设置集群名称-e http.host0.0.0.0监听的地址可以外网访问-e ES_JAVA_OPTS-Xms512m -Xmx512m内存大小-e discovery.typesingle-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://192.168.1.12:9200 即可看到elasticsearch的响应结果 部署kibana kibana可以给我们提供一个elasticsearch的可视化界面便于我们学习 部署 运行docker命令部署kibana docker run -d \ --name kibana \ -e ELASTICSEARCH_HOSTShttp://es:9200 \ --networkes-net \ -p 5601:5601 \ kibana:7.12.1--network es-net 加入一个名为es-net的网络中与elasticsearch在同一个网络中-e ELASTICSEARCH_HOSTShttp://es:9200设置elasticsearch的地址因为kibana已经与elasticsearch在一个网络因此可以用容器名直接访问elasticsearch-p 5601:5601端口映射配置 kibana启动一般比较慢需要多等待一会可以通过命令docker logs -f kibana 查看运行日志当查看到下面的日志说明成功 安装IK分词器 在线安装ik插件较慢 # 进入容器内部 docker exec -it elasticsearch /bin/bash# 在线下载并安装 ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip#退出 exit #重启容器 docker restart elasticsearch离线安装ik插件推荐 查看数据卷目录 安装插件需要知道elasticsearch的plugins目录位置而我们用了数据卷挂载因此需要查看elasticsearch的数据卷目录通过下面命令查看docker volume inspect es-plugins 下载解压缩分词器安装包 重命名为ik 上传到es容器的插件数据卷中也就是/var/lib/docker/volumes/es-plugins/_data 重启容器docker restart es 测试 IK分词器包含两种模式 ik_smart最少切分 ik_max_word最细切分 GET /_analyze {analyzer: ik_max_word,text: 小吴在敲Bug }结果 {tokens : [{token : 小吴,start_offset : 0,end_offset : 2,type : CN_WORD,position : 0},{token : 在,start_offset : 2,end_offset : 3,type : CN_CHAR,position : 1},{token : 敲,start_offset : 3,end_offset : 4,type : CN_CHAR,position : 2},{token : bug,start_offset : 4,end_offset : 7,type : ENGLISH,position : 3}] }扩展词词典 随着互联网的发展“造词运动”也越发的频繁。出现了很多新的词语在原有的词汇列表中并不存在。比如“奥力给”“尊嘟假嘟” 等 所以我们的词汇也需要不断的更新IK分词器提供了扩展词汇的功能 打开IK分词器config目录在IKAnalyzer.cfg.xml配置文件内容添加 ?xml version1.0 encodingUTF-8? !DOCTYPE properties SYSTEM http://java.sun.com/dtd/properties.dtd propertiescommentIK Analyzer 扩展配置/comment!--用户可以在这里配置自己的扩展字典 *** 添加扩展词典--entry keyext_dictext.dic/entry /properties新建一个 ext.dic可以参考config目录下复制一个配置文件进行修改 尊嘟假嘟 奥力给重启elasticsearch docker restart es 停用词词典 在互联网项目中在网络间传输的速度很快所以很多语言是不允许在网络上传递的如关于宗教、政治等敏感词语那么我们在搜索时也应该忽略当前词汇 IK分词器也提供了强大的停用词功能让我们在索引时就直接忽略当前的停用词汇表中的内容 IKAnalyzer.cfg.xml配置文件内容添加 ?xml version1.0 encodingUTF-8? !DOCTYPE properties SYSTEM http://java.sun.com/dtd/properties.dtd propertiescommentIK Analyzer 扩展配置/comment!--用户可以在这里配置自己的扩展字典--entry keyext_dictext.dic/entry!--用户可以在这里配置自己的扩展停止词字典 *** 添加停用词词典--entry keyext_stopwordsstopword.dic/entry /properties在 stopword.dic 添加停用词 啊 哦 额重启elasticsearch docker restart es 索引库操作 索引库就类似数据库表mapping映射就类似表的结构 我们要向es中存储数据必须先创建“库”和“表” mapping映射属性 mapping是对索引库中文档的约束常见的mapping属性包括 type字段数据类型常见的简单类型有 字符串text可分词的文本、keyword精确值例如品牌、国家、ip地址数值long、integer、short、byte、double、float、布尔boolean日期date对象object index是否创建索引默认为trueanalyzer使用哪种分词器properties该字段的子字段 例如下面的json文档 {age: 21,weight: 52.1,isMarried: false,info: 小吴在敲Bug,email: zyitcast.cn,score: [99.1, 99.5, 98.9],name: {firstName: 云,lastName: 赵} }对应的每个字段映射mapping age类型为 integer参与搜索因此需要index为true无需分词器weight类型为float参与搜索因此需要index为true无需分词器isMarried类型为boolean参与搜索因此需要index为true无需分词器info类型为字符串需要分词因此是text参与搜索因此需要index为true分词器可以用ik_smartemail类型为字符串但是不需要分词因此是keyword不参与搜索因此需要index为false无需分词器score虽然是数组但是我们只看元素的类型类型为float参与搜索因此需要index为true无需分词器name类型为object需要定义多个子属性 name.firstName类型为字符串但是不需要分词因此是keyword参与搜索因此需要index为true无需分词器name.lastName类型为字符串但是不需要分词因此是keyword参与搜索因此需要index为true无需分词器 索引库的CRUD 这里我们统一使用Kibana编写DSL的方式来演示 创建索引库和映射 基本语法 请求方式PUT请求路径/索引库名可以自定义请求参数mapping映射 格式 PUT /索引库名称 {mappings: {properties: {字段名:{type: text,analyzer: ik_smart},字段名2:{type: keyword,index: false},字段名3:{properties: {子字段: {type: keyword}}},// ...略}} }查询索引库 基本语法 请求方式GET 请求路径/索引库名 请求参数无 格式GET /索引库名 修改索引库 倒排索引结构虽然不复杂但是一旦数据结构改变比如改变了分词器就需要重新创建倒排索引这简直是灾难。因此索引库一旦创建无法修改mapping 虽然无法修改mapping中已有的字段但是却允许添加新的字段到mapping中因为不会对倒排索引产生影响 语法说明 PUT /索引库名/_mapping {properties: {新字段名:{type: integer}} }删除索引库 语法 请求方式DELETE 请求路径/索引库名 请求参数无 格式 DELETE /索引库名 文档操作 新增文档 POST /索引库名/_doc/文档id {字段1: 值1,字段2: 值2,字段3: {子属性1: 值3,子属性2: 值4},// ... }查询文档根据rest风格新增是post查询应该是get不过查询一般都需要条件这里我们把文档id带上 GET /{索引库名称}/_doc/{id} 删除文档删除使用DELETE请求同样需要根据id进行删除 DELETE /{索引库名}/_doc/id值 修改文档 修改有两种方式 全量修改直接覆盖原来的文档 根据指定的id删除文档新增一个相同id的文档 注意如果根据id删除时id不存在第二步的新增也会执行也就从修改变成了新增操作了 PUT /{索引库名}/_doc/文档id {字段1: 值1,字段2: 值2,// ... 略 }增量修改修改文档中的部分字段 增量修改是只修改指定id匹配的文档中的部分字段 POST /{索引库名}/_update/文档id {doc: {字段名: 新的值,} }RestClient操作索引库 ES官方提供了各种不同语言的客户端用来操作ES。这些客户端的本质就是组装DSL语句通过http请求发送给ES 其中的Java Rest Client又包括两种 Java Low Level Rest ClientJava High Level Rest Client 我们学习的是Java High Level Rest Client客户端API 导入Demo工程 导入数据 首先导入课前资料提供的数据库数据 数据结构如下 CREATE TABLE tb_hotel (id bigint(20) NOT NULL COMMENT 酒店id,name varchar(255) NOT NULL COMMENT 酒店名称例7天酒店,address varchar(255) NOT NULL COMMENT 酒店地址例航头路,price int(10) NOT NULL COMMENT 酒店价格例329,score int(2) NOT NULL COMMENT 酒店评分例45就是4.5分,brand varchar(32) NOT NULL COMMENT 酒店品牌例如家,city varchar(32) NOT NULL COMMENT 所在城市例上海,star_name varchar(16) DEFAULT NULL COMMENT 酒店星级从低到高分别是1星到5星1钻到5钻,business varchar(255) DEFAULT NULL COMMENT 商圈例虹桥,latitude varchar(32) NOT NULL COMMENT 纬度例31.2497,longitude varchar(32) NOT NULL COMMENT 经度例120.3925,pic varchar(255) DEFAULT NULL COMMENT 酒店图片例:/img/1.jpg,PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;导入项目 然后导入课前资料提供的项目: 项目结构如图 mapping映射分析 创建索引库最关键的是mapping映射而mapping映射要考虑的信息包括 字段名字段数据类型是否参与搜索是否需要分词如果分词分词器是什么 其中 字段名、字段数据类型可以参考数据表结构的名称和类型是否参与搜索要分析业务来判断例如图片地址就无需参与搜索是否分词呢要看内容内容如果是一个整体就无需分词反之则要分词分词器我们可以统一使用ik_max_word 酒店数据的索引库结构: PUT /hotel {mappings: {properties: {id:{type: keyword},name:{type: text,analyzer: ik_max_word,copy_to: all},address:{type: keyword,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一个组合字段其目的是将多字段的值 利用copy_to合并提供给用户搜索 地理坐标说明 copy_to说明 初始化RestClient 在elasticsearch提供的API中与elasticsearch一切交互都封装在一个名为RestHighLevelClient的类中必须先完成这个对象的初始化建立与elasticsearch的连接 引入es的RestHighLevelClient依赖 dependencygroupIdorg.elasticsearch.client/groupIdartifactIdelasticsearch-rest-high-level-client/artifactIdversion7.12.1/version /dependency因为SpringBoot默认的ES版本是7.6.2所以我们需要覆盖默认的ES版本 propertieselasticsearch.version7.12.1/elasticsearch.version /properties初始化RestHighLevelClient RestHighLevelClient client new RestHighLevelClient(RestClient.builder(HttpHost.create(http://192.168.1.12:9200) ));这里为了单元测试方便我们创建一个测试类HotelIndexTest然后将初始化的代码编写在BeforeEach方法中 public class HotelIndexTest {private RestHighLevelClient client;BeforeEachvoid setUp() {this.client new RestHighLevelClient(RestClient.builder(HttpHost.create(http://192.168.150.101:9200)));}AfterEachvoid tearDown() throws IOException {this.client.close();} }创建索引库 创建索引库的API如下 代码分为三步 创建Request对象。因为是创建索引库的操作因此Request是CreateIndexRequest添加请求参数其实就是DSL的JSON参数部分。因为json字符串很长这里是定义了静态字符串常量MAPPING_TEMPLATE让代码看起来更加优雅发送请求client.indices()方法的返回值是IndicesClient类型封装了所有与索引库操作有关的方法 在hotel-demo的cn.itcast.hotel.constants包下创建一个类定义mapping映射的JSON字符串常量 public class HotelConstants {public static final String MAPPING_TEMPLATE {\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 \address\:{\n \type\: \keyword\,\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 \copy_to\: \all\\n },\n \starName\:{\n \type\: \keyword\\n },\n \business\:{\n \type\: \keyword\\n },\n \location\:{\n \type\: \geo_point\\n },\n \pic\:{\n \type\: \keyword\,\n \index\: false\n },\n \all\:{\n \type\: \text\,\n \analyzer\: \ik_max_word\\n }\n }\n }\n }; }在hotel-demo中的HotelIndexTest测试类中编写单元测试实现创建索引 Test void createHotelIndex() throws IOException {// 1.创建Request对象CreateIndexRequest request new CreateIndexRequest(hotel);// 2.准备请求的参数DSL语句request.source(MAPPING_TEMPLATE, XContentType.JSON);// 3.发送请求client.indices().create(request, RequestOptions.DEFAULT); }删除索引库 删除索引库的DSL语句非常简单DELETE /hotel 与创建索引库相比 请求方式从PUT变为DELTE请求路径不变无请求参数 所以代码的差异注意体现在Request对象上。依然是三步走 创建Request对象。这次是DeleteIndexRequest对象准备参数。这里是无参发送请求。改用delete方法 在hotel-demo中的HotelIndexTest测试类中编写单元测试实现删除索引 Test void testDeleteHotelIndex() throws IOException {// 1.创建Request对象DeleteIndexRequest request new DeleteIndexRequest(hotel);// 2.发送请求client.indices().delete(request, RequestOptions.DEFAULT); }判断索引库是否存在 判断索引库是否存在本质就是查询对应的DSL是GET /hotel 因此与删除的Java代码流程是类似的。依然是三步走 创建Request对象。这次是GetIndexRequest对象准备参数。这里是无参发送请求。改用exists方法 Test void testExistsHotelIndex() throws IOException {// 1.创建Request对象GetIndexRequest request new GetIndexRequest(hotel);// 2.发送请求boolean exists client.indices().exists(request, RequestOptions.DEFAULT);// 3.输出System.err.println(exists ? 索引库已经存在 : 索引库不存在); }RestClient操作文档 为了与索引库操作分离我们再次参加一个测试类做两件事情 初始化RestHighLevelClient我们的酒店数据在数据库需要利用IHotelService去查询所以注入这个接口 SpringBootTest public class HotelDocumentTest {Autowiredprivate IHotelService hotelService;private RestHighLevelClient client;BeforeEachvoid setUp() {this.client new RestHighLevelClient(RestClient.builder(HttpHost.create(http://192.168.1.12:9200)));}AfterEachvoid tearDown() throws IOException {this.client.close();} }新增文档 我们要将数据库的酒店数据查询出来写入elasticsearch中 索引库实体类 数据库查询后的结果是一个Hotel类型的对象。结构如下 Data TableName(tb_hotel) public class Hotel {TableId(type IdType.INPUT)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 longitude;private String latitude;private String pic; }与我们的索引库结构存在差异 longitude和latitude需要合并为location 因此我们需要定义一个新的类型与索引库结构吻合 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;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();} }语法说明 新增文档的DSL语句如下 POST /{索引库名}/_doc/1 {name: Jack,age: 21 }对应的java代码如图 可以看到与创建索引库类似同样是三步走 创建Request对象准备请求参数也就是DSL中的JSON文档发送请求 变化的地方在于这里直接使用client.xxx()的API不再需要client.indices()了 完整代码 我们导入酒店数据基本流程一致但是需要考虑几点变化 酒店数据来自于数据库我们需要先查询出来得到hotel对象hotel对象需要转为HotelDoc对象HotelDoc需要序列化为json格式 因此代码整体步骤如下 根据id查询酒店数据Hotel将Hotel封装为HotelDoc将HotelDoc序列化为JSON创建IndexRequest指定索引库名和id准备请求参数也就是JSON文档发送请求 在hotel-demo的HotelDocumentTest测试类中编写单元测试 Test void testAddDocument() throws IOException {// 1.根据id查询酒店数据Hotel hotel hotelService.getById(61083L);// 2.转换为文档类型HotelDoc hotelDoc new HotelDoc(hotel);// 3.将HotelDoc转jsonString json JSON.toJSONString(hotelDoc);// 1.准备Request对象IndexRequest request new IndexRequest(hotel).id(hotelDoc.getId().toString());// 2.准备Json文档request.source(json, XContentType.JSON);// 3.发送请求client.index(request, RequestOptions.DEFAULT); }查询文档 查询的DSL语句如下GET /hotel/_doc/{id} 非常简单因此代码大概分两步 准备Request对象发送请求 不过查询的目的是得到结果解析为HotelDoc因此难点是结果的解析。完整代码如下 可以看到结果是一个JSON其中文档放在一个_source属性中因此解析就是拿到_source反序列化为Java对象即可。 与之前类似也是三步走 准备Request对象。这次是查询所以是GetRequest发送请求得到结果。因为是查询这里调用client.get()方法解析结果就是对JSON做反序列化 完整代码 Test void testGetDocumentById() throws IOException {// 1.准备RequestGetRequest request new GetRequest(hotel, 61082);// 2.发送请求得到响应GetResponse response client.get(request, RequestOptions.DEFAULT);// 3.解析响应结果String json response.getSourceAsString();HotelDoc hotelDoc JSON.parseObject(json, HotelDoc.class);System.out.println(hotelDoc); }修改文档 修改我们讲过两种方式 全量修改本质是先根据id删除再新增增量修改修改文档中的指定字段值 在RestClient的API中全量修改与新增的API完全一致判断依据是ID 如果新增时ID已经存在则修改如果新增时ID不存在则新增 这里不再赘述我们主要关注增量修改。 代码示例如图 与之前类似也是三步走 准备Request对象。这次是修改所以是UpdateRequest准备参数。也就是JSON文档里面包含要修改的字段更新文档。这里调用client.update()方法 完整代码 在hotel-demo的HotelDocumentTest测试类中编写单元测试 Test void testUpdateDocument() throws IOException {// 1.准备RequestUpdateRequest request new UpdateRequest(hotel, 61083);// 2.准备请求参数request.doc(price, 952,starName, 四钻);// 3.发送请求client.update(request, RequestOptions.DEFAULT); }删除文档 删除的DSL为是这样的DELETE /hotel/_doc/{id} 与查询相比仅仅是请求方式从DELETE变成GET可以想象Java代码应该依然是三步走 准备Request对象因为是删除这次是DeleteRequest对象。要指定索引库名和id准备参数无参发送请求。因为是删除所以是client.delete()方法 完整代码 在hotel-demo的HotelDocumentTest测试类中编写单元测试 Test void testDeleteDocument() throws IOException {// 1.准备RequestDeleteRequest request new DeleteRequest(hotel, 61083);// 2.发送请求client.delete(request, RequestOptions.DEFAULT); }批量导入文档 案例需求利用BulkRequest批量将数据库数据导入到索引库中。 步骤如下 利用mybatis-plus查询酒店数据 将查询到的酒店数据Hotel转换为文档类型数据HotelDoc 利用JavaRestClient中的BulkRequest批处理实现批量新增文档 批量处理BulkRequest其本质就是将多个普通的CRUD请求组合在一起发送 其中提供了一个add方法用来添加其他请求 可以看到能添加的请求包括 IndexRequest也就是新增UpdateRequest也就是修改DeleteRequest也就是删除 因此Bulk中添加了多个IndexRequest就是批量新增功能了。示例 其实还是三步走 创建Request对象。这里是BulkRequest准备参数。批处理的参数就是其它Request对象这里就是多个IndexRequest发起请求。这里是批处理调用的方法为client.bulk()方法 完整代码 我们在导入酒店数据时将上述代码改造成for循环处理即可 在hotel-demo的HotelDocumentTest测试类中编写单元测试 Test void testBulkRequest() throws IOException {// 批量查询酒店数据ListHotel hotels hotelService.list();// 1.创建RequestBulkRequest request new BulkRequest();// 2.准备参数添加多个新增的Requestfor (Hotel hotel : hotels) {// 2.1.转换为文档类型HotelDocHotelDoc hotelDoc new HotelDoc(hotel);// 2.2.创建新增文档的Request对象request.add(new IndexRequest(hotel).id(hotelDoc.getId().toString()).source(JSON.toJSONString(hotelDoc), XContentType.JSON));}// 3.发送请求client.bulk(request, RequestOptions.DEFAULT); }DSL查询文档 elasticsearch的查询依然是基于JSON风格的DSL来实现的 DSL查询分类 Elasticsearch提供了基于JSON的DSLDomain Specific Language来定义查询。常见的查询类型包括 查询所有查询出所有数据一般测试用。例如match_all 全文检索full text查询利用分词器对用户输入内容分词然后去倒排索引库中匹配。例如 match_querymulti_match_query 精确查询根据精确词条值查找数据一般是查找keyword、数值、日期、boolean等类型字段。例如 idsrangeterm 地理geo查询根据经纬度查询。例如 geo_distancegeo_bounding_box 复合compound查询复合查询可以将上述各种查询条件组合起来合并查询条件。例如 boolfunction_score 查询的语法基本一致 GET /indexName/_search {query: {查询类型: {查询条件: 条件值}} }我们以查询所有为例其中 查询类型为match_all没有查询条件 // 查询所有 GET /indexName/_search {query: {match_all: {}} }其它查询无非就是查询类型、查询条件的变化 全文检索查询 使用场景 全文检索查询的基本流程如下 对用户搜索的内容做分词得到词条根据词条去倒排索引库中匹配得到文档id根据文档id找到文档返回给用户 比较常用的场景包括 商城的输入框搜索百度输入框搜索 例如京东 因为是拿着词条去匹配因此参与搜索的字段也必须是可分词的text类型的字段 基本语法 常见的全文检索查询包括 match查询单字段查询multi_match查询多字段查询任意一个字段符合条件就算符合查询条件 match查询语法如下 GET /indexName/_search {query: {match: {字段名: 查询的关键字}} }mulit_match语法如下 GET /indexName/_search {query: {multi_match: {query: 查询的关键字,fields: [字段名1, 字段名2]}} }精准查询 精确查询一般是查找keyword、数值、日期、boolean等类型字段。所以不会对搜索条件分词。常见的有 term根据词条精确值查询range根据值的范围查询 term查询 因为精确查询的字段搜是不分词的字段因此查询的条件也必须是不分词的词条。查询时用户输入的内容跟自动值完全匹配时才认为符合条件。如果用户输入的内容过多反而搜索不到数据 语法说明 // term查询 GET /indexName/_search {query: {term: {字段名: {value: 关键字}}} }range查询 范围查询一般应用在对数值类型做范围过滤的时候。比如做价格范围过滤 基本语法 // range查询 GET /indexName/_search {query: {range: {字段名: {gte: 10, // 这里的gte代表大于等于gt则代表大于lte: 20 // lte代表小于等于lt则代表小于}}} }理坐标查询 所谓的地理坐标查询其实就是根据经纬度查询官方文档 常见的使用场景包括 携程搜索我附近的酒店滴滴搜索我附近的出租车微信搜索我附近的人 矩形范围查询 矩形范围查询也就是geo_bounding_box查询查询坐标落在某个矩形范围的所有文档 查询时需要指定矩形的左上、右下两个点的坐标然后画出一个矩形落在该矩形内的都是符合条件的点 语法如下 GET /indexName/_search {query: {geo_bounding_box: {字段名: {top_left: { // 左上点lat: 31.1,lon: 121.5},bottom_right: { // 右下点lat: 30.9,lon: 121.7}}}} }附近查询 附近查询也叫做距离查询geo_distance查询到指定中心点小于某个距离值的所有文档 换句话来说在地图上找一个点作为圆心以指定距离为半径画一个圆落在圆内的坐标都算符合条件 语法说明 GET /indexName/_search {query: {geo_distance: {distance: 15km, // 半径字段名: 31.21,121.5 // 圆心}} }复合查询 复合compound查询复合查询可以将其它简单查询组合起来实现更复杂的搜索逻辑。常见的有两种 fuction score算分函数查询可以控制文档相关性算分控制文档排名bool query布尔查询利用逻辑关系组合多个其它的查询实现复杂搜索 算分函数查询 根据相关度打分是比较合理的需求但合理的不一定是产品经理需要的 以百度为例你搜索的结果中并不是相关度越高排名越靠前而是谁掏的钱多排名就越靠前。如图 要想人为控制相关性算分就需要利用elasticsearch中的function score 查询了 语法说明 function score 查询中包含四部分内容 原始查询条件query部分基于这个条件搜索文档并且基于BM25算法给文档打分原始算分query score)过滤条件filter部分符合该条件的文档才会重新算分算分函数符合filter条件的文档要根据这个函数做运算得到的函数算分function score有四种函数 weight函数结果是常量field_value_factor以文档中的某个字段值作为函数结果random_score以随机数作为函数结果script_score自定义算分函数算法 运算模式算分函数的结果、原始查询的相关性算分两者之间的运算方式包括 multiply相乘replace用function score替换query score其它例如sum、avg、max、min function score的运行流程如下 根据原始条件查询搜索文档并且计算相关性算分称为原始算分query score根据过滤条件过滤文档符合过滤条件的文档基于算分函数运算得到函数算分function score将原始算分query score和函数算分function score基于运算模式做运算得到最终结果作为相关性算分。 因此其中的关键点是 过滤条件决定哪些文档的算分被修改算分函数决定函数算分的算法运算模式决定最终算分结果 示例 需求给“如家”这个品牌的酒店排名靠前一些 翻译一下这个需求转换为之前说的四个要点 原始条件不确定可以任意变化过滤条件brand “如家”算分函数可以简单粗暴直接给固定的算分结果weight运算模式比如求和 因此最终的DSL语句如下 GET /hotel/_search {query: {function_score: {query: { .... }, // 原始查询可以是任意条件functions: [ // 算分函数{filter: { // 满足的条件品牌必须是如家term: {brand: 如家}},weight: 2 // 算分权重为2}],boost_mode: sum // 加权模式求和}} }布尔查询 布尔查询是一个或多个查询子句的组合每一个子句就是一个子查询。子查询的组合方式有 must必须匹配每个子查询类似“与”should选择性匹配子查询类似“或”must_not必须不匹配不参与算分类似“非”filter必须匹配不参与算分 比如在搜索酒店时除了关键字搜索外我们还可能根据品牌、价格、城市等字段做过滤 每一个不同的字段其查询的条件、方式都不一样必须是多个不同的查询而要组合这些查询就必须用bool查询了。 需要注意的是搜索时参与打分的字段越多查询的性能也越差。因此这种多条件查询时建议这样做 搜索框的关键字搜索是全文检索查询使用must查询参与算分其它过滤条件采用filter查询。不参与算分 语法示例 GET /hotel/_search {query: {bool: {must: [{term: {city: 上海 }}],should: [{term: {brand: 皇冠假日 }},{term: {brand: 华美达 }}],must_not: [{ range: { price: { lte: 500 } }}],filter: [{ range: {score: { gte: 45 } }}]}} }示例 需求搜索名字包含“如家”价格不高于400在坐标31.21,121.5周围10km范围内的酒店。 分析 名称搜索属于全文检索查询应该参与算分。放到must中价格不高于400用range查询属于过滤条件不参与算分。放到must_not中周围10km范围内用geo_distance查询属于过滤条件不参与算分。放到filter中 搜索结果处理 搜索的结果可以按照用户指定的方式去处理或展示 排序 elasticsearch默认是根据相关度算分_score来排序但是也支持自定义方式对搜索结果排序。可以排序字段类型有keyword类型、数值类型、地理坐标类型、日期类型等 普通字段排序 keyword、数值、日期类型排序的语法基本一致 语法 GET /indexName/_search {query: {match_all: {}},sort: [{字段名: desc // 排序字段、排序方式ASC、DESC}] }排序条件是一个数组也就是可以写多个排序条件。按照声明的顺序当第一个条件相等时再按照第二个条件排序以此类推 地理坐标排序 语法说明 GET /indexName/_search {query: {match_all: {}},sort: [{_geo_distance : {字段名 : 纬度经度, // 文档中geo_point类型的字段名、目标坐标点order : asc, // 排序方式unit : km // 排序的距离单位}}] }这个查询的含义是 指定一个坐标作为目标点计算每一个文档中指定字段必须是geo_point类型的坐标 到目标点的距离是多少根据距离排序 分页 elasticsearch 默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了。elasticsearch中通过修改from、size参数来控制要返回的分页结果 from从第几个文档开始size总共查询几个文档 类似于mysql中的limit ?, ? 基本的分页 GET /hotel/_search {query: {match_all: {}},from: 0, // 分页开始的位置默认为0size: 10, // 期望获取的文档总数sort: [{price: asc}] }深度分页问题 现在我要查询990~1000的数据查询逻辑要这么写 GET /hotel/_search {query: {match_all: {}},from: 990, // 分页开始的位置默认为0size: 10, // 期望获取的文档总数sort: [{price: asc}] }这里是查询990开始的数据也就是 第990~第1000条 数据 不过elasticsearch内部分页时必须先查询 0~1000条然后截取其中的990 ~ 1000的这10条 查询TOP1000如果es是单点模式这并无太大影响 但是elasticsearch将来一定是集群例如我集群有5个节点我要查询TOP1000的数据并不是每个节点查询200条就可以了 因为节点A的TOP200在另一个节点可能排到10000名以外了 因此要想获取整个集群的TOP1000必须先查询出每个节点的TOP1000汇总结果后重新排名重新截取TOP1000 那如果我要查询9900~10000的数据呢是不是要先查询TOP10000呢那每个节点都要查询10000条汇总到内存中 当查询分页深度较大时汇总数据过多对内存和CPU会产生非常大的压力因此elasticsearch会禁止from size 超过10000的请求 针对深度分页ES提供了两种解决方案官方文档 search after分页时需要排序原理是从上一次的排序值开始查询下一页数据。官方推荐使用的方式。scroll原理将排序后的文档id形成快照保存在内存。官方已经不推荐使用。 高亮 高亮原理 什么是高亮显示呢 我们在百度京东搜索时关键字会变成红色比较醒目这叫高亮显示 高亮显示的实现分为两步 给文档中的所有关键字都添加一个标签例如em标签页面给em标签编写CSS样式 实现高亮 高亮的语法 GET /hotel/_search {query: {match: {字段名: 关键字 // 查询条件高亮一定要使用全文检索查询}},highlight: {fields: { // 指定要高亮的字段字段名: {pre_tags: em, // 用来标记高亮字段的前置标签post_tags: /em // 用来标记高亮字段的后置标签}}} }注意 高亮是对关键字高亮因此搜索条件必须带有关键字而不能是范围这样的查询。默认情况下高亮的字段必须与搜索指定的字段一致否则无法高亮如果要对非搜索字段高亮则需要添加一个属性required_field_matchfalse RestClient查询文档 文档的查询同样适用昨天学习的 RestHighLevelClient对象基本步骤包括 准备Request对象准备请求参数发起请求解析响应 快速入门 发起查询请求 代码解读 第一步创建SearchRequest对象指定索引库名 第二步利用request.source()构建DSLDSL中可以包含查询、分页、排序、高亮等 query()代表查询条件利用QueryBuilders.matchAllQuery()构建一个match_all查询的DSL 第三步利用client.search()发送请求得到响应 这里关键的API有两个一个是request.source()其中包含了查询、排序、分页、高亮等所有功能 另一个是QueryBuilders其中包含match、term、function_score、bool等各种查询 解析响应 elasticsearch返回的结果是一个JSON字符串结构包含 hits命中的结果 total总条数其中的value是具体的总条数值max_score所有结果中得分最高的文档的相关性算分hits搜索结果的文档数组其中的每个文档都是一个json对象 _source文档中的原始数据也是json对象 因此我们解析响应结果就是逐层解析JSON字符串流程如下 SearchHits通过response.getHits()获取就是JSON中的最外层的hits代表命中的结果 SearchHits#getTotalHits().value获取总条数信息SearchHits#getHits()获取SearchHit数组也就是文档数组 SearchHit#getSourceAsString()获取文档结果中的_source也就是原始的json文档数据 match查询 全文检索的match和multi_match查询与match_all的API基本一致。差别是查询条件也就是query的部分 因此Java代码上的差异主要是request.source().query()中的参数了。同样是利用QueryBuilders提供的方法 而结果解析代码则完全一致可以抽取并共享 完整代码如下 Test void testMatch() throws IOException {// 1.准备RequestSearchRequest request new SearchRequest(hotel);// 2.准备DSLrequest.source().query(QueryBuilders.matchQuery(all, 如家));// 3.发送请求SearchResponse response client.search(request, RequestOptions.DEFAULT);// 4.解析响应handleResponse(response);}精确查询 精确查询主要是两者 term词条精确匹配range范围查询 与之前的查询相比差异同样在查询条件其它都一样 查询条件构造的API如下 复合查询 布尔查询是用must、must_not、filter等方式组合其它查询代码示例如下 可以看到API与其它查询的差别同样是在查询条件的构建QueryBuilders结果解析等其他代码完全不变 完整代码如下 Test void testBool() throws IOException {// 1.准备RequestSearchRequest request new SearchRequest(hotel);// 2.准备DSL// 2.1.准备BooleanQueryBoolQueryBuilder boolQuery QueryBuilders.boolQuery();// 2.2.添加termboolQuery.must(QueryBuilders.termQuery(city, 杭州));// 2.3.添加rangeboolQuery.filter(QueryBuilders.rangeQuery(price).lte(250));request.source().query(boolQuery);// 3.发送请求SearchResponse response client.search(request, RequestOptions.DEFAULT);// 4.解析响应handleResponse(response);}排序、分页 搜索结果的排序和分页是与query同级的参数因此同样是使用request.source()来设置 对应的API如下 完整代码示例 Test void testPageAndSort() throws IOException {// 页码每页大小int page 1, size 5;// 1.准备RequestSearchRequest request new SearchRequest(hotel);// 2.准备DSL// 2.1.queryrequest.source().query(QueryBuilders.matchAllQuery());// 2.2.排序 sortrequest.source().sort(price, SortOrder.ASC);// 2.3.分页 from、sizerequest.source().from((page - 1) * size).size(5);// 3.发送请求SearchResponse response client.search(request, RequestOptions.DEFAULT);// 4.解析响应handleResponse(response); }高亮 高亮的代码与之前代码差异较大有两点 查询的DSL其中除了查询条件还需要添加高亮条件同样是与query同级。结果解析结果除了要解析_source文档数据还要解析高亮结果 高亮请求构建 高亮请求的构建API如下 上述代码省略了查询条件部分但是大家不要忘了高亮查询必须使用全文检索查询并且要有搜索关键字将来才可以对关键字高亮 完整代码如下 Test void testHighlight() throws IOException {// 1.准备RequestSearchRequest request new SearchRequest(hotel);// 2.准备DSL// 2.1.queryrequest.source().query(QueryBuilders.matchQuery(all, 如家));// 2.2.高亮request.source().highlighter(new HighlightBuilder().field(name).requireFieldMatch(false));// 3.发送请求SearchResponse response client.search(request, RequestOptions.DEFAULT);// 4.解析响应handleResponse(response); }高亮结果解析 高亮的结果与查询的文档结果默认是分离的并不在一起 因此解析高亮的代码需要额外处理 代码解读 第一步从结果中获取source。hit.getSourceAsString()这部分是非高亮结果json字符串。还需要反序列为HotelDoc对象第二步获取高亮结果。hit.getHighlightFields()返回值是一个Mapkey是高亮字段名称值是HighlightField对象代表高亮值第三步从map中根据高亮字段名称获取高亮字段值对象HighlightField第四步从HighlightField中获取Fragments并且转为字符串。这部分就是真正的高亮字符串了第五步用高亮的结果替换HotelDoc中的非高亮结果 完整代码如下 private void handleResponse(SearchResponse response) {// 4.解析响应SearchHits searchHits response.getHits();// 4.1.获取总条数long total searchHits.getTotalHits().value;System.out.println(共搜索到 total 条数据);// 4.2.文档数组SearchHit[] hits searchHits.getHits();// 4.3.遍历for (SearchHit hit : hits) {// 获取文档sourceString json hit.getSourceAsString();// 反序列化HotelDoc hotelDoc JSON.parseObject(json, HotelDoc.class);// 获取高亮结果MapString, HighlightField highlightFields hit.getHighlightFields();if (!CollectionUtils.isEmpty(highlightFields)) {// 根据字段名获取高亮结果HighlightField highlightField highlightFields.get(name);if (highlightField ! null) {// 获取高亮值String name highlightField.getFragments()[0].string();// 覆盖非高亮结果hotelDoc.setName(name);}}System.out.println(hotelDoc hotelDoc);} }数据聚合 聚合aggregations 可以让我们极其方便的实现对数据的统计、分析、运算。例如 什么品牌的手机最受欢迎这些手机的平均价格、最高价格、最低价格这些手机每月的销售情况如何 实现这些统计功能的比数据库的sql要方便的多而且查询速度非常快可以实现近实时搜索效果。 聚合常见的有三类 注意 参加聚合的字段必须是keyword、日期、数值、布尔类型 桶Bucket 聚合用来对文档做分组 TermAggregation按照文档字段值分组例如按照品牌值分组、按照国家分组Date Histogram按照日期阶梯分组例如一周为一组或者一月为一组 度量Metric 聚合用以计算一些值比如最大值、最小值、平均值等 Avg求平均值Max求最大值Min求最小值Stats同时求max、min、avg、sum等 管道pipeline 聚合其它聚合的结果为基础做聚合 DSL实现聚合 现在我们要统计所有数据中的酒店品牌有几种其实就是按照品牌对数据分组。此时可以根据酒店品牌的名称做聚合也就是Bucket聚合 Bucket聚合语法 语法如下 GET /hotel/_search {size: 0, // 设置size为0结果中不包含文档只包含聚合结果aggs: { // 定义聚合brandAgg: { //给聚合起个名字terms: { // 聚合的类型按照品牌值聚合所以选择termfield: brand, // 参与聚合的字段size: 20 // 希望获取的聚合结果数量}}} }结果如图 聚合结果排序 默认情况下Bucket聚合会统计Bucket内的文档数量记为_count并且按照_count降序排序 我们可以指定order属性自定义聚合的排序方式 GET /hotel/_search {size: 0, aggs: {brandAgg: {terms: {field: brand,order: {_count: asc // 按照_count升序排列},size: 20}}} }限定聚合范围 默认情况下Bucket聚合是对索引库的所有文档做聚合但真实场景下用户会输入搜索条件因此聚合必须是对搜索结果聚合。那么聚合必须添加限定条件 我们可以限定要聚合的文档范围只要添加query条件即可 GET /hotel/_search {query: {range: {price: {lte: 200 // 只对200元以下的文档聚合}}}, size: 0, aggs: {brandAgg: {terms: {field: brand,size: 20}}} }这次聚合得到的品牌明显变少了 Metric聚合语法 上面我们对酒店按照品牌分组形成了一个个桶。现在我们需要对桶内的酒店做运算获取每个品牌的用户评分的min、max、avg等值 这就要用到Metric聚合了例如stat聚合就可以获取min、max、avg等结果 语法如下 GET /hotel/_search {size: 0, aggs: {brandAgg: { terms: { field: brand, size: 20},aggs: { // 是brands聚合的子聚合也就是分组后对每组分别计算score_stats: { // 聚合名称stats: { // 聚合类型这里stats可以计算min、max、avg等field: score // 聚合字段这里是score}}}}} }这次的score_stats聚合是在brandAgg的聚合内部嵌套的子聚合。因为我们需要在每个桶分别计算 另外我们还可以给聚合结果做个排序例如按照每个桶的酒店平均分做排序 RestAPI实现聚合 API语法 聚合条件与query条件同级别因此需要使用request.source()来指定聚合条件 聚合条件的语法 聚合的结果也与查询结果不同API也比较特殊。不过同样是JSON逐层解析 自动补全 当用户在搜索框输入字符时我们应该提示出与该字符有关的搜索项如图 这种根据用户输入的字母提示完整词条的功能就是自动补全了 因为需要根据拼音字母来推断因此要用到拼音分词功能 拼音分词器 要实现根据字母做补全就必须对文档按照拼音分词。在GitHub上恰好有elasticsearch的拼音分词插件 安装方式与IK分词器一样分三步 下载解压 上传到虚拟机中elasticsearch的plugin目录 重启elasticsearch 测试用法如下 POST /_analyze {text: 如家酒店还不错,analyzer: pinyin }结果 自定义分词器 默认的拼音分词器会将每个汉字单独分为拼音而我们希望的是每个词条形成一组拼音需要对拼音分词器做个性化定制形成自定义分词器 elasticsearch中分词器analyzer的组成包含三部分 character filters在tokenizer之前对文本进行处理。例如删除字符、替换字符tokenizer将文本按照一定的规则切割成词条term。例如keyword就是不分词还有ik_smarttokenizer filter将tokenizer输出的词条做进一步处理。例如大小写转换、同义词处理、拼音处理等 文档分词时会依次由这三部分来处理文档 声明自定义分词器的语法如下 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: {name:{type: text,analyzer: my_analyzer,search_analyzer: ik_smart}}} }测试 自动补全查询 elasticsearch提供了Completion Suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询的效率对于文档中字段的类型有一些约束 参与补全查询的字段必须是completion类型字段的内容一般是用来补全的多个词条形成的数组 比如一个这样的索引库 # 创建索引库 PUT test {mappings: {properties: {title:{type: completion}}} }然后插入下面的数据 # 示例数据 POST test/_doc {title: [Sony, WH-1000XM3] } POST test/_doc {title: [SK-II, PITERA] } POST test/_doc {title: [Nintendo, switch] }查询的DSL语句如下 # 自动补全查询 GET /test/_search {suggest: {title_suggest: {text: s, // 关键字completion: {field: title, // 补全查询的字段skip_duplicates: true, // 跳过重复的size: 10 // 获取前10条结果}}} }实现酒店搜索框自动补全 现在我们的hotel索引库还没有设置拼音分词器需要修改索引库中的配置。但是我们知道索引库是无法修改的只能删除然后重新创建 另外我们需要添加一个字段用来做自动补全将brand、suggestion、city等都放进去作为自动补全的提示 因此总结一下我们需要做的事情包括 修改hotel索引库结构设置自定义拼音分词器 修改索引库的name、all字段使用自定义分词器 索引库添加一个新字段suggestion类型为completion类型使用自定义的分词器 给HotelDoc类添加suggestion字段内容包含brand、business 重新导入数据到hotel库 修改酒店映射结构 // 酒店数据索引库 PUT /hotel {settings: {analysis: {analyzer: {text_anlyzer: {tokenizer: ik_max_word,filter: py},completion_analyzer: {tokenizer: keyword,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},name:{type: text,analyzer: text_anlyzer,search_analyzer: ik_smart,copy_to: all},address:{type: keyword,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: text_anlyzer,search_analyzer: ik_smart},suggestion:{type: completion,analyzer: completion_analyzer}}} }修改HotelDoc实体 HotelDoc中要添加一个字段用来做自动补全内容可以是酒店品牌、城市、商圈等信息。按照自动补全字段的要求最好是这些字段的数组 因此我们在HotelDoc中添加一个suggestion字段类型为ListString然后将brand、city、business等信息放到里面 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 ListString 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();// 组装suggestionif(this.business.contains(/)){// business有多个值需要切割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);}} }重新导入 重新执行之前编写的导入数据功能可以看到新的酒店数据中包含了suggestion 自动补全查询的JavaAPI 之前我们学习了自动补全查询的DSL而没有学习对应的JavaAPI这里给出一个示例 而自动补全的结果也比较特殊解析的代码如下 实现搜索框自动补全 查看前端页面可以发现当我们在输入框键入时前端会发起ajax请求 返回值是补全词条的集合类型为ListString 在cn.itcast.hotel.web包下的HotelController中添加新接口接收新的请求 GetMapping(suggestion) public ListString getSuggestions(RequestParam(key) String prefix) {return hotelService.getSuggestions(prefix); }在cn.itcast.hotel.service包下的IhotelService中添加方法 ListString getSuggestions(String prefix);在cn.itcast.hotel.service.impl.HotelService中实现该方法 Override public ListString getSuggestions(String prefix) {try {// 1.准备RequestSearchRequest request new SearchRequest(hotel);// 2.准备DSLrequest.source().suggest(new SuggestBuilder().addSuggestion(suggestions,SuggestBuilders.completionSuggestion(suggestion).prefix(prefix).skipDuplicates(true).size(10)));// 3.发起请求SearchResponse response client.search(request, RequestOptions.DEFAULT);// 4.解析结果Suggest suggest response.getSuggest();// 4.1.根据补全查询名称获取补全结果CompletionSuggestion suggestions suggest.getSuggestion(suggestions);// 4.2.获取optionsListCompletionSuggestion.Entry.Option options suggestions.getOptions();// 4.3.遍历ListString list new ArrayList(options.size());for (CompletionSuggestion.Entry.Option option : options) {String text option.getText().toString();list.add(text);}return list;} catch (IOException e) {throw new RuntimeException(e);} }数据同步 elasticsearch中的酒店数据来自于mysql数据库因此mysql数据发生改变时elasticsearch也必须跟着改变这个就是elasticsearch与mysql之间的数据同步 思路分析 常见的数据同步方案有三种 同步调用异步通知监听binlog 同步调用 基本步骤如下 hotel-demo对外提供接口用来修改elasticsearch中的数据酒店管理服务在完成数据库操作后直接调用hotel-demo提供的接口 异步通知 流程如下 hotel-admin对mysql数据库数据完成增、删、改后发送MQ消息hotel-demo监听MQ接收到消息后完成elasticsearch数据修改 监听binlog 流程如下 给mysql开启binlog功能mysql完成增、删、改操作都会记录在binlog中hotel-demo基于canal监听binlog变化实时更新elasticsearch中的内容 选择 方式一同步调用 优点实现简单粗暴缺点业务耦合度高 方式二异步通知 优点低耦合实现难度一般缺点依赖mq的可靠性 方式三监听binlog 优点完全解除服务间耦合缺点开启binlog增加数据库负担、实现复杂度高 实现数据同步 思路 利用课前资料提供的hotel-admin项目作为酒店管理的微服务。当酒店数据发生增、删、改时要求对elasticsearch中数据也要完成相同操作。 步骤 导入课前资料提供的hotel-admin项目启动并测试酒店数据的CRUD 声明exchange、queue、RoutingKey 在hotel-admin中的增、删、改业务中完成消息发送 在hotel-demo中完成消息监听并更新elasticsearch中数据 启动并测试数据同步功能 导入demo 导入课前资料提供的hotel-admin项目 运行后访问 http://localhost:8099 其中包含了酒店的CRUD功能 声明交换机、队列 引入依赖 在hotel-admin、hotel-demo中引入rabbitmq的依赖 !--amqp-- dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-amqp/artifactId /dependency配置mq地址 spring:rabbitmq:host: 192.168.1.12port: 5672username: xiaowupassword: 123321virtual-host: /声明队列交换机名称 在hotel-admin和hotel-demo中的cn.itcast.hotel.constatnts包下新建一个类MqConstants public class MqConstants {/*** 交换机*/public final static String HOTEL_EXCHANGE hotel.topic;/*** 监听新增和修改的队列*/public final static String HOTEL_INSERT_QUEUE hotel.insert.queue;/*** 监听删除的队列*/public final static String HOTEL_DELETE_QUEUE hotel.delete.queue;/*** 新增或修改的RoutingKey*/public final static String HOTEL_INSERT_KEY hotel.insert;/*** 删除的RoutingKey*/public final static String HOTEL_DELETE_KEY hotel.delete; }声明队列交换机 在hotel-demo中定义配置类声明队列、交换机 Configuration public class MqConfig {Beanpublic TopicExchange topicExchange(){return new TopicExchange(MqConstants.HOTEL_EXCHANGE, true, false);}Beanpublic Queue insertQueue(){return new Queue(MqConstants.HOTEL_INSERT_QUEUE, true);}Beanpublic Queue deleteQueue(){return new Queue(MqConstants.HOTEL_DELETE_QUEUE, true);}Beanpublic Binding insertQueueBinding(){return BindingBuilder.bind(insertQueue()).to(topicExchange()).with(MqConstants.HOTEL_INSERT_KEY);}Beanpublic Binding deleteQueueBinding(){return BindingBuilder.bind(deleteQueue()).to(topicExchange()).with(MqConstants.HOTEL_DELETE_KEY);} }发送MQ消息 在hotel-admin中的增、删、改业务中分别发送MQ消息 接收MQ消息 hotel-demo接收到MQ消息要做的事情包括 新增消息根据传递的hotel的id查询hotel信息然后新增一条数据到索引库删除消息根据传递的hotel的id删除索引库中的一条数据 首先在hotel-demo的cn.itcast.hotel.service包下的IHotelService中新增新增、删除业务 void deleteById(Long id);void insertById(Long id);给hotel-demo中的cn.itcast.hotel.service.impl包下的HotelService中实现业务 Override public void deleteById(Long id) {try {// 1.准备RequestDeleteRequest request new DeleteRequest(hotel, id.toString());// 2.发送请求client.delete(request, RequestOptions.DEFAULT);} catch (IOException e) {throw new RuntimeException(e);} }Override public void insertById(Long id) {try {// 0.根据id查询酒店数据Hotel hotel getById(id);// 转换为文档类型HotelDoc hotelDoc new HotelDoc(hotel);// 1.准备Request对象IndexRequest request new IndexRequest(hotel).id(hotel.getId().toString());// 2.准备Json文档request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);// 3.发送请求client.index(request, RequestOptions.DEFAULT);} catch (IOException e) {throw new RuntimeException(e);} }编写监听器在hotel-demo中的cn.itcast.hotel.mq包新增一个类 Component public class HotelListener {Autowiredprivate IHotelService hotelService;/*** 监听酒店新增或修改的业务* param id 酒店id*/RabbitListener(queues MqConstants.HOTEL_INSERT_QUEUE)public void listenHotelInsertOrUpdate(Long id){hotelService.insertById(id);}/*** 监听酒店删除的业务* param id 酒店id*/RabbitListener(queues MqConstants.HOTEL_DELETE_QUEUE)public void listenHotelDelete(Long id){hotelService.deleteById(id);} }ES集群 单机的elasticsearch做数据存储必然面临两个问题海量数据存储问题、单点故障问题。 海量数据存储问题将索引库从逻辑上拆分为N个分片shard存储到多个节点单点故障问题将分片数据在不同节点备份replica ES集群相关概念: 集群cluster一组拥有共同的 cluster name 的 节点节点node) 集群中的一个 Elasticearch 实例分片shard索引可以被拆分为不同的部分进行存储称为分片。在集群环境下一个索引的不同分片可以拆分到不同的节点中 解决问题数据量太大单点存储量有限的问题 此处我们把数据分成3片shard0、shard1、shard2主分片Primary shard相对于副本分片的定义副本分片Replica shard每个主分片可以有一个或者多个副本数据和主分片一样 数据备份可以保证高可用但是每个分片备份一份所需要的节点数量就会翻一倍成本实在是太高了 为了在高可用和成本间寻求平衡我们可以这样做 首先对数据分片存储到不同节点然后对每个分片进行备份放到对方节点完成互相备份 这样可以大大减少所需要的服务节点数量如图我们以3分片每个分片备份一份为例 现在每个分片都有1个备份存储在3个节点 node0保存了分片0和1node1保存了分片0和2node2保存了分片1和2 搭建集群 创建es集群 首先编写一个docker-compose文件内容如下 version: 2.2 services:es01:image: elasticsearch:7.12.1 #镜像container_name: es01 #容器名称environment: #环境变量- node.namees01 #节点名称- cluster.namees-docker-cluster #集群名称- discovery.seed_hostses02,es03 #集群中其他节点的地址- cluster.initial_master_nodeses01,es02,es03 #初始化的主节点- ES_JAVA_OPTS-Xms512m -Xmx512m #JVM堆内存大小volumes:- data01:/usr/share/elasticsearch/dataports:- 9200:9200networks:- elastices02:image: elasticsearch:7.12.1container_name: es02environment:- node.namees02- cluster.namees-docker-cluster- discovery.seed_hostses01,es03- cluster.initial_master_nodeses01,es02,es03- ES_JAVA_OPTS-Xms512m -Xmx512mvolumes:- data02:/usr/share/elasticsearch/dataports:- 9201:9200networks:- elastices03:image: elasticsearch:7.12.1container_name: es03environment:- node.namees03- cluster.namees-docker-cluster- discovery.seed_hostses01,es02- cluster.initial_master_nodeses01,es02,es03- ES_JAVA_OPTS-Xms512m -Xmx512mvolumes:- data03:/usr/share/elasticsearch/datanetworks:- elasticports:- 9202:9200 volumes:data01:driver: localdata02:driver: localdata03:driver: localnetworks:elastic:driver: bridgees运行需要修改一些linux系统权限修改/etc/sysctl.conf文件 vi /etc/sysctl.conf添加下面的内容 vm.max_map_count262144然后执行命令让配置生效 sysctl -p通过docker-compose启动集群 docker-compose up -d集群状态监控 kibana可以监控es集群不过新版本需要依赖es的x-pack 功能配置比较复杂 这里推荐使用cerebro来监控es集群状态解压即可使用非常方便 解压好的目录如下 进入对应的bin目录 双击其中的cerebro.bat文件即可启动服务 访问http://localhost:9000 即可进入管理界面 输入你的elasticsearch的任意节点的地址和端口点击connect即可 绿色的条代表集群处于绿色健康状态 集群脑裂问题 集群脑裂问题 elasticsearch中集群节点有不同的职责划分 默认情况下集群中的任何一个节点都同时具备上述四种角色 但是真实的集群一定要将集群职责分离 master节点对CPU要求高但是内存要求低data节点对CPU和内存要求都高coordinating节点对网络带宽、CPU要求高 职责分离可以让我们根据不同节点的需求分配不同的硬件去部署。而且避免业务之间的互相干扰。 一个典型的es集群职责划分如图 脑裂问题 脑裂是因为集群中的节点失联导致的 例如一个集群中主节点与其它节点失联 此时node2和node3认为node1宕机就会重新选主 当node3当选后集群继续对外提供服务node2和node3自成集群node1自成集群两个集群数据不同步出现数据差异 当网络恢复后因为集群中有两个master节点集群状态的不一致出现脑裂的情况 解决脑裂的方案是要求选票超过 ( eligible节点数量 1 / 2 才能当选为主因此eligible节点数量最好是奇数。对应配置项是discovery.zen.minimum_master_nodes在es7.0以后已经成为默认配置因此一般不会发生脑裂问题 例如3个节点形成的集群选票必须超过 3 1 / 2 也就是2票。node3得到node2和node3的选票当选为主。node1只有自己1票没有当选。集群中依然只有1个主节点没有出现脑裂 集群分布式存储 当新增文档时应该保存到不同分片保证数据均衡那么coordinating node如何确定数据该存储到哪个分片呢 分片存储测试 插入三条数据 测试可以看到三条数据分别在不同分片 结果 分片存储原理 elasticsearch会通过hash算法来计算文档应该存储到哪个分片 说明 _routing默认是文档的id算法与分片数量有关因此索引库一旦创建分片数量不能修改 新增文档的流程如下 集群分布式查询 elasticsearch的查询分成两个阶段 scatter phase分散阶段coordinating node会把请求分发到每一个分片 gather phase聚集阶段coordinating node汇总data node的搜索结果并处理为最终结果集返回给用户 集群故障转移 集群的master节点会监控集群中的节点状态如果发现有节点宕机会立即将宕机节点的分片数据迁移到其它节点确保数据安全这个叫做故障转移 例如一个集群结构如图 现在node1是主节点其它两个节点是从节点突然node1发生了故障 宕机后的第一件事需要重新选主例如选中了node2 node2成为主节点后会检测集群监控状态发现shard-1、shard-0没有副本节点。因此需要将node1上的数据迁移到node2、node3
http://www.hkea.cn/news/14484687/

相关文章:

  • 做美食的网站有那一些做网页用什么编程语言
  • 网站开发费用摊销时间百度网址导航主页
  • 360网站怎么建设在线3d设计家官网
  • 郴州 网站建设wordpress action edit
  • 微信红包建设网站seoaoo
  • tp5企业网站开发毕业设计网站
  • 建微网站有什么好处做动态效果的网站
  • 网站备案要钱么网站建设提供排名
  • 石家庄网站app制作wordpress多用户 2015
  • 做个人的网站怎么做如何制作一个购物网站
  • 能打开网站的浏览器东莞网站建设备案
  • 音乐设计网站推荐中国和住房城乡建设部网站
  • wordpress主题函数CHM台州网站优化公司
  • 室内设计在线生成厦门seo优化外包公司
  • 网站制作好了怎么上传网站开发浏览器
  • 东莞网站建设运营cargo创建个人网站
  • 静态网站开发课程相关新闻百度搜索风云榜排行榜
  • aspnet网站开发到部署流程建站的公司
  • 网站的锚点链接怎么做上海高端室内设计事务所
  • 重庆蒲公英网站建设公司百度推广平台收费标准
  • 建设银行手机银行网站用户名是什么意思室内装修设计软件推荐
  • 怎么样建公司网站网页设计与制作教程 刘瑞新
  • 如何提高网站浏览量好用的免费网站建设
  • 上海有色金属门户网站官方网站旗舰店
  • rails 开发的网站开发阳谷网页设计
  • 网站建设方案范文杭州做兼职网站建设
  • 网站开发 理念怎么写松江新城投资建设有限公司网站
  • 专门做代工产品的网站我的小程序入口
  • 昆明免费建站模板实惠高端网站设计品牌
  • 服装公司做哪个网站ugc网站开发