Eureka注册中心

发布于 2022-04-12  2.85k 次阅读


一,服务注册中心概述

一,注册中心简介

  注册中心类似于微服务架构中的"通讯录",它记录了服务和服务地址的映射关系,在分布式架构中,服务会注册到这里,当服务需要调用其他服务时,就到注册中心找服务的地址,进行调用 

我们在手机的通讯录的两个使用场景:① 服务注册  ② 服务发现

  • 当我想给法外狂徒张三打电话时,那我需要在通讯录中按照张三的名字为检索寻找张三,然后就可以找到他的电话号码拨打电话  -----  服务的发现
  • 李四办了个手机号码发微信告诉我,我把李四的电话号码存击通讯录,后续我就可以从通讯录找到他  ----服务的注册

二,常见的注册中心

注册中心:

  • NetFlix Eureka
  • Alibaba Nacos
  • HashiCorp Consul
  • Apache Zookeeper
  • CoreOS Etcd
  • CNCF CoreDNS

三,需要注册中心的原因和其需要解决的问题

在分布式系统中,开发者不仅仅只是需要注册中心找到服务和服务地址的映射关系这么简单,它还需要解决更多复杂的问题:

  1. 服务注册后,如何被及时发现
  2. 服务宕机后,如何及时下线
  3. 服务如何有效的水平拓展
  4. 服务发现时,如何进行路由
  5. 服务异常时,如何进行降级
  6. 注册中心如何实现自身的高可用

   这些问题的解决都依赖于服务注册中心,简单的看,服务注册中心的功能类似与DNS服务器,你输入域名它帮你定位到具体的服务器并返回数据,注册中心作为微服务的基础组件,可能要更加复杂,也需要更多的灵活性和时效性

做为注册中心主要解决的问题:

  • 服务的管理
  • 服务依赖关系的管理

二,Eureka概述

Spring Cloud封装了Netfix公司开发的Eureka模块来实现服务治理

   在传统的RPC远程调用框架中,管理每一个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务与服务之间的依赖关系,可以实现服务调用,负载均衡,容错等,实现服务发现与注册

服务治理:就是包含服务发现,服务发现,服务调用等一系列策略的集合

一,Eureke的服务注册和发现

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

在服务注册与发现中,有一个注册中心,当服务启动的时候,会把当前自己的服务器信息 比如:服务地址,通信地址等以别名的方式注册到注册中心上,另一方(消费者|服务提供者),以别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用或RPC远程调用

二,Eureka的两大组件

两大组件:

  1. Eureka Server:提供服务的注册和发现
  2. Eureka Client:提供注册中心进行访问(远程调用)

1,Eureka Server

   各个微服务节点通过配置启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表将存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到

2,Eureka Client

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

三,Eureka服务端的安装和客户端的使用

一,Eureka服务端的安装

Eureka服务端内嵌于SpringBoot,依赖SpringBoot进行构建,它不同于Zookeeper后者有独立的服务端,需要linux单独安装

安装步骤:

  1. 新建一个SpringBoot项目(模块)
  2. 导入相关的Maven
  3. 配置application.yml文件(服务地址,本身是否注册等)
  4. 在启动类上标注@EnableEurekaServer表示为服务端
  5. 通过URL访问Eureka服务端

父工程依赖:

<?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</groupId>
    <artifactId>SpringCloud-Dome</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <junit.version>4.12</junit.version>
        <lombok.version>1.18.10</lombok.version>
        <log4j.version>1.2.17</log4j.version>
        <mysql.version>5.1.31</mysql.version>
        <druid.version>1.1.16</druid.version>
        <mybatis.spring.boot.version>2.1.1</mybatis.spring.boot.version>
    </properties>
    <dependencyManagement>
        <dependencies>
          <!--dependencies代表父工程-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.2.2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.1.0.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

① 新建SpringBoot模块省略

② 导入Maven依赖

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

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-actuator</artifactId>
    </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-server</artifactId>
    </dependency>
</dependencies>

③ 配置application.yml文件

server:
  port: 9090
eureka:
  instance:
    hostname: localhost #eureka实例的名称
  client:
    register-with-eureka: false #flase表示不向注册中心注册自己(服务端不用注册)
    fetch-registry: false #flase表示自己就是注册中心,我的职责是维护实例,并不需要检索实例
    service-url: #设置与Eureka Server交互和访问的地址,之后服务注册和调用都需要这个地址
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

④ 在启动类上标注@EnableEurekaServer

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

⑤ 通过URL访问Eureka服务端

二,client服务驻入Eureka服务端

 Eureka Server作为服务器需要单独安装运行,而Client是嵌入到具体的项目模块中的,无论是生产者还是消费者都需要Eureka Client跟Eureka Server通从而实现服务注册,服务发现,调用

步骤:

  1. 新建一个SpringBoot的业务模块
  2. 导入相关的Eureka Client依赖
  3. 配置application.yml文件(Eureka服务端的地址等)
  4. 在启动类上标注@EnableEurekaClient表示为客户端
  5. 通过URL访问Eureka服务端看是否有该Client端

父工程和Eureka服务端安装是一样的(省略)

① SpringBoot业务模块省略

② 导入相关的Eureka Client依赖

<dependencies>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>${cloud-springbootweb}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-actuator</artifactId>
        <version>${cloud-springbootactuator}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <version>${cloud-devotools}</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${cloud-lombok}</version>
        <scope>provided</scope>
    </dependency>
  
    <!--Eureka客户端依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        <version>3.0.0</version>
    </dependency>

</dependencies>

③ 配置application.yml文件

server:
  port: 8080
eureka:
  client:
    fetch-registry: true #是否抓取已有的注册信息,默认为true,单节点无所谓,集群时必须设置为true才能配合Ribbon使用负载均衡
    register-with-eureka: true #是否将自己注册进Eureka Server
    service-url:
      defaultZone: http://localhost:9090/eureka #Eureka的地址

④ 启动类上标注@EnableEurekaClient

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

⑤ 测试是否成功

四,Eureka集群搭建和使用

Eureka集群的作用:实现高可用和避免单点故障

Eureka集群顾名思义就是有多台Eureka Server,它们组成一个Eureka整体对外只暴露一个集群地址

Eureka搭建集群的方法:Server相互注册,通过service-url:defaultZone保存连接

演示:新建两个服务一个cloud-eureka-server1和cloud-eureka-server2

由于是单机演示不能用两个IP,所以修改本地hosts文件进行映射

127.0.0.1 eureka1.com
127.0.0.1 eureka2.com
服务 cloud-eureka-server1 cloud-eureka-server2
IP eureka1.com eureka2.com
端口 9090 9091

1,cloud-eureka-server1的配置(其他和单机部署一样,省略)

server:
  port: 9090
eureka:
  instance:
    hostname: eureka1.com #eureka实例的名称
  client:
    register-with-eureka: false 
    fetch-registry: false 
    service-url: 
      defaultZone: http://qureka.com:9091/eureka/#绑定cloud-eureka-server2

2,cloud-eureka-server2的配置

server:
  port: 9091
eureka:
  instance:
    hostname: eureka2.com 
  client:
    register-with-eureka: false 
    fetch-registry: false 
    service-url:
      defaultZone: http://eureka1.com:9090/eureka/ #相互绑定

测试:

① 访问cloud-eureka-server1的eureka1.com:9090

② 访问cloud-eureka-server2的eureka2.com:9091

二,多个Eureka Client构成服务集群

之前是Eureka服务端搭建集群,其实消费(客户)端也可以组成一个集群对外提供服务

前期准备:新建三个Eureka Client模块一个订单模块,两个支付模块

模块名称
cloud-consumer-order
cloud-payment-server1
cloud-payment-server2
端口
8081
8080
8082
client服务名称
cloud-consumer-server
cloud-payment-server
cloud-payment-server

一,cloud-consumer-order模块的搭建

① pom依赖

<properties>
    <cloud-springbootweb>2.5.5</cloud-springbootweb>
    <cloud-springbootactuator>2.5.5</cloud-springbootactuator>
    <cloud-devotools>2.5.5</cloud-devotools>
    <cloud-lombok>1.18.2</cloud-lombok>
</properties>

<dependencies>
    <dependency>
        <groupId>com.cloud.commons</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>${project.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>${cloud-springbootweb}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-actuator</artifactId>
        <version>${cloud-springbootactuator}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <version>${cloud-devotools}</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${cloud-lombok}</version>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        <version>3.0.0</version>
    </dependency>
</dependencies>

② application配置

server:
  port: 8081 #端口

spring:
  application:
    name: cloud-consumer-server #服务名称

eureka:
  client:
    fetch-registry: true #是否抓取已有的注册信息,默认为true,单节点无所谓,集群时必须设置为true才能配合Ribbon使用负载均衡
    register-with-eureka: true #是否将自己注册进Eureka Server
    service-url: #eureka server地址,因为是eureka集群所以加入两个server地址
      defaultZone: http://eureka1.com:9090/eureka/,http://eureka2.com:9091/eureka/

③ 服务类配置

@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced //让RestTemplate类可以进行负载均衡(轮询)
    public RestTemplate getRestTemplate(){  //RestTemplate底层封装了HttpClient可以实现URL的访问,提供这个对象实现访问的调用,生产者提供访问的URL和端口,消费者就通过http服务进行调用
        return new RestTemplate();
    }
}
④ 调用订单模块
@RequestMapping("/consumer")
@Slf4j
public class ConsumerController {
    //CLOUD-PAYMENT-SERVER为服务名称,搭建完cloud-payment-server生产者服务后提供的服务地址
    public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVER";

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping(value = "/payment/create",method = RequestMethod.GET)
    public CommonResult<payment> create(payment payments){
        /*
            方法:postForObject发送POST请求
            参数一:url地址  参数二:请求参数(可以传输类) 参数三:返回值类class
    */
       return  restTemplate.postForObject(PAYMENT_URL+"/payment/create",payments,CommonResult.class); //调用生产者服务

    }

    @RequestMapping("/payment/select/{id}")
    public CommonResult<payment> select(@PathVariable("id") int id){
        
        return  restTemplate.getForObject(PAYMENT_URL+"/payment/select/"+id,CommonResult.class);

    }
}

二,cloud-payment-server模块的搭建

两个模块maven依赖和server,Controller是一样的,只是配置有所不同
① pom依赖
<properties>
    <cloud-springbootweb>2.5.5</cloud-springbootweb>
    <cloud-springbootactuator>2.5.5</cloud-springbootactuator>
    <cloud-springbootmybatis>2.1.0</cloud-springbootmybatis>
    <cloud-druid>1.2.1</cloud-druid>
    <cloud-mysql>5.1.47</cloud-mysql>
    <cloud-jdbc>2.5.5</cloud-jdbc>
    <cloud-devotools>2.5.5</cloud-devotools>
    <cloud-lombok>1.18.2</cloud-lombok>
</properties>
<dependencies>
    <dependency>
        <groupId>com.cloud.commons</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>${project.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>${cloud-springbootweb}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-actuator</artifactId>
        <version>${cloud-springbootactuator}</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>${cloud-springbootmybatis}</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>${cloud-druid}</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${cloud-mysql}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
        <version>${cloud-jdbc}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <version>${cloud-devotools}</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${cloud-lombok}</version>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        <version>3.0.0</version>
    </dependency>

</dependencies>

② Mybatis的xml映射文件

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="springcloud.dao.PaymentDao">

    <insert id="insert" parameterType="com.cloud.commons.entities.payment" >
        insert into payment(serial) values (#{serial})
    </insert>

    <resultMap id="mypayment" type="com.cloud.commons.entities.payment">
        <id property="id" column="id" javaType="int" jdbcType="INTEGER"/>
        <id property="serial" column="serial" javaType="string" jdbcType="VARCHAR"/>
    </resultMap>

    <select id="select" parameterType="int" resultMap="mypayment">
        select * from payment where id=#{id}
    </select>
</mapper>

③ pojo,server,返回值工具类 等省略(太多了)

④ controller

@Controller
@RequestMapping("/payment")
@Slf4j
public class PaymentController {

    @Resource
    PaymentServiceImpl paymentService;

    @Value("${server.port}")
    String server;

    @RequestMapping(value = "/create",method = RequestMethod.POST)
    @ResponseBody
    public CommonResult create(@RequestBody payment pay){

        boolean b = paymentService.create(pay);

        if(b){
            log.info("----插入成功----");
            return new CommonResult(200,"插入成功:服务"+server);
        }
        return  new CommonResult(400,"插入失败");
    }

    @RequestMapping(value = "/select/{id}",method = RequestMethod.GET)
    @ResponseBody
    public CommonResult create(@PathVariable("id") int id) {


        payment paymentByid = paymentService.getPaymentByid(id);
        if(paymentByid!=null){
            log.info("----查询成功-----");
            return new CommonResult<payment>(200,"查询成功:服务"+server,paymentByid);
        }
        return new CommonResult(400,"查询失败");
    }

}

⑤ application的配置

1,cloud-payment-server1的配置

server:
  port: 8080


spring:
  application:
    name: cloud-payment-server
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    username: root
    password: 123
    url: jdbc:mysql://localhost:3306/springcloud_test?useUnicode=true&characterEncoding=utf8
    driver-class-name: org.gjt.mm.mysql.Driver


mybatis:
  mapper-locations: classpath:mapper/*.xml
eureka:
  client:
    fetch-registry: true #是否抓取已有的注册信息,默认为true,单节点无所谓,集群时必须设置为true才能配合Ribbon使用负载均衡
    register-with-eureka: true #是否将自己注册进Eureka Server
    service-url:
      defaultZone: http://eureka1.com:9090/eureka/,http://eureka2.com:9091/eureka/

2,cloud-payment-server2的配置(只是修改的部分)

server:
  port: 8082 #端口

spring:
  application:
    name: cloud-payment-server #服务名和上面是一样的,这样两个模块集群就只提供一个服务
注:省略了挺多东西的,大家脑补
 
测试:

order调用pyment服务:

RestTemplate轮询的负载均衡访问一个集群地址访问,在多个服务下,一定需要设置RestTemplate负载均衡(加入@LoadBalanced注解)

五,Eureka服务发现Discovery

Eureka提供Discovery对象进行服务发现,这个对象在创建Eureka Client就已经创建了,开发者可以提供注入的方式进行调用

import org.springframework.cloud.client.discovery.DiscoveryClient;

例:

@RestController
@RequestMapping("/consumer")
@Slf4j
public class ConsumerController {
    @Autowired//注入discoveryClient对象
    private DiscoveryClient discoveryClient;

    @GetMapping("/discovery")
    public Object get_server(){

        //获取注册的服务列表
        List<String> services = discoveryClient.getServices();
        for(String server:services){
            log.info("-----"+server+"------");
        }
        //提供服务名获取具体的服务实例
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVER");
        for(ServiceInstance server:instances){
            log.info("服务地址:"+server.getHost()+"\t"+"服务接口:"+server.getPort()+"服务URL:"+server.getUri()+"服务ID:"+server.getServiceId());
        }
        return this.discoveryClient;
    }}

六,Eureka的自我保护机制

一,Eureka保护机制概述

保护机制主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护,一旦加入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务

如果Eureka进入保护模式Server主页会有一串信息进行提示:

当保护机制开启,某一刻微服务不可用,Eureka不会立即清除这个服务,依然对该服务信息进行保存,来保证服务的可用性

注:Eureka采用AP的设计原则(保证可用性和分区容错性)

产生Eureka保护机制的原因:防止client短暂的网络不通导致失联,服务端删除消息后client再次上线,导致不必要的麻烦

自我保护机制的细节:

    默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例心跳,EurekaServer将会注销该实例(默认90秒),但是但网络分区故障发生(延时,卡顿,拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变成非常危险--因为微服务本身其实是健康的,此时本不应该注销这个服务,Eureka通过"自我保护机制"来解决这个问题 -- 当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就进入自我保护模式

在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例

它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例

二,禁止自我保护机制

Eureka Server端设置:

eureka:
  server:
    enable-self-preservation: false #关闭自我保护机制,默认为true开启
    eviction-interval-timer-in-ms: 2000 #设置心跳检测的时间间隔(单位:毫秒)

Eureka Client端设置(一般可以不设置,只是演示):

eureka:
  instance:
    instance-id: order-server
    prefer-ip-address: true #访问信息可以显示IP地址
    lease-renewal-interval-in-seconds: 1 #客户端向服务端发送心跳的时间间隔默认间隔30秒发送一次(单位:m)
    lease-expiration-duration-in-seconds: 2 #服务端在收到客户端最后一次心跳的等待时间上线(默认90)