SpringCloud与Ribbon,Feign分享

1,770 阅读17分钟

本次分享主要内容为,架构的演进,微服务架构的优缺点,以及其中的两个组件Ribbon,Feign

架构演进之路

架构的发展历程对应着业务需求的发展,如互联网带来的系统访问量增长,敏捷开发模式 企业合作开发模式对解耦提出的更高要求

架构为业务服务,选择合适的架构使我们开发职责清晰,界限分明,但架构的过度设计会带来不必要的麻烦,我们学习微服务,是要明白它的优缺点,在最适合它的业务场景中使用 并不是为了使用微服务而使用微服务

技术更新,理念更新很快。把握住架构升级的思路,即适配业务场景,和适合业务将来的发展 选择最合适于业务场景的架构

Servlet

首先来看第一个项目, Servlet项目,也就是JavaWeb脱去层层封装的样子。 创建类,并实现Servlet接口实现类的增强,重写Servlet五个生命周期,就完成了一个JavaWeb

在servlet方法中我们甚至可以将数据库交互、数据处理,甚至html页面都写在一起,

  • 缺点:强耦合性,无分层,
  • 优点:足够简单,完全掌控

这里的简单并不是SpringBoot所谓的简单,这里的简单指的是我们可以掌控项目所有细节,而SpringBoot采用Starter引入依赖,使用约定优于配置的思路,为我们写好了配置类,并提供大量的默认配置,以达成开箱即用.

即已经为我们注入好了Bean供我们使用,如果想根据项目需要自定义Bean,那么需要去重写Bean,或者仔细研究各个application.yml提供的配置项

如我们经常使用的 MyBatis,如果不搭配 Spring,如图

而如果使用 spingboot,引用 mybatis-Spring-boot-starter 即可, 在 application.yml 做些简单的数据源配置后 源码中 MybatisAutoConfiguration 会自动为我们将 sqlsessionFactoy和sqlSessionTemplate 声明为 Bean 供我们使用。

单体MVC阶段

进行了对servlet的封装,代码的分层,代表性框架SpringMVC,核心类DispatchServlet负责了请求的统一分发,handler处理,最终view的返回,进行了业务逻辑层,数据访问层,表示层的划分

虽然对软件结构进行划分,但对业务场景没有划分,典型的场景就是将各种业务场景的MVC都放在一个工程里 经过编译打包布署在一台服务器上,如图的单体架构

  • 优点:项目初期开发速度快,结构清晰,易于部署
  • 缺点:随着业务逐步复杂
  1. 单体代码量越来越大,可读性,可维护性,可扩展性下降
  2. 单体项目运行于同一JVM以之上,随业务复杂,用户增加,性能下降 且垂直伸缩成本巨大
  3. 牵一发动全身,个别bug,导致整个系统无法运转
  4. 单体war包越来越大,编译部署耗时增加

到了 springMVC 后期,会做一些垂直拆分,即业务上没有关联的系统进行拆分,形成独立的,对外提供服务的系统 此时,服务之间独立,天法进行RPC,很多基础代码不可复用,得复制使用

或者将应用服务器换为应用服务器集群,通过nginx对服务器进行反向代理+负载均衡, 添加缓存服务, mysql 读写分离提高性能,却依然改变不了单体应用本身难于扩展维护这个问题.

但是,如果对于一些业务联系十分紧密,不会大规模扩展的系统,使用SpringBoot开发为单体应用,就十分适合

SOA实践-微服务

微服务架构就是将一个单体的巨无霸系统拆分成一组可复用的服务,基于这些服务构成的应用系统。图中左边是早期的单体应用系统架构,里面的各个模块互相调用、耦合,所有的系统和模块打包在一起,最后组成一个庞大的巨无霸系统。右边是微服务架构,根据服务的粒度和可复用的级别,对服务进行拆分,以独立部署服务的方式,对外提供服务调用。而应用系统也按照用途和场景的不同,依赖这些可复用的服务,进行逻辑组合,构建成自己的业务系统。

通过这样一种方式,系统变得比较简单,复用级别也比较高,同时也解决了前面提出的单体巨无霸的几个重要问题。因为每一个服务或是应用系统,代码都比较简单,所以编译和部署、开发和测试,都比较简单和快速。而且这些服务都是独立维护和部署的,它的代码分支也是独立的,不会和其他的代码分支一起进行管理,减少了代码冲突的可能性。发布的时候,也是每个服务独立发布,只要做好服务的版本控制和接口兼容,应用系统不需要跟随服务一起更新发布。

在微服务体系中,连接数据库的是具体的服务,应用系统不需要自己去连接数据库,只需要调用组合服务,对服务进行编排。所以对数据库的连接也相对比以前更少一些。最主要的是当需要开发新业务的时候,使用这种方式不需要对原有的单体系统进行各种重构和代码修改,只需要开发一个新的业务系统,组合调用现有的微服务,就可以组合出来一个新的产品功能,可以快速开发新产品。

微服务架构代表有分布式RPC框架dubbo,基于HTTP通信的代表的SpringCloud,还有SpringCloud和dubbo结合的SpringCloud Alibaba等,微服务架构目前已经比较成熟,供选择,成熟的组件非常多,如netflix的eureka+feign+ribbon+zuul+hystrix已经经过了大规模的商用,微服务其实是更偏向业务场景的一种划分

  1. 首先是业务场景中微服务应该如何划分,是不是有清晰的边界,服务之间依赖关系是怎么样的,这决定了一个微服务的复用程度,开发的便利程度,所以转型微服务要业务先行,理清依赖关系和边界,设计好耦合关系少,依赖清晰的模块后,才可以构建一个好的微服务系统,如果说模块本身混乱,耦合严重,拆成微服务架构就是给自己找麻烦,让事情变得更加复杂
  2. 跟其他技术不同,微服务具有强业务属性,业务如果本身结构混乱,目标不清晰,仓促使用微服务,可能会使整个系统变得更加复杂和难以控制。所以在使用微服务前,最重要的是要先明确自己的需求:我们到底想用微服务达到什么样的目的?需求清晰了,再去考虑具体的方案和技术。这也是使用大多数技术的时候应有的方法和思路。要使用微服务架构的时候,一定要搞清楚实施微服务的目的究竟是什么,是为了业务复用,是为了开发边界清晰,是为了分布式集群提升性能,还是仅仅想要使用微服务?目的一定要清楚。

微服务架构的缺点

  1. 架构复杂,组件众多
  2. 分布式事务,无法像单机系统的ACID事务控制,只能选择AP或者CP,CAP--BASE

  1. 如何划分出边界清晰低耦合的模块?

  2. 部署复杂,需要学习DevOps全自动部署技术栈

  3. 故障的传播性(雪崩现象) 如果某个服务发生异常或者网络错误,它的响应延迟或者失败率增加的时候,继续调用这个服务实例会导致请求者阻塞。请求阻塞以后会导致资源消耗增加,最后可能会导致请求者也失败和崩溃,进而出现服务的级联崩溃,也就是服务请求者的请求者也失败,最后会导致整个系统全部失败,即雪崩现象。在 Spring Cloud 中可以使用 Hystrix 实现断路器,实现服务的fail-fast机制,避免故障的进一步扩散,待情况回复后自动复原.

组件分享

Spring-Boot-starter-actuator+Springboot admin

先补充下即SpringBoot Actuator+SpringBoot Admin 是spring提供的对应用各个方面指标的监测功能,如普通服务的暴露的端点,网络配置的路由等

引入的方式依然是springboot的标准方式,starter+配置

1.引入依赖

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

2.application.yml配置

#配置actuator/info端点信息,这里引用的Maven配置文件中的项目信息
info:
  build:
    artifact: "@project.artifactId@"
    name: "@project.name@"
    version: "@project.version@"
#暴露所有端点
management:
  endpoints:
    web:
      exposure:
        include: "*"
#显示详情
  endpoint:
    health:
      show-details: always
    # 可以远程关闭
    shutdown:
      enabled: true

配置好后,我们可以在idea中看当前应用的父子上下文,查看其中的Bean以及依赖关系

查看项目的健康状态

查看项目暴露的端点

也可以通过网页方式访问/actuator

比如我们随便点一个Beans,可以看到SpringBoot为我们注入的和自己声明的Bean

在微服务网关gateway中,我们可以通过对端点actuator/gateway/routes发起get,得当前网关配置的所有路由信息

接着看看springboot-admin 可以实时监控到我们注册到上面的微服务的各种信息,其实大部分信息是我们刚才提到的actuator的可视化信息

我们刚才说了微服务两种常见的通信方式,基于RPC的dubbo和基于HTTP的SpringCloud,今天分享SpringCloud中两种基于HTTP的网络请求框架,和与之配合的负载均衡组件Ribbon,以及dubbo,我们在demo和应用里学习

RestTemplate

RestTemplate是Spring中一个访问第三方RestfulAPI接口的网络请求框架,设计原则是和其他SpringTemplate,如JdbcTemplate,JmsTemplate类似,为执行复杂行为提供一个简单方法

RestTemplate是用来消费RestFul服务的,所以RestTemplate的主要方法都与Rest的方法紧密相关,如GET,POST,PUT对应getForObject(),postForObject(),put(),delete()等

单独使用(例子在microservice-01中)

  1. 注入Bean
/**
 * Spring Boot  1.4版本之后不再提供RestTemplate
 * 定义了一个RestTemplateBuilder允许您更好地控制所RestTemplate创建的对象。
 * 你可以RestTemplateBuilder在你的@Bean方法中注入一个参数来创建一个RestTemplate:
 * @author zhaoxu
 */
@Configuration
public class RestTemplateConfig {

    @Resource
    RestTemplateBuilder restTemplateBuilder;

    @Bean(name = "restTemplate")
    public RestTemplate getRestTemplate() {
        return restTemplateBuilder.build();
    }

}


  1. 把声明好的Bean注入使用,这里获取百度的首页html代码
/**
 * RestTemplateController RestTemplate 单独使用的示例Controller
 * @author zhaoxu
 */
@RestController
public class RestTemplateController {

    @Resource
    RestTemplate restTemplate;

    @GetMapping("/baidu")
    public String getBaiduJson() {
        return  restTemplate.getForObject("https://www.baidu.com",String.class);
    }
}
  1. 测试

RestTemplate结合Ribbon做负载均衡,结合ZK做注册中心(ribbon-consumer-client中)

  1. 引入Ribbon的starter
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
  1. 在单独使用的基础中添加@LoadBalanced注解
@Configuration
public class RestTemplateConfig {

    @Resource
    RestTemplateBuilder restTemplateBuilder;

    @LoadBalanced
    @Bean(name = "restTemplate")
    public RestTemplate getRestTemplate() {
        return restTemplateBuilder.build();
    }

}
  1. 就实现了负载均衡,我这里使用了zookeeper作为注册中心,关于zk的配置文件补充一下,如果配置了discovery,instant-host,那么address会显示instance-host配置的名称,配置hosts即可访问
spring:
  cloud:
    zookeeper:
      #home
      connect-string: 192.168.50.75:2181
      discovery:
        root: /topsec/services
        instance-host: asset-manage

如果不配置instance-host,则会显示机器名,通过机器名去访问服务

这里可以看到application name为microservice的节点我注册了两个服务 ,这时通过服务名就可以进行服务调用,并负载均衡

     @Resource
    RestTemplate restTemplate;

    /**
     * 借助注册中心zk,ribbon+RestTemplate实现负载均衡
     */
    @GetMapping("/microserviceinfo")
    public String getMicroServiceActuatorInfo () {
        return restTemplate.getForObject("http://microservice/actuator/info",String.class);
    }


Ribbon

关于负载均衡有两种方式

而Ribbon是接着来说Ribbon,作为一个负载均衡的组件,Ribbon将负载均衡逻辑以代码形式封装到服务消费者的客户端上,也就是ribbon的的负载均衡,是写在调用者的代码中的,在gateway中配置路由的时候,同样是使用lb启动了netflix ribbon

服务消费者客户端维护了一份服务提供者的信息列表 有了信息列表,通过负载均衡策略分摊给多个服务者 从而达到负载均衡,Ribbon 使用客户端负载均衡的方式 将负载均衡逻辑封装在客户端中,并且运行在客户端们进程里

原生RibbonApi

不借助任何框架我们也可以使用Ribbon,这里我们借助Ribbon原生API,自己维护了两个服务列表,采用随机负载均衡,调用30次查看结果

/**
 * ribbon原生Api
 * @author zhaoxu
 */
public class RibbonNativeClientDemo {
    public static void main(String[] args) {
        //服务列表
        List<Server> serverList = Arrays.asList(new Server("baidu.com",8081),new Server("google.com",8082));
        //构建负载实例
        BaseLoadBalancer loadBalancer = LoadBalancerBuilder.newBuilder().buildFixedServerListLoadBalancer(serverList);
        loadBalancer.setRule(new RandomRule());
        //重复调用查看效果


        for (int i = 0; i <30 ; i++) {
            String result =LoadBalancerCommand.builder().withLoadBalancer(loadBalancer).build().submit(new ServerOperation<Object>() {
                @Override
                public Observable<Object> call(Server server) {
                    String addr = server.getHost()+":"+server.getPort();
                    System.out.println("调用地址"+addr);
                    return Observable.just("");
                }
            }).toBlocking().first().toString();
        }
    }
}

看出结果是随机分布的,对我们维护的两个节点进行了调用

Ribbon仅结合ZK拉取服务注册信息

 /**
     * 单独使用Ribbon,不使用RestTemplate,是依靠从zk上拉取注册信息,实现负载均衡
     */
    @GetMapping("/testRibbon")
    public String testRibbon () {
        ServiceInstance serviceInstance =loadBalancerClient.choose("microservice");
        return serviceInstance.getHost()+":"+serviceInstance.getPort();
    }

Ribbon结合RestTemplate

这一点我们在刚刚的RestTemplate讲过,加一个@LoadBalanced注解即可,我们来说说如何改变与RestTemplate结合时的负载均衡策略,自定义Bean,返回新的策略,我们同样可以实现IRule接口实现自己的策略

/**
 * 修改ribbon的策略
 * @author zhaoxu
 */
@Configuration
public class RibbonConfig {


    @Bean
    public IRule ribbonRule(){
        //随机策略,一种负载均衡策略,可在现有服务器之间随机分配流量。
        //return new RandomRule();
        //选择最小请求数 A rule that skips servers with "tripped" circuit breaker and picks the
        // * server with lowest concurrent requests.
        //return new BestAvailableRule();
        //轮询 The most well known and basic load balancing strategy, i.e. Round Robin Rule.
        //return new RoundRobinRule();
        //默认根据轮询的方式重试,级联策略,可传入其他的策略
        return new RetryRule(new RandomRule(),300);
        //根据响应时间去分配一个weight,weight越低,被选择的可能性越低
        //return new WeightedResponseTimeRule();
        //根据server的zone区域和可用性来轮询选择
        //return new ZoneAvoidanceRule();
    }
}

目前提供的有这么多策略,我们一个个看一下

Feign

Feign是一个伪JavaHTTP客户端,Feign不做任何的请求处理,通过注解生成Request模板,简化HTTPAPi的开发,在真正的发送HTTP请求之前,Feign通过处理注解的方式替换Request模板中的参数,生成真正的Request,交给JavaHttp客户端去处理 也就是发给Feign的client组件,即真正发送Request请求和接受Response请求的组件,Client在Feign中是一个接口,默认情况下是HttpUrlConnnction,也可以替换成其他的Http框架,如HttpClient和OkHttp 利用这种方式,开发者只需要关注Feign注解模板的开发,而不用关注Http请求本身

Contract 契约组件

在 Feign 中可以通过定义 API 接口的方式来调用远程的 Http API,在定义调用 Client 的时候需要增加一些注解来描述这个调用 API 的基本信息,比如请求类型是 GET 还是 POST,请求的 URI 是什么。Contract 允许用户自定义契约去解析注解信息,最典型的应用场景就是在 Spring Cloud 中使用 Feign,我们可以使用 Spring MVC 的注解来定义 Feign 的客户端,这是因为 Spring Cloud OpenFeign 中实现了自己的 SpringMvcContract。

Encoder 编码组件

通过该组件我们可以将请求信息采用指定的编码方式进行编码后传输。

Decoder 解码组件

Decoder 将相应数据解码成对象。

Client 请求执行组件

Client 是负责 HTTP 请求执行的组件,Feign 将请求信息封装好后会交由 Client 来执行,Feign 中默认的 Client 是通过 JDK 的 HttpURLConnection 来发起请求的,在每次发送请求的时候,都会创建新的 HttpURLConnection 链接,使用默认的方式,Feign 的性能会很差,原因就是使用了 HttpURLConnection。你可以通过扩展该接口,使用 Apache HttpClient 等基于连接池的高性能 HTTP 客户端。

Feign原生Api

与Ribbon相似,同样可以不借助任何框架单独使用Feign,这样仅仅把它作为了一个Http框架而已

/**
 * @author zhaoxu
 */
public class FeignApiDemo {
    public static void main(String[] args) {
        MicroService microService = Feign.builder().
                decoder(new GsonDecoder())
                .target(MicroService.class,"http://127.0.0.1:8763/");
        System.out.print(microService.getActuatorInfoFromMicroService());
    }
}

/**
 * 内部接口
 * @author zhaoxu
 */
interface MicroService {
    @RequestLine("GET /actuator/info")
    JsonObject getActuatorInfoFromMicroService();

}

Feign结合Ribbon

原生的 Feign 在使用层面已经很方便了,但是在 Spring Cloud 体系中却不那么适用,所以官方团队在 Feign 的基础上进行扩展,推出了 spring-cloud-openfeign,目的是能够让广大的开发者在 Spring Cloud 体系中使用 Feign 变得更加简单。

 

首先我们开发的 API 都用的是 Spring MVC 的注解,比如 RequestMapping 等,Feign 的注解是单独的一套,所以我们编写调用 Client 接口时,需要根据已有的接口来编写,在 spring-cloud-openfeign 中,实现了 Spring MVC 的一套注解,调用方 Client 接口中的注解和 API 方可以一致,非常方便。

这是我们开发中最常用,最方便的一种组合了,放在最后介绍,集成方式如下

  1. 还是引入依赖,一个是feign的starter,一个是feign的编码器,解码器等的依赖
   <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-gson -->
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-gson</artifactId>
            <version>11.0</version>
        </dependency>

我们点进spring-cloud-starter-openfeign,看到其实feig的start已经集成了ribbon,hystrix熔断器依赖

2.主启动类配置 @EnableFeignClients开启包扫描,扫描被@Feign注解的接口

/**
 * @author zhaoxu
 */
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class FeignClientApplication {

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

}
  1. 写一个@FeignClient修饰的接口

这里value为服务名,而@GetMapping写远端服务的接口

/**
 * @author zhaoxu
 */
@FeignClient(value = "microservice")
public interface IMicroService {

    @GetMapping(value = "/actuator/info")
    String getActuatorInfoFromMicroService();
}

  1. 使用,注入接口,直接如同调用本地方法般使用,并且已经自动集成了负载均衡
/**
 * @author zhaoxu
 */
@RestController
public class FeignTestController {

    @Resource
    IMicroService microService;

    @GetMapping("/microserviceinfo")
    public String getMicroServiceInfo () {
        return microService.getActuatorInfoFromMicroService();
    }

}
  1. 我们还可以做一些配置,比如声明FeignConfig配置类,做一些超时重连的配置,或者声明RibbonConfig类,修改负载均衡策略等.

dubbo的RPC调用

成熟的微服务框架还有RPC方式,如dubbo框架,同样也是经过了大业务量的考验,也来学一下,方便在最适合的业务中,选最适合的框架

 Dubbo 中发起远程调用是不需要使用者去关注服务提供者信息的,直接注入一个接口,然后调用接口中的方法,就像调用本地方法一样。这样的好处是开发效率高,代码优雅。    我们看一个原生duboo的demo,通过xml注解形式配置的,这里我们引入了最小依赖,仅仅引入了spring-context容器,dubbo依赖,封装了NIO的netty等几个核心包  

应用中的微服务调用

1.feign方式 需要的配置与服务方没有什么关系,也是Feign的优点之一,因为本身就是伪HTTP调用框架,那么我们查看服务提供方暴露的接口,参数,根据上面我们讲过的方式在调用者主启动类加上@EnableFeignClients开启包扫描,扫描所有被@FeignClient(value = "asset-manage")修饰的接口,再使用springmvc 的注解写feign的调用,接着在使用处进行Bean的注入即可

    <!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-spring-boot-starter -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>2.7.6</version>
    </dependency>
    
    

而dubbo除了使用dubbo的一整套微服务框架,也可以使用dubbo-spring-boot-starter进行整合,分别在客户端和服务端进行配置 provider

consumer注解注入

之后就可以调用这个service接口中的所有方法