tar -zxvf elasticsearch-7.6.1-linux-x86_64.tar.gz
1.2修改配置文件ulimit -Hn 65536
vim /etc/security/limits.conf
- soft nofile 65536
- hard nofile 65536
st soft memlock unlimited
st hard memlock unlimited
vim /etc/sysctl.conf
vm.max_map_count=655360
vm.swappiness=0
sysctl -p
vim config/elasticsearch.yml
#集群名称
cluster.name: shunteng-test
#节点名称
node.name: test-node-1
#数据文件夹
path.data: /u01/install/elasticsearch/data
#日志文件夹
path.logs: /u01/install/elasticsearch/log
#内存锁
bootstrap.memory_lock: true
官方文档建议(your node will need to bind to a non-loopback address)
network.host: 10.10.**//这行需要替换成自己的机器地址
#内部通信端口
transport.tcp.port: 9700
#端口号
http.port: 9200
cluster.name: shunteng-test
node.name: test-node-2
path.data: /u01/install/elasticsearch/data
path.logs: /u01/install/elasticsearch/log
bootstrap.memory_lock: true
network.host: 10.10.**//当前机器地址
http.port: 9200
discovery.seed_hosts: ["10.10., "10.10.]//这行需要替换成自己的机器地址
cluster.initial_master_nodes: [“test-node-1”,“test-node-2”]
discovery.zen.minimum_master_nodes: 1
action.destructive_requires_name: true
#内部通信端口
transport.tcp.port: 9700
node.max_local_storage_nodes: 3
./bin/elasticsearch -d
1.5检测是否正确启动curl http://10.10.:9200/_cat/health?v//这行需要替换成自己的机器地址
1.6java版本问题
如果因为elasticsearch启动时有配置java_home且java版本不同与所需java版本
可以修改elasticsearch-env脚本
去掉
39 if [ ! -z “
J
A
V
A
H
O
M
E
"
]
;
t
h
e
n
40
J
A
V
A
=
"
JAVA_HOME" ]; then 40 JAVA="
JAVAHOME"];then40JAVA="JAVA_HOME/bin/java”
41 JAVA_TYPE=“JAVA_HOME”
42 else
#中间部分要保留
50 fi
2安装ik分词插件
cd elasticsearch-7.6.1/plugins/
mkdir ik
cd ik/
cp /u01/install/elasticsearch-analysis-ik-7.6.1.zip elasticsearch-analysis-ik-7.6.1.zip
unzip elasticsearch-analysis-ik-7.6.1.zip
curl http://10.10.:9200/_cat/plugins//这行需要替换成自己的机器地址
tar -xzf kibana-7.6.1-linux-x86_64.tar.gz
cd kibana-7.6.1-linux-x86_64/config/
vim kibana.yml
server.port: 5601
server.host: “0.0.0.0”
server.name: “kibana-test”
#elasticsearch节点链接
elasticsearch.hosts: [“http://10.10.:9200","http://10.10.:9200”]
elasticsearch.requestTimeout: 99999
i18n.locale: “zh-CN”
cd /u01/logs/
mkdir kibana
cd kibana/
启动方式:
nohup /u01/install/kibana-7.6.1-linux-x86_64/bin/kibana > /u01/logs/kibana/kibana.log 2>&1 &
查找kibana 进程
ps -auxf|grep kibana
kibana首页:http://10.10.*:5601/app/kibana#/home
3常见elasticSearch *** 作 3.1索引 *** 作添加索引
put index_test
查询索引
Get index_test
删除索引
Delete index_test
关闭索引
post index_test/_close
打开索引
post index_test/_open
新建映射
PUT index_test_2/_mappings
{ “properties”: {
“test”: {
“type”: “text”
}
}
}
ik分词类型有两种ik_max_word,ik_smart
新建索引并新建映射
PUT article_index
{
“settings”: {
“analysis”: {
“analyzer”: {
“comma”: {
“type”: “pattern”,
“pattern”:",|,"
}
}
}
},
“mappings”: {
“properties”: {
“upadta_time”:{
“type”: “date”
},
“title”:{
“type”: “text”,
“analyzer”: “ik_smart”,
“search_analyzer”: “ik_smart”
},
“keywords”: {
“type”: “text”,
“analyzer”: “comma”,
“search_analyzer”: “ik_smart”
},
“category_id”:{
“type”: “long”,
“null_value”: 0
}
}
}
}
PUT index_test/_doc/1520 { "id" :1520, "content":"测试", "title":"测试" } GET /index_test/_search { "query": { "match": { "id":1520 } } }
根据id删除文档
DELETE /index_test/_doc/152
POST seller_settle_index/_search { "size":0, "post_filter":{ "bool":{ "filter":[ { "term":{ "isSettle":{ "value":1, "boost":1 } } } ], "adjust_pure_negative":true, "boost":1 } }, "aggregations":{ "agg":{ "date_histogram":{ "field":"updatetime", "format":"yyyy-MM-dd", "calendar_interval":"1d", "offset":0, "order":{ "_key":"asc" }, "keyed":false, "min_doc_count":1 }, "aggregations":{ "payAmount":{ "sum":{ "field":"payAmount" } }, "amount":{ "sum":{ "field":"amount" } }, "fee":{ "sum":{ "field":"fee" } }, "platformCharges":{ "sum":{ "field":"platformCharges" } }, "bucket_field":{ "bucket_sort":{ "from":0, "size":10, "gap_policy":"SKIP" } } } } }, "aggs": { "count": { "cardinality": { "field": "" } } } }4. 实际应用
用于产品搜索,实际需求是需要付费用户在前,但是不能出现一个用户太多产品,导致一页全是该用户的情况出现
4.1term查询问题解决办法:
1 直接使用match查询,但会分词,查出其他不必要的
2 使用该字段的mapping属性".keyword",因为es对string类型的字段默认为text,fields表示对一个字段设置多种索引模式,同一个字段的值,一个分词,一个不分词,而keyword就是不分词。
3 在写入新字段钱先手动设置mapping,这可保证你需要字段的类型
4.2.取别名处理中将付费产品和免费产品分作不同的索引,用于优化根据会员类型查询时的消耗
但是查询中可能需要同时查询两个索引,所以给两个索引取了别名
POST /_aliases { "actions": [ { "add": { "index": "index_product_payed", "alias": "index_product_all" } }, { "add": { "index": "index_product", "alias": "index_product_all" } } ] }4.3 实际应用上根据userId分组产品并自定义评分,按照评分排序
POST index_product_payed/_search { "from": 0, "size": 0, "query": { "function_score": {//自定义评分, "query": { "bool": { "should": [ { "term": { "productName": { "value": "机械", "boost": 300 } } } ], "adjust_pure_negative": true, "boost": 1 } }, "functions": [ { "field_value_factor": { "field": "productScore",//创建索引时会在后端判断评分,自己添加进去一个影响评分的字段 "factor": 1, "missing": 1, "modifier": "sqrt" } } ], "score_mode": "multiply", "boost_mode": "multiply", "max_boost": 3.4028235e+38, "boost": 1 } }, "aggregations": { "group_by_userId": { "terms": { "field": "userId", "size": 400,//获取的桶数量 "min_doc_count": 1, "shard_min_doc_count": 0, "show_term_doc_count_error": false, "order": [//按照分桶内最大值排序 { "maxSource": "desc" }, { "_key": "asc" } ] }, "aggregations": { "maxSource": { "max": {//因为实际上_score是一个runtime字段所以想要取这个字段需要使用"script", //如果需要使用文档内字段”price“可以直接使用 "field": "price" "script": { "source": "_score" } } }, "onlyOne": { "top_hits": { "from": 0, "size": 1,//一个分桶内只获取一条数据 "version": false, "seq_no_primary_term": false, "explain": false,//为true的话评分明细返回,会影响性能 "_source": {//需要返回的字段 "includes": [ "productName", "companyName", "companyAddr", "imgs", "price", "majorProd", "majorProdProp", "manageModel", "id", "minordernum", "productAttr", "shopId", "userId", "enName", "isPromote", "updateTime", "categoryId", "minordernum", "calCeil" ], "excludes": [] }, "sort": [ {//按照分桶内分数排序 "_score":{ "order": "desc" } } ] } } } } } }4.4 java实现
@Service public class IndexProductServiceImpl implements IndexProductService{ protected Logger log = LoggerFactory.getLogger(getClass()); static String listIncludes[] = {"productName","companyName","companyAddr","imgs","price","majorProd","majorProdProp","manageModel", "id","minordernum","productAttr","shopId","userId","enName","isPromote","updateTime","categoryId","minordernum","calCeil"}; static String imgIncludes[] = {"productName","companyName","companyAddr","imgs","price","manageModel","id","minordernum", "shopId","userId","enName","isPromote","updateTime","categoryId","minordernum","calCeil"}; //默认分类id评分300 static Integer categoryIdBoost = 300; @Autowired private RestHighLevelClient restHighLevelClient; private static final String payIndexName = "index_product_payed"; private static final String AllIndexName = "index_product_all"; private static final String freeIndexName = "index_product"; private static final Integer aggDefaultNum = 400; private static final Integer compAddrBoost = 0; private static final String CACHEKEY = "groupNum_kw_"; private static final Integer freeUserType = 0; private static final Integer vipUserType = 1; @Autowired private JedisUtil searchGroupNumCache; private List5 所使用的性能优化matchIndexAll(ProductsQuery productsQuery) throws IOException { SearchRequest productSearch = new SearchRequest(AllIndexName); //搜索条件对象 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); Integer pageNum = null == productsQuery.getPageNum() ? 1 : productsQuery.getPageNum(); Integer pageSize = null == productsQuery.getPageSize() ? 40 : productsQuery.getPageSize(); Integer offset = pageNum - 1 * pageSize; MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("productName", productsQuery.getKw()); //非聚合查询 if(productsQuery.getOffset() != null) { offset = productsQuery.getOffset() + offset; } sourceBuilder.from(offset); sourceBuilder.size(pageSize); //选取需要返回的字段 if(StringUtils.isBlank(productsQuery.getView()) || "list".equals(productsQuery.getView())){ sourceBuilder.fetchSource(listIncludes, null); } else { sourceBuilder.fetchSource(imgIncludes, null); } sourceBuilder.query(matchQuery); //发送请求 SearchResponse response = restHighLevelClient.search(productSearch, RequestOptions.DEFAULT); List list=new ArrayList<>(); //这里到时候调试的时候要转成对应的类型 for (SearchHit hit : response.getHits().getHits()) { IndexProduct index = BeanUtil.mapToBean(hit.getSourceAsMap(), IndexProduct.class, true); index.setId(Long.valueOf(hit.getId())); list.add(index); } return list; } @Override public List query(ProductsQuery productsQuery) throws IOException { if(CollectionUtils.isNotEmpty(productsQuery.getSearchWords()) && productsQuery.getSearchWords().size() > 5 && StringUtils.isNotBlank(productsQuery.getKw()) && productsQuery.getKw().length() > 12) { matchIndexAll(productsQuery); } Long startime = System.currentTimeMillis(); //创建搜索对象 SearchRequest productSearch; if(productsQuery.getUserType() == null || productsQuery.getUserType() == vipUserType) { productSearch = new SearchRequest(payIndexName); }else { productSearch = new SearchRequest(freeIndexName); productsQuery.setQueryType(ProductSearchType.NO_AGG_SEARCH.getId()); } //搜索条件对象 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); //配置分页信息 Integer pageNum = null == productsQuery.getPageNum() ? 1 : productsQuery.getPageNum(); Integer pageSize = null == productsQuery.getPageSize() ? 40 : productsQuery.getPageSize(); Integer needCount = pageNum * pageSize; //拼接搜索条件 BoolQueryBuilder query = buildBoolQuery(productsQuery); FieldValueFactorFunctionBuilder fieldQuery = new FieldValueFactorFunctionBuilder("productScore"); // 额外分数=log(1+score) fieldQuery.factor(1).missing(1).modifier(FieldValueFactorFunction.Modifier.SQRT); FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(query, fieldQuery); functionScoreQuery.boostMode(CombineFunction.MULTIPLY); if(productsQuery.getQueryType() == ProductSearchType.NO_AGG_SEARCH.getId()) { //非聚合查询 Integer offset = (pageNum -1)*pageSize; if(productsQuery.getOffset() != null) { offset = productsQuery.getOffset() + offset; } sourceBuilder.from(offset); sourceBuilder.size(pageSize); //选取需要返回的字段 if(StringUtils.isBlank(productsQuery.getView()) || "list".equals(productsQuery.getView())){ sourceBuilder.fetchSource(listIncludes, null); } else { sourceBuilder.fetchSource(imgIncludes, null); } }else { //如果超出默认查询部分就从免费中获取数据 if(needCount > aggDefaultNum) { productsQuery.setUserType(freeUserType); productsQuery.setPageSize(pageSize); Integer freePageNum = needCount - aggDefaultNum % 40 == 0 ? needCount - aggDefaultNum / 40 : needCount - aggDefaultNum / 40 + 1; productsQuery.setPageNum(freePageNum); Object numCache = searchGroupNumCache.getObj(CACHEKEY + productsQuery.getKw()); if(numCache == null) { //如果没有付费数据查询数量的缓存进行一次查询 ProductsQuery onePageQuery = BeanUtil.copyProperties(productsQuery, ProductsQuery.class); onePageQuery.setPageNum(1); onePageQuery.setPageSize(1); query(onePageQuery); numCache = searchGroupNumCache.getObj(CACHEKEY + productsQuery.getKw()); } Integer offset = (Integer)numCache; productsQuery.setOffset(offset); log.error("take time :"+ (System.currentTimeMillis()-startime)); return query(productsQuery); } //聚合查询 分组查询时不返回hits sourceBuilder.from(0); sourceBuilder.size(0); AggregationBuilder aggregation = AggregationBuilders.terms("group_by_userId").field("userId").minDocCount(1).size(aggDefaultNum) //排序,false是DESC,true是ASC .order(BucketOrder.aggregation("maxSource", false)); TopHitsAggregationBuilder topHitsAggregation = AggregationBuilders.topHits("onlyOne").size(aggDefaultNum).size(1); if(productsQuery.getExpain()) { topHitsAggregation.explain(true); } //选取需要返回的字段 if(StringUtils.isBlank(productsQuery.getView()) || "list".equals(productsQuery.getView())){ topHitsAggregation.fetchSource(listIncludes, null); } else { topHitsAggregation.fetchSource(imgIncludes, null); } if(productsQuery.getQueryType() == ProductSearchType.AGG_BY_PRICE.getId()) { //排序的额外字段为价格 aggregation.subAggregation(AggregationBuilders.max("maxSource").field("price")); }else { aggregation.subAggregation(AggregationBuilders.max("maxSource").script( new script("_score"))); } aggregation.subAggregation(topHitsAggregation); sourceBuilder.aggregation(aggregation); } //封装搜索条件 if(productsQuery.getQueryType() == ProductSearchType.AGG_BY_PRICE.getId()) { sourceBuilder.query(query); }else { sourceBuilder.query(functionScoreQuery); } //封装搜索对象 productSearch.source(sourceBuilder); //发送请求 SearchResponse response = restHighLevelClient.search(productSearch, RequestOptions.DEFAULT); List list=new ArrayList<>(); //这里到时候调试的时候要转成对应的类型 if(productsQuery.getQueryType() == ProductSearchType.NO_AGG_SEARCH.getId()) { //非聚合查询的解析 for (SearchHit hit : response.getHits().getHits()) { IndexProduct index = BeanUtil.mapToBean(hit.getSourceAsMap(), IndexProduct.class, true); index.setId(Long.valueOf(hit.getId())); list.add(index); } }else { Aggregations aggregations = response.getAggregations(); List aggList = aggregations.asList(); if(CollectionUtils.isEmpty(aggList)) { log.error("take time :"+ (System.currentTimeMillis()-startime)); return list; }else { //聚合查询的解析 Terms terms = (Terms) aggregations.asList().get(0); for (Bucket bucket : terms.getBuckets()) { List aggs = bucket.getAggregations().asList(); TopHits topHits = (TopHits) aggs.get(0); SearchHits hits = topHits.getHits(); if(hits.getHits() != null && hits.getHits().length > 0) { for(SearchHit hit:hits.getHits()) { IndexProduct index = BeanUtil.mapToBean(hit.getSourceAsMap(), IndexProduct.class, true); if(productsQuery.getExpain()) { Explanation expain = hit.getExplanation(); log.error("pid_"+ index.getId() + "_explanation:" + expain.toString()); } index.setId(Long.valueOf(hit.getId())); list.add(index); } } } } searchGroupNumCache.setObj(CACHEKEY + productsQuery.getKw(), aggDefaultNum - list.size()); //如果付费数量不足从免费中抽取数量,补足400条 if(list.size() < needCount) { productsQuery.setUserType(freeUserType); Integer pageSise = needCount - list.size(); productsQuery.setPageSize(pageSise); productsQuery.setPageNum(1); Integer offset = (Integer) searchGroupNumCache.getObj(CACHEKEY + productsQuery.getKw()); productsQuery.setOffset(offset); list.addAll(query(productsQuery)); } } log.error("take time :"+ (System.currentTimeMillis()-startime)); if(list.size() > pageNum * pageSize) { return list.subList((pageNum-1) * pageSize, pageNum * pageSize); } return list; } @Override public Long queryCount(ProductsQuery productsQuery) throws IOException { //创建付费搜索对象 CountRequest vipCountSearch = new CountRequest(payIndexName); BoolQueryBuilder query = buildBoolQuery(productsQuery); vipCountSearch.query(query); //发送请求 CountResponse vipResponse = restHighLevelClient.count(vipCountSearch, RequestOptions.DEFAULT); Long vipCount = vipResponse.getCount(); //创建免费搜索对象 CountRequest freeCountSearch = new CountRequest(freeIndexName); freeCountSearch.query(query); //发送请求 CountResponse freeResponse = restHighLevelClient.count(freeCountSearch, RequestOptions.DEFAULT); Long freeCount = freeResponse.getCount(); return vipCount + freeCount; } private BoolQueryBuilder buildBoolQuery(ProductsQuery productsQuery){ BoolQueryBuilder query = QueryBuilders.boolQuery(); if (StringUtils.isNotBlank(productsQuery.getKw())){ query.should(QueryBuilders.termQuery("productName", productsQuery.getKw())); } //产品名称搜索词数组 if (CollectionUtils.isNotEmpty(productsQuery.getSearchWords())){ for(SearchWords kw : productsQuery.getSearchWords()) { if(kw != null && StringUtils.isNotBlank(kw.getWord())) { query.should(QueryBuilders.termQuery("productName", kw.getWord()).boost(kw.getBoost())); query.should(QueryBuilders.termQuery("keywords", kw.getWord()).boost(kw.getBoost())); } } } //分类id数组 if (CollectionUtils.isNotEmpty(productsQuery.getCategoryIds())){ for(Long id : productsQuery.getCategoryIds()) { if(id != null) { query.should(QueryBuilders.termQuery("categoryId", id).boost(categoryIdBoost)); } } } //产品属性 if (CollectionUtils.isNotEmpty(productsQuery.getFeatures())){ for(String featureWord : productsQuery.getFeatures()) { if(StringUtils.isNotBlank(featureWord)) { query.should(QueryBuilders.termQuery("productAttrValue", featureWord)); } } } if (productsQuery.getUserSource() != null) { query.must(QueryBuilders.termQuery("userSource", productsQuery.getUserSource())); } if (productsQuery.getCityId() != null) { query.must(QueryBuilders.termQuery("cityId", productsQuery.getCityId())); } if (productsQuery.getProvinceId() != null) { query.must(QueryBuilders.termQuery("provinceId", productsQuery.getProvinceId())); } if (productsQuery.getCategoryName() != null) { query.must(QueryBuilders.termQuery("categoryName", productsQuery.getCategoryName())); } return query; } }
最开始这个分组的查询语句耗时甚至需要几千毫秒,显然是不可以接受的,此是就遇到了性能优化问题,
1将需要精准命中的数字字段修改成了keyword字段, 2 设置需要分组的字段官方文档给这个字段的定义
默认情况下会在第一次搜索期间加载字典,但是设置eager_global_ordinals后,在每次refresh时会更新字典,常驻于内存会减少分组的耗时,但是有利有弊会增加新增索引以及refresh的开销,由于类似于groupBy的 *** 作,所以是按照相同值进行分组,并不适合大量新增以及修改的索引,也不适合于大量不同值,很少出现相同值的字段
"userId" : { "type" : "keyword", "eager_global_ordinals" : true }3 业务层面上减少搜索的条件 避免一个条件会查询到大量文档
处理中将付费产品和免费产品分作不同的索引,用于优化根据会员类型查询时的消耗
进行优化后,当构造器被缓存之后,查询基本在50ms左右,如果查询结果被缓存后,查询时间会压缩到10ms以内
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)