2020:0709--2--SpringCloud和Eureka

1,794 阅读9分钟

主要内容

Eureka服务注册与发现
单机Eureka构建
集群Eureka构建

1 Eureka基础知识

    前面我们没有服务注册中心,也可以服务间调用,为什么还要服务注册?

    当服务很多时,单靠代码手动管理是很麻烦的,需要一个公共组件,统一管理多服务之间的相互调用。

    Eureka用于服务注册,目前官网已经停止更新。

1.1 什么是服务治理

    SpringCloud 封装了 Netflix 公司开发的Eureka模块来实现服务治理
    
    在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,所以需要使用服务治理,
    管理服务与服务之间依赖关系,可以实现服务调用,负载均衡,容错等,实现服务发现与注册。
    
    n个生产者和n个消费者之间相互调用。

1.2 什么是服务注册与发现

    如果你的服务注册中心只有一台机器,那么只要它一宕机,就会产生单点故障。
    所以一般服务注册中心能配多个就配多个,主要就是为了避免单点故障。

1.3 Eureka的两个组件

    1.  Eureka Server:提供服务注册的服务。
    
        各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册
        表将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
    
    2.  Eureka Client:微服务通过其和Eureka Server保持通信。
    
        它是一个Java客户端,用于简化微服务和EurekaServer的交互。客户端同时也具备一个内置的,
        使用轮询(round-robin)负载算法的负载均衡器。
        在应用启动后,将会向EurekaServer发送心跳(默认周期30s)。如果EurekaServer在多个心跳
        周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90s)。

2 单机Eureka构建步骤

2.1 建module:cloud-eureka-server7001

2.2 改pom

1.  1.x和2.x的Eureka依赖对比说明

    1.x:
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
    2.x
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
2.  添加依赖
    <dependencies>
        <!--eureka server-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

        <!--引入我们自定义的公共api jar包-->
        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--图形监控-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!--一般通用配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

2.3. 写yml

        Eureka服务器 请求地址:
        http://${eureka.instance.hostname}:${server.port}/eureka/
        http:/localhost:7001/eureka/
    server:
      port: 7001
    
    eureka:
      instance:
        hostname: localhost
      client:
        # false表示不向注册中心注册自己(中介不用注册了)
        register-with-eureka: false
        # false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
        fetch-registry: false
    
        service-url: 
          # 设置Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
          defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

2.4 主启动类

    注意开启注解:@EnableEurekaServer
    指明该模块作为Eureka注册中心的服务器。
    @EnableEurekaServer
    @SpringBootApplication
    public class EurekaMain7001 {
        public static void main(String[] args) {
            SpringApplication.run(EurekaMain7001.class, args);
        }
    }

2.5 service类

  Eureka Server不需要写service类

2.6 测试

    启动,发送http://localhost:7001/ 请求

    测试成功

    只是目前还没有任何实例(微服务)注册尽进来。

3. 微服务注册

3.1 生产者微服务8001注册EurekaServer

1.  改POM
    
    添加一个Eureka-Client依赖:
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
2.  改yml:添加如下配置
eureka:
  client:
    # 表示将自己注册进EurekaServer默认为true
    register-with-eureka: true
    # 表示可以从Eureka抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetch-registry: true
    service-url: 
      defaultZone: http://localhost:7001/eureka
3.  修改主启动类

    添加注解:@EnableEurekaClient。
    指明这是一个Eureka客户端,要注册进EurekaServer中。
    @SpringBootApplication
    @EnableEurekaClient
    public class PaymentMain8001 {
        public static void main(String[] args) {
            SpringApplication.run(PaymentMain8001.class, args);
        }
    }
4.  测试

    启动Payment8001模块:

    发现我们的8001服务注册进来了。
    
    红色字体是Eureka的自我保护机制,后面再详细说。
    
    注意:注册进来的服务名,就是我们在该服务yml文件中配置的微服务名

3.2 消费者微服务80注册进EurekaServer

1.  改POM
    
    添加Eureka Client 依赖
        <!--eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
2.  改yml
    # 微服务名称
    spring:
      application:
        name: cloud-order-service
        
    eureka:
      client:
        # 表示将自己注册进EurekaServer默认为true
        register-with-eureka: true
        # 表示可以从Eureka抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
        fetch-registry: true
        service-url:
          defaultZone: http://localhost:7001/eureka 
3.  修改主启动类

    添加:@EnableEurekaClient

4.  测试:
    启动消费者80微服务。
    
    刷新http://localhost:7001/页面:消费者80微服务也注册进来了。

5.  测试微服务调用

6.  不想将那个服务注册,就在yml中配置
    eureka:
      client:
        register-with-eureka: false
    重启服务再刷新EurekaServer主页

小结:单机Eureka构建步骤到此就创建完成了。

4 集群Eureka构建步骤

    单机版Eureka构建到这就结束了,但是企业中不可能有谁敢说它的服务注册中心是单机版。
    因为没有集群的高可用,就会带来一个严重的问题,单点故障。
    
    1.  Eureka集群原理说明
    2.  EurekaServer集群环境构建步骤
    3.  将支付服务8001微服务发布到上面2台Eureka集群配置中
    4.  将订单服务80微服务发布到上面2台Eureka集群配置中
    5.  测试01
    6.  支付服务提供者8001集群环境构建。
    7.  负载均衡
    8.  测试02

4.1 Eureka集群原理说明

    Eureka流程原理:

    Eureka集群原理:互相注册,相互守望

4.2 EurekaServer集群环境构建步骤

4.2.1 新建一个cloud-eureka-server7002模块
    1.  新建一个cloud-eureka-server7002模块
4.2.4 pom依赖
    2.  pom依赖同cloud-eureka-server7001
4.2.4 修改两个EurekaServer的yml配置
    4.  yml
        
        现在有多台Eureka服务器,所以没台Eureka服务器都要有自己的主机名。
        
        同时Eureka7001和Eureka7002要互相注册。
        
        我们先修改映射配置文件:第3步
        
        1.  Eureka7001的yml
        server:
          port: 7001
        
        eureka:
          instance:
            hostname: eureka7001.com  #eureka服务端的实例名称
          client:
            # false表示不向注册中心注册自己(中介不用注册了)
            register-with-eureka: false
            # false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
            fetch-registry: false
        
            service-url:
              # 以7001作为服务提供者,向该指定的服务注册中心的位置注册7001服务
              defaultZone: http://eureka7002.com:7002/eureka/
        2.  Eureka7002的yml
            server:
              port: 7002
            
            eureka:
              instance:
                hostname: eureka7002.com  #eureka服务端的实例名称
              client:
                # false表示不向注册中心注册自己(中介不用注册了)
                register-with-eureka: false
                # false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
                fetch-registry: false
            
                service-url:
                  # 以7002作为服务提供者,向该指定的服务注册中心的位置注册7002服务
                  defaultZone: http://eureka7001.com:7001/eureka/
4.2.3 修改域名映射配置文件
    3.  修改映射配置文件
    
        修改:C:\Windows\System32\drivers\etc 路径下的host文件
        
        受限于我们只有一台物理机器,我们用不同的端口号来映射同一个地址。

        这样修改是是什么意思:
            由于我只有一台电脑,而且将这台电脑也作为服务器。每次我们发送localhost,就会被域名解析器
        解析成127.0.0.1,也就是我们本机的ip回送地址,访问我们本机。
            
            现在我们将eureka7001.com和eureka7002.com作为域名映射到127.0.0.1,那么我们就可以
        模拟请求不同的服务器名。实际上都会解析到我们本机。
        
        注意这个映射修改了不意味着localhost 127.0.0.1之间的映射关系没有,还有。
        即localhost/eureka7001.com/eureka7002.com 都会被域名解析器解析成127.0.0.1
            
        例子1:
            之前的单机的EurekaServer的yml配置:
            server.port=7001
            eureka.instance.hostname=localhost
            eureka.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
            
            那么我通过发送http://localhost:7001/eureka能访问到服务器在本地主机的EurekaServer。
            
        例子2:以两个集群为例
            现在集群的EurekaServer的yml配置
            server.port=7001
            eureka.instance.hostname=eureka7001.com
            eureka.service-url.defaultZone=http://eureka7002.com:7002/eureka/
            
            server.port=7002
            eureka.instance.hostname=eureka7002.com
            eureka.service-url.defaultZone=http://eureka7001.com:7001/eureka/ 
            
            那么这样配置后,我们就可以通过:
                http://eureka7001.com:7001和http://eureka7002.com:7002来
                分别访问这两个EurekaServer。
                同时这两个EurekaServer实现了互相注册。
4.2.5 主配置类
        @SpringBootApplication
        @EnableEurekaServer
        public class EurekaMain7002 {
            public static void main(String[] args) {
                SpringApplication.run(EurekaMain7002.class, args);
            }
        }
4.2.6 测试
    开启eureka7002 和 eureka7001 这两个服务
    
    测试:  http://localhost:7001/ 和 http://localhost:7002/ 
            
            都成功连接。        

    测试:  http://eureka7002.com:7002/ 和 http://eureka7001.com:7001/
  
            都成功连接。

    并且实现了两个Euraka服务器的相互注册:

4.2.7 我又测试一个eureka7003
server:
  port: 7003

eureka:
  instance:
    hostname: eureka7003.com  #eureka服务端的实例名称
  client:
    # false表示不向注册中心注册自己(中介不用注册了)
    register-with-eureka: false
    # false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    fetch-registry: false

    service-url:
      # 以7001作为服务提供者,向该指定的服务注册中心的位置注册7001服务
      defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7001.com:7001/eureka/
将其注册到eureka7002和eureka7001中。

5 注册提供者微服务和消费者微服务

5.1 将提供者服务8001微服务发布到上面的3台Eureka集群配置中

    1.  修改yml

defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka

    2.  其他不改

5.2 将消费者服务80微服务发布到上面的3台Eureka集群配置中

    1.  修改yml

defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka

    2.  其他不改

5.3 测试

    1.  启动顺序
        先启动Eureka 7001/7002/7003
        在启动payment8001,order80
        
    2.  测试:http://eureka7003.com:7003/  成功

    3.  测试:http://eureka7001.com:7001/和http://eureka7002.com:7002/
        
        略...
    
    4.  测试order80微服务

6 生产者微服务集群构建

    一个提供者微服务不可能应付所有的消费者微服务。

6.1 新建cloud-provider-payment8002 微服务

1.  POM和cloud-provider-payment8001一致
    
    此处有差别:

2.  写yml:

    和payment8001一致,只改一个端口。
    # 微服务端口号
    server:
      port: 8002
3.  主启动类
    @SpringBootApplication
    @EnableEurekaClient
    public class PaymentMain8002 {
        public static void main(String[] args) {
            SpringApplication.run(PaymentMain8002.class, args);
        }
    }
4.  其他业务类和payment8001一致一致。

    其他业务类controller/service/dao 直接复制。
    注意:报错的话,重新alt+enter导下包。
    
    mapper映射文件直接复制
    
    entities通过cloud-api-commons引入。

6.2 修改controller

1.  注意:两个生产者微服务在注册中心中的注册名是一样的
    
    这两个微服务对外暴露的都是同一个名字。

2.  消费者微服务调用的是cloud-payment-service这个服务,但是在这个名字下面可能有多台机器。
    那么怎么确定消费者微服务调用哪台机器上的cloud-payment-service服务?
    
    看端口号!
    
3.  修改两个生产者微服务的controller方法

    两个生产者微服务的controller都改成这样。
    public class PaymentController {
    
        @Resource
        private PaymentService paymentService;
    
        @Value("${server.port}") //Value绑定单一的属性值
        private String serverPort;
        
        //传给前端JSON
        @PostMapping(value = "/payment/create")    //写操作POST
        public CommonResult create(@RequestBody Payment payment){
    
            //由于在mapper.xml配置了useGeneratedKeys="true" keyProperty="id",会将自增的id封装到实体类中
            int result = paymentService.create(payment);
    
            log.info("*****插入结果:" + result);
    
            if(result > 0){
                //restful风格:对象以json串返回到页面
                return new CommonResult(200, "插入数据库成功,serverPort:"+serverPort, result);
            }else {
                return new CommonResult(444,"插入数据库失败", null);
            }
        }
        
        //传给前端JSON
        @GetMapping(value = "/payment/get/{id}")    //写操作POST
        public CommonResult getPaymentById(@PathVariable("id") Long id){
    
            Payment payment = paymentService.getPaymentById(id);
    
            log.info("*****查询结果:" + payment);
    
            if(payment != null){
                return new CommonResult(200, "查询数据库成功,serverPort:"+serverPort, payment);
            }else {
                return new CommonResult(444,"查询ID:"+id+"没有对应记录", null);
            }
        }
    }
4.  启动测试:EurekaServer

    两个生产者微服务都注册进来了。

5.  测试:生产者微服务

6.  测试:消费者微服务

    发现测试若干次不同请求,但是走的都是8001端口的生产者微服务。

7.  原因是我们在消费者微服务中将地址写死了

    所以在访问消费者微服务后,消费者微服务再去调用生产者中的方法。
    调用发送的请求一直都是http://localhost:8001/...
    所以一直调用了8001端口的微服务。
    
    那么现在我们不指明具体的微服务端口,指定一个这两个生产者微服务共有的指向:
    即微服务名:CLOUD-PAYMENT-SERVICE

    那么消费者再去调用生产者中的方法时,就没有指定具体的端口(具体的微服务)。那么哪一个微服务
    被调用有其他方式来决定。
    
    注意这个说法:
        这个程序的使用者会知道这些微服务的端口或者地址吗?
        消费者使用时,他们根本不想知道具体哪个微服务对应哪个端口或者具体的地址。他只关心微服务的名称。
        他们只想通过看到的微服务名,来使用这些微服务。
        也就是说,我们对外暴露的只能是微服务名,来供使用者
        
8.  再次测试:

    发现报错:

    原因:
        这个微服务下面对应着多个微服务,这个微服务名下的多个微服务到底是哪个出来响应,没有办法识别。

6.3 使用@LoadBlanced注解赋予RestTemplate负载均衡的能力

    1.  以前我们把生产者微服务写死,没关系,因为只有一个生产者微服务,只认一个。
    
    但是现在不能写死了,因为这个微服务名下对应有多个微服务,那么调用时,必须得说明
    哪一个被调用了。
    
    需要规定一种默认的负载均衡机制:@LoadBlanced

    我们在这里通过@LoadBlanced赋予了RestTemplate负载均衡的能力
    
    2.  测试:

        开启了默认的轮询的负载机制,8001和8002交替出现(循环交替)
        
        提前说一下这就是Ribbon的负载均衡功能默认就是轮询。
        
        即:Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号,
        且该服务还有负载功能了。
    
        消费者只关注生产者微服务名称,生产者微服务一定是一个集群,那就要有一个默认的
        负载均衡。

7 actuator微服务信息完善

7.1 主机名称:服务名称的规范和修改

    1.  按照规范的要求只暴露服务名,不带有主机名。
    
    2.  修改生产者微服务的yml

    3.  测试:只暴露服务名

    4.  actuator:监测检查
    
        1. 	显示应用的基本信息

        2.  显示应用的健康状态(健康检查)

7.2 访问信息有IP信息提示

    1.  我现在点击这个微服务的链接,没有ip信息提示。
    
        实际工作中,我们都会说这个微服务是部署在几号机器上面的几号端口。我们要让访问信息
        有ip信息提示。
        
    2.  在每个微服务的yml中添加如下

    3.  实现

8 服务发现Discovery

    1. 我们现在已经实现了这6个微服务的调用。

        不排除我们微服务自身,想对外提供功能。那么我们就要拿到在微服务中注册了的微服务的信息。
        比如:主机名称,端口号。
        
    2.  服务发现:
        
        对于注册进eureka里面的微服务,可以通过服务发现来获得这些微服务的信息。
    
    3.  修改cloud-provider-payment8001的controller:写好提供给外部的信息

注意写在80和8002中也都可以获得所有的微服务信息,这里只是在8001上进行测试。

        @Resource  //发现自己的服务信息
        protected DiscoveryClient discoveryClient;
        
        //服务发现Discovery
        @GetMapping(value = "/payment/discovery")
        public Object discovery(){
            //得到服务清单列表
            List<String> services = discoveryClient.getServices();
            for (String service : services) {
                log.info("*********service: "+ service);
            }
    
            //根据微服务的具体服务名称:得到微服务实例
            List<ServiceInstance> instances = discoveryClient.getInstances("cloud-payment-service");
            for (ServiceInstance instance : instances) {
                log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
            }
    
            return this.discoveryClient;
        }
        注入一个DiscoveryClient类,可以通过其中的两个方法得到我们注册了的服务的信息
            
        3.1 得到服务清单:    
            discoveryClient.getServices();
        
        3.2 根据微服务的具体服务名称:得到微服务实例
            discoveryClient.getInstances("cloud-payment-service");
            
    4.  在8001主启动类开启注解:@EnableDiscoveryClient
    
    5.  测试
        发型请求:http://localhost:8001/payment/discovery
        页面返回的是discoveryClient的json传:

        控制台打印的信息:

    6.  所以我们今后只要对外暴露这样的一个rest服务接口地址。
        
        调用者就可以访问这个地址获取微服务的信息。

9 eureka自我保护

9.1 自我保护

1.  概述:
    保护模式主要用于一组客户端和EurekaServer之间存在网络分区场景下的保护。一旦进入保护模式,
    EurekaServer将会尝试保护其服务注册表中的信息,不在删除服务注册表中的数据,也就是不会注销
    任何微服务。
    
    
    如果在EurekaServer的首页看到以下这段提示,则说明Eureka进入了保护模式:

    一句话:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存。
    
    属于CAP里面的AP分支。
    
    Eureka它的设计思想:
    分布式CAP里面的AP分支,某时刻某一个微服务不可用了,Eureka不会立刻清理,
    依旧会对该微服务的信息进行保存。
    
2.  为什么会产生Eureka自我保护机制

    因为可能存在这样的情况:
        EurekaClient可以正常运行,但是与EurekaServer网络不通。
    
    此时EurekaServer不会立刻将EurekaClient服务剔除。

3.  自我保护

    如果一定时间内丢失大量该微服务的实例,这时Eureka就会开启自我保护机制,不会剔除该服务。
    因为这个现象可能是因为网络暂时不通,出现了Eureka的假死,拥堵,卡顿。
    稍等会客户端还能正常发送心跳。
    
    这就是CAP里面的AP分支思想。
    
4.  设计哲学

    宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。

9.2 怎么禁止自我保护

    默认是开启自我保护。
    
    禁止自我保护:只要掉线了,立马删掉该服务实例

    1.  注册中心:7001
    
        出厂默认,自我保护机制是开启的
        eureka.server.enable-self-preservation=true
        
        默认服务实例掉线9s后删掉
        eviction-interval-timer-in-ms: 9000
        
    2.  修改一下yml
    
        关闭自我保护,并且掉线2s就删除。

    3.  启动7001
        
        自我保护已关闭。

    4.  修改eurekaClient端8001
    
        它有两个默认的配置
        # Eureka客户端向服务端发送心跳的时间间隔默认30秒
        eureka.instance.lease-renewal-interval-in-seconds: 30       单位秒
        # Eureka服务端在收到最后一次心跳后,90s没有收到心跳,剔除服务
        eureka.instance.lease-expiration-duration-in-seconds: 90    单位秒
        
    5.  测试:
        
        8001能正常注册

    6.  模拟发生故障:
            
        关闭8001
        
        如果是默认的话,由于Eureka自我保护机制,会在90s后剔除该服务。
        
        现在关闭了自我保护机制,并且在7001设置了掉线两秒就会剔除。

        迅速刷新几下后:该服务被剔除了。