版本声明
以下内容都是基于elasticsearch-6.3.2.jar
一、kibana开启调试
“explain”: true 会打印分数计算过程
{
"value": 2.0702924e-8,
"description": "Function for field createdAt:",
"details": [
{
"value": 2.0702924e-8,
"description": "exp(- MAX[Math.max(Math.abs(1.582024989E12(=doc value) - 1.5842304E12(=origin))) - 0.0(=offset), 0)] * 8.022536812036404E-9)",
"details": []
}
]
}
以上结果片段是衰减函数exp的计算日志,由details-》description 大概是可以看出exp函数的公式, 但是有一个陌生的数字 8.022536812036404E-9 这是什么?怎么来了?日志中无法看到它的来路,只能在源码中一探究竟。
二、源码透视
经过一系列的代码定位 搜索 终于找到了打印此处日志的代码块
org.elasticsearch.index.query.functionscore.ExponentialDecayFunctionBuilder.explainFunction:72
public Explanation explainFunction(String valueExpl, double value, double scale) {
return Explanation.match(
(float) evaluate(value, scale),
"exp(- " + valueExpl + " * " + -1 * scale + ")");
}
继续深入
org.elasticsearch.index.query.functionscore.DecayFunctionBuilder:558
public Explanation explainScore(int docId, Explanation subQueryScore) throws IOException {
if (distance.advanceExact(docId) == false) {
return Explanation.noMatch("No value for the distance");
}
double value = distance.doubleValue();
return Explanation.match(
(float) score(docId, subQueryScore.getValue()),
"Function for field " + getFieldName() + ":",
func.explainFunction(getDistanceString(ctx, docId), value, scale));
}
func.explainFunction 看下func的面目
org.elasticsearch.index.query.functionscore.DecayFunctionBuilder:506
private final DecayFunction func;
public interface DecayFunction {
double evaluate(double value, double scale);
Explanation explainFunction(String valueString, double value, double scale);
/**
* The final scale parameter is computed from the scale parameter given by
* the user and a value. This value is the value that the decay function
* should compute if document distance and user defined scale equal. The
* scale parameter for the function must be adjusted accordingly in this
* function
*
* @param scale
* the raw scale value given by the user
* @param decay
* the value which decay function should take once the distance
* reaches this scale
* */
double processScale(double scale, double decay);
}
实现类有三个ExponentialDecayScoreFunction、GaussScoreFunction、LinearDecayScoreFunction 刚好对应了es的三种衰减函数。
注意一下processScale这个方法,看注释说 最终比例参数 是此方法计算出来的,看一下exp是怎么实现的。
org.elasticsearch.index.query.functionscore.ExponentialDecayFunctionBuilder:79
public double processScale(double scale, double decay) {
return Math.log(decay) / scale;
}
看到此处就可以明白 8.022536812036404E-9是怎么来的了。
回到org.elasticsearch.index.query.functionscore.DecayFunctionBuilder:558
public Explanation explainScore(int docId, Explanation subQueryScore) throws IOException {
if (distance.advanceExact(docId) == false) {
return Explanation.noMatch("No value for the distance");
}
double value = distance.doubleValue();
return Explanation.match(
(float) score(docId, subQueryScore.getValue()),
"Function for field " + getFieldName() + ":",
func.explainFunction(getDistanceString(ctx, docId), value, scale));
}
此处调用了explainFunction方法 前面已经说过此方法用于打印调试日志。进入getDistanceString 方法看一下,此方法是一个抽象方法,实现类有有两个,用于数值衰减 和 地理位置衰减。
protected String getDistanceString(LeafReaderContext ctx, int docId) throws IOException {
StringBuilder values = new StringBuilder(mode.name());
values.append("[");
final SortedNumericDoubleValues doubleValues = fieldData.load(ctx).getDoubleValues();
if (doubleValues.advanceExact(docId)) {
final int num = doubleValues.docValueCount();
for (int i = 0; i < num; i++) {
double value = doubleValues.nextValue();
values.append("Math.max(Math.abs(");
values.append(value).append("(=doc value) - ");
values.append(origin).append("(=origin))) - ");
values.append(offset).append("(=offset), 0)");
if (i != num - 1) {
values.append(", ");
}
}
} else {
values.append("0.0");
}
values.append("]");
return values.toString();
}
正是调试日志。
再看func.explainFunction(getDistanceString(ctx, docId), value, scale));
value参数是double value = distance.doubleValue()得到的,继续深入,可以看到
final NumericDoubleValues distance = distance(ctx); 537行
进入distance方法
protected NumericDoubleValues distance(LeafReaderContext context) {
final SortedNumericDoubleValues doubleValues = fieldData.load(context).getDoubleValues();
return mode.select(new SortingNumericDoubleValues() {
@Override
public boolean advanceExact(int docId) throws IOException {
if (doubleValues.advanceExact(docId)) {
int n = doubleValues.docValueCount();
resize(n);
for (int i = 0; i < n; i++) {
values[i] = Math.max(0.0d, Math.abs(doubleValues.nextValue() - origin) - offset);
}
sort();
return true;
} else {
return false;
}
}
}, 0.0);
}
446行
Math.max(0.0d, Math.abs(doubleValues.nextValue() - origin) - offset);
再看func.explainFunction(getDistanceString(ctx, docId), value, scale));
看下scale参数的值
org.elasticsearch.index.query.functionscore.DecayFunctionBuilder:519行
this.scale = func.processScale(userSuppiedScale, decay);
processScale这个方法和我们前面猜想的一样 就是根据设置的scale 和 decay计算出一个比例
返回主线 继续看 explainScore 方法 556行
(float) score(docId, subQueryScore.getValue())
这里就是计算最终分数的了,点进去看下
public double score(int docId, float subQueryScore) throws IOException {
if (distance.advanceExact(docId)) {
return func.evaluate(distance.doubleValue(), scale);
} else {
return 0;
}
}
evaluate 此方法正是前面说的接口中三个方法的其中之一,看下exp如何实现的
public double evaluate(double value, double scale) {
return Math.exp(scale * value);
}
此刻真相大白
EXP函数公式
value=Math.max(0.0d, Math.abs(doubleValues.nextValue() - origin) - offset);
processScale= Math.log(decay) / scale;
最终:
sorce=Math.exp(processScale* value)
- doubleValues.nextValue() 此值为目标字段的值
附exp配置示例
衰减函数配置
"exp": {
"createdAt": {
"origin": "2020-03-15 00:00:00",
"scale": "1d",
"offset": "0d",
"decay": 0.5
},
"multi_value_mode": "MAX"
}