赞
踩
1、ElasticSearch的产生。
以我的理解:比如有这样一个需求,比如我们租房,想在北京范围内搜索带“融泽”的小区或者描述,我们首先想到数据库查询select * from house where distinct like %融泽% and des like %融泽%,但是我们还想这个融泽匹配相关评论,我们再次and 加上这个字段吗,但是匹配好多字段都要and吗,还要这些字段是多个表关联级联查询,这样的sql不是太复杂了吗,查询速度很慢,不符合并发量的情况还不好优化。这样ElasticSearch诞生了呢。他就是解决这样的问题,它提供了一个分布式多用户能力的全文搜索引擎相关说明自己百度都可以找到,主要它存的数据是文档的。
2、具体ElasticSearch检索速度比MySQL快呢?想听深层次原理。
存储的数据文档类型的json数据,比如:
{
“settings”: {
“number_of_shards”: 3,
“number_of_replicas”: 0
},
“mappings”: {
“man”: {
“properties”: {
“name”: {
“type”: “text”
},
“data”: {
“type”: “date”,
“format”: “yyyy-MM-dd”
}
}
}
}
}
解释:pepole是索引、man是类型 pepole相当于数据库 man相当于数据库中的表,里面的name相当于数据库里面的字段。像mysql一个数据库有好多表但是ElasticSearch相当于一个索引就有一个类型。
比如年龄在 18 和 30 之间,性别为女性这样的组合查询。倒排索引很多地方都有介绍,但是其比关系型数据库的 b-tree 索引快在哪里?到底为什么快呢?
比如ElasticSearch存储这些数据,这里面类型(相当于mysql数据库)和索引(相当于mysql的表)不说了,这里面docid相当于数据库的主键id,这里面叫倒排索引,一个字段有一个自己的倒排索引。18,20 这些叫做 term,而 [1,3] 就是 posting list。Posting list 就是一个 int 的数组,存储了所有符合某个 term 的文档 id。那么什么是 term dictionary 和 term index?
假设我们有很多个 term,比如:Carla,Sara,Elin,Ada,Patty,Kate,Selena
如果按照这样的顺序排列,找出某个特定的 term 一定很慢,因为 term 没有排序,需要全部过滤一遍才能找出特定的 term。
排序之后就变成了:Ada,Carla,Elin,Kate,Patty,Sara,Selena
这样我们可以用二分查找的方式,比全遍历更快地找出目标的 term。这个就是 term dictionary。有了 term dictionary 之后,可以用 logN 次磁盘查找得到目标。但是磁盘的随机读操作仍然是非常昂贵的(一次 random access 大概需要 10ms 的时间)。所以尽量少的读磁盘,有必要把一些数据缓存到内存里。但是整个 term dictionary 本身又太大了,无法完整地放到内存里。于是就有了 term index。term index 有点像一本字典的大的章节表。比如:
A 开头的 term ……………. Xxx 页
C 开头的 term ……………. Xxx 页
E 开头的 term ……………. Xxx 页
如果所有的 term 都是英文字符的话,可能这个 term index 就真的是 26 个英文字符表构成的了。但是实际的情况是,term 未必都是英文字符,term 可以是任意的 byte 数组。而且 26 个英文字符也未必是每一个字符都有均等的 term,比如 x 字符开头的 term 可能一个都没有,而 s 开头的 term 又特别多。实际的 term index 是一棵 trie 树:
例子是一个包含 “A”, “to”, “tea”, “ted”, “ten”, “i”, “in”, 和 “inn” 的 trie 树。这棵树不会包含所有的 term,它包含的是 term 的一些前缀。通过 term index 可以快速地定位到 term dictionary 的某个 begin,然后从这个位置再往后顺序查找,这句话的意思就是比如查字节A(年龄18的term index标识)到i(年龄20 term index 标识)的范围,通过term index查到 term dictionary的范围,再通过 term dictionary 找到文档的位置,然后就找到数据找到。再加上一些压缩技术(搜索 Lucene Finite State Transducers) term index 的尺寸可以只有所有 term 的尺寸的几十分之一,使得用内存缓存整个 term index 变成可能。整体上来说就是这样的效果。
现在我们可以回答“为什么 Elasticsearch/Lucene 检索可以比 mysql 快了。Mysql 只有 term dictionary 这一层,是以 b-tree 排序的方式存储在磁盘上的。检索一个 term 需要若干次的 random access 的磁盘操作。而 Lucene 在 term dictionary 的基础上添加了 term index 来加速检索,term index 以树的形式缓存在内存中。从 term index 查到对应的 term dictionary 的 block 位置之后,再去磁盘上找 term,大大减少了磁盘的 random access 次数。
3、ElasticSearch的搭建(我的也是基于阿里云)
推荐链接:https://www.cnblogs.com/yijialong/p/9707238.html
我就说的下安装ElasticSearch,Header插件和中文分词器都是按照上面的搭建的。但是你要搭建ElasticSearch需要安装jdk和maven.
一、下载与解压(前提安装jdk8版本)
下载: https://www.elastic.co/downloads/elasticsearch
下载完后:放在/usr/java/elasticseach下
解压:tar -xzf elasticsearch-6.3.2.tar.gz
2、官方文档上说ElasticSearch不适合在root管理员帐号下运行,所以要先建立一个账号专门运行ElasticSearch。以下是创建es组和其下用户es。
1 [root@izwz9eu3mkqq1njlkrfhc8z ~]# groupadd es
1 [root@izwz9eu3mkqq1njlkrfhc8z ~]# useradd -g es es
1 [root@izwz9eu3mkqq1njlkrfhc8z ~]# passwd 123456789
3、修改limits.conf与sysctl.conf文件的系统参数
soft nofile 65536
hard nofile 65536
soft nproc 4096
hard nproc 4096
4、编辑 sysctl.conf 文件并添加内容,因为max virtual memory areas vm.max_map_count increase to at least [262144]:
1 [root@izwz9eu3mkqq1njlkrfhc8z ~]# vim /etc/sysctl.conf
1 vm.max_map_count=262144
二、安装与配置
a、解压完成后,进入config目录,编辑elasticsearch.yml文件
vim /usr/java/elasticsearch/elasticsearch-6.3.2/config/elasticsearch.yml
#这是集群名字,起名为elasticsearch
#es启动后会将具有相同集群名字的节点放到一个集群下。
cluster.name: elasticsearch
#节点名字。
node.name: node1
path.data: /usr/java/elasticsearch/elasticsearch-6.3.2/data
path.logs: /usr/java/elasticsearch/elasticsearch-6.3.2/logs
#设置绑定的ip地址,可以是ipv4或ipv6的,默认为0.0.0.0
#network.bind_host: xxxxxx
#设置其它节点和该节点交互的ip地址,如果不设置它会自动设置,值必须是个真实的ip地址
#network.publish_host: xxxxxx
#同时设置bind_host和publish_host上面两个参数,该地址为默认地址(这样配置外网才可以访问)
network.host: 0.0.0.0
#transport.tcp.port: 9300
transport.tcp.compress: true
#http.port: 9200
#http.enabled: false
#discovery.zen.ping.unicast.hosts:[“节点1的 ip”,“节点2 的ip”,“节点3的ip”]
#这是一个集群中的主节点的初始列表,当节点(主节点或者数据节点)启动时使用这个列表进行探测
discovery.zen.ping.unicast.hosts: ["“0.0.0.0:9300”]
#指定集群中的节点中有几个有master资格的节点。
#对于大集群可以写(2-4)。
discovery.zen.minimum_master_nodes: 1
#解决head的集群健康值问题,后续会安装head插件
http.cors.enabled: true
http.cors.allow-origin: “*”
http.cors.allow-headers: Authorization,X-Requested-With,Content-Length,Content-Type
b、若服务器运行内存不大,也可能还有一个错误是关于Jvm内存分配的问题,需要修改Jvm配置。如下所示。
[root@izwz9eu3mkqq1njlkrfhc8z ~]# vim /usr/java/elasticsearch/elasticsearch-6.3.2/config/jvm.options
三、启动与测试
chomd -r es:es /usr/java/elasticseach 给当前这个目录赋予权限(赋予权限这一步不知写的对否,可以查下相关文章)
su es 切换到es账户
./bin/elasticseach -d 启动Elasticsearch 在后台启动
lsof -i:9200 查看9200端口
杀死进程 kill -9 id
浏览器访问:ip:9200
心得:才开始安装ElasticeSearch是在自己电脑上安装上的,但是不知怎么回事我的主机一直访问不通,先ping了一下ping不通,然后考虑是不是因为防火墙,然后关闭防火墙还是不能访问,是不是配置有问题,然后百度搜了一下,就在elasticsearch.yml的network.host: 0.0.0.0这样才能访问,然后就在阿里云安装,但是上面的情况都考虑还是访问不通,就自己百度一下是因为阿里云的环境的端口需要专门配置呢,不然防火墙是不让通过的。把Header插件和中文分词器都安装成功了可以访问,当时不满足单机版,然后安装集群的ElasticSearch,虽然安装完了但是报内存溢出,所以就改每个ElasticSearch改小点,但是还是内存溢出,然后考虑先放一下,后期再说,先把es集成完。
4、ElasticSearch的基本使用
工具:postMan一、
创建索引。http://ip:9200/people (put方式)
Content-Type:application/json; charset=utf-8
{
“settings”: {
“number_of_shards”: 3,
“number_of_replicas”: 0
},
“mappings”: {
“man”: {
“properties”: {
“name”: {
“type”: “text”
},
“data”: {
“type”: “date”,
“format”: “yyyy-MM-dd”
}
}
}
}
}
解释:pepole是索引、man是类型 pepole相当于数据库 man相当于数据库中的表记住一个索引只有一个类型,原因如下:
在ES6.0.0及更高的版本中,创建的索引只能包含一个映射类型。在6.0.0以下的版本中创建的一个索引映射多个类型的索引在6.0.0版本中继续发挥作用,但是将在7.0.0中完全删除。
为什么删除多个类型映射?
我们谈到了一个类似于SQL数据库中的“数据库”的“索引”,一个“类型”相当于一个“表”。这是一个糟糕的类比,导致了错误的假设。在SQL数据库中,表是相互独立的。一个表中的列与另一个表中具有相同名称的列没有关联。这不是映射类型中的字段的情况。在弹性搜索索引中,在不同的映射类型中具有相同名称的字段由相同的Lucene字段在内部支持。
换句话说:用户类型中的UsRyNoNd字段与Twitter类型中的UsSeriNoX字段完全相同的字段,并且两个用户名称字段必须在两种类型中都具有相同的映射(定义)。
例如,当您希望删除一个类型中的日期字段和在同一索引中的另一个类型中的布尔字段时,这可能会导致挫折。
除此之外,在同一索引中存储很少或没有相同字段的不同实体会导致稀疏数据并干扰Lucene高效压缩文档的能力。
如何解决多个类型映射问题?
第一种选择是每个文档类型都有索引。您可以在推特索引和用户索引中存储Twitter,而不是在单个Twitter索引中存储Twitter和用户。索引是完全独立的,因此在索引之间不会有字段类型冲突。
这种方法有两个好处:数据更可能是密集的,因此受益于Lucene中使用的压缩技术。在全文搜索中用于评分的术语更可能是准确的,因为同一索引中的所有文档都代表单个实体。 每个索引可以适当地为它包含的文档的数量大小:你可以为user使用较少数量的分片和用于Twitter的更大数量的分片。
二、插入操作:
指定文档id插入
put请求: ip:port/索引/类型/id
自动产生文档id插入
post请求: ip:port/索引/类型 (会自动生成ID)
http://127.0.0.1:9200/proper/man/1 put
{
“name”: “瓦力”,
“country”: “china”,
“age”: 30,
“date”: “1987-03-07”
}
http://127.0.0.1:9200/people/man (post方式)
{
“name”: “超级瓦力”,
“country”: “china”,
“age”: 40,
“date”: “1977-03-07”
}
三、修改:
url需要加/_update后缀
例如:http://123.56.222.170:9200/people/man/1/_update(post方式)
参数:
{
“doc”: {
“name”: “我是谁”
}
}
四、删除:
删除文档 删除索引
DELETE 请求 ip:端口/索引/类型/id
DELETE 请求 ip:端口/索引
例子:
删除文档:
http://123.56.222.170:9200/people/man/1 (delete方式)
删除索引为1的文档数据
删除索引(test001的索引):
http://123.56.222.170:9200/test001(delete方式)
五、查询:
简单查询: http://123.56.222.170:9200/book/novel/1(get方式)
条件查询:采用的是post方式进行条件查询,ip/索引/_search 参数为:{“query":{“match_all”:{},“from”:3,“size”:1}}
bool的must是必须的 条件都得满足 must not 是不必须的没用过 should是至少满足一个就行
查询时:sort是排序的意思,sort:[“publish_date”:{“order”:“desc”}]
聚合查询:就想咱们写sql 的group by 是统计一些销量
“aggs” : 这个是aggregations的缩写,这边用户随意,可以写全称也可以缩写
“group_by_word_count” : //定义一个聚合的名字,与java的方法命名类似,建议用’_'线来分隔单词
“terms” : 定义单个桶(集合)的类型为 terms
“field” : “doc.word_count”(字段颜色进行分类,类似于sql中的group by doc.word_count)
GET /cars/transactions/_search
{
“size”: 0,
“aggs”: {
“colors”: {
“terms”: {
“field”: “color”
},
“aggs”: {
//为指标新增aggs层"avg_price": {
//指定指标的名字,
在返回的结果中也是用这个变量名来储存数值的"avg": {
//指标参数: 平均值"field": “price”//明确求平均值的字段为’price’
}
}
}
}
}
}
参考文章:https://blog.csdn.net/ydwyyy/article/details/79487995
高级查询:
模糊查询:
match匹配的是 doc.title中包含“入”、“入门”、“门”的数据
match_phrase匹配的是doc.title中包含“入门”的数据
备注:term是精确查找,精确匹配
多个字段查询:
字段doc.author或者doc.title中包含“瓦力”的数据就行,记住是或的关系
query_string的用法是以每个单词进行匹配的,顺序是可以颠倒的,如果想用两个词就使用大写的AND操作
解释:所有字段中包含Elasticsearch和很胖 或者包含Python的数据
解释:doc.title和doc.author字段里面包含Elasticsearch或者瓦力的数据
解释:查到字段doc.word_count数据在>=1000<=2000的数据。gte =right+equals
解释:这个暂时不需要解释,应该能看懂
固定分数查询:
参数:{
“query”:
{
“constant_score”:
{
“filter”:
{
“match”:
{
“doc.title”:“Elasticsearch”
}
},
“boost”:2
}
}
}
不输入boost:2的话,默认查出分数为1的数据,相当于boost:1
注意:但是如果不输入filter直接:{
“query”:
{
“constant_score”:
{
“match”:
{
“doc.title”:“Elasticsearch”
}
}
}
}
会报:
不支持match查询
bool查询(重点)
参数:{“query”:{
“bool”:
{
“should”:
[{
“match”:
{
“doc.title”:“Elasticsearch”
}
},
{
“match”:
{
“doc.autor”:“瓦力”
}
}
]
}
}}
参数:{“query”:{
“bool”:
{
“must”:
[{
“match”:
{
“doc.title”:“Elasticsearch”
}
},
{
“match”:
{
“doc.autor”:“瓦力”
}
}
]
}
}}
参数:{“query”:{
“bool”:
{
“should”:
[{
“match”:
{
“doc.title”:“Elasticsearch”
}
},
{
“match”:
{
“doc.autor”:“瓦力”
}
}
],
“filter”:
[{
“term”:
{
“doc.word_count”:1000
}
}
]
}
}}
must和filter也可以连用
五:ElasticSearch集成SpringBoot项目
这个没必要说了吧。提供gitHub地址下载看一下就OK:https://github.com/muzhenhua/ElasticSearchAndRedis-project
六、怎么进行查找呢?(重点学习的)
直接来个需求吧:比如我们在搜房时,想通过几个字匹配到房源里面的标题、交通情况、所在小区、周边配套、附近地铁名字等等,我们应该怎么操作呢?
我们可以这样想想一下:
咱们要想从es里面能查到数据就需要把数据存到es里面,但是怎么存呢?全量存吗,把数据库里面的数据都存到es里面,那数据未免太多了吧。当然有人说就存进去吧,毕竟es是大数据量的,是的这样可以实现但是站在优化的角度,查询速度的快角度,我感觉可以这样操作把要查询的数据存到es里面然后把查到的数据id与数据库关联这样就达到es(id)+数据库操作了呢。
6.1 怎么存呢,又得存又得数据库没数据了怎么进行删除呢?
首先存的话代码上:
相关代码在我的项目:SearchServiceImpl类的第126行
private void createOrUpdateIndex(HouseIndexMessage message)
{
//通过housId从数据库查到数据
Long houseId =message.getHouseId();
House house = houseRepository.findOne(houseId);
if(house==null)
{
logger.error(“Index house {} dose not exist!”,houseId);
this.index(houseId,message.getRetry()+1);
return;
}
HouseIndexTemplate indexTemplate = new HouseIndexTemplate();
modelMapper.map(house,indexTemplate);
//create HouseDetail detail = houseDetailRepository.findByHouseId(houseId); if(detail==null) { //TODO 异常情况 } modelMapper.map(detail,indexTemplate); SupportAddress city = supportAddressRepository.findByEnNameAndLevel(house.getCityEnName(), SupportAddress.Level.CITY.getValue()); SupportAddress region = supportAddressRepository.findByEnNameAndLevel(house.getRegionEnName(), SupportAddress.Level.REGION.getValue()); //详细地址拼接 String address = city.getCnName() + region.getCnName() + house.getStreet() + house.getDistrict() + detail.getDetailAddress(); ServiceResult<BaiduMapLocation> location = addressService.getBaiduMapLocation(city.getCnName(), address); //获取地址不行接着访问 if (!location.isSuccess()) { this.index(message.getHouseId(), message.getRetry() + 1); return; } indexTemplate.setLocation(location.getResult()); List<HouseTag> tags = houseTagRepository.findAllByHouseId(houseId); if(tags!=null&&!tags.isEmpty()) { List<String> tagStrings= new ArrayList<>(); //jdk8的lamad表达式 tags.forEach(houseTag -> tagStrings.add(houseTag.getName())); indexTemplate.setTags(tagStrings); } Boolean success; **//上面都是组装数据大致看下就可以了呢** **//先通过HouseId查数据看是否有数据呢** SearchRequestBuilder requestBuilder = this.esClient.prepareSearch(INDEX_NAME) //xunwu .setTypes(INDEX_TYPE) //house .setQuery(QueryBuilders.termQuery(HouseIndexKey.HOUSE_ID, houseId)); **//通过houseId查数据** logger.debug(requestBuilder.toString()); SearchResponse searchResponse = requestBuilder.get(); **//通过hids查询到返回的数据总量** long totalHits = searchResponse.getHits().getTotalHits(); if(totalHits==0) { **//没查到说明为null需要创建** success= create(indexTemplate); }else if(totalHits==1) { **//通过获得hids的第0个就可以获取Id了,需要修改** String esId = searchResponse.getHits().getAt(0).getId(); success= update(esId,indexTemplate); }else { **//可能有很多个就是先删后增加比较好呢** success= deleteAndCreate(totalHits,indexTemplate); } //这段代码先别看,后期会解释但不影响存值 ServiceResult serviceResult = addressService.lbsUpload(location.getResult(), house.getStreet() + house.getDistrict(), city.getCnName() + region.getCnName() + house.getStreet() + house.getDistrict(), message.getHouseId(), house.getPrice(), house.getArea()); //如果失败就再次创建 if (!success || !serviceResult.isSuccess()) { //这个意思是失败了再次尝试访问一下呢 this.index(message.getHouseId(), message.getRetry() + 1); } else { logger.debug("Index success with house " + houseId); } }
6.1 那具体是怎么保存呢,具体是怎么修改的呢,具体是怎么删除+保存?
创建代码如下:
private Boolean create(HouseIndexTemplate indexTemplate)
{
if (!updateSuggest(indexTemplate)) {
return false;
}
try {
//往es里面写数据 用esclient.prepareIndex(索引名,类型);
IndexResponse response = this.esClient.prepareIndex(INDEX_NAME, INDEX_TYPE)
//setSource(json);
.setSource(objectMapper.writeValueAsBytes(indexTemplate), XContentType.JSON).get();
logger.debug("Create index with house:"+indexTemplate.getHouseId());
//通过respose.staus判断是否保存成功
if(response.status()==RestStatus.CREATED)
{
return true;
}else
{
return false;
}
}catch (JsonProcessingException e)
{
logger.error("Error to index house "+indexTemplate.getHouseId(),e);
return false;
}
}
修改操作:
/**
* update ES
* @param esId
* @param indexTemplate
* @return
*/
private Boolean update(String esId,HouseIndexTemplate indexTemplate)
{
if (!updateSuggest(indexTemplate)) {
return false;
}
try {
//往es里面修改数据 esClient.prepareUpdate(索引,类型,esId);
UpdateResponse response = this.esClient.prepareUpdate(INDEX_NAME, INDEX_TYPE,esId)
//setDoc(修改的数据,json类型).get();
.setDoc(objectMapper.writeValueAsBytes(indexTemplate), XContentType.JSON).get();
logger.debug("Update index with house:"+indexTemplate.getHouseId());
if(response.status()==RestStatus.OK)
{
return true;
}else
{
return false;
}
}catch (JsonProcessingException e)
{
logger.error("Error to index house "+indexTemplate.getHouseId(),e);
return false;
}
}
//先删除后添加
private boolean deleteAndCreate(Long total,HouseIndexTemplate indexTemplate)
{
//es里面删除数据 DeleteByQueryAction…
DeleteByQueryRequestBuilder builder = DeleteByQueryAction.INSTANCE.newRequestBuilder(esClient)
//通过houseId进行删除
.filter(QueryBuilders.termQuery(HouseIndexKey.HOUSE_ID, indexTemplate.getHouseId()))
.source(INDEX_NAME);
logger.debug(“Delete by query for house:”+builder);
//builder.get()就删除了呢
BulkByScrollResponse response = builder.get();
long deleted = response.getDeleted();
if(deleted!=total)
{
//查到的和删掉的不一致
logger.info("Need delete {},but {} was delete!",total,deleted);
return false;
}else
{
//删除完后进行创建
return create(indexTemplate);
}
}
6.2 如果某些数据下线了呢,在点击下线操作然后触发es的数据要移除。
private void removeIndex(HouseIndexMessage message)
{
Long houseId =message.getHouseId();
//es里面删除数据 —和上面的先删除后添加的操作相似
DeleteByQueryRequestBuilder builder = DeleteByQueryAction.INSTANCE.newRequestBuilder(esClient)
.filter(QueryBuilders.termQuery(HouseIndexKey.HOUSE_ID, houseId))
.source(INDEX_NAME);
logger.debug(“Delete by query for house:”+builder);
//删除数据
BulkByScrollResponse response = builder.get();
long deleted = response.getDeleted();
logger.debug("Delete total "+deleted);
ServiceResult serviceResult = addressService.removeLbs(houseId);
//删除失败再次尝试
if (!serviceResult.isSuccess() || deleted <= 0) {
logger.warn("Did not remove data from es for response: " + response);
// 重新加入消息队列
this.remove(houseId, message.getRetry() + 1);
}
}
//代码在SearchServiceImpl的第352行 -----加粗的是关注的点模糊查询很多字段
public ServiceMultiResult query(RentSearch rentSearch) {
//先用boolQuery查询
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//根据城市查询是全量传的 boolQuery.filter( QueryBuilders.termQuery(HouseIndexKey.CITY_EN_NAME, rentSearch.getCityEnName()) ); //对城市下面的地区查询就需要判断前端除了这个字段有没,也是全量传递参数相当于数据库里面的=某个字段 if (rentSearch.getRegionEnName() != null && !"*".equals(rentSearch.getRegionEnName())) { boolQuery.filter( QueryBuilders.termQuery(HouseIndexKey.REGION_EN_NAME, rentSearch.getRegionEnName()) ); } //面积查询 //第一步前端传过来的area比如(*-30)的值,通过这个值在固定几种类型中进行匹配,匹配到全部就不需要加条件搜索了呢 //只有匹配到了一部分数据才进行搜索 RentValueBlock area = RentValueBlock.matchArea(rentSearch.getAreaBlock()); if (!RentValueBlock.ALL.equals(area)) { //调用的方法是QueryBuilders.rangeQuery RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(HouseIndexKey.AREA); if (area.getMax() > 0) { //小于等于-1 rangeQueryBuilder.lte(area.getMax()); } if (area.getMin() > 0) { //大于等于 30 rangeQueryBuilder.gte(area.getMin()); } //filter与rangeQuery boolQuery.filter(rangeQueryBuilder); } //价格和面积的说辞是一样的呢 RentValueBlock price = RentValueBlock.matchPrice(rentSearch.getPriceBlock()); if (!RentValueBlock.ALL.equals(price)) { RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(HouseIndexKey.PRICE); if (price.getMax() > 0) { rangeQuery.lte(price.getMax()); } if (price.getMin() > 0) { rangeQuery.gte(price.getMin()); } boolQuery.filter(rangeQuery); } if (rentSearch.getDirection() > 0) { boolQuery.filter( QueryBuilders.termQuery(HouseIndexKey.DIRECTION, rentSearch.getDirection()) ); } if (rentSearch.getRentWay() > -1) { boolQuery.filter( QueryBuilders.termQuery(HouseIndexKey.RENT_WAY, rentSearch.getRentWay()) ); } //TODO 是关于这个title字段的权重比其他要重要点,默认都是1.0,现在改为2.0优先级会高点呢 ---这个是一个优化点
// boolQuery.must(
// QueryBuilders.matchQuery(HouseIndexKey.TITLE, rentSearch.getKeywords())
// .boost(2.0f)
// );
//搜索到关键字与相关字段匹配 这里面must其实就一个条件,重点
boolQuery.must(
//multiMatchQuery keyWord前端传来的关键字
QueryBuilders.multiMatchQuery(rentSearch.getKeywords(),
HouseIndexKey.TITLE, //对房源的简单描述
HouseIndexKey.TRAFFIC, //交通情况
HouseIndexKey.DISTRICT, //所在小区
HouseIndexKey.ROUND_SERVICE, //周边配套
HouseIndexKey.SUBWAY_LINE_NAME, //附近地铁名字
HouseIndexKey.SUBWAY_STATION_NAME //地铁站名字
));
**//调用boolquery查询 -----重点集成es** SearchRequestBuilder requestBuilder = this.esClient.prepareSearch(INDEX_NAME) .setTypes(INDEX_TYPE) .setQuery(boolQuery) //排序 .addSort( // field ,order HouseSort.getSortKey(rentSearch.getOrderBy()), //desc 用sortOrder来进行操作 SortOrder.fromString(rentSearch.getOrderDirection()) ) //数据起始点 0,5 记得从0开始 .setFrom(rentSearch.getStart()) .setSize(rentSearch.getSize()) **// TODO 这儿是优化的点这返回HousId就可以了,其他不需要返回所以要这样设置 ----重点优化 setFechSource(hourId,null) 返回只返回houseId其他数据不返回** .setFetchSource(HouseIndexKey.HOUSE_ID, null); logger.debug(requestBuilder.toString()); List<Long> houseIds = new ArrayList<>(); **SearchResponse response = requestBuilder.get();** if (response.status() != RestStatus.OK) { logger.warn("Search status is no ok for " + requestBuilder); return new ServiceMultiResult<>(0, houseIds); } //从es里面查到的数据存到放到list,然后list里面都存的HouseId for (SearchHit hit : response.getHits()) { System.out.println(hit.getSource()); //从es里面获取hoursId houseIds.add(Longs.tryParse(String.valueOf(hit.getSource().get(HouseIndexKey.HOUSE_ID)))); } return new ServiceMultiResult<>(response.getHits().totalHits, houseIds); }
6.3 上面虽然是查到数据,但是只是查到英文的方式不能查到中文的方式,需要配置中文分词器,上面安装有介绍,但是建索引是加什么东西呢,首先一点需要分词的看采用粗粒度还是细粒度
粗粒度:ik_smart 细粒度:ik_max_word ,keyWord类型就不需要分词是关键字
我这边是细粒度:
{
“settings”: {
“number_of_replicas”: 0
},
“mappings”: {
“house”: {
“dynamic”: false,
“properties”: {
“houseId”: {
“type”: “long”
},
“title”: {
“type”: “text”,
“analyzer”: “ik_smart”,
“search_analyzer”: “ik_smart”
},
“price”: {
“type”: “integer”
},
“area”: {
“type”: “integer”
},
“createTime”: {
“type”: “date”,
“format”: “strict_date_optional_time||epoch_millis”
},
“lastUpdateTime”: {
“type”: “date”,
“format”: “strict_date_optional_time||epoch_millis”
},
“cityEnName”: {
“type”: “keyword”
},
“regionEnName”: {
“type”: “keyword”
},
“direction”: {
“type”: “integer”
},
“distanceToSubway”: {
“type”: “integer”
},
“subwayLineName”: {
“type”: “keyword”
},
“subwayStationName”: {
“type”: “keyword”
},
“tags”: {
“type”: “text”
},
“street”: {
“type”: “keyword”
},
“district”: {
“type”: “keyword”
},
“description”: {
“type”: “text”,
“analyzer”: “ik_smart”,
“search_analyzer”: “ik_smart”
},
“layoutDesc” : {
“type”: “text”,
“analyzer”: “ik_smart”,
“search_analyzer”: “ik_smart”
},
“traffic”: {
“type”: “text”,
“analyzer”: “ik_smart”,
“search_analyzer”: “ik_smart”
},
“roundService”: {
“type”: “text”,
“analyzer”: “ik_smart”,
“search_analyzer”: “ik_smart”
},
“rentWay”: {
“type”: “integer”
}
}
}
}
}
6.4 怎么进行自动补全呢,我记得咱们公司项目是后台配置,把配置的存到数据库然后搜索是默认提示到数据模糊搜索。但是我不想那么做那样只是搜索到配置的,但是好多数据不是配置可以查到的需要做全量搜索,查到某个字就能相关提示但是没咱们的项目没配置的话收都不到的,显然是不合适的呢。但是要想达到这种效果,就必须在es里面建一个字段专门用于存这些建议的数据并匹配到相关字段,先存,再查。
6.4.1 怎么存?
要想
private boolean updateSuggest(HouseIndexTemplate indexTemplate) {
//所有参与的字段进行分词
AnalyzeRequestBuilder requestBuilder = new AnalyzeRequestBuilder(
//房源描述
this.esClient, AnalyzeAction.INSTANCE, INDEX_NAME, indexTemplate.getTitle(),
//房子布局描述
indexTemplate.getLayoutDesc(), indexTemplate.getRoundService(),
//地铁线的名字
indexTemplate.getDescription(), indexTemplate.getSubwayLineName(),
//地铁站名字的描述
indexTemplate.getSubwayStationName());
//粗粒度 requestBuilder.setAnalyzer("ik_smart"); AnalyzeResponse response = requestBuilder.get(); //不能分词 List<AnalyzeResponse.AnalyzeToken> tokens = response.getTokens(); if (tokens == null) { logger.warn("Can not analyze token for house: " + indexTemplate.getHouseId()); return false; } List<HouseSuggest> suggests = new ArrayList<>(); for (AnalyzeResponse.AnalyzeToken token : tokens) { // 排序数字类型 & 小于2个字符的分词结果 //排数数字类型,小于两个字符的也排除 if ("<NUM>".equals(token.getType()) || token.getTerm().length() < 2) { continue; } **HouseSuggest suggest = new HouseSuggest(); suggest.setInput(token.getTerm()); suggests.add(suggest);** } // 定制化小区名不需要分词 自动补全 HouseSuggest suggest = new HouseSuggest(); //小区自动补全 suggest.setInput(indexTemplate.getDistrict()); suggests.add(suggest); //indexTemplate的suggest操作 indexTemplate.setSuggest(suggests); return true; }
这就把数据放到template然后和上面一样都存进去e就可以了呢
6.4.2 怎么查呢?
public ServiceResult<List> suggest(String prefix) {
//自动提示从es里面拿数据 只查5条数据
CompletionSuggestionBuilder suggestion = SuggestBuilders.completionSuggestion(“suggest”).prefix(prefix).size(5);
SuggestBuilder suggestBuilder = new SuggestBuilder(); **suggestBuilder.addSuggestion("autocomplete", suggestion);** //查询传递的参数 **SearchRequestBuilder requestBuilder = this.esClient.prepareSearch(INDEX_NAME) .setTypes(INDEX_TYPE) .suggest(suggestBuilder);** logger.debug(requestBuilder.toString()); SearchResponse response = requestBuilder.get(); Suggest suggest = response.getSuggest(); if (suggest == null) { return ServiceResult.of(new ArrayList<>()); } Suggest.Suggestion result = suggest.getSuggestion("autocomplete"); int maxSuggest = 0; Set<String> suggestSet = new HashSet<>(); for (Object term : result.getEntries()) { if (term instanceof CompletionSuggestion.Entry) { CompletionSuggestion.Entry item = (CompletionSuggestion.Entry) term; if (item.getOptions().isEmpty()) { continue; } **for (CompletionSuggestion.Entry.Option option : item.getOptions()) { String tip = option.getText().string(); if (suggestSet.contains(tip)) { continue; } suggestSet.add(tip); maxSuggest++; }** } if (maxSuggest > 5) { break; } } List<String> suggests = Lists.newArrayList(suggestSet.toArray(new String[]{})); return ServiceResult.of(suggests); }
以后会写es的坐标存储集成百度地图,大家有啥不懂的多多交流,es的核心的使用内容就讲到这儿呢。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。