lucene中的filter其实并不起眼,大家对其对性能的影响也不是很关注,但实际上filter是除了单纯搜索以外,其他搜索附加功能的必选组件,其性能很大程度上会直接影响搜索的性能,之前我一直认为filter的性能比query高,但事实说明并不完全如此(这里所说的负荷是指io消耗并不是cpu),实际上在lucene中充满着各种io流,也就是说很多东西都无法从根本上保存,这也给缓存带来了很大难度(这个问题看似简单,但是在超复杂的组合查询下,缓存可能会几乎无用,原因就是key怎么把握)
首先来看看filter的接口定义:
public abstract class Filter implements java.io.Serializable {
public abstract BitSet bits(IndexReader reader) throws IOException;
}
简单明了从reader中知道哪些记录是可以读出来的用true false放在bitsets中,然后再用这去和总集合做and操作得到剩余记录数,然后再通过query查询.原理知道了,下面来考虑下它的缓存:
缓存filter本身,由于他是序列化对象,那么已经具备了缓存的条件,但是这是一个错误,因为你缓存了这个类,而当你把参数reader拿出来依然会和机器产生io,因此这是极其不恰当的方法,应该缓存它的结果.
在lucene中有这么几个和filter有关的类:
CachingWrapperFilter
CachingSpanFilter
RemoteCachingWrapperFilter
FilterManager
其实我想质疑前两个,为什么呢,请看他的源码:
protected transient Map cache;
他放置缓存的map居然是transient的,这意味着即使你把它实例在static中这个变量依然会每次要new的,这样的缓存有意义吗?我看不出他怎么缓存的
/**
* A transient Filter cache. To cache Filters even when using {@link org.apache.lucene.search.RemoteSearchable} use
* {@link org.apache.lucene.search.RemoteCachingWrapperFilter} instead.
*/
上面这句注释总算明了了,呵呵.
那么其实RemoteCachingWrapperFilter才是真正的cache类,他的实现借助于filterManager,这个类是我们平时能理解的那种cache结构
public BitSet bits(IndexReader reader) throws IOException {
Filter cachedFilter = FilterManager.getInstance().getFilter(filter);
return cachedFilter.bits(reader);
}
但这个还不够,第一他的性能我心里没谱,遇到上万的访问怎么办?所以还是要用第三方的缓存,我使用的是memcached,这个东西不介绍了,只有一个问题,就是必须要求对象是可序列化的,这个不难理解,要想网络传输只能治么搞.
我的缓存策略:把最细胞的filter用memached缓存他的结果集,而他的组合fliter用自带的filtermanager管理就好了.filter怎么合在一起上次写过一个,看这里: http://www.edwardpro.com/post/572/
而我这样的道理也是基于filtermanager的key是reader的hashcode,因此他是对应不同的索引的.那么肯定有朋友问怎么刷新呢?太简单了啊,你的key只要有reader或者search的hashcode就可以了,你一旦更新的源hashcode就变化了.(如果你的search和reader的hash不是固定的那么你肯定承受不了100以上的并行访问,io会高得惊人.)
另外一个技巧,是关于rangefilter的,这个东西不错,但是有一点难,在哪里呢?因为他的查询似乎效率不高,因此一定要缓存! 但是key呢?比如我常用的key是timestamp,但是实际中就会发现如果用毫秒的timestamp那么key几乎无用,因为很少相同的,经过改进,我把时间可以用月做单位,查询也是如此,如果你的要求高我觉得做到天就ok了,如果你数据再多用到小时肯定也够了吧,这样filter的缓存会带来极大的性能提升.
那么实际效果呢,在原来使用时候2台集群机(nginx作为前端代理,后部用resin作为应用服务器)io平均1.xx 现在加了缓存之后常年保持在0.2左右!性能得到了几乎5~6倍的提升.而一般查询一个十万当量的+ 5个关键字 + 3个filter 时间大约是<10ms 非命中时大约是 70~80ms 这个速度如果得到同样结果的数据库至少要放大1000倍的时间.
由于我memcached没有做集群是独立的(事实也应该如此,因为你两台机器的reader的hashcode肯定是不一样的,放一起也是这样的结果,这样也没有不好,当一台机器出现问题或者需要更新代码可以用时间差来保证负荷平稳过渡,不像以前一台机器每次重启都是有点怕怕的,只能找空闲时间才敢这么做.
最后要讲的query,其实前面我说了半天没有提到query,query的缓存呢? 其实在lucene中有这么个类:
QueryFilter
这个类简单说就是把query变成filter,那干什么呢?很简单啊,这样任何查询都会变成filter的,所以所有的缓存都是filter!那么从缓存中取出来的filterquery怎么用?
MatchAllDocsQuery matchAll = new MatchAllDocsQuery();
result = isearch.search(matchAll, filter, sort);
filter是用我的合成filter组合的,这样消耗就更低了,当然不建议无限制增加系统负荷,因为那样就几乎无法重启了,呵呵.好了基本说到这里,其实最后我想说我的核心思想: 任何query都是filter,lucene就是filter查询,事实是如此的,当然这点可能还可以讨论下,因为我用filter比较依赖.
lucene中的filter器群组及其缓存大盘点
最新推荐文章于 2024-08-29 10:53:37 发布