SpringCloud學習:Eureka、Ribbon和Feign

 Talk is cheap,show me the code , 書上得來終覺淺,絕知此事要躬行。在本身真正實現的過程當中,會遇到不少莫名其妙的問題,而正是在解決這些問題的過程當中,你會發現本身以前思惟的盲點。
引子

看完《微服務設計》後,算是補上了本身在服務化這塊的理論知識,在業界,通常有兩種微服務的實踐方法:基於dubbo的微服務架構、基於Spring Cloud的微服務架構。從概念上來說,Dubbo和Spring Cloud並不能放在一塊兒對比,由於Dubbo僅僅是一個RPC框架,實現Java程序的遠程調用,實施服務化的中間件則須要本身開發;而Spring Cloud則是實施微服務的一系列套件,包括:服務註冊與發現、斷路器、服務狀態監控、配置管理、智能路由、一次性令牌、全局鎖、分佈式會話管理、集羣狀態管理等。

在有贊,咱們基於Dubbo實施服務化,剛開始是基於ZooKeeper進行服務註冊與發現,如今已經轉成使用Etcd。我此次學習Spring Cloud,則是想成體系得學習下微服務架構的實現,也許可以對基於Dubbo實施微服務架構有所借鑑。

Spring Cloud下有不少工程:

    Spring Cloud Config:依靠git倉庫實現的中心化配置管理。配置資源能夠映射到Spring的不一樣開發環境中,可是也可使用在非Spring應用中。
    Spring Cloud Netflix:不一樣的Netflix OSS組件的集合:Eureka、Hystrix、Zuul、Archaius等。
    Spring Cloud Bus:事件總線,利用分佈式消息將多個服務鏈接起來。很是適合在集羣中傳播狀態的改變事件(例如:配置變動事件)
    Spring Cloud Consul:服務發現和配置管理,由Hashicorp團隊開發。

我決定先從Spring Cloud Netflix看起,它提供了以下的功能特性:

    服務發現:Eureka-server實例做爲服務提供者,能夠註冊到服務註冊中心,Eureka客戶端能夠經過Spring管理的bean發現實例;
    服務發現:嵌套式的Eureka服務能夠經過聲明式的Java配置文件建立;
    斷路器:利用註解,能夠建立一個簡單的Hystrix客戶端;
    斷路器:經過Java配置文件能夠建立內嵌的Hystrix控制面板;
    聲明式REST客戶端:使用Feign能夠建立聲明式、模板化的HTTP客戶端;
    客戶端負載均衡器:Ribbon
    路由器和過濾器:Zuul能夠在微服務架構中提供路由功能、身份驗證、服務遷移、金絲雀發佈等功能。

本文計劃利用Eureka實現一個簡答的服務註冊於發現的例子,須要建立三個角色:服務註冊中心、服務提供者、服務消費者。
實踐
1. 服務註冊中心

在IDEA中建立一個Spring Cloud工程,引入Eureka-Server包,pom文件總體以下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example.springcloud</groupId>
    <artifactId>service-register</artifactId>
    <version>1.0-SNAPSHOT</version>


    <!-- spring boot的parent 配置文件,有大部分spring boot須要用的Jar包 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <!-- spring boot的maven打包插件 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!-- eureka-server -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>

        <!-- spring boot test-->
        <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>Camden.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

在src/main/java包下建立一個名爲hello的包,而後建立 EurekaServiceRegisterApplication 類,並用@EnableEurekaServer和@SpringBootApplication兩個註解修飾,後者是Spring Boot應用都須要用的,這裏不做過多解釋;@EnableEurekaServer註解的做用是觸發Spring Boot的自動配置機制,因爲咱們以前在pom文件中導入了eureka-server,spring boot會在容器中建立對應的bean。EurekaServiceRegisterApplication的代碼以下:

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
 * Created by IntelliJ IDEA.
 * User: duqi
 * Date: 2017/3/2
 * Time: 20:02
 */

@EnableEurekaServer //經過@EnableEurekaServer啓動一個服務註冊中心給其餘應用使用
@SpringBootApplication
public class EurekaServiceRegisterApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaServiceRegisterApplication.class, args);
    }
}

在application.properties中還須要增長以下配置,才能建立一個真正可使用的服務註冊中心。

#註冊服務的端口號
server.port=8761

#是否須要註冊到註冊中心,由於該項目自己做爲服務註冊中心,因此爲false
eureka.client.register-with-eureka=false
#是否須要從註冊中心獲取服務列表,緣由同上,爲false
eureka.client.fetch-registry=false
#註冊服務器的地址:服務提供者和服務消費者都要依賴這個地址
eureka.client.service-url.defaultZone=http://localhost:${server.port}/eureka

logging.level.com.netflix.eureka=OFF
logging.level.com.netflix.discovery=OFF

啓動註冊服務,並訪問: http://localhost:8761 ,就能夠看到以下界面。

服務註冊中心後臺
2. 服務提供者

建立一個Spring Boot工程,表明服務提供者,該服務提供者會暴露一個加法服務,接受客戶端傳來的加數和被加數,並返回二者的和。

工程的pom文件內容以下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example.springcloud</groupId>
    <artifactId>service-client</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

其中的關鍵在於spring-cloud-starter-eureka這個Jar包,其中包含了eureka的客戶端實現。

在src/main/java/hello下建立工程的主類EurekaServerProducerApplication,使用@EnableDiscoveryClient註解修飾,該註解在服務啓動的時候,能夠觸發服務註冊的過程,向配置文件中指定的服務註冊中心(Eureka-Server)的地址註冊本身提供的服務。EurekaServerProducerApplication的源碼以下:

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * Created by IntelliJ IDEA.
 * User: duqi
 * Date: 2017/3/2
 * Time: 20:34
 */
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaServerProducerApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaServerProducerApplication.class, args);
    }

}

配置文件的內容以下:

#服務提供者的名字
spring.application.name=compute-service

#服務提供者的端口號
server.port=8888

#服務註冊中心的地址
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/

服務提供者的基本框架搭好後,須要實現服務的具體內容,在ServiceInstanceRestController類中實現,它的具體代碼以下:

package hello;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * Created by IntelliJ IDEA.
 * User: duqi
 * Date: 2017/3/2
 * Time: 20:36
 */
@RestController
public class ServiceInstanceRestController {

    private static final Logger logger = LoggerFactory.getLogger(ServiceInstanceRestController.class);

    @Autowired
    private DiscoveryClient discoveryClient; //服務發現客戶端

    @GetMapping(value = "/add")
    public Integer add(@RequestParam Integer a, @RequestParam Integer b) {
        ServiceInstance instance = discoveryClient.getLocalServiceInstance();
        Integer r = a + b;
        logger.info("/add, host:" + instance.getHost() + ", service_id:" + instance.getServiceId() + ", result:" + r);
        return r;
    }
}

先啓動服務註冊中心的工程,而後再啓動服務提供者,在訪問: localhost:8761,以下圖所示,服務提供者已經註冊到服務註冊中心啦。

服務提供者註冊到服務註冊中心

在Spring Cloud Netflix中,使用Ribbon實現客戶端負載均衡,使用Feign實現聲明式HTTP客戶端調用——即寫得像本地函數調用同樣。
3. 服務消費者-Ribbon

建立一個Spring boot工程,引入ribbon和eureka,pom文件內容以下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example.springcloud</groupId>
    <artifactId>serviceconsumer</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!-- 客戶端負載均衡 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
        </dependency>

        <!-- eureka客戶端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

        <!-- spring boot實現Java Web服務-->
        <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>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

建立EurekaConsumerApplication類,定義REST客戶端實例,代碼以下:

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * Created by IntelliJ IDEA.
 * User: duqi
 * Date: 2017/3/2
 * Time: 22:55
 */
@EnableDiscoveryClient //開啓服務發現的能力
@SpringBootApplication
public class EurekaConsumerApplication {

    @Bean //定義REST客戶端,RestTemplate實例
    @LoadBalanced //開啓負債均衡的能力
    RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(EurekaConsumerApplication.class, args);
    }
}

application.properties中定義了服務註冊中心的地址、消費者服務的端口號、消費者服務的名稱這些內容:

#應用名稱
spring.application.name=ribbon-consumer

#端口號
server.port=9000

#註冊中心的地址
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/

消費者服務的入口爲:ConsumerController,咱們經過這個實例進行測試。消費者服務啓動過程當中,會從服務註冊中心中拉最新的服務列表,當瀏覽器觸發對應的請求,就會根據COMPUTE-SERVICE查找服務提供者的IP和端口號,而後發起調用。

package hello;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * Created by IntelliJ IDEA.
 * User: duqi
 * Date: 2017/3/2
 * Time: 22:58
 */
@RestController
public class ConsumerController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping(value = "/add")
    public String add() {
        return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();
    }
}

首先啓動服務註冊中心,第二分別啓動兩個服務提供者(IP相同、端口不一樣便可),而後啓動服務消費者。

兩個服務提供者

在瀏覽器裏訪問 localhost:9000/add 兩次,能夠看到請求有時候會在8888端口的服務,有時候會到8889的服務。具體背後選擇的原理,還有待後續研究。
4. 服務消費者-Feign

上一節中,使用相似 restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody() 這樣的語句進行服務間調用並不是不能夠,只是咱們在服務化的過程當中,但願跨服務調用可以看起來像本地調用,這也是我理解的Feign的使用場景。

建立一個spring boot工程,該工程的pom文件與上一節的相似,只是把ribbon的依賴換爲feign的便可,代碼以下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example.springcloud</groupId>
    <artifactId>serviceconsumer</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!-- Feign實現聲明式HTTP客戶端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>

        <!-- eureka客戶端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

        <!-- spring boot實現Java Web服務-->
        <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>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

首先建立應用程序啓動類:EurekaConsumerApplication,代碼以下:

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;


/**
 * Created by IntelliJ IDEA.
 * User: duqi
 * Date: 2017/3/19
 * Time: 16:59
 */
@EnableDiscoveryClient //用於啓動服務發現功能
@EnableFeignClients //用於啓動Fegin功能
@SpringBootApplication
public class EurekaConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaConsumerApplication.class);
    }
}

而後定義遠程調用的接口,在hello包下建立depend包,而後建立ComputeClient接口,使用@FeignClient("COMPUTE-SERVICE")註解修飾,COMPUTE-SERVICE就是服務提供者的名稱,而後定義要使用的服務,代碼以下:

package hello.depend;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * Created by IntelliJ IDEA.
 * User: duqi
 * Date: 2017/3/19
 * Time: 17:02
 */
@FeignClient("COMPUTE-SERVICE")
public interface ComputeClient {

    @RequestMapping(method = RequestMethod.GET, value = "/add")
    Integer add(@RequestParam(value = "a") Integer a, @RequestParam(value = "b") Integer b);
}

在ConsumerController中,像引入普通的spring bean同樣引入ComputeClient對象,其餘的和Ribbon的相似。

package hello;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import hello.depend.ComputeClient;

/**
 * Created by IntelliJ IDEA.
 * User: duqi
 * Date: 2017/3/19
 * Time: 17:06
 */
@RestController
public class ConsumerController {

    @Autowired
    private ComputeClient computeClient;

    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public Integer add() {
        return computeClient.add(10, 20);
    }
}

application.properties的內容以下:

#應用名稱
spring.application.name=fegin-consumer

#端口號
server.port=9000

#註冊中心的地址
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/

啓動fegin消費者,訪問 localhost:9000/add ,也能夠看到服務提供者已經收到了消費者發來的請求。

請求到達服務提供者1
java

 


請求到達服務提供者2
git

 原文轉自: http://lib.csdn.net/article/microservice/62911web

相關文章
相關標籤/搜索