Spring Cloud微服務實戰:手把手帶你整合eureka&zuul&feign&hystrix(附源碼)

Spring Cloud微服務實戰:手把手帶你整合eureka&zuul&feign&hystrix(附源碼)

Spring Cloud簡介

Spring Cloud是一個基於Spring Boot實現的微服務架構開發工具。它爲微服務架構中涉及的配置管理、服務治理、斷路器、智能路由、微代理、控制總線、全局鎖、決策競選、分佈式會話和集羣狀態管理等操做提供了一種簡單的開發方式。html

Spring Cloud包含了多個子項目,好比Spring Cloud Config、Spring Cloud Netflix、Spring Cloud Bus、Spring Cloud Stream、Spring Cloud Zookeeper等等。java

本文介紹基於Spring Boot 2.0.5版本,Spring Cloud Finchley.SR1版本的微服務搭建,包括eureka&zuul&feign&hystrix的整合。git

最終項目結構

項目結構.png

文末附源碼地址。github

服務註冊發現模塊

該模塊對應本次搭建項目中的cloud-eureka,eureka做爲服務發現註冊中心首先搭建,由於後面的服務都要註冊到上面。固然服務發現還能夠用zookeeper、consul等等,最近阿里也啓動了新的服務發現開源項目Nacos,各類服務註冊發現中間件真是層出不窮。web

首先使用idea生成多模塊maven主工程,新建一個空白標準的maven project(不要選擇Create from archetype選項)
spring

多模塊maven主工程.png

在主工程上新建module,選擇Spring Initializr
後端

idea構建spring boot初始化模塊.png

輸入cloud-eureka服務註冊中心模塊信息
api

新建eureka服務註冊中心模塊.png

選擇Cloud Discovery中的Eureka Server依賴
bash

eureka服務註冊中心依賴選擇.png

生成的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/ ,可看到以下界面,其中尚未服務實例

spring boot控制檯無服務實例.png

客服端模塊(服務提供者)

該模塊對應本次搭建項目中的cloud-provider,其做爲服務提供者客戶端在註冊中心進行註冊。搭建過程和cloud-eureka相似,在主工程上新建module並選擇Spring Initializr便可,惟一區別是依賴選擇Cloud Discovery中的Eureka Discovery:

服務註冊客戶端依賴選擇.png

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服務:

服務註冊中心客戶端實例.png

定義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 , 返回信息以下

cloud-provider REST請求.jpg

聲明式服務調用組件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 , 成功調用遠程服務:

經過feign成功調用遠程服務.jpg

服務熔斷測試,application.properties配置修改以下:

  1. feign.hystrix.enabled=true註釋打開,開啓Hystrix以支持服務熔斷,這邊高版本默認爲false
  2. 關閉cloud-provider服務或者去除ribbon請求處理超時時間及服務熔斷超時時間的配置

從新啓動cloud-consumer服務,再次訪問,服務熔斷成功調用了本地的方法:

服務成功熔斷.jpg

服務網關組件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 , 成功執行網關過濾器中的方法邏輯,請求被過濾,沒有調用遠程服務返回了設置的錯誤內容:

zuul服務被過濾.jpg

訪問:http://127.0.0.1:8071/api/info?username=bright ,執行網關過濾器中的方法邏輯,請求參數合法,因此請求沒有被過濾成功調用了遠程服務:

網關過濾合法.jpg

項目源碼

歡迎微信掃碼關注微信公衆號:後端開發者中心,不按期推送服務端各種技術文章。

相關文章
相關標籤/搜索