前一阵子准备为项目搭建一个简单的搜索服务,虽然业务数据库mongodb提供了文本搜索的支持,但是在大量文档需要通过关键词进行定位时,es明显更加适合去作为一个搜索引擎(虽然我们之前大部分使用到了elk那套分析和可视化的特性)。elasticsearch建立在lucene之上并且支持极其快速的查询和丰富的查询语法,偶尔也可以作为一个轻量级的nosql。但是对复杂查询和聚合操作的能力并不是很强。
本篇不会提及如何搭建一个简单搜索服务,而是记录一下大约一周工作时间内遇见的几个坑。。
为什么选择elasticsearch 5.x?
新服务没有任何历史包袱,理论上应该用最新的6.x,然而spring-data-elasticsearch只支持到的5.x,时间紧也无法很好直接封装一层api,也是因为elk那套东西之前版本混乱,无奈es从2.x直接到了5.x。查询一下5.x和2.x的差别,简单说就是磁盘空间-50%,索引时间-50%,查询性能+25%。
由于spring-data-elasticsearch必须升级到3.0.7,导致spring必须升级到2.x,也直接导致了后面踩到的坑。
docker安装es会默认安装x-path plugin
虽然spring-data支持es5.x,但是功能并不非常完善,因此如果安装了x-path插件,需要引入org.elasticsearch.client:x-pack-transport:5.5.0,版本必须和es版本一致,并且自己实现transportclient,如下
1
2
3
4
5
6
7
8
9
10
11
12
|
@component public class esconfig { @bean public transportclient transportclient() throws unknownhostexception { transportclient client = new prebuiltxpacktransportclient(settings.builder() .put( "cluster.name" , "docker-cluster" ) .put( "xpack.security.user" , "elastic:changeme" ) .build()) .addtransportaddress( new inetsockettransportaddress(inetaddress.getbyname( "0.0.0.0" ), 9300 )); return client; } } |
这也是因为不想再到docker里去处理x-path这个插件而选择的一个比较快捷的解决方案,没必要的情况下,暂时也不用接触到es本身的一些东西。
mq会保存message的class信息导致deserialized失败
一直没有提到标题中的rabbitmq,因为只是单纯的用它作为一个消息队列,当数据发生变化时,将消息id丢入mq,由search服务这边的consumer去消费。
问题就是在消息丢入mq时,封装成了一个自己的object, 导致使用rabbittemplate.receiveandconvert时失败,因为message会带着object的package信息。无奈之下,consumer只能直接获取queue里的message bytes, 用objectmapper.readvalue的方法将json形式转换成一个object。
gradle配置可以使用-dloader.main指定启动函数
正是因为引入了mq,所以search服务需要启动一个consumer,用的方法是另外实现一个不启动web服务的application,并且配置一个simplemessagelistenercontainer和messagelisteneradapter如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@bean simplemessagelistenercontainer container(connectionfactory connectionfactory, messagelisteneradapter listeneradapter, mqconfig properties) { simplemessagelistenercontainer container = new simplemessagelistenercontainer(); container.setconnectionfactory(connectionfactory); container.setqueuenames(properties.getqueuename()); container.setmessagelistener(listeneradapter); return container; } @bean messagelisteneradapter listeneradapter() { messagelisteneradapter listeneradapter = new messagelisteneradapter(itemconsumer, "consume" ); return listeneradapter; } |
问题在于gradle配置的时候,找了很久如何使得build出来的jar包可以指定-dloader.main指定启动application,解决方法如下:
在xxx.gradle文件里添加
1
2
3
4
5
|
bootjar { manifest { attributes 'main-class' : 'org.springframework.boot.loader.propertieslauncher' } } |
在springboot 1.5.9的项目里,需要指定启动application,需要添加
1
2
3
|
springboot{ layout = "zip" } |
查看是否生效的办法是build以后 直接解压jar包,在xxx(项目名)/meta-info/manifest.mf里查看,如果
main-class: org.springframework.boot.loader.propertieslauncher
则正确,如果
main-class: org.springframework.boot.loader.jarlauncher
则依旧会启动文件里的start-class
es无法修改index的mapping
由于只是单纯使用了es的文本检索功能,导致实际应用时有许多搜索结果不尽如人意的地方,比如搜索“桌子”, 无法搜索到 “电脑桌/办公桌”等xx桌内容,这样的情况还有很多。 因此加入了synonym dictionary,在需要分词的字段上不使用本身的ik_smart分词器,这样某些字段的mapping需要改为
1
2
3
|
// analyzer是自己的分词器名字 @field (type = fieldtype.text, index = true , analyzer = "synonym" ) private string description; |
由于es的mapping无法修改,只能通过手动创建一个新的mapping,再通过reindex方法去backfill数据(es5.x自带了reindex 的api)。网上有通过alias的方法,在某些修改场景下,不需要重新启动/部署应用就可以平滑的修改mapping,具体可以查询了解一下。
以上差不多搭建一个搜索服务踩到的一些坑,有几个消耗了大量时间和精力去解决,在此列出来希望希望有借鉴意义。之后搜索服务有优化的地方,还会继续慢慢更新,也希望大家多多支持服务器之家。
原文链接:https://www.jianshu.com/p/9c60ed244878