SpringCloud

SpringCloud

一、概述

1.1 简介

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务注册发现、配置中心、消息总线、
负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、
易部署和易维护的分布式系统开发工具包。

二、详解

2.1 Spring Cloud注册中心

服务注册与发现的基本框架包括三部分:服务提供者、注册中心和服务消费者。其中,服务提供者将自己注册到注册中心(服务注册),
服务消费者从服务注册中心获取可用的服务列表(服务发现)。

2.1.1 Spring Cloud注册中心

Spring Cloud提供了几种服务注册与发现的实现方式,包括:

  1. Eureka:Netflix公司开源的、最为流行的一种服务注册中心(已闭源);
  2. Consul:由Hashicorp公司开发的一种服务发现组件,支持多数据中心;
  3. ZooKeeper:由Apache项目开发的一种分布式协调框架。

这里以Eureka为例,介绍Spring Cloud注册中心。

首先需要在pom.xml文件中添加以下依赖:

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

接着,在启动类中加上@EnableEurekaServer注解,表明该服务作为Eureka Server运行:


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

在配置文件application.yml中指定服务端口、Eureka相关配置:

server:
  port: 8761

eureka:
  client:
    register-with-eureka: false #服务注册:用于生产者
    fetch-registry: false # 服务发现:获取注册信息用于消费者
    service-url: # eureka服务地址
      defaultZone: ${EUREKA_SERVER:http://localhost:${server.port}/eureka/}
  server:
    enable-self-preservation: false # 是否开启自我保护
    eviction-interval-timer-in-ms: 60000 #服务注册表的清理间隔(根据健康状态信息,去除宕机服务)

2.1.2 Spring Cloud服务注册与发现

以Eureka为例,介绍Spring Cloud服务注册与发现的实现方式。

服务提供者和服务消费者需要在pom.xml文件中添加以下依赖:

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

在启动类中加上@EnableDiscoveryClient注解,表明该服务需要作为Eureka Client运行,并向Eureka Server注册:


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

同时,在配置文件application.yml中指定应用名称、Eureka相关配置:

spring:
  application:
    name: demp-service

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

2.2 Spring Cloud配置中心

2.2.1 常见的配置中心

  1. Nacos:阿里巴巴开发维护的,致力于帮助您发现、配置和管理微服务。Nacos: Dynamic Naming and Configuration Service。
  2. Spring Cloud Config:Spring原生的配置中心,Spring Cloud Config为分布式系统中的外部化配置提供服务器端和客户端支持。
  3. Apollo(阿波罗):携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、
    流程治理等特性,适用于微服务配置管理场景。

2.2.2 原理

2.2.2.1 BootstrapApplicationListener

SpringApplication的run方法执行时,当执行到prepareEnvironment环境准备时,会广播ApplicationEnvironmentPreparedEvent事件,此时
BootstrapApplicationListener监听到这个事件,开始执行自己的代码。

ApplicationEnvironmentPreparedEvent事件有两个特别重要的监听器:

  1. EnvironmentPostProcessorApplicationListener:用来处理本地配置文件的,他会解析配置文件的配置,放到Environment中
  2. BootstrapApplicationListener:用来专门来跟配置中心交互的

在这里插入图片描述

源码:

public class BootstrapApplicationListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        // 获取环境变量对象
        ConfigurableEnvironment environment = event.getEnvironment();
        // 读取spring.cloud.bootstrap.enabled环境属性,默认为false,可通过系统变量设置,MARKER_CLASS存在时也返回true
        if (!bootstrapEnabled(environment) && !useLegacyProcessing(environment)) {
            return;
        }
        // don't listen to events in a bootstrap context
        if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
            return;
        }
        // 寻找当前环境是否已存在BootstrapContext
        ConfigurableApplicationContext context = null;
        String configName = environment.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
        for (ApplicationContextInitializer<?> initializer : event.getSpringApplication().getInitializers()) {
            if (initializer instanceof ParentContextApplicationContextInitializer) {
                context = findBootstrapContext((ParentContextApplicationContextInitializer) initializer, configName);
            }
        }
        // 如果还没有被创建,则开始创建
        if (context == null) {
            // 这个方法是整个Bootstrap的核心
            context = bootstrapServiceContext(environment, event.getSpringApplication(), configName);
            // 注册注销监听器
            event.getSpringApplication().addListeners(new CloseContextOnFailureApplicationListener(context));
        }
        // 加载BoostrapContext上的ApplicationContextInitializers到用户Context上
        apply(context, event.getSpringApplication(), environment);
    }

    /**
     *
     *  create bootstrap context
     *
     * @param environment   全局Environment
     * @param application   SpringApplication
     * @param configName    bootstrapContext对应配置文件的加载名,默认为bootstrap
     * @return bootstrapContext
     */
    private ConfigurableApplicationContext bootstrapServiceContext(ConfigurableEnvironment environment,
                                                                   final SpringApplication application, String configName) {
        // 构建一空的 bootstrapEnvironment 对象
        ConfigurableEnvironment bootstrapEnvironment = new AbstractEnvironment() {
        };
        MutablePropertySources bootstrapProperties = bootstrapEnvironment.getPropertySources();
        // 读取spring.cloud.bootstrap.location属性,一般通过系统变量设置,默认为空
        String configLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
        String configAdditionalLocation = environment
                .resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
        Map<String, Object> bootstrapMap = new HashMap<>();
        bootstrapMap.put("spring.config.name", configName);
        bootstrapMap.put("spring.main.web-application-type", "none");
        if (StringUtils.hasText(configLocation)) {
            bootstrapMap.put("spring.config.location", configLocation);
        }
        if (StringUtils.hasText(configAdditionalLocation)) {
            bootstrapMap.put("spring.config.additional-location", configAdditionalLocation);
        }
        bootstrapProperties.addFirst(new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
        for (PropertySource<?> source : environment.getPropertySources()) {
            if (source instanceof StubPropertySource) {
                continue;
            }
            bootstrapProperties.addLast(source);
        }
        // TODO: is it possible or sensible to share a ResourceLoader?
        SpringApplicationBuilder builder = new SpringApplicationBuilder()
                // 此处activeProfiles是通过系统变量设置的,此处稍微备注下
                .profiles(environment.getActiveProfiles())
                .bannerMode(Mode.OFF)
                // 应用bootstrap本身的环境变量
                .environment(bootstrapEnvironment)
                // Don't use the default properties in this builder
                .registerShutdownHook(false).logStartupInfo(false).web(WebApplicationType.NONE);
        final SpringApplication builderApplication = builder.application();
        // 配置入口函数类
        if (builderApplication.getMainApplicationClass() == null) {
            builder.main(application.getMainApplicationClass());
        }
        if (environment.getPropertySources().contains("refreshArgs")) {
            builderApplication.setListeners(filterListeners(builderApplication.getListeners()));
        }
        // 增加入口类BootstrapImportSelectorConfiguration
        builder.sources(BootstrapImportSelectorConfiguration.class);
        final ConfigurableApplicationContext context = builder.run();
        // 设置bootstrapContext的别名为bootstrap
        context.setId("bootstrap");
        // 配置bootstrapContext为用户Context的父类
        addAncestorInitializer(application, context);
        // 合并defaultProperties对应的变量至childEnvironment
        bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
        mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
        return context;
    }

    private void apply(ConfigurableApplicationContext context, SpringApplication application,
                       ConfigurableEnvironment environment) {
        if (application.getAllSources().contains(BootstrapMarkerConfiguration.class)) {
            return;
        }
        application.addPrimarySources(Arrays.asList(BootstrapMarkerConfiguration.class));
        // 将application容器的初始化器和bootstrap隔离容器中的初始化器进行了合并,并设置到application容器所对应的SpringApplication当中。
        Set target = new LinkedHashSet<>(application.getInitializers());
        target.addAll(getOrderedBeansOfType(context, ApplicationContextInitializer.class));
        application.setInitializers(target);
        addBootstrapDecryptInitializer(application);

        environment.setActiveProfiles(context.getEnvironment().getActiveProfiles());
    }
}
2.2.2.2 BootstrapConfiguration

@Configuration(proxyBeanMethods = false)
@Import(BootstrapImportSelector.class)
public class BootstrapImportSelectorConfiguration {

}

BootstrapImportSelector

作用:加载META-INF/spring.factories中BootstrapConfiguration的实现。

public class BootstrapImportSelector implements EnvironmentAware, DeferredImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // Use names and ensure unique to protect against duplicates
        List<String> names = new ArrayList<>(
                SpringFactoriesLoader.loadFactoryNames(BootstrapConfiguration.class, classLoader));
        names.addAll(Arrays.asList(StringUtils
                .commaDelimitedListToStringArray(this.environment.getProperty("spring.cloud.bootstrap.sources", ""))));

        List<OrderedAnnotatedElement> elements = new ArrayList<>();
        for (String name : names) {
            try {
                elements.add(new OrderedAnnotatedElement(this.metadataReaderFactory, name));
            } catch (IOException e) {
                continue;
            }
        }
        AnnotationAwareOrderComparator.sort(elements);

        String[] classNames = elements.stream().map(e -> e.name).toArray(String[]::new);

        return classNames;
    }
}
2.2.2.2.1 PropertySourceBootstrapConfiguration

它本身是一个ApplicationContext的初始化器,并且会自动注入Bootstrap隔离容器中配置的所有的Locator,这个Locator是实现分布式配置中心的关键。
但是这个组件并不会在Bootstrap隔离容器中生效,这个组件,实际上会在app容器中去生效,在BootstrapApplicationListener的apply方法中将bootstrap
隔离容器中的初始化器合并到application容器中,并在application容器中生效:SpringApplication.run --> prepareContext -->
applyInitializers


@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class PropertySourceBootstrapConfiguration implements ApplicationListener<ContextRefreshedEvent>,
        ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

    public static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME
            + "Properties";

    private static Log logger = LogFactory.getLog(PropertySourceBootstrapConfiguration.class);

    private int order = Ordered.HIGHEST_PRECEDENCE + 10;
    // 依赖注入
    @Autowired(required = false)
    private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();

    @Autowired
    private PropertySourceBootstrapProperties bootstrapProperties;

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        if (!bootstrapProperties.isInitializeOnContextRefresh() || !applicationContext.getEnvironment()
                .getPropertySources().contains(BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
            doInitialize(applicationContext);
        }
    }

    private void doInitialize(ConfigurableApplicationContext applicationContext) {
        List<PropertySource<?>> composite = new ArrayList<>();
        AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
        boolean empty = true;
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        for (PropertySourceLocator locator : this.propertySourceLocators) {
            // 回调所有实现PropertySourceLocator接口实例的locate方法,例如:NacosPropertySourceLocator
            Collection<PropertySource<?>> source = locator.locateCollection(environment);
            if (source == null || source.size() == 0) {
                continue;
            }
            List<PropertySource<?>> sourceList = new ArrayList<>();
            for (PropertySource<?> p : source) {
                if (p instanceof EnumerablePropertySource) {
                    EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) p;
                    sourceList.add(new BootstrapPropertySource<>(enumerable));
                } else {
                    sourceList.add(new SimpleBootstrapPropertySource(p));
                }
            }
            logger.info("Located property source: " + sourceList);
            composite.addAll(sourceList);
            empty = false;
        }
        if (!empty) {
            MutablePropertySources propertySources = environment.getPropertySources();
            String logConfig = environment.resolvePlaceholders("${logging.config:}");
            LogFile logFile = LogFile.get(environment);
            for (PropertySource<?> p : environment.getPropertySources()) {
                if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
                    propertySources.remove(p.getName());
                }
            }
            // //将刚获取到的配置变量添加到环境中
            insertPropertySources(propertySources, composite);
            reinitializeLoggingSystem(environment, logConfig, logFile);
            setLogLevels(applicationContext, environment);
            handleProfiles(environment);
        }
    }

    /**
     * 将获取到的对象添加至environment中,重点关注的添加后环境变量的优先级
     * @param propertySources
     * @param composite
     */
    private void insertPropertySources(MutablePropertySources propertySources, List<PropertySource<?>> composite) {
        MutablePropertySources incoming = new MutablePropertySources();
        List<PropertySource<?>> reversedComposite = new ArrayList<>(composite);
        // 反转集合,后续可以按照原顺序加入当前环境集合中
        Collections.reverse(reversedComposite);
        for (PropertySource<?> p : reversedComposite) {
            incoming.addFirst(p);
        }
        // overrideSystemProperties: 外部属性可以覆盖系统属性 默认true
        // allowOverride: 允许覆盖 默认true
        // overrideNone: 当allowOverride为true时,外部属性应该具有最低优先级,并且不应该覆盖任何现有的属性源(包括本地配置文件),默认false
        PropertySourceBootstrapProperties remoteProperties = new PropertySourceBootstrapProperties();
        Binder.get(environment(incoming)).bind("spring.cloud.config", Bindable.ofInstance(remoteProperties));
        // 默认赋值方式
        if (!remoteProperties.isAllowOverride()
                || (!remoteProperties.isOverrideNone() && remoteProperties.isOverrideSystemProperties())) {
            for (PropertySource<?> p : reversedComposite) {
                if (propertySources.contains(DECRYPTED_PROPERTY_SOURCE_NAME)) {
                    propertySources.addAfter(DECRYPTED_PROPERTY_SOURCE_NAME, p);
                } else {
                    // 添加进当前环境属性源中,远程配置优先级高
                    propertySources.addFirst(p);
                }
            }
            return;
        }
        // 远程配置优先级低
        if (remoteProperties.isOverrideNone()) {
            for (PropertySource<?> p : composite) {
                propertySources.addLast(p);
            }
            return;
        }
        if (propertySources.contains(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME)) {
            if (!remoteProperties.isOverrideSystemProperties()) {
                for (PropertySource<?> p : reversedComposite) {
                    propertySources.addAfter(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, p);
                }
            } else {
                for (PropertySource<?> p : composite) {
                    propertySources.addBefore(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, p);
                }
            }
        } else {
            for (PropertySource<?> p : composite) {
                propertySources.addLast(p);
            }
        }
    }
}
  1. 通过Spring注入PropertySourceLocator对象。
  2. 并且通过PropertySourceLocator对象的locateCollection方法获取到配置变量。
  3. 将刚获取到的配置变量加载到environment中。

三、其他

3.1 Spring Cloud 和 SpringBoot 版本依赖关系

参考–官网:Spring Cloud

3.2 SpringCloud 和 Spring Cloud Alibaba 版本对应关系

参考–https://sca.aliyun.com/zh-cn/docs/2022.0.0.0/overview/version-explain

3.3 SpringCloud中Bootstrap配置

在SpringBoot 2.4.x的版本之后,对于bootstrap.properties/bootstrap.yaml配置文件(我们合起来成为Bootstrap配置文件)
的支持,需要导入如下的依赖:

    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-bootstrap</artifactId>
   </dependency>

该依赖有两个作用:

  1. 这个jar包包含了一个标识类Marker,用来标识要开启Bootstrap配置文件的支持
  2. 引入了 spring-cloud-starter 依赖。

实际上,通过在启动参数中配置相关的属性也可以–spring.cloud.bootstrap.enabled=true(因为Bootstrap的监听器优先级比较高,
因此配置在application配置文件当中无效,application配置文件还未加载到环境当中来,所以必须在启动参数中去进行配置),
不用非得导入jar包(前提容器中已经有了cloud-context的jar包,因为这个jar包当中导入了BootstrapApplicationListener组件)。

官网:https://docs.spring.io/spring-cloud-config/reference/client.html

3.4 微服务

“微服务 ”是在2014年3月名字叫Martin Fowler所提出的,可以在他的官方博客上找到:
microservices

微服务是系统架构上的一种设计风格, 它的主旨是将一个原本独立的系统拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间通过基于HTTP的RESTful
API进行通信协作。由于有了轻量级的通信协作基础, 所以这些微服务可以使用不同的语言来编写。

优点:

  1. 灵活性高:它将应用程序分解为小型服务(松散耦合),使其开发、维护更快,更易于理解,可以提供更高的灵活性;
  2. 独立扩展:它使每个服务能够独立扩展,将系统中的不同功能模块拆分成多个不同的服务,这些服务进行独立地开发和部署,每个服务都运行在自己的进程内,
    这样每个服务的更新都不会影响其他服务的运行;
  3. 支持多种编程语言:微服务可通过最佳及最合适的不同的编程语言与工具进行开发,能够做到有的放矢地解决针对性问题;
  4. 自动部署与持续集成工具集成:它允许以灵活的方式将自动部署与持续集成工具集成,例如Jenkins,Hudson等;
  5. 通用性:通过服务实现应用的组件化(按功能拆分、可独立部署和维护),围绕业务能力组织服务,根据业务不同的需求进行不同组件的使用,所做产品非项目化,对于平台具有一定的通用性。

缺点:

  1. 处理故障难度高:微服务架构是一个分布式系统,必须构建一个相互通信机制并处理部分故障;
  2. 部署工作量大:单体应用程序可以部署在负载平衡器后面的相同服务器上。但对于微服务,每个服务都有不同的实例,每个实例都需要配置、部署、缩放和监控;
  3. 测试复杂度高:微服务在一定程度上也会导致系统变得越来越复杂,增加了集成测试的复杂度;
  4. 运营成本增加:单体应用可能只需部署至一小片应用服务区集群,而微服务架构可能变成需要构建/测试/部署/运行数十个独立的服务,并可能需要支持多种语言和环境。
    这导致一个整体式系统如果由20个微服务组成,可能需要40~60个进程;
  5. 发布风险高:把系统分为多个协作组件后会产生新的接口,这意味着简单的交叉变化可能需要改变许多组件,并需协调一起发布。在实际环境中,
    一个新品发布可能被迫同时发布大量服务,由于集成点的大量增加,微服务架构会有更高的发布风险;
  6. 分布性系统问题:作为一种分布式系统,微服务引入了复杂性和其他若干问题,例如网络延迟、容错性、消息序列化、不可靠的网络、异步机制、版本化、
    差异化的工作负载等,开发人员需要考虑以上的分布式系统问题。
  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fanderboy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值