近程字典
ES-ik的字典加载反对近程扩大字典,并且能够实现热加载,基于此能够实现自定义字典的近实时加载,从而做到对分词后果的灵便管制。
配置与原理
ES-ik是ES的一个分词插件,放在在elasticsearch\plugins上面,在ES-ik的config\ik\IKAnalyzer.cfg.xml中配置了分词依赖的相干字典信息,包含“用户自定义扩大字典”、“用户自定义扩大进行词字典”、“近程扩大字典”、“近程扩大进行词字典”,如图:
近程扩大字典是配置一个近程的url获取,将url返回的字典数据加载到ES-ik分词依赖的内存字典中,实现字典数据的非本地化治理。如图:
对于近程扩大字典,ES-ik的依赖包中的实现如下,会每隔1分钟检测下近程扩大字典是否有更新,如果有更新,那么就会将最新的数据加载进来。
Monitor.java中的要害代码如下,会依据Last-Modified字段或者ETag字段的变动来判断字典是否有更新。
表结构设计
功能设计
导入字典单词
将行业单词,比方设施的机型、品牌、型号等作为字典批量导入,这样就能够对行业单词依照字典进行精确的分词。
增加扩大字典单词
从治理后盾零碎操作增加字典单词将字典数据保留到ik_ext_dict表中,同时更新ik_ext_dict表的更新工夫。例如:新的机型、品牌、型号等数据,
增加停用字典单词
将停用词数据保留到ik_ext_stop_dict表中,同时更新ik_ext_stop_dict表的更新工夫,例如:一些不须要在全文检索过程中参加分词要被过滤掉的单词。
近程字典接口实现
“近程扩大字典”的数据加载,服务端提供接口,首先从redis的ext_dict中获取,如果ext_dict中有数据,间接返回,如果没有,从ik_ext_dict表中加载数据并返回,同时将数据set到redis中ext_dict缓存中。代码如下:
public void remoteExtDic(HttpServletRequest request, HttpServletResponse response) { try { OutputStream out = response.getOutputStream(); String modifyTime = request.getHeader("If-Modified-Since"); String remoteDicModifyTime = ikDicService.getRemoteDicModifyTime(); response.setHeader("ETag", "extDic"); response.setHeader("Last-Modified", remoteDicModifyTime); response.setContentType(contentType); if(!remoteDicModifyTime.equals(modifyTime)){ String remoteDicText = ikDicService.getRemoteDicText(); out.write(remoteDicText.getBytes(charsetName)); } out.flush(); out.close(); } catch (Exception e) { logger.error("近程扩大词字典接口异样", e); }}
“近程扩大停用词字典”的数据加载,服务端提供接口,从redis中的ext_stop_dict获取,如果没有,从ik_ext_stop_dict表中加载数据并返回,同时将数据set到redis中的ext_stop_dict缓存中。
public void remoteStopDic(HttpServletRequest request, HttpServletResponse response) { try { OutputStream out = response.getOutputStream(); String modifyTime = request.getHeader("If-Modified-Since"); String remoteStopDicModifyTime = ikDicService.getRemoteStopDicModifyTime(); response.setHeader("ETag", "stopDic"); response.setHeader("Last-Modified", remoteStopDicModifyTime); response.setContentType(contentType); if(!remoteStopDicModifyTime.equals(modifyTime)){ String remoteStopDicText = ikDicService.getRemoteStopDicText(); out.write(remoteStopDicText.getBytes(charsetName)); } out.flush(); out.close(); } catch (Exception e) { logger.error("近程扩大停用词字典接口异样", e); }}
索引版本治理
ES对索引字段的从新分词须要重建索引实现,在白天零碎正在运行的时候如果重建索引就会影响到线上零碎的应用。
能够对索引进行版本治理,每次索引的重建都是另外建设一个新的版本,在索引重建的过程中,零碎还是从老版本的索引中获取数据,等新版本的索引实现后,将索引指向新版本,而后将老版本索引删掉。
以设施索引为例,程序中拜访的索引名称是equipment,具体的操作如下:
1.为旧版本索引建设别名,例如:给equipment_old建设别名为equipment
PUT 'http://192.168.199.122:9200/e...'
创立胜利后,查看如下:
2.建设新版本的索引,如:equipment_new
3.删除equipment_old的别名关联,切换索引到新版本equipment_new
POST http://192.168.199.122:9200/_aliases{ "actions": [ { "remove": { "index": "equipment_old", "alias": "equipment" } }, { "add": { "index": "equipment_new", "alias": "equipment" } } ]}
切换实现后后果如下:
近义词转换
全文检索的时候,检索的keyword中有时候输出的是一些口头语,例如:输出“卡特勾机”,分词后果为:“卡特”,“勾机”。其实“卡特”指的是“卡特彼勒”,“勾机”指的是“挖掘机”。
在索引文档中只存在规范的“卡特彼勒”和“挖掘机”,所以下面的搜寻后果为空,为了优化体验,就须要对keyword的分词后果“卡特”和“卡特彼勒”建设近义词,同理对“勾机”和“挖掘机”建设近义词。
所以在扩大字典中录入单词的时候,须要依据须要保护这个单词的近义词,比方:在录入“勾机”到扩大字典中的时候,指定近义词是“挖掘机”,那么在进行全文检索之前,先尽心一次分词失去“勾机”,而后判断勾机在零碎中有没有近义词,此时是有近义词“挖掘机”,那么理论交给ES进行搜寻的单词就是“挖掘机”,这样就实现了比拟匹配的搜寻成果。代码如下:
String searchWord = "";List<String> tokens = ESUtils.getTokens(keywords, null);for (String token : tokens) { searchWord = searchWord + token; IkExtDic ikExtDic = ikDicService.getIkExtDicByWord(token); if (ikExtDic == null) continue; String similarWord = ikExtDic.getSimilarWord(); if (!StringUtils.isEmpty(similarWord)) searchWord = searchWord.replace(token, similarWord);}
砍词再搜寻
空的搜寻后果是不太敌对的,如果用户在搜寻框中输出“陕西省西安市雁塔区挖掘机卡特彼勒320D”,如果没有这样的设施数据间接展现搜寻后果为空,给用户的体验就比拟差,比拟僵硬。
针对下面的状况,能够对搜寻的keyword进行砍词解决,搜寻出尽量匹配的后果,有可能就能达到用户的搜寻需要,比方将下面的搜寻文本通过砍词,变成搜寻“陕西省西安市雁塔区挖掘机卡特彼勒”,那么就有后果呈现了,体验上会比拟敌对,另外对SEO实现站点搜寻优化有帮忙。
在保护字典单词的时候,咱们须要保护该单词的属性,比方:该单词是省份,还是城市,还是县等。这样,通过分词后,咱们是晓得这个单词是什么属性,对于砍词,须要依据业务规定定义一个砍词程序,比方:县,城市,省份,型号,品牌,机型。而后依照分词后果每个单词的属性,联合砍词程序进行砍词再搜寻。
定义排序规定
定义设施评优的规定,对设施进行多维度的评分,搜寻后果依照评分由高到低排序,把最优的设施展现在最后面。
能够实现一个存储过程,在利用代码中,通过定时工作调用存储过程,更新每个设施的评分。
就近地位优先
ES能够实现基于地理位置的查问,比方:查问周边n公里内的设施信息。在ES中,地理位置通过geo_point这个数据类型来示意,地理位置数据须要提供经纬度。
地位的示意
在ES中地位数据能够有三种表现形式,别离是字符串、对象、数组,上面别离以不同的形式示意北京的地位。
字符串的形式:"lat,lon":
{ "city" : "Beijing", "location" : "39.91667,116.41667", "state" : "BJ"}
对象的形式:
{ "city" : "Beijing", "location" : { "lat" : "39.91667", "lon" : "116.41667" }, "state" : "BJ"}
数组的形式:[lon,lat]
{
"city" : "Beijing",
"location" : [116.41667,39.91667],
"state" : "BJ"
}
地位过滤
ES中有四种地位过滤器,别离是:
1、geo_distance: 查找间隔某个中心点间隔在肯定范畴内的地位
2、geo_bounding_box: 查找某个长方形区域内的地位
3、geo_distance_range: 查找间隔某个核心的间隔在min和max之间的地位
4、geo_polygon: 查找位于多边形内的地点
功能测试
建设一个城市的索引,索引名称为city,索引的类型为city,索引中蕴含城市的名称,地位,区域描述等信息,其中地位信息的类型指定为geo_point类型。索引对应的mapping如下:
{ "city" : { "properties" : { "cityEname" : { "type" : "string" }, "location" : { "type" : "geo_point" }, "state" : { "type" : "string" } } }}
建设5条城市数据到ES中,代码如下:
List<Map<String, Object>> cdata = new ArrayList<>();Map<String, Object> d1 = new HashMap<>();d1.put("cityEname", "Beijing");d1.put("state", "BJ");d1.put("location", "39.91667,116.41667");Map<String, Object> d2 = new HashMap<>();d2.put("cityEname", "Xiamen");d2.put("state", "FJ");d2.put("location", "24.46667,118.10000");Map<String, Object> d3 = new HashMap<>();d3.put("cityEname", "Shanghai");d3.put("state", "SH");d3.put("location", "34.50000,121.43333");Map<String, Object> d4 = new HashMap<>();d4.put("cityEname", "Fuzhou");d4.put("state", "FJ");d4.put("location", "26.08333,119.30000");Map<String, Object> d5 = new HashMap<>();d5.put("cityEname", "Guangzhou");d5.put("state", "GD");d5.put("location", "23.16667,113.23333");cdata.add(d1);cdata.add(d2);cdata.add(d3);cdata.add(d4);cdata.add(d5);ESUtils.batchIndex("city", "city", cdata, "City");
依照厦门远近进行查找,间隔厦门近的排在最前,代码如下:
QueryRule queryRule = QueryRule.getInstance();queryRule.setGeoField(new GeoField("location", 24.46667, 118.10000, SortOrder.ASC));List<Map> list = ESUtils.list("city", "city", Map.class, queryRule, 10);for(Map map : list){ System.out.println(map);}
控制台打印后果如下:
{cityEname=Xiamen, location=24.46667,118.10000, state=FJ}{cityEname=Fuzhou, location=26.08333,119.30000, state=FJ}{cityEname=Guangzhou, location=23.16667,113.23333, state=GD}{cityEname=Shanghai, location=34.50000,121.43333, state=SH}{cityEname=Beijing, location=39.91667,116.41667, state=BJ}
理论场景
在建设设施索引信息的时候,把设施所在地的经纬度一起建设到设施索引中,用户在登录app进行设施查找的时候,首先前端获取到用户所在地的经纬度,而后ES在做设施搜寻的时候就能够给用户举荐合乎用户需要的就近的设施信息。