背景:需要根据一个实时计算处理的结果值进行排序,数据从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));
}

elasticsearch中runtime_mapping实战的更多相关文章

  1. Java生鲜电商平台-电商中海量搜索ElasticSearch架构设计实战与源码解析

    Java生鲜电商平台-电商中海量搜索ElasticSearch架构设计实战与源码解析 生鲜电商搜索引擎的特点 众所周知,标准的搜索引擎主要分成三个大的部分,第一步是爬虫系统,第二步是数据分析,第三步才 ...

  2. Elasticsearch.net项目实战

    elasticsearch.net项目实战 目录 Elasticsearch+kibana 环境搭建 windows 10环境配置 安装Elasticsearch head安装(非必需) 安装kiba ...

  3. 一文带您了解 Elasticsearch 中,如何进行索引管理(图文教程)

    欢迎关注笔者的公众号: 小哈学Java, 每日推送 Java 领域干货文章,关注即免费无套路附送 100G 海量学习.面试资源哟!! 个人网站: https://www.exception.site/ ...

  4. Elasticsearch核心技术与实战,性能是真牛

    Elasticsearch 是一款非常强大的开源搜索及分析引擎.结合 Kibana.Logstash和Beats,Elasticsearch 还被广泛运用在大数据近实时分析,包括日志分析.指标监控.信 ...

  5. 如何在Elasticsearch中安装中文分词器(IK+pinyin)

    如果直接使用Elasticsearch的朋友在处理中文内容的搜索时,肯定会遇到很尴尬的问题--中文词语被分成了一个一个的汉字,当用Kibana作图的时候,按照term来分组,结果一个汉字被分成了一组. ...

  6. elasticsearch运维实战之2 - 系统性能调优

    elasticsearch性能调优 集群规划 独立的master节点,不存储数据, 数量不少于2 数据节点(Data Node) 查询节点(Query Node),起到负载均衡的作用 Linux系统参 ...

  7. elasticsearch中常用的API

    elasticsearch中常用的API分类如下: 文档API: 提供对文档的增删改查操作 搜索API: 提供对文档进行某个字段的查询 索引API: 提供对索引进行操作,查看索引信息等 查看API: ...

  8. 在Elasticsearch中查询Term Vectors词条向量信息

    这篇文章有点深度,可能需要一些Lucene或者全文检索的背景.由于我也很久没有看过Lucene了,有些地方理解的不对还请多多指正. 更多内容还请参考整理的ELK教程 关于Term Vectors 额, ...

  9. elasticsearch中的API

    elasticsearch中的API es中的API按照大类分为下面几种: 文档API: 提供对文档的增删改查操作 搜索API: 提供对文档进行某个字段的查询 索引API: 提供对索引进行操作 查看A ...

  10. 使用Hive或Impala执行SQL语句,对存储在Elasticsearch中的数据操作(二)

    CSSDesk body { background-color: #2574b0; } /*! zybuluo */ article,aside,details,figcaption,figure,f ...

随机推荐

  1. NEMU PA 3-3 实验报告

    一.实验目的 在上一章PA3-2中,我们实现了分段机制,将48位的虚拟地址vaddr转换成了laddr.为什么不是paddr呢?这就要说到这一章要完成的东西:**分页机制 **. 从80386开始,计 ...

  2. NC19429 红球进黑洞

    题目链接 题目 题目描述 在心理疏导室中有一种奇特的疏导工具,叫做红球.红球被提前分为了许多正方形小方格. 每当有人来找ATB做心理疏导时,ATB就会让他去先玩红球,然后通过红球小格方的高度来判断一个 ...

  3. keras建模的3种方式——序列模型、函数模型、子类模型

    1 前言 keras是Google公司于2016年发布的以tensorflow为后端的用于深度学习网络训练的高阶API,因接口设计非常人性化,深受程序员的喜爱. keras建模有3种实现方式--序列模 ...

  4. PC端应用程序自动化测试——pywinauto、pywin32、pyautogui

    1 前言 PC 端自动化测试使用到的 python 模块主要有 pywinauto.win32gui.pyautogui,主要功能如下: pywinauto:主要使用到 Application 类,用 ...

  5. C++ std::move 的一些问题

    看 SO 上有一个比较奇怪的问题, When does an rvalue reference result in a move vs copy constructor and why? 问题代码: ...

  6. 多线程系列(八) -ReentrantLock基本用法介绍

    一.简介 在之前的线程系列文章中,我们介绍到了使用synchronized关键字可以实现线程同步安全的效果,以及采用wait().notify()和notifyAll()方法,可以实现多个线程之间的通 ...

  7. 【Android 逆向】【攻防世界】easy-so

    1. apk安装到手机,随便输入点内容,提示错误 2. jadx打开apk btn.setOnClickListener(new View.OnClickListener() { // from cl ...

  8. 【架构师视角系列】QConfig配置中心系列之Client端(二)

    目录 声明 配置中心系列文章 一.架构 一.客户端架 1.Server 职责 (1)配置管理 (2)配置发布 (3)配置读取 2.Client 职责 (1)配置拉取 (2)配置注入 (3)配置变更监听 ...

  9. @staticmethod/@classmethod/实例方法/@abstractmethod

    from abc import ABCMeta, abstractmethod # 定义一个抽象类 class Person(metaclass=ABCMeta): name = "泰山&q ...

  10. django中使用redis管道

    管道(事务),要是都成功则成功,失败一个全部失败 原理:将数据操作放在内存中,只有成功后,才会一次性全部放入redis 记住,redis中的管道可以开启事务处理,但是并没有回滚这一说法!跟mysql中 ...