# Solr性能优化


<!--more-->


## 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 参数提升性能

  ```java
  //查询优化：游标查询
      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 都查询

  ```java
  String shards = "192.168.1.215:8983/solr/a,192.168.1.214:8983/solr/b"; solrParams.set("shards", shards);//设置shard
  ```

- 合理使用 ﬁlterquery，ﬁlterquery 结合 ﬁlterCache，能把常用的查询条件缓存起来，提升查询效率。对于不使用 ﬁlterCache 的情况下，多个 OR 条件组合起来的查询也应该用 ﬁlterquery，因为ﬁlterquery 是不算分的，性能会好很多。主查询中的 OR 查询，对于一个 document 并不是有一个 OR 查询条件命中即返回，接着去查下一个 document，而是所有的 OR 条件都去查一遍，记录命中的 OR 条件的数量，由此来计算得分，所以会比在 ﬁlterQuery 中查询慢很多

## 4. 系统

- 内存分配

  合理设置 jvm 内存大小，Solr 缓存、lucene 缓存、词典文件等都需要加载到内存中，内存设置太小，会造成 gc 频繁。但也不要把内存用得太满，在 jvm 内存足够的情况下，多留点给操作系统做文件缓存，这样索引文件能更多地被操作系统缓存起来，减少磁盘 IO，提升搜索的性能

  ```properties
  方法一：直接修改配置文件参数 
  打开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 掉

  ```sh
  如果内存够大，应当告诉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 磁盘


















