spring-cloud中eureka进行服务治理

Posted 不去天涯

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring-cloud中eureka进行服务治理相关的知识,希望对你有一定的参考价值。

spring-cloud其他文章:
我们有必要花时间了解spring boot吗?
spring boot自动化配置原理
spring boot集成ActiveMQ

eureka

eureka是netflix开源的服务治理中间件,可以类比dubbo、thrift,在后端服务中一般出现在上游服务和下游服务之间,作用就是服务注册和发现。

一般是图中这种样式。eureka、dubbo、thrift都是作为注册中心出。图中的下游服务是服务的提供者,上游服务是服务的消费者,下游服务从注册中心获取下游服务列表后,直接向下游服务发出调用请求。

这里我们看到,注册中心起到登记服务的作用,所有的请求流量都没有经过注册中心。上下游服务之间的黑线是双向的。

来对比一下使用lvs作为中间负载工具时的场景:

上游服务的请求先经过lvs负载,路由到某一台下游服务,下游服务将结果直接返回给上游服务(非NAT模式)。

也就是说请求流量会多经过一层传输。

那么如果是NAT模式,或者我们用nginx做负载均衡会怎么样的场景呢?

负载均衡完全变成了一个中间桥梁,不管是请求流量,还是响应流量都必须经过中间的负载均衡服务传递。

eureka有什么不一样?

dubbo我们知道是基于RPC的,也就是说服务提供者开放出来的是RPC接口,上游服务也需要使用RPC向下游服务请求。

RPC能够在请求端直接把返回结果映射到对象实例,不需要我们再自己对相应字符串进行解析,而是直接使用。然而也正是这一点,上游服务必须要有下游服务的接口层的东西(接口和模型类),导致上下游之间有比较紧密的相关性,也就是耦合。

所以,现在大家比较倾向于使用Http的协议来做接口。也就是Dubbox要做的工作,Dubbox对Dubbo进行了扩展实现,增加了http协议的支持。

eureka采用的是http协议,没有其他协议的支持。总体来说,eureka比dubbo要轻量,能够支持的实现也相对较少,没有dubbo可配置的权重、路由、单实例禁用等的支持。

当然eureka也有自己的路由规则,能够做到HA和跨机房的容灾处理,这个留到后边说。

eureka使用的是自己的服务作为注册中心,dubbo使用的是zookeeper做注册中心。相对来说,仍然是dubbo偏重量级,eureka偏轻量级。

注册中心部署

eureka的注册中心可以直接从官网下载,也可以自己实现。这里使用自己实现的spring-boot工程。

pom.xml如下:

<?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>com.dangdang</groupId>
    <artifactId>eureka-register</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka-register</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <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>Dalston.SR4</spring-cloud.version>
    </properties>

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

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


</project>

application.yml

spring:
  profiles: eureka1
  application:
    name: eureka-register
server:
  port: 8081
eureka:
  datacenter: beijing1
  instance:
    hostname: eureka1
  client:
    serviceUrl:
      defaultZone: http://localhost:8082/eureka/
---
spring:
  profiles: eureka2
  application:
    name: eureka-register
server:
  port: 8082
eureka:
  datacenter: beijing1
  instance:
    hostname: eureka2
  client:
    serviceUrl:
      defaultZone: http://localhost:8081/eureka/

EurekaRegisterApplication.java


package com.dangdang.eurekaregister;

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

@SpringBootApplication
@EnableEurekaServer
public class EurekaRegisterApplication 

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

打包之后,使用如下命令启动即可:
启动注册中心1:

java -jar eureka-register-0.0.1-SNAPSHOT.jar --spring.profiles.active=eureka1

启动注册中心2:

java -jar eureka-register-0.0.1-SNAPSHOT.jar --spring.profiles.active=eureka2

两注册中心的信息会互相备份,也就是在任何一个注册中心都会包含在两个注册中心注册的所有服务提供者和服务调用者。

服务提供者实现

注册中心使用的是@EnableEurekaServer,服务提供者相对于eureka来说是一个客户端,所以需要使用@EnableEurekaClient。一块看一下服务提供者的实现:

pom.xml

<?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>com.dangdang</groupId>
    <artifactId>eureka-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka-server</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <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>Dalston.SR4</spring-cloud.version>
    </properties>

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

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


</project>

application.yml

spring:
  profiles: eureka1
  application:
    name: service-hi
server:
  port: 8181
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8081/eureka/

---
spring:
  profiles: eureka2
  application:
    name: service-hi
server:
  port: 8182
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8082/eureka/

EurekaServerApplication.java

package com.dangdang.eurekaserver;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@EnableEurekaClient
@RestController
public class EurekaServerApplication 

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

    @Value("$server.port")
    String port;
    @RequestMapping("/hi")
    public String home(@RequestParam String name) 
        return "hi "+name+",i am from port:" +port;
    

启动方式和注册中心启动类似。

服务调用者实现

服务调用者可以使用netflix的ribbon也可以使用feign,这里使用ribbon。

pom.xml

<?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>com.dangdang</groupId>
    <artifactId>eureka-consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka-consumer</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <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>Dalston.SR4</spring-cloud.version>
    </properties>

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

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


</project>

application.yml,注意其中的defaultZone,需要把多个注册中心用逗号分隔都写进来。

defaultZone如果要做到eureka注册中心的高可用,必须把多个注册中心写在这里,否则一旦指定的一个注册中心宕机会导致服务的上下线均无法感知。当然了,这时候又会eureka服务调用者仍然会使用自身缓存的服务提供者列表进行服务调用,所以不会导致整个服务不可用。

eureka:
  client:
    region: beijing1
    preferSameZoneEureka: true
    serviceUrl:
      defaultZone: http://localhost:8081/eureka/,http://localhost:8082/eureka/
server:
  port: 8281
spring:
  application:
    name: eureka-consumer

HelloService.java

package com.dangdang.eurekaconsumer.com.dangdang.eureka.consumer.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class HelloService 

    @Autowired
    RestTemplate restTemplate;

    public String hiService(String name) 
        return restTemplate.getForObject("http://SERVICE-HI/hi?name="+name,String.class);
    

HelloController.java

package com.dangdang.eurekaconsumer.controller;

import com.dangdang.eurekaconsumer.com.dangdang.eureka.consumer.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloControler 
    @Autowired
    HelloService helloService;
    @RequestMapping(value = "/hi")
    public String hi(@RequestParam String name)
        return helloService.hiService(name);
    

EurekaConsumerApplication.java

package com.dangdang.eurekaconsumer;

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;

@SpringBootApplication
@EnableDiscoveryClient
public class EurekaConsumerApplication 

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

    @Bean
    @LoadBalanced
    RestTemplate restTemplate() 
        return new RestTemplate();
    

这里使用了RestTemplate作为http接口的调用工具,RestTemplate使用Ribbon作为负载均衡器。

多注册中心架构解析

这是eureka官方给的一张架构图,我们来剖析一下:

先看第一排的4个方块,绿色的代表服务提供者,直接注册到了us-east-1c对应的蓝色方块,这是一个eureka Server也就是注册中心。

3个蓝色方块之间是双向的箭头,表示注册中心之间的信息会互相备份,到达的效果就是互相连接的多个注册中心具有其他注册中心上注册的服务提供者和调用者。

这里有一个问题,图中的3个注册中心的注册信息完全一样吗?

其实是不一样的,这是eureka注册中心的一个特性决定的。即,注册信息不会垮中心传递,仅传递给临近的注册中心。也就是us-east-1d中包含图中的两个Application-Service,us-east-1c中只包含左侧的Application-Servicce,us-east-1e仅包含右下角的Application-Service。

这个可以从图中左下角的两个Client的make remote call箭头看出来。

我们把官方的图缩减一下,就成了最简单的注册中心HA方案:

zone的使用

eureka的spring-cloud.html的官方文档上也有提到,eureka有一个特性是优先调用相同zone下的服务,当自己的zone下服务不可用时,自动容灾到其他的zone,也就是调用其他zone的服务,这个东西怎么实现呢?

我们只需要在Application Client的配置上做文章就可以了:

eureka:
  client:
    region: beijing1
    preferSameZoneEureka: true
    serviceUrl:
      shunyi: http://eureka1:8081/eureka/
      changping: http://eureka1:8082/eureka/
    availability-zones:
      beijing1: changping,shunyi

如上配置,eureka会把changping作为客户端自己的zone,然后有限从changping对应的注册中心上的服务作为目标服务调用,也就是使用http://eureka1:8082/eureka/上的服务,但changping的注册中心挂掉之后,会重新把自己注册到shunyi的注册中心,调用shunyi注册中心的服务。

如果我们把availability-zones:配置的顺序改成beijing1: shunyi,changping那么客户端会优先使用shunyi区的服务提供者。

以上是关于spring-cloud中eureka进行服务治理的主要内容,如果未能解决你的问题,请参考以下文章

Spring Cloud 学习总结001-服务治理-Eureka

Spring-cloud之Eureka服务搭建集群

Spring-cloud微服务实战:eureka注册中心(下)

Spring-Cloud Eureka集群配置

Spring-Cloud Eureka集群配置

spring-cloud: eureka之:ribbon负载均衡配置