Eureka服务注册与发现

服务治理

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

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

服务注册与发现

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

在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息 比如 服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心上,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念).在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址));

​ 左边是Eureka系统架构,右边是Dubbo的架构

1596525780055
1596525780055

Eureka包含两个组件:Eureka Server和Eureka Client

Eureka Server提供服务注册服务

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

EurekaClient通过注册中心进行访问

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

1596526701490
1596526701490

服务端安装

新建model:cloud-eureka-server7001

pom.xml

 <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency><!--引入自己定义的api通用包,可以使用Payment支付Entity-->
            <groupId>com.kayleh.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!--boot web actuator-->
        <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>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
    </dependencies>

applicaiton.yml

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

主启动类

package com.kayleh.springcloud;

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

/**
 * @Author: Wizard
 * @Date: 2020/8/4 15:57
 */
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaMain7001.class, args);
    }
}

访问 http://localhost:7001

支付微服务8001入驻进EurekaServer

微服务8001的pom文件:添加坐标

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

application.yml下添加

eureka:
  client:
    # 表示是否将自己注册进eurekaServer默认为true
    register-with-eureka: true
    # 表示从eurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetch-registry: true
    service-url:
      # 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址.
      defaultZone: http://localhost:7001/eureka

主启动类添加client注解

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

微服务注册名配置说明

1596529308640
1596529308640

访问Eureka出现红字原因:

自我保护机制.

配置微服务80进驻Eureka;

改pom,添加坐标

改yml

server:
  port: 80

spring:
  application:
    name: cloud-order-service

eureka:
  client:
    # 表示是否将自己注册进eurekaServer默认为true
    register-with-eureka: true
    # 表示从eurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetch-registry: true
    service-url:
      # 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址.
      defaultZone: http://localhost:7001/eureka

主启动类添加client注解.

集群eureka构建

eureka集群原理分析

1596544509634
1596544509634

解决办法:搭建eureka注册中心集群,实现负载均衡+故障容错

构建集群(单机走向集群)

新建model:cloud-eureka-server7002

复制微服务7001的pom.xml

修改C:\Windows\System32\drivers\etc目录下的hosts文件

添加进hosts文件

127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com

修改7001和7002的application.yml (如果是三台集群的话,在service-url下继续写,用逗号分隔开)

7001:
server:
  port: 7001

eureka:
  instance:
    hostname: eureka7001.com #eureka服务端的实例名称
  client:
    # false表示不向注册中心注册自己
    register-with-eureka: false
    # false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务.
    fetch-registry: false
    service-url:
      defaultZone: http://eureka7002.com:7002/eureka/
--------------------------------------------------------
7002:
server:
  port: 7002

eureka:
  instance:
    hostname: eureka7002.com #eureka服务端的实例名称
  client:
    # false表示不向注册中心注册自己
    register-with-eureka: false
    # false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务.
    fetch-registry: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/

7002启动类加注解

访问

eureka7001.com:7001
eureka7002.com:7002

将80和8001模块注册进eureka, 修改yaml文件

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

测试

http://localhost/consumer/payment/get/1

支付服务提供者8001集群环境构建

新建cloud-provider-payment8002

pom.xml和8001一致

copy 8001的yml文件到8002,修改端口号

server:
    port: 8002

主启动类,业务类 直接cpoy8001

修改8001和8002的controller

添加
@Value("${server.port}")
private String serverPort;

修改打印
"插入数据库成功,serverPort" + serverPort
"查询成功,serverPort:" + serverPort

负载均衡

修改消费者80模块的OrderController的订单服务访问地址

public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
1596602970422
1596602970422

修改ApplicationContextConfig

@Configuration
public class ApplicationContextConfig {
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}
添加@LoadBalanced开启RestTemplate的负载均衡

测试

http://localhost/consumer/payment/get/1

可以看到8001端口和8002端口交替出现.

Ribbon和Eureka整合后,Consumer可以直接调用服务而不用关心地址和端口号,且该服务还有负载功能.

axtuator微服务信息完善

当前问题

1596603735423
1596603735423

暴露主机名

修改cloud-provider-payment8001和8002 的yml

eureka:
  client:
    # 表示是否将自己注册进eurekaServer默认为true
    register-with-eureka: true
    # 表示从eurekaServer抓取已有的注册信息,默认为true。单节点无所
    fetch-registry: true
    service-url:
      # 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址.
      #      defaultZone: http://localhost:7001/eureka/
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
  instance:
    instance-id: payment8001
添加instance配置

更改之后

1596604101892
1596604101892

点开链接,测试

http://wizard:8002/actuator/health

访问地址显示ip地址

修改8001,8002的yml,添加

instance:
   instance-id: payment8001
   prefer-ip-address: true

服务发现Discovery

对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息.

修改8001的controller

添加
@Resource
private DiscoveryClient discoveryClient;

@GetMapping(value = "/payment/discovery")
public Object discovery() {
      List<String> services = discoveryClient.getServices();
      for (String element : services) {
            log.info("----------element:" + element);
      }
      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;
    }

启动类添加注解

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

测试

http://localhost:8001/payment/discovery
------------------------------------------
{"services":["cloud-order-service","cloud-payment-service"],"order":0}
1596605686156
1596605686156

Eureka的自我保护机制

故障现象:

1596605819802
1596605819802

导致原因:

某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存.

属于CAP里面的AP分支

1596606071261
1596606071261
1596606189669
1596606189669
1596606288542
1596606288542

关闭自我保护

修改7001的yaml

添加 
server:
    enable-self-preservation: false
---------------------------------------
eureka:
  instance:
    hostname: eureka7001.com
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://eureka7002.com:7002/eureka/
  server:
    #关闭自我保护机制,保证不可用服务被及时删除
    enable-self-preservation: false
    #时间间隔
    eviction-interval-timer-in-ms: 2000

8001的yml加入

  instance:
    instance-id: payment8001
    prefer-ip-address: true
    # eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
    lease-expiration-duration-in-seconds: 1
    # eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
    lease-renewal-interval-in-seconds: 2

Zookepper

需要Linux安装Zookepper

新建工程cloud-provider-payment8004

pom.xml

<dependencies>
        <!--        Zookeeper客户端-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
        </dependency>

        <dependency><!--引入自己定义的api通用包,可以使用Payment支付Entity-->
            <groupId>com.kayleh.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</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>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
    </dependencies>

application.yml

server:
  port: 8004

#服务别名
spring:
  application:
    name: cloud-provider-payment
  cloud:
    zookeeper:
      # zookeeper的机器ip加端口号
      connect-string: 192.168.111.144:2181

启动类

package com.kayleh.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * @Author: Wizard
 * @Date: 2020/8/5 16:58
 */
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain8004 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8004.class, args);
    }
}

PaymentController

package com.kayleh.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

/**
 * @Author: Wizard
 * @Date: 2020/8/5 17:00
 */
@RestController
@Slf4j
public class PaymentController {
    @Value("${server.port}")
    private String serverPort;

    @RequestMapping(value = "/payment/zk")
    public String paymentzk() {
        return "Springcloud with zookeeper:" + serverPort + "\t" + UUID.randomUUID().toString();
    }
}

jar包冲突

 <!--        Zookeeper客户端-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
            <!--            排除自带的zookeeper3.5.3-->
            <exclusions>
                <exclusion>
                    <groupId>org.apache.zookeeper</groupId>
                    <artifactId>zookeeper</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--        添加zookeeper3.4.9-->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.9</version>
        </dependency>

测试

1596618784943
1596618784943

访问

localhost:8004/payment/zk
1596618927219
1596618927219

是临时节点,项目停止后,连接会持续一小段时间,然后丢失。重新连接后是另一个UUID的Zookepper。

1596626952182
1596626952182

订单服务注册zookeeper

新建cloud-consumerzk-order80

复制80的pom

yml

server:
  port: 80

#服务别名
spring:
  application:
    name: cloud-consumer-order
  cloud:
    zookeeper:
      # zookeeper的机器ip加端口号
      connect-string: 192.168.111.144:2181

启动:

package com.kayleh.springcloud;

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

config:

package com.kayleh.springcloud.config;

@Configuration
public class ApplicationContextConfig {
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

controller

package com.kayleh.springcloud.controller;

@RestController
@Slf4j
public class OrderZkController {
    public static final String INVOKE_URL = "http://cloud-provider-payment";
    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/create")
    public CommonResult<Payment> create(Payment payment) {
        return restTemplate.postForObject(INVOKE_URL + "/payment/create", payment, CommonResult.class);
    }

    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
        return restTemplate.getForObject(INVOKE_URL + "/payment/get/" + id, CommonResult.class);
    }
}

服务访问地址INVOKE_URL填linux上的zookeeper名称

1596627798682
1596627798682

测试,启动 80zk 和 8004.

1596628332087
1596628332087

访问

http://localhost/consumer/payment/zk

Consul

1596629165834
1596629165834

https://www.consul.io/downloads.html

1596629552902
1596629552902

启动

consul agent -dev

访问

http://localhost:8500/ui/

服务提供者注册进Consul

新建模块cloud-providerConsul-payment8006

pom.xml

    <dependencies>
        <!--consul-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

        <dependency><!--引入自己定义的api通用包,可以使用Payment支付Entity-->
            <groupId>com.kayleh.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</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!--        mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</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>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
    </dependencies>

yml

server:
  port: 8006

#服务别名
spring:
  application:
    name: consul-provider-payment
###consul注册中心地址
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
#        hostname: 127.0.0.1

主启动类

controller

@RestController
@Slf4j
public class PaymentController {
    @Value("${server.port}")
    private String serverPort;

    @RequestMapping(value = "/payment/consul")
    public String paymentConsul() {
        return "Springcloud with consul:" + serverPort + "\t" + UUID.randomUUID().toString();
    }
}

测试

http://localhost:8006/payment/consul

服务消费者注册进Consul

新建模块cloud-consumer-consul-order80

pom.xml

<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <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>
            <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-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

主启动类

复制cloud-consumerzk-order80模块的ApplicationContextConfig

controller

@RestController
@Slf4j
public class OrderConsulController {
    public static final String INVOKE_URL = "http://consul-provider-payment";
    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/consul")
    public String paymentInfo() {
        String result = restTemplate.getForObject(INVOKE_URL + "/payment/consul", String.class);
        return result;
    }

}

三个注册中心的异同

1596714189759
1596714189759
1596714223828
1596714223828
1596714422562
1596714422562

C : Consistency(强一致性)

A : Availability(可用性)

P : Partition tolerance(分区容错性)

CAP理论关注粒度是数据,而不是整体系统设计的策略

AP架构

当网络分区出现后,为了保证可用性,系统B可用返回旧值,保证系统的可用性

结论:违背了一致性C的要求,只满足可用性和分区容错,即AP

1596714606488
1596714606488" style="zoom: 67%;

CP架构

当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性。

结论:违背了可用性A的要求,只满足一致性和分区容错,即CP

1596714860983
1596714860983

评论