陋室铭
永远也不要停下学习的脚步(大道至简至易)

一、简介

Solr 性能优化是一个很复杂的任务,也是一个长期与之斗争的过程。在开始之前,首先要对影响 Solr 性能的基本因素有个大致的认知。影响 Solr 性能的一个主要因素就是内存。Solr需要有足够的内存用于两个方面:一部分用于 Java 堆内存,一部分用于操作系统的硬盘缓存。另外一个潜在的问题就是过高的查询频率,增加内存有时可以使 Solr 处理速度更快。但是如果你想要查询具有可伸缩性,那么最终光靠增加内存是行不通了。此时你需要使用 SolrCloud。

二、Schema 设计的注意事项

对于indexed属性而言,indexed=true的域比index=false的域在索引时需要占用更多的内存、索引合并和索引优化的时间更长、索引体积也相应变大,当然如果你不关系Term在文档中出现总次数对于文档最后评分的影响,那么你可以设置omitNorms=true,它能减少磁盘和内存空间的占用并加快索引创建速度。如果你不需要对该域进行高亮,你还可以设置omitPositions=true进一步减小索引体积。如果你只需要利用倒排索引结构根据指定的Term找到相应的Document,不需要计算Term在Document中的出现频率来考虑每个索引文档的权重从而决定其返回的排序,那么你还可以设置omitTermFreqAndPostions=true即忽略TF计算以及Term在Term Vector中的位置信息,这样能够进一步减小索引体积。需不需要为域设置indexed=true,这取决于你需不需要根据该域进行查询,如果不需要请设置indexed=false。

对于stored属性而言,在响应结果集中通过fl参数返回stored=true的域的执行开销很大,因为域值需要存储到硬盘,查询时提取域值需要磁盘IO,因此需要考虑你是否确实需要存储该域值,特别是该域值文本很长很长,比如是一篇几千几万字的文档的内容。但是如果你确实需要在Solr查询中能够返回该域值,此时可以考虑使用ExternalFileField域。如果你想要存储的域值长度并不大,但是为了能够缓解提取存储域带来的磁盘IO,此时可以设置compressed=true即启用域值数据压缩。但是请注意,开启域值数据压缩可以降低磁盘IO,同时会增加CPU执行开销。如果并不是一直都需要使用存储域,你可以设置域延迟加载,尤其是当你开启了域值数据压缩。启用域延迟加载,需要在solrconfig.xml中进行如下配置:

 
<enableLazyFiledLoading>false</enableLazyFiledLoading>
 

如果你的域值很大很大,比如一个几十MB的PDF文件的全部内容,此时如果你将该域的stored属性设置为true存储在Solr索引中,这显然不合适,它不仅会影响你的索引创建性能,还会影响你的查询性能,如果查询时fl参数里返回该域那将是雪上加霜。此时可以使用上面提到ExternalFileField域来解决,但是ExternalFileField域有一定的限制,比如不能支持Solr的查询,只能用于显示和Function计算,还可以考虑将域的sotred设置为false,而域值存储在外部系统,比如Memcache、Redis之类的缓存中,当你需要获取域值时,通过Solr里的UniqueKey去缓存中提取。

对于tokenized属性而言,当将一段文本索引到Solr的域时,需要考虑后续你是否需要执行类似根据指定关键字查询文档中是否包含该关键字的文档的查询,如果需要,那么此时你就需要设置tokenized=true开启对域值进行分词处理。当你的域值本身就是一个简短的单词或者词语,例如表示手机品牌的数据:小米、华为、三星、iPhone,对这些数据进行索引就没必要分词了。这并不意味着该域就一定不能分词,如果你的需求是:当用户输入“小”,能够提示“小米”,输入字母“i”能够提示"iPhone",就需要对该域进行NGram分词处理。因此,一个域的tokenized属性该如何设置,这完全取决于实际查询需求。

对于multiValued属性而言,当你的索引数据有多个时,需要设置multiValued=true来开启多值域。例如对“鞋子”相关数据进行索引,一般鞋子会有尺码信息,而且鞋子尺码一般会有多个,比如:36、38、39、40、41、42等。多值域在功能上会有一些限制,比如LatLonType域类型不支持多值域,Solr中的Group查询也不支持多值域,solr内置的ord函数不支持多值域。因此,当你选择使用多值域时,需要考虑这些限制是否会影响到实际需求的实现。如果这些限制确实约束到你,此时可以考虑采用嵌套Document来解决此类问题。

对于java里的日期时间类型的数据,建议你使用Solr里的date域类型,如果你需要进行日期时间范围区间查询,那么建议使用Solr里的date域类型,而不是string域类型。

对于UniqueKey主键域,建议使用string域类型而不是int或long。之所以这样建议,是由于以下3点理由:

  1. Solr中的QueryElevationComponent组件不支持非string域类型的UniqueKey主键域。
  2. SolrCloud中的CompositeId路由要求UniqueKey主键域必须为string域类型。
  3. 比如手机号码如果使用long域类型,那么你就不能进行NGram处理。

Solr官方提供的schema.xml示例文件中定义了很多、以及,这里建议你将它们都清理掉,只保留一些基本的string、boolean、int、long、float、double、date即可,其他配置后续按需添加即可。但是需要注意,务必保留内置的version域,如果你在solrconfig.xml中开启了Update Log,那么schema.xml中必须定义version域名。

三、solr 索引更新与提交的优化建议

solr中的索引更新与提交一般不建议显式的调用commit()进行硬提交,请在solrconfig.xml中配置自动硬/软提交。而且在SolrCloud模式下,建议每个Replica的自动硬/软提交的配置保持一致。

此外你应该在solrconfig.xml中适当调大ramBufferSizeMB和maxBufferedDocs参数值,以减缓Solr自动触发提交的频率。客户端在提交索引文档时,建议采用批量软提交方式添加索引文档,比如每1000个索引文档为一个批次,目的是尽量减少commit次数。

 

在SolrCloud模式下,Shard Leader会将索引更新转发给同Shard下的其他Replica,因此Shard Leader与其他各Replica之间的网络带宽决定了索引更新的效率。如果该Shard下的Replica个数太多,那索引更新的性能瓶颈就落在网络IO上,因此,此时即便是Shard Leader与其他各Replica之间的网络状况很好,由于网络IO消耗过多时间同样会影响索引更新性能,此时如果有可能,可以尝试停掉部分Replica,减少索引更新请求在网络之间进行转发的次数,从而加快索引更细你的速度。停掉部分Replica可能不够优雅,甚至可能会导致部分Replica节点上数据与Shard Leader不一致,此时如果可能的话,你可以尝试再增加几个shard,用多个shard去分摊索引更新请求的转发来缓解网络IO带来的索引更新性能影响。

对于Solr单击模式下,更新或提交索引文档建议你再Solr客户端程序使用Concurrent UpdateSolrClient类,对于在SolrCloud分布式模式下,建议你使用CloudSolrClient类来更新或提交索引文档。

当你进行索引更新或提交时,默认情况下,Solr会将Document的每个域的整个域值进行索引,如果Document中的某个域的域值很长很长,因为创建索引过程中Solr需要将Document缓存在内存中,如果个别域的域值很长,你的内存占用就会很大,内存暂用越大,可能会触发更频率的JVM GC,而JVM GC可能会暂停索引创建线程,从而影响索引更新或提交的性能。因此,此时建议你在schema.xml中对大文本域使用的域类型配置LimitTokenCountFilterFactory来限制实际索引的文本长度,从而减少索引过程中内存占用。

在创建索引时,当需要对文本进行分词处理时,建议你配置停用词来剔除掉无用的噪音Token,这样即能减小索引体积,同时能够避免噪音Token影响最终查询结果。同时,建议你禁用CompoundFile(复合)文件,虽然它能减少段文件个数,但是他会使得你的索引创建时间增加7%-33%,一般建议你禁用它,请在solrconfig.xml中的元素中间添加如下配置:

<useCompoundFile>false</useCompoundFile>
<mergePolicy class="org.apache.lucene.index.TieredMergePolicy">
    <float name="noCFSRatio">0.0</float>
</mergePolicy>

如果索引创建速度你还是觉得很慢,此时可以考虑借助MapReduce分布式处理框架,利用多台机器的资源并行创建Solr索引,从而加快索引创建速度。

四、索引合并性能调优

在索引合并操作中,mergeFactor参数很大程度上决定了索引合并的频率,虽然索引合并之后能加快Solr的查询性能,但是索引合并是一个执行开销很大的操作,因此你需要在保证查询性能的前提下,尽量的降低索引合并的频率。索引中的段文件个数增加和每个段文件的体积增大可能会直接触发段文件合并,导致段文件个数增加或段文件体积增大的主要行为就是索引提交。因为每次索引提交都会重新创建一个新的段文件进行索引写入。索引提交除了用户显式的执行commit之外,ramBufferSizeMB或者maxBUfferedDocs参数达到限定的阀值之后也会自动触发索引提交。因此,为了降低索引合并的频率,应该加大ramBufferSizeMB和maxBUfferedDocs参数值,并且尽量降低显式提交的频率,比如采用批量commit,或者直接在solrconfig.xml中配置自动提交并控制自动提交的频率,避免显式提交。同时,适当加大mergeFactor参数值来降低索引合并频率。加大mergeFactor参数值确实可以加快索引创建速度,降低索引合并频率,但是他同时也会降低你Solr查询响应速度。因此,mergeFactor参数是一把双刃剑,你需要在两者之间做好权衡。

Solr 5.5 之前的 mergeFactor 参数配置如下:

<mergeFactor>12</mergeFactor>
<maxMergeDocs>345</maxMergeDocs>
<mergePolicy class="..."/>
    <str name="abc">def</str>
</mergePolicy>

而 5.5 版本之后,你需要这样配置 mergeFactor 参数

<mergePolicyFactory class="...Factory">
    <int name="mergeFactory">12</int>
    <int name="maxMergeDocs">345</int>
    <str name="abc">def</str>
</mergePolicyFactory>

五、索引优化的注意事项

你需要定期优化你的索引,因为Solr的索引提交式每次生成新的索引段文件,段文件数量越多,会严重影响查询性能,但是索引优化操作的执行开销很大,建议你在凌晨1-2点钟再执行此操作。索引优化执行频率与你的索引更新频率相关,如果你的索引数据频繁在更新,那么你需要适当调整索引优化次数。

在Master/Slave主从索引复制架构中,如果你的Master节点上执行索引优化,将段文件合并为一个,此时Slave与Master进行数据同步需要复制一个很大的段文件,这会增大索引同步的时间,从而会影响Slave上的查询响应速度。所以通常一般建议在Slave上进行索引优化,而不是Master上。

六、Solr 缓存

Solr缓存在Solr中扮演着重要的角色,它很大程度上决定i的Solr查询性能。Sole内置提供了4种类型的缓存:FilterCache、documentCache、queryResultCache、fieldValueCache。Solr中的缓存都是由SolrIndexSearcher实例来管理,一个SolrIndexSearcher实例对应一套缓存体系,如果你从新new了一个SolrIndexSearcher实例,那么之前的SolrIndexSearcher实例对应的缓存就全部失效。因此此时你需要对新的SolrIndexSearcher实例提前进行缓存自动预热处理,以提升查询性能。

1、Solr 缓存的常见配置参数

Solr中的缓存配置是在solrconfig.xml的元素下进行配置,而这些缓存大都拥有相同的配置属性。首先class属性用于配置Solr缓存的实现类,内置提供了3种缓存实现:LRUCache、FastLRUCache、LFUCache。当然你也可以选择继承SolrCacheBase类进行自定义缓存实现。LRUCache表示距离上一次命中间隔时间最长的缓存对象优先被剔除。FastLRUCache比LRUCache的读取速度更快但是插入速度更慢。size表示缓存项的最大数目,这并不是表示内存大小。initialSize表示初始化能够缓存的项的数目,autowarmCount表示自动预热过程中从旧缓存中复制的缓存项的最大数目或者百分比(比如90%),此参数值设置的越大,自动预热的缓存项越多,查询缓存命中率越高,但是同时也会延长自动预热的完成时间,如果自动预热耗时过长可能会造成查询延迟。minSize参数只适用于FastLRUCache,是一个可选参数,他表示当缓存项达到size大小之后,剔除缓存项直到minSize大小,默认值是0.9size。acceptableSize参数只适用于FastLRUCache,是一个可选参数,他表示当剔除缓存项时首先需要尝试删除到minSize,如果达不到,那么至少尝试删除到acceptableSize,默认值是0.95size。cleanupThread参数只适用于FastLRUCache,是一个可选参数,他表示是否需要单独启一个线程来剔除缓存项,当你的缓存size非常大时你可能会需要将此参数设置为true,默认值是false。timeDecay参数只适用于LFUCache,是一个可选参数,他表示当缓存满了,是否需要将旧的缓存项的命中次数按照时间衰变进行减小,以保证它们最终能够被剔除。默认值true。showItems参数只适用于FastLRUCache和LFUCache,这是一个用于调试的可选参数,表示缓存项的数目是否需要在统计页面上显示,默认值为true。

2、Filter 缓存

FilterCache(即FilterQuery缓存)是Solr中的顶级缓存,它主要用于缓存Filter Query从硬盘上提取出来的Document的无序ID,下次执行同样的Filter Query就直接命中缓存。但是只要SolrIndexSearcher被重新open,那么FilterQuery缓存就立即失效。以下几个场景会用到Filter缓存:

Filter缓存会缓存任何FilterQuery返回的结果集(实际缓存的是Document的ID无序Set集合),Solr会显式的将主查询(q参数)返回的结果集与Filter缓存的无序Document ID Set集合求交集。

尤其是,当 facet.method=enum 时,Filter 缓存会用于 Facet 查询。

如果Solrconfig.xml中配置了true那么对于Solr排序操作也会使用Filter缓存。

Filter 缓存通常还会用于其他Solr查询,比如facet.query、group.query。

如果使用了facet.method=enum,建议你将的size属性值设置的大于facet域的唯一值总个数。Filter缓存的一个配置示例如下所示:

<filterCache class="solr.LRUCache" size="16384" initialSize="4096" autowarmCount="4096"/>

至于上面的参数值设置多大,你需要观察Solr后台管理界面里的缓存监控中统计的缓存命中率,然后不断测试调整达到最优。

3、Document 缓存

documentCache(即索引文档缓存)用于存储已经从硬盘上提取出来的Lucene中的Document对象。Document缓存的size应该大于*。表示查询中返回的结果集数量可能的最大值,表示查询的最大并发量,这样做是为了确保Solr查询不需要再从硬盘上提取索引文档。但是你索引文档的域越多,Document缓存暂用的内存也就越大。

Document缓存中的Document对象包含了多个Field的引用。如果你设置了enableLazy FieldLoading=true,并且开启了Document缓存,那么IndexReader提取的Document对象就仅仅只包含fl参数指定的Field,其他Field会被标记为“延迟加载”。这样能够减缓Document的内存暂用,但是如果延迟加载域后续被请求到,那么IndexEader会临时从硬盘上加载该域。

 

注意:Document缓存不能用于自动预热,因为Document缓存使用的是Lucene内部的Document ID,而当索引数据变化了,该ID也会发生变化,所以不能用于新的IndexSearch实例中。

4、QueryResult 缓存

QueryResultCache(即查询结果集缓存)用于唤春查询的Top N结果集的有序Document ID,按照排序域进行排序。查询结果集缓存的内存占用明显要比Filter缓存小,因为只有q参数、fq参数、sort参数同时一致的查询才会命中缓存,

5、FieldValue 缓存

FieldValueCache(即域值缓存)与Lucene中的fieldCache很相似,但是不同的是FieldValueCache支持每个Document对应多个值(多值域的多个域值或者单值域的域值因为分词生成多个Term)。此缓存主要用于Facet查询。此缓存的key为域的名称,value为docId到多个值的映射的数据结构。如果solrconfig.xml中没有定义,那么solr会自动为你生成一个size=10,maxSize=10000,无autowarm的。

6、HTTP 缓存

除了可以在后台服务层启用Solr缓存之外,你还可以在前端HTTP协议曾启用HTTP缓存,对于没有更新的资源,可以直接从HTTP缓存中直接返回,避免了同样的查询请求频繁请求服务器,这能在一定程度上减轻Solr Server的负载压力。在Solr中,Http缓存默认并没有开启,如果你想要开启Solr中的HTTP缓存,你需要在solrconfig.xml中进行如下配置:

<httpCaching never304="false">
    <cacheControl>max-age=30,public</cacheControl>
</httpCaching>

或者

<httpCaching lastModifiedFrom="openTime" etagSeed="Solr">
    <cacheControl>max-age=30,public</cacheControl>
</httpCaching>

如果两者都有配置,那么以第一种配置为准。never304参数设置为false即表示开启Solr中的HTTP缓存,默认为never304=true即禁用HTTP缓存。Solr中的HTTP缓存只支持GET和HEAD请求,不支持POST请求。Solr HTTP缓存兼容HTTP1.0和HTTP1.1协议头信息。

如果你的Solr索引更新频率仅仅只是偶尔,比如一小时更新一次,一天一次、一周一次...此时你可以为Cache-Control请求头添加max-age,max-age的值为索引更新频率的一半,单位:秒。如果你仅仅只需要浏览器缓存来缓存Solr的Response信息,那么你可以为Cache-Control添加private设置,示例如下:

<cacheControl>max-age=30,private</cacheControl>

7、缓存相关的其他设置

你还可以在solrconfig.xml配置firstSearcher和newSearcher事件监听器来自动触发缓存自动预热。newSearcher用于当一个新的IndexSearcher实例被创建时,除了从旧IndexSearcher实例自动预热一部分缓存之外,还可以显式的指定一个查询来对缓存进行预热。当某个查询耗时很长时,你可以提前通过newSearch监听器进行预热,这样后续你再执行该慢查询时会直接命中缓存。

firstSearcher表示当一个新的IndexSearcher实例正在被初始化并且当前没有旧的IndexSearch实例用于新的IndexSearch实例进行缓存自动预热,此时你需要显式指定一个查询来自动预热缓存。这个firstSearcher主要用于配置Solr刚启动时执行设置查询并放入缓存。因为Solr刚启动时,缓存肯定是空的,为了保证刚启动的一段事件内的查询性能高效,因此你需要配置firstSearcher来提取预热。

 

当使用queryResult缓存时,你还可以额外添加配置来对其进行优化。当一个查询被执行,返回的Document ID是[10,19]之间,如果queryWindowSize=50,那么Document ID[0,50]会被收集并缓存,在此范围内的Document将会命中缓存。

七、Solr 查询性能的优化建议

你的系统可能每天有成千上万的Document被添加进来,每小时有几百几千个查询在执行,此时查询性能至关重要。索引文档不断在增加,你迟早会遇到堆内存不足的问题,因此我强烈建议你首先加大你的JVM堆内存,如果你使用的是Tomcat容器,那么请在catalina.sh脚本中添加如下配置:

JAVA_OPTS="$JAVA_OPTS-server-Xms2048m -Xmx2048m"

#OS specific support.$var_must_be set to either true or false.

如果你使用的是默认的Jetty容器,那么请在solr.in.sh中进行如下配置:

SOLR_JAVA_MEM="-server -Xms2048m -Xmx2048m"

当你的索引数据量很庞大时,进行Facet查询或者Sort排序时会需要大量的内存占用。当让你可以通过增加Shard来缓解。然而,Lucene提供一种特殊的结构:DocValues,它使得Lucene的Field Cache能够运行更快速,并且能减少内存占用。因此当你需要对某个域进行排序或者进行Facet查询时,你可以在schema.xml中为域设置DocValues=true,配置示例如下所示:

<field name="title_sort" type="string" indexed="false" stored="false" docValues="true"/>

在Solr中默认会为每个FilterQuery启用Filter缓存,大部分情况下这能提升查询性能,并没有什么大问题。但是,考虑这样一个查询,比如你想要查询某个价格区间内的书籍,Solr查询如下所示:

q=solr&fq=category:books&fq=price[50 TO 100]

但是对于每个用户而言,设置的价格区间参数可能是不一致的,有的用户提供的价格区间可能是[20,50],也有可能是[20,60]、[30,60],这种价格区间太多太多,如果对每个价格区间的Filter Query都启用Filter缓存需要大量的内存来支持。再比如时间区间查询,如果区间范围精确到了秒,那么此时也不太适合用Filter缓存,比如像下面这个时间区间的查询:

q=solr&fq=category:books&fq=date[2016-08-01T13:26:09Z+TO+2016-11-11T10:30:59Z]

上面的Filter Query的时间区间都精确到了秒,每个用户的时间区间相同的可能性微乎其微,为每个用户这样的时间区间添加缓存显然不合适也不现实。因此,此时你应该为类似这样的Filter Query禁用Filter缓存,示例如下:

q=solr&fq=category:books&fq={! cache=false}date[2016-08-01T13:26:09Z+TO+2016-11-11T10:30:59Z]

至于什么时候应该禁用FilterQuery的缓存,你应该考虑的是你的Filter Query是否拥有共性,比如fq=category:books就适合使用Filter缓存,因为category域的域值就固定的几个,用户指定的category参数值可能不同,但是毕竟category域值可选值可以预计不会太多,不会达到几百上千个。而且category:books这类查询用户使用频率高,这样缓存命中率就高,查询性能自然得以提升。比如时间区间查询,由于区间范围不确定,无法确定对那个区间的查询进行缓存,即便区间确定了,也很难保证每个用户都会频繁命中这个区间。总之,你要谨记缓存确实可以提升查询性能,但是同时你还要考虑缓存命中率,如果设置一个缓存几天几个月都没有命中几次,那么这是对内存和CPU资源的一种浪费行为。

假如你的Solr查询中有多个FilterQuery,此时你需要考虑每个Filter Query的执行顺序,因为Filter Query的执行顺序可能会影响最终的Solr查询性能,考虑下面这样一个查询:

q=sort&fq=category:book&fq={! frange l=10 u=100} log(sum(sqrt(popularity),100)) &fq{! frange l=0 u=10} if(exists(price_a),sum(0,price_a),sum(0,price))

这里假定第二个FilterQuery相比第1个FilterQuery执行开销更大,且能够过滤的索引文档更少,而我们应该优先让那些能够过滤掉大部分索引文档的Filter Query先执行,而控制Filter Query执行顺序的就是通过设置cost属性值,cost属性值越小表明其执行开销越小,应该越优先执行。因此,这里我们应该对上 main的查询进行如下优化:

q=sort&fq=category:book&fq={! frange l=10 u=100 cost=10} log(sum(sqrt(popularity),100)) &fq{! frange l=0 u=10 cache=false cost=50} if(exists(price_a),sum(0,price_a),sum(0,price))

当需要对数字域进行范围区间查询时,你还可以通过调整precisionStep属性值来对Solr的Range Query性能进行优化。假如你需要对price(价格)域进行区间范围查找,price域在schema.xml中的定义可能类似下面这样:

<filed name="price" type="float" indexed="true" stored="true"/>
<fieldType name="float" class="solr.TrieFloatField" precisionStep="8"/>

此时你可以将float域类型的precisionStep属性值由8改为4,这样能够大幅度提升针对price域的Range Query性能。但是你需要注意precisionStep属性值不是越小越好,因为设置越小,你的索引体积也会越大,你应该在两者之间做好权衡。此外修改了域类型属性,记得重新加载core或者collection,然后重建索引。

如果你的查询需要在3个域上执行查询,比如:title:aa content:bbb tag:ccc,此时你可以考虑使用CopyFiled将title、conten、tag这3个域的域值复制到一个新的域上,然后在新域上执行查询,因为在单个域上执行查询比在N个域上执行查询性能要高。但是这样做有个弊端,在多个域上分别查询你可能单独为每个域设置权重,而采用CopyFiled全部复制到一个域上,你就无法实现权重设置。

关于Solr查询性能的优化还有很多优化点,这里就不再一一展开了,下面就几个我觉得需要提出来的优化点稍做列举:

前缀查询使用N-Gram来实现,而不是使用abc*的方式进行查询。

Phrese Query(短语查询)可以尝试使用ShingleFilterFactory来提升性能。

你可以使用游标来提升Solr分页性能

查询的时候fl参数尽量不要返回无关的域,尽量少返回stored=true的域

尽量减少fq的个数,考虑是否可以将多个fq合并为单个fq查询

如果你确定不需要Solr中的NRT近实时查询,那么请注释掉solrconfig.xml中的自动软提交配置。

如果你正在使用Filed Facet,那么此时你可以尝试在请求参数中设置facet.threads=1000来提升Field Facet查询的性能,但是此参数只适用于Field Facet查询,并且当你的Solr查询并发量很大时请不要开启此参数,比如在互联网电商项目中请不要使用。

在SolrCloud模式下,如果你明确知道你想要的查询的Document在哪些Shard上,那么请显式指定shard参数或者route参数。

如果有可能,尽量使用最新版本的JDK和Solr

如果单机已经无法支撑你的业务需求,此时你可以考虑使用SolrCloud来解决单击性能瓶颈。

八、JVM 以及 Web 容器的优化

九、操作系统级别的优化建议

操作系统和硬件,从根本上决定了系统的性能。Solr使用标准的文件系统来存储索引数据,而且Solr的所有操作都依赖底层的操作系统API。为了使得服务器能够支持更多的TCP/IP连接以及更高的系统吞吐量,你需要对操作系统进行适当的调优。注意:这里只讨论linux系统优化。

Linux 系统的 /etc/security/limits.conf 配置文件中可以被限制的系统资源包括:core(限制内核文件的大小)、data(最大数据大小)、fsize(最大文件大小)、memlock(最大锁定内存地址空间)、nofile(打开文件的最大数目)、rss(最大持久设置大小)、stack(最大栈大小)、cpu(以分钟为单位的最多CPU时间)、noproc(进程的最大数目)、maxlogins(此用户允许邓肯的最大数目)。

limit.conf 的配置格式如下:

<domain><type><item><value>

<domain>可以是一个系统用户名称,也可以是一个用户组名称,采用@用户组名称语法格式,可以使星号表示系统所有。
<type>可选值有soft和hard,soft设置起一个警示作用,hard类型才是硬性限制。
<item>就是被限制的资源,比如fsize、nofile、cpu、core等

由于 Solr 中每次索引提交都会重新创建一个段文件用于索引写入,如果你的索引提交频率很频繁,那么你可能会遇到"Too many open files"异常。很多操作系统默认能够打开的文件个数是1024个,因此你需要调大这个限制,此时你可以在/etc/security/limits.conf配置文件中设置nofile限制,配置示例如下:

root hard nofile 100000
@solr hard nofile 100000

建议调大Linux系统用户的最大可创建进程数。编辑/etc/security/limit.d/90-nproc.conf配置文件进行调整,配置示例如下:

* soft nproc 1024
root soft nproc unlimited
hadoop soft nproc unlimited

你还应该将TCP缓冲区调达到至少10M,配置示例如下:

sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216
sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 87380 16777216"

默认连接监听队列大小为128,如果你正在运行的服务器负载很高,并且开始出现TCP级别的连接被拒绝问题,那么此时你需要增大此参数值。如果将此参数设置的过大,大量的TCP连接会耗费大量的系统资源,如果这设置的过小,那么会出现TCP连接被拒绝,配置示例如下:

sysctl -w net.core.somaxconn=4096

net.core.somaxconn 用于控制用于上层(java)处理的传入数据包队列大小。默认值是 2048,你可以加大此参数值,以及调整相关参数:

sysctl -w net.core.netdev_max_backlog=16384
sysctl -w net.ipv4.tcp_max_syn_backlog=8192
sysctl -w net.ipv4.tcp_syncookies=1

如果你创建了很多连接,比如 load generator(负载生成器),此时操作系统在端口号上运行会很慢,因此你最好是增加端口号的范围,并且允许重用 TIME_WAIT 状态的 socket。

sysctl -w net.ipv4.tcp_local_port_range="1024 65535"
sysctl -w net.ipv4.tcp_tw_recycle=1

TCP 拥塞控制算法对 TCP 传输速率的影响可很大。丢包使得 TCP 传输速度大幅下降的主要原因就是丢包重传机制,控制这一机制的就是 TCP 拥塞控制算法。执行如下命令查看当前系统内核中可用的 TCP 拥塞控制算法有那些:

sysctl.net.ipv4.tcp_available_congestion_control

如果 cubic 或 htcp 没有列举出来,那么你可以尝试设置 cubic,如下所示:

sysctl -w net.ipv4.tcp_congestion_control=cubic

 

 
posted on 2023-08-09 12:36  宏宇  阅读(707)  评论(0编辑  收藏  举报