前言
基礎篇全部代碼和資料已上傳到gitee,大家需要可自取: https://gitee.com/da-ji/spring-cloud-learning-notes
點個Star,后續更新高級篇和面試篇不迷路 ?_?
本筆記基于:
1、 尚硅谷 2020.3 SpringCloud(H版&alibaba)框架開發教程
2、 黑馬 2021.8 SpringCloud*+RabbitMQ+Docker+Redis+搜索+分布式
代碼和資料基于:
黑馬 2021.8 SpringCloud*+RabbitMQ+Docker+Redis+搜索+分布式
第一套教程是經典的尚硅谷陽哥的教程,好處是經過時間的沉淀,已經非常成熟,網上大神的筆記也多,只要是人類出現的問題網上一搜都有答案;非常適合自學,
第二套教程是黑馬程式員的2021年8月份最新版教程,截止到發稿時應該是全網最最新的教程,在計算機技術日新月異的今天,盡可能往前學最新的技術至少沒錯,而且該套教程有一個特點在于,將課程分為實用篇和高級篇:
- 實用篇基本上以微課堂的形式出現,平均視頻時長也就10分鐘左右,易于接受,涵蓋了80%開箱上手就能用的知識;
- 而高級篇就比較深入和復雜了,為應對企業的復雜作業設計,每個視頻長度都為一小時左右,同時也是面試常問的地方,
由于本人已經作業了,為了在作業中快速拿起來就能用,我選擇的學習路線是:先刷黑馬程式員的實用篇,以最少的時間快速掌握SpringCloud的相關知識,然后視情況而定深入學尚硅谷的教程或是黑馬程式員的高級篇,
最后,這兩篇教程雖然都非常好,但是都沒有推出面試篇(原始碼深入講解),如果大家經濟上允許,可以支持一波培訓機構內部課程;經濟不允許也可以自學,當然我也會在博客和Gitee中陸續更新一些更高深的技術,
為方便大家速查,后文中這種顏色的字體,代表知識點在對應模塊代碼中的位置
為方便大家速查,后文中這種顏色的字體,代表知識點在對應模塊代碼中的位置
為方便大家速查,后文中這種顏色的字體,代表知識點在對應模塊代碼中的位置
課程資料鏈接:https://pan.baidu.com/s/169SFtYEvel44hRJhmFTRTQ 提取碼:1234
課程資料鏈接:https://pan.baidu.com/s/169SFtYEvel44hRJhmFTRTQ 提取碼:1234
課程資料鏈接:https://pan.baidu.com/s/169SFtYEvel44hRJhmFTRTQ 提取碼:1234
目錄
- 前言
- 一、微服務技術堆疊導學
- 二、Dubbo&Zookeeper
- 三、微服務遠程呼叫Demo——RestTemplate基本使用
- 四、Eureka注冊中心
- 五、Ribbon負載均衡
- 六、Nacos注冊中心
- 6.1 安裝啟動
- 6.2 Nacos自定義負載均衡策略
- 6.3 Nacos實作配置熱更新
- 6.4 Nacos集群
- 七、Feign遠程呼叫
- 7.1 還原事故現場
- 7.2 Feign自定義配置
- 7.3 Feign性能優化
- 7.4 Feign最佳實踐
- 八、統一Gateway網關
- 8.1 概述
- 8.2 搭建網關服務
- 8.3 路由過濾
- 8.3.1 斷言工廠:對請求進行過濾
- 8.3.2 過濾器GatewayFilter:對請求和回應進行過濾
- 8.3.3 全域過濾器GlobalFilter:可以自定義過濾邏輯代碼實作
- 8.3.4 過濾器鏈執行順序
- 8.4 網關跨域問題處理
- 九、Docker
- 9.1 Docker概念
- 9.2 Docker常用命令
- 十、MQ(Message Queue)訊息佇列
- 10.1 概述
- 10.2 RabbitMQ安裝
- 10.3 常見訊息模型
- 10.3.1 簡單佇列模型
- 10.4 Spring AMQP
- 10.3.2 WorkQueue模型
- 10.3.3 發布-訂閱模型
- A. Fanout Exchange
- B. Direct Exchange
- C. Topic Exchange
- 10.3.4 訊息轉換器
- 十一、ElasticSearch分布式搜索
- 11.1 ES基礎概念
- 11.2 安裝部署ES
- [Debug] 停止ES容器(或是重啟Linux)后,如何恢復Docker網路:
- 11.3 索引庫操作
- 11.4 檔案操作
- 11.5 RestClient操作索引庫和檔案
- 11.6 DSL查詢語法
- 11.7 Java RestClient查詢語法
- 11.8 ES綜合案例:黑馬旅游
- 11.9 ES資料聚合
- 11.10 ES資料補全
- 11.11 ES與MySQL之間資料同步(面試常問)
- 11.12 搭建高可用ES集群
- 后記
一、微服務技術堆疊導學
從單體架構過度到微服務架構,需要一系列中間技術支撐,其中重要的部分包括:
- 注冊中心:Eureka 、Zookeeper、Nacos
- 服務網關:Zuul 、Gateway
- 微服務遠程呼叫:RestTemplate、Feign
- 容器化技術 Docker
- 訊息佇列 MQ(多種實作方式)
- 負載均衡 Ribbon 、 Nginx
- 分布式搜索技術:ElasticSearch
尚硅谷陽哥的SpringCloud版本選型:

黑馬程式員的SpringCloud版本選型:

可以看到,黑馬的版本明顯較新,本文采用黑馬程式員的版本(Hoxton.SR10 + SpringBoot 2.3.x)
二、Dubbo&Zookeeper
核心代碼位置:在模塊 dubbo+zookeeper 下
這部分是跟狂神說Java學習的(黑馬版直接跳過了這兩個技術),Zookeeper與Eureka 、Nacos一樣也是一種注冊中心,
三、微服務遠程呼叫Demo——RestTemplate基本使用
核心代碼位置:在模塊 01-cloud-demo 下的order-service 和 user-service
核心代碼如下圖:實作了跨服務遠程呼叫

總結:RestTemplate微服務呼叫方式
基于RestTemplate發起的http請求實作遠程呼叫
http請求做遠程呼叫是與語言無關的呼叫,只要知道對方
的ip、埠、介面路徑、請求引數即可,
四、Eureka注冊中心
核心代碼位置:在模塊 01-cloud-demo 下的eureka-server(注冊的是order-service 和 user-service)
Eureka的作用:
- 消費者該如何獲取服務提供者具體資訊?
- 服務提供者啟動時向eureka注冊自己的資訊
- eureka保存這些資訊
- 消費者根據服務名稱向eureka拉取提供者資訊
- 如果有多個服務提供者,消費者該如何選擇?
- 服務消費者利用負載均衡演算法,從服務串列中挑選一個
- 消費者如何感知服務提供者健康狀態?
- 服務提供者會每隔30秒向EurekaServer發送心跳請求,報告健康狀態
- eureka會更新記錄服務串列資訊,心跳不正常會被剔除
- 消費者就可以拉取到最新的資訊
注意點:
Eureka自己也是一個微服務,Eureka啟動時,要把自己也注冊進去,這是因為如果后續搭建Eureka集群時做資料交流:
server:
port: 10086 # 服務埠
spring:
application:
name: eurekaserver # eureka的服務名稱
eureka:
client:
service-url: # eureka的地址資訊
defaultZone: http://127.0.0.1:10086/eureka
上段代碼塊中,defaultZone,將自己也注冊進去了,效果如下圖:

五、Ribbon負載均衡
兩個疑問:
- 如果有多個服務提供者,服務呼叫者如何知道究竟呼叫哪個服務呢?
- 而且服務呼叫者為何不用寫死服務提供者的鏈接(ip和埠),只需要寫服務名稱即可?為什么我們只輸入了服務名稱就可以訪問了呢?
(String url = "http://userservice/user/" + order.getUserId(); //由于已經在Eureka里面配置了服務,這里只需要寫配置的服務名即可)
這都是Ribbon的負載均衡做到的,針對問題一,通過跟斷點得知,Ribbon是通過幾種不同的負載均衡演算法實作的這一個機制(比如輪詢演算法);針對問題二,Ribbon會根據服務名稱去Eureka注冊中心拉取服務,如下兩個圖所示:


Ribbon 負載均衡策略

RoundRobin —— 意為輪詢,作業系統也有類似的概念(CPU時間片輪轉)

可以使用如下代碼配置對某個服務的負載均衡策略(在 application.yml里配置)
userservice: # 給某個微服務配置負載均衡規則,這里是userservice服務為例
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 負載均衡規則
Ribbon開啟饑餓加載
Ribbon默認是采用懶加載,即第一次訪問時才會去創建LoadBalanceClient,請求時間會很長,
而饑餓加載則會在專案啟動時創建,降低第一次訪問的耗時,通過下面配置開啟饑餓加載:
ribbon:
eager-load:
enabled: true # 開啟饑餓加載
clients:
- userservice # 指定饑餓加載的服務名稱
- xxxxservice # 如果需要指定多個,需要這么寫
六、Nacos注冊中心
和前面的Eureka、Zookeeper是同型別技術
6.1 安裝啟動
下載地址:https://github.com/alibaba/nacos/releases
本文選用1.4.1版本
解壓完成后,cd到nacos的bin目錄下,然后輸入命令:
startup.cmd -m standalone
關閉的話,如果是linux系統,就運行shutdown.sh即可

出現如上圖所示界面,說明啟動成功,通過上圖也可知它的默認埠是8848(國人做的注冊中心果然不一樣 8848氪金手機~)
輸入地址http://127.0.0.1:8848/nacos 即可訪問主頁,用戶名和密碼都是nacos
核心代碼位置:在模塊 01-cloud-demo 下注冊了order-service 和 user-service,同時注釋掉了兩個模塊的Eureka代碼(包括pom.xml也注釋了,畢竟是同類技術)
注意,必須將之前的Eureka代碼和pom都注釋掉,而且把SpringCloud也注釋掉(因為已經用了SpringCloudAlibaba),否則有可能報:APPLICATION FAILED TO START這個錯誤
對比之前的Eureka,我們是在idea里面專門啟動了一個Eureka的工程,所以 Eureka不需要下載,就可以通過埠號訪問Eureka的注冊中心,而Nacos是 下載并運行的,所以不需要在idea啟動某個模塊,直接通過運行Nacos的startup.cmd即可通過埠號訪問Nacos的注冊中心,
6.2 Nacos自定義負載均衡策略
也是使用的Ribbon,下面一個例子將Nacos配置成同集群優先的負載均衡策略:
默認的ZoneAvoidanceRule并不能實作根據同集群優先來實作負載均衡,
Nacos中提供了一個NacosRule的實作,可以優先從同集群中挑選實體,
1)給order-service配置集群資訊
修改order-service的application.yml檔案,添加集群配置:
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ # 集群名稱
2)修改負載均衡規則
修改order-service的application.yml檔案,修改負載均衡規則:
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 負載均衡規則
配置完成之后,就可以實作同集群優先的 負載均衡了
6.3 Nacos實作配置熱更新
有兩種方式,都在代碼中配置了,具體位置在:
核心代碼位置:在模塊 01-cloud-demo 下 user-service,第一種方式是通過組態檔方式(PatternProperties.java);第二種方式是通過注解@Value("${yaml里定義的鍵值對}")的方式
-
熱更新注意點:
你在Nacos中配置的:
你在bootstrap.yaml里配置的:
這兩張圖應該是一致的,注意 -和.的區別!!! -
熱更新優先級
Nacos帶環境的配置 > Nacos不帶環境的配置 > 本地yaml檔案配置
很好理解,Nacos帶環境可以理解為專屬化配置(開發環境和生產環境)、肯定優先于Nacos不帶環境的全域配置;本地yaml檔案配置則肯定低于Nacos的配置,
6.4 Nacos集群
位置:在模塊 01-cloud-demo 下根目錄,有一個叫Nacos集群搭建.md的檔案
注意點:修改兩個組態檔:
- 修改cluster.conf

- 修改Nacos的application.properties(不是你的application.properties)


修改完成后保存即可,
- 使用Nginx對Nacos做反向代理
這里需要Nginx前置知識,可以看我以下這一篇文章:Nginx入門:通俗理解反向代理和負載均衡,簡單配置Nginx
如果你的Nacos配置集群死活報下圖的錯誤:

請檢查你的MySQL版本,需要在5.7及以上,而且在8.0以下(比較苛刻)
七、Feign遠程呼叫
核心代碼位置:如下圖所示:

order-service會引入上圖的feign-api,實作遠程呼叫
7.1 還原事故現場
由于上一章(第六章)做了Nacos集群,但是整個第七章是基于單體的注冊中心,所以要把集群恢復成單體,
- nacos不使用集群啟動,恢復你standalone環境,主要是修改組態檔的nacos埠
- 這樣做的目的是讓微服務注冊進注冊中心,你用nacos還原事故現場也行,用eureka還原事故現場也行,反正能還原即可,
- 打開你的資料庫服務
引入feign版本報錯bug問題解決:
我手工指定了一個版本,版本號是:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.9.RELEASE</version>
</dependency>
7.2 Feign自定義配置
分為組態檔方式和代碼方式,
- 組態檔方式:

- 代碼方式(新建一個配置類):

我們采用的是代碼方式,并全域生效(新建一個配置類)
7.3 Feign性能優化
底層的客戶端實作是:
- URLConnection:默認實作,不支持連接池
- Apache HttpClient: 支持連接池(常用)
- OKHttp:支持連接池
第一種方式是默認的,不支持連接池,所以這里的性能優化指的是:換成第二種方式或者第三種方式,
其中第二種方式 Apache HttpClient 常用于模擬postman的樣式,發送一個form-data樣式的post請求,也可在這個post請求里上傳檔案,我們也采用的是這種方式
性能優化步驟:
1、引入jar包:
<!--HttpClient依賴-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>10.1.0</version>
</dependency>
2、在組態檔yml中配置:
feign:
httpclient:
enabled: true # 支持HttpClient的開關
max-connections: 200 # 最大連接數
max-connections-per-route: 50 # 單個路徑的最大連接數
這里的改動都是在order-service模塊下
7.4 Feign最佳實踐
打成jar包方式:
java中的JAR包
1、在專案中添加單獨的jar包步驟:
寫好自己的maven專案后,執行clean package,即可得到一個jar包
2、在專案中引入單獨的jar包圖解:

上圖其實是在專案的根目錄創建了一個叫lib的檔案夾,里面存著自定義jar包,然后即可引入,
3、針對1和2的補充,有的時候沒必要非得打jar包,可以寫一個子模塊引入呀,如下圖所示:

這塊看不懂,可以自行搜索maven的jar包引入方式和順序
八、統一Gateway網關
8.1 概述
三大功能:
- 身份認證和權限校驗
- 服務路由、負載均衡
- 請求限流
在SpringCloud中網關技術包括兩種:gateway和zuul
其中Zuul是基于Servlet的實作,屬于阻塞式編程,而Gateway則是基于SPring5中提供的WebFlux,屬于回應式編程的實作,具備更好的性能,
8.2 搭建網關服務
核心代碼位置:如下圖

步驟:
- 新建模塊
- 撰寫組態檔yml:
- 注冊進nacos的配置
- 網關自身的埠號
- 網關路由配置
server:
port: 10010
spring:
application:
name: gateway
cloud:
nacos:
server-addr: 127.0.0.1:8848 # nacos地址
gateway:
routes:
- id: user-service # 路由標識,必須唯一
uri: lb://userservice # 路由的目標地址 lb就是負載均衡,后面跟著是服務名稱
predicates: # 路由斷言,判斷請求是否符合規則
- Path=/user/** # 路徑斷言,判斷路徑是否是以/user開頭,如果是則符合規則
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
除了上面這些,還可以配置路由過濾器,后面會講到,
配置完畢后,啟動你的網關服務和你的user-service和order-service服務,即可通過網關訪問到user-service和order-service
作業原理總結

8.3 路由過濾
8.3.1 斷言工廠:對請求進行過濾

如果你不會寫匹配運算式,可以去spring官網查:

如果你的請求不符合路由斷言,那你的請求就會被拒絕,回傳一個404. 我們可以通過配置路由斷言工廠的方式來過濾某些請求,
8.3.2 過濾器GatewayFilter:對請求和回應進行過濾
它和8.3.1講述的斷言工廠一樣,都配置在yaml里
- GatewayFilter 和 8.3.1講述的斷言工廠的區別:

- 與斷言工廠類似,spring也為我們提供了過濾器工廠:


GatewayFilter可以針對某一類路由標識單獨配置,也可以配置成全域配置(所有路由id都生效),具體可自行百度,但是過濾器鏈執行順序有變化,可以看8.8.4詳解
8.3.3 全域過濾器GlobalFilter:可以自定義過濾邏輯代碼實作


案例正確執行的效果圖:
不加引數被過濾器攔截:

加了引數,不被攔截,正確獲得回應!

8.3.4 過濾器鏈執行順序
原理:關鍵詞 配接器模式
順序:

8.4 網關跨域問題處理
域名不一致就是跨域:
- 域名不同 比如www.baidu.com 和 www.bilibili.com
- 域名相同,埠不同
跨域是一個前端的概念,瀏覽器禁止請求的發起者和服務端發生跨域ajax請求,該請求會被瀏覽器攔截,
解決方案:CORS
之所以之前的user-service呼叫order-service不存在跨域,是因為不是ajax請求,因為這是一個瀏覽器行為,只有ajax請求會被攔截
處理方法:
簡單配置即可:
spring:
cloud:
gateway:
globalcors: # 全域的跨域處理
add-to-simple-url-handler-mapping: true # 解決options請求被攔截問題
corsConfigurations:
'[/**]':
allowedOrigins: # 允許哪些網站的跨域請求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: # 允許的跨域ajax的請求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允許在請求中攜帶的頭資訊
allowCredentials: true # 是否允許攜帶cookie
maxAge: 360000 # 這次跨域檢測的有效期
如果想要演示,需要啟動一個前端工程模擬一個ajax請求,
九、Docker
Docker命令居多,可以看我下面兩張思維導圖,包含了概念理解和常用命令,
9.1 Docker概念

9.2 Docker常用命令

十、MQ(Message Queue)訊息佇列
10.1 概述
-
事件驅動架構的概念:
MQ是事件驅動架構的實作形式,MQ其實就是事件驅動架構的Broker, -
異步應用場景:
如果是傳統軟體行業:雖然不需要太高并發,但是涉及到和其它系統做對接,我方系統處理速度(50ms)遠快于對方系統處理速度(1-3s),為了兼顧用戶的體驗,加快單據處理速度,故引入MQ,
用戶只用點擊我方系統的按鈕,我方按鈕發送到MQ即可給用戶回傳處理成功資訊,背后交由對方系統做處理即可,至于處理失敗,補償機制就不是用戶體驗要考慮的事情了,這樣可以大大提升用戶體驗, -
異步通訊優缺點:
- 優點:
- 耦合度低
- 吞吐量提升
- 故障隔離
- 流量削峰
- 缺點:
- 依賴于MQ的可靠性,安全性,吞吐能力(因為加了一層MQ,當然高度依賴它)
- 業務復雜了,業務沒有明顯的流程線,不好追蹤管理
- 優點:
-
MQ常見技術介紹:

10.2 RabbitMQ安裝
如何安裝,見下圖檔案:RabbitMQ部署指南.md

執行MQ容器的命令和簡單說明:
docker run \
-e RABBITMQ_DEFAULT_USER=root \ #用戶名
-e RABBITMQ_DEFAULT_PASS=root \ # 密碼
--name mq \
--hostname mq1 \ # 主機名,將來做集群部署要用
-p 15672:15672 \ # 埠映射,映射RabbitMQ管理平臺埠
-p 5672:5672 \ # 埠映射,訊息通信埠
-d \ # 后臺運行
rabbitmq:3-management # 鏡像名稱
#號不被識別,下面提供一個沒有#的版本
docker run \
-e RABBITMQ_DEFAULT_USER=root \
-e RABBITMQ_DEFAULT_PASS=root \
--name mq \
--hostname mq1 \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:3-management
最后在瀏覽器地址欄輸入:你的埠號:15672

如果看到上圖頁面,就說明成功了!
虛擬主機,租戶隔離的概念,重要!!!
vitural host:虛擬主機,是對queue、exchange等資源的邏輯分組
10.3 常見訊息模型
10.3.1 簡單佇列模型
核心代碼位置:下圖所示

10.4 Spring AMQP
概述
AMQP(Advanced Message Queuing Protocol),是用于在應用程式之間傳遞業務資訊的開放標準,該協議與語言和平臺無關,更符合微服務中獨立性的要求
SpringAMQP就是Spring基于AMQP定義的一套API規范,
使用Spring AMQP實作簡單佇列模型步驟:
以生產者為例:
由于這玩意已被spring托管了,所以對比之前rabbitmq demo的方式,不需要在代碼里寫配置了,直接在spring的application.yml里寫組態檔即可.
配置如下:
# 1.1.設定連接引數,分別是:主機名、埠號、用戶名、密碼、vhost
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: root
password: root
virtual-host: /
然后撰寫測驗類,以及測驗代碼,位置如下圖所示:

消費者一側,和生產者類似,不再贅述,如下圖進行配置即可:

至于如何啟動消費者 一側?如下圖所示:

10.3.2 WorkQueue模型
之所以 10.3.2 放在 10.4章,因為demo模型的演示,今后就是以 Spring AMQP為例了
概述
其實就是一個佇列,系結了多個消費者,一條訊息只能由一個消費者進行消費,默認情況下,每個消費者是輪詢消費的,區別于下文的發布-訂閱模型(該模型允許將同一訊息發給多消費者)
案例:

10.3.3 發布-訂閱模型
概念
允許將同一個訊息發給多個消費者,
其實就是加了一層交換機而已,如下圖所示:

交換機型別有很多,下文逐一介紹,下圖表示了各交換機型別的繼承關系

最后,交換機只能做訊息的轉發而不是存盤,如果將來路由(交換機和訊息佇列queue的連接稱作路由)沒有成功,訊息會丟失
A. Fanout Exchange
位置如下圖,注意一定要放在consumer包下,因為是消費者消費行為:

生產者添加代碼位置如下圖:

佇列系結成功后,打開mq可視化頁面,會看到如下圖所示:

寫好代碼后,分別啟動生產方,消費方,即可看到除錯成功資訊輸出:

概念:
這種模型中生產者發送的訊息所有消費者都可以消費,
案例:

總結:workQueue模式和FanoutQueue模式區別:
P代表生產者,C代表消費者 X代表交換機,紅色部分代表訊息佇列
workQueue:

FanoutQUeue:

可以發現,FanoutQueue增加了一層交換機,可以多個佇列對應多個消費者,而且比起WorkQueue,FanoutQueue生產者是先發送到交換機; 而WorkQueue是直接發送到佇列
B. Direct Exchange
概念:DirectExchange 會將接收到的訊息根據規則路由到指定的queue,因此稱為路由模式,如下圖所示:
P代表生產者,C代表消費者 X代表交換機,紅色部分代表訊息佇列

- 每一個queue都會與Exchange設定一個BindingKey
- 將來發布者發布訊息時,會指定訊息的RoutingKey
- Exchange將訊息路由到BingingKey與RoutingKey一致的佇列
- 實際應用時,可以系結多個key,
- 如果所有queue和所有Exchange系結了一樣的key,那生產者所有符合key的訊息消費者都會消費,如果這樣做,那DirectExchange就相當于FanoutExchange了(Direct可以模擬Fanout的全部功能)
案例如圖:

消費者添加代碼位置如下圖:

發送佇列添加代碼位置如下圖:

這次的案例,我們用注解的方式宣告佇列和系結交換機,之前Fanout的Demo是手寫了個配置類, 直接在監聽佇列里面宣告如下圖注解即可:

上圖的@QueueBinding點進去:

上面的key是個陣列,可以寫多個key,
寫完代碼后啟動消費者的SpiringBoot主啟動類(報錯資訊不用管),然后進入rabbitMQ可視化控制臺,出現下圖則說明配置成功:

隨后運行發送佇列的Test代碼,打開消費者的控制臺,出現如下圖輸出,則說明案例測驗通過:

C. Topic Exchange
概念: 和上面的Direct Exchange及其相似:
(下圖來源于Java旅途 ,作者大堯)

案例:

發送佇列、消費者的添加代碼位置和上面的DirectExchange位置一致,就在DirectExchange代碼下面,
寫完代碼后啟動消費者的SpiringBoot主啟動類(報錯資訊不用管),然后進入rabbitMQ可視化控制臺,出現下圖則說明配置成功:

10.3.4 訊息轉換器
引入:
在之前的案例中,我們發送到佇列的都是String型別,但是實際上,我們可以往訊息佇列中扔進去任何型別,我們看下圖,convertAndSend這個方法,第三個引數也是Object,這說明可以發送任何型別給訊息佇列:

案例:
創建一個佇列,向該佇列扔一個任意物件(Object型別)
創建佇列位置、發送佇列的添加代碼位置如下圖
創建佇列位置:

發送:

寫完代碼后啟動發送的Test,去看RabbitMQ控制臺,發現我們發過來的物件在內部被序列化(ObjectOutPutStream)了,如下圖所示:

如果不知道什么是ObjectOutPutStream可自行百度:

上面說的ObjectOutPutStream這個序列化方式,缺點很多(性能差、長度太長、安全性有問題),我們可以在這里調優一下,推薦JSON的序列化方式,于是引出了這一節的正文:自定義訊息轉換器(覆寫了原有的Bean配置):

宣告配置位置如下圖

配置了訊息轉換器轉換成json,然后重復之前的步驟,使用發送者發送一條訊息到佇列,發送完成后打開RabbitMQ控制臺,出現如下圖所示:

該物件被成功序列為json格式了!!!!!
- 對剛才發送過來的json格式訊息進行接收,需要修改消費者一側的代碼,并不復雜,如下圖所示:

消費者配置、監聽訊息位置如下2圖:


總結
- 訊息序列化和反序列化使用MessageConverter實作
- SpringAMQP的訊息序列化默認底層是使用JDK的序列化
- 我們可以手動配置成其它的序列化方式(覆寫MessageConverter配置Bean),推薦json
- 發送方和接收方必須使用相同的MessageConverter
十一、ElasticSearch分布式搜索
11.1 ES基礎概念
ES概述:
ELK(Elastic Stack)是以Elastic為核心的技術堆疊,如下圖所示:

ElasticSearch底層是Lucene(側面說明了ES和Hadoop千絲萬縷的關系)
推薦下面一篇文章:深入淺出大資料(From Zhihu)https://zhuanlan.zhihu.com/p/54994736

這個Lucene使用java寫成的,其實就是個jar包,我們引入之后就可以使用這個Lucene的API,而ES就是基于Lucene的二次開發,對其API進行進一步封裝:

倒排索引基礎概念:
先了解傳統MySQL的正向索引:

倒排索引基本概念:

這個倒排索引其實和生活中字典相當像,你拿到一本字典的目錄,肯定不會傻到先找頁碼,你肯定是先大略看一眼目錄的關鍵字,然后找到關鍵字之后,去看關鍵字旁邊的頁碼,最后再根據頁碼翻到書對應的那一頁,
倒排索引其實就是上面的例子,
然而MySQL這種正向索引,就是基于檔案id創建索引,查詢詞條的時候必須先找到檔案,然后根據檔案內容判斷是否包含詞條,
倒排索引正式一點的說法就是:對檔案內容分詞,對詞條創建索引,并記錄詞條所在檔案的資訊,查詢時先根據詞條查詢檔案id,然后根據id找到該檔案,
檔案和詞條的概念:
每一條資料就是一個檔案,對檔案的內容分詞,得到的詞語就是詞條,
ES 和 MySQL 概念對比


11.2 安裝部署ES
見課前資料的:安裝elasticsearch.md
使用docker容器化部署,這里針對啟動容器命令決議一下:
-e “ES_JAVA_OPTS=-Xms512m -Xmx512m” 配置堆記憶體(JVM),因為ES底層是java實作的,所以要配置jvm記憶體大小,默認值是1T,對于輕量級服務器太大了,所以適當減少為512M(但是不能再弄少了,再少的話可能跟著視頻走,會出現記憶體不足的問題)
-e “discovery.type=single-node” 單點模式運行(區別于集群模式運行)
兩個-v引數:資料卷掛載,分別是資料保存目錄(data),和插件目錄(plugins)
–network es-net 將ES容器加入到剛剛創建的docker網路中
-p 9200:9200 和 -p 9300:9300 是暴露的埠,9200是用戶訪問的http協議埠,9300是ES容器節點互聯的埠
elasticsearch:7.12.1 鏡像名稱
docker run -d \
--name es \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "discovery.type=single-node" \
-v es-data:/usr/share/elasticsearch/data \
-v es-plugins:/usr/share/elasticsearch/plugins \
--privileged \
--network es-net \
-p 9200:9200 \
-p 9300:9300 \
elasticsearch:7.12.1
-
安裝部署kibana(資料可視化界面)
黑馬官方的kibana的tar包有問題,建議自己從docker hub拉下來鏡像,但是拉下來之前要注意 ES 和 kibana的版本對應關系:
找到對應版本后(我已經找好了),執行命令:docker pull kibana:7.12.1從官網拉下來,這個程序比較慢,慢慢等
-
什么是分詞器?為什么要安裝分詞器?


分詞器我們選擇IK分詞器(來源于github,專門適配了中文)

該分詞器的具體安裝也在檔案里有寫, -
分詞器總結

[Debug] 停止ES容器(或是重啟Linux)后,如何恢復Docker網路:

11.3 索引庫操作
先給出ES官方幫助檔案地址:
https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
索引庫相當于MySQL中的Table,具體操作有兩個:
- Mapping映射屬性
- 索引庫的CRUD
先介紹Mapping映射屬性:

- 創建索引庫

一個簡單的創建索引庫的陳述句:
# 創建索引庫
PUT /heima
{
"mappings": {
"properties": {
"info": {
"type": "text",
"analyzer": "ik_smart"
},
"email": {
"type": "keyword",
"index": false
},
"name": {
"type": "object",
"properties": {
"firstName": {
"type": "keyword"
},
"lastName": {
"type": "keyword"
}
}
}
}
}
}
- 查看、修改、洗掉索引庫
查看索引庫:GET /索引庫名
洗掉索引庫:DELETE /索引庫名
修改索引庫從設計上被禁止了,索引庫和mapping一旦創建無法修改,但是可以添加新的欄位 (該欄位必須是全新的欄位) ,
它們的語法如下:
# 查詢
GET /heima
# 修改(必須添加一個全新的欄位)
PUT /heima/_mapping
{
"properties":{
"age":{
"type": "integer"
}
}
}
# 洗掉
DELETE /heima
11.4 檔案操作
索引庫相當于資料庫的table,檔案就相當于資料庫的行,
- 添加檔案

# 插入一個檔案
POST /heima/_doc/1
{
"info": "黑馬程式員java講師",
"email": "112837@qq.com",
"name":{
"firstName":"云",
"lastName":"趙"
}
}
- 查看、洗掉檔案

# 查詢
GET /heima/_doc/1
# 洗掉
DELETE /heima/_doc/1
每次寫操作的時候,都會使得檔案的"_version"欄位+1
- 修改檔案方式1 全量修改
它會洗掉舊檔案,新增新檔案
語法:和新增的語法完全一致,只不過新增是POST,全量修改是PUT
示例:
# 插入一個檔案
PUT /heima/_doc/1
{
"info": "黑馬程式員java講師",
"email": "112837@qq.com",
"name":{
"firstName":"云",
"lastName":"趙"
}
}
如果id在索引庫里面不存在,并不會報錯,而是直接新增,如果索引庫存在該記錄,就會先刪掉該記錄,然后增加一個全新的,
- 修改檔案方式2 增量修改
只修改某記錄的指定欄位值
語法:
# 區域修改檔案欄位
# 第三行,必須跟一個doc
POST /heima/_update/1
{
"doc": {
"email":"lbwnb@qq.com"
}
}
檔案操作總結

11.5 RestClient操作索引庫和檔案
-
概念
ES官方為各種語言操作ES提供了客戶端API,用來操作ES,其實本質都是組裝ES陳述句,通過http請求發送給ES, 官方檔案地址:https://www.elastic.co/guide/en/elasticsearch/client/index.html

可以看到有很多語言的版本, -
案例和代碼位置

代碼位置(大量代碼寫在測驗類中),該案例需要匯入資料庫,資料庫執行腳本位置同代碼目錄:

- 撰寫DSL陳述句,創建索引庫(相當與MySQL中建表)
陳述句如下:
# 酒店的mapping
PUT /hotel
{
"mappings": {
"properties": {
"id":{
"type": "keyword"
},
"name":{
"type": "text"
, "analyzer": "ik_max_word",
"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": "ik_max_word"
}
}
}
}
有時候可能會疑惑,同樣的一個文本型欄位,有的用text,有的用keyword,到底怎么選擇呢?首先要了解索引和分詞的概念:
- 索引(參與搜索,排序篩選等操作)
- 分詞(把詞看作一個整體還是把詞用某種規則分開)
- 比如 : 上海,北京這種欄位,不需要分詞(這種欄位在一個整體才有意義,分詞就亂套了)
- “震驚!盧bw將于2022年復出” 這種就需要分詞搜索,既然要分詞了,肯定要選擇分詞器,
了解了上面的概念,再看一下下圖(圖來源于博客園——瘦風的南墻):

備注1:index如果設定成false,則既不參與索引也不參與分詞,
備注2:索引庫的id總是被要求成keyword(也就是String)型別,即使資料庫的主鍵id可能是int
欄位引數(用于聚合):copy to ;
地理位置特殊資料型別:geo_point
使用RestClient操作檔案(索引庫相當于資料庫的table,檔案就相當于資料庫的行,),全都寫在demo代碼中,還是那句話:Java的API本質都是組裝ES陳述句,通過http請求發送給ES,
11.6 DSL查詢語法
先給出幫助檔案,幫助檔案永遠是學東西最準確的方式:
https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
- 快速入門—簡單查詢:



全文檢索查詢例:
# match 和 multi_match
GET /hotel/_search
{
"query": {
"match": {
"address": "如家外灘"
}
}
}
GET /hotel/_search
{
"query": {
"multi_match": {
"query": "外灘如家",
"fields": ["brand","name","business"]
}
}
}

精確查詢例:
# 精確查詢(term查詢)
GET /hotel/_search
{
"query": {
"term": {
"city": {
"value": "上海"
}
}
}
}
# 精確查詢(范圍range)
GET /hotel/_search
{
"query": {
"range": {
"price": {
"gte": 100,
"lte": 300
}
}
}
}


地理查詢例:
# distance查詢
GET /hotel/_search
{
"query": {
"geo_distance":{
"distance": "5km",
"location": "31.21, 121.5"
}
}
}
- 快速入門—打分演算法:
打分演算法(重點):

對默認算分方式進行修改:

組合查詢-function score 對應的Java RestClient代碼:


上面例子的查詢陳述句:
GET /hotel/_search
{
"query": {
"function_score": {
"query": {
"match": {
"address": "外灘"
}
},
"functions": [
{
"filter": {
"term": {
"brand": "如家"
}
},"weight": 10
}
]
}
}
}
- 快速入門—復合查詢:
復合查詢可以將其它簡單查詢組合起來,實作更復雜的搜索邏輯,
Boolean Query

注意,算分條件越多,性能就會越差,所以能使用filter的就別使用must,能不算分就不算分
案例:搜索名字包含“如家”,價格不高于400,在坐標31.21,121.5周圍10km范圍內的酒店
參考答案:
GET /hotel/_search
{
"query": {
"bool": {
"must": [
{"match": {
"name": "如家"
}}
],
"must_not": [
{"range": {
"price": {
"gt": 400
}
}}
],
"filter": [
{
"geo_distance": {
"distance": "100km",
"location": {
"lat": 31.21,
"lon": 121.5
}
}
}
]
}
}
}
- 快速入門—搜索結果處理:
搜索結果的處理主要包括排序、分頁、高亮,默認ES是根據得分排序的,但是你如果指定了按某種欄位排序,就會按你指定的方法排序,
A.排序

案例:

查詢陳述句實作:
# sort排序
GET /hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"score": "desc"
},
{
"price": "asc"
}
]組合查詢-function score 對應的Java RestClient代碼:
}
案例2:

查詢陳述句實作2:
GET /hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"_geo_distance": {
"location": {
"lat": 31.03,
"lon": 121.61
},
"order": "asc"
, "unit": "km"
}
}
]
}
地理位置排序對應的java restclient代碼:

注意:一旦指定了某種排序之后,ES就會放棄打分,因為打分沒意義了:

B.分頁
ES默認情況只回傳10條資料,如果想回傳更多條資料,則需修改分頁引數,
分頁語法(有點像MySQL的limit):

示例:
GET /hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"price": "asc"
}
],
"from": 20
, "size": 5
}
分頁出現的問題:ES底層是倒排索引,不利于分頁,所以分頁查詢是一種邏輯上的分頁,比如現在要查從990開始,截取10條資料(990~1000這10條),對ES來講,是先查出來0~1000條資料,查出來之后邏輯分頁截取10條給你,這么做如果是單體,最多只是效率問題,但是如果是集群,就會壞事,如下圖所示:

針對只能查詢10000條結果的解決方案:

C.高亮

示例:
# 高亮查詢,默認情況下ES搜索欄位必須與高亮欄位一致
GET /hotel/_search
{
"query": {
"match": {
"name": "如家"
}
},"highlight": {
"fields": {
"name": {
}
}
}
}

總結:

11.7 Java RestClient查詢語法
要構建查詢條件,只要記住一個類:QueryBuilders,
要構建搜索DSL,只需記住一個API:SearchRequest的source()方法(支持鏈式編程)
核心代碼位置:

這里只有一個注意點:高亮結果的決議,比較麻煩,代碼要配合下圖理解:

11.8 ES綜合案例:黑馬旅游
代碼位置:就是11.7那個類,直接啟動SpringBoot主啟動類,然后訪問localhost:8089即可訪問到前端頁面
要實作的功能:
- 酒店搜索和分頁
- 酒店結果過濾
- 我周邊的酒店
- 酒店競價排名
視頻可能出現的bug:
bug1 : 如果前端顯示例外(搜索不生效),根據前端debug資訊,修改index.html的第417行代碼修改成如下圖所示:

bug2: 黑馬旅游網的酒店競價排名實作不了
由于在視頻里創建索引庫里并沒有創建isAD這個欄位,我們需要手動追加該欄位,在kibana控制臺執行如下代碼即可修復:
# 給索引庫新增一個叫isAD的欄位,型別是布爾型別
PUT /hotel/_mapping
{
"properties":{
"isAD":{
"type": "boolean"
}
}
}
# 給索引庫id為45845的記錄賦值,讓其isAD欄位為true(用于測驗廣告競價排名,該記錄會靠前)
POST /hotel/_update/45845
{
"doc": {
"isAD":true
}
}
GET hotel/_doc/45845
11.9 ES資料聚合
聚合,類似于MySQL的group by(對資料的統計分析和計算),聚合不能是text型別,不能分詞
聚合一共有幾十種,在官方檔案可以查到,但是主要分為三大類:

管道聚合 可以理解為linux的 |
1、Bucket聚合

查詢實體:

上圖圖例的結果是由count進行降序排列的,如果想讓其升序排列,只需如下代碼:
# 聚合功能
GET hotel/_search
{
"size": 0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand",
"size": 10,
"order": {
"_count": "asc" #結果按照count升序排列
}
}
}
}
}
限定聚合范圍:

2、Metrics聚合

示例:
# 嵌套聚合metric
GET hotel/_search
{
"size": 0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand",
"size": 10,
"order": {
"scoreAgg.avg": "asc" # 根據下面的子聚合結果的avg進行升序排序
}
},
"aggs": {
"scoreAgg": {
"stats": {
"field": "score"
}
}
}
}
}
}
使用Java Restclient實作上面幾種聚合方式,位置如下:

Java Restclient對應Json的圖例:

Java代碼對應結果決議的圖例:

3、聚合案例:

案例位置同上面的 ES綜合案例:黑馬旅游
11.10 ES資料補全
比如你在京東輸入 sj 這兩個字母,搜索框就會猜測出你想輸入手機,這個就是資料補全
安裝資料補全分詞器:
分詞器在課前資料里有

測驗你的分詞器是否生效:
POST _analyze
{
"text": ["盧本偉"],
"analyzer": "pinyin"
}
自定義配置分詞器:
概念:



將下圖位置的自定義配置分詞器的第一段粘貼至kibana控制臺,即可完成自定義配置:

Completion Suggester查詢實作自動補全:

Completion Suggester語法:
// 自動補全查詢
GET /test/_search
{
"suggest": {
"title_suggest": {
"text": "s", // 關鍵字
"completion": {
"field": "title", // 補全欄位
"skip_duplicates": true, // 跳過重復的
"size": 10 // 獲取前10條結果
}
}
}
}
總結:
自動補全對欄位的要求:
型別是completion型別;欄位值是多詞條的陣列,

案例:實作hotel索引庫的自動補全、拼音搜索功能:

找到下圖位置,復制粘貼進kibana控制臺并且執行(這一步是重建酒店資料索引庫,在此之前要刪掉原有的酒店資料索引庫):

注意事項:

在Java代碼中重新定義轉換物體的操作,定義一個新的欄位suggestion,并且在kibana控制臺進行測驗:

經過上面一番操作后,型別為completion型別的suggestion欄位就有了我們想要自動補全的例子,然后執行下面的查詢陳述句:

至此,自動補全、拼音搜索的demo已成功展示!
對上圖的DSL陳述句在Java RestAPI里面進行發送:


使用Java Restclient實作上面自動補全方式,位置如下:

案例效果:

11.11 ES與MySQL之間資料同步(面試常問)
概念
ES中的酒店資料來自于MySQL索引庫,因此mysql資料發生改變時,ES的值也會跟著改變,這個就是ES和MySQL的資料同步,
思考:在微服務中,操作MySQL的業務和操作ES的業務可能在不同的微服務上,這種情況應該怎么實作資料同步呢?
解決方案:



案例:利用MQ實作mysql與es的資料同步

思路:

資料同步案例后臺管理頁面代碼位置如下圖(資料庫就用之前的ES綜合案例:黑馬旅游):

資料同步案例前端顯示代碼就是之前的ES綜合案例:黑馬旅游,前后端的微服務是分離的,埠號也不同,
實際上,這個專案hotel-admin專案相當于生產者,負責發送資料庫增刪改訊息;hotel-demo(之前的黑馬旅游前端專案)相當于消費者,負責監聽訊息并更新ES中的資料,
這樣就實作了在微服務中,操作MySQL的業務和操作ES的業務在不同的微服務上的跨服務資料同步
用心跟著代碼走,這個案例是完全可以做完并實作視頻全部功能的,沒有一句廢話多余,
11.12 搭建高可用ES集群
概念

搭建ES集群
位置同之前的elasticsearch.md,找到該檔案第四節:部署ES集群
集群腦裂問題


腦裂問題:一個集群出現了2個主節點:


集群分布式存盤和分布式查詢



集群故障轉移

集群故障轉移總結:
- Master掛掉后,EligibleMaster選舉為新的主節點
- master節點監控分片,節點狀態,將故障節點的分片轉移到正常節點,確保資料安全,
后記
黑馬 SpringCloud 2021 基礎篇筆記和代碼已更新完畢,不得不說黑馬的這套課程的確是良心之作,而且官方居然還開源出來讓大家都可以學習,實在是難能可貴,
如果大家在學習基礎篇的同時有疑問,歡迎在評論區討論和留言,也可以關注我,日后我還會陸續更新完高級篇和面試篇,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/340593.html
標籤:其他
下一篇:個人簡介~小蘇打
