Eureka

1,172 阅读13分钟

一、前言

Spring Cloud 是一系列框架的有序集合。它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 Spring Boot 的开发风格做到一键启动和部署。

SpringCloud体系介绍:官方地址

二、简单介绍

Eureka是Netflix的一个子模块,也是核心模块之一。Eureka是一个基于 REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移。

服务注册和发现对于微服务架构来说是非常重要的。有了服务发现和注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件。该功能类似于 Dubbo 的注册中心,比如 Zookeeper。

Eureka 采用了C-S的设计架构。Eureka Server作为服务注册功能的服务端,它是服务注册中心。而系统中的其他微服务,使用Eureka的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。

其运行原理如下图:

由图可知,Eureka 的运行原理和 Dubbo 大同小异, Eureka 包含两个组件:Eureka Server和Eureka Client。

  • Eureka Server 提供服务注册服务。各个服务节点启动后会在 Eureka Server中进行注册,Eureka Server 中的服务注册表会存储所有可用的服务节点的信息,服务节点的信息可以在界面中直观的看到。

  • Eureka Client 是一个Java 客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询负载算法的负载均衡器。在应用启动后,会向Eureka Server发送心跳(默认周期30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server会从服务注册表中把这个服务节点移除(默认90s)。

Eureka三大角色:

  • Eureka Server:提供服务注册和发现
  • Service Provider:服务提供方将自身服务注册到Eureka,从而使服务消费方能够找到该服务
  • Service Consumer:服务消费方从Eureka获取注册服务列表,从而能够消费服务

Eureka 2.x闭源后选择: 参考一 参考二

三、什么是微服务的注册中心

注册中心:是微服务架构中的“通讯录”,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到注册中心,当一个服务需要调用其它服务时,就会到注册中心里去找服务的地址,然后进行调用。核心是通过服务注册表管理服务,通过心跳机制动态维护服务

服务提供者provider: 服务启动的时候会向注册中心上报自己的网络信息

服务消费者consumer: 服务启动的时候会向注册中心上报自己的网络信息,拉取服务提供者provider的相关网络信息

为什么要用注册中心:微服务应用和机器越来越多,调用方需要知道接口的网络地址,如果靠配置文件的方式去控制网络地址,对于动态新增机器,维护带来很大问题

主流的注册中心:zookeeper、Eureka、consul、etcd 等

四、CAP理论

CAP定理:指的是在一个分布式系统中,Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性),三者不可同时获得。

  • 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(所有节点在同一时间的数据完全一致,越多节点,数据同步越耗时)
  • 可用性(A):负载过大后,集群整体是否还能响应客户端的读写请求。(服务一直可用,而且是正常响应时间)
  • 分区容错性(P):分区容忍性,就是高可用性,一个节点崩了,并不影响其它的节点(100个节点,挂了几个,不影响服务,越多机器越好)

CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容错性是我们必须需要实现的。所以我们只能在一致性和可用性之间进行权衡

五、分布式系统CAP原理常见面试题和注册中心的选择

  • CA 满足的情况下,P不能满足的原因:数据同步(C)需要时间,也要正常的时间内响应(A),那么机器数量就要少,所以P就不满足
  • CP 满足的情况下,A不能满足的原因:数据同步(C)需要时间,机器数量多(P),但是同步数据需要时间,所以不能再正常时间内响应,所以A就不满足
  • AP 满足的情况下,C不能满足的原因:机器数量多(P),正常的时间内响应(A),那么数据就不能及时同步到其他节点,所以C不满足

注册中心选择:

  • Zookeeper:CP设计,保证了一致性,集群搭建的时候,某个节点失效,则会进行选举行的leader,或者半数以上节点不可用,则无法提供服务,因此可用性没法满足
  • Eureka:AP原则,无主从节点,一个节点挂了,自动切换其他节点可以使用,去中心化

结论:分布式系统中P,肯定要满足,所以只能在CA中二选一

没有最好的选择,最好的选择是根据业务场景来进行架构设计
如果要求一致性,则选择zookeeper,如金融行业
如果要去可用性,则Eureka,如电商系统

六、搭建注册中心(Eureka 服务端)

官方文档

maven仓库地址配置

6.1、添加依赖

父工程依赖
<packaging>pom</packaging>

<modules>
    <module>../microservicecloud-api</module>
    <module>../microservicecloud-eureka-7001</module>
    <module>../microservicecloud-provider-dept-8001</module>
</modules>

<properties>
    <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
    <spring-boot.version>2.1.0.RELEASE</spring-boot.version>
    <mybatis.version>1.3.0</mybatis.version>
    <lombok.version>1.16.18</lombok.version>
    <druid.version>1.1.10</druid.version>
</properties>

<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>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>${druid.version}</version>
            </dependency>
    </dependencies>
</dependencyManagement>
<build>
    <finalName>microservicecloud</finalName>
    <resources>
        <resource>
            <!-- 允许访问src/main/resources下的内容 -->
            <directory>src/main/resources</directory>
            <!-- 过滤开启 -->
            <filtering>true</filtering>
        </resource>
    </resources>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-resources-plugin</artifactId>
            <configuration>
                <delimiters>
                    <!-- 在$符号开始$符号结尾的src/main/resources下的内容都可访问 -->
                    <delimit>$</delimit>
                </delimiters>
            </configuration>
        </plugin>
    </plugins>
</build>
公共工程依赖
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${lombok.version}</version>
</dependency>
公共依赖实体类
@AllArgsConstructor
@NoArgsConstructor
@Data
@Accessors(chain = true)
@SuppressWarnings("serial")
public class Dept implements Serializable {
    private static final long serialVersionUID = 8003851260429627546L;
    private Long deptno;
    private String dname;
    private String db_source;

    public Dept(String dname){
        super();
        this.dname=dname;
    }
}
Eureka服务注册端依赖
<!-- Eureka服务端 -->
<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>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>springloaded</artifactId>
    <version>1.2.7.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
</dependency>
注意:Spring Boot 与 SpringCloud 有版本兼容关系,如果引用版本不对应,项目启动会报错。

6.2、application.yml参数配置

server:
 port: 7001
eureka:
 instance:
   # eureka服务端的实例名称
   hostname: 127.0.0.1     
 client:
   # 声明自己是个服务端
   register-with-eureka: false     # false表示不向注册中心注册自己
   fetch-registry: false       # 是否检索服务,false表示自己端就是注册中心,不检索服务
   serviceUrl:
     defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 注册中心访问地址
 #server:
   #enable-self-preservation: false     # 关闭自我保护机制,不推荐
   #eviction-interval-timer-in-ms: 5000     # 每隔5s扫描服务列表,移除失效服务

6.3、开启注册中心功能

在启动类上添加 @EnableEurekaServer注解。

@SpringBootApplication
@EnableEurekaServer//todo Eureka服务器端启动类,接受其它微服务注册进来
public class EurekaServer7001_App {
   public static void main(String[] args) {
       SpringApplication.run(EurekaServer7001_App.class,args);
   }
}

准备工作完成,启动项目完成后,浏览器访问 http://127.0.0.1:7001 ,查看Eureka 服务监控界面,如下图:

通过该网址可以查看注册中心注册服务的相关信息。当前还没有服务注册,因此没有服务信息。

注意:http://localhost:7001 是Eureka监管界面访问地址,而http://localhost:7001/eureka/Eureka注册服务的地址

七、实战演练

官方文档

了解Eureka的环境搭建后,我们需要进行实战直观的感受Eureka 的真正作用,这样才能清楚掌握和学习Eureka

7.1、服务提供者(Eureka客户端)

1、添加依赖
<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>
</dependency>
<dependency>
    <groupId>com.bilemon.springcloud</groupId>
    <artifactId>microservicecloud-api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

<!-- 热部署依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>springloaded</artifactId>
    <version>1.2.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
</dependency>

<!-- mybatis依赖 -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>

<!-- mysql数据库依赖 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

<!-- druid连接池依赖 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
</dependency>

<!-- Eureka客户端 -->
<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-actuator</artifactId>
</dependency>

<!-- 分布式配置中心组件 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
2、配置参数
server:
  port: 8001
  
mybatis:
  config-location: classpath:mybatis/mybatis-config.xml  # mybatis配置文件所在路径
  type-aliases-package: com.bjlemon.springcloud.entities  # 所有Entity所在包
  mapper-locations:
  - classpath:mybatis/mapper/**/*.xml # mapper映射文件的位置

spring:
  application:
    name: microservicecloud-dept # 服务的名称
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource  # 当前数据源操作类型
    driver-class-name: com.mysql.cj.jdbc.Driver  # mysql驱动包
    url: jdbc:mysql://localhost:3306/cloudDB01?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=utf-8  # 数据库名称
    username: root
    password: root
    dbcp2:
      min-idle: 5  # 数据库连接池的最小维持连接数
      initial-size: 5  # 初始化连接数
      max-total: 5  # 最大连接数
      max-wait-millis: 200  # 等待连接获取的最大超时时间

#指定注册中心地址
eureka:
  client:  
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka/ # 客户端注册进eureka服务列表内,注册中心访问地址
  instance:
    instance-id: microservicecloud-dept8001   # 自定义服务名称信息
    prefer-ip-address: true   # 访问路径可以显示IP地址

# 服务名称信息链接访问的内容,需要在父工程配置访问权限
info:
  app.name: bjlemon-microservicecloud
  company.name: www.bjlemon.com
  build.artifactId: ${project.artifactId}
  build.version: ${project.version}
注意:http://localhost:7001/eureka/ 就是第六节中配置的注册中心的地址。
3、服务接口
public interface DeptService {
    boolean add(Dept dept);
    Dept get(Long id);
    List<Dept> list();
}
4、Controller层
@RestController
public class DeptController {
    @Autowired
    private DeptService deptService;

    @RequestMapping(value="/dept/add",method = RequestMethod.POST)
    public boolean add(@RequestBody Dept dept){
        return deptService.add(dept);
    }

    @RequestMapping(value="/dept/get/{id}",method = RequestMethod.GET)
    public Dept get(@PathVariable("id")Long id){
        return deptService.get(id);
    }

    @RequestMapping(value="/dept/list",method = RequestMethod.GET)
    public List<Dept> list(){
        return deptService.list();
    }
}
注意:该 controller 是给web使用的内部服务,不是给浏览器端调用的。
5、开启服务注册功能

在启动类上添加 @EnableEurekaClient 注解(也可以不加自动注册)

@SpringBootApplication
@EnableEurekaClient //本服务启动后会自动注册进eureka server中
public class DeptProvider8001_App {
    public static void main(String[] args) {
        SpringApplication.run(DeptProvider8001_App.class,args);
    }
}

启动项目完成后,浏览器访问 http://localhost:7001 查看Eureka服务监控界面 ,如下图:

从图可知,dept相关服务信息已经注册到Eureka服务中了。

注意:Eureka默认是开启保护模式的。当 EurekaServer 在短时间内丢失过多客户端时(可能发生了网络故障),Eureka Server 将进入自我保护模式。进入该模式后,EurekaServer 会保护服务注册表中的信息不被删除。当网络故障恢复后,EurekaServer 会自动退出自我保护模式

7.2、Eureka服务消费者,与浏览器端交互(Eureka客户端)

1、添加依赖
<dependency>
    <groupId>com.bilemon.springcloud</groupId>
    <artifactId>microservicecloud-api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>springloaded</artifactId>
    <version>1.2.7.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
</dependency>

<!-- Eureka客户端 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2、配置参数
server:
  port: 80

eureka:
  client:
    register-with-eureka: false  # 不向注册中心注册自己
    fetch-registry: true  # 是否检索服务,默认就是true检索
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka/  #注册中心访问地址
4、配置信息
@Configuration
public class ConfigRest {
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}
5、Controller层
@RestController
public class DeptControllerConsumer {

    private String uri="http://localhost:8001";

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient client;

    @PostConstruct
    public void initUri(){
        List<ServiceInstance> list=this.client.getInstances("MICROSERVICECLOUD-DEPT");
        if(list!=null && list.size()>0){
            for(ServiceInstance serviceInstance:list){
                System.out.println(serviceInstance.getUri());
                System.out.println(serviceInstance.getUri()!=null);
                System.out.println("".equals(serviceInstance.getUri()));
                if(serviceInstance.getUri()!=null && !"".equals(serviceInstance.getUri())){
                    uri=serviceInstance.getUri().toString();
                    break;
                }
            }
        }
    }

    @RequestMapping(value = "/consumer/dept/add")
    public boolean add(Dept dept){
        return restTemplate.postForObject(uri+"/dept/add",dept,Boolean.class);
    }

    @RequestMapping(value = "/consumer/dept/get/{id}")
    public Dept get(@PathVariable("id")Long id){
        return restTemplate.getForObject(uri+"/dept/get/"+id, Dept.class);
    }

    @RequestMapping(value = "/consumer/dept/list")
    public List<Dept> list(){
        return restTemplate.getForObject(uri+"/dept/list",List.class);
    }


6、开启服务发现功能

在启动类上添加 @EnableDiscoveryClient 注解

@SpringBootApplication
@EnableEurekaClient
public class DeptConsumer80_App {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumer80_App.class,args);
    }
}

使用浏览器访问http://192.168.30.25/consumer/dept/list,运行结果如下:

至此,Eureka 的服务注册和发现演示完毕。

八、Eureka 集群

Eureka 作为注册中心,保存了系统服务的相关信息,如果注册中心挂掉,那么系统就瘫痪了。因此,对 Eureka 做集群实现高可用是必不可少的。

本次测试使用一台机器部署Eureka集群,通过名字和端口区分不同的eureka服务。

1、由于使用一台机器做集群,需要修改 C:\Windows\System32\drivers\etc 下的 host 文件,添加如下配置:

127.0.0.1	eureka7001.com
127.0.0.1	eureka7002.com
127.0.0.1	eureka7003.com

2、application.yml文件需要进行如下修改:

server:
  port: 7001

eureka:
  instance:
    #hostname: 127.0.0.1   # 单机hostname配置,eureka服务端的实例名称
    hostname: eureka7001.com   # 集群hostname配置,eureka服务端的实例名称
  client:
    register-with-eureka: false   # todo false表示不向注册中心注册自己
    fetch-registry: false   # todo false表示自己端就是注册中心
    service-url:
      #defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/    # 单机defaultZone配置
      defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ # 集群配置
  #server:
    #enable-self-preservation: false   # 关闭自我保护机制
    #eviction-interval-timer-in-ms: 5000   # 每隔5s扫描服务列表,移除失效服务

三个Eureka服务实例的配置文件修改方式类似,将名称和端口以及注册中心地址进行修改即可。

3、服务提供者将 eureka.client.service-url.defaultZone 改成集群的 url 即可。

eureka:
  client:    
    service-url:
      #defaultZone: http://127.0.0.1:7001/eureka/
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/  # 客户端注册进eureka服务列表内

启动效果如下图: