使用SpringCloud实现Java分布式开发【part-1】:Eureka注册中心的搭建、高可用集群的配置及高级配置

SpringCloud简介

Spring Cloud是Spring旗下的项目之一
Spring Cloud并不是一个组件 而是许多组件的集合
其将当下非常流行的一些技术整合到了一起 实现了多个分布式开发中的重要功能
协调了分布式环境中各个系统 并且为各类服务提供模板性的配置

其主要涉及的组件包括:

  • Eureka:注册中心
  • Zuul或Spring Cloud Gateway:服务网关
  • Ribbon:负载均衡
  • Feign:服务调用
  • Hystrix或Resilience4j:熔断器

【在本篇中 将介绍Eureka注册中心】

Eureka注册中心

在传统的分布式开发中会面临着一个问题:提供服务者需要对外暴露自己的地址 而服务调用者需要记录服务提供者的地址
若地址出现变更 则还需要及时更新 在现在日益复杂的互联网环境 一个项目可能拆分出多个微服务
此时若人为管理地址 不仅开发困难 且在测试 发布上线时都会非常麻烦

而SpringCloud的Eureka组件可以解决该问题

Eureka好比一个中介 其负责管理和记录服务提供者的信息
那么 服务调用者无需自己寻找服务 而是将自己的需求告诉Eureka 然后Eureka会告诉你符合需求的服务地址
同时 服务提供方与Eureka之间通过心跳 (顾名思义 类似于心跳 会定时检测是否存活) 机制进行监控
如此 若当某个服务提供方出现问题 Eureka自然会把其从服务列表中剔除
以此确保了服务的自动注册 发现 状态监控
在这里插入图片描述
需要注意的是 Eureka是服务注册中心 仅仅只提供服务注册能力 自身并不提供服务也不消费服务

一、搭建步骤:

1、Eureka服务端

添加Eureka服务端的依赖:
(由于我已在父工程中添加了spring-cloud-dependencies版本管理 因此这里不再配置版本号了)

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

在Eureka的服务端的启动类上添加@EnableEurekaServer注解以声明当前应用为Eureka的服务端:

@SpringBootApplication
// 声明当前应用为Eureka的服务端
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class,args);
    }
}

配置文件application.yml:

server:
  # 当前服务的端口号
  port: 10086
spring:
  # 当前应用的名称 便于区分
  application:
    name: eureka-server
eureka:
  client:
    service-url:
      # 设置Eureka服务的默认地址 需要注意的是后面需带上一个/erueka
      # 若为集群 则需指定其它集群的Eureka地址
      defaultZone: http://127.0.0.1:10086/eureka
    # 当前服务不注册到Eureka 因为当前服务就是Eureka提供者本身 并不提供服务
    # 若为集群 该配置则要注释掉 为了让其它Eureka发现该Eureka
    register-with-eureka: false
    # 不从Eureka拉取服务
    fetch-registry: false

2、Eureka服务客户端

服务提供方将服务注册到Eureka以便于调用 服务消费方从Eureka获取地址列表
因此 服务提供方和服务消费方都属于Eureka客户端

服务提供方和服务消费方都需添加Eureka客户端依赖 服务提供方自动将服务注册到EurekaServer服务地址列表 服务消费方使用工具类根据服务名称获取对应的可用的服务地址列表

①、服务提供方:

添加Eureka客户端的依赖:
(由于我已在父工程中添加了spring-cloud-dependencies版本管理 因此这里不再配置版本号了)

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

在Eureka的客户端的启动类上添加@EnableDiscoveryClient注解以开启Eureka客户端的发现功能:

@SpringBootApplication
//  开启Eureka客户端的发现功能
@EnableDiscoveryClient
@MapperScan("net.zjitc.mapper")
public class UserApplication {

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

配置文件application.yml:

# 当前应用的名称 便于区分
application:
  name: user-service
    
eureka:
  client:
    service-url:
      # 设置Eureka服务的默认地址
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    # 将实例的ip注册到eureka中 否则默认以主机名注册
    prefer-ip-address: true

对外开放http接口:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public User queryById(@PathVariable Long id)
    {
        return userService.queryById(id);
    }
}

启动服务器 服务提供者成功被Eureka发现:
在这里插入图片描述

②、服务消费方:

同样 需要添加Eureka客户端的依赖:

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

同样 在Eureka的客户端的启动类上添加@EnableDiscoveryClient注解以开启Eureka客户端的发现功能:

@SpringBootApplication
//  开启Eureka客户端的发现功能
@EnableDiscoveryClient
public class ConsumerApplication {

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

    // 注册RestTemplate
    @Bean
    public RestTemplate restTemplate()
    {
        return new RestTemplate();
    }
}

配置文件application.yml:

eureka:
  client:
    service-url:
      # 设置Eureka服务的默认地址
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    # 将实例的ip注册到eureka中 否则默认以主机名注册
    prefer-ip-address: true
spring:
  application:
    name: consumer-demo

动态获取可用的服务提供方地址(关键)
首先需要注入发现工具类:
(注:所在包org.springframework.cloud.client.discovery.DiscoveryClient)

@Autowired
private DiscoveryClient discoveryClient;

获取Eureka中注册的user-service实例列表:

 List<ServiceInstance> instances = discoveryClient.getInstances("user-service");

从可用的实例列表中获取第一个实例对象:

ServiceInstance serviceInstance = instances.get(0);

拼接ip地址和端口:

String url="http://"+serviceInstance.getHost()+serviceInstance.getPort()+"/user/"+id;

因此 最终实现的例子是这样的:

@RestController
@RequestMapping("/consumer")
public class ConsumerController {

    @Autowired
    private RestTemplate restTemplate;

    // 注入发现工具类
    @Autowired
    private DiscoveryClient discoveryClient;

    @GetMapping("/{id}")
    public User queryById(@PathVariable Long id)
    {
        // 获取Eureka中注册的user-service实例列表
        List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
        // 从可用的实例列表中获取第一个实例对象
        ServiceInstance serviceInstance = instances.get(0);
        // 拼接ip地址和端口
        String url="http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/user/"+id;
        return restTemplate.getForObject(url,User.class);
    }
}

二、Eureka高可用集群:

Eureka可以是一个集群 形成高可用的Eureka中心
多个Eureka Server之间互相注册为服务 当服务提供者注册到Eureka Server集群中的某个节点时 该节点会将服务的信息同步给集群中的每个节点 从而实现数据同步
因此 无论客户端访问到Eureka Server集群中的任意一个节点 都可以获取到完整的服务列表信息

而作为客户端 在注册的时候需要注意 要将信息注册到每个Eureka中
在这里插入图片描述

例如 若有三个Eureka服务端 分别为10086、10087、10088
则:10086要注册到10087和10088上
10087要注册到10086和10088上
10088要注册到10086和10087上

配置时 要注释掉register-with-eureka和fetch-registry
服务端1的配置(端口为10086):

server:
  port: 10086
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10087/eureka
    # 若为集群 该配置则要注释掉 为了让其它Eureka发现该Eureka
    # register-with-eureka: false
    # 不从Eureka拉取服务
    # fetch-registry: false

服务端2的配置(端口为10087):

server:
  port: 10087
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
    # 若为集群 该配置则要注释掉 为了让其它Eureka发现该Eureka
    # register-with-eureka: false
    # 不从Eureka拉取服务
    # fetch-registry: false

多个地址之间用逗号隔开:

# 设置Eureka服务的默认地址 多个用逗号隔开
defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka

小结:Eureka高可用集群 实际上就是将Eureka Server自己也作为一个服务注册到其它的Eureka Server上 这样多个Eureka Server之间就能互相发现对方 从而形成集群

三、Eureka服务端及客户端的高级配置

对于Eureka客户端:

  • 对于服务提供者 可以配置服务地址使用ip的方式续约
  • 对于服务消费者 可以配置获取服务地址的频率

对于Eureka服务端:
可以配置服务下线和失效剔除自我保护

1、服务地址使用ip的方式【Eureka客户端-服务提供者配置】

说到这个 则需要了解Eureka的数据保存方式了:

服务提供者在启动时会检测配置属性中的eureka.client.register-with-erueka=true参数是否正确
默认为true 若值为true 则会向EurekaServer发起一个Rest请求并携带自己的元数据信息
Eureka Server会将这些信息保存到一个双层Map结构
第一层Map的Key为服务id 通常为配置中的spring.application.name属性
第二层Map的key为服务的实例id 格式通常为为host+serviceId+port 例如:localhost:user-service:8080
值则是服务的实例对象

也就是说 一个服务可以同时启动多个不同实例 以此来形成集群
当默认注册时 使用的是主机名或localhost 若想用ip进行注册 可添加配置如下:

eureka:
  instance:
    # 将实例的ip注册到eureka中 否则默认以主机名注册
    prefer-ip-address: true
    # 设置服务的ip地址
    ip-address: 127.0.0.1

2、服务续约【Eureka客户端-服务提供者配置】

在注册服务完成以后 服务提供者会维持一个心跳 (即定时向EurekaServer发起Rest请求)
告诉EurekaServer还存活
此行为称为服务的续约(renew)
有两个重要参数可以修改服务续约的行为 可在配置文件中添加如下配置项:

eureka:
  instance:
    # 服务续约(renew)的间隔 默认为30秒
    lease-renewal-interval-in-seconds: 30
    # 服务失效时间 默认为90秒
    lease-expiration-duration-in-seconds: 90

这两项只是Eureka认为可以被移除的时间 并不是真正被立刻移除(因为要避免网络原因干扰)

默认情况下 每隔30秒 服务会向注册中心发送一次心跳证明自己还活着
若超过90秒没有发送心跳 则Eureka Server就会认为该服务宕机 会定时(按照eureka.server.eviction-interval-timer-in-ms设定的时间)从服务列表中移除

3、获取服务地址的频率【Eureka客户端-服务消费者配置】

当服务消费者启动时会检测eureka.client.fetch-registry=true参数的值 若为true 则从Eureka
Server服务的列表拉取只读备份然后缓存到本地
并且每隔30秒(默认值)会重新拉取并更新数据
可在配置文件中通过以下参数来修改:

eureka:
  client:
    # 获取服务器可用地址列表的时间 默认为30秒
    registry-fetch-interval-seconds: 30

4、服务下线和失效剔除【Eureka服务端配置】

服务下线

当服务进行正常关闭操作时 会触发一个服务下线的REST请求发送给Eureka Server 以此来告诉服务注册中心要下线了
当服务中心接受到请求之后 随即将该服务置为下线状态

失效剔除

有时服务可能由于内存溢出或网络故障等原因使得服务不能正常的工作 而服务注册中心并未收到服务下线的请求
那么 相对于服务提供者的服务续约操作 服务注册中心在启动时会创建一个定时任务
每隔一段时间 (默认为60秒) 将当前清单中超时 (默认为90秒) 没有续约的服务剔除
该操作被称为失效剔除
可通过eureka.server.eviction-interval-timer-in-ms参数对其进行修改 单位为毫秒

eureka:
  server:
    # 服务失效剔除的时间间隔 默认为60秒 单位为毫秒
    eviction-interval-timer-in-ms: 60000

5、自我保护【Eureka服务端配置】

在这里插入图片描述
当关停一个服务时 很可能会在Eureka面板看到一条红色的警告
这是触发了Eureka的自我保护机制 当服务未按时进行心跳续约时 Eureka会统计服务实例最近15分钟心跳续约的比例是否低于85%

在生产环境下 由于网络延迟等原因 心跳失败实例的比例很有可能超标
但若此时就把服务剔除出列表并不妥当 因为服务很可能没有宕机
因此 Eureka在这段时间内不会剔除任何服务实例 直到网络恢复正常

这在生产环境下很有效 保证了大多数服务依然可用 不过也有可能获取到失败的服务实例 因此服务调用者必须做好服务的失败容错

eureka:
  server:
    # 关闭自我保护模式(默认为true打开)
    enable-self-preservation: true

小结:

  • Eureka客户端(Client):

  • 服务提供者:
eureka:
  client:
    service-url:
      # 设置Eureka服务的默认地址 多个用逗号隔开
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    # 将实例的ip注册到eureka中 否则默认以主机名注册
    prefer-ip-address: true
    # 设置服务的ip地址
    ip-address: 127.0.0.1
    # 服务续约(renew)的间隔 默认为30秒
    lease-renewal-interval-in-seconds: 30
    # 服务失效时间 默认为90秒
    lease-expiration-duration-in-seconds: 90
  • 服务消费者:
eureka:
  client:
    service-url:
      # 设置Eureka服务的默认地址
      defaultZone: http://127.0.0.1:10086/eureka
    # 获取服务器可用地址列表的时间 默认为30秒
    registry-fetch-interval-seconds: 30
  instance:
    # 将实例的ip注册到eureka中 否则默认以主机名注册
    prefer-ip-address: true
  • Eureka服务端(Server):

eureka:
  client:
    service-url:
      # 设置Eureka服务的默认地址 需要注意的是后面需带上一个/erueka
      # 若为集群 则需指定其它集群的Eureka地址
      defaultZone: http://127.0.0.1:10086/eureka
    # 当前服务不注册到Eureka 因为当前服务就是Eureka提供者本身 并不提供服务
    # 若为集群 该配置则要注释掉 为了让其它Eureka发现该Eureka
    register-with-eureka: false
    # 不从Eureka拉取服务
    fetch-registry: false
  server:
    # 服务失效剔除的时间间隔 默认为60秒 单位为毫秒
    eviction-interval-timer-in-ms: 60000
    # 关闭自我保护模式(默认为true打开)
    enable-self-preservation: false

©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页