1. 微服务的注册中心

注册中心可以说是微服务架构中的”通讯录“,它记录了服务和服务地址的映射关系。在分布式架构中,
服务会注册到这里,当服务需要调用其它服务时,就这里找到服务的地址,进行调用
在这里插入图片描述

1.1 注册中心的主要作用

服务注册中心(下称注册中心)是微服务架构非常重要的一个组件,在微服务架构里主要起到了协调者
的一个作用。注册中心一般包含如下几个功能:

  1. 服务发现:
    服务注册/反注册:保存服务提供者和服务调用者的信息
    服务订阅/取消订阅:服务调用者订阅服务提供者的信息,最好有实时推送的功能
    服务路由(可选):具有筛选整合服务提供者的能力。
  2. 服务配置:
    配置订阅:服务提供者和服务调用者订阅微服务相关的配置
    配置下发:主动将配置推送给服务提供者和服务调用者
  3. 服务健康检测
    检测服务提供者的健康情况

1.2 常见的注册中心

  • Zookeeper
    zookeeper它是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式应
    用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项
    的管理等。简单来说zookeeper=文件系统+监听通知机制。
  • Eureka
    Eureka是在Java语言上,基于Restful Api开发的服务注册与发现组件,Springcloud Netflix中的重要组
  • Consul
    Consul是由HashiCorp基于Go语言开发的支持多数据中心分布式高可用的服务发布和注册服务软件,
    采用Raft算法保证服务的一致性,且支持健康检查。
  • Nacos
    Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。简单来说 Nacos 就是
    注册中心 + 配置中心的组合,提供简单易用的特性集,帮助我们解决微服务开发必会涉及到的服务注册
    与发现,服务配置,服务管理等问题。Nacos 还是 Spring Cloud Alibaba 组件之一,负责服务注册与
    发现

    在这里插入图片描述

2. Eureka的概述

2.1 Eureka的基础知识

Eureka是Netflix开发的服务发现框架,SpringCloud将它集成在自己的子项目spring-cloud-netflix中,
实现SpringCloud的服务发现功能。
在这里插入图片描述
上图简要描述了Eureka的基本架构,由3个角色组成:
1、Eureka Server
提供服务注册和发现
2、Service Provider
服务提供方
将自身服务注册到Eureka,从而使服务消费方能够找到
3、Service Consumer
服务消费方
从Eureka获取注册服务列表,从而能够消费服务

2.2 Eureka的交互流程与原理

在这里插入图片描述

  • Application Service 相当于本书中的服务提供者,Application Client相当于服务消费者;
  • Make Remote Call,可以简单理解为调用RESTful API;
  • us-east-1c、us-east-1d等都是zone,它们都属于us-east-1这个region;

由图可知,Eureka包含两个组件:Eureka Server 和 Eureka Client,它们的作用如下:

  • Eureka Client是一个Java客户端,用于简化与Eureka Server的交互;
  • Eureka Server提供服务发现的能力,各个微服务启动时,会通过Eureka Client向Eureka Server
    进行注册自己的信息(例如网络信息),Eureka Server会存储该服务的信息;
  • 微服务启动后,会周期性地向Eureka Server发送心跳(默认周期为30秒)以续约自己的信息。如
    果Eureka Server在一定时间内没有接收到某个微服务节点的心跳,Eureka Server将会注销该微服
    务节点(默认90秒);
  • 每个Eureka Server同时也是Eureka Client,多个Eureka Server之间通过复制的方式完成服务注
    册表的同步;
  • Eureka Client会缓存Eureka Server中的信息。即使所有的Eureka Server节点都宕掉,服务消费
    者依然可以使用缓存中的信息找到服务提供者。

综上,Eureka通过心跳检测、健康检查和客户端缓存等机制,提高了系统的灵活性、可伸缩性和可用

3 搭建Eureka注册中心

3.1 搭建Eureka服务中心

  1. 创建shop_eureka_server子模块
    在 shop_parent 下创建子模块 shop_eureka_server
  • pom.xml
  <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>
  • 配置application.yml
server:
  port: 9000 #端口
# 配置eureka-server
eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: false #s是否将自己注册到注册中心
    fetch-registry: false #是否从eureka中获取注册信息
    # 配置暴露给eureka client的请求地址\
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

registerWithEureka: 是否将自己注册到Eureka服务中,本身就是所有无需注册
fetchRegistry : 是否从Eureka中获取注册信息
serviceUrlEureka: 客户端与Eureka服务端进行交互的地址

  • 配置启动类
    @EnableEurekaServer 激活Eureka Server端配置
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
 public static void main(String[] args) {
 SpringApplication.run(EurekaServerApplication.class, args);
 }
}

结果
在这里插入图片描述

3.2 服务注册到Eureka注册中心

  • pom.xml
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
  • 配置application.yml文件
#配置Eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/ #多个eurekaserver之间用,隔开
  instance:
    prefer-ip-address: true #使用ip地址注册
    instance-id: ${spring.cloud.client.ip-address}:${server.port} #向注册中心中注册服务id
    #lease-renewal-interval-in-seconds: 5 #向注册中心中注册服务id
   # lease-expiration-duration-in-seconds: 10 #续约到期的时间
  • 修改启动类添加服务注册注解
    这个选项是可选的 不进行注解也可以 只要配置了eureka的注册中心 就会自动进行注册
    //激活eurekaClient
    //@EnableEurekaClient
    //@EnableDiscoveryClient
    这两个注解的作用是一样的 只是 出厂不一样

3.3 EurekaService的高可用

在这里插入图片描述
准备两个EurekaService需要相互作用
1号service 9000
2号service 8000
需要将微服务注册到两个Eureka上
9000的yml


server:
  port: 9000 #端口
# 配置eureka-server
eureka:
  #instance:

  client:
    #register-with-eureka: false #s是否将自己注册到注册中心
    #fetch-registry: false #是否从eureka中获取注册信息
    # 配置暴露给eureka client的请求地址\
    service-url:
      defaultZone: http://127.0.0.1:8000/eureka/
spring:
  application:
    name: EurekaService1

8000的yml

spring:
  application:
    name: EurekaService2
server:
  port: 8000 #端口
# 配置eureka-server
eureka:
  #instance:

  client:
    #register-with-eureka: false #s是否将自己注册到注册中心
    #fetch-registry: false #是否从eureka中获取注册信息
    # 配置暴露给eureka client的请求地址\
    service-url:
      defaultZone: http://127.0.0.1:9000/eureka/

因为提供服务的微服务只注册到了9000上
但是只要两个server互通就可以同步到另一个服务上
在这里插入图片描述
在这里插入图片描述

3.4 将一个服务注册到两个EurekaServer上

#配置Eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/,http://localhost:8000/eureka/ #多个eurekaserver之间用,隔开

微服务分别注册到两个EurekaServer中,可以信息同步进去
当然可以注册到两个服务上 服务url用逗号隔开即可

3.5 细节问题

1. 在控制台上显示ip
#配置Eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/,http://localhost:8000/eureka/ #多个eurekaserver之间用,隔开
  instance:
    prefer-ip-address: true #使用ip地址注册
    instance-id: ${spring.cloud.client.ip-address}:${server.port} #向注册中心中注册服务id

2. 设置服务续约到期时间

在服务提供方进行配置

#配置Eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/,http://localhost:8000/eureka/ #多个eurekaserver之间用,隔开
  instance:
    prefer-ip-address: true #使用ip地址注册
    instance-id: ${spring.cloud.client.ip-address}:${server.port} #向注册中心中注册服务id
    lease-renewal-interval-in-seconds: 5 #向注册中心中注册服务id
    lease-expiration-duration-in-seconds: 10 #续约到期的时间
3. 关闭自我保护机制

在eurekaServer中进行配置

eureka:
  #instance:

  client:
    register-with-eureka: false #s是否将自己注册到注册中心
    fetch-registry: false #是否从eureka中获取注册信息
    # 配置暴露给eureka client的请求地址\
    service-url:
      defaultZone: http://127.0.0.1:9000/eureka/
  server:
    enable-self-preservation: false  #将自我保护机制关闭

3.6 Eureka源码解析

1. Springboot的自动装载
1.1 ImportSelector

ImportSelector接口是Spring导入外部配置的核心接口,在SpringBoot的自动化配置和@EnableXXX(功能性注解)中起到了决定性的作用。当在@Configuration标注的Class上使用@Import引入了一个ImportSelector实现类后,会把实现类中返回的Class名称都定义为bean。

public interface ImportSelector {
   String[] selectImports(AnnotationMetadata var1);
}

DeferredImportSelector接口继承ImportSelector,他和ImportSelector的区别在于装载bean的时机上,DeferredImportSelector需要等所有@Configuration都执行完毕后才会进行装载

public interface DeferredImportSelector extends ImportSelector {
 //...省略
}

接下来写一个小小的例子

  • 定义bean
@Data
public class User {
    private String username;
    private Integer age;
}

  • 定义配置类
public class UserConfigtion {

    @Bean
    public User getUser(){
        User user = new User();
        user.setAge(11);
        user.setUsername("tjk");
        return user;
    }
}
  • 定义UserImportSelector
public class UserImportSelector  implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        //获取配置类的名称
        return new String[]{UserConfigtion.class.getName()};
    }
}
  • 定义EnableUserBean注解
@Retention(RetentionPolicy.RUNTIME)//规定什么时候运行
@Documented
@Target(ElementType.TYPE)//注解什么时候起作用
@Import(UserImportSelector.class)
public @interface EnableUserBean {
}
  • 测试
@EnableUserBean
public class TestApplication {
    public static void main(String[] args) {
        //获取SPringle容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestApplication.class);
        User user = applicationContext.getBean(User.class);
        System.out.println(user);

    }
}
  • 结果
    在这里插入图片描述
    由此可见,对象并没有使用Spring的对象对象创建注解声明(@Controller,@Service,@Repostiroty),而是使用编程的方式动态的载入bean。
    这个接口在哪里调用呢?我们可以来看一下ConfigurationClassParser这个类的processImports方法
private void processImports(ConfigurationClass configClass, SourceClass
currentSourceClass,
            Collection<SourceClass> importCandidates, boolean
checkForCircularImports) {
        if (importCandidates.isEmpty()) {
            return;
       }
        if (checkForCircularImports && isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, 
this.importStack));
       }
         else {
            this.importStack.push(configClass);
            try {
                for (SourceClass candidate : importCandidates) 
{ //对ImportSelector的处理
                    if (candidate.isAssignable(ImportSelector.class)) {
                        // Candidate class is an ImportSelector -> delegate to 
it to determine imports
                        Class<?> candidateClass = candidate.loadClass();
                        ImportSelector selector =
BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                        ParserStrategyUtils.invokeAwareMethods(
                                selector, this.environment, this.resourceLoader, 
this.registry);
                        if (this.deferredImportSelectors != null && selector
instanceof DeferredImportSelector) { //如果为延迟导入处理
则加入集合当中
                            this.deferredImportSelectors.add(
                                    new
DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
                       }
                        else { //根据ImportSelector方法
的返回值来进行递归操作
                            String[] importClassNames =
selector.selectImports(currentSourceClass.getMetadata());
                            Collection<SourceClass> importSourceClasses =
asSourceClasses(importClassNames);
                            processImports(configClass, currentSourceClass, 
importSourceClasses, false);
                       }
                   }
                    else if
(candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        // Candidate class is an ImportBeanDefinitionRegistrar -
>
                        // delegate to it to register additional bean 
definitions
                        Class<?> candidateClass = candidate.loadClass();
                        ImportBeanDefinitionRegistrar registrar =
                                BeanUtils.instantiateClass(candidateClass, 
ImportBeanDefinitionRegistrar.class);
                        ParserStrategyUtils.invokeAwareMethods(
                                registrar, this.environment, 
this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, 
currentSourceClass.getMetadata());
                   }
                    else { // 如果当前的类既不是
ImportSelector也不是ImportBeanDefinitionRegistar就进行@Configuration的解析处理
                        // Candidate class not an ImportSelector or 
ImportBeanDefinitionRegistrar ->
                        // process it as an @Configuration class
                        this.importStack.registerImport(
                                currentSourceClass.getMetadata(), 
candidate.getMetadata().getClassName());
                       
processConfigurationClass(candidate.asConfigClass(configClass));
                   }
                       }
           }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
           }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to process import candidates for configuration 
class [" +
                        configClass.getMetadata().getClassName() + "]", ex);
           }
            finally {
                this.importStack.pop();
           }
       }
   }

在这里我们可以看到ImportSelector接口的返回值会递归进行解析,把解析到的类全名按照@Configuration进行处理

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐