Spring Cloud之Feign消费和Hystrix熔断

Spring Cloud的Feign消费和Hystrix熔断

现如今,由于互联网的流行,很多特产都可以在网上订购,你可以在堆满积雪的冬北订购海南的椰子,海南的椰子就会采用很快的物流方式调送到堆满积地的东北,就相当于在本地实现了买椰子的举动,这种远程调用的方法称为Feign。如下图所示。

 

一、Feign的解释

Feign 主要是帮助我们方便进行rest api服务间的调用,其大体实现思路就我们通过标记注解在一个接口类上(注解上将包含要调用的接口信息),之后在调用时根据注解信息组装好请求信息,通过服务器获取生成真实的服务地址,最后将请求发送出去;之后将接收到的结果反序列化为相关的Java对象供我们直接使用。

Feign在实际应用中通过在启动类上标记 @EnableFeignClients 注解来开启feign的功能,服务启动后会扫描 @FeignClient 注解标记的接口,然后根据扫描的注解信息为每个接口类生成feign客户端请求,同时解析接口方法中的Spring MVC的相关注解,通过专门的注解解析器识别这些注解信息,以便后面可以正确的组装请求参数,使用Eureka 获取到请求服务的真实地址等信息,最后使用 http 相关组件进行执行调用。其大致流程图如下:

 

在Feign的通信过程中,可能会出现服务器荡机的状况,Hystrix起到了一定的熔断保护措施。

二、Hystrix的解释

Hystrix是一个用于处理分布式系统的延迟和容错开源库,在分布式系统中,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix能保证在一个依赖出现问题时,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。

hystrix提供了五种理论技术,如下:

1、降级:当调用出现异常或超时等无法返回正常数据时,返回一个合理的结果或实现fallback方法,针对客户端而言。

2、熔断:当失败率达到阈值自动触发降级,这里的熔断就是具有特定条件的降级,当出现熔断时在设定的时间内不在请求。熔断有自动恢复机制,举个例子说,当熔断器启动后,每隔10秒,尝试将新的请求发送给service,如果服务可正常执行并返回结果,则关闭熔断器,恢复服务。如果仍调用失败,则继续返回fallback,熔断器持续开启。

3、请求缓存:服务A调用服务B,如果在A中添加请求缓存,第一次请求后走缓存,不在访问微服务B,即使出现大量请求,不会对B产生高负荷。请求缓存可以使用spring cache实现。

4、请求合并:当服务A调用服务B时,设定在5毫秒内所有请求合并到一起,对于服务B的负荷就会减少。使用@HystrixCollapser。方法返回值必须为Future

5、隔离:隔离分为线程池隔离合信号量隔离。通过判断线程池或信号量是否满,超过容量的请求直接降级,从而达到限流。

Hystrix可以理解成电路中的保险丝,线路不通还有那么一层保护,如下图所示。

 

三、产品的eureka服务器的启动

1、点击File--->New---->Project.....,如下图。

 

2、在弹出的地话框中,左边点击Spring Initializr,表示Spring的初始程序,右边在默认的地方可能会初始化失败,需要选择自定义的地址:http://start.aliyun.com。如右图所示。

 

3、在接下来的弹出框中输入spring cloud项目名称的Group和artifactId。如下图所示。

 

4、点击Next进入下一步,左边先点击Web,右边点击Spring web可以建立Web应用程序。如下图。

 

5、继续在这个对话框中,左边点击Spring Cloud Discovery,右边点击Eureka Server,如下图所示。

 

6、然后点击Next进入到“下一步”,会出现对话框。如下图所示。

 

7、在出现的对话框中,点击Finish后完成项目的构建向导。

项目框架建立后,修改主程序中的主类,如下图所示。

 

8、在打开的主程序中,加入注解@EnableEurekaServer。如下图所示。

 

最终,eurekaserver的程序代码如下。

package com.myfirsteurekabalance.myfirsteurekabalance;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class MyfirsteurekabalanceApplication {

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

}

9、下面需要编辑resources下面的application.yml文件,在原来的resouces目录下只有application.properties,需要把application.properties改成application.yml。然后编辑该文件。

 

10、application.yml的文件内容如下。

# 应用名称
spring:
  application:
    name: myfirsteurekabalance
# 应用服务 WEB 访问端口
server:
  port: 8819
eureka:
  client:
    register-with-eureka: true
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:8819/eureka
  instance:
    hostname: myprovider1

其文件内容的解释如下 。

 

四、产品的Feign客户端的搭建

1、同样点击File--->New----->Project....,如下图所示。

 

2、在打开的对话框中,左边继续点击Spring Initializr,表示Spring的初始程序,右边在默认的地方可能会初始化失败,需要选择自定义的地址:http://start.aliyun.com。如下图所示。

 

3、点击Next,然后在出现的对话框中输入项目名称。如下图所示。

 

4、在接下来出现的对话框中,左边选择Web,右边选择Spring Web。如下图所示。

 

5、继续在这个对话框中,左边选择Spring Cloud Discovery,右边选择Eureka Discovery Client。如下图所示。

 

6、然后点击左侧的Spring Cloud Routing,然后点右侧的OpenFeign。再点击Next。

 

7、然后点击下一步即Next,在出现的对话框中直接点击Finish完成项目建立的向导。如下图所示。

 

项目完成后,接下来设置pom.xml中需要用到的依赖包,具体依赖内容如下。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <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>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        <version>2.2.10.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zipkin</artifactId>
        <version>2.2.8.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

这里面后加入的依赖是spring-cloud-starter-zipkin、lombok、spring-cloud-starter-netflix-hystrix。具体依赖的内容如下。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>2.2.10.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
    <version>2.2.8.RELEASE</version>
</dependency>

8、这里是需要Feign的fallback,Fallback可以帮助我们在使用Feign去调用另外一个服务时,如果出现了问题,走服务降级,返回一个错误数据,避免功能因为一个服务出现问题,全部失效。需要首先定义feign包,然后产生feign的java包。这里feign的包中包括两个Feign消费端,一个是Product的Feign消费端,一个是Order的Feign消费端。结构如下。

 

对于ProductFeign需要使用FegnClient指定消费客户端,同时指定消费在eureka注册中心的服务器名称,并且指明如果服务出现问题的服务降级错误处理fallback。

注意,这里的feign是一个接口,代码如下。

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.stereotype.Component;
@FeignClient(name="myprovider5",fallback= ProductFallBackMethod.class)
public interface ProductFeign {
    @GetMapping("/product/lists")
    public String lists();
}

对于OrderFeign的代码也是做这样的处理,代码如下。

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.stereotype.Component;
@FeignClient(name="myprovider4",fallback= OrderFallBackMethod.class)
public interface OrderFeign {
    @GetMapping("/order/lists")
    public String lists();
}

9、每个feign消费端都指定了一个fallback类方法,所以需要分别定义ProductFallback和OrderFallback的类方法,可以把这两个类放在fallback包下面.结构如下图。

 

在ProductFallbackMethod方法中实现定义的Feign接口,并且重写ProductFeign接口中的lists方法.特别注意的是这个类必须使用@Component注解来说明,不然spring cloud找不到这个fallback方法。代码如下。

package com.example.mymicroservice1.fallback;

import com.example.mymicroservice1.feigns.ProductFeign;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class ProductFallBackMethod implements ProductFeign {
    @Override
    public String lists() {
        log.info("ProductFacllbackMethod调用异常");
        return "ProductFacllbackMethod调用异常";

    }
}

代码中的log只是输出日志文件, return是返回到网页中的显示数据,这里的log使用lombok的Slf4j注解来实现,实现后就会有log的声明。

同理实现OrderFallBackMethod方法,代码如下。

import com.example.mymicroservice1.feigns.OrderFeign;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class OrderFallBackMethod implements OrderFeign {
    @Override
    public String lists() {
        log.info("OrderFallBackMethod调用异常");
        return "OrderFallBackMethod调用异常";
    }
}

实现了这个fallback类后,对这两个Feign的调用可以通过service来实现,首先可以建立services包,然后建立一个微服务接口,注意,这里是接口, queryProductList方法中未来会实现ProductFeign, queryOrderList方法中未来会实现OrderFeign。

将两个服务放在一个微服务service的接口Micro1Service代码如下。

package com.example.mymicroservice1.services;
public interface Micro1Service {
    String queryProductList();
    String queryOrderList();
}

接下来定义第一个微服务Micro1Service接口的实现类。services包的结构图如下。

 

这里建立一个impl包,在impl包下面建立Micro1Service的接口的实现类Micro1ServiceImpl.代码如下。

package com.example.mymicroservice1.services.impl;

import com.example.mymicroservice1.feigns.OrderFeign;
import com.example.mymicroservice1.feigns.ProductFeign;
import com.example.mymicroservice1.services.Micro1Service;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@Service
@Slf4j
public class Micro1ServiceImpl  implements Micro1Service {
    @Autowired
    private ProductFeign productFeign;
    @Autowired
    private OrderFeign orderFeign;
    @Override
    public String queryProductList() {
        String lists=productFeign.lists();
        log.info("ProductFeign调用返回结果:{}",lists);
        return lists;
    }

    @Override
    public String queryOrderList() {
        String lists=orderFeign.lists();
        log.info("OrderFeign调用结果返回:{}",lists);
        return lists;
    }
}

注意,这里的Micro1ServiceImpl实现类需要实现接口Micro1Service,同时需要使用注解Service,这样Spring Cloud可以得到这样的Service.在Service代码中将ProductFeign和OrderFeign 注入到Service当中,再实现Micro1Service的两个方法。在两个方法中分别调用ProductFeign和OrderFeign的lists接口。最后返回lists()接口返回的数据。

Service代码设置成功后,设置Controller控制器的代码,在控制器实现ProductList商品列表的显示及OrderList订单列表的显示.同时需要RestController对于页面返回数据的显示,在控制器中第一个微服务Micro1Service也需要注入到Controller控制器中。

代码如下。

package com.example.mymicroservice1.controller;
import com.example.mymicroservice1.services.Micro1Service;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@Slf4j
@RestController
@RequestMapping("/micro1")
public class Micro1Controller {
    @Autowired
    private Micro1Service micro1service;
    @RequestMapping("/productList")
    public String productList(){
        return micro1service.queryProductList();
    }
    @RequestMapping("/orderList")
    public String orderList(){
        return micro1service.queryOrderList();
    }
}

因为这个项目中使用Feign消费端,所以需要在主启动类中加入@EnableFeignClients的注解.代码如下。

package com.example.mymicroservice1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class Mymicroservice1Application {

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

}

最后的yml文件中需要设置客户端feign的启动,新版本的启动hystrix使用circuitbreaker,代码如下。

spring:
  application:
    name: myprovider2
  zipkin:
    base-url: http://localhost:9411/
  sleuth:
    sampler:
      probability: 1.0
server:
  port: 8830
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8819/eureka
  instance:
    hostname: myprovider2
feign:
  circuitbreaker:
    enabled: true

五、产品的Product消费端的产生.

1、同样点击File--->New----->Project....,如下图所示。

 

2、在打开的对话框中,左边继续点击Spring Initializr,表示Spring的初始程序,右边在默认的地方可能会初始化失败,需要选择自定义的地址:http://start.aliyun.com。如下图所示。

 

3、点击Next,然后在出现的对话框中输入项目名称。如下图所示。

 

4、在接下来出现的对话框中,左边选择Web,右边选择Spring Web。如下图所示。

 

5、继续在这个对话框中,左边选择Spring Cloud Discovery,右边选择Eureka Discovery Client。如下图所示。

 

6、然后点击下一步即Next,在出现的对话框中直接点击Finish完成项目建立的向导。如下图所示。

 

7、在Product的微服务消费端中需要定义一个Pojo与数据库中的product表相对应。建立pojo对应的包entity,然后在entity包中产生Pojo的类Product。结构如下图所示。

 

8、在Product商品类中定义商品需要的信息。

package com.example.mymicroservice2.entity;
import lombok.Data;
import java.util.Date;
@Data
public class Product {
    private Integer id;
    private String productName;
    private Double productPrice;
    private String crop;
    private Date createTime;
    private Date modifyTime;
    private String remark;
}

这里通过lombok的Data注解来实现类中的Getter和Setter方法。

根据mvc的原理,下面需要根据Product定义dao模型。首先建立dao包,然后来dao包下建立ProductDao接口文件,注意这里是一个接口,结构如下图。

 

在接口文件中,定义接口方法lists,其目的显示product表中的所有数据。同时将接口中的注解Mapper来实现与mybatis和配置文件进行对应。代码如下。

package com.example.mymicroservice2.dao;
import com.example.mymicroservice2.entity.Product;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ProductDao {
    public List<Product> lists();
}

这个ProductDao接口需要对应于mybatis的一个ProductDao.xml的mapper文件,mapper文件是存放在resource目录下,结构截图如下。

 

mapper文件内容中定义resultMap对应于Product类的类中字段,并通过select方法来完成product表的查询语句SQL操作,ProdudctDao.xml具体代码内容如下。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mymicroservice2.dao.ProductDao">
    <resultMap id="BaseResultMap" type="com.example.mymicroservice2.entity.Product">
        <id column="id" jdbcType="INTEGER" property="id"></id>
        <result column="product_name" jdbcType="VARCHAR" property="productName"></result>
        <result column="product_price" jdbcType="NUMERIC" property="productPrice"></result>
        <result column="crop" jdbcType="VARCHAR" property="crop"></result>
        <result column="create_time" jdbcType="TIMESTAMP" property="createTime"></result>
        <result column="modify_time" jdbcType="TIMESTAMP" property="modifyTime"></result>
        <result column="remark" jdbcType="VARCHAR" property="remark"></result>
    </resultMap>
    <select id="lists" resultMap="BaseResultMap">
        select * from product
    </select>
</mapper>

注意mapper文件中resultMap中的column属性指向数据库中字段名,jdbcType指示数据库中的字段类型,property指示java bean中的类属性。

在<select>标签中指示select查询SQL语句查找product表中的所有数据。

有了dao层和pojo模型后,定义service层的接口,然后通过实现service层的接口实现接口的内部实现代码,结构图如下图。

 

ProductService接口方法中定义查询所有商品的接口lists,其代码如下。

package com.example.mymicroservice2.services;

import com.example.mymicroservice2.entity.Product;
import java.util.List;
public interface ProductService {
    public List<Product> lists();
}

对这样的Service接口定义实现,需要使用implements关键字实现这个接口,重写其中的lists方法,同时将ProductDao作为dao层注到Service层中,ProductDao层的逻辑是通过mybatis的配置文件中SQL语句来实现的.ProductServceImpl的代码如下。

package com.example.mymicroservice2.services.impl;

import com.example.mymicroservice2.dao.ProductDao;
import com.example.mymicroservice2.entity.Product;
import com.example.mymicroservice2.services.ProductService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import com.alibaba.fastjson.JSON;
import java.util.List;

@Service
@Slf4j
public class ProductServiceImpl  implements ProductService {
    @Resource
    private ProductDao productDao;

    @Override
    public List<Product> lists() {
        List<Product> mylist=productDao.lists();
        log.info("查询商品的结果:{}",JSON.toJSONString(mylist));
        return mylist;
    }
}

这里输出调用了alibaba的json模块把输出的字符串以json的形式输出。

Service接口实现后,就可以实现Controller控制器的逻辑.在Controller接口中需要注意前面Feign消费端定义的请求接口是”/product”中的”/lists”路由路径.因此这里的Controller也要实现这样的路径.代码如下。

package com.example.mymicroservice2.services.impl;
import com.example.mymicroservice2.dao.ProductDao;
import com.example.mymicroservice2.entity.Product;
import com.example.mymicroservice2.services.ProductService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import com.alibaba.fastjson.JSON;
import java.util.List;
@Service
@Slf4j
public class ProductServiceImpl  implements ProductService {
    @Resource
    private ProductDao productDao;

    @Override
    public List<Product> lists() {
        List<Product> mylist=productDao.lists();
        log.info("查询商品的结果:{}",JSON.toJSONString(mylist));
        return mylist;
    }
}

代码中也需要指明返回前端页面的是json数据的字符串化,使用了alibaba的json模块来进行转换。

代码截图如下。

package com.example.mymicroservice2.controller;
import com.example.mymicroservice2.entity.Product;
import com.example.mymicroservice2.services.ProductService;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.alibaba.fastjson.JSON;
import java.util.List;
@RestController
@RequestMapping("/product")
public class ProductController {
    @Autowired
    private ProductService productService;
    @GetMapping("/lists")
    public String lists(){
        List<Product> lists=productService.lists();
        return JSON.toJSONString(lists);
    }
}

客户端的yml文件中需要增加指明zipkin的路径,数据库的连接四个参数及mybatis的配置文件路径。具体内容如下。

spring:
    application:
        name: myprovider5
    datasource:
        url: jdbc:mysql://localhost:3306/testservice?characterEncoding=utf8
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: admin
    zipkin:
        base-url: http://localhost:9411/
    sleuth:
        sampler:
            probability: 1.0
mybatis:
    mapper-locations: classpath*:/mapper/*.xml
server:
    port: 8065
eureka:
    client:
        service-url:
            defaultZone: http://localhost:8819/eureka

    instance:
        hostname: myprovider5

六、环境测试

1、新版本的zipKin,先启动zipkin的server端,通过maven仓库下载exec包的zipkin的jar包。然后通过java来执行。执行方法如下。

 java -jar zipkin-server-2.12.9-exec.jar

运行结果如下图所示。

 

2、启动eurekaserver端注册中心。

3、然后启动mymicro1server端的关于商品和订单消费的feign微服务。

4、再次启动mymicro2server端关于商品请求的与数据库关联的逻辑接口服务。

5、最后启动mymicro3server端关于订单请求的与数据库关联的逻辑接口程序。

zipkin启动后,可以通过网页web页面访问9411端口,即可看到zipkin页面。如下图所示。

 

7、urekaserver端,mymicro1server端,mymicro2server端三者都启动后,可以在浏览器中访问接口,就会返回数据信息。如下图所示。

 

注意:这里需要建立mysql数据库,数据库名与配置文件中的一致,并且在当前数据库中建立product表格和order表格,product表格中的字段与mapper文件中配置的resultMapper中的类型匹配需一致,并且向表中插入数据,表的结构和数据如下图.

 

8、当访问成功后,断开mymicro2server端,也就是断开商品列表的控制器端,最后访问结果如下 ,证明feign消费端的fallback有效。

 

9、访问成功后,刷新 zipkin的访问页面。可以看到服务名及服务的接口span名。点击查找时,如下图所示。

 

10、此时,可以点击zipkin页面左上角的Try lens UI按钮,然后可以看到图形化的链路跟踪。如下图。

 

看到的效果如下图。

 

 

 

七、注意点

1、新版的Spring cloud必须有新版的依赖,通过mvnrepository仓库查询最新版的hystrix的版本是2.2.10.RELEASE。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>2.2.10.RELEASE</version>
</dependency>

2、新版的spring cloud启动hystrix需要使用circuitbreaker,设置其为true,代码如下.

feign:
circuitbreaker:
   enabled:true

3、fallback方法中必须加入@Component注解,不然不能将fallback注册到方法中。

4、在主启动类中使用注解@EnableFeignClients.

5、在Feign的接口方法中,使用@FeignClient注解,在注解中使用能够ping通的子服务服务器名称,同时调用熔断的fallback方法,后面是fallback方法的类名。

6、测试时当子服务的服务器没有启动时,会显示fallback方法后面的类名中实现的方法。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值