Spring Cloud(二) Eureka

服务治理:Spring Cloud Eureka

  Spring Cloud Eureka是Spring Cloud Netflix的一部分,它是基于Netflix Eureka 做了二次的封装,主要完成微服务的服务治理功能。

服务治理

  服务治理可以说是微服务架构中的最核心和最基础的模块,它主要用来实现各个微服务实例的自动化注册和发现。

为什么需要服务治理

  随着业务的发展,系统功能越来越复杂,相应的微服务不断增加,使用静态配置的话,就会变得越来越难以维护,并且面对不断发展业务,群集规模、服务位置、服务命名规则可能发生变化。

服务注册

  在服务治理治理框架中,通常都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,将主机与端口号、版本号、通信协议等一些附加信息告知注册中心,而注册中心形成一种清单来存储这些信息。另外,服务注册中心还需要以心跳的方式来监测清单中的服务是否可用,不可用则需要从清单中剔除。

服务发现

  在服务治理框架下运转,服务间的调用不再通过具体的实例地址来实现,通过向服务名发起请求调用实现。因此,服务调用方在发起调用的时候并不知道服务实例的位置,因此,调用方需要向注册中心发出咨询服务来获取服务实例清单,以实现对具体服务实例的访问。

Eureka和Ribbon

Eureka服务端

  Eureka服务端,亦称之为服务注册中心,同其他服务注册中心一样,支持高可用配置。它依托于强一致性提供良好的服务实例可用性,可以应对多种不同的故障场景。

搭建服务注册中心
  1. 创建SprinBoot工程(略),在此,我命名为eurekaserver,添加maven依赖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <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.version>Edgware.SR4</spring-cloud.version>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </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>
  2. 通过注解EnableEurekaServer启动一个注册服务中心提供给其他应用进行对话
    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @EnableEurekaServer
    public class EurekaServerApplication {
    public static void main(String[] args) {
    SpringApplication.run(EurekaServerApplication.class, args);
    }
    }
  3. 默认配置下,该注册服务中心会将自己作为客户端来尝试注册他自己,通过配置application.yml/application.properties来增加配置来禁用它的客户端注册行为。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    server:
    port: 1111 #在同一个服务器下进行学习测试,需要修改端口
    eureka:
    instance:
    hostname: localhost
    client:
    register-with-eureka: false #设置为false,不向注册中心注册自己
    fetch-registry: false #注册中心的职责是维护服务实例,因此不需要去检索服务
    serviceUrl:
    defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  4. 通过浏览http://localhost:1111/可以查看注册中心的控制台信息
高可用注册中心

  在微服务架构的分布式环境中,我们需要充分考虑发生故障的情况,所以在生产中我们需要进行高可用部署。对于服务中心,亦是如此。
  Eureka Server设计之初就好率了高可用的问题,在Eureka的服务治理设计中,所有节点既是服务提供方,也是服务消费方,Eureka Server的高可用实际上就是将自己做为服务向其他注册中心注册自己 ,形成一组互相注册的服务注册中心,实现服务清单的互相同步,实现高可用的效果。

  1. 在以前搭建注册中心的基础上,添加application-peer1.yml和application-peer2.yml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    ##### application-peer1.yml
    server:
    port: 1111 #在同一个服务器下进行学习测试,需要修改端口
    eureka:
    instance:
    hostname: peer1
    client:
    register-with-eureka: false #设置为false,不向注册中心注册自己
    fetch-registry: false #注册中心的职责是维护服务实例,因此不需要去检索服务
    serviceUrl:
    defaultZone: http://peer2:1112/eureka/

    ##### application-peer2.yml
    server:
    port: 1112 #在同一个服务器下进行学习测试,需要修改端口
    eureka:
    instance:
    hostname: peer2
    client:
    register-with-eureka: false #设置为false,不向注册中心注册自己
    fetch-registry: false #注册中心的职责是维护服务实例,因此不需要去检索服务
    serviceUrl:
    defaultZone: http://peer1:1111/eureka/
  2. 因为在同一台服务器下操作,需要在hosts文件中添加peer1和peer2的转换,修改/etc/hosts,新增以下内容
    1
    2
    127.0.0.1 peer1
    127.0.0.1 peer2
  3. 通过spring.profiles.active属性分别启动peer1和peer2,启动指令
    1
    2
    java -jar eurekserver-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer2
    java -jar eurekserver-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer1

Eureka客户端

  Eureka客户端,主要处理服务的注册与发现,Eureka客户端向注册中心注册自身提供的服务并周期性地发送心跳来更新它的租服务租约,同时,他也能从服务段查询当前注册的服务信息并把它们缓存到本地并周期性地刷新服务状态。

注册服务提供
  1. 创建SprinBoot工程(略),在此,我命名为eurekaclient,添加maven依赖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    <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.version>Edgware.SR4</spring-cloud.version>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
    </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>

    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>
    </build>
  2. 编写/hello请求处理接口,在日志中打印出服务相关

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    @RestController
    @Log4j
    public class HelloController {

    @Resource
    private DiscoveryClient client;

    @Resource
    private Registration registration;

    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String index() {
    //getLocalServiceInstance 方法已经过时,可以采用下边的testBalance
    ServiceInstance instance = client.getLocalServiceInstance();
    log.info(
    "/test, host:" + instance.getHost() + ", service_id:" + instance.getServiceId() );
    return "From Service-A";

    }

    @RequestMapping(value = "/testBalance", method = RequestMethod.GET)
    public String testBalance(@RequestParam String param) {
    ServiceInstance instance = serviceInstance();
    String result = "/testBalance, host:port=" + instance.getUri() + ", "
    + "service_id:" + instance.getServiceId();
    log.info(result);
    return "From Service-A , " + result;
    }

    public ServiceInstance serviceInstance() {
    List<ServiceInstance> list = client.getInstances(registration.getServiceId());
    if (list != null && list.size() > 0) {
    for(ServiceInstance itm : list){
    if (itm.getPort() == 2001) {
    return itm;
    }
    }
    }
    return null;
    }
    }
  3. 在启动类上添加EnableDiscoveryClient注释,激活Eureka中的DiscoveryClient实现

    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @EnableDiscoveryClient
    public class EurekaclientApplication {
    public static void main(String[] args) {
    SpringApplication.run(EurekaclientApplication.class, args);
    }
    }
  4. 修改配置文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    server:
    port: 1211 #因为在同一台服务器下学习测试,需要修改端口
    spring:
    application:
    name: hello-service #为服务命名
    eureka:
    client:
    serviceUrl:
    defaultZone: http://localhost:1111/eureka/ # 指定注册中心的地址
  5. 通过浏览http://localhost:1111/可以查看注册中心中有hello-service,在注册中心的日志中可以发现
    1
    2018-10-12 04:07:43.379  INFO 11069 --- [nio-1111-exec-5] c.n.e.registry.AbstractInstanceRegistry  : Registered instance HELLO-SERVICE/bogon:hello-service:1211 with status UP (replication=false)
    访问http://localhost:1211/hello之后,可以在控制台中发现以下日志信息
    1
    2018-10-12 04:19:53.087  INFO 11306 --- [nio-1211-exec-1] x.z.e.controller.HelloController         : /test, host:bogon, service_id:hello-service
服务发现与消费

  服务消费者,主要完成发现服务以及服务消费。其中,发现服务由Eureka客户端完成,而服务消费则由Ribbon完成。

Ribbon

  Ribbon是一个基于HTTP和TCP的客户端负载均衡器,它可以通过客户端中配置的ribbonServerList服务端列表去轮询访问以达到负载均衡的作用;当Ribbon和Eureka联合使用,Ribbon的服务清单RibbonServerList会被DiscoveryEnableNIWSServerList重写,扩展成从Eureka注册中心来获取服务列表,同时,它也会用NIWSDiscoveryPing来代替IPing,将职责委托给Eureka来确定服务端是否已经启动。

服务发现与服务消费示例
  1. 启动之前的服务注册中心,启动两个hello-service
    1
    2
    3
    4
    5
    6
    7
    #启动注册服务中心
    nohup java -jar /opt/eureka-server/eurekserver-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer1 > /opt/log/out-eureka-server-peer1.log &
    nohup java -jar /opt/eureka-server/eurekserver-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer2 > /opt/log/out-eureka-server-peer2.log &

    #启动两个hello-service
    nohup java -jar eurekaclient-0.0.1-SNAPSHOT.jar --server.port=1201 > /opt/log/out-eureka-client-1201.log &
    nohup java -jar eurekaclient-0.0.1-SNAPSHOT.jar --server.port=1202 > /opt/log/out-eureka-client-1202.log &
  2. 创建SprinBoot工程(略),取名为sibbon-consumer,并引入以下依赖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <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.version>Edgware.SR4</spring-cloud.version>
    </properties>

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

    </dependencies>
  3. 在启动类上添加EnableDiscoveryClient注释,启动服务发现功能,编写配置类开启客户端负载均衡,编写controller实现ribbon-consumer接口
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    //项目启动类 RibbonconsumerApplication.java
    @SpringBootApplication
    @EnableDiscoveryClient
    public class RibbonconsumerApplication {
    public static void main(String[] args) {
    SpringApplication.run(RibbonconsumerApplication.class, args);
    }
    }

    //配置类 config/LoadBalanceConfig.java
    @Configuration
    public class LoadBalanceConfig {
    @Bean
    @LoadBalanced //开启客户端负载均衡
    RestTemplate restTemplate() {
    return new RestTemplate();
    }
    }

    //实现ribbon-consumer接口的 controller/ConsumerController.java
    @RestController
    public class ConsumerController {
    @Resource
    private
    RestTemplate restTemplate;

    @RequestMapping(value = "/ribbon-comsumer", method = RequestMethod.GET)
    public String helloConsumer() {
    return restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class).getBody();
    }
    }

  4. 修改配置文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    server:
    port: 1301
    spring:
    application:
    name: ribbon-consumer
    eureka:
    client:
    serviceUrl:
    defaultZone: http://localhost:1111/eureka/
  5. 启动项目,查看http://localhost:1301/ribbon-consumer,通过两个hello-service的日志可以看到确实是轮询实现的负载均衡

项目代码地址

Github