谷粒商城高级篇

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);
相关推荐
KIDULT°8 小时前
MySQL系列 (1):数据库原理
运维·数据库·mysql·云计算
wewowilling11 小时前
认识微服务
java·运维·微服务·springcloud
玖石书11 小时前
Ubuntu 24.04 UFW 全命令攻略:从入门到端口开放实战
linux·运维·ubuntu
weifont13 小时前
自动化抓取和上架商品:能不动手,就少动手
运维·前端·自动化
拂拉氏13 小时前
【知识讲解】 Linux之动静态库的了解
linux·运维·服务器·动静态库
Mikko714 小时前
Linux 重启后日志没了?进程被 OOM 杀了去哪看?logrotate 之后日志还往旧文件写?三台真机实测日志与性能排查
linux·运维·centos
光依旧14 小时前
发版就掉请求?Spring Boot优雅停机的四步
运维·spring boot·nginx·高可用·优雅停机·mcp server
yichengerp15 小时前
电子行业常用erp系统有哪些?推荐适合中小电子厂的易呈erp软件,操作简单,易落地
大数据·运维·人工智能·云计算·制造
天国梦15 小时前
国密门禁品牌怎么选?2026年最新3个避坑要点
运维·服务器·数据库·python
信可维16 小时前
自动化操作多平台账号:登录态、双会话与 User Data 的 3 个坑
运维·自动化