商城项目-分布式基础-03-es服务搭建

448 阅读4分钟

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  

image-20201109213148366

put请求不允许不带请求。

如果是post请求

  1. 不带id就是新增
  2. 带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

image-20201109214245776

失败

image-20201109214309993

更新

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分词器字典

image-20201112193718057

/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;
    }
}