(黑马出品_07)SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式

(黑马出品_07)SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式

    • = = = = = = = = = = = = = = = 微服务技术分布式搜索 = = = = = = = = = = = = = = =
    • 今日目标
    • 1.数据聚合
      • 1.1.聚合的种类
      • 1.2.DSL实现聚合
      • 1.2.1.Bucket聚合语法
        • 1.2.2.聚合结果排序
        • 1.2.3.限定聚合范围
        • 1.2.4.Metric聚合语法
        • 1.2.5.小结
      • 1.3.RestAPI实现聚合
        • 1.3.1.API语法
        • 1.3.2.业务需求
        • 1.3.3.业务实现
    • 2.自动补全
      • 2.1.拼音分词器
      • 2.2.自定义分词器
      • 2.3.自动补全查询
      • 2.4.实现酒店搜索框自动补全
        • 2.4.1.修改酒店映射结构
        • 2.4.2.修改HotelDoc实体
        • 2.4.3.重新导入
        • 2.4.4.自动补全查询的JavaAPI
        • 2.4.5.实现搜索框自动补全
    • 3.数据同步
      • 3.1.思路分析
        • 3.1.1.同步调用
        • 3.1.2.异步通知
        • 3.1.3.监听binlog
        • 3.1.4.选择
      • 3.2.实现数据同步
        • 3.2.1.思路
        • 3.2.2.导入demo
      • 3.2.3.声明交换机、队列
          • 1)引入依赖
          • 2)声明队列交换机名称
          • 3)声明队列交换机
        • 3.2.4.发送MQ消息
        • 3.2.5.接收MQ消息
    • 4.集群
      • 4.1.搭建ES集群
        • 1.部署es集群
          • 1.1.创建es集群
          • 1.2.集群状态监控
          • 1.3.创建索引库
            • 1)利用kibana的DevTools创建索引库
            • 2)利用cerebro创建索引库
          • 1.4.查看分片效果
      • 4.2.集群脑裂问题
        • 4.2.1.集群职责划分
        • 4.2.2.脑裂问题
        • 4.2.3.小结
      • 4.3.集群分布式存储
        • 4.3.1.分片存储测
        • 4.3.2.分片存储原理
      • 4.4.集群分布式查询
      • 4.5.集群故障转移

在这里插入图片描述
在这里插入图片描述

[此文档是在心向阳光的天域的博客加了一些有助于自己的知识体系,也欢迎大家关注这个大佬的博客](https://blog.csdn.net/sinat_38316216/category_12263516.html)
[是这个视频](https://www.bilibili.com/video/BV1LQ4y127n4/?p=5&spm_id_from=pageDriver&vd_source=9beb0a2f0cec6f01c2433a881b54152c)

= = = = = = = = = = = = = = = 微服务技术分布式搜索 = = = = = = = = = = = = = = =

今日目标

在这里插入图片描述
在这里插入图片描述

1.数据聚合

聚合 可以让我们极其方便的实现对数据的统计、分析、运算。例如:

  • 什么品牌的手机最受欢迎?
  • 这些手机的平均价格、最高价格、最低价格?
  • 这些手机每月的销售情况如何?

实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现近实时搜索效果。

1.1.聚合的种类

聚合常见的有三类:

  • 桶(Bucket)聚合:用来对文档做分组

    • 在这里插入图片描述

    • TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组

    • Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组

  • 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等

    • Avg:求平均值
    • Max:求最大值
    • Min:求最小值
    • Stats:同时求max、min、avg、sum等
  • 管道(pipeline)聚合:其它聚合的结果为基础做聚合

注意:参加聚合的字段必须是keyword、日期、数值、布尔类型

1.2.DSL实现聚合

现在,我们要统计所有数据中的酒店品牌有几种,其实就是按照品牌对数据分组。此时可以根据酒店品牌的名称做聚合,也就是Bucket聚合。

1.2.1.Bucket聚合语法

语法如下:

GET /hotel/_search
{"size": 0,  // 设置size为0,结果中不包含文档,只包含聚合结果"aggs": { // 定义聚合"brandAgg": { //给聚合起个名字"terms": { // 聚合的类型,按照品牌值聚合,所以选择term"field": "brand", // 参与聚合的字段"size": 20 // 希望获取的聚合结果数量}}}
}

结果如图:
在这里插入图片描述

1.2.2.聚合结果排序

默认情况下,Bucket聚合会统计Bucket内的文档数量,记为_count,并且按照_count降序排序。
我们可以指定order属性,自定义聚合的排序方式:

GET /hotel/_search
{"size": 0, "aggs": {"brandAgg": {"terms": {"field": "brand","order": {"_count": "asc" // 按照_count升序排列},"size": 20}}}
}
1.2.3.限定聚合范围

默认情况下,Bucket聚合是对索引库的所有文档做聚合,但真实场景下,用户会输入搜索条件,因此聚合必须是对搜索结果聚合。那么聚合必须添加限定条件。

我们可以限定要聚合的文档范围,只要添加query条件即可:

GET /hotel/_search
{"query": {"range": {"price": {"lte": 200 // 只对200元以下的文档聚合}}}, "size": 0, "aggs": {"brandAgg": {"terms": {"field": "brand","size": 20}}}
}

这次,聚合得到的品牌明显变少了:
在这里插入图片描述

1.2.4.Metric聚合语法

我们对酒店按照品牌分组,形成了一个个桶。现在我们需要对桶内的酒店做运算,获取每个品牌的用户评分的min、max、avg等值。

这就要用到Metric聚合了,例如stat聚合:就可以获取min、max、avg等结果。

语法如下:

GET /hotel/_search
{"size": 0, "aggs": {"brandAgg": { "terms": { "field": "brand", "size": 20},"aggs": { // 是brands聚合的子聚合,也就是分组后对每组分别计算"score_stats": { // 聚合名称"stats": { // 聚合类型,这里stats可以计算min、max、avg等"field": "score" // 聚合字段,这里是score}}}}}
}

这次的score_stats聚合是在brandAgg的聚合内部嵌套的子聚合。因为我们需要在每个桶分别计算。

另外,我们还可以给聚合结果做个排序,例如按照每个桶的酒店平均分做排序:
在这里插入图片描述

1.2.5.小结

aggs代表聚合,与query同级,此时query的作用是?

  • 限定聚合的的文档范围

聚合必须的三要素:

  • 聚合名称

  • 聚合类型

  • 聚合字段

聚合可配置属性有:

  • size:指定聚合结果数量
  • order:指定聚合结果排序方式
  • field:指定聚合字段

1.3.RestAPI实现聚合

1.3.1.API语法

聚合条件与query条件同级别,因此需要使用request.source()来指定聚合条件。

聚合条件的语法:
在这里插入图片描述

聚合的结果也与查询结果不同,API也比较特殊。不过同样是JSON逐层解析:
在这里插入图片描述
最终代码
HotelSearchTest.java

 @Testpublic void testAggregation() throws IOException {// 1.准备requestSearchRequest searchRequest = new SearchRequest("hotel");// 2.准备DSLsearchRequest.source().size(0);searchRequest.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand").size(10).order(BucketOrder.aggregation("_count", true)));// 3.发出请求SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);// 4.给出结果//System.out.println(response);Aggregations aggregations = response.getAggregations();Terms brandTerms = aggregations.get("brandAgg");List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();for (Terms.Bucket bucket : buckets) {String brandName = bucket.getKeyAsString();System.out.println(brandName);}}

输出结果:
在这里插入图片描述

1.3.2.业务需求

需求:搜索页面的品牌、城市等信息不应该是在页面写死,而是通过聚合索引库中的酒店数据得来的:
在这里插入图片描述

分析:

目前,页面的城市列表、星级列表、品牌列表都是写死的,并不会随着搜索结果的变化而变化。但是用户搜索条件改变时,搜索结果会跟着变化。

例如:用户搜索“东方明珠”,那搜索的酒店肯定是在上海东方明珠附近,因此,城市只能是上海,此时城市列表中就不应该显示北京、深圳、杭州这些信息了。

也就是说,搜索结果中包含哪些城市,页面就应该列出哪些城市;搜索结果中包含哪些品牌,页面就应该列出哪些品牌。

如何得知搜索结果中包含哪些品牌?如何得知搜索结果中包含哪些城市?

使用聚合功能,利用Bucket聚合,对搜索结果中的文档基于品牌分组、基于城市分组,就能得知包含哪些品牌、哪些城市了。

因为是对搜索结果聚合,因此聚合是限定范围的聚合,也就是说聚合的限定条件跟搜索文档的条件一致。

查看浏览器可以发现,前端其实已经发出了这样的一个请求:
在这里插入图片描述

请求参数与搜索文档的参数完全一致

返回值类型就是页面要展示的最终结果:
在这里插入图片描述

结果是一个Map结构:

  • key是字符串,城市、星级、品牌、价格
  • value是集合,例如多个城市的名称
1.3.3.业务实现

cn.itcast.hotel.web包的HotelController中添加一个方法,遵循下面的要求:

  • 请求方式:POST
  • 请求路径:/hotel/filters
  • 请求参数:RequestParams,与搜索文档的参数一致
  • 返回值类型:Map<String, List<String>>

代码:

    @PostMapping("filters")public Map<String, List<String>> getFilters(@RequestBody RequestParams params){return hotelService.getFilters(params);}

这里调用了IHotelService中的getFilters方法,尚未实现。
cn.itcast.hotel.service.IHotelService中定义新方法:

Map<String, List<String>> filters(RequestParams params);

cn.itcast.hotel.service.impl.HotelService中实现该方法:

@Override
public Map<String, List<String>> filters(RequestParams params) {try {// 1.准备RequestSearchRequest request = new SearchRequest("hotel");// 2.准备DSL// 2.1.querybuildBasicQuery(params, request);// 2.2.设置sizerequest.source().size(0);// 2.3.聚合buildAggregation(request);// 3.发出请求SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.解析结果Map<String, List<String>> result = new HashMap<>();Aggregations aggregations = response.getAggregations();// 4.1.根据品牌名称,获取品牌结果List<String> brandList = getAggByName(aggregations, "brandAgg");result.put("brand", brandList);// 4.2.根据品牌名称,获取品牌结果List<String> cityList = getAggByName(aggregations, "cityAgg");result.put("city", cityList);// 4.3.根据品牌名称,获取品牌结果List<String> starList = getAggByName(aggregations, "starAgg");result.put("starName", starList);return result;} catch (IOException e) {throw new RuntimeException(e);}
}private void buildAggregation(SearchRequest request) {request.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand").size(100));request.source().aggregation(AggregationBuilders.terms("cityAgg").field("city").size(100));request.source().aggregation(AggregationBuilders.terms("starAgg").field("starName").size(100));
}private List<String> getAggByName(Aggregations aggregations, String aggName) {// 4.1.根据聚合名称获取聚合结果Terms brandTerms = aggregations.get(aggName);// 4.2.获取bucketsList<? extends Terms.Bucket> buckets = brandTerms.getBuckets();// 4.3.遍历List<String> brandList = new ArrayList<>();for (Terms.Bucket bucket : buckets) {// 4.4.获取keyString key = bucket.getKeyAsString();brandList.add(key);}return brandList;
}

查看结果:
在这里插入图片描述
在这里插入图片描述

2.自动补全

当用户在搜索框输入字符时,我们应该提示出与该字符有关的搜索项,如图:
在这里插入图片描述
这种根据用户输入的字母,提示完整词条的功能,就是自动补全了。
因为需要根据拼音字母来推断,因此要用到拼音分词功能。

2.1.拼音分词器

要实现根据字母做补全,就必须对文档按照拼音分词。在GitHub上恰好有elasticsearch的拼音分词插件。地址:拼音分词器插件
在这里插入图片描述
课前资料中也提供了拼音分词器的安装包:
在这里插入图片描述
安装方式与IK分词器一样,分三步:
​①解压
​②上传到虚拟机中,elasticsearch的plugin目录
目录位置:
/var/lib/docker/volumes/es-plugins/_data
在这里插入图片描述

​③重启elasticsearch

docker restart es

在这里插入图片描述

​④测试
详细安装步骤可以参考IK分词器的安装过程。
测试用法如下:

POST /_analyze
{"text": "如家酒店还不错","analyzer": "pinyin"
}

结果:
在这里插入图片描述
可以看到拼音分词器存在问题
1.只有拼音没有汉字,拼音应该是锦上添花,而不能只有拼音
2.拼音没有实现分词,而是全称

基于以上的问题我们需要自定义拼音分词器

2.2.自定义分词器

默认的拼音分词器会将每个汉字单独分为拼音,而我们希望的是每个词条形成一组拼音,需要对拼音分词器做个性化定制,形成自定义分词器。

elasticsearch中分词器(analyzer)的组成包含三部分:

  • character filters:在tokenizer之前对文本进行处理。例如删除字符、替换字符
  • tokenizer:将文本按照一定的规则切割成词条(term)。例如keyword,就是不分词;还有ik_smart
  • tokenizer filter:将tokenizer输出的词条做进一步处理。例如大小写转换、同义词处理、拼音处理等

文档分词时会依次由这三部分来处理文档:
在这里插入图片描述
声明自定义分词器的语法如下:

PUT /test
{"settings": {"analysis": {"analyzer": { // 自定义分词器"my_analyzer": {  // 分词器名称"tokenizer": "ik_max_word","filter": "py"}},"filter": { // 自定义tokenizer filter"py": { // 过滤器名称"type": "pinyin", // 过滤器类型,这里是pinyin"keep_full_pinyin": false,"keep_joined_full_pinyin": true,"keep_original": true,"limit_first_letter_length": 16,"remove_duplicated_term": true,"none_chinese_pinyin_tokenize": false}}}},"mappings": {"properties": {"name": {"type": "text","analyzer": "my_analyzer",}}}
}

测试:
在这里插入图片描述
另一种方式测试:

# 测试分词器
POST /test/_doc/1
{"id" : 1,"name":"狮子"
}POST /test/_doc/2
{"id" : 2,"name":"虱子"
}GET /test/_search
{"query": {"match": {"name": "shizi"}}
}

测试结果:
在这里插入图片描述
我们搜索一下汉字呢,结果把虱子搜出来了,这显然是不对的
在这里插入图片描述

总结:

如何使用拼音分词器?

  • ①下载pinyin分词器
  • ②解压并放到elasticsearch的plugin目录
  • ③重启即可

如何自定义分词器?

  • ①创建索引库时,在settings中配置,可以包含三部分

  • ②character filter

  • ③tokenizer

  • ④filter

拼音分词器注意事项?

  • 为了避免搜索到同音字,搜索时不要使用拼音分词器
    在这里插入图片描述
    解决方案:添加search_analyzer
PUT /test
{"settings": {"analysis": {"analyzer": { // 自定义分词器"my_analyzer": {  // 分词器名称"tokenizer": "ik_max_word","filter": "py"}},"filter": { // 自定义tokenizer filter"py": { // 过滤器名称"type": "pinyin", // 过滤器类型,这里是pinyin"keep_full_pinyin": false,"keep_joined_full_pinyin": true,"keep_original": true,"limit_first_letter_length": 16,"remove_duplicated_term": true,"none_chinese_pinyin_tokenize": false}}}},"mappings": {"properties": {"name": {"type": "text","analyzer": "my_analyzer","search_analyzer": "ik_smart"}}}
}

再此搜索
在这里插入图片描述

2.3.自动补全查询

elasticsearch提供了Completion Suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询的效率,对于文档中字段的类型有一些约束:

  • 参与补全查询的字段必须是completion类型。
  • 字段的内容一般是用来补全的多个词条形成的数组。

可以把之前测试的索引库删除掉

DELETE /test

比如,一个这样的索引库:

// 创建索引库
PUT test
{"mappings": {"properties": {"title":{"type": "completion"}}}
}

然后插入下面的数据:

// 示例数据
POST test/_doc
{"title": ["Sony", "WH-1000XM3"]
}
POST test/_doc
{"title": ["SK-II", "PITERA"]
}
POST test/_doc
{"title": ["Nintendo", "switch"]
}

查询的DSL语句如下:

// 自动补全查询
GET /test/_search
{"suggest": {"title_suggest": {"text": "s", // 关键字"completion": {"field": "title", // 补全查询的字段"skip_duplicates": true, // 跳过重复的"size": 10 // 获取前10条结果}}}
}

查询后显示:
在这里插入图片描述
自动补全对字段的要求:

  • 类型是completion类型
  • 字段值是多词条的数组

2.4.实现酒店搜索框自动补全

现在,我们的hotel索引库还没有设置拼音分词器,需要修改索引库中的配置。但是我们知道索引库是无法修改的,只能删除然后重新创建。
另外,我们需要添加一个字段,用来做自动补全,将brand、suggestion、city等都放进去,作为自动补全的提示。

因此,总结一下,我们需要做的事情包括:

  1. 修改hotel索引库结构,设置自定义拼音分词器
  2. 修改索引库的name、all字段,使用自定义分词器
  3. 索引库添加一个新字段suggestion,类型为completion类型,使用自定义的分词器
  4. 给HotelDoc类添加suggestion字段,内容包含brand、business
  5. 重新导入数据到hotel库
2.4.1.修改酒店映射结构

代码如下:
先删掉以前的索引库

DELETE /hotel
# 酒店数据索引库
PUT /hotel
{"settings": {"analysis": {"analyzer": {"text_anlyzer": {"tokenizer": "ik_max_word","filter": "py"},"completion_analyzer": {"tokenizer": "keyword","filter": "py"}},"filter": {"py": {"type": "pinyin","keep_full_pinyin": false,"keep_joined_full_pinyin": true,"keep_original": true,"limit_first_letter_length": 16,"remove_duplicated_term": true,"none_chinese_pinyin_tokenize": false}}}},"mappings": {"properties": {"id":{"type": "keyword"},"name":{"type": "text","analyzer": "text_anlyzer","search_analyzer": "ik_smart","copy_to": "all"},"address":{"type": "keyword","index": false},"price":{"type": "integer"},"score":{"type": "integer"},"brand":{"type": "keyword","copy_to": "all"},"city":{"type": "keyword"},"starName":{"type": "keyword"},"business":{"type": "keyword","copy_to": "all"},"location":{"type": "geo_point"},"pic":{"type": "keyword","index": false},"all":{"type": "text","analyzer": "text_anlyzer","search_analyzer": "ik_smart"},"suggestion":{"type": "completion","analyzer": "completion_analyzer"}}}
}
2.4.2.修改HotelDoc实体

HotelDoc中要添加一个字段,用来做自动补全,内容可以是酒店品牌、城市、商圈等信息。按照自动补全字段的要求,最好是这些字段的数组。

因此我们在HotelDoc中添加一个suggestion字段,类型为List<String>,然后将brand、city、business等信息放到里面。

代码如下:

package cn.itcast.hotel.pojo;import lombok.Data;
import lombok.NoArgsConstructor;import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;@Data
@NoArgsConstructor
public class HotelDoc {private Long id;private String name;private String address;private Integer price;private Integer score;private String brand;private String city;private String starName;private String business;private String location;private String pic;private Object distance;private Boolean isAD;private List<String> suggestion;public HotelDoc(Hotel hotel) {this.id = hotel.getId();this.name = hotel.getName();this.address = hotel.getAddress();this.price = hotel.getPrice();this.score = hotel.getScore();this.brand = hotel.getBrand();this.city = hotel.getCity();this.starName = hotel.getStarName();this.business = hotel.getBusiness();this.location = hotel.getLatitude() + ", " + hotel.getLongitude();this.pic = hotel.getPic();this.suggestion = Arrays.asList(this.brand, this.business);}
}
2.4.3.重新导入

通过之前的单元测试批处理导入
在这里插入图片描述

重新执行之前编写的导入数据功能,可以看到新的酒店数据中包含了suggestion:但是如果包含了2个商圈,会是顿号,那我们需要将顿号分割
在这里插入图片描述

修改实体类HotelDoc.java
增加对顿号的分割

package cn.itcast.hotel.pojo;import lombok.Data;
import lombok.NoArgsConstructor;import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;@Data
@NoArgsConstructor
public class HotelDoc {private Long id;private String name;private String address;private Integer price;private Integer score;private String brand;private String city;private String starName;private String business;private String location;private String pic;private Object distance;private Boolean isAD;private List<String> suggestion;public HotelDoc(Hotel hotel) {this.id = hotel.getId();this.name = hotel.getName();this.address = hotel.getAddress();this.price = hotel.getPrice();this.score = hotel.getScore();this.brand = hotel.getBrand();this.city = hotel.getCity();this.starName = hotel.getStarName();this.business = hotel.getBusiness();this.location = hotel.getLatitude() + ", " + hotel.getLongitude();this.pic = hotel.getPic();// 组装suggestionif(this.business.contains("/")){// business有多个值,需要切割String[] arr = this.business.split("、");// 添加元素this.suggestion = new ArrayList<>();this.suggestion.add(this.brand);this.suggestion.add(this.city);Collections.addAll(this.suggestion, arr);}else {this.suggestion = Arrays.asList(this.brand, this.business, this.city);}}
}

然后查看结果:
在这里插入图片描述
另外测试一下自动补全

# 测试提示查询
GET /hotel/_search
{"suggest": {"suggestions": {"text": "sd","completion": {"field": "suggestion","skip_duplicates" : true,"size" : 10}}}
}

查询结果:
都是SD开头的
在这里插入图片描述

2.4.4.自动补全查询的JavaAPI

之前我们学习了自动补全查询的DSL,而没有学习对应的JavaAPI,这里给出一个示例:
在这里插入图片描述
而自动补全的结果也比较特殊,解析的代码如下:
在这里插入图片描述
我们先编写一个测试类测试一下
修改HotelSearchTest.java

@Testpublic void testSuggestionsSearch() throws IOException {// 1.准备SearchRequestSearchRequest searchRequest = new SearchRequest("hotel");// 2.准备DSLsearchRequest.source().suggest(new SuggestBuilder().addSuggestion("suggestions",SuggestBuilders.completionSuggestion("suggestion").prefix("sd").skipDuplicates(true).size(10)));// 3.发送请求SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);// 4.解析结果Suggest suggest = response.getSuggest();CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");List<CompletionSuggestion.Entry.Option> options = suggestions.getOptions();List<String> list = new ArrayList<>(options.size());for (CompletionSuggestion.Entry.Option option : options) {String text = option.getText().toString();list.add(text);}System.out.println(list);}

查询结果:
在这里插入图片描述

2.4.5.实现搜索框自动补全

查看前端页面,可以发现当我们在输入框键入时,前端会发起ajax请求:
在这里插入图片描述
返回值是补全词条的集合,类型为List<String>

1)在cn.itcast.hotel.web包下的HotelController中添加新接口,接收新的请求:

@GetMapping("suggestion")
public List<String> getSuggestions(@RequestParam("key") String prefix) {return hotelService.getSuggestions(prefix);
}

2)在cn.itcast.hotel.service包下的IhotelService中添加方法:

List<String> getSuggestions(String prefix);

3)在cn.itcast.hotel.service.impl.HotelService中实现该方法:

@Override
public List<String> getSuggestions(String prefix) {try {// 1.准备RequestSearchRequest request = new SearchRequest("hotel");// 2.准备DSLrequest.source().suggest(new SuggestBuilder().addSuggestion("suggestions",SuggestBuilders.completionSuggestion("suggestion").prefix(prefix).skipDuplicates(true).size(10)));// 3.发起请求SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.解析结果Suggest suggest = response.getSuggest();// 4.1.根据补全查询名称,获取补全结果CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");// 4.2.获取optionsList<CompletionSuggestion.Entry.Option> options = suggestions.getOptions();// 4.3.遍历List<String> list = new ArrayList<>(options.size());for (CompletionSuggestion.Entry.Option option : options) {String text = option.getText().toString();list.add(text);}return list;} catch (IOException e) {throw new RuntimeException(e);}
}

查询结果:
在这里插入图片描述
在这里插入图片描述

3.数据同步

elasticsearch中的酒店数据来自于mysql数据库,因此mysql数据发生改变时,elasticsearch也必须跟着改变,这个就是elasticsearch与mysql之间的数据同步
在这里插入图片描述

3.1.思路分析

常见的数据同步方案有三种:

  • 同步调用
  • 异步通知
  • 监听binlog
3.1.1.同步调用

方案一:同步调用
在这里插入图片描述

基本步骤如下:

  • hotel-demo对外提供接口,用来修改elasticsearch中的数据
  • 酒店管理服务在完成数据库操作后,直接调用hotel-demo提供的接口,
3.1.2.异步通知

方案二:异步通知
在这里插入图片描述

流程如下:

  • hotel-admin对mysql数据库数据完成增、删、改后,发送MQ消息
  • hotel-demo监听MQ,接收到消息后完成elasticsearch数据修改
3.1.3.监听binlog

方案三:监听binlog
在这里插入图片描述
流程如下:

  • 给mysql开启binlog功能
  • mysql完成增、删、改操作都会记录在binlog中
  • hotel-demo基于canal监听binlog变化,实时更新elasticsearch中的内容
3.1.4.选择

方式一:同步调用

  • 优点:实现简单,粗暴
  • 缺点:业务耦合度高

方式二:异步通知

  • 优点:低耦合,实现难度一般
  • 缺点:依赖mq的可靠性

方式三:监听binlog

  • 优点:完全解除服务间耦合
  • 缺点:开启binlog增加数据库负担、实现复杂度高

3.2.实现数据同步

3.2.1.思路

利用课前资料提供的hotel-admin项目作为酒店管理的微服务。当酒店数据发生增、删、改时,要求对elasticsearch中数据也要完成相同操作。

步骤:

  • 导入课前资料提供的hotel-admin项目,启动并测试酒店数据的CRUD
  • 声明exchange、queue、RoutingKey
  • 在hotel-admin中的增、删、改业务中完成消息发送
  • 在hotel-demo中完成消息监听,并更新elasticsearch中数据
  • 启动并测试数据同步功能
3.2.2.导入demo

导入课前资料提供的hotel-admin项目:
在这里插入图片描述

运行后,访问 http://localhost:8099
在这里插入图片描述
其中包含了酒店的CRUD功能:
在这里插入图片描述

3.2.3.声明交换机、队列

MQ结构如图:
在这里插入图片描述
启动mq

docker start mq

在这里插入图片描述

1)引入依赖

在hotel-admin、hotel-demo中引入rabbitmq的依赖:

<!--amqp-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2)声明队列交换机名称

在hotel-admin和hotel-demo中的cn.itcast.hotel.constatnts包下新建一个类MqConstants

package cn.itcast.hotel.constatnts;public class MqConstants {/*** 交换机*/public final static String HOTEL_EXCHANGE = "hotel.topic";/*** 监听新增和修改的队列*/public final static String HOTEL_INSERT_QUEUE = "hotel.insert.queue";/*** 监听删除的队列*/public final static String HOTEL_DELETE_QUEUE = "hotel.delete.queue";/*** 新增或修改的RoutingKey*/public final static String HOTEL_INSERT_KEY = "hotel.insert";/*** 删除的RoutingKey*/public final static String HOTEL_DELETE_KEY = "hotel.delete";
}
3)声明队列交换机

在hotel-demo和hotel-admin中分别定义配置类,声明队列、交换机:

package cn.itcast.hotel.config;import cn.itcast.hotel.constants.MqConstants;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MqConfig {@Beanpublic TopicExchange topicExchange(){return new TopicExchange(MqConstants.HOTEL_EXCHANGE, true, false);}@Beanpublic Queue insertQueue(){return new Queue(MqConstants.HOTEL_INSERT_QUEUE, true);}@Beanpublic Queue deleteQueue(){return new Queue(MqConstants.HOTEL_DELETE_QUEUE, true);}@Beanpublic Binding insertQueueBinding(){return BindingBuilder.bind(insertQueue()).to(topicExchange()).with(MqConstants.HOTEL_INSERT_KEY);}@Beanpublic Binding deleteQueueBinding(){return BindingBuilder.bind(deleteQueue()).to(topicExchange()).with(MqConstants.HOTEL_DELETE_KEY);}
}
3.2.4.发送MQ消息

在hotel-admin中的增、删、改业务中分别发送MQ消息:
在这里插入图片描述
代码如下:

@PostMappingpublic void saveHotel(@RequestBody Hotel hotel) {hotelService.save(hotel);rabbitTemplate.convertAndSend(MqConstant.HOTEL_EXCHANGE, MqConstant.HOTEL_INSERT_KEY, hotel.getId());}@PutMapping()public void updateById(@RequestBody Hotel hotel) {if (hotel.getId() == null) {throw new InvalidParameterException("id不能为空");}hotelService.updateById(hotel);rabbitTemplate.convertAndSend(MqConstant.HOTEL_EXCHANGE, MqConstant.HOTEL_INSERT_KEY, hotel.getId());}@DeleteMapping("/{id}")public void deleteById(@PathVariable("id") Long id) {hotelService.removeById(id);rabbitTemplate.convertAndSend(MqConstant.HOTEL_EXCHANGE, MqConstant.HOTEL_DELETE_KEY, id);}
3.2.5.接收MQ消息

hotel-demo接收到MQ消息要做的事情包括:

  • 新增消息:根据传递的hotel的id查询hotel信息,然后新增一条数据到索引库
  • 删除消息:根据传递的hotel的id删除索引库中的一条数据

1)首先在hotel-demo的cn.itcast.hotel.service包下的IHotelService中新增新增、删除业务

void deleteById(Long id);
void insertById(Long id);

2)给hotel-demo中的cn.itcast.hotel.service.impl包下的HotelService中实现业务:

@Override
public void deleteById(Long id) {try {// 1.准备RequestDeleteRequest request = new DeleteRequest("hotel", id.toString());// 2.发送请求client.delete(request, RequestOptions.DEFAULT);} catch (IOException e) {throw new RuntimeException(e);}
}@Override
public void insertById(Long id) {try {// 0.根据id查询酒店数据Hotel hotel = getById(id);// 转换为文档类型HotelDoc hotelDoc = new HotelDoc(hotel);// 1.准备Request对象IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());// 2.准备Json文档request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);// 3.发送请求client.index(request, RequestOptions.DEFAULT);} catch (IOException e) {throw new RuntimeException(e);}
}

3)编写监听器

在hotel-demo中的cn.itcast.hotel.mq包新增一个类:

package cn.itcast.hotel.mq;import cn.itcast.hotel.constants.MqConstants;
import cn.itcast.hotel.service.IHotelService;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class HotelListener {@Autowiredprivate IHotelService hotelService;/*** 监听酒店新增或修改的业务* @param id 酒店id*/@RabbitListener(queues = MqConstants.HOTEL_INSERT_QUEUE)public void listenHotelInsertOrUpdate(Long id){hotelService.insertById(id);}/*** 监听酒店删除的业务* @param id 酒店id*/@RabbitListener(queues = MqConstants.HOTEL_DELETE_QUEUE)public void listenHotelDelete(Long id){hotelService.deleteById(id);}
}

先启动SpringBoot,查看mq的客户端,可以看到交换机
在这里插入图片描述

可以看到队列
在这里插入图片描述
绑定关系如下:
在这里插入图片描述
下面我们测试一下功能:
下载Vue.js的浏览器插件,点击拓展
在这里插入图片描述
添加新拓展

在这里插入图片描述
搜索Vue,下载Vue.js Devtools
在这里插入图片描述
先查看酒店id
在这里插入图片描述
然后我们去酒店管理中更改价格,更改为334

在这里插入图片描述
然后我们去MQ的管理页面看一下有没有发送消息,发现确实有1条消息
在这里插入图片描述

看一下酒店查询页面,确实修改成功了
在这里插入图片描述

测试一下删除,我们删除这个上海希尔顿酒店,先拷贝一下Vue的信息
在这里插入图片描述
然后去酒店管理中删除希尔顿酒店
在这里插入图片描述
删除后,我们查看MQ的消息界面,发现删除消息新增了1条
在这里插入图片描述
我们去酒店搜索,发现希尔顿的确是没了,(原本13条)
在这里插入图片描述
然后我们再新增回希尔顿,查看一下
在这里插入图片描述
拷贝刚才粘贴的value即可
在这里插入图片描述
新增成功
在这里插入图片描述

新增后我们查看MQ的管理,发现新增1条消息
在这里插入图片描述
最后查看酒店搜索,添加成功
在这里插入图片描述
在这里插入图片描述

4.集群

单机的elasticsearch做数据存储,必然面临两个问题:海量数据存储问题、单点故障问题。

  • 海量数据存储问题:将索引库从逻辑上拆分为N个分片(shard),存储到多个节点
  • 单点故障问题:将分片数据在不同节点备份(replica )

ES集群相关概念:

  • 集群(cluster):一组拥有共同的 cluster name 的 节点。

  • 节点(node) :集群中的一个 Elasticearch 实例

  • 分片(shard):索引可以被拆分为不同的部分进行存储,称为分片。在集群环境下,一个索引的不同分片可以拆分到不同的节点中

解决问题:数据量太大,单点存储量有限的问题。

在这里插入图片描述

此处,我们把数据分成3片:shard0、shard1、shard2

  • 主分片(Primary shard):相对于副本分片的定义。

  • 副本分片(Replica shard):每个主分片可以有一个或者多个副本,数据和主分片一样。

数据备份可以保证高可用,但是每个分片备份一份,所需要的节点数量就会翻一倍,成本实在是太高了!

为了在高可用和成本间寻求平衡,我们可以这样做:

  • 首先对数据分片,存储到不同节点
  • 然后对每个分片进行备份,放到对方节点,完成互相备份

这样可以大大减少所需要的服务节点数量,如图,我们以3分片,每个分片备份一份为例:
在这里插入图片描述

现在,每个分片都有1个备份,存储在3个节点:

  • node0:保存了分片0和1
  • node1:保存了分片0和2
  • node2:保存了分片1和2

4.1.搭建ES集群

参考课前资料的文档:
在这里插入图片描述
其中的第四章节:
在这里插入图片描述

1.部署es集群

我们会在单机上利用docker容器运行多个es实例来模拟es集群。不过生产环境推荐大家每一台服务节点仅部署一个es的实例。

部署es集群可以直接使用docker-compose来完成,但这要求你的Linux虚拟机至少有4G的内存空间

1.1.创建es集群

首先编写一个docker-compose文件,内容如下:

version: '2.2'
services:es01:image: elasticsearch:7.12.1container_name: es01environment:- node.name=es01- cluster.name=es-docker-cluster- discovery.seed_hosts=es02,es03- cluster.initial_master_nodes=es01,es02,es03- "ES_JAVA_OPTS=-Xms512m -Xmx512m"volumes:- data01:/usr/share/elasticsearch/dataports:- 9200:9200networks:- elastices02:image: elasticsearch:7.12.1container_name: es02environment:- node.name=es02- cluster.name=es-docker-cluster- discovery.seed_hosts=es01,es03- cluster.initial_master_nodes=es01,es02,es03- "ES_JAVA_OPTS=-Xms512m -Xmx512m"volumes:- data02:/usr/share/elasticsearch/dataports:- 9201:9200networks:- elastices03:image: elasticsearch:7.12.1container_name: es03environment:- node.name=es03- cluster.name=es-docker-cluster- discovery.seed_hosts=es01,es02- cluster.initial_master_nodes=es01,es02,es03- "ES_JAVA_OPTS=-Xms512m -Xmx512m"volumes:- data03:/usr/share/elasticsearch/datanetworks:- elasticports:- 9202:9200
volumes:data01:driver: localdata02:driver: localdata03:driver: localnetworks:elastic:driver: bridge

es运行需要修改一些linux系统权限,修改/etc/sysctl.conf文件

vi /etc/sysctl.conf

添加下面的内容:

vm.max_map_count=262144

在这里插入图片描述

然后执行命令,让配置生效:

sysctl -p

在这里插入图片描述

通过docker-compose启动集群:

docker-compose up -d

在这里插入图片描述
查看容器状态

docker ps

在这里插入图片描述

1.2.集群状态监控

kibana可以监控es集群,不过新版本需要依赖es的x-pack 功能,配置比较复杂。

这里推荐使用cerebro来监控es集群状态,官方网址:https://github.com/lmenezes/cerebro

课前资料已经提供了安装包:
在这里插入图片描述
解压即可使用,非常方便。
解压好的目录如下:
在这里插入图片描述
进入对应的bin目录:
在这里插入图片描述

双击其中的cerebro.bat文件即可启动服务。
在这里插入图片描述
访问http://localhost:9000 即可进入管理界面:
在这里插入图片描述

输入你的elasticsearch的任意节点的地址和端口,点击connect即可:
在这里插入图片描述

绿色的条,代表集群处于绿色(健康状态)。
在这里插入图片描述

1.3.创建索引库
1)利用kibana的DevTools创建索引库

在DevTools中输入指令:

PUT /itcast
{"settings": {"number_of_shards": 3, // 分片数量"number_of_replicas": 1 // 副本数量},"mappings": {"properties": {// mapping映射定义 ...}}
}
2)利用cerebro创建索引库

利用cerebro还可以创建索引库:
在这里插入图片描述

填写索引库信息:
在这里插入图片描述

点击右下角的create按钮:

在这里插入图片描述

1.4.查看分片效果

回到首页,即可查看索引库分片效果:

在这里插入图片描述

4.2.集群脑裂问题

4.2.1.集群职责划分

elasticsearch中集群节点有不同的职责划分:
在这里插入图片描述

默认情况下,集群中的任何一个节点都同时具备上述四种角色
但是真实的集群一定要将集群职责分离:

  • master节点:对CPU要求高,但是内存要求第
  • data节点:对CPU和内存要求都高
  • coordinating节点:对网络带宽、CPU要求高
    职责分离可以让我们根据不同节点的需求分配不同的硬件去部署。而且避免业务之间的互相干扰。

一个典型的es集群职责划分如图:
在这里插入图片描述

4.2.2.脑裂问题

脑裂是因为集群中的节点失联导致的。

例如一个集群中,主节点与其它节点失联:
在这里插入图片描述
此时,node2和node3认为node1宕机,就会重新选主:
在这里插入图片描述

当node3当选后,集群继续对外提供服务,node2和node3自成集群,node1自成集群,两个集群数据不同步,出现数据差异。

当网络恢复后,因为集群中有两个master节点,集群状态的不一致,出现脑裂的情况:
在这里插入图片描述

解决脑裂的方案是,要求选票超过 ( eligible节点数量 + 1 )/ 2 才能当选为主,因此eligible节点数量最好是奇数。对应配置项是discovery.zen.minimum_master_nodes,在es7.0以后,已经成为默认配置,因此一般不会发生脑裂问题

例如:3个节点形成的集群,选票必须超过 (3 + 1) / 2 ,也就是2票。node3得到node2和node3的选票,当选为主。node1只有自己1票,没有当选。集群中依然只有1个主节点,没有出现脑裂。

4.2.3.小结

master eligible节点的作用是什么?

  • 参与集群选主

  • 主节点可以管理集群状态、管理分片信息、处理创建和删除索引库的请求
    data节点的作用是什么?

  • 数据的CRUD

coordinator节点的作用是什么?

  • 路由请求到其它节点

  • 合并查询到的结果,返回给用户

4.3.集群分布式存储

当新增文档时,应该保存到不同分片,保证数据均衡,那么coordinating node如何确定数据该存储到哪个分片呢?

4.3.1.分片存储测

插入三条数据:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

测试可以看到,三条数据分别在不同分片:
在这里插入图片描述

结果:
在这里插入图片描述

4.3.2.分片存储原理

elasticsearch会通过hash算法来计算文档应该存储到哪个分片:
在这里插入图片描述

说明:

  • _routing默认是文档的id
  • 算法与分片数量有关,因此索引库一旦创建,分片数量不能修改!

新增文档的流程如下:
在这里插入图片描述

解读:

  • 1)新增一个id=1的文档
  • 2)对id做hash运算,假如得到的是2,则应该存储到shard-2
  • 3)shard-2的主分片在node3节点,将数据路由到node3
  • 4)保存文档
  • 5)同步给shard-2的副本replica-2,在node2节点
  • 6)返回结果给coordinating-node节点

4.4.集群分布式查询

elasticsearch的查询分成两个阶段:

  • scatter phase:分散阶段,coordinating node会把请求分发到每一个分片

  • gather phase:聚集阶段,coordinating node汇总data node的搜索结果,并处理为最终结果集返回给用户

在这里插入图片描述

4.5.集群故障转移

集群的master节点会监控集群中的节点状态,如果发现有节点宕机,会立即将宕机节点的分片数据迁移到其它节点,确保数据安全,这个叫做故障转移。

1)例如一个集群结构如图:
在这里插入图片描述
现在,node1是主节点,其它两个节点是从节点。

2)突然,node1发生了故障:
在这里插入图片描述
宕机后的第一件事,需要重新选主,例如选中了node2:
在这里插入图片描述
node2成为主节点后,会检测集群监控状态,发现:shard-1、shard-0没有副本节点。因此需要将node1上的数据迁移到node2、node3:
在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/738149.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

6.S081的Lab学习——Lab1: Xv6 and Unix utilities

文章目录 前言一、启动xv6(难度&#xff1a;Easy)解析&#xff1a; 二、sleep(难度&#xff1a;Easy)解析&#xff1a; 三、pingpong&#xff08;难度&#xff1a;Easy&#xff09;解析&#xff1a; 四、Primes(素数&#xff0c;难度&#xff1a;Moderate/Hard)解析&#xff1a…

node.js入门

一、cmd常用命令 windowsR 再输入cmd 打开命令提示符 (1)切换盘符 c: / d: (2)dir 查看全部内容 (3)cd 你需要打开的文件所处的大文件夹名字 (4)cd 大文件夹名再输入dir 查看该大文件里面的所有内容 (5)输出结果里 一个.表示当前目录&#xff0c;两个..表示上级目录 …

怎样在CSDN赚点零花钱

请教一下各位大佬&#xff0c;看到你们在CSDN很多都几万粉丝以上&#xff0c;能不能分享一下有什么涨粉的经验&#xff0c;还有怎样转化为额外收益……感谢各位提供宝贵的经验&#xff0c;谢谢……

文献阅读笔记:全卷积神经网络

文献阅读笔记&#xff1a;全卷积神经网络 摘要Abstract1. 全卷积神经网络1.1 文献摘要1.2 全卷积神经网络1.2.1 网络结构1.2.0 从分类器到密集 FCN1.2.2 上采样 Upsampling1.2.3 跳级结构1.2.4 FCN训练 1.3 实验1.4 总结 2. 代码实现 摘要 本周学习了全卷积神经网络&#xff0…

Acer宏碁非凡Swift SFG16-71工厂模式原厂Win11系统,预装OEM系统恢复开箱状态

宏基笔记本电脑SFG16-71原装出厂Windows11系统安装工厂包下载&#xff0c;带恢复重置功能 链接&#xff1a;https://pan.baidu.com/s/1JK02kBbwKG_cIBNlEOzrOw?pwdzdfm 提取码&#xff1a;zdfm 原装工厂包系统自带所有驱动、Office办公软件、出厂时自带主题壁纸图片、系统…

如何免费获取基于公网 IP 的 SSL 证书 (无需域名)

现在给网站安装SSL证书来实现网站的HTTPS安全访问已经成了大多数人的共识&#xff0c;但是有一些特殊情况&#xff1a;比如对于个别的应用IP地址不需要绑定域名&#xff0c;只是单纯用IP来访问网站&#xff0c;这种情况下&#xff0c;可以实现HTTPS访问吗&#xff1f; 先说答案…

vue-创建vue项目记录

安装node.js 先安装node.js的运行环境node.js的下载地址 安装后就可以使用npm命令 1、清除npm缓存&#xff1a;npm cache clean --force 2、禁用SSL&#xff1a;npm config set strict-ssl false 3、手动设置npm镜像源&#xff1a;npm config set registry https://registry.…

揭密无文件勒索病毒攻击,思考网络安全新威胁

前言 最近几年基于无文件攻击的网络犯罪活动越来越多&#xff0c;一些网络犯罪团伙开发了各种基于无文件攻击的恶意软件攻击套件&#xff0c;这些恶意软件攻击套件可用于勒索病毒、挖矿病毒、RAT远控、僵尸网络等恶意软件&#xff0c;在过去的几年时间里&#xff0c;无文件感染…

L1-8 静静的推荐(Python)

天梯赛结束后&#xff0c;某企业的人力资源部希望组委会能推荐一批优秀的学生&#xff0c;这个整理推荐名单的任务就由静静姐负责。企业接受推荐的流程是这样的&#xff1a; 只考虑得分不低于 175 分的学生&#xff1b;一共接受 K 批次的推荐名单&#xff1b;同一批推荐名单上…

Day35:安全开发-JavaEE应用原生反序列化重写方法链条分析触发类类加载

目录 Java-原生使用-序列化&反序列化 Java-安全问题-重写方法&触发方法 Java-安全问题-可控其他类重写方法 思维导图 Java知识点&#xff1a; 功能&#xff1a;数据库操作&#xff0c;文件操作&#xff0c;序列化数据&#xff0c;身份验证&#xff0c;框架开发&…

pandas plot函数:数据可视化的快捷通道

一般来说&#xff0c;我们先用pandas分析数据&#xff0c;然后用matplotlib之类的可视化库来显示分析结果。而pandas库中有一个强大的工具--plot函数&#xff0c;可以使数据可视化变得简单而高效。 1. plot 函数简介 plot函数是pandas中用于数据可视化的一个重要工具&#xff0…

凌鲨本地接口架构

本地API通过监听本地端口&#xff0c;提供http服务&#xff0c;让本地应用可以获取信息和操作凌鲨客户端。 本地API架构 #mermaid-svg-seodZa6VsI4Qc8Cj {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-seodZa6VsI4…

辽宁博学优晨教育:视频剪辑培训的安全正规之路

在当今数字化时代&#xff0c;视频剪辑已成为一项炙手可热的技能。为满足广大学习者的需求&#xff0c;辽宁博学优晨教育推出了一系列专业的视频剪辑培训课程。本文将重点介绍辽宁博学优晨教育的视频剪辑培训如何在保障学员安全和学习效果方面做出了卓越的努力。 一、正规资质&…

Linux操作系统-06-进程与服务管理

使用ps命令查看进程。包括过滤进程信息 使用systemctl命令管理和运行Linux服务 进程&#xff08;Process&#xff09;&#xff1a;操作系统正在运行的应用程序。任意一个进程&#xff0c;都会消耗CPU和内存资源&#xff0c; 服务&#xff08;Service&#xff09;&#xff1a…

Word中解决插入脚注导致的分页位置错误问题

先放一个截图&#xff1a; 上面的截图中&#xff0c;样式为标题3的段落“四、固执的念头”前插入了连续型分节符&#xff0c;并且该分节符的样式为正文&#xff0c;前后的正文段落中有脚注&#xff0c;结果在分页时&#xff0c;标题3段落“四、固执的念头”后的正文段落自动进入…

【JAVA】我和“JAVA“的细水长流,输入输出

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法|MySQL| ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-Ck59fYFNNdpuGmVT {font-family:"trebuchet ms",verdana,arial,sans-serif;f…

JVM-3

HotSpot虚拟机对象 我在网上看了很多相关的文章&#xff0c;发现在创建对象和对象的结构中内容都不太一样&#xff0c;一些关键字也很不同&#xff0c;于是我通过参考《深入理解Java虚拟机》这本书&#xff0c;自己总结了一篇。 1.对象的创建 当JVM收到一条创建对象的字节码…

全面的 DevSecOps 指南:有效保护 CI/CD 管道的关键注意事项

数字化转型时代带来了对更快、更高效、更安全的软件开发流程的需求。DevSecOps&#xff1a;一种将安全实践集成到 DevOps 流程中的理念&#xff0c;旨在将安全性嵌入到开发生命周期的每个阶段 - 从代码编写到生产中的应用程序部署。DevSecOps 的结合可以带来许多好处&#xff0…

交叉编译x264 zlib ffmpeg以及OpenCV等 以及解决交叉编译OpenCV时ffmpeg始终为NO的问题

文章目录 环境编译流程nasm编译x264编译zlib编译libJPEG编译libPNG编译libtiff编译 FFmpeg编译OpenCV编译问题1解决方案 问题2解决方案 总结 环境 系统&#xff1a;Ubutu 18.04交叉编译链&#xff1a;gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu 我的路径/opt/toolch…

Java零基础入门-Comparable vs Comparator(上)

哈喽&#xff0c;各位小伙伴们好&#xff0c;我是喵手。 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流学习&#xff0c;互相学习&#xff0c;才能成长的更快&#xff0c;对吧。 我是一名java开发&#xff0c;所以日常接触到最多…