elasticsearch中runtime_mapping实战

背景:需要根据一个实时计算处理的结果值进行排序,数据从es中查询。(基于业务背景:佣金排序)

es版本:7.17.1;spring-data-elasticsearch版本:4.3.9

方式一:mysql新增字段:mysql根据业务操作,直接在在代码中刷取数据存储到mysql中(未采用)
优点:代码简单,后期查询时候排序简单

缺点:如果相关因素变化,需要扫描刷新大量相关数据

方式二:pipeline,数据往es同步的时候计算出来需排序的字段(暂未采用)

优点:实现简单,后期排序简单

缺点:当前业务背景下,也需要刷新大量数据,只是不用自己计算而已。相当于计算过程中部分字段在原始schema中也没有,复杂度和方式一差不多。

方式二.一:(暂未采用)
创建索引时指定mapping中设置runtime字段,支持数据插入的时候即生成runtime字段
优点:性能比查询时直接使用runtime mapping性能高
缺点:需要重新创建索引,reindex所有历史数据。

方式三:使用runtime mapping在查询过程中直接实现(采用)

优点:实现简单,直接用script脚本即可实现

缺点:数据量大的时候,会有性能问题,生产环境使用前,需要压测

下面重点讲解一下方式三实现:

版本一:

script是一个对象{},可以动态传递参数,更加灵活。

POST /t_spu/_search
{
  "size": 1000, 
  "runtime_mappings": {
    "commission": {
      "type": "double",
      "script": {
        "source": """
        String commissionStr = doc['commission_price.keyword'].value;
        long price = doc['min_sku_sale_price'].value;
        int indexNo = commissionStr.indexOf('~');
        if (indexNo > 0) {
            double allCommission = Double.parseDouble(commissionStr.substring(0, indexNo))*100;
            emit(allCommission - price * params.platformCommissionRate);
        } else {
            emit(Double.parseDouble(commissionStr)*100 - price * params.platformCommissionRate);
        }
        """,
        "lang": "painless",
        "params": {
          "platformCommissionRate": 0.03
        }
      }
    }
  },
  "fields": [
    "commission"
  ],
  "query": {
    "bool": {
      "filter": [
      {"term": {
      "goods_source_type": {
        "value": "1"
      }
    }}
      ]
    }
  },
  "sort": [
    {
      "commission": {
        "order": "desc"
      }
    }
  ]
}

 java:(部分查询条件未具体实现)

1、这里用到的script是一个对象{},ElasticsearchRestTemplate不支持。es版本:7.17.1;spring-data-elasticsearch版本:4.3.9

只能用RestHighLevelClient客户端实现java代码。

@Autowired
private RestHighLevelClient restHighLevelClient;


String commissionScript = "String commissionStr = doc['commission_price.keyword'].value;" + "long price = doc['min_sku_sale_price'].value;" + "int indexNo = commissionStr.indexOf('~');" + "if (indexNo > 0) {double allCommission = Double.parseDouble(commissionStr.substring(0, indexNo))*100;" + "emit(allCommission - price * params.platform_commission_rate);}" + "else {emit(Double.parseDouble(commissionStr)*100 - price * params.platform_commission_rate);}"; //runtime_mappings final String COMMISSION = "commission"; Map<String, Object> params = new HashMap<>(); params.put(GoodsSearchEsConstant.PLATFORM_COMMISSION_RATE, realPlatformCommissionRate); Script script = new Script(ScriptType.INLINE, "painless", commissionScript, params); SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); // sourceBuilder.from(0); // sourceBuilder.size(10); // sourceBuilder.fetchSource(new String[]{"title"}, new String[]{}); // MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", "商品名称"); TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("goods_source_type", 1); // RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("payTime"); // rangeQueryBuilder.gte("2023-01-26T08:00:00Z"); // rangeQueryBuilder.lte("2023-01-26T20:00:00Z"); BoolQueryBuilder boolBuilder = QueryBuilders.boolQuery(); // boolBuilder.must(matchQueryBuilder); boolBuilder.must(termQueryBuilder); // boolBuilder.must(rangeQueryBuilder); sourceBuilder.query(boolBuilder); //runtime_mappings 部分 HashMap<String, Object> runtimeMappings = new HashMap<>(); HashMap<String, Object> commObj = new HashMap<>(); commObj.put("script", script); commObj.put("type", FieldType.Double.getMappedName()); runtimeMappings.put(COMMISSION, commObj); // sourceBuilder.fetchField("*"); sourceBuilder.fetchField("commission"); sourceBuilder.runtimeMappings(runtimeMappings); sourceBuilder.sort(COMMISSION, SortOrder.DESC); SearchRequest searchRequest = new SearchRequest("index_t_spu"); searchRequest.source(sourceBuilder); try { SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); System.out.println(response); } catch (IOException e) { e.printStackTrace(); }

版本二:

script直接传source,string类型脚本,这样的话动态参数params就无法添加了,只能自己提前拼接好完整的script。类型是String。

POST /tb-g-mysql.tb_retail_goods.t_spu/_search
{
  "size": 1120, 
  "runtime_mappings": {
    "commission": {
      "type": "double",
      "script": "String commissionStr = doc['commission_price.keyword'].value;long price = doc['min_sku_sale_price'].value / 100;int indexNo = commissionStr.indexOf('~');if (indexNo > 0) {double allCommission = Double.parseDouble(commissionStr.substring(0, indexNo))*100;emit(allCommission - price * 0.03);} else {emit(Double.parseDouble(commissionStr)*100 - price * 0.03);}"
    }
  },
  "fields": [
    "commission"
  ],
  "query": {
    "term": {
     "goods_source_type": {
       "value": "1"
     }
   }
  },
  "sort": [
    {
      "commission": {
        "order": "desc"
      }
    }
  ]
}

java代码实现:(部分查询条件未具体实现)

spring-data-elasticsearch的客户端ElasticsearchRestTemplate 不支持:因为:RuntimeField不支持Script对象参数,因此直接string替换动态参数拼接script

es版本:7.17.1;spring-data-elasticsearch版本:4.3.9

@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;


NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder().filter(QueryBuilders.termQuery("xxx", 1));
NativeSearchQuery nativeSearchQuery = searchQueryBuilder.withTrackScores(true)
.withQuery(boolQueryBuilder)
.withPageable(PageRequest.of(dto.getPage() - 1, dto.getLimit()))
.withIndicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN).build();
String realPlatformCommissionRate = BigDecimal.valueOf(platformCommissionRate).divide(BigDecimal.valueOf(1000), 2, RoundingMode.HALF_UP).toString();
        String commissionScript = ("String commissionRateStr = doc['commission_rate.keyword'].value;" +
                "long price = doc['min_sku_sale_price'].value;" +
                "int indexNo = commissionRateStr.indexOf('~');" +
                "if (indexNo > 0) {double allCommission = Double.parseDouble(commissionRateStr.substring(0, indexNo))*100;" +
                "emit(allCommission - price * realPlatformCommissionRate);}" +
                "else {emit(Double.parseDouble(commissionRateStr)*100 - price * realPlatformCommissionRate);}")
                .replace("realPlatformCommissionRate", realPlatformCommissionRate);
        //runtime_mappings
        final String COMMISSION = "commission";
        //注意:RuntimeField不支持Script对象参数,因此直接string替换动态参数拼接script
        RuntimeField runtimeField = new RuntimeField(COMMISSION, FieldType.Double.getMappedName(), commissionScript);
        searchQuery.addRuntimeField(runtimeField);
        searchQuery.addFields(COMMISSION);
        if (StringUtils.isNotBlank(dto.getSort()) && SqlKeyword.DESC.name().equals(dto.getSort().toUpperCase())) {
            searchQuery.addSort(Sort.by(Sort.Direction.DESC, COMMISSION));
        } else {
            searchQuery.addSort(Sort.by(Sort.Direction.ASC, COMMISSION));
        }

 

posted @ 2023-04-17 18:53  下午喝什么茶  阅读(97)  评论(0编辑  收藏  举报