Spring Cloud 入门 ---- OpenFeign 服务调用【随笔】

服务调用

Spring Cloud 入门 ---- OpenFeign 服务调用
简介

Feign 是一个声明式 WebService 客户端,使用 Feign 能让编写 WebService 客户端更加简单。

它的使用方法是定义一个服务接口然后在上面添加注解。Feign 也支持可插拔式的编码器和解码器。Spring Cloud 对 Feign 进行了封装,使其支持 Spring MVC 标注注解和 HttpMessageConverters。OpenFeign 可以与 Eureka 和 Ribbon 组合使用以支持负载均衡。

OpenFeign 能干什么

OpenFeign 旨在使编写 Java Http 客户端变得更加容易。前面我们使用 Ribbon + RestTemplate 时,利用 RestTemplate 对 http 请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多次调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。 所以,OpenFeign 在此基础上做了进一步封装,由它来帮助我们定义和实现依赖服务接口的定义。在 OpenFeign 的实现下,我们只需要创建一个接口并使用注解的方式来配置它【类似于以前 Dao 接口上标注 Mapper 注解】,现在是一个微服务接口上面标注一个 OpenFeign 注解即可, 即可完成对微服务提供方接口的绑定,简化了使用 Ribbon + RestTemplate 时,自动封装服务调用客户端的开发量。

OpenFeign 集成了 Ribbon

利用 Ribbon 维护了服务列表信息,并且通过轮询实现了客户端的负载均衡。而与 Ribbon 不同的是,通过 OpenFeign 只需要定义服务绑定接口并且以声明式的方法,优雅而简单的实现服务调用。

Feign 与 OpenFeign 区别

1604385539612

创建演示项目

沿用之前的 Eureka 注册中心,与 ribbon 一样 Open Feign 也是用在客户端的,也就是消费方。

创建服务提供者

导入 pom 依赖

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

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- druid-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>

添加 yml 配置,由于需要演示负载均衡,需要添加两个配置文件 application-one.ymlapplication-two.yml 由于这两个配置文件基本相同,这里只给出 application-one.yml 的全部配置,另一个只给出不同的配置。

application-one.yml

server:
  port: 8013

spring:
  application:
    name: feign-service-provider
  security:
    # 配置spring security登录用户名和密码
    user:
      name: akieay
      password: 1qaz2wsx
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3600/cloud?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: root
    #   数据源其他配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    #   配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    filters: stat,wall
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500


eureka:
  client:
    #表示是否将自己注册进 Eureka Server服务 默认为true
    register-with-eureka: true
    #f是否从Eureka Server抓取已有的注册信息,默认是true。单点无所谓,集群必需设置为true才能配合ribbon使用负载均衡
    fetch-registry: true
    service-url: # 设置与 Eureka Server 交互的地址 查询服务与注册服务都需要这个地址
      #      defaultZone: http://localhost:7001/eureka
      defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@eureka7001.com:7001/eureka,http://${spring.security.user.name}:${spring.security.user.password}@eureka7002.com:7002/eureka
  instance:
    instance-id: feign-provider-8013
    ## 当调用getHostname获取实例的hostname时,返回ip而不是host名
    prefer-ip-address: true
    # Eureka客户端向服务端发送心跳的时间间隔,单位秒(默认30秒)
    lease-renewal-interval-in-seconds: 10
    # Eureka服务端在收到最后一次心跳后的等待时间上限,单位秒(默认90秒)
    lease-expiration-duration-in-seconds: 30


#mybatis-plus
mybatis-plus:
  mapper-locations: classpath*:mapper/*.xml
  #实体扫描,多个package用逗号或者分号分隔
  typeAliasesPackage: com.akieay.cloud.fuser.entity;
  configuration:
    #是否开启驼峰命名自动映射
    map-underscore-to-camel-case: true
    #全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。
    cache-enabled: false
    #指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法
    call-setters-on-nulls: true
    #指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

logging:
  level:
    root: info

application-two.yml

server:
  port: 8014
  
eureka:
  instance:
    instance-id: feign-provider-8014

主启动

@SpringBootApplication
@EnableEurekaClient
@MapperScan(value = "com.akieay.cloud.fuser.mapper")
public class FeignProviderApplication {

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

业务类,这里只给出 UserController 的详细,其它只是整合了 MyBatis-Plus 框架,并没有具体业务实现。

1604418839658

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    @Autowired
    private UserService userService;

    @Value("${server.port}")
    private String serverPort;

    @PostMapping(value = "/create")
    public CommonResult create(@RequestBody UserVO userVo) {
        UserEntity user = new UserEntity();
        user.setId(userVo.getId());
        user.setUsername(userVo.getUsername());
        user.setAge(userVo.getAge());
        user.setCreateTime(new Date());
        user.setPassword(userVo.getPassword());
        boolean flag = userService.save(user);
        log.info("*****插入结果:" + flag);

        if (flag) {
            return new CommonResult(200, "插入数据库成功, serverPort:" + serverPort, flag);
        } else {
            return new CommonResult(444, "插入数据库失败");
        }
    }

    @GetMapping(value = "/get/{id}")
    public CommonResult<UserVO> getUserById(@PathVariable("id") Long id) {
        UserEntity userEntity = userService.getById(id);
        UserVO userVO = new UserVO();
        userVO.setId(userEntity.getId());
        userVO.setAge(userEntity.getAge());
        userVO.setUsername(userEntity.getUsername());
        userVO.setCreateTime(userEntity.getCreateTime());
        log.info("*****查询结果:" + userEntity);

        if (null != userEntity) {
            return new CommonResult(200, "查询数据成功, serverPort:" + serverPort, userVO);
        } else {
            return new CommonResult(400, "没有对应的记录,查询ID:" + id);
        }
    }

    @PostMapping(value = "/update")
    public CommonResult<Boolean> updateById(@RequestBody UserVO userVo) {
        UserEntity user = new UserEntity();
        user.setId(userVo.getId());
        user.setUsername(userVo.getUsername());
        user.setAge(userVo.getAge());
        user.setPassword(userVo.getPassword());
        boolean flag = userService.updateById(user);
        if (flag) {
            return new CommonResult(200, "修改数据成功,serverPort: " + serverPort, flag);
        } else {
            return new CommonResult(500, "修改数据库失败");
        }
    }

    @GetMapping(value = "/remove/{id}")
    public CommonResult<Long> removeById(@PathVariable("id") Long id) {
        boolean flag = userService.removeById(id);
        if (flag) {
            return new CommonResult(200, "删除成功,serverPort: " + serverPort, id);
        } else {
            return new CommonResult(500, "删除失败");
        }
    }

}

创建两个启动服务,具体细节已经在前面的模块做了介绍,这里就不介绍了

1604418963463

1604418988167

启动这两个服务,并打卡注册中心,即可看到这两个服务已经注册到了注册中心中;

1604419129612

至此 服务提供者创建完成

创建服务消费者

导入 pom 依赖

<!--引入 openfeign-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

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

添加 application.yml 配置文件,注意指定 ribbon 客户端的超时时间【默认等待1秒钟】,一旦服务端业务所需的等待时间超过 1 秒,注意要修改 客户端超时时间。

server:
  port: 80
spring:
  application:
    name: feign-service-concumer
  security:
    # 配置spring security登录用户名和密码
    user:
      name: akieay
      password: 1qaz2wsx

eureka:
  client:
    #表示是否将自己注册进 Eureka Server服务 默认为true
    register-with-eureka: true
    #f是否从Eureka Server抓取已有的注册信息,默认是true。单点无所谓,集群必需设置为true才能配合ribbon使用负载均衡
    fetch-registry: true
    service-url: # 设置与 Eureka Server 交互的地址 查询服务与注册服务都需要这个地址
      #      defaultZone: http://localhost:7001/eureka
      defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@eureka7001.com:7001/eureka,http://${spring.security.user.name}:${spring.security.user.password}@eureka7002.com:7002/eureka
  instance:
    instance-id: feign-concumer-80
    prefer-ip-address: true
    # Eureka客户端向服务端发送心跳的时间间隔,单位秒(默认30秒)
    lease-renewal-interval-in-seconds: 10
    # Eureka服务端在收到最后一次心跳后的等待时间上限,单位秒(默认90秒)
    lease-expiration-duration-in-seconds: 30

logging:
  level:
    #feign日志以什么级别监控哪个接口
    com.akieay.cloud.feign.service.UserService: debug

# 设置服务的 ribbon 配置
FEIGN-SERVICE-PROVIDER:
  ribbon:
    ConnectTimeout: 1000 #服务请求连接超时时间(毫秒)
    ReadTimeout: 3000 #服务请求处理超时时间(毫秒)
    OkToRetryOnAllOperations: true #对超时请求启用重试机制
    MaxAutoRetriesNextServer: 1 #切换重试实例的最大个数
    MaxAutoRetries: 1 # 切换实例后重试最大次数
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡算法-随机

主启动 @RibbonClient 配置 FEIGN-SERVICE-PROVIDER 服务的负载均衡策略

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class FeignConcumerApplication {

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

业务类,@FeignClientFEIGN-SERVICE-PROVIDER 服务绑定到 UserService 接口,其中要保证 接口的返回值、参数、请求路径 要与服务提供者 FEIGN-SERVICE-PROVIDER 中的 UserController 的一致【如下图所示】;注意:若想使 @FeignClient 生效,主启动中的 @EnableFeignClients 配置不可少,它表示开启基于注解的 OpenFeign 功能。

1604420318188

@Component
@RequestMapping("/user")
@FeignClient(value = "FEIGN-SERVICE-PROVIDER")
public interface UserService {

    /**
     * 创建用户
     * @param userVo
     * @return
     */
    @PostMapping(value = "/create")
    CommonResult create(@RequestBody UserVO userVo);

    /**
     * 获取指定id 的用户信息
     * @param id
     * @return
     */
    @GetMapping(value = "/get/{id}")
    CommonResult<UserVO> getUserById(@PathVariable("id") Long id);

    /**
     * 修改指定id 的用户信息
     * @param userVo
     * @return
     */
    @PostMapping(value = "/update")
    CommonResult<Boolean> updateById(@RequestBody UserVO userVo);

    /**
     * 删除指定id 的用户信息
     * @param id
     * @return
     */
    @GetMapping(value = "/remove/{id}")
    CommonResult<Long> removeById(@PathVariable("id") Long id);
}
@RestController
@Slf4j
public class UserController {

    @Resource
    UserService userService;

    @GetMapping("/concumer/user/get/{id}")
    public CommonResult<UserVO> getUser(@PathVariable("id") Long id) {
        CommonResult<UserVO> result = userService.getUserById(id);
        return result;
    }
}

重启服务,访问 Eureka 注册中心,即可看到服务已经注册成功;访问:http://localhost/concumer/user/get/1 可以看到 使用的是我们指定的 随机 的负载均衡策略【而不是默认的 轮询 】来随机获取 8013 与 8014 服务提供者节点的服务。

1604420515450

OpenFeign 超时控制

OpenFeign默认等待1秒钟,超时后会报错;即,若是调用服务提供者提供的方法,超过1秒钟还没得到响应,会立刻抛出异常(哪怕该服务正常调用需要2秒)

1604421436476

解决方案:针对这种情况,可以修改超时时间,在yml添加如下配置【前面的演示中已经添加了配置】:

#设置feign客户端超时时间(openfeign默认支持ribbon)
ribbon:
  #指的是建立连接所用时间,适用于网络正常情况下,两端连接所用的时间
  ReadTimeout: 5000
  #指的是建立连接后从服务器读取到可用资源所用的时间
  ConnectTimeout: 5000
OpenFeign 的日志打印功能

Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Http 请求的细节。说白了就是 对 Feign 接口的调用情况进行监控和输出

日志级别
  • NONE:默认的,不显示任何日志;
  • BASIC:仅记录请求方法、URL、响应状态码及执行时间;
  • HEADERS:除了 BASIC 中定义的信息之外,还有请求头和响应的头信息;
  • FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。
启用日志功能

添加 feign 配置类

@Configuration
public class FeignConfig {

    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}

yml 新增配置

logging:
  level:
    #feign日志以什么级别监控哪个接口
    com.akieay.cloud.feign.service.UserService: debug

重启服务,调用方法即可看到详细的服务调用日志,如下:

1604421916400

常见的应用程序属性

官网:https://docs.spring.io/spring-cloud-openfeign/docs/2.2.5.RELEASE/reference/html/appendix.html

NameDefaultDescription
feign.client.config
feign.client.default-configdefault
feign.client.default-to-propertiestrue
feign.compression.request.enabledfalseEnables the request sent by Feign to be compressed.
feign.compression.request.mime-types[text/xml, application/xml, application/json]The list of supported mime types.
feign.compression.request.min-request-size2048The minimum threshold content size.
feign.compression.response.enabledfalseEnables the response from Feign to be compressed.
feign.compression.response.useGzipDecoderfalseEnables the default gzip decoder to be used.
feign.httpclient.connection-timeout2000
feign.httpclient.connection-timer-repeat3000
feign.httpclient.disable-ssl-validationfalse
feign.httpclient.enabledtrueEnables the use of the Apache HTTP Client by Feign.
feign.httpclient.follow-redirectstrue
feign.httpclient.max-connections200
feign.httpclient.max-connections-per-route50
feign.httpclient.time-to-live900
feign.httpclient.time-to-live-unit
feign.hystrix.enabledfalseIf true, an OpenFeign client will be wrapped with a Hystrix circuit breaker.
feign.okhttp.enabledfalseEnables the use of the OK HTTP Client by Feign.
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值