ElasticSearch的学习

ES中索引类似于mysql的库
类型类似于表
文档类似于记录
然后字段为属性,然后是以json的方式存储在内存中
docker 进行Es的安装
1.拉镜像
docker pull elasticsearch:7.4.2 存储和检索数据
docker pull kibana:7.4.2 可视化检索数据
2.设置配置文件的相关路径 用于后面挂载
mkdir-p /mydata/elasticsearch/config
mkdir-p /mydata/elasticsearch/data
echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml
3.docker启动容器
docker run--name elasticsearch-p 9200:9200-p 9300:9300 -e "discovery.type=single-node"
-e ES_JAVA_OPTS="-Xms64m-Xmx512m" -v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml -v /mydata/elasticsearch/data:/usr/share/elasticsearch/data -v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins -d elasticsearch:7.4
-p 端口的设置 -e是环境变量的设置 -v是配置文件的挂载 -d表示后台运行
4.查看相应容器的日志
docker logs 容器id/容器名
ES的常规操作(以下过时了,ES还需学新版本)
接口的形式进行对ES的操作
查看节点情况

post和put的方式保存数据(说是类型的概念已经没有了)

查询文档

在具体的接口下根据Id查到信息后,修改时尽量要带上
?if_seq_no=* &if_primary_term= *
进行乐观锁判断,就你get查到的这两个字段是什么则带上,如果成功修改会自动加1
更新文档

除了之前post和put带上id的情况下可以直接更新
也可以路径带上_update(更新值与原来相同时不变,版本与序列号也不变,也可以更新的同时新增)
要用doc{
} 框起
删除文档

批量录入_bulk
当直接为 localhost:9200/_bulk,这种就是所有的索引都进行文档的批量操作
路径没有指定具体的索引
需要请求体时,则两个大括号为一组
index和create操作似乎都能创建索引,但肯定是有什么不同的
create就是更具体,内容更详细了
两种检索索引数据的方式

第一个get为url的请求方式
但通常用第二种 称为query DSL
queryDSL基本语法
基本查询

查找的索引返回部分字段

_source指定要查询的字段
match查询 指定具体的查询条件 (注意得分,分越高,相关性越高)

当查询的内容不想进行分词去查找时,则要指定为短语查找,当作整一句进行看
多字段查询

会分词查询
复合查询 bool

must为必须字段必须满足的情况,mustnot为比不满足,should是可以满足,满足时得分最高
filter 与 must 功能和用法相同,但不会影响分数
term(非文本时使用) 与 match(文本时使用)作用相同,但是精确查找
aggregations 类似sql的group和各种聚合函数
有很多种不同的聚合方法 也就是类似于sql的各种函数方法,要了解时需在官方文档查看
es
GET bank/_search
{
"query":{
"match": {
"address": "mill"
}
},
//使用聚合的关键字
"aggs": {
//聚合名 ageAgg
"ageAgg": {
//聚合类型 terms
//age值进行分类,显示前10个分类
"terms": {
"field": "age",
"size": 10
}
},
//第二个聚合 可在写一套聚合规则,且可在该聚合的基础下,再进行聚合,子聚合只能写一个
"ageAgg2":{
"aggAvg":{
//工资的平均值
"avg":{
"filed": "balance"
}
}
},
//直接在aggs内部再写个aggs
"aggs":{
}
}
}
mapping类型映射
创建索引时,创建的字段ES会自动分配字段类型,都有时候不符合我们的要求
则我们可以再创建索引的时候,就指定好类型
es
PUT /my_index
{
"mappings":{
"properties" :{
"age":{"type":"integer"},
"email":{"type":"keyword"},//类型为keyword的时候不会分词搜索
"name":{"type":"text"}//会分词搜索
}
}
}
给索引添加新的映射字段
es
PUT /my_index/_mapping
{
"mappings":{
"properties" :{
"employee-id":{
"type":"integer",
"index": false //默认为true,设置为false时则不可由该字段进行检索}
}
}
}
修改索引的映射字段 不能修改已存在的字段,只能创建新索引,再把旧索引的数据迁移

在服务器上快速启动一个nginx服务器
首先直接通过docker拉取一个nginx服务器,没有镜像会自动先下载镜像
docker run -p 80:80 --name nginx -d nginx:1.10
我们所要的是拉取下来的nginx服务器的配置文件,找到自己想要保存配置的地址下,输入下面的命令,然后调整文件的保存目录
docker container cp nginx:/etc/nginx .
mv nginx conf
mkdir nginx
mv conf nginx
有了这套配置文件后,就docker正常运行nginx容器然后挂载
docker run -p 80:80 --name nginx
-v /mydata/nginx/html:/usr/share/nginx/html
-v /mydata/nginx/logs:/var/log/nginx
-v /mydata/nginx/conf:/etc/nginx
-d nginx:1.10
挂载 html页面的路径,然后日志的路径 ,配置的路径
创建新模块,用于elasticsearch
有很多种在springboot调用ela的方式,这边用的是
maven
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.4.2</version>
</dependency>
不能直接用,需要自己去写配置类
java
/**
* 1、导入依赖
* 2、编写配置,给容器中注入一个RestHighLevelClient
* 3、
*/
@Configuration
public class GulimallElasticSearchConfig {
@Bean
public RestHighLevelClient esRestClient(){
RestClientBuilder builder = null;
builder = RestClient.builder(new HttpHost("192.168.29.103",9200,"http"));
RestHighLevelClient client = new RestHighLevelClient(builder);
return client;
}
}
设置同一的请求头
我们知道是通过接口去访问es,进行增删改查的,则对于各种请求我们想要设置,则需在配置类中调整,做为单实例
java
@Configuration
public class GulimallElasticSearchConfig {
public static final RequestOptions COMMON_OPTIONS;
static {
RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
//对于builder进行各种的设置
COMMON_OPTIONS = builder.build();
}
@Bean
public RestHighLevelClient esRestClient(){
RestClientBuilder builder = null;
builder = RestClient.builder(new HttpHost("192.168.29.103",9200,"http"));
RestHighLevelClient client = new RestHighLevelClient(builder);
return client;
}
}
索引的保存和更新,只要指定好了索引id
java
@Test
public void indexData() throws IOException {
//设置索引名
IndexRequest indexRequest = new IndexRequest("users");
//设置id
indexRequest.id("1");
//键值对的方式往索引加值
//indexRequest.source("userName","zhangsan","age",18);
//第二种,以json对象的方式加入值,也是我们常用的
User user = new User();
user.setUserName("lihao");
user.setAge(18);
user.setGender("男");
String jsonString = JSON.toJSONString(user);
//第二个参数要设置为json类型
indexRequest.source(jsonString, XContentType.JSON);
//我们创建好的索引,我们配置类设置好的请求头
IndexResponse index = client.index(indexRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
System.out.println(index);
}
保存 查询 删除的api方式需要查看es文档
复杂查询
一个简单的查询过程
java
@Test
public void searchData() throws IOException{
//1.创建检索请求
SearchRequest searchRequest = new SearchRequest();
//指定索引
searchRequest.indices("bank");
//指定DSL,检索条件
//通过SearchSourceBuilder sourceBuilder 封装的条件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//1.1构造检索条件 es控制台能操作的,代码也都是可以操作
// sourceBuilder.query();
// sourceBuilder.from();
// sourceBuilder.size();
// sourceBuilder.aggregation();
//又进行QueryBuilders的条件封装
sourceBuilder.query(QueryBuilders.matchQuery("address","mill"));
System.out.println(searchRequest.toString());
searchRequest.source(sourceBuilder);
//2.执行检索
SearchResponse searchResponse = client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
//3.分析结果
System.out.println(searchResponse.toString());
}
加入聚合后
java
@Test
public void searchData() throws IOException{
//1.创建检索请求
SearchRequest searchRequest = new SearchRequest();
//指定索引
searchRequest.indices("bank");
//指定DSL,检索条件
//通过SearchSourceBuilder sourceBuilder 封装的条件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//1.1构造检索条件 es控制台能操作的,代码也都是可以操作
// sourceBuilder.query();
// sourceBuilder.from();
// sourceBuilder.size();
// sourceBuilder.aggregation();
//又进行QueryBuilders的条件封装
sourceBuilder.query(QueryBuilders.matchQuery("address","mill"));
//1.2 按照年龄的值分布进行聚合
TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age").size(10);
sourceBuilder.aggregation(ageAgg);
//1.3 利用聚合 计算平均薪资
AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
sourceBuilder.aggregation(balanceAvg);
System.out.println(searchRequest.toString());
searchRequest.source(sourceBuilder);
//2.执行检索
SearchResponse searchResponse = client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
//3.分析结果
System.out.println(searchResponse.toString());
//正常我们拿到值,肯定是要存入bean中的,也可以先存在内存中,拿一个map存一存
//Map map = JSON.parseObject(searchResponse.toString(),Map.class);
}
//3.1获取所有查到的数据
//外面一层的hits,有很多信息,但保存的数据还再更里面一层hits
SearchHits hits = searchResponse.getHits();
//真正的数据hits,以keyvalue的形似
SearchHit[] searchHits = hits.getHits();
for(SearchHit hit : searchHits){
/**
* 各种除了数据外的信息
* _index:"bank"索引名
* "_type":"account"类型,现在类型取消了,统一为doc
* "_id":"345",
* "_score":5.4022得分,也就是符合程度
* "_source"这就是我们的数据了
*/
// hit.getIndex();hit.getType();hit.getId();
//拿到json形式,然后解析后可以封装到javaBean中
String sourceAsString = hit.getSourceAsString();
//Account accout = JSON.parseObject(String,Account.class);
//3.2获取这次检索到分析信息,就是聚合后的数据,统计分析过的数据
Aggregations aggregations = searchResponse.getAggregations();
//可以通过foreach每个进行访问,也可以通过聚合名进行具体访问
//我知道该聚合的类型为Terms,所以直接写了,这些的父类都是Aggregation
Terms ageAgg1 = aggregations.get("ageAgg");
for(Terms.Bucket bucket : ageAgg1.getBuckets()){
//拿到统计的信息
String keyAsString = bucket.getKeyAsString();
}
//这是平均值的聚合
Avg balanceAvg1 = aggregations.get("balanceAvg");
ES使用
对于大数据,多筛选情况下,用es比较好
存储该商城所需要的筛选条件
mappings(映射)是用来定义索引中字段的数据类型、分词方式及其他属性的核心配置。它相当于传统数据库中的"表结构定义"。
当然可以直接创建索引的时候同时创建字段和值,不用先mapping
es
PUT product{
"mappings":{
"properties":{"skuId":{"type":"long"},
"spuId":{"type":"keyword"},
//标题设置为了text,全文检索进行分词,为模糊查找
//类型为keyword时是不可以分词的,为精确查找
"skuTitle":{"type":"text",
//该字段指定了分词器
"analyzer":"ik_smart"},
"skuPrice":{"type":"keyword"},
//关于图片字段都是为了可以快速在内存查到显示在前段,不用进行聚合和检索,所以设置了false
"skuImg":{"type":"keyword",
"index":false,"doc_values":false},
"saleCount":{"type":"long"},
"hasStock":{"type":"boolean"},
"hotScore":{"type":"long"},
"brandId":{"type":"long"},
"catalogId":{"type":"long"},
"brandName":{"type":"keyword",
"index":false,
"doc_values":false},
"brandImg":{"type":"keyword",
"index":false,
"doc_values":false},
"catalogName":{"type":"keyword",
"index":false,
"doc_values":false},
//attrs是内部的数组,当数组为一个对象时,一定要设置type nested标注,但数组只是普通的值则不用设置nested,会自动扁平化存储
"attrs":{"type":"nested",
"properties":{"attrId":{"type":"long"},
"attrName":{"type":"keyword",
"index":false,
"doc_values":false},
"attrValue":{"type":"keyword"}}}}}}
实际操作
创建一个跟ES索引属性对应的Bean对象
spu信息的es内存保存(现在es创建好了索引,定义好了字段(setting和mapping),相当于在mysql创建好了表和字段)
java
@Slf4j
@Service
public class ProductSaveServiceImpl implements ProductSaveService {
@Autowired
RestHighLevelClient restHighLevelClient;
@Override
public boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException {
//把sku信息保存到es,可供快速检索
//1、给es中建立索引。 product 建立好映射关系
//2.给es中保存这些数据
//由于是list,最好不用index方法单独保存数据,采用bulk批量存储参数所需BulkRequest bulkRequest,RequestOptions options
BulkRequest bulkRequest = new BulkRequest();
for(SkuEsModel model : skuEsModels){
//1、构造保存请求
IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);
indexRequest.id(model.getSkuId().toString());
String s = JSON.toJSONString(model);
indexRequest.source(s, XContentType.JSON);
bulkRequest.add(indexRequest);
}
BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
//检查批量保存是否有出错
boolean b = bulk.hasFailures();
List<String> collect = Arrays.stream(bulk.getItems()).map(item -> {
return item.getId();
}).collect(Collectors.toList());
log.error("商品上架错误:{}",collect);
return b;
}
}
为什么需要预先创建索引?
索引相当于数据库的表:Elasticsearch 的索引(Index)类似于关系型数据库中的表,它定义了数据的存储结构(Mapping)和配置(Settings)。
如果索引不存在,默认会自动创建:
Elasticsearch 在写入数据时,如果目标索引不存在,默认会自动创建,并使用动态映射(Dynamic Mapping)推断字段类型。
但自动创建的索引可能不符合业务需求,比如某些字段应该设为 keyword 而不是 text,或者需要特殊的分词器(Analyzer)。
ES存储时,遇到一个类型转换的问题
我们想在返回类R中进行存储data然后返回,但R基础于hashMap,导致data类型都应该是keyvalue的map,但我们要的是list<所需的类>
java
public class R extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
//alibaba fastjson的转换方式 复杂类型转换时用TypeReference
public <T>T getData(TypeReference<T> tTypeReference){
Object o = get("data");
String s = JSON.toJSONString(o);
T t = JSON.parseObject(s, tTypeReference);
return t;
}
public R setData(Object data){
put("data",data);
return this;
}
应用时
java
//TypeReference是抽象类,则需要以匿名内部类的形式进行类的构建
TypeReference<List<SkuHasStockVo>> listTypeReference = new TypeReference<List<SkuHasStockVo>>(){};
//getdata传入该类型
stockMap = skusHasStock.getData(listTypeReference).stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, item -> item.getHasStock()));
NGINX配置
分布式缓存
异步和线程池
线程池

创建线程的service继承于Executor,注意有的方法参数就是Executor,这也就是叫我们指定传线程池
正常使用一般都是使用线程池,省去了创建和销毁线程的时间

创建线程池的7个参数
常见四种线程池

异步编排 CompletableFuture
有时候在面对多线程并行时,有的线程需要其他线程拿到得值作为参数才能继续进行,所以这里我们将用CompletableFuture,实现于Future。
Future类主要是可以有指定得返回值,和进行线程先后顺序执行的编排
CompletableFuture(基础使用)

当lambda表达式,一个有返回值一个没有返回值
java
public class ThreadTest {
public static ExecutorService executor = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("main...start...");
//无返回类型
CompletableFuture.runAsync(()->{
System.out.println("当前线程:"+Thread.currentThread().getId());
},executor);
//有返回类型
CompletableFuture<Long> future = CompletableFuture.supplyAsync(()->{
System.out.println("当前线程:"+Thread.currentThread().getId());
return Thread.currentThread().getId();
},executor);
Long aLong = future.get();
}
}
基础使用的延申(计算完成时回调方法)

当得到结果后,可以进行链式编程直接再对结果进行操作,同样有异步和同步的两种选择,并且也可以对异常后的返回可以进行指定
java
//有返回类型
CompletableFuture<Long> future = CompletableFuture.supplyAsync(()->{
System.out.println("当前线程:"+Thread.currentThread().getId());
return Thread.currentThread().getId();
},executor).whenComplete((res,excption)->{
System.out.println("异步任务成功完成,结果是:"+res+",异常是:"+excption);
}).exceptionally(throwable -> {
//指定异常后,我们可以进行的值返回
return 10L;
});
handle方法(和complete一样只是可以直接指定返回值)

线程串行化 链式执行?

thenRun 拿不到上一步的执行结果
java
CompletableFuture<Void> future = CompletableFuture.supplyAsync(()->{
System.out.println("当前线程:"+Thread.currentThread().getId());
return Thread.currentThread().getId();
},executor).thenRunAsync(()->{
System.out.println("任务开始执行");
},executor);
thenAccept 可以拿到上一步的结果进行处理,但无返回值
java
CompletableFuture<Void> future = CompletableFuture.supplyAsync(()->{
System.out.println("当前线程:"+Thread.currentThread().getId());
return Thread.currentThread().getId();
},executor).thenAccept ((res)->{
System.out.println("任务开始执行"+res);
},executor);
thenApplyAsync 可以拿到上一步的结果,且自己也可以进行返回值,这是要注意给future的泛型不要设置为空了
java
CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{
System.out.println("当前线程:"+Thread.currentThread().getId());
return Thread.currentThread().getId();
},executor).thenApplyAsync ((res)->{
System.out.println("任务开始执行"+res);
return "成功了"+res;
},executor);
String a = future.get();
两任务组合(同时完成后,进行第三个任务的触发)

runAfterBothAsync 执行两个任务 拿不到上次执行的结果也没有返回值
java
CompletableFuture<Long> future1 = CompletableFuture.supplyAsync(()->{
System.out.println("当前线程:"+Thread.currentThread().getId());
System.out.println("任务1开始");
System.out.println("任务1结束");
return Thread.currentThread().getId();
},executor);
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(()->{
System.out.println("任务2开始");
System.out.println("任务2结束");
return "hello";
},executor);
future1.runAfterBothAsync(future2,()->{
System.out.println("任务3开始..");
},executor);
thenAcceptBothAsync 可以拿到两个线程的返回值进行新的操作
java
CompletableFuture<Long> future1 = CompletableFuture.supplyAsync(()->{
System.out.println("当前线程:"+Thread.currentThread().getId());
System.out.println("任务1开始");
System.out.println("任务1结束");
return Thread.currentThread().getId();
},executor);
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(()->{
System.out.println("任务2开始");
System.out.println("任务2结束");
return "hello";
},executor);
future1.thenAcceptBothAsync (future2,(f1,f2)->{
System.out.println("任务3开始.."+f1,+f2);
},executor);
thenCombineAsync 可以拿到上次的返回值并且自己可以进行值返回
java
CompletableFuture<Long> future1 = CompletableFuture.supplyAsync(()->{
System.out.println("当前线程:"+Thread.currentThread().getId());
System.out.println("任务1开始");
System.out.println("任务1结束");
return Thread.currentThread().getId();
},executor);
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(()->{
System.out.println("任务2开始");
System.out.println("任务2结束");
return "hello";
},executor);
//有返回值了,就要用future进行接受
CompletableFuture<String> future3 = future1.thenCombineAsync (future2,(f1,f2)->{
System.out.println("任务3开始.."+f1,+f2);
return "返回了"+f1+f2;
},executor);