ElasticSearch 查询与 Java API 实践(上)

简介: ElasticSearch 查询与 Java API 实践

一、ElasticSearch文档分值_score计算底层原理


1)boolean model


根据用户的query条件,先过滤出包含指定term的doc


query "hello world" ‐‐> hello / world / hello & world 
 bool ‐‐> must/must not/should ‐‐> 过滤 ‐‐> 包含 / 不包含 / 可能包含 
 doc ‐‐> 不打分数 ‐‐> 正或反 true or false ‐‐> 为了减少后续要计算的doc的数量,提升性能 


2) relevance score算法,简单来说,就是计算出,一个索引中的文本,与搜索文本,他们之间的关联匹配程度


Elasticsearch使用的是 term frequency/inverse document frequency算法,简称为

TF/IDF算法


Term frequency:搜索文本中的各个词条在field文本中出现了多少次,出现次数越多,就越相关


搜索请求:hello world 
 doc1:hello you, and world is very good 
 doc2:hello, how are you 


Inverse document frequency:搜索文本中的各个词条在整个索引的所有文档中出现了多少次,出现的次数越多,就越不相关


搜索请求:hello world 
 doc1:hello, tuling is very good6
 doc2:hi world, how are you 


比如说,在index中有1万条document,hello这个单词在所有的document中,一共出现了 1000次;world这个单词在所有的document中,一共出现了100次


Field-length norm:field长度,field越长,相关度越弱


搜索请求:hello world


doc1:{ "title": "hello article", "content": "...... N个单词" } 
 doc2:{ "title": "my article", "content": "...... N个单词,hi world" } 


hello world在整个index中出现的次数是一样多的


doc1更相关,title field更短


**2、分析一个document上的_score是如何被计算出来的 **


GET /es_db/_doc/1/_explain 
 { 
 "query": { 
 "match": { 
 "remark": "java developer" 
 } 
 } 
 } 


3、vector space model (向量空间模型)


多个term对一个doc的总分数hello world --> es会根据hello world在所有doc中的评分情况,计算出一个query vector,query 向量


hello这个term,给的基于所有doc的一个评分就是2


world这个term,给的基于所有doc的一个评分就是5


[2, 5]


query vector


doc vector,3个doc,一个包含1个term,一个包含另一个term,一个包含2个term

3个doc


doc1:包含hello --> [2, 0]

doc2:包含world --> [0, 5]

doc3:包含hello, world --> [2, 5]


会给每一个doc,拿每个term计算出一个分数来,hello有一个分数,world有一个分数,再拿所有term的分数组成一个doc vector 画在一个图中,取每个doc vector对query vector的弧度,给出每个doc对多个term的总分数


每个doc vector计算出对query vector的弧度,最后基于这个弧度给出一个doc相对于query中多个 term的总分数


弧度越大,分数月底; 弧度越小,分数越高


如果是多个term,那么就是线性代数来计算,无法用图表示


image.png


二、es生产集群部署之针对生产集群的脑裂问题专门定制的重要参数


**集群脑裂是什么? **


所谓脑裂问题,就是同一个集群中的不同节点,对于集群的状态有了不一样的理解, 比如集群中存在两个master ,如果因为网络的故障,导致一个集群被划分成了两片,每片都有多个node,以及一个 master,那么集群中就出现了两个master了。


但是因为master是集群中非常重要的一个角色,主宰了集群状态的维护,以及shard的分配,因此如果有两个master,可能会导致破坏数据。


如:


image.png


节点1在启动时被选举为主节点并保存主分片标记为0P,而节点2保存复制分片标记为

0R 现在,如果在两个节点之间的通讯中断了,会发生什么?由于网络问题或只是因为其

中一个节点无响应,这是有可能发生的。


image.png


两个节点都相信对方已经挂了。节点1不需要做什么,因为它本来就被选举为主节点。但是节点2会自动选举它自己为主节点,因为它相信集群的一部分没有主节点了。


在elasticsearch集群,是有主节点来决定将分片平均的分布到节点上的。节点2保存的是

复制分片,但它相信主节点不可用了。所以它会自动提升复制节点为主节点。


image.png


现在我们的集群在一个不一致的状态了。打在节点1上的索引请求会将索引数据分配在主节点,同时打在节点2的请求会将索引数据放在分片上。在这种情况下,分片的两份数据分开了,如果不做一个全量的重索引很难对它们进行重排序。在更坏的情况下,一个对集群无感知的索引客户端(例如,使用REST接口的),这个问题非常透明难以发现,无论哪个节点被命中索引请求仍然在每次都会成功完成。问题只有在搜索数据时才会被隐约发现:取决于搜索请求命中了哪个节点,结果都会不同。


那么那个参数的作用,就是告诉es直到有足够的master候选节点时,才可以选举出一个

master,否则就不要选举出一个master。这个参数必须被设置为集群中master候选节点的quorum数量,也就是大多数。至于quorum的算法,就是:master候选节点数量 / 2 + 1。


比如我们有10个节点,都能维护数据,也可以是master候选节点,那么quorum就是10 / 2 + 1 = 6。


如果我们有三个master候选节点,还有100个数据节点,那么quorum就是3 / 2 + 1 = 2


如果我们有2个节点,都可以是master候选节点,那么quorum是2 / 2 + 1 = 2。此时就有问题了,因为如果一个node挂掉了,那么剩下一个master候选节点,是无法满足quorum数量的,也就无法选举出新的master,集群就彻底挂掉了。此时就只能将这个参数设置为1,但是这就无法阻止脑裂的发生了。


2个节点,discovery.zen.minimum_master_nodes分别设置成2和1会怎么样


综上所述,一个生产环境的es集群,至少要有3个节点,同时将这个参数设置为quorum,也就是2。discovery.zen.minimum_master_nodes设置为2,如何避免脑裂呢?


那么这个是参数是如何避免脑裂问题的产生的呢?比如我们有3个节点,quorum是2. 现在网络故障,1个节点在一个网络区域,另外2个节点在另外一个网络区域,不同的网络区域内无法通信。这个时候有两种情况情况:


(1)如果master是单独的那个节点,另外2个节点是master候选节点,那么此时那个单独的master节点因为没有指定数量的候选master node在自己当前所在的集群内,因此就会取消当前master的角色,尝试重新选举,但是无法选举成功。然后另外一个网络区域内的node因为无法连接到master,就会发起重新选举,因为有两个master候选节点,满足了quorum,因此可以成功选举出一个master。此时集群中就会还是只有一个master。


(2)如果master和另外一个node在一个网络区域内,然后一个node单独在一个网络域

内。那么此时那个单独的node因为连接不上master,会尝试发起选举,但是因为master候选节点数量不到quorum,因此无法选举出master。而另外一个网络区域内,原先的那个master还会继续工作。这也可以保证集群内只有一个master节点。


综上所述,集群中master节点的数量至少3台,三台主节点通过在elasticsearch.yml中配置discovery.zen.minimum_master_nodes: 2,就可以避免脑裂问题的产生。


二、数据建模


1、案例


**案例:  设计一个用户document数据类型,其中包含一个地址数据的数组,这种设计方式 **


**相对复杂,但是在管理数据时,更加的灵活。 **


PUT /user_index 
 { 
 "mappings": { 
 "properties": { 
 "login_name" : { 
 "type" : "keyword" 
 }, 
 "age " : { 
 "type" : "short" 
 }, 
 "address" : { 
 "properties": { 
 "province" : { 
 "type" : "keyword" 
 }, 
 "city" : { 
 "type" : "keyword" }, 
 "street" : { 
 "type" : "keyword" 
 } 
 } 
 } 
 } 
 } 
 } 


但是上述的数据建模有其明显的缺陷,就是针对地址数据做数据搜索的时候,经常会搜索出不必要的数据,如:在下述数据环境中,搜索一个province为北京,city为天津的用

户。


PUT /user_index/_doc/1
{
  "login_name": "jack",
  "age": 25,
  "address": [
    {
      "province": "北京",
      "city": "北京",
      "street": "枫林三路"
    },
    {
      "province": "天津",
      "city": "天津",
      "street": "华夏路"
    }
  ]
} 
PUT /user_index/_doc/2
{
  "login_name": "rose",
  "age": 21,
  "address": [
    {
      "province": "河北",
      "city": "廊坊",
      "street": "燕郊经济开发区"
    },
    {
      "province": "天津",
      "city": "天津",
      "street": "华夏路"
    }
  ]
} 


执行的搜索应该如下:


GET /user_index/_search 
 { 
 "query": { 
 "bool": { 
 "must": [ 
 { 
 "match": { 
 "address.province": "北京" 
 } 
 }, 
 { 
 "match": { 
 "address.city": "天津" 
 } 
 } 
 ] 
 } 
 } 
 } 


但是得到的结果并不准确,这个时候就需要使用nested object来定义数据建模。


2、nested object


使用nested object作为地址数组的集体类型,可以解决上述问题,document模型如下:


PUT /user_index 
{ 
"mappings": { 
"properties": { 
"login_name" : { 
"type" : "keyword" 
}, 
"age" : { 
"type" : "short" 
}, 
"address" : { 
"type": "nested", 
"properties": { 
"province" : {
"type" : "keyword" 
}, 
"city" : { 
"type" : "keyword" 
}, 
"street" : { 
"type" : "keyword" 
} 
} 
} 
} 
} 
} 


这个时候就需要使用nested对应的搜索语法来执行搜索了,语法如下:


GET /user_index/_search 
{ 
"query": { 
"bool": { 
"must": [ 
{ 
"nested": { 
"path": "address", 
"query": { 
"bool": { 
"must": [ 
{ 
"match": { 
"address.province": "北京" 
} 
}, 
{ 
"match": { 
"address.city": "天津" 
} 
} 
] 
} 
} 
} 
} 
] 
} 
} 
}


虽然语法变的复杂了,但是在数据的读写操作上都不会有错误发生,是推荐的设计方式。


其原因是:


普通的数组数据在ES中会被扁平化处理,处理方式如下:(如果字段需要分词,会将分词数据保存在对应的字段位置,当然应该是一个倒排索引,这里只是一个直观的案例)


{ 
 "login_name" : "jack", 
 "address.province" : [ "北京", "天津" ], 
 "address.city" : [ "北京", "天津" ] 
 "address.street" : [ "西三旗东路", "古文化街" ] 
 } 


那么nested object数据类型ES在保存的时候不会有扁平化处理,保存方式如下:所以在搜索的时候一定会有需要的搜索结果。


{ 
"login_name" : "jack" 
} 
{ 
"address.province" : "北京", 
"address.city" : "北京", 
"address.street" : "西三旗东路" 
} 
{ 
"address.province" : "北京", 
"address.city" : "北京", 
"address.street" : "西三旗东路", 
}


相关实践学习
以电商场景为例搭建AI语义搜索应用
本实验旨在通过阿里云Elasticsearch结合阿里云搜索开发工作台AI模型服务,构建一个高效、精准的语义搜索系统,模拟电商场景,深入理解AI搜索技术原理并掌握其实现过程。
ElasticSearch 最新快速入门教程
本课程由千锋教育提供。全文搜索的需求非常大。而开源的解决办法Elasricsearch(Elastic)就是一个非常好的工具。目前是全文搜索引擎的首选。本系列教程由浅入深讲解了在CentOS7系统下如何搭建ElasticSearch,如何使用Kibana实现各种方式的搜索并详细分析了搜索的原理,最后讲解了在Java应用中如何集成ElasticSearch并实现搜索。  
相关文章
|
5月前
|
缓存 监控 前端开发
顺企网 API 开发实战:搜索 / 详情接口从 0 到 1 落地(附 Elasticsearch 优化 + 错误速查)
企业API开发常陷参数、缓存、错误处理三大坑?本指南拆解顺企网双接口全流程,涵盖搜索优化、签名验证、限流应对,附可复用代码与错误速查表,助你2小时高效搞定开发,提升响应速度与稳定性。
|
7月前
|
JSON Java API
【干货满满】分享京东API接口到手价,用Java语言实现
本示例使用 Java 调用京东开放平台商品价格及优惠信息 API,通过商品详情和促销接口获取到手价(含优惠券、满减等),包含签名生成、HTTP 请求及响应解析逻辑,适用于比价工具、电商系统集成等场景。
|
12月前
|
缓存 监控 负载均衡
如何提升 API 性能:来自 Java 和测试开发者的优化建议
本文探讨了如何优化API响应时间,提升用户体验。通过缓存(如Redis/Memcached)、减少数据负载(REST过滤字段或GraphQL精确请求)、负载均衡(Nginx/AWS等工具)、数据压缩(Gzip/Brotli)、限流节流、监控性能(Apipost/New Relic等工具)、升级基础设施、减少第三方依赖、优化数据库查询及采用异步处理等方式,可显著提高API速度。快速响应的API不仅让用户满意,还能增强应用整体性能。
|
8月前
|
Java Apache 开发者
解决java.lang.IllegalArgumentException: Invalid uri由无效查询引起的问题
最后,当你修改代码以避免这个异常时,保持代码的整洁和可读性同样重要。注释你的代码,用意图清晰的方法名,并确保逻辑简单明了,这样在未来你或其他开发者需要时可以轻松地维护它。
938 20
|
7月前
|
JSON Java API
【干货满满】分享拼多多API接口到手价,用Java语言实现
本方案基于 Java 实现调用拼多多开放平台商品详情 API,通过联盟接口获取商品到手价(含拼团折扣与优惠券),包含签名生成、HTTP 请求及响应解析逻辑,适用于电商比价、导购系统集成。
|
7月前
|
JSON Java API
【干货满满】分享淘宝API接口到手价,用Java语言实现
本文介绍了如何使用 Java 调用淘宝开放平台 API 获取商品到手价,涵盖依赖配置、签名生成、HTTP 请求与响应解析等核心实现步骤。
|
9月前
|
SQL Java 数据库
解决Java Spring Boot应用中MyBatis-Plus查询问题的策略。
保持技能更新是侦探的重要素质。定期回顾最佳实践和新技术。比如,定期查看MyBatis-Plus的更新和社区的最佳做法,这样才能不断提升查询效率和性能。
461 1
|
8月前
|
JSON JavaScript 前端开发
Python+JAVA+PHP语言,苏宁商品详情API
调用苏宁商品详情API,可通过HTTP/HTTPS发送请求并解析响应数据,支持多种编程语言,如JavaScript、Java、PHP、C#、Ruby等。核心步骤包括构造请求URL、发送GET/POST请求及解析JSON/XML响应。不同语言示例展示了如何获取商品名称与价格等信息,实际使用时请参考苏宁开放平台最新文档以确保兼容性。
|
12月前
|
前端开发 Cloud Native Java
Java||Springboot读取本地目录的文件和文件结构,读取服务器文档目录数据供前端渲染的API实现
博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
Java||Springboot读取本地目录的文件和文件结构,读取服务器文档目录数据供前端渲染的API实现
|
12月前
|
缓存 安全 Java
《从头开始学java,一天一个知识点》之:字符串处理:String类的核心API
🌱 **《字符串处理:String类的核心API》一分钟速通!** 本文快速介绍Java中String类的3个高频API:`substring`、`indexOf`和`split`,并通过代码示例展示其用法。重点提示:`substring`的结束索引不包含该位置,`split`支持正则表达式。进一步探讨了String不可变性的高效设计原理及企业级编码规范,如避免使用`new String()`、拼接时使用`StringBuilder`等。最后通过互动解密游戏帮助读者巩固知识。 (上一篇:《多维数组与常见操作》 | 下一篇预告:《输入与输出:Scanner与System类》)
321 11