理解SpringCloud组件扮演的角色(上)

278 阅读14分钟

前言

让时光积累真正的价值

上一篇笔记《理解什么是分布式系统》,我们大概理解了分布式是什么玩意。Spring Cloud是广泛使用的微服务架构。

学习Spring Cloud就免不了要学习Spring Cloud的各种组件,在这一篇笔记中,我们来了解一下Spring Cloud各个组件扮演的角色。

参考博客:外行人都能看懂的SpringCloud,错过了血亏!

导读

Spring Cloud提供一系列组件来解决分布式环境遇到的问题。我们来看看这些组件针对什么场景的

问题一:多节点下,节点之间需要相互通信怎么解决?

Spring Cloud提供Eureka组件,服务实例管理无压力。

问题二:服务A调用服务B,但是服务B有多个节点该怎么选择?

使用Ribbon组件,提供负载均衡功能,实例决策不用愁。

问题三:服务A调用服务B,服务B凉了,怎么办?

使用Hystrix组件,提供服务熔断和错误回调服务,保证不影响其他服务。

问题四:远程调用要写Http,代码看到自己都烦,怎么办?

使用Feign组件,你定义接口,Feign帮你实现,助力优雅远程调用。

实例管理员———Eureka

Eureka是用来解决什么问题的?

原来的系统使用Spring Cloud拆分了以后,节点之间通信就成了大问题。

在这里你可能会问了,不就是通信嘛,算什么大问题?直接写一份配置文件,然后通过HTTP调用不就可以通信了,这算是什么大问题呀?

这样做也可以解决问题,但是有很多缺点的,一旦节点的IP改变就要手动修改配置信息。服务节点一多起来,维护工作简直就是噩梦。

其实这个问题说白了就是实例维护的问题:

为了解决微服务中服务实例维护的问题(IP问题),诞生了很多服务治理的框架和产品,它们都是通过在服务注册与发现的机制,来实现服务实例的自动化管理。

Spring Cloud的组件中,Eureka是最常使用的实例维护组件。

Eureka怎样解决实例维护问题?

服务节点启动的时,将自己注册到Eureka注册中心,并且从注册中心中获取其他服务实例节点信息。

服务A获取到服务B的实例节点信息,就可以远程调用服务B了。

在项目中应该要如何使用Eureka?

在这里先了解一下Eureka相关的内容,Eureka分为Eureka客户端和Eureka服务端。

Eureka服务端(注册中心)

每个服务单元都会向服务中心注册自己提供的服务,包括IP域名、端口号、服务版本号、通信协议等一些附加信息。同时Eureka服务端还会通过检测心跳信息来判断服务实例的状态。

Eureka客户端

运行在服务实例节点上,在服务实例启动时,会将自己注册到注册中心。注册完成以后,eureka会获取到其他服务节点的信息。并且Eureka客户端会定期向服务端发送心跳消息。

Eureka Client又可以分为服务提供者和服务消费者,不过在大多数的情况下,服务节点是服务消费者同时又是服务提供者。

很显然,使用Eureka最简单的方式,就是创建一个Eureka服务端和将服务实例注册为Eureka 客户端。

创建Eureka服务端:

首先要创建一个Spring Cloud的项目,添加eureka服务端依赖

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

在启动类添加注解

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

配置application.yml

server:
  port: 8080
eureka:
  instance:
    hostname: localhost # eureka 实例名称
  client:
    register-with-eureka: false # 不向注册中心注册自己
    fetch-registry: false # 是否检索服务
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 注册中心访问地址
spring:
  application:
    name: eureka-server

使用Eureka 客户端

添加Eureka客户端依赖

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

添加启动类:

@EnableEurekaClient
public class ServerAApplication{
    public static void main(String[] args){
        SpringApplication.run(ServerAApplication.class, args);
    }
}
Eureka内容的说明

这里仅仅对Eureka做一个入门的介绍,Eureka其他的内容由于篇幅的关系改天再做介绍(我不会告诉你们我还没完全整明白……..)

参考文章:Spring Cloud Eureka详解

负载均衡器——Ribbon

引入Eureka,做到了实例自动管理。再也不用担心实例维护问题了。

现在服务A调用服务B来完成请求,但是服务B的运行速度太慢了,每次服务A都要等这服务B很久才能完成,这样的话就拉低了请求整体的响应时间。分布式系统的下,这个问题好解决!增加一个服务B的实例就可以了,如一个不行那就两个……

可是问题就来了,有多个服务B,服务A该选哪一个?

服务实例太多,怎么选?

这简直就是皇帝的烦恼,妃子太多,宠幸哪个都要想半天….

其实皇帝还是不少方案的,妃子太多了,那就翻牌决定吧,翻到哪一个妃子就区宠幸哪个。

不喜欢这样?

那就轮流,初一选一号妃子,初二选二号妃子…..一个月天天不重样…..

spring cloud提供一个组件——Ribbon。就跟皇帝选妃子一样,Ribbon就是决策具体调用哪个服务实例的。说白了就是复杂均衡。

负载均衡,其含义就是将负载(工作任务)进行平衡,分摊到多个操作单元(例如wed服务器)上运行,从而协同完成工作任务。

Ribbon怎么做决策的?

Ribbon是随机选择实例,还是轮流调用实例?

其实两种方式Ribbon都支持,怎样决策实例调用,在Ribbon抽象为策略。除了随机和轮询,Ribbon还支持很多种策略,也支持用户自定义策略。

Ribbon提供了一个ILoadBalance的接口,用户只要实现这个接口,并设置到Ribbon,就可以定义自己的负载策略了。当然全部自己实现就很费劲了,Ribbon还提供了一个模板类AbstractLoadBalancerRule,它实现了ILoadBalance接口,用户只需要实现少量的操作就可以实现自己的负载策略了。

Ribbon常见的策略有:

  • 随机策略(RandomRule):随机选择server
  • 轮询策略(RoundRibbonRule):按照顺序选择server
  • 重试策略(RetryRule):在配置的一段时间server不成功,则一直尝试其他server,知道成功
  • 响应时间加权(ResponseTimeWeightedRule):根据响应是时间快慢进行加权,有限选择响应时间块的server
  • 可用过滤(AvailabilityFilteringRule):server一直失败会被标记和过滤,负载重的server也会被过滤。

……………

在开发中应该怎样使用Ribbon?

Ribbon有很多的使用方式,这里介绍一下最简单的一种——eureka搭配Ribbon和RestTemplate使用。

这种用法的原理是这样的:Eureka客户端注册完成,实例获取到了其它服务实例的列表,Ribbon利用这个实例列表的数据来做调用决策。在容器中配置一个RestTemplate实例,Ribbon会使用动态代理的方式代理RestTemplate实例,将访问的实例动态修改为Ribbon决策出来的实例。

Eureka和Ribbon相关的包:

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

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

服务A的启动类,添加RestTemplate实例到容器:

@SpringBootApplication
@EnableEurekaClient
public class ServerAApplication{
    public static void main(String[] args){
        SpringApplication.run(ServerAApplication.class, args);
    }
 @Bean
@LoadBalanced
  public RestTemplate restTemplate() {
      return new RestTemplate();
  }
}

使用Ribbon之前:

@RestController
public class ControllerA{

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping
    public String getServerBData(){
        ResponseEntity<String> forEntity = restTemplate.getForEntity("http://localhost:8081/Bdata", String.class);
        String body = forEntity.getBody();
        return body;
    }
}

使用Ribbon之后:

@RestController
public class ControllerA{

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping
    public String getServerBData(){
        /**
        * 注意SERVER_B是所有服务B在Application里面配置的name
        * 这里SERVER_B代表所有服务B的节点
        */

        ResponseEntity<String> forEntity = restTemplate.getForEntity("http://SERVER_B/Bdata", String.class);
        String body = forEntity.getBody();
        return body;
    }
}
关于Ribbon内容的说明:

这里仅仅简单的介绍了Ribbon的作用,想要深入了解Ribbon的相关内容可以在网上查找详细的资料,或者期待一下我之后的笔记

关于Ribbon的内容还参考了这篇博客:

Ribbon详解与实例

服务的保护者———Hystrix

系统不可用了,为什么?

现在需求改了,服务A需要依赖服务B,服务B需要依赖服务C,服务C需要依赖服务D(A-->B-->C-->D)。

完成需求以后。一上线的就出现了很大的问题。系统老是被运行不良的实例拖垮:

原来在高并发的情况下,一旦这个某服务出现了延迟的情况,可能导致大量的请求都处于延迟在状态。请求积累越来越多,资源被耗尽,最终整个分布式系统不可用。这就是“雪崩”。

在这里举个例子:

月生在一家物流仓库做兼职,他被分配到一条流水线上工作,这条流水线的任务是将客户在某宝下单的商品封箱,并贴上快递单。

在流水线上有几组岗位,A组工人负责将商品放上流水线,B组工人负责将填充好泡沫的包装箱放上流水线,C组工人负责将商品放进箱子里面并封上胶带,D组工人负责给包装好的箱子贴上快递单。我们的包裹 就是通过这一条流水线生产出来的。

很幸运,月生被分配到A组,A组的工作很简单,只有月生一个人。相比起来B组和C组的人就会多很多。下游里面有一些职责月生工作慢的同事,面对这种情况月生也不敢说什么。

后来,C组工人突然突然有好几个去了上厕所,还有一个人突然要接电话。月生当让不会放过这个机会,心想“哈哈让你们说我慢”,扬起嘴角的月生加快了工作的速度。剩余的C组工人处理不完包装箱和商品,很快商品和包装箱就积满了整条流水线。为了应对这堆积如山的商品和货物,工人只能把流水线先关停了。

这跟微服务的情况很像,商品越积越多,导致这条流水线不能使用。

怎么解决这样的问题?

就拿上面的例子来说,当C组工人处理不完工作时,让月生休息一下再往流水线上加商品就好了。

在Spring Cloud中提供了Hystrix组件,Hystrix有断路器的功能。

当一个服务出现大量异常,再次调用这个服务,Hystrix会直接返回,而不是继续等待。这样就阻断了故障的蔓延。

此外,Hystrix实现了资源隔离,依赖的服务都独立使用一个线程池,防止一个服务的线程资源耗尽会影响到其他的正常服务的调用。

下面是开源中国上Hystrix的介绍:

Hystrix供分布式系统使用,提供容错和延迟功能,隔离远程系统、远程访问、第三方程序的访问点,防止级联失败。保证分布式系统面临不可避免失败时仍然有弹性。

关于Hystrix内容的说明:

Hystrix一般结合Feign使用(在下面会介绍),这里就不介绍Hystrix单独使用的方式了(我是为了压缩篇幅,才不是我没用过…..)

优雅调用助手———Feign

为什么要使用Feign?

在接入了Eureka、Ribbon和Hystrix之后,我们遇到的大部分的问题都解决了,但是这样开发会觉得繁琐。为什么呢?

远程调用需要RestTemplate,应用层的代码里需要指定远程服务地址。这种感觉,就像直接写JDBC的代码一样。

简化开发步骤,可以在项目中使用Feign。

Feign是一种声明式,模板化的HTTP客户端。在Spring Cloud项目中使用Feign,我们可以做到像调用本地方法一样调用HTTP接口。开发者完全感觉不到这个是远程的方法。

怎么使用Feign实现远程调用?

在项目中使用Feign,需要引入Spring Cloud Feign的依赖。值得注意的是,Spring Cloud Feign整合了Ribbon和Hystrix,只需要引入下面的依赖,不需要额外引入Ribbon和Hystrix。

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

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

在启动类添加注解,激活Feign:

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

声明远程接口:

/**
* 指定使用服务B,整合Hystrix指定错误回调类
*/

@FeignClient(name = "SEVER_B",fallbackFactory = ServerBRemote.ServerBRemoteFallbackFactory.class)
public interface ServerBRemote {

    //声明远程调用的接口
    @GetMapping("/Bdata")
    String getBdata();

    class ServerBRemoteFallbackFactory implements FallbackFactory<FirstRemote{

        @Override
        public ServerBRemote create(Throwable throwable) {
            throwable.printStackTrace();
            return new ServerBRemote() {
                @Override
                public String getBdata() {
                    return "error";
                }
            };
        }
    }
}

调用远程服务:

@RestController
public class ControllerA{

    @Autowired
    private ServerBRemote serverBRemote;

    @GetMapping
    public String getServerBData(){
        return serverBRemote.getBdata();
    }
}

最后

各位小哥哥小姐姐,都看到这里了,给个赞,点下关注呗。前几天第一个陌生人给我点了关注,我整个人都高兴到跳起来,还顺带做了20多个俯卧撑…..

刚刚开始写博客,难免有很多不足之处,期待各位大佬的指点