前言:本篇文章侧重于实战,不涉及原理相关的,毕竟能力有限,希望与大家共同进步学习,我也想向大神们多学习底层原理的东西,hhh。首先我会贴出以下两点:

  1. https://www.elastic.co/guide/index.html

  2. 官网十分详细,多看官网,定有帮助, 务必好好利用。

背景:一开始,对于es完全懵逼的状态,什么是es,可以支持sql吗?比mysql快吗?听说分布式高并发毫秒级响应,惊呼一声... ,小白入场了。借助质检项目,深陷es无法自拔,接下来请直接看代码。

一、Spring Data Elasticsearch的使用

说明:本文都是默认使用的springboot框架,使用es还是很方便的,要注意安装es的版本,这里我这边使用的6.3.2版本。附上官网:https://spring.io/projects/spring-data-elasticsearch

1、 pom依赖

org.springframework.boot

spring-boot-starter

2.1.6.RELEASE

org.springframework.boot

spring-boot-starter-data-elasticsearch

2.1.6.RELEASE

//注意:因为质检监播项目用的es是6.3.2,这里要使用2.0版本以上的springBoot版本。版本冲突相信大家都不愿再深陷其中了。

2、 spingboot配置文件

spring.data.elasticsearch.cluster-nodes=192.168.1.121:9200,192.168.1.122:9200,192.168.1.123:9200

spring.data.elasticsearch.cluster-name=zm-elk-test

// 就两行,so easy!

3、 java代码

@Data

@Builder

@Document(

indexName = "test_lesson_behavior_idx",

type = "result",

refreshInterval = "-1"

)

@NoArgsConstructor

@AllArgsConstructor

public class SuperviseCourseDoc implements Serializable {

/**

* id作为es主键

*/

@Id

@JsonProperty("id")

private String id;

@JsonProperty("les_uid")

private String lesUid;

/**

* 课程Id

*/

@JsonProperty("les_id")

private int lesId;

/**

* 年级

*/

@JsonProperty("grade")

private int grade;

/**

* 科目

*/

@JsonProperty("subject")

private int subject;

/**

* 上课时间

*/

@JsonProperty("les_start_time")

private String lesStartTime;

//...省略其他字段

}

简单解释下以上的SuperviseCourseDoc实体类相关注解 @Document 作用在类,标记实体类为文档对象,一般以下属性:

  • indexName:对应索引库名称

  • type:对应在索引库中的类型

  • shards:分片数量,默认5

  • replicas:副本数量,默认1

  • refreshInterval:es进行refresh操作的时间间隔,这里设置-1,因为不涉及更新操作,需要进行新增和更新es文档的,可自行设置该值。

作用在字段的注解:

  • @Id 作用在成员变量,标记一个字段作为id主键,注意,必须有@Id属性,不然就是一片红。。。

  • @Field 作用在成员变量,标记为文档的字段,并指定字段映射属性:

    type:字段类型,是枚举:FieldType,可以是text、long、short、date、integer、object等。

下面罗列出es常用的简单字段类型

字段类型

说明
text 存储数据时候,会自动分词,并生成索引
keyword 存储数据时候,不会分词建立索引
Numerical 基本数据类型:long、interger、short、byte、double、float...等
Date 日期类型,es可以对日期格式化为字符串存储,但是建议我们存储为毫秒值,存储为long,节省空间
keyword 存储数据时候,不会分词建立索引

这里着重介绍对于@mapping 注解使用的案例:

@Data

@Builder

@Document(

indexName = "meta_data_statistic_idx",

type = "result",

refreshInterval = "10s"

)

@NoArgsConstructor

@AllArgsConstructor

@Mapping(mappingPath = "/mappings/meta_data_statistic_idx.json")

public class MetaDataStatisticDoc implements Serializable {

/**

* es主键

*/

@Id

private String id;

/**

* 用户id

*/

@Field(type = FieldType.Keyword,fielddata = true)

private String userId;

/**

* 用户名称

*/

@Field(type = FieldType.Keyword,fielddata = true)

private String userName;

/**

* 访问时间

*/

@Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss")

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")

private Date visitDate;

...

}

metadatastatistic_idx.json文件内容:

{

"result": {

"properties": {

"tableId": {

"type": "long"

},

"userId": {

"type": "text",

"fields": {

"keyword": {

"type": "keyword",

"ignore_above": 256

}

}

},

"userName": {

"type": "text",

"fields": {

"keyword": {

"type": "keyword",

"ignore_above": 256

}

}

},

"visitDate": {

"type": "date",

"format": "yyyy-MM-dd HH:mm:ss"

}

...

}

}

}

es会默认根据新增doc的数据进行类型推断,生成映射的mapping。但是大多数时候,需要用到date类型或者其他复杂的object类型,就需要先定义好mapping而不是使用默认的。

在上述索引metadatastatistic_idx中,涉及到聚合和排序的场景:如果使用默认的生成mapping,会导致聚合或者排序报错。

注意:

  1. 聚合、排序的字段需要加 keyword 属性 或者 设置 fielddata = true(默认为false,尽量不要使用,耗内存)。所以这里使用@Mapping注解,提前创建好固定的mapping配置。@Mapping注解中mappingPath属性就是映射文件的地址,使用json格式,一般放在resources文件夹下。

  2. 对于date日期类型一定要注意时区问题,不然会比实际时间少8小时。这句注解记得带上,@JsonFormat (shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")


回到正文,介绍完es中doc实体相关的注解后,接下来就是调用dao查询。这里使用JPA的方式,只要定义一个接口,然后继承Repository提供的一些子接口,就可以拥有增删改查...

@Repository

public interface SuperviseCourseSearchRepository extends ElasticsearchRepository<SuperviseCourseDoc, String> {

@Override

Page<SuperviseCourseDoc> search(SearchQuery searchQuery);

}

点进 elasticsearchRepository,可以看到很多方法

public interface ElasticsearchRepository<T, ID extends Serializable> extends ElasticsearchCrudRepository<T, ID> {

<S extends T> S index(S var1);

Iterable<T> search(QueryBuilder var1);

Page<T> search(QueryBuilder var1, Pageable var2);

Page<T> search(SearchQuery var1);

...

void refresh();

}

// 这里我门值要用到分页条件查询的方法,有兴趣的可以去试试每一个方法的用法。

图1

图2

特别注意

  1. 小心使得万年船,这话太有道理了。不然你会死得很惨。一开始没有注意这里图2第一个红框框的类型,一顿copy-paste操作,发现运行就是报错,脑袋瓜挠破了也没用,心里琢磨着:别人的代码还是不好使,自己从头一个一个字母敲吧。

  2. 于是乎各种检索相关es资料,很快便发现差异之处,有的定义是int类型,有的是String类型。回头再看下es官网,发现es的id默认是会帮你生成一个长度为20个字符,URL安全,base64编码,GUID,分布式系统并行生成时不可能会发生冲突的主键。

  3. es还支持自定义id生成规则,所以有的是int类型有的是String类型。于是我改为将图2中的int改为了String,然后run起来,开森ing...


bug总是如影随形,谁让我们是最可爱和可恨的程序员。不是在改bug的路上,就是在创造bug的路上。

org.elasticsearch.transport.netty4.Netty4Transport.onException:1028 -exception caught on transport layer [[id: 0x223e1eef, L:/192.168.241.153:51555 - R:/10.31.53.185:9200]], closing connection io.netty.handler.codec.DecoderException: java.io.StreamCorruptedException: invalid internal transport message format, got (48,54,54,50) ......

2019-08-02 21:03:46.062 ERROR[main]o.s.d.e.r.support.AbstractElasticsearchRepository.:91 -failed to load elasticsearch nodes : org.elasticsearch.client.transport.NoNodeAvailableException: None of the configured nodes are available:

细心的童鞋,或许开头就看到潜藏的bug了。不过作为程序员,挖坑再填坑也是我们的求生技能。看到最后一句,好像说是节点不可用,那我就找运维去,运维大哥来了一句,别人都用着好好的呢,要不你问下别人。这有点尴尬,于是乎百度,google一顿搜索,几乎都是在说版本冲突,就这样深陷jar包冲突中不可自拔。一天时间怎么如此快。。。第二天早早来到公司,突然想起来核对下所有代码,包括配置项。真的是踩着雷了,端口号竟然弄错,为什么和运维提供的端口号不一样?怎么是9300而不是9200。

spring.data.elasticsearch.cluster-nodes=192.168.1.121:9300,192.168.1.122:9300,192.168.1.123:9300

spring.data.elasticsearch.cluster-name=zm-elk-test

// 于是我以迅雷不及掩耳之势修改了端口号,这回总算开心的跑起来了。

于是查了下es的9200与9300的区别:

  1. 9200作为Http协议,主要用于外部通讯。

  2. 9300作为Tcp协议,jar之间就是通过tcp协议通讯。

  3. ES集群之间是通过9300进行通讯。

看着log一行一行的打印到控制台,舒服。。。。

第一章节告一段落,请见下一章。

二、es聚合使用

前言:在质检系统中,有多个报表查询,例如这个:满意度趋势,按周为单位,显示近8周,按不同满意度进行区分。其中满意度的对象包含家长、学生、老师等角色,满意度分为1星、2星、...、5星。

用过mysql/oracle.....等的都知道,分不同角色进行简单的group by 时间周 and 满意度 就搞定。那elasticsearch是否能够支持?带着疑问打开官网,发现es的聚合功能如此强大。

1、es聚合概念

聚合认知概念:

  • 桶(Buckets)——满足特定条件的文档的集合

  • 指标(Metrics)——对桶内的文档进行统计计算(例如最小值,求和,最大值等).

  • SELECT COUNT(moduleid) FROM table GROUP BY moduleid。COUNT(moduleid) 相当于指标。GROUP BY moduleid相当于桶。

上图是从网上拿过来的一张图,很清晰地罗列出es的聚合分类与mysql的对照,方便大家理解。简单的count、sum、聚合也就不再举例,下面主要针对复杂的聚合

2、特殊的聚合

1) Histogram Aggregation

直方图聚合。就像名称一样,采用的是直方图的聚合方式,划分多个区间,根据划分的区间对数据进行聚合。理所当然的,直方图聚合指定的域必须是数字类型的。

关键属性

histogram:直方图聚合,指定的域必须是数字类型的;

interval:表示的是直方图的间隔,也就是各个直方的宽度;

extended_bounds:可以将直方图的范围延申。比如统计的2016-2019之间,若没有该属性,会导致没有值的年份,聚合结果没有此项。有时项目需要,没有结果的项也需要显示出来。

2) Date Histogram Aggregation

针对于时间格式数据的直方图聚合,基本的特性与 Histogram Aggregation 一致。详情参考:Date Histogram Aggregation(https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-datehistogram-aggregation.html)

关键属性

date_histogram:时间格式直方图聚合,指定的域必须是时间类型的;

interval:表示的是直方图的间隔,也就是各个直方的宽度,这里由几个值可选:

minute 、hours 、days 、week、month、quarter、year

extended_bounds:可以将直方图的范围延申,道理同histogram的case。

3、在某次迭代中,产品提了一个获取题目难度趋势的需求:

以下是16年到18年的统计数据表格:(仅测试参考数据)

难度等级 2016年 2017年 2018年 题目总数
一级 5 5 5 15
二级 5 5 5 15
三级 5 5 5 15
四级 5 5 5 15
五级 5 5 5 15

分析了需求后,不难发现只需要三个聚合就可实现,两个桶聚合:难度(qu_difficulty)、年份(year)(Histogram子聚合),还需要一个指标聚合对题目的count求和。案例code:

public class ExamAnalysisStrategyTest {

@Autowired

private ElasticsearchTemplate elasticsearchTemplate;

public void difficultyTrendTest(){

// 1、构建Aggregation对象(首先根据qu_difficulty聚合(升序排序),然后再根据year聚合(Histogram子聚合)并对count求和)

TermsAggregationBuilder aggregationBuilder= AggregationBuilders.terms(Constant.AGG_NAME_DIFFICULTYS)

.field(Constant.QU_DIFFICULTY).size(Integer.MAX_VALUE).order(Terms.Order.term(true));

HistogramAggregationBuilder subAggregationBuilder = AggregationBuilders.histogram(Constant.AGG_NAME_YEARS)

.field(Constant.YEAR).interval(Constant.HISTOGRAM_AGG_INTERVAL).extendedBounds(2016,2019);

SumAggregationBuilder sumAggregationBuilder = AggregationBuilders.sum(Constant.AGG_NAME_TOTAL_COUNT)

.field(Constant.COUNT);

aggregationBuilder.subAggregation(subAggregationBuilder.subAggregation(sumAggregationBuilder));

SearchRequestBuilder searchRequestBuilder = elasticsearchTemplate.getClient().prepareSearch("haku-taku_difficulty_summary").setTypes("result");

QueryBuilder queryBuilder = QueryBuilders.boolQuery()

.filter(QueryBuilders.termQuery("subject_id", 1))

.filter(QueryBuilders.termQuery("grade_id", 9));

searchRequestBuilder

.setQuery(queryBuilder)

.addAggregation(aggregationBuilder)

.setSize(0);

Aggregations aggregations = searchRequestBuilder.get().getAggregations();

// 2、转换成map集合

Map<String, Aggregation> aggregationMap = aggregations.asMap();

if(MapUtils.isNotEmpty(aggregationMap) && aggregationMap.get("years") != null){

// 3、获得对应的聚合函数的聚合子类,并获得Bucket

LongTerms longTerms = (LongTerms) aggregationMap.get("years");

// 4、获得所有的桶

List<LongTerms.Bucket> buckets = longTerms.getBuckets();

// 5、根据buckets结果进行解析,得到想要的数据.......

}

}

}

三、自定义脚本

前言:

在质检项目中,为了有效掌握异常课程数据,更好地进行决策,需要对数据的每一个属性进行权重打分。于是,开始了漫长的打分征途......打怪升级,有木有。

先看一下需求中定义的打分规则:

二级分类 权重
课程 10%
互动 20%
学生 10%
... ...
教师 10%
一级分类 权重
互动时间[0,10) 10%
互动时间[10,20) 30%
互动时间大于20 60%
... ...

打分规则还是比较复杂的,涉及到多个属性字段。es中有_score相关度得分这个概念:

1. Elasticsearch 默认是按照文档与查询的相关度(匹配度)的得分倒序返回结果的. 得分 (_score) 就越大, 表示相关性越高.

2. 分析应用场景后,发现查询时就需要设置权重boost,可是,我这里是不需要绑定查询条件来算出分数。并且使用sort排序过于绝对,它会直接忽略掉文档本身的相关度(根本不会去计算)。在很多时候这样做的效果并不好,这时候就需要对多个字段进行综合评估,得出一个最终的排序。

3. 于是疯狂查资料,原来es有自定义打分的函数。function_score: 用于处理文档分值的,它会在查询结束后对每一个匹配的文档进行一系列的重打分操作,最后以生成的最终分数进行排序。

它提供了几种默认的计算分值的函数:

参数 说明
weight 设置权重
fieldvaluefactor 将某个字段的值进行计算得出分数
random_score 随机得到 0 到 1 分数
衰减函数 同样以某个字段的值为标准,距离某个值越近得分越高
script_score 通过自定义脚本计算分值

它还有一个属性boostmode可以指定计算后的分数与原始的score如何合并,有以下选项:

参数 说明
multiply 将结果乘以_score
sum 将结果加上_score
min 取结果与_score的较小值
max 取结果与_score的较大值
replace 使结果替换掉_score

通过以上分析,script_score函数比较适合质检监播项目。

"script" : {

"source" : "params.weight * _score",

"params": {

"weight": 2

}

}

上述是一个简单的script脚本,根据传入的参数*es计算的score得到最后的分数。

目前es支持一种内置的painless是一种新支持的脚本语言,语言格式和java十分类似。可以参考以下文档:https://www.elastic.co/guide/en/elasticsearch/reference/5.6/modules-scripting-painless.html

说明:在es之前的老版本是支持groovy语言,后面才使用painless替换。至于为什么会如此,提供一个参考说法:关于script脚本的操作,以特定的方法使用groovy会在es里会遇到编译内存不释放的问题,长期使用会导致集群频繁old gc,最终full gc。

以下是质检监播的自定义script_score的脚本:

GET lesson_behavior_test3/_search

{

"from": 1,

"size": 20,

"query":{

"function_score" : {

"query" : {

"bool" : {

"boost" : 1.0

}},

"functions" : [

{

"script_score" : {

"script" : {

"source" : "List fieldList = new ArrayList();Map boostScoreMap = new HashMap();int totalScore = 0;fieldList.add('courseware_type');List coursewareTypeScoreList = new ArrayList();coursewareTypeScoreList.add(['dictCode':1,'score':0]);coursewareTypeScoreList.add(['dictCode':2,'score':5]);boostScoreMap.put('courseware_type',coursewareTypeScoreList);

...//其他参与打分的属性字段

;for(field in fieldList){int fieldValue = (int) doc[field].value;List fieldScoreList = (List)boostScoreMap.get(field);for(sMap in fieldScoreList){

...

} return totalScore;",

"lang" : "painless"

}}}

],

"boost_mode" : "replace",

"boost" : 1.0

}}}

看到这么长的脚本有木有绝望。。脚本语言没法用IDE工具调试,只能小心翼翼拼字符串。

这么难看的代码,估计没谁瞧得上了。。。于是就在es对应的config文件下定义好文件,把以上脚本写进文件里,省去拼接这么一长串字符,代码洁癖有时候还是有必要的!

就这样,重新计算score的代码开发完毕,紧接着在测试环境(数据比较少)简单测了一下,完美出来数据。。正开心那会,心想何不去uat环境模拟百万量级的数据show一把。。结果,被虐的不行,请继续看后文。

以下是抽样进行了多次试验,主要是按照索引分片、参与计算字段个数两大因素得出的平均耗时情况:

数量级 计算字段个数 5个分片耗时(ms) 10个分片耗时(ms) 15个分片耗时(ms)
400万 5 3101 1740 1030
400万 20 time-out 19240 21710
200万 5 1530 816 537
200万 20 time-out 12733 10806
100万 5 612 292 276
100万 20 10924 5790 4970
... ... ... ... ...
... ... ... ... ...

一看uat环境测出来的性能,这跟es快的特点完全不沾边啊,到底发生了什么?竟然还有timeout,这是在跟我开玩笑吧。然后得劲儿查资料,看到许多开发者都说scriptscore是会极大影响es查询性能的(从上表中看出参与计算字段达到22个的时候,耗时明显增长),具体影响多少,还不确定。紧接着,就是如何优化scriptscore。

谁能想到,竟是第一次写这种工具插件,内心还有小兴奋,边查资料边撸代码。原来写插件这么简单,官网就两句话:

The plugin documentation has more information on how to write a plugin so that Elasticsearch will properly load it. To register the ScriptEngine, your plugin should implement the ScriptPlugin interface and override the getScriptEngine(Settings settings) method.

The following is an example of a custom ScriptEngine which uses the language name expertscripts. It implements a single script called puredf which may be used as a search script to override each document’s score as the document frequency of a provided term.

第一句话,就是实现ScriptPlugin,实现es插件接口,可以被es作为插件加载

第二句话,就是实现ScriptEngine,自定义打分逻辑。

这是质检打分插件的代码结构:

至于红框框的到底是有什么作用,可以参考官网说明,接下来直接上代码:

打分逻辑实现:

package com.zmlearn.rec.qv.es.plugin.scoring;

import org.apache.commons.io.IOUtils;

import org.apache.logging.log4j.Logger;

import org.apache.lucene.index.LeafReaderContext;

import java.io.*;

import java.util.*;

/**

* @ClassName CalculateScoreScriptPlugin

* @Description 计算分数插件

* @Athor wenjie.fei

* @Date 2019/7/27 10:06

* @Version 1.0

* @CopyRight: 上海掌小门教育科技有限公司

**/

public class CalculateScoreScriptPlugin extends Plugin implements ScriptPlugin {

private final static Logger logger = ESLoggerFactory.getLogger(CalculateScoreScriptPlugin.class);

private final static String SPILT_SYMBOL = ",";

// 1、获取属性-score的权重配置(key:驼峰命名的java字段,value:权重score)

public static Map<String,JSONObject> boostScores = new HashMap<>();

/**

* 字段类型权重

*/

private static Map<String,Double> fieldTypeBoost = new HashMap<>();

static{

initFieldScoreBoost();

}

@Override

public ScriptEngine getScriptEngine(Settings settings, Collection<ScriptContext>> contexts) {

return new CalculateScoreScriptEngine();

}

/**

* 初始化字段分数权重

*/

private static void initFieldScoreBoost() {

fieldTypeBoost.put("teacher",0.4);

fieldTypeBoost.put("student",0.6);

InputStream inputStream = CalculateScoreScriptPlugin.class.getResourceAsStream("/data/field-score-data.json");

try {

String str = IOUtils.toString(new InputStreamReader(inputStream,"UTF-8"));

boostScores = JSONObject.parseObject(str,Map.class);

} catch (Exception e){

logger.error("CalAbnormalScoreScript initFieldScoreBoost is error",e);

}

}

private static class CalculateScoreScriptEngine implements ScriptEngine{

@Override

public String getType() {

return "abnormal_scoring_script";

}

@Override

public <T> T compile(String scriptName, String scriptSource, ScriptContext<T> context, Map<String, String> params) {

if (!context.equals(SearchScript.CONTEXT)) {

throw new IllegalArgumentException(getType() + " scripts cannot be used for context [" + context.name + "]");

}

if("qi_sv_abnormal_score".equals(scriptSource)) {

SearchScript.Factory factory = (p, lookup) -> new SearchScript.LeafFactory() {

// 对入参检查

String[] esScoreFieldsArray;

{

...

esScoreFieldsArray = ...;

}

@Override

public SearchScript newInstance(LeafReaderContext context) throws IOException {

return new SearchScript(p, lookup, context) {

@Override

public double runAsDouble() {

double totalScore = 0.0D;

int length = esScoreFieldsArray.length;

for(int i=0; i<length; i++){

...

//具体逻辑

}

....

return totalScore;

}

};

}

@Override

public boolean needs_score() {

return false;

}

};

return context.factoryClazz.cast(factory);

}throw new IllegalArgumentException("Unknown script name " + scriptSource);

}

@Override

public void close() {

}

}

}

需要注意的文件配置:

plugin-descriptor.properties

description=qi-supervise-video-esplugin to calculate score jar

version=1.0

name=qi-sv-abnormal

classname=com.zmlearn.rec.qv.es.plugin.scoring.CalculateScoreScriptPlugin

java.version=1.8

elasticsearch.version=6.3.2

至于plugin.xml(可自行百度)

一口气写完打分逻辑的代码,然后兴奋地打包放到本地es的plugins目录下,这里还涉及到plugin-security.policy文件相关的问题,可百度解决,最后重启es,执行以下语句:

GET test-lesson_behavior/_search

{

"from": 0,

"size": 20,

"query": {

"function_score": {

"query": {

"bool": {

"filter" : [

{

"range" : {

"les_start_time" : {

"from" : "2019-07-01 00:00:00",

"to" : "2019-09-26 23:59:59",

"include_lower" : true,

"include_upper" : false,

"boost" : 1.0

}

}

}

]

}

},

"functions": [

{

"script_score": {

"script": {

"source": "qi_sv_abnormal_score",

"lang": "abnormal_scoring_script",

"params": {"esScoreFields":"student_assessment_star,parent_assessment_star,sale_assessment_star,...course_ware_type"}

}

}

}

],

"score_mode": "multiply",

"boost_mode": "replace",

"boost": 1

}

}

}

注意: 可以看到script_score函数中 对应的source 属性就是自定义脚本中的 待编译执行的脚本源,而 lang 属性就是自定义脚本的type类型。

撸完代码的那个兴奋劲还没消失。。。瞬间变成沮丧感,将之前的测试方案执行一遍,发现性能指标竟然没什么变化。插件的性能竟没有比脚本提升多少?问题到底出在哪里?肯定有人说,那就加机器加内存,或许可以提升性能,但是硬件的价格总是不那么友好,也得考虑下成本。

总结:

  1. 其实重打分这个场景运用还是很广泛的,查阅官网和相关资料后,不少说script_score功能是会影响es性能的,特别是数据量比较大的情况下。但可结合rescore重打分功能,有效地使用,比如限定TOP1000之类的,一般用户可能就只对排在前面的数据感兴趣。

  2. es的应用越来越广泛,ELK都算是标配了。es的集群有良好的横向扩展性,也可做单机部署的轻量级搜索引擎。同时具有丰富的功能,比如全文检索、同义词处理、相关度排名、复杂数据分析、海量数据的近实时处理等。有兴趣的,可以多看源码,了解底层原理,挖掘es设计的美妙之处。

项目实体类报错_分享elasticserch在质检算法项目中的应用相关推荐

  1. 项目实体类报错_第一次开发项目感想

    1.大一感想 第一次真正的开发网站,就前两周开始,我大二期间学习了Java,在那时,我就开始考虑以后到底要从事哪方面的职业,我的专业是物联网,说实话,我真的对这方面没兴趣,我为什么对专业没兴趣,我也曾 ...

  2. spring boot多模块项目一个模块引用另一个模块的实体类报错空指针

    项目结构: server模块引用shiro模块中的实体类,server中的controller代码: @RestController public class ApiController {@Post ...

  3. jhipster修改jdl生成的实体类报错:liquibase.exception.ValidationFailedException: Validation Failed

    使用jhipster创建一个实体类之后,发现实体类不满足业务需求,需要修改: 如果是使用jdl创建的,那么我们只需要在原先的.jh文件中修改实体类内容,然后在项目根目录下执行命令: jhipster ...

  4. java项目导入包报错_转!java web项目 build path 导入jar包,tomcat启动报错 找不到该类...

    在eclipse集成tomcat开发java web项目时,引入的外部jar包,编译通过,但启动tomcat运行web时提示找不到jar包内的类,需要作如下配置,将jar包在部署到集成的tomcat环 ...

  5. git 在拉取代码的时候connect 谷歌报错_工具 | 手把手教你在VSCode中使用Git

    在一个目录下clone项目: git clone XXXXXX.git 使用VScode 打开项目 右击通过Code打开. 使用vscode提交代码 1.打开下面视图,添加一行文字: ## 测试提交 ...

  6. IDEA生成JPA实体类报错显示please choose pesistence unit

    出现这种情况的原因是因为添加JPA插件时没有添加persistence.xml映射文件,缺少数据库信息,无法进行持久化配置, 以下修改方法: 在添加JPA时点右侧+号,选择persistence.xm ...

  7. vue项目Error: Cannot find module ‘xxx’类报错的解决方法

    现发现只要是报错 Error: Cannot find module 'xxx'(例如 Error: Cannot find module 'webpack')这类的问题都可以用下面的方法解决. 报错 ...

  8. gradle 项目运行主类报错

    Error:gradle-resources-: java.lang.NoClassDefFoundError: org/apache/tools/ant/util/ReaderInputStream ...

  9. ssm启动不报错_解决idea导入ssm项目启动tomcat报错404的问题

    用idea写ssm项目,基于之前一直在用spring boot 对于idea如何运行ssm花费了一番功夫 启动tom act一直在报404 我搜了网上各种解决办法都不行,花费一天多的时间解决不了 就是 ...

最新文章

  1. 清华大学人工智能研究院成立听觉智能研究中心,将专注基础研究和成果产业化
  2. MySQL 报 Can't create more than max_prepared_stmt_count statements
  3. 30-35岁职场规划深谈,字字戳心
  4. oracle删除无效归档日志,求助:rman无法按照策略删除过期的归档日志
  5. 26期20180703 正则 grep
  6. leetcode 371. 两整数之和(不用算术运算符实现两个数的加法:按位异或原理)
  7. 带你从零入门 Serverless | 一文详解 Serverless 架构模式
  8. 112_Power Pivot 销售订单按 sku 订单类型特殊分类及占比相关
  9. 金橙子打标卡labview打标开发没头绪?c#封装成dll后labview调用真香!
  10. [短线是银]条件选股公式集源码!
  11. android 识别车牌颜色,Android、ios移动端车牌识别sdk / 车牌识别API
  12. 梅特勒托利多xk3124电子秤说明书_梅特勒托利多电子称设置方法
  13. 电商支付-使用Restful api接口集成Paypal支付方式(一)
  14. while循环的使用
  15. android自动照相机2.0,Nano Camera
  16. awl 多线程SYN***工具0.2版[转]
  17. OCR目标识别(车辆VIN码识别效果)
  18. 【C4D】材质+渲染自学宝典(纯干货)
  19. 51单片机中断地址表
  20. 我的世界服务器怎么做无限商店,我的世界无限商店指令 | 手游网游页游攻略大全...

热门文章

  1. TurboMail邮件系统为防垃圾邮件盗号提供专业方案
  2. vs2005中的aspnetdb(转)
  3. C++ 将模板申明为友元
  4. vue动态请求到的多重数组循环遍历,取值问题,如果某个值存在则显示,不存在则不显示。...
  5. 3 深入解析controlfile
  6. mysql 根据总分排名
  7. vue循环渲染本地图片不显示?
  8. python基于pillow库的简单图像处理
  9. LeetCode Search a 2D Matrix
  10. JWTToken在线编码生成