Spring Cloud微服務實戰:整合eureka&zuul&feign&hystrix入門
Spring Cloud簡介
Spring Cloud是一個基於Spring Boot實現的微服務架構開發工具。它為微服務架構中涉及的配置管理、服務治理、斷路器、智慧路由、微代理、控制匯流排、全域性鎖、決策競選、分散式會話和叢集狀態管理等操作提供了一種簡單的開發方式。
Spring Cloud包含了多個子專案,比如Spring Cloud Config、Spring Cloud Netflix、Spring Cloud Bus、Spring Cloud Stream、Spring Cloud Zookeeper等等。
本文介紹基於Spring Boot 2.0.5版本,Spring Cloud Finchley.SR1版本的微服務搭建,包括eureka&zuul&feign&hystrix的整合。
最終專案結構
文末附原始碼地址。
服務註冊發現模組
該模組對應本次搭建專案中的cloud-eureka,eureka作為服務發現註冊中心首先搭建,因為後面的服務都要註冊到上面。當然服務發現還可以用zookeeper、consul等等,最近阿里也啟動了新的服務發現開源專案Nacos,各種服務註冊發現中介軟體真是層出不窮。
首先使用idea生成多模組maven主工程,新建一個空白標準的maven project(不要選擇Create from archetype選項)
在主工程上新建module,選擇Spring Initializr
輸入cloud-eureka服務註冊中心模組資訊
選擇Cloud Discovery中的Eureka Server依賴
生成的pom檔案部分配置:
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <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>
啟動類,加上@EnableEurekaServer註解:
@SpringBootApplication
@EnableEurekaServer
public class CloudEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(CloudEurekaApplication.class, args);
}
}
預設情況下服務註冊中心會將自己作為客戶端註冊到Eureka Server,所以需要禁用它的客戶端註冊行為,配置檔案application.properties新增如下配置:
#埠號.
server.port=8070
#關閉自我保護.
eureka.server.enable-self-preservation=false
#清理伺服器時間間隔[5s]
eureka.server.eviction-interval-timer-in-ms=5000
#主機名.
eureka.instance.hostname=localhost
#是否將自己作為客戶端註冊到Eureka Server[當前模組只是作為Eureka Server服務端所以設為false]
eureka.client.register-with-eureka=false
#是否從Eureka Server獲取註冊資訊[當前是單點的Eureka Server所以不需要同步其它節點的資料]
eureka.client.fetch-registry=false
#Eureka Server[查詢註冊服務]地址.
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
啟動工程訪問:http://localhost/8070/ ,可看到如下介面,其中還沒有服務例項
客服端模組(服務提供者)
該模組對應本次搭建專案中的cloud-provider,其作為服務提供者客戶端在註冊中心進行註冊。搭建過程和cloud-eureka類似,在主工程上新建module並選擇Spring Initializr即可,唯一區別是依賴選擇Cloud Discovery中的Eureka Discovery:
pom檔案依賴配置如下:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</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>
</dependencies>
啟動類,加上@EnableDiscoveryClient,表示其作為服務發現客戶端
@SpringBootApplication
@EnableDiscoveryClient
public class CloudProviderApplication {
public static void main(String[] args) {
SpringApplication.run(CloudProviderApplication.class, args);
}
}
application.properties新增如下配置:
#應用名稱.
spring.application.name=cloud-provider
#應用埠號.
server.port=8080
#Eureka Server伺服器地址.
eureka.client.serviceUrl.defaultZone=http://localhost:8070/eureka/
通過spring.application.name指定微服務服務提供者的名稱,後續使用該名稱便可以訪問該服務。
eureka.client.serviceUrl.defaultZone指定服務註冊中心地址。
啟動該工程,再次訪問:http://localhost/8070/ , 可以看到出現了啟動的CLOUD-PROVIDER服務:
定義MyController類,使用Rest風格請求,新增info方法如下:
@RestController
public class MyController {
@RequestMapping(value = "/info", method = RequestMethod.GET)
public String info() {
try {
//休眠2秒,測試超時服務熔斷[直接關閉服務提供者亦可]
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello, cloud-provider";
}
}
訪問:http://127.0.0.1:8080/info , 返回資訊如下
宣告式服務呼叫元件Feign及服務熔斷元件Hystrix整合
新建服務消費者模組,該模組對應本次搭建專案中的cloud-consumer。同樣,新建過程和上述模組類似,這裡不再贅述。本模組將通過Feign元件呼叫上一個模組服務的info方法,並通過Hystrix實現服務呼叫失敗時的服務熔斷。
maven依賴配置:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</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>
</dependencies>
啟動類,加上@EnableFeignClients和@EnableEurekaClient
@SpringBootApplication
@EnableFeignClients //呼叫者啟動時,開啟Feign開關
@EnableEurekaClient
public class CloudConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(CloudConsumerApplication.class, args);
}
}
application.properties新增如下配置:
#應用名稱.
spring.application.name=cloud-consumer
#埠號.
server.port=8081
#Eureka Server伺服器地址.
eureka.client.serviceUrl.defaultZone=http://localhost:8070/eureka/
#高版本spring-cloud-openfeign請求分為兩層,先ribbon控制,後hystrix控制.
#ribbon請求處理的超時時間.
ribbon.ReadTimeout=5000
#ribbon請求連線的超時時間
ribbon.ConnectionTimeout=5000
##設定服務熔斷超時時間[預設1s]
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=10000
#開啟Hystrix以支援服務熔斷[高版本預設false關閉],如果置為false,則請求超時交給ribbon控制.
#feign.hystrix.enabled=true
定義服務介面類InfoClient,作為呼叫遠端服務的本地入口:
//1.name為被呼叫的服務應用名稱.
//2.InfoFallBack作為熔斷實現,當請求cloud-provider失敗時呼叫其中的方法.
//3.feign配置.
@FeignClient(name = "cloud-provider", fallback = InfoFallBack.class, configuration = MyFeignConfig.class)
public interface InfoClient {
//被請求微服務的地址
@RequestMapping("/info")
String info();
}
定義熔斷類InfoFallBack,如果遠端服務無法成功請求,則呼叫指定的本地邏輯方法:
@Component
public class InfoFallBack implements InfoClient {
@Override
public String info() {
return "fallback info";
}
}
定義個性化的feign配置類MyFeignConfig:
@Configuration
public class MyFeignConfig {
/**
* feign列印日誌等級
* @return
*/
@Bean
Logger.Level feignLoggerLeval(){
return Logger.Level.FULL;
}
}
定義服務呼叫類ConsumerController,通過本地方法入口呼叫遠端服務:
@RestController
@Configuration
public class ConsumerController {
@Autowired
InfoClient infoClient;
@RequestMapping(value = "/consumerInfo", method = RequestMethod.GET)
public String consumerInfo(){
return infoClient.info();
}
}
啟動工程,訪問:http://127.0.0.1:8081/consumerInfo , 成功呼叫遠端服務:
服務熔斷測試,application.properties配置修改如下:
- feign.hystrix.enabled=true註釋開啟,開啟Hystrix以支援服務熔斷,這邊高版本預設為false
- 關閉cloud-provider服務或者去除ribbon請求處理超時時間及服務熔斷超時時間的配置
重新啟動cloud-consumer服務,再次訪問,服務熔斷成功呼叫了本地的方法:
服務閘道器元件Zuul整合
該元件提供了智慧路由以及訪問過濾等功能。新建服務閘道器模組cloud-zuul,過程和以上同樣類似,這裡省略。
maven依賴配置:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
啟動類,加上@EnableZuulProxy和@EnableEurekaClient註解:
@SpringBootApplication
@EnableZuulProxy //開啟閘道器Zuul
@EnableEurekaClient
public class CloudZuulApplication {
public static void main(String[] args) {
SpringApplication.run(CloudZuulApplication.class, args);
}
}
application.properties新增如下配置:
#應用名稱.
spring.application.name=cloud-zuul
#應用埠號.
server.port=8071
#Eureka Server伺服器地址.
eureka.client.serviceUrl.defaultZone=http://localhost:8070/eureka/
#通過指定URL配置了Zuul路由,則配置以下兩個超時時間.
#zuul.host.connect-timeout-millis=5000
#zuul.host.socket-timeout-millis=5000
#zuul使用服務發現的方式[通過serviceId路由服務],得配置ribbon的超時時間.
#官網文件已說明:http://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html#_zuul_timeouts
#ribbon請求處理的超時時間.
ribbon.ReadTimeout=5000
#ribbon請求連線的超時時間.
ribbon.ConnectionTimeout=5000
##設定服務熔斷超時時間[預設1s]
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=10000
#只要訪問以/api/開頭的多層目錄都可以路由到服務名為cloud-provider的服務上.
zuul.routes.cloud-provider=/api/**
注意zuul.routes.cloud-provider表示要訪問的服務以何種路徑方式路由。
定義閘道器過濾器AccessFilter,根據過濾器的不同生命週期在呼叫服務時呼叫過濾器中的方法邏輯。
/**
* 服務閘道器過濾器
*/
@Component
public class AccessFilter extends ZuulFilter {
/**
* 返回一個字串代表過濾器的型別,在zuul中定義了四種不同生命週期的過濾器型別:
* pre:可以在請求被路由之前呼叫
* route:在路由請求時候被呼叫
* post:在route和error過濾器之後被呼叫
* error:處理請求時發生錯誤時被呼叫
* @return
*/
@Override
public String filterType() {
return "pre"; //前置過濾器
}
@Override
public int filterOrder() {
return 0; //過濾器的執行順序,數字越大優先順序越低
}
@Override
public boolean shouldFilter() {
return true;//是否執行該過濾器,此處為true,說明需要過濾
}
/**
* 過濾器具體邏輯
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
System.out.println(String.format("%s demoFilter request to %s", request.getMethod(), request.getRequestURL().toString()));
String username = request.getParameter("username");// 獲取請求的引數
if(!StringUtils.isEmpty(username)&&username.equals("bright")){//當請求引數username為“bright”時通過
ctx.setSendZuulResponse(true);// 對該請求進行路由
ctx.setResponseStatusCode(200);
ctx.set("isSuccess", true);// 設值,讓下一個Filter看到上一個Filter的狀態
return null;
}else{
ctx.setSendZuulResponse(false);// 過濾該請求,不對其進行路由
ctx.setResponseStatusCode(401);// 返回錯誤碼
ctx.setResponseBody("{\"result\":\"username is not correct!\"}");// 返回錯誤內容
ctx.set("isSuccess", false);
return null;
}
}
}
啟動該工程,訪問:http://127.0.0.1:8071/api/info , 成功執行閘道器過濾器中的方法邏輯,請求被過濾,沒有呼叫遠端服務返回了設定的錯誤內容:
訪問:http://127.0.0.1:8071/api/info?username=bright ,執行閘道器過濾器中的方法邏輯,請求引數合法,所以請求沒有被過濾成功呼叫了遠端服務:
轉載自:https://www.jianshu.com/p/cab8f83b0f0e