docker安装es
docker pull docker.elastic.co/kibana/kibana:7.9.3
docker pull docker.elastic.co/elasticsearch/elasticsearch:7.9.3
mkdir -p /root/app/elasticsearch/config
mkdir -p /root/app/elasticsearch/data
echo "http.host: 0.0.0.0" >/root/app/elasticsearch/config/elasticsearch.yml
chmod -R 777 /root/app/elasticsearch/
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
-v /root/app/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /root/app/elasticsearch/data:/usr/share/elasticsearch/data \
-v /root/app/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d docker.elastic.co/elasticsearch/elasticsearch:7.9.3
docker update elasticsearch --restart=always
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://47.112.150.204:9200 -p 5601:5601 -d docker.elastic.co/kibana/kibana:7.9.3
docker update kibana --restart=always
初步检索
_cat
get
查看es健康信息 http://47.112.150.204:9200/_cat/health
查看es所有节点 http://47.112.150.204:9200/_cat/nodes
查看es所有节点 http://47.112.150.204:9200/_cat/master
查看es所有节点 http://47.112.150.204:9200/_cat/indices
索引一个文档(保存)
put or post
http://47.112.150.204:9200/index/_doc/1
put请求不允许不带请求。
如果是post请求
- 不带id就是新增
- 带id且有数据就是更新
查询文档
get
http://47.112.150.204:9200/index/_doc/1
{
"_index": "index", // 索引
"_type": "_doc", // 那个类型
"_id": "1", // id
"_version": 2, // 版本号
"_seq_no": 1, // 并发控制,每次更新就会+1,乐观锁
"_primary_term": 1, // 同上,主分片重新分配,如重启就会从新分配
"found": true,
"_source": { // 真正类容
"name": "John Doe"
}
}
乐观锁查询测试
?if_seq_no=6&if_primary_term=1
成功
http://47.112.150.204:9200/index/_doc/1?if_seq_no=5&if_primary_term=1
失败
更新
post http://47.112.150.204:9200/index/_doc/1
{
"name":"123"
}
post http://47.112.150.204:9200/index/_doc/1/_update
{
"doc":{
"name":"123"
}
}
如果带update,内容和原来一样就什么都不做
删除
delete http://47.112.150.204:9200/index/_doc/1
批量导入
POST customer/external/_bulk
{"index":{"_id":"1"}}
{"name":"John Doe"}
{"index":{"_id":"2"}}
{"name":"John Doe"}
POST /_bulk
{"delete":{"_index":"website","_type":"blog","_id":"123"}}
{"create":{"_index":"website","_type":"blog","_id":"123"}}
{"title":"my first blog post"}
{"index":{"_index":"website","_type":"blog"}}
{"title":"my second blog post"}
{"update":{"_index":"website","_type":"blog","_id":"123"}}
{"doc":{"title":"my updated blog post"}}
https://github.com/elastic/elasticsearch/blob/master/docs/src/test/resources/accounts.json
搜索
检索方式
url
GET bank/_search?q=*&sort=account_number:asc
requestBody
GET /bank/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"account_number": "asc"
},
{
"balance": "desc"
}
]
}
聚合
GET /bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 100
},
"aggs": {
"genderAgg": {
"terms": {
"field": "gender.keyword",
"size": 10
},
"aggs": {
"blanceAvg": {
"avg": {
"field": "balance"
}
}
}
},
"avgBalanceAvg":{
"avg": {
"field": "balance"
}
}
}
}
}
}
映射
创建
PUT /like
{
"mappings": {
"properties": {
"name": { "type": "text"},
"age": {"type": "integer"},
"email":{"type": "keyword"}
}
}
}
给索引添加新的字段
PUT /like/_mapping
{
"properties": {
"emp-id": {
"type": "keyword",
"index": false
}
}
}
数据迁移
POST _reindex
{
"source": {
"index": "bank",
"type": "account"
},
"dest": {
"index": "newBank"
}
}
ik分词
安装,后解压放入plugins中
git clone https://github.com/medcl/elasticsearch-analysis-ik cd elasticsearch-analysis-ik git checkout tags/{version} mvn clean mvn compile mvn package
使用
POST _analyze
{
"analyzer": "ik_smart",
"text": "我是中国人"
}
POST _analyze
{
"analyzer": "ik_max_word",
"text": "我是中国人"
}
自定义分词
安装nginx
docker run -p80:80 --name nginx -
docker container cp nginx:/etc/nginx .
mkdir nginx
mv conf nginx/
cd nginx
docker run -p 80:80 --name nginx \
-v /root/app/nginx/html:/usr/share/nginx/html \
-v /root/app/nginx/logs:/var/log/nginx \
-v /root/app/nginx/conf/:/etc/nginx \
-d nginx
docker container cp nginx:/etc/nginx/conf/ /root/app/nginx/conf/
使用远程网页作为ik分词器字典
/root/app/nginx/html/es
cd /root/app/elasticsearch/plugins/elasticsearch-analysis-ik-7.9.3/config
<!--用户可以在这里配置远程扩展字典 -->
<entry key="remote_ext_dict">http://47.112.150.204/es/ik-test.txt</entry>
docker update elasticsearch --restart=always
Java整合es
<elasticsearch.version>7.9.3</elasticsearch.version>
<!--导入es高级api-->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.9.3</version>
</dependency>
添加配置類
@Configuration
public class ESConfig {
public static final RequestOptions COMMON_OPTIONS;
static {
RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
/* builder.addHeader("Authorization","Bearer"+TOKEN)
.setHttpAsyncResponseConsumerFactory(new HttpAsyncResponseConsumerFactory
.HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));*/
COMMON_OPTIONS = builder.build();
}
/**
* 注册es客户端
*
* @return {@link RestHighLevelClient}
*/
@Bean
public RestHighLevelClient esClient() {
return new RestHighLevelClient(
RestClient.builder(new HttpHost("47.112.150.204", 9200, "http"))
);
}
}
SKU在es中的模型
DELETE product
put product
{
"mappings": {
"properties": {
"skuId": {
"type": "long"
},
"spuId": {
"type": "keyword"
},
"skuTitle": {
"type": "text",
"analyzer": "ik_smart"
},
"skuPrice": {
"type": "keyword"
},
"skuImg": {
"type": "keyword",
"index": false,
"doc_values": false
},
"saleCount": {
"type": "long"
},
"hasStock": {
"type": "boolean"
},
"hotScore": {
"type": "long"
},
"brandId": {
"type": "long"
},
"brandName": {
"type": "keyword",
"index": false,
"doc_values": true
},
"brandImg": {
"type": "keyword",
"index": false,
"doc_values": true
},
"catalogId": {
"type": "long"
},
"catalogName": {
"type": "keyword",
"index": false,
"doc_values": true
},
"attrs": {
"type": "nested",
"properties": {
"attrId": {
"type": "long"
},
"attrName": {
"type": "keyword",
"index": false,
"doc_values": true
},
"attrValue":{
"type":"keyword"
}
}
}
}
}
}
上架spu
@Override
public boolean up(Long spuId) {
// 组装需要的数据
// 1.查出当前spuId对应的所有sku信息以及品牌
List<SkuInfoEntity> skuInfos = skuInfoService.getSkuBySpuId(spuId);
// 2.查询所有能被用来检索的属性
List<ProductAttrValueEntity> baseAttr = productAttrValueService.baseAttrList(String.valueOf(spuId));
// 收集所有属性的id
List<Long> attrIdList = baseAttr.stream()
.map(ProductAttrValueEntity::getAttrId).collect(Collectors.toList());
// 找到这些属性是可检索属性id
List<Long> searchAttrId = attrService.selectSearchAttrs(attrIdList);
HashSet<Long> idSet = new HashSet<>(searchAttrId);
// 找到这些可检索属性
List<SkuEsModel.Attrs> attrs = baseAttr.stream()
.filter(i -> idSet.contains(i.getAttrId()))
.map(i -> {
SkuEsModel.Attrs a = new SkuEsModel.Attrs();
a.setAttrId(i.getAttrId());
a.setAttrName(i.getAttrName());
a.setAttrValue(i.getAttrValue());
return a;
}).collect(Collectors.toList());
// 3.发送远程调用,查询是否有库存
Map<Long, Boolean> stockMap = null;
try {
List<SkuStockVo> data = wareFeignService.skuHasStock(skuInfos.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList()));
stockMap = data.stream()
.collect(Collectors.toMap(SkuStockVo::getSkuId, SkuStockVo::getHasStock));
} catch (Exception e) {
log.error("库存服务查询异常:原因{}", e);
}
// 4.将skuInfo封装成skuEsModel
Map<Long, Boolean> finalStockMap = stockMap;
List<SkuEsModel> esDataSkuEsModels = skuInfos.stream()
.map(sku -> {
SkuEsModel esModels = new SkuEsModel();
BeanUtils.copyProperties(sku, esModels);
esModels.setSkuPrice(sku.getPrice());
esModels.setSkuImg(sku.getSkuDefaultImg());
// 设置是否有销量
if (finalStockMap != null) {
esModels.setHasStock(finalStockMap.get(sku.getSkuId()));
} else {
esModels.setHasStock(true);
}
// TODO 2: 2020/11/17 热度
esModels.setHotScore(0L);
// 设置品牌相关信息
BrandEntity brandInfo = brandService.getById(esModels.getBrandId());
esModels.setBrandName(brandInfo.getName());
esModels.setBrandImg(brandInfo.getLogo());
// 设置分类相关的信息
CategoryEntity categoryInfo = categoryService.getById(esModels.getCatalogId());
esModels.setCatalogName(categoryInfo.getName());
// 设置属性
esModels.setAttrs(attrs);
return esModels;
}).collect(Collectors.toList());
// 5: 保存到es-上架
R esSave = null;
boolean flag = false;
try {
esSave = esFeignService.productStatusUp(esDataSkuEsModels);
if (esSave != null) {
flag = (boolean) esSave.get("hasFailures");
}
} catch (Exception e) {
log.error("es服务保存异常:原因{}", e);
}
if (!flag) {
// 上架成功,修改spu的状态
this.baseMapper.updateSpuStatus(spuId, ProductConstant.UP);
} else {
// 调用失败
// TODO: 2020/11/17 重复调用,接口幂等性
}
return !flag;
}
远程调用es服务
@FeignClient("mall-search")
public interface EsFeignService {
@PostMapping("/search/save/product")
R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels);
}
@Configuration
public class ESConfig {
public static final RequestOptions COMMON_OPTIONS;
static {
RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
/* builder.addHeader("Authorization","Bearer"+TOKEN)
.setHttpAsyncResponseConsumerFactory(new HttpAsyncResponseConsumerFactory
.HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));*/
COMMON_OPTIONS = builder.build();
}
/**
* 注册es客户端
*
* @return {@link RestHighLevelClient}
*/
@Bean
public RestHighLevelClient esClient() {
return new RestHighLevelClient(
RestClient.builder(new HttpHost("47.112.150.204", 9200, "http"))
);
}
}
@RestController
@RequestMapping("/search/save")
public class EsSaveController {
@Resource
ProductSaveService productService;
/**
* 上架产品
*
* @param skuEsModels sku es模型
* @return {@link R}
*/
@PostMapping("/product")
public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels) {
boolean b = productService.productStatusUp(skuEsModels);
return R.ok().put("hasFailures",b);
}
}
@Service
@Slf4j
public class ProductSaveServiceImpl implements ProductSaveService {
@Resource
RestHighLevelClient esClient;
@Override
public boolean productStatusUp(List<SkuEsModel> skuEsModels) {
BulkRequest bulkRequest = new BulkRequest();
for (SkuEsModel skuEsModel : skuEsModels) {
bulkRequest.add(new IndexRequest(EsConstant.PRODUCT_INDEX)
.id(skuEsModel.getSkuId().toString())
.source(JSON.toJSONString(skuEsModel), XContentType.JSON));
}
boolean hasFailures = false;
try {
BulkResponse bulk = esClient.bulk(bulkRequest, ESConfig.COMMON_OPTIONS);
hasFailures = bulk.hasFailures();
List<String> ids = Arrays.stream(bulk.getItems()).map(BulkItemResponse::getId).collect(Collectors.toList());
log.info("商品上" + (hasFailures ? EsConstant.failure : EsConstant.success) + ":id{}", ids);
} catch (IOException e) {
log.error("es存储异常:原因{}", e.getMessage());
}
return hasFailures;
}
}