SpringCloud学习笔记(一) 搭建一个SpringCloud

简介

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

反正SpringCloud是一个用来做微服务的框架,主要是基于SpringBoot来完成的,相关的微服务框架还有比较出名的Dubbo和DubboX。关于微服务是用来做什么的,有什么好处自行百度。

SpringCloud是Spring全家桶中的一员,所以SpringCloud对于Spring的其他组件都有着很好的支持。

SpringCloud主要采用的Eureka,Dubbo主要采用的是zookeeper。

上代码

provider 服务提供者

直接新建一个SpringBoot项目,由于是为了示例,本次示例中不使用数据库,只引入了spring-boot-starter-web

项目及其简单,只暴露了一个接口,用于测试,yml文件只配置了端口号为7900

这里写图片描述

consumer 服务消费者

同样新建一个SpringBoot项目,使其调用consumer的接口,同样只引入了spring-boot-starter-web,yml文件只是修改了端口号为7910。

这里写图片描述

这里解释一下RestTemplates这个类,关于这个类,这个老哥https://blog.csdn.net/u012702547/article/details/77917939的解释我觉得非常完整了,RestTemplates在SpringCloud中,消费者通过这个类访问生产者,@bean注解是为了实例化这个类,实例化之后通过@AutoWired注解引入,将其交给Spring进行管理。

分别启动两个类,分别访问localhost:7900/provider/demohttp://localhost:7910/consumer/demo应该都能得到ProviderDemo这个结果,说明consumer成功调用了provider中的方法。

问题:
这样的编码方式是将接口(http://localhost:7900/provider/demo)硬编码在代码中,但是项目发布之后,ip地址必然是变动的。而且,硬编码的方式肯定是无法实现负载均衡的,就是说如果同时启动多个provider服务,这种硬编码方式是无法根据负载均衡策略去调用服务的。

解决方法:
在Dubbo中使用的ZooKeeper作为服务注册与发现的容器,在Springcloud中使用的是Eureka作为服务注册与发现的容器。

Eureka

同样创建一个SPringBoot项目,下面是pom.xml文件,我是在idea中直接添加spring-cloud-starter-netflix-eureka-server这个依赖的。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.edu.nuaa</groupId>
    <artifactId>eureka-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka-demo</name>
    <description>eureka-demo</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
    </properties>

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

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

  
  

    然后在Springboot的启动类上添加@EnableEurekaServer或者@EnableDiscoveryClient,这两个注解的注解的区别主要是:

    @EnableEurekaServer是基于 spring-cloud-netflix依赖,只能为eureka作用,是专门给Eureka用的
    @EnableDiscoveryClient是基于 spring-cloud-commons依赖,并且在classpath中实现,是给比如zookeeper、consul使用的,
    旧版本的@EnableEurekaServer的源码上面也是也是有@EnableDiscoveryClient注解的。
    本示例是使用Eureka的,故推荐使用@EnableEurekaServer

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

      然后在yml文件中配置如下信息:

      server:
        port: 8761
      eureka:
        client:
          register-with-eureka: false
          fetch-registry: false
          service-url:
            defaultZone: http://localhost:8761/eureka/
        
        

        这个端口号官网设置成8761,这里也设置成8761;

        `register-with-eureka: false这个的默认值为true,设置为true不会对使用不会有很大的影响,但是在启动的时候会保下面的错误:
        was unable to refresh its cache! status = Cannot execute request on any known server
        是因为启动的时候自己注册了自己而引起的冲突

        defaultZone配置eureka的地址,这个如果有多个注册中心,则用逗号隔开。
        为了服务注册中心的安全考虑,很多时候我们都会为服务注册中心加入安全认证。通常与SpringSecurity整合,主要是SpringSecurity的内容,这里不做讨论(主要是我自己尝试的时候失败了,啊哈哈哈)。

        配置信息完成之后,启动eureka项目,访问localhost:8761便可以得到下面的界面,目前上面两个写的provider和consumer还没有注册到该注册中心:
        这里写图片描述

        关于Eureka配置的含义及其默认值主要参考下表:

        服务注册类配置

        省略了eureka.client前缀

        参数名 说明 默认值
        enabled 启用Eureka客户端 true
        registryFetchIntervalSeconds 从Eureka服务端获取注册信息的间隔时间,单位为秒 30
        instanceInfoReplicationIntervalSeconds 更新实例信息的变化到Eureka服务端的间隔时间,单位为秒 30
        initialInstanceInfoReplicationIntervalSeconds 初始化实例信息到Eureka服务端的间隔时间,单位为秒 40
        eurekaServiceUrlPollIntervalSeconds 轮询Eureka服务端地址更改的间隔时间,单位为秒。当我们与Spring CLoud Config整合,动态刷新Eureka的serviceURL地址时需要关注该参数 300
        eurekaServerReadTimeoutSeconds 读取Eureka Server信息的超时时间,单位为秒 8
        eurekaServerConnectTimeoutSeconds 链接Eureka Server的超时时间,单位为秒 5
        eurekaServerTotalConnections 从Eureka客户端到所有Eureka服务端的连接总数 200
        eurekaServerTotalConnectionsPerHost 从Eureka客户端到每个Eureka服务端主机的连接总数 50
        eurekaConnectionIdleTimeoutSeconds Eureka服务端连接的空闲关闭时间,单位为秒 30
        heartbeatExecutorThreadPoolSize 心跳连接池的初始化线程数 2
        heartbeatExecutorExponentialBackOffBound 心跳超时重试延迟时间的最大乘数值 10
        cacheRefreshExecutorThreadPoolSize 缓存刷新线程池的初始化线程数 2
        cacheRefreshExecutorExponentialBackOffBound 缓存刷新重试延迟时间的最大乘数值 10
        useDnsForFetchingServiceUrls 使用DNS来获取Eureka服务端的serviceUrl false
        registerWithEureka 是否要将自身的实例信息注册到Eureka服务端 true
        preferSameZoneEureka 是否偏好使用处于相同Zone的Eureka服务端 true
        filterOnlyUpInstances 获取实例时是否过滤,仅保留UP状态的实例 true
        fetchRegistry 是否从Eureka服务端获取注册信息 true

        服务实例类配置

        eureka.instance.instanceId为实例的id,可以自定义,默认值为

        ${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}
          
          

          省略了eureka.instance为前缀

          参数名 说明 默认值
          preferIpAddress 是否优先使用IP地址作为主机名的标识 false
          leaseRenewalIntervalInSeconds Eureka客户端向服务端发送心跳的时间间隔,单位为秒 30
          leaseExpirationDurationInSeconds Eureka服务端在收到最后一次心跳之后等待的时间上限,单位为秒。超过该时间之后服务端会将该服务实例从服务清单中剔除,从而禁止服务调用请求被发送到该实例上 90
          nonSecurePort 非安全的通信端口号 80
          securePort 安全的通信端口号 443
          nonSecurePortEnabled 是否启用非安全的通信端口号 true
          securePortEnabled 是否启用安全的通信端口号
          appname 服务名,默认取spring.application.name的配置值,如果没有则为unknown
          hostname 主机名,不配置的时候讲根据操作系统的主机名来获取

          注册服务到Eureka

          回到之前写的provider-demo项目中,在项目中添加下面这个依赖,注意version:

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

          在之前的SpringCloud版本中,还有出现过下面的依赖,但是目前最新的版本是上面这个,亲测,在本示例中两个都是可以的。

          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-eureka</artifactId>
              <version>RELEASE</version>
          </dependency>
            
            

            然后在项目的启动类上添加@EnableEurekaClient注解

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

              从依赖的名字spring-cloud-starter-netflix-eureka-client的名字就知道这个依赖是用来做eureka的客户端,然后注册服务到eureka上的。然后在项目的启动类上的添加的注解为@EnableEurekaClient

              回到上文中会发现搭建eureka注册中心的时候会发现引入的包是spring-cloud-starter-netflix-eureka-server,在项目的启动类上的添加的注解为@EnableEurekaServer还是很好记忆和理解的。

              最后在yml文件中添加上eureka注册中心的地址并可以了:

              server:
                port: 7900
              eureka:
                client:
                  serviceUrl:
                    defaultZone: http://localhost:8761/eureka/
                
                

                分别启动一下eureka和provider项目,然后访问一下localhost:8761便会得到下面的页面:
                这里写图片描述

                可以看到provider的服务已经注册上eureka上了。
                发现application的名称为UNKNOWN,可以在yml文件中添加下面内容修改名字:

                spring:
                  application:
                    name: provider-demo
                  
                  

                  重启一下服务便可以看到名字,这里就不贴图片了。

                  有可能会遇到下面的红色字体:

                  EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

                  这个主要是eureka的自我保护的心跳机制导致的,即如果真实的服务已经Down掉,但在注册中心界面服务却一直存在,且显示为UP状态。解决方法可以添加下面内容,上面有eureka的配置信息中解释含义:

                  eureka:
                    client:
                      serviceUrl:
                        defaultZone: http://localhost:8761/eureka/
                    instance:
                      lease-renewal-interval-in-seconds: 1
                      lease-expiration-duration-in-seconds: 2
                    
                    

                    服务监控

                    对于服务的众多信息可能都需要监控和监听,SpringCloud主要采用的是下面这个依赖对其实现监听的:

                    <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-actuator</artifactId>
                    </dependency>
                      
                      

                      添加这个依赖之后重启服务会发现console中多了下图这几个端点:

                      这里写图片描述

                      /actuator/health和/actuator/info以及/actuator这三个就是actuator提供的端点,注意以前的版本是没有/actuator前缀的,2.0以后的版本都加了/actuator前缀。可以访问localhost:7900/actuator尝试看监听的都是一些什么内容

                      如果要开启全部的监听端点,可以在yml文件中加入下面的配置信息:

                      management:
                        endpoints:
                          web:
                            exposure:
                              include: "*"
                        
                        

                        重启服务便可以看到所有的端点都开启用了。

                        主要的端点的含义可以参考下面的表格:

                        端点 描述
                        actuator 为其他端点提供“发现页面”。要求Spring HATEOAS在classpath路径上。
                        auditevents 陈列当前应用程序的审计事件信息。
                        autoconfig 展示自动配置信息并且显示所有自动配置候选人以及他们“被不被”应用的原因。
                        beans 显示应用程序中所有Spring bean的完整列表。
                        configprops 显示所有配置信息。
                        dump dump所有线程。
                        env 陈列所有的环境变量。
                        flyway Shows any Flyway database migrations that have been applied.
                        health 显示应用程序运行状况信息
                        info 显示应用信息。
                        loggers 显示和修改应用程序中的loggers配置。
                        liquibase 显示已经应用的任何Liquibase数据库迁移。
                        metrics 显示当前应用程序的“指标”信息。
                        mappings 显示所有@RequestMapping的url整理列表。
                        shutdown 关闭应用(默认情况下不启用)。
                        trace 显示跟踪信息(默认最后100个HTTP请求)。

                        使用同样的方法把consumer-demo一起注册到Eureka上。

                        ribbon

                        上文只是将服务注册到eureka上,但是consumer还是硬编码调用,前文也有提到这种硬编码方式肯定是不合理的,一来服务上线之后,IP地址肯定是变动的, 再则,采用硬编码的方式是无法实现负载均衡的。

                        ribbon便是一个用来做负载均衡的组件。

                        点进spring-cloud-starter-netflix-eureka-client依赖,会发现,这个依赖中已经添加了spring-cloud-starter-netflix-ribbon,故在项目中可以直接使用ribbon,不用重新添加依赖。

                        在加载restTemplates的方法上添加@LoadBalanced注解,使其具有负载均衡的能力,然后将硬编码的ip地址换成服务提供者的应用名字(application.name属性的值),修改之后controller便变成了下面的样子,其他地方不做修改。

                        @RestController
                        public class ConsumerController {
                        
                            @Bean
                            @LoadBalanced
                            public RestTemplate restTemplate(){
                                return new RestTemplate();
                            }
                        
                            @Autowired
                            private RestTemplate restTemplate;
                        
                            @RequestMapping("/consumer/demo")
                            public String ConsumerDemo(){
                                return this.restTemplate.getForObject("http://provider-demo:7900/provider/demo", String.class);
                            }
                        
                        }
                          
                          

                          重启一下Eureka,provider-demo,consumer-demo,分别访问localhost:7900/provider/demohttp://localhost:7910/consumer/demo应该都能得到ProviderDemo 这个结果,说明consumer成功调用了provider中的方法。便解决了不采用硬编码的方式,使得consumer-demo调用provider-demo接口。

                          ribbon默认的负载均衡策略是轮询的。测试一下:

                          首先启动多个provider-demo项目,我这里分别用的接口是7900和7901两个接口,可以在Eureka中看一下provider的信息:

                          这里写图片描述

                          然后在consumer-demo项目的controller中添加下面的内容,代码应该很容易看懂就是模拟loadBalancerClient客户端来访问名字为provider-demo的服务接口,然后输出访问的接口端口号。

                           @Autowired
                           private LoadBalancerClient loadBalancerClient;
                          
                           @RequestMapping("/consumer/getInterfaceInfo")
                           public void getInterfaceInfo(){
                               ServiceInstance choose = loadBalancerClient.choose("provider-demo");
                               System.out.println(choose.getPort());
                           }
                            
                            

                            然后分别访问一下多次访问localhost:7910//consumer/getInterfaceInfo这个URL,可以看到控制台输出的信息在下面,很容易看出是轮询。

                            ribbon有多种修改负载均衡的策略,可以通过代码,也可以通过配置文件,个人觉得配置文件的方法比较方便,其他的方法可以自行百度:

                            在consumer-demo的application.yml文件中添加下面的内容,这里使用的是随机的策略,该测试就不贴图了,反正最后结果是随机的:

                            #服务提供者的名字
                            provider-demo:  
                              ribbon:
                                # 所要采用的策略
                                NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
                              
                              

                              ribbon所有的策略可以参照下表:

                              策略名 策略描述 实现说明
                              BestAvailableRule 选择一个最小的并发请求的server 逐个考察Server,如果Server被tripped了,则忽略,在选择其中ActiveRequestsCount最小的server
                              AvailabilityFilteringRule 过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值) 使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个server的运行状态
                              WeightedResponseTimeRule 根据响应时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低。 一个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。Weight的计算也比较简单responsetime 减去每个server自己平均的responsetime是server的权重。当刚开始运行,没有形成status时,使用roubine策略选择server。
                              RetryRule 对选定的负载均衡策略机上重试机制。 在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server
                              RoundRobinRule roundRobin方式轮询选择server 轮询index,选择index对应位置的server
                              RandomRule 随机选择一个server 在index上随机,选择index对应位置的server
                              ZoneAvoidanceRule 复合判断server所在区域的性能和server的可用性选择server 使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个zone的运行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于过滤掉连接数过多的Server。

                              猜你喜欢

                              转载自blog.csdn.net/weixin_42537413/article/details/86499915