1. 微服務架構
- "微服務”一詞源于 Martin Fowler的名為 Microservices的博文
- 微服務是系統架構上的一種設計風格,它的主旨是将一個原本獨立的系統拆分成多個小型服務,這些小型服務都在各自獨立的程序中運作,服務之間一般通過 HTTP 的 RESTfuLAPI 進行通信協作。
-
被拆分成的每一個小型服務都圍繞着系統中的某一項或些耦合度較高的業務功能進行建構,并且每個服務都維護着自身的資料存儲、業務開發自動化測試案例以及獨立部署機
制。
- 由于有了輕量級的通信協作基礎,是以這些微服務可以使用不同的語言來編寫。
2. 初識Spring Cloud
2.1 SpringCloud介紹
- Spring Cloud 是一系列架構的有序集合。
- Spring Cloud 并沒有重複制造輪子,它隻是将目前各家公司開發的比較成熟、經得起實際考驗的服務架構組合起來。
- 通過 Spring Boot 風格進行再封裝屏蔽掉了複雜的配置和實作原理,最終給開發者留出了一套簡單易懂、易部署和易維護的分布式系統開發工具包。
- 它利用Spring Boot的開發便利性巧妙地簡化了分布式系統基礎設施的開發,如服務發現注冊、配置中心、消息總線、負載均衡、 斷路器、資料監控等,都可以用Spring Boot的開發風格做到一鍵啟動和部署。
- Spring Cloud項目官方網址:https://spring.io/projects/spring-cloud
- Spring Cloud 版本命名方式采用了倫敦地鐵站的名稱,同時根據字母表的順序來對應版本時間順序,比如:最早的Release版本:Angel,第二個Release版本:Brixton,然後是Camden、Dalston、Edgware,Finchley,Greenwich,Hoxton。
- 目前最新的是Hoxton版本。
2.2 Spring Cloud 和dubbo對比
- Spring Cloud 與 Dubbo 都是實作微服務有效的工具。
- Dubbo 隻是實作了服務治理,而 Spring Cloud 子項目分别覆寫了微服務架構下的衆多部件。
- Dubbo 使用 RPC 通訊協定,Spring Cloud 使用 RESTful 完成通信,Dubbo 效率略高于 Spring Cloud。
總結
- 微服務就是将項目的各個子產品拆分為可獨立運作、部署、測試的架構設計風格。
Spring 公司将其他公司中微服務架構常用的元件整合起來,并使用 SpringBoot 簡化其開發、配置。
稱為 Spring Cloud
- Spring Cloud 與 Dubbo都是實作微服務有效的工具。Dubbo 性能更好,而 Spring Cloud 功能更全面
3. Spring Cloud服務治理
3.1 Eureka
3.1.1 介紹
• Eureka 是 Netflix 公司開源的一個服務注冊與發現的元件 。
• Eureka 和其他 Netflix 公司的服務元件(例如負載均衡、熔斷器、網關等) 一起,被 Spring Cloud 社群整合為
Spring-Cloud-Netflix
子產品。
• Eureka 包含兩個元件:Eureka Server (注冊中心) 和 Eureka Client (服務提供者、服務消費者)。
3.1.2 示範案例
- 環境搭建
- 建立父工程Spring-cloud-parent
- Spring-cloud-parent pom.xml
<!--spring boot 環境 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.0.RELEASE</version> <relativePath/> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties>
- 建立服務提供者eureka-provider
- eureka-provider pom.xml
<dependencies> <!--spring boot web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
- GoodsController
package com.itheima.provider.controller; import com.itheima.provider.domain.Goods; import com.itheima.provider.service.GoodsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * Goods Controller 服務提供方 */ @RestController @RequestMapping("/goods") public class GoodsController { @Autowired private GoodsService goodsService; @GetMapping("/findOne/{id}") public Goods findOne(@PathVariable("id") int id){ Goods goods = goodsService.findOne(id); return goods; } }
- GoodsService
package com.itheima.provider.service; import com.itheima.provider.dao.GoodsDao; import com.itheima.provider.domain.Goods; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * Goods 業務層 */ @Service public class GoodsService { @Autowired private GoodsDao goodsDao; /** * 根據id查詢 * @param id * @return */ public Goods findOne(int id){ return goodsDao.findOne(id); } }
- Goods
package com.itheima.provider.domain; /** * 商品實體類 */ public class Goods { private int id; private String title;//商品标題 private double price;//商品價格 private int count;//商品庫存 public Goods() { } public Goods(int id, String title, double price, int count) { this.id = id; this.title = title; this.price = price; this.count = count; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } }
- GoodsDao
package com.itheima.provider.dao; import com.itheima.provider.domain.Goods; import org.springframework.stereotype.Repository; import javax.validation.ReportAsSingleViolation; /** * 商品Dao */ @Repository public class GoodsDao { public Goods findOne(int id){ return new Goods(1,"華為手機",3999,10000); } }
- ProviderApp
package com.itheima.provider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * 啟動類 */ @SpringBootApplication public class ProviderApp { public static void main(String[] args) { SpringApplication.run(ProviderApp.class,args); } }
- application.yml
server: port: 8000
- 建立服務消費者eureka-consumer
- OrderController
package com.itheima.consumer.controller; import com.itheima.consumer.domain.Goods; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 服務的調用方 */ @RestController @RequestMapping("/order") public class OrderController { @GetMapping("/goods/{id}") public Goods findGoodsById(@PathVariable("id") int id){ System.out.println("findGoodsById..."+id); //遠端調用Goods服務中的findOne接口 return null; } }
- Goods
package com.itheima.consumer.domain; /** * 商品實體類 */ public class Goods { private int id; private String title;//商品标題 private double price;//商品價格 private int count;//商品庫存 public Goods() { } public Goods(int id, String title, double price, int count) { this.id = id; this.title = title; this.price = price; this.count = count; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } }
- ConsumerApp
package com.itheima.consumer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ConsumerApp { public static void main(String[] args) { SpringApplication.run(ConsumerApp.class,args); } }
- application.yml
server: port: 9000
3.1.3 RestTemplate遠端調用
- Spring提供的一種簡單便捷的模闆類,用于在 java 代碼裡通路 restful 服務。
- 其功能與 HttpClient 類似,但是 RestTemplate 實作更優雅,使用更友善。
- 修改消費方代碼
- RestTemplateConfig
package com.itheima.consumer.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }
- OrderController
package com.itheima.consumer.controller; import com.itheima.consumer.domain.Goods; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; /** * 服務的調用方 */ @RestController @RequestMapping("/order") public class OrderController { @Autowired private RestTemplate restTemplate; @GetMapping("/goods/{id}") public Goods findGoodsById(@PathVariable("id") int id){ System.out.println("findGoodsById..."+id); /* //遠端調用Goods服務中的findOne接口 使用RestTemplate 1. 定義Bean restTemplate 2. 注入Bean 3. 調用方法 */ String url = "http://localhost:8000/goods/findOne/"+id; // 3. 調用方法 Goods goods = restTemplate.getForObject(url, Goods.class); return goods; } }
3.1.4 Eureka Server搭建
- 建立 eureka-server 子產品
- 引入 SpringCloud 和 euraka-server 相關依賴
<!--Spring-cloud-parent pom.xml-->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<!--spring cloud 版本-->
<spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
</properties>
<!--引入Spring Cloud 依賴-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!--eureka-server pom.xml-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
- EurekaApp
package com.itheima.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
// 啟用EurekaServer
@EnableEurekaServer
public class EurekaApp {
public static void main(String[] args) {
SpringApplication.run(EurekaApp.class,args);
}
}
- 完成 Eureka Server 相關配置
#application.yml
server:
port: 8761
# eureka 配置
# eureka 一共有4部分 配置
# 1. dashboard:eureka的web控制台配置
# 2. server:eureka的服務端配置
# 3. client:eureka的用戶端配置
# 4. instance:eureka的執行個體配置
eureka:
instance:
hostname: localhost # 主機名
client:
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka # eureka服務端位址,将來用戶端使用該位址和eureka進行通信
register-with-eureka: false # 是否将自己的路徑 注冊到eureka上。eureka server 不需要的,eureka provider client 需要
fetch-registry: false # 是否需要從eureka中抓取路徑。eureka server 不需要的,eureka consumer client 需要
- 啟動該子產品
3.1.5 Eureka控制台介紹
- System status:系統狀态資訊
- DS Replicas:叢集資訊
- tance currently registered with Eureka: 執行個體注冊資訊
- General Info :通用資訊
- Instance Info :執行個體資訊
3.1.6 Eureka Client
- 引入eureka-client 相關依賴
<!--eureka-provider pom.xml-->
<dependencies>
<!--spring boot web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
- ProviderApp
package com.itheima.provider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* 啟動類
*/
@EnableEurekaClient //該注解 在新版本中可以省略
@SpringBootApplication
public class ProviderApp {
public static void main(String[] args) {
SpringApplication.run(ProviderApp.class,args);
}
}
- 完成 eureka client 相關配置
# application.yml
server:
port: 8001
eureka:
instance:
hostname: localhost # 主機名
client:
service-url:
defaultZone: http://localhost:8761/eureka # eureka服務端位址,将來用戶端使用該位址和eureka進行通信
spring:
application:
name: eureka-provider # 設定目前應用的名稱。将來會在eureka中Application顯示。将來需要使用該名稱來擷取路徑
- 啟動 測試
- 服務消費者eureka-consumer通過修改,也可以展示在控制台
- eureka-consumer在這裡僅僅是我們人為定義為消費者,作為一個服務,其實既可以作為服務提供方,同時也可以作為服務消費方
- ConsumerApp添加@EnableEurekaClient
package com.itheima.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient
@SpringBootApplication
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class,args);
}
}
- application.yml
server:
port: 9000
eureka:
instance:
hostname: localhost # 主機名
client:
service-url:
defaultZone: http://localhost:8761/eureka # eureka服務端位址,将來用戶端使用該位址和eureka進行通信
spring:
application:
name: eureka-consumer # 設定目前應用的名稱。将來會在eureka中Application顯示。将來需要使用該名稱來擷取路徑
3.1.7 動态擷取路徑
- ConsumerApp添加@EnableDiscoveryClient
package com.itheima.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableDiscoveryClient // 激活DiscoveryClient
@EnableEurekaClient
@SpringBootApplication
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class,args);
}
}
- OrderController修改代碼動态擷取路徑
package com.itheima.consumer.controller;
import com.itheima.consumer.domain.Goods;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
* 服務的調用方
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/goods/{id}")
public Goods findGoodsById(@PathVariable("id") int id){
System.out.println("findGoodsById..."+id);
/*
//遠端調用Goods服務中的findOne接口
使用RestTemplate
1. 定義Bean restTemplate
2. 注入Bean
3. 調用方法
*/
/*
動态從Eureka Server 中擷取 provider 的 ip 和端口
1. 注入 DiscoveryClient 對象.激活
2. 調用方法
*/
//示範discoveryClient 使用
List<ServiceInstance> instances = discoveryClient.getInstances("EUREKA-PROVIDER");
//判斷集合是否有資料
if(instances == null || instances.size() == 0){
//集合沒有資料
return null;
}
ServiceInstance instance = instances.get(0);
String host = instance.getHost();//擷取ip
int port = instance.getPort();//擷取端口
System.out.println(host);
System.out.println(port);
String url = "http://"+host+":"+port+"/goods/findOne/"+id;
// 3. 調用方法
Goods goods = restTemplate.getForObject(url, Goods.class);
return goods;
}
}
3.1.8 Eureka屬性
- instance相關屬性
- Eureka Instance的配置資訊全部儲存在
配置類裡,實際上它是org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean
的實作類,替代了netflix的com.netflix.appinfo.EurekaInstanceConfig
的預設實作。com.netflix.appinfo.CloudInstanceConfig
- Eureka Instance的配置資訊全部以
的格式配置。eureka.instance.xxx
- 配置清單
-
:應用名,首先擷取spring.application.name的值,如果取值為空,則取預設unknown。appname = unknown
-
:應用組名appGroupName = null
-
:執行個體注冊到Eureka上是,是否立刻開啟通訊。有時候應用在準備好服務之前需要一些預處理。instanceEnabledOnit = false
-
:非安全的端口nonSecurePort = 80
-
:安全端口securePort = 443
-
:是否開啟非安全端口通訊nonSecurePortEnabled = true
-
:是否開啟安全端口通訊securePortEnabled = false
-
:執行個體續約間隔時間leaseRenewalIntervalInSeconds = 30
-
:執行個體逾時時間,表示最大leaseExpirationDurationInSeconds秒後沒有續約,Server就認為他不可用了,随之就會将其剔除。leaseExpirationDurationInSeconds = 90
-
:虛拟主機名,首先擷取spring.application.name的值,如果取值為空,則取預設unknown。virtualHostName = unknown
-
:注冊到eureka上的唯一執行個體ID,不能與相同appname的其他執行個體重複。instanceId
-
:安全虛拟主機名,首先擷取spring.application.name的值,如果取值為空,則取預設unknown。secureVirtualHostName = unknown
-
:執行個體中繼資料,可以供其他執行個體使用。比如spring-boot-admin在監控時,擷取執行個體的上下文和端口。metadataMap = new HashMap();
-
:執行個體部署的資料中心。如AWS、MyOwn。dataCenterInfo = new MyDataCenterInfo(DataCenterInfo.Name.MyOwn);
-
:執行個體的IP位址ipAddress=null
-
:執行個體狀态頁相對urlstatusPageUrlPath = "/actuator/info"
-
:執行個體狀态頁絕對URLstatusPageUrl = null
-
:執行個體首頁相對URLhomePageUrlPath = "/"
-
:執行個體首頁絕對URLhomePageUrl = null
-
:執行個體健康檢查相對URLhealthCheckUrlUrlPath = "/actuator/health"
-
:執行個體健康檢查絕對URLhealthCheckUrl = null
-
:執行個體安全的健康檢查絕對URLsecureHealthCheckUrl = null
-
:配置屬性的命名空間(Spring Cloud中被忽略)namespace = "eureka"
-
:主機名,不配置的時候講根據作業系統的主機名來擷取hostname = null
-
:是否優先使用IP位址作為主機名的辨別preferIpAddress = false
-
- server相關屬性
- Eureka Server注冊中心端的配置是對注冊中心的特性配置。Eureka Server的配置全部在
裡,實際上它是org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean
的實作類,替代了netflix的預設實作。com.netflix.eureka.EurekaServerConfig
- Eureka Server的配置全部以eureka.server.xxx的格式進行配置。
- Eureka Server注冊中心端的配置是對注冊中心的特性配置。Eureka Server的配置全部在
- 配置清單
-
:是否開啟自我保護enableSelfPreservation=true
-
:自我保護續約百分比閥值因子。如果實際續約數小于續約數閥值,則開啟自我保護renewalPercentThreshold = 0.85
-
:續約數閥值更新頻率。renewalThresholdUpdateIntervalMs = 15 * 60 * 1000
-
:Eureka Server節點更新頻率。peerEurekaNodesUpdateIntervalMs = 10 * 60 * 1000
-
:是否啟用複制請求壓縮。enableReplicatedRequestCompression = false
-
:當從其他節點同步執行個體資訊為空時等待的時間。waitTimeInMsWhenSyncEmpty=5 * 60 * 1000
-
:節點間連接配接的逾時時間。peerNodeConnectTimeoutMs=200
-
:節點間讀取資訊的逾時時間。peerNodeReadTimeoutMs=200
-
:節點間連接配接總數。peerNodeTotalConnections=1000
-
:單個節點間連接配接總數。peerNodeTotalConnectionsPerHost = 500;
-
:節點間連接配接空閑逾時時間。peerNodeConnectionIdleTimeoutSeconds = 30;
-
:增量隊列的緩存時間。retentionTimeInMSInDeltaQueue = 3 * MINUTES;
-
:清理增量隊列中過期的頻率。deltaRetentionTimerIntervalInMs = 30 * 1000;
-
:剔除任務頻率。evictionIntervalTimerInMs = 60 * 1000;
-
:注冊清單緩存逾時時間(當注冊清單沒有變化時)responseCacheAutoExpirationInSeconds = 180;
-
:注冊清單緩存更新頻率。responseCacheUpdateIntervalMs = 30 * 1000;
-
:是否開啟注冊清單的二級緩存。useReadOnlyResponseCache = true;
-
:是否為client提供增量資訊。disableDelta=false。
-
:狀态同步的最大線程數。maxThreadsForStatusReplication = 1;
-
:狀态同步隊列的最大容量。maxElementsInStatusReplicationPool = 10000;
-
:當時間差異時是否同步。syncWhenTimestampDiffers = true;
-
:注冊資訊同步重試次數。registrySyncRetries = 0;
-
:注冊資訊同步重試期間的時間間隔。registrySyncRetryWaitMs = 30 * 1000;
-
:節點間同步事件的最大容量。maxElementsInPeerReplicationPool = 10000;
-
:節點間同步的最小線程數。minThreadsForPeerReplication = 5;
-
:節點間同步的最大線程數。maxThreadsForPeerReplication = 20;
-
:節點間同步的最大時間,機關為毫秒。maxTimeForReplication = 30000;
-
:是否啟用遠端區域增量。disableDeltaForRemoteRegions = false;
-
:遠端區域連接配接逾時時間。remoteRegionConnectTimeoutMs = 1000;
-
:遠端區域讀取逾時時間。remoteRegionReadTimeoutMs = 1000;
-
:遠端區域最大連接配接數remoteRegionTotalConnections = 1000;
-
:遠端區域單機連接配接數remoteRegionTotalConnectionsPerHost = 500;
-
:遠端區域連接配接空閑逾時時間。remoteRegionConnectionIdleTimeoutSeconds = 30;
-
:遠端區域注冊資訊拉取頻率。remoteRegionRegistryFetchInterval = 30;
-
:遠端區域注冊資訊線程數。remoteRegionFetchThreadPoolSize = 20;
-
3.1.9 Eureka高可用
- 搭建
- 準備兩個Eureka Server
- 分别進行配置,互相注冊
- Eureka Client 分别注冊到這兩個 Eureka Server中
- 建立eureka-server-1
- pom.xml
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies>
- application.yml
server: port: 8761 eureka: instance: hostname: eureka-server1 # 主機名 client: service-url: defaultZone: http://eureka-server2:8762/eureka register-with-eureka: true # 是否将自己的路徑 注冊到eureka上。eureka server 不需要的,eureka provider client 需要 fetch-registry: true # 是否需要從eureka中抓取路徑。eureka server 不需要的,eureka consumer client 需要 spring: application: name: eureka-server-ha
- Eureka1App
package eureka; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication // 啟用EurekaServer @EnableEurekaServer public class Eureka1App { public static void main(String[] args) { SpringApplication.run(Eureka1App.class,args); } }
- 建立eureka-server-2
- pom.xml
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies>
- application.yml
server: port: 8762 eureka: instance: hostname: eureka-server2 # 主機名 client: service-url: defaultZone: http://eureka-server1:8761/eureka register-with-eureka: true # 是否将自己的路徑 注冊到eureka上。eureka server 不需要的,eureka provider client 需要 fetch-registry: true # 是否需要從eureka中抓取路徑。eureka server 不需要的,eureka consumer client 需要 spring: application: name: eureka-server-ha
- Eureka2App
package eureka; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication // 啟用EurekaServer @EnableEurekaServer public class Eureka2App { public static void main(String[] args) { SpringApplication.run(Eureka2App.class,args); } }
- 修改本地host
- 用戶端測試
- 修改服務提供者和服務消費者配置檔案中的注冊服務位址
- eureka-provider application.yml
server: port: 8001 eureka: instance: hostname: localhost # 主機名 prefer-ip-address: true # 将目前執行個體的ip注冊到eureka server 中。預設是false 注冊主機名 ip-address: 127.0.0.1 # 設定目前執行個體的ip instance-id: ${eureka.instance.ip-address}:${spring.application.name}:${server.port} # 設定web控制台顯示的 執行個體id lease-renewal-interval-in-seconds: 3 # 每隔3 秒發一次心跳包 lease-expiration-duration-in-seconds: 9 # 如果9秒沒有發心跳包,伺服器呀,你把我幹掉吧~ client: service-url: defaultZone: http://eureka-server1:8761/eureka,http://eureka-server2:8762/eureka # eureka服務端位址,将來用戶端使用該位址和eureka進行通信 spring: application: name: eureka-provider # 設定目前應用的名稱。将來會在eureka中Application顯示。将來需要使用該名稱來擷取路徑
- eureka-consumer application.yml
server: port: 9000 eureka: instance: hostname: localhost # 主機名 client: service-url: defaultZone: http://eureka-server1:8761/eureka,http://eureka-server2:8762/eureka # eureka服務端位址,将來用戶端使用該位址和eureka進行通信 spring: application: name: eureka-consumer # 設定目前應用的名稱。将來會在eureka中Application顯示。将來需要使用該名稱來擷取路徑
- 測試結果
3.2 Consul
3.2.1 Consul 概述
- Consul 是由 HashiCorp 基于 Go 語言開發的,支援多資料中心,分布式高可用的服務釋出和注冊服務軟體。
- 用于實作分布式系統的服務發現與配置。
- 使用起來也較 為簡單。具有天然可移植性(支援Linux、windows和Mac OS X);安裝包僅包含一個可執行檔案,友善部署 。
- 官網位址: https://www.consul.io
3.2.2 啟動consul
- dev模式:不會持久化資料
- 啟動成功
- 控制台
3.2.3 Consul案例示範
- 步驟
- 搭建 Provider 和 Consumer 服務。
- 使用 RestTemplate 完成遠端調用。
- 将Provider服務注冊到Consul中。
- Consumer 服務 通過從 Consul 中抓取 Provider 位址 完成 遠端調用
- Provider pom.xml
<dependencies>
<!--consul 用戶端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
- application.yml
server:
port: 8000
spring:
cloud:
consul:
host: localhost # consul 服務端的 ip
port: 8500 # consul 服務端的端口 預設8500
discovery:
service-name: ${spring.application.name} # 目前應用注冊到consul的名稱
prefer-ip-address: true # 注冊ip
application:
name: consul-provider # 應用名稱
- consumer pom.xml
<dependencies>
<!--consul 用戶端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
- application.yml
server:
port: 9000
spring:
cloud:
consul:
host: localhost # consul 服務端的 ip
port: 8500 # consul 服務端的端口 預設8500
discovery:
service-name: ${spring.application.name} # 目前應用注冊到consul的名稱
prefer-ip-address: true # 注冊ip
application:
name: consul-consumer # 應用名稱
- OrderController
package com.itheima.consul.controller;
import com.itheima.consul.domain.Goods;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
* 服務的調用方
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/goods/{id}")
public Goods findGoodsById(@PathVariable("id") int id){
//示範discoveryClient 使用
List<ServiceInstance> instances = discoveryClient.getInstances("consul-PROVIDER");
//判斷集合是否有資料
if(instances == null || instances.size() == 0){
//集合沒有資料
return null;
}
ServiceInstance instance = instances.get(0);
String host = instance.getHost();//擷取ip
int port = instance.getPort();//擷取端口
System.out.println(host);
System.out.println(port);
String url = "http://"+host+":"+port+"/goods/findOne/"+id;
// 3. 調用方法
Goods goods = restTemplate.getForObject(url, Goods.class);
return goods;
}
}
3.3 Nacos
3.3.1 Nacos 概述
- Nacos(Dynamic Naming and Configuration Service) 是阿裡巴巴2018年7月開源的項目。
- 它專注于服務發現和配置管理領域 緻力于幫助您發現、配置和管理微服務。Nacos 支援幾乎所有主流類型的“服務”的發現、配置和管理。
- 一句話概括就是Nacos = Spring Cloud注冊中心 + Spring Cloud配置中心。
- 官網:https://nacos.io/
- 下載下傳位址: https://github.com/alibaba/nacos/releases
3.3.2 啟動
3.3.3 控制台
- 控制台登入(賬号,密碼:nacos)
- 控制台頁面
- Spring cloud Alibaba 元件
3.3.4 Nacos 示範案例
- nacos-provider pom.xml
<dependencies>
<!--nacos-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
- application.yml
server:
port: 8000
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 配置nacos 服務端位址
application:
name: nacos-provider # 服務名稱
- nacos consumer pom.xml
<dependencies>
<!--nacos-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
- application.yml
server:
port: 9000
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 配置nacos 服務端位址
application:
name: nacos-consumer # 服務名稱
- 控制台顯示
- 詳情頁面
- 示例代碼
4. Ribbon
- 用戶端的負載均衡器
- ribbon有七種負載均衡政策可以選擇
政策類 命名 描述 RandomRule 随機政策 随機選擇server RoundRobinRule 輪詢政策 按照順序選擇server(ribbon預設政策) RetryRule 重試政策 在一個配置時間段内,當選擇server不成功,則一直嘗試選擇一個可用的server BestAvailableRule 最低并發政策 逐個考察server,如果server斷路器打開,則忽略,再選擇其中并發連結最低的server AvailabilityFilteringRule 可用過濾政策 過濾掉一直失敗并被标記為circuit tripped的server,過濾掉那些高并發連結的server(active connections超過配置的門檻值) ResponseTimeWeightedRule 響應時間權重重政策 根據server的響應時間配置設定權重,響應時間越長,權重越低,被選擇到的機率也就越低,響應時間越短,權重越高,被選中的機率越高,這個政策很貼切,綜合了各種因素 ZoneAvoidanceRule 區域權重政策 綜合判斷server所在區域的性能,和server的可用性,輪詢選擇server并且判斷一個AWS Zone的運作性能是否可用,剔除不可用的Zone中的所有server
5. Feign
5.1 Feign概述
- Feign 是一個聲明式的 REST 用戶端,它用了基于接口的注解方式,很友善實作用戶端配置。
- Feign 最初由 Netflix 公司提供,但不支援SpringMVC注解,後由 SpringCloud 對其封裝,支援了SpringMVC注解,讓使用者更易于接受
5.2 Feign示範案例
- 在消費端引入 open-feign 依賴
<!--feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 編寫Feign調用接口
package com.itheima.consumer.feign;
import com.itheima.consumer.config.FeignLogConfig;
import com.itheima.consumer.domain.Goods;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
*
* feign聲明式接口。發起遠端調用的。
*
String url = "http://FEIGN-PROVIDER/goods/findOne/"+id;
Goods goods = restTemplate.getForObject(url, Goods.class);
*
* 1. 定義接口
* 2. 接口上添加注解 @FeignClient,設定value屬性為 服務提供者的 應用名稱
* 3. 編寫調用接口,接口的聲明規則 和 提供方接口保持一緻。
* 4. 注入該接口對象,調用接口方法完成遠端調用
*/
@FeignClient(value = "FEIGN-PROVIDER")
public interface GoodsFeignClient {
@GetMapping("/goods/findOne/{id}")
public Goods findGoodsById(@PathVariable("id") int id);
}
- OrderController
package com.itheima.consumer.controller;
import com.itheima.consumer.domain.Goods;
import com.itheima.consumer.feign.GoodsFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private GoodsFeignClient goodsFeignClient;
@GetMapping("/goods/{id}")
public Goods findGoodsById(@PathVariable("id") int id){
/*
String url = "http://FEIGN-PROVIDER/goods/findOne/"+id;
// 3. 調用方法
Goods goods = restTemplate.getForObject(url, Goods.class);
return goods;*/
Goods goods = goodsFeignClient.findGoodsById(id);
return goods;
}
}
- goodsFeignClient報紅,不影響使用
- 在啟動類 添加 @EnableFeignClients 注解,開啟Feign功能
package com.itheima.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableDiscoveryClient // 激活DiscoveryClient
@EnableEurekaClient
@SpringBootApplication
@EnableFeignClients //開啟Feign的功能
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class,args);
}
}
- 測試調用
5.3 Feign逾時配置
- Feign 底層依賴于 Ribbon 實作負載均衡和遠端調用。
- Ribbon預設1秒逾時。
- 逾時配置:
#feign-consumer application.yml
# 設定Ribbon的逾時時間
ribbon:
ConnectTimeout: 1000 # 連接配接逾時時間 預設1s 預設機關毫秒
ReadTimeout: 3000 # 邏輯處理的逾時時間 預設1s 預設機關毫秒
5.4 Feign日志記錄
- Feign 隻能記錄 debug 級别的日志資訊。
- feign-consumer application.yml
# 設定目前的日志級别 debug,feign隻支援記錄debug級别的日志 logging: level: com.itheima: debug
- 定義Feign日志級别Bean
- FeignLogConfig
package com.itheima.consumer.config; import feign.Logger; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FeignLogConfig { /* NONE,不記錄 BASIC,記錄基本的請求行,響應狀态碼資料 HEADERS,記錄基本的請求行,響應狀态碼資料,記錄響應頭資訊 FULL;記錄完成的請求 響應資料 */ @Bean public Logger.Level level(){ return Logger.Level.FULL; } }
- 啟用該Bean:
package com.itheima.consumer.feign; import com.itheima.consumer.config.FeignLogConfig; import com.itheima.consumer.domain.Goods; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; /** * * feign聲明式接口。發起遠端調用的。 * String url = "http://FEIGN-PROVIDER/goods/findOne/"+id; Goods goods = restTemplate.getForObject(url, Goods.class); * * 1. 定義接口 * 2. 接口上添加注解 @FeignClient,設定value屬性為 服務提供者的 應用名稱 * 3. 編寫調用接口,接口的聲明規則 和 提供方接口保持一緻。 * 4. 注入該接口對象,調用接口方法完成遠端調用 */ @FeignClient(value = "FEIGN-PROVIDER",configuration = FeignLogConfig.class) public interface GoodsFeignClient { @GetMapping("/goods/findOne/{id}") public Goods findGoodsById(@PathVariable("id") int id); }
6. Hystrix
6.1 Hystrix概述
- Hystix 是 Netflix 開源的一個延遲和容錯庫,用于隔離通路遠端服務、第三方庫,防止出現級聯失敗(雪崩)。
- 雪崩:一個服務失敗,導緻整條鍊路的服務都失敗的情形
- Hystix 主要功能
- 隔離
- 線程池隔離
- 信号量隔離
- 降級
- 異常
- 逾時
- 熔斷
- 限流
- 隔離
6.2 Hystrix-降級
- Hystix 降級:當服務發生異常或調用逾時,傳回預設資料
- 服務提供方降級
- 在服務提供方,引入 hystrix 依賴
<!-- hystrix --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
- 定義降級方法
/** * 定義降級方法: * 1. 方法的傳回值需要和原方法一樣 * 2. 方法的參數需要和原方法一樣 */ public Goods findOne_fallback(int id){ Goods goods = new Goods(); goods.setTitle("降級了~~~"); return goods; }
- 使用 @HystrixCommand 注解配置降級方法
/** * 降級: * 1. 出現異常 * 2. 服務調用逾時 * * 預設1s逾時 * * @HystrixCommand(fallbackMethod = "findOne_fallback") * fallbackMethod:指定降級後調用的方法名稱 */ @GetMapping("/findOne/{id}") @HystrixCommand(fallbackMethod = "findOne_fallback",commandProperties = { //設定Hystrix的逾時時間,預設1s @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000") }) public Goods findOne(@PathVariable("id") int id){ //1.造個異常 int i = 3/0; try { //2. 休眠2秒 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } Goods goods = goodsService.findOne(id); goods.setTitle(goods.getTitle() + ":" + port);//将端口号,設定到了 商品标題上 return goods; }
- 在啟動類上開啟Hystrix功能:@EnableCircuitBreaker
package com.itheima.provider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; /** * 啟動類 */ @EnableEurekaClient //該注解 在新版本中可以省略 @SpringBootApplication @EnableCircuitBreaker // 開啟Hystrix功能 public class ProviderApp { public static void main(String[] args) { SpringApplication.run(ProviderApp.class,args); } }
- 消費方降級
- feign 元件已經內建了 hystrix 元件。
- 定義feign 調用接口實作類,複寫方法,即 降級方法
- GoodsFeignClientFallback
package com.itheima.consumer.feign; import com.itheima.consumer.domain.Goods; import org.springframework.stereotype.Component; /** * Feign 用戶端的降級處理類 * 1. 定義類 實作 Feign 用戶端接口 * 2. 使用@Component注解将該類的Bean加入SpringIOC容器 */ @Component public class GoodsFeignClientFallback implements GoodsFeignClient { @Override public Goods findGoodsById(int id) { Goods goods = new Goods(); goods.setTitle("又被降級了~~~"); return goods; } }
- 在 @FeignClient 注解中使用 fallback 屬性設定降級處理類。
- GoodsFeignClient
package com.itheima.consumer.feign; import com.itheima.consumer.domain.Goods; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient(value = "HYSTRIX-PROVIDER",fallback = GoodsFeignClientFallback.class) public interface GoodsFeignClient { @GetMapping("/goods/findOne/{id}") public Goods findGoodsById(@PathVariable("id") int id); }
- 配置開啟 feign.hystrix.enabled = true
- application.yml
# 開啟feign對hystrix的支援 feign: hystrix: enabled: true
6.3 Hystrix熔斷
- 概念
- Hystrix 熔斷機制,用于監控微服務調用情況,當失敗的情況達到預定的門檻值(5秒失敗20次),會打開斷路器,拒絕所有請求,直到服務恢複正常為止。
- 斷路器三種狀态:打開、半開、關閉、
- 熔斷-代碼示範
- 修改服務提供方的方法,示範熔斷機制
- 熔斷配置
- circuitBreaker.sleepWindowInMilliseconds:監控時間
- circuitBreaker.requestVolumeThreshold:失敗次數
- circuitBreaker.errorThresholdPercentage:失敗率
- GoodsController
package com.itheima.provider.controller; import com.itheima.provider.domain.Goods; import com.itheima.provider.service.GoodsService; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Date; /** * Goods Controller 服務提供方 */ @RestController @RequestMapping("/goods") public class GoodsController { @Autowired private GoodsService goodsService; @Value("${server.port}") private int port; /** * 降級: * 1. 出現異常 * 2. 服務調用逾時 * * 預設1s逾時 * * @HystrixCommand(fallbackMethod = "findOne_fallback") * fallbackMethod:指定降級後調用的方法名稱 */ @GetMapping("/findOne/{id}") @HystrixCommand(fallbackMethod = "findOne_fallback",commandProperties = { //設定Hystrix的逾時時間,預設1s @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000"), //監控時間 預設5000 毫秒 @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value = "5000"), //失敗次數。預設20次 @HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value = "20"), //失敗率 預設50% @HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value = "50") }) public Goods findOne(@PathVariable("id") int id){ //如果id == 1 ,則出現異常,id != 1 則正常通路 if(id == 1){ //1.造個異常 int i = 3/0; } /*try { //2. 休眠2秒 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }*/ Goods goods = goodsService.findOne(id); goods.setTitle(goods.getTitle() + ":" + port);//将端口号,設定到了 商品标題上 return goods; } /** * 定義降級方法: * 1. 方法的傳回值需要和原方法一樣 * 2. 方法的參數需要和原方法一樣 */ public Goods findOne_fallback(int id){ Goods goods = new Goods(); goods.setTitle("降級了~~~"); return goods; } }
- 熔斷監控
- Hystrix 提供了 Hystrix-dashboard 功能,用于實時監控微服務運作狀态。
- 但是Hystrix-dashboard隻能監控一個微服務。
- Netflix 還提供了 Turbine ,進行聚合監控。
7. Gateway
7.1 Gateway概述
- 網關旨在為微服務架構提供一種簡單而有效的統一的API路由管理方式。
- 在微服務架構中,不同的微服務可以有不同的網絡位址,各個微服務之間通過互相調用完成使用者請求,用戶端可能通過調用N個微服務的接口完成一個使用者請求。
- 存在的問題
- 用戶端多次請求不同的微服務,增加用戶端的複雜性
- 認證複雜,每個服務都要進行認證
- http請求不同服務次數增加,性能不高
- 網關就是系統的入口,封裝了應用程式的内部結構,為用戶端提供統一服務,一些與業務本身功能無關的公共邏輯可以在這裡實作,諸如認證、鑒權、監控、緩存、負載均衡、流量管控、路由轉發等
- 在目前的網關解決方案裡,有Nginx+ Lua、Netflix Zuul 、Spring Cloud Gateway等等
7.2 Gateway示範案例
-
建立api-gateway-server子產品
引入依賴:starter-gateway
<dependencies>
<!--引入gateway 網關-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
- 編寫啟動類
package com.itheima.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class ApiGatewayApp {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApp.class,args);
}
}
-
編寫配置檔案
application.yml
server:
port: 80
spring:
application:
name: api-gateway-server
cloud:
# 網關配置
gateway:
# 路由配置:轉發規則
routes: #集合。
# id: 唯一辨別。預設是一個UUID
# uri: 轉發路徑
# predicates: 條件,用于請求網關路徑的比對規則
- id: gateway-provider
uri: http://localhost:8001/
predicates:
- Path=/goods/**
- 啟動測試
7.3 Gateway靜态路由
- application.yml 中的uri是寫死的,就是靜态路由
server:
port: 80
spring:
application:
name: api-gateway-server
cloud:
# 網關配置
gateway:
# 路由配置:轉發規則
routes: #集合。
# id: 唯一辨別。預設是一個UUID
# uri: 轉發路徑
# predicates: 條件,用于請求網關路徑的比對規則
# filters:配置局部過濾器的
- id: gateway-provider
# 靜态路由
uri: http://localhost:8001/
predicates:
- Path=/goods/**
7.4 Gateway動态路由
- 啟動類添加@EnableEurekaClient(新版本不加也可以)
package com.itheima.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class ApiGatewayApp {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApp.class,args);
}
}
- 引入eureka-client配置
- application.yml 中修改uri屬性:uri: lb://服務名稱
server:
port: 80
spring:
application:
name: api-gateway-server
cloud:
# 網關配置
gateway:
# 路由配置:轉發規則
routes: #集合。
# id: 唯一辨別。預設是一個UUID
# uri: 轉發路徑
# predicates: 條件,用于請求網關路徑的比對規則
# filters:配置局部過濾器的
- id: gateway-provider
# 靜态路由
# uri: http://localhost:8001/
# 動态路由
uri: lb://GATEWAY-PROVIDER
predicates:
- Path=/goods/**
7.5 Gateway微服務名稱配置
- application.yml中配置微服務名稱配置
# 微服務名稱配置
discovery:
locator:
enabled: true # 設定為true 請求路徑前可以添加微服務名稱
lower-case-service-id: true # 允許為小寫
7.6 Gateway過濾器
7.6.1 概述
- Gateway 支援過濾器功能,對請求或響應進行攔截,完成一些通用操作。
- Gateway 提供兩種過濾器方式:“pre”和“post”
- pre 過濾器,在轉發之前執行,可以做參數校驗、權限校驗、流量監控、日志輸出、協定轉換等。
- post 過濾器,在響應之前執行,可以做響應内容、響應頭的修改,日志的輸出,流量監控等。
- Gateway 還提供了兩種類型過濾器
- GatewayFilter:局部過濾器,針對單個路由
- GlobalFilter :全局過濾器,針對所有路由
7.6.2 局部過濾器
- GatewayFilter 局部過濾器,是針對單個路由的過濾器。
- 在Spring Cloud Gateway 元件中提供了大量内置的局部過濾器,對請求和響應做過濾操作。
- 遵循約定大于配置的思想,隻需要在配置檔案配置局部過濾器名稱,并為其指定對應的值,就可以讓其生效。
7.6.3 全局過濾器
- GlobalFilter 全局過濾器,不需要在配置檔案中配置,系統初始化時加載,并作用在每個路由上。
- Spring Cloud Gateway 核心的功能也是通過内置的全局過濾器來完成。
- 自定義全局過濾器步驟:
- 定義類實作 GlobalFilter 和 Ordered接口
- 複寫方法
- 完成邏輯處理
- MyFilter
package com.itheima.gateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class MyFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("自定義全局過濾器執行了~~~");
return chain.filter(exchange);//放行
}
/**
* 過濾器排序
* @return 數值越小 越先執行
*/
@Override
public int getOrder() {
return 0;
}
}
8. config
8.1 config概述
- Spring Cloud Config 解決了在分布式場景下多環境配置檔案的管理和維護。
- 好處:
- 集中管理配置檔案
- 不同環境不同配置,動态化的配置更新
- 配置資訊改變時,不需要重新開機即可更新配置資訊到服務
8.2 config入門
8.2.1 gitee搭建遠端倉庫
- 編寫倉庫名稱、倉庫路徑、公開(公開的比較友善)
- 語言和模闆可以不選,一般使用單分支模型,隻建立master分支
- 使用小烏龜工具,将遠端倉庫clone到本地
- 使用小烏龜工具将配置檔案送出到遠端倉庫
8.2.2 config server搭建
- config server:
- 使用gitee建立遠端倉庫,上傳配置檔案
- 搭建 config server 子產品
- 導入 config-server 依賴
<dependencies> <!-- config-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> </dependencies>
- 編寫配置,設定 gitee 遠端倉庫位址
server: port: 9527 spring: application: name: config-server # spring cloud config cloud: config: server: # git 的 遠端倉庫位址 git: uri: https://gitee.com/itheima_cch/itheima-configs.git label: master # 分支配置
- 測試通路遠端配置檔案
8.2.3 config client搭建
- 導入 starter-config 依賴
<!--config client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
-
配置config server 位址,讀取配置檔案名稱等資訊
建立配置檔案bootstrap.yml
# 配置config-server位址
# 配置獲得配置檔案的名稱等資訊
spring:
cloud:
config:
# 配置config-server位址
uri: http://localhost:9527
# 配置獲得配置檔案的名稱等資訊
name: config # 檔案名
profile: dev # profile指定, config-dev.yml
label: master # 分支
- 擷取配置值
@Value("${itheima}")
private String itheima;
- 啟動測試
8.2.4 config client重新整理
- 在 config 用戶端引入 actuator 依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 擷取配置資訊類上,添加 @RefreshScope 注解
/**
* Goods Controller 服務提供方
*/
@RestController
@RequestMapping("/goods")
@RefreshScope // 開啟重新整理功能
public class GoodsController {
@Autowired
private GoodsService goodsService;
@Value("${server.port}")
private int port;
@Value("${itheima}")
private String itheima;
...
}
- 添加配置management.endpoints.web.exposure.include: refresh
# 配置config-server位址
# 配置獲得配置檔案的名稱等資訊
spring:
cloud:
config:
# 配置config-server位址
uri: http://localhost:9527
# 配置獲得配置檔案的名稱等資訊
name: config # 檔案名
profile: dev # profile指定, config-dev.yml
label: master # 分支
management:
endpoints:
web:
exposure:
include: refresh
- 使用curl工具發送post請求
curl -X POST http://localhost:8001/actuator/refresh
8.3 config內建Eureka
- config-server pom.xml中引入eureka-client 坐标
<!-- eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 配置檔案中配置eureka位址
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
- 啟動類中添加注解
package com.itheima.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer // 啟用config server功能
@EnableEurekaClient
public class ConfigServerApp {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApp.class,args);
}
}
- config-provider 工程中bootstrap.yaml中注掉寫死的位址,改為從Eureka中擷取
# 配置config-server位址
# 配置獲得配置檔案的名稱等資訊
spring:
cloud:
config:
# 配置config-server位址
#uri: http://localhost:9527
# 配置獲得配置檔案的名稱等資訊
name: config # 檔案名
profile: dev # profile指定, config-dev.yml
label: master # 分支
#從注冊中心擷取config-server位址
discovery:
enabled:true
service-id:CONFIG-SERVER
9. bus
9.1 bus概述
- Spring Cloud Bus 是用輕量的消息中間件将分布式的節點連接配接起來,可以用于廣播配置檔案的更改或者服務的監控管理。關鍵的思想就是,消息總線可以為微服務做監控,也可以實作應用程式之間相通信。
- Spring Cloud Bus 可選的消息中間件包括 RabbitMQ 和 Kafka
9.2 bus-rabbitmq回顧
- RabbitMQ 提供了 6 種工作模式:簡單模式、work queues、Publish/Subscribe 釋出與訂閱模式、Routing路由模式、Topics 主題模式、RPC 遠端調用模式(遠端調用,不太算 MQ;暫不作介紹)。
9.3 bus示範案例
- 分别在 config-server 和 config-client中引入 bus依賴:bus-amqp
<!-- bus -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
- 分别在 config-server 和 config-client中配置 RabbitMQ
- bootstrap.yml
#配置rabbitmq資訊
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
- OrderController上添加@RefreshScope注解
@RestController
@RequestMapping("/order")
@RefreshScope
public class OrderController {
@Value("${itheima}")
private String itheima;
...
}
- 在config-server中設定暴露監控斷點:bus-refresh
- application.yml
# 暴露bus的重新整理端點
management:
endpoints:
web:
exposure:
include: 'bus-refresh'
- 啟動測試
- curl結果中沒有資訊,說明成功了
10. stream
10.1 stream概述
- Spring Cloud Stream 是一個建構消息驅動微服務應用的架構。
- Stream 解決了開發人員無感覺的使用消息中間件的問題,因為Stream對消息中間件的進一步封裝,可以做到代碼層面對中間件的無感覺,甚至于動态的切換中間件,使得微服務開發的高度解耦,服務可以關注更多自己的業務流程。
- Spring Cloud Stream目前支援兩種消息中間件RabbitMQ和Kafka
10.2 stream元件
- Spring Cloud Stream 建構的應用程式與消息中間件之間是通過綁定器 Binder相關聯的。綁定器對于應用程式而言起到了隔離作用, 它使得不同消息中間件的實作細節對應用程式來說是透明的。
- binding 是我們通過配置把應用和spring cloud stream 的 binder 綁定在一起
- output:發送消息 Channel,内置 Source接口
- input:接收消息 Channel,内置 Sink接口
10.3 stream消息生産者
- 建立消息生産者子產品,引入依賴 starter-stream-rabbit
<!-- stream -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
- 編寫配置,定義 binder,和 bingings
server:
port: 8000
spring:
cloud:
stream:
# 定義綁定器,綁定到哪個消息中間件上
binders:
itheima_binder: # 自定義的綁定器名稱
type: rabbit # 綁定器類型
environment: # 指定mq的環境
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
bindings:
output: # channel名稱
binder: itheima_binder #指定使用哪一個binder
destination: itheima_exchange # 消息目的地
- 定義消息發送業務類。添加 @EnableBinding(Source.class),注入MessageChannel output ,完成消息發送
- MessageProducer
package com.itheima.stream.producer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
@Component
@EnableBinding(Source.class)
public class MessageProducer {
@Autowired
private MessageChannel output;
public void send(){
String msessage = "hello stream~~~";
//發送消息
output.send(MessageBuilder.withPayload(msessage).build());
System.out.println("消息發送成功~~~");
}
}
- ProducerController
package com.itheima.stream.producer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProducerController {
@Autowired
private MessageProducer producer;
@RequestMapping("/send")
public String sendMsg(){
producer.send();
return "success";
}
}
- 編寫啟動類,測試
package com.itheima.stream;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProducerApp {
public static void main(String[] args) {
SpringApplication.run(ProducerApp.class,args);
}
}
10.4 stream消息消費者
- 建立消息消費者子產品,引入依賴 starter-stream-rabbit
<!-- stream -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
- 編寫配置,定義 binder,和 bingings
server:
port: 9000
spring:
cloud:
stream:
# 定義綁定器,綁定到哪個消息中間件上
binders:
itheima_binder: # 自定義的綁定器名稱
type: rabbit # 綁定器類型
environment: # 指定mq的環境
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
bindings:
output: # channel名稱
binder: itheima_binder #指定使用哪一個binder
destination: itheima_exchange # 消息目的地
- 定義消息接收業務類。添加 @EnableBinding(Sink.class),使用@StreamListener(Sink.INPUT),完成消息接收。
package com.itheima.stream.consumer;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
/**
* 消息接收類
*/
@EnableBinding({Sink.class})
@Component
public class MessageListener {
@StreamListener(Sink.INPUT)
public void receive(Message message){
System.out.println(message);
System.out.println(message.getPayload());
}
}
- 編寫啟動類,測試
package com.itheima.stream;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class,args);
}
}
11. Sleuth+Zipkin
11.1 概述
- Spring Cloud Sleuth 其實是一個工具,它在整個分布式系統中能跟蹤一個使用者請求的過程,捕獲這些跟蹤資料,就能建構微服務的整個調用鍊的視圖,這是調試和監控微服務的關鍵工具。
- 耗時分析
- 可視化錯誤
- 鍊路優化
-
Zipkin 是 Twitter 的一個開源項目,它緻力于收集服務的定時資料,以解決微服務架構中的延遲問題,包
括資料的收集、存儲、查找和展現。
11.2 入門
- 安裝啟動zipkin。 java –jar zipkin.jar
- 啟動成功日志
- 通路zipkin web界面。
http://localhost:9411/
- 在服務提供方和消費方分别引入 sleuth 和 zipkin 依賴
<!-- sleuth-zipkin -->
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
- 分别配置服務提供方和消費方。
- sleuth-provider application.yaml
server:
port: 8001
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
spring:
application:
name: feign-provider
zipkin:
base-url: http://localhost:9411/ # 設定zipkin的服務端路徑
sleuth:
sampler:
probability: 1 # 采集率 預設 0.1 百分之十。
- sleuth-consumer application.yaml
server:
port: 9000
eureka:
instance:
hostname: localhost # 主機名
client:
service-url:
defaultZone: http://localhost:8761/eureka
spring:
application:
name: feign-consumer # 設定目前應用的名稱。将來會在eureka中Application顯示。将來需要使用該名稱來擷取路徑
zipkin:
base-url: http://localhost:9411/ # 設定zipkin的服務端路徑
sleuth:
sampler:
probability: 1 # 采集率 預設 0.1 百分之十。
logging:
level:
com.itheima: debug
- 啟動,測試 http://localhost:9411/
- 詳細資訊