初识Spring Cloud

springcloud ~ 框架的集合

练习时源码:https://github.com/lhcmmd/spring-cloud

1.单体应用架构存在的问题

一个归档包(例如war)包含所有功能的应用程序,通常称为单体应用。

 

相信很多项目都是从单体应用开始的,单体应用比较容易部署和测试,项目的初期,项目可以很好的运行,然而,随着需求的不断增加,越来越多的人加入到开发团队,代码库也在飞速的膨胀,慢慢的单体应用变得越来越臃肿,可维护性,灵活性逐渐降低,维护的成本越来越高,以下列举单体架构中存在的问题:

  • 复杂性高:定时炸弹

  • 技术债务:no broken don't fix

  • 可靠性差:单个bug,导致整个系统瘫痪

  • 阻碍技术创新

2.架构的演变

 

3.什么是微服务

the microservice architectural style [1] is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.

  • 由一系列微小的服务组成

  • 每个服务独立运行在自己的进程中

  • 独立部署

  • 基于分布式的管理

官网链接https://www.martinfowler.com/articles/microservices.html

4.微服务的解决方案

 

5.什么是springcloud

Spring Cloud是一个含概多个子项目的开发工具集,集合了众多的开源框架,他利用了Spring Boot开发的便利性实现了很多功能,如服务注册,服务注册发现,负载均衡等.Spring Cloud在整合过程中主要是针对Netflix(耐非)开源组件的封装.

NetFlix 是美国的一个在线视频网站,微服务业的翘楚,他是公认的大规模生产级微服务的杰出实践者,NetFlix的开源组件已经在他大规模分布式微服务环境中经过多年的生产实战验证,因此spring cloud中很多组件都是基于NetFlix组件的封装

Spring Cloud的出现真正的简化了分布式架构的开发

6.springcloud的特点

  • 服务注册和发现

  • 路由

  • service - to - service调用

  • 负载均衡

  • 断路器

7.Spring Cloud 的服务架构图

 

8.Eureka组件

Eureka是Netfilx开源的服务发现组件,本身是一个基于rest的服务,它包含client和server两部分。

Spirng Cloud将它集成在子项目Spirng Cloud Netfilx中,从而实现服务的注册和发现

1.eureka中的server和client的介绍及特点

  • Eureka Server:提供服务发现的能力,各个微服务启动时,会向Eureka Server注册自己的信息例如(IP,端口号,服务名称等),Eureka会存储这些信息

  • Eureka Client:是一个java的客户端用来简化Eureka Server的交互

  • 微服务启动后会周期性的(默认30秒)向Eureka Server发送心跳,如果Eureka在规定的时间没有收到心跳,则会注销该实例(默认90秒)

  • Eureka Client会缓存服务注册表中的信息。这种方式有一定的优势首先可以降低Eureka Server的压力,其次当所有的Eureka Server宕机服务调用方依然可以完成调用

2.服务注册与服务发现

  • 服务注册:当微服务client启动时,向Eureka Server发起注册,并上报该节点的相关信息

  • 服务发现:client从Eureka Server获取注册信息,然后发起调用

3.Eureka Server开发

1.引入springcloud的相关依赖
<?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>com.lhc</groupId>
   <artifactId>springcloud_regist</artifactId>
   <version>1.0-SNAPSHOT</version>
   <name>springcloud_regist</name>
   <url>http://www.example.com</url>

   <properties>
       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
       <maven.compiler.source>1.8</maven.compiler.source>
       <maven.compiler.target>1.8</maven.compiler.target>
   </properties>

   <!--引入springboot的父项目-->
   <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>1.5.7.RELEASE</version>
   </parent>
   <dependencies>
       <!--eureka server-->
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-eureka-server</artifactId>
       </dependency>
       <!-- spring boot test-->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
       </dependency>
   </dependencies>

 <dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RC1</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>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>

</project>


2.入口类的开发
@EnableEurekaServer
@SpringBootApplication
public class EurekaServer {
   public static void main(String[] args) {
       SpringApplication.run(EurekaServer.class,args);
  }
}
3.配置文件
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
spring.application.name=eureka
server.port=8761

4.Eureka Client的开发

1.jar包和Server相同
2.入口类
@EnableDiscoveryClient
@SpringBootApplication
public class HiApplication {

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

3.配置文件

server.port=8989
eureka.client.service-url.default-zone=http://localhost:8761/eureka
spring.application.name=hi
eureka.instance.prefer-ip-address=true

5.Eureka client之间的相互调用

1.导入相关的jar
和eureka server 一致
2.java配置RestTemplate
    @Bean
   @LoadBalanced
   RestTemplate getRestTemplate(){
       return new RestTemplate();
  }
3.调用
   @Autowired
  RestTemplate restTemplate;

   @RequestMapping("/hi")
   public String hi(String name) {
       String restTemplateForObject = restTemplate.getForObject("http://服务名/test/test?name=" + name, String.class);
       return restTemplateForObject;
  }

6.Eureka的自我保护机制

Eureka进入自我保护机制的最直接体现,是Eureka首页输出警告如图:

 

默认情况下,如果Eureka Server在一定时间内没有接受到服务实例的心跳,Eureka将会注销该实例(默认90秒).但是当网络分区发生故障时,微服务和Eureka Server 无法正常通信.以上行为可能变得特别危险了-因为微服务本身是健康的,此时不能注销该服务实例.

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

综上,自我保护模式是一种应对网络故障的安全保护措施,它的架构哲学是宁可同时保留所有的微服务,也不盲目注销任何健康的微服务,使用自我保护模式可以让Eureka,更加健壮,稳定。

在springcloud中可以通过

#关闭自我保护机制  默认开启
eureka.server.enable-self-preservation=false

如果想及时剔除eureka的服务除了关闭自我保护机制外,可以调低eureka的心跳值
eureka-server服务端
配置文件中我们添加如下配置
#关闭保护机制,以确保注册中心将不可用的实例正确剔除
eureka.server.enable-self-preservation=false
#(代表是5秒,单位是毫秒,清理失效服务的间隔 )
eureka.server.eviction-interval-timer-in-ms=5000

客户端
配置文件中我们添加如下配置
# 心跳检测检测与续约时间
# 测试时将值设置设置小些,保证服务关闭后注册中心能及时踢出服务
# 配置说明
#  lease-renewal-interval-in-seconds 每间隔10s,向服务端发送一次心跳,证明自己依然”存活“
#  lease-expiration-duration-in-seconds  告诉服务端,如果我20s之内没有给你发心跳,就代表我“死”了,将我踢出掉。
eureka.instance.lease-renewal-interval-in-seconds=10
eureka.instance.lease-expiration-duration-in-seconds=20

7.client的高可用

保证端口号不一致(测试环境)
保证实例名一致

配置如下

eureka.client.service-url.defaultZone=http://peer:8761/eureka
spring.application.name=eureka-producter
server.port=8764        
eureka.client.service-url.defaultZone=http://peer:8761/eureka
spring.application.name=eureka-producter
server.port=8763        

7.Eureka Server的高可用

单节点的Eureka Server 不适合线上的生产环境,Eureka Client会定时连接Eureka Server,获取服务注册表中的信息并缓存在本地,微服务消费远程API总是使用本地缓存的数据,因此一般来说既是Eureka Server发生宕机,也不会影响到服务的调用,但是如果Eureka Server宕机时某些微服务也出现了不可用的情况,Eurek Client中的缓存若不被更新,可能会影响到服务的调用,甚至影响到整个系统的高可用性,因此在生产环境会部署一个高可用的Eureka Server集群。

Eureka可以通过运行多个实例并互相注册实现高可用部署,Eureka Server实例会彼此同步信息。

server.port=8761
eureka.client.service-url.defaultZone=http://peer2:8762/eureka
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.instance.hostname=peer1
server.port=8762
eureka.client.service-url.defaultZone=http://peer1:8761/eureka
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.instance.hostname=peer2

注意:在client注册eureka server时 需要填写所有eureka的地址

eureka.client.service-url.defaultZone=http://peer:8761/eureka,http://peer1:8765/eureka

9.Eureka的健康监测

人会生病,就像人一样服务有时候也会出现异常情况,我们也需要知道某个服务的健康状况。我们可以通过添加如下依赖,开启某个服务的健康检查。

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

我们来访问一下这个接口http://localhost:9001/health,看到了一个很简短的健康报告:

{"description":"Spring Cloud Eureka Discovery Client","status":"UP"}

类似的还有

  • info 显示任意的应用信息

  • metrics 展示当前应用的指标信息 true

  • mappings 显示所有@RequestMapping路径的整理列表

  • trace 显示trace信息(默认为最新的一些HTTP请求)

  • health 展示应用的健康信息(默认显示简短信息)

  • beans 显示一个应用中所有Spring Beans的完整列表

这其中有一些是敏感信息,出于安全考虑,一般用户无法访问,如果内网情况下可以 客户端配置

eureka.client.healthcheck.enabled=true
#关掉认证(公网下的生产环境不建议,内网部署可以)
management.security.enabled=false

10.使用Ribbon完成负载均衡

1.Ribbon简介

producter服务方启动时需要在eureka-server完成注册,consumer(调用方)调用方调用服务时需要获取eureka-server中注册的服务列表,获取后缓存一份,(product1,product2,product3),此时进行负载均衡。于是需要组件ribben↓

ribbin是Netflix发布的负载均衡器,有助于控制http和tcp客户端的行为,为ribbon配置服务提供者列表后,ribbon就可以基于某种负载均衡算法,自动的帮助服务消费者去请求。ribbon提供了很多的负载均衡算法例如

  • RoundRobinRule(轮询算法)

  • RandomRule(随机算法)

  • AvailabilityFilteringRule():会先过滤由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问

  • WeightedResponseTimeRule():根据平均响应的时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高,刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够会切换到WeightedResponseTimeRule

  • RetryRule():先按照RoundRobinRule的策略获取服务,如果获取失败则在制定时间内进行重试,获取可用的服务。

  • BestAviableRule():会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务

在springCloud中,当ribbon和Eureka配和使用时ribbon可以自动获取服务注册列表,并基于负载均衡算法,请求其中的一个服务提供实例

 

2.为服务消费者整合ribbon

1.ribbon和springcloud的启动器

注意:如果已经引入了spring-cloud-starter-eureka-server该依赖已经包含了spring-cloud-starter-ribbon,所以不需要再次引入

<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon -->
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
2.ribbon整合RestTemplate
   @Bean
  @LoadBalanced
   RestTemplate getRestTemplate(){
       return new RestTemplate();
  }
3.使用
    @Autowired
   RestTemplate restTemplate;

   
@RequestMapping("getPro")
public String getPro(String name){
String forObject = restTemplate.getForObject("http://EUREKA-PRODUCTER/pro/pro?name="+name, String.class);
return forObject;
}
 
4.验证负载均衡
@Autowired
LoadBalancerClient loadBalancerClient;
@RequestMapping("load")
public void load(){
ServiceInstance serviceInstance = loadBalancerClient.choose("EUREKA-PRODUCTER");
System.out.println(serviceInstance.getHost());
System.out.println(serviceInstance.getHost());
System.out.println(serviceInstance.getUri());
}

3.自定义ribbon配置

复制IRule的实现类名

EUREKA-PRODUCTER.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

11.使用fegin实现声明式的调用

使用RestTemplate+ribbon已经可以完成对服务端的调用,为什么还要使用Fegin?

   @RequestMapping("/hi")
   public String hi(String name) {
       String restTemplateForObject = restTemplate.getForObject("http://HI-SERVICE/test/test?name=" + name, String.class);
       return restTemplateForObject;
  }

上述代码采用url拼接参数的形式发送请求,如果要发送多个请求参数那么代码就会变得很低效并且难以维护。例如

http://localhost:8989/hi/hi?name=zhangsan&password=123456&age=18

如果使用字符串拼接的方式,那么代码可以编排为:

  @RequestMapping("/hi")
   public String hi(String name, String password, Integer age) {
       Map<Object, Object> map = new HashMap<>();
       map.put("name", name);
       map.put("password", password);
       map.put("age", age);
String restTemplateForObject = restTemplate.getForObject("http://HI-SERVICE/test/test?name={name}&password={password}&age={age}", String.class, map);
       return restTemplateForObject;
  }

在这里url仅仅包含三个参数,如果url为10个参数那么代码会变的更加难以维护。

1.Fegin简介

Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用Feign 注解和JAX-RS注解。Feign支持可插拔的编码器和解码器。Feign默认集成了Ribbon,并和Eureka结合,默认实现了负载均衡的效果并且springcloud为fegin添加了springmvc注解的支持。

2.为服务消费者整合fegin

1.引入fegin的相关依赖
       <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
2.入口类开启fegin支持
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
public class HiApplication {
   public static void main(String[] args) {
       SpringApplication.run(HiApplication.class, args);
  }
}
3.创建fegin接口
@FeignClient(serviceId = "HI-SERVICE")
public interface FeginTest {
   @RequestMapping("/test/test")//映射到调用服务方(producter)
   public String sayHi(@RequestParam("name") String name);
}
4.测试
//该client通过Feign(伪客户端远程调用服务producter)调用
@RequestMapping("/hi")
   public String hi(@RequestParam("name") String name) {
       final String s = feginTest.sayHi(name);
       System.out.println(s);
       return s;
  }
//product接收(集群所有product)
@RequestMapping("testFeign")
   public String testFeign(String name,String password){
       return "hello:"+name+" password:"+password;
}

3.fegin日志

很多的场景下,需要了解fegin处理请求的具体细节,如何满足这种需求呢?

fegin对日志的处理非常灵活可为每个Fegin客户端指定日志记录策略,每个客户端都会创建一个logger默认情况下logger的名称是fegin的全限定名需要注意的是,fegin日志的打印只会DEBUG级别做出响应。

我们可以为fegin客户端配置各自的logger.lever对象,告诉fegin记录那些日志logger.lever有以下的几种值

  • NONE 不记录任何日志

  • BASIC 仅仅记录请求方法,url,响应状态代码及执行时间

  • HEAdERS 记录Basic级别的基础上,记录请求和响应的header

  • FULL 记录请求和响应的header,body和元数据

1.java配置核心日志类
@Configuration
public class FeginConf {
   @Bean
   public Logger.Level feginConfiguration() {
       return Logger.Level.FULL;
  }
}
2.配置fegin客户端的日志级别
logging.level.com.lhc.fegin.FeginTest=debug

4.fegin构造多参数请求

1.错误方式
public interface FeginPost {
   @RequestMapping(value = "/test/test1", method = RequestMethod.GET)
   public User sayHi(User user);
}

错误日志信息

feign.FeignException: status 405 reading FeginPost#sayHi(User); content:
{"timestamp":1546852876890,"status":405,"error":"Method Not Allowed","exception":"org.springframework.web.HttpRequestMethodNotSupportedException","message":"Request method 'POST' not supported","path":"/test/test1"}

由异常信息可知,尽管指定了get的方法,fegin依然会使用post发送请求(对象传输时)。正确的方式如下:

注意:坑一

客户端的核心包为下如果也用server的依赖,在返回对象到页面是会出现返回的是xml而不是json

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

以下是server-eureka的依赖

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

2.get
    @RequestMapping("/test/test",method = RequestMethod.GET)
   public String sayHi(@RequestParam("name") String name,@RequestParam("password")String password);
3.post

fegin配置

@FeignClient(serviceId = "HI-SERVICE")
public interface FeginPost {
   @RequestMapping(value = "/test/test1", method = RequestMethod.POST)
   public User sayHi(User user);
}

服务提供方

  @RequestMapping("/test1")
   public User test1(@RequestBody User user) {
       return user;
  }

12.使用Hystrix实现微服务的容错处理

1.实现容错的手段

如果服务提供者响应的速度特别慢,那么消费者对提供者的请求就会强制等待,直到提供者响应或者超时。在高负载的情况下,如果不做任何处理,此类问题可能会导致服务消费者的资源耗尽甚至整个系统的崩溃。例如曾经发生的一个案例,某个电子商务网站在某个星期五发生过载,过多的兵法请求,导致用户支付请求延迟很久没有响应,在等待很长时间后最终失败,支付失败又导致用户重新刷新页面再次尝试支付,进一步增加了服务器的负载,最终整个系统崩溃了。

1.1雪崩效应

我们常把基础服务故障导致级联服务故障的现象称为雪崩效应。雪崩效应描述的是提供方不可用导致消费方不可用并将不可用逐渐放大的过程。

1.2.如何容错

想要避免雪崩效应,必须有强大的容错机制,该容错机制需要实现以下两点

  • 为网络请求设置超时

  • 使用短路器模式

    正常情况下断路器关闭可正常请求服务,当一定时间内请求达到了一定的阈值(错误率达到百分之50或者100次/分钟),断路器就会打开此时不会再去请求依赖服务,短路器打开一段时间之后,会自动进入半开的状态。此时断路器允许一个请求请求服务实例,如果该服务可以调用成功,则关闭断路器,否则继续保持打开状态。

     

2.使用hystrix实现容错

hystrix是Netfilx开源的延迟和容错库,用于隔离访问远程系统,服务或者第三方库,防止级联失败,从而提升系统的可用性和容错性

3.通用方式整合Hystrix

1.为项目添加依赖
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-hystrix -->
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
2.在消费者的入口类上添加注解
@EnableHystrix
@EnableDiscoveryClient
@SpringBootApplication
public class HiApplication {

   public static void main(String[] args) {

       SpringApplication.run(HiApplication.class, args);
  }
3.让当前方法具备容错的能力
 @Autowired
   LoadBalancerClient loadBalancerClient;
   @HystrixCommand(fallbackMethod = "testFallback")
   @RequestMapping("/hi")
   public User hi(String name) {
       User restTemplateForObject = restTemplate.getForObject("http://HI-SERVICE/test/test?name=" + name, User.class);
       return restTemplateForObject;
  }
//断路器打开时,请求超时异常时会调用此方法(容错方法)
public User testFallback(String name){
       User user=new User();
       user.setName(name);
       user.setAge(-1);
       return user;
  }
4.测试
1.启动eureka服务
2.启动生产者
3.启动消费者
4.访问消费者
结果
{"name":"xiaohei","password":null,"age":null}
5.关闭生产者
6.访问消费者
结果
{"name":"xiaohei","password":null,"age":-1}

我们知道,当请求失败,被拒绝超时或者短路器打开时都会进入到回退方法,但是进入回退方法并不是意味着断路器被打开,那怎样才算断路器打开呢?

5.实验 进行健康监测实时查看断路器是否打开
1.启动eureka服务
2.启动生产者
3.启动消费者
4.访问消费者
结果
{"name":"xiaohei","password":null,"age":null}
5.访问 http://ip:port/health
{
   status:"up",
   hystrix:{
       status:"up"
  }
}
6.关闭生产者
7.访问消费者
结果
{"name":"xiaohei","password":null,"age":-1}
8.访问 http://ip:port/health
{
   status:"up",
   hystrix:{
       status:"up"
  }
}
通过这里我们可以发现,尽管执行了fallback方法,但此时hystrix状态依然是up,这是因为咱们的失败率还没有达到阀值(默认5秒20次失败),这里再次强调,执行回退的逻辑并不代表断路器已经打开,请求超时,失败,被拒绝以及断路器打开都会执行回退逻辑
9.5秒请求20次
{
   status:"up",
   hystrix:{
       status:"CIRCUIT_OPEN"(断路器打开的标识)
  }
}

4.Fegin整合Hystrix

很显然@HystrixCommand是添加到方法上的,那么对于fegin来说一定不适用,如何让Hystrix整合Fegin

1.Fegin方法上添加相关注解
@FeignClient(serviceId = "HI-SERVICE",fallback = FeignClientFallBack.class)
public interface FeginPost {
   @RequestMapping(value = "/test/test1", method = RequestMethod.POST)
   public User sayHi(User user);
}
2.实现Feign接口
@Component
public class FeignClientFallBack implements FeginPost {
   @Override
   public User sayHi(User user) {
       System.out.println("Hystrix method is invoke");
       return null;
  }
}

3.打开两者的集成


​为feign开启断路器
feign.hystrix.enabled=true
 
原文地址:https://www.cnblogs.com/lhc-hhh/p/10241405.html