Springcloud

来自小能手俱乐部
跳到导航 跳到搜索

Spring Cloud是什么

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。 Spring Cloud正是对Netflix的多个开源组件进一步的封装而成,同时又实现了和云端平台,和Spring Boot开发框架很好的集成。 Spring Cloud是一个相对比较新的微服务框架,2016年才推出1.0的release版本. 虽然Spring Cloud时间最短, 但是相比Dubbo等RPC框架, Spring Cloud提供的全套的分布式系统解决方案。 Spring Cloud 为开发者提供了在分布式系统(配置管理,服务发现,熔断,路由,微代理,控制总线,一次性token,全居琐,leader选举,分布式session,集群状态)中快速构建的工具,使用Spring Cloud的开发者可以快速的启动服务或构建应用、同时能够快速和云平台资源进行对接。

生成缩略图出错:无法找到文件

从上图可以看出Spring Cloud各个组件相互配合,合作支持了一套完整的微服务架构。

  • Eureka负责服务的注册与发现,很好将各服务连接起来。
  • Hystrix 负责监控服务之间的调用情况,连续多次失败进行熔断保护。
  • Hystrix dashboard,Turbine 负责监控 Hystrix的熔断情况,并给予图形化的展示。
  • Spring Cloud Config 提供了统一的配置中心服务。
  • 当配置文件发生变化的时候,Spring Cloud Bus 负责通知各服务去获取最新的配置信息。
  • 所有对外的请求和服务,我们都通过Zuul来进行转发,起到API网关的作用。
  • 最后我们使用Sleuth+Zipkin将所有的请求数据记录下来,方便我们进行后续分析。

Spring Cloud从设计之初就考虑了绝大多数互联网公司架构演化所需的功能,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等。这些功能都是以插拔的形式提供出来,方便我们系统架构演进的过程中,可以合理的选择需要的组件进行集成,从而在架构演进的过程中会更加平滑、顺利。

Spring Cloud快速入门

搭建配置一个服务提供者

众所周知,SpringCloud构建微服务是基于SpringBoot开发的。

  1. 创建一个SpringBoot工程,并且添加SpringBoot的相关依赖;
  2. 创建服务提供者的访问方法,也就是后续消费者如何访问提供者。
  3. Spring Cloud是基于rest的访问,所以我们添加一个Controller,在该Controller中提供一个访问入口:
    @RestController
    public class HelloController {
        @RequestMapping(value = "/hello", method = RequestMethod.GET)
        public String hello() {
            return "Hello Spring Cloud";
        }
    }
    
  4. 启动运行该SpringBoot程序,访问该controller。

搭建配置一个服务消费者

  1. 创建一个SpringBoot工程,并且添加SpringBoot的相关依赖。
  2. 开发一个消费者方法,去消费服务提供者提供的服务,这个消费者方法也是一个Controller:
    @RestController
    public class ConsumerController {
        @Autowired
        private RestTemplate restTemplate;
        @RequestMapping(value="/cloud/hello")
        public String helloController() {
            return restTemplate.getForEntity("http://localhost:9100/hello", String.class).getBody();
        }
    }
    
  3. 启动该SpringBoot程序,测试服务消费者调用服务提供者。

走进服务注册中心Eureka

在微服务架构中,服务注册与发现是核心组件之一,手动指定每个服务是很低效的,Spring Cloud提供了多种服务注册与发现的实现方式,例如:Eureka、Consul、Zookeeper。

服务注册

服务注册:将服务所在主机、端口、版本号、通信协议等信息登记到注册中心上。

服务发现

服务发现:服务消费者向注册中心请求已经登记的服务列表,然后得到某个服务的主机、端口、版本号、通信协议等信息,从而实现对具体服务的调用。

Eureka是什么

Eureka是一个服务治理组件,它主要包括服务注册和服务发现,主要用来搭建服务注册中心;

Eureka 是一个基于 REST 的服务,用来定位服务,进行中间层服务器的负载均衡和故障转移;

Eureka是Netflix 公司开发的,Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务注册和发现,也就是说Spring Cloud对Netflix Eureka 做了二次封装;

Eureka 采用了C-S(客户端/服务端)的设计架构,也就是Eureka由两个组件组成:Eureka服务端和Eureka客户端。Eureka Server 作为服务注册的服务端,它是服务注册中心,而系统中的其他微服务,使用 Eureka 的客户端连接到 Eureka Server服务端,并维持心跳连接,Eureka客户端是一个Java客户端,用来简化与服务器的交互、负载均衡,服务的故障切换等;

有了Eureka注册中心,系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。

搭建与配置Eureka服务注册中心

Spring Cloud要使用Eureka注册中心非常简单和方便,Spring Cloud中的Eureka服务注册中心实际上也是一个Spring Boot工程,我们只需通过引入相关依赖和注解配置就能让Spring Boot构建的微服务应用轻松地与Eureka进行整合。

  1. 创建一个SpringBoot项目,并且添加SpringBoot的相关依赖;
  2. 添加eureka的依赖:
    <!--Spring Cloud的eureka-server起步依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    
  3. 在Spring Boot的入口类上添加一个@EnableEurekaServer注解,用于开启Eureka注册中心服务端;
  4. 在application.properties文件中配置Eureka服务注册中心信息:
    #内嵌定时tomcat的端口
    server.port=8761
    #设置该服务注册中心的hostname
    eureka.instance.hostname=localhost
    #由于我们目前创建的应用是一个服务注册中心,而不是普通的应用,默认情况下,这个应用会向注册中心(也是它自己)注册它自己,设置为false表示禁止这种自己向自己注册的默认行为
    eureka.client.register-with-eureka=false
    #表示不去检索其他的服务,因为服务注册中心本身的职责就是维护服务实例,它不需要去检索其他服务
    eureka.client.fetch-registry=false
    #指定服务注册中心的位置
    eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
    
  5. 启动SpringBoot程序,main方法运行。通过浏览器地址访问注册中心。

向Eureka服务注册中心注册服务

我们前面搭建了服务提供者项目,接下来我们就可以将该服务提供者注册到Eureke注册中心,步骤如下:

  1. 在该服务提供者中添加eureka的依赖,因为服务提供者向注册中心注册服务,需要连接eureka,所以需要eureka客户端的支持;
    <!--SpringCloud集成eureka客户端的起步依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
  2. 在Spring Boot的入口函数处,通过添加@EnableEurekaClient注解来表明自己是一个eureka客户端,让我的服务提供者可以连接eureka注册中心;
  3. 配置服务名称和注册中心地址;
    spring.application.name=02-springcloud-service-provider
    eureka.client.service-url.defaultZone=http://localhost:8761/eureka
    
  4. 启动服务提供者SpringBoot程序的main方法运行;
  5. 启动运行之后,通过在浏览器地址栏访问我们之前搭建好的eureka注册中心,就可以看到有一个服务已经注册成功了。

从Eureka服务注册中心发现与消费服务

我们已经搭建一个服务注册中心,同时也向这个服务注册中心注册了服务,接下来我们就可以发现和消费服务了,这其中服务的发现由eureka客户端实现,而服务的消费由Ribbon实现,也就是说服务的调用需要eureka客户端和Ribbon两者配合起来才能实现。

Eureka客户端是什么

Eureka客户端是一个Java客户端,用来连接Eureka服务端,与服务端进行交互、负载均衡,服务的故障切换等。

Ribbon是什么

Ribbon是一个基于HTTP 和 TCP 的客户端负载均衡器,当使用Ribbon对服务进行访问的时候,它会扩展Eureka客户端的服务发现功能,实现从Eureka注册中心中获取服务端列表,并通过Eureka客户端来确定服务端是否己经启动。Ribbon在Eureka客户端服务发现的基础上,实现了对服务实例的选择策略,从而实现对服务的负载均衡消费。


接下来我们来让服务消费者去消费服务:

我们前面搭建了服务消费者项目,接下来我们就可以使用该服务消费者通过注册中心去调用服务提供者,步骤如下:

  1. 在该消费者项目中添加eureka的依赖,因为服务消费者从注册中心获取服务,需要连接eureka,所以需要eureka客户端的支持;
    <!--SpringCloud集成eureka客户端的起步依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
  2. 在Spring Boot的入口函数处,通过添加@EnableEurekaClient注解来表明自己是一个eureka客户端,让我的服务消费者可以使用eureka注册中心;
  3. 配置服务的名称和注册中心的地址:
    spring.application.name=03-springcloud-web-consumer
    eureka.client.service-url.defaultZone=http://localhost:8761/eureka
    
  4. 前面我介绍了服务的发现由eureka客户端实现,而服务的真正调用由ribbon实现,所以我们需要在调用服务提供者时使用ribbon来调用:
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate () {
        return new RestTemplate();
    }
    
    加入了ribbon的支持,那么在调用时,即可改为使用服务名称来访问:
    restTemplate.getForEntity("http://02-SPRINGCLOUD-SERVICE-PROVIDER/hello", String.class).getBody();
    
  5. 完成上面的步骤后,我们就可以启动消费者的SpringBoot程序,main方法运行;
  6. 启动成功之后,通过在浏览器地址栏访问我们的消费者,看是否可以正常调用远程服务提供者提供的服务。

Spring Cloud Eureka服务注册中心

在微服务架构的这种分布式系统中,我们要充分考虑各个微服务组件的高可用性问题,不能有单点故障,由于注册中心eureka本身也是一个服务,如果它只有一个节点,那么它有可能发生故障,这样我们就不能注册与查询服务了,所以我们需要一个高可用的服务注册中心,这就需要通过注册中心集群来解决。

eureka服务注册中心它本身也是一个服务,它也可以看做是一个提供者,又可以看做是一个消费者,我们之前通过配置:eureka.client.register-with-eureka=false 让注册中心不注册自己,但是我们可以向其他注册中心注册自己。

Eureka Server的高可用实际上就是将自己作为服务向其他服务注册中心注册自己,这样就会形成一组互相注册的服务注册中心,进而实现服务清单的互相同步,往注册中心A上注册的服务,可以被复制同步到注册中心B上,所以从任何一台注册中心上都能查询到已经注册的服务,从而达到高可用的效果。

Eureka.png

Eureka注册中心高可用集群搭建

我们知道,Eureka注册中心高可用集群就是各个注册中心相互注册,所以:

  1. 在8761的配置文件中,让它的service-url指向8762,在8762的配置文件中让它的service-url指向8761;
  2. 由于8761和8762互相指向对方,实际上我们构建了一个双节点的服务注册中心集群;
    eureka.client.service-url.defaultZone=http://eureka8762:8762/eureka/
    eureka.client.service-url.defaultZone=http://eureka8761:8761/eureka/
    
  3. 然后在本地hosts文件配置:C:\Windows\System32\drivers\etc\hosts;
    127.0.0.1 eureka8761
    127.0.0.1 eureka8762
    
  4. 运行时,在运行配置项目Program Arguments 中配配置;
    --spring.profiles.active=eureka8761 
    --spring.profiles.active=eureka8762
    
  5. 分别启动两个注册中心,访问两个注册中心页面,观察注册中心页面是否正常。

Eureka注册中心高可用集群测试

在要进行注册的服务中配置:

eureka.client.service-url.defaultZone=http://eureka8761:8761/eureka/,http://eureka8762:8762/eureka/

启动服务提供者服务,然后观察注册中心页面,可以看到服务会在两个注册中心上都注册成功。

Eureka服务注册中心自我保护机制

自我保护机制是Eureka注册中心的重要特性,当Eureka注册中心进入自我保护模式时,在Eureka Server首页会输出如下警告信息:

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.

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自我保护的情况下,如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例,但是当发生网络分区故障时,那么微服务与Eureka Server之间将无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是正常的,此时不应该注销这个微服务,如果没有自我保护机制,那么Eureka Server就会将此服务注销掉。

Eureka通过“自我保护模式”来解决这个问题——当Eureka Server节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么就会把这个微服务节点进行保护。一旦进入自我保护模式,Eureka Server就会保护服务注册表中的信息,不删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会再自动退出自我保护模式。

所以,自我保护模式是一种应对网络异常的安全保护措施,它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务,使用自我保护模式,可以让Eureka集群更加的健壮、稳定。

当然也可以使用配置项:eureka.server.enable-self-preservation = false 禁用自我保护模式。

但是Eureka Server 自我保护模式也会给我们带来一些困扰,如果在保护期内某个服务提供者刚好非正常下线了,此时服务消费者就会拿到一个无效的服务实例,此时会调用失败,对于这个问题需要服务消费者端具有一些容错机制,如重试,断路器等。

Eureka的自我保护模式是有意义的,该模式被激活后,它不会从注册列表中剔除因长时间没收到心跳导致注册过期的服务,而是等待修复,直到心跳恢复正常之后,它自动退出自我保护模式。这种模式旨在避免因网络分区故障导致服务不可用的问题。

例如,两个微服务客户端实例A和B之间有调用的关系,A是消费者,B是提供者,但是由于网络故障,B未能及时向Eureka发送心跳续约,这时候Eureka 不能简单的将B从注册表中剔除,因为如果剔除了,A就无法从Eureka 服务器中获取B注册的服务,但是这时候B服务是可用的。

所以,Eureka的自我保护模式最好还是开启它。

关于自我保护常用几个配置如下:

服务器端配置:

#测试时关闭自我保护机制,保证不可用服务及时踢出

eureka.server.enable-self-preservation=false

客户配置:

#每间隔2s,向服务端发送一次心跳,证明自己依然"存活"

eureka.instance.lease-renewal-interval-in-seconds=2

东岱告诉服务端,如果我10s之内没有给你发心跳,就代表我故障了,将我踢出掉

eureka.instance.lease-expiration-duration-in-seconds=10

Spring Cloud Ribbon客户端负载均衡

Spring Cloud中的Ribbon是什么

我们通常说的负载均衡是指将一个请求均匀地分摊到不同的节点单元上执行,负载均和分为硬件负载均衡和软件负载均衡:

硬件负载均衡:比如 F5、深信服、Array 等;

软件负载均衡:比如 Nginx、LVS、HAProxy 等;

硬件负载均衡或是软件负载均衡,他们都会维护一个可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候,该设备按某种算法(比如轮询、权重、 最小连接数等)从维护的可用服务端清单中取出一台服务端的地址,然后进行转发。

Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,是一个基于HTTP和TCP的客户端负载均衡工具。

Spring Cloud对Ribbon做了二次封装,可以让我们使用RestTemplate的服务请求,自动转换成客户端负载均衡的服务调用。

Ribbon支持多种负载均衡算法,还支持自定义的负载均衡算法。

Ribbon只是一个工具类框架,比较小巧,Spring Cloud对它封装后使用也非常方便,它不像服务注册中心、配置中心、API网关那样需要独立部署,Ribbon只需要在代码直接使用即可。

Ribbon 与 Nginx 的区别

Ribbon是客户端的负载均衡工具,而客户端负载均衡和服务端负载均衡最大的区别在于服务清单所存储的位置不同,在客户端负载均衡中,所有客户端节点下的服务端清单,需要自己从服务注册中心上获取,比如Eureka服务注册中心。同服务端负载均衡的架构类似,在客户端负载均衡中也需要心跳去维护服务端清单的健康性,只是这个步骤需要与服务注册中心配合完成。在Spring Cloud中,由于Spring Cloud对Ribbon做了二次封装,所以默认会创建针对Ribbon的自动化整合配置。

在Spring Cloud中,Ribbon主要与RestTemplate对象配合起来使用,Ribbon会自动化配置RestTemplate对象,通过@LoadBalanced开启RestTemplate对象调用时的负载均衡。

生成缩略图出错:无法找到文件

Ribbon实现客户端负载均衡

由于Spring Cloud Ribbon的封装, 我们在微服务架构中使用客户端负载均衡调用非常简单, 只需要如下两步:

  1. 启动多个服务提供者实例并注册到一个服务注册中心或是服务注册中心集群。
  2. 服务消费者通过被@LoadBalanced注解修饰过的RestTemplate来调用服务提供者。

这样,我们就可以实现服务提供者的高可用以及服务消费者的负载均衡调用。

Ribbon负载均衡策略

Ribbon的负载均衡策略是由IRule接口定义, 该接口由如下实现:

生成缩略图出错:无法找到文件
RandomRule 随机
RoundRobinRule 轮询
AvailabilityFilteringRule 先过滤掉由于多次访问故障的服务,以及并发连接数超过阈值的服务,然后对剩下的服务按照轮询策略进行访问;
                                                                  WeightedResponseTimeRule                                                                                     根据平均响应时间计算所有服务的权重,响应时间越快服务权重就越大被选中的概率即越高,如果服务刚启动时统计信息不足,则使用RoundRobinRule策略,待统计信息足够会切换到该WeightedResponseTimeRule策略;
RetryRule 先按照RoundRobinRule策略分发,如果分发到的服务不能访问,则在指定时间内进行重试,分发其他可用的服务;
BestAvailableRule 先过滤掉由于多次访问故障的服务,然后选择一个并发量最小的服务;
ZoneAvoidanceRule 综合判断服务节点所在区域的性能和服务节点的可用性,来决定选择哪个服务;

Rest请求模板类解读

当我们从服务消费端去调用服务提供者的服务的时候,使用了一个极其方便的对象叫RestTemplate,当时我们只使用了RestTemplate中最简单的一个功能getForEntity发起了一个get请求去调用服务端的数据,同时,我们还通过配置@LoadBalanced注解开启客户端负载均衡,RestTemplate的功能非常强大,那么接下来我们就来详细的看一下RestTemplate中几种常见请求方法的使用。

在日常操作中,基于Rest的方式通常是四种情况,它们分表是:

  • GET请求 --查询数据
  • POST请求 –添加数据
  • PUT请求 – 修改数据
  • DELETE请求 –删除数据
RestTemplate的GET请求

有两种方式:

第一种:getForEntity

该方法返回一个ResponseEntity对象,ResponseEntity是Spring对HTTP请求响应的封装,包括了几个重要的元素,比如响应码、contentType、contentLength、响应消息体等;

ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://02-SPRINGCLOUD-SERVICE-PROVIDER/hello", String.class);
String body = responseEntity.getBody();
HttpStatus statusCode = responseEntity.getStatusCode();
int statusCodeValue = responseEntity.getStatusCodeValue();
HttpHeaders headers = responseEntity.getHeaders();

System.out.println(body);
System.out.println(statusCode);
System.out.println(statusCodeValue);
System.out.println(headers);

getForEntity方法第一个参数为要调用的服务的地址,即服务提供者提供的http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello接口地址,注意这里是通过服务名调用而不是服务地址,如果改为服务地址就无法使用Ribbon实现客户端负载均衡了。

getForEntity方法第二个参数String.class表示希望返回的body类型是String类型,如果希望返回一个对象,也是可以的,比如User对象。

第二种:getForObject()

与getForEntity使用类似,只不过getForObject是在getForEntity基础上进行了再次封装,可以将http的响应体body信息转化成指定的对象,方便我们的代码开发;

当你不需要返回响应中的其他信息,只需要body体信息的时候,可以使用这个更方便。

RestTemplate的POST请求
restTemplate.postForObject();
restTemplate.postForEntity();
restTemplate.postForLocation();
RestTemplate的PUT请求
restTemplate.put();
RestTemplate的DELETE请求
restTemplate.delete();