Spring-Cloud 学习笔记-(3)注册中心Eureka

Spring-Cloud 学习笔记-(3)注册中心Eureka

1、前言

1.1、上个章节我们做了什么?

  • user-service:作为服务提供者对外提供一个借口,根据用户id 查询用户基本信息

  • order-service:作为服务调用者,通过RestTemplate远程调用user-service

    流程如下:

2.1、本章节我们讲会做什么?

服务的注册与发现

2、什么是Eureka

2.1、问题分析

在上一章的案例中,user-service对外提供服务,需要对外暴露自己的地址。而order-service(调用者)需要记录服务提供者的地址。将来地址出现变更,还需要及时更新。这在服务较少的时候并不觉得有什么,但是在现在日益复杂的互联网环境,一个项目肯定会拆分出十几,甚至数十个微服务。此时如果还人为管理地址,不仅开发困难,将来测试、发布上线都会非常麻烦,这与DevOps的思想是背道而驰的。

2.2、滴滴打车

这就好比是 网约车出现以前,人们出门叫车只能叫出租车。一些私家车想做出租却没有资格,被称为黑车。而很多人想要约车,但是无奈出租车太少,不方便。私家车很多却不敢拦,而且满大街的车,谁知道哪个才是愿意载人的。一个想要,一个愿意给,就是缺少引子,缺乏管理啊。

此时滴滴这样的网约车平台出现了,所有想载客的私家车全部到滴滴注册,记录你的车型(服务类型),身份信息(联系方式)。这样提供服务的私家车,在滴滴那里都能找到,一目了然。

此时要叫车的人,只需要打开APP,输入你的目的地,选择车型(服务类型),滴滴自动安排一个符合需求的车到你面前,为你服务,完美!

2.3、Eureka做什么?

Eureka就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉Eureka,然后Eureka会把符合你需求的服务告诉你。

同时,服务提供方与Eureka之间通过心跳机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务列表中剔除。

这就实现了服务的自动注册、发现、状态监控。

3、原理

3.1、架构图

该图片来自于Eureka开源代码的文档,地址为https://github.com/Netflix/eureka/wiki/Eureka-at-a-glance

从图上可以看得出Eureka主要有两个角色:Eureka-Server(服务端)和Eureka-Client(客户端),而客户端又可以分为ApplicationService(服务的提供者)和ApplicationClient(服务的调用者)

3.2、服务注册于发现:

Eureka-Client向Eureka-Server注册,并将自己的信息以key,value形式(key:serviceId,value:ip地址、端口等信息)注册到Eureka-Server,Eureka-Server再同步给其他Eureka-Server,Eureka-Client会每间接一段时间(默认30秒)向Eureka-Server发送一次心跳来表明自己还活着,如果客户端不能正常续约,他会在一定时间内(默认90秒)从服务器注册列表中剔除,然后服务器会复制更新到其他Eureka-Server集群,当ApplicationClient想要调用ApplicationService的方法时,会向EurekaServer拉取注册列表,通过serviceId找到ip和端口进行远程调用,所以保证每个区域内的Eureka-Server集群至少有一个Eureka-Server能够正常运行,防止服务器瘫痪。

4、快速入门

4.1、编写EurekaServer

新建一个Module作为EurekaServer:

我们同样选择Maven工程,方法跟之前的一样

4.1.1、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">
    <parent>
        <artifactId>cloud-demo</artifactId>
        <groupId>com.bigfly</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>eureka-server</artifactId>

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

</project>

4.1.2、文件结构

4.1.3、代码编写

EurekaServerApplication启动类:

package com.bigfly;

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

@SpringBootApplication
@EnableEurekaServer//启动Eureka-Server
public class EurekaServerApplication {

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

}

yml文件:

server:
  port: 8761

4.1.4、测试

启动服务

访问:http://localhost:8761/

控制台报错:

报错分析:

为了保证服务的可用性Eureka-Server以后将会集群搭建,所以Eureka-Server也作为一个客户端向其他的Eureka-Server注册,这里是因为Eureka-Server找不到注册中心地址所以报错,所以我们需要在yml文件中配置注册中心地址

解决:

yml文件增加

eureka:
  instance:
    hostname: 127.0.0.1
  client:
    #自己不注册自己
    registerWithEureka: false
    #不需要检索服务信息
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

如果页面出现如下错误,没有关系,这是因为Eureka-Server发现长时间没有客户端注册进来, Eureka Server在运行期间,会统计心跳失败的比例在15分钟之内是否低于85%,如果出现低于的情况(在单机调试的时候很容易满足,实际在
生产环境上通常是由于网络不稳定导致),Eureka Server会将当前的实例注册信息保护起来,同时提示这个警告。保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)

4.2、将客户端注册到Eureka-Server

4.2.1、user-service代码修改

pom文件增加:

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

application.yml文件增加:

#服务名称
spring:
  application:
    name: user-service

# 注册中心
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka

4.2.2、order-service代码修改

pom文件增加:

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

application.yml文件增加:

#服务名称
spring:
  application:
    name: order-service

# 注册中心
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka

4.2.3、测试

先启动eureka-server服务,启动完毕再启动user-service和order-service

访问http://127.0.0.1:8761 效果:

4.3、order-service 调用user-service

4.3.1、修改代码

OrderServiceImpl订单服务实现类:

package com.bigfly.service.impl;

import com.bigfly.entity.Order;
import com.bigfly.service.OrderService;
import com.bigfly.utils.JsonUtils;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.List;
import java.util.UUID;

@Service
public class OrderServcieImpl implements OrderService {

    @Autowired
    private RestTemplate restTemplate;

    //↓↓↓↓↓↓↓↓↓↓↓↓修改部分↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
    @Autowired
    private DiscoveryClient discoveryClient;
    //↑↑↑↑↑↑↑↑↑↑↑↑修改部分↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
    
    /**
     * 根据用户id 查询用户信息
     * @param userId 用户id
     * @return
     */


    @Override
    public Order findById(int userId) {

        //↓↓↓↓↓↓↓↓↓↓↓↓修改部分↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
        //通过serviceId 拉取服务列表
        List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
        ServiceInstance instance = instances.get(0);
        //getForObject 第一个参数url代表访问路径 第二个参数代表 返回值类型
        String jsonStr = restTemplate.getForObject("http://"+instance.getHost()+":"+instance.getPort()+"/api/v1/user/2", String.class);
//        String jsonStr = restTemplate.getForObject("http://127.0.0.1:8771/api/v1/user/2", String.class);
        //↑↑↑↑↑↑↑↑↑↑↑↑修改部分↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑

        JsonNode jsonNode = JsonUtils.str2JsonNode(jsonStr);
        Order order = new Order();
        order.setOrderName("我是一个订单");
        order.setSerialId(UUID.randomUUID().toString());
        order.setUserName(jsonNode.get("data").get("name").textValue());
        order.setPort(jsonNode.get("data").get("port").textValue());
        return order;
    }
}

4.3.2、断点调式

  1. 复制一个user-service模拟集群

  1. 启动服务

    先启动eureka-server,启动完毕后启动其他服务,其中order-service断点模式启动

  1. 调试

    访问:http://localhost:8781/api/v1/order/2

    进入断点,F8下一步

放开断点访问结果

5、Eureka详解

5.1、高可用的Eureka

模拟多个eureka-server相互注册

修改eureka-server配置文件application.yml

server:
  port: 8761

eureka:
  instance:
    hostname: 127.0.0.1
  client:
    #自己不注册自己
#    registerWithEureka: false
    #不需要检索服务信息
#    fetchRegistry: false
    serviceUrl:
      defaultZone: http://127.0.0.1:8762/eureka

spring:
  application:
    name: eureka-server

复制一个eureka-server,方法同上

此配置表示,端口号为8761的eureka会注册到8762上,端口号为8762的eureka会注册到8761上,因为eureka会把自己的注册列表复制更新到其他的注册中心上,所以我们最后访问 http://127.0.0.1:8761/eureka 和http://127.0.0.1:8762/eureka我们将看到这样的结果

5.2、服务的调用方和提供方配置详解

一个服务既可以作为服务的提供方,也可以作为服务的调用方,这里方便理解我们分开说

  1. 一个服务的提供方(比如:user-service),在服务一启动时会检查eureka.client.register-with-erueka=true属性是否为true(不配置默认是true),如果是true就会把自己注册到注册中心(eureka-server)去,反之,如果为false就不会注册自己。

  2. 在服务注册完毕过后,服务的提供方会跟注册中心维持一个心跳(没间隔一段时间向注册中心发送一次rest请求)来告诉注册中心“我还活着”,这里我们叫服务的续约(renew),如果超过一段时间(默认是90秒)注册中心还没有收到心跳注册中心就会认为此服务已经宕机,然后把这个服务从注册列表中剔除。

    eureka:
      instance:
      	#服务的续约时间间隔
      	lease-renewal-interval-in-seconds: 30
      	#服务的失效时间间隔
        lease-expiration-duration-in-seconds: 90
    

    如果在开发阶段我们可以适当把这个值跳小一点,在生产环境中这个值尽量不用修改

  3. 服务的调用方(比如:order-service),在启动时候会检测eureka.client.fetch-registry=true属性是否为true(不配置同样是true),如果是true,就会把注册中心(比如:eureka-server)的服务列表拉到本地缓存,并且没间隔一段时间(默认30秒)会重新拉取更新一次,每次调用都是从自己缓存中获取注册列表,后期如果注册中心挂了,服务依旧可以调用,但是不会更新注册列表了,所以注册中心尽量集群搭建,确保服务的可用性。

    eureka:
      client:
      	#拉去列表时间间隔
        registry-fetch-interval-seconds: 30
    

    同样,生成环境我们不用修改,开发环境我们可以修改小一点。

5.3、失效剔除和自我保护

  1. 注册中心(eureka-server)会每间隔一段时间(默认60秒)把失效的服务(90秒没有收到心跳)从注册列表中剔除,也就是说一个服务,如果注册中心90秒没有收到心跳回复,不会立即剔除,而是每间隔一段时间统一剔除。
  2. 注册中心(eureka-server)会统计最近15分钟每分钟续约量是否超过85%,如果低于85%,eureka就会认为你没有宕机,可能是因为网络延迟等其他原因,eureka就会开启保护机制,这些实例就会被eureka保护起来,就算没有续约也不会剔除。我们在开发过程中很容易就满足续约量低于85%,所以一般开发时候会关闭自我保护(true:开启,false:关闭)
eureka:
  server:
  	# 扫描失效服务的间隔时间(缺省为60*1000ms)
    eviction-interval-timer-in-ms: 60000 
  	# 关闭自我保护模式(缺省为打开)
    enable-self-preservation: true 

5.4、小知识

强制关闭某个服务:put:{eureka_ip:eureka_port}/eureka/apps/{appname}/{service_id}/status?value={UP/DOWN}

posted @ 2018-12-26 15:26  神秘的大飞子  阅读(221)  评论(0编辑  收藏  举报