问题

在开发中遇到一个情况,在直接使用Lucene查询时,使用查询语句:

1
hk (-cs)

无法查出内容:

1
security in hk

但是使用hk -(cs)又是可以的。

同时,还存在一个情况,如果使用ES集群查询,又是可以的。

思路

一开始这个查询很复杂,排除掉了分词问题等其他问题后,简化成上述的这种情况。看起来,就是在queryParse时,解析出来的查询有问题。于是开始Debug之路:

  1. 首先,我们来看下转成的最终 query 是啥:+content:hk +(-content:cs),看起来有点别扭,后面这个+(-*应该有问题;
  2. 那我们就再看一下hk -(cs)会转成什么样:+content:hk -content:cs,很明显,少了+(那一截,问题应该就在这了;

分析 Lucene 的方式

初步确定问题,我们再调试一下:

这一步是构建查询时的Weight,会调用一次rewrite

image-20210224171330339

调用后,可以看到,后面这个+(-*的语法,变成了一个MatchNoDocsQuery("pure negative BooleanQuery"),这不就是说,这个查询不匹配任何数据嘛,然后又是MUST,所以最后压根就查询不到数据。

image-20210224171432672

问题确定到这里,那怎么解决?这时,我想到再ES里面的查询是没问题的,那它又是怎么做的呢?源码翻一翻:

分析 ES 的方式

拿到ES源码,直奔QueryStringQueryBuilder这个类,找到doToQuery()方法,可以看到:

image-20210224171959657

这里,再做完queryParase后,多了两行转换,看方法名query = Queries.fixNegativeQueryIfNeeded(query);就知道,就是专门处理pure negative BooleanQuery的,进去再看:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public static Query fixNegativeQueryIfNeeded(Query q) {
        if (isNegativeQuery(q)) {
            BooleanQuery bq = (BooleanQuery) q;
            BooleanQuery.Builder builder = new BooleanQuery.Builder();
            for (BooleanClause clause : bq) {
                builder.add(clause);
            }
            builder.add(newMatchAllQuery(), BooleanClause.Occur.FILTER);
            return builder.build();
        }
        return q;
    }

可以看到,当遇到NegativeQuery时,会增加一个MatchAllQuery,具体长啥样,请看:

image-20210224172405480

在原有的+(-*语法后,直接加了一个#*:*,有没有一点粗暴的感觉?它认为,你这个纯否定语法有问题,直接给你加个filter all,结果当然可以查出来数据了。

咦~,细心的你可能已经发现了,在这一步的时候,压根还没经过后面这个

1
query = Queries.fixNegativeQueryIfNeeded(query);

方法,就已经转换好了,那是哪里做的呢?

这时,我们就得看这个queryParser具体类QueryStringQueryParser的内部代码了,因为这个类继承了QueryParser,那我们可以先看一下它覆写了哪些方法吧:

image-20210224173128093

可以看到这里有很多getXXX()的方法,那我们就看一下getBooleanQuery()

1
2
3
4
5
6
7
8
@Override
protected Query getBooleanQuery(List<BooleanClause> clauses) throws ParseException {
    Query q = super.getBooleanQuery(clauses);
    if (q == null) {
        return null;
    }
    return fixNegativeQueryIfNeeded(q);
}

有没有一目了然?在这里也调用了刚刚那个方法,也就是在这里就会被转换了。

到这里,我们解决方案也就有了,直接Copy嘛。

总结

在实际使用中,ES其实做了很多的处理,如果直接使用Lucene,就会碰到很多意外情况。当然,因为业务需要,我们在某些模块是直接使用的Lucene,而通过不断填坑,也能够让我的技术有所提升。

相对于直接一头扎进源码,我可能更喜欢带着问题去看源码,理解得更为透彻,印象也更深。

最重要的,还是要自己记录下来,如果有机会,给别人去详细地讲解一番就更能够加深理解了。

以上。