Solr性能优化
1. 配置文件
1.1 schema.xml配置
schema 配置不合理,往往会导致查询性能低,索引占用磁盘、内存空间大的问题
- 合理设置域属性
- 域是否要检索(indexed),是否要存储(stored),按需配置,避免不必要的空间浪费。
- 域是否需要根据文本长度算分,是否需要在建索引时设置权重,如果不需要,设置omitNorms=true
- omitPositions、omitTermFreqAndPositions,词频信息和打分相关,位置信息和高亮显示相关,当不需要这些功能,则可设置为 true,节省磁盘空间,提升搜索速度。
- 对于需要排序的字段,使用 docValues,构造 fifieldCache 会进行压缩,节省内存使用
- 使用正确的数据类型
- 对于数值类型,使用能正确存储的最小数值类型,更小的数值类型占用更小的磁盘、内存、CPU缓存,并且处理时的 CPU 周期也更少。
- 数值类型不要用 string,一个整型占 4 字节,用 string,大小为 1000 以上的整型就占了 4 个字节了。当然,对于只有几个值(比如 0、1、2、3)的可枚举的,可以用 string。
- 不需要分词的域,用 string,不要用 text,text 默认用标准分词器分词。
- 需要范围查询的数值类型,需要使用 plong、pint 等分精度索引的类型,范围查询性能是不分精度索引的数值类型的 10 倍。当然,也不能滥用,分精度索引的数值类型比较占用空间,如果没有范围查询的需求,则不需要使用。
1.2 solrconfig.xml
索引目录类型
采用 NRTCachingDirectoryFactory,这种目录类型,小索引会缓存在内存中,减少磁盘 IO。而且这些小文件往往是频繁变化的,放在内存中,则 reopen 的时候,不需要读磁盘,性能会好很多。
autoSoftCommit 和 autoCommit
hardcommit 作用是使索引持久化,会 flflush 当前正在索引的段到磁盘,比较重,影响查询性能,时间间隔可以设置得较长。没有 hardcommit,机器挂了 ,重启后,会从 tlog 恢复。
softcommit作用是使索引可见,可根据实时性需要设置适当的长度。需要注意,softcommit会导致searcher层cache失效。索引实时性要求不高的情况下,频率尽可能设置长一点
cache配置
缓存类型
1.queryResultCache,查询结果缓存,key 由 q 参数、fq 参数、sort 参数组成,value 是 docId 和 score(score 可能没有)的有序集合 DocList。只要 q、fq、sort 参数有一个变化了,则属于不同 的 key,不会命中结果。 2.documentCache,document 的缓存,key 是 docId,value 是 document。 3.filterCache,filterQuery 结果缓存,key 是 fq 参数的值,value 是 docId 的无序集合 DocSet。 4.fieldCache,正排索引缓存,可通过 docId 获取字段值。排序、facet、group 等需要正排索引的 查询需要用到 fieldCache。fieldCache 是基于段的,一个字段的 fieldCache 是在第一次使用的时 候加载到内存中的。Lucene 用一个 weakHashMap 存放已加载的段的 fieldCache,key 为段的 indexReader。因此,fieldCache 是常驻内存,不会自动释放的,除非段被合并,不存在了,才会 释放掉。使用时,要注意内存的消耗,避免内存不够用,发生 OOM。
缓存注意事项
1.queryResultCache、documentCache、filterCache 都是 searcher 级别的缓存,searcher 重新 打开(softcommit 会触发),则缓存失效。其中 queryResultCache、filterCache 可以配置 autowarm 使 searcher 预热时重新加载。documentCache 的 key 是 docId,重新打开后, document 的 id 已经变化,因此 documentCache 不能进行 autowarm。 <filterCache class="solr.LRUCache" size="16384" initialSize="4096" autowarmCount="4096"/> 2.对于实时查询(比如 softcommit 频率为 1 秒),最好不要配置 cache,因为缓存失效太快,缓存 命中率可能比较低,如果配置了 autowarm,还会导致不停的 autowarm,加重服务器负担。除 非查询语句都比较集中,缓存条目很少。 3.对于有翻页的查询,可以适当调整 queryResultWindowSize 参数,比如一页的大小为 10,则 queryResultWindowSize 设置为 50,则后面 4 页,都会命中缓存,当然参数设置越大占用内存越 多 4.enableLazyFieldLoading 配置为 true,只读取需要的字段。这个属性要配合 documentCache 使 用,即开启了 documentCache,才能发挥 lazyLoading 的作用。 举个例子,比如查询出来的条目可能只显示简单的信息,点击具体条目,则需要把整个条目的所有信 息展示出来。则点击具体条目去查询时,命中了 cache,获取到的 document 就是有 lazyField 的 document 了,Solr 把它返回给调用者时,就会去加载 lazyField 的真实的值。 5.可配置 firstSearcher、newSearcher 来预热耗时的查询,使查询结果缓存起来,使查询性能更平 滑。 <listener event="newSearcher" class="solr.MyQuerySenderListener">
2. 索引构建
建议采用离线方式构建索引 ,离线构建索引的好处:
- 避免在线构建索引对在线服务的影响。
- 不用考虑记录更新的情况,省掉查询老记录是否存在的步骤,性能有提升。
- 可以使用内存大的机器,在内存中(RAMDirectory)进行构建(内存放不下,可考虑一个段一个段的构建),速度往往是在线构建的好几倍。
- 离线构建可以打开多个 indexWriter 进行构建(最好多个进程,因为 IndexWriter 存在类上加锁的情况,多个线程还是存在锁等待),然后再合并到一个 index 里,虽然在线索引也可以多个线程并发写,但会存在并发锁的问题。
- 离线索引追求的是吞吐量,对响应时间要求不高,可按照高吞吐量调配 jvm 参数,而在线索引还得考虑 jvm 对在线查询的影响。
- 离线构建可减少中间的段的生成,可以调大 ramBufffferSzie 和 mergeFactor,避免生成小的段,避免在构建的过程中自动合并,在构建完成后再根据需要触发合并动作,省掉中间的合并过程,速度也有一定的提高。
3. 搜索
如果不是所有字段都需要返回,则明确指定需要返回的字段,减少系统开销
一次返回的记录数不要太多,深度翻页使用 cursorMark 参数提升性能
//查询优化:游标查询 public void cursorQuery() throws Exception { //solr查询封装 SolrQuery sq = new SolrQuery(); sq.setRows(2);//设置游标一次读的数量 sq.set("q", "*:*");//按条件检索 sq.setSort("id", SolrQuery.ORDER.asc);//根据主键排序 String cursorMark = CursorMarkParams.CURSOR_MARK_START;//游标初始化 boolean done = false; while (!done) { sq.set(CursorMarkParams.CURSOR_MARK_PARAM, cursorMark);//变化游标条件 QueryResponse rsp = solrClient.query(sq);//执行多次查询读取 String nextCursorMark = rsp.getNextCursorMark();//获取下次游标 // 做一些操作数据的事 for (SolrDocument sd : rsp.getResults()) { System.out.println(sd.get("id")); } //如果两次游标一样,说明数据拉取完毕,可以结束循环了 if (cursorMark.equals(nextCursorMark)) { done = true; } cursorMark = nextCursorMark; } }
查询条件中包含路由字段的,可以先计算出分片,再从分片中获取机器,并指定 shard 来查询,只查询相应的 shard,避免服务端所有 shard 都查询
String shards = "192.168.1.215:8983/solr/a,192.168.1.214:8983/solr/b"; solrParams.set("shards", shards);//设置shard
合理使用 filterquery,filterquery 结合 filterCache,能把常用的查询条件缓存起来,提升查询效率。对于不使用 filterCache 的情况下,多个 OR 条件组合起来的查询也应该用 filterquery,因为filterquery 是不算分的,性能会好很多。主查询中的 OR 查询,对于一个 document 并不是有一个 OR 查询条件命中即返回,接着去查下一个 document,而是所有的 OR 条件都去查一遍,记录命中的 OR 条件的数量,由此来计算得分,所以会比在 filterQuery 中查询慢很多
4. 系统
内存分配
合理设置 jvm 内存大小,Solr 缓存、lucene 缓存、词典文件等都需要加载到内存中,内存设置太小,会造成 gc 频繁。但也不要把内存用得太满,在 jvm 内存足够的情况下,多留点给操作系统做文件缓存,这样索引文件能更多地被操作系统缓存起来,减少磁盘 IO,提升搜索的性能
方法一:直接修改配置文件参数 打开solr\bin目录下的solr.in.sh脚本文件 搜索找到 SOLR_HEAP 或者 SOLR_JAVA_MEM ,然后修改 SOLR_HEAP="1024m" 或者 SOLR_JAVA_MEM="-Xms512m -Xmx512m" 方法二:修改JVM内存存执 打开 tomcat\bin下面的catalina.sh文件,加入 set JAVA_OPTS= -Xms1024m -Xmx1024m 方法三:修改系统环境变量 设置CATALINA_OPTS 或者 JAVA_OPTS CATALINA_OPTS="-Xms128m -Xmx1024m -XX:PermSize=64M -XX:MaxPermSize=512M " 方法四:在启动时直接设置JVM大小 ./solr -m 2g
swap
系统内存不足时,操作系统会拿交换区(磁盘)来当做内存使用,交换区的访问速度和内存比非常慢,会影响搜索的性能,对 gc 也有较大的影响,jvm 收集交换区的垃圾对象时,常常速度很慢,造成应用停顿。建议内存足够的情况下,把 swap 关掉。如果内存比较紧张,则建议把 swapness参数的值调小,让操作系统尽量少使用 swap,完全关掉可能会造成操作系统内存不足,进程而被操作系统 kill 掉
如果内存够大,应当告诉linux不必太多的使用SWAP分区,可以通过修改swappiness的数值。swappiness=0的时候表示最大限度使用物理内存,然后才是swap空间,swappiness=100的时候表示积极的使用swap分区,并且把内存上的数据及时的搬运到swap空间里面。 现在一般1个G的内存可修改为10,2个G的可改为5,甚至是0。具体这样做: 1.查看你的系统里面的swappiness $cat/proc/sys/vm/swappiness 2.修改swappiness值为10 $sudo sysctl vm.swappiness=10 但是这只是临时性的修改,在你重启系统后会恢复默认的,为长治久安,还要更进一步: $sudo gedit /etc/sysctl.conf 在这个文档的最后加上这样一行: vm.swappiness=10
磁盘
搜索引擎对磁盘的随机访问比较多,推荐使用 ssd 磁盘