微服務子產品建構
一、建立父工程
建立Maven父工程用于依賴的版本控制管理
-
建立一個maven工程,隻保留pom檔案
需要做相關的修改:
- 修改Pom檔案
- 将父工程的‘packaging’設定為pom
- 添加依賴管理版本統一管理和依賴管理
<packaging>pom</packaging> <!--統一管理jar包版本--> <properties> <projetc.build.sourceEncoding>UTF-8</projetc.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <junit.version>4.12</junit.version> <log4j.version>1.2.17</log4j.version> <lonbok.version>1.16.18</lonbok.version> <mysql.version>5.1.47</mysql.version> <druid.version>1.1.16</druid.version> <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version> </properties> <!--子子產品繼承後,不用寫version--> <dependencyManagement> <dependencies> <!--spring boot 2.2.2--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.3.8.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!--spring cloud Hoxten--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR10</version> <type>pom</type> <scope>import</scope> </dependency> <!--spring cloud alibaba 2.1.0--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.1.0.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.spring.boot.version}</version> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <fork>true</fork> <addResources>true</addResources> </configuration> </plugin> </plugins> </build>
二、建立provider-payment子子產品
在父工程的基礎上建立新的Module,需要繼承父工程
1、修改POM檔案
修改建立的Module的pom檔案,添加相關的依賴(由于在父工程中已經進行相關的管理,是以隻需要加入groupId和artifactId即可)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2、修改yml配置檔案
server:
port: 8001 # 端口
spring:
application:
name: cloud-provider-payment # 微服務名稱
datasource: # 資料庫連結
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jabc:mysql://localhost:3306/dbcloud02?useUnicode=true&characterEncoding=utf-8&useSSL=false
data-username: root
data-password: root
mybatis: # 整合mybatis
mapper-locations: classpath:mapper/*.xml # 映射配置檔案位置
type-aliases-package: com.zjj.cloud02.pojo # 别名所在包
3、建立啟動類
@SpringBootApplication
public class PaymentMain {
public static void main(String[] args) {
SpringApplication.run(PaymentMain.class,args);
}
}
4、業務邏輯
- 建立資料庫
create table `payment`( id BIGINT(20) primary key auto_increment, `serial` VARCHAR(200) DEFAULT '' )ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
- 建立實體類、建立統一傳回的結果CommonResult類
- 建立Dao層:PaymentMapper、PaymentMapper.xml
- 建立service層,PaymentService及PaymentServiceImpl
- 建立Controller層,PaymentController
- 使用postman測試
三、建立consumer-payment子子產品
1.修改pom檔案
2.建立yml檔案:端口80
3.建立啟動類
4.業務邏輯
// controller
@RestController
@Slf4j
public class PaymentController {
@Autowired
private RestTemplate restTemplate; // 這裡需要建立一個配置類,注冊RestTemplate的Bean
static final String host = "http://localhost:8001";
@GetMapping("/consumer/save")
public CommonResult saveOrder(Payment payment){
return restTemplate.postForObject(host+"/payment/save",payment,CommonResult.class );
}
@GetMapping("/consumer/select/{id}")
public CommonResult selectById(@PathVariable(value = "id") long id){
return restTemplate.getForObject(host+"/payment/select/"+id, CommonResult.class);
}
}
// config
@Configuration
public class ApplicationConfig {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
四、抽取通用部分,建構Common通用子子產品
該子產品用于存放重複類或工具類
Eureka元件
一、創 建eureka子產品
1.修改pom,需要用到新的依賴,如下
<!--eureka導入-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
2.修改yml檔案
server:
port: 5000
spring:
application:
name: eureka
eureka:
instance:
hostname: localhost # eureka服務的IP
client:
registerWithEureka: false # 本身就是注冊中心,不需要注冊
fetchRegistry: false # 本身就是注冊中心,不需要擷取注冊資訊
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
3.建立啟動類
需要在主啟動類上添加
@EnableEurekaServer
注解,表名目前微服務是eureka服務注冊中心
二、注冊provider
1.添加依賴
<!--eureka-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2.修改yml
# 添加eurek相關配置
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:5000/eureka/ # 注冊中心位址
3.修改主啟動類
添加
@EnableEurekaClient
注解,表名目前微服務是eureka服務注冊中心,可省略
三、注冊Consumer
同上。
四、eureka注冊中心叢集
1.建立一個新的eureka注冊中心(同第一個注冊中心),端口:5001
2.修改yml配置檔案
由于是在本地叢集模拟,是以需要修改hosts檔案,配置如下
127.0.0.1 eureka5000.com
127.0.0.1 eureka5001.com
127.0.0.1 eureka5002.com
# 5000
server:
port: 5000
spring:
application:
name: eureka5000
eureka:
instance:
hostname: eureka5000.com # eureka1的位址名稱
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://eureka5001.com:5001/eureka/ # 在eureka5001注冊該注冊中心
# 5001
server:
port: 5001
spring:
application:
name: eureka5001
eureka:
instance:
hostname: eureka5001.com # eureka2的位址名稱
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://eureka5000.com:5000/eureka/ # 在eureka5000注冊該注冊中心
在使用eureka注冊中心進行叢集的時候,需要互相注冊
3.修改consumer、provider的注冊位置,需要同時指向兩個注冊中心
defaultZone: http://eureka5000.com:5000/eureka/,http://eureka5001.com:5001/eureka/
五、服務提供者叢集
1.建立一個新的provider,代碼級别的内容與8001端口的一緻,不需要修改
2.配置檔案隻需要改變端口即可,将端口改為8002(因為是叢集,是以微服務的名稱需要一緻,在eureka中可以看到如下)
六、開啟Consumer的負載均衡(輪詢)
(與nginx的負載均衡類似)
1.在consumer的配置檔案中注冊RestTemplate的方法上加上
@LoadBalancer
,開啟負載均衡
2.在Controller中,不再使用provider的IP+PORT,而是使用provider的微服務名稱(在eureka中的注冊名稱)
測試:測試結果可見,每次通路都會來回切換兩個provider的通路。
tips:服務資訊的顯示配置如下:
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka5001.com:5001/eureka/,http://eureka5002.com:5002/eureka/
instance:
instance-id: payment8001 # 設定該微服務在eureka中的名稱
prefer-ip-address: true # 顯示ip
七、Discovery的初步接觸
1.在provider的controller中注入DiscoveryClient
@Autowired
private DiscoveryClient discoveryClient;
2.編寫方法擷取相關資訊
@GetMapping(value = "/payment/get/discovery")
public Object discovery(){
// 擷取全部的執行個體
List<String> services = discoveryClient.getServices();
for (String s : services) {
log.info("======="+s);
}
// 擷取某個執行個體的相關資訊
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PROVIDER-PAYMENT");
for (ServiceInstance instance : instances) {
log.info(instance.getInstanceId() + "\t" + instance.getHost() + "\t" + instance.getPort() + "\t" + instance.getUri());
}
return discoveryClient;
}
3.在啟動類上添加
@EnableDiscoveryClient
注解
4.通路該url即可擷取相關資訊
eureka的自我保護機制:eureka在一段時間内沒有收到某個微服務的心跳,不會立刻将該微服務剔除。會保留錯誤的微服務,使eureka更加健壯。
相關配置:
# 用戶端
# eureka用戶端向服務端發送心跳的時間間隔
lease-renewal-interval-in-seconds: 1
# 伺服器收到最後一次心跳後,最長的等待時間,逾時将剔除
lease-expiration-duration-in-seconds: 2
# 伺服器端
server:
enable-self-preservation: false # 關閉自我保護機制
使用zookeeper
待
使用consul元件
待
Ribbon負載均衡工具
使用在用戶端
一、RestTemplate簡單使用
ForEntity與ForObject的差別:ForEntity傳回的是一個對象裡面包含相應狀态碼,響應頭,響應體等資訊;而ForObject傳回的是一個字元串,主要包響應體
// 使用postForObject,傳回json
@GetMapping("/consumer/save")
public CommonResult saveOrder(Payment payment){
return restTemplate.postForObject(Payment_URL+"/payment/save",payment,CommonResult.class );
}
// 使用postForEntity,ResponseEntity對象
@GetMapping("/consumer/save/postForEntity")
public CommonResult saveOrder1(Payment payment){
ResponseEntity<CommonResult> entity = restTemplate.postForEntity(Payment_URL + "/payment/save", payment, CommonResult.class);
if (entity.getStatusCode().is2xxSuccessful()){
return new CommonResult(200,"postForEntity success",entity.getBody());
}else{
return new CommonResult(444,"postForEntity false");
}
}
// 使用getForObject,傳回字元串
@GetMapping("/consumer/select/{id}")
public CommonResult selectById(@PathVariable(value = "id") long id){
return restTemplate.getForObject(Payment_URL+"/payment/select/"+id, CommonResult.class);
}
// 使用getForEntity,傳回entity對象
@GetMapping("/consumer/select/getForEntity/{id}")
public CommonResult selectById2(@PathVariable(value = "id") long id){
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(Payment_URL + "/payment/select/" + id, CommonResult.class);
if(entity.getStatusCode().is2xxSuccessful()){
return new CommonResult(200,"getForEntity success",entity.getBody());
}else{
return new CommonResult(444,"getForEntity false");
}
}
二、Ribbon自帶負載均衡算法的使用切換
Ribbon的規則都是實作于IRule接口,有以下的算法:
- RoundRobinRule:輪詢(預設)
- RandomRule:随機
- RetryRule:
- WeightedResponseTimeRule:按照響應速度選擇
- BestAvailableRule:
- AvailabilityFilteringRule:
- ZoneAvoidanceRule:
操作步驟:
- 建立一個新的包(
)注意:此包不能在@ConponentScan注解所在包及其子包下,否則會失效
- 在該包下建立一個配置類,注冊一個規則
@Configuration public class MyRule { @Bean public RandomRule getRule(){ return new RandomRule(); // 随機規則 } }
- 在主啟動類上添加注解
,表示使用配置方式@RibbonClient
輪詢機制的實作原理:
使用目前是第幾次通路(current)%服務數量(modulo),以得到的餘數為下标選擇對應的伺服器;
當伺服器重新開機後,将會從1開始計數。
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PROVIDER-PAYMENT");
instances.get(1);
instances.get(2);
instances.get(1);
instances.get(2);
......
OpenFeign元件
與Ribbon一樣,作用在用戶端。隻需要編寫接口,并添加注解
@FeignClient
即可使用。
Feign中帶有Ribbon,是以配置Feign後,會自動開啟負載均衡
一、簡單的使用
1.建立一個consumer的module
2.添加新的依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
3.配置yml檔案
server:
port: 82
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka5000.com:5000/eureka/,http://eureka5001.com:5001/eureka/
spring:
application:
name: FeignConsumer
4.建立啟動類
注意添加
@EnableFeignClient
注解,表示啟用Feign元件
5.建立接口
需要添加注解@FeignClient,參數為調用的微服務名稱
@Component
@FeignClient(value = "CLOUD-PROVIDER-PAYMENT")
public interface PaymentService {
// 抽象方法為服務提供方的service接口的抽象方法,
// 在這裡直接使用服務提供方的controller方法,但是底層依然是使用service接口的方法
@GetMapping("/payment/select/{id}")
public CommonResult selectPaymentById(@PathVariable(value = "id") long id);
}
二、Feign逾時
Feign預設等待服務端響應的時間為1s,逾時将會報錯
測試:在服務提供端,設定sleep : 3000,此時consumer調用服務端服務就會進入逾時狀态
設定逾時時間(使用ribbon設定):
# 修改配置檔案
ribbon:
ReadTimeout: 3000
ConnectTimeout: 3000
三、Feign的日志
通過配置Feign的日志,可以列印Feign的相關資訊。可以列印Feign的以下幾個level
- NONE
- BASIC
- HEADERS
- FULL
配置步驟:
1.建立配置類,注冊Feign的配置級别
@Configuration
public class LogConfig {
@Bean
Logger.Level getLevel(){
return Logger.Level.FULL;
}
}
2.yml配置答應日志的級别
logging:
level:
# 接口的全類名:級别
com.zjj.cloud01.service.PaymentService: debug
Hystrix斷路器
一、相關概念
在分布式中,一個客戶請求可能會有很多個依賴,當一個依賴出現故障時可能會導緻該使用者長時間等待和占用線程,故而可能引發資源浪費、甚至系統崩潰的後果。為了避免這種情況,服務熔斷機制就應運而生,當某個服務出現故障時,系統可以根據預備方案做出處理,避免出現系統破潰等問題。
Hystrix時處理分布式系統延遲和容錯的開源庫
Hystrix功能:
- 服務降級
- 服務熔斷
- 接近實時的監控
- …
相關概念:
-
服務降級(FallBack):當服務出現故障,給使用者回報(如提示系統繁忙,請稍後再試等)
觸發降級的原因:
- 程式異常
- 逾時
- 服務熔斷觸發降級
- 線程池導緻服務降級
- 服務熔斷(break):當伺服器達到最大通路量後,直接拒絕通路,并調用服務降級方法返,友好傳回
- 服務限流(flowlimit):高并發操作時,限制通路,如每秒鐘隻能通路n個
JMETER壓測:
環境:
- 兩個服務:1.直接傳回(模拟簡單請求,速度快)2.等待3秒傳回(模拟複雜請求,速度慢)
- 在正常情況下,二者互不幹擾
測試:使用jmeter壓測開啟200個線程和100次循環/每秒。
壓測可以直覺顯示:當大量通路複雜的服務時,伺服器(tSpringboot預設omcat)内部線程資源就會被全部使用,進而導緻簡單的請求也需要排隊等待線程的配置設定,是以簡單的服務響應時間也會變得很慢。這時,就需要使用到Hystrix的服務降級等機制。
二、服務降級
既可以放在消費者,也可放在提供者,一般放在消費者端
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-puMyLd9y-1619615404043)(C:\Users\18264\Desktop\學習文檔\springCloud\springCloud.assets\image-20210425184645608.png)]
1.服務提供端自身的服務降級:
如果服務端調用自身的方法出錯(逾時、系統錯誤等),就會服務降級,傳回可預期的結果
- 為方法添加注解
@HystrixCommand
-
建立降級處理方法
相關資訊可以在源碼HystrixCommandProperties類中檢視
@Override @HystrixCommand(fallbackMethod = "fallBackMethod",commandProperties = { // 設定逾時時間 @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "4000") }) public String Hystrix_ERROR(Long id) { int a = 1/0; // try { // Thread.sleep(3000); // } catch (InterruptedException e) { // e.printStackTrace(); // } return "【" + Thread.currentThread().getName() + "】 Hystrix_ERROR id:" + id; } public String fallBackMethod(Long id){ return "【" + Thread.currentThread().getName() + "】 系統錯誤或請求逾時 id:" + id; }
- 為主啟動類加上注解
@EnableCircuitBreaker
結果:傳回 “【” + Thread.currentThread().getName() + “】 系統錯誤或請求逾時 id:” + id;
2.在Consumer端進行服務降級
- 添加依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
- 修改yml
# 開啟Hystrix功能 feign: hystrix: enabled: true
- 主啟動類添加注解
@EnableHystrix
- 為需要處理的請求添加@HystrixCommand注解
結果:由于在服務端設定的逾時時間是4000ms,而用戶端設定的是2000ms,在業務邏輯中設定的sleep(3000),是以會直接傳回:@GetMapping("/consumer/Hystrix/error/{id}") @HystrixCommand(fallbackMethod = "consumerFallBackMethod",commandProperties = { // 設定逾時時間 @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "2000") }) public String hystrix_error(@PathVariable(value = "id") Long id){ return paymentService.Hystrix_ERROR(id); } public String consumerFallBackMethod(Long id){ return "consumer timeout ~~~~"; }
@DefaultProperties(defaultFallback = “global_Fallback_method”)注解的使用:
當為每一個方法綁定一個服務降級方法會導緻代碼膨脹,是以可以使用通用的降級方法。
在類上添加注解@DefaultProperties(),則對于該類下的每個加有@HystrixCommand注解的服務方法,都會使用預設(通用)的降級方法;而對于特殊的需要單獨定制的服務方法,隻需要在@HystrixCommand()的屬性中添加具體的方法名稱并建立該方法即可。
3.對Consumer的feign接口進行服務降級
由于,用戶端使用feign實作負載均很等操作,是以一定會使用标注@FeignClient注解的接口,是以隻需要實作該接口,即可作為服務降級方法。
如此設定,則不在用戶端的controller的方法上标注@HystrixCommand也可以在出錯或其他異常的情況下實作服務降級。
因為實作接口時,實作類中會重寫方法,是以接口中的請求方法與實作類中的服務降級方法一一對應,就能解決代碼混亂的情況
- 修改yml
# 開啟Hystrix功能 feign: hystrix: enabled: true
- 實作Feign的接口:注意添加@Componet注解才能被容器掃描到
@Component public class PaymentServiceImpl implements PaymentService{ @Override public String Hystrix_OK(Long id) { return "Hystrix_OK method error"; } @Override public String Hystrix_ERROR(Long id) { return "Hystrix_ERROR method error"; } }
-
注解添加屬性@FeignClient
三、服務熔斷
什麼是服務熔斷?
當服務出現故障的時候(服務不可用或響應時間太長),對服務進行降級,處于自我保護的目的,當失敗達到一定門檻值時,執行熔斷(類似于保險絲)。其後,對部分請求嘗試調用方法,進入半開狀态。當服務正常後,會自動恢複鍊路調用。
流程:服務降級>>>熔斷開啟>>>服務恢複
開啟服務熔斷同樣使用@HystrixCommand注解
測試:
背景:調用服務端的服務:通過id查詢相關資訊,如果id的值>=0則正常通路,如果id<0則抛出異常,請求失敗,服務降級
- 服務端service代碼
@HystrixCommand(fallbackMethod = "Hystrix_break_method",commandProperties = { // 開啟服務熔斷 @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), // 請求數量(門檻值) @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), // 視窗期(時間) @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 在視窗期内請求失敗百分比,當達到該值時,開啟熔斷 @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60") }) public String Hystrix_break(Long id){ if(id < 0 ){ throw new RuntimeException("id錯誤"); } String ID = IdUtil.simpleUUID(); return "【"+ Thread.currentThread().getName() +"】 ID:" + ID; } /*服務熔斷服務降級方法*/ public String Hystrix_break_method(Long id){ return "請求失敗,id錯誤:" + id; }
- 服務端Controller代碼
/*======服務熔斷測試=======*/ @GetMapping("/hystrix/break/{id}") public String Hystrix_break(@PathVariable(value = "id") Long id){ return paymentService.Hystrix_break(id); }
- 結果:當id為正常id時,會傳回正常的預期結果;當id為負數時,便會傳回服務降級提供的錯誤資訊傳回;當在10秒内的10次請求的失敗率達到60%時,出現服務熔斷,即:當發送正确請求時,也會傳回服務降級的結果,等待一段時間後,再次發送正确請求,傳回正确的結果(服務自動恢複)
注意:當在視窗期内請求次數沒有達到設定的請求次數門檻值,即使請求全部失敗,也不會開啟熔斷。預設值:10s内20次請請求,50%通過率
四、服務限流
使用alibaba的sentienl
五、HystrixDashboard
Hystrix的監控可視化,可以檢視請求的數量,請求成功與否,熔斷器是否打開
配置步驟:
監控程式配置:
- 建立一個新的Module,用于開啟監控程式
- pom新增依賴有:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency>
- yml配置
server: port: 9001 hystrix: dashboard: proxy-stream-allow-list: - 'localhost'
- 主啟動類添加注解
@EnableHystrixDashboard
被監控程式配置:
- 添加依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
- 修該yml
management: endpoints: web: exposure: include: 'hystrix.stream'
通路位址:http://localhost:8001/actuator/hystrix.stream (被監控程式ip+port/actuator/hystrix.stream)即可圖形化顯示
Gateway網關
為什麼用Gateway?與Zuul的差別?
Gatewway的三個核心概念:
- 路由
- 斷言
- 過濾
一、路由轉發
配置:
- 建立新的module用于啟動gateway
- 添加依賴
注:網關需要注冊進入eureka,是以需要添加eureka-client依賴;添加gateway依賴(該依賴由spring官方提供),在添加該依賴時,一定不能在添加web依賴(如spring-boot-starter-web等),否則會報錯<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
- 配置yml
對網關配置解釋:在以上配置中,包含兩個路由,兩個服務位址都是在同一個微服務項目中;當url與“/payment/select/**”比對時,轉發到該位址,否則不轉發;第二個路由也同理。server: port: 9500 spring: application: name: Payment-Gateway # 配置網關資訊 cloud: gateway: # 配置路由資訊 routes: - id: payment-route1 # 路由名稱 uri: http://localhost:8001 # 轉發的位址 predicates: - Path=/payment/select/** # 斷言,隻有當url與斷言的url配置(true)時才進行轉發 - id: payment-route2 #第二個路由 uri: http://localhost:8001 predicates: - Path=/payment/port/** # 需要将網關注冊進入注冊中心 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka5001.com:5001/eureka/,http://eureka5002.com:5002/eureka/ instance: instance-id: gateway prefer-ip-address: true
- 配置啟動類
添加兩個注解即可@SpringBootApplication @EnableEurekaClient
-
測試:
在配置完成後,依次啟動eureka,provider8001,gateway。
在注冊中心中可以看到注冊了兩個微服務(provider8001、gateway)。
調用服務方式一:http://localhost:8001/payment/select/2 可以調用其服務
調用服務方式二:http://localhost:9500/payment/select/2 也可以調用其服務
由此可見,網關配置實作,即通路網關位址,由網關對其進行解析和路由轉發,以達到保護微服務的目的。
路由配置方式二:代碼配置
建立一個配置類實作配置
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator getRoutes(RouteLocatorBuilder builder){
RouteLocatorBuilder.Builder routes = builder.routes();
routes.route("toute1", // 路由id
r -> r.path("/springboot") // 網關url
.uri("https://spring.io/projects/spring-boot")) // 轉發位址
.build();
routes.route("toute2",
r -> r.path("/springcloud")
.uri("https://spring.io/projects/spring-cloud"))
.build();
routes.route("toute3",
r -> r.path("/guoji")
.uri("https://news.baidu.com/guoji"))
.build();
return routes.build();
}
}
通路localhost:9500/guoji即可轉發到https://news.baidu.com/guoji(但是該網站的其他頁面點選無效,因為沒有對其進行配置);測試中,前兩個沒有成功顯示,隻顯示了一半(css等沒顯示),個人認為時https的原因。
二、動态路由
由于服務提供者(provider)一般都是叢集的,是以不能将uri寫死,這就需要動态路由使用微服務名稱來實作動态路由
修改yml配置檔案,主要變動如下:
- 開啟動态路由:discovery.locator.enabled=true
- 配置uri:uri: lb://CLOUD-PROVIDER-PAYMENT
server:
port: 9500
spring:
application:
name: Payment-Gateway
# 配置網關資訊
cloud:
gateway:
# 開啟動态路由
discovery:
locator:
enabled: true
# 配置路由資訊
routes:
- id: payment-route1 # 路由名稱
# uri: http://localhost:8001 # 轉發的位址
uri: lb://CLOUD-PROVIDER-PAYMENT # 比對後提供服務的路由位址(服務名稱)
predicates:
- Path=/payment/select/** # 斷言,隻有當url與斷言的url配置(true)時才進行轉發
- id: payment-route2 #第二個路由
# uri: http://localhost:8001
uri: lb://CLOUD-PROVIDER-PAYMENT
predicates:
- Path=/payment/port/**
# 需要将網關注冊進入注冊中心
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka5001.com:5001/eureka/,http://eureka5002.com:5002/eureka/
instance:
instance-id: gateway
prefer-ip-address: true
測試結果,通過網關通路:http://localhost:9500/payment/select/1 将會負載均衡,在兩台微服務(8001、8002)之間切換提供服務。
三、斷言規則
官網:https://docs.spring.io/spring-cloud-gateway/docs/3.0.2/reference/html/#gateway-request-predicates-factories
斷言規則:
-
# 表示請求的時間必須在該時間之後 - After=2017-01-20T17:42:47.789-07:00[America/Denver]
-
# 表示請求時間必須在該時間之前 - Before=2017-01-20T17:42:47.789-07:00[America/Denver]
-
#表示請求時間必須在改時間段之内 - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
-
# 表示請求必須帶有cookie,且以k,v鍵值對方式 - Cookie=chocolate, ch.p
-
# 表示請求頭要包含後面正規表達式中的資訊 - Header=X-Request-Id, \d+
-
# 主機 - Host=**.somehost.org,**.anotherhost.org
-
# 請求方法 - Method=GET,POST
-
# 請求路徑 - Path=/red/{segment},/blue/{segment}
-
# 請求參數 - Query=green
-
# 遠端主機位址指定 - RemoteAddr=192.168.1.1/24
個别規則的測試:
- 在指定時間之後才能進行通路。如下:在上海時間xxxxx之後才能通路
predicates: - Path=/payment/select/** # 斷言,隻有當url與斷言的url配置(true)時才進行轉發 - After=2021-04-26T12:32:06.165+08:00[Asia/Shanghai]
- 隻有在帶有cookie的請求才能通路
predicates: - Path=/payment/select/ - Cookie=username,zjj
測試:
使用dos指令行進行測試
C:\Users\18264>curl http://localhost:9500/payment/select/1 # 通路失敗
C:\Users\18264>curl http://localhost:9500/payment/select/1 --cookie "username=zjj" # 通路成功
四、Filter
對請求的過濾,符合條件的給予放行
GaateFilter
官網:https://docs.spring.io/spring-cloud-gateway/docs/3.0.2/reference/html/#gatewayfilter-factories
GlobalFilter
官網:https://docs.spring.io/spring-cloud-gateway/docs/3.0.2/reference/html/#global-filters
自定義GlobalFilter
配置一個自定義的過濾器
需求:設定一個過濾器,隻有當請求中包含有username的key時,且key不為空時,請求有效
隻需要建立一個過濾器類,實作接口 GlobalFilter, Ordered,并将其注入容器即可
@Component
@Slf4j
public class ParamKeyFilter implements GlobalFilter, Ordered {
// 該方法對請求進行過濾檢查
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("********進入ParamKeyFilter過濾器"+ ZonedDateTime.now());
// 擷取請求中的username的值
String username = exchange.getRequest().getQueryParams().getFirst("username");
// 如果username為空,則設定響應狀态碼為請求失敗
if(username == null){
log.info("非法請求,請求失敗");
exchange.getResponse().setRawStatusCode(HttpStatus.SC_NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
// 否則,放行
return chain.filter(exchange);
}
// 該方法表示該過濾器的處理級别(優先級)
@Override
public int getOrder() {
return 0;
}
}
Config配置
什麼是SpringCloud Config?
SpringCloud Config為微服務架構中的微服務提供集中化的外部配置支援,配置伺服器為各個不同微服務應用的所有環境提供了一個中心化的外部配置。
Config分為服務端和用戶端,服務端也稱為分布式配置中心,他是一個獨立的微服務應用,用來連接配接配置伺服器并為用戶端提供擷取配置資訊,加密、解密資訊等通路接口。用戶端則是通過指定的配置中心來管理引用資源,以及與業務相關的配置内容,并在啟動的時候從配置中心擷取和加載配置資訊。
一、ConfigServer
配置中心是一個微服務,是以需要建立新的module
git中的配置資訊為:
- 建立新的module:springCloud01-config-center
- 添加pom
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
- 配置yml
server: port: 2000 spring: application: name: config-center cloud: config: server: git: # git倉庫名稱 uri: https://gitee.com/wuraoo/springcloud01_config.git # 搜尋目錄 search-paths: springcloud01_config # 分支 label: master eureka: client: fetch-registry: true register-with-eureka: true service-url: defaultZone: http://eureka5001.com:5001/eureka/,http://eureka5002.com:5002/eureka/ instance: prefer-ip-address: true instance-id: config-center
- 配置主啟動類:需要添加開啟config注解
@EnableConfigServer
- 測試:通路http://localhost:2000/master/config-info.yml 位址,即可看到git倉庫中的配置檔案的内容,即配置成功
通路位址規範:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
取第三種解釋:配置中心位址(localhost:2000)/分支名稱/微服務名稱-環境名稱.yml
label分支名稱; application:微服務名稱; profile:環境名稱
二、ConfigClient
在springCloud中有兩種配置檔案:application.yml、bootstrap.yml。application.yml是微服務的自己的配置檔案,而bootstrap.yml屬于系統配置檔案,且bootstrap.yml的加載優先級大于application.yml;在springCloud開發中,這兩種配置檔案一起使用,bootstrap.yml用于由配置中心統一管理配置檔案。
通過配置實作:client擷取配置中心的統一管理配置(實際過程為:client-Config>>>serverConfig>>>gitConfig)
配置過程:
- 建立新的module,config-client2100
- 添加依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>
- 配置bootstrap.yml
server: port: 2100 spring: application: name: config-client2100 cloud: # spring cloud 配置 config: # 分支 label: master # 項目名稱 一般在git中的配置檔案命名格式為:{application}-{profile}.yml name: config # 環境版本 profile: info # 配置中心位址 uri: http://localhost:2000 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka5001.com:5001/eureka/,http://eureka5002.com:5002/eureka/
- 建立啟動類
- 建立controller
@RestController public class ConfigController { @Value("${config.info}") private String info; @GetMapping("/getConfig") public String getConfig(){ return info; } }
代碼邏輯為通路該微服務"/getConfig"的URL,将會得到服務中心(其實是git中的)的配置檔案資訊;
@Value代碼表示:由于使用bootstrap.yml,是以會優先加載其;又因為在bootstrap.yml中配置了使用配置中心的配置資訊,是以配置中心的配置資訊會加載到bootstrap.yml中,是以可使用@Value來直接後去配置資訊。
-
測試:
首先測試配置中心是否能夠通路到git中的配置檔案
在通過client2100通路:/getConfig位址是否能夠得到配置資訊。
三、Client動态重新整理配置
**問題:**擋在git上修改配置資訊後,配置中心(config-server)能夠立刻更新(不需要重新開機服務),而用戶端(config-client)不能更新配置資訊,隻能重新開機才能更新配置,如何解決?
- 修改pom,添加依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
- 修改bootstrap.yml
management: endpoints: web: exposure: include: "*"
- controller添加注解,
@RefreshScope
- git跟新配置資訊後,需要給client發送post請求,則client端便會更新配置
curl -X POST http://localhost:2100/actuator/refresh
完成如上操作即可實作不用重新開機client微服務也能夠更新配置
Bus消息總線
bus簡單了解就是拓撲結構中的總線結構,即廣播。
springCloudBus支援兩種消息代理:RabbitMQ和Kafka
準備:需要安裝Erlang和rabbitmq環境,安裝成功通路位址:localhost:15672跳轉到登陸界面即安裝成功
一、Bus廣播-全部配置更新
準備:建立一個module與client2100一樣的client2200
1.消息總線通知一個用戶端(Client),進而更新全部用戶端的配置
2.消息總線通知一個服務端(Server),進而更新全部用戶端的配置
推薦使用第二種通知總控:
- 保持了微服務的職責單一性,配置中心始終是管理配置的;而用戶端有其自己的功能,不需要額外添加功能
- 如果配置在client,則增加了該用戶端的壓力,如果該client當機,則全盤無法操作;同時破壞了用戶端叢集的平等性(子產品結構不一樣了)
配置:
-
ConfigServer2000:
pom配置,添加依賴
修改yml<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
#rabbitmq相關配置 這一段屬于spring下面的配置 spring: rabbitmq: host: localhost port: 5672 # 15672是web管理端口 5672是通路端口 username: guest password: guest # 暴露 bus重新整理配置的端點 management: endpoints: web: exposure: include: 'bus-refresh'
-
ConfigClient2100:
添加依賴
修改yml<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
rabbitmq: host: localhost port: 5672 username: guest password: guest
-
ConfigClient2200:
添加依賴
修改yml<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
rabbitmq: host: localhost port: 5672 username: guest password: guest
- 配置完成後,當運維人員修改git上的配置檔案時,配置中心會自動更新;用戶端隻需要對配置中心發送post請求:
即可是是實作一處廣播,處處更新curl -X POST http://localhost:2000/actuator/bus-refresh
注意:client的主啟動類上要添加注解``@RefreshScope`,才能開啟自動重新整理
二、定點更新
當某些情況下,需要對個别的用戶端不需要更新,則可以使用頂點廣播更新
使用指令:
http://配置中心位址:端口/actuator/bus-refresh/{application名稱:具體端口}
如:
curl -X POST http://localhost:2000/actuator/bus-refresh/config-client:2200
表示隻更新微服務名稱為config-client、端口号為2200的微服務配置。
Stream消息驅動
當一個架構中存在兩個MQ時,為了避免不同的消息隊列的不同和需要同時掌握兩種技術,則可以引入cloudStream。
CloudStream由Binder(綁定器對象)來實作與不同的MQ之間的互動,屏蔽了各種MQ之間的細節,隻關注她們之間的互動。CloudStream目前隻支援RabbitMQ和Kafka之間的轉換。
一、消息生産者建構
建立module
- 添加依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency>
- 修改yml
server: port: 8100 spring: application: name: stream-rabbit-provider cloud: stream: binders: # 配置要綁定的額rabbitmq的服務資訊 defaultRabbit: # 表示定義的名稱,用于binding的整合 type: rabbit # 消息元件類型 environment: # rabbitmq的環境 spring: rabbitmq: host: localhost port: 5672 username: guest password: guest bindings: # 服務的整合 output: # 通道名稱 destination: studyExchange # 要使用的Exchange的名稱 content-type: application/json # 消息類型 binder: defaultRabbit #要綁定消息服務的具體設定 eureka: client: service-url: defaultZone: http://eureka5001.com:5001/eureka/,http://eureka5002.com:5002/eureka/
- 建立主啟動類
- 建立controller
表示沒發送其次請求,向rabbitmq發送一個消息@RestController public class SendMessageController { @Autowired private MessageService messageService; @GetMapping("/sendMessage") public String sendMessage(){ return messageService.send(); } }
- 建立service
這裡需要用到注解@EnableBinding(Source.class) // 定義消息推送管道,source表示消息生産者 public class MessageServiceImpl implements MessageService { // 消息通道 @Autowired private MessageChannel output; @Override public String send() { String s = UUID.randomUUID().toString(); System.out.println("===========s"); // 使用MessageBuilder發送消息 output.send(MessageBuilder.withPayload(s).build()); return null; } }
@EnableBinding
- 完成配置後,啟動微服務,每次通路url,就可以在rabbitmq的管理頁面看到消息波峰流量
二、消息消費者建構
建立module
- 添加pom
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency>
- 修改yml
# 在yml中,出了端口、名稱等正常配置與消息生産者不同外,隻有下面的一點與生産者不同 input: # 将output通道變為input通道即可 destination: studyExchange # 要使用的Exchange的名稱(與生産者一緻)
- 建立主啟動類
- 建立controller監聽消息
@RestController @EnableBinding(Sink.class) // 定義消息管道,表示目前為接收消息管道 public class ReciveMessageController { @Value("${server.port}") private String port; // 消息監聽注解,固定配置 @StreamListener(Sink.INPUT) public void reciveMessage(Message<String> message){ // 需要傳入Message對象,用于擷取消息 System.out.println("port: "+ port + " 接收到消息:"+message.getPayload()); } }
- 測試:當對消息生産者發送請求後,生産者或發送消息至rabbitmq;然後消息消費者會監聽消息,當監聽到消息後,便在控制台列印消息。
三、分組
在SpringCloudStream中,處于同一個分組的消息消費者時競争關系,即隻有一個能獲得消息
1.重複消費
當消息消費者存在多個時,他們都訂閱了同一個消息,而此時他們處于不同的分組,那麼就會造成消息生産者發送一次消息,同時被多個消費者收到,就造成的重複消費的問題。
實際案例:在商城系統中,當8001發送一個訂單消息,同時被多個消費者收到,就會産生一個訂單産生的多次,就會造成重複扣款等情況。
自定義分組:
yml配置:
隻需要添加group配置即可
input: # 通道名稱
destination: studyExchange # 要使用的Exchange的名稱
content-type: application/json # 消息類型
binder: defaultRabbit #要綁定消息服務的具體設定
group: consumer1
input: # 通道名稱
destination: studyExchange # 要使用的Exchange的名稱
content-type: application/json # 消息類型
binder: defaultRabbit #要綁定消息服務的具體設定
group: consumer2
如下:
由此可以得到:如果将兩個消息消費者的分組設定為同一個分組即可避免重複消費
2.持久化
什麼是持久化?
當配置了分組後,當消息消費者停止工作了(stop、關閉),此時如果消息生産者發送消息,那麼在配置由group的的消息消費者在重新啟動的時候就會收到之前沒有收到的消息,而沒有配置group的消息消費者則收不到之前的消息。
測試:将上一步中的client關閉,将8300的group配置注釋,而8200的group配置保留,請求8100(消息生産者)發送消息。在啟動兩個client,會發現8200會收到這些消息,而8300收不到這些消息。
Sleuth分布式請求鍊路跟蹤
Sleuth提供完整的請求服務跟蹤解決方案
需要配合zipkin-jar包一起使用
在現在的版本中,隻需要導入GAV即可,其中包含的sleuth和zipkin
具體配置:
- 為每個微服務添加依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>
- yml添加配置
spring: application: name: cloud-provider-payment # 微服務名稱 zipkin: base-url: http://localhost:9411 # zipkin位址 sleuth: sampler: probability: 1 # 采集的數量 0-1之間
啟動zipkin-jar包後,通路:http://localhost:9411即可檢視相關的請求消息。