1. 程式人生 > >Spring Cloud微服務實戰:整合eureka&zuul&feign&hystrix入門

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配置修改如下:

  1. feign.hystrix.enabled=true註釋開啟,開啟Hystrix以支援服務熔斷,這邊高版本預設為false
  2. 關閉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