谷粒商城高级篇

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);
相关推荐
Continue_with几秒前
docker设置代理
运维·docker·容器
彭泽布衣30 分钟前
远程登录docker执行shell报错input is not a terminal问题
运维·docker·容器
霖0033 分钟前
FPGA通信设计十问
运维·人工智能·经验分享·vscode·fpga开发·编辑器
NUZGNAW44 分钟前
VMware安装Centos 7
linux·运维·centos
筑梦之路1 小时前
linux 系统找出磁盘IO占用元凶 —— 筑梦之路
linux·运维·服务器
ezreal_pan2 小时前
docker设置镜像加速
运维·docker·容器
杰哥技术分享3 小时前
Ubuntu 22.04安装SQL Server指南
linux·运维·ubuntu·sqlserver
遇见火星3 小时前
ubuntu18.04 升级Ubuntu 20.04
linux·运维·ubuntu·系统升级
Gene_20223 小时前
【TOOL】ubuntu升级cmake版本
linux·运维·ubuntu
宇钶宇夕3 小时前
S7-200 SMART CPU 密码清除全指南:从已知密码到忘记密码的解决方法
运维·服务器·数据库·程序人生·自动化